@planu/cli 5.3.67 → 5.3.69

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 (389) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/.planu-build.json +1 -1
  3. package/dist/cli/commands/install.d.ts +2 -15
  4. package/dist/config/release-policy.json +2 -2
  5. package/dist/core/shared/fs.d.ts +11 -0
  6. package/dist/core/shared/fs.js +90 -0
  7. package/dist/core/shared/strings.d.ts +3 -0
  8. package/dist/core/shared/strings.js +13 -0
  9. package/dist/engine/actuals/git-analyzer.js +1 -1
  10. package/dist/engine/actuals/progress-parser.js +1 -3
  11. package/dist/engine/agent-handoff-store.js +1 -0
  12. package/dist/engine/agent-squad/dispatcher.js +1 -0
  13. package/dist/engine/agent-telemetry.js +1 -0
  14. package/dist/engine/auth/config.js +1 -0
  15. package/dist/engine/auto-updater/atomic-upgrade.js +1 -0
  16. package/dist/engine/auto-updater/index.js +1 -0
  17. package/dist/engine/autopilot/action-registry.js +20 -0
  18. package/dist/engine/autopilot/audit-logger.js +1 -0
  19. package/dist/engine/autopilot/bootstrap.js +9 -2
  20. package/dist/engine/autopilot/complete-loop.js +4 -0
  21. package/dist/engine/autopilot/crash-shield-rate-limiter.js +1 -0
  22. package/dist/engine/autopilot/event-bus.js +1 -0
  23. package/dist/engine/autopilot/state-updater.js +6 -0
  24. package/dist/engine/browser-validator.js +1 -3
  25. package/dist/engine/build-tool-detector.js +1 -8
  26. package/dist/engine/bundle-installer.js +2 -10
  27. package/dist/engine/cascade-hooks/core/append-releases.js +1 -0
  28. package/dist/engine/cascade-hooks/core/write-session-json.js +2 -2
  29. package/dist/engine/cascade-hooks/hooks/dispatch-hook-event.hook.d.ts +3 -1
  30. package/dist/engine/cascade-hooks/hooks/dispatch-hook-event.hook.js +12 -3
  31. package/dist/engine/cascade-hooks/hooks/estimate-calibration.hook.js +1 -0
  32. package/dist/engine/cascade-hooks/hooks/housekeeping-on-done.hook.js +2 -0
  33. package/dist/engine/cascade-hooks/hooks/implementing-kickoff.hook.js +5 -1
  34. package/dist/engine/cascade-hooks/hooks/lesson-drift.hook.js +1 -0
  35. package/dist/engine/cascade-hooks/hooks/orchestration-plan.hook.js +1 -0
  36. package/dist/engine/cascade-hooks/hooks/session-context.hook.js +4 -2
  37. package/dist/engine/cascade-hooks/hooks/slack-notify.hook.js +1 -0
  38. package/dist/engine/cascade-hooks/hooks/status-json.hook.js +2 -0
  39. package/dist/engine/cascade-hooks/hooks/velocity-profile.hook.js +2 -0
  40. package/dist/engine/cascade-hooks/runner.js +2 -0
  41. package/dist/engine/cascade-hooks/state-drift-detector.js +2 -0
  42. package/dist/engine/checkpoint/checkpoint-manager.js +2 -0
  43. package/dist/engine/checkpoint/policy-engine.js +1 -0
  44. package/dist/engine/ci-generator/stack-detector.js +2 -10
  45. package/dist/engine/clarification-gate/token-store.js +1 -0
  46. package/dist/engine/claude-config-auditor/claudemd-auditor.js +2 -10
  47. package/dist/engine/claude-config-auditor/settings-auditor.js +2 -10
  48. package/dist/engine/client-detection.js +2 -10
  49. package/dist/engine/compliance/auto-remediator.js +1 -0
  50. package/dist/engine/compliance/evidence-builder.d.ts +2 -2
  51. package/dist/engine/compliance/framework-catalog.d.ts +6 -6
  52. package/dist/engine/compliance/gap-analyzer.d.ts +2 -2
  53. package/dist/engine/config-health/build-tool-checker.js +1 -9
  54. package/dist/engine/config-health/ci-commands-collector.d.ts +2 -1
  55. package/dist/engine/config-health/ci-commands-collector.js +2 -9
  56. package/dist/engine/config-health/go-checker.js +1 -9
  57. package/dist/engine/config-health/mcp-config-checker.js +2 -10
  58. package/dist/engine/config-health/python-checker.js +1 -9
  59. package/dist/engine/config-health/rust-checker.js +1 -9
  60. package/dist/engine/config-health/ts-checker-scripts.js +1 -9
  61. package/dist/engine/config-health/ts-checker.js +1 -9
  62. package/dist/engine/context-artifacts/store.js +1 -0
  63. package/dist/engine/context-md-generator.js +1 -0
  64. package/dist/engine/context-orchestrator/index.js +2 -0
  65. package/dist/engine/convention-scanner/convention-parser.js +2 -9
  66. package/dist/engine/cost-guardrails.js +1 -0
  67. package/dist/engine/cost-tracking/ledger.js +1 -0
  68. package/dist/engine/criterion-identity.js +1 -1
  69. package/dist/engine/dashboard/data-loader.js +1 -0
  70. package/dist/engine/dashboard/drift-score.js +2 -0
  71. package/dist/engine/dashboard/spec-updater.d.ts +3 -1
  72. package/dist/engine/dashboard/spec-updater.js +9 -2
  73. package/dist/engine/dashboard/templates-layout.d.ts +2 -2
  74. package/dist/engine/dashboard/templates-layout.js +2 -9
  75. package/dist/engine/data-projects-gc/gc-runner.js +2 -4
  76. package/dist/engine/deleter/audit-logger.js +1 -0
  77. package/dist/engine/deleter/core.js +6 -0
  78. package/dist/engine/deleter/trash-manager.js +1 -0
  79. package/dist/engine/desktop-detector.js +1 -9
  80. package/dist/engine/detection-utils.d.ts +2 -1
  81. package/dist/engine/detection-utils.js +3 -10
  82. package/dist/engine/detectors/advanced-framework-detector/helpers.d.ts +3 -2
  83. package/dist/engine/detectors/advanced-framework-detector/helpers.js +3 -17
  84. package/dist/engine/detectors/cicd-detector.js +2 -10
  85. package/dist/engine/detectors/css-framework-detector.js +2 -10
  86. package/dist/engine/detectors/deployment-detector.js +3 -12
  87. package/dist/engine/detectors/frontend-perf-detector.js +2 -10
  88. package/dist/engine/detectors/gitops-detector.js +3 -11
  89. package/dist/engine/detectors/idp-detector.js +1 -3
  90. package/dist/engine/detectors/k3s-detector.js +3 -11
  91. package/dist/engine/detectors/llm-detector/file-readers.d.ts +2 -1
  92. package/dist/engine/detectors/llm-detector/file-readers.js +2 -9
  93. package/dist/engine/detectors/mfe-detector.js +5 -13
  94. package/dist/engine/detectors/monitoring-detector.js +2 -11
  95. package/dist/engine/detectors/monorepo-detector.d.ts +1 -0
  96. package/dist/engine/detectors/monorepo-detector.js +1 -10
  97. package/dist/engine/detectors/nomad-detector.js +3 -11
  98. package/dist/engine/detectors/otel-ecosystem-detectors.d.ts +2 -1
  99. package/dist/engine/detectors/otel-ecosystem-detectors.js +2 -10
  100. package/dist/engine/doc-generator/executive-summary/html-template.js +1 -7
  101. package/dist/engine/doc-generator/executive-summary/section-builders.js +1 -7
  102. package/dist/engine/doc-generator/per-spec-report/mermaid-renderer.js +1 -7
  103. package/dist/engine/doc-generator/per-spec-report/spec-section-builders.js +1 -7
  104. package/dist/engine/doc-generator/per-spec-report/technical-report.js +1 -7
  105. package/dist/engine/doc-generator/portal/analytics-page.js +1 -7
  106. package/dist/engine/doc-generator/portal/architecture-page.js +1 -7
  107. package/dist/engine/doc-generator/portal/changelog-page.js +1 -7
  108. package/dist/engine/doc-generator/portal/decision-page.js +1 -7
  109. package/dist/engine/doc-generator/portal/decision-timeline-builder.js +1 -7
  110. package/dist/engine/doc-generator/portal/kanban-view-builder.js +1 -7
  111. package/dist/engine/doc-generator/portal/portal-breadcrumbs.js +1 -7
  112. package/dist/engine/doc-generator/portal/portal-landing-cards.js +1 -7
  113. package/dist/engine/doc-generator/portal/portal-navbar.js +1 -7
  114. package/dist/engine/doc-generator/portal/portal-page-detector.d.ts +3 -0
  115. package/dist/engine/doc-generator/portal/portal-page-detector.js +1 -10
  116. package/dist/engine/doc-generator/portal/portal-regenerator.js +2 -0
  117. package/dist/engine/doc-generator/portal/risk-matrix-svg.js +1 -7
  118. package/dist/engine/doc-generator/portal/risk-page.js +1 -7
  119. package/dist/engine/doc-generator/portal/roadmap-page.js +1 -7
  120. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js +1 -7
  121. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js +1 -7
  122. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js +2 -8
  123. package/dist/engine/doc-generator/proposal/proposal-section-builders.js +1 -7
  124. package/dist/engine/docs-site-generator/html-templates.js +1 -7
  125. package/dist/engine/docs-site-generator/markdown-renderer.js +1 -7
  126. package/dist/engine/drift/violation-resolver.js +2 -0
  127. package/dist/engine/dynamic-knowledge/knowledge-cache.js +1 -0
  128. package/dist/engine/ecosystem-absorber/knowledge-cache.js +1 -0
  129. package/dist/engine/edge-db-detector.js +1 -10
  130. package/dist/engine/escalator/retry-counter.js +1 -0
  131. package/dist/engine/evidence-gates/artifact-reader.js +1 -0
  132. package/dist/engine/evidence-gates/evidence-autofill.js +1 -0
  133. package/dist/engine/evidence-gates/evidence-skeletons.js +1 -0
  134. package/dist/engine/evidence-gates/reconciliation-freshness.js +1 -0
  135. package/dist/engine/execution/durable-job-heartbeat-runtime.js +1 -0
  136. package/dist/engine/execution/job-runtime.js +2 -0
  137. package/dist/engine/execution/validate-job-executor.d.ts +1 -1
  138. package/dist/engine/execution/validate-job-executor.js +3 -0
  139. package/dist/engine/execution/validate-job-payload.js +1 -0
  140. package/dist/engine/execution/validate-worker-thread.js +1 -0
  141. package/dist/engine/facilitate/context-loader.js +1 -0
  142. package/dist/engine/figma/change-detector.d.ts +1 -8
  143. package/dist/engine/figma/code-connect-fetcher.d.ts +1 -7
  144. package/dist/engine/figma/design-enricher.d.ts +1 -17
  145. package/dist/engine/figma/design-enricher.js +0 -1
  146. package/dist/engine/figma/e2e-generator.d.ts +1 -23
  147. package/dist/engine/figma/flow-analyzer.d.ts +1 -11
  148. package/dist/engine/figma/frame-extractor.d.ts +1 -11
  149. package/dist/engine/figma/frame-extractor.js +3 -0
  150. package/dist/engine/figma/multi-file-manager.d.ts +1 -6
  151. package/dist/engine/figma/spec-generator.js +2 -0
  152. package/dist/engine/figma/thumbnail-fetcher.d.ts +1 -1
  153. package/dist/engine/figma/token-extractor.d.ts +1 -6
  154. package/dist/engine/figma/token-extractor.js +0 -1
  155. package/dist/engine/figma/version-tracker.d.ts +1 -7
  156. package/dist/engine/figma/version-tracker.js +0 -1
  157. package/dist/engine/figma/visual-qa.d.ts +5 -19
  158. package/dist/engine/figma/visual-qa.js +1 -2
  159. package/dist/engine/figma/webhook-manager.d.ts +1 -7
  160. package/dist/engine/frontmatter-sha/reseal.js +1 -0
  161. package/dist/engine/frontmatter-sha/verify-terminal.js +1 -0
  162. package/dist/engine/git-hook-injector.js +2 -10
  163. package/dist/engine/github/quality-dimensions.js +1 -3
  164. package/dist/engine/global-rules-manager.js +2 -3
  165. package/dist/engine/handoff-artifacts/implementation-review-reader.js +1 -0
  166. package/dist/engine/handoff-artifacts/io.js +2 -0
  167. package/dist/engine/handoff-packager.js +2 -1
  168. package/dist/engine/health/auto-fixer.js +1 -0
  169. package/dist/engine/hooks/context-injector.js +1 -0
  170. package/dist/engine/hooks-reconciler.js +1 -8
  171. package/dist/engine/impact-detector/index.js +2 -9
  172. package/dist/engine/implementation-contract/common.js +1 -3
  173. package/dist/engine/implementation-contract/evaluator.js +1 -3
  174. package/dist/engine/keyword-matcher/boundary.d.ts +2 -7
  175. package/dist/engine/keyword-matcher/boundary.js +2 -14
  176. package/dist/engine/lifecycle-reconciliation-io.js +1 -0
  177. package/dist/engine/lifecycle-reconciliation.js +3 -0
  178. package/dist/engine/local-first/detector.js +1 -9
  179. package/dist/engine/marketplace-fetcher/cache.js +2 -0
  180. package/dist/engine/marketplace-fetcher/index.js +1 -0
  181. package/dist/engine/mcp-config/mcp-config-writer.js +2 -10
  182. package/dist/engine/migrations/remove-commercial-state.js +2 -0
  183. package/dist/engine/mobile-detector-shared.d.ts +1 -1
  184. package/dist/engine/mobile-detector-shared.js +2 -9
  185. package/dist/engine/multi-channel-notifier.js +1 -0
  186. package/dist/engine/multi-teammate-review/disagreement-counter.js +1 -0
  187. package/dist/engine/multi-teammate-review/panel-orchestrator.js +1 -0
  188. package/dist/engine/onboarding-engine.js +1 -9
  189. package/dist/engine/permissions-merger/stack-detectors.js +2 -10
  190. package/dist/engine/pii-detector/core.js +1 -3
  191. package/dist/engine/platform-engineering/dx-analyzer.js +1 -10
  192. package/dist/engine/platform-engineering/idp-detector.js +2 -10
  193. package/dist/engine/project-dna/ai-tool-detector.js +2 -10
  194. package/dist/engine/project-dna/stack-detector.js +2 -10
  195. package/dist/engine/project-graph/builder.js +11 -2
  196. package/dist/engine/project-graph/cache.d.ts +5 -2
  197. package/dist/engine/project-graph/cache.js +174 -34
  198. package/dist/engine/project-graph/extractors/decision-store-extractor.js +1 -0
  199. package/dist/engine/project-graph/freshness-projection.js +3 -0
  200. package/dist/engine/project-graph/query.js +14 -11
  201. package/dist/engine/project-health-checker.js +2 -6
  202. package/dist/engine/provider-adapters/shared/spec-loader.js +1 -0
  203. package/dist/engine/quality-gates/gate-evaluator.js +1 -3
  204. package/dist/engine/react-detector.js +6 -14
  205. package/dist/engine/reconcile/apply-changes.js +1 -3
  206. package/dist/engine/registry/core.js +1 -0
  207. package/dist/engine/registry/loader.js +2 -0
  208. package/dist/engine/release/postmortem-generator.js +1 -0
  209. package/dist/engine/release/rollback-runner.d.ts +2 -2
  210. package/dist/engine/release/stuck-spec-detector.js +7 -0
  211. package/dist/engine/release-pipeline/release-spec-closer.js +3 -0
  212. package/dist/engine/rules-reconciler.d.ts +1 -1
  213. package/dist/engine/rules-reconciler.js +2 -9
  214. package/dist/engine/safety/atomic-writer.js +1 -0
  215. package/dist/engine/safety/cross-process-lock.js +2 -0
  216. package/dist/engine/safety/file-mutex.js +1 -0
  217. package/dist/engine/safety/lock-lease-store.js +1 -0
  218. package/dist/engine/safety/lock-scavenger.js +2 -0
  219. package/dist/engine/saga/transition-cascade.js +3 -0
  220. package/dist/engine/scan-project/index.js +1 -0
  221. package/dist/engine/scan-project/module-discoverer.js +1 -9
  222. package/dist/engine/schema-parity-auditor/handler-checker.js +1 -3
  223. package/dist/engine/schema-parity-auditor/index.js +1 -3
  224. package/dist/engine/schema-parity-auditor/type-checker.js +1 -3
  225. package/dist/engine/self-healing/strategies/coverage-fix.js +2 -10
  226. package/dist/engine/session-context-generator.d.ts +8 -0
  227. package/dist/engine/session-context-generator.js +33 -23
  228. package/dist/engine/session-state/session-artifact-refresher.d.ts +7 -0
  229. package/dist/engine/session-state/session-artifact-refresher.js +140 -0
  230. package/dist/engine/session-state/writer.d.ts +3 -0
  231. package/dist/engine/session-state/writer.js +35 -17
  232. package/dist/engine/shared-agent-memory.js +1 -0
  233. package/dist/engine/skill-adapter/index.js +3 -0
  234. package/dist/engine/skill-bootstrap/registry-fetcher.js +1 -0
  235. package/dist/engine/skill-bootstrap/skill-writer.js +2 -9
  236. package/dist/engine/skill-bootstrap/stack-matcher.js +1 -9
  237. package/dist/engine/skill-discovery/multi-agent-writer.d.ts +1 -0
  238. package/dist/engine/skill-discovery/multi-agent-writer.js +1 -9
  239. package/dist/engine/skill-generator/workflow-skill-generator.js +1 -0
  240. package/dist/engine/skill-registry/ttl-refresh.js +2 -0
  241. package/dist/engine/spec-audit/index.js +2 -0
  242. package/dist/engine/spec-audit/report-writer.js +1 -0
  243. package/dist/engine/spec-coverage/core.js +1 -0
  244. package/dist/engine/spec-decomposer/analyzer.js +1 -11
  245. package/dist/engine/spec-dependency-graph/dot-exporter.d.ts +1 -1
  246. package/dist/engine/spec-dependency-graph/graph-builder.d.ts +1 -20
  247. package/dist/engine/spec-dependency-graph/superseded-set.d.ts +1 -1
  248. package/dist/engine/spec-diff-engine.js +1 -7
  249. package/dist/engine/spec-format/read-technical-section.js +1 -3
  250. package/dist/engine/spec-format/replace-section.js +1 -3
  251. package/dist/engine/spec-format/spec-id.d.ts +9 -1
  252. package/dist/engine/spec-format/spec-id.js +24 -3
  253. package/dist/engine/spec-format/unified-spec-builder.js +1 -3
  254. package/dist/engine/spec-migrator/canonical-deduplicator.js +2 -0
  255. package/dist/engine/spec-observability/tracker.js +1 -0
  256. package/dist/engine/spec-registry/adapter.js +2 -10
  257. package/dist/engine/spec-registry/validator.d.ts +1 -0
  258. package/dist/engine/spec-registry/validator.js +1 -9
  259. package/dist/engine/spec-state-machine/transition-spec.js +1 -0
  260. package/dist/engine/spec-templates/custom-loader.js +1 -0
  261. package/dist/engine/stack-auditor/build-tool-auditor.js +1 -9
  262. package/dist/engine/staleness/stale-implementing.js +5 -14
  263. package/dist/engine/status-reconciler/index.js +4 -1
  264. package/dist/engine/structural-memory/helpers.js +1 -0
  265. package/dist/engine/supply-chain/slsa-checker.js +1 -9
  266. package/dist/engine/tdd-enforcer/stub-artifact-path.js +1 -0
  267. package/dist/engine/telemetry/health-checker.js +1 -0
  268. package/dist/engine/telemetry/telemetry-store.js +1 -0
  269. package/dist/engine/token-optimizer/response-cache.js +1 -3
  270. package/dist/engine/tool-groups/group-manager.js +2 -0
  271. package/dist/engine/update-notifier.js +1 -0
  272. package/dist/engine/validation/approved-spec-portability.js +1 -0
  273. package/dist/engine/validation/durable-validation.js +1 -0
  274. package/dist/engine/validation/validation-freshness.js +2 -0
  275. package/dist/engine/validation/validation-receipt.js +1 -0
  276. package/dist/engine/validation/validation-submission.d.ts +1 -1
  277. package/dist/engine/validation-evidence-ledger.js +1 -0
  278. package/dist/engine/validator/deep-code-checker.js +1 -3
  279. package/dist/engine/validator/executable-evidence.d.ts +1 -14
  280. package/dist/engine/validator/scope-resolver.d.ts +2 -2
  281. package/dist/engine/validator/spec-compliance-runner.d.ts +1 -31
  282. package/dist/engine/validator/validation-report-writer.js +1 -1
  283. package/dist/engine/validator.d.ts +1 -1
  284. package/dist/engine/validator.js +1 -0
  285. package/dist/engine/velocity/measure-velocity.js +2 -0
  286. package/dist/engine/version-resolver.js +1 -0
  287. package/dist/engine/workers/handlers/auto-drift.js +3 -0
  288. package/dist/engine/workspace-manager.js +4 -0
  289. package/dist/hosts/gemini/config-scaffold.js +2 -10
  290. package/dist/storage/approval-operation-lock.js +1 -0
  291. package/dist/storage/auto-promoter-config-store.js +1 -0
  292. package/dist/storage/autopilot-log-store.js +1 -0
  293. package/dist/storage/base-store.js +1 -0
  294. package/dist/storage/checkpoint-store.js +1 -0
  295. package/dist/storage/feedback-remote.js +1 -0
  296. package/dist/storage/gaps-log.js +1 -0
  297. package/dist/storage/global-projects-store.d.ts +17 -0
  298. package/dist/storage/global-projects-store.js +47 -0
  299. package/dist/storage/global-store.js +1 -0
  300. package/dist/storage/knowledge-store/sessions.js +2 -0
  301. package/dist/storage/migrations/canonical-storage.js +42 -18
  302. package/dist/storage/project-drift-store.js +1 -0
  303. package/dist/storage/project-identity.d.ts +27 -0
  304. package/dist/storage/project-identity.js +132 -2
  305. package/dist/storage/project-resolver.js +1 -17
  306. package/dist/storage/runtime-db.d.ts +2 -20
  307. package/dist/storage/runtime-db.js +2 -9
  308. package/dist/storage/semantic-index-store.js +1 -0
  309. package/dist/storage/sentry-store.js +1 -0
  310. package/dist/storage/session-state-store.js +1 -0
  311. package/dist/storage/spec-store.d.ts +31 -3
  312. package/dist/storage/spec-store.js +146 -5
  313. package/dist/storage/status-store/atomic-status-write.js +1 -0
  314. package/dist/storage/status-store/file-lock.js +1 -0
  315. package/dist/storage/status-store/self-healing.js +1 -0
  316. package/dist/storage/storage-bundle.d.ts +12 -2
  317. package/dist/storage/supabase-store.js +1 -0
  318. package/dist/storage/technology-selection-store.js +1 -0
  319. package/dist/storage/trash-store.d.ts +2 -4
  320. package/dist/storage/trash-store.js +2 -15
  321. package/dist/storage/vector-store/json-fallback.js +1 -0
  322. package/dist/storage/vector-store/migrator.js +1 -0
  323. package/dist/storage/vector-store/sqlite-adapter.js +1 -0
  324. package/dist/tools/audit-claude-config.js +1 -10
  325. package/dist/tools/challenge-spec-helpers.d.ts +1 -1
  326. package/dist/tools/challenge-spec-helpers.js +7 -2
  327. package/dist/tools/challenge-spec.js +11 -6
  328. package/dist/tools/check-readiness.js +121 -24
  329. package/dist/tools/clarify-requirements/multiple-choice.d.ts +3 -3
  330. package/dist/tools/configure-oauth-handler.js +1 -0
  331. package/dist/tools/create-spec-helpers.d.ts +1 -3
  332. package/dist/tools/create-spec-helpers.js +1 -8
  333. package/dist/tools/create-spec.js +1 -13
  334. package/dist/tools/dashboard.js +5 -0
  335. package/dist/tools/generate-execution-plan.js +10 -6
  336. package/dist/tools/github-pr-handler.js +1 -0
  337. package/dist/tools/github-release-handler.d.ts +1 -0
  338. package/dist/tools/github-release-handler.js +4 -3
  339. package/dist/tools/graph-specs.d.ts +1 -1
  340. package/dist/tools/heal-spec-docs.js +1 -3
  341. package/dist/tools/init-project/git-setup.d.ts +2 -2
  342. package/dist/tools/init-project/host-assets-writer.js +2 -10
  343. package/dist/tools/init-project/lifecycle-helpers.d.ts +2 -2
  344. package/dist/tools/init-project/lint-scaffolder.js +2 -10
  345. package/dist/tools/init-project/stack-detector.d.ts +2 -2
  346. package/dist/tools/reconcile-interactive-question-hooks.d.ts +2 -2
  347. package/dist/tools/register-sdd-tools.js +1 -0
  348. package/dist/tools/safe-handler.js +1 -0
  349. package/dist/tools/schemas/spec-id.d.ts +1 -6
  350. package/dist/tools/schemas/spec-id.js +1 -16
  351. package/dist/tools/tool-entry.d.ts +2 -2
  352. package/dist/tools/tool-entry.js +1 -1
  353. package/dist/tools/tool-registry/core-tools.d.ts +2 -2
  354. package/dist/tools/tool-registry/deprecated-stubs.d.ts +3 -3
  355. package/dist/tools/tool-registry/deprecated-stubs.js +1 -1
  356. package/dist/tools/triage-request.d.ts +2 -2
  357. package/dist/tools/update-status/approval-gates.d.ts +2 -2
  358. package/dist/tools/update-status/approval-gates.js +1 -0
  359. package/dist/tools/update-status/dod-gates.d.ts +1 -1
  360. package/dist/tools/update-status/done-receipt-verifier.d.ts +1 -1
  361. package/dist/tools/update-status/evidence-gate.js +5 -5
  362. package/dist/tools/update-status/file-sync.js +11 -39
  363. package/dist/tools/update-status/index.js +1 -1
  364. package/dist/tools/update-status/side-effects.js +5 -0
  365. package/dist/tools/update-status/transition-guard.d.ts +1 -1
  366. package/dist/tools/update-status/transition-guard.js +40 -8
  367. package/dist/tools/update-status-actions.js +28 -40
  368. package/dist/tools/validate-graph-coverage.js +14 -2
  369. package/dist/tools/validate.js +2 -0
  370. package/dist/transports/middleware/types.d.ts +3 -3
  371. package/dist/types/atomic-upgrade.d.ts +1 -1
  372. package/dist/types/cascade-hooks.d.ts +3 -0
  373. package/dist/types/cross-repo-search.d.ts +2 -0
  374. package/dist/types/dashboard.d.ts +4 -1
  375. package/dist/types/durable-job.d.ts +20 -0
  376. package/dist/types/durable-job.js +9 -1
  377. package/dist/types/index.d.ts +2 -0
  378. package/dist/types/index.js +2 -0
  379. package/dist/types/installation.d.ts +16 -0
  380. package/dist/types/installation.js +2 -0
  381. package/dist/types/project-knowledge-graph.d.ts +11 -0
  382. package/dist/types/session-checkpoint.d.ts +16 -0
  383. package/dist/types/spec-dependency-graph.d.ts +21 -0
  384. package/dist/types/spec-dependency-graph.js +2 -0
  385. package/dist/types/validation-evidence.d.ts +45 -0
  386. package/package.json +2 -2
  387. package/planu-plugin.json +1 -1
  388. package/dist/types/storage-bundle.d.ts +0 -13
  389. package/dist/types/storage-bundle.js +0 -2
@@ -6,6 +6,7 @@ import { readFile, writeFile } from 'node:fs/promises';
6
6
  import { tmpdir } from 'node:os';
7
7
  import { join } from 'node:path';
8
8
  import { fetchLatestVersion } from './package-registry.js';
9
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
9
10
  import { getCachedVersions, saveCachedVersions, isExpired as isCachedVersionExpired, } from '../storage/version-cache-store.js';
10
11
  // -- Constants ---------------------------------------------------------------
11
12
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
@@ -1,8 +1,11 @@
1
1
  // engine/workers/handlers/auto-drift.ts — Auto drift detection on file changes (SPEC-225)
2
2
  import { relative, extname } from 'node:path';
3
3
  import { readdir, stat } from 'node:fs/promises';
4
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
4
5
  import { specStore } from '../../../storage/index.js';
6
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
5
7
  import { hashProjectPath } from '../../../storage/base-store.js';
8
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
6
9
  import { DriftCacheStore } from '../../../storage/drift-cache-store.js';
7
10
  import { emitAutopilotEvent } from '../../autopilot/event-bus.js';
8
11
  // ---------------------------------------------------------------------------
@@ -1,8 +1,12 @@
1
1
  // engine/workspace-manager.ts — SPEC-363: Multi-tenant Workspace Dashboard
2
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
2
3
  import { getProjects, addProject } from '../storage/global-projects-store.js';
4
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
3
5
  import { specStore } from '../storage/index.js';
6
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
4
7
  import { hashProjectPath, readJson } from '../storage/base-store.js';
5
8
  import { glob } from 'glob';
9
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
6
10
  import { resolveStorageLayout } from '../storage/storage-layout.js';
7
11
  // ---------------------------------------------------------------------------
8
12
  // SPEC-789: Project discovery fallback
@@ -2,11 +2,12 @@
2
2
  // Writes .gemini/conventions.md, .gemini/skills/planu-*.md, and .gemini/hooks/
3
3
  // equivalents when init_project runs inside a Gemini workspace.
4
4
  // NOTE: This function deliberately does NOT create .claude/ files.
5
- import { writeFile, readFile, mkdir, access } from 'node:fs/promises';
5
+ import { writeFile, readFile, mkdir } from 'node:fs/promises';
6
6
  import { join } from 'node:path';
7
7
  import { buildRulesForHost } from '../../engine/host-rules-templates/index.js';
8
8
  import { assertEnglishOnlyArtifactText } from '../../engine/spec-language/english-only.js';
9
9
  import { PLANU_VERSION } from '../../config/version.js';
10
+ import { fileExists } from '../../core/shared/fs.js';
10
11
  // ---------------------------------------------------------------------------
11
12
  // File content generators
12
13
  // ---------------------------------------------------------------------------
@@ -188,13 +189,4 @@ async function injectRulesBlock(filePath, version) {
188
189
  // ---------------------------------------------------------------------------
189
190
  // Internal helpers
190
191
  // ---------------------------------------------------------------------------
191
- async function fileExists(filePath) {
192
- try {
193
- await access(filePath);
194
- return true;
195
- }
196
- catch {
197
- return false;
198
- }
199
- }
200
192
  //# sourceMappingURL=config-scaffold.js.map
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import { isAbsolute, resolve } from 'node:path';
4
4
  import { withFileLock } from './file-mutex.js';
5
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
5
6
  import { LockBusyError, withSpecLock } from '../engine/safety/cross-process-lock.js';
6
7
  const heldApprovalLocks = new AsyncLocalStorage();
7
8
  function approvalLockId(specId) {
@@ -1,6 +1,7 @@
1
1
  // storage/auto-promoter-config-store.ts — Persists auto-promoter config (SPEC-391)
2
2
  // Layout: data/projects/{hash}/config/auto-promoter.json
3
3
  import { readJson, writeJson, projectDataDir, hashProjectPath } from './base-store.js';
4
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
4
5
  import { getDefaultConfig } from '../engine/auto-promoter.js';
5
6
  function configFile(projectId) {
6
7
  return `${projectDataDir(projectId)}/config/auto-promoter.json`;
@@ -1,6 +1,7 @@
1
1
  // storage/autopilot-log-store.ts — SPEC-772: Autopilot cascade log persistence
2
2
  import { readFile, mkdir } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
4
5
  import { atomicWriteFile } from '../engine/safety/atomic-write-file.js';
5
6
  import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
6
7
  import { projectDataDir } from './base-store.js';
@@ -3,6 +3,7 @@
3
3
  import { readFile, mkdir } from 'node:fs/promises';
4
4
  import { readFileSync } from 'node:fs';
5
5
  import { dirname, isAbsolute, join, resolve } from 'node:path';
6
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
6
7
  import { writeJsonSafe } from '../engine/safety/atomic-writer.js';
7
8
  import { resolveStorageLayout } from './storage-layout.js';
8
9
  import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
@@ -3,6 +3,7 @@ import { readdir, mkdir } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { readJson, writeJson, projectDataDir } from './base-store.js';
5
5
  import { withFileLock } from './file-mutex.js';
6
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
6
7
  import { writeJsonSafe } from '../engine/safety/atomic-writer.js';
7
8
  // ---------------------------------------------------------------------------
8
9
  // Helpers
@@ -1,5 +1,6 @@
1
1
  // storage/feedback-remote.ts — Configured remote feedback relay (fire-and-forget)
2
2
  // No PII is ever sent: no project paths, no user names, no emails.
3
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
3
4
  import { networkFetch, withNetworkConsent } from '../engine/network-policy.js';
4
5
  function resolveConfiguredFeedbackEndpoint(endpoint) {
5
6
  const configured = endpoint ?? process.env.PLANU_FEEDBACK_ENDPOINT;
@@ -7,6 +7,7 @@
7
7
  import { createHash, randomUUID } from 'node:crypto';
8
8
  import { appendFile, readFile, mkdir } from 'node:fs/promises';
9
9
  import { dirname, join } from 'node:path';
10
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
10
11
  import { fastAppendGapEntry, fastVerifyGapsChain } from '../engine/core-bridge.js';
11
12
  import { projectDataDir } from './base-store.js';
12
13
  import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
@@ -1,4 +1,6 @@
1
1
  import type { RegisteredProject, GlobalProjectsRegistry } from '../types/index.js';
2
+ /** Best-effort, non-creating read of a checkout's logical project identity. */
3
+ export declare function readLogicalProjectId(projectPath: string): Promise<string | undefined>;
2
4
  /**
3
5
  * Read the full registry from disk.
4
6
  * Returns an empty registry when the file does not exist.
@@ -15,6 +17,21 @@ export declare function saveRegistry(registry: GlobalProjectsRegistry): Promise<
15
17
  * Returns the registered project entry.
16
18
  */
17
19
  export declare function addProject(projectPath: string): Promise<RegisteredProject>;
20
+ /** Stable, classified failure for a logical project id with no unambiguous canonical root. */
21
+ export type CanonicalRootResult = {
22
+ readonly ok: true;
23
+ readonly root: string;
24
+ } | {
25
+ readonly ok: false;
26
+ readonly reason: 'zero' | 'ambiguous';
27
+ readonly roots: readonly string[];
28
+ };
29
+ /**
30
+ * Resolve the single canonical root registered for a logical project identity.
31
+ * Fails closed — never guesses a first match — when zero or more than one
32
+ * distinct registered path claims the same `logicalProjectId`.
33
+ */
34
+ export declare function getCanonicalRoot(logicalProjectId: string): Promise<CanonicalRootResult>;
18
35
  /**
19
36
  * Remove a project from the registry by path.
20
37
  * Returns true if the project was found and removed, false otherwise.
@@ -2,8 +2,26 @@
2
2
  // Persistence file: data/global/registered-projects.json
3
3
  import { readJson, writeJson, globalDataDir, hashProjectPath } from './base-store.js';
4
4
  import { withFileLock } from './file-mutex.js';
5
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
5
6
  import { isEphemeralProject } from '../engine/data-projects-gc/pattern-matcher.js';
6
7
  import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
8
+ import { readFile } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11
+ /** Best-effort, non-creating read of a checkout's logical project identity. */
12
+ export async function readLogicalProjectId(projectPath) {
13
+ try {
14
+ const raw = JSON.parse(await readFile(join(projectPath, 'planu', 'project.json'), 'utf8'));
15
+ const id = raw?.logicalProjectId;
16
+ return typeof id === 'string' && UUID_PATTERN.test(id) ? id : undefined;
17
+ }
18
+ catch (error) {
19
+ if (error.code !== 'ENOENT') {
20
+ reportClassifiedDegradation('LOGICAL_PROJECT_ID_READ_FAILED', error);
21
+ }
22
+ return undefined;
23
+ }
24
+ }
7
25
  // ---------------------------------------------------------------------------
8
26
  // File path
9
27
  // ---------------------------------------------------------------------------
@@ -46,11 +64,15 @@ export async function addProject(projectPath) {
46
64
  specCount: 0,
47
65
  };
48
66
  }
67
+ const logicalProjectId = await readLogicalProjectId(projectPath);
49
68
  return withFileLock(registryFile(), async () => {
50
69
  const registry = await getRegistry();
51
70
  const existing = registry.projects.find((p) => p.path === projectPath);
52
71
  if (existing !== undefined) {
53
72
  existing.hash = hash;
73
+ if (logicalProjectId !== undefined) {
74
+ existing.logicalProjectId = logicalProjectId;
75
+ }
54
76
  registry.updatedAt = new Date().toISOString();
55
77
  await saveRegistry(registry);
56
78
  return existing;
@@ -60,6 +82,7 @@ export async function addProject(projectPath) {
60
82
  hash,
61
83
  registeredAt: new Date().toISOString(),
62
84
  specCount: 0,
85
+ ...(logicalProjectId !== undefined && { logicalProjectId }),
63
86
  };
64
87
  registry.projects.push(entry);
65
88
  registry.updatedAt = new Date().toISOString();
@@ -67,6 +90,30 @@ export async function addProject(projectPath) {
67
90
  return entry;
68
91
  });
69
92
  }
93
+ /**
94
+ * Resolve the single canonical root registered for a logical project identity.
95
+ * Fails closed — never guesses a first match — when zero or more than one
96
+ * distinct registered path claims the same `logicalProjectId`.
97
+ */
98
+ export async function getCanonicalRoot(logicalProjectId) {
99
+ const projects = await getProjects();
100
+ const roots = [
101
+ ...new Set(projects
102
+ .filter((project) => project.logicalProjectId === logicalProjectId)
103
+ .map((project) => project.path)),
104
+ ];
105
+ if (roots.length === 0) {
106
+ return { ok: false, reason: 'zero', roots };
107
+ }
108
+ if (roots.length > 1) {
109
+ return { ok: false, reason: 'ambiguous', roots };
110
+ }
111
+ const [root] = roots;
112
+ if (root === undefined) {
113
+ return { ok: false, reason: 'zero', roots };
114
+ }
115
+ return { ok: true, root };
116
+ }
70
117
  /**
71
118
  * Remove a project from the registry by path.
72
119
  * Returns true if the project was found and removed, false otherwise.
@@ -1,5 +1,6 @@
1
1
  import { readJson, writeJson, globalDataDir } from './base-store.js';
2
2
  import { withFileLock } from './file-mutex.js';
3
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
3
4
  import { applyLearnedLimit, markVerified } from '../engine/registry-extender.js';
4
5
  import { readFileSync } from 'node:fs';
5
6
  import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
@@ -1,6 +1,8 @@
1
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
1
2
  import { mergeContext } from '../../engine/context-merger.js';
2
3
  import { readJson, writeJson, projectDataDir } from '../base-store.js';
3
4
  import { withFileLock } from '../file-mutex.js';
5
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
4
6
  import { addMetricsToState } from '../../engine/calibrator.js';
5
7
  import { getKnowledge, knowledgeFile } from './knowledge.js';
6
8
  // --- File paths ---
@@ -3,7 +3,9 @@ import { copyFile, lstat, mkdir, readFile, readdir, rename, rm, writeFile } from
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
4
  import { homedir, tmpdir } from 'node:os';
5
5
  import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from 'node:path';
6
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
6
7
  import { acquireLock as acquireFileMutex, releaseLock as releaseFileMutex, } from '../../engine/safety/file-mutex.js';
8
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
7
9
  import { assertNoObsoleteFileLock } from '../../engine/safety/obsolete-lock-guard.js';
8
10
  function sha256(value) {
9
11
  return createHash('sha256').update(value).digest('hex');
@@ -174,10 +176,17 @@ async function readJournal(path, expected) {
174
176
  throw error;
175
177
  }
176
178
  }
179
+ function isTerminalPhaseValue(value) {
180
+ return value === 'completed';
181
+ }
182
+ async function quarantineStaleJournal(journalPath) {
183
+ const quarantinePath = journalPath.replace(/\.journal\.json$/, `.journal.stale-${String(Date.now())}.json`);
184
+ await rename(journalPath, quarantinePath);
185
+ }
177
186
  async function retireCompletedJournalForNewSources(journalPath, expected) {
178
- let parsed;
187
+ let rawText;
179
188
  try {
180
- parsed = JSON.parse(await readFile(journalPath, 'utf8'));
189
+ rawText = await readFile(journalPath, 'utf8');
181
190
  }
182
191
  catch (error) {
183
192
  if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
@@ -185,32 +194,47 @@ async function retireCompletedJournalForNewSources(journalPath, expected) {
185
194
  }
186
195
  throw error;
187
196
  }
197
+ const parsed = JSON.parse(rawText);
188
198
  if (!parsed || typeof parsed !== 'object' || !('legacyRoots' in parsed)) {
189
199
  throw new Error('[Planu] Invalid or unsafe migration journal');
190
200
  }
191
- const rawRoots = parsed.legacyRoots;
201
+ const record = parsed;
202
+ const rawRoots = record.legacyRoots;
192
203
  if (!Array.isArray(rawRoots) || !rawRoots.every((root) => typeof root === 'string')) {
193
204
  throw new Error('[Planu] Invalid or unsafe migration journal');
194
205
  }
195
- if (JSON.stringify(rawRoots) === JSON.stringify(expected.legacyRoots)) {
196
- return;
206
+ const rootsChanged = JSON.stringify(rawRoots) !== JSON.stringify(expected.legacyRoots);
207
+ const previousRoots = rootsChanged
208
+ ? rawRoots.map((root) => assertAbsolute(root, 'journal legacyRoot'))
209
+ : [...expected.legacyRoots];
210
+ if (rootsChanged) {
211
+ for (const root of previousRoots) {
212
+ assertNarrowLegacyRoot(root);
213
+ if (isInside(root, expected.destinationRoot) || isInside(expected.destinationRoot, root)) {
214
+ throw new Error('[Planu] Invalid or unsafe migration journal');
215
+ }
216
+ }
197
217
  }
198
- const previousRoots = rawRoots.map((root) => assertAbsolute(root, 'journal legacyRoot'));
199
- for (const root of previousRoots) {
200
- assertNarrowLegacyRoot(root);
201
- if (isInside(root, expected.destinationRoot) || isInside(expected.destinationRoot, root)) {
202
- throw new Error('[Planu] Invalid or unsafe migration journal');
218
+ try {
219
+ const previous = validateJournal(parsed, { ...expected, legacyRoots: previousRoots });
220
+ if (!rootsChanged) {
221
+ return;
222
+ }
223
+ if (previous.phase !== 'completed') {
224
+ throw new Error('[Planu] Active migration journal does not match requested roots');
203
225
  }
226
+ // A completed destination remains live and may legitimately receive newer
227
+ // canonical records. Re-inventory its real directory instead of requiring
228
+ // the historical generation digest to remain frozen forever.
229
+ await inventoryStorageRoot(expected.destinationRoot);
230
+ await rm(journalPath, { force: true });
204
231
  }
205
- const previous = validateJournal(parsed, { ...expected, legacyRoots: previousRoots });
206
- if (previous.phase !== 'completed') {
207
- throw new Error('[Planu] Active migration journal does not match requested roots');
232
+ catch (error) {
233
+ if (!isTerminalPhaseValue(record.phase)) {
234
+ throw error;
235
+ }
236
+ await quarantineStaleJournal(journalPath);
208
237
  }
209
- // A completed destination remains live and may legitimately receive newer
210
- // canonical records. Re-inventory its real directory instead of requiring
211
- // the historical generation digest to remain frozen forever.
212
- await inventoryStorageRoot(expected.destinationRoot);
213
- await rm(journalPath, { force: true });
214
238
  }
215
239
  function recordTimestamp(value) {
216
240
  if (!value || typeof value !== 'object') {
@@ -1,6 +1,7 @@
1
1
  // storage/project-drift-store.ts — SPEC-666: Read/write drift signals from .planu/drift-commits.ndjson
2
2
  import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
4
5
  import { analyzeCommitRecord } from '../engine/project-drift-detector.js';
5
6
  import { hashProjectPath, projectDataDir } from './base-store.js';
6
7
  import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
@@ -27,6 +27,33 @@ export declare function hashWorkspaceKey(workspaceKey: string): string;
27
27
  export declare function deriveWorkspaceKey(projectPath: string): Promise<string>;
28
28
  /** Resolve or create stable project and machine-workspace UUIDs. */
29
29
  export declare function ensureProjectIdentity(projectPath: string, options: ProjectIdentityOptions): Promise<ResolvedProjectIdentity>;
30
+ export type PortablePathErrorCode = 'INVALID_TYPE' | 'EMPTY' | 'NUL_BYTE' | 'NOT_PORTABLE_SHAPE' | 'WRONG_SPEC' | 'ESCAPES_ROOT' | 'ROOT_NOT_FOUND' | 'ROOT_UNVERIFIED' | 'NOT_FOUND' | 'SYMLINK' | 'NOT_FILE';
31
+ /** Stable, classified failure for a spec path that cannot be resolved as a portable identity. */
32
+ export declare class PortablePathError extends Error {
33
+ readonly code: PortablePathErrorCode;
34
+ constructor(code: PortablePathErrorCode, message: string);
35
+ }
36
+ /**
37
+ * Extract the portable `planu/specs/<specId[-slug]>/<filename>` suffix from a stored
38
+ * path value, rejecting anything that is not that exact shape for the given spec.
39
+ * Works for both legacy absolute values and already-portable relative ones — the
40
+ * incoming prefix is discarded, so this doubles as the legacy-path normalizer.
41
+ */
42
+ export declare function toPortableSpecPath(specId: string, storedPath: unknown): string;
43
+ /**
44
+ * Resolve a stored `specPath`/`technicalPath` value against the verified canonical
45
+ * root, returning the realpath of the contained file. Fails closed — with a stable
46
+ * classified {@link PortablePathError} — for any absent, malformed, escaping,
47
+ * symlinked, or cross-spec value, before any content is read or written.
48
+ */
49
+ export declare function resolvePortableSpecPath(specId: string, storedPath: unknown, canonicalRoot: string): Promise<string>;
50
+ /**
51
+ * Lifecycle-consumer entry point: verify the candidate root against the
52
+ * global project registry before resolving through it, so an unregistered,
53
+ * ambiguous, or stale checkout never stands in for the real canonical root.
54
+ * Delegates to {@link resolvePortableSpecPath} once verified.
55
+ */
56
+ export declare function resolveVerifiedSpecPath(specId: string, storedPath: unknown, canonicalRoot: string): Promise<string>;
30
57
  /** Explicitly turn a copied project into an independent logical project. */
31
58
  export declare function forkProjectIdentity(projectPath: string, options: ProjectIdentityOptions): Promise<ResolvedProjectIdentity>;
32
59
  //# sourceMappingURL=project-identity.d.ts.map
@@ -1,7 +1,10 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
- import { mkdir, open, readFile, realpath, rename, rm, stat } from 'node:fs/promises';
3
- import { basename, dirname, join, normalize, resolve, sep } from 'node:path';
2
+ import { lstat, mkdir, open, readFile, realpath, rename, rm, stat } from 'node:fs/promises';
3
+ import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
4
4
  import { resolveStorageLayout } from './storage-layout.js';
5
+ import { getCanonicalRoot, readLogicalProjectId } from './global-projects-store.js';
6
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
7
+ import { isEphemeralProject } from '../engine/data-projects-gc/pattern-matcher.js';
5
8
  const IDENTITY_VERSION = 1;
6
9
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7
10
  function isProjectIdentity(value) {
@@ -156,6 +159,133 @@ export async function ensureProjectIdentity(projectPath, options) {
156
159
  const workspace = await ensureWorkspaceDocument(options.layout ?? resolveStorageLayout(), options.workspaceKey, project.logicalProjectId);
157
160
  return { project, workspace, canonicalProjectPath };
158
161
  }
162
+ /** Stable, classified failure for a spec path that cannot be resolved as a portable identity. */
163
+ export class PortablePathError extends Error {
164
+ code;
165
+ constructor(code, message) {
166
+ super(message);
167
+ this.name = 'PortablePathError';
168
+ this.code = code;
169
+ }
170
+ }
171
+ /**
172
+ * Extract the portable `planu/specs/<specId[-slug]>/<filename>` suffix from a stored
173
+ * path value, rejecting anything that is not that exact shape for the given spec.
174
+ * Works for both legacy absolute values and already-portable relative ones — the
175
+ * incoming prefix is discarded, so this doubles as the legacy-path normalizer.
176
+ */
177
+ export function toPortableSpecPath(specId, storedPath) {
178
+ if (typeof storedPath !== 'string') {
179
+ throw new PortablePathError('INVALID_TYPE', '[Planu] specPath must be a string');
180
+ }
181
+ if (storedPath.includes('\0')) {
182
+ throw new PortablePathError('NUL_BYTE', '[Planu] specPath must not contain a NUL byte');
183
+ }
184
+ if (storedPath.trim().length === 0) {
185
+ throw new PortablePathError('EMPTY', '[Planu] specPath must not be empty or whitespace-only');
186
+ }
187
+ const segments = normalize(storedPath).split(sep).filter(Boolean);
188
+ const planuIndex = segments.lastIndexOf('planu');
189
+ const dir = segments[planuIndex + 2];
190
+ const filename = segments[planuIndex + 3];
191
+ const isPortableShape = planuIndex >= 0 &&
192
+ segments[planuIndex + 1] === 'specs' &&
193
+ dir !== undefined &&
194
+ filename !== undefined &&
195
+ planuIndex + 4 === segments.length;
196
+ if (!isPortableShape) {
197
+ throw new PortablePathError('NOT_PORTABLE_SHAPE', '[Planu] specPath is not a portable planu/specs/<dir>/<file> contract');
198
+ }
199
+ if (dir !== specId && !dir.startsWith(`${specId}-`)) {
200
+ throw new PortablePathError('WRONG_SPEC', `[Planu] specPath directory "${dir}" does not belong to ${specId}`);
201
+ }
202
+ return join('planu', 'specs', dir, filename);
203
+ }
204
+ function rejectsRootEscape(root, candidate) {
205
+ const rel = relative(root, candidate);
206
+ return rel.startsWith(`..${sep}`) || rel === '..' || isAbsolute(rel);
207
+ }
208
+ async function realpathClassified(path, notFoundCode) {
209
+ try {
210
+ return await realpath(path);
211
+ }
212
+ catch (error) {
213
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
214
+ throw new PortablePathError(notFoundCode, `[Planu] path not found: ${notFoundCode}`);
215
+ }
216
+ throw error;
217
+ }
218
+ }
219
+ /**
220
+ * Reject a candidate root that carries a tracked logical project identity but
221
+ * is not the single registry-confirmed canonical root for it — a checkout
222
+ * left over from a rename/fork, or an ambiguous/unregistered project never
223
+ * resolves specs against an arbitrary directory. A candidate with no tracked
224
+ * identity (`planu/project.json` absent) is untracked, not unverified, and
225
+ * passes through unchanged — as does an ephemeral checkout (test fixture,
226
+ * scratch clone), since the global registry deliberately excludes those
227
+ * (SPEC-581) and can never confirm a root for them.
228
+ */
229
+ async function rejectsUnverifiedRoot(root) {
230
+ if (isEphemeralProject(root)) {
231
+ return false;
232
+ }
233
+ const logicalProjectId = await readLogicalProjectId(root);
234
+ if (logicalProjectId === undefined) {
235
+ return false;
236
+ }
237
+ const canonical = await getCanonicalRoot(logicalProjectId);
238
+ return !canonical.ok || resolve(canonical.root) !== root;
239
+ }
240
+ /**
241
+ * Resolve a stored `specPath`/`technicalPath` value against the verified canonical
242
+ * root, returning the realpath of the contained file. Fails closed — with a stable
243
+ * classified {@link PortablePathError} — for any absent, malformed, escaping,
244
+ * symlinked, or cross-spec value, before any content is read or written.
245
+ */
246
+ export async function resolvePortableSpecPath(specId, storedPath, canonicalRoot) {
247
+ const suffix = toPortableSpecPath(specId, storedPath);
248
+ const root = resolve(canonicalRoot);
249
+ const candidate = resolve(root, suffix);
250
+ if (rejectsRootEscape(root, candidate)) {
251
+ throw new PortablePathError('ESCAPES_ROOT', '[Planu] specPath escapes the canonical root');
252
+ }
253
+ const rootReal = await realpathClassified(root, 'ROOT_NOT_FOUND');
254
+ let candidateReal;
255
+ let candidateStat;
256
+ try {
257
+ candidateReal = await realpathClassified(candidate, 'NOT_FOUND');
258
+ candidateStat = await lstat(candidate);
259
+ }
260
+ catch (error) {
261
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
262
+ throw new PortablePathError('NOT_FOUND', '[Planu] spec file not found under canonical root');
263
+ }
264
+ throw error;
265
+ }
266
+ if (candidateStat.isSymbolicLink()) {
267
+ throw new PortablePathError('SYMLINK', '[Planu] specPath must not resolve through a symlink');
268
+ }
269
+ if (!candidateStat.isFile()) {
270
+ throw new PortablePathError('NOT_FILE', '[Planu] specPath must resolve to a regular file');
271
+ }
272
+ if (rejectsRootEscape(rootReal, candidateReal)) {
273
+ throw new PortablePathError('ESCAPES_ROOT', '[Planu] specPath escapes the canonical root');
274
+ }
275
+ return candidateReal;
276
+ }
277
+ /**
278
+ * Lifecycle-consumer entry point: verify the candidate root against the
279
+ * global project registry before resolving through it, so an unregistered,
280
+ * ambiguous, or stale checkout never stands in for the real canonical root.
281
+ * Delegates to {@link resolvePortableSpecPath} once verified.
282
+ */
283
+ export async function resolveVerifiedSpecPath(specId, storedPath, canonicalRoot) {
284
+ if (await rejectsUnverifiedRoot(resolve(canonicalRoot))) {
285
+ throw new PortablePathError('ROOT_UNVERIFIED', '[Planu] canonical root is not the single registry-confirmed root for this project');
286
+ }
287
+ return resolvePortableSpecPath(specId, storedPath, canonicalRoot);
288
+ }
159
289
  /** Explicitly turn a copied project into an independent logical project. */
160
290
  export async function forkProjectIdentity(projectPath, options) {
161
291
  const canonicalProjectPath = await canonicalizeProjectPath(projectPath);
@@ -2,27 +2,11 @@
2
2
  // Resolves a project from its data directory OR from disk fingerprints (planu/ + specs/).
3
3
  // Used by planu_status, challenge_spec, and snapshot_spec_hashes to avoid "Project not found"
4
4
  // when the knowledge entry is missing but the project clearly exists on disk.
5
- import { access } from 'node:fs/promises';
6
5
  import { join } from 'node:path';
7
6
  import { getKnowledge, saveKnowledge } from './knowledge-store/knowledge.js';
8
7
  import { hashProjectPath, projectDataDir } from './base-store.js';
9
8
  import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
10
- /**
11
- * Check whether a file/directory path exists on disk (no-throw helper).
12
- */
13
- async function pathExists(p) {
14
- try {
15
- await access(p);
16
- return true;
17
- }
18
- catch (error) {
19
- if (error.code === 'ENOENT' ||
20
- (error instanceof Error && error.message === 'ENOENT')) {
21
- return false;
22
- }
23
- throw error;
24
- }
25
- }
9
+ import { pathExistsStrict as pathExists } from '../core/shared/fs.js';
26
10
  /**
27
11
  * Resolve a project from its projectPath.
28
12
  *
@@ -1,4 +1,6 @@
1
+ import { DURABLE_JOB_STATES, type DurableJobRecord, type DurableJobState } from '../types/durable-job.js';
1
2
  import { type ValidationReceiptCheckpointLimits } from '../types/durable-validation.js';
3
+ export { DURABLE_JOB_STATES, type DurableJobRecord, type DurableJobState };
2
4
  /** Count-only process diagnostic for deterministic resource lifecycle tests. */
3
5
  export declare function getRuntimeDatabaseDiagnosticsForTests(): {
4
6
  readonly openConnections: number;
@@ -50,26 +52,6 @@ export interface RuntimeProjectBundle {
50
52
  readonly outbox: readonly Record<string, unknown>[];
51
53
  readonly audit: readonly Record<string, unknown>[];
52
54
  }
53
- export declare const DURABLE_JOB_STATES: readonly ['accepted', 'running', 'checkpointed', 'cancelled', 'failed', 'dead-letter', 'completed'];
54
- export type DurableJobState = (typeof DURABLE_JOB_STATES)[number];
55
- export interface DurableJobRecord {
56
- readonly id: string;
57
- readonly projectId: string;
58
- readonly workspaceId: string;
59
- readonly state: DurableJobState;
60
- readonly payload: unknown;
61
- readonly checkpoint?: unknown;
62
- readonly result?: unknown;
63
- readonly error?: string;
64
- readonly attempts: number;
65
- readonly maxAttempts: number;
66
- readonly cancelRequested: boolean;
67
- readonly leaseOwner?: string;
68
- readonly leaseExpiresAt?: string;
69
- readonly fencingToken: number;
70
- readonly createdAt: string;
71
- readonly updatedAt: string;
72
- }
73
55
  export interface JobLeaseInput {
74
56
  readonly id: string;
75
57
  readonly projectId: string;
@@ -2,7 +2,9 @@
2
2
  import { mkdirSync } from 'node:fs';
3
3
  import { dirname, isAbsolute, resolve } from 'node:path';
4
4
  import { DatabaseSync } from 'node:sqlite';
5
+ import { DURABLE_JOB_STATES, } from '../types/durable-job.js';
5
6
  import { parseValidationReceiptPendingCheckpoint, } from '../types/durable-validation.js';
7
+ export { DURABLE_JOB_STATES };
6
8
  const CURRENT_SCHEMA_VERSION = 4;
7
9
  const DEFAULT_BUSY_TIMEOUT_MS = 5_000;
8
10
  const DEFAULT_MAX_CONNECTIONS = 4;
@@ -47,15 +49,6 @@ export function getRuntimeDatabaseDiagnosticsForTests() {
47
49
  }
48
50
  return { openConnections: count, integrityCheckedOpens, integrityBypassedOpens };
49
51
  }
50
- export const DURABLE_JOB_STATES = [
51
- 'accepted',
52
- 'running',
53
- 'checkpointed',
54
- 'cancelled',
55
- 'failed',
56
- 'dead-letter',
57
- 'completed',
58
- ];
59
52
  export class RuntimeDatabaseCorruptionError extends Error {
60
53
  databasePath;
61
54
  detail;
@@ -6,6 +6,7 @@
6
6
  import { createHash } from 'node:crypto';
7
7
  import { readJson, writeJson, projectDataDir } from './base-store.js';
8
8
  import { withFileLock } from './file-mutex.js';
9
+ // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
9
10
  import { TFIDFEngine, TFIDF_SCHEMA_VERSION, tokenize } from '../engine/vector-store/tfidf.js';
10
11
  function hashContent(content) {
11
12
  return createHash('sha256').update(content).digest('hex');