@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.
@@ -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;