iterate-plugin 2.9.0 → 2.9.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 +39 -1
- package/dist/skill-prompt.js +8 -5
- package/dist/tools/checkpoint.js +5 -5
- package/dist/tools/config.js +4 -4
- package/dist/tools/context.js +4 -2
- package/dist/tools/review.js +1 -1
- package/dist/tools/triage.js +2 -2
- package/package.json +1 -1
- package/src/config-loader.ts +36 -1
- package/src/skill-prompt.ts +8 -5
- package/src/tools/checkpoint.ts +5 -5
- package/src/tools/config.ts +4 -4
- package/src/tools/context.ts +5 -2
- package/src/tools/review.ts +1 -1
- package/src/tools/triage.ts +2 -2
package/dist/config-loader.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
2
3
|
import { join, resolve, sep } from 'node:path';
|
|
3
4
|
import yaml from 'js-yaml';
|
|
4
5
|
/**
|
|
@@ -168,9 +169,46 @@ export function validateConfig(config) {
|
|
|
168
169
|
*/
|
|
169
170
|
export function resolveProjectRoot(input) {
|
|
170
171
|
const raw = (input ?? '').trim();
|
|
171
|
-
const root = raw ? resolve(raw) : resolve(
|
|
172
|
+
const root = raw ? resolve(raw) : resolve(effectiveCwd());
|
|
172
173
|
if (!root || root === sep) {
|
|
173
174
|
return { ok: false, reason: 'Refusing filesystem root as project root.' };
|
|
174
175
|
}
|
|
175
176
|
return { ok: true, root };
|
|
176
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Resolve the default working directory for tools invoked without an explicit
|
|
180
|
+
* `path`. Prefers the process cwd, but a daemon-managed web server can start
|
|
181
|
+
* with cwd = `/` (e.g. launchd), which is not a usable project root. In that
|
|
182
|
+
* case fall back to the session workspace encoded in `DSH_SESSION_JSONL`
|
|
183
|
+
* (`…/sessions/<encoded-workspace>/<session-id>/session.jsonl.zstd`), where
|
|
184
|
+
* the workspace directory is `--`-wrapped with `/` → `-` and percent-encoded
|
|
185
|
+
* bytes spelled as `~<hex>` (e.g. `/Volumes/Eng-Dev/iterate-skill` →
|
|
186
|
+
* `--Volumes-Eng-Dev-iterate-skill--`).
|
|
187
|
+
*/
|
|
188
|
+
function effectiveCwd() {
|
|
189
|
+
let cwd = '';
|
|
190
|
+
try {
|
|
191
|
+
cwd = process.cwd();
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// cwd may be unreadable (deleted dir) — fall through to session workspace
|
|
195
|
+
}
|
|
196
|
+
if (cwd && cwd !== sep && cwd !== homedir())
|
|
197
|
+
return cwd;
|
|
198
|
+
const session = process.env.DSH_SESSION_JSONL;
|
|
199
|
+
if (session) {
|
|
200
|
+
const m = session.match(/\/sessions\/([^/]+)\//);
|
|
201
|
+
const encoded = m ? m[1] : undefined;
|
|
202
|
+
if (encoded && encoded.startsWith('--') && encoded.endsWith('--')) {
|
|
203
|
+
try {
|
|
204
|
+
const decoded = decodeURIComponent(encoded.slice(2, -2).replace(/~/g, '%'));
|
|
205
|
+
if (decoded && decoded.startsWith(sep))
|
|
206
|
+
return decoded;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// malformed encoding — fall through to cwd
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return cwd || sep;
|
|
214
|
+
}
|
package/dist/skill-prompt.js
CHANGED
|
@@ -90,7 +90,7 @@ for (let r = 1; r <= maxRounds; r++) {
|
|
|
90
90
|
: ''
|
|
91
91
|
const raw = await parallel(dims.map(dim => () => agent(
|
|
92
92
|
'Review dimension "' + dim + '".' +
|
|
93
|
-
(attachments.length > 0 ? ' User-attached images are part of the evidence (
|
|
93
|
+
(attachments.length > 0 ? ' User-attached images are part of the evidence; use their descriptions when judging (you see the metadata/descriptions below, not the pixels): ' + JSON.stringify(attachments) + '.' : '') +
|
|
94
94
|
' Already-known findings (do NOT re-report): ' +
|
|
95
95
|
JSON.stringify(known) + nudge + '\\nReturn the findings JSON object.',
|
|
96
96
|
Object.assign({ label: 'review:' + dim + ':r' + r, schema: plan.dimensions.find(x => x.id === dim).findingsSchema }, backend)
|
|
@@ -190,11 +190,13 @@ const checkpoint = (ckRes && ckRes.checkpoint) ? ckRes.checkpoint : null
|
|
|
190
190
|
const startRound = (checkpoint && typeof checkpoint.round === 'number') ? checkpoint.round + 1 : 1
|
|
191
191
|
// Track how many times this checkpoint has already been resumed (interruption recovery).
|
|
192
192
|
const resumeCount = (checkpoint && typeof checkpoint.resumeCount === 'number') ? checkpoint.resumeCount : 0
|
|
193
|
+
// The effective count AFTER this recovery: this run counts as one more resume.
|
|
194
|
+
const effectiveResumeCount = checkpoint ? resumeCount + 1 : 0
|
|
193
195
|
if (checkpoint) {
|
|
194
196
|
// A previous run left a checkpoint — record the recovery so the decision log
|
|
195
197
|
// shows the resume, then continue where it left off.
|
|
196
198
|
await agent(
|
|
197
|
-
'Call iterate_decision_log({operation:"append", type:"resume", round:' + startRound + ', data:{resumedFromRound:' + checkpoint.round + ', resumeCount:' +
|
|
199
|
+
'Call iterate_decision_log({operation:"append", type:"resume", round:' + startRound + ', data:{resumedFromRound:' + checkpoint.round + ', resumeCount:' + effectiveResumeCount + '}})',
|
|
198
200
|
Object.assign({ label: 'log:resume' }, backend)
|
|
199
201
|
)
|
|
200
202
|
}
|
|
@@ -216,7 +218,8 @@ const knownIntentional = (plan.knownIntentional || []) // config personalizati
|
|
|
216
218
|
const dims = plan.dimensions.map(d => d.id)
|
|
217
219
|
const maxRounds = plan.maxReviewRounds
|
|
218
220
|
const rounds = [] // findings per review round (each on the then-current code state)
|
|
219
|
-
|
|
221
|
+
// Restore previously-unfixed architectural findings when resuming an interrupted run.
|
|
222
|
+
const architectural = (checkpoint && Array.isArray(checkpoint.findings)) ? checkpoint.findings : [] // findings deliberately left unfixed (reported at the end)
|
|
220
223
|
let fixedCount = (checkpoint && typeof checkpoint.fixedCount === 'number') ? checkpoint.fixedCount : 0
|
|
221
224
|
let converged = false
|
|
222
225
|
let abortedByValidation = false
|
|
@@ -235,7 +238,7 @@ for (let r = startRound; r <= maxRounds; r++) {
|
|
|
235
238
|
: ''
|
|
236
239
|
const raw = await parallel(dims.map(dim => () => agent(
|
|
237
240
|
'Review dimension "' + dim + '" on the CURRENT code state (previous atomic findings are fixed). ' +
|
|
238
|
-
(attachments.length > 0 ? ' User-attached images are part of the evidence (
|
|
241
|
+
(attachments.length > 0 ? ' User-attached images are part of the evidence; use their descriptions when judging (you see the metadata/descriptions below, not the pixels): ' + JSON.stringify(attachments) + '.' : '') +
|
|
239
242
|
'Do NOT re-report already-known architectural findings: ' + JSON.stringify(architectural) + nudge + '\\nReturn the findings JSON object.',
|
|
240
243
|
Object.assign({ label: 'review:' + dim + ':r' + r, schema: plan.dimensions.find(x => x.id === dim).findingsSchema }, backend)
|
|
241
244
|
)))
|
|
@@ -341,7 +344,7 @@ for (let r = startRound; r <= maxRounds; r++) {
|
|
|
341
344
|
|
|
342
345
|
// Persist progress so an interrupted run can resume from the next round.
|
|
343
346
|
await agent(
|
|
344
|
-
'Call iterate_checkpoint({ operation: "save", mode: "normal", round:' + r + ', maxRounds:' + maxRounds + ', fixedCount:' + fixedCount + ', architecturalCount:' + architectural.length + ', resumeCount:' +
|
|
347
|
+
'Call iterate_checkpoint({ operation: "save", mode: "normal", round:' + r + ', maxRounds:' + maxRounds + ', fixedCount:' + fixedCount + ', architecturalCount:' + architectural.length + ', resumeCount:' + effectiveResumeCount + ', findings:' + JSON.stringify(architectural) + ' }) and return the checkpoint JSON.',
|
|
345
348
|
Object.assign({ label: 'checkpoint:save:r' + r }, backend)
|
|
346
349
|
)
|
|
347
350
|
|
package/dist/tools/checkpoint.js
CHANGED
|
@@ -149,7 +149,7 @@ export function registerCheckpointTool(ctx) {
|
|
|
149
149
|
const projectRoot = resolved.root;
|
|
150
150
|
if (args.operation === 'load') {
|
|
151
151
|
const checkpoint = readCheckpoint(projectRoot);
|
|
152
|
-
return { operation: 'load', ok: true, checkpoint: checkpoint
|
|
152
|
+
return { operation: 'load', ok: true, checkpoint: checkpoint };
|
|
153
153
|
}
|
|
154
154
|
if (args.operation === 'clear') {
|
|
155
155
|
const existed = existsSync(checkpointPath(projectRoot));
|
|
@@ -217,7 +217,7 @@ export function registerStatusTool(ctx) {
|
|
|
217
217
|
additionalProperties: false,
|
|
218
218
|
properties: {
|
|
219
219
|
ok: { type: 'boolean', required: true },
|
|
220
|
-
mode: { type: 'string' },
|
|
220
|
+
mode: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
221
221
|
currentRound: { type: 'integer' },
|
|
222
222
|
totalRounds: { type: 'integer' },
|
|
223
223
|
fixedCount: { type: 'integer' },
|
|
@@ -227,7 +227,7 @@ export function registerStatusTool(ctx) {
|
|
|
227
227
|
hasCheckpoint: { type: 'boolean' },
|
|
228
228
|
interrupted: { type: 'boolean', description: 'True when a checkpoint exists, meaning the previous run was interrupted before finishing.' },
|
|
229
229
|
resumeCount: { type: 'integer', description: 'How many times the current checkpoint has already been resumed.' },
|
|
230
|
-
lastUpdated: { type: 'string' },
|
|
230
|
+
lastUpdated: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
231
231
|
error: { type: 'string' },
|
|
232
232
|
},
|
|
233
233
|
},
|
|
@@ -258,7 +258,7 @@ export function registerStatusTool(ctx) {
|
|
|
258
258
|
});
|
|
259
259
|
return {
|
|
260
260
|
ok: true,
|
|
261
|
-
mode: status.mode ??
|
|
261
|
+
mode: status.mode ?? null,
|
|
262
262
|
currentRound: status.currentRound,
|
|
263
263
|
totalRounds: status.totalRounds,
|
|
264
264
|
fixedCount: status.fixedCount,
|
|
@@ -268,7 +268,7 @@ export function registerStatusTool(ctx) {
|
|
|
268
268
|
hasCheckpoint: status.hasCheckpoint,
|
|
269
269
|
interrupted: status.interrupted,
|
|
270
270
|
resumeCount: status.resumeCount,
|
|
271
|
-
lastUpdated: status.lastUpdated ??
|
|
271
|
+
lastUpdated: status.lastUpdated ?? null,
|
|
272
272
|
};
|
|
273
273
|
},
|
|
274
274
|
}));
|
package/dist/tools/config.js
CHANGED
|
@@ -47,14 +47,14 @@ export function registerConfigTool(ctx) {
|
|
|
47
47
|
properties: {
|
|
48
48
|
found: { type: 'boolean', required: true },
|
|
49
49
|
valid: { type: 'boolean' },
|
|
50
|
-
errors: { type: 'array', items: { type: 'string' } },
|
|
50
|
+
errors: { oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'null' }] },
|
|
51
51
|
section: { type: 'string' },
|
|
52
52
|
data: { type: 'json' },
|
|
53
53
|
config: { type: 'json' },
|
|
54
54
|
availableSections: { type: 'array', items: { type: 'string' } },
|
|
55
55
|
operation: { type: 'string' },
|
|
56
56
|
ok: { type: 'boolean' },
|
|
57
|
-
backupPath: { type: 'string' },
|
|
57
|
+
backupPath: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
58
58
|
error: { type: 'string' },
|
|
59
59
|
},
|
|
60
60
|
},
|
|
@@ -100,7 +100,7 @@ export function registerConfigTool(ctx) {
|
|
|
100
100
|
operation: 'write',
|
|
101
101
|
ok: true,
|
|
102
102
|
found: true,
|
|
103
|
-
backupPath: result.backupPath ??
|
|
103
|
+
backupPath: result.backupPath ?? null,
|
|
104
104
|
config: config,
|
|
105
105
|
};
|
|
106
106
|
}
|
|
@@ -112,7 +112,7 @@ export function registerConfigTool(ctx) {
|
|
|
112
112
|
return {
|
|
113
113
|
found: hasOverride,
|
|
114
114
|
valid: errors.length === 0,
|
|
115
|
-
errors: errors.length > 0 ? errors :
|
|
115
|
+
errors: errors.length > 0 ? errors : null,
|
|
116
116
|
section: 'validation_report',
|
|
117
117
|
};
|
|
118
118
|
}
|
package/dist/tools/context.js
CHANGED
|
@@ -7,6 +7,8 @@ import { resolveProjectRoot } from "../config-loader.js";
|
|
|
7
7
|
const MAX_SKILL_DIR_LOOKUP_DEPTH = 12;
|
|
8
8
|
/** Maximum number of image attachments relayed into the context in one call. */
|
|
9
9
|
const MAX_ATTACHMENTS = 8;
|
|
10
|
+
/** Maximum intrinsic width/height (px) accepted for an attached image. */
|
|
11
|
+
const MAX_ATTACHMENT_DIMENSION = 16384;
|
|
10
12
|
/**
|
|
11
13
|
* Validate and normalize one raw image-attachment entry passed by the
|
|
12
14
|
* orchestrator. The top-level model observes user-attached images in its own
|
|
@@ -33,8 +35,8 @@ export function normalizeAttachment(raw) {
|
|
|
33
35
|
}
|
|
34
36
|
for (const dim of ['width', 'height']) {
|
|
35
37
|
if (entry[dim] !== undefined) {
|
|
36
|
-
if (typeof entry[dim] !== 'number' || !Number.isInteger(entry[dim]) || entry[dim] < 0 || entry[dim] >
|
|
37
|
-
return { ok: false, error: `attachment.${dim} must be an integer in [0,
|
|
38
|
+
if (typeof entry[dim] !== 'number' || !Number.isInteger(entry[dim]) || entry[dim] < 0 || entry[dim] > MAX_ATTACHMENT_DIMENSION) {
|
|
39
|
+
return { ok: false, error: `attachment.${dim} must be an integer in [0, ${MAX_ATTACHMENT_DIMENSION}]` };
|
|
38
40
|
}
|
|
39
41
|
out[dim] = entry[dim];
|
|
40
42
|
}
|
package/dist/tools/review.js
CHANGED
package/dist/tools/triage.js
CHANGED
|
@@ -262,7 +262,7 @@ export function registerTriageTool(ctx) {
|
|
|
262
262
|
skipped: { type: 'integer' },
|
|
263
263
|
count: { type: 'integer' },
|
|
264
264
|
path: { type: 'string' },
|
|
265
|
-
backupPath: { type: 'string' },
|
|
265
|
+
backupPath: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
266
266
|
entries: { type: 'json' },
|
|
267
267
|
errors: { type: 'array', items: { type: 'string' } },
|
|
268
268
|
error: { type: 'string' },
|
|
@@ -321,7 +321,7 @@ export function registerTriageTool(ctx) {
|
|
|
321
321
|
skipped: result.skipped,
|
|
322
322
|
count: result.count,
|
|
323
323
|
path: result.configPath,
|
|
324
|
-
backupPath: result.backupPath ??
|
|
324
|
+
backupPath: result.backupPath ?? null,
|
|
325
325
|
};
|
|
326
326
|
}
|
|
327
327
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-plugin",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.2",
|
|
4
4
|
"description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus a dry-run pure-review mode with multi-round convergence and a meta-review that audits the report and emits a final review report.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/config-loader.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
2
3
|
import { join, resolve, sep } from 'node:path'
|
|
3
4
|
import yaml from 'js-yaml'
|
|
4
5
|
import type { IterateConfig } from './types.ts'
|
|
@@ -185,9 +186,43 @@ export type ProjectRootResult = { ok: true; root: string } | { ok: false; reason
|
|
|
185
186
|
*/
|
|
186
187
|
export function resolveProjectRoot(input?: string): ProjectRootResult {
|
|
187
188
|
const raw = (input ?? '').trim()
|
|
188
|
-
const root = raw ? resolve(raw) : resolve(
|
|
189
|
+
const root = raw ? resolve(raw) : resolve(effectiveCwd())
|
|
189
190
|
if (!root || root === sep) {
|
|
190
191
|
return { ok: false, reason: 'Refusing filesystem root as project root.' }
|
|
191
192
|
}
|
|
192
193
|
return { ok: true, root }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Resolve the default working directory for tools invoked without an explicit
|
|
198
|
+
* `path`. Prefers the process cwd, but a daemon-managed web server can start
|
|
199
|
+
* with cwd = `/` (e.g. launchd), which is not a usable project root. In that
|
|
200
|
+
* case fall back to the session workspace encoded in `DSH_SESSION_JSONL`
|
|
201
|
+
* (`…/sessions/<encoded-workspace>/<session-id>/session.jsonl.zstd`), where
|
|
202
|
+
* the workspace directory is `--`-wrapped with `/` → `-` and percent-encoded
|
|
203
|
+
* bytes spelled as `~<hex>` (e.g. `/Volumes/Eng-Dev/iterate-skill` →
|
|
204
|
+
* `--Volumes-Eng-Dev-iterate-skill--`).
|
|
205
|
+
*/
|
|
206
|
+
function effectiveCwd(): string {
|
|
207
|
+
let cwd = ''
|
|
208
|
+
try {
|
|
209
|
+
cwd = process.cwd()
|
|
210
|
+
} catch {
|
|
211
|
+
// cwd may be unreadable (deleted dir) — fall through to session workspace
|
|
212
|
+
}
|
|
213
|
+
if (cwd && cwd !== sep && cwd !== homedir()) return cwd
|
|
214
|
+
const session = process.env.DSH_SESSION_JSONL
|
|
215
|
+
if (session) {
|
|
216
|
+
const m = session.match(/\/sessions\/([^/]+)\//)
|
|
217
|
+
const encoded = m ? m[1] : undefined
|
|
218
|
+
if (encoded && encoded.startsWith('--') && encoded.endsWith('--')) {
|
|
219
|
+
try {
|
|
220
|
+
const decoded = decodeURIComponent(encoded.slice(2, -2).replace(/~/g, '%'))
|
|
221
|
+
if (decoded && decoded.startsWith(sep)) return decoded
|
|
222
|
+
} catch {
|
|
223
|
+
// malformed encoding — fall through to cwd
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return cwd || sep
|
|
193
228
|
}
|
package/src/skill-prompt.ts
CHANGED
|
@@ -91,7 +91,7 @@ for (let r = 1; r <= maxRounds; r++) {
|
|
|
91
91
|
: ''
|
|
92
92
|
const raw = await parallel(dims.map(dim => () => agent(
|
|
93
93
|
'Review dimension "' + dim + '".' +
|
|
94
|
-
(attachments.length > 0 ? ' User-attached images are part of the evidence (
|
|
94
|
+
(attachments.length > 0 ? ' User-attached images are part of the evidence; use their descriptions when judging (you see the metadata/descriptions below, not the pixels): ' + JSON.stringify(attachments) + '.' : '') +
|
|
95
95
|
' Already-known findings (do NOT re-report): ' +
|
|
96
96
|
JSON.stringify(known) + nudge + '\\nReturn the findings JSON object.',
|
|
97
97
|
Object.assign({ label: 'review:' + dim + ':r' + r, schema: plan.dimensions.find(x => x.id === dim).findingsSchema }, backend)
|
|
@@ -191,11 +191,13 @@ const checkpoint = (ckRes && ckRes.checkpoint) ? ckRes.checkpoint : null
|
|
|
191
191
|
const startRound = (checkpoint && typeof checkpoint.round === 'number') ? checkpoint.round + 1 : 1
|
|
192
192
|
// Track how many times this checkpoint has already been resumed (interruption recovery).
|
|
193
193
|
const resumeCount = (checkpoint && typeof checkpoint.resumeCount === 'number') ? checkpoint.resumeCount : 0
|
|
194
|
+
// The effective count AFTER this recovery: this run counts as one more resume.
|
|
195
|
+
const effectiveResumeCount = checkpoint ? resumeCount + 1 : 0
|
|
194
196
|
if (checkpoint) {
|
|
195
197
|
// A previous run left a checkpoint — record the recovery so the decision log
|
|
196
198
|
// shows the resume, then continue where it left off.
|
|
197
199
|
await agent(
|
|
198
|
-
'Call iterate_decision_log({operation:"append", type:"resume", round:' + startRound + ', data:{resumedFromRound:' + checkpoint.round + ', resumeCount:' +
|
|
200
|
+
'Call iterate_decision_log({operation:"append", type:"resume", round:' + startRound + ', data:{resumedFromRound:' + checkpoint.round + ', resumeCount:' + effectiveResumeCount + '}})',
|
|
199
201
|
Object.assign({ label: 'log:resume' }, backend)
|
|
200
202
|
)
|
|
201
203
|
}
|
|
@@ -217,7 +219,8 @@ const knownIntentional = (plan.knownIntentional || []) // config personalizati
|
|
|
217
219
|
const dims = plan.dimensions.map(d => d.id)
|
|
218
220
|
const maxRounds = plan.maxReviewRounds
|
|
219
221
|
const rounds = [] // findings per review round (each on the then-current code state)
|
|
220
|
-
|
|
222
|
+
// Restore previously-unfixed architectural findings when resuming an interrupted run.
|
|
223
|
+
const architectural = (checkpoint && Array.isArray(checkpoint.findings)) ? checkpoint.findings : [] // findings deliberately left unfixed (reported at the end)
|
|
221
224
|
let fixedCount = (checkpoint && typeof checkpoint.fixedCount === 'number') ? checkpoint.fixedCount : 0
|
|
222
225
|
let converged = false
|
|
223
226
|
let abortedByValidation = false
|
|
@@ -236,7 +239,7 @@ for (let r = startRound; r <= maxRounds; r++) {
|
|
|
236
239
|
: ''
|
|
237
240
|
const raw = await parallel(dims.map(dim => () => agent(
|
|
238
241
|
'Review dimension "' + dim + '" on the CURRENT code state (previous atomic findings are fixed). ' +
|
|
239
|
-
(attachments.length > 0 ? ' User-attached images are part of the evidence (
|
|
242
|
+
(attachments.length > 0 ? ' User-attached images are part of the evidence; use their descriptions when judging (you see the metadata/descriptions below, not the pixels): ' + JSON.stringify(attachments) + '.' : '') +
|
|
240
243
|
'Do NOT re-report already-known architectural findings: ' + JSON.stringify(architectural) + nudge + '\\nReturn the findings JSON object.',
|
|
241
244
|
Object.assign({ label: 'review:' + dim + ':r' + r, schema: plan.dimensions.find(x => x.id === dim).findingsSchema }, backend)
|
|
242
245
|
)))
|
|
@@ -342,7 +345,7 @@ for (let r = startRound; r <= maxRounds; r++) {
|
|
|
342
345
|
|
|
343
346
|
// Persist progress so an interrupted run can resume from the next round.
|
|
344
347
|
await agent(
|
|
345
|
-
'Call iterate_checkpoint({ operation: "save", mode: "normal", round:' + r + ', maxRounds:' + maxRounds + ', fixedCount:' + fixedCount + ', architecturalCount:' + architectural.length + ', resumeCount:' +
|
|
348
|
+
'Call iterate_checkpoint({ operation: "save", mode: "normal", round:' + r + ', maxRounds:' + maxRounds + ', fixedCount:' + fixedCount + ', architecturalCount:' + architectural.length + ', resumeCount:' + effectiveResumeCount + ', findings:' + JSON.stringify(architectural) + ' }) and return the checkpoint JSON.',
|
|
346
349
|
Object.assign({ label: 'checkpoint:save:r' + r }, backend)
|
|
347
350
|
)
|
|
348
351
|
|
package/src/tools/checkpoint.ts
CHANGED
|
@@ -173,7 +173,7 @@ export function registerCheckpointTool(ctx: { tools: { register: (def: ReturnTyp
|
|
|
173
173
|
|
|
174
174
|
if (args.operation === 'load') {
|
|
175
175
|
const checkpoint = readCheckpoint(projectRoot)
|
|
176
|
-
return { operation: 'load', ok: true, checkpoint:
|
|
176
|
+
return { operation: 'load', ok: true, checkpoint: checkpoint as unknown as JsonValue | null }
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
if (args.operation === 'clear') {
|
|
@@ -245,7 +245,7 @@ export function registerStatusTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
245
245
|
additionalProperties: false,
|
|
246
246
|
properties: {
|
|
247
247
|
ok: { type: 'boolean', required: true },
|
|
248
|
-
mode: { type: 'string' },
|
|
248
|
+
mode: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
249
249
|
currentRound: { type: 'integer' },
|
|
250
250
|
totalRounds: { type: 'integer' },
|
|
251
251
|
fixedCount: { type: 'integer' },
|
|
@@ -255,7 +255,7 @@ export function registerStatusTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
255
255
|
hasCheckpoint: { type: 'boolean' },
|
|
256
256
|
interrupted: { type: 'boolean', description: 'True when a checkpoint exists, meaning the previous run was interrupted before finishing.' },
|
|
257
257
|
resumeCount: { type: 'integer', description: 'How many times the current checkpoint has already been resumed.' },
|
|
258
|
-
lastUpdated: { type: 'string' },
|
|
258
|
+
lastUpdated: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
259
259
|
error: { type: 'string' },
|
|
260
260
|
},
|
|
261
261
|
},
|
|
@@ -285,7 +285,7 @@ export function registerStatusTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
285
285
|
})
|
|
286
286
|
return {
|
|
287
287
|
ok: true,
|
|
288
|
-
mode: status.mode ??
|
|
288
|
+
mode: status.mode ?? null,
|
|
289
289
|
currentRound: status.currentRound,
|
|
290
290
|
totalRounds: status.totalRounds,
|
|
291
291
|
fixedCount: status.fixedCount,
|
|
@@ -295,7 +295,7 @@ export function registerStatusTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
295
295
|
hasCheckpoint: status.hasCheckpoint,
|
|
296
296
|
interrupted: status.interrupted,
|
|
297
297
|
resumeCount: status.resumeCount,
|
|
298
|
-
lastUpdated: status.lastUpdated ??
|
|
298
|
+
lastUpdated: status.lastUpdated ?? null,
|
|
299
299
|
}
|
|
300
300
|
},
|
|
301
301
|
}),
|
package/src/tools/config.ts
CHANGED
|
@@ -60,14 +60,14 @@ export function registerConfigTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
60
60
|
properties: {
|
|
61
61
|
found: { type: 'boolean', required: true },
|
|
62
62
|
valid: { type: 'boolean' },
|
|
63
|
-
errors: { type: 'array', items: { type: 'string' } },
|
|
63
|
+
errors: { oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'null' }] },
|
|
64
64
|
section: { type: 'string' },
|
|
65
65
|
data: { type: 'json' },
|
|
66
66
|
config: { type: 'json' },
|
|
67
67
|
availableSections: { type: 'array', items: { type: 'string' } },
|
|
68
68
|
operation: { type: 'string' },
|
|
69
69
|
ok: { type: 'boolean' },
|
|
70
|
-
backupPath: { type: 'string' },
|
|
70
|
+
backupPath: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
71
71
|
error: { type: 'string' },
|
|
72
72
|
},
|
|
73
73
|
},
|
|
@@ -113,7 +113,7 @@ export function registerConfigTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
113
113
|
operation: 'write',
|
|
114
114
|
ok: true,
|
|
115
115
|
found: true,
|
|
116
|
-
backupPath: result.backupPath ??
|
|
116
|
+
backupPath: result.backupPath ?? null,
|
|
117
117
|
config: config as unknown as JsonValue,
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -127,7 +127,7 @@ export function registerConfigTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
127
127
|
return {
|
|
128
128
|
found: hasOverride,
|
|
129
129
|
valid: errors.length === 0,
|
|
130
|
-
errors: errors.length > 0 ? errors :
|
|
130
|
+
errors: errors.length > 0 ? errors : null,
|
|
131
131
|
section: 'validation_report',
|
|
132
132
|
}
|
|
133
133
|
}
|
package/src/tools/context.ts
CHANGED
|
@@ -10,6 +10,9 @@ const MAX_SKILL_DIR_LOOKUP_DEPTH = 12
|
|
|
10
10
|
/** Maximum number of image attachments relayed into the context in one call. */
|
|
11
11
|
const MAX_ATTACHMENTS = 8
|
|
12
12
|
|
|
13
|
+
/** Maximum intrinsic width/height (px) accepted for an attached image. */
|
|
14
|
+
const MAX_ATTACHMENT_DIMENSION = 16384
|
|
15
|
+
|
|
13
16
|
/** A validated, normalized image-attachment entry carried into review context. */
|
|
14
17
|
export interface NormalizedAttachment {
|
|
15
18
|
name?: string
|
|
@@ -50,8 +53,8 @@ export function normalizeAttachment(raw: unknown): AttachmentValidationResult {
|
|
|
50
53
|
}
|
|
51
54
|
for (const dim of ['width', 'height'] as const) {
|
|
52
55
|
if (entry[dim] !== undefined) {
|
|
53
|
-
if (typeof entry[dim] !== 'number' || !Number.isInteger(entry[dim]) || entry[dim] < 0 || entry[dim] >
|
|
54
|
-
return { ok: false, error: `attachment.${dim} must be an integer in [0,
|
|
56
|
+
if (typeof entry[dim] !== 'number' || !Number.isInteger(entry[dim]) || entry[dim] < 0 || entry[dim] > MAX_ATTACHMENT_DIMENSION) {
|
|
57
|
+
return { ok: false, error: `attachment.${dim} must be an integer in [0, ${MAX_ATTACHMENT_DIMENSION}]` }
|
|
55
58
|
}
|
|
56
59
|
out[dim] = entry[dim]
|
|
57
60
|
}
|
package/src/tools/review.ts
CHANGED
|
@@ -213,7 +213,7 @@ export function registerReviewTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
213
213
|
operation: 'aggregate',
|
|
214
214
|
mode,
|
|
215
215
|
report: report as unknown as JsonValue,
|
|
216
|
-
schemaValidation: schemaValidation as unknown as JsonValue |
|
|
216
|
+
schemaValidation: (schemaValidation ?? null) as unknown as JsonValue | null,
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
package/src/tools/triage.ts
CHANGED
|
@@ -302,7 +302,7 @@ export function registerTriageTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
302
302
|
skipped: { type: 'integer' },
|
|
303
303
|
count: { type: 'integer' },
|
|
304
304
|
path: { type: 'string' },
|
|
305
|
-
backupPath: { type: 'string' },
|
|
305
|
+
backupPath: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
306
306
|
entries: { type: 'json' },
|
|
307
307
|
errors: { type: 'array', items: { type: 'string' } },
|
|
308
308
|
error: { type: 'string' },
|
|
@@ -363,7 +363,7 @@ export function registerTriageTool(ctx: { tools: { register: (def: ReturnType<ty
|
|
|
363
363
|
skipped: result.skipped,
|
|
364
364
|
count: result.count,
|
|
365
365
|
path: result.configPath,
|
|
366
|
-
backupPath: result.backupPath ??
|
|
366
|
+
backupPath: result.backupPath ?? null,
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
|