supipowers 1.5.3 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +131 -42
  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 +18 -8
  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 +135 -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 +268 -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 +221 -109
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +114 -13
  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 +1 -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 +1399 -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,249 @@
1
+ /**
2
+ * APPROVE stage runner.
3
+ *
4
+ * Deterministic disk-only operation — no agent is spawned. Promotes an approved draft to
5
+ * canonical artifacts: writes `authored.json`, `manifest.json` (with `authoring` block
6
+ * cleared and `state: "ready"`), updates `index.json`, and renders `authored.md`.
7
+ *
8
+ * Resume semantics: skipped when `<session>/authored.json` already exists and the
9
+ * manifest's `authoring` block is absent (i.e., the promotion already happened).
10
+ */
11
+
12
+ import * as fs from "node:fs";
13
+ import * as path from "node:path";
14
+
15
+ import { persistAuthoredUltraPlanSession } from "../../authoring-persist.js";
16
+ import { validateUltraPlanAuthoredArtifact } from "../../contracts.js";
17
+ import {
18
+ getUltraplanAuthoredJsonPath,
19
+ getUltraplanAuthoredMarkdownPath,
20
+ getUltraplanAuthoringDraftAuthoredJsonPath,
21
+ getUltraplanAuthoringDraftFindingsPath,
22
+ ULTRAPLAN_AUTHORED_JSON_FILENAME,
23
+ ULTRAPLAN_AUTHORED_MARKDOWN_FILENAME,
24
+ } from "../../project-paths.js";
25
+ import { loadUltraPlanManifest } from "../../storage.js";
26
+ import { appendPipelineLog, loadDraftAuthoredJson } from "../storage.js";
27
+ import {
28
+ nowIso,
29
+ toManifestStageStatus,
30
+ type StageRunResult,
31
+ type StageRunner,
32
+ type StageRunnerContext,
33
+ } from "../stage-runner.js";
34
+ import type { UltraPlanAuthoredArtifact } from "../../../types.js";
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Public input
38
+ // ---------------------------------------------------------------------------
39
+
40
+ export interface ApproveStageInput {
41
+ /** Which iteration's draft to promote. Must match an existing `drafts/iteration-N/` dir. */
42
+ iteration: number;
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Markdown renderer
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /**
50
+ * Produce a simple human-readable summary of the authored artifact. No external helper
51
+ * exists in the codebase yet, so this renderer is inline.
52
+ */
53
+ function renderApprovedMarkdown(authored: UltraPlanAuthoredArtifact): string {
54
+ const lines: string[] = [
55
+ `# ${authored.title}`,
56
+ ``,
57
+ `**Goal:** ${authored.goal}`,
58
+ ``,
59
+ ];
60
+
61
+ const applicable = authored.stacks.filter((s) => s.applicability === "applicable");
62
+ if (applicable.length > 0) {
63
+ lines.push(`## Stacks`);
64
+ lines.push(``);
65
+ for (const stack of applicable) {
66
+ const count = stack.domains.reduce(
67
+ (n, d) => n + d.unit.length + d.integration.length + d.e2e.length,
68
+ 0,
69
+ );
70
+ lines.push(`### ${stack.stack}`);
71
+ lines.push(``);
72
+ for (const domain of stack.domains) {
73
+ const domainCount =
74
+ domain.unit.length + domain.integration.length + domain.e2e.length;
75
+ lines.push(
76
+ `- **${domain.name}** (${domainCount} scenario${domainCount === 1 ? "" : "s"})`,
77
+ );
78
+ for (const scenario of [...domain.unit, ...domain.integration, ...domain.e2e]) {
79
+ lines.push(` - ${scenario.title}`);
80
+ }
81
+ }
82
+ lines.push(``);
83
+ lines.push(
84
+ `_${count} total scenario${count === 1 ? "" : "s"}_`,
85
+ );
86
+ lines.push(``);
87
+ }
88
+ }
89
+
90
+ lines.push(`---`);
91
+ lines.push(`_Session ID: ${authored.sessionId}_`);
92
+ lines.push(``);
93
+
94
+ return lines.join("\n");
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Stage implementation
99
+ // ---------------------------------------------------------------------------
100
+
101
+ export class ApproveStage implements StageRunner {
102
+ readonly stage = "approve" as const;
103
+
104
+ constructor(private readonly input: ApproveStageInput) {}
105
+
106
+ async isReady(ctx: StageRunnerContext): Promise<boolean> {
107
+ const draftPath = getUltraplanAuthoringDraftAuthoredJsonPath(
108
+ ctx.paths, ctx.cwd, ctx.sessionId, this.input.iteration,
109
+ );
110
+ const findingsPath = getUltraplanAuthoringDraftFindingsPath(
111
+ ctx.paths, ctx.cwd, ctx.sessionId, this.input.iteration,
112
+ );
113
+ return fs.existsSync(draftPath) && fs.existsSync(findingsPath);
114
+ }
115
+
116
+ async isComplete(ctx: StageRunnerContext): Promise<boolean> {
117
+ const authoredPath = getUltraplanAuthoredJsonPath(ctx.paths, ctx.cwd, ctx.sessionId);
118
+ if (!fs.existsSync(authoredPath)) return false;
119
+ const manifestResult = loadUltraPlanManifest(ctx.paths, ctx.cwd, ctx.sessionId);
120
+ if (!manifestResult.ok) return false;
121
+ // Complete when the authoring block has been cleared (promotion succeeded).
122
+ return !manifestResult.value.authoring;
123
+ }
124
+
125
+ async run(ctx: StageRunnerContext): Promise<StageRunResult> {
126
+ if (!(await this.isReady(ctx))) {
127
+ const draftPath = getUltraplanAuthoringDraftAuthoredJsonPath(
128
+ ctx.paths, ctx.cwd, ctx.sessionId, this.input.iteration,
129
+ );
130
+ const findingsPath = getUltraplanAuthoringDraftFindingsPath(
131
+ ctx.paths, ctx.cwd, ctx.sessionId, this.input.iteration,
132
+ );
133
+ const missingDraft = !fs.existsSync(draftPath);
134
+ return {
135
+ status: "failed",
136
+ stage: this.stage,
137
+ artifactPaths: [],
138
+ error: missingDraft
139
+ ? `APPROVE requires a draft authored.json at iteration ${this.input.iteration}; run the synthesize stage first.`
140
+ : `APPROVE requires findings.json at iteration ${this.input.iteration}; run the review stage first.`,
141
+ };
142
+ }
143
+
144
+ if (await this.isComplete(ctx)) {
145
+ return {
146
+ status: "skipped",
147
+ stage: this.stage,
148
+ artifactPaths: [ULTRAPLAN_AUTHORED_JSON_FILENAME, ULTRAPLAN_AUTHORED_MARKDOWN_FILENAME],
149
+ details: { reason: "canonical authored.json already exists and authoring block is cleared" },
150
+ };
151
+ }
152
+
153
+ // Step 1: load and validate the draft authored.json.
154
+ const draftResult = loadDraftAuthoredJson(
155
+ ctx.paths, ctx.cwd, ctx.sessionId, this.input.iteration,
156
+ );
157
+ if (!draftResult.ok) {
158
+ return {
159
+ status: "blocked",
160
+ stage: this.stage,
161
+ artifactPaths: [],
162
+ error: `APPROVE could not read draft authored.json at iteration ${this.input.iteration}: ${draftResult.error.message}`,
163
+ };
164
+ }
165
+
166
+ const validation = validateUltraPlanAuthoredArtifact(draftResult.value);
167
+ if (!validation.ok) {
168
+ const detail = validation.errors.slice(0, 3).join("; ");
169
+ return {
170
+ status: "blocked",
171
+ stage: this.stage,
172
+ artifactPaths: [],
173
+ error: `APPROVE draft authored.json failed schema validation: ${detail}`,
174
+ };
175
+ }
176
+
177
+ const authored: UltraPlanAuthoredArtifact = validation.value;
178
+
179
+ // Step 2: load the existing manifest, drop the `authoring` block, set state to "ready".
180
+ const manifestResult = loadUltraPlanManifest(ctx.paths, ctx.cwd, ctx.sessionId);
181
+ if (!manifestResult.ok) {
182
+ return {
183
+ status: "blocked",
184
+ stage: this.stage,
185
+ artifactPaths: [],
186
+ error: `APPROVE could not read the session manifest: ${manifestResult.error.message}`,
187
+ };
188
+ }
189
+
190
+ const { authoring: _authBlock, ...baseManifest } = manifestResult.value;
191
+ void _authBlock;
192
+ const canonicalManifest = {
193
+ ...baseManifest,
194
+ state: "ready" as const,
195
+ updatedAt: nowIso(ctx),
196
+ };
197
+
198
+ // Step 3: persist authored.json + manifest.json + index.json atomically.
199
+ const persistResult = persistAuthoredUltraPlanSession({
200
+ paths: ctx.paths,
201
+ cwd: ctx.cwd,
202
+ authored,
203
+ manifest: canonicalManifest,
204
+ });
205
+
206
+ if (!persistResult.ok) {
207
+ const kind = persistResult.error.kind;
208
+ // session-id-exists is only reachable if a previous run partially succeeded and left
209
+ // a stale index entry with a valid manifest; treat it as a recoverable block.
210
+ return {
211
+ status: "blocked",
212
+ stage: this.stage,
213
+ artifactPaths: [],
214
+ error: `APPROVE failed to persist canonical artifacts (${kind}): session may already be promoted or the index is corrupt.`,
215
+ };
216
+ }
217
+
218
+ // Step 4: write authored.md (best-effort — the canonical JSON is already committed).
219
+ const markdownPath = getUltraplanAuthoredMarkdownPath(ctx.paths, ctx.cwd, ctx.sessionId);
220
+ const markdown = renderApprovedMarkdown(authored);
221
+ try {
222
+ fs.mkdirSync(path.dirname(markdownPath), { recursive: true });
223
+ fs.writeFileSync(markdownPath, markdown, "utf8");
224
+ } catch {
225
+ // Non-critical: authored.json is already persisted; md failure is cosmetic.
226
+ }
227
+
228
+ // Step 5: append a pipeline-log entry summarising the promotion.
229
+ appendPipelineLog(ctx.paths, ctx.cwd, ctx.sessionId, {
230
+ recordedAt: nowIso(ctx),
231
+ stage: this.stage,
232
+ stageStatus: toManifestStageStatus("completed"),
233
+ iteration: this.input.iteration,
234
+ summary: `approved iteration ${this.input.iteration} to ${ctx.sessionId}`,
235
+ details: {
236
+ authoredPath: persistResult.authoredPath,
237
+ manifestPath: persistResult.manifestPath,
238
+ indexPath: persistResult.indexPath,
239
+ },
240
+ });
241
+
242
+ return {
243
+ status: "completed",
244
+ stage: this.stage,
245
+ artifactPaths: [ULTRAPLAN_AUTHORED_JSON_FILENAME, ULTRAPLAN_AUTHORED_MARKDOWN_FILENAME],
246
+ details: { iteration: this.input.iteration },
247
+ };
248
+ }
249
+ }
@@ -0,0 +1,289 @@
1
+ /**
2
+ * DISCOVER stage runner.
3
+ *
4
+ * Spawns a single `createAgentSession` running the `discoverer` slot agent. The agent reads the
5
+ * intake + scout artifacts, then calls `ultraplan_decision_record` for each gray-area question
6
+ * that needs a locked answer before synthesis, and optionally `ultraplan_defer_idea` for items
7
+ * that are out of scope for this session.
8
+ *
9
+ * Resume semantics: skipped when decisions.jsonl exists with at least one line.
10
+ *
11
+ * The stage returns `awaiting-user` (not `completed`): the discover stage gate is a user review
12
+ * step. The pipeline driver (Phase 9) flips the stage to done on user approval.
13
+ *
14
+ * On success the stage also persists `authoring/discuss.md` rendered from all decision records.
15
+ */
16
+
17
+ import * as fs from "node:fs";
18
+
19
+ import { resolveAuthoringSlot } from "../agent-catalog.js";
20
+ import { resolveAuthoringSlotModel } from "../model.js";
21
+ import { modelRegistry } from "../../../config/model-registry-instance.js";
22
+ import {
23
+ appendPipelineLog,
24
+ loadIntakeArtifact,
25
+ loadScoutArtifact,
26
+ saveAuthoringState,
27
+ saveDiscussArtifact,
28
+ } from "../storage.js";
29
+ import {
30
+ buildAgentDisplayName,
31
+ nowIso,
32
+ toManifestStageStatus,
33
+ type StageRunResult,
34
+ type StageRunner,
35
+ type StageRunnerContext,
36
+ } from "../stage-runner.js";
37
+ import {
38
+ getUltraplanAuthoringDecisionsPath,
39
+ getUltraplanAuthoringIntakePath,
40
+ getUltraplanAuthoringScoutPath,
41
+ } from "../../project-paths.js";
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Decision record shape (as written by ultraplan_decision_record).
45
+ // ---------------------------------------------------------------------------
46
+
47
+ interface DecisionRecord {
48
+ sessionId?: string;
49
+ area: string;
50
+ question: string;
51
+ decision: string;
52
+ rationale?: string;
53
+ impact?: string[];
54
+ recordedAt?: string;
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Helpers
59
+ // ---------------------------------------------------------------------------
60
+
61
+ function readDecisionLines(decisionsPath: string): DecisionRecord[] {
62
+ if (!fs.existsSync(decisionsPath)) return [];
63
+ const raw = fs.readFileSync(decisionsPath, "utf8");
64
+ const records: DecisionRecord[] = [];
65
+ for (const line of raw.split("\n").filter((l) => l.trim().length > 0)) {
66
+ try {
67
+ records.push(JSON.parse(line) as DecisionRecord);
68
+ } catch {
69
+ // Skip malformed lines; the stage only needs at least one to proceed.
70
+ }
71
+ }
72
+ return records;
73
+ }
74
+
75
+ function renderDiscussMarkdown(decisions: DecisionRecord[]): string {
76
+ if (decisions.length === 0) return "# Decisions\n\n(none)\n";
77
+ const sections = decisions.map((d) => {
78
+ const lines: string[] = [
79
+ `## ${d.area}`,
80
+ ``,
81
+ `Q: ${d.question}`,
82
+ `A: ${d.decision}`,
83
+ ];
84
+ if (d.rationale) {
85
+ lines.push(``, `Rationale: ${d.rationale}`);
86
+ }
87
+ if (d.impact && d.impact.length > 0) {
88
+ lines.push(``, `Impact: ${d.impact.join(", ")}`);
89
+ }
90
+ lines.push(``);
91
+ return lines.join("\n");
92
+ });
93
+ return `# Decisions\n\n${sections.join("\n")}`;
94
+ }
95
+
96
+ function buildDiscoverAssignment(
97
+ ctx: StageRunnerContext,
98
+ intake: unknown,
99
+ scout: unknown,
100
+ ): string {
101
+ return [
102
+ `# UltraPlan authoring · discover`,
103
+ ``,
104
+ `Session id: ${ctx.sessionId}`,
105
+ `cwd: ${ctx.cwd}`,
106
+ ``,
107
+ `## Intake artifact (verbatim)`,
108
+ "```json",
109
+ JSON.stringify(intake, null, 2),
110
+ "```",
111
+ ``,
112
+ `## Scout artifact (verbatim)`,
113
+ "```json",
114
+ JSON.stringify(scout, null, 2),
115
+ "```",
116
+ ``,
117
+ `## Your task`,
118
+ `Identify every gray area — ambiguous, underspecified, or high-risk decision — that must be`,
119
+ `locked before the synthesis stage can produce a coherent plan. For each gray area, call`,
120
+ `\`ultraplan_decision_record\` with sessionId=${JSON.stringify(ctx.sessionId)} and these fields:`,
121
+ `- area: short label (e.g. 'auth-strategy')`,
122
+ `- question: the exact question that needs a locked answer`,
123
+ `- decision: the locked answer you are recommending`,
124
+ `- rationale: why you chose this answer (optional but strongly recommended)`,
125
+ `- impact: list of domains/scenarios this affects (optional)`,
126
+ ``,
127
+ `For any idea that is clearly out of scope for this session, call \`ultraplan_defer_idea\` with`,
128
+ `sessionId=${JSON.stringify(ctx.sessionId)}, idea, and reason.`,
129
+ ``,
130
+ `Call \`ultraplan_decision_record\` at least once. Return after all tool calls.`,
131
+ ].join("\n");
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // Stage runner
136
+ // ---------------------------------------------------------------------------
137
+
138
+ export class DiscoverStage implements StageRunner {
139
+ readonly stage = "discover" as const;
140
+
141
+ async isReady(ctx: StageRunnerContext): Promise<boolean> {
142
+ return (
143
+ fs.existsSync(getUltraplanAuthoringIntakePath(ctx.paths, ctx.cwd, ctx.sessionId)) &&
144
+ fs.existsSync(getUltraplanAuthoringScoutPath(ctx.paths, ctx.cwd, ctx.sessionId))
145
+ );
146
+ }
147
+
148
+ async isComplete(ctx: StageRunnerContext): Promise<boolean> {
149
+ const decisionsPath = getUltraplanAuthoringDecisionsPath(ctx.paths, ctx.cwd, ctx.sessionId);
150
+ if (!fs.existsSync(decisionsPath)) return false;
151
+ const lines = fs.readFileSync(decisionsPath, "utf8").split("\n").filter((l) => l.trim().length > 0);
152
+ return lines.length > 0;
153
+ }
154
+
155
+ async run(ctx: StageRunnerContext): Promise<StageRunResult> {
156
+ if (!(await this.isReady(ctx))) {
157
+ return {
158
+ status: "failed",
159
+ stage: this.stage,
160
+ artifactPaths: [],
161
+ error: "DISCOVER requires both the intake and scout artifacts; run the intake and scout stages first.",
162
+ };
163
+ }
164
+
165
+ if (await this.isComplete(ctx)) {
166
+ return {
167
+ status: "skipped",
168
+ stage: this.stage,
169
+ artifactPaths: ["authoring/decisions.jsonl", "authoring/discuss.md"],
170
+ details: { reason: "decisions.jsonl already exists with at least one line" },
171
+ };
172
+ }
173
+
174
+ const intakeResult = loadIntakeArtifact(ctx.paths, ctx.cwd, ctx.sessionId);
175
+ if (!intakeResult.ok) {
176
+ return {
177
+ status: "failed",
178
+ stage: this.stage,
179
+ artifactPaths: [],
180
+ error: `DISCOVER could not read intake artifact: ${intakeResult.error.message}`,
181
+ };
182
+ }
183
+
184
+ const scoutResult = loadScoutArtifact(ctx.paths, ctx.cwd, ctx.sessionId);
185
+ if (!scoutResult.ok) {
186
+ return {
187
+ status: "failed",
188
+ stage: this.stage,
189
+ artifactPaths: [],
190
+ error: `DISCOVER could not read scout artifact: ${scoutResult.error.message}`,
191
+ };
192
+ }
193
+
194
+ const slotBinding = resolveAuthoringSlot("discoverer", ctx.paths, ctx.cwd);
195
+ const resolvedModel =
196
+ ctx.modelOverride ?? resolveAuthoringSlotModel(
197
+ "discoverer",
198
+ null,
199
+ ctx.modelConfig,
200
+ modelRegistry,
201
+ {
202
+ getModelForRole: (role) => ctx.platform.getModelForRole?.(role) ?? null,
203
+ getCurrentModel: () => ctx.platform.getCurrentModel?.() ?? "unknown",
204
+ },
205
+ );
206
+
207
+ saveAuthoringState(ctx.paths, ctx.cwd, ctx.sessionId, {
208
+ pipeline: "multi-stage",
209
+ stage: this.stage,
210
+ stageStatus: "running",
211
+ iteration: 1,
212
+ stallReentryCount: 0,
213
+ artifacts: { intake: "authoring/intake.json", scout: "authoring/scout.json" },
214
+ blocker: null,
215
+ startedAt: nowIso(ctx),
216
+ updatedAt: nowIso(ctx),
217
+ });
218
+
219
+ const sessionOpts: Record<string, unknown> = {
220
+ cwd: ctx.cwd,
221
+ agentDisplayName: buildAgentDisplayName(this.stage),
222
+ agentId: `ultraplan-authoring-${this.stage}-${ctx.sessionId}`,
223
+ };
224
+ if (resolvedModel.model) sessionOpts.model = resolvedModel.model;
225
+ if (resolvedModel.thinkingLevel) sessionOpts.thinkingLevel = resolvedModel.thinkingLevel;
226
+
227
+ const agentSession = await ctx.platform.createAgentSession(sessionOpts as never);
228
+ const assignment = [
229
+ slotBinding.definition.prompt.trim(),
230
+ "",
231
+ buildDiscoverAssignment(ctx, intakeResult.value, scoutResult.value),
232
+ ].join("\n");
233
+
234
+ try {
235
+ await agentSession.prompt(assignment, { expandPromptTemplates: false });
236
+ } finally {
237
+ await agentSession.dispose();
238
+ }
239
+
240
+ const verified = await this.isComplete(ctx);
241
+ if (!verified) {
242
+ return {
243
+ status: "failed",
244
+ stage: this.stage,
245
+ artifactPaths: [],
246
+ error:
247
+ "DISCOVER agent finished without persisting any decision records (ultraplan_decision_record was not called).",
248
+ };
249
+ }
250
+
251
+ // Build and persist discuss.md from the decisions.jsonl records.
252
+ const decisionsPath = getUltraplanAuthoringDecisionsPath(ctx.paths, ctx.cwd, ctx.sessionId);
253
+ const decisions = readDecisionLines(decisionsPath);
254
+ const markdown = renderDiscussMarkdown(decisions);
255
+ saveDiscussArtifact(ctx.paths, ctx.cwd, ctx.sessionId, markdown);
256
+
257
+ appendPipelineLog(ctx.paths, ctx.cwd, ctx.sessionId, {
258
+ recordedAt: nowIso(ctx),
259
+ stage: this.stage,
260
+ stageStatus: toManifestStageStatus("awaiting-user"),
261
+ iteration: 1,
262
+ summary: `discover stage awaiting user review (${decisions.length} decision(s) recorded)`,
263
+ details: { model: resolvedModel.model ?? null, decisionCount: decisions.length },
264
+ });
265
+
266
+ saveAuthoringState(ctx.paths, ctx.cwd, ctx.sessionId, {
267
+ pipeline: "multi-stage",
268
+ stage: this.stage,
269
+ stageStatus: "awaiting-user",
270
+ iteration: 1,
271
+ stallReentryCount: 0,
272
+ artifacts: {
273
+ intake: "authoring/intake.json",
274
+ scout: "authoring/scout.json",
275
+ discuss: "authoring/discuss.md",
276
+ },
277
+ blocker: null,
278
+ startedAt: nowIso(ctx),
279
+ updatedAt: nowIso(ctx),
280
+ });
281
+
282
+ return {
283
+ status: "awaiting-user",
284
+ stage: this.stage,
285
+ artifactPaths: ["authoring/decisions.jsonl", "authoring/discuss.md"],
286
+ details: { model: resolvedModel.model ?? null, decisionCount: decisions.length },
287
+ };
288
+ }
289
+ }