iterate-plugin 2.3.7 → 2.4.0

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/lib/parse.js ADDED
@@ -0,0 +1,459 @@
1
+ /**
2
+ * lib/parse.js — Pure logic for iterate client UI.
3
+ *
4
+ * Framework-agnostic, DOM-free, single-file, testable with Node.js assert.
5
+ * Every function is exported for unit test coverage.
6
+ *
7
+ * @module iterate-ui/parse
8
+ */
9
+
10
+ // ─── Constants ───────────────────────────────────────────────────────────────
11
+
12
+ /** Severity ordering (lowest index = most severe). */
13
+ export const SEVERITY_ORDER = ['critical', 'high', 'medium', 'low']
14
+
15
+ /** Severity labels (short form for badges). */
16
+ export const SEVERITY_LABEL = {
17
+ critical: 'CRIT',
18
+ high: 'HIGH',
19
+ medium: 'MED',
20
+ low: 'LOW',
21
+ }
22
+
23
+ /** Severity colors (CSS-compatible). */
24
+ export const SEVERITY_COLOR = {
25
+ critical: '#ef4444',
26
+ high: '#f97316',
27
+ medium: '#eab308',
28
+ low: '#6b7280',
29
+ }
30
+
31
+ // ─── ReviewReport detection ──────────────────────────────────────────────────
32
+
33
+ /**
34
+ * Check whether `obj` is a valid ReviewReport-like object.
35
+ * The minimum requirement: an object with `convergence` (object),
36
+ * `findings` (array), and `rounds` (array).
37
+ *
38
+ * @param {unknown} obj
39
+ * @returns {obj is Record<string, unknown>}
40
+ */
41
+ export function isReviewReport(obj) {
42
+ if (!obj || typeof obj !== 'object') return false
43
+ const o = /** @type {Record<string, unknown>} */ (obj)
44
+ return (
45
+ typeof o.convergence === 'object' &&
46
+ o.convergence !== null &&
47
+ Array.isArray(o.findings) &&
48
+ Array.isArray(o.rounds)
49
+ )
50
+ }
51
+
52
+ /**
53
+ * Deep-scan an object tree for the first ReviewReport.
54
+ *
55
+ * - Uses a `seen` Set to avoid circular references.
56
+ * - Respects `maxDepth` (default 20) to cap stack depth.
57
+ * - Returns the first Report found (breadth-first precedence), or null.
58
+ *
59
+ * @param {unknown} obj
60
+ * @param {Set<unknown>} [seen]
61
+ * @param {number} [maxDepth=20]
62
+ * @returns {Record<string, unknown> | null}
63
+ */
64
+ export function findReportInObject(obj, seen, maxDepth = 20) {
65
+ if (maxDepth <= 0) return null
66
+ if (!obj || typeof obj !== 'object') return null
67
+
68
+ const s = seen || new Set()
69
+ if (s.has(obj)) return null
70
+ s.add(obj)
71
+
72
+ // Check self
73
+ if (isReviewReport(obj)) return /** @type {Record<string, unknown>} */ (obj)
74
+
75
+ // Check arrays first (breadth-first within a node)
76
+ if (Array.isArray(obj)) {
77
+ for (const item of obj) {
78
+ const found = findReportInObject(item, s, maxDepth - 1)
79
+ if (found) return found
80
+ }
81
+ return null
82
+ }
83
+
84
+ // Check object values
85
+ const o = /** @type {Record<string, unknown>} */ (obj)
86
+ for (const key of Object.keys(o)) {
87
+ const val = o[key]
88
+ if (val && typeof val === 'object') {
89
+ // Check leaf values that are arrays or objects
90
+ const found = findReportInObject(val, s, maxDepth - 1)
91
+ if (found) return found
92
+ }
93
+ }
94
+
95
+ return null
96
+ }
97
+
98
+ /**
99
+ * Scan a session snapshot (or any object) for the latest iterate_review tool
100
+ * call result that contains a ReviewReport. Prefers the most recent one.
101
+ *
102
+ * @param {unknown} session
103
+ * @returns {Record<string, unknown> | null}
104
+ */
105
+ export function scanSessionForReport(session) {
106
+ if (!session || typeof session !== 'object') return null
107
+
108
+ // Try direct find first
109
+ const direct = findReportInObject(session)
110
+ if (direct) return direct
111
+
112
+ // Try common session structures
113
+ const s = /** @type {Record<string, unknown>} */ (session)
114
+
115
+ // Common pattern: session.toolCalls[].result.report
116
+ if (Array.isArray(s.toolCalls)) {
117
+ const calls = /** @type {Array<Record<string, unknown>>} */ (s.toolCalls)
118
+ for (let i = calls.length - 1; i >= 0; i--) {
119
+ const call = calls[i]
120
+ if (!call) continue
121
+ if (call.tool === 'iterate_review' || String(call.tool ?? '').endsWith('iterate_review')) {
122
+ const result = call.result
123
+ if (result && typeof result === 'object') {
124
+ const r = /** @type {Record<string, unknown>} */ (result)
125
+ if (r.report && typeof r.report === 'object') {
126
+ return /** @type {Record<string, unknown>} */ (r.report)
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // Common pattern: session.messages[].tool_calls[].function.arguments
134
+ if (Array.isArray(s.messages)) {
135
+ const msgs = /** @type {Array<Record<string, unknown>>} */ (s.messages)
136
+ for (let i = msgs.length - 1; i >= 0; i--) {
137
+ const msg = msgs[i]
138
+ if (!msg || !Array.isArray(msg.tool_calls)) continue
139
+ const calls = /** @type {Array<Record<string, unknown>>} */ (msg.tool_calls)
140
+ for (const call of calls) {
141
+ if (!call) continue
142
+ const fn = call.function
143
+ if (fn && typeof fn === 'object') {
144
+ const f = /** @type {Record<string, unknown>} */ (fn)
145
+ if (String(f.name ?? '').endsWith('iterate_review')) {
146
+ // Try to parse arguments
147
+ try {
148
+ const args = JSON.parse(String(f.arguments ?? '{}'))
149
+ const found = findReportInObject(args)
150
+ if (found) return found
151
+ } catch {
152
+ // Not JSON, skip
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ return null
161
+ }
162
+
163
+ // ─── Normalization ───────────────────────────────────────────────────────────
164
+
165
+ /**
166
+ * Normalize a ReviewReport, filling in missing optional fields with computed
167
+ * defaults. Never mutates the input.
168
+ *
169
+ * @param {Record<string, unknown>} report
170
+ * @returns {Record<string, unknown>}
171
+ */
172
+ export function normalizeReport(report) {
173
+ const convergence = /** @type {Record<string, unknown>} */ (report.convergence ?? {})
174
+ const rounds = /** @type {Array<unknown>} */ (report.rounds ?? [])
175
+ const findings = /** @type {Array<Record<string, unknown>>} */ (report.findings ?? [])
176
+
177
+ // Normalize convergence
178
+ const totalRounds =
179
+ typeof convergence.totalRounds === 'number'
180
+ ? convergence.totalRounds
181
+ : rounds.length
182
+
183
+ const normalizedConvergence = {
184
+ totalRounds,
185
+ findingsByRound: Array.isArray(convergence.findingsByRound)
186
+ ? convergence.findingsByRound
187
+ : rounds.map((r) => {
188
+ const rr = /** @type {Record<string, unknown>} */ (r)
189
+ return Array.isArray(rr?.findings) ? rr.findings.length : 0
190
+ }),
191
+ converged: convergence.converged === true,
192
+ stoppedReason: convergence.stoppedReason ?? (rounds.length < totalRounds ? 'converged' : 'max_rounds_reached'),
193
+ }
194
+
195
+ // Compute summary if missing. Always build a NEW object so the input's
196
+ // summary (or any other field) is never mutated.
197
+ let summary = report.summary
198
+ if (!summary || typeof summary !== 'object') {
199
+ summary = computeSummaryFromFindings(findings)
200
+ } else {
201
+ const s = /** @type {Record<string, unknown>} */ (summary)
202
+ const computed = computeSummaryFromFindings(findings)
203
+ summary = {
204
+ totalFindings: typeof s.totalFindings === 'number' ? s.totalFindings : findings.length,
205
+ critical: typeof s.critical === 'number' ? s.critical : computed.critical,
206
+ high: typeof s.high === 'number' ? s.high : computed.high,
207
+ medium: typeof s.medium === 'number' ? s.medium : computed.medium,
208
+ low: typeof s.low === 'number' ? s.low : computed.low,
209
+ byDimension: s.byDimension && typeof s.byDimension === 'object'
210
+ ? s.byDimension
211
+ : computed.byDimension,
212
+ }
213
+ }
214
+
215
+ return {
216
+ mode: report.mode ?? 'dry-run',
217
+ goal: report.goal ?? '',
218
+ dimensions: Array.isArray(report.dimensions) ? report.dimensions : [],
219
+ maxReviewRounds: report.maxReviewRounds ?? totalRounds,
220
+ rounds,
221
+ findings,
222
+ convergence: normalizedConvergence,
223
+ summary,
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Compute summary stats from findings array.
229
+ *
230
+ * @param {Array<Record<string, unknown>>} findings
231
+ * @returns {{ totalFindings: number, critical: number, high: number, medium: number, low: number, byDimension: Record<string, number> }}
232
+ */
233
+ function computeSummaryFromFindings(findings) {
234
+ const counts = { critical: 0, high: 0, medium: 0, low: 0 }
235
+ /** @type {Record<string, number>} */
236
+ const byDimension = {}
237
+
238
+ for (const f of findings) {
239
+ const sev = String(f.severity ?? 'low')
240
+ if (sev in counts) counts[sev]++
241
+ const dim = String(f.dimension ?? 'unknown')
242
+ byDimension[dim] = (byDimension[dim] ?? 0) + 1
243
+ }
244
+
245
+ return {
246
+ totalFindings: findings.length,
247
+ critical: counts.critical,
248
+ high: counts.high,
249
+ medium: counts.medium,
250
+ low: counts.low,
251
+ byDimension,
252
+ }
253
+ }
254
+
255
+ export { computeSummaryFromFindings }
256
+
257
+ // ─── Convergence helpers ─────────────────────────────────────────────────────
258
+
259
+ /**
260
+ * Compute progress percentage (0-100) from a normalized report.
261
+ *
262
+ * @param {Record<string, unknown>} report
263
+ * @returns {number}
264
+ */
265
+ export function computeConvergenceProgress(report) {
266
+ const convergence = /** @type {Record<string, unknown>} */ (report.convergence ?? {})
267
+ const totalRounds = typeof convergence.totalRounds === 'number'
268
+ ? convergence.totalRounds
269
+ : 1
270
+ const currentRounds = /** @type {Array<unknown>} */ (report.rounds ?? []).length
271
+ // Guard against an empty report (totalRounds <= 0) producing NaN.
272
+ if (!(totalRounds > 0)) return 0
273
+ return Math.min(100, Math.round((currentRounds / totalRounds) * 100))
274
+ }
275
+
276
+ /**
277
+ * Get the current round number (1-indexed) from a report.
278
+ *
279
+ * @param {Record<string, unknown>} report
280
+ * @returns {number}
281
+ */
282
+ export function getCurrentRound(report) {
283
+ return (/** @type {Array<unknown>} */ (report.rounds ?? [])).length
284
+ }
285
+
286
+ /**
287
+ * Get the total round count (max) from a report.
288
+ *
289
+ * @param {Record<string, unknown>} report
290
+ * @returns {number}
291
+ */
292
+ export function getTotalRounds(report) {
293
+ const convergence = /** @type {Record<string, unknown>} */ (report.convergence ?? {})
294
+ return typeof convergence.totalRounds === 'number'
295
+ ? convergence.totalRounds
296
+ : 1
297
+ }
298
+
299
+ // ─── Severity stats ──────────────────────────────────────────────────────────
300
+
301
+ /**
302
+ * Count findings by severity. Returns an object with `critical`, `high`,
303
+ * `medium`, `low` keys.
304
+ *
305
+ * @param {Record<string, unknown>} report
306
+ * @returns {{ critical: number, high: number, medium: number, low: number }}
307
+ */
308
+ export function severityStats(report) {
309
+ const findings = /** @type {Array<Record<string, unknown>>} */ (report.findings ?? [])
310
+ const counts = { critical: 0, high: 0, medium: 0, low: 0 }
311
+ for (const f of findings) {
312
+ const sev = String(f.severity ?? 'low')
313
+ if (sev in counts) counts[sev]++
314
+ }
315
+ return counts
316
+ }
317
+
318
+ // ─── Dimension grouping ──────────────────────────────────────────────────────
319
+
320
+ /**
321
+ * Group findings by dimension. Returns a Record<string, Array<finding>>.
322
+ *
323
+ * @param {Record<string, unknown>} report
324
+ * @returns {Record<string, Array<Record<string, unknown>>>}
325
+ */
326
+ export function groupByDimension(report) {
327
+ const findings = /** @type {Array<Record<string, unknown>>} */ (report.findings ?? [])
328
+ /** @type {Record<string, Array<Record<string, unknown>>>} */
329
+ const groups = {}
330
+ for (const f of findings) {
331
+ const dim = String(f.dimension ?? 'unknown')
332
+ if (!groups[dim]) groups[dim] = []
333
+ groups[dim].push(f)
334
+ }
335
+ return groups
336
+ }
337
+
338
+ // ─── Triage state ────────────────────────────────────────────────────────────
339
+
340
+ /** Triage verdict values */
341
+ export const TRIAGE_VERDICTS = /** @type {const} */ (['keep', 'skip', 'ignore'])
342
+
343
+ /**
344
+ * Build initial triage state for a report. Each finding gets a default verdict
345
+ * of 'keep'. Returns a Map where key = finding index (string), value = verdict.
346
+ *
347
+ * @param {Record<string, unknown>} report
348
+ * @returns {Record<string, 'keep' | 'skip' | 'ignore'>}
349
+ */
350
+ export function buildTriageState(report) {
351
+ const findings = /** @type {Array<unknown>} */ (report.findings ?? [])
352
+ /** @type {Record<string, 'keep' | 'skip' | 'ignore'>} */
353
+ const state = {}
354
+ for (let i = 0; i < findings.length; i++) {
355
+ state[String(i)] = 'keep'
356
+ }
357
+ return state
358
+ }
359
+
360
+ // ─── Report hashing (for localStorage key) ────────────────────────────────────
361
+
362
+ /**
363
+ * Create a deterministic hash string from a report's key fields.
364
+ * Used as localStorage key for persisting triage verdicts.
365
+ *
366
+ * @param {Record<string, unknown>} report
367
+ * @returns {string}
368
+ */
369
+ export function hashReport(report) {
370
+ const convergence = /** @type {Record<string, unknown>} */ (report.convergence ?? {})
371
+ const totalRounds = String(convergence.totalRounds ?? '')
372
+ const findingsCount = String((/** @type {Array<unknown>} */ (report.findings ?? [])).length)
373
+ const firstFinding = /** @type {Array<Record<string, unknown>>} */ (report.findings ?? [])[0]
374
+ const firstSummary = firstFinding ? String(firstFinding.summary ?? '') : ''
375
+ const mode = String(report.mode ?? '')
376
+ // Use mode + totalRounds + findingsCount + first 20 chars of first finding summary
377
+ return `iterate-triage-${mode}-${totalRounds}-${findingsCount}-${firstSummary.slice(0, 20)}`
378
+ }
379
+
380
+ // ─── Known-intentional YAML builder ──────────────────────────────────────────
381
+
382
+ /**
383
+ * Convert triage entries with verdict 'ignore' to a YAML-compatible text
384
+ * snippet for known_intentional entries.
385
+ *
386
+ * @param {Array<{ file: string, line?: number, dimension: string, reason: string }>} entries
387
+ * @returns {string}
388
+ */
389
+ export function toKnownIntentionalYaml(entries) {
390
+ if (!entries || entries.length === 0) return ''
391
+
392
+ const lines = ['known_intentional:']
393
+ for (const e of entries) {
394
+ lines.push(` - file: ${JSON.stringify(e.file)}`)
395
+ if (e.line !== undefined && e.line > 0) {
396
+ lines.push(` line: ${e.line}`)
397
+ }
398
+ lines.push(` dimension: ${JSON.stringify(e.dimension)}`)
399
+ lines.push(` reason: ${JSON.stringify(e.reason)}`)
400
+ }
401
+ return lines.join('\n')
402
+ }
403
+
404
+ /**
405
+ * Build a text instruction that the user can paste to the model to trigger
406
+ * `iterate_triage` tool call. Works even if the user hasn't yet configured
407
+ * an `iterate_triage` tool — the instruction tells the model what to do.
408
+ *
409
+ * @param {Array<{ file: string, line?: number, dimension: string, reason: string }>} entries
410
+ * @returns {string}
411
+ */
412
+ export function buildApplyInstruction(entries) {
413
+ if (!entries || entries.length === 0) return ''
414
+
415
+ const payload = JSON.stringify(
416
+ {
417
+ operation: 'apply',
418
+ entries: entries.map((e) => ({
419
+ file: e.file,
420
+ ...(e.line !== undefined ? { line: e.line } : {}),
421
+ dimension: e.dimension,
422
+ reason: e.reason,
423
+ })),
424
+ },
425
+ null,
426
+ 2,
427
+ )
428
+
429
+ return (
430
+ `Please call \`iterate_triage\` with the following payload to apply the triage verdicts:\n\n` +
431
+ `\`\`\`json\n${payload}\n\`\`\``
432
+ )
433
+ }
434
+
435
+ /**
436
+ * Collect ignored entries from triage state + findings, returning the
437
+ * structured data ready for `iterate_triage` tool call.
438
+ *
439
+ * @param {Record<string, 'keep' | 'skip' | 'ignore'>} triageState
440
+ * @param {Array<Record<string, unknown>>} findings
441
+ * @returns {Array<{ file: string, line?: number, dimension: string, reason: string }>}
442
+ */
443
+ export function collectIgnoredEntries(triageState, findings) {
444
+ const entries = []
445
+ for (const [idx, verdict] of Object.entries(triageState)) {
446
+ if (verdict !== 'ignore') continue
447
+ const finding = findings[Number(idx)]
448
+ if (!finding) continue
449
+ entries.push({
450
+ file: String(finding.file ?? ''),
451
+ ...(typeof finding.line === 'number' && finding.line > 0
452
+ ? { line: finding.line }
453
+ : {}),
454
+ dimension: String(finding.dimension ?? ''),
455
+ reason: String(finding.summary ?? ''),
456
+ })
457
+ }
458
+ return entries
459
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iterate-plugin",
3
- "version": "2.3.7",
3
+ "version": "2.4.0",
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",
@@ -26,10 +26,17 @@
26
26
  "dsh": {
27
27
  "bundle": {
28
28
  "patch": "./cordis.patch.yml"
29
+ },
30
+ "client": {
31
+ "platform": "web",
32
+ "inject": [
33
+ "@deepseek-ai/dsh-client-connection"
34
+ ]
29
35
  }
30
36
  },
31
37
  "files": [
32
38
  "src",
39
+ "lib",
33
40
  "cordis.patch.yml",
34
41
  "README.md",
35
42
  "LICENSE"
@@ -38,7 +45,8 @@
38
45
  ".": {
39
46
  "types": "./src/index.ts",
40
47
  "default": "./src/index.ts"
41
- }
48
+ },
49
+ "./client": "./lib/client.js"
42
50
  },
43
51
  "scripts": {
44
52
  "typecheck": "tsc --noEmit",
@@ -48,11 +56,10 @@
48
56
  "dependencies": {
49
57
  "@deepseek-ai/cordis": "4.0.1",
50
58
  "@deepseek-ai/dsh-tools": "0.1.0-rc.6",
51
- "@deepseek-ai/dsh-workflow": "0.1.0-rc.6",
52
- "@deepseek-ai/schemastery": "3.18.1",
53
59
  "js-yaml": "4.3.1"
54
60
  },
55
61
  "devDependencies": {
62
+ "@deepseek-ai/dsh-session": "0.1.0-rc.6",
56
63
  "@types/js-yaml": "4.0.9",
57
64
  "@types/node": "22.15.0",
58
65
  "tsx": "4.20.3",
package/src/index.ts CHANGED
@@ -2,11 +2,11 @@
2
2
  * iterate-plugin — dsh plugin for the iterate autonomous closed-loop workflow
3
3
  *
4
4
  * Architecture:
5
- * - The plugin registers 4 tools (config, validate, decision-log, context)
5
+ * - The plugin registers 6 tools (config, validate, decision-log, context, review, triage)
6
6
  * - The plugin injects a system prompt section teaching the iterate workflow pattern
7
7
  * - The model (prompted by the skill) writes a workflow script using dsh's `workflow` tool
8
8
  * - The workflow script uses `agent()` / `parallel()` / `phase()` / `log()` to orchestrate
9
- * - Subagents use the 4 tools to do real work (read config, run validation, log decisions)
9
+ * - Subagents use the 6 tools to do real work (read config, run validation, log decisions, review, triage)
10
10
  *
11
11
  * Tool invocation model:
12
12
  * - Workflow script CANNOT call tools directly (sandboxed vm, no Node API)
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * Key files:
18
18
  * - src/index.ts — Plugin entry: register tools + inject skill prompt
19
- * - src/tools/ — 4 tool implementations
19
+ * - src/tools/ — 6 tool implementations + meta-review/review engines
20
20
  * - src/config-loader.ts — YAML config loading
21
21
  * - src/types.ts — Shared types
22
22
  */
@@ -27,18 +27,20 @@ import { registerValidateTool } from './tools/validate.ts'
27
27
  import { registerDecisionLogTool } from './tools/decision-log.ts'
28
28
  import { registerContextTool } from './tools/context.ts'
29
29
  import { registerReviewTool } from './tools/review.ts'
30
+ import { registerTriageTool } from './tools/triage.ts'
30
31
  import { ITERATE_SKILL_PROMPT } from './skill-prompt.ts'
31
32
 
32
33
  export const name = 'iterate-plugin'
33
34
  export const inject = ['tools', 'systemPrompt']
34
35
 
35
36
  export function apply(ctx: Context): void {
36
- // 1. Register the 5 core tools
37
+ // 1. Register the 6 core tools
37
38
  registerConfigTool(ctx)
38
39
  registerValidateTool(ctx)
39
40
  registerDecisionLogTool(ctx)
40
41
  registerContextTool(ctx)
41
42
  registerReviewTool(ctx)
43
+ registerTriageTool(ctx)
42
44
 
43
45
  // 2. Inject the iterate skill prompt as a system prompt section
44
46
  // This teaches the model how to write iterate workflow scripts using the tools.
package/src/review.ts CHANGED
@@ -344,21 +344,28 @@ export function buildReviewPlan(input: {
344
344
  maxReviewRounds: number
345
345
  knownIntentional: KnownIntentional[]
346
346
  } {
347
+ // Defensive reads: a malformed config (e.g. `dimensions` as a non-array, or
348
+ // `review`/`atomic` missing) must degrade to sane defaults instead of
349
+ // throwing an uncaught TypeError inside the tool's `execute`.
347
350
  const language = input.config.language === 'zh' ? 'Chinese (中文)' : 'English'
351
+ const goal = input.config.goal ?? ''
352
+ const scope = input.config.review?.scope ?? 'full'
353
+ const dimensions = Array.isArray(input.config.dimensions) ? input.config.dimensions : []
354
+ const maxLines = input.config.atomic?.max_lines ?? 20
348
355
  return {
349
356
  mode: input.mode,
350
- goal: input.config.goal,
351
- scope: input.config.review.scope,
352
- dimensions: input.config.dimensions.map((d) => ({
357
+ goal,
358
+ scope,
359
+ dimensions: dimensions.map((d) => ({
353
360
  id: d,
354
361
  reviewerPrompt: reviewerTaskPrompt({
355
362
  dimension: d,
356
- goal: input.config.goal,
357
- scope: input.config.review.scope,
363
+ goal,
364
+ scope,
358
365
  mode: input.mode,
359
366
  alreadyKnown: [],
360
367
  outputLanguage: language,
361
- maxLines: input.config.atomic.max_lines,
368
+ maxLines,
362
369
  }),
363
370
  findingsSchema: findingsSchema(),
364
371
  })),
@@ -82,6 +82,16 @@ export function registerDecisionLogTool(ctx: { tools: { register: (def: ReturnTy
82
82
  description:
83
83
  'Entry type (required for append): round_start, review_result, atomic_fix, ' +
84
84
  'architectural_fix, revert, validation, decision, report.',
85
+ enum: [
86
+ 'round_start',
87
+ 'review_result',
88
+ 'atomic_fix',
89
+ 'architectural_fix',
90
+ 'revert',
91
+ 'validation',
92
+ 'decision',
93
+ 'report',
94
+ ],
85
95
  },
86
96
  round: {
87
97
  type: 'integer',