@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.
- package/README.md +71 -180
- package/node_modules/@mrclrchtr/supi-core/package.json +2 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +40 -0
- package/package.json +3 -3
- package/src/config.ts +30 -21
- package/src/git-command.ts +78 -0
- package/src/git.ts +311 -507
- package/src/history/collect.ts +43 -120
- package/src/model.ts +3 -1
- package/src/review-path.ts +85 -0
- package/src/review-result.ts +43 -92
- package/src/review.ts +187 -286
- package/src/session/review-plan-store.ts +20 -51
- package/src/target/packet.ts +46 -369
- package/src/tool/agent-review-schemas.ts +108 -104
- package/src/tool/agent-review-tools.ts +283 -307
- package/src/tool/child-failure-diagnostics.ts +59 -1
- package/src/tool/child-lifecycle-trace.ts +32 -3
- package/src/tool/child-resource-loader.ts +37 -0
- package/src/tool/child-session-runner.ts +83 -0
- package/src/tool/output-page.ts +47 -0
- package/src/tool/planner-runner.ts +69 -0
- package/src/tool/review-runner.ts +42 -257
- package/src/tool/review-system-prompt.ts +12 -110
- package/src/tool/review-tools.ts +119 -0
- package/src/tool/review-workflow.ts +325 -0
- package/src/tool/runner-helpers.ts +3 -8
- package/src/tool/schemas.ts +75 -62
- package/src/tui/common.ts +237 -0
- package/src/tui/prepare.ts +132 -0
- package/src/tui/run.ts +160 -0
- package/src/types.ts +157 -275
- package/src/history/synthesize.ts +0 -121
- package/src/tool/agent-review-workflow.ts +0 -398
- package/src/tool/brief-runner.ts +0 -180
- package/src/tool/guidance.ts +0 -21
- package/src/tool/review-handlers.ts +0 -314
- package/src/tool/snapshot-tools.ts +0 -117
- package/src/ui/flow.ts +0 -189
- package/src/ui/format-content.ts +0 -143
- package/src/ui/renderer.ts +0 -274
- package/src/ui/review-plan-inspector.ts +0 -391
- package/src/ui/review-tool-format.ts +0 -138
- 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
|
-
|
|
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
|
|
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
|
-

|
|
43
|
-
|
|
44
|
-

|
|
45
|
-
|
|
46
|
-

|
|
47
|
-
|
|
48
|
-

|
|
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
|
-
|
|
13
|
+
## Surfaces
|
|
82
14
|
|
|
83
|
-
|
|
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
|
-
|
|
19
|
+
Preparation is optional. Skills and agents that already know how to review should use Direct Review.
|
|
86
20
|
|
|
87
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
+
## Prepared Review
|
|
98
49
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
57
|
+
Preparation returns a session-scoped, one-shot `planId` and optional Planner Draft. Execute it with an explicit decision:
|
|
112
58
|
|
|
113
|
-
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mode": "prepared",
|
|
62
|
+
"planId": "review-plan-...",
|
|
63
|
+
"decision": { "kind": "accept-draft" }
|
|
64
|
+
}
|
|
65
|
+
```
|
|
114
66
|
|
|
115
|
-
|
|
67
|
+
Use `use-review` with a complete replacement review when the draft needs editing.
|
|
116
68
|
|
|
117
|
-
##
|
|
69
|
+
## Targets
|
|
118
70
|
|
|
119
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
77
|
+
## Reviewer protocol
|
|
129
78
|
|
|
130
|
-
|
|
79
|
+
Callers own task methodology. The Review Engine owns:
|
|
131
80
|
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
+
## Result
|
|
140
91
|
|
|
141
|
-
|
|
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
|
-
|
|
94
|
+
- `blocksAcceptance`
|
|
95
|
+
- `impact`: `low | medium | high`
|
|
96
|
+
- `effort`: `small | medium | large`
|
|
97
|
+
- `confidence`: `0..1`
|
|
98
|
+
- optional target-relative location
|
|
148
99
|
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
{
|
|
153
|
-
"review": {
|
|
154
|
-
"agentModel": "openai/gpt-5"
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
```
|
|
102
|
+
## Models
|
|
158
103
|
|
|
159
|
-
|
|
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": "
|
|
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": "
|
|
4
|
-
"description": "SuPi Review extension —
|
|
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": "
|
|
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
|
|
6
|
+
/** Persisted model choices for agent-triggered reviews and optional planning. */
|
|
7
7
|
export interface ReviewConfig extends Record<string, unknown> {
|
|
8
|
-
/** Canonical
|
|
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
|
|
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
|
|
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
|
|
24
|
-
typeof
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
/**
|
|
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: "
|
|
42
|
-
description: "Model
|
|
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
|
+
}
|