@syntesseraai/opencode-feature-factory 0.10.8 → 0.10.9

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.
@@ -138,6 +138,7 @@ export function createMiniLoopTool(client) {
138
138
  let implGate = { decision: 'REWORK', feedback: requirements };
139
139
  let lastImplRaw = '';
140
140
  let lastImplementationHandoff = '';
141
+ let implementationStageHandoff = '';
141
142
  let nextImplementationDirective;
142
143
  // Phase start notification
143
144
  await notify(`# Mini-Loop: Building started\n\nStarting implementation phase...\n`);
@@ -147,7 +148,7 @@ export function createMiniLoopTool(client) {
147
148
  const reviewTitle = `ff-mini-review-${iteration}`;
148
149
  const buildInput = implIter === 0
149
150
  ? requirements
150
- : `${requirements}\n\nPrevious implementation handoff for context:\n${lastImplementationHandoff || lastImplRaw}`;
151
+ : `${requirements}\n\nPrevious implementation/review/rework handoff:\n${implementationStageHandoff || lastImplementationHandoff || lastImplRaw}`;
151
152
  context.metadata({
152
153
  title: `⏳ Building (iteration ${iteration}/10)...`,
153
154
  metadata: {
@@ -284,11 +285,25 @@ export function createMiniLoopTool(client) {
284
285
  nextImplementationDirective = actionFirstReworkPrompt({
285
286
  ...review.stageOutput,
286
287
  status: implGate.decision,
287
- }, `${requirements}\n\nPrevious implementation handoff for context:\n${implementationHandoff}\n\nReview feedback:\n${implGate.feedback || review.reworkInstructions || review.raw}`, implGate.feedback || review.reworkInstructions || review.raw);
288
+ }, `${requirements}\n\n` +
289
+ `Latest implementation handoff:\n${implementationHandoff}\n\n` +
290
+ `Prior implementation/review/rework handoff:\n${implementationStageHandoff || 'No prior handoff recorded.'}\n\n` +
291
+ `Review raw output:\n${reviewRaw}\n\n` +
292
+ `Review feedback:\n${implGate.feedback || review.reworkInstructions || review.raw}`, implGate.feedback || review.reworkInstructions || review.raw);
288
293
  }
289
294
  else {
290
295
  nextImplementationDirective = undefined;
291
296
  }
297
+ const implementationIterationHandoff = `### Implementation Iteration ${iteration}\n\n` +
298
+ `BUILD_OUTPUT:\n${lastImplRaw}\n\n` +
299
+ `IMPLEMENTATION_HANDOFF:\n${implementationHandoff}\n\n` +
300
+ `REVIEW_OUTPUT:\n${reviewRaw}\n\n` +
301
+ `GATE_DECISION: ${implGate.decision}\n` +
302
+ `GATE_FEEDBACK:\n${implGate.feedback || review.reworkInstructions || review.raw}\n\n` +
303
+ `NEXT_REWORK_DIRECTIVE:\n${nextImplementationDirective || 'No rework directive for this iteration.'}`;
304
+ implementationStageHandoff = implementationStageHandoff
305
+ ? `${implementationStageHandoff}\n\n${implementationIterationHandoff}`
306
+ : implementationIterationHandoff;
292
307
  if (implGate.decision === 'APPROVED') {
293
308
  context.metadata({
294
309
  title: `✅ Implementation APPROVED (confidence: ${review.confidence}, iteration: ${iteration})`,
@@ -296,6 +311,7 @@ export function createMiniLoopTool(client) {
296
311
  phase: 'implementation',
297
312
  step: 'gate',
298
313
  decision: implGate.decision,
314
+ changeRequested: review.changeRequested,
299
315
  confidence: review.confidence,
300
316
  iteration,
301
317
  maxIterations: 10,
@@ -309,6 +325,7 @@ export function createMiniLoopTool(client) {
309
325
  phase: 'implementation',
310
326
  step: 'gate',
311
327
  decision: implGate.decision,
328
+ changeRequested: review.changeRequested,
312
329
  confidence: review.confidence,
313
330
  iteration,
314
331
  maxIterations: 10,
@@ -322,6 +339,7 @@ export function createMiniLoopTool(client) {
322
339
  phase: 'implementation',
323
340
  step: 'gate',
324
341
  decision: implGate.decision,
342
+ changeRequested: review.changeRequested,
325
343
  confidence: review.confidence,
326
344
  iteration,
327
345
  maxIterations: 10,
@@ -340,6 +358,7 @@ export function createMiniLoopTool(client) {
340
358
  // Notify each implementation iteration gate decision
341
359
  await notify(`Stage: Building — iteration ${iteration}/10\n` +
342
360
  `${formatStructuredStage(implementationStageOutput, implGate.decision === 'REWORK')}\n` +
361
+ `Change Requested: ${review.changeRequested ? 'YES' : 'NO'}\n` +
343
362
  `Confidence: ${review.confidence}%\n` +
344
363
  `Unresolved Issues: ${review.unresolvedIssues}\n` +
345
364
  `Duration: ${formatElapsed(implementationStartMs, Date.now())}`);
@@ -387,7 +406,8 @@ export function createMiniLoopTool(client) {
387
406
  lastPhase = 'Documentation';
388
407
  const documentationStartMs = Date.now();
389
408
  const documentationIterationDetails = [];
390
- let docInput = lastImplementationHandoff || lastImplRaw;
409
+ let docInput = `Latest implementation handoff:\n${lastImplementationHandoff || lastImplRaw}\n\n` +
410
+ `Implementation/review/rework handoff history:\n${implementationStageHandoff || 'No implementation handoff history captured.'}`;
391
411
  let docGate = { decision: 'REWORK' };
392
412
  // Phase start notification
393
413
  await notify(`# Mini-Loop: Documentation started\n\nStarting documentation phase...\n`);
@@ -439,6 +459,7 @@ export function createMiniLoopTool(client) {
439
459
  phase: 'documentation',
440
460
  step: 'gate',
441
461
  decision: docGate.decision,
462
+ changeRequested: docReview.changeRequested,
442
463
  confidence: docReview.confidence,
443
464
  iteration,
444
465
  maxIterations: 5,
@@ -452,6 +473,7 @@ export function createMiniLoopTool(client) {
452
473
  phase: 'documentation',
453
474
  step: 'gate',
454
475
  decision: docGate.decision,
476
+ changeRequested: docReview.changeRequested,
455
477
  confidence: docReview.confidence,
456
478
  iteration,
457
479
  maxIterations: 5,
@@ -465,6 +487,7 @@ export function createMiniLoopTool(client) {
465
487
  phase: 'documentation',
466
488
  step: 'gate',
467
489
  decision: docGate.decision,
490
+ changeRequested: docReview.changeRequested,
468
491
  confidence: docReview.confidence,
469
492
  iteration,
470
493
  maxIterations: 5,
@@ -478,19 +501,24 @@ export function createMiniLoopTool(client) {
478
501
  documentationIterationDetails.push(`### Iteration ${iteration}\n` +
479
502
  `- **Write**: ${writeTitle} (${formatElapsed(writeStartMs, writeEndMs)})\n` +
480
503
  `- **Review**: ${reviewTitle} (${formatElapsed(reviewStartMs, reviewEndMs)})\n` +
481
- `- **Gate**: ${docGate.decision} (confidence: ${docReview.confidence}, unresolved: ${docReview.unresolvedIssues})\n` +
504
+ `- **Gate**: ${docGate.decision} (change requested: ${docReview.changeRequested ? 'yes' : 'no'}, confidence: ${docReview.confidence}, unresolved: ${docReview.unresolvedIssues})\n` +
482
505
  `${formatStructuredStage(docStageOutput, docGate.decision === 'REWORK')}`);
483
506
  // Notify each documentation iteration gate decision
484
507
  await notify(`Stage: Documentation — iteration ${iteration}/5\n` +
485
508
  `${formatStructuredStage(docStageOutput, docGate.decision === 'REWORK')}\n` +
509
+ `Change Requested: ${docReview.changeRequested ? 'YES' : 'NO'}\n` +
486
510
  `Confidence: ${docReview.confidence}%\n` +
487
511
  `Unresolved Issues: ${docReview.unresolvedIssues}\n` +
488
512
  `Duration: ${formatElapsed(documentationStartMs, Date.now())}`);
489
513
  if (docGate.decision === 'REWORK') {
514
+ const docIterationHandoff = `Documentation write output:\n${docRaw}\n\n` +
515
+ `Documentation review output:\n${docRevRaw}\n\n` +
516
+ `Documentation gate decision: ${docGate.decision}\n` +
517
+ `Documentation gate feedback:\n${docGate.feedback || docReview.reworkInstructions || docReview.raw}`;
490
518
  docInput = actionFirstReworkPrompt({
491
519
  ...docReview.stageOutput,
492
520
  status: docGate.decision,
493
- }, `${docInput}\n\nDocumentation review feedback:\n${docGate.feedback || docReview.reworkInstructions || docReview.raw}`, docGate.feedback || docReview.reworkInstructions || docReview.raw);
521
+ }, `${docInput}\n\n${docIterationHandoff}`, docGate.feedback || docReview.reworkInstructions || docReview.raw);
494
522
  }
495
523
  }
496
524
  const documentationEndMs = Date.now();
@@ -174,7 +174,9 @@ export function parseReviewSynthesis(raw) {
174
174
  const confidence = extractNumber(raw, 'confidence');
175
175
  const unresolvedIssues = extractNumber(raw, 'unresolved');
176
176
  const verdictMatch = raw.match(/verdict[=:\s]*(APPROVED|REWORK_REQUIRED)/i);
177
- const verdict = (verdictMatch?.[1]?.toUpperCase() === 'APPROVED' ? 'APPROVED' : 'REWORK_REQUIRED');
177
+ const parsedVerdict = (verdictMatch?.[1]?.toUpperCase() === 'APPROVED' ? 'APPROVED' : 'REWORK_REQUIRED');
178
+ const changeRequested = isYes(raw, 'CHANGE_REQUESTED') || parsedVerdict === 'REWORK_REQUIRED';
179
+ const verdict = changeRequested ? 'REWORK_REQUIRED' : 'APPROVED';
178
180
  const stageOutput = parseStageOutput(raw, {
179
181
  status: verdict,
180
182
  summary: `Review confidence ${confidence}/100 with ${unresolvedIssues} unresolved issues.`,
@@ -183,6 +185,7 @@ export function parseReviewSynthesis(raw) {
183
185
  overallConfidence: confidence,
184
186
  consolidatedFindings: raw,
185
187
  unresolvedIssues,
188
+ changeRequested,
186
189
  verdict,
187
190
  reworkInstructions: stageOutput.actionItems.length > 0
188
191
  ? formatActionItems(stageOutput.actionItems)
@@ -205,7 +208,9 @@ export function parseDocReview(raw) {
205
208
  const confidence = extractNumber(raw, 'confidence');
206
209
  const unresolvedIssues = extractNumber(raw, 'unresolved');
207
210
  const verdictMatch = raw.match(/verdict[=:\s]*(APPROVED|REWORK_REQUIRED)/i);
208
- const verdict = (verdictMatch?.[1]?.toUpperCase() === 'APPROVED' ? 'APPROVED' : 'REWORK_REQUIRED');
211
+ const parsedVerdict = (verdictMatch?.[1]?.toUpperCase() === 'APPROVED' ? 'APPROVED' : 'REWORK_REQUIRED');
212
+ const changeRequested = isYes(raw, 'CHANGE_REQUESTED') || parsedVerdict === 'REWORK_REQUIRED';
213
+ const verdict = changeRequested ? 'REWORK_REQUIRED' : 'APPROVED';
209
214
  const stageOutput = parseStageOutput(raw, {
210
215
  status: verdict,
211
216
  summary: `Documentation confidence ${confidence}/100 with ${unresolvedIssues} unresolved issues.`,
@@ -214,6 +219,7 @@ export function parseDocReview(raw) {
214
219
  verdict,
215
220
  unresolvedIssues,
216
221
  confidence,
222
+ changeRequested,
217
223
  reworkInstructions: stageOutput.actionItems.length > 0
218
224
  ? formatActionItems(stageOutput.actionItems)
219
225
  : extractSection(raw, 'rework') || undefined,
@@ -45,6 +45,12 @@ const formatStructuredStage = (stage, includeActionItems) => {
45
45
  }
46
46
  return `${base}\nACTION_ITEMS:\n${asBulletList(stage.actionItems, 'No action items required.')}`;
47
47
  };
48
+ const formatFanOutOutputs = (outputs) => {
49
+ if (outputs.length === 0) {
50
+ return 'No fan-out outputs captured.';
51
+ }
52
+ return outputs.map((output) => `--- ${output.tag} ---\n${output.raw}`).join('\n\n');
53
+ };
48
54
  export function createPipelineTool(client) {
49
55
  return tool({
50
56
  description: 'Run the full Feature Factory pipeline: multi-model planning → build → review → documentation. ' +
@@ -500,6 +506,7 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
500
506
  const reviewStartMs = Date.now();
501
507
  const reviewIterationDetails = [];
502
508
  let reviewInput = implementationHandoff;
509
+ let reviewStageHandoff = '';
503
510
  let reviewGate = { decision: 'REWORK' };
504
511
  // Phase start notification
505
512
  await notify(`# Pipeline: Reviewing started\n\nStarting review phase with ${revModels.length} models...\n`);
@@ -526,6 +533,8 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
526
533
  title: triageTitle,
527
534
  });
528
535
  const triageEndMs = Date.now();
536
+ const reviewContextForIteration = `Latest implementation handoff:\n${reviewInput}\n\n` +
537
+ `Prior review/rework handoff:\n${reviewStageHandoff || 'No prior review handoff recorded.'}`;
529
538
  context.metadata({
530
539
  title: `⏳ Review fan-out (iteration ${iteration}/10)...`,
531
540
  metadata: {
@@ -537,8 +546,9 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
537
546
  });
538
547
  // Fan-out review
539
548
  const fanOutStartMs = Date.now();
540
- const reviewResults = await fanOut(client, runContext, revModels, (tag) => reviewPrompt(brief, tag), 'reviewing');
549
+ const reviewResults = await fanOut(client, runContext, revModels, (tag) => reviewPrompt(brief, tag, reviewContextForIteration), 'reviewing');
541
550
  const fanOutEndMs = Date.now();
551
+ const reviewFanOutOutput = formatFanOutOutputs(reviewResults);
542
552
  context.metadata({
543
553
  title: '⏳ Synthesizing reviews...',
544
554
  metadata: {
@@ -567,6 +577,7 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
567
577
  step: 'gate',
568
578
  decision: reviewGate.decision,
569
579
  confidence: synthesis.overallConfidence,
580
+ changeRequested: synthesis.changeRequested,
570
581
  unresolvedIssues: synthesis.unresolvedIssues,
571
582
  iteration,
572
583
  maxIterations: 10,
@@ -581,6 +592,7 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
581
592
  step: 'gate',
582
593
  decision: reviewGate.decision,
583
594
  confidence: synthesis.overallConfidence,
595
+ changeRequested: synthesis.changeRequested,
584
596
  unresolvedIssues: synthesis.unresolvedIssues,
585
597
  iteration,
586
598
  maxIterations: 10,
@@ -595,6 +607,7 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
595
607
  step: 'gate',
596
608
  decision: reviewGate.decision,
597
609
  confidence: synthesis.overallConfidence,
610
+ changeRequested: synthesis.changeRequested,
598
611
  unresolvedIssues: synthesis.unresolvedIssues,
599
612
  iteration,
600
613
  maxIterations: 10,
@@ -602,6 +615,8 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
602
615
  });
603
616
  }
604
617
  let reworkLine = '- **Rework**: not required';
618
+ let reworkRaw = '';
619
+ let reworkHandoff = '';
605
620
  if (reviewGate.decision === 'REWORK') {
606
621
  context.metadata({
607
622
  title: `⏳ Applying rework (iteration ${iteration}/10)...`,
@@ -617,16 +632,32 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
617
632
  const reworkSecondaryContext = `Original requirements:\n${requirements}\n\n` +
618
633
  `Original batches:\n${batchesRaw}\n\n` +
619
634
  `Latest implementation handoff:\n${reviewInput}\n\n` +
620
- `Prior synthesis raw output:\n${synthRaw}`;
635
+ `Triage output:\n${brief}\n\n` +
636
+ `Review fan-out outputs:\n${reviewFanOutOutput}\n\n` +
637
+ `Latest synthesis output:\n${synthRaw}\n\n` +
638
+ `Gate feedback:\n${reviewGate.feedback || synthesis.reworkInstructions || 'No gate feedback provided.'}\n\n` +
639
+ `Prior review/rework handoff:\n${reviewStageHandoff || 'No prior review handoff recorded.'}`;
621
640
  const reworkDirective = actionFirstReworkPrompt({
622
641
  ...synthesis.stageOutput,
623
642
  status: reviewGate.decision,
624
643
  }, reworkSecondaryContext, reviewGate.feedback || synthesis.reworkInstructions || synthRaw);
625
- const fixRaw = await promptSession(client, runContext, pipelineReworkPrompt(reworkDirective, reworkSecondaryContext), { model: buildModel, agent: 'building', title: reworkTitle });
644
+ reworkRaw = await promptSession(client, runContext, pipelineReworkPrompt(reworkDirective, reworkSecondaryContext), { model: buildModel, agent: 'building', title: reworkTitle });
626
645
  const reworkEndMs = Date.now();
627
646
  reworkLine = `- **Rework**: ${reworkTitle} (${formatElapsed(reworkStartMs, reworkEndMs)})`;
628
- reviewInput = formatImplementationReportForHandoff(parseImplementationReport(fixRaw));
647
+ reworkHandoff = formatImplementationReportForHandoff(parseImplementationReport(reworkRaw));
648
+ reviewInput = reworkHandoff;
629
649
  }
650
+ const reviewIterationHandoff = `### Review Iteration ${iteration}\n\n` +
651
+ `TRIAGE_OUTPUT:\n${brief}\n\n` +
652
+ `FAN_OUT_OUTPUTS:\n${reviewFanOutOutput}\n\n` +
653
+ `SYNTHESIS_OUTPUT:\n${synthRaw}\n\n` +
654
+ `GATE_DECISION: ${reviewGate.decision}\n` +
655
+ `GATE_FEEDBACK:\n${reviewGate.feedback || synthesis.reworkInstructions || 'No gate feedback provided.'}\n\n` +
656
+ `REWORK_OUTPUT:\n${reworkRaw || 'No rework output for this iteration.'}\n\n` +
657
+ `REWORK_HANDOFF:\n${reworkHandoff || 'No rework handoff for this iteration.'}`;
658
+ reviewStageHandoff = reviewStageHandoff
659
+ ? `${reviewStageHandoff}\n\n${reviewIterationHandoff}`
660
+ : reviewIterationHandoff;
630
661
  const reviewStageOutput = {
631
662
  ...synthesis.stageOutput,
632
663
  status: reviewGate.decision,
@@ -637,12 +668,13 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
637
668
  `- **Triage**: ${triageTitle} (${formatElapsed(triageStartMs, triageEndMs)})\n` +
638
669
  `- **Fan-out**: ${fanOutTitles.join(', ')} (${formatElapsed(fanOutStartMs, fanOutEndMs)})\n` +
639
670
  `- **Synthesis**: ${synthTitle} (${formatElapsed(synthStartMs, synthEndMs)})\n` +
640
- `- **Gate**: ${reviewGate.decision} (confidence: ${synthesis.overallConfidence}, unresolved: ${synthesis.unresolvedIssues})\n` +
671
+ `- **Gate**: ${reviewGate.decision} (change requested: ${synthesis.changeRequested ? 'yes' : 'no'}, confidence: ${synthesis.overallConfidence}, unresolved: ${synthesis.unresolvedIssues})\n` +
641
672
  `${reworkLine}\n` +
642
673
  `${formatStructuredStage(reviewStageOutput, reviewGate.decision === 'REWORK')}`);
643
674
  // Notify each review iteration gate decision
644
675
  await notify(`Stage: Reviewing — iteration ${iteration}/10\n` +
645
676
  `${formatStructuredStage(reviewStageOutput, reviewGate.decision === 'REWORK')}\n` +
677
+ `Change Requested: ${synthesis.changeRequested ? 'YES' : 'NO'}\n` +
646
678
  `Confidence: ${synthesis.overallConfidence}%\n` +
647
679
  `Unresolved Issues: ${synthesis.unresolvedIssues}\n` +
648
680
  `Duration: ${formatElapsed(reviewStartMs, Date.now())}`);
@@ -691,7 +723,9 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
691
723
  lastPhase = 'Documentation';
692
724
  const documentationStartMs = Date.now();
693
725
  const documentationIterationDetails = [];
694
- let docInput = `Implementation handoff:\n${implementationHandoff}\n\nLatest implementation handoff after review loop:\n${reviewInput}`;
726
+ let docInput = `Implementation handoff (initial build):\n${implementationHandoff}\n\n` +
727
+ `Implementation handoff (latest after review loop):\n${reviewInput}\n\n` +
728
+ `Review and rework handoff history:\n${reviewStageHandoff || 'No review handoff captured.'}`;
695
729
  let docGate = { decision: 'REWORK' };
696
730
  // Phase start notification
697
731
  await notify(`# Pipeline: Documentation started\n\nStarting documentation phase...\n`);
@@ -744,6 +778,7 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
744
778
  step: 'gate',
745
779
  decision: docGate.decision,
746
780
  confidence: docReview.confidence,
781
+ changeRequested: docReview.changeRequested,
747
782
  unresolvedIssues: docReview.unresolvedIssues,
748
783
  iteration,
749
784
  maxIterations: 5,
@@ -758,6 +793,7 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
758
793
  step: 'gate',
759
794
  decision: docGate.decision,
760
795
  confidence: docReview.confidence,
796
+ changeRequested: docReview.changeRequested,
761
797
  unresolvedIssues: docReview.unresolvedIssues,
762
798
  iteration,
763
799
  maxIterations: 5,
@@ -772,6 +808,7 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
772
808
  step: 'gate',
773
809
  decision: docGate.decision,
774
810
  confidence: docReview.confidence,
811
+ changeRequested: docReview.changeRequested,
775
812
  unresolvedIssues: docReview.unresolvedIssues,
776
813
  iteration,
777
814
  maxIterations: 5,
@@ -785,20 +822,25 @@ Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
785
822
  documentationIterationDetails.push(`### Iteration ${iteration}\n` +
786
823
  `- **Write**: ${writeTitle} (${formatElapsed(writeStartMs, writeEndMs)})\n` +
787
824
  `- **Review**: ${reviewTitle} (${formatElapsed(reviewDocStartMs, reviewDocEndMs)})\n` +
788
- `- **Gate**: ${docGate.decision} (confidence: ${docReview.confidence}, unresolved: ${docReview.unresolvedIssues})\n` +
825
+ `- **Gate**: ${docGate.decision} (change requested: ${docReview.changeRequested ? 'yes' : 'no'}, confidence: ${docReview.confidence}, unresolved: ${docReview.unresolvedIssues})\n` +
789
826
  `${formatStructuredStage(docStageOutput, docGate.decision === 'REWORK')}`);
790
827
  // Notify each documentation iteration gate decision
791
828
  await notify(`Stage: Documentation — iteration ${iteration}/5\n` +
792
829
  `${formatStructuredStage(docStageOutput, docGate.decision === 'REWORK')}\n` +
830
+ `Change Requested: ${docReview.changeRequested ? 'YES' : 'NO'}\n` +
793
831
  `Confidence: ${docReview.confidence}%\n` +
794
832
  `Unresolved Issues: ${docReview.unresolvedIssues}\n` +
795
833
  `Duration: ${formatElapsed(documentationStartMs, Date.now())}`);
796
834
  if (docGate.decision === 'REWORK') {
797
835
  // Feed feedback into the next iteration
836
+ const docIterationHandoff = `Documentation write output:\n${docRaw}\n\n` +
837
+ `Documentation review output:\n${docRevRaw}\n\n` +
838
+ `Documentation gate decision: ${docGate.decision}\n` +
839
+ `Documentation gate feedback:\n${docGate.feedback || docReview.reworkInstructions || docReview.raw}`;
798
840
  docInput = actionFirstReworkPrompt({
799
841
  ...docReview.stageOutput,
800
842
  status: docGate.decision,
801
- }, `${docInput}\n\nDocumentation review feedback:\n${docGate.feedback || docReview.reworkInstructions || docReview.raw}`, docGate.feedback || docReview.reworkInstructions || docReview.raw);
843
+ }, `${docInput}\n\n${docIterationHandoff}`, docGate.feedback || docReview.reworkInstructions || docReview.raw);
802
844
  }
803
845
  }
804
846
  const documentationEndMs = Date.now();
@@ -21,7 +21,7 @@ export declare function validateBatchPrompt(tasks: string): string;
21
21
  export declare function implementBatchPrompt(batches: string): string;
22
22
  export declare function pipelineReworkPrompt(primaryDirective: string, secondaryContext: string): string;
23
23
  export declare function triagePrompt(implementationReport: string): string;
24
- export declare function reviewPrompt(brief: string, modelTag: string): string;
24
+ export declare function reviewPrompt(brief: string, modelTag: string, fullContext: string): string;
25
25
  export declare function reviewSynthesisPrompt(reviews: Array<{
26
26
  tag: string;
27
27
  raw: string;
@@ -154,13 +154,17 @@ Include:
154
154
 
155
155
  Return a structured REVIEW_BRIEF output.`;
156
156
  }
157
- export function reviewPrompt(brief, modelTag) {
157
+ export function reviewPrompt(brief, modelTag, fullContext) {
158
158
  return `[MODEL_TAG:${modelTag}]
159
159
 
160
- Review the current task from the triage brief below for correctness, quality, architecture validity, testing, security, and edge cases.
160
+ Review the current task using both the triage brief and the full upstream context below for correctness, quality, architecture validity, testing, security, and edge cases.
161
161
 
162
+ TRIAGE_BRIEF:
162
163
  ${brief}
163
164
 
165
+ FULL_UPSTREAM_CONTEXT:
166
+ ${fullContext}
167
+
164
168
  Classify findings as critical/high/medium/low and include a confidence score (0-100).
165
169
 
166
170
  Return a structured review report.`;
@@ -178,8 +182,9 @@ Required output fields (strict):
178
182
  4. ISSUES
179
183
  5. ACTION_ITEMS (required when verdict is REWORK_REQUIRED)
180
184
  6. CONFIDENCE (0-100)
181
- 7. UNRESOLVED_ISSUES (integer)
182
- 8. VERDICT (APPROVED or REWORK_REQUIRED)
185
+ 7. CHANGE_REQUESTED=YES|NO
186
+ 8. UNRESOLVED_ISSUES (integer)
187
+ 9. VERDICT (APPROVED or REWORK_REQUIRED)
183
188
 
184
189
  When rework is needed, ACTION_ITEMS must be concrete, testable, and ordered by dependency.`;
185
190
  }
@@ -215,9 +220,10 @@ Required output fields (strict):
215
220
  3. CHECKLIST
216
221
  4. ISSUES
217
222
  5. ACTION_ITEMS (required when verdict is REWORK_REQUIRED)
218
- 6. VERDICT (APPROVED or REWORK_REQUIRED)
219
- 7. UNRESOLVED_DOCUMENTATION_ISSUES (integer)
220
- 8. CONFIDENCE (0-100)`;
223
+ 6. CHANGE_REQUESTED=YES|NO
224
+ 7. VERDICT (APPROVED or REWORK_REQUIRED)
225
+ 8. UNRESOLVED_DOCUMENTATION_ISSUES (integer)
226
+ 9. CONFIDENCE (0-100)`;
221
227
  }
222
228
  // ---------------------------------------------------------------------------
223
229
  // Mini-loop specific
@@ -17,28 +17,25 @@ export declare function evaluatePlanningGate(consensus: ConsensusPlan): GateResu
17
17
  /**
18
18
  * Evaluate the review approval gate.
19
19
  *
20
- * - confidence >=95 AND unresolvedIssues == 0APPROVED
21
- * - vacuous review (0 issues, 0 action items) → APPROVED (nothing to fix)
20
+ * - change requested OR confidence < 90REWORK
22
21
  * - iteration >= maxIterations → ESCALATE
23
- * - otherwise → REWORK
22
+ * - otherwise → APPROVED
24
23
  */
25
24
  export declare function evaluateReviewGate(synthesis: ReviewSynthesis, iteration: number, maxIterations?: number): GateResult;
26
25
  /**
27
26
  * Evaluate the documentation approval gate.
28
27
  *
29
- * - confidence >95, no change requested, 0 unresolved APPROVED
30
- * - vacuous review (0 issues, 0 action items) → APPROVED (nothing to fix)
28
+ * - change requested OR confidence < 90REWORK
31
29
  * - iteration >= maxIterations → ESCALATE
32
- * - otherwise → REWORK
30
+ * - otherwise → APPROVED
33
31
  */
34
32
  export declare function evaluateDocGate(review: DocReview, iteration: number, maxIterations?: number): GateResult;
35
33
  /**
36
34
  * Evaluate the mini-loop implementation gate.
37
35
  *
38
- * - confidence >95, no change requested, 0 blocking issuesAPPROVED
39
- * - vacuous review (0 issues, 0 action items) → APPROVED (nothing to fix)
36
+ * - change requested OR confidence < 90 REWORK
40
37
  * - iteration >= maxIterations → ESCALATE
41
- * - otherwise → REWORK
38
+ * - otherwise → APPROVED
42
39
  */
43
40
  export declare function evaluateMiniLoopImplGate(review: MiniLoopReview, iteration: number, maxIterations?: number): GateResult;
44
41
  /**
@@ -5,28 +5,29 @@
5
5
  * threshold logic directly. The LLM still produces the synthesis; the
6
6
  * gate decision is computed here.
7
7
  */
8
+ const REWORK_CONFIDENCE_THRESHOLD = 90;
9
+ function asBulletList(items, empty) {
10
+ if (items.length === 0) {
11
+ return `- ${empty}`;
12
+ }
13
+ return items.map((item) => `- ${item}`).join('\n');
14
+ }
8
15
  function formatActionItems(actionItems) {
9
16
  if (actionItems.length === 0) {
10
- return '';
17
+ return '- No explicit action items provided.';
11
18
  }
12
19
  return actionItems.map((item, index) => `${index + 1}. ${item}`).join('\n');
13
20
  }
14
- // ---------------------------------------------------------------------------
15
- // Vacuous review detection
16
- // ---------------------------------------------------------------------------
17
- /**
18
- * Detect whether a review is "vacuous" — the reviewer produced no
19
- * substantive feedback. This happens when the LLM returns no
20
- * structured fields or an empty review: confidence falls back to 0,
21
- * unresolvedIssues is 0, and there are no real action items.
22
- *
23
- * When a review is vacuous the gate should auto-approve rather than
24
- * triggering an infinite rework loop with no actionable feedback.
25
- */
26
- function isVacuousReview(unresolvedIssues, stageOutput) {
27
- return (unresolvedIssues === 0 &&
28
- stageOutput.actionItems.length === 0 &&
29
- stageOutput.issues.length === 0);
21
+ function formatFullStageFeedback(stageOutput, rawOutput) {
22
+ const normalizedRaw = rawOutput.trim().length > 0 ? rawOutput.trim() : stageOutput.raw.trim();
23
+ return [
24
+ `STATUS: ${stageOutput.status}`,
25
+ `SUMMARY: ${stageOutput.summary}`,
26
+ `CHECKLIST:\n${asBulletList(stageOutput.checklist, 'No checklist provided.')}`,
27
+ `ISSUES:\n${asBulletList(stageOutput.issues, 'No issues reported.')}`,
28
+ `ACTION_ITEMS:\n${formatActionItems(stageOutput.actionItems)}`,
29
+ `RAW_STAGE_OUTPUT:\n${normalizedRaw || 'No raw output provided.'}`,
30
+ ].join('\n\n');
30
31
  }
31
32
  // ---------------------------------------------------------------------------
32
33
  // Planning gate
@@ -59,32 +60,25 @@ export function evaluatePlanningGate(consensus) {
59
60
  /**
60
61
  * Evaluate the review approval gate.
61
62
  *
62
- * - confidence >=95 AND unresolvedIssues == 0APPROVED
63
- * - vacuous review (0 issues, 0 action items) → APPROVED (nothing to fix)
63
+ * - change requested OR confidence < 90REWORK
64
64
  * - iteration >= maxIterations → ESCALATE
65
- * - otherwise → REWORK
65
+ * - otherwise → APPROVED
66
66
  */
67
67
  export function evaluateReviewGate(synthesis, iteration, maxIterations = 10) {
68
- if (synthesis.overallConfidence >= 95 && synthesis.unresolvedIssues === 0) {
69
- return { decision: 'APPROVED' };
70
- }
71
- if (isVacuousReview(synthesis.unresolvedIssues, synthesis.stageOutput)) {
68
+ const needsRework = synthesis.changeRequested || synthesis.overallConfidence < REWORK_CONFIDENCE_THRESHOLD;
69
+ if (!needsRework) {
72
70
  return { decision: 'APPROVED' };
73
71
  }
74
72
  if (iteration >= maxIterations) {
75
73
  return {
76
74
  decision: 'ESCALATE',
77
- reason: `Max review iterations reached (${maxIterations}). Confidence: ${synthesis.overallConfidence}, unresolved: ${synthesis.unresolvedIssues}`,
75
+ reason: `Max review iterations reached (${maxIterations}). ` +
76
+ `Change requested: ${synthesis.changeRequested}, confidence: ${synthesis.overallConfidence}, unresolved: ${synthesis.unresolvedIssues}`,
78
77
  };
79
78
  }
80
- const actionFirstFeedback = synthesis.stageOutput.actionItems.length > 0
81
- ? `ACTION_ITEMS:\n${formatActionItems(synthesis.stageOutput.actionItems)}\n\nSUMMARY:\n${synthesis.stageOutput.summary}`
82
- : undefined;
83
79
  return {
84
80
  decision: 'REWORK',
85
- feedback: actionFirstFeedback ??
86
- synthesis.reworkInstructions ??
87
- `Confidence ${synthesis.overallConfidence}/100 with ${synthesis.unresolvedIssues} unresolved issues.`,
81
+ feedback: formatFullStageFeedback(synthesis.stageOutput, synthesis.raw),
88
82
  };
89
83
  }
90
84
  // ---------------------------------------------------------------------------
@@ -93,32 +87,25 @@ export function evaluateReviewGate(synthesis, iteration, maxIterations = 10) {
93
87
  /**
94
88
  * Evaluate the documentation approval gate.
95
89
  *
96
- * - confidence >95, no change requested, 0 unresolved APPROVED
97
- * - vacuous review (0 issues, 0 action items) → APPROVED (nothing to fix)
90
+ * - change requested OR confidence < 90REWORK
98
91
  * - iteration >= maxIterations → ESCALATE
99
- * - otherwise → REWORK
92
+ * - otherwise → APPROVED
100
93
  */
101
94
  export function evaluateDocGate(review, iteration, maxIterations = 5) {
102
- if (review.verdict === 'APPROVED' && review.unresolvedIssues === 0 && review.confidence > 95) {
103
- return { decision: 'APPROVED' };
104
- }
105
- if (isVacuousReview(review.unresolvedIssues, review.stageOutput)) {
95
+ const needsRework = review.changeRequested || review.confidence < REWORK_CONFIDENCE_THRESHOLD;
96
+ if (!needsRework) {
106
97
  return { decision: 'APPROVED' };
107
98
  }
108
99
  if (iteration >= maxIterations) {
109
100
  return {
110
101
  decision: 'ESCALATE',
111
- reason: `Max doc iterations reached (${maxIterations}). Confidence: ${review.confidence}, unresolved: ${review.unresolvedIssues}`,
102
+ reason: `Max doc iterations reached (${maxIterations}). ` +
103
+ `Change requested: ${review.changeRequested}, confidence: ${review.confidence}, unresolved: ${review.unresolvedIssues}`,
112
104
  };
113
105
  }
114
- const actionFirstFeedback = review.stageOutput.actionItems.length > 0
115
- ? `ACTION_ITEMS:\n${formatActionItems(review.stageOutput.actionItems)}\n\nSUMMARY:\n${review.stageOutput.summary}`
116
- : undefined;
117
106
  return {
118
107
  decision: 'REWORK',
119
- feedback: actionFirstFeedback ??
120
- review.reworkInstructions ??
121
- `Documentation review verdict: ${review.verdict}. ${review.unresolvedIssues} unresolved issues.`,
108
+ feedback: formatFullStageFeedback(review.stageOutput, review.raw),
122
109
  };
123
110
  }
124
111
  // ---------------------------------------------------------------------------
@@ -127,32 +114,25 @@ export function evaluateDocGate(review, iteration, maxIterations = 5) {
127
114
  /**
128
115
  * Evaluate the mini-loop implementation gate.
129
116
  *
130
- * - confidence >95, no change requested, 0 blocking issuesAPPROVED
131
- * - vacuous review (0 issues, 0 action items) → APPROVED (nothing to fix)
117
+ * - change requested OR confidence < 90 REWORK
132
118
  * - iteration >= maxIterations → ESCALATE
133
- * - otherwise → REWORK
119
+ * - otherwise → APPROVED
134
120
  */
135
121
  export function evaluateMiniLoopImplGate(review, iteration, maxIterations = 10) {
136
- if (review.confidence > 95 && !review.changeRequested && review.unresolvedIssues === 0) {
137
- return { decision: 'APPROVED' };
138
- }
139
- if (isVacuousReview(review.unresolvedIssues, review.stageOutput)) {
122
+ const needsRework = review.changeRequested || review.confidence < REWORK_CONFIDENCE_THRESHOLD;
123
+ if (!needsRework) {
140
124
  return { decision: 'APPROVED' };
141
125
  }
142
126
  if (iteration >= maxIterations) {
143
127
  return {
144
128
  decision: 'ESCALATE',
145
- reason: `Max mini-loop iterations reached (${maxIterations}). Confidence: ${review.confidence}, unresolved: ${review.unresolvedIssues}`,
129
+ reason: `Max mini-loop iterations reached (${maxIterations}). ` +
130
+ `Change requested: ${review.changeRequested}, confidence: ${review.confidence}, unresolved: ${review.unresolvedIssues}`,
146
131
  };
147
132
  }
148
- const actionFirstFeedback = review.stageOutput.actionItems.length > 0
149
- ? `ACTION_ITEMS:\n${formatActionItems(review.stageOutput.actionItems)}\n\nSUMMARY:\n${review.stageOutput.summary}`
150
- : undefined;
151
133
  return {
152
134
  decision: 'REWORK',
153
- feedback: actionFirstFeedback ??
154
- review.reworkInstructions ??
155
- `Change requested: ${review.changeRequested}, confidence: ${review.confidence}, unresolved: ${review.unresolvedIssues}`,
135
+ feedback: formatFullStageFeedback(review.stageOutput, review.raw),
156
136
  };
157
137
  }
158
138
  // ---------------------------------------------------------------------------
@@ -105,6 +105,7 @@ export interface ReviewSynthesis {
105
105
  overallConfidence: number;
106
106
  consolidatedFindings: string;
107
107
  unresolvedIssues: number;
108
+ changeRequested: boolean;
108
109
  verdict: 'APPROVED' | 'REWORK_REQUIRED';
109
110
  reworkInstructions?: string;
110
111
  stageOutput: StageOutput;
@@ -124,6 +125,7 @@ export interface DocReview {
124
125
  verdict: 'APPROVED' | 'REWORK_REQUIRED';
125
126
  unresolvedIssues: number;
126
127
  confidence: number;
128
+ changeRequested: boolean;
127
129
  reworkInstructions?: string;
128
130
  stageOutput: StageOutput;
129
131
  raw: string;
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.8",
4
+ "version": "0.10.9",
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",