@syntesseraai/opencode-feature-factory 0.9.0 → 0.10.1

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/README.md CHANGED
@@ -47,6 +47,7 @@ All shipped Feature Factory agent manifests under `agents/*.md` include a `color
47
47
  | `ff-research` | `#6366f1` |
48
48
 
49
49
  These colors are intentionally unique to avoid collisions in OpenCode agent UIs and logs.
50
+
50
51
  ## Pipeline Entrypoint
51
52
 
52
53
  - Invoke `/pipeline/start <requirements-brief>` directly from any agent (e.g. `@building`).
@@ -60,11 +61,11 @@ These colors are intentionally unique to avoid collisions in OpenCode agent UIs
60
61
 
61
62
  The plugin exposes three MCP tools via the `feature-factory` agent:
62
63
 
63
- | Tool | Description |
64
- |------|-------------|
65
- | `ff_pipeline` | Full multi-model pipeline: planning → build → review → documentation. Uses hardcoded per-role model defaults (see Model Routing below). |
66
- | `ff_mini_loop` | Lightweight build → review → documentation loop. **Does not hardcode model defaults** — all roles inherit the current session model when omitted. |
67
- | `ff_list_models` | Read-only discovery tool. Queries the OpenCode SDK to list all available providers, models, capability badges, connected status, and defaults. |
64
+ | Tool | Description |
65
+ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
66
+ | `ff_pipeline` | Full multi-model pipeline: planning → build → review → documentation. Uses hardcoded per-role model defaults (see Model Routing below). |
67
+ | `ff_mini_loop` | Lightweight build → review → documentation loop. **Does not hardcode model defaults** — all roles inherit the current session model when omitted. |
68
+ | `ff_list_models` | Read-only discovery tool. Queries the OpenCode SDK to list all available providers, models, capability badges, connected status, and defaults. |
68
69
 
69
70
  ### Mini-Loop Model Inheritance
70
71
 
@@ -110,6 +111,45 @@ Models are declared in each command's frontmatter (`model:` field). Multi-model
110
111
  - Documentation approval: documentation reviewer verdict `APPROVED` with zero unresolved documentation issues.
111
112
  - Planning loop confirmation: after 5 unsuccessful planning iterations, pipeline asks user whether to continue.
112
113
 
114
+ ## Async Progress Notifications
115
+
116
+ Both `ff_pipeline` and `ff_mini_loop` tools run asynchronously with real-time progress notifications:
117
+
118
+ - **Immediate return**: Tools return instantly with a brief acknowledgment (e.g. `Pipeline started for: <summary>`), so the LLM can continue the conversation.
119
+ - **Background orchestration**: The full pipeline or mini-loop runs in a detached `Promise`. All child session orchestration (fan-out, gates, loops) remains identical — it just executes after the tool returns.
120
+ - **Progress updates via `promptAsync(noReply: true)`**: After each major phase completes, a structured notification is injected into the parent session as a visible chat message. These appear in the OpenCode TUI without triggering an LLM turn.
121
+ - **Phase-by-phase visibility**: Users see updates for planning, building, each review iteration gate decision, each documentation iteration, and the final completion report.
122
+ - **Error notifications**: If the background orchestration throws, a `# Pipeline: Error` or `# Mini-Loop: Error` notification is sent with the last phase and error message.
123
+ - **`context.metadata()` retained**: All existing metadata calls remain in place for future-proofing (when OpenCode's TUI renders tool metadata natively).
124
+
125
+ ### Notification Format
126
+
127
+ Pipeline notifications use plain-text markdown headers with phase START/END bracketing and per-iteration gate details:
128
+
129
+ ```
130
+ # Pipeline: Reviewing — Iteration 2/10
131
+
132
+ Status: APPROVED
133
+ Confidence: 97%
134
+ Unresolved Issues: 0
135
+ Duration: 45.3s
136
+ Feedback: N/A
137
+ ```
138
+
139
+ Mini-loop notifications follow the same pattern:
140
+
141
+ ```
142
+ # Mini-Loop: Building — Iteration 1/10
143
+
144
+ Status: REWORK
145
+ Confidence: 82%
146
+ Unresolved Issues: 2
147
+ Duration: 23.1s
148
+ Feedback: Fix type errors in handler.ts
149
+ ```
150
+
151
+ Final reports use `# Pipeline: Complete` or `# Mini-Loop: Complete` headers containing the full markdown report. Errors use `# Pipeline: Error` or `# Mini-Loop: Error`.
152
+
113
153
  ## Related Docs
114
154
 
115
155
  - `docs/PIPELINE_ORCHESTRATION.md`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Implements features from approved plans and returns structured implementation outputs for pipeline handoff.
3
- mode: subagent
3
+ mode: primary
4
4
  color: '#22c55e'
5
5
  tools:
6
6
  read: true
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Documentation implementation specialist for pipeline documentation stage.
3
- mode: subagent
3
+ mode: primary
4
4
  color: '#f97316'
5
5
  tools:
6
6
  read: true
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Creates implementation plans and planning gates for pipeline and ad-hoc work. Uses result-based handoff instead of file artifacts.
3
- mode: subagent
3
+ mode: primary
4
4
  color: '#3b82f6'
5
5
  tools:
6
6
  read: true
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Unified validation agent for code and documentation. Performs acceptance, quality, security, and architecture review with context-driven scope.
3
- mode: subagent
3
+ mode: primary
4
4
  color: '#8b5cf6'
5
5
  tools:
6
6
  read: true
@@ -3,6 +3,10 @@
3
3
  *
4
4
  * A simpler two-stage workflow (no multi-model fan-out for planning).
5
5
  * Implements a build/review loop, then a documentation loop.
6
+ *
7
+ * The tool returns immediately with an acknowledgment. Orchestration
8
+ * runs asynchronously in the background, sending progress notifications
9
+ * to the parent session via `promptAsync(noReply: true)`.
6
10
  */
7
11
  import { type Client } from '../workflow/orchestrator.js';
8
12
  export declare function createMiniLoopTool(client: Client): {
@@ -3,9 +3,13 @@
3
3
  *
4
4
  * A simpler two-stage workflow (no multi-model fan-out for planning).
5
5
  * Implements a build/review loop, then a documentation loop.
6
+ *
7
+ * The tool returns immediately with an acknowledgment. Orchestration
8
+ * runs asynchronously in the background, sending progress notifications
9
+ * to the parent session via `promptAsync(noReply: true)`.
6
10
  */
7
11
  import { tool } from '@opencode-ai/plugin/tool';
8
- import { promptSession, evaluateMiniLoopImplGate, evaluateMiniLoopDocGate, parseModelString, } from '../workflow/orchestrator.js';
12
+ import { promptSession, notifyParent, evaluateMiniLoopImplGate, evaluateMiniLoopDocGate, parseModelString, } from '../workflow/orchestrator.js';
9
13
  import { miniBuildPrompt, miniReviewPrompt, documentPrompt, docReviewPrompt } from './prompts.js';
10
14
  import { parseMiniReview, parseDocReview } from './parsers.js';
11
15
  // ---------------------------------------------------------------------------
@@ -39,8 +43,8 @@ export function createMiniLoopTool(client) {
39
43
  .describe('provider/model for documentation review. When omitted, inherits the session model.'),
40
44
  },
41
45
  async execute(args, context) {
42
- const totalStartMs = Date.now();
43
46
  const sessionId = context.sessionID;
47
+ const agent = context.agent;
44
48
  const { requirements } = args;
45
49
  // Resolve models — use provided overrides or undefined (inherit session model)
46
50
  const buildModel = args.build_model
@@ -57,105 +61,139 @@ export function createMiniLoopTool(client) {
57
61
  const docReviewModel = args.doc_review_model
58
62
  ? parseModelString(args.doc_review_model)
59
63
  : undefined;
60
- const report = [];
61
- const addReport = (phase, msg) => {
62
- report.push(`## ${phase}\n${msg}`);
63
- };
64
- // ===================================================================
65
- // PHASE 1: IMPLEMENTATION LOOP (build → review → gate, up to 10)
66
- // ===================================================================
67
- const implementationStartMs = Date.now();
68
- const implementationIterationDetails = [];
69
- let implGate = { decision: 'REWORK', feedback: requirements };
70
- let lastImplRaw = '';
71
- for (let implIter = 0; implIter < 10 && implGate.decision === 'REWORK'; implIter++) {
72
- const iteration = implIter + 1;
73
- const buildTitle = `ff-mini-build-${iteration}`;
74
- const reviewTitle = `ff-mini-review-${iteration}`;
75
- const buildInput = implIter === 0
76
- ? requirements
77
- : `${requirements}\n\nPrevious review feedback:\n${implGate.feedback}`;
78
- context.metadata({
79
- title: `⏳ Building (iteration ${iteration}/10)...`,
80
- metadata: {
81
- phase: 'implementation',
82
- step: 'build',
83
- iteration,
84
- maxIterations: 10,
85
- },
86
- });
87
- // Build
88
- const buildStartMs = Date.now();
89
- lastImplRaw = await promptSession(client, sessionId, miniBuildPrompt(buildInput, implIter > 0 ? implGate.feedback : undefined), { model: buildModel, agent: 'building', title: buildTitle });
90
- const buildEndMs = Date.now();
91
- context.metadata({
92
- title: `⏳ Reviewing (iteration ${iteration}/10)...`,
93
- metadata: {
94
- phase: 'implementation',
95
- step: 'review',
96
- iteration,
97
- maxIterations: 10,
98
- },
99
- });
100
- // Review
101
- const reviewStartMs = Date.now();
102
- const reviewRaw = await promptSession(client, sessionId, miniReviewPrompt(lastImplRaw), {
103
- model: reviewModel,
104
- agent: 'reviewing',
105
- title: reviewTitle,
106
- });
107
- const reviewEndMs = Date.now();
108
- const review = parseMiniReview(reviewRaw);
109
- // Gate (deterministic)
110
- implGate = evaluateMiniLoopImplGate(review, iteration);
111
- if (implGate.decision === 'APPROVED') {
64
+ // Fire-and-forget: run orchestration in background
65
+ let lastPhase = 'init';
66
+ const asyncOrchestration = async () => {
67
+ const totalStartMs = Date.now();
68
+ const report = [];
69
+ const addReport = (phase, msg) => {
70
+ report.push(`## ${phase}\n${msg}`);
71
+ };
72
+ // Notify helper bound to this session
73
+ const notify = (message) => notifyParent(client, sessionId, agent, message);
74
+ // ===================================================================
75
+ // PHASE 1: IMPLEMENTATION LOOP (build review gate, up to 10)
76
+ // ===================================================================
77
+ lastPhase = 'Implementation';
78
+ const implementationStartMs = Date.now();
79
+ const implementationIterationDetails = [];
80
+ let implGate = { decision: 'REWORK', feedback: requirements };
81
+ let lastImplRaw = '';
82
+ // Phase start notification
83
+ await notify(`# Mini-Loop: Building started\n\nStarting implementation phase...\n`);
84
+ for (let implIter = 0; implIter < 10 && implGate.decision === 'REWORK'; implIter++) {
85
+ const iteration = implIter + 1;
86
+ const buildTitle = `ff-mini-build-${iteration}`;
87
+ const reviewTitle = `ff-mini-review-${iteration}`;
88
+ const buildInput = implIter === 0
89
+ ? requirements
90
+ : `${requirements}\n\nPrevious review feedback:\n${implGate.feedback}`;
112
91
  context.metadata({
113
- title: `✅ Implementation APPROVED (confidence: ${review.confidence}, iteration: ${iteration})`,
92
+ title: `⏳ Building (iteration ${iteration}/10)...`,
114
93
  metadata: {
115
94
  phase: 'implementation',
116
- step: 'gate',
117
- decision: implGate.decision,
118
- confidence: review.confidence,
95
+ step: 'build',
119
96
  iteration,
120
97
  maxIterations: 10,
121
98
  },
122
99
  });
123
- }
124
- else if (implGate.decision === 'ESCALATE') {
100
+ // Build
101
+ const buildStartMs = Date.now();
102
+ lastImplRaw = await promptSession(client, sessionId, miniBuildPrompt(buildInput, implIter > 0 ? implGate.feedback : undefined), { model: buildModel, agent: 'building', title: buildTitle });
103
+ const buildEndMs = Date.now();
125
104
  context.metadata({
126
- title: '⚠️ Implementation ESCALATED',
105
+ title: `⏳ Reviewing (iteration ${iteration}/10)...`,
127
106
  metadata: {
128
107
  phase: 'implementation',
129
- step: 'gate',
130
- decision: implGate.decision,
131
- confidence: review.confidence,
108
+ step: 'review',
132
109
  iteration,
133
110
  maxIterations: 10,
134
111
  },
135
112
  });
136
- }
137
- else {
138
- context.metadata({
139
- title: `🔄 Rework required (confidence: ${review.confidence}, iteration: ${iteration}/10)`,
140
- metadata: {
141
- phase: 'implementation',
142
- step: 'gate',
143
- decision: implGate.decision,
144
- confidence: review.confidence,
145
- iteration,
146
- maxIterations: 10,
147
- },
113
+ // Review
114
+ const reviewStartMs = Date.now();
115
+ const reviewRaw = await promptSession(client, sessionId, miniReviewPrompt(lastImplRaw), {
116
+ model: reviewModel,
117
+ agent: 'reviewing',
118
+ title: reviewTitle,
148
119
  });
120
+ const reviewEndMs = Date.now();
121
+ const review = parseMiniReview(reviewRaw);
122
+ // Gate (deterministic)
123
+ implGate = evaluateMiniLoopImplGate(review, iteration);
124
+ if (implGate.decision === 'APPROVED') {
125
+ context.metadata({
126
+ title: `✅ Implementation APPROVED (confidence: ${review.confidence}, iteration: ${iteration})`,
127
+ metadata: {
128
+ phase: 'implementation',
129
+ step: 'gate',
130
+ decision: implGate.decision,
131
+ confidence: review.confidence,
132
+ iteration,
133
+ maxIterations: 10,
134
+ },
135
+ });
136
+ }
137
+ else if (implGate.decision === 'ESCALATE') {
138
+ context.metadata({
139
+ title: '⚠️ Implementation ESCALATED',
140
+ metadata: {
141
+ phase: 'implementation',
142
+ step: 'gate',
143
+ decision: implGate.decision,
144
+ confidence: review.confidence,
145
+ iteration,
146
+ maxIterations: 10,
147
+ },
148
+ });
149
+ }
150
+ else {
151
+ context.metadata({
152
+ title: `🔄 Rework required (confidence: ${review.confidence}, iteration: ${iteration}/10)`,
153
+ metadata: {
154
+ phase: 'implementation',
155
+ step: 'gate',
156
+ decision: implGate.decision,
157
+ confidence: review.confidence,
158
+ iteration,
159
+ maxIterations: 10,
160
+ },
161
+ });
162
+ }
163
+ const feedback = review.reworkInstructions || implGate.feedback || review.raw;
164
+ implementationIterationDetails.push(`### Iteration ${iteration}\n` +
165
+ `- **Build**: ${buildTitle} (${formatElapsed(buildStartMs, buildEndMs)})\n` +
166
+ `- **Review**: ${reviewTitle} (${formatElapsed(reviewStartMs, reviewEndMs)})\n` +
167
+ `- **Gate**: ${implGate.decision} (confidence: ${review.confidence}, change requested: ${review.changeRequested ? 'yes' : 'no'}, unresolved: ${review.unresolvedIssues})\n` +
168
+ `- **Feedback**: ${feedback}`);
169
+ // Notify each implementation iteration gate decision
170
+ await notify(`# Mini-Loop: Building — Iteration ${iteration}/10\n\nStatus: ${implGate.decision}\nConfidence: ${review.confidence}%\nUnresolved Issues: ${review.unresolvedIssues}\nDuration: ${formatElapsed(implementationStartMs, Date.now())}\nFeedback: ${feedback}\n`);
171
+ if (implGate.decision === 'ESCALATE') {
172
+ const implementationEndMs = Date.now();
173
+ addReport('Implementation', `${implementationIterationDetails.join('\n\n')}\n\n**Outcome**: ESCALATE: ${implGate.reason}\n**Phase time**: ${formatElapsed(implementationStartMs, implementationEndMs)}`);
174
+ // Phase end notification
175
+ await notify(`# Mini-Loop: Building ended\n\nOutcome: ESCALATE\nReason: ${implGate.reason}\nIterations: ${iteration}\nPhase time: ${formatElapsed(implementationStartMs, implementationEndMs)}\n`);
176
+ context.metadata({
177
+ title: '⚠️ Mini-loop finished with issues',
178
+ metadata: {
179
+ phase: 'complete',
180
+ outcome: 'issues',
181
+ totalElapsedMs: Date.now() - totalStartMs,
182
+ },
183
+ });
184
+ addReport('Complete', `Mini-loop finished with issues.\n**Total time**: ${formatElapsed(totalStartMs, Date.now())}`);
185
+ await notify(`# Mini-Loop: Complete\n\n${report.join('\n\n')}\n`);
186
+ return;
187
+ }
188
+ // REWORK continues the loop
149
189
  }
150
- const feedback = review.reworkInstructions || implGate.feedback || review.raw;
151
- implementationIterationDetails.push(`### Iteration ${iteration}\n` +
152
- `- **Build**: ${buildTitle} (${formatElapsed(buildStartMs, buildEndMs)})\n` +
153
- `- **Review**: ${reviewTitle} (${formatElapsed(reviewStartMs, reviewEndMs)})\n` +
154
- `- **Gate**: ${implGate.decision} (confidence: ${review.confidence}, change requested: ${review.changeRequested ? 'yes' : 'no'}, unresolved: ${review.unresolvedIssues})\n` +
155
- `- **Feedback**: ${feedback}`);
156
- if (implGate.decision === 'ESCALATE') {
157
- const implementationEndMs = Date.now();
158
- addReport('Implementation', `${implementationIterationDetails.join('\n\n')}\n\n**Outcome**: ESCALATE: ${implGate.reason}\n**Phase time**: ${formatElapsed(implementationStartMs, implementationEndMs)}`);
190
+ const implementationEndMs = Date.now();
191
+ addReport('Implementation', `${implementationIterationDetails.join('\n\n')}\n\n**Outcome**: ${implGate.decision === 'APPROVED'
192
+ ? 'APPROVED'
193
+ : `REWORK exhausted (10 iterations). Last feedback: ${implGate.feedback}`}\n**Phase time**: ${formatElapsed(implementationStartMs, implementationEndMs)}`);
194
+ // Phase end notification
195
+ await notify(`# Mini-Loop: Building ended\n\nOutcome: ${implGate.decision}\nIterations: ${implGate.decision === 'APPROVED' ? 'converged' : '10 (exhausted)'}\nPhase time: ${formatElapsed(implementationStartMs, implementationEndMs)}\n`);
196
+ if (implGate.decision !== 'APPROVED') {
159
197
  context.metadata({
160
198
  title: '⚠️ Mini-loop finished with issues',
161
199
  metadata: {
@@ -165,141 +203,143 @@ export function createMiniLoopTool(client) {
165
203
  },
166
204
  });
167
205
  addReport('Complete', `Mini-loop finished with issues.\n**Total time**: ${formatElapsed(totalStartMs, Date.now())}`);
168
- return report.join('\n\n');
206
+ await notify(`# Mini-Loop: Complete\n\n${report.join('\n\n')}\n`);
207
+ return;
169
208
  }
170
- // REWORK continues the loop
171
- }
172
- const implementationEndMs = Date.now();
173
- addReport('Implementation', `${implementationIterationDetails.join('\n\n')}\n\n**Outcome**: ${implGate.decision === 'APPROVED'
174
- ? 'APPROVED'
175
- : `REWORK exhausted (10 iterations). Last feedback: ${implGate.feedback}`}\n**Phase time**: ${formatElapsed(implementationStartMs, implementationEndMs)}`);
176
- if (implGate.decision !== 'APPROVED') {
177
- context.metadata({
178
- title: '⚠️ Mini-loop finished with issues',
179
- metadata: {
180
- phase: 'complete',
181
- outcome: 'issues',
182
- totalElapsedMs: Date.now() - totalStartMs,
183
- },
184
- });
185
- addReport('Complete', `Mini-loop finished with issues.\n**Total time**: ${formatElapsed(totalStartMs, Date.now())}`);
186
- return report.join('\n\n');
187
- }
188
- // ===================================================================
189
- // PHASE 2: DOCUMENTATION LOOP (document → review → gate, up to 5)
190
- // ===================================================================
191
- const documentationStartMs = Date.now();
192
- const documentationIterationDetails = [];
193
- let docInput = lastImplRaw;
194
- let docGate = { decision: 'REWORK' };
195
- for (let docIter = 0; docIter < 5 && docGate.decision === 'REWORK'; docIter++) {
196
- const iteration = docIter + 1;
197
- const writeTitle = `ff-mini-doc-write-${iteration}`;
198
- const reviewTitle = `ff-mini-doc-review-${iteration}`;
199
- context.metadata({
200
- title: `⏳ Writing documentation (iteration ${iteration}/5)...`,
201
- metadata: {
202
- phase: 'documentation',
203
- step: 'write',
204
- iteration,
205
- maxIterations: 5,
206
- },
207
- });
208
- // Write docs
209
- const writeStartMs = Date.now();
210
- const docRaw = await promptSession(client, sessionId, documentPrompt(docInput), {
211
- model: docModel,
212
- agent: 'documenting',
213
- title: writeTitle,
214
- });
215
- const writeEndMs = Date.now();
216
- context.metadata({
217
- title: `⏳ Reviewing documentation (iteration ${iteration}/5)...`,
218
- metadata: {
219
- phase: 'documentation',
220
- step: 'review',
221
- iteration,
222
- maxIterations: 5,
223
- },
224
- });
225
- // Review docs
226
- const reviewStartMs = Date.now();
227
- const docRevRaw = await promptSession(client, sessionId, docReviewPrompt(docRaw), {
228
- model: docReviewModel,
229
- agent: 'reviewing',
230
- title: reviewTitle,
231
- });
232
- const reviewEndMs = Date.now();
233
- const docReview = parseDocReview(docRevRaw);
234
- // Gate (deterministic)
235
- docGate = evaluateMiniLoopDocGate(docReview, iteration);
236
- if (docGate.decision === 'APPROVED') {
209
+ // ===================================================================
210
+ // PHASE 2: DOCUMENTATION LOOP (document → review → gate, up to 5)
211
+ // ===================================================================
212
+ lastPhase = 'Documentation';
213
+ const documentationStartMs = Date.now();
214
+ const documentationIterationDetails = [];
215
+ let docInput = lastImplRaw;
216
+ let docGate = { decision: 'REWORK' };
217
+ // Phase start notification
218
+ await notify(`# Mini-Loop: Documentation started\n\nStarting documentation phase...\n`);
219
+ for (let docIter = 0; docIter < 5 && docGate.decision === 'REWORK'; docIter++) {
220
+ const iteration = docIter + 1;
221
+ const writeTitle = `ff-mini-doc-write-${iteration}`;
222
+ const reviewTitle = `ff-mini-doc-review-${iteration}`;
237
223
  context.metadata({
238
- title: `✅ Documentation APPROVED (confidence: ${docReview.confidence}, iteration: ${iteration})`,
224
+ title: `⏳ Writing documentation (iteration ${iteration}/5)...`,
239
225
  metadata: {
240
226
  phase: 'documentation',
241
- step: 'gate',
242
- decision: docGate.decision,
243
- confidence: docReview.confidence,
227
+ step: 'write',
244
228
  iteration,
245
229
  maxIterations: 5,
246
230
  },
247
231
  });
248
- }
249
- else if (docGate.decision === 'ESCALATE') {
250
- context.metadata({
251
- title: '⚠️ Documentation ESCALATED',
252
- metadata: {
253
- phase: 'documentation',
254
- step: 'gate',
255
- decision: docGate.decision,
256
- confidence: docReview.confidence,
257
- iteration,
258
- maxIterations: 5,
259
- },
232
+ // Write docs
233
+ const writeStartMs = Date.now();
234
+ const docRaw = await promptSession(client, sessionId, documentPrompt(docInput), {
235
+ model: docModel,
236
+ agent: 'documenting',
237
+ title: writeTitle,
260
238
  });
261
- }
262
- else {
239
+ const writeEndMs = Date.now();
263
240
  context.metadata({
264
- title: `🔄 Documentation rework required (confidence: ${docReview.confidence}, iteration: ${iteration}/5)`,
241
+ title: `⏳ Reviewing documentation (iteration ${iteration}/5)...`,
265
242
  metadata: {
266
243
  phase: 'documentation',
267
- step: 'gate',
268
- decision: docGate.decision,
269
- confidence: docReview.confidence,
244
+ step: 'review',
270
245
  iteration,
271
246
  maxIterations: 5,
272
247
  },
273
248
  });
249
+ // Review docs
250
+ const reviewStartMs = Date.now();
251
+ const docRevRaw = await promptSession(client, sessionId, docReviewPrompt(docRaw), {
252
+ model: docReviewModel,
253
+ agent: 'reviewing',
254
+ title: reviewTitle,
255
+ });
256
+ const reviewEndMs = Date.now();
257
+ const docReview = parseDocReview(docRevRaw);
258
+ // Gate (deterministic)
259
+ docGate = evaluateMiniLoopDocGate(docReview, iteration);
260
+ if (docGate.decision === 'APPROVED') {
261
+ context.metadata({
262
+ title: `✅ Documentation APPROVED (confidence: ${docReview.confidence}, iteration: ${iteration})`,
263
+ metadata: {
264
+ phase: 'documentation',
265
+ step: 'gate',
266
+ decision: docGate.decision,
267
+ confidence: docReview.confidence,
268
+ iteration,
269
+ maxIterations: 5,
270
+ },
271
+ });
272
+ }
273
+ else if (docGate.decision === 'ESCALATE') {
274
+ context.metadata({
275
+ title: '⚠️ Documentation ESCALATED',
276
+ metadata: {
277
+ phase: 'documentation',
278
+ step: 'gate',
279
+ decision: docGate.decision,
280
+ confidence: docReview.confidence,
281
+ iteration,
282
+ maxIterations: 5,
283
+ },
284
+ });
285
+ }
286
+ else {
287
+ context.metadata({
288
+ title: `🔄 Documentation rework required (confidence: ${docReview.confidence}, iteration: ${iteration}/5)`,
289
+ metadata: {
290
+ phase: 'documentation',
291
+ step: 'gate',
292
+ decision: docGate.decision,
293
+ confidence: docReview.confidence,
294
+ iteration,
295
+ maxIterations: 5,
296
+ },
297
+ });
298
+ }
299
+ const feedback = docReview.reworkInstructions || docGate.feedback || docReview.raw;
300
+ documentationIterationDetails.push(`### Iteration ${iteration}\n` +
301
+ `- **Write**: ${writeTitle} (${formatElapsed(writeStartMs, writeEndMs)})\n` +
302
+ `- **Review**: ${reviewTitle} (${formatElapsed(reviewStartMs, reviewEndMs)})\n` +
303
+ `- **Gate**: ${docGate.decision} (confidence: ${docReview.confidence}, unresolved: ${docReview.unresolvedIssues})\n` +
304
+ `- **Feedback**: ${feedback}`);
305
+ // Notify each documentation iteration gate decision
306
+ await notify(`# Mini-Loop: Documentation — Iteration ${iteration}/5\n\nStatus: ${docGate.decision}\nConfidence: ${docReview.confidence}%\nUnresolved Issues: ${docReview.unresolvedIssues}\nDuration: ${formatElapsed(documentationStartMs, Date.now())}\nFeedback: ${feedback}\n`);
307
+ if (docGate.decision === 'REWORK') {
308
+ docInput = `${docInput}\n\nDocumentation review feedback:\n${docGate.feedback}`;
309
+ }
274
310
  }
275
- const feedback = docReview.reworkInstructions || docGate.feedback || docReview.raw;
276
- documentationIterationDetails.push(`### Iteration ${iteration}\n` +
277
- `- **Write**: ${writeTitle} (${formatElapsed(writeStartMs, writeEndMs)})\n` +
278
- `- **Review**: ${reviewTitle} (${formatElapsed(reviewStartMs, reviewEndMs)})\n` +
279
- `- **Gate**: ${docGate.decision} (confidence: ${docReview.confidence}, unresolved: ${docReview.unresolvedIssues})\n` +
280
- `- **Feedback**: ${feedback}`);
281
- if (docGate.decision === 'REWORK') {
282
- docInput = `${docInput}\n\nDocumentation review feedback:\n${docGate.feedback}`;
283
- }
284
- }
285
- const documentationEndMs = Date.now();
286
- addReport('Documentation', `${documentationIterationDetails.join('\n\n')}\n\n**Outcome**: ${docGate.decision === 'APPROVED'
287
- ? 'APPROVED'
288
- : docGate.decision === 'ESCALATE'
289
- ? `ESCALATE: ${docGate.reason}`
290
- : `REWORK exhausted (5 iterations). Last feedback: ${docGate.feedback}`}\n**Phase time**: ${formatElapsed(documentationStartMs, documentationEndMs)}`);
291
- const totalEndMs = Date.now();
292
- const completedWithoutIssues = docGate.decision === 'APPROVED';
293
- context.metadata({
294
- title: completedWithoutIssues ? '✅ Mini-loop complete' : '⚠️ Mini-loop finished with issues',
295
- metadata: {
296
- phase: 'complete',
297
- outcome: completedWithoutIssues ? 'success' : 'issues',
298
- totalElapsedMs: totalEndMs - totalStartMs,
299
- },
311
+ const documentationEndMs = Date.now();
312
+ addReport('Documentation', `${documentationIterationDetails.join('\n\n')}\n\n**Outcome**: ${docGate.decision === 'APPROVED'
313
+ ? 'APPROVED'
314
+ : docGate.decision === 'ESCALATE'
315
+ ? `ESCALATE: ${docGate.reason}`
316
+ : `REWORK exhausted (5 iterations). Last feedback: ${docGate.feedback}`}\n**Phase time**: ${formatElapsed(documentationStartMs, documentationEndMs)}`);
317
+ // Phase end notification
318
+ await notify(`# Mini-Loop: Documentation ended\n\nOutcome: ${docGate.decision}\nConfidence: ${docGate.decision === 'APPROVED' ? 'converged' : 'N/A'}\nPhase time: ${formatElapsed(documentationStartMs, documentationEndMs)}\n`);
319
+ const totalEndMs = Date.now();
320
+ const completedWithoutIssues = docGate.decision === 'APPROVED';
321
+ context.metadata({
322
+ title: completedWithoutIssues
323
+ ? '✅ Mini-loop complete'
324
+ : '⚠️ Mini-loop finished with issues',
325
+ metadata: {
326
+ phase: 'complete',
327
+ outcome: completedWithoutIssues ? 'success' : 'issues',
328
+ totalElapsedMs: totalEndMs - totalStartMs,
329
+ },
330
+ });
331
+ addReport('Complete', `${completedWithoutIssues ? 'Mini-loop finished successfully.' : 'Mini-loop finished with issues.'}\n**Total time**: ${formatElapsed(totalStartMs, totalEndMs)}`);
332
+ // Send final completion report as notification
333
+ await notify(`# Mini-Loop: Complete\n\n${report.join('\n\n')}\n`);
334
+ }; // end asyncOrchestration
335
+ // Launch orchestration in background — fire-and-forget
336
+ void asyncOrchestration().catch(async (err) => {
337
+ const message = err instanceof Error ? err.message : String(err);
338
+ await notifyParent(client, sessionId, agent, `# Mini-Loop: Error\n\nPhase: ${lastPhase}\nError: ${message}\n`);
300
339
  });
301
- addReport('Complete', `${completedWithoutIssues ? 'Mini-loop finished successfully.' : 'Mini-loop finished with issues.'}\n**Total time**: ${formatElapsed(totalStartMs, totalEndMs)}`);
302
- return report.join('\n\n');
340
+ // Return immediately with acknowledgment
341
+ const summary = requirements.length > 120 ? requirements.slice(0, 120) + '...' : requirements;
342
+ return `Mini-loop started for: ${summary}\n\nYou will receive progress updates as each phase completes.`;
303
343
  },
304
344
  });
305
345
  }