@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.
- package/dist/tools/mini-loop.js +33 -5
- package/dist/tools/parsers.js +8 -2
- package/dist/tools/pipeline.js +50 -8
- package/dist/tools/prompts.d.ts +1 -1
- package/dist/tools/prompts.js +13 -7
- package/dist/workflow/gate-evaluator.d.ts +6 -6
- package/dist/workflow/gate-evaluator.js +40 -31
- package/dist/workflow/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/tools/mini-loop.js
CHANGED
|
@@ -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
|
|
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\
|
|
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\
|
|
521
|
+
}, `${docInput}\n\n${docIterationHandoff}`, docGate.feedback || docReview.reworkInstructions || docReview.raw);
|
|
494
522
|
}
|
|
495
523
|
}
|
|
496
524
|
const documentationEndMs = Date.now();
|
package/dist/tools/parsers.js
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
package/dist/tools/pipeline.js
CHANGED
|
@@ -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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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\
|
|
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\
|
|
843
|
+
}, `${docInput}\n\n${docIterationHandoff}`, docGate.feedback || docReview.reworkInstructions || docReview.raw);
|
|
802
844
|
}
|
|
803
845
|
}
|
|
804
846
|
const documentationEndMs = Date.now();
|
package/dist/tools/prompts.d.ts
CHANGED
|
@@ -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;
|
package/dist/tools/prompts.js
CHANGED
|
@@ -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
|
|
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.
|
|
182
|
-
8.
|
|
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.
|
|
219
|
-
7.
|
|
220
|
-
8.
|
|
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
|
-
* -
|
|
20
|
+
* - change requested OR confidence < 90 → REWORK
|
|
21
21
|
* - iteration >= maxIterations → ESCALATE
|
|
22
|
-
* - otherwise →
|
|
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
|
-
* -
|
|
28
|
+
* - change requested OR confidence < 90 → REWORK
|
|
29
29
|
* - iteration >= maxIterations → ESCALATE
|
|
30
|
-
* - otherwise →
|
|
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
|
-
* -
|
|
36
|
+
* - change requested OR confidence < 90 → REWORK
|
|
37
37
|
* - iteration >= maxIterations → ESCALATE
|
|
38
|
-
* - otherwise →
|
|
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
|
-
* -
|
|
63
|
+
* - change requested OR confidence < 90 → REWORK
|
|
46
64
|
* - iteration >= maxIterations → ESCALATE
|
|
47
|
-
* - otherwise →
|
|
65
|
+
* - otherwise → APPROVED
|
|
48
66
|
*/
|
|
49
67
|
export function evaluateReviewGate(synthesis, iteration, maxIterations = 10) {
|
|
50
|
-
|
|
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}).
|
|
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:
|
|
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
|
-
* -
|
|
90
|
+
* - change requested OR confidence < 90 → REWORK
|
|
76
91
|
* - iteration >= maxIterations → ESCALATE
|
|
77
|
-
* - otherwise →
|
|
92
|
+
* - otherwise → APPROVED
|
|
78
93
|
*/
|
|
79
94
|
export function evaluateDocGate(review, iteration, maxIterations = 5) {
|
|
80
|
-
|
|
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}).
|
|
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:
|
|
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
|
-
* -
|
|
117
|
+
* - change requested OR confidence < 90 → REWORK
|
|
106
118
|
* - iteration >= maxIterations → ESCALATE
|
|
107
|
-
* - otherwise →
|
|
119
|
+
* - otherwise → APPROVED
|
|
108
120
|
*/
|
|
109
121
|
export function evaluateMiniLoopImplGate(review, iteration, maxIterations = 10) {
|
|
110
|
-
|
|
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}).
|
|
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:
|
|
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
|
// ---------------------------------------------------------------------------
|
package/dist/workflow/types.d.ts
CHANGED
|
@@ -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.
|
|
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",
|