@syntesseraai/opencode-feature-factory 0.6.21 → 0.7.1
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/agents/feature-factory.md +95 -0
- package/dist/index.js +9 -2
- package/dist/tools/index.d.ts +16 -0
- package/dist/tools/index.js +21 -0
- package/dist/tools/mini-loop.d.ts +24 -0
- package/dist/tools/mini-loop.js +133 -0
- package/dist/tools/parsers.d.ts +28 -0
- package/dist/tools/parsers.js +142 -0
- package/dist/tools/pipeline.d.ts +32 -0
- package/dist/tools/pipeline.js +227 -0
- package/dist/tools/prompts.d.ts +24 -0
- package/dist/tools/prompts.js +182 -0
- package/dist/workflow/fan-out.d.ts +84 -0
- package/dist/workflow/fan-out.js +83 -0
- package/dist/workflow/gate-evaluator.d.ts +50 -0
- package/dist/workflow/gate-evaluator.js +118 -0
- package/dist/workflow/orchestrator.d.ts +9 -0
- package/dist/workflow/orchestrator.js +9 -0
- package/dist/workflow/types.d.ts +148 -0
- package/dist/workflow/types.js +62 -0
- package/package.json +1 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ff_pipeline — Full plan → build → review → document workflow tool.
|
|
3
|
+
*
|
|
4
|
+
* This is an entrypoint tool exposed to the LLM. It orchestrates the
|
|
5
|
+
* complete Feature Factory pipeline using deterministic control flow
|
|
6
|
+
* (TypeScript loops and gates) while delegating creative work to
|
|
7
|
+
* model-specific prompts via the SDK client.
|
|
8
|
+
*/
|
|
9
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
10
|
+
import { fanOut, promptSession, evaluatePlanningGate, evaluateReviewGate, evaluateDocGate, PLANNING_MODELS, REVIEW_MODELS, ORCHESTRATOR_MODEL, BUILD_MODEL, DOC_MODEL, VALIDATE_MODEL, DOC_REVIEW_MODEL, parseModelString, parseNamedModels, } from '../workflow/orchestrator.js';
|
|
11
|
+
import { planningPrompt, synthesisPrompt, breakdownPrompt, validateBatchPrompt, implementBatchPrompt, triagePrompt, reviewPrompt, reviewSynthesisPrompt, documentPrompt, docReviewPrompt, } from './prompts.js';
|
|
12
|
+
import { parseConsensusPlan, parseReviewSynthesis, parseImplementationReport, parseDocReview, } from './parsers.js';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Tool factory — needs the SDK client from plugin init
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
export function createPipelineTool(client) {
|
|
17
|
+
return tool({
|
|
18
|
+
description: 'Run the full Feature Factory pipeline: multi-model planning → build → review → documentation. ' +
|
|
19
|
+
'Returns a structured completion report. All model parameters are optional and use sensible defaults.',
|
|
20
|
+
args: {
|
|
21
|
+
requirements: tool.schema
|
|
22
|
+
.string()
|
|
23
|
+
.describe('The feature requirements or task description to implement'),
|
|
24
|
+
planning_models: tool.schema
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Comma-separated list of tag:provider/model for planning fan-out. ' +
|
|
28
|
+
'Example: "opus:anthropic/claude-opus-4-6, gemini:google/gemini-2.5-pro". ' +
|
|
29
|
+
'Defaults to opus + gemini + codex.'),
|
|
30
|
+
review_models: tool.schema
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Comma-separated list of tag:provider/model for review fan-out. ' +
|
|
34
|
+
'Defaults to the same models as planning_models.'),
|
|
35
|
+
orchestrator_model: tool.schema
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('provider/model for synthesis and triage steps. Defaults to openai/gpt-5.4.'),
|
|
39
|
+
build_model: tool.schema
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('provider/model for building and implementation. Defaults to openai/gpt-5.3-codex.'),
|
|
43
|
+
validate_model: tool.schema
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('provider/model for batch validation. Defaults to opencode/gemini-3.1-pro.'),
|
|
47
|
+
doc_model: tool.schema
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe('provider/model for documentation writing. Defaults to the build model.'),
|
|
51
|
+
doc_review_model: tool.schema
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('provider/model for documentation review. Defaults to the validate model.'),
|
|
55
|
+
},
|
|
56
|
+
async execute(args, context) {
|
|
57
|
+
const sessionId = context.sessionID;
|
|
58
|
+
const { requirements } = args;
|
|
59
|
+
// Resolve models — use provided overrides or fall back to defaults
|
|
60
|
+
const planModels = args.planning_models
|
|
61
|
+
? parseNamedModels(args.planning_models)
|
|
62
|
+
: PLANNING_MODELS;
|
|
63
|
+
const revModels = args.review_models
|
|
64
|
+
? parseNamedModels(args.review_models)
|
|
65
|
+
: args.planning_models
|
|
66
|
+
? planModels
|
|
67
|
+
: REVIEW_MODELS;
|
|
68
|
+
const orchestratorModel = args.orchestrator_model
|
|
69
|
+
? parseModelString(args.orchestrator_model)
|
|
70
|
+
: ORCHESTRATOR_MODEL;
|
|
71
|
+
const buildModel = args.build_model
|
|
72
|
+
? parseModelString(args.build_model)
|
|
73
|
+
: BUILD_MODEL;
|
|
74
|
+
const validateModel = args.validate_model
|
|
75
|
+
? parseModelString(args.validate_model)
|
|
76
|
+
: VALIDATE_MODEL;
|
|
77
|
+
const docModel = args.doc_model
|
|
78
|
+
? parseModelString(args.doc_model)
|
|
79
|
+
: args.build_model
|
|
80
|
+
? buildModel
|
|
81
|
+
: DOC_MODEL;
|
|
82
|
+
const docReviewModel = args.doc_review_model
|
|
83
|
+
? parseModelString(args.doc_review_model)
|
|
84
|
+
: args.validate_model
|
|
85
|
+
? validateModel
|
|
86
|
+
: DOC_REVIEW_MODEL;
|
|
87
|
+
const report = [];
|
|
88
|
+
const addReport = (phase, msg) => {
|
|
89
|
+
report.push(`## ${phase}\n${msg}`);
|
|
90
|
+
};
|
|
91
|
+
// ===================================================================
|
|
92
|
+
// PHASE 1: PLANNING (fan-out → synthesize → gate, loop up to 5)
|
|
93
|
+
// ===================================================================
|
|
94
|
+
let planningGate = { decision: 'REWORK', feedback: requirements };
|
|
95
|
+
let finalPlan = '';
|
|
96
|
+
for (let planIter = 0; planIter < 5 && planningGate.decision === 'REWORK'; planIter++) {
|
|
97
|
+
const planInput = planIter === 0
|
|
98
|
+
? requirements
|
|
99
|
+
: `${requirements}\n\nPrevious feedback:\n${planningGate.feedback}`;
|
|
100
|
+
// Fan-out: N models plan in parallel
|
|
101
|
+
const fanOutResults = await fanOut(client, sessionId, planModels, (tag) => planningPrompt(planInput, tag), 'planning');
|
|
102
|
+
// Synthesize consensus
|
|
103
|
+
const synthesisRaw = await promptSession(client, sessionId, synthesisPrompt(fanOutResults), {
|
|
104
|
+
model: orchestratorModel,
|
|
105
|
+
agent: 'planning',
|
|
106
|
+
title: `ff-plan-synthesis-${planIter + 1}`,
|
|
107
|
+
});
|
|
108
|
+
const consensus = parseConsensusPlan(synthesisRaw);
|
|
109
|
+
// Gate (deterministic)
|
|
110
|
+
planningGate = evaluatePlanningGate(consensus);
|
|
111
|
+
if (planningGate.decision === 'APPROVED') {
|
|
112
|
+
finalPlan = consensus.synthesizedPlan || synthesisRaw;
|
|
113
|
+
addReport('Planning', `APPROVED (score: ${consensus.consensusScore}, iteration: ${planIter + 1})`);
|
|
114
|
+
}
|
|
115
|
+
else if (planningGate.decision === 'BLOCKED') {
|
|
116
|
+
addReport('Planning', `BLOCKED: ${planningGate.reason}`);
|
|
117
|
+
return report.join('\n\n');
|
|
118
|
+
}
|
|
119
|
+
// REWORK continues the loop
|
|
120
|
+
}
|
|
121
|
+
if (planningGate.decision !== 'APPROVED') {
|
|
122
|
+
addReport('Planning', `REWORK exhausted (5 iterations). Last feedback:\n${planningGate.feedback}`);
|
|
123
|
+
return report.join('\n\n');
|
|
124
|
+
}
|
|
125
|
+
// ===================================================================
|
|
126
|
+
// PHASE 2: BUILDING (breakdown → validate → implement)
|
|
127
|
+
// ===================================================================
|
|
128
|
+
// Breakdown
|
|
129
|
+
const tasksRaw = await promptSession(client, sessionId, breakdownPrompt(finalPlan), {
|
|
130
|
+
model: buildModel,
|
|
131
|
+
agent: 'building',
|
|
132
|
+
title: 'ff-build-breakdown',
|
|
133
|
+
});
|
|
134
|
+
// Validate batches
|
|
135
|
+
const batchesRaw = await promptSession(client, sessionId, validateBatchPrompt(tasksRaw), {
|
|
136
|
+
model: validateModel,
|
|
137
|
+
agent: 'building',
|
|
138
|
+
title: 'ff-build-validate',
|
|
139
|
+
});
|
|
140
|
+
// Implement
|
|
141
|
+
const implRaw = await promptSession(client, sessionId, implementBatchPrompt(batchesRaw), {
|
|
142
|
+
model: buildModel,
|
|
143
|
+
agent: 'building',
|
|
144
|
+
title: 'ff-build-implement',
|
|
145
|
+
});
|
|
146
|
+
const implementation = parseImplementationReport(implRaw);
|
|
147
|
+
addReport('Building', `Implementation completed. Tests passed: ${implementation.testsPassed}`);
|
|
148
|
+
// ===================================================================
|
|
149
|
+
// PHASE 3: REVIEWING (triage → fan-out review → synthesize → gate, loop up to 10)
|
|
150
|
+
// ===================================================================
|
|
151
|
+
let reviewInput = implRaw;
|
|
152
|
+
let reviewGate = { decision: 'REWORK' };
|
|
153
|
+
for (let revIter = 0; revIter < 10 && reviewGate.decision === 'REWORK'; revIter++) {
|
|
154
|
+
// Triage
|
|
155
|
+
const brief = await promptSession(client, sessionId, triagePrompt(reviewInput), {
|
|
156
|
+
model: orchestratorModel,
|
|
157
|
+
agent: 'reviewing',
|
|
158
|
+
title: `ff-review-triage-${revIter + 1}`,
|
|
159
|
+
});
|
|
160
|
+
// Fan-out review
|
|
161
|
+
const reviewResults = await fanOut(client, sessionId, revModels, (tag) => reviewPrompt(brief, tag), 'reviewing');
|
|
162
|
+
// Synthesize
|
|
163
|
+
const synthRaw = await promptSession(client, sessionId, reviewSynthesisPrompt(reviewResults), {
|
|
164
|
+
model: orchestratorModel,
|
|
165
|
+
agent: 'reviewing',
|
|
166
|
+
title: `ff-review-synthesis-${revIter + 1}`,
|
|
167
|
+
});
|
|
168
|
+
const synthesis = parseReviewSynthesis(synthRaw);
|
|
169
|
+
// Gate (deterministic)
|
|
170
|
+
reviewGate = evaluateReviewGate(synthesis, revIter + 1);
|
|
171
|
+
if (reviewGate.decision === 'APPROVED') {
|
|
172
|
+
addReport('Reviewing', `APPROVED (confidence: ${synthesis.overallConfidence}, iteration: ${revIter + 1})`);
|
|
173
|
+
}
|
|
174
|
+
else if (reviewGate.decision === 'ESCALATE') {
|
|
175
|
+
addReport('Reviewing', `ESCALATE: ${reviewGate.reason}`);
|
|
176
|
+
return report.join('\n\n');
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// REWORK — apply fixes, then re-review
|
|
180
|
+
const fixRaw = await promptSession(client, sessionId, implementBatchPrompt(`Rework required:\n${reviewGate.feedback}\n\nOriginal batches:\n${batchesRaw}`), { model: buildModel, agent: 'building', title: `ff-review-rework-${revIter + 1}` });
|
|
181
|
+
reviewInput = fixRaw;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (reviewGate.decision !== 'APPROVED') {
|
|
185
|
+
addReport('Reviewing', `REWORK exhausted (10 iterations). Last feedback:\n${reviewGate.feedback}`);
|
|
186
|
+
return report.join('\n\n');
|
|
187
|
+
}
|
|
188
|
+
// ===================================================================
|
|
189
|
+
// PHASE 4: DOCUMENTATION (document → review → gate, loop up to 5)
|
|
190
|
+
// ===================================================================
|
|
191
|
+
let docInput = `Implementation report:\n${implRaw}\n\nReview synthesis:\n${reviewInput}`;
|
|
192
|
+
let docGate = { decision: 'REWORK' };
|
|
193
|
+
for (let docIter = 0; docIter < 5 && docGate.decision === 'REWORK'; docIter++) {
|
|
194
|
+
// Write docs
|
|
195
|
+
const docRaw = await promptSession(client, sessionId, documentPrompt(docInput), {
|
|
196
|
+
model: docModel,
|
|
197
|
+
agent: 'documenting',
|
|
198
|
+
title: `ff-doc-write-${docIter + 1}`,
|
|
199
|
+
});
|
|
200
|
+
// Review docs
|
|
201
|
+
const docRevRaw = await promptSession(client, sessionId, docReviewPrompt(docRaw), {
|
|
202
|
+
model: docReviewModel,
|
|
203
|
+
agent: 'reviewing',
|
|
204
|
+
title: `ff-doc-review-${docIter + 1}`,
|
|
205
|
+
});
|
|
206
|
+
const docReview = parseDocReview(docRevRaw);
|
|
207
|
+
// Gate (deterministic)
|
|
208
|
+
docGate = evaluateDocGate(docReview, docIter + 1);
|
|
209
|
+
if (docGate.decision === 'APPROVED') {
|
|
210
|
+
addReport('Documentation', `APPROVED (confidence: ${docReview.confidence}, iteration: ${docIter + 1})`);
|
|
211
|
+
}
|
|
212
|
+
else if (docGate.decision === 'ESCALATE') {
|
|
213
|
+
addReport('Documentation', `ESCALATE: ${docGate.reason}`);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// Feed feedback into the next iteration
|
|
217
|
+
docInput = `${docInput}\n\nDocumentation review feedback:\n${docGate.feedback}`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// ===================================================================
|
|
221
|
+
// FINAL REPORT
|
|
222
|
+
// ===================================================================
|
|
223
|
+
addReport('Complete', 'Pipeline finished.');
|
|
224
|
+
return report.join('\n\n');
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt templates for workflow tools.
|
|
3
|
+
*
|
|
4
|
+
* Centralised here so that tools stay lean and prompts are easy to review
|
|
5
|
+
* and tune independently.
|
|
6
|
+
*/
|
|
7
|
+
export declare function planningPrompt(requirements: string, modelTag: string): string;
|
|
8
|
+
export declare function synthesisPrompt(proposals: Array<{
|
|
9
|
+
tag: string;
|
|
10
|
+
raw: string;
|
|
11
|
+
}>): string;
|
|
12
|
+
export declare function breakdownPrompt(finalPlan: string): string;
|
|
13
|
+
export declare function validateBatchPrompt(tasks: string): string;
|
|
14
|
+
export declare function implementBatchPrompt(batches: string): string;
|
|
15
|
+
export declare function triagePrompt(implementationReport: string): string;
|
|
16
|
+
export declare function reviewPrompt(brief: string, modelTag: string): string;
|
|
17
|
+
export declare function reviewSynthesisPrompt(reviews: Array<{
|
|
18
|
+
tag: string;
|
|
19
|
+
raw: string;
|
|
20
|
+
}>): string;
|
|
21
|
+
export declare function documentPrompt(input: string): string;
|
|
22
|
+
export declare function docReviewPrompt(docUpdate: string): string;
|
|
23
|
+
export declare function miniBuildPrompt(requirements: string, reworkFeedback?: string): string;
|
|
24
|
+
export declare function miniReviewPrompt(implementationReport: string): string;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt templates for workflow tools.
|
|
3
|
+
*
|
|
4
|
+
* Centralised here so that tools stay lean and prompts are easy to review
|
|
5
|
+
* and tune independently.
|
|
6
|
+
*/
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Planning
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export function planningPrompt(requirements, modelTag) {
|
|
11
|
+
return `[MODEL_TAG:${modelTag}]
|
|
12
|
+
|
|
13
|
+
Create a comprehensive implementation plan from the requirements below.
|
|
14
|
+
|
|
15
|
+
Requirements:
|
|
16
|
+
${requirements}
|
|
17
|
+
|
|
18
|
+
Return a structured proposal with these sections:
|
|
19
|
+
|
|
20
|
+
1. REQUIREMENTS_SUMMARY
|
|
21
|
+
2. ARCHITECTURE_VALIDATION
|
|
22
|
+
3. IMPLEMENTATION_STEPS
|
|
23
|
+
4. RISKS_AND_MITIGATIONS
|
|
24
|
+
5. TESTING_AND_VALIDATION_STRATEGY`;
|
|
25
|
+
}
|
|
26
|
+
export function synthesisPrompt(proposals) {
|
|
27
|
+
const formatted = proposals.map((p) => `--- ${p.tag} ---\n${p.raw}`).join('\n\n');
|
|
28
|
+
return `Synthesize the following model planning outputs into a consensus report.
|
|
29
|
+
|
|
30
|
+
${formatted}
|
|
31
|
+
|
|
32
|
+
Produce a consensus report with these sections:
|
|
33
|
+
|
|
34
|
+
1. CONSENSUS_SCORE (0-100)
|
|
35
|
+
2. AGREED_ELEMENTS
|
|
36
|
+
3. DIVERGENT_ELEMENTS
|
|
37
|
+
4. SYNTHESIZED_PLAN
|
|
38
|
+
5. OPEN_QUESTIONS`;
|
|
39
|
+
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Building
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
export function breakdownPrompt(finalPlan) {
|
|
44
|
+
return `Read the approved final plan below and produce atomic tasks.
|
|
45
|
+
|
|
46
|
+
${finalPlan}
|
|
47
|
+
|
|
48
|
+
Each task must include:
|
|
49
|
+
- task id
|
|
50
|
+
- title and description
|
|
51
|
+
- target files
|
|
52
|
+
- dependencies
|
|
53
|
+
- acceptance criteria
|
|
54
|
+
|
|
55
|
+
Return structured TASKS output.`;
|
|
56
|
+
}
|
|
57
|
+
export function validateBatchPrompt(tasks) {
|
|
58
|
+
return `Read the TASKS input below and create dependency-safe batches.
|
|
59
|
+
|
|
60
|
+
${tasks}
|
|
61
|
+
|
|
62
|
+
For each batch:
|
|
63
|
+
1. validate architecture and codebase fit
|
|
64
|
+
2. confirm or adjust file targets
|
|
65
|
+
3. flag architectural risks
|
|
66
|
+
4. mark whether tasks can run in parallel
|
|
67
|
+
|
|
68
|
+
Return structured BATCHES output.`;
|
|
69
|
+
}
|
|
70
|
+
export function implementBatchPrompt(batches) {
|
|
71
|
+
return `Read the BATCHES input below and implement each batch.
|
|
72
|
+
|
|
73
|
+
${batches}
|
|
74
|
+
|
|
75
|
+
Execution strategy:
|
|
76
|
+
1. Process batches in dependency order.
|
|
77
|
+
2. Within each batch, implement tasks that have no mutual dependency edges.
|
|
78
|
+
3. Tasks that share file targets or have explicit dependency edges must run sequentially.
|
|
79
|
+
4. Wait for all tasks in a batch to complete before starting the next batch.
|
|
80
|
+
|
|
81
|
+
For each task:
|
|
82
|
+
1. implement code changes
|
|
83
|
+
2. add/update tests
|
|
84
|
+
3. run lint/typecheck/tests for impacted scope
|
|
85
|
+
4. return a structured completion report
|
|
86
|
+
|
|
87
|
+
After all batches complete, merge the per-task completion reports into a single IMPLEMENTATION_REPORT output.`;
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Reviewing
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
export function triagePrompt(implementationReport) {
|
|
93
|
+
return `Prepare a review brief for the completed implementation.
|
|
94
|
+
|
|
95
|
+
${implementationReport}
|
|
96
|
+
|
|
97
|
+
Include:
|
|
98
|
+
- summary of implemented changes
|
|
99
|
+
- files changed
|
|
100
|
+
- focus areas and risk points
|
|
101
|
+
|
|
102
|
+
Return a structured REVIEW_BRIEF output.`;
|
|
103
|
+
}
|
|
104
|
+
export function reviewPrompt(brief, modelTag) {
|
|
105
|
+
return `[MODEL_TAG:${modelTag}]
|
|
106
|
+
|
|
107
|
+
Review the current task from the triage brief below for correctness, quality, architecture validity, testing, security, and edge cases.
|
|
108
|
+
|
|
109
|
+
${brief}
|
|
110
|
+
|
|
111
|
+
Classify findings as critical/high/medium/low and include a confidence score (0-100).
|
|
112
|
+
|
|
113
|
+
Return a structured review report.`;
|
|
114
|
+
}
|
|
115
|
+
export function reviewSynthesisPrompt(reviews) {
|
|
116
|
+
const formatted = reviews.map((r) => `--- ${r.tag} ---\n${r.raw}`).join('\n\n');
|
|
117
|
+
return `Read the three review inputs below and synthesize a single authoritative output.
|
|
118
|
+
|
|
119
|
+
${formatted}
|
|
120
|
+
|
|
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`;
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Documentation
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
export function documentPrompt(input) {
|
|
132
|
+
return `Document the approved code changes and update repository documentation.
|
|
133
|
+
|
|
134
|
+
${input}
|
|
135
|
+
|
|
136
|
+
Requirements:
|
|
137
|
+
1. Use the latest approved review outputs and implementation artifacts as source of truth.
|
|
138
|
+
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.
|
|
140
|
+
4. Summarize what docs were changed and why.
|
|
141
|
+
|
|
142
|
+
Return a structured documentation update summary.`;
|
|
143
|
+
}
|
|
144
|
+
export function docReviewPrompt(docUpdate) {
|
|
145
|
+
return `Review the latest documentation pass for completeness, correctness, and repository doc consistency.
|
|
146
|
+
|
|
147
|
+
${docUpdate}
|
|
148
|
+
|
|
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)`;
|
|
154
|
+
}
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Mini-loop specific
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
export function miniBuildPrompt(requirements, reworkFeedback) {
|
|
159
|
+
const rework = reworkFeedback
|
|
160
|
+
? `\n\nPrevious review feedback to address:\n${reworkFeedback}`
|
|
161
|
+
: '';
|
|
162
|
+
return `Implement the current mini-loop requirements below.
|
|
163
|
+
|
|
164
|
+
${requirements}${rework}
|
|
165
|
+
|
|
166
|
+
Requirements:
|
|
167
|
+
1. Apply requested code rework when present.
|
|
168
|
+
2. Add or update tests for behavioral changes.
|
|
169
|
+
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.`;
|
|
171
|
+
}
|
|
172
|
+
export function miniReviewPrompt(implementationReport) {
|
|
173
|
+
return `Review the latest mini-loop implementation output below.
|
|
174
|
+
|
|
175
|
+
${implementationReport}
|
|
176
|
+
|
|
177
|
+
Required output fields:
|
|
178
|
+
1. CHANGE_REQUESTED=YES|NO
|
|
179
|
+
2. UNRESOLVED_BLOCKING_ISSUES=<integer>
|
|
180
|
+
3. CONFIDENCE=<0-100>
|
|
181
|
+
4. concise rework instructions when change is requested`;
|
|
182
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fan-out helpers for multi-model parallel invocation.
|
|
3
|
+
*
|
|
4
|
+
* Every sub-step runs in an **isolated child session** so that
|
|
5
|
+
* intermediate prompts and responses stay off the main context window.
|
|
6
|
+
* Only the final tool return value surfaces in the parent conversation.
|
|
7
|
+
*
|
|
8
|
+
* Because tools execute synchronously in OpenCode (the tool blocks until
|
|
9
|
+
* `execute()` resolves), we can safely `Promise.all` the SDK calls inside
|
|
10
|
+
* a single tool invocation.
|
|
11
|
+
*/
|
|
12
|
+
import type { NamedModel, ModelId } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Opaque client type — the plugin receives `ReturnType<typeof createOpencodeClient>`
|
|
15
|
+
* but we only need `.session.create` and `.session.prompt`.
|
|
16
|
+
* We keep it loosely typed so this module doesn't import the full SDK.
|
|
17
|
+
*/
|
|
18
|
+
export type Client = {
|
|
19
|
+
session: {
|
|
20
|
+
create(options: {
|
|
21
|
+
body?: {
|
|
22
|
+
parentID?: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
};
|
|
25
|
+
}): Promise<{
|
|
26
|
+
data?: {
|
|
27
|
+
id: string;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
prompt(options: {
|
|
31
|
+
path: {
|
|
32
|
+
id: string;
|
|
33
|
+
};
|
|
34
|
+
body: {
|
|
35
|
+
model?: {
|
|
36
|
+
providerID: string;
|
|
37
|
+
modelID: string;
|
|
38
|
+
};
|
|
39
|
+
agent?: string;
|
|
40
|
+
parts: Array<{
|
|
41
|
+
type: 'text';
|
|
42
|
+
text: string;
|
|
43
|
+
}>;
|
|
44
|
+
};
|
|
45
|
+
}): Promise<{
|
|
46
|
+
data?: {
|
|
47
|
+
info: unknown;
|
|
48
|
+
parts: Array<{
|
|
49
|
+
type: string;
|
|
50
|
+
text?: string;
|
|
51
|
+
[k: string]: unknown;
|
|
52
|
+
}>;
|
|
53
|
+
};
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/** Extract all text parts from an SDK prompt response. */
|
|
58
|
+
export declare function extractText(parts: Array<{
|
|
59
|
+
type: string;
|
|
60
|
+
text?: string;
|
|
61
|
+
[k: string]: unknown;
|
|
62
|
+
}>): string;
|
|
63
|
+
export interface FanOutResult {
|
|
64
|
+
tag: string;
|
|
65
|
+
raw: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create one isolated child session per model (in parallel), prompt each,
|
|
69
|
+
* and return the collected text outputs tagged by model.
|
|
70
|
+
*
|
|
71
|
+
* Each model's work runs in its own child session so none of the
|
|
72
|
+
* intermediate outputs pollute the parent context window.
|
|
73
|
+
*/
|
|
74
|
+
export declare function fanOut(client: Client, parentSessionId: string, models: readonly NamedModel[], buildPrompt: (tag: string) => string, agent?: string): Promise<FanOutResult[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Prompt in an isolated child session and return the raw text response.
|
|
77
|
+
*
|
|
78
|
+
* This keeps the sub-step off the parent's context window.
|
|
79
|
+
*/
|
|
80
|
+
export declare function promptSession(client: Client, parentSessionId: string, prompt: string, options?: {
|
|
81
|
+
model?: ModelId;
|
|
82
|
+
agent?: string;
|
|
83
|
+
title?: string;
|
|
84
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fan-out helpers for multi-model parallel invocation.
|
|
3
|
+
*
|
|
4
|
+
* Every sub-step runs in an **isolated child session** so that
|
|
5
|
+
* intermediate prompts and responses stay off the main context window.
|
|
6
|
+
* Only the final tool return value surfaces in the parent conversation.
|
|
7
|
+
*
|
|
8
|
+
* Because tools execute synchronously in OpenCode (the tool blocks until
|
|
9
|
+
* `execute()` resolves), we can safely `Promise.all` the SDK calls inside
|
|
10
|
+
* a single tool invocation.
|
|
11
|
+
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Text extraction helper
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/** Extract all text parts from an SDK prompt response. */
|
|
16
|
+
export function extractText(parts) {
|
|
17
|
+
return parts
|
|
18
|
+
.filter((p) => p.type === 'text' && typeof p.text === 'string')
|
|
19
|
+
.map((p) => p.text)
|
|
20
|
+
.join('\n');
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Isolated session helper
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* Create an isolated child session, send a prompt, and return the text.
|
|
27
|
+
*
|
|
28
|
+
* The child session is parented to `parentSessionId` so it appears in the
|
|
29
|
+
* session tree but its messages do **not** pollute the parent context window.
|
|
30
|
+
*/
|
|
31
|
+
async function promptInChildSession(client, parentSessionId, prompt, options) {
|
|
32
|
+
// 1. Create an isolated child session
|
|
33
|
+
const session = await client.session.create({
|
|
34
|
+
body: {
|
|
35
|
+
parentID: parentSessionId,
|
|
36
|
+
title: options?.title,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const childId = session.data?.id;
|
|
40
|
+
if (!childId) {
|
|
41
|
+
throw new Error('Failed to create child session');
|
|
42
|
+
}
|
|
43
|
+
// 2. Send the prompt to the child session
|
|
44
|
+
const response = await client.session.prompt({
|
|
45
|
+
path: { id: childId },
|
|
46
|
+
body: {
|
|
47
|
+
model: options?.model,
|
|
48
|
+
agent: options?.agent,
|
|
49
|
+
parts: [{ type: 'text', text: prompt }],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
// 3. Extract and return the text
|
|
53
|
+
return extractText(response.data?.parts ?? []);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create one isolated child session per model (in parallel), prompt each,
|
|
57
|
+
* and return the collected text outputs tagged by model.
|
|
58
|
+
*
|
|
59
|
+
* Each model's work runs in its own child session so none of the
|
|
60
|
+
* intermediate outputs pollute the parent context window.
|
|
61
|
+
*/
|
|
62
|
+
export async function fanOut(client, parentSessionId, models, buildPrompt, agent) {
|
|
63
|
+
const results = await Promise.all(models.map(async (nm) => {
|
|
64
|
+
const raw = await promptInChildSession(client, parentSessionId, buildPrompt(nm.tag), {
|
|
65
|
+
model: nm.model,
|
|
66
|
+
agent,
|
|
67
|
+
title: `ff-fanout-${nm.tag}`,
|
|
68
|
+
});
|
|
69
|
+
return { tag: nm.tag, raw };
|
|
70
|
+
}));
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Single prompt helper (isolated)
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
/**
|
|
77
|
+
* Prompt in an isolated child session and return the raw text response.
|
|
78
|
+
*
|
|
79
|
+
* This keeps the sub-step off the parent's context window.
|
|
80
|
+
*/
|
|
81
|
+
export async function promptSession(client, parentSessionId, prompt, options) {
|
|
82
|
+
return promptInChildSession(client, parentSessionId, prompt, options);
|
|
83
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic gate evaluation functions.
|
|
3
|
+
*
|
|
4
|
+
* These replace the LLM-evaluated gate commands with code that applies
|
|
5
|
+
* threshold logic directly. The LLM still produces the synthesis; the
|
|
6
|
+
* gate decision is computed here.
|
|
7
|
+
*/
|
|
8
|
+
import type { GateResult, ConsensusPlan, ReviewSynthesis, DocReview } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Evaluate the planning consensus gate.
|
|
11
|
+
*
|
|
12
|
+
* - `>=75` consensus score → APPROVED
|
|
13
|
+
* - `50-74` → REWORK
|
|
14
|
+
* - `<50` → BLOCKED
|
|
15
|
+
*/
|
|
16
|
+
export declare function evaluatePlanningGate(consensus: ConsensusPlan): GateResult;
|
|
17
|
+
/**
|
|
18
|
+
* Evaluate the review approval gate.
|
|
19
|
+
*
|
|
20
|
+
* - confidence >=95 AND unresolvedIssues == 0 → APPROVED
|
|
21
|
+
* - iteration >= maxIterations → ESCALATE
|
|
22
|
+
* - otherwise → REWORK
|
|
23
|
+
*/
|
|
24
|
+
export declare function evaluateReviewGate(synthesis: ReviewSynthesis, iteration: number, maxIterations?: number): GateResult;
|
|
25
|
+
/**
|
|
26
|
+
* Evaluate the documentation approval gate.
|
|
27
|
+
*
|
|
28
|
+
* - confidence >95, no change requested, 0 unresolved → APPROVED
|
|
29
|
+
* - iteration >= maxIterations → ESCALATE
|
|
30
|
+
* - otherwise → REWORK
|
|
31
|
+
*/
|
|
32
|
+
export declare function evaluateDocGate(review: DocReview, iteration: number, maxIterations?: number): GateResult;
|
|
33
|
+
/**
|
|
34
|
+
* Evaluate the mini-loop implementation gate.
|
|
35
|
+
*
|
|
36
|
+
* - confidence >95, no change requested, 0 blocking issues → APPROVED
|
|
37
|
+
* - iteration >= maxIterations → ESCALATE
|
|
38
|
+
* - otherwise → REWORK
|
|
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;
|
|
46
|
+
/**
|
|
47
|
+
* Evaluate the mini-loop documentation gate.
|
|
48
|
+
* Same thresholds as the pipeline doc gate.
|
|
49
|
+
*/
|
|
50
|
+
export declare const evaluateMiniLoopDocGate: typeof evaluateDocGate;
|