supipowers 1.5.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/README.md +14 -8
  2. package/bin/install.mjs +20 -5
  3. package/bin/install.ts +95 -0
  4. package/package.json +8 -4
  5. package/skills/context-mode/SKILL.md +17 -10
  6. package/skills/harness/SKILL.md +94 -0
  7. package/skills/ui-design/SKILL.md +63 -0
  8. package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
  9. package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
  10. package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
  11. package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
  12. package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
  13. package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
  14. package/skills/ultraplan-discover/SKILL.md +96 -0
  15. package/skills/ultraplan-intake/SKILL.md +89 -0
  16. package/skills/ultraplan-research/SKILL.md +129 -0
  17. package/skills/ultraplan-review/SKILL.md +86 -0
  18. package/skills/ultraplan-review-scope/SKILL.md +111 -0
  19. package/skills/ultraplan-review-structure/SKILL.md +120 -0
  20. package/skills/ultraplan-review-tdd/SKILL.md +142 -0
  21. package/skills/ultraplan-scout/SKILL.md +110 -0
  22. package/skills/ultraplan-synthesize/SKILL.md +124 -0
  23. package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
  24. package/src/ai/schema-text.ts +129 -0
  25. package/src/ai/structured-output.ts +274 -0
  26. package/src/ai/template.ts +27 -0
  27. package/src/bootstrap.ts +63 -28
  28. package/src/commands/agents.ts +149 -45
  29. package/src/commands/ai-review.ts +251 -30
  30. package/src/commands/clear.ts +434 -0
  31. package/src/commands/commit.ts +1 -0
  32. package/src/commands/config.ts +242 -44
  33. package/src/commands/context.ts +55 -28
  34. package/src/commands/doctor.ts +234 -6
  35. package/src/commands/fix-pr.ts +306 -131
  36. package/src/commands/generate.ts +111 -21
  37. package/src/commands/memory.ts +192 -0
  38. package/src/commands/model-picker.ts +28 -21
  39. package/src/commands/model.ts +19 -9
  40. package/src/commands/optimize-context.ts +408 -29
  41. package/src/commands/plan.ts +2 -0
  42. package/src/commands/qa.ts +312 -137
  43. package/src/commands/release.ts +259 -76
  44. package/src/commands/review.ts +293 -59
  45. package/src/commands/status.ts +200 -13
  46. package/src/commands/supi.ts +3 -35
  47. package/src/commands/ui-design.ts +394 -0
  48. package/src/commands/ultraplan.ts +1518 -0
  49. package/src/commands/update.ts +86 -0
  50. package/src/config/defaults.ts +62 -0
  51. package/src/config/loader.ts +448 -60
  52. package/src/config/schema.ts +108 -2
  53. package/src/context/optimizer.ts +25 -33
  54. package/src/context/rule-renderer.ts +223 -0
  55. package/src/context/savings.ts +258 -0
  56. package/src/context/startup-check.ts +380 -0
  57. package/src/context/startup-optimizer.ts +355 -0
  58. package/src/context/tokenignore.ts +146 -0
  59. package/src/context-mode/cache-handle.ts +49 -0
  60. package/src/context-mode/cache-preview.ts +71 -0
  61. package/src/context-mode/cache-store.ts +738 -0
  62. package/src/context-mode/compressor.ts +131 -26
  63. package/src/context-mode/dedup.ts +108 -0
  64. package/src/context-mode/detector.ts +35 -4
  65. package/src/context-mode/event-extractor.ts +14 -12
  66. package/src/context-mode/event-store.ts +91 -36
  67. package/src/context-mode/hooks.ts +798 -56
  68. package/src/context-mode/knowledge/store.ts +255 -11
  69. package/src/context-mode/memory-store.ts +325 -0
  70. package/src/context-mode/metrics-recorder.ts +158 -0
  71. package/src/context-mode/metrics-store.ts +765 -0
  72. package/src/context-mode/model.ts +24 -0
  73. package/src/context-mode/processor-keys.ts +29 -0
  74. package/src/context-mode/processors/build.ts +66 -0
  75. package/src/context-mode/processors/docker.ts +57 -0
  76. package/src/context-mode/processors/git.ts +111 -0
  77. package/src/context-mode/processors/json.ts +112 -0
  78. package/src/context-mode/processors/k8s.ts +67 -0
  79. package/src/context-mode/processors/lint.ts +67 -0
  80. package/src/context-mode/processors/log.ts +86 -0
  81. package/src/context-mode/processors/registry.ts +116 -0
  82. package/src/context-mode/processors/test-runner.ts +102 -0
  83. package/src/context-mode/processors/types.ts +20 -0
  84. package/src/context-mode/repomap.ts +400 -0
  85. package/src/context-mode/routing.ts +97 -24
  86. package/src/context-mode/sandbox/runners.ts +5 -1
  87. package/src/context-mode/snapshot-builder.ts +106 -11
  88. package/src/context-mode/source-hash.ts +173 -0
  89. package/src/context-mode/tool-name.ts +11 -0
  90. package/src/context-mode/tools.ts +654 -22
  91. package/src/context-mode/web/fetcher.ts +31 -12
  92. package/src/debug/logger.ts +2 -1
  93. package/src/deps/registry.ts +1 -1
  94. package/src/discipline/failure-summarizer.ts +170 -0
  95. package/src/discipline/failure-taxonomy.ts +131 -0
  96. package/src/discipline/workflow-invariants.ts +125 -0
  97. package/src/discovery/index.ts +31 -0
  98. package/src/discovery/lsp.ts +87 -0
  99. package/src/discovery/rank.ts +144 -0
  100. package/src/discovery/sources.ts +89 -0
  101. package/src/discovery/workflow.ts +87 -0
  102. package/src/docs/contracts.ts +39 -0
  103. package/src/docs/drift.ts +117 -87
  104. package/src/fix-pr/assessment.ts +200 -0
  105. package/src/fix-pr/contracts.ts +47 -0
  106. package/src/fix-pr/fetch-comments.ts +80 -0
  107. package/src/fix-pr/prompt-builder.ts +58 -40
  108. package/src/fix-pr/scripts/exec.ts +34 -0
  109. package/src/fix-pr/scripts/trigger-review.ts +106 -0
  110. package/src/fix-pr/scripts/wait-and-check.ts +108 -0
  111. package/src/fix-pr/types.ts +4 -0
  112. package/src/git/branch-finish.ts +5 -0
  113. package/src/git/commit-contract.ts +83 -0
  114. package/src/git/commit.ts +121 -184
  115. package/src/git/status.ts +62 -8
  116. package/src/harness/anti_slop/architecture-parser.ts +210 -0
  117. package/src/harness/anti_slop/backend-factory.ts +30 -0
  118. package/src/harness/anti_slop/backend.ts +140 -0
  119. package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
  120. package/src/harness/anti_slop/fallow-adapter.ts +305 -0
  121. package/src/harness/anti_slop/installer.ts +227 -0
  122. package/src/harness/anti_slop/queue.ts +216 -0
  123. package/src/harness/anti_slop/recommend.ts +84 -0
  124. package/src/harness/anti_slop/score.ts +180 -0
  125. package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
  126. package/src/harness/artifacts/agents-md.ts +88 -0
  127. package/src/harness/artifacts/checks-wiring.ts +57 -0
  128. package/src/harness/artifacts/docs-tree.ts +79 -0
  129. package/src/harness/artifacts/lint-configs.ts +136 -0
  130. package/src/harness/artifacts/review-agents.ts +67 -0
  131. package/src/harness/bare-entry.ts +108 -0
  132. package/src/harness/command.ts +1010 -0
  133. package/src/harness/default-agents/design.md +23 -0
  134. package/src/harness/default-agents/discover.md +18 -0
  135. package/src/harness/default-agents/implement.md +24 -0
  136. package/src/harness/default-agents/plan.md +19 -0
  137. package/src/harness/default-agents/research.md +21 -0
  138. package/src/harness/default-agents/validate.md +22 -0
  139. package/src/harness/gc/reporter.ts +28 -0
  140. package/src/harness/gc/runner.ts +136 -0
  141. package/src/harness/hooks/layer-context-inject.ts +155 -0
  142. package/src/harness/hooks/post-session-sweep.ts +130 -0
  143. package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
  144. package/src/harness/hooks/register.ts +118 -0
  145. package/src/harness/model.ts +117 -0
  146. package/src/harness/pipeline.ts +348 -0
  147. package/src/harness/project-paths.ts +235 -0
  148. package/src/harness/stage-runner.ts +107 -0
  149. package/src/harness/stages/design.ts +386 -0
  150. package/src/harness/stages/discover.ts +454 -0
  151. package/src/harness/stages/implement.ts +162 -0
  152. package/src/harness/stages/plan.ts +335 -0
  153. package/src/harness/stages/research.ts +263 -0
  154. package/src/harness/stages/validate.ts +684 -0
  155. package/src/harness/storage.ts +467 -0
  156. package/src/harness/tools.ts +426 -0
  157. package/src/lsp/bridge.ts +56 -95
  158. package/src/lsp/capabilities.ts +108 -0
  159. package/src/lsp/contracts.ts +35 -0
  160. package/src/lsp/detector.ts +8 -12
  161. package/src/markdown-frontmatter.ts +68 -0
  162. package/src/mempalace/bridge.ts +129 -0
  163. package/src/mempalace/config.ts +75 -0
  164. package/src/mempalace/format.ts +163 -0
  165. package/src/mempalace/hooks.ts +370 -0
  166. package/src/mempalace/installer-helper.ts +194 -0
  167. package/src/mempalace/python/mempalace_bridge.py +440 -0
  168. package/src/mempalace/runtime.ts +565 -0
  169. package/src/mempalace/schema.ts +264 -0
  170. package/src/mempalace/session-summary.ts +198 -0
  171. package/src/mempalace/tool.ts +186 -0
  172. package/src/mempalace/uv.ts +256 -0
  173. package/src/migrate/runner.ts +354 -0
  174. package/src/planning/approval-flow.ts +206 -9
  175. package/src/planning/plan-writer-prompt.ts +4 -3
  176. package/src/planning/planning-ask-tool.ts +39 -0
  177. package/src/planning/render-markdown.ts +74 -0
  178. package/src/planning/spec.ts +42 -0
  179. package/src/planning/system-prompt.ts +11 -8
  180. package/src/planning/validate.ts +84 -0
  181. package/src/platform/omp.ts +15 -2
  182. package/src/platform/system-prompt.ts +37 -0
  183. package/src/platform/test-utils.ts +3 -0
  184. package/src/platform/types.ts +6 -1
  185. package/src/qa/config.ts +12 -6
  186. package/src/qa/detect-app-type.ts +13 -6
  187. package/src/qa/matrix.ts +12 -6
  188. package/src/qa/prompt-builder.ts +28 -30
  189. package/src/qa/scripts/dev-server-utils.ts +72 -0
  190. package/src/qa/scripts/run-e2e-tests.ts +226 -0
  191. package/src/qa/scripts/start-dev-server.ts +138 -0
  192. package/src/qa/scripts/stop-dev-server.ts +77 -0
  193. package/src/qa/session.ts +13 -7
  194. package/src/quality/ai-setup.ts +27 -25
  195. package/src/quality/contracts.ts +34 -0
  196. package/src/quality/gates/ai-review.ts +20 -58
  197. package/src/quality/gates/command.ts +249 -46
  198. package/src/quality/review-gates.ts +18 -2
  199. package/src/quality/runner.ts +63 -22
  200. package/src/quality/schemas.ts +37 -2
  201. package/src/quality/setup.ts +96 -16
  202. package/src/release/changelog.ts +1 -1
  203. package/src/release/channels/custom.ts +13 -3
  204. package/src/release/channels/types.ts +5 -0
  205. package/src/release/contracts.ts +90 -0
  206. package/src/release/executor.ts +122 -45
  207. package/src/release/prompt.ts +18 -2
  208. package/src/release/targets.ts +86 -0
  209. package/src/release/version.ts +96 -71
  210. package/src/review/agent-loader.ts +298 -127
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +115 -14
  213. package/src/review/output.ts +12 -139
  214. package/src/review/runner.ts +12 -6
  215. package/src/review/scope.ts +144 -24
  216. package/src/review/types.ts +11 -20
  217. package/src/review/validator.ts +12 -6
  218. package/src/storage/fix-pr-sessions.ts +21 -14
  219. package/src/storage/plans.ts +14 -5
  220. package/src/storage/qa-sessions.ts +25 -19
  221. package/src/storage/reliability-metrics.ts +180 -0
  222. package/src/storage/reports.ts +8 -7
  223. package/src/storage/review-sessions.ts +55 -20
  224. package/src/tool-catalog/active-tool-controller.ts +164 -0
  225. package/src/tool-catalog/active-tool-planner.ts +212 -0
  226. package/src/tool-catalog/tool-groups.ts +102 -0
  227. package/src/types.ts +1401 -5
  228. package/src/ui-design/backend-adapter.ts +78 -0
  229. package/src/ui-design/backends/local-html.ts +82 -0
  230. package/src/ui-design/backends/pencil-mcp.ts +111 -0
  231. package/src/ui-design/components-scanner.ts +124 -0
  232. package/src/ui-design/config.ts +55 -0
  233. package/src/ui-design/pen-scanner.ts +95 -0
  234. package/src/ui-design/pen-selector.ts +72 -0
  235. package/src/ui-design/prompt-builder.ts +73 -0
  236. package/src/ui-design/scanner.ts +136 -0
  237. package/src/ui-design/session.ts +974 -0
  238. package/src/ui-design/system-prompt.ts +312 -0
  239. package/src/ui-design/tokens-scanner.ts +181 -0
  240. package/src/ui-design/types.ts +96 -0
  241. package/src/ultraplan/agent-catalog.ts +522 -0
  242. package/src/ultraplan/authoring/agent-catalog.ts +310 -0
  243. package/src/ultraplan/authoring/authoring-tools.ts +552 -0
  244. package/src/ultraplan/authoring/command-handlers.ts +339 -0
  245. package/src/ultraplan/authoring/markdown.ts +510 -0
  246. package/src/ultraplan/authoring/model.ts +162 -0
  247. package/src/ultraplan/authoring/pipeline.ts +319 -0
  248. package/src/ultraplan/authoring/stage-runner.ts +141 -0
  249. package/src/ultraplan/authoring/stages/approve.ts +249 -0
  250. package/src/ultraplan/authoring/stages/discover.ts +289 -0
  251. package/src/ultraplan/authoring/stages/intake.ts +203 -0
  252. package/src/ultraplan/authoring/stages/research.ts +399 -0
  253. package/src/ultraplan/authoring/stages/review.ts +333 -0
  254. package/src/ultraplan/authoring/stages/scout.ts +188 -0
  255. package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
  256. package/src/ultraplan/authoring/storage.ts +594 -0
  257. package/src/ultraplan/authoring/synth-gate.ts +165 -0
  258. package/src/ultraplan/authoring-draft.ts +653 -0
  259. package/src/ultraplan/authoring-persist.ts +180 -0
  260. package/src/ultraplan/authoring-tool.ts +608 -0
  261. package/src/ultraplan/authoring-wizard.ts +587 -0
  262. package/src/ultraplan/batch/merge.ts +98 -0
  263. package/src/ultraplan/batch/planner.ts +150 -0
  264. package/src/ultraplan/batch/presenter.ts +97 -0
  265. package/src/ultraplan/batch/storage.ts +420 -0
  266. package/src/ultraplan/batch/supervisor.ts +317 -0
  267. package/src/ultraplan/batch/worker.ts +26 -0
  268. package/src/ultraplan/batch/worktree.ts +110 -0
  269. package/src/ultraplan/contracts.ts +1593 -0
  270. package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
  271. package/src/ultraplan/default-agents/authoring/intake.md +12 -0
  272. package/src/ultraplan/default-agents/authoring/planner.md +12 -0
  273. package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
  274. package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
  275. package/src/ultraplan/default-agents/authoring/scout.md +12 -0
  276. package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
  277. package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
  278. package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
  279. package/src/ultraplan/default-agents/backend-executor.md +10 -0
  280. package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
  281. package/src/ultraplan/default-agents/backend-tester.md +10 -0
  282. package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
  283. package/src/ultraplan/default-agents/frontend-executor.md +10 -0
  284. package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
  285. package/src/ultraplan/default-agents/frontend-tester.md +10 -0
  286. package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
  287. package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
  288. package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
  289. package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
  290. package/src/ultraplan/execution/contract.ts +71 -0
  291. package/src/ultraplan/execution/policy.ts +217 -0
  292. package/src/ultraplan/execution/runtime-tools.ts +107 -0
  293. package/src/ultraplan/execution/session-runner.ts +281 -0
  294. package/src/ultraplan/next-router.ts +85 -0
  295. package/src/ultraplan/presenter.ts +359 -0
  296. package/src/ultraplan/project-paths.ts +342 -0
  297. package/src/ultraplan/runtime/active-execution.ts +72 -0
  298. package/src/ultraplan/runtime/apply-mutation.ts +416 -0
  299. package/src/ultraplan/runtime/blockers.ts +243 -0
  300. package/src/ultraplan/runtime/hook-bridge.ts +486 -0
  301. package/src/ultraplan/runtime/launch-context.ts +207 -0
  302. package/src/ultraplan/runtime/migration.ts +524 -0
  303. package/src/ultraplan/runtime/normalize.ts +281 -0
  304. package/src/ultraplan/runtime/proof.ts +260 -0
  305. package/src/ultraplan/runtime/reducer.ts +416 -0
  306. package/src/ultraplan/runtime/repair.ts +251 -0
  307. package/src/ultraplan/runtime/tracker-storage.ts +368 -0
  308. package/src/ultraplan/session-selection.ts +291 -0
  309. package/src/ultraplan/storage.ts +374 -0
  310. package/src/utils/editor.ts +38 -0
  311. package/src/utils/executable.ts +80 -0
  312. package/src/utils/paths.ts +1 -20
  313. package/src/utils/shell.ts +31 -0
  314. package/src/visual/companion.ts +2 -1
  315. package/src/visual/scripts/frame-template.html +60 -0
  316. package/src/visual/scripts/index.js +59 -13
  317. package/src/visual/scripts/package.json +3 -0
  318. package/src/visual/start-server.ts +2 -1
  319. package/src/workspace/git-scope.ts +64 -0
  320. package/src/workspace/locks.ts +23 -0
  321. package/src/workspace/package-manager.ts +117 -0
  322. package/src/workspace/path-mapping.ts +75 -0
  323. package/src/workspace/project-slug.ts +92 -0
  324. package/src/workspace/repo-root.ts +137 -0
  325. package/src/workspace/selector.ts +115 -0
  326. package/src/workspace/state-paths.ts +118 -0
  327. package/src/workspace/targets.ts +313 -0
  328. package/src/fix-pr/scripts/diff-comments.sh +0 -33
  329. package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
  330. package/src/fix-pr/scripts/trigger-review.sh +0 -36
  331. package/src/fix-pr/scripts/wait-and-check.sh +0 -37
  332. package/src/qa/scripts/detect-app-type.sh +0 -68
  333. package/src/qa/scripts/discover-routes.sh +0 -143
  334. package/src/qa/scripts/run-e2e-tests.sh +0 -131
  335. package/src/qa/scripts/start-dev-server.sh +0 -46
  336. package/src/qa/scripts/stop-dev-server.sh +0 -36
  337. package/src/review/prompts/fix-output-schema.md +0 -18
  338. package/src/review/prompts/review-output-schema.md +0 -38
  339. package/src/review/template.ts +0 -15
  340. /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
@@ -0,0 +1,594 @@
1
+ /**
2
+ * Storage helpers for the multi-stage authoring pipeline.
3
+ *
4
+ * Authoring artifacts live under `<session>/authoring/` and never touch the runtime tracker
5
+ * or the canonical `authored.json` until the APPROVE stage promotes them. This module is a
6
+ * thin wrapper over `node:fs` that:
7
+ * - validates each artifact against its TypeBox schema before writing,
8
+ * - writes JSON atomically (temp + rename),
9
+ * - appends to `pipeline-log.jsonl` line-by-line (no rewriting the whole file),
10
+ * - exposes load helpers that distinguish "missing" from "invalid" cleanly.
11
+ *
12
+ * Filesystem failures are returned as structured `UltraPlanStorageResult` values so callers
13
+ * never need to wrap calls in try/catch. The shape mirrors `src/ultraplan/storage.ts` and the
14
+ * runtime `tracker-storage.ts` deliberately so the consumer ergonomics are identical.
15
+ */
16
+ import * as fs from "node:fs";
17
+ import * as path from "node:path";
18
+
19
+ import type { PlatformPaths } from "../../platform/types.js";
20
+ import type {
21
+ UltraPlanAuthoringFindingsArtifact,
22
+ UltraPlanAuthoringPipelineEvent,
23
+ UltraPlanAuthoringState,
24
+ UltraPlanStackId,
25
+ UltraPlanStorageError,
26
+ UltraPlanStorageResult,
27
+ } from "../../types.js";
28
+ import {
29
+ validateUltraPlanAuthoringFindingsArtifact,
30
+ validateUltraPlanAuthoringPipelineEvent,
31
+ validateUltraPlanAuthoringState,
32
+ } from "../contracts.js";
33
+ import {
34
+ getUltraplanAuthoringDecisionsPath,
35
+ getUltraplanAuthoringDeferredIdeasPath,
36
+ getUltraplanAuthoringDir,
37
+ getUltraplanAuthoringDiscussPath,
38
+ getUltraplanAuthoringDraftAuthoredJsonPath,
39
+ getUltraplanAuthoringDraftAuthoredMarkdownPath,
40
+ getUltraplanAuthoringDraftFindingsPath,
41
+ getUltraplanAuthoringDraftIterationDir,
42
+ getUltraplanAuthoringDraftPlannerJsonPath,
43
+ getUltraplanAuthoringIntakePath,
44
+ getUltraplanAuthoringPipelineLogPath,
45
+ getUltraplanAuthoringResearchStackPath,
46
+ getUltraplanAuthoringResearchSummaryPath,
47
+ getUltraplanAuthoringScoutPath,
48
+ } from "../project-paths.js";
49
+ import { loadUltraPlanManifest, saveUltraPlanManifest } from "../storage.js";
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Result helpers (kept private; matches the shape used elsewhere in storage.ts).
53
+ // ---------------------------------------------------------------------------
54
+
55
+ function success<T>(value: T): UltraPlanStorageResult<T> {
56
+ return { ok: true, value };
57
+ }
58
+
59
+ function failure(
60
+ pathname: string,
61
+ kind: UltraPlanStorageError["kind"],
62
+ message: string,
63
+ details?: string[],
64
+ ): UltraPlanStorageResult<never> {
65
+ return {
66
+ ok: false,
67
+ error: {
68
+ kind,
69
+ path: pathname,
70
+ message,
71
+ ...(details ? { details } : {}),
72
+ },
73
+ };
74
+ }
75
+
76
+ function ensureDir(filePath: string): void {
77
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
78
+ }
79
+
80
+ function ensureDirExists(dirPath: string): void {
81
+ fs.mkdirSync(dirPath, { recursive: true });
82
+ }
83
+
84
+ /**
85
+ * Atomic JSON write: serialize → write to a per-process temp file → rename. The rename is
86
+ * atomic on the same filesystem, so concurrent readers either see the previous file or the
87
+ * full new file, never a half-written one.
88
+ */
89
+ function writeJsonAtomic(filePath: string, payload: unknown): UltraPlanStorageResult<string> {
90
+ try {
91
+ ensureDir(filePath);
92
+ const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
93
+ fs.writeFileSync(tmpPath, `${JSON.stringify(payload, null, 2)}\n`);
94
+ fs.renameSync(tmpPath, filePath);
95
+ return success(filePath);
96
+ } catch (error) {
97
+ return failure(
98
+ filePath,
99
+ "io",
100
+ error instanceof Error ? error.message : `Unable to write ${filePath}`,
101
+ );
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Atomic text write (markdown, decisions, deferred ideas). Same temp+rename strategy.
107
+ */
108
+ function writeTextAtomic(filePath: string, content: string): UltraPlanStorageResult<string> {
109
+ try {
110
+ ensureDir(filePath);
111
+ const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
112
+ fs.writeFileSync(tmpPath, content.endsWith("\n") ? content : `${content}\n`);
113
+ fs.renameSync(tmpPath, filePath);
114
+ return success(filePath);
115
+ } catch (error) {
116
+ return failure(
117
+ filePath,
118
+ "io",
119
+ error instanceof Error ? error.message : `Unable to write ${filePath}`,
120
+ );
121
+ }
122
+ }
123
+
124
+ function readJsonFile(filePath: string): UltraPlanStorageResult<unknown> {
125
+ if (!fs.existsSync(filePath)) {
126
+ return failure(filePath, "missing", `Artifact not found: ${filePath}`);
127
+ }
128
+ try {
129
+ return success(JSON.parse(fs.readFileSync(filePath, "utf8")));
130
+ } catch (error) {
131
+ return failure(
132
+ filePath,
133
+ "invalid-json",
134
+ error instanceof Error ? error.message : `Invalid JSON in ${filePath}`,
135
+ );
136
+ }
137
+ }
138
+
139
+ function readTextFile(filePath: string): UltraPlanStorageResult<string> {
140
+ if (!fs.existsSync(filePath)) {
141
+ return failure(filePath, "missing", `Artifact not found: ${filePath}`);
142
+ }
143
+ try {
144
+ return success(fs.readFileSync(filePath, "utf8"));
145
+ } catch (error) {
146
+ return failure(
147
+ filePath,
148
+ "io",
149
+ error instanceof Error ? error.message : `Unable to read ${filePath}`,
150
+ );
151
+ }
152
+ }
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // Authoring state (the manifest's `authoring` block).
156
+ //
157
+ // The state lives inside the manifest, not in a sibling file: the manifest is already the
158
+ // canonical truth for "what session is this and what state is it in," and the authoring
159
+ // block is just an extension of that. We expose load/save helpers that go through the
160
+ // manifest so callers don't need to know the embedding.
161
+ // ---------------------------------------------------------------------------
162
+
163
+ /**
164
+ * Load just the authoring block from the manifest. Returns `null` (wrapped in success) when
165
+ * the manifest exists but has no authoring block — this is the common case for legacy
166
+ * single-shot sessions and for sessions that have been promoted to `state: "ready"`.
167
+ */
168
+ export function loadAuthoringState(
169
+ paths: PlatformPaths,
170
+ cwd: string,
171
+ sessionId: string,
172
+ ): UltraPlanStorageResult<UltraPlanAuthoringState | null> {
173
+ const manifestResult = loadUltraPlanManifest(paths, cwd, sessionId);
174
+ if (!manifestResult.ok) return manifestResult;
175
+ return success(manifestResult.value.authoring ?? null);
176
+ }
177
+
178
+ /**
179
+ * Persist the authoring state by overwriting the manifest's `authoring` field. The full
180
+ * manifest is round-tripped through schema validation before being written, so this also
181
+ * implicitly validates the authoring block against `UltraPlanAuthoringStateSchema`.
182
+ *
183
+ * Pre-validates the authoring block independently for a clearer error path — callers see
184
+ * the authoring-specific errors instead of a manifest-shaped error message.
185
+ */
186
+ export function saveAuthoringState(
187
+ paths: PlatformPaths,
188
+ cwd: string,
189
+ sessionId: string,
190
+ authoring: UltraPlanAuthoringState,
191
+ ): UltraPlanStorageResult<string> {
192
+ const validation = validateUltraPlanAuthoringState(authoring);
193
+ if (!validation.ok) {
194
+ return failure(
195
+ "authoring-state",
196
+ "validation-error",
197
+ "Authoring state failed schema validation",
198
+ validation.errors,
199
+ );
200
+ }
201
+
202
+ const manifestResult = loadUltraPlanManifest(paths, cwd, sessionId);
203
+ if (!manifestResult.ok) return manifestResult;
204
+
205
+ const next = { ...manifestResult.value, authoring: validation.value, updatedAt: new Date().toISOString() };
206
+ const saved = saveUltraPlanManifest(paths, cwd, sessionId, next);
207
+ return saved;
208
+ }
209
+
210
+ /**
211
+ * Clear the authoring block. Used by the APPROVE stage after the canonical artifacts are
212
+ * promoted. Equivalent to `saveAuthoringState` with `undefined`, but the underlying manifest
213
+ * schema makes the field optional so we explicitly drop it.
214
+ */
215
+ export function clearAuthoringState(
216
+ paths: PlatformPaths,
217
+ cwd: string,
218
+ sessionId: string,
219
+ ): UltraPlanStorageResult<string> {
220
+ const manifestResult = loadUltraPlanManifest(paths, cwd, sessionId);
221
+ if (!manifestResult.ok) return manifestResult;
222
+ const { authoring: _drop, ...rest } = manifestResult.value;
223
+ void _drop;
224
+ const next = { ...rest, updatedAt: new Date().toISOString() };
225
+ return saveUltraPlanManifest(paths, cwd, sessionId, next);
226
+ }
227
+
228
+ // ---------------------------------------------------------------------------
229
+ // Stage artifacts (intake, scout, discuss, deferred-ideas, research/<stack>.md, drafts).
230
+ // JSON artifacts go through schema validation; markdown artifacts are stored as opaque text.
231
+ // ---------------------------------------------------------------------------
232
+
233
+ export function saveIntakeArtifact(
234
+ paths: PlatformPaths,
235
+ cwd: string,
236
+ sessionId: string,
237
+ artifact: unknown,
238
+ ): UltraPlanStorageResult<string> {
239
+ // The intake schema is owned by the intake stage runner (Phase 3) — at the substrate level
240
+ // we accept any JSON that round-trips, so legacy callers and tests can drop any object.
241
+ return writeJsonAtomic(getUltraplanAuthoringIntakePath(paths, cwd, sessionId), artifact);
242
+ }
243
+
244
+ export function loadIntakeArtifact(
245
+ paths: PlatformPaths,
246
+ cwd: string,
247
+ sessionId: string,
248
+ ): UltraPlanStorageResult<unknown> {
249
+ return readJsonFile(getUltraplanAuthoringIntakePath(paths, cwd, sessionId));
250
+ }
251
+
252
+ export function saveScoutArtifact(
253
+ paths: PlatformPaths,
254
+ cwd: string,
255
+ sessionId: string,
256
+ artifact: unknown,
257
+ ): UltraPlanStorageResult<string> {
258
+ return writeJsonAtomic(getUltraplanAuthoringScoutPath(paths, cwd, sessionId), artifact);
259
+ }
260
+
261
+ export function loadScoutArtifact(
262
+ paths: PlatformPaths,
263
+ cwd: string,
264
+ sessionId: string,
265
+ ): UltraPlanStorageResult<unknown> {
266
+ return readJsonFile(getUltraplanAuthoringScoutPath(paths, cwd, sessionId));
267
+ }
268
+
269
+ export function saveDiscussArtifact(
270
+ paths: PlatformPaths,
271
+ cwd: string,
272
+ sessionId: string,
273
+ markdown: string,
274
+ ): UltraPlanStorageResult<string> {
275
+ return writeTextAtomic(getUltraplanAuthoringDiscussPath(paths, cwd, sessionId), markdown);
276
+ }
277
+
278
+ export function loadDiscussArtifact(
279
+ paths: PlatformPaths,
280
+ cwd: string,
281
+ sessionId: string,
282
+ ): UltraPlanStorageResult<string> {
283
+ return readTextFile(getUltraplanAuthoringDiscussPath(paths, cwd, sessionId));
284
+ }
285
+
286
+ export function saveDeferredIdeas(
287
+ paths: PlatformPaths,
288
+ cwd: string,
289
+ sessionId: string,
290
+ markdown: string,
291
+ ): UltraPlanStorageResult<string> {
292
+ return writeTextAtomic(getUltraplanAuthoringDeferredIdeasPath(paths, cwd, sessionId), markdown);
293
+ }
294
+
295
+ export function loadDeferredIdeas(
296
+ paths: PlatformPaths,
297
+ cwd: string,
298
+ sessionId: string,
299
+ ): UltraPlanStorageResult<string> {
300
+ return readTextFile(getUltraplanAuthoringDeferredIdeasPath(paths, cwd, sessionId));
301
+ }
302
+
303
+ /** Append a single decision JSONL line. Caller is responsible for the schema/shape. */
304
+ export function appendDecisionRecord(
305
+ paths: PlatformPaths,
306
+ cwd: string,
307
+ sessionId: string,
308
+ decision: Record<string, unknown>,
309
+ ): UltraPlanStorageResult<string> {
310
+ const filePath = getUltraplanAuthoringDecisionsPath(paths, cwd, sessionId);
311
+ try {
312
+ ensureDir(filePath);
313
+ fs.appendFileSync(filePath, `${JSON.stringify(decision)}\n`);
314
+ return success(filePath);
315
+ } catch (error) {
316
+ return failure(
317
+ filePath,
318
+ "io",
319
+ error instanceof Error ? error.message : `Unable to append decision to ${filePath}`,
320
+ );
321
+ }
322
+ }
323
+
324
+ export function saveResearchStackArtifact(
325
+ paths: PlatformPaths,
326
+ cwd: string,
327
+ sessionId: string,
328
+ stack: UltraPlanStackId,
329
+ markdown: string,
330
+ ): UltraPlanStorageResult<string> {
331
+ return writeTextAtomic(getUltraplanAuthoringResearchStackPath(paths, cwd, sessionId, stack), markdown);
332
+ }
333
+
334
+ export function loadResearchStackArtifact(
335
+ paths: PlatformPaths,
336
+ cwd: string,
337
+ sessionId: string,
338
+ stack: UltraPlanStackId,
339
+ ): UltraPlanStorageResult<string> {
340
+ return readTextFile(getUltraplanAuthoringResearchStackPath(paths, cwd, sessionId, stack));
341
+ }
342
+
343
+ /**
344
+ * Remove a per-stack research artifact. Used when a stack flips from `applicable` to
345
+ * `not-applicable` mid-pipeline (the skip-stack invariant in Phase 5). Missing files are a
346
+ * no-op, not an error.
347
+ */
348
+ export function deleteResearchStackArtifact(
349
+ paths: PlatformPaths,
350
+ cwd: string,
351
+ sessionId: string,
352
+ stack: UltraPlanStackId,
353
+ ): UltraPlanStorageResult<string> {
354
+ const filePath = getUltraplanAuthoringResearchStackPath(paths, cwd, sessionId, stack);
355
+ try {
356
+ if (fs.existsSync(filePath)) {
357
+ fs.unlinkSync(filePath);
358
+ }
359
+ return success(filePath);
360
+ } catch (error) {
361
+ return failure(
362
+ filePath,
363
+ "io",
364
+ error instanceof Error ? error.message : `Unable to delete ${filePath}`,
365
+ );
366
+ }
367
+ }
368
+
369
+ export function saveResearchSummary(
370
+ paths: PlatformPaths,
371
+ cwd: string,
372
+ sessionId: string,
373
+ markdown: string,
374
+ ): UltraPlanStorageResult<string> {
375
+ return writeTextAtomic(getUltraplanAuthoringResearchSummaryPath(paths, cwd, sessionId), markdown);
376
+ }
377
+
378
+ export function loadResearchSummary(
379
+ paths: PlatformPaths,
380
+ cwd: string,
381
+ sessionId: string,
382
+ ): UltraPlanStorageResult<string> {
383
+ return readTextFile(getUltraplanAuthoringResearchSummaryPath(paths, cwd, sessionId));
384
+ }
385
+
386
+ // ---------------------------------------------------------------------------
387
+ // Drafts: per-iteration directories under `drafts/iteration-N/`.
388
+ // ---------------------------------------------------------------------------
389
+
390
+ export function ensureDraftIterationDir(
391
+ paths: PlatformPaths,
392
+ cwd: string,
393
+ sessionId: string,
394
+ iteration: number,
395
+ ): string {
396
+ const dir = getUltraplanAuthoringDraftIterationDir(paths, cwd, sessionId, iteration);
397
+ ensureDirExists(dir);
398
+ return dir;
399
+ }
400
+
401
+ export function saveDraftAuthoredJson(
402
+ paths: PlatformPaths,
403
+ cwd: string,
404
+ sessionId: string,
405
+ iteration: number,
406
+ artifact: unknown,
407
+ ): UltraPlanStorageResult<string> {
408
+ return writeJsonAtomic(
409
+ getUltraplanAuthoringDraftAuthoredJsonPath(paths, cwd, sessionId, iteration),
410
+ artifact,
411
+ );
412
+ }
413
+
414
+ export function loadDraftAuthoredJson(
415
+ paths: PlatformPaths,
416
+ cwd: string,
417
+ sessionId: string,
418
+ iteration: number,
419
+ ): UltraPlanStorageResult<unknown> {
420
+ return readJsonFile(getUltraplanAuthoringDraftAuthoredJsonPath(paths, cwd, sessionId, iteration));
421
+ }
422
+
423
+ export function saveDraftAuthoredMarkdown(
424
+ paths: PlatformPaths,
425
+ cwd: string,
426
+ sessionId: string,
427
+ iteration: number,
428
+ markdown: string,
429
+ ): UltraPlanStorageResult<string> {
430
+ return writeTextAtomic(
431
+ getUltraplanAuthoringDraftAuthoredMarkdownPath(paths, cwd, sessionId, iteration),
432
+ markdown,
433
+ );
434
+ }
435
+
436
+ export function loadDraftAuthoredMarkdown(
437
+ paths: PlatformPaths,
438
+ cwd: string,
439
+ sessionId: string,
440
+ iteration: number,
441
+ ): UltraPlanStorageResult<string> {
442
+ return readTextFile(getUltraplanAuthoringDraftAuthoredMarkdownPath(paths, cwd, sessionId, iteration));
443
+ }
444
+
445
+ /**
446
+ * Snapshot the planner's emitted draft before any user editing happens. Stored alongside the
447
+ * editable draft so forensics can compare what the planner wrote vs. what the user shipped.
448
+ */
449
+ export function saveDraftPlannerJson(
450
+ paths: PlatformPaths,
451
+ cwd: string,
452
+ sessionId: string,
453
+ iteration: number,
454
+ artifact: unknown,
455
+ ): UltraPlanStorageResult<string> {
456
+ return writeJsonAtomic(
457
+ getUltraplanAuthoringDraftPlannerJsonPath(paths, cwd, sessionId, iteration),
458
+ artifact,
459
+ );
460
+ }
461
+
462
+ export function saveFindingsArtifact(
463
+ paths: PlatformPaths,
464
+ cwd: string,
465
+ sessionId: string,
466
+ iteration: number,
467
+ findings: UltraPlanAuthoringFindingsArtifact,
468
+ ): UltraPlanStorageResult<string> {
469
+ const validation = validateUltraPlanAuthoringFindingsArtifact(findings);
470
+ if (!validation.ok) {
471
+ return failure(
472
+ "findings-artifact",
473
+ "validation-error",
474
+ "Findings artifact failed schema validation",
475
+ validation.errors,
476
+ );
477
+ }
478
+ return writeJsonAtomic(
479
+ getUltraplanAuthoringDraftFindingsPath(paths, cwd, sessionId, iteration),
480
+ validation.value,
481
+ );
482
+ }
483
+
484
+ export function loadFindingsArtifact(
485
+ paths: PlatformPaths,
486
+ cwd: string,
487
+ sessionId: string,
488
+ iteration: number,
489
+ ): UltraPlanStorageResult<UltraPlanAuthoringFindingsArtifact> {
490
+ const filePath = getUltraplanAuthoringDraftFindingsPath(paths, cwd, sessionId, iteration);
491
+ const parsed = readJsonFile(filePath);
492
+ if (!parsed.ok) return parsed;
493
+ const validation = validateUltraPlanAuthoringFindingsArtifact(parsed.value);
494
+ if (!validation.ok) {
495
+ return failure(filePath, "validation-error", `Findings failed schema validation: ${filePath}`, validation.errors);
496
+ }
497
+ return success(validation.value);
498
+ }
499
+
500
+ // ---------------------------------------------------------------------------
501
+ // Pipeline log (append-only JSONL).
502
+ // ---------------------------------------------------------------------------
503
+
504
+ /**
505
+ * Append a single event to `pipeline-log.jsonl`. The event is validated against
506
+ * `UltraPlanAuthoringPipelineEventSchema` so malformed entries never reach disk.
507
+ *
508
+ * `fs.appendFileSync` is sufficient here because line-oriented writes < PIPE_BUF are atomic
509
+ * on POSIX, and Windows serializes `appendFileSync` calls within a process. Concurrent
510
+ * authoring runs across different session IDs write to different files, so contention is
511
+ * not a concern.
512
+ */
513
+ export function appendPipelineLog(
514
+ paths: PlatformPaths,
515
+ cwd: string,
516
+ sessionId: string,
517
+ event: UltraPlanAuthoringPipelineEvent,
518
+ ): UltraPlanStorageResult<string> {
519
+ const validation = validateUltraPlanAuthoringPipelineEvent(event);
520
+ if (!validation.ok) {
521
+ return failure(
522
+ "pipeline-log-event",
523
+ "validation-error",
524
+ "Pipeline event failed schema validation",
525
+ validation.errors,
526
+ );
527
+ }
528
+
529
+ const filePath = getUltraplanAuthoringPipelineLogPath(paths, cwd, sessionId);
530
+ try {
531
+ ensureDir(filePath);
532
+ fs.appendFileSync(filePath, `${JSON.stringify(validation.value)}\n`);
533
+ return success(filePath);
534
+ } catch (error) {
535
+ return failure(
536
+ filePath,
537
+ "io",
538
+ error instanceof Error ? error.message : `Unable to append pipeline log entry to ${filePath}`,
539
+ );
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Read the full pipeline log into memory. Returns an empty array on missing file (this is
545
+ * the common state on a brand-new authoring session). Lines that fail schema validation are
546
+ * skipped silently — a corrupt log line should not block the picker or status presenter.
547
+ */
548
+ export function readPipelineLog(
549
+ paths: PlatformPaths,
550
+ cwd: string,
551
+ sessionId: string,
552
+ ): UltraPlanStorageResult<UltraPlanAuthoringPipelineEvent[]> {
553
+ const filePath = getUltraplanAuthoringPipelineLogPath(paths, cwd, sessionId);
554
+ if (!fs.existsSync(filePath)) {
555
+ return success([]);
556
+ }
557
+ let raw: string;
558
+ try {
559
+ raw = fs.readFileSync(filePath, "utf8");
560
+ } catch (error) {
561
+ return failure(
562
+ filePath,
563
+ "io",
564
+ error instanceof Error ? error.message : `Unable to read ${filePath}`,
565
+ );
566
+ }
567
+ const events: UltraPlanAuthoringPipelineEvent[] = [];
568
+ for (const line of raw.split(/\r?\n/)) {
569
+ if (!line) continue;
570
+ let parsed: unknown;
571
+ try {
572
+ parsed = JSON.parse(line);
573
+ } catch {
574
+ continue;
575
+ }
576
+ const validation = validateUltraPlanAuthoringPipelineEvent(parsed);
577
+ if (validation.ok) {
578
+ events.push(validation.value);
579
+ }
580
+ }
581
+ return success(events);
582
+ }
583
+
584
+ /**
585
+ * Convenience: returns whether an authoring directory exists for the session. Cheap check
586
+ * used by the resume picker.
587
+ */
588
+ export function hasAuthoringWorkspace(
589
+ paths: PlatformPaths,
590
+ cwd: string,
591
+ sessionId: string,
592
+ ): boolean {
593
+ return fs.existsSync(getUltraplanAuthoringDir(paths, cwd, sessionId));
594
+ }