iterate-plugin 3.2.1 → 3.3.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.
@@ -59,8 +59,16 @@ export function readDefenseEvents(projectRoot: string): DefenseEventStream {
59
59
  return emptyStream()
60
60
  }
61
61
 
62
- /** Write the defense events stream to disk. */
63
- export function writeDefenseEvents(projectRoot: string, stream: DefenseEventStream): void {
62
+ /**
63
+ * Write the defense events stream to disk.
64
+ * Returns `{ ok: true }` on success or `{ ok: false, error }` when the write
65
+ * fails — a caller must surface the failure instead of reporting success for
66
+ * an event that was never persisted.
67
+ */
68
+ export function writeDefenseEvents(
69
+ projectRoot: string,
70
+ stream: DefenseEventStream,
71
+ ): { ok: true } | { ok: false; error: string } {
64
72
  const dirPath = path.join(projectRoot, '.iterate')
65
73
  const filePath = path.join(dirPath, DEFENSE_EVENTS_FILE)
66
74
 
@@ -69,9 +77,10 @@ export function writeDefenseEvents(projectRoot: string, stream: DefenseEventStre
69
77
  fs.mkdirSync(dirPath, { recursive: true })
70
78
  }
71
79
  fs.writeFileSync(filePath, JSON.stringify(stream, null, 2), 'utf-8')
72
- } catch {
73
- // Silently fail - defense events are not critical
80
+ } catch (err) {
81
+ return { ok: false, error: `unable to write ${filePath}: ${String(err)}` }
74
82
  }
83
+ return { ok: true }
75
84
  }
76
85
 
77
86
  /** Add a defense event to the stream. */
@@ -195,7 +195,10 @@ export function registerExperienceBankTool(ctx: { tools: { register: (def: Retur
195
195
  }
196
196
  const bank = readExperienceBank(projectRoot)
197
197
  const { bank: next, added, entryId } = upsertExperience(bank, normalizeExperienceInput(raw as Record<string, unknown>))
198
- writeExperienceBank(projectRoot, next)
198
+ const write = writeExperienceBank(projectRoot, next)
199
+ if (!write.ok) {
200
+ return { ok: false, kind: 'experience', operation: 'add', error: write.error }
201
+ }
199
202
  const entry = next.entries.find((e) => e.id === entryId)
200
203
  return {
201
204
  ok: true,
@@ -35,8 +35,16 @@ export function readExperienceBank(projectRoot: string): ExperienceBank {
35
35
  return emptyBank()
36
36
  }
37
37
 
38
- /** Write the experience bank to disk. */
39
- export function writeExperienceBank(projectRoot: string, bank: ExperienceBank): void {
38
+ /**
39
+ * Write the experience bank to disk.
40
+ * Returns `{ ok: true }` on success or `{ ok: false, error }` when the write
41
+ * fails — a caller must surface the failure instead of reporting success for
42
+ * an entry that was never persisted.
43
+ */
44
+ export function writeExperienceBank(
45
+ projectRoot: string,
46
+ bank: ExperienceBank,
47
+ ): { ok: true } | { ok: false; error: string } {
40
48
  const dirPath = path.join(projectRoot, '.iterate')
41
49
  const filePath = path.join(dirPath, EXPERIENCE_FILE)
42
50
 
@@ -45,9 +53,10 @@ export function writeExperienceBank(projectRoot: string, bank: ExperienceBank):
45
53
  fs.mkdirSync(dirPath, { recursive: true })
46
54
  }
47
55
  fs.writeFileSync(filePath, JSON.stringify(bank, null, 2), 'utf-8')
48
- } catch {
49
- // Silently fail - experience bank is not critical
56
+ } catch (err) {
57
+ return { ok: false, error: `unable to write ${filePath}: ${String(err)}` }
50
58
  }
59
+ return { ok: true }
51
60
  }
52
61
 
53
62
  /** Search experience entries by query string. */
@@ -171,7 +171,10 @@ export function registerQualityGateTool(ctx: { tools: { register: (def: ReturnTy
171
171
  findingsByRound,
172
172
  fixedByDimension,
173
173
  })
174
- writeQualityGate(projectRoot, snapshot)
174
+ const write = writeQualityGate(projectRoot, snapshot)
175
+ if (!write.ok) {
176
+ return { ok: false, kind: 'quality_gate', operation: 'compute', error: write.error }
177
+ }
175
178
  return {
176
179
  ok: true,
177
180
  kind: 'quality_gate',
@@ -46,8 +46,16 @@ export function readQualityGate(projectRoot: string): QualityGateSnapshot {
46
46
  return emptySnapshot()
47
47
  }
48
48
 
49
- /** Write the quality gate snapshot to disk. */
50
- export function writeQualityGate(projectRoot: string, snapshot: QualityGateSnapshot): void {
49
+ /**
50
+ * Write the quality gate snapshot to disk.
51
+ * Returns `{ ok: true }` on success or `{ ok: false, error }` when the write
52
+ * fails — a caller must surface the failure instead of reporting success for
53
+ * a snapshot that was never persisted.
54
+ */
55
+ export function writeQualityGate(
56
+ projectRoot: string,
57
+ snapshot: QualityGateSnapshot,
58
+ ): { ok: true } | { ok: false; error: string } {
51
59
  const dirPath = path.join(projectRoot, '.iterate')
52
60
  const filePath = path.join(dirPath, QUALITY_GATE_FILE)
53
61
 
@@ -56,9 +64,10 @@ export function writeQualityGate(projectRoot: string, snapshot: QualityGateSnaps
56
64
  fs.mkdirSync(dirPath, { recursive: true })
57
65
  }
58
66
  fs.writeFileSync(filePath, JSON.stringify(snapshot, null, 2), 'utf-8')
59
- } catch {
60
- // Silently fail - quality gate is not critical
67
+ } catch (err) {
68
+ return { ok: false, error: `unable to write ${filePath}: ${String(err)}` }
61
69
  }
70
+ return { ok: true }
62
71
  }
63
72
 
64
73
  /**
@@ -136,6 +136,11 @@ export function registerTranscriptTool(ctx: {
136
136
  description: 'For `capture`: run mode ("dry-run" | "normal"). Default dry-run.',
137
137
  enum: ['dry-run', 'normal'],
138
138
  },
139
+ taskMode: {
140
+ type: 'string',
141
+ description: 'For `capture`: harness execution mode ("code" | "iterate"). Default derives from the review loop (iterate).',
142
+ enum: ['code', 'iterate'],
143
+ },
139
144
  goal: { type: 'string', description: 'For `capture`: run goal.' },
140
145
  maxRounds: { type: 'integer', description: 'For `capture`: round cap.' },
141
146
  roundsExecuted: { type: 'integer', description: 'For `capture`: number of rounds actually executed.' },
@@ -229,10 +234,11 @@ export function registerTranscriptTool(ctx: {
229
234
 
230
235
  // capture
231
236
  const mode = args.mode === 'normal' ? 'normal' : 'dry-run'
237
+ const taskMode = args.taskMode === 'code' || args.taskMode === 'iterate' ? args.taskMode : undefined
232
238
  const goal = typeof args.goal === 'string' ? args.goal : ''
233
239
  const maxRounds =
234
240
  typeof args.maxRounds === 'number' ? Math.floor(args.maxRounds) : 0
235
- const builder = new ReviewTranscriptBuilder({ project: projectRoot, mode, approval, goal, maxRounds })
241
+ const builder = new ReviewTranscriptBuilder({ project: projectRoot, mode, taskMode, approval, goal, maxRounds })
236
242
  const report = args.report as Record<string, unknown> | null | undefined
237
243
  const reportFindings: unknown =
238
244
  report && typeof report === 'object' && Array.isArray(report.findings)
@@ -300,6 +306,7 @@ function rehydrateBuilder(manifest: TranscriptManifest, approval: 'ask' | 'deny'
300
306
  const builder = new ReviewTranscriptBuilder({
301
307
  project: manifest.project,
302
308
  mode: manifest.mode ?? null,
309
+ taskMode: manifest.taskMode ?? null,
303
310
  approval,
304
311
  goal: manifest.goal,
305
312
  maxRounds: manifest.maxRounds,
package/src/transcript.ts CHANGED
@@ -126,6 +126,7 @@ function mergeReportIntoThread(
126
126
  export class ReviewTranscriptBuilder {
127
127
  private readonly project: string
128
128
  private readonly mode: 'dry-run' | 'normal' | null
129
+ private readonly taskMode: 'code' | 'iterate' | null
129
130
  private readonly approval: 'ask' | 'deny' | 'allow'
130
131
  private goal = ''
131
132
  private readonly phases: string[] = []
@@ -144,6 +145,7 @@ export class ReviewTranscriptBuilder {
144
145
  constructor(input: {
145
146
  project: string
146
147
  mode?: 'dry-run' | 'normal' | null
148
+ taskMode?: 'code' | 'iterate' | null
147
149
  approval?: 'ask' | 'deny' | 'allow'
148
150
  goal?: string
149
151
  maxRounds?: number
@@ -152,6 +154,14 @@ export class ReviewTranscriptBuilder {
152
154
  this.project = input.project || ''
153
155
  this.mode =
154
156
  input.mode === 'dry-run' || input.mode === 'normal' ? input.mode : null
157
+ // v3.0: task_mode indicator. An explicit valid value wins; otherwise a
158
+ // run that exercises the review loop (any mode) defaults to "iterate".
159
+ this.taskMode =
160
+ input.taskMode === 'code' || input.taskMode === 'iterate'
161
+ ? input.taskMode
162
+ : input.mode !== null && input.mode !== undefined
163
+ ? 'iterate'
164
+ : null
155
165
  this.approval =
156
166
  input.approval === 'ask' || input.approval === 'deny' || input.approval === 'allow'
157
167
  ? input.approval
@@ -393,6 +403,7 @@ export class ReviewTranscriptBuilder {
393
403
  updatedAt: this.updatedAt,
394
404
  active: this.active,
395
405
  mode: this.mode,
406
+ taskMode: this.taskMode,
396
407
  goal: this.goal,
397
408
  phases: this.phases,
398
409
  round: this.round,