@syntesseraai/opencode-feature-factory 0.11.10 → 0.11.12
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 +9 -3
- package/agents/feature-factory.md +22 -26
- package/agents/planning.md +1 -1
- package/agents/reviewing.md +0 -22
- package/dist/auto-handoff.d.ts +3 -15
- package/dist/auto-handoff.js +53 -90
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,12 +90,18 @@ Feature Factory agent assets follow OpenCode granular `permissions` blocks (incl
|
|
|
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
|
|
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
|
-
-
|
|
98
|
-
-
|
|
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
|
|
|
@@ -2,7 +2,7 @@
|
|
|
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:
|
|
5
|
+
model: openai/gpt-5.4
|
|
6
6
|
permissions:
|
|
7
7
|
write: deny
|
|
8
8
|
edit: deny
|
|
@@ -212,8 +212,8 @@ For each iteration `n`:
|
|
|
212
212
|
- After the required user confirmation checkpoint, successful Build always proceeds to Document.
|
|
213
213
|
- After the required user confirmation checkpoint, successful Document always proceeds to Review.
|
|
214
214
|
- Build and Document are stage-complete states, not workflow-complete states.
|
|
215
|
-
- Therefore, successful Build and successful Document must
|
|
216
|
-
-
|
|
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.
|
|
217
217
|
|
|
218
218
|
### Direct-execution prohibition
|
|
219
219
|
|
|
@@ -233,40 +233,36 @@ When a workflow ends (success or escalation), return:
|
|
|
233
233
|
5. `OPEN_ISSUES`
|
|
234
234
|
6. `RECOMMENDED_NEXT_STEP`
|
|
235
235
|
|
|
236
|
-
`RECOMMENDED_NEXT_STEP`
|
|
236
|
+
`RECOMMENDED_NEXT_STEP` should provide the exact next action when deterministic continuation is appropriate.
|
|
237
237
|
|
|
238
|
-
##
|
|
238
|
+
## Plugin continuation contract
|
|
239
239
|
|
|
240
|
-
When the next step is deterministic and does not require user input, append
|
|
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:
|
|
241
241
|
|
|
242
242
|
```text
|
|
243
|
-
RECOMMENDED_NEXT_STEP
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
<
|
|
250
|
-
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>"
|
|
251
250
|
```
|
|
252
251
|
|
|
253
252
|
Rules:
|
|
254
253
|
|
|
255
|
-
- After the required user confirmation checkpoint,
|
|
254
|
+
- After the required user confirmation checkpoint, emit `RECOMMENDED_NEXT_STEP` whenever the next workflow step is predetermined by the fixed workflow.
|
|
256
255
|
- In the fixed workflow, successful Build must always continue to Document.
|
|
257
256
|
- In the fixed workflow, successful Document must always continue to Review.
|
|
258
|
-
- Successful Build and successful Document are stage-complete but not workflow-complete;
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
- Never use `AUTO_HANDOFF=YES` before the required user confirmation checkpoint.
|
|
262
|
-
- Use `AUTO_HANDOFF=NO` only when the workflow is fully complete, blocked, escalated, or waiting on required user input.
|
|
263
|
-
- `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.
|
|
264
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`.
|
|
265
261
|
|
|
266
262
|
Expected mappings:
|
|
267
263
|
|
|
268
|
-
- Planning approved + user confirmed -> `
|
|
269
|
-
- Build succeeded -> `
|
|
270
|
-
- Document succeeded -> `
|
|
271
|
-
- Review requires changes -> `
|
|
272
|
-
- Review approved with no further work -> `
|
|
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`
|
package/agents/planning.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Creates implementation plans and planning gates for pipeline and ad-hoc work. Uses result-based handoff instead of file artifacts.
|
|
3
3
|
mode: all
|
|
4
4
|
color: '#3b82f6'
|
|
5
|
-
model: openai/gpt-5.4
|
|
5
|
+
model: openai/gpt-5.4-pro
|
|
6
6
|
permissions:
|
|
7
7
|
write: deny
|
|
8
8
|
edit: deny
|
package/agents/reviewing.md
CHANGED
|
@@ -118,28 +118,6 @@ When acting as gate reviewer, output status line exactly:
|
|
|
118
118
|
- `REVIEW_GATE=APPROVED|REWORK|ESCALATE`
|
|
119
119
|
- `DOCUMENTATION_GATE=APPROVED|REWORK|ESCALATE`
|
|
120
120
|
|
|
121
|
-
## Auto-handoff output contract (for standalone review runs)
|
|
122
|
-
|
|
123
|
-
When a standalone review clearly requires rework and the next action is deterministic, append:
|
|
124
|
-
|
|
125
|
-
```text
|
|
126
|
-
RECOMMENDED_NEXT_STEP=REWORK "<specific rework summary>"
|
|
127
|
-
AUTO_HANDOFF=YES
|
|
128
|
-
AUTO_HANDOFF_TARGET=building
|
|
129
|
-
AUTO_HANDOFF_REASON=REWORK
|
|
130
|
-
AUTO_HANDOFF_PROMPT<<FF_PROMPT
|
|
131
|
-
apply the rework recommendations
|
|
132
|
-
|
|
133
|
-
- <specific action item>
|
|
134
|
-
FF_PROMPT
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Otherwise emit:
|
|
138
|
-
|
|
139
|
-
```text
|
|
140
|
-
AUTO_HANDOFF=NO
|
|
141
|
-
```
|
|
142
|
-
|
|
143
121
|
## Operating Mode
|
|
144
122
|
|
|
145
123
|
- Use normalized, resolved handoff context (summary/status/action items/issues plus resolved prior output excerpts) rather than file-based artifacts.
|
package/dist/auto-handoff.d.ts
CHANGED
|
@@ -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
|
|
17
|
-
export declare function fingerprint(sessionId: string, messageId: string,
|
|
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
|
|
9
|
+
export declare const RecommendedNextStepHooksPlugin: Plugin;
|
|
22
10
|
export {};
|
package/dist/auto-handoff.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
const SERVICE_NAME = 'feature-factory';
|
|
2
|
-
const
|
|
3
|
-
const
|
|
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(
|
|
23
|
-
if (!
|
|
19
|
+
function extractMessageText(message) {
|
|
20
|
+
if (!message || typeof message !== 'object') {
|
|
24
21
|
return '';
|
|
25
22
|
}
|
|
26
|
-
const 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
|
|
37
|
+
export function parseRecommendedNextStep(text) {
|
|
41
38
|
const normalizedText = text.replace(/\r\n/g, '\n');
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
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,
|
|
67
|
-
return [sessionId, messageId,
|
|
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
|
-
|
|
82
|
-
|
|
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
|
|
132
|
-
if (!
|
|
108
|
+
const latestAssistantMessage = await getLatestAssistantMessage(client, sessionId);
|
|
109
|
+
if (!latestAssistantMessage?.text) {
|
|
133
110
|
return;
|
|
134
111
|
}
|
|
135
|
-
const
|
|
136
|
-
if (!
|
|
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
|
-
|
|
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:
|
|
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:
|
|
135
|
+
agent: 'feature-factory',
|
|
172
136
|
parts: [
|
|
173
137
|
{
|
|
174
138
|
type: 'text',
|
|
175
|
-
text:
|
|
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:
|
|
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 {
|
|
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
|
|
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.
|
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.
|
|
4
|
+
"version": "0.11.12",
|
|
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",
|