@mrclrchtr/supi-review 2.8.0 → 3.1.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 (45) hide show
  1. package/README.md +71 -180
  2. package/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  3. package/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
  4. package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +40 -0
  5. package/package.json +3 -3
  6. package/src/config.ts +30 -21
  7. package/src/git-command.ts +78 -0
  8. package/src/git.ts +311 -507
  9. package/src/history/collect.ts +43 -120
  10. package/src/model.ts +3 -1
  11. package/src/review-path.ts +85 -0
  12. package/src/review-result.ts +43 -92
  13. package/src/review.ts +187 -286
  14. package/src/session/review-plan-store.ts +20 -51
  15. package/src/target/packet.ts +46 -369
  16. package/src/tool/agent-review-schemas.ts +108 -104
  17. package/src/tool/agent-review-tools.ts +283 -307
  18. package/src/tool/child-failure-diagnostics.ts +59 -1
  19. package/src/tool/child-lifecycle-trace.ts +32 -3
  20. package/src/tool/child-resource-loader.ts +37 -0
  21. package/src/tool/child-session-runner.ts +83 -0
  22. package/src/tool/output-page.ts +47 -0
  23. package/src/tool/planner-runner.ts +69 -0
  24. package/src/tool/review-runner.ts +42 -257
  25. package/src/tool/review-system-prompt.ts +12 -110
  26. package/src/tool/review-tools.ts +119 -0
  27. package/src/tool/review-workflow.ts +325 -0
  28. package/src/tool/runner-helpers.ts +3 -8
  29. package/src/tool/schemas.ts +75 -62
  30. package/src/tui/common.ts +237 -0
  31. package/src/tui/prepare.ts +132 -0
  32. package/src/tui/run.ts +160 -0
  33. package/src/types.ts +157 -275
  34. package/src/history/synthesize.ts +0 -121
  35. package/src/tool/agent-review-workflow.ts +0 -398
  36. package/src/tool/brief-runner.ts +0 -180
  37. package/src/tool/guidance.ts +0 -21
  38. package/src/tool/review-handlers.ts +0 -314
  39. package/src/tool/snapshot-tools.ts +0 -117
  40. package/src/ui/flow.ts +0 -189
  41. package/src/ui/format-content.ts +0 -143
  42. package/src/ui/renderer.ts +0 -274
  43. package/src/ui/review-plan-inspector.ts +0 -391
  44. package/src/ui/review-tool-format.ts +0 -138
  45. package/src/ui/review-tool-renderer.ts +0 -234
package/README.md CHANGED
@@ -1,14 +1,6 @@
1
- <div align="center">
2
- <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-review">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-review/assets/logo.png" alt="SuPi" width="50%">
5
- </picture>
6
- </a>
7
- </div>
8
-
9
1
  # @mrclrchtr/supi-review
10
2
 
11
- Adds human-driven and agent-driven Session-Aware Review workflows to the [pi coding agent](https://github.com/earendil-works/pi).
3
+ Runs caller-defined, read-only code review tasks in managed Pi child sessions.
12
4
 
13
5
  ## Install
14
6
 
@@ -16,198 +8,97 @@ Adds human-driven and agent-driven Session-Aware Review workflows to the [pi cod
16
8
  pi install npm:@mrclrchtr/supi-review
17
9
  ```
18
10
 
19
- This is a **beta** package. Install individually.
20
-
21
- For local development:
22
-
23
- ```bash
24
- pi install ./packages/supi-review
25
- ```
26
-
27
- ## What you get
28
-
29
- After install, pi gets one human-driven command and two agent-facing tools:
30
-
31
- - `/supi-review` — launch a guided review flow over a concrete git snapshot
32
- - `supi_review_prepare` — synthesize a versioned brief and return a session-scoped `planId`
33
- - `supi_review_run` — accept the main agent's structured brief critique, freshness-check the plan, and run focused reviewers concurrently
34
-
35
- The run tool is initially inactive and is dynamically enabled after `supi_review_prepare` returns a plan. This keeps the initial agent-facing prompt surface small.
36
-
37
- The reviewer runs in managed child agent sessions:
38
-
39
- - a **brief synthesizer** creates a structured review brief from the active session branch
40
- - a **read-only reviewer** inspects the selected snapshot (without receiving bulk inline diffs) and submits structured review items
41
-
42
- ![Review target selection](https://raw.githubusercontent.com/mrclrchtr/supi/main/screenshots/supi-review-1.png)
43
-
44
- ![Review brief preview](https://raw.githubusercontent.com/mrclrchtr/supi/main/screenshots/supi-review-2.png)
45
-
46
- ![Review result](https://raw.githubusercontent.com/mrclrchtr/supi/main/screenshots/supi-review-3.png)
47
-
48
- ![Review progress](https://raw.githubusercontent.com/mrclrchtr/supi/main/screenshots/supi-review-4.png)
49
-
50
- ## Review flow
51
-
52
- `/supi-review` walks you through:
53
-
54
- 1. choose a review target
55
- 2. choose the reviewer model
56
- 3. optionally add a short note
57
- 4. resolve the snapshot
58
- 5. synthesize a review brief from the current session history
59
- 6. preview the synthesized brief + compact prompt preview, then press `v` for an in-app inspector (Overview first, Raw Prompt via `tab`, export via `e`)
60
- 7. the reviewer fetches per-file diffs on demand via snapshot-aware tools; live progress widget shows activity
61
- 8. normalize the submitted review items into a host-derived verdict + structured result
62
- 9. if review items exist, hand off to the main agent so it can ask what to do next with fixed options (`Fix all`, `Fix selected`, `Verify findings`, `Skip`)
63
-
64
- ## Agent-driven review flow
65
-
66
- The agent-facing path deliberately separates preparation from execution:
67
-
68
- 1. `supi_review_prepare` resolves the target, serializes the active session context, and synthesizes a generated brief.
69
- 2. The main agent compares that brief with the user request, session evidence, and snapshot.
70
- 3. The main agent calls `supi_review_run` with:
71
- - the prepared `planId`
72
- - an `accept` or `revise` critique
73
- - evidence-backed critique findings
74
- - at least one evidence-backed finding and a full `revisedBrief` when revision is required
75
- - one to four independent reviewer assignments
76
- 4. The host atomically consumes the plan, checks snapshot freshness, and runs reviewer child sessions concurrently.
77
- 5. The tool returns each normalized review result plus a retained brief evaluation artifact.
78
-
79
- The evaluation artifact keeps the generated brief, main-agent critique, effective brief, synthesis prompt version, model id, and snapshot fingerprint separate. This makes brief-synthesis prompt regressions inspectable instead of hiding main-agent repairs. The full artifact is stored in tool-result `details`; a summary is also recorded as a `supi-review/brief-critique` SuPi Debug event when debug capture is enabled.
11
+ This is a beta package with intentionally unstable interfaces.
80
12
 
81
- Prepared plans are session-scoped. File or git changes before or during review invalidate the run, so `supi_review_run` should not share a tool batch with mutating tools.
13
+ ## Surfaces
82
14
 
83
- ## Review targets
15
+ - `/supi-review` — interactive Direct or Planner-assisted review
16
+ - `supi_review_prepare` — optional one-shot preparation with `planning: "none" | "suggest"`
17
+ - `supi_review_run` — universal Direct or Prepared Review execution
84
18
 
85
- Current targets:
19
+ Preparation is optional. Skills and agents that already know how to review should use Direct Review.
86
20
 
87
- - working tree
88
- - branch diff vs a selected local base branch (`merge-base..HEAD`, excluding dirty worktree changes)
89
- - one commit (recent-commit picker in the command; hexadecimal object id in the agent tool)
21
+ ## Direct Review
90
22
 
91
- Agent-provided branch targets must name an existing local branch. Commit targets must be unique 7–64 character hexadecimal commit-object ids: object-only disambiguation rejects ambiguous prefixes, non-commit objects, and hexadecimal branch/tag fallback. Git revision arguments are option-hardened before execution.
92
-
93
- ## Session-aware brief synthesis
23
+ ```json
24
+ {
25
+ "mode": "direct",
26
+ "target": {
27
+ "kind": "comparison",
28
+ "baseCommit": "<full commit object id>"
29
+ },
30
+ "review": {
31
+ "sharedContext": "The change implements issue #123.",
32
+ "tasks": [
33
+ {
34
+ "id": "standards",
35
+ "instructions": "Review against the repository standards."
36
+ },
37
+ {
38
+ "id": "spec",
39
+ "instructions": "Review against issue #123 and cite unmet requirements."
40
+ }
41
+ ]
42
+ }
43
+ }
44
+ ```
94
45
 
95
- The generated review prompt is **not** just a static diff wrapper.
46
+ All tasks use one reviewer model and run concurrently. A run accepts one to four tasks. Direct and Prepared adapters use the same canonical packet compiler; every task result includes the SHA-256 of the exact packet bytes.
96
47
 
97
- Before the actual review starts, the package:
48
+ ## Prepared Review
98
49
 
99
- - resolves the **active session branch into the current LLM-visible context**
100
- - **serializes** that resolved context into a compaction-style transcript
101
- - feeds the serialized transcript (plus snapshot + optional note) to a dedicated brief synthesizer
102
- - synthesizes a structured brief with:
103
- - summary
104
- - intended outcome
105
- - constraints to preserve
106
- - focus areas
107
- - risky files
108
- - unresolved questions
109
- - `reviewInstructionBlockIds` selected from a fixed host-owned catalog
50
+ ```json
51
+ {
52
+ "target": { "kind": "working-tree" },
53
+ "planning": "suggest"
54
+ }
55
+ ```
110
56
 
111
- The synthesizer also receives a bounded diff excerpt from the snapshot so it can reason about actual code changes, not just filenames.
57
+ Preparation returns a session-scoped, one-shot `planId` and optional Planner Draft. Execute it with an explicit decision:
112
58
 
113
- That synthesized brief is then combined with the git snapshot into a compact reviewer prompt. The host owns a fixed catalog of review instruction blocks, and the brief selects zero or more block IDs from that catalog when extra review guidance is warranted. The resulting prompt contains the brief, file manifest, per-file overview, and any brief-selected **mandatory review instructions**, but no large inline diffs. Instead, the reviewer session gets snapshot-aware tools (`read_snapshot_diff`, `read_snapshot_file`) to fetch exact per-file diffs and before/after file contents on demand.
59
+ ```json
60
+ {
61
+ "mode": "prepared",
62
+ "planId": "review-plan-...",
63
+ "decision": { "kind": "accept-draft" }
64
+ }
65
+ ```
114
66
 
115
- The session-transcript approach mirrors how Pi summarizes context for compaction: the entire resolved conversation is rendered in a readable label format and sent to the model as a whole, rather than relying on heuristic excerpt ranking.
67
+ Use `use-review` with a complete replacement review when the draft needs editing.
116
68
 
117
- ## Review-plan inspector
69
+ ## Targets
118
70
 
119
- Before the reviewer runs, the plan preview stays inside Pi:
71
+ - `working-tree` net `HEAD` to current filesystem, plus non-ignored untracked files, computed through a temporary HEAD-seeded index rather than the caller's real index
72
+ - `comparison` — merge base of a full `baseCommit` and captured `HEAD` to captured `HEAD`
73
+ - `commit` — first parent (or empty tree) to a full commit object id
120
74
 
121
- - `v` opens an in-app inspector instead of spawning an external pager
122
- - the inspector opens in **Overview** mode first
123
- - `tab` toggles between **Overview** and **Raw Prompt**
124
- - `↑↓` or `j` / `k` scroll long content in the inspector
125
- - `q` or `esc` returns to the summary preview without canceling the review
126
- - `e` exports the raw prompt to a temp file as a debugging fallback
75
+ Agent-facing commit values must be full hexadecimal object ids. The repository must remain stable from preparation or Direct invocation until reviewers finish; drift is not detected.
127
76
 
128
- The Overview mode uses the same structured packet data that feeds the reviewer prompt: mandatory review instructions, file overview rows, and truncated snapshot notes all come from shared packet derivation rather than re-parsing the raw prompt text.
77
+ ## Reviewer protocol
129
78
 
130
- ## Model selection
79
+ Callers own task methodology. The Review Engine owns:
131
80
 
132
- Every `/supi-review` command run asks you to choose the reviewer model.
81
+ - target resolution
82
+ - target-aware read-only tools
83
+ - minimal finding-quality instructions
84
+ - structured submission
85
+ - child lifecycle handling
86
+ - per-task verdict derivation
133
87
 
134
- - the picker only shows **scoped models** from Pi's `enabledModels` configuration
135
- - the current session model is preselected only when it is inside that scoped set
136
- - the selected model is used for both brief synthesis and the final review
137
- - the command selection is not persisted
88
+ Reviewers receive no shell, arbitrary extension tools, Pi context files, skills, or prompt templates. Discovered `SYSTEM.md` and `APPEND_SYSTEM.md` files are explicitly suppressed; only the package-owned reviewer protocol is appended to Pi's base system prompt. In-memory child settings disable compaction and retries so provider limits apply to the original packet. Inspection-tool and parent-visible outputs are paged/bounded; canonical packet bytes are never truncated.
138
89
 
139
- Agent-driven tool runs use the **Review → Agent tool model** setting in `/supi-settings` when `@mrclrchtr/supi-settings` is installed:
90
+ ## Result
140
91
 
141
- - `current session model` is the default and preserves the active-session behavior
142
- - an explicit choice stores a canonical `provider/model-id` from Pi's scoped `enabledModels` set
143
- - the configured model is used for brief synthesis and every focused reviewer
144
- - project settings override global settings through the normal SuPi settings scopes
145
- - a configured model that is no longer scoped or available causes preparation to fail with a corrective error
92
+ Each successful task returns a summary and ordered findings. Findings contain:
146
93
 
147
- The resolved model is retained in the prepared plan, so changing the setting or active session model between tool calls cannot silently alter the run.
94
+ - `blocksAcceptance`
95
+ - `impact`: `low | medium | high`
96
+ - `effort`: `small | medium | large`
97
+ - `confidence`: `0..1`
98
+ - optional target-relative location
148
99
 
149
- For a standalone `supi-review` install without `supi-settings`, set the same value directly in the global `~/.pi/agent/supi/config.json` or project `.pi/supi/config.json` file:
100
+ The Review Engine derives `pass` or `issues` per task and never aggregates or reranks tasks. Batch details also record `mode`, caller/planner provenance, and each task's packet hash.
150
101
 
151
- ```json
152
- {
153
- "review": {
154
- "agentModel": "openai/gpt-5"
155
- }
156
- }
157
- ```
102
+ ## Models
158
103
 
159
- Use `"current"` instead of a canonical model id to follow the active session model.
160
-
161
- ## Result shape
162
-
163
- A successful review includes:
164
-
165
- - a host-derived binary verdict:
166
- - `PATCH IS CORRECT`
167
- - `PATCH HAS ISSUES`
168
- - overall explanation
169
- - overall confidence score
170
- - normalized action/category summary counts
171
- - structured review items with:
172
- - title
173
- - body
174
- - category
175
- - impact
176
- - effort
177
- - recommended action
178
- - confidence score
179
- - suggested fix
180
- - verification hint
181
- - optional code location
182
- - the synthesized brief that drove the review
183
-
184
- The renderer also handles failed, canceled, and timed-out reviews.
185
-
186
- The reviewer model does **not** decide the final binary verdict directly. It submits review items plus overall explanation/confidence, then the host derives the verdict from the normalized items (`must-fix` items => `PATCH HAS ISSUES`).
187
-
188
- When a successful review contains review items, `supi-review` also injects an agent-visible hidden follow-up message that asks the main agent to decide the next step with the user. If `ask_user` is available, the main agent is instructed to use it and offer:
189
-
190
- - Fix all
191
- - Fix selected
192
- - Verify findings
193
- - Skip
194
-
195
- ## Source
196
-
197
- - `src/review.ts` — command orchestration and interactive flow
198
- - `src/config.ts` — persisted agent-tool model setting and `/supi-settings` registration
199
- - `src/model.ts` — explicit and configured model selection helpers
200
- - `src/git.ts` — git snapshot resolution
201
- - `src/history/collect.ts` — compaction-style session-context serialization
202
- - `src/history/synthesize.ts` — brief synthesis orchestration
203
- - `src/review-result.ts` — review-item normalization, verdict derivation, and summary counts
204
- - `src/target/review-instruction-blocks.ts` — fixed catalog of host-owned review instruction blocks
205
- - `src/target/packet.ts` — final reviewer packet builder + shared preview-data derivation for the inspector
206
- - `src/session/review-plan-store.ts` — session-scoped, one-shot prepared review plans
207
- - `src/tool/agent-review-tools.ts` — prepare/run tool registration, dynamic activation, progress, and debug artifact recording
208
- - `src/tool/agent-review-workflow.ts` — preparation, critique validation, freshness checks, and concurrent reviewer fan-out
209
- - `src/tool/brief-runner.ts` — brief synthesis child session
210
- - `src/tool/review-runner.ts` — read-only reviewer child session with snapshot-aware tools
211
- - `src/tool/snapshot-tools.ts` — per-file diff and before/after content tools scoped to the selected snapshot
212
- - `src/ui/review-plan-inspector.ts` — in-app summary/inspector preview with Overview + Raw Prompt modes and export fallback
213
- - `src/ui/renderer.ts` — structured result rendering
104
+ `/supi-review` asks for the reviewer model. Agent-triggered runs use `review.agentModel`. Optional planning uses the separately configured `review.plannerModel` at low thinking effort. Both settings default to the current session model for availability; configure `review.plannerModel` to a lightweight model when using Planner suggestions.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "2.8.0",
3
+ "version": "3.1.0",
4
4
  "description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -55,6 +55,7 @@
55
55
  "./config": "./src/config.ts",
56
56
  "./context": "./src/context.ts",
57
57
  "./debug": "./src/debug-registry.ts",
58
+ "./evidence-badge": "./src/evidence-badge.ts",
58
59
  "./footer-registry": "./src/footer-registry.ts",
59
60
  "./llm": "./src/llm.ts",
60
61
  "./model-selection": "./src/model-selection.ts",
@@ -15,6 +15,8 @@ export * from "./context.ts";
15
15
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
16
16
  export * from "./debug-registry.ts";
17
17
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
18
+ export * from "./evidence-badge.ts";
19
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
18
20
  export * from "./footer-registry.ts";
19
21
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
20
22
  export * from "./llm.ts";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * TUI-facing evidence badge string formatter.
3
+ *
4
+ * Pure string formatting — no pi-tui dependency. Consumes evidence
5
+ * completeness metadata and produces compact human-readable badges.
6
+ */
7
+
8
+ /** Metadata describing how many evidence atoms were shown vs exist. */
9
+ export interface EvidenceBadgeInput {
10
+ shownCount: number;
11
+ totalCount: number | null;
12
+ omittedCount: number | null;
13
+ partialReason: string | null;
14
+ /** Human-readable label for the badge, e.g. "references", "symbols". */
15
+ label: string;
16
+ }
17
+
18
+ /**
19
+ * Format a compact evidence completeness badge.
20
+ *
21
+ * | Input | Output |
22
+ * |------------------------------------------------------|---------------------------------------|
23
+ * | shown=12, total=12, omitted=0, label="references" | `12 references` |
24
+ * | shown=8, total=20, omitted=12, label="symbols" | `8 of 20 symbols (12 omitted)` |
25
+ * | shown=5, total=null, reason="timeout", label="matches" | `5 matches — timeout` |
26
+ */
27
+ export function formatEvidenceBadge(input: EvidenceBadgeInput): string {
28
+ const { shownCount, totalCount, omittedCount, partialReason, label } = input;
29
+
30
+ if (totalCount === null) {
31
+ const reasonSuffix = partialReason ? ` — ${partialReason}` : "";
32
+ return `${shownCount} ${label}${reasonSuffix}`;
33
+ }
34
+
35
+ if (omittedCount !== null && omittedCount > 0) {
36
+ return `${shownCount} of ${totalCount} ${label} (${omittedCount} omitted)`;
37
+ }
38
+
39
+ return `${shownCount} ${label}`;
40
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-review",
3
- "version": "2.8.0",
4
- "description": "SuPi Review extension — session-aware review command and agent tools",
3
+ "version": "3.1.0",
4
+ "description": "SuPi Review extension — caller-defined read-only review tasks for users and agents",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -31,7 +31,7 @@
31
31
  "README.md"
32
32
  ],
33
33
  "dependencies": {
34
- "@mrclrchtr/supi-core": "2.8.0"
34
+ "@mrclrchtr/supi-core": "3.1.0"
35
35
  },
36
36
  "bundledDependencies": [
37
37
  "@mrclrchtr/supi-core"
package/src/config.ts CHANGED
@@ -3,32 +3,39 @@ import { loadSupiConfig } from "@mrclrchtr/supi-core/config";
3
3
  import { registerDeclarativeSettings } from "@mrclrchtr/supi-core/settings";
4
4
  import { CURRENT_SESSION_REVIEW_MODEL } from "./model.ts";
5
5
 
6
- /** Persisted configuration for agent-driven Session-Aware Review. */
6
+ /** Persisted model choices for agent-triggered reviews and optional planning. */
7
7
  export interface ReviewConfig extends Record<string, unknown> {
8
- /** Canonical `provider/model-id`, or `current` to use the active session model. */
8
+ /** Canonical reviewer model id, or `current` for the active session model. */
9
9
  agentModel: string;
10
+ /** Canonical Planner model id, or `current` for the active session model. */
11
+ plannerModel: string;
10
12
  }
11
13
 
12
- /** Shared SuPi config section owned by supi-review. */
14
+ /** Shared SuPi configuration section owned by this package. */
13
15
  export const REVIEW_CONFIG_SECTION = "review";
14
-
15
- /** Package defaults for supi-review configuration. */
16
+ /** Availability-safe defaults; users can configure a separate lightweight Planner model. */
16
17
  export const REVIEW_DEFAULTS: ReviewConfig = {
17
18
  agentModel: CURRENT_SESSION_REVIEW_MODEL,
19
+ plannerModel: CURRENT_SESSION_REVIEW_MODEL,
18
20
  };
19
21
 
20
- /** Load merged, validated supi-review configuration for a workspace. */
22
+ /** Load merged and normalized review configuration. */
21
23
  export function loadReviewConfig(cwd: string, homeDir?: string): ReviewConfig {
22
24
  const raw = loadSupiConfig(REVIEW_CONFIG_SECTION, cwd, REVIEW_DEFAULTS, { homeDir });
23
- const agentModel =
24
- typeof raw.agentModel === "string" && raw.agentModel.trim()
25
- ? raw.agentModel.trim()
26
- : REVIEW_DEFAULTS.agentModel;
27
- return { agentModel };
25
+ const readModel = (value: unknown, fallback: string) =>
26
+ typeof value === "string" && value.trim() ? value.trim() : fallback;
27
+ return {
28
+ agentModel: readModel(raw.agentModel, REVIEW_DEFAULTS.agentModel),
29
+ plannerModel: readModel(raw.plannerModel, REVIEW_DEFAULTS.plannerModel),
30
+ };
28
31
  }
29
32
 
30
- /** Register the Review section contributed to `/supi-settings`. */
33
+ /** Contribute reviewer and Planner model pickers to `/supi-settings`. */
31
34
  export function registerReviewSettings(pi: ExtensionAPI, homeDir?: string): void {
35
+ const currentOption = {
36
+ value: CURRENT_SESSION_REVIEW_MODEL,
37
+ label: "current session model",
38
+ };
32
39
  registerDeclarativeSettings(pi, {
33
40
  id: REVIEW_CONFIG_SECTION,
34
41
  label: "Review",
@@ -38,16 +45,18 @@ export function registerReviewSettings(pi: ExtensionAPI, homeDir?: string): void
38
45
  {
39
46
  kind: "modelPicker",
40
47
  key: "agentModel",
41
- label: "Agent tool model",
42
- description: "Model used for brief synthesis and reviewers started by supi_review_prepare.",
48
+ label: "Reviewer model",
49
+ description: "Model shared by all tasks in an agent-triggered review run.",
50
+ includeDisabled: false,
51
+ staticOptions: [currentOption],
52
+ },
53
+ {
54
+ kind: "modelPicker",
55
+ key: "plannerModel",
56
+ label: "Planner model",
57
+ description: "Lightweight model used only when planning is set to suggest.",
43
58
  includeDisabled: false,
44
- staticOptions: [
45
- {
46
- value: CURRENT_SESSION_REVIEW_MODEL,
47
- label: "current session model",
48
- description: "Use the model active when supi_review_prepare starts",
49
- },
50
- ],
59
+ staticOptions: [currentOption],
51
60
  },
52
61
  ],
53
62
  ...(homeDir ? { homeDir } : {}),
@@ -0,0 +1,78 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { promisify } from "node:util";
6
+
7
+ const execFileAsync = promisify(execFile);
8
+ const GIT_TIMEOUT_MS = 30_000;
9
+
10
+ function gitOptions(cwd: string, indexFile?: string) {
11
+ const env = { ...process.env };
12
+ for (const key of Object.keys(env)) if (key.startsWith("GIT_")) delete env[key];
13
+ if (indexFile) env.GIT_INDEX_FILE = indexFile;
14
+ return { cwd, env, timeout: GIT_TIMEOUT_MS, maxBuffer: 50 * 1024 * 1024 };
15
+ }
16
+
17
+ /** Run Git with bounded resources and a sanitized environment. */
18
+ export async function runGit(cwd: string, args: string[], indexFile?: string): Promise<string> {
19
+ return (await execFileAsync("git", args, gitOptions(cwd, indexFile))).stdout;
20
+ }
21
+
22
+ export function expectedGitExitOutput(error: unknown, allowedCodes: number[]): string | undefined {
23
+ if (!error || typeof error !== "object") return undefined;
24
+ const failure = error as {
25
+ code?: unknown;
26
+ killed?: unknown;
27
+ signal?: unknown;
28
+ stdout?: unknown;
29
+ };
30
+ if (
31
+ typeof failure.code !== "number" ||
32
+ !allowedCodes.includes(failure.code) ||
33
+ failure.killed === true ||
34
+ failure.signal
35
+ ) {
36
+ return undefined;
37
+ }
38
+ if (typeof failure.stdout === "string") return failure.stdout;
39
+ if (Buffer.isBuffer(failure.stdout)) return failure.stdout.toString("utf8");
40
+ return "";
41
+ }
42
+
43
+ /** Allow only documented non-zero Git outcomes while preserving operational failures. */
44
+ export async function runGitAllowExit(
45
+ cwd: string,
46
+ args: string[],
47
+ allowedCodes: number[],
48
+ indexFile?: string,
49
+ ): Promise<string> {
50
+ try {
51
+ return await runGit(cwd, args, indexFile);
52
+ } catch (error) {
53
+ const stdout = expectedGitExitOutput(error, allowedCodes);
54
+ if (stdout !== undefined) return stdout;
55
+ throw error;
56
+ }
57
+ }
58
+
59
+ /** Execute an operation against a temporary index seeded only from HEAD. */
60
+ export async function withHeadIndex<T>(
61
+ cwd: string,
62
+ head: string,
63
+ operation: (indexFile: string) => Promise<T>,
64
+ ): Promise<T> {
65
+ const directory = await mkdtemp(join(tmpdir(), "supi-review-index-"));
66
+ const indexFile = join(directory, "index");
67
+ try {
68
+ await runGit(cwd, ["read-tree", head], indexFile);
69
+ return await operation(indexFile);
70
+ } finally {
71
+ await rm(directory, { recursive: true, force: true });
72
+ }
73
+ }
74
+
75
+ /** Disable Git pathspec magic for a path-taking command. */
76
+ export function literalPathspec(args: string[]): string[] {
77
+ return ["--literal-pathspecs", ...args];
78
+ }