@planu/cli 4.14.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/CHANGELOG.md +35 -1
  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 +2 -2
  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 +1 -1
  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 +10 -1
  46. package/dist/engine/evidence-gates/artifact-reader.js +115 -2
  47. package/dist/engine/evidence-gates/lifecycle-gate.js +4 -3
  48. package/dist/engine/evidence-gates/reconciliation-freshness.d.ts +11 -0
  49. package/dist/engine/evidence-gates/reconciliation-freshness.js +72 -0
  50. package/dist/engine/evidence-index/done-drift.d.ts +1 -1
  51. package/dist/engine/evidence-index/done-drift.js +40 -18
  52. package/dist/engine/execution/context.js +3 -0
  53. package/dist/engine/execution/durable-job-heartbeat-runtime.d.ts +4 -0
  54. package/dist/engine/execution/durable-job-heartbeat-runtime.js +400 -0
  55. package/dist/engine/execution/job-runtime.d.ts +30 -0
  56. package/dist/engine/execution/job-runtime.js +114 -6
  57. package/dist/engine/execution/public-job-sanitizer.d.ts +4 -0
  58. package/dist/engine/execution/public-job-sanitizer.js +31 -0
  59. package/dist/engine/execution/validate-job-executor.d.ts +12 -1
  60. package/dist/engine/execution/validate-job-executor.js +549 -86
  61. package/dist/engine/execution/validate-job-payload.js +107 -50
  62. package/dist/engine/execution/validate-job-recovery.js +1 -1
  63. package/dist/engine/execution/validate-worker-runtime.d.ts +5 -0
  64. package/dist/engine/execution/validate-worker-runtime.js +90 -2
  65. package/dist/engine/execution/validate-worker-thread.js +17 -24
  66. package/dist/engine/handoff-artifacts/schemas.d.ts +2 -1
  67. package/dist/engine/handoff-artifacts/schemas.js +33 -0
  68. package/dist/engine/handoff-packager.d.ts +19 -0
  69. package/dist/engine/handoff-packager.js +415 -13
  70. package/dist/engine/hooks/effort-configurator.js +1 -1
  71. package/dist/engine/impact-detector/test-break-predictor.js +3 -3
  72. package/dist/engine/impact-detector/tool-registration.js +1 -1
  73. package/dist/engine/implementation-contract/renderer.js +23 -8
  74. package/dist/engine/lifecycle-reconciliation-io.d.ts +11 -0
  75. package/dist/engine/lifecycle-reconciliation-io.js +180 -0
  76. package/dist/engine/lifecycle-reconciliation.d.ts +16 -0
  77. package/dist/engine/lifecycle-reconciliation.js +406 -0
  78. package/dist/engine/local-first/tool-classification.js +2 -0
  79. package/dist/engine/macos-keychain-native-port.d.ts +5 -0
  80. package/dist/engine/macos-keychain-native-port.js +19 -7
  81. package/dist/engine/migrations/remove-commercial-state.d.ts +3 -0
  82. package/dist/engine/migrations/remove-commercial-state.js +343 -0
  83. package/dist/engine/network-policy.js +1 -0
  84. package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
  85. package/dist/engine/planu-core.darwin-arm64.node.sbom.json +10 -10
  86. package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
  87. package/dist/engine/planu-core.darwin-x64.node.sbom.json +10 -10
  88. package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
  89. package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +10 -10
  90. package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
  91. package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +10 -10
  92. package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
  93. package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +10 -10
  94. package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
  95. package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +10 -10
  96. package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
  97. package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +10 -10
  98. package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
  99. package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +10 -10
  100. package/dist/engine/readiness-checker.d.ts +1 -16
  101. package/dist/engine/readiness-checker.js +27 -88
  102. package/dist/engine/runtime-policy.d.ts +2 -0
  103. package/dist/engine/runtime-policy.js +42 -5
  104. package/dist/engine/runtime-security/rate-limiter.js +0 -1
  105. package/dist/engine/safety/contained-project-file.d.ts +3 -0
  106. package/dist/engine/safety/contained-project-file.js +23 -0
  107. package/dist/engine/scope-boundaries/contradiction-checker.js +63 -1
  108. package/dist/engine/scope-boundaries/scope-validator.js +45 -7
  109. package/dist/engine/session/checkpoint-writer.js +0 -9
  110. package/dist/engine/spec-format/acceptance-criteria.d.ts +6 -2
  111. package/dist/engine/spec-format/acceptance-criteria.js +77 -18
  112. package/dist/engine/spec-format/markdown-sections.d.ts +14 -0
  113. package/dist/engine/spec-format/markdown-sections.js +105 -0
  114. package/dist/engine/spec-format/read-technical-section.js +33 -5
  115. package/dist/engine/spec-format/technical-md-populator.js +3 -2
  116. package/dist/engine/spec-format/unified-spec-builder.d.ts +7 -0
  117. package/dist/engine/spec-format/unified-spec-builder.js +202 -0
  118. package/dist/engine/spec-migrator/canonical-deduplicator.d.ts +18 -0
  119. package/dist/engine/spec-migrator/canonical-deduplicator.js +479 -0
  120. package/dist/engine/spec-migrator/filesystem-import.d.ts +4 -8
  121. package/dist/engine/spec-migrator/filesystem-import.js +121 -14
  122. package/dist/engine/spec-migrator/frontmatter-parser.js +32 -15
  123. package/dist/engine/spec-migrator/index.d.ts +1 -0
  124. package/dist/engine/spec-migrator/index.js +1 -0
  125. package/dist/engine/spec-state-machine/transition-spec.d.ts +6 -1
  126. package/dist/engine/spec-state-machine/transition-spec.js +10 -1
  127. package/dist/engine/technical-enricher/index.js +16 -8
  128. package/dist/engine/technical-enricher/render-enriched.d.ts +1 -0
  129. package/dist/engine/technical-enricher/render-enriched.js +42 -14
  130. package/dist/engine/telemetry/health-checker.js +53 -17
  131. package/dist/engine/telemetry/telemetry-client.js +26 -8
  132. package/dist/engine/text-signal-boundaries.d.ts +2 -0
  133. package/dist/engine/text-signal-boundaries.js +25 -2
  134. package/dist/engine/time/relative-date.d.ts +3 -0
  135. package/dist/engine/time/relative-date.js +16 -0
  136. package/dist/engine/timing/budget.d.ts +11 -0
  137. package/dist/engine/timing/budget.js +27 -9
  138. package/dist/engine/token-optimizer/analytics.d.ts +1 -1
  139. package/dist/engine/token-optimizer/analytics.js +5 -6
  140. package/dist/engine/usage-tracker/core.d.ts +2 -2
  141. package/dist/engine/usage-tracker/core.js +1 -2
  142. package/dist/engine/usage-tracker/index.d.ts +0 -3
  143. package/dist/engine/usage-tracker/index.js +0 -3
  144. package/dist/engine/usage-tracker/stats.d.ts +2 -2
  145. package/dist/engine/usage-tracker/stats.js +1 -28
  146. package/dist/engine/validation/durable-validation.d.ts +1 -1
  147. package/dist/engine/validation/durable-validation.js +3 -82
  148. package/dist/engine/validation/validation-freshness.d.ts +11 -1
  149. package/dist/engine/validation/validation-freshness.js +231 -91
  150. package/dist/engine/validation/validation-receipt.d.ts +9 -6
  151. package/dist/engine/validation/validation-receipt.js +84 -24
  152. package/dist/engine/validation/validation-source-digest.d.ts +2 -0
  153. package/dist/engine/validation/validation-source-digest.js +86 -0
  154. package/dist/engine/validation/validation-submission.d.ts +5 -4
  155. package/dist/engine/validation/validation-submission.js +25 -11
  156. package/dist/engine/validation/validation-worktree.d.ts +21 -0
  157. package/dist/engine/validation/validation-worktree.js +260 -0
  158. package/dist/engine/validation-impact-planner.d.ts +14 -2
  159. package/dist/engine/validation-impact-planner.js +0 -3
  160. package/dist/engine/validator/dor-dod.d.ts +1 -0
  161. package/dist/engine/validator/dor-dod.js +5 -3
  162. package/dist/engine/validator/extractors.d.ts +1 -5
  163. package/dist/engine/validator/extractors.js +24 -69
  164. package/dist/engine/validator/spec-compliance-runner.d.ts +12 -24
  165. package/dist/engine/validator/spec-compliance-runner.js +429 -211
  166. package/dist/engine/validator.js +35 -22
  167. package/dist/index.js +4 -16
  168. package/dist/resources/usage-stats-resource.js +2 -18
  169. package/dist/security/adapters/linux-secret-service.js +7 -3
  170. package/dist/security/adapters/windows-credential-manager.js +6 -4
  171. package/dist/server/routes/specs.js +10 -0
  172. package/dist/storage/base-store.d.ts +1 -1
  173. package/dist/storage/base-store.js +1 -1
  174. package/dist/storage/feedback-remote.d.ts +2 -3
  175. package/dist/storage/feedback-remote.js +45 -31
  176. package/dist/storage/global-store.d.ts +1 -17
  177. package/dist/storage/global-store.js +1 -42
  178. package/dist/storage/index.d.ts +0 -2
  179. package/dist/storage/index.js +0 -2
  180. package/dist/storage/migrations/canonical-storage.js +124 -11
  181. package/dist/storage/runtime-db.d.ts +9 -0
  182. package/dist/storage/runtime-db.js +64 -8
  183. package/dist/storage/spec-store.d.ts +14 -2
  184. package/dist/storage/spec-store.js +104 -14
  185. package/dist/storage/status-store/atomic-status-write.d.ts +5 -0
  186. package/dist/storage/status-store/atomic-status-write.js +57 -0
  187. package/dist/storage/status-store/file-lock.js +30 -11
  188. package/dist/storage/status-store/self-healing.js +3 -3
  189. package/dist/storage/status-store/version-sync.d.ts +2 -1
  190. package/dist/storage/status-store/version-sync.js +35 -40
  191. package/dist/storage/usage-store.d.ts +1 -5
  192. package/dist/storage/usage-store.js +0 -35
  193. package/dist/tools/auto-update-handler.d.ts +3 -3
  194. package/dist/tools/auto-update-handler.js +3 -3
  195. package/dist/tools/challenge-spec/resilience-challenge-scenarios.d.ts +2 -1
  196. package/dist/tools/challenge-spec/resilience-challenge-scenarios.js +10 -7
  197. package/dist/tools/challenge-spec/scenario-collector.js +1 -1
  198. package/dist/tools/challenge-spec/scenarios-data.js +1 -1
  199. package/dist/tools/challenge-spec/scenarios-utils.d.ts +1 -1
  200. package/dist/tools/challenge-spec/scenarios-utils.js +35 -11
  201. package/dist/tools/challenge-spec.js +5 -5
  202. package/dist/tools/check-readiness.js +1 -1
  203. package/dist/tools/configure-telemetry.js +30 -2
  204. package/dist/tools/create-spec/spec-builder.js +0 -11
  205. package/dist/tools/create-spec.d.ts +1 -0
  206. package/dist/tools/create-spec.js +444 -239
  207. package/dist/tools/git/branch-ops.d.ts +1 -1
  208. package/dist/tools/git/branch-ops.js +3 -2
  209. package/dist/tools/init-project/handler.js +10 -12
  210. package/dist/tools/init-project/host-assets-writer.js +26 -1
  211. package/dist/tools/init-project/lint-scaffolder.d.ts +3 -3
  212. package/dist/tools/init-project/migration-runner.d.ts +2 -4
  213. package/dist/tools/init-project/migration-runner.js +3 -2
  214. package/dist/tools/init-project/portable-index-reconciler.d.ts +4 -0
  215. package/dist/tools/init-project/portable-index-reconciler.js +14 -0
  216. package/dist/tools/init-project/result-builder.js +1 -3
  217. package/dist/tools/jobs/handlers.js +38 -6
  218. package/dist/tools/list-specs.js +5 -26
  219. package/dist/tools/merge-risk-handler.js +2 -2
  220. package/dist/tools/package-handoff.js +2 -0
  221. package/dist/tools/reconcile-spec.js +0 -27
  222. package/dist/tools/register-agent-squad-tools.js +2 -2
  223. package/dist/tools/register-platform-tools/design-stack-tools.js +11 -11
  224. package/dist/tools/register-platform-tools/lifecycle-infra-tools.js +7 -7
  225. package/dist/tools/register-sdd-tools.d.ts +1 -1
  226. package/dist/tools/register-sdd-tools.js +2 -29
  227. package/dist/tools/register-spec-tools/analysis-tools.js +8 -8
  228. package/dist/tools/register-spec-tools/core-spec-tools.js +17 -8
  229. package/dist/tools/safe-handler.d.ts +5 -13
  230. package/dist/tools/safe-handler.js +7 -23
  231. package/dist/tools/schemas/index.d.ts +1 -1
  232. package/dist/tools/schemas/index.js +1 -1
  233. package/dist/tools/schemas/output-schemas.d.ts +0 -23
  234. package/dist/tools/schemas/output-schemas.js +0 -28
  235. package/dist/tools/schemas/spec.d.ts +4 -0
  236. package/dist/tools/schemas/spec.js +3 -0
  237. package/dist/tools/schemas/validate-output-schema.d.ts +11 -0
  238. package/dist/tools/schemas/validate-output-schema.js +7 -0
  239. package/dist/tools/spec-prompt-handler.js +3 -3
  240. package/dist/tools/sync-spec-state-handler.d.ts +1 -1
  241. package/dist/tools/sync-spec-state-handler.js +1 -1
  242. package/dist/tools/tool-entry.d.ts +4 -4
  243. package/dist/tools/tool-entry.js +3 -3
  244. package/dist/tools/tool-registry/core-tools.js +31 -15
  245. package/dist/tools/tool-registry/group-infra.js +27 -110
  246. package/dist/tools/tool-registry/group-quality-compliance.js +26 -26
  247. package/dist/tools/tool-registry/inventory.d.ts +2 -0
  248. package/dist/tools/tool-registry/inventory.js +2 -0
  249. package/dist/tools/tool-registry-helpers.d.ts +3 -3
  250. package/dist/tools/tool-registry-helpers.js +3 -3
  251. package/dist/tools/tool-usage-report/registered-tools.d.ts +1 -1
  252. package/dist/tools/tool-usage-report/registered-tools.js +3 -15
  253. package/dist/tools/update-status/approval-gates.js +3 -0
  254. package/dist/tools/update-status/done-receipt-verifier.js +40 -27
  255. package/dist/tools/update-status/evidence-gate.js +36 -1
  256. package/dist/tools/update-status/file-sync.d.ts +2 -1
  257. package/dist/tools/update-status/file-sync.js +57 -6
  258. package/dist/tools/update-status/index.d.ts +2 -0
  259. package/dist/tools/update-status/index.js +95 -58
  260. package/dist/tools/update-status/response-builder.js +3 -3
  261. package/dist/tools/update-status/side-effects.d.ts +2 -0
  262. package/dist/tools/update-status/side-effects.js +2 -2
  263. package/dist/tools/update-status/transition-guard.js +32 -15
  264. package/dist/tools/update-status-actions.d.ts +4 -2
  265. package/dist/tools/update-status-actions.js +16 -14
  266. package/dist/tools/usage-report.js +1 -16
  267. package/dist/tools/usage-stats.js +1 -18
  268. package/dist/tools/usage-tracking.d.ts +4 -0
  269. package/dist/tools/usage-tracking.js +20 -0
  270. package/dist/tools/validate-lint.d.ts +1 -0
  271. package/dist/tools/validate-lint.js +1 -1
  272. package/dist/tools/validate.d.ts +2 -1
  273. package/dist/tools/validate.js +114 -34
  274. package/dist/transports/http-transport.js +6 -8
  275. package/dist/transports/mcp-types.d.ts +0 -2
  276. package/dist/transports/session-context.js +1 -1
  277. package/dist/types/cascade-hooks.d.ts +1 -1
  278. package/dist/types/ci.d.ts +0 -4
  279. package/dist/types/clarification-token.d.ts +1 -1
  280. package/dist/types/claude-code-runtime.d.ts +0 -27
  281. package/dist/types/commercial-migration.d.ts +17 -0
  282. package/dist/types/commercial-migration.js +2 -0
  283. package/dist/types/common/primitives.d.ts +0 -2
  284. package/dist/types/core-bridge.d.ts +4 -10
  285. package/dist/types/dashboard.d.ts +3 -7
  286. package/dist/types/docs.d.ts +0 -1
  287. package/dist/types/durable-job.d.ts +4 -4
  288. package/dist/types/durable-validation.d.ts +100 -2
  289. package/dist/types/durable-validation.js +165 -1
  290. package/dist/types/errors/structured-error.d.ts +3 -3
  291. package/dist/types/estimation.d.ts +2 -0
  292. package/dist/types/evidence-gates.d.ts +9 -1
  293. package/dist/types/execution.d.ts +2 -0
  294. package/dist/types/gemini-integration.d.ts +0 -11
  295. package/dist/types/handoff-artifacts.d.ts +41 -0
  296. package/dist/types/hooks-advanced.d.ts +0 -13
  297. package/dist/types/impact-detection.d.ts +2 -2
  298. package/dist/types/index.d.ts +2 -6
  299. package/dist/types/index.js +1 -5
  300. package/dist/types/kiro-integration.d.ts +0 -6
  301. package/dist/types/network-policy.d.ts +1 -1
  302. package/dist/types/project/lint-scaffolder.d.ts +1 -1
  303. package/dist/types/runtime-policy.d.ts +11 -0
  304. package/dist/types/session-safeguard.d.ts +0 -31
  305. package/dist/types/spec/canonical-repair.d.ts +81 -0
  306. package/dist/types/spec/canonical-repair.js +2 -0
  307. package/dist/types/spec/core.d.ts +14 -0
  308. package/dist/types/spec/index.d.ts +1 -0
  309. package/dist/types/spec/index.js +1 -0
  310. package/dist/types/spec/inputs.d.ts +29 -0
  311. package/dist/types/spec-format.d.ts +24 -0
  312. package/dist/types/telemetry.d.ts +4 -1
  313. package/dist/types/transport.d.ts +0 -2
  314. package/dist/types/usage.d.ts +1 -36
  315. package/dist/types/usage.js +1 -1
  316. package/dist/types/validate-worker.d.ts +6 -0
  317. package/dist/types/validation-receipt.d.ts +1 -1
  318. package/dist/types/validation.d.ts +2 -2
  319. package/package.json +16 -12
  320. package/planu-native.json +1 -1
  321. package/planu-plugin.json +23 -40
  322. package/scripts/lib/pending-release-file.mjs +93 -0
  323. package/src/i18n/messages/en.json +0 -41
  324. package/src/i18n/messages/es.json +0 -41
  325. package/src/i18n/messages/pt.json +0 -41
  326. package/dist/cli/commands/activate.d.ts +0 -14
  327. package/dist/cli/commands/activate.js +0 -174
  328. package/dist/config/license-plans.json +0 -251
  329. package/dist/config/product-public.ts +0 -148
  330. package/dist/config/server-instructions.ts +0 -121
  331. package/dist/config/version-defaults.ts +0 -76
  332. package/dist/config/version.ts +0 -21
  333. package/dist/engine/ai-integration/gemini/settings-generator.d.ts +0 -4
  334. package/dist/engine/ai-integration/gemini/settings-generator.js +0 -195
  335. package/dist/engine/ai-integration/kiro/hooks-generator.d.ts +0 -6
  336. package/dist/engine/ai-integration/kiro/hooks-generator.js +0 -75
  337. package/dist/engine/hooks/full-spectrum-generator.d.ts +0 -32
  338. package/dist/engine/hooks/full-spectrum-generator.js +0 -117
  339. package/dist/engine/license-validator/config-loader.d.ts +0 -5
  340. package/dist/engine/license-validator/config-loader.js +0 -16
  341. package/dist/engine/license-validator/core.d.ts +0 -15
  342. package/dist/engine/license-validator/core.js +0 -151
  343. package/dist/engine/license-validator/fingerprint.d.ts +0 -3
  344. package/dist/engine/license-validator/fingerprint.js +0 -17
  345. package/dist/engine/license-validator/lemon-squeezy.d.ts +0 -5
  346. package/dist/engine/license-validator/lemon-squeezy.js +0 -55
  347. package/dist/engine/license-validator.d.ts +0 -5
  348. package/dist/engine/license-validator.js +0 -6
  349. package/dist/engine/product-intelligence/index.d.ts +0 -7
  350. package/dist/engine/product-intelligence/index.js +0 -211
  351. package/dist/engine/session/auto-save.d.ts +0 -18
  352. package/dist/engine/session/auto-save.js +0 -98
  353. package/dist/engine/session/context-snapshot.d.ts +0 -8
  354. package/dist/engine/session/context-snapshot.js +0 -49
  355. package/dist/engine/session/memory-sync.d.ts +0 -12
  356. package/dist/engine/session/memory-sync.js +0 -34
  357. package/dist/engine/session/session-diff.d.ts +0 -11
  358. package/dist/engine/session/session-diff.js +0 -42
  359. package/dist/engine/session/session-merge.d.ts +0 -12
  360. package/dist/engine/session/session-merge.js +0 -55
  361. package/dist/engine/session-safeguard/checkpoint-runner.d.ts +0 -4
  362. package/dist/engine/session-safeguard/checkpoint-runner.js +0 -70
  363. package/dist/engine/session-safeguard/learnings-buffer.d.ts +0 -15
  364. package/dist/engine/session-safeguard/learnings-buffer.js +0 -91
  365. package/dist/engine/session-safeguard/precompaction-drain.d.ts +0 -10
  366. package/dist/engine/session-safeguard/precompaction-drain.js +0 -82
  367. package/dist/engine/session-safeguard/unpushed-detector.d.ts +0 -7
  368. package/dist/engine/session-safeguard/unpushed-detector.js +0 -59
  369. package/dist/engine/spec-changelog/core.d.ts +0 -16
  370. package/dist/engine/spec-changelog/core.js +0 -176
  371. package/dist/engine/spec-changelog/diff.d.ts +0 -18
  372. package/dist/engine/spec-changelog/diff.js +0 -121
  373. package/dist/engine/spec-changelog/index.d.ts +0 -3
  374. package/dist/engine/spec-changelog/index.js +0 -4
  375. package/dist/engine/trial-engine.d.ts +0 -5
  376. package/dist/engine/trial-engine.js +0 -14
  377. package/dist/engine/usage-tracker/rate-check.d.ts +0 -7
  378. package/dist/engine/usage-tracker/rate-check.js +0 -18
  379. package/dist/engine/usage-tracker/rate-limiter.d.ts +0 -14
  380. package/dist/engine/usage-tracker/rate-limiter.js +0 -73
  381. package/dist/engine/usage-tracker/trial.d.ts +0 -27
  382. package/dist/engine/usage-tracker/trial.js +0 -49
  383. package/dist/engine/webhook/event-handlers.d.ts +0 -25
  384. package/dist/engine/webhook/event-handlers.js +0 -89
  385. package/dist/engine/webhook/index.d.ts +0 -4
  386. package/dist/engine/webhook/index.js +0 -5
  387. package/dist/engine/webhook/server.d.ts +0 -12
  388. package/dist/engine/webhook/server.js +0 -249
  389. package/dist/engine/webhook/signature.d.ts +0 -16
  390. package/dist/engine/webhook/signature.js +0 -49
  391. package/dist/storage/license-store.d.ts +0 -6
  392. package/dist/storage/license-store.js +0 -63
  393. package/dist/storage/session-store.d.ts +0 -20
  394. package/dist/storage/session-store.js +0 -136
  395. package/dist/tools/activate-license.d.ts +0 -6
  396. package/dist/tools/activate-license.js +0 -132
  397. package/dist/tools/checkpoint-handler.d.ts +0 -3
  398. package/dist/tools/checkpoint-handler.js +0 -130
  399. package/dist/tools/export-session.d.ts +0 -3
  400. package/dist/tools/export-session.js +0 -90
  401. package/dist/tools/license-gate.d.ts +0 -18
  402. package/dist/tools/license-gate.js +0 -222
  403. package/dist/tools/license-status.d.ts +0 -3
  404. package/dist/tools/license-status.js +0 -93
  405. package/dist/tools/list-sessions.d.ts +0 -3
  406. package/dist/tools/list-sessions.js +0 -46
  407. package/dist/tools/planu-session-checkpoint.d.ts +0 -6
  408. package/dist/tools/planu-session-checkpoint.js +0 -42
  409. package/dist/tools/restore-session.d.ts +0 -3
  410. package/dist/tools/restore-session.js +0 -71
  411. package/dist/tools/spec-history.d.ts +0 -3
  412. package/dist/tools/spec-history.js +0 -59
  413. package/dist/tools/webhook.d.ts +0 -6
  414. package/dist/tools/webhook.js +0 -150
  415. package/dist/types/changelog.d.ts +0 -49
  416. package/dist/types/changelog.js +0 -3
  417. package/dist/types/licensing.d.ts +0 -109
  418. package/dist/types/licensing.js +0 -4
  419. package/dist/types/product-intelligence.d.ts +0 -60
  420. package/dist/types/product-intelligence.js +0 -3
  421. package/dist/types/session.d.ts +0 -84
  422. package/dist/types/session.js +0 -3
  423. package/dist/types/webhook.d.ts +0 -62
  424. package/dist/types/webhook.js +0 -4
@@ -13,8 +13,8 @@ import { getAsyncAnalysisPath } from './create-spec/post-creation.js';
13
13
  import { extractCriteria, generateLeanSpecContent, } from '../engine/spec-format/lean-spec-generator.js';
14
14
  import { generateLeanTechnicalContent, } from '../engine/spec-format/lean-technical-generator.js';
15
15
  import { extractFilesFromSpecBody } from '../engine/spec-format/technical-md-populator.js';
16
- import { buildUnifiedSpecContent } from '../engine/spec-format/unified-spec-builder.js';
17
- import { appendImplementationContractIfMissing } from '../engine/implementation-contract/index.js';
16
+ import { buildCanonicalUnifiedSpecContent, validateUnifiedSpecCandidate, } from '../engine/spec-format/unified-spec-builder.js';
17
+ import { buildImplementationContractSection } from '../engine/implementation-contract/index.js';
18
18
  import { validateEnglishOnlySpecText } from '../engine/spec-language/english-only.js';
19
19
  import { FallbackGenerator } from '../engine/spec-generator/index.js';
20
20
  import { analyzeProjectForSpec, getEmptyAutopilotResult, } from './create-spec/autopilot-analyzer.js';
@@ -31,6 +31,8 @@ import { buildCriterionGroundingRecords, filterGroundedCriteria, getAdvisoryCrit
31
31
  import { checkGenericSpecOutput } from '../engine/spec-quality/generic-output-gate.js';
32
32
  import { calculateActionableSpecMetrics, shouldExposeMetric, } from '../engine/spec-metrics/actionable-metrics.js';
33
33
  import { RuntimeDatabase } from '../storage/runtime-db.js';
34
+ import { BoundaryFailure } from '../errors/error-taxonomy.js';
35
+ import { SpecAcFormatEnum, SpecScopeEnum, SpecTargetEnum, SpecTypeEnum } from './schemas/spec.js';
34
36
  import { SPEC_CREATED_POST_COMMIT_TASKS } from '../types/outbox-worker.js';
35
37
  import { resolveStorageLayout } from '../storage/storage-layout.js';
36
38
  import { OperationJournal, recoverOperationJournal, } from '../engine/execution/operation-journal.js';
@@ -185,8 +187,15 @@ function buildAdvisoryCompat() {
185
187
  };
186
188
  }
187
189
  /** Persist only out-of-scope items supplied explicitly by the caller. */
188
- function resolveOutOfScope(provided) {
189
- return provided?.filter((item) => item.trim().length > 0) ?? [];
190
+ function resolveOutOfScope(provided, source = '') {
191
+ const explicit = provided?.filter((item) => item.trim().length > 0) ?? [];
192
+ if (explicit.length > 0) {
193
+ return explicit;
194
+ }
195
+ const section = /(?:^|\n)##\s+Out of scope\s*\n([\s\S]*?)(?=\n##\s|$)/i.exec(source)?.[1];
196
+ return section
197
+ ? Array.from(section.matchAll(/^[-*]\s+(.+)$/gm), (match) => match[1]?.trim() ?? '').filter(Boolean)
198
+ : [];
190
199
  }
191
200
  /** SPEC-783: Synthesize agent team findings into unified spec.md ## Technical section. */
192
201
  async function handleAgentTeamSynthesis(specId, projectPath, findings) {
@@ -330,9 +339,12 @@ function buildCommittedCreateResult(input) {
330
339
  deprecatedAlias: 'riskWarning',
331
340
  }));
332
341
  }
333
- return toolResult(`Spec ${spec.id} created and committed.`, {
342
+ return toolResult(`Spec ${spec.id} created and persisted.`, {
334
343
  operationId: input.operationId,
344
+ /** @deprecated Operation-journal durability; this is not Git evidence. */
335
345
  committed: true,
346
+ persisted: true,
347
+ gitCommitPerformed: false,
336
348
  pendingAnalysis: true,
337
349
  specId: spec.id,
338
350
  title: spec.title,
@@ -711,28 +723,27 @@ async function buildIdempotencyMatchResult(projectPath, existing) {
711
723
  };
712
724
  }
713
725
  const FRONTMATTER_ID_RE = /^id:\s*(SPEC-\d+)/m;
714
- const FRONTMATTER_TITLE_RE = /^title:\s*"?([^"\n]+?)"?\s*$/m;
715
726
  const FRONTMATTER_STATUS_RE = /^status:\s*(\S+)/m;
716
- /** SPEC-770: Scan store and filesystem for a spec with matching idempotency key within windowMs. */
717
- async function findByIdempotencyKey(projectPath, key, projectId, windowMs = IDEMPOTENCY_MATCH_WINDOW_MS) {
718
- const cutoff = Date.now() - windowMs;
719
- // Fast path: check store (always authoritative for successfully created specs)
727
+ async function findStoreByIdempotencyKey(projectId, key, cutoff) {
720
728
  try {
721
729
  const existing = await specStore.listSpecs(projectId);
722
- const match = existing.find((s) => s.idempotencyKey === key && new Date(s.createdAt).getTime() > cutoff);
723
- if (match !== undefined) {
724
- return match;
725
- }
730
+ return existing.find((s) => s.idempotencyKey === key && new Date(s.createdAt).getTime() > cutoff);
726
731
  }
727
732
  catch {
728
- /* best-effort */
733
+ return undefined;
734
+ }
735
+ }
736
+ /** SPEC-770: Scan store and filesystem for a spec with matching idempotency key within windowMs. */
737
+ async function findByIdempotencyKey(projectPath, key, projectId, windowMs = IDEMPOTENCY_MATCH_WINDOW_MS) {
738
+ const cutoff = Date.now() - windowMs;
739
+ const storeMatch = await findStoreByIdempotencyKey(projectId, key, cutoff);
740
+ if (storeMatch) {
741
+ return storeMatch;
729
742
  }
730
- // Transactional fallback: the request key stays in external project data, not spec.md.
731
743
  const evidenceMatch = await findByIdempotencyEvidence(projectPath, key, cutoff);
732
744
  if (evidenceMatch !== undefined) {
733
745
  return evidenceMatch;
734
746
  }
735
- // Compatibility fallback for specs created before external evidence was introduced.
736
747
  try {
737
748
  const { glob } = await import('glob');
738
749
  const { join: joinPath } = await import('node:path');
@@ -762,7 +773,7 @@ async function findByIdempotencyKey(projectPath, key, projectId, windowMs = IDEM
762
773
  return undefined;
763
774
  }
764
775
  /** SPEC-770: Scan for recently written spec.md files (for possibleDuplicates in timeout response). */
765
- async function findRecentlyWrittenSpecs(projectPath, windowMs) {
776
+ async function findRecentlyWrittenSpecs(projectPath, windowMs, requestedTitle) {
766
777
  try {
767
778
  const { glob } = await import('glob');
768
779
  const { join: joinPath } = await import('node:path');
@@ -776,9 +787,11 @@ async function findRecentlyWrittenSpecs(projectPath, windowMs) {
776
787
  }
777
788
  const content = await readFile(specFile, 'utf-8').catch(() => '');
778
789
  const idMatch = FRONTMATTER_ID_RE.exec(content);
779
- const titleMatch = FRONTMATTER_TITLE_RE.exec(content);
780
- if (idMatch?.[1] !== undefined) {
781
- results.push({ id: idMatch[1], title: titleMatch?.[1] ?? '' });
790
+ const title = extractFrontmatterTitle(content);
791
+ if (idMatch?.[1] !== undefined &&
792
+ normalizeDuplicateTitle(title) !== '' &&
793
+ normalizeDuplicateTitle(title) === normalizeDuplicateTitle(requestedTitle)) {
794
+ results.push({ id: idMatch[1], title });
782
795
  }
783
796
  }
784
797
  return results;
@@ -787,6 +800,69 @@ async function findRecentlyWrittenSpecs(projectPath, windowMs) {
787
800
  return [];
788
801
  }
789
802
  }
803
+ function extractFrontmatterTitle(content) {
804
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content)?.[1];
805
+ const rawTitle = frontmatter ? /^title:\s*(.*?)\s*$/m.exec(frontmatter)?.[1] : undefined;
806
+ if (!rawTitle) {
807
+ return '';
808
+ }
809
+ if (rawTitle.startsWith('"') && rawTitle.endsWith('"')) {
810
+ try {
811
+ const parsed = JSON.parse(rawTitle);
812
+ return typeof parsed === 'string' ? parsed : '';
813
+ }
814
+ catch {
815
+ return '';
816
+ }
817
+ }
818
+ if (rawTitle.startsWith("'") && rawTitle.endsWith("'")) {
819
+ return rawTitle.slice(1, -1).replace(/''/g, "'");
820
+ }
821
+ return rawTitle;
822
+ }
823
+ function normalizeDuplicateTitle(value) {
824
+ return value
825
+ .normalize('NFKC')
826
+ .toLocaleLowerCase('en-US')
827
+ .replace(/[\p{P}]+/gu, ' ')
828
+ .replace(/\s+/g, ' ')
829
+ .trim();
830
+ }
831
+ const CREATE_SPEC_ENUMS = {
832
+ type: SpecTypeEnum,
833
+ scope: SpecScopeEnum,
834
+ target: SpecTargetEnum,
835
+ acFormat: SpecAcFormatEnum,
836
+ };
837
+ function validateCreateSpecEnums(input) {
838
+ for (const [field, schema] of Object.entries(CREATE_SPEC_ENUMS)) {
839
+ const received = input[field];
840
+ if (received === undefined || schema.safeParse(received).success) {
841
+ continue;
842
+ }
843
+ const boundedReceived = received === null || ['string', 'number', 'boolean'].includes(typeof received)
844
+ ? received
845
+ : typeof received;
846
+ return {
847
+ content: [
848
+ {
849
+ type: 'text',
850
+ text: `${field} must be one of: ${schema.options.join(', ')}.`,
851
+ },
852
+ ],
853
+ isError: true,
854
+ structuredContent: {
855
+ error: 'INVALID_INPUT',
856
+ code: 'INVALID_INPUT',
857
+ status: 422,
858
+ field,
859
+ received: boundedReceived,
860
+ allowed: schema.options,
861
+ },
862
+ };
863
+ }
864
+ return null;
865
+ }
790
866
  const DESCRIPTION_MAX_CHARS = 10_000;
791
867
  const DESCRIPTION_EXCEED_MSG = 'Description exceeds 10000 chars. Create with a summary (<3k) and use reconcile_spec to add the rest.';
792
868
  function checkEnglishOnlyInput(params) {
@@ -811,8 +887,260 @@ function checkEnglishOnlyInput(params) {
811
887
  },
812
888
  };
813
889
  }
890
+ function quotePosixShellArgument(value) {
891
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
892
+ }
893
+ function earlyPreparation(result, clarificationProjectId) {
894
+ return {
895
+ kind: 'early',
896
+ result,
897
+ ...(clarificationProjectId ? { clarificationProjectId } : {}),
898
+ };
899
+ }
900
+ export async function resolveGroundedVerificationCommands(projectPath, testPaths) {
901
+ if (testPaths.length === 0) {
902
+ return [];
903
+ }
904
+ const packageJsonRaw = await readFile(pathJoin(projectPath, 'package.json'), 'utf8').catch(() => '');
905
+ if (!packageJsonRaw) {
906
+ return [];
907
+ }
908
+ try {
909
+ const manifest = JSON.parse(packageJsonRaw);
910
+ if (typeof manifest.packageManager !== 'string') {
911
+ return [];
912
+ }
913
+ const packageManager = /^(pnpm|npm|yarn|bun)@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u.exec(manifest.packageManager)?.[1];
914
+ if (!packageManager) {
915
+ return [];
916
+ }
917
+ if (typeof manifest.scripts?.test !== 'string' || manifest.scripts.test.trim() === '') {
918
+ return [];
919
+ }
920
+ return [`${packageManager} run test -- ${testPaths.map(quotePosixShellArgument).join(' ')}`];
921
+ }
922
+ catch {
923
+ return [];
924
+ }
925
+ }
926
+ async function prepareCreateSpecCandidate(initialParams, server) {
927
+ let params = initialParams;
928
+ const buildResult = await measureStep('buildSpecContext', () => buildSpecContext(params));
929
+ if (!buildResult.ok) {
930
+ return earlyPreparation({
931
+ content: [{ type: 'text', text: buildResult.errorMessage }],
932
+ isError: true,
933
+ });
934
+ }
935
+ const context = buildResult.context;
936
+ const { spec, estimation, projectId } = context;
937
+ const knowledge = await measureStep('loadKnowledge', () => knowledgeStore.getKnowledge(projectId));
938
+ const clarificationSession = params.clarificationSessionId
939
+ ? await knowledgeStore.getClarification(projectId, params.clarificationSessionId)
940
+ : null;
941
+ const constitutionCheck = await measureStep('validateConstitution', () => validateConstitution(projectId, spec.title, spec.tags));
942
+ if (constitutionCheck.blocked && constitutionCheck.errorResult) {
943
+ return earlyPreparation(constitutionCheck.errorResult);
944
+ }
945
+ const inlineAnalysis = await withBudget('create-spec-inline-analysis', getRuntimePolicy().createSpec.inlineAnalysisBudgetMs, (signal) => analyzeProjectForSpec(params.projectPath ?? '', params.description, spec.title, knowledge, signal));
946
+ const autopilot = unwrapBudget(inlineAnalysis, getEmptyAutopilotResult());
947
+ if (autopilot.isIdea) {
948
+ return earlyPreparation({
949
+ content: [
950
+ {
951
+ type: 'text',
952
+ text: `💡 **Idea captured**: "${params.description}"\n\nThe description is too brief for a full spec. Use \`capture_idea\` to save it to the backlog, or provide more detail (>10 words) to create a spec.`,
953
+ },
954
+ ],
955
+ });
956
+ }
957
+ const clarificationResult = handleClarification(server, params.description, knowledge, autopilot, params);
958
+ if (clarificationResult !== null) {
959
+ if ('earlyReturn' in clarificationResult) {
960
+ return earlyPreparation(clarificationResult.earlyReturn, projectId);
961
+ }
962
+ params = clarificationResult.params;
963
+ }
964
+ const filteredCriteria = filterGroundedCriteria(autopilot.suggestedCriteria);
965
+ const technologyContract = await readTechnologySelectionContract(params.projectPath ?? '');
966
+ const contractNote = technologyContract
967
+ ? [
968
+ '',
969
+ 'Technology Contract:',
970
+ `- Mode: ${technologyContract.mode}`,
971
+ technologyContract.language ? `- Language: ${technologyContract.language}` : '',
972
+ technologyContract.framework
973
+ ? `- Framework: ${technologyContract.framework}`
974
+ : '- Framework: none or not selected',
975
+ technologyContract.platform ? `- Platform: ${technologyContract.platform}` : '',
976
+ technologyContract.projectType ? `- Project type: ${technologyContract.projectType}` : '',
977
+ '- Agents must not choose a different stack unless the user approves a new contract.',
978
+ ]
979
+ .filter(Boolean)
980
+ .join('\n')
981
+ : '';
982
+ const generatedSpec = await measureStep('generateSpecBody', () => new FallbackGenerator().generate({
983
+ title: spec.title,
984
+ description: `${params.description}${contractNote}`,
985
+ type: spec.type,
986
+ scope: spec.scope,
987
+ target: spec.target,
988
+ acFormat: params.acFormat,
989
+ projectContext: {
990
+ language: technologyContract?.language ?? knowledge?.language ?? undefined,
991
+ framework: technologyContract?.framework ?? knowledge?.framework ?? undefined,
992
+ architecture: knowledge?.architecture.primary ?? undefined,
993
+ },
994
+ }));
995
+ spec.generation = generatedSpec.generation;
996
+ spec.qualityWarnings =
997
+ generatedSpec.qualityWarnings.length > 0 ? generatedSpec.qualityWarnings : undefined;
998
+ const baseCriteria = extractCriteria(generatedSpec.specBody).map((criterion) => criterion.text);
999
+ const groundingCriteria = buildCriterionGroundingRecords({
1000
+ criteria: [...baseCriteria, ...filteredCriteria],
1001
+ userInput: params.description,
1002
+ generatedEvidence: [generatedSpec.generation.modelId ?? generatedSpec.generation.method],
1003
+ });
1004
+ const contractCriteria = getContractCriteria(groundingCriteria);
1005
+ const advisoryCriteria = getAdvisoryCriteria(groundingCriteria).map((record) => record.text);
1006
+ if (contractCriteria.length === 0) {
1007
+ return earlyPreparation({
1008
+ content: [
1009
+ {
1010
+ type: 'text',
1011
+ text: 'create_spec did not persist a spec because no acceptance criterion was grounded in the user request. ' +
1012
+ 'Needs decision: provide at least one explicit, verifiable criterion as a checkbox or Given/When/Then outcome.',
1013
+ },
1014
+ ],
1015
+ isError: true,
1016
+ structuredContent: {
1017
+ error: 'MISSING_GROUNDED_ACCEPTANCE_CRITERIA',
1018
+ missingDecision: 'Provide at least one explicit, verifiable acceptance criterion.',
1019
+ persisted: false,
1020
+ },
1021
+ });
1022
+ }
1023
+ const actionableMetrics = calculateActionableSpecMetrics({
1024
+ criteria: [...baseCriteria, ...filteredCriteria],
1025
+ groundingRecords: groundingCriteria,
1026
+ }).filter(shouldExposeMetric);
1027
+ const technicalFiles = await measureStep('resolveTechnicalFiles', () => resolveTechnicalFiles({
1028
+ generatedSpecBody: generatedSpec.specBody,
1029
+ generatedTechnicalSection: generatedSpec.technicalSection,
1030
+ projectPath: params.projectPath ?? '',
1031
+ }));
1032
+ const groundedTechnical = await measureStep('groundTechnicalFiles', () => groundTechnicalFiles({
1033
+ files: technicalFiles,
1034
+ projectPath: params.projectPath ?? '',
1035
+ userInput: params.description,
1036
+ autopilot,
1037
+ }));
1038
+ const outOfScope = resolveOutOfScope(params.outOfScope, params.description);
1039
+ const scenarioTestPaths = groundedTechnical.files.test.map((file) => file.path);
1040
+ const criteria = contractCriteria.map((record) => ({ text: record.text, done: false }));
1041
+ const leanSpec = generateLeanSpecContent({
1042
+ spec,
1043
+ description: generatedSpec.specBody,
1044
+ estimation,
1045
+ criteriaOverride: criteria,
1046
+ groundingCriteria: contractCriteria,
1047
+ groundingTechnicalReferences: groundedTechnical.records,
1048
+ acFormat: params.acFormat,
1049
+ scenarioTestPaths,
1050
+ });
1051
+ const leanTechnical = generateLeanTechnicalContent({
1052
+ specId: spec.id,
1053
+ filesToCreate: groundedTechnical.files.create,
1054
+ filesToModify: groundedTechnical.files.modify,
1055
+ filesToTest: groundedTechnical.files.test,
1056
+ includeDecisionRequiredPlaceholder: spec.scope !== 'trivial',
1057
+ });
1058
+ const verificationCommands = await resolveGroundedVerificationCommands(params.projectPath ?? '', scenarioTestPaths);
1059
+ const implementationContract = buildImplementationContractSection({
1060
+ description: generatedSpec.specBody,
1061
+ criteria,
1062
+ files: groundedTechnical.files,
1063
+ outOfScope,
1064
+ verificationCommands,
1065
+ });
1066
+ const unifiedSpec = buildCanonicalUnifiedSpecContent({
1067
+ leanSpecBody: leanSpec,
1068
+ sourceDescription: params.description,
1069
+ technicalBody: leanTechnical,
1070
+ implementationContract,
1071
+ criteria,
1072
+ files: groundedTechnical.files,
1073
+ outOfScope,
1074
+ });
1075
+ const candidateValidation = validateUnifiedSpecCandidate(unifiedSpec, {
1076
+ groundedFilePaths: groundedTechnical.records.map((record) => record.path),
1077
+ });
1078
+ if (!candidateValidation.valid) {
1079
+ return earlyPreparation({
1080
+ content: [
1081
+ {
1082
+ type: 'text',
1083
+ text: `Unified spec candidate is invalid: ${candidateValidation.issues
1084
+ .slice(0, 3)
1085
+ .map((issue) => issue.message)
1086
+ .join('; ')}`,
1087
+ },
1088
+ ],
1089
+ isError: true,
1090
+ structuredContent: {
1091
+ error: 'SPEC_FORMAT_INVALID',
1092
+ code: 422,
1093
+ persisted: false,
1094
+ issues: candidateValidation.issues,
1095
+ },
1096
+ });
1097
+ }
1098
+ const genericOutputGate = checkGenericSpecOutput(unifiedSpec);
1099
+ if (!genericOutputGate.passed) {
1100
+ return earlyPreparation({
1101
+ content: [
1102
+ {
1103
+ type: 'text',
1104
+ text: 'Spec quality gate blocked generic output before persistence. ' +
1105
+ genericOutputGate.issues
1106
+ .slice(0, 3)
1107
+ .map((issue) => `${issue.phrase}: ${issue.reason}`)
1108
+ .join('; '),
1109
+ },
1110
+ ],
1111
+ isError: true,
1112
+ structuredContent: {
1113
+ error: 'GENERIC_SPEC_OUTPUT_BLOCKED',
1114
+ sddConstitutionRuleId: 'sdd.no-generic-output',
1115
+ issues: genericOutputGate.issues,
1116
+ fixHint: 'Replace generic criteria or placeholder references with grounded, testable behavior.',
1117
+ },
1118
+ });
1119
+ }
1120
+ if (outOfScope.length > 0) {
1121
+ spec.outOfScope = outOfScope;
1122
+ }
1123
+ return {
1124
+ kind: 'ready',
1125
+ prepared: {
1126
+ params,
1127
+ context,
1128
+ knowledge,
1129
+ clarificationSession,
1130
+ constitutionWarnings: constitutionCheck.warnings,
1131
+ advisoryCriteria,
1132
+ actionableMetrics,
1133
+ unifiedSpec,
1134
+ simplicityResult: runSimplicityCheck([params.description, ...filteredCriteria].join('\n'), estimation.devHours),
1135
+ },
1136
+ };
1137
+ }
814
1138
  // eslint-disable-next-line max-lines-per-function
815
1139
  export async function handleCreateSpec(inputParams, server) {
1140
+ const enumValidation = validateCreateSpecEnums(inputParams);
1141
+ if (enumValidation) {
1142
+ return enumValidation;
1143
+ }
816
1144
  if (inputParams.idempotencyKey !== undefined &&
817
1145
  !/^[A-Za-z0-9._:-]{8,128}$/.test(inputParams.idempotencyKey)) {
818
1146
  return {
@@ -870,19 +1198,60 @@ export async function handleCreateSpec(inputParams, server) {
870
1198
  if (languageGate) {
871
1199
  return languageGate;
872
1200
  }
1201
+ // SPEC-584: Gate check — block if pending clarification token exists and no answers provided
1202
+ const gateError = await runClarificationGate(resolvedPath, 'create_spec', inputParams.clarificationAnswers);
1203
+ if (gateError) {
1204
+ return gateError;
1205
+ }
1206
+ const idempotencyKey = computeIdempotencyKey(inputParams.title, resolvedPath, inputParams.idempotencyKey);
1207
+ const cutoff = Date.now() - IDEMPOTENCY_MATCH_WINDOW_MS;
1208
+ const storeOnlyCandidate = await findStoreByIdempotencyKey(hashProjectPath(resolvedPath), idempotencyKey, cutoff);
1209
+ let preparation;
1210
+ try {
1211
+ preparation = await prepareCreateSpecCandidate(resolvedInputParams, server);
1212
+ }
1213
+ catch (error) {
1214
+ const message = error instanceof Error ? error.message : String(error);
1215
+ return {
1216
+ content: [{ type: 'text', text: ti('errors.internalError', { message }) }],
1217
+ isError: true,
1218
+ };
1219
+ }
1220
+ if (preparation.kind === 'early') {
1221
+ if (preparation.clarificationProjectId) {
1222
+ await persistClarificationToken(preparation.result, preparation.clarificationProjectId, 'create_spec');
1223
+ }
1224
+ return preparation.result;
1225
+ }
1226
+ const prepared = preparation.prepared;
1227
+ prepared.context.spec.idempotencyKey = idempotencyKey;
1228
+ if (storeOnlyCandidate) {
1229
+ const committedEvidence = await findByIdempotencyEvidence(resolvedPath, idempotencyKey, cutoff);
1230
+ if (!committedEvidence) {
1231
+ return {
1232
+ content: [
1233
+ {
1234
+ type: 'text',
1235
+ text: `A partial create_spec transaction exists for ${storeOnlyCandidate.id}: the store row has no committed idempotency evidence. Recovery is required before retrying.`,
1236
+ },
1237
+ ],
1238
+ isError: true,
1239
+ structuredContent: {
1240
+ error: 'PARTIAL_IDEMPOTENCY_STATE',
1241
+ code: 409,
1242
+ persisted: false,
1243
+ specId: storeOnlyCandidate.id,
1244
+ },
1245
+ };
1246
+ }
1247
+ }
873
1248
  // SPEC-665: Fire-and-forget TTL refresh for conventions.json (never blocks create_spec)
874
1249
  void import('../engine/conventions-ttl.js').then(({ triggerConventionsTtlRefresh }) => {
875
1250
  void triggerConventionsTtlRefresh(resolvedPath).catch(() => {
876
1251
  /* best-effort */
877
1252
  });
878
1253
  });
879
- // SPEC-584: Gate check — block if pending clarification token exists and no answers provided
880
- const gateError = await runClarificationGate(resolvedPath, 'create_spec', inputParams.clarificationAnswers);
881
- if (gateError) {
882
- return gateError;
883
- }
884
1254
  // SPEC-770: Idempotency check — return existing spec if same title+projectPath within 10 minutes
885
- const idempotencyKey = computeIdempotencyKey(inputParams.title, resolvedPath, inputParams.idempotencyKey);
886
1255
  const operationKey = computeOperationKey(resolvedInputParams, resolvedPath, idempotencyKey);
887
1256
  const runtimeDatabase = new RuntimeDatabase({ path: resolveStorageLayout().runtimeDatabase });
888
1257
  const operationJournal = new OperationJournal(runtimeDatabase, createHash('sha256').update(hashProjectPath(resolvedPath)).digest('hex').slice(0, 32));
@@ -915,6 +1284,25 @@ export async function handleCreateSpec(inputParams, server) {
915
1284
  }
916
1285
  catch (error) {
917
1286
  runtimeDatabase.close();
1287
+ if (error instanceof BoundaryFailure &&
1288
+ error.kind === 'Conflict' &&
1289
+ error.operation === 'operation begin') {
1290
+ return {
1291
+ content: [
1292
+ {
1293
+ type: 'text',
1294
+ text: 'The idempotency key is already bound to a different create_spec request.',
1295
+ },
1296
+ ],
1297
+ isError: true,
1298
+ structuredContent: {
1299
+ error: 'IDEMPOTENCY_CONFLICT',
1300
+ code: 'IDEMPOTENCY_CONFLICT',
1301
+ status: 409,
1302
+ persisted: false,
1303
+ },
1304
+ };
1305
+ }
918
1306
  throw error;
919
1307
  }
920
1308
  if ((journalEntry.state === 'committed' || journalEntry.state === 'acknowledged') &&
@@ -984,216 +1372,14 @@ export async function handleCreateSpec(inputParams, server) {
984
1372
  idempotencyKey,
985
1373
  ownerId: idempotencyClaim.ownerId,
986
1374
  });
987
- // eslint-disable-next-line max-lines-per-function -- cohesive transactional create boundary
988
1375
  return await trackCost(resolvedInputParams.projectPath ?? '', 'create_spec', async () => {
989
- // Allow internal re-enrichment without mutating function parameter
990
- let params = resolvedInputParams;
991
- const { description, clarificationSessionId } = params;
992
1376
  try {
993
1377
  // SPEC-713: Phase 1 — Critical path: build + persist spec within 25s hard ceiling.
994
1378
  // Only the steps required to produce spec.md on disk are inside this budget.
995
1379
  // Durable post-commit work is delegated to the spec.created outbox consumer.
996
1380
  const criticalResult = await withTotalBudget('create_spec-critical', getRuntimePolicy().createSpec.criticalTimeoutMs, async (criticalSignal) => {
997
- // Build spec context (ID, estimation, diagrams, scope filters)
998
- const buildResult = await measureStep('buildSpecContext', () => buildSpecContext(params));
999
- if (!buildResult.ok) {
1000
- return {
1001
- ok: false,
1002
- earlyReturn: {
1003
- content: [{ type: 'text', text: buildResult.errorMessage }],
1004
- isError: true,
1005
- },
1006
- };
1007
- }
1008
- const { spec, specDir, specPath, estimation, splitSuggestion, duplicate, projectId } = buildResult.context;
1009
- // Kept in external store/evidence for retries; the value-only serializer omits it.
1010
- spec.idempotencyKey = idempotencyKey;
1011
- // Load project knowledge and clarification session
1012
- const knowledge = await measureStep('loadKnowledge', () => knowledgeStore.getKnowledge(projectId));
1013
- const clarificationSession = clarificationSessionId
1014
- ? await knowledgeStore.getClarification(projectId, clarificationSessionId)
1015
- : null;
1016
- // Validate against Constitution
1017
- const constitutionCheck = await measureStep('validateConstitution', () => validateConstitution(projectId, spec.title, spec.tags));
1018
- if (constitutionCheck.blocked && constitutionCheck.errorResult) {
1019
- return { ok: false, earlyReturn: constitutionCheck.errorResult };
1020
- }
1021
- // Full project-wide analysis is delivered by the durable spec.created outbox consumer.
1022
- // A tiny inline budget preserves deterministic idea/clarification behavior when the
1023
- // answer is immediately available without allowing repository scans to delay ACK.
1024
- const inlineAnalysis = await withBudget('create-spec-inline-analysis', getRuntimePolicy().createSpec.inlineAnalysisBudgetMs, (signal) => analyzeProjectForSpec(params.projectPath ?? '', description, spec.title, knowledge, signal));
1025
- const autopilot = unwrapBudget(inlineAnalysis, getEmptyAutopilotResult());
1026
- // If very vague (<10 words), capture as idea instead of spec
1027
- if (autopilot.isIdea) {
1028
- return {
1029
- ok: false,
1030
- earlyReturn: {
1031
- content: [
1032
- {
1033
- type: 'text',
1034
- text: `💡 **Idea captured**: "${description}"\n\nThe description is too brief for a full spec. Use \`capture_idea\` to save it to the backlog, or provide more detail (>10 words) to create a spec.`,
1035
- },
1036
- ],
1037
- },
1038
- };
1039
- }
1040
- // SPEC-463 / SPEC-471: Interactive clarification
1041
- const clarificationResult = handleClarification(server, description, knowledge, autopilot, params);
1042
- if (clarificationResult !== null) {
1043
- if ('earlyReturn' in clarificationResult) {
1044
- // SPEC-584: Persist token so the gate blocks retries without answers
1045
- await persistClarificationToken(clarificationResult.earlyReturn, projectId, 'create_spec');
1046
- return { ok: false, earlyReturn: clarificationResult.earlyReturn };
1047
- }
1048
- params = clarificationResult.params;
1049
- }
1050
- const filteredCriteria = filterGroundedCriteria(autopilot.suggestedCriteria);
1051
- const technologyContract = await readTechnologySelectionContract(params.projectPath ?? '');
1052
- const contractNote = technologyContract
1053
- ? [
1054
- '',
1055
- 'Technology Contract:',
1056
- `- Mode: ${technologyContract.mode}`,
1057
- technologyContract.language ? `- Language: ${technologyContract.language}` : '',
1058
- technologyContract.framework
1059
- ? `- Framework: ${technologyContract.framework}`
1060
- : '- Framework: none or not selected',
1061
- technologyContract.platform ? `- Platform: ${technologyContract.platform}` : '',
1062
- technologyContract.projectType
1063
- ? `- Project type: ${technologyContract.projectType}`
1064
- : '',
1065
- '- Agents must not choose a different stack unless the user approves a new contract.',
1066
- ]
1067
- .filter(Boolean)
1068
- .join('\n')
1069
- : '';
1070
- const specGenerator = new FallbackGenerator();
1071
- const generatedSpec = await measureStep('generateSpecBody', () => specGenerator.generate({
1072
- title: spec.title,
1073
- description: `${description}${contractNote}`,
1074
- type: spec.type,
1075
- scope: spec.scope,
1076
- target: spec.target,
1077
- acFormat: params.acFormat,
1078
- projectContext: {
1079
- language: technologyContract?.language ?? knowledge?.language ?? undefined,
1080
- framework: technologyContract?.framework ?? knowledge?.framework ?? undefined,
1081
- architecture: knowledge?.architecture.primary ?? undefined,
1082
- },
1083
- }));
1084
- spec.generation = generatedSpec.generation;
1085
- spec.qualityWarnings =
1086
- generatedSpec.qualityWarnings.length > 0 ? generatedSpec.qualityWarnings : undefined;
1087
- const baseCriteria = extractCriteria(generatedSpec.specBody).map((criterion) => criterion.text);
1088
- const groundingCriteria = buildCriterionGroundingRecords({
1089
- criteria: [...baseCriteria, ...filteredCriteria],
1090
- userInput: description,
1091
- generatedEvidence: [
1092
- generatedSpec.generation.modelId ?? generatedSpec.generation.method,
1093
- ],
1094
- });
1095
- const contractCriteria = getContractCriteria(groundingCriteria);
1096
- const advisoryCriteria = getAdvisoryCriteria(groundingCriteria).map((record) => record.text);
1097
- if (contractCriteria.length === 0) {
1098
- return {
1099
- ok: false,
1100
- earlyReturn: {
1101
- content: [
1102
- {
1103
- type: 'text',
1104
- text: 'create_spec did not persist a spec because no acceptance criterion was grounded in the user request. ' +
1105
- 'Needs decision: provide at least one explicit, verifiable criterion as a checkbox or Given/When/Then outcome.',
1106
- },
1107
- ],
1108
- isError: true,
1109
- structuredContent: {
1110
- error: 'MISSING_GROUNDED_ACCEPTANCE_CRITERIA',
1111
- missingDecision: 'Provide at least one explicit, verifiable acceptance criterion.',
1112
- persisted: false,
1113
- },
1114
- },
1115
- };
1116
- }
1117
- const actionableMetrics = calculateActionableSpecMetrics({
1118
- criteria: [...baseCriteria, ...filteredCriteria],
1119
- groundingRecords: groundingCriteria,
1120
- }).filter(shouldExposeMetric);
1121
- const technicalFiles = await measureStep('resolveTechnicalFiles', () => resolveTechnicalFiles({
1122
- generatedSpecBody: generatedSpec.specBody,
1123
- generatedTechnicalSection: generatedSpec.technicalSection,
1124
- projectPath: params.projectPath ?? '',
1125
- }));
1126
- const groundedTechnical = await measureStep('groundTechnicalFiles', () => groundTechnicalFiles({
1127
- files: technicalFiles,
1128
- projectPath: params.projectPath ?? '',
1129
- userInput: description,
1130
- autopilot,
1131
- }));
1132
- const outOfScope = resolveOutOfScope(params.outOfScope);
1133
- const scenarioTestPaths = groundedTechnical.files.test.map((file) => file.path);
1134
- const leanSpec = generateLeanSpecContent({
1135
- spec,
1136
- description: generatedSpec.specBody,
1137
- estimation,
1138
- criteriaOverride: contractCriteria.map((record) => ({
1139
- text: record.text,
1140
- done: false,
1141
- })),
1142
- groundingCriteria: contractCriteria,
1143
- groundingTechnicalReferences: groundedTechnical.records,
1144
- acFormat: params.acFormat,
1145
- scenarioTestPaths,
1146
- });
1147
- const leanTechnical = generateLeanTechnicalContent({
1148
- specId: spec.id,
1149
- filesToCreate: groundedTechnical.files.create,
1150
- filesToModify: groundedTechnical.files.modify,
1151
- filesToTest: groundedTechnical.files.test,
1152
- includeDecisionRequiredPlaceholder: spec.scope !== 'trivial',
1153
- });
1154
- // SPEC-709: write unified spec.md from origin — no separate technical.md.
1155
- // The legacy two-file output is preserved by appending the technical body
1156
- // as a `## Technical` section inside spec.md.
1157
- const unifiedWithoutContract = buildUnifiedSpecContent(leanSpec, leanTechnical);
1158
- const unifiedSpec = appendImplementationContractIfMissing(unifiedWithoutContract, {
1159
- description: generatedSpec.specBody,
1160
- criteria: contractCriteria.map((record) => ({ text: record.text, done: false })),
1161
- files: groundedTechnical.files,
1162
- outOfScope,
1163
- verificationCommands: [],
1164
- });
1165
- const genericOutputGate = checkGenericSpecOutput(unifiedSpec);
1166
- if (!genericOutputGate.passed) {
1167
- return {
1168
- ok: false,
1169
- earlyReturn: {
1170
- content: [
1171
- {
1172
- type: 'text',
1173
- text: 'Spec quality gate blocked generic output before persistence. ' +
1174
- genericOutputGate.issues
1175
- .slice(0, 3)
1176
- .map((issue) => `${issue.phrase}: ${issue.reason}`)
1177
- .join('; '),
1178
- },
1179
- ],
1180
- isError: true,
1181
- structuredContent: {
1182
- error: 'GENERIC_SPEC_OUTPUT_BLOCKED',
1183
- sddConstitutionRuleId: 'sdd.no-generic-output',
1184
- issues: genericOutputGate.issues,
1185
- fixHint: 'Replace generic criteria or placeholder references with grounded, testable behavior.',
1186
- },
1187
- },
1188
- };
1189
- }
1190
- // Contradiction analysis is durable post-commit work. Its synchronous scanners can
1191
- // block the event loop, so it must never run on the persistence acknowledgement path.
1192
- const contradictionHint = undefined;
1193
- const committedSimplicity = runSimplicityCheck([params.description, ...filteredCriteria].join('\n'), estimation.devHours);
1194
- if (outOfScope.length > 0) {
1195
- spec.outOfScope = outOfScope;
1196
- }
1381
+ const { params, context, knowledge, clarificationSession, constitutionWarnings, advisoryCriteria, actionableMetrics, unifiedSpec, simplicityResult: committedSimplicity, } = prepared;
1382
+ const { spec, specDir, specPath, splitSuggestion, duplicate, projectId } = context;
1197
1383
  let committedPlannerToken;
1198
1384
  try {
1199
1385
  committedPlannerToken = await issuePlannerToken(resolvedPath, spec.id, params.sessionId ?? 'unknown-session', params.modelId ?? 'unknown-model', params.host ?? 'unknown-host');
@@ -1205,13 +1391,13 @@ export async function handleCreateSpec(inputParams, server) {
1205
1391
  spec,
1206
1392
  operationId: operationKey,
1207
1393
  duplicate,
1208
- constitutionWarnings: constitutionCheck.warnings,
1394
+ constitutionWarnings,
1209
1395
  clarificationSession,
1210
1396
  advisoryCriteria,
1211
1397
  actionableMetrics,
1212
1398
  splitSuggestion,
1213
1399
  experienceLevel: knowledge?.experienceLevel,
1214
- contradictionHint,
1400
+ contradictionHint: undefined,
1215
1401
  simplicityResult: committedSimplicity,
1216
1402
  plannerToken: committedPlannerToken,
1217
1403
  });
@@ -1256,11 +1442,18 @@ export async function handleCreateSpec(inputParams, server) {
1256
1442
  const storeRolledBack = storeCreated
1257
1443
  ? await specStore.deleteSpec(projectId, spec.id).catch(() => false)
1258
1444
  : true;
1259
- if (storeRolledBack) {
1260
- await cleanupExecutionArtifact(getIdempotencyEvidencePath(resolvedPath, idempotencyKey), {
1261
- force: true,
1445
+ await cleanupExecutionArtifact(getIdempotencyEvidencePath(resolvedPath, idempotencyKey), { force: true });
1446
+ await cleanupExecutionArtifact(specDir, { recursive: true, force: true });
1447
+ try {
1448
+ operationJournal.resolveRecovery('create_spec', operationKey, {
1449
+ action: storeRolledBack ? 'rollback' : 'quarantine',
1450
+ reason: storeRolledBack
1451
+ ? 'create_spec persistence failed and compensating cleanup completed'
1452
+ : 'create_spec persistence failed and the spec store rollback was incomplete',
1262
1453
  });
1263
- await cleanupExecutionArtifact(specDir, { recursive: true, force: true });
1454
+ }
1455
+ catch {
1456
+ // The original persistence failure remains the primary error.
1264
1457
  }
1265
1458
  throw writeErr;
1266
1459
  }
@@ -1276,7 +1469,7 @@ export async function handleCreateSpec(inputParams, server) {
1276
1469
  claimLifecycle.retainForInFlightWork = criticalResult.warning.includes('exceeded');
1277
1470
  // SPEC-770: Wait briefly for any in-flight spec write to complete, then scan for duplicates
1278
1471
  await new Promise((resolve) => setTimeout(resolve, 500));
1279
- const recentSpecs = await findRecentlyWrittenSpecs(resolvedPath, 2 * 60 * 1000);
1472
+ const recentSpecs = await findRecentlyWrittenSpecs(resolvedPath, 2 * 60 * 1000, resolvedInputParams.title);
1280
1473
  const possibleDuplicates = recentSpecs.length > 0 ? recentSpecs : undefined;
1281
1474
  const dupNote = possibleDuplicates
1282
1475
  ? `\n\nA spec may have been created server-side: ${possibleDuplicates.map((s) => s.id).join(', ')}. Retry with the same title to return it instead of creating a duplicate.`
@@ -1285,13 +1478,25 @@ export async function handleCreateSpec(inputParams, server) {
1285
1478
  content: [
1286
1479
  {
1287
1480
  type: 'text',
1288
- text: `❌ create_spec exceeded 25s internal budget.\n\n${criticalResult.warning}${dupNote}`,
1481
+ text: `❌ create_spec exceeded its ${String(criticalResult.ceilingMs)}ms internal budget.\n\n${criticalResult.warning}${dupNote}`,
1289
1482
  },
1290
1483
  ],
1291
1484
  isError: true,
1292
1485
  ...(possibleDuplicates ? { structuredContent: { possibleDuplicates } } : {}),
1293
1486
  };
1294
1487
  }
1488
+ if (criticalResult.status === 'failed') {
1489
+ return {
1490
+ content: [{ type: 'text', text: criticalResult.warning }],
1491
+ isError: true,
1492
+ structuredContent: {
1493
+ error: 'OPERATION_FAILED',
1494
+ category: criticalResult.category,
1495
+ elapsedMs: criticalResult.elapsedMs,
1496
+ persisted: false,
1497
+ },
1498
+ };
1499
+ }
1295
1500
  if (!criticalResult.value.ok) {
1296
1501
  return criticalResult.value.earlyReturn;
1297
1502
  }