@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.
Files changed (104) hide show
  1. package/README.md +24 -17
  2. package/agents/building.md +28 -541
  3. package/agents/documenting.md +39 -0
  4. package/agents/ff-research.md +18 -410
  5. package/agents/pipeline.md +27 -69
  6. package/agents/planning.md +28 -350
  7. package/agents/reviewing.md +27 -475
  8. package/bin/ff-deploy.js +7 -7
  9. package/{commands → command}/pipeline/building/breakdown.md +3 -3
  10. package/{commands → command}/pipeline/building/implement-batch.md +3 -3
  11. package/command/pipeline/building/run.md +19 -0
  12. package/{commands → command}/pipeline/building/validate-batch.md +3 -3
  13. package/{commands → command}/pipeline/complete.md +1 -2
  14. package/{commands/pipeline/documentation/run-codex.md → command/pipeline/documentation/document.md} +4 -6
  15. package/{commands → command}/pipeline/documentation/gate.md +3 -4
  16. package/{commands/pipeline/documentation/run-gemini.md → command/pipeline/documentation/review.md} +3 -3
  17. package/command/pipeline/documentation/run.md +25 -0
  18. package/command/pipeline/planning/gate.md +23 -0
  19. package/command/pipeline/planning/plan.md +25 -0
  20. package/command/pipeline/planning/run.md +24 -0
  21. package/command/pipeline/planning/synthesize.md +21 -0
  22. package/{commands → command}/pipeline/reviewing/gate.md +3 -4
  23. package/command/pipeline/reviewing/review.md +20 -0
  24. package/command/pipeline/reviewing/run.md +23 -0
  25. package/{commands → command}/pipeline/reviewing/synthesize.md +3 -4
  26. package/{commands → command}/pipeline/reviewing/triage.md +2 -3
  27. package/command/pipeline/start.md +29 -0
  28. package/dist/index.d.ts +1 -2
  29. package/dist/index.js +3 -52
  30. package/package.json +2 -2
  31. package/skills/ff-reviewing-architecture/SKILL.md +34 -0
  32. package/skills/ff-reviewing-code-quality/SKILL.md +34 -0
  33. package/skills/ff-reviewing-documentation/SKILL.md +34 -0
  34. package/skills/ff-reviewing-security/SKILL.md +34 -0
  35. package/agents/ff-acceptance.md +0 -285
  36. package/agents/ff-building-codex.md +0 -305
  37. package/agents/ff-building-gemini.md +0 -305
  38. package/agents/ff-building-opus.md +0 -305
  39. package/agents/ff-planning-codex.md +0 -335
  40. package/agents/ff-planning-gemini.md +0 -335
  41. package/agents/ff-planning-opus.md +0 -335
  42. package/agents/ff-review.md +0 -288
  43. package/agents/ff-reviewing-codex.md +0 -259
  44. package/agents/ff-reviewing-gemini.md +0 -259
  45. package/agents/ff-reviewing-opus.md +0 -259
  46. package/agents/ff-security.md +0 -322
  47. package/agents/ff-validate.md +0 -316
  48. package/agents/ff-well-architected.md +0 -284
  49. package/commands/pipeline/building/run.md +0 -19
  50. package/commands/pipeline/documentation/run.md +0 -27
  51. package/commands/pipeline/planning/gate.md +0 -22
  52. package/commands/pipeline/planning/run-codex.md +0 -22
  53. package/commands/pipeline/planning/run-gemini.md +0 -21
  54. package/commands/pipeline/planning/run-opus.md +0 -21
  55. package/commands/pipeline/planning/run.md +0 -25
  56. package/commands/pipeline/planning/synthesize.md +0 -18
  57. package/commands/pipeline/reviewing/run-codex.md +0 -12
  58. package/commands/pipeline/reviewing/run-gemini.md +0 -11
  59. package/commands/pipeline/reviewing/run-opus.md +0 -11
  60. package/commands/pipeline/reviewing/run.md +0 -24
  61. package/commands/pipeline/start.md +0 -22
  62. package/dist/agent-context.d.ts +0 -57
  63. package/dist/agent-context.js +0 -282
  64. package/dist/plugins/ff-agent-context-create-plugin.d.ts +0 -2
  65. package/dist/plugins/ff-agent-context-create-plugin.js +0 -82
  66. package/dist/plugins/ff-agent-context-update-plugin.d.ts +0 -2
  67. package/dist/plugins/ff-agent-context-update-plugin.js +0 -78
  68. package/dist/plugins/ff-agents-clear-plugin.d.ts +0 -2
  69. package/dist/plugins/ff-agents-clear-plugin.js +0 -40
  70. package/dist/plugins/ff-agents-current-plugin.d.ts +0 -2
  71. package/dist/plugins/ff-agents-current-plugin.js +0 -45
  72. package/dist/plugins/ff-agents-delete-plugin.d.ts +0 -2
  73. package/dist/plugins/ff-agents-delete-plugin.js +0 -32
  74. package/dist/plugins/ff-agents-get-plugin.d.ts +0 -2
  75. package/dist/plugins/ff-agents-get-plugin.js +0 -32
  76. package/dist/plugins/ff-agents-list-plugin.d.ts +0 -2
  77. package/dist/plugins/ff-agents-list-plugin.js +0 -42
  78. package/dist/plugins/ff-agents-show-plugin.d.ts +0 -2
  79. package/dist/plugins/ff-agents-show-plugin.js +0 -22
  80. package/dist/plugins/ff-agents-update-plugin.d.ts +0 -2
  81. package/dist/plugins/ff-agents-update-plugin.js +0 -32
  82. package/dist/plugins/ff-plan-create-plugin.d.ts +0 -2
  83. package/dist/plugins/ff-plan-create-plugin.js +0 -61
  84. package/dist/plugins/ff-plan-update-plugin.d.ts +0 -2
  85. package/dist/plugins/ff-plan-update-plugin.js +0 -142
  86. package/dist/plugins/ff-plans-delete-plugin.d.ts +0 -2
  87. package/dist/plugins/ff-plans-delete-plugin.js +0 -32
  88. package/dist/plugins/ff-plans-get-plugin.d.ts +0 -2
  89. package/dist/plugins/ff-plans-get-plugin.js +0 -32
  90. package/dist/plugins/ff-plans-list-plugin.d.ts +0 -2
  91. package/dist/plugins/ff-plans-list-plugin.js +0 -42
  92. package/dist/plugins/ff-plans-update-plugin.d.ts +0 -2
  93. package/dist/plugins/ff-plans-update-plugin.js +0 -32
  94. package/dist/plugins/ff-review-create-plugin.d.ts +0 -2
  95. package/dist/plugins/ff-review-create-plugin.js +0 -256
  96. package/dist/plugins/ff-reviews-get-plugin.d.ts +0 -2
  97. package/dist/plugins/ff-reviews-get-plugin.js +0 -32
  98. package/dist/plugins/ff-reviews-list-plugin.d.ts +0 -2
  99. package/dist/plugins/ff-reviews-list-plugin.js +0 -42
  100. package/dist/plugins/ff-reviews-update-plugin.d.ts +0 -2
  101. package/dist/plugins/ff-reviews-update-plugin.js +0 -32
  102. package/skills/ff-context-tracking/SKILL.md +0 -573
  103. package/skills/ff-delegation/SKILL.md +0 -457
  104. package/skills/ff-swarm/SKILL.md +0 -209
@@ -1,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFAgentsClearTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFAgentsCurrentTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFAgentsDeleteTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFAgentsGetTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFAgentsListTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFAgentsShowTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFAgentsUpdateTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFPlanCreateTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFPlanUpdateTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFPlansDeleteTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFPlansGetTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFPlansListTool(): ReturnType<typeof tool>;
@@ -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,2 +0,0 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- export declare function createFFPlansUpdateTool(): ReturnType<typeof tool>;
@@ -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
- }