@oh-my-pi/pi-coding-agent 15.8.0 → 15.8.3
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/CHANGELOG.md +37 -1
- package/dist/types/config/keybindings.d.ts +5 -1
- package/dist/types/edit/index.d.ts +6 -0
- package/dist/types/edit/streaming.d.ts +8 -0
- package/dist/types/export/ttsr.d.ts +9 -0
- package/dist/types/mcp/transports/stdio.d.ts +19 -0
- package/dist/types/plan-mode/plan-protection.d.ts +12 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/utils/jj.d.ts +49 -0
- package/package.json +9 -9
- package/src/config/keybindings.ts +8 -1
- package/src/config/model-registry.ts +18 -7
- package/src/discovery/builtin-rules/index.ts +2 -0
- package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
- package/src/edit/index.ts +10 -0
- package/src/edit/streaming.ts +65 -0
- package/src/export/ttsr.ts +18 -1
- package/src/extensibility/custom-commands/bundled/review/index.ts +74 -45
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/mcp/transports/stdio.ts +55 -22
- package/src/plan-mode/plan-protection.ts +31 -0
- package/src/prompts/agents/reviewer.md +2 -2
- package/src/prompts/review-request.md +1 -1
- package/src/prompts/system/empty-stop-retry.md +6 -0
- package/src/prompts/tools/search-tool-bm25.md +9 -2
- package/src/prompts/tools/todo-write.md +8 -0
- package/src/session/agent-session.ts +162 -10
- package/src/tools/search-tool-bm25.ts +7 -1
- package/src/tools/write.ts +6 -0
- package/src/utils/git.ts +19 -23
- package/src/utils/jj.ts +248 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. Review a specific commit
|
|
8
8
|
* 4. Custom review instructions
|
|
9
9
|
*
|
|
10
|
-
* Runs
|
|
10
|
+
* Runs VCS diffs upfront, parses results, filters noise, and provides
|
|
11
11
|
* rich context for the orchestrating agent to distribute work across
|
|
12
12
|
* multiple reviewer agents based on diff weight and locality.
|
|
13
13
|
*/
|
|
@@ -18,6 +18,7 @@ import reviewCustomRequestTemplate from "../../../../prompts/review-custom-reque
|
|
|
18
18
|
import reviewHeadlessRequestTemplate from "../../../../prompts/review-headless-request.md" with { type: "text" };
|
|
19
19
|
import reviewRequestTemplate from "../../../../prompts/review-request.md" with { type: "text" };
|
|
20
20
|
import * as git from "../../../../utils/git";
|
|
21
|
+
import * as jj from "../../../../utils/jj";
|
|
21
22
|
|
|
22
23
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
24
|
// Types
|
|
@@ -37,6 +38,13 @@ interface DiffStats {
|
|
|
37
38
|
excluded: { path: string; reason: string; linesAdded: number; linesRemoved: number }[];
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
interface CurrentReviewDiff {
|
|
42
|
+
diffInstruction: string;
|
|
43
|
+
diffText: string;
|
|
44
|
+
emptyMessage?: string;
|
|
45
|
+
mode: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
49
|
// Exclusion patterns for noise files
|
|
42
50
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -195,11 +203,20 @@ function getDiffPreview(hunks: string, maxLines: number): string {
|
|
|
195
203
|
// Thresholds for diff inclusion
|
|
196
204
|
const MAX_DIFF_CHARS = 50_000; // Don't include diff above this
|
|
197
205
|
const MAX_FILES_FOR_INLINE_DIFF = 20; // Don't include diff if more files than this
|
|
206
|
+
const DEFAULT_LARGE_DIFF_INSTRUCTION = "MUST run `git diff`/`git show` for assigned files";
|
|
207
|
+
const GIT_UNCOMMITTED_DIFF_INSTRUCTION =
|
|
208
|
+
"MUST run both `git diff -- <path>` and `git diff --cached -- <path>` for assigned files";
|
|
209
|
+
const JJ_UNCOMMITTED_DIFF_INSTRUCTION = "MUST run `jj --ignore-working-copy diff --git -- <path>` for assigned files";
|
|
198
210
|
|
|
199
211
|
/**
|
|
200
212
|
* Build the full review prompt with diff stats and distribution guidance.
|
|
201
213
|
*/
|
|
202
|
-
function buildReviewPrompt(
|
|
214
|
+
function buildReviewPrompt(
|
|
215
|
+
mode: string,
|
|
216
|
+
stats: DiffStats,
|
|
217
|
+
rawDiff: string,
|
|
218
|
+
options: { additionalInstructions?: string; diffInstruction?: string } = {},
|
|
219
|
+
): string {
|
|
203
220
|
const agentCount = getRecommendedAgentCount(stats);
|
|
204
221
|
const skipDiff = rawDiff.length > MAX_DIFF_CHARS || stats.files.length > MAX_FILES_FOR_INLINE_DIFF;
|
|
205
222
|
const totalLines = stats.totalAdded + stats.totalRemoved;
|
|
@@ -223,7 +240,8 @@ function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string, addi
|
|
|
223
240
|
skipDiff,
|
|
224
241
|
rawDiff: rawDiff.trim(),
|
|
225
242
|
linesPerFile,
|
|
226
|
-
additionalInstructions,
|
|
243
|
+
additionalInstructions: options.additionalInstructions,
|
|
244
|
+
diffInstruction: options.diffInstruction ?? DEFAULT_LARGE_DIFF_INSTRUCTION,
|
|
227
245
|
});
|
|
228
246
|
}
|
|
229
247
|
|
|
@@ -305,49 +323,32 @@ export class ReviewCommand implements CustomCommand {
|
|
|
305
323
|
`Reviewing changes between \`${baseBranch}\` and \`${currentBranch}\` (PR-style)`,
|
|
306
324
|
stats,
|
|
307
325
|
diffText,
|
|
308
|
-
extraInstructions,
|
|
326
|
+
{ additionalInstructions: extraInstructions },
|
|
309
327
|
);
|
|
310
328
|
}
|
|
311
329
|
|
|
312
330
|
case 2: {
|
|
313
|
-
|
|
314
|
-
const status = await getGitStatus(this.api);
|
|
315
|
-
if (!status.trim()) {
|
|
316
|
-
ctx.ui.notify("No uncommitted changes found", "warning");
|
|
317
|
-
return undefined;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
let unstagedDiff: string;
|
|
321
|
-
let stagedDiff: string;
|
|
322
|
-
try {
|
|
323
|
-
[unstagedDiff, stagedDiff] = await Promise.all([
|
|
324
|
-
git.diff(this.api.cwd),
|
|
325
|
-
git.diff(this.api.cwd, { cached: true }),
|
|
326
|
-
]);
|
|
327
|
-
} catch (err) {
|
|
331
|
+
const reviewDiff = await getUncommittedReviewDiff(this.api).catch(err => {
|
|
328
332
|
ctx.ui.notify(`Failed to get diff: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
329
333
|
return undefined;
|
|
330
|
-
}
|
|
334
|
+
});
|
|
335
|
+
if (!reviewDiff) return undefined;
|
|
331
336
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (!combinedDiff.trim()) {
|
|
335
|
-
ctx.ui.notify("No diff content found", "warning");
|
|
337
|
+
if (!reviewDiff.diffText.trim()) {
|
|
338
|
+
ctx.ui.notify(reviewDiff.emptyMessage ?? "No diff content found", "warning");
|
|
336
339
|
return undefined;
|
|
337
340
|
}
|
|
338
341
|
|
|
339
|
-
const stats = parseDiff(
|
|
342
|
+
const stats = parseDiff(reviewDiff.diffText);
|
|
340
343
|
if (stats.files.length === 0) {
|
|
341
344
|
ctx.ui.notify("No reviewable files (all changes filtered out)", "warning");
|
|
342
345
|
return undefined;
|
|
343
346
|
}
|
|
344
347
|
|
|
345
|
-
return buildReviewPrompt(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
extraInstructions,
|
|
350
|
-
);
|
|
348
|
+
return buildReviewPrompt(reviewDiff.mode, stats, reviewDiff.diffText, {
|
|
349
|
+
additionalInstructions: extraInstructions,
|
|
350
|
+
diffInstruction: reviewDiff.diffInstruction,
|
|
351
|
+
});
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
case 3: {
|
|
@@ -383,11 +384,13 @@ export class ReviewCommand implements CustomCommand {
|
|
|
383
384
|
return undefined;
|
|
384
385
|
}
|
|
385
386
|
|
|
386
|
-
return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats, diffText,
|
|
387
|
+
return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats, diffText, {
|
|
388
|
+
additionalInstructions: extraInstructions,
|
|
389
|
+
});
|
|
387
390
|
}
|
|
388
391
|
|
|
389
392
|
case 4: {
|
|
390
|
-
// Custom instructions
|
|
393
|
+
// Custom instructions with opportunistic current-diff context.
|
|
391
394
|
const instructions = await ctx.ui.editor(
|
|
392
395
|
"Enter custom review instructions",
|
|
393
396
|
"Review the following:\n\n",
|
|
@@ -396,23 +399,19 @@ export class ReviewCommand implements CustomCommand {
|
|
|
396
399
|
);
|
|
397
400
|
if (!instructions?.trim()) return undefined;
|
|
398
401
|
|
|
399
|
-
|
|
400
|
-
let diffText: string | undefined;
|
|
401
|
-
try {
|
|
402
|
-
diffText = await git.diff(this.api.cwd, { base: "HEAD" });
|
|
403
|
-
} catch {
|
|
404
|
-
diffText = undefined;
|
|
405
|
-
}
|
|
406
|
-
const reviewDiff = diffText?.trim();
|
|
402
|
+
const reviewDiff = await getUncommittedReviewDiff(this.api).catch(() => undefined);
|
|
407
403
|
|
|
408
|
-
if (reviewDiff) {
|
|
409
|
-
const stats = parseDiff(reviewDiff);
|
|
404
|
+
if (reviewDiff?.diffText.trim()) {
|
|
405
|
+
const stats = parseDiff(reviewDiff.diffText);
|
|
410
406
|
// Even if all files filtered, include the custom instructions
|
|
411
407
|
return buildReviewPrompt(
|
|
412
408
|
`Custom review: ${instructions.split("\n")[0].slice(0, 60)}…`,
|
|
413
409
|
stats,
|
|
414
|
-
reviewDiff,
|
|
415
|
-
|
|
410
|
+
reviewDiff.diffText,
|
|
411
|
+
{
|
|
412
|
+
additionalInstructions: instructions,
|
|
413
|
+
diffInstruction: reviewDiff.diffInstruction,
|
|
414
|
+
},
|
|
416
415
|
);
|
|
417
416
|
}
|
|
418
417
|
|
|
@@ -449,6 +448,36 @@ async function getGitStatus(api: CustomCommandAPI): Promise<string> {
|
|
|
449
448
|
}
|
|
450
449
|
}
|
|
451
450
|
|
|
451
|
+
async function getUncommittedReviewDiff(api: CustomCommandAPI): Promise<CurrentReviewDiff> {
|
|
452
|
+
if (await jj.repo.is(api.cwd)) {
|
|
453
|
+
return {
|
|
454
|
+
diffText: await jj.diff(api.cwd),
|
|
455
|
+
diffInstruction: JJ_UNCOMMITTED_DIFF_INSTRUCTION,
|
|
456
|
+
emptyMessage: "No uncommitted changes found",
|
|
457
|
+
mode: "Reviewing JJ working-copy changes",
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const status = await getGitStatus(api);
|
|
462
|
+
if (!status.trim()) {
|
|
463
|
+
return {
|
|
464
|
+
diffText: "",
|
|
465
|
+
diffInstruction: GIT_UNCOMMITTED_DIFF_INSTRUCTION,
|
|
466
|
+
emptyMessage: "No uncommitted changes found",
|
|
467
|
+
mode: "Reviewing uncommitted changes (staged + unstaged)",
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const [unstagedDiff, stagedDiff] = await Promise.all([git.diff(api.cwd), git.diff(api.cwd, { cached: true })]);
|
|
472
|
+
const combinedDiff = [unstagedDiff, stagedDiff].filter(Boolean).join("\n");
|
|
473
|
+
return {
|
|
474
|
+
diffText: combinedDiff,
|
|
475
|
+
diffInstruction: GIT_UNCOMMITTED_DIFF_INSTRUCTION,
|
|
476
|
+
emptyMessage: "No diff content found",
|
|
477
|
+
mode: "Reviewing uncommitted changes (staged + unstaged)",
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
452
481
|
async function getRecentCommits(api: CustomCommandAPI, count: number): Promise<string[]> {
|
|
453
482
|
try {
|
|
454
483
|
return await git.log.onelines(api.cwd, count);
|