diff --git a/CLAUDE.md b/CLAUDE.md index bc1852e7..70ef6859 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,6 +63,30 @@ node scripts/framework-audit-response.js \ **See**: inst_078 in instruction-history.json +## šŸ” FRAMEWORK TRIGGER: "ffs" + +When user types **ffs**, display full framework statistics: + +```bash +node scripts/framework-stats.js +``` + +**Purpose**: On-demand visibility into framework operational metrics during session + +**Reports**: +- Session state (ID, message count, status) +- Token usage & checkpoints (25%, 50%, 75%) +- Context pressure level & metrics +- Instruction counts (by quadrant/persistence) +- Audit log counts (by service) +- Framework service status (all 6 services) + +**Output**: Formatted report + JSON for programmatic access + +**When**: User wants to see framework health/activity at any point in session + +**See**: inst_082 in instruction-history.json + --- ## šŸŽÆ QUICK REFERENCE diff --git a/scripts/add-inst-082-ffs-trigger.js b/scripts/add-inst-082-ffs-trigger.js new file mode 100755 index 00000000..b1e718dd --- /dev/null +++ b/scripts/add-inst-082-ffs-trigger.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +/** + * Add inst_082: "ffs" Framework Stats Trigger + * Adds instruction about the "ffs" codeword for viewing framework statistics + */ + +const fs = require('fs'); +const path = require('path'); + +const INSTRUCTION_FILE = path.join(__dirname, '../.claude/instruction-history.json'); + +// Load current instruction history +const history = JSON.parse(fs.readFileSync(INSTRUCTION_FILE, 'utf8')); + +// New instruction +const newInstruction = { + "id": "inst_082", + "text": "When user types 'ffs' (Full Framework Stats), invoke framework-stats.js script to display comprehensive session statistics. Usage: node scripts/framework-stats.js. Reports: session state, token usage & checkpoints, context pressure level, instruction counts by quadrant/persistence, audit log counts by service, framework service status. Output formatted report + JSON for programmatic access.", + "timestamp": new Date().toISOString(), + "quadrant": "SYSTEM", + "persistence": "HIGH", + "temporal_scope": "PROJECT", + "verification_required": "OPTIONAL", + "explicitness": 0.95, + "source": "user", + "session_id": "2025-10-25-ffs-trigger-implementation", + "parameters": { + "trigger": "ffs", + "script": "scripts/framework-stats.js", + "purpose": "framework_statistics_display", + "reports": [ + "session_state", + "token_usage", + "context_pressure", + "instruction_stats", + "audit_logs", + "service_status" + ] + }, + "active": true, + "notes": "Complements 'ff' trigger (inst_078). Provides on-demand visibility into framework operational metrics." +}; + +// Check if inst_082 already exists +const existingIndex = history.instructions.findIndex(i => i.id === 'inst_082'); + +if (existingIndex >= 0) { + console.log('āš ļø inst_082 already exists - updating...'); + history.instructions[existingIndex] = newInstruction; +} else { + console.log('āœ… Adding new instruction: inst_082'); + history.instructions.push(newInstruction); +} + +// Update metadata +history.version = "4.1"; +history.last_updated = new Date().toISOString(); + +// Write back +fs.writeFileSync(INSTRUCTION_FILE, JSON.stringify(history, null, 2)); + +console.log('āœ… inst_082 added successfully'); +console.log('šŸ“„ File:', INSTRUCTION_FILE); +console.log('šŸ“Š Total instructions:', history.instructions.length); +console.log('\nInstruction content:'); +console.log(' ID:', newInstruction.id); +console.log(' Trigger:', newInstruction.parameters.trigger); +console.log(' Script:', newInstruction.parameters.script); +console.log(' Persistence:', newInstruction.persistence); +console.log(' Quadrant:', newInstruction.quadrant); diff --git a/scripts/framework-stats.js b/scripts/framework-stats.js new file mode 100755 index 00000000..cff01fd0 --- /dev/null +++ b/scripts/framework-stats.js @@ -0,0 +1,307 @@ +#!/usr/bin/env node + +/** + * Framework Statistics Reporter + * Displays comprehensive framework statistics for current session + * + * Triggered by "ffs" (Full Framework Stats) codeword + * + * Reports: + * - Session context pressure and token usage + * - Framework service invocation counts + * - Recent audit log entries (last 10) + * - Active instruction statistics + * - Service health/initialization status + */ + +const mongoose = require('mongoose'); +const path = require('path'); +const fs = require('fs'); + +// Load environment +require('dotenv').config(); + +// Framework services +const BoundaryEnforcer = require('../src/services/BoundaryEnforcer.service'); +const MetacognitiveVerifier = require('../src/services/MetacognitiveVerifier.service'); +const ContextPressureMonitor = require('../src/services/ContextPressureMonitor.service'); +const CrossReferenceValidator = require('../src/services/CrossReferenceValidator.service'); +const InstructionPersistenceClassifier = require('../src/services/InstructionPersistenceClassifier.service'); +const PluralisticDeliberationOrchestrator = require('../src/services/PluralisticDeliberationOrchestrator.service'); + +async function getSessionStats() { + const sessionStatePath = path.join(__dirname, '../.claude/session-state.json'); + if (fs.existsSync(sessionStatePath)) { + return JSON.parse(fs.readFileSync(sessionStatePath, 'utf8')); + } + return null; +} + +async function getTokenCheckpoints() { + const checkpointPath = path.join(__dirname, '../.claude/token-checkpoints.json'); + if (fs.existsSync(checkpointPath)) { + return JSON.parse(fs.readFileSync(checkpointPath, 'utf8')); + } + return null; +} + +async function getInstructionStats() { + const instructionPath = path.join(__dirname, '../.claude/instruction-history.json'); + if (fs.existsSync(instructionPath)) { + const history = JSON.parse(fs.readFileSync(instructionPath, 'utf8')); + + const active = history.instructions.filter(i => i.active); + const byQuadrant = active.reduce((acc, i) => { + acc[i.quadrant] = (acc[i.quadrant] || 0) + 1; + return acc; + }, {}); + + const byPersistence = active.reduce((acc, i) => { + acc[i.persistence] = (acc[i.persistence] || 0) + 1; + return acc; + }, {}); + + return { + total: history.instructions.length, + active: active.length, + inactive: history.instructions.length - active.length, + byQuadrant, + byPersistence, + version: history.version, + lastUpdated: history.last_updated + }; + } + return null; +} + +async function getAuditLogStats() { + try { + const db = require('../src/utils/db.util'); + const collection = await db.getCollection('auditLogs'); + + // Get total count + const total = await collection.countDocuments(); + + // Get counts by service + const byService = await collection.aggregate([ + { $group: { _id: '$service', count: { $sum: 1 } } }, + { $sort: { count: -1 } } + ]).toArray(); + + // Get recent entries (last 10) + const recent = await collection.find() + .sort({ timestamp: -1 }) + .limit(10) + .toArray(); + + // Get today's count + const todayStart = new Date(); + todayStart.setHours(0, 0, 0, 0); + const todayCount = await collection.countDocuments({ + timestamp: { $gte: todayStart } + }); + + return { + total, + todayCount, + byService: byService.reduce((acc, s) => { + acc[s._id] = s.count; + return acc; + }, {}), + recent: recent.map(r => ({ + service: r.service, + action: r.action, + decision: r.decision, + timestamp: r.timestamp, + sessionId: r.context?.sessionId + })) + }; + } catch (error) { + console.error('Error fetching audit logs:', error.message); + return null; + } +} + +async function main() { + try { + // Connect to MongoDB + await mongoose.connect('mongodb://localhost:27017/tractatus_dev', { + serverSelectionTimeoutMS: 2000 + }); + + // Initialize framework services + await BoundaryEnforcer.initialize(); + await MetacognitiveVerifier.initialize(); + await ContextPressureMonitor.initialize('framework-stats'); + await CrossReferenceValidator.initialize(); + await InstructionPersistenceClassifier.initialize(); + await PluralisticDeliberationOrchestrator.initialize(); + + // Gather statistics + const [sessionStats, tokenCheckpoints, instructionStats, auditStats] = await Promise.all([ + getSessionStats(), + getTokenCheckpoints(), + getInstructionStats(), + getAuditLogStats() + ]); + + // Get current pressure stats + const monitorStats = ContextPressureMonitor.getStats(); + const pressureHistory = ContextPressureMonitor.getPressureHistory(); + const latestPressure = pressureHistory && pressureHistory.length > 0 + ? pressureHistory[pressureHistory.length - 1] + : null; + + // Build report + const report = { + timestamp: new Date().toISOString(), + session: sessionStats ? { + sessionId: sessionStats.sessionId, + startTime: sessionStats.startTime, + messageCount: sessionStats.messageCount, + status: sessionStats.status + } : null, + + tokenUsage: tokenCheckpoints ? { + budget: tokenCheckpoints.tokenBudget, + checkpoints: tokenCheckpoints.checkpoints, + nextCheckpoint: tokenCheckpoints.checkpoints.find(c => !c.reached) + } : null, + + contextPressure: latestPressure ? { + level: latestPressure.pressureLevel?.name || 'UNKNOWN', + score: latestPressure.overallScore, + timestamp: latestPressure.timestamp, + metrics: latestPressure.metrics + } : (monitorStats ? { + level: 'UNKNOWN', + score: 0, + stats: monitorStats + } : null), + + instructions: instructionStats, + + auditLogs: auditStats ? { + total: auditStats.total, + today: auditStats.todayCount, + byService: auditStats.byService, + recentCount: auditStats.recent.length + } : null, + + frameworkServices: { + BoundaryEnforcer: 'ACTIVE', + MetacognitiveVerifier: 'ACTIVE', + ContextPressureMonitor: 'ACTIVE', + CrossReferenceValidator: 'ACTIVE', + InstructionPersistenceClassifier: 'ACTIVE', + PluralisticDeliberationOrchestrator: 'ACTIVE' + } + }; + + // Output formatted report + console.log('\n╔════════════════════════════════════════════════════════════════╗'); + console.log('ā•‘ TRACTATUS FRAMEWORK STATISTICS (ffs) ā•‘'); + console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n'); + + // Session Info + if (report.session) { + console.log('šŸ“Š SESSION'); + console.log(` Session ID: ${report.session.sessionId}`); + console.log(` Start Time: ${new Date(report.session.startTime).toLocaleString()}`); + console.log(` Message Count: ${report.session.messageCount}`); + console.log(` Status: ${report.session.status}`); + console.log(); + } + + // Token Usage + if (report.tokenUsage && report.tokenUsage.budget) { + const next = report.tokenUsage.nextCheckpoint; + console.log('šŸŽÆ TOKEN BUDGET'); + console.log(` Total Budget: ${report.tokenUsage.budget.toLocaleString()}`); + if (next) { + console.log(` Next Checkpoint: ${next.threshold.toLocaleString()} (${next.percentage}%)`); + } + const reached = report.tokenUsage.checkpoints.filter(c => c.reached).length; + console.log(` Checkpoints: ${reached}/${report.tokenUsage.checkpoints.length} reached`); + console.log(); + } + + // Context Pressure + if (report.contextPressure) { + console.log('āš ļø CONTEXT PRESSURE'); + console.log(` Level: ${report.contextPressure.level}`); + console.log(` Overall Score: ${report.contextPressure.score}%`); + if (report.contextPressure.timestamp) { + console.log(` Last Updated: ${new Date(report.contextPressure.timestamp).toLocaleString()}`); + } + if (report.contextPressure.metrics) { + console.log(' Metrics:'); + Object.entries(report.contextPressure.metrics).forEach(([metric, data]) => { + if (data && typeof data === 'object' && 'normalized' in data) { + console.log(` • ${metric}: ${(data.normalized * 100).toFixed(1)}%`); + } + }); + } + if (report.contextPressure.stats) { + console.log(' Analysis Count:', report.contextPressure.stats.analysis_count); + } + console.log(); + } + + // Instruction Stats + if (report.instructions) { + console.log('šŸ“‹ INSTRUCTIONS'); + console.log(` Total: ${report.instructions.total}`); + console.log(` Active: ${report.instructions.active}`); + console.log(` Inactive: ${report.instructions.inactive}`); + console.log(` Version: ${report.instructions.version}`); + console.log(' By Quadrant:'); + Object.entries(report.instructions.byQuadrant).forEach(([quad, count]) => { + console.log(` • ${quad}: ${count}`); + }); + console.log(' By Persistence:'); + Object.entries(report.instructions.byPersistence).forEach(([pers, count]) => { + console.log(` • ${pers}: ${count}`); + }); + console.log(); + } + + // Audit Logs + if (report.auditLogs) { + console.log('šŸ“ AUDIT LOGS'); + console.log(` Total Decisions: ${report.auditLogs.total}`); + console.log(` Today: ${report.auditLogs.today}`); + console.log(' By Service:'); + Object.entries(report.auditLogs.byService) + .sort((a, b) => b[1] - a[1]) + .forEach(([service, count]) => { + console.log(` • ${service}: ${count}`); + }); + console.log(); + } + + // Framework Services + console.log('šŸ”§ FRAMEWORK SERVICES'); + Object.entries(report.frameworkServices).forEach(([service, status]) => { + const icon = status === 'ACTIVE' ? 'āœ“' : 'āœ—'; + console.log(` ${icon} ${service}: ${status}`); + }); + console.log(); + + console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n'); + + // Also output JSON for programmatic access + console.log('\n// JSON OUTPUT FOR PROGRAMMATIC ACCESS:'); + console.log(JSON.stringify(report, null, 2)); + + await mongoose.disconnect(); + process.exit(0); + + } catch (error) { + console.error('Error generating framework statistics:', error); + await mongoose.disconnect(); + process.exit(1); + } +} + +main();