@syntesseraai/opencode-feature-factory 0.6.8 → 0.6.9
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 +6 -4
- package/agents/building.md +28 -541
- package/agents/documenting.md +39 -0
- package/agents/ff-research.md +18 -410
- package/agents/pipeline.md +20 -71
- package/agents/planning.md +28 -350
- package/agents/reviewing.md +27 -475
- package/commands/pipeline/building/breakdown.md +4 -3
- package/commands/pipeline/building/implement-batch.md +4 -3
- package/commands/pipeline/building/run.md +8 -8
- package/commands/pipeline/building/validate-batch.md +4 -3
- package/commands/pipeline/complete.md +1 -1
- package/commands/pipeline/documentation/{run-codex.md → document.md} +3 -4
- package/commands/pipeline/documentation/gate.md +3 -3
- package/commands/pipeline/documentation/{run-gemini.md → review.md} +4 -3
- package/commands/pipeline/documentation/run.md +6 -7
- package/commands/pipeline/planning/gate.md +8 -6
- package/commands/pipeline/planning/plan.md +25 -0
- package/commands/pipeline/planning/run.md +7 -7
- package/commands/pipeline/planning/synthesize.md +7 -3
- package/commands/pipeline/reviewing/gate.md +3 -3
- package/commands/pipeline/reviewing/review.md +20 -0
- package/commands/pipeline/reviewing/run.md +6 -6
- package/commands/pipeline/reviewing/synthesize.md +3 -3
- package/commands/pipeline/reviewing/triage.md +2 -2
- package/commands/pipeline/start.md +5 -5
- package/dist/index.d.ts +1 -2
- package/dist/index.js +3 -52
- package/package.json +1 -1
- package/skills/ff-reviewing-architecture/SKILL.md +34 -0
- package/skills/ff-reviewing-code-quality/SKILL.md +34 -0
- package/skills/ff-reviewing-documentation/SKILL.md +34 -0
- package/skills/ff-reviewing-security/SKILL.md +34 -0
- package/agents/ff-acceptance.md +0 -285
- package/agents/ff-building-codex.md +0 -305
- package/agents/ff-building-gemini.md +0 -305
- package/agents/ff-building-opus.md +0 -305
- package/agents/ff-planning-codex.md +0 -335
- package/agents/ff-planning-gemini.md +0 -335
- package/agents/ff-planning-opus.md +0 -335
- package/agents/ff-review.md +0 -288
- package/agents/ff-reviewing-codex.md +0 -259
- package/agents/ff-reviewing-gemini.md +0 -259
- package/agents/ff-reviewing-opus.md +0 -259
- package/agents/ff-security.md +0 -322
- package/agents/ff-validate.md +0 -316
- package/agents/ff-well-architected.md +0 -284
- package/commands/pipeline/planning/run-codex.md +0 -22
- package/commands/pipeline/planning/run-gemini.md +0 -21
- package/commands/pipeline/planning/run-opus.md +0 -21
- package/commands/pipeline/reviewing/run-codex.md +0 -12
- package/commands/pipeline/reviewing/run-gemini.md +0 -11
- package/commands/pipeline/reviewing/run-opus.md +0 -11
- package/dist/agent-context.d.ts +0 -57
- package/dist/agent-context.js +0 -282
- package/dist/plugins/ff-agent-context-create-plugin.d.ts +0 -2
- package/dist/plugins/ff-agent-context-create-plugin.js +0 -82
- package/dist/plugins/ff-agent-context-update-plugin.d.ts +0 -2
- package/dist/plugins/ff-agent-context-update-plugin.js +0 -78
- package/dist/plugins/ff-agents-clear-plugin.d.ts +0 -2
- package/dist/plugins/ff-agents-clear-plugin.js +0 -40
- package/dist/plugins/ff-agents-current-plugin.d.ts +0 -2
- package/dist/plugins/ff-agents-current-plugin.js +0 -45
- package/dist/plugins/ff-agents-delete-plugin.d.ts +0 -2
- package/dist/plugins/ff-agents-delete-plugin.js +0 -32
- package/dist/plugins/ff-agents-get-plugin.d.ts +0 -2
- package/dist/plugins/ff-agents-get-plugin.js +0 -32
- package/dist/plugins/ff-agents-list-plugin.d.ts +0 -2
- package/dist/plugins/ff-agents-list-plugin.js +0 -42
- package/dist/plugins/ff-agents-show-plugin.d.ts +0 -2
- package/dist/plugins/ff-agents-show-plugin.js +0 -22
- package/dist/plugins/ff-agents-update-plugin.d.ts +0 -2
- package/dist/plugins/ff-agents-update-plugin.js +0 -32
- package/dist/plugins/ff-plan-create-plugin.d.ts +0 -2
- package/dist/plugins/ff-plan-create-plugin.js +0 -61
- package/dist/plugins/ff-plan-update-plugin.d.ts +0 -2
- package/dist/plugins/ff-plan-update-plugin.js +0 -142
- package/dist/plugins/ff-plans-delete-plugin.d.ts +0 -2
- package/dist/plugins/ff-plans-delete-plugin.js +0 -32
- package/dist/plugins/ff-plans-get-plugin.d.ts +0 -2
- package/dist/plugins/ff-plans-get-plugin.js +0 -32
- package/dist/plugins/ff-plans-list-plugin.d.ts +0 -2
- package/dist/plugins/ff-plans-list-plugin.js +0 -42
- package/dist/plugins/ff-plans-update-plugin.d.ts +0 -2
- package/dist/plugins/ff-plans-update-plugin.js +0 -32
- package/dist/plugins/ff-review-create-plugin.d.ts +0 -2
- package/dist/plugins/ff-review-create-plugin.js +0 -256
- package/dist/plugins/ff-reviews-get-plugin.d.ts +0 -2
- package/dist/plugins/ff-reviews-get-plugin.js +0 -32
- package/dist/plugins/ff-reviews-list-plugin.d.ts +0 -2
- package/dist/plugins/ff-reviews-list-plugin.js +0 -42
- package/dist/plugins/ff-reviews-update-plugin.d.ts +0 -2
- package/dist/plugins/ff-reviews-update-plugin.js +0 -32
- package/skills/ff-context-tracking/SKILL.md +0 -573
- package/skills/ff-delegation/SKILL.md +0 -457
- package/skills/ff-swarm/SKILL.md +0 -209
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { writeFile, mkdir } from 'fs/promises';
|
|
3
|
-
import { dirname } from 'path';
|
|
4
|
-
export function createFFReviewCreateTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Create a new review report file in .feature-factory/reviews/. Use this to document review findings, validation results, or audit reports.',
|
|
7
|
-
args: {
|
|
8
|
-
title: tool.schema.string().describe('Title of the review'),
|
|
9
|
-
description: tool.schema.string().describe('Brief description of what was reviewed'),
|
|
10
|
-
reviewType: tool.schema
|
|
11
|
-
.enum([
|
|
12
|
-
'code-review',
|
|
13
|
-
'security-audit',
|
|
14
|
-
'acceptance-validation',
|
|
15
|
-
'architecture-review',
|
|
16
|
-
'comprehensive-validation',
|
|
17
|
-
])
|
|
18
|
-
.describe('Type of review performed'),
|
|
19
|
-
target: tool.schema.string().describe('What was reviewed (e.g., PR #123, feature X, file Y)'),
|
|
20
|
-
verdict: tool.schema
|
|
21
|
-
.enum(['approved', 'changes-requested', 'rejected'])
|
|
22
|
-
.describe('Overall review verdict'),
|
|
23
|
-
confidence: tool.schema.number().min(0).max(100).describe('Confidence score (0-100)'),
|
|
24
|
-
findings: tool.schema
|
|
25
|
-
.array(tool.schema.object({
|
|
26
|
-
severity: tool.schema
|
|
27
|
-
.enum(['critical', 'high', 'medium', 'low'])
|
|
28
|
-
.describe('Severity level'),
|
|
29
|
-
category: tool.schema
|
|
30
|
-
.string()
|
|
31
|
-
.describe('Category (e.g., security, quality, performance)'),
|
|
32
|
-
file: tool.schema.string().optional().describe('Affected file path'),
|
|
33
|
-
line: tool.schema.number().optional().describe('Line number'),
|
|
34
|
-
description: tool.schema.string().describe('Description of the finding'),
|
|
35
|
-
recommendation: tool.schema.string().describe('Recommended fix or improvement'),
|
|
36
|
-
}))
|
|
37
|
-
.optional()
|
|
38
|
-
.describe('List of findings/issues discovered'),
|
|
39
|
-
positives: tool.schema
|
|
40
|
-
.array(tool.schema.string())
|
|
41
|
-
.optional()
|
|
42
|
-
.describe('List of positive observations'),
|
|
43
|
-
metrics: tool.schema
|
|
44
|
-
.object({
|
|
45
|
-
testsPassed: tool.schema.string().optional(),
|
|
46
|
-
coverage: tool.schema.string().optional(),
|
|
47
|
-
securityScore: tool.schema.number().optional(),
|
|
48
|
-
qualityScore: tool.schema.number().optional(),
|
|
49
|
-
})
|
|
50
|
-
.optional()
|
|
51
|
-
.describe('Review metrics and scores'),
|
|
52
|
-
actionItems: tool.schema
|
|
53
|
-
.array(tool.schema.object({
|
|
54
|
-
priority: tool.schema
|
|
55
|
-
.enum(['critical', 'high', 'medium', 'low'])
|
|
56
|
-
.describe('Priority level'),
|
|
57
|
-
description: tool.schema.string().describe('Action item description'),
|
|
58
|
-
assignee: tool.schema.string().optional().describe('Who should address this'),
|
|
59
|
-
}))
|
|
60
|
-
.optional()
|
|
61
|
-
.describe('Action items to address findings'),
|
|
62
|
-
relatedPlan: tool.schema.string().optional().describe('ID of related implementation plan'),
|
|
63
|
-
relatedAgent: tool.schema
|
|
64
|
-
.string()
|
|
65
|
-
.optional()
|
|
66
|
-
.describe('UUID of agent that performed the review'),
|
|
67
|
-
notes: tool.schema.string().optional().describe('Additional notes or context'),
|
|
68
|
-
},
|
|
69
|
-
async execute(args, toolCtx) {
|
|
70
|
-
try {
|
|
71
|
-
const timestamp = new Date().toISOString();
|
|
72
|
-
const reviewId = `${timestamp.replace(/[:.]/g, '-').slice(0, 19)}-${args.reviewType}`;
|
|
73
|
-
const filePath = `${toolCtx.directory}/.feature-factory/reviews/${reviewId}.md`;
|
|
74
|
-
// Generate frontmatter
|
|
75
|
-
const frontmatter = `---
|
|
76
|
-
id: "${reviewId}"
|
|
77
|
-
title: "${args.title}"
|
|
78
|
-
description: "${args.description}"
|
|
79
|
-
created: "${timestamp}"
|
|
80
|
-
review_type: "${args.reviewType}"
|
|
81
|
-
target: "${args.target}"
|
|
82
|
-
verdict: "${args.verdict}"
|
|
83
|
-
confidence: ${args.confidence}
|
|
84
|
-
${args.relatedPlan ? `related_plan: "${args.relatedPlan}"` : ''}
|
|
85
|
-
${args.relatedAgent ? `related_agent: "${args.relatedAgent}"` : ''}
|
|
86
|
-
---`;
|
|
87
|
-
// Generate review content
|
|
88
|
-
let content = `# ${args.title}
|
|
89
|
-
|
|
90
|
-
**Review Type:** ${args.reviewType}
|
|
91
|
-
**Target:** ${args.target}
|
|
92
|
-
**Verdict:** ${args.verdict === 'approved' ? '✅ Approved' : args.verdict === 'changes-requested' ? '⚠️ Changes Requested' : '❌ Rejected'}
|
|
93
|
-
**Confidence:** ${args.confidence}%
|
|
94
|
-
**Date:** ${timestamp}
|
|
95
|
-
|
|
96
|
-
## Summary
|
|
97
|
-
|
|
98
|
-
${args.description}
|
|
99
|
-
|
|
100
|
-
`;
|
|
101
|
-
// Add metrics if provided
|
|
102
|
-
if (args.metrics && Object.keys(args.metrics).length > 0) {
|
|
103
|
-
content += `## Metrics
|
|
104
|
-
|
|
105
|
-
`;
|
|
106
|
-
if (args.metrics.testsPassed) {
|
|
107
|
-
content += `- **Tests Passed:** ${args.metrics.testsPassed}\n`;
|
|
108
|
-
}
|
|
109
|
-
if (args.metrics.coverage) {
|
|
110
|
-
content += `- **Coverage:** ${args.metrics.coverage}\n`;
|
|
111
|
-
}
|
|
112
|
-
if (args.metrics.securityScore !== undefined) {
|
|
113
|
-
content += `- **Security Score:** ${args.metrics.securityScore}/100\n`;
|
|
114
|
-
}
|
|
115
|
-
if (args.metrics.qualityScore !== undefined) {
|
|
116
|
-
content += `- **Quality Score:** ${args.metrics.qualityScore}/100\n`;
|
|
117
|
-
}
|
|
118
|
-
content += `\n`;
|
|
119
|
-
}
|
|
120
|
-
// Add findings
|
|
121
|
-
if (args.findings && args.findings.length > 0) {
|
|
122
|
-
const critical = args.findings.filter((f) => f.severity === 'critical');
|
|
123
|
-
const high = args.findings.filter((f) => f.severity === 'high');
|
|
124
|
-
const medium = args.findings.filter((f) => f.severity === 'medium');
|
|
125
|
-
const low = args.findings.filter((f) => f.severity === 'low');
|
|
126
|
-
if (critical.length > 0) {
|
|
127
|
-
content += `## 🚨 Critical Issues (${critical.length})
|
|
128
|
-
|
|
129
|
-
`;
|
|
130
|
-
critical.forEach((finding, idx) => {
|
|
131
|
-
content += `### ${idx + 1}. ${finding.category}: ${finding.description.substring(0, 50)}${finding.description.length > 50 ? '...' : ''}\n\n`;
|
|
132
|
-
content += `- **Severity:** Critical\n`;
|
|
133
|
-
if (finding.file) {
|
|
134
|
-
content += `- **Location:** \`${finding.file}${finding.line ? `:${finding.line}` : ''}\`\n`;
|
|
135
|
-
}
|
|
136
|
-
content += `- **Description:** ${finding.description}\n`;
|
|
137
|
-
content += `- **Recommendation:** ${finding.recommendation}\n\n`;
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
if (high.length > 0) {
|
|
141
|
-
content += `## ⚠️ High Priority Issues (${high.length})
|
|
142
|
-
|
|
143
|
-
`;
|
|
144
|
-
high.forEach((finding, idx) => {
|
|
145
|
-
content += `### ${idx + 1}. ${finding.category}: ${finding.description.substring(0, 50)}${finding.description.length > 50 ? '...' : ''}\n\n`;
|
|
146
|
-
content += `- **Severity:** High\n`;
|
|
147
|
-
if (finding.file) {
|
|
148
|
-
content += `- **Location:** \`${finding.file}${finding.line ? `:${finding.line}` : ''}\`\n`;
|
|
149
|
-
}
|
|
150
|
-
content += `- **Description:** ${finding.description}\n`;
|
|
151
|
-
content += `- **Recommendation:** ${finding.recommendation}\n\n`;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
if (medium.length > 0) {
|
|
155
|
-
content += `## 🟡 Medium Priority Issues (${medium.length})
|
|
156
|
-
|
|
157
|
-
`;
|
|
158
|
-
medium.forEach((finding, idx) => {
|
|
159
|
-
content += `${idx + 1}. **${finding.category}:** ${finding.description}\n`;
|
|
160
|
-
if (finding.recommendation) {
|
|
161
|
-
content += ` - *Recommendation:* ${finding.recommendation}\n`;
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
content += `\n`;
|
|
165
|
-
}
|
|
166
|
-
if (low.length > 0) {
|
|
167
|
-
content += `## 🟢 Low Priority / Suggestions (${low.length})
|
|
168
|
-
|
|
169
|
-
`;
|
|
170
|
-
low.forEach((finding, idx) => {
|
|
171
|
-
content += `${idx + 1}. **${finding.category}:** ${finding.description}\n`;
|
|
172
|
-
if (finding.recommendation) {
|
|
173
|
-
content += ` - *Suggestion:* ${finding.recommendation}\n`;
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
content += `\n`;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
// Add positives
|
|
180
|
-
if (args.positives && args.positives.length > 0) {
|
|
181
|
-
content += `## ✅ Positives
|
|
182
|
-
|
|
183
|
-
`;
|
|
184
|
-
args.positives.forEach((positive) => {
|
|
185
|
-
content += `- ${positive}\n`;
|
|
186
|
-
});
|
|
187
|
-
content += `\n`;
|
|
188
|
-
}
|
|
189
|
-
// Add action items
|
|
190
|
-
if (args.actionItems && args.actionItems.length > 0) {
|
|
191
|
-
content += `## 📋 Action Items
|
|
192
|
-
|
|
193
|
-
`;
|
|
194
|
-
const critical = args.actionItems.filter((a) => a.priority === 'critical');
|
|
195
|
-
const high = args.actionItems.filter((a) => a.priority === 'high');
|
|
196
|
-
const medium = args.actionItems.filter((a) => a.priority === 'medium');
|
|
197
|
-
const low = args.actionItems.filter((a) => a.priority === 'low');
|
|
198
|
-
if (critical.length > 0) {
|
|
199
|
-
content += `### 🔴 Critical (Must Fix)\n\n`;
|
|
200
|
-
critical.forEach((item, idx) => {
|
|
201
|
-
content += `${idx + 1}. ${item.description}${item.assignee ? ` (@${item.assignee})` : ''}\n`;
|
|
202
|
-
});
|
|
203
|
-
content += `\n`;
|
|
204
|
-
}
|
|
205
|
-
if (high.length > 0) {
|
|
206
|
-
content += `### 🟠 High Priority (Should Fix)\n\n`;
|
|
207
|
-
high.forEach((item, idx) => {
|
|
208
|
-
content += `${idx + 1}. ${item.description}${item.assignee ? ` (@${item.assignee})` : ''}\n`;
|
|
209
|
-
});
|
|
210
|
-
content += `\n`;
|
|
211
|
-
}
|
|
212
|
-
if (medium.length > 0) {
|
|
213
|
-
content += `### 🟡 Medium Priority (Fix if Time)\n\n`;
|
|
214
|
-
medium.forEach((item, idx) => {
|
|
215
|
-
content += `${idx + 1}. ${item.description}${item.assignee ? ` (@${item.assignee})` : ''}\n`;
|
|
216
|
-
});
|
|
217
|
-
content += `\n`;
|
|
218
|
-
}
|
|
219
|
-
if (low.length > 0) {
|
|
220
|
-
content += `### 🟢 Low Priority (Optional)\n\n`;
|
|
221
|
-
low.forEach((item, idx) => {
|
|
222
|
-
content += `${idx + 1}. ${item.description}${item.assignee ? ` (@${item.assignee})` : ''}\n`;
|
|
223
|
-
});
|
|
224
|
-
content += `\n`;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
// Add notes
|
|
228
|
-
if (args.notes) {
|
|
229
|
-
content += `## 📝 Notes
|
|
230
|
-
|
|
231
|
-
${args.notes}\n\n`;
|
|
232
|
-
}
|
|
233
|
-
const fullContent = `${frontmatter}\n\n${content}`;
|
|
234
|
-
// Ensure directory exists
|
|
235
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
236
|
-
// Write file
|
|
237
|
-
await writeFile(filePath, fullContent, 'utf-8');
|
|
238
|
-
return JSON.stringify({
|
|
239
|
-
success: true,
|
|
240
|
-
reviewId,
|
|
241
|
-
filePath: `.feature-factory/reviews/${reviewId}.md`,
|
|
242
|
-
message: `Review created successfully at .feature-factory/reviews/${reviewId}.md`,
|
|
243
|
-
verdict: args.verdict,
|
|
244
|
-
confidence: args.confidence,
|
|
245
|
-
findingsCount: args.findings?.length || 0,
|
|
246
|
-
}, null, 2);
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
return JSON.stringify({
|
|
250
|
-
success: false,
|
|
251
|
-
error: `Failed to create review: ${error}`,
|
|
252
|
-
}, null, 2);
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { validateSafePath, resolveSafePath, getFeatureFactoryDir } from '../utils/file-utils.js';
|
|
3
|
-
import { readFile } from 'fs/promises';
|
|
4
|
-
export function createFFReviewsGetTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Read a review file by name from .feature-factory/reviews',
|
|
7
|
-
args: {
|
|
8
|
-
fileName: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.describe('Name of the review file (e.g., "security-audit-abc123.md")'),
|
|
11
|
-
},
|
|
12
|
-
async execute(args, toolCtx) {
|
|
13
|
-
try {
|
|
14
|
-
const reviewsDir = getFeatureFactoryDir(toolCtx.directory, 'reviews');
|
|
15
|
-
// Validate the file path
|
|
16
|
-
if (!validateSafePath(reviewsDir, args.fileName)) {
|
|
17
|
-
return `Error: Invalid or unsafe file name "${args.fileName}". Only .md files with alphanumeric names are allowed.`;
|
|
18
|
-
}
|
|
19
|
-
const filePath = resolveSafePath(reviewsDir, args.fileName);
|
|
20
|
-
// Read file content
|
|
21
|
-
const content = await readFile(filePath, 'utf-8');
|
|
22
|
-
return content;
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
26
|
-
return `Error: File "${args.fileName}" not found in .feature-factory/reviews`;
|
|
27
|
-
}
|
|
28
|
-
return `Error reading review file: ${error}`;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { validateSafePattern, ensureDirectoryExists, getFeatureFactoryDir, } from '../utils/file-utils.js';
|
|
3
|
-
import { readdir } from 'fs/promises';
|
|
4
|
-
export function createFFReviewsListTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'List all review files in .feature-factory/reviews',
|
|
7
|
-
args: {
|
|
8
|
-
pattern: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.optional()
|
|
11
|
-
.describe('Optional filter pattern (e.g., "security-*.md")'),
|
|
12
|
-
},
|
|
13
|
-
async execute(args, toolCtx) {
|
|
14
|
-
try {
|
|
15
|
-
const reviewsDir = getFeatureFactoryDir(toolCtx.directory, 'reviews');
|
|
16
|
-
// Ensure the reviews directory exists
|
|
17
|
-
await ensureDirectoryExists(reviewsDir);
|
|
18
|
-
// List files
|
|
19
|
-
const entries = await readdir(reviewsDir, { withFileTypes: true });
|
|
20
|
-
let files = entries
|
|
21
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
22
|
-
.map((entry) => entry.name);
|
|
23
|
-
// Apply pattern filter if provided
|
|
24
|
-
if (args.pattern) {
|
|
25
|
-
if (!validateSafePattern(args.pattern)) {
|
|
26
|
-
return `Error: Invalid pattern "${args.pattern}". Only safe pattern characters are allowed.`;
|
|
27
|
-
}
|
|
28
|
-
// Simple glob-like matching
|
|
29
|
-
const regex = new RegExp('^' + args.pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
|
|
30
|
-
files = files.filter((f) => regex.test(f));
|
|
31
|
-
}
|
|
32
|
-
if (files.length === 0) {
|
|
33
|
-
return 'No review files found in .feature-factory/reviews';
|
|
34
|
-
}
|
|
35
|
-
return JSON.stringify(files, null, 2);
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
return `Error listing review files: ${error}`;
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { validateSafePath, resolveSafePath, ensureDirectoryExists, getFeatureFactoryDir, } from '../utils/file-utils.js';
|
|
3
|
-
import { writeFile } from 'fs/promises';
|
|
4
|
-
export function createFFReviewsUpdateTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Create or update a review file in .feature-factory/reviews',
|
|
7
|
-
args: {
|
|
8
|
-
fileName: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.describe('Name of the review file (e.g., "security-audit-abc123.md")'),
|
|
11
|
-
content: tool.schema.string().describe('Content to write to the file'),
|
|
12
|
-
},
|
|
13
|
-
async execute(args, toolCtx) {
|
|
14
|
-
try {
|
|
15
|
-
const reviewsDir = getFeatureFactoryDir(toolCtx.directory, 'reviews');
|
|
16
|
-
// Validate the file path
|
|
17
|
-
if (!validateSafePath(reviewsDir, args.fileName)) {
|
|
18
|
-
return `Error: Invalid or unsafe file name "${args.fileName}". Only .md files with alphanumeric names are allowed.`;
|
|
19
|
-
}
|
|
20
|
-
// Ensure the reviews directory exists
|
|
21
|
-
await ensureDirectoryExists(reviewsDir);
|
|
22
|
-
const filePath = resolveSafePath(reviewsDir, args.fileName);
|
|
23
|
-
// Write file content
|
|
24
|
-
await writeFile(filePath, args.content, 'utf-8');
|
|
25
|
-
return `Successfully updated review file: ${args.fileName}`;
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
return `Error updating review file: ${error}`;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
}
|