iterate-plugin 3.5.3 → 3.5.5
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/atomic-fs.js +22 -4
- package/dist/config-loader.js +8 -3
- package/dist/config-write.js +50 -9
- package/dist/evidence.js +28 -1
- package/dist/git-scope.js +14 -5
- package/dist/jobs.js +15 -1
- package/dist/live.js +39 -22
- package/dist/meta-review.js +2 -2
- package/dist/review-scope.js +6 -2
- package/dist/review.js +7 -6
- package/dist/session-hooks.js +48 -4
- package/dist/skill-prompt.js +66 -32
- package/dist/tools/context.js +5 -1
- package/dist/tools/decision-log.js +142 -17
- package/dist/tools/defense-events.js +7 -3
- package/dist/tools/defense-store.js +14 -7
- package/dist/tools/experience-bank.js +6 -3
- package/dist/tools/experience-store.js +24 -12
- package/dist/tools/fix.js +24 -3
- package/dist/tools/prune.js +185 -17
- package/dist/tools/review.js +17 -3
- package/dist/tools/transcript.js +28 -6
- package/dist/tools/triage.js +38 -4
- package/dist/transcript.js +68 -34
- package/lib/client.js +41 -11
- package/lib/parse.js +30 -2
- package/package.json +10 -7
- package/src/atomic-fs.ts +20 -4
- package/src/client/index.ts +23 -3
- package/src/config-loader.ts +8 -3
- package/src/config-write.ts +59 -9
- package/src/evidence.ts +29 -1
- package/src/git-scope.ts +14 -3
- package/src/jobs.ts +15 -1
- package/src/live.ts +41 -22
- package/src/meta-review.ts +2 -2
- package/src/review-scope.ts +6 -2
- package/src/review.ts +7 -6
- package/src/session-hooks.ts +50 -4
- package/src/skill-prompt.ts +66 -32
- package/src/tools/context.ts +5 -1
- package/src/tools/decision-log.ts +136 -16
- package/src/tools/defense-events.ts +7 -3
- package/src/tools/defense-store.ts +14 -7
- package/src/tools/experience-bank.ts +6 -3
- package/src/tools/experience-store.ts +24 -12
- package/src/tools/fix.ts +25 -3
- package/src/tools/prune.ts +194 -17
- package/src/tools/review.ts +16 -3
- package/src/tools/transcript.ts +26 -5
- package/src/tools/triage.ts +39 -4
- package/src/transcript.ts +68 -31
- package/src/types.ts +2 -0
package/src/transcript.ts
CHANGED
|
@@ -71,6 +71,20 @@ function clampStringList(source: string[], cap: number): string[] {
|
|
|
71
71
|
return out
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Deep-clone a decision-timeline payload so it is decoupled from the caller's
|
|
76
|
+
* live object. structuredClone is available in every supported Node release;
|
|
77
|
+
* on the off chance it is missing, fall back to a JSON round-trip (decision
|
|
78
|
+
* payloads are JSON-serializable by construction).
|
|
79
|
+
*/
|
|
80
|
+
function cloneDecisionData(data: Record<string, unknown>): Record<string, unknown> {
|
|
81
|
+
try {
|
|
82
|
+
return structuredClone(data)
|
|
83
|
+
} catch {
|
|
84
|
+
return JSON.parse(JSON.stringify(data)) as Record<string, unknown>
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
74
88
|
/** Normalize a single finding, dropping malformed entries. */
|
|
75
89
|
function normalizeFinding(input: unknown): TranscriptFinding | null {
|
|
76
90
|
if (!input || typeof input !== 'object') return null
|
|
@@ -113,6 +127,19 @@ interface LiveRound {
|
|
|
113
127
|
threads: LiveThread[]
|
|
114
128
|
}
|
|
115
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Append one raw finding to a thread's list, bound to MAX_FINDINGS_PER_THREAD
|
|
132
|
+
* with NEWEST-WINS eviction (drop the oldest findings once the cap is hit).
|
|
133
|
+
* A single oversized fixer agent could otherwise OOM the client with an
|
|
134
|
+
* unbounded finding dump inside one thread.
|
|
135
|
+
*/
|
|
136
|
+
function pushThreadFinding(thread: LiveThread, raw: unknown): void {
|
|
137
|
+
thread.findings.push(raw)
|
|
138
|
+
if (thread.findings.length > MAX_FINDINGS_PER_THREAD) {
|
|
139
|
+
thread.findings.splice(0, thread.findings.length - MAX_FINDINGS_PER_THREAD)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
116
143
|
/** Merge a report snapshot's findings/readFiles into a thread by dimension. */
|
|
117
144
|
function mergeReportIntoThread(
|
|
118
145
|
thread: LiveThread,
|
|
@@ -120,8 +147,7 @@ function mergeReportIntoThread(
|
|
|
120
147
|
readFiles: readonly unknown[] | undefined,
|
|
121
148
|
): void {
|
|
122
149
|
for (const raw of findings) {
|
|
123
|
-
|
|
124
|
-
if (f) thread.findings.push(raw)
|
|
150
|
+
if (normalizeFinding(raw)) pushThreadFinding(thread, raw)
|
|
125
151
|
}
|
|
126
152
|
for (const r of readFiles ?? []) {
|
|
127
153
|
if (typeof r === 'string') thread.readFiles.push(r)
|
|
@@ -142,9 +168,11 @@ export class ReviewTranscriptBuilder {
|
|
|
142
168
|
private round = 0
|
|
143
169
|
private maxRounds = 0
|
|
144
170
|
private active = true
|
|
171
|
+
private stoppedReason: string | null = null
|
|
145
172
|
private readonly rounds: LiveRound[] = []
|
|
146
173
|
private readonly convergence: number[] = []
|
|
147
174
|
private readonly globalFindings: TranscriptFinding[] = []
|
|
175
|
+
private readonly globalSeenKeys = new Set<string>()
|
|
148
176
|
private readonly fixes: TranscriptFix[] = []
|
|
149
177
|
private checkpoint: TranscriptCheckpoint | null = null
|
|
150
178
|
private readonly timeline: TranscriptEntry[] = []
|
|
@@ -203,9 +231,14 @@ export class ReviewTranscriptBuilder {
|
|
|
203
231
|
this.touch()
|
|
204
232
|
}
|
|
205
233
|
|
|
206
|
-
/**
|
|
207
|
-
|
|
234
|
+
/**
|
|
235
|
+
* End the run (stops the "active" pulsing in the UI). `reason` records WHY
|
|
236
|
+
* it ended, so a run stopped by max-rounds or validation is never mistaken
|
|
237
|
+
* for a live run NOR for a clean convergence.
|
|
238
|
+
*/
|
|
239
|
+
finish(reason?: string): void {
|
|
208
240
|
this.active = false
|
|
241
|
+
if (typeof reason === 'string' && reason.trim()) this.stoppedReason = reason.trim()
|
|
209
242
|
this.touch()
|
|
210
243
|
}
|
|
211
244
|
|
|
@@ -276,12 +309,11 @@ export class ReviewTranscriptBuilder {
|
|
|
276
309
|
for (const raw of findings) {
|
|
277
310
|
const f = normalizeFinding(raw)
|
|
278
311
|
if (f) {
|
|
279
|
-
thread
|
|
280
|
-
this.
|
|
312
|
+
pushThreadFinding(thread, raw)
|
|
313
|
+
this.addGlobal(f)
|
|
281
314
|
}
|
|
282
315
|
}
|
|
283
316
|
}
|
|
284
|
-
this.reevaluateGlobal()
|
|
285
317
|
this.touch()
|
|
286
318
|
}
|
|
287
319
|
|
|
@@ -293,9 +325,8 @@ export class ReviewTranscriptBuilder {
|
|
|
293
325
|
mergeReportIntoThread(thread, findings, readFiles)
|
|
294
326
|
for (const raw of findings) {
|
|
295
327
|
const f = normalizeFinding(raw)
|
|
296
|
-
if (f) this.
|
|
328
|
+
if (f) this.addGlobal(f)
|
|
297
329
|
}
|
|
298
|
-
this.reevaluateGlobal()
|
|
299
330
|
this.touch()
|
|
300
331
|
}
|
|
301
332
|
|
|
@@ -384,8 +415,12 @@ export class ReviewTranscriptBuilder {
|
|
|
384
415
|
: this.round,
|
|
385
416
|
type,
|
|
386
417
|
data:
|
|
387
|
-
entry.data && typeof entry.data === 'object'
|
|
388
|
-
|
|
418
|
+
entry.data && typeof entry.data === 'object' && !Array.isArray(entry.data)
|
|
419
|
+
// Deep clone so a caller mutating its own object later can never
|
|
420
|
+
// alias into the timeline (serialize would then emit the mutated
|
|
421
|
+
// values instead of what was actually decided). Decision-log payloads
|
|
422
|
+
// are small JSON, so the clone cost is negligible.
|
|
423
|
+
? cloneDecisionData(entry.data as Record<string, unknown>)
|
|
389
424
|
: {},
|
|
390
425
|
})
|
|
391
426
|
if (this.timeline.length > MAX_TIMELINE) {
|
|
@@ -435,14 +470,14 @@ export class ReviewTranscriptBuilder {
|
|
|
435
470
|
maxRounds: this.maxRounds,
|
|
436
471
|
rounds,
|
|
437
472
|
convergence: this.convergence,
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
: this.globalFindings,
|
|
473
|
+
// addGlobal maintains the newest-wins cap incrementally, so the array is
|
|
474
|
+
// already ≤ MAX_FINDINGS_TOTAL — no post-hoc slice needed.
|
|
475
|
+
findings: this.globalFindings,
|
|
442
476
|
fixes: this.fixes,
|
|
443
477
|
checkpoint: this.checkpoint,
|
|
444
478
|
timeline: this.timeline,
|
|
445
479
|
nudge: this.nudge,
|
|
480
|
+
stoppedReason: this.stoppedReason,
|
|
446
481
|
approval: {
|
|
447
482
|
active: this.approval !== 'allow',
|
|
448
483
|
policy: this.approval,
|
|
@@ -470,22 +505,24 @@ export class ReviewTranscriptBuilder {
|
|
|
470
505
|
return live.threads.length
|
|
471
506
|
}
|
|
472
507
|
|
|
473
|
-
/**
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
508
|
+
/**
|
|
509
|
+
* Insert one deduplicated finding into the compact global list.
|
|
510
|
+
* NEWEST-WINS: when the global cap (MAX_FINDINGS_TOTAL) is hit, the oldest
|
|
511
|
+
* kept finding is evicted so fresh, still-relevant findings survive — this
|
|
512
|
+
* is the documented contract. Amortized O(1): dedupe via a seen-key set and
|
|
513
|
+
* eviction is one `shift` per overflow insert.
|
|
514
|
+
*/
|
|
515
|
+
private addGlobal(f: TranscriptFinding): void {
|
|
516
|
+
const key = `${f.file}\u0000${f.line ?? 0}\u0000${f.dimension}\u0000${f.summary}`
|
|
517
|
+
if (this.globalSeenKeys.has(key)) return
|
|
518
|
+
this.globalSeenKeys.add(key)
|
|
519
|
+
this.globalFindings.push(f)
|
|
520
|
+
if (this.globalFindings.length > MAX_FINDINGS_TOTAL) {
|
|
521
|
+
const evicted = this.globalFindings.shift()
|
|
522
|
+
if (evicted) {
|
|
523
|
+
this.globalSeenKeys.delete(
|
|
524
|
+
`${evicted.file}\u0000${evicted.line ?? 0}\u0000${evicted.dimension}\u0000${evicted.summary}`,
|
|
525
|
+
)
|
|
489
526
|
}
|
|
490
527
|
}
|
|
491
528
|
}
|
package/src/types.ts
CHANGED
|
@@ -349,6 +349,8 @@ export interface TranscriptManifest {
|
|
|
349
349
|
}
|
|
350
350
|
/** v3.0: task_mode indicator from harness status */
|
|
351
351
|
taskMode?: 'code' | 'iterate' | null
|
|
352
|
+
/** Why the run ended: converged / max_rounds_reached / aborted_by_validation. */
|
|
353
|
+
stoppedReason?: string | null
|
|
352
354
|
}
|
|
353
355
|
|
|
354
356
|
// ─── v3.0: Quality Gate ──────────────────────────────────────────────────────
|