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,203 @@
1
+ /**
2
+ * INTAKE stage runner.
3
+ *
4
+ * Receives the user's seed prompt (captured by the bare-entry TUI or `plan "..."` args) and
5
+ * spawns a single `createAgentSession` running the `intake` slot agent. The agent performs
6
+ * **structured extraction only** — it does not chat-interrogate the user, pick libraries, or
7
+ * generate scenarios. Its single side-effect is one call to `ultraplan_intake_record` which
8
+ * persists `<session>/authoring/intake.json`.
9
+ *
10
+ * Resume semantics: if the intake artifact already exists on disk and validates, the stage
11
+ * is `skipped`. Otherwise it runs from scratch — re-running INTAKE is safe because the
12
+ * artifact is overwritten atomically.
13
+ */
14
+
15
+ import * as fs from "node:fs";
16
+
17
+ import {
18
+ resolveAuthoringSlot,
19
+ } 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
+ saveAuthoringState,
26
+ } from "../storage.js";
27
+ import {
28
+ buildAgentDisplayName,
29
+ nowIso,
30
+ toManifestStageStatus,
31
+ type StageRunResult,
32
+ type StageRunner,
33
+ type StageRunnerContext,
34
+ } from "../stage-runner.js";
35
+ import {
36
+ getUltraplanAuthoringIntakePath,
37
+ } from "../../project-paths.js";
38
+
39
+ /**
40
+ * Inputs unique to INTAKE: the seed prompt that the user typed when they invoked
41
+ * `/supi:ultraplan` (bare or via `plan "..."`).
42
+ */
43
+ export interface IntakeStageInput {
44
+ seedPrompt: string;
45
+ }
46
+
47
+ /**
48
+ * The intake assignment is fully deterministic. We render the agent's prompt below and pass
49
+ * it verbatim to `session.prompt`. The agent's system prompt (the slot's markdown body)
50
+ * already explains the structured-extraction contract.
51
+ */
52
+ function buildIntakeAssignment(ctx: StageRunnerContext, input: IntakeStageInput): string {
53
+ return [
54
+ `# UltraPlan authoring · intake`,
55
+ ``,
56
+ `Session id: ${ctx.sessionId}`,
57
+ `cwd: ${ctx.cwd}`,
58
+ ``,
59
+ `## Seed prompt (verbatim)`,
60
+ "",
61
+ "```",
62
+ input.seedPrompt,
63
+ "```",
64
+ ``,
65
+ `## Your task`,
66
+ `Extract the implementation goal as structured fields. Do not chat with the user. Do not pick libraries. Do not generate scenarios.`,
67
+ ``,
68
+ `Call \`ultraplan_intake_record\` exactly once with sessionId=${JSON.stringify(ctx.sessionId)} and these fields:`,
69
+ `- title: short session title (5\u201310 words)`,
70
+ `- goal: one-line implementation goal`,
71
+ `- candidateStacks: per-stack applicability (\"applicable\" or \"not-applicable\") for frontend / backend / infrastructure`,
72
+ `- rawUserNotes: the verbatim seed prompt`,
73
+ `- deferredIdeas: anything in the seed that is out of scope for this session`,
74
+ ``,
75
+ `Return after the tool call. Do not append a chat summary.`,
76
+ ].join("\n");
77
+ }
78
+
79
+ export class IntakeStage implements StageRunner {
80
+ readonly stage = "intake" as const;
81
+
82
+ constructor(private readonly input: IntakeStageInput) {}
83
+
84
+ async isReady(_ctx: StageRunnerContext): Promise<boolean> {
85
+ // INTAKE has no upstream artifacts; it's always ready as long as a seed prompt was
86
+ // provided.
87
+ return this.input.seedPrompt.trim().length > 0;
88
+ }
89
+
90
+ async isComplete(ctx: StageRunnerContext): Promise<boolean> {
91
+ const artifactPath = getUltraplanAuthoringIntakePath(ctx.paths, ctx.cwd, ctx.sessionId);
92
+ if (!fs.existsSync(artifactPath)) return false;
93
+ const loaded = loadIntakeArtifact(ctx.paths, ctx.cwd, ctx.sessionId);
94
+ return loaded.ok;
95
+ }
96
+
97
+ async run(ctx: StageRunnerContext): Promise<StageRunResult> {
98
+ if (!(await this.isReady(ctx))) {
99
+ return {
100
+ status: "failed",
101
+ stage: this.stage,
102
+ artifactPaths: [],
103
+ error: "INTAKE has no seed prompt; supply one via /supi:ultraplan plan \"...\" or the TUI input.",
104
+ };
105
+ }
106
+
107
+ if (await this.isComplete(ctx)) {
108
+ return {
109
+ status: "skipped",
110
+ stage: this.stage,
111
+ artifactPaths: ["authoring/intake.json"],
112
+ details: { reason: "intake artifact already exists" },
113
+ };
114
+ }
115
+
116
+ const slotBinding = resolveAuthoringSlot("intake", ctx.paths, ctx.cwd);
117
+ const resolvedModel =
118
+ ctx.modelOverride ?? resolveAuthoringSlotModel(
119
+ "intake",
120
+ null,
121
+ ctx.modelConfig,
122
+ modelRegistry,
123
+ {
124
+ getModelForRole: (role) => ctx.platform.getModelForRole?.(role) ?? null,
125
+ getCurrentModel: () => ctx.platform.getCurrentModel?.() ?? "unknown",
126
+ },
127
+ );
128
+
129
+ // Mark the stage running on disk before spawning, so resume can find it mid-flight.
130
+ saveAuthoringState(ctx.paths, ctx.cwd, ctx.sessionId, {
131
+ pipeline: "multi-stage",
132
+ stage: this.stage,
133
+ stageStatus: "running",
134
+ iteration: 1,
135
+ stallReentryCount: 0,
136
+ artifacts: {},
137
+ blocker: null,
138
+ startedAt: nowIso(ctx),
139
+ updatedAt: nowIso(ctx),
140
+ });
141
+
142
+ const sessionOpts: Record<string, unknown> = {
143
+ cwd: ctx.cwd,
144
+ agentDisplayName: buildAgentDisplayName(this.stage),
145
+ agentId: `ultraplan-authoring-${this.stage}-${ctx.sessionId}`,
146
+ };
147
+ if (resolvedModel.model) sessionOpts.model = resolvedModel.model;
148
+ if (resolvedModel.thinkingLevel) sessionOpts.thinkingLevel = resolvedModel.thinkingLevel;
149
+
150
+ const agentSession = await ctx.platform.createAgentSession(sessionOpts as never);
151
+ const assignment = [
152
+ slotBinding.definition.prompt.trim(),
153
+ "",
154
+ buildIntakeAssignment(ctx, this.input),
155
+ ].join("\n");
156
+
157
+ try {
158
+ await agentSession.prompt(assignment, { expandPromptTemplates: false });
159
+ } finally {
160
+ await agentSession.dispose();
161
+ }
162
+
163
+ // Verify the agent actually wrote the artifact. If the tool call didn't happen, surface
164
+ // a structured failure rather than silently advancing.
165
+ const verified = await this.isComplete(ctx);
166
+ if (!verified) {
167
+ return {
168
+ status: "failed",
169
+ stage: this.stage,
170
+ artifactPaths: [],
171
+ error: "INTAKE agent finished without persisting an intake artifact (ultraplan_intake_record was not called).",
172
+ };
173
+ }
174
+
175
+ appendPipelineLog(ctx.paths, ctx.cwd, ctx.sessionId, {
176
+ recordedAt: nowIso(ctx),
177
+ stage: this.stage,
178
+ stageStatus: toManifestStageStatus("completed"),
179
+ iteration: 1,
180
+ summary: "intake artifact recorded",
181
+ details: { model: resolvedModel.model ?? null },
182
+ });
183
+
184
+ saveAuthoringState(ctx.paths, ctx.cwd, ctx.sessionId, {
185
+ pipeline: "multi-stage",
186
+ stage: this.stage,
187
+ stageStatus: "done",
188
+ iteration: 1,
189
+ stallReentryCount: 0,
190
+ artifacts: { intake: "authoring/intake.json" },
191
+ blocker: null,
192
+ startedAt: nowIso(ctx),
193
+ updatedAt: nowIso(ctx),
194
+ });
195
+
196
+ return {
197
+ status: "completed",
198
+ stage: this.stage,
199
+ artifactPaths: ["authoring/intake.json"],
200
+ details: { model: resolvedModel.model ?? null },
201
+ };
202
+ }
203
+ }
@@ -0,0 +1,399 @@
1
+ /**
2
+ * RESEARCH stage runner.
3
+ *
4
+ * Fans out per applicable stack using `Promise.all` over `createAgentSession` calls.
5
+ * Each researcher uses `resolveAuthoringSlotModel("researcher", stack, ...)` so
6
+ * per-stack model overrides in `model.json` work uniformly. After all per-stack
7
+ * researchers complete, builds a deterministic SUMMARY.md by concatenating the
8
+ * first ~10 lines of each `<stack>.md`.
9
+ *
10
+ * Resume semantics: if every applicable stack has its `<stack>.md` artifact AND
11
+ * `SUMMARY.md` exists, the stage is skipped.
12
+ */
13
+
14
+ import * as fs from "node:fs";
15
+
16
+ import { resolveAuthoringSlot } from "../agent-catalog.js";
17
+ import { resolveAuthoringSlotModel } from "../model.js";
18
+ import { modelRegistry } from "../../../config/model-registry-instance.js";
19
+ import {
20
+ appendPipelineLog,
21
+ deleteResearchStackArtifact,
22
+ loadDiscussArtifact,
23
+ loadIntakeArtifact,
24
+ loadResearchStackArtifact,
25
+ loadScoutArtifact,
26
+ saveAuthoringState,
27
+ saveResearchSummary,
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
+ getUltraplanAuthoringResearchStackPath,
41
+ getUltraplanAuthoringResearchSummaryPath,
42
+ getUltraplanAuthoringScoutPath,
43
+ } from "../../project-paths.js";
44
+ import { ULTRAPLAN_STACKS } from "../../contracts.js";
45
+ import type { UltraPlanBlocker, UltraPlanStackId, UltraPlanApplicability } from "../../../types.js";
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Internal helpers
49
+ // ---------------------------------------------------------------------------
50
+
51
+ interface CandidateStackEntry {
52
+ stack: UltraPlanStackId;
53
+ applicability: UltraPlanApplicability;
54
+ }
55
+
56
+ /**
57
+ * Extract the list of applicable stacks from the intake artifact. Returns an
58
+ * empty array when `candidateStacks` is missing or malformed — the caller treats
59
+ * that as "no stacks applicable" and proceeds with SUMMARY.md only.
60
+ */
61
+ function getApplicableStacks(intake: unknown): UltraPlanStackId[] {
62
+ if (
63
+ typeof intake !== "object" ||
64
+ intake === null ||
65
+ !Array.isArray((intake as Record<string, unknown>).candidateStacks)
66
+ ) {
67
+ return [];
68
+ }
69
+ const candidates = (intake as { candidateStacks: CandidateStackEntry[] }).candidateStacks;
70
+ return candidates
71
+ .filter((c) => typeof c === "object" && c !== null && c.applicability === "applicable")
72
+ .map((c) => c.stack);
73
+ }
74
+
75
+ /**
76
+ * Returns true when decisions.jsonl exists and contains at least one non-blank line.
77
+ */
78
+ function decisionsHasAtLeastOneLine(decisionsPath: string): boolean {
79
+ if (!fs.existsSync(decisionsPath)) return false;
80
+ try {
81
+ const content = fs.readFileSync(decisionsPath, "utf8");
82
+ return content.split(/\r?\n/).some((line) => line.trim().length > 0);
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+
88
+ function buildResearchAssignment(
89
+ ctx: StageRunnerContext,
90
+ stack: UltraPlanStackId,
91
+ intake: unknown,
92
+ scout: unknown,
93
+ discuss: string | null,
94
+ decisions: string,
95
+ ): string {
96
+ const parts: string[] = [
97
+ `# UltraPlan authoring · research · ${stack}`,
98
+ ``,
99
+ `Session id: ${ctx.sessionId}`,
100
+ `cwd: ${ctx.cwd}`,
101
+ `Stack: ${stack}`,
102
+ ``,
103
+ `## Intake artifact`,
104
+ "```json",
105
+ JSON.stringify(intake, null, 2),
106
+ "```",
107
+ ``,
108
+ `## Scout artifact`,
109
+ "```json",
110
+ JSON.stringify(scout, null, 2),
111
+ "```",
112
+ ];
113
+
114
+ if (discuss) {
115
+ parts.push(``, `## Discuss artifact`, ``, discuss);
116
+ }
117
+
118
+ if (decisions.trim().length > 0) {
119
+ parts.push(``, `## Decisions (JSONL)`, "```", decisions.trim(), "```");
120
+ }
121
+
122
+ parts.push(
123
+ ``,
124
+ `## Your task`,
125
+ `Research the **${stack}** stack for this UltraPlan session. Identify:`,
126
+ `- Technology choices and library recommendations`,
127
+ `- Architecture patterns appropriate for the goal`,
128
+ `- Integration concerns with existing codebase`,
129
+ `- Risk areas and mitigations`,
130
+ ``,
131
+ `Call \`ultraplan_research_record\` with sessionId=${JSON.stringify(ctx.sessionId)} and stack=${JSON.stringify(stack)}.`,
132
+ `Return after the tool call. Do not append a chat summary.`,
133
+ );
134
+
135
+ return parts.join("\n");
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Stage runner
140
+ // ---------------------------------------------------------------------------
141
+
142
+ export class ResearchStage implements StageRunner {
143
+ readonly stage = "research" as const;
144
+
145
+ async isReady(ctx: StageRunnerContext): Promise<boolean> {
146
+ if (!fs.existsSync(getUltraplanAuthoringIntakePath(ctx.paths, ctx.cwd, ctx.sessionId))) {
147
+ return false;
148
+ }
149
+ if (!fs.existsSync(getUltraplanAuthoringScoutPath(ctx.paths, ctx.cwd, ctx.sessionId))) {
150
+ return false;
151
+ }
152
+ const decisionsPath = getUltraplanAuthoringDecisionsPath(ctx.paths, ctx.cwd, ctx.sessionId);
153
+ return decisionsHasAtLeastOneLine(decisionsPath);
154
+ }
155
+
156
+ async isComplete(ctx: StageRunnerContext): Promise<boolean> {
157
+ // SUMMARY.md is the canonical completion gate.
158
+ if (
159
+ !fs.existsSync(
160
+ getUltraplanAuthoringResearchSummaryPath(ctx.paths, ctx.cwd, ctx.sessionId),
161
+ )
162
+ ) {
163
+ return false;
164
+ }
165
+ // Every applicable stack must also have its per-stack artifact.
166
+ const intakeResult = loadIntakeArtifact(ctx.paths, ctx.cwd, ctx.sessionId);
167
+ if (!intakeResult.ok) return false;
168
+ const applicableStacks = getApplicableStacks(intakeResult.value);
169
+ return applicableStacks.every((stack) =>
170
+ fs.existsSync(
171
+ getUltraplanAuthoringResearchStackPath(ctx.paths, ctx.cwd, ctx.sessionId, stack),
172
+ ),
173
+ );
174
+ }
175
+
176
+ async run(ctx: StageRunnerContext): Promise<StageRunResult> {
177
+ if (!(await this.isReady(ctx))) {
178
+ return {
179
+ status: "failed",
180
+ stage: this.stage,
181
+ artifactPaths: [],
182
+ error:
183
+ "RESEARCH requires intake.json, scout.json, and at least one decision in decisions.jsonl.",
184
+ };
185
+ }
186
+
187
+ if (await this.isComplete(ctx)) {
188
+ return {
189
+ status: "skipped",
190
+ stage: this.stage,
191
+ artifactPaths: [],
192
+ details: { reason: "all research artifacts already exist" },
193
+ };
194
+ }
195
+
196
+ // Read upstream artifacts.
197
+ const intakeResult = loadIntakeArtifact(ctx.paths, ctx.cwd, ctx.sessionId);
198
+ if (!intakeResult.ok) {
199
+ return {
200
+ status: "failed",
201
+ stage: this.stage,
202
+ artifactPaths: [],
203
+ error: `RESEARCH could not read intake artifact: ${intakeResult.error.message}`,
204
+ };
205
+ }
206
+
207
+ const scoutResult = loadScoutArtifact(ctx.paths, ctx.cwd, ctx.sessionId);
208
+ if (!scoutResult.ok) {
209
+ return {
210
+ status: "failed",
211
+ stage: this.stage,
212
+ artifactPaths: [],
213
+ error: `RESEARCH could not read scout artifact: ${scoutResult.error.message}`,
214
+ };
215
+ }
216
+
217
+ const discussResult = loadDiscussArtifact(ctx.paths, ctx.cwd, ctx.sessionId);
218
+ const discuss: string | null = discussResult.ok ? discussResult.value : null;
219
+
220
+ const decisionsPath = getUltraplanAuthoringDecisionsPath(ctx.paths, ctx.cwd, ctx.sessionId);
221
+ const decisions = fs.existsSync(decisionsPath)
222
+ ? fs.readFileSync(decisionsPath, "utf8")
223
+ : "";
224
+
225
+ const applicableStacks = getApplicableStacks(intakeResult.value);
226
+ const notApplicableStacks = ULTRAPLAN_STACKS.filter((s) => !applicableStacks.includes(s));
227
+
228
+ // Skip-stack invariant: purge stale research artifacts for non-applicable stacks.
229
+ for (const stack of notApplicableStacks) {
230
+ deleteResearchStackArtifact(ctx.paths, ctx.cwd, ctx.sessionId, stack);
231
+ }
232
+
233
+ // Mark running before spawning so resume can find us mid-flight.
234
+ saveAuthoringState(ctx.paths, ctx.cwd, ctx.sessionId, {
235
+ pipeline: "multi-stage",
236
+ stage: this.stage,
237
+ stageStatus: "running",
238
+ iteration: 1,
239
+ stallReentryCount: 0,
240
+ artifacts: {
241
+ intake: "authoring/intake.json",
242
+ scout: "authoring/scout.json",
243
+ },
244
+ blocker: null,
245
+ startedAt: nowIso(ctx),
246
+ updatedAt: nowIso(ctx),
247
+ });
248
+
249
+ const slotBinding = resolveAuthoringSlot("researcher", ctx.paths, ctx.cwd);
250
+
251
+ // Fan out: one researcher per applicable stack, all in parallel.
252
+ await Promise.all(
253
+ applicableStacks.map(async (stack) => {
254
+ const resolvedModel =
255
+ ctx.modelOverride ??
256
+ resolveAuthoringSlotModel("researcher", stack, ctx.modelConfig, modelRegistry, {
257
+ getModelForRole: (role) => ctx.platform.getModelForRole?.(role) ?? null,
258
+ getCurrentModel: () => ctx.platform.getCurrentModel?.() ?? "unknown",
259
+ });
260
+
261
+ const sessionOpts: Record<string, unknown> = {
262
+ cwd: ctx.cwd,
263
+ agentDisplayName: buildAgentDisplayName(this.stage, stack),
264
+ agentId: `ultraplan-authoring-${this.stage}-${stack}-${ctx.sessionId}`,
265
+ };
266
+ if (resolvedModel.model) sessionOpts.model = resolvedModel.model;
267
+ if (resolvedModel.thinkingLevel) sessionOpts.thinkingLevel = resolvedModel.thinkingLevel;
268
+
269
+ const agentSession = await ctx.platform.createAgentSession(sessionOpts as never);
270
+ const assignment = [
271
+ slotBinding.definition.prompt.trim(),
272
+ "",
273
+ buildResearchAssignment(
274
+ ctx,
275
+ stack,
276
+ intakeResult.value,
277
+ scoutResult.value,
278
+ discuss,
279
+ decisions,
280
+ ),
281
+ ].join("\n");
282
+
283
+ try {
284
+ await agentSession.prompt(assignment, { expandPromptTemplates: false });
285
+ } finally {
286
+ await agentSession.dispose();
287
+ }
288
+ }),
289
+ );
290
+
291
+ // Verify every applicable stack produced its artifact.
292
+ const missingStacks = applicableStacks.filter(
293
+ (stack) =>
294
+ !fs.existsSync(
295
+ getUltraplanAuthoringResearchStackPath(ctx.paths, ctx.cwd, ctx.sessionId, stack),
296
+ ),
297
+ );
298
+
299
+ if (missingStacks.length > 0) {
300
+ const blocker: UltraPlanBlocker = {
301
+ code: "research-incomplete",
302
+ message: `Research artifacts missing for stacks: ${missingStacks.join(", ")}`,
303
+ scope: "session",
304
+ affected: { stack: null, domainId: null, level: null, scenarioId: null },
305
+ recoverable: true,
306
+ recoveryMode: "retry",
307
+ nextAction:
308
+ "Re-run the research stage; the researcher agent for the affected stacks must call ultraplan_research_record.",
309
+ retryable: true,
310
+ detectedAt: nowIso(ctx),
311
+ details: { missingStacks },
312
+ };
313
+
314
+ saveAuthoringState(ctx.paths, ctx.cwd, ctx.sessionId, {
315
+ pipeline: "multi-stage",
316
+ stage: this.stage,
317
+ stageStatus: "blocked",
318
+ iteration: 1,
319
+ stallReentryCount: 0,
320
+ artifacts: {
321
+ intake: "authoring/intake.json",
322
+ scout: "authoring/scout.json",
323
+ },
324
+ blocker,
325
+ startedAt: nowIso(ctx),
326
+ updatedAt: nowIso(ctx),
327
+ });
328
+
329
+ appendPipelineLog(ctx.paths, ctx.cwd, ctx.sessionId, {
330
+ recordedAt: nowIso(ctx),
331
+ stage: this.stage,
332
+ stageStatus: toManifestStageStatus("blocked"),
333
+ iteration: 1,
334
+ summary: `Research incomplete: missing artifacts for ${missingStacks.join(", ")}`,
335
+ details: { missingStacks },
336
+ });
337
+
338
+ return {
339
+ status: "blocked",
340
+ stage: this.stage,
341
+ artifactPaths: [],
342
+ blocker,
343
+ };
344
+ }
345
+
346
+ // Build deterministic SUMMARY.md — first ~10 lines of each applicable stack's artifact.
347
+ const summaryParts: string[] = [];
348
+ for (const stack of applicableStacks) {
349
+ const loaded = loadResearchStackArtifact(ctx.paths, ctx.cwd, ctx.sessionId, stack);
350
+ if (loaded.ok) {
351
+ const excerpt = loaded.value.split("\n").slice(0, 10).join("\n");
352
+ summaryParts.push(`## ${stack}\n\n${excerpt}\n`);
353
+ }
354
+ }
355
+ saveResearchSummary(ctx.paths, ctx.cwd, ctx.sessionId, summaryParts.join("\n"));
356
+
357
+ const artifactPaths: string[] = [
358
+ ...applicableStacks.map((s) => `authoring/research/${s}.md`),
359
+ "authoring/research/SUMMARY.md",
360
+ ];
361
+
362
+ const researchRefs: { stack: UltraPlanStackId; path: string }[] = applicableStacks.map(
363
+ (s) => ({ stack: s, path: `authoring/research/${s}.md` }),
364
+ );
365
+
366
+ appendPipelineLog(ctx.paths, ctx.cwd, ctx.sessionId, {
367
+ recordedAt: nowIso(ctx),
368
+ stage: this.stage,
369
+ stageStatus: toManifestStageStatus("completed"),
370
+ iteration: 1,
371
+ summary: `research completed for stacks: ${applicableStacks.join(", ")}`,
372
+ details: { stacks: applicableStacks },
373
+ });
374
+
375
+ saveAuthoringState(ctx.paths, ctx.cwd, ctx.sessionId, {
376
+ pipeline: "multi-stage",
377
+ stage: this.stage,
378
+ stageStatus: "done",
379
+ iteration: 1,
380
+ stallReentryCount: 0,
381
+ artifacts: {
382
+ intake: "authoring/intake.json",
383
+ scout: "authoring/scout.json",
384
+ research: researchRefs,
385
+ researchSummary: "authoring/research/SUMMARY.md",
386
+ },
387
+ blocker: null,
388
+ startedAt: nowIso(ctx),
389
+ updatedAt: nowIso(ctx),
390
+ });
391
+
392
+ return {
393
+ status: "completed",
394
+ stage: this.stage,
395
+ artifactPaths,
396
+ details: { stacks: applicableStacks },
397
+ };
398
+ }
399
+ }