@syntesseraai/opencode-feature-factory 0.10.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
@@ -119,35 +119,36 @@ Both `ff_pipeline` and `ff_mini_loop` tools run asynchronously with real-time pr
119
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
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
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 `<ff_pipeline_error>` or `<ff_mini_loop_error>` notification is sent with the last phase and error message.
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
123
  - **`context.metadata()` retained**: All existing metadata calls remain in place for future-proofing (when OpenCode's TUI renders tool metadata natively).
124
124
 
125
125
  ### Notification Format
126
126
 
127
- Pipeline updates use XML-style tags for structured parsing:
127
+ Pipeline notifications use plain-text markdown headers with phase START/END bracketing and per-iteration gate details:
128
128
 
129
129
  ```
130
- <ff_pipeline_update>
131
- Phase: Planning
130
+ # Pipeline: Reviewing — Iteration 2/10
131
+
132
132
  Status: APPROVED
133
- Duration: 45.2s
134
- Next Phase: Building
135
- </ff_pipeline_update>
133
+ Confidence: 97%
134
+ Unresolved Issues: 0
135
+ Duration: 45.3s
136
+ Feedback: N/A
136
137
  ```
137
138
 
138
- Mini-loop updates follow the same pattern:
139
+ Mini-loop notifications follow the same pattern:
139
140
 
140
141
  ```
141
- <ff_mini_loop_update>
142
- Phase: Implementation
143
- Status: APPROVED
144
- Confidence: 97%
145
- Iteration: 2/10
146
- Duration: 32.1s
147
- </ff_mini_loop_update>
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
148
149
  ```
149
150
 
150
- Final reports are wrapped in `<ff_pipeline_complete>` or `<ff_mini_loop_complete>` tags containing the full markdown report.
151
+ Final reports use `# Pipeline: Complete` or `# Mini-Loop: Complete` headers containing the full markdown report. Errors use `# Pipeline: Error` or `# Mini-Loop: Error`.
151
152
 
152
153
  ## Related Docs
153
154
 
@@ -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
@@ -79,6 +79,8 @@ export function createMiniLoopTool(client) {
79
79
  const implementationIterationDetails = [];
80
80
  let implGate = { decision: 'REWORK', feedback: requirements };
81
81
  let lastImplRaw = '';
82
+ // Phase start notification
83
+ await notify(`# Mini-Loop: Building started\n\nStarting implementation phase...\n`);
82
84
  for (let implIter = 0; implIter < 10 && implGate.decision === 'REWORK'; implIter++) {
83
85
  const iteration = implIter + 1;
84
86
  const buildTitle = `ff-mini-build-${iteration}`;
@@ -165,10 +167,12 @@ export function createMiniLoopTool(client) {
165
167
  `- **Gate**: ${implGate.decision} (confidence: ${review.confidence}, change requested: ${review.changeRequested ? 'yes' : 'no'}, unresolved: ${review.unresolvedIssues})\n` +
166
168
  `- **Feedback**: ${feedback}`);
167
169
  // Notify each implementation iteration gate decision
168
- await notify(`<ff_mini_loop_update>\nPhase: Implementation\nStatus: ${implGate.decision}\nConfidence: ${review.confidence}%\nIteration: ${iteration}/10\nDuration: ${formatElapsed(implementationStartMs, Date.now())}\n</ff_mini_loop_update>`);
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`);
169
171
  if (implGate.decision === 'ESCALATE') {
170
172
  const implementationEndMs = Date.now();
171
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`);
172
176
  context.metadata({
173
177
  title: '⚠️ Mini-loop finished with issues',
174
178
  metadata: {
@@ -178,7 +182,7 @@ export function createMiniLoopTool(client) {
178
182
  },
179
183
  });
180
184
  addReport('Complete', `Mini-loop finished with issues.\n**Total time**: ${formatElapsed(totalStartMs, Date.now())}`);
181
- await notify(`<ff_mini_loop_complete>\n${report.join('\n\n')}\n</ff_mini_loop_complete>`);
185
+ await notify(`# Mini-Loop: Complete\n\n${report.join('\n\n')}\n`);
182
186
  return;
183
187
  }
184
188
  // REWORK continues the loop
@@ -187,6 +191,8 @@ export function createMiniLoopTool(client) {
187
191
  addReport('Implementation', `${implementationIterationDetails.join('\n\n')}\n\n**Outcome**: ${implGate.decision === 'APPROVED'
188
192
  ? 'APPROVED'
189
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`);
190
196
  if (implGate.decision !== 'APPROVED') {
191
197
  context.metadata({
192
198
  title: '⚠️ Mini-loop finished with issues',
@@ -197,7 +203,7 @@ export function createMiniLoopTool(client) {
197
203
  },
198
204
  });
199
205
  addReport('Complete', `Mini-loop finished with issues.\n**Total time**: ${formatElapsed(totalStartMs, Date.now())}`);
200
- await notify(`<ff_mini_loop_complete>\n${report.join('\n\n')}\n</ff_mini_loop_complete>`);
206
+ await notify(`# Mini-Loop: Complete\n\n${report.join('\n\n')}\n`);
201
207
  return;
202
208
  }
203
209
  // ===================================================================
@@ -208,6 +214,8 @@ export function createMiniLoopTool(client) {
208
214
  const documentationIterationDetails = [];
209
215
  let docInput = lastImplRaw;
210
216
  let docGate = { decision: 'REWORK' };
217
+ // Phase start notification
218
+ await notify(`# Mini-Loop: Documentation started\n\nStarting documentation phase...\n`);
211
219
  for (let docIter = 0; docIter < 5 && docGate.decision === 'REWORK'; docIter++) {
212
220
  const iteration = docIter + 1;
213
221
  const writeTitle = `ff-mini-doc-write-${iteration}`;
@@ -295,7 +303,7 @@ export function createMiniLoopTool(client) {
295
303
  `- **Gate**: ${docGate.decision} (confidence: ${docReview.confidence}, unresolved: ${docReview.unresolvedIssues})\n` +
296
304
  `- **Feedback**: ${feedback}`);
297
305
  // Notify each documentation iteration gate decision
298
- await notify(`<ff_mini_loop_update>\nPhase: Documentation\nStatus: ${docGate.decision}\nConfidence: ${docReview.confidence}%\nIteration: ${iteration}/5\nDuration: ${formatElapsed(documentationStartMs, Date.now())}\n</ff_mini_loop_update>`);
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`);
299
307
  if (docGate.decision === 'REWORK') {
300
308
  docInput = `${docInput}\n\nDocumentation review feedback:\n${docGate.feedback}`;
301
309
  }
@@ -306,6 +314,8 @@ export function createMiniLoopTool(client) {
306
314
  : docGate.decision === 'ESCALATE'
307
315
  ? `ESCALATE: ${docGate.reason}`
308
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`);
309
319
  const totalEndMs = Date.now();
310
320
  const completedWithoutIssues = docGate.decision === 'APPROVED';
311
321
  context.metadata({
@@ -320,12 +330,12 @@ export function createMiniLoopTool(client) {
320
330
  });
321
331
  addReport('Complete', `${completedWithoutIssues ? 'Mini-loop finished successfully.' : 'Mini-loop finished with issues.'}\n**Total time**: ${formatElapsed(totalStartMs, totalEndMs)}`);
322
332
  // Send final completion report as notification
323
- await notify(`<ff_mini_loop_complete>\n${report.join('\n\n')}\n</ff_mini_loop_complete>`);
333
+ await notify(`# Mini-Loop: Complete\n\n${report.join('\n\n')}\n`);
324
334
  }; // end asyncOrchestration
325
335
  // Launch orchestration in background — fire-and-forget
326
336
  void asyncOrchestration().catch(async (err) => {
327
337
  const message = err instanceof Error ? err.message : String(err);
328
- await notifyParent(client, sessionId, agent, `<ff_mini_loop_error>\nPhase: ${lastPhase}\nError: ${message}\nDuration: N/A\n</ff_mini_loop_error>`);
338
+ await notifyParent(client, sessionId, agent, `# Mini-Loop: Error\n\nPhase: ${lastPhase}\nError: ${message}\n`);
329
339
  });
330
340
  // Return immediately with acknowledgment
331
341
  const summary = requirements.length > 120 ? requirements.slice(0, 120) + '...' : requirements;
@@ -108,6 +108,8 @@ export function createPipelineTool(client) {
108
108
  const planningIterationDetails = [];
109
109
  let planningGate = { decision: 'REWORK', feedback: requirements };
110
110
  let finalPlan = '';
111
+ // Phase start notification
112
+ await notify(`# Pipeline: Planning started\n\nStarting planning phase with ${planModels.length} models...\n`);
111
113
  for (let planIter = 0; planIter < 5 && planningGate.decision === 'REWORK'; planIter++) {
112
114
  const iteration = planIter + 1;
113
115
  const synthesisTitle = `ff-plan-synthesis-${iteration}`;
@@ -197,9 +199,13 @@ export function createPipelineTool(client) {
197
199
  `- **Synthesis**: ${synthesisTitle} (${formatElapsed(synthesisStartMs, synthesisEndMs)})\n` +
198
200
  `- **Gate**: ${planningGate.decision} (score: ${consensus.consensusScore})\n` +
199
201
  `- **Feedback**: ${feedback}`);
202
+ // Notify each planning iteration gate decision
203
+ await notify(`# Pipeline: Planning — Iteration ${iteration}/5\n\nStatus: ${planningGate.decision}\nConsensus Score: ${consensus.consensusScore}%\nDuration: ${formatElapsed(planningStartMs, Date.now())}\nFeedback: ${feedback}\n`);
200
204
  if (planningGate.decision === 'BLOCKED') {
201
205
  const planningEndMs = Date.now();
202
206
  addReport('Planning', `${planningIterationDetails.join('\n\n')}\n\n**Outcome**: BLOCKED: ${planningGate.reason}\n**Phase time**: ${formatElapsed(planningStartMs, planningEndMs)}`);
207
+ // Phase end notification
208
+ await notify(`# Pipeline: Planning ended\n\nOutcome: BLOCKED\nReason: ${planningGate.reason}\nConsensus Score: ${consensus.consensusScore}%\nIterations: ${iteration}/5\nPhase time: ${formatElapsed(planningStartMs, planningEndMs)}\n`);
203
209
  context.metadata({
204
210
  title: '⚠️ Pipeline finished with issues',
205
211
  metadata: {
@@ -209,8 +215,7 @@ export function createPipelineTool(client) {
209
215
  },
210
216
  });
211
217
  addReport('Complete', `Pipeline finished with issues.\n**Total time**: ${formatElapsed(totalStartMs, Date.now())}`);
212
- await notify(`<ff_pipeline_update>\nPhase: Planning\nStatus: BLOCKED\nConsensus Score: ${consensus.consensusScore}%\nIterations: ${iteration}/5\nDuration: ${formatElapsed(planningStartMs, planningEndMs)}\n</ff_pipeline_update>`);
213
- await notify(`<ff_pipeline_complete>\n${report.join('\n\n')}\n</ff_pipeline_complete>`);
218
+ await notify(`# Pipeline: Complete\n\n${report.join('\n\n')}\n`);
214
219
  return;
215
220
  }
216
221
  // REWORK continues the loop
@@ -219,8 +224,8 @@ export function createPipelineTool(client) {
219
224
  addReport('Planning', `${planningIterationDetails.join('\n\n')}\n\n**Outcome**: ${planningGate.decision === 'APPROVED'
220
225
  ? 'APPROVED'
221
226
  : `REWORK exhausted (5 iterations). Last feedback: ${planningGate.feedback}`}\n**Phase time**: ${formatElapsed(planningStartMs, planningEndMs)}`);
222
- // Notify planning phase complete
223
- await notify(`<ff_pipeline_update>\nPhase: Planning\nStatus: ${planningGate.decision}\nDuration: ${formatElapsed(planningStartMs, planningEndMs)}\nNext Phase: ${planningGate.decision === 'APPROVED' ? 'Building' : 'Finished'}\n</ff_pipeline_update>`);
227
+ // Phase end notification
228
+ await notify(`# Pipeline: Planning ended\n\nOutcome: ${planningGate.decision}\nDuration: ${formatElapsed(planningStartMs, planningEndMs)}\nNext Phase: ${planningGate.decision === 'APPROVED' ? 'Building' : 'Finished'}\n`);
224
229
  if (planningGate.decision !== 'APPROVED') {
225
230
  context.metadata({
226
231
  title: '⚠️ Pipeline finished with issues',
@@ -231,7 +236,7 @@ export function createPipelineTool(client) {
231
236
  },
232
237
  });
233
238
  addReport('Complete', `Pipeline finished with issues.\n**Total time**: ${formatElapsed(totalStartMs, Date.now())}`);
234
- await notify(`<ff_pipeline_complete>\n${report.join('\n\n')}\n</ff_pipeline_complete>`);
239
+ await notify(`# Pipeline: Complete\n\n${report.join('\n\n')}\n`);
235
240
  return;
236
241
  }
237
242
  // ===================================================================
@@ -239,6 +244,8 @@ export function createPipelineTool(client) {
239
244
  // ===================================================================
240
245
  lastPhase = 'Building';
241
246
  const buildingStartMs = Date.now();
247
+ // Phase start notification
248
+ await notify(`# Pipeline: Building started\n\nStarting build phase (breakdown → validate → implement)...\n`);
242
249
  context.metadata({
243
250
  title: '⏳ Breaking down tasks...',
244
251
  metadata: {
@@ -296,8 +303,8 @@ export function createPipelineTool(client) {
296
303
  `- **Tests passed**: ${implementation.testsPassed}\n` +
297
304
  `- **Open issues**: ${implementation.openIssues.length > 0 ? implementation.openIssues.join('; ') : 'none'}\n\n` +
298
305
  `**Phase time**: ${formatElapsed(buildingStartMs, buildingEndMs)}`);
299
- // Notify building phase complete
300
- await notify(`<ff_pipeline_update>\nPhase: Building\nStatus: COMPLETE\nFiles Changed: ${implementation.filesChanged.length}\nTests Passed: ${implementation.testsPassed}\nDuration: ${formatElapsed(buildingStartMs, buildingEndMs)}\nNext Phase: Reviewing\n</ff_pipeline_update>`);
306
+ // Phase end notification
307
+ await notify(`# Pipeline: Building ended\n\nOutcome: COMPLETE\nFiles Changed: ${implementation.filesChanged.length}\nTests Passed: ${implementation.testsPassed}\nOpen Issues: ${implementation.openIssues.length > 0 ? implementation.openIssues.join('; ') : 'none'}\nPhase time: ${formatElapsed(buildingStartMs, buildingEndMs)}\n`);
301
308
  // ===================================================================
302
309
  // PHASE 3: REVIEWING (triage → fan-out review → synthesize → gate, loop up to 10)
303
310
  // ===================================================================
@@ -306,6 +313,8 @@ export function createPipelineTool(client) {
306
313
  const reviewIterationDetails = [];
307
314
  let reviewInput = implRaw;
308
315
  let reviewGate = { decision: 'REWORK' };
316
+ // Phase start notification
317
+ await notify(`# Pipeline: Reviewing started\n\nStarting review phase with ${revModels.length} models...\n`);
309
318
  for (let revIter = 0; revIter < 10 && reviewGate.decision === 'REWORK'; revIter++) {
310
319
  const iteration = revIter + 1;
311
320
  const triageTitle = `ff-review-triage-${iteration}`;
@@ -435,10 +444,12 @@ export function createPipelineTool(client) {
435
444
  `${reworkLine}\n` +
436
445
  `- **Feedback**: ${feedback}`);
437
446
  // Notify each review iteration gate decision
438
- await notify(`<ff_pipeline_update>\nPhase: Reviewing\nStatus: ${reviewGate.decision}\nConfidence: ${synthesis.overallConfidence}%\nUnresolved Issues: ${synthesis.unresolvedIssues}\nIteration: ${iteration}/10\nDuration: ${formatElapsed(reviewStartMs, Date.now())}\n</ff_pipeline_update>`);
447
+ await notify(`# Pipeline: Reviewing — Iteration ${iteration}/10\n\nStatus: ${reviewGate.decision}\nConfidence: ${synthesis.overallConfidence}%\nUnresolved Issues: ${synthesis.unresolvedIssues}\nDuration: ${formatElapsed(reviewStartMs, Date.now())}\nFeedback: ${feedback}\n`);
439
448
  if (reviewGate.decision === 'ESCALATE') {
440
449
  const reviewEndMs = Date.now();
441
450
  addReport('Reviewing', `${reviewIterationDetails.join('\n\n')}\n\n**Outcome**: ESCALATE: ${reviewGate.reason}\n**Phase time**: ${formatElapsed(reviewStartMs, reviewEndMs)}`);
451
+ // Phase end notification
452
+ await notify(`# Pipeline: Reviewing ended\n\nOutcome: ESCALATE\nReason: ${reviewGate.reason}\nIterations: ${iteration}\nPhase time: ${formatElapsed(reviewStartMs, reviewEndMs)}\n`);
442
453
  context.metadata({
443
454
  title: '⚠️ Pipeline finished with issues',
444
455
  metadata: {
@@ -448,7 +459,7 @@ export function createPipelineTool(client) {
448
459
  },
449
460
  });
450
461
  addReport('Complete', `Pipeline finished with issues.\n**Total time**: ${formatElapsed(totalStartMs, Date.now())}`);
451
- await notify(`<ff_pipeline_complete>\n${report.join('\n\n')}\n</ff_pipeline_complete>`);
462
+ await notify(`# Pipeline: Complete\n\n${report.join('\n\n')}\n`);
452
463
  return;
453
464
  }
454
465
  }
@@ -456,6 +467,8 @@ export function createPipelineTool(client) {
456
467
  addReport('Reviewing', `${reviewIterationDetails.join('\n\n')}\n\n**Outcome**: ${reviewGate.decision === 'APPROVED'
457
468
  ? 'APPROVED'
458
469
  : `REWORK exhausted (10 iterations). Last feedback: ${reviewGate.feedback}`}\n**Phase time**: ${formatElapsed(reviewStartMs, reviewEndMs)}`);
470
+ // Phase end notification
471
+ await notify(`# Pipeline: Reviewing ended\n\nOutcome: ${reviewGate.decision}\nDuration: ${formatElapsed(reviewStartMs, reviewEndMs)}\n`);
459
472
  if (reviewGate.decision !== 'APPROVED') {
460
473
  context.metadata({
461
474
  title: '⚠️ Pipeline finished with issues',
@@ -466,7 +479,7 @@ export function createPipelineTool(client) {
466
479
  },
467
480
  });
468
481
  addReport('Complete', `Pipeline finished with issues.\n**Total time**: ${formatElapsed(totalStartMs, Date.now())}`);
469
- await notify(`<ff_pipeline_complete>\n${report.join('\n\n')}\n</ff_pipeline_complete>`);
482
+ await notify(`# Pipeline: Complete\n\n${report.join('\n\n')}\n`);
470
483
  return;
471
484
  }
472
485
  // ===================================================================
@@ -477,6 +490,8 @@ export function createPipelineTool(client) {
477
490
  const documentationIterationDetails = [];
478
491
  let docInput = `Implementation report:\n${implRaw}\n\nReview synthesis:\n${reviewInput}`;
479
492
  let docGate = { decision: 'REWORK' };
493
+ // Phase start notification
494
+ await notify(`# Pipeline: Documentation started\n\nStarting documentation phase...\n`);
480
495
  for (let docIter = 0; docIter < 5 && docGate.decision === 'REWORK'; docIter++) {
481
496
  const iteration = docIter + 1;
482
497
  const writeTitle = `ff-doc-write-${iteration}`;
@@ -567,7 +582,7 @@ export function createPipelineTool(client) {
567
582
  `- **Gate**: ${docGate.decision} (confidence: ${docReview.confidence}, unresolved: ${docReview.unresolvedIssues})\n` +
568
583
  `- **Feedback**: ${feedback}`);
569
584
  // Notify each documentation iteration gate decision
570
- await notify(`<ff_pipeline_update>\nPhase: Documentation\nStatus: ${docGate.decision}\nConfidence: ${docReview.confidence}%\nIteration: ${iteration}/5\nDuration: ${formatElapsed(documentationStartMs, Date.now())}\n</ff_pipeline_update>`);
585
+ await notify(`# Pipeline: Documentation — Iteration ${iteration}/5\n\nStatus: ${docGate.decision}\nConfidence: ${docReview.confidence}%\nUnresolved Issues: ${docReview.unresolvedIssues}\nDuration: ${formatElapsed(documentationStartMs, Date.now())}\nFeedback: ${feedback}\n`);
571
586
  if (docGate.decision === 'REWORK') {
572
587
  // Feed feedback into the next iteration
573
588
  docInput = `${docInput}\n\nDocumentation review feedback:\n${docGate.feedback}`;
@@ -579,6 +594,8 @@ export function createPipelineTool(client) {
579
594
  : docGate.decision === 'ESCALATE'
580
595
  ? `ESCALATE: ${docGate.reason}`
581
596
  : `REWORK exhausted (5 iterations). Last feedback: ${docGate.feedback}`}\n**Phase time**: ${formatElapsed(documentationStartMs, documentationEndMs)}`);
597
+ // Phase end notification
598
+ await notify(`# Pipeline: Documentation ended\n\nOutcome: ${docGate.decision}\nPhase time: ${formatElapsed(documentationStartMs, documentationEndMs)}\n`);
582
599
  // ===================================================================
583
600
  // FINAL REPORT
584
601
  // ===================================================================
@@ -596,12 +613,12 @@ export function createPipelineTool(client) {
596
613
  });
597
614
  addReport('Complete', `${completedWithoutIssues ? 'Pipeline finished successfully.' : 'Pipeline finished with issues.'}\n**Total time**: ${formatElapsed(totalStartMs, totalEndMs)}`);
598
615
  // Send final completion report as notification
599
- await notify(`<ff_pipeline_complete>\n${report.join('\n\n')}\n</ff_pipeline_complete>`);
616
+ await notify(`# Pipeline: Complete\n\n${report.join('\n\n')}\n`);
600
617
  }; // end asyncOrchestration
601
618
  // Launch orchestration in background — fire-and-forget
602
619
  void asyncOrchestration().catch(async (err) => {
603
620
  const message = err instanceof Error ? err.message : String(err);
604
- await notifyParent(client, sessionId, agent, `<ff_pipeline_error>\nPhase: ${lastPhase}\nError: ${message}\nDuration: N/A\n</ff_pipeline_error>`);
621
+ await notifyParent(client, sessionId, agent, `# Pipeline: Error\n\nPhase: ${lastPhase}\nError: ${message}\n`);
605
622
  });
606
623
  // Return immediately with acknowledgment
607
624
  const summary = requirements.length > 120 ? requirements.slice(0, 120) + '...' : requirements;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@syntesseraai/opencode-feature-factory",
4
- "version": "0.10.0",
4
+ "version": "0.10.1",
5
5
  "type": "module",
6
6
  "description": "OpenCode plugin for Feature Factory agents - provides sub-agents and skills for validation, review, security, and architecture assessment",
7
7
  "license": "MIT",