@ydtb/specsmd 0.1.22

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 (143) hide show
  1. package/README.md +322 -0
  2. package/bin/cli.js +21 -0
  3. package/flows/aidlc/README.md +372 -0
  4. package/flows/aidlc/agents/construction-agent.md +80 -0
  5. package/flows/aidlc/agents/inception-agent.md +97 -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 +67 -0
  9. package/flows/aidlc/commands/inception-agent.md +59 -0
  10. package/flows/aidlc/commands/master-agent.md +51 -0
  11. package/flows/aidlc/commands/operations-agent.md +81 -0
  12. package/flows/aidlc/context-config.yaml +67 -0
  13. package/flows/aidlc/memory-bank.yaml +105 -0
  14. package/flows/aidlc/quick-start.md +322 -0
  15. package/flows/aidlc/scripts/artifact-validator.cjs +594 -0
  16. package/flows/aidlc/scripts/bolt-complete.cjs +606 -0
  17. package/flows/aidlc/scripts/status-integrity.cjs +598 -0
  18. package/flows/aidlc/skills/construction/bolt-list.md +163 -0
  19. package/flows/aidlc/skills/construction/bolt-replan.md +345 -0
  20. package/flows/aidlc/skills/construction/bolt-start.md +442 -0
  21. package/flows/aidlc/skills/construction/bolt-status.md +185 -0
  22. package/flows/aidlc/skills/construction/navigator.md +196 -0
  23. package/flows/aidlc/skills/construction/prototype-apply.md +311 -0
  24. package/flows/aidlc/skills/inception/bolt-plan.md +372 -0
  25. package/flows/aidlc/skills/inception/context.md +171 -0
  26. package/flows/aidlc/skills/inception/intent-create.md +211 -0
  27. package/flows/aidlc/skills/inception/intent-list.md +124 -0
  28. package/flows/aidlc/skills/inception/navigator.md +207 -0
  29. package/flows/aidlc/skills/inception/requirements.md +227 -0
  30. package/flows/aidlc/skills/inception/review.md +248 -0
  31. package/flows/aidlc/skills/inception/story-create.md +304 -0
  32. package/flows/aidlc/skills/inception/units.md +278 -0
  33. package/flows/aidlc/skills/inception/vibe-to-spec.md +410 -0
  34. package/flows/aidlc/skills/master/analyze-context.md +239 -0
  35. package/flows/aidlc/skills/master/answer-question.md +141 -0
  36. package/flows/aidlc/skills/master/explain-flow.md +158 -0
  37. package/flows/aidlc/skills/master/project-init.md +281 -0
  38. package/flows/aidlc/skills/master/route-request.md +126 -0
  39. package/flows/aidlc/skills/operations/build.md +237 -0
  40. package/flows/aidlc/skills/operations/deploy.md +259 -0
  41. package/flows/aidlc/skills/operations/monitor.md +265 -0
  42. package/flows/aidlc/skills/operations/navigator.md +209 -0
  43. package/flows/aidlc/skills/operations/verify.md +224 -0
  44. package/flows/aidlc/templates/construction/bolt-template.md +226 -0
  45. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +49 -0
  46. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +55 -0
  47. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +67 -0
  48. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +62 -0
  49. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt.md +590 -0
  50. package/flows/aidlc/templates/construction/bolt-types/simple-construction-bolt.md +347 -0
  51. package/flows/aidlc/templates/construction/bolt-types/spike-bolt.md +240 -0
  52. package/flows/aidlc/templates/construction/construction-log-template.md +129 -0
  53. package/flows/aidlc/templates/construction/standards/coding-standards.md +29 -0
  54. package/flows/aidlc/templates/construction/standards/system-architecture.md +22 -0
  55. package/flows/aidlc/templates/construction/standards/tech-stack.md +19 -0
  56. package/flows/aidlc/templates/inception/inception-log-template.md +134 -0
  57. package/flows/aidlc/templates/inception/project/README.md +55 -0
  58. package/flows/aidlc/templates/inception/requirements-template.md +144 -0
  59. package/flows/aidlc/templates/inception/stories-template.md +38 -0
  60. package/flows/aidlc/templates/inception/story-template.md +147 -0
  61. package/flows/aidlc/templates/inception/system-context-template.md +29 -0
  62. package/flows/aidlc/templates/inception/unit-brief-template.md +177 -0
  63. package/flows/aidlc/templates/inception/units-template.md +52 -0
  64. package/flows/aidlc/templates/standards/catalog.yaml +345 -0
  65. package/flows/aidlc/templates/standards/coding-standards.guide.md +553 -0
  66. package/flows/aidlc/templates/standards/data-stack.guide.md +162 -0
  67. package/flows/aidlc/templates/standards/decision-index-template.md +32 -0
  68. package/flows/aidlc/templates/standards/tech-stack.guide.md +280 -0
  69. package/flows/fire/README.md +19 -0
  70. package/flows/fire/agents/builder/agent.md +254 -0
  71. package/flows/fire/agents/builder/skills/code-review/SKILL.md +257 -0
  72. package/flows/fire/agents/builder/skills/code-review/references/auto-fix-rules.md +218 -0
  73. package/flows/fire/agents/builder/skills/code-review/references/review-categories.md +154 -0
  74. package/flows/fire/agents/builder/skills/code-review/templates/review-report.md.hbs +120 -0
  75. package/flows/fire/agents/builder/skills/commit-changes/SKILL.md +232 -0
  76. package/flows/fire/agents/builder/skills/commit-changes/scripts/commit-changes.cjs +447 -0
  77. package/flows/fire/agents/builder/skills/run-execute/SKILL.md +700 -0
  78. package/flows/fire/agents/builder/skills/run-execute/scripts/complete-run.cjs +748 -0
  79. package/flows/fire/agents/builder/skills/run-execute/scripts/init-run.cjs +457 -0
  80. package/flows/fire/agents/builder/skills/run-execute/scripts/update-phase.cjs +239 -0
  81. package/flows/fire/agents/builder/skills/run-execute/templates/plan.md.hbs +61 -0
  82. package/flows/fire/agents/builder/skills/run-execute/templates/test-report.md.hbs +81 -0
  83. package/flows/fire/agents/builder/skills/run-plan/SKILL.md +366 -0
  84. package/flows/fire/agents/builder/skills/run-status/SKILL.md +96 -0
  85. package/flows/fire/agents/builder/skills/walkthrough-generate/SKILL.md +181 -0
  86. package/flows/fire/agents/builder/skills/walkthrough-generate/templates/walkthrough.md.hbs +108 -0
  87. package/flows/fire/agents/orchestrator/agent.md +144 -0
  88. package/flows/fire/agents/orchestrator/skills/project-init/SKILL.md +226 -0
  89. package/flows/fire/agents/orchestrator/skills/project-init/templates/coding-standards.md.hbs +149 -0
  90. package/flows/fire/agents/orchestrator/skills/project-init/templates/constitution.md.hbs +43 -0
  91. package/flows/fire/agents/orchestrator/skills/project-init/templates/system-architecture.md.hbs +101 -0
  92. package/flows/fire/agents/orchestrator/skills/project-init/templates/tech-stack.md.hbs +136 -0
  93. package/flows/fire/agents/orchestrator/skills/project-init/templates/testing-standards.md.hbs +94 -0
  94. package/flows/fire/agents/orchestrator/skills/route/SKILL.md +146 -0
  95. package/flows/fire/agents/orchestrator/skills/status/SKILL.md +696 -0
  96. package/flows/fire/agents/planner/agent.md +143 -0
  97. package/flows/fire/agents/planner/skills/design-doc-generate/SKILL.md +156 -0
  98. package/flows/fire/agents/planner/skills/design-doc-generate/templates/design.md.hbs +124 -0
  99. package/flows/fire/agents/planner/skills/intent-capture/SKILL.md +125 -0
  100. package/flows/fire/agents/planner/skills/intent-capture/templates/brief.md.hbs +40 -0
  101. package/flows/fire/agents/planner/skills/work-item-decompose/SKILL.md +166 -0
  102. package/flows/fire/agents/planner/skills/work-item-decompose/templates/work-item.md.hbs +40 -0
  103. package/flows/fire/commands/fire-builder.md +56 -0
  104. package/flows/fire/commands/fire-planner.md +48 -0
  105. package/flows/fire/commands/fire.md +46 -0
  106. package/flows/fire/memory-bank.yaml +240 -0
  107. package/flows/fire/quick-start.md +146 -0
  108. package/flows/simple/README.md +190 -0
  109. package/flows/simple/agents/agent.md +404 -0
  110. package/flows/simple/commands/agent.md +60 -0
  111. package/flows/simple/context-config.yaml +34 -0
  112. package/flows/simple/memory-bank.yaml +66 -0
  113. package/flows/simple/quick-start.md +231 -0
  114. package/flows/simple/skills/design.md +96 -0
  115. package/flows/simple/skills/execute.md +190 -0
  116. package/flows/simple/skills/requirements.md +94 -0
  117. package/flows/simple/skills/tasks.md +136 -0
  118. package/flows/simple/templates/design-template.md +138 -0
  119. package/flows/simple/templates/requirements-template.md +85 -0
  120. package/flows/simple/templates/tasks-template.md +104 -0
  121. package/lib/InstallerFactory.js +36 -0
  122. package/lib/analytics/env-detector.js +92 -0
  123. package/lib/analytics/index.js +22 -0
  124. package/lib/analytics/machine-id.js +33 -0
  125. package/lib/analytics/tracker.js +232 -0
  126. package/lib/cli-utils.js +342 -0
  127. package/lib/constants.js +44 -0
  128. package/lib/installer.js +406 -0
  129. package/lib/installers/AntigravityInstaller.js +22 -0
  130. package/lib/installers/ClaudeInstaller.js +85 -0
  131. package/lib/installers/ClineInstaller.js +21 -0
  132. package/lib/installers/CodexInstaller.js +21 -0
  133. package/lib/installers/CopilotInstaller.js +113 -0
  134. package/lib/installers/CursorInstaller.js +63 -0
  135. package/lib/installers/GeminiInstaller.js +75 -0
  136. package/lib/installers/KiroInstaller.js +77 -0
  137. package/lib/installers/OpenCodeInstaller.js +30 -0
  138. package/lib/installers/RooInstaller.js +22 -0
  139. package/lib/installers/ToolInstaller.js +76 -0
  140. package/lib/installers/WindsurfInstaller.js +22 -0
  141. package/lib/markdown-validator.ts +175 -0
  142. package/lib/yaml-validator.ts +99 -0
  143. package/package.json +69 -0
@@ -0,0 +1,406 @@
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, LINKS } = require('./constants');
8
+ const analytics = require('./analytics');
9
+
10
+ // Use theme from CLIUtils for consistent styling
11
+ const { theme } = CLIUtils;
12
+
13
+ /**
14
+ * Categorize an error for analytics tracking
15
+ * @param {Error} error - The error to categorize
16
+ * @returns {string} Error category
17
+ */
18
+ function categorizeError(error) {
19
+ const message = (error.message || '').toLowerCase();
20
+
21
+ if (message.includes('permission') || message.includes('eacces')) {
22
+ return 'file_permission';
23
+ }
24
+ if (message.includes('enoent') || message.includes('not found')) {
25
+ return 'file_not_found';
26
+ }
27
+ if (message.includes('network') || message.includes('enotfound') || message.includes('timeout')) {
28
+ return 'network';
29
+ }
30
+ if (message.includes('enospc') || message.includes('disk')) {
31
+ return 'disk_space';
32
+ }
33
+ return 'unknown';
34
+ }
35
+
36
+ /**
37
+ * Count files in a directory recursively
38
+ * @param {string} dir - Directory path
39
+ * @returns {Promise<number>} File count
40
+ */
41
+ async function countFiles(dir) {
42
+ let count = 0;
43
+ try {
44
+ const entries = await fs.readdir(dir, { withFileTypes: true });
45
+ for (const entry of entries) {
46
+ if (entry.isDirectory()) {
47
+ count += await countFiles(path.join(dir, entry.name));
48
+ } else {
49
+ count++;
50
+ }
51
+ }
52
+ } catch {
53
+ // Ignore errors (directory might not exist)
54
+ }
55
+ return count;
56
+ }
57
+
58
+ async function detectTools() {
59
+ const detected = [];
60
+ const installers = InstallerFactory.getInstallers();
61
+
62
+ for (const installer of installers) {
63
+ if (await installer.detect()) {
64
+ detected.push(installer.key);
65
+ }
66
+ }
67
+ return detected;
68
+ }
69
+
70
+ async function install() {
71
+ // Initialize analytics (respects opt-out env vars)
72
+ analytics.init();
73
+ await analytics.trackInstallerStarted();
74
+
75
+ const installStartTime = Date.now();
76
+
77
+ await CLIUtils.displayLogo();
78
+ CLIUtils.displayHeader('Installation', '');
79
+
80
+ // Step 1: Detect agentic coding tools
81
+ const detectedToolKeys = await detectTools();
82
+ const installers = InstallerFactory.getInstallers();
83
+ const detectedNames = installers
84
+ .filter(i => detectedToolKeys.includes(i.key))
85
+ .map(i => i.name);
86
+
87
+ CLIUtils.displayStep(1, 4, 'Detecting agentic coding tools...');
88
+ if (detectedNames.length > 0) {
89
+ CLIUtils.displayStatus('', `Detected: ${detectedNames.join(', ')}`, 'success');
90
+ } else {
91
+ CLIUtils.displayStatus('', 'No agentic coding tools detected', 'warning');
92
+ }
93
+ console.log('');
94
+
95
+ // Step 2: Select tools
96
+ CLIUtils.displayStep(2, 4, 'Select target tools');
97
+
98
+ // Build choices with descriptive formatting
99
+ const toolChoices = installers.map(installer => ({
100
+ title: installer.name + (detectedToolKeys.includes(installer.key) ? theme.dim(' (detected)') : ''),
101
+ value: installer.key,
102
+ selected: detectedToolKeys.includes(installer.key)
103
+ }));
104
+
105
+ console.log(theme.dim(' [Space] toggle [Enter] confirm [a] toggle all'));
106
+ console.log(theme.dim(` ${theme.success('[x]')} = selected ${theme.dim('[ ]')} = not selected\n`));
107
+
108
+ const { selectedToolKeys } = await prompts({
109
+ type: 'multiselect',
110
+ name: 'selectedToolKeys',
111
+ message: 'Choose tools:',
112
+ choices: toolChoices,
113
+ min: 1,
114
+ instructions: false
115
+ });
116
+
117
+ if (!selectedToolKeys || selectedToolKeys.length === 0) {
118
+ CLIUtils.displayError('Installation cancelled - no tools selected');
119
+ process.exit(1);
120
+ }
121
+
122
+ // Track IDE selection (await to ensure delivery before potential cancel)
123
+ await analytics.trackIdesConfirmed(selectedToolKeys);
124
+
125
+ // Step 3: Select Flow
126
+ console.log('');
127
+ CLIUtils.displayStep(3, 4, 'Select SDLC flow');
128
+ console.log(theme.dim(` Learn more about flows: ${LINKS.flows}\n`));
129
+ const flowChoices = Object.entries(FLOWS).map(([key, flow]) => ({
130
+ title: `${flow.name} - ${flow.description}${flow.message || ''}`,
131
+ value: key,
132
+ disabled: flow.disabled
133
+ }));
134
+
135
+ const { selectedFlow } = await prompts({
136
+ type: 'select',
137
+ name: 'selectedFlow',
138
+ message: 'Which SDLC flow would you like to install?',
139
+ choices: flowChoices
140
+ });
141
+
142
+ if (!selectedFlow) {
143
+ CLIUtils.displayError('Installation cancelled');
144
+ process.exit(1);
145
+ }
146
+
147
+ // Track flow selection (await to ensure delivery before potential cancel)
148
+ await analytics.trackFlowSelected(selectedFlow);
149
+
150
+ // Step 4: Install flow files
151
+ console.log('');
152
+ CLIUtils.displayStep(4, 4, `Installing ${FLOWS[selectedFlow].name} flow...`);
153
+
154
+ try {
155
+ const filesCreated = await installFlow(selectedFlow, selectedToolKeys);
156
+
157
+ // Track successful installation for each tool
158
+ const durationMs = Date.now() - installStartTime;
159
+ for (const toolKey of selectedToolKeys) {
160
+ analytics.trackInstallationCompleted(toolKey, selectedFlow, durationMs, filesCreated);
161
+ }
162
+
163
+ CLIUtils.displaySuccess(`${FLOWS[selectedFlow].name} flow installed successfully!`, 'Installation Complete');
164
+
165
+ // Get selected tool names for next steps message
166
+ const selectedToolNames = installers
167
+ .filter(i => selectedToolKeys.includes(i.key))
168
+ .map(i => i.name);
169
+
170
+ const nextSteps = [
171
+ `Read .specsmd/${selectedFlow}/quick-start.md for getting started`,
172
+ `Open ${selectedToolNames.join(' or ')} and run /specsmd-master-agent`
173
+ ];
174
+ CLIUtils.displayNextSteps(nextSteps);
175
+
176
+ // Display IDE extension info with brand colors
177
+ console.log('\n' + theme.primary('─'.repeat(72)));
178
+ console.log(CLIUtils.logoGradient(' ★ IDE Extension'));
179
+ console.log(theme.primary('─'.repeat(72)));
180
+ console.log('');
181
+ console.log(' ' + CLIUtils.logoGradient('Enhance your experience with the specsmd IDE extension!'));
182
+ console.log('');
183
+ console.log(` ${theme.primary('Learn more:')} ${LINKS.ideExtension}`);
184
+ console.log(` ${theme.primary('VS Code:')} ${LINKS.vscodeMarketplace}`);
185
+ console.log(` ${theme.primary('Cursor/Antigravity/Windsurf:')} ${LINKS.openVsx}`);
186
+ console.log('');
187
+ console.log(theme.primary('─'.repeat(72)) + '\n');
188
+ console.log('');
189
+ } catch (error) {
190
+ // Track installation failure
191
+ const errorCategory = categorizeError(error);
192
+ for (const toolKey of selectedToolKeys) {
193
+ analytics.trackInstallationFailed(toolKey, errorCategory, selectedFlow);
194
+ }
195
+
196
+ CLIUtils.displayError(`Installation failed: ${error.message}`);
197
+ console.log(theme.dim('\nRolling back changes...'));
198
+ await rollback(selectedFlow, selectedToolKeys);
199
+ CLIUtils.displayStatus('', 'Installation rolled back', 'warning');
200
+ process.exit(1);
201
+ }
202
+ }
203
+
204
+ async function installFlow(flowKey, toolKeys) {
205
+ const flowPath = path.join(__dirname, '..', 'flows', FLOWS[flowKey].path);
206
+
207
+ // Step 1: Install commands for each tool
208
+ // Pass empty config since config.yaml is removed
209
+ const dummyConfig = {};
210
+ for (const toolKey of toolKeys) {
211
+ const installer = InstallerFactory.getInstaller(toolKey);
212
+ if (installer) {
213
+ await installer.installCommands(flowPath, dummyConfig);
214
+ }
215
+ }
216
+
217
+ // Step 2: Install shared flow config
218
+ const specsmdDir = '.specsmd';
219
+ const targetFlowDir = path.join(specsmdDir, flowKey);
220
+
221
+ console.log(theme.dim(` Installing flow resources to ${targetFlowDir}/...`));
222
+ await fs.ensureDir(targetFlowDir);
223
+
224
+ // Copy agents
225
+ await fs.copy(path.join(flowPath, 'agents'), path.join(targetFlowDir, 'agents'));
226
+
227
+ // Copy internal agent capabilities (legacy check)
228
+ if (await fs.pathExists(path.join(flowPath, 'agent-capabilities'))) {
229
+ await fs.copy(path.join(flowPath, 'agent-capabilities'), path.join(targetFlowDir, 'agent-capabilities'));
230
+ }
231
+
232
+ // Copy bolt-types if they exist (legacy check)
233
+ if (await fs.pathExists(path.join(flowPath, 'bolt-types'))) {
234
+ await fs.copy(path.join(flowPath, 'bolt-types'), path.join(targetFlowDir, 'bolt-types'));
235
+ }
236
+
237
+ // Copy skills, templates, shared (now at flow root level, not nested in .specsmd)
238
+ if (await fs.pathExists(path.join(flowPath, 'skills'))) {
239
+ await fs.copy(path.join(flowPath, 'skills'), path.join(targetFlowDir, 'skills'));
240
+ }
241
+ if (await fs.pathExists(path.join(flowPath, 'templates'))) {
242
+ await fs.copy(path.join(flowPath, 'templates'), path.join(targetFlowDir, 'templates'));
243
+ }
244
+ if (await fs.pathExists(path.join(flowPath, 'shared'))) {
245
+ await fs.copy(path.join(flowPath, 'shared'), path.join(targetFlowDir, 'shared'));
246
+ }
247
+ if (await fs.pathExists(path.join(flowPath, 'scripts'))) {
248
+ await fs.copy(path.join(flowPath, 'scripts'), path.join(targetFlowDir, 'scripts'));
249
+ }
250
+
251
+ // Copy config files
252
+ if (await fs.pathExists(path.join(flowPath, 'memory-bank.yaml'))) {
253
+ await fs.copy(path.join(flowPath, 'memory-bank.yaml'), path.join(targetFlowDir, 'memory-bank.yaml'));
254
+ }
255
+ if (await fs.pathExists(path.join(flowPath, 'context-config.yaml'))) {
256
+ await fs.copy(path.join(flowPath, 'context-config.yaml'), path.join(targetFlowDir, 'context-config.yaml'));
257
+ }
258
+ if (await fs.pathExists(path.join(flowPath, 'quick-start.md'))) {
259
+ await fs.copy(path.join(flowPath, 'quick-start.md'), path.join(targetFlowDir, 'quick-start.md'));
260
+ }
261
+
262
+ // Copy docs
263
+ await fs.copy(path.join(flowPath, 'README.md'), path.join(targetFlowDir, 'README.md'));
264
+
265
+ if (await fs.pathExists(path.join(flowPath, 'constitution.md'))) {
266
+ await fs.copy(path.join(flowPath, 'constitution.md'), path.join(targetFlowDir, 'constitution.md'));
267
+ }
268
+
269
+ CLIUtils.displayStatus('', 'Installed flow resources', 'success');
270
+
271
+ // NOTE: memory-bank/ is NOT created during installation
272
+ // It will be created when user runs project-init
273
+ // This allows us to detect if project is initialized by checking for memory-bank/standards/
274
+
275
+ // Step 3: Create manifest
276
+ const manifest = {
277
+ flow: flowKey,
278
+ version: require('../package.json').version,
279
+ installed_at: new Date().toISOString(),
280
+ tools: toolKeys
281
+ };
282
+
283
+ await fs.writeFile(
284
+ path.join(specsmdDir, 'manifest.yaml'),
285
+ yaml.dump(manifest),
286
+ 'utf8'
287
+ );
288
+
289
+ CLIUtils.displayStatus('', 'Created installation manifest', 'success');
290
+
291
+ // Count files created for analytics
292
+ const filesCreated = await countFiles(specsmdDir);
293
+ return filesCreated;
294
+ }
295
+
296
+ async function rollback(flowKey, toolKeys) {
297
+ // Remove tool command files
298
+ for (const toolKey of toolKeys) {
299
+ const installer = InstallerFactory.getInstaller(toolKey);
300
+ if (installer) {
301
+ const commandsDir = installer.commandsDir;
302
+ if (await fs.pathExists(commandsDir)) {
303
+ const files = await fs.readdir(commandsDir);
304
+ for (const file of files) {
305
+ if (file.startsWith('specsmd-')) {
306
+ await fs.remove(path.join(commandsDir, file));
307
+ }
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ // Remove .specsmd directory
314
+ if (await fs.pathExists('.specsmd')) {
315
+ await fs.remove('.specsmd');
316
+ }
317
+ }
318
+
319
+ async function uninstall() {
320
+ CLIUtils.displayHeader('Uninstall', '');
321
+
322
+ // Check if specsmd is installed
323
+ if (!await fs.pathExists('.specsmd/manifest.yaml')) {
324
+ CLIUtils.displayWarning('specsmd is not installed in this project');
325
+ return;
326
+ }
327
+
328
+ // Read manifest
329
+ const manifestContent = await fs.readFile('.specsmd/manifest.yaml', 'utf8');
330
+ const manifest = yaml.load(manifestContent);
331
+
332
+ const installers = InstallerFactory.getInstallers();
333
+ // Support both old 'ides' key and new 'tools' key for backward compatibility
334
+ const installedToolKeys = manifest.tools || manifest.ides || [];
335
+ const installedNames = installers
336
+ .filter(i => installedToolKeys.includes(i.key))
337
+ .map(i => i.name);
338
+
339
+ console.log(theme.dim(`Found installation: ${FLOWS[manifest.flow].name} flow`));
340
+ console.log(theme.dim(`Installed for: ${installedNames.join(', ')}\n`));
341
+
342
+ const { confirm } = await prompts({
343
+ type: 'confirm',
344
+ name: 'confirm',
345
+ message: 'Are you sure you want to uninstall specsmd?',
346
+ initial: false
347
+ });
348
+
349
+ if (!confirm) {
350
+ CLIUtils.displayStatus('', 'Uninstall cancelled', 'warning');
351
+ return;
352
+ }
353
+
354
+ // Ask about memory bank
355
+ const { keepMemoryBank } = await prompts({
356
+ type: 'confirm',
357
+ name: 'keepMemoryBank',
358
+ message: 'Keep memory-bank folder? (Contains your project artifacts)',
359
+ initial: true
360
+ });
361
+
362
+ console.log(theme.primary('\nUninstalling...\n'));
363
+
364
+ try {
365
+ // Remove command files
366
+ for (const toolKey of installedToolKeys) {
367
+ const installer = InstallerFactory.getInstaller(toolKey);
368
+ if (installer) {
369
+ const commandsDir = installer.commandsDir;
370
+ if (await fs.pathExists(commandsDir)) {
371
+ console.log(theme.dim(` Removing commands from ${commandsDir}/...`));
372
+ const files = await fs.readdir(commandsDir);
373
+ for (const file of files) {
374
+ if (file.startsWith('specsmd-')) {
375
+ await fs.remove(path.join(commandsDir, file));
376
+ }
377
+ }
378
+ }
379
+ }
380
+ }
381
+
382
+ // Remove .specsmd directory
383
+ console.log(theme.dim(' Removing .specsmd/...'));
384
+ await fs.remove('.specsmd');
385
+
386
+ // Optionally remove memory-bank
387
+ if (!keepMemoryBank && await fs.pathExists('memory-bank')) {
388
+ console.log(theme.dim(' Removing memory-bank/...'));
389
+ await fs.remove('memory-bank');
390
+ }
391
+
392
+ CLIUtils.displaySuccess('Uninstall complete!', 'Complete');
393
+ if (keepMemoryBank) {
394
+ console.log(theme.dim('memory-bank/ was preserved\n'));
395
+ }
396
+ } catch (error) {
397
+ CLIUtils.displayError(`Uninstall failed: ${error.message}`);
398
+ process.exit(1);
399
+ }
400
+ }
401
+
402
+ module.exports = {
403
+ install,
404
+ uninstall
405
+ };
406
+
@@ -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', 'workflows');
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;