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,510 @@
1
+ /**
2
+ * Round-trip-stable markdown <-> authored.json serializer for the synth-stage `$EDITOR`
3
+ * gate.
4
+ *
5
+ * Design: the markdown surfaces only the **editable** fields (frontmatter + scenario titles,
6
+ * steps, dependencies, level, status). Non-editable fields (agent slot bindings, proofs,
7
+ * progress summaries, review gates) are preserved from the original draft JSON via the
8
+ * `applyAuthoredPatch` helper. This lets the user re-shape the plan without having to
9
+ * understand the full schema, and keeps the planner's slot/proof choices intact unless the
10
+ * user explicitly re-runs the planner.
11
+ *
12
+ * Format:
13
+ *
14
+ * ---
15
+ * sessionId: <id>
16
+ * title: <title>
17
+ * goal: <goal>
18
+ * createdAt: <iso>
19
+ * updatedAt: <iso>
20
+ * ---
21
+ * ## Stack: <stack>
22
+ *
23
+ * - applicability: applicable
24
+ *
25
+ * ### Domain: <name> (id=<domain-id>)
26
+ *
27
+ * #### Scenario: <title> (id=<id>) [level=<unit|integration|e2e>]
28
+ *
29
+ * - status: planned
30
+ * - steps:
31
+ * - step 1
32
+ * - step 2
33
+ * - dependencies:
34
+ * - other-id
35
+ *
36
+ * Comments (`<!-- ... -->`) are tolerated and ignored on parse so we can prepend error
37
+ * annotations to a malformed file without breaking the round-trip.
38
+ */
39
+
40
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
41
+
42
+ import type {
43
+ UltraPlanApplicability,
44
+ UltraPlanAuthoredArtifact,
45
+ UltraPlanScenarioLevel,
46
+ UltraPlanScenarioStatus,
47
+ UltraPlanStackId,
48
+ } from "../../types.js";
49
+ import {
50
+ validateUltraPlanAuthoredArtifact,
51
+ ULTRAPLAN_LEVELS,
52
+ ULTRAPLAN_STACKS,
53
+ } from "../contracts.js";
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Public types
57
+ // ---------------------------------------------------------------------------
58
+
59
+ export interface AuthoredMarkdownParseError {
60
+ line: number | null;
61
+ message: string;
62
+ }
63
+
64
+ /**
65
+ * Editable slice of an authored artifact \u2014 the fields the user can change via `$EDITOR`.
66
+ * Everything not in this struct (agent slots, proofs, progress, review gates, status of
67
+ * stacks/domains) is preserved verbatim from the original draft.
68
+ */
69
+ export interface AuthoredEditablePatch {
70
+ sessionId: string;
71
+ title: string;
72
+ goal: string;
73
+ createdAt: string;
74
+ updatedAt: string;
75
+ stacks: AuthoredEditableStack[];
76
+ }
77
+
78
+ export interface AuthoredEditableStack {
79
+ stack: UltraPlanStackId;
80
+ applicability: UltraPlanApplicability;
81
+ domains: AuthoredEditableDomain[];
82
+ }
83
+
84
+ export interface AuthoredEditableDomain {
85
+ id: string;
86
+ name: string;
87
+ scenarios: AuthoredEditableScenario[];
88
+ }
89
+
90
+ export interface AuthoredEditableScenario {
91
+ id: string;
92
+ title: string;
93
+ level: UltraPlanScenarioLevel;
94
+ status: UltraPlanScenarioStatus;
95
+ steps: string[];
96
+ dependencies: string[];
97
+ }
98
+
99
+ export type AuthoredMarkdownParseResult =
100
+ | { ok: true; patch: AuthoredEditablePatch }
101
+ | { ok: false; errors: AuthoredMarkdownParseError[] };
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Serialization
105
+ // ---------------------------------------------------------------------------
106
+
107
+ export function serializeAuthoredToMarkdown(authored: UltraPlanAuthoredArtifact): string {
108
+ const lines: string[] = [];
109
+ lines.push("---");
110
+ lines.push(stringifyYaml({
111
+ sessionId: authored.sessionId,
112
+ title: authored.title,
113
+ goal: authored.goal,
114
+ createdAt: authored.createdAt,
115
+ updatedAt: authored.updatedAt,
116
+ }).trimEnd());
117
+ lines.push("---");
118
+ lines.push("");
119
+
120
+ for (const stack of authored.stacks) {
121
+ lines.push(`## Stack: ${stack.stack}`);
122
+ lines.push("");
123
+ lines.push(`- applicability: ${stack.applicability}`);
124
+ lines.push("");
125
+ for (const domain of stack.domains) {
126
+ lines.push(`### Domain: ${domain.name} (id=${domain.id})`);
127
+ lines.push("");
128
+ const allScenarios = [...domain.unit, ...domain.integration, ...domain.e2e];
129
+ for (const scenario of allScenarios) {
130
+ lines.push(`#### Scenario: ${scenario.title} (id=${scenario.id}) [level=${scenario.level}]`);
131
+ lines.push("");
132
+ lines.push(`- status: ${scenario.status}`);
133
+ lines.push(`- steps:${scenario.steps.length === 0 ? " []" : ""}`);
134
+ for (const step of scenario.steps) lines.push(` - ${step}`);
135
+ const deps = scenario.dependencies ?? [];
136
+ lines.push(`- dependencies:${deps.length === 0 ? " []" : ""}`);
137
+ for (const dep of deps) lines.push(` - ${dep}`);
138
+ lines.push("");
139
+ }
140
+ }
141
+ }
142
+
143
+ return lines.join("\n").trimEnd() + "\n";
144
+ }
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // Parsing
148
+ // ---------------------------------------------------------------------------
149
+
150
+ const STACK_HEADING = /^##\s+Stack:\s+(.+)$/;
151
+ const DOMAIN_HEADING = /^###\s+Domain:\s+(.+?)\s*\(id=([^)]+)\)\s*$/;
152
+ const SCENARIO_HEADING = /^####\s+Scenario:\s+(.+?)\s*\(id=([^)]+)\)\s*\[level=(unit|integration|e2e)\]\s*$/;
153
+ const FRONTMATTER_DELIM = /^---\s*$/;
154
+
155
+ interface ListContext {
156
+ key: "steps" | "dependencies";
157
+ items: string[];
158
+ }
159
+
160
+ interface ParserState {
161
+ errors: AuthoredMarkdownParseError[];
162
+ fmRaw: string[];
163
+ stacks: AuthoredEditableStack[];
164
+ currentStack: AuthoredEditableStack | null;
165
+ currentDomain: AuthoredEditableDomain | null;
166
+ currentScenario: AuthoredEditableScenario | null;
167
+ list: ListContext | null;
168
+ }
169
+
170
+ function pushError(state: ParserState, line: number | null, message: string) {
171
+ state.errors.push({ line, message });
172
+ }
173
+
174
+ function flushList(state: ParserState) {
175
+ if (!state.list || !state.currentScenario) return;
176
+ if (state.list.key === "steps") state.currentScenario.steps = state.list.items;
177
+ else state.currentScenario.dependencies = state.list.items;
178
+ state.list = null;
179
+ }
180
+
181
+ function applyScenarioField(state: ParserState, key: string, value: string, line: number) {
182
+ const s = state.currentScenario!;
183
+ switch (key) {
184
+ case "status":
185
+ s.status = value.trim() as UltraPlanScenarioStatus;
186
+ return;
187
+ case "steps":
188
+ case "dependencies": {
189
+ flushList(state);
190
+ if (value.trim() === "[]") {
191
+ if (key === "steps") s.steps = [];
192
+ else s.dependencies = [];
193
+ state.list = null;
194
+ } else {
195
+ state.list = { key, items: [] };
196
+ }
197
+ return;
198
+ }
199
+ default:
200
+ pushError(state, line, `Unknown scenario field: ${key}`);
201
+ }
202
+ }
203
+
204
+ function applyStackField(state: ParserState, key: string, value: string, line: number) {
205
+ const s = state.currentStack!;
206
+ if (key === "applicability") {
207
+ s.applicability = value.trim() as UltraPlanApplicability;
208
+ return;
209
+ }
210
+ // Tolerate unknown stack-level fields (status, etc.) — they are non-editable and round-trip
211
+ // through the original draft via applyAuthoredPatch.
212
+ void line;
213
+ }
214
+
215
+ export function parseAuthoredFromMarkdown(markdown: string): AuthoredMarkdownParseResult {
216
+ const state: ParserState = {
217
+ errors: [],
218
+ fmRaw: [],
219
+ stacks: [],
220
+ currentStack: null,
221
+ currentDomain: null,
222
+ currentScenario: null,
223
+ list: null,
224
+ };
225
+
226
+ const rawLines = markdown.split(/\r?\n/);
227
+ let i = 0;
228
+
229
+ // Skip leading comment annotations and blank lines.
230
+ while (i < rawLines.length) {
231
+ const t = rawLines[i]!.trim();
232
+ if (t === "" || /^<!--[\s\S]*-->$/.test(t)) {
233
+ i += 1;
234
+ continue;
235
+ }
236
+ break;
237
+ }
238
+
239
+ if (i >= rawLines.length || !FRONTMATTER_DELIM.test(rawLines[i]!)) {
240
+ pushError(state, i + 1, "Missing YAML frontmatter; expected --- on the first non-blank, non-comment line.");
241
+ return { ok: false, errors: state.errors };
242
+ }
243
+ i += 1;
244
+ const fmStartIndex = i;
245
+ while (i < rawLines.length && !FRONTMATTER_DELIM.test(rawLines[i]!)) {
246
+ state.fmRaw.push(rawLines[i]!);
247
+ i += 1;
248
+ }
249
+ if (i >= rawLines.length) {
250
+ pushError(state, fmStartIndex, "Frontmatter not terminated by closing ---.");
251
+ return { ok: false, errors: state.errors };
252
+ }
253
+ i += 1;
254
+
255
+ let frontmatter: Record<string, unknown> = {};
256
+ try {
257
+ const parsed = parseYaml(state.fmRaw.join("\n"));
258
+ frontmatter = (parsed && typeof parsed === "object") ? (parsed as Record<string, unknown>) : {};
259
+ } catch (error) {
260
+ pushError(state, 1, `Frontmatter YAML parse error: ${error instanceof Error ? error.message : String(error)}`);
261
+ return { ok: false, errors: state.errors };
262
+ }
263
+
264
+ for (; i < rawLines.length; i += 1) {
265
+ const raw = rawLines[i]!;
266
+ const lineNo = i + 1;
267
+ const trimmed = raw.trim();
268
+
269
+ if (trimmed === "" || /^<!--[\s\S]*-->$/.test(trimmed)) continue;
270
+
271
+ const stackMatch = STACK_HEADING.exec(raw);
272
+ if (stackMatch) {
273
+ flushList(state);
274
+ const stackId = stackMatch[1]!.trim() as UltraPlanStackId;
275
+ if (!ULTRAPLAN_STACKS.includes(stackId)) {
276
+ pushError(state, lineNo, `Unknown stack: ${stackId}`);
277
+ continue;
278
+ }
279
+ const next: AuthoredEditableStack = { stack: stackId, applicability: "applicable", domains: [] };
280
+ state.stacks.push(next);
281
+ state.currentStack = next;
282
+ state.currentDomain = null;
283
+ state.currentScenario = null;
284
+ state.list = null;
285
+ continue;
286
+ }
287
+
288
+ const domainMatch = DOMAIN_HEADING.exec(raw);
289
+ if (domainMatch) {
290
+ flushList(state);
291
+ if (!state.currentStack) {
292
+ pushError(state, lineNo, "Domain heading without a parent ## Stack heading.");
293
+ continue;
294
+ }
295
+ const domain: AuthoredEditableDomain = {
296
+ id: domainMatch[2]!.trim(),
297
+ name: domainMatch[1]!.trim(),
298
+ scenarios: [],
299
+ };
300
+ state.currentStack.domains.push(domain);
301
+ state.currentDomain = domain;
302
+ state.currentScenario = null;
303
+ state.list = null;
304
+ continue;
305
+ }
306
+
307
+ const scenarioMatch = SCENARIO_HEADING.exec(raw);
308
+ if (scenarioMatch) {
309
+ flushList(state);
310
+ if (!state.currentStack || !state.currentDomain) {
311
+ pushError(state, lineNo, "Scenario heading without parent stack and domain.");
312
+ continue;
313
+ }
314
+ const level = scenarioMatch[3]! as UltraPlanScenarioLevel;
315
+ if (!ULTRAPLAN_LEVELS.includes(level)) {
316
+ pushError(state, lineNo, `Unknown scenario level: ${level}`);
317
+ continue;
318
+ }
319
+ const scenario: AuthoredEditableScenario = {
320
+ id: scenarioMatch[2]!.trim(),
321
+ title: scenarioMatch[1]!.trim(),
322
+ level,
323
+ status: "planned",
324
+ steps: [],
325
+ dependencies: [],
326
+ };
327
+ state.currentDomain.scenarios.push(scenario);
328
+ state.currentScenario = scenario;
329
+ state.list = null;
330
+ continue;
331
+ }
332
+
333
+ if (/^\s+-\s+/.test(raw) && state.list && state.currentScenario) {
334
+ const item = raw.replace(/^\s+-\s+/, "").trim();
335
+ state.list.items.push(item);
336
+ continue;
337
+ }
338
+
339
+ const fieldMatch = /^-\s+([A-Za-z][\w-]*):\s*(.*)$/.exec(raw);
340
+ if (fieldMatch) {
341
+ const key = fieldMatch[1]!;
342
+ const value = fieldMatch[2]!;
343
+ flushList(state);
344
+ if (state.currentScenario) {
345
+ applyScenarioField(state, key, value, lineNo);
346
+ } else if (state.currentStack && !state.currentDomain) {
347
+ applyStackField(state, key, value, lineNo);
348
+ }
349
+ continue;
350
+ }
351
+ }
352
+ flushList(state);
353
+
354
+ if (state.errors.length > 0) {
355
+ return { ok: false, errors: state.errors };
356
+ }
357
+
358
+ const sessionId = String(frontmatter.sessionId ?? "");
359
+ if (!sessionId) {
360
+ pushError(state, null, "Frontmatter is missing sessionId.");
361
+ return { ok: false, errors: state.errors };
362
+ }
363
+
364
+ return {
365
+ ok: true,
366
+ patch: {
367
+ sessionId,
368
+ title: String(frontmatter.title ?? ""),
369
+ goal: String(frontmatter.goal ?? ""),
370
+ createdAt: String(frontmatter.createdAt ?? ""),
371
+ updatedAt: String(frontmatter.updatedAt ?? ""),
372
+ stacks: state.stacks,
373
+ },
374
+ };
375
+ }
376
+
377
+ // ---------------------------------------------------------------------------
378
+ // Patch application
379
+ // ---------------------------------------------------------------------------
380
+
381
+ /**
382
+ * Apply an editable patch onto an existing draft authored artifact, then validate the
383
+ * result. Non-editable fields (agentSlots, proofs, progress, review gates, etc.) are
384
+ * preserved from `original`; the patch only touches the visible-in-markdown subset.
385
+ *
386
+ * Scenarios in the patch are matched to scenarios in the original by id; any scenario whose
387
+ * id is not in the original is skipped (its agent-slot bindings would be undefined). Any
388
+ * original scenario whose id is missing from the patch is dropped (the user removed it).
389
+ *
390
+ * Stacks and domains follow the same id-matching rule; new stacks/domains in the patch with
391
+ * no original counterpart cannot be applied because they lack agent-slot bindings, and we
392
+ * surface that as a validation error rather than silently inserting empty data.
393
+ */
394
+ export function applyAuthoredPatch(
395
+ original: UltraPlanAuthoredArtifact,
396
+ patch: AuthoredEditablePatch,
397
+ ): { ok: true; value: UltraPlanAuthoredArtifact } | { ok: false; errors: string[] } {
398
+ if (patch.sessionId !== original.sessionId) {
399
+ return { ok: false, errors: [`Patch sessionId ${patch.sessionId} does not match original ${original.sessionId}`] };
400
+ }
401
+
402
+ const originalStacksById = new Map(original.stacks.map((s) => [s.stack, s]));
403
+ const newStacks = [];
404
+ const errors: string[] = [];
405
+
406
+ for (const stackPatch of patch.stacks) {
407
+ const originalStack = originalStacksById.get(stackPatch.stack);
408
+ if (!originalStack) {
409
+ errors.push(
410
+ `Stack ${stackPatch.stack} appears in the edited markdown but not in the original draft; new stacks cannot be added through the editor.`,
411
+ );
412
+ continue;
413
+ }
414
+
415
+ const originalDomainsById = new Map(originalStack.domains.map((d) => [d.id, d]));
416
+ const newDomains = [];
417
+ for (const domainPatch of stackPatch.domains) {
418
+ const originalDomain = originalDomainsById.get(domainPatch.id);
419
+ if (!originalDomain) {
420
+ errors.push(
421
+ `Domain ${domainPatch.id} (stack ${stackPatch.stack}) appears in the edited markdown but not in the original draft.`,
422
+ );
423
+ continue;
424
+ }
425
+ const originalScenariosById = new Map<string, typeof originalDomain.unit[number]>();
426
+ for (const s of [...originalDomain.unit, ...originalDomain.integration, ...originalDomain.e2e]) {
427
+ originalScenariosById.set(s.id, s);
428
+ }
429
+
430
+ const unit = [];
431
+ const integration = [];
432
+ const e2e = [];
433
+ for (const scenarioPatch of domainPatch.scenarios) {
434
+ const originalScenario = originalScenariosById.get(scenarioPatch.id);
435
+ if (!originalScenario) {
436
+ errors.push(
437
+ `Scenario ${scenarioPatch.id} (domain ${domainPatch.id}) appears in the edited markdown but not in the original draft.`,
438
+ );
439
+ continue;
440
+ }
441
+ const merged = {
442
+ ...originalScenario,
443
+ title: scenarioPatch.title,
444
+ level: scenarioPatch.level,
445
+ status: scenarioPatch.status,
446
+ steps: scenarioPatch.steps,
447
+ dependencies: scenarioPatch.dependencies,
448
+ };
449
+ if (merged.level === "unit") unit.push(merged);
450
+ else if (merged.level === "integration") integration.push(merged);
451
+ else e2e.push(merged);
452
+ }
453
+
454
+ newDomains.push({
455
+ ...originalDomain,
456
+ name: domainPatch.name,
457
+ unit,
458
+ integration,
459
+ e2e,
460
+ });
461
+ }
462
+
463
+ newStacks.push({
464
+ ...originalStack,
465
+ applicability: stackPatch.applicability,
466
+ domains: newDomains,
467
+ });
468
+ }
469
+
470
+ if (errors.length > 0) {
471
+ return { ok: false, errors };
472
+ }
473
+
474
+ const next: UltraPlanAuthoredArtifact = {
475
+ ...original,
476
+ title: patch.title,
477
+ goal: patch.goal,
478
+ updatedAt: patch.updatedAt || original.updatedAt,
479
+ stacks: newStacks,
480
+ };
481
+
482
+ const validation = validateUltraPlanAuthoredArtifact(next);
483
+ if (!validation.ok) {
484
+ return { ok: false, errors: validation.errors };
485
+ }
486
+ return { ok: true, value: validation.value };
487
+ }
488
+
489
+ // ---------------------------------------------------------------------------
490
+ // Error annotation
491
+ // ---------------------------------------------------------------------------
492
+
493
+ export function annotateParseErrors(markdown: string, errors: AuthoredMarkdownParseError[]): string {
494
+ if (errors.length === 0) return markdown;
495
+ const header: string[] = [];
496
+ header.push("<!-- AUTHORED EDIT ERRORS \u2014 fix the issues below, then save and close to re-validate. -->");
497
+ for (const err of errors) {
498
+ const prefix = err.line !== null ? `[line ${err.line}] ` : "";
499
+ header.push(`<!-- ${prefix}${err.message} -->`);
500
+ }
501
+ header.push("");
502
+ return `${header.join("\n")}\n${markdown}`;
503
+ }
504
+
505
+ export function stripParseErrorAnnotations(markdown: string): string {
506
+ return markdown.replace(
507
+ /^(<!--\s*AUTHORED EDIT ERRORS[\s\S]*?-->\n)((<!--[\s\S]*?-->\n)*)\n?/,
508
+ "",
509
+ );
510
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Per-action-id model resolution for the multi-stage authoring pipeline.
3
+ *
4
+ * The pipeline runs every stage in a fresh `platform.createAgentSession`. Each spawned session
5
+ * needs a model identifier and (optionally) a thinking level. Selection follows the standard
6
+ * supipowers four-tier resolution implemented by `resolveModelForAction`:
7
+ * 1. `model.json` per-action override (`actions["ultraplan.authoring.<slot>"]`)
8
+ * 2. `model.json#default` (the workspace-wide supipowers default)
9
+ * 3. The platform's harness role hint (architect / research / review / default)
10
+ * 4. The main session model
11
+ *
12
+ * Stack-parameterized researchers each get their own action id (`...researcher.frontend`,
13
+ * `...researcher.backend`, `...researcher.infrastructure`), so per-stack overrides are a flat
14
+ * lookup in `model.json`. We register every action id at module load so the resolver and any
15
+ * tooling that introspects the registry sees them on first import.
16
+ */
17
+
18
+ import type {
19
+ ModelConfig,
20
+ ResolvedModel,
21
+ UltraPlanAuthoringSlotName,
22
+ UltraPlanStackId,
23
+ } from "../../types.js";
24
+ import {
25
+ resolveModelForAction,
26
+ type ModelPlatformBridge,
27
+ } from "../../config/model-resolver.js";
28
+ import type { ModelActionRegistry } from "../../config/model-registry.js";
29
+ import { modelRegistry } from "../../config/model-registry-instance.js";
30
+ import { ULTRAPLAN_STACKS } from "../contracts.js";
31
+
32
+ /** Action-id namespace for the authoring pipeline. Centralized so callers do not inline. */
33
+ export const ULTRAPLAN_AUTHORING_ACTION_NAMESPACE = "ultraplan.authoring";
34
+
35
+ /**
36
+ * Returns the action id for an authoring slot, optionally parameterised by stack for the
37
+ * researcher slot.
38
+ *
39
+ * - For non-researcher slots, the stack hint is ignored.
40
+ * - For the researcher slot with no stack hint, the unparameterised id is returned (used by
41
+ * tooling that needs to address "the family of researcher actions" in the abstract). At
42
+ * resolution time, callers should always pass a stack hint.
43
+ */
44
+ export function getAuthoringActionId(
45
+ slot: UltraPlanAuthoringSlotName,
46
+ stackHint: UltraPlanStackId | null = null,
47
+ ): string {
48
+ if (slot === "researcher" && stackHint !== null) {
49
+ return `${ULTRAPLAN_AUTHORING_ACTION_NAMESPACE}.researcher.${stackHint}`;
50
+ }
51
+ return `${ULTRAPLAN_AUTHORING_ACTION_NAMESPACE}.${slot}`;
52
+ }
53
+
54
+ /**
55
+ * Resolve the model and thinking level for an authoring slot. Mirrors `resolveModelForAction`
56
+ * exactly — this is a thin wrapper that builds the correct action id and delegates so the
57
+ * four-tier fallback semantics stay identical to every other resolver call site.
58
+ *
59
+ * Use this in stage runners just before `createAgentSession`:
60
+ *
61
+ * const { model, thinkingLevel } = resolveAuthoringSlotModel(
62
+ * "researcher", "backend", config, modelRegistry, platformBridge,
63
+ * );
64
+ * const session = platform.createAgentSession({ ..., model, thinkingLevel });
65
+ */
66
+ export function resolveAuthoringSlotModel(
67
+ slot: UltraPlanAuthoringSlotName,
68
+ stackHint: UltraPlanStackId | null,
69
+ config: ModelConfig,
70
+ registry: ModelActionRegistry,
71
+ platform: ModelPlatformBridge,
72
+ ): ResolvedModel {
73
+ const actionId = getAuthoringActionId(slot, stackHint);
74
+ return resolveModelForAction(actionId, registry, config, platform);
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Action registration. Runs at module load — every `import "..."` of this file ensures the
79
+ // authoring action ids are visible to the registry. Cheap to do up-front because each slot
80
+ // is registered exactly once (the registry is idempotent for repeated `register` calls with
81
+ // the same id; see `model-registry.ts`).
82
+ //
83
+ // Harness role hints map authoring slots to the four canonical harness roles (architect,
84
+ // research, review, default) so users on a stock harness get sensible models without ever
85
+ // editing model.json. Per-stack researchers all share the `research` role hint.
86
+ // ---------------------------------------------------------------------------
87
+
88
+ interface AuthoringActionRegistration {
89
+ id: string;
90
+ label: string;
91
+ harnessRoleHint: string;
92
+ }
93
+
94
+ function buildAuthoringActionRegistrations(): AuthoringActionRegistration[] {
95
+ const fixed: AuthoringActionRegistration[] = [
96
+ {
97
+ id: getAuthoringActionId("intake"),
98
+ label: "UltraPlan authoring · intake",
99
+ harnessRoleHint: "architect",
100
+ },
101
+ {
102
+ id: getAuthoringActionId("scout"),
103
+ label: "UltraPlan authoring · scout",
104
+ harnessRoleHint: "research",
105
+ },
106
+ {
107
+ id: getAuthoringActionId("discoverer"),
108
+ label: "UltraPlan authoring · discover",
109
+ harnessRoleHint: "architect",
110
+ },
111
+ {
112
+ id: getAuthoringActionId("planner"),
113
+ label: "UltraPlan authoring · planner",
114
+ harnessRoleHint: "architect",
115
+ },
116
+ {
117
+ id: getAuthoringActionId("structure-checker"),
118
+ label: "UltraPlan authoring · structure checker",
119
+ harnessRoleHint: "review",
120
+ },
121
+ {
122
+ id: getAuthoringActionId("scope-checker"),
123
+ label: "UltraPlan authoring · scope checker",
124
+ harnessRoleHint: "review",
125
+ },
126
+ {
127
+ id: getAuthoringActionId("tdd-checker"),
128
+ label: "UltraPlan authoring · TDD checker",
129
+ harnessRoleHint: "review",
130
+ },
131
+ ];
132
+
133
+ const researchers: AuthoringActionRegistration[] = ULTRAPLAN_STACKS.map((stack) => ({
134
+ id: getAuthoringActionId("researcher", stack),
135
+ label: `UltraPlan authoring · researcher (${stack})`,
136
+ harnessRoleHint: "research",
137
+ }));
138
+
139
+ return [...fixed, ...researchers];
140
+ }
141
+
142
+ /**
143
+ * Registry of every authoring action id, exposed for tooling and tests. The order is stable
144
+ * (fixed slots first, then researchers in `ULTRAPLAN_STACKS` order) so snapshot tests can
145
+ * match without sorting.
146
+ */
147
+ export const AUTHORING_ACTION_REGISTRATIONS: readonly AuthoringActionRegistration[] = Object.freeze(
148
+ buildAuthoringActionRegistrations(),
149
+ );
150
+
151
+ // Register them once at module load. Node's module cache guarantees this file's body runs at
152
+ // most once per process, so the registry never sees duplicate calls (the registry rejects
153
+ // duplicate ids by design).
154
+ for (const action of AUTHORING_ACTION_REGISTRATIONS) {
155
+ modelRegistry.register({
156
+ id: action.id,
157
+ category: "sub-agent",
158
+ parent: "ultraplan",
159
+ label: action.label,
160
+ harnessRoleHint: action.harnessRoleHint,
161
+ });
162
+ }