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,426 @@
1
+ /**
2
+ * Harness pipeline tool registrations.
3
+ *
4
+ * Mirrors `src/ultraplan/authoring/authoring-tools.ts`:
5
+ * - one tool per stage artifact + queue mutation,
6
+ * - thin JSON-schema validation in the harness layer,
7
+ * - thin JSON-shape sanity check before the storage layer's atomic write,
8
+ * - structured `{ok, message?, path?, details?}` returns instead of thrown errors.
9
+ *
10
+ * Tools registered:
11
+ * - harness_discover_record
12
+ * - harness_research_record
13
+ * - harness_decision_record
14
+ * - harness_design_spec_persist
15
+ * - harness_validate_finding
16
+ * - harness_slop_queue_append
17
+ * - harness_slop_queue_resolve
18
+ */
19
+
20
+ import type { Platform } from "../platform/types.js";
21
+ import type {
22
+ HarnessSlopQueueEntry,
23
+ HarnessValidateFinding,
24
+ UltraPlanStorageResult,
25
+ } from "../types.js";
26
+ import {
27
+ appendHarnessDecision,
28
+ appendImplementLog,
29
+ saveHarnessDesignSpec,
30
+ saveHarnessDiscover,
31
+ saveHarnessResearchTopic,
32
+ } from "./storage.js";
33
+ import {
34
+ appendOpen as appendQueueEntry,
35
+ computeQueueEntryId,
36
+ resolve as resolveQueueEntry,
37
+ markWontfix as markQueueWontfix,
38
+ } from "./anti_slop/queue.js";
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Helpers
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function isRecord(value: unknown): value is Record<string, unknown> {
45
+ return typeof value === "object" && value !== null && !Array.isArray(value);
46
+ }
47
+
48
+ interface ToolReturn {
49
+ ok: boolean;
50
+ message?: string;
51
+ path?: string;
52
+ id?: string;
53
+ details?: unknown;
54
+ }
55
+
56
+ function toolResult(payload: ToolReturn): ToolReturn {
57
+ return payload;
58
+ }
59
+
60
+ const SESSION_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9_-])?$/;
61
+
62
+ function isSafeSessionId(value: string): boolean {
63
+ if (!SESSION_ID_PATTERN.test(value)) return false;
64
+ if (value.includes("..")) return false;
65
+ return true;
66
+ }
67
+
68
+ function isSafeTopicSlug(value: string): boolean {
69
+ // Stricter than session id: slugs cannot contain dots so they don't escape via
70
+ // ".." or hidden files.
71
+ return /^[A-Za-z0-9](?:[A-Za-z0-9_-]{0,126}[A-Za-z0-9_-])?$/.test(value);
72
+ }
73
+
74
+ function readSessionId(
75
+ params: unknown,
76
+ toolName: string,
77
+ ): { ok: true; sessionId: string } | { ok: false; message: string } {
78
+ if (!isRecord(params)) return { ok: false, message: `${toolName} requires an object payload` };
79
+ const raw = params.sessionId;
80
+ if (typeof raw !== "string" || raw.trim().length === 0) {
81
+ return { ok: false, message: `${toolName} requires a sessionId string` };
82
+ }
83
+ if (!isSafeSessionId(raw)) {
84
+ return {
85
+ ok: false,
86
+ message: `${toolName} rejected sessionId: must match ${SESSION_ID_PATTERN} and contain no '..' segments`,
87
+ };
88
+ }
89
+ return { ok: true, sessionId: raw };
90
+ }
91
+
92
+ function readCwd(toolCtx: unknown, toolName: string): { ok: true; cwd: string } | { ok: false; message: string } {
93
+ if (isRecord(toolCtx) && typeof toolCtx.cwd === "string" && toolCtx.cwd.trim().length > 0) {
94
+ return { ok: true, cwd: toolCtx.cwd };
95
+ }
96
+ return { ok: false, message: `${toolName} requires a tool context cwd` };
97
+ }
98
+
99
+ function unwrap<T>(result: UltraPlanStorageResult<T>, toolName: string): { ok: true; value: T } | { ok: false; message: string } {
100
+ if (result.ok) return { ok: true, value: result.value };
101
+ const detail = result.error.details && result.error.details.length > 0 ? `: ${result.error.details.join(", ")}` : "";
102
+ return {
103
+ ok: false,
104
+ message: `${toolName} storage error (${result.error.kind}) at ${result.error.path}${detail}: ${result.error.message}`,
105
+ };
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Schema fragments
110
+ // ---------------------------------------------------------------------------
111
+
112
+ const SESSION_ID_PROP = {
113
+ type: "string",
114
+ description: "Harness session id; assigned by the pipeline runner and passed through the agent prompt.",
115
+ } as const;
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Public registration entry point
119
+ // ---------------------------------------------------------------------------
120
+
121
+ /**
122
+ * Register every harness pipeline tool. Idempotent at the harness boundary: when
123
+ * `platform.registerTool` is missing (legacy harness), we silently no-op so existing
124
+ * deploys keep booting.
125
+ */
126
+ export function registerHarnessPipelineTools(platform: Platform): void {
127
+ if (!platform.registerTool) return;
128
+
129
+ // -------- harness_discover_record ----------
130
+ platform.registerTool({
131
+ name: "harness_discover_record",
132
+ label: "Harness Discover Record",
133
+ description: "Record the discover stage artifact for a harness session.",
134
+ parameters: {
135
+ type: "object",
136
+ properties: {
137
+ sessionId: SESSION_ID_PROP,
138
+ artifact: {
139
+ type: "object",
140
+ description: "HarnessDiscoverArtifact JSON. Schema: docs/supipowers/harness.md.",
141
+ },
142
+ },
143
+ required: ["sessionId", "artifact"],
144
+ },
145
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
146
+ const cwdR = readCwd(toolCtx, "harness_discover_record");
147
+ if (!cwdR.ok) return toolResult(cwdR);
148
+ const sidR = readSessionId(params, "harness_discover_record");
149
+ if (!sidR.ok) return toolResult(sidR);
150
+ const p = params as Record<string, unknown>;
151
+ if (!isRecord(p.artifact)) {
152
+ return toolResult({ ok: false, message: "harness_discover_record requires an artifact object" });
153
+ }
154
+ const persisted = saveHarnessDiscover(platform.paths, cwdR.cwd, sidR.sessionId, p.artifact as never);
155
+ const result = unwrap(persisted, "harness_discover_record");
156
+ if (!result.ok) return toolResult(result);
157
+ return toolResult({ ok: true, path: result.value });
158
+ },
159
+ });
160
+
161
+ // -------- harness_research_record ----------
162
+ platform.registerTool({
163
+ name: "harness_research_record",
164
+ label: "Harness Research Record",
165
+ description: "Record a research topic writeup for a harness session.",
166
+ parameters: {
167
+ type: "object",
168
+ properties: {
169
+ sessionId: SESSION_ID_PROP,
170
+ topicSlug: {
171
+ type: "string",
172
+ description: "Slug for this topic (lowercase letters, digits, hyphen, underscore only).",
173
+ },
174
+ markdown: {
175
+ type: "string",
176
+ description: "Full markdown body. Must include `## Options` and `## Recommendation` headings and ≥2 source URLs.",
177
+ },
178
+ },
179
+ required: ["sessionId", "topicSlug", "markdown"],
180
+ },
181
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
182
+ const cwdR = readCwd(toolCtx, "harness_research_record");
183
+ if (!cwdR.ok) return toolResult(cwdR);
184
+ const sidR = readSessionId(params, "harness_research_record");
185
+ if (!sidR.ok) return toolResult(sidR);
186
+ const p = params as Record<string, unknown>;
187
+ const slug = typeof p.topicSlug === "string" ? p.topicSlug : "";
188
+ if (!isSafeTopicSlug(slug)) {
189
+ return toolResult({ ok: false, message: "harness_research_record rejected topicSlug: must be alphanumeric with hyphen/underscore only" });
190
+ }
191
+ const markdown = typeof p.markdown === "string" ? p.markdown : "";
192
+ if (markdown.length === 0) {
193
+ return toolResult({ ok: false, message: "harness_research_record requires a non-empty markdown body" });
194
+ }
195
+ const persisted = saveHarnessResearchTopic(platform.paths, cwdR.cwd, sidR.sessionId, slug, markdown);
196
+ const result = unwrap(persisted, "harness_research_record");
197
+ if (!result.ok) return toolResult(result);
198
+ return toolResult({ ok: true, path: result.value });
199
+ },
200
+ });
201
+
202
+ // -------- harness_decision_record ----------
203
+ platform.registerTool({
204
+ name: "harness_decision_record",
205
+ label: "Harness Decision Record",
206
+ description: "Append a single Design-stage decision to decisions.jsonl.",
207
+ parameters: {
208
+ type: "object",
209
+ properties: {
210
+ sessionId: SESSION_ID_PROP,
211
+ area: { type: "string", description: "Short label for the decision area, e.g. 'anti-slop-backend'." },
212
+ question: { type: "string", description: "The exact question that was answered." },
213
+ decision: { type: "string", description: "The locked answer." },
214
+ rationale: { type: "string", description: "Why this decision was chosen." },
215
+ impact: { type: "array", items: { type: "string" }, description: "Modules / files this decision affects." },
216
+ },
217
+ required: ["sessionId", "area", "question", "decision"],
218
+ },
219
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
220
+ const cwdR = readCwd(toolCtx, "harness_decision_record");
221
+ if (!cwdR.ok) return toolResult(cwdR);
222
+ const sidR = readSessionId(params, "harness_decision_record");
223
+ if (!sidR.ok) return toolResult(sidR);
224
+ const p = params as Record<string, unknown>;
225
+ const area = typeof p.area === "string" ? p.area : "";
226
+ const question = typeof p.question === "string" ? p.question : "";
227
+ const decision = typeof p.decision === "string" ? p.decision : "";
228
+ if (!area || !question || !decision) {
229
+ return toolResult({ ok: false, message: "harness_decision_record requires non-empty area, question, decision" });
230
+ }
231
+ const record: Record<string, unknown> = {
232
+ recordedAt: new Date().toISOString(),
233
+ area,
234
+ question,
235
+ decision,
236
+ ...(typeof p.rationale === "string" ? { rationale: p.rationale } : {}),
237
+ ...(Array.isArray(p.impact) ? { impact: p.impact } : {}),
238
+ };
239
+ const persisted = appendHarnessDecision(platform.paths, cwdR.cwd, sidR.sessionId, record);
240
+ const r = unwrap(persisted, "harness_decision_record");
241
+ if (!r.ok) return toolResult(r);
242
+ return toolResult({ ok: true, path: r.value });
243
+ },
244
+ });
245
+
246
+ // -------- harness_design_spec_persist ----------
247
+ platform.registerTool({
248
+ name: "harness_design_spec_persist",
249
+ label: "Harness Design Spec Persist",
250
+ description: "Persist the rendered design-spec.md for a harness session.",
251
+ parameters: {
252
+ type: "object",
253
+ properties: {
254
+ sessionId: SESSION_ID_PROP,
255
+ markdown: { type: "string", description: "Full markdown body of the design spec." },
256
+ },
257
+ required: ["sessionId", "markdown"],
258
+ },
259
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
260
+ const cwdR = readCwd(toolCtx, "harness_design_spec_persist");
261
+ if (!cwdR.ok) return toolResult(cwdR);
262
+ const sidR = readSessionId(params, "harness_design_spec_persist");
263
+ if (!sidR.ok) return toolResult(sidR);
264
+ const p = params as Record<string, unknown>;
265
+ const markdown = typeof p.markdown === "string" ? p.markdown : "";
266
+ if (markdown.length === 0) {
267
+ return toolResult({ ok: false, message: "harness_design_spec_persist requires a non-empty markdown body" });
268
+ }
269
+ const persisted = saveHarnessDesignSpec(platform.paths, cwdR.cwd, sidR.sessionId, markdown);
270
+ const r = unwrap(persisted, "harness_design_spec_persist");
271
+ if (!r.ok) return toolResult(r);
272
+ return toolResult({ ok: true, path: r.value });
273
+ },
274
+ });
275
+
276
+ // -------- harness_validate_finding ----------
277
+ platform.registerTool({
278
+ name: "harness_validate_finding",
279
+ label: "Harness Validate Finding",
280
+ description: "Append a single Validate-stage finding to the implement log (forensics).",
281
+ parameters: {
282
+ type: "object",
283
+ properties: {
284
+ sessionId: SESSION_ID_PROP,
285
+ finding: {
286
+ type: "object",
287
+ properties: {
288
+ severity: { type: "string", enum: ["error", "warning", "info"] },
289
+ file: { type: "string" },
290
+ line: { type: "integer", minimum: 1 },
291
+ message: { type: "string" },
292
+ remediation: { type: "string" },
293
+ source: { type: "string" },
294
+ },
295
+ required: ["severity", "file", "message", "remediation", "source"],
296
+ },
297
+ },
298
+ required: ["sessionId", "finding"],
299
+ },
300
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
301
+ const cwdR = readCwd(toolCtx, "harness_validate_finding");
302
+ if (!cwdR.ok) return toolResult(cwdR);
303
+ const sidR = readSessionId(params, "harness_validate_finding");
304
+ if (!sidR.ok) return toolResult(sidR);
305
+ const p = params as Record<string, unknown>;
306
+ if (!isRecord(p.finding)) {
307
+ return toolResult({ ok: false, message: "harness_validate_finding requires a finding object" });
308
+ }
309
+ const finding = p.finding as unknown as HarnessValidateFinding;
310
+ const persisted = appendImplementLog(platform.paths, cwdR.cwd, sidR.sessionId, {
311
+ kind: "validate-finding",
312
+ recordedAt: new Date().toISOString(),
313
+ finding,
314
+ });
315
+ const r = unwrap(persisted, "harness_validate_finding");
316
+ if (!r.ok) return toolResult(r);
317
+ return toolResult({ ok: true, path: r.value });
318
+ },
319
+ });
320
+
321
+ // -------- harness_slop_queue_append ----------
322
+ platform.registerTool({
323
+ name: "harness_slop_queue_append",
324
+ label: "Harness Slop Queue Append",
325
+ description: "Append a slop violation to the project's persistent queue.",
326
+ parameters: {
327
+ type: "object",
328
+ properties: {
329
+ kind: { type: "string", enum: ["duplicate", "dead-code", "layer-violation", "naming", "file-too-large", "complexity", "circular-dependency", "other"] },
330
+ file: { type: "string" },
331
+ startLine: { type: "integer", minimum: 1 },
332
+ endLine: { type: "integer", minimum: 1 },
333
+ severity: { type: "string", enum: ["blocker", "warning", "info"] },
334
+ source: { type: "string", enum: ["fallow", "desloppify", "checks", "review", "supi-native"] },
335
+ message: { type: "string" },
336
+ remediation: { type: "string" },
337
+ ruleHint: { type: "string", description: "Optional rule slug from the source backend; combined into the queue id hash." },
338
+ },
339
+ required: ["kind", "file", "severity", "source", "message"],
340
+ },
341
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
342
+ const cwdR = readCwd(toolCtx, "harness_slop_queue_append");
343
+ if (!cwdR.ok) return toolResult(cwdR);
344
+ if (!isRecord(params)) {
345
+ return toolResult({ ok: false, message: "harness_slop_queue_append requires an object payload" });
346
+ }
347
+ const p = params as Record<string, unknown>;
348
+ const range =
349
+ typeof p.startLine === "number"
350
+ ? { startLine: p.startLine, endLine: typeof p.endLine === "number" ? p.endLine : p.startLine }
351
+ : null;
352
+ const id = computeQueueEntryId({
353
+ kind: p.kind as HarnessSlopQueueEntry["kind"],
354
+ file: typeof p.file === "string" ? p.file : "",
355
+ range,
356
+ ruleHint: typeof p.ruleHint === "string" ? p.ruleHint : undefined,
357
+ });
358
+ const entry: HarnessSlopQueueEntry = {
359
+ id,
360
+ kind: p.kind as HarnessSlopQueueEntry["kind"],
361
+ file: typeof p.file === "string" ? p.file : "",
362
+ range,
363
+ severity: p.severity as HarnessSlopQueueEntry["severity"],
364
+ source: p.source as HarnessSlopQueueEntry["source"],
365
+ state: "open",
366
+ message: typeof p.message === "string" ? p.message : "",
367
+ remediation: typeof p.remediation === "string" ? p.remediation : undefined,
368
+ ts: new Date().toISOString(),
369
+ };
370
+ const persisted = appendQueueEntry(platform.paths, cwdR.cwd, entry);
371
+ const r = unwrap(persisted, "harness_slop_queue_append");
372
+ if (!r.ok) return toolResult(r);
373
+ return toolResult({ ok: true, id, path: r.value });
374
+ },
375
+ });
376
+
377
+ // -------- harness_slop_queue_resolve ----------
378
+ platform.registerTool({
379
+ name: "harness_slop_queue_resolve",
380
+ label: "Harness Slop Queue Resolve",
381
+ description: "Mark a queue entry as resolved or wontfix.",
382
+ parameters: {
383
+ type: "object",
384
+ properties: {
385
+ id: { type: "string", description: "The queue entry id." },
386
+ state: { type: "string", enum: ["resolved", "wontfix"] },
387
+ },
388
+ required: ["id", "state"],
389
+ },
390
+ async execute(_id: string, params: unknown, _signal: AbortSignal, _onUpdate: unknown, toolCtx: unknown) {
391
+ const cwdR = readCwd(toolCtx, "harness_slop_queue_resolve");
392
+ if (!cwdR.ok) return toolResult(cwdR);
393
+ if (!isRecord(params)) {
394
+ return toolResult({ ok: false, message: "harness_slop_queue_resolve requires an object payload" });
395
+ }
396
+ const p = params as Record<string, unknown>;
397
+ const id = typeof p.id === "string" ? p.id : "";
398
+ const state = typeof p.state === "string" ? p.state : "";
399
+ if (!id || (state !== "resolved" && state !== "wontfix")) {
400
+ return toolResult({ ok: false, message: "harness_slop_queue_resolve requires id and state ∈ {resolved, wontfix}" });
401
+ }
402
+ const result = state === "resolved"
403
+ ? resolveQueueEntry(platform.paths, cwdR.cwd, id)
404
+ : markQueueWontfix(platform.paths, cwdR.cwd, id);
405
+ const r = unwrap(result, "harness_slop_queue_resolve");
406
+ if (!r.ok) return toolResult(r);
407
+ if (r.value === null) {
408
+ return toolResult({ ok: false, message: `harness_slop_queue_resolve: id ${id} not found` });
409
+ }
410
+ return toolResult({ ok: true, id, details: { state } });
411
+ },
412
+ });
413
+ }
414
+
415
+ /** Names exposed for hook-bridge correlation. */
416
+ export const HARNESS_PIPELINE_TOOL_NAMES = [
417
+ "harness_discover_record",
418
+ "harness_research_record",
419
+ "harness_decision_record",
420
+ "harness_design_spec_persist",
421
+ "harness_validate_finding",
422
+ "harness_slop_queue_append",
423
+ "harness_slop_queue_resolve",
424
+ ] as const;
425
+
426
+ export type HarnessPipelineToolName = (typeof HARNESS_PIPELINE_TOOL_NAMES)[number];
package/src/lsp/bridge.ts CHANGED
@@ -1,93 +1,25 @@
1
1
  // src/lsp/bridge.ts
2
2
  import type { GateExecutionContext, GateIssue } from "../types.js";
3
- import { runStructuredAgentSession } from "../quality/ai-session.js";
4
- import { stripMarkdownCodeFence } from "../text.js";
5
-
6
- export interface DiagnosticsResult {
7
- file: string;
8
- diagnostics: Diagnostic[];
9
- }
10
-
11
- export interface Diagnostic {
12
- severity: "error" | "warning" | "info" | "hint";
13
- message: string;
14
- line: number;
15
- column: number;
16
- }
17
-
18
- function isDiagnostic(value: unknown): value is Diagnostic {
19
- return (
20
- typeof value === "object" &&
21
- value !== null &&
22
- ["error", "warning", "info", "hint"].includes((value as Diagnostic).severity) &&
23
- typeof (value as Diagnostic).message === "string" &&
24
- typeof (value as Diagnostic).line === "number" &&
25
- typeof (value as Diagnostic).column === "number"
26
- );
27
- }
28
-
29
- function isDiagnosticsResult(value: unknown): value is DiagnosticsResult {
30
- return (
31
- typeof value === "object" &&
32
- value !== null &&
33
- typeof (value as DiagnosticsResult).file === "string" &&
34
- Array.isArray((value as DiagnosticsResult).diagnostics) &&
35
- (value as DiagnosticsResult).diagnostics.every((diagnostic) => isDiagnostic(diagnostic))
36
- );
37
- }
38
-
39
- function normalizeDiagnosticSeverity(severity: Diagnostic["severity"]): GateIssue["severity"] {
3
+ import { parseStructuredOutput, runWithOutputValidation, type ReliabilityReporter } from "../ai/structured-output.js";
4
+ import { renderSchemaText } from "../ai/schema-text.js";
5
+ import { appendReliabilityRecord } from "../storage/reliability-metrics.js";
6
+ import { probeLspCapabilities } from "./capabilities.js";
7
+ import {
8
+ LspDiagnosticsResultsSchema,
9
+ type LspDiagnostic,
10
+ type LspDiagnosticsResults,
11
+ } from "./contracts.js";
12
+
13
+ const LSP_DIAGNOSTICS_SCHEMA_TEXT = renderSchemaText(LspDiagnosticsResultsSchema);
14
+
15
+ function normalizeDiagnosticSeverity(severity: LspDiagnostic["severity"]): GateIssue["severity"] {
40
16
  return severity === "hint" ? "info" : severity;
41
17
  }
42
18
 
43
- function formatDiagnosticDetail(diagnostic: Diagnostic): string | undefined {
19
+ function formatDiagnosticDetail(diagnostic: LspDiagnostic): string | undefined {
44
20
  return diagnostic.column > 0 ? `column ${diagnostic.column}` : undefined;
45
21
  }
46
22
 
47
- function extractJsonArray(raw: string): unknown | null {
48
- const trimmed = raw.trim();
49
-
50
- // 1. Try direct parse (no fence)
51
- try {
52
- const parsed = JSON.parse(trimmed);
53
- if (Array.isArray(parsed)) return parsed;
54
- } catch {}
55
-
56
- // 2. Strip markdown code fence and retry
57
- const stripped = stripMarkdownCodeFence(trimmed);
58
- if (stripped !== trimmed) {
59
- try {
60
- const parsed = JSON.parse(stripped);
61
- if (Array.isArray(parsed)) return parsed;
62
- } catch {}
63
- }
64
-
65
- // 3. Extract first JSON array from prose via bracket matching
66
- const start = trimmed.indexOf("[");
67
- if (start === -1) return null;
68
-
69
- let depth = 0;
70
- for (let i = start; i < trimmed.length; i++) {
71
- if (trimmed[i] === "[") depth++;
72
- else if (trimmed[i] === "]") depth--;
73
- if (depth === 0) {
74
- try {
75
- const parsed = JSON.parse(trimmed.slice(start, i + 1));
76
- if (Array.isArray(parsed)) return parsed;
77
- } catch {}
78
- break;
79
- }
80
- }
81
-
82
- return null;
83
- }
84
-
85
- function parseLspDiagnosticsResults(raw: string): DiagnosticsResult[] | null {
86
- const parsed = extractJsonArray(raw);
87
- if (!Array.isArray(parsed)) return null;
88
- return parsed.every((entry) => isDiagnosticsResult(entry)) ? parsed : null;
89
- }
90
-
91
23
  /**
92
24
  * Request LSP diagnostics via a headless agent session that can call the lsp tool.
93
25
  */
@@ -112,8 +44,8 @@ export function buildLspDiagnosticsPrompt(
112
44
  "You are collecting structured LSP diagnostics for a review quality gate.",
113
45
  ...scopeInstruction,
114
46
  "",
115
- "Return JSON only as an array with this exact shape:",
116
- '[{"file":"path/to/file.ts","diagnostics":[{"severity":"error|warning|info|hint","message":"text","line":1,"column":1}]}]',
47
+ "Return JSON only matching this schema:",
48
+ LSP_DIAGNOSTICS_SCHEMA_TEXT,
117
49
  "",
118
50
  "Rules:",
119
51
  "- Do not wrap the JSON in markdown fences.",
@@ -128,29 +60,58 @@ export async function collectLspDiagnostics(options: {
128
60
  fileScope: GateExecutionContext["fileScope"];
129
61
  createAgentSession: GateExecutionContext["createAgentSession"];
130
62
  reviewModel?: GateExecutionContext["reviewModel"];
63
+ reliability?: ReliabilityReporter;
131
64
  }): Promise<GateIssue[]> {
132
- const sessionResult = await runStructuredAgentSession(options.createAgentSession, {
65
+ const caps = await probeLspCapabilities({
66
+ cwd: options.cwd,
67
+ createAgentSession: options.createAgentSession,
68
+ reviewModel: options.reviewModel,
69
+ reliability: options.reliability,
70
+ });
71
+ if (!caps.diagnostics) {
72
+ // Active LSP server is registered but advertises no
73
+ // textDocument/diagnostic support — fail the gate cleanly with an
74
+ // empty issue list rather than throwing on a vacuous probe error.
75
+ // Emit a single reliability record so /supi:doctor surfaces the skip.
76
+ if (options.reliability) {
77
+ try {
78
+ appendReliabilityRecord(options.reliability.paths, options.reliability.cwd, {
79
+ ts: new Date().toISOString(),
80
+ command: options.reliability.command,
81
+ operation: options.reliability.operation,
82
+ outcome: "fallback",
83
+ attempts: 0,
84
+ reason: "lsp-server-lacks-diagnostics",
85
+ cwd: options.reliability.cwd,
86
+ });
87
+ } catch {
88
+ // appendReliabilityRecord already swallows its own errors; this
89
+ // try/catch is belt-and-braces in case the helper itself throws.
90
+ }
91
+ }
92
+ return [];
93
+ }
94
+
95
+ const result = await runWithOutputValidation<LspDiagnosticsResults>(options.createAgentSession, {
133
96
  cwd: options.cwd,
134
97
  prompt: buildLspDiagnosticsPrompt(options.scopeFiles, options.fileScope),
98
+ schema: LSP_DIAGNOSTICS_SCHEMA_TEXT,
99
+ parse: (raw) => parseStructuredOutput<LspDiagnosticsResults>(raw, LspDiagnosticsResultsSchema),
135
100
  model: options.reviewModel?.model,
136
101
  thinkingLevel: options.reviewModel?.thinkingLevel ?? null,
137
102
  timeoutMs: 120_000,
103
+ reliability: options.reliability,
138
104
  });
139
105
 
140
- if (sessionResult.status !== "ok") {
141
- throw new Error(sessionResult.error);
142
- }
143
-
144
- const parsed = parseLspDiagnosticsResults(sessionResult.finalText);
145
- if (!parsed) {
146
- throw new Error("LSP diagnostics integration returned invalid JSON.");
106
+ if (result.status === "blocked") {
107
+ throw new Error(`LSP diagnostics integration failed: ${result.error}`);
147
108
  }
148
109
 
149
- return parsed.flatMap((result) =>
150
- result.diagnostics.map<GateIssue>((diagnostic) => ({
110
+ return result.output.flatMap((entry) =>
111
+ entry.diagnostics.map<GateIssue>((diagnostic) => ({
151
112
  severity: normalizeDiagnosticSeverity(diagnostic.severity),
152
113
  message: diagnostic.message,
153
- file: result.file,
114
+ file: entry.file,
154
115
  line: diagnostic.line,
155
116
  detail: formatDiagnosticDetail(diagnostic),
156
117
  })),