trackfw 6.7.1 → 6.8.0
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/package.json +1 -1
- package/src/generators/hooks.js +169 -60
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackfw",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.8.0",
|
|
4
4
|
"description": "Governance CLI for AI-native software delivery and AI coding agents. Enforces traceability: ADR → REQ → ROADMAP → kanban. Native support for Codex, Claude Code, Gemini CLI, Cursor, Copilot, Windsurf, Amazon Q, OpenCode, and Kiro.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
package/src/generators/hooks.js
CHANGED
|
@@ -85,14 +85,20 @@ function mergeClaudeHookArray(existing, matcher, command) {
|
|
|
85
85
|
return arr
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
//
|
|
89
|
-
// matching the given matcher inside a
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
88
|
+
// migrateHookCommand rewrites a legacy hook command to a new one, in place, for every entry
|
|
89
|
+
// matching the given matcher inside a "matcher + hooks[].command" shaped array -- the format
|
|
90
|
+
// shared by Claude, Codex and Gemini's merge-based settings files (PreToolUse/PostToolUse/
|
|
91
|
+
// PermissionRequest/Notification/BeforeTool/AfterTool). Used to fix settings files already written
|
|
92
|
+
// by an older trackfw before a command string changes -- without this, re-running
|
|
93
|
+
// `trackfw init`/`update` only ever appends the new (fixed) command alongside the stale one (merge
|
|
94
|
+
// dedup in mergeClaudeHookArray keys on the exact command string, so it can't tell "same guard, new
|
|
95
|
+
// path" from "a different hook"), leaving the broken entry in place to keep firing and failing
|
|
96
|
+
// forever. Originally written for Claude only (hence the doc comment history); generalized
|
|
97
|
+
// (ROADMAP-2026-08-11 ML-1A) so Codex/Gemini injectors can call it too, ahead of the
|
|
98
|
+
// mechanism-specific string changes those CLIs' waves make. Must always be called before the
|
|
99
|
+
// corresponding mergeClaudeHookArray call for the same matcher, or the merge's exact-string dedup
|
|
100
|
+
// will append a duplicate instead of rewriting in place.
|
|
101
|
+
function migrateHookCommand(existing, matcher, oldCommand, newCommand) {
|
|
96
102
|
const arr = Array.isArray(existing) ? existing : []
|
|
97
103
|
for (const item of arr) {
|
|
98
104
|
if (!item || item.matcher !== matcher) continue
|
|
@@ -434,9 +440,76 @@ function generateGlobalCredentialGuardScript(home) {
|
|
|
434
440
|
console.log(' ✓ .trackfw/scripts/trackfw-credential-guard.sh')
|
|
435
441
|
}
|
|
436
442
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
443
|
+
// SIGNAL_CMD_*/CLEANUP_CMD_* — split per-CLI (ROADMAP-2026-08-11 ML-2A) from what used to be two
|
|
444
|
+
// constants (SIGNAL_CMD/CLEANUP_CMD) shared by all 6 CLI injectors. Mutating a shared constant to
|
|
445
|
+
// fix one CLI's path-resolution mechanism silently changed the emission of the other 5 -- Wave 0 of
|
|
446
|
+
// that roadmap proved Cursor and Copilot are *already correct* (Cursor hooks run from the project
|
|
447
|
+
// root; Copilot emits the native "cwd": "." field), so an accidental shared-constant edit there
|
|
448
|
+
// would be a regression in verified-good wiring, not just scope creep. Each constant starts equal
|
|
449
|
+
// to the pre-split literal; only the Claude ones (ML-2A, this change) move to the
|
|
450
|
+
// $CLAUDE_PROJECT_DIR-pinned form below. GUARD_CMD was later split too (ML-3A) once Codex's guard
|
|
451
|
+
// command needed to change -- see the GUARD_CMD_* block below.
|
|
452
|
+
// ROADMAP-2026-08-11 ML-3A: Codex CLI does not expose a project-root env var for repo-local hooks
|
|
453
|
+
// (unlike Claude's $CLAUDE_PROJECT_DIR or Gemini's $GEMINI_PROJECT_DIR) -- the only documented
|
|
454
|
+
// mechanism is shell substitution. Per ADR-2026-08-11 ("Codex — alterar, com dependência explícita
|
|
455
|
+
// de shell e git"), the command is wrapped in literal double quotes around
|
|
456
|
+
// `$(git rev-parse --show-toplevel)`, matching every repo-local hook example in the official Codex
|
|
457
|
+
// docs (https://developers.openai.com/codex/config-advanced): "For repo-local hooks, prefer
|
|
458
|
+
// resolving from the git root instead of using a relative path such as `.codex/hooks/...`."
|
|
459
|
+
const CODEX_ROOT = '"$(git rev-parse --show-toplevel)'
|
|
460
|
+
|
|
461
|
+
const SIGNAL_CMD_CLAUDE = '$CLAUDE_PROJECT_DIR/scripts/trackfw-attention-signal.sh'
|
|
462
|
+
const SIGNAL_CMD_CODEX = CODEX_ROOT + '/scripts/trackfw-attention-signal.sh"'
|
|
463
|
+
// $GEMINI_PROJECT_DIR (ROADMAP-2026-08-11 ML-4A): distinct from the session-following
|
|
464
|
+
// $GEMINI_CWD, documented and used in 100% of the Gemini CLI's official hook command
|
|
465
|
+
// examples (ADR-2026-08-11, "Gemini CLI — alterar, por argumento de assimetria"). Expanded
|
|
466
|
+
// by the Gemini CLI runtime itself -- no shell substitution needed, no literal quotes.
|
|
467
|
+
const SIGNAL_CMD_GEMINI = '$GEMINI_PROJECT_DIR/scripts/trackfw-attention-signal.sh'
|
|
468
|
+
const SIGNAL_CMD_KIRO = 'scripts/trackfw-attention-signal.sh'
|
|
469
|
+
const SIGNAL_CMD_COPILOT = 'scripts/trackfw-attention-signal.sh'
|
|
470
|
+
const SIGNAL_CMD_CURSOR = 'scripts/trackfw-attention-signal.sh'
|
|
471
|
+
|
|
472
|
+
const CLEANUP_CMD_CLAUDE = '$CLAUDE_PROJECT_DIR/scripts/trackfw-attention-cleanup.sh'
|
|
473
|
+
const CLEANUP_CMD_CODEX = CODEX_ROOT + '/scripts/trackfw-attention-cleanup.sh"'
|
|
474
|
+
const CLEANUP_CMD_GEMINI = '$GEMINI_PROJECT_DIR/scripts/trackfw-attention-cleanup.sh'
|
|
475
|
+
const CLEANUP_CMD_KIRO = 'scripts/trackfw-attention-cleanup.sh'
|
|
476
|
+
const CLEANUP_CMD_COPILOT = 'scripts/trackfw-attention-cleanup.sh'
|
|
477
|
+
const CLEANUP_CMD_CURSOR = 'scripts/trackfw-attention-cleanup.sh'
|
|
478
|
+
|
|
479
|
+
// Pre-ML-2A literal value of SIGNAL_CMD_CLAUDE/CLEANUP_CMD_CLAUDE, kept only as the `oldCommand`
|
|
480
|
+
// argument to the migration calls in injectClaudeHooks below (rewrites settings.json entries
|
|
481
|
+
// written by a pre-ML-2A trackfw in place, mirroring the GUARD_CMD_CLAUDE migration pattern).
|
|
482
|
+
const SIGNAL_CMD_CLAUDE_LEGACY = 'scripts/trackfw-attention-signal.sh'
|
|
483
|
+
const CLEANUP_CMD_CLAUDE_LEGACY = 'scripts/trackfw-attention-cleanup.sh'
|
|
484
|
+
|
|
485
|
+
// Pre-ML-3A literal value of SIGNAL_CMD_CODEX/CLEANUP_CMD_CODEX, kept only as the `oldCommand`
|
|
486
|
+
// argument to the migration calls in injectCodexHooks below.
|
|
487
|
+
const SIGNAL_CMD_CODEX_LEGACY = 'scripts/trackfw-attention-signal.sh'
|
|
488
|
+
const CLEANUP_CMD_CODEX_LEGACY = 'scripts/trackfw-attention-cleanup.sh'
|
|
489
|
+
|
|
490
|
+
// Pre-ML-4A literal value of SIGNAL_CMD_GEMINI/CLEANUP_CMD_GEMINI/GUARD_CMD_GEMINI, kept only as
|
|
491
|
+
// the `oldCommand` argument to the migration calls in injectGeminiHooks below.
|
|
492
|
+
const SIGNAL_CMD_GEMINI_LEGACY = 'scripts/trackfw-attention-signal.sh'
|
|
493
|
+
const CLEANUP_CMD_GEMINI_LEGACY = 'scripts/trackfw-attention-cleanup.sh'
|
|
494
|
+
const GUARD_CMD_GEMINI_LEGACY = 'scripts/trackfw-credential-guard.sh'
|
|
495
|
+
|
|
496
|
+
// GUARD_CMD_* -- split per-CLI (ROADMAP-2026-08-11 ML-3A) from what used to be one constant
|
|
497
|
+
// (GUARD_CMD) shared by Codex/Gemini/Kiro/Copilot/Cursor (Claude already had its own,
|
|
498
|
+
// GUARD_CMD_CLAUDE, since ML-2A). Same rationale as the SIGNAL_CMD_*/CLEANUP_CMD_* split above:
|
|
499
|
+
// Cursor and Copilot are verified-correct wiring (ML-0A), so a shared-constant edit made for Codex
|
|
500
|
+
// would have silently regressed them. Each constant starts equal to the pre-split literal; only
|
|
501
|
+
// GUARD_CMD_CODEX (this ML) moves to the new form below.
|
|
502
|
+
// Legacy relative-path literal, pre-dating both the ML-2A Claude fix and the ML-3A Codex fix --
|
|
503
|
+
// kept only as the `oldCommand` argument to migration calls (Claude's migration below, and Codex's
|
|
504
|
+
// until this ML rewrites it), mirroring the SIGNAL_CMD_CLAUDE_LEGACY/CLEANUP_CMD_CLAUDE_LEGACY
|
|
505
|
+
// pattern above.
|
|
506
|
+
const GUARD_CMD_LEGACY = 'scripts/trackfw-credential-guard.sh'
|
|
507
|
+
const GUARD_CMD_CODEX = CODEX_ROOT + '/scripts/trackfw-credential-guard.sh"'
|
|
508
|
+
const GUARD_CMD_GEMINI = '$GEMINI_PROJECT_DIR/scripts/trackfw-credential-guard.sh'
|
|
509
|
+
const GUARD_CMD_KIRO = 'scripts/trackfw-credential-guard.sh'
|
|
510
|
+
const GUARD_CMD_COPILOT = 'scripts/trackfw-credential-guard.sh'
|
|
511
|
+
const GUARD_CMD_CURSOR = 'scripts/trackfw-credential-guard.sh'
|
|
512
|
+
|
|
440
513
|
// Claude Code only (2026-08-09 fix, reported in production against the CMDB project): Claude Code
|
|
441
514
|
// resolves a bare relative hook command against the hook's *dynamic* cwd (tracks `cd`s the agent
|
|
442
515
|
// runs during the session), not the project root -- confirmed against
|
|
@@ -579,14 +652,24 @@ function injectClaudeHooks(cwd) {
|
|
|
579
652
|
const data = readJSON(filePath)
|
|
580
653
|
|
|
581
654
|
if (!data.hooks) data.hooks = {}
|
|
582
|
-
|
|
655
|
+
|
|
656
|
+
// Migration (ROADMAP-2026-08-11 ML-2A): rewrite any stale relative-path attention-signal/cleanup
|
|
657
|
+
// command from an older trackfw run before merging the $CLAUDE_PROJECT_DIR-pinned one below, so
|
|
658
|
+
// upgrading doesn't just append a second, still-cwd-fragile entry alongside the fixed one (same
|
|
659
|
+
// "No such file or directory" bug class as the credential-guard fix below, and the same
|
|
660
|
+
// migrate-before-merge ordering requirement -- mergeClaudeHookArray's dedup keys on the exact
|
|
661
|
+
// command string, so it can't tell "same hook, new path" from "a different hook").
|
|
662
|
+
migrateHookCommand(data.hooks.PreToolUse, 'AskUserQuestion', SIGNAL_CMD_CLAUDE_LEGACY, SIGNAL_CMD_CLAUDE)
|
|
663
|
+
migrateHookCommand(data.hooks.PostToolUse, 'AskUserQuestion', CLEANUP_CMD_CLAUDE_LEGACY, CLEANUP_CMD_CLAUDE)
|
|
664
|
+
|
|
665
|
+
data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'AskUserQuestion', SIGNAL_CMD_CLAUDE)
|
|
583
666
|
|
|
584
667
|
// Rewrite any stale relative-path credential-guard command from an older trackfw run before
|
|
585
668
|
// merging the fixed one below, so upgrading doesn't just append a second, still-broken entry
|
|
586
669
|
// alongside the new one (see GUARD_CMD_CLAUDE comment for the "No such file or directory" bug).
|
|
587
670
|
for (const matcher of ['Bash', 'Read', 'Write|Edit']) {
|
|
588
|
-
|
|
589
|
-
|
|
671
|
+
migrateHookCommand(data.hooks.PreToolUse, matcher, GUARD_CMD_LEGACY, GUARD_CMD_CLAUDE)
|
|
672
|
+
migrateHookCommand(data.hooks.PostToolUse, matcher, GUARD_CMD_LEGACY, GUARD_CMD_CLAUDE)
|
|
590
673
|
}
|
|
591
674
|
|
|
592
675
|
// Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
|
|
@@ -599,7 +682,7 @@ function injectClaudeHooks(cwd) {
|
|
|
599
682
|
data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Read', GUARD_CMD_CLAUDE)
|
|
600
683
|
data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Write|Edit', GUARD_CMD_CLAUDE)
|
|
601
684
|
}
|
|
602
|
-
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'AskUserQuestion',
|
|
685
|
+
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'AskUserQuestion', CLEANUP_CMD_CLAUDE)
|
|
603
686
|
if (!globalCredentialGuardInstalledClaude()) {
|
|
604
687
|
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Bash', GUARD_CMD_CLAUDE)
|
|
605
688
|
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Read', GUARD_CMD_CLAUDE)
|
|
@@ -633,19 +716,30 @@ function injectCodexHooks(cwd) {
|
|
|
633
716
|
const data = readJSON(filePath)
|
|
634
717
|
|
|
635
718
|
if (!data.hooks) data.hooks = {}
|
|
636
|
-
|
|
719
|
+
|
|
720
|
+
// Migration wiring (ROADMAP-2026-08-11 ML-1A, strings updated in ML-3A): rewrites any stale
|
|
721
|
+
// relative-path entry from before this fix in place, so `trackfw update` doesn't just append the
|
|
722
|
+
// new $(git rev-parse ...) entry alongside the still-cwd-fragile old one.
|
|
723
|
+
migrateHookCommand(data.hooks.PermissionRequest, '.*', SIGNAL_CMD_CODEX_LEGACY, SIGNAL_CMD_CODEX)
|
|
724
|
+
migrateHookCommand(data.hooks.PreToolUse, 'Bash', GUARD_CMD_LEGACY, GUARD_CMD_CODEX)
|
|
725
|
+
migrateHookCommand(data.hooks.PreToolUse, 'apply_patch', GUARD_CMD_LEGACY, GUARD_CMD_CODEX)
|
|
726
|
+
migrateHookCommand(data.hooks.PostToolUse, '.*', CLEANUP_CMD_CODEX_LEGACY, CLEANUP_CMD_CODEX)
|
|
727
|
+
migrateHookCommand(data.hooks.PostToolUse, 'Bash', GUARD_CMD_LEGACY, GUARD_CMD_CODEX)
|
|
728
|
+
migrateHookCommand(data.hooks.PostToolUse, 'apply_patch', GUARD_CMD_LEGACY, GUARD_CMD_CODEX)
|
|
729
|
+
|
|
730
|
+
data.hooks.PermissionRequest = mergeClaudeHookArray(data.hooks.PermissionRequest, '.*', SIGNAL_CMD_CODEX)
|
|
637
731
|
// Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
|
|
638
732
|
// Wave 2 to apply_patch): skip project-scope credential-guard when the global one is already
|
|
639
733
|
// installed for this CLI.
|
|
640
734
|
const skipCodexCG = globalCredentialGuardInstalledCodex()
|
|
641
735
|
if (!skipCodexCG) {
|
|
642
|
-
data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Bash',
|
|
643
|
-
data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'apply_patch',
|
|
736
|
+
data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Bash', GUARD_CMD_CODEX)
|
|
737
|
+
data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'apply_patch', GUARD_CMD_CODEX)
|
|
644
738
|
}
|
|
645
|
-
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, '.*',
|
|
739
|
+
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, '.*', CLEANUP_CMD_CODEX)
|
|
646
740
|
if (!skipCodexCG) {
|
|
647
|
-
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Bash',
|
|
648
|
-
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'apply_patch',
|
|
741
|
+
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Bash', GUARD_CMD_CODEX)
|
|
742
|
+
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'apply_patch', GUARD_CMD_CODEX)
|
|
649
743
|
}
|
|
650
744
|
|
|
651
745
|
writeJSON(filePath, data)
|
|
@@ -682,21 +776,36 @@ function injectGeminiHooks(cwd) {
|
|
|
682
776
|
const data = readJSON(filePath)
|
|
683
777
|
|
|
684
778
|
if (!data.hooks) data.hooks = {}
|
|
685
|
-
|
|
779
|
+
|
|
780
|
+
// Migration wiring (ROADMAP-2026-08-11 ML-1A): old === new is a functional no-op today, but
|
|
781
|
+
// proves the call point exists and runs before the merge below. The wave that changes the
|
|
782
|
+
// Gemini command strings (ML-4A) updates the oldCommand argument here instead of adding this
|
|
783
|
+
// call from scratch -- without it, the merge's exact-string dedup would append a duplicate
|
|
784
|
+
// alongside the stale entry.
|
|
785
|
+
migrateHookCommand(data.hooks.Notification, 'ToolPermission', SIGNAL_CMD_GEMINI_LEGACY, SIGNAL_CMD_GEMINI)
|
|
786
|
+
migrateHookCommand(data.hooks.BeforeTool, 'run_shell_command', GUARD_CMD_GEMINI_LEGACY, GUARD_CMD_GEMINI)
|
|
787
|
+
migrateHookCommand(data.hooks.BeforeTool, 'read_file|read_many_files', GUARD_CMD_GEMINI_LEGACY, GUARD_CMD_GEMINI)
|
|
788
|
+
migrateHookCommand(data.hooks.BeforeTool, 'write_file|replace', GUARD_CMD_GEMINI_LEGACY, GUARD_CMD_GEMINI)
|
|
789
|
+
migrateHookCommand(data.hooks.AfterTool, '*', CLEANUP_CMD_GEMINI_LEGACY, CLEANUP_CMD_GEMINI)
|
|
790
|
+
migrateHookCommand(data.hooks.AfterTool, 'run_shell_command', GUARD_CMD_GEMINI_LEGACY, GUARD_CMD_GEMINI)
|
|
791
|
+
migrateHookCommand(data.hooks.AfterTool, 'read_file|read_many_files', GUARD_CMD_GEMINI_LEGACY, GUARD_CMD_GEMINI)
|
|
792
|
+
migrateHookCommand(data.hooks.AfterTool, 'write_file|replace', GUARD_CMD_GEMINI_LEGACY, GUARD_CMD_GEMINI)
|
|
793
|
+
|
|
794
|
+
data.hooks.Notification = mergeClaudeHookArray(data.hooks.Notification, 'ToolPermission', SIGNAL_CMD_GEMINI)
|
|
686
795
|
// Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
|
|
687
796
|
// Wave 2 to read_file|read_many_files / write_file|replace): skip project-scope
|
|
688
797
|
// credential-guard when the global one is already installed.
|
|
689
798
|
const skipGeminiCG = globalCredentialGuardInstalledGemini()
|
|
690
799
|
if (!skipGeminiCG) {
|
|
691
|
-
data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'run_shell_command',
|
|
692
|
-
data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'read_file|read_many_files',
|
|
693
|
-
data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'write_file|replace',
|
|
800
|
+
data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'run_shell_command', GUARD_CMD_GEMINI)
|
|
801
|
+
data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'read_file|read_many_files', GUARD_CMD_GEMINI)
|
|
802
|
+
data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'write_file|replace', GUARD_CMD_GEMINI)
|
|
694
803
|
}
|
|
695
|
-
data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, '*',
|
|
804
|
+
data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, '*', CLEANUP_CMD_GEMINI)
|
|
696
805
|
if (!skipGeminiCG) {
|
|
697
|
-
data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'run_shell_command',
|
|
698
|
-
data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'read_file|read_many_files',
|
|
699
|
-
data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'write_file|replace',
|
|
806
|
+
data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'run_shell_command', GUARD_CMD_GEMINI)
|
|
807
|
+
data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'read_file|read_many_files', GUARD_CMD_GEMINI)
|
|
808
|
+
data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'write_file|replace', GUARD_CMD_GEMINI)
|
|
700
809
|
}
|
|
701
810
|
|
|
702
811
|
writeJSON(filePath, data)
|
|
@@ -732,14 +841,14 @@ function injectKiroHooks(cwd) {
|
|
|
732
841
|
description: 'Signals trackfw board when agent executes a tool',
|
|
733
842
|
trigger: 'PreToolUse',
|
|
734
843
|
matcher: '*',
|
|
735
|
-
action: { type: 'command', command:
|
|
844
|
+
action: { type: 'command', command: SIGNAL_CMD_KIRO },
|
|
736
845
|
},
|
|
737
846
|
{
|
|
738
847
|
name: 'trackfw-attention-cleanup',
|
|
739
848
|
description: 'Clears trackfw board attention after tool completes',
|
|
740
849
|
trigger: 'PostToolUse',
|
|
741
850
|
matcher: '*',
|
|
742
|
-
action: { type: 'command', command:
|
|
851
|
+
action: { type: 'command', command: CLEANUP_CMD_KIRO },
|
|
743
852
|
},
|
|
744
853
|
]
|
|
745
854
|
|
|
@@ -753,14 +862,14 @@ function injectKiroHooks(cwd) {
|
|
|
753
862
|
description: 'Blocks/warns on possible plaintext credential materialization before a shell command executes',
|
|
754
863
|
trigger: 'PreToolUse',
|
|
755
864
|
matcher: 'shell',
|
|
756
|
-
action: { type: 'command', command:
|
|
865
|
+
action: { type: 'command', command: GUARD_CMD_KIRO },
|
|
757
866
|
},
|
|
758
867
|
{
|
|
759
868
|
name: 'trackfw-credential-guard-post',
|
|
760
869
|
description: 'Warns on possible plaintext credential materialization after a shell command executes',
|
|
761
870
|
trigger: 'PostToolUse',
|
|
762
871
|
matcher: 'shell',
|
|
763
|
-
action: { type: 'command', command:
|
|
872
|
+
action: { type: 'command', command: GUARD_CMD_KIRO },
|
|
764
873
|
},
|
|
765
874
|
// Read/Write coverage (ADR-2026-08-06 emenda 7, 2026-08-08): "read" and "write" are the
|
|
766
875
|
// documented Kiro tool-category aliases (fs_read/fs_write), same pattern as "shell" above.
|
|
@@ -769,28 +878,28 @@ function injectKiroHooks(cwd) {
|
|
|
769
878
|
description: 'Blocks/warns on possible plaintext credential materialization before a file read',
|
|
770
879
|
trigger: 'PreToolUse',
|
|
771
880
|
matcher: 'read',
|
|
772
|
-
action: { type: 'command', command:
|
|
881
|
+
action: { type: 'command', command: GUARD_CMD_KIRO },
|
|
773
882
|
},
|
|
774
883
|
{
|
|
775
884
|
name: 'trackfw-credential-guard-read-post',
|
|
776
885
|
description: 'Warns on possible plaintext credential materialization after a file read',
|
|
777
886
|
trigger: 'PostToolUse',
|
|
778
887
|
matcher: 'read',
|
|
779
|
-
action: { type: 'command', command:
|
|
888
|
+
action: { type: 'command', command: GUARD_CMD_KIRO },
|
|
780
889
|
},
|
|
781
890
|
{
|
|
782
891
|
name: 'trackfw-credential-guard-write-pre',
|
|
783
892
|
description: 'Blocks/warns on possible plaintext credential materialization before a file write',
|
|
784
893
|
trigger: 'PreToolUse',
|
|
785
894
|
matcher: 'write',
|
|
786
|
-
action: { type: 'command', command:
|
|
895
|
+
action: { type: 'command', command: GUARD_CMD_KIRO },
|
|
787
896
|
},
|
|
788
897
|
{
|
|
789
898
|
name: 'trackfw-credential-guard-write-post',
|
|
790
899
|
description: 'Warns on possible plaintext credential materialization after a file write',
|
|
791
900
|
trigger: 'PostToolUse',
|
|
792
901
|
matcher: 'write',
|
|
793
|
-
action: { type: 'command', command:
|
|
902
|
+
action: { type: 'command', command: GUARD_CMD_KIRO },
|
|
794
903
|
},
|
|
795
904
|
)
|
|
796
905
|
}
|
|
@@ -834,19 +943,19 @@ function injectKiroHooks(cwd) {
|
|
|
834
943
|
function injectCopilotHooks(cwd) {
|
|
835
944
|
const filePath = path.join(cwd, '.github', 'hooks', 'trackfw-attention.json')
|
|
836
945
|
|
|
837
|
-
const preToolUse = [{ type: 'command', bash:
|
|
838
|
-
const postToolUse = [{ type: 'command', bash:
|
|
946
|
+
const preToolUse = [{ type: 'command', bash: SIGNAL_CMD_COPILOT, cwd: '.', timeoutSec: 10 }]
|
|
947
|
+
const postToolUse = [{ type: 'command', bash: CLEANUP_CMD_COPILOT, cwd: '.', timeoutSec: 10 }]
|
|
839
948
|
|
|
840
949
|
// Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
|
|
841
950
|
// Wave 2 to view / create|edit): skip project-scope credential-guard entries when the global
|
|
842
951
|
// one is already installed.
|
|
843
952
|
if (!globalCredentialGuardInstalledCopilot()) {
|
|
844
|
-
preToolUse.push({ type: 'command', matcher: 'bash', bash:
|
|
845
|
-
preToolUse.push({ type: 'command', matcher: 'view', bash:
|
|
846
|
-
preToolUse.push({ type: 'command', matcher: 'create|edit', bash:
|
|
847
|
-
postToolUse.push({ type: 'command', matcher: 'bash', bash:
|
|
848
|
-
postToolUse.push({ type: 'command', matcher: 'view', bash:
|
|
849
|
-
postToolUse.push({ type: 'command', matcher: 'create|edit', bash:
|
|
953
|
+
preToolUse.push({ type: 'command', matcher: 'bash', bash: GUARD_CMD_COPILOT, cwd: '.', timeoutSec: 10 })
|
|
954
|
+
preToolUse.push({ type: 'command', matcher: 'view', bash: GUARD_CMD_COPILOT, cwd: '.', timeoutSec: 10 })
|
|
955
|
+
preToolUse.push({ type: 'command', matcher: 'create|edit', bash: GUARD_CMD_COPILOT, cwd: '.', timeoutSec: 10 })
|
|
956
|
+
postToolUse.push({ type: 'command', matcher: 'bash', bash: GUARD_CMD_COPILOT, cwd: '.', timeoutSec: 10 })
|
|
957
|
+
postToolUse.push({ type: 'command', matcher: 'view', bash: GUARD_CMD_COPILOT, cwd: '.', timeoutSec: 10 })
|
|
958
|
+
postToolUse.push({ type: 'command', matcher: 'create|edit', bash: GUARD_CMD_COPILOT, cwd: '.', timeoutSec: 10 })
|
|
850
959
|
}
|
|
851
960
|
|
|
852
961
|
const data = {
|
|
@@ -932,29 +1041,29 @@ function injectCursorHooks(cwd) {
|
|
|
932
1041
|
// Migrate any legacy top-level preToolUse/postToolUse trackfw entries
|
|
933
1042
|
// (written by trackfw before this ML) into the nested, real hooks.
|
|
934
1043
|
if (!Array.isArray(data.hooks.preToolUse)) data.hooks.preToolUse = []
|
|
935
|
-
if (!hasEntry(data.hooks.preToolUse, 'command',
|
|
936
|
-
data.hooks.preToolUse.push({ command:
|
|
1044
|
+
if (!hasEntry(data.hooks.preToolUse, 'command', SIGNAL_CMD_CURSOR)) {
|
|
1045
|
+
data.hooks.preToolUse.push({ command: SIGNAL_CMD_CURSOR })
|
|
937
1046
|
}
|
|
938
|
-
removeKnownCommandFromLegacyTopLevelArray(data, 'preToolUse',
|
|
1047
|
+
removeKnownCommandFromLegacyTopLevelArray(data, 'preToolUse', SIGNAL_CMD_CURSOR)
|
|
939
1048
|
|
|
940
1049
|
if (!Array.isArray(data.hooks.postToolUse)) data.hooks.postToolUse = []
|
|
941
|
-
if (!hasEntry(data.hooks.postToolUse, 'command',
|
|
942
|
-
data.hooks.postToolUse.push({ command:
|
|
1050
|
+
if (!hasEntry(data.hooks.postToolUse, 'command', CLEANUP_CMD_CURSOR)) {
|
|
1051
|
+
data.hooks.postToolUse.push({ command: CLEANUP_CMD_CURSOR })
|
|
943
1052
|
}
|
|
944
|
-
removeKnownCommandFromLegacyTopLevelArray(data, 'postToolUse',
|
|
1053
|
+
removeKnownCommandFromLegacyTopLevelArray(data, 'postToolUse', CLEANUP_CMD_CURSOR)
|
|
945
1054
|
|
|
946
1055
|
// Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
|
|
947
1056
|
// Wave 2 to Read/Write via the generic preToolUse/postToolUse events): skip project-scope
|
|
948
1057
|
// credential-guard entries when the global one is already installed.
|
|
949
1058
|
if (!globalCredentialGuardInstalledCursor()) {
|
|
950
1059
|
if (!Array.isArray(data.hooks.beforeShellExecution)) data.hooks.beforeShellExecution = []
|
|
951
|
-
if (!hasEntry(data.hooks.beforeShellExecution, 'command',
|
|
952
|
-
data.hooks.beforeShellExecution.push({ command:
|
|
1060
|
+
if (!hasEntry(data.hooks.beforeShellExecution, 'command', GUARD_CMD_CURSOR)) {
|
|
1061
|
+
data.hooks.beforeShellExecution.push({ command: GUARD_CMD_CURSOR })
|
|
953
1062
|
}
|
|
954
1063
|
|
|
955
1064
|
if (!Array.isArray(data.hooks.afterShellExecution)) data.hooks.afterShellExecution = []
|
|
956
|
-
if (!hasEntry(data.hooks.afterShellExecution, 'command',
|
|
957
|
-
data.hooks.afterShellExecution.push({ command:
|
|
1065
|
+
if (!hasEntry(data.hooks.afterShellExecution, 'command', GUARD_CMD_CURSOR)) {
|
|
1066
|
+
data.hooks.afterShellExecution.push({ command: GUARD_CMD_CURSOR })
|
|
958
1067
|
}
|
|
959
1068
|
|
|
960
1069
|
// Read/Write coverage (ADR-2026-08-06 emenda 7, 2026-08-08): wired via the generic
|
|
@@ -965,19 +1074,19 @@ function injectCursorHooks(cwd) {
|
|
|
965
1074
|
// unfiltered signal entry and these matcher-scoped guard entries share the same array, so
|
|
966
1075
|
// dedup must also check `matcher`.
|
|
967
1076
|
const hasGuardMatcherEntry = (arr, matcher) =>
|
|
968
|
-
Array.isArray(arr) && arr.some(e => e && e.command ===
|
|
1077
|
+
Array.isArray(arr) && arr.some(e => e && e.command === GUARD_CMD_CURSOR && e.matcher === matcher)
|
|
969
1078
|
|
|
970
1079
|
if (!hasGuardMatcherEntry(data.hooks.preToolUse, 'Read')) {
|
|
971
|
-
data.hooks.preToolUse.push({ command:
|
|
1080
|
+
data.hooks.preToolUse.push({ command: GUARD_CMD_CURSOR, matcher: 'Read' })
|
|
972
1081
|
}
|
|
973
1082
|
if (!hasGuardMatcherEntry(data.hooks.preToolUse, 'Write')) {
|
|
974
|
-
data.hooks.preToolUse.push({ command:
|
|
1083
|
+
data.hooks.preToolUse.push({ command: GUARD_CMD_CURSOR, matcher: 'Write' })
|
|
975
1084
|
}
|
|
976
1085
|
if (!hasGuardMatcherEntry(data.hooks.postToolUse, 'Read')) {
|
|
977
|
-
data.hooks.postToolUse.push({ command:
|
|
1086
|
+
data.hooks.postToolUse.push({ command: GUARD_CMD_CURSOR, matcher: 'Read' })
|
|
978
1087
|
}
|
|
979
1088
|
if (!hasGuardMatcherEntry(data.hooks.postToolUse, 'Write')) {
|
|
980
|
-
data.hooks.postToolUse.push({ command:
|
|
1089
|
+
data.hooks.postToolUse.push({ command: GUARD_CMD_CURSOR, matcher: 'Write' })
|
|
981
1090
|
}
|
|
982
1091
|
}
|
|
983
1092
|
|