@ornexus/neocortex 4.60.72 → 4.60.73

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 (243) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +24 -24
  3. package/install.ps1 +1 -1
  4. package/install.sh +1 -1
  5. package/package.json +2 -2
  6. package/packages/client/dist/adapters/adapter-registry.js +108 -1
  7. package/packages/client/dist/adapters/antigravity-adapter.js +77 -2
  8. package/packages/client/dist/adapters/claude-code-adapter.js +79 -3
  9. package/packages/client/dist/adapters/codex-adapter.js +80 -2
  10. package/packages/client/dist/adapters/cursor-adapter.js +115 -4
  11. package/packages/client/dist/adapters/gemini-adapter.js +71 -2
  12. package/packages/client/dist/adapters/grok-adapter.js +42 -2
  13. package/packages/client/dist/adapters/index.js +22 -1
  14. package/packages/client/dist/adapters/platform-detector.js +212 -1
  15. package/packages/client/dist/adapters/target-adapter.js +12 -0
  16. package/packages/client/dist/adapters/vscode-adapter.js +72 -2
  17. package/packages/client/dist/agent/refresh-stubs.js +417 -2
  18. package/packages/client/dist/agent/update-agent-yaml.js +102 -1
  19. package/packages/client/dist/agent/update-description.js +251 -1
  20. package/packages/client/dist/cache/crypto-utils.js +78 -1
  21. package/packages/client/dist/cache/encrypted-cache.js +108 -1
  22. package/packages/client/dist/cache/in-memory-asset-cache.js +73 -1
  23. package/packages/client/dist/cache/index.js +13 -1
  24. package/packages/client/dist/cache/protected-pi-boundary.js +44 -1
  25. package/packages/client/dist/checkpoint/checkpoint-client-reader.js +127 -2
  26. package/packages/client/dist/checkpoint/index.js +11 -1
  27. package/packages/client/dist/checkpoint/shared-checkpoint-types.js +32 -1
  28. package/packages/client/dist/cli.js +358 -5
  29. package/packages/client/dist/commands/activate.js +394 -8
  30. package/packages/client/dist/commands/architecture-answers.js +274 -7
  31. package/packages/client/dist/commands/architecture-memory-terminal.js +253 -4
  32. package/packages/client/dist/commands/architecture-non-ui-continuation.js +100 -1
  33. package/packages/client/dist/commands/architecture-root-binding.js +51 -1
  34. package/packages/client/dist/commands/cache-status.js +112 -2
  35. package/packages/client/dist/commands/config.js +88 -1
  36. package/packages/client/dist/commands/doctor.js +635 -2
  37. package/packages/client/dist/commands/invoke.js +7200 -143
  38. package/packages/client/dist/commands/legacy-epic-terminal.js +71 -1
  39. package/packages/client/dist/commands/legacy-story-branch.js +96 -1
  40. package/packages/client/dist/commands/legacy-story-pr.js +276 -1
  41. package/packages/client/dist/commands/mutation-qa-checkpoint.js +285 -5
  42. package/packages/client/dist/commands/mutation-qa-gate.js +54 -1
  43. package/packages/client/dist/commands/mutation-qa-git.js +41 -1
  44. package/packages/client/dist/commands/mutation-qa-policy.js +102 -1
  45. package/packages/client/dist/commands/mutation-qa-run.js +219 -3
  46. package/packages/client/dist/commands/mutation-qa-verdict.js +149 -1
  47. package/packages/client/dist/commands/mutation-qa.js +220 -1
  48. package/packages/client/dist/commands/native-agent.js +136 -2
  49. package/packages/client/dist/commands/planning-diagnosis.js +43 -1
  50. package/packages/client/dist/commands/planning-generation.js +53 -1
  51. package/packages/client/dist/commands/planning-memory.js +40 -1
  52. package/packages/client/dist/commands/planning-research.js +42 -1
  53. package/packages/client/dist/commands/private-session.js +75 -1
  54. package/packages/client/dist/commands/property-qa.js +139 -1
  55. package/packages/client/dist/commands/qa-control.js +16 -1
  56. package/packages/client/dist/commands/qa-scan-browser-launch.js +55 -1
  57. package/packages/client/dist/commands/qa-scan-browser-route.js +28 -1
  58. package/packages/client/dist/commands/qa-scan-capture.js +663 -5
  59. package/packages/client/dist/commands/qa-scan-discovery.js +376 -3
  60. package/packages/client/dist/commands/qa-scan-review.js +826 -5
  61. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +266 -1
  62. package/packages/client/dist/commands/qa-synthetic-backend-review.js +297 -1
  63. package/packages/client/dist/commands/qa-synthetic-backend.js +340 -2
  64. package/packages/client/dist/commands/qa-synthetic-broker-port.js +85 -1
  65. package/packages/client/dist/commands/qa-synthetic-browser.js +39 -1
  66. package/packages/client/dist/commands/refactor-characterization.js +671 -21
  67. package/packages/client/dist/commands/refactor-commit.js +399 -13
  68. package/packages/client/dist/commands/refactor-control.js +142 -3
  69. package/packages/client/dist/commands/refactor-cpp-assertions.js +93 -3
  70. package/packages/client/dist/commands/refactor-review.js +283 -5
  71. package/packages/client/dist/commands/refactor-root.js +588 -7
  72. package/packages/client/dist/commands/refresh-memory.js +156 -3
  73. package/packages/client/dist/commands/shared-architecture-non-ui.js +24 -1
  74. package/packages/client/dist/commands/story-checkpoint.js +171 -1
  75. package/packages/client/dist/config/profile-directory.js +13 -1
  76. package/packages/client/dist/config/resolver-selection.js +278 -1
  77. package/packages/client/dist/config/secure-config.js +281 -12
  78. package/packages/client/dist/config.js +223 -2
  79. package/packages/client/dist/constants.js +25 -1
  80. package/packages/client/dist/context/context-collector.js +222 -2
  81. package/packages/client/dist/context/context-sanitizer.js +145 -1
  82. package/packages/client/dist/continuity/autonomous-governance.js +285 -1
  83. package/packages/client/dist/continuity/continuity-client-state-store.js +1587 -1
  84. package/packages/client/dist/continuity/invoke-hooks.js +129 -1
  85. package/packages/client/dist/continuity/migrations/001-initial-schema.js +12 -2
  86. package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +12 -2
  87. package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +12 -2
  88. package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +12 -2
  89. package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +12 -2
  90. package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +10 -2
  91. package/packages/client/dist/continuity/native-agent-store.js +69 -1
  92. package/packages/client/dist/continuity/native-agent-types.js +90 -1
  93. package/packages/client/dist/continuity/outbox.js +117 -1
  94. package/packages/client/dist/continuity/runner-bootstrap-policy.js +172 -1
  95. package/packages/client/dist/continuity/sqlite-store.d.ts +22 -1
  96. package/packages/client/dist/continuity/sqlite-store.js +2612 -81
  97. package/packages/client/dist/effects/invoke-effect-gateways.js +62 -1
  98. package/packages/client/dist/errors/error-messages.js +228 -2
  99. package/packages/client/dist/errors/frontmatter-diagnostics.js +125 -1
  100. package/packages/client/dist/evidence/collector.js +347 -1
  101. package/packages/client/dist/evidence/index.js +1 -1
  102. package/packages/client/dist/evidence/types.js +2 -0
  103. package/packages/client/dist/fs/atomic-state-replace.js +152 -1
  104. package/packages/client/dist/generated/command-registry.generated.js +4076 -1
  105. package/packages/client/dist/generated/public-projections.generated.js +149 -1
  106. package/packages/client/dist/git/direct-yolo-c01-retry.js +138 -4
  107. package/packages/client/dist/git/direct-yolo-checkpoint.js +1234 -4
  108. package/packages/client/dist/git/epic-operational-state.d.ts +198 -0
  109. package/packages/client/dist/git/epic-operational-state.js +672 -0
  110. package/packages/client/dist/git/git-lifecycle-step-ids.js +14 -1
  111. package/packages/client/dist/git/git-lifecycle-store.d.ts +9 -1
  112. package/packages/client/dist/git/git-lifecycle-store.js +1981 -9
  113. package/packages/client/dist/git/legacy-story-branch-checkpoint.js +259 -9
  114. package/packages/client/dist/git/legacy-story-branch-setup.js +348 -4
  115. package/packages/client/dist/git/legacy-story-pr.js +3538 -70
  116. package/packages/client/dist/git/secure-filesystem-boundary.js +182 -5
  117. package/packages/client/dist/graph-retrieval/pre-command-hook.js +284 -1
  118. package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +237 -1
  119. package/packages/client/dist/i18n/first-run.js +60 -2
  120. package/packages/client/dist/index.js +47 -1
  121. package/packages/client/dist/invocation/invoke-input.js +24 -1
  122. package/packages/client/dist/license/index.js +5 -1
  123. package/packages/client/dist/license/license-client.js +304 -1
  124. package/packages/client/dist/machine/fingerprint.js +163 -2
  125. package/packages/client/dist/machine/index.js +5 -1
  126. package/packages/client/dist/memory/project-memory-path-scope.js +62 -1
  127. package/packages/client/dist/memory/project-memory-writer.js +1007 -40
  128. package/packages/client/dist/memory/shared-project-memory-types.js +253 -2
  129. package/packages/client/dist/migrations/epic-lifecycle-backfill.d.ts +97 -0
  130. package/packages/client/dist/migrations/epic-lifecycle-backfill.js +1060 -0
  131. package/packages/client/dist/migrations/project-identity-capability.js +50 -1
  132. package/packages/client/dist/migrations/project-identity.js +493 -7
  133. package/packages/client/dist/migrations/state-json.d.ts +22 -0
  134. package/packages/client/dist/migrations/state-json.js +393 -4
  135. package/packages/client/dist/policy/architecture-policy.js +184 -2
  136. package/packages/client/dist/policy/index.js +7 -1
  137. package/packages/client/dist/policy/shared-policy-types.js +38 -0
  138. package/packages/client/dist/policy/subagent-approval-policy.js +55 -1
  139. package/packages/client/dist/private-execution/clean-baseline.js +279 -2
  140. package/packages/client/dist/private-execution/codex-executor.js +574 -4
  141. package/packages/client/dist/private-execution/codex-host-runtime.js +137 -1
  142. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +97 -1
  143. package/packages/client/dist/private-execution/codex-scoped-permissions.js +128 -1
  144. package/packages/client/dist/private-execution/delivery.js +410 -13
  145. package/packages/client/dist/private-execution/diagnosis-root-review.js +294 -2
  146. package/packages/client/dist/private-execution/frozen-handoff.js +19 -6
  147. package/packages/client/dist/private-execution/implementation-validation.js +106 -2
  148. package/packages/client/dist/private-execution/isolated-admission.js +1432 -23
  149. package/packages/client/dist/private-execution/isolated-session.js +725 -5
  150. package/packages/client/dist/private-execution/isolated-workspace.js +122 -1
  151. package/packages/client/dist/private-execution/pact-consumer-runtime.js +409 -3
  152. package/packages/client/dist/private-execution/pact-test-source.js +258 -1
  153. package/packages/client/dist/private-execution/pact-toolchain.js +290 -3
  154. package/packages/client/dist/private-execution/pact-write-set.js +172 -1
  155. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +406 -5
  156. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +462 -5
  157. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +139 -3
  158. package/packages/client/dist/private-execution/planning-research-checkpoint.js +325 -4
  159. package/packages/client/dist/private-execution/private-report-store.js +42 -1
  160. package/packages/client/dist/private-execution/property-test-candidate.js +103 -1
  161. package/packages/client/dist/private-execution/property-test-choices.js +147 -3
  162. package/packages/client/dist/private-execution/property-test-plan.js +271 -7
  163. package/packages/client/dist/private-execution/property-test-review.js +258 -4
  164. package/packages/client/dist/private-execution/property-test-runtime.js +372 -9
  165. package/packages/client/dist/private-execution/property-test-source.js +139 -1
  166. package/packages/client/dist/private-execution/root-checkpoint.js +80 -1
  167. package/packages/client/dist/private-execution/root-memory-git-evidence.js +135 -3
  168. package/packages/client/dist/private-execution/root-memory-recovery.js +93 -1
  169. package/packages/client/dist/private-execution/root-memory-request.js +29 -1
  170. package/packages/client/dist/private-execution/root-memory-scope.js +178 -1
  171. package/packages/client/dist/private-execution/root-memory-writer.js +102 -1
  172. package/packages/client/dist/private-execution/session-lease.js +63 -1
  173. package/packages/client/dist/private-execution/shared-private-admission-types.js +50 -1
  174. package/packages/client/dist/private-execution/shared-project-validation.js +66 -1
  175. package/packages/client/dist/private-execution/shared-root-memory-types.js +61 -1
  176. package/packages/client/dist/private-execution/ux-source-validation.js +372 -1
  177. package/packages/client/dist/private-execution/ux-write-set.js +191 -2
  178. package/packages/client/dist/registry/registry-reconciliation.js +234 -1
  179. package/packages/client/dist/resilience/circuit-breaker.js +170 -1
  180. package/packages/client/dist/resilience/degradation-manager.js +164 -1
  181. package/packages/client/dist/resilience/freshness-indicator.js +100 -1
  182. package/packages/client/dist/resilience/index.js +8 -1
  183. package/packages/client/dist/resilience/recovery-detector.js +74 -1
  184. package/packages/client/dist/resolvers/asset-resolver.js +13 -0
  185. package/packages/client/dist/resolvers/local-resolver.js +255 -8
  186. package/packages/client/dist/resolvers/remote-resolver.js +233 -1
  187. package/packages/client/dist/runner/binary.js +404 -1
  188. package/packages/client/dist/runner/cli.js +1571 -17
  189. package/packages/client/dist/runner/headless/adapters.js +49 -1
  190. package/packages/client/dist/runner/headless/claude-code-adapter.js +79 -1
  191. package/packages/client/dist/runner/headless/continuous-story-workspace.js +1628 -11
  192. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +69 -1
  193. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +110 -2
  194. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +1236 -8
  195. package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +318 -2
  196. package/packages/client/dist/runner/headless/fake-adapter.js +62 -1
  197. package/packages/client/dist/runner/headless/managed-profile.js +132 -1
  198. package/packages/client/dist/runner/headless/opencode-adapter.js +145 -2
  199. package/packages/client/dist/runner/headless/prepared-story-bundle.js +43 -1
  200. package/packages/client/dist/runner/headless/resolver.js +231 -1
  201. package/packages/client/dist/runner/headless/run-yolo.js +885 -1
  202. package/packages/client/dist/runner/headless/run-yoloop.js +2574 -2
  203. package/packages/client/dist/runner/headless/types.js +574 -2
  204. package/packages/client/dist/runner/local-console.js +396 -13
  205. package/packages/client/dist/runner/portal-sync-worker.js +1487 -1
  206. package/packages/client/dist/runner/project-identity.js +106 -1
  207. package/packages/client/dist/runner/scheduler.js +995 -7
  208. package/packages/client/dist/runner-cli.js +18 -2
  209. package/packages/client/dist/state/invocation-shape.js +13 -1
  210. package/packages/client/dist/state/project-state-snapshot.d.ts +8 -0
  211. package/packages/client/dist/state/project-state-snapshot.js +1731 -1
  212. package/packages/client/dist/state/state-json-repair.js +785 -3
  213. package/packages/client/dist/state/structural-dag-projection.js +118 -1
  214. package/packages/client/dist/telemetry/index.js +5 -1
  215. package/packages/client/dist/telemetry/offline-queue.js +131 -1
  216. package/packages/client/dist/tier/index.js +5 -1
  217. package/packages/client/dist/tier/tier-aware-client.js +405 -1
  218. package/packages/client/dist/types/index.js +38 -1
  219. package/packages/client/dist/version.js +27 -1
  220. package/packages/client/dist/yolo/change-evidence-collector.js +126 -1
  221. package/packages/client/dist/yolo/invoke-envelope-capability.js +62 -1
  222. package/packages/client/dist/yolo/spec-envelope-collector.js +184 -5
  223. package/packages/client/dist/yoloop/discovery-hook.js +300 -2
  224. package/packages/client/dist/yoloop/index.js +8 -1
  225. package/packages/client/dist/yoloop/invoke-hooks.js +669 -12
  226. package/packages/client/dist/yoloop/shared-discover-epics.js +1007 -1
  227. package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +65 -1
  228. package/packages/client/dist/yoloop/shared-yoloop-types.js +33 -1
  229. package/packages/client/dist/yoloop/target-normalization.js +180 -1
  230. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +8 -0
  231. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1920 -1
  232. package/packages/shared/dist/yolo/spec-envelope.js +3 -6
  233. package/targets-stubs/antigravity/gemini.md +1 -1
  234. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  235. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  236. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  237. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  238. package/targets-stubs/claude-code/neocortex.md +2 -2
  239. package/targets-stubs/codex/AGENTS.md +1 -1
  240. package/targets-stubs/cursor/agent.md +2 -2
  241. package/targets-stubs/gemini-cli/agent.md +2 -2
  242. package/targets-stubs/opencode/neocortex-root.md +1 -1
  243. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -1,5 +1,462 @@
1
- import{execFileSync as lt,spawnSync as st}from"node:child_process";import{createHash as ht,randomUUID as mt}from"node:crypto";import{closeSync as U,fsyncSync as Z,lstatSync as X,mkdirSync as yt,openSync as W,readFileSync as ut,realpathSync as Y,renameSync as St,unlinkSync as it,writeFileSync as nt}from"node:fs";import{isAbsolute as It,join as N,resolve as K}from"node:path";import{isDeepStrictEqual as G}from"node:util";import{planningPendingSidecarDigest as ct,planningPhysicalPatchDigest as Dt}from"./planning-diagnosis-checkpoint.js";import{acceptedPlanningResearchPredecessor as rt}from"./planning-research-checkpoint.js";const _=/^[a-f0-9]{64}$/,ot=/^P[1-9][0-9]{0,11}$/,at=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/,pt="acd455ed0e39d98b90fb57391db75e366e4c5604db043e03a89a20e33d643c3c",dt="fa2194f89c01f07e225fd6f39e98e679f628e072239b6cd0af3ee527f0722518",gt="8636f22ec5950410902448ee1e9cb4947359ac98df518742bddbbb367468e3ae";function r(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)}function l(t){return ht("sha256").update(t).digest("hex")}function V(t,e,s=128*1024){return lt("git",[...e],{cwd:t,encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:s}).trim()}function q(t,e,s){const i=X(N(t,e));if(!i.isFile()||i.isSymbolicLink()||i.size<1||i.size>s)throw new Error("planning-generation-physical-artifact-invalid");return ut(N(t,e))}function J(t,e,s,i="HEAD"){const c=st("git",["show",`${i}:${e}`],{cwd:t,encoding:"buffer",maxBuffer:s});if(c.status!==0||!Buffer.isBuffer(c.stdout)||c.stdout.length<1||c.stdout.length>s)throw new Error("planning-generation-predecessor-uncommitted");return c.stdout}function v(t,e){for(const s of[".neocortex",".neocortex/planning",`.neocortex/planning/epic-${e}`,"docs","docs/epics","docs/stories"]){const i=X(N(t,s));if(!i.isDirectory()||i.isSymbolicLink()||i.uid!==process.getuid?.()||s===".neocortex"&&(i.mode&18)!==0)throw new Error("planning-generation-directory-boundary-invalid")}}function H(t,e){const i=/^---\n([\s\S]{1,8192}?)\n---\n/.exec(t.replace(/\r\n/g,`
2
- `))?.[1]?.split(`
3
- `).filter(c=>c.startsWith(`${e}:`))??[];return i.length===1?i[0].slice(e.length+1).trim().replace(/^(?:"(.*)"|'(.*)')$/,"$1$2"):void 0}function L(t,e){return typeof t=="string"&&t.length<=128&&l(t)===e}function Q(t,e){return Array.from({length:e},(s,i)=>`docs/stories/${t}.${String(i+1).padStart(2,"0")}.story.md`)}function bt(t){if(!r(t)||Object.keys(t).sort().join("|")!==["schemaVersion","verdict","claimId","epicId","diagnosisReceiptId","researchReceiptId","intakeRevision","sourceHead","sourceBranch","sourceStateSha256","admission","artifactDigests","qaEvidenceId","artifactSummary"].sort().join("|")||t.schemaVersion!=="PlanningGenerationRootReview/1"||t.verdict!=="accepted"||typeof t.claimId!="string"||!at.test(t.claimId)||typeof t.epicId!="string"||!ot.test(t.epicId)||typeof t.diagnosisReceiptId!="string"||!_.test(t.diagnosisReceiptId)||typeof t.researchReceiptId!="string"||!_.test(t.researchReceiptId)||typeof t.intakeRevision!="string"||!_.test(t.intakeRevision)||typeof t.sourceHead!="string"||!/^[a-f0-9]{40}$/.test(t.sourceHead)||typeof t.sourceBranch!="string"||!/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$/.test(t.sourceBranch)||typeof t.sourceStateSha256!="string"||!_.test(t.sourceStateSha256)||!r(t.admission)||Object.keys(t.admission).sort().join("|")!==["schemaVersion","outcome","baseSha","executionBranch","changedPaths","patchSha256"].sort().join("|")||t.admission.schemaVersion!==1||t.admission.outcome!=="admitted-unverified"||t.admission.baseSha!==t.sourceHead||t.admission.executionBranch!==t.sourceBranch||typeof t.admission.patchSha256!="string"||!_.test(t.admission.patchSha256)||!r(t.artifactDigests)||Object.keys(t.artifactDigests).sort().join("|")!=="epic|index|stories"||typeof t.artifactDigests.epic!="string"||!_.test(t.artifactDigests.epic)||typeof t.artifactDigests.index!="string"||!_.test(t.artifactDigests.index)||!r(t.artifactDigests.stories)||Object.keys(t.artifactDigests.stories).length<1||Object.keys(t.artifactDigests.stories).length>24||Object.values(t.artifactDigests.stories).some(s=>typeof s!="string"||!_.test(s))||typeof t.qaEvidenceId!="string"||!/^qa-[a-z0-9-]{8,80}$/.test(t.qaEvidenceId)||typeof t.artifactSummary!="string"||t.artifactSummary.length<16||t.artifactSummary.length>2048)throw new Error("planning-generation-review-invalid");const e=Q(t.epicId,Object.keys(t.artifactDigests.stories).length);if(!G(Object.keys(t.artifactDigests.stories).sort(),[...e].sort()))throw new Error("planning-generation-review-invalid")}function Et(t){const e={schemaVersion:"PlanningGenerationPhysicalAcceptance/1",claimId:t.claimId,epicId:t.epicId,diagnosisReceiptId:t.diagnosisReceiptId,researchReceiptId:t.researchReceiptId,intakeRevision:t.intakeRevision,qaEvidenceId:t.qaEvidenceId,sourceHead:t.sourceHead,sourceStateSha256:t.sourceStateSha256,admissionPatchSha256:t.admission.patchSha256,artifactDigests:t.artifactDigests};return{...e,receiptId:l(JSON.stringify(e)),publicSafe:!0}}function tt(t,e,s,i,c){if(v(t,e.epicId),V(t,["rev-parse","HEAD"])!==e.sourceHead||V(t,["symbolic-ref","--quiet","--short","HEAD"])!==e.sourceBranch)throw new Error("planning-generation-head-conflict");const y=`.neocortex/planning/epic-${e.epicId}`,u=`docs/epics/epic-${e.epicId}.md`,o=".neocortex/planning/planning.json",h=Q(e.epicId,Object.keys(e.artifactDigests.stories).length),n={index:o,epic:u,...Object.fromEntries(h.map((m,f)=>[`story${f}`,m]))};if(!Array.isArray(e.admission.changedPaths)||!G([...e.admission.changedPaths].sort(),Object.values(n).sort())||s&&Dt(t,n,i,!0)!==e.admission.patchSha256)throw new Error("planning-generation-admission-patch-conflict");const B=q(t,o,128*1024),R=q(t,u,512*1024),S=h.map(m=>q(t,m,128*1024));if(l(B)!==e.artifactDigests.index||l(R)!==e.artifactDigests.epic||h.some((m,f)=>l(S[f])!==e.artifactDigests.stories[m]))throw new Error("planning-generation-artifact-conflict");const $=R.toString("utf8"),T=H($,"title");if(H($,"id")!==e.epicId||H($,"stories_count")!==String(h.length)||!T||T.length>200||!$.includes("## Stories"))throw new Error("planning-generation-epic-invalid");for(const[m,f]of S.entries()){const d=f.toString("utf8"),D=/^---\n([\s\S]{1,8192}?)\n---\n/.exec(d.replace(/\r\n/g,`
4
- `))?.[1]??"",C=H(d,"epic_id"),g=C===e.epicId&&!/^\s*epic\s*:/m.test(D)||!s&&C===void 0&&!/^\s*epic_id\s*:/m.test(D)&&H(d,"epic")===e.epicId;if(H(d,"id")!==`${e.epicId}.${String(m+1).padStart(2,"0")}`||!g||H(d,"status")!=="backlog"||!H(d,"title")||!d.includes("## Acceptance Criteria")||!/\bGiven\b[\s\S]*\bWhen\b[\s\S]*\bThen\b/.test(d))throw new Error("planning-generation-story-invalid")}const b=c??q(t,".neocortex/state.json",8*1024*1024),a=JSON.parse(b.toString("utf8"));if(!r(a)||!r(a.epics)||!r(a.stories))throw new Error("planning-generation-state-invalid");const E=rt(a,e.claimId,e.epicId);if(E.diagnosisReceiptId!==e.diagnosisReceiptId||E.researchReceiptId!==e.researchReceiptId||E.intakeRevision!==e.intakeRevision||s&&l(b)!==e.sourceStateSha256)throw new Error("planning-generation-state-conflict");const k=a.epics[e.epicId];if(!r(k)||h.some((m,f)=>Object.hasOwn(a.stories,`${e.epicId}.${String(f+1).padStart(2,"0")}`)))throw new Error("planning-generation-story-collision");const P=k.createEpicPlanningPhysical,x=k.createEpicPlanningResearchPhysical;if(!r(P)||!r(x)||!r(P.artifactDigests)||!r(x.artifactDigests))throw new Error("planning-generation-predecessor-invalid");for(const[m,f]of Object.entries({idea:`${y}/idea.json`,diagnosis:`${y}/diagnosis.md`,enrichment:`${y}/enriched-prompt.md`,research:`${y}/research.md`})){const d=m==="research"?x.artifactDigests.research:P.artifactDigests[m],D=q(t,f,m==="idea"?32*1024:256*1024);if(l(D)!==d||!D.equals(J(t,f,512*1024)))throw new Error("planning-generation-predecessor-artifact-drift")}const j=J(t,o,128*1024);if(l(j)!==x.artifactDigests.index)throw new Error("planning-generation-predecessor-index-drift");const A=JSON.parse(j.toString("utf8")),w=JSON.parse(B.toString("utf8"));if(!r(A)||!r(A.epics)||!r(w)||!r(w.epics))throw new Error("planning-generation-index-invalid");const p=A.epics[e.epicId],I=w.epics[e.epicId];if(!r(p)||p.id!==e.epicId||p.status!=="researched"||!Array.isArray(p.steps_completed)||p.steps_completed.length!==2||!L(p.steps_completed[0],pt)||!L(p.steps_completed[1],dt)||!G(p.output_files,{diagnosis:`${y}/diagnosis.md`,research:`${y}/research.md`})||!r(I)||!Array.isArray(I.steps_completed)||!L(I.steps_completed[2],gt))throw new Error("planning-generation-index-predecessor-invalid");const O={...A,last_scan:w.last_scan,epics:{...A.epics,[e.epicId]:{...p,status:"generated",steps_completed:[...p.steps_completed,I.steps_completed[2]],completed_at:I.completed_at,output_files:{...p.output_files,epic:u,stories:h}}}};if(typeof w.last_scan!="string"||!Number.isFinite(Date.parse(w.last_scan))||typeof I.completed_at!="string"||!Number.isFinite(Date.parse(I.completed_at))||!G(w,O))throw new Error("planning-generation-index-transition-invalid");return{stateRaw:b,state:a,title:T,stories:h,storyBytes:S,completedAt:I.completed_at}}function et(t,e,s){const i=t.state,c=t.stories.map(o=>o.slice(13,-9)),y=i.epics[e.epicId],u={...i.stories};for(const[o,h]of c.entries())u[h]={id:h,epic_id:e.epicId,title:H(t.storyBytes[o].toString("utf8"),"title"),status:"backlog"};return{...i,epics:{...i.epics,[e.epicId]:{...y,title:t.title,status:"generated",stories:c,total_stories:c.length,completed_stories:0,createEpicPlanningGenerationPhysical:s}},stories:u,last_updated:t.completedAt}}function kt(t,e,s){const i=()=>new Error("planning-generation-committed-evidence-invalid"),c=K(t);if(!at.test(e)||!ot.test(s)||Y(c)!==c||V(c,["rev-parse","--show-toplevel"])!==c)throw i();v(c,s);const y=V(c,["rev-parse","HEAD"]),u=q(c,".neocortex/state.json",8*1024*1024);if(!u.equals(J(c,".neocortex/state.json",8*1024*1024)))throw i();const o=JSON.parse(u.toString("utf8")),h=r(o)&&r(o.epics)?o.epics[s]:void 0,n=r(h)?h.createEpicPlanningGenerationPhysical:void 0;if(!r(n)||n.schemaVersion!=="PlanningGenerationPhysicalAcceptance/1"||n.claimId!==e||n.epicId!==s||typeof n.sourceHead!="string"||!/^[a-f0-9]{40}$/.test(n.sourceHead)||typeof n.qaEvidenceId!="string"||!/^qa-[a-z0-9-]{8,80}$/.test(n.qaEvidenceId)||["diagnosisReceiptId","researchReceiptId","intakeRevision","sourceStateSha256","admissionPatchSha256"].some(g=>typeof n[g]!="string"||!_.test(n[g]))||!r(n.artifactDigests)||Object.keys(n.artifactDigests).sort().join("|")!=="epic|index|stories"||typeof n.artifactDigests.epic!="string"||!_.test(n.artifactDigests.epic)||typeof n.artifactDigests.index!="string"||!_.test(n.artifactDigests.index)||!r(n.artifactDigests.stories))throw i();const B=Object.keys(n.artifactDigests.stories).length,R=Q(s,B);if(B<1||B>24||!G(Object.keys(n.artifactDigests.stories).sort(),[...R].sort())||Object.values(n.artifactDigests.stories).some(g=>typeof g!="string"||!_.test(g)))throw i();const S={schemaVersion:"PlanningGenerationPhysicalAcceptance/1",claimId:e,epicId:s,diagnosisReceiptId:n.diagnosisReceiptId,researchReceiptId:n.researchReceiptId,intakeRevision:n.intakeRevision,qaEvidenceId:n.qaEvidenceId,sourceHead:n.sourceHead,sourceStateSha256:n.sourceStateSha256,admissionPatchSha256:n.admissionPatchSha256,artifactDigests:n.artifactDigests},$={...S,receiptId:l(JSON.stringify(S)),publicSafe:!0};if(!G(n,$)||st("git",["merge-base","--is-ancestor",n.sourceHead,"HEAD"],{cwd:c,stdio:"ignore"}).status!==0)throw i();const b=J(c,".neocortex/state.json",8*1024*1024,n.sourceHead);if(l(b)!==n.sourceStateSha256)throw i();const a=JSON.parse(b.toString("utf8")),E=rt(a,e,s);if(E.diagnosisReceiptId!==n.diagnosisReceiptId||E.researchReceiptId!==n.researchReceiptId||E.intakeRevision!==n.intakeRevision||!r(a)||!r(a.epics)||!r(a.epics[s]))throw i();const k=a.epics[s],P=k.createEpicPlanningPhysical,x=k.createEpicPlanningResearchPhysical;if(!r(P)||!r(P.artifactDigests)||!r(x)||!r(x.artifactDigests))throw i();const j=`.neocortex/planning/epic-${s}`;for(const[g,M,z]of[["idea",`${j}/idea.json`,32*1024],["diagnosis",`${j}/diagnosis.md`,256*1024],["enrichment",`${j}/enriched-prompt.md`,256*1024],["research",`${j}/research.md`,256*1024]]){const F=q(c,M,z),ft=g==="research"?x.artifactDigests.research:P.artifactDigests[g];if(l(F)!==ft||!F.equals(J(c,M,z))||!F.equals(J(c,M,z,n.sourceHead)))throw i()}const A=(g,M,z)=>{const F=q(c,g,z);if(l(F)!==M||!F.equals(J(c,g,z)))throw i();return F},w=A(`docs/epics/epic-${s}.md`,S.artifactDigests.epic,512*1024),p=R.map(g=>A(g,S.artifactDigests.stories[g],128*1024)),I=".neocortex/planning/planning.json",O=JSON.parse(A(I,S.artifactDigests.index,128*1024).toString("utf8")),m=J(c,I,128*1024,n.sourceHead);if(l(m)!==x.artifactDigests.index)throw i();const f=JSON.parse(m.toString("utf8"));if(!r(O)||!r(O.epics)||!r(O.epics[s])||!r(f)||!r(f.epics)||!r(f.epics[s]))throw i();const d=O.epics[s],D=f.epics[s];if(!Array.isArray(D.steps_completed)||D.steps_completed.length!==2||!L(D.steps_completed[0],pt)||!L(D.steps_completed[1],dt)||!Array.isArray(d.steps_completed)||!L(d.steps_completed[2],gt)||typeof d.completed_at!="string"||!Number.isFinite(Date.parse(d.completed_at))||typeof O.last_scan!="string"||!Number.isFinite(Date.parse(O.last_scan))||!G(O,{...f,last_scan:O.last_scan,epics:{...f.epics,[s]:{...D,status:"generated",steps_completed:[...D.steps_completed,d.steps_completed[2]],completed_at:d.completed_at,output_files:{...D.output_files,epic:`docs/epics/epic-${s}.md`,stories:R}}}}))throw i();const C=H(w.toString("utf8"),"title");if(!C||!G(o,et({stateRaw:b,state:a,title:C,stories:R,storyBytes:p,completedAt:d.completed_at},{epicId:s},$))||V(c,["rev-parse","HEAD"])!==y||!q(c,".neocortex/state.json",8*1024*1024).equals(u))throw i();return{epicId:s,physicalAcceptanceReceiptId:$.receiptId,storyIds:R.map(g=>g.slice(13,-9)),verifiedHead:y,stateSha256:l(u),publicSafe:!0}}function At(t,e){bt(e);const s=e,i=K(t);if(Y(i)!==i||V(i,["rev-parse","--show-toplevel"])!==i)throw new Error("planning-generation-root-invalid");v(i,s.epicId);const c=N(i,".neocortex"),y=N(c,"state.json.planning.lock"),u=W(y,"wx",384);let o;try{const h=ct(i),n=Et(s),B=Q(s.epicId,Object.keys(s.artifactDigests.stories).length).map(p=>p.slice(13,-9)),R=q(i,".neocortex/state.json",8*1024*1024);if(l(R)!==s.sourceStateSha256){const p=J(i,".neocortex/state.json",8388608);if(l(p)!==s.sourceStateSha256)throw new Error("planning-generation-replay-conflict");const I=tt(i,s,!1,void 0,p);if(!G(JSON.parse(R.toString("utf8")),et(I,s,n)))throw new Error("planning-generation-replay-conflict");return{epicId:s.epicId,physicalAcceptanceReceiptId:n.receiptId,qaEvidenceId:s.qaEvidenceId,storyIds:B}}const S=tt(i,s,!0),$=et(S,s,n),T=V(i,["rev-parse","--git-common-dir"]),b=It(T)?K(T):K(i,T);if(Y(b)!==b||X(b).isSymbolicLink())throw new Error("planning-generation-backup-dir-invalid");const a=N(b,"neocortex-planning");yt(a,{recursive:!0,mode:448});const E=X(a);if(!E.isDirectory()||E.isSymbolicLink()||E.uid!==process.getuid?.()||(E.mode&63)!==0)throw new Error("planning-generation-backup-dir-invalid");const k=mt(),P=W(N(a,`state-backup-${k}.json`),"wx",384);try{nt(P,S.stateRaw),Z(P)}finally{U(P)}const x=W(a,"r");try{Z(x)}finally{U(x)}o=N(c,`state.json.planning-tmp-${k}`);const j=W(o,"wx",384);try{nt(j,`${JSON.stringify($,null,2)}
5
- `),Z(j)}finally{U(j)}if(!tt(i,s,!0,`.neocortex/state.json.planning-tmp-${k}`).stateRaw.equals(S.stateRaw))throw new Error("planning-generation-state-conflict");if(ct(i)!==h)throw new Error("planning-generation-pending-conflict");St(o,N(c,"state.json")),o=void 0;const w=W(c,"r");try{Z(w)}finally{U(w)}return{epicId:s.epicId,physicalAcceptanceReceiptId:n.receiptId,qaEvidenceId:s.qaEvidenceId,storyIds:B}}finally{if(o)try{it(o)}catch{}U(u),it(y)}}export{At as sealPlanningGeneration,kt as verifyCommittedPlanningGeneration};
1
+ /** Root-owned P03 transition after review of an isolated, admitted generation candidate. */
2
+ import { execFileSync, spawnSync } from 'node:child_process';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ import { closeSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
5
+ import { isAbsolute, join, resolve } from 'node:path';
6
+ import { isDeepStrictEqual } from 'node:util';
7
+ import { planningPendingSidecarDigest, planningPhysicalPatchDigest } from './planning-diagnosis-checkpoint.js';
8
+ import { acceptedPlanningResearchPredecessor } from './planning-research-checkpoint.js';
9
+ const HEX = /^[a-f0-9]{64}$/;
10
+ const EPIC = /^P[1-9][0-9]{0,11}$/;
11
+ const CLAIM = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
12
+ const P01_STEP_DIGEST = 'acd455ed0e39d98b90fb57391db75e366e4c5604db043e03a89a20e33d643c3c';
13
+ const P02_STEP_DIGEST = 'fa2194f89c01f07e225fd6f39e98e679f628e072239b6cd0af3ee527f0722518';
14
+ const P03_STEP_DIGEST = '8636f22ec5950410902448ee1e9cb4947359ac98df518742bddbbb367468e3ae';
15
+ function record(value) {
16
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
17
+ }
18
+ function digest(value) {
19
+ return createHash('sha256').update(value).digest('hex');
20
+ }
21
+ function git(root, args, maxBuffer = 128 * 1024) {
22
+ return execFileSync('git', [...args], { cwd: root, encoding: 'utf8',
23
+ stdio: ['ignore', 'pipe', 'ignore'], maxBuffer }).trim();
24
+ }
25
+ function regularBytes(root, path, maxBytes) {
26
+ const entry = lstatSync(join(root, path));
27
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size < 1 || entry.size > maxBytes) {
28
+ throw new Error('planning-generation-physical-artifact-invalid');
29
+ }
30
+ return readFileSync(join(root, path));
31
+ }
32
+ function committedBytes(root, path, maxBytes, revision = 'HEAD') {
33
+ const result = spawnSync('git', ['show', `${revision}:${path}`], { cwd: root,
34
+ encoding: 'buffer', maxBuffer: maxBytes });
35
+ if (result.status !== 0 || !Buffer.isBuffer(result.stdout)
36
+ || result.stdout.length < 1 || result.stdout.length > maxBytes) {
37
+ throw new Error('planning-generation-predecessor-uncommitted');
38
+ }
39
+ return result.stdout;
40
+ }
41
+ function safeParents(root, epicId) {
42
+ for (const relativePath of ['.neocortex', '.neocortex/planning',
43
+ `.neocortex/planning/epic-${epicId}`, 'docs', 'docs/epics', 'docs/stories']) {
44
+ const entry = lstatSync(join(root, relativePath));
45
+ if (!entry.isDirectory() || entry.isSymbolicLink()
46
+ || entry.uid !== process.getuid?.()
47
+ || relativePath === '.neocortex' && (entry.mode & 0o022) !== 0) {
48
+ throw new Error('planning-generation-directory-boundary-invalid');
49
+ }
50
+ }
51
+ }
52
+ function scalar(content, key) {
53
+ const header = /^---\n([\s\S]{1,8192}?)\n---\n/.exec(content.replace(/\r\n/g, '\n'))?.[1];
54
+ const fields = header?.split('\n').filter((line) => line.startsWith(`${key}:`)) ?? [];
55
+ return fields.length === 1 ? fields[0].slice(key.length + 1).trim()
56
+ .replace(/^(?:"(.*)"|'(.*)')$/, '$1$2') : undefined;
57
+ }
58
+ function stepMatches(value, expected) {
59
+ return typeof value === 'string' && value.length <= 128 && digest(value) === expected;
60
+ }
61
+ function storyPaths(epicId, count) {
62
+ return Array.from({ length: count }, (_, index) => `docs/stories/${epicId}.${String(index + 1).padStart(2, '0')}.story.md`);
63
+ }
64
+ function validateReview(value) {
65
+ if (!record(value) || Object.keys(value).sort().join('|') !== [
66
+ 'schemaVersion', 'verdict', 'claimId', 'epicId', 'diagnosisReceiptId',
67
+ 'researchReceiptId', 'intakeRevision', 'sourceHead', 'sourceBranch',
68
+ 'sourceStateSha256', 'admission', 'artifactDigests', 'qaEvidenceId', 'artifactSummary',
69
+ ].sort().join('|') || value.schemaVersion !== 'PlanningGenerationRootReview/1'
70
+ || value.verdict !== 'accepted' || typeof value.claimId !== 'string' || !CLAIM.test(value.claimId)
71
+ || typeof value.epicId !== 'string' || !EPIC.test(value.epicId)
72
+ || typeof value.diagnosisReceiptId !== 'string' || !HEX.test(value.diagnosisReceiptId)
73
+ || typeof value.researchReceiptId !== 'string' || !HEX.test(value.researchReceiptId)
74
+ || typeof value.intakeRevision !== 'string' || !HEX.test(value.intakeRevision)
75
+ || typeof value.sourceHead !== 'string' || !/^[a-f0-9]{40}$/.test(value.sourceHead)
76
+ || typeof value.sourceBranch !== 'string'
77
+ || !/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$/.test(value.sourceBranch)
78
+ || typeof value.sourceStateSha256 !== 'string' || !HEX.test(value.sourceStateSha256)
79
+ || !record(value.admission) || Object.keys(value.admission).sort().join('|') !== [
80
+ 'schemaVersion', 'outcome', 'baseSha', 'executionBranch', 'changedPaths', 'patchSha256',
81
+ ].sort().join('|') || value.admission.schemaVersion !== 1
82
+ || value.admission.outcome !== 'admitted-unverified'
83
+ || value.admission.baseSha !== value.sourceHead
84
+ || value.admission.executionBranch !== value.sourceBranch
85
+ || typeof value.admission.patchSha256 !== 'string' || !HEX.test(value.admission.patchSha256)
86
+ || !record(value.artifactDigests)
87
+ || Object.keys(value.artifactDigests).sort().join('|') !== 'epic|index|stories'
88
+ || typeof value.artifactDigests.epic !== 'string' || !HEX.test(value.artifactDigests.epic)
89
+ || typeof value.artifactDigests.index !== 'string' || !HEX.test(value.artifactDigests.index)
90
+ || !record(value.artifactDigests.stories)
91
+ || Object.keys(value.artifactDigests.stories).length < 1
92
+ || Object.keys(value.artifactDigests.stories).length > 24
93
+ || Object.values(value.artifactDigests.stories).some((entry) => typeof entry !== 'string' || !HEX.test(entry))
94
+ || typeof value.qaEvidenceId !== 'string' || !/^qa-[a-z0-9-]{8,80}$/.test(value.qaEvidenceId)
95
+ || typeof value.artifactSummary !== 'string' || value.artifactSummary.length < 16
96
+ || value.artifactSummary.length > 2048) {
97
+ throw new Error('planning-generation-review-invalid');
98
+ }
99
+ const stories = storyPaths(value.epicId, Object.keys(value.artifactDigests.stories).length);
100
+ if (!isDeepStrictEqual(Object.keys(value.artifactDigests.stories).sort(), [...stories].sort())) {
101
+ throw new Error('planning-generation-review-invalid');
102
+ }
103
+ }
104
+ function physicalAcceptance(review) {
105
+ const fields = { schemaVersion: 'PlanningGenerationPhysicalAcceptance/1',
106
+ claimId: review.claimId, epicId: review.epicId,
107
+ diagnosisReceiptId: review.diagnosisReceiptId, researchReceiptId: review.researchReceiptId,
108
+ intakeRevision: review.intakeRevision, qaEvidenceId: review.qaEvidenceId,
109
+ sourceHead: review.sourceHead, sourceStateSha256: review.sourceStateSha256,
110
+ admissionPatchSha256: review.admission.patchSha256,
111
+ artifactDigests: review.artifactDigests };
112
+ return { ...fields, receiptId: digest(JSON.stringify(fields)), publicSafe: true };
113
+ }
114
+ function evidence(root, review, checkPatch, ownTemporaryPath, sourceStateOverride) {
115
+ safeParents(root, review.epicId);
116
+ if (git(root, ['rev-parse', 'HEAD']) !== review.sourceHead
117
+ || git(root, ['symbolic-ref', '--quiet', '--short', 'HEAD']) !== review.sourceBranch) {
118
+ throw new Error('planning-generation-head-conflict');
119
+ }
120
+ const prefix = `.neocortex/planning/epic-${review.epicId}`;
121
+ const epicPath = `docs/epics/epic-${review.epicId}.md`;
122
+ const indexPath = '.neocortex/planning/planning.json';
123
+ const stories = storyPaths(review.epicId, Object.keys(review.artifactDigests.stories).length);
124
+ const allowed = { index: indexPath, epic: epicPath,
125
+ ...Object.fromEntries(stories.map((path, index) => [`story${index}`, path])) };
126
+ if (!Array.isArray(review.admission.changedPaths)
127
+ || !isDeepStrictEqual([...review.admission.changedPaths].sort(), Object.values(allowed).sort())
128
+ || checkPatch && planningPhysicalPatchDigest(root, allowed, ownTemporaryPath, true)
129
+ !== review.admission.patchSha256) {
130
+ throw new Error('planning-generation-admission-patch-conflict');
131
+ }
132
+ const indexBytes = regularBytes(root, indexPath, 128 * 1024);
133
+ const epicBytes = regularBytes(root, epicPath, 512 * 1024);
134
+ const storyBytes = stories.map((path) => regularBytes(root, path, 128 * 1024));
135
+ if (digest(indexBytes) !== review.artifactDigests.index
136
+ || digest(epicBytes) !== review.artifactDigests.epic
137
+ || stories.some((path, index) => digest(storyBytes[index]) !== review.artifactDigests.stories[path])) {
138
+ throw new Error('planning-generation-artifact-conflict');
139
+ }
140
+ const epicText = epicBytes.toString('utf8');
141
+ const epicTitle = scalar(epicText, 'title');
142
+ if (scalar(epicText, 'id') !== review.epicId
143
+ || scalar(epicText, 'stories_count') !== String(stories.length)
144
+ || !epicTitle || epicTitle.length > 200 || !epicText.includes('## Stories')) {
145
+ throw new Error('planning-generation-epic-invalid');
146
+ }
147
+ for (const [index, bytes] of storyBytes.entries()) {
148
+ const text = bytes.toString('utf8');
149
+ const header = /^---\n([\s\S]{1,8192}?)\n---\n/.exec(text.replace(/\r\n/g, '\n'))?.[1] ?? '';
150
+ const canonicalEpic = scalar(text, 'epic_id');
151
+ // Old accepted receipts keep their original bytes on replay. New seals
152
+ // require the canonical field consumed by story-context collection.
153
+ const epicMatches = canonicalEpic === review.epicId && !/^\s*epic\s*:/m.test(header)
154
+ || !checkPatch && canonicalEpic === undefined && !/^\s*epic_id\s*:/m.test(header)
155
+ && scalar(text, 'epic') === review.epicId;
156
+ if (scalar(text, 'id') !== `${review.epicId}.${String(index + 1).padStart(2, '0')}`
157
+ || !epicMatches || scalar(text, 'status') !== 'backlog'
158
+ || !scalar(text, 'title') || !text.includes('## Acceptance Criteria')
159
+ || !/\bGiven\b[\s\S]*\bWhen\b[\s\S]*\bThen\b/.test(text)) {
160
+ throw new Error('planning-generation-story-invalid');
161
+ }
162
+ }
163
+ const stateRaw = sourceStateOverride ?? regularBytes(root, '.neocortex/state.json', 8 * 1024 * 1024);
164
+ const state = JSON.parse(stateRaw.toString('utf8'));
165
+ if (!record(state) || !record(state.epics) || !record(state.stories)) {
166
+ throw new Error('planning-generation-state-invalid');
167
+ }
168
+ const predecessor = acceptedPlanningResearchPredecessor(state, review.claimId, review.epicId);
169
+ if (predecessor.diagnosisReceiptId !== review.diagnosisReceiptId
170
+ || predecessor.researchReceiptId !== review.researchReceiptId
171
+ || predecessor.intakeRevision !== review.intakeRevision
172
+ || checkPatch && digest(stateRaw) !== review.sourceStateSha256) {
173
+ throw new Error('planning-generation-state-conflict');
174
+ }
175
+ const priorEpic = state.epics[review.epicId];
176
+ if (!record(priorEpic) || stories.some((path, index) => Object.hasOwn(state.stories, `${review.epicId}.${String(index + 1).padStart(2, '0')}`))) {
177
+ throw new Error('planning-generation-story-collision');
178
+ }
179
+ const p01 = priorEpic.createEpicPlanningPhysical;
180
+ const p02 = priorEpic.createEpicPlanningResearchPhysical;
181
+ if (!record(p01) || !record(p02) || !record(p01.artifactDigests) || !record(p02.artifactDigests)) {
182
+ throw new Error('planning-generation-predecessor-invalid');
183
+ }
184
+ for (const [name, path] of Object.entries({ idea: `${prefix}/idea.json`,
185
+ diagnosis: `${prefix}/diagnosis.md`, enrichment: `${prefix}/enriched-prompt.md`,
186
+ research: `${prefix}/research.md` })) {
187
+ const expected = name === 'research' ? p02.artifactDigests.research
188
+ : p01.artifactDigests[name];
189
+ const current = regularBytes(root, path, name === 'idea' ? 32 * 1024 : 256 * 1024);
190
+ if (digest(current) !== expected || !current.equals(committedBytes(root, path, 512 * 1024))) {
191
+ throw new Error('planning-generation-predecessor-artifact-drift');
192
+ }
193
+ }
194
+ const baseline = committedBytes(root, indexPath, 128 * 1024);
195
+ if (digest(baseline) !== p02.artifactDigests.index) {
196
+ throw new Error('planning-generation-predecessor-index-drift');
197
+ }
198
+ const prior = JSON.parse(baseline.toString('utf8'));
199
+ const next = JSON.parse(indexBytes.toString('utf8'));
200
+ if (!record(prior) || !record(prior.epics) || !record(next) || !record(next.epics)) {
201
+ throw new Error('planning-generation-index-invalid');
202
+ }
203
+ const previous = prior.epics[review.epicId];
204
+ const generated = next.epics[review.epicId];
205
+ if (!record(previous) || previous.id !== review.epicId || previous.status !== 'researched'
206
+ || !Array.isArray(previous.steps_completed) || previous.steps_completed.length !== 2
207
+ || !stepMatches(previous.steps_completed[0], P01_STEP_DIGEST)
208
+ || !stepMatches(previous.steps_completed[1], P02_STEP_DIGEST)
209
+ || !isDeepStrictEqual(previous.output_files, { diagnosis: `${prefix}/diagnosis.md`,
210
+ research: `${prefix}/research.md` }) || !record(generated)
211
+ || !Array.isArray(generated.steps_completed)
212
+ || !stepMatches(generated.steps_completed[2], P03_STEP_DIGEST)) {
213
+ throw new Error('planning-generation-index-predecessor-invalid');
214
+ }
215
+ const expected = { ...prior, last_scan: next.last_scan, epics: { ...prior.epics,
216
+ [review.epicId]: { ...previous, status: 'generated',
217
+ steps_completed: [...previous.steps_completed, generated.steps_completed[2]],
218
+ completed_at: generated.completed_at,
219
+ output_files: { ...previous.output_files,
220
+ epic: epicPath, stories } } } };
221
+ if (typeof next.last_scan !== 'string' || !Number.isFinite(Date.parse(next.last_scan))
222
+ || typeof generated.completed_at !== 'string'
223
+ || !Number.isFinite(Date.parse(generated.completed_at))
224
+ || !isDeepStrictEqual(next, expected)) {
225
+ throw new Error('planning-generation-index-transition-invalid');
226
+ }
227
+ return { stateRaw, state, title: epicTitle, stories, storyBytes,
228
+ completedAt: generated.completed_at };
229
+ }
230
+ function projectState(before, review, receipt) {
231
+ const state = before.state;
232
+ const storyIds = before.stories.map((path) => path.slice('docs/stories/'.length, -'.story.md'.length));
233
+ const epic = state.epics[review.epicId];
234
+ const stories = { ...state.stories };
235
+ for (const [index, id] of storyIds.entries()) {
236
+ stories[id] = { id, epic_id: review.epicId,
237
+ title: scalar(before.storyBytes[index].toString('utf8'), 'title'), status: 'backlog' };
238
+ }
239
+ return { ...state, epics: { ...state.epics, [review.epicId]: { ...epic,
240
+ title: before.title, status: 'generated', stories: storyIds,
241
+ total_stories: storyIds.length, completed_stories: 0,
242
+ createEpicPlanningGenerationPhysical: receipt } },
243
+ stories, last_updated: before.completedAt };
244
+ }
245
+ /**
246
+ * Reopen an already sealed and committed P03 before root memory work. This is
247
+ * physical consistency verification, not new QA, a claim or terminal authority.
248
+ * The sealer intentionally stays bound to its original pre-commit HEAD.
249
+ */
250
+ export function verifyCommittedPlanningGeneration(projectRoot, claimId, epicId) {
251
+ const invalid = () => new Error('planning-generation-committed-evidence-invalid');
252
+ const root = resolve(projectRoot);
253
+ if (!CLAIM.test(claimId) || !EPIC.test(epicId) || realpathSync(root) !== root
254
+ || git(root, ['rev-parse', '--show-toplevel']) !== root)
255
+ throw invalid();
256
+ safeParents(root, epicId);
257
+ const verifiedHead = git(root, ['rev-parse', 'HEAD']);
258
+ const stateRaw = regularBytes(root, '.neocortex/state.json', 8 * 1024 * 1024);
259
+ if (!stateRaw.equals(committedBytes(root, '.neocortex/state.json', 8 * 1024 * 1024)))
260
+ throw invalid();
261
+ const current = JSON.parse(stateRaw.toString('utf8'));
262
+ const epic = record(current) && record(current.epics) ? current.epics[epicId] : undefined;
263
+ const stored = record(epic) ? epic.createEpicPlanningGenerationPhysical : undefined;
264
+ if (!record(stored) || stored.schemaVersion !== 'PlanningGenerationPhysicalAcceptance/1'
265
+ || stored.claimId !== claimId || stored.epicId !== epicId
266
+ || typeof stored.sourceHead !== 'string' || !/^[a-f0-9]{40}$/.test(stored.sourceHead)
267
+ || typeof stored.qaEvidenceId !== 'string' || !/^qa-[a-z0-9-]{8,80}$/.test(stored.qaEvidenceId)
268
+ || ['diagnosisReceiptId', 'researchReceiptId', 'intakeRevision', 'sourceStateSha256',
269
+ 'admissionPatchSha256'].some(key => typeof stored[key] !== 'string' || !HEX.test(stored[key]))
270
+ || !record(stored.artifactDigests)
271
+ || Object.keys(stored.artifactDigests).sort().join('|') !== 'epic|index|stories'
272
+ || typeof stored.artifactDigests.epic !== 'string' || !HEX.test(stored.artifactDigests.epic)
273
+ || typeof stored.artifactDigests.index !== 'string' || !HEX.test(stored.artifactDigests.index)
274
+ || !record(stored.artifactDigests.stories))
275
+ throw invalid();
276
+ const count = Object.keys(stored.artifactDigests.stories).length;
277
+ const stories = storyPaths(epicId, count);
278
+ if (count < 1 || count > 24
279
+ || !isDeepStrictEqual(Object.keys(stored.artifactDigests.stories).sort(), [...stories].sort())
280
+ || Object.values(stored.artifactDigests.stories).some(value => typeof value !== 'string' || !HEX.test(value))) {
281
+ throw invalid();
282
+ }
283
+ const fields = { schemaVersion: 'PlanningGenerationPhysicalAcceptance/1',
284
+ claimId, epicId, diagnosisReceiptId: stored.diagnosisReceiptId,
285
+ researchReceiptId: stored.researchReceiptId, intakeRevision: stored.intakeRevision,
286
+ qaEvidenceId: stored.qaEvidenceId, sourceHead: stored.sourceHead,
287
+ sourceStateSha256: stored.sourceStateSha256,
288
+ admissionPatchSha256: stored.admissionPatchSha256,
289
+ // The original sealer hashes this closed object's reviewed insertion order.
290
+ artifactDigests: stored.artifactDigests };
291
+ const receipt = { ...fields, receiptId: digest(JSON.stringify(fields)), publicSafe: true };
292
+ if (!isDeepStrictEqual(stored, receipt))
293
+ throw invalid();
294
+ const ancestry = spawnSync('git', ['merge-base', '--is-ancestor', stored.sourceHead, 'HEAD'], { cwd: root, stdio: 'ignore' });
295
+ if (ancestry.status !== 0)
296
+ throw invalid();
297
+ const baselineRaw = committedBytes(root, '.neocortex/state.json', 8 * 1024 * 1024, stored.sourceHead);
298
+ if (digest(baselineRaw) !== stored.sourceStateSha256)
299
+ throw invalid();
300
+ const baseline = JSON.parse(baselineRaw.toString('utf8'));
301
+ const predecessor = acceptedPlanningResearchPredecessor(baseline, claimId, epicId);
302
+ if (predecessor.diagnosisReceiptId !== stored.diagnosisReceiptId
303
+ || predecessor.researchReceiptId !== stored.researchReceiptId
304
+ || predecessor.intakeRevision !== stored.intakeRevision || !record(baseline)
305
+ || !record(baseline.epics) || !record(baseline.epics[epicId]))
306
+ throw invalid();
307
+ const baselineEpic = baseline.epics[epicId];
308
+ const p01 = baselineEpic.createEpicPlanningPhysical;
309
+ const p02 = baselineEpic.createEpicPlanningResearchPhysical;
310
+ if (!record(p01) || !record(p01.artifactDigests) || !record(p02) || !record(p02.artifactDigests))
311
+ throw invalid();
312
+ const prefix = `.neocortex/planning/epic-${epicId}`;
313
+ for (const [name, path, limit] of [['idea', `${prefix}/idea.json`, 32 * 1024],
314
+ ['diagnosis', `${prefix}/diagnosis.md`, 256 * 1024],
315
+ ['enrichment', `${prefix}/enriched-prompt.md`, 256 * 1024],
316
+ ['research', `${prefix}/research.md`, 256 * 1024]]) {
317
+ const bytes = regularBytes(root, path, limit);
318
+ const expected = name === 'research' ? p02.artifactDigests.research : p01.artifactDigests[name];
319
+ if (digest(bytes) !== expected || !bytes.equals(committedBytes(root, path, limit))
320
+ || !bytes.equals(committedBytes(root, path, limit, stored.sourceHead)))
321
+ throw invalid();
322
+ }
323
+ const readAccepted = (path, expected, limit) => {
324
+ const bytes = regularBytes(root, path, limit);
325
+ if (digest(bytes) !== expected || !bytes.equals(committedBytes(root, path, limit)))
326
+ throw invalid();
327
+ return bytes;
328
+ };
329
+ const epicBytes = readAccepted(`docs/epics/epic-${epicId}.md`, fields.artifactDigests.epic, 512 * 1024);
330
+ const storyBytes = stories.map(path => readAccepted(path, fields.artifactDigests.stories[path], 128 * 1024));
331
+ const indexPath = '.neocortex/planning/planning.json';
332
+ const index = JSON.parse(readAccepted(indexPath, fields.artifactDigests.index, 128 * 1024).toString('utf8'));
333
+ const priorIndexRaw = committedBytes(root, indexPath, 128 * 1024, stored.sourceHead);
334
+ if (digest(priorIndexRaw) !== p02.artifactDigests.index)
335
+ throw invalid();
336
+ const priorIndex = JSON.parse(priorIndexRaw.toString('utf8'));
337
+ if (!record(index) || !record(index.epics) || !record(index.epics[epicId])
338
+ || !record(priorIndex) || !record(priorIndex.epics) || !record(priorIndex.epics[epicId]))
339
+ throw invalid();
340
+ const entry = index.epics[epicId];
341
+ const priorEntry = priorIndex.epics[epicId];
342
+ if (!Array.isArray(priorEntry.steps_completed) || priorEntry.steps_completed.length !== 2
343
+ || !stepMatches(priorEntry.steps_completed[0], P01_STEP_DIGEST)
344
+ || !stepMatches(priorEntry.steps_completed[1], P02_STEP_DIGEST)
345
+ || !Array.isArray(entry.steps_completed) || !stepMatches(entry.steps_completed[2], P03_STEP_DIGEST)
346
+ || typeof entry.completed_at !== 'string' || !Number.isFinite(Date.parse(entry.completed_at))
347
+ || typeof index.last_scan !== 'string' || !Number.isFinite(Date.parse(index.last_scan))
348
+ || !isDeepStrictEqual(index, { ...priorIndex, last_scan: index.last_scan, epics: { ...priorIndex.epics,
349
+ [epicId]: { ...priorEntry, status: 'generated',
350
+ steps_completed: [...priorEntry.steps_completed, entry.steps_completed[2]], completed_at: entry.completed_at,
351
+ output_files: { ...priorEntry.output_files,
352
+ epic: `docs/epics/epic-${epicId}.md`, stories } } } }))
353
+ throw invalid();
354
+ const title = scalar(epicBytes.toString('utf8'), 'title');
355
+ if (!title || !isDeepStrictEqual(current, projectState({ stateRaw: baselineRaw, state: baseline,
356
+ title, stories, storyBytes, completedAt: entry.completed_at }, { epicId }, receipt)))
357
+ throw invalid();
358
+ if (git(root, ['rev-parse', 'HEAD']) !== verifiedHead
359
+ || !regularBytes(root, '.neocortex/state.json', 8 * 1024 * 1024).equals(stateRaw))
360
+ throw invalid();
361
+ return { epicId, physicalAcceptanceReceiptId: receipt.receiptId,
362
+ storyIds: stories.map(path => path.slice('docs/stories/'.length, -'.story.md'.length)),
363
+ verifiedHead, stateSha256: digest(stateRaw), publicSafe: true };
364
+ }
365
+ /** The root supplies independent QA; this CAS persists the accepted P03 checkpoint. */
366
+ export function sealPlanningGeneration(projectRoot, reviewInput) {
367
+ validateReview(reviewInput);
368
+ const review = reviewInput;
369
+ const root = resolve(projectRoot);
370
+ if (realpathSync(root) !== root || git(root, ['rev-parse', '--show-toplevel']) !== root) {
371
+ throw new Error('planning-generation-root-invalid');
372
+ }
373
+ safeParents(root, review.epicId);
374
+ const stateDir = join(root, '.neocortex');
375
+ const lockPath = join(stateDir, 'state.json.planning.lock');
376
+ const lock = openSync(lockPath, 'wx', 0o600);
377
+ let temporaryPath;
378
+ try {
379
+ const pendingDigest = planningPendingSidecarDigest(root);
380
+ const receipt = physicalAcceptance(review);
381
+ const storyIds = storyPaths(review.epicId, Object.keys(review.artifactDigests.stories).length)
382
+ .map((path) => path.slice('docs/stories/'.length, -'.story.md'.length));
383
+ const currentRaw = regularBytes(root, '.neocortex/state.json', 8 * 1024 * 1024);
384
+ if (digest(currentRaw) !== review.sourceStateSha256) {
385
+ const sourceState = committedBytes(root, '.neocortex/state.json', 8 * 1024 * 1024);
386
+ if (digest(sourceState) !== review.sourceStateSha256) {
387
+ throw new Error('planning-generation-replay-conflict');
388
+ }
389
+ const baseline = evidence(root, review, false, undefined, sourceState);
390
+ if (!isDeepStrictEqual(JSON.parse(currentRaw.toString('utf8')), projectState(baseline, review, receipt))) {
391
+ throw new Error('planning-generation-replay-conflict');
392
+ }
393
+ return { epicId: review.epicId, physicalAcceptanceReceiptId: receipt.receiptId,
394
+ qaEvidenceId: review.qaEvidenceId, storyIds };
395
+ }
396
+ const before = evidence(root, review, true);
397
+ const next = projectState(before, review, receipt);
398
+ const commonRaw = git(root, ['rev-parse', '--git-common-dir']);
399
+ const commonDir = isAbsolute(commonRaw) ? resolve(commonRaw) : resolve(root, commonRaw);
400
+ if (realpathSync(commonDir) !== commonDir || lstatSync(commonDir).isSymbolicLink()) {
401
+ throw new Error('planning-generation-backup-dir-invalid');
402
+ }
403
+ const backupDir = join(commonDir, 'neocortex-planning');
404
+ mkdirSync(backupDir, { recursive: true, mode: 0o700 });
405
+ const backupEntry = lstatSync(backupDir);
406
+ if (!backupEntry.isDirectory() || backupEntry.isSymbolicLink()
407
+ || backupEntry.uid !== process.getuid?.() || (backupEntry.mode & 0o077) !== 0) {
408
+ throw new Error('planning-generation-backup-dir-invalid');
409
+ }
410
+ const suffix = randomUUID();
411
+ const backup = openSync(join(backupDir, `state-backup-${suffix}.json`), 'wx', 0o600);
412
+ try {
413
+ writeFileSync(backup, before.stateRaw);
414
+ fsyncSync(backup);
415
+ }
416
+ finally {
417
+ closeSync(backup);
418
+ }
419
+ const backupDirectory = openSync(backupDir, 'r');
420
+ try {
421
+ fsyncSync(backupDirectory);
422
+ }
423
+ finally {
424
+ closeSync(backupDirectory);
425
+ }
426
+ temporaryPath = join(stateDir, `state.json.planning-tmp-${suffix}`);
427
+ const temporary = openSync(temporaryPath, 'wx', 0o600);
428
+ try {
429
+ writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`);
430
+ fsyncSync(temporary);
431
+ }
432
+ finally {
433
+ closeSync(temporary);
434
+ }
435
+ const recheck = evidence(root, review, true, `.neocortex/state.json.planning-tmp-${suffix}`);
436
+ if (!recheck.stateRaw.equals(before.stateRaw))
437
+ throw new Error('planning-generation-state-conflict');
438
+ if (planningPendingSidecarDigest(root) !== pendingDigest) {
439
+ throw new Error('planning-generation-pending-conflict');
440
+ }
441
+ renameSync(temporaryPath, join(stateDir, 'state.json'));
442
+ temporaryPath = undefined;
443
+ const stateDirectory = openSync(stateDir, 'r');
444
+ try {
445
+ fsyncSync(stateDirectory);
446
+ }
447
+ finally {
448
+ closeSync(stateDirectory);
449
+ }
450
+ return { epicId: review.epicId, physicalAcceptanceReceiptId: receipt.receiptId,
451
+ qaEvidenceId: review.qaEvidenceId, storyIds };
452
+ }
453
+ finally {
454
+ if (temporaryPath)
455
+ try {
456
+ unlinkSync(temporaryPath);
457
+ }
458
+ catch { /* preserve primary failure */ }
459
+ closeSync(lock);
460
+ unlinkSync(lockPath);
461
+ }
462
+ }