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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackfw",
3
- "version": "6.7.1",
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,14 +85,20 @@ function mergeClaudeHookArray(existing, matcher, command) {
85
85
  return arr
86
86
  }
87
87
 
88
- // migrateClaudeHookCommand rewrites a legacy hook command to a new one, in place, for every entry
89
- // matching the given matcher inside a Claude PreToolUse/PostToolUse array. Used to fix
90
- // .claude/settings.json files already written by an older trackfw before a command string changes
91
- // -- without this, re-running `trackfw init`/`update` only ever appends the new (fixed) command
92
- // alongside the stale one (merge dedup in mergeClaudeHookArray keys on the exact command string, so
93
- // it can't tell "same guard, new path" from "a different hook"), leaving the broken entry in place
94
- // to keep firing and failing forever.
95
- function migrateClaudeHookCommand(existing, matcher, oldCommand, newCommand) {
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
- const SIGNAL_CMD = 'scripts/trackfw-attention-signal.sh'
438
- const CLEANUP_CMD = 'scripts/trackfw-attention-cleanup.sh'
439
- 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
+
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
- 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)
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
- migrateClaudeHookCommand(data.hooks.PreToolUse, matcher, GUARD_CMD, GUARD_CMD_CLAUDE)
589
- migrateClaudeHookCommand(data.hooks.PostToolUse, matcher, GUARD_CMD, GUARD_CMD_CLAUDE)
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', CLEANUP_CMD)
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
- 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)
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', GUARD_CMD)
643
- 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)
644
738
  }
645
- data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, '.*', CLEANUP_CMD)
739
+ data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, '.*', CLEANUP_CMD_CODEX)
646
740
  if (!skipCodexCG) {
647
- data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Bash', GUARD_CMD)
648
- 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)
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
- 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)
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', GUARD_CMD)
692
- data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'read_file|read_many_files', GUARD_CMD)
693
- 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)
694
803
  }
695
- data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, '*', CLEANUP_CMD)
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', GUARD_CMD)
698
- data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'read_file|read_many_files', GUARD_CMD)
699
- 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)
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: SIGNAL_CMD },
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: CLEANUP_CMD },
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: GUARD_CMD },
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: GUARD_CMD },
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: GUARD_CMD },
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: GUARD_CMD },
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: GUARD_CMD },
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: GUARD_CMD },
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: SIGNAL_CMD, cwd: '.', timeoutSec: 10 }]
838
- 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 }]
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: GUARD_CMD, cwd: '.', timeoutSec: 10 })
845
- preToolUse.push({ type: 'command', matcher: 'view', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
846
- preToolUse.push({ type: 'command', matcher: 'create|edit', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
847
- postToolUse.push({ type: 'command', matcher: 'bash', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
848
- postToolUse.push({ type: 'command', matcher: 'view', bash: GUARD_CMD, cwd: '.', timeoutSec: 10 })
849
- 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 })
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', SIGNAL_CMD)) {
936
- 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 })
937
1046
  }
938
- removeKnownCommandFromLegacyTopLevelArray(data, 'preToolUse', SIGNAL_CMD)
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', CLEANUP_CMD)) {
942
- 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 })
943
1052
  }
944
- removeKnownCommandFromLegacyTopLevelArray(data, 'postToolUse', CLEANUP_CMD)
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', GUARD_CMD)) {
952
- 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 })
953
1062
  }
954
1063
 
955
1064
  if (!Array.isArray(data.hooks.afterShellExecution)) data.hooks.afterShellExecution = []
956
- if (!hasEntry(data.hooks.afterShellExecution, 'command', GUARD_CMD)) {
957
- 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 })
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 === GUARD_CMD && e.matcher === matcher)
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: GUARD_CMD, matcher: 'Read' })
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: GUARD_CMD, matcher: 'Write' })
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: GUARD_CMD, matcher: 'Read' })
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: GUARD_CMD, matcher: 'Write' })
1089
+ data.hooks.postToolUse.push({ command: GUARD_CMD_CURSOR, matcher: 'Write' })
981
1090
  }
982
1091
  }
983
1092