trackfw 6.7.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackfw",
3
- "version": "6.7.0",
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",
@@ -85,6 +85,30 @@ function mergeClaudeHookArray(existing, matcher, command) {
85
85
  return arr
86
86
  }
87
87
 
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) {
102
+ const arr = Array.isArray(existing) ? existing : []
103
+ for (const item of arr) {
104
+ if (!item || item.matcher !== matcher) continue
105
+ const innerHooks = Array.isArray(item.hooks) ? item.hooks : []
106
+ for (const h of innerHooks) {
107
+ if (h && h.command === oldCommand) h.command = newCommand
108
+ }
109
+ }
110
+ }
111
+
88
112
  // ---------------------------------------------------------------------------
89
113
  // Scripts content
90
114
  // ---------------------------------------------------------------------------
@@ -416,9 +440,86 @@ function generateGlobalCredentialGuardScript(home) {
416
440
  console.log(' ✓ .trackfw/scripts/trackfw-credential-guard.sh')
417
441
  }
418
442
 
419
- const SIGNAL_CMD = 'scripts/trackfw-attention-signal.sh'
420
- const CLEANUP_CMD = 'scripts/trackfw-attention-cleanup.sh'
421
- const GUARD_CMD = 'scripts/trackfw-credential-guard.sh'
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
+
513
+ // Claude Code only (2026-08-09 fix, reported in production against the CMDB project): Claude Code
514
+ // resolves a bare relative hook command against the hook's *dynamic* cwd (tracks `cd`s the agent
515
+ // runs during the session), not the project root -- confirmed against
516
+ // https://code.claude.com/docs/en/hooks: "Handlers run in the current directory... cwd is dynamic".
517
+ // Any Bash/Read/Write/Edit call after the agent `cd`s into a subdirectory (e.g. a monorepo package)
518
+ // made the hook fail with "No such file or directory". $CLAUDE_PROJECT_DIR is the env var Claude
519
+ // Code guarantees stays pinned to the project root regardless of cwd drift (same doc) -- used here
520
+ // instead of GUARD_CMD, matching the pattern this project's own custom hooks already relied on
521
+ // successfully in practice.
522
+ const GUARD_CMD_CLAUDE = '$CLAUDE_PROJECT_DIR/scripts/trackfw-credential-guard.sh'
422
523
 
423
524
  // ---------------------------------------------------------------------------
424
525
  // Global credential-guard dedup (ROADMAP-2026-08-06 Wave 3/ML-3A)
@@ -551,22 +652,41 @@ function injectClaudeHooks(cwd) {
551
652
  const data = readJSON(filePath)
552
653
 
553
654
  if (!data.hooks) data.hooks = {}
554
- data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'AskUserQuestion', SIGNAL_CMD)
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)
666
+
667
+ // Rewrite any stale relative-path credential-guard command from an older trackfw run before
668
+ // merging the fixed one below, so upgrading doesn't just append a second, still-broken entry
669
+ // alongside the new one (see GUARD_CMD_CLAUDE comment for the "No such file or directory" bug).
670
+ for (const matcher of ['Bash', 'Read', 'Write|Edit']) {
671
+ migrateHookCommand(data.hooks.PreToolUse, matcher, GUARD_CMD_LEGACY, GUARD_CMD_CLAUDE)
672
+ migrateHookCommand(data.hooks.PostToolUse, matcher, GUARD_CMD_LEGACY, GUARD_CMD_CLAUDE)
673
+ }
674
+
555
675
  // Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
556
676
  // Wave 2 to Read/Write|Edit): skip project-scope credential-guard when the global one is
557
677
  // already installed for this CLI.
558
678
  if (!globalCredentialGuardInstalledClaude()) {
559
- data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Bash', GUARD_CMD)
679
+ data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Bash', GUARD_CMD_CLAUDE)
560
680
  // ADR-2026-08-06 emenda 7 (2026-08-08): Read/Write/Edit coverage — extraction via direct
561
681
  // file read, or materialization via write/edit, never went through the hook before.
562
- data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Read', GUARD_CMD)
563
- data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Write|Edit', GUARD_CMD)
682
+ data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Read', GUARD_CMD_CLAUDE)
683
+ data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Write|Edit', GUARD_CMD_CLAUDE)
564
684
  }
565
- data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'AskUserQuestion', CLEANUP_CMD)
685
+ data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'AskUserQuestion', CLEANUP_CMD_CLAUDE)
566
686
  if (!globalCredentialGuardInstalledClaude()) {
567
- data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Bash', GUARD_CMD)
568
- data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Read', GUARD_CMD)
569
- data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Write|Edit', GUARD_CMD)
687
+ data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Bash', GUARD_CMD_CLAUDE)
688
+ data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Read', GUARD_CMD_CLAUDE)
689
+ data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Write|Edit', GUARD_CMD_CLAUDE)
570
690
  }
571
691
 
572
692
  writeJSON(filePath, data)
@@ -596,19 +716,30 @@ function injectCodexHooks(cwd) {
596
716
  const data = readJSON(filePath)
597
717
 
598
718
  if (!data.hooks) data.hooks = {}
599
- data.hooks.PermissionRequest = mergeClaudeHookArray(data.hooks.PermissionRequest, '.*', SIGNAL_CMD)
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)
600
731
  // Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
601
732
  // Wave 2 to apply_patch): skip project-scope credential-guard when the global one is already
602
733
  // installed for this CLI.
603
734
  const skipCodexCG = globalCredentialGuardInstalledCodex()
604
735
  if (!skipCodexCG) {
605
- data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Bash', GUARD_CMD)
606
- data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'apply_patch', GUARD_CMD)
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)
607
738
  }
608
- data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, '.*', CLEANUP_CMD)
739
+ data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, '.*', CLEANUP_CMD_CODEX)
609
740
  if (!skipCodexCG) {
610
- data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Bash', GUARD_CMD)
611
- data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'apply_patch', GUARD_CMD)
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)
612
743
  }
613
744
 
614
745
  writeJSON(filePath, data)
@@ -645,21 +776,36 @@ function injectGeminiHooks(cwd) {
645
776
  const data = readJSON(filePath)
646
777
 
647
778
  if (!data.hooks) data.hooks = {}
648
- data.hooks.Notification = mergeClaudeHookArray(data.hooks.Notification, 'ToolPermission', SIGNAL_CMD)
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)
649
795
  // Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
650
796
  // Wave 2 to read_file|read_many_files / write_file|replace): skip project-scope
651
797
  // credential-guard when the global one is already installed.
652
798
  const skipGeminiCG = globalCredentialGuardInstalledGemini()
653
799
  if (!skipGeminiCG) {
654
- data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'run_shell_command', GUARD_CMD)
655
- data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'read_file|read_many_files', GUARD_CMD)
656
- data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'write_file|replace', GUARD_CMD)
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)
657
803
  }
658
- data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, '*', CLEANUP_CMD)
804
+ data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, '*', CLEANUP_CMD_GEMINI)
659
805
  if (!skipGeminiCG) {
660
- data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'run_shell_command', GUARD_CMD)
661
- data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'read_file|read_many_files', GUARD_CMD)
662
- data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'write_file|replace', GUARD_CMD)
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)
663
809
  }
664
810
 
665
811
  writeJSON(filePath, data)
@@ -695,14 +841,14 @@ function injectKiroHooks(cwd) {
695
841
  description: 'Signals trackfw board when agent executes a tool',
696
842
  trigger: 'PreToolUse',
697
843
  matcher: '*',
698
- action: { type: 'command', command: SIGNAL_CMD },
844
+ action: { type: 'command', command: SIGNAL_CMD_KIRO },
699
845
  },
700
846
  {
701
847
  name: 'trackfw-attention-cleanup',
702
848
  description: 'Clears trackfw board attention after tool completes',
703
849
  trigger: 'PostToolUse',
704
850
  matcher: '*',
705
- action: { type: 'command', command: CLEANUP_CMD },
851
+ action: { type: 'command', command: CLEANUP_CMD_KIRO },
706
852
  },
707
853
  ]
708
854
 
@@ -716,14 +862,14 @@ function injectKiroHooks(cwd) {
716
862
  description: 'Blocks/warns on possible plaintext credential materialization before a shell command executes',
717
863
  trigger: 'PreToolUse',
718
864
  matcher: 'shell',
719
- action: { type: 'command', command: GUARD_CMD },
865
+ action: { type: 'command', command: GUARD_CMD_KIRO },
720
866
  },
721
867
  {
722
868
  name: 'trackfw-credential-guard-post',
723
869
  description: 'Warns on possible plaintext credential materialization after a shell command executes',
724
870
  trigger: 'PostToolUse',
725
871
  matcher: 'shell',
726
- action: { type: 'command', command: GUARD_CMD },
872
+ action: { type: 'command', command: GUARD_CMD_KIRO },
727
873
  },
728
874
  // Read/Write coverage (ADR-2026-08-06 emenda 7, 2026-08-08): "read" and "write" are the
729
875
  // documented Kiro tool-category aliases (fs_read/fs_write), same pattern as "shell" above.
@@ -732,28 +878,28 @@ function injectKiroHooks(cwd) {
732
878
  description: 'Blocks/warns on possible plaintext credential materialization before a file read',
733
879
  trigger: 'PreToolUse',
734
880
  matcher: 'read',
735
- action: { type: 'command', command: GUARD_CMD },
881
+ action: { type: 'command', command: GUARD_CMD_KIRO },
736
882
  },
737
883
  {
738
884
  name: 'trackfw-credential-guard-read-post',
739
885
  description: 'Warns on possible plaintext credential materialization after a file read',
740
886
  trigger: 'PostToolUse',
741
887
  matcher: 'read',
742
- action: { type: 'command', command: GUARD_CMD },
888
+ action: { type: 'command', command: GUARD_CMD_KIRO },
743
889
  },
744
890
  {
745
891
  name: 'trackfw-credential-guard-write-pre',
746
892
  description: 'Blocks/warns on possible plaintext credential materialization before a file write',
747
893
  trigger: 'PreToolUse',
748
894
  matcher: 'write',
749
- action: { type: 'command', command: GUARD_CMD },
895
+ action: { type: 'command', command: GUARD_CMD_KIRO },
750
896
  },
751
897
  {
752
898
  name: 'trackfw-credential-guard-write-post',
753
899
  description: 'Warns on possible plaintext credential materialization after a file write',
754
900
  trigger: 'PostToolUse',
755
901
  matcher: 'write',
756
- action: { type: 'command', command: GUARD_CMD },
902
+ action: { type: 'command', command: GUARD_CMD_KIRO },
757
903
  },
758
904
  )
759
905
  }
@@ -797,19 +943,19 @@ function injectKiroHooks(cwd) {
797
943
  function injectCopilotHooks(cwd) {
798
944
  const filePath = path.join(cwd, '.github', 'hooks', 'trackfw-attention.json')
799
945
 
800
- const preToolUse = [{ type: 'command', bash: SIGNAL_CMD, cwd: '.', timeoutSec: 10 }]
801
- const postToolUse = [{ type: 'command', bash: CLEANUP_CMD, cwd: '.', timeoutSec: 10 }]
946
+ const preToolUse = [{ type: 'command', bash: SIGNAL_CMD_COPILOT, cwd: '.', timeoutSec: 10 }]
947
+ const postToolUse = [{ type: 'command', bash: CLEANUP_CMD_COPILOT, cwd: '.', timeoutSec: 10 }]
802
948
 
803
949
  // Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
804
950
  // Wave 2 to view / create|edit): skip project-scope credential-guard entries when the global
805
951
  // one is already installed.
806
952
  if (!globalCredentialGuardInstalledCopilot()) {
807
- preToolUse.push({ type: 'command', matcher: 'bash', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
808
- preToolUse.push({ type: 'command', matcher: 'view', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
809
- preToolUse.push({ type: 'command', matcher: 'create|edit', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
810
- postToolUse.push({ type: 'command', matcher: 'bash', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
811
- postToolUse.push({ type: 'command', matcher: 'view', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
812
- postToolUse.push({ type: 'command', matcher: 'create|edit', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
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 })
813
959
  }
814
960
 
815
961
  const data = {
@@ -895,29 +1041,29 @@ function injectCursorHooks(cwd) {
895
1041
  // Migrate any legacy top-level preToolUse/postToolUse trackfw entries
896
1042
  // (written by trackfw before this ML) into the nested, real hooks.
897
1043
  if (!Array.isArray(data.hooks.preToolUse)) data.hooks.preToolUse = []
898
- if (!hasEntry(data.hooks.preToolUse, 'command', SIGNAL_CMD)) {
899
- data.hooks.preToolUse.push({ command: SIGNAL_CMD })
1044
+ if (!hasEntry(data.hooks.preToolUse, 'command', SIGNAL_CMD_CURSOR)) {
1045
+ data.hooks.preToolUse.push({ command: SIGNAL_CMD_CURSOR })
900
1046
  }
901
- removeKnownCommandFromLegacyTopLevelArray(data, 'preToolUse', SIGNAL_CMD)
1047
+ removeKnownCommandFromLegacyTopLevelArray(data, 'preToolUse', SIGNAL_CMD_CURSOR)
902
1048
 
903
1049
  if (!Array.isArray(data.hooks.postToolUse)) data.hooks.postToolUse = []
904
- if (!hasEntry(data.hooks.postToolUse, 'command', CLEANUP_CMD)) {
905
- data.hooks.postToolUse.push({ command: CLEANUP_CMD })
1050
+ if (!hasEntry(data.hooks.postToolUse, 'command', CLEANUP_CMD_CURSOR)) {
1051
+ data.hooks.postToolUse.push({ command: CLEANUP_CMD_CURSOR })
906
1052
  }
907
- removeKnownCommandFromLegacyTopLevelArray(data, 'postToolUse', CLEANUP_CMD)
1053
+ removeKnownCommandFromLegacyTopLevelArray(data, 'postToolUse', CLEANUP_CMD_CURSOR)
908
1054
 
909
1055
  // Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
910
1056
  // Wave 2 to Read/Write via the generic preToolUse/postToolUse events): skip project-scope
911
1057
  // credential-guard entries when the global one is already installed.
912
1058
  if (!globalCredentialGuardInstalledCursor()) {
913
1059
  if (!Array.isArray(data.hooks.beforeShellExecution)) data.hooks.beforeShellExecution = []
914
- if (!hasEntry(data.hooks.beforeShellExecution, 'command', GUARD_CMD)) {
915
- data.hooks.beforeShellExecution.push({ command: GUARD_CMD })
1060
+ if (!hasEntry(data.hooks.beforeShellExecution, 'command', GUARD_CMD_CURSOR)) {
1061
+ data.hooks.beforeShellExecution.push({ command: GUARD_CMD_CURSOR })
916
1062
  }
917
1063
 
918
1064
  if (!Array.isArray(data.hooks.afterShellExecution)) data.hooks.afterShellExecution = []
919
- if (!hasEntry(data.hooks.afterShellExecution, 'command', GUARD_CMD)) {
920
- data.hooks.afterShellExecution.push({ command: GUARD_CMD })
1065
+ if (!hasEntry(data.hooks.afterShellExecution, 'command', GUARD_CMD_CURSOR)) {
1066
+ data.hooks.afterShellExecution.push({ command: GUARD_CMD_CURSOR })
921
1067
  }
922
1068
 
923
1069
  // Read/Write coverage (ADR-2026-08-06 emenda 7, 2026-08-08): wired via the generic
@@ -928,19 +1074,19 @@ function injectCursorHooks(cwd) {
928
1074
  // unfiltered signal entry and these matcher-scoped guard entries share the same array, so
929
1075
  // dedup must also check `matcher`.
930
1076
  const hasGuardMatcherEntry = (arr, matcher) =>
931
- Array.isArray(arr) && arr.some(e => e && e.command === GUARD_CMD && e.matcher === matcher)
1077
+ Array.isArray(arr) && arr.some(e => e && e.command === GUARD_CMD_CURSOR && e.matcher === matcher)
932
1078
 
933
1079
  if (!hasGuardMatcherEntry(data.hooks.preToolUse, 'Read')) {
934
- data.hooks.preToolUse.push({ command: GUARD_CMD, matcher: 'Read' })
1080
+ data.hooks.preToolUse.push({ command: GUARD_CMD_CURSOR, matcher: 'Read' })
935
1081
  }
936
1082
  if (!hasGuardMatcherEntry(data.hooks.preToolUse, 'Write')) {
937
- data.hooks.preToolUse.push({ command: GUARD_CMD, matcher: 'Write' })
1083
+ data.hooks.preToolUse.push({ command: GUARD_CMD_CURSOR, matcher: 'Write' })
938
1084
  }
939
1085
  if (!hasGuardMatcherEntry(data.hooks.postToolUse, 'Read')) {
940
- data.hooks.postToolUse.push({ command: GUARD_CMD, matcher: 'Read' })
1086
+ data.hooks.postToolUse.push({ command: GUARD_CMD_CURSOR, matcher: 'Read' })
941
1087
  }
942
1088
  if (!hasGuardMatcherEntry(data.hooks.postToolUse, 'Write')) {
943
- data.hooks.postToolUse.push({ command: GUARD_CMD, matcher: 'Write' })
1089
+ data.hooks.postToolUse.push({ command: GUARD_CMD_CURSOR, matcher: 'Write' })
944
1090
  }
945
1091
  }
946
1092