trackfw 6.9.1 → 7.0.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/README.md +0 -3
- package/package.json +1 -1
- package/src/branch/runner.js +33 -20
- package/src/changelog/index.js +114 -0
- package/src/commands/branch.js +12 -9
- package/src/commands/changelog.js +29 -0
- package/src/commands/commit.js +60 -0
- package/src/commands/discover.js +69 -0
- package/src/commands/index.js +22 -4
- package/src/commands/integrations.js +7 -0
- package/src/commands/ship.js +4 -3
- package/src/commands/thirdparty.js +384 -0
- package/src/commands/update-harness.js +3 -1
- package/src/commands/update.js +3 -1
- package/src/commit/runner.js +352 -0
- package/src/config/index.js +27 -0
- package/src/generators/hooks.js +479 -2
- package/src/generators/init.js +28 -6
- package/src/i18n/locales/en-US.json +1 -19
- package/src/i18n/locales/es-ES.json +1 -19
- package/src/i18n/locales/pt-BR.json +1 -19
- package/src/integrations/index.js +19 -2
- package/src/integrations/manager.js +15 -2
- package/src/integrations/render.js +102 -4
- package/src/lib/unknown-command.js +82 -0
- package/src/ship/runner.js +213 -26
- package/src/thirdparty/fetch.js +140 -0
- package/src/thirdparty/markers.js +207 -0
- package/src/thirdparty/provenance.js +114 -0
- package/src/thirdparty/quarantine.js +113 -0
- package/src/thirdparty/references.js +194 -0
- package/src/validator/index.js +715 -16
- package/src/commands/plugins.js +0 -184
package/src/generators/hooks.js
CHANGED
|
@@ -43,6 +43,23 @@ function mergeSimpleCommandArray(existing, command) {
|
|
|
43
43
|
return arr
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Merge helper for Windsurf's `.windsurf/hooks.json` `hooks.<event>` arrays
|
|
48
|
+
* (e.g. `hooks.pre_run_command`): each entry is `{"command": "...",
|
|
49
|
+
* "show_output": bool}` -- one field richer than mergeSimpleCommandArray's
|
|
50
|
+
* plain `{"command"}` shape (Cursor), so it needs its own merge helper.
|
|
51
|
+
* Mirrors Go's mergeSimpleCommandArray call in InjectWindsurfHooks
|
|
52
|
+
* (internal/generators/agentfiles.go), which is generic over the entry
|
|
53
|
+
* shape via makeEntry/getCmd callbacks -- Node has no equivalent generic
|
|
54
|
+
* variant, so this is a dedicated function instead.
|
|
55
|
+
*/
|
|
56
|
+
function mergeWindsurfHookArray(existing, command, showOutput) {
|
|
57
|
+
const arr = Array.isArray(existing) ? existing.slice() : []
|
|
58
|
+
if (hasEntry(arr, 'command', command)) return arr
|
|
59
|
+
arr.push({ command, show_output: showOutput })
|
|
60
|
+
return arr
|
|
61
|
+
}
|
|
62
|
+
|
|
46
63
|
/**
|
|
47
64
|
* Merge helper para arrays de hooks tipo GitHub Copilot
|
|
48
65
|
* (hooks.preToolUse/postToolUse): cada entry é
|
|
@@ -404,6 +421,218 @@ const CREDENTIAL_GUARD_SCRIPT = CG_HEADER + CG_PROJECT_GUARD + CG_DETECTION_CORE
|
|
|
404
421
|
|
|
405
422
|
const GLOBAL_CREDENTIAL_GUARD_SCRIPT = CG_HEADER + CG_DETECTION_CORE + CG_GLOBAL_TAIL
|
|
406
423
|
|
|
424
|
+
// ---------------------------------------------------------------------------
|
|
425
|
+
// GIT_BRANCH_GUARD_SCRIPT — trackfw-git-branch-guard.sh (ML-1A, ROADMAP-2026-08-14)
|
|
426
|
+
// ---------------------------------------------------------------------------
|
|
427
|
+
// Byte-identical port of internal/generators/scaffold.go:gitBranchGuardScript
|
|
428
|
+
// (Go's canonical reference). Blocks raw `git commit`/`git push`/
|
|
429
|
+
// `git checkout -b` by a subagent, regardless of runtime contract: emits BOTH
|
|
430
|
+
// `{"decision":"block","reason":"..."}` on stdout (Claude/Gemini JSON-stdout
|
|
431
|
+
// style) AND `exit 2` (Codex/Windsurf/Cursor exit-code style) simultaneously,
|
|
432
|
+
// same simplification decision as the Go const's doc comment. Unlike
|
|
433
|
+
// CREDENTIAL_GUARD_SCRIPT, this script is IDENTICAL between project and
|
|
434
|
+
// global scope (no trackfw.yaml dependency, no mode/roadmap_dir resolution)
|
|
435
|
+
// — a single constant is written by both generateGitBranchGuardScript and
|
|
436
|
+
// generateGlobalGitBranchGuardScript below, mirroring Go's single
|
|
437
|
+
// `gitBranchGuardScript` const reused by both Generate*/GenerateGlobal*
|
|
438
|
+
// functions.
|
|
439
|
+
//
|
|
440
|
+
// KNOWN LIMITATION (fix for 3 real bugs found by ML-4A manual E2E testing, 2026-08-14 —
|
|
441
|
+
// chained command `git status; git push ...` escaping detection, absolute path
|
|
442
|
+
// `/usr/bin/git commit` escaping via exact-string compare, and prose mentioning "git commit"
|
|
443
|
+
// inside a quoted string being read as a real command): the script's `match_subcommand()`
|
|
444
|
+
// now splits the raw command into segments on `;`/`&&`/`||`/`|`/newline and only treats a
|
|
445
|
+
// segment as a real git invocation if `git` (by basename) is that segment's FIRST token —
|
|
446
|
+
// this fixes all 3 bugs, but the splitter is NOT shell-quote-aware. It splits on those
|
|
447
|
+
// delimiter characters wherever they occur in the raw string, even inside single/double
|
|
448
|
+
// quotes. Practical consequence proven live in this session: a line inside a multi-line
|
|
449
|
+
// heredoc body that itself STARTS with the token `git` still blocks (newline is treated as
|
|
450
|
+
// a command boundary regardless of quoting context), and testing this script by embedding a
|
|
451
|
+
// literal `; git push`/`; git commit` substring inside a quoted JSON payload passed as a raw
|
|
452
|
+
// Bash tool command can trip the SAME guard when it's wired as this session's own
|
|
453
|
+
// PreToolUse/Bash hook — write such payloads to a file and pipe `< file.json` instead of
|
|
454
|
+
// inlining them in the shell command text. This limitation applies equally to the generated
|
|
455
|
+
// shell script itself (Go-generated and Node-generated content is byte-identical), even
|
|
456
|
+
// though internal/generators/scaffold.go's gitBranchGuardScript doc comment does not yet
|
|
457
|
+
// call it out explicitly — flagging for trackfw_architect to consider adding there too.
|
|
458
|
+
// GBG_BACKTICK — literal 2-char sequence "\\`" (backslash + backtick), used to embed a
|
|
459
|
+
// shell-escaped backtick (so REASON's `` `cmd` `` doesn't trigger bash command substitution
|
|
460
|
+
// inside the surrounding double-quoted string) without breaking the enclosing JS template
|
|
461
|
+
// literal. Mirrors the exact same problem Go's `gitBranchGuardScript` const solves by
|
|
462
|
+
// breaking out of its raw string and concatenating `"`"` — see scaffold.go's REASON lines
|
|
463
|
+
// (`\` + "`" + `trackfw ...`). A plain, unescaped backtick inside a JS template literal
|
|
464
|
+
// would either be interpreted as JS template-literal syntax (illegal placement) or, if
|
|
465
|
+
// naively escaped as just `` \` ``, would defeat the same regex-based source extraction the
|
|
466
|
+
// credential-guard parity test already documents as unable to handle string concatenation
|
|
467
|
+
// (see the CG_PROJECT_TAIL/CG_GLOBAL_TAIL comment above) — an embedded, un-terminated
|
|
468
|
+
// `` \` `` inside a single backtick-delimited block would make a naive
|
|
469
|
+
// `` const NAME = \`...\` `` regex stop at the first inner backtick instead of the real
|
|
470
|
+
// closing one. Splitting at every backtick insertion point, exactly like Go's own
|
|
471
|
+
// workaround, sidesteps both problems.
|
|
472
|
+
const GBG_BACKTICK = '\\`'
|
|
473
|
+
|
|
474
|
+
const GIT_BRANCH_GUARD_SCRIPT = `#!/usr/bin/env bash
|
|
475
|
+
# trackfw git branch guard — bloqueia git commit/push/checkout -b brutos por subagente
|
|
476
|
+
set -euo pipefail
|
|
477
|
+
set -f
|
|
478
|
+
|
|
479
|
+
# --- 1. Obter o comando git bruto ------------------------------------------------------------
|
|
480
|
+
if [ "$#" -gt 0 ]; then
|
|
481
|
+
CMD_RAW="$*"
|
|
482
|
+
else
|
|
483
|
+
INPUT=$(cat 2>/dev/null || true)
|
|
484
|
+
TRIMMED=$(printf '%s' "$INPUT" | sed -e 's/^[[:space:]]*//')
|
|
485
|
+
case "$TRIMMED" in
|
|
486
|
+
\\{*)
|
|
487
|
+
CMD_RAW=""
|
|
488
|
+
if command -v jq >/dev/null 2>&1; then
|
|
489
|
+
CMD_RAW=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // .command // .tool_info.command_line // .hook_input.command // empty' 2>/dev/null || true)
|
|
490
|
+
fi
|
|
491
|
+
if [ -z "$CMD_RAW" ] || [ "$CMD_RAW" = "null" ]; then
|
|
492
|
+
CMD_RAW=$(printf '%s' "$INPUT" | sed -n 's/.*"tool_input"[[:space:]]*:[[:space:]]*{[^}]*"command"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | head -1)
|
|
493
|
+
fi
|
|
494
|
+
if [ -z "$CMD_RAW" ]; then
|
|
495
|
+
CMD_RAW=$(printf '%s' "$INPUT" | sed -n 's/.*"tool_info"[[:space:]]*:[[:space:]]*{[^}]*"command_line"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | head -1)
|
|
496
|
+
fi
|
|
497
|
+
if [ -z "$CMD_RAW" ]; then
|
|
498
|
+
CMD_RAW=$(printf '%s' "$INPUT" | sed -n 's/.*"hook_input"[[:space:]]*:[[:space:]]*{[^}]*"command"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | head -1)
|
|
499
|
+
fi
|
|
500
|
+
if [ -z "$CMD_RAW" ]; then
|
|
501
|
+
CMD_RAW=$(printf '%s' "$INPUT" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | head -1)
|
|
502
|
+
fi
|
|
503
|
+
;;
|
|
504
|
+
*)
|
|
505
|
+
CMD_RAW="$INPUT"
|
|
506
|
+
;;
|
|
507
|
+
esac
|
|
508
|
+
fi
|
|
509
|
+
|
|
510
|
+
if [ -z "$CMD_RAW" ]; then
|
|
511
|
+
CMD_RAW="\${TRACKFW_GIT_COMMAND:-}"
|
|
512
|
+
fi
|
|
513
|
+
|
|
514
|
+
[ -n "$CMD_RAW" ] || exit 0
|
|
515
|
+
|
|
516
|
+
# --- 2. Casar contra "git (commit|push|checkout -b)", segmento por segmento -----------------
|
|
517
|
+
# Cada segmento é um comando real (dividido por ; && || | e por quebra de linha). "git" só
|
|
518
|
+
# conta se for o PRIMEIRO token do segmento (por basename, então /usr/bin/git também casa) —
|
|
519
|
+
# nunca uma ocorrência solta em qualquer posição da string inteira. Isso evita: (a) o segundo
|
|
520
|
+
# comando de uma cadeia escapar da checagem, (b) um path absoluto para o git escapar por
|
|
521
|
+
# comparação de igualdade exata, e (c) texto de prosa (ex.: mensagem de commit mencionando
|
|
522
|
+
# "git commit" no meio de uma frase) ser tratado como comando.
|
|
523
|
+
match_subcommand() {
|
|
524
|
+
normalized=$(printf '%s' "$1" | sed -e 's/&&/\\n/g' -e 's/||/\\n/g' -e 's/[;|]/\\n/g')
|
|
525
|
+
while IFS= read -r seg; do
|
|
526
|
+
seg_trimmed=$(printf '%s' "$seg" | sed -e 's/^[[:space:]]*//')
|
|
527
|
+
[ -n "$seg_trimmed" ] || continue
|
|
528
|
+
|
|
529
|
+
set -- $seg_trimmed
|
|
530
|
+
first="$1"
|
|
531
|
+
base="\${first##*/}"
|
|
532
|
+
[ "$base" = "git" ] || continue
|
|
533
|
+
shift
|
|
534
|
+
|
|
535
|
+
sub=""
|
|
536
|
+
while [ "$#" -gt 0 ]; do
|
|
537
|
+
tok="$1"
|
|
538
|
+
case "$tok" in
|
|
539
|
+
-C|-c|--work-tree|--git-dir|--namespace)
|
|
540
|
+
if [ "$#" -ge 2 ]; then shift 2; else shift; fi
|
|
541
|
+
continue
|
|
542
|
+
;;
|
|
543
|
+
-*)
|
|
544
|
+
shift
|
|
545
|
+
continue
|
|
546
|
+
;;
|
|
547
|
+
*)
|
|
548
|
+
sub="$tok"
|
|
549
|
+
shift
|
|
550
|
+
break
|
|
551
|
+
;;
|
|
552
|
+
esac
|
|
553
|
+
done
|
|
554
|
+
|
|
555
|
+
case "$sub" in
|
|
556
|
+
commit)
|
|
557
|
+
echo "commit"
|
|
558
|
+
return 0
|
|
559
|
+
;;
|
|
560
|
+
push)
|
|
561
|
+
echo "push"
|
|
562
|
+
return 0
|
|
563
|
+
;;
|
|
564
|
+
checkout)
|
|
565
|
+
if [ "\${1:-}" = "-b" ]; then
|
|
566
|
+
echo "checkout-b"
|
|
567
|
+
return 0
|
|
568
|
+
fi
|
|
569
|
+
;;
|
|
570
|
+
esac
|
|
571
|
+
done <<EOF
|
|
572
|
+
$normalized
|
|
573
|
+
EOF
|
|
574
|
+
return 1
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
SUBCOMMAND=$(match_subcommand "$CMD_RAW") || exit 0
|
|
578
|
+
|
|
579
|
+
case "$SUBCOMMAND" in
|
|
580
|
+
checkout-b)
|
|
581
|
+
REASON="trackfw: git checkout -b bruto bloqueado. Use ` + GBG_BACKTICK + `trackfw branch new <type>/<slug>` + GBG_BACKTICK + `. Ver CLAUDE.md §1."
|
|
582
|
+
;;
|
|
583
|
+
commit)
|
|
584
|
+
REASON="trackfw: git commit bruto bloqueado. Use ` + GBG_BACKTICK + `trackfw commit -m '<mensagem>'` + GBG_BACKTICK + `. Ver CLAUDE.md §1."
|
|
585
|
+
;;
|
|
586
|
+
push)
|
|
587
|
+
REASON="trackfw: git push bruto bloqueado. Use ` + GBG_BACKTICK + `trackfw ship` + GBG_BACKTICK + `. Ver CLAUDE.md §1."
|
|
588
|
+
;;
|
|
589
|
+
*)
|
|
590
|
+
exit 0
|
|
591
|
+
;;
|
|
592
|
+
esac
|
|
593
|
+
|
|
594
|
+
printf '{"decision":"block","reason":"%s"}\\n' "$REASON"
|
|
595
|
+
echo "$REASON" >&2
|
|
596
|
+
exit 2
|
|
597
|
+
`
|
|
598
|
+
|
|
599
|
+
// ---------------------------------------------------------------------------
|
|
600
|
+
// generateGitBranchGuardScript — writes scripts/trackfw-git-branch-guard.sh
|
|
601
|
+
// ---------------------------------------------------------------------------
|
|
602
|
+
// This ML only creates the script — it is NOT wired into any hooks.json/
|
|
603
|
+
// settings.json here (that is this roadmap's Wave 3 scope). Mirrors
|
|
604
|
+
// generateCredentialGuardScript exactly (mkdirSync + writeFileSync, mode 0o755).
|
|
605
|
+
function generateGitBranchGuardScript(cwd) {
|
|
606
|
+
const root = cwd || process.cwd()
|
|
607
|
+
const scriptsDir = path.join(root, 'scripts')
|
|
608
|
+
fs.mkdirSync(scriptsDir, { recursive: true })
|
|
609
|
+
|
|
610
|
+
const scriptPath = path.join(scriptsDir, 'trackfw-git-branch-guard.sh')
|
|
611
|
+
fs.writeFileSync(scriptPath, GIT_BRANCH_GUARD_SCRIPT, { encoding: 'utf8', mode: 0o755 })
|
|
612
|
+
|
|
613
|
+
console.log(' ✓ scripts/trackfw-git-branch-guard.sh')
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ---------------------------------------------------------------------------
|
|
617
|
+
// generateGlobalGitBranchGuardScript — writes <home>/.trackfw/scripts/trackfw-git-branch-guard.sh
|
|
618
|
+
// ---------------------------------------------------------------------------
|
|
619
|
+
// Destinado a ser referenciado por hooks globais de CLI, instalados via
|
|
620
|
+
// `trackfw update harness` -- não é chamado por `trackfw init`/`trackfw
|
|
621
|
+
// update` (escopo de projeto), que continuam usando
|
|
622
|
+
// generateGitBranchGuardScript. Mirrors generateGlobalCredentialGuardScript.
|
|
623
|
+
function generateGlobalGitBranchGuardScript(home) {
|
|
624
|
+
if (!home) {
|
|
625
|
+
throw new Error('home directory vazio')
|
|
626
|
+
}
|
|
627
|
+
const scriptsDir = path.join(home, '.trackfw', 'scripts')
|
|
628
|
+
fs.mkdirSync(scriptsDir, { recursive: true })
|
|
629
|
+
|
|
630
|
+
const scriptPath = path.join(scriptsDir, 'trackfw-git-branch-guard.sh')
|
|
631
|
+
fs.writeFileSync(scriptPath, GIT_BRANCH_GUARD_SCRIPT, { encoding: 'utf8', mode: 0o755 })
|
|
632
|
+
|
|
633
|
+
console.log(' ✓ .trackfw/scripts/trackfw-git-branch-guard.sh')
|
|
634
|
+
}
|
|
635
|
+
|
|
407
636
|
// ---------------------------------------------------------------------------
|
|
408
637
|
// generateCredentialGuardScript — writes scripts/trackfw-credential-guard.sh
|
|
409
638
|
// ---------------------------------------------------------------------------
|
|
@@ -521,6 +750,53 @@ const GUARD_CMD_CURSOR = 'scripts/trackfw-credential-guard.sh'
|
|
|
521
750
|
// successfully in practice.
|
|
522
751
|
const GUARD_CMD_CLAUDE = '$CLAUDE_PROJECT_DIR/scripts/trackfw-credential-guard.sh'
|
|
523
752
|
|
|
753
|
+
// ---------------------------------------------------------------------------
|
|
754
|
+
// GBG_CMD_* — git-branch-guard command per CLI (ROADMAP-2026-08-14, ML-3B,
|
|
755
|
+
// Wave 3). Same path-resolution mechanism per runtime already established
|
|
756
|
+
// above for GUARD_CMD_*/SIGNAL_CMD_* (Claude's $CLAUDE_PROJECT_DIR pin,
|
|
757
|
+
// Codex's `$(git rev-parse --show-toplevel)` substitution, Gemini's
|
|
758
|
+
// $GEMINI_PROJECT_DIR, Copilot/Cursor's project-root-relative path) — the
|
|
759
|
+
// git branch guard has no per-CLI variance of its own, it just needs the
|
|
760
|
+
// same "always resolve to the project root regardless of cwd drift" fix
|
|
761
|
+
// each of those constants already encodes for the credential guard.
|
|
762
|
+
//
|
|
763
|
+
// Wiring scope of this ML (Claude/Codex/Gemini/Cursor): registers the guard
|
|
764
|
+
// against the SAME hook events/matchers already used for the credential
|
|
765
|
+
// guard in each of these 4 CLIs' existing merge-based hooks.json/
|
|
766
|
+
// settings.json contract — a `Bash`/`run_shell_command`/`beforeShellExecution`
|
|
767
|
+
// PreToolUse-equivalent entry is sufficient to intercept `git commit`/`push`/
|
|
768
|
+
// `checkout -b` before they execute; no PostToolUse entry is added (unlike
|
|
769
|
+
// the credential guard) because blocking after the git command already ran
|
|
770
|
+
// is too late to be useful.
|
|
771
|
+
//
|
|
772
|
+
// NOT implemented in this ML (documented gap, mirrors the Go side of this
|
|
773
|
+
// same roadmap wave, which also has not built these yet): GitHub Copilot's
|
|
774
|
+
// `--deny-tool='shell(git commit)'`-style entries in a dedicated
|
|
775
|
+
// permissions-config.json/settings.json (a different mechanism than the
|
|
776
|
+
// existing `.github/hooks/trackfw-attention.json` hooks file this module
|
|
777
|
+
// already generates for Copilot); Windsurf's `pre_run_command` hook +
|
|
778
|
+
// `windsurf.cascadeCommandsAllowList` deny entry (injectWindsurfHooks today
|
|
779
|
+
// only injects textual rules, same as Go's InjectWindsurfHooks); and Amazon Q
|
|
780
|
+
// Developer's `preToolUse`/`execute_bash` hook + `deniedCommands` regex +
|
|
781
|
+
// restricted custom-agent toolset (no Amazon Q hook generator exists in this
|
|
782
|
+
// module today — only the textual `.amazonq/developer/guidelines.md` rules
|
|
783
|
+
// generator in generators/init.js — matching Go, which also has no
|
|
784
|
+
// InjectAmazonQHooks yet). Each of these requires a NEW file-format decision
|
|
785
|
+
// this roadmap wave has not yet settled on either stack; building one from
|
|
786
|
+
// scratch here, ahead of that decision, risks diverging from whatever the Go
|
|
787
|
+
// side lands on. Flagged for a follow-up ML once that decision is made.
|
|
788
|
+
const GBG_CMD_CLAUDE = '$CLAUDE_PROJECT_DIR/scripts/trackfw-git-branch-guard.sh'
|
|
789
|
+
const GBG_CMD_CODEX = CODEX_ROOT + '/scripts/trackfw-git-branch-guard.sh"'
|
|
790
|
+
const GBG_CMD_GEMINI = '$GEMINI_PROJECT_DIR/scripts/trackfw-git-branch-guard.sh'
|
|
791
|
+
const GBG_CMD_CURSOR = 'scripts/trackfw-git-branch-guard.sh'
|
|
792
|
+
const GBG_CMD_COPILOT = 'scripts/trackfw-git-branch-guard.sh'
|
|
793
|
+
const GBG_CMD_PLAIN = 'scripts/trackfw-git-branch-guard.sh'
|
|
794
|
+
// GBG_CMD_WINDSURF -- Windsurf invokes hooks.pre_run_command entries via a shell (confirmed
|
|
795
|
+
// against https://docs.devin.ai/desktop/cascade/hooks), so the guard script is wrapped in
|
|
796
|
+
// `bash <path>` rather than invoked directly, unlike every other CLI above. Mirrors Go's
|
|
797
|
+
// windsurfGitGuardCmd (internal/generators/agentfiles.go).
|
|
798
|
+
const GBG_CMD_WINDSURF = 'bash scripts/trackfw-git-branch-guard.sh'
|
|
799
|
+
|
|
524
800
|
// ---------------------------------------------------------------------------
|
|
525
801
|
// Global credential-guard dedup (ROADMAP-2026-08-06 Wave 3/ML-3A)
|
|
526
802
|
// ---------------------------------------------------------------------------
|
|
@@ -689,6 +965,13 @@ function injectClaudeHooks(cwd) {
|
|
|
689
965
|
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'Write|Edit', GUARD_CMD_CLAUDE)
|
|
690
966
|
}
|
|
691
967
|
|
|
968
|
+
// git branch guard (ROADMAP-2026-08-14, ML-3B/Wave 3, step 1): PreToolUse-only, matcher
|
|
969
|
+
// "Bash" — blocks raw `git commit`/`git push`/`git checkout -b` before they execute. No
|
|
970
|
+
// global-install dedup here (unlike GUARD_CMD_CLAUDE above): there is no per-CLI global
|
|
971
|
+
// git-branch-guard harness target yet (only the plain script-generation call at
|
|
972
|
+
// GenerateGlobalGitBranchGuardScript/generateGlobalGitBranchGuardScript exists so far).
|
|
973
|
+
data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Bash', GBG_CMD_CLAUDE)
|
|
974
|
+
|
|
692
975
|
writeJSON(filePath, data)
|
|
693
976
|
}
|
|
694
977
|
|
|
@@ -742,6 +1025,11 @@ function injectCodexHooks(cwd) {
|
|
|
742
1025
|
data.hooks.PostToolUse = mergeClaudeHookArray(data.hooks.PostToolUse, 'apply_patch', GUARD_CMD_CODEX)
|
|
743
1026
|
}
|
|
744
1027
|
|
|
1028
|
+
// git branch guard (ROADMAP-2026-08-14, ML-3B/Wave 3, step 2): PreToolUse-only, matcher
|
|
1029
|
+
// "Bash" -- git commands only ever run via the Bash tool, never apply_patch. No
|
|
1030
|
+
// global-install dedup (see GBG_CMD_CLAUDE comment).
|
|
1031
|
+
data.hooks.PreToolUse = mergeClaudeHookArray(data.hooks.PreToolUse, 'Bash', GBG_CMD_CODEX)
|
|
1032
|
+
|
|
745
1033
|
writeJSON(filePath, data)
|
|
746
1034
|
}
|
|
747
1035
|
|
|
@@ -808,6 +1096,11 @@ function injectGeminiHooks(cwd) {
|
|
|
808
1096
|
data.hooks.AfterTool = mergeClaudeHookArray(data.hooks.AfterTool, 'write_file|replace', GUARD_CMD_GEMINI)
|
|
809
1097
|
}
|
|
810
1098
|
|
|
1099
|
+
// git branch guard (ROADMAP-2026-08-14, ML-3B/Wave 3, step 3): BeforeTool-only, matcher
|
|
1100
|
+
// "run_shell_command" -- git commands only ever run via the shell tool. No global-install
|
|
1101
|
+
// dedup (see GBG_CMD_CLAUDE comment).
|
|
1102
|
+
data.hooks.BeforeTool = mergeClaudeHookArray(data.hooks.BeforeTool, 'run_shell_command', GBG_CMD_GEMINI)
|
|
1103
|
+
|
|
811
1104
|
writeJSON(filePath, data)
|
|
812
1105
|
}
|
|
813
1106
|
|
|
@@ -958,6 +1251,18 @@ function injectCopilotHooks(cwd) {
|
|
|
958
1251
|
postToolUse.push({ type: 'command', matcher: 'create|edit', bash: GUARD_CMD_COPILOT, cwd: '.', timeoutSec: 10 })
|
|
959
1252
|
}
|
|
960
1253
|
|
|
1254
|
+
// git branch guard (ROADMAP-2026-08-14, ML-3B/Wave 3, step 4): the roadmap describes
|
|
1255
|
+
// `--deny-tool='shell(git commit)'`-style CLI flags in a permissions-config.json/
|
|
1256
|
+
// settings.json file, but no such file/flag mechanism exists anywhere else in this
|
|
1257
|
+
// module -- Copilot's only established deny-adjacent mechanism here is this same
|
|
1258
|
+
// preToolUse/postToolUse hooks file already used for credential-guard above. Mirrors
|
|
1259
|
+
// Go's InjectCopilotHooks (internal/generators/agentfiles.go), which made the same
|
|
1260
|
+
// choice for the same reason; the CLI-flag/permissions-config.json approach is a
|
|
1261
|
+
// documented, deliberate divergence from the roadmap's literal wording. This file is
|
|
1262
|
+
// overwritten wholesale every run, so no dedup-against-global check is needed --
|
|
1263
|
+
// added unconditionally, matching the always-on attention-signal/cleanup entries.
|
|
1264
|
+
preToolUse.push({ type: 'command', matcher: 'bash', bash: GBG_CMD_COPILOT, cwd: '.', timeoutSec: 10 })
|
|
1265
|
+
|
|
961
1266
|
const data = {
|
|
962
1267
|
version: 1,
|
|
963
1268
|
hooks: { preToolUse, postToolUse },
|
|
@@ -1090,16 +1395,178 @@ function injectCursorHooks(cwd) {
|
|
|
1090
1395
|
}
|
|
1091
1396
|
}
|
|
1092
1397
|
|
|
1398
|
+
// git branch guard (ROADMAP-2026-08-14, ML-3B/Wave 3, step 5): beforeShellExecution-only --
|
|
1399
|
+
// Cursor's dedicated pre-execution Bash event; git commands only ever run there. No
|
|
1400
|
+
// global-install dedup (see GBG_CMD_CLAUDE comment).
|
|
1401
|
+
if (!Array.isArray(data.hooks.beforeShellExecution)) data.hooks.beforeShellExecution = []
|
|
1402
|
+
if (!hasEntry(data.hooks.beforeShellExecution, 'command', GBG_CMD_CURSOR)) {
|
|
1403
|
+
data.hooks.beforeShellExecution.push({ command: GBG_CMD_CURSOR })
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1093
1406
|
writeJSON(filePath, data)
|
|
1094
1407
|
}
|
|
1095
1408
|
|
|
1096
1409
|
// ---------------------------------------------------------------------------
|
|
1097
|
-
// Windsurf — update .windsurfrules with attention instruction
|
|
1410
|
+
// Windsurf — update .windsurfrules with attention instruction, and register
|
|
1411
|
+
// the `pre_run_command` git-branch-guard hook in Windsurf's real hooks file.
|
|
1098
1412
|
// ---------------------------------------------------------------------------
|
|
1413
|
+
// Path/schema correction (apolo-tf, 2026-08-14, post-ML-3A audit): the
|
|
1414
|
+
// original ML-3A implementation wrote a dedicated, wholly-owned file at
|
|
1415
|
+
// `.windsurf/hooks/trackfw-git-branch-guard.json` with an invented payload
|
|
1416
|
+
// shape (`{"version":1,"hooks":[{"name":...,"trigger":"pre_run_command",
|
|
1417
|
+
// "action":{...}}]}`) that was flagged in its own doc comment as UNCONFIRMED
|
|
1418
|
+
// against official documentation. A verification pass against
|
|
1419
|
+
// https://docs.devin.ai/desktop/cascade/hooks confirmed both the path and
|
|
1420
|
+
// the shape were wrong:
|
|
1421
|
+
// - Windsurf reads hooks from a single fixed-name file, `.windsurf/hooks.json`
|
|
1422
|
+
// -- NOT a directory of per-hook files under `.windsurf/hooks/`.
|
|
1423
|
+
// - The schema is `{"hooks": {"<event>": [{"command": "...", "show_output":
|
|
1424
|
+
// bool}]}}` -- an object keyed by event name (e.g. "pre_run_command",
|
|
1425
|
+
// "post_run_command"), each mapping to an ARRAY of hook defs. There is no
|
|
1426
|
+
// "name"/"trigger"/"action" envelope.
|
|
1427
|
+
// - The hook script receives its context via stdin as JSON, including
|
|
1428
|
+
// `tool_info.command_line` -- GIT_BRANCH_GUARD_SCRIPT above now tries
|
|
1429
|
+
// this field explicitly (in addition to the generic `.command`/
|
|
1430
|
+
// `.tool_input.command`/`.hook_input.command` fields it already
|
|
1431
|
+
// handled). This is a byte-shared script across all 3 CLIs -- do not
|
|
1432
|
+
// edit it again without mirroring Go's gitBranchGuardScript.
|
|
1433
|
+
//
|
|
1434
|
+
// Merge is idempotent and shaped like every other multi-tool settings file in
|
|
1435
|
+
// this module: existing `pre_run_command` entries from other tools (or a
|
|
1436
|
+
// prior trackfw run) are preserved; only an entry with our exact command
|
|
1437
|
+
// string is deduped via mergeWindsurfHookArray. Other events already present
|
|
1438
|
+
// (e.g. a user- or third-party-authored `post_run_command`) are left
|
|
1439
|
+
// untouched.
|
|
1440
|
+
//
|
|
1441
|
+
// Migration: if the stale `.windsurf/hooks/trackfw-git-branch-guard.json`
|
|
1442
|
+
// file from the incorrect ML-3A version exists on disk, it is removed here
|
|
1443
|
+
// (never left orphaned), and the now-possibly-empty legacy `.windsurf/hooks`
|
|
1444
|
+
// directory is best-effort cleaned up too -- mirrors Go's InjectWindsurfHooks
|
|
1445
|
+
// migration step exactly (os.Remove + best-effort rmdir).
|
|
1446
|
+
//
|
|
1447
|
+
// `windsurf.cascadeCommandsAllowList` (an IDE *user settings* key, not a
|
|
1448
|
+
// project-local file) remains out of scope -- same reasoning as before this
|
|
1449
|
+
// fix: trackfw has no established, confirmed mechanism for rewriting IDE user
|
|
1450
|
+
// settings safely, and inventing one on a guess repeats the exact mistake
|
|
1451
|
+
// this fix corrects. Documented as an open gap in docs/cli-parity.md.
|
|
1452
|
+
const LEGACY_WINDSURF_HOOKS_FILE = 'trackfw-git-branch-guard.json'
|
|
1099
1453
|
|
|
1100
1454
|
function injectWindsurfHooks(cwd) {
|
|
1101
1455
|
const { injectRulesForTool } = require('./init')
|
|
1102
|
-
|
|
1456
|
+
injectRulesForTool('windsurf', cwd)
|
|
1457
|
+
|
|
1458
|
+
// Migration: remove the incorrect, previously-written dedicated hook file
|
|
1459
|
+
// from an older (buggy) trackfw run, so it doesn't linger as a dead,
|
|
1460
|
+
// never-consumed artifact once the correct .windsurf/hooks.json exists.
|
|
1461
|
+
const legacyDir = path.join(cwd, '.windsurf', 'hooks')
|
|
1462
|
+
const legacyPath = path.join(legacyDir, LEGACY_WINDSURF_HOOKS_FILE)
|
|
1463
|
+
try {
|
|
1464
|
+
fs.unlinkSync(legacyPath)
|
|
1465
|
+
} catch (e) {
|
|
1466
|
+
if (e.code !== 'ENOENT') throw e
|
|
1467
|
+
}
|
|
1468
|
+
// Best-effort cleanup of the now-possibly-empty legacy directory; ignore
|
|
1469
|
+
// failure (non-empty dir, e.g. holding unrelated user files, or already
|
|
1470
|
+
// gone) -- never fatal.
|
|
1471
|
+
try {
|
|
1472
|
+
fs.rmdirSync(legacyDir)
|
|
1473
|
+
} catch (_) {
|
|
1474
|
+
// ignore
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
const dir = path.join(cwd, '.windsurf')
|
|
1478
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
1479
|
+
const filePath = path.join(dir, 'hooks.json')
|
|
1480
|
+
const data = readJSON(filePath)
|
|
1481
|
+
|
|
1482
|
+
if (typeof data.hooks !== 'object' || data.hooks === null || Array.isArray(data.hooks)) {
|
|
1483
|
+
data.hooks = {}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
data.hooks.pre_run_command = mergeWindsurfHookArray(data.hooks.pre_run_command, GBG_CMD_WINDSURF, true)
|
|
1487
|
+
|
|
1488
|
+
writeJSON(filePath, data)
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// ---------------------------------------------------------------------------
|
|
1492
|
+
// Amazon Q Developer CLI — .amazonq/cli-agents/q_cli_default.json
|
|
1493
|
+
// (ROADMAP-2026-08-14, ML-3B/Wave 3, step 7)
|
|
1494
|
+
// ---------------------------------------------------------------------------
|
|
1495
|
+
// Path/schema correction (apolo-tf, 2026-08-14, post-ML-3A audit): the
|
|
1496
|
+
// original ML-3A implementation wrote to `.amazonq/settings.json`, which was
|
|
1497
|
+
// flagged in its own doc comment as NOT independently confirmed against
|
|
1498
|
+
// official Amazon Q documentation. Verification against
|
|
1499
|
+
// https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-custom-agents-configuration.html
|
|
1500
|
+
// and https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-agents-default-behavior.html
|
|
1501
|
+
// confirmed the real mechanism is a *custom agent* file, not a settings
|
|
1502
|
+
// file: `.amazonq/cli-agents/q_cli_default.json` -- `q_cli_default.json` is
|
|
1503
|
+
// the documented convention closest to activating automatically without
|
|
1504
|
+
// requiring a manual `--agent` flag, though AWS has an open bug where this
|
|
1505
|
+
// default-name override is not always honored
|
|
1506
|
+
// (github.com/aws/amazon-q-developer-cli#2922) -- a custom agent named
|
|
1507
|
+
// `q_cli_default.json` is not guaranteed to always be picked up
|
|
1508
|
+
// automatically depending on CLI version/config. Documented rather than
|
|
1509
|
+
// worked around.
|
|
1510
|
+
//
|
|
1511
|
+
// Two guard mechanisms are wired, unchanged from the original ML-3A
|
|
1512
|
+
// implementation -- only the target file (and the minimal-but-valid custom
|
|
1513
|
+
// agent envelope around it) moved:
|
|
1514
|
+
// - hooks.preToolUse[matcher:"execute_bash"] -> the guard script, same
|
|
1515
|
+
// matcher+hooks[].command shape already used by Claude/Codex/Gemini
|
|
1516
|
+
// above (reuses mergeClaudeHookArray for idempotent merge).
|
|
1517
|
+
// - toolsSettings.execute_bash.deniedCommands -> a regex denylist
|
|
1518
|
+
// evaluated before allow, independent of and in addition to the hook.
|
|
1519
|
+
//
|
|
1520
|
+
// Minimal-but-valid custom agent schema (per command-line-custom-agents-
|
|
1521
|
+
// configuration.html): only set defaults for fields not already present, so
|
|
1522
|
+
// re-running against a hand-edited or previously-generated file never
|
|
1523
|
+
// clobbers user customization -- same "preserve existing settings" contract
|
|
1524
|
+
// as every other merge-based injector in this module. `tools: ["*"]` is
|
|
1525
|
+
// written on first creation so the default agent keeps today's unrestricted
|
|
1526
|
+
// tool access (this fix does not narrow what any agent can do, only where
|
|
1527
|
+
// the deny wiring lives). Mirrors Go's InjectAmazonQHooks
|
|
1528
|
+
// (internal/generators/agentfiles.go) field-for-field.
|
|
1529
|
+
//
|
|
1530
|
+
// Native custom-agent toolset restriction (REQ acceptance criterion): still
|
|
1531
|
+
// NOT implemented here -- this ML only wires the guard/deny fields on the
|
|
1532
|
+
// one default agent file; per-specialist-agent toolset restriction is out of
|
|
1533
|
+
// scope, same limitation already accepted for Gemini above.
|
|
1534
|
+
const AMAZONQ_CLI_AGENTS_DIR = 'cli-agents'
|
|
1535
|
+
const AMAZONQ_DEFAULT_AGENT_FILE = 'q_cli_default.json'
|
|
1536
|
+
const GBG_DENIED_COMMANDS_PATTERN = '^git (commit|push|checkout -b)'
|
|
1537
|
+
|
|
1538
|
+
function injectAmazonQHooks(cwd) {
|
|
1539
|
+
const filePath = path.join(cwd, '.amazonq', AMAZONQ_CLI_AGENTS_DIR, AMAZONQ_DEFAULT_AGENT_FILE)
|
|
1540
|
+
const data = readJSON(filePath)
|
|
1541
|
+
|
|
1542
|
+
const defaults = {
|
|
1543
|
+
name: 'q_cli_default',
|
|
1544
|
+
description: 'trackfw-managed default agent — wires the git branch guard hook/denylist. See docs/cli-parity.md.',
|
|
1545
|
+
prompt: null,
|
|
1546
|
+
mcpServers: {},
|
|
1547
|
+
tools: ['*'],
|
|
1548
|
+
toolAliases: {},
|
|
1549
|
+
allowedTools: [],
|
|
1550
|
+
resources: [],
|
|
1551
|
+
useLegacyMcpJson: false,
|
|
1552
|
+
}
|
|
1553
|
+
for (const [k, v] of Object.entries(defaults)) {
|
|
1554
|
+
if (!Object.prototype.hasOwnProperty.call(data, k)) data[k] = v
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
if (!data.hooks) data.hooks = {}
|
|
1558
|
+
data.hooks.preToolUse = mergeClaudeHookArray(data.hooks.preToolUse, 'execute_bash', GBG_CMD_PLAIN)
|
|
1559
|
+
|
|
1560
|
+
if (!data.toolsSettings) data.toolsSettings = {}
|
|
1561
|
+
if (!data.toolsSettings.execute_bash) data.toolsSettings.execute_bash = {}
|
|
1562
|
+
if (!Array.isArray(data.toolsSettings.execute_bash.deniedCommands)) {
|
|
1563
|
+
data.toolsSettings.execute_bash.deniedCommands = []
|
|
1564
|
+
}
|
|
1565
|
+
if (!data.toolsSettings.execute_bash.deniedCommands.includes(GBG_DENIED_COMMANDS_PATTERN)) {
|
|
1566
|
+
data.toolsSettings.execute_bash.deniedCommands.push(GBG_DENIED_COMMANDS_PATTERN)
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
writeJSON(filePath, data)
|
|
1103
1570
|
}
|
|
1104
1571
|
|
|
1105
1572
|
// ---------------------------------------------------------------------------
|
|
@@ -1146,6 +1613,13 @@ function injectHooksDetected(cwd) {
|
|
|
1146
1613
|
check: () => fs.existsSync(path.join(root, '.windsurfrules')),
|
|
1147
1614
|
fn: injectWindsurfHooks,
|
|
1148
1615
|
},
|
|
1616
|
+
// amazonq (ROADMAP-2026-08-14, ML-3B/Wave 3, step 7 dispatch): mirrors Go's
|
|
1617
|
+
// hooks.go InjectHooksDetected entry -- dispatches injectAmazonQHooks (git branch
|
|
1618
|
+
// guard, .amazonq/settings.json) whenever the .amazonq directory is present.
|
|
1619
|
+
amazonq: {
|
|
1620
|
+
check: () => fs.existsSync(path.join(root, '.amazonq')),
|
|
1621
|
+
fn: injectAmazonQHooks,
|
|
1622
|
+
},
|
|
1149
1623
|
}
|
|
1150
1624
|
|
|
1151
1625
|
for (const [name, { check, fn }] of Object.entries(detections)) {
|
|
@@ -1162,6 +1636,8 @@ module.exports = {
|
|
|
1162
1636
|
generateAttentionScripts,
|
|
1163
1637
|
generateCredentialGuardScript,
|
|
1164
1638
|
generateGlobalCredentialGuardScript,
|
|
1639
|
+
generateGitBranchGuardScript,
|
|
1640
|
+
generateGlobalGitBranchGuardScript,
|
|
1165
1641
|
injectClaudeHooks,
|
|
1166
1642
|
injectCodexHooks,
|
|
1167
1643
|
injectGeminiHooks,
|
|
@@ -1169,6 +1645,7 @@ module.exports = {
|
|
|
1169
1645
|
injectCopilotHooks,
|
|
1170
1646
|
injectCursorHooks,
|
|
1171
1647
|
injectWindsurfHooks,
|
|
1648
|
+
injectAmazonQHooks,
|
|
1172
1649
|
injectHooksDetected,
|
|
1173
1650
|
mergeClaudeHookArray,
|
|
1174
1651
|
mergeSimpleCommandArray,
|
package/src/generators/init.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const path = require('path')
|
|
5
5
|
const os = require('os')
|
|
6
|
+
const { readAgentConventions } = require('../config/index.js')
|
|
6
7
|
|
|
7
8
|
const GOV_DIRS = [
|
|
8
9
|
'docs/adr',
|
|
@@ -34,6 +35,7 @@ async function scaffold(cfg, cwd) {
|
|
|
34
35
|
generateValidateScript(cfg, root)
|
|
35
36
|
generateAttentionScripts(cfg, root)
|
|
36
37
|
generateCredentialGuardScript(root)
|
|
38
|
+
generateGitBranchGuardScript(root)
|
|
37
39
|
generateCIWorkflow(cfg, root)
|
|
38
40
|
generateGitHooks(cfg, root)
|
|
39
41
|
generateCommitMsgHook(cfg, root)
|
|
@@ -174,6 +176,11 @@ function generateCredentialGuardScript(cwd) {
|
|
|
174
176
|
gen(cwd || process.cwd())
|
|
175
177
|
}
|
|
176
178
|
|
|
179
|
+
function generateGitBranchGuardScript(cwd) {
|
|
180
|
+
const { generateGitBranchGuardScript: gen } = require('./hooks')
|
|
181
|
+
gen(cwd || process.cwd())
|
|
182
|
+
}
|
|
183
|
+
|
|
177
184
|
function injectHooksDetected(cwd) {
|
|
178
185
|
const { injectHooksDetected: inject } = require('./hooks')
|
|
179
186
|
inject(cwd || process.cwd())
|
|
@@ -392,7 +399,20 @@ const AGENT_HEADERS = {
|
|
|
392
399
|
cursor: '---\ndescription: trackfw governance rules\nglob: "**/*"\nalwaysApply: true\n---\n',
|
|
393
400
|
}
|
|
394
401
|
|
|
395
|
-
function trackfwRulesBlock() {
|
|
402
|
+
function trackfwRulesBlock(agentConventions) {
|
|
403
|
+
let conventionsSection = ''
|
|
404
|
+
if (agentConventions && String(agentConventions).trim() !== '') {
|
|
405
|
+
conventionsSection = `
|
|
406
|
+
|
|
407
|
+
### Project Conventions
|
|
408
|
+
> Declared by the team in \`trackfw.yaml\`'s \`agent_conventions\` field — NOT
|
|
409
|
+
> inferred automatically. trackfw does not impose an architectural standard; it only
|
|
410
|
+
> propagates what the project has already decided.
|
|
411
|
+
|
|
412
|
+
${String(agentConventions).trim()}
|
|
413
|
+
`
|
|
414
|
+
}
|
|
415
|
+
|
|
396
416
|
return RULES_START + `
|
|
397
417
|
## trackfw — Governance Rules
|
|
398
418
|
|
|
@@ -435,7 +455,7 @@ Delete the file when resolved. Visible as a live banner in \`trackfw serve\`.
|
|
|
435
455
|
- **Security wave:** include a red-team review wave in every feature roadmap
|
|
436
456
|
- **Test coverage:** TDD for critical logic; min 60% (prototype) / 80% (production)
|
|
437
457
|
- Use \`/trackfw:architect\` to define stack before the first REQ
|
|
438
|
-
|
|
458
|
+
` + conventionsSection + `
|
|
439
459
|
### Key Commands
|
|
440
460
|
- \`trackfw context\` — current governance state (always run first)
|
|
441
461
|
- \`trackfw status\` — all artifacts and states
|
|
@@ -451,10 +471,10 @@ Delete the file when resolved. Visible as a live banner in \`trackfw serve\`.
|
|
|
451
471
|
* - Arquivo existe, sem marcador: appenda o bloco no final
|
|
452
472
|
* - Arquivo existe, com marcador: substitui conteúdo entre marcadores
|
|
453
473
|
*/
|
|
454
|
-
function injectOrUpdateRules(filePath, headerIfNew) {
|
|
474
|
+
function injectOrUpdateRules(filePath, headerIfNew, cwd) {
|
|
455
475
|
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
456
476
|
|
|
457
|
-
const block = trackfwRulesBlock()
|
|
477
|
+
const block = trackfwRulesBlock(readAgentConventions(cwd))
|
|
458
478
|
|
|
459
479
|
if (!fs.existsSync(filePath)) {
|
|
460
480
|
let content = headerIfNew || ''
|
|
@@ -497,7 +517,8 @@ function injectRulesForTool(tool, cwd) {
|
|
|
497
517
|
const relPath = AGENT_FILES[tool]
|
|
498
518
|
if (!relPath) return
|
|
499
519
|
const header = AGENT_HEADERS[tool] || ''
|
|
500
|
-
|
|
520
|
+
const root = cwd || process.cwd()
|
|
521
|
+
injectOrUpdateRules(path.join(root, relPath), header, root)
|
|
501
522
|
}
|
|
502
523
|
|
|
503
524
|
/**
|
|
@@ -702,7 +723,7 @@ function generateClaudeMD(cfg) {
|
|
|
702
723
|
content += 'Decide low-risk details autonomously following existing project conventions, '
|
|
703
724
|
content += 'and record autonomous decisions in the commit message.\n'
|
|
704
725
|
|
|
705
|
-
injectOrUpdateRules('CLAUDE.md', content)
|
|
726
|
+
injectOrUpdateRules('CLAUDE.md', content, '.')
|
|
706
727
|
console.log(' ✓ CLAUDE.md')
|
|
707
728
|
}
|
|
708
729
|
|
|
@@ -1329,6 +1350,7 @@ module.exports = {
|
|
|
1329
1350
|
generateValidateScript,
|
|
1330
1351
|
generateAttentionScripts,
|
|
1331
1352
|
generateCredentialGuardScript,
|
|
1353
|
+
generateGitBranchGuardScript,
|
|
1332
1354
|
generateCIWorkflow,
|
|
1333
1355
|
generateGitHooks,
|
|
1334
1356
|
generateCommitMsgHook,
|