@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
@@ -1,12 +1,12 @@
1
1
  import { parseFrontmatterScenarios } from '../validator/spec-compliance-runner.js';
2
2
  import { stripFrontmatter } from '../frontmatter-parser.js';
3
- import { extractListItems, extractSection } from '../validator/extractors.js';
3
+ import { extractListItems, extractSection } from './markdown-sections.js';
4
4
  import { createCriterionIdentity, normalizeCriterionText } from '../criterion-identity.js';
5
- function extractSectionCriteria(section) {
6
- const listItems = extractListItems(section);
7
- if (listItems.length > 0) {
8
- return listItems;
9
- }
5
+ const BDD_STEP = /^(GIVEN|WHEN|THEN|AND|DADO|CUANDO|ENTONCES|Y)\b/i;
6
+ const BDD_START = /^(GIVEN|DADO)\b/i;
7
+ const TECHNICAL_MARKER = /^(FILES|FUNCTIONS|TEST|OWNER):/i;
8
+ const EXPLICIT_CRITERION_HEADING = /^#{3,6}\s+((?:AC|AB)\s*-?\s*\d+)\b/i;
9
+ function extractBddBlocks(section) {
10
10
  const lines = section.split('\n').map((line) => line.trim());
11
11
  if (lines.length === 0) {
12
12
  return [];
@@ -21,16 +21,21 @@ function extractSectionCriteria(section) {
21
21
  }
22
22
  continue;
23
23
  }
24
- if (/^(GIVEN|WHEN|THEN|AND|DADO|CUANDO|ENTONCES|Y)\b/i.test(line)) {
25
- if (/^(GIVEN|DADO)\b/i.test(line) && current.length > 0) {
24
+ const withoutListMarker = line.replace(/^(?:[-*+]\s+(?:\[[ xX]\]\s*)?|\d+[.)]\s+)/, '');
25
+ if (TECHNICAL_MARKER.test(withoutListMarker) || /^#{1,6}\s+/.test(withoutListMarker)) {
26
+ if (current.length > 0) {
26
27
  bddBlocks.push(current.join(' '));
27
28
  current = [];
28
29
  }
29
- current.push(line.replace(/^[-*+]\s*/, ''));
30
30
  continue;
31
31
  }
32
- if (current.length > 0) {
33
- current.push(line);
32
+ if (BDD_STEP.test(withoutListMarker)) {
33
+ if (BDD_START.test(withoutListMarker) && current.length > 0) {
34
+ bddBlocks.push(current.join(' '));
35
+ current = [];
36
+ }
37
+ current.push(withoutListMarker);
38
+ continue;
34
39
  }
35
40
  }
36
41
  if (current.length > 0) {
@@ -38,7 +43,54 @@ function extractSectionCriteria(section) {
38
43
  }
39
44
  return bddBlocks;
40
45
  }
41
- function extractBodyCriteria(raw) {
46
+ function extractExplicitCriterionBodies(section) {
47
+ const lines = section.split('\n');
48
+ const bodies = [];
49
+ let current = null;
50
+ for (const line of lines) {
51
+ const heading = EXPLICIT_CRITERION_HEADING.exec(line.trim());
52
+ if (heading?.[1]) {
53
+ if (current !== null) {
54
+ bodies.push(current);
55
+ }
56
+ current = { id: heading[1].replace(/[\s-]/g, ''), body: '' };
57
+ continue;
58
+ }
59
+ if (current !== null) {
60
+ current.body += `${current.body.length > 0 ? '\n' : ''}${line}`;
61
+ }
62
+ }
63
+ if (current !== null) {
64
+ bodies.push(current);
65
+ }
66
+ return bodies;
67
+ }
68
+ function extractOneCriterion(body) {
69
+ const bddBlocks = extractBddBlocks(body);
70
+ if (bddBlocks.length > 0) {
71
+ return bddBlocks.reduce((selected, candidate) => candidate.length > selected.length ? candidate : selected);
72
+ }
73
+ const listItem = extractListItems(body).find((item) => !TECHNICAL_MARKER.test(item));
74
+ return listItem ?? null;
75
+ }
76
+ function extractSectionCriteria(section) {
77
+ const explicitBodies = extractExplicitCriterionBodies(section);
78
+ if (explicitBodies.length > 0) {
79
+ return explicitBodies
80
+ .map(({ id, body }) => {
81
+ const criterion = extractOneCriterion(body);
82
+ return criterion === null ? null : `${id}: ${criterion}`;
83
+ })
84
+ .filter((criterion) => criterion !== null);
85
+ }
86
+ const listItems = extractListItems(section).filter((item) => !TECHNICAL_MARKER.test(item));
87
+ if (listItems.length > 0) {
88
+ const nonStepItems = listItems.filter((item) => !BDD_STEP.test(item));
89
+ return nonStepItems.length > 0 ? nonStepItems : extractBddBlocks(section);
90
+ }
91
+ return extractBddBlocks(section);
92
+ }
93
+ function extractBodyCriteria(raw, options) {
42
94
  const body = stripFrontmatter(raw);
43
95
  const acceptanceCriteria = extractSection(body, 'acceptance criteria', 'criterios de aceptaci');
44
96
  if (acceptanceCriteria) {
@@ -47,11 +99,18 @@ function extractBodyCriteria(raw) {
47
99
  return sectionCriteria;
48
100
  }
49
101
  }
50
- return body
102
+ if (options.allowLegacyBodyFallback === false) {
103
+ return [];
104
+ }
105
+ const checkboxCriteria = body
51
106
  .split('\n')
52
107
  .map((line) => line.trim())
53
- .filter((line) => /^-\s*\[[ x]\]/i.test(line) || /^- .*\bgiven\b.*\bwhen\b.*\bthen\b/i.test(line))
108
+ .filter((line) => /^-\s*\[[ x]\]/i.test(line))
54
109
  .map((line) => line.replace(/^-\s*\[[ x]\]\s*/i, '').trim());
110
+ if (checkboxCriteria.length > 0) {
111
+ return checkboxCriteria;
112
+ }
113
+ return extractBddBlocks(body);
55
114
  }
56
115
  function buildCriterion(text, source, index) {
57
116
  const trimmed = text.trim();
@@ -68,8 +127,8 @@ function buildCriterion(text, source, index) {
68
127
  aliases: identity.aliases,
69
128
  };
70
129
  }
71
- export function extractNormalizedAcceptanceCriteria(raw) {
72
- const bodyCriteria = extractBodyCriteria(raw)
130
+ export function extractNormalizedAcceptanceCriteria(raw, options = {}) {
131
+ const bodyCriteria = extractBodyCriteria(raw, options)
73
132
  .map((criterion, index) => buildCriterion(criterion, 'body', index))
74
133
  .filter((criterion) => criterion !== null);
75
134
  if (bodyCriteria.length > 0) {
@@ -79,8 +138,8 @@ export function extractNormalizedAcceptanceCriteria(raw) {
79
138
  .map((scenario, index) => buildCriterion(scenario.title, 'frontmatter', index))
80
139
  .filter((criterion) => criterion !== null);
81
140
  }
82
- export function extractAcceptanceCriteriaTexts(raw) {
83
- return extractNormalizedAcceptanceCriteria(raw).map((criterion) => criterion.text);
141
+ export function extractAcceptanceCriteriaTexts(raw, options = {}) {
142
+ return extractNormalizedAcceptanceCriteria(raw, options).map((criterion) => criterion.text);
84
143
  }
85
144
  export function normalizeAcceptanceCriterionToken(value) {
86
145
  return normalizeCriterionText(value);
@@ -0,0 +1,14 @@
1
+ /** Locate one exact markdown section without normalizing or copying source bytes. */
2
+ export declare function findMarkdownSectionRange(content: string, headingName: string): {
3
+ headingStart: number;
4
+ contentStart: number;
5
+ end: number;
6
+ level: number;
7
+ } | null;
8
+ /**
9
+ * Extract the text content under a markdown heading that matches any keyword.
10
+ */
11
+ export declare function extractSection(content: string, ...headingKeywords: string[]): string | null;
12
+ /** Extract bullet and numbered-list item text from a markdown block. */
13
+ export declare function extractListItems(content: string): string[];
14
+ //# sourceMappingURL=markdown-sections.d.ts.map
@@ -0,0 +1,105 @@
1
+ function markdownLines(content) {
2
+ const lines = [];
3
+ let start = 0;
4
+ while (start < content.length) {
5
+ const newline = content.indexOf('\n', start);
6
+ const end = newline === -1 ? content.length : newline + 1;
7
+ const contentEnd = newline === -1
8
+ ? end
9
+ : newline > start && content[newline - 1] === '\r'
10
+ ? newline - 1
11
+ : newline;
12
+ lines.push({ start, contentEnd, end });
13
+ start = end;
14
+ }
15
+ return lines;
16
+ }
17
+ /** Locate one exact markdown section without normalizing or copying source bytes. */
18
+ export function findMarkdownSectionRange(content, headingName) {
19
+ let selected = null;
20
+ let fence = null;
21
+ for (const line of markdownLines(content)) {
22
+ const text = content.slice(line.start, line.contentEnd);
23
+ const fenceMatch = /^[ \t]{0,3}(`{3,}|~{3,})/.exec(text);
24
+ if (fenceMatch?.[1]) {
25
+ const marker = fenceMatch[1][0];
26
+ if (fence === null) {
27
+ fence = { marker, length: fenceMatch[1].length };
28
+ }
29
+ else if (fence.marker === marker && fenceMatch[1].length >= fence.length) {
30
+ fence = null;
31
+ }
32
+ continue;
33
+ }
34
+ if (fence !== null) {
35
+ continue;
36
+ }
37
+ const heading = /^(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/.exec(text);
38
+ if (!heading?.[1] || heading[2] === undefined) {
39
+ continue;
40
+ }
41
+ const level = heading[1].length;
42
+ if (selected !== null && level <= selected.level) {
43
+ return { ...selected, end: line.start };
44
+ }
45
+ if (selected === null && heading[2].trim().toLowerCase() === headingName.trim().toLowerCase()) {
46
+ selected = {
47
+ headingStart: line.start,
48
+ contentStart: line.end,
49
+ end: content.length,
50
+ level,
51
+ };
52
+ }
53
+ }
54
+ return selected;
55
+ }
56
+ /**
57
+ * Extract the text content under a markdown heading that matches any keyword.
58
+ */
59
+ export function extractSection(content, ...headingKeywords) {
60
+ const lines = content.split('\n');
61
+ let capturing = false;
62
+ let capturedLevel = 0;
63
+ const captured = [];
64
+ for (const line of lines) {
65
+ const headingMatch = /^(#{1,6})\s+(.+)/.exec(line);
66
+ if (headingMatch?.[1] !== undefined && headingMatch[2] !== undefined) {
67
+ const level = headingMatch[1].length;
68
+ const title = headingMatch[2].toLowerCase();
69
+ if (capturing && level <= capturedLevel) {
70
+ break;
71
+ }
72
+ if (!capturing && headingKeywords.some((keyword) => title.includes(keyword))) {
73
+ capturing = true;
74
+ capturedLevel = level;
75
+ continue;
76
+ }
77
+ }
78
+ if (capturing) {
79
+ captured.push(line);
80
+ }
81
+ }
82
+ return captured.length > 0 ? captured.join('\n') : null;
83
+ }
84
+ /** Extract bullet and numbered-list item text from a markdown block. */
85
+ export function extractListItems(content) {
86
+ const items = [];
87
+ for (const line of content.split('\n')) {
88
+ const listMatch = /^\s*[-*+]\s+\[?\s*[xX ]?\s*\]?\s*(.+)/.exec(line);
89
+ if (listMatch?.[1]) {
90
+ const item = listMatch[1].trim();
91
+ if (item.length > 5) {
92
+ items.push(item);
93
+ }
94
+ }
95
+ const numberedMatch = /^\s*\d+[.)]\s+(.+)/.exec(line);
96
+ if (numberedMatch?.[1]) {
97
+ const item = numberedMatch[1].trim();
98
+ if (item.length > 5) {
99
+ items.push(item);
100
+ }
101
+ }
102
+ }
103
+ return items;
104
+ }
105
+ //# sourceMappingURL=markdown-sections.js.map
@@ -60,11 +60,39 @@ export function extractSectionBody(body, sectionName) {
60
60
  * against the masked variant.
61
61
  */
62
62
  function maskFencedBlocks(body) {
63
- return body.replace(/(```|~~~)[\s\S]*?\1/g, (block) => {
64
- // Replace every non-newline char with a space so offsets (and line
65
- // numbers) are preserved exactly. Keep newlines intact.
66
- return block.replace(/[^\n]/g, ' ');
67
- });
63
+ const masked = body.split('');
64
+ let activeMarker = null;
65
+ let minimumClosingLength = 0;
66
+ let lineStart = 0;
67
+ while (lineStart < body.length) {
68
+ const newlineIndex = body.indexOf('\n', lineStart);
69
+ const lineEnd = newlineIndex === -1 ? body.length : newlineIndex;
70
+ const line = body.slice(lineStart, lineEnd);
71
+ if (activeMarker === null) {
72
+ const opening = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
73
+ if (opening !== undefined) {
74
+ activeMarker = opening[0];
75
+ minimumClosingLength = opening.length;
76
+ }
77
+ }
78
+ else {
79
+ const closing = /^ {0,3}(`+|~+)[ \t]*$/.exec(line)?.[1];
80
+ if (closing?.startsWith(activeMarker) === true && closing.length >= minimumClosingLength) {
81
+ activeMarker = null;
82
+ minimumClosingLength = 0;
83
+ }
84
+ }
85
+ if (activeMarker !== null || minimumClosingLength === 0) {
86
+ const isFenceBoundary = /^ {0,3}(`{3,}|~{3,})/.test(line) || /^ {0,3}(`+|~+)[ \t]*$/.test(line);
87
+ if (activeMarker !== null || isFenceBoundary) {
88
+ for (let index = lineStart; index < lineEnd; index += 1) {
89
+ masked[index] = ' ';
90
+ }
91
+ }
92
+ }
93
+ lineStart = newlineIndex === -1 ? body.length : newlineIndex + 1;
94
+ }
95
+ return masked.join('');
68
96
  }
69
97
  function escapeRegex(input) {
70
98
  return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -18,7 +18,7 @@ function extractPathsFromSection(sectionText) {
18
18
  return paths;
19
19
  }
20
20
  function parseSectionedFileLists(body) {
21
- const filesSection = /##\s+Files\s+likely\s+to\s+change[\s\S]*?(?=\n##\s|$)/.exec(body);
21
+ const filesSection = /##\s+Files(?:\s+likely\s+to\s+change)?\s*[\s\S]*?(?=\n##\s|$)/.exec(body);
22
22
  const scopedBody = filesSection?.[0] ?? body;
23
23
  const createSection = /###\s+Create[\s\S]*?(?=\n###|\n##\s|$)/.exec(scopedBody);
24
24
  const modifySection = /###\s+Modify[\s\S]*?(?=\n###|\n##\s|$)/.exec(scopedBody);
@@ -67,7 +67,8 @@ async function categorizeByExistence(paths, projectPath) {
67
67
  */
68
68
  export async function extractFilesFromSpecBody(specBody, projectPath) {
69
69
  const structured = parseSectionedFileLists(specBody);
70
- if (structured !== null && (structured.create.length > 0 || structured.modify.length > 0)) {
70
+ if (structured !== null &&
71
+ (structured.create.length > 0 || structured.modify.length > 0 || structured.test.length > 0)) {
71
72
  return structured;
72
73
  }
73
74
  const allPaths = [...new Set(Array.from(specBody.matchAll(FILE_PATH_RE), (m) => m[0]))];
@@ -1,3 +1,6 @@
1
+ import type { CanonicalUnifiedSpecInput, UnifiedSpecCandidateValidation } from '../../types/index.js';
2
+ /** Compose the one canonical persisted spec document from already-grounded inputs. */
3
+ export declare function buildCanonicalUnifiedSpecContent(input: CanonicalUnifiedSpecInput): string;
1
4
  /**
2
5
  * Combine a generated lean spec.md body and a generated lean technical.md body
3
6
  * into a single unified spec.md content. The technical body's YAML frontmatter
@@ -10,4 +13,8 @@
10
13
  * inject sections the user did not author themselves.
11
14
  */
12
15
  export declare function buildUnifiedSpecContent(leanSpecBody: string, leanTechnicalBody: string): string;
16
+ /** Pure pre-persistence validation for the complete unified spec candidate. */
17
+ export declare function validateUnifiedSpecCandidate(candidate: string, options?: {
18
+ groundedFilePaths?: Iterable<string>;
19
+ }): UnifiedSpecCandidateValidation;
13
20
  //# sourceMappingURL=unified-spec-builder.d.ts.map
@@ -1,6 +1,44 @@
1
1
  // engine/spec-format/unified-spec-builder.ts — SPEC-709/SPEC-1010 Bug C: Build unified spec.md.
2
2
  // Closes the SPEC-630 residue: every new spec is born unified, never written as a
3
3
  // two-file spec.md + technical.md pair only to be merged on the next list_specs run.
4
+ import { stripFrontmatter } from '../frontmatter-parser.js';
5
+ const REQUIRED_SECTIONS = [
6
+ 'Problem',
7
+ 'Goal',
8
+ 'Technical',
9
+ 'Implementation Contract',
10
+ 'Acceptance Criteria',
11
+ 'Files',
12
+ 'Out of scope',
13
+ ];
14
+ /** Compose the one canonical persisted spec document from already-grounded inputs. */
15
+ export function buildCanonicalUnifiedSpecContent(input) {
16
+ const frontmatter = /^---\n[\s\S]*?\n---\n?/.exec(input.leanSpecBody)?.[0]?.trimEnd() ?? '';
17
+ const source = input.sourceDescription.replace(/\r\n/g, '\n').trim();
18
+ const explicitProblem = extractTopLevelSectionBody(source, 'Problem');
19
+ const proseBeforeHeading = source.split(/^##[ \t]+\S.*$/m, 1)[0]?.trim() ?? '';
20
+ const problem = firstNonEmpty(explicitProblem, proseBeforeHeading, source);
21
+ const explicitGoal = extractTopLevelSectionBody(source, 'Goal');
22
+ const goal = firstNonEmpty(explicitGoal, firstSentence(problem), problem);
23
+ const explicitTechnical = extractTopLevelSectionBody(source, 'Technical');
24
+ const generatedNotes = extractTopLevelSectionBody(input.technicalBody, 'Implementation Notes');
25
+ const technical = firstNonEmpty(explicitTechnical, generatedNotes, 'Implement only the grounded ownership and observable behavior declared by this contract.');
26
+ const acceptance = extractExecutableCriteria(source, input.criteria);
27
+ const explicitOutOfScope = extractTopLevelSectionBody(source, 'Out of scope');
28
+ const outOfScope = input.outOfScope.length > 0
29
+ ? input.outOfScope.map((item) => `- ${item}`).join('\n')
30
+ : (explicitOutOfScope ?? '');
31
+ const sections = [
32
+ renderSection('Problem', problem),
33
+ renderSection('Goal', goal),
34
+ renderSection('Technical', technical),
35
+ input.implementationContract.trim(),
36
+ renderSection('Acceptance Criteria', acceptance),
37
+ renderSection('Files', renderOwnedFiles(input.files)),
38
+ renderSection('Out of scope', outOfScope),
39
+ ];
40
+ return `${frontmatter ? `${frontmatter}\n\n` : ''}${sections.join('\n\n')}\n`;
41
+ }
4
42
  /**
5
43
  * Combine a generated lean spec.md body and a generated lean technical.md body
6
44
  * into a single unified spec.md content. The technical body's YAML frontmatter
@@ -78,4 +116,168 @@ function extractSection(body, sectionName) {
78
116
  const end = next ? next.index : body.length;
79
117
  return body.slice(start, end).trimEnd();
80
118
  }
119
+ /** Pure pre-persistence validation for the complete unified spec candidate. */
120
+ // eslint-disable-next-line max-lines-per-function -- validation order mirrors the public issue contract
121
+ export function validateUnifiedSpecCandidate(candidate, options = {}) {
122
+ const body = stripFrontmatter(candidate.replace(/\r\n/g, '\n'));
123
+ const scannable = maskFencedAndQuotedText(body);
124
+ const issues = [];
125
+ const positions = new Map();
126
+ for (const section of REQUIRED_SECTIONS) {
127
+ const pattern = new RegExp(`^##[ \\t]+${escapeRegex(section)}[ \\t]*$`, 'gim');
128
+ positions.set(section, Array.from(scannable.matchAll(pattern), (match) => match.index));
129
+ }
130
+ for (const section of REQUIRED_SECTIONS) {
131
+ const matches = positions.get(section) ?? [];
132
+ if (matches.length === 0) {
133
+ issues.push({
134
+ code: 'MISSING_REQUIRED_SECTION',
135
+ section,
136
+ message: `Missing required top-level section: ${section}`,
137
+ });
138
+ }
139
+ else if (matches.length > 1) {
140
+ issues.push({
141
+ code: 'DUPLICATE_REQUIRED_SECTION',
142
+ section,
143
+ message: `Required top-level section appears more than once: ${section}`,
144
+ });
145
+ }
146
+ }
147
+ const observedOrder = REQUIRED_SECTIONS.flatMap((section) => (positions.get(section) ?? []).slice(0, 1).map((position) => ({ section, position }))).sort((left, right) => left.position - right.position);
148
+ if (observedOrder.length === REQUIRED_SECTIONS.length &&
149
+ observedOrder.some((entry, index) => entry.section !== REQUIRED_SECTIONS[index])) {
150
+ issues.push({
151
+ code: 'MISORDERED_REQUIRED_SECTION',
152
+ message: `Required sections must appear in canonical order: ${REQUIRED_SECTIONS.join(' -> ')}`,
153
+ });
154
+ }
155
+ const embedded = findEmbeddedRequiredSections(body);
156
+ for (const section of embedded) {
157
+ issues.push({
158
+ code: 'EMBEDDED_REQUIRED_SECTION',
159
+ section,
160
+ message: `Required section heading is embedded in quoted, fenced, or nested content: ${section}`,
161
+ });
162
+ }
163
+ if (/\b(?:Needs decision|TBD|TODO)\b/i.test(scannable)) {
164
+ issues.push({
165
+ code: 'UNRESOLVED_CONTRACT_PLACEHOLDER',
166
+ message: 'Unified candidate contains an unresolved contract placeholder.',
167
+ });
168
+ }
169
+ const acceptanceSection = extractTopLevelSectionBody(scannable, 'Acceptance Criteria');
170
+ if (acceptanceSection === null ||
171
+ !/\bGIVEN\b[\s\S]*\bWHEN\b[\s\S]*\bTHEN\b/i.test(acceptanceSection)) {
172
+ issues.push({
173
+ code: 'MISSING_EXECUTABLE_CRITERIA',
174
+ section: 'Acceptance Criteria',
175
+ message: 'Acceptance Criteria must contain executable GIVEN/WHEN/THEN behavior.',
176
+ });
177
+ }
178
+ if (options.groundedFilePaths !== undefined) {
179
+ const grounded = new Set(options.groundedFilePaths);
180
+ const filesSection = extractTopLevelSectionBody(scannable, 'Files') ?? '';
181
+ const ownedPaths = Array.from(filesSection.matchAll(/^[ \t]*-[ \t]+`?([^`\r\n]+?)`?[ \t]*$/gm), (match) => match[1]?.trim() ?? '').filter((path) => path.length > 0 && path !== 'None.');
182
+ for (const path of [...new Set(ownedPaths)]) {
183
+ if (!grounded.has(path)) {
184
+ issues.push({
185
+ code: 'UNGROUNDED_FILE_OWNERSHIP',
186
+ path,
187
+ message: `File ownership is not grounded in explicit input or project evidence: ${path}`,
188
+ });
189
+ }
190
+ }
191
+ }
192
+ return { valid: issues.length === 0, issues };
193
+ }
194
+ function maskFencedAndQuotedText(body) {
195
+ const lines = body.split('\n');
196
+ let fence = null;
197
+ return lines
198
+ .map((line) => {
199
+ const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
200
+ if (marker) {
201
+ const kind = marker[0];
202
+ fence = fence === null ? kind : fence === kind ? null : fence;
203
+ return ' '.repeat(line.length);
204
+ }
205
+ if (fence !== null || /^\s*>/.test(line)) {
206
+ return ' '.repeat(line.length);
207
+ }
208
+ return line;
209
+ })
210
+ .join('\n');
211
+ }
212
+ function findEmbeddedRequiredSections(body) {
213
+ const embedded = new Set();
214
+ let fence = null;
215
+ for (const line of body.split('\n')) {
216
+ const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
217
+ if (marker) {
218
+ const kind = marker[0];
219
+ fence = fence === null ? kind : fence === kind ? null : fence;
220
+ continue;
221
+ }
222
+ const candidate = (fence !== null ? line : line.replace(/^\s*>\s?/, '')).trim();
223
+ if (fence === null && !/^\s*>/.test(line) && !/^#{3,6}\s+/.test(line)) {
224
+ continue;
225
+ }
226
+ const title = /^#{2,6}\s+(.+?)\s*$/.exec(candidate)?.[1];
227
+ const canonical = REQUIRED_SECTIONS.find((section) => title?.toLowerCase() === section.toLowerCase());
228
+ if (canonical) {
229
+ embedded.add(canonical);
230
+ }
231
+ }
232
+ return [...embedded];
233
+ }
234
+ function extractTopLevelSectionBody(body, section) {
235
+ const heading = new RegExp(`^##[ \\t]+${escapeRegex(section)}[ \\t]*$`, 'mi').exec(body);
236
+ if (!heading) {
237
+ return null;
238
+ }
239
+ const start = heading.index + heading[0].length;
240
+ const rest = body.slice(start);
241
+ const next = /^##[ \t]+\S.*$/m.exec(rest);
242
+ return rest.slice(0, next?.index ?? rest.length).trim();
243
+ }
244
+ function renderSection(title, body) {
245
+ return `## ${title}\n\n${body.trim()}`;
246
+ }
247
+ function firstNonEmpty(...values) {
248
+ return (values.find((value) => value !== undefined && value !== null && value.length > 0) ?? '');
249
+ }
250
+ function firstSentence(text) {
251
+ const sentence = text
252
+ .split(/(?<=[.!?])\s+/)
253
+ .map((part) => part.trim())
254
+ .find((part) => part.length > 0 && !part.startsWith('#'));
255
+ return sentence ?? null;
256
+ }
257
+ function extractExecutableCriteria(source, criteria) {
258
+ const explicit = /\bGIVEN\b[\s\S]*?(?=(?:\n##|\bFILES:|\bFUNCTIONS:|\bTEST:|$))/i
259
+ .exec(source)?.[0]
260
+ ?.trim()
261
+ .replace(/[.\s]+$/, '.');
262
+ if (explicit) {
263
+ return `- ${explicit}`;
264
+ }
265
+ return criteria
266
+ .map((criterion) => `- GIVEN the requested change is implemented WHEN the behavior is exercised THEN ${criterion.text}`)
267
+ .join('\n');
268
+ }
269
+ function renderOwnedFiles(files) {
270
+ const sections = [];
271
+ for (const [title, entries] of [
272
+ ['Create', files.create],
273
+ ['Modify', files.modify],
274
+ ['Test', files.test],
275
+ ]) {
276
+ sections.push(`### ${title}`);
277
+ sections.push('');
278
+ sections.push(...(entries.length > 0 ? entries.map((entry) => `- \`${entry.path}\``) : ['- None.']));
279
+ sections.push('');
280
+ }
281
+ return sections.join('\n').trim();
282
+ }
81
283
  //# sourceMappingURL=unified-spec-builder.js.map
@@ -0,0 +1,18 @@
1
+ import type { CanonicalRepairManifest, CanonicalRepairPlan, CanonicalRepairRawStore, RepairCanonicalStoreInput } from '../../types/index.js';
2
+ declare function digest(value: Buffer | string): string;
3
+ declare function isContained(parent: string, child: string): boolean;
4
+ declare function artifactPath(projectId: string, artifactId: string): string;
5
+ declare function parseRawStore(raw: Buffer): CanonicalRepairRawStore;
6
+ /** Pure planner. It does not read or write the canonical store. */
7
+ export declare function buildCanonicalRepairPlan(raw: Buffer, manifest: CanonicalRepairManifest, manifestDigest?: string, requestedProjectId?: string): CanonicalRepairPlan;
8
+ /** Runs dry-run or the explicitly authorized, lock-protected repair. */
9
+ export declare function repairDuplicateSpecIds(input: RepairCanonicalStoreInput): Promise<CanonicalRepairPlan>;
10
+ export declare function rollbackCanonicalRepair(projectId: string, receiptPath: string): Promise<void>;
11
+ export declare const CANONICAL_DEDUPLICATOR_INTERNALS: {
12
+ digest: typeof digest;
13
+ parseRawStore: typeof parseRawStore;
14
+ artifactPath: typeof artifactPath;
15
+ isContained: typeof isContained;
16
+ };
17
+ export {};
18
+ //# sourceMappingURL=canonical-deduplicator.d.ts.map