@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.
@@ -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, options);
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
- `- **Gate**: ${planningGate.decision} (score: ${consensus.consensusScore})\n` +
220
- `- **Feedback**: ${feedback}`);
274
+ `${formatStructuredStage(planningStageOutput, planningGate.decision === 'REWORK')}`);
221
275
  // Notify each planning iteration gate decision
222
- await notify(`# Pipeline: Planning — Iteration ${iteration}/5\n\nStatus: ${planningGate.decision}\nConsensus Score: ${consensus.consensusScore}%\nDuration: ${formatElapsed(planningStartMs, Date.now())}\nFeedback: ${feedback}\n`);
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
- await notify(`# Pipeline: CI passed (attempt ${ciAttempt}/${maxCiAttempts})\n`);
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
- await notify(`# Pipeline: CI failed (attempt ${ciAttempt}/${maxCiAttempts})\n\n\`\`\`\n${ciResult.output}\n\`\`\`\n`);
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\n` +
384
- `- **Breakdown**: ${breakdownTitle} (${formatElapsed(breakdownStartMs, breakdownEndMs)})\n` +
385
- `- **Validate**: ${validateTitle} (${formatElapsed(validateStartMs, validateEndMs)})\n` +
386
- `- **Implement**: ${implementTitle} (${formatElapsed(implementStartMs, implementEndMs)})\n` +
387
- `- **Tests passed**: ${implementation.testsPassed}\n` +
388
- `- **Open issues**: ${implementation.openIssues.length > 0 ? implementation.openIssues.join('; ') : 'none'}\n\n` +
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\n\nOutcome: COMPLETE\nFiles Changed: ${implementation.filesChanged.length}\nTests Passed: ${implementation.testsPassed}\nOpen Issues: ${implementation.openIssues.length > 0 ? implementation.openIssues.join('; ') : 'none'}\nPhase time: ${formatElapsed(buildingStartMs, buildingEndMs)}\n`);
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 = implRaw;
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 fixRaw = await promptSession(client, runContext, implementBatchPrompt(`Rework required:\n${reviewGate.feedback}\n\nOriginal batches:\n${batchesRaw}`), { model: buildModel, agent: 'building', title: reworkTitle });
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
- `- **Feedback**: ${feedback}`);
642
+ `${formatStructuredStage(reviewStageOutput, reviewGate.decision === 'REWORK')}`);
530
643
  // Notify each review iteration gate decision
531
- await notify(`# Pipeline: Reviewing — Iteration ${iteration}/10\n\nStatus: ${reviewGate.decision}\nConfidence: ${synthesis.overallConfidence}%\nUnresolved Issues: ${synthesis.unresolvedIssues}\nDuration: ${formatElapsed(reviewStartMs, Date.now())}\nFeedback: ${feedback}\n`);
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
- : `REWORK exhausted (10 iterations). Last feedback: ${reviewGate.feedback}`}\n**Phase time**: ${formatElapsed(reviewStartMs, reviewEndMs)}`);
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 report:\n${implRaw}\n\nReview synthesis:\n${reviewInput}`;
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 feedback = docReview.reworkInstructions || docGate.feedback || docReview.raw;
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
- `- **Feedback**: ${feedback}`);
789
+ `${formatStructuredStage(docStageOutput, docGate.decision === 'REWORK')}`);
668
790
  // Notify each documentation iteration gate decision
669
- await notify(`# Pipeline: Documentation — Iteration ${iteration}/5\n\nStatus: ${docGate.decision}\nConfidence: ${docReview.confidence}%\nUnresolved Issues: ${docReview.unresolvedIssues}\nDuration: ${formatElapsed(documentationStartMs, Date.now())}\nFeedback: ${feedback}\n`);
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 = `${docInput}\n\nDocumentation review feedback:\n${docGate.feedback}`;
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: Error\n\nPhase: ${lastPhase}\nError: ${message}\n`, { noReply: false });
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
  }
@@ -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 {};
@@ -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, merge the per-task completion reports into a single IMPLEMENTATION_REPORT output.`;
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. overall confidence (0-100)
123
- 2. consolidated deduplicated findings
124
- 3. unresolved issues list (count)
125
- 4. verdict APPROVED or REWORK_REQUIRED
126
- 5. explicit rework instructions when needed`;
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, incorporate documentation reviewer feedback from the previous documentation review.
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. verdict APPROVED or REWORK_REQUIRED
151
- 2. unresolved documentation issues (count, integer)
152
- 3. explicit rework instructions
153
- 4. confidence score (0-100)`;
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\nPrevious review feedback to address:\n${reworkFeedback}`
227
+ ? `\n\nACTION-FIRST REWORK DIRECTIVE (primary):\n${reworkFeedback}`
161
228
  : '';
162
229
  return `Implement the current mini-loop requirements below.
163
230
 
164
- ${requirements}${rework}
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 concise implementation report with changed files, tests run, and known open issues.`;
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 concise implementation report with changed files and what was fixed.`;
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. CHANGE_REQUESTED=YES|NO
195
- 2. UNRESOLVED_BLOCKING_ISSUES=<integer>
196
- 3. CONFIDENCE=<0-100>
197
- 4. concise rework instructions when change is requested`;
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.