@syntesseraai/opencode-feature-factory 0.11.9 → 0.11.11

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.md CHANGED
@@ -41,7 +41,7 @@ When work changes behavior, workflows, configuration, operational guidance, or r
41
41
  - Use `read`, `glob`, and `grep` for targeted file inspection.
42
42
  - Writable agents should prefer native `edit` for file updates.
43
43
  - Keep `edit` restricted on read-only agents (`planning`, `reviewing`).
44
- - Route Git operations through `bash` with explicit `git`/`gh` allowlists (and optional `oo git`/`oo gh` wrappers) per OpenCode granular `permission.bash` rules, with last-match-wins ordering.
44
+ - Route Git operations through `bash` with explicit `git`/`gh` allowlists (and optional `oo git`/`oo gh` wrappers) per OpenCode granular `permissions` rules (`permissions.bash` object syntax), with last-match-wins ordering.
45
45
  - Keep read-only agents deny-first for shell usage (`bash: {'*': deny}` with explicit `git`/`gh` and `oo git`/`oo gh` overrides).
46
46
  - Explicitly disable PTY tools (`pty_spawn`, `pty_write`, `pty_read`, `pty_list`, `pty_kill`) on read-only agents; they must not rely on PTY as a back door.
47
47
  - Use `todowrite` for multi-step tasks to keep progress visible.
package/README.md CHANGED
@@ -82,7 +82,7 @@ Stage-agent code discovery is graph-first: planning/building/reviewing/documenti
82
82
 
83
83
  ### Permission policy for shell and PTY tools
84
84
 
85
- Feature Factory agent assets follow OpenCode granular permissions (`permission.bash`) with object syntax and deny/allow precedence based on **last matching rule wins**.
85
+ Feature Factory agent assets follow OpenCode granular `permissions` blocks (including `permissions.bash` object syntax) with deny/allow precedence based on **last matching rule wins**.
86
86
 
87
87
  - Stage agents (`planning`, `building`, `reviewing`, `documenting`) expose `bash` and explicitly allow `git`/`gh` command patterns, including `oo`-prefixed variants (`oo git ...`, `oo gh ...`).
88
88
  - Read-only agents (`planning`, `reviewing`) are deny-first for shell commands (`'*': deny`) with explicit `git`/`gh` exceptions, including `oo git`/`oo gh`.
@@ -90,12 +90,18 @@ Feature Factory agent assets follow OpenCode granular permissions (`permission.b
90
90
 
91
91
  ### Plugin auto-handoff safety net
92
92
 
93
- The plugin includes an auto-handoff hook that continues deterministic next steps by reading explicit machine-readable fields from assistant output and dispatching the extracted prompt as a normal text prompt via `client.session.prompt(...)` while setting the target agent.
93
+ The plugin includes an auto-handoff hook that continues deterministic next steps by reading `RECOMMENDED_NEXT_STEP` from assistant output and dispatching the extracted text as a normal prompt via `client.session.prompt(...)`.
94
94
 
95
95
  - The orchestrator (`@feature-factory`) remains the source of truth for workflow progression.
96
96
  - Auto-handoff is a continuation safety net, not a replacement for orchestrator logic.
97
- - Multi-stage continuation should target `feature-factory`; one-stage follow-up can target a stage agent.
98
- - Dispatch is performed through direct API prompt calls (not slash-command execution), and handoffs are injected as the next user turn for the targeted agent.
97
+ - On `session.idle`, the hook queries `client.session.messages({ path: { id: sessionId } })`, scans messages in reverse order, and parses only the latest assistant message.
98
+ - The hook extracts the next prompt only from these exact machine-readable formats:
99
+ - `RECOMMENDED_NEXT_STEP: "<human-readable next step>"`
100
+ - `RECOMMENDED_NEXT_STEP="<human-readable next step>"`
101
+
102
+ - If the latest assistant message does not include one of those formats, the hook does nothing.
103
+ - The hook does not buffer streaming assistant chunks and does not enforce a fixed per-session handoff cap.
104
+ - Dispatch is performed through direct API prompt calls (not slash-command execution), and the extracted next-step text is injected as the next user turn to `feature-factory`.
99
105
 
100
106
  ## Quick Commands
101
107
 
@@ -132,7 +138,6 @@ The plugin merges the following MCP servers into global OpenCode config when mis
132
138
 
133
139
  - `jina-ai`
134
140
  - `gh_grep`
135
- - `context7`
136
141
 
137
142
  `codebase-memory-mcp` is currently treated as an optional prerequisite (not auto-provisioned by this plugin) because it commonly requires repository-specific indexing/setup.
138
143
 
@@ -145,12 +150,12 @@ The plugin also merges the following default plugins when they are missing:
145
150
 
146
151
  ### Merge Outcomes
147
152
 
148
- `DEFAULT_MCP_SERVERS` currently includes 3 servers (`jina-ai`, `gh_grep`, `context7`).
153
+ `DEFAULT_MCP_SERVERS` currently includes 2 servers (`jina-ai`, `gh_grep`).
149
154
 
150
155
  The merge behavior is additive and non-destructive:
151
156
 
152
- - Empty or missing existing MCP config -> 3 servers after merge.
153
- - Existing config with 1 custom server and a customized `gh_grep` entry -> 4 servers after merge (custom + customized `gh_grep` + 2 missing defaults).
157
+ - Empty or missing existing MCP config -> 2 servers after merge.
158
+ - Existing config with 1 custom server and a customized `gh_grep` entry -> 3 servers after merge (custom + customized `gh_grep` + 1 missing default).
154
159
  - Existing server definitions are preserved and never overwritten by defaults.
155
160
 
156
161
  Plugin merge behavior is also additive and non-destructive:
@@ -3,19 +3,7 @@ description: Implements features from approved plans and returns structured impl
3
3
  mode: all
4
4
  color: '#d2d21a'
5
5
  model: openai/gpt-5.3-codex
6
- tools:
7
- read: true
8
- write: true
9
- edit: true
10
- bash: true
11
- skill: true
12
- task: true
13
- 'codebase-memory-mcp_search_graph': true
14
- 'codebase-memory-mcp_get_architecture': true
15
- 'codebase-memory-mcp_trace_call_path': true
16
- 'codebase-memory-mcp_get_code_snippet': true
17
- 'codebase-memory-mcp_search_code': true
18
- permission:
6
+ permissions:
19
7
  skill:
20
8
  '*': allow
21
9
  task:
@@ -3,24 +3,12 @@ description: Documentation implementation specialist for pipeline documentation
3
3
  mode: all
4
4
  color: '#f97316'
5
5
  model: opencode/gemini-3.1-pro
6
- tools:
7
- read: true
8
- write: true
9
- edit: true
10
- bash: true
11
- pty_spawn: false
12
- pty_write: false
13
- pty_read: false
14
- pty_list: false
15
- pty_kill: false
16
- skill: true
17
- task: true
18
- 'codebase-memory-mcp_search_graph': true
19
- 'codebase-memory-mcp_get_architecture': true
20
- 'codebase-memory-mcp_trace_call_path': true
21
- 'codebase-memory-mcp_get_code_snippet': true
22
- 'codebase-memory-mcp_search_code': true
23
- permission:
6
+ permissions:
7
+ pty_spawn: deny
8
+ pty_write: deny
9
+ pty_read: deny
10
+ pty_list: deny
11
+ pty_kill: deny
24
12
  skill:
25
13
  '*': allow
26
14
  task:
@@ -2,21 +2,16 @@
2
2
  description: Feature Factory — native stage orchestrator for planning, building, reviewing, and documenting through sub-agents.
3
3
  mode: primary
4
4
  color: '#0fc24e'
5
- model: github-copilot/gpt-5.4-mini
6
- tools:
7
- read: true
8
- glob: true
9
- write: false
10
- edit: false
11
- bash: false
12
- pty_spawn: false
13
- pty_write: false
14
- pty_read: false
15
- pty_list: false
16
- pty_kill: false
17
- skill: true
18
- task: true
19
- permission:
5
+ model: openai/gpt-5.4-pro
6
+ permissions:
7
+ write: deny
8
+ edit: deny
9
+ bash: deny
10
+ pty_spawn: deny
11
+ pty_write: deny
12
+ pty_read: deny
13
+ pty_list: deny
14
+ pty_kill: deny
20
15
  skill:
21
16
  '*': allow
22
17
  task:
@@ -217,8 +212,8 @@ For each iteration `n`:
217
212
  - After the required user confirmation checkpoint, successful Build always proceeds to Document.
218
213
  - After the required user confirmation checkpoint, successful Document always proceeds to Review.
219
214
  - Build and Document are stage-complete states, not workflow-complete states.
220
- - Therefore, successful Build and successful Document must emit `AUTO_HANDOFF=YES`.
221
- - Successful termination with `AUTO_HANDOFF=NO` is allowed only when Review is approved with no further required work (or when blocked/escalated/waiting on required user input).
215
+ - Therefore, successful Build and successful Document must include a deterministic `RECOMMENDED_NEXT_STEP` so continuation can proceed automatically.
216
+ - Omit `RECOMMENDED_NEXT_STEP` only when the workflow is fully complete, blocked, escalated, or waiting on required user input.
222
217
 
223
218
  ### Direct-execution prohibition
224
219
 
@@ -238,40 +233,36 @@ When a workflow ends (success or escalation), return:
238
233
  5. `OPEN_ISSUES`
239
234
  6. `RECOMMENDED_NEXT_STEP`
240
235
 
241
- `RECOMMENDED_NEXT_STEP` is reserved for true end-of-workflow recommendations only; do not use it to defer unfinished required stages.
236
+ `RECOMMENDED_NEXT_STEP` should provide the exact next action when deterministic continuation is appropriate.
242
237
 
243
- ## Auto-handoff contract (plugin-facing)
238
+ ## Plugin continuation contract
244
239
 
245
- When the next step is deterministic and does not require user input, append this exact block at the end of the response:
240
+ When the next step is deterministic and does not require user input, append exactly one of these lines at the end of the response:
246
241
 
247
242
  ```text
248
- RECOMMENDED_NEXT_STEP=<ACTION> "<human-readable next step>"
249
-
250
- AUTO_HANDOFF=YES|NO
251
- AUTO_HANDOFF_TARGET=feature-factory|building|documenting|reviewing
252
- AUTO_HANDOFF_REASON=CONTINUE_WORKFLOW|REWORK|DOCUMENT|REVIEW
253
- AUTO_HANDOFF_PROMPT<<FF_PROMPT
254
- <exact prompt text for next turn>
255
- FF_PROMPT
243
+ RECOMMENDED_NEXT_STEP: "<human-readable next step>"
244
+ ```
245
+
246
+ or
247
+
248
+ ```text
249
+ RECOMMENDED_NEXT_STEP="<human-readable next step>"
256
250
  ```
257
251
 
258
252
  Rules:
259
253
 
260
- - After the required user confirmation checkpoint, use `AUTO_HANDOFF=YES` whenever the next workflow step is predetermined by the fixed workflow.
254
+ - After the required user confirmation checkpoint, emit `RECOMMENDED_NEXT_STEP` whenever the next workflow step is predetermined by the fixed workflow.
261
255
  - In the fixed workflow, successful Build must always continue to Document.
262
256
  - In the fixed workflow, successful Document must always continue to Review.
263
- - Successful Build and successful Document are stage-complete but not workflow-complete; they must not output `AUTO_HANDOFF=NO`.
264
- - Use `AUTO_HANDOFF_TARGET=feature-factory` when the workflow should continue across multiple stages.
265
- - Use stage targets (`building`, `documenting`, `reviewing`) only for focused one-stage follow-up.
266
- - Never use `AUTO_HANDOFF=YES` before the required user confirmation checkpoint.
267
- - Use `AUTO_HANDOFF=NO` only when the workflow is fully complete, blocked, escalated, or waiting on required user input.
268
- - `RECOMMENDED_NEXT_STEP` must match the machine-readable handoff block semantically.
257
+ - Successful Build and successful Document are stage-complete but not workflow-complete; do not omit `RECOMMENDED_NEXT_STEP` for these outcomes.
258
+ - Never emit `RECOMMENDED_NEXT_STEP` before the required user confirmation checkpoint.
259
+ - Omit `RECOMMENDED_NEXT_STEP` when the workflow is fully complete, blocked, escalated, or waiting on required user input.
269
260
  - For normal workflow progression, continue by delegating to stage agents directly (`@building`, `@documenting`, `@reviewing`), not by invoking slash commands such as `/ff-review`, `/ff-document`, or `/ff-rework`.
270
261
 
271
262
  Expected mappings:
272
263
 
273
- - Planning approved + user confirmed -> `AUTO_HANDOFF=YES`, `AUTO_HANDOFF_TARGET=feature-factory`, `AUTO_HANDOFF_REASON=CONTINUE_WORKFLOW`
274
- - Build succeeded -> `AUTO_HANDOFF=YES`, `AUTO_HANDOFF_TARGET=feature-factory`, `AUTO_HANDOFF_REASON=DOCUMENT`
275
- - Document succeeded -> `AUTO_HANDOFF=YES`, `AUTO_HANDOFF_TARGET=feature-factory`, `AUTO_HANDOFF_REASON=REVIEW`
276
- - Review requires changes -> `AUTO_HANDOFF=YES`, `AUTO_HANDOFF_TARGET=feature-factory`, `AUTO_HANDOFF_REASON=REWORK`
277
- - Review approved with no further work -> `AUTO_HANDOFF=NO`
264
+ - Planning approved + user confirmed -> `RECOMMENDED_NEXT_STEP` describes running Build
265
+ - Build succeeded -> `RECOMMENDED_NEXT_STEP` describes running Document
266
+ - Document succeeded -> `RECOMMENDED_NEXT_STEP` describes running Review
267
+ - Review requires changes -> `RECOMMENDED_NEXT_STEP` describes Build rework actions
268
+ - Review approved with no further work -> omit `RECOMMENDED_NEXT_STEP`
@@ -3,24 +3,14 @@ description: Creates implementation plans and planning gates for pipeline and ad
3
3
  mode: all
4
4
  color: '#3b82f6'
5
5
  model: openai/gpt-5.4
6
- tools:
7
- read: true
8
- write: false
9
- edit: false
10
- bash: true
11
- pty_spawn: false
12
- pty_write: false
13
- pty_read: false
14
- pty_list: false
15
- pty_kill: false
16
- skill: true
17
- task: true
18
- 'codebase-memory-mcp_search_graph': true
19
- 'codebase-memory-mcp_get_architecture': true
20
- 'codebase-memory-mcp_trace_call_path': true
21
- 'codebase-memory-mcp_get_code_snippet': true
22
- 'codebase-memory-mcp_search_code': true
23
- permission:
6
+ permissions:
7
+ write: deny
8
+ edit: deny
9
+ pty_spawn: deny
10
+ pty_write: deny
11
+ pty_read: deny
12
+ pty_list: deny
13
+ pty_kill: deny
24
14
  skill:
25
15
  '*': allow
26
16
  task:
@@ -3,24 +3,14 @@ description: Unified validation agent for code and documentation. Performs accep
3
3
  mode: all
4
4
  color: '#8b5cf6'
5
5
  model: opencode/glm-5.1
6
- tools:
7
- read: true
8
- write: false
9
- edit: false
10
- bash: true
11
- pty_spawn: false
12
- pty_write: false
13
- pty_read: false
14
- pty_list: false
15
- pty_kill: false
16
- skill: true
17
- task: true
18
- 'codebase-memory-mcp_search_graph': true
19
- 'codebase-memory-mcp_get_architecture': true
20
- 'codebase-memory-mcp_trace_call_path': true
21
- 'codebase-memory-mcp_get_code_snippet': true
22
- 'codebase-memory-mcp_search_code': true
23
- permission:
6
+ permissions:
7
+ write: deny
8
+ edit: deny
9
+ pty_spawn: deny
10
+ pty_write: deny
11
+ pty_read: deny
12
+ pty_list: deny
13
+ pty_kill: deny
24
14
  skill:
25
15
  '*': allow
26
16
  task:
@@ -128,28 +118,6 @@ When acting as gate reviewer, output status line exactly:
128
118
  - `REVIEW_GATE=APPROVED|REWORK|ESCALATE`
129
119
  - `DOCUMENTATION_GATE=APPROVED|REWORK|ESCALATE`
130
120
 
131
- ## Auto-handoff output contract (for standalone review runs)
132
-
133
- When a standalone review clearly requires rework and the next action is deterministic, append:
134
-
135
- ```text
136
- RECOMMENDED_NEXT_STEP=REWORK "<specific rework summary>"
137
- AUTO_HANDOFF=YES
138
- AUTO_HANDOFF_TARGET=building
139
- AUTO_HANDOFF_REASON=REWORK
140
- AUTO_HANDOFF_PROMPT<<FF_PROMPT
141
- apply the rework recommendations
142
-
143
- - <specific action item>
144
- FF_PROMPT
145
- ```
146
-
147
- Otherwise emit:
148
-
149
- ```text
150
- AUTO_HANDOFF=NO
151
- ```
152
-
153
121
  ## Operating Mode
154
122
 
155
123
  - Use normalized, resolved handoff context (summary/status/action items/issues plus resolved prior output excerpts) rather than file-based artifacts.
package/bin/ff-deploy.js CHANGED
@@ -45,13 +45,6 @@ const DEFAULT_MCP_SERVERS = {
45
45
  type: 'remote',
46
46
  url: 'https://mcp.grep.app',
47
47
  },
48
- context7: {
49
- type: 'remote',
50
- url: 'https://mcp.context7.com/mcp',
51
- headers: {
52
- CONTEXT7_API_KEY: '{env:CONTEXT7_API_KEY}',
53
- },
54
- },
55
48
  };
56
49
 
57
50
  const DEFAULT_PLUGINS = [
@@ -1,22 +1,10 @@
1
1
  import { type Plugin, type PluginInput } from '@opencode-ai/plugin';
2
- type AutoHandoffTarget = 'feature-factory' | 'building' | 'documenting' | 'reviewing';
3
- type AutoHandoffReason = 'CONTINUE_WORKFLOW' | 'REWORK' | 'DOCUMENT' | 'REVIEW';
4
- export type ParsedAutoHandoff = {
5
- enabled: false;
6
- } | {
7
- enabled: true;
8
- target: AutoHandoffTarget;
9
- reason: AutoHandoffReason;
10
- prompt: string;
11
- };
12
2
  type Client = PluginInput['client'];
13
3
  type SessionMetadata = {
14
4
  parentID?: string;
15
5
  };
16
- export declare function parseAutoHandoff(text: string): ParsedAutoHandoff | null;
17
- export declare function fingerprint(sessionId: string, messageId: string, handoff: Extract<ParsedAutoHandoff, {
18
- enabled: true;
19
- }>): string;
6
+ export declare function parseRecommendedNextStep(text: string): string | null;
7
+ export declare function fingerprint(sessionId: string, messageId: string, recommendedNextStep: string): string;
20
8
  export declare function getSessionMetadata(client: Client, sessionId: string): Promise<SessionMetadata>;
21
- export declare const AutoHandoffHooksPlugin: Plugin;
9
+ export declare const RecommendedNextStepHooksPlugin: Plugin;
22
10
  export {};
@@ -1,9 +1,6 @@
1
1
  const SERVICE_NAME = 'feature-factory';
2
- const MAX_AUTO_HANDOFFS_PER_SESSION = 5;
3
- const AUTO_HANDOFF_FLAG_RE = /^\s*AUTO_HANDOFF\s*=\s*(YES|NO)\s*$/im;
4
- const AUTO_HANDOFF_TARGET_RE = /^\s*AUTO_HANDOFF_TARGET\s*=\s*(feature-factory|building|documenting|reviewing)\s*$/im;
5
- const AUTO_HANDOFF_REASON_RE = /^\s*AUTO_HANDOFF_REASON\s*=\s*(CONTINUE_WORKFLOW|REWORK|DOCUMENT|REVIEW)\s*$/im;
6
- const AUTO_HANDOFF_PROMPT_RE = /^\s*(?:AUTO_HANDOFF_PROMPT|FF_PROMPT)\s*<<\s*FF_PROMPT\s*\r?\n([\s\S]*?)\r?\n\s*FF_PROMPT\s*$/im;
2
+ const RECOMMENDED_NEXT_STEP_COLON_RE = /^\s*RECOMMENDED_NEXT_STEP\s*:\s*"([^"\r\n]+)"\s*$/im;
3
+ const RECOMMENDED_NEXT_STEP_EQUALS_RE = /^\s*RECOMMENDED_NEXT_STEP\s*=\s*"([^"\r\n]+)"\s*$/im;
7
4
  async function log(client, level, message, extra) {
8
5
  try {
9
6
  await client.app.log({
@@ -19,11 +16,11 @@ async function log(client, level, message, extra) {
19
16
  return undefined;
20
17
  }
21
18
  }
22
- function extractMessageText(info) {
23
- if (!info || typeof info !== 'object') {
19
+ function extractMessageText(message) {
20
+ if (!message || typeof message !== 'object') {
24
21
  return '';
25
22
  }
26
- const parts = info.parts;
23
+ const parts = message.parts;
27
24
  if (!Array.isArray(parts)) {
28
25
  return '';
29
26
  }
@@ -37,34 +34,20 @@ function extractMessageText(info) {
37
34
  })
38
35
  .join('');
39
36
  }
40
- export function parseAutoHandoff(text) {
37
+ export function parseRecommendedNextStep(text) {
41
38
  const normalizedText = text.replace(/\r\n/g, '\n');
42
- const enabledMatch = normalizedText.match(AUTO_HANDOFF_FLAG_RE);
43
- if (!enabledMatch) {
44
- return null;
45
- }
46
- const enabled = enabledMatch[1]?.toUpperCase();
47
- if (enabled === 'NO') {
48
- return { enabled: false };
39
+ const colonMatch = normalizedText.match(RECOMMENDED_NEXT_STEP_COLON_RE);
40
+ if (colonMatch?.[1]) {
41
+ return colonMatch[1].trim() || null;
49
42
  }
50
- const targetMatch = normalizedText.match(AUTO_HANDOFF_TARGET_RE);
51
- const reasonMatch = normalizedText.match(AUTO_HANDOFF_REASON_RE);
52
- const promptMatch = normalizedText.match(AUTO_HANDOFF_PROMPT_RE);
53
- const target = targetMatch?.[1]?.toLowerCase();
54
- const reason = reasonMatch?.[1]?.toUpperCase();
55
- const prompt = promptMatch?.[1]?.trim();
56
- if (!target || !reason || !prompt) {
57
- return null;
43
+ const equalsMatch = normalizedText.match(RECOMMENDED_NEXT_STEP_EQUALS_RE);
44
+ if (equalsMatch?.[1]) {
45
+ return equalsMatch[1].trim() || null;
58
46
  }
59
- return {
60
- enabled: true,
61
- target,
62
- reason,
63
- prompt,
64
- };
47
+ return null;
65
48
  }
66
- export function fingerprint(sessionId, messageId, handoff) {
67
- return [sessionId, messageId, handoff.target, handoff.reason, handoff.prompt].join('::');
49
+ export function fingerprint(sessionId, messageId, recommendedNextStep) {
50
+ return [sessionId, messageId, recommendedNextStep].join('::');
68
51
  }
69
52
  export async function getSessionMetadata(client, sessionId) {
70
53
  try {
@@ -78,8 +61,34 @@ export async function getSessionMetadata(client, sessionId) {
78
61
  return {};
79
62
  }
80
63
  }
81
- export const AutoHandoffHooksPlugin = async ({ client }) => {
82
- const buffers = new Map();
64
+ async function getLatestAssistantMessage(client, sessionId) {
65
+ try {
66
+ const response = await client.session.messages({
67
+ path: { id: sessionId },
68
+ });
69
+ const messages = Array.isArray(response.data) ? response.data : [];
70
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
71
+ const current = messages[index];
72
+ if (!current || typeof current !== 'object') {
73
+ continue;
74
+ }
75
+ const info = current.info;
76
+ if (!info || info.role !== 'assistant' || !info.id) {
77
+ continue;
78
+ }
79
+ return {
80
+ messageId: info.id,
81
+ text: extractMessageText(current),
82
+ };
83
+ }
84
+ return null;
85
+ }
86
+ catch {
87
+ await log(client, 'warn', 'auto-handoff.session-messages-fetch-failed', { sessionId });
88
+ return null;
89
+ }
90
+ }
91
+ export const RecommendedNextStepHooksPlugin = async ({ client }) => {
83
92
  const sessionState = new Map();
84
93
  return {
85
94
  event: async ({ event }) => {
@@ -90,89 +99,44 @@ export const AutoHandoffHooksPlugin = async ({ client }) => {
90
99
  return;
91
100
  }
92
101
  if (event.type === 'session.deleted') {
93
- buffers.delete(sessionId);
94
102
  sessionState.delete(sessionId);
95
103
  return;
96
104
  }
97
- if (event.type === 'message.updated') {
98
- const info = event
99
- .properties?.info;
100
- if (!info || info.role !== 'assistant' || !info.id) {
101
- return;
102
- }
103
- buffers.set(sessionId, {
104
- messageId: info.id,
105
- text: extractMessageText(info),
106
- });
107
- return;
108
- }
109
- if (event.type === 'message.part.updated') {
110
- const state = buffers.get(sessionId);
111
- if (!state) {
112
- return;
113
- }
114
- const props = event.properties;
115
- const part = props?.part;
116
- if (part?.type !== 'text') {
117
- return;
118
- }
119
- if (typeof props?.delta === 'string') {
120
- state.text += props.delta;
121
- }
122
- else if (typeof part.text === 'string') {
123
- state.text = part.text;
124
- }
125
- buffers.set(sessionId, state);
126
- return;
127
- }
128
105
  if (event.type !== 'session.idle') {
129
106
  return;
130
107
  }
131
- const buffer = buffers.get(sessionId);
132
- if (!buffer?.text) {
108
+ const latestAssistantMessage = await getLatestAssistantMessage(client, sessionId);
109
+ if (!latestAssistantMessage?.text) {
133
110
  return;
134
111
  }
135
- const parsed = parseAutoHandoff(buffer.text);
136
- if (!parsed || !parsed.enabled) {
112
+ const recommendedNextStep = parseRecommendedNextStep(latestAssistantMessage.text);
113
+ if (!recommendedNextStep) {
137
114
  return;
138
115
  }
139
116
  const meta = await getSessionMetadata(client, sessionId);
140
117
  const dispatchSessionId = meta.parentID ?? sessionId;
141
- const state = sessionState.get(sessionId) ?? {
142
- count: 0,
143
- fingerprints: new Set(),
144
- };
145
- if (state.count >= MAX_AUTO_HANDOFFS_PER_SESSION) {
146
- await log(client, 'warn', 'auto-handoff.skipped-cap-reached', {
147
- sessionId,
148
- max: MAX_AUTO_HANDOFFS_PER_SESSION,
149
- });
150
- sessionState.set(sessionId, state);
151
- return;
152
- }
153
- const key = fingerprint(sessionId, buffer.messageId, parsed);
118
+ const state = sessionState.get(sessionId) ?? { fingerprints: new Set() };
119
+ const key = fingerprint(sessionId, latestAssistantMessage.messageId, recommendedNextStep);
154
120
  if (state.fingerprints.has(key)) {
155
121
  await log(client, 'debug', 'auto-handoff.skipped-duplicate', { sessionId });
156
122
  return;
157
123
  }
158
124
  state.fingerprints.add(key);
159
- state.count += 1;
160
125
  sessionState.set(sessionId, state);
161
126
  await log(client, 'info', 'auto-handoff.dispatching', {
162
127
  sessionId,
163
128
  dispatchSessionId,
164
- target: parsed.target,
165
- reason: parsed.reason,
129
+ target: 'feature-factory',
166
130
  });
167
131
  try {
168
132
  await client.session.prompt({
169
133
  path: { id: dispatchSessionId },
170
134
  body: {
171
- agent: parsed.target,
135
+ agent: 'feature-factory',
172
136
  parts: [
173
137
  {
174
138
  type: 'text',
175
- text: parsed.prompt,
139
+ text: recommendedNextStep,
176
140
  },
177
141
  ],
178
142
  },
@@ -182,8 +146,7 @@ export const AutoHandoffHooksPlugin = async ({ client }) => {
182
146
  await log(client, 'error', 'auto-handoff.dispatch-failed', {
183
147
  sessionId,
184
148
  dispatchSessionId,
185
- target: parsed.target,
186
- reason: parsed.reason,
149
+ target: 'feature-factory',
187
150
  error: String(error),
188
151
  });
189
152
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { StopQualityGateHooksPlugin } from './stop-quality-gate.js';
2
- import { AutoHandoffHooksPlugin } from './auto-handoff.js';
2
+ import { RecommendedNextStepHooksPlugin } from './auto-handoff.js';
3
3
  import { updateMCPConfig } from './mcp-config.js';
4
4
  import { updateAgentConfig } from './agent-config.js';
5
5
  import { updatePluginConfig } from './plugin-config.js';
@@ -54,7 +54,7 @@ export const FeatureFactoryPlugin = async (input) => {
54
54
  }
55
55
  // Load hooks from the quality gate plugin
56
56
  const qualityGateHooks = await StopQualityGateHooksPlugin(input).catch(() => ({}));
57
- const autoHandoffHooks = await AutoHandoffHooksPlugin(input).catch(() => ({}));
57
+ const autoHandoffHooks = await RecommendedNextStepHooksPlugin(input).catch(() => ({}));
58
58
  // Feature Factory orchestration now runs via native sub-agent handoff in
59
59
  // the feature-factory agent prompt (LLM-driven workflow). ff_* workflow MCP
60
60
  // tools are no longer registered from this plugin.
@@ -19,13 +19,6 @@ export declare const DEFAULT_MCP_SERVERS: {
19
19
  readonly type: "remote";
20
20
  readonly url: "https://mcp.grep.app";
21
21
  };
22
- readonly context7: {
23
- readonly type: "remote";
24
- readonly url: "https://mcp.context7.com/mcp";
25
- readonly headers: {
26
- readonly CONTEXT7_API_KEY: "{env:CONTEXT7_API_KEY}";
27
- };
28
- };
29
22
  };
30
23
  export interface MCPServerConfig {
31
24
  type: 'local' | 'remote';
@@ -19,13 +19,6 @@ export const DEFAULT_MCP_SERVERS = {
19
19
  type: 'remote',
20
20
  url: 'https://mcp.grep.app',
21
21
  },
22
- context7: {
23
- type: 'remote',
24
- url: 'https://mcp.context7.com/mcp',
25
- headers: {
26
- CONTEXT7_API_KEY: '{env:CONTEXT7_API_KEY}',
27
- },
28
- },
29
22
  };
30
23
  /**
31
24
  * Merge MCP servers, preserving existing servers and adding new ones.
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.11.9",
4
+ "version": "0.11.11",
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",
@@ -18,7 +18,6 @@ Use these tools directly when available in the active agent:
18
18
 
19
19
  - `jina-ai_search_web` / `jina-ai_read_url` / `jina-ai_expand_query`
20
20
  - `jina-ai_parallel_search_web` / `jina-ai_parallel_read_url`
21
- - `context7_resolve-library-id` / `context7_query-docs`
22
21
  - `gh_grep_searchGitHub`
23
22
  - `jina-ai_search_arxiv` / `jina-ai_search_ssrn`
24
23
 
@@ -95,7 +94,7 @@ Use `jina-ai_read_url` for:
95
94
 
96
95
  ### 4. Code Search (PROGRAMMING-SPECIFIC)
97
96
 
98
- Use `context7_query-docs` and `gh_grep_searchGitHub` for:
97
+ Use `gh_grep_searchGitHub` for:
99
98
 
100
99
  - Finding library/SDK/API examples
101
100
  - Understanding implementation patterns