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
@@ -0,0 +1,170 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
+ import test from "node:test";
8
+ import { initialize, snapshot, record, check, close, taskPath } from "../.claude/nccgs/tools/gates.mjs";
9
+ import { artifact, atomicJson, json, hash } from "../.claude/nccgs/tools/policy.mjs";
10
+ import { run } from "../.claude/nccgs/tools/task.mjs";
11
+ import { observe } from "../.claude/nccgs/tools/runtime.mjs";
12
+
13
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
+ function fixture(options = {}) {
15
+ const project = fs.mkdtempSync(path.join(os.tmpdir(), "nccgs-gates-"));
16
+ const write = (name, value) => {
17
+ const file = path.join(project, name);
18
+ fs.mkdirSync(path.dirname(file), { recursive: true });
19
+ fs.writeFileSync(file, typeof value === "string" ? value : JSON.stringify(value));
20
+ return artifact(project, name);
21
+ };
22
+ for (const file of ["pipeline.json", "studio.json"]) write(`.claude/nccgs/${file}`, fs.readFileSync(path.join(root, ".claude/nccgs", file), "utf8"));
23
+ const policy = write(".nccgs/project.yaml", "schema_version: 2\nmodels:\n profile: astra-claude\n");
24
+ write(".nccgs/gates.json", { schemaVersion: 1, policySha256: policy.sha256, requiredChecks: ["regression"], requiredArtifacts: options.artifacts ?? [], manualGates: options.manual ?? [] });
25
+ write("Assets/game.cs", "// initial implementation\n");
26
+ write(".nccgs/plan.md", "Astra plan: preserve health invariant and verify regression.");
27
+ initialize(project, "feature", ".nccgs/plan.md");
28
+ let index = 0;
29
+ const snap = () => snapshot(project).sha256;
30
+ const task = () => json(taskPath(project, "feature"));
31
+ function runtime(stage, overrides = {}) {
32
+ const source = `.nccgs/evidence/raw-${++index}.json`;
33
+ write(source, { taskId: "feature", snapshot: snap(), sessionId: "session", agentId: stage,
34
+ observedModel: stage === "implementation" ? "claude-opus-5-5" : "claude-fable-5-1",
35
+ observedEffort: stage === "implementation" ? "xhigh" : "high", capturedAt: new Date().toISOString(),
36
+ status: "completed", provenance: { kind: "provider-log", reference: "test fixture: captured runtime request metadata" }, ...overrides });
37
+ return run(["observe", "--project", project, "--id", "feature", "--file", source]);
38
+ }
39
+ const impl = (overrides = {}) => record(project, "feature", "implementation", { snapshot: snap(), runtime: runtime("implementation"),
40
+ checks: [{ name: "regression", status: "PASS", evidence: write(`.nccgs/evidence/test-${++index}.log`, "fixture regression: PASS") }], ...overrides });
41
+ const review = (overrides = {}) => record(project, "feature", "review", { snapshot: snap(), runtime: runtime("review"), verdict: "PASS", criteriaMet: true, findings: [], evidence: write(`.nccgs/evidence/review-${++index}.md`, "Fixture independent review findings: none"), ...overrides });
42
+ const report = (overrides = {}) => record(project, "feature", "report", { snapshot: snap(), evidence: write(`.nccgs/evidence/report-${++index}.md`, "Fixture report, review reference and evidence"), ...overrides });
43
+ const approval = (stage, overrides = {}) => record(project, "feature", stage, { snapshot: snap(), evidence: write(`.nccgs/approvals/${stage}-${++index}.json`, {
44
+ actor: stage === "astra" ? "Astra" : "Product Owner", decision: stage === "astra" ? "PASS" : "ACCEPTED",
45
+ taskId: "feature", snapshot: snap(), decidedAt: new Date().toISOString(), statement: "Explicit TEST FIXTURE approval", reference: "test-only external decision event", ...overrides }) });
46
+ return { project, write, snap, task, runtime, impl, review, report, approval,
47
+ dispose() {
48
+ const resolved = path.resolve(project);
49
+ assert.ok(resolved.startsWith(path.resolve(os.tmpdir()) + path.sep + "nccgs-gates-"));
50
+ fs.rmSync(resolved, { recursive: true, force: true });
51
+ } };
52
+ }
53
+
54
+ test("end-to-end gates require Fable, Astra and explicit acceptance in order", () => {
55
+ const f = fixture();
56
+ try {
57
+ assert.throws(() => close(f.project, "feature"), /implementation: missing/);
58
+ assert.throws(() => f.report(), /implementation: missing/);
59
+ f.impl();
60
+ assert.throws(() => f.report(), /review: missing/);
61
+ f.review();
62
+ assert.equal(check(f.project, f.task(), "report").allowed, true);
63
+ f.report();
64
+ assert.throws(() => close(f.project, "feature"), /astra: missing/);
65
+ f.approval("astra");
66
+ assert.throws(() => close(f.project, "feature"), /acceptance: missing/);
67
+ f.approval("acceptance");
68
+ assert.equal(close(f.project, "feature").phase, "DONE");
69
+ } finally { f.dispose(); }
70
+ });
71
+
72
+ test("source additions, deletions and edits invalidate review and approvals", () => {
73
+ const f = fixture();
74
+ try {
75
+ f.impl(); f.review(); f.report(); f.approval("astra"); f.approval("acceptance"); close(f.project, "feature");
76
+ const before = f.snap();
77
+ f.write("Assets/new.cs", "untracked new source");
78
+ assert.notEqual(f.snap(), before);
79
+ assert.throws(() => close(f.project, "feature"), /stale/);
80
+ fs.unlinkSync(path.join(f.project, "Assets/game.cs"));
81
+ assert.throws(() => check(f.project, f.task(), "report"), /stale/);
82
+ f.impl();
83
+ assert.deepEqual(Object.keys(f.task().stages), ["implementation"]);
84
+ assert.equal(f.task().closed, undefined);
85
+ assert.ok(f.task().history.length > 0);
86
+ assert.throws(() => f.report(), /review: missing/);
87
+ } finally { f.dispose(); }
88
+ });
89
+
90
+ test("unknown effort, wrong model, fallback, and same-agent review cannot pass", () => {
91
+ const f = fixture();
92
+ try {
93
+ assert.throws(() => f.impl({ runtime: f.runtime("implementation", { observedEffort: "high" }) }), /effort/);
94
+ assert.throws(() => f.impl({ runtime: f.runtime("implementation", { observedModel: "claude-sonnet-4-6" }) }), /model/);
95
+ f.impl();
96
+ assert.throws(() => f.review({ runtime: f.runtime("review", { agentId: "implementation" }) }), /independent/);
97
+ assert.throws(() => f.review({ runtime: f.runtime("review", { modelsUsed: ["claude-fable-5-1", "claude-opus-5-5"] }) }), /fallback/);
98
+ const event = observe(f.project, { session_id: "session", tool_input: { subagent_type: "nccgs-adversarial-reviewer" }, tool_response: { resolvedModel: "claude-fable-5-1", status: "completed", agentId: "native-review" } }, "agent_result");
99
+ assert.equal(event.observedEffort, null);
100
+ assert.equal(event.requestedEffort, "high");
101
+ assert.ok(event.missing.includes("effort"));
102
+ const ref = f.write(".nccgs/evidence/unknown-effort.json", { ...event, taskId: "feature", snapshot: f.snap() });
103
+ assert.throws(() => f.review({ runtime: ref }), /effort/);
104
+ } finally { f.dispose(); }
105
+ });
106
+
107
+ test("negative review and rejection remain blocking; amended evidence invalidates results", () => {
108
+ const f = fixture();
109
+ try {
110
+ f.impl(); f.review({ verdict: "CHANGES_REQUIRED", findings: [{ severity: "HIGH", status: "OPEN" }] });
111
+ assert.throws(() => f.report(), /not passed/);
112
+ assert.throws(() => f.review({ findings: [{ severity: "HIGH", status: "OPEN" }] }), /blocking findings/);
113
+ f.review(); f.report(); f.approval("astra"); f.approval("acceptance");
114
+ f.approval("acceptance", { decision: "REJECTED" });
115
+ assert.throws(() => close(f.project, "feature"), /explicit Product Owner/);
116
+ const evidence = f.task().stages.review.evidence;
117
+ f.write(evidence.path, "altered review");
118
+ assert.throws(() => check(f.project, f.task(), "report"), /stale artifact/);
119
+ } finally { f.dispose(); }
120
+ });
121
+
122
+ test("required checks, closure artifacts, manual gates and policy hashes are enforced", () => {
123
+ const f = fixture({ artifacts: [".nccgs/evidence/build.log"], manual: ["playtest"] });
124
+ try {
125
+ assert.throws(() => f.impl({ checks: [{ name: "unrelated", status: "PASS", evidence: f.write(".nccgs/evidence/unrelated.log", "PASS") }] }), /Required check missing/);
126
+ f.impl(); f.review();
127
+ assert.throws(() => f.report(), /closure artifact missing/);
128
+ f.report({ artifacts: [f.write(".nccgs/evidence/build.log", "Build successful")] });
129
+ f.approval("astra");
130
+ assert.throws(() => f.approval("acceptance"), /Manual gate missing/);
131
+ f.approval("acceptance", { gates: [{ id: "playtest", decision: "ACCEPTED", reference: "test fixture playtest decision" }] });
132
+ close(f.project, "feature");
133
+ f.write(".nccgs/plan.md", "Changed plan");
134
+ assert.throws(() => close(f.project, "feature"), /stale artifact/);
135
+ } finally { f.dispose(); }
136
+ });
137
+
138
+ test("path traversal and premature/fabricated external sign-offs fail", () => {
139
+ const f = fixture();
140
+ try {
141
+ assert.throws(() => taskPath(f.project, "../escape"), /Invalid task/);
142
+ assert.throws(() => artifact(f.project, "../outside.txt"), /escapes/);
143
+ f.impl(); f.review(); f.report();
144
+ assert.throws(() => f.approval("astra", { actor: "Claude" }), /explicit Astra/);
145
+ assert.throws(() => f.approval("astra", { decidedAt: "2000-01-01T00:00:00.000Z" }), /predates/);
146
+ const old = process.env.CLAUDECODE;
147
+ try { process.env.CLAUDECODE = "1"; assert.throws(() => f.approval("astra"), /outside the Claude/); }
148
+ finally { if (old === undefined) delete process.env.CLAUDECODE; else process.env.CLAUDECODE = old; }
149
+ const guard = spawnSync(process.execPath, [path.join(root, ".claude/nccgs/hooks/closure-guard.mjs"), "pre"], {
150
+ cwd: f.project, encoding: "utf8", input: JSON.stringify({ session_id: "session", tool_input: { file_path: path.join(f.project, ".nccgs/state.md"), content: "Final state: DONE" } }) });
151
+ assert.equal(guard.status, 2);
152
+ assert.match(guard.stderr, /bound to an NCCGS task/);
153
+ } finally { f.dispose(); }
154
+ });
155
+
156
+ test("verification runner captures real exit status and rejects changed logs", () => {
157
+ const f = fixture();
158
+ try {
159
+ const execute = source => run(["verify", "--project", f.project, "--id", "feature", "--name", "regression", "--", process.execPath, "-e", source]);
160
+ const failed = execute("process.exit(3)");
161
+ assert.equal(failed.status, "FAIL");
162
+ assert.throws(() => f.impl({ checks: [failed] }), /failed\/unproven/);
163
+ const passed = execute("console.log('fixture verification passed')");
164
+ assert.equal(passed.status, "PASS");
165
+ f.impl({ checks: [passed] });
166
+ const execution = json(path.join(f.project, passed.evidence.path));
167
+ f.write(execution.log.path, "tampered log");
168
+ assert.throws(() => check(f.project, f.task(), "review"), /stale artifact/);
169
+ } finally { f.dispose(); }
170
+ });