iterate-plugin 2.12.1 → 2.12.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/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/dist/git-scope.js +61 -7
- package/dist/jobs.js +68 -0
- package/dist/review.js +47 -0
- package/dist/tools/fix.js +160 -156
- package/dist/tools/review.js +135 -114
- package/lib/client.js +356 -100
- package/lib/parse.js +93 -0
- package/package.json +6 -4
- package/src/client/index.ts +265 -44
- package/src/git-scope.ts +48 -7
- package/src/jobs.ts +97 -0
- package/src/review.ts +64 -0
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/config.ts +1 -1
- package/src/tools/decision-log.ts +1 -1
- package/src/tools/fix.ts +5 -1
- package/src/tools/history.ts +1 -1
- package/src/tools/prune.ts +1 -1
- package/src/tools/review.ts +28 -3
- package/src/tools/transcript.ts +1 -1
- package/src/tools/triage.ts +1 -1
- package/src/types.ts +26 -0
package/dist/tools/review.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
2
|
import { loadEffectiveConfig, resolveProjectRootForExec } from "../config-loader.js";
|
|
3
|
+
import { runWithJob } from "../jobs.js";
|
|
3
4
|
import { buildReviewPlan, buildReviewReport, sanitizeRounds, validateRoundsSchema, } from "../review.js";
|
|
4
5
|
import { buildFinalReviewReport, metaReviewReport } from "../meta-review.js";
|
|
5
6
|
import { evidenceToPlain, verifyFindings } from "../evidence.js";
|
|
@@ -65,6 +66,15 @@ export function registerReviewTool(ctx) {
|
|
|
65
66
|
description: 'For `meta-review`: the ReviewReport JSON (as returned by `aggregate`) to audit for ' +
|
|
66
67
|
'internal consistency and produce the final review report.',
|
|
67
68
|
},
|
|
69
|
+
attachments: {
|
|
70
|
+
type: 'json',
|
|
71
|
+
description: 'Optional (plan only): image/visual attachments to thread into the review, e.g. ' +
|
|
72
|
+
'[{"path":"screens/hits.png","caption":"reproduced layout bug"}]. Each entry: ' +
|
|
73
|
+
'{path?, data?, media_type?, caption?} — path resolves relative to the project root, ' +
|
|
74
|
+
'data is a base64 payload (media_type e.g. image/png), caption gives human context. ' +
|
|
75
|
+
'Injected as a mandatory clause into every dimension reviewer prompt so screenshots/' +
|
|
76
|
+
'mockups/failure repros are weighed alongside the code.',
|
|
77
|
+
},
|
|
68
78
|
fixedCount: {
|
|
69
79
|
type: 'integer',
|
|
70
80
|
description: 'For `aggregate` (normal mode only): number of atomic fixes applied so far. ' +
|
|
@@ -107,134 +117,145 @@ export function registerReviewTool(ctx) {
|
|
|
107
117
|
],
|
|
108
118
|
},
|
|
109
119
|
async execute(args, exec) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const projectRoot = resolved.root;
|
|
115
|
-
// Effective config = defaults merged with project overrides. Never
|
|
116
|
-
// null, so `plan`/`aggregate` work even without a config file.
|
|
117
|
-
const { config } = loadEffectiveConfig(projectRoot);
|
|
118
|
-
const mode = args.mode ?? 'dry-run';
|
|
119
|
-
if (args.operation === 'plan') {
|
|
120
|
-
const maxReviewRounds = args.maxReviewRounds ?? config.max_rounds ?? DEFAULT_MAX_REVIEW_ROUNDS;
|
|
121
|
-
const knownIntentional = config.personalization
|
|
122
|
-
?.known_intentional;
|
|
123
|
-
// changed-only scope: resolve the changed-file set against
|
|
124
|
-
// git.target_branch before building the plan so reviewers get the
|
|
125
|
-
// concrete file list (and the plan auto-falls back to full when there
|
|
126
|
-
// are no changes). git failures degrade to a full-scope plan.
|
|
127
|
-
let changedFiles;
|
|
128
|
-
if (config.review?.scope === 'changed-only') {
|
|
129
|
-
const gitScope = await resolveChangedFiles(projectRoot, config.git?.target_branch ?? 'main');
|
|
130
|
-
changedFiles = gitScope.changedFiles;
|
|
120
|
+
const { result } = await runWithJob(ctx, 'iterate-review', `iterate_review ${String(args.operation ?? '')} (${String(args.mode ?? 'dry-run')})`, async () => {
|
|
121
|
+
const resolved = resolveProjectRootForExec(exec, args.path);
|
|
122
|
+
if (!resolved.ok) {
|
|
123
|
+
return { operation: args.operation, error: resolved.reason };
|
|
131
124
|
}
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
125
|
+
const projectRoot = resolved.root;
|
|
126
|
+
// Effective config = defaults merged with project overrides. Never
|
|
127
|
+
// null, so `plan`/`aggregate` work even without a config file.
|
|
128
|
+
const { config } = loadEffectiveConfig(projectRoot);
|
|
129
|
+
const mode = args.mode ?? 'dry-run';
|
|
130
|
+
if (args.operation === 'plan') {
|
|
131
|
+
const maxReviewRounds = args.maxReviewRounds ?? config.max_rounds ?? DEFAULT_MAX_REVIEW_ROUNDS;
|
|
132
|
+
const knownIntentional = config.personalization
|
|
133
|
+
?.known_intentional;
|
|
134
|
+
// changed-only scope: resolve the changed-file set against
|
|
135
|
+
// git.target_branch before building the plan so reviewers get the
|
|
136
|
+
// concrete file list (and the plan auto-falls back to full when there
|
|
137
|
+
// are no changes). git failures degrade to a full-scope plan.
|
|
138
|
+
let changedFiles;
|
|
139
|
+
if (config.review?.scope === 'changed-only') {
|
|
140
|
+
const gitScope = await resolveChangedFiles(projectRoot, config.git?.target_branch ?? 'main');
|
|
141
|
+
changedFiles = gitScope.changedFiles;
|
|
142
|
+
}
|
|
143
|
+
// Full-codebase review: pre-collect the source inventory so
|
|
144
|
+
// buildReviewPlan can batch it into per-chunk reviewer tasks
|
|
145
|
+
// (coverage enforcement).
|
|
146
|
+
let scopeFiles;
|
|
147
|
+
if (config.review?.scope === 'full') {
|
|
148
|
+
scopeFiles = collectScopeFiles(projectRoot, { scope: 'full' });
|
|
149
|
+
}
|
|
150
|
+
// Thread image/visual attachments (screenshots/mockups/failure repros)
|
|
151
|
+
// into the plan so every reviewer prompt weighs them alongside code.
|
|
152
|
+
const attachments = Array.isArray(args.attachments)
|
|
153
|
+
? args.attachments.filter((a) => Boolean(a) &&
|
|
154
|
+
typeof a === 'object' &&
|
|
155
|
+
((typeof a.path === 'string' && a.path.length > 0) ||
|
|
156
|
+
(typeof a.data === 'string' && a.data.length > 0)))
|
|
150
157
|
: [];
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (
|
|
158
|
+
const plan = buildReviewPlan({ config, mode, maxReviewRounds, knownIntentional, changedFiles, scopeFiles, attachments });
|
|
159
|
+
return { operation: 'plan', mode, found: true, plan: plan };
|
|
160
|
+
}
|
|
161
|
+
if (args.operation === 'aggregate') {
|
|
162
|
+
const rawRounds = Array.isArray(args.rounds) ? args.rounds : [];
|
|
163
|
+
const rounds = rawRounds
|
|
164
|
+
.map((r) => {
|
|
165
|
+
const rr = r;
|
|
166
|
+
const findings = Array.isArray(rr?.findings) ? rr.findings : [];
|
|
167
|
+
const readFiles = Array.isArray(rr?.readFiles)
|
|
168
|
+
? rr.readFiles.filter((f) => typeof f === 'string')
|
|
169
|
+
: [];
|
|
170
|
+
return { round: typeof rr?.round === 'number' ? rr.round : 0, findings, readFiles };
|
|
171
|
+
})
|
|
172
|
+
.filter((r) => r.round > 0);
|
|
173
|
+
if (rounds.length === 0) {
|
|
174
|
+
return {
|
|
175
|
+
operation: 'aggregate',
|
|
176
|
+
mode,
|
|
177
|
+
error: 'rounds must be a non-empty array of {round, findings}.',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const maxReviewRounds = args.maxReviewRounds ?? config.max_rounds ?? DEFAULT_MAX_REVIEW_ROUNDS;
|
|
181
|
+
const goal = args.goal ?? config.goal ?? '';
|
|
182
|
+
const dimensions = config.dimensions ?? [];
|
|
183
|
+
// Output schema validation gate (reviewer.output_schema_validation,
|
|
184
|
+
// default true): validate every round's findings against the findings
|
|
185
|
+
// schema, then drop schema-invalid entries before the deterministic
|
|
186
|
+
// core so malformed reviewer output can never crash dedupe/sort or
|
|
187
|
+
// leak into fixes. The `schemaValidation` array is surfaced so the
|
|
188
|
+
// workflow can retry failing rounds (≤2 times) with a strict-JSON
|
|
189
|
+
// nudge. When disabled, non-object entries are still dropped for
|
|
190
|
+
// crash-safety.
|
|
191
|
+
const schemaEnabled = config.reviewer?.output_schema_validation !== false;
|
|
192
|
+
const schemaValidation = schemaEnabled ? validateRoundsSchema(rounds) : null;
|
|
193
|
+
const cleanRounds = sanitizeRounds(rounds, schemaValidation);
|
|
194
|
+
const report = buildReviewReport({
|
|
195
|
+
mode,
|
|
196
|
+
goal,
|
|
197
|
+
dimensions,
|
|
198
|
+
maxReviewRounds,
|
|
199
|
+
rounds: cleanRounds,
|
|
200
|
+
knownIntentional: args.knownIntentional,
|
|
201
|
+
fixedCount: typeof args.fixedCount === 'number' ? args.fixedCount : undefined,
|
|
202
|
+
});
|
|
155
203
|
return {
|
|
156
204
|
operation: 'aggregate',
|
|
157
205
|
mode,
|
|
158
|
-
|
|
206
|
+
report: report,
|
|
207
|
+
schemaValidation: (schemaValidation ?? null),
|
|
159
208
|
};
|
|
160
209
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
210
|
+
if (args.operation === 'meta-review') {
|
|
211
|
+
const source = args.report;
|
|
212
|
+
if (!source || typeof source !== 'object') {
|
|
213
|
+
return {
|
|
214
|
+
operation: 'meta-review',
|
|
215
|
+
mode,
|
|
216
|
+
error: 'report must be a ReviewReport JSON object (as returned by `aggregate`).',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const audit = metaReviewReport(source);
|
|
220
|
+
// Hard code-evidence gate (default on): every finding's file/line is
|
|
221
|
+
// validated against real files on disk before folding into the final
|
|
222
|
+
// verdict. Disable via config `reviewer.evidence_validation: false`.
|
|
223
|
+
const evidenceEnabled = config.reviewer?.evidence_validation !== false;
|
|
224
|
+
const findings = Array.isArray(source.findings) ? source.findings : [];
|
|
225
|
+
const evidence = evidenceEnabled ? verifyFindings(projectRoot, findings) : null;
|
|
226
|
+
// Prompt-informative coverage: compare the reviewer's self-reported
|
|
227
|
+
// reads against the assigned scope inventory (never flips the
|
|
228
|
+
// verdict). Disable via config `reviewer.coverage_validation: false`.
|
|
229
|
+
const coverageEnabled = config.reviewer?.coverage_validation !== false;
|
|
230
|
+
let coverage = null;
|
|
231
|
+
if (coverageEnabled) {
|
|
232
|
+
const assigned = collectScopeFiles(projectRoot, {
|
|
233
|
+
scope: config.review?.scope === 'changed-only' ? 'changed-only' : 'full',
|
|
234
|
+
});
|
|
235
|
+
const readFiles = Array.isArray(source.readFiles)
|
|
236
|
+
? source.readFiles
|
|
237
|
+
: null;
|
|
238
|
+
if (readFiles && readFiles.length > 0) {
|
|
239
|
+
coverage = computeCoverage(assigned, readFiles);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const finalReport = buildFinalReviewReport(source, { evidence, coverage });
|
|
194
243
|
return {
|
|
195
244
|
operation: 'meta-review',
|
|
196
245
|
mode,
|
|
197
|
-
|
|
246
|
+
found: true,
|
|
247
|
+
report: audit,
|
|
248
|
+
evidence: evidence ? evidenceToPlain(evidence) : null,
|
|
249
|
+
coverage: coverage ? coverageToDict(coverage) : null,
|
|
250
|
+
finalReport: finalReport,
|
|
198
251
|
};
|
|
199
252
|
}
|
|
200
|
-
const audit = metaReviewReport(source);
|
|
201
|
-
// Hard code-evidence gate (default on): every finding's file/line is
|
|
202
|
-
// validated against real files on disk before folding into the final
|
|
203
|
-
// verdict. Disable via config `reviewer.evidence_validation: false`.
|
|
204
|
-
const evidenceEnabled = config.reviewer?.evidence_validation !== false;
|
|
205
|
-
const findings = Array.isArray(source.findings) ? source.findings : [];
|
|
206
|
-
const evidence = evidenceEnabled ? verifyFindings(projectRoot, findings) : null;
|
|
207
|
-
// Prompt-informative coverage: compare the reviewer's self-reported
|
|
208
|
-
// reads against the assigned scope inventory (never flips the
|
|
209
|
-
// verdict). Disable via config `reviewer.coverage_validation: false`.
|
|
210
|
-
const coverageEnabled = config.reviewer?.coverage_validation !== false;
|
|
211
|
-
let coverage = null;
|
|
212
|
-
if (coverageEnabled) {
|
|
213
|
-
const assigned = collectScopeFiles(projectRoot, {
|
|
214
|
-
scope: config.review?.scope === 'changed-only' ? 'changed-only' : 'full',
|
|
215
|
-
});
|
|
216
|
-
const readFiles = Array.isArray(source.readFiles)
|
|
217
|
-
? source.readFiles
|
|
218
|
-
: null;
|
|
219
|
-
if (readFiles && readFiles.length > 0) {
|
|
220
|
-
coverage = computeCoverage(assigned, readFiles);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const finalReport = buildFinalReviewReport(source, { evidence, coverage });
|
|
224
253
|
return {
|
|
225
|
-
operation:
|
|
226
|
-
|
|
227
|
-
found: true,
|
|
228
|
-
report: audit,
|
|
229
|
-
evidence: evidence ? evidenceToPlain(evidence) : null,
|
|
230
|
-
coverage: coverage ? coverageToDict(coverage) : null,
|
|
231
|
-
finalReport: finalReport,
|
|
254
|
+
operation: args.operation,
|
|
255
|
+
error: `Unknown operation "${args.operation}". Use "plan", "aggregate", or "meta-review".`,
|
|
232
256
|
};
|
|
233
|
-
}
|
|
234
|
-
return
|
|
235
|
-
operation: args.operation,
|
|
236
|
-
error: `Unknown operation "${args.operation}". Use "plan", "aggregate", or "meta-review".`,
|
|
237
|
-
};
|
|
257
|
+
});
|
|
258
|
+
return result;
|
|
238
259
|
},
|
|
239
260
|
}));
|
|
240
261
|
}
|