deepflow 0.1.103 → 0.1.105
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/bin/install-dynamic-hooks.test.js +461 -0
- package/bin/install.js +171 -250
- package/bin/install.test.js +205 -0
- package/bin/lineage-ingest.js +70 -0
- package/hooks/df-check-update.js +1 -0
- package/hooks/df-command-usage.js +18 -0
- package/hooks/df-dashboard-push.js +5 -4
- package/hooks/df-dashboard-push.test.js +256 -0
- package/hooks/df-execution-history.js +1 -0
- package/hooks/df-explore-protocol.js +83 -0
- package/hooks/df-explore-protocol.test.js +228 -0
- package/hooks/df-hook-event-tags.test.js +127 -0
- package/hooks/df-invariant-check.js +4 -3
- package/hooks/df-invariant-check.test.js +141 -0
- package/hooks/df-quota-logger.js +12 -23
- package/hooks/df-quota-logger.test.js +324 -0
- package/hooks/df-snapshot-guard.js +1 -0
- package/hooks/df-spec-lint.js +58 -1
- package/hooks/df-spec-lint.test.js +412 -0
- package/hooks/df-statusline.js +1 -0
- package/hooks/df-subagent-registry.js +1 -0
- package/hooks/df-tool-usage.js +13 -3
- package/hooks/df-worktree-guard.js +1 -0
- package/package.json +1 -1
- package/src/commands/df/debate.md +1 -1
- package/src/commands/df/eval.md +117 -0
- package/src/commands/df/execute.md +1 -1
- package/src/commands/df/fix.md +104 -0
- package/src/eval/git-memory.js +159 -0
- package/src/eval/git-memory.test.js +439 -0
- package/src/eval/hypothesis.js +80 -0
- package/src/eval/hypothesis.test.js +169 -0
- package/src/eval/loop.js +378 -0
- package/src/eval/loop.test.js +306 -0
- package/src/eval/metric-collector.js +163 -0
- package/src/eval/metric-collector.test.js +369 -0
- package/src/eval/metric-pivot.js +119 -0
- package/src/eval/metric-pivot.test.js +350 -0
- package/src/eval/mutator-prompt.js +106 -0
- package/src/eval/mutator-prompt.test.js +180 -0
- package/templates/config-template.yaml +5 -6
- package/templates/eval-fixture-template/config.yaml +39 -0
- package/templates/eval-fixture-template/fixture/.deepflow/decisions.md +5 -0
- package/templates/eval-fixture-template/fixture/hooks/invariant.js +28 -0
- package/templates/eval-fixture-template/fixture/package.json +12 -0
- package/templates/eval-fixture-template/fixture/specs/doing-example-task.md +18 -0
- package/templates/eval-fixture-template/fixture/src/commands/df/example.md +18 -0
- package/templates/eval-fixture-template/fixture/src/config.js +40 -0
- package/templates/eval-fixture-template/fixture/src/index.js +19 -0
- package/templates/eval-fixture-template/fixture/src/pipeline.js +40 -0
- package/templates/eval-fixture-template/fixture/src/skills/example-skill/SKILL.md +32 -0
- package/templates/eval-fixture-template/fixture/src/spec-loader.js +35 -0
- package/templates/eval-fixture-template/fixture/src/task-runner.js +32 -0
- package/templates/eval-fixture-template/fixture/src/verifier.js +37 -0
- package/templates/eval-fixture-template/hypotheses.md +14 -0
- package/templates/eval-fixture-template/spec.md +34 -0
- package/templates/eval-fixture-template/tests/behavior.test.js +69 -0
- package/templates/eval-fixture-template/tests/guard.test.js +108 -0
- package/templates/eval-fixture-template.test.js +318 -0
- package/templates/explore-agent.md +5 -74
- package/templates/explore-protocol.md +44 -0
- package/templates/spec-template.md +4 -0
package/bin/install.js
CHANGED
|
@@ -134,6 +134,13 @@ async function main() {
|
|
|
134
134
|
);
|
|
135
135
|
log('Agents installed');
|
|
136
136
|
|
|
137
|
+
// Copy templates (explore-protocol, explore-agent, etc.)
|
|
138
|
+
copyDir(
|
|
139
|
+
path.join(PACKAGE_DIR, 'templates'),
|
|
140
|
+
path.join(CLAUDE_DIR, 'templates')
|
|
141
|
+
);
|
|
142
|
+
log('Templates installed');
|
|
143
|
+
|
|
137
144
|
// Copy bin utilities (plan-consolidator, wave-runner, ratchet)
|
|
138
145
|
const binDest = path.join(CLAUDE_DIR, 'bin');
|
|
139
146
|
fs.mkdirSync(binDest, { recursive: true });
|
|
@@ -198,8 +205,9 @@ async function main() {
|
|
|
198
205
|
console.log(' skills/ — gap-discovery, atomic-commits, code-completeness, browse-fetch, browse-verify, auto-cycle');
|
|
199
206
|
console.log(' agents/ — reasoner (/df:auto — autonomous execution via /loop)');
|
|
200
207
|
console.log(' bin/ — plan-consolidator, wave-runner, ratchet');
|
|
208
|
+
console.log(' templates/ — explore-protocol (auto-injected into Explore agents via hook)');
|
|
201
209
|
if (level === 'global') {
|
|
202
|
-
console.log(' hooks/ — statusline, update checker, invariant checker, worktree guard');
|
|
210
|
+
console.log(' hooks/ — statusline, update checker, invariant checker, worktree guard, explore protocol');
|
|
203
211
|
}
|
|
204
212
|
console.log(' hooks/df-spec-* — spec validation (auto-enforced by /df:spec and /df:plan)');
|
|
205
213
|
console.log(' env/ — ENABLE_LSP_TOOL (code navigation via goToDefinition, findReferences, workspaceSymbol)');
|
|
@@ -230,12 +238,33 @@ function isInstalled(claudeDir) {
|
|
|
230
238
|
function copyDir(src, dest) {
|
|
231
239
|
if (!fs.existsSync(src)) return;
|
|
232
240
|
|
|
241
|
+
const resolvedSrcRoot = path.resolve(src);
|
|
242
|
+
const resolvedDestRoot = path.resolve(dest);
|
|
243
|
+
|
|
233
244
|
fs.mkdirSync(dest, { recursive: true });
|
|
234
245
|
|
|
235
246
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
236
247
|
const srcPath = path.join(src, entry.name);
|
|
237
248
|
const destPath = path.join(dest, entry.name);
|
|
238
249
|
|
|
250
|
+
// Reject symlinks to prevent symlink attacks
|
|
251
|
+
if (entry.isSymbolicLink()) {
|
|
252
|
+
process.stderr.write(`[deepflow] skipping symlink: ${srcPath}\n`);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Guard against path traversal — resolved paths must stay under their roots
|
|
257
|
+
const resolvedSrc = path.resolve(srcPath);
|
|
258
|
+
const resolvedDest = path.resolve(destPath);
|
|
259
|
+
if (!resolvedSrc.startsWith(resolvedSrcRoot + path.sep) && resolvedSrc !== resolvedSrcRoot) {
|
|
260
|
+
process.stderr.write(`[deepflow] skipping path traversal attempt (src): ${srcPath}\n`);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (!resolvedDest.startsWith(resolvedDestRoot + path.sep) && resolvedDest !== resolvedDestRoot) {
|
|
264
|
+
process.stderr.write(`[deepflow] skipping path traversal attempt (dest): ${destPath}\n`);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
239
268
|
if (entry.isDirectory()) {
|
|
240
269
|
copyDir(srcPath, destPath);
|
|
241
270
|
} else {
|
|
@@ -244,19 +273,82 @@ function copyDir(src, dest) {
|
|
|
244
273
|
}
|
|
245
274
|
}
|
|
246
275
|
|
|
276
|
+
// Valid hook events (settings.hooks keys + special "statusLine")
|
|
277
|
+
const VALID_HOOK_EVENTS = new Set([
|
|
278
|
+
'SessionStart', 'SessionEnd', 'PreToolUse', 'PostToolUse', 'SubagentStop', 'statusLine'
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Scan hook source files for @hook-event tags. Returns:
|
|
283
|
+
* { eventMap: Map<event, [filename, ...]>, untagged: [filename, ...] }
|
|
284
|
+
*/
|
|
285
|
+
function scanHookEvents(hooksSourceDir) {
|
|
286
|
+
const eventMap = new Map(); // event → [filenames]
|
|
287
|
+
const untagged = [];
|
|
288
|
+
|
|
289
|
+
if (!fs.existsSync(hooksSourceDir)) return { eventMap, untagged };
|
|
290
|
+
|
|
291
|
+
for (const file of fs.readdirSync(hooksSourceDir)) {
|
|
292
|
+
if (!file.endsWith('.js') || file.endsWith('.test.js')) continue;
|
|
293
|
+
|
|
294
|
+
const content = fs.readFileSync(path.join(hooksSourceDir, file), 'utf8');
|
|
295
|
+
const firstLines = content.split('\n').slice(0, 10).join('\n');
|
|
296
|
+
const match = firstLines.match(/\/\/\s*@hook-event:\s*(.+)/);
|
|
297
|
+
|
|
298
|
+
if (!match) {
|
|
299
|
+
untagged.push(file);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const events = match[1].split(',').map(e => e.trim()).filter(Boolean);
|
|
304
|
+
let hasValidEvent = false;
|
|
305
|
+
|
|
306
|
+
for (const event of events) {
|
|
307
|
+
if (!VALID_HOOK_EVENTS.has(event)) {
|
|
308
|
+
console.log(` ${c.yellow}!${c.reset} Warning: unknown event "${event}" in ${file} — skipped`);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
hasValidEvent = true;
|
|
312
|
+
if (!eventMap.has(event)) eventMap.set(event, []);
|
|
313
|
+
eventMap.get(event).push(file);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!hasValidEvent) {
|
|
317
|
+
untagged.push(file);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return { eventMap, untagged };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Remove all deepflow hook entries (commands containing /hooks/df-) from settings.
|
|
326
|
+
* Preserves non-deepflow hooks.
|
|
327
|
+
*/
|
|
328
|
+
function removeDeepflowHooks(settings) {
|
|
329
|
+
const isDeepflow = (hook) => {
|
|
330
|
+
const cmd = hook.hooks?.[0]?.command || '';
|
|
331
|
+
return cmd.includes('/hooks/df-');
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// Clean settings.hooks.*
|
|
335
|
+
if (settings.hooks) {
|
|
336
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
337
|
+
settings.hooks[event] = settings.hooks[event].filter(h => !isDeepflow(h));
|
|
338
|
+
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
339
|
+
}
|
|
340
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Clean settings.statusLine if it's a deepflow hook
|
|
344
|
+
if (settings.statusLine?.command && settings.statusLine.command.includes('/hooks/df-')) {
|
|
345
|
+
delete settings.statusLine;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
247
349
|
async function configureHooks(claudeDir) {
|
|
248
350
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
249
|
-
const
|
|
250
|
-
const updateCheckCmd = `node "${path.join(claudeDir, 'hooks', 'df-check-update.js')}"`;
|
|
251
|
-
const quotaLoggerCmd = `node "${path.join(claudeDir, 'hooks', 'df-quota-logger.js')}"`;
|
|
252
|
-
const toolUsageCmd = `node "${path.join(claudeDir, 'hooks', 'df-tool-usage.js')}"`;
|
|
253
|
-
const dashboardPushCmd = `node "${path.join(claudeDir, 'hooks', 'df-dashboard-push.js')}"`;
|
|
254
|
-
const executionHistoryCmd = `node "${path.join(claudeDir, 'hooks', 'df-execution-history.js')}"`;
|
|
255
|
-
const worktreeGuardCmd = `node "${path.join(claudeDir, 'hooks', 'df-worktree-guard.js')}"`;
|
|
256
|
-
const snapshotGuardCmd = `node "${path.join(claudeDir, 'hooks', 'df-snapshot-guard.js')}"`;
|
|
257
|
-
const invariantCheckCmd = `node "${path.join(claudeDir, 'hooks', 'df-invariant-check.js')}"`;
|
|
258
|
-
const subagentRegistryCmd = `node "${path.join(claudeDir, 'hooks', 'df-subagent-registry.js')}"`;
|
|
259
|
-
const commandUsageCmd = `node "${path.join(claudeDir, 'hooks', 'df-command-usage.js')}"`;
|
|
351
|
+
const hooksSourceDir = path.join(PACKAGE_DIR, 'hooks');
|
|
260
352
|
|
|
261
353
|
let settings = {};
|
|
262
354
|
|
|
@@ -277,194 +369,64 @@ async function configureHooks(claudeDir) {
|
|
|
277
369
|
configurePermissions(settings);
|
|
278
370
|
log('Agent permissions configured');
|
|
279
371
|
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
if (process.stdin.isTTY) {
|
|
283
|
-
const answer = await ask(
|
|
284
|
-
` ${c.yellow}!${c.reset} Existing statusLine found. Replace with deepflow? [y/N] `
|
|
285
|
-
);
|
|
286
|
-
if (answer.toLowerCase() === 'y') {
|
|
287
|
-
settings.statusLine = { type: 'command', command: statuslineCmd };
|
|
288
|
-
log('Statusline configured');
|
|
289
|
-
} else {
|
|
290
|
-
console.log(` ${c.yellow}!${c.reset} Skipped statusline configuration`);
|
|
291
|
-
}
|
|
292
|
-
} else {
|
|
293
|
-
// Non-interactive (e.g. Claude Code bash tool) — skip prompt, keep existing
|
|
294
|
-
console.log(` ${c.yellow}!${c.reset} Existing statusLine found — kept (non-interactive mode)`);
|
|
295
|
-
}
|
|
296
|
-
} else {
|
|
297
|
-
settings.statusLine = { type: 'command', command: statuslineCmd };
|
|
298
|
-
log('Statusline configured');
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Configure SessionStart hook for update checking
|
|
302
|
-
if (!settings.hooks) {
|
|
303
|
-
settings.hooks = {};
|
|
304
|
-
}
|
|
305
|
-
if (!settings.hooks.SessionStart) {
|
|
306
|
-
settings.hooks.SessionStart = [];
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Remove any existing deepflow update check / quota logger hooks from SessionStart
|
|
310
|
-
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(hook => {
|
|
311
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
312
|
-
return !cmd.includes('df-check-update') && !cmd.includes('df-quota-logger');
|
|
313
|
-
});
|
|
372
|
+
// Scan hook files for @hook-event tags
|
|
373
|
+
const { eventMap, untagged } = scanHookEvents(hooksSourceDir);
|
|
314
374
|
|
|
315
|
-
//
|
|
316
|
-
settings.
|
|
317
|
-
hooks
|
|
318
|
-
type: 'command',
|
|
319
|
-
command: updateCheckCmd
|
|
320
|
-
}]
|
|
321
|
-
});
|
|
375
|
+
// Remember if there was a pre-existing non-deepflow statusLine
|
|
376
|
+
const hadExternalStatusLine = settings.statusLine &&
|
|
377
|
+
!settings.statusLine.command?.includes('/hooks/df-');
|
|
322
378
|
|
|
323
|
-
//
|
|
324
|
-
settings
|
|
325
|
-
hooks: [{
|
|
326
|
-
type: 'command',
|
|
327
|
-
command: quotaLoggerCmd
|
|
328
|
-
}]
|
|
329
|
-
});
|
|
330
|
-
log('SessionStart hook configured');
|
|
379
|
+
// Remove all existing deepflow hooks (orphan cleanup + idempotency)
|
|
380
|
+
removeDeepflowHooks(settings);
|
|
331
381
|
|
|
332
|
-
//
|
|
333
|
-
if (!settings.hooks
|
|
334
|
-
settings.hooks.SessionEnd = [];
|
|
335
|
-
}
|
|
382
|
+
// Wire hooks by event
|
|
383
|
+
if (!settings.hooks) settings.hooks = {};
|
|
336
384
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
385
|
+
for (const [event, files] of eventMap) {
|
|
386
|
+
if (event === 'statusLine') {
|
|
387
|
+
// Handle statusLine separately — it's settings.statusLine, not settings.hooks
|
|
388
|
+
const statusFile = files[0]; // Only one statusline hook expected
|
|
389
|
+
const statusCmd = `node "${path.join(claudeDir, 'hooks', statusFile)}"`;
|
|
342
390
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}]
|
|
365
|
-
});
|
|
366
|
-
log('Quota logger + dashboard push + command usage configured (SessionEnd)');
|
|
367
|
-
|
|
368
|
-
// Configure PostToolUse hook for tool usage instrumentation
|
|
369
|
-
if (!settings.hooks.PostToolUse) {
|
|
370
|
-
settings.hooks.PostToolUse = [];
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Remove any existing deepflow tool usage / execution history / worktree guard / snapshot guard / invariant check / command usage hooks from PostToolUse
|
|
374
|
-
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(hook => {
|
|
375
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
376
|
-
return !cmd.includes('df-tool-usage') && !cmd.includes('df-execution-history') && !cmd.includes('df-worktree-guard') && !cmd.includes('df-snapshot-guard') && !cmd.includes('df-invariant-check') && !cmd.includes('df-command-usage');
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// Add tool usage hook
|
|
380
|
-
settings.hooks.PostToolUse.push({
|
|
381
|
-
hooks: [{
|
|
382
|
-
type: 'command',
|
|
383
|
-
command: toolUsageCmd
|
|
384
|
-
}]
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
// Add execution history hook
|
|
388
|
-
settings.hooks.PostToolUse.push({
|
|
389
|
-
hooks: [{
|
|
390
|
-
type: 'command',
|
|
391
|
-
command: executionHistoryCmd
|
|
392
|
-
}]
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// Add worktree guard hook (blocks Write/Edit to main-branch files when df/* worktree exists)
|
|
396
|
-
settings.hooks.PostToolUse.push({
|
|
397
|
-
hooks: [{
|
|
398
|
-
type: 'command',
|
|
399
|
-
command: worktreeGuardCmd
|
|
400
|
-
}]
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// Add snapshot guard hook (blocks Write/Edit to ratchet-baseline files in auto-snapshot.txt)
|
|
404
|
-
settings.hooks.PostToolUse.push({
|
|
405
|
-
hooks: [{
|
|
406
|
-
type: 'command',
|
|
407
|
-
command: snapshotGuardCmd
|
|
408
|
-
}]
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// Add invariant check hook (exits 1 on hard failures after git commit)
|
|
412
|
-
settings.hooks.PostToolUse.push({
|
|
413
|
-
hooks: [{
|
|
414
|
-
type: 'command',
|
|
415
|
-
command: invariantCheckCmd
|
|
416
|
-
}]
|
|
417
|
-
});
|
|
391
|
+
if (hadExternalStatusLine) {
|
|
392
|
+
if (process.stdin.isTTY) {
|
|
393
|
+
const answer = await ask(
|
|
394
|
+
` ${c.yellow}!${c.reset} Existing statusLine found. Replace with deepflow? [y/N] `
|
|
395
|
+
);
|
|
396
|
+
if (answer.toLowerCase() === 'y') {
|
|
397
|
+
settings.statusLine = { type: 'command', command: statusCmd };
|
|
398
|
+
log('Statusline configured');
|
|
399
|
+
} else {
|
|
400
|
+
console.log(` ${c.yellow}!${c.reset} Skipped statusline configuration`);
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
// Non-interactive (e.g. Claude Code bash tool) — skip prompt, keep existing
|
|
404
|
+
console.log(` ${c.yellow}!${c.reset} Existing statusLine found — kept (non-interactive mode)`);
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
settings.statusLine = { type: 'command', command: statusCmd };
|
|
408
|
+
log('Statusline configured');
|
|
409
|
+
}
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
418
412
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
hooks: [{
|
|
422
|
-
type: 'command',
|
|
423
|
-
command: commandUsageCmd
|
|
424
|
-
}]
|
|
425
|
-
});
|
|
426
|
-
log('PostToolUse hook configured');
|
|
413
|
+
// Regular hook events
|
|
414
|
+
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
427
415
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
416
|
+
for (const file of files) {
|
|
417
|
+
const cmd = `node "${path.join(claudeDir, 'hooks', file)}"`;
|
|
418
|
+
settings.hooks[event].push({
|
|
419
|
+
hooks: [{ type: 'command', command: cmd }]
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
log(`${event} hook configured`);
|
|
431
423
|
}
|
|
432
424
|
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return !cmd.includes('df-subagent-registry');
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
// Add subagent registry hook
|
|
440
|
-
settings.hooks.SubagentStop.push({
|
|
441
|
-
hooks: [{
|
|
442
|
-
type: 'command',
|
|
443
|
-
command: subagentRegistryCmd
|
|
444
|
-
}]
|
|
445
|
-
});
|
|
446
|
-
log('SubagentStop hook configured');
|
|
447
|
-
|
|
448
|
-
// Configure PreToolUse hook for command usage instrumentation
|
|
449
|
-
if (!settings.hooks.PreToolUse) {
|
|
450
|
-
settings.hooks.PreToolUse = [];
|
|
425
|
+
// Log untagged files (copied but not wired)
|
|
426
|
+
for (const file of untagged) {
|
|
427
|
+
console.log(` ${c.dim}${file} copied (no @hook-event tag — not wired)${c.reset}`);
|
|
451
428
|
}
|
|
452
429
|
|
|
453
|
-
// Remove any existing deepflow command usage hooks from PreToolUse
|
|
454
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(hook => {
|
|
455
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
456
|
-
return !cmd.includes('df-command-usage');
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Add command usage hook to PreToolUse
|
|
460
|
-
settings.hooks.PreToolUse.push({
|
|
461
|
-
hooks: [{
|
|
462
|
-
type: 'command',
|
|
463
|
-
command: commandUsageCmd
|
|
464
|
-
}]
|
|
465
|
-
});
|
|
466
|
-
log('PreToolUse hook configured');
|
|
467
|
-
|
|
468
430
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
469
431
|
}
|
|
470
432
|
|
|
@@ -644,11 +606,20 @@ async function uninstall() {
|
|
|
644
606
|
'agents/reasoner.md',
|
|
645
607
|
'bin/plan-consolidator.js',
|
|
646
608
|
'bin/wave-runner.js',
|
|
647
|
-
'bin/ratchet.js'
|
|
609
|
+
'bin/ratchet.js',
|
|
610
|
+
'templates'
|
|
648
611
|
];
|
|
649
612
|
|
|
650
613
|
if (level === 'global') {
|
|
651
|
-
|
|
614
|
+
// Dynamically find all df-*.js hook files to remove
|
|
615
|
+
const hooksDir = path.join(CLAUDE_DIR, 'hooks');
|
|
616
|
+
if (fs.existsSync(hooksDir)) {
|
|
617
|
+
for (const file of fs.readdirSync(hooksDir)) {
|
|
618
|
+
if (file.startsWith('df-') && file.endsWith('.js')) {
|
|
619
|
+
toRemove.push(`hooks/${file}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
652
623
|
}
|
|
653
624
|
|
|
654
625
|
for (const item of toRemove) {
|
|
@@ -668,76 +639,25 @@ async function uninstall() {
|
|
|
668
639
|
}
|
|
669
640
|
}
|
|
670
641
|
|
|
671
|
-
// Remove
|
|
642
|
+
// Remove hook entries and settings from global settings.json
|
|
672
643
|
if (level === 'global') {
|
|
673
644
|
const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
|
|
674
645
|
if (fs.existsSync(settingsPath)) {
|
|
675
646
|
try {
|
|
676
647
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
677
|
-
if (settings.hooks?.SessionStart) {
|
|
678
|
-
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(hook => {
|
|
679
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
680
|
-
return !cmd.includes('df-check-update') && !cmd.includes('df-quota-logger');
|
|
681
|
-
});
|
|
682
|
-
if (settings.hooks.SessionStart.length === 0) {
|
|
683
|
-
delete settings.hooks.SessionStart;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
if (settings.hooks?.SessionEnd) {
|
|
687
|
-
settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter(hook => {
|
|
688
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
689
|
-
return !cmd.includes('df-quota-logger') && !cmd.includes('df-dashboard-push') && !cmd.includes('df-command-usage');
|
|
690
|
-
});
|
|
691
|
-
if (settings.hooks.SessionEnd.length === 0) {
|
|
692
|
-
delete settings.hooks.SessionEnd;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
if (settings.hooks?.PostToolUse) {
|
|
696
|
-
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(hook => {
|
|
697
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
698
|
-
return !cmd.includes('df-tool-usage') && !cmd.includes('df-execution-history') && !cmd.includes('df-worktree-guard') && !cmd.includes('df-snapshot-guard') && !cmd.includes('df-invariant-check') && !cmd.includes('df-command-usage');
|
|
699
|
-
});
|
|
700
|
-
if (settings.hooks.PostToolUse.length === 0) {
|
|
701
|
-
delete settings.hooks.PostToolUse;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
if (settings.hooks?.PreToolUse) {
|
|
705
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(hook => {
|
|
706
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
707
|
-
return !cmd.includes('df-command-usage');
|
|
708
|
-
});
|
|
709
|
-
if (settings.hooks.PreToolUse.length === 0) {
|
|
710
|
-
delete settings.hooks.PreToolUse;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
if (settings.hooks?.SubagentStop) {
|
|
714
|
-
settings.hooks.SubagentStop = settings.hooks.SubagentStop.filter(hook => {
|
|
715
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
716
|
-
return !cmd.includes('df-subagent-registry');
|
|
717
|
-
});
|
|
718
|
-
if (settings.hooks.SubagentStop.length === 0) {
|
|
719
|
-
delete settings.hooks.SubagentStop;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
723
|
-
delete settings.hooks;
|
|
724
|
-
}
|
|
725
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
726
|
-
console.log(` ${c.green}✓${c.reset} Removed SessionStart/SessionEnd/PreToolUse/PostToolUse/SubagentStop hooks`);
|
|
727
|
-
} catch (e) {
|
|
728
|
-
// Fail silently
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
648
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
649
|
+
// Remove all deepflow hook wiring dynamically
|
|
650
|
+
removeDeepflowHooks(settings);
|
|
651
|
+
console.log(` ${c.green}✓${c.reset} Removed deepflow hooks from settings`);
|
|
652
|
+
|
|
653
|
+
// Remove ENABLE_LSP_TOOL
|
|
736
654
|
if (settings.env?.ENABLE_LSP_TOOL) {
|
|
737
655
|
delete settings.env.ENABLE_LSP_TOOL;
|
|
738
656
|
if (settings.env && Object.keys(settings.env).length === 0) delete settings.env;
|
|
739
657
|
console.log(` ${c.green}✓${c.reset} Removed ENABLE_LSP_TOOL from settings`);
|
|
740
658
|
}
|
|
659
|
+
|
|
660
|
+
// Remove deepflow permissions
|
|
741
661
|
if (settings.permissions?.allow) {
|
|
742
662
|
const dfPerms = new Set(DEEPFLOW_PERMISSIONS);
|
|
743
663
|
settings.permissions.allow = settings.permissions.allow.filter(p => !dfPerms.has(p));
|
|
@@ -745,6 +665,7 @@ async function uninstall() {
|
|
|
745
665
|
if (settings.permissions && Object.keys(settings.permissions).length === 0) delete settings.permissions;
|
|
746
666
|
console.log(` ${c.green}✓${c.reset} Removed deepflow permissions from settings`);
|
|
747
667
|
}
|
|
668
|
+
|
|
748
669
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
749
670
|
} catch (e) {
|
|
750
671
|
// Fail silently
|