supipowers 1.5.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/README.md +14 -8
  2. package/bin/install.mjs +20 -5
  3. package/bin/install.ts +95 -0
  4. package/package.json +8 -4
  5. package/skills/context-mode/SKILL.md +17 -10
  6. package/skills/harness/SKILL.md +94 -0
  7. package/skills/ui-design/SKILL.md +63 -0
  8. package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
  9. package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
  10. package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
  11. package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
  12. package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
  13. package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
  14. package/skills/ultraplan-discover/SKILL.md +96 -0
  15. package/skills/ultraplan-intake/SKILL.md +89 -0
  16. package/skills/ultraplan-research/SKILL.md +129 -0
  17. package/skills/ultraplan-review/SKILL.md +86 -0
  18. package/skills/ultraplan-review-scope/SKILL.md +111 -0
  19. package/skills/ultraplan-review-structure/SKILL.md +120 -0
  20. package/skills/ultraplan-review-tdd/SKILL.md +142 -0
  21. package/skills/ultraplan-scout/SKILL.md +110 -0
  22. package/skills/ultraplan-synthesize/SKILL.md +124 -0
  23. package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
  24. package/src/ai/schema-text.ts +129 -0
  25. package/src/ai/structured-output.ts +274 -0
  26. package/src/ai/template.ts +27 -0
  27. package/src/bootstrap.ts +63 -28
  28. package/src/commands/agents.ts +149 -45
  29. package/src/commands/ai-review.ts +251 -30
  30. package/src/commands/clear.ts +434 -0
  31. package/src/commands/commit.ts +1 -0
  32. package/src/commands/config.ts +242 -44
  33. package/src/commands/context.ts +55 -28
  34. package/src/commands/doctor.ts +234 -6
  35. package/src/commands/fix-pr.ts +306 -131
  36. package/src/commands/generate.ts +111 -21
  37. package/src/commands/memory.ts +192 -0
  38. package/src/commands/model-picker.ts +28 -21
  39. package/src/commands/model.ts +19 -9
  40. package/src/commands/optimize-context.ts +408 -29
  41. package/src/commands/plan.ts +2 -0
  42. package/src/commands/qa.ts +312 -137
  43. package/src/commands/release.ts +259 -76
  44. package/src/commands/review.ts +293 -59
  45. package/src/commands/status.ts +200 -13
  46. package/src/commands/supi.ts +3 -35
  47. package/src/commands/ui-design.ts +394 -0
  48. package/src/commands/ultraplan.ts +1518 -0
  49. package/src/commands/update.ts +86 -0
  50. package/src/config/defaults.ts +62 -0
  51. package/src/config/loader.ts +448 -60
  52. package/src/config/schema.ts +108 -2
  53. package/src/context/optimizer.ts +25 -33
  54. package/src/context/rule-renderer.ts +223 -0
  55. package/src/context/savings.ts +258 -0
  56. package/src/context/startup-check.ts +380 -0
  57. package/src/context/startup-optimizer.ts +355 -0
  58. package/src/context/tokenignore.ts +146 -0
  59. package/src/context-mode/cache-handle.ts +49 -0
  60. package/src/context-mode/cache-preview.ts +71 -0
  61. package/src/context-mode/cache-store.ts +738 -0
  62. package/src/context-mode/compressor.ts +131 -26
  63. package/src/context-mode/dedup.ts +108 -0
  64. package/src/context-mode/detector.ts +35 -4
  65. package/src/context-mode/event-extractor.ts +14 -12
  66. package/src/context-mode/event-store.ts +91 -36
  67. package/src/context-mode/hooks.ts +798 -56
  68. package/src/context-mode/knowledge/store.ts +255 -11
  69. package/src/context-mode/memory-store.ts +325 -0
  70. package/src/context-mode/metrics-recorder.ts +158 -0
  71. package/src/context-mode/metrics-store.ts +765 -0
  72. package/src/context-mode/model.ts +24 -0
  73. package/src/context-mode/processor-keys.ts +29 -0
  74. package/src/context-mode/processors/build.ts +66 -0
  75. package/src/context-mode/processors/docker.ts +57 -0
  76. package/src/context-mode/processors/git.ts +111 -0
  77. package/src/context-mode/processors/json.ts +112 -0
  78. package/src/context-mode/processors/k8s.ts +67 -0
  79. package/src/context-mode/processors/lint.ts +67 -0
  80. package/src/context-mode/processors/log.ts +86 -0
  81. package/src/context-mode/processors/registry.ts +116 -0
  82. package/src/context-mode/processors/test-runner.ts +102 -0
  83. package/src/context-mode/processors/types.ts +20 -0
  84. package/src/context-mode/repomap.ts +400 -0
  85. package/src/context-mode/routing.ts +97 -24
  86. package/src/context-mode/sandbox/runners.ts +5 -1
  87. package/src/context-mode/snapshot-builder.ts +106 -11
  88. package/src/context-mode/source-hash.ts +173 -0
  89. package/src/context-mode/tool-name.ts +11 -0
  90. package/src/context-mode/tools.ts +654 -22
  91. package/src/context-mode/web/fetcher.ts +31 -12
  92. package/src/debug/logger.ts +2 -1
  93. package/src/deps/registry.ts +1 -1
  94. package/src/discipline/failure-summarizer.ts +170 -0
  95. package/src/discipline/failure-taxonomy.ts +131 -0
  96. package/src/discipline/workflow-invariants.ts +125 -0
  97. package/src/discovery/index.ts +31 -0
  98. package/src/discovery/lsp.ts +87 -0
  99. package/src/discovery/rank.ts +144 -0
  100. package/src/discovery/sources.ts +89 -0
  101. package/src/discovery/workflow.ts +87 -0
  102. package/src/docs/contracts.ts +39 -0
  103. package/src/docs/drift.ts +117 -87
  104. package/src/fix-pr/assessment.ts +200 -0
  105. package/src/fix-pr/contracts.ts +47 -0
  106. package/src/fix-pr/fetch-comments.ts +80 -0
  107. package/src/fix-pr/prompt-builder.ts +58 -40
  108. package/src/fix-pr/scripts/exec.ts +34 -0
  109. package/src/fix-pr/scripts/trigger-review.ts +106 -0
  110. package/src/fix-pr/scripts/wait-and-check.ts +108 -0
  111. package/src/fix-pr/types.ts +4 -0
  112. package/src/git/branch-finish.ts +5 -0
  113. package/src/git/commit-contract.ts +83 -0
  114. package/src/git/commit.ts +121 -184
  115. package/src/git/status.ts +62 -8
  116. package/src/harness/anti_slop/architecture-parser.ts +210 -0
  117. package/src/harness/anti_slop/backend-factory.ts +30 -0
  118. package/src/harness/anti_slop/backend.ts +140 -0
  119. package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
  120. package/src/harness/anti_slop/fallow-adapter.ts +305 -0
  121. package/src/harness/anti_slop/installer.ts +227 -0
  122. package/src/harness/anti_slop/queue.ts +216 -0
  123. package/src/harness/anti_slop/recommend.ts +84 -0
  124. package/src/harness/anti_slop/score.ts +180 -0
  125. package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
  126. package/src/harness/artifacts/agents-md.ts +88 -0
  127. package/src/harness/artifacts/checks-wiring.ts +57 -0
  128. package/src/harness/artifacts/docs-tree.ts +79 -0
  129. package/src/harness/artifacts/lint-configs.ts +136 -0
  130. package/src/harness/artifacts/review-agents.ts +67 -0
  131. package/src/harness/bare-entry.ts +108 -0
  132. package/src/harness/command.ts +1010 -0
  133. package/src/harness/default-agents/design.md +23 -0
  134. package/src/harness/default-agents/discover.md +18 -0
  135. package/src/harness/default-agents/implement.md +24 -0
  136. package/src/harness/default-agents/plan.md +19 -0
  137. package/src/harness/default-agents/research.md +21 -0
  138. package/src/harness/default-agents/validate.md +22 -0
  139. package/src/harness/gc/reporter.ts +28 -0
  140. package/src/harness/gc/runner.ts +136 -0
  141. package/src/harness/hooks/layer-context-inject.ts +155 -0
  142. package/src/harness/hooks/post-session-sweep.ts +130 -0
  143. package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
  144. package/src/harness/hooks/register.ts +118 -0
  145. package/src/harness/model.ts +117 -0
  146. package/src/harness/pipeline.ts +348 -0
  147. package/src/harness/project-paths.ts +235 -0
  148. package/src/harness/stage-runner.ts +107 -0
  149. package/src/harness/stages/design.ts +386 -0
  150. package/src/harness/stages/discover.ts +454 -0
  151. package/src/harness/stages/implement.ts +162 -0
  152. package/src/harness/stages/plan.ts +335 -0
  153. package/src/harness/stages/research.ts +263 -0
  154. package/src/harness/stages/validate.ts +684 -0
  155. package/src/harness/storage.ts +467 -0
  156. package/src/harness/tools.ts +426 -0
  157. package/src/lsp/bridge.ts +56 -95
  158. package/src/lsp/capabilities.ts +108 -0
  159. package/src/lsp/contracts.ts +35 -0
  160. package/src/lsp/detector.ts +8 -12
  161. package/src/markdown-frontmatter.ts +68 -0
  162. package/src/mempalace/bridge.ts +129 -0
  163. package/src/mempalace/config.ts +75 -0
  164. package/src/mempalace/format.ts +163 -0
  165. package/src/mempalace/hooks.ts +370 -0
  166. package/src/mempalace/installer-helper.ts +194 -0
  167. package/src/mempalace/python/mempalace_bridge.py +440 -0
  168. package/src/mempalace/runtime.ts +565 -0
  169. package/src/mempalace/schema.ts +264 -0
  170. package/src/mempalace/session-summary.ts +198 -0
  171. package/src/mempalace/tool.ts +186 -0
  172. package/src/mempalace/uv.ts +256 -0
  173. package/src/migrate/runner.ts +354 -0
  174. package/src/planning/approval-flow.ts +206 -9
  175. package/src/planning/plan-writer-prompt.ts +4 -3
  176. package/src/planning/planning-ask-tool.ts +39 -0
  177. package/src/planning/render-markdown.ts +74 -0
  178. package/src/planning/spec.ts +42 -0
  179. package/src/planning/system-prompt.ts +11 -8
  180. package/src/planning/validate.ts +84 -0
  181. package/src/platform/omp.ts +15 -2
  182. package/src/platform/system-prompt.ts +37 -0
  183. package/src/platform/test-utils.ts +3 -0
  184. package/src/platform/types.ts +6 -1
  185. package/src/qa/config.ts +12 -6
  186. package/src/qa/detect-app-type.ts +13 -6
  187. package/src/qa/matrix.ts +12 -6
  188. package/src/qa/prompt-builder.ts +28 -30
  189. package/src/qa/scripts/dev-server-utils.ts +72 -0
  190. package/src/qa/scripts/run-e2e-tests.ts +226 -0
  191. package/src/qa/scripts/start-dev-server.ts +138 -0
  192. package/src/qa/scripts/stop-dev-server.ts +77 -0
  193. package/src/qa/session.ts +13 -7
  194. package/src/quality/ai-setup.ts +27 -25
  195. package/src/quality/contracts.ts +34 -0
  196. package/src/quality/gates/ai-review.ts +20 -58
  197. package/src/quality/gates/command.ts +249 -46
  198. package/src/quality/review-gates.ts +18 -2
  199. package/src/quality/runner.ts +63 -22
  200. package/src/quality/schemas.ts +37 -2
  201. package/src/quality/setup.ts +96 -16
  202. package/src/release/changelog.ts +1 -1
  203. package/src/release/channels/custom.ts +13 -3
  204. package/src/release/channels/types.ts +5 -0
  205. package/src/release/contracts.ts +90 -0
  206. package/src/release/executor.ts +122 -45
  207. package/src/release/prompt.ts +18 -2
  208. package/src/release/targets.ts +86 -0
  209. package/src/release/version.ts +96 -71
  210. package/src/review/agent-loader.ts +298 -127
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +115 -14
  213. package/src/review/output.ts +12 -139
  214. package/src/review/runner.ts +12 -6
  215. package/src/review/scope.ts +144 -24
  216. package/src/review/types.ts +11 -20
  217. package/src/review/validator.ts +12 -6
  218. package/src/storage/fix-pr-sessions.ts +21 -14
  219. package/src/storage/plans.ts +14 -5
  220. package/src/storage/qa-sessions.ts +25 -19
  221. package/src/storage/reliability-metrics.ts +180 -0
  222. package/src/storage/reports.ts +8 -7
  223. package/src/storage/review-sessions.ts +55 -20
  224. package/src/tool-catalog/active-tool-controller.ts +164 -0
  225. package/src/tool-catalog/active-tool-planner.ts +212 -0
  226. package/src/tool-catalog/tool-groups.ts +102 -0
  227. package/src/types.ts +1401 -5
  228. package/src/ui-design/backend-adapter.ts +78 -0
  229. package/src/ui-design/backends/local-html.ts +82 -0
  230. package/src/ui-design/backends/pencil-mcp.ts +111 -0
  231. package/src/ui-design/components-scanner.ts +124 -0
  232. package/src/ui-design/config.ts +55 -0
  233. package/src/ui-design/pen-scanner.ts +95 -0
  234. package/src/ui-design/pen-selector.ts +72 -0
  235. package/src/ui-design/prompt-builder.ts +73 -0
  236. package/src/ui-design/scanner.ts +136 -0
  237. package/src/ui-design/session.ts +974 -0
  238. package/src/ui-design/system-prompt.ts +312 -0
  239. package/src/ui-design/tokens-scanner.ts +181 -0
  240. package/src/ui-design/types.ts +96 -0
  241. package/src/ultraplan/agent-catalog.ts +522 -0
  242. package/src/ultraplan/authoring/agent-catalog.ts +310 -0
  243. package/src/ultraplan/authoring/authoring-tools.ts +552 -0
  244. package/src/ultraplan/authoring/command-handlers.ts +339 -0
  245. package/src/ultraplan/authoring/markdown.ts +510 -0
  246. package/src/ultraplan/authoring/model.ts +162 -0
  247. package/src/ultraplan/authoring/pipeline.ts +319 -0
  248. package/src/ultraplan/authoring/stage-runner.ts +141 -0
  249. package/src/ultraplan/authoring/stages/approve.ts +249 -0
  250. package/src/ultraplan/authoring/stages/discover.ts +289 -0
  251. package/src/ultraplan/authoring/stages/intake.ts +203 -0
  252. package/src/ultraplan/authoring/stages/research.ts +399 -0
  253. package/src/ultraplan/authoring/stages/review.ts +333 -0
  254. package/src/ultraplan/authoring/stages/scout.ts +188 -0
  255. package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
  256. package/src/ultraplan/authoring/storage.ts +594 -0
  257. package/src/ultraplan/authoring/synth-gate.ts +165 -0
  258. package/src/ultraplan/authoring-draft.ts +653 -0
  259. package/src/ultraplan/authoring-persist.ts +180 -0
  260. package/src/ultraplan/authoring-tool.ts +608 -0
  261. package/src/ultraplan/authoring-wizard.ts +587 -0
  262. package/src/ultraplan/batch/merge.ts +98 -0
  263. package/src/ultraplan/batch/planner.ts +150 -0
  264. package/src/ultraplan/batch/presenter.ts +97 -0
  265. package/src/ultraplan/batch/storage.ts +420 -0
  266. package/src/ultraplan/batch/supervisor.ts +317 -0
  267. package/src/ultraplan/batch/worker.ts +26 -0
  268. package/src/ultraplan/batch/worktree.ts +110 -0
  269. package/src/ultraplan/contracts.ts +1593 -0
  270. package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
  271. package/src/ultraplan/default-agents/authoring/intake.md +12 -0
  272. package/src/ultraplan/default-agents/authoring/planner.md +12 -0
  273. package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
  274. package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
  275. package/src/ultraplan/default-agents/authoring/scout.md +12 -0
  276. package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
  277. package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
  278. package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
  279. package/src/ultraplan/default-agents/backend-executor.md +10 -0
  280. package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
  281. package/src/ultraplan/default-agents/backend-tester.md +10 -0
  282. package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
  283. package/src/ultraplan/default-agents/frontend-executor.md +10 -0
  284. package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
  285. package/src/ultraplan/default-agents/frontend-tester.md +10 -0
  286. package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
  287. package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
  288. package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
  289. package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
  290. package/src/ultraplan/execution/contract.ts +71 -0
  291. package/src/ultraplan/execution/policy.ts +217 -0
  292. package/src/ultraplan/execution/runtime-tools.ts +107 -0
  293. package/src/ultraplan/execution/session-runner.ts +281 -0
  294. package/src/ultraplan/next-router.ts +85 -0
  295. package/src/ultraplan/presenter.ts +359 -0
  296. package/src/ultraplan/project-paths.ts +342 -0
  297. package/src/ultraplan/runtime/active-execution.ts +72 -0
  298. package/src/ultraplan/runtime/apply-mutation.ts +416 -0
  299. package/src/ultraplan/runtime/blockers.ts +243 -0
  300. package/src/ultraplan/runtime/hook-bridge.ts +486 -0
  301. package/src/ultraplan/runtime/launch-context.ts +207 -0
  302. package/src/ultraplan/runtime/migration.ts +524 -0
  303. package/src/ultraplan/runtime/normalize.ts +281 -0
  304. package/src/ultraplan/runtime/proof.ts +260 -0
  305. package/src/ultraplan/runtime/reducer.ts +416 -0
  306. package/src/ultraplan/runtime/repair.ts +251 -0
  307. package/src/ultraplan/runtime/tracker-storage.ts +368 -0
  308. package/src/ultraplan/session-selection.ts +291 -0
  309. package/src/ultraplan/storage.ts +374 -0
  310. package/src/utils/editor.ts +38 -0
  311. package/src/utils/executable.ts +80 -0
  312. package/src/utils/paths.ts +1 -20
  313. package/src/utils/shell.ts +31 -0
  314. package/src/visual/companion.ts +2 -1
  315. package/src/visual/scripts/frame-template.html +60 -0
  316. package/src/visual/scripts/index.js +59 -13
  317. package/src/visual/scripts/package.json +3 -0
  318. package/src/visual/start-server.ts +2 -1
  319. package/src/workspace/git-scope.ts +64 -0
  320. package/src/workspace/locks.ts +23 -0
  321. package/src/workspace/package-manager.ts +117 -0
  322. package/src/workspace/path-mapping.ts +75 -0
  323. package/src/workspace/project-slug.ts +92 -0
  324. package/src/workspace/repo-root.ts +137 -0
  325. package/src/workspace/selector.ts +115 -0
  326. package/src/workspace/state-paths.ts +118 -0
  327. package/src/workspace/targets.ts +313 -0
  328. package/src/fix-pr/scripts/diff-comments.sh +0 -33
  329. package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
  330. package/src/fix-pr/scripts/trigger-review.sh +0 -36
  331. package/src/fix-pr/scripts/wait-and-check.sh +0 -37
  332. package/src/qa/scripts/detect-app-type.sh +0 -68
  333. package/src/qa/scripts/discover-routes.sh +0 -143
  334. package/src/qa/scripts/run-e2e-tests.sh +0 -131
  335. package/src/qa/scripts/start-dev-server.sh +0 -46
  336. package/src/qa/scripts/stop-dev-server.sh +0 -36
  337. package/src/review/prompts/fix-output-schema.md +0 -18
  338. package/src/review/prompts/review-output-schema.md +0 -38
  339. package/src/review/template.ts +0 -15
  340. /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
@@ -0,0 +1,552 @@
1
+ /**
2
+ * Authoring-pipeline tool registrations.
3
+ *
4
+ * Every stage in the multi-stage pipeline emits its artifact via a dedicated tool that the
5
+ * spawned sub-agent calls. The tools are thin wrappers: they validate the JSON payload
6
+ * against the relevant TypeBox schema and persist it atomically through `authoring/storage`.
7
+ *
8
+ * Why one tool per stage instead of a single `ultraplan_authoring_record({ stage, ... })`?
9
+ * - Each stage has a different payload shape; one tool per stage keeps the JSON schema
10
+ * advertised to the model tightly scoped, which materially improves tool-call quality.
11
+ * - The hook bridge can correlate by tool name alone, no ambient discriminator needed.
12
+ * - Future stages can ship their own tool without rev'ing the existing schema.
13
+ *
14
+ * Registration is idempotent at the harness boundary: if `platform.registerTool` is missing
15
+ * (legacy harness) we silently no-op. The pipeline driver expects these tools to be
16
+ * registered at extension load and stays out of registration concerns.
17
+ */
18
+
19
+ import type { Platform } from "../../platform/types.js";
20
+ import type {
21
+ UltraPlanAuthoringFinding,
22
+ UltraPlanStackId,
23
+ UltraPlanStorageResult,
24
+ } from "../../types.js";
25
+ import { ULTRAPLAN_STACKS } from "../contracts.js";
26
+ import {
27
+ appendDecisionRecord,
28
+ loadDeferredIdeas,
29
+ loadFindingsArtifact,
30
+ saveDeferredIdeas,
31
+ saveDraftAuthoredJson,
32
+ saveDraftPlannerJson,
33
+ saveFindingsArtifact,
34
+ saveIntakeArtifact,
35
+ saveResearchStackArtifact,
36
+ saveResearchSummary,
37
+ saveScoutArtifact,
38
+ } from "./storage.js";
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Helpers shared across tool registrations
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function isRecord(value: unknown): value is Record<string, unknown> {
45
+ return typeof value === "object" && value !== null && !Array.isArray(value);
46
+ }
47
+
48
+ interface ToolReturn {
49
+ ok: boolean;
50
+ message?: string;
51
+ path?: string;
52
+ details?: unknown;
53
+ }
54
+
55
+ function toolResult(payload: ToolReturn): { ok: boolean; message?: string; path?: string; details?: unknown } {
56
+ return payload;
57
+ }
58
+
59
+ /**
60
+ * Reject session ids that could escape the intended UltraPlan session directory when joined
61
+ * with `path.join(...)`. Authoring tools accept the id from agent-supplied tool params, so a
62
+ * rogue or malformed call must not be able to write artifacts outside the session.
63
+ *
64
+ * Allowed: short ASCII slug — letters, digits, dot (mid-string), underscore, hyphen.
65
+ * Disallowed: empty/whitespace, leading dot, path separators, parent-segment patterns,
66
+ * absolute paths, NUL bytes, anything longer than 128 chars.
67
+ */
68
+ const SESSION_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9_-])?$/;
69
+
70
+ function isSafeSessionId(value: string): boolean {
71
+ if (!SESSION_ID_PATTERN.test(value)) return false;
72
+ if (value.includes("..")) return false;
73
+ return true;
74
+ }
75
+
76
+ function readSessionId(params: unknown, toolName: string): { ok: true; sessionId: string } | { ok: false; message: string } {
77
+ if (!isRecord(params)) {
78
+ return { ok: false, message: `${toolName} requires an object payload` };
79
+ }
80
+ const raw = params.sessionId;
81
+ if (typeof raw !== "string" || raw.trim().length === 0) {
82
+ return { ok: false, message: `${toolName} requires a sessionId string` };
83
+ }
84
+ if (!isSafeSessionId(raw)) {
85
+ return {
86
+ ok: false,
87
+ message: `${toolName} rejected sessionId: must match ${SESSION_ID_PATTERN} and contain no '..' segments`,
88
+ };
89
+ }
90
+ return { ok: true, sessionId: raw };
91
+ }
92
+
93
+ function readCwd(toolCtx: unknown, toolName: string): { ok: true; cwd: string } | { ok: false; message: string } {
94
+ if (isRecord(toolCtx) && typeof toolCtx.cwd === "string" && toolCtx.cwd.trim().length > 0) {
95
+ return { ok: true, cwd: toolCtx.cwd };
96
+ }
97
+ return { ok: false, message: `${toolName} requires a tool context cwd` };
98
+ }
99
+
100
+ function unwrap<T>(result: UltraPlanStorageResult<T>, toolName: string): { ok: true; value: T; path?: string } | { ok: false; message: string } {
101
+ if (result.ok) {
102
+ return { ok: true, value: result.value, path: typeof result.value === "string" ? (result.value as string) : undefined };
103
+ }
104
+ const detail = result.error.details && result.error.details.length > 0 ? `: ${result.error.details.join(", ")}` : "";
105
+ return { ok: false, message: `${toolName} storage error (${result.error.kind}) at ${result.error.path}${detail}: ${result.error.message}` };
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Schema fragments reused across multiple tools
110
+ // ---------------------------------------------------------------------------
111
+
112
+ const SESSION_ID_PROP = {
113
+ type: "string",
114
+ description: "UltraPlan session id; assigned by the pipeline runner and passed through the agent prompt.",
115
+ } as const;
116
+
117
+ const STACK_ENUM = {
118
+ type: "string",
119
+ enum: ULTRAPLAN_STACKS,
120
+ description: "Stack identifier (frontend / backend / infrastructure).",
121
+ } as const;
122
+
123
+ const FINDING_TARGET = {
124
+ type: "object",
125
+ properties: {
126
+ stack: { type: ["string", "null"], enum: [...ULTRAPLAN_STACKS, null] },
127
+ domainId: { type: ["string", "null"] },
128
+ scenarioId: { type: ["string", "null"] },
129
+ },
130
+ required: ["stack", "domainId", "scenarioId"],
131
+ } as const;
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Public registration entry point
135
+ // ---------------------------------------------------------------------------
136
+
137
+ /**
138
+ * Register every authoring tool with the harness. Safe to call once at extension boot —
139
+ * subsequent calls would throw at the harness layer (each tool name registers exactly once).
140
+ *
141
+ * Stage tools registered:
142
+ * - ultraplan_intake_record
143
+ * - ultraplan_scout_record
144
+ * - ultraplan_decision_record
145
+ * - ultraplan_defer_idea
146
+ * - ultraplan_research_record
147
+ * - ultraplan_research_summary
148
+ * - ultraplan_synth_draft
149
+ * - ultraplan_review_finding
150
+ * - ultraplan_revise_apply
151
+ */
152
+ export function registerUltraPlanAuthoringPipelineTools(platform: Platform): void {
153
+ if (!platform.registerTool) return;
154
+
155
+ // -------- ultraplan_intake_record ----------
156
+ platform.registerTool({
157
+ name: "ultraplan_intake_record",
158
+ label: "UltraPlan Intake Record",
159
+ description: "Record the intake stage artifact for an UltraPlan authoring session.",
160
+ parameters: {
161
+ type: "object",
162
+ properties: {
163
+ sessionId: SESSION_ID_PROP,
164
+ title: { type: "string", description: "Short session title shown in the picker." },
165
+ goal: { type: "string", description: "One-line implementation goal." },
166
+ candidateStacks: {
167
+ type: "array",
168
+ description: "Per-stack applicability hints; downstream stages refine these.",
169
+ items: {
170
+ type: "object",
171
+ properties: {
172
+ stack: STACK_ENUM,
173
+ applicability: { type: "string", enum: ["applicable", "not-applicable"] },
174
+ note: { type: "string" },
175
+ },
176
+ required: ["stack", "applicability"],
177
+ },
178
+ },
179
+ rawUserNotes: { type: "string", description: "Verbatim seed prompt or pasted user notes for forensics." },
180
+ deferredIdeas: { type: "array", items: { type: "string" } },
181
+ },
182
+ required: ["sessionId", "title", "goal", "candidateStacks"],
183
+ },
184
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
185
+ const cwdR = readCwd(toolCtx, "ultraplan_intake_record");
186
+ if (!cwdR.ok) return toolResult(cwdR);
187
+ const sidR = readSessionId(params, "ultraplan_intake_record");
188
+ if (!sidR.ok) return toolResult(sidR);
189
+
190
+ const persisted = saveIntakeArtifact(platform.paths, cwdR.cwd, sidR.sessionId, params);
191
+ const result = unwrap(persisted, "ultraplan_intake_record");
192
+ if (!result.ok) return toolResult(result);
193
+ return toolResult({ ok: true, path: result.value });
194
+ },
195
+ });
196
+
197
+ // -------- ultraplan_scout_record ----------
198
+ platform.registerTool({
199
+ name: "ultraplan_scout_record",
200
+ label: "UltraPlan Scout Record",
201
+ description: "Record the scout stage artifact (codebase reconnaissance).",
202
+ parameters: {
203
+ type: "object",
204
+ properties: {
205
+ sessionId: SESSION_ID_PROP,
206
+ reusableAssets: {
207
+ type: "array",
208
+ description: "File paths or symbol references the implementation can reuse.",
209
+ items: {
210
+ type: "object",
211
+ properties: {
212
+ kind: { type: "string", description: "asset|module|function|test|config" },
213
+ path: { type: "string" },
214
+ note: { type: "string" },
215
+ },
216
+ required: ["kind", "path"],
217
+ },
218
+ },
219
+ integrationPoints: {
220
+ type: "array",
221
+ description: "Existing modules the new work needs to integrate with.",
222
+ items: {
223
+ type: "object",
224
+ properties: { path: { type: "string" }, note: { type: "string" } },
225
+ required: ["path"],
226
+ },
227
+ },
228
+ conventionsByStack: {
229
+ type: "object",
230
+ description: "Conventions grouped by stack id.",
231
+ properties: {
232
+ frontend: { type: "array", items: { type: "string" } },
233
+ backend: { type: "array", items: { type: "string" } },
234
+ infrastructure: { type: "array", items: { type: "string" } },
235
+ },
236
+ },
237
+ existingTests: { type: "array", items: { type: "string" } },
238
+ },
239
+ required: ["sessionId"],
240
+ },
241
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
242
+ const cwdR = readCwd(toolCtx, "ultraplan_scout_record");
243
+ if (!cwdR.ok) return toolResult(cwdR);
244
+ const sidR = readSessionId(params, "ultraplan_scout_record");
245
+ if (!sidR.ok) return toolResult(sidR);
246
+
247
+ const persisted = saveScoutArtifact(platform.paths, cwdR.cwd, sidR.sessionId, params);
248
+ const result = unwrap(persisted, "ultraplan_scout_record");
249
+ if (!result.ok) return toolResult(result);
250
+ return toolResult({ ok: true, path: result.value });
251
+ },
252
+ });
253
+
254
+ // -------- ultraplan_decision_record ----------
255
+ platform.registerTool({
256
+ name: "ultraplan_decision_record",
257
+ label: "UltraPlan Decision Record",
258
+ description: "Append one decision (locked answer to a gray-area question) to the discuss artifact.",
259
+ parameters: {
260
+ type: "object",
261
+ properties: {
262
+ sessionId: SESSION_ID_PROP,
263
+ area: { type: "string", description: "Short label for the decision area (e.g. 'auth-strategy')." },
264
+ question: { type: "string", description: "The exact gray-area question that was answered." },
265
+ decision: { type: "string", description: "The locked answer." },
266
+ rationale: { type: "string", description: "Why this decision was chosen." },
267
+ impact: { type: "array", items: { type: "string" }, description: "Domains/scenarios this decision affects." },
268
+ },
269
+ required: ["sessionId", "area", "question", "decision"],
270
+ },
271
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
272
+ const cwdR = readCwd(toolCtx, "ultraplan_decision_record");
273
+ if (!cwdR.ok) return toolResult(cwdR);
274
+ const sidR = readSessionId(params, "ultraplan_decision_record");
275
+ if (!sidR.ok) return toolResult(sidR);
276
+
277
+ // Decisions are written line-by-line as JSONL. The discuss.md aggregation happens at
278
+ // discover-stage commit time (the user gate).
279
+ const recordedAt = new Date().toISOString();
280
+ const decision = { ...(params as Record<string, unknown>), recordedAt };
281
+ const persisted = appendDecisionRecord(platform.paths, cwdR.cwd, sidR.sessionId, decision);
282
+ const result = unwrap(persisted, "ultraplan_decision_record");
283
+ if (!result.ok) return toolResult(result);
284
+ return toolResult({ ok: true, path: result.value });
285
+ },
286
+ });
287
+
288
+ // -------- ultraplan_defer_idea ----------
289
+ platform.registerTool({
290
+ name: "ultraplan_defer_idea",
291
+ label: "UltraPlan Defer Idea",
292
+ description: "Append an idea to deferred-ideas.md so the discover stage stays scoped.",
293
+ parameters: {
294
+ type: "object",
295
+ properties: {
296
+ sessionId: SESSION_ID_PROP,
297
+ idea: { type: "string", description: "One-line description of the idea being deferred." },
298
+ reason: { type: "string", description: "Why it is out of scope for this session." },
299
+ },
300
+ required: ["sessionId", "idea"],
301
+ },
302
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
303
+ const cwdR = readCwd(toolCtx, "ultraplan_defer_idea");
304
+ if (!cwdR.ok) return toolResult(cwdR);
305
+ const sidR = readSessionId(params, "ultraplan_defer_idea");
306
+ if (!sidR.ok) return toolResult(sidR);
307
+
308
+ const p = params as Record<string, unknown>;
309
+ const idea = typeof p.idea === "string" ? p.idea : "";
310
+ const reason = typeof p.reason === "string" ? p.reason : "";
311
+ if (idea.length === 0) return toolResult({ ok: false, message: "ultraplan_defer_idea requires a non-empty idea" });
312
+
313
+ // Append (load + concat + save) — avoids overwriting prior ideas on re-runs.
314
+ const prior = loadDeferredIdeas(platform.paths, cwdR.cwd, sidR.sessionId);
315
+ const existing = prior.ok ? prior.value : "";
316
+ const next = `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}- ${idea}${reason ? ` — ${reason}` : ""}\n`;
317
+ const persisted = saveDeferredIdeas(platform.paths, cwdR.cwd, sidR.sessionId, next);
318
+ const result = unwrap(persisted, "ultraplan_defer_idea");
319
+ if (!result.ok) return toolResult(result);
320
+ return toolResult({ ok: true, path: result.value });
321
+ },
322
+ });
323
+
324
+ // -------- ultraplan_research_record ----------
325
+ platform.registerTool({
326
+ name: "ultraplan_research_record",
327
+ label: "UltraPlan Research Record",
328
+ description: "Record per-stack research findings (libraries, patterns, pitfalls).",
329
+ parameters: {
330
+ type: "object",
331
+ properties: {
332
+ sessionId: SESSION_ID_PROP,
333
+ stack: STACK_ENUM,
334
+ markdown: {
335
+ type: "string",
336
+ description: "Full markdown body for this stack's research artifact.",
337
+ },
338
+ },
339
+ required: ["sessionId", "stack", "markdown"],
340
+ },
341
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
342
+ const cwdR = readCwd(toolCtx, "ultraplan_research_record");
343
+ if (!cwdR.ok) return toolResult(cwdR);
344
+ const sidR = readSessionId(params, "ultraplan_research_record");
345
+ if (!sidR.ok) return toolResult(sidR);
346
+
347
+ const p = params as Record<string, unknown>;
348
+ const stack = p.stack as UltraPlanStackId;
349
+ const markdown = typeof p.markdown === "string" ? p.markdown : "";
350
+ if (!ULTRAPLAN_STACKS.includes(stack)) {
351
+ return toolResult({ ok: false, message: `ultraplan_research_record stack must be one of ${ULTRAPLAN_STACKS.join(", ")}` });
352
+ }
353
+ if (markdown.length === 0) {
354
+ return toolResult({ ok: false, message: "ultraplan_research_record markdown must be non-empty" });
355
+ }
356
+
357
+ const persisted = saveResearchStackArtifact(platform.paths, cwdR.cwd, sidR.sessionId, stack, markdown);
358
+ const result = unwrap(persisted, "ultraplan_research_record");
359
+ if (!result.ok) return toolResult(result);
360
+ return toolResult({ ok: true, path: result.value });
361
+ },
362
+ });
363
+
364
+ // -------- ultraplan_research_summary ----------
365
+ platform.registerTool({
366
+ name: "ultraplan_research_summary",
367
+ label: "UltraPlan Research Summary",
368
+ description: "Persist the per-session research summary that consolidates per-stack findings.",
369
+ parameters: {
370
+ type: "object",
371
+ properties: {
372
+ sessionId: SESSION_ID_PROP,
373
+ markdown: { type: "string" },
374
+ },
375
+ required: ["sessionId", "markdown"],
376
+ },
377
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
378
+ const cwdR = readCwd(toolCtx, "ultraplan_research_summary");
379
+ if (!cwdR.ok) return toolResult(cwdR);
380
+ const sidR = readSessionId(params, "ultraplan_research_summary");
381
+ if (!sidR.ok) return toolResult(sidR);
382
+
383
+ const md = (params as Record<string, unknown>).markdown;
384
+ if (typeof md !== "string" || md.length === 0) {
385
+ return toolResult({ ok: false, message: "ultraplan_research_summary requires non-empty markdown" });
386
+ }
387
+ const persisted = saveResearchSummary(platform.paths, cwdR.cwd, sidR.sessionId, md);
388
+ const result = unwrap(persisted, "ultraplan_research_summary");
389
+ if (!result.ok) return toolResult(result);
390
+ return toolResult({ ok: true, path: result.value });
391
+ },
392
+ });
393
+
394
+ // -------- ultraplan_synth_draft ----------
395
+ platform.registerTool({
396
+ name: "ultraplan_synth_draft",
397
+ label: "UltraPlan Synth Draft",
398
+ description: "Persist the synthesizer's authored.json + manifest.json draft for the given iteration.",
399
+ parameters: {
400
+ type: "object",
401
+ properties: {
402
+ sessionId: SESSION_ID_PROP,
403
+ iteration: { type: "integer", minimum: 1 },
404
+ authored: { type: "object", description: "Full authored.json payload (UltraPlanAuthoredArtifact shape)." },
405
+ manifest: { type: "object", description: "Full manifest.json payload (UltraPlanManifest shape)." },
406
+ },
407
+ required: ["sessionId", "iteration", "authored", "manifest"],
408
+ },
409
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
410
+ const cwdR = readCwd(toolCtx, "ultraplan_synth_draft");
411
+ if (!cwdR.ok) return toolResult(cwdR);
412
+ const sidR = readSessionId(params, "ultraplan_synth_draft");
413
+ if (!sidR.ok) return toolResult(sidR);
414
+
415
+ const p = params as Record<string, unknown>;
416
+ const iteration = typeof p.iteration === "number" ? p.iteration : 0;
417
+ if (!Number.isInteger(iteration) || iteration < 1) {
418
+ return toolResult({ ok: false, message: "ultraplan_synth_draft requires a positive integer iteration" });
419
+ }
420
+ if (!isRecord(p.authored)) return toolResult({ ok: false, message: "ultraplan_synth_draft requires an authored object" });
421
+
422
+ // Snapshot the planner's emission first (forensics) and then save the editable copy.
423
+ const plannerSnapshot = saveDraftPlannerJson(platform.paths, cwdR.cwd, sidR.sessionId, iteration, p.authored);
424
+ const snapshotResult = unwrap(plannerSnapshot, "ultraplan_synth_draft");
425
+ if (!snapshotResult.ok) return toolResult(snapshotResult);
426
+
427
+ const editable = saveDraftAuthoredJson(platform.paths, cwdR.cwd, sidR.sessionId, iteration, p.authored);
428
+ const editableResult = unwrap(editable, "ultraplan_synth_draft");
429
+ if (!editableResult.ok) return toolResult(editableResult);
430
+
431
+ return toolResult({ ok: true, path: editableResult.value });
432
+ },
433
+ });
434
+
435
+ // -------- ultraplan_review_finding ----------
436
+ platform.registerTool({
437
+ name: "ultraplan_review_finding",
438
+ label: "UltraPlan Review Finding",
439
+ description: "Append one finding from a review checker against a draft iteration.",
440
+ parameters: {
441
+ type: "object",
442
+ properties: {
443
+ sessionId: SESSION_ID_PROP,
444
+ iteration: { type: "integer", minimum: 1 },
445
+ id: { type: "string", description: "Stable id for this finding." },
446
+ severity: { type: "string", enum: ["BLOCKER", "WARNING"] },
447
+ source: { type: "string", enum: ["structure-checker", "scope-checker", "tdd-checker"] },
448
+ target: FINDING_TARGET,
449
+ message: { type: "string" },
450
+ recommendation: { type: "string" },
451
+ },
452
+ required: ["sessionId", "iteration", "id", "severity", "source", "target", "message", "recommendation"],
453
+ },
454
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
455
+ const cwdR = readCwd(toolCtx, "ultraplan_review_finding");
456
+ if (!cwdR.ok) return toolResult(cwdR);
457
+ const sidR = readSessionId(params, "ultraplan_review_finding");
458
+ if (!sidR.ok) return toolResult(sidR);
459
+
460
+ const p = params as Record<string, unknown>;
461
+ const iteration = typeof p.iteration === "number" ? p.iteration : 0;
462
+ if (!Number.isInteger(iteration) || iteration < 1) {
463
+ return toolResult({ ok: false, message: "ultraplan_review_finding requires a positive integer iteration" });
464
+ }
465
+
466
+ const finding: UltraPlanAuthoringFinding = {
467
+ id: String(p.id),
468
+ severity: p.severity as UltraPlanAuthoringFinding["severity"],
469
+ source: p.source as UltraPlanAuthoringFinding["source"],
470
+ target: p.target as UltraPlanAuthoringFinding["target"],
471
+ message: String(p.message),
472
+ recommendation: String(p.recommendation),
473
+ recordedAt: new Date().toISOString(),
474
+ };
475
+
476
+ // Append to the iteration's findings.json: load if present, otherwise start fresh.
477
+ const existing = loadFindingsArtifact(platform.paths, cwdR.cwd, sidR.sessionId, iteration);
478
+ const findings = existing.ok
479
+ ? [...existing.value.findings, finding]
480
+ : [finding];
481
+ const draftRef = `drafts/iteration-${iteration}/authored.json`;
482
+ const artifact = {
483
+ iteration,
484
+ draftRef,
485
+ recordedAt: new Date().toISOString(),
486
+ findings,
487
+ };
488
+ const persisted = saveFindingsArtifact(platform.paths, cwdR.cwd, sidR.sessionId, iteration, artifact);
489
+ const result = unwrap(persisted, "ultraplan_review_finding");
490
+ if (!result.ok) return toolResult(result);
491
+ return toolResult({ ok: true, path: result.value });
492
+ },
493
+ });
494
+
495
+ // -------- ultraplan_revise_apply ----------
496
+ platform.registerTool({
497
+ name: "ultraplan_revise_apply",
498
+ label: "UltraPlan Revise Apply",
499
+ description: "Persist the revised authored.json + manifest.json for a new iteration after review feedback.",
500
+ parameters: {
501
+ type: "object",
502
+ properties: {
503
+ sessionId: SESSION_ID_PROP,
504
+ iteration: { type: "integer", minimum: 2, description: "Revisions start at iteration 2." },
505
+ authored: { type: "object" },
506
+ manifest: { type: "object" },
507
+ },
508
+ required: ["sessionId", "iteration", "authored", "manifest"],
509
+ },
510
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
511
+ const cwdR = readCwd(toolCtx, "ultraplan_revise_apply");
512
+ if (!cwdR.ok) return toolResult(cwdR);
513
+ const sidR = readSessionId(params, "ultraplan_revise_apply");
514
+ if (!sidR.ok) return toolResult(sidR);
515
+
516
+ const p = params as Record<string, unknown>;
517
+ const iteration = typeof p.iteration === "number" ? p.iteration : 0;
518
+ if (!Number.isInteger(iteration) || iteration < 2) {
519
+ return toolResult({ ok: false, message: "ultraplan_revise_apply requires iteration >= 2" });
520
+ }
521
+ if (!isRecord(p.authored)) return toolResult({ ok: false, message: "ultraplan_revise_apply requires an authored object" });
522
+
523
+ // Same atomic write semantics as synth: planner snapshot then editable copy.
524
+ const snapshot = saveDraftPlannerJson(platform.paths, cwdR.cwd, sidR.sessionId, iteration, p.authored);
525
+ const snap = unwrap(snapshot, "ultraplan_revise_apply");
526
+ if (!snap.ok) return toolResult(snap);
527
+ const editable = saveDraftAuthoredJson(platform.paths, cwdR.cwd, sidR.sessionId, iteration, p.authored);
528
+ const editableResult = unwrap(editable, "ultraplan_revise_apply");
529
+ if (!editableResult.ok) return toolResult(editableResult);
530
+
531
+ return toolResult({ ok: true, path: editableResult.value });
532
+ },
533
+ });
534
+ }
535
+
536
+ // ---------------------------------------------------------------------------
537
+ // Names exposed for hook-bridge correlation
538
+ // ---------------------------------------------------------------------------
539
+
540
+ export const ULTRAPLAN_AUTHORING_TOOL_NAMES = [
541
+ "ultraplan_intake_record",
542
+ "ultraplan_scout_record",
543
+ "ultraplan_decision_record",
544
+ "ultraplan_defer_idea",
545
+ "ultraplan_research_record",
546
+ "ultraplan_research_summary",
547
+ "ultraplan_synth_draft",
548
+ "ultraplan_review_finding",
549
+ "ultraplan_revise_apply",
550
+ ] as const;
551
+
552
+ export type UltraPlanAuthoringToolName = (typeof ULTRAPLAN_AUTHORING_TOOL_NAMES)[number];