@syntesseraai/opencode-feature-factory 0.10.6 → 0.10.7
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 +118 -19
- package/dist/tools/parsers.d.ts +5 -8
- package/dist/tools/parsers.js +193 -13
- package/dist/tools/pipeline.js +163 -34
- package/dist/tools/prompts.d.ts +11 -0
- package/dist/tools/prompts.js +163 -23
- package/dist/workflow/gate-evaluator.d.ts +2 -7
- package/dist/workflow/gate-evaluator.js +21 -3
- package/dist/workflow/types.d.ts +23 -7
- package/package.json +1 -1
package/dist/tools/pipeline.js
CHANGED
|
@@ -11,15 +11,40 @@
|
|
|
11
11
|
* to the parent session via `promptAsync(noReply: true)`.
|
|
12
12
|
*/
|
|
13
13
|
import { tool } from '@opencode-ai/plugin/tool';
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
14
15
|
import { fanOut, promptSession, notifyParent, evaluatePlanningGate, evaluateReviewGate, evaluateDocGate, PLANNING_MODELS, REVIEW_MODELS, ORCHESTRATOR_MODEL, BUILD_MODEL, DOC_MODEL, VALIDATE_MODEL, DOC_REVIEW_MODEL, parseModelString, parseNamedModels, } from '../workflow/orchestrator.js';
|
|
15
|
-
import { planningPrompt, synthesisPrompt, breakdownPrompt, validateBatchPrompt, implementBatchPrompt, triagePrompt, reviewPrompt, reviewSynthesisPrompt, documentPrompt, docReviewPrompt, ciFixPrompt, } from './prompts.js';
|
|
16
|
+
import { planningPrompt, synthesisPrompt, breakdownPrompt, validateBatchPrompt, implementBatchPrompt, pipelineReworkPrompt, triagePrompt, reviewPrompt, reviewSynthesisPrompt, documentPrompt, docReviewPrompt, ciFixPrompt, runNamePrompt, actionFirstReworkPrompt, } from './prompts.js';
|
|
16
17
|
import { ciScriptExists, runCI } from '../workflow/ci-runner.js';
|
|
17
18
|
import { cleanupWorktree, resolveRunDirectory } from '../workflow/run-isolation.js';
|
|
18
|
-
import { parseConsensusPlan, parseReviewSynthesis, parseImplementationReport, parseDocReview, } from './parsers.js';
|
|
19
|
+
import { parseConsensusPlan, parseReviewSynthesis, parseImplementationReport, formatImplementationReportForHandoff, parseDocReview, } from './parsers.js';
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// Tool factory — needs the SDK client from plugin init
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
const formatElapsed = (startMs, endMs) => `${((endMs - startMs) / 1000).toFixed(1)}s`;
|
|
24
|
+
const normalizeRunName = (raw, fallback) => {
|
|
25
|
+
const normalized = raw
|
|
26
|
+
.trim()
|
|
27
|
+
.replace(/^[-#*\s]+/, '')
|
|
28
|
+
.replace(/\s+/g, ' ')
|
|
29
|
+
.slice(0, 80);
|
|
30
|
+
return normalized.length > 0 ? normalized : fallback;
|
|
31
|
+
};
|
|
32
|
+
const asBulletList = (items, empty) => {
|
|
33
|
+
if (items.length === 0) {
|
|
34
|
+
return `- ${empty}`;
|
|
35
|
+
}
|
|
36
|
+
return items.map((item) => `- ${item}`).join('\n');
|
|
37
|
+
};
|
|
38
|
+
const formatStructuredStage = (stage, includeActionItems) => {
|
|
39
|
+
const base = `STATUS: ${stage.status}\n` +
|
|
40
|
+
`SUMMARY: ${stage.summary}\n` +
|
|
41
|
+
`CHECKLIST:\n${asBulletList(stage.checklist, 'No checklist provided.')}\n` +
|
|
42
|
+
`ISSUES:\n${asBulletList(stage.issues, 'No issues reported.')}`;
|
|
43
|
+
if (!includeActionItems) {
|
|
44
|
+
return base;
|
|
45
|
+
}
|
|
46
|
+
return `${base}\nACTION_ITEMS:\n${asBulletList(stage.actionItems, 'No action items required.')}`;
|
|
47
|
+
};
|
|
23
48
|
export function createPipelineTool(client) {
|
|
24
49
|
return tool({
|
|
25
50
|
description: 'Run the full Feature Factory pipeline: multi-model planning → build → review → documentation. ' +
|
|
@@ -72,6 +97,8 @@ export function createPipelineTool(client) {
|
|
|
72
97
|
const callerSessionId = context.sessionID;
|
|
73
98
|
const agent = context.agent;
|
|
74
99
|
const { requirements } = args;
|
|
100
|
+
const runId = randomUUID();
|
|
101
|
+
let runName = normalizeRunName(requirements.split('\n')[0] || '', 'Pipeline Workflow');
|
|
75
102
|
// Resolve models — use provided overrides or fall back to defaults
|
|
76
103
|
const planModels = args.planning_models
|
|
77
104
|
? parseNamedModels(args.planning_models)
|
|
@@ -108,6 +135,18 @@ export function createPipelineTool(client) {
|
|
|
108
135
|
sessionId: callerSessionId,
|
|
109
136
|
directory: runDirectoryContext.runDirectory,
|
|
110
137
|
};
|
|
138
|
+
try {
|
|
139
|
+
const runNameRaw = await promptSession(client, runContext, runNamePrompt('Pipeline', requirements), {
|
|
140
|
+
model: orchestratorModel,
|
|
141
|
+
agent: 'planning',
|
|
142
|
+
title: 'ff-pipeline-run-name',
|
|
143
|
+
});
|
|
144
|
+
runName = normalizeRunName(runNameRaw, runName);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Keep fallback name derived from requirements when name generation fails.
|
|
148
|
+
}
|
|
149
|
+
const runHeader = `Pipeline: ${runName} (${runId})`;
|
|
111
150
|
// Fire-and-forget: run full orchestration in background
|
|
112
151
|
let lastPhase = 'init';
|
|
113
152
|
const asyncOrchestration = async () => {
|
|
@@ -118,7 +157,7 @@ export function createPipelineTool(client) {
|
|
|
118
157
|
report.push(`## ${phase}\n${msg}`);
|
|
119
158
|
};
|
|
120
159
|
// Notify helper bound to this session
|
|
121
|
-
const notify = (message, options) => notifyParent(client, runContext, agent, message
|
|
160
|
+
const notify = (message, options) => notifyParent(client, runContext, agent, `# ${runHeader}\n\n${message}`, options);
|
|
122
161
|
// ===================================================================
|
|
123
162
|
// PHASE 1: PLANNING (fan-out → synthesize → gate, loop up to 5)
|
|
124
163
|
// ===================================================================
|
|
@@ -213,13 +252,30 @@ export function createPipelineTool(client) {
|
|
|
213
252
|
planningGate.feedback ||
|
|
214
253
|
consensus.divergentElements ||
|
|
215
254
|
'N/A';
|
|
255
|
+
const planningStageOutput = {
|
|
256
|
+
status: planningGate.decision,
|
|
257
|
+
summary: `Planning iteration ${iteration} produced consensus score ${consensus.consensusScore}%.`,
|
|
258
|
+
checklist: [
|
|
259
|
+
`Fan-out models: ${fanOutTitles.join(', ')}`,
|
|
260
|
+
`Synthesis session: ${synthesisTitle}`,
|
|
261
|
+
`Gate decision: ${planningGate.decision}`,
|
|
262
|
+
],
|
|
263
|
+
issues: planningGate.decision === 'APPROVED'
|
|
264
|
+
? []
|
|
265
|
+
: [feedback],
|
|
266
|
+
actionItems: planningGate.decision === 'REWORK'
|
|
267
|
+
? [feedback]
|
|
268
|
+
: [],
|
|
269
|
+
raw: feedback,
|
|
270
|
+
};
|
|
216
271
|
planningIterationDetails.push(`### Iteration ${iteration}\n` +
|
|
217
272
|
`- **Fan-out**: ${fanOutTitles.join(', ')} (${formatElapsed(fanOutStartMs, fanOutEndMs)})\n` +
|
|
218
273
|
`- **Synthesis**: ${synthesisTitle} (${formatElapsed(synthesisStartMs, synthesisEndMs)})\n` +
|
|
219
|
-
|
|
220
|
-
`- **Feedback**: ${feedback}`);
|
|
274
|
+
`${formatStructuredStage(planningStageOutput, planningGate.decision === 'REWORK')}`);
|
|
221
275
|
// Notify each planning iteration gate decision
|
|
222
|
-
await notify(
|
|
276
|
+
await notify(`Stage: Planning — iteration ${iteration}/5\n` +
|
|
277
|
+
`${formatStructuredStage(planningStageOutput, planningGate.decision === 'REWORK')}\n` +
|
|
278
|
+
`Duration: ${formatElapsed(planningStartMs, Date.now())}`);
|
|
223
279
|
if (planningGate.decision === 'BLOCKED') {
|
|
224
280
|
const planningEndMs = Date.now();
|
|
225
281
|
addReport('Planning', `${planningIterationDetails.join('\n\n')}\n\n**Outcome**: BLOCKED: ${planningGate.reason}\n**Phase time**: ${formatElapsed(planningStartMs, planningEndMs)}`);
|
|
@@ -314,6 +370,18 @@ export function createPipelineTool(client) {
|
|
|
314
370
|
});
|
|
315
371
|
const implementEndMs = Date.now();
|
|
316
372
|
const implementation = parseImplementationReport(implRaw);
|
|
373
|
+
const implementationHandoff = formatImplementationReportForHandoff(implementation);
|
|
374
|
+
const buildingStageOutput = {
|
|
375
|
+
...implementation.stageOutput,
|
|
376
|
+
status: implementation.stageOutput.status ||
|
|
377
|
+
(implementation.testsPassed && implementation.openIssues.length === 0
|
|
378
|
+
? 'APPROVED'
|
|
379
|
+
: 'REWORK_REQUIRED'),
|
|
380
|
+
};
|
|
381
|
+
await notify(`Stage: Building — implementation handoff
|
|
382
|
+
${formatStructuredStage(buildingStageOutput, buildingStageOutput.actionItems.length > 0)}
|
|
383
|
+
Files Changed: ${implementation.filesChanged.length}
|
|
384
|
+
Tests Passed: ${implementation.testsPassed}`);
|
|
317
385
|
// ---------------------------------------------------------------
|
|
318
386
|
// CI validation (deterministic subprocess — not an LLM prompt)
|
|
319
387
|
// ---------------------------------------------------------------
|
|
@@ -334,14 +402,32 @@ export function createPipelineTool(client) {
|
|
|
334
402
|
maxCiAttempts,
|
|
335
403
|
},
|
|
336
404
|
});
|
|
337
|
-
await notify(`# Pipeline: CI validation — attempt ${ciAttempt}/${maxCiAttempts}\n`);
|
|
338
405
|
const ciResult = await runCI(ciDir);
|
|
339
406
|
if (ciResult.passed) {
|
|
340
|
-
|
|
407
|
+
const ciStageOutput = {
|
|
408
|
+
status: 'APPROVED',
|
|
409
|
+
summary: `CI validation passed on attempt ${ciAttempt}/${maxCiAttempts}.`,
|
|
410
|
+
checklist: ['ff-ci.sh executed in run directory', 'No failing lint/typecheck/test/build checks'],
|
|
411
|
+
issues: [],
|
|
412
|
+
actionItems: [],
|
|
413
|
+
raw: 'CI passed',
|
|
414
|
+
};
|
|
415
|
+
await notify(`Stage: CI Validation — attempt ${ciAttempt}/${maxCiAttempts}\n` +
|
|
416
|
+
`${formatStructuredStage(ciStageOutput, false)}`);
|
|
341
417
|
break;
|
|
342
418
|
}
|
|
343
419
|
// CI failed
|
|
344
|
-
|
|
420
|
+
const ciFailureStage = {
|
|
421
|
+
status: 'REWORK',
|
|
422
|
+
summary: `CI failed on attempt ${ciAttempt}/${maxCiAttempts}.`,
|
|
423
|
+
checklist: ['ff-ci.sh executed in run directory'],
|
|
424
|
+
issues: ['CI reported failing checks.'],
|
|
425
|
+
actionItems: ['Fix CI failures before continuing to the next attempt.'],
|
|
426
|
+
raw: ciResult.output,
|
|
427
|
+
};
|
|
428
|
+
await notify(`Stage: CI Validation — attempt ${ciAttempt}/${maxCiAttempts}\n` +
|
|
429
|
+
`${formatStructuredStage(ciFailureStage, true)}\n\n` +
|
|
430
|
+
`\`\`\`\n${ciResult.output}\n\`\`\`\n`);
|
|
345
431
|
if (ciAttempt < maxCiAttempts) {
|
|
346
432
|
// Ask build model to fix the CI failures
|
|
347
433
|
context.metadata({
|
|
@@ -380,22 +466,40 @@ export function createPipelineTool(client) {
|
|
|
380
466
|
}
|
|
381
467
|
}
|
|
382
468
|
const buildingEndMs = Date.now();
|
|
383
|
-
addReport('Building', `### Execution
|
|
384
|
-
|
|
385
|
-
`- **
|
|
386
|
-
|
|
387
|
-
`- **
|
|
388
|
-
|
|
469
|
+
addReport('Building', `### Execution
|
|
470
|
+
` +
|
|
471
|
+
`- **Breakdown**: ${breakdownTitle} (${formatElapsed(breakdownStartMs, breakdownEndMs)})
|
|
472
|
+
` +
|
|
473
|
+
`- **Validate**: ${validateTitle} (${formatElapsed(validateStartMs, validateEndMs)})
|
|
474
|
+
` +
|
|
475
|
+
`- **Implement**: ${implementTitle} (${formatElapsed(implementStartMs, implementEndMs)})
|
|
476
|
+
` +
|
|
477
|
+
`- **Tests passed**: ${implementation.testsPassed}
|
|
478
|
+
` +
|
|
479
|
+
`- **Open issues**: ${implementation.openIssues.length > 0 ? implementation.openIssues.join('; ') : 'none'}
|
|
480
|
+
|
|
481
|
+
` +
|
|
482
|
+
`${formatStructuredStage(buildingStageOutput, buildingStageOutput.actionItems.length > 0)}
|
|
483
|
+
|
|
484
|
+
` +
|
|
389
485
|
`**Phase time**: ${formatElapsed(buildingStartMs, buildingEndMs)}`);
|
|
390
486
|
// Phase end notification
|
|
391
|
-
await notify(`# Pipeline: Building ended
|
|
487
|
+
await notify(`# Pipeline: Building ended
|
|
488
|
+
|
|
489
|
+
Outcome: COMPLETE
|
|
490
|
+
${formatStructuredStage(buildingStageOutput, buildingStageOutput.actionItems.length > 0)}
|
|
491
|
+
Files Changed: ${implementation.filesChanged.length}
|
|
492
|
+
Tests Passed: ${implementation.testsPassed}
|
|
493
|
+
Open Issues: ${implementation.openIssues.length > 0 ? implementation.openIssues.join('; ') : 'none'}
|
|
494
|
+
Phase time: ${formatElapsed(buildingStartMs, buildingEndMs)}
|
|
495
|
+
`);
|
|
392
496
|
// ===================================================================
|
|
393
497
|
// PHASE 3: REVIEWING (triage → fan-out review → synthesize → gate, loop up to 10)
|
|
394
498
|
// ===================================================================
|
|
395
499
|
lastPhase = 'Reviewing';
|
|
396
500
|
const reviewStartMs = Date.now();
|
|
397
501
|
const reviewIterationDetails = [];
|
|
398
|
-
let reviewInput =
|
|
502
|
+
let reviewInput = implementationHandoff;
|
|
399
503
|
let reviewGate = { decision: 'REWORK' };
|
|
400
504
|
// Phase start notification
|
|
401
505
|
await notify(`# Pipeline: Reviewing started\n\nStarting review phase with ${revModels.length} models...\n`);
|
|
@@ -498,10 +602,6 @@ export function createPipelineTool(client) {
|
|
|
498
602
|
});
|
|
499
603
|
}
|
|
500
604
|
let reworkLine = '- **Rework**: not required';
|
|
501
|
-
let feedback = synthesis.reworkInstructions ||
|
|
502
|
-
reviewGate.feedback ||
|
|
503
|
-
synthesis.consolidatedFindings ||
|
|
504
|
-
'N/A';
|
|
505
605
|
if (reviewGate.decision === 'REWORK') {
|
|
506
606
|
context.metadata({
|
|
507
607
|
title: `⏳ Applying rework (iteration ${iteration}/10)...`,
|
|
@@ -514,21 +614,38 @@ export function createPipelineTool(client) {
|
|
|
514
614
|
});
|
|
515
615
|
// REWORK — apply fixes, then re-review
|
|
516
616
|
const reworkStartMs = Date.now();
|
|
517
|
-
const
|
|
617
|
+
const reworkSecondaryContext = `Original requirements:\n${requirements}\n\n` +
|
|
618
|
+
`Original batches:\n${batchesRaw}\n\n` +
|
|
619
|
+
`Latest implementation handoff:\n${reviewInput}\n\n` +
|
|
620
|
+
`Prior synthesis raw output:\n${synthRaw}`;
|
|
621
|
+
const reworkDirective = actionFirstReworkPrompt({
|
|
622
|
+
...synthesis.stageOutput,
|
|
623
|
+
status: reviewGate.decision,
|
|
624
|
+
}, reworkSecondaryContext, reviewGate.feedback || synthesis.reworkInstructions || synthRaw);
|
|
625
|
+
const fixRaw = await promptSession(client, runContext, pipelineReworkPrompt(reworkDirective, reworkSecondaryContext), { model: buildModel, agent: 'building', title: reworkTitle });
|
|
518
626
|
const reworkEndMs = Date.now();
|
|
519
627
|
reworkLine = `- **Rework**: ${reworkTitle} (${formatElapsed(reworkStartMs, reworkEndMs)})`;
|
|
520
|
-
reviewInput = fixRaw;
|
|
521
|
-
feedback = reviewGate.feedback || feedback;
|
|
628
|
+
reviewInput = formatImplementationReportForHandoff(parseImplementationReport(fixRaw));
|
|
522
629
|
}
|
|
630
|
+
const reviewStageOutput = {
|
|
631
|
+
...synthesis.stageOutput,
|
|
632
|
+
status: reviewGate.decision,
|
|
633
|
+
summary: synthesis.stageOutput.summary ||
|
|
634
|
+
`Review confidence ${synthesis.overallConfidence}% with ${synthesis.unresolvedIssues} unresolved issues.`,
|
|
635
|
+
};
|
|
523
636
|
reviewIterationDetails.push(`### Iteration ${iteration}\n` +
|
|
524
637
|
`- **Triage**: ${triageTitle} (${formatElapsed(triageStartMs, triageEndMs)})\n` +
|
|
525
638
|
`- **Fan-out**: ${fanOutTitles.join(', ')} (${formatElapsed(fanOutStartMs, fanOutEndMs)})\n` +
|
|
526
639
|
`- **Synthesis**: ${synthTitle} (${formatElapsed(synthStartMs, synthEndMs)})\n` +
|
|
527
640
|
`- **Gate**: ${reviewGate.decision} (confidence: ${synthesis.overallConfidence}, unresolved: ${synthesis.unresolvedIssues})\n` +
|
|
528
641
|
`${reworkLine}\n` +
|
|
529
|
-
|
|
642
|
+
`${formatStructuredStage(reviewStageOutput, reviewGate.decision === 'REWORK')}`);
|
|
530
643
|
// Notify each review iteration gate decision
|
|
531
|
-
await notify(
|
|
644
|
+
await notify(`Stage: Reviewing — iteration ${iteration}/10\n` +
|
|
645
|
+
`${formatStructuredStage(reviewStageOutput, reviewGate.decision === 'REWORK')}\n` +
|
|
646
|
+
`Confidence: ${synthesis.overallConfidence}%\n` +
|
|
647
|
+
`Unresolved Issues: ${synthesis.unresolvedIssues}\n` +
|
|
648
|
+
`Duration: ${formatElapsed(reviewStartMs, Date.now())}`);
|
|
532
649
|
if (reviewGate.decision === 'ESCALATE') {
|
|
533
650
|
const reviewEndMs = Date.now();
|
|
534
651
|
addReport('Reviewing', `${reviewIterationDetails.join('\n\n')}\n\n**Outcome**: ESCALATE: ${reviewGate.reason}\n**Phase time**: ${formatElapsed(reviewStartMs, reviewEndMs)}`);
|
|
@@ -550,7 +667,9 @@ export function createPipelineTool(client) {
|
|
|
550
667
|
const reviewEndMs = Date.now();
|
|
551
668
|
addReport('Reviewing', `${reviewIterationDetails.join('\n\n')}\n\n**Outcome**: ${reviewGate.decision === 'APPROVED'
|
|
552
669
|
? 'APPROVED'
|
|
553
|
-
:
|
|
670
|
+
: reviewGate.decision === 'ESCALATE'
|
|
671
|
+
? `ESCALATE: ${reviewGate.reason}`
|
|
672
|
+
: `REWORK exhausted (10 iterations). Last feedback: ${reviewGate.feedback}`}\n**Phase time**: ${formatElapsed(reviewStartMs, reviewEndMs)}`);
|
|
554
673
|
// Phase end notification
|
|
555
674
|
await notify(`# Pipeline: Reviewing ended\n\nOutcome: ${reviewGate.decision}\nDuration: ${formatElapsed(reviewStartMs, reviewEndMs)}\n`);
|
|
556
675
|
if (reviewGate.decision !== 'APPROVED') {
|
|
@@ -572,7 +691,7 @@ export function createPipelineTool(client) {
|
|
|
572
691
|
lastPhase = 'Documentation';
|
|
573
692
|
const documentationStartMs = Date.now();
|
|
574
693
|
const documentationIterationDetails = [];
|
|
575
|
-
let docInput = `Implementation
|
|
694
|
+
let docInput = `Implementation handoff:\n${implementationHandoff}\n\nLatest implementation handoff after review loop:\n${reviewInput}`;
|
|
576
695
|
let docGate = { decision: 'REWORK' };
|
|
577
696
|
// Phase start notification
|
|
578
697
|
await notify(`# Pipeline: Documentation started\n\nStarting documentation phase...\n`);
|
|
@@ -659,17 +778,27 @@ export function createPipelineTool(client) {
|
|
|
659
778
|
},
|
|
660
779
|
});
|
|
661
780
|
}
|
|
662
|
-
const
|
|
781
|
+
const docStageOutput = {
|
|
782
|
+
...docReview.stageOutput,
|
|
783
|
+
status: docGate.decision,
|
|
784
|
+
};
|
|
663
785
|
documentationIterationDetails.push(`### Iteration ${iteration}\n` +
|
|
664
786
|
`- **Write**: ${writeTitle} (${formatElapsed(writeStartMs, writeEndMs)})\n` +
|
|
665
787
|
`- **Review**: ${reviewTitle} (${formatElapsed(reviewDocStartMs, reviewDocEndMs)})\n` +
|
|
666
788
|
`- **Gate**: ${docGate.decision} (confidence: ${docReview.confidence}, unresolved: ${docReview.unresolvedIssues})\n` +
|
|
667
|
-
|
|
789
|
+
`${formatStructuredStage(docStageOutput, docGate.decision === 'REWORK')}`);
|
|
668
790
|
// Notify each documentation iteration gate decision
|
|
669
|
-
await notify(
|
|
791
|
+
await notify(`Stage: Documentation — iteration ${iteration}/5\n` +
|
|
792
|
+
`${formatStructuredStage(docStageOutput, docGate.decision === 'REWORK')}\n` +
|
|
793
|
+
`Confidence: ${docReview.confidence}%\n` +
|
|
794
|
+
`Unresolved Issues: ${docReview.unresolvedIssues}\n` +
|
|
795
|
+
`Duration: ${formatElapsed(documentationStartMs, Date.now())}`);
|
|
670
796
|
if (docGate.decision === 'REWORK') {
|
|
671
797
|
// Feed feedback into the next iteration
|
|
672
|
-
docInput =
|
|
798
|
+
docInput = actionFirstReworkPrompt({
|
|
799
|
+
...docReview.stageOutput,
|
|
800
|
+
status: docGate.decision,
|
|
801
|
+
}, `${docInput}\n\nDocumentation review feedback:\n${docGate.feedback || docReview.reworkInstructions || docReview.raw}`, docGate.feedback || docReview.reworkInstructions || docReview.raw);
|
|
673
802
|
}
|
|
674
803
|
}
|
|
675
804
|
const documentationEndMs = Date.now();
|
|
@@ -706,11 +835,11 @@ export function createPipelineTool(client) {
|
|
|
706
835
|
// Launch orchestration in background — fire-and-forget
|
|
707
836
|
void asyncOrchestration().catch(async (err) => {
|
|
708
837
|
const message = err instanceof Error ? err.message : String(err);
|
|
709
|
-
await notifyParent(client, runContext, agent, `# Pipeline:
|
|
838
|
+
await notifyParent(client, runContext, agent, `# Pipeline: ${runName} (${runId})\n\nStage: Error\nSTATUS: FAILED\nSUMMARY: Workflow terminated during ${lastPhase}.\nCHECKLIST:\n- Captured terminal error\nISSUES:\n- ${message}\n`, { noReply: false });
|
|
710
839
|
});
|
|
711
840
|
// Return immediately with acknowledgment
|
|
712
841
|
const summary = requirements.length > 120 ? requirements.slice(0, 120) + '...' : requirements;
|
|
713
|
-
return `Pipeline started for: ${summary}\n\nRun directory: ${runDirectoryContext.runDirectory}\nWorktree isolation: ${runDirectoryContext.worktreeEnabled ? 'enabled' : 'disabled'}\nYou will receive progress updates as each phase completes.`;
|
|
842
|
+
return `Pipeline started for: ${summary}\n\nRun: ${runName} (${runId})\nRun directory: ${runDirectoryContext.runDirectory}\nWorktree isolation: ${runDirectoryContext.worktreeEnabled ? 'enabled' : 'disabled'}\nYou will receive progress updates as each phase completes.`;
|
|
714
843
|
},
|
|
715
844
|
});
|
|
716
845
|
}
|
package/dist/tools/prompts.d.ts
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* Centralised here so that tools stay lean and prompts are easy to review
|
|
5
5
|
* and tune independently.
|
|
6
6
|
*/
|
|
7
|
+
type ReworkStageContract = {
|
|
8
|
+
status: string;
|
|
9
|
+
summary: string;
|
|
10
|
+
checklist: string[];
|
|
11
|
+
issues: string[];
|
|
12
|
+
actionItems: string[];
|
|
13
|
+
};
|
|
7
14
|
export declare function planningPrompt(requirements: string, modelTag: string): string;
|
|
8
15
|
export declare function synthesisPrompt(proposals: Array<{
|
|
9
16
|
tag: string;
|
|
@@ -12,6 +19,7 @@ export declare function synthesisPrompt(proposals: Array<{
|
|
|
12
19
|
export declare function breakdownPrompt(finalPlan: string): string;
|
|
13
20
|
export declare function validateBatchPrompt(tasks: string): string;
|
|
14
21
|
export declare function implementBatchPrompt(batches: string): string;
|
|
22
|
+
export declare function pipelineReworkPrompt(primaryDirective: string, secondaryContext: string): string;
|
|
15
23
|
export declare function triagePrompt(implementationReport: string): string;
|
|
16
24
|
export declare function reviewPrompt(brief: string, modelTag: string): string;
|
|
17
25
|
export declare function reviewSynthesisPrompt(reviews: Array<{
|
|
@@ -23,3 +31,6 @@ export declare function docReviewPrompt(docUpdate: string): string;
|
|
|
23
31
|
export declare function miniBuildPrompt(requirements: string, reworkFeedback?: string): string;
|
|
24
32
|
export declare function ciFixPrompt(requirements: string, ciOutput: string): string;
|
|
25
33
|
export declare function miniReviewPrompt(implementationReport: string): string;
|
|
34
|
+
export declare function runNamePrompt(workflowLabel: 'Pipeline' | 'Mini-Loop', requirements: string): string;
|
|
35
|
+
export declare function actionFirstReworkPrompt(stage: ReworkStageContract, secondaryContext: string, gateFeedback?: string): string;
|
|
36
|
+
export {};
|
package/dist/tools/prompts.js
CHANGED
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
* Centralised here so that tools stay lean and prompts are easy to review
|
|
5
5
|
* and tune independently.
|
|
6
6
|
*/
|
|
7
|
+
function asNumberedList(items, empty) {
|
|
8
|
+
if (items.length === 0) {
|
|
9
|
+
return `1. ${empty}`;
|
|
10
|
+
}
|
|
11
|
+
return items.map((item, index) => `${index + 1}. ${item}`).join('\n');
|
|
12
|
+
}
|
|
13
|
+
function extractActionItemsFromFeedback(feedback) {
|
|
14
|
+
const explicitActionBlock = feedback.match(/ACTION_ITEMS?[\s:=]*\n([\s\S]*)/i)?.[1] || '';
|
|
15
|
+
const source = explicitActionBlock || feedback;
|
|
16
|
+
return source
|
|
17
|
+
.split('\n')
|
|
18
|
+
.map((line) => line.trim())
|
|
19
|
+
.map((line) => line.replace(/^[-*\d.)\s]+/, '').trim())
|
|
20
|
+
.filter((line) => line.length > 0)
|
|
21
|
+
.filter((line) => !/^(STATUS|SUMMARY|CHECKLIST|ISSUES|ACTION_ITEMS|VERDICT|CONFIDENCE|UNRESOLVED)/i.test(line))
|
|
22
|
+
.slice(0, 8);
|
|
23
|
+
}
|
|
7
24
|
// ---------------------------------------------------------------------------
|
|
8
25
|
// Planning
|
|
9
26
|
// ---------------------------------------------------------------------------
|
|
@@ -84,7 +101,43 @@ For each task:
|
|
|
84
101
|
3. run lint/typecheck/tests for impacted scope
|
|
85
102
|
4. return a structured completion report
|
|
86
103
|
|
|
87
|
-
After all batches complete,
|
|
104
|
+
After all batches complete, return a final implementation handoff using this exact section schema:
|
|
105
|
+
- STATUS
|
|
106
|
+
- SUMMARY
|
|
107
|
+
- CHECKLIST
|
|
108
|
+
- ISSUES
|
|
109
|
+
- ACTION_ITEMS (required if unresolved issues remain)
|
|
110
|
+
- IMPLEMENTED_TASKS
|
|
111
|
+
- FILES_CHANGED
|
|
112
|
+
- TEST_RESULTS
|
|
113
|
+
- OPEN_ISSUES
|
|
114
|
+
- ASSUMPTIONS_MADE`;
|
|
115
|
+
}
|
|
116
|
+
export function pipelineReworkPrompt(primaryDirective, secondaryContext) {
|
|
117
|
+
return `Apply review-requested rework for the current pipeline iteration.
|
|
118
|
+
|
|
119
|
+
PRIMARY DIRECTIVE (must execute first):
|
|
120
|
+
${primaryDirective}
|
|
121
|
+
|
|
122
|
+
SECONDARY CONTEXT (for reference only):
|
|
123
|
+
${secondaryContext}
|
|
124
|
+
|
|
125
|
+
Requirements:
|
|
126
|
+
1. Execute ACTION_ITEMS in dependency order and verify each item is resolved.
|
|
127
|
+
2. Keep original requirements/spec/history as contextual reference only.
|
|
128
|
+
3. Add or update tests for behavioral changes.
|
|
129
|
+
4. Run lint/typecheck/tests only for impacted scope.
|
|
130
|
+
5. Return a structured implementation handoff using this exact section schema:
|
|
131
|
+
- STATUS
|
|
132
|
+
- SUMMARY
|
|
133
|
+
- CHECKLIST
|
|
134
|
+
- ISSUES
|
|
135
|
+
- ACTION_ITEMS (required if unresolved issues remain)
|
|
136
|
+
- IMPLEMENTED_TASKS
|
|
137
|
+
- FILES_CHANGED
|
|
138
|
+
- TEST_RESULTS
|
|
139
|
+
- OPEN_ISSUES
|
|
140
|
+
- ASSUMPTIONS_MADE`;
|
|
88
141
|
}
|
|
89
142
|
// ---------------------------------------------------------------------------
|
|
90
143
|
// Reviewing
|
|
@@ -118,12 +171,17 @@ export function reviewSynthesisPrompt(reviews) {
|
|
|
118
171
|
|
|
119
172
|
${formatted}
|
|
120
173
|
|
|
121
|
-
Required output:
|
|
122
|
-
1.
|
|
123
|
-
2.
|
|
124
|
-
3.
|
|
125
|
-
4.
|
|
126
|
-
5.
|
|
174
|
+
Required output fields (strict):
|
|
175
|
+
1. STATUS
|
|
176
|
+
2. SUMMARY
|
|
177
|
+
3. CHECKLIST
|
|
178
|
+
4. ISSUES
|
|
179
|
+
5. ACTION_ITEMS (required when verdict is REWORK_REQUIRED)
|
|
180
|
+
6. CONFIDENCE (0-100)
|
|
181
|
+
7. UNRESOLVED_ISSUES (integer)
|
|
182
|
+
8. VERDICT (APPROVED or REWORK_REQUIRED)
|
|
183
|
+
|
|
184
|
+
When rework is needed, ACTION_ITEMS must be concrete, testable, and ordered by dependency.`;
|
|
127
185
|
}
|
|
128
186
|
// ---------------------------------------------------------------------------
|
|
129
187
|
// Documentation
|
|
@@ -136,38 +194,61 @@ ${input}
|
|
|
136
194
|
Requirements:
|
|
137
195
|
1. Use the latest approved review outputs and implementation artifacts as source of truth.
|
|
138
196
|
2. Update all affected docs so behavior and operational steps match shipped code.
|
|
139
|
-
3. If this is a rework iteration,
|
|
197
|
+
3. If this is a rework iteration, treat ACTION_ITEMS as the primary instructions and treat prior requirements/spec text as secondary context.
|
|
140
198
|
4. Summarize what docs were changed and why.
|
|
141
199
|
|
|
142
|
-
Return a structured documentation update summary
|
|
200
|
+
Return a structured documentation update summary with sections:
|
|
201
|
+
- STATUS
|
|
202
|
+
- SUMMARY
|
|
203
|
+
- CHECKLIST
|
|
204
|
+
- ISSUES
|
|
205
|
+
- ACTION_ITEMS (required if documentation rework remains).`;
|
|
143
206
|
}
|
|
144
207
|
export function docReviewPrompt(docUpdate) {
|
|
145
208
|
return `Review the latest documentation pass for completeness, correctness, and repository doc consistency.
|
|
146
209
|
|
|
147
210
|
${docUpdate}
|
|
148
211
|
|
|
149
|
-
Required output:
|
|
150
|
-
1.
|
|
151
|
-
2.
|
|
152
|
-
3.
|
|
153
|
-
4.
|
|
212
|
+
Required output fields (strict):
|
|
213
|
+
1. STATUS
|
|
214
|
+
2. SUMMARY
|
|
215
|
+
3. CHECKLIST
|
|
216
|
+
4. ISSUES
|
|
217
|
+
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)`;
|
|
154
221
|
}
|
|
155
222
|
// ---------------------------------------------------------------------------
|
|
156
223
|
// Mini-loop specific
|
|
157
224
|
// ---------------------------------------------------------------------------
|
|
158
225
|
export function miniBuildPrompt(requirements, reworkFeedback) {
|
|
159
226
|
const rework = reworkFeedback
|
|
160
|
-
? `\n\
|
|
227
|
+
? `\n\nACTION-FIRST REWORK DIRECTIVE (primary):\n${reworkFeedback}`
|
|
161
228
|
: '';
|
|
162
229
|
return `Implement the current mini-loop requirements below.
|
|
163
230
|
|
|
164
|
-
|
|
231
|
+
Primary directive:
|
|
232
|
+
${rework || 'No rework directive for this iteration.'}
|
|
233
|
+
|
|
234
|
+
Secondary context:
|
|
235
|
+
${requirements}
|
|
165
236
|
|
|
166
237
|
Requirements:
|
|
167
238
|
1. Apply requested code rework when present.
|
|
168
239
|
2. Add or update tests for behavioral changes.
|
|
169
240
|
3. Run lint/typecheck/tests only for impacted scope.
|
|
170
|
-
4. Return a
|
|
241
|
+
4. Return a structured implementation handoff using this exact section schema:
|
|
242
|
+
- STATUS
|
|
243
|
+
- SUMMARY
|
|
244
|
+
- CHECKLIST
|
|
245
|
+
- ISSUES
|
|
246
|
+
- ACTION_ITEMS (required if unresolved issues remain)
|
|
247
|
+
- IMPLEMENTED_TASKS
|
|
248
|
+
- FILES_CHANGED
|
|
249
|
+
- TEST_RESULTS
|
|
250
|
+
- OPEN_ISSUES
|
|
251
|
+
- ASSUMPTIONS_MADE`;
|
|
171
252
|
}
|
|
172
253
|
export function ciFixPrompt(requirements, ciOutput) {
|
|
173
254
|
return `The CI checks (ff-ci.sh) failed after your implementation. Fix the issues identified below.
|
|
@@ -183,16 +264,75 @@ ${ciOutput}
|
|
|
183
264
|
Requirements:
|
|
184
265
|
1. Fix all failing checks (lint, typecheck, build, tests).
|
|
185
266
|
2. Do not remove or skip tests — fix the underlying issues.
|
|
186
|
-
3. Return a
|
|
267
|
+
3. Return a structured implementation handoff using this exact section schema:
|
|
268
|
+
- STATUS
|
|
269
|
+
- SUMMARY
|
|
270
|
+
- CHECKLIST
|
|
271
|
+
- ISSUES
|
|
272
|
+
- ACTION_ITEMS (required if unresolved issues remain)
|
|
273
|
+
- IMPLEMENTED_TASKS
|
|
274
|
+
- FILES_CHANGED
|
|
275
|
+
- TEST_RESULTS
|
|
276
|
+
- OPEN_ISSUES
|
|
277
|
+
- ASSUMPTIONS_MADE`;
|
|
187
278
|
}
|
|
188
279
|
export function miniReviewPrompt(implementationReport) {
|
|
189
280
|
return `Review the latest mini-loop implementation output below.
|
|
190
281
|
|
|
191
282
|
${implementationReport}
|
|
192
283
|
|
|
193
|
-
Required output fields:
|
|
194
|
-
1.
|
|
195
|
-
2.
|
|
196
|
-
3.
|
|
197
|
-
4.
|
|
284
|
+
Required output fields (strict):
|
|
285
|
+
1. STATUS
|
|
286
|
+
2. SUMMARY
|
|
287
|
+
3. CHECKLIST
|
|
288
|
+
4. ISSUES
|
|
289
|
+
5. ACTION_ITEMS (required when CHANGE_REQUESTED=YES)
|
|
290
|
+
6. CHANGE_REQUESTED=YES|NO
|
|
291
|
+
7. UNRESOLVED_BLOCKING_ISSUES=<integer>
|
|
292
|
+
8. CONFIDENCE=<0-100>`;
|
|
293
|
+
}
|
|
294
|
+
export function runNamePrompt(workflowLabel, requirements) {
|
|
295
|
+
return `Generate a concise descriptive run name for this ${workflowLabel} orchestration run.
|
|
296
|
+
|
|
297
|
+
Requirements context:
|
|
298
|
+
${requirements}
|
|
299
|
+
|
|
300
|
+
Constraints:
|
|
301
|
+
1. 3-7 words.
|
|
302
|
+
2. Imperative or outcome-focused.
|
|
303
|
+
3. No punctuation other than spaces and hyphens.
|
|
304
|
+
4. Return only the run name text with no labels.`;
|
|
305
|
+
}
|
|
306
|
+
export function actionFirstReworkPrompt(stage, secondaryContext, gateFeedback) {
|
|
307
|
+
const fallbackActionItems = gateFeedback ? extractActionItemsFromFeedback(gateFeedback) : [];
|
|
308
|
+
const actionItems = stage.actionItems.length > 0
|
|
309
|
+
? stage.actionItems
|
|
310
|
+
: fallbackActionItems.length > 0
|
|
311
|
+
? fallbackActionItems
|
|
312
|
+
: stage.issues.map((issue) => `Resolve: ${issue}`);
|
|
313
|
+
const checklist = stage.checklist.length > 0
|
|
314
|
+
? stage.checklist
|
|
315
|
+
: ['No explicit checklist was provided; verify acceptance criteria and impacted tests.'];
|
|
316
|
+
const issues = stage.issues.length > 0
|
|
317
|
+
? stage.issues
|
|
318
|
+
: ['No explicit issues listed; confirm no regressions remain.'];
|
|
319
|
+
const additionalFeedback = gateFeedback ? `\n\nADDITIONAL_GATE_FEEDBACK:\n${gateFeedback}` : '';
|
|
320
|
+
return `ACTION-FIRST REWORK DIRECTIVE (primary):
|
|
321
|
+
STATUS:
|
|
322
|
+
${stage.status}
|
|
323
|
+
|
|
324
|
+
SUMMARY:
|
|
325
|
+
${stage.summary}
|
|
326
|
+
|
|
327
|
+
CHECKLIST:
|
|
328
|
+
${asNumberedList(checklist, 'No checklist provided.')}
|
|
329
|
+
|
|
330
|
+
ISSUES:
|
|
331
|
+
${asNumberedList(issues, 'No issues reported.')}
|
|
332
|
+
|
|
333
|
+
ACTION_ITEMS:
|
|
334
|
+
${asNumberedList(actionItems, 'No explicit action items were provided; resolve the listed issues and verify acceptance criteria.')}${additionalFeedback}
|
|
335
|
+
|
|
336
|
+
SECONDARY CONTEXT (history/spec):
|
|
337
|
+
${secondaryContext}`;
|
|
198
338
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* threshold logic directly. The LLM still produces the synthesis; the
|
|
6
6
|
* gate decision is computed here.
|
|
7
7
|
*/
|
|
8
|
-
import type { GateResult, ConsensusPlan, ReviewSynthesis, DocReview } from './types.js';
|
|
8
|
+
import type { GateResult, ConsensusPlan, ReviewSynthesis, DocReview, MiniLoopReview } from './types.js';
|
|
9
9
|
/**
|
|
10
10
|
* Evaluate the planning consensus gate.
|
|
11
11
|
*
|
|
@@ -37,12 +37,7 @@ export declare function evaluateDocGate(review: DocReview, iteration: number, ma
|
|
|
37
37
|
* - iteration >= maxIterations → ESCALATE
|
|
38
38
|
* - otherwise → REWORK
|
|
39
39
|
*/
|
|
40
|
-
export declare function evaluateMiniLoopImplGate(review:
|
|
41
|
-
confidence: number;
|
|
42
|
-
changeRequested: boolean;
|
|
43
|
-
unresolvedIssues: number;
|
|
44
|
-
reworkInstructions?: string;
|
|
45
|
-
}, iteration: number, maxIterations?: number): GateResult;
|
|
40
|
+
export declare function evaluateMiniLoopImplGate(review: MiniLoopReview, iteration: number, maxIterations?: number): GateResult;
|
|
46
41
|
/**
|
|
47
42
|
* Evaluate the mini-loop documentation gate.
|
|
48
43
|
* Same thresholds as the pipeline doc gate.
|