@ia-ccun/code-agent-cli 0.0.54-beta.1 → 0.0.54-beta.4
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/bin/cli.js +116 -74
- package/config/agent/extensions/aicode-advisor.ts +187 -64
- package/config/agent/extensions/aicode-banner.ts +101 -0
- package/config/agent/extensions/aicode-btw.ts +619 -182
- package/config/agent/extensions/aicode-cd.ts +246 -63
- package/config/agent/extensions/aicode-pets.ts +0 -11
- package/config/agent/extensions/aicode-plan.ts +2 -0
- package/config/agent/extensions/aicode-safety-guard.ts +3 -0
- package/config/agent/extensions/aicode-tips.ts +1 -16
- package/config/agent/extensions/context-monitor.ts +30 -15
- package/config/agent/extensions/context.ts +132 -60
- package/config/agent/extensions/custom-footer.ts +42 -24
- package/config/agent/extensions/custom.ts +41 -29
- package/config/agent/extensions/error-monitor.ts +37 -33
- package/config/agent/extensions/goal.ts +8 -6
- package/config/agent/extensions/msg-debug.ts +103 -112
- package/config/agent/extensions/review.ts +365 -164
- package/config/agent/extensions/working-msg.ts +2 -4
- package/config/agent/settings.json +15 -14
- package/config/agent/skills/hello/SKILL.md +1 -1
- package/config/agent/skills/refactor/SKILL.md +40 -0
- package/dist/banner.d.ts.map +1 -1
- package/dist/banner.js +8 -1
- package/dist/banner.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +9 -2
- package/dist/config-loader.js.map +1 -1
- package/package.json +6 -7
- package/scripts/regression/README.md +224 -0
- package/scripts/regression/cases/01-version-and-deps.sh +94 -0
- package/scripts/regression/cases/02-extension-loading.sh +130 -0
- package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
- package/scripts/regression/cases/04-prompt-stream.sh +130 -0
- package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
- package/scripts/regression/cases/06-tool-execution.sh +115 -0
- package/scripts/regression/cases/07-pi-new-features.sh +141 -0
- package/scripts/regression/cases/08-cwd-resilience.sh +84 -0
- package/scripts/regression/lib/common.sh +287 -0
- package/scripts/regression/results/.last_pid +1 -0
- package/scripts/regression/results/.last_workdir +1 -0
- package/scripts/regression/results/02-extension-loading.log +19 -0
- package/scripts/regression/results/03-prompt-stream.log +16 -0
- package/scripts/regression/results/03-rpc-protocol.log +28 -0
- package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
- package/scripts/regression/results/05-tool-execution.log +8 -0
- package/scripts/regression/results/06-pi-new-features.log +27 -0
- package/scripts/regression/results/08-cwd-resilience.log +1 -0
- package/scripts/regression/run-all.sh +187 -0
- package/config/agent/bin/fd.exe +0 -0
- package/config/agent/bin/rg.exe +0 -0
- package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
- package/config/agent/extensions/aicode-snake.ts +0 -354
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
- /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
- /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
- /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/references/stdio-protocol.md +0 -0
|
@@ -27,9 +27,18 @@
|
|
|
27
27
|
* Note: PR review requires a clean working tree (no uncommitted changes to tracked files).
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
import type {
|
|
30
|
+
import type {
|
|
31
|
+
ExtensionAPI,
|
|
32
|
+
ExtensionContext,
|
|
33
|
+
ExtensionCommandContext,
|
|
34
|
+
} from "@earendil-works/pi-coding-agent";
|
|
31
35
|
import { DynamicBorder, BorderedLoader } from "@earendil-works/pi-coding-agent";
|
|
32
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
Container,
|
|
38
|
+
type SelectItem,
|
|
39
|
+
SelectList,
|
|
40
|
+
Text,
|
|
41
|
+
} from "@earendil-works/pi-tui";
|
|
33
42
|
import path from "node:path";
|
|
34
43
|
import { promises as fs } from "node:fs";
|
|
35
44
|
|
|
@@ -54,7 +63,11 @@ function setReviewWidget(ctx: ExtensionContext, active: boolean) {
|
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
ctx.ui.setWidget("review", (_tui, theme) => {
|
|
57
|
-
const text = new Text(
|
|
66
|
+
const text = new Text(
|
|
67
|
+
theme.fg("warning", "Review session active, return with /end-review"),
|
|
68
|
+
0,
|
|
69
|
+
0,
|
|
70
|
+
);
|
|
58
71
|
return {
|
|
59
72
|
render(width: number) {
|
|
60
73
|
return text.render(width);
|
|
@@ -107,18 +120,19 @@ const BASE_BRANCH_PROMPT_WITH_MERGE_BASE =
|
|
|
107
120
|
"Review the code changes against the base branch '{baseBranch}'. The merge base commit for this comparison is {mergeBaseSha}. Run `git diff {mergeBaseSha}` to inspect the changes relative to {baseBranch}. Provide prioritized, actionable findings.";
|
|
108
121
|
|
|
109
122
|
const BASE_BRANCH_PROMPT_FALLBACK =
|
|
110
|
-
|
|
123
|
+
'Review the code changes against the base branch \'{branch}\'. Start by finding the merge diff between the current branch and {branch}\'s upstream e.g. (`git merge-base HEAD "$(git rev-parse --abbrev-ref "{branch}@{upstream}")"`), then run `git diff` against that SHA to see what changes we would merge into the {branch} branch. Provide prioritized, actionable findings.';
|
|
111
124
|
|
|
112
125
|
const COMMIT_PROMPT_WITH_TITLE =
|
|
113
126
|
'Review the code changes introduced by commit {sha} ("{title}"). Provide prioritized, actionable findings.';
|
|
114
127
|
|
|
115
|
-
const COMMIT_PROMPT =
|
|
128
|
+
const COMMIT_PROMPT =
|
|
129
|
+
"Review the code changes introduced by commit {sha}. Provide prioritized, actionable findings.";
|
|
116
130
|
|
|
117
131
|
const PULL_REQUEST_PROMPT =
|
|
118
|
-
|
|
132
|
+
"Review pull request #{prNumber} (\"{title}\") against the base branch '{baseBranch}'. The merge base commit for this comparison is {mergeBaseSha}. Run `git diff {mergeBaseSha}` to inspect the changes that would be merged. Provide prioritized, actionable findings.";
|
|
119
133
|
|
|
120
134
|
const PULL_REQUEST_PROMPT_FALLBACK =
|
|
121
|
-
|
|
135
|
+
"Review pull request #{prNumber} (\"{title}\") against the base branch '{baseBranch}'. Start by finding the merge base between the current branch and {baseBranch} (e.g., `git merge-base HEAD {baseBranch}`), then run `git diff` against that SHA to see the changes that would be merged. Provide prioritized, actionable findings.";
|
|
122
136
|
|
|
123
137
|
const FOLDER_REVIEW_PROMPT =
|
|
124
138
|
"Review the code in the following paths: {paths}. This is a snapshot review (not a diff). Read the files directly in these paths and provide prioritized, actionable findings.";
|
|
@@ -192,7 +206,9 @@ Provide your findings in a clear, structured format:
|
|
|
192
206
|
|
|
193
207
|
Output all findings the author would fix if they knew about them. If there are no qualifying findings, explicitly state the code looks good. Don't stop at the first finding - list every qualifying issue.`;
|
|
194
208
|
|
|
195
|
-
async function loadProjectReviewGuidelines(
|
|
209
|
+
async function loadProjectReviewGuidelines(
|
|
210
|
+
cwd: string,
|
|
211
|
+
): Promise<string | null> {
|
|
196
212
|
let currentDir = path.resolve(cwd);
|
|
197
213
|
|
|
198
214
|
while (true) {
|
|
@@ -238,14 +254,22 @@ async function getMergeBase(
|
|
|
238
254
|
]);
|
|
239
255
|
|
|
240
256
|
if (upstreamCode === 0 && upstream.trim()) {
|
|
241
|
-
const { stdout: mergeBase, code } = await pi.exec("git", [
|
|
257
|
+
const { stdout: mergeBase, code } = await pi.exec("git", [
|
|
258
|
+
"merge-base",
|
|
259
|
+
"HEAD",
|
|
260
|
+
upstream.trim(),
|
|
261
|
+
]);
|
|
242
262
|
if (code === 0 && mergeBase.trim()) {
|
|
243
263
|
return mergeBase.trim();
|
|
244
264
|
}
|
|
245
265
|
}
|
|
246
266
|
|
|
247
267
|
// Fall back to using the branch directly
|
|
248
|
-
const { stdout: mergeBase, code } = await pi.exec("git", [
|
|
268
|
+
const { stdout: mergeBase, code } = await pi.exec("git", [
|
|
269
|
+
"merge-base",
|
|
270
|
+
"HEAD",
|
|
271
|
+
branch,
|
|
272
|
+
]);
|
|
249
273
|
if (code === 0 && mergeBase.trim()) {
|
|
250
274
|
return mergeBase.trim();
|
|
251
275
|
}
|
|
@@ -260,7 +284,10 @@ async function getMergeBase(
|
|
|
260
284
|
* Get list of local branches
|
|
261
285
|
*/
|
|
262
286
|
async function getLocalBranches(pi: ExtensionAPI): Promise<string[]> {
|
|
263
|
-
const { stdout, code } = await pi.exec("git", [
|
|
287
|
+
const { stdout, code } = await pi.exec("git", [
|
|
288
|
+
"branch",
|
|
289
|
+
"--format=%(refname:short)",
|
|
290
|
+
]);
|
|
264
291
|
if (code !== 0) return [];
|
|
265
292
|
return stdout
|
|
266
293
|
.trim()
|
|
@@ -271,8 +298,16 @@ async function getLocalBranches(pi: ExtensionAPI): Promise<string[]> {
|
|
|
271
298
|
/**
|
|
272
299
|
* Get list of recent commits
|
|
273
300
|
*/
|
|
274
|
-
async function getRecentCommits(
|
|
275
|
-
|
|
301
|
+
async function getRecentCommits(
|
|
302
|
+
pi: ExtensionAPI,
|
|
303
|
+
limit: number = 10,
|
|
304
|
+
): Promise<Array<{ sha: string; title: string }>> {
|
|
305
|
+
const { stdout, code } = await pi.exec("git", [
|
|
306
|
+
"log",
|
|
307
|
+
`--oneline`,
|
|
308
|
+
`-n`,
|
|
309
|
+
`${limit}`,
|
|
310
|
+
]);
|
|
276
311
|
if (code !== 0) return [];
|
|
277
312
|
|
|
278
313
|
return stdout
|
|
@@ -303,7 +338,10 @@ async function hasPendingChanges(pi: ExtensionAPI): Promise<boolean> {
|
|
|
303
338
|
if (code !== 0) return false;
|
|
304
339
|
|
|
305
340
|
// Filter out untracked files (lines starting with ??)
|
|
306
|
-
const lines = stdout
|
|
341
|
+
const lines = stdout
|
|
342
|
+
.trim()
|
|
343
|
+
.split("\n")
|
|
344
|
+
.filter((line) => line.trim());
|
|
307
345
|
const trackedChanges = lines.filter((line) => !line.startsWith("??"));
|
|
308
346
|
return trackedChanges.length > 0;
|
|
309
347
|
}
|
|
@@ -334,10 +372,16 @@ function parsePrReference(ref: string): number | null {
|
|
|
334
372
|
/**
|
|
335
373
|
* Get PR information from GitHub CLI
|
|
336
374
|
*/
|
|
337
|
-
async function getPrInfo(
|
|
375
|
+
async function getPrInfo(
|
|
376
|
+
pi: ExtensionAPI,
|
|
377
|
+
prNumber: number,
|
|
378
|
+
): Promise<{ baseBranch: string; title: string; headBranch: string } | null> {
|
|
338
379
|
const { stdout, code } = await pi.exec("gh", [
|
|
339
|
-
"pr",
|
|
340
|
-
"
|
|
380
|
+
"pr",
|
|
381
|
+
"view",
|
|
382
|
+
String(prNumber),
|
|
383
|
+
"--json",
|
|
384
|
+
"baseRefName,title,headRefName",
|
|
341
385
|
]);
|
|
342
386
|
|
|
343
387
|
if (code !== 0) return null;
|
|
@@ -357,8 +401,15 @@ async function getPrInfo(pi: ExtensionAPI, prNumber: number): Promise<{ baseBran
|
|
|
357
401
|
/**
|
|
358
402
|
* Checkout a PR using GitHub CLI
|
|
359
403
|
*/
|
|
360
|
-
async function checkoutPr(
|
|
361
|
-
|
|
404
|
+
async function checkoutPr(
|
|
405
|
+
pi: ExtensionAPI,
|
|
406
|
+
prNumber: number,
|
|
407
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
408
|
+
const { stdout, stderr, code } = await pi.exec("gh", [
|
|
409
|
+
"pr",
|
|
410
|
+
"checkout",
|
|
411
|
+
String(prNumber),
|
|
412
|
+
]);
|
|
362
413
|
|
|
363
414
|
if (code !== 0) {
|
|
364
415
|
return { success: false, error: stderr || stdout || "Failed to checkout PR" };
|
|
@@ -383,7 +434,11 @@ async function getCurrentBranch(pi: ExtensionAPI): Promise<string | null> {
|
|
|
383
434
|
*/
|
|
384
435
|
async function getDefaultBranch(pi: ExtensionAPI): Promise<string> {
|
|
385
436
|
// Try to get from remote HEAD
|
|
386
|
-
const { stdout, code } = await pi.exec("git", [
|
|
437
|
+
const { stdout, code } = await pi.exec("git", [
|
|
438
|
+
"symbolic-ref",
|
|
439
|
+
"refs/remotes/origin/HEAD",
|
|
440
|
+
"--short",
|
|
441
|
+
]);
|
|
387
442
|
if (code === 0 && stdout.trim()) {
|
|
388
443
|
return stdout.trim().replace("origin/", "");
|
|
389
444
|
}
|
|
@@ -399,7 +454,10 @@ async function getDefaultBranch(pi: ExtensionAPI): Promise<string> {
|
|
|
399
454
|
/**
|
|
400
455
|
* Build the review prompt based on target
|
|
401
456
|
*/
|
|
402
|
-
async function buildReviewPrompt(
|
|
457
|
+
async function buildReviewPrompt(
|
|
458
|
+
pi: ExtensionAPI,
|
|
459
|
+
target: ReviewTarget,
|
|
460
|
+
): Promise<string> {
|
|
403
461
|
switch (target.type) {
|
|
404
462
|
case "uncommitted":
|
|
405
463
|
return UNCOMMITTED_PROMPT;
|
|
@@ -407,17 +465,20 @@ async function buildReviewPrompt(pi: ExtensionAPI, target: ReviewTarget): Promis
|
|
|
407
465
|
case "baseBranch": {
|
|
408
466
|
const mergeBase = await getMergeBase(pi, target.branch);
|
|
409
467
|
if (mergeBase) {
|
|
410
|
-
return BASE_BRANCH_PROMPT_WITH_MERGE_BASE.replace(
|
|
411
|
-
/{
|
|
412
|
-
|
|
413
|
-
);
|
|
468
|
+
return BASE_BRANCH_PROMPT_WITH_MERGE_BASE.replace(
|
|
469
|
+
/{baseBranch}/g,
|
|
470
|
+
target.branch,
|
|
471
|
+
).replace(/{mergeBaseSha}/g, mergeBase);
|
|
414
472
|
}
|
|
415
473
|
return BASE_BRANCH_PROMPT_FALLBACK.replace(/{branch}/g, target.branch);
|
|
416
474
|
}
|
|
417
475
|
|
|
418
476
|
case "commit":
|
|
419
477
|
if (target.title) {
|
|
420
|
-
return COMMIT_PROMPT_WITH_TITLE.replace("{sha}", target.sha).replace(
|
|
478
|
+
return COMMIT_PROMPT_WITH_TITLE.replace("{sha}", target.sha).replace(
|
|
479
|
+
"{title}",
|
|
480
|
+
target.title,
|
|
481
|
+
);
|
|
421
482
|
}
|
|
422
483
|
return COMMIT_PROMPT.replace("{sha}", target.sha);
|
|
423
484
|
|
|
@@ -427,14 +488,15 @@ async function buildReviewPrompt(pi: ExtensionAPI, target: ReviewTarget): Promis
|
|
|
427
488
|
case "pullRequest": {
|
|
428
489
|
const mergeBase = await getMergeBase(pi, target.baseBranch);
|
|
429
490
|
if (mergeBase) {
|
|
430
|
-
return PULL_REQUEST_PROMPT
|
|
431
|
-
.replace(/{prNumber}/g, String(target.prNumber))
|
|
491
|
+
return PULL_REQUEST_PROMPT.replace(/{prNumber}/g, String(target.prNumber))
|
|
432
492
|
.replace(/{title}/g, target.title)
|
|
433
493
|
.replace(/{baseBranch}/g, target.baseBranch)
|
|
434
494
|
.replace(/{mergeBaseSha}/g, mergeBase);
|
|
435
495
|
}
|
|
436
|
-
return PULL_REQUEST_PROMPT_FALLBACK
|
|
437
|
-
|
|
496
|
+
return PULL_REQUEST_PROMPT_FALLBACK.replace(
|
|
497
|
+
/{prNumber}/g,
|
|
498
|
+
String(target.prNumber),
|
|
499
|
+
)
|
|
438
500
|
.replace(/{title}/g, target.title)
|
|
439
501
|
.replace(/{baseBranch}/g, target.baseBranch);
|
|
440
502
|
}
|
|
@@ -455,19 +517,26 @@ function getUserFacingHint(target: ReviewTarget): string {
|
|
|
455
517
|
return `changes against '${target.branch}'`;
|
|
456
518
|
case "commit": {
|
|
457
519
|
const shortSha = target.sha.slice(0, 7);
|
|
458
|
-
return target.title
|
|
520
|
+
return target.title
|
|
521
|
+
? `commit ${shortSha}: ${target.title}`
|
|
522
|
+
: `commit ${shortSha}`;
|
|
459
523
|
}
|
|
460
524
|
case "custom":
|
|
461
|
-
return target.instructions.length > 40
|
|
525
|
+
return target.instructions.length > 40
|
|
526
|
+
? target.instructions.slice(0, 37) + "..."
|
|
527
|
+
: target.instructions;
|
|
462
528
|
|
|
463
529
|
case "pullRequest": {
|
|
464
|
-
const shortTitle =
|
|
530
|
+
const shortTitle =
|
|
531
|
+
target.title.length > 30 ? target.title.slice(0, 27) + "..." : target.title;
|
|
465
532
|
return `PR #${target.prNumber}: ${shortTitle}`;
|
|
466
533
|
}
|
|
467
534
|
|
|
468
535
|
case "folder": {
|
|
469
536
|
const joined = target.paths.join(", ");
|
|
470
|
-
return joined.length > 40
|
|
537
|
+
return joined.length > 40
|
|
538
|
+
? `folders: ${joined.slice(0, 37)}...`
|
|
539
|
+
: `folders: ${joined}`;
|
|
471
540
|
}
|
|
472
541
|
}
|
|
473
542
|
}
|
|
@@ -475,10 +544,22 @@ function getUserFacingHint(target: ReviewTarget): string {
|
|
|
475
544
|
// Review preset options for the selector (keep this order stable)
|
|
476
545
|
const REVIEW_PRESETS = [
|
|
477
546
|
{ value: "uncommitted", label: "Review uncommitted changes", description: "" },
|
|
478
|
-
{
|
|
547
|
+
{
|
|
548
|
+
value: "baseBranch",
|
|
549
|
+
label: "Review against a base branch",
|
|
550
|
+
description: "(local)",
|
|
551
|
+
},
|
|
479
552
|
{ value: "commit", label: "Review a commit", description: "" },
|
|
480
|
-
{
|
|
481
|
-
|
|
553
|
+
{
|
|
554
|
+
value: "pullRequest",
|
|
555
|
+
label: "Review a pull request",
|
|
556
|
+
description: "(GitHub PR)",
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
value: "folder",
|
|
560
|
+
label: "Review a folder (or more)",
|
|
561
|
+
description: "(snapshot, not diff)",
|
|
562
|
+
},
|
|
482
563
|
{ value: "custom", label: "Custom review instructions", description: "" },
|
|
483
564
|
] as const;
|
|
484
565
|
|
|
@@ -487,10 +568,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
487
568
|
applyReviewState(ctx);
|
|
488
569
|
});
|
|
489
570
|
|
|
490
|
-
pi.
|
|
491
|
-
|
|
571
|
+
// pi 1.1.0: session_switch 改名为 session_before_switch
|
|
572
|
+
pi.on("session_before_switch", (_event, _ctx) => {
|
|
573
|
+
applyReviewState(_ctx);
|
|
492
574
|
});
|
|
493
|
-
|
|
494
575
|
pi.on("session_tree", (_event, ctx) => {
|
|
495
576
|
applyReviewState(ctx);
|
|
496
577
|
});
|
|
@@ -498,7 +579,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
498
579
|
/**
|
|
499
580
|
* Determine the smart default review type based on git state
|
|
500
581
|
*/
|
|
501
|
-
async function getSmartDefault(): Promise<
|
|
582
|
+
async function getSmartDefault(): Promise<
|
|
583
|
+
"uncommitted" | "baseBranch" | "commit"
|
|
584
|
+
> {
|
|
502
585
|
// Priority 1: If there are uncommitted changes, default to reviewing them
|
|
503
586
|
if (await hasUncommittedChanges(pi)) {
|
|
504
587
|
return "uncommitted";
|
|
@@ -518,7 +601,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
518
601
|
/**
|
|
519
602
|
* Show the review preset selector
|
|
520
603
|
*/
|
|
521
|
-
async function showReviewSelector(
|
|
604
|
+
async function showReviewSelector(
|
|
605
|
+
ctx: ExtensionContext,
|
|
606
|
+
): Promise<ReviewTarget | null> {
|
|
522
607
|
// Determine smart default (but keep the list order stable)
|
|
523
608
|
const smartDefault = await getSmartDefault();
|
|
524
609
|
const items: SelectItem[] = REVIEW_PRESETS.map((preset) => ({
|
|
@@ -526,47 +611,55 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
526
611
|
label: preset.label,
|
|
527
612
|
description: preset.description,
|
|
528
613
|
}));
|
|
529
|
-
const smartDefaultIndex = items.findIndex(
|
|
614
|
+
const smartDefaultIndex = items.findIndex(
|
|
615
|
+
(item) => item.value === smartDefault,
|
|
616
|
+
);
|
|
530
617
|
|
|
531
618
|
while (true) {
|
|
532
|
-
const result = await ctx.ui.custom<string | null>(
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
container.addChild(selectList);
|
|
554
|
-
container.addChild(new Text(theme.fg("dim", "Press enter to confirm or esc to go back")));
|
|
555
|
-
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
619
|
+
const result = await ctx.ui.custom<string | null>(
|
|
620
|
+
(tui, theme, _kb, done) => {
|
|
621
|
+
const container = new Container();
|
|
622
|
+
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
623
|
+
container.addChild(
|
|
624
|
+
new Text(theme.fg("accent", theme.bold("Select a review preset"))),
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
628
|
+
selectedPrefix: (text) => theme.fg("accent", text),
|
|
629
|
+
selectedText: (text) => theme.fg("accent", text),
|
|
630
|
+
description: (text) => theme.fg("muted", text),
|
|
631
|
+
scrollInfo: (text) => theme.fg("dim", text),
|
|
632
|
+
noMatch: (text) => theme.fg("warning", text),
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// Preselect the smart default without reordering the list
|
|
636
|
+
if (smartDefaultIndex >= 0) {
|
|
637
|
+
selectList.setSelectedIndex(smartDefaultIndex);
|
|
638
|
+
}
|
|
556
639
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
640
|
+
selectList.onSelect = (item) => done(item.value);
|
|
641
|
+
selectList.onCancel = () => done(null);
|
|
642
|
+
|
|
643
|
+
container.addChild(selectList);
|
|
644
|
+
container.addChild(
|
|
645
|
+
new Text(theme.fg("dim", "Press enter to confirm or esc to go back")),
|
|
646
|
+
);
|
|
647
|
+
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
648
|
+
|
|
649
|
+
return {
|
|
650
|
+
render(width: number) {
|
|
651
|
+
return container.render(width);
|
|
652
|
+
},
|
|
653
|
+
invalidate() {
|
|
654
|
+
container.invalidate();
|
|
655
|
+
},
|
|
656
|
+
handleInput(data: string) {
|
|
657
|
+
selectList.handleInput(data);
|
|
658
|
+
tui.requestRender();
|
|
659
|
+
},
|
|
660
|
+
};
|
|
661
|
+
},
|
|
662
|
+
);
|
|
570
663
|
|
|
571
664
|
if (!result) return null;
|
|
572
665
|
|
|
@@ -614,17 +707,23 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
614
707
|
/**
|
|
615
708
|
* Show branch selector for base branch review
|
|
616
709
|
*/
|
|
617
|
-
async function showBranchSelector(
|
|
710
|
+
async function showBranchSelector(
|
|
711
|
+
ctx: ExtensionContext,
|
|
712
|
+
): Promise<ReviewTarget | null> {
|
|
618
713
|
const branches = await getLocalBranches(pi);
|
|
619
714
|
const currentBranch = await getCurrentBranch(pi);
|
|
620
715
|
const defaultBranch = await getDefaultBranch(pi);
|
|
621
716
|
|
|
622
717
|
// Never offer the current branch as a base branch (reviewing against itself is meaningless).
|
|
623
|
-
const candidateBranches = currentBranch
|
|
718
|
+
const candidateBranches = currentBranch
|
|
719
|
+
? branches.filter((b) => b !== currentBranch)
|
|
720
|
+
: branches;
|
|
624
721
|
|
|
625
722
|
if (candidateBranches.length === 0) {
|
|
626
723
|
ctx.ui.notify(
|
|
627
|
-
currentBranch
|
|
724
|
+
currentBranch
|
|
725
|
+
? `No other branches found (current branch: ${currentBranch})`
|
|
726
|
+
: "No branches found",
|
|
628
727
|
"error",
|
|
629
728
|
);
|
|
630
729
|
return null;
|
|
@@ -646,7 +745,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
646
745
|
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
647
746
|
const container = new Container();
|
|
648
747
|
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
649
|
-
container.addChild(
|
|
748
|
+
container.addChild(
|
|
749
|
+
new Text(theme.fg("accent", theme.bold("Select base branch"))),
|
|
750
|
+
);
|
|
650
751
|
|
|
651
752
|
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
652
753
|
selectedPrefix: (text) => theme.fg("accent", text),
|
|
@@ -656,14 +757,15 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
656
757
|
noMatch: (text) => theme.fg("warning", text),
|
|
657
758
|
});
|
|
658
759
|
|
|
659
|
-
// Enable search
|
|
660
|
-
selectList.searchable = true;
|
|
661
|
-
|
|
662
760
|
selectList.onSelect = (item) => done(item.value);
|
|
663
761
|
selectList.onCancel = () => done(null);
|
|
664
762
|
|
|
665
763
|
container.addChild(selectList);
|
|
666
|
-
container.addChild(
|
|
764
|
+
container.addChild(
|
|
765
|
+
new Text(
|
|
766
|
+
theme.fg("dim", "Type to filter • enter to select • esc to cancel"),
|
|
767
|
+
),
|
|
768
|
+
);
|
|
667
769
|
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
668
770
|
|
|
669
771
|
return {
|
|
@@ -687,7 +789,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
687
789
|
/**
|
|
688
790
|
* Show commit selector
|
|
689
791
|
*/
|
|
690
|
-
async function showCommitSelector(
|
|
792
|
+
async function showCommitSelector(
|
|
793
|
+
ctx: ExtensionContext,
|
|
794
|
+
): Promise<ReviewTarget | null> {
|
|
691
795
|
const commits = await getRecentCommits(pi, 20);
|
|
692
796
|
|
|
693
797
|
if (commits.length === 0) {
|
|
@@ -701,49 +805,54 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
701
805
|
description: "",
|
|
702
806
|
}));
|
|
703
807
|
|
|
704
|
-
const result = await ctx.ui.custom<{ sha: string; title: string } | null>(
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
selectedText: (text) => theme.fg("accent", text),
|
|
712
|
-
description: (text) => theme.fg("muted", text),
|
|
713
|
-
scrollInfo: (text) => theme.fg("dim", text),
|
|
714
|
-
noMatch: (text) => theme.fg("warning", text),
|
|
715
|
-
});
|
|
808
|
+
const result = await ctx.ui.custom<{ sha: string; title: string } | null>(
|
|
809
|
+
(tui, theme, _kb, done) => {
|
|
810
|
+
const container = new Container();
|
|
811
|
+
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
812
|
+
container.addChild(
|
|
813
|
+
new Text(theme.fg("accent", theme.bold("Select commit to review"))),
|
|
814
|
+
);
|
|
716
815
|
|
|
717
|
-
|
|
718
|
-
|
|
816
|
+
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
817
|
+
selectedPrefix: (text) => theme.fg("accent", text),
|
|
818
|
+
selectedText: (text) => theme.fg("accent", text),
|
|
819
|
+
description: (text) => theme.fg("muted", text),
|
|
820
|
+
scrollInfo: (text) => theme.fg("dim", text),
|
|
821
|
+
noMatch: (text) => theme.fg("warning", text),
|
|
822
|
+
});
|
|
719
823
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
824
|
+
selectList.onSelect = (item) => {
|
|
825
|
+
const commit = commits.find((c) => c.sha === item.value);
|
|
826
|
+
if (commit) {
|
|
827
|
+
done(commit);
|
|
828
|
+
} else {
|
|
829
|
+
done(null);
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
selectList.onCancel = () => done(null);
|
|
729
833
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
834
|
+
container.addChild(selectList);
|
|
835
|
+
container.addChild(
|
|
836
|
+
new Text(
|
|
837
|
+
theme.fg("dim", "Type to filter • enter to select • esc to cancel"),
|
|
838
|
+
),
|
|
839
|
+
);
|
|
840
|
+
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
733
841
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
842
|
+
return {
|
|
843
|
+
render(width: number) {
|
|
844
|
+
return container.render(width);
|
|
845
|
+
},
|
|
846
|
+
invalidate() {
|
|
847
|
+
container.invalidate();
|
|
848
|
+
},
|
|
849
|
+
handleInput(data: string) {
|
|
850
|
+
selectList.handleInput(data);
|
|
851
|
+
tui.requestRender();
|
|
852
|
+
},
|
|
853
|
+
};
|
|
854
|
+
},
|
|
855
|
+
);
|
|
747
856
|
|
|
748
857
|
if (!result) return null;
|
|
749
858
|
return { type: "commit", sha: result.sha, title: result.title };
|
|
@@ -752,7 +861,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
752
861
|
/**
|
|
753
862
|
* Show custom instructions input
|
|
754
863
|
*/
|
|
755
|
-
async function showCustomInput(
|
|
864
|
+
async function showCustomInput(
|
|
865
|
+
ctx: ExtensionContext,
|
|
866
|
+
): Promise<ReviewTarget | null> {
|
|
756
867
|
const result = await ctx.ui.editor(
|
|
757
868
|
"Enter review instructions:",
|
|
758
869
|
"Review the code for security vulnerabilities and potential bugs...",
|
|
@@ -772,7 +883,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
772
883
|
/**
|
|
773
884
|
* Show folder input
|
|
774
885
|
*/
|
|
775
|
-
async function showFolderInput(
|
|
886
|
+
async function showFolderInput(
|
|
887
|
+
ctx: ExtensionContext,
|
|
888
|
+
): Promise<ReviewTarget | null> {
|
|
776
889
|
const result = await ctx.ui.editor(
|
|
777
890
|
"Enter folders/files to review (space-separated or one per line):",
|
|
778
891
|
".",
|
|
@@ -788,10 +901,15 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
788
901
|
/**
|
|
789
902
|
* Show PR input and handle checkout
|
|
790
903
|
*/
|
|
791
|
-
async function showPrInput(
|
|
904
|
+
async function showPrInput(
|
|
905
|
+
ctx: ExtensionContext,
|
|
906
|
+
): Promise<ReviewTarget | null> {
|
|
792
907
|
// First check for pending changes that would prevent branch switching
|
|
793
908
|
if (await hasPendingChanges(pi)) {
|
|
794
|
-
ctx.ui.notify(
|
|
909
|
+
ctx.ui.notify(
|
|
910
|
+
"Cannot checkout PR: you have uncommitted changes. Please commit or stash them first.",
|
|
911
|
+
"error",
|
|
912
|
+
);
|
|
795
913
|
return null;
|
|
796
914
|
}
|
|
797
915
|
|
|
@@ -805,7 +923,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
805
923
|
|
|
806
924
|
const prNumber = parsePrReference(prRef);
|
|
807
925
|
if (!prNumber) {
|
|
808
|
-
ctx.ui.notify(
|
|
926
|
+
ctx.ui.notify(
|
|
927
|
+
"Invalid PR reference. Enter a number or GitHub PR URL.",
|
|
928
|
+
"error",
|
|
929
|
+
);
|
|
809
930
|
return null;
|
|
810
931
|
}
|
|
811
932
|
|
|
@@ -814,13 +935,19 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
814
935
|
const prInfo = await getPrInfo(pi, prNumber);
|
|
815
936
|
|
|
816
937
|
if (!prInfo) {
|
|
817
|
-
ctx.ui.notify(
|
|
938
|
+
ctx.ui.notify(
|
|
939
|
+
`Could not find PR #${prNumber}. Make sure gh is authenticated and the PR exists.`,
|
|
940
|
+
"error",
|
|
941
|
+
);
|
|
818
942
|
return null;
|
|
819
943
|
}
|
|
820
944
|
|
|
821
945
|
// Check again for pending changes (in case something changed)
|
|
822
946
|
if (await hasPendingChanges(pi)) {
|
|
823
|
-
ctx.ui.notify(
|
|
947
|
+
ctx.ui.notify(
|
|
948
|
+
"Cannot checkout PR: you have uncommitted changes. Please commit or stash them first.",
|
|
949
|
+
"error",
|
|
950
|
+
);
|
|
824
951
|
return null;
|
|
825
952
|
}
|
|
826
953
|
|
|
@@ -846,10 +973,17 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
846
973
|
/**
|
|
847
974
|
* Execute the review
|
|
848
975
|
*/
|
|
849
|
-
async function executeReview(
|
|
976
|
+
async function executeReview(
|
|
977
|
+
ctx: ExtensionCommandContext,
|
|
978
|
+
target: ReviewTarget,
|
|
979
|
+
useFreshSession: boolean,
|
|
980
|
+
): Promise<void> {
|
|
850
981
|
// Check if we're already in a review
|
|
851
982
|
if (reviewOriginId) {
|
|
852
|
-
ctx.ui.notify(
|
|
983
|
+
ctx.ui.notify(
|
|
984
|
+
"Already in a review. Use /end-review to finish first.",
|
|
985
|
+
"warning",
|
|
986
|
+
);
|
|
853
987
|
return;
|
|
854
988
|
}
|
|
855
989
|
|
|
@@ -858,7 +992,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
858
992
|
// Store current position (where we'll return to)
|
|
859
993
|
const originId = ctx.sessionManager.getLeafId() ?? undefined;
|
|
860
994
|
if (!originId) {
|
|
861
|
-
ctx.ui.notify(
|
|
995
|
+
ctx.ui.notify(
|
|
996
|
+
"Failed to determine review origin. Try again from a session with messages.",
|
|
997
|
+
"error",
|
|
998
|
+
);
|
|
862
999
|
return;
|
|
863
1000
|
}
|
|
864
1001
|
reviewOriginId = originId;
|
|
@@ -881,7 +1018,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
881
1018
|
// Navigate to first user message to create a new branch from that point
|
|
882
1019
|
// Label it as "code-review" so it's visible in the tree
|
|
883
1020
|
try {
|
|
884
|
-
const result = await ctx.navigateTree(firstUserMessage.id, {
|
|
1021
|
+
const result = await ctx.navigateTree(firstUserMessage.id, {
|
|
1022
|
+
summarize: false,
|
|
1023
|
+
label: "code-review",
|
|
1024
|
+
});
|
|
885
1025
|
if (result.cancelled) {
|
|
886
1026
|
reviewOriginId = undefined;
|
|
887
1027
|
return;
|
|
@@ -889,7 +1029,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
889
1029
|
} catch (error) {
|
|
890
1030
|
// Clean up state if navigation fails
|
|
891
1031
|
reviewOriginId = undefined;
|
|
892
|
-
ctx.ui.notify(
|
|
1032
|
+
ctx.ui.notify(
|
|
1033
|
+
`Failed to start review: ${error instanceof Error ? error.message : String(error)}`,
|
|
1034
|
+
"error",
|
|
1035
|
+
);
|
|
893
1036
|
return;
|
|
894
1037
|
}
|
|
895
1038
|
|
|
@@ -903,7 +1046,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
903
1046
|
setReviewWidget(ctx, true);
|
|
904
1047
|
|
|
905
1048
|
// Persist review state so tree navigation can restore/reset it
|
|
906
|
-
pi.appendEntry(REVIEW_STATE_TYPE, {
|
|
1049
|
+
pi.appendEntry(REVIEW_STATE_TYPE, {
|
|
1050
|
+
active: true,
|
|
1051
|
+
originId: lockedOriginId,
|
|
1052
|
+
});
|
|
907
1053
|
}
|
|
908
1054
|
|
|
909
1055
|
const prompt = await buildReviewPrompt(pi, target);
|
|
@@ -931,7 +1077,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
931
1077
|
* Parse command arguments for direct invocation
|
|
932
1078
|
* Returns the target or a special marker for PR that needs async handling
|
|
933
1079
|
*/
|
|
934
|
-
function parseArgs(
|
|
1080
|
+
function parseArgs(
|
|
1081
|
+
args: string | undefined,
|
|
1082
|
+
): ReviewTarget | { type: "pr"; ref: string } | null {
|
|
935
1083
|
if (!args?.trim()) return null;
|
|
936
1084
|
|
|
937
1085
|
const parts = args.trim().split(/\s+/);
|
|
@@ -980,16 +1128,25 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
980
1128
|
/**
|
|
981
1129
|
* Handle PR checkout and return a ReviewTarget (or null on failure)
|
|
982
1130
|
*/
|
|
983
|
-
async function handlePrCheckout(
|
|
1131
|
+
async function handlePrCheckout(
|
|
1132
|
+
ctx: ExtensionContext,
|
|
1133
|
+
ref: string,
|
|
1134
|
+
): Promise<ReviewTarget | null> {
|
|
984
1135
|
// First check for pending changes
|
|
985
1136
|
if (await hasPendingChanges(pi)) {
|
|
986
|
-
ctx.ui.notify(
|
|
1137
|
+
ctx.ui.notify(
|
|
1138
|
+
"Cannot checkout PR: you have uncommitted changes. Please commit or stash them first.",
|
|
1139
|
+
"error",
|
|
1140
|
+
);
|
|
987
1141
|
return null;
|
|
988
1142
|
}
|
|
989
1143
|
|
|
990
1144
|
const prNumber = parsePrReference(ref);
|
|
991
1145
|
if (!prNumber) {
|
|
992
|
-
ctx.ui.notify(
|
|
1146
|
+
ctx.ui.notify(
|
|
1147
|
+
"Invalid PR reference. Enter a number or GitHub PR URL.",
|
|
1148
|
+
"error",
|
|
1149
|
+
);
|
|
993
1150
|
return null;
|
|
994
1151
|
}
|
|
995
1152
|
|
|
@@ -998,7 +1155,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
998
1155
|
const prInfo = await getPrInfo(pi, prNumber);
|
|
999
1156
|
|
|
1000
1157
|
if (!prInfo) {
|
|
1001
|
-
ctx.ui.notify(
|
|
1158
|
+
ctx.ui.notify(
|
|
1159
|
+
`Could not find PR #${prNumber}. Make sure gh is authenticated and the PR exists.`,
|
|
1160
|
+
"error",
|
|
1161
|
+
);
|
|
1002
1162
|
return null;
|
|
1003
1163
|
}
|
|
1004
1164
|
|
|
@@ -1023,7 +1183,8 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1023
1183
|
|
|
1024
1184
|
// Register the /review command
|
|
1025
1185
|
pi.registerCommand("review", {
|
|
1026
|
-
description:
|
|
1186
|
+
description:
|
|
1187
|
+
"Review code changes (PR, uncommitted, branch, commit, folder, or custom)",
|
|
1027
1188
|
handler: async (args, ctx) => {
|
|
1028
1189
|
if (!ctx.hasUI) {
|
|
1029
1190
|
ctx.ui.notify("Review requires interactive mode", "error");
|
|
@@ -1032,7 +1193,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1032
1193
|
|
|
1033
1194
|
// Check if we're already in a review
|
|
1034
1195
|
if (reviewOriginId) {
|
|
1035
|
-
ctx.ui.notify(
|
|
1196
|
+
ctx.ui.notify(
|
|
1197
|
+
"Already in a review. Use /end-review to finish first.",
|
|
1198
|
+
"warning",
|
|
1199
|
+
);
|
|
1036
1200
|
return;
|
|
1037
1201
|
}
|
|
1038
1202
|
|
|
@@ -1084,7 +1248,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1084
1248
|
|
|
1085
1249
|
if (messageCount > 0) {
|
|
1086
1250
|
// Existing session - ask user which mode they want
|
|
1087
|
-
const choice = await ctx.ui.select("Start review in:", [
|
|
1251
|
+
const choice = await ctx.ui.select("Start review in:", [
|
|
1252
|
+
"Empty branch",
|
|
1253
|
+
"Current session",
|
|
1254
|
+
]);
|
|
1088
1255
|
|
|
1089
1256
|
if (choice === undefined) {
|
|
1090
1257
|
if (fromSelector) {
|
|
@@ -1161,7 +1328,10 @@ Instructions:
|
|
|
1161
1328
|
if (state?.active) {
|
|
1162
1329
|
setReviewWidget(ctx, false);
|
|
1163
1330
|
pi.appendEntry(REVIEW_STATE_TYPE, { active: false });
|
|
1164
|
-
ctx.ui.notify(
|
|
1331
|
+
ctx.ui.notify(
|
|
1332
|
+
"Review state was missing origin info; cleared review status.",
|
|
1333
|
+
"warning",
|
|
1334
|
+
);
|
|
1165
1335
|
}
|
|
1166
1336
|
|
|
1167
1337
|
return undefined;
|
|
@@ -1187,7 +1357,10 @@ Instructions:
|
|
|
1187
1357
|
const originId = getActiveReviewOrigin(ctx);
|
|
1188
1358
|
if (!originId) {
|
|
1189
1359
|
if (!getReviewState(ctx)?.active) {
|
|
1190
|
-
ctx.ui.notify(
|
|
1360
|
+
ctx.ui.notify(
|
|
1361
|
+
"Not in a review branch (use /review first, or review was started in current session mode)",
|
|
1362
|
+
"info",
|
|
1363
|
+
);
|
|
1191
1364
|
}
|
|
1192
1365
|
return;
|
|
1193
1366
|
}
|
|
@@ -1215,11 +1388,17 @@ Instructions:
|
|
|
1215
1388
|
try {
|
|
1216
1389
|
const result = await ctx.navigateTree(originId, { summarize: false });
|
|
1217
1390
|
if (result.cancelled) {
|
|
1218
|
-
ctx.ui.notify(
|
|
1391
|
+
ctx.ui.notify(
|
|
1392
|
+
"Navigation cancelled. Use /end-review to try again.",
|
|
1393
|
+
"info",
|
|
1394
|
+
);
|
|
1219
1395
|
return;
|
|
1220
1396
|
}
|
|
1221
1397
|
} catch (error) {
|
|
1222
|
-
ctx.ui.notify(
|
|
1398
|
+
ctx.ui.notify(
|
|
1399
|
+
`Failed to return: ${error instanceof Error ? error.message : String(error)}`,
|
|
1400
|
+
"error",
|
|
1401
|
+
);
|
|
1223
1402
|
return;
|
|
1224
1403
|
}
|
|
1225
1404
|
|
|
@@ -1228,23 +1407,39 @@ Instructions:
|
|
|
1228
1407
|
return;
|
|
1229
1408
|
}
|
|
1230
1409
|
|
|
1231
|
-
const summaryResult = await ctx.ui.custom<{
|
|
1232
|
-
|
|
1410
|
+
const summaryResult = await ctx.ui.custom<{
|
|
1411
|
+
cancelled: boolean;
|
|
1412
|
+
error?: string;
|
|
1413
|
+
} | null>((tui, theme, _kb, done) => {
|
|
1414
|
+
const loader = new BorderedLoader(
|
|
1415
|
+
tui,
|
|
1416
|
+
theme,
|
|
1417
|
+
"Returning and summarizing review branch...",
|
|
1418
|
+
);
|
|
1233
1419
|
loader.onAbort = () => done(null);
|
|
1234
1420
|
|
|
1235
|
-
ctx
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1421
|
+
ctx
|
|
1422
|
+
.navigateTree(originId, {
|
|
1423
|
+
summarize: true,
|
|
1424
|
+
customInstructions: REVIEW_SUMMARY_PROMPT,
|
|
1425
|
+
replaceInstructions: true,
|
|
1426
|
+
})
|
|
1240
1427
|
.then(done)
|
|
1241
|
-
.catch((err) =>
|
|
1428
|
+
.catch((err) =>
|
|
1429
|
+
done({
|
|
1430
|
+
cancelled: false,
|
|
1431
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1432
|
+
}),
|
|
1433
|
+
);
|
|
1242
1434
|
|
|
1243
1435
|
return loader;
|
|
1244
1436
|
});
|
|
1245
1437
|
|
|
1246
1438
|
if (summaryResult === null) {
|
|
1247
|
-
ctx.ui.notify(
|
|
1439
|
+
ctx.ui.notify(
|
|
1440
|
+
"Summarization cancelled. Use /end-review to try again.",
|
|
1441
|
+
"info",
|
|
1442
|
+
);
|
|
1248
1443
|
return;
|
|
1249
1444
|
}
|
|
1250
1445
|
|
|
@@ -1254,7 +1449,10 @@ Instructions:
|
|
|
1254
1449
|
}
|
|
1255
1450
|
|
|
1256
1451
|
if (summaryResult.cancelled) {
|
|
1257
|
-
ctx.ui.notify(
|
|
1452
|
+
ctx.ui.notify(
|
|
1453
|
+
"Navigation cancelled. Use /end-review to try again.",
|
|
1454
|
+
"info",
|
|
1455
|
+
);
|
|
1258
1456
|
return;
|
|
1259
1457
|
}
|
|
1260
1458
|
|
|
@@ -1269,7 +1467,10 @@ Instructions:
|
|
|
1269
1467
|
}
|
|
1270
1468
|
|
|
1271
1469
|
pi.sendUserMessage(REVIEW_FIX_FINDINGS_PROMPT, { deliverAs: "followUp" });
|
|
1272
|
-
ctx.ui.notify(
|
|
1470
|
+
ctx.ui.notify(
|
|
1471
|
+
"Review complete! Returned and queued a follow-up to fix findings.",
|
|
1472
|
+
"info",
|
|
1473
|
+
);
|
|
1273
1474
|
} finally {
|
|
1274
1475
|
endReviewInProgress = false;
|
|
1275
1476
|
}
|