@planu/cli 4.14.1 → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (452) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/LICENSE +55 -12
  3. package/README.md +10 -2
  4. package/dist/cli/commands/install.d.ts +1 -12
  5. package/dist/cli/commands/install.js +9 -65
  6. package/dist/cli/commands/job.js +4 -3
  7. package/dist/cli/commands/spec.js +12 -3
  8. package/dist/cli/commands/validate.js +2 -2
  9. package/dist/cli/router.js +1 -3
  10. package/dist/config/environment-schema.json +1 -3
  11. package/dist/config/official-sdd-tools.d.ts +4 -0
  12. package/dist/config/official-sdd-tools.js +35 -0
  13. package/dist/config/product-public.d.ts +139 -3
  14. package/dist/config/product-public.js +8 -1
  15. package/dist/config/product-public.json +7 -36
  16. package/dist/config/registries/hosts/codex.json +20 -363
  17. package/dist/config/release-policy.json +84 -2
  18. package/dist/config/runtime-policy.json +12 -1
  19. package/dist/config/skill-templates/planu-resume-work.md +2 -2
  20. package/dist/core/index.d.ts +1 -1
  21. package/dist/core/spec-validator.d.ts +4 -4
  22. package/dist/core/spec-validator.js +33 -19
  23. package/dist/engine/abortable-process-runner.d.ts +20 -2
  24. package/dist/engine/abortable-process-runner.js +243 -74
  25. package/dist/engine/ambiguity-scorer.js +4 -26
  26. package/dist/engine/auth/scope-mapper.js +1 -7
  27. package/dist/engine/autopilot/state-updater.js +4 -4
  28. package/dist/engine/cascade-hooks/hooks/review-enricher.hook.js +9 -6
  29. package/dist/engine/cascade-hooks/state-drift-detector.d.ts +3 -3
  30. package/dist/engine/cascade-hooks/state-drift-detector.js +1 -1
  31. package/dist/engine/ci-generator/planu-config.js +1 -7
  32. package/dist/engine/clarification-gate/gate.d.ts +3 -3
  33. package/dist/engine/code-scanner/layer-scanner.js +6 -22
  34. package/dist/engine/core-bridge.d.ts +3 -3
  35. package/dist/engine/cost-guardrails.d.ts +1 -1
  36. package/dist/engine/cost-guardrails.js +1 -1
  37. package/dist/engine/criterion-identity.d.ts +1 -0
  38. package/dist/engine/criterion-identity.js +7 -0
  39. package/dist/engine/dashboard/data-loader.js +2 -7
  40. package/dist/engine/dashboard/templates-layout.js +0 -3
  41. package/dist/engine/dashboard/templates-project.js +1 -13
  42. package/dist/engine/docs-site-generator/data-collector.d.ts +1 -1
  43. package/dist/engine/docs-site-generator/data-collector.js +7 -28
  44. package/dist/engine/docs-site-generator/tools-renderer.js +3 -10
  45. package/dist/engine/evidence-gates/artifact-reader.d.ts +12 -1
  46. package/dist/engine/evidence-gates/artifact-reader.js +174 -4
  47. package/dist/engine/evidence-gates/evidence-autofill.d.ts +10 -0
  48. package/dist/engine/evidence-gates/evidence-autofill.js +148 -0
  49. package/dist/engine/evidence-gates/evidence-skeletons.d.ts +19 -0
  50. package/dist/engine/evidence-gates/evidence-skeletons.js +69 -0
  51. package/dist/engine/evidence-gates/lifecycle-gate.js +4 -3
  52. package/dist/engine/evidence-gates/reconciliation-freshness.d.ts +11 -0
  53. package/dist/engine/evidence-gates/reconciliation-freshness.js +72 -0
  54. package/dist/engine/evidence-index/done-drift.d.ts +1 -1
  55. package/dist/engine/evidence-index/done-drift.js +40 -18
  56. package/dist/engine/execution/context.js +3 -0
  57. package/dist/engine/execution/durable-job-heartbeat-runtime.d.ts +4 -0
  58. package/dist/engine/execution/durable-job-heartbeat-runtime.js +400 -0
  59. package/dist/engine/execution/job-runtime.d.ts +30 -0
  60. package/dist/engine/execution/job-runtime.js +114 -6
  61. package/dist/engine/execution/operation-journal.js +10 -4
  62. package/dist/engine/execution/public-job-sanitizer.d.ts +4 -0
  63. package/dist/engine/execution/public-job-sanitizer.js +31 -0
  64. package/dist/engine/execution/validate-job-executor.d.ts +12 -1
  65. package/dist/engine/execution/validate-job-executor.js +549 -86
  66. package/dist/engine/execution/validate-job-payload.js +107 -50
  67. package/dist/engine/execution/validate-job-recovery.js +1 -1
  68. package/dist/engine/execution/validate-worker-runtime.d.ts +5 -0
  69. package/dist/engine/execution/validate-worker-runtime.js +90 -2
  70. package/dist/engine/execution/validate-worker-thread.js +17 -24
  71. package/dist/engine/handoff-artifacts/schemas.d.ts +2 -1
  72. package/dist/engine/handoff-artifacts/schemas.js +33 -0
  73. package/dist/engine/handoff-packager.d.ts +19 -0
  74. package/dist/engine/handoff-packager.js +415 -13
  75. package/dist/engine/hooks/effort-configurator.js +1 -1
  76. package/dist/engine/impact-detector/test-break-predictor.js +3 -3
  77. package/dist/engine/impact-detector/tool-registration.js +1 -1
  78. package/dist/engine/implementation-contract/renderer.js +23 -8
  79. package/dist/engine/lifecycle-reconciliation-io.d.ts +11 -0
  80. package/dist/engine/lifecycle-reconciliation-io.js +180 -0
  81. package/dist/engine/lifecycle-reconciliation.d.ts +16 -0
  82. package/dist/engine/lifecycle-reconciliation.js +406 -0
  83. package/dist/engine/local-first/tool-classification.js +2 -0
  84. package/dist/engine/macos-keychain-native-port.d.ts +5 -0
  85. package/dist/engine/macos-keychain-native-port.js +19 -7
  86. package/dist/engine/migrations/remove-commercial-state.d.ts +3 -0
  87. package/dist/engine/migrations/remove-commercial-state.js +343 -0
  88. package/dist/engine/minimality/policy-loader.js +247 -6
  89. package/dist/engine/network-policy.js +1 -0
  90. package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
  91. package/dist/engine/planu-core.darwin-arm64.node.sbom.json +20 -20
  92. package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
  93. package/dist/engine/planu-core.darwin-x64.node.sbom.json +20 -20
  94. package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
  95. package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +20 -20
  96. package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
  97. package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +20 -20
  98. package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
  99. package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +20 -20
  100. package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
  101. package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +20 -20
  102. package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
  103. package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +20 -20
  104. package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
  105. package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +20 -20
  106. package/dist/engine/readiness-checker.d.ts +1 -16
  107. package/dist/engine/readiness-checker.js +27 -88
  108. package/dist/engine/reverse-engineer/api-detector.js +2 -13
  109. package/dist/engine/reverse-engineer/complexity-analyzer.js +2 -13
  110. package/dist/engine/reverse-engineer/config-analyzer.js +2 -13
  111. package/dist/engine/reverse-engineer/dependency-graph.js +2 -13
  112. package/dist/engine/reverse-engineer/test-analyzer.js +2 -13
  113. package/dist/engine/reverse-engineer/walk-ignore.d.ts +3 -0
  114. package/dist/engine/reverse-engineer/walk-ignore.js +26 -0
  115. package/dist/engine/runtime-policy.d.ts +2 -0
  116. package/dist/engine/runtime-policy.js +42 -5
  117. package/dist/engine/runtime-security/rate-limiter.js +0 -1
  118. package/dist/engine/safety/contained-project-file.d.ts +3 -0
  119. package/dist/engine/safety/contained-project-file.js +23 -0
  120. package/dist/engine/scope-boundaries/contradiction-checker.js +63 -1
  121. package/dist/engine/scope-boundaries/scope-validator.js +45 -7
  122. package/dist/engine/session/checkpoint-writer.js +0 -9
  123. package/dist/engine/spec-format/acceptance-criteria.d.ts +6 -2
  124. package/dist/engine/spec-format/acceptance-criteria.js +82 -22
  125. package/dist/engine/spec-format/markdown-sections.d.ts +14 -0
  126. package/dist/engine/spec-format/markdown-sections.js +105 -0
  127. package/dist/engine/spec-format/read-technical-section.js +33 -5
  128. package/dist/engine/spec-format/technical-md-populator.js +3 -2
  129. package/dist/engine/spec-format/text-fences.js +20 -2
  130. package/dist/engine/spec-format/unified-spec-builder.d.ts +7 -0
  131. package/dist/engine/spec-format/unified-spec-builder.js +202 -0
  132. package/dist/engine/spec-migrator/canonical-deduplicator.d.ts +18 -0
  133. package/dist/engine/spec-migrator/canonical-deduplicator.js +479 -0
  134. package/dist/engine/spec-migrator/filesystem-import.d.ts +4 -8
  135. package/dist/engine/spec-migrator/filesystem-import.js +121 -14
  136. package/dist/engine/spec-migrator/frontmatter-parser.js +32 -15
  137. package/dist/engine/spec-migrator/index.d.ts +1 -0
  138. package/dist/engine/spec-migrator/index.js +1 -0
  139. package/dist/engine/spec-state-machine/transition-spec.d.ts +6 -1
  140. package/dist/engine/spec-state-machine/transition-spec.js +10 -1
  141. package/dist/engine/spec-state-syncer.js +1 -1
  142. package/dist/engine/technical-enricher/index.js +16 -8
  143. package/dist/engine/technical-enricher/render-enriched.d.ts +1 -0
  144. package/dist/engine/technical-enricher/render-enriched.js +42 -14
  145. package/dist/engine/telemetry/health-checker.js +53 -17
  146. package/dist/engine/telemetry/telemetry-client.js +26 -8
  147. package/dist/engine/text-signal-boundaries.d.ts +2 -0
  148. package/dist/engine/text-signal-boundaries.js +25 -2
  149. package/dist/engine/time/relative-date.d.ts +3 -0
  150. package/dist/engine/time/relative-date.js +16 -0
  151. package/dist/engine/timing/budget.d.ts +11 -0
  152. package/dist/engine/timing/budget.js +31 -9
  153. package/dist/engine/token-optimizer/analytics.d.ts +1 -1
  154. package/dist/engine/token-optimizer/analytics.js +5 -6
  155. package/dist/engine/usage-tracker/core.d.ts +2 -2
  156. package/dist/engine/usage-tracker/core.js +1 -2
  157. package/dist/engine/usage-tracker/index.d.ts +0 -3
  158. package/dist/engine/usage-tracker/index.js +0 -3
  159. package/dist/engine/usage-tracker/stats.d.ts +2 -2
  160. package/dist/engine/usage-tracker/stats.js +1 -28
  161. package/dist/engine/validation/durable-validation.d.ts +1 -1
  162. package/dist/engine/validation/durable-validation.js +3 -82
  163. package/dist/engine/validation/validation-freshness.d.ts +11 -1
  164. package/dist/engine/validation/validation-freshness.js +231 -91
  165. package/dist/engine/validation/validation-receipt.d.ts +9 -6
  166. package/dist/engine/validation/validation-receipt.js +84 -24
  167. package/dist/engine/validation/validation-source-digest.d.ts +2 -0
  168. package/dist/engine/validation/validation-source-digest.js +86 -0
  169. package/dist/engine/validation/validation-submission.d.ts +5 -4
  170. package/dist/engine/validation/validation-submission.js +25 -11
  171. package/dist/engine/validation/validation-worktree.d.ts +21 -0
  172. package/dist/engine/validation/validation-worktree.js +260 -0
  173. package/dist/engine/validation-impact-planner.d.ts +14 -2
  174. package/dist/engine/validation-impact-planner.js +0 -3
  175. package/dist/engine/validator/dor-dod.d.ts +1 -0
  176. package/dist/engine/validator/dor-dod.js +5 -3
  177. package/dist/engine/validator/extractors.d.ts +1 -5
  178. package/dist/engine/validator/extractors.js +24 -69
  179. package/dist/engine/validator/spec-compliance-runner.d.ts +12 -24
  180. package/dist/engine/validator/spec-compliance-runner.js +429 -211
  181. package/dist/engine/validator.js +35 -22
  182. package/dist/index.js +4 -16
  183. package/dist/resources/usage-stats-resource.js +2 -18
  184. package/dist/security/adapters/linux-secret-service.js +7 -3
  185. package/dist/security/adapters/windows-credential-manager.js +6 -4
  186. package/dist/server/routes/specs.js +13 -1
  187. package/dist/storage/base-store.d.ts +1 -1
  188. package/dist/storage/base-store.js +1 -1
  189. package/dist/storage/feedback-remote.d.ts +2 -3
  190. package/dist/storage/feedback-remote.js +45 -31
  191. package/dist/storage/global-store.d.ts +1 -17
  192. package/dist/storage/global-store.js +1 -42
  193. package/dist/storage/index.d.ts +0 -2
  194. package/dist/storage/index.js +0 -2
  195. package/dist/storage/migrations/canonical-storage.js +124 -11
  196. package/dist/storage/runtime-db.d.ts +9 -0
  197. package/dist/storage/runtime-db.js +64 -8
  198. package/dist/storage/spec-store.d.ts +14 -2
  199. package/dist/storage/spec-store.js +104 -14
  200. package/dist/storage/status-store/atomic-status-write.d.ts +5 -0
  201. package/dist/storage/status-store/atomic-status-write.js +57 -0
  202. package/dist/storage/status-store/file-lock.js +30 -11
  203. package/dist/storage/status-store/self-healing.js +3 -3
  204. package/dist/storage/status-store/version-sync.d.ts +2 -1
  205. package/dist/storage/status-store/version-sync.js +35 -40
  206. package/dist/storage/usage-store.d.ts +1 -5
  207. package/dist/storage/usage-store.js +0 -35
  208. package/dist/tools/auto-update-handler.d.ts +3 -3
  209. package/dist/tools/auto-update-handler.js +3 -3
  210. package/dist/tools/challenge-spec/resilience-challenge-scenarios.d.ts +2 -1
  211. package/dist/tools/challenge-spec/resilience-challenge-scenarios.js +10 -7
  212. package/dist/tools/challenge-spec/scenario-collector.js +1 -1
  213. package/dist/tools/challenge-spec/scenarios-data.js +1 -1
  214. package/dist/tools/challenge-spec/scenarios-utils.d.ts +1 -1
  215. package/dist/tools/challenge-spec/scenarios-utils.js +35 -11
  216. package/dist/tools/challenge-spec-helpers.d.ts +10 -1
  217. package/dist/tools/challenge-spec-helpers.js +63 -22
  218. package/dist/tools/challenge-spec.js +23 -8
  219. package/dist/tools/check-readiness.js +38 -14
  220. package/dist/tools/configure-telemetry.js +30 -2
  221. package/dist/tools/create-spec/spec-builder.d.ts +7 -0
  222. package/dist/tools/create-spec/spec-builder.js +19 -15
  223. package/dist/tools/create-spec.d.ts +1 -0
  224. package/dist/tools/create-spec.js +584 -309
  225. package/dist/tools/git/branch-ops.d.ts +1 -1
  226. package/dist/tools/git/branch-ops.js +3 -2
  227. package/dist/tools/init-project/handler.js +10 -12
  228. package/dist/tools/init-project/host-assets-writer.js +26 -1
  229. package/dist/tools/init-project/lint-scaffolder.d.ts +3 -3
  230. package/dist/tools/init-project/migration-runner.d.ts +2 -4
  231. package/dist/tools/init-project/migration-runner.js +3 -2
  232. package/dist/tools/init-project/portable-index-reconciler.d.ts +4 -0
  233. package/dist/tools/init-project/portable-index-reconciler.js +14 -0
  234. package/dist/tools/init-project/result-builder.js +1 -3
  235. package/dist/tools/jobs/handlers.js +38 -6
  236. package/dist/tools/list-specs.js +5 -26
  237. package/dist/tools/merge-risk-handler.js +2 -2
  238. package/dist/tools/package-handoff.js +2 -0
  239. package/dist/tools/reconcile-spec.js +0 -27
  240. package/dist/tools/register-agent-squad-tools.js +2 -2
  241. package/dist/tools/register-platform-tools/design-stack-tools.js +11 -11
  242. package/dist/tools/register-platform-tools/lifecycle-infra-tools.js +7 -7
  243. package/dist/tools/register-sdd-tools.d.ts +1 -1
  244. package/dist/tools/register-sdd-tools.js +2 -29
  245. package/dist/tools/register-spec-tools/analysis-tools.js +8 -8
  246. package/dist/tools/register-spec-tools/core-spec-tools.js +29 -19
  247. package/dist/tools/safe-handler.d.ts +5 -13
  248. package/dist/tools/safe-handler.js +7 -23
  249. package/dist/tools/schemas/index.d.ts +1 -1
  250. package/dist/tools/schemas/index.js +1 -1
  251. package/dist/tools/schemas/output-schemas.d.ts +0 -23
  252. package/dist/tools/schemas/output-schemas.js +0 -28
  253. package/dist/tools/schemas/spec.d.ts +4 -0
  254. package/dist/tools/schemas/spec.js +3 -0
  255. package/dist/tools/schemas/validate-output-schema.d.ts +11 -0
  256. package/dist/tools/schemas/validate-output-schema.js +7 -0
  257. package/dist/tools/spec-prompt-handler.js +3 -3
  258. package/dist/tools/sync-spec-state-handler.d.ts +1 -1
  259. package/dist/tools/sync-spec-state-handler.js +50 -2
  260. package/dist/tools/tool-entry.d.ts +4 -4
  261. package/dist/tools/tool-entry.js +3 -3
  262. package/dist/tools/tool-registry/core-tools.js +31 -15
  263. package/dist/tools/tool-registry/group-infra.js +27 -110
  264. package/dist/tools/tool-registry/group-quality-compliance.js +26 -26
  265. package/dist/tools/tool-registry/inventory.d.ts +2 -0
  266. package/dist/tools/tool-registry/inventory.js +2 -0
  267. package/dist/tools/tool-registry-helpers.d.ts +3 -3
  268. package/dist/tools/tool-registry-helpers.js +3 -3
  269. package/dist/tools/tool-usage-report/registered-tools.d.ts +1 -1
  270. package/dist/tools/tool-usage-report/registered-tools.js +3 -15
  271. package/dist/tools/update-status/approval-gates.js +3 -0
  272. package/dist/tools/update-status/batch.d.ts +6 -2
  273. package/dist/tools/update-status/batch.js +58 -1
  274. package/dist/tools/update-status/dod-gates.d.ts +16 -1
  275. package/dist/tools/update-status/dod-gates.js +191 -1
  276. package/dist/tools/update-status/done-receipt-verifier.d.ts +8 -0
  277. package/dist/tools/update-status/done-receipt-verifier.js +75 -27
  278. package/dist/tools/update-status/evidence-gate.d.ts +4 -0
  279. package/dist/tools/update-status/evidence-gate.js +103 -3
  280. package/dist/tools/update-status/file-sync.d.ts +4 -3
  281. package/dist/tools/update-status/file-sync.js +57 -6
  282. package/dist/tools/update-status/index.d.ts +25 -1
  283. package/dist/tools/update-status/index.js +292 -78
  284. package/dist/tools/update-status/response-builder.js +3 -3
  285. package/dist/tools/update-status/side-effects.d.ts +2 -0
  286. package/dist/tools/update-status/side-effects.js +2 -2
  287. package/dist/tools/update-status/transition-guard.js +45 -16
  288. package/dist/tools/update-status-actions.d.ts +4 -2
  289. package/dist/tools/update-status-actions.js +16 -14
  290. package/dist/tools/usage-report.js +1 -16
  291. package/dist/tools/usage-stats.js +1 -18
  292. package/dist/tools/usage-tracking.d.ts +4 -0
  293. package/dist/tools/usage-tracking.js +20 -0
  294. package/dist/tools/validate-lint.d.ts +1 -0
  295. package/dist/tools/validate-lint.js +1 -1
  296. package/dist/tools/validate.d.ts +2 -1
  297. package/dist/tools/validate.js +114 -34
  298. package/dist/tools/workspace-dashboard-handler.js +38 -0
  299. package/dist/transports/http-transport.js +6 -8
  300. package/dist/transports/mcp-types.d.ts +0 -2
  301. package/dist/transports/session-context.js +1 -1
  302. package/dist/types/cascade-hooks.d.ts +1 -1
  303. package/dist/types/ci.d.ts +0 -4
  304. package/dist/types/clarification-token.d.ts +1 -1
  305. package/dist/types/claude-code-runtime.d.ts +0 -27
  306. package/dist/types/commercial-migration.d.ts +17 -0
  307. package/dist/types/commercial-migration.js +2 -0
  308. package/dist/types/common/primitives.d.ts +0 -2
  309. package/dist/types/core-bridge.d.ts +4 -10
  310. package/dist/types/dashboard.d.ts +3 -7
  311. package/dist/types/docs.d.ts +0 -1
  312. package/dist/types/durable-job.d.ts +4 -4
  313. package/dist/types/durable-validation.d.ts +100 -2
  314. package/dist/types/durable-validation.js +165 -1
  315. package/dist/types/errors/structured-error.d.ts +3 -3
  316. package/dist/types/estimation.d.ts +2 -0
  317. package/dist/types/evidence-autofill.d.ts +34 -0
  318. package/dist/types/evidence-autofill.js +2 -0
  319. package/dist/types/evidence-gates.d.ts +9 -1
  320. package/dist/types/execution.d.ts +2 -0
  321. package/dist/types/gemini-integration.d.ts +0 -11
  322. package/dist/types/handoff-artifacts.d.ts +41 -0
  323. package/dist/types/hooks-advanced.d.ts +0 -13
  324. package/dist/types/impact-detection.d.ts +2 -2
  325. package/dist/types/index.d.ts +3 -6
  326. package/dist/types/index.js +2 -5
  327. package/dist/types/kiro-integration.d.ts +0 -6
  328. package/dist/types/network-policy.d.ts +1 -1
  329. package/dist/types/project/lint-scaffolder.d.ts +1 -1
  330. package/dist/types/runtime-policy.d.ts +11 -0
  331. package/dist/types/session-safeguard.d.ts +0 -31
  332. package/dist/types/spec/canonical-repair.d.ts +81 -0
  333. package/dist/types/spec/canonical-repair.js +2 -0
  334. package/dist/types/spec/core.d.ts +20 -0
  335. package/dist/types/spec/index.d.ts +1 -0
  336. package/dist/types/spec/index.js +1 -0
  337. package/dist/types/spec/inputs.d.ts +35 -1
  338. package/dist/types/spec-format.d.ts +25 -1
  339. package/dist/types/telemetry.d.ts +4 -1
  340. package/dist/types/transition-log.d.ts +1 -1
  341. package/dist/types/transport.d.ts +0 -2
  342. package/dist/types/usage.d.ts +1 -36
  343. package/dist/types/usage.js +1 -1
  344. package/dist/types/validate-worker.d.ts +6 -0
  345. package/dist/types/validation-receipt.d.ts +1 -1
  346. package/dist/types/validation.d.ts +10 -4
  347. package/package.json +17 -12
  348. package/planu-native.json +1 -1
  349. package/planu-plugin.json +23 -40
  350. package/scripts/lib/pending-release-file.mjs +93 -0
  351. package/src/i18n/messages/en.json +0 -41
  352. package/src/i18n/messages/es.json +0 -41
  353. package/src/i18n/messages/pt.json +0 -41
  354. package/dist/cli/commands/activate.d.ts +0 -14
  355. package/dist/cli/commands/activate.js +0 -174
  356. package/dist/config/license-plans.json +0 -251
  357. package/dist/config/product-public.ts +0 -148
  358. package/dist/config/server-instructions.ts +0 -121
  359. package/dist/config/version-defaults.ts +0 -76
  360. package/dist/config/version.ts +0 -21
  361. package/dist/engine/ai-integration/gemini/settings-generator.d.ts +0 -4
  362. package/dist/engine/ai-integration/gemini/settings-generator.js +0 -195
  363. package/dist/engine/ai-integration/kiro/hooks-generator.d.ts +0 -6
  364. package/dist/engine/ai-integration/kiro/hooks-generator.js +0 -75
  365. package/dist/engine/hooks/full-spectrum-generator.d.ts +0 -32
  366. package/dist/engine/hooks/full-spectrum-generator.js +0 -117
  367. package/dist/engine/license-validator/config-loader.d.ts +0 -5
  368. package/dist/engine/license-validator/config-loader.js +0 -16
  369. package/dist/engine/license-validator/core.d.ts +0 -15
  370. package/dist/engine/license-validator/core.js +0 -151
  371. package/dist/engine/license-validator/fingerprint.d.ts +0 -3
  372. package/dist/engine/license-validator/fingerprint.js +0 -17
  373. package/dist/engine/license-validator/lemon-squeezy.d.ts +0 -5
  374. package/dist/engine/license-validator/lemon-squeezy.js +0 -55
  375. package/dist/engine/license-validator.d.ts +0 -5
  376. package/dist/engine/license-validator.js +0 -6
  377. package/dist/engine/product-intelligence/index.d.ts +0 -7
  378. package/dist/engine/product-intelligence/index.js +0 -211
  379. package/dist/engine/session/auto-save.d.ts +0 -18
  380. package/dist/engine/session/auto-save.js +0 -98
  381. package/dist/engine/session/context-snapshot.d.ts +0 -8
  382. package/dist/engine/session/context-snapshot.js +0 -49
  383. package/dist/engine/session/memory-sync.d.ts +0 -12
  384. package/dist/engine/session/memory-sync.js +0 -34
  385. package/dist/engine/session/session-diff.d.ts +0 -11
  386. package/dist/engine/session/session-diff.js +0 -42
  387. package/dist/engine/session/session-merge.d.ts +0 -12
  388. package/dist/engine/session/session-merge.js +0 -55
  389. package/dist/engine/session-safeguard/checkpoint-runner.d.ts +0 -4
  390. package/dist/engine/session-safeguard/checkpoint-runner.js +0 -70
  391. package/dist/engine/session-safeguard/learnings-buffer.d.ts +0 -15
  392. package/dist/engine/session-safeguard/learnings-buffer.js +0 -91
  393. package/dist/engine/session-safeguard/precompaction-drain.d.ts +0 -10
  394. package/dist/engine/session-safeguard/precompaction-drain.js +0 -82
  395. package/dist/engine/session-safeguard/unpushed-detector.d.ts +0 -7
  396. package/dist/engine/session-safeguard/unpushed-detector.js +0 -59
  397. package/dist/engine/spec-changelog/core.d.ts +0 -16
  398. package/dist/engine/spec-changelog/core.js +0 -176
  399. package/dist/engine/spec-changelog/diff.d.ts +0 -18
  400. package/dist/engine/spec-changelog/diff.js +0 -121
  401. package/dist/engine/spec-changelog/index.d.ts +0 -3
  402. package/dist/engine/spec-changelog/index.js +0 -4
  403. package/dist/engine/trial-engine.d.ts +0 -5
  404. package/dist/engine/trial-engine.js +0 -14
  405. package/dist/engine/usage-tracker/rate-check.d.ts +0 -7
  406. package/dist/engine/usage-tracker/rate-check.js +0 -18
  407. package/dist/engine/usage-tracker/rate-limiter.d.ts +0 -14
  408. package/dist/engine/usage-tracker/rate-limiter.js +0 -73
  409. package/dist/engine/usage-tracker/trial.d.ts +0 -27
  410. package/dist/engine/usage-tracker/trial.js +0 -49
  411. package/dist/engine/webhook/event-handlers.d.ts +0 -25
  412. package/dist/engine/webhook/event-handlers.js +0 -89
  413. package/dist/engine/webhook/index.d.ts +0 -4
  414. package/dist/engine/webhook/index.js +0 -5
  415. package/dist/engine/webhook/server.d.ts +0 -12
  416. package/dist/engine/webhook/server.js +0 -249
  417. package/dist/engine/webhook/signature.d.ts +0 -16
  418. package/dist/engine/webhook/signature.js +0 -49
  419. package/dist/storage/license-store.d.ts +0 -6
  420. package/dist/storage/license-store.js +0 -63
  421. package/dist/storage/session-store.d.ts +0 -20
  422. package/dist/storage/session-store.js +0 -136
  423. package/dist/tools/activate-license.d.ts +0 -6
  424. package/dist/tools/activate-license.js +0 -132
  425. package/dist/tools/checkpoint-handler.d.ts +0 -3
  426. package/dist/tools/checkpoint-handler.js +0 -130
  427. package/dist/tools/export-session.d.ts +0 -3
  428. package/dist/tools/export-session.js +0 -90
  429. package/dist/tools/license-gate.d.ts +0 -18
  430. package/dist/tools/license-gate.js +0 -222
  431. package/dist/tools/license-status.d.ts +0 -3
  432. package/dist/tools/license-status.js +0 -93
  433. package/dist/tools/list-sessions.d.ts +0 -3
  434. package/dist/tools/list-sessions.js +0 -46
  435. package/dist/tools/planu-session-checkpoint.d.ts +0 -6
  436. package/dist/tools/planu-session-checkpoint.js +0 -42
  437. package/dist/tools/restore-session.d.ts +0 -3
  438. package/dist/tools/restore-session.js +0 -71
  439. package/dist/tools/spec-history.d.ts +0 -3
  440. package/dist/tools/spec-history.js +0 -59
  441. package/dist/tools/webhook.d.ts +0 -6
  442. package/dist/tools/webhook.js +0 -150
  443. package/dist/types/changelog.d.ts +0 -49
  444. package/dist/types/changelog.js +0 -3
  445. package/dist/types/licensing.d.ts +0 -109
  446. package/dist/types/licensing.js +0 -4
  447. package/dist/types/product-intelligence.d.ts +0 -60
  448. package/dist/types/product-intelligence.js +0 -3
  449. package/dist/types/session.d.ts +0 -84
  450. package/dist/types/session.js +0 -3
  451. package/dist/types/webhook.d.ts +0 -62
  452. package/dist/types/webhook.js +0 -4
@@ -0,0 +1,479 @@
1
+ /* eslint-disable max-lines, max-lines-per-function, complexity -- one audited administrative transaction */
2
+ import { createHash } from 'node:crypto';
3
+ import { mkdir as mkdirFs, readFile as readFileFs } from 'node:fs/promises';
4
+ import { join, relative, resolve } from 'node:path';
5
+ import { atomicWriteFile } from '../safety/atomic-write-file.js';
6
+ import { projectDataDir } from '../../storage/base-store.js';
7
+ import { invalidateSpecsCache, validateCanonicalSpecRows, withCanonicalSpecRepairLock, } from '../../storage/spec-store.js';
8
+ function digest(value) {
9
+ return createHash('sha256').update(value).digest('hex');
10
+ }
11
+ function canonicalJson(value) {
12
+ return `${JSON.stringify(value, null, 2)}\n`;
13
+ }
14
+ function privateFailure(message) {
15
+ return new Error(`[Planu] Canonical repair blocked: ${message}`);
16
+ }
17
+ function repairPaths(projectId) {
18
+ const root = projectDataDir(projectId);
19
+ return { store: join(root, 'specs.json'), dir: join(root, 'repairs', 'spec-id-dedup') };
20
+ }
21
+ function isContained(parent, child) {
22
+ const rel = relative(resolve(parent), resolve(child));
23
+ return rel !== '' && !rel.startsWith('..') && !rel.includes('/../');
24
+ }
25
+ function artifactPath(projectId, artifactId) {
26
+ const { dir } = repairPaths(projectId);
27
+ if (!artifactId.startsWith('repairs/spec-id-dedup/')) {
28
+ throw privateFailure('repair artifact is outside the project repair directory');
29
+ }
30
+ const candidate = resolve(projectDataDir(projectId), artifactId);
31
+ if (!isContained(dir, candidate)) {
32
+ throw privateFailure('repair artifact is outside the project repair directory');
33
+ }
34
+ return candidate;
35
+ }
36
+ function parseRawStore(raw) {
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(raw.toString('utf8'));
40
+ }
41
+ catch {
42
+ throw privateFailure('store is not valid JSON');
43
+ }
44
+ if (Array.isArray(parsed)) {
45
+ return { envelope: false, rows: parsed };
46
+ }
47
+ if (parsed && typeof parsed === 'object' && Array.isArray(parsed.data)) {
48
+ return { envelope: true, rows: parsed.data };
49
+ }
50
+ throw privateFailure('store shape is invalid');
51
+ }
52
+ function serializeStore(store, rows) {
53
+ return Buffer.from(canonicalJson(store.envelope ? { schemaVersion: 1, data: rows } : rows), 'utf8');
54
+ }
55
+ function requireString(value, label) {
56
+ if (typeof value !== 'string' || value.length === 0) {
57
+ throw privateFailure(`${label} is missing`);
58
+ }
59
+ return value;
60
+ }
61
+ function validateManifest(manifest, requestedProjectId) {
62
+ if (manifest.projectId !== requestedProjectId) {
63
+ throw privateFailure('manifest project does not match requested project');
64
+ }
65
+ if (!/^[a-f0-9]{64}$/.test(manifest.sourceDigest)) {
66
+ throw privateFailure('manifest source digest is invalid');
67
+ }
68
+ if (!manifest.sourceProjectAliases.includes(manifest.canonicalSourceProjectAlias) ||
69
+ manifest.canonicalSourceProjectAlias.length === 0) {
70
+ throw privateFailure('canonical source alias is not declared');
71
+ }
72
+ if (new Set(manifest.foreignOwnerSingletonIds).size !== manifest.foreignOwnerSingletonIds.length) {
73
+ throw privateFailure('foreign singleton IDs are duplicated');
74
+ }
75
+ for (const override of Object.values(manifest.lifecycleOverrides ?? {})) {
76
+ if (override.reason.length < 100 || !override.canonicalUuid) {
77
+ throw privateFailure('lifecycle override is incomplete');
78
+ }
79
+ }
80
+ }
81
+ function identityFieldsAgree(rows) {
82
+ const first = rows[0];
83
+ return (first !== undefined &&
84
+ rows.every((row) => row.id === first.id &&
85
+ row.title === first.title &&
86
+ row.slug === first.slug &&
87
+ row.specPath === first.specPath));
88
+ }
89
+ function validateRawUuidIdentities(rows) {
90
+ const identities = new Set();
91
+ for (const row of rows) {
92
+ const primary = requireString(row.uuid, 'primary UUID');
93
+ for (const identity of [primary, ...(row.uuidAliases ?? [])]) {
94
+ const normalized = requireString(identity, 'UUID alias');
95
+ if (identities.has(normalized)) {
96
+ throw privateFailure('raw UUID identity is repeated or ambiguous');
97
+ }
98
+ identities.add(normalized);
99
+ }
100
+ }
101
+ }
102
+ function planPayload(plan) {
103
+ return {
104
+ changed: plan.changed,
105
+ projectId: plan.projectId,
106
+ sourceDigest: plan.sourceDigest,
107
+ manifestDigest: plan.manifestDigest,
108
+ resultDigest: plan.resultDigest,
109
+ duplicateGroups: plan.duplicateGroups,
110
+ foreignOwnerSingletons: plan.foreignOwnerSingletons,
111
+ fieldChanges: plan.fieldChanges,
112
+ artifactId: plan.artifactId,
113
+ };
114
+ }
115
+ /** Pure planner. It does not read or write the canonical store. */
116
+ export function buildCanonicalRepairPlan(raw, manifest, manifestDigest = digest(canonicalJson(manifest)), requestedProjectId = manifest.projectId) {
117
+ validateManifest(manifest, requestedProjectId);
118
+ const sourceDigest = digest(raw);
119
+ if (sourceDigest !== manifest.sourceDigest) {
120
+ const repaired = parseRawStore(raw);
121
+ try {
122
+ validateCanonicalSpecRows(requestedProjectId, repaired.rows);
123
+ const unchanged = {
124
+ changed: false,
125
+ projectId: requestedProjectId,
126
+ sourceDigest,
127
+ manifestDigest,
128
+ resultDigest: sourceDigest,
129
+ duplicateGroups: 0,
130
+ foreignOwnerSingletons: 0,
131
+ fieldChanges: [],
132
+ artifactId: `repairs/spec-id-dedup/${sourceDigest}.dry-run.json`,
133
+ };
134
+ return {
135
+ ...unchanged,
136
+ planDigest: digest(canonicalJson(planPayload(unchanged))),
137
+ resultBytes: raw,
138
+ };
139
+ }
140
+ catch {
141
+ throw privateFailure('source digest does not match manifest');
142
+ }
143
+ }
144
+ const store = parseRawStore(raw);
145
+ validateRawUuidIdentities(store.rows);
146
+ const groups = new Map();
147
+ for (const row of store.rows) {
148
+ const id = requireString(row.id, 'spec ID');
149
+ const group = groups.get(id);
150
+ if (group) {
151
+ group.push(row);
152
+ }
153
+ else {
154
+ groups.set(id, [row]);
155
+ }
156
+ }
157
+ const fieldChanges = [];
158
+ const nextRows = [];
159
+ const listedSingletons = new Set(manifest.foreignOwnerSingletonIds);
160
+ let duplicates = 0;
161
+ let singletonChanges = 0;
162
+ for (const [id, rows] of groups) {
163
+ const row = rows[0];
164
+ if (!row) {
165
+ throw privateFailure('empty duplicate group');
166
+ }
167
+ if (rows.length === 1) {
168
+ if (row.projectId === requestedProjectId) {
169
+ nextRows.push({ ...row });
170
+ continue;
171
+ }
172
+ if (!listedSingletons.delete(id) || !manifest.sourceProjectAliases.includes(row.projectId)) {
173
+ throw privateFailure('foreign singleton is not explicitly authorized');
174
+ }
175
+ nextRows.push({ ...row, projectId: requestedProjectId });
176
+ singletonChanges += 1;
177
+ fieldChanges.push({ id, field: 'projectId', from: row.projectId, to: requestedProjectId });
178
+ continue;
179
+ }
180
+ duplicates += 1;
181
+ if (!identityFieldsAgree(rows)) {
182
+ throw privateFailure('duplicate group has incompatible identity fields');
183
+ }
184
+ const canonicalRows = rows.filter((candidate) => candidate.projectId === manifest.canonicalSourceProjectAlias);
185
+ if (canonicalRows.length !== 1) {
186
+ throw privateFailure('duplicate group must have exactly one canonical-source row');
187
+ }
188
+ const canonical = canonicalRows[0];
189
+ if (!canonical) {
190
+ throw privateFailure('canonical-source row is missing');
191
+ }
192
+ const primaryUuid = requireString(canonical.uuid, 'canonical UUID');
193
+ const statuses = new Set(rows.map((candidate) => candidate.status));
194
+ const override = manifest.lifecycleOverrides?.[id];
195
+ if (statuses.size > 1) {
196
+ if (!override) {
197
+ throw privateFailure('lifecycle disagreement has no valid override');
198
+ }
199
+ if (override.canonicalUuid !== primaryUuid ||
200
+ override.retainedStatus !== canonical.status ||
201
+ override.reason.length < 100) {
202
+ throw privateFailure('lifecycle disagreement has no valid override');
203
+ }
204
+ }
205
+ const aliases = new Set(canonical.uuidAliases ?? []);
206
+ for (const candidate of rows) {
207
+ const candidateUuid = requireString(candidate.uuid, 'duplicate UUID');
208
+ if (candidate !== canonical) {
209
+ if (!manifest.sourceProjectAliases.includes(candidate.projectId)) {
210
+ throw privateFailure('duplicate row has an unknown source project alias');
211
+ }
212
+ aliases.add(candidateUuid);
213
+ for (const alias of candidate.uuidAliases ?? []) {
214
+ aliases.add(alias);
215
+ }
216
+ for (const field of ['technicalPath', 'progressPath']) {
217
+ if (candidate[field] !== canonical[field]) {
218
+ fieldChanges.push({
219
+ id,
220
+ field,
221
+ from: candidate[field] ?? null,
222
+ to: canonical[field] ?? null,
223
+ });
224
+ }
225
+ }
226
+ }
227
+ }
228
+ aliases.delete(primaryUuid);
229
+ nextRows.push({
230
+ ...canonical,
231
+ projectId: requestedProjectId,
232
+ ...(aliases.size > 0
233
+ ? { uuidAliases: [...aliases].sort((left, right) => left.localeCompare(right)) }
234
+ : {}),
235
+ });
236
+ fieldChanges.push({
237
+ id,
238
+ field: 'projectId',
239
+ from: canonical.projectId,
240
+ to: requestedProjectId,
241
+ });
242
+ }
243
+ if (listedSingletons.size > 0) {
244
+ throw privateFailure('manifest lists a missing foreign singleton');
245
+ }
246
+ validateCanonicalSpecRows(requestedProjectId, nextRows);
247
+ const resultBytes = serializeStore(store, nextRows);
248
+ const base = {
249
+ changed: duplicates > 0 || singletonChanges > 0,
250
+ projectId: requestedProjectId,
251
+ sourceDigest,
252
+ manifestDigest,
253
+ resultDigest: digest(resultBytes),
254
+ duplicateGroups: duplicates,
255
+ foreignOwnerSingletons: singletonChanges,
256
+ fieldChanges,
257
+ artifactId: `repairs/spec-id-dedup/${sourceDigest}.dry-run.json`,
258
+ };
259
+ return { ...base, planDigest: digest(canonicalJson(planPayload(base))), resultBytes };
260
+ }
261
+ function toPrivateReport(plan) {
262
+ return {
263
+ schemaVersion: 1,
264
+ ...planPayload(plan),
265
+ planDigest: plan.planDigest,
266
+ };
267
+ }
268
+ function buildReceipt(plan, raw, review, authorizationReason, manifest) {
269
+ const source = parseRawStore(raw).rows;
270
+ const groups = new Map();
271
+ for (const row of source) {
272
+ const group = groups.get(row.id);
273
+ if (group) {
274
+ group.push(row);
275
+ }
276
+ else {
277
+ groups.set(row.id, [row]);
278
+ }
279
+ }
280
+ const removedRows = [];
281
+ const singletonOwnershipChanges = [];
282
+ const retainedUuids = [];
283
+ const aliasesAdded = [];
284
+ for (const [id, rows] of groups) {
285
+ if (rows.length === 1) {
286
+ const only = rows[0];
287
+ if (!only) {
288
+ throw privateFailure('singleton row is missing');
289
+ }
290
+ if (only.projectId !== manifest.projectId) {
291
+ singletonOwnershipChanges.push({
292
+ id,
293
+ beforeProjectId: only.projectId,
294
+ afterProjectId: manifest.projectId,
295
+ });
296
+ }
297
+ continue;
298
+ }
299
+ const canonical = rows.find((row) => row.projectId === manifest.canonicalSourceProjectAlias);
300
+ if (!canonical) {
301
+ throw privateFailure('canonical-source row is missing');
302
+ }
303
+ retainedUuids.push({ id, uuid: canonical.uuid });
304
+ const aliases = rows
305
+ .filter((row) => row !== canonical)
306
+ .flatMap((row) => [row.uuid, ...(row.uuidAliases ?? [])])
307
+ .filter((uuid) => typeof uuid === 'string' && uuid.length > 0)
308
+ .sort((left, right) => left.localeCompare(right));
309
+ aliasesAdded.push({ id, aliases });
310
+ removedRows.push(...rows.filter((row) => row !== canonical));
311
+ }
312
+ return {
313
+ schemaVersion: 1,
314
+ state: 'prepared',
315
+ projectId: manifest.projectId,
316
+ reviewer: review.reviewer,
317
+ verdict: review.verdict,
318
+ authorizationReason,
319
+ sourceDigest: plan.sourceDigest,
320
+ manifestDigest: plan.manifestDigest,
321
+ planDigest: plan.planDigest,
322
+ resultDigest: plan.resultDigest,
323
+ backupArtifactId: `repairs/spec-id-dedup/${plan.sourceDigest}.backup.json`,
324
+ backupDigest: digest(raw),
325
+ removedRows,
326
+ singletonOwnershipChanges,
327
+ retainedUuids,
328
+ aliasesAdded,
329
+ fieldChanges: plan.fieldChanges,
330
+ };
331
+ }
332
+ function validateApplyInput(plan, input, review) {
333
+ const authorizationReason = input.authorizationReason;
334
+ if (authorizationReason === undefined || authorizationReason.length < 100) {
335
+ throw privateFailure('authorization reason is too short');
336
+ }
337
+ if (input.expectedSourceDigest !== plan.sourceDigest ||
338
+ input.expectedManifestDigest !== plan.manifestDigest ||
339
+ input.expectedPlanDigest !== plan.planDigest ||
340
+ input.expectedResultDigest !== plan.resultDigest) {
341
+ throw privateFailure('reviewed digest does not match current plan');
342
+ }
343
+ if (review.reviewer !== 'planu-data-repair-reviewer' ||
344
+ review.verdict !== 'approved' ||
345
+ review.sourceDigest !== plan.sourceDigest ||
346
+ review.manifestDigest !== plan.manifestDigest ||
347
+ review.planDigest !== plan.planDigest ||
348
+ review.resultDigest !== plan.resultDigest) {
349
+ throw privateFailure('independent review evidence is invalid');
350
+ }
351
+ return authorizationReason;
352
+ }
353
+ function ioFor(io) {
354
+ return {
355
+ readFile: io?.readFile ?? ((path) => readFileFs(path)),
356
+ writeAtomic: io?.writeAtomic ?? ((path, bytes) => atomicWriteFile(path, bytes, { mode: 0o600 })),
357
+ mkdir: io?.mkdir ??
358
+ (async (path) => {
359
+ await mkdirFs(path, { recursive: true });
360
+ }),
361
+ withLock: io?.withLock ?? withCanonicalSpecRepairLock,
362
+ };
363
+ }
364
+ async function readJsonArtifact(path, label, io) {
365
+ try {
366
+ return JSON.parse((await io.readFile(path)).toString('utf8'));
367
+ }
368
+ catch {
369
+ throw privateFailure(`${label} is unreadable`);
370
+ }
371
+ }
372
+ /** Runs dry-run or the explicitly authorized, lock-protected repair. */
373
+ export async function repairDuplicateSpecIds(input) {
374
+ const io = ioFor(input.io);
375
+ const manifestBytes = await io.readFile(input.manifestPath).catch(() => {
376
+ throw privateFailure('manifest is unreadable');
377
+ });
378
+ let manifest;
379
+ try {
380
+ manifest = JSON.parse(manifestBytes.toString('utf8'));
381
+ }
382
+ catch {
383
+ throw privateFailure('manifest is not valid JSON');
384
+ }
385
+ // This binding intentionally precedes every canonical store read and write.
386
+ validateManifest(manifest, input.projectId);
387
+ const manifestDigest = digest(manifestBytes);
388
+ const paths = repairPaths(input.projectId);
389
+ if (input.dryRun === true) {
390
+ const raw = await io.readFile(paths.store);
391
+ const plan = buildCanonicalRepairPlan(raw, manifest, manifestDigest, input.projectId);
392
+ await io.mkdir(paths.dir);
393
+ await io.writeAtomic(artifactPath(input.projectId, plan.artifactId), canonicalJson(toPrivateReport(plan)));
394
+ return plan;
395
+ }
396
+ if (!input.reviewPath) {
397
+ throw privateFailure('apply requires review evidence');
398
+ }
399
+ const review = await readJsonArtifact(input.reviewPath, 'review evidence', io);
400
+ return io.withLock(input.projectId, async () => {
401
+ const raw = await io.readFile(paths.store);
402
+ const plan = buildCanonicalRepairPlan(raw, manifest, manifestDigest, input.projectId);
403
+ if (!plan.changed) {
404
+ return plan;
405
+ }
406
+ const authorizationReason = validateApplyInput(plan, input, review);
407
+ const receipt = buildReceipt(plan, raw, review, authorizationReason, manifest);
408
+ const backupPath = artifactPath(input.projectId, receipt.backupArtifactId);
409
+ const receiptId = `repairs/spec-id-dedup/${plan.sourceDigest}.receipt.json`;
410
+ const receiptPath = artifactPath(input.projectId, receiptId);
411
+ await io.mkdir(paths.dir);
412
+ await io.writeAtomic(backupPath, raw);
413
+ await io.writeAtomic(receiptPath, canonicalJson(receipt));
414
+ await io.writeAtomic(paths.store, plan.resultBytes);
415
+ try {
416
+ await io.writeAtomic(receiptPath, canonicalJson({ ...receipt, state: 'committed' }));
417
+ }
418
+ catch {
419
+ invalidateSpecsCache(input.projectId);
420
+ return { ...plan, finalizationDegraded: true };
421
+ }
422
+ invalidateSpecsCache(input.projectId);
423
+ return plan;
424
+ });
425
+ }
426
+ function isValidReceipt(value, projectId) {
427
+ if (!value || typeof value !== 'object') {
428
+ return false;
429
+ }
430
+ const receipt = value;
431
+ return (receipt.schemaVersion === 1 &&
432
+ receipt.projectId === projectId &&
433
+ (receipt.state === 'prepared' || receipt.state === 'committed') &&
434
+ typeof receipt.sourceDigest === 'string' &&
435
+ /^[a-f0-9]{64}$/.test(receipt.sourceDigest) &&
436
+ typeof receipt.manifestDigest === 'string' &&
437
+ /^[a-f0-9]{64}$/.test(receipt.manifestDigest) &&
438
+ typeof receipt.planDigest === 'string' &&
439
+ /^[a-f0-9]{64}$/.test(receipt.planDigest) &&
440
+ typeof receipt.resultDigest === 'string' &&
441
+ /^[a-f0-9]{64}$/.test(receipt.resultDigest) &&
442
+ typeof receipt.backupDigest === 'string' &&
443
+ /^[a-f0-9]{64}$/.test(receipt.backupDigest) &&
444
+ typeof receipt.backupArtifactId === 'string' &&
445
+ receipt.backupArtifactId.length > 0);
446
+ }
447
+ export async function rollbackCanonicalRepair(projectId, receiptPath) {
448
+ const paths = repairPaths(projectId);
449
+ if (!isContained(paths.dir, receiptPath)) {
450
+ throw privateFailure('receipt is outside the project repair directory');
451
+ }
452
+ const io = ioFor(undefined);
453
+ const receipt = await readJsonArtifact(receiptPath, 'receipt', io);
454
+ if (!isValidReceipt(receipt, projectId)) {
455
+ throw privateFailure('receipt schema, project, state, or digest is invalid');
456
+ }
457
+ const backupPath = artifactPath(projectId, receipt.backupArtifactId);
458
+ return withCanonicalSpecRepairLock(projectId, async () => {
459
+ const current = await io.readFile(paths.store);
460
+ if (digest(current) !== receipt.resultDigest) {
461
+ throw privateFailure('rollback source does not match committed result');
462
+ }
463
+ const backup = await io.readFile(backupPath).catch(() => {
464
+ throw privateFailure('backup is unreadable');
465
+ });
466
+ if (digest(backup) !== receipt.backupDigest) {
467
+ throw privateFailure('backup digest does not match receipt');
468
+ }
469
+ await io.writeAtomic(paths.store, backup);
470
+ invalidateSpecsCache(projectId);
471
+ });
472
+ }
473
+ export const CANONICAL_DEDUPLICATOR_INTERNALS = {
474
+ digest,
475
+ parseRawStore,
476
+ artifactPath,
477
+ isContained,
478
+ };
479
+ //# sourceMappingURL=canonical-deduplicator.js.map
@@ -1,14 +1,10 @@
1
- import type { Spec } from '../../types/index.js';
1
+ import type { FilesystemImportDeps, FilesystemImportResult } from '../../types/index.js';
2
2
  /**
3
3
  * Import specs that exist on disk (planu/specs/) but are missing from the store.
4
4
  * Reads frontmatter from each spec.md and creates a minimal Spec record in the store.
5
5
  * This ensures list_specs returns ALL specs, not just those created via create_spec.
6
6
  */
7
- export declare function importFilesystemSpecs(projectPath: string, deps: {
8
- listSpecs: (projectId: string) => Promise<Spec[]>;
9
- createSpec: (projectId: string, spec: Spec) => Promise<Spec>;
10
- }, projectId: string): Promise<{
11
- importedCount: number;
12
- errors: string[];
13
- }>;
7
+ export declare function importFilesystemSpecs(projectPath: string, deps: FilesystemImportDeps, projectId: string, options?: {
8
+ strictPortableContract?: boolean;
9
+ }): Promise<FilesystemImportResult>;
14
10
  //# sourceMappingURL=filesystem-import.d.ts.map
@@ -3,16 +3,74 @@ import { readdir, readFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { parseFrontmatterLocal, validSpecType, validSpecScope, validSpecStatus, validRiskLevel, validSpecTarget, validDifficulty, } from './frontmatter-parser.js';
5
5
  const SPEC_FOLDER_ID_RE = /^(SPEC-\d{3,})/;
6
+ function optionalString(value) {
7
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
8
+ }
9
+ function compatibleIdentity(existing, candidate) {
10
+ return (existing.id === candidate.id &&
11
+ existing.title === candidate.title &&
12
+ existing.slug === candidate.slug &&
13
+ (existing.uuid === undefined ||
14
+ candidate.uuid === undefined ||
15
+ existing.uuid === candidate.uuid));
16
+ }
17
+ function hasUuidIdentity(spec, uuid) {
18
+ return uuid !== undefined && (spec.uuid === uuid || spec.uuidAliases?.includes(uuid) === true);
19
+ }
20
+ function hasAnyUuidIdentity(existing, candidate) {
21
+ return [candidate.uuid, ...(candidate.uuidAliases ?? [])].some((uuid) => hasUuidIdentity(existing, uuid));
22
+ }
23
+ function isValidPortableMetadata(specId, metadata) {
24
+ return (metadata.id === specId &&
25
+ typeof metadata.title === 'string' &&
26
+ metadata.title.trim().length > 0 &&
27
+ validSpecType(metadata.type) !== null &&
28
+ validSpecScope(metadata.scope) !== null &&
29
+ validSpecStatus(metadata.status) !== null &&
30
+ validRiskLevel(metadata.risk) !== null &&
31
+ validSpecTarget(metadata.target) !== null &&
32
+ validDifficulty(metadata.difficulty) !== null);
33
+ }
34
+ function generationFromMetadata(metadata, fallbackGeneratedAt) {
35
+ const value = metadata.generation;
36
+ if (typeof value !== 'object' || value === null) {
37
+ return undefined;
38
+ }
39
+ const generationMetadata = value;
40
+ const method = generationMetadata.method;
41
+ if (method !== 'deterministic' && method !== 'internal-model' && method !== 'host') {
42
+ return undefined;
43
+ }
44
+ const host = optionalString(generationMetadata.host);
45
+ const modelId = optionalString(generationMetadata.modelId);
46
+ return {
47
+ method,
48
+ generatedAt: optionalString(generationMetadata.generatedAt) ?? fallbackGeneratedAt,
49
+ ...(host ? { host } : {}),
50
+ ...(modelId ? { modelId } : {}),
51
+ };
52
+ }
53
+ // eslint-disable-next-line max-lines-per-function, complexity -- portable metadata is normalized at one boundary
6
54
  function buildSpecFromMetadata(specId, entryName, projectPath, projectId, metadata) {
7
55
  const now = new Date().toISOString();
8
56
  const folder = join(projectPath, 'planu', 'specs', entryName);
9
57
  const specPath = join(folder, 'spec.md');
10
58
  const slug = entryName.replace(/^SPEC-\d{3,}-/, '');
11
59
  const stringList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
60
+ const generation = generationFromMetadata(metadata, now);
12
61
  return {
13
62
  id: specId,
63
+ ...(optionalString(metadata.uuid) ? { uuid: optionalString(metadata.uuid) } : {}),
64
+ ...(Array.isArray(metadata.uuidAliases)
65
+ ? {
66
+ uuidAliases: metadata.uuidAliases
67
+ .filter((value) => typeof value === 'string' && value.length > 0)
68
+ .sort((left, right) => left.localeCompare(right)),
69
+ }
70
+ : {}),
14
71
  title: typeof metadata.title === 'string' ? metadata.title : specId,
15
72
  slug,
73
+ ...(optionalString(metadata.feature) ? { feature: optionalString(metadata.feature) } : {}),
16
74
  type: validSpecType(metadata.type) ?? 'feature',
17
75
  scope: validSpecScope(metadata.scope) ?? 'feature',
18
76
  status: validSpecStatus(metadata.status) ?? 'draft',
@@ -46,6 +104,21 @@ function buildSpecFromMetadata(specId, entryName, projectPath, projectId, metada
46
104
  tags: stringList(metadata.tags),
47
105
  dependencies: stringList(metadata.dependencies),
48
106
  blockedBy: stringList(metadata.blockedBy),
107
+ outOfScope: stringList(metadata.outOfScope),
108
+ ...(metadata.priority === 'P0'
109
+ ? { priority: 'high' }
110
+ : metadata.priority === 'high' ||
111
+ metadata.priority === 'medium' ||
112
+ metadata.priority === 'low'
113
+ ? { priority: metadata.priority }
114
+ : {}),
115
+ ...(metadata.spec_format_version === '0.x' || metadata.spec_format_version === '1.0'
116
+ ? { spec_format_version: metadata.spec_format_version }
117
+ : {}),
118
+ ...(optionalString(metadata.spec_version)
119
+ ? { spec_version: optionalString(metadata.spec_version) }
120
+ : {}),
121
+ ...(generation ? { generation } : {}),
49
122
  gitBranch: typeof metadata.branch === 'string'
50
123
  ? metadata.branch
51
124
  : `feat/${specId.toLowerCase()}-${slug}`,
@@ -57,18 +130,18 @@ function buildSpecFromMetadata(specId, entryName, projectPath, projectId, metada
57
130
  * Reads frontmatter from each spec.md and creates a minimal Spec record in the store.
58
131
  * This ensures list_specs returns ALL specs, not just those created via create_spec.
59
132
  */
60
- export async function importFilesystemSpecs(projectPath, deps, projectId) {
133
+ export async function importFilesystemSpecs(projectPath, deps, projectId, options = {}) {
61
134
  const specsRoot = join(projectPath, 'planu/specs');
62
- const errors = [];
63
- let importedCount = 0;
135
+ const failures = [];
136
+ const importedSpecIds = [];
64
137
  const storeSpecs = await deps.listSpecs(projectId);
65
- const storeIds = new Set(storeSpecs.map((s) => s.id));
138
+ const specsById = new Map(storeSpecs.map((spec) => [spec.id, spec]));
66
139
  let entries;
67
140
  try {
68
141
  entries = await readdir(specsRoot, { withFileTypes: true });
69
142
  }
70
143
  catch {
71
- return { importedCount: 0, errors: [] };
144
+ return { importedCount: 0, importedSpecIds, failures, errors: [] };
72
145
  }
73
146
  for (const entry of entries) {
74
147
  if (!entry.isDirectory()) {
@@ -79,22 +152,56 @@ export async function importFilesystemSpecs(projectPath, deps, projectId) {
79
152
  continue;
80
153
  }
81
154
  const specId = idMatch[1];
82
- if (storeIds.has(specId)) {
83
- continue;
84
- }
85
155
  const specMdPath = join(specsRoot, entry.name, 'spec.md');
156
+ const relativeSpecPath = `planu/specs/${entry.name}/spec.md`;
157
+ let spec;
86
158
  try {
87
159
  const content = await readFile(specMdPath, 'utf-8');
88
160
  const { metadata } = parseFrontmatterLocal(content);
89
- const spec = buildSpecFromMetadata(specId, entry.name, projectPath, projectId, metadata);
161
+ if (options.strictPortableContract === true && !isValidPortableMetadata(specId, metadata)) {
162
+ failures.push({ specId, path: relativeSpecPath, code: 'INVALID_CONTRACT' });
163
+ continue;
164
+ }
165
+ spec = buildSpecFromMetadata(specId, entry.name, projectPath, projectId, metadata);
166
+ }
167
+ catch {
168
+ failures.push({ specId, path: relativeSpecPath, code: 'READ_FAILED' });
169
+ continue;
170
+ }
171
+ const existing = specsById.get(specId);
172
+ if (existing !== undefined) {
173
+ if (!compatibleIdentity(existing, spec)) {
174
+ failures.push({ specId, path: relativeSpecPath, code: 'INCOMPATIBLE_IDENTITY' });
175
+ }
176
+ continue;
177
+ }
178
+ if (storeSpecs.some((stored) => hasAnyUuidIdentity(stored, spec))) {
179
+ failures.push({ specId, path: relativeSpecPath, code: 'INCOMPATIBLE_IDENTITY' });
180
+ continue;
181
+ }
182
+ try {
90
183
  await deps.createSpec(projectId, spec);
91
- storeIds.add(specId);
92
- importedCount++;
184
+ specsById.set(specId, spec);
185
+ importedSpecIds.push(specId);
93
186
  }
94
- catch (err) {
95
- errors.push(`${specId}: ${err instanceof Error ? err.message : String(err)}`);
187
+ catch {
188
+ const winner = await deps.getSpecFresh?.(projectId, specId).catch(() => null);
189
+ if (winner !== undefined && winner !== null && compatibleIdentity(winner, spec)) {
190
+ specsById.set(specId, winner);
191
+ continue;
192
+ }
193
+ failures.push({
194
+ specId,
195
+ path: relativeSpecPath,
196
+ code: winner ? 'INCOMPATIBLE_IDENTITY' : 'CREATE_FAILED',
197
+ });
96
198
  }
97
199
  }
98
- return { importedCount, errors };
200
+ return {
201
+ importedCount: importedSpecIds.length,
202
+ importedSpecIds,
203
+ failures,
204
+ errors: failures.map((failure) => `${failure.specId}: ${failure.code}`),
205
+ };
99
206
  }
100
207
  //# sourceMappingURL=filesystem-import.js.map