@claude-flow/cli 3.1.0-alpha.2 → 3.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/auto-memory-hook.mjs +350 -0
- package/.claude/helpers/hook-handler.cjs +173 -0
- package/.claude/settings.json +86 -141
- package/README.md +603 -353
- package/bin/cli.js +6 -2
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +209 -2
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +190 -4
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/memory.d.ts.map +1 -1
- package/dist/src/commands/memory.js +12 -2
- package/dist/src/commands/memory.js.map +1 -1
- package/dist/src/init/executor.d.ts +8 -2
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +247 -5
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/settings-generator.d.ts.map +1 -1
- package/dist/src/init/settings-generator.js +146 -14
- package/dist/src/init/settings-generator.js.map +1 -1
- package/dist/src/init/types.d.ts +10 -0
- package/dist/src/init/types.d.ts.map +1 -1
- package/dist/src/init/types.js +11 -0
- package/dist/src/init/types.js.map +1 -1
- package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +4 -1
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/memory/memory-initializer.d.ts +1 -0
- package/dist/src/memory/memory-initializer.d.ts.map +1 -1
- package/dist/src/memory/memory-initializer.js +14 -9
- package/dist/src/memory/memory-initializer.js.map +1 -1
- package/dist/src/services/headless-worker-executor.js +3 -3
- package/dist/src/services/headless-worker-executor.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Auto Memory Bridge Hook (ADR-048/049)
|
|
4
|
+
*
|
|
5
|
+
* Wires AutoMemoryBridge + LearningBridge + MemoryGraph into Claude Code
|
|
6
|
+
* session lifecycle. Called by settings.json SessionStart/SessionEnd hooks.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node auto-memory-hook.mjs import # SessionStart: import auto memory files into backend
|
|
10
|
+
* node auto-memory-hook.mjs sync # SessionEnd: sync insights back to MEMORY.md
|
|
11
|
+
* node auto-memory-hook.mjs status # Show bridge status
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
15
|
+
import { join, dirname } from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
const PROJECT_ROOT = join(__dirname, '../..');
|
|
21
|
+
const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
|
|
22
|
+
const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
|
|
23
|
+
|
|
24
|
+
// Colors
|
|
25
|
+
const GREEN = '\x1b[0;32m';
|
|
26
|
+
const CYAN = '\x1b[0;36m';
|
|
27
|
+
const DIM = '\x1b[2m';
|
|
28
|
+
const RESET = '\x1b[0m';
|
|
29
|
+
|
|
30
|
+
const log = (msg) => console.log(`${CYAN}[AutoMemory] ${msg}${RESET}`);
|
|
31
|
+
const success = (msg) => console.log(`${GREEN}[AutoMemory] ✓ ${msg}${RESET}`);
|
|
32
|
+
const dim = (msg) => console.log(` ${DIM}${msg}${RESET}`);
|
|
33
|
+
|
|
34
|
+
// Ensure data dir
|
|
35
|
+
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Simple JSON File Backend (implements IMemoryBackend interface)
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
class JsonFileBackend {
|
|
42
|
+
constructor(filePath) {
|
|
43
|
+
this.filePath = filePath;
|
|
44
|
+
this.entries = new Map();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async initialize() {
|
|
48
|
+
if (existsSync(this.filePath)) {
|
|
49
|
+
try {
|
|
50
|
+
const data = JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
|
51
|
+
if (Array.isArray(data)) {
|
|
52
|
+
for (const entry of data) this.entries.set(entry.id, entry);
|
|
53
|
+
}
|
|
54
|
+
} catch { /* start fresh */ }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async shutdown() { this._persist(); }
|
|
59
|
+
async store(entry) { this.entries.set(entry.id, entry); this._persist(); }
|
|
60
|
+
async get(id) { return this.entries.get(id) ?? null; }
|
|
61
|
+
async getByKey(key, ns) {
|
|
62
|
+
for (const e of this.entries.values()) {
|
|
63
|
+
if (e.key === key && (!ns || e.namespace === ns)) return e;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
async update(id, updates) {
|
|
68
|
+
const e = this.entries.get(id);
|
|
69
|
+
if (!e) return null;
|
|
70
|
+
if (updates.metadata) Object.assign(e.metadata, updates.metadata);
|
|
71
|
+
if (updates.content !== undefined) e.content = updates.content;
|
|
72
|
+
if (updates.tags) e.tags = updates.tags;
|
|
73
|
+
e.updatedAt = Date.now();
|
|
74
|
+
this._persist();
|
|
75
|
+
return e;
|
|
76
|
+
}
|
|
77
|
+
async delete(id) { return this.entries.delete(id); }
|
|
78
|
+
async query(opts) {
|
|
79
|
+
let results = [...this.entries.values()];
|
|
80
|
+
if (opts?.namespace) results = results.filter(e => e.namespace === opts.namespace);
|
|
81
|
+
if (opts?.type) results = results.filter(e => e.type === opts.type);
|
|
82
|
+
if (opts?.limit) results = results.slice(0, opts.limit);
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
async search() { return []; } // No vector search in JSON backend
|
|
86
|
+
async bulkInsert(entries) { for (const e of entries) this.entries.set(e.id, e); this._persist(); }
|
|
87
|
+
async bulkDelete(ids) { let n = 0; for (const id of ids) { if (this.entries.delete(id)) n++; } this._persist(); return n; }
|
|
88
|
+
async count() { return this.entries.size; }
|
|
89
|
+
async listNamespaces() {
|
|
90
|
+
const ns = new Set();
|
|
91
|
+
for (const e of this.entries.values()) ns.add(e.namespace || 'default');
|
|
92
|
+
return [...ns];
|
|
93
|
+
}
|
|
94
|
+
async clearNamespace(ns) {
|
|
95
|
+
let n = 0;
|
|
96
|
+
for (const [id, e] of this.entries) {
|
|
97
|
+
if (e.namespace === ns) { this.entries.delete(id); n++; }
|
|
98
|
+
}
|
|
99
|
+
this._persist();
|
|
100
|
+
return n;
|
|
101
|
+
}
|
|
102
|
+
async getStats() {
|
|
103
|
+
return {
|
|
104
|
+
totalEntries: this.entries.size,
|
|
105
|
+
entriesByNamespace: {},
|
|
106
|
+
entriesByType: { semantic: 0, episodic: 0, procedural: 0, working: 0, cache: 0 },
|
|
107
|
+
memoryUsage: 0, avgQueryTime: 0, avgSearchTime: 0,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async healthCheck() {
|
|
111
|
+
return {
|
|
112
|
+
status: 'healthy',
|
|
113
|
+
components: {
|
|
114
|
+
storage: { status: 'healthy', latency: 0 },
|
|
115
|
+
index: { status: 'healthy', latency: 0 },
|
|
116
|
+
cache: { status: 'healthy', latency: 0 },
|
|
117
|
+
},
|
|
118
|
+
timestamp: Date.now(), issues: [], recommendations: [],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_persist() {
|
|
123
|
+
try {
|
|
124
|
+
writeFileSync(this.filePath, JSON.stringify([...this.entries.values()], null, 2), 'utf-8');
|
|
125
|
+
} catch { /* best effort */ }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// Resolve memory package path (local dev or npm installed)
|
|
131
|
+
// ============================================================================
|
|
132
|
+
|
|
133
|
+
async function loadMemoryPackage() {
|
|
134
|
+
// Strategy 1: Local dev (built dist)
|
|
135
|
+
const localDist = join(PROJECT_ROOT, 'v3/@claude-flow/memory/dist/index.js');
|
|
136
|
+
if (existsSync(localDist)) {
|
|
137
|
+
try {
|
|
138
|
+
return await import(`file://${localDist}`);
|
|
139
|
+
} catch { /* fall through */ }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Strategy 2: npm installed @claude-flow/memory
|
|
143
|
+
try {
|
|
144
|
+
return await import('@claude-flow/memory');
|
|
145
|
+
} catch { /* fall through */ }
|
|
146
|
+
|
|
147
|
+
// Strategy 3: Installed via @claude-flow/cli which includes memory
|
|
148
|
+
const cliMemory = join(PROJECT_ROOT, 'node_modules/@claude-flow/memory/dist/index.js');
|
|
149
|
+
if (existsSync(cliMemory)) {
|
|
150
|
+
try {
|
|
151
|
+
return await import(`file://${cliMemory}`);
|
|
152
|
+
} catch { /* fall through */ }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Read config from .claude-flow/config.yaml
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
function readConfig() {
|
|
163
|
+
const configPath = join(PROJECT_ROOT, '.claude-flow', 'config.yaml');
|
|
164
|
+
const defaults = {
|
|
165
|
+
learningBridge: { enabled: true, sonaMode: 'balanced', confidenceDecayRate: 0.005, accessBoostAmount: 0.03, consolidationThreshold: 10 },
|
|
166
|
+
memoryGraph: { enabled: true, pageRankDamping: 0.85, maxNodes: 5000, similarityThreshold: 0.8 },
|
|
167
|
+
agentScopes: { enabled: true, defaultScope: 'project' },
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if (!existsSync(configPath)) return defaults;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const yaml = readFileSync(configPath, 'utf-8');
|
|
174
|
+
// Simple YAML parser for the memory section
|
|
175
|
+
const getBool = (key) => {
|
|
176
|
+
const match = yaml.match(new RegExp(`${key}:\\s*(true|false)`, 'i'));
|
|
177
|
+
return match ? match[1] === 'true' : undefined;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const lbEnabled = getBool('learningBridge[\\s\\S]*?enabled');
|
|
181
|
+
if (lbEnabled !== undefined) defaults.learningBridge.enabled = lbEnabled;
|
|
182
|
+
|
|
183
|
+
const mgEnabled = getBool('memoryGraph[\\s\\S]*?enabled');
|
|
184
|
+
if (mgEnabled !== undefined) defaults.memoryGraph.enabled = mgEnabled;
|
|
185
|
+
|
|
186
|
+
const asEnabled = getBool('agentScopes[\\s\\S]*?enabled');
|
|
187
|
+
if (asEnabled !== undefined) defaults.agentScopes.enabled = asEnabled;
|
|
188
|
+
|
|
189
|
+
return defaults;
|
|
190
|
+
} catch {
|
|
191
|
+
return defaults;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// Commands
|
|
197
|
+
// ============================================================================
|
|
198
|
+
|
|
199
|
+
async function doImport() {
|
|
200
|
+
log('Importing auto memory files into bridge...');
|
|
201
|
+
|
|
202
|
+
const memPkg = await loadMemoryPackage();
|
|
203
|
+
if (!memPkg || !memPkg.AutoMemoryBridge) {
|
|
204
|
+
dim('Memory package not available — skipping auto memory import');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const config = readConfig();
|
|
209
|
+
const backend = new JsonFileBackend(STORE_PATH);
|
|
210
|
+
await backend.initialize();
|
|
211
|
+
|
|
212
|
+
const bridgeConfig = {
|
|
213
|
+
workingDir: PROJECT_ROOT,
|
|
214
|
+
syncMode: 'on-session-end',
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Wire learning if enabled and available
|
|
218
|
+
if (config.learningBridge.enabled && memPkg.LearningBridge) {
|
|
219
|
+
bridgeConfig.learning = {
|
|
220
|
+
sonaMode: config.learningBridge.sonaMode,
|
|
221
|
+
confidenceDecayRate: config.learningBridge.confidenceDecayRate,
|
|
222
|
+
accessBoostAmount: config.learningBridge.accessBoostAmount,
|
|
223
|
+
consolidationThreshold: config.learningBridge.consolidationThreshold,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Wire graph if enabled and available
|
|
228
|
+
if (config.memoryGraph.enabled && memPkg.MemoryGraph) {
|
|
229
|
+
bridgeConfig.graph = {
|
|
230
|
+
pageRankDamping: config.memoryGraph.pageRankDamping,
|
|
231
|
+
maxNodes: config.memoryGraph.maxNodes,
|
|
232
|
+
similarityThreshold: config.memoryGraph.similarityThreshold,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const bridge = new memPkg.AutoMemoryBridge(backend, bridgeConfig);
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const result = await bridge.importFromAutoMemory();
|
|
240
|
+
success(`Imported ${result.imported} entries (${result.skipped} skipped)`);
|
|
241
|
+
dim(`├─ Backend entries: ${await backend.count()}`);
|
|
242
|
+
dim(`├─ Learning: ${config.learningBridge.enabled ? 'active' : 'disabled'}`);
|
|
243
|
+
dim(`├─ Graph: ${config.memoryGraph.enabled ? 'active' : 'disabled'}`);
|
|
244
|
+
dim(`└─ Agent scopes: ${config.agentScopes.enabled ? 'active' : 'disabled'}`);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
dim(`Import failed (non-critical): ${err.message}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await backend.shutdown();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function doSync() {
|
|
253
|
+
log('Syncing insights to auto memory files...');
|
|
254
|
+
|
|
255
|
+
const memPkg = await loadMemoryPackage();
|
|
256
|
+
if (!memPkg || !memPkg.AutoMemoryBridge) {
|
|
257
|
+
dim('Memory package not available — skipping sync');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const config = readConfig();
|
|
262
|
+
const backend = new JsonFileBackend(STORE_PATH);
|
|
263
|
+
await backend.initialize();
|
|
264
|
+
|
|
265
|
+
const entryCount = await backend.count();
|
|
266
|
+
if (entryCount === 0) {
|
|
267
|
+
dim('No entries to sync');
|
|
268
|
+
await backend.shutdown();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const bridgeConfig = {
|
|
273
|
+
workingDir: PROJECT_ROOT,
|
|
274
|
+
syncMode: 'on-session-end',
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
if (config.learningBridge.enabled && memPkg.LearningBridge) {
|
|
278
|
+
bridgeConfig.learning = {
|
|
279
|
+
sonaMode: config.learningBridge.sonaMode,
|
|
280
|
+
confidenceDecayRate: config.learningBridge.confidenceDecayRate,
|
|
281
|
+
consolidationThreshold: config.learningBridge.consolidationThreshold,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (config.memoryGraph.enabled && memPkg.MemoryGraph) {
|
|
286
|
+
bridgeConfig.graph = {
|
|
287
|
+
pageRankDamping: config.memoryGraph.pageRankDamping,
|
|
288
|
+
maxNodes: config.memoryGraph.maxNodes,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const bridge = new memPkg.AutoMemoryBridge(backend, bridgeConfig);
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const syncResult = await bridge.syncToAutoMemory();
|
|
296
|
+
success(`Synced ${syncResult.synced} entries to auto memory`);
|
|
297
|
+
dim(`├─ Categories updated: ${syncResult.categories?.join(', ') || 'none'}`);
|
|
298
|
+
dim(`└─ Backend entries: ${entryCount}`);
|
|
299
|
+
|
|
300
|
+
// Curate MEMORY.md index with graph-aware ordering
|
|
301
|
+
await bridge.curateIndex();
|
|
302
|
+
success('Curated MEMORY.md index');
|
|
303
|
+
} catch (err) {
|
|
304
|
+
dim(`Sync failed (non-critical): ${err.message}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (bridge.destroy) bridge.destroy();
|
|
308
|
+
await backend.shutdown();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function doStatus() {
|
|
312
|
+
const memPkg = await loadMemoryPackage();
|
|
313
|
+
const config = readConfig();
|
|
314
|
+
|
|
315
|
+
console.log('\n=== Auto Memory Bridge Status ===\n');
|
|
316
|
+
console.log(` Package: ${memPkg ? '✅ Available' : '❌ Not found'}`);
|
|
317
|
+
console.log(` Store: ${existsSync(STORE_PATH) ? '✅ ' + STORE_PATH : '⏸ Not initialized'}`);
|
|
318
|
+
console.log(` LearningBridge: ${config.learningBridge.enabled ? '✅ Enabled' : '⏸ Disabled'}`);
|
|
319
|
+
console.log(` MemoryGraph: ${config.memoryGraph.enabled ? '✅ Enabled' : '⏸ Disabled'}`);
|
|
320
|
+
console.log(` AgentScopes: ${config.agentScopes.enabled ? '✅ Enabled' : '⏸ Disabled'}`);
|
|
321
|
+
|
|
322
|
+
if (existsSync(STORE_PATH)) {
|
|
323
|
+
try {
|
|
324
|
+
const data = JSON.parse(readFileSync(STORE_PATH, 'utf-8'));
|
|
325
|
+
console.log(` Entries: ${Array.isArray(data) ? data.length : 0}`);
|
|
326
|
+
} catch { /* ignore */ }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
console.log('');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ============================================================================
|
|
333
|
+
// Main
|
|
334
|
+
// ============================================================================
|
|
335
|
+
|
|
336
|
+
const command = process.argv[2] || 'status';
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
switch (command) {
|
|
340
|
+
case 'import': await doImport(); break;
|
|
341
|
+
case 'sync': await doSync(); break;
|
|
342
|
+
case 'status': await doStatus(); break;
|
|
343
|
+
default:
|
|
344
|
+
console.log('Usage: auto-memory-hook.mjs <import|sync|status>');
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
} catch (err) {
|
|
348
|
+
// Hooks must never crash Claude Code - fail silently
|
|
349
|
+
dim(`Error (non-critical): ${err.message}`);
|
|
350
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Flow Hook Handler
|
|
4
|
+
* Cross-platform CommonJS dispatcher for Claude Code hooks.
|
|
5
|
+
* Delegates to router.js, session.js, memory.js helpers with
|
|
6
|
+
* console suppression during require() to prevent noisy output.
|
|
7
|
+
*
|
|
8
|
+
* Usage: node .claude/helpers/hook-handler.cjs <command> [args...]
|
|
9
|
+
*
|
|
10
|
+
* Commands:
|
|
11
|
+
* route - Route task to optimal agent (UserPromptSubmit)
|
|
12
|
+
* pre-bash - Pre-command safety check (PreToolUse:Bash)
|
|
13
|
+
* post-edit - Post-edit learning (PostToolUse:Write|Edit)
|
|
14
|
+
* session-start - Start new session (SessionStart)
|
|
15
|
+
* session-restore - Restore previous session (SessionStart:resume)
|
|
16
|
+
* session-end - End session, persist state (SessionEnd)
|
|
17
|
+
* memory-import - Import auto memory entries (SessionStart)
|
|
18
|
+
* memory-sync - Sync memory to files (SessionEnd/Stop)
|
|
19
|
+
* status - Show hook handler status
|
|
20
|
+
*/
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Require a helper module with console suppressed to prevent
|
|
29
|
+
* CLI output from the module's top-level code.
|
|
30
|
+
*/
|
|
31
|
+
function quietRequire(modulePath) {
|
|
32
|
+
const origLog = console.log;
|
|
33
|
+
const origErr = console.error;
|
|
34
|
+
const origWarn = console.warn;
|
|
35
|
+
try {
|
|
36
|
+
console.log = () => {};
|
|
37
|
+
console.error = () => {};
|
|
38
|
+
console.warn = () => {};
|
|
39
|
+
return require(modulePath);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
} finally {
|
|
43
|
+
console.log = origLog;
|
|
44
|
+
console.error = origErr;
|
|
45
|
+
console.warn = origWarn;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Try to load auto-memory-hook.mjs (ESM) via dynamic import.
|
|
51
|
+
* Falls back gracefully if unavailable.
|
|
52
|
+
*/
|
|
53
|
+
async function runAutoMemory(command) {
|
|
54
|
+
try {
|
|
55
|
+
const hookPath = path.join(__dirname, 'auto-memory-hook.mjs');
|
|
56
|
+
// Dynamic import for ESM module
|
|
57
|
+
const mod = await import('file://' + hookPath.replace(/\\/g, '/'));
|
|
58
|
+
if (typeof mod.default === 'function') {
|
|
59
|
+
await mod.default(command);
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// auto-memory-hook not available — non-critical
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Command handlers ─────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
const commands = {
|
|
69
|
+
'route': () => {
|
|
70
|
+
const router = quietRequire(path.join(__dirname, 'router.js'));
|
|
71
|
+
if (!router) return;
|
|
72
|
+
// Read task from stdin env or use a generic route
|
|
73
|
+
const task = process.env.USER_PROMPT || process.argv.slice(3).join(' ') || '';
|
|
74
|
+
if (task && router.routeTask) {
|
|
75
|
+
const result = router.routeTask(task);
|
|
76
|
+
if (result) {
|
|
77
|
+
console.log(`Routed to: ${result.agent} (confidence: ${result.confidence})`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
'pre-bash': () => {
|
|
83
|
+
// Lightweight safety check — just validates the command isn't destructive
|
|
84
|
+
const cmd = process.env.TOOL_INPUT || process.argv[3] || '';
|
|
85
|
+
const dangerous = /rm\s+-rf\s+[\/~]|mkfs|dd\s+if=|>\s*\/dev\/sd|shutdown|reboot/i;
|
|
86
|
+
if (dangerous.test(cmd)) {
|
|
87
|
+
console.error('BLOCKED: Potentially destructive command detected');
|
|
88
|
+
process.exit(2);
|
|
89
|
+
}
|
|
90
|
+
// Pass — no output means approved
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
'post-edit': () => {
|
|
94
|
+
const session = quietRequire(path.join(__dirname, 'session.js'));
|
|
95
|
+
if (session && session.metric) {
|
|
96
|
+
session.metric('edits');
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
'session-start': () => {
|
|
101
|
+
const session = quietRequire(path.join(__dirname, 'session.js'));
|
|
102
|
+
if (session && session.start) {
|
|
103
|
+
session.start();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
'session-restore': () => {
|
|
108
|
+
const session = quietRequire(path.join(__dirname, 'session.js'));
|
|
109
|
+
if (!session) return;
|
|
110
|
+
// Try restore first, fall back to start
|
|
111
|
+
if (session.restore) {
|
|
112
|
+
const restored = session.restore();
|
|
113
|
+
if (restored) {
|
|
114
|
+
console.log(`Session restored: ${restored.id}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (session.start) {
|
|
119
|
+
const s = session.start();
|
|
120
|
+
if (s) console.log(`Session started: ${s.id}`);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
'session-end': async () => {
|
|
125
|
+
const session = quietRequire(path.join(__dirname, 'session.js'));
|
|
126
|
+
if (session && session.end) {
|
|
127
|
+
session.end();
|
|
128
|
+
}
|
|
129
|
+
// Also sync auto-memory
|
|
130
|
+
await runAutoMemory('sync');
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
'memory-import': async () => {
|
|
134
|
+
await runAutoMemory('import');
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
'memory-sync': async () => {
|
|
138
|
+
await runAutoMemory('sync');
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
'status': () => {
|
|
142
|
+
const helpers = ['router.js', 'session.js', 'memory.js', 'auto-memory-hook.mjs', 'statusline.cjs'];
|
|
143
|
+
const fs = require('fs');
|
|
144
|
+
|
|
145
|
+
console.log('Hook Handler Status');
|
|
146
|
+
console.log('-------------------');
|
|
147
|
+
for (const h of helpers) {
|
|
148
|
+
const exists = fs.existsSync(path.join(__dirname, h));
|
|
149
|
+
console.log(` ${exists ? 'OK' : 'MISSING'} ${h}`);
|
|
150
|
+
}
|
|
151
|
+
console.log(` Platform: ${process.platform}`);
|
|
152
|
+
console.log(` Node: ${process.version}`);
|
|
153
|
+
console.log(` CWD: ${process.cwd()}`);
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// ── Main ─────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
const command = process.argv[2];
|
|
160
|
+
|
|
161
|
+
if (command && commands[command]) {
|
|
162
|
+
const result = commands[command]();
|
|
163
|
+
// Handle async commands
|
|
164
|
+
if (result && typeof result.then === 'function') {
|
|
165
|
+
result.catch(() => {});
|
|
166
|
+
}
|
|
167
|
+
} else if (command) {
|
|
168
|
+
// Unknown command — silent pass (don't break hooks)
|
|
169
|
+
process.exit(0);
|
|
170
|
+
} else {
|
|
171
|
+
console.log('Usage: hook-handler.cjs <command> [args...]');
|
|
172
|
+
console.log('Commands: ' + Object.keys(commands).join(', '));
|
|
173
|
+
}
|