@syntesseraai/opencode-feature-factory 0.10.7 → 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,25 +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
20
+ * - change requested OR confidence < 90REWORK
21
21
  * - iteration >= maxIterations → ESCALATE
22
- * - otherwise → REWORK
22
+ * - otherwise → APPROVED
23
23
  */
24
24
  export declare function evaluateReviewGate(synthesis: ReviewSynthesis, iteration: number, maxIterations?: number): GateResult;
25
25
  /**
26
26
  * Evaluate the documentation approval gate.
27
27
  *
28
- * - confidence >95, no change requested, 0 unresolved APPROVED
28
+ * - change requested OR confidence < 90REWORK
29
29
  * - iteration >= maxIterations → ESCALATE
30
- * - otherwise → REWORK
30
+ * - otherwise → APPROVED
31
31
  */
32
32
  export declare function evaluateDocGate(review: DocReview, iteration: number, maxIterations?: number): GateResult;
33
33
  /**
34
34
  * Evaluate the mini-loop implementation gate.
35
35
  *
36
- * - confidence >95, no change requested, 0 blocking issuesAPPROVED
36
+ * - change requested OR confidence < 90 REWORK
37
37
  * - iteration >= maxIterations → ESCALATE
38
- * - otherwise → REWORK
38
+ * - otherwise → APPROVED
39
39
  */
40
40
  export declare function evaluateMiniLoopImplGate(review: MiniLoopReview, iteration: number, maxIterations?: number): GateResult;
41
41
  /**
@@ -5,12 +5,30 @@
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
  }
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');
31
+ }
14
32
  // ---------------------------------------------------------------------------
15
33
  // Planning gate
16
34
  // ---------------------------------------------------------------------------
@@ -42,28 +60,25 @@ export function evaluatePlanningGate(consensus) {
42
60
  /**
43
61
  * Evaluate the review approval gate.
44
62
  *
45
- * - confidence >=95 AND unresolvedIssues == 0APPROVED
63
+ * - change requested OR confidence < 90REWORK
46
64
  * - iteration >= maxIterations → ESCALATE
47
- * - otherwise → REWORK
65
+ * - otherwise → APPROVED
48
66
  */
49
67
  export function evaluateReviewGate(synthesis, iteration, maxIterations = 10) {
50
- if (synthesis.overallConfidence >= 95 && synthesis.unresolvedIssues === 0) {
68
+ const needsRework = synthesis.changeRequested || synthesis.overallConfidence < REWORK_CONFIDENCE_THRESHOLD;
69
+ if (!needsRework) {
51
70
  return { decision: 'APPROVED' };
52
71
  }
53
72
  if (iteration >= maxIterations) {
54
73
  return {
55
74
  decision: 'ESCALATE',
56
- 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}`,
57
77
  };
58
78
  }
59
- const actionFirstFeedback = synthesis.stageOutput.actionItems.length > 0
60
- ? `ACTION_ITEMS:\n${formatActionItems(synthesis.stageOutput.actionItems)}\n\nSUMMARY:\n${synthesis.stageOutput.summary}`
61
- : undefined;
62
79
  return {
63
80
  decision: 'REWORK',
64
- feedback: actionFirstFeedback ??
65
- synthesis.reworkInstructions ??
66
- `Confidence ${synthesis.overallConfidence}/100 with ${synthesis.unresolvedIssues} unresolved issues.`,
81
+ feedback: formatFullStageFeedback(synthesis.stageOutput, synthesis.raw),
67
82
  };
68
83
  }
69
84
  // ---------------------------------------------------------------------------
@@ -72,28 +87,25 @@ export function evaluateReviewGate(synthesis, iteration, maxIterations = 10) {
72
87
  /**
73
88
  * Evaluate the documentation approval gate.
74
89
  *
75
- * - confidence >95, no change requested, 0 unresolved APPROVED
90
+ * - change requested OR confidence < 90REWORK
76
91
  * - iteration >= maxIterations → ESCALATE
77
- * - otherwise → REWORK
92
+ * - otherwise → APPROVED
78
93
  */
79
94
  export function evaluateDocGate(review, iteration, maxIterations = 5) {
80
- if (review.verdict === 'APPROVED' && review.unresolvedIssues === 0 && review.confidence > 95) {
95
+ const needsRework = review.changeRequested || review.confidence < REWORK_CONFIDENCE_THRESHOLD;
96
+ if (!needsRework) {
81
97
  return { decision: 'APPROVED' };
82
98
  }
83
99
  if (iteration >= maxIterations) {
84
100
  return {
85
101
  decision: 'ESCALATE',
86
- 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}`,
87
104
  };
88
105
  }
89
- const actionFirstFeedback = review.stageOutput.actionItems.length > 0
90
- ? `ACTION_ITEMS:\n${formatActionItems(review.stageOutput.actionItems)}\n\nSUMMARY:\n${review.stageOutput.summary}`
91
- : undefined;
92
106
  return {
93
107
  decision: 'REWORK',
94
- feedback: actionFirstFeedback ??
95
- review.reworkInstructions ??
96
- `Documentation review verdict: ${review.verdict}. ${review.unresolvedIssues} unresolved issues.`,
108
+ feedback: formatFullStageFeedback(review.stageOutput, review.raw),
97
109
  };
98
110
  }
99
111
  // ---------------------------------------------------------------------------
@@ -102,28 +114,25 @@ export function evaluateDocGate(review, iteration, maxIterations = 5) {
102
114
  /**
103
115
  * Evaluate the mini-loop implementation gate.
104
116
  *
105
- * - confidence >95, no change requested, 0 blocking issuesAPPROVED
117
+ * - change requested OR confidence < 90 REWORK
106
118
  * - iteration >= maxIterations → ESCALATE
107
- * - otherwise → REWORK
119
+ * - otherwise → APPROVED
108
120
  */
109
121
  export function evaluateMiniLoopImplGate(review, iteration, maxIterations = 10) {
110
- if (review.confidence > 95 && !review.changeRequested && review.unresolvedIssues === 0) {
122
+ const needsRework = review.changeRequested || review.confidence < REWORK_CONFIDENCE_THRESHOLD;
123
+ if (!needsRework) {
111
124
  return { decision: 'APPROVED' };
112
125
  }
113
126
  if (iteration >= maxIterations) {
114
127
  return {
115
128
  decision: 'ESCALATE',
116
- 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}`,
117
131
  };
118
132
  }
119
- const actionFirstFeedback = review.stageOutput.actionItems.length > 0
120
- ? `ACTION_ITEMS:\n${formatActionItems(review.stageOutput.actionItems)}\n\nSUMMARY:\n${review.stageOutput.summary}`
121
- : undefined;
122
133
  return {
123
134
  decision: 'REWORK',
124
- feedback: actionFirstFeedback ??
125
- review.reworkInstructions ??
126
- `Change requested: ${review.changeRequested}, confidence: ${review.confidence}, unresolved: ${review.unresolvedIssues}`,
135
+ feedback: formatFullStageFeedback(review.stageOutput, review.raw),
127
136
  };
128
137
  }
129
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.7",
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",