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
@@ -1,5 +1,4 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import type { TSchema } from "@sinclair/typebox";
3
2
  import { Value } from "@sinclair/typebox/value";
4
3
  import type {
5
4
  ConfiguredReviewAgent,
@@ -17,6 +16,7 @@ import type {
17
16
  ReviewScopeStats,
18
17
  ReviewSession,
19
18
  ReviewSessionArtifacts,
19
+ ThinkingLevel,
20
20
  } from "../types.js";
21
21
  export type {
22
22
  ConfiguredReviewAgent,
@@ -122,6 +122,16 @@ export const ReviewAgentConfigSchema = Type.Object(
122
122
  enabled: Type.Boolean(),
123
123
  data: Type.String({ minLength: 1 }),
124
124
  model: Type.Union([Type.String({ minLength: 1 }), Type.Null()]),
125
+ thinkingLevel: Type.Optional(Type.Union([
126
+ Type.Literal("off"),
127
+ Type.Literal("minimal"),
128
+ Type.Literal("low"),
129
+ Type.Literal("medium"),
130
+ Type.Literal("high"),
131
+ Type.Literal("xhigh"),
132
+ Type.Null(),
133
+ ])),
134
+ peerCoordination: Type.Optional(Type.Boolean()),
125
135
  },
126
136
  { additionalProperties: false },
127
137
  );
@@ -212,25 +222,6 @@ export const ReviewSessionSchema = Type.Object(
212
222
  { additionalProperties: false },
213
223
  );
214
224
 
215
- export interface ReviewValidationError {
216
- path: string;
217
- message: string;
218
- }
219
-
220
- function normalizeErrorPath(path: string): string {
221
- return path.replace(/^\//, "").replace(/\//g, ".") || "(root)";
222
- }
223
-
224
- export function collectReviewValidationErrors(schema: TSchema, data: unknown): ReviewValidationError[] {
225
- return [...Value.Errors(schema, data)].map((error) => ({
226
- path: normalizeErrorPath(error.path),
227
- message: error.message,
228
- }));
229
- }
230
-
231
- export function formatReviewValidationErrors(errors: ReviewValidationError[]): string[] {
232
- return errors.map((error) => `${error.path}: ${error.message}`);
233
- }
234
225
 
235
226
  export function isReviewScopeFile(value: unknown): value is ReviewScopeFile {
236
227
  return Value.Check(ReviewScopeFileSchema, value);
@@ -1,8 +1,12 @@
1
- import reviewOutputSchema from "./prompts/review-output-schema.md" with { type: "text" };
2
1
  import validationReviewPrompt from "./prompts/validation-review.md" with { type: "text" };
3
2
  import type { GateExecutionContext, ReviewFinding, ReviewOutput, ReviewScope } from "../types.js";
4
- import { explainReviewOutputFailure, parseReviewOutput, runWithOutputValidation } from "./output.js";
5
- import { renderReviewTemplate } from "./template.js";
3
+ import { runWithOutputValidation, type ReliabilityReporter } from "../ai/structured-output.js";
4
+ import { renderSchemaText } from "../ai/schema-text.js";
5
+ import { explainReviewOutputFailure, parseReviewOutput } from "./output.js";
6
+ import { renderTemplate } from "../ai/template.js";
7
+ import { ReviewOutputSchema } from "./types.js";
8
+
9
+ const REVIEW_OUTPUT_SCHEMA_TEXT = renderSchemaText(ReviewOutputSchema);
6
10
 
7
11
  export interface ReviewValidationInput {
8
12
  cwd: string;
@@ -14,6 +18,7 @@ export interface ReviewValidationInput {
14
18
  timeoutMs?: number;
15
19
  validatorName?: string;
16
20
  now?: () => Date;
21
+ reliability?: ReliabilityReporter;
17
22
  }
18
23
 
19
24
  export interface ReviewValidationResult {
@@ -80,12 +85,12 @@ export function buildValidationPrompt(
80
85
  validatorName: string,
81
86
  validatedAt: string,
82
87
  ): string {
83
- return renderReviewTemplate(validationReviewPrompt, {
88
+ return renderTemplate(validationReviewPrompt, {
84
89
  scope,
85
90
  findingsJson: JSON.stringify(findings, null, 2),
86
91
  validatorName,
87
92
  validatedAt,
88
- outputSchema: reviewOutputSchema.trim(),
93
+ outputSchema: REVIEW_OUTPUT_SCHEMA_TEXT,
89
94
  });
90
95
  }
91
96
 
@@ -107,7 +112,7 @@ export async function validateReviewFindings(input: ReviewValidationInput): Prom
107
112
  const result = await runWithOutputValidation(input.createAgentSession, {
108
113
  cwd: input.cwd,
109
114
  prompt: buildValidationPrompt(input.scope, input.findings, validatorName, validatedAt),
110
- schema: reviewOutputSchema.trim(),
115
+ schema: REVIEW_OUTPUT_SCHEMA_TEXT,
111
116
  parse(raw) {
112
117
  const output = parseReviewOutput(raw);
113
118
  return {
@@ -118,6 +123,7 @@ export async function validateReviewFindings(input: ReviewValidationInput): Prom
118
123
  model: input.model,
119
124
  thinkingLevel: input.thinkingLevel ?? null,
120
125
  timeoutMs: input.timeoutMs ?? 120_000,
126
+ reliability: input.reliability,
121
127
  });
122
128
 
123
129
  if (result.status === "blocked") {
@@ -2,15 +2,17 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { FixPrSessionLedger } from "../fix-pr/types.js";
4
4
  import type { PlatformPaths } from "../platform/types.js";
5
+ import type { WorkspaceTarget } from "../types.js";
6
+ import { getProjectTargetStatePath } from "../workspace/state-paths.js";
5
7
 
6
8
  const SESSIONS_DIR = "fix-pr-sessions";
7
9
 
8
- function getBaseDir(paths: PlatformPaths, cwd: string): string {
9
- return paths.project(cwd, SESSIONS_DIR);
10
+ function getBaseDir(paths: PlatformPaths, target: WorkspaceTarget): string {
11
+ return getProjectTargetStatePath(paths, target, SESSIONS_DIR);
10
12
  }
11
13
 
12
- export function getSessionDir(paths: PlatformPaths, cwd: string, sessionId: string): string {
13
- return path.join(getBaseDir(paths, cwd), sessionId);
14
+ export function getSessionDir(paths: PlatformPaths, target: WorkspaceTarget, sessionId: string): string {
15
+ return path.join(getBaseDir(paths, target), sessionId);
14
16
  }
15
17
 
16
18
  export function generateFixPrSessionId(): string {
@@ -21,14 +23,14 @@ export function generateFixPrSessionId(): string {
21
23
  return `fpr-${date}-${time}-${rand}`;
22
24
  }
23
25
 
24
- export function createFixPrSession(paths: PlatformPaths, cwd: string, ledger: FixPrSessionLedger): void {
25
- const sessionDir = getSessionDir(paths, cwd, ledger.id);
26
+ export function createFixPrSession(paths: PlatformPaths, target: WorkspaceTarget, ledger: FixPrSessionLedger): void {
27
+ const sessionDir = getSessionDir(paths, target, ledger.id);
26
28
  fs.mkdirSync(path.join(sessionDir, "snapshots"), { recursive: true });
27
29
  fs.writeFileSync(path.join(sessionDir, "ledger.json"), JSON.stringify(ledger, null, 2));
28
30
  }
29
31
 
30
- export function loadFixPrSession(paths: PlatformPaths, cwd: string, sessionId: string): FixPrSessionLedger | null {
31
- const ledgerPath = path.join(getSessionDir(paths, cwd, sessionId), "ledger.json");
32
+ export function loadFixPrSession(paths: PlatformPaths, target: WorkspaceTarget, sessionId: string): FixPrSessionLedger | null {
33
+ const ledgerPath = path.join(getSessionDir(paths, target, sessionId), "ledger.json");
32
34
  if (!fs.existsSync(ledgerPath)) return null;
33
35
  try {
34
36
  return JSON.parse(fs.readFileSync(ledgerPath, "utf-8")) as FixPrSessionLedger;
@@ -37,14 +39,19 @@ export function loadFixPrSession(paths: PlatformPaths, cwd: string, sessionId: s
37
39
  }
38
40
  }
39
41
 
40
- export function updateFixPrSession(paths: PlatformPaths, cwd: string, ledger: FixPrSessionLedger): void {
41
- const ledgerPath = path.join(getSessionDir(paths, cwd, ledger.id), "ledger.json");
42
+ export function updateFixPrSession(paths: PlatformPaths, target: WorkspaceTarget, ledger: FixPrSessionLedger): void {
43
+ const ledgerPath = path.join(getSessionDir(paths, target, ledger.id), "ledger.json");
42
44
  ledger.updatedAt = new Date().toISOString();
43
45
  fs.writeFileSync(ledgerPath, JSON.stringify(ledger, null, 2));
44
46
  }
45
47
 
46
- export function findActiveFixPrSession(paths: PlatformPaths, cwd: string): FixPrSessionLedger | null {
47
- const baseDir = getBaseDir(paths, cwd);
48
+ export function findActiveFixPrSession(
49
+ paths: PlatformPaths,
50
+ target: WorkspaceTarget,
51
+ repo: string,
52
+ prNumber: number,
53
+ ): FixPrSessionLedger | null {
54
+ const baseDir = getBaseDir(paths, target);
48
55
  if (!fs.existsSync(baseDir)) return null;
49
56
 
50
57
  const dirs = fs.readdirSync(baseDir)
@@ -53,8 +60,8 @@ export function findActiveFixPrSession(paths: PlatformPaths, cwd: string): FixPr
53
60
  .reverse();
54
61
 
55
62
  for (const dir of dirs) {
56
- const ledger = loadFixPrSession(paths, cwd, dir);
57
- if (ledger && ledger.status === "running") return ledger;
63
+ const ledger = loadFixPrSession(paths, target, dir);
64
+ if (ledger && ledger.status === "running" && ledger.repo === repo && ledger.prNumber === prNumber) return ledger;
58
65
  }
59
66
  return null;
60
67
  }
@@ -1,16 +1,15 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { normalizeLineEndings } from "../text.js";
4
- import type { Plan, PlanTask, TaskComplexity } from "../types.js";
4
+ import type { Plan, PlanTask, TaskComplexity, WorkspaceTarget } from "../types.js";
5
5
  import type { PlatformPaths } from "../platform/types.js";
6
+ import { getProjectStatePath, getProjectTargetStatePath } from "../workspace/state-paths.js";
6
7
 
7
8
  function getPlansDir(paths: PlatformPaths, cwd: string): string {
8
- return paths.project(cwd, "plans");
9
+ return getProjectStatePath(paths, cwd, "plans");
9
10
  }
10
11
 
11
- /** List all saved plans */
12
- export function listPlans(paths: PlatformPaths, cwd: string): string[] {
13
- const dir = getPlansDir(paths, cwd);
12
+ function listPlanFiles(dir: string): string[] {
14
13
  if (!fs.existsSync(dir)) return [];
15
14
  return fs
16
15
  .readdirSync(dir)
@@ -19,6 +18,16 @@ export function listPlans(paths: PlatformPaths, cwd: string): string[] {
19
18
  .reverse();
20
19
  }
21
20
 
21
+ /** List all saved plans */
22
+ export function listPlans(paths: PlatformPaths, cwd: string): string[] {
23
+ return listPlanFiles(getPlansDir(paths, cwd));
24
+ }
25
+
26
+ /** List all saved plans for a specific workspace target. */
27
+ export function listTargetPlans(paths: PlatformPaths, target: WorkspaceTarget): string[] {
28
+ return listPlanFiles(getProjectTargetStatePath(paths, target, "plans"));
29
+ }
30
+
22
31
  /** Read a plan file by name */
23
32
  export function readPlanFile(paths: PlatformPaths, cwd: string, name: string): string | null {
24
33
  const filePath = path.join(getPlansDir(paths, cwd), name);
@@ -1,14 +1,20 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import type { E2eSessionLedger } from "../qa/types.js";
4
3
  import type { PlatformPaths } from "../platform/types.js";
4
+ import type { WorkspaceTarget } from "../types.js";
5
+ import { getProjectStatePath, getProjectTargetStatePath } from "../workspace/state-paths.js";
6
+ import type { E2eSessionLedger } from "../qa/types.js";
7
+
8
+ function getSessionsDir(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): string {
9
+ if (target) {
10
+ return getProjectTargetStatePath(paths, target, "qa-sessions");
11
+ }
5
12
 
6
- function getSessionsDir(paths: PlatformPaths, cwd: string): string {
7
- return paths.project(cwd, "qa-sessions");
13
+ return getProjectStatePath(paths, cwd, "qa-sessions");
8
14
  }
9
15
 
10
- export function getSessionDir(paths: PlatformPaths, cwd: string, sessionId: string): string {
11
- return path.join(getSessionsDir(paths, cwd), sessionId);
16
+ export function getSessionDir(paths: PlatformPaths, cwd: string, sessionId: string, target?: WorkspaceTarget): string {
17
+ return path.join(getSessionsDir(paths, cwd, target), sessionId);
12
18
  }
13
19
 
14
20
  /** Generate a unique QA session ID */
@@ -21,8 +27,8 @@ export function generateSessionId(): string {
21
27
  }
22
28
 
23
29
  /** Create a new QA session */
24
- export function createSession(paths: PlatformPaths, cwd: string, ledger: E2eSessionLedger): void {
25
- const sessionDir = getSessionDir(paths, cwd, ledger.id);
30
+ export function createSession(paths: PlatformPaths, cwd: string, ledger: E2eSessionLedger, target?: WorkspaceTarget): void {
31
+ const sessionDir = getSessionDir(paths, cwd, ledger.id, target);
26
32
  fs.mkdirSync(sessionDir, { recursive: true });
27
33
  fs.writeFileSync(
28
34
  path.join(sessionDir, "ledger.json"),
@@ -31,8 +37,8 @@ export function createSession(paths: PlatformPaths, cwd: string, ledger: E2eSess
31
37
  }
32
38
 
33
39
  /** Load a QA session ledger */
34
- export function loadSession(paths: PlatformPaths, cwd: string, sessionId: string): E2eSessionLedger | null {
35
- const filePath = path.join(getSessionDir(paths, cwd, sessionId), "ledger.json");
40
+ export function loadSession(paths: PlatformPaths, cwd: string, sessionId: string, target?: WorkspaceTarget): E2eSessionLedger | null {
41
+ const filePath = path.join(getSessionDir(paths, cwd, sessionId, target), "ledger.json");
36
42
  if (!fs.existsSync(filePath)) return null;
37
43
  try {
38
44
  return JSON.parse(fs.readFileSync(filePath, "utf-8"));
@@ -42,14 +48,14 @@ export function loadSession(paths: PlatformPaths, cwd: string, sessionId: string
42
48
  }
43
49
 
44
50
  /** Update a QA session ledger */
45
- export function updateSession(paths: PlatformPaths, cwd: string, ledger: E2eSessionLedger): void {
46
- const filePath = path.join(getSessionDir(paths, cwd, ledger.id), "ledger.json");
51
+ export function updateSession(paths: PlatformPaths, cwd: string, ledger: E2eSessionLedger, target?: WorkspaceTarget): void {
52
+ const filePath = path.join(getSessionDir(paths, cwd, ledger.id, target), "ledger.json");
47
53
  fs.writeFileSync(filePath, JSON.stringify(ledger, null, 2) + "\n");
48
54
  }
49
55
 
50
56
  /** List all QA sessions, newest first */
51
- export function listSessions(paths: PlatformPaths, cwd: string): string[] {
52
- const dir = getSessionsDir(paths, cwd);
57
+ export function listSessions(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): string[] {
58
+ const dir = getSessionsDir(paths, cwd, target);
53
59
  if (!fs.existsSync(dir)) return [];
54
60
  return fs
55
61
  .readdirSync(dir)
@@ -59,9 +65,9 @@ export function listSessions(paths: PlatformPaths, cwd: string): string[] {
59
65
  }
60
66
 
61
67
  /** Find the latest session with incomplete phases */
62
- export function findActiveSession(paths: PlatformPaths, cwd: string): E2eSessionLedger | null {
63
- for (const sessionId of listSessions(paths, cwd)) {
64
- const ledger = loadSession(paths, cwd, sessionId);
68
+ export function findActiveSession(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): E2eSessionLedger | null {
69
+ for (const sessionId of listSessions(paths, cwd, target)) {
70
+ const ledger = loadSession(paths, cwd, sessionId, target);
65
71
  if (!ledger) continue;
66
72
  const allCompleted = Object.values(ledger.phases).every(
67
73
  (p) => p.status === "completed",
@@ -72,9 +78,9 @@ export function findActiveSession(paths: PlatformPaths, cwd: string): E2eSession
72
78
  }
73
79
 
74
80
  /** Find the latest session with failed test results */
75
- export function findSessionWithFailures(paths: PlatformPaths, cwd: string): E2eSessionLedger | null {
76
- for (const sessionId of listSessions(paths, cwd)) {
77
- const ledger = loadSession(paths, cwd, sessionId);
81
+ export function findSessionWithFailures(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): E2eSessionLedger | null {
82
+ for (const sessionId of listSessions(paths, cwd, target)) {
83
+ const ledger = loadSession(paths, cwd, sessionId, target);
78
84
  if (!ledger) continue;
79
85
  if (ledger.results.some((r) => r.status === "fail")) return ledger;
80
86
  }
@@ -0,0 +1,180 @@
1
+ // src/storage/reliability-metrics.ts
2
+ //
3
+ // Local-first reliability metrics. Each AI-heavy command appends one
4
+ // ReliabilityRecord per attempt to .omp/supipowers/reliability/events.jsonl.
5
+ // /supi:status and /supi:doctor read these records to surface concrete
6
+ // numbers (parse-success rate, blocked rate, retries-per-run, fallback
7
+ // counts) instead of vibes.
8
+ //
9
+ // Storage format: append-only JSONL. One record per line. Robust to partial
10
+ // writes — readers skip malformed lines rather than aborting.
11
+ //
12
+ // Non-goals:
13
+ // - Hosted telemetry (records never leave the project)
14
+ // - Streaming queries (everything is in-memory after read)
15
+ // - Cross-cwd aggregation in a single read (callers pass the cwd they want)
16
+
17
+ import * as fs from "node:fs";
18
+ import * as path from "node:path";
19
+ import type { PlatformPaths } from "../platform/types.js";
20
+ import { getProjectStatePath } from "../workspace/state-paths.js";
21
+ import type { ReliabilityOutcome, ReliabilityRecord, ReliabilitySummary } from "../types.js";
22
+
23
+ const EVENTS_FILE = "events.jsonl";
24
+ const RELIABILITY_DIR = "reliability";
25
+
26
+ function getReliabilityDir(paths: PlatformPaths, cwd: string): string {
27
+ return getProjectStatePath(paths, cwd, RELIABILITY_DIR);
28
+ }
29
+
30
+ function getEventsPath(paths: PlatformPaths, cwd: string): string {
31
+ return path.join(getReliabilityDir(paths, cwd), EVENTS_FILE);
32
+ }
33
+
34
+ /**
35
+ * Append a single reliability record. Best-effort: failures are swallowed
36
+ * because metrics must never crash the workflow they observe.
37
+ */
38
+ export function appendReliabilityRecord(
39
+ paths: PlatformPaths,
40
+ cwd: string,
41
+ record: ReliabilityRecord,
42
+ ): void {
43
+ try {
44
+ fs.mkdirSync(getReliabilityDir(paths, cwd), { recursive: true });
45
+ fs.appendFileSync(getEventsPath(paths, cwd), JSON.stringify(record) + "\n");
46
+ } catch {
47
+ // Swallow — metrics observability must not break the workflow.
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Read all reliability records for the given cwd. Malformed lines are
53
+ * skipped silently (best-effort recovery). Returns an empty array when the
54
+ * file does not exist yet.
55
+ */
56
+ export function readReliabilityRecords(paths: PlatformPaths, cwd: string): ReliabilityRecord[] {
57
+ const file = getEventsPath(paths, cwd);
58
+ if (!fs.existsSync(file)) return [];
59
+ const records: ReliabilityRecord[] = [];
60
+ let raw: string;
61
+ try {
62
+ raw = fs.readFileSync(file, "utf-8");
63
+ } catch {
64
+ return [];
65
+ }
66
+ for (const line of raw.split("\n")) {
67
+ if (!line.trim()) continue;
68
+ try {
69
+ const parsed = JSON.parse(line) as ReliabilityRecord;
70
+ // Minimal shape check — drop records missing the required fields.
71
+ if (
72
+ typeof parsed?.ts === "string" &&
73
+ typeof parsed.command === "string" &&
74
+ typeof parsed.outcome === "string" &&
75
+ typeof parsed.attempts === "number"
76
+ ) {
77
+ records.push(parsed);
78
+ }
79
+ } catch {
80
+ // Skip malformed line.
81
+ }
82
+ }
83
+ return records;
84
+ }
85
+
86
+ const ZERO_OUTCOME_COUNTS: Record<ReliabilityOutcome, number> = {
87
+ ok: 0,
88
+ blocked: 0,
89
+ "retry-exhausted": 0,
90
+ fallback: 0,
91
+ "agent-error": 0,
92
+ };
93
+
94
+ /**
95
+ * Aggregate records into per-command summaries. Returns one summary per
96
+ * distinct `command` that appears in `records`, sorted by command name.
97
+ */
98
+ export function summarizeReliabilityRecords(records: ReliabilityRecord[]): ReliabilitySummary[] {
99
+ const buckets = new Map<string, ReliabilityRecord[]>();
100
+ for (const record of records) {
101
+ const list = buckets.get(record.command) ?? [];
102
+ list.push(record);
103
+ buckets.set(record.command, list);
104
+ }
105
+
106
+ const summaries: ReliabilitySummary[] = [];
107
+ for (const [command, list] of buckets) {
108
+ const byOutcome = { ...ZERO_OUTCOME_COUNTS };
109
+ let attemptsTotal = 0;
110
+ let lastRecordedAt: string | null = null;
111
+ for (const r of list) {
112
+ byOutcome[r.outcome] = (byOutcome[r.outcome] ?? 0) + 1;
113
+ attemptsTotal += r.attempts;
114
+ if (!lastRecordedAt || r.ts > lastRecordedAt) lastRecordedAt = r.ts;
115
+ }
116
+ summaries.push({
117
+ command,
118
+ total: list.length,
119
+ byOutcome,
120
+ avgAttempts: list.length > 0 ? attemptsTotal / list.length : 0,
121
+ lastRecordedAt,
122
+ });
123
+ }
124
+
125
+ summaries.sort((a, b) => a.command.localeCompare(b.command));
126
+ return summaries;
127
+ }
128
+
129
+ /**
130
+ * Convenience: load + summarize in one call.
131
+ */
132
+ export function loadReliabilitySummaries(
133
+ paths: PlatformPaths,
134
+ cwd: string,
135
+ ): ReliabilitySummary[] {
136
+ return summarizeReliabilityRecords(readReliabilityRecords(paths, cwd));
137
+ }
138
+
139
+ const RELIABILITY_OUTCOMES: ReliabilityOutcome[] = [
140
+ "ok",
141
+ "blocked",
142
+ "retry-exhausted",
143
+ "fallback",
144
+ "agent-error",
145
+ ];
146
+
147
+ /**
148
+ * Render a concise, aligned reliability section suitable for TUI output.
149
+ * When no records exist yet, returns a single non-alarming empty-state line.
150
+ */
151
+ export function formatReliabilitySection(summaries: ReliabilitySummary[]): string[] {
152
+ if (summaries.length === 0) {
153
+ return ["Reliability: no records yet (metrics appear after AI-heavy commands run)."];
154
+ }
155
+
156
+ const total = summaries.reduce((n, s) => n + s.total, 0);
157
+ const nameWidth = Math.max(...summaries.map((s) => s.command.length));
158
+ const colWidths: Record<ReliabilityOutcome, number> = { ...ZERO_OUTCOME_COUNTS };
159
+ for (const s of summaries) {
160
+ for (const outcome of RELIABILITY_OUTCOMES) {
161
+ const w = String(s.byOutcome[outcome] ?? 0).length;
162
+ if (w > colWidths[outcome]) colWidths[outcome] = w;
163
+ }
164
+ }
165
+ for (const outcome of RELIABILITY_OUTCOMES) {
166
+ if (colWidths[outcome] < 1) colWidths[outcome] = 1;
167
+ }
168
+
169
+ const lines: string[] = [`Reliability (last ${total} record${total === 1 ? "" : "s"})`];
170
+ for (const s of summaries) {
171
+ const name = s.command.padEnd(nameWidth);
172
+ const counts = RELIABILITY_OUTCOMES
173
+ .map((o) => `${o} ${String(s.byOutcome[o] ?? 0).padStart(colWidths[o])}`)
174
+ .join(" ");
175
+ const avg = s.avgAttempts.toFixed(1);
176
+ const last = s.lastRecordedAt ? s.lastRecordedAt.slice(0, 10) : "\u2014";
177
+ lines.push(`${name} ${counts} avg-attempts ${avg} last ${last}`);
178
+ }
179
+ return lines;
180
+ }
@@ -1,10 +1,11 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import type { GateResult, GateStatus, ReviewReport } from "../types.js";
4
3
  import type { PlatformPaths } from "../platform/types.js";
4
+ import type { GateResult, GateStatus, ReviewReport, WorkspaceTarget } from "../types.js";
5
+ import { getProjectTargetStatePath } from "../workspace/state-paths.js";
5
6
 
6
- function getReportsDir(paths: PlatformPaths, cwd: string): string {
7
- return paths.project(cwd, "reports");
7
+ function getReportsDir(paths: PlatformPaths, target: WorkspaceTarget): string {
8
+ return getProjectTargetStatePath(paths, target, "reports");
8
9
  }
9
10
 
10
11
  function isGateStatus(value: unknown): value is GateStatus {
@@ -43,8 +44,8 @@ function isReviewReport(value: unknown): value is ReviewReport {
43
44
  }
44
45
 
45
46
  /** Save a review report */
46
- export function saveReviewReport(paths: PlatformPaths, cwd: string, report: ReviewReport): string {
47
- const dir = getReportsDir(paths, cwd);
47
+ export function saveReviewReport(paths: PlatformPaths, target: WorkspaceTarget, report: ReviewReport): string {
48
+ const dir = getReportsDir(paths, target);
48
49
  fs.mkdirSync(dir, { recursive: true });
49
50
  const filename = `review-${report.timestamp.slice(0, 10)}.json`;
50
51
  const filePath = path.join(dir, filename);
@@ -53,8 +54,8 @@ export function saveReviewReport(paths: PlatformPaths, cwd: string, report: Revi
53
54
  }
54
55
 
55
56
  /** Load the latest review report */
56
- export function loadLatestReport(paths: PlatformPaths, cwd: string): ReviewReport | null {
57
- const dir = getReportsDir(paths, cwd);
57
+ export function loadLatestReport(paths: PlatformPaths, target: WorkspaceTarget): ReviewReport | null {
58
+ const dir = getReportsDir(paths, target);
58
59
  if (!fs.existsSync(dir)) return null;
59
60
 
60
61
  const files = fs
@@ -1,15 +1,18 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { PlatformPaths } from "../platform/types.js";
4
- import type { ReviewSession } from "../types.js";
5
4
  import { isReviewSession } from "../review/types.js";
5
+ import type { ReviewSession, WorkspaceTarget } from "../types.js";
6
+ import { getProjectStatePath, getProjectTargetStatePath } from "../workspace/state-paths.js";
6
7
 
7
8
  const SESSIONS_DIR = "reviews";
8
9
  const ITERATIONS_DIR = "iterations";
9
10
  const AGENTS_DIR = "agents";
10
11
 
11
- function getSessionsDir(paths: PlatformPaths, cwd: string): string {
12
- return paths.project(cwd, SESSIONS_DIR);
12
+ function getSessionsDir(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget | null): string {
13
+ return target
14
+ ? getProjectTargetStatePath(paths, target, SESSIONS_DIR)
15
+ : getProjectStatePath(paths, cwd, SESSIONS_DIR);
13
16
  }
14
17
 
15
18
  function ensureLayout(sessionDir: string): void {
@@ -17,8 +20,13 @@ function ensureLayout(sessionDir: string): void {
17
20
  fs.mkdirSync(path.join(sessionDir, AGENTS_DIR), { recursive: true });
18
21
  }
19
22
 
20
- function getLedgerPath(paths: PlatformPaths, cwd: string, sessionId: string): string {
21
- return path.join(getReviewSessionDir(paths, cwd, sessionId), "session.json");
23
+ function getLedgerPath(
24
+ paths: PlatformPaths,
25
+ cwd: string,
26
+ sessionId: string,
27
+ target?: WorkspaceTarget | null,
28
+ ): string {
29
+ return path.join(getReviewSessionDir(paths, cwd, sessionId, target), "session.json");
22
30
  }
23
31
 
24
32
  function resolveArtifactPath(sessionDir: string, relativePath: string): string {
@@ -35,8 +43,13 @@ function resolveArtifactPath(sessionDir: string, relativePath: string): string {
35
43
  return resolved;
36
44
  }
37
45
 
38
- export function getReviewSessionDir(paths: PlatformPaths, cwd: string, sessionId: string): string {
39
- return path.join(getSessionsDir(paths, cwd), sessionId);
46
+ export function getReviewSessionDir(
47
+ paths: PlatformPaths,
48
+ cwd: string,
49
+ sessionId: string,
50
+ target?: WorkspaceTarget | null,
51
+ ): string {
52
+ return path.join(getSessionsDir(paths, cwd, target), sessionId);
40
53
  }
41
54
 
42
55
  export function generateReviewSessionId(now = new Date()): string {
@@ -47,14 +60,24 @@ export function generateReviewSessionId(now = new Date()): string {
47
60
  return `review-${date}-${time}-${suffix}`;
48
61
  }
49
62
 
50
- export function createReviewSession(paths: PlatformPaths, cwd: string, session: ReviewSession): void {
51
- const sessionDir = getReviewSessionDir(paths, cwd, session.id);
63
+ export function createReviewSession(
64
+ paths: PlatformPaths,
65
+ cwd: string,
66
+ session: ReviewSession,
67
+ target?: WorkspaceTarget | null,
68
+ ): void {
69
+ const sessionDir = getReviewSessionDir(paths, cwd, session.id, target);
52
70
  ensureLayout(sessionDir);
53
- fs.writeFileSync(getLedgerPath(paths, cwd, session.id), `${JSON.stringify(session, null, 2)}\n`);
71
+ fs.writeFileSync(getLedgerPath(paths, cwd, session.id, target), `${JSON.stringify(session, null, 2)}\n`);
54
72
  }
55
73
 
56
- export function loadReviewSession(paths: PlatformPaths, cwd: string, sessionId: string): ReviewSession | null {
57
- const ledgerPath = getLedgerPath(paths, cwd, sessionId);
74
+ export function loadReviewSession(
75
+ paths: PlatformPaths,
76
+ cwd: string,
77
+ sessionId: string,
78
+ target?: WorkspaceTarget | null,
79
+ ): ReviewSession | null {
80
+ const ledgerPath = getLedgerPath(paths, cwd, sessionId, target);
58
81
  if (!fs.existsSync(ledgerPath)) {
59
82
  return null;
60
83
  }
@@ -67,15 +90,20 @@ export function loadReviewSession(paths: PlatformPaths, cwd: string, sessionId:
67
90
  }
68
91
  }
69
92
 
70
- export function updateReviewSession(paths: PlatformPaths, cwd: string, session: ReviewSession): void {
93
+ export function updateReviewSession(
94
+ paths: PlatformPaths,
95
+ cwd: string,
96
+ session: ReviewSession,
97
+ target?: WorkspaceTarget | null,
98
+ ): void {
71
99
  session.updatedAt = new Date().toISOString();
72
- const sessionDir = getReviewSessionDir(paths, cwd, session.id);
100
+ const sessionDir = getReviewSessionDir(paths, cwd, session.id, target);
73
101
  ensureLayout(sessionDir);
74
- fs.writeFileSync(getLedgerPath(paths, cwd, session.id), `${JSON.stringify(session, null, 2)}\n`);
102
+ fs.writeFileSync(getLedgerPath(paths, cwd, session.id, target), `${JSON.stringify(session, null, 2)}\n`);
75
103
  }
76
104
 
77
- export function listReviewSessions(paths: PlatformPaths, cwd: string): string[] {
78
- const sessionsDir = getSessionsDir(paths, cwd);
105
+ export function listReviewSessions(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget | null): string[] {
106
+ const sessionsDir = getSessionsDir(paths, cwd, target);
79
107
  if (!fs.existsSync(sessionsDir)) {
80
108
  return [];
81
109
  }
@@ -93,8 +121,9 @@ export function writeReviewArtifact(
93
121
  sessionId: string,
94
122
  relativePath: string,
95
123
  payload: unknown,
124
+ target?: WorkspaceTarget | null,
96
125
  ): string {
97
- const sessionDir = getReviewSessionDir(paths, cwd, sessionId);
126
+ const sessionDir = getReviewSessionDir(paths, cwd, sessionId, target);
98
127
  ensureLayout(sessionDir);
99
128
  const artifactPath = resolveArtifactPath(sessionDir, relativePath);
100
129
  fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
@@ -106,8 +135,14 @@ export function writeReviewArtifact(
106
135
  return artifactPath;
107
136
  }
108
137
 
109
- export function readReviewArtifact(paths: PlatformPaths, cwd: string, sessionId: string, relativePath: string): string | null {
110
- const sessionDir = getReviewSessionDir(paths, cwd, sessionId);
138
+ export function readReviewArtifact(
139
+ paths: PlatformPaths,
140
+ cwd: string,
141
+ sessionId: string,
142
+ relativePath: string,
143
+ target?: WorkspaceTarget | null,
144
+ ): string | null {
145
+ const sessionDir = getReviewSessionDir(paths, cwd, sessionId, target);
111
146
  const artifactPath = resolveArtifactPath(sessionDir, relativePath);
112
147
  if (!fs.existsSync(artifactPath)) {
113
148
  return null;