feat(claude-flow): Init claude-flow v3, pretrain on repo, update CLAUDE.md

- Run npx @claude-flow/cli@latest init --force: 115 files created
  (agents, commands, helpers, skills, settings, MCP config)
- Initialize memory.db (147 KB): 84 files analyzed, 30 patterns
  extracted, 46 trajectories evaluated via 4-step RETRIEVE/JUDGE/DISTILL/CONSOLIDATE
- Run pretraining with MoE model: hyperbolic Poincaré embeddings,
  3 contradictions resolved, all-MiniLM-L6-v2 ONNX embedding index
- Include .claude/memory.db and .claude-flow/metrics/learning.json in
  repo for team sharing (semantic search available to all contributors)
- Update CLAUDE.md: add wifi-densepose project context, key crates,
  ruvector integration map, correct build/test commands for this repo,
  ADR cross-reference (ADR-014 through ADR-017)

https://claude.ai/code/session_01BSBAQJ34SLkiJy4A8SoiL4
This commit is contained in:
Claude
2026-02-28 16:06:55 +00:00
parent 0e7e01c649
commit 6c931b826f
57 changed files with 4637 additions and 1181 deletions

View File

@@ -0,0 +1,350 @@
#!/usr/bin/env node
/**
* Auto Memory Bridge Hook (ADR-048/049)
*
* Wires AutoMemoryBridge + LearningBridge + MemoryGraph into Claude Code
* session lifecycle. Called by settings.json SessionStart/SessionEnd hooks.
*
* Usage:
* node auto-memory-hook.mjs import # SessionStart: import auto memory files into backend
* node auto-memory-hook.mjs sync # SessionEnd: sync insights back to MEMORY.md
* node auto-memory-hook.mjs status # Show bridge status
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const PROJECT_ROOT = join(__dirname, '../..');
const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
// Colors
const GREEN = '\x1b[0;32m';
const CYAN = '\x1b[0;36m';
const DIM = '\x1b[2m';
const RESET = '\x1b[0m';
const log = (msg) => console.log(`${CYAN}[AutoMemory] ${msg}${RESET}`);
const success = (msg) => console.log(`${GREEN}[AutoMemory] ✓ ${msg}${RESET}`);
const dim = (msg) => console.log(` ${DIM}${msg}${RESET}`);
// Ensure data dir
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
// ============================================================================
// Simple JSON File Backend (implements IMemoryBackend interface)
// ============================================================================
class JsonFileBackend {
constructor(filePath) {
this.filePath = filePath;
this.entries = new Map();
}
async initialize() {
if (existsSync(this.filePath)) {
try {
const data = JSON.parse(readFileSync(this.filePath, 'utf-8'));
if (Array.isArray(data)) {
for (const entry of data) this.entries.set(entry.id, entry);
}
} catch { /* start fresh */ }
}
}
async shutdown() { this._persist(); }
async store(entry) { this.entries.set(entry.id, entry); this._persist(); }
async get(id) { return this.entries.get(id) ?? null; }
async getByKey(key, ns) {
for (const e of this.entries.values()) {
if (e.key === key && (!ns || e.namespace === ns)) return e;
}
return null;
}
async update(id, updates) {
const e = this.entries.get(id);
if (!e) return null;
if (updates.metadata) Object.assign(e.metadata, updates.metadata);
if (updates.content !== undefined) e.content = updates.content;
if (updates.tags) e.tags = updates.tags;
e.updatedAt = Date.now();
this._persist();
return e;
}
async delete(id) { return this.entries.delete(id); }
async query(opts) {
let results = [...this.entries.values()];
if (opts?.namespace) results = results.filter(e => e.namespace === opts.namespace);
if (opts?.type) results = results.filter(e => e.type === opts.type);
if (opts?.limit) results = results.slice(0, opts.limit);
return results;
}
async search() { return []; } // No vector search in JSON backend
async bulkInsert(entries) { for (const e of entries) this.entries.set(e.id, e); this._persist(); }
async bulkDelete(ids) { let n = 0; for (const id of ids) { if (this.entries.delete(id)) n++; } this._persist(); return n; }
async count() { return this.entries.size; }
async listNamespaces() {
const ns = new Set();
for (const e of this.entries.values()) ns.add(e.namespace || 'default');
return [...ns];
}
async clearNamespace(ns) {
let n = 0;
for (const [id, e] of this.entries) {
if (e.namespace === ns) { this.entries.delete(id); n++; }
}
this._persist();
return n;
}
async getStats() {
return {
totalEntries: this.entries.size,
entriesByNamespace: {},
entriesByType: { semantic: 0, episodic: 0, procedural: 0, working: 0, cache: 0 },
memoryUsage: 0, avgQueryTime: 0, avgSearchTime: 0,
};
}
async healthCheck() {
return {
status: 'healthy',
components: {
storage: { status: 'healthy', latency: 0 },
index: { status: 'healthy', latency: 0 },
cache: { status: 'healthy', latency: 0 },
},
timestamp: Date.now(), issues: [], recommendations: [],
};
}
_persist() {
try {
writeFileSync(this.filePath, JSON.stringify([...this.entries.values()], null, 2), 'utf-8');
} catch { /* best effort */ }
}
}
// ============================================================================
// Resolve memory package path (local dev or npm installed)
// ============================================================================
async function loadMemoryPackage() {
// Strategy 1: Local dev (built dist)
const localDist = join(PROJECT_ROOT, 'v3/@claude-flow/memory/dist/index.js');
if (existsSync(localDist)) {
try {
return await import(`file://${localDist}`);
} catch { /* fall through */ }
}
// Strategy 2: npm installed @claude-flow/memory
try {
return await import('@claude-flow/memory');
} catch { /* fall through */ }
// Strategy 3: Installed via @claude-flow/cli which includes memory
const cliMemory = join(PROJECT_ROOT, 'node_modules/@claude-flow/memory/dist/index.js');
if (existsSync(cliMemory)) {
try {
return await import(`file://${cliMemory}`);
} catch { /* fall through */ }
}
return null;
}
// ============================================================================
// Read config from .claude-flow/config.yaml
// ============================================================================
function readConfig() {
const configPath = join(PROJECT_ROOT, '.claude-flow', 'config.yaml');
const defaults = {
learningBridge: { enabled: true, sonaMode: 'balanced', confidenceDecayRate: 0.005, accessBoostAmount: 0.03, consolidationThreshold: 10 },
memoryGraph: { enabled: true, pageRankDamping: 0.85, maxNodes: 5000, similarityThreshold: 0.8 },
agentScopes: { enabled: true, defaultScope: 'project' },
};
if (!existsSync(configPath)) return defaults;
try {
const yaml = readFileSync(configPath, 'utf-8');
// Simple YAML parser for the memory section
const getBool = (key) => {
const match = yaml.match(new RegExp(`${key}:\\s*(true|false)`, 'i'));
return match ? match[1] === 'true' : undefined;
};
const lbEnabled = getBool('learningBridge[\\s\\S]*?enabled');
if (lbEnabled !== undefined) defaults.learningBridge.enabled = lbEnabled;
const mgEnabled = getBool('memoryGraph[\\s\\S]*?enabled');
if (mgEnabled !== undefined) defaults.memoryGraph.enabled = mgEnabled;
const asEnabled = getBool('agentScopes[\\s\\S]*?enabled');
if (asEnabled !== undefined) defaults.agentScopes.enabled = asEnabled;
return defaults;
} catch {
return defaults;
}
}
// ============================================================================
// Commands
// ============================================================================
async function doImport() {
log('Importing auto memory files into bridge...');
const memPkg = await loadMemoryPackage();
if (!memPkg || !memPkg.AutoMemoryBridge) {
dim('Memory package not available — skipping auto memory import');
return;
}
const config = readConfig();
const backend = new JsonFileBackend(STORE_PATH);
await backend.initialize();
const bridgeConfig = {
workingDir: PROJECT_ROOT,
syncMode: 'on-session-end',
};
// Wire learning if enabled and available
if (config.learningBridge.enabled && memPkg.LearningBridge) {
bridgeConfig.learning = {
sonaMode: config.learningBridge.sonaMode,
confidenceDecayRate: config.learningBridge.confidenceDecayRate,
accessBoostAmount: config.learningBridge.accessBoostAmount,
consolidationThreshold: config.learningBridge.consolidationThreshold,
};
}
// Wire graph if enabled and available
if (config.memoryGraph.enabled && memPkg.MemoryGraph) {
bridgeConfig.graph = {
pageRankDamping: config.memoryGraph.pageRankDamping,
maxNodes: config.memoryGraph.maxNodes,
similarityThreshold: config.memoryGraph.similarityThreshold,
};
}
const bridge = new memPkg.AutoMemoryBridge(backend, bridgeConfig);
try {
const result = await bridge.importFromAutoMemory();
success(`Imported ${result.imported} entries (${result.skipped} skipped)`);
dim(`├─ Backend entries: ${await backend.count()}`);
dim(`├─ Learning: ${config.learningBridge.enabled ? 'active' : 'disabled'}`);
dim(`├─ Graph: ${config.memoryGraph.enabled ? 'active' : 'disabled'}`);
dim(`└─ Agent scopes: ${config.agentScopes.enabled ? 'active' : 'disabled'}`);
} catch (err) {
dim(`Import failed (non-critical): ${err.message}`);
}
await backend.shutdown();
}
async function doSync() {
log('Syncing insights to auto memory files...');
const memPkg = await loadMemoryPackage();
if (!memPkg || !memPkg.AutoMemoryBridge) {
dim('Memory package not available — skipping sync');
return;
}
const config = readConfig();
const backend = new JsonFileBackend(STORE_PATH);
await backend.initialize();
const entryCount = await backend.count();
if (entryCount === 0) {
dim('No entries to sync');
await backend.shutdown();
return;
}
const bridgeConfig = {
workingDir: PROJECT_ROOT,
syncMode: 'on-session-end',
};
if (config.learningBridge.enabled && memPkg.LearningBridge) {
bridgeConfig.learning = {
sonaMode: config.learningBridge.sonaMode,
confidenceDecayRate: config.learningBridge.confidenceDecayRate,
consolidationThreshold: config.learningBridge.consolidationThreshold,
};
}
if (config.memoryGraph.enabled && memPkg.MemoryGraph) {
bridgeConfig.graph = {
pageRankDamping: config.memoryGraph.pageRankDamping,
maxNodes: config.memoryGraph.maxNodes,
};
}
const bridge = new memPkg.AutoMemoryBridge(backend, bridgeConfig);
try {
const syncResult = await bridge.syncToAutoMemory();
success(`Synced ${syncResult.synced} entries to auto memory`);
dim(`├─ Categories updated: ${syncResult.categories?.join(', ') || 'none'}`);
dim(`└─ Backend entries: ${entryCount}`);
// Curate MEMORY.md index with graph-aware ordering
await bridge.curateIndex();
success('Curated MEMORY.md index');
} catch (err) {
dim(`Sync failed (non-critical): ${err.message}`);
}
if (bridge.destroy) bridge.destroy();
await backend.shutdown();
}
async function doStatus() {
const memPkg = await loadMemoryPackage();
const config = readConfig();
console.log('\n=== Auto Memory Bridge Status ===\n');
console.log(` Package: ${memPkg ? '✅ Available' : '❌ Not found'}`);
console.log(` Store: ${existsSync(STORE_PATH) ? '✅ ' + STORE_PATH : '⏸ Not initialized'}`);
console.log(` LearningBridge: ${config.learningBridge.enabled ? '✅ Enabled' : '⏸ Disabled'}`);
console.log(` MemoryGraph: ${config.memoryGraph.enabled ? '✅ Enabled' : '⏸ Disabled'}`);
console.log(` AgentScopes: ${config.agentScopes.enabled ? '✅ Enabled' : '⏸ Disabled'}`);
if (existsSync(STORE_PATH)) {
try {
const data = JSON.parse(readFileSync(STORE_PATH, 'utf-8'));
console.log(` Entries: ${Array.isArray(data) ? data.length : 0}`);
} catch { /* ignore */ }
}
console.log('');
}
// ============================================================================
// Main
// ============================================================================
const command = process.argv[2] || 'status';
try {
switch (command) {
case 'import': await doImport(); break;
case 'sync': await doSync(); break;
case 'status': await doStatus(); break;
default:
console.log('Usage: auto-memory-hook.mjs <import|sync|status>');
process.exit(1);
}
} catch (err) {
// Hooks must never crash Claude Code - fail silently
dim(`Error (non-critical): ${err.message}`);
}

View File

@@ -57,7 +57,7 @@ is_running() {
# Start the swarm monitor daemon
start_swarm_monitor() {
local interval="${1:-3}"
local interval="${1:-30}"
if is_running "$SWARM_MONITOR_PID"; then
log "Swarm monitor already running (PID: $(cat "$SWARM_MONITOR_PID"))"
@@ -78,7 +78,7 @@ start_swarm_monitor() {
# Start the metrics update daemon
start_metrics_daemon() {
local interval="${1:-30}" # Default 30 seconds for V3 sync
local interval="${1:-60}" # Default 60 seconds - less frequent updates
if is_running "$METRICS_DAEMON_PID"; then
log "Metrics daemon already running (PID: $(cat "$METRICS_DAEMON_PID"))"
@@ -126,8 +126,8 @@ stop_daemon() {
# Start all daemons
start_all() {
log "Starting all Claude Flow daemons..."
start_swarm_monitor "${1:-3}"
start_metrics_daemon "${2:-5}"
start_swarm_monitor "${1:-30}"
start_metrics_daemon "${2:-60}"
# Initial metrics update
"$SCRIPT_DIR/swarm-monitor.sh" check > /dev/null 2>&1
@@ -207,22 +207,22 @@ show_status() {
# Main command handling
case "${1:-status}" in
"start")
start_all "${2:-3}" "${3:-5}"
start_all "${2:-30}" "${3:-60}"
;;
"stop")
stop_all
;;
"restart")
restart_all "${2:-3}" "${3:-5}"
restart_all "${2:-30}" "${3:-60}"
;;
"status")
show_status
;;
"start-swarm")
start_swarm_monitor "${2:-3}"
start_swarm_monitor "${2:-30}"
;;
"start-metrics")
start_metrics_daemon "${2:-5}"
start_metrics_daemon "${2:-60}"
;;
"help"|"-h"|"--help")
echo "Claude Flow V3 Daemon Manager"
@@ -239,8 +239,8 @@ case "${1:-status}" in
echo " help Show this help"
echo ""
echo "Examples:"
echo " $0 start # Start with defaults (3s swarm, 5s metrics)"
echo " $0 start 2 3 # Start with 2s swarm, 3s metrics intervals"
echo " $0 start # Start with defaults (30s swarm, 60s metrics)"
echo " $0 start 10 30 # Start with 10s swarm, 30s metrics intervals"
echo " $0 status # Show current status"
echo " $0 stop # Stop all daemons"
;;

View File

@@ -0,0 +1,232 @@
#!/usr/bin/env node
/**
* Claude Flow Hook Handler (Cross-Platform)
* Dispatches hook events to the appropriate helper modules.
*
* Usage: node hook-handler.cjs <command> [args...]
*
* Commands:
* route - Route a task to optimal agent (reads PROMPT from env/stdin)
* pre-bash - Validate command safety before execution
* post-edit - Record edit outcome for learning
* session-restore - Restore previous session state
* session-end - End session and persist state
*/
const path = require('path');
const fs = require('fs');
const helpersDir = __dirname;
// Safe require with stdout suppression - the helper modules have CLI
// sections that run unconditionally on require(), so we mute console
// during the require to prevent noisy output.
function safeRequire(modulePath) {
try {
if (fs.existsSync(modulePath)) {
const origLog = console.log;
const origError = console.error;
console.log = () => {};
console.error = () => {};
try {
const mod = require(modulePath);
return mod;
} finally {
console.log = origLog;
console.error = origError;
}
}
} catch (e) {
// silently fail
}
return null;
}
const router = safeRequire(path.join(helpersDir, 'router.js'));
const session = safeRequire(path.join(helpersDir, 'session.js'));
const memory = safeRequire(path.join(helpersDir, 'memory.js'));
const intelligence = safeRequire(path.join(helpersDir, 'intelligence.cjs'));
// Get the command from argv
const [,, command, ...args] = process.argv;
// Get prompt from environment variable (set by Claude Code hooks)
const prompt = process.env.PROMPT || process.env.TOOL_INPUT_command || args.join(' ') || '';
const handlers = {
'route': () => {
// Inject ranked intelligence context before routing
if (intelligence && intelligence.getContext) {
try {
const ctx = intelligence.getContext(prompt);
if (ctx) console.log(ctx);
} catch (e) { /* non-fatal */ }
}
if (router && router.routeTask) {
const result = router.routeTask(prompt);
// Format output for Claude Code hook consumption
const output = [
`[INFO] Routing task: ${prompt.substring(0, 80) || '(no prompt)'}`,
'',
'Routing Method',
' - Method: keyword',
' - Backend: keyword matching',
` - Latency: ${(Math.random() * 0.5 + 0.1).toFixed(3)}ms`,
' - Matched Pattern: keyword-fallback',
'',
'Semantic Matches:',
' bugfix-task: 15.0%',
' devops-task: 14.0%',
' testing-task: 13.0%',
'',
'+------------------- Primary Recommendation -------------------+',
`| Agent: ${result.agent.padEnd(53)}|`,
`| Confidence: ${(result.confidence * 100).toFixed(1)}%${' '.repeat(44)}|`,
`| Reason: ${result.reason.substring(0, 53).padEnd(53)}|`,
'+--------------------------------------------------------------+',
'',
'Alternative Agents',
'+------------+------------+-------------------------------------+',
'| Agent Type | Confidence | Reason |',
'+------------+------------+-------------------------------------+',
'| researcher | 60.0% | Alternative agent for researcher... |',
'| tester | 50.0% | Alternative agent for tester cap... |',
'+------------+------------+-------------------------------------+',
'',
'Estimated Metrics',
' - Success Probability: 70.0%',
' - Estimated Duration: 10-30 min',
' - Complexity: LOW',
];
console.log(output.join('\n'));
} else {
console.log('[INFO] Router not available, using default routing');
}
},
'pre-bash': () => {
// Basic command safety check
const cmd = prompt.toLowerCase();
const dangerous = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:'];
for (const d of dangerous) {
if (cmd.includes(d)) {
console.error(`[BLOCKED] Dangerous command detected: ${d}`);
process.exit(1);
}
}
console.log('[OK] Command validated');
},
'post-edit': () => {
// Record edit for session metrics
if (session && session.metric) {
try { session.metric('edits'); } catch (e) { /* no active session */ }
}
// Record edit for intelligence consolidation
if (intelligence && intelligence.recordEdit) {
try {
const file = process.env.TOOL_INPUT_file_path || args[0] || '';
intelligence.recordEdit(file);
} catch (e) { /* non-fatal */ }
}
console.log('[OK] Edit recorded');
},
'session-restore': () => {
if (session) {
// Try restore first, fall back to start
const existing = session.restore && session.restore();
if (!existing) {
session.start && session.start();
}
} else {
// Minimal session restore output
const sessionId = `session-${Date.now()}`;
console.log(`[INFO] Restoring session: %SESSION_ID%`);
console.log('');
console.log(`[OK] Session restored from %SESSION_ID%`);
console.log(`New session ID: ${sessionId}`);
console.log('');
console.log('Restored State');
console.log('+----------------+-------+');
console.log('| Item | Count |');
console.log('+----------------+-------+');
console.log('| Tasks | 0 |');
console.log('| Agents | 0 |');
console.log('| Memory Entries | 0 |');
console.log('+----------------+-------+');
}
// Initialize intelligence graph after session restore
if (intelligence && intelligence.init) {
try {
const result = intelligence.init();
if (result && result.nodes > 0) {
console.log(`[INTELLIGENCE] Loaded ${result.nodes} patterns, ${result.edges} edges`);
}
} catch (e) { /* non-fatal */ }
}
},
'session-end': () => {
// Consolidate intelligence before ending session
if (intelligence && intelligence.consolidate) {
try {
const result = intelligence.consolidate();
if (result && result.entries > 0) {
console.log(`[INTELLIGENCE] Consolidated: ${result.entries} entries, ${result.edges} edges${result.newEntries > 0 ? `, ${result.newEntries} new` : ''}, PageRank recomputed`);
}
} catch (e) { /* non-fatal */ }
}
if (session && session.end) {
session.end();
} else {
console.log('[OK] Session ended');
}
},
'pre-task': () => {
if (session && session.metric) {
try { session.metric('tasks'); } catch (e) { /* no active session */ }
}
// Route the task if router is available
if (router && router.routeTask && prompt) {
const result = router.routeTask(prompt);
console.log(`[INFO] Task routed to: ${result.agent} (confidence: ${result.confidence})`);
} else {
console.log('[OK] Task started');
}
},
'post-task': () => {
// Implicit success feedback for intelligence
if (intelligence && intelligence.feedback) {
try {
intelligence.feedback(true);
} catch (e) { /* non-fatal */ }
}
console.log('[OK] Task completed');
},
'stats': () => {
if (intelligence && intelligence.stats) {
intelligence.stats(args.includes('--json'));
} else {
console.log('[WARN] Intelligence module not available. Run session-restore first.');
}
},
};
// Execute the handler
if (command && handlers[command]) {
try {
handlers[command]();
} catch (e) {
// Hooks should never crash Claude Code - fail silently
console.log(`[WARN] Hook ${command} encountered an error: ${e.message}`);
}
} else if (command) {
// Unknown command - pass through without error
console.log(`[OK] Hook: ${command}`);
} else {
console.log('Usage: hook-handler.cjs <route|pre-bash|post-edit|session-restore|session-end|pre-task|post-task|stats>');
}

View File

@@ -0,0 +1,916 @@
#!/usr/bin/env node
/**
* Intelligence Layer (ADR-050)
*
* Closes the intelligence loop by wiring PageRank-ranked memory into
* the hook system. Pure CJS — no ESM imports of @claude-flow/memory.
*
* Data files (all under .claude-flow/data/):
* auto-memory-store.json — written by auto-memory-hook.mjs
* graph-state.json — serialized graph (nodes + edges + pageRanks)
* ranked-context.json — pre-computed ranked entries for fast lookup
* pending-insights.jsonl — append-only edit/task log
*/
'use strict';
const fs = require('fs');
const path = require('path');
const DATA_DIR = path.join(process.cwd(), '.claude-flow', 'data');
const STORE_PATH = path.join(DATA_DIR, 'auto-memory-store.json');
const GRAPH_PATH = path.join(DATA_DIR, 'graph-state.json');
const RANKED_PATH = path.join(DATA_DIR, 'ranked-context.json');
const PENDING_PATH = path.join(DATA_DIR, 'pending-insights.jsonl');
const SESSION_DIR = path.join(process.cwd(), '.claude-flow', 'sessions');
const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
// ── Stop words for trigram matching ──────────────────────────────────────────
const STOP_WORDS = new Set([
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
'should', 'may', 'might', 'shall', 'can', 'to', 'of', 'in', 'for',
'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through', 'during',
'before', 'after', 'and', 'but', 'or', 'nor', 'not', 'so', 'yet',
'both', 'either', 'neither', 'each', 'every', 'all', 'any', 'few',
'more', 'most', 'other', 'some', 'such', 'no', 'only', 'own', 'same',
'than', 'too', 'very', 'just', 'because', 'if', 'when', 'which',
'who', 'whom', 'this', 'that', 'these', 'those', 'it', 'its',
]);
// ── Helpers ──────────────────────────────────────────────────────────────────
function ensureDataDir() {
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
}
function readJSON(filePath) {
try {
if (fs.existsSync(filePath)) return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
} catch { /* corrupt file — start fresh */ }
return null;
}
function writeJSON(filePath, data) {
ensureDataDir();
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
}
function tokenize(text) {
if (!text) return [];
return text.toLowerCase()
.replace(/[^a-z0-9\s-]/g, ' ')
.split(/\s+/)
.filter(w => w.length > 2 && !STOP_WORDS.has(w));
}
function trigrams(words) {
const t = new Set();
for (const w of words) {
for (let i = 0; i <= w.length - 3; i++) t.add(w.slice(i, i + 3));
}
return t;
}
function jaccardSimilarity(setA, setB) {
if (setA.size === 0 && setB.size === 0) return 0;
let intersection = 0;
for (const item of setA) { if (setB.has(item)) intersection++; }
return intersection / (setA.size + setB.size - intersection);
}
// ── Session state helpers ────────────────────────────────────────────────────
function sessionGet(key) {
try {
if (!fs.existsSync(SESSION_FILE)) return null;
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
return key ? (session.context || {})[key] : session.context;
} catch { return null; }
}
function sessionSet(key, value) {
try {
if (!fs.existsSync(SESSION_DIR)) fs.mkdirSync(SESSION_DIR, { recursive: true });
let session = {};
if (fs.existsSync(SESSION_FILE)) {
session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
}
if (!session.context) session.context = {};
session.context[key] = value;
session.updatedAt = new Date().toISOString();
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2), 'utf-8');
} catch { /* best effort */ }
}
// ── PageRank ─────────────────────────────────────────────────────────────────
function computePageRank(nodes, edges, damping, maxIter) {
damping = damping || 0.85;
maxIter = maxIter || 30;
const ids = Object.keys(nodes);
const n = ids.length;
if (n === 0) return {};
// Build adjacency: outgoing edges per node
const outLinks = {};
const inLinks = {};
for (const id of ids) { outLinks[id] = []; inLinks[id] = []; }
for (const edge of edges) {
if (outLinks[edge.sourceId]) outLinks[edge.sourceId].push(edge.targetId);
if (inLinks[edge.targetId]) inLinks[edge.targetId].push(edge.sourceId);
}
// Initialize ranks
const ranks = {};
for (const id of ids) ranks[id] = 1 / n;
// Power iteration (with dangling node redistribution)
for (let iter = 0; iter < maxIter; iter++) {
const newRanks = {};
let diff = 0;
// Collect rank from dangling nodes (no outgoing edges)
let danglingSum = 0;
for (const id of ids) {
if (outLinks[id].length === 0) danglingSum += ranks[id];
}
for (const id of ids) {
let sum = 0;
for (const src of inLinks[id]) {
const outCount = outLinks[src].length;
if (outCount > 0) sum += ranks[src] / outCount;
}
// Dangling rank distributed evenly + teleport
newRanks[id] = (1 - damping) / n + damping * (sum + danglingSum / n);
diff += Math.abs(newRanks[id] - ranks[id]);
}
for (const id of ids) ranks[id] = newRanks[id];
if (diff < 1e-6) break; // converged
}
return ranks;
}
// ── Edge building ────────────────────────────────────────────────────────────
function buildEdges(entries) {
const edges = [];
const byCategory = {};
for (const entry of entries) {
const cat = entry.category || entry.namespace || 'default';
if (!byCategory[cat]) byCategory[cat] = [];
byCategory[cat].push(entry);
}
// Temporal edges: entries from same sourceFile
const byFile = {};
for (const entry of entries) {
const file = (entry.metadata && entry.metadata.sourceFile) || null;
if (file) {
if (!byFile[file]) byFile[file] = [];
byFile[file].push(entry);
}
}
for (const file of Object.keys(byFile)) {
const group = byFile[file];
for (let i = 0; i < group.length - 1; i++) {
edges.push({
sourceId: group[i].id,
targetId: group[i + 1].id,
type: 'temporal',
weight: 0.5,
});
}
}
// Similarity edges within categories (Jaccard > 0.3)
for (const cat of Object.keys(byCategory)) {
const group = byCategory[cat];
for (let i = 0; i < group.length; i++) {
const triA = trigrams(tokenize(group[i].content || group[i].summary || ''));
for (let j = i + 1; j < group.length; j++) {
const triB = trigrams(tokenize(group[j].content || group[j].summary || ''));
const sim = jaccardSimilarity(triA, triB);
if (sim > 0.3) {
edges.push({
sourceId: group[i].id,
targetId: group[j].id,
type: 'similar',
weight: sim,
});
}
}
}
}
return edges;
}
// ── Bootstrap from MEMORY.md files ───────────────────────────────────────────
/**
* If auto-memory-store.json is empty, bootstrap by parsing MEMORY.md and
* topic files from the auto-memory directory. This removes the dependency
* on @claude-flow/memory for the initial seed.
*/
function bootstrapFromMemoryFiles() {
const entries = [];
const cwd = process.cwd();
// Search for auto-memory directories
const candidates = [
// Claude Code auto-memory (project-scoped)
path.join(require('os').homedir(), '.claude', 'projects'),
// Local project memory
path.join(cwd, '.claude-flow', 'memory'),
path.join(cwd, '.claude', 'memory'),
];
// Find MEMORY.md in project-scoped dirs
for (const base of candidates) {
if (!fs.existsSync(base)) continue;
// For the projects dir, scan subdirectories for memory/
if (base.endsWith('projects')) {
try {
const projectDirs = fs.readdirSync(base);
for (const pdir of projectDirs) {
const memDir = path.join(base, pdir, 'memory');
if (fs.existsSync(memDir)) {
parseMemoryDir(memDir, entries);
}
}
} catch { /* skip */ }
} else if (fs.existsSync(base)) {
parseMemoryDir(base, entries);
}
}
return entries;
}
function parseMemoryDir(dir, entries) {
try {
const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
for (const file of files) {
const filePath = path.join(dir, file);
const content = fs.readFileSync(filePath, 'utf-8');
if (!content.trim()) continue;
// Parse markdown sections as separate entries
const sections = content.split(/^##?\s+/m).filter(Boolean);
for (const section of sections) {
const lines = section.trim().split('\n');
const title = lines[0].trim();
const body = lines.slice(1).join('\n').trim();
if (!body || body.length < 10) continue;
const id = `mem-${file.replace('.md', '')}-${title.replace(/[^a-z0-9]/gi, '-').toLowerCase().slice(0, 30)}`;
entries.push({
id,
key: title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50),
content: body.slice(0, 500),
summary: title,
namespace: file === 'MEMORY.md' ? 'core' : file.replace('.md', ''),
type: 'semantic',
metadata: { sourceFile: filePath, bootstrapped: true },
createdAt: Date.now(),
});
}
}
} catch { /* skip unreadable dirs */ }
}
// ── Exported functions ───────────────────────────────────────────────────────
/**
* init() — Called from session-restore. Budget: <200ms.
* Reads auto-memory-store.json, builds graph, computes PageRank, writes caches.
* If store is empty, bootstraps from MEMORY.md files directly.
*/
function init() {
ensureDataDir();
// Check if graph-state.json is fresh (within 60s of store)
const graphState = readJSON(GRAPH_PATH);
let store = readJSON(STORE_PATH);
// Bootstrap from MEMORY.md files if store is empty
if (!store || !Array.isArray(store) || store.length === 0) {
const bootstrapped = bootstrapFromMemoryFiles();
if (bootstrapped.length > 0) {
store = bootstrapped;
writeJSON(STORE_PATH, store);
} else {
return { nodes: 0, edges: 0, message: 'No memory entries to index' };
}
}
// Skip rebuild if graph is fresh and store hasn't changed
if (graphState && graphState.nodeCount === store.length) {
const age = Date.now() - (graphState.updatedAt || 0);
if (age < 60000) {
return {
nodes: graphState.nodeCount || Object.keys(graphState.nodes || {}).length,
edges: (graphState.edges || []).length,
message: 'Graph cache hit',
};
}
}
// Build nodes
const nodes = {};
for (const entry of store) {
const id = entry.id || entry.key || `entry-${Math.random().toString(36).slice(2, 8)}`;
nodes[id] = {
id,
category: entry.namespace || entry.type || 'default',
confidence: (entry.metadata && entry.metadata.confidence) || 0.5,
accessCount: (entry.metadata && entry.metadata.accessCount) || 0,
createdAt: entry.createdAt || Date.now(),
};
// Ensure entry has id for edge building
entry.id = id;
}
// Build edges
const edges = buildEdges(store);
// Compute PageRank
const pageRanks = computePageRank(nodes, edges, 0.85, 30);
// Write graph state
const graph = {
version: 1,
updatedAt: Date.now(),
nodeCount: Object.keys(nodes).length,
nodes,
edges,
pageRanks,
};
writeJSON(GRAPH_PATH, graph);
// Build ranked context for fast lookup
const rankedEntries = store.map(entry => {
const id = entry.id;
const content = entry.content || entry.value || '';
const summary = entry.summary || entry.key || '';
const words = tokenize(content + ' ' + summary);
return {
id,
content,
summary,
category: entry.namespace || entry.type || 'default',
confidence: nodes[id] ? nodes[id].confidence : 0.5,
pageRank: pageRanks[id] || 0,
accessCount: nodes[id] ? nodes[id].accessCount : 0,
words,
};
}).sort((a, b) => {
const scoreA = 0.6 * a.pageRank + 0.4 * a.confidence;
const scoreB = 0.6 * b.pageRank + 0.4 * b.confidence;
return scoreB - scoreA;
});
const ranked = {
version: 1,
computedAt: Date.now(),
entries: rankedEntries,
};
writeJSON(RANKED_PATH, ranked);
return {
nodes: Object.keys(nodes).length,
edges: edges.length,
message: 'Graph built and ranked',
};
}
/**
* getContext(prompt) — Called from route. Budget: <15ms.
* Matches prompt to ranked entries, returns top-5 formatted context.
*/
function getContext(prompt) {
if (!prompt) return null;
const ranked = readJSON(RANKED_PATH);
if (!ranked || !ranked.entries || ranked.entries.length === 0) return null;
const promptWords = tokenize(prompt);
if (promptWords.length === 0) return null;
const promptTrigrams = trigrams(promptWords);
const ALPHA = 0.6; // content match weight
const MIN_THRESHOLD = 0.05;
const TOP_K = 5;
// Score each entry
const scored = [];
for (const entry of ranked.entries) {
const entryTrigrams = trigrams(entry.words || []);
const contentMatch = jaccardSimilarity(promptTrigrams, entryTrigrams);
const score = ALPHA * contentMatch + (1 - ALPHA) * (entry.pageRank || 0);
if (score >= MIN_THRESHOLD) {
scored.push({ ...entry, score });
}
}
if (scored.length === 0) return null;
// Sort by score descending, take top-K
scored.sort((a, b) => b.score - a.score);
const topEntries = scored.slice(0, TOP_K);
// Boost previously matched patterns (implicit success: user continued working)
const prevMatched = sessionGet('lastMatchedPatterns');
// Store NEW matched IDs in session state for feedback
const matchedIds = topEntries.map(e => e.id);
sessionSet('lastMatchedPatterns', matchedIds);
// Only boost previous if they differ from current (avoid double-boosting)
if (prevMatched && Array.isArray(prevMatched)) {
const newSet = new Set(matchedIds);
const toBoost = prevMatched.filter(id => !newSet.has(id));
if (toBoost.length > 0) boostConfidence(toBoost, 0.03);
}
// Format output
const lines = ['[INTELLIGENCE] Relevant patterns for this task:'];
for (let i = 0; i < topEntries.length; i++) {
const e = topEntries[i];
const display = (e.summary || e.content || '').slice(0, 80);
const accessed = e.accessCount || 0;
lines.push(` * (${e.score.toFixed(2)}) ${display} [rank #${i + 1}, ${accessed}x accessed]`);
}
return lines.join('\n');
}
/**
* recordEdit(file) — Called from post-edit. Budget: <2ms.
* Appends to pending-insights.jsonl.
*/
function recordEdit(file) {
ensureDataDir();
const entry = JSON.stringify({
type: 'edit',
file: file || 'unknown',
timestamp: Date.now(),
sessionId: sessionGet('sessionId') || null,
});
fs.appendFileSync(PENDING_PATH, entry + '\n', 'utf-8');
}
/**
* feedback(success) — Called from post-task. Budget: <10ms.
* Boosts or decays confidence for last-matched patterns.
*/
function feedback(success) {
const matchedIds = sessionGet('lastMatchedPatterns');
if (!matchedIds || !Array.isArray(matchedIds)) return;
const amount = success ? 0.05 : -0.02;
boostConfidence(matchedIds, amount);
}
function boostConfidence(ids, amount) {
const ranked = readJSON(RANKED_PATH);
if (!ranked || !ranked.entries) return;
let changed = false;
for (const entry of ranked.entries) {
if (ids.includes(entry.id)) {
entry.confidence = Math.max(0, Math.min(1, (entry.confidence || 0.5) + amount));
entry.accessCount = (entry.accessCount || 0) + 1;
changed = true;
}
}
if (changed) writeJSON(RANKED_PATH, ranked);
// Also update graph-state confidence
const graph = readJSON(GRAPH_PATH);
if (graph && graph.nodes) {
for (const id of ids) {
if (graph.nodes[id]) {
graph.nodes[id].confidence = Math.max(0, Math.min(1, (graph.nodes[id].confidence || 0.5) + amount));
graph.nodes[id].accessCount = (graph.nodes[id].accessCount || 0) + 1;
}
}
writeJSON(GRAPH_PATH, graph);
}
}
/**
* consolidate() — Called from session-end. Budget: <500ms.
* Processes pending insights, rebuilds edges, recomputes PageRank.
*/
function consolidate() {
ensureDataDir();
const store = readJSON(STORE_PATH);
if (!store || !Array.isArray(store)) {
return { entries: 0, edges: 0, newEntries: 0, message: 'No store to consolidate' };
}
// 1. Process pending insights
let newEntries = 0;
if (fs.existsSync(PENDING_PATH)) {
const lines = fs.readFileSync(PENDING_PATH, 'utf-8').trim().split('\n').filter(Boolean);
const editCounts = {};
for (const line of lines) {
try {
const insight = JSON.parse(line);
if (insight.file) {
editCounts[insight.file] = (editCounts[insight.file] || 0) + 1;
}
} catch { /* skip malformed */ }
}
// Create entries for frequently-edited files (3+ edits)
for (const [file, count] of Object.entries(editCounts)) {
if (count >= 3) {
const exists = store.some(e =>
(e.metadata && e.metadata.sourceFile === file && e.metadata.autoGenerated)
);
if (!exists) {
store.push({
id: `insight-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
key: `frequent-edit-${path.basename(file)}`,
content: `File ${file} was edited ${count} times this session — likely a hot path worth monitoring.`,
summary: `Frequently edited: ${path.basename(file)} (${count}x)`,
namespace: 'insights',
type: 'procedural',
metadata: { sourceFile: file, editCount: count, autoGenerated: true },
createdAt: Date.now(),
});
newEntries++;
}
}
}
// Clear pending
fs.writeFileSync(PENDING_PATH, '', 'utf-8');
}
// 2. Confidence decay for unaccessed entries
const graph = readJSON(GRAPH_PATH);
if (graph && graph.nodes) {
const now = Date.now();
for (const id of Object.keys(graph.nodes)) {
const node = graph.nodes[id];
const hoursSinceCreation = (now - (node.createdAt || now)) / (1000 * 60 * 60);
if (node.accessCount === 0 && hoursSinceCreation > 24) {
node.confidence = Math.max(0.05, (node.confidence || 0.5) - 0.005 * Math.floor(hoursSinceCreation / 24));
}
}
}
// 3. Rebuild edges with updated store
for (const entry of store) {
if (!entry.id) entry.id = `entry-${Math.random().toString(36).slice(2, 8)}`;
}
const edges = buildEdges(store);
// 4. Build updated nodes
const nodes = {};
for (const entry of store) {
nodes[entry.id] = {
id: entry.id,
category: entry.namespace || entry.type || 'default',
confidence: (graph && graph.nodes && graph.nodes[entry.id])
? graph.nodes[entry.id].confidence
: (entry.metadata && entry.metadata.confidence) || 0.5,
accessCount: (graph && graph.nodes && graph.nodes[entry.id])
? graph.nodes[entry.id].accessCount
: (entry.metadata && entry.metadata.accessCount) || 0,
createdAt: entry.createdAt || Date.now(),
};
}
// 5. Recompute PageRank
const pageRanks = computePageRank(nodes, edges, 0.85, 30);
// 6. Write updated graph
writeJSON(GRAPH_PATH, {
version: 1,
updatedAt: Date.now(),
nodeCount: Object.keys(nodes).length,
nodes,
edges,
pageRanks,
});
// 7. Write updated ranked context
const rankedEntries = store.map(entry => {
const id = entry.id;
const content = entry.content || entry.value || '';
const summary = entry.summary || entry.key || '';
const words = tokenize(content + ' ' + summary);
return {
id,
content,
summary,
category: entry.namespace || entry.type || 'default',
confidence: nodes[id] ? nodes[id].confidence : 0.5,
pageRank: pageRanks[id] || 0,
accessCount: nodes[id] ? nodes[id].accessCount : 0,
words,
};
}).sort((a, b) => {
const scoreA = 0.6 * a.pageRank + 0.4 * a.confidence;
const scoreB = 0.6 * b.pageRank + 0.4 * b.confidence;
return scoreB - scoreA;
});
writeJSON(RANKED_PATH, {
version: 1,
computedAt: Date.now(),
entries: rankedEntries,
});
// 8. Persist updated store (with new insight entries)
if (newEntries > 0) writeJSON(STORE_PATH, store);
// 9. Save snapshot for delta tracking
const updatedGraph = readJSON(GRAPH_PATH);
const updatedRanked = readJSON(RANKED_PATH);
saveSnapshot(updatedGraph, updatedRanked);
return {
entries: store.length,
edges: edges.length,
newEntries,
message: 'Consolidated',
};
}
// ── Snapshot for delta tracking ─────────────────────────────────────────────
const SNAPSHOT_PATH = path.join(DATA_DIR, 'intelligence-snapshot.json');
function saveSnapshot(graph, ranked) {
const snap = {
timestamp: Date.now(),
nodes: graph ? Object.keys(graph.nodes || {}).length : 0,
edges: graph ? (graph.edges || []).length : 0,
pageRankSum: 0,
confidences: [],
accessCounts: [],
topPatterns: [],
};
if (graph && graph.pageRanks) {
for (const v of Object.values(graph.pageRanks)) snap.pageRankSum += v;
}
if (graph && graph.nodes) {
for (const n of Object.values(graph.nodes)) {
snap.confidences.push(n.confidence || 0.5);
snap.accessCounts.push(n.accessCount || 0);
}
}
if (ranked && ranked.entries) {
snap.topPatterns = ranked.entries.slice(0, 10).map(e => ({
id: e.id,
summary: (e.summary || '').slice(0, 60),
confidence: e.confidence || 0.5,
pageRank: e.pageRank || 0,
accessCount: e.accessCount || 0,
}));
}
// Keep history: append to array, cap at 50
let history = readJSON(SNAPSHOT_PATH);
if (!Array.isArray(history)) history = [];
history.push(snap);
if (history.length > 50) history = history.slice(-50);
writeJSON(SNAPSHOT_PATH, history);
}
/**
* stats() — Diagnostic report showing intelligence health and improvement.
* Can be called as: node intelligence.cjs stats [--json]
*/
function stats(outputJson) {
const graph = readJSON(GRAPH_PATH);
const ranked = readJSON(RANKED_PATH);
const history = readJSON(SNAPSHOT_PATH) || [];
const pending = fs.existsSync(PENDING_PATH)
? fs.readFileSync(PENDING_PATH, 'utf-8').trim().split('\n').filter(Boolean).length
: 0;
// Current state
const nodes = graph ? Object.keys(graph.nodes || {}).length : 0;
const edges = graph ? (graph.edges || []).length : 0;
const density = nodes > 1 ? (2 * edges) / (nodes * (nodes - 1)) : 0;
// Confidence distribution
const confidences = [];
const accessCounts = [];
if (graph && graph.nodes) {
for (const n of Object.values(graph.nodes)) {
confidences.push(n.confidence || 0.5);
accessCounts.push(n.accessCount || 0);
}
}
confidences.sort((a, b) => a - b);
const confMin = confidences.length ? confidences[0] : 0;
const confMax = confidences.length ? confidences[confidences.length - 1] : 0;
const confMean = confidences.length ? confidences.reduce((s, c) => s + c, 0) / confidences.length : 0;
const confMedian = confidences.length ? confidences[Math.floor(confidences.length / 2)] : 0;
// Access stats
const totalAccess = accessCounts.reduce((s, c) => s + c, 0);
const accessedCount = accessCounts.filter(c => c > 0).length;
// PageRank stats
let prSum = 0, prMax = 0, prMaxId = '';
if (graph && graph.pageRanks) {
for (const [id, pr] of Object.entries(graph.pageRanks)) {
prSum += pr;
if (pr > prMax) { prMax = pr; prMaxId = id; }
}
}
// Top patterns by composite score
const topPatterns = (ranked && ranked.entries || []).slice(0, 10).map((e, i) => ({
rank: i + 1,
summary: (e.summary || '').slice(0, 60),
confidence: (e.confidence || 0.5).toFixed(3),
pageRank: (e.pageRank || 0).toFixed(4),
accessed: e.accessCount || 0,
score: (0.6 * (e.pageRank || 0) + 0.4 * (e.confidence || 0.5)).toFixed(4),
}));
// Edge type breakdown
const edgeTypes = {};
if (graph && graph.edges) {
for (const e of graph.edges) {
edgeTypes[e.type || 'unknown'] = (edgeTypes[e.type || 'unknown'] || 0) + 1;
}
}
// Delta from previous snapshot
let delta = null;
if (history.length >= 2) {
const prev = history[history.length - 2];
const curr = history[history.length - 1];
const elapsed = (curr.timestamp - prev.timestamp) / 1000;
const prevConfMean = prev.confidences.length
? prev.confidences.reduce((s, c) => s + c, 0) / prev.confidences.length : 0;
const currConfMean = curr.confidences.length
? curr.confidences.reduce((s, c) => s + c, 0) / curr.confidences.length : 0;
const prevAccess = prev.accessCounts.reduce((s, c) => s + c, 0);
const currAccess = curr.accessCounts.reduce((s, c) => s + c, 0);
delta = {
elapsed: elapsed < 3600 ? `${Math.round(elapsed / 60)}m` : `${(elapsed / 3600).toFixed(1)}h`,
nodes: curr.nodes - prev.nodes,
edges: curr.edges - prev.edges,
confidenceMean: currConfMean - prevConfMean,
totalAccess: currAccess - prevAccess,
};
}
// Trend over all history
let trend = null;
if (history.length >= 3) {
const first = history[0];
const last = history[history.length - 1];
const sessions = history.length;
const firstConfMean = first.confidences.length
? first.confidences.reduce((s, c) => s + c, 0) / first.confidences.length : 0;
const lastConfMean = last.confidences.length
? last.confidences.reduce((s, c) => s + c, 0) / last.confidences.length : 0;
trend = {
sessions,
nodeGrowth: last.nodes - first.nodes,
edgeGrowth: last.edges - first.edges,
confidenceDrift: lastConfMean - firstConfMean,
direction: lastConfMean > firstConfMean ? 'improving' :
lastConfMean < firstConfMean ? 'declining' : 'stable',
};
}
const report = {
graph: { nodes, edges, density: +density.toFixed(4) },
confidence: {
min: +confMin.toFixed(3), max: +confMax.toFixed(3),
mean: +confMean.toFixed(3), median: +confMedian.toFixed(3),
},
access: { total: totalAccess, patternsAccessed: accessedCount, patternsNeverAccessed: nodes - accessedCount },
pageRank: { sum: +prSum.toFixed(4), topNode: prMaxId, topNodeRank: +prMax.toFixed(4) },
edgeTypes,
pendingInsights: pending,
snapshots: history.length,
topPatterns,
delta,
trend,
};
if (outputJson) {
console.log(JSON.stringify(report, null, 2));
return report;
}
// Human-readable output
const bar = '+' + '-'.repeat(62) + '+';
console.log(bar);
console.log('|' + ' Intelligence Diagnostics (ADR-050)'.padEnd(62) + '|');
console.log(bar);
console.log('');
console.log(' Graph');
console.log(` Nodes: ${nodes}`);
console.log(` Edges: ${edges} (${Object.entries(edgeTypes).map(([t,c]) => `${c} ${t}`).join(', ') || 'none'})`);
console.log(` Density: ${(density * 100).toFixed(1)}%`);
console.log('');
console.log(' Confidence');
console.log(` Min: ${confMin.toFixed(3)}`);
console.log(` Max: ${confMax.toFixed(3)}`);
console.log(` Mean: ${confMean.toFixed(3)}`);
console.log(` Median: ${confMedian.toFixed(3)}`);
console.log('');
console.log(' Access');
console.log(` Total accesses: ${totalAccess}`);
console.log(` Patterns used: ${accessedCount}/${nodes}`);
console.log(` Never accessed: ${nodes - accessedCount}`);
console.log(` Pending insights: ${pending}`);
console.log('');
console.log(' PageRank');
console.log(` Sum: ${prSum.toFixed(4)} (should be ~1.0)`);
console.log(` Top node: ${prMaxId || '(none)'} (${prMax.toFixed(4)})`);
console.log('');
if (topPatterns.length > 0) {
console.log(' Top Patterns (by composite score)');
console.log(' ' + '-'.repeat(60));
for (const p of topPatterns) {
console.log(` #${p.rank} ${p.summary}`);
console.log(` conf=${p.confidence} pr=${p.pageRank} score=${p.score} accessed=${p.accessed}x`);
}
console.log('');
}
if (delta) {
console.log(` Last Delta (${delta.elapsed} ago)`);
const sign = v => v > 0 ? `+${v}` : `${v}`;
console.log(` Nodes: ${sign(delta.nodes)}`);
console.log(` Edges: ${sign(delta.edges)}`);
console.log(` Confidence: ${delta.confidenceMean >= 0 ? '+' : ''}${delta.confidenceMean.toFixed(4)}`);
console.log(` Accesses: ${sign(delta.totalAccess)}`);
console.log('');
}
if (trend) {
console.log(` Trend (${trend.sessions} snapshots)`);
console.log(` Node growth: ${trend.nodeGrowth >= 0 ? '+' : ''}${trend.nodeGrowth}`);
console.log(` Edge growth: ${trend.edgeGrowth >= 0 ? '+' : ''}${trend.edgeGrowth}`);
console.log(` Confidence drift: ${trend.confidenceDrift >= 0 ? '+' : ''}${trend.confidenceDrift.toFixed(4)}`);
console.log(` Direction: ${trend.direction.toUpperCase()}`);
console.log('');
}
if (!delta && !trend) {
console.log(' No history yet — run more sessions to see deltas and trends.');
console.log('');
}
console.log(bar);
return report;
}
module.exports = { init, getContext, recordEdit, feedback, consolidate, stats };
// ── CLI entrypoint ──────────────────────────────────────────────────────────
if (require.main === module) {
const cmd = process.argv[2];
const jsonFlag = process.argv.includes('--json');
const cmds = {
init: () => { const r = init(); console.log(JSON.stringify(r)); },
stats: () => { stats(jsonFlag); },
consolidate: () => { const r = consolidate(); console.log(JSON.stringify(r)); },
};
if (cmd && cmds[cmd]) {
cmds[cmd]();
} else {
console.log('Usage: intelligence.cjs <stats|init|consolidate> [--json]');
console.log('');
console.log(' stats Show intelligence diagnostics and trends');
console.log(' stats --json Output as JSON for programmatic use');
console.log(' init Build graph and rank entries');
console.log(' consolidate Process pending insights and recompute');
}
}

View File

@@ -100,6 +100,14 @@ const commands = {
return session;
},
get: (key) => {
if (!fs.existsSync(SESSION_FILE)) return null;
try {
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
return key ? (session.context || {})[key] : session.context;
} catch { return null; }
},
metric: (name) => {
if (!fs.existsSync(SESSION_FILE)) {
return null;

View File

@@ -1,32 +1,31 @@
#!/usr/bin/env node
/**
* Claude Flow V3 Statusline Generator
* Claude Flow V3 Statusline Generator (Optimized)
* Displays real-time V3 implementation progress and system status
*
* Usage: node statusline.cjs [--json] [--compact]
*
* IMPORTANT: This file uses .cjs extension to work in ES module projects.
* The require() syntax is intentional for CommonJS compatibility.
* Performance notes:
* - Single git execSync call (combines branch + status + upstream)
* - No recursive file reading (only stat/readdir, never read test contents)
* - No ps aux calls (uses process.memoryUsage() + file-based metrics)
* - Strict 2s timeout on all execSync calls
* - Shared settings cache across functions
*/
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const os = require('os');
// Configuration
const CONFIG = {
enabled: true,
showProgress: true,
showSecurity: true,
showSwarm: true,
showHooks: true,
showPerformance: true,
refreshInterval: 5000,
maxAgents: 15,
topology: 'hierarchical-mesh',
};
const CWD = process.cwd();
// ANSI colors
const c = {
reset: '\x1b[0m',
@@ -47,270 +46,709 @@ const c = {
brightWhite: '\x1b[1;37m',
};
// Get user info
function getUserInfo() {
let name = 'user';
let gitBranch = '';
let modelName = 'Opus 4.5';
// Safe execSync with strict timeout (returns empty string on failure)
function safeExec(cmd, timeoutMs = 2000) {
try {
name = execSync('git config user.name 2>/dev/null || echo "user"', { encoding: 'utf-8' }).trim();
gitBranch = execSync('git branch --show-current 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim();
} catch (e) {
// Ignore errors
return execSync(cmd, {
encoding: 'utf-8',
timeout: timeoutMs,
stdio: ['pipe', 'pipe', 'pipe'],
}).trim();
} catch {
return '';
}
return { name, gitBranch, modelName };
}
// Get learning stats from memory database
function getLearningStats() {
const memoryPaths = [
path.join(process.cwd(), '.swarm', 'memory.db'),
path.join(process.cwd(), '.claude', 'memory.db'),
path.join(process.cwd(), 'data', 'memory.db'),
];
// Safe JSON file reader (returns null on failure)
function readJSON(filePath) {
try {
if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
}
} catch { /* ignore */ }
return null;
}
let patterns = 0;
let sessions = 0;
let trajectories = 0;
// Safe file stat (returns null on failure)
function safeStat(filePath) {
try {
return fs.statSync(filePath);
} catch { /* ignore */ }
return null;
}
// Try to read from sqlite database
for (const dbPath of memoryPaths) {
if (fs.existsSync(dbPath)) {
try {
// Count entries in memory file (rough estimate from file size)
const stats = fs.statSync(dbPath);
const sizeKB = stats.size / 1024;
// Estimate: ~2KB per pattern on average
patterns = Math.floor(sizeKB / 2);
sessions = Math.max(1, Math.floor(patterns / 10));
trajectories = Math.floor(patterns / 5);
break;
} catch (e) {
// Ignore
// Shared settings cache — read once, used by multiple functions
let _settingsCache = undefined;
function getSettings() {
if (_settingsCache !== undefined) return _settingsCache;
_settingsCache = readJSON(path.join(CWD, '.claude', 'settings.json'))
|| readJSON(path.join(CWD, '.claude', 'settings.local.json'))
|| null;
return _settingsCache;
}
// ─── Data Collection (all pure-Node.js or single-exec) ──────────
// Get all git info in ONE shell call
function getGitInfo() {
const result = {
name: 'user', gitBranch: '', modified: 0, untracked: 0,
staged: 0, ahead: 0, behind: 0,
};
// Single shell: get user.name, branch, porcelain status, and upstream diff
const script = [
'git config user.name 2>/dev/null || echo user',
'echo "---SEP---"',
'git branch --show-current 2>/dev/null',
'echo "---SEP---"',
'git status --porcelain 2>/dev/null',
'echo "---SEP---"',
'git rev-list --left-right --count HEAD...@{upstream} 2>/dev/null || echo "0 0"',
].join('; ');
const raw = safeExec("sh -c '" + script + "'", 3000);
if (!raw) return result;
const parts = raw.split('---SEP---').map(s => s.trim());
if (parts.length >= 4) {
result.name = parts[0] || 'user';
result.gitBranch = parts[1] || '';
// Parse porcelain status
if (parts[2]) {
for (const line of parts[2].split('\n')) {
if (!line || line.length < 2) continue;
const x = line[0], y = line[1];
if (x === '?' && y === '?') { result.untracked++; continue; }
if (x !== ' ' && x !== '?') result.staged++;
if (y !== ' ' && y !== '?') result.modified++;
}
}
// Parse ahead/behind
const ab = (parts[3] || '0 0').split(/\s+/);
result.ahead = parseInt(ab[0]) || 0;
result.behind = parseInt(ab[1]) || 0;
}
// Also check for session files
const sessionsPath = path.join(process.cwd(), '.claude', 'sessions');
if (fs.existsSync(sessionsPath)) {
try {
const sessionFiles = fs.readdirSync(sessionsPath).filter(f => f.endsWith('.json'));
sessions = Math.max(sessions, sessionFiles.length);
} catch (e) {
// Ignore
return result;
}
// Detect model name from Claude config (pure file reads, no exec)
function getModelName() {
try {
const claudeConfig = readJSON(path.join(os.homedir(), '.claude.json'));
if (claudeConfig && claudeConfig.projects) {
for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) {
if (CWD === projectPath || CWD.startsWith(projectPath + '/')) {
const usage = projectConfig.lastModelUsage;
if (usage) {
const ids = Object.keys(usage);
if (ids.length > 0) {
let modelId = ids[ids.length - 1];
let latest = 0;
for (const id of ids) {
const ts = usage[id] && usage[id].lastUsedAt ? new Date(usage[id].lastUsedAt).getTime() : 0;
if (ts > latest) { latest = ts; modelId = id; }
}
if (modelId.includes('opus')) return 'Opus 4.6';
if (modelId.includes('sonnet')) return 'Sonnet 4.6';
if (modelId.includes('haiku')) return 'Haiku 4.5';
return modelId.split('-').slice(1, 3).join(' ');
}
}
break;
}
}
}
} catch { /* ignore */ }
// Fallback: settings.json model field
const settings = getSettings();
if (settings && settings.model) {
const m = settings.model;
if (m.includes('opus')) return 'Opus 4.6';
if (m.includes('sonnet')) return 'Sonnet 4.6';
if (m.includes('haiku')) return 'Haiku 4.5';
}
return 'Claude Code';
}
// Get learning stats from memory database (pure stat calls)
function getLearningStats() {
const memoryPaths = [
path.join(CWD, '.swarm', 'memory.db'),
path.join(CWD, '.claude-flow', 'memory.db'),
path.join(CWD, '.claude', 'memory.db'),
path.join(CWD, 'data', 'memory.db'),
path.join(CWD, '.agentdb', 'memory.db'),
];
for (const dbPath of memoryPaths) {
const stat = safeStat(dbPath);
if (stat) {
const sizeKB = stat.size / 1024;
const patterns = Math.floor(sizeKB / 2);
return {
patterns,
sessions: Math.max(1, Math.floor(patterns / 10)),
};
}
}
return { patterns, sessions, trajectories };
// Check session files count
let sessions = 0;
try {
const sessDir = path.join(CWD, '.claude', 'sessions');
if (fs.existsSync(sessDir)) {
sessions = fs.readdirSync(sessDir).filter(f => f.endsWith('.json')).length;
}
} catch { /* ignore */ }
return { patterns: 0, sessions };
}
// Get V3 progress from learning state (grows as system learns)
// V3 progress from metrics files (pure file reads)
function getV3Progress() {
const learning = getLearningStats();
// DDD progress based on actual learned patterns
// New install: 0 patterns = 0/5 domains, 0% DDD
// As patterns grow: 10+ patterns = 1 domain, 50+ = 2, 100+ = 3, 200+ = 4, 500+ = 5
let domainsCompleted = 0;
if (learning.patterns >= 500) domainsCompleted = 5;
else if (learning.patterns >= 200) domainsCompleted = 4;
else if (learning.patterns >= 100) domainsCompleted = 3;
else if (learning.patterns >= 50) domainsCompleted = 2;
else if (learning.patterns >= 10) domainsCompleted = 1;
const totalDomains = 5;
const dddProgress = Math.min(100, Math.floor((domainsCompleted / totalDomains) * 100));
const dddData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'ddd-progress.json'));
let dddProgress = dddData ? (dddData.progress || 0) : 0;
let domainsCompleted = Math.min(5, Math.floor(dddProgress / 20));
if (dddProgress === 0 && learning.patterns > 0) {
if (learning.patterns >= 500) domainsCompleted = 5;
else if (learning.patterns >= 200) domainsCompleted = 4;
else if (learning.patterns >= 100) domainsCompleted = 3;
else if (learning.patterns >= 50) domainsCompleted = 2;
else if (learning.patterns >= 10) domainsCompleted = 1;
dddProgress = Math.floor((domainsCompleted / totalDomains) * 100);
}
return {
domainsCompleted,
totalDomains,
dddProgress,
domainsCompleted, totalDomains, dddProgress,
patternsLearned: learning.patterns,
sessionsCompleted: learning.sessions
sessionsCompleted: learning.sessions,
};
}
// Get security status based on actual scans
// Security status (pure file reads)
function getSecurityStatus() {
// Check for security scan results in memory
const scanResultsPath = path.join(process.cwd(), '.claude', 'security-scans');
let cvesFixed = 0;
const totalCves = 3;
if (fs.existsSync(scanResultsPath)) {
try {
const scans = fs.readdirSync(scanResultsPath).filter(f => f.endsWith('.json'));
// Each successful scan file = 1 CVE addressed
cvesFixed = Math.min(totalCves, scans.length);
} catch (e) {
// Ignore
}
const auditData = readJSON(path.join(CWD, '.claude-flow', 'security', 'audit-status.json'));
if (auditData) {
return {
status: auditData.status || 'PENDING',
cvesFixed: auditData.cvesFixed || 0,
totalCves: auditData.totalCves || 3,
};
}
// Also check .swarm/security for audit results
const auditPath = path.join(process.cwd(), '.swarm', 'security');
if (fs.existsSync(auditPath)) {
try {
const audits = fs.readdirSync(auditPath).filter(f => f.includes('audit'));
cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
} catch (e) {
// Ignore
let cvesFixed = 0;
try {
const scanDir = path.join(CWD, '.claude', 'security-scans');
if (fs.existsSync(scanDir)) {
cvesFixed = Math.min(totalCves, fs.readdirSync(scanDir).filter(f => f.endsWith('.json')).length);
}
}
const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
} catch { /* ignore */ }
return {
status,
status: cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING',
cvesFixed,
totalCves,
};
}
// Get swarm status
// Swarm status (pure file reads, NO ps aux)
function getSwarmStatus() {
let activeAgents = 0;
let coordinationActive = false;
try {
const ps = execSync('ps aux 2>/dev/null | grep -c agentic-flow || echo "0"', { encoding: 'utf-8' });
activeAgents = Math.max(0, parseInt(ps.trim()) - 1);
coordinationActive = activeAgents > 0;
} catch (e) {
// Ignore errors
const activityData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'swarm-activity.json'));
if (activityData && activityData.swarm) {
return {
activeAgents: activityData.swarm.agent_count || 0,
maxAgents: CONFIG.maxAgents,
coordinationActive: activityData.swarm.coordination_active || activityData.swarm.active || false,
};
}
return {
activeAgents,
maxAgents: CONFIG.maxAgents,
coordinationActive,
};
const progressData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'v3-progress.json'));
if (progressData && progressData.swarm) {
return {
activeAgents: progressData.swarm.activeAgents || progressData.swarm.agent_count || 0,
maxAgents: progressData.swarm.totalAgents || CONFIG.maxAgents,
coordinationActive: progressData.swarm.active || (progressData.swarm.activeAgents > 0),
};
}
return { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false };
}
// Get system metrics (dynamic based on actual state)
// System metrics (uses process.memoryUsage() — no shell spawn)
function getSystemMetrics() {
let memoryMB = 0;
let subAgents = 0;
try {
const mem = execSync('ps aux | grep -E "(node|agentic|claude)" | grep -v grep | awk \'{sum += \$6} END {print int(sum/1024)}\'', { encoding: 'utf-8' });
memoryMB = parseInt(mem.trim()) || 0;
} catch (e) {
// Fallback
memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
}
// Get learning stats for intelligence %
const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
const learning = getLearningStats();
const agentdb = getAgentDBStats();
// Intelligence % based on learned patterns (0 patterns = 0%, 1000+ = 100%)
const intelligencePct = Math.min(100, Math.floor((learning.patterns / 10) * 1));
// Intelligence from learning.json
const learningData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'learning.json'));
let intelligencePct = 0;
let contextPct = 0;
// Context % based on session history (0 sessions = 0%, grows with usage)
const contextPct = Math.min(100, Math.floor(learning.sessions * 5));
// Count active sub-agents from process list
try {
const agents = execSync('ps aux 2>/dev/null | grep -c "claude-flow.*agent" || echo "0"', { encoding: 'utf-8' });
subAgents = Math.max(0, parseInt(agents.trim()) - 1);
} catch (e) {
// Ignore
if (learningData && learningData.intelligence && learningData.intelligence.score !== undefined) {
intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
} else {
const fromPatterns = learning.patterns > 0 ? Math.min(100, Math.floor(learning.patterns / 10)) : 0;
const fromVectors = agentdb.vectorCount > 0 ? Math.min(100, Math.floor(agentdb.vectorCount / 100)) : 0;
intelligencePct = Math.max(fromPatterns, fromVectors);
}
return {
memoryMB,
contextPct,
intelligencePct,
subAgents,
};
// Maturity fallback (pure fs checks, no git exec)
if (intelligencePct === 0) {
let score = 0;
if (fs.existsSync(path.join(CWD, '.claude'))) score += 15;
const srcDirs = ['src', 'lib', 'app', 'packages', 'v3'];
for (const d of srcDirs) { if (fs.existsSync(path.join(CWD, d))) { score += 15; break; } }
const testDirs = ['tests', 'test', '__tests__', 'spec'];
for (const d of testDirs) { if (fs.existsSync(path.join(CWD, d))) { score += 10; break; } }
const cfgFiles = ['package.json', 'tsconfig.json', 'pyproject.toml', 'Cargo.toml', 'go.mod'];
for (const f of cfgFiles) { if (fs.existsSync(path.join(CWD, f))) { score += 5; break; } }
intelligencePct = Math.min(100, score);
}
if (learningData && learningData.sessions && learningData.sessions.total !== undefined) {
contextPct = Math.min(100, learningData.sessions.total * 5);
} else {
contextPct = Math.min(100, Math.floor(learning.sessions * 5));
}
// Sub-agents from file metrics (no ps aux)
let subAgents = 0;
const activityData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'swarm-activity.json'));
if (activityData && activityData.processes && activityData.processes.estimated_agents) {
subAgents = activityData.processes.estimated_agents;
}
return { memoryMB, contextPct, intelligencePct, subAgents };
}
// Generate progress bar
// ADR status (count files only — don't read contents)
function getADRStatus() {
const complianceData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'adr-compliance.json'));
if (complianceData) {
const checks = complianceData.checks || {};
const total = Object.keys(checks).length;
const impl = Object.values(checks).filter(c => c.compliant).length;
return { count: total, implemented: impl, compliance: complianceData.compliance || 0 };
}
// Fallback: just count ADR files (don't read them)
const adrPaths = [
path.join(CWD, 'v3', 'implementation', 'adrs'),
path.join(CWD, 'docs', 'adrs'),
path.join(CWD, '.claude-flow', 'adrs'),
];
for (const adrPath of adrPaths) {
try {
if (fs.existsSync(adrPath)) {
const files = fs.readdirSync(adrPath).filter(f =>
f.endsWith('.md') && (f.startsWith('ADR-') || f.startsWith('adr-') || /^\d{4}-/.test(f))
);
const implemented = Math.floor(files.length * 0.7);
const compliance = files.length > 0 ? Math.floor((implemented / files.length) * 100) : 0;
return { count: files.length, implemented, compliance };
}
} catch { /* ignore */ }
}
return { count: 0, implemented: 0, compliance: 0 };
}
// Hooks status (shared settings cache)
function getHooksStatus() {
let enabled = 0;
const total = 17;
const settings = getSettings();
if (settings && settings.hooks) {
for (const category of Object.keys(settings.hooks)) {
const h = settings.hooks[category];
if (Array.isArray(h) && h.length > 0) enabled++;
}
}
try {
const hooksDir = path.join(CWD, '.claude', 'hooks');
if (fs.existsSync(hooksDir)) {
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.js') || f.endsWith('.sh')).length;
enabled = Math.max(enabled, hookFiles);
}
} catch { /* ignore */ }
return { enabled, total };
}
// AgentDB stats (pure stat calls)
function getAgentDBStats() {
let vectorCount = 0;
let dbSizeKB = 0;
let namespaces = 0;
let hasHnsw = false;
const dbFiles = [
path.join(CWD, '.swarm', 'memory.db'),
path.join(CWD, '.claude-flow', 'memory.db'),
path.join(CWD, '.claude', 'memory.db'),
path.join(CWD, 'data', 'memory.db'),
];
for (const f of dbFiles) {
const stat = safeStat(f);
if (stat) {
dbSizeKB = stat.size / 1024;
vectorCount = Math.floor(dbSizeKB / 2);
namespaces = 1;
break;
}
}
if (vectorCount === 0) {
const dbDirs = [
path.join(CWD, '.claude-flow', 'agentdb'),
path.join(CWD, '.swarm', 'agentdb'),
path.join(CWD, '.agentdb'),
];
for (const dir of dbDirs) {
try {
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
const files = fs.readdirSync(dir);
namespaces = files.filter(f => f.endsWith('.db') || f.endsWith('.sqlite')).length;
for (const file of files) {
const stat = safeStat(path.join(dir, file));
if (stat && stat.isFile()) dbSizeKB += stat.size / 1024;
}
vectorCount = Math.floor(dbSizeKB / 2);
break;
}
} catch { /* ignore */ }
}
}
const hnswPaths = [
path.join(CWD, '.swarm', 'hnsw.index'),
path.join(CWD, '.claude-flow', 'hnsw.index'),
];
for (const p of hnswPaths) {
const stat = safeStat(p);
if (stat) {
hasHnsw = true;
vectorCount = Math.max(vectorCount, Math.floor(stat.size / 512));
break;
}
}
return { vectorCount, dbSizeKB: Math.floor(dbSizeKB), namespaces, hasHnsw };
}
// Test stats (count files only — NO reading file contents)
function getTestStats() {
let testFiles = 0;
function countTestFiles(dir, depth) {
if (depth === undefined) depth = 0;
if (depth > 2) return;
try {
if (!fs.existsSync(dir)) return;
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
countTestFiles(path.join(dir, entry.name), depth + 1);
} else if (entry.isFile()) {
const n = entry.name;
if (n.includes('.test.') || n.includes('.spec.') || n.includes('_test.') || n.includes('_spec.')) {
testFiles++;
}
}
}
} catch { /* ignore */ }
}
var testDirNames = ['tests', 'test', '__tests__', 'v3/__tests__'];
for (var i = 0; i < testDirNames.length; i++) {
countTestFiles(path.join(CWD, testDirNames[i]));
}
countTestFiles(path.join(CWD, 'src'));
return { testFiles, testCases: testFiles * 4 };
}
// Integration status (shared settings + file checks)
function getIntegrationStatus() {
const mcpServers = { total: 0, enabled: 0 };
const settings = getSettings();
if (settings && settings.mcpServers && typeof settings.mcpServers === 'object') {
const servers = Object.keys(settings.mcpServers);
mcpServers.total = servers.length;
mcpServers.enabled = settings.enabledMcpjsonServers
? settings.enabledMcpjsonServers.filter(s => servers.includes(s)).length
: servers.length;
}
if (mcpServers.total === 0) {
const mcpConfig = readJSON(path.join(CWD, '.mcp.json'))
|| readJSON(path.join(os.homedir(), '.claude', 'mcp.json'));
if (mcpConfig && mcpConfig.mcpServers) {
const s = Object.keys(mcpConfig.mcpServers);
mcpServers.total = s.length;
mcpServers.enabled = s.length;
}
}
const hasDatabase = ['.swarm/memory.db', '.claude-flow/memory.db', 'data/memory.db']
.some(p => fs.existsSync(path.join(CWD, p)));
const hasApi = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
return { mcpServers, hasDatabase, hasApi };
}
// Session stats (pure file reads)
function getSessionStats() {
var sessionPaths = ['.claude-flow/session.json', '.claude/session.json'];
for (var i = 0; i < sessionPaths.length; i++) {
const data = readJSON(path.join(CWD, sessionPaths[i]));
if (data && data.startTime) {
const diffMs = Date.now() - new Date(data.startTime).getTime();
const mins = Math.floor(diffMs / 60000);
const duration = mins < 60 ? mins + 'm' : Math.floor(mins / 60) + 'h' + (mins % 60) + 'm';
return { duration: duration };
}
}
return { duration: '' };
}
// ─── Rendering ──────────────────────────────────────────────────
function progressBar(current, total) {
const width = 5;
const filled = Math.round((current / total) * width);
const empty = width - filled;
return '[' + '\u25CF'.repeat(filled) + '\u25CB'.repeat(empty) + ']';
return '[' + '\u25CF'.repeat(filled) + '\u25CB'.repeat(width - filled) + ']';
}
// Generate full statusline
function generateStatusline() {
const user = getUserInfo();
const git = getGitInfo();
// Prefer model name from Claude Code stdin data, fallback to file-based detection
const modelName = getModelFromStdin() || getModelName();
const ctxInfo = getContextFromStdin();
const costInfo = getCostFromStdin();
const progress = getV3Progress();
const security = getSecurityStatus();
const swarm = getSwarmStatus();
const system = getSystemMetrics();
const adrs = getADRStatus();
const hooks = getHooksStatus();
const agentdb = getAgentDBStats();
const tests = getTestStats();
const session = getSessionStats();
const integration = getIntegrationStatus();
const lines = [];
// Header Line
let header = `${c.bold}${c.brightPurple} Claude Flow V3 ${c.reset}`;
header += `${swarm.coordinationActive ? c.brightCyan : c.dim}${c.brightCyan}${user.name}${c.reset}`;
if (user.gitBranch) {
header += ` ${c.dim}${c.reset} ${c.brightBlue}${user.gitBranch}${c.reset}`;
// Header
let header = c.bold + c.brightPurple + '\u258A Claude Flow V3 ' + c.reset;
header += (swarm.coordinationActive ? c.brightCyan : c.dim) + '\u25CF ' + c.brightCyan + git.name + c.reset;
if (git.gitBranch) {
header += ' ' + c.dim + '\u2502' + c.reset + ' ' + c.brightBlue + '\u23C7 ' + git.gitBranch + c.reset;
const changes = git.modified + git.staged + git.untracked;
if (changes > 0) {
let ind = '';
if (git.staged > 0) ind += c.brightGreen + '+' + git.staged + c.reset;
if (git.modified > 0) ind += c.brightYellow + '~' + git.modified + c.reset;
if (git.untracked > 0) ind += c.dim + '?' + git.untracked + c.reset;
header += ' ' + ind;
}
if (git.ahead > 0) header += ' ' + c.brightGreen + '\u2191' + git.ahead + c.reset;
if (git.behind > 0) header += ' ' + c.brightRed + '\u2193' + git.behind + c.reset;
}
header += ' ' + c.dim + '\u2502' + c.reset + ' ' + c.purple + modelName + c.reset;
// Show session duration from Claude Code stdin if available, else from local files
const duration = costInfo ? costInfo.duration : session.duration;
if (duration) header += ' ' + c.dim + '\u2502' + c.reset + ' ' + c.cyan + '\u23F1 ' + duration + c.reset;
// Show context usage from Claude Code stdin if available
if (ctxInfo && ctxInfo.usedPct > 0) {
const ctxColor = ctxInfo.usedPct >= 90 ? c.brightRed : ctxInfo.usedPct >= 70 ? c.brightYellow : c.brightGreen;
header += ' ' + c.dim + '\u2502' + c.reset + ' ' + ctxColor + '\u25CF ' + ctxInfo.usedPct + '% ctx' + c.reset;
}
// Show cost from Claude Code stdin if available
if (costInfo && costInfo.costUsd > 0) {
header += ' ' + c.dim + '\u2502' + c.reset + ' ' + c.brightYellow + '$' + costInfo.costUsd.toFixed(2) + c.reset;
}
header += ` ${c.dim}${c.reset} ${c.purple}${user.modelName}${c.reset}`;
lines.push(header);
// Separator
lines.push(`${c.dim}─────────────────────────────────────────────────────${c.reset}`);
lines.push(c.dim + '\u2500'.repeat(53) + c.reset);
// Line 1: DDD Domain Progress
// Line 1: DDD Domains
const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
let perfIndicator;
if (agentdb.hasHnsw && agentdb.vectorCount > 0) {
const speedup = agentdb.vectorCount > 10000 ? '12500x' : agentdb.vectorCount > 1000 ? '150x' : '10x';
perfIndicator = c.brightGreen + '\u26A1 HNSW ' + speedup + c.reset;
} else if (progress.patternsLearned > 0) {
const pk = progress.patternsLearned >= 1000 ? (progress.patternsLearned / 1000).toFixed(1) + 'k' : String(progress.patternsLearned);
perfIndicator = c.brightYellow + '\uD83D\uDCDA ' + pk + ' patterns' + c.reset;
} else {
perfIndicator = c.dim + '\u26A1 target: 150x-12500x' + c.reset;
}
lines.push(
`${c.brightCyan}🏗️ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
`${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ` +
`${c.brightYellow}⚡ 1.0x${c.reset} ${c.dim}${c.reset} ${c.brightYellow}2.49x-7.47x${c.reset}`
c.brightCyan + '\uD83C\uDFD7\uFE0F DDD Domains' + c.reset + ' ' + progressBar(progress.domainsCompleted, progress.totalDomains) + ' ' +
domainsColor + progress.domainsCompleted + c.reset + '/' + c.brightWhite + progress.totalDomains + c.reset + ' ' + perfIndicator
);
// Line 2: Swarm + CVE + Memory + Context + Intelligence
const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}${c.reset}` : `${c.dim}${c.reset}`;
// Line 2: Swarm + Hooks + CVE + Memory + Intelligence
const swarmInd = swarm.coordinationActive ? c.brightGreen + '\u25C9' + c.reset : c.dim + '\u25CB' + c.reset;
const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
let securityIcon = security.status === 'CLEAN' ? '🟢' : security.status === 'IN_PROGRESS' ? '🟡' : '🔴';
let securityColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'IN_PROGRESS' ? c.brightYellow : c.brightRed;
const secIcon = security.status === 'CLEAN' ? '\uD83D\uDFE2' : security.status === 'IN_PROGRESS' ? '\uD83D\uDFE1' : '\uD83D\uDD34';
const secColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'IN_PROGRESS' ? c.brightYellow : c.brightRed;
const hooksColor = hooks.enabled > 0 ? c.brightGreen : c.dim;
const intellColor = system.intelligencePct >= 80 ? c.brightGreen : system.intelligencePct >= 40 ? c.brightYellow : c.dim;
lines.push(
`${c.brightYellow}🤖 Swarm${c.reset} ${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
`${c.brightPurple}👥 ${system.subAgents}${c.reset} ` +
`${securityIcon} ${securityColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
`${c.brightCyan}💾 ${system.memoryMB}MB${c.reset} ` +
`${c.brightGreen}📂 ${String(system.contextPct).padStart(3)}%${c.reset} ` +
`${c.dim}🧠 ${String(system.intelligencePct).padStart(3)}%${c.reset}`
c.brightYellow + '\uD83E\uDD16 Swarm' + c.reset + ' ' + swarmInd + ' [' + agentsColor + String(swarm.activeAgents).padStart(2) + c.reset + '/' + c.brightWhite + swarm.maxAgents + c.reset + '] ' +
c.brightPurple + '\uD83D\uDC65 ' + system.subAgents + c.reset + ' ' +
c.brightBlue + '\uD83E\uDE9D ' + hooksColor + hooks.enabled + c.reset + '/' + c.brightWhite + hooks.total + c.reset + ' ' +
secIcon + ' ' + secColor + 'CVE ' + security.cvesFixed + c.reset + '/' + c.brightWhite + security.totalCves + c.reset + ' ' +
c.brightCyan + '\uD83D\uDCBE ' + system.memoryMB + 'MB' + c.reset + ' ' +
intellColor + '\uD83E\uDDE0 ' + String(system.intelligencePct).padStart(3) + '%' + c.reset
);
// Line 3: Architecture status
// Line 3: Architecture
const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
const adrColor = adrs.count > 0 ? (adrs.implemented === adrs.count ? c.brightGreen : c.yellow) : c.dim;
const adrDisplay = adrs.compliance > 0 ? adrColor + '\u25CF' + adrs.compliance + '%' + c.reset : adrColor + '\u25CF' + adrs.implemented + '/' + adrs.count + c.reset;
lines.push(
`${c.brightPurple}🔧 Architecture${c.reset} ` +
`${c.cyan}DDD${c.reset} ${dddColor}${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}${c.reset} ` +
`${c.cyan}Security${c.reset} ${securityColor}${security.status}${c.reset} ${c.dim}${c.reset} ` +
`${c.cyan}Memory${c.reset} ${c.brightGreen}●AgentDB${c.reset} ${c.dim}${c.reset} ` +
`${c.cyan}Integration${c.reset} ${swarm.coordinationActive ? c.brightCyan : c.dim}${c.reset}`
c.brightPurple + '\uD83D\uDD27 Architecture' + c.reset + ' ' +
c.cyan + 'ADRs' + c.reset + ' ' + adrDisplay + ' ' + c.dim + '\u2502' + c.reset + ' ' +
c.cyan + 'DDD' + c.reset + ' ' + dddColor + '\u25CF' + String(progress.dddProgress).padStart(3) + '%' + c.reset + ' ' + c.dim + '\u2502' + c.reset + ' ' +
c.cyan + 'Security' + c.reset + ' ' + secColor + '\u25CF' + security.status + c.reset
);
// Line 4: AgentDB, Tests, Integration
const hnswInd = agentdb.hasHnsw ? c.brightGreen + '\u26A1' + c.reset : '';
const sizeDisp = agentdb.dbSizeKB >= 1024 ? (agentdb.dbSizeKB / 1024).toFixed(1) + 'MB' : agentdb.dbSizeKB + 'KB';
const vectorColor = agentdb.vectorCount > 0 ? c.brightGreen : c.dim;
const testColor = tests.testFiles > 0 ? c.brightGreen : c.dim;
let integStr = '';
if (integration.mcpServers.total > 0) {
const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen :
integration.mcpServers.enabled > 0 ? c.brightYellow : c.red;
integStr += c.cyan + 'MCP' + c.reset + ' ' + mcpCol + '\u25CF' + integration.mcpServers.enabled + '/' + integration.mcpServers.total + c.reset;
}
if (integration.hasDatabase) integStr += (integStr ? ' ' : '') + c.brightGreen + '\u25C6' + c.reset + 'DB';
if (integration.hasApi) integStr += (integStr ? ' ' : '') + c.brightGreen + '\u25C6' + c.reset + 'API';
if (!integStr) integStr = c.dim + '\u25CF none' + c.reset;
lines.push(
c.brightCyan + '\uD83D\uDCCA AgentDB' + c.reset + ' ' +
c.cyan + 'Vectors' + c.reset + ' ' + vectorColor + '\u25CF' + agentdb.vectorCount + hnswInd + c.reset + ' ' + c.dim + '\u2502' + c.reset + ' ' +
c.cyan + 'Size' + c.reset + ' ' + c.brightWhite + sizeDisp + c.reset + ' ' + c.dim + '\u2502' + c.reset + ' ' +
c.cyan + 'Tests' + c.reset + ' ' + testColor + '\u25CF' + tests.testFiles + c.reset + ' ' + c.dim + '(~' + tests.testCases + ' cases)' + c.reset + ' ' + c.dim + '\u2502' + c.reset + ' ' +
integStr
);
return lines.join('\n');
}
// Generate JSON data
// JSON output
function generateJSON() {
const git = getGitInfo();
return {
user: getUserInfo(),
user: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
v3Progress: getV3Progress(),
security: getSecurityStatus(),
swarm: getSwarmStatus(),
system: getSystemMetrics(),
performance: {
flashAttentionTarget: '2.49x-7.47x',
searchImprovement: '150x-12,500x',
memoryReduction: '50-75%',
},
adrs: getADRStatus(),
hooks: getHooksStatus(),
agentdb: getAgentDBStats(),
tests: getTestStats(),
git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
lastUpdated: new Date().toISOString(),
};
}
// Main
// ─── Stdin reader (Claude Code pipes session JSON) ──────────────
// Claude Code sends session JSON via stdin (model, context, cost, etc.)
// Read it synchronously so the script works both:
// 1. When invoked by Claude Code (stdin has JSON)
// 2. When invoked manually from terminal (stdin is empty/tty)
let _stdinData = null;
function getStdinData() {
if (_stdinData !== undefined && _stdinData !== null) return _stdinData;
try {
// Check if stdin is a TTY (manual run) — skip reading
if (process.stdin.isTTY) { _stdinData = null; return null; }
// Read stdin synchronously via fd 0
const chunks = [];
const buf = Buffer.alloc(4096);
let bytesRead;
try {
while ((bytesRead = fs.readSync(0, buf, 0, buf.length, null)) > 0) {
chunks.push(buf.slice(0, bytesRead));
}
} catch { /* EOF or read error */ }
const raw = Buffer.concat(chunks).toString('utf-8').trim();
if (raw && raw.startsWith('{')) {
_stdinData = JSON.parse(raw);
} else {
_stdinData = null;
}
} catch {
_stdinData = null;
}
return _stdinData;
}
// Override model detection to prefer stdin data from Claude Code
function getModelFromStdin() {
const data = getStdinData();
if (data && data.model && data.model.display_name) return data.model.display_name;
return null;
}
// Get context window info from Claude Code session
function getContextFromStdin() {
const data = getStdinData();
if (data && data.context_window) {
return {
usedPct: Math.floor(data.context_window.used_percentage || 0),
remainingPct: Math.floor(data.context_window.remaining_percentage || 100),
};
}
return null;
}
// Get cost info from Claude Code session
function getCostFromStdin() {
const data = getStdinData();
if (data && data.cost) {
const durationMs = data.cost.total_duration_ms || 0;
const mins = Math.floor(durationMs / 60000);
const secs = Math.floor((durationMs % 60000) / 1000);
return {
costUsd: data.cost.total_cost_usd || 0,
duration: mins > 0 ? mins + 'm' + secs + 's' : secs + 's',
linesAdded: data.cost.total_lines_added || 0,
linesRemoved: data.cost.total_lines_removed || 0,
};
}
return null;
}
// ─── Main ───────────────────────────────────────────────────────
if (process.argv.includes('--json')) {
console.log(JSON.stringify(generateJSON(), null, 2));
} else if (process.argv.includes('--compact')) {

View File

@@ -18,7 +18,7 @@ const CONFIG = {
showSwarm: true,
showHooks: true,
showPerformance: true,
refreshInterval: 5000,
refreshInterval: 30000,
maxAgents: 15,
topology: 'hierarchical-mesh',
};