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,374 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { PlatformPaths } from "../platform/types.js";
4
+ import type {
5
+ UltraPlanAuthoredArtifact,
6
+ UltraPlanDomainReview,
7
+ UltraPlanIndex,
8
+ UltraPlanManifest,
9
+ UltraPlanRuntimeTracker,
10
+ UltraPlanSessionMigrationRecord,
11
+ UltraPlanSessionSummary,
12
+ UltraPlanStackId,
13
+ UltraPlanStackReview,
14
+ UltraPlanStorageError,
15
+ UltraPlanStorageResult,
16
+ } from "../types.js";
17
+ import {
18
+ getUltraPlanSchemaErrors,
19
+ UltraPlanDomainReviewSchema,
20
+ UltraPlanStackReviewSchema,
21
+ validateUltraPlanAuthoredArtifact,
22
+ validateUltraPlanIndex,
23
+ validateUltraPlanManifest,
24
+ } from "./contracts.js";
25
+ import {
26
+ getUltraplanAuthoredJsonPath,
27
+ getUltraplanDomainReviewPath,
28
+ getUltraplanIndexPath,
29
+ getUltraplanManifestPath,
30
+ getUltraplanSessionDir,
31
+ getUltraplanStackReviewPath,
32
+ } from "./project-paths.js";
33
+ import {
34
+ loadMigrationRecord,
35
+ loadTracker,
36
+ saveMigrationRecord,
37
+ saveTrackerAtomic,
38
+ } from "./runtime/tracker-storage.js";
39
+
40
+ function success<T>(value: T): UltraPlanStorageResult<T> {
41
+ return { ok: true, value };
42
+ }
43
+
44
+ function failure(pathname: string, kind: UltraPlanStorageError["kind"], message: string, details?: string[]): UltraPlanStorageResult<never> {
45
+ return {
46
+ ok: false,
47
+ error: {
48
+ kind,
49
+ path: pathname,
50
+ message,
51
+ ...(details ? { details } : {}),
52
+ },
53
+ };
54
+ }
55
+
56
+ function ensureDir(filePath: string): void {
57
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
58
+ }
59
+
60
+ function readJsonFile(filePath: string): UltraPlanStorageResult<unknown> {
61
+ if (!fs.existsSync(filePath)) {
62
+ return failure(filePath, "missing", `Artifact not found: ${filePath}`);
63
+ }
64
+
65
+ try {
66
+ return success(JSON.parse(fs.readFileSync(filePath, "utf8")));
67
+ } catch (error) {
68
+ return failure(
69
+ filePath,
70
+ "invalid-json",
71
+ error instanceof Error ? error.message : `Invalid JSON in ${filePath}`,
72
+ );
73
+ }
74
+ }
75
+
76
+ function writeJsonFile(filePath: string, payload: unknown): UltraPlanStorageResult<string> {
77
+ try {
78
+ ensureDir(filePath);
79
+ fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`);
80
+ return success(filePath);
81
+ } catch (error) {
82
+ return failure(
83
+ filePath,
84
+ "io",
85
+ error instanceof Error ? error.message : `Unable to write ${filePath}`,
86
+ );
87
+ }
88
+ }
89
+
90
+ function resolveSessionArtifactPath(sessionDir: string, artifactPath: string): string {
91
+ return path.isAbsolute(artifactPath) ? artifactPath : path.join(sessionDir, artifactPath);
92
+ }
93
+
94
+ function loadValidatedArtifact<T>(
95
+ filePath: string,
96
+ validate: (value: unknown) => { ok: true; value: T } | { ok: false; errors: string[] },
97
+ ): UltraPlanStorageResult<T> {
98
+ const parsed = readJsonFile(filePath);
99
+ if (!parsed.ok) {
100
+ return parsed;
101
+ }
102
+
103
+ const validation = validate(parsed.value);
104
+ if (!validation.ok) {
105
+ return failure(filePath, "validation-error", `Artifact failed schema validation: ${filePath}`, validation.errors);
106
+ }
107
+
108
+ return success(validation.value);
109
+ }
110
+
111
+ function loadOptionalValidatedArtifact<T>(
112
+ filePath: string,
113
+ schemaErrors: (value: unknown) => string[],
114
+ ): UltraPlanStorageResult<T | null> {
115
+ if (!fs.existsSync(filePath)) {
116
+ return success(null);
117
+ }
118
+
119
+ const parsed = readJsonFile(filePath);
120
+ if (!parsed.ok) {
121
+ return parsed;
122
+ }
123
+
124
+ const errors = schemaErrors(parsed.value);
125
+ if (errors.length > 0) {
126
+ return failure(filePath, "validation-error", `Artifact failed schema validation: ${filePath}`, errors);
127
+ }
128
+
129
+ return success(parsed.value as T);
130
+ }
131
+
132
+ export function saveUltraPlanIndex(
133
+ paths: PlatformPaths,
134
+ cwd: string,
135
+ index: UltraPlanIndex,
136
+ ): UltraPlanStorageResult<string> {
137
+ const validation = validateUltraPlanIndex(index);
138
+ if (!validation.ok) {
139
+ const filePath = getUltraplanIndexPath(paths, cwd);
140
+ return failure(filePath, "validation-error", `Artifact failed schema validation: ${filePath}`, validation.errors);
141
+ }
142
+
143
+ return writeJsonFile(getUltraplanIndexPath(paths, cwd), validation.value);
144
+ }
145
+
146
+ export function loadUltraPlanIndex(paths: PlatformPaths, cwd: string): UltraPlanStorageResult<UltraPlanIndex> {
147
+ const filePath = getUltraplanIndexPath(paths, cwd);
148
+ if (!fs.existsSync(filePath)) {
149
+ return failure(filePath, "missing", `Artifact not found: ${filePath}`);
150
+ }
151
+
152
+ return loadValidatedArtifact(filePath, validateUltraPlanIndex);
153
+ }
154
+
155
+ export function saveUltraPlanManifest(
156
+ paths: PlatformPaths,
157
+ cwd: string,
158
+ sessionId: string,
159
+ manifest: UltraPlanManifest,
160
+ ): UltraPlanStorageResult<string> {
161
+ const filePath = getUltraplanManifestPath(paths, cwd, sessionId);
162
+ const validation = validateUltraPlanManifest(manifest);
163
+ if (!validation.ok) {
164
+ return failure(filePath, "validation-error", `Artifact failed schema validation: ${filePath}`, validation.errors);
165
+ }
166
+
167
+ return writeJsonFile(filePath, validation.value);
168
+ }
169
+
170
+ export function loadUltraPlanManifest(
171
+ paths: PlatformPaths,
172
+ cwd: string,
173
+ sessionId: string,
174
+ ): UltraPlanStorageResult<UltraPlanManifest> {
175
+ return loadValidatedArtifact(getUltraplanManifestPath(paths, cwd, sessionId), validateUltraPlanManifest);
176
+ }
177
+
178
+ export function saveUltraPlanAuthoredArtifact(
179
+ paths: PlatformPaths,
180
+ cwd: string,
181
+ sessionId: string,
182
+ authored: UltraPlanAuthoredArtifact,
183
+ ): UltraPlanStorageResult<string> {
184
+ const filePath = getUltraplanAuthoredJsonPath(paths, cwd, sessionId);
185
+ const validation = validateUltraPlanAuthoredArtifact(authored);
186
+ if (!validation.ok) {
187
+ return failure(filePath, "validation-error", `Artifact failed schema validation: ${filePath}`, validation.errors);
188
+ }
189
+
190
+ return writeJsonFile(filePath, validation.value);
191
+ }
192
+
193
+ export function loadUltraPlanAuthoredArtifact(
194
+ paths: PlatformPaths,
195
+ cwd: string,
196
+ sessionId: string,
197
+ ): UltraPlanStorageResult<UltraPlanAuthoredArtifact> {
198
+ return loadValidatedArtifact(getUltraplanAuthoredJsonPath(paths, cwd, sessionId), validateUltraPlanAuthoredArtifact);
199
+ }
200
+
201
+ export function loadUltraPlanDomainReview(
202
+ paths: PlatformPaths,
203
+ cwd: string,
204
+ sessionId: string,
205
+ stack: UltraPlanStackId,
206
+ domainId: string,
207
+ ): UltraPlanStorageResult<UltraPlanDomainReview | null> {
208
+ return loadOptionalValidatedArtifact<UltraPlanDomainReview>(
209
+ getUltraplanDomainReviewPath(paths, cwd, sessionId, stack, domainId),
210
+ (value) => getUltraPlanSchemaErrors(UltraPlanDomainReviewSchema, value),
211
+ );
212
+ }
213
+
214
+ export function loadUltraPlanStackReview(
215
+ paths: PlatformPaths,
216
+ cwd: string,
217
+ sessionId: string,
218
+ stack: UltraPlanStackId,
219
+ ): UltraPlanStorageResult<UltraPlanStackReview | null> {
220
+ return loadOptionalValidatedArtifact<UltraPlanStackReview>(
221
+ getUltraplanStackReviewPath(paths, cwd, sessionId, stack),
222
+ (value) => getUltraPlanSchemaErrors(UltraPlanStackReviewSchema, value),
223
+ );
224
+ }
225
+
226
+ function validatePassedReviewReference(
227
+ paths: PlatformPaths,
228
+ cwd: string,
229
+ sessionId: string,
230
+ review: UltraPlanManifest["reviews"][number],
231
+ ): UltraPlanStorageResult<null> {
232
+ if (review.status !== "passed") {
233
+ return success(null);
234
+ }
235
+
236
+ const sessionDir = getUltraplanSessionDir(paths, cwd, sessionId);
237
+ const referencedPath = resolveSessionArtifactPath(sessionDir, review.path);
238
+
239
+ if (review.type === "domain") {
240
+ if (!review.domainId) {
241
+ return failure(referencedPath, "validation-error", "Passed domain review reference is missing a domainId");
242
+ }
243
+
244
+ const expectedPath = getUltraplanDomainReviewPath(paths, cwd, sessionId, review.stack, review.domainId);
245
+ if (referencedPath !== expectedPath) {
246
+ return failure(referencedPath, "validation-error", `Passed domain review reference points at an unexpected artifact path: ${referencedPath}`);
247
+ }
248
+
249
+ const domainReview = loadUltraPlanDomainReview(paths, cwd, sessionId, review.stack, review.domainId);
250
+ if (!domainReview.ok) {
251
+ return domainReview;
252
+ }
253
+ if (!domainReview.value) {
254
+ return failure(expectedPath, "missing", `Passed domain review reference is missing review artifact: ${expectedPath}`);
255
+ }
256
+ if (domainReview.value.status !== "passed" || domainReview.value.stack !== review.stack || domainReview.value.domainId !== review.domainId) {
257
+ return failure(expectedPath, "validation-error", `Passed domain review reference does not match the validated review artifact: ${expectedPath}`);
258
+ }
259
+
260
+ return success(null);
261
+ }
262
+
263
+ if (review.domainId !== null) {
264
+ return failure(referencedPath, "validation-error", "Stack review references must not include a domainId");
265
+ }
266
+
267
+ const expectedPath = getUltraplanStackReviewPath(paths, cwd, sessionId, review.stack);
268
+ if (referencedPath !== expectedPath) {
269
+ return failure(referencedPath, "validation-error", `Passed stack review reference points at an unexpected artifact path: ${referencedPath}`);
270
+ }
271
+
272
+ const stackReview = loadUltraPlanStackReview(paths, cwd, sessionId, review.stack);
273
+ if (!stackReview.ok) {
274
+ return stackReview;
275
+ }
276
+ if (!stackReview.value) {
277
+ return failure(expectedPath, "missing", `Passed stack review reference is missing review artifact: ${expectedPath}`);
278
+ }
279
+ if (stackReview.value.status !== "passed" || stackReview.value.stack !== review.stack) {
280
+ return failure(expectedPath, "validation-error", `Passed stack review reference does not match the validated review artifact: ${expectedPath}`);
281
+ }
282
+
283
+ return success(null);
284
+ }
285
+
286
+ export function validateUltraPlanManifestReviewReferences(
287
+ paths: PlatformPaths,
288
+ cwd: string,
289
+ sessionId: string,
290
+ manifest: UltraPlanManifest,
291
+ ): UltraPlanStorageResult<null> {
292
+ for (const review of manifest.reviews) {
293
+ const reviewValidation = validatePassedReviewReference(paths, cwd, sessionId, review);
294
+ if (!reviewValidation.ok) {
295
+ return reviewValidation;
296
+ }
297
+ }
298
+
299
+ return success(null);
300
+ }
301
+
302
+
303
+ export function loadUltraPlanSessionSummary(
304
+ paths: PlatformPaths,
305
+ cwd: string,
306
+ sessionId: string,
307
+ ): UltraPlanStorageResult<UltraPlanSessionSummary> {
308
+ const manifest = loadUltraPlanManifest(paths, cwd, sessionId);
309
+ if (!manifest.ok) {
310
+ return manifest;
311
+ }
312
+ const manifestValue = manifest.value;
313
+ const reviewValidation = validateUltraPlanManifestReviewReferences(paths, cwd, sessionId, manifestValue);
314
+ if (!reviewValidation.ok) {
315
+ return reviewValidation;
316
+ }
317
+
318
+ return success({
319
+ sessionId: manifestValue.sessionId,
320
+ projectName: manifestValue.projectName,
321
+ title: manifestValue.title,
322
+ state: manifestValue.state,
323
+ createdAt: manifestValue.createdAt,
324
+ updatedAt: manifestValue.updatedAt,
325
+ cursor: manifestValue.cursor,
326
+ lastCompleted: manifestValue.lastCompleted,
327
+ blocker: manifestValue.blocker,
328
+ progress: manifestValue.progress,
329
+ stacks: manifestValue.stacks,
330
+ reviews: manifestValue.reviews,
331
+ });
332
+ }
333
+
334
+
335
+ // ---------------------------------------------------------------------------
336
+ // Runtime tracker + migration record wrappers (Slice 2 / 1.4).
337
+ // These delegate to `runtime/tracker-storage.ts` so the storage module stays
338
+ // the single public entry point for session-level persistence while the tracker
339
+ // module owns durability details.
340
+ // ---------------------------------------------------------------------------
341
+
342
+ export function saveUltraPlanRuntimeTracker(
343
+ paths: PlatformPaths,
344
+ cwd: string,
345
+ sessionId: string,
346
+ tracker: UltraPlanRuntimeTracker,
347
+ ): UltraPlanStorageResult<string> {
348
+ return saveTrackerAtomic(paths, cwd, sessionId, tracker);
349
+ }
350
+
351
+ export function loadUltraPlanRuntimeTracker(
352
+ paths: PlatformPaths,
353
+ cwd: string,
354
+ sessionId: string,
355
+ ): UltraPlanStorageResult<UltraPlanRuntimeTracker> {
356
+ return loadTracker(paths, cwd, sessionId);
357
+ }
358
+
359
+ export function saveUltraPlanSessionMigrationRecord(
360
+ paths: PlatformPaths,
361
+ cwd: string,
362
+ sessionId: string,
363
+ record: UltraPlanSessionMigrationRecord,
364
+ ): UltraPlanStorageResult<string> {
365
+ return saveMigrationRecord(paths, cwd, sessionId, record);
366
+ }
367
+
368
+ export function loadUltraPlanSessionMigrationRecord(
369
+ paths: PlatformPaths,
370
+ cwd: string,
371
+ sessionId: string,
372
+ ): UltraPlanStorageResult<UltraPlanSessionMigrationRecord> {
373
+ return loadMigrationRecord(paths, cwd, sessionId);
374
+ }
@@ -0,0 +1,38 @@
1
+ import type { Platform } from "../platform/types.js";
2
+
3
+ /**
4
+ * Open a file in the user's preferred editor and wait for the editor to exit.
5
+ *
6
+ * Resolution order:
7
+ * 1. `$VISUAL`
8
+ * 2. `$EDITOR`
9
+ * 3. OS default opener (`open` on darwin, `start` on win32, `xdg-open` elsewhere)
10
+ *
11
+ * `platform.exec` blocks until the spawned editor process exits, which is what
12
+ * the synthesize stage needs for its `$EDITOR` round-trip. For OS-default openers
13
+ * on darwin and linux, the spawned process returns immediately — callers that
14
+ * need true blocking behavior on save must set `$VISUAL` or `$EDITOR` explicitly
15
+ * (which is the documented requirement for the synth round-trip).
16
+ *
17
+ * Errors are non-fatal — if the editor can't be launched, the function returns
18
+ * without throwing. Callers that need to verify the user actually edited the file
19
+ * should detect changes by comparing mtime / contents before and after.
20
+ */
21
+ export async function openInEditor(platform: Platform, filePath: string): Promise<void> {
22
+ const editor = process.env.VISUAL || process.env.EDITOR;
23
+ try {
24
+ if (editor) {
25
+ // Tokenize on whitespace so users can set EDITOR="code --wait" etc.
26
+ const tokens = editor.split(/\s+/).filter((t) => t.length > 0);
27
+ const cmd = tokens[0];
28
+ const args = [...tokens.slice(1), filePath];
29
+ await platform.exec(cmd, args);
30
+ } else {
31
+ const cmd = process.platform === "darwin" ? "open"
32
+ : process.platform === "win32" ? "start" : "xdg-open";
33
+ await platform.exec(cmd, [filePath]);
34
+ }
35
+ } catch {
36
+ // Editor open failed — non-fatal, file was still written
37
+ }
38
+ }
@@ -0,0 +1,80 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ const DEFAULT_WINDOWS_EXECUTABLE_EXTENSIONS = [".exe", ".cmd", ".bat", ""];
5
+ const POSIX_EXECUTABLE_EXTENSIONS = [""];
6
+
7
+ export interface ExecutableSearchOptions {
8
+ cwd?: string;
9
+ localDirs?: string[];
10
+ preferLocal?: boolean;
11
+ searchPath?: string;
12
+ pathext?: string;
13
+ }
14
+
15
+ function windowsExecutableExtensions(pathext?: string): string[] {
16
+ const ordered = (pathext ?? process.env.PATHEXT ?? "")
17
+ .split(";")
18
+ .map((ext) => ext.trim())
19
+ .filter(Boolean)
20
+ .map((ext) => (ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`));
21
+
22
+ return [...new Set([...ordered, ...DEFAULT_WINDOWS_EXECUTABLE_EXTENSIONS])];
23
+ }
24
+
25
+ function executableExtensions(options: ExecutableSearchOptions): string[] {
26
+ return process.platform === "win32"
27
+ ? windowsExecutableExtensions(options.pathext)
28
+ : POSIX_EXECUTABLE_EXTENSIONS;
29
+ }
30
+
31
+ function isFile(filePath: string): boolean {
32
+ try {
33
+ return fs.statSync(filePath).isFile();
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ function resolveSearchDirectories(options: ExecutableSearchOptions): string[] {
40
+ const pathDirs = (options.searchPath ?? process.env.PATH ?? "")
41
+ .split(path.delimiter)
42
+ .filter(Boolean);
43
+ const localDirs = (options.localDirs ?? []).map((dir) =>
44
+ options.cwd ? path.join(options.cwd, dir) : dir,
45
+ );
46
+
47
+ return options.preferLocal
48
+ ? [...localDirs, ...pathDirs]
49
+ : [...pathDirs, ...localDirs];
50
+ }
51
+
52
+ export function findExecutable(
53
+ executable: string,
54
+ options: ExecutableSearchOptions = {},
55
+ ): string | null {
56
+ const seen = new Set<string>();
57
+ const extensions = executableExtensions(options);
58
+
59
+ for (const directory of resolveSearchDirectories(options)) {
60
+ for (const extension of extensions) {
61
+ const candidate = path.join(directory, `${executable}${extension}`);
62
+ if (seen.has(candidate)) {
63
+ continue;
64
+ }
65
+ seen.add(candidate);
66
+ if (isFile(candidate)) {
67
+ return candidate;
68
+ }
69
+ }
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ export function hasExecutable(
76
+ executable: string,
77
+ options: ExecutableSearchOptions = {},
78
+ ): boolean {
79
+ return findExecutable(executable, options) !== null;
80
+ }
@@ -8,9 +8,7 @@ import * as path from "node:path";
8
8
  * The naive alternative — `path.dirname(new URL(import.meta.url).pathname)` —
9
9
  * is broken on Windows: `URL.pathname` for a `file:///C:/foo/bar.ts` URL
10
10
  * yields `/C:/foo/bar.ts` (leading slash before the drive letter), which
11
- * `path.dirname` on Windows converts to a backslash path. Those backslashes,
12
- * when later embedded in a bash command, are consumed as escape sequences and
13
- * silently corrupt the path.
11
+ * `path.dirname` on Windows converts to a backslash path.
14
12
  *
15
13
  * `fileURLToPath` handles the Windows case correctly, stripping the leading
16
14
  * slash and returning a proper `C:\foo\bar.ts` path.
@@ -20,20 +18,3 @@ import * as path from "node:path";
20
18
  export function moduleDir(importMetaUrl: string): string {
21
19
  return path.dirname(fileURLToPath(importMetaUrl));
22
20
  }
23
-
24
- /**
25
- * Normalizes path separators to forward slashes so the path is safe to embed
26
- * in bash commands or shell-script arguments on Windows.
27
- *
28
- * Git Bash (and WSL) on Windows interpret `\` as an escape character, so a
29
- * native Windows path like `C:\Users\foo\bar` silently becomes `C:Usersfoobar`
30
- * when passed as a bash argument. Forward slashes are accepted by both Git
31
- * Bash and Node.js file APIs on Windows, so this conversion is safe to apply
32
- * universally.
33
- *
34
- * Use on every path that will be passed to `platform.exec("bash", [...])` or
35
- * embedded as a literal in an AI prompt that contains bash commands.
36
- */
37
- export function toBashPath(p: string): string {
38
- return p.replace(/\\/g, "/");
39
- }
@@ -0,0 +1,31 @@
1
+ import type { ExecOptions, ExecResult } from "../platform/types.js";
2
+
3
+ export interface ShellInvocation {
4
+ command: string;
5
+ args: string[];
6
+ }
7
+
8
+ export function getShellInvocation(command: string): ShellInvocation {
9
+ if (process.platform === "win32") {
10
+ return { command: "cmd", args: ["/d", "/s", "/c", command] };
11
+ }
12
+
13
+ return { command: "sh", args: ["-lc", command] };
14
+ }
15
+
16
+ export function createExecShell(
17
+ exec: (cmd: string, args: string[], opts?: ExecOptions) => Promise<ExecResult>,
18
+ ): (command: string, opts?: ExecOptions) => Promise<ExecResult> {
19
+ return async (command: string, opts?: ExecOptions): Promise<ExecResult> => {
20
+ const shell = getShellInvocation(command);
21
+ return exec(shell.command, shell.args, opts);
22
+ };
23
+ }
24
+
25
+ export async function execShellCommand(
26
+ exec: (cmd: string, args: string[], opts?: ExecOptions) => Promise<ExecResult>,
27
+ command: string,
28
+ opts?: ExecOptions,
29
+ ): Promise<ExecResult> {
30
+ return createExecShell(exec)(command, opts);
31
+ }
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import type { VisualServerInfo, VisualEvent } from "./types.js";
5
5
  import { normalizeLineEndings } from "../text.js";
6
6
  import type { PlatformPaths } from "../platform/types.js";
7
+ import { getProjectStatePath } from "../workspace/state-paths.js";
7
8
 
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
10
 
@@ -18,7 +19,7 @@ export function generateVisualSessionId(): string {
18
19
 
19
20
  /** Create the session directory and return its path */
20
21
  export function createSessionDir(paths: PlatformPaths, cwd: string, sessionId: string): string {
21
- const sessionDir = paths.project(cwd, "visual", sessionId);
22
+ const sessionDir = getProjectStatePath(paths, cwd, "visual", sessionId);
22
23
  fs.mkdirSync(sessionDir, { recursive: true });
23
24
  return sessionDir;
24
25
  }
@@ -179,6 +179,66 @@
179
179
  .mock-content { padding: 1.5rem; flex: 1; }
180
180
  .mock-button { background: var(--accent); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.85rem; }
181
181
  .mock-input { background: var(--bg-primary); border: 1px solid var(--border); border-radius: 6px; padding: 0.5rem; width: 100%; }
182
+
183
+ /* ===== UI-DESIGN COMPANION ===== */
184
+ .component-gallery {
185
+ display: grid;
186
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
187
+ gap: 1rem;
188
+ margin: 1rem 0;
189
+ }
190
+ .component-gallery .component-tile {
191
+ background: var(--bg-secondary);
192
+ border: 1px solid var(--border);
193
+ border-radius: 10px;
194
+ overflow: hidden;
195
+ }
196
+ .component-gallery .component-tile-header {
197
+ background: var(--bg-tertiary);
198
+ padding: 0.5rem 0.75rem;
199
+ font-size: 0.75rem;
200
+ color: var(--text-secondary);
201
+ border-bottom: 1px solid var(--border);
202
+ }
203
+ .component-gallery .component-tile-body { padding: 1rem; }
204
+
205
+ .preview-grid {
206
+ display: grid;
207
+ grid-template-columns: 1fr 1fr;
208
+ gap: 1rem;
209
+ margin: 1rem 0;
210
+ }
211
+ @media (max-width: 800px) { .preview-grid { grid-template-columns: 1fr; } }
212
+ .preview-grid > .preview-item {
213
+ background: var(--bg-secondary);
214
+ border: 1px solid var(--border);
215
+ border-radius: 10px;
216
+ padding: 1rem;
217
+ }
218
+
219
+ .critique-report { display: flex; flex-direction: column; gap: 1rem; margin: 1rem 0; }
220
+ .critique-report .critique-section {
221
+ background: var(--bg-secondary);
222
+ border: 1px solid var(--border);
223
+ border-radius: 10px;
224
+ padding: 1rem 1.25rem;
225
+ }
226
+ .critique-report .critique-section h3 {
227
+ font-size: 0.95rem;
228
+ margin-bottom: 0.5rem;
229
+ }
230
+ .critique-report .critique-section.fixable h3 { color: var(--warning); }
231
+ .critique-report .critique-section.advisory h3 { color: var(--text-secondary); }
232
+
233
+ .decomposition-tree {
234
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
235
+ font-size: 0.85rem;
236
+ background: var(--bg-secondary);
237
+ border: 1px solid var(--border);
238
+ border-radius: 10px;
239
+ padding: 1rem 1.25rem;
240
+ white-space: pre-wrap;
241
+ }
182
242
  </style>
183
243
  </head>
184
244
  <body>