iterate-plugin 2.8.0 → 2.8.2
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/dist/config-loader.js +6 -1
- package/dist/evidence.js +21 -3
- package/dist/git-scope.js +101 -0
- package/dist/meta-review.js +50 -3
- package/dist/method-scope.js +173 -0
- package/dist/review-scope.js +187 -0
- package/dist/review.js +216 -16
- package/dist/skill-prompt.js +65 -27
- package/dist/tools/fix.js +12 -2
- package/dist/tools/review.js +68 -5
- package/lib/client.js +3 -25
- package/package.json +1 -1
- package/src/client/index.ts +18 -14
- package/src/config-loader.ts +6 -1
- package/src/evidence.ts +22 -3
- package/src/git-scope.ts +129 -0
- package/src/meta-review.ts +65 -5
- package/src/method-scope.ts +201 -0
- package/src/review-scope.ts +203 -0
- package/src/review.ts +301 -16
- package/src/skill-prompt.ts +65 -27
- package/src/tools/fix.ts +13 -2
- package/src/tools/review.ts +82 -5
- package/src/types.ts +6 -1
package/src/skill-prompt.ts
CHANGED
|
@@ -14,7 +14,7 @@ You have the iterate plugin installed, which registers these tools:
|
|
|
14
14
|
- \`iterate_validate\` — run a whitelisted validation command
|
|
15
15
|
- \`iterate_decision_log\` — append to the decision log, or read entries back for review
|
|
16
16
|
- \`iterate_context\` — read SKILL.md / ITERATE.md project context
|
|
17
|
-
- \`iterate_review\` — deterministic review engine: \`plan\` builds the review plan; \`aggregate\` dedupes/merges findings and computes convergence; \`meta-review\` audits a built report for internal consistency (counts, buckets, sorting, convergence math) and returns a final report with an \`approved\` / \`needs_revision\` verdict. Purely computational.
|
|
17
|
+
- \`iterate_review\` — deterministic review engine: \`plan\` builds the review plan (for \`review.scope: changed-only\`, it resolves the git-diff file set against \`git.target_branch\` and auto-falls back to \`full\` when nothing changed); \`aggregate\` dedupes/merges findings, validates every finding against the findings schema when \`reviewer.output_schema_validation\` is on (dropping invalid entries and reporting them via \`schemaValidation\`), and computes convergence; \`meta-review\` audits a built report for internal consistency (counts, buckets, sorting, convergence math) and returns a final report with an \`approved\` / \`needs_revision\` verdict. Purely computational.
|
|
18
18
|
- \`iterate_triage\` — manage "known_intentional" entries in the config (list / apply, with dedupe + backup + rollback)
|
|
19
19
|
- \`iterate_fix\` — apply ONE atomic fix: backs up the file, enforces the atomic max_lines threshold, writes the new content, and records the fix (id + diff summary) in \`.iterate/fixes/registry.json\`
|
|
20
20
|
- \`iterate_diff\` — show the accumulated diff for a fixed file (vs its original backup) or a per-file summary of all fixes
|
|
@@ -62,18 +62,36 @@ const rounds = [] // raw per-round findings
|
|
|
62
62
|
phase('review')
|
|
63
63
|
for (let r = 1; r <= maxRounds; r++) {
|
|
64
64
|
log('round ' + r + ' of ' + maxRounds + ' — finding NEW issues only')
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
let agg = null
|
|
66
|
+
let schemaInvalid = false
|
|
67
|
+
let retries = 0
|
|
68
|
+
do {
|
|
69
|
+
// Schema validation retry: on the 2nd+ pass, nudge reviewers toward strict JSON.
|
|
70
|
+
const nudge = retries > 0
|
|
71
|
+
? '\\nSTRICT JSON REQUIRED: your previous output failed schema validation. Return ONLY a JSON object {"findings":[...]} where EVERY finding has dimension, file, line (non-negative integer; 0 = whole-file), severity (critical|high|medium|low), summary, failure_scenario, suggested_fix, is_atomic (boolean).'
|
|
72
|
+
: ''
|
|
73
|
+
const raw = await parallel(dims.map(dim => () => agent(
|
|
74
|
+
'Review dimension "' + dim + '". Already-known findings (do NOT re-report): ' +
|
|
75
|
+
JSON.stringify(known) + nudge + '\\nReturn the findings JSON object.',
|
|
76
|
+
{ label: 'review:' + dim + ':r' + r, schema: plan.dimensions.find(x => x.id === dim).findingsSchema }
|
|
77
|
+
)))
|
|
78
|
+
const thisRound = { round: r, findings: [].concat(...raw.map(x => x && x.findings ? x.findings : [])) }
|
|
79
|
+
if (rounds.length >= r) rounds[r - 1] = thisRound; else rounds.push(thisRound)
|
|
80
|
+
// Deterministic aggregate: cross-round dedupe + known_intentional filter + severity sort.
|
|
81
|
+
agg = await agent(
|
|
82
|
+
'Call iterate_review({operation:"aggregate", mode:"dry-run", rounds:' + JSON.stringify(rounds) + ', maxReviewRounds:' + maxRounds + ', knownIntentional:' + JSON.stringify(knownIntentional) + '}) and return the report JSON.',
|
|
83
|
+
{ label: 'review:aggregate:r' + r }
|
|
84
|
+
)
|
|
85
|
+
// reviewer.output_schema_validation (default on): aggregate returns per-round
|
|
86
|
+
// schemaValidation; retry the just-finished round (≤2 times) when invalid.
|
|
87
|
+
schemaInvalid = agg && agg.schemaValidation && agg.schemaValidation.length > 0
|
|
88
|
+
? agg.schemaValidation[agg.schemaValidation.length - 1].valid === false
|
|
89
|
+
: false
|
|
90
|
+
if (schemaInvalid && retries < 2) {
|
|
91
|
+
retries += 1
|
|
92
|
+
log('retry ' + retries + ': round ' + r + ' output failed schema validation — re-running reviewers with strict-JSON emphasis')
|
|
93
|
+
}
|
|
94
|
+
} while (schemaInvalid && retries <= 2)
|
|
77
95
|
// Feed the DEDUPED + already-filtered set back (not raw findings) so the known
|
|
78
96
|
// list stays bounded and reviewers never see the same issue twice.
|
|
79
97
|
if (agg && agg.report && Array.isArray(agg.report.findings)) known = agg.report.findings
|
|
@@ -124,6 +142,7 @@ Key rules for dry-run:
|
|
|
124
142
|
- **NEVER call a fixer / never edit files / never create branches or worktree.** Reviewers read only.
|
|
125
143
|
- **Every reviewer MUST actually read each file it reports on (read_file) BEFORE judging it, and anchor every finding to a real location. Fabricated file paths or invented line numbers are poisoned evidence and fail the run.** Subagents never report on code they didn't inspect.
|
|
126
144
|
- Each round feeds the already-known findings to reviewers so they hunt NEW issues only → that is what drives convergence.
|
|
145
|
+
- **Schema validation & retry**: when \`reviewer.output_schema_validation\` is on (default), \`aggregate\` validates every finding against the findings schema and returns \`schemaValidation\` (per-round {round, valid, issues}). If the just-finished round is invalid, retry its reviewers up to 2 times with the strict-JSON nudge (see the loop above), then re-aggregate. Schema-invalid findings are dropped by \`aggregate\` and must NEVER be fed back as known findings or reported as converged.
|
|
127
146
|
- Stop when a round reports 0 new findings (converged) or maxReviewRounds is reached.
|
|
128
147
|
- The report (with per-round convergence stats + suggested fix priorities) is the deliverable.
|
|
129
148
|
- **Meta-review**: after building the report, audit it with \`iterate_review({operation:"meta-review"})\` for internal consistency (counts, severity buckets, dimension sums, sort order, convergence math). The meta-review ALSO runs the hard code-evidence gate (default on): every finding's file/line is validated against real files on disk, so any fabricated location surfaces as a critical \`EVIDENCE_VIOLATION\` and flips the verdict to \`needs_revision\`. The \`finalReport.verdict\` is \`approved\` only when the report passes every check AND every finding anchors to real, read code; otherwise \`needs_revision\`. Surface the final report and its verdict as the closing deliverable.
|
|
@@ -170,21 +189,39 @@ let failedCommands = []
|
|
|
170
189
|
phase('loop')
|
|
171
190
|
for (let r = startRound; r <= maxRounds; r++) {
|
|
172
191
|
log('round ' + r + ' of ' + maxRounds + ' — review current state, fix atomics via iterate_fix, validate')
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
192
|
+
let agg = null
|
|
193
|
+
let schemaInvalid = false
|
|
194
|
+
let retries = 0
|
|
195
|
+
do {
|
|
196
|
+
// Schema validation retry: on the 2nd+ pass, nudge reviewers toward strict JSON.
|
|
197
|
+
const nudge = retries > 0
|
|
198
|
+
? '\\nSTRICT JSON REQUIRED: your previous output failed schema validation. Return ONLY a JSON object {"findings":[...]} where EVERY finding has dimension, file, line (non-negative integer; 0 = whole-file), severity (critical|high|medium|low), summary, failure_scenario, suggested_fix, is_atomic (boolean).'
|
|
199
|
+
: ''
|
|
200
|
+
const raw = await parallel(dims.map(dim => () => agent(
|
|
201
|
+
'Review dimension "' + dim + '" on the CURRENT code state (previous atomic findings are fixed). ' +
|
|
202
|
+
'Do NOT re-report already-known architectural findings: ' + JSON.stringify(architectural) + nudge + '\\nReturn the findings JSON object.',
|
|
203
|
+
{ label: 'review:' + dim + ':r' + r, schema: plan.dimensions.find(x => x.id === dim).findingsSchema }
|
|
204
|
+
)))
|
|
205
|
+
const thisRound = { round: r, findings: [].concat(...raw.map(x => x && x.findings ? x.findings : [])) }
|
|
206
|
+
if (rounds.length >= r) rounds[r - 1] = thisRound; else rounds.push(thisRound)
|
|
180
207
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
208
|
+
// Deterministic dedupe / known_intentional filter / severity sort for this round.
|
|
209
|
+
// \`fixedCount\` is threaded into the report summary so the client dashboard can
|
|
210
|
+
// show a running "fixes applied" metric for normal mode.
|
|
211
|
+
agg = await agent(
|
|
212
|
+
'Call iterate_review({operation:"aggregate", mode:"normal", rounds:' + JSON.stringify([thisRound]) + ', knownIntentional:' + JSON.stringify(knownIntentional) + ', fixedCount:' + fixedCount + '}) and return the report JSON.',
|
|
213
|
+
{ label: 'review:aggregate:r' + r }
|
|
214
|
+
)
|
|
215
|
+
// reviewer.output_schema_validation (default on): aggregate returns per-round
|
|
216
|
+
// schemaValidation; retry the just-finished round (≤2 times) when invalid.
|
|
217
|
+
schemaInvalid = agg && agg.schemaValidation && agg.schemaValidation.length > 0
|
|
218
|
+
? agg.schemaValidation[agg.schemaValidation.length - 1].valid === false
|
|
219
|
+
: false
|
|
220
|
+
if (schemaInvalid && retries < 2) {
|
|
221
|
+
retries += 1
|
|
222
|
+
log('retry ' + retries + ': round ' + r + ' output failed schema validation — re-running reviewers with strict-JSON emphasis')
|
|
223
|
+
}
|
|
224
|
+
} while (schemaInvalid && retries <= 2)
|
|
188
225
|
const findings = (agg && agg.report && agg.report.findings) ? agg.report.findings : thisRound.findings
|
|
189
226
|
const atomic = findings.filter(f => f.is_atomic === true)
|
|
190
227
|
const remaining = findings.filter(f => f.is_atomic !== true)
|
|
@@ -321,6 +358,7 @@ return {
|
|
|
321
358
|
Key rules for normal mode:
|
|
322
359
|
- Fixers are the ONLY agents allowed to write files, and they must go through \`iterate_fix\` — never edit files directly. That is what gives every change a backup, a diff, and a rollback path. Reviewers read only. Architectural findings are reported, never auto-fixed.
|
|
323
360
|
- Aggregate the current round deterministically (\`report.findings\`) before fixing, so fixes act on deduped/filtered/sorted findings.
|
|
361
|
+
- **Schema validation & retry**: when \`reviewer.output_schema_validation\` is on (default), retry the round's reviewers up to 2 times when \`aggregate\` reports \`schemaValidation\` valid=false for it, then re-aggregate. Never forward schema-invalid findings into \`iterate_fix\`.
|
|
324
362
|
- Apply atomic fixes **per file**: one fixer agent handles all findings for a given file serially (so the same file is never edited concurrently); different files are fixed in parallel.
|
|
325
363
|
- **Resume**: load the checkpoint first; if a previous run left one, continue from \`checkpoint.round + 1\` (its \`fixedCount\` and deduped \`findings\` are carried forward).
|
|
326
364
|
- **Validate after every round** of fixes; on ANY validation failure, roll back the round's fixes via \`iterate_rollback\` and stop (the checkpoint is left in place so the run can be resumed).
|
package/src/tools/fix.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { join } from 'node:path'
|
|
|
22
22
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
23
23
|
import type { JsonValue } from '@deepseek-ai/dsh-session'
|
|
24
24
|
import { loadEffectiveConfig, resolveProjectRoot } from '../config-loader.ts'
|
|
25
|
+
import { countTouchedMethods } from '../method-scope.ts'
|
|
25
26
|
import { fixBackupPath, fixRegistryPath, fixesDir } from '../paths.ts'
|
|
26
27
|
import { appendDecisionEntry } from './decision-log.ts'
|
|
27
28
|
import type { FileDiffHunk, FixRecord, FixRegistry, ReviewFinding } from '../types.ts'
|
|
@@ -228,7 +229,7 @@ export function registerFixTool(ctx: { tools: { register: (def: ReturnType<typeo
|
|
|
228
229
|
description:
|
|
229
230
|
'Apply ONE atomic fix to a file. Pass the target relative `file`, the finding that motivated ' +
|
|
230
231
|
'the fix, the NEW full `content` of that file (after your edit), and the current `round`. ' +
|
|
231
|
-
'The tool backs up the original, enforces the atomic `max_lines`
|
|
232
|
+
'The tool backs up the original, enforces the atomic `max_lines` and `max_adjacent_methods` thresholds (unless `force`), ' +
|
|
232
233
|
'writes the new content, and records the fix for later diff/rollback. ' +
|
|
233
234
|
'This is the ONLY sanctioned way to apply fixes in normal mode.',
|
|
234
235
|
parameters: {
|
|
@@ -289,6 +290,7 @@ export function registerFixTool(ctx: { tools: { register: (def: ReturnType<typeo
|
|
|
289
290
|
const projectRoot = resolved.root
|
|
290
291
|
const { config } = loadEffectiveConfig(projectRoot)
|
|
291
292
|
const maxLines = config.atomic?.max_lines ?? 20
|
|
293
|
+
const maxAdjacentMethods = config.atomic?.max_adjacent_methods ?? 3
|
|
292
294
|
|
|
293
295
|
const file = typeof args.file === 'string' ? args.file : ''
|
|
294
296
|
if (!file) return { ok: false, error: 'file is required' }
|
|
@@ -316,6 +318,7 @@ export function registerFixTool(ctx: { tools: { register: (def: ReturnType<typeo
|
|
|
316
318
|
const current = readProjectFile(projectRoot, file)
|
|
317
319
|
if (!current.ok) return { ok: false, error: current.reason }
|
|
318
320
|
|
|
321
|
+
const hunks = diffLines(current.content, args.content)
|
|
319
322
|
const { added, removed } = countChangedLines(current.content, args.content)
|
|
320
323
|
if (!args.force && (added > maxLines || removed > maxLines)) {
|
|
321
324
|
return {
|
|
@@ -325,6 +328,15 @@ export function registerFixTool(ctx: { tools: { register: (def: ReturnType<typeo
|
|
|
325
328
|
}
|
|
326
329
|
}
|
|
327
330
|
|
|
331
|
+
const touchedMethods = countTouchedMethods(current.content, args.content, hunks)
|
|
332
|
+
if (!args.force && touchedMethods > maxAdjacentMethods) {
|
|
333
|
+
return {
|
|
334
|
+
ok: false,
|
|
335
|
+
error: `Change to ${file} touches ${touchedMethods} adjacent method(s), exceeds atomic.max_adjacent_methods (${maxAdjacentMethods}). ` +
|
|
336
|
+
'Split it into smaller atomic fixes or pass force:true if this is a deliberate multi-method change.',
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
328
340
|
const id = fixId(finding)
|
|
329
341
|
const registry = readRegistry(projectRoot)
|
|
330
342
|
if (findFixRecord(registry, id)) {
|
|
@@ -349,7 +361,6 @@ export function registerFixTool(ctx: { tools: { register: (def: ReturnType<typeo
|
|
|
349
361
|
return { ok: false, error: `failed to write file: ${String(err)}` }
|
|
350
362
|
}
|
|
351
363
|
|
|
352
|
-
const hunks = diffLines(current.content, args.content)
|
|
353
364
|
const record: FixRecord = {
|
|
354
365
|
id,
|
|
355
366
|
timestamp,
|
package/src/tools/review.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
2
2
|
import type { JsonValue } from '@deepseek-ai/dsh-session'
|
|
3
3
|
import { loadEffectiveConfig, resolveProjectRoot } from '../config-loader.ts'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildReviewPlan,
|
|
6
|
+
buildReviewReport,
|
|
7
|
+
sanitizeRounds,
|
|
8
|
+
validateRoundsSchema,
|
|
9
|
+
} from '../review.ts'
|
|
5
10
|
import { buildFinalReviewReport, metaReviewReport } from '../meta-review.ts'
|
|
6
11
|
import { evidenceToPlain, verifyFindings } from '../evidence.ts'
|
|
12
|
+
import {
|
|
13
|
+
collectScopeFiles,
|
|
14
|
+
computeCoverage,
|
|
15
|
+
coverageToDict,
|
|
16
|
+
} from '../review-scope.ts'
|
|
17
|
+
import { resolveChangedFiles } from '../git-scope.ts'
|
|
7
18
|
import type { KnownIntentional, ReviewFinding, ReviewReport, ReviewRound } from '../types.ts'
|
|
19
|
+
import type { CoverageResult } from '../review-scope.ts'
|
|
8
20
|
|
|
9
21
|
/** Default round cap when neither the arg nor config provides one. */
|
|
10
22
|
const DEFAULT_MAX_REVIEW_ROUNDS = 3
|
|
@@ -95,7 +107,21 @@ export function registerReviewTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
95
107
|
found: { type: 'boolean' },
|
|
96
108
|
plan: { type: 'json' },
|
|
97
109
|
report: { type: 'json' },
|
|
110
|
+
schemaValidation: {
|
|
111
|
+
type: 'json',
|
|
112
|
+
description:
|
|
113
|
+
'For `aggregate`: per-round schema validation results (round, valid, issues). ' +
|
|
114
|
+
'Present only when reviewer.output_schema_validation is enabled; the workflow ' +
|
|
115
|
+
'retries rounds with valid=false (≤2 times) before forwarding findings.',
|
|
116
|
+
},
|
|
98
117
|
evidence: { type: 'json' },
|
|
118
|
+
coverage: {
|
|
119
|
+
type: 'json',
|
|
120
|
+
description:
|
|
121
|
+
'For `meta-review`: prompt-informative scope coverage result ' +
|
|
122
|
+
'(assigned vs self-reported reads). Present only when ' +
|
|
123
|
+
'reviewer.coverage_validation is enabled and readFiles were supplied.',
|
|
124
|
+
},
|
|
99
125
|
finalReport: { type: 'json' },
|
|
100
126
|
error: { type: 'string' },
|
|
101
127
|
},
|
|
@@ -120,7 +146,23 @@ export function registerReviewTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
120
146
|
const maxReviewRounds = args.maxReviewRounds ?? config.max_rounds ?? DEFAULT_MAX_REVIEW_ROUNDS
|
|
121
147
|
const knownIntentional = (config.personalization as { known_intentional?: KnownIntentional[] } | undefined)
|
|
122
148
|
?.known_intentional
|
|
123
|
-
|
|
149
|
+
// changed-only scope: resolve the changed-file set against
|
|
150
|
+
// git.target_branch before building the plan so reviewers get the
|
|
151
|
+
// concrete file list (and the plan auto-falls back to full when there
|
|
152
|
+
// are no changes). git failures degrade to a full-scope plan.
|
|
153
|
+
let changedFiles: string[] | undefined
|
|
154
|
+
if (config.review?.scope === 'changed-only') {
|
|
155
|
+
const gitScope = await resolveChangedFiles(projectRoot, config.git?.target_branch ?? 'main')
|
|
156
|
+
changedFiles = gitScope.changedFiles
|
|
157
|
+
}
|
|
158
|
+
// Full-codebase review: pre-collect the source inventory so
|
|
159
|
+
// buildReviewPlan can batch it into per-chunk reviewer tasks
|
|
160
|
+
// (coverage enforcement).
|
|
161
|
+
let scopeFiles: string[] | undefined
|
|
162
|
+
if (config.review?.scope === 'full') {
|
|
163
|
+
scopeFiles = collectScopeFiles(projectRoot, { scope: 'full' })
|
|
164
|
+
}
|
|
165
|
+
const plan = buildReviewPlan({ config, mode, maxReviewRounds, knownIntentional, changedFiles, scopeFiles })
|
|
124
166
|
return { operation: 'plan', mode, found: true, plan: plan as unknown as JsonValue }
|
|
125
167
|
}
|
|
126
168
|
|
|
@@ -145,16 +187,34 @@ export function registerReviewTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
145
187
|
const maxReviewRounds = args.maxReviewRounds ?? config.max_rounds ?? DEFAULT_MAX_REVIEW_ROUNDS
|
|
146
188
|
const goal = args.goal ?? config.goal ?? ''
|
|
147
189
|
const dimensions = config.dimensions ?? []
|
|
190
|
+
|
|
191
|
+
// Output schema validation gate (reviewer.output_schema_validation,
|
|
192
|
+
// default true): validate every round's findings against the findings
|
|
193
|
+
// schema, then drop schema-invalid entries before the deterministic
|
|
194
|
+
// core so malformed reviewer output can never crash dedupe/sort or
|
|
195
|
+
// leak into fixes. The `schemaValidation` array is surfaced so the
|
|
196
|
+
// workflow can retry failing rounds (≤2 times) with a strict-JSON
|
|
197
|
+
// nudge. When disabled, non-object entries are still dropped for
|
|
198
|
+
// crash-safety.
|
|
199
|
+
const schemaEnabled = config.reviewer?.output_schema_validation !== false
|
|
200
|
+
const schemaValidation = schemaEnabled ? validateRoundsSchema(rounds) : null
|
|
201
|
+
const cleanRounds = sanitizeRounds(rounds, schemaValidation)
|
|
202
|
+
|
|
148
203
|
const report = buildReviewReport({
|
|
149
204
|
mode,
|
|
150
205
|
goal,
|
|
151
206
|
dimensions,
|
|
152
207
|
maxReviewRounds,
|
|
153
|
-
rounds,
|
|
208
|
+
rounds: cleanRounds,
|
|
154
209
|
knownIntentional: args.knownIntentional as KnownIntentional[] | undefined,
|
|
155
210
|
fixedCount: typeof args.fixedCount === 'number' ? args.fixedCount : undefined,
|
|
156
211
|
})
|
|
157
|
-
return {
|
|
212
|
+
return {
|
|
213
|
+
operation: 'aggregate',
|
|
214
|
+
mode,
|
|
215
|
+
report: report as unknown as JsonValue,
|
|
216
|
+
schemaValidation: schemaValidation as unknown as JsonValue | undefined,
|
|
217
|
+
}
|
|
158
218
|
}
|
|
159
219
|
|
|
160
220
|
if (args.operation === 'meta-review') {
|
|
@@ -173,13 +233,30 @@ export function registerReviewTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
173
233
|
const evidenceEnabled = config.reviewer?.evidence_validation !== false
|
|
174
234
|
const findings: ReviewFinding[] = Array.isArray(source.findings) ? source.findings : []
|
|
175
235
|
const evidence = evidenceEnabled ? verifyFindings(projectRoot, findings) : null
|
|
176
|
-
|
|
236
|
+
// Prompt-informative coverage: compare the reviewer's self-reported
|
|
237
|
+
// reads against the assigned scope inventory (never flips the
|
|
238
|
+
// verdict). Disable via config `reviewer.coverage_validation: false`.
|
|
239
|
+
const coverageEnabled = config.reviewer?.coverage_validation !== false
|
|
240
|
+
let coverage: CoverageResult | null = null
|
|
241
|
+
if (coverageEnabled) {
|
|
242
|
+
const assigned = collectScopeFiles(projectRoot, {
|
|
243
|
+
scope: config.review?.scope === 'changed-only' ? 'changed-only' : 'full',
|
|
244
|
+
})
|
|
245
|
+
const readFiles = Array.isArray((source as unknown as { readFiles?: unknown }).readFiles)
|
|
246
|
+
? ((source as unknown as { readFiles?: unknown }).readFiles as string[])
|
|
247
|
+
: null
|
|
248
|
+
if (readFiles && readFiles.length > 0) {
|
|
249
|
+
coverage = computeCoverage(assigned, readFiles)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const finalReport = buildFinalReviewReport(source, { evidence, coverage })
|
|
177
253
|
return {
|
|
178
254
|
operation: 'meta-review',
|
|
179
255
|
mode,
|
|
180
256
|
found: true,
|
|
181
257
|
report: audit as unknown as JsonValue,
|
|
182
258
|
evidence: evidence ? (evidenceToPlain(evidence) as unknown as JsonValue) : null,
|
|
259
|
+
coverage: coverage ? (coverageToDict(coverage) as unknown as JsonValue) : null,
|
|
183
260
|
finalReport: finalReport as unknown as JsonValue,
|
|
184
261
|
}
|
|
185
262
|
}
|
package/src/types.ts
CHANGED
|
@@ -16,7 +16,12 @@ export interface IterateConfig {
|
|
|
16
16
|
command_whitelist: string[]
|
|
17
17
|
commands: Record<string, string[]>
|
|
18
18
|
}
|
|
19
|
-
reviewer: {
|
|
19
|
+
reviewer: {
|
|
20
|
+
output_schema_validation: boolean
|
|
21
|
+
evidence_validation: boolean
|
|
22
|
+
coverage_validation: boolean
|
|
23
|
+
scope_chunk_size: number
|
|
24
|
+
}
|
|
20
25
|
onboarding?: Record<string, unknown>
|
|
21
26
|
personalization?: Record<string, unknown>
|
|
22
27
|
}
|