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,256 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import type { MempalaceRuntimeError, ProcessRunner } from "./runtime.js";
5
+
6
+ /**
7
+ * Pinned uv release version. Updating this constant is the only way to bump uv.
8
+ * Astral's release URLs and per-asset SHA256 files are stable, but the SHA256 is
9
+ * verified per-download so a tampered mirror cannot inject a different binary.
10
+ */
11
+ export const PINNED_UV_VERSION = "0.5.30";
12
+
13
+ const UV_BASE_URL = "https://github.com/astral-sh/uv/releases/download";
14
+
15
+ export type UvPlatform =
16
+ | "darwin-arm64"
17
+ | "darwin-x64"
18
+ | "linux-x64"
19
+ | "linux-arm64"
20
+ | "win32-x64";
21
+
22
+ export interface UvTarget {
23
+ triple: string;
24
+ archive: string;
25
+ binary: string;
26
+ archiveBinaryRelativePath: string;
27
+ }
28
+
29
+ export function detectUvPlatform(
30
+ platform: NodeJS.Platform = process.platform,
31
+ arch: string = process.arch,
32
+ ): UvPlatform | null {
33
+ if (platform === "darwin" && arch === "arm64") return "darwin-arm64";
34
+ if (platform === "darwin" && arch === "x64") return "darwin-x64";
35
+ if (platform === "linux" && arch === "x64") return "linux-x64";
36
+ if (platform === "linux" && arch === "arm64") return "linux-arm64";
37
+ if (platform === "win32" && arch === "x64") return "win32-x64";
38
+ return null;
39
+ }
40
+
41
+ export function uvTargetFor(uvPlatform: UvPlatform): UvTarget {
42
+ switch (uvPlatform) {
43
+ case "darwin-arm64":
44
+ return target("aarch64-apple-darwin", ".tar.gz", "uv");
45
+ case "darwin-x64":
46
+ return target("x86_64-apple-darwin", ".tar.gz", "uv");
47
+ case "linux-x64":
48
+ return target("x86_64-unknown-linux-gnu", ".tar.gz", "uv");
49
+ case "linux-arm64":
50
+ return target("aarch64-unknown-linux-gnu", ".tar.gz", "uv");
51
+ case "win32-x64":
52
+ return target("x86_64-pc-windows-msvc", ".zip", "uv.exe");
53
+ }
54
+ }
55
+
56
+ function target(triple: string, archiveSuffix: string, binary: string): UvTarget {
57
+ const archive = `uv-${triple}${archiveSuffix}`;
58
+ return {
59
+ triple,
60
+ archive,
61
+ binary,
62
+ archiveBinaryRelativePath: path.join(`uv-${triple}`, binary),
63
+ };
64
+ }
65
+
66
+ export interface UvFetchResponse {
67
+ status: number;
68
+ bytes: ArrayBuffer;
69
+ }
70
+
71
+ export type UvFetcher = (url: string) => Promise<UvFetchResponse>;
72
+
73
+ async function defaultFetcher(url: string): Promise<UvFetchResponse> {
74
+ const response = await fetch(url);
75
+ return { status: response.status, bytes: await response.arrayBuffer() };
76
+ }
77
+
78
+ export interface EnsureUvOptions {
79
+ managedBinDir: string;
80
+ version?: string;
81
+ platform?: UvPlatform | null;
82
+ fetcher?: UvFetcher;
83
+ runner: ProcessRunner;
84
+ onProgress?: (message: string) => void;
85
+ }
86
+
87
+ export type EnsureUvResult =
88
+ | { ok: true; uvPath: string; version: string; source: "cached" | "downloaded" }
89
+ | { ok: false; error: MempalaceRuntimeError };
90
+
91
+ function sha256(bytes: ArrayBuffer): string {
92
+ return createHash("sha256").update(Buffer.from(bytes)).digest("hex");
93
+ }
94
+
95
+ function parseShaFile(text: string): string | null {
96
+ const match = /\b([0-9a-f]{64})\b/i.exec(text);
97
+ return match ? match[1].toLowerCase() : null;
98
+ }
99
+
100
+ function versionStampPath(managedBinDir: string): string {
101
+ return path.join(managedBinDir, "uv.version");
102
+ }
103
+
104
+ function readVersionStamp(managedBinDir: string): string | null {
105
+ try {
106
+ return fs.readFileSync(versionStampPath(managedBinDir), "utf8").trim();
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+
112
+ function writeVersionStamp(managedBinDir: string, version: string): void {
113
+ fs.writeFileSync(versionStampPath(managedBinDir), `${version}\n`, "utf8");
114
+ }
115
+
116
+ export async function ensureUv(options: EnsureUvOptions): Promise<EnsureUvResult> {
117
+ const version = options.version ?? PINNED_UV_VERSION;
118
+ const uvPlatform = options.platform === undefined ? detectUvPlatform() : options.platform;
119
+ if (!uvPlatform) {
120
+ return {
121
+ ok: false,
122
+ error: {
123
+ code: "uv_unsupported_platform",
124
+ message: `MemPalace setup does not yet support ${process.platform}/${process.arch}.`,
125
+ remediation: "File an issue with supipowers, or set MEMPALACE_PYTHON to a Python 3.9+ on PATH.",
126
+ },
127
+ };
128
+ }
129
+
130
+ const targetSpec = uvTargetFor(uvPlatform);
131
+ const managedPath = path.join(options.managedBinDir, targetSpec.binary);
132
+
133
+ if (fs.existsSync(managedPath) && readVersionStamp(options.managedBinDir) === version) {
134
+ return { ok: true, uvPath: managedPath, version, source: "cached" };
135
+ }
136
+
137
+ const fetcher = options.fetcher ?? defaultFetcher;
138
+ options.onProgress?.(`Downloading uv ${version} for ${targetSpec.triple}`);
139
+
140
+ const archiveUrl = `${UV_BASE_URL}/${version}/${targetSpec.archive}`;
141
+ const shaUrl = `${archiveUrl}.sha256`;
142
+
143
+ let shaResponse: UvFetchResponse;
144
+ try {
145
+ shaResponse = await fetcher(shaUrl);
146
+ } catch (error) {
147
+ return uvNetworkError(`fetch checksum at ${shaUrl}`, error);
148
+ }
149
+ if (shaResponse.status !== 200) {
150
+ return {
151
+ ok: false,
152
+ error: {
153
+ code: "uv_download_failed",
154
+ message: `Failed to fetch uv checksum (HTTP ${shaResponse.status}) at ${shaUrl}.`,
155
+ remediation: "Verify network access to github.com and retry `/supi:memory setup`.",
156
+ },
157
+ };
158
+ }
159
+ const expectedSha = parseShaFile(new TextDecoder().decode(shaResponse.bytes));
160
+ if (!expectedSha) {
161
+ return {
162
+ ok: false,
163
+ error: {
164
+ code: "uv_checksum_invalid",
165
+ message: `uv checksum file at ${shaUrl} did not contain a valid SHA256.`,
166
+ remediation: "Retry. If this persists, the upstream uv release manifest may be malformed.",
167
+ },
168
+ };
169
+ }
170
+
171
+ let archiveResponse: UvFetchResponse;
172
+ try {
173
+ archiveResponse = await fetcher(archiveUrl);
174
+ } catch (error) {
175
+ return uvNetworkError(`fetch archive at ${archiveUrl}`, error);
176
+ }
177
+ if (archiveResponse.status !== 200) {
178
+ return {
179
+ ok: false,
180
+ error: {
181
+ code: "uv_download_failed",
182
+ message: `Failed to fetch uv archive (HTTP ${archiveResponse.status}) at ${archiveUrl}.`,
183
+ remediation: "Verify network access to github.com and retry `/supi:memory setup`.",
184
+ },
185
+ };
186
+ }
187
+ const actualSha = sha256(archiveResponse.bytes);
188
+ if (actualSha !== expectedSha) {
189
+ return {
190
+ ok: false,
191
+ error: {
192
+ code: "uv_checksum_mismatch",
193
+ message: `uv archive SHA256 mismatch (expected ${expectedSha}, got ${actualSha}).`,
194
+ remediation: "Retry; if it persists, the network path to github.com may be tampered with.",
195
+ },
196
+ };
197
+ }
198
+
199
+ fs.mkdirSync(options.managedBinDir, { recursive: true });
200
+ const archivePath = path.join(options.managedBinDir, targetSpec.archive);
201
+ fs.writeFileSync(archivePath, Buffer.from(archiveResponse.bytes));
202
+
203
+ options.onProgress?.(`Extracting uv ${version}`);
204
+ const extract = await options.runner("tar", ["-xf", archivePath, "-C", options.managedBinDir]);
205
+ if (extract.code !== 0) {
206
+ return {
207
+ ok: false,
208
+ error: {
209
+ code: "uv_extract_failed",
210
+ message: `tar failed to extract uv archive (code ${extract.code}): ${
211
+ extract.stderr.trim() || extract.stdout.trim() || "no output"
212
+ }`,
213
+ remediation: "Ensure tar is on PATH (built-in on macOS, Linux, and Windows 10+).",
214
+ },
215
+ };
216
+ }
217
+
218
+ const extractedBinary = path.join(options.managedBinDir, targetSpec.archiveBinaryRelativePath);
219
+ if (!fs.existsSync(extractedBinary)) {
220
+ return {
221
+ ok: false,
222
+ error: {
223
+ code: "uv_extract_failed",
224
+ message: `uv binary not found at ${extractedBinary} after tar extraction.`,
225
+ },
226
+ };
227
+ }
228
+
229
+ // Replace any pre-existing managed binary atomically-ish.
230
+ if (fs.existsSync(managedPath)) {
231
+ try { fs.unlinkSync(managedPath); } catch { /* best effort */ }
232
+ }
233
+ fs.renameSync(extractedBinary, managedPath);
234
+ if (process.platform !== "win32") {
235
+ fs.chmodSync(managedPath, 0o755);
236
+ }
237
+
238
+ // Cleanup the extracted directory and downloaded archive.
239
+ try { fs.rmSync(path.join(options.managedBinDir, `uv-${targetSpec.triple}`), { recursive: true, force: true }); } catch { /* */ }
240
+ try { fs.unlinkSync(archivePath); } catch { /* */ }
241
+
242
+ writeVersionStamp(options.managedBinDir, version);
243
+
244
+ return { ok: true, uvPath: managedPath, version, source: "downloaded" };
245
+ }
246
+
247
+ function uvNetworkError(action: string, error: unknown): EnsureUvResult {
248
+ return {
249
+ ok: false,
250
+ error: {
251
+ code: "uv_download_failed",
252
+ message: `Network error trying to ${action}: ${(error as Error).message}`,
253
+ remediation: "Check internet connectivity and retry `/supi:memory setup`.",
254
+ },
255
+ };
256
+ }
@@ -0,0 +1,354 @@
1
+ // src/migrate/runner.ts
2
+ //
3
+ // Execution-state migration engine: moves per-invocation artifacts from the
4
+ // legacy repo-local tree (`<repoRoot>/.omp/supipowers/<dir>`) into the
5
+ // project-scoped global tree (`<homedir>/.omp/supipowers/projects/<slug>/<dir>`).
6
+ //
7
+ // Contracts:
8
+ // - Fail-closed on conflicts. If both source and destination exist, leave both
9
+ // in place and surface the conflict. We never merge silently.
10
+ // - Idempotent. A marker file in the repo-local tree records completed runs;
11
+ // subsequent invocations are no-ops unless `force` is set.
12
+ // - Hot-DB safe. SQLite databases under `sessions/` are not moved while
13
+ // WAL/SHM sidecars are non-empty (indicates a live connection).
14
+ // - Workspace-aware. Mirrors under `workspaces/<rel>/` are walked so every
15
+ // workspace target's execution state is migrated too.
16
+
17
+ import * as fs from "node:fs";
18
+ import * as path from "node:path";
19
+ import { projectSlugFromRepoRoot } from "../workspace/project-slug.js";
20
+ import { resolveRepoIdentityRootFromFs } from "../workspace/repo-root.js";
21
+
22
+ /** Execution-state directories/files that moved to the project-scoped global tree. */
23
+ export const EXECUTION_STATE_ENTRIES = [
24
+ "plans",
25
+ "reviews",
26
+ "reports",
27
+ "fix-pr-sessions",
28
+ "qa-sessions",
29
+ "reliability",
30
+ "debug",
31
+ "visual",
32
+ "ui-design",
33
+ "sessions",
34
+ "doc-drift.json",
35
+ ] as const;
36
+
37
+ export type MigrationEntryStatus = "moved" | "skipped" | "conflict" | "hot-db";
38
+
39
+ export interface MigrationEntryResult {
40
+ rel: string;
41
+ source: string;
42
+ dest: string;
43
+ status: MigrationEntryStatus;
44
+ reason?: string;
45
+ }
46
+
47
+ export interface MigrationResult {
48
+ repoRoot: string;
49
+ slug: string;
50
+ markerPath: string;
51
+ moved: MigrationEntryResult[];
52
+ skipped: MigrationEntryResult[];
53
+ conflicts: MigrationEntryResult[];
54
+ /** All per-entry results, including workspace mirrors. */
55
+ entries: MigrationEntryResult[];
56
+ /** Whether the marker file was written. */
57
+ markerWritten: boolean;
58
+ /** True when the marker already existed and migration was skipped as a no-op. */
59
+ alreadyMigrated: boolean;
60
+ }
61
+
62
+ export interface MigrationOptions {
63
+ /** Path inside a repo to migrate. Resolves to the repo identity root. */
64
+ cwd: string;
65
+ /** Home dir whose `<home>/.omp/supipowers` tree receives the moved state. */
66
+ homedir: string;
67
+ /** Re-run even if the marker file is present. */
68
+ force?: boolean;
69
+ }
70
+
71
+ export const MIGRATION_MARKER_FILENAME = ".migration-v2.json";
72
+ export const MIGRATION_SCHEMA_VERSION = 2;
73
+
74
+ const WORKSPACES_DIR = "workspaces";
75
+ const SUPIPOWERS_DIR = "supipowers";
76
+ const DOT_DIR = ".omp";
77
+ const PROJECTS_DIR = "projects";
78
+
79
+ function legacyRoot(repoRoot: string): string {
80
+ return path.join(repoRoot, DOT_DIR, SUPIPOWERS_DIR);
81
+ }
82
+
83
+ function globalProjectDir(homedir: string, slug: string): string {
84
+ return path.join(homedir, DOT_DIR, SUPIPOWERS_DIR, PROJECTS_DIR, slug);
85
+ }
86
+
87
+ /**
88
+ * Walk `<repoRoot>/.omp/supipowers/workspaces/...` and yield every workspace
89
+ * subdirectory as a relative path like `workspaces/packages/api`.
90
+ */
91
+ function discoverWorkspaceMirrors(repoRoot: string): string[] {
92
+ const workspacesDir = path.join(legacyRoot(repoRoot), WORKSPACES_DIR);
93
+ if (!fs.existsSync(workspacesDir)) return [];
94
+
95
+ const results: string[] = [];
96
+ const stack: Array<{ absolute: string; relative: string }> = [
97
+ { absolute: workspacesDir, relative: WORKSPACES_DIR },
98
+ ];
99
+
100
+ while (stack.length > 0) {
101
+ const entry = stack.pop()!;
102
+ let children: fs.Dirent[];
103
+ try {
104
+ children = fs.readdirSync(entry.absolute, { withFileTypes: true });
105
+ } catch {
106
+ continue;
107
+ }
108
+
109
+ // A workspace mirror is a directory that contains at least one of the
110
+ // known execution-state dirs as a child. Otherwise descend further.
111
+ const hasExecChild = children.some((c) =>
112
+ c.isDirectory() && (EXECUTION_STATE_ENTRIES as readonly string[]).includes(c.name),
113
+ );
114
+
115
+ if (hasExecChild) {
116
+ results.push(entry.relative);
117
+ continue;
118
+ }
119
+
120
+ for (const child of children) {
121
+ if (!child.isDirectory()) continue;
122
+ stack.push({
123
+ absolute: path.join(entry.absolute, child.name),
124
+ relative: path.join(entry.relative, child.name),
125
+ });
126
+ }
127
+ }
128
+
129
+ return results;
130
+ }
131
+
132
+ /**
133
+ * Check whether a SQLite DB under `sessions/` has an active writer attached.
134
+ * Presence of a non-empty `-wal` or `-shm` sidecar indicates a live connection
135
+ * and we refuse to move the DB in that case.
136
+ */
137
+ function isSqliteHot(dbFile: string): boolean {
138
+ for (const suffix of ["-wal", "-shm"]) {
139
+ const sidecar = `${dbFile}${suffix}`;
140
+ try {
141
+ const stats = fs.statSync(sidecar);
142
+ if (stats.isFile() && stats.size > 0) return true;
143
+ } catch {
144
+ // Missing sidecar is fine.
145
+ }
146
+ }
147
+ return false;
148
+ }
149
+
150
+ function hasHotSqliteDb(dir: string): boolean {
151
+ if (!fs.existsSync(dir)) return false;
152
+ let entries: fs.Dirent[];
153
+ try {
154
+ entries = fs.readdirSync(dir, { withFileTypes: true });
155
+ } catch {
156
+ return false;
157
+ }
158
+ for (const entry of entries) {
159
+ if (entry.isFile() && entry.name.endsWith(".db")) {
160
+ if (isSqliteHot(path.join(dir, entry.name))) return true;
161
+ }
162
+ }
163
+ return false;
164
+ }
165
+
166
+ function moveEntry(source: string, dest: string): void {
167
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
168
+ try {
169
+ fs.renameSync(source, dest);
170
+ } catch (err: unknown) {
171
+ // Cross-device rename — fall back to recursive copy + delete.
172
+ if (
173
+ err &&
174
+ typeof err === "object" &&
175
+ "code" in err &&
176
+ (err as NodeJS.ErrnoException).code === "EXDEV"
177
+ ) {
178
+ fs.cpSync(source, dest, { recursive: true });
179
+ fs.rmSync(source, { recursive: true, force: true });
180
+ return;
181
+ }
182
+ throw err;
183
+ }
184
+ }
185
+
186
+ interface EntryPlan {
187
+ /** Relative path under the supipowers root, e.g. "plans" or "workspaces/packages/api/reviews". */
188
+ rel: string;
189
+ /** `sessions` entries get extra hot-DB protection. */
190
+ isSessions: boolean;
191
+ }
192
+
193
+ function collectEntryPlans(repoRoot: string): EntryPlan[] {
194
+ const plans: EntryPlan[] = [];
195
+
196
+ for (const name of EXECUTION_STATE_ENTRIES) {
197
+ plans.push({ rel: name, isSessions: name === "sessions" });
198
+ }
199
+
200
+ for (const workspaceRel of discoverWorkspaceMirrors(repoRoot)) {
201
+ for (const name of EXECUTION_STATE_ENTRIES) {
202
+ plans.push({
203
+ rel: path.join(workspaceRel, name),
204
+ isSessions: name === "sessions",
205
+ });
206
+ }
207
+ }
208
+
209
+ return plans;
210
+ }
211
+
212
+ function processEntry(
213
+ plan: EntryPlan,
214
+ repoRoot: string,
215
+ projectDir: string,
216
+ ): MigrationEntryResult {
217
+ const source = path.join(legacyRoot(repoRoot), plan.rel);
218
+ const dest = path.join(projectDir, plan.rel);
219
+
220
+ const sourceExists = fs.existsSync(source);
221
+ const destExists = fs.existsSync(dest);
222
+
223
+ if (!sourceExists) {
224
+ return { rel: plan.rel, source, dest, status: "skipped", reason: "source-missing" };
225
+ }
226
+
227
+ if (destExists) {
228
+ return {
229
+ rel: plan.rel,
230
+ source,
231
+ dest,
232
+ status: "conflict",
233
+ reason: "destination-already-exists",
234
+ };
235
+ }
236
+
237
+ if (plan.isSessions && hasHotSqliteDb(source)) {
238
+ return {
239
+ rel: plan.rel,
240
+ source,
241
+ dest,
242
+ status: "hot-db",
243
+ reason: "sqlite-wal-or-shm-non-empty",
244
+ };
245
+ }
246
+
247
+ moveEntry(source, dest);
248
+ return { rel: plan.rel, source, dest, status: "moved" };
249
+ }
250
+
251
+ function readMarker(markerPath: string): { migratedAt: string } | null {
252
+ if (!fs.existsSync(markerPath)) return null;
253
+ try {
254
+ return JSON.parse(fs.readFileSync(markerPath, "utf-8"));
255
+ } catch {
256
+ return null;
257
+ }
258
+ }
259
+
260
+ function writeMarker(markerPath: string, payload: unknown): void {
261
+ fs.mkdirSync(path.dirname(markerPath), { recursive: true });
262
+ fs.writeFileSync(markerPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
263
+ }
264
+
265
+ /**
266
+ * Execute the execution-state migration for the repo containing `cwd`.
267
+ * Returns a structured result describing every moved/skipped/conflicted entry.
268
+ */
269
+ export function runMigration(options: MigrationOptions): MigrationResult {
270
+ const identityRoot = resolveRepoIdentityRootFromFs(options.cwd);
271
+ const slug = projectSlugFromRepoRoot(identityRoot);
272
+ const projectDir = globalProjectDir(options.homedir, slug);
273
+ const markerPath = path.join(legacyRoot(identityRoot), MIGRATION_MARKER_FILENAME);
274
+
275
+ const existingMarker = readMarker(markerPath);
276
+ if (existingMarker && !options.force) {
277
+ return {
278
+ repoRoot: identityRoot,
279
+ slug,
280
+ markerPath,
281
+ moved: [],
282
+ skipped: [],
283
+ conflicts: [],
284
+ entries: [],
285
+ markerWritten: false,
286
+ alreadyMigrated: true,
287
+ };
288
+ }
289
+
290
+ // Ensure the destination root exists so the first renameSync has a parent.
291
+ fs.mkdirSync(projectDir, { recursive: true });
292
+
293
+ const plans = collectEntryPlans(identityRoot);
294
+ const entries: MigrationEntryResult[] = plans.map((plan) =>
295
+ processEntry(plan, identityRoot, projectDir),
296
+ );
297
+
298
+ const moved = entries.filter((e) => e.status === "moved");
299
+ const skipped = entries.filter((e) => e.status === "skipped");
300
+ const conflicts = entries.filter(
301
+ (e) => e.status === "conflict" || e.status === "hot-db",
302
+ );
303
+
304
+ writeMarker(markerPath, {
305
+ schemaVersion: MIGRATION_SCHEMA_VERSION,
306
+ migratedAt: new Date().toISOString(),
307
+ slug,
308
+ identityRoot,
309
+ projectDir,
310
+ moved: moved.map((e) => e.rel),
311
+ skipped: skipped.map((e) => ({ rel: e.rel, reason: e.reason })),
312
+ conflicts: conflicts.map((e) => ({ rel: e.rel, reason: e.reason, status: e.status })),
313
+ });
314
+
315
+ return {
316
+ repoRoot: identityRoot,
317
+ slug,
318
+ markerPath,
319
+ moved,
320
+ skipped,
321
+ conflicts,
322
+ entries,
323
+ markerWritten: true,
324
+ alreadyMigrated: false,
325
+ };
326
+ }
327
+
328
+ export function formatMigrationSummary(result: MigrationResult): string[] {
329
+ if (result.alreadyMigrated) {
330
+ return [
331
+ `supipowers state at ${result.repoRoot} already migrated (marker: ${result.markerPath}).`,
332
+ "Re-run with --force to migrate again.",
333
+ ];
334
+ }
335
+
336
+ const lines: string[] = [];
337
+ lines.push(`Migrated supipowers execution state for ${result.repoRoot}`);
338
+ lines.push(` slug: ${result.slug}`);
339
+ lines.push(` moved: ${result.moved.length}`);
340
+ lines.push(` skipped: ${result.skipped.length}`);
341
+ lines.push(` conflicts: ${result.conflicts.length}`);
342
+ if (result.conflicts.length > 0) {
343
+ lines.push("");
344
+ lines.push("Conflicts (both locations exist — inspect and resolve manually):");
345
+ for (const c of result.conflicts) {
346
+ lines.push(` - ${c.rel} [${c.status}${c.reason ? ": " + c.reason : ""}]`);
347
+ lines.push(` source: ${c.source}`);
348
+ lines.push(` dest: ${c.dest}`);
349
+ }
350
+ }
351
+ lines.push("");
352
+ lines.push(`Marker: ${result.markerPath}`);
353
+ return lines;
354
+ }