@syntesseraai/opencode-feature-factory 0.7.3 → 0.8.0

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/README.md CHANGED
@@ -56,6 +56,20 @@ These colors are intentionally unique to avoid collisions in OpenCode agent UIs
56
56
  - Coordinator and synthesis model defaults to ChatGPT 5.4 via frontmatter `model:` declarations in orchestrator commands.
57
57
  - All orchestrator commands include an explicit **orchestrator constraint**: they must NOT directly read, edit, modify, write, or create any code/config/repository files — they only dispatch sub-commands, pass structured data via `{as:name}` / `$RESULT[name]`, and evaluate gate/loop conditions.
58
58
 
59
+ ## Tools
60
+
61
+ The plugin exposes three MCP tools via the `feature-factory` agent:
62
+
63
+ | Tool | Description |
64
+ |------|-------------|
65
+ | `ff_pipeline` | Full multi-model pipeline: planning → build → review → documentation. Uses hardcoded per-role model defaults (see Model Routing below). |
66
+ | `ff_mini_loop` | Lightweight build → review → documentation loop. **Does not hardcode model defaults** — all roles inherit the current session model when omitted. |
67
+ | `ff_list_models` | Read-only discovery tool. Queries the OpenCode SDK to list all available providers, models, capability badges, connected status, and defaults. |
68
+
69
+ ### Mini-Loop Model Inheritance
70
+
71
+ When `ff_mini_loop` model parameters are omitted, child sessions inherit the parent session's model (the SDK `model` field is omitted entirely from the prompt body). The `doc_model` has a cascade: if `build_model` is explicitly set but `doc_model` is not, the documentation step uses the same model as build. Users can call `ff_list_models` to discover available `provider/model` strings before overriding.
72
+
59
73
  ## Command Tree
60
74
 
61
75
  - `/pipeline/start`
@@ -11,6 +11,7 @@ tools:
11
11
  task: true
12
12
  ff_pipeline: true
13
13
  ff_mini_loop: true
14
+ ff_list_models: true
14
15
  permission:
15
16
  skill:
16
17
  '*': allow
@@ -64,7 +65,7 @@ Ask the user which workflow they prefer. If they are unsure, recommend one based
64
65
 
65
66
  Present the default models that will be used for the chosen workflow:
66
67
 
67
- **Pipeline defaults:**
68
+ **Pipeline defaults:** (hardcoded per-role model assignments)
68
69
  - Planning fan-out: opus (anthropic/claude-opus-4-6), gemini (opencode/gemini-3.1-pro), codex (openai/gpt-5.3-codex)
69
70
  - Review fan-out: same as planning
70
71
  - Orchestrator (synthesis/triage): openai/gpt-5.4
@@ -73,13 +74,14 @@ Present the default models that will be used for the chosen workflow:
73
74
  - Documentation: openai/gpt-5.3-codex
74
75
  - Doc review: opencode/gemini-3.1-pro
75
76
 
76
- **Mini-loop defaults:**
77
- - Build: openai/gpt-5.3-codex
78
- - Review: openai/gpt-5.4
79
- - Documentation: openai/gpt-5.3-codex
80
- - Doc review: opencode/gemini-3.1-pro
77
+ **Mini-loop defaults:** (inherits the current session model)
78
+ - All roles (build, review, documentation, doc review) default to the **current session model** — no hardcoded model is forced.
79
+ - This means the mini-loop automatically uses whatever model you're currently chatting with.
80
+ - You can override any individual role if desired (e.g., use a different model for review).
81
+
82
+ For the **Pipeline**, ask: "Would you like to use these default models, or would you like to override any of them?"
81
83
 
82
- Ask: "Would you like to use these default models, or would you like to override any of them?"
84
+ For the **Mini-loop**, ask: "The mini-loop will use your current session model for all roles by default. Would you like to override any specific roles (build, review, doc, doc_review)? You can call `ff_list_models` to see available providers and models."
83
85
 
84
86
  If they want to override, collect the provider/model strings for each role they want to change.
85
87
 
@@ -2,9 +2,9 @@
2
2
  * Tool registry for Feature Factory.
3
3
  *
4
4
  * Exports a function that creates all FF tools given the SDK client.
5
- * Only entrypoint tools (pipeline, mini-loop) are exposed to the LLM.
5
+ * Only entrypoint tools (pipeline, mini-loop) and discovery tools
6
6
  * Internal workflow steps are orchestrated programmatically, not as
7
- * individually callable tools.
7
+ * individually callable tools. The list-models tool is a read-only discovery helper.
8
8
  */
9
9
  import type { ToolDefinition } from '@opencode-ai/plugin/tool';
10
10
  import type { Client } from '../workflow/fan-out.js';
@@ -2,12 +2,13 @@
2
2
  * Tool registry for Feature Factory.
3
3
  *
4
4
  * Exports a function that creates all FF tools given the SDK client.
5
- * Only entrypoint tools (pipeline, mini-loop) are exposed to the LLM.
5
+ * Only entrypoint tools (pipeline, mini-loop) and discovery tools
6
6
  * Internal workflow steps are orchestrated programmatically, not as
7
- * individually callable tools.
7
+ * individually callable tools. The list-models tool is a read-only discovery helper.
8
8
  */
9
9
  import { createPipelineTool } from './pipeline.js';
10
10
  import { createMiniLoopTool } from './mini-loop.js';
11
+ import { createListModelsTool } from './list-models.js';
11
12
  /**
12
13
  * Build the tool map to be merged into the plugin's `Hooks.tool` output.
13
14
  *
@@ -17,5 +18,6 @@ export function createWorkflowTools(client) {
17
18
  return {
18
19
  ff_pipeline: createPipelineTool(client),
19
20
  ff_mini_loop: createMiniLoopTool(client),
21
+ ff_list_models: createListModelsTool(client),
20
22
  };
21
23
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * ff_list_models — Discovery tool for available providers and models.
3
+ *
4
+ * Read-only tool that queries the OpenCode SDK to list all available
5
+ * providers and their models, so users can see what's available before
6
+ * overriding mini-loop or pipeline model parameters.
7
+ */
8
+ import type { Client } from '../workflow/fan-out.js';
9
+ export declare function createListModelsTool(client: Client): {
10
+ description: string;
11
+ args: {};
12
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin/tool").ToolContext): Promise<string>;
13
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * ff_list_models — Discovery tool for available providers and models.
3
+ *
4
+ * Read-only tool that queries the OpenCode SDK to list all available
5
+ * providers and their models, so users can see what's available before
6
+ * overriding mini-loop or pipeline model parameters.
7
+ */
8
+ import { tool } from '@opencode-ai/plugin/tool';
9
+ // ---------------------------------------------------------------------------
10
+ // Tool factory
11
+ // ---------------------------------------------------------------------------
12
+ export function createListModelsTool(client) {
13
+ return tool({
14
+ description: 'List all available AI providers and their models. ' +
15
+ 'Use this to discover which provider/model strings can be passed to ff_pipeline or ff_mini_loop.',
16
+ args: {},
17
+ async execute() {
18
+ const response = await client.provider.list();
19
+ const data = response.data;
20
+ if (!data) {
21
+ return 'Unable to retrieve provider list from the OpenCode SDK.';
22
+ }
23
+ const { all: providers, connected, default: defaults } = data;
24
+ const lines = ['# Available Providers and Models\n'];
25
+ // Show default model assignments
26
+ if (defaults && Object.keys(defaults).length > 0) {
27
+ lines.push('## Defaults\n');
28
+ for (const [role, model] of Object.entries(defaults)) {
29
+ lines.push(`- **${role}**: ${model}`);
30
+ }
31
+ lines.push('');
32
+ }
33
+ // Show connected providers
34
+ if (connected.length > 0) {
35
+ lines.push(`## Connected Providers\n`);
36
+ lines.push(connected.map((p) => `\`${p}\``).join(', '));
37
+ lines.push('');
38
+ }
39
+ // List all providers and their models
40
+ for (const provider of providers) {
41
+ const isConnected = connected.includes(provider.id);
42
+ const statusBadge = isConnected ? '✅' : '⚠️ (not connected)';
43
+ lines.push(`## ${provider.name} ${statusBadge}\n`);
44
+ lines.push(`Provider ID: \`${provider.id}\`\n`);
45
+ const modelEntries = Object.entries(provider.models);
46
+ if (modelEntries.length === 0) {
47
+ lines.push('_No models available._\n');
48
+ continue;
49
+ }
50
+ lines.push('| Model ID | Name | Reasoning | Tool Calls | Attachments |');
51
+ lines.push('|----------|------|-----------|------------|-------------|');
52
+ for (const [, model] of modelEntries) {
53
+ const flags = [
54
+ model.reasoning ? '✅' : '—',
55
+ model.tool_call ? '✅' : '—',
56
+ model.attachment ? '✅' : '—',
57
+ ];
58
+ lines.push(`| \`${provider.id}/${model.id}\` | ${model.name} | ${flags[0]} | ${flags[1]} | ${flags[2]} |`);
59
+ }
60
+ lines.push('');
61
+ }
62
+ lines.push('---\n' +
63
+ 'Use the `provider_id/model_id` format when overriding models in `ff_mini_loop` or `ff_pipeline`.');
64
+ return lines.join('\n');
65
+ },
66
+ });
67
+ }
@@ -5,7 +5,7 @@
5
5
  * Implements a build/review loop, then a documentation loop.
6
6
  */
7
7
  import { tool } from '@opencode-ai/plugin/tool';
8
- import { promptSession, evaluateMiniLoopImplGate, evaluateMiniLoopDocGate, BUILD_MODEL, DOC_MODEL, DOC_REVIEW_MODEL, ORCHESTRATOR_MODEL, parseModelString, } from '../workflow/orchestrator.js';
8
+ import { promptSession, evaluateMiniLoopImplGate, evaluateMiniLoopDocGate, parseModelString, } from '../workflow/orchestrator.js';
9
9
  import { miniBuildPrompt, miniReviewPrompt, documentPrompt, docReviewPrompt } from './prompts.js';
10
10
  import { parseMiniReview, parseDocReview } from './parsers.js';
11
11
  // ---------------------------------------------------------------------------
@@ -16,7 +16,7 @@ export function createMiniLoopTool(client) {
16
16
  return tool({
17
17
  description: 'Run the Feature Factory mini-loop: build → review (with rework loop) → documentation. ' +
18
18
  'Lighter weight than the full pipeline — no multi-model planning phase. ' +
19
- 'All model parameters are optional and use sensible defaults.',
19
+ 'All model parameters are optional when omitted, child sessions inherit the current session model.',
20
20
  args: {
21
21
  requirements: tool.schema
22
22
  .string()
@@ -24,39 +24,39 @@ export function createMiniLoopTool(client) {
24
24
  build_model: tool.schema
25
25
  .string()
26
26
  .optional()
27
- .describe('provider/model for building and implementation. Defaults to openai/gpt-5.3-codex.'),
27
+ .describe('provider/model for building and implementation. When omitted, inherits the session model.'),
28
28
  review_model: tool.schema
29
29
  .string()
30
30
  .optional()
31
- .describe('provider/model for implementation review. Defaults to openai/gpt-5.4.'),
31
+ .describe('provider/model for implementation review. When omitted, inherits the session model.'),
32
32
  doc_model: tool.schema
33
33
  .string()
34
34
  .optional()
35
- .describe('provider/model for documentation writing. Defaults to the build model.'),
35
+ .describe('provider/model for documentation writing. When omitted, defaults to build_model if provided, otherwise inherits the session model.'),
36
36
  doc_review_model: tool.schema
37
37
  .string()
38
38
  .optional()
39
- .describe('provider/model for documentation review. Defaults to opencode/gemini-3.1-pro.'),
39
+ .describe('provider/model for documentation review. When omitted, inherits the session model.'),
40
40
  },
41
41
  async execute(args, context) {
42
42
  const totalStartMs = Date.now();
43
43
  const sessionId = context.sessionID;
44
44
  const { requirements } = args;
45
- // Resolve models — use provided overrides or fall back to defaults
45
+ // Resolve models — use provided overrides or undefined (inherit session model)
46
46
  const buildModel = args.build_model
47
47
  ? parseModelString(args.build_model)
48
- : BUILD_MODEL;
48
+ : undefined;
49
49
  const reviewModel = args.review_model
50
50
  ? parseModelString(args.review_model)
51
- : ORCHESTRATOR_MODEL;
51
+ : undefined;
52
52
  const docModel = args.doc_model
53
53
  ? parseModelString(args.doc_model)
54
54
  : args.build_model
55
55
  ? buildModel
56
- : DOC_MODEL;
56
+ : undefined;
57
57
  const docReviewModel = args.doc_review_model
58
58
  ? parseModelString(args.doc_review_model)
59
- : DOC_REVIEW_MODEL;
59
+ : undefined;
60
60
  const report = [];
61
61
  const addReport = (phase, msg) => {
62
62
  report.push(`## ${phase}\n${msg}`);
@@ -12,7 +12,7 @@
12
12
  import type { NamedModel, ModelId } from './types.js';
13
13
  /**
14
14
  * Opaque client type — the plugin receives `ReturnType<typeof createOpencodeClient>`
15
- * but we only need `.session.create` and `.session.prompt`.
15
+ * but we only need a subset of the SDK surface.
16
16
  * We keep it loosely typed so this module doesn't import the full SDK.
17
17
  */
18
18
  export type Client = {
@@ -53,6 +53,33 @@ export type Client = {
53
53
  };
54
54
  }>;
55
55
  };
56
+ provider: {
57
+ list(options?: {
58
+ query?: {
59
+ directory?: string;
60
+ };
61
+ }): Promise<{
62
+ data?: {
63
+ all: Array<{
64
+ id: string;
65
+ name: string;
66
+ models: {
67
+ [key: string]: {
68
+ id: string;
69
+ name: string;
70
+ attachment: boolean;
71
+ reasoning: boolean;
72
+ tool_call: boolean;
73
+ };
74
+ };
75
+ }>;
76
+ connected: Array<string>;
77
+ default: {
78
+ [key: string]: string;
79
+ };
80
+ };
81
+ }>;
82
+ };
56
83
  };
57
84
  /** Extract all text parts from an SDK prompt response. */
58
85
  export declare function extractText(parts: Array<{
@@ -41,13 +41,20 @@ async function promptInChildSession(client, parentSessionId, prompt, options) {
41
41
  throw new Error('Failed to create child session');
42
42
  }
43
43
  // 2. Send the prompt to the child session
44
+ // When model is undefined, omit it entirely so the child session
45
+ // inherits the parent session's model (SDK default behavior).
46
+ const body = {
47
+ parts: [{ type: 'text', text: prompt }],
48
+ };
49
+ if (options?.model) {
50
+ body.model = options.model;
51
+ }
52
+ if (options?.agent) {
53
+ body.agent = options.agent;
54
+ }
44
55
  const response = await client.session.prompt({
45
56
  path: { id: childId },
46
- body: {
47
- model: options?.model,
48
- agent: options?.agent,
49
- parts: [{ type: 'text', text: prompt }],
50
- },
57
+ body,
51
58
  });
52
59
  // 3. Extract and return the text
53
60
  return extractText(response.data?.parts ?? []);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@syntesseraai/opencode-feature-factory",
4
- "version": "0.7.3",
4
+ "version": "0.8.0",
5
5
  "type": "module",
6
6
  "description": "OpenCode plugin for Feature Factory agents - provides sub-agents and skills for validation, review, security, and architecture assessment",
7
7
  "license": "MIT",