trackfw 7.5.0 → 7.5.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": "7.5.0",
3
+ "version": "7.5.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",
@@ -7,7 +7,7 @@ const identityStore = require('../identity')
7
7
  const projectConfig = require('../config')
8
8
  const { catalog, buildPlans, IntegrationManager, globalGroupPath } = require('../integrations')
9
9
  const { tildeify, validateTargets, buildDocument, humanReport, silenceConsole } = require('../lib/update-engine')
10
- const { mergeClaudeHookArray, mergeSimpleCommandArray, mergeCopilotHookArray, generateGlobalCredentialGuardScript, generateGlobalGitBranchGuardScript } = require('../generators/hooks')
10
+ const { mergeClaudeHookArray, mergeSimpleCommandArray, mergeCopilotHookArray, GIT_BRANCH_GUARD_SCRIPT, GLOBAL_CREDENTIAL_GUARD_SCRIPT } = require('../generators/hooks')
11
11
  const { homedir } = require('../homedir')
12
12
 
13
13
  // `trackfw update harness` is the global counterpart to `trackfw update` —
@@ -73,6 +73,72 @@ Cadeia: **ADR → REQ → ROADMAP** · Estados: \`backlog / wip / blocked / done
73
73
  `
74
74
  }
75
75
 
76
+ // gitBranchGuardScriptTarget — evaluates (and, unless --dry-run, applies) the
77
+ // global git-branch-guard shell script at
78
+ // ~/.trackfw/scripts/trackfw-git-branch-guard.sh.
79
+ //
80
+ // This target OWNS the script file. It compares content before writing so that
81
+ // an already up-to-date script reports "skipped" and its mtime is left unchanged
82
+ // (REQ-2026-09-09 AC3). Creation does not require --install-missing because
83
+ // ~/.trackfw/scripts/ is 100% trackfw-owned.
84
+ function gitBranchGuardScriptTarget(homeRoot, { dryRun }) {
85
+ const id = 'git-branch-guard-script'
86
+ const filePath = path.join(homeRoot, '.trackfw', 'scripts', 'trackfw-git-branch-guard.sh')
87
+ const displayPath = tildeify(homeRoot, filePath)
88
+ const desired = GIT_BRANCH_GUARD_SCRIPT
89
+
90
+ try {
91
+ const exists = fs.existsSync(filePath)
92
+ const actual = exists ? fs.readFileSync(filePath, 'utf8') : null
93
+
94
+ if (exists && actual === desired) {
95
+ // Content identical — skip write, repair execute bit without touching mtime.
96
+ try { fs.chmodSync(filePath, 0o755) } catch (_) { /* best-effort */ }
97
+ return { id, state: 'skipped', path: displayPath }
98
+ }
99
+
100
+ if (dryRun) return { id, state: 'updated', path: displayPath }
101
+
102
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
103
+ fs.writeFileSync(filePath, desired, { encoding: 'utf8', mode: 0o755 })
104
+ return { id, state: 'updated', path: displayPath }
105
+ } catch (e) {
106
+ return { id, state: 'failed', path: displayPath, message: e.message }
107
+ }
108
+ }
109
+
110
+ // credentialGuardScriptTarget — evaluates (and, unless --dry-run, applies) the
111
+ // global credential-guard shell script at
112
+ // ~/.trackfw/scripts/trackfw-credential-guard.sh.
113
+ //
114
+ // Mirrors gitBranchGuardScriptTarget — same ownership model, same idempotency
115
+ // contract, same absence of --install-missing gate.
116
+ function credentialGuardScriptTarget(homeRoot, { dryRun }) {
117
+ const id = 'credential-guard-script'
118
+ const filePath = path.join(homeRoot, '.trackfw', 'scripts', 'trackfw-credential-guard.sh')
119
+ const displayPath = tildeify(homeRoot, filePath)
120
+ const desired = GLOBAL_CREDENTIAL_GUARD_SCRIPT
121
+
122
+ try {
123
+ const exists = fs.existsSync(filePath)
124
+ const actual = exists ? fs.readFileSync(filePath, 'utf8') : null
125
+
126
+ if (exists && actual === desired) {
127
+ // Content identical — skip write, repair execute bit without touching mtime.
128
+ try { fs.chmodSync(filePath, 0o755) } catch (_) { /* best-effort */ }
129
+ return { id, state: 'skipped', path: displayPath }
130
+ }
131
+
132
+ if (dryRun) return { id, state: 'updated', path: displayPath }
133
+
134
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
135
+ fs.writeFileSync(filePath, desired, { encoding: 'utf8', mode: 0o755 })
136
+ return { id, state: 'updated', path: displayPath }
137
+ } catch (e) {
138
+ return { id, state: 'failed', path: displayPath, message: e.message }
139
+ }
140
+ }
141
+
76
142
  function claudeSkillTarget(homeRoot, { dryRun, installMissing }) {
77
143
  const id = 'claude-skill'
78
144
  const filePath = path.join(homeRoot, '.claude', 'skills', 'trackfw', 'SKILL.md')
@@ -799,7 +865,10 @@ function catalogBundleTarget(toolId, kind, homeRoot, identityConfig, { dryRun, i
799
865
  // which precede claude-agents/claude-skills), with credential-guard always
800
866
  // preceding git-branch-guard within a tool — see buildHarnessTargetIDs's
801
867
  // comment in update.go for the full rationale.
802
- const HARNESS_TARGET_IDS = ['claude-skill', 'claude-credential-guard', 'claude-git-branch-guard']
868
+ // Script targets precede every wiring target that references them so that even
869
+ // a --targets-filtered run that only requests a wiring target still finds the
870
+ // script already in place from a prior full run.
871
+ const HARNESS_TARGET_IDS = ['claude-skill', 'git-branch-guard-script', 'credential-guard-script', 'claude-credential-guard', 'claude-git-branch-guard']
803
872
  for (const target of catalog.targets) {
804
873
  if (target.id === 'codex') HARNESS_TARGET_IDS.push('codex-credential-guard', 'codex-git-branch-guard')
805
874
  if (target.id === 'gemini') HARNESS_TARGET_IDS.push('gemini-credential-guard', 'gemini-git-branch-guard')
@@ -818,6 +887,8 @@ function buildHarnessTargets(homeRoot, identityConfig, { dryRun, installMissing
818
887
  const include = (id) => !wanted || wanted.includes(id)
819
888
  const targets = []
820
889
  if (include('claude-skill')) targets.push(claudeSkillTarget(homeRoot, { dryRun, installMissing }))
890
+ if (include('git-branch-guard-script')) targets.push(gitBranchGuardScriptTarget(homeRoot, { dryRun }))
891
+ if (include('credential-guard-script')) targets.push(credentialGuardScriptTarget(homeRoot, { dryRun }))
821
892
  if (include('claude-credential-guard')) targets.push(credentialGuardTargetClaude(homeRoot, { dryRun, installMissing }))
822
893
  if (include('claude-git-branch-guard')) targets.push(gitBranchGuardTargetClaude(homeRoot, { dryRun, installMissing }))
823
894
  for (const target of catalog.targets) {
@@ -885,20 +956,11 @@ function run(options) {
885
956
  const dryRun = Boolean(options.dryRun)
886
957
  const installMissing = Boolean(options.installMissing)
887
958
 
888
- // The per-CLI *-credential-guard targets below only wire hook entries that
889
- // point at ~/.trackfw/scripts/trackfw-credential-guard.sh — none of them
890
- // write the script itself (ADR-2026-08-06, decision #2/#3). Without this
891
- // call the wiring is installed but every hook invocation fails with
892
- // "No such file or directory" because the script never exists.
893
- if (!dryRun) {
894
- if (options.json) {
895
- silenceConsole(() => generateGlobalCredentialGuardScript(homeRoot))
896
- silenceConsole(() => generateGlobalGitBranchGuardScript(homeRoot))
897
- } else {
898
- generateGlobalCredentialGuardScript(homeRoot)
899
- generateGlobalGitBranchGuardScript(homeRoot)
900
- }
901
- }
959
+ // NOTE: the script files are now first-class targets ("git-branch-guard-script",
960
+ // "credential-guard-script") positioned before the wiring targets in
961
+ // HARNESS_TARGET_IDS. The pre-loop unconditional writes that used to live here
962
+ // have been removed — the target functions handle creation and idempotency.
963
+ // (REQ-2026-09-09, ML-1A)
902
964
 
903
965
  // Identidade resolvida do disco antes de buildPlans — pular esta etapa
904
966
  // reverteria silenciosamente os nomes customizados para os defaults
@@ -2409,5 +2409,6 @@ module.exports = {
2409
2409
  SIGNAL_SCRIPT,
2410
2410
  CLEANUP_SCRIPT,
2411
2411
  CREDENTIAL_GUARD_SCRIPT,
2412
+ GLOBAL_CREDENTIAL_GUARD_SCRIPT,
2412
2413
  GIT_BRANCH_GUARD_SCRIPT,
2413
2414
  }