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,200 @@
1
+ // src/fix-pr/assessment.ts
2
+ //
3
+ // Runs the structured per-comment assessment for fix-pr. One schema-backed
4
+ // AI call per cluster; the validated FixPrAssessmentBatch is the single
5
+ // source of truth for downstream work batches.
6
+
7
+ import type { Platform, PlatformPaths } from "../platform/types.js";
8
+ import {
9
+ parseStructuredOutput,
10
+ runWithOutputValidation,
11
+ type StructuredOutputResult,
12
+ } from "../ai/structured-output.js";
13
+ import { renderSchemaText } from "../ai/schema-text.js";
14
+ import {
15
+ FixPrAssessmentBatchSchema,
16
+ type FixPrAssessmentBatch,
17
+ type FixPrWorkBatch,
18
+ } from "./contracts.js";
19
+ import type { PrComment } from "./types.js";
20
+
21
+ export interface RunFixPrAssessmentInput {
22
+ createAgentSession: Platform["createAgentSession"];
23
+ paths?: PlatformPaths;
24
+ cwd: string;
25
+ comments: readonly PrComment[];
26
+ repo: string;
27
+ prNumber: number;
28
+ selectedTargetLabel: string;
29
+ model?: string;
30
+ thinkingLevel?: string | null;
31
+ maxAttempts?: number;
32
+ }
33
+
34
+ interface BuildAssessmentPromptArgs {
35
+ schemaText: string;
36
+ comments: readonly PrComment[];
37
+ repo: string;
38
+ prNumber: number;
39
+ selectedTargetLabel: string;
40
+ }
41
+
42
+ function buildAssessmentPrompt(args: BuildAssessmentPromptArgs): string {
43
+ const commentsJsonl = args.comments.map((c) => JSON.stringify(c)).join("\n");
44
+ return [
45
+ "# Fix-PR Assessment",
46
+ "",
47
+ `You are assessing PR review comments on \`${args.repo}\` PR #${args.prNumber} for target: ${args.selectedTargetLabel}.`,
48
+ "",
49
+ "For each comment, emit one assessment object with:",
50
+ `- verdict: "apply" (reviewer is right, fix it), "reject" (reviewer is wrong, explain), "investigate" (needs more info before deciding)`,
51
+ "- rationale: 1-3 sentences of technical reasoning grounded in the actual code",
52
+ `- affectedFiles: files that would be edited if verdict is "apply"; empty array otherwise`,
53
+ "- rippleEffects: downstream impacts (callers, tests, docs); empty array if none",
54
+ "- verificationPlan: how to confirm the fix is correct (which tests to run, behaviour to check)",
55
+ "",
56
+ "Rules:",
57
+ "- Read the referenced code before assigning a verdict.",
58
+ "- Do not perform any code edits. This is a pure assessment pass.",
59
+ "- One assessment per comment. `commentId` ties back to the PR comment id.",
60
+ "",
61
+ "Comments (JSONL, one per line):",
62
+ "```jsonl",
63
+ commentsJsonl,
64
+ "```",
65
+ "",
66
+ "Respond with a JSON object that matches this TypeScript shape exactly:",
67
+ "",
68
+ "```ts",
69
+ args.schemaText,
70
+ "```",
71
+ "",
72
+ "Respond with only the JSON object. You may wrap it in a ```json fence.",
73
+ ].join("\n");
74
+ }
75
+
76
+ /**
77
+ * Run a schema-backed assessment over a cluster of PR comments.
78
+ *
79
+ * Empty clusters short-circuit with `{ assessments: [] }` without calling
80
+ * the AI, so a no-op target can be persisted and grouped without cost.
81
+ */
82
+ export async function runFixPrAssessment(
83
+ input: RunFixPrAssessmentInput,
84
+ ): Promise<StructuredOutputResult<FixPrAssessmentBatch>> {
85
+ if (input.comments.length === 0) {
86
+ return {
87
+ status: "ok",
88
+ output: { assessments: [] },
89
+ rawOutput: "",
90
+ attempts: 0,
91
+ };
92
+ }
93
+
94
+ const schemaText = renderSchemaText(FixPrAssessmentBatchSchema);
95
+ const prompt = buildAssessmentPrompt({
96
+ schemaText,
97
+ comments: input.comments,
98
+ repo: input.repo,
99
+ prNumber: input.prNumber,
100
+ selectedTargetLabel: input.selectedTargetLabel,
101
+ });
102
+
103
+ return runWithOutputValidation<FixPrAssessmentBatch>(
104
+ input.createAgentSession as any,
105
+ {
106
+ cwd: input.cwd,
107
+ prompt,
108
+ schema: schemaText,
109
+ parse: (raw) =>
110
+ parseStructuredOutput<FixPrAssessmentBatch>(raw, FixPrAssessmentBatchSchema),
111
+ model: input.model,
112
+ thinkingLevel: input.thinkingLevel ?? null,
113
+ maxAttempts: input.maxAttempts,
114
+ reliability: input.paths
115
+ ? { paths: input.paths, cwd: input.cwd, command: "fix-pr", operation: "assessment" }
116
+ : undefined,
117
+ },
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Deterministic grouping rule:
123
+ *
124
+ * 1. Only `apply` verdicts produce work batches; `reject` and `investigate`
125
+ * are preserved in the artifact but excluded here.
126
+ * 2. Two `apply` assessments go in the same batch when their `affectedFiles`
127
+ * sets share at least one path (connected components over the file graph).
128
+ * 3. An `apply` assessment with empty `affectedFiles` is its own singleton
129
+ * batch — nothing to merge against.
130
+ * 4. Within a batch, commentIds are sorted ascending; batches are ordered by
131
+ * their smallest commentId. Batch ids are `batch-<minCommentId>`.
132
+ *
133
+ * The rule depends only on the validated artifact, so batching is a pure
134
+ * function of `FixPrAssessmentBatch`.
135
+ */
136
+ export function groupAssessmentsIntoBatches(
137
+ batch: FixPrAssessmentBatch,
138
+ ): FixPrWorkBatch[] {
139
+ const applies = batch.assessments.filter((a) => a.verdict === "apply");
140
+ if (applies.length === 0) return [];
141
+
142
+ const parent = applies.map((_, i) => i);
143
+ const find = (i: number): number => {
144
+ let root = i;
145
+ while (parent[root] !== root) root = parent[root];
146
+ // path compression
147
+ let cur = i;
148
+ while (parent[cur] !== root) {
149
+ const next = parent[cur];
150
+ parent[cur] = root;
151
+ cur = next;
152
+ }
153
+ return root;
154
+ };
155
+ const union = (a: number, b: number) => {
156
+ const ra = find(a);
157
+ const rb = find(b);
158
+ if (ra !== rb) parent[ra] = rb;
159
+ };
160
+
161
+ const fileOwner = new Map<string, number>();
162
+ for (let i = 0; i < applies.length; i += 1) {
163
+ for (const file of applies[i].affectedFiles) {
164
+ const existing = fileOwner.get(file);
165
+ if (existing === undefined) {
166
+ fileOwner.set(file, i);
167
+ } else {
168
+ union(existing, i);
169
+ }
170
+ }
171
+ }
172
+
173
+ const groups = new Map<number, number[]>();
174
+ for (let i = 0; i < applies.length; i += 1) {
175
+ const root = find(i);
176
+ const list = groups.get(root);
177
+ if (list) list.push(i);
178
+ else groups.set(root, [i]);
179
+ }
180
+
181
+ const result: FixPrWorkBatch[] = [];
182
+ for (const members of groups.values()) {
183
+ const commentIds = members
184
+ .map((i) => applies[i].commentId)
185
+ .sort((a, b) => a - b);
186
+ const fileSet = new Set<string>();
187
+ for (const i of members) {
188
+ for (const f of applies[i].affectedFiles) fileSet.add(f);
189
+ }
190
+ const affectedFiles = [...fileSet].sort();
191
+ result.push({
192
+ id: `batch-${commentIds[0]}`,
193
+ commentIds,
194
+ affectedFiles,
195
+ });
196
+ }
197
+
198
+ result.sort((a, b) => a.commentIds[0] - b.commentIds[0]);
199
+ return result;
200
+ }
@@ -0,0 +1,47 @@
1
+ // src/fix-pr/contracts.ts
2
+ //
3
+ // Schema-backed contract for the per-comment assessment artifact produced
4
+ // before any code edits begin. Every fix-pr run must emit JSON that parses
5
+ // against FixPrAssessmentBatchSchema; downstream work batches are derived
6
+ // from this validated artifact, not from ad-hoc orchestration prose.
7
+
8
+ import { Type, type Static } from "@sinclair/typebox";
9
+
10
+ export const FIX_PR_ASSESSMENT_VERDICTS = ["apply", "reject", "investigate"] as const;
11
+ export type FixPrAssessmentVerdict = (typeof FIX_PR_ASSESSMENT_VERDICTS)[number];
12
+
13
+ export const FixPrCommentAssessmentSchema = Type.Object(
14
+ {
15
+ commentId: Type.Integer(),
16
+ verdict: Type.Union(
17
+ FIX_PR_ASSESSMENT_VERDICTS.map((value) => Type.Literal(value)),
18
+ ),
19
+ rationale: Type.String({ minLength: 1 }),
20
+ affectedFiles: Type.Array(Type.String({ minLength: 1 })),
21
+ rippleEffects: Type.Array(Type.String({ minLength: 1 })),
22
+ verificationPlan: Type.String({ minLength: 1 }),
23
+ },
24
+ { additionalProperties: false },
25
+ );
26
+
27
+ export const FixPrAssessmentBatchSchema = Type.Object(
28
+ {
29
+ assessments: Type.Array(FixPrCommentAssessmentSchema),
30
+ summary: Type.Optional(Type.String()),
31
+ },
32
+ { additionalProperties: false },
33
+ );
34
+
35
+ export type FixPrCommentAssessment = Static<typeof FixPrCommentAssessmentSchema>;
36
+ export type FixPrAssessmentBatch = Static<typeof FixPrAssessmentBatchSchema>;
37
+
38
+ /**
39
+ * A deterministic execution unit derived from a validated FixPrAssessmentBatch.
40
+ * Only `apply` verdicts produce work batches; reject/investigate are tracked in
41
+ * the assessment artifact but excluded from execution.
42
+ */
43
+ export interface FixPrWorkBatch {
44
+ id: string;
45
+ commentIds: number[];
46
+ affectedFiles: string[];
47
+ }
@@ -1,6 +1,9 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { Platform } from "../platform/types.js";
4
+ import type { WorkspaceTarget } from "../types.js";
5
+ import { findWorkspaceTargetForPath } from "../workspace/path-mapping.js";
6
+ import type { PrComment } from "./types.js";
4
7
 
5
8
  const INLINE_COMMENTS_JQ =
6
9
  '.[] | {id, path, line: .line, body, user: .user.login, userType: .user.type, createdAt: .created_at, updatedAt: .updated_at, inReplyToId: .in_reply_to_id, diffHunk: .diff_hunk, state: "COMMENTED"}';
@@ -8,6 +11,83 @@ const INLINE_COMMENTS_JQ =
8
11
  const REVIEW_COMMENTS_JQ =
9
12
  '.[] | select(.body != null and .body != "") | {id, path: null, line: null, body, user: .user.login, userType: .user.type, createdAt: .submitted_at, updatedAt: .submitted_at, inReplyToId: null, diffHunk: null, state}';
10
13
 
14
+ export interface ClusteredPrComments<TTarget extends WorkspaceTarget = WorkspaceTarget> {
15
+ allComments: PrComment[];
16
+ commentsByTargetId: Map<string, PrComment[]>;
17
+ unscopedComments: PrComment[];
18
+ }
19
+
20
+ function appendComment(commentsByTargetId: Map<string, PrComment[]>, targetId: string, comment: PrComment): void {
21
+ const existing = commentsByTargetId.get(targetId);
22
+ if (existing) {
23
+ existing.push(comment);
24
+ return;
25
+ }
26
+
27
+ commentsByTargetId.set(targetId, [comment]);
28
+ }
29
+
30
+ export function parsePrCommentsJsonl(content: string): PrComment[] {
31
+ return content
32
+ .split(/\r?\n/)
33
+ .map((line) => line.trim())
34
+ .filter(Boolean)
35
+ .flatMap((line) => {
36
+ try {
37
+ return [JSON.parse(line) as PrComment];
38
+ } catch {
39
+ return [];
40
+ }
41
+ });
42
+ }
43
+
44
+ export function stringifyPrCommentsJsonl(comments: readonly PrComment[]): string {
45
+ if (comments.length === 0) {
46
+ return "";
47
+ }
48
+
49
+ return `${comments.map((comment) => JSON.stringify(comment)).join("\n")}\n`;
50
+ }
51
+
52
+ export function clusterPrCommentsByTarget<TTarget extends WorkspaceTarget>(
53
+ targets: readonly TTarget[],
54
+ comments: readonly PrComment[],
55
+ ): ClusteredPrComments<TTarget> {
56
+ const commentsByTargetId = new Map<string, PrComment[]>();
57
+ const rootTarget = targets.find((target) => target.kind === "root") ?? null;
58
+ const unscopedComments: PrComment[] = [];
59
+
60
+ for (const target of targets) {
61
+ commentsByTargetId.set(target.id, []);
62
+ }
63
+
64
+ for (const comment of comments) {
65
+ const commentPath = comment.path?.trim();
66
+ if (!commentPath) {
67
+ if (targets.length === 1 && rootTarget) {
68
+ appendComment(commentsByTargetId, rootTarget.id, comment);
69
+ } else {
70
+ unscopedComments.push(comment);
71
+ }
72
+ continue;
73
+ }
74
+
75
+ const owner = findWorkspaceTargetForPath([...targets], commentPath);
76
+ if (!owner) {
77
+ unscopedComments.push(comment);
78
+ continue;
79
+ }
80
+
81
+ appendComment(commentsByTargetId, owner.id, comment);
82
+ }
83
+
84
+ return {
85
+ allComments: [...comments],
86
+ commentsByTargetId,
87
+ unscopedComments,
88
+ };
89
+ }
90
+
11
91
  /**
12
92
  * Fetch all review comments for a PR and write them as JSONL to outputPath.
13
93
  *
@@ -1,4 +1,7 @@
1
1
  import type { FixPrConfig } from "./types.js";
2
+ import type { FixPrAssessmentBatch, FixPrWorkBatch } from "./contracts.js";
3
+ import { FixPrAssessmentBatchSchema } from "./contracts.js";
4
+ import { renderSchemaText } from "../ai/schema-text.js";
2
5
  import { buildReceivingReviewInstructions } from "../discipline/receiving-review.js";
3
6
 
4
7
  export interface FixPrPromptOptions {
@@ -10,8 +13,11 @@ export interface FixPrPromptOptions {
10
13
  config: FixPrConfig;
11
14
  iteration: number;
12
15
  skillContent: string;
13
- /** Resolved model ID for sub-agent tasks (planner, fixer roles). */
14
16
  taskModel: string;
17
+ selectedTargetLabel: string;
18
+ deferredCommentsSummary: string | null;
19
+ assessment: FixPrAssessmentBatch;
20
+ workBatches: FixPrWorkBatch[];
15
21
  }
16
22
 
17
23
  function buildReplyInstructions(config: FixPrConfig): string {
@@ -49,7 +55,21 @@ function buildReplyInstructions(config: FixPrConfig): string {
49
55
  }
50
56
 
51
57
  export function buildFixPrOrchestratorPrompt(options: FixPrPromptOptions): string {
52
- const { prNumber, repo, comments, sessionDir, scriptsDir, config, iteration, skillContent, taskModel } = options;
58
+ const {
59
+ prNumber,
60
+ repo,
61
+ comments,
62
+ sessionDir,
63
+ scriptsDir,
64
+ config,
65
+ iteration,
66
+ skillContent,
67
+ taskModel,
68
+ selectedTargetLabel,
69
+ deferredCommentsSummary,
70
+ assessment,
71
+ workBatches,
72
+ } = options;
53
73
  const { loop, reviewer } = config;
54
74
  const maxIter = loop.maxIterations;
55
75
  const delay = loop.delaySeconds;
@@ -63,8 +83,20 @@ export function buildFixPrOrchestratorPrompt(options: FixPrPromptOptions): strin
63
83
  "",
64
84
  `- Session dir: \`${sessionDir}\``,
65
85
  `- Iteration: ${iteration} of ${maxIter}`,
86
+ `- Selected target: ${selectedTargetLabel}`,
66
87
  `- Comment reply policy: ${config.commentPolicy}`,
67
88
  `- Reviewer: ${reviewer.type}${reviewer.triggerMethod ? ` (trigger: ${reviewer.triggerMethod})` : ""}`,
89
+ deferredCommentsSummary
90
+ ? `- Deferred comments outside this target: ${deferredCommentsSummary}`
91
+ : "- Deferred comments outside this target: none",
92
+ "",
93
+ "## Review Scope Rules",
94
+ "",
95
+ "- Process only the comments listed below for the selected target.",
96
+ deferredCommentsSummary
97
+ ? "- Comments outside the selected target were intentionally excluded for a separate run. Do not remediate them here."
98
+ : "- There are no deferred comments outside this target in this snapshot.",
99
+ "- After each wait-and-check cycle, keep enforcing the same target boundary. If new root or sibling-package comments appear, surface them explicitly and leave them for a separate run.",
68
100
  "",
69
101
  "## Review Comments to Process",
70
102
  "",
@@ -76,7 +108,6 @@ export function buildFixPrOrchestratorPrompt(options: FixPrPromptOptions): strin
76
108
  "",
77
109
  ];
78
110
 
79
- // Embedded skill
80
111
  if (skillContent) {
81
112
  sections.push(
82
113
  "## Assessment Methodology",
@@ -86,7 +117,6 @@ export function buildFixPrOrchestratorPrompt(options: FixPrPromptOptions): strin
86
117
  );
87
118
  }
88
119
 
89
- // Receiving review discipline
90
120
  sections.push(
91
121
  "## Review Discipline",
92
122
  "",
@@ -94,39 +124,34 @@ export function buildFixPrOrchestratorPrompt(options: FixPrPromptOptions): strin
94
124
  "",
95
125
  );
96
126
 
97
- // Step 1: Assess
98
127
  sections.push(
99
- "## Step 1: Assess Each Comment",
128
+ "## Step 1: Validated Assessment Artifact",
100
129
  "",
101
- "For each comment:",
102
- "1. Read the actual code at the file and line referenced",
103
- "2. Determine the verdict: **ACCEPT** / **REJECT** / **INVESTIGATE**",
104
- "3. Check ripple effects — who calls this, what tests cover it",
105
- "4. YAGNI check — does the reviewer's suggestion address a real problem?",
130
+ "The per-comment assessment for this run has already been validated against the `FixPrAssessmentBatchSchema` contract. Treat it as the source of truth: do not re-assess, do not change verdicts. Each entry has a verdict (`apply` / `reject` / `investigate`), rationale, affectedFiles, rippleEffects (downstream callers/tests/docs), and a verificationPlan.",
106
131
  "",
107
- "Record your assessment:",
132
+ "Schema:",
133
+ "```ts",
134
+ renderSchemaText(FixPrAssessmentBatchSchema),
108
135
  "```",
109
- "Comment #ID by @user on file:line",
110
- "Verdict: ACCEPT | REJECT | INVESTIGATE",
111
- "Reasoning: [1-2 sentences]",
112
- "Ripple effects: [list or none]",
113
- "Group: [group-id]",
136
+ "",
137
+ "Validated artifact:",
138
+ "```json",
139
+ JSON.stringify(assessment, null, 2),
114
140
  "```",
115
141
  "",
116
142
  );
117
143
 
118
- // Step 2: Group
119
144
  sections.push(
120
- "## Step 2: Group Comments",
145
+ "## Step 2: Work Batches (Parallel Execution Groups)",
146
+ "",
147
+ "Batches were derived deterministically from the artifact above by grouping `apply` assessments whose `affectedFiles` overlap. Independent batches may run in parallel; a batch's commentIds share at least one file and must execute together. `reject` and `investigate` verdicts produce no batch — handle those per the reply policy below.",
121
148
  "",
122
- "Group accepted comments for parallel execution:",
123
- "- Same file or tightly coupled files → same group",
124
- "- Independent files/areas → separate groups",
125
- "- Cosmetic vs functional → separate groups",
149
+ "```json",
150
+ JSON.stringify(workBatches, null, 2),
151
+ "```",
126
152
  "",
127
153
  );
128
154
 
129
- // Step 3: Plan
130
155
  sections.push(
131
156
  "## Step 3: Plan Each Group",
132
157
  "",
@@ -138,7 +163,6 @@ export function buildFixPrOrchestratorPrompt(options: FixPrPromptOptions): strin
138
163
  "",
139
164
  );
140
165
 
141
- // Step 4: Execute
142
166
  sections.push(
143
167
  "## Step 4: Execute Fixes",
144
168
  "",
@@ -149,27 +173,25 @@ export function buildFixPrOrchestratorPrompt(options: FixPrPromptOptions): strin
149
173
  "",
150
174
  );
151
175
 
152
- // Step 5: Reply
153
176
  sections.push(buildReplyInstructions(config), "");
154
177
 
155
- // Step 6: Push and loop
156
178
  sections.push(
157
179
  "## Step 6: Push and Check for New Comments",
158
180
  "",
159
- '1. Stage and commit: `git add -A && git commit -m "fix: address PR review comments (iteration ' + iteration + ')"`',
181
+ `1. Stage and commit: \`git add -A && git commit -m \"fix: address PR review comments (iteration ${iteration})\"\``,
160
182
  "2. Push: `git push`",
161
183
  );
162
184
 
163
185
  if (reviewer.type !== "none" && reviewer.triggerMethod) {
164
186
  sections.push(
165
- `3. Trigger re-review: \`bash ${scriptsDir}/trigger-review.sh "${repo}" ${prNumber} "${reviewer.type}" "${reviewer.triggerMethod}"\``,
187
+ `3. Trigger re-review: \`bun \"${scriptsDir}/trigger-review.ts\" \"${repo}\" ${prNumber} \"${reviewer.type}\" \"${reviewer.triggerMethod}\"\``,
166
188
  );
167
189
  }
168
190
 
169
191
  sections.push(
170
- `${reviewer.type !== "none" ? "4" : "3"}. Run the check script:`,
171
- "```bash",
172
- `bash ${scriptsDir}/wait-and-check.sh "${sessionDir}" ${delay} ${iteration + 1} "${repo}" ${prNumber}`,
192
+ `${reviewer.type !== "none" ? "4" : "3"}. Run the wait-and-check runner:`,
193
+ "```text",
194
+ `bun \"${scriptsDir}/wait-and-check.ts\" \"${sessionDir}\" ${delay} ${iteration + 1} \"${repo}\" ${prNumber}`,
173
195
  "```",
174
196
  `${reviewer.type !== "none" ? "5" : "4"}. Read the last line of output:`,
175
197
  ` - If \`hasNewComments: true\` and iteration < ${maxIter}: process the new comments (go back to Step 1)`,
@@ -177,22 +199,18 @@ export function buildFixPrOrchestratorPrompt(options: FixPrPromptOptions): strin
177
199
  "",
178
200
  );
179
201
 
180
- // Script paths reference
181
202
  sections.push(
182
- "## Script Paths",
203
+ "## Runner Paths",
183
204
  "",
184
- `- fetch-pr-comments.sh: \`${scriptsDir}/fetch-pr-comments.sh\``,
185
- `- diff-comments.sh: \`${scriptsDir}/diff-comments.sh\``,
186
- `- trigger-review.sh: \`${scriptsDir}/trigger-review.sh\``,
187
- `- wait-and-check.sh: \`${scriptsDir}/wait-and-check.sh\``,
205
+ `- trigger-review.ts: \`${scriptsDir}/trigger-review.ts\``,
206
+ `- wait-and-check.ts: \`${scriptsDir}/wait-and-check.ts\``,
188
207
  "",
189
208
  );
190
209
 
191
- // Model guidance
192
210
  sections.push(
193
211
  "## Model Guidance",
194
212
  "",
195
- `- **Orchestrator** (this session): handles assessment & grouping`,
213
+ "- **Orchestrator** (this session): handles assessment & grouping",
196
214
  `- **Planner & Fixer** (sub-agents): use model \`${taskModel}\``,
197
215
  "",
198
216
  "Sub-agents inherit the task model for planning and code changes.",
@@ -0,0 +1,34 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import type { ExecOptions, ExecResult, Platform } from "../../platform/types.js";
3
+ import { findExecutable } from "../../utils/executable.js";
4
+
5
+ export function runCliCommand(
6
+ command: string,
7
+ args: string[],
8
+ options?: ExecOptions,
9
+ ): ExecResult {
10
+ const env = options?.env ? { ...process.env, ...options.env } : process.env;
11
+ const resolvedCommand = findExecutable(command, {
12
+ searchPath: env.PATH,
13
+ pathext: env.PATHEXT,
14
+ }) ?? command;
15
+
16
+ const result = spawnSync(resolvedCommand, args, {
17
+ cwd: options?.cwd,
18
+ env,
19
+ encoding: "utf8",
20
+ });
21
+
22
+ return {
23
+ stdout: result.stdout ?? "",
24
+ stderr: result.stderr || (result.error instanceof Error ? result.error.message : ""),
25
+ code: result.status ?? (result.error ? 1 : 0),
26
+ killed: result.signal != null,
27
+ };
28
+ }
29
+
30
+ export function createCliPlatformExec(): Pick<Platform, "exec"> {
31
+ return {
32
+ exec: async (command, args, options) => runCliCommand(command, args, options),
33
+ };
34
+ }
@@ -0,0 +1,106 @@
1
+ import * as path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { runCliCommand } from "./exec.js";
4
+
5
+ export interface TriggerReviewResult {
6
+ triggered: boolean;
7
+ reviewer: string;
8
+ error?: string;
9
+ }
10
+
11
+ function buildResult(result: TriggerReviewResult): string {
12
+ return JSON.stringify(result);
13
+ }
14
+
15
+ function postIssueComment(repo: string, prNumber: number, body: string): TriggerReviewResult {
16
+ const result = runCliCommand("gh", [
17
+ "api",
18
+ `repos/${repo}/issues/${prNumber}/comments`,
19
+ "-f",
20
+ `body=${body}`,
21
+ ]);
22
+
23
+ if (result.code !== 0) {
24
+ throw new Error(result.stderr || result.stdout || "gh api comment request failed");
25
+ }
26
+
27
+ return { triggered: true, reviewer: "comment" };
28
+ }
29
+
30
+ export function triggerReview(
31
+ repo: string,
32
+ prNumber: number,
33
+ reviewer: string,
34
+ triggerMethod = "",
35
+ ): { exitCode: number; output: string } {
36
+ try {
37
+ switch (reviewer) {
38
+ case "coderabbit": {
39
+ postIssueComment(repo, prNumber, triggerMethod);
40
+ return { exitCode: 0, output: buildResult({ triggered: true, reviewer: "coderabbit" }) };
41
+ }
42
+ case "copilot": {
43
+ if (triggerMethod) {
44
+ postIssueComment(repo, prNumber, triggerMethod);
45
+ } else {
46
+ runCliCommand("gh", [
47
+ "api",
48
+ `repos/${repo}/pulls/${prNumber}/requested_reviewers`,
49
+ "--method",
50
+ "POST",
51
+ "-f",
52
+ "reviewers[]=copilot",
53
+ ]);
54
+ }
55
+ return { exitCode: 0, output: buildResult({ triggered: true, reviewer: "copilot" }) };
56
+ }
57
+ case "gemini": {
58
+ postIssueComment(repo, prNumber, triggerMethod);
59
+ return { exitCode: 0, output: buildResult({ triggered: true, reviewer: "gemini" }) };
60
+ }
61
+ case "none":
62
+ return { exitCode: 0, output: buildResult({ triggered: false, reviewer: "none" }) };
63
+ default:
64
+ return {
65
+ exitCode: 1,
66
+ output: buildResult({
67
+ triggered: false,
68
+ reviewer,
69
+ error: `unknown reviewer type: ${reviewer}`,
70
+ }),
71
+ };
72
+ }
73
+ } catch (error) {
74
+ return {
75
+ exitCode: 1,
76
+ output: buildResult({
77
+ triggered: false,
78
+ reviewer,
79
+ error: error instanceof Error ? error.message : String(error),
80
+ }),
81
+ };
82
+ }
83
+ }
84
+
85
+ function main(): void {
86
+ const [repo, prNumberArg, reviewer, triggerMethod = ""] = process.argv.slice(2);
87
+ const prNumber = Number.parseInt(prNumberArg ?? "", 10);
88
+
89
+ if (!repo || !Number.isInteger(prNumber) || !reviewer) {
90
+ console.log(buildResult({
91
+ triggered: false,
92
+ reviewer: reviewer ?? "unknown",
93
+ error: "Usage: trigger-review.ts <owner/repo> <pr_number> <reviewer_type> <trigger_method>",
94
+ }));
95
+ process.exit(1);
96
+ }
97
+
98
+ const result = triggerReview(repo, prNumber, reviewer, triggerMethod);
99
+ console.log(result.output);
100
+ process.exit(result.exitCode);
101
+ }
102
+
103
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
104
+ if (isMain) {
105
+ main();
106
+ }