@planu/cli 4.14.1 → 5.1.1

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 (452) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/LICENSE +55 -12
  3. package/README.md +10 -2
  4. package/dist/cli/commands/install.d.ts +1 -12
  5. package/dist/cli/commands/install.js +9 -65
  6. package/dist/cli/commands/job.js +4 -3
  7. package/dist/cli/commands/spec.js +12 -3
  8. package/dist/cli/commands/validate.js +2 -2
  9. package/dist/cli/router.js +1 -3
  10. package/dist/config/environment-schema.json +1 -3
  11. package/dist/config/official-sdd-tools.d.ts +4 -0
  12. package/dist/config/official-sdd-tools.js +35 -0
  13. package/dist/config/product-public.d.ts +139 -3
  14. package/dist/config/product-public.js +8 -1
  15. package/dist/config/product-public.json +7 -36
  16. package/dist/config/registries/hosts/codex.json +20 -363
  17. package/dist/config/release-policy.json +84 -2
  18. package/dist/config/runtime-policy.json +12 -1
  19. package/dist/config/skill-templates/planu-resume-work.md +2 -2
  20. package/dist/core/index.d.ts +1 -1
  21. package/dist/core/spec-validator.d.ts +4 -4
  22. package/dist/core/spec-validator.js +33 -19
  23. package/dist/engine/abortable-process-runner.d.ts +20 -2
  24. package/dist/engine/abortable-process-runner.js +243 -74
  25. package/dist/engine/ambiguity-scorer.js +4 -26
  26. package/dist/engine/auth/scope-mapper.js +1 -7
  27. package/dist/engine/autopilot/state-updater.js +4 -4
  28. package/dist/engine/cascade-hooks/hooks/review-enricher.hook.js +9 -6
  29. package/dist/engine/cascade-hooks/state-drift-detector.d.ts +3 -3
  30. package/dist/engine/cascade-hooks/state-drift-detector.js +1 -1
  31. package/dist/engine/ci-generator/planu-config.js +1 -7
  32. package/dist/engine/clarification-gate/gate.d.ts +3 -3
  33. package/dist/engine/code-scanner/layer-scanner.js +6 -22
  34. package/dist/engine/core-bridge.d.ts +3 -3
  35. package/dist/engine/cost-guardrails.d.ts +1 -1
  36. package/dist/engine/cost-guardrails.js +1 -1
  37. package/dist/engine/criterion-identity.d.ts +1 -0
  38. package/dist/engine/criterion-identity.js +7 -0
  39. package/dist/engine/dashboard/data-loader.js +2 -7
  40. package/dist/engine/dashboard/templates-layout.js +0 -3
  41. package/dist/engine/dashboard/templates-project.js +1 -13
  42. package/dist/engine/docs-site-generator/data-collector.d.ts +1 -1
  43. package/dist/engine/docs-site-generator/data-collector.js +7 -28
  44. package/dist/engine/docs-site-generator/tools-renderer.js +3 -10
  45. package/dist/engine/evidence-gates/artifact-reader.d.ts +12 -1
  46. package/dist/engine/evidence-gates/artifact-reader.js +174 -4
  47. package/dist/engine/evidence-gates/evidence-autofill.d.ts +10 -0
  48. package/dist/engine/evidence-gates/evidence-autofill.js +148 -0
  49. package/dist/engine/evidence-gates/evidence-skeletons.d.ts +19 -0
  50. package/dist/engine/evidence-gates/evidence-skeletons.js +69 -0
  51. package/dist/engine/evidence-gates/lifecycle-gate.js +4 -3
  52. package/dist/engine/evidence-gates/reconciliation-freshness.d.ts +11 -0
  53. package/dist/engine/evidence-gates/reconciliation-freshness.js +72 -0
  54. package/dist/engine/evidence-index/done-drift.d.ts +1 -1
  55. package/dist/engine/evidence-index/done-drift.js +40 -18
  56. package/dist/engine/execution/context.js +3 -0
  57. package/dist/engine/execution/durable-job-heartbeat-runtime.d.ts +4 -0
  58. package/dist/engine/execution/durable-job-heartbeat-runtime.js +400 -0
  59. package/dist/engine/execution/job-runtime.d.ts +30 -0
  60. package/dist/engine/execution/job-runtime.js +114 -6
  61. package/dist/engine/execution/operation-journal.js +10 -4
  62. package/dist/engine/execution/public-job-sanitizer.d.ts +4 -0
  63. package/dist/engine/execution/public-job-sanitizer.js +31 -0
  64. package/dist/engine/execution/validate-job-executor.d.ts +12 -1
  65. package/dist/engine/execution/validate-job-executor.js +549 -86
  66. package/dist/engine/execution/validate-job-payload.js +107 -50
  67. package/dist/engine/execution/validate-job-recovery.js +1 -1
  68. package/dist/engine/execution/validate-worker-runtime.d.ts +5 -0
  69. package/dist/engine/execution/validate-worker-runtime.js +90 -2
  70. package/dist/engine/execution/validate-worker-thread.js +17 -24
  71. package/dist/engine/handoff-artifacts/schemas.d.ts +2 -1
  72. package/dist/engine/handoff-artifacts/schemas.js +33 -0
  73. package/dist/engine/handoff-packager.d.ts +19 -0
  74. package/dist/engine/handoff-packager.js +415 -13
  75. package/dist/engine/hooks/effort-configurator.js +1 -1
  76. package/dist/engine/impact-detector/test-break-predictor.js +3 -3
  77. package/dist/engine/impact-detector/tool-registration.js +1 -1
  78. package/dist/engine/implementation-contract/renderer.js +23 -8
  79. package/dist/engine/lifecycle-reconciliation-io.d.ts +11 -0
  80. package/dist/engine/lifecycle-reconciliation-io.js +180 -0
  81. package/dist/engine/lifecycle-reconciliation.d.ts +16 -0
  82. package/dist/engine/lifecycle-reconciliation.js +406 -0
  83. package/dist/engine/local-first/tool-classification.js +2 -0
  84. package/dist/engine/macos-keychain-native-port.d.ts +5 -0
  85. package/dist/engine/macos-keychain-native-port.js +19 -7
  86. package/dist/engine/migrations/remove-commercial-state.d.ts +3 -0
  87. package/dist/engine/migrations/remove-commercial-state.js +343 -0
  88. package/dist/engine/minimality/policy-loader.js +247 -6
  89. package/dist/engine/network-policy.js +1 -0
  90. package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
  91. package/dist/engine/planu-core.darwin-arm64.node.sbom.json +20 -20
  92. package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
  93. package/dist/engine/planu-core.darwin-x64.node.sbom.json +20 -20
  94. package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
  95. package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +20 -20
  96. package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
  97. package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +20 -20
  98. package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
  99. package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +20 -20
  100. package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
  101. package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +20 -20
  102. package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
  103. package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +20 -20
  104. package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
  105. package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +20 -20
  106. package/dist/engine/readiness-checker.d.ts +1 -16
  107. package/dist/engine/readiness-checker.js +27 -88
  108. package/dist/engine/reverse-engineer/api-detector.js +2 -13
  109. package/dist/engine/reverse-engineer/complexity-analyzer.js +2 -13
  110. package/dist/engine/reverse-engineer/config-analyzer.js +2 -13
  111. package/dist/engine/reverse-engineer/dependency-graph.js +2 -13
  112. package/dist/engine/reverse-engineer/test-analyzer.js +2 -13
  113. package/dist/engine/reverse-engineer/walk-ignore.d.ts +3 -0
  114. package/dist/engine/reverse-engineer/walk-ignore.js +26 -0
  115. package/dist/engine/runtime-policy.d.ts +2 -0
  116. package/dist/engine/runtime-policy.js +42 -5
  117. package/dist/engine/runtime-security/rate-limiter.js +0 -1
  118. package/dist/engine/safety/contained-project-file.d.ts +3 -0
  119. package/dist/engine/safety/contained-project-file.js +23 -0
  120. package/dist/engine/scope-boundaries/contradiction-checker.js +63 -1
  121. package/dist/engine/scope-boundaries/scope-validator.js +45 -7
  122. package/dist/engine/session/checkpoint-writer.js +0 -9
  123. package/dist/engine/spec-format/acceptance-criteria.d.ts +6 -2
  124. package/dist/engine/spec-format/acceptance-criteria.js +82 -22
  125. package/dist/engine/spec-format/markdown-sections.d.ts +14 -0
  126. package/dist/engine/spec-format/markdown-sections.js +105 -0
  127. package/dist/engine/spec-format/read-technical-section.js +33 -5
  128. package/dist/engine/spec-format/technical-md-populator.js +3 -2
  129. package/dist/engine/spec-format/text-fences.js +20 -2
  130. package/dist/engine/spec-format/unified-spec-builder.d.ts +7 -0
  131. package/dist/engine/spec-format/unified-spec-builder.js +202 -0
  132. package/dist/engine/spec-migrator/canonical-deduplicator.d.ts +18 -0
  133. package/dist/engine/spec-migrator/canonical-deduplicator.js +479 -0
  134. package/dist/engine/spec-migrator/filesystem-import.d.ts +4 -8
  135. package/dist/engine/spec-migrator/filesystem-import.js +121 -14
  136. package/dist/engine/spec-migrator/frontmatter-parser.js +32 -15
  137. package/dist/engine/spec-migrator/index.d.ts +1 -0
  138. package/dist/engine/spec-migrator/index.js +1 -0
  139. package/dist/engine/spec-state-machine/transition-spec.d.ts +6 -1
  140. package/dist/engine/spec-state-machine/transition-spec.js +10 -1
  141. package/dist/engine/spec-state-syncer.js +1 -1
  142. package/dist/engine/technical-enricher/index.js +16 -8
  143. package/dist/engine/technical-enricher/render-enriched.d.ts +1 -0
  144. package/dist/engine/technical-enricher/render-enriched.js +42 -14
  145. package/dist/engine/telemetry/health-checker.js +53 -17
  146. package/dist/engine/telemetry/telemetry-client.js +26 -8
  147. package/dist/engine/text-signal-boundaries.d.ts +2 -0
  148. package/dist/engine/text-signal-boundaries.js +25 -2
  149. package/dist/engine/time/relative-date.d.ts +3 -0
  150. package/dist/engine/time/relative-date.js +16 -0
  151. package/dist/engine/timing/budget.d.ts +11 -0
  152. package/dist/engine/timing/budget.js +31 -9
  153. package/dist/engine/token-optimizer/analytics.d.ts +1 -1
  154. package/dist/engine/token-optimizer/analytics.js +5 -6
  155. package/dist/engine/usage-tracker/core.d.ts +2 -2
  156. package/dist/engine/usage-tracker/core.js +1 -2
  157. package/dist/engine/usage-tracker/index.d.ts +0 -3
  158. package/dist/engine/usage-tracker/index.js +0 -3
  159. package/dist/engine/usage-tracker/stats.d.ts +2 -2
  160. package/dist/engine/usage-tracker/stats.js +1 -28
  161. package/dist/engine/validation/durable-validation.d.ts +1 -1
  162. package/dist/engine/validation/durable-validation.js +3 -82
  163. package/dist/engine/validation/validation-freshness.d.ts +11 -1
  164. package/dist/engine/validation/validation-freshness.js +231 -91
  165. package/dist/engine/validation/validation-receipt.d.ts +9 -6
  166. package/dist/engine/validation/validation-receipt.js +84 -24
  167. package/dist/engine/validation/validation-source-digest.d.ts +2 -0
  168. package/dist/engine/validation/validation-source-digest.js +86 -0
  169. package/dist/engine/validation/validation-submission.d.ts +5 -4
  170. package/dist/engine/validation/validation-submission.js +25 -11
  171. package/dist/engine/validation/validation-worktree.d.ts +21 -0
  172. package/dist/engine/validation/validation-worktree.js +260 -0
  173. package/dist/engine/validation-impact-planner.d.ts +14 -2
  174. package/dist/engine/validation-impact-planner.js +0 -3
  175. package/dist/engine/validator/dor-dod.d.ts +1 -0
  176. package/dist/engine/validator/dor-dod.js +5 -3
  177. package/dist/engine/validator/extractors.d.ts +1 -5
  178. package/dist/engine/validator/extractors.js +24 -69
  179. package/dist/engine/validator/spec-compliance-runner.d.ts +12 -24
  180. package/dist/engine/validator/spec-compliance-runner.js +429 -211
  181. package/dist/engine/validator.js +35 -22
  182. package/dist/index.js +4 -16
  183. package/dist/resources/usage-stats-resource.js +2 -18
  184. package/dist/security/adapters/linux-secret-service.js +7 -3
  185. package/dist/security/adapters/windows-credential-manager.js +6 -4
  186. package/dist/server/routes/specs.js +13 -1
  187. package/dist/storage/base-store.d.ts +1 -1
  188. package/dist/storage/base-store.js +1 -1
  189. package/dist/storage/feedback-remote.d.ts +2 -3
  190. package/dist/storage/feedback-remote.js +45 -31
  191. package/dist/storage/global-store.d.ts +1 -17
  192. package/dist/storage/global-store.js +1 -42
  193. package/dist/storage/index.d.ts +0 -2
  194. package/dist/storage/index.js +0 -2
  195. package/dist/storage/migrations/canonical-storage.js +124 -11
  196. package/dist/storage/runtime-db.d.ts +9 -0
  197. package/dist/storage/runtime-db.js +64 -8
  198. package/dist/storage/spec-store.d.ts +14 -2
  199. package/dist/storage/spec-store.js +104 -14
  200. package/dist/storage/status-store/atomic-status-write.d.ts +5 -0
  201. package/dist/storage/status-store/atomic-status-write.js +57 -0
  202. package/dist/storage/status-store/file-lock.js +30 -11
  203. package/dist/storage/status-store/self-healing.js +3 -3
  204. package/dist/storage/status-store/version-sync.d.ts +2 -1
  205. package/dist/storage/status-store/version-sync.js +35 -40
  206. package/dist/storage/usage-store.d.ts +1 -5
  207. package/dist/storage/usage-store.js +0 -35
  208. package/dist/tools/auto-update-handler.d.ts +3 -3
  209. package/dist/tools/auto-update-handler.js +3 -3
  210. package/dist/tools/challenge-spec/resilience-challenge-scenarios.d.ts +2 -1
  211. package/dist/tools/challenge-spec/resilience-challenge-scenarios.js +10 -7
  212. package/dist/tools/challenge-spec/scenario-collector.js +1 -1
  213. package/dist/tools/challenge-spec/scenarios-data.js +1 -1
  214. package/dist/tools/challenge-spec/scenarios-utils.d.ts +1 -1
  215. package/dist/tools/challenge-spec/scenarios-utils.js +35 -11
  216. package/dist/tools/challenge-spec-helpers.d.ts +10 -1
  217. package/dist/tools/challenge-spec-helpers.js +63 -22
  218. package/dist/tools/challenge-spec.js +23 -8
  219. package/dist/tools/check-readiness.js +38 -14
  220. package/dist/tools/configure-telemetry.js +30 -2
  221. package/dist/tools/create-spec/spec-builder.d.ts +7 -0
  222. package/dist/tools/create-spec/spec-builder.js +19 -15
  223. package/dist/tools/create-spec.d.ts +1 -0
  224. package/dist/tools/create-spec.js +584 -309
  225. package/dist/tools/git/branch-ops.d.ts +1 -1
  226. package/dist/tools/git/branch-ops.js +3 -2
  227. package/dist/tools/init-project/handler.js +10 -12
  228. package/dist/tools/init-project/host-assets-writer.js +26 -1
  229. package/dist/tools/init-project/lint-scaffolder.d.ts +3 -3
  230. package/dist/tools/init-project/migration-runner.d.ts +2 -4
  231. package/dist/tools/init-project/migration-runner.js +3 -2
  232. package/dist/tools/init-project/portable-index-reconciler.d.ts +4 -0
  233. package/dist/tools/init-project/portable-index-reconciler.js +14 -0
  234. package/dist/tools/init-project/result-builder.js +1 -3
  235. package/dist/tools/jobs/handlers.js +38 -6
  236. package/dist/tools/list-specs.js +5 -26
  237. package/dist/tools/merge-risk-handler.js +2 -2
  238. package/dist/tools/package-handoff.js +2 -0
  239. package/dist/tools/reconcile-spec.js +0 -27
  240. package/dist/tools/register-agent-squad-tools.js +2 -2
  241. package/dist/tools/register-platform-tools/design-stack-tools.js +11 -11
  242. package/dist/tools/register-platform-tools/lifecycle-infra-tools.js +7 -7
  243. package/dist/tools/register-sdd-tools.d.ts +1 -1
  244. package/dist/tools/register-sdd-tools.js +2 -29
  245. package/dist/tools/register-spec-tools/analysis-tools.js +8 -8
  246. package/dist/tools/register-spec-tools/core-spec-tools.js +29 -19
  247. package/dist/tools/safe-handler.d.ts +5 -13
  248. package/dist/tools/safe-handler.js +7 -23
  249. package/dist/tools/schemas/index.d.ts +1 -1
  250. package/dist/tools/schemas/index.js +1 -1
  251. package/dist/tools/schemas/output-schemas.d.ts +0 -23
  252. package/dist/tools/schemas/output-schemas.js +0 -28
  253. package/dist/tools/schemas/spec.d.ts +4 -0
  254. package/dist/tools/schemas/spec.js +3 -0
  255. package/dist/tools/schemas/validate-output-schema.d.ts +11 -0
  256. package/dist/tools/schemas/validate-output-schema.js +7 -0
  257. package/dist/tools/spec-prompt-handler.js +3 -3
  258. package/dist/tools/sync-spec-state-handler.d.ts +1 -1
  259. package/dist/tools/sync-spec-state-handler.js +50 -2
  260. package/dist/tools/tool-entry.d.ts +4 -4
  261. package/dist/tools/tool-entry.js +3 -3
  262. package/dist/tools/tool-registry/core-tools.js +31 -15
  263. package/dist/tools/tool-registry/group-infra.js +27 -110
  264. package/dist/tools/tool-registry/group-quality-compliance.js +26 -26
  265. package/dist/tools/tool-registry/inventory.d.ts +2 -0
  266. package/dist/tools/tool-registry/inventory.js +2 -0
  267. package/dist/tools/tool-registry-helpers.d.ts +3 -3
  268. package/dist/tools/tool-registry-helpers.js +3 -3
  269. package/dist/tools/tool-usage-report/registered-tools.d.ts +1 -1
  270. package/dist/tools/tool-usage-report/registered-tools.js +3 -15
  271. package/dist/tools/update-status/approval-gates.js +3 -0
  272. package/dist/tools/update-status/batch.d.ts +6 -2
  273. package/dist/tools/update-status/batch.js +58 -1
  274. package/dist/tools/update-status/dod-gates.d.ts +16 -1
  275. package/dist/tools/update-status/dod-gates.js +191 -1
  276. package/dist/tools/update-status/done-receipt-verifier.d.ts +8 -0
  277. package/dist/tools/update-status/done-receipt-verifier.js +75 -27
  278. package/dist/tools/update-status/evidence-gate.d.ts +4 -0
  279. package/dist/tools/update-status/evidence-gate.js +103 -3
  280. package/dist/tools/update-status/file-sync.d.ts +4 -3
  281. package/dist/tools/update-status/file-sync.js +57 -6
  282. package/dist/tools/update-status/index.d.ts +25 -1
  283. package/dist/tools/update-status/index.js +292 -78
  284. package/dist/tools/update-status/response-builder.js +3 -3
  285. package/dist/tools/update-status/side-effects.d.ts +2 -0
  286. package/dist/tools/update-status/side-effects.js +2 -2
  287. package/dist/tools/update-status/transition-guard.js +45 -16
  288. package/dist/tools/update-status-actions.d.ts +4 -2
  289. package/dist/tools/update-status-actions.js +16 -14
  290. package/dist/tools/usage-report.js +1 -16
  291. package/dist/tools/usage-stats.js +1 -18
  292. package/dist/tools/usage-tracking.d.ts +4 -0
  293. package/dist/tools/usage-tracking.js +20 -0
  294. package/dist/tools/validate-lint.d.ts +1 -0
  295. package/dist/tools/validate-lint.js +1 -1
  296. package/dist/tools/validate.d.ts +2 -1
  297. package/dist/tools/validate.js +114 -34
  298. package/dist/tools/workspace-dashboard-handler.js +38 -0
  299. package/dist/transports/http-transport.js +6 -8
  300. package/dist/transports/mcp-types.d.ts +0 -2
  301. package/dist/transports/session-context.js +1 -1
  302. package/dist/types/cascade-hooks.d.ts +1 -1
  303. package/dist/types/ci.d.ts +0 -4
  304. package/dist/types/clarification-token.d.ts +1 -1
  305. package/dist/types/claude-code-runtime.d.ts +0 -27
  306. package/dist/types/commercial-migration.d.ts +17 -0
  307. package/dist/types/commercial-migration.js +2 -0
  308. package/dist/types/common/primitives.d.ts +0 -2
  309. package/dist/types/core-bridge.d.ts +4 -10
  310. package/dist/types/dashboard.d.ts +3 -7
  311. package/dist/types/docs.d.ts +0 -1
  312. package/dist/types/durable-job.d.ts +4 -4
  313. package/dist/types/durable-validation.d.ts +100 -2
  314. package/dist/types/durable-validation.js +165 -1
  315. package/dist/types/errors/structured-error.d.ts +3 -3
  316. package/dist/types/estimation.d.ts +2 -0
  317. package/dist/types/evidence-autofill.d.ts +34 -0
  318. package/dist/types/evidence-autofill.js +2 -0
  319. package/dist/types/evidence-gates.d.ts +9 -1
  320. package/dist/types/execution.d.ts +2 -0
  321. package/dist/types/gemini-integration.d.ts +0 -11
  322. package/dist/types/handoff-artifacts.d.ts +41 -0
  323. package/dist/types/hooks-advanced.d.ts +0 -13
  324. package/dist/types/impact-detection.d.ts +2 -2
  325. package/dist/types/index.d.ts +3 -6
  326. package/dist/types/index.js +2 -5
  327. package/dist/types/kiro-integration.d.ts +0 -6
  328. package/dist/types/network-policy.d.ts +1 -1
  329. package/dist/types/project/lint-scaffolder.d.ts +1 -1
  330. package/dist/types/runtime-policy.d.ts +11 -0
  331. package/dist/types/session-safeguard.d.ts +0 -31
  332. package/dist/types/spec/canonical-repair.d.ts +81 -0
  333. package/dist/types/spec/canonical-repair.js +2 -0
  334. package/dist/types/spec/core.d.ts +20 -0
  335. package/dist/types/spec/index.d.ts +1 -0
  336. package/dist/types/spec/index.js +1 -0
  337. package/dist/types/spec/inputs.d.ts +35 -1
  338. package/dist/types/spec-format.d.ts +25 -1
  339. package/dist/types/telemetry.d.ts +4 -1
  340. package/dist/types/transition-log.d.ts +1 -1
  341. package/dist/types/transport.d.ts +0 -2
  342. package/dist/types/usage.d.ts +1 -36
  343. package/dist/types/usage.js +1 -1
  344. package/dist/types/validate-worker.d.ts +6 -0
  345. package/dist/types/validation-receipt.d.ts +1 -1
  346. package/dist/types/validation.d.ts +10 -4
  347. package/package.json +17 -12
  348. package/planu-native.json +1 -1
  349. package/planu-plugin.json +23 -40
  350. package/scripts/lib/pending-release-file.mjs +93 -0
  351. package/src/i18n/messages/en.json +0 -41
  352. package/src/i18n/messages/es.json +0 -41
  353. package/src/i18n/messages/pt.json +0 -41
  354. package/dist/cli/commands/activate.d.ts +0 -14
  355. package/dist/cli/commands/activate.js +0 -174
  356. package/dist/config/license-plans.json +0 -251
  357. package/dist/config/product-public.ts +0 -148
  358. package/dist/config/server-instructions.ts +0 -121
  359. package/dist/config/version-defaults.ts +0 -76
  360. package/dist/config/version.ts +0 -21
  361. package/dist/engine/ai-integration/gemini/settings-generator.d.ts +0 -4
  362. package/dist/engine/ai-integration/gemini/settings-generator.js +0 -195
  363. package/dist/engine/ai-integration/kiro/hooks-generator.d.ts +0 -6
  364. package/dist/engine/ai-integration/kiro/hooks-generator.js +0 -75
  365. package/dist/engine/hooks/full-spectrum-generator.d.ts +0 -32
  366. package/dist/engine/hooks/full-spectrum-generator.js +0 -117
  367. package/dist/engine/license-validator/config-loader.d.ts +0 -5
  368. package/dist/engine/license-validator/config-loader.js +0 -16
  369. package/dist/engine/license-validator/core.d.ts +0 -15
  370. package/dist/engine/license-validator/core.js +0 -151
  371. package/dist/engine/license-validator/fingerprint.d.ts +0 -3
  372. package/dist/engine/license-validator/fingerprint.js +0 -17
  373. package/dist/engine/license-validator/lemon-squeezy.d.ts +0 -5
  374. package/dist/engine/license-validator/lemon-squeezy.js +0 -55
  375. package/dist/engine/license-validator.d.ts +0 -5
  376. package/dist/engine/license-validator.js +0 -6
  377. package/dist/engine/product-intelligence/index.d.ts +0 -7
  378. package/dist/engine/product-intelligence/index.js +0 -211
  379. package/dist/engine/session/auto-save.d.ts +0 -18
  380. package/dist/engine/session/auto-save.js +0 -98
  381. package/dist/engine/session/context-snapshot.d.ts +0 -8
  382. package/dist/engine/session/context-snapshot.js +0 -49
  383. package/dist/engine/session/memory-sync.d.ts +0 -12
  384. package/dist/engine/session/memory-sync.js +0 -34
  385. package/dist/engine/session/session-diff.d.ts +0 -11
  386. package/dist/engine/session/session-diff.js +0 -42
  387. package/dist/engine/session/session-merge.d.ts +0 -12
  388. package/dist/engine/session/session-merge.js +0 -55
  389. package/dist/engine/session-safeguard/checkpoint-runner.d.ts +0 -4
  390. package/dist/engine/session-safeguard/checkpoint-runner.js +0 -70
  391. package/dist/engine/session-safeguard/learnings-buffer.d.ts +0 -15
  392. package/dist/engine/session-safeguard/learnings-buffer.js +0 -91
  393. package/dist/engine/session-safeguard/precompaction-drain.d.ts +0 -10
  394. package/dist/engine/session-safeguard/precompaction-drain.js +0 -82
  395. package/dist/engine/session-safeguard/unpushed-detector.d.ts +0 -7
  396. package/dist/engine/session-safeguard/unpushed-detector.js +0 -59
  397. package/dist/engine/spec-changelog/core.d.ts +0 -16
  398. package/dist/engine/spec-changelog/core.js +0 -176
  399. package/dist/engine/spec-changelog/diff.d.ts +0 -18
  400. package/dist/engine/spec-changelog/diff.js +0 -121
  401. package/dist/engine/spec-changelog/index.d.ts +0 -3
  402. package/dist/engine/spec-changelog/index.js +0 -4
  403. package/dist/engine/trial-engine.d.ts +0 -5
  404. package/dist/engine/trial-engine.js +0 -14
  405. package/dist/engine/usage-tracker/rate-check.d.ts +0 -7
  406. package/dist/engine/usage-tracker/rate-check.js +0 -18
  407. package/dist/engine/usage-tracker/rate-limiter.d.ts +0 -14
  408. package/dist/engine/usage-tracker/rate-limiter.js +0 -73
  409. package/dist/engine/usage-tracker/trial.d.ts +0 -27
  410. package/dist/engine/usage-tracker/trial.js +0 -49
  411. package/dist/engine/webhook/event-handlers.d.ts +0 -25
  412. package/dist/engine/webhook/event-handlers.js +0 -89
  413. package/dist/engine/webhook/index.d.ts +0 -4
  414. package/dist/engine/webhook/index.js +0 -5
  415. package/dist/engine/webhook/server.d.ts +0 -12
  416. package/dist/engine/webhook/server.js +0 -249
  417. package/dist/engine/webhook/signature.d.ts +0 -16
  418. package/dist/engine/webhook/signature.js +0 -49
  419. package/dist/storage/license-store.d.ts +0 -6
  420. package/dist/storage/license-store.js +0 -63
  421. package/dist/storage/session-store.d.ts +0 -20
  422. package/dist/storage/session-store.js +0 -136
  423. package/dist/tools/activate-license.d.ts +0 -6
  424. package/dist/tools/activate-license.js +0 -132
  425. package/dist/tools/checkpoint-handler.d.ts +0 -3
  426. package/dist/tools/checkpoint-handler.js +0 -130
  427. package/dist/tools/export-session.d.ts +0 -3
  428. package/dist/tools/export-session.js +0 -90
  429. package/dist/tools/license-gate.d.ts +0 -18
  430. package/dist/tools/license-gate.js +0 -222
  431. package/dist/tools/license-status.d.ts +0 -3
  432. package/dist/tools/license-status.js +0 -93
  433. package/dist/tools/list-sessions.d.ts +0 -3
  434. package/dist/tools/list-sessions.js +0 -46
  435. package/dist/tools/planu-session-checkpoint.d.ts +0 -6
  436. package/dist/tools/planu-session-checkpoint.js +0 -42
  437. package/dist/tools/restore-session.d.ts +0 -3
  438. package/dist/tools/restore-session.js +0 -71
  439. package/dist/tools/spec-history.d.ts +0 -3
  440. package/dist/tools/spec-history.js +0 -59
  441. package/dist/tools/webhook.d.ts +0 -6
  442. package/dist/tools/webhook.js +0 -150
  443. package/dist/types/changelog.d.ts +0 -49
  444. package/dist/types/changelog.js +0 -3
  445. package/dist/types/licensing.d.ts +0 -109
  446. package/dist/types/licensing.js +0 -4
  447. package/dist/types/product-intelligence.d.ts +0 -60
  448. package/dist/types/product-intelligence.js +0 -3
  449. package/dist/types/session.d.ts +0 -84
  450. package/dist/types/session.js +0 -3
  451. package/dist/types/webhook.d.ts +0 -62
  452. package/dist/types/webhook.js +0 -4
@@ -1,3 +1,11 @@
1
+ /* eslint-disable max-lines -- done evidence gates remain co-located for one auditable fail-closed boundary */
2
+ // tools/update-status/dod-gates.ts — DoD gate checks and forceStatus bypass
3
+ // SPEC-729: Validate gate wrapped with withEscalation for bounded retries.
4
+ // SPEC-780: checkApprovedFormatGate honours forceApprove, mirroring SPEC-769 readiness gate.
5
+ import { createHash } from 'node:crypto';
6
+ import { constants } from 'node:fs';
7
+ import { lstat, open, realpath } from 'node:fs/promises';
8
+ import { isAbsolute, relative, resolve as resolvePath, sep } from 'node:path';
1
9
  import { validateSpec } from '../../engine/validator.js';
2
10
  import { generateDoD } from '../../engine/validator/dor-dod.js';
3
11
  import { addLesson, getLessons } from '../../storage/lessons-store.js';
@@ -9,6 +17,8 @@ import { writeSpecReviewFeedback } from '../../engine/validator/spec-review-writ
9
17
  import { formatKeyValue } from '../output-formatter.js';
10
18
  import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
11
19
  import { verifyCurrentDoneReceipt } from './done-receipt-verifier.js';
20
+ import { projectDataDir } from '../../storage/base-store.js';
21
+ import { ValidationReportV1Schema } from '../../engine/handoff-artifacts/schemas.js';
12
22
  export { completedJobPublishesReceipt } from './done-receipt-verifier.js';
13
23
  /**
14
24
  * SPEC-721 / SPEC-222 Trigger 1: Run validate engine before marking done.
@@ -275,6 +285,176 @@ function validationReceiptGateError(specId, reason) {
275
285
  },
276
286
  };
277
287
  }
288
+ function doneReviewDigestError(specId, code, message) {
289
+ const artifactPath = `external Planu project data: handoffs/${specId}/validation-report.json`;
290
+ const fixHint = 'Run validate, then recompute the sha256 digest from the exact report bytes.';
291
+ return {
292
+ content: [{ type: 'text', text: `${code}: ${message}\nFix: ${fixHint}` }],
293
+ isError: true,
294
+ structuredContent: {
295
+ error: code,
296
+ code: 422,
297
+ context: { specId, artifactPath },
298
+ fixHint,
299
+ },
300
+ };
301
+ }
302
+ const MAX_VALIDATION_REPORT_BYTES = 5 * 1024 * 1024;
303
+ function isContained(root, candidate) {
304
+ const pathFromRoot = relative(root, candidate);
305
+ return (pathFromRoot === '' ||
306
+ (!isAbsolute(pathFromRoot) && pathFromRoot !== '..' && !pathFromRoot.startsWith(`..${sep}`)));
307
+ }
308
+ function sameFileMetadata(left, right) {
309
+ return (left.dev === right.dev &&
310
+ left.ino === right.ino &&
311
+ left.size === right.size &&
312
+ left.mtimeNs === right.mtimeNs &&
313
+ left.ctimeNs === right.ctimeNs);
314
+ }
315
+ async function readStableDoneReviewReport(specId, projectId) {
316
+ const handoffsRoot = resolvePath(projectDataDir(projectId), 'handoffs');
317
+ const specRoot = resolvePath(handoffsRoot, specId);
318
+ const reportPath = resolvePath(specRoot, 'validation-report.json');
319
+ if (!isContained(handoffsRoot, specRoot) || !isContained(specRoot, reportPath)) {
320
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report path escapes its contained handoff directory.');
321
+ }
322
+ let canonicalHandoffs;
323
+ let canonicalSpecRoot;
324
+ let canonicalReport;
325
+ let pathStats;
326
+ try {
327
+ [canonicalHandoffs, canonicalSpecRoot, canonicalReport, pathStats] = await Promise.all([
328
+ realpath(handoffsRoot),
329
+ realpath(specRoot),
330
+ realpath(reportPath),
331
+ lstat(reportPath, { bigint: true }),
332
+ ]);
333
+ }
334
+ catch {
335
+ return {
336
+ ...doneReviewDigestError(specId, 'DONE_REVIEW_DIGEST_MISMATCH', 'No persisted validation report was found for this spec. Run validate first, then retry done.'),
337
+ isError: true,
338
+ };
339
+ }
340
+ if (!isContained(canonicalHandoffs, canonicalSpecRoot) ||
341
+ !isContained(canonicalSpecRoot, canonicalReport) ||
342
+ pathStats.isSymbolicLink() ||
343
+ !pathStats.isFile() ||
344
+ pathStats.size > BigInt(MAX_VALIDATION_REPORT_BYTES)) {
345
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report is not a contained bounded regular file.');
346
+ }
347
+ if (typeof constants.O_NOFOLLOW !== 'number') {
348
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The platform cannot safely open the validation report without following links.');
349
+ }
350
+ let handle;
351
+ try {
352
+ handle = await open(reportPath, constants.O_RDONLY | constants.O_NOFOLLOW);
353
+ }
354
+ catch {
355
+ return {
356
+ ...doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report could not be opened without following links.'),
357
+ isError: true,
358
+ };
359
+ }
360
+ try {
361
+ const openedStats = await handle.stat({ bigint: true });
362
+ if (!openedStats.isFile() || !sameFileMetadata(pathStats, openedStats)) {
363
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The validation report identity changed before it was opened.');
364
+ }
365
+ const reportBytes = Buffer.alloc(Number(openedStats.size));
366
+ let offset = 0;
367
+ while (offset < reportBytes.length) {
368
+ const { bytesRead } = await handle.read(reportBytes, offset, reportBytes.length - offset, offset);
369
+ if (bytesRead === 0) {
370
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The validation report changed while it was being read.');
371
+ }
372
+ offset += bytesRead;
373
+ }
374
+ const finalHandleStats = await handle.stat({ bigint: true });
375
+ if (!sameFileMetadata(openedStats, finalHandleStats)) {
376
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The opened validation report changed while it was being read.');
377
+ }
378
+ let finalPathStats;
379
+ let finalCanonicalReport;
380
+ try {
381
+ [finalPathStats, finalCanonicalReport] = await Promise.all([
382
+ lstat(reportPath, { bigint: true }),
383
+ realpath(reportPath),
384
+ ]);
385
+ }
386
+ catch {
387
+ return {
388
+ ...doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The validation report path changed while it was being read.'),
389
+ isError: true,
390
+ };
391
+ }
392
+ if (!isContained(canonicalSpecRoot, finalCanonicalReport)) {
393
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report path escaped containment while it was being read.');
394
+ }
395
+ if (finalPathStats.isSymbolicLink() ||
396
+ !finalPathStats.isFile() ||
397
+ !sameFileMetadata(openedStats, finalPathStats)) {
398
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The validation report path identity changed while it was being read.');
399
+ }
400
+ return reportBytes;
401
+ }
402
+ finally {
403
+ await handle.close();
404
+ }
405
+ }
406
+ /**
407
+ * SPEC-1356 (T-A1): auto-derive the sha256 digest of the persisted
408
+ * `validation-report.json` so callers no longer have to compute it by hand.
409
+ * Reads and hashes the exact same bytes `checkDoneReviewDigest` would validate
410
+ * against, using the same hardened path/identity checks. Returns the digest, or
411
+ * the exact blocking `ToolResult` when the report cannot be safely read (e.g.
412
+ * validate has not run yet). Exported for direct unit testing and for the
413
+ * orchestration layer (`update-status/index.ts`) that no longer requires the
414
+ * caller to supply `implementationReviewDigest`.
415
+ */
416
+ export async function deriveDoneReviewDigest(specId, projectId) {
417
+ const reportResult = await readStableDoneReviewReport(specId, projectId);
418
+ if (!Buffer.isBuffer(reportResult)) {
419
+ return { error: reportResult };
420
+ }
421
+ return { digest: `sha256:${createHash('sha256').update(reportResult).digest('hex')}` };
422
+ }
423
+ async function checkDoneReviewDigest(specId, projectId, implementationReviewDigest) {
424
+ if (!/^sha256:[a-f0-9]{64}$/u.test(implementationReviewDigest)) {
425
+ return doneReviewDigestError(specId, 'DONE_REVIEW_DIGEST_INVALID', 'The implementation review digest is malformed.');
426
+ }
427
+ const reportResult = await readStableDoneReviewReport(specId, projectId);
428
+ if (!Buffer.isBuffer(reportResult)) {
429
+ return reportResult;
430
+ }
431
+ const observed = `sha256:${createHash('sha256').update(reportResult).digest('hex')}`;
432
+ if (observed !== implementationReviewDigest) {
433
+ return doneReviewDigestError(specId, 'DONE_REVIEW_DIGEST_MISMATCH', 'The implementation review digest does not match the persisted validation report.');
434
+ }
435
+ let decoded;
436
+ try {
437
+ decoded = JSON.parse(reportResult.toString('utf8'));
438
+ }
439
+ catch {
440
+ return {
441
+ ...doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report bytes are not valid JSON.'),
442
+ isError: true,
443
+ };
444
+ }
445
+ const parsed = ValidationReportV1Schema.safeParse(decoded);
446
+ if (!parsed.success ||
447
+ parsed.data.specId !== specId ||
448
+ !parsed.data.passed ||
449
+ parsed.data.gates.some((gate) => !gate.passed) ||
450
+ parsed.data.minimalityReport?.blocked === true ||
451
+ parsed.data.reviewer.kind !== 'implementation-review-agent' ||
452
+ parsed.data.reviewer.agent !== 'planu-implementation-reviewer' ||
453
+ parsed.data.reviewer.verdict !== 'approved') {
454
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report schema or independent reviewer evidence is invalid.');
455
+ }
456
+ return null;
457
+ }
278
458
  /**
279
459
  * SPEC-725: Check the validation-report artifact gate before marking done.
280
460
  * SPEC-1050: Fail closed. A done transition requires a fresh validation-report
@@ -493,8 +673,18 @@ function specReviewGateError(args) {
493
673
  * When force=true, gates are not blocking but failing items are recorded in
494
674
  * the project's force-bypass-log.json for traceability (guard: done ≠ implemented).
495
675
  */
496
- export async function checkDoneGates(spec, specId, projectId, projectPath, _force, _forceReason = 'No force reason provided', verifyReceipt = verifyCurrentDoneReceipt) {
676
+ export async function checkDoneGates(spec, specId, projectId, projectPath, _force, _forceReason = 'No force reason provided', verifyReceipt = verifyCurrentDoneReceipt, implementationReviewDigest) {
497
677
  try {
678
+ if (implementationReviewDigest !== undefined) {
679
+ const digestError = await checkDoneReviewDigest(specId, projectId, implementationReviewDigest);
680
+ if (digestError) {
681
+ return {
682
+ blocked: digestError,
683
+ forcedBypassWarning: null,
684
+ freshnessLease: null,
685
+ };
686
+ }
687
+ }
498
688
  const result = await verifyReceipt({ projectId, specId, projectPath, specPath: spec.specPath });
499
689
  return result.valid
500
690
  ? {
@@ -1,5 +1,6 @@
1
1
  import { type DurableJobRecord } from '../../storage/runtime-db.js';
2
2
  import { type ValidationFreshnessLease } from '../../engine/validation/validation-freshness.js';
3
+ import type { ValidationReceiptBindings } from '../../types/validation-receipt.js';
3
4
  export type DoneReceiptVerifier = (identity: {
4
5
  projectId: string;
5
6
  specId: string;
@@ -11,5 +12,12 @@ export type DoneReceiptVerifier = (identity: {
11
12
  freshnessLease?: ValidationFreshnessLease;
12
13
  }>;
13
14
  export declare function completedJobPublishesReceipt(job: DurableJobRecord | undefined, receiptId: string): boolean;
15
+ /**
16
+ * Evidence artifacts (discovery/task-plan/traceability/contract-validation) may legitimately
17
+ * be regenerated after a receipt was issued (e.g. autopilot skeleton fill). If ONLY the evidence
18
+ * digests changed while every code-facing binding stayed byte-identical, the receipt is rebound
19
+ * to the freshly observed evidence digests instead of being rejected as stale.
20
+ */
21
+ export declare function doneReceiptBindingsDifferOnlyInEvidence(observed: ValidationReceiptBindings | undefined, stored: ValidationReceiptBindings | undefined): boolean;
14
22
  export declare function verifyCurrentDoneReceipt(identity: Parameters<DoneReceiptVerifier>[0]): ReturnType<DoneReceiptVerifier>;
15
23
  //# sourceMappingURL=done-receipt-verifier.d.ts.map
@@ -54,8 +54,9 @@ import { isAbsolute, join } from 'node:path';
54
54
  import { RuntimeDatabase } from '../../storage/runtime-db.js';
55
55
  import { resolveStorageLayout } from '../../storage/storage-layout.js';
56
56
  import { projectDataDir } from '../../storage/base-store.js';
57
+ import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
57
58
  import { ValidationReceiptStore, LOCAL_VALIDATION_RECEIPT_ISSUER, createValidationReceiptAuthority, readCurrentValidationReceipt, } from '../../engine/validation/validation-receipt.js';
58
- import { beginValidationFreshnessBarrier, bindValidationFreshnessLease, captureValidationFreshnessLease, } from '../../engine/validation/validation-freshness.js';
59
+ import { beginValidationFreshnessBarrier, bindValidationFreshnessLease, captureValidationFreshnessLease, closeValidationFreshnessLease, } from '../../engine/validation/validation-freshness.js';
59
60
  import { computeDurableValidationBindings, toValidationReceiptBindings, } from '../../engine/validation/durable-validation.js';
60
61
  export function completedJobPublishesReceipt(job, receiptId) {
61
62
  if (job?.state !== 'completed' || !job.result || typeof job.result !== 'object') {
@@ -69,6 +70,31 @@ export function completedJobPublishesReceipt(job, receiptId) {
69
70
  return (published.receiptId === receiptId &&
70
71
  published.receiptKey === `validation-receipt:${receiptId.slice('sha256:'.length)}`);
71
72
  }
73
+ /**
74
+ * Evidence artifacts (discovery/task-plan/traceability/contract-validation) may legitimately
75
+ * be regenerated after a receipt was issued (e.g. autopilot skeleton fill). If ONLY the evidence
76
+ * digests changed while every code-facing binding stayed byte-identical, the receipt is rebound
77
+ * to the freshly observed evidence digests instead of being rejected as stale.
78
+ */
79
+ export function doneReceiptBindingsDifferOnlyInEvidence(observed, stored) {
80
+ if (!observed?.artifactDigests || !stored?.artifactDigests) {
81
+ return false;
82
+ }
83
+ const codeBindingsMatch = observed.projectId === stored.projectId &&
84
+ observed.specId === stored.specId &&
85
+ observed.sourceDigest === stored.sourceDigest &&
86
+ observed.specDigest === stored.specDigest &&
87
+ observed.policyDigest === stored.policyDigest &&
88
+ observed.toolchainDigest === stored.toolchainDigest &&
89
+ observed.artifactDigests.approvedSpec === stored.artifactDigests.approvedSpec;
90
+ if (!codeBindingsMatch) {
91
+ return false;
92
+ }
93
+ return (observed.artifactDigests.discovery !== stored.artifactDigests.discovery ||
94
+ observed.artifactDigests.taskPlan !== stored.artifactDigests.taskPlan ||
95
+ observed.artifactDigests.traceability !== stored.artifactDigests.traceability ||
96
+ observed.artifactDigests.contractValidation !== stored.artifactDigests.contractValidation);
97
+ }
72
98
  export async function verifyCurrentDoneReceipt(identity) {
73
99
  const env_1 = { stack: [], error: void 0, hasError: false };
74
100
  try {
@@ -105,34 +131,56 @@ export async function verifyCurrentDoneReceipt(identity) {
105
131
  if (!receiptJobExists) {
106
132
  return { valid: false, reason: 'missing-receipt' };
107
133
  }
108
- let freshnessLease = await captureValidationFreshnessLease(identity, currentBindings);
109
- if (!freshnessLease && identity.projectPath && identity.specPath) {
110
- const projectPath = identity.projectPath;
111
- const specPath = isAbsolute(identity.specPath)
112
- ? identity.specPath
113
- : join(projectPath, identity.specPath);
114
- const lease = await beginValidationFreshnessBarrier({
115
- projectId: identity.projectId,
116
- specId: identity.specId,
117
- projectPath,
118
- handoffPath: join(projectDataDir(identity.projectId), 'handoffs', identity.specId),
119
- invalidateCurrentBindings: false,
120
- });
121
- const observed = await computeDurableValidationBindings({
122
- canonicalProjectId: identity.projectId,
123
- specId: identity.specId,
124
- projectPath,
125
- specPath,
126
- });
127
- const observedBindings = toValidationReceiptBindings(observed.bindings, observed.artifactDigests);
128
- if (JSON.stringify(observedBindings) === JSON.stringify(currentBindings) &&
129
- (await bindValidationFreshnessLease(lease, observedBindings))) {
130
- freshnessLease = lease;
134
+ let ownedLease = await captureValidationFreshnessLease(identity, currentBindings);
135
+ try {
136
+ if (!ownedLease && identity.projectPath && identity.specPath) {
137
+ const projectPath = identity.projectPath;
138
+ const specPath = isAbsolute(identity.specPath)
139
+ ? identity.specPath
140
+ : join(projectPath, identity.specPath);
141
+ ownedLease = await beginValidationFreshnessBarrier({
142
+ projectId: identity.projectId,
143
+ specId: identity.specId,
144
+ projectPath,
145
+ handoffPath: join(projectDataDir(identity.projectId), 'handoffs', identity.specId),
146
+ invalidateCurrentBindings: false,
147
+ });
148
+ const observed = await computeDurableValidationBindings({
149
+ canonicalProjectId: identity.projectId,
150
+ specId: identity.specId,
151
+ projectPath,
152
+ specPath,
153
+ });
154
+ const observedBindings = toValidationReceiptBindings(observed.bindings, observed.artifactDigests);
155
+ if (JSON.stringify(observedBindings) !== JSON.stringify(currentBindings)) {
156
+ if (!doneReceiptBindingsDifferOnlyInEvidence(observedBindings, currentBindings)) {
157
+ return { valid: false, reason: 'stale-bindings' };
158
+ }
159
+ await store.projectCurrentBindings(observedBindings);
160
+ console.warn('[planu:evidence-rebind]', {
161
+ projectId: identity.projectId,
162
+ specId: identity.specId,
163
+ rebindAt: new Date().toISOString(),
164
+ });
165
+ }
166
+ if (!(await bindValidationFreshnessLease(ownedLease, observedBindings))) {
167
+ return { valid: false, reason: 'stale-bindings' };
168
+ }
169
+ }
170
+ if (!ownedLease) {
171
+ return { valid: false, reason: 'stale-bindings' };
172
+ }
173
+ const transferred = ownedLease;
174
+ ownedLease = undefined;
175
+ return { ...verification, freshnessLease: transferred };
176
+ }
177
+ finally {
178
+ if (ownedLease) {
179
+ await closeValidationFreshnessLease(ownedLease).catch((error) => {
180
+ reportClassifiedDegradation('VALIDATION_FRESHNESS_CHECK_FAILED', error);
181
+ });
131
182
  }
132
183
  }
133
- return freshnessLease
134
- ? { ...verification, freshnessLease }
135
- : { valid: false, reason: 'stale-bindings' };
136
184
  }
137
185
  catch (e_1) {
138
186
  env_1.error = e_1;
@@ -1,6 +1,10 @@
1
1
  import type { ToolResult } from '../../types/index.js';
2
2
  import type { Spec } from '../../types/spec/core.js';
3
3
  import type { EvidenceGateTransition } from '../../types/evidence-gates.js';
4
+ /** SPEC-1356: repo-relative paths mentioned in the spec's Problem/Technical prose also count
5
+ * as approved canonical scope, even when they are not in the file-ownership checklist. Paths
6
+ * do not need to exist yet (a spec may name files it has not created). */
7
+ export declare function extractCanonicalScopePathsFromSections(body: string): string[];
4
8
  /** SPEC-1054: BDD/SDD lifecycle evidence gate. */
5
9
  export declare function checkLifecycleEvidenceTransitionGate(args: {
6
10
  spec: Spec;
@@ -66,9 +66,38 @@ import { ValidationReceiptStore, LOCAL_VALIDATION_RECEIPT_ISSUER, createValidati
66
66
  import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
67
67
  import { captureValidationFreshnessLease } from '../../engine/validation/validation-freshness.js';
68
68
  import { computeDurableValidationBindings, toValidationReceiptBindings, } from '../../engine/validation/durable-validation.js';
69
+ import { checkReconciliationFreshness } from '../../engine/evidence-gates/reconciliation-freshness.js';
70
+ import { extractCanonicalFileOwnership } from '../../engine/handoff-packager.js';
71
+ import { generateDiscoverySkeleton, generateTaskPlanSkeleton, writeEvidenceSkeleton, } from '../../engine/evidence-gates/evidence-skeletons.js';
72
+ import { extractSection } from '../../engine/spec-format/markdown-sections.js';
73
+ const CANONICAL_SCOPE_PATH = /(?:src|tests|scripts|website)\/[\w/.@-]+\.\w+/g;
74
+ /** SPEC-1356: repo-relative paths mentioned in the spec's Problem/Technical prose also count
75
+ * as approved canonical scope, even when they are not in the file-ownership checklist. Paths
76
+ * do not need to exist yet (a spec may name files it has not created). */
77
+ export function extractCanonicalScopePathsFromSections(body) {
78
+ const problem = extractSection(body, 'problem') ?? '';
79
+ const technical = extractSection(body, 'technical') ?? '';
80
+ const matches = `${problem}\n${technical}`.match(CANONICAL_SCOPE_PATH) ?? [];
81
+ return [...new Set(matches)];
82
+ }
69
83
  /** SPEC-1054: BDD/SDD lifecycle evidence gate. */
70
84
  export async function checkLifecycleEvidenceTransitionGate(args) {
71
- const [criteriaResult, artifacts] = await Promise.all([
85
+ const staleReconciliationEvidence = await checkReconciliationFreshness({
86
+ projectId: args.projectId,
87
+ specId: args.specId,
88
+ });
89
+ if (staleReconciliationEvidence.length > 0) {
90
+ return evidenceGateError({
91
+ specId: args.specId,
92
+ transition: args.transition,
93
+ issues: staleReconciliationEvidence.map(({ artifactId }) => ({
94
+ code: 'SUPERSEDED_RECONCILIATION_EVIDENCE',
95
+ message: artifactId,
96
+ })),
97
+ requiredContractKinds: [],
98
+ });
99
+ }
100
+ const [criteriaResult, initialArtifacts] = await Promise.all([
72
101
  extractLifecycleGateCriteriaAndBody(args.spec, args.projectPath),
73
102
  readEvidenceArtifacts({
74
103
  spec: args.spec,
@@ -77,6 +106,16 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
77
106
  projectPath: args.projectPath,
78
107
  }),
79
108
  ]);
109
+ const { criteria, body } = criteriaResult;
110
+ const artifacts = await autofillMissingEvidenceSkeletons({
111
+ spec: args.spec,
112
+ projectId: args.projectId,
113
+ specId: args.specId,
114
+ transition: args.transition,
115
+ criteria,
116
+ body,
117
+ artifacts: initialArtifacts,
118
+ });
80
119
  const receiptResult = args.transition === 'done'
81
120
  ? await readVerifiedCurrentReceipt({
82
121
  projectId: args.projectId,
@@ -93,7 +132,6 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
93
132
  requiredContractKinds: [],
94
133
  });
95
134
  }
96
- const { criteria, body } = criteriaResult;
97
135
  const result = checkLifecycleEvidenceGate({
98
136
  transition: args.transition,
99
137
  spec: args.spec,
@@ -108,8 +146,28 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
108
146
  artifacts,
109
147
  projectPath: args.projectPath,
110
148
  });
149
+ const canonicalOwnership = body
150
+ ? extractCanonicalFileOwnership(body)
151
+ : { present: false, toCreate: [], toModify: [], toTest: [], blockers: [] };
111
152
  const groundedTechnicalPaths = body ? parseGroundedTechnicalPaths(body) : [];
112
- result.issues.push(...checkDoneDriftContract({ index, groundedTechnicalPaths }));
153
+ const problemTechnicalScopePaths = body ? extractCanonicalScopePathsFromSections(body) : [];
154
+ const approvedScopePaths = [
155
+ ...new Set([
156
+ ...canonicalOwnership.toCreate,
157
+ ...canonicalOwnership.toModify,
158
+ ...canonicalOwnership.toTest,
159
+ ...groundedTechnicalPaths,
160
+ ...problemTechnicalScopePaths,
161
+ ]),
162
+ ];
163
+ if (canonicalOwnership.blockers.length > 0) {
164
+ const safeBlockers = canonicalOwnership.blockers.map((blocker) => blocker.startsWith('CONFLICTING_FILE_OWNERSHIP:') ? blocker : 'INVALID_FILE_OWNERSHIP');
165
+ result.issues.push({
166
+ code: 'done_drift_unapproved_scope',
167
+ message: `Approved canonical ownership is invalid: ${[...new Set(safeBlockers)].join(', ')}.`,
168
+ });
169
+ }
170
+ result.issues.push(...checkDoneDriftContract({ index, approvedScopePaths }));
113
171
  }
114
172
  if (result.issues.length === 0) {
115
173
  return null;
@@ -124,6 +182,48 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
124
182
  requiredContractKinds: result.requiredContractKinds,
125
183
  });
126
184
  }
185
+ /** True when the artifact truly has no file yet, as opposed to failing schema validation. */
186
+ function isArtifactAbsent(artifacts, label) {
187
+ return !artifacts.invalidArtifacts.some((entry) => entry.startsWith(label));
188
+ }
189
+ /**
190
+ * SPEC-1356: self-heal missing (not malformed) discovery/task-plan handoff evidence by
191
+ * generating a schema-valid skeleton and persisting it, instead of blocking the transition
192
+ * on a manual authoring step. Malformed existing artifacts still fail the gate as before.
193
+ */
194
+ async function autofillMissingEvidenceSkeletons(args) {
195
+ if (args.spec.scope === 'trivial') {
196
+ return args.artifacts;
197
+ }
198
+ let artifacts = args.artifacts;
199
+ if (args.transition === 'approved' &&
200
+ !artifacts.discovery &&
201
+ isArtifactAbsent(artifacts, 'Discovery evidence')) {
202
+ const skeleton = generateDiscoverySkeleton({ spec: args.spec, body: args.body });
203
+ await writeEvidenceSkeleton({
204
+ projectId: args.projectId,
205
+ specId: args.specId,
206
+ filename: 'discovery.json',
207
+ artifact: skeleton,
208
+ });
209
+ console.warn('[planu:evidence-skeleton]', { specId: args.specId, filename: 'discovery.json' });
210
+ artifacts = { ...artifacts, discovery: skeleton };
211
+ }
212
+ if (args.transition === 'implementing' &&
213
+ !artifacts.taskPlan &&
214
+ isArtifactAbsent(artifacts, 'Task plan evidence')) {
215
+ const skeleton = generateTaskPlanSkeleton({ criteria: args.criteria });
216
+ await writeEvidenceSkeleton({
217
+ projectId: args.projectId,
218
+ specId: args.specId,
219
+ filename: 'task-plan.json',
220
+ artifact: skeleton,
221
+ });
222
+ console.warn('[planu:evidence-skeleton]', { specId: args.specId, filename: 'task-plan.json' });
223
+ artifacts = { ...artifacts, taskPlan: skeleton };
224
+ }
225
+ return artifacts;
226
+ }
127
227
  async function readVerifiedCurrentReceipt(identity) {
128
228
  try {
129
229
  const env_1 = { stack: [], error: void 0, hasError: false };
@@ -1,6 +1,6 @@
1
- import type { SpecStatus, UpdateStatusInput } from '../../types/index.js';
1
+ import type { SpecStatus, Actuals } from '../../types/index.js';
2
2
  import type { Spec } from '../../types/spec/core.js';
3
- export declare function recordDoneMetrics(projectId: string, specId: string, spec: Spec, actuals: NonNullable<UpdateStatusInput['actuals']>, projectPath?: string): Promise<void>;
3
+ export declare function recordDoneMetrics(projectId: string, specId: string, spec: Spec, actuals: Actuals, projectPath?: string): Promise<void>;
4
4
  /**
5
5
  * SPEC-698: A frontmatter sync warning surfaced when `update_status` could not
6
6
  * keep the on-disk spec.md status in sync with the data store.
@@ -10,7 +10,7 @@ export interface FrontmatterSyncWarning {
10
10
  specPath: string | null;
11
11
  reason: string;
12
12
  }
13
- export declare function syncSpecFiles(updatedSpec: Spec, _currentStatus: SpecStatus, newStatus: SpecStatus, projectPath?: string): Promise<{
13
+ export declare function syncSpecFiles(updatedSpec: Spec, _currentStatus: SpecStatus, newStatus: SpecStatus, projectPath?: string, qualityWarnings?: readonly string[]): Promise<{
14
14
  warning?: FrontmatterSyncWarning;
15
15
  }>;
16
16
  export declare function tryReconcile(newStatus: string, specId: string, projectId: string): Promise<string | null>;
@@ -32,5 +32,6 @@ export declare function recordTerminalTransitionEvent(args: {
32
32
  modelId?: string;
33
33
  gateResults?: Record<string, 'pass' | 'fail' | 'skip' | 'forced'>;
34
34
  meta?: Record<string, unknown>;
35
+ projectPath?: string;
35
36
  }): Promise<void>;
36
37
  //# sourceMappingURL=file-sync.d.ts.map
@@ -3,11 +3,12 @@ import { metricsStore } from '../../storage/index.js';
3
3
  import { addLesson, getLessons } from '../../storage/lessons-store.js';
4
4
  import { dispatchFeedbackEvent } from '../learn.js';
5
5
  import { autoReconcileOnDone } from '../update-status-reconcile.js';
6
- import { readFile } from 'node:fs/promises';
6
+ import { lstat, readFile, realpath } from 'node:fs/promises';
7
7
  import { parseFrontmatter } from '../../engine/frontmatter-parser.js';
8
8
  import { computeFrontmatterSha } from '../../engine/frontmatter-sha/index.js';
9
9
  import { appendTransitionEvent } from '../../storage/transition-log.js';
10
10
  import { atomicWriteFile } from '../../engine/safety/atomic-write-file.js';
11
+ import { isAbsolute, normalize, relative, resolve, sep } from 'node:path';
11
12
  import { redactFailureMessage, reportClassifiedDegradation, } from '../../errors/classified-degradation.js';
12
13
  export async function recordDoneMetrics(projectId, specId, spec, actuals, projectPath) {
13
14
  if (actuals.devHours + actuals.reviewHours === 0) {
@@ -74,6 +75,46 @@ async function autoCaptureLessonEstimation(spec, actuals, accuracy, projectId, p
74
75
  }
75
76
  const SPEC_BODY_SHRINK_ABSOLUTE_THRESHOLD = 200;
76
77
  const SPEC_BODY_SHRINK_RATIO_THRESHOLD = 0.05;
78
+ async function resolveSpecPathForTransition(spec, projectPath) {
79
+ if (!projectPath) {
80
+ return spec.specPath;
81
+ }
82
+ const normalizedPath = normalize(spec.specPath);
83
+ const segments = normalizedPath.split(sep).filter(Boolean);
84
+ const planuIndex = segments.lastIndexOf('planu');
85
+ const specDirectory = segments[planuIndex + 2];
86
+ const isPortableContract = planuIndex >= 0 &&
87
+ segments[planuIndex + 1] === 'specs' &&
88
+ segments[planuIndex + 3] === 'spec.md' &&
89
+ planuIndex + 4 === segments.length &&
90
+ specDirectory !== undefined &&
91
+ (specDirectory === spec.id || specDirectory.startsWith(`${spec.id}-`));
92
+ if (!isPortableContract) {
93
+ throw new Error('SPEC_PATH_RELOCALIZATION_FAILED: specPath is not a portable spec contract');
94
+ }
95
+ const selectedRoot = resolve(projectPath);
96
+ const candidate = resolve(selectedRoot, 'planu', 'specs', specDirectory, 'spec.md');
97
+ const relativeCandidate = relative(selectedRoot, candidate);
98
+ if (relativeCandidate.startsWith(`..${sep}`) ||
99
+ relativeCandidate === '..' ||
100
+ isAbsolute(relativeCandidate)) {
101
+ throw new Error('SPEC_PATH_RELOCALIZATION_FAILED: specPath escapes the selected root');
102
+ }
103
+ const [rootRealpath, candidateRealpath, candidateStat] = await Promise.all([
104
+ realpath(selectedRoot),
105
+ realpath(candidate),
106
+ lstat(candidate),
107
+ ]);
108
+ const realRelative = relative(rootRealpath, candidateRealpath);
109
+ if (candidateStat.isSymbolicLink() ||
110
+ !candidateStat.isFile() ||
111
+ realRelative.startsWith(`..${sep}`) ||
112
+ realRelative === '..' ||
113
+ isAbsolute(realRelative)) {
114
+ throw new Error('SPEC_PATH_RELOCALIZATION_FAILED: specPath is not a contained regular file');
115
+ }
116
+ return candidateRealpath;
117
+ }
77
118
  function bodyWithoutFrontmatter(content) {
78
119
  return parseFrontmatter(content).body;
79
120
  }
@@ -95,13 +136,23 @@ function assertSpecBodyIntegrity(args) {
95
136
  throw new Error(`SPEC_INTEGRITY_WRITE_FAILED: refusing to write ${args.specId} because update_status(${args.newStatus}) would shrink spec.md body by ${String(lost)} characters without an explicit migration marker.`);
96
137
  }
97
138
  }
98
- export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, projectPath) {
139
+ export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, projectPath, qualityWarnings = []) {
99
140
  let warning;
100
141
  if (updatedSpec.specPath) {
101
142
  try {
102
- const specContent = await readFile(updatedSpec.specPath, 'utf-8');
143
+ const transitionSpecPath = await resolveSpecPathForTransition(updatedSpec, projectPath);
144
+ const specContent = await readFile(transitionSpecPath, 'utf-8');
145
+ if (projectPath) {
146
+ const parsedId = parseFrontmatter(specContent).metadata.id;
147
+ if (parsedId !== updatedSpec.id) {
148
+ throw new Error('SPEC_PATH_RELOCALIZATION_FAILED: spec frontmatter ID does not match');
149
+ }
150
+ }
103
151
  const { updateFrontmatterField } = await import('../../engine/frontmatter-parser.js');
104
152
  let updatedContent = updateFrontmatterField(specContent, 'status', newStatus);
153
+ if (qualityWarnings.length > 0) {
154
+ updatedContent = updateFrontmatterField(updatedContent, 'qualityWarnings', JSON.stringify(qualityWarnings));
155
+ }
105
156
  if (newStatus === 'done') {
106
157
  updatedContent = markAllCriteriaDone(updatedContent);
107
158
  if (isLeanSpecContent(updatedContent)) {
@@ -114,7 +165,7 @@ export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, proj
114
165
  newStatus,
115
166
  specId: updatedSpec.id,
116
167
  });
117
- await atomicWriteFile(updatedSpec.specPath, updatedContent);
168
+ await atomicWriteFile(transitionSpecPath, updatedContent);
118
169
  // SPEC-544: Auto-stage spec.md after writing so it's included in the user's next commit
119
170
  // SPEC-575: Auto-commit staged planu/ docs (idempotent, safe-fail)
120
171
  // Commit message must reflect the actual transition — 'mark-done' is only
@@ -125,7 +176,7 @@ export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, proj
125
176
  process.env.PLANU_SUPPRESS_AUTOCOMMIT !== 'true') {
126
177
  try {
127
178
  const { git: gitCmd } = await import('../git/git-helpers.js');
128
- await gitCmd(projectPath, ['add', '--', updatedSpec.specPath]);
179
+ await gitCmd(projectPath, ['add', '--', transitionSpecPath]);
129
180
  const { planuAutoCommit } = await import('../../engine/git/planu-autocommit.js');
130
181
  void planuAutoCommit({
131
182
  projectPath,
@@ -197,7 +248,7 @@ export async function recordTerminalTransitionEvent(args) {
197
248
  let frontmatterSha;
198
249
  if (args.specPath) {
199
250
  try {
200
- const content = await readFile(args.specPath, 'utf-8');
251
+ const content = await readFile(await resolveSpecPathForTransition({ id: args.specId, specPath: args.specPath }, args.projectPath), 'utf-8');
201
252
  const { metadata } = parseFrontmatter(content);
202
253
  frontmatterSha = computeFrontmatterSha(metadata);
203
254
  }