trackfw 7.0.0 → 7.2.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/bin/trackfw +38 -1
- package/package.json +1 -1
- package/src/branch/prune.js +450 -0
- package/src/commands/branch.js +47 -0
- package/src/commands/commit.js +7 -3
- package/src/commands/discover.js +6 -0
- package/src/commands/doctor.js +33 -0
- package/src/commands/index.js +3 -0
- package/src/commands/integrations.js +79 -2
- package/src/commands/push.js +51 -0
- package/src/commands/release.js +47 -0
- package/src/commands/serve.js +52 -4
- package/src/commands/ship.js +10 -2
- package/src/commands/update-harness.js +319 -12
- package/src/commands/update.js +1 -1
- package/src/config/index.js +17 -2
- package/src/generators/hooks.js +650 -45
- package/src/generators/init.js +12 -0
- package/src/i18n/locales/en-US.json +0 -3
- package/src/i18n/locales/es-ES.json +0 -3
- package/src/i18n/locales/pt-BR.json +0 -3
- package/src/integrations/assets/agents/architect.md +12 -0
- package/src/integrations/doctor.js +0 -0
- package/src/integrations/index.js +1 -1
- package/src/integrations/manager.js +118 -17
- package/src/integrations/render.js +116 -2
- package/src/lib/fatal-error.js +64 -0
- package/src/push/runner.js +223 -0
- package/src/release/runner.js +474 -0
- package/src/ship/runner.js +226 -93
- package/src/validator/index.js +686 -95
|
@@ -4,6 +4,7 @@ const fs = require('fs')
|
|
|
4
4
|
const os = require('os')
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const identityStore = require('../identity')
|
|
7
|
+
const projectConfig = require('../config')
|
|
7
8
|
const { catalog, buildPlans, IntegrationManager, globalGroupPath } = require('../integrations')
|
|
8
9
|
const { tildeify, validateTargets, buildDocument, humanReport, silenceConsole } = require('../lib/update-engine')
|
|
9
10
|
const { mergeClaudeHookArray, mergeSimpleCommandArray, mergeCopilotHookArray, generateGlobalCredentialGuardScript, generateGlobalGitBranchGuardScript } = require('../generators/hooks')
|
|
@@ -462,6 +463,288 @@ function credentialGuardTargetKiro(homeRoot, { dryRun, installMissing }) {
|
|
|
462
463
|
}
|
|
463
464
|
}
|
|
464
465
|
|
|
466
|
+
// ---------------------------------------------------------------------------
|
|
467
|
+
// git-branch-guard global-scope wiring (ROADMAP-2026-08-17 Wave 2/ML-2A).
|
|
468
|
+
// Mirrors the six credentialGuardTarget<Tool> functions above, entry-for-
|
|
469
|
+
// entry — same 4-state contract, same displayPath per tool, same reuse of
|
|
470
|
+
// mergeClaudeHookArray/mergeSimpleCommandArray/mergeCopilotHookArray (those
|
|
471
|
+
// helpers only need a scriptPath, which here is trackfw-git-branch-guard.sh
|
|
472
|
+
// instead of trackfw-credential-guard.sh). Mirrors
|
|
473
|
+
// internal/generators/update.go:harnessGitBranchGuardTarget<Tool>.
|
|
474
|
+
//
|
|
475
|
+
// Kiro is the one structural exception: credentialGuardTargetKiro rewrites
|
|
476
|
+
// ~/.kiro/hooks/trackfw-credential-guard.json WHOLESALE every run (never
|
|
477
|
+
// merges), so sharing that file with a second wholesale writer would make
|
|
478
|
+
// the two targets flap between each other's desired 2-entry document forever
|
|
479
|
+
// (idempotency failure). gitBranchGuardTargetKiro therefore writes its OWN
|
|
480
|
+
// dedicated file, ~/.kiro/hooks/trackfw-git-branch-guard.json, same
|
|
481
|
+
// {"version":"v1","hooks":[pre,post]} schema, hook names
|
|
482
|
+
// "trackfw-git-branch-guard-global-pre"/"-global-post".
|
|
483
|
+
// ---------------------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
function gitBranchGuardTargetClaude(homeRoot, { dryRun, installMissing }) {
|
|
486
|
+
const id = 'claude-git-branch-guard'
|
|
487
|
+
const filePath = path.join(homeRoot, '.claude', 'settings.json')
|
|
488
|
+
const displayPath = tildeify(homeRoot, filePath)
|
|
489
|
+
const scriptPath = path.join(homeRoot, '.trackfw', 'scripts', 'trackfw-git-branch-guard.sh')
|
|
490
|
+
|
|
491
|
+
let root
|
|
492
|
+
let raw = null
|
|
493
|
+
try {
|
|
494
|
+
if (fs.existsSync(filePath)) {
|
|
495
|
+
raw = fs.readFileSync(filePath, 'utf8')
|
|
496
|
+
root = raw.length ? JSON.parse(raw) : {}
|
|
497
|
+
} else {
|
|
498
|
+
if (!installMissing) return { id, state: 'missing', path: displayPath }
|
|
499
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
500
|
+
root = {}
|
|
501
|
+
if (!root.hooks) root.hooks = {}
|
|
502
|
+
root.hooks.PreToolUse = mergeClaudeHookArray(root.hooks.PreToolUse, 'Bash', scriptPath)
|
|
503
|
+
root.hooks.PostToolUse = mergeClaudeHookArray(root.hooks.PostToolUse, 'Bash', scriptPath)
|
|
504
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
505
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
506
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
507
|
+
return { id, state: 'updated', path: displayPath }
|
|
508
|
+
}
|
|
509
|
+
} catch (e) {
|
|
510
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
if (!root.hooks) root.hooks = {}
|
|
515
|
+
root.hooks.PreToolUse = mergeClaudeHookArray(root.hooks.PreToolUse, 'Bash', scriptPath)
|
|
516
|
+
root.hooks.PostToolUse = mergeClaudeHookArray(root.hooks.PostToolUse, 'Bash', scriptPath)
|
|
517
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
518
|
+
if (desired === raw) return { id, state: 'skipped', path: displayPath }
|
|
519
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
520
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
521
|
+
return { id, state: 'updated', path: displayPath }
|
|
522
|
+
} catch (e) {
|
|
523
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function gitBranchGuardTargetCodex(homeRoot, { dryRun, installMissing }) {
|
|
528
|
+
const id = 'codex-git-branch-guard'
|
|
529
|
+
const filePath = path.join(homeRoot, '.codex', 'hooks.json')
|
|
530
|
+
const displayPath = tildeify(homeRoot, filePath)
|
|
531
|
+
const scriptPath = path.join(homeRoot, '.trackfw', 'scripts', 'trackfw-git-branch-guard.sh')
|
|
532
|
+
|
|
533
|
+
let root
|
|
534
|
+
let raw = null
|
|
535
|
+
try {
|
|
536
|
+
if (fs.existsSync(filePath)) {
|
|
537
|
+
raw = fs.readFileSync(filePath, 'utf8')
|
|
538
|
+
root = raw.length ? JSON.parse(raw) : {}
|
|
539
|
+
} else {
|
|
540
|
+
if (!installMissing) return { id, state: 'missing', path: displayPath }
|
|
541
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
542
|
+
root = {}
|
|
543
|
+
if (!root.hooks) root.hooks = {}
|
|
544
|
+
root.hooks.PreToolUse = mergeClaudeHookArray(root.hooks.PreToolUse, 'Bash', scriptPath)
|
|
545
|
+
root.hooks.PostToolUse = mergeClaudeHookArray(root.hooks.PostToolUse, 'Bash', scriptPath)
|
|
546
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
547
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
548
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
549
|
+
return { id, state: 'updated', path: displayPath }
|
|
550
|
+
}
|
|
551
|
+
} catch (e) {
|
|
552
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
if (!root.hooks) root.hooks = {}
|
|
557
|
+
root.hooks.PreToolUse = mergeClaudeHookArray(root.hooks.PreToolUse, 'Bash', scriptPath)
|
|
558
|
+
root.hooks.PostToolUse = mergeClaudeHookArray(root.hooks.PostToolUse, 'Bash', scriptPath)
|
|
559
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
560
|
+
if (desired === raw) return { id, state: 'skipped', path: displayPath }
|
|
561
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
562
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
563
|
+
return { id, state: 'updated', path: displayPath }
|
|
564
|
+
} catch (e) {
|
|
565
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function gitBranchGuardTargetGemini(homeRoot, { dryRun, installMissing }) {
|
|
570
|
+
const id = 'gemini-git-branch-guard'
|
|
571
|
+
const filePath = path.join(homeRoot, '.gemini', 'settings.json')
|
|
572
|
+
const displayPath = tildeify(homeRoot, filePath)
|
|
573
|
+
const scriptPath = path.join(homeRoot, '.trackfw', 'scripts', 'trackfw-git-branch-guard.sh')
|
|
574
|
+
|
|
575
|
+
let root
|
|
576
|
+
let raw = null
|
|
577
|
+
try {
|
|
578
|
+
if (fs.existsSync(filePath)) {
|
|
579
|
+
raw = fs.readFileSync(filePath, 'utf8')
|
|
580
|
+
root = raw.length ? JSON.parse(raw) : {}
|
|
581
|
+
} else {
|
|
582
|
+
if (!installMissing) return { id, state: 'missing', path: displayPath }
|
|
583
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
584
|
+
root = {}
|
|
585
|
+
if (!root.hooks) root.hooks = {}
|
|
586
|
+
root.hooks.BeforeTool = mergeClaudeHookArray(root.hooks.BeforeTool, 'run_shell_command', scriptPath)
|
|
587
|
+
root.hooks.AfterTool = mergeClaudeHookArray(root.hooks.AfterTool, 'run_shell_command', scriptPath)
|
|
588
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
589
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
590
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
591
|
+
return { id, state: 'updated', path: displayPath }
|
|
592
|
+
}
|
|
593
|
+
} catch (e) {
|
|
594
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
if (!root.hooks) root.hooks = {}
|
|
599
|
+
root.hooks.BeforeTool = mergeClaudeHookArray(root.hooks.BeforeTool, 'run_shell_command', scriptPath)
|
|
600
|
+
root.hooks.AfterTool = mergeClaudeHookArray(root.hooks.AfterTool, 'run_shell_command', scriptPath)
|
|
601
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
602
|
+
if (desired === raw) return { id, state: 'skipped', path: displayPath }
|
|
603
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
604
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
605
|
+
return { id, state: 'updated', path: displayPath }
|
|
606
|
+
} catch (e) {
|
|
607
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function gitBranchGuardTargetCursor(homeRoot, { dryRun, installMissing }) {
|
|
612
|
+
const id = 'cursor-git-branch-guard'
|
|
613
|
+
const filePath = path.join(homeRoot, '.cursor', 'hooks.json')
|
|
614
|
+
const displayPath = tildeify(homeRoot, filePath)
|
|
615
|
+
const scriptPath = path.join(homeRoot, '.trackfw', 'scripts', 'trackfw-git-branch-guard.sh')
|
|
616
|
+
|
|
617
|
+
let root
|
|
618
|
+
let raw = null
|
|
619
|
+
try {
|
|
620
|
+
if (fs.existsSync(filePath)) {
|
|
621
|
+
raw = fs.readFileSync(filePath, 'utf8')
|
|
622
|
+
root = raw.length ? JSON.parse(raw) : {}
|
|
623
|
+
} else {
|
|
624
|
+
if (!installMissing) return { id, state: 'missing', path: displayPath }
|
|
625
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
626
|
+
root = {}
|
|
627
|
+
if (typeof root.version === 'undefined') root.version = 1
|
|
628
|
+
if (!root.hooks) root.hooks = {}
|
|
629
|
+
root.hooks.beforeShellExecution = mergeSimpleCommandArray(root.hooks.beforeShellExecution, scriptPath)
|
|
630
|
+
root.hooks.afterShellExecution = mergeSimpleCommandArray(root.hooks.afterShellExecution, scriptPath)
|
|
631
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
632
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
633
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
634
|
+
return { id, state: 'updated', path: displayPath }
|
|
635
|
+
}
|
|
636
|
+
} catch (e) {
|
|
637
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
try {
|
|
641
|
+
if (typeof root.version === 'undefined') root.version = 1
|
|
642
|
+
if (!root.hooks) root.hooks = {}
|
|
643
|
+
root.hooks.beforeShellExecution = mergeSimpleCommandArray(root.hooks.beforeShellExecution, scriptPath)
|
|
644
|
+
root.hooks.afterShellExecution = mergeSimpleCommandArray(root.hooks.afterShellExecution, scriptPath)
|
|
645
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
646
|
+
if (desired === raw) return { id, state: 'skipped', path: displayPath }
|
|
647
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
648
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
649
|
+
return { id, state: 'updated', path: displayPath }
|
|
650
|
+
} catch (e) {
|
|
651
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function gitBranchGuardTargetCopilot(homeRoot, { dryRun, installMissing }) {
|
|
656
|
+
const id = 'copilot-git-branch-guard'
|
|
657
|
+
const filePath = path.join(homeRoot, '.copilot', 'settings.json')
|
|
658
|
+
const displayPath = tildeify(homeRoot, filePath)
|
|
659
|
+
const scriptPath = path.join(homeRoot, '.trackfw', 'scripts', 'trackfw-git-branch-guard.sh')
|
|
660
|
+
|
|
661
|
+
let root
|
|
662
|
+
let raw = null
|
|
663
|
+
try {
|
|
664
|
+
if (fs.existsSync(filePath)) {
|
|
665
|
+
raw = fs.readFileSync(filePath, 'utf8')
|
|
666
|
+
root = raw.length ? JSON.parse(raw) : {}
|
|
667
|
+
} else {
|
|
668
|
+
if (!installMissing) return { id, state: 'missing', path: displayPath }
|
|
669
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
670
|
+
root = {}
|
|
671
|
+
if (!root.hooks) root.hooks = {}
|
|
672
|
+
root.hooks.preToolUse = mergeCopilotHookArray(root.hooks.preToolUse, scriptPath)
|
|
673
|
+
root.hooks.postToolUse = mergeCopilotHookArray(root.hooks.postToolUse, scriptPath)
|
|
674
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
675
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
676
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
677
|
+
return { id, state: 'updated', path: displayPath }
|
|
678
|
+
}
|
|
679
|
+
} catch (e) {
|
|
680
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
try {
|
|
684
|
+
if (!root.hooks) root.hooks = {}
|
|
685
|
+
root.hooks.preToolUse = mergeCopilotHookArray(root.hooks.preToolUse, scriptPath)
|
|
686
|
+
root.hooks.postToolUse = mergeCopilotHookArray(root.hooks.postToolUse, scriptPath)
|
|
687
|
+
const desired = JSON.stringify(root, null, 2) + '\n'
|
|
688
|
+
if (desired === raw) return { id, state: 'skipped', path: displayPath }
|
|
689
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
690
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
691
|
+
return { id, state: 'updated', path: displayPath }
|
|
692
|
+
} catch (e) {
|
|
693
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// gitBranchGuardTargetKiro writes a DEDICATED file,
|
|
698
|
+
// ~/.kiro/hooks/trackfw-git-branch-guard.json — deliberately NOT
|
|
699
|
+
// credentialGuardTargetKiro's file. See this section's header comment for
|
|
700
|
+
// why sharing would break idempotency.
|
|
701
|
+
function gitBranchGuardTargetKiro(homeRoot, { dryRun, installMissing }) {
|
|
702
|
+
const id = 'kiro-git-branch-guard'
|
|
703
|
+
const filePath = path.join(homeRoot, '.kiro', 'hooks', 'trackfw-git-branch-guard.json')
|
|
704
|
+
const displayPath = tildeify(homeRoot, filePath)
|
|
705
|
+
const scriptPath = path.join(homeRoot, '.trackfw', 'scripts', 'trackfw-git-branch-guard.sh')
|
|
706
|
+
|
|
707
|
+
const desired = JSON.stringify(
|
|
708
|
+
{
|
|
709
|
+
version: 'v1',
|
|
710
|
+
hooks: [
|
|
711
|
+
{
|
|
712
|
+
name: 'trackfw-git-branch-guard-global-pre',
|
|
713
|
+
description: 'Blocks branch-creation git subcommands issued outside trackfw branch new (global, all trackfw projects)',
|
|
714
|
+
trigger: 'PreToolUse',
|
|
715
|
+
matcher: 'shell',
|
|
716
|
+
action: { type: 'command', command: scriptPath },
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
name: 'trackfw-git-branch-guard-global-post',
|
|
720
|
+
description: 'Warns on branch-creation git subcommands issued outside trackfw branch new (global, all trackfw projects)',
|
|
721
|
+
trigger: 'PostToolUse',
|
|
722
|
+
matcher: 'shell',
|
|
723
|
+
action: { type: 'command', command: scriptPath },
|
|
724
|
+
},
|
|
725
|
+
],
|
|
726
|
+
},
|
|
727
|
+
null,
|
|
728
|
+
2
|
|
729
|
+
) + '\n'
|
|
730
|
+
|
|
731
|
+
try {
|
|
732
|
+
const exists = fs.existsSync(filePath)
|
|
733
|
+
const actual = exists ? fs.readFileSync(filePath, 'utf8') : null
|
|
734
|
+
|
|
735
|
+
if (!exists && !installMissing) return { id, state: 'missing', path: displayPath }
|
|
736
|
+
if (exists && actual === desired) return { id, state: 'skipped', path: displayPath }
|
|
737
|
+
|
|
738
|
+
if (dryRun) return { id, state: 'updated', path: displayPath }
|
|
739
|
+
|
|
740
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
741
|
+
fs.writeFileSync(filePath, desired, 'utf8')
|
|
742
|
+
return { id, state: 'updated', path: displayPath }
|
|
743
|
+
} catch (e) {
|
|
744
|
+
return { id, state: 'failed', path: displayPath, message: e.message }
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
465
748
|
// catalogBundleTarget — one target per (tool, kind) pair at global scope.
|
|
466
749
|
// Uses IntegrationManager.inspect (read-only) to classify every catalog
|
|
467
750
|
// item under that pair, then only calls manager.update() for the subset
|
|
@@ -475,7 +758,7 @@ function catalogBundleTarget(toolId, kind, homeRoot, identityConfig, { dryRun, i
|
|
|
475
758
|
let displayPath = `~/.${toolId}`
|
|
476
759
|
try {
|
|
477
760
|
displayPath = globalGroupPath(toolId, kind)
|
|
478
|
-
const plans = buildPlans(kind, { targets: [toolId], scope: 'global', identity: identityConfig })
|
|
761
|
+
const plans = buildPlans(kind, { targets: [toolId], scope: 'global', identity: identityConfig, agentModels: projectConfig.load().agentModels || {} })
|
|
479
762
|
if (!plans.length) return { id, state: 'missing', path: displayPath }
|
|
480
763
|
|
|
481
764
|
const manager = new IntegrationManager({ homeRoot })
|
|
@@ -506,18 +789,20 @@ function catalogBundleTarget(toolId, kind, homeRoot, identityConfig, { dryRun, i
|
|
|
506
789
|
}
|
|
507
790
|
|
|
508
791
|
// HARNESS_TARGET_IDS — mirrors internal/generators/update.go:HarnessTargetIDs.
|
|
509
|
-
// "codex-credential-guard"/"
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
|
|
792
|
+
// "codex-credential-guard"/"codex-git-branch-guard",
|
|
793
|
+
// "gemini-credential-guard"/"gemini-git-branch-guard", etc. are each
|
|
794
|
+
// inserted immediately BEFORE their tool's "-agents"/"-skills" pair (same
|
|
795
|
+
// relative position as claude-credential-guard/claude-git-branch-guard,
|
|
796
|
+
// which precede claude-agents/claude-skills), with credential-guard always
|
|
797
|
+
// preceding git-branch-guard within a tool — see buildHarnessTargetIDs's
|
|
798
|
+
// comment in update.go for the full rationale.
|
|
799
|
+
const HARNESS_TARGET_IDS = ['claude-skill', 'claude-credential-guard', 'claude-git-branch-guard']
|
|
515
800
|
for (const target of catalog.targets) {
|
|
516
|
-
if (target.id === 'codex') HARNESS_TARGET_IDS.push('codex-credential-guard')
|
|
517
|
-
if (target.id === 'gemini') HARNESS_TARGET_IDS.push('gemini-credential-guard')
|
|
518
|
-
if (target.id === 'cursor') HARNESS_TARGET_IDS.push('cursor-credential-guard')
|
|
519
|
-
if (target.id === 'copilot') HARNESS_TARGET_IDS.push('copilot-credential-guard')
|
|
520
|
-
if (target.id === 'kiro') HARNESS_TARGET_IDS.push('kiro-credential-guard')
|
|
801
|
+
if (target.id === 'codex') HARNESS_TARGET_IDS.push('codex-credential-guard', 'codex-git-branch-guard')
|
|
802
|
+
if (target.id === 'gemini') HARNESS_TARGET_IDS.push('gemini-credential-guard', 'gemini-git-branch-guard')
|
|
803
|
+
if (target.id === 'cursor') HARNESS_TARGET_IDS.push('cursor-credential-guard', 'cursor-git-branch-guard')
|
|
804
|
+
if (target.id === 'copilot') HARNESS_TARGET_IDS.push('copilot-credential-guard', 'copilot-git-branch-guard')
|
|
805
|
+
if (target.id === 'kiro') HARNESS_TARGET_IDS.push('kiro-credential-guard', 'kiro-git-branch-guard')
|
|
521
806
|
HARNESS_TARGET_IDS.push(`${target.id}-agents`, `${target.id}-skills`)
|
|
522
807
|
}
|
|
523
808
|
|
|
@@ -531,22 +816,38 @@ function buildHarnessTargets(homeRoot, identityConfig, { dryRun, installMissing
|
|
|
531
816
|
const targets = []
|
|
532
817
|
if (include('claude-skill')) targets.push(claudeSkillTarget(homeRoot, { dryRun, installMissing }))
|
|
533
818
|
if (include('claude-credential-guard')) targets.push(credentialGuardTargetClaude(homeRoot, { dryRun, installMissing }))
|
|
819
|
+
if (include('claude-git-branch-guard')) targets.push(gitBranchGuardTargetClaude(homeRoot, { dryRun, installMissing }))
|
|
534
820
|
for (const target of catalog.targets) {
|
|
535
821
|
if (target.id === 'codex' && include('codex-credential-guard')) {
|
|
536
822
|
targets.push(credentialGuardTargetCodex(homeRoot, { dryRun, installMissing }))
|
|
537
823
|
}
|
|
824
|
+
if (target.id === 'codex' && include('codex-git-branch-guard')) {
|
|
825
|
+
targets.push(gitBranchGuardTargetCodex(homeRoot, { dryRun, installMissing }))
|
|
826
|
+
}
|
|
538
827
|
if (target.id === 'gemini' && include('gemini-credential-guard')) {
|
|
539
828
|
targets.push(credentialGuardTargetGemini(homeRoot, { dryRun, installMissing }))
|
|
540
829
|
}
|
|
830
|
+
if (target.id === 'gemini' && include('gemini-git-branch-guard')) {
|
|
831
|
+
targets.push(gitBranchGuardTargetGemini(homeRoot, { dryRun, installMissing }))
|
|
832
|
+
}
|
|
541
833
|
if (target.id === 'cursor' && include('cursor-credential-guard')) {
|
|
542
834
|
targets.push(credentialGuardTargetCursor(homeRoot, { dryRun, installMissing }))
|
|
543
835
|
}
|
|
836
|
+
if (target.id === 'cursor' && include('cursor-git-branch-guard')) {
|
|
837
|
+
targets.push(gitBranchGuardTargetCursor(homeRoot, { dryRun, installMissing }))
|
|
838
|
+
}
|
|
544
839
|
if (target.id === 'copilot' && include('copilot-credential-guard')) {
|
|
545
840
|
targets.push(credentialGuardTargetCopilot(homeRoot, { dryRun, installMissing }))
|
|
546
841
|
}
|
|
842
|
+
if (target.id === 'copilot' && include('copilot-git-branch-guard')) {
|
|
843
|
+
targets.push(gitBranchGuardTargetCopilot(homeRoot, { dryRun, installMissing }))
|
|
844
|
+
}
|
|
547
845
|
if (target.id === 'kiro' && include('kiro-credential-guard')) {
|
|
548
846
|
targets.push(credentialGuardTargetKiro(homeRoot, { dryRun, installMissing }))
|
|
549
847
|
}
|
|
848
|
+
if (target.id === 'kiro' && include('kiro-git-branch-guard')) {
|
|
849
|
+
targets.push(gitBranchGuardTargetKiro(homeRoot, { dryRun, installMissing }))
|
|
850
|
+
}
|
|
550
851
|
const agentsId = `${target.id}-agents`
|
|
551
852
|
const skillsId = `${target.id}-skills`
|
|
552
853
|
if (include(agentsId)) targets.push(catalogBundleTarget(target.id, 'agents', homeRoot, identityConfig, { dryRun, installMissing }))
|
|
@@ -627,4 +928,10 @@ module.exports = {
|
|
|
627
928
|
credentialGuardTargetCursor,
|
|
628
929
|
credentialGuardTargetCopilot,
|
|
629
930
|
credentialGuardTargetKiro,
|
|
931
|
+
gitBranchGuardTargetClaude,
|
|
932
|
+
gitBranchGuardTargetCodex,
|
|
933
|
+
gitBranchGuardTargetGemini,
|
|
934
|
+
gitBranchGuardTargetCursor,
|
|
935
|
+
gitBranchGuardTargetCopilot,
|
|
936
|
+
gitBranchGuardTargetKiro,
|
|
630
937
|
}
|
package/src/commands/update.js
CHANGED
|
@@ -151,7 +151,7 @@ function codexProjectAgentsTarget(cwd, identityConfig, { dryRun, installMissing
|
|
|
151
151
|
const manager = new IntegrationManager({ projectRoot: cwd });
|
|
152
152
|
let wroteAny = false;
|
|
153
153
|
for (const kind of ['agents', 'skills']) {
|
|
154
|
-
const plans = buildPlans(kind, { targets: ['codex'], scope: 'project', identity: identityConfig });
|
|
154
|
+
const plans = buildPlans(kind, { targets: ['codex'], scope: 'project', identity: identityConfig, agentModels: projectConfig.load(cwd).agentModels || {} });
|
|
155
155
|
const statuses = manager.inspect(plans);
|
|
156
156
|
const toWrite = plans.filter((_, index) => {
|
|
157
157
|
const state = statuses[index].state;
|
package/src/config/index.js
CHANGED
|
@@ -65,6 +65,10 @@ function defaults() {
|
|
|
65
65
|
credentialGuard: {
|
|
66
66
|
mode: 'warn',
|
|
67
67
|
},
|
|
68
|
+
// agent_models field — see ADR-2026-08-21-versao-do-modelo-por-tier-com-composicao-por-alvo.md.
|
|
69
|
+
// Maps tier name (e.g. "opus", "sonnet") to version string (e.g. "5", "4.6").
|
|
70
|
+
// Absent or empty map → behavior identical to today (tier alias used verbatim).
|
|
71
|
+
agentModels: {},
|
|
68
72
|
rules: {
|
|
69
73
|
wip_has_req: 'error',
|
|
70
74
|
wip_acceptance: 'error',
|
|
@@ -278,6 +282,17 @@ function parse(content, cfg) {
|
|
|
278
282
|
if (mode === 'warn' || mode === 'block') cfg.credentialGuard.mode = mode;
|
|
279
283
|
}
|
|
280
284
|
|
|
285
|
+
// agent_models field — flat mapping from tier name to version string.
|
|
286
|
+
// See ADR-2026-08-21-versao-do-modelo-por-tier-com-composicao-por-alvo.md.
|
|
287
|
+
// An absent, malformed, or empty block leaves agentModels as the empty object from defaults(),
|
|
288
|
+
// preserving identical behavior to today. A key with an empty string value is stored as-is
|
|
289
|
+
// (the render layer treats empty string as "no pin" — fall back to tier alias).
|
|
290
|
+
if (m.agent_models !== undefined && typeof m.agent_models === 'object' && !Array.isArray(m.agent_models)) {
|
|
291
|
+
for (const [k, v] of Object.entries(m.agent_models)) {
|
|
292
|
+
if (typeof v === 'string') cfg.agentModels[k] = v;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
281
296
|
return false;
|
|
282
297
|
}
|
|
283
298
|
|
|
@@ -298,7 +313,7 @@ function parseRulesFromContent(content) {
|
|
|
298
313
|
// never creates them) — so this cannot be a bare { rules: {} } literal or parse() throws on any
|
|
299
314
|
// content that sets one of those nested keys. rules starts empty (not seeded from defaults()),
|
|
300
315
|
// deliberately: the caller wants exactly what `rules:` in content declares, nothing else.
|
|
301
|
-
const cfg = { rules: {}, credentialGuard: {}, update: {}, sync: {}, linkFields: {} };
|
|
316
|
+
const cfg = { rules: {}, credentialGuard: {}, update: {}, sync: {}, linkFields: {}, agentModels: {} };
|
|
302
317
|
parse(content, cfg);
|
|
303
318
|
return cfg.rules;
|
|
304
319
|
}
|
|
@@ -316,7 +331,7 @@ function readAgentConventions(cwd) {
|
|
|
316
331
|
} catch (_) {
|
|
317
332
|
return '';
|
|
318
333
|
}
|
|
319
|
-
const cfg = { rules: {}, credentialGuard: {}, update: { agentConventions: '' }, sync: {}, linkFields: {} };
|
|
334
|
+
const cfg = { rules: {}, credentialGuard: {}, update: { agentConventions: '' }, sync: {}, linkFields: {}, agentModels: {} };
|
|
320
335
|
try {
|
|
321
336
|
parse(content, cfg);
|
|
322
337
|
} catch (_) {
|