iterate-plugin 2.7.1 → 2.7.3

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/src/index.ts CHANGED
@@ -2,11 +2,13 @@
2
2
  * iterate-plugin — dsh plugin for the iterate autonomous closed-loop workflow
3
3
  *
4
4
  * Architecture:
5
- * - The plugin registers 6 tools (config, validate, decision-log, context, review, triage)
5
+ * - The plugin registers 13 tools (config, validate, decision-log, context, review,
6
+ * triage, fix, diff, rollback, checkpoint, status, history, prune)
6
7
  * - The plugin injects a system prompt section teaching the iterate workflow pattern
7
8
  * - The model (prompted by the skill) writes a workflow script using dsh's `workflow` tool
8
9
  * - The workflow script uses `agent()` / `parallel()` / `phase()` / `log()` to orchestrate
9
- * - Subagents use the 6 tools to do real work (read config, run validation, log decisions, review, triage)
10
+ * - Subagents use the 13 tools to do real work (read config, run validation, log decisions,
11
+ * review, triage, apply/rollback/fixing, checkpoint, status, history, prune)
10
12
  *
11
13
  * Tool invocation model:
12
14
  * - Workflow script CANNOT call tools directly (sandboxed vm, no Node API)
@@ -16,7 +18,7 @@
16
18
  *
17
19
  * Key files:
18
20
  * - src/index.ts — Plugin entry: register tools + inject skill prompt
19
- * - src/tools/ — 6 tool implementations + meta-review/review engines
21
+ * - src/tools/ — 13 tool implementations + meta-review/review engines
20
22
  * - src/config-loader.ts — YAML config loading
21
23
  * - src/types.ts — Shared types
22
24
  */
@@ -38,7 +40,7 @@ export const name = 'iterate-plugin'
38
40
  export const inject = ['tools', 'systemPrompt']
39
41
 
40
42
  export function apply(ctx: Context): void {
41
- // 1. Register the 11 tools
43
+ // 1. Register the 13 tools
42
44
  registerConfigTool(ctx)
43
45
  registerValidateTool(ctx)
44
46
  registerDecisionLogTool(ctx)
package/src/review.ts CHANGED
@@ -218,6 +218,8 @@ export function buildReviewReport(input: {
218
218
  maxReviewRounds: number
219
219
  rounds: ReviewRound[]
220
220
  knownIntentional?: KnownIntentional[]
221
+ /** Number of atomic fixes applied so far. Normal mode only; omitted in dry-run. */
222
+ fixedCount?: number
221
223
  }): ReviewReport {
222
224
  // 1. Filter known-intentional per round (before cross-round dedupe).
223
225
  const filteredRounds = input.rounds.map((r) => ({
@@ -246,6 +248,12 @@ export function buildReviewReport(input: {
246
248
  lastRound > 0 ? (findingsByRound[lastRound - 1] ?? 0) : 0
247
249
  const converged = filteredRounds.length > 0 && lastRoundCount === 0
248
250
 
251
+ // Attach the normal-mode fix count to the summary (dry-run leaves it absent).
252
+ const computed = summarize(sorted)
253
+ if (input.mode === 'normal' && typeof input.fixedCount === 'number' && Number.isInteger(input.fixedCount)) {
254
+ computed.fixedCount = input.fixedCount
255
+ }
256
+
249
257
  return {
250
258
  mode: input.mode,
251
259
  goal: input.goal,
@@ -264,7 +272,7 @@ export function buildReviewReport(input: {
264
272
  ? 'converged'
265
273
  : 'max_rounds_reached',
266
274
  },
267
- summary: summarize(sorted),
275
+ summary: computed,
268
276
  }
269
277
  }
270
278
 
@@ -14,13 +14,15 @@ 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. Purely computational.
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.
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
21
21
  - \`iterate_rollback\` — revert a fix by id: restore the file from its backup, remove the fix from the registry, log a \`revert\` entry. Use when a round's validation fails
22
22
  - \`iterate_checkpoint\` — save / load / clear an iteration checkpoint (\`.iterate/checkpoint.json\`) so a long run can resume where it left off
23
23
  - \`iterate_status\` — summarize the current run: mode, round, fixes applied, architectural remaining, decision-log size, checkpoint presence
24
+ - \`iterate_history\` — inspect the runtime state in detail: decision-log entries and applied fixes (optionally scoped to a round or a fixed file)
25
+ - \`iterate_prune\` — remove stale runtime artifacts (\`.iterate/\` entries). Defaults to a read-only dry-run that reports what WOULD be removed; pass \`dryRun:false\` to actually prune.
24
26
 
25
27
  ### When to use
26
28
  When the user asks to review or iterate on the project (e.g. "review this project", "iterate on error handling", "check the codebase for issues", "dry-run review", "反复审查"), run an iterate **workflow** by calling the \`workflow\` tool.
@@ -176,8 +178,10 @@ for (let r = startRound; r <= maxRounds; r++) {
176
178
  rounds.push(thisRound)
177
179
 
178
180
  // Deterministic dedupe / known_intentional filter / severity sort for this round.
181
+ // \`fixedCount\` is threaded into the report summary so the client dashboard can
182
+ // show a running "fixes applied" metric for normal mode.
179
183
  const agg = await agent(
180
- 'Call iterate_review({operation:"aggregate", mode:"normal", rounds:' + JSON.stringify([thisRound]) + ', knownIntentional:' + JSON.stringify(knownIntentional) + '}) and return the report JSON.',
184
+ 'Call iterate_review({operation:"aggregate", mode:"normal", rounds:' + JSON.stringify([thisRound]) + ', knownIntentional:' + JSON.stringify(knownIntentional) + ', fixedCount:' + fixedCount + '}) and return the report JSON.',
181
185
  { label: 'review:aggregate:r' + r }
182
186
  )
183
187
  const findings = (agg && agg.report && agg.report.findings) ? agg.report.findings : thisRound.findings
@@ -8,6 +8,36 @@ import type { DecisionLogEntry } from '../types.ts'
8
8
  const LOG_DIR = '.iterate'
9
9
  const LOG_FILE = 'decision-log.jsonl'
10
10
 
11
+ /** All valid DecisionLogEntry `type` values (must stay in sync with Types). */
12
+ const VALID_ENTRY_TYPES = new Set<DecisionLogEntry['type']>([
13
+ 'round_start',
14
+ 'review_result',
15
+ 'atomic_fix',
16
+ 'architectural_fix',
17
+ 'revert',
18
+ 'round_failed',
19
+ 'validation',
20
+ 'decision',
21
+ 'report',
22
+ ])
23
+
24
+ /**
25
+ * Validate a candidate (type, round, data) triple for an append operation.
26
+ * Returns an error string on failure, or null when the entry is well-formed.
27
+ */
28
+ function validateEntryInput(type: unknown, round: unknown, data: unknown): string | null {
29
+ if (typeof type !== 'string' || !VALID_ENTRY_TYPES.has(type as DecisionLogEntry['type'])) {
30
+ return `type must be one of: ${[...VALID_ENTRY_TYPES].join(', ')}.`
31
+ }
32
+ if (typeof round !== 'number' || !Number.isInteger(round) || round < 1) {
33
+ return 'round must be a positive integer.'
34
+ }
35
+ if (data !== undefined && data !== null && typeof data !== 'object') {
36
+ return 'data must be an object (or omitted).'
37
+ }
38
+ return null
39
+ }
40
+
11
41
  /**
12
42
  * Resolve the log file path, creating the directory if needed.
13
43
  */
@@ -81,13 +111,14 @@ export function registerDecisionLogTool(ctx: { tools: { register: (def: ReturnTy
81
111
  type: 'string',
82
112
  description:
83
113
  'Entry type (required for append): round_start, review_result, atomic_fix, ' +
84
- 'architectural_fix, revert, validation, decision, report.',
114
+ 'architectural_fix, revert, round_failed, validation, decision, report.',
85
115
  enum: [
86
116
  'round_start',
87
117
  'review_result',
88
118
  'atomic_fix',
89
119
  'architectural_fix',
90
120
  'revert',
121
+ 'round_failed',
91
122
  'validation',
92
123
  'decision',
93
124
  'report',
@@ -144,18 +175,24 @@ export function registerDecisionLogTool(ctx: { tools: { register: (def: ReturnTy
144
175
  }
145
176
 
146
177
  if (args.operation === 'append') {
147
- if (!args.type || !args.round) {
178
+ const invalid = validateEntryInput(args.type, args.round, args.data)
179
+ if (invalid !== null) {
148
180
  return {
149
181
  operation: 'append',
150
- error: 'type and round are required for append operation.',
182
+ error: invalid,
151
183
  }
152
184
  }
185
+ const type = args.type as DecisionLogEntry['type']
186
+ const round = args.round as number
187
+ const data = args.data === undefined || args.data === null
188
+ ? {}
189
+ : args.data as Record<string, unknown>
153
190
 
154
191
  const entry: DecisionLogEntry = {
155
192
  timestamp: new Date().toISOString(),
156
- round: args.round,
157
- type: args.type as DecisionLogEntry['type'],
158
- data: (args.data as Record<string, unknown>) ?? {},
193
+ round,
194
+ type,
195
+ data,
159
196
  }
160
197
 
161
198
  const result = appendDecisionEntry(projectRoot, entry)
@@ -72,6 +72,12 @@ export function registerReviewTool(ctx: { tools: { register: (def: ReturnType<ty
72
72
  'For `meta-review`: the ReviewReport JSON (as returned by `aggregate`) to audit for ' +
73
73
  'internal consistency and produce the final review report.',
74
74
  },
75
+ fixedCount: {
76
+ type: 'integer',
77
+ description:
78
+ 'For `aggregate` (normal mode only): number of atomic fixes applied so far. ' +
79
+ 'Surfaces a running "fixes applied" metric on the report summary.',
80
+ },
75
81
  path: {
76
82
  type: 'string',
77
83
  description: 'Project root directory (default: current working directory).',
@@ -144,6 +150,7 @@ export function registerReviewTool(ctx: { tools: { register: (def: ReturnType<ty
144
150
  maxReviewRounds,
145
151
  rounds,
146
152
  knownIntentional: args.knownIntentional as KnownIntentional[] | undefined,
153
+ fixedCount: typeof args.fixedCount === 'number' ? args.fixedCount : undefined,
147
154
  })
148
155
  return { operation: 'aggregate', mode, report: report as unknown as JsonValue }
149
156
  }
package/src/types.ts CHANGED
@@ -34,6 +34,7 @@ export interface DecisionLogEntry {
34
34
  | 'validation'
35
35
  | 'decision'
36
36
  | 'report'
37
+ | 'round_failed'
37
38
  data: Record<string, unknown>
38
39
  }
39
40
 
@@ -82,6 +83,8 @@ export interface ReviewReport {
82
83
  medium: number
83
84
  low: number
84
85
  byDimension: Record<string, number>
86
+ /** Number of atomic fixes applied so far (normal mode only; absent in dry-run). */
87
+ fixedCount?: number
85
88
  }
86
89
  }
87
90