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.
- package/.claude/agents/nccgs-adversarial-reviewer.md +7 -1
- package/.claude/agents/nccgs-engine-programmer.md +2 -1
- package/.claude/agents/nccgs-gameplay-programmer.md +2 -1
- package/.claude/agents/nccgs-qa-engineer.md +2 -1
- package/.claude/agents/nccgs-ui-programmer.md +2 -1
- package/.claude/agents/nccgs-unity-build-specialist.md +2 -1
- package/.claude/agents/nccgs-unity-implementer.md +2 -1
- package/.claude/agents/nccgs-unity-systems-specialist.md +2 -1
- package/.claude/agents/nccgs-unity-ui-specialist.md +2 -1
- package/.claude/agents/nccgs-verification-engineer.md +2 -1
- package/.claude/nccgs/VERSION +1 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-accessibility-specialist.md +0 -1
- package/.claude/nccgs/agent-library/nccgs-adversarial-reviewer.md +30 -0
- package/.claude/{agents → nccgs/agent-library}/nccgs-ai-programmer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-analytics-engineer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-art-direction-lead.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-audio-direction-lead.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-creative-director.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-documentation-manager.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-economy-designer.md +0 -1
- package/.claude/nccgs/agent-library/nccgs-engine-programmer.md +25 -0
- package/.claude/{agents → nccgs/agent-library}/nccgs-game-design-lead.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-game-designer.md +0 -1
- package/.claude/nccgs/agent-library/nccgs-gameplay-programmer.md +25 -0
- package/.claude/{agents → nccgs/agent-library}/nccgs-level-designer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-live-ops-designer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-localization-lead.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-narrative-lead.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-network-programmer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-performance-analyst.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-producer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-production-coordinator.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-programming-lead.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-prototyper.md +0 -1
- package/.claude/nccgs/agent-library/nccgs-qa-engineer.md +25 -0
- package/.claude/{agents → nccgs/agent-library}/nccgs-qa-lead.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-release-engineer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-release-lead.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-security-engineer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-sound-designer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-systems-designer.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-technical-architect.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-technical-artist.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-technical-director.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-tools-programmer.md +0 -1
- package/.claude/nccgs/agent-library/nccgs-ui-programmer.md +25 -0
- package/.claude/nccgs/agent-library/nccgs-unity-build-specialist.md +25 -0
- package/.claude/{agents → nccgs/agent-library}/nccgs-unity-content-specialist.md +0 -1
- package/.claude/nccgs/agent-library/nccgs-unity-implementer.md +25 -0
- package/.claude/{agents → nccgs/agent-library}/nccgs-unity-rendering-specialist.md +0 -1
- package/.claude/nccgs/agent-library/nccgs-unity-systems-specialist.md +25 -0
- package/.claude/nccgs/agent-library/nccgs-unity-ui-specialist.md +25 -0
- package/.claude/{agents → nccgs/agent-library}/nccgs-ux-designer.md +0 -1
- package/.claude/nccgs/agent-library/nccgs-verification-engineer.md +25 -0
- package/.claude/{agents → nccgs/agent-library}/nccgs-world-builder.md +0 -1
- package/.claude/{agents → nccgs/agent-library}/nccgs-writer.md +0 -1
- package/.claude/nccgs/constitution.md +13 -0
- package/.claude/nccgs/hooks/agent-audit.mjs +6 -0
- package/.claude/nccgs/hooks/closure-guard.mjs +34 -0
- package/.claude/nccgs/hooks/runtime-observation.mjs +11 -0
- package/.claude/nccgs/hooks/session-start.mjs +8 -1
- package/.claude/nccgs/pipeline.json +176 -0
- package/.claude/nccgs/protocols/agent-contract.md +5 -0
- package/.claude/nccgs/protocols/astra-claude.md +77 -0
- package/.claude/nccgs/protocols/evidence.md +6 -0
- package/.claude/nccgs/protocols/model-routing.md +27 -5
- package/.claude/nccgs/protocols/orchestration.md +5 -0
- package/.claude/nccgs/protocols/task-gates.md +66 -0
- package/.claude/nccgs/routing.generated.md +11 -0
- package/.claude/nccgs/settings.fragment.json +73 -9
- package/.claude/nccgs/studio.json +187 -96
- package/.claude/nccgs/tools/compile-policy.mjs +83 -0
- package/.claude/nccgs/tools/configure-models.mjs +75 -35
- package/.claude/nccgs/tools/gates.mjs +167 -0
- package/.claude/nccgs/tools/policy.mjs +62 -0
- package/.claude/nccgs/tools/runtime.mjs +40 -0
- package/.claude/nccgs/tools/task.mjs +77 -0
- package/.claude/nccgs/workflow-catalog.json +90 -13
- package/.claude/skills/accessibility-review/SKILL.md +6 -5
- package/.claude/skills/accessibility-review/references/astra.md +17 -0
- package/.claude/skills/accessibility-review/references/legacy.md +7 -0
- package/.claude/skills/architecture-decision/SKILL.md +6 -5
- package/.claude/skills/architecture-decision/references/astra.md +12 -0
- package/.claude/skills/architecture-decision/references/legacy.md +7 -0
- package/.claude/skills/asset-audit/SKILL.md +6 -5
- package/.claude/skills/asset-audit/references/astra.md +17 -0
- package/.claude/skills/asset-audit/references/legacy.md +7 -0
- package/.claude/skills/audit/SKILL.md +6 -7
- package/.claude/skills/audit/references/astra.md +17 -0
- package/.claude/skills/audit/references/legacy.md +9 -0
- package/.claude/skills/balance-review/SKILL.md +6 -5
- package/.claude/skills/balance-review/references/astra.md +17 -0
- package/.claude/skills/balance-review/references/legacy.md +7 -0
- package/.claude/skills/bug-triage/SKILL.md +6 -5
- package/.claude/skills/bug-triage/references/astra.md +20 -0
- package/.claude/skills/bug-triage/references/legacy.md +7 -0
- package/.claude/skills/closure/SKILL.md +5 -4
- package/.claude/skills/closure/references/astra.md +15 -0
- package/.claude/skills/closure/references/legacy.md +7 -0
- package/.claude/skills/compatibility-review/SKILL.md +6 -5
- package/.claude/skills/compatibility-review/references/astra.md +17 -0
- package/.claude/skills/compatibility-review/references/legacy.md +7 -0
- package/.claude/skills/context-pack/SKILL.md +5 -4
- package/.claude/skills/context-pack/references/astra.md +12 -0
- package/.claude/skills/context-pack/references/legacy.md +7 -0
- package/.claude/skills/dependency-review/SKILL.md +6 -5
- package/.claude/skills/dependency-review/references/astra.md +17 -0
- package/.claude/skills/dependency-review/references/legacy.md +7 -0
- package/.claude/skills/design/SKILL.md +6 -11
- package/.claude/skills/design/references/astra.md +12 -0
- package/.claude/skills/design/references/legacy.md +13 -0
- package/.claude/skills/design-review/SKILL.md +6 -5
- package/.claude/skills/design-review/references/astra.md +17 -0
- package/.claude/skills/design-review/references/legacy.md +7 -0
- package/.claude/skills/evidence-review/SKILL.md +6 -5
- package/.claude/skills/evidence-review/references/astra.md +17 -0
- package/.claude/skills/evidence-review/references/legacy.md +7 -0
- package/.claude/skills/hotfix/SKILL.md +6 -5
- package/.claude/skills/hotfix/references/astra.md +23 -0
- package/.claude/skills/hotfix/references/legacy.md +7 -0
- package/.claude/skills/incident-recovery/SKILL.md +6 -5
- package/.claude/skills/incident-recovery/references/astra.md +20 -0
- package/.claude/skills/incident-recovery/references/legacy.md +7 -0
- package/.claude/skills/localize-game/SKILL.md +6 -5
- package/.claude/skills/localize-game/references/astra.md +20 -0
- package/.claude/skills/localize-game/references/legacy.md +7 -0
- package/.claude/skills/migrate-project/SKILL.md +6 -16
- package/.claude/skills/migrate-project/references/astra.md +23 -0
- package/.claude/skills/migrate-project/references/legacy.md +19 -0
- package/.claude/skills/migrate-project/references/procedure.md +8 -0
- package/.claude/skills/milestone-review/SKILL.md +6 -5
- package/.claude/skills/milestone-review/references/astra.md +12 -0
- package/.claude/skills/milestone-review/references/legacy.md +7 -0
- package/.claude/skills/nccgs-code-review/SKILL.md +6 -4
- package/.claude/skills/nccgs-code-review/references/astra.md +15 -0
- package/.claude/skills/nccgs-code-review/references/legacy.md +7 -0
- package/.claude/skills/performance-audit/SKILL.md +6 -5
- package/.claude/skills/performance-audit/references/astra.md +17 -0
- package/.claude/skills/performance-audit/references/legacy.md +7 -0
- package/.claude/skills/plan-feature/SKILL.md +6 -5
- package/.claude/skills/plan-feature/references/astra.md +12 -0
- package/.claude/skills/plan-feature/references/legacy.md +7 -0
- package/.claude/skills/playtest/SKILL.md +5 -4
- package/.claude/skills/playtest/references/astra.md +20 -0
- package/.claude/skills/playtest/references/legacy.md +7 -0
- package/.claude/skills/project-stage/SKILL.md +5 -4
- package/.claude/skills/project-stage/references/astra.md +12 -0
- package/.claude/skills/project-stage/references/legacy.md +7 -0
- package/.claude/skills/prototype-feature/SKILL.md +6 -5
- package/.claude/skills/prototype-feature/references/astra.md +20 -0
- package/.claude/skills/prototype-feature/references/legacy.md +7 -0
- package/.claude/skills/qa-plan/SKILL.md +6 -5
- package/.claude/skills/qa-plan/references/astra.md +12 -0
- package/.claude/skills/qa-plan/references/legacy.md +7 -0
- package/.claude/skills/release/SKILL.md +6 -9
- package/.claude/skills/release/references/astra.md +23 -0
- package/.claude/skills/release/references/legacy.md +11 -0
- package/.claude/skills/release-readiness/SKILL.md +6 -5
- package/.claude/skills/release-readiness/references/astra.md +17 -0
- package/.claude/skills/release-readiness/references/legacy.md +7 -0
- package/.claude/skills/retrospective/SKILL.md +6 -5
- package/.claude/skills/retrospective/references/astra.md +12 -0
- package/.claude/skills/retrospective/references/legacy.md +7 -0
- package/.claude/skills/review/SKILL.md +6 -7
- package/.claude/skills/review/references/astra.md +15 -0
- package/.claude/skills/review/references/legacy.md +9 -0
- package/.claude/skills/security-audit/SKILL.md +6 -5
- package/.claude/skills/security-audit/references/astra.md +17 -0
- package/.claude/skills/security-audit/references/legacy.md +7 -0
- package/.claude/skills/sprint-plan/SKILL.md +6 -5
- package/.claude/skills/sprint-plan/references/astra.md +12 -0
- package/.claude/skills/sprint-plan/references/legacy.md +7 -0
- package/.claude/skills/status/SKILL.md +5 -6
- package/.claude/skills/status/references/astra.md +13 -0
- package/.claude/skills/status/references/legacy.md +9 -0
- package/.claude/skills/story-readiness/SKILL.md +5 -4
- package/.claude/skills/story-readiness/references/astra.md +17 -0
- package/.claude/skills/story-readiness/references/legacy.md +7 -0
- package/.claude/skills/test/SKILL.md +6 -7
- package/.claude/skills/test/references/astra.md +20 -0
- package/.claude/skills/test/references/legacy.md +9 -0
- package/.claude/skills/ui-review/SKILL.md +6 -5
- package/.claude/skills/ui-review/references/astra.md +17 -0
- package/.claude/skills/ui-review/references/legacy.md +7 -0
- package/.claude/skills/work/SKILL.md +6 -27
- package/.claude/skills/work/references/astra.md +20 -0
- package/.claude/skills/work/references/closure.md +10 -0
- package/.claude/skills/work/references/feature-contracts.md +4 -0
- package/.claude/skills/work/references/legacy.md +29 -0
- package/README.md +59 -25
- package/UPGRADING.md +37 -0
- package/VERSION +1 -1
- package/docs/ARCHITECTURE.md +30 -13
- package/docs/HUONG-DAN-MIGRATE-VA-SU-DUNG.md +34 -329
- package/docs/MIGRATION-MATRIX.md +1 -1
- package/docs/NCCGS-1.3.md +125 -0
- package/docs/PROJECT-POLICY.md +18 -3
- package/docs/WORKFLOWS.md +19 -13
- package/package.json +5 -3
- package/scaffold/.nccgs/gates.json +7 -0
- package/scaffold/.nccgs/project.yaml +6 -3
- package/scaffold/.nccgs/state.md +1 -1
- package/scaffold/.nccgs/templates/agent-handoff.md +10 -0
- package/scaffold/.nccgs/templates/closure-record.md +11 -0
- package/scaffold/.nccgs/templates/feature-contract.md +9 -0
- package/scaffold/.nccgs/templates/implementation-report.md +49 -0
- package/scripts/cli.mjs +9 -4
- package/scripts/doctor.mjs +27 -1
- package/scripts/install.mjs +4 -3
- package/scripts/validate.mjs +20 -7
- package/tests/framework.test.mjs +143 -2
- package/tests/gates.test.mjs +170 -0
package/scaffold/.nccgs/state.md
CHANGED
|
@@ -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:
|
|
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
|
|
29
|
-
const
|
|
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,
|
|
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
|
}
|
package/scripts/doctor.mjs
CHANGED
|
@@ -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
|
|
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}`);
|
package/scripts/install.mjs
CHANGED
|
@@ -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
|
|
168
|
-
|
|
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`.");
|
package/scripts/validate.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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}`);
|
package/tests/framework.test.mjs
CHANGED
|
@@ -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/
|
|
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
|
-
|
|
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")));
|