@syntesseraai/opencode-feature-factory 0.2.2 → 0.2.4

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.
@@ -1,450 +0,0 @@
1
- import type { PluginInput, Hooks } from '@opencode-ai/plugin';
2
-
3
- const SERVICE_NAME = 'feature-factory';
4
- const IDLE_DEBOUNCE_MS = 500;
5
- const CI_TIMEOUT_MS = 300000; // 5 minutes
6
- const SESSION_TTL_MS = 3600000; // 1 hour
7
- const CLEANUP_INTERVAL_MS = 600000; // 10 minutes
8
-
9
- export const SECRET_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [
10
- // AWS Access Key IDs
11
- { pattern: /AKIA[0-9A-Z]{16}/g, replacement: '[REDACTED_AWS_KEY]' },
12
- // GitHub Personal Access Tokens (classic)
13
- { pattern: /ghp_[A-Za-z0-9]{36}/g, replacement: '[REDACTED_GH_TOKEN]' },
14
- // GitHub Personal Access Tokens (fine-grained)
15
- { pattern: /github_pat_[A-Za-z0-9_]{22,}/g, replacement: '[REDACTED_GH_TOKEN]' },
16
- // GitHub OAuth tokens
17
- { pattern: /gho_[A-Za-z0-9]{36}/g, replacement: '[REDACTED_GH_TOKEN]' },
18
- // GitHub App tokens (user-to-server and server-to-server)
19
- { pattern: /ghu_[A-Za-z0-9]{36}/g, replacement: '[REDACTED_GH_TOKEN]' },
20
- { pattern: /ghs_[A-Za-z0-9]{36}/g, replacement: '[REDACTED_GH_TOKEN]' },
21
- // GitLab Personal Access Tokens
22
- { pattern: /glpat-[A-Za-z0-9-]{20,}/g, replacement: '[REDACTED_GITLAB_TOKEN]' },
23
- // npm tokens
24
- { pattern: /npm_[A-Za-z0-9]{36}/g, replacement: '[REDACTED_NPM_TOKEN]' },
25
- // Slack bot tokens
26
- { pattern: /xoxb-[0-9A-Za-z-]+/g, replacement: '[REDACTED_SLACK_TOKEN]' },
27
- // Slack user tokens
28
- { pattern: /xoxp-[0-9A-Za-z-]+/g, replacement: '[REDACTED_SLACK_TOKEN]' },
29
- // Slack app tokens
30
- { pattern: /xapp-[0-9A-Za-z-]+/g, replacement: '[REDACTED_SLACK_TOKEN]' },
31
- // Slack webhook URLs
32
- { pattern: /hooks\.slack\.com\/services\/[A-Z0-9/]+/g, replacement: '[REDACTED_SLACK_WEBHOOK]' },
33
- // Stripe live secret keys
34
- { pattern: /sk_live_[0-9a-zA-Z]{24,}/g, replacement: '[REDACTED_STRIPE_KEY]' },
35
- // Stripe test secret keys
36
- { pattern: /sk_test_[0-9a-zA-Z]{24,}/g, replacement: '[REDACTED_STRIPE_KEY]' },
37
- // Stripe live restricted keys
38
- { pattern: /rk_live_[0-9a-zA-Z]{24,}/g, replacement: '[REDACTED_STRIPE_KEY]' },
39
- // Stripe test restricted keys
40
- { pattern: /rk_test_[0-9a-zA-Z]{24,}/g, replacement: '[REDACTED_STRIPE_KEY]' },
41
- // Bearer tokens
42
- { pattern: /Bearer\s+[\w\-.]+/gi, replacement: 'Bearer [REDACTED]' },
43
- // API keys (api_key, api-key, apikey, apikeys, etc.)
44
- { pattern: /api[_-]?keys?[=:\s]+['"]?[\w-]+['"]?/gi, replacement: 'api_key=[REDACTED]' },
45
- // Tokens (token, tokens) - require minimum 8 char value to reduce false positives
46
- { pattern: /tokens?[=:\s]+['"]?([A-Za-z0-9_-]{8,})['"]?/gi, replacement: 'token=[REDACTED]' },
47
- // Passwords
48
- { pattern: /passwords?[=:\s]+['"]?[^\s'"]+['"]?/gi, replacement: 'password=[REDACTED]' },
49
- // Generic secrets
50
- { pattern: /secrets?[=:\s]+['"]?[^\s'"]+['"]?/gi, replacement: 'secret=[REDACTED]' },
51
- // Base64-encoded long strings that look like secrets (40-500 chars to prevent ReDoS)
52
- { pattern: /[A-Za-z0-9+/]{40,500}={0,2}/g, replacement: '[REDACTED_BASE64]' },
53
- // Private keys (RSA, DSA, EC, OpenSSH, etc.)
54
- {
55
- pattern: /-----BEGIN[\s\w]+PRIVATE KEY-----[\s\S]*?-----END[\s\w]+PRIVATE KEY-----/g,
56
- replacement: '[REDACTED_PRIVATE_KEY]',
57
- },
58
- // Database connection strings with credentials (postgres, postgresql, mysql, mongodb, redis)
59
- // Password portion handles URL-encoded characters like %40 (for @) and %23 (for #)
60
- {
61
- pattern:
62
- /(postgres|postgresql|mysql|mongodb(\+srv)?|rediss?):\/\/[^\s/:]+:(?:[^@\s]|%[0-9A-Fa-f]{2})+@[^\s]+/gi,
63
- replacement: '[REDACTED_CONNECTION_STRING]',
64
- },
65
- // GCP API keys
66
- { pattern: /AIza[0-9A-Za-z_-]{35}/g, replacement: '[REDACTED_GCP_KEY]' },
67
- // GCP OAuth tokens
68
- { pattern: /ya29\.[0-9A-Za-z_-]+/g, replacement: '[REDACTED_GCP_TOKEN]' },
69
- ];
70
-
71
- /**
72
- * Sanitizes CI output by redacting common secret patterns before sending to the LLM.
73
- * This helps prevent accidental exposure of sensitive information in prompts.
74
- */
75
- export function sanitizeOutput(output: string): string {
76
- let sanitized = output;
77
- for (const { pattern, replacement } of SECRET_PATTERNS) {
78
- sanitized = sanitized.replace(pattern, replacement);
79
- }
80
- return sanitized;
81
- }
82
-
83
- /**
84
- * Truncates CI output to the last N lines to reduce prompt size and focus on relevant errors.
85
- * Adds a header indicating truncation if the output was longer than the limit.
86
- */
87
- export function truncateOutput(output: string, maxLines: number = 20): string {
88
- const lines = output.split('\n');
89
- if (lines.length <= maxLines) {
90
- return output;
91
- }
92
- const truncatedLines = lines.slice(-maxLines);
93
- const omittedCount = lines.length - maxLines;
94
- return `... (${omittedCount} lines omitted)\n${truncatedLines.join('\n')}`;
95
- }
96
-
97
- interface SessionState {
98
- lastRunAt: number;
99
- dirty: boolean;
100
- qualityGatePassed: boolean;
101
- idleDebounce: ReturnType<typeof setTimeout> | null;
102
- running: boolean;
103
- parentID?: string;
104
- isReadOnly?: boolean;
105
- lastAccess: number;
106
- }
107
-
108
- const sessions = new Map<string, SessionState>();
109
- let cleanupIntervalId: ReturnType<typeof setInterval> | null = null;
110
-
111
- /**
112
- * Removes sessions that haven't been accessed within the TTL period.
113
- * Skips sessions that are currently running a quality gate check.
114
- */
115
- function cleanupStaleSessions(): void {
116
- const now = Date.now();
117
- for (const [sessionId, state] of sessions) {
118
- if (state.running) continue; // Don't evict active sessions
119
- if (now - state.lastAccess > SESSION_TTL_MS) {
120
- if (state.idleDebounce) {
121
- clearTimeout(state.idleDebounce);
122
- }
123
- sessions.delete(sessionId);
124
- }
125
- }
126
- }
127
-
128
- function startCleanupInterval(): void {
129
- if (cleanupIntervalId) return;
130
- cleanupIntervalId = setInterval(cleanupStaleSessions, CLEANUP_INTERVAL_MS);
131
- // Allow the process to exit even if the interval is running
132
- if (cleanupIntervalId.unref) {
133
- cleanupIntervalId.unref();
134
- }
135
- }
136
-
137
- function getSessionState(sessionId: string): SessionState {
138
- const existing = sessions.get(sessionId);
139
- if (existing) {
140
- existing.lastAccess = Date.now();
141
- return existing;
142
- }
143
-
144
- startCleanupInterval();
145
-
146
- const now = Date.now();
147
- const state: SessionState = {
148
- lastRunAt: 0,
149
- dirty: true,
150
- qualityGatePassed: false,
151
- idleDebounce: null,
152
- running: false,
153
- lastAccess: now,
154
- };
155
- sessions.set(sessionId, state);
156
- return state;
157
- }
158
-
159
- interface PermissionRule {
160
- permission: string;
161
- pattern: string;
162
- action: 'allow' | 'deny' | 'ask';
163
- }
164
-
165
- function isSessionReadOnly(permission?: PermissionRule[]): boolean {
166
- if (!permission) return false;
167
- return permission.some(
168
- (rule) => (rule.permission === 'edit' || rule.permission === 'bash') && rule.action === 'deny'
169
- );
170
- }
171
-
172
- type Client = PluginInput['client'];
173
-
174
- async function log(
175
- client: Client,
176
- level: 'debug' | 'info' | 'warn' | 'error',
177
- message: string,
178
- extra?: Record<string, unknown>
179
- ): Promise<void> {
180
- try {
181
- await client.app.log({
182
- body: {
183
- service: SERVICE_NAME,
184
- level,
185
- message,
186
- extra,
187
- },
188
- });
189
- } catch {
190
- return undefined;
191
- }
192
- }
193
-
194
- export async function createQualityGateHooks(input: PluginInput): Promise<Partial<Hooks>> {
195
- const { client, $, directory } = input;
196
-
197
- async function ciShExists(): Promise<boolean> {
198
- try {
199
- await $`test -f ${directory}/management/ci.sh`.quiet();
200
- return true;
201
- } catch {
202
- return false;
203
- }
204
- }
205
-
206
- async function _ensureSessionMetadata(sessionId: string, state: SessionState): Promise<void> {
207
- if (state.isReadOnly !== undefined) return;
208
-
209
- try {
210
- const response = await client.session.get({ path: { id: sessionId } });
211
- if (response.data) {
212
- state.parentID = response.data.parentID;
213
- const permission = (response.data as { permission?: PermissionRule[] }).permission;
214
- state.isReadOnly = isSessionReadOnly(permission);
215
- await log(client, 'debug', 'session.metadata-fetched', {
216
- sessionId,
217
- parentID: state.parentID,
218
- isReadOnly: state.isReadOnly,
219
- });
220
- }
221
- } catch {
222
- state.isReadOnly = false;
223
- await log(client, 'warn', 'session.metadata-fetch-failed', { sessionId });
224
- }
225
- }
226
-
227
- async function runQualityGate(sessionId: string): Promise<void> {
228
- const state = getSessionState(sessionId);
229
-
230
- if (!sessions.has(sessionId)) {
231
- await log(client, 'debug', 'quality-gate.skipped (session deleted)', { sessionId });
232
- return;
233
- }
234
-
235
- if (state.running) {
236
- await log(client, 'debug', 'quality-gate.skipped (already running)', { sessionId });
237
- return;
238
- }
239
-
240
- const now = Date.now();
241
- const cacheExpired = now - state.lastRunAt > 30 * 1000;
242
-
243
- if (state.qualityGatePassed && !state.dirty && !cacheExpired) {
244
- await log(client, 'debug', 'quality-gate.skipped (cached pass)', { sessionId });
245
- return;
246
- }
247
-
248
- const hasCiSh = await ciShExists();
249
-
250
- if (!hasCiSh) {
251
- await log(client, 'debug', 'quality-gate.skipped (no ci.sh)', { sessionId });
252
- return;
253
- }
254
-
255
- state.running = true;
256
-
257
- try {
258
- await log(client, 'info', 'quality-gate.started', { sessionId, directory });
259
-
260
- let ciOutput = '';
261
- let ciPassed = false;
262
- let timedOut = false;
263
-
264
- const ciPath = `${directory}/management/ci.sh`;
265
- // eslint-disable-next-line no-undef
266
- const proc = Bun.spawn(['bash', ciPath], {
267
- cwd: directory,
268
- stdout: 'pipe',
269
- stderr: 'pipe',
270
- });
271
-
272
- let timeoutId: ReturnType<typeof setTimeout> | null = null;
273
- let forceKillTimeoutId: ReturnType<typeof setTimeout> | null = null;
274
- const timeoutPromise = new Promise<void>((resolve) => {
275
- timeoutId = setTimeout(() => {
276
- timedOut = true;
277
- // Graceful termination: SIGTERM first, then SIGKILL after grace period
278
- proc.kill('SIGTERM');
279
- forceKillTimeoutId = setTimeout(() => {
280
- // Force kill if still running after grace period
281
- try {
282
- proc.kill('SIGKILL');
283
- } catch {
284
- // Process already terminated
285
- }
286
- }, 5000);
287
- resolve();
288
- }, CI_TIMEOUT_MS);
289
- });
290
-
291
- // Race the process completion against the timeout
292
- await Promise.race([proc.exited, timeoutPromise]);
293
-
294
- // Clear timeouts if process completed before timeout
295
- if (timeoutId) {
296
- clearTimeout(timeoutId);
297
- }
298
- if (forceKillTimeoutId) {
299
- clearTimeout(forceKillTimeoutId);
300
- }
301
-
302
- const exitCode = proc.exitCode;
303
- const stdout = await new Response(proc.stdout).text();
304
- const stderr = await new Response(proc.stderr).text();
305
-
306
- if (timedOut) {
307
- await log(client, 'warn', 'quality-gate.timeout', {
308
- sessionId,
309
- timeoutMs: CI_TIMEOUT_MS,
310
- });
311
- ciOutput =
312
- `CI execution timed out after ${CI_TIMEOUT_MS / 1000} seconds\n\n${stdout}\n${stderr}`.trim();
313
- ciPassed = false;
314
- } else {
315
- ciOutput = stdout + (stderr ? `\n${stderr}` : '');
316
- ciPassed = exitCode === 0;
317
- }
318
-
319
- state.lastRunAt = Date.now();
320
- state.dirty = false;
321
- state.qualityGatePassed = ciPassed;
322
-
323
- if (!ciPassed) {
324
- // Sanitize secrets first, then truncate to last 20 lines to reduce prompt size
325
- const sanitizedOutput = truncateOutput(sanitizeOutput(ciOutput), 20);
326
- const instructions = `
327
-
328
- **Important:** Do not interrupt your current task. Add "Fix quality gate failures" to your todo list and continue with what you were doing. Address the quality gate issues after completing your current task.
329
-
330
- If the failure details are missing or truncated, run "management/ci.sh" to get the full output.`;
331
- const message = timedOut
332
- ? `⏱️ Quality gate timed out\n\nThe CI execution exceeded the ${CI_TIMEOUT_MS / 1000} second timeout. The build may be hanging or taking too long.\n\n\`\`\`\n${sanitizedOutput}\n\`\`\`${instructions}`
333
- : `❌ Quality gate failed\n\nThe CI checks did not pass. Please review the output below and fix the issues:\n\n\`\`\`\n${sanitizedOutput}\n\`\`\`${instructions}`;
334
- await client.session.prompt({
335
- path: { id: sessionId },
336
- body: {
337
- parts: [
338
- {
339
- type: 'text',
340
- text: message,
341
- },
342
- ],
343
- },
344
- });
345
- await log(
346
- client,
347
- 'debug',
348
- timedOut ? 'quality-gate.timeout-reported' : 'quality-gate.failed',
349
- { sessionId }
350
- );
351
- }
352
- } finally {
353
- state.running = false;
354
- }
355
- }
356
-
357
- return {
358
- event: async ({ event }) => {
359
- const eventProps = (event as { properties?: { sessionID?: string } }).properties;
360
- const sessionId = eventProps?.sessionID;
361
-
362
- if (event.type === 'session.created') {
363
- const sessionInfo = (
364
- event as {
365
- properties?: {
366
- info?: { id: string; parentID?: string; permission?: PermissionRule[] };
367
- };
368
- }
369
- ).properties?.info;
370
- const id = sessionInfo?.id;
371
- if (id) {
372
- startCleanupInterval();
373
- const isReadOnly = isSessionReadOnly(sessionInfo?.permission);
374
- sessions.set(id, {
375
- lastRunAt: 0,
376
- dirty: true,
377
- qualityGatePassed: false,
378
- idleDebounce: null,
379
- running: false,
380
- parentID: sessionInfo?.parentID,
381
- isReadOnly,
382
- lastAccess: Date.now(),
383
- });
384
- await log(client, 'debug', 'session.created', {
385
- sessionId: id,
386
- parentID: sessionInfo?.parentID,
387
- isReadOnly,
388
- });
389
- }
390
- }
391
-
392
- if (event.type === 'session.deleted' && sessionId) {
393
- const state = sessions.get(sessionId);
394
- if (state?.idleDebounce) {
395
- clearTimeout(state.idleDebounce);
396
- }
397
- sessions.delete(sessionId);
398
- await log(client, 'debug', 'session.deleted', { sessionId });
399
- }
400
-
401
- if (event.type === 'session.idle' && sessionId) {
402
- const state = getSessionState(sessionId);
403
-
404
- if (state.parentID) {
405
- await log(client, 'debug', 'quality-gate.skipped (sub-agent session)', {
406
- sessionId,
407
- parentID: state.parentID,
408
- });
409
- return;
410
- }
411
-
412
- if (state.isReadOnly) {
413
- await log(client, 'debug', 'quality-gate.skipped (read-only session)', { sessionId });
414
- return;
415
- }
416
-
417
- if (state.idleDebounce) {
418
- clearTimeout(state.idleDebounce);
419
- }
420
-
421
- state.idleDebounce = setTimeout(async () => {
422
- state.idleDebounce = null;
423
- if (sessions.has(sessionId)) {
424
- try {
425
- await runQualityGate(sessionId);
426
- } catch (err) {
427
- await log(client, 'error', 'quality-gate.unhandled-error', {
428
- sessionId,
429
- error: String(err),
430
- });
431
- }
432
- }
433
- }, IDLE_DEBOUNCE_MS);
434
- }
435
- },
436
-
437
- 'tool.execute.before': async (input) => {
438
- const sessionId = input.sessionID;
439
- if (!sessionId) return;
440
-
441
- const state = getSessionState(sessionId);
442
-
443
- if (input.tool === 'edit' || input.tool === 'write' || input.tool === 'patch') {
444
- state.dirty = true;
445
- state.qualityGatePassed = false;
446
- await log(client, 'debug', 'session.dirty', { sessionId, tool: input.tool });
447
- }
448
- },
449
- };
450
- }
@@ -1,31 +0,0 @@
1
- import { type Plugin, tool } from '@opencode-ai/plugin';
2
-
3
- /**
4
- * @ff-acceptance Tool Plugin (Backward Compatibility)
5
- *
6
- * This tool delegates to the @ff-acceptance sub-agent.
7
- *
8
- * NOTE: New code should use the sub-agent directly via @ff-acceptance
9
- * or invoke it through the @ff-validate orchestrator.
10
- */
11
-
12
- export const FFAcceptancePlugin: Plugin = async (_ctx) => {
13
- return {
14
- tool: {
15
- ff_acceptance: tool({
16
- description:
17
- 'Validates implementation against acceptance criteria (@ff-acceptance), checks if code meets all requirements. DEPRECATED: Use @ff-acceptance sub-agent directly.',
18
- args: {
19
- task: tool.schema
20
- .string()
21
- .describe('The validation task or acceptance criteria to check'),
22
- },
23
- async execute({ task }) {
24
- // Invoke the sub-agent via the Task tool
25
- // This maintains backward compatibility while using the new sub-agent
26
- return `Invoking @ff-acceptance sub-agent to validate: ${task}\n\nPlease wait for the acceptance validation to complete. The @ff-acceptance agent will:\n1. Create a validation plan\n2. Check all acceptance criteria\n3. Provide a detailed report with findings\n\nFor direct access to the sub-agent, use: @ff-acceptance ${task}`;
27
- },
28
- }),
29
- },
30
- };
31
- };
@@ -1,28 +0,0 @@
1
- import { type Plugin, tool } from '@opencode-ai/plugin';
2
-
3
- /**
4
- * @ff-mini-plan Tool Plugin (Backward Compatibility)
5
- *
6
- * This tool loads the ff-mini-plan skill and provides guidance.
7
- *
8
- * NOTE: New code should use the skill directly via the skill tool
9
- * or invoke it through sub-agents that load it automatically.
10
- */
11
-
12
- export const FFMiniPlanPlugin: Plugin = async (ctx) => {
13
- return {
14
- tool: {
15
- ff_mini_plan: tool({
16
- description:
17
- 'Creates mini implementation plans for smaller tasks (2-5 steps) (@ff-mini-plan), quick planning, small fixes. DEPRECATED: Load the ff-mini-plan skill directly.',
18
- args: {
19
- task: tool.schema.string().describe('The simple task or issue description to plan for'),
20
- },
21
- async execute({ task }) {
22
- // Provide guidance on using the skill instead
23
- return `# Mini Planning\n\nFor task: ${task}\n\n## Using the ff-mini-plan Skill\n\nThe ff-mini-plan skill is now available globally. To use it:\n\n1. **In sub-agents**: Skills are loaded automatically at the start of tasks\n2. **Manually**: Use the skill tool to load ff-mini-plan\n\n## Quick Planning Guide\n\nThe mini-plan skill helps create 2-5 step plans for simple tasks:\n\n### Planning Process:\n1. Assess task complexity (suitable for 2-5 steps?)\n2. Create concrete steps with specific files\n3. Estimate effort and complexity\n4. Identify quick wins\n\n### Escalation Criteria:\n- Requires more than 5 implementation steps\n- Affects multiple components or services\n- Requires architecture decisions\n- Has security implications\n\n### Example Plan Structure:\n\`\`\`\n# Mini Plan: [Task]\n\n**Status:** Ready\n**Time Estimate:** 5-10 minutes\n**Complexity:** Simple\n\n## 📋 Steps\n\n1. **[Step 1]**\n - What to do: [Action]\n - Files: \`file.ts\`\n - Tests: [Verification]\n\n2. **[Step 2]**\n - What to do: [Action]\n - Files: \`file.ts\`\n - Tests: [Verification]\n\n## ⚡ Quick Wins\n\n- [Improvement] - [Benefit]\n\`\`\`\n\nFor comprehensive planning, use @ff-validate to run all validation agents with mini-plans.`;
24
- },
25
- }),
26
- },
27
- };
28
- };
@@ -1,28 +0,0 @@
1
- import { type Plugin, tool } from '@opencode-ai/plugin';
2
-
3
- /**
4
- * @ff-review Tool Plugin (Backward Compatibility)
5
- *
6
- * This tool delegates to the @ff-review sub-agent.
7
- *
8
- * NOTE: New code should use the sub-agent directly via @ff-review
9
- * or invoke it through the @ff-validate orchestrator.
10
- */
11
-
12
- export const FFReviewPlugin: Plugin = async (_ctx) => {
13
- return {
14
- tool: {
15
- ff_review: tool({
16
- description:
17
- 'Reviews code changes for correctness, quality, and test coverage (@ff-review), code review, quality check. DEPRECATED: Use @ff-review sub-agent directly.',
18
- args: {
19
- task: tool.schema.string().describe('The code review task or diff to analyze'),
20
- },
21
- async execute({ task }) {
22
- // Invoke the sub-agent via the Task tool
23
- return `Invoking @ff-review sub-agent to review: ${task}\n\nPlease wait for the code review to complete. The @ff-review agent will:\n1. Create a review plan\n2. Check correctness, quality, and tests\n3. Provide a detailed report with findings\n\nFor direct access to the sub-agent, use: @ff-review ${task}`;
24
- },
25
- }),
26
- },
27
- };
28
- };
@@ -1,28 +0,0 @@
1
- import { type Plugin, tool } from '@opencode-ai/plugin';
2
-
3
- /**
4
- * @ff-security Tool Plugin (Backward Compatibility)
5
- *
6
- * This tool delegates to the @ff-security sub-agent.
7
- *
8
- * NOTE: New code should use the sub-agent directly via @ff-security
9
- * or invoke it through the @ff-validate orchestrator.
10
- */
11
-
12
- export const FFSecurityPlugin: Plugin = async (_ctx) => {
13
- return {
14
- tool: {
15
- ff_security: tool({
16
- description:
17
- 'Performs deep security audits on code changes (@ff-security), security check, vulnerability audit. DEPRECATED: Use @ff-security sub-agent directly.',
18
- args: {
19
- task: tool.schema.string().describe('The security audit task or code to analyze'),
20
- },
21
- async execute({ task }) {
22
- // Invoke the sub-agent via the Task tool
23
- return `Invoking @ff-security sub-agent to audit: ${task}\n\nPlease wait for the security audit to complete. The @ff-security agent will:\n1. Create an audit plan\n2. Check for OWASP Top 10 and other vulnerabilities\n3. Provide a detailed security report\n\nFor direct access to the sub-agent, use: @ff-security ${task}`;
24
- },
25
- }),
26
- },
27
- };
28
- };
@@ -1,28 +0,0 @@
1
- import { type Plugin, tool } from '@opencode-ai/plugin';
2
-
3
- /**
4
- * @ff-validate Tool Plugin (Backward Compatibility)
5
- *
6
- * This tool delegates to the @ff-validate sub-agent.
7
- *
8
- * NOTE: New code should use the sub-agent directly via @ff-validate
9
- * for comprehensive validation.
10
- */
11
-
12
- export const FFValidatePlugin: Plugin = async (_ctx) => {
13
- return {
14
- tool: {
15
- ff_validate: tool({
16
- description:
17
- 'Orchestrates comprehensive validation by running multiple review agents in parallel (@ff-validate), validate changes, comprehensive review. DEPRECATED: Use @ff-validate sub-agent directly.',
18
- args: {
19
- task: tool.schema.string().describe('The validation task description'),
20
- },
21
- async execute({ task }) {
22
- // Invoke the sub-agent via the Task tool
23
- return `Invoking @ff-validate sub-agent to orchestrate comprehensive validation: ${task}\n\nPlease wait for the validation to complete. The @ff-validate agent will:\n1. Create an orchestration plan\n2. Run all validation agents in parallel:\n - @ff-acceptance (requirements validation)\n - @ff-review (code quality)\n - @ff-security (security audit)\n - @ff-well-architected (architecture review)\n3. Aggregate results and provide a comprehensive report\n\nFor direct access to the orchestrator, use: @ff-validate ${task}`;
24
- },
25
- }),
26
- },
27
- };
28
- };
@@ -1,30 +0,0 @@
1
- import { type Plugin, tool } from '@opencode-ai/plugin';
2
-
3
- /**
4
- * @ff-well-architected Tool Plugin (Backward Compatibility)
5
- *
6
- * This tool delegates to the @ff-well-architected sub-agent.
7
- *
8
- * NOTE: New code should use the sub-agent directly via @ff-well-architected
9
- * or invoke it through the @ff-validate orchestrator.
10
- */
11
-
12
- export const FFWellArchitectedPlugin: Plugin = async (_ctx) => {
13
- return {
14
- tool: {
15
- ff_well_architected: tool({
16
- description:
17
- 'Reviews code against AWS Well-Architected Framework pillars (@ff-well-architected), architecture review, AWS best practices. DEPRECATED: Use @ff-well-architected sub-agent directly.',
18
- args: {
19
- task: tool.schema
20
- .string()
21
- .describe('The architecture review task or system design to analyze'),
22
- },
23
- async execute({ task }) {
24
- // Invoke the sub-agent via the Task tool
25
- return `Invoking @ff-well-architected sub-agent to review: ${task}\n\nPlease wait for the architecture review to complete. The @ff-well-architected agent will:\n1. Create a review plan\n2. Assess against AWS Well-Architected 6 pillars:\n - Operational Excellence\n - Security\n - Reliability\n - Performance Efficiency\n - Cost Optimization\n - Sustainability\n3. Provide a detailed report with pillar scores\n\nFor direct access to the sub-agent, use: @ff-well-architected ${task}`;
26
- },
27
- }),
28
- },
29
- };
30
- };