supipowers 1.5.3 → 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 +131 -42
  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 +18 -8
  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 +221 -109
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +114 -13
  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 +1 -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 +1399 -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,144 @@
1
+ // src/discovery/rank.ts
2
+ //
3
+ // Deterministic ranking over a set of candidate paths. Each scoring source
4
+ // contributes a weighted score and a rationale line; the final ranking is
5
+ // the sum of contributions, sorted desc and then lex for stability.
6
+
7
+ export interface DiscoveryInput {
8
+ cwd: string;
9
+ /** Absolute or cwd-relative repo root. */
10
+ repoRoot: string;
11
+ /** Free-text workflow query, e.g. "fix the login bug". Used for path-token scoring. */
12
+ query?: string;
13
+ /** Files changed in the current context (git diff, uncommitted, PR scope). */
14
+ changedFiles?: string[];
15
+ /**
16
+ * All discoverable files to consider. If omitted, only `changedFiles` are
17
+ * scored. Callers should keep this list pre-filtered to reasonable size.
18
+ */
19
+ candidatePool?: string[];
20
+ /**
21
+ * Additional per-source boost map: path → { score, rationale }. Used by
22
+ * workflow-specific callers (e.g. fix-pr injecting files mentioned in PR
23
+ * comments).
24
+ */
25
+ externalSignals?: Record<string, { score: number; rationale: string }>;
26
+ /** Cap the returned ranked list. Default: 20. */
27
+ limit?: number;
28
+ }
29
+
30
+ export type DiscoverySource =
31
+ | "changed"
32
+ | "query-path-match"
33
+ | "external-signal"
34
+ | "lsp";
35
+
36
+ export interface DiscoveryCandidate {
37
+ path: string;
38
+ score: number;
39
+ /** Each source that contributed to the score. */
40
+ sources: DiscoverySource[];
41
+ /** Human-readable reasons explaining the score. */
42
+ rationale: string[];
43
+ }
44
+
45
+ export interface DiscoveryResult {
46
+ candidates: DiscoveryCandidate[];
47
+ /** Every source that touched at least one candidate, for observability. */
48
+ sourcesUsed: DiscoverySource[];
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Source weights. Small and explicit — avoid hidden tuning.
53
+ // ---------------------------------------------------------------------------
54
+
55
+ const WEIGHT_CHANGED = 10;
56
+ const WEIGHT_QUERY_TOKEN = 2;
57
+ const MIN_QUERY_TOKEN_LENGTH = 4;
58
+
59
+ function normalizePath(p: string): string {
60
+ return p.replace(/\\/g, "/").replace(/^\.\//, "");
61
+ }
62
+
63
+ function tokenize(query: string): string[] {
64
+ return [
65
+ ...new Set(
66
+ query
67
+ .toLowerCase()
68
+ .split(/[^a-z0-9_-]+/)
69
+ .filter((t) => t.length >= MIN_QUERY_TOKEN_LENGTH),
70
+ ),
71
+ ];
72
+ }
73
+
74
+ function countTokenHitsInPath(path: string, tokens: string[]): number {
75
+ if (tokens.length === 0) return 0;
76
+ const lower = path.toLowerCase();
77
+ let hits = 0;
78
+ for (const t of tokens) {
79
+ if (lower.includes(t)) hits += 1;
80
+ }
81
+ return hits;
82
+ }
83
+
84
+ /**
85
+ * Rank candidate files. Deterministic given identical input: sort is by
86
+ * score desc, then path asc, so ties resolve stably.
87
+ */
88
+ export function rankDiscoveryCandidates(input: DiscoveryInput): DiscoveryResult {
89
+ const changedFiles = new Set((input.changedFiles ?? []).map(normalizePath));
90
+ const pool = new Set<string>([
91
+ ...changedFiles,
92
+ ...(input.candidatePool ?? []).map(normalizePath),
93
+ ...Object.keys(input.externalSignals ?? {}).map(normalizePath),
94
+ ]);
95
+
96
+ const queryTokens = input.query ? tokenize(input.query) : [];
97
+ const sourcesUsed = new Set<DiscoverySource>();
98
+
99
+ const candidates: DiscoveryCandidate[] = [];
100
+ for (const path of pool) {
101
+ const rationale: string[] = [];
102
+ const sources: DiscoverySource[] = [];
103
+ let score = 0;
104
+
105
+ if (changedFiles.has(path)) {
106
+ score += WEIGHT_CHANGED;
107
+ sources.push("changed");
108
+ sourcesUsed.add("changed");
109
+ rationale.push("changed in current context");
110
+ }
111
+
112
+ const tokenHits = countTokenHitsInPath(path, queryTokens);
113
+ if (tokenHits > 0) {
114
+ const contribution = tokenHits * WEIGHT_QUERY_TOKEN;
115
+ score += contribution;
116
+ sources.push("query-path-match");
117
+ sourcesUsed.add("query-path-match");
118
+ rationale.push(`path matches ${tokenHits} query token${tokenHits === 1 ? "" : "s"}`);
119
+ }
120
+
121
+ const external = input.externalSignals?.[path];
122
+ if (external) {
123
+ score += external.score;
124
+ sources.push("external-signal");
125
+ sourcesUsed.add("external-signal");
126
+ rationale.push(external.rationale);
127
+ }
128
+
129
+ if (score > 0) {
130
+ candidates.push({ path, score, sources, rationale });
131
+ }
132
+ }
133
+
134
+ candidates.sort((a, b) => {
135
+ if (b.score !== a.score) return b.score - a.score;
136
+ return a.path.localeCompare(b.path);
137
+ });
138
+
139
+ const limit = input.limit ?? 20;
140
+ return {
141
+ candidates: candidates.slice(0, limit),
142
+ sourcesUsed: [...sourcesUsed].sort(),
143
+ };
144
+ }
@@ -0,0 +1,89 @@
1
+ // src/discovery/sources.ts
2
+ //
3
+ // Side-effectful discovery helpers that read from the filesystem / git /
4
+ // workspace. Kept in a dedicated module so `rank.ts` stays pure and unit-
5
+ // testable without mocks.
6
+
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+ import type { DiscoveryInput, DiscoveryResult } from "./rank.js";
10
+ import { rankDiscoveryCandidates } from "./rank.js";
11
+
12
+ export interface SourcesDiscoveryOptions extends Omit<DiscoveryInput, "candidatePool"> {
13
+ /**
14
+ * Glob-lite allow-list of path suffixes to include in the candidate pool
15
+ * (e.g. [".ts", ".tsx"]). Empty means no suffix filter.
16
+ */
17
+ extensions?: string[];
18
+ /**
19
+ * Max pool size after filesystem walk. Prevents huge repos from dominating
20
+ * candidate evaluation. Default: 5000.
21
+ */
22
+ maxPoolSize?: number;
23
+ /**
24
+ * Directory names to skip while walking. Defaults cover `.git`, `node_modules`,
25
+ * `dist`, `.omp`, `.cache`.
26
+ */
27
+ excludeDirs?: string[];
28
+ }
29
+
30
+ const DEFAULT_EXCLUDES = new Set([".git", "node_modules", "dist", "build", ".omp", ".cache", ".next", ".turbo"]);
31
+
32
+ function walkFiles(
33
+ root: string,
34
+ options: { extensions?: string[]; maxPoolSize: number; excludeDirs: Set<string> },
35
+ ): string[] {
36
+ const results: string[] = [];
37
+ const stack: string[] = [root];
38
+ const extFilter = options.extensions && options.extensions.length > 0 ? options.extensions : null;
39
+
40
+ while (stack.length > 0 && results.length < options.maxPoolSize) {
41
+ const current = stack.pop()!;
42
+ let entries: fs.Dirent[];
43
+ try {
44
+ entries = fs.readdirSync(current, { withFileTypes: true });
45
+ } catch {
46
+ continue;
47
+ }
48
+
49
+ for (const entry of entries) {
50
+ if (options.excludeDirs.has(entry.name)) continue;
51
+ const full = path.join(current, entry.name);
52
+ if (entry.isDirectory()) {
53
+ stack.push(full);
54
+ continue;
55
+ }
56
+ if (!entry.isFile()) continue;
57
+ if (extFilter && !extFilter.some((ext) => entry.name.endsWith(ext))) continue;
58
+ results.push(path.relative(root, full));
59
+ if (results.length >= options.maxPoolSize) break;
60
+ }
61
+ }
62
+
63
+ return results;
64
+ }
65
+
66
+ /**
67
+ * Walk `repoRoot`, build a candidate pool, then rank. Deterministic given
68
+ * the same filesystem state.
69
+ */
70
+ export function discoverFromSources(options: SourcesDiscoveryOptions): DiscoveryResult {
71
+ const excludeDirs = new Set([...DEFAULT_EXCLUDES, ...(options.excludeDirs ?? [])]);
72
+ const maxPoolSize = options.maxPoolSize ?? 5000;
73
+
74
+ const candidatePool = walkFiles(options.repoRoot, {
75
+ extensions: options.extensions,
76
+ maxPoolSize,
77
+ excludeDirs,
78
+ });
79
+
80
+ return rankDiscoveryCandidates({
81
+ cwd: options.cwd,
82
+ repoRoot: options.repoRoot,
83
+ query: options.query,
84
+ changedFiles: options.changedFiles,
85
+ candidatePool,
86
+ externalSignals: options.externalSignals,
87
+ limit: options.limit,
88
+ });
89
+ }
@@ -0,0 +1,87 @@
1
+ // src/discovery/workflow.ts
2
+ //
3
+ // High-level integration helper for workflows. Commands (/supi:review,
4
+ // /supi:plan, /supi:qa, /supi:fix-pr) adopt discovery by calling
5
+ // `suggestCandidatesForWorkflow` with the context they already have. The
6
+ // helper composes the deterministic ranker, optional LSP augmentation, and
7
+ // a concise rationale formatter so workflows don't wire the pieces
8
+ // themselves.
9
+ //
10
+ // The helper is safe-by-default: when no inputs are provided, it returns
11
+ // an empty result rather than scanning the whole repo. Workflows that want
12
+ // a full filesystem pool should call `discoverFromSources` directly.
13
+
14
+ import { rankDiscoveryCandidates, type DiscoveryCandidate, type DiscoveryInput } from "./rank.js";
15
+ import { rankWithLspAugmentation, type LspSymbolLocation } from "./lsp.js";
16
+
17
+ export interface WorkflowDiscoveryInput extends Omit<DiscoveryInput, "candidatePool"> {
18
+ /**
19
+ * Optional pre-filtered candidate pool (e.g. tracked files for a workspace
20
+ * target). When omitted, only changedFiles + externalSignals seed the pool.
21
+ */
22
+ candidatePool?: string[];
23
+ /**
24
+ * Optional LSP symbol lookup. When provided, LSP hits are folded in as
25
+ * external signals; failures degrade to the non-LSP ranking.
26
+ */
27
+ querySymbols?: (query: string) => LspSymbolLocation[] | Promise<LspSymbolLocation[]>;
28
+ }
29
+
30
+ export interface WorkflowDiscoveryResult {
31
+ candidates: DiscoveryCandidate[];
32
+ /** True when LSP augmentation ran successfully. False when disabled or it threw. */
33
+ lspUsed: boolean;
34
+ /** Short formatted summary lines suitable for notify / log / prompt injection. */
35
+ summaryLines: string[];
36
+ }
37
+
38
+ function formatSummary(candidates: DiscoveryCandidate[], maxLines = 5): string[] {
39
+ return candidates.slice(0, maxLines).map((c) => {
40
+ const why = c.rationale.join("; ");
41
+ return `${c.path} (score ${c.score}) — ${why}`;
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Produce ranked candidates for a workflow. Always safe to call — returns
47
+ * empty candidates when no signals are provided. Prefer this over calling
48
+ * `rankDiscoveryCandidates` / `rankWithLspAugmentation` directly so every
49
+ * workflow uses the same composition and rationale format.
50
+ */
51
+ export async function suggestCandidatesForWorkflow(
52
+ input: WorkflowDiscoveryInput,
53
+ ): Promise<WorkflowDiscoveryResult> {
54
+ if (input.querySymbols) {
55
+ const result = await rankWithLspAugmentation({
56
+ cwd: input.cwd,
57
+ repoRoot: input.repoRoot,
58
+ query: input.query,
59
+ changedFiles: input.changedFiles,
60
+ candidatePool: input.candidatePool,
61
+ externalSignals: input.externalSignals,
62
+ limit: input.limit,
63
+ querySymbols: input.querySymbols,
64
+ });
65
+ return {
66
+ candidates: result.candidates,
67
+ lspUsed: result.lspAvailable && result.lspHitCount > 0,
68
+ summaryLines: formatSummary(result.candidates),
69
+ };
70
+ }
71
+
72
+ const ranked = rankDiscoveryCandidates({
73
+ cwd: input.cwd,
74
+ repoRoot: input.repoRoot,
75
+ query: input.query,
76
+ changedFiles: input.changedFiles,
77
+ candidatePool: input.candidatePool,
78
+ externalSignals: input.externalSignals,
79
+ limit: input.limit,
80
+ });
81
+
82
+ return {
83
+ candidates: ranked.candidates,
84
+ lspUsed: false,
85
+ summaryLines: formatSummary(ranked.candidates),
86
+ };
87
+ }
@@ -0,0 +1,39 @@
1
+ // src/docs/contracts.ts
2
+ //
3
+ // Schema-backed contract for doc-drift sub-agent output. Every doc-drift
4
+ // sub-agent must emit JSON that parses against DocDriftOutputSchema. The
5
+ // retry loop (runWithOutputValidation) will hand validation errors back to
6
+ // the model rather than letting a silent regex heuristic invent findings.
7
+
8
+ import { Type, type Static } from "@sinclair/typebox";
9
+
10
+ export const DOC_DRIFT_SEVERITIES = ["info", "warning", "error"] as const;
11
+ export const DOC_DRIFT_STATUSES = ["ok", "drifted"] as const;
12
+
13
+ export type DocDriftSeverity = (typeof DOC_DRIFT_SEVERITIES)[number];
14
+ export type DocDriftStatus = (typeof DOC_DRIFT_STATUSES)[number];
15
+
16
+ export const DocDriftFindingSchema = Type.Object(
17
+ {
18
+ file: Type.String({ minLength: 1 }),
19
+ description: Type.String({ minLength: 1 }),
20
+ severity: Type.Union(
21
+ DOC_DRIFT_SEVERITIES.map((value) => Type.Literal(value)),
22
+ ),
23
+ relatedFiles: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
24
+ },
25
+ { additionalProperties: false },
26
+ );
27
+
28
+ export const DocDriftOutputSchema = Type.Object(
29
+ {
30
+ findings: Type.Array(DocDriftFindingSchema),
31
+ status: Type.Union(
32
+ DOC_DRIFT_STATUSES.map((value) => Type.Literal(value)),
33
+ ),
34
+ },
35
+ { additionalProperties: false },
36
+ );
37
+
38
+ export type DocDriftFinding = Static<typeof DocDriftFindingSchema>;
39
+ export type DocDriftOutput = Static<typeof DocDriftOutputSchema>;
package/src/docs/drift.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { Platform, PlatformPaths } from "../platform/types.js";
4
- import type { DocDriftState, DriftCheckResult, DriftFinding } from "../types.js";
5
- import { runStructuredAgentSession } from "../quality/ai-session.js";
4
+ import type { DocDriftState, DriftCheckResult, DriftFinding, WorkspaceTarget } from "../types.js";
5
+ import { runWithOutputValidation, parseStructuredOutput } from "../ai/structured-output.js";
6
+ import { renderSchemaText } from "../ai/schema-text.js";
7
+ import { DocDriftOutputSchema, type DocDriftOutput } from "./contracts.js";
8
+ import { ReleaseDocFixOutputSchema } from "../release/contracts.js";
9
+ import { filterPathsForWorkspaceTarget } from "../workspace/path-mapping.js";
10
+ import { getProjectStatePath, getProjectTargetStatePath } from "../workspace/state-paths.js";
6
11
 
7
12
  // ── State persistence ─────────────────────────────────────────
8
13
 
@@ -14,21 +19,41 @@ const EMPTY_STATE: DocDriftState = {
14
19
  lastRunAt: null,
15
20
  };
16
21
 
17
- export function statePath(paths: PlatformPaths, cwd: string): string {
18
- return paths.project(cwd, STATE_FILENAME);
22
+ export interface DocDriftScope<TTarget extends WorkspaceTarget = WorkspaceTarget> {
23
+ target: TTarget;
24
+ allTargets: TTarget[];
19
25
  }
20
26
 
21
- export function loadState(paths: PlatformPaths, cwd: string): DocDriftState {
22
- const file = statePath(paths, cwd);
27
+ function filterTrackedFilesToScope(
28
+ trackedFiles: string[],
29
+ scope?: DocDriftScope,
30
+ ): string[] {
31
+ if (!scope) {
32
+ return trackedFiles;
33
+ }
34
+
35
+ return filterPathsForWorkspaceTarget(scope.allTargets, scope.target, trackedFiles);
36
+ }
37
+
38
+ export function statePath(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): string {
39
+ return target ? getProjectTargetStatePath(paths, target, STATE_FILENAME) : getProjectStatePath(paths, cwd, STATE_FILENAME);
40
+ }
41
+
42
+ export function loadState(paths: PlatformPaths, cwd: string, scope?: DocDriftScope): DocDriftState {
43
+ const file = statePath(paths, cwd, scope?.target);
23
44
  try {
24
- return JSON.parse(fs.readFileSync(file, "utf-8")) as DocDriftState;
45
+ const state = JSON.parse(fs.readFileSync(file, "utf-8")) as DocDriftState;
46
+ return {
47
+ ...state,
48
+ trackedFiles: filterTrackedFilesToScope(state.trackedFiles, scope),
49
+ };
25
50
  } catch {
26
51
  return { ...EMPTY_STATE, trackedFiles: [] };
27
52
  }
28
53
  }
29
54
 
30
- export function saveState(paths: PlatformPaths, cwd: string, state: DocDriftState): void {
31
- const file = statePath(paths, cwd);
55
+ export function saveState(paths: PlatformPaths, cwd: string, state: DocDriftState, target?: WorkspaceTarget): void {
56
+ const file = statePath(paths, cwd, target);
32
57
  fs.mkdirSync(path.dirname(file), { recursive: true });
33
58
  fs.writeFileSync(file, JSON.stringify(state, null, 2) + "\n");
34
59
  }
@@ -77,6 +102,7 @@ export function isProjectDoc(filePath: string): boolean {
77
102
  export async function discoverDocFiles(
78
103
  platform: Platform,
79
104
  cwd: string,
105
+ scope?: DocDriftScope,
80
106
  ): Promise<string[]> {
81
107
  const result = await platform.exec(
82
108
  "git",
@@ -85,13 +111,16 @@ export async function discoverDocFiles(
85
111
  );
86
112
  if (result.code !== 0) return [];
87
113
 
88
- const files = result.stdout
89
- .split("\n")
90
- .map((f) => f.trim())
91
- .filter((f) => f.length > 0 && isProjectDoc(f));
114
+ const files = [...new Set(
115
+ result.stdout
116
+ .split("\n")
117
+ .map((f) => f.trim())
118
+ .filter((f) => f.length > 0 && isProjectDoc(f)),
119
+ )].sort();
92
120
 
93
- // Deduplicate (globs may overlap)
94
- return [...new Set(files)].sort();
121
+ return scope
122
+ ? filterPathsForWorkspaceTarget(scope.allTargets, scope.target, files).sort()
123
+ : files;
95
124
  }
96
125
 
97
126
  // ── Git helpers ───────────────────────────────────────────────
@@ -279,85 +308,48 @@ export function buildSubAgentPrompt(group: DocDriftGroup, isFirstRun: boolean):
279
308
  `- Only flag things that are factually wrong or missing`,
280
309
  `- If documentation is accurate, say so`,
281
310
  ``,
282
- `Respond with a JSON object:`,
283
- `{`,
284
- ` "findings": [`,
285
- ` {`,
286
- ` "file": "path/to/doc.md",`,
287
- ` "description": "What is wrong or missing",`,
288
- ` "severity": "info" | "warning" | "error",`,
289
- ` "relatedFiles": ["path/to/source.ts"]`,
290
- ` }`,
291
- ` ],`,
292
- ` "status": "ok" | "drifted"`,
293
- `}`,
311
+ `Respond with a JSON object that matches this TypeScript shape exactly:`,
312
+ ``,
313
+ `\`\`\`ts`,
314
+ renderSchemaText(DocDriftOutputSchema),
315
+ `\`\`\``,
294
316
  ``,
295
- `Set status to "drifted" if ANY findings exist, "ok" if all docs are accurate.`,
296
- `Respond ONLY with the JSON object, no other text.`,
317
+ `Set "status" to "drifted" if ANY findings exist, "ok" if all docs are accurate.`,
318
+ `Respond with only the JSON object. You may wrap it in a \`\`\`json fence.`,
297
319
  );
298
320
 
299
321
  return parts.join("\n");
300
322
  }
301
323
 
302
- // ── Response parsing ──────────────────────────────────────────
303
-
304
- export function parseDriftFindings(text: string): { findings: DriftFinding[]; status: string } {
305
- try {
306
- const jsonMatch = text.match(/\{[\s\S]*\}/);
307
- if (jsonMatch) {
308
- const parsed = JSON.parse(jsonMatch[0]);
309
- if (Array.isArray(parsed.findings)) {
310
- const findings: DriftFinding[] = parsed.findings
311
- .filter(
312
- (f: any) =>
313
- typeof f.file === "string" &&
314
- typeof f.description === "string",
315
- )
316
- .map((f: any) => ({
317
- file: f.file,
318
- description: f.description,
319
- severity: f.severity === "warning" || f.severity === "error" ? f.severity : "info",
320
- ...(Array.isArray(f.relatedFiles) ? { relatedFiles: f.relatedFiles } : {}),
321
- }));
322
- return { findings, status: parsed.status === "ok" ? "ok" : "drifted" };
323
- }
324
- }
325
- } catch {
326
- // Fall through
327
- }
328
-
329
- // Fallback: treat unparseable response as potential drift
330
- const lower = text.toLowerCase();
331
- const likelyDrifted =
332
- lower.includes("inaccura") ||
333
- lower.includes("outdated") ||
334
- lower.includes("missing") ||
335
- lower.includes("drift");
336
- return {
337
- findings: likelyDrifted
338
- ? [{ file: "unknown", description: text.slice(0, 200), severity: "warning" }]
339
- : [],
340
- status: likelyDrifted ? "drifted" : "ok",
341
- };
342
- }
343
-
344
324
  // ── Sub-agent runner ──────────────────────────────────────────
345
325
 
326
+ export type DriftSubAgentResult =
327
+ | { status: "ok"; output: DocDriftOutput }
328
+ | { status: "blocked"; error: string };
329
+
346
330
  async function runDriftSubAgent(
347
331
  createAgentSession: Platform["createAgentSession"],
332
+ paths: PlatformPaths,
348
333
  group: DocDriftGroup,
349
334
  isFirstRun: boolean,
350
335
  cwd: string,
351
- ): Promise<{ findings: DriftFinding[]; status: string }> {
336
+ ): Promise<DriftSubAgentResult> {
352
337
  const prompt = buildSubAgentPrompt(group, isFirstRun);
353
- const result = await runStructuredAgentSession(
354
- createAgentSession.bind(undefined) as any,
355
- { cwd, prompt },
338
+ const result = await runWithOutputValidation<DocDriftOutput>(
339
+ createAgentSession as any,
340
+ {
341
+ cwd,
342
+ prompt,
343
+ schema: renderSchemaText(DocDriftOutputSchema),
344
+ parse: (raw) => parseStructuredOutput<DocDriftOutput>(raw, DocDriftOutputSchema),
345
+ reliability: { paths, cwd, command: "docs", operation: "drift-analyze" },
346
+ },
356
347
  );
357
- if (result.status !== "ok" || !result.finalText) {
358
- return { findings: [], status: "error" };
348
+
349
+ if (result.status === "ok") {
350
+ return { status: "ok", output: result.output };
359
351
  }
360
- return parseDriftFindings(result.finalText);
352
+ return { status: "blocked", error: result.error };
361
353
  }
362
354
 
363
355
  // ── Orchestrator ──────────────────────────────────────────────
@@ -365,19 +357,25 @@ async function runDriftSubAgent(
365
357
  /**
366
358
  * Checks tracked docs for drift using parallel sub-agents.
367
359
  * Returns null to skip silently, or a DriftCheckResult with per-doc findings.
360
+ * Sub-agents whose output fails schema validation surface their error via
361
+ * `result.errors` — the orchestrator never fabricates findings on parse failure.
368
362
  */
369
363
  export async function checkDocDrift(
370
364
  platform: Platform,
371
365
  cwd: string,
366
+ scope?: DocDriftScope,
372
367
  ): Promise<DriftCheckResult | null> {
373
- const state = loadState(platform.paths, cwd);
368
+ const state = loadState(platform.paths, cwd, scope);
374
369
  if (state.trackedFiles.length === 0) return null;
375
370
 
376
371
  let changedFiles: string[] = [];
377
372
  const isFirstRun = !state.lastCommit;
378
373
 
379
374
  if (!isFirstRun) {
380
- changedFiles = await getDiffFilesSince(platform, cwd, state.lastCommit!);
375
+ const diffFiles = await getDiffFilesSince(platform, cwd, state.lastCommit!);
376
+ changedFiles = scope
377
+ ? filterPathsForWorkspaceTarget(scope.allTargets, scope.target, diffFiles)
378
+ : diffFiles;
381
379
  if (changedFiles.length === 0) return null;
382
380
  }
383
381
 
@@ -390,6 +388,7 @@ export async function checkDocDrift(
390
388
  groups.map((group) =>
391
389
  runDriftSubAgent(
392
390
  platform.createAgentSession.bind(platform),
391
+ platform.paths,
393
392
  group,
394
393
  isFirstRun,
395
394
  cwd,
@@ -397,18 +396,32 @@ export async function checkDocDrift(
397
396
  ),
398
397
  );
399
398
 
400
- // Aggregate findings
399
+ // Aggregate findings and errors
401
400
  const allFindings: DriftFinding[] = [];
401
+ const errors: string[] = [];
402
402
  for (const result of results) {
403
- allFindings.push(...result.findings);
403
+ if (result.status === "ok") {
404
+ allFindings.push(...result.output.findings);
405
+ } else {
406
+ errors.push(result.error);
407
+ }
404
408
  }
405
409
 
406
410
  const drifted = allFindings.length > 0;
407
- const summary = drifted
408
- ? `${allFindings.length} finding(s) across ${new Set(allFindings.map((f) => f.file)).size} doc(s)`
409
- : "All documentation is up to date.";
411
+ const parts: string[] = [];
412
+ if (drifted) {
413
+ parts.push(`${allFindings.length} finding(s) across ${new Set(allFindings.map((f) => f.file)).size} doc(s)`);
414
+ } else if (errors.length === 0) {
415
+ parts.push("All documentation is up to date.");
416
+ }
417
+ if (errors.length > 0) {
418
+ parts.push(`${errors.length} sub-agent(s) failed validation`);
419
+ }
420
+ const summary = parts.join(" · ");
410
421
 
411
- return { drifted, summary, findings: allFindings };
422
+ return errors.length > 0
423
+ ? { drifted, summary, findings: allFindings, errors }
424
+ : { drifted, summary, findings: allFindings };
412
425
  }
413
426
 
414
427
  // ── Doc fix prompt ────────────────────────────────────────────
@@ -449,6 +462,23 @@ export function buildFixPrompt(findings: DriftFinding[]): string {
449
462
  parts.push(``);
450
463
  }
451
464
 
452
- parts.push(`Read each file listed above, apply the fixes, and write the corrected files.`);
465
+ parts.push(
466
+ `Read each file listed above, apply the fixes, and write the corrected files.`,
467
+ ``,
468
+ `## Output`,
469
+ ``,
470
+ `After applying the edits, respond with a JSON object that matches this TypeScript shape exactly:`,
471
+ ``,
472
+ "```ts",
473
+ renderSchemaText(ReleaseDocFixOutputSchema),
474
+ "```",
475
+ ``,
476
+ `Field guide:`,
477
+ `- \`edits\`: one entry per file you modified, with \`file\` as the relative path and \`instructions\` as a short description of what you changed.`,
478
+ `- \`summary\`: one-sentence summary of the overall fix.`,
479
+ `- \`status\`: \`"ok"\` when every finding was addressed; \`"blocked"\` when you could not complete the fixes — put the reason in \`summary\`.`,
480
+ ``,
481
+ "Respond with only the JSON object. You may wrap it in a ```json fence.",
482
+ );
453
483
  return parts.join("\n");
454
484
  }