trackfw 6.7.0 → 6.7.1

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.7.1",
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,24 @@ 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) {
96
+ const arr = Array.isArray(existing) ? existing : []
97
+ for (const item of arr) {
98
+ if (!item || item.matcher !== matcher) continue
99
+ const innerHooks = Array.isArray(item.hooks) ? item.hooks : []
100
+ for (const h of innerHooks) {
101
+ if (h && h.command === oldCommand) h.command = newCommand
102
+ }
103
+ }
104
+ }
105
+
88
106
  // ---------------------------------------------------------------------------
89
107
  // Scripts content
90
108
  // ---------------------------------------------------------------------------
@@ -419,6 +437,16 @@ function generateGlobalCredentialGuardScript(home) {
419
437
  const SIGNAL_CMD = 'scripts/trackfw-attention-signal.sh'
420
438
  const CLEANUP_CMD = 'scripts/trackfw-attention-cleanup.sh'
421
439
  const GUARD_CMD = 'scripts/trackfw-credential-guard.sh'
440
+ // Claude Code only (2026-08-09 fix, reported in production against the CMDB project): Claude Code
441
+ // resolves a bare relative hook command against the hook's *dynamic* cwd (tracks `cd`s the agent
442
+ // runs during the session), not the project root -- confirmed against
443
+ // https://code.claude.com/docs/en/hooks: "Handlers run in the current directory... cwd is dynamic".
444
+ // Any Bash/Read/Write/Edit call after the agent `cd`s into a subdirectory (e.g. a monorepo package)
445
+ // made the hook fail with "No such file or directory". $CLAUDE_PROJECT_DIR is the env var Claude
446
+ // Code guarantees stays pinned to the project root regardless of cwd drift (same doc) -- used here
447
+ // instead of GUARD_CMD, matching the pattern this project's own custom hooks already relied on
448
+ // successfully in practice.
449
+ const GUARD_CMD_CLAUDE = '$CLAUDE_PROJECT_DIR/scripts/trackfw-credential-guard.sh'
422
450
 
423
451
  // ---------------------------------------------------------------------------
424
452
  // Global credential-guard dedup (ROADMAP-2026-08-06 Wave 3/ML-3A)
@@ -552,21 +580,30 @@ function injectClaudeHooks(cwd) {
552
580
 
553
581
  if (!data.hooks) data.hooks = {}
554
582
  data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'AskUserQuestion', SIGNAL_CMD)
583
+
584
+ // Rewrite any stale relative-path credential-guard command from an older trackfw run before
585
+ // merging the fixed one below, so upgrading doesn't just append a second, still-broken entry
586
+ // alongside the new one (see GUARD_CMD_CLAUDE comment for the "No such file or directory" bug).
587
+ 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)
590
+ }
591
+
555
592
  // Dedup (ROADMAP-2026-08-06 Wave 3/ML-3A, extended ADR-2026-08-06 emenda 7/ROADMAP-2026-08-08
556
593
  // Wave 2 to Read/Write|Edit): skip project-scope credential-guard when the global one is
557
594
  // already installed for this CLI.
558
595
  if (!globalCredentialGuardInstalledClaude()) {
559
- data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Bash', GUARD_CMD)
596
+ data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Bash', GUARD_CMD_CLAUDE)
560
597
  // ADR-2026-08-06 emenda 7 (2026-08-08): Read/Write/Edit coverage — extraction via direct
561
598
  // 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)
599
+ data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Read', GUARD_CMD_CLAUDE)
600
+ data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Write|Edit', GUARD_CMD_CLAUDE)
564
601
  }
565
602
  data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'AskUserQuestion', CLEANUP_CMD)
566
603
  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)
604
+ data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Bash', GUARD_CMD_CLAUDE)
605
+ data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Read', GUARD_CMD_CLAUDE)
606
+ data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Write|Edit', GUARD_CMD_CLAUDE)
570
607
  }
571
608
 
572
609
  writeJSON(filePath, data)