nccgs 1.1.1 → 1.3.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.
Files changed (212) hide show
  1. package/.claude/agents/nccgs-adversarial-reviewer.md +7 -1
  2. package/.claude/agents/nccgs-engine-programmer.md +2 -1
  3. package/.claude/agents/nccgs-gameplay-programmer.md +2 -1
  4. package/.claude/agents/nccgs-qa-engineer.md +2 -1
  5. package/.claude/agents/nccgs-ui-programmer.md +2 -1
  6. package/.claude/agents/nccgs-unity-build-specialist.md +2 -1
  7. package/.claude/agents/nccgs-unity-implementer.md +2 -1
  8. package/.claude/agents/nccgs-unity-systems-specialist.md +2 -1
  9. package/.claude/agents/nccgs-unity-ui-specialist.md +2 -1
  10. package/.claude/agents/nccgs-verification-engineer.md +2 -1
  11. package/.claude/nccgs/VERSION +1 -1
  12. package/.claude/{agents → nccgs/agent-library}/nccgs-accessibility-specialist.md +0 -1
  13. package/.claude/nccgs/agent-library/nccgs-adversarial-reviewer.md +30 -0
  14. package/.claude/{agents → nccgs/agent-library}/nccgs-ai-programmer.md +0 -1
  15. package/.claude/{agents → nccgs/agent-library}/nccgs-analytics-engineer.md +0 -1
  16. package/.claude/{agents → nccgs/agent-library}/nccgs-art-direction-lead.md +0 -1
  17. package/.claude/{agents → nccgs/agent-library}/nccgs-audio-direction-lead.md +0 -1
  18. package/.claude/{agents → nccgs/agent-library}/nccgs-creative-director.md +0 -1
  19. package/.claude/{agents → nccgs/agent-library}/nccgs-documentation-manager.md +0 -1
  20. package/.claude/{agents → nccgs/agent-library}/nccgs-economy-designer.md +0 -1
  21. package/.claude/nccgs/agent-library/nccgs-engine-programmer.md +25 -0
  22. package/.claude/{agents → nccgs/agent-library}/nccgs-game-design-lead.md +0 -1
  23. package/.claude/{agents → nccgs/agent-library}/nccgs-game-designer.md +0 -1
  24. package/.claude/nccgs/agent-library/nccgs-gameplay-programmer.md +25 -0
  25. package/.claude/{agents → nccgs/agent-library}/nccgs-level-designer.md +0 -1
  26. package/.claude/{agents → nccgs/agent-library}/nccgs-live-ops-designer.md +0 -1
  27. package/.claude/{agents → nccgs/agent-library}/nccgs-localization-lead.md +0 -1
  28. package/.claude/{agents → nccgs/agent-library}/nccgs-narrative-lead.md +0 -1
  29. package/.claude/{agents → nccgs/agent-library}/nccgs-network-programmer.md +0 -1
  30. package/.claude/{agents → nccgs/agent-library}/nccgs-performance-analyst.md +0 -1
  31. package/.claude/{agents → nccgs/agent-library}/nccgs-producer.md +0 -1
  32. package/.claude/{agents → nccgs/agent-library}/nccgs-production-coordinator.md +0 -1
  33. package/.claude/{agents → nccgs/agent-library}/nccgs-programming-lead.md +0 -1
  34. package/.claude/{agents → nccgs/agent-library}/nccgs-prototyper.md +0 -1
  35. package/.claude/nccgs/agent-library/nccgs-qa-engineer.md +25 -0
  36. package/.claude/{agents → nccgs/agent-library}/nccgs-qa-lead.md +0 -1
  37. package/.claude/{agents → nccgs/agent-library}/nccgs-release-engineer.md +0 -1
  38. package/.claude/{agents → nccgs/agent-library}/nccgs-release-lead.md +0 -1
  39. package/.claude/{agents → nccgs/agent-library}/nccgs-security-engineer.md +0 -1
  40. package/.claude/{agents → nccgs/agent-library}/nccgs-sound-designer.md +0 -1
  41. package/.claude/{agents → nccgs/agent-library}/nccgs-systems-designer.md +0 -1
  42. package/.claude/{agents → nccgs/agent-library}/nccgs-technical-architect.md +0 -1
  43. package/.claude/{agents → nccgs/agent-library}/nccgs-technical-artist.md +0 -1
  44. package/.claude/{agents → nccgs/agent-library}/nccgs-technical-director.md +0 -1
  45. package/.claude/{agents → nccgs/agent-library}/nccgs-tools-programmer.md +0 -1
  46. package/.claude/nccgs/agent-library/nccgs-ui-programmer.md +25 -0
  47. package/.claude/nccgs/agent-library/nccgs-unity-build-specialist.md +25 -0
  48. package/.claude/{agents → nccgs/agent-library}/nccgs-unity-content-specialist.md +0 -1
  49. package/.claude/nccgs/agent-library/nccgs-unity-implementer.md +25 -0
  50. package/.claude/{agents → nccgs/agent-library}/nccgs-unity-rendering-specialist.md +0 -1
  51. package/.claude/nccgs/agent-library/nccgs-unity-systems-specialist.md +25 -0
  52. package/.claude/nccgs/agent-library/nccgs-unity-ui-specialist.md +25 -0
  53. package/.claude/{agents → nccgs/agent-library}/nccgs-ux-designer.md +0 -1
  54. package/.claude/nccgs/agent-library/nccgs-verification-engineer.md +25 -0
  55. package/.claude/{agents → nccgs/agent-library}/nccgs-world-builder.md +0 -1
  56. package/.claude/{agents → nccgs/agent-library}/nccgs-writer.md +0 -1
  57. package/.claude/nccgs/constitution.md +13 -0
  58. package/.claude/nccgs/hooks/agent-audit.mjs +6 -0
  59. package/.claude/nccgs/hooks/closure-guard.mjs +34 -0
  60. package/.claude/nccgs/hooks/runtime-observation.mjs +11 -0
  61. package/.claude/nccgs/hooks/session-start.mjs +8 -1
  62. package/.claude/nccgs/pipeline.json +176 -0
  63. package/.claude/nccgs/protocols/agent-contract.md +5 -0
  64. package/.claude/nccgs/protocols/astra-claude.md +77 -0
  65. package/.claude/nccgs/protocols/evidence.md +6 -0
  66. package/.claude/nccgs/protocols/model-routing.md +27 -5
  67. package/.claude/nccgs/protocols/orchestration.md +5 -0
  68. package/.claude/nccgs/protocols/task-gates.md +66 -0
  69. package/.claude/nccgs/routing.generated.md +11 -0
  70. package/.claude/nccgs/settings.fragment.json +73 -9
  71. package/.claude/nccgs/studio.json +187 -96
  72. package/.claude/nccgs/tools/compile-policy.mjs +83 -0
  73. package/.claude/nccgs/tools/configure-models.mjs +75 -35
  74. package/.claude/nccgs/tools/gates.mjs +167 -0
  75. package/.claude/nccgs/tools/policy.mjs +62 -0
  76. package/.claude/nccgs/tools/runtime.mjs +40 -0
  77. package/.claude/nccgs/tools/task.mjs +77 -0
  78. package/.claude/nccgs/workflow-catalog.json +90 -13
  79. package/.claude/skills/accessibility-review/SKILL.md +6 -5
  80. package/.claude/skills/accessibility-review/references/astra.md +17 -0
  81. package/.claude/skills/accessibility-review/references/legacy.md +7 -0
  82. package/.claude/skills/architecture-decision/SKILL.md +6 -5
  83. package/.claude/skills/architecture-decision/references/astra.md +12 -0
  84. package/.claude/skills/architecture-decision/references/legacy.md +7 -0
  85. package/.claude/skills/asset-audit/SKILL.md +6 -5
  86. package/.claude/skills/asset-audit/references/astra.md +17 -0
  87. package/.claude/skills/asset-audit/references/legacy.md +7 -0
  88. package/.claude/skills/audit/SKILL.md +6 -7
  89. package/.claude/skills/audit/references/astra.md +17 -0
  90. package/.claude/skills/audit/references/legacy.md +9 -0
  91. package/.claude/skills/balance-review/SKILL.md +6 -5
  92. package/.claude/skills/balance-review/references/astra.md +17 -0
  93. package/.claude/skills/balance-review/references/legacy.md +7 -0
  94. package/.claude/skills/bug-triage/SKILL.md +6 -5
  95. package/.claude/skills/bug-triage/references/astra.md +20 -0
  96. package/.claude/skills/bug-triage/references/legacy.md +7 -0
  97. package/.claude/skills/closure/SKILL.md +5 -4
  98. package/.claude/skills/closure/references/astra.md +15 -0
  99. package/.claude/skills/closure/references/legacy.md +7 -0
  100. package/.claude/skills/compatibility-review/SKILL.md +6 -5
  101. package/.claude/skills/compatibility-review/references/astra.md +17 -0
  102. package/.claude/skills/compatibility-review/references/legacy.md +7 -0
  103. package/.claude/skills/context-pack/SKILL.md +5 -4
  104. package/.claude/skills/context-pack/references/astra.md +12 -0
  105. package/.claude/skills/context-pack/references/legacy.md +7 -0
  106. package/.claude/skills/dependency-review/SKILL.md +6 -5
  107. package/.claude/skills/dependency-review/references/astra.md +17 -0
  108. package/.claude/skills/dependency-review/references/legacy.md +7 -0
  109. package/.claude/skills/design/SKILL.md +6 -11
  110. package/.claude/skills/design/references/astra.md +12 -0
  111. package/.claude/skills/design/references/legacy.md +13 -0
  112. package/.claude/skills/design-review/SKILL.md +6 -5
  113. package/.claude/skills/design-review/references/astra.md +17 -0
  114. package/.claude/skills/design-review/references/legacy.md +7 -0
  115. package/.claude/skills/evidence-review/SKILL.md +6 -5
  116. package/.claude/skills/evidence-review/references/astra.md +17 -0
  117. package/.claude/skills/evidence-review/references/legacy.md +7 -0
  118. package/.claude/skills/hotfix/SKILL.md +6 -5
  119. package/.claude/skills/hotfix/references/astra.md +23 -0
  120. package/.claude/skills/hotfix/references/legacy.md +7 -0
  121. package/.claude/skills/incident-recovery/SKILL.md +6 -5
  122. package/.claude/skills/incident-recovery/references/astra.md +20 -0
  123. package/.claude/skills/incident-recovery/references/legacy.md +7 -0
  124. package/.claude/skills/localize-game/SKILL.md +6 -5
  125. package/.claude/skills/localize-game/references/astra.md +20 -0
  126. package/.claude/skills/localize-game/references/legacy.md +7 -0
  127. package/.claude/skills/migrate-project/SKILL.md +6 -16
  128. package/.claude/skills/migrate-project/references/astra.md +23 -0
  129. package/.claude/skills/migrate-project/references/legacy.md +19 -0
  130. package/.claude/skills/migrate-project/references/procedure.md +8 -0
  131. package/.claude/skills/milestone-review/SKILL.md +6 -5
  132. package/.claude/skills/milestone-review/references/astra.md +12 -0
  133. package/.claude/skills/milestone-review/references/legacy.md +7 -0
  134. package/.claude/skills/nccgs-code-review/SKILL.md +6 -4
  135. package/.claude/skills/nccgs-code-review/references/astra.md +15 -0
  136. package/.claude/skills/nccgs-code-review/references/legacy.md +7 -0
  137. package/.claude/skills/performance-audit/SKILL.md +6 -5
  138. package/.claude/skills/performance-audit/references/astra.md +17 -0
  139. package/.claude/skills/performance-audit/references/legacy.md +7 -0
  140. package/.claude/skills/plan-feature/SKILL.md +6 -5
  141. package/.claude/skills/plan-feature/references/astra.md +12 -0
  142. package/.claude/skills/plan-feature/references/legacy.md +7 -0
  143. package/.claude/skills/playtest/SKILL.md +5 -4
  144. package/.claude/skills/playtest/references/astra.md +20 -0
  145. package/.claude/skills/playtest/references/legacy.md +7 -0
  146. package/.claude/skills/project-stage/SKILL.md +5 -4
  147. package/.claude/skills/project-stage/references/astra.md +12 -0
  148. package/.claude/skills/project-stage/references/legacy.md +7 -0
  149. package/.claude/skills/prototype-feature/SKILL.md +6 -5
  150. package/.claude/skills/prototype-feature/references/astra.md +20 -0
  151. package/.claude/skills/prototype-feature/references/legacy.md +7 -0
  152. package/.claude/skills/qa-plan/SKILL.md +6 -5
  153. package/.claude/skills/qa-plan/references/astra.md +12 -0
  154. package/.claude/skills/qa-plan/references/legacy.md +7 -0
  155. package/.claude/skills/release/SKILL.md +6 -9
  156. package/.claude/skills/release/references/astra.md +23 -0
  157. package/.claude/skills/release/references/legacy.md +11 -0
  158. package/.claude/skills/release-readiness/SKILL.md +6 -5
  159. package/.claude/skills/release-readiness/references/astra.md +17 -0
  160. package/.claude/skills/release-readiness/references/legacy.md +7 -0
  161. package/.claude/skills/retrospective/SKILL.md +6 -5
  162. package/.claude/skills/retrospective/references/astra.md +12 -0
  163. package/.claude/skills/retrospective/references/legacy.md +7 -0
  164. package/.claude/skills/review/SKILL.md +6 -7
  165. package/.claude/skills/review/references/astra.md +15 -0
  166. package/.claude/skills/review/references/legacy.md +9 -0
  167. package/.claude/skills/security-audit/SKILL.md +6 -5
  168. package/.claude/skills/security-audit/references/astra.md +17 -0
  169. package/.claude/skills/security-audit/references/legacy.md +7 -0
  170. package/.claude/skills/sprint-plan/SKILL.md +6 -5
  171. package/.claude/skills/sprint-plan/references/astra.md +12 -0
  172. package/.claude/skills/sprint-plan/references/legacy.md +7 -0
  173. package/.claude/skills/status/SKILL.md +5 -6
  174. package/.claude/skills/status/references/astra.md +13 -0
  175. package/.claude/skills/status/references/legacy.md +9 -0
  176. package/.claude/skills/story-readiness/SKILL.md +5 -4
  177. package/.claude/skills/story-readiness/references/astra.md +17 -0
  178. package/.claude/skills/story-readiness/references/legacy.md +7 -0
  179. package/.claude/skills/test/SKILL.md +6 -7
  180. package/.claude/skills/test/references/astra.md +20 -0
  181. package/.claude/skills/test/references/legacy.md +9 -0
  182. package/.claude/skills/ui-review/SKILL.md +6 -5
  183. package/.claude/skills/ui-review/references/astra.md +17 -0
  184. package/.claude/skills/ui-review/references/legacy.md +7 -0
  185. package/.claude/skills/work/SKILL.md +6 -27
  186. package/.claude/skills/work/references/astra.md +20 -0
  187. package/.claude/skills/work/references/closure.md +10 -0
  188. package/.claude/skills/work/references/feature-contracts.md +4 -0
  189. package/.claude/skills/work/references/legacy.md +29 -0
  190. package/README.md +59 -25
  191. package/UPGRADING.md +37 -0
  192. package/VERSION +1 -1
  193. package/docs/ARCHITECTURE.md +30 -13
  194. package/docs/HUONG-DAN-MIGRATE-VA-SU-DUNG.md +34 -329
  195. package/docs/MIGRATION-MATRIX.md +1 -1
  196. package/docs/NCCGS-1.3.md +125 -0
  197. package/docs/PROJECT-POLICY.md +18 -3
  198. package/docs/WORKFLOWS.md +19 -13
  199. package/package.json +5 -3
  200. package/scaffold/.nccgs/gates.json +7 -0
  201. package/scaffold/.nccgs/project.yaml +6 -3
  202. package/scaffold/.nccgs/state.md +1 -1
  203. package/scaffold/.nccgs/templates/agent-handoff.md +10 -0
  204. package/scaffold/.nccgs/templates/closure-record.md +11 -0
  205. package/scaffold/.nccgs/templates/feature-contract.md +9 -0
  206. package/scaffold/.nccgs/templates/implementation-report.md +49 -0
  207. package/scripts/cli.mjs +9 -4
  208. package/scripts/doctor.mjs +27 -1
  209. package/scripts/install.mjs +4 -3
  210. package/scripts/validate.mjs +20 -7
  211. package/tests/framework.test.mjs +143 -2
  212. package/tests/gates.test.mjs +170 -0
@@ -14,7 +14,7 @@ Updated: [timestamp]
14
14
 
15
15
  - Last completed checkpoint: NCCGS initialized
16
16
  - Current step: configure or migrate project policy
17
- - Next safe action: run `/migrate-project review` for an existing project
17
+ - Next safe action: Astra prepares project policy, gate mapping and migration plan; Claude then runs `/migrate-project`
18
18
 
19
19
  ## Decisions pending
20
20
 
@@ -23,3 +23,13 @@
23
23
  ## Next owner
24
24
 
25
25
  [role or workflow]
26
+
27
+ ## Model and review trace (astra-claude profile)
28
+
29
+ - Requested model/effort:
30
+ - Observed model/effort and session/agent reference:
31
+ - Plan and working snapshot:
32
+ - Fable review record and verdict:
33
+ - Report path for Astra (after Fable PASS only):
34
+ - Astra check: PENDING unless referenced
35
+ - Product Owner acceptance: PENDING unless explicitly referenced
@@ -49,3 +49,14 @@
49
49
  ## Known issues and residual risk
50
50
 
51
51
  - None
52
+
53
+ ## Astra/Claude chain (required for astra-claude)
54
+
55
+ - **Final snapshot**: [commit plus dirty/untracked diff or file hashes]
56
+ - **Fable review**: PENDING/PASS/CHANGES_REQUIRED/BLOCKED [record and snapshot]
57
+ - **Report to Astra**: [path; issued only after Fable PASS]
58
+ - **Astra check**: PENDING/PASS/CHANGES_REQUIRED [Astra reference and snapshot]
59
+ - **Product Owner acceptance**: PENDING/ACCEPTED/REJECTED [user reference and snapshot]
60
+ - **Current phase**: IMPLEMENTING/FABLE_REVIEW/AWAITING_ASTRA_CHECK/AWAITING_PO_ACCEPTANCE/CLOSED
61
+
62
+ All sign-offs must cover the same final snapshot. Pending or stale gates prevent DONE.
@@ -33,3 +33,12 @@
33
33
  ## Open PO decisions
34
34
 
35
35
  - None
36
+
37
+ ## Astra/Claude ownership (astra-claude profile)
38
+
39
+ - Plan and canonical documents: Astra [plan revision/reference]
40
+ - Implementation and remediation: Claude Opus 5.5 / xhigh
41
+ - Independent review: Fable / high
42
+ - Report checking and document synchronization: Astra
43
+ - Final acceptance: Product Owner [explicit decision required]
44
+ - Review/report snapshot identification: [commit plus dirty/untracked diff or hashes]
@@ -0,0 +1,49 @@
1
+ # Implementation report for Astra: [feature ID]
2
+
3
+ - **Phase**: AWAITING_ASTRA_CHECK
4
+ - **Task ID and record**: [ID; .nccgs/tasks/ID.json]
5
+ - **Report gate**: [task check --id ID --target report result]
6
+ - **Technical state**: IMPLEMENTED | VERIFIED | BLOCKED
7
+ - **Astra plan and revision**: [path/reference]
8
+ - **Implemented snapshot**: [SHA-256 from task snapshot]
9
+ - **Implementation model / effort (requested)**: [from pipeline.json]
10
+ - **Implementation model / effort (observed)**: [runtime evidence; unknown is not proof]
11
+ - **Implementation session / agent**: [ID/reference]
12
+
13
+ ## Plan compliance and changes
14
+
15
+ | Requirement | Changed files/behavior | Evidence and result | Deviations |
16
+ |---|---|---|---|
17
+ | [REQ-ID] | [change] | [direct reference] | [none or explanation] |
18
+
19
+ ## Verification
20
+
21
+ - Commands/scenarios, environment, counts, failures, skips, runtime/build results:
22
+ - Relevant regression and unverified behavior:
23
+
24
+ ## Independent Fable review
25
+
26
+ - Requested model / effort: [review route from pipeline.json]
27
+ - Observed model / effort and session / agent: [runtime evidence]
28
+ - Reviewed snapshot: [must match implemented snapshot]
29
+ - Verdict: PASS | CHANGES_REQUIRED | BLOCKED
30
+ - Review record: [path or verbatim independent findings]
31
+ - Findings, Opus fixes, repeated checks, and Fable re-review references:
32
+ - Remaining non-blocking findings and residual risk:
33
+
34
+ ## Documents for Astra
35
+
36
+ - Proposed canonical document updates with exact sections and reasons:
37
+ - Changed scope or decisions requiring Astra/Product Owner resolution:
38
+
39
+ ## Downstream decisions — stored separately
40
+
41
+ Do not amend this report after recording its hash. Astra check and Product Owner
42
+ acceptance are separate JSON evidence files linked by the task record. Both are
43
+ pending when this report is handed over; never author their decisions for them.
44
+
45
+ - Reproduction/acceptance steps:
46
+
47
+ ## Next owner
48
+
49
+ Astra. Do not mark DONE while either downstream sign-off is pending.
package/scripts/cli.mjs CHANGED
@@ -19,14 +19,16 @@ if (command === "version" || command === "--version" || command === "-v") {
19
19
  process.exit(0);
20
20
  }
21
21
 
22
- if (!new Set(["install", "update", "doctor"]).has(command)) {
22
+ if (!new Set(["install", "update", "doctor", "task", "agents", "policy"]).has(command)) {
23
23
  console.error(`Unknown command: ${command}`);
24
24
  printHelp();
25
25
  process.exit(2);
26
26
  }
27
27
 
28
- const script = command === "doctor" ? "doctor.mjs" : "install.mjs";
29
- const forwardedArgs = command === "update" ? ["--update-only", ...args] : args;
28
+ const scripts = { doctor: "doctor.mjs", task: "../.claude/nccgs/tools/task.mjs", agents: "../.claude/nccgs/tools/configure-models.mjs", policy: "../.claude/nccgs/tools/compile-policy.mjs" };
29
+ const script = scripts[command] || "install.mjs";
30
+ let forwardedArgs = command === "update" ? ["--update-only", ...args] : args;
31
+ if (command === "agents" && !args.includes("--project")) forwardedArgs = ["--project", process.cwd(), ...args];
30
32
  const result = spawnSync(process.execPath, [path.join(scriptDir, script), ...forwardedArgs], {
31
33
  cwd: process.cwd(),
32
34
  stdio: "inherit"
@@ -45,6 +47,9 @@ Usage:
45
47
  nccgs install [options]
46
48
  nccgs update [options]
47
49
  nccgs doctor [options]
50
+ nccgs task init|snapshot|ref|observe|verify|record|check|close|bind [options]
51
+ nccgs agents --enable <role> | --disable <role> [--project <directory>]
52
+ nccgs policy [--write] Check/regenerate package routing
48
53
  nccgs --version
49
54
 
50
55
  Install target:
@@ -54,7 +59,7 @@ Install target:
54
59
  Options:
55
60
  --dry-run Preview without writing files
56
61
  --force Back up and discard detected project overrides
57
- --model-profile <profile> quality, balanced, or inherit
62
+ --model-profile <profile> astra-claude (default), quality, balanced, inherit
58
63
  --project <directory> Optional explicit target directory
59
64
  --allow-non-unity Allow a target without Unity markers`);
60
65
  }
@@ -3,6 +3,8 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { matchesFingerprint, sameFingerprint } from "../.claude/nccgs/tools/integrity.mjs";
6
+ import { check as checkTask, snapshot, stages } from "../.claude/nccgs/tools/gates.mjs";
7
+ import { contained, hash, json } from "../.claude/nccgs/tools/policy.mjs";
6
8
 
7
9
  const args = process.argv.slice(2);
8
10
  const projectIndex = args.indexOf("--project");
@@ -75,11 +77,35 @@ const policyPath = path.join(project, ".nccgs", "project.yaml");
75
77
  let policySchema = null;
76
78
  if (!fs.existsSync(policyPath)) errors.push(".nccgs/project.yaml is missing.");
77
79
  else {
78
- const match = fs.readFileSync(policyPath, "utf8").match(/^schema_version:\s*(\d+)\s*$/m);
80
+ const policyText = fs.readFileSync(policyPath, "utf8");
81
+ const match = policyText.match(/^schema_version:\s*(\d+)\s*$/m);
79
82
  policySchema = match ? Number(match[1]) : null;
80
83
  if (policySchema !== 2) migrationIssue(`Project policy schema must be 2; found ${policySchema ?? "unknown"}.`);
84
+ if (/^profile_contracts:/m.test(policyText)) migrationIssue("Legacy embedded profile_contracts require Astra migration to pipeline.json plus project gate mapping.");
81
85
  }
82
86
 
87
+ const activationPath = path.join(project, ".nccgs/agent-activation.json");
88
+ if (fs.existsSync(activationPath)) {
89
+ try {
90
+ const activation = json(activationPath);
91
+ for (const [relative, expected] of Object.entries(activation.generated)) {
92
+ const bytes = fs.readFileSync(contained(project, relative));
93
+ if (hash(bytes) !== expected && hash(bytes.toString("utf8").replace(/\r\n/g, "\n")) !== activation.generatedCanonical?.[relative]) errors.push(`Activated agent modified: ${relative}`);
94
+ }
95
+ for (const warning of activation.warnings ?? []) migrationIssue(warning);
96
+ } catch (error) { errors.push(`Invalid agent activation state: ${error.message}`); }
97
+ }
98
+ const tasksDir = path.join(project, ".nccgs/tasks");
99
+ if (fs.existsSync(tasksDir)) for (const file of fs.readdirSync(tasksDir).filter(f => f.endsWith(".json"))) {
100
+ try {
101
+ const task = json(path.join(tasksDir, file));
102
+ if (task.closed) {
103
+ if (!stages.every(s => task.stages?.[s]) || task.closed.snapshot !== task.stages.acceptance.snapshot) throw new Error("Closure lacks its required stage records");
104
+ if (task.closed.snapshot === snapshot(project).sha256) checkTask(project, task, "done");
105
+ else warnings.push(`Historical closure ${task.id} is for a different snapshot; it cannot approve current work.`);
106
+ }
107
+ } catch (error) { errors.push(`Invalid closed task ${file}: ${error.message}`); }
108
+ }
83
109
  if (errors.length) {
84
110
  for (const error of errors) console.error(`ERROR: ${error}`);
85
111
  for (const warning of warnings) console.warn(`WARN: ${warning}`);
@@ -164,8 +164,9 @@ const settingsPlan = planManagedSettings(project);
164
164
  const targetPolicyPath = path.join(project, ".nccgs", "project.yaml");
165
165
  const targetPolicy = fs.existsSync(targetPolicyPath) ? fs.readFileSync(targetPolicyPath, "utf8") : "";
166
166
  const policyModelProfile = targetPolicy.match(/^models:\s*$[\s\S]*?^\s+profile:\s*([^#\r\n]+)/m)?.[1]?.trim();
167
- const modelProfile = requestedModelProfile || policyModelProfile || "quality";
168
- if (!new Set(["quality", "balanced", "inherit"]).has(modelProfile)) {
167
+ const studio = JSON.parse(fs.readFileSync(path.join(root, ".claude/nccgs/studio.json"), "utf8"));
168
+ const modelProfile = requestedModelProfile || policyModelProfile || studio.defaultProfile;
169
+ if (!Object.hasOwn(studio.modelProfiles, modelProfile)) {
169
170
  console.error(`Unknown model profile: ${modelProfile}`);
170
171
  process.exit(2);
171
172
  }
@@ -248,7 +249,7 @@ if (fs.existsSync(backupRoot)) {
248
249
  }
249
250
  const installedPolicy = fs.readFileSync(targetPolicyPath, "utf8");
250
251
  const installedPolicySchema = Number(installedPolicy.match(/^schema_version:\s*(\d+)\s*$/m)?.[1] ?? 0);
251
- if (installedPolicySchema !== 2 || pendingOverrides.length) {
252
+ if (installedPolicySchema !== 2 || pendingOverrides.length || /^profile_contracts:/m.test(installedPolicy)) {
252
253
  console.log("Project migration required: open a new Claude Code session and run `/migrate-project` once.");
253
254
  } else if (!previousInstallState) {
254
255
  console.log("First NCCGS adoption: run `/migrate-project` once for an existing project; a new project can start with `/status`.");
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { compile } from "../.claude/nccgs/tools/compile-policy.mjs";
6
7
 
7
8
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
9
  const errors = [];
@@ -44,9 +45,13 @@ for (const relative of [
44
45
  "VERSION", "README.md", "CLAUDE.md", "LICENSE", "package.json",
45
46
  ".claude/nccgs/VERSION", ".claude/nccgs/constitution.md",
46
47
  ".claude/nccgs/studio.json", ".claude/nccgs/workflow-catalog.json",
48
+ ".claude/nccgs/pipeline.json", ".claude/nccgs/protocols/task-gates.md",
49
+ ".claude/nccgs/tools/task.mjs", ".claude/nccgs/tools/gates.mjs", ".claude/nccgs/tools/runtime.mjs",
50
+ "scaffold/.nccgs/gates.json", "tests/gates.test.mjs",
47
51
  ".claude/nccgs/settings.fragment.json", ".claude/nccgs/unity-skills-manifest.json",
48
52
  ".claude/nccgs/protocols/agent-contract.md", ".claude/nccgs/protocols/orchestration.md",
49
53
  ".claude/nccgs/protocols/model-routing.md", ".claude/nccgs/protocols/context-packets.md",
54
+ ".claude/nccgs/protocols/astra-claude.md", "scaffold/.nccgs/templates/implementation-report.md",
50
55
  ".claude/nccgs/protocols/evidence.md", ".claude/nccgs/protocols/unity-boundary.md",
51
56
  "scaffold/.nccgs/project.yaml", "scaffold/.nccgs/state.md",
52
57
  "scaffold/.nccgs/requirements.yaml", "scaffold/.nccgs/templates/context-packet.yaml",
@@ -78,17 +83,22 @@ const rootClaude = fs.readFileSync(path.join(root, "CLAUDE.md"), "utf8");
78
83
  if (!rootClaude.includes("@.claude/nccgs/constitution.md")) errors.push("CLAUDE.md does not import the NCCGS constitution.");
79
84
 
80
85
  const studio = JSON.parse(fs.readFileSync(path.join(root, ".claude/nccgs/studio.json"), "utf8"));
86
+ const pipeline = JSON.parse(fs.readFileSync(path.join(root, ".claude/nccgs/pipeline.json"), "utf8"));
87
+ for (const stale of compile(root)) errors.push(`Stale generated policy: ${stale}; run npm run policy:build`);
81
88
  const agentNames = new Set();
82
89
  for (const agent of studio.agents ?? []) {
83
90
  if (agentNames.has(agent.name)) errors.push(`Duplicate catalog agent: ${agent.name}`);
84
91
  agentNames.add(agent.name);
85
- const file = path.join(root, ".claude", "agents", `${agent.name}.md`);
92
+ const active = pipeline.profiles[pipeline.defaultProfile].activeAgents.includes(agent.name);
93
+ const file = path.join(root, active ? ".claude/agents" : ".claude/nccgs/agent-library", `${agent.name}.md`);
86
94
  if (!fs.existsSync(file)) { errors.push(`Catalog agent file missing: ${agent.name}`); continue; }
87
95
  const metadata = frontmatter(file);
88
96
  if (metadata?.name !== agent.name || !metadata?.description) errors.push(`Invalid agent frontmatter: ${agent.name}`);
89
97
  const expected = studio.modelProfiles[studio.defaultProfile]?.[agent.modelClass];
90
- if (metadata?.model !== expected) errors.push(`Agent model mismatch: ${agent.name} expected ${expected}, found ${metadata?.model}`);
91
- if ((metadata?.tools ?? "").split(/,\s*/).includes("Task")) errors.push(`Agent may recursively delegate: ${agent.name}`);
98
+ if (active && metadata?.model !== expected) errors.push(`Agent model mismatch: ${agent.name} expected ${expected}, found ${metadata?.model}`);
99
+ const expectedEffort = studio.effortProfiles?.[studio.defaultProfile]?.[agent.modelClass];
100
+ if (active && metadata?.effort !== expectedEffort) errors.push(`Agent effort mismatch: ${agent.name} expected ${expectedEffort}, found ${metadata?.effort}`);
101
+ if ((metadata?.tools ?? "").split(/,\s*/).some(tool => ["Task", "Agent"].includes(tool))) errors.push(`Agent may recursively delegate: ${agent.name}`);
92
102
  if (!fs.readFileSync(file, "utf8").includes("protocols/agent-contract.md")) errors.push(`Agent does not load common contract: ${agent.name}`);
93
103
  if (fs.readFileSync(file, "utf8").split(/\r?\n/).length > 80) errors.push(`Agent entrypoint exceeds 80 lines: ${agent.name}`);
94
104
  }
@@ -96,6 +106,7 @@ for (const agent of studio.agents ?? []) {
96
106
  const actualAgents = walk(path.join(root, ".claude/agents")).filter((file) => file.endsWith(".md"));
97
107
  for (const file of actualAgents) {
98
108
  if (!agentNames.has(path.basename(file, ".md"))) errors.push(`Uncataloged NCCGS agent: ${posix(path.relative(root, file))}`);
109
+ if (!pipeline.coreAgents.includes(path.basename(file, ".md"))) errors.push(`Non-core agent active in source package: ${posix(path.relative(root, file))}`);
99
110
  }
100
111
 
101
112
  const workflows = JSON.parse(fs.readFileSync(path.join(root, ".claude/nccgs/workflow-catalog.json"), "utf8"));
@@ -111,6 +122,10 @@ for (const workflow of workflows.workflows ?? []) {
111
122
  if (lines > 120) errors.push(`Workflow entrypoint exceeds 120 lines: ${workflow.name}`);
112
123
  if (fs.existsSync(path.join(path.dirname(file), "README.md"))) errors.push(`NCCGS-owned skill contains redundant README: ${workflow.name}`);
113
124
  for (const role of workflow.primaryRoles ?? []) if (!agentNames.has(role)) errors.push(`Workflow ${workflow.name} references unknown role ${role}`);
125
+ for (const [profile, roles] of Object.entries(workflow.primaryRolesByProfile ?? {})) {
126
+ if (!studio.modelProfiles[profile]) errors.push(`Workflow ${workflow.name} references unknown profile ${profile}`);
127
+ for (const role of roles) if (!agentNames.has(role)) errors.push(`Workflow ${workflow.name} references unknown role ${role}`);
128
+ }
114
129
  }
115
130
  for (const entry of workflows.entrypoints ?? []) if (!workflowNames.has(entry)) errors.push(`Missing entrypoint workflow: ${entry}`);
116
131
 
@@ -137,9 +152,7 @@ for (const command of hookCommands) {
137
152
  if (check.status !== 0) errors.push(`Hook syntax failure ${match[1]}: ${check.stderr.trim()}`);
138
153
  }
139
154
  for (const file of [
140
- path.join(root, ".claude/nccgs/tools/configure-models.mjs"),
141
- path.join(root, ".claude/nccgs/tools/integrity.mjs"),
142
- path.join(root, ".claude/nccgs/tools/approve-overrides.mjs"),
155
+ ...walk(path.join(root, ".claude/nccgs/tools")).filter(file => file.endsWith(".mjs")),
143
156
  path.join(root, "scripts/cli.mjs"),
144
157
  path.join(root, "scripts/doctor.mjs"),
145
158
  path.join(root, "scripts/install.mjs")
@@ -213,7 +226,7 @@ if (errors.length) {
213
226
 
214
227
  console.log("NCCGS validation passed.");
215
228
  console.log(`NCCGS workflows: ${workflowNames.size} (${workflows.entrypoints.length} entrypoints)`);
216
- console.log(`Studio agents: ${agentNames.size}`);
229
+ console.log(`Studio agents: ${agentNames.size} in library; ${pipeline.coreAgents.length} active by default`);
217
230
  console.log(`Path-scoped rules: ${rules.length}`);
218
231
  console.log(`Managed hooks: ${hookCommands.length}`);
219
232
  console.log(`Vendored Unity Skills: ${manifest?.skills?.length ?? 0}`);
@@ -6,13 +6,46 @@ import path from "node:path";
6
6
  import { spawnSync } from "node:child_process";
7
7
  import test from "node:test";
8
8
  import { fileURLToPath } from "node:url";
9
+ import { compile, renderAgent } from "../.claude/nccgs/tools/compile-policy.mjs";
9
10
 
10
11
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11
12
  const studio = JSON.parse(fs.readFileSync(path.join(root, ".claude/nccgs/studio.json"), "utf8"));
12
13
  const workflows = JSON.parse(fs.readFileSync(path.join(root, ".claude/nccgs/workflow-catalog.json"), "utf8"));
14
+ const pipeline = JSON.parse(fs.readFileSync(path.join(root, ".claude/nccgs/pipeline.json"), "utf8"));
15
+ function copyAgentFixture(temp) {
16
+ fs.cpSync(path.join(root, ".claude/nccgs"), path.join(temp, ".claude/nccgs"), { recursive: true });
17
+ fs.mkdirSync(path.join(temp, ".claude/agents"), { recursive: true });
18
+ for (const agent of studio.agents) {
19
+ const template = fs.readFileSync(path.join(root, ".claude/nccgs/agent-library", `${agent.name}.md`), "utf8");
20
+ fs.writeFileSync(path.join(temp, ".claude/agents", `${agent.name}.md`), renderAgent(template, pipeline.profiles["astra-claude"].models[agent.modelClass]));
21
+ }
22
+ }
13
23
  const frameworkVersion = fs.readFileSync(path.join(root, "VERSION"), "utf8").trim();
14
24
  const sha256 = (value) => crypto.createHash("sha256").update(value).digest("hex");
15
25
 
26
+ test("canonical policy changes invalidate generated routing until compilation", () => {
27
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), "nccgs-policy-test-"));
28
+ try {
29
+ copyAgentFixture(temp);
30
+ compile(temp, true);
31
+ assert.deepEqual(compile(temp), []);
32
+ const file = path.join(temp, ".claude/nccgs/pipeline.json");
33
+ const policy = JSON.parse(fs.readFileSync(file, "utf8"));
34
+ policy.profiles["astra-claude"].models.review.effort = "medium";
35
+ fs.writeFileSync(file, JSON.stringify(policy));
36
+ const stale = compile(temp);
37
+ assert.ok(stale.includes(".claude/agents/nccgs-adversarial-reviewer.md"));
38
+ assert.ok(stale.includes(".claude/nccgs/workflow-catalog.json"));
39
+ compile(temp, true);
40
+ assert.deepEqual(compile(temp), []);
41
+ const catalog = JSON.parse(fs.readFileSync(path.join(temp, ".claude/nccgs/studio.json"), "utf8"));
42
+ assert.equal(catalog.effortProfiles["astra-claude"].review, "medium");
43
+ } finally {
44
+ assert.ok(path.resolve(temp).startsWith(path.resolve(os.tmpdir()) + path.sep + "nccgs-policy-test-"));
45
+ fs.rmSync(temp, { recursive: true, force: true });
46
+ }
47
+ });
48
+
16
49
  test("studio roles are unique and workflow references resolve", () => {
17
50
  const roles = new Set(studio.agents.map((agent) => agent.name));
18
51
  assert.equal(roles.size, studio.agents.length);
@@ -27,8 +60,116 @@ test("quality profile assigns all model classes", () => {
27
60
  }
28
61
  });
29
62
 
63
+ test("optional specialists survive updates and can be disabled without changing core agents", () => {
64
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), "nccgs-optional-agents-"));
65
+ try {
66
+ for (const marker of ["Assets", "Packages", "ProjectSettings"]) fs.mkdirSync(path.join(temp, marker));
67
+ const run = (...args) => spawnSync(process.execPath, [path.join(root, "scripts/cli.mjs"), ...args], { cwd: temp, encoding: "utf8" });
68
+ const install = run("install");
69
+ assert.equal(install.status, 0, install.stderr || install.stdout);
70
+ const role = "nccgs-network-programmer";
71
+ const file = path.join(temp, ".claude/agents", `${role}.md`);
72
+ assert.ok(!fs.existsSync(file));
73
+ assert.equal(run("agents", "--enable", role).status, 0);
74
+ assert.match(fs.readFileSync(file, "utf8"), /model: claude-opus-5-5/);
75
+ const update = run("update");
76
+ assert.equal(update.status, 0, update.stderr || update.stdout);
77
+ assert.ok(fs.existsSync(file));
78
+ assert.equal(run("doctor").status, 0);
79
+ assert.equal(run("agents", "--disable", role).status, 0);
80
+ assert.ok(!fs.existsSync(file));
81
+ assert.notEqual(run("agents", "--disable", "nccgs-adversarial-reviewer").status, 0);
82
+ assert.equal(fs.readdirSync(path.join(temp, ".claude/agents")).length, pipeline.coreAgents.length);
83
+ } finally { fs.rmSync(temp, { recursive: true, force: true }); }
84
+ });
85
+
86
+ test("default install configures Opus coding and independent Fable review without erasing settings", () => {
87
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), "nccgs-astra-install-"));
88
+ try {
89
+ for (const marker of ["Assets", "Packages", "ProjectSettings", ".claude"]) fs.mkdirSync(path.join(temp, marker));
90
+ const settingsPath = path.join(temp, ".claude/settings.json");
91
+ fs.writeFileSync(settingsPath, JSON.stringify({ model: "sonnet", effortLevel: "medium", modelSettings: { "claude-opus-5-5": { effortLevel: "low" }, "claude-sonnet-4-6": { effortLevel: "medium" } }, permissions: { allow: ["Bash(git status*)"] }, env: { NCCGS_TEST: "preserved" } }));
92
+ const run = (...args) => spawnSync(process.execPath, [path.join(root, "scripts/cli.mjs"), ...args], { cwd: temp, encoding: "utf8" });
93
+ const dry = run("install", "--dry-run");
94
+ assert.equal(dry.status, 0, dry.stderr || dry.stdout);
95
+ assert.equal(JSON.parse(fs.readFileSync(settingsPath)).model, "sonnet");
96
+ assert.ok(!fs.existsSync(path.join(temp, ".claude/agents")));
97
+ const install = run("install");
98
+ assert.equal(install.status, 0, install.stderr || install.stdout);
99
+ const settings = JSON.parse(fs.readFileSync(settingsPath));
100
+ assert.equal(settings.model, "claude-opus-5-5");
101
+ assert.equal(settings.effortLevel, "xhigh");
102
+ assert.equal(settings.modelSettings["claude-opus-5-5"].effortLevel, "xhigh");
103
+ assert.equal(settings.modelSettings["claude-sonnet-4-6"].effortLevel, "medium");
104
+ assert.equal(settings.env.NCCGS_TEST, "preserved");
105
+ assert.deepEqual(settings.permissions.allow, ["Bash(git status*)"]);
106
+ for (const agent of studio.agents) {
107
+ if (!pipeline.coreAgents.includes(agent.name)) {
108
+ assert.ok(!fs.existsSync(path.join(temp, ".claude/agents", `${agent.name}.md`)));
109
+ continue;
110
+ }
111
+ const text = fs.readFileSync(path.join(temp, ".claude/agents", `${agent.name}.md`), "utf8");
112
+ const expected = agent.name === "nccgs-adversarial-reviewer" ? "fable" : "claude-opus-5-5";
113
+ assert.equal(text.match(/^model:\s*(\S+)/m)?.[1], expected, agent.name);
114
+ const expectedEffort = agent.name === "nccgs-adversarial-reviewer" ? "high" : "xhigh";
115
+ assert.equal(text.match(/^effort:\s*(\S+)/m)?.[1], expectedEffort, agent.name);
116
+ }
117
+ assert.match(fs.readFileSync(path.join(temp, ".nccgs/project.yaml"), "utf8"), /profile: astra-claude/);
118
+ assert.ok(fs.existsSync(path.join(temp, ".nccgs/templates/implementation-report.md")));
119
+ const update = run("update");
120
+ assert.equal(update.status, 0, update.stderr || update.stdout);
121
+ assert.match(update.stdout, /0 preserved override/);
122
+ const doctor = run("doctor");
123
+ assert.equal(doctor.status, 0, doctor.stderr || doctor.stdout);
124
+ } finally {
125
+ fs.rmSync(temp, { recursive: true, force: true });
126
+ }
127
+ });
128
+
129
+ test("profile changes clear effort pins and preserve skipped custom agents", () => {
130
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), "nccgs-effort-profile-"));
131
+ try {
132
+ fs.mkdirSync(path.join(temp, ".claude/nccgs"), { recursive: true });
133
+ fs.mkdirSync(path.join(temp, ".claude/agents"));
134
+ fs.mkdirSync(path.join(temp, ".nccgs"));
135
+ fs.copyFileSync(path.join(root, ".claude/nccgs/studio.json"), path.join(temp, ".claude/nccgs/studio.json"));
136
+ fs.writeFileSync(path.join(temp, ".nccgs/project.yaml"), "models:\n profile: astra-claude\n");
137
+ copyAgentFixture(temp);
138
+ const run = (profile, ...args) => spawnSync(process.execPath, [path.join(root, ".claude/nccgs/tools/configure-models.mjs"), "--project", temp, "--profile", profile, ...args], { encoding: "utf8" });
139
+ for (const profile of ["inherit", "quality", "balanced", "astra-claude"]) {
140
+ const result = run(profile);
141
+ assert.equal(result.status, 0, result.stderr || result.stdout);
142
+ for (const agent of studio.agents) {
143
+ if (profile === "astra-claude" && !pipeline.coreAgents.includes(agent.name)) {
144
+ assert.ok(!fs.existsSync(path.join(temp, ".claude/agents", `${agent.name}.md`)));
145
+ continue;
146
+ }
147
+ const text = fs.readFileSync(path.join(temp, ".claude/agents", `${agent.name}.md`), "utf8");
148
+ assert.equal(text.match(/^model:\s*(\S+)/m)?.[1] ?? null, studio.modelProfiles[profile][agent.modelClass], agent.name);
149
+ const expectedEffort = profile === "astra-claude"
150
+ ? (agent.name === "nccgs-adversarial-reviewer" ? "high" : "xhigh") : null;
151
+ assert.equal(text.match(/^effort:\s*(\S+)/m)?.[1] ?? null, expectedEffort, agent.name);
152
+ }
153
+ }
154
+ const file = path.join(temp, ".claude/agents/nccgs-adversarial-reviewer.md");
155
+ const before = fs.readFileSync(file, "utf8");
156
+ assert.equal(run("inherit", "--skip-path", ".claude/agents/nccgs-adversarial-reviewer.md").status, 0);
157
+ assert.equal(fs.readFileSync(file, "utf8"), before);
158
+ const invalid = run("unknown");
159
+ assert.notEqual(invalid.status, 0);
160
+ assert.equal(fs.readFileSync(file, "utf8"), before);
161
+ fs.writeFileSync(path.join(temp, ".claude/settings.json"), "invalid JSON");
162
+ assert.notEqual(run("astra-claude").status, 0);
163
+ assert.equal(fs.readFileSync(file, "utf8"), before);
164
+ const other = fs.readFileSync(path.join(temp, ".claude/agents/nccgs-unity-implementer.md"), "utf8");
165
+ assert.ok(!/^model:/m.test(other), "invalid settings must fail before rewriting agents");
166
+ } finally {
167
+ fs.rmSync(temp, { recursive: true, force: true });
168
+ }
169
+ });
170
+
30
171
  test("migrate-project defaults to a single full run", () => {
31
- const skill = fs.readFileSync(path.join(root, ".claude/skills/migrate-project/SKILL.md"), "utf8");
172
+ const skill = fs.readFileSync(path.join(root, ".claude/skills/migrate-project/references/legacy.md"), "utf8");
32
173
  assert.match(skill, /Default to `full`/);
33
174
  assert.match(skill, /review, plan, apply, and verify in one invocation/i);
34
175
  });
@@ -42,7 +183,7 @@ test("model configurator can remove and restore model pins", () => {
42
183
  fs.copyFileSync(path.join(root, ".claude/nccgs/studio.json"), path.join(temp, ".claude/nccgs/studio.json"));
43
184
  fs.copyFileSync(path.join(root, ".claude/nccgs/tools/configure-models.mjs"), path.join(temp, ".claude/nccgs/tools/configure-models.mjs"));
44
185
  fs.writeFileSync(path.join(temp, ".nccgs/project.yaml"), "models:\n profile: quality\n", "utf8");
45
- for (const agent of studio.agents) fs.copyFileSync(path.join(root, ".claude/agents", `${agent.name}.md`), path.join(temp, ".claude/agents", `${agent.name}.md`));
186
+ copyAgentFixture(temp);
46
187
  const tool = path.join(temp, ".claude/nccgs/tools/configure-models.mjs");
47
188
  assert.equal(spawnSync(process.execPath, [tool, "--project", temp, "--profile", "inherit"]).status, 0);
48
189
  assert.ok(!/^model:/m.test(fs.readFileSync(path.join(temp, ".claude/agents", `${studio.agents[0].name}.md`), "utf8")));