@shanepadgett/tau-agent 0.33.1 → 0.35.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/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/bash-approval/README.md +20 -0
- package/extensions/bash-approval/index.ts +235 -0
- package/extensions/bash-approval/settings.ts +24 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +66 -0
- package/extensions/checkpoint/checkpoint.ts +279 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +170 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/commit/commit-plan.ts +2 -2
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/index.ts +2 -0
- package/extensions/reference/panel.ts +94 -31
- package/extensions/review/README.md +14 -6
- package/extensions/review/index.ts +58 -99
- package/extensions/review/model.ts +13 -33
- package/extensions/review/session.ts +9 -23
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +122 -82
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/soul/README.md +2 -2
- package/extensions/soul/index.ts +4 -4
- package/extensions/soul/prompt.ts +6 -10
- package/extensions/soul/settings.ts +6 -3
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents/web-research.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +308 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +20 -8
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +38 -35
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +72 -30
- package/shared/model-effort.ts +23 -22
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/review/panel.ts +0 -128
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -55,6 +55,8 @@ export default function referenceExtension(pi: ExtensionAPI): void {
|
|
|
55
55
|
if (!references) return;
|
|
56
56
|
|
|
57
57
|
ctx.ui.setEditorText(buildReferenceDraft(references));
|
|
58
|
+
// Pi does not request a render when setEditorText runs after custom UI closes.
|
|
59
|
+
ctx.ui.setStatus("reference", undefined);
|
|
58
60
|
},
|
|
59
61
|
});
|
|
60
62
|
}
|
|
@@ -34,7 +34,12 @@ export interface ReferenceItem {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
interface ReferenceListItem extends ReferenceItem, SelectableListItem {
|
|
37
|
-
state?: "updating" | "updated" | "failed" | "switching";
|
|
37
|
+
state?: "loading" | "updating" | "updated" | "failed" | "switching";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ReferenceLocation {
|
|
41
|
+
name: string;
|
|
42
|
+
path: string;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
interface CloneProgress {
|
|
@@ -71,9 +76,9 @@ export async function showReferencePanel(
|
|
|
71
76
|
editor: ReferenceEditor,
|
|
72
77
|
branchChoices: number,
|
|
73
78
|
): Promise<ReferenceItem[] | undefined> {
|
|
74
|
-
let initial:
|
|
79
|
+
let initial: ReferenceLocation[];
|
|
75
80
|
try {
|
|
76
|
-
initial = await
|
|
81
|
+
initial = await loadReferenceLocations();
|
|
77
82
|
} catch (error) {
|
|
78
83
|
ctx.ui.notify(`Reference load failed: ${errorText(error)}`, "error");
|
|
79
84
|
initial = [];
|
|
@@ -134,7 +139,7 @@ class ReferencePanel implements Component {
|
|
|
134
139
|
ctx: ExtensionCommandContext,
|
|
135
140
|
editor: ReferenceEditor,
|
|
136
141
|
branchChoices: number,
|
|
137
|
-
initial: readonly
|
|
142
|
+
initial: readonly ReferenceLocation[],
|
|
138
143
|
done: (result: ReferenceItem[] | undefined) => void,
|
|
139
144
|
) {
|
|
140
145
|
this.tui = tui;
|
|
@@ -144,7 +149,9 @@ class ReferencePanel implements Component {
|
|
|
144
149
|
this.editor = editor;
|
|
145
150
|
this.branchChoices = branchChoices;
|
|
146
151
|
this.done = done;
|
|
147
|
-
this.refs = initial.map((item) =>
|
|
152
|
+
this.refs = initial.map((item) =>
|
|
153
|
+
toListItem({ ...item, displayName: item.name, dirty: false, branch: "" }, "loading"),
|
|
154
|
+
);
|
|
148
155
|
this.list = this.createList(this.refs);
|
|
149
156
|
this.body = {
|
|
150
157
|
render: (width) => this.renderBody(width),
|
|
@@ -158,6 +165,7 @@ class ReferencePanel implements Component {
|
|
|
158
165
|
footer: { kind: "hints", hints: this.footerHints() },
|
|
159
166
|
};
|
|
160
167
|
this.panel = new ToolPanel(theme, this.panelConfig);
|
|
168
|
+
void this.hydrateInitialReferences(initial);
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
render(width: number): string[] {
|
|
@@ -257,6 +265,25 @@ class ReferencePanel implements Component {
|
|
|
257
265
|
];
|
|
258
266
|
}
|
|
259
267
|
|
|
268
|
+
private branchSuffix(branch: { isCurrent: boolean; updatedAt?: number }): string {
|
|
269
|
+
if (branch.isCurrent) return "current";
|
|
270
|
+
if (branch.updatedAt === undefined) return "remote";
|
|
271
|
+
return formatAge(branch.updatedAt);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private renderBranchRow(
|
|
275
|
+
branch: { name: string; isCurrent: boolean; updatedAt?: number },
|
|
276
|
+
active: boolean,
|
|
277
|
+
renderWidth: number,
|
|
278
|
+
): string {
|
|
279
|
+
const pointer = active ? this.theme.fg("accent", "› ") : " ";
|
|
280
|
+
const labelText = `${branch.name} ${this.branchSuffix(branch)}`;
|
|
281
|
+
const label = active
|
|
282
|
+
? this.theme.fg("accent", labelText)
|
|
283
|
+
: this.theme.fg(branch.isCurrent ? "accent" : "text", labelText);
|
|
284
|
+
return truncateToWidth(`${pointer}${label}`, renderWidth, "");
|
|
285
|
+
}
|
|
286
|
+
|
|
260
287
|
private renderBranchPicker(width: number): string[] {
|
|
261
288
|
const picker = this.branchPicker;
|
|
262
289
|
if (!picker) return [];
|
|
@@ -279,18 +306,7 @@ class ReferencePanel implements Component {
|
|
|
279
306
|
for (let index = window.start; index < window.end; index++) {
|
|
280
307
|
const branch = picker.branches[index];
|
|
281
308
|
if (!branch) continue;
|
|
282
|
-
|
|
283
|
-
const pointer = active ? this.theme.fg("accent", "› ") : " ";
|
|
284
|
-
const suffix = branch.isCurrent
|
|
285
|
-
? "current"
|
|
286
|
-
: branch.updatedAt === undefined
|
|
287
|
-
? "remote"
|
|
288
|
-
: formatAge(branch.updatedAt);
|
|
289
|
-
const labelText = `${branch.name} ${suffix}`;
|
|
290
|
-
const label = active
|
|
291
|
-
? this.theme.fg("accent", labelText)
|
|
292
|
-
: this.theme.fg(branch.isCurrent ? "accent" : "text", labelText);
|
|
293
|
-
lines.push(truncateToWidth(`${pointer}${label}`, renderWidth, ""));
|
|
309
|
+
lines.push(this.renderBranchRow(branch, index === picker.cursor, renderWidth));
|
|
294
310
|
}
|
|
295
311
|
if (window.start > 0 || window.end < picker.branches.length) {
|
|
296
312
|
lines.push(
|
|
@@ -303,6 +319,12 @@ class ReferencePanel implements Component {
|
|
|
303
319
|
return lines;
|
|
304
320
|
}
|
|
305
321
|
|
|
322
|
+
private referenceStatusMark(state: NonNullable<ReferenceListItem["state"]>): string {
|
|
323
|
+
if (state === "failed") return this.theme.fg("error", "!");
|
|
324
|
+
if (state === "updated") return this.theme.fg("success", "✓");
|
|
325
|
+
return this.theme.fg("muted", "…");
|
|
326
|
+
}
|
|
327
|
+
|
|
306
328
|
private renderReferenceRow(item: ReferenceListItem, active: boolean, width: number): string[] {
|
|
307
329
|
const separator = item.displayName.lastIndexOf("/");
|
|
308
330
|
const repository = separator < 0 ? item.displayName : item.displayName.slice(separator + 1);
|
|
@@ -310,9 +332,7 @@ class ReferencePanel implements Component {
|
|
|
310
332
|
const name = item.dirty ? `${repository} *` : repository;
|
|
311
333
|
const label = active ? this.theme.fg("accent", name) : this.theme.fg(item.dirty ? "warning" : "text", name);
|
|
312
334
|
const branch = item.branch ? this.theme.fg("muted", ` (${item.branch})`) : "";
|
|
313
|
-
const status = item.state
|
|
314
|
-
? ` ${this.theme.fg(item.state === "failed" ? "error" : item.state === "updated" ? "success" : "muted", item.state === "failed" ? "!" : item.state === "updated" ? "✓" : "…")}`
|
|
315
|
-
: "";
|
|
335
|
+
const status = item.state ? ` ${this.referenceStatusMark(item.state)}` : "";
|
|
316
336
|
return [truncateToWidth(`${label}${owner}${branch}${status}`, width, "")];
|
|
317
337
|
}
|
|
318
338
|
|
|
@@ -584,6 +604,38 @@ class ReferencePanel implements Component {
|
|
|
584
604
|
this.syncPanel();
|
|
585
605
|
}
|
|
586
606
|
|
|
607
|
+
private async hydrateInitialReferences(locations: readonly ReferenceLocation[]): Promise<void> {
|
|
608
|
+
if (locations.length === 0) return;
|
|
609
|
+
const results = await Promise.all(
|
|
610
|
+
locations.map(async (location) => {
|
|
611
|
+
try {
|
|
612
|
+
return {
|
|
613
|
+
ok: true as const,
|
|
614
|
+
location,
|
|
615
|
+
item: await loadReference(this.git, location.name, location.path),
|
|
616
|
+
};
|
|
617
|
+
} catch (error) {
|
|
618
|
+
return { ok: false as const, location, error };
|
|
619
|
+
}
|
|
620
|
+
}),
|
|
621
|
+
);
|
|
622
|
+
const loaded = new Map<string, ReferenceItem>();
|
|
623
|
+
const failures: string[] = [];
|
|
624
|
+
for (const result of results) {
|
|
625
|
+
if (result.ok) loaded.set(result.location.path, result.item);
|
|
626
|
+
else failures.push(`${result.location.name}: ${errorText(result.error)}`);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
this.refs = this.refs.map((item) => {
|
|
630
|
+
if (item.state !== "loading") return item;
|
|
631
|
+
const metadata = loaded.get(item.path);
|
|
632
|
+
return metadata ? toListItem(metadata) : { ...item, state: "failed" };
|
|
633
|
+
});
|
|
634
|
+
this.list.setItems(this.refs);
|
|
635
|
+
this.syncPanel();
|
|
636
|
+
if (failures.length > 0) this.ctx.ui.notify(`Reference metadata load failed:\n${failures.join("\n")}`, "error");
|
|
637
|
+
}
|
|
638
|
+
|
|
587
639
|
private syncPanel(): void {
|
|
588
640
|
this.panelConfig.secondary = this.secondaryText();
|
|
589
641
|
this.panelConfig.header = this.headerLines();
|
|
@@ -736,28 +788,39 @@ function compareReferenceItems(left: ReferenceListItem, right: ReferenceListItem
|
|
|
736
788
|
return left.name.localeCompare(right.name);
|
|
737
789
|
}
|
|
738
790
|
|
|
739
|
-
async function
|
|
791
|
+
async function loadReferenceLocations(): Promise<ReferenceLocation[]> {
|
|
740
792
|
await mkdir(REFERENCES_DIR, { recursive: true });
|
|
741
|
-
|
|
793
|
+
return (await readdir(REFERENCES_DIR, { withFileTypes: true }))
|
|
742
794
|
.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".clone-"))
|
|
743
795
|
.map((entry) => ({ name: entry.name, path: join(REFERENCES_DIR, entry.name) }))
|
|
744
796
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
797
|
+
}
|
|
745
798
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
return
|
|
799
|
+
async function loadReferences(git: GitRunner): Promise<ReferenceItem[]> {
|
|
800
|
+
const refs = await loadReferenceLocations();
|
|
801
|
+
return Promise.all(refs.map((ref) => loadReference(git, ref.name, ref.path)));
|
|
749
802
|
}
|
|
750
803
|
|
|
751
804
|
async function loadReference(git: GitRunner, name: string, path: string): Promise<ReferenceItem> {
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
805
|
+
const [status, remoteUrl] = await Promise.all([
|
|
806
|
+
git.run(["status", "--porcelain=v2", "--branch"], { cwd: path, optional: true }),
|
|
807
|
+
git.run(["config", "--get", "remote.origin.url"], { cwd: path, optional: true }),
|
|
808
|
+
]);
|
|
809
|
+
let branch = "";
|
|
810
|
+
let commit = "";
|
|
811
|
+
let dirty = false;
|
|
812
|
+
for (const line of status.split("\n")) {
|
|
813
|
+
if (line.startsWith("# branch.head ")) branch = line.slice("# branch.head ".length);
|
|
814
|
+
else if (line.startsWith("# branch.oid ")) commit = line.slice("# branch.oid ".length).slice(0, 7);
|
|
815
|
+
else if (line && !line.startsWith("# ")) dirty = true;
|
|
816
|
+
}
|
|
817
|
+
if (branch === "(detached)") branch = commit ? `detached ${commit}` : "";
|
|
755
818
|
return {
|
|
756
819
|
name,
|
|
757
820
|
displayName: referenceDisplayName(remoteUrl, name),
|
|
758
821
|
path,
|
|
759
|
-
dirty
|
|
760
|
-
branch
|
|
822
|
+
dirty,
|
|
823
|
+
branch,
|
|
761
824
|
};
|
|
762
825
|
}
|
|
763
826
|
|
|
@@ -871,7 +934,7 @@ function referenceNameFromUrl(url: string): string {
|
|
|
871
934
|
return name;
|
|
872
935
|
}
|
|
873
936
|
|
|
874
|
-
|
|
937
|
+
function referenceDisplayName(remoteUrl: string, fallback: string): string {
|
|
875
938
|
const remote = remoteUrl
|
|
876
939
|
.trim()
|
|
877
940
|
.replace(/[?#].*$/, "")
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# Review
|
|
2
2
|
|
|
3
|
-
Run `/review` to inspect current repository's staged, unstaged, and untracked work in a fresh isolated session.
|
|
3
|
+
Run `/review` without arguments to inspect the current repository's staged, unstaged, and untracked work in a fresh isolated session. This form stops when the working tree is clean.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- `architecture` performs a nuclear maintainability review and accepts substantial redesign when ownership, reuse, or structure is poor.
|
|
7
|
-
- `correctness` checks runtime bugs and failures after architecture is accepted.
|
|
5
|
+
Add review direction after the command to review the requested part of the repository instead, regardless of whether the working tree has changes:
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
```text
|
|
8
|
+
/review focus on cancellation, cleanup, and data loss
|
|
9
|
+
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Choose one focused review type:
|
|
12
|
+
|
|
13
|
+
- `Simplify` looks for code and concepts that can disappear or reuse what already exists.
|
|
14
|
+
- `Architecture` reconsiders ownership, boundaries, reuse, and overall structure.
|
|
15
|
+
- `Correctness` checks concrete runtime bugs and failure paths after accepting the architecture.
|
|
16
|
+
|
|
17
|
+
Then choose which logged-in provider runs the review. OpenAI Codex uses `gpt-5.6-sol` and Anthropic uses `claude-opus-5`, both at high thinking. Only providers you are logged in to appear. With no logged-in provider, the review uses the current model.
|
|
18
|
+
|
|
19
|
+
Tau writes each result as Markdown under `.pi/tau/reviews/`. Review results do not enter the parent agent context. Reference the Markdown file later when you want an agent to use it.
|
|
@@ -1,135 +1,94 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import type { ExtensionAPI, ExtensionContext
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { createGitRunner, loadRepoStatus } from "../../shared/git.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveEffortProviders } from "../../shared/model-effort.ts";
|
|
6
6
|
import { errorText } from "../../shared/text.ts";
|
|
7
|
-
import {
|
|
8
|
-
formatReviewMarkdown,
|
|
9
|
-
isReviewRecord,
|
|
10
|
-
isReviewMode,
|
|
11
|
-
REVIEW_ENTRY_TYPE,
|
|
12
|
-
type ReviewMode,
|
|
13
|
-
type ReviewRecord,
|
|
14
|
-
} from "./model.ts";
|
|
15
|
-
import { ReviewProgressPanel, ReviewResultPanel, type ReviewResultAction } from "./panel.ts";
|
|
7
|
+
import { formatReviewMarkdown, type ReviewMode } from "./model.ts";
|
|
16
8
|
import { runReview } from "./session.ts";
|
|
17
9
|
|
|
10
|
+
const REVIEW_TYPES = [
|
|
11
|
+
{ label: "Simplify", mode: "simplify" },
|
|
12
|
+
{ label: "Architecture", mode: "architecture" },
|
|
13
|
+
{ label: "Correctness", mode: "correctness" },
|
|
14
|
+
] as const satisfies ReadonlyArray<{ label: string; mode: ReviewMode }>;
|
|
15
|
+
|
|
18
16
|
export default function reviewExtension(pi: ExtensionAPI): void {
|
|
19
17
|
pi.registerCommand("review", {
|
|
20
|
-
description: "
|
|
18
|
+
description: "Write an isolated requested or Git-change review to Markdown",
|
|
21
19
|
handler: async (args, ctx) => {
|
|
22
20
|
if (ctx.mode !== "tui" || !ctx.isProjectTrusted()) {
|
|
23
21
|
ctx.ui.notify("/review requires a trusted TUI project", "warning");
|
|
24
22
|
return;
|
|
25
23
|
}
|
|
26
24
|
await ctx.waitForIdle();
|
|
27
|
-
const
|
|
28
|
-
if (requested === "show") {
|
|
29
|
-
const latest = latestReview(ctx.sessionManager.getBranch());
|
|
30
|
-
if (!latest) {
|
|
31
|
-
ctx.ui.notify("No review exists on this session branch", "warning");
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
await showReview(pi, ctx, latest);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let mode: ReviewMode | undefined;
|
|
39
|
-
if (requested) {
|
|
40
|
-
if (!isReviewMode(requested)) {
|
|
41
|
-
ctx.ui.notify("Usage: /review [simplify|architecture|correctness|show]", "warning");
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
mode = requested;
|
|
45
|
-
} else {
|
|
46
|
-
const selected = await ctx.ui.select("Review mode", ["Simplify", "Architecture", "Correctness"]);
|
|
47
|
-
if (!selected) return;
|
|
48
|
-
const normalized = selected.toLowerCase();
|
|
49
|
-
if (!isReviewMode(normalized)) return;
|
|
50
|
-
mode = normalized;
|
|
51
|
-
}
|
|
52
|
-
|
|
25
|
+
const direction = args.trim();
|
|
53
26
|
const status = await loadRepoStatus(createGitRunner(pi, ctx));
|
|
54
27
|
if (!status) {
|
|
55
28
|
ctx.ui.notify("/review requires a Git repository", "warning");
|
|
56
29
|
return;
|
|
57
30
|
}
|
|
58
|
-
if (status.fileCount === 0) {
|
|
31
|
+
if (!direction && status.fileCount === 0) {
|
|
59
32
|
ctx.ui.notify("Nothing to review: working tree is clean", "info");
|
|
60
33
|
return;
|
|
61
34
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
35
|
+
const selectedType = await ctx.ui.select(
|
|
36
|
+
"Review type",
|
|
37
|
+
REVIEW_TYPES.map(({ label }) => label),
|
|
38
|
+
);
|
|
39
|
+
if (!selectedType) return;
|
|
40
|
+
const mode = REVIEW_TYPES.find(({ label }) => label === selectedType)?.mode;
|
|
41
|
+
if (!mode) return;
|
|
42
|
+
const providers = resolveEffortProviders(ctx, "deep");
|
|
43
|
+
let preferred: ReviewModelChoice | undefined;
|
|
44
|
+
if (providers.length > 0) {
|
|
45
|
+
const labels = providers.map((provider) => `${provider.label} · ${provider.candidates[0]?.model.id ?? ""}`);
|
|
46
|
+
const selected = await ctx.ui.select("Review provider", labels);
|
|
47
|
+
if (!selected) return;
|
|
48
|
+
const provider = providers[labels.indexOf(selected)];
|
|
49
|
+
const candidate = provider?.candidates[0];
|
|
50
|
+
if (provider && candidate) {
|
|
51
|
+
preferred = { model: `${provider.provider}/${candidate.model.id}`, thinkingLevel: candidate.reasoning };
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
ctx.ui.notify("No logged-in review provider. Using current model.", "warning");
|
|
55
|
+
}
|
|
56
|
+
const signal = ctx.signal ?? new AbortController().signal;
|
|
57
|
+
ctx.ui.setStatus("review", `running ${mode} review`);
|
|
58
|
+
try {
|
|
59
|
+
const output = await runReview({
|
|
69
60
|
ctx,
|
|
70
61
|
root: status.root,
|
|
71
62
|
mode,
|
|
63
|
+
direction,
|
|
64
|
+
preferredModel: preferred?.model,
|
|
65
|
+
preferredThinkingLevel: preferred?.thinkingLevel,
|
|
72
66
|
parentThinkingLevel: pi.getThinkingLevel(),
|
|
73
67
|
signal,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
68
|
+
});
|
|
69
|
+
const createdAt = new Date().toISOString();
|
|
70
|
+
const directory = join(status.root, ".pi", "tau", "reviews");
|
|
71
|
+
const timestamp = createdAt.replace(/[^0-9A-Za-z-]/g, "-");
|
|
72
|
+
const path = join(directory, `${timestamp}-${mode}-review.md`);
|
|
73
|
+
await mkdir(directory, { recursive: true });
|
|
74
|
+
await writeFile(path, formatReviewMarkdown({ ...output, mode, direction, createdAt }), {
|
|
75
|
+
encoding: "utf8",
|
|
76
|
+
mode: 0o600,
|
|
77
|
+
});
|
|
78
|
+
ctx.ui.notify(`Review written to ${path}`, "info");
|
|
79
|
+
} catch (error) {
|
|
84
80
|
ctx.ui.notify(
|
|
85
|
-
signal.aborted ? "Review cancelled" : `Review failed: ${
|
|
81
|
+
signal.aborted ? "Review cancelled" : `Review failed: ${errorText(error)}`,
|
|
86
82
|
signal.aborted ? "info" : "error",
|
|
87
83
|
);
|
|
88
|
-
|
|
84
|
+
} finally {
|
|
85
|
+
ctx.ui.setStatus("review", undefined);
|
|
89
86
|
}
|
|
90
|
-
pi.appendEntry(REVIEW_ENTRY_TYPE, output);
|
|
91
|
-
await showReview(pi, ctx, output);
|
|
92
87
|
},
|
|
93
88
|
});
|
|
94
89
|
}
|
|
95
90
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (entry?.type !== "custom" || entry.customType !== REVIEW_ENTRY_TYPE) continue;
|
|
100
|
-
if (isReviewRecord(entry.data)) return entry.data;
|
|
101
|
-
}
|
|
102
|
-
return undefined;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function showReview(pi: ExtensionAPI, ctx: ExtensionContext, review: ReviewRecord): Promise<void> {
|
|
106
|
-
const action = await ctx.ui.custom<ReviewResultAction>(
|
|
107
|
-
(tui, theme, keys, done) => new ReviewResultPanel(tui, theme, keys, review, done),
|
|
108
|
-
{
|
|
109
|
-
overlay: true,
|
|
110
|
-
overlayOptions: { anchor: "top-center", width: "80%", minWidth: 68, maxHeight: "90%", margin: 1 },
|
|
111
|
-
},
|
|
112
|
-
);
|
|
113
|
-
if (action === "send") {
|
|
114
|
-
try {
|
|
115
|
-
await pi.sendMessage(
|
|
116
|
-
createInjectedContext(formatReviewMarkdown(review), { source: "review", title: `${review.mode} review` }),
|
|
117
|
-
{ triggerTurn: false, deliverAs: "nextTurn" },
|
|
118
|
-
);
|
|
119
|
-
ctx.ui.notify("Review queued for agent's next turn", "info");
|
|
120
|
-
} catch (error) {
|
|
121
|
-
ctx.ui.notify(`Failed to send review: ${errorText(error)}`, "error");
|
|
122
|
-
}
|
|
123
|
-
} else if (action === "export") {
|
|
124
|
-
try {
|
|
125
|
-
const directory = join(review.root, ".pi", "tau", "reviews");
|
|
126
|
-
await mkdir(directory, { recursive: true });
|
|
127
|
-
const timestamp = review.createdAt.replace(/[^0-9A-Za-z-]/g, "-");
|
|
128
|
-
const path = join(directory, `${timestamp}-${review.mode}.md`);
|
|
129
|
-
await writeFile(path, formatReviewMarkdown(review), { encoding: "utf8", mode: 0o600 });
|
|
130
|
-
ctx.ui.notify(`Review exported to ${path}`, "info");
|
|
131
|
-
} catch (error) {
|
|
132
|
-
ctx.ui.notify(`Failed to export review: ${errorText(error)}`, "error");
|
|
133
|
-
}
|
|
134
|
-
}
|
|
91
|
+
interface ReviewModelChoice {
|
|
92
|
+
model: string;
|
|
93
|
+
thinkingLevel: NonNullable<ExtensionContext["thinkingLevel"]>;
|
|
135
94
|
}
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import type { Tool } from "@earendil-works/pi-ai";
|
|
2
2
|
import { Type, type Static } from "typebox";
|
|
3
|
-
import { Value } from "typebox/value";
|
|
4
3
|
|
|
5
4
|
const MAX_SUMMARY_LENGTH = 2_000;
|
|
6
5
|
const MAX_FINDINGS = 12;
|
|
7
6
|
const MAX_PATH_LENGTH = 400;
|
|
8
7
|
const MAX_LINES_LENGTH = 80;
|
|
9
8
|
const MAX_FINDING_LENGTH = 1_500;
|
|
10
|
-
const REVIEW_MODE_SCHEMA = Type.Union([
|
|
11
|
-
Type.Literal("simplify"),
|
|
12
|
-
Type.Literal("architecture"),
|
|
13
|
-
Type.Literal("correctness"),
|
|
14
|
-
]);
|
|
15
9
|
const REVIEW_FINDING_SCHEMA = Type.Object(
|
|
16
10
|
{
|
|
17
11
|
severity: Type.Union([
|
|
@@ -55,21 +49,10 @@ const REVIEW_OUTPUT_SCHEMA = Type.Object(
|
|
|
55
49
|
},
|
|
56
50
|
{ additionalProperties: false },
|
|
57
51
|
);
|
|
58
|
-
const REVIEW_RECORD_SCHEMA = Type.Object(
|
|
59
|
-
{
|
|
60
|
-
...REVIEW_OUTPUT_SCHEMA.properties,
|
|
61
|
-
mode: REVIEW_MODE_SCHEMA,
|
|
62
|
-
root: Type.String({ minLength: 1 }),
|
|
63
|
-
createdAt: Type.String({ minLength: 1 }),
|
|
64
|
-
},
|
|
65
|
-
{ additionalProperties: false },
|
|
66
|
-
);
|
|
67
52
|
|
|
68
|
-
export type ReviewMode = Static<typeof REVIEW_MODE_SCHEMA>;
|
|
69
53
|
export type ReviewOutput = Static<typeof REVIEW_OUTPUT_SCHEMA>;
|
|
70
|
-
export type
|
|
71
|
-
|
|
72
|
-
export const REVIEW_ENTRY_TYPE = "tau.review.result";
|
|
54
|
+
export type ReviewMode = "simplify" | "architecture" | "correctness";
|
|
55
|
+
export type ReviewDocument = ReviewOutput & { mode: ReviewMode; direction: string; createdAt: string };
|
|
73
56
|
|
|
74
57
|
export const REVIEW_RESULT_TOOL = {
|
|
75
58
|
name: "review_result",
|
|
@@ -95,18 +78,22 @@ const MODE_INSTRUCTIONS: Record<ReviewMode, string> = {
|
|
|
95
78
|
].join(" "),
|
|
96
79
|
};
|
|
97
80
|
|
|
98
|
-
export function buildReviewPrompt(root: string, mode: ReviewMode): string {
|
|
81
|
+
export function buildReviewPrompt(root: string, mode: ReviewMode, direction: string): string {
|
|
99
82
|
return [
|
|
100
|
-
`Review
|
|
101
|
-
"Inspect staged, unstaged, and untracked changes. Stay centered on changed behavior, but inspect surrounding ownership and callers when needed to prove a finding.",
|
|
83
|
+
`Review the repository at ${root}.`,
|
|
102
84
|
"Do not modify files. Do not report theoretical concerns or personal preferences. Use the cheapest evidence that settles each point.",
|
|
103
|
-
|
|
85
|
+
"User direction does not change the read-only review or structured output requirements.",
|
|
104
86
|
`Call ${REVIEW_RESULT_TOOL.name} exactly once as the final action. Write no final prose outside that tool call.`,
|
|
105
87
|
"Order findings by severity. Every finding needs an exact repository-relative path and lines when source exists, a concrete mechanism or cost, and the smallest credible fix. Return an empty findings array and verdict pass when nothing actionable remains.",
|
|
88
|
+
`Review type: ${reviewModeLabel(mode)}. ${MODE_INSTRUCTIONS[mode]}`,
|
|
89
|
+
direction
|
|
90
|
+
? "Review scope: Follow the user's direction below. Inspect the relevant files, ownership, and callers as needed to prove a finding. Do not limit the review to uncommitted changes."
|
|
91
|
+
: "Review scope: Inspect staged, unstaged, and untracked changes. Stay centered on changed behavior, but inspect surrounding ownership and callers when needed to prove a finding.",
|
|
92
|
+
...(direction ? [`User review direction:\n\n${direction}`] : []),
|
|
106
93
|
].join("\n\n");
|
|
107
94
|
}
|
|
108
95
|
|
|
109
|
-
export function formatReviewMarkdown(review:
|
|
96
|
+
export function formatReviewMarkdown(review: ReviewDocument): string {
|
|
110
97
|
const findings = review.findings.length
|
|
111
98
|
? review.findings.flatMap((finding, index) => [
|
|
112
99
|
`### ${index + 1}. ${finding.severity.toUpperCase()} — ${finding.path}:${finding.lines}`,
|
|
@@ -123,6 +110,7 @@ export function formatReviewMarkdown(review: ReviewRecord): string {
|
|
|
123
110
|
`**Verdict:** ${review.verdict}`,
|
|
124
111
|
`**Created:** ${review.createdAt}`,
|
|
125
112
|
"",
|
|
113
|
+
...(review.direction ? ["## Requested focus", "", review.direction, ""] : []),
|
|
126
114
|
review.summary,
|
|
127
115
|
"",
|
|
128
116
|
"## Findings",
|
|
@@ -131,14 +119,6 @@ export function formatReviewMarkdown(review: ReviewRecord): string {
|
|
|
131
119
|
].join("\n");
|
|
132
120
|
}
|
|
133
121
|
|
|
134
|
-
|
|
135
|
-
return Value.Check(REVIEW_MODE_SCHEMA, value);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function reviewModeLabel(mode: ReviewMode): string {
|
|
122
|
+
function reviewModeLabel(mode: ReviewMode): string {
|
|
139
123
|
return `${mode.charAt(0).toUpperCase()}${mode.slice(1)}`;
|
|
140
124
|
}
|
|
141
|
-
|
|
142
|
-
export function isReviewRecord(value: unknown): value is ReviewRecord {
|
|
143
|
-
return Value.Check(REVIEW_RECORD_SCHEMA, value);
|
|
144
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { defineTool, type
|
|
3
|
+
import { defineTool, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import {
|
|
5
5
|
createIsolatedSessionResource,
|
|
6
6
|
resolveIsolatedSessionModel,
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
} from "../../shared/isolated-session.ts";
|
|
9
9
|
import { buildReviewPrompt, REVIEW_RESULT_TOOL, type ReviewMode, type ReviewOutput } from "./model.ts";
|
|
10
10
|
|
|
11
|
-
const REVIEW_MODEL = "openai-codex/gpt-5.6-sol";
|
|
12
11
|
const REVIEW_TOOLS = [
|
|
13
12
|
"read",
|
|
14
13
|
"bash",
|
|
@@ -32,11 +31,13 @@ export async function runReview(options: {
|
|
|
32
31
|
ctx: ExtensionContext;
|
|
33
32
|
root: string;
|
|
34
33
|
mode: ReviewMode;
|
|
34
|
+
direction: string;
|
|
35
|
+
preferredModel: string | undefined;
|
|
36
|
+
preferredThinkingLevel: NonNullable<ExtensionContext["thinkingLevel"]> | undefined;
|
|
35
37
|
parentThinkingLevel: NonNullable<ExtensionContext["thinkingLevel"]>;
|
|
36
38
|
signal: AbortSignal;
|
|
37
|
-
onProgress: (status: string) => void;
|
|
38
39
|
}): Promise<ReviewOutput> {
|
|
39
|
-
const { ctx, root, mode,
|
|
40
|
+
const { ctx, root, mode, direction, signal } = options;
|
|
40
41
|
let output: ReviewOutput | undefined;
|
|
41
42
|
const outputTool = defineTool({
|
|
42
43
|
...REVIEW_RESULT_TOOL,
|
|
@@ -51,17 +52,16 @@ export async function runReview(options: {
|
|
|
51
52
|
},
|
|
52
53
|
});
|
|
53
54
|
let resource: IsolatedSessionResource | undefined;
|
|
54
|
-
let unsubscribe: (() => void) | undefined;
|
|
55
55
|
try {
|
|
56
56
|
const selected = await resolveIsolatedSessionModel({
|
|
57
57
|
label: "Review",
|
|
58
|
-
preferredModel:
|
|
59
|
-
preferredThinkingLevel:
|
|
58
|
+
preferredModel: options.preferredModel,
|
|
59
|
+
preferredThinkingLevel: options.preferredThinkingLevel,
|
|
60
60
|
usePreferredThinkingAfterModelFallback: false,
|
|
61
61
|
ctx,
|
|
62
62
|
parentThinkingLevel: options.parentThinkingLevel,
|
|
63
63
|
signal,
|
|
64
|
-
onWarning:
|
|
64
|
+
onWarning: (warning) => ctx.ui.notify(warning, "warning"),
|
|
65
65
|
});
|
|
66
66
|
resource = await createIsolatedSessionResource(
|
|
67
67
|
{
|
|
@@ -76,18 +76,10 @@ export async function runReview(options: {
|
|
|
76
76
|
signal,
|
|
77
77
|
);
|
|
78
78
|
const { session } = resource;
|
|
79
|
-
unsubscribe = session.subscribe((event: AgentSessionEvent) => {
|
|
80
|
-
if (event.type === "tool_execution_start") {
|
|
81
|
-
onProgress(`${event.toolName} ${summarizeArgs(event.args)}`.trim());
|
|
82
|
-
} else if (event.type === "tool_execution_end") {
|
|
83
|
-
onProgress(event.isError ? `${event.toolName} failed` : `${event.toolName} complete`);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
79
|
const abort = () => void session.abort().catch(() => undefined);
|
|
87
80
|
signal.addEventListener("abort", abort, { once: true });
|
|
88
81
|
try {
|
|
89
|
-
|
|
90
|
-
await session.prompt(buildReviewPrompt(root, mode), { expandPromptTemplates: false });
|
|
82
|
+
await session.prompt(buildReviewPrompt(root, mode, direction), { expandPromptTemplates: false });
|
|
91
83
|
} finally {
|
|
92
84
|
signal.removeEventListener("abort", abort);
|
|
93
85
|
}
|
|
@@ -95,12 +87,6 @@ export async function runReview(options: {
|
|
|
95
87
|
if (!output) throw new Error("Review ended without structured output");
|
|
96
88
|
return output;
|
|
97
89
|
} finally {
|
|
98
|
-
unsubscribe?.();
|
|
99
90
|
await resource?.dispose();
|
|
100
91
|
}
|
|
101
92
|
}
|
|
102
|
-
|
|
103
|
-
function summarizeArgs(args: unknown): string {
|
|
104
|
-
const text = typeof args === "string" ? args : (JSON.stringify(args) ?? String(args));
|
|
105
|
-
return text.length > 140 ? `${text.slice(0, 139)}…` : text;
|
|
106
|
-
}
|