agileflow 2.30.1 → 2.31.0
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/package.json +1 -1
- package/tools/cli/commands/install.js +14 -0
- package/tools/cli/commands/update.js +17 -0
- package/tools/cli/installers/ide/_base-ide.js +30 -0
- package/tools/cli/installers/ide/claude-code.js +14 -90
- package/tools/cli/installers/ide/cursor.js +34 -105
- package/tools/cli/installers/ide/manager.js +14 -0
- package/tools/cli/installers/ide/windsurf.js +14 -95
- package/tools/cli/lib/docs-setup.js +425 -0
- package/tools/cli/lib/ui.js +19 -6
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ const path = require('node:path');
|
|
|
9
9
|
const { Installer } = require('../installers/core/installer');
|
|
10
10
|
const { IdeManager } = require('../installers/ide/manager');
|
|
11
11
|
const { promptInstall, success, error, info, displaySection } = require('../lib/ui');
|
|
12
|
+
const { createDocsStructure } = require('../lib/docs-setup');
|
|
12
13
|
|
|
13
14
|
const installer = new Installer();
|
|
14
15
|
const ideManager = new IdeManager();
|
|
@@ -31,6 +32,7 @@ module.exports = {
|
|
|
31
32
|
ides: ['claude-code'],
|
|
32
33
|
userName: 'Developer',
|
|
33
34
|
agileflowFolder: '.agileflow',
|
|
35
|
+
docsFolder: 'docs',
|
|
34
36
|
};
|
|
35
37
|
} else {
|
|
36
38
|
// Interactive prompts
|
|
@@ -55,11 +57,23 @@ module.exports = {
|
|
|
55
57
|
displaySection('Configuring IDEs');
|
|
56
58
|
|
|
57
59
|
ideManager.setAgileflowFolder(config.agileflowFolder);
|
|
60
|
+
ideManager.setDocsFolder(config.docsFolder);
|
|
58
61
|
|
|
59
62
|
for (const ide of config.ides) {
|
|
60
63
|
await ideManager.setup(ide, config.directory, coreResult.path);
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
// Create docs structure
|
|
67
|
+
displaySection('Creating Documentation Structure', `Folder: ${config.docsFolder}/`);
|
|
68
|
+
const docsResult = await createDocsStructure(config.directory, config.docsFolder);
|
|
69
|
+
|
|
70
|
+
if (!docsResult.success) {
|
|
71
|
+
error('Failed to create docs structure');
|
|
72
|
+
if (docsResult.errors.length > 0) {
|
|
73
|
+
docsResult.errors.forEach((err) => error(` ${err}`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
63
77
|
// Final summary
|
|
64
78
|
console.log(chalk.green('\n✨ Installation complete!\n'));
|
|
65
79
|
|
|
@@ -9,6 +9,7 @@ const path = require('node:path');
|
|
|
9
9
|
const { Installer } = require('../installers/core/installer');
|
|
10
10
|
const { IdeManager } = require('../installers/ide/manager');
|
|
11
11
|
const { displayLogo, displaySection, success, warning, error, info, confirm } = require('../lib/ui');
|
|
12
|
+
const { createDocsStructure, getDocsFolderName } = require('../lib/docs-setup');
|
|
12
13
|
|
|
13
14
|
const installer = new Installer();
|
|
14
15
|
const ideManager = new IdeManager();
|
|
@@ -60,12 +61,16 @@ module.exports = {
|
|
|
60
61
|
|
|
61
62
|
console.log();
|
|
62
63
|
|
|
64
|
+
// Get docs folder name from metadata (or default to 'docs')
|
|
65
|
+
const docsFolder = await getDocsFolderName(directory);
|
|
66
|
+
|
|
63
67
|
// Re-run installation with existing config
|
|
64
68
|
const config = {
|
|
65
69
|
directory,
|
|
66
70
|
ides: status.ides || ['claude-code'],
|
|
67
71
|
userName: 'Developer', // Could read from existing config
|
|
68
72
|
agileflowFolder: path.basename(status.path),
|
|
73
|
+
docsFolder,
|
|
69
74
|
};
|
|
70
75
|
|
|
71
76
|
// Run core installation
|
|
@@ -80,11 +85,23 @@ module.exports = {
|
|
|
80
85
|
|
|
81
86
|
// Re-setup IDEs
|
|
82
87
|
ideManager.setAgileflowFolder(config.agileflowFolder);
|
|
88
|
+
ideManager.setDocsFolder(config.docsFolder);
|
|
83
89
|
|
|
84
90
|
for (const ide of config.ides) {
|
|
85
91
|
await ideManager.setup(ide, directory, status.path);
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
// Create/update docs structure (idempotent - only creates missing files)
|
|
95
|
+
displaySection('Updating Documentation Structure', `Folder: ${docsFolder}/`);
|
|
96
|
+
const docsResult = await createDocsStructure(directory, docsFolder);
|
|
97
|
+
|
|
98
|
+
if (!docsResult.success) {
|
|
99
|
+
warning('Failed to update docs structure');
|
|
100
|
+
if (docsResult.errors.length > 0) {
|
|
101
|
+
docsResult.errors.forEach((err) => error(` ${err}`));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
88
105
|
console.log(chalk.green(`\n✨ Update complete! (${status.version} → ${newVersion})\n`));
|
|
89
106
|
|
|
90
107
|
process.exit(0);
|
|
@@ -19,6 +19,7 @@ class BaseIdeSetup {
|
|
|
19
19
|
this.preferred = preferred;
|
|
20
20
|
this.configDir = null; // Override in subclasses
|
|
21
21
|
this.agileflowFolder = '.agileflow';
|
|
22
|
+
this.docsFolder = 'docs';
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -29,6 +30,35 @@ class BaseIdeSetup {
|
|
|
29
30
|
this.agileflowFolder = folderName;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Set the docs folder name
|
|
35
|
+
* @param {string} folderName - The docs folder name
|
|
36
|
+
*/
|
|
37
|
+
setDocsFolder(folderName) {
|
|
38
|
+
this.docsFolder = folderName;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Replace docs/ references in content with custom folder name
|
|
43
|
+
* @param {string} content - File content
|
|
44
|
+
* @returns {string} Updated content
|
|
45
|
+
*/
|
|
46
|
+
replaceDocsReferences(content) {
|
|
47
|
+
if (this.docsFolder === 'docs') {
|
|
48
|
+
return content; // No replacement needed
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Replace all variations of docs/ references
|
|
52
|
+
return content
|
|
53
|
+
.replace(/docs\//g, `${this.docsFolder}/`)
|
|
54
|
+
.replace(/`docs\//g, `\`${this.docsFolder}/`)
|
|
55
|
+
.replace(/"docs\//g, `"${this.docsFolder}/`)
|
|
56
|
+
.replace(/'docs\//g, `'${this.docsFolder}/`)
|
|
57
|
+
.replace(/\(docs\//g, `(${this.docsFolder}/`)
|
|
58
|
+
.replace(/docs\/\)/g, `${this.docsFolder}/)`)
|
|
59
|
+
.replace(/\bdocs\b(?!\.)/g, this.docsFolder); // Replace standalone "docs" word
|
|
60
|
+
}
|
|
61
|
+
|
|
32
62
|
/**
|
|
33
63
|
* Main setup method - must be implemented by subclasses
|
|
34
64
|
* @param {string} projectDir - Project directory
|
|
@@ -46,11 +46,14 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|
|
46
46
|
const commands = await this.scanDirectory(commandsSource, '.md');
|
|
47
47
|
|
|
48
48
|
for (const command of commands) {
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
// Read the original command content
|
|
50
|
+
let content = await this.readFile(command.path);
|
|
51
|
+
|
|
52
|
+
// Replace docs/ references with custom folder name
|
|
53
|
+
content = this.replaceDocsReferences(content);
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
const targetPath = path.join(agileflowCommandsDir, `${command.name}.md`);
|
|
56
|
+
await this.writeFile(targetPath, content);
|
|
54
57
|
commandCount++;
|
|
55
58
|
}
|
|
56
59
|
}
|
|
@@ -67,11 +70,14 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|
|
67
70
|
const agents = await this.scanDirectory(agentsSource, '.md');
|
|
68
71
|
|
|
69
72
|
for (const agent of agents) {
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
// Read the original agent content
|
|
74
|
+
let content = await this.readFile(agent.path);
|
|
75
|
+
|
|
76
|
+
// Replace docs/ references with custom folder name
|
|
77
|
+
content = this.replaceDocsReferences(content);
|
|
73
78
|
|
|
74
|
-
|
|
79
|
+
const targetPath = path.join(agileflowAgentsDir, `${agent.name}.md`);
|
|
80
|
+
await this.writeFile(targetPath, content);
|
|
75
81
|
agentCount++;
|
|
76
82
|
}
|
|
77
83
|
}
|
|
@@ -87,88 +93,6 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|
|
87
93
|
agents: agentCount,
|
|
88
94
|
};
|
|
89
95
|
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Create a command launcher file
|
|
93
|
-
* @param {Object} command - Command info
|
|
94
|
-
* @param {string} agileflowDir - AgileFlow directory
|
|
95
|
-
* @param {string} projectDir - Project directory
|
|
96
|
-
* @returns {Promise<string>} Launcher content
|
|
97
|
-
*/
|
|
98
|
-
async createCommandLauncher(command, agileflowDir, projectDir) {
|
|
99
|
-
// Read the original command file
|
|
100
|
-
const content = await this.readFile(command.path);
|
|
101
|
-
|
|
102
|
-
// Extract description from frontmatter if present
|
|
103
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
104
|
-
let description = command.name;
|
|
105
|
-
|
|
106
|
-
if (frontmatterMatch) {
|
|
107
|
-
const descMatch = frontmatterMatch[1].match(/description:\s*["']?([^"'\n]+)["']?/);
|
|
108
|
-
if (descMatch) {
|
|
109
|
-
description = descMatch[1];
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Create launcher that loads the full command
|
|
114
|
-
const relativePath = path.relative(projectDir, command.path);
|
|
115
|
-
|
|
116
|
-
return `---
|
|
117
|
-
description: "${description}"
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
# ${command.name}
|
|
121
|
-
|
|
122
|
-
Load and execute the AgileFlow command.
|
|
123
|
-
|
|
124
|
-
\`\`\`
|
|
125
|
-
Read and follow the instructions in: ${relativePath}
|
|
126
|
-
\`\`\`
|
|
127
|
-
`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Create an agent launcher file
|
|
132
|
-
* @param {Object} agent - Agent info
|
|
133
|
-
* @param {string} agileflowDir - AgileFlow directory
|
|
134
|
-
* @param {string} projectDir - Project directory
|
|
135
|
-
* @returns {Promise<string>} Launcher content
|
|
136
|
-
*/
|
|
137
|
-
async createAgentLauncher(agent, agileflowDir, projectDir) {
|
|
138
|
-
// Read the original agent file
|
|
139
|
-
const content = await this.readFile(agent.path);
|
|
140
|
-
|
|
141
|
-
// Extract metadata from frontmatter
|
|
142
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
143
|
-
let description = agent.name;
|
|
144
|
-
let name = agent.name;
|
|
145
|
-
|
|
146
|
-
if (frontmatterMatch) {
|
|
147
|
-
const descMatch = frontmatterMatch[1].match(/description:\s*["']?([^"'\n]+)["']?/);
|
|
148
|
-
const nameMatch = frontmatterMatch[1].match(/name:\s*["']?([^"'\n]+)["']?/);
|
|
149
|
-
|
|
150
|
-
if (descMatch) description = descMatch[1];
|
|
151
|
-
if (nameMatch) name = nameMatch[1];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Create launcher that loads the full agent
|
|
155
|
-
const relativePath = path.relative(projectDir, agent.path);
|
|
156
|
-
|
|
157
|
-
return `---
|
|
158
|
-
description: "${description}"
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
# ${name}
|
|
162
|
-
|
|
163
|
-
Activate the AgileFlow agent.
|
|
164
|
-
|
|
165
|
-
\`\`\`
|
|
166
|
-
Read and fully embody the agent defined in: ${relativePath}
|
|
167
|
-
|
|
168
|
-
Follow all instructions, adopt the persona, and use the specified tools.
|
|
169
|
-
\`\`\`
|
|
170
|
-
`;
|
|
171
|
-
}
|
|
172
96
|
}
|
|
173
97
|
|
|
174
98
|
module.exports = { ClaudeCodeSetup };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AgileFlow CLI - Cursor IDE Installer
|
|
3
3
|
*
|
|
4
|
-
* Installs AgileFlow
|
|
5
|
-
* Cursor uses
|
|
4
|
+
* Installs AgileFlow commands for Cursor IDE.
|
|
5
|
+
* Cursor uses plain Markdown files in .cursor/commands/
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const path = require('node:path');
|
|
@@ -15,9 +15,9 @@ const { BaseIdeSetup } = require('./_base-ide');
|
|
|
15
15
|
*/
|
|
16
16
|
class CursorSetup extends BaseIdeSetup {
|
|
17
17
|
constructor() {
|
|
18
|
-
super('cursor', 'Cursor',
|
|
18
|
+
super('cursor', 'Cursor', false);
|
|
19
19
|
this.configDir = '.cursor';
|
|
20
|
-
this.
|
|
20
|
+
this.commandsDir = 'commands';
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -32,12 +32,12 @@ class CursorSetup extends BaseIdeSetup {
|
|
|
32
32
|
// Clean up old installation first
|
|
33
33
|
await this.cleanup(projectDir);
|
|
34
34
|
|
|
35
|
-
// Create .cursor/
|
|
35
|
+
// Create .cursor/commands/AgileFlow directory
|
|
36
36
|
const cursorDir = path.join(projectDir, this.configDir);
|
|
37
|
-
const
|
|
38
|
-
const
|
|
37
|
+
const commandsDir = path.join(cursorDir, this.commandsDir);
|
|
38
|
+
const agileflowCommandsDir = path.join(commandsDir, 'AgileFlow');
|
|
39
39
|
|
|
40
|
-
await this.ensureDir(
|
|
40
|
+
await this.ensureDir(agileflowCommandsDir);
|
|
41
41
|
|
|
42
42
|
// Get commands from AgileFlow installation
|
|
43
43
|
const commandsSource = path.join(agileflowDir, 'commands');
|
|
@@ -47,17 +47,20 @@ class CursorSetup extends BaseIdeSetup {
|
|
|
47
47
|
const commands = await this.scanDirectory(commandsSource, '.md');
|
|
48
48
|
|
|
49
49
|
for (const command of commands) {
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
const targetPath = path.join(agileflowRulesDir, `${command.name}.mdc`);
|
|
50
|
+
// Read the original command content
|
|
51
|
+
let content = await this.readFile(command.path);
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
// Replace docs/ references with custom folder name
|
|
54
|
+
content = this.replaceDocsReferences(content);
|
|
55
|
+
|
|
56
|
+
const targetPath = path.join(agileflowCommandsDir, `${command.name}.md`);
|
|
57
|
+
await this.writeFile(targetPath, content);
|
|
55
58
|
commandCount++;
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
// Create agents subdirectory
|
|
60
|
-
const agileflowAgentsDir = path.join(
|
|
63
|
+
const agileflowAgentsDir = path.join(agileflowCommandsDir, 'agents');
|
|
61
64
|
await this.ensureDir(agileflowAgentsDir);
|
|
62
65
|
|
|
63
66
|
// Get agents from AgileFlow installation
|
|
@@ -68,19 +71,22 @@ class CursorSetup extends BaseIdeSetup {
|
|
|
68
71
|
const agents = await this.scanDirectory(agentsSource, '.md');
|
|
69
72
|
|
|
70
73
|
for (const agent of agents) {
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
// Read the original agent content
|
|
75
|
+
let content = await this.readFile(agent.path);
|
|
76
|
+
|
|
77
|
+
// Replace docs/ references with custom folder name
|
|
78
|
+
content = this.replaceDocsReferences(content);
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
const targetPath = path.join(agileflowAgentsDir, `${agent.name}.md`);
|
|
81
|
+
await this.writeFile(targetPath, content);
|
|
76
82
|
agentCount++;
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
console.log(chalk.green(` ✓ ${this.displayName} configured:`));
|
|
81
|
-
console.log(chalk.dim(` - ${commandCount}
|
|
82
|
-
console.log(chalk.dim(` - ${agentCount}
|
|
83
|
-
console.log(chalk.dim(` - Path: ${path.relative(projectDir,
|
|
87
|
+
console.log(chalk.dim(` - ${commandCount} commands installed`));
|
|
88
|
+
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
|
89
|
+
console.log(chalk.dim(` - Path: ${path.relative(projectDir, agileflowCommandsDir)}`));
|
|
84
90
|
|
|
85
91
|
return {
|
|
86
92
|
success: true,
|
|
@@ -94,95 +100,18 @@ class CursorSetup extends BaseIdeSetup {
|
|
|
94
100
|
* @param {string} projectDir - Project directory
|
|
95
101
|
*/
|
|
96
102
|
async cleanup(projectDir) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
// Remove old .cursor/rules/agileflow (deprecated)
|
|
104
|
+
const oldRulesPath = path.join(projectDir, this.configDir, 'rules', 'agileflow');
|
|
105
|
+
if (await this.exists(oldRulesPath)) {
|
|
106
|
+
await fs.remove(oldRulesPath);
|
|
100
107
|
console.log(chalk.dim(` Removed old AgileFlow rules from ${this.displayName}`));
|
|
101
108
|
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Create an MDC file for a command
|
|
106
|
-
* @param {Object} command - Command info
|
|
107
|
-
* @param {string} agileflowDir - AgileFlow directory
|
|
108
|
-
* @param {string} projectDir - Project directory
|
|
109
|
-
* @returns {Promise<string>} MDC content
|
|
110
|
-
*/
|
|
111
|
-
async createCommandMdc(command, agileflowDir, projectDir) {
|
|
112
|
-
// Read the original command file
|
|
113
|
-
const content = await this.readFile(command.path);
|
|
114
|
-
|
|
115
|
-
// Extract description from frontmatter if present
|
|
116
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
117
|
-
let description = command.name;
|
|
118
|
-
|
|
119
|
-
if (frontmatterMatch) {
|
|
120
|
-
const descMatch = frontmatterMatch[1].match(/description:\s*["']?([^"'\n]+)["']?/);
|
|
121
|
-
if (descMatch) {
|
|
122
|
-
description = descMatch[1];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Create MDC format with metadata
|
|
127
|
-
const relativePath = path.relative(projectDir, command.path);
|
|
128
|
-
|
|
129
|
-
return `---
|
|
130
|
-
description: "${description}"
|
|
131
|
-
globs: []
|
|
132
|
-
alwaysApply: false
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
# AgileFlow: ${command.name}
|
|
136
|
-
|
|
137
|
-
Load and execute the AgileFlow command from: ${relativePath}
|
|
138
109
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Create an MDC file for an agent
|
|
145
|
-
* @param {Object} agent - Agent info
|
|
146
|
-
* @param {string} agileflowDir - AgileFlow directory
|
|
147
|
-
* @param {string} projectDir - Project directory
|
|
148
|
-
* @returns {Promise<string>} MDC content
|
|
149
|
-
*/
|
|
150
|
-
async createAgentMdc(agent, agileflowDir, projectDir) {
|
|
151
|
-
// Read the original agent file
|
|
152
|
-
const content = await this.readFile(agent.path);
|
|
153
|
-
|
|
154
|
-
// Extract metadata from frontmatter
|
|
155
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
156
|
-
let description = agent.name;
|
|
157
|
-
let name = agent.name;
|
|
158
|
-
|
|
159
|
-
if (frontmatterMatch) {
|
|
160
|
-
const descMatch = frontmatterMatch[1].match(/description:\s*["']?([^"'\n]+)["']?/);
|
|
161
|
-
const nameMatch = frontmatterMatch[1].match(/name:\s*["']?([^"'\n]+)["']?/);
|
|
162
|
-
|
|
163
|
-
if (descMatch) description = descMatch[1];
|
|
164
|
-
if (nameMatch) name = nameMatch[1];
|
|
110
|
+
// Remove .cursor/commands/AgileFlow (for re-installation)
|
|
111
|
+
const commandsPath = path.join(projectDir, this.configDir, this.commandsDir, 'AgileFlow');
|
|
112
|
+
if (await this.exists(commandsPath)) {
|
|
113
|
+
await fs.remove(commandsPath);
|
|
165
114
|
}
|
|
166
|
-
|
|
167
|
-
// Create MDC format
|
|
168
|
-
const relativePath = path.relative(projectDir, agent.path);
|
|
169
|
-
|
|
170
|
-
return `---
|
|
171
|
-
description: "${description}"
|
|
172
|
-
globs: []
|
|
173
|
-
alwaysApply: false
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
# AgileFlow Agent: ${name}
|
|
177
|
-
|
|
178
|
-
Activate the AgileFlow agent from: ${relativePath}
|
|
179
|
-
|
|
180
|
-
When this rule is activated:
|
|
181
|
-
1. Read the full agent file
|
|
182
|
-
2. Adopt the agent's persona and communication style
|
|
183
|
-
3. Follow all instructions and use specified tools
|
|
184
|
-
4. Stay in character until given an exit command
|
|
185
|
-
`;
|
|
186
115
|
}
|
|
187
116
|
}
|
|
188
117
|
|
|
@@ -15,6 +15,7 @@ class IdeManager {
|
|
|
15
15
|
constructor() {
|
|
16
16
|
this.handlers = new Map();
|
|
17
17
|
this.agileflowFolder = '.agileflow';
|
|
18
|
+
this.docsFolder = 'docs';
|
|
18
19
|
this.loadHandlers();
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -31,6 +32,19 @@ class IdeManager {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Set the docs folder name for all IDE handlers
|
|
37
|
+
* @param {string} folderName - The docs folder name
|
|
38
|
+
*/
|
|
39
|
+
setDocsFolder(folderName) {
|
|
40
|
+
this.docsFolder = folderName;
|
|
41
|
+
for (const handler of this.handlers.values()) {
|
|
42
|
+
if (typeof handler.setDocsFolder === 'function') {
|
|
43
|
+
handler.setDocsFolder(folderName);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
/**
|
|
35
49
|
* Dynamically load all IDE handlers from directory
|
|
36
50
|
*/
|
|
@@ -47,11 +47,14 @@ class WindsurfSetup extends BaseIdeSetup {
|
|
|
47
47
|
const commands = await this.scanDirectory(commandsSource, '.md');
|
|
48
48
|
|
|
49
49
|
for (const command of commands) {
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
// Read the original command content
|
|
51
|
+
let content = await this.readFile(command.path);
|
|
52
|
+
|
|
53
|
+
// Replace docs/ references with custom folder name
|
|
54
|
+
content = this.replaceDocsReferences(content);
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
const targetPath = path.join(agileflowWorkflowsDir, `${command.name}.md`);
|
|
57
|
+
await this.writeFile(targetPath, content);
|
|
55
58
|
commandCount++;
|
|
56
59
|
}
|
|
57
60
|
}
|
|
@@ -68,11 +71,14 @@ class WindsurfSetup extends BaseIdeSetup {
|
|
|
68
71
|
const agents = await this.scanDirectory(agentsSource, '.md');
|
|
69
72
|
|
|
70
73
|
for (const agent of agents) {
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
// Read the original agent content
|
|
75
|
+
let content = await this.readFile(agent.path);
|
|
76
|
+
|
|
77
|
+
// Replace docs/ references with custom folder name
|
|
78
|
+
content = this.replaceDocsReferences(content);
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
const targetPath = path.join(agileflowAgentsDir, `${agent.name}.md`);
|
|
81
|
+
await this.writeFile(targetPath, content);
|
|
76
82
|
agentCount++;
|
|
77
83
|
}
|
|
78
84
|
}
|
|
@@ -100,93 +106,6 @@ class WindsurfSetup extends BaseIdeSetup {
|
|
|
100
106
|
console.log(chalk.dim(` Removed old AgileFlow workflows from ${this.displayName}`));
|
|
101
107
|
}
|
|
102
108
|
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Create a workflow file for a command
|
|
106
|
-
* @param {Object} command - Command info
|
|
107
|
-
* @param {string} agileflowDir - AgileFlow directory
|
|
108
|
-
* @param {string} projectDir - Project directory
|
|
109
|
-
* @returns {Promise<string>} Workflow content
|
|
110
|
-
*/
|
|
111
|
-
async createCommandWorkflow(command, agileflowDir, projectDir) {
|
|
112
|
-
// Read the original command file
|
|
113
|
-
const content = await this.readFile(command.path);
|
|
114
|
-
|
|
115
|
-
// Extract description from frontmatter if present
|
|
116
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
117
|
-
let description = command.name;
|
|
118
|
-
|
|
119
|
-
if (frontmatterMatch) {
|
|
120
|
-
const descMatch = frontmatterMatch[1].match(/description:\s*["']?([^"'\n]+)["']?/);
|
|
121
|
-
if (descMatch) {
|
|
122
|
-
description = descMatch[1];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Create Windsurf workflow format
|
|
127
|
-
const relativePath = path.relative(projectDir, command.path);
|
|
128
|
-
|
|
129
|
-
return `---
|
|
130
|
-
description: ${description}
|
|
131
|
-
auto_execution_mode: 2
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
# AgileFlow: ${command.name}
|
|
135
|
-
|
|
136
|
-
Load and execute the AgileFlow command.
|
|
137
|
-
|
|
138
|
-
## Instructions
|
|
139
|
-
|
|
140
|
-
Read and follow the full command from: \`${relativePath}\`
|
|
141
|
-
|
|
142
|
-
Execute the command according to its specifications.
|
|
143
|
-
`;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Create a workflow file for an agent
|
|
148
|
-
* @param {Object} agent - Agent info
|
|
149
|
-
* @param {string} agileflowDir - AgileFlow directory
|
|
150
|
-
* @param {string} projectDir - Project directory
|
|
151
|
-
* @returns {Promise<string>} Workflow content
|
|
152
|
-
*/
|
|
153
|
-
async createAgentWorkflow(agent, agileflowDir, projectDir) {
|
|
154
|
-
// Read the original agent file
|
|
155
|
-
const content = await this.readFile(agent.path);
|
|
156
|
-
|
|
157
|
-
// Extract metadata from frontmatter
|
|
158
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
159
|
-
let description = agent.name;
|
|
160
|
-
let name = agent.name;
|
|
161
|
-
|
|
162
|
-
if (frontmatterMatch) {
|
|
163
|
-
const descMatch = frontmatterMatch[1].match(/description:\s*["']?([^"'\n]+)["']?/);
|
|
164
|
-
const nameMatch = frontmatterMatch[1].match(/name:\s*["']?([^"'\n]+)["']?/);
|
|
165
|
-
|
|
166
|
-
if (descMatch) description = descMatch[1];
|
|
167
|
-
if (nameMatch) name = nameMatch[1];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Create Windsurf workflow format
|
|
171
|
-
const relativePath = path.relative(projectDir, agent.path);
|
|
172
|
-
|
|
173
|
-
return `---
|
|
174
|
-
description: ${description}
|
|
175
|
-
auto_execution_mode: 3
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
# AgileFlow Agent: ${name}
|
|
179
|
-
|
|
180
|
-
Activate the AgileFlow agent.
|
|
181
|
-
|
|
182
|
-
## Instructions
|
|
183
|
-
|
|
184
|
-
1. Read the full agent definition from: \`${relativePath}\`
|
|
185
|
-
2. Adopt the agent's persona and communication style
|
|
186
|
-
3. Follow all instructions and use the specified tools
|
|
187
|
-
4. Maintain the agent's character throughout the session
|
|
188
|
-
`;
|
|
189
|
-
}
|
|
190
109
|
}
|
|
191
110
|
|
|
192
111
|
module.exports = { WindsurfSetup };
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgileFlow CLI - Docs Structure Setup
|
|
3
|
+
*
|
|
4
|
+
* Creates the complete AgileFlow docs/ directory structure with README files.
|
|
5
|
+
* Idempotent - checks for existing files and only creates missing ones.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs-extra');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Directory structure to create
|
|
14
|
+
* @param {string} docsFolder - Name of the docs folder (default: "docs")
|
|
15
|
+
*/
|
|
16
|
+
function getDirectoryStructure(docsFolder = 'docs') {
|
|
17
|
+
return [
|
|
18
|
+
`${docsFolder}/00-meta/templates`,
|
|
19
|
+
`${docsFolder}/00-meta/guides`,
|
|
20
|
+
`${docsFolder}/00-meta/scripts`,
|
|
21
|
+
`${docsFolder}/01-brainstorming/ideas`,
|
|
22
|
+
`${docsFolder}/01-brainstorming/sketches`,
|
|
23
|
+
`${docsFolder}/02-practices/prompts/agents`,
|
|
24
|
+
`${docsFolder}/03-decisions`,
|
|
25
|
+
`${docsFolder}/04-architecture`,
|
|
26
|
+
`${docsFolder}/05-epics`,
|
|
27
|
+
`${docsFolder}/06-stories`,
|
|
28
|
+
`${docsFolder}/07-testing/acceptance`,
|
|
29
|
+
`${docsFolder}/07-testing/test-cases`,
|
|
30
|
+
`${docsFolder}/08-project`,
|
|
31
|
+
`${docsFolder}/09-agents/bus`,
|
|
32
|
+
`${docsFolder}/10-research`,
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* README content templates
|
|
38
|
+
* @param {string} docsFolder - Name of the docs folder
|
|
39
|
+
*/
|
|
40
|
+
function getReadmeTemplates(docsFolder = 'docs') {
|
|
41
|
+
return {
|
|
42
|
+
[`${docsFolder}/README.md`]: `# AgileFlow Documentation
|
|
43
|
+
|
|
44
|
+
This directory contains all AgileFlow documentation and project management files.
|
|
45
|
+
|
|
46
|
+
## Directory Structure
|
|
47
|
+
|
|
48
|
+
- **00-meta/**: System metadata, templates, and guides
|
|
49
|
+
- **01-brainstorming/**: Ideas and sketches for features
|
|
50
|
+
- **02-practices/**: Project practices, conventions, and standards
|
|
51
|
+
- **03-decisions/**: Architecture Decision Records (ADRs)
|
|
52
|
+
- **04-architecture/**: System architecture documentation
|
|
53
|
+
- **05-epics/**: Epic definitions and planning
|
|
54
|
+
- **06-stories/**: User stories and implementation details
|
|
55
|
+
- **07-testing/**: Test cases and acceptance criteria
|
|
56
|
+
- **08-project/**: Project management (roadmap, backlog, milestones)
|
|
57
|
+
- **09-agents/**: Agent status tracking and communication
|
|
58
|
+
- **10-research/**: Research notes and findings
|
|
59
|
+
|
|
60
|
+
## Getting Started
|
|
61
|
+
|
|
62
|
+
Use AgileFlow slash commands to work with these directories:
|
|
63
|
+
- \`/AgileFlow:epic\` - Create a new epic
|
|
64
|
+
- \`/AgileFlow:story\` - Create a user story
|
|
65
|
+
- \`/AgileFlow:status\` - Update story status
|
|
66
|
+
- \`/AgileFlow:help\` - See all available commands
|
|
67
|
+
`,
|
|
68
|
+
|
|
69
|
+
[`${docsFolder}/00-meta/README.md`]: `# Meta Documentation
|
|
70
|
+
|
|
71
|
+
System metadata, templates, and guides for AgileFlow.
|
|
72
|
+
|
|
73
|
+
## Contents
|
|
74
|
+
|
|
75
|
+
- **templates/**: Document templates (story, epic, ADR, etc.)
|
|
76
|
+
- **guides/**: How-to guides and best practices
|
|
77
|
+
- **scripts/**: Utility scripts for automation
|
|
78
|
+
- **agileflow-metadata.json**: System configuration and settings
|
|
79
|
+
- **glossary.md**: Project terminology and definitions
|
|
80
|
+
- **conventions.md**: Coding and documentation conventions
|
|
81
|
+
`,
|
|
82
|
+
|
|
83
|
+
[`${docsFolder}/01-brainstorming/README.md`]: `# Brainstorming
|
|
84
|
+
|
|
85
|
+
Ideas and sketches for future features and improvements.
|
|
86
|
+
|
|
87
|
+
## Contents
|
|
88
|
+
|
|
89
|
+
- **ideas/**: Feature ideas and proposals
|
|
90
|
+
- **sketches/**: UI/UX sketches and mockups
|
|
91
|
+
|
|
92
|
+
Use this space to capture thoughts before they become formal stories.
|
|
93
|
+
`,
|
|
94
|
+
|
|
95
|
+
[`${docsFolder}/02-practices/README.md`]: `# Project Practices
|
|
96
|
+
|
|
97
|
+
Project-specific practices, conventions, and standards for YOUR codebase.
|
|
98
|
+
|
|
99
|
+
## Contents
|
|
100
|
+
|
|
101
|
+
- **testing.md**: Testing strategies and patterns
|
|
102
|
+
- **git-branching.md**: Git workflow and branching strategy
|
|
103
|
+
- **releasing.md**: Release process and versioning
|
|
104
|
+
- **security.md**: Security practices and guidelines
|
|
105
|
+
- **ci.md**: CI/CD configuration and workflows
|
|
106
|
+
- **prompts/agents/**: Custom agent prompts for this project
|
|
107
|
+
|
|
108
|
+
**Note**: This is for YOUR project's practices (styling, typography, component patterns, API conventions), NOT AgileFlow system practices (those go in 00-meta/).
|
|
109
|
+
`,
|
|
110
|
+
|
|
111
|
+
[`${docsFolder}/03-decisions/README.md`]: `# Architecture Decision Records (ADRs)
|
|
112
|
+
|
|
113
|
+
Technical decisions, trade-offs, and alternatives considered.
|
|
114
|
+
|
|
115
|
+
## Format
|
|
116
|
+
|
|
117
|
+
Use \`/AgileFlow:adr\` to create new decision records.
|
|
118
|
+
|
|
119
|
+
Each ADR should include:
|
|
120
|
+
- **Context**: What problem are we solving?
|
|
121
|
+
- **Decision**: What did we decide?
|
|
122
|
+
- **Consequences**: What are the impacts?
|
|
123
|
+
`,
|
|
124
|
+
|
|
125
|
+
[`${docsFolder}/04-architecture/README.md`]: `# Architecture Documentation
|
|
126
|
+
|
|
127
|
+
System architecture, data models, API specifications, and technical designs.
|
|
128
|
+
|
|
129
|
+
## Contents
|
|
130
|
+
|
|
131
|
+
- Data models and schemas
|
|
132
|
+
- API specifications
|
|
133
|
+
- Component architecture
|
|
134
|
+
- File/directory structure
|
|
135
|
+
- Testing architecture
|
|
136
|
+
- Technical constraints
|
|
137
|
+
|
|
138
|
+
Use this documentation to maintain architectural context for development.
|
|
139
|
+
`,
|
|
140
|
+
|
|
141
|
+
[`${docsFolder}/05-epics/README.md`]: `# Epics
|
|
142
|
+
|
|
143
|
+
Large features broken down into user stories.
|
|
144
|
+
|
|
145
|
+
## Format
|
|
146
|
+
|
|
147
|
+
Use \`/AgileFlow:epic\` to create new epics.
|
|
148
|
+
|
|
149
|
+
Each epic includes:
|
|
150
|
+
- Epic ID (EP-XXXX)
|
|
151
|
+
- Description and goals
|
|
152
|
+
- Related stories
|
|
153
|
+
- Milestones and timeline
|
|
154
|
+
`,
|
|
155
|
+
|
|
156
|
+
[`${docsFolder}/06-stories/README.md`]: `# User Stories
|
|
157
|
+
|
|
158
|
+
Implementation tasks with acceptance criteria and technical details.
|
|
159
|
+
|
|
160
|
+
## Format
|
|
161
|
+
|
|
162
|
+
Use \`/AgileFlow:story\` to create new stories.
|
|
163
|
+
|
|
164
|
+
Each story includes:
|
|
165
|
+
- Story ID (US-XXXX)
|
|
166
|
+
- Description
|
|
167
|
+
- Acceptance criteria (Given/When/Then)
|
|
168
|
+
- Architecture context
|
|
169
|
+
- Testing strategy
|
|
170
|
+
- Implementation notes
|
|
171
|
+
`,
|
|
172
|
+
|
|
173
|
+
[`${docsFolder}/07-testing/README.md`]: `# Testing Documentation
|
|
174
|
+
|
|
175
|
+
Test cases, acceptance criteria, and testing strategies.
|
|
176
|
+
|
|
177
|
+
## Contents
|
|
178
|
+
|
|
179
|
+
- **acceptance/**: Acceptance test definitions
|
|
180
|
+
- **test-cases/**: Detailed test case documentation
|
|
181
|
+
|
|
182
|
+
Use \`/AgileFlow:tests\` to set up testing infrastructure.
|
|
183
|
+
`,
|
|
184
|
+
|
|
185
|
+
[`${docsFolder}/08-project/README.md`]: `# Project Management
|
|
186
|
+
|
|
187
|
+
Project-level planning and tracking.
|
|
188
|
+
|
|
189
|
+
## Contents
|
|
190
|
+
|
|
191
|
+
- **roadmap.md**: Product roadmap and vision
|
|
192
|
+
- **backlog.md**: Prioritized backlog
|
|
193
|
+
- **milestones.md**: Release milestones
|
|
194
|
+
- **risks.md**: Project risks and mitigation strategies
|
|
195
|
+
`,
|
|
196
|
+
|
|
197
|
+
[`${docsFolder}/09-agents/README.md`]: `# Agent Status Tracking
|
|
198
|
+
|
|
199
|
+
Real-time status of stories being worked on by agents.
|
|
200
|
+
|
|
201
|
+
## Files
|
|
202
|
+
|
|
203
|
+
- **status.json**: Active stories and recent completions
|
|
204
|
+
- **status-archive.json**: Archived completed stories
|
|
205
|
+
- **bus/log.jsonl**: Agent communication log
|
|
206
|
+
|
|
207
|
+
Use \`/AgileFlow:status\` to update story status.
|
|
208
|
+
`,
|
|
209
|
+
|
|
210
|
+
[`${docsFolder}/10-research/README.md`]: `# Research Notes
|
|
211
|
+
|
|
212
|
+
Research findings, investigations, and technical explorations.
|
|
213
|
+
|
|
214
|
+
## Format
|
|
215
|
+
|
|
216
|
+
Use \`/AgileFlow:research\` to create new research notes.
|
|
217
|
+
|
|
218
|
+
| Date | Topic | Path | Summary |
|
|
219
|
+
|------|-------|------|---------|
|
|
220
|
+
| | | | |
|
|
221
|
+
`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create docs structure with README files
|
|
227
|
+
* @param {string} targetDir - Target directory for installation
|
|
228
|
+
* @param {string} docsFolder - Name of the docs folder (default: "docs")
|
|
229
|
+
* @returns {Promise<Object>} Result object with counts
|
|
230
|
+
*/
|
|
231
|
+
async function createDocsStructure(targetDir, docsFolder = 'docs') {
|
|
232
|
+
const result = {
|
|
233
|
+
success: false,
|
|
234
|
+
counts: {
|
|
235
|
+
directoriesCreated: 0,
|
|
236
|
+
filesCreated: 0,
|
|
237
|
+
filesSkipped: 0,
|
|
238
|
+
},
|
|
239
|
+
errors: [],
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
console.log(chalk.cyan(`\nCreating ${docsFolder}/ structure...`));
|
|
244
|
+
|
|
245
|
+
// Create directories
|
|
246
|
+
const directories = getDirectoryStructure(docsFolder);
|
|
247
|
+
for (const dir of directories) {
|
|
248
|
+
const fullPath = path.join(targetDir, dir);
|
|
249
|
+
if (!fs.existsSync(fullPath)) {
|
|
250
|
+
await fs.ensureDir(fullPath);
|
|
251
|
+
result.counts.directoriesCreated++;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Create README files
|
|
256
|
+
const readmes = getReadmeTemplates(docsFolder);
|
|
257
|
+
for (const [filePath, content] of Object.entries(readmes)) {
|
|
258
|
+
const fullPath = path.join(targetDir, filePath);
|
|
259
|
+
if (!fs.existsSync(fullPath)) {
|
|
260
|
+
await fs.writeFile(fullPath, content, 'utf8');
|
|
261
|
+
result.counts.filesCreated++;
|
|
262
|
+
console.log(chalk.green(` ✓ Created ${filePath}`));
|
|
263
|
+
} else {
|
|
264
|
+
result.counts.filesSkipped++;
|
|
265
|
+
console.log(chalk.dim(` ⊘ Skipped ${filePath} (already exists)`));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Create agileflow-metadata.json
|
|
270
|
+
const metadataPath = path.join(targetDir, docsFolder, '00-meta', 'agileflow-metadata.json');
|
|
271
|
+
if (!fs.existsSync(metadataPath)) {
|
|
272
|
+
const metadata = {
|
|
273
|
+
version: '2.30.1',
|
|
274
|
+
created: new Date().toISOString(),
|
|
275
|
+
updated: new Date().toISOString(),
|
|
276
|
+
docsFolder: docsFolder,
|
|
277
|
+
archival: {
|
|
278
|
+
threshold_days: 30,
|
|
279
|
+
enabled: true,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf8');
|
|
283
|
+
result.counts.filesCreated++;
|
|
284
|
+
console.log(chalk.green(` ✓ Created ${docsFolder}/00-meta/agileflow-metadata.json`));
|
|
285
|
+
} else {
|
|
286
|
+
// Update existing metadata with docsFolder if missing
|
|
287
|
+
try {
|
|
288
|
+
const existing = JSON.parse(await fs.readFile(metadataPath, 'utf8'));
|
|
289
|
+
if (!existing.docsFolder) {
|
|
290
|
+
existing.docsFolder = docsFolder;
|
|
291
|
+
existing.updated = new Date().toISOString();
|
|
292
|
+
await fs.writeFile(metadataPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
293
|
+
console.log(chalk.yellow(` ↻ Updated ${docsFolder}/00-meta/agileflow-metadata.json (added docsFolder)`));
|
|
294
|
+
}
|
|
295
|
+
} catch (err) {
|
|
296
|
+
console.log(chalk.yellow(` ⚠ Could not update metadata: ${err.message}`));
|
|
297
|
+
}
|
|
298
|
+
result.counts.filesSkipped++;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Create status.json
|
|
302
|
+
const statusPath = path.join(targetDir, docsFolder, '09-agents', 'status.json');
|
|
303
|
+
if (!fs.existsSync(statusPath)) {
|
|
304
|
+
const status = {
|
|
305
|
+
updated: new Date().toISOString(),
|
|
306
|
+
stories: {},
|
|
307
|
+
};
|
|
308
|
+
await fs.writeFile(statusPath, JSON.stringify(status, null, 2), 'utf8');
|
|
309
|
+
result.counts.filesCreated++;
|
|
310
|
+
console.log(chalk.green(` ✓ Created ${docsFolder}/09-agents/status.json`));
|
|
311
|
+
} else {
|
|
312
|
+
result.counts.filesSkipped++;
|
|
313
|
+
console.log(chalk.dim(` ⊘ Skipped ${docsFolder}/09-agents/status.json (already exists)`));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Create empty bus log
|
|
317
|
+
const busLogPath = path.join(targetDir, docsFolder, '09-agents', 'bus', 'log.jsonl');
|
|
318
|
+
if (!fs.existsSync(busLogPath)) {
|
|
319
|
+
await fs.writeFile(busLogPath, '', 'utf8');
|
|
320
|
+
result.counts.filesCreated++;
|
|
321
|
+
console.log(chalk.green(` ✓ Created ${docsFolder}/09-agents/bus/log.jsonl`));
|
|
322
|
+
} else {
|
|
323
|
+
result.counts.filesSkipped++;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Create basic practice files
|
|
327
|
+
const practiceFiles = {
|
|
328
|
+
[`${docsFolder}/02-practices/testing.md`]: `# Testing Practices
|
|
329
|
+
|
|
330
|
+
Document your testing strategies and patterns here.
|
|
331
|
+
`,
|
|
332
|
+
[`${docsFolder}/02-practices/git-branching.md`]: `# Git Branching Strategy
|
|
333
|
+
|
|
334
|
+
Document your git workflow and branching strategy here.
|
|
335
|
+
`,
|
|
336
|
+
[`${docsFolder}/02-practices/releasing.md`]: `# Release Process
|
|
337
|
+
|
|
338
|
+
Document your release process and versioning strategy here.
|
|
339
|
+
`,
|
|
340
|
+
[`${docsFolder}/02-practices/security.md`]: `# Security Practices
|
|
341
|
+
|
|
342
|
+
Document your security guidelines and practices here.
|
|
343
|
+
`,
|
|
344
|
+
[`${docsFolder}/02-practices/ci.md`]: `# CI/CD Configuration
|
|
345
|
+
|
|
346
|
+
Document your CI/CD workflows and configuration here.
|
|
347
|
+
`,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
for (const [filePath, content] of Object.entries(practiceFiles)) {
|
|
351
|
+
const fullPath = path.join(targetDir, filePath);
|
|
352
|
+
if (!fs.existsSync(fullPath)) {
|
|
353
|
+
await fs.writeFile(fullPath, content, 'utf8');
|
|
354
|
+
result.counts.filesCreated++;
|
|
355
|
+
console.log(chalk.green(` ✓ Created ${filePath}`));
|
|
356
|
+
} else {
|
|
357
|
+
result.counts.filesSkipped++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Create .gitignore additions for docs folder
|
|
362
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
363
|
+
const gitignoreEntries = [
|
|
364
|
+
'.env',
|
|
365
|
+
'.env.*',
|
|
366
|
+
'!.env.example',
|
|
367
|
+
'.DS_Store',
|
|
368
|
+
'node_modules/',
|
|
369
|
+
'dist/',
|
|
370
|
+
'build/',
|
|
371
|
+
'coverage/',
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
if (fs.existsSync(gitignorePath)) {
|
|
375
|
+
const existingGitignore = await fs.readFile(gitignorePath, 'utf8');
|
|
376
|
+
const newEntries = gitignoreEntries.filter(entry => !existingGitignore.includes(entry));
|
|
377
|
+
if (newEntries.length > 0) {
|
|
378
|
+
await fs.appendFile(gitignorePath, '\n' + newEntries.join('\n') + '\n', 'utf8');
|
|
379
|
+
console.log(chalk.yellow(` ↻ Updated .gitignore with ${newEntries.length} entries`));
|
|
380
|
+
}
|
|
381
|
+
} else {
|
|
382
|
+
await fs.writeFile(gitignorePath, gitignoreEntries.join('\n') + '\n', 'utf8');
|
|
383
|
+
result.counts.filesCreated++;
|
|
384
|
+
console.log(chalk.green(` ✓ Created .gitignore`));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
result.success = true;
|
|
388
|
+
console.log(
|
|
389
|
+
chalk.green(
|
|
390
|
+
`\n✨ Docs structure created: ${result.counts.directoriesCreated} directories, ${result.counts.filesCreated} files`
|
|
391
|
+
)
|
|
392
|
+
);
|
|
393
|
+
if (result.counts.filesSkipped > 0) {
|
|
394
|
+
console.log(chalk.dim(` ${result.counts.filesSkipped} files skipped (already exist)`));
|
|
395
|
+
}
|
|
396
|
+
} catch (err) {
|
|
397
|
+
result.errors.push(err.message);
|
|
398
|
+
console.error(chalk.red(`\n✗ Failed to create docs structure: ${err.message}`));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get the docs folder name from metadata or default
|
|
406
|
+
* @param {string} targetDir - Target directory
|
|
407
|
+
* @returns {Promise<string>} Docs folder name
|
|
408
|
+
*/
|
|
409
|
+
async function getDocsFolderName(targetDir) {
|
|
410
|
+
try {
|
|
411
|
+
const metadataPath = path.join(targetDir, 'docs', '00-meta', 'agileflow-metadata.json');
|
|
412
|
+
if (fs.existsSync(metadataPath)) {
|
|
413
|
+
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf8'));
|
|
414
|
+
return metadata.docsFolder || 'docs';
|
|
415
|
+
}
|
|
416
|
+
} catch (err) {
|
|
417
|
+
// Ignore errors, return default
|
|
418
|
+
}
|
|
419
|
+
return 'docs';
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
module.exports = {
|
|
423
|
+
createDocsStructure,
|
|
424
|
+
getDocsFolderName,
|
|
425
|
+
};
|
package/tools/cli/lib/ui.js
CHANGED
|
@@ -18,12 +18,12 @@ const packageJson = require(packageJsonPath);
|
|
|
18
18
|
*/
|
|
19
19
|
function displayLogo() {
|
|
20
20
|
const logo = `
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/
|
|
25
|
-
\\
|
|
26
|
-
|
|
21
|
+
_ _ _ _____ _
|
|
22
|
+
/ \\ __ _(_) | ___| ___| | _____ __
|
|
23
|
+
/ _ \\ / _\` | | |/ _ \\ |_ | |/ _ \\ \\ /\\ / /
|
|
24
|
+
/ ___ \\ (_| | | | __/ _| | | (_) \\ V V /
|
|
25
|
+
/_/ \\_\\__, |_|_|\\___|_| |_|\\___/ \\_/\\_/
|
|
26
|
+
|___/ `;
|
|
27
27
|
console.log(chalk.cyan(logo));
|
|
28
28
|
console.log(chalk.dim(` AgileFlow v${packageJson.version} - AI-Driven Agile Development\n`));
|
|
29
29
|
}
|
|
@@ -152,6 +152,18 @@ async function promptInstall() {
|
|
|
152
152
|
return true;
|
|
153
153
|
},
|
|
154
154
|
},
|
|
155
|
+
{
|
|
156
|
+
type: 'input',
|
|
157
|
+
name: 'docsFolder',
|
|
158
|
+
message: 'Documentation folder name:',
|
|
159
|
+
default: 'docs',
|
|
160
|
+
validate: (input) => {
|
|
161
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(input)) {
|
|
162
|
+
return 'Folder name can only contain letters, numbers, dots, underscores, and hyphens';
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
},
|
|
166
|
+
},
|
|
155
167
|
]);
|
|
156
168
|
|
|
157
169
|
return {
|
|
@@ -159,6 +171,7 @@ async function promptInstall() {
|
|
|
159
171
|
ides: answers.ides,
|
|
160
172
|
userName: answers.userName,
|
|
161
173
|
agileflowFolder: answers.agileflowFolder,
|
|
174
|
+
docsFolder: answers.docsFolder,
|
|
162
175
|
};
|
|
163
176
|
}
|
|
164
177
|
|