- Create Economist SubmissionTracking package correctly: * mainArticle = full blog post content * coverLetter = 216-word SIR— letter * Links to blog post via blogPostId - Archive 'Letter to The Economist' from blog posts (it's the cover letter) - Fix date display on article cards (use published_at) - Target publication already displaying via blue badge Database changes: - Make blogPostId optional in SubmissionTracking model - Economist package ID: 68fa85ae49d4900e7f2ecd83 - Le Monde package ID: 68fa2abd2e6acd5691932150 Next: Enhanced modal with tabs, validation, export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
247 lines
6.4 KiB
JavaScript
Executable file
247 lines
6.4 KiB
JavaScript
Executable file
#!/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();
|