@dreki-gg/pi-code-reviewer 0.1.1 → 0.2.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.
|
@@ -38,7 +38,7 @@ export function registerReviewTool(pi: ExtensionAPI) {
|
|
|
38
38
|
),
|
|
39
39
|
}),
|
|
40
40
|
|
|
41
|
-
async execute(_toolCallId, params, signal,
|
|
41
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
42
42
|
const cwd = ctx.cwd;
|
|
43
43
|
const config = loadConfig(cwd);
|
|
44
44
|
const lensDir = getLensDir(cwd, config);
|
|
@@ -58,12 +58,14 @@ export function registerReviewTool(pi: ExtensionAPI) {
|
|
|
58
58
|
|
|
59
59
|
const lensNames = resolveLensNames(params.lenses, config, available);
|
|
60
60
|
|
|
61
|
+
ctx.ui.setStatus('code-review', '🔍 Collecting diff...');
|
|
61
62
|
const diff = await collectDiff(pi, cwd, {
|
|
62
63
|
base: params.base,
|
|
63
64
|
staged: params.staged,
|
|
64
65
|
});
|
|
65
66
|
|
|
66
67
|
if (!diff.diff.trim()) {
|
|
68
|
+
ctx.ui.setStatus('code-review', undefined);
|
|
67
69
|
return {
|
|
68
70
|
content: [{ type: 'text', text: 'No changes to review.' }],
|
|
69
71
|
details: {},
|
|
@@ -71,15 +73,25 @@ export function registerReviewTool(pi: ExtensionAPI) {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
const results: LensResult[] = [];
|
|
74
|
-
for (
|
|
76
|
+
for (let i = 0; i < lensNames.length; i++) {
|
|
75
77
|
if (signal?.aborted) break;
|
|
76
78
|
|
|
79
|
+
const name = lensNames[i];
|
|
80
|
+
const progressMsg = `Lens ${i + 1}/${lensNames.length}: ${name}`;
|
|
81
|
+
ctx.ui.setStatus('code-review', `🔍 ${progressMsg}`);
|
|
82
|
+
onUpdate?.({
|
|
83
|
+
content: [{ type: 'text', text: progressMsg }],
|
|
84
|
+
details: { currentLens: name, lensIndex: i + 1, totalLenses: lensNames.length },
|
|
85
|
+
});
|
|
86
|
+
|
|
77
87
|
const lens = available.get(name)!;
|
|
78
88
|
const content = getLensContent(lensDir, name) ?? '';
|
|
79
89
|
const result = await reviewWithLens(pi, ctx, cwd, lens, content, diff, signal);
|
|
80
90
|
results.push(result);
|
|
81
91
|
}
|
|
82
92
|
|
|
93
|
+
ctx.ui.setStatus('code-review', undefined);
|
|
94
|
+
|
|
83
95
|
const report: ReviewReport = {
|
|
84
96
|
diff: diff.diff,
|
|
85
97
|
diffStat: diff.stat,
|
|
@@ -3,7 +3,7 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
|
3
3
|
import { loadConfig, getLensDir } from '../config';
|
|
4
4
|
import { collectDiff } from '../diff';
|
|
5
5
|
import { discoverLenses, getLensContent } from '../lenses';
|
|
6
|
-
import { reviewWithLens } from '../reviewer';
|
|
6
|
+
import { reviewWithLens, buildDiffSection } from '../reviewer';
|
|
7
7
|
import { parseReviewArgs } from '../parse-args';
|
|
8
8
|
|
|
9
9
|
export function registerReviewCommand(pi: ExtensionAPI) {
|
|
@@ -34,36 +34,61 @@ export function registerReviewCommand(pi: ExtensionAPI) {
|
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
ctx.ui.setStatus('code-review', '🔍 Collecting diff...');
|
|
37
38
|
const diff = await collectDiff(pi, cwd, {
|
|
38
39
|
base: parsed.base,
|
|
39
40
|
staged: parsed.staged,
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
if (!diff.diff.trim()) {
|
|
44
|
+
ctx.ui.setStatus('code-review', undefined);
|
|
43
45
|
ctx.ui.notify('No changes to review', 'info');
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
ctx.ui.notify(`Reviewing ${diff.label} through ${lensNames.length} lens(es)...`, 'info');
|
|
50
|
+
ctx.ui.setStatus('code-review', `🔍 Reviewing (0/${lensNames.length})...`);
|
|
51
|
+
|
|
52
|
+
const lensSections: string[] = [];
|
|
53
|
+
for (let i = 0; i < lensNames.length; i++) {
|
|
54
|
+
const name = lensNames[i];
|
|
55
|
+
ctx.ui.setStatus('code-review', `🔍 Lens ${i + 1}/${lensNames.length}: ${name}`);
|
|
48
56
|
|
|
49
|
-
const lensPrompts: string[] = [];
|
|
50
|
-
for (const name of lensNames) {
|
|
51
57
|
const lens = available.get(name)!;
|
|
52
58
|
const content = getLensContent(lensDir, name) ?? '';
|
|
53
59
|
const result = await reviewWithLens(pi, ctx, cwd, lens, content, diff);
|
|
54
60
|
|
|
55
|
-
if (result.
|
|
56
|
-
|
|
61
|
+
if (result._lensSection) {
|
|
62
|
+
lensSections.push(result._lensSection);
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
ctx.ui.setStatus('code-review', undefined);
|
|
67
|
+
|
|
60
68
|
const combinedPrompt = [
|
|
61
69
|
`Review the following changes through ${lensNames.length} lens(es): ${lensNames.join(', ')}.`,
|
|
62
70
|
'',
|
|
63
71
|
'For each lens, evaluate the diff against its criteria and produce findings.',
|
|
64
72
|
'Output your review as a structured report with sections per lens.',
|
|
65
73
|
'',
|
|
66
|
-
|
|
74
|
+
buildDiffSection(diff),
|
|
75
|
+
'',
|
|
76
|
+
'## Lenses',
|
|
77
|
+
'',
|
|
78
|
+
...lensSections,
|
|
79
|
+
'',
|
|
80
|
+
'## Instructions',
|
|
81
|
+
'',
|
|
82
|
+
'For each lens above, review the diff and output a JSON array of findings:',
|
|
83
|
+
'',
|
|
84
|
+
'```json',
|
|
85
|
+
'[',
|
|
86
|
+
' { "file": "path/to/file.ts", "line": 42, "severity": "warning", "message": "Description" }',
|
|
87
|
+
']',
|
|
88
|
+
'```',
|
|
89
|
+
'',
|
|
90
|
+
'After each lens JSON array, write a 2-3 sentence summary.',
|
|
91
|
+
'If there are no findings for a lens, return an empty array `[]` and note the code looks good.',
|
|
67
92
|
].join('\n');
|
|
68
93
|
|
|
69
94
|
pi.sendUserMessage(combinedPrompt, { deliverAs: 'followUp' });
|
|
@@ -33,23 +33,13 @@ async function runLensTools(
|
|
|
33
33
|
return outputs;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/** Build the review prompt
|
|
37
|
-
function
|
|
38
|
-
lens: LensConfig,
|
|
39
|
-
lensContent: string,
|
|
40
|
-
diff: DiffSource,
|
|
41
|
-
toolOutputs: Record<string, string>,
|
|
42
|
-
): string {
|
|
36
|
+
/** Build the shared diff section of the review prompt (included once). */
|
|
37
|
+
export function buildDiffSection(diff: DiffSource): string {
|
|
43
38
|
const parts: string[] = [];
|
|
44
|
-
|
|
45
|
-
parts.push(`You are reviewing code changes through the "${lens.name}" lens.`);
|
|
46
|
-
parts.push('');
|
|
47
|
-
parts.push('## Lens Definition');
|
|
48
|
-
parts.push(lensContent);
|
|
49
|
-
parts.push('');
|
|
50
|
-
parts.push(`## Diff (${diff.label})`);
|
|
51
39
|
const maxDiffLen = 50_000;
|
|
52
40
|
const diffTruncated = diff.diff.length > maxDiffLen;
|
|
41
|
+
|
|
42
|
+
parts.push(`## Diff (${diff.label})`);
|
|
53
43
|
parts.push('```diff');
|
|
54
44
|
parts.push(diff.diff.slice(0, maxDiffLen));
|
|
55
45
|
parts.push('```');
|
|
@@ -64,21 +54,60 @@ function buildReviewPrompt(
|
|
|
64
54
|
parts.push(diff.stat);
|
|
65
55
|
parts.push('```');
|
|
66
56
|
|
|
57
|
+
return parts.join('\n');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Build the lens-specific section of the review prompt (no diff duplication). */
|
|
61
|
+
function buildLensSection(
|
|
62
|
+
lens: LensConfig,
|
|
63
|
+
lensContent: string,
|
|
64
|
+
toolOutputs: Record<string, string>,
|
|
65
|
+
): string {
|
|
66
|
+
const parts: string[] = [];
|
|
67
|
+
|
|
68
|
+
parts.push(`### Lens: ${lens.name}`);
|
|
69
|
+
parts.push('');
|
|
70
|
+
parts.push('#### Lens Definition');
|
|
71
|
+
parts.push(lensContent);
|
|
72
|
+
|
|
67
73
|
if (Object.keys(toolOutputs).length > 0) {
|
|
68
74
|
parts.push('');
|
|
69
|
-
parts.push('
|
|
75
|
+
parts.push('#### Tool Outputs');
|
|
70
76
|
for (const [cmd, output] of Object.entries(toolOutputs)) {
|
|
71
|
-
parts.push(
|
|
77
|
+
parts.push(`##### \`${cmd}\``);
|
|
72
78
|
parts.push('```');
|
|
73
79
|
parts.push(output.slice(0, 20_000));
|
|
74
80
|
parts.push('```');
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
|
|
84
|
+
parts.push('');
|
|
85
|
+
parts.push('#### Severity levels');
|
|
86
|
+
if (lens.severityRules.blocker) parts.push(`- **blocker**: ${lens.severityRules.blocker}`);
|
|
87
|
+
if (lens.severityRules.warning) parts.push(`- **warning**: ${lens.severityRules.warning}`);
|
|
88
|
+
if (lens.severityRules.note) parts.push(`- **note**: ${lens.severityRules.note}`);
|
|
89
|
+
|
|
90
|
+
return parts.join('\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Build the full review prompt for a single lens (includes diff — used by the tool path). */
|
|
94
|
+
function buildReviewPrompt(
|
|
95
|
+
lens: LensConfig,
|
|
96
|
+
lensContent: string,
|
|
97
|
+
diff: DiffSource,
|
|
98
|
+
toolOutputs: Record<string, string>,
|
|
99
|
+
): string {
|
|
100
|
+
const parts: string[] = [];
|
|
101
|
+
|
|
102
|
+
parts.push(`You are reviewing code changes through the "${lens.name}" lens.`);
|
|
103
|
+
parts.push('');
|
|
104
|
+
parts.push(buildDiffSection(diff));
|
|
105
|
+
parts.push('');
|
|
106
|
+
parts.push(buildLensSection(lens, lensContent, toolOutputs));
|
|
78
107
|
parts.push('');
|
|
79
108
|
parts.push('## Instructions');
|
|
80
109
|
parts.push('');
|
|
81
|
-
parts.push('Review the diff through this lens. For each finding, output a JSON array:');
|
|
110
|
+
parts.push('Review the diff above through this lens. For each finding, output a JSON array:');
|
|
82
111
|
parts.push('');
|
|
83
112
|
parts.push('```json');
|
|
84
113
|
parts.push('[');
|
|
@@ -88,11 +117,6 @@ function buildReviewPrompt(
|
|
|
88
117
|
parts.push(']');
|
|
89
118
|
parts.push('```');
|
|
90
119
|
parts.push('');
|
|
91
|
-
parts.push('Severity levels:');
|
|
92
|
-
if (lens.severityRules.blocker) parts.push(`- **blocker**: ${lens.severityRules.blocker}`);
|
|
93
|
-
if (lens.severityRules.warning) parts.push(`- **warning**: ${lens.severityRules.warning}`);
|
|
94
|
-
if (lens.severityRules.note) parts.push(`- **note**: ${lens.severityRules.note}`);
|
|
95
|
-
parts.push('');
|
|
96
120
|
parts.push(
|
|
97
121
|
'After the JSON array, write a 2-3 sentence summary of your review through this lens.',
|
|
98
122
|
);
|
|
@@ -116,15 +140,14 @@ export async function reviewWithLens(
|
|
|
116
140
|
|
|
117
141
|
// Build the prompt
|
|
118
142
|
const prompt = buildReviewPrompt(lens, lensContent, diff, toolOutputs);
|
|
143
|
+
const lensSection = buildLensSection(lens, lensContent, toolOutputs);
|
|
119
144
|
|
|
120
|
-
// Use sendMessage to delegate to the agent for review
|
|
121
|
-
// For now, we return the prompt and let the command handler
|
|
122
|
-
// pass it to a subagent via the subagent tool
|
|
123
145
|
return {
|
|
124
146
|
lens: lens.name,
|
|
125
147
|
findings: [],
|
|
126
148
|
summary: '',
|
|
127
149
|
toolOutputs,
|
|
128
150
|
_prompt: prompt,
|
|
151
|
+
_lensSection: lensSection,
|
|
129
152
|
};
|
|
130
153
|
}
|
|
@@ -22,6 +22,8 @@ export type LensResult = {
|
|
|
22
22
|
toolOutputs?: Record<string, string>;
|
|
23
23
|
/** Review prompt built for this lens, used internally to delegate to the agent. */
|
|
24
24
|
_prompt?: string;
|
|
25
|
+
/** Lens-specific section (without diff), used by /review command to avoid diff duplication. */
|
|
26
|
+
_lensSection?: string;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
export type ReviewConfig = {
|