- 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>
237 lines
7 KiB
JavaScript
Executable file
237 lines
7 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Action Pattern Tracker
|
|
*
|
|
* Tracks patterns indicating "stuck in loop" situations:
|
|
* - Same file edited multiple times consecutively
|
|
* - Same function/section modified repeatedly
|
|
* - User frustration signals in messages
|
|
*
|
|
* This feeds into:
|
|
* - MetacognitiveVerifier (triggers verification on 3+ failed attempts)
|
|
* - ContextPressureMonitor (elevates pressure on loop detection)
|
|
*
|
|
* Usage:
|
|
* node scripts/track-action-patterns.js --action edit --file path/to/file
|
|
* node scripts/track-action-patterns.js --check
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const SESSION_STATE_PATH = path.join(__dirname, '../.claude/session-state.json');
|
|
const ACTION_PATTERNS_PATH = path.join(__dirname, '../.claude/action-patterns.json');
|
|
|
|
/**
|
|
* Load session state
|
|
*/
|
|
function loadSessionState() {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
|
|
} catch (err) {
|
|
console.error(`Error loading session state: ${err.message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load action patterns (or initialize)
|
|
*/
|
|
function loadActionPatterns() {
|
|
try {
|
|
if (fs.existsSync(ACTION_PATTERNS_PATH)) {
|
|
return JSON.parse(fs.readFileSync(ACTION_PATTERNS_PATH, 'utf8'));
|
|
}
|
|
} catch (err) {
|
|
console.warn(`Warning: Could not load action patterns: ${err.message}`);
|
|
}
|
|
|
|
// Initialize new patterns structure
|
|
return {
|
|
version: "1.0",
|
|
session_id: null,
|
|
started: new Date().toISOString(),
|
|
actions: [],
|
|
patterns: {
|
|
repeated_edits: [],
|
|
same_file_edits: {},
|
|
user_frustration_signals: []
|
|
},
|
|
alerts: [],
|
|
last_updated: new Date().toISOString()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Save action patterns
|
|
*/
|
|
function saveActionPatterns(patterns) {
|
|
patterns.last_updated = new Date().toISOString();
|
|
fs.writeFileSync(ACTION_PATTERNS_PATH, JSON.stringify(patterns, null, 2));
|
|
}
|
|
|
|
/**
|
|
* Track new action
|
|
*/
|
|
function trackAction(actionType, filePath) {
|
|
const patterns = loadActionPatterns();
|
|
const sessionState = loadSessionState();
|
|
|
|
// Update session ID if changed
|
|
if (patterns.session_id !== sessionState.session_id) {
|
|
patterns.session_id = sessionState.session_id;
|
|
patterns.actions = []; // Reset for new session
|
|
patterns.patterns.same_file_edits = {};
|
|
patterns.patterns.repeated_edits = [];
|
|
patterns.alerts = [];
|
|
}
|
|
|
|
// Add action
|
|
const action = {
|
|
type: actionType,
|
|
file: filePath,
|
|
timestamp: new Date().toISOString(),
|
|
message_number: sessionState.message_count
|
|
};
|
|
patterns.actions.push(action);
|
|
|
|
// Track same-file edits
|
|
if (actionType === 'edit' || actionType === 'write') {
|
|
if (!patterns.patterns.same_file_edits[filePath]) {
|
|
patterns.patterns.same_file_edits[filePath] = {
|
|
count: 0,
|
|
timestamps: [],
|
|
alert_threshold: 3
|
|
};
|
|
}
|
|
patterns.patterns.same_file_edits[filePath].count++;
|
|
patterns.patterns.same_file_edits[filePath].timestamps.push(action.timestamp);
|
|
|
|
// Check for loop pattern
|
|
if (patterns.patterns.same_file_edits[filePath].count >= 3) {
|
|
// Check if edits are recent (within last 10 actions)
|
|
const recentActions = patterns.actions.slice(-10);
|
|
const recentEditsOfThisFile = recentActions.filter(a =>
|
|
(a.type === 'edit' || a.type === 'write') && a.file === filePath
|
|
);
|
|
|
|
if (recentEditsOfThisFile.length >= 3) {
|
|
const alert = {
|
|
type: 'repeated_file_edit',
|
|
severity: 'MEDIUM',
|
|
file: filePath,
|
|
count: patterns.patterns.same_file_edits[filePath].count,
|
|
message: `File edited ${patterns.patterns.same_file_edits[filePath].count} times - possible stuck loop`,
|
|
timestamp: new Date().toISOString(),
|
|
recommendation: 'Run MetacognitiveVerifier to assess current approach'
|
|
};
|
|
patterns.alerts.push(alert);
|
|
|
|
console.log(`⚠️ ALERT: Repeated edits to ${path.basename(filePath)}`);
|
|
console.log(` Count: ${patterns.patterns.same_file_edits[filePath].count}`);
|
|
console.log(` Recommendation: Pause and verify approach`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep only last 100 actions
|
|
if (patterns.actions.length > 100) {
|
|
patterns.actions = patterns.actions.slice(-100);
|
|
}
|
|
|
|
saveActionPatterns(patterns);
|
|
|
|
return patterns;
|
|
}
|
|
|
|
/**
|
|
* Check for active patterns/alerts
|
|
*/
|
|
function checkPatterns() {
|
|
const patterns = loadActionPatterns();
|
|
const activeAlerts = patterns.alerts.filter(alert => {
|
|
// Consider alerts from last hour as active
|
|
const alertTime = new Date(alert.timestamp);
|
|
const hourAgo = new Date(Date.now() - 60 * 60 * 1000);
|
|
return alertTime > hourAgo;
|
|
});
|
|
|
|
if (activeAlerts.length === 0) {
|
|
console.log('✅ No active pattern alerts');
|
|
return { hasAlerts: false, alerts: [] };
|
|
}
|
|
|
|
console.log(`⚠️ ${activeAlerts.length} active alert(s):`);
|
|
activeAlerts.forEach((alert, i) => {
|
|
console.log(`\n${i + 1}. [${alert.severity}] ${alert.type}`);
|
|
console.log(` ${alert.message}`);
|
|
console.log(` Recommendation: ${alert.recommendation}`);
|
|
});
|
|
|
|
return { hasAlerts: true, alerts: activeAlerts };
|
|
}
|
|
|
|
/**
|
|
* Get pattern summary
|
|
*/
|
|
function getPatternSummary() {
|
|
const patterns = loadActionPatterns();
|
|
|
|
const summary = {
|
|
total_actions: patterns.actions.length,
|
|
files_edited_multiple_times: Object.keys(patterns.patterns.same_file_edits).filter(
|
|
file => patterns.patterns.same_file_edits[file].count >= 2
|
|
),
|
|
active_alerts: patterns.alerts.filter(alert => {
|
|
const alertTime = new Date(alert.timestamp);
|
|
const hourAgo = new Date(Date.now() - 60 * 60 * 1000);
|
|
return alertTime > hourAgo;
|
|
}).length
|
|
};
|
|
|
|
return summary;
|
|
}
|
|
|
|
/**
|
|
* Main
|
|
*/
|
|
function main() {
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.includes('--check')) {
|
|
checkPatterns();
|
|
} else if (args.includes('--summary')) {
|
|
const summary = getPatternSummary();
|
|
console.log('Action Pattern Summary:');
|
|
console.log(` Total actions: ${summary.total_actions}`);
|
|
console.log(` Files with multiple edits: ${summary.files_edited_multiple_times.length}`);
|
|
if (summary.files_edited_multiple_times.length > 0) {
|
|
summary.files_edited_multiple_times.forEach(file => {
|
|
console.log(` - ${path.basename(file)}`);
|
|
});
|
|
}
|
|
console.log(` Active alerts: ${summary.active_alerts}`);
|
|
} else if (args.includes('--action')) {
|
|
const actionIndex = args.indexOf('--action');
|
|
const fileIndex = args.indexOf('--file');
|
|
|
|
if (actionIndex === -1 || fileIndex === -1) {
|
|
console.error('Usage: --action <type> --file <path>');
|
|
process.exit(1);
|
|
}
|
|
|
|
const actionType = args[actionIndex + 1];
|
|
const filePath = args[fileIndex + 1];
|
|
|
|
trackAction(actionType, filePath);
|
|
console.log(`✅ Tracked: ${actionType} on ${path.basename(filePath)}`);
|
|
} else {
|
|
console.log('Usage:');
|
|
console.log(' --action <type> --file <path> Track an action');
|
|
console.log(' --check Check for active alerts');
|
|
console.log(' --summary Show pattern summary');
|
|
}
|
|
}
|
|
|
|
main();
|