iterate-plugin 3.5.5 → 3.5.7
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 +22 -2
- package/dist/config-write.js +28 -0
- package/dist/evidence.js +5 -1
- package/dist/meta-review.js +5 -1
- package/dist/review.js +27 -9
- package/dist/skill-prompt.js +86 -27
- package/dist/tools/checkpoint.js +11 -4
- package/dist/tools/decision-log.js +11 -0
- package/dist/tools/history.js +1 -1
- package/dist/tools/prune.js +29 -6
- package/dist/tools/transcript.js +17 -6
- package/dist/transcript.js +78 -2
- package/lib/client.js +63 -31
- package/lib/parse.js +28 -0
- package/package.json +12 -8
- package/src/client/index.ts +45 -24
- package/src/config-loader.ts +20 -2
- package/src/config-write.ts +32 -0
- package/src/evidence.ts +5 -1
- package/src/meta-review.ts +5 -1
- package/src/review.ts +40 -8
- package/src/skill-prompt.ts +86 -27
- package/src/tools/checkpoint.ts +12 -4
- package/src/tools/decision-log.ts +12 -0
- package/src/tools/history.ts +1 -1
- package/src/tools/prune.ts +28 -6
- package/src/tools/transcript.ts +16 -5
- package/src/transcript.ts +85 -2
- package/src/types.ts +9 -4
package/src/transcript.ts
CHANGED
|
@@ -98,7 +98,10 @@ function normalizeFinding(input: unknown): TranscriptFinding | null {
|
|
|
98
98
|
sev === 'critical' || sev === 'high' || sev === 'medium' || sev === 'low'
|
|
99
99
|
? sev
|
|
100
100
|
: 'low'
|
|
101
|
-
|
|
101
|
+
// Only a non-negative integer is a real line anchor; a fractional/malformed
|
|
102
|
+
// value (e.g. 3.7 from a bypassed schema) must not leak into the manifest.
|
|
103
|
+
const line =
|
|
104
|
+
typeof f.line === 'number' && Number.isInteger(f.line) && f.line >= 0 ? f.line : 0
|
|
102
105
|
return {
|
|
103
106
|
dimension,
|
|
104
107
|
file,
|
|
@@ -267,7 +270,8 @@ export class ReviewTranscriptBuilder {
|
|
|
267
270
|
const att = typeof attempt === 'number' && Number.isFinite(attempt) ? Math.floor(attempt) : 1
|
|
268
271
|
this.roundStart(this.round)
|
|
269
272
|
const live = this.rounds[this.round - 1]! as LiveRound | undefined
|
|
270
|
-
if (live
|
|
273
|
+
if (!live) return
|
|
274
|
+
if (this.threadCount(live) < MAX_THREADS_PER_ROUND) {
|
|
271
275
|
live.threads.push({
|
|
272
276
|
dimension: dim || 'review',
|
|
273
277
|
attempt: att > 0 ? att : 1,
|
|
@@ -275,7 +279,86 @@ export class ReviewTranscriptBuilder {
|
|
|
275
279
|
readFiles: [],
|
|
276
280
|
findings: [],
|
|
277
281
|
})
|
|
282
|
+
} else {
|
|
283
|
+
// Thread cap reached: route the extra dimension/retry into ONE shared
|
|
284
|
+
// "other" overflow thread instead of silently dropping it. A dropped
|
|
285
|
+
// reviewerStart used to leave `currentThread()` pointing at the PREVIOUS
|
|
286
|
+
// dimension's thread, so its findings were mis-attributed to that
|
|
287
|
+
// dimension. The overflow thread stays bounded (at most ONE extra thread
|
|
288
|
+
// per round) while keeping over-cap findings attributable to "other".
|
|
289
|
+
let overflow = live.threads.find((t) => t.dimension === 'other')
|
|
290
|
+
if (!overflow) {
|
|
291
|
+
overflow = {
|
|
292
|
+
dimension: 'other',
|
|
293
|
+
attempt: att > 0 ? att : 1,
|
|
294
|
+
messages: [],
|
|
295
|
+
readFiles: [],
|
|
296
|
+
findings: [],
|
|
297
|
+
}
|
|
298
|
+
live.threads.push(overflow)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
this.touch()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Restore ONE persisted thread during rehydration, preserving the message
|
|
306
|
+
* ARRAY boundaries (a rehydrated manifest is re-serialized on the next nudge
|
|
307
|
+
* edit, so collapsed messages would silently rewrite the evidence) and the
|
|
308
|
+
* thread's own dimension (never merging it into a previous dimension's
|
|
309
|
+
* thread). Bypasses the live per-round cap — the manifest already enforced
|
|
310
|
+
* it at capture time — but every restored field is hard-bounded so a hostile
|
|
311
|
+
* hand-edited manifest can never blow up memory.
|
|
312
|
+
*/
|
|
313
|
+
restoreThread(
|
|
314
|
+
round: number,
|
|
315
|
+
thread: {
|
|
316
|
+
dimension?: unknown
|
|
317
|
+
attempt?: unknown
|
|
318
|
+
messages?: unknown
|
|
319
|
+
readFiles?: unknown
|
|
320
|
+
findings?: unknown
|
|
321
|
+
},
|
|
322
|
+
): void {
|
|
323
|
+
const r =
|
|
324
|
+
typeof round === 'number' && Number.isFinite(round)
|
|
325
|
+
? Math.min(Math.floor(round), MAX_ROUNDS)
|
|
326
|
+
: 1
|
|
327
|
+
this.roundStart(r)
|
|
328
|
+
const live = this.rounds[this.round - 1]
|
|
329
|
+
if (!live) return
|
|
330
|
+
if (!thread || typeof thread !== 'object') return
|
|
331
|
+
// Hostile-manifest guard: keep the live hard bound (with headroom for the
|
|
332
|
+
// single overflow thread) and newest-first truncate beyond it.
|
|
333
|
+
if (live.threads.length >= MAX_THREADS_PER_ROUND * 2 + 1) return
|
|
334
|
+
const dim =
|
|
335
|
+
typeof thread.dimension === 'string' && thread.dimension.trim()
|
|
336
|
+
? thread.dimension.trim()
|
|
337
|
+
: 'review'
|
|
338
|
+
const att =
|
|
339
|
+
typeof thread.attempt === 'number' && Number.isFinite(thread.attempt)
|
|
340
|
+
? Math.floor(thread.attempt)
|
|
341
|
+
: 1
|
|
342
|
+
const t: LiveThread = {
|
|
343
|
+
dimension: dim,
|
|
344
|
+
attempt: att > 0 ? att : 1,
|
|
345
|
+
messages: [],
|
|
346
|
+
readFiles: [],
|
|
347
|
+
findings: [],
|
|
348
|
+
}
|
|
349
|
+
const messages = Array.isArray(thread.messages) ? thread.messages : []
|
|
350
|
+
for (const m of messages.slice(0, MAX_MESSAGES_PER_THREAD)) {
|
|
351
|
+
if (typeof m === 'string' && m.trim()) t.messages.push(m)
|
|
352
|
+
}
|
|
353
|
+
if (Array.isArray(thread.readFiles)) {
|
|
354
|
+
t.readFiles.push(...dedupePaths(thread.readFiles as string[]))
|
|
355
|
+
}
|
|
356
|
+
if (Array.isArray(thread.findings)) {
|
|
357
|
+
for (const f of thread.findings) {
|
|
358
|
+
if (normalizeFinding(f)) pushThreadFinding(t, f)
|
|
359
|
+
}
|
|
278
360
|
}
|
|
361
|
+
live.threads.push(t)
|
|
279
362
|
this.touch()
|
|
280
363
|
}
|
|
281
364
|
|
package/src/types.ts
CHANGED
|
@@ -18,9 +18,12 @@ export interface IterateConfig {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* LLM reasoning effort for review passes ('low' | 'medium' | 'high').
|
|
21
|
-
* Absent → follow the provider default.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Absent → follow the provider default. Accepted by `iterate_config` write
|
|
22
|
+
* validation (config-write), exposed in the settings guide, and carried on
|
|
23
|
+
* the review plan (`buildReviewPlan.reasoningEffort`, plus an effort
|
|
24
|
+
* directive injected into every reviewer prompt) so the orchestrating
|
|
25
|
+
* harness can honor it per reviewer subagent. The plugin itself never reads
|
|
26
|
+
* or modifies the provider request body.
|
|
24
27
|
*/
|
|
25
28
|
reasoning_effort?: 'low' | 'medium' | 'high'
|
|
26
29
|
reviewer: {
|
|
@@ -226,7 +229,9 @@ export interface IterationStatus {
|
|
|
226
229
|
findingsCount: number
|
|
227
230
|
totalDecisionLogEntries: number
|
|
228
231
|
hasCheckpoint: boolean
|
|
229
|
-
/** True when a checkpoint is present
|
|
232
|
+
/** True when a checkpoint is present AND no decision-log entry is newer than
|
|
233
|
+
* it — i.e. the previous run left a checkpoint and nothing has logged since
|
|
234
|
+
* (a live run that saved progress keeps `interrupted=false`). */
|
|
230
235
|
interrupted: boolean
|
|
231
236
|
/** How many times the current checkpoint has already been resumed. */
|
|
232
237
|
resumeCount: number
|