specsmd 0.0.1

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 (83) hide show
  1. package/README.md +300 -0
  2. package/bin/cli.js +21 -0
  3. package/flows/aidlc/README.md +372 -0
  4. package/flows/aidlc/agents/construction-agent.md +81 -0
  5. package/flows/aidlc/agents/inception-agent.md +95 -0
  6. package/flows/aidlc/agents/master-agent.md +61 -0
  7. package/flows/aidlc/agents/operations-agent.md +89 -0
  8. package/flows/aidlc/commands/construction-agent.md +63 -0
  9. package/flows/aidlc/commands/inception-agent.md +55 -0
  10. package/flows/aidlc/commands/master-agent.md +47 -0
  11. package/flows/aidlc/commands/operations-agent.md +77 -0
  12. package/flows/aidlc/context-config.yaml +41 -0
  13. package/flows/aidlc/memory-bank.yaml +104 -0
  14. package/flows/aidlc/quick-start.md +315 -0
  15. package/flows/aidlc/skills/construction/bolt-list.md +163 -0
  16. package/flows/aidlc/skills/construction/bolt-replan.md +343 -0
  17. package/flows/aidlc/skills/construction/bolt-start.md +289 -0
  18. package/flows/aidlc/skills/construction/bolt-status.md +185 -0
  19. package/flows/aidlc/skills/construction/navigator.md +196 -0
  20. package/flows/aidlc/skills/inception/bolt-plan.md +338 -0
  21. package/flows/aidlc/skills/inception/context.md +171 -0
  22. package/flows/aidlc/skills/inception/intent-create.md +211 -0
  23. package/flows/aidlc/skills/inception/intent-list.md +124 -0
  24. package/flows/aidlc/skills/inception/navigator.md +207 -0
  25. package/flows/aidlc/skills/inception/requirements.md +227 -0
  26. package/flows/aidlc/skills/inception/review.md +248 -0
  27. package/flows/aidlc/skills/inception/story-create.md +304 -0
  28. package/flows/aidlc/skills/inception/units.md +271 -0
  29. package/flows/aidlc/skills/master/analyze-context.md +132 -0
  30. package/flows/aidlc/skills/master/answer-question.md +141 -0
  31. package/flows/aidlc/skills/master/explain-flow.md +146 -0
  32. package/flows/aidlc/skills/master/project-init.md +281 -0
  33. package/flows/aidlc/skills/master/route-request.md +126 -0
  34. package/flows/aidlc/skills/operations/build.md +237 -0
  35. package/flows/aidlc/skills/operations/deploy.md +259 -0
  36. package/flows/aidlc/skills/operations/monitor.md +265 -0
  37. package/flows/aidlc/skills/operations/navigator.md +209 -0
  38. package/flows/aidlc/skills/operations/verify.md +224 -0
  39. package/flows/aidlc/templates/construction/bolt-template.md +193 -0
  40. package/flows/aidlc/templates/construction/bolt-types/bdd-construction-bolt.md +250 -0
  41. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +49 -0
  42. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +55 -0
  43. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +67 -0
  44. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +62 -0
  45. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt.md +528 -0
  46. package/flows/aidlc/templates/construction/bolt-types/simple-construction-bolt.md +273 -0
  47. package/flows/aidlc/templates/construction/bolt-types/spike-bolt.md +240 -0
  48. package/flows/aidlc/templates/construction/bolt-types/tdd-construction-bolt.md +259 -0
  49. package/flows/aidlc/templates/construction/construction-log-template.md +129 -0
  50. package/flows/aidlc/templates/construction/standards/coding-standards.md +29 -0
  51. package/flows/aidlc/templates/construction/standards/system-architecture.md +22 -0
  52. package/flows/aidlc/templates/construction/standards/tech-stack.md +19 -0
  53. package/flows/aidlc/templates/inception/inception-log-template.md +134 -0
  54. package/flows/aidlc/templates/inception/project/README.md +55 -0
  55. package/flows/aidlc/templates/inception/requirements-template.md +144 -0
  56. package/flows/aidlc/templates/inception/stories-template.md +38 -0
  57. package/flows/aidlc/templates/inception/story-template.md +147 -0
  58. package/flows/aidlc/templates/inception/system-context-template.md +29 -0
  59. package/flows/aidlc/templates/inception/unit-brief-template.md +177 -0
  60. package/flows/aidlc/templates/inception/units-template.md +52 -0
  61. package/flows/aidlc/templates/standards/catalog.yaml +345 -0
  62. package/flows/aidlc/templates/standards/coding-standards.guide.md +553 -0
  63. package/flows/aidlc/templates/standards/data-stack.guide.md +162 -0
  64. package/flows/aidlc/templates/standards/tech-stack.guide.md +280 -0
  65. package/lib/InstallerFactory.js +36 -0
  66. package/lib/cli-utils.js +372 -0
  67. package/lib/constants.js +31 -0
  68. package/lib/installer.js +314 -0
  69. package/lib/installers/AntigravityInstaller.js +22 -0
  70. package/lib/installers/ClaudeInstaller.js +85 -0
  71. package/lib/installers/ClineInstaller.js +21 -0
  72. package/lib/installers/CodexInstaller.js +21 -0
  73. package/lib/installers/CopilotInstaller.js +113 -0
  74. package/lib/installers/CursorInstaller.js +63 -0
  75. package/lib/installers/GeminiInstaller.js +75 -0
  76. package/lib/installers/KiroInstaller.js +22 -0
  77. package/lib/installers/OpenCodeInstaller.js +22 -0
  78. package/lib/installers/RooInstaller.js +22 -0
  79. package/lib/installers/ToolInstaller.js +73 -0
  80. package/lib/installers/WindsurfInstaller.js +76 -0
  81. package/lib/markdown-validator.ts +175 -0
  82. package/lib/yaml-validator.ts +99 -0
  83. package/package.json +65 -0
@@ -0,0 +1,31 @@
1
+
2
+ // Theme Colors (Terracotta/Orange inspired by Claude Code)
3
+ const THEME_COLORS = {
4
+ primary: '#D97757', // Terracotta
5
+ secondary: '#ff9966', // Light orange
6
+ success: '#22c55e', // Green
7
+ error: '#ef4444', // Red
8
+ warning: '#f59e0b', // Amber
9
+ info: '#3b82f6', // Blue
10
+ dim: '#6b7280' // Gray
11
+ };
12
+
13
+ const FLOWS = {
14
+ aidlc: {
15
+ name: 'AI-DLC',
16
+ description: 'AI-Driven Development Life Cycle - Best for greenfield projects with AI-native development',
17
+ path: 'aidlc'
18
+ },
19
+ agile: {
20
+ name: 'Agile',
21
+ description: 'Sprint-based Agile development - Best for iterative development with changing requirements',
22
+ path: 'agile',
23
+ disabled: true, // Not implemented yet
24
+ message: '(Coming soon)'
25
+ }
26
+ };
27
+
28
+ module.exports = {
29
+ THEME_COLORS,
30
+ FLOWS
31
+ };
@@ -0,0 +1,314 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const prompts = require('prompts');
4
+ const yaml = require('js-yaml');
5
+ const CLIUtils = require('./cli-utils');
6
+ const InstallerFactory = require('./InstallerFactory');
7
+ const { FLOWS } = require('./constants');
8
+
9
+ // Use theme from CLIUtils for consistent styling
10
+ const { theme } = CLIUtils;
11
+
12
+ async function detectTools() {
13
+ const detected = [];
14
+ const installers = InstallerFactory.getInstallers();
15
+
16
+ for (const installer of installers) {
17
+ if (await installer.detect()) {
18
+ detected.push(installer.key);
19
+ }
20
+ }
21
+ return detected;
22
+ }
23
+
24
+ async function install() {
25
+ await CLIUtils.displayLogo();
26
+ CLIUtils.displayHeader('Installation', '');
27
+
28
+ // Step 1: Detect agentic coding tools
29
+ const detectedToolKeys = await detectTools();
30
+ const installers = InstallerFactory.getInstallers();
31
+ const detectedNames = installers
32
+ .filter(i => detectedToolKeys.includes(i.key))
33
+ .map(i => i.name);
34
+
35
+ CLIUtils.displayStep(1, 4, 'Detecting agentic coding tools...');
36
+ if (detectedNames.length > 0) {
37
+ CLIUtils.displayStatus('', `Detected: ${detectedNames.join(', ')}`, 'success');
38
+ } else {
39
+ CLIUtils.displayStatus('', 'No agentic coding tools detected', 'warning');
40
+ }
41
+ console.log('');
42
+
43
+ // Step 2: Select tools
44
+ CLIUtils.displayStep(2, 4, 'Select target tools');
45
+
46
+ // Build choices with descriptive formatting
47
+ const toolChoices = installers.map(installer => ({
48
+ title: installer.name + (detectedToolKeys.includes(installer.key) ? theme.dim(' (detected)') : ''),
49
+ value: installer.key,
50
+ selected: detectedToolKeys.includes(installer.key)
51
+ }));
52
+
53
+ console.log(theme.dim(' [Space] toggle [Enter] confirm [a] toggle all'));
54
+ console.log(theme.dim(` ${theme.success('[x]')} = selected ${theme.dim('[ ]')} = not selected\n`));
55
+
56
+ const { selectedToolKeys } = await prompts({
57
+ type: 'multiselect',
58
+ name: 'selectedToolKeys',
59
+ message: 'Choose tools:',
60
+ choices: toolChoices,
61
+ min: 1,
62
+ instructions: false
63
+ });
64
+
65
+ if (!selectedToolKeys || selectedToolKeys.length === 0) {
66
+ CLIUtils.displayError('Installation cancelled - no tools selected');
67
+ process.exit(1);
68
+ }
69
+
70
+ // Step 3: Select Flow
71
+ console.log('');
72
+ CLIUtils.displayStep(3, 4, 'Select SDLC flow');
73
+ const flowChoices = Object.entries(FLOWS).map(([key, flow]) => ({
74
+ title: `${flow.name} - ${flow.description}${flow.message || ''}`,
75
+ value: key,
76
+ disabled: flow.disabled
77
+ }));
78
+
79
+ const { selectedFlow } = await prompts({
80
+ type: 'select',
81
+ name: 'selectedFlow',
82
+ message: 'Which SDLC flow would you like to install?',
83
+ choices: flowChoices
84
+ });
85
+
86
+ if (!selectedFlow) {
87
+ CLIUtils.displayError('Installation cancelled');
88
+ process.exit(1);
89
+ }
90
+
91
+ // Step 4: Install flow files
92
+ console.log('');
93
+ CLIUtils.displayStep(4, 4, `Installing ${FLOWS[selectedFlow].name} flow...`);
94
+
95
+ try {
96
+ await installFlow(selectedFlow, selectedToolKeys);
97
+
98
+ CLIUtils.displaySuccess(`${FLOWS[selectedFlow].name} flow installed successfully!`, 'Installation Complete');
99
+
100
+ // Get selected tool names for next steps message
101
+ const selectedToolNames = installers
102
+ .filter(i => selectedToolKeys.includes(i.key))
103
+ .map(i => i.name);
104
+
105
+ const nextSteps = [
106
+ `Read .specsmd/${selectedFlow}/quick-start.md for getting started`,
107
+ `Open ${selectedToolNames.join(' or ')} and run /specsmd-master-agent`
108
+ ];
109
+ CLIUtils.displayNextSteps(nextSteps);
110
+ } catch (error) {
111
+ CLIUtils.displayError(`Installation failed: ${error.message}`);
112
+ console.log(theme.dim('\nRolling back changes...'));
113
+ await rollback(selectedFlow, selectedToolKeys);
114
+ CLIUtils.displayStatus('', 'Installation rolled back', 'warning');
115
+ process.exit(1);
116
+ }
117
+ }
118
+
119
+ async function installFlow(flowKey, toolKeys) {
120
+ const flowPath = path.join(__dirname, '..', 'flows', FLOWS[flowKey].path);
121
+
122
+ // Step 1: Install commands for each tool
123
+ // Pass empty config since config.yaml is removed
124
+ const dummyConfig = {};
125
+ for (const toolKey of toolKeys) {
126
+ const installer = InstallerFactory.getInstaller(toolKey);
127
+ if (installer) {
128
+ await installer.installCommands(flowPath, dummyConfig);
129
+ }
130
+ }
131
+
132
+ // Step 2: Install shared flow config
133
+ const specsmdDir = '.specsmd';
134
+ const targetFlowDir = path.join(specsmdDir, flowKey);
135
+
136
+ console.log(theme.dim(` Installing flow resources to ${targetFlowDir}/...`));
137
+ await fs.ensureDir(targetFlowDir);
138
+
139
+ // Copy agents
140
+ await fs.copy(path.join(flowPath, 'agents'), path.join(targetFlowDir, 'agents'));
141
+
142
+ // Copy internal agent capabilities (legacy check)
143
+ if (await fs.pathExists(path.join(flowPath, 'agent-capabilities'))) {
144
+ await fs.copy(path.join(flowPath, 'agent-capabilities'), path.join(targetFlowDir, 'agent-capabilities'));
145
+ }
146
+
147
+ // Copy bolt-types if they exist (legacy check)
148
+ if (await fs.pathExists(path.join(flowPath, 'bolt-types'))) {
149
+ await fs.copy(path.join(flowPath, 'bolt-types'), path.join(targetFlowDir, 'bolt-types'));
150
+ }
151
+
152
+ // Copy skills, templates, shared (now at flow root level, not nested in .specsmd)
153
+ if (await fs.pathExists(path.join(flowPath, 'skills'))) {
154
+ await fs.copy(path.join(flowPath, 'skills'), path.join(targetFlowDir, 'skills'));
155
+ }
156
+ if (await fs.pathExists(path.join(flowPath, 'templates'))) {
157
+ await fs.copy(path.join(flowPath, 'templates'), path.join(targetFlowDir, 'templates'));
158
+ }
159
+ if (await fs.pathExists(path.join(flowPath, 'shared'))) {
160
+ await fs.copy(path.join(flowPath, 'shared'), path.join(targetFlowDir, 'shared'));
161
+ }
162
+
163
+ // Copy config files
164
+ if (await fs.pathExists(path.join(flowPath, 'memory-bank.yaml'))) {
165
+ await fs.copy(path.join(flowPath, 'memory-bank.yaml'), path.join(targetFlowDir, 'memory-bank.yaml'));
166
+ }
167
+ if (await fs.pathExists(path.join(flowPath, 'context-config.yaml'))) {
168
+ await fs.copy(path.join(flowPath, 'context-config.yaml'), path.join(targetFlowDir, 'context-config.yaml'));
169
+ }
170
+ if (await fs.pathExists(path.join(flowPath, 'quick-start.md'))) {
171
+ await fs.copy(path.join(flowPath, 'quick-start.md'), path.join(targetFlowDir, 'quick-start.md'));
172
+ }
173
+
174
+ // Copy docs
175
+ await fs.copy(path.join(flowPath, 'README.md'), path.join(targetFlowDir, 'README.md'));
176
+
177
+ if (await fs.pathExists(path.join(flowPath, 'constitution.md'))) {
178
+ await fs.copy(path.join(flowPath, 'constitution.md'), path.join(targetFlowDir, 'constitution.md'));
179
+ }
180
+
181
+ CLIUtils.displayStatus('', 'Installed flow resources', 'success');
182
+
183
+ // NOTE: memory-bank/ is NOT created during installation
184
+ // It will be created when user runs project-init
185
+ // This allows us to detect if project is initialized by checking for memory-bank/standards/
186
+
187
+ // Step 3: Create manifest
188
+ const manifest = {
189
+ flow: flowKey,
190
+ version: require('../package.json').version,
191
+ installed_at: new Date().toISOString(),
192
+ tools: toolKeys
193
+ };
194
+
195
+ await fs.writeFile(
196
+ path.join(specsmdDir, 'manifest.yaml'),
197
+ yaml.dump(manifest),
198
+ 'utf8'
199
+ );
200
+
201
+ CLIUtils.displayStatus('', 'Created installation manifest', 'success');
202
+ }
203
+
204
+ async function rollback(flowKey, toolKeys) {
205
+ // Remove tool command files
206
+ for (const toolKey of toolKeys) {
207
+ const installer = InstallerFactory.getInstaller(toolKey);
208
+ if (installer) {
209
+ const commandsDir = installer.commandsDir;
210
+ if (await fs.pathExists(commandsDir)) {
211
+ const files = await fs.readdir(commandsDir);
212
+ for (const file of files) {
213
+ if (file.startsWith('specsmd-')) {
214
+ await fs.remove(path.join(commandsDir, file));
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ // Remove .specsmd directory
222
+ if (await fs.pathExists('.specsmd')) {
223
+ await fs.remove('.specsmd');
224
+ }
225
+ }
226
+
227
+ async function uninstall() {
228
+ CLIUtils.displayHeader('Uninstall', '');
229
+
230
+ // Check if specsmd is installed
231
+ if (!await fs.pathExists('.specsmd/manifest.yaml')) {
232
+ CLIUtils.displayWarning('specsmd is not installed in this project');
233
+ return;
234
+ }
235
+
236
+ // Read manifest
237
+ const manifestContent = await fs.readFile('.specsmd/manifest.yaml', 'utf8');
238
+ const manifest = yaml.load(manifestContent);
239
+
240
+ const installers = InstallerFactory.getInstallers();
241
+ // Support both old 'ides' key and new 'tools' key for backward compatibility
242
+ const installedToolKeys = manifest.tools || manifest.ides || [];
243
+ const installedNames = installers
244
+ .filter(i => installedToolKeys.includes(i.key))
245
+ .map(i => i.name);
246
+
247
+ console.log(theme.dim(`Found installation: ${FLOWS[manifest.flow].name} flow`));
248
+ console.log(theme.dim(`Installed for: ${installedNames.join(', ')}\n`));
249
+
250
+ const { confirm } = await prompts({
251
+ type: 'confirm',
252
+ name: 'confirm',
253
+ message: 'Are you sure you want to uninstall specsmd?',
254
+ initial: false
255
+ });
256
+
257
+ if (!confirm) {
258
+ CLIUtils.displayStatus('', 'Uninstall cancelled', 'warning');
259
+ return;
260
+ }
261
+
262
+ // Ask about memory bank
263
+ const { keepMemoryBank } = await prompts({
264
+ type: 'confirm',
265
+ name: 'keepMemoryBank',
266
+ message: 'Keep memory-bank folder? (Contains your project artifacts)',
267
+ initial: true
268
+ });
269
+
270
+ console.log(theme.primary('\nUninstalling...\n'));
271
+
272
+ try {
273
+ // Remove command files
274
+ for (const toolKey of installedToolKeys) {
275
+ const installer = InstallerFactory.getInstaller(toolKey);
276
+ if (installer) {
277
+ const commandsDir = installer.commandsDir;
278
+ if (await fs.pathExists(commandsDir)) {
279
+ console.log(theme.dim(` Removing commands from ${commandsDir}/...`));
280
+ const files = await fs.readdir(commandsDir);
281
+ for (const file of files) {
282
+ if (file.startsWith('specsmd-')) {
283
+ await fs.remove(path.join(commandsDir, file));
284
+ }
285
+ }
286
+ }
287
+ }
288
+ }
289
+
290
+ // Remove .specsmd directory
291
+ console.log(theme.dim(' Removing .specsmd/...'));
292
+ await fs.remove('.specsmd');
293
+
294
+ // Optionally remove memory-bank
295
+ if (!keepMemoryBank && await fs.pathExists('memory-bank')) {
296
+ console.log(theme.dim(' Removing memory-bank/...'));
297
+ await fs.remove('memory-bank');
298
+ }
299
+
300
+ CLIUtils.displaySuccess('Uninstall complete!', 'Complete');
301
+ if (keepMemoryBank) {
302
+ console.log(theme.dim('memory-bank/ was preserved\n'));
303
+ }
304
+ } catch (error) {
305
+ CLIUtils.displayError(`Uninstall failed: ${error.message}`);
306
+ process.exit(1);
307
+ }
308
+ }
309
+
310
+ module.exports = {
311
+ install,
312
+ uninstall
313
+ };
314
+
@@ -0,0 +1,22 @@
1
+ const ToolInstaller = require('./ToolInstaller');
2
+ const path = require('path');
3
+
4
+ class AntigravityInstaller extends ToolInstaller {
5
+ get key() {
6
+ return 'antigravity';
7
+ }
8
+
9
+ get name() {
10
+ return 'Google Antigravity';
11
+ }
12
+
13
+ get commandsDir() {
14
+ return path.join('.agent', 'agents');
15
+ }
16
+
17
+ get detectPath() {
18
+ return '.agent';
19
+ }
20
+ }
21
+
22
+ module.exports = AntigravityInstaller;
@@ -0,0 +1,85 @@
1
+ const ToolInstaller = require('./ToolInstaller');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const CLIUtils = require('../cli-utils');
5
+ const { theme } = CLIUtils;
6
+
7
+ class ClaudeInstaller extends ToolInstaller {
8
+ get key() {
9
+ return 'claude';
10
+ }
11
+
12
+ get name() {
13
+ return 'Claude Code';
14
+ }
15
+
16
+ get commandsDir() {
17
+ return path.join('.claude', 'commands');
18
+ }
19
+
20
+ get agentsDir() {
21
+ return path.join('.claude', 'agents');
22
+ }
23
+
24
+ get detectPath() {
25
+ return '.claude';
26
+ }
27
+
28
+ /**
29
+ * Override to install both commands and agents
30
+ */
31
+ async installCommands(flowPath, config) {
32
+ // Install commands (default behavior)
33
+ const installedCommands = await super.installCommands(flowPath, config);
34
+
35
+ // Install agents
36
+ const installedAgents = await this.installAgents(flowPath, config);
37
+
38
+ return [...installedCommands, ...installedAgents];
39
+ }
40
+
41
+ /**
42
+ * Install agents to .claude/agents/
43
+ * Uses the commands folder as source (same files serve as both commands and agents)
44
+ */
45
+ async installAgents(flowPath, config) {
46
+ const targetAgentsDir = this.agentsDir;
47
+ console.log(theme.dim(` Installing agents to ${targetAgentsDir}/...`));
48
+ await fs.ensureDir(targetAgentsDir);
49
+
50
+ const commandsSourceDir = path.join(flowPath, 'commands');
51
+
52
+ if (!await fs.pathExists(commandsSourceDir)) {
53
+ console.log(theme.dim(` No commands folder found at ${commandsSourceDir}`));
54
+ return [];
55
+ }
56
+
57
+ const agentFiles = await fs.readdir(commandsSourceDir);
58
+ const installedFiles = [];
59
+
60
+ for (const agentFile of agentFiles) {
61
+ if (agentFile.endsWith('.md')) {
62
+ try {
63
+ const sourcePath = path.join(commandsSourceDir, agentFile);
64
+ const prefix = (config && config.command && config.command.prefix) ? `${config.command.prefix}-` : '';
65
+ const targetFileName = `specsmd-${prefix}${agentFile}`;
66
+ const targetPath = path.join(targetAgentsDir, targetFileName);
67
+
68
+ const content = await fs.readFile(sourcePath, 'utf8');
69
+ await fs.outputFile(targetPath, content, 'utf8');
70
+ installedFiles.push(targetFileName);
71
+ } catch (err) {
72
+ console.log(theme.warning(` Failed to install agent ${agentFile}: ${err.message}`));
73
+ }
74
+ }
75
+ }
76
+
77
+ if (installedFiles.length > 0) {
78
+ CLIUtils.displayStatus('', `Installed ${installedFiles.length} agents for ${this.name}`, 'success');
79
+ }
80
+
81
+ return installedFiles;
82
+ }
83
+ }
84
+
85
+ module.exports = ClaudeInstaller;
@@ -0,0 +1,21 @@
1
+ const ToolInstaller = require('./ToolInstaller');
2
+
3
+ class ClineInstaller extends ToolInstaller {
4
+ get key() {
5
+ return 'cline';
6
+ }
7
+
8
+ get name() {
9
+ return 'Cline';
10
+ }
11
+
12
+ get commandsDir() {
13
+ return '.clinerules';
14
+ }
15
+
16
+ get detectPath() {
17
+ return '.clinerules';
18
+ }
19
+ }
20
+
21
+ module.exports = ClineInstaller;
@@ -0,0 +1,21 @@
1
+ const ToolInstaller = require('./ToolInstaller');
2
+
3
+ class CodexInstaller extends ToolInstaller {
4
+ get key() {
5
+ return 'codex';
6
+ }
7
+
8
+ get name() {
9
+ return 'Codex';
10
+ }
11
+
12
+ get commandsDir() {
13
+ return '.codex';
14
+ }
15
+
16
+ get detectPath() {
17
+ return '.codex';
18
+ }
19
+ }
20
+
21
+ module.exports = CodexInstaller;
@@ -0,0 +1,113 @@
1
+ const ToolInstaller = require('./ToolInstaller');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const CLIUtils = require('../cli-utils');
5
+ const { theme } = CLIUtils;
6
+
7
+ class CopilotInstaller extends ToolInstaller {
8
+ get key() {
9
+ return 'copilot';
10
+ }
11
+
12
+ get name() {
13
+ return 'GitHub Copilot';
14
+ }
15
+
16
+ get commandsDir() {
17
+ return path.join('.github', 'prompts');
18
+ }
19
+
20
+ get agentsDir() {
21
+ return path.join('.github', 'agents');
22
+ }
23
+
24
+ get detectPath() {
25
+ return '.github';
26
+ }
27
+
28
+ /**
29
+ * Override to install both commands and agents
30
+ */
31
+ async installCommands(flowPath, config) {
32
+ const installedCommands = await this.installCommandFiles(flowPath, config);
33
+ const installedAgents = await this.installAgentFiles(flowPath, config);
34
+ return [...installedCommands, ...installedAgents];
35
+ }
36
+
37
+ /**
38
+ * Install prompts to .github/prompts/ with .prompt.md extension
39
+ */
40
+ async installCommandFiles(flowPath, config) {
41
+ const targetDir = this.commandsDir;
42
+ console.log(theme.dim(` Installing prompts to ${targetDir}/...`));
43
+ await fs.ensureDir(targetDir);
44
+
45
+ const sourceDir = path.join(flowPath, 'commands');
46
+
47
+ if (!await fs.pathExists(sourceDir)) {
48
+ console.log(theme.dim(` No commands folder found at ${sourceDir}`));
49
+ return [];
50
+ }
51
+
52
+ const files = await fs.readdir(sourceDir);
53
+ const installedFiles = [];
54
+
55
+ for (const file of files) {
56
+ if (file.endsWith('.md')) {
57
+ const sourcePath = path.join(sourceDir, file);
58
+ const prefix = (config && config.command && config.command.prefix) ? `${config.command.prefix}-` : '';
59
+ // Transform .md to .prompt.md for Copilot prompts
60
+ const targetFileName = `specsmd-${prefix}${file}`.replace(/\.md$/, '.prompt.md');
61
+ const targetPath = path.join(targetDir, targetFileName);
62
+
63
+ await fs.copy(sourcePath, targetPath);
64
+ installedFiles.push(targetFileName);
65
+ }
66
+ }
67
+
68
+ if (installedFiles.length > 0) {
69
+ CLIUtils.displayStatus('', `Installed ${installedFiles.length} prompts for ${this.name}`, 'success');
70
+ }
71
+ return installedFiles;
72
+ }
73
+
74
+ /**
75
+ * Install agents to .github/agents/ with .agent.md extension
76
+ * Uses the commands folder as source (same files serve as both commands and agents)
77
+ */
78
+ async installAgentFiles(flowPath, config) {
79
+ const targetDir = this.agentsDir;
80
+ console.log(theme.dim(` Installing agents to ${targetDir}/...`));
81
+ await fs.ensureDir(targetDir);
82
+
83
+ const sourceDir = path.join(flowPath, 'commands');
84
+
85
+ if (!await fs.pathExists(sourceDir)) {
86
+ console.log(theme.dim(` No commands folder found at ${sourceDir}`));
87
+ return [];
88
+ }
89
+
90
+ const files = await fs.readdir(sourceDir);
91
+ const installedFiles = [];
92
+
93
+ for (const file of files) {
94
+ if (file.endsWith('.md')) {
95
+ const sourcePath = path.join(sourceDir, file);
96
+ const prefix = (config && config.command && config.command.prefix) ? `${config.command.prefix}-` : '';
97
+ // Transform .md to .agent.md for Copilot agents
98
+ const targetFileName = `specsmd-${prefix}${file}`.replace(/\.md$/, '.agent.md');
99
+ const targetPath = path.join(targetDir, targetFileName);
100
+
101
+ await fs.copy(sourcePath, targetPath);
102
+ installedFiles.push(targetFileName);
103
+ }
104
+ }
105
+
106
+ if (installedFiles.length > 0) {
107
+ CLIUtils.displayStatus('', `Installed ${installedFiles.length} agents for ${this.name}`, 'success');
108
+ }
109
+ return installedFiles;
110
+ }
111
+ }
112
+
113
+ module.exports = CopilotInstaller;
@@ -0,0 +1,63 @@
1
+ const ToolInstaller = require('./ToolInstaller');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const CLIUtils = require('../cli-utils');
5
+ const { theme } = CLIUtils;
6
+
7
+ class CursorInstaller extends ToolInstaller {
8
+ get key() {
9
+ return 'cursor';
10
+ }
11
+
12
+ get name() {
13
+ return 'Cursor';
14
+ }
15
+
16
+ get commandsDir() {
17
+ return path.join('.cursor', 'commands');
18
+ }
19
+
20
+ get detectPath() {
21
+ return '.cursor';
22
+ }
23
+
24
+ /**
25
+ * Override to install commands as .md files to .cursor/commands
26
+ */
27
+ async installCommands(flowPath, config) {
28
+ const targetCommandsDir = this.commandsDir;
29
+ console.log(theme.dim(` Installing commands to ${targetCommandsDir}/...`));
30
+ await fs.ensureDir(targetCommandsDir);
31
+
32
+ const commandsSourceDir = path.join(flowPath, 'commands');
33
+
34
+ if (!await fs.pathExists(commandsSourceDir)) {
35
+ console.log(theme.warning(` No commands folder found at ${commandsSourceDir}`));
36
+ return [];
37
+ }
38
+
39
+ const commandFiles = await fs.readdir(commandsSourceDir);
40
+ const installedFiles = [];
41
+
42
+ for (const cmdFile of commandFiles) {
43
+ if (cmdFile.endsWith('.md')) {
44
+ const sourcePath = path.join(commandsSourceDir, cmdFile);
45
+ const prefix = (config && config.command && config.command.prefix) ? `${config.command.prefix}-` : '';
46
+
47
+ // Keep .md extension for Cursor commands
48
+ const targetFileName = `specsmd-${prefix}${cmdFile}`;
49
+ const targetPath = path.join(targetCommandsDir, targetFileName);
50
+
51
+ // Copy content directly without adding frontmatter
52
+ const content = await fs.readFile(sourcePath, 'utf8');
53
+ await fs.writeFile(targetPath, content);
54
+ installedFiles.push(targetFileName);
55
+ }
56
+ }
57
+
58
+ CLIUtils.displayStatus('', `Installed ${installedFiles.length} commands for ${this.name}`, 'success');
59
+ return installedFiles;
60
+ }
61
+ }
62
+
63
+ module.exports = CursorInstaller;