#!/usr/bin/env node /** * User Suggestion Tracker * * Tracks user technical hypotheses and debugging suggestions so that: * - MetacognitiveVerifier can check if user hypothesis was tested * - BoundaryEnforcer can flag ignoring user expertise * - CrossReferenceValidator can match actions against suggestions * * Implements inst_049: "Test user hypothesis first" * * Usage: * node scripts/track-user-suggestions.js --add "user hypothesis text" * node scripts/track-user-suggestions.js --mark-tested "hypothesis id" * node scripts/track-user-suggestions.js --check-untested * * Copyright 2025 Tractatus Project * Licensed under Apache License 2.0 */ const fs = require('fs'); const path = require('path'); const SUGGESTIONS_PATH = path.join(__dirname, '../.claude/user-suggestions.json'); const SESSION_STATE_PATH = path.join(__dirname, '../.claude/session-state.json'); /** * Load user suggestions */ function loadSuggestions() { try { if (fs.existsSync(SUGGESTIONS_PATH)) { return JSON.parse(fs.readFileSync(SUGGESTIONS_PATH, 'utf8')); } } catch (err) { console.warn(`Warning: Could not load suggestions: ${err.message}`); } return { version: "1.0", session_id: null, suggestions: [], last_updated: new Date().toISOString() }; } /** * Save user suggestions */ function saveSuggestions(data) { data.last_updated = new Date().toISOString(); fs.writeFileSync(SUGGESTIONS_PATH, JSON.stringify(data, null, 2)); } /** * Get current session ID */ function getCurrentSessionId() { try { const sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8')); return sessionState.session_id; } catch (err) { return 'unknown'; } } /** * Add new user suggestion/hypothesis */ function addSuggestion(text) { const data = loadSuggestions(); const sessionId = getCurrentSessionId(); // Reset if new session if (data.session_id !== sessionId) { data.session_id = sessionId; data.suggestions = []; } // Extract key phrases that indicate technical hypothesis const hypothesisIndicators = [ 'could be', 'might be', 'issue', 'problem', 'try', 'check', 'examine', 'look at', 'debug', 'test' ]; const isHypothesis = hypothesisIndicators.some(indicator => text.toLowerCase().includes(indicator) ); const suggestion = { id: `sugg_${Date.now()}`, text: text, timestamp: new Date().toISOString(), tested: false, result: null, is_hypothesis: isHypothesis, priority: isHypothesis ? 'HIGH' : 'MEDIUM' }; data.suggestions.push(suggestion); saveSuggestions(data); console.log(`āœ… Tracked user suggestion: ${suggestion.id}`); if (isHypothesis) { console.log(` šŸ“‹ Marked as HYPOTHESIS - should test before alternatives`); } return suggestion; } /** * Mark suggestion as tested */ function markTested(suggestionId, result = null) { const data = loadSuggestions(); const suggestion = data.suggestions.find(s => s.id === suggestionId); if (!suggestion) { console.error(`Error: Suggestion ${suggestionId} not found`); return null; } suggestion.tested = true; suggestion.result = result; suggestion.tested_at = new Date().toISOString(); saveSuggestions(data); console.log(`āœ… Marked suggestion as tested: ${suggestionId}`); if (result) { console.log(` Result: ${result}`); } return suggestion; } /** * Check for untested hypotheses */ function checkUntested() { const data = loadSuggestions(); const untested = data.suggestions.filter(s => !s.tested && s.is_hypothesis); if (untested.length === 0) { console.log('āœ… All user hypotheses have been tested'); return { hasUntested: false, untested: [] }; } console.log(`āš ļø ${untested.length} untested user hypothesis(es):`); untested.forEach((sugg, i) => { console.log(`\n${i + 1}. [${sugg.priority}] ${sugg.id}`); console.log(` "${sugg.text}"`); console.log(` Suggested: ${new Date(sugg.timestamp).toLocaleString()}`); }); console.log('\nšŸ’” inst_049: Test user hypotheses BEFORE pursuing alternatives'); return { hasUntested: true, untested }; } /** * Get suggestion summary */ function getSummary() { const data = loadSuggestions(); const summary = { total: data.suggestions.length, hypotheses: data.suggestions.filter(s => s.is_hypothesis).length, tested: data.suggestions.filter(s => s.tested).length, untested: data.suggestions.filter(s => !s.tested).length, untested_hypotheses: data.suggestions.filter(s => !s.tested && s.is_hypothesis).length }; return summary; } /** * Display summary */ function displaySummary() { const summary = getSummary(); console.log('User Suggestion Summary:'); console.log(` Total suggestions: ${summary.total}`); console.log(` Hypotheses: ${summary.hypotheses}`); console.log(` Tested: ${summary.tested}`); console.log(` Untested: ${summary.untested}`); if (summary.untested_hypotheses > 0) { console.log(`\n āš ļø ${summary.untested_hypotheses} untested hypothesis(es) - violation risk`); } else { console.log(`\n āœ… All hypotheses tested`); } } /** * Main */ function main() { const args = process.argv.slice(2); if (args.length === 0 || args.includes('--help')) { console.log('User Suggestion Tracker'); console.log('\nUsage:'); console.log(' --add "text" Add user suggestion/hypothesis'); console.log(' --mark-tested ID [result] Mark suggestion as tested'); console.log(' --check-untested Check for untested hypotheses'); console.log(' --summary Show summary statistics'); process.exit(0); } if (args.includes('--add')) { const index = args.indexOf('--add'); const text = args[index + 1]; if (!text) { console.error('Error: --add requires suggestion text'); process.exit(1); } addSuggestion(text); } else if (args.includes('--mark-tested')) { const index = args.indexOf('--mark-tested'); const id = args[index + 1]; const result = args[index + 2] || null; if (!id) { console.error('Error: --mark-tested requires suggestion ID'); process.exit(1); } markTested(id, result); } else if (args.includes('--check-untested')) { const result = checkUntested(); process.exit(result.hasUntested ? 1 : 0); } else if (args.includes('--summary')) { displaySummary(); } } main();