@syntesseraai/opencode-feature-factory 0.6.8 → 0.6.10
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 +24 -17
- package/agents/building.md +28 -541
- package/agents/documenting.md +39 -0
- package/agents/ff-research.md +18 -410
- package/agents/pipeline.md +27 -69
- package/agents/planning.md +28 -350
- package/agents/reviewing.md +27 -475
- package/bin/ff-deploy.js +7 -7
- package/{commands → command}/pipeline/building/breakdown.md +3 -3
- package/{commands → command}/pipeline/building/implement-batch.md +3 -3
- package/command/pipeline/building/run.md +19 -0
- package/{commands → command}/pipeline/building/validate-batch.md +3 -3
- package/{commands → command}/pipeline/complete.md +1 -2
- package/{commands/pipeline/documentation/run-codex.md → command/pipeline/documentation/document.md} +4 -6
- package/{commands → command}/pipeline/documentation/gate.md +3 -4
- package/{commands/pipeline/documentation/run-gemini.md → command/pipeline/documentation/review.md} +3 -3
- package/command/pipeline/documentation/run.md +25 -0
- package/command/pipeline/planning/gate.md +23 -0
- package/command/pipeline/planning/plan.md +25 -0
- package/command/pipeline/planning/run.md +24 -0
- package/command/pipeline/planning/synthesize.md +21 -0
- package/{commands → command}/pipeline/reviewing/gate.md +3 -4
- package/command/pipeline/reviewing/review.md +20 -0
- package/command/pipeline/reviewing/run.md +23 -0
- package/{commands → command}/pipeline/reviewing/synthesize.md +3 -4
- package/{commands → command}/pipeline/reviewing/triage.md +2 -3
- package/command/pipeline/start.md +29 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +3 -52
- package/package.json +2 -2
- 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/building/run.md +0 -19
- package/commands/pipeline/documentation/run.md +0 -27
- package/commands/pipeline/planning/gate.md +0 -22
- 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/planning/run.md +0 -25
- package/commands/pipeline/planning/synthesize.md +0 -18
- 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/commands/pipeline/reviewing/run.md +0 -24
- package/commands/pipeline/start.md +0 -22
- 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,40 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { findAgentFiles, findAgentFilesById, findAllAgentFiles, deleteAgentFiles, } from '../agent-context.js';
|
|
3
|
-
export function createFFAgentsClearTool() {
|
|
4
|
-
return tool({
|
|
5
|
-
description: 'Clear agent context files. Can clear all, or filter by session, agent type, or specific UUID',
|
|
6
|
-
args: {
|
|
7
|
-
sessionID: tool.schema.string().optional().describe('Clear only agents for specific session'),
|
|
8
|
-
agent: tool.schema.string().optional().describe('Clear only specific agent type'),
|
|
9
|
-
id: tool.schema.string().optional().describe('Clear specific agent by UUID'),
|
|
10
|
-
},
|
|
11
|
-
async execute(args, toolCtx) {
|
|
12
|
-
try {
|
|
13
|
-
let files = [];
|
|
14
|
-
if (args.id) {
|
|
15
|
-
files = await findAgentFilesById(toolCtx.directory, args.id);
|
|
16
|
-
}
|
|
17
|
-
else if (args.agent && args.sessionID) {
|
|
18
|
-
files = await findAgentFiles(toolCtx.directory, args.agent, args.sessionID);
|
|
19
|
-
}
|
|
20
|
-
else if (args.sessionID) {
|
|
21
|
-
files = await findAgentFiles(toolCtx.directory, undefined, args.sessionID);
|
|
22
|
-
}
|
|
23
|
-
else if (args.agent) {
|
|
24
|
-
files = await findAgentFiles(toolCtx.directory, args.agent);
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
files = await findAllAgentFiles(toolCtx.directory);
|
|
28
|
-
}
|
|
29
|
-
if (files.length === 0) {
|
|
30
|
-
return 'No agent context files found to clear.';
|
|
31
|
-
}
|
|
32
|
-
const deletedCount = await deleteAgentFiles(toolCtx.directory, files);
|
|
33
|
-
return `Cleared ${deletedCount} agent context file(s)`;
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
return `Error clearing agents: ${error}`;
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { listActiveAgents } from '../agent-context.js';
|
|
3
|
-
export function createFFAgentsCurrentTool() {
|
|
4
|
-
return tool({
|
|
5
|
-
description: 'List all currently active Feature Factory agents with their UUIDs and status',
|
|
6
|
-
args: {
|
|
7
|
-
sessionID: tool.schema.string().optional().describe('Filter by specific session ID'),
|
|
8
|
-
agent: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.optional()
|
|
11
|
-
.describe('Filter by agent type (e.g., planning, research)'),
|
|
12
|
-
},
|
|
13
|
-
async execute(args, toolCtx) {
|
|
14
|
-
try {
|
|
15
|
-
const agents = await listActiveAgents(toolCtx.directory, args.sessionID, args.agent);
|
|
16
|
-
if (agents.length === 0) {
|
|
17
|
-
return JSON.stringify({
|
|
18
|
-
count: 0,
|
|
19
|
-
message: 'No active agents found',
|
|
20
|
-
agents: [],
|
|
21
|
-
}, null, 2);
|
|
22
|
-
}
|
|
23
|
-
return JSON.stringify({
|
|
24
|
-
count: agents.length,
|
|
25
|
-
agents: agents.map((a) => ({
|
|
26
|
-
id: a.id,
|
|
27
|
-
agent: a.agent,
|
|
28
|
-
title: a.title,
|
|
29
|
-
folder: a.folder,
|
|
30
|
-
status: a.status,
|
|
31
|
-
started: a.started,
|
|
32
|
-
session: a.session,
|
|
33
|
-
parent: a.parent || null,
|
|
34
|
-
delegated_to: a.delegated_to || [],
|
|
35
|
-
})),
|
|
36
|
-
}, null, 2);
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
return JSON.stringify({
|
|
40
|
-
error: `Failed to list agents: ${error}`,
|
|
41
|
-
}, null, 2);
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { validateSafePath, resolveSafePath, getFeatureFactoryDir } from '../utils/file-utils.js';
|
|
3
|
-
import { unlink } from 'fs/promises';
|
|
4
|
-
export function createFFAgentsDeleteTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Delete an agent context file from .feature-factory/agents',
|
|
7
|
-
args: {
|
|
8
|
-
fileName: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.describe('Name of the agent file to delete (e.g., "planning-abc123.md")'),
|
|
11
|
-
},
|
|
12
|
-
async execute(args, toolCtx) {
|
|
13
|
-
try {
|
|
14
|
-
const agentsDir = getFeatureFactoryDir(toolCtx.directory, 'agents');
|
|
15
|
-
// Validate the file path
|
|
16
|
-
if (!validateSafePath(agentsDir, 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(agentsDir, args.fileName);
|
|
20
|
-
// Delete the file
|
|
21
|
-
await unlink(filePath);
|
|
22
|
-
return `Successfully deleted agent file: ${args.fileName}`;
|
|
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/agents`;
|
|
27
|
-
}
|
|
28
|
-
return `Error deleting agent file: ${error}`;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
}
|
|
@@ -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 createFFAgentsGetTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Read an agent context file by name from .feature-factory/agents',
|
|
7
|
-
args: {
|
|
8
|
-
fileName: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.describe('Name of the agent file (e.g., "planning-abc123.md")'),
|
|
11
|
-
},
|
|
12
|
-
async execute(args, toolCtx) {
|
|
13
|
-
try {
|
|
14
|
-
const agentsDir = getFeatureFactoryDir(toolCtx.directory, 'agents');
|
|
15
|
-
// Validate the file path
|
|
16
|
-
if (!validateSafePath(agentsDir, 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(agentsDir, 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/agents`;
|
|
27
|
-
}
|
|
28
|
-
return `Error reading agent 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 createFFAgentsListTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'List all agent context files in .feature-factory/agents',
|
|
7
|
-
args: {
|
|
8
|
-
pattern: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.optional()
|
|
11
|
-
.describe('Optional filter pattern (e.g., "planning-*.md")'),
|
|
12
|
-
},
|
|
13
|
-
async execute(args, toolCtx) {
|
|
14
|
-
try {
|
|
15
|
-
const agentsDir = getFeatureFactoryDir(toolCtx.directory, 'agents');
|
|
16
|
-
// Ensure the agents directory exists
|
|
17
|
-
await ensureDirectoryExists(agentsDir);
|
|
18
|
-
// List files
|
|
19
|
-
const entries = await readdir(agentsDir, { 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 agent files found in .feature-factory/agents';
|
|
34
|
-
}
|
|
35
|
-
return JSON.stringify(files, null, 2);
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
return `Error listing agent files: ${error}`;
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { readAgentContextById } from '../agent-context.js';
|
|
3
|
-
export function createFFAgentsShowTool() {
|
|
4
|
-
return tool({
|
|
5
|
-
description: 'Show detailed context for a specific agent by UUID',
|
|
6
|
-
args: {
|
|
7
|
-
id: tool.schema.string().describe('Agent UUID to show details for'),
|
|
8
|
-
},
|
|
9
|
-
async execute(args, toolCtx) {
|
|
10
|
-
try {
|
|
11
|
-
const agentContext = await readAgentContextById(toolCtx.directory, args.id);
|
|
12
|
-
if (!agentContext) {
|
|
13
|
-
return `Agent with ID "${args.id}" not found`;
|
|
14
|
-
}
|
|
15
|
-
return JSON.stringify(agentContext, null, 2);
|
|
16
|
-
}
|
|
17
|
-
catch (error) {
|
|
18
|
-
return `Error reading agent: ${error}`;
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
}
|
|
@@ -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 createFFAgentsUpdateTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Create or update an agent context file in .feature-factory/agents',
|
|
7
|
-
args: {
|
|
8
|
-
fileName: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.describe('Name of the agent file (e.g., "planning-abc123.md")'),
|
|
11
|
-
content: tool.schema.string().describe('Content to write to the file'),
|
|
12
|
-
},
|
|
13
|
-
async execute(args, toolCtx) {
|
|
14
|
-
try {
|
|
15
|
-
const agentsDir = getFeatureFactoryDir(toolCtx.directory, 'agents');
|
|
16
|
-
// Validate the file path
|
|
17
|
-
if (!validateSafePath(agentsDir, args.fileName)) {
|
|
18
|
-
return `Error: Invalid or unsafe file name "${args.fileName}". Only .md files with alphanumeric names are allowed.`;
|
|
19
|
-
}
|
|
20
|
-
// Ensure the agents directory exists
|
|
21
|
-
await ensureDirectoryExists(agentsDir);
|
|
22
|
-
const filePath = resolveSafePath(agentsDir, args.fileName);
|
|
23
|
-
// Write file content
|
|
24
|
-
await writeFile(filePath, args.content, 'utf-8');
|
|
25
|
-
return `Successfully updated agent file: ${args.fileName}`;
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
return `Error updating agent file: ${error}`;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
}
|
|
@@ -1,61 +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 createFFPlanCreateTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Create a new implementation plan file in .feature-factory/plans/. Use this to document planning decisions, architecture choices, and implementation strategies.',
|
|
7
|
-
args: {
|
|
8
|
-
title: tool.schema.string().describe('Title of the plan'),
|
|
9
|
-
description: tool.schema.string().describe('Detailed description of what the plan covers'),
|
|
10
|
-
content: tool.schema
|
|
11
|
-
.string()
|
|
12
|
-
.describe('Full markdown content of the plan including all sections'),
|
|
13
|
-
planType: tool.schema
|
|
14
|
-
.enum(['implementation', 'architecture', 'migration', 'research'])
|
|
15
|
-
.describe('Type of plan being created'),
|
|
16
|
-
relatedIssues: tool.schema
|
|
17
|
-
.array(tool.schema.string())
|
|
18
|
-
.optional()
|
|
19
|
-
.describe('Array of related issue IDs or PR numbers'),
|
|
20
|
-
estimatedEffort: tool.schema
|
|
21
|
-
.string()
|
|
22
|
-
.optional()
|
|
23
|
-
.describe('Estimated time/effort (e.g., "2-3 hours", "1-2 days")'),
|
|
24
|
-
},
|
|
25
|
-
async execute(args, toolCtx) {
|
|
26
|
-
try {
|
|
27
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
28
|
-
const planId = `${timestamp}-${args.planType}`;
|
|
29
|
-
const filePath = `${toolCtx.directory}/.feature-factory/plans/${planId}.md`;
|
|
30
|
-
// Generate frontmatter
|
|
31
|
-
const frontmatter = `---
|
|
32
|
-
id: "${planId}"
|
|
33
|
-
title: "${args.title}"
|
|
34
|
-
description: "${args.description}"
|
|
35
|
-
created: "${new Date().toISOString()}"
|
|
36
|
-
plan_type: "${args.planType}"
|
|
37
|
-
status: "draft"
|
|
38
|
-
${args.relatedIssues ? `related_issues:\n${args.relatedIssues.map((i) => ` - "${i}"`).join('\n')}` : ''}
|
|
39
|
-
${args.estimatedEffort ? `estimated_effort: "${args.estimatedEffort}"` : ''}
|
|
40
|
-
---`;
|
|
41
|
-
const fullContent = `${frontmatter}\n\n${args.content}`;
|
|
42
|
-
// Ensure directory exists
|
|
43
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
44
|
-
// Write file
|
|
45
|
-
await writeFile(filePath, fullContent, 'utf-8');
|
|
46
|
-
return JSON.stringify({
|
|
47
|
-
success: true,
|
|
48
|
-
planId,
|
|
49
|
-
filePath: `.feature-factory/plans/${planId}.md`,
|
|
50
|
-
message: `Plan created successfully at .feature-factory/plans/${planId}.md`,
|
|
51
|
-
}, null, 2);
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
return JSON.stringify({
|
|
55
|
-
success: false,
|
|
56
|
-
error: `Failed to create plan: ${error}`,
|
|
57
|
-
}, null, 2);
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { readFile, writeFile } from 'fs/promises';
|
|
3
|
-
export function createFFPlanUpdateTool() {
|
|
4
|
-
return tool({
|
|
5
|
-
description: 'Update an existing implementation plan file in .feature-factory/plans/. Use this to modify plans when changes are requested or requirements evolve.',
|
|
6
|
-
args: {
|
|
7
|
-
planId: tool.schema
|
|
8
|
-
.string()
|
|
9
|
-
.describe('ID of the plan to update (from the plan file frontmatter)'),
|
|
10
|
-
updates: tool.schema
|
|
11
|
-
.object({
|
|
12
|
-
title: tool.schema.string().optional().describe('New title for the plan'),
|
|
13
|
-
description: tool.schema.string().optional().describe('New description'),
|
|
14
|
-
content: tool.schema
|
|
15
|
-
.string()
|
|
16
|
-
.optional()
|
|
17
|
-
.describe('New full markdown content (replaces existing content after frontmatter)'),
|
|
18
|
-
status: tool.schema
|
|
19
|
-
.enum(['draft', 'in-progress', 'completed', 'archived'])
|
|
20
|
-
.optional()
|
|
21
|
-
.describe('Updated status'),
|
|
22
|
-
relatedIssues: tool.schema
|
|
23
|
-
.array(tool.schema.string())
|
|
24
|
-
.optional()
|
|
25
|
-
.describe('Updated array of related issue IDs'),
|
|
26
|
-
estimatedEffort: tool.schema.string().optional().describe('Updated time/effort estimate'),
|
|
27
|
-
})
|
|
28
|
-
.describe('Object containing the fields to update'),
|
|
29
|
-
appendContent: tool.schema
|
|
30
|
-
.string()
|
|
31
|
-
.optional()
|
|
32
|
-
.describe('Content to append to the existing plan (instead of replacing)'),
|
|
33
|
-
addSection: tool.schema
|
|
34
|
-
.object({
|
|
35
|
-
heading: tool.schema.string().describe('Section heading'),
|
|
36
|
-
content: tool.schema.string().describe('Section content'),
|
|
37
|
-
})
|
|
38
|
-
.optional()
|
|
39
|
-
.describe('Add a new section to the plan'),
|
|
40
|
-
},
|
|
41
|
-
async execute(args, toolCtx) {
|
|
42
|
-
try {
|
|
43
|
-
// Find the plan file by ID
|
|
44
|
-
const plansDir = `${toolCtx.directory}/.feature-factory/plans`;
|
|
45
|
-
const { glob } = await import('glob');
|
|
46
|
-
const planFiles = await glob('*.md', { cwd: plansDir, absolute: true });
|
|
47
|
-
let targetFile = null;
|
|
48
|
-
let existingContent = null;
|
|
49
|
-
for (const file of planFiles) {
|
|
50
|
-
try {
|
|
51
|
-
const content = await readFile(file, 'utf-8');
|
|
52
|
-
const idMatch = content.match(/id:\s*"([^"]+)"/);
|
|
53
|
-
if (idMatch && idMatch[1] === args.planId) {
|
|
54
|
-
targetFile = file;
|
|
55
|
-
existingContent = content;
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (!targetFile || !existingContent) {
|
|
64
|
-
return JSON.stringify({
|
|
65
|
-
success: false,
|
|
66
|
-
error: `Plan not found with ID: ${args.planId}`,
|
|
67
|
-
}, null, 2);
|
|
68
|
-
}
|
|
69
|
-
// Parse existing frontmatter
|
|
70
|
-
const frontmatterMatch = existingContent.match(/^---\n([\s\S]*?)\n---/);
|
|
71
|
-
if (!frontmatterMatch) {
|
|
72
|
-
return JSON.stringify({
|
|
73
|
-
success: false,
|
|
74
|
-
error: 'Invalid plan file format: missing frontmatter',
|
|
75
|
-
}, null, 2);
|
|
76
|
-
}
|
|
77
|
-
const existingFrontmatter = frontmatterMatch[1];
|
|
78
|
-
const existingBody = existingContent.substring(frontmatterMatch[0].length).trim();
|
|
79
|
-
// Update frontmatter fields
|
|
80
|
-
let updatedFrontmatter = existingFrontmatter;
|
|
81
|
-
if (args.updates.title) {
|
|
82
|
-
updatedFrontmatter = updatedFrontmatter.replace(/title:\s*"[^"]+"/, `title: "${args.updates.title}"`);
|
|
83
|
-
}
|
|
84
|
-
if (args.updates.description) {
|
|
85
|
-
updatedFrontmatter = updatedFrontmatter.replace(/description:\s*"[^"]+"/, `description: "${args.updates.description}"`);
|
|
86
|
-
}
|
|
87
|
-
if (args.updates.status) {
|
|
88
|
-
if (updatedFrontmatter.includes('status:')) {
|
|
89
|
-
updatedFrontmatter = updatedFrontmatter.replace(/status:\s*\w+/, `status: "${args.updates.status}"`);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
updatedFrontmatter += `\nstatus: "${args.updates.status}"`;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (args.updates.estimatedEffort) {
|
|
96
|
-
if (updatedFrontmatter.includes('estimated_effort:')) {
|
|
97
|
-
updatedFrontmatter = updatedFrontmatter.replace(/estimated_effort:\s*"[^"]+"/, `estimated_effort: "${args.updates.estimatedEffort}"`);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
updatedFrontmatter += `\nestimated_effort: "${args.updates.estimatedEffort}"`;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (args.updates.relatedIssues) {
|
|
104
|
-
if (updatedFrontmatter.includes('related_issues:')) {
|
|
105
|
-
updatedFrontmatter = updatedFrontmatter.replace(/related_issues:\n(?:\s+-\s+"[^"]+"\n)*/, `related_issues:\n${args.updates.relatedIssues.map((i) => ` - "${i}"`).join('\n')}`);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
updatedFrontmatter += `\nrelated_issues:\n${args.updates.relatedIssues.map((i) => ` - "${i}"`).join('\n')}`;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// Update body content
|
|
112
|
-
let updatedBody = existingBody;
|
|
113
|
-
if (args.updates.content) {
|
|
114
|
-
updatedBody = args.updates.content;
|
|
115
|
-
}
|
|
116
|
-
else if (args.appendContent) {
|
|
117
|
-
updatedBody += '\n\n' + args.appendContent;
|
|
118
|
-
}
|
|
119
|
-
else if (args.addSection) {
|
|
120
|
-
updatedBody += `\n\n## ${args.addSection.heading}\n\n${args.addSection.content}`;
|
|
121
|
-
}
|
|
122
|
-
// Reconstruct the file
|
|
123
|
-
const updatedContent = `---\n${updatedFrontmatter}\n---\n\n${updatedBody}`;
|
|
124
|
-
// Write updated file
|
|
125
|
-
await writeFile(targetFile, updatedContent, 'utf-8');
|
|
126
|
-
return JSON.stringify({
|
|
127
|
-
success: true,
|
|
128
|
-
planId: args.planId,
|
|
129
|
-
filePath: targetFile.replace(`${toolCtx.directory}/`, ''),
|
|
130
|
-
message: `Plan updated successfully`,
|
|
131
|
-
updates: Object.keys(args.updates).filter((k) => args.updates[k] !== undefined),
|
|
132
|
-
}, null, 2);
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
return JSON.stringify({
|
|
136
|
-
success: false,
|
|
137
|
-
error: `Failed to update plan: ${error}`,
|
|
138
|
-
}, null, 2);
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { validateSafePath, resolveSafePath, getFeatureFactoryDir } from '../utils/file-utils.js';
|
|
3
|
-
import { unlink } from 'fs/promises';
|
|
4
|
-
export function createFFPlansDeleteTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Delete a plan file from .feature-factory/plans',
|
|
7
|
-
args: {
|
|
8
|
-
fileName: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.describe('Name of the plan file to delete (e.g., "implementation-plan.md")'),
|
|
11
|
-
},
|
|
12
|
-
async execute(args, toolCtx) {
|
|
13
|
-
try {
|
|
14
|
-
const plansDir = getFeatureFactoryDir(toolCtx.directory, 'plans');
|
|
15
|
-
// Validate the file path
|
|
16
|
-
if (!validateSafePath(plansDir, 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(plansDir, args.fileName);
|
|
20
|
-
// Delete the file
|
|
21
|
-
await unlink(filePath);
|
|
22
|
-
return `Successfully deleted plan file: ${args.fileName}`;
|
|
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/plans`;
|
|
27
|
-
}
|
|
28
|
-
return `Error deleting plan file: ${error}`;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
}
|
|
@@ -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 createFFPlansGetTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Read a plan file by name from .feature-factory/plans',
|
|
7
|
-
args: {
|
|
8
|
-
fileName: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.describe('Name of the plan file (e.g., "implementation-plan.md")'),
|
|
11
|
-
},
|
|
12
|
-
async execute(args, toolCtx) {
|
|
13
|
-
try {
|
|
14
|
-
const plansDir = getFeatureFactoryDir(toolCtx.directory, 'plans');
|
|
15
|
-
// Validate the file path
|
|
16
|
-
if (!validateSafePath(plansDir, 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(plansDir, 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/plans`;
|
|
27
|
-
}
|
|
28
|
-
return `Error reading plan 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 createFFPlansListTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'List all plan files in .feature-factory/plans',
|
|
7
|
-
args: {
|
|
8
|
-
pattern: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.optional()
|
|
11
|
-
.describe('Optional filter pattern (e.g., "*-plan.md")'),
|
|
12
|
-
},
|
|
13
|
-
async execute(args, toolCtx) {
|
|
14
|
-
try {
|
|
15
|
-
const plansDir = getFeatureFactoryDir(toolCtx.directory, 'plans');
|
|
16
|
-
// Ensure the plans directory exists
|
|
17
|
-
await ensureDirectoryExists(plansDir);
|
|
18
|
-
// List files
|
|
19
|
-
const entries = await readdir(plansDir, { 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 plan files found in .feature-factory/plans';
|
|
34
|
-
}
|
|
35
|
-
return JSON.stringify(files, null, 2);
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
return `Error listing plan 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 createFFPlansUpdateTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Create or update a plan file in .feature-factory/plans',
|
|
7
|
-
args: {
|
|
8
|
-
fileName: tool.schema
|
|
9
|
-
.string()
|
|
10
|
-
.describe('Name of the plan file (e.g., "implementation-plan.md")'),
|
|
11
|
-
content: tool.schema.string().describe('Content to write to the file'),
|
|
12
|
-
},
|
|
13
|
-
async execute(args, toolCtx) {
|
|
14
|
-
try {
|
|
15
|
-
const plansDir = getFeatureFactoryDir(toolCtx.directory, 'plans');
|
|
16
|
-
// Validate the file path
|
|
17
|
-
if (!validateSafePath(plansDir, args.fileName)) {
|
|
18
|
-
return `Error: Invalid or unsafe file name "${args.fileName}". Only .md files with alphanumeric names are allowed.`;
|
|
19
|
-
}
|
|
20
|
-
// Ensure the plans directory exists
|
|
21
|
-
await ensureDirectoryExists(plansDir);
|
|
22
|
-
const filePath = resolveSafePath(plansDir, args.fileName);
|
|
23
|
-
// Write file content
|
|
24
|
-
await writeFile(filePath, args.content, 'utf-8');
|
|
25
|
-
return `Successfully updated plan file: ${args.fileName}`;
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
return `Error updating plan file: ${error}`;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
}
|