@devobsessed/code-captain 0.0.6 → 0.0.8

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 (34) hide show
  1. package/README.md +1 -10
  2. package/bin/install.js +178 -182
  3. package/claude-code/agents/code-captain.md +17 -20
  4. package/copilot/README.md +26 -16
  5. package/copilot/chatmodes/Code Captain.chatmode.md +11 -16
  6. package/copilot/prompts/create-spec.prompt.md +5 -8
  7. package/copilot/prompts/explain-code.prompt.md +5 -8
  8. package/copilot/prompts/new-command.prompt.md +60 -21
  9. package/copilot/prompts/research.prompt.md +5 -8
  10. package/copilot/prompts/status.prompt.md +13 -2
  11. package/copilot/prompts/swab.prompt.md +1 -0
  12. package/cursor/README.md +8 -23
  13. package/cursor/cc.md +2 -29
  14. package/cursor/cc.mdc +3 -10
  15. package/cursor/commands/create-adr.md +1 -1
  16. package/cursor/commands/create-spec.md +9 -12
  17. package/cursor/commands/explain-code.md +5 -8
  18. package/cursor/commands/initialize.md +1 -1
  19. package/cursor/commands/new-command.md +5 -4
  20. package/cursor/commands/research.md +6 -9
  21. package/cursor/commands/status.md +13 -2
  22. package/cursor/commands/swab.md +61 -2
  23. package/manifest.json +150 -166
  24. package/package.json +12 -2
  25. package/windsurf/workflows/explain-code.md +4 -8
  26. package/windsurf/workflows/plan-product.md +330 -0
  27. package/windsurf/workflows/research.md +240 -0
  28. package/windsurf/workflows/swab.md +212 -0
  29. package/cursor/integrations/azure-devops/create-azure-work-items.md +0 -403
  30. package/cursor/integrations/azure-devops/sync-azure-work-items.md +0 -486
  31. package/cursor/integrations/github/create-github-issues.md +0 -765
  32. package/cursor/integrations/github/scripts/create-issues-batch.sh +0 -272
  33. package/cursor/integrations/github/sync-github-issues.md +0 -237
  34. package/cursor/integrations/github/sync.md +0 -305
package/bin/install.js CHANGED
@@ -24,9 +24,7 @@ class CodeCaptainInstaller {
24
24
  baseUrl: 'https://raw.githubusercontent.com/devobsessed/code-captain/main',
25
25
  version: 'main',
26
26
  // Default to local source when running from npm package
27
- localSource: process.env.CC_LOCAL_SOURCE || (isNpmPackage ? packageRoot : null),
28
- versionFile: '.code-captain/.version',
29
- manifestFile: '.code-captain/.manifest.json'
27
+ localSource: process.env.CC_LOCAL_SOURCE || (isNpmPackage ? packageRoot : null)
30
28
  };
31
29
 
32
30
  this.ides = {
@@ -36,19 +34,19 @@ class CodeCaptainInstaller {
36
34
  details: 'Uses .code-captain/ structure + .cursor/rules/cc.mdc'
37
35
  },
38
36
  copilot: {
39
- name: 'VS Code with GitHub Copilot',
40
- description: 'Visual Studio Code with GitHub Copilot extension',
37
+ name: 'Copilot',
38
+ description: 'Visual Studio Code with Copilot extension',
41
39
  details: 'Uses .github/chatmodes/ + .github/prompts/ + .code-captain/docs/'
42
40
  },
43
41
  windsurf: {
44
42
  name: 'Windsurf',
45
43
  description: 'Codeium\'s AI-powered development environment',
46
- details: 'Uses windsurf/rules/ for custom workflows'
44
+ details: 'Uses .windsurf/rules/ and .windsurf/workflows/ for custom workflows'
47
45
  },
48
46
  claude: {
49
47
  name: 'Claude Code',
50
48
  description: 'Direct integration with Claude for development workflows',
51
- details: 'Uses .code-captain/claude/ structure with agents and commands'
49
+ details: 'Uses .claude/ for agents and commands (+ .code-captain/ for shared docs)'
52
50
  }
53
51
  };
54
52
  }
@@ -119,7 +117,7 @@ class CodeCaptainInstaller {
119
117
  'Code Captain Core': ['.code-captain/'],
120
118
  'Cursor Integration': ['.cursor/rules/cc.mdc', '.cursor/rules/'],
121
119
  'Copilot Integration': ['.github/chatmodes/', '.github/prompts/'],
122
- 'Windsurf Integration': ['windsurf/rules/', 'windsurf/workflows/'],
120
+ 'Windsurf Integration': ['.windsurf/rules/', '.windsurf/workflows/'],
123
121
  'Claude Integration': ['.code-captain/claude/', 'claude-code/', '.claude/'],
124
122
  'Legacy Structure': ['cursor/', 'copilot/', 'windsurf/']
125
123
  };
@@ -156,13 +154,24 @@ class CodeCaptainInstaller {
156
154
  async calculateFileHash(filePath) {
157
155
  try {
158
156
  const crypto = await import('crypto');
159
- const content = await fs.readFile(filePath, 'utf8');
157
+ const content = await fs.readFile(filePath); // Buffer
160
158
  return crypto.default.createHash('sha256').update(content).digest('hex');
161
159
  } catch (error) {
162
160
  return null;
163
161
  }
164
162
  }
165
163
 
164
+ // Fetch with timeout using AbortController
165
+ async fetchWithTimeout(url, init = {}, timeoutMs = 15000) {
166
+ const controller = new AbortController();
167
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
168
+ try {
169
+ return await fetch(url, { ...init, signal: controller.signal });
170
+ } finally {
171
+ clearTimeout(timer);
172
+ }
173
+ }
174
+
166
175
  // Get remote manifest with file versions/hashes
167
176
  async getRemoteManifest() {
168
177
  try {
@@ -172,20 +181,24 @@ class CodeCaptainInstaller {
172
181
  const localManifestPath = path.join(this.config.localSource, 'manifest.json');
173
182
  if (await fs.pathExists(localManifestPath)) {
174
183
  const content = await fs.readFile(localManifestPath, 'utf8');
175
- return JSON.parse(content);
184
+ const manifest = JSON.parse(content);
185
+ return { manifest, isFallback: false };
176
186
  }
177
187
  } else {
178
- const response = await fetch(manifestUrl);
188
+ const response = await this.fetchWithTimeout(manifestUrl, {}, 15000);
179
189
  if (response.ok) {
180
- return await response.json();
190
+ const manifest = await response.json();
191
+ return { manifest, isFallback: false };
181
192
  }
182
193
  }
183
194
 
184
195
  // Fallback: generate manifest from current commit
185
- return await this.generateFallbackManifest();
196
+ const fallbackManifest = await this.generateFallbackManifest();
197
+ return { manifest: fallbackManifest, isFallback: true };
186
198
  } catch (error) {
187
199
  console.warn(chalk.yellow('Warning: Could not fetch remote manifest, using fallback'));
188
- return await this.generateFallbackManifest();
200
+ const fallbackManifest = await this.generateFallbackManifest();
201
+ return { manifest: fallbackManifest, isFallback: true };
189
202
  }
190
203
  }
191
204
 
@@ -200,47 +213,43 @@ class CodeCaptainInstaller {
200
213
  };
201
214
  }
202
215
 
203
- // Get local manifest if it exists
204
- async getLocalManifest() {
205
- try {
206
- if (await fs.pathExists(this.config.manifestFile)) {
207
- const content = await fs.readFile(this.config.manifestFile, 'utf8');
208
- return JSON.parse(content);
209
- }
210
- } catch (error) {
211
- console.warn(chalk.yellow('Warning: Could not read local manifest'));
212
- }
213
- return null;
214
- }
215
216
 
216
- // Compare manifests and detect changes
217
+
218
+ // Compare current files with remote manifest to detect changes
217
219
  async detectChanges(selectedIDE) {
218
220
  const spinner = ora('Analyzing file changes...').start();
219
221
 
220
222
  try {
221
- const [remoteManifest, localManifest] = await Promise.all([
222
- this.getRemoteManifest(),
223
- this.getLocalManifest()
224
- ]);
223
+ const { manifest: remoteManifest, isFallback: manifestIsFallback } = await this.getRemoteManifest();
224
+ const files = this.getIDEFiles(selectedIDE);
225
225
 
226
- if (!localManifest) {
227
- spinner.succeed('No previous manifest found - treating as fresh installation');
226
+ // Check if this looks like a first install (no existing files)
227
+ const existingFiles = [];
228
+ for (const file of files) {
229
+ if (await fs.pathExists(file.target)) {
230
+ existingFiles.push(file.target);
231
+ }
232
+ }
228
233
 
229
- // Get proper version information for first install
230
- const currentVersion = this.config.localSource ? await this.getPackageVersion() : 'unknown';
231
- const availableVersion = remoteManifest.version;
234
+ if (existingFiles.length === 0) {
235
+ if (manifestIsFallback) {
236
+ spinner.succeed('No existing files found - treating as fresh installation (offline mode)');
237
+ } else {
238
+ spinner.succeed('No existing files found - treating as fresh installation');
239
+ }
232
240
 
241
+ const availableVersion = remoteManifest.version;
233
242
  return {
234
243
  isFirstInstall: true,
235
- localVersion: currentVersion,
236
244
  remoteVersion: availableVersion,
237
245
  changes: [],
238
246
  newFiles: [],
239
- recommendations: ['Full installation recommended (no change tracking available)']
247
+ recommendations: ['Full installation recommended'],
248
+ manifestIsFallback
240
249
  };
241
250
  }
242
251
 
243
- const files = this.getIDEFiles(selectedIDE);
252
+ // Analyze existing files for changes
244
253
  const changes = [];
245
254
  const newFiles = [];
246
255
  let filesAnalyzed = 0;
@@ -286,7 +295,6 @@ class CodeCaptainInstaller {
286
295
  changes.push({
287
296
  file: remotePath,
288
297
  component: file.component,
289
- localVersion: localManifest.files?.[remotePath]?.version || 'unknown',
290
298
  remoteVersion: remoteFileInfo.version || 'latest',
291
299
  reason: 'File content has changed',
292
300
  localHash: localFileHash.substring(0, 8),
@@ -294,30 +302,39 @@ class CodeCaptainInstaller {
294
302
  });
295
303
  }
296
304
  } else {
297
- // No remote file info - treat as new in remote
298
- newFiles.push({
299
- file: remotePath,
300
- component: file.component,
301
- reason: 'New file in remote repository'
302
- });
305
+ // No remote file info - check if we're in fallback mode
306
+ if (manifestIsFallback) {
307
+ // In fallback mode, we can't determine if files are new
308
+ // Skip these files from change detection
309
+ continue;
310
+ } else {
311
+ // Not in fallback mode - truly new file in remote
312
+ newFiles.push({
313
+ file: remotePath,
314
+ component: file.component,
315
+ reason: 'New file in remote repository'
316
+ });
317
+ }
303
318
  }
304
319
  }
305
320
 
306
- const recommendations = this.generateUpdateRecommendations(changes, newFiles);
321
+ const recommendations = this.generateUpdateRecommendations(changes, newFiles, manifestIsFallback);
307
322
 
308
- spinner.succeed(`Found ${changes.length} updated files, ${newFiles.length} new files`);
323
+ if (manifestIsFallback) {
324
+ spinner.succeed(`Found ${changes.length} updated files, ${newFiles.length} new files (offline mode - limited change detection)`);
325
+ } else {
326
+ spinner.succeed(`Found ${changes.length} updated files, ${newFiles.length} new files`);
327
+ }
309
328
 
310
- // Get proper version information
311
- const currentVersion = this.config.localSource ? await this.getPackageVersion() : 'unknown';
312
329
  const availableVersion = remoteManifest.version;
313
330
 
314
331
  return {
315
332
  isFirstInstall: false,
316
- localVersion: currentVersion,
317
333
  remoteVersion: availableVersion,
318
334
  changes,
319
335
  newFiles,
320
- recommendations
336
+ recommendations,
337
+ manifestIsFallback
321
338
  };
322
339
 
323
340
  } catch (error) {
@@ -327,13 +344,14 @@ class CodeCaptainInstaller {
327
344
  changes: [],
328
345
  newFiles: [],
329
346
  recommendations: ['Unable to detect changes - consider full update'],
347
+ manifestIsFallback: true, // Assume fallback mode on error
330
348
  error: error.message
331
349
  };
332
350
  }
333
351
  }
334
352
 
335
353
  // Generate smart update recommendations
336
- generateUpdateRecommendations(changes, newFiles) {
354
+ generateUpdateRecommendations(changes, newFiles, manifestIsFallback = false) {
337
355
  const recommendations = [];
338
356
  const changedComponents = new Set();
339
357
 
@@ -344,61 +362,38 @@ class CodeCaptainInstaller {
344
362
  }
345
363
  });
346
364
 
347
- if (changedComponents.size === 0) {
348
- recommendations.push(' All files are up to date!');
349
- } else {
350
- recommendations.push(`📦 Recommended updates: ${Array.from(changedComponents).join(', ')}`);
365
+ if (manifestIsFallback) {
366
+ recommendations.push('⚠️ Operating in offline/fallback mode - limited change detection');
367
+ recommendations.push('📶 Consider checking internet connection for full update analysis');
351
368
 
352
- // Specific recommendations
353
- if (changedComponents.has('commands')) {
354
- recommendations.push('🚀 Commands updated - new features or bug fixes available');
355
- }
356
- if (changedComponents.has('rules')) {
357
- recommendations.push('⚙️ Rules updated - improved AI agent behavior');
369
+ if (changedComponents.size === 0) {
370
+ recommendations.push('📦 No local file changes detected - full reinstall recommended for latest updates');
371
+ } else {
372
+ recommendations.push(`📦 Local changes detected in: ${Array.from(changedComponents).join(', ')}`);
358
373
  }
359
- if (changedComponents.has('docs')) {
360
- recommendations.push('📚 Documentation updated - check for new best practices');
374
+ } else {
375
+ if (changedComponents.size === 0) {
376
+ recommendations.push('✅ All files are up to date!');
377
+ } else {
378
+ recommendations.push(`📦 Recommended updates: ${Array.from(changedComponents).join(', ')}`);
379
+
380
+ // Specific recommendations
381
+ if (changedComponents.has('commands')) {
382
+ recommendations.push('🚀 Commands updated - new features or bug fixes available');
383
+ }
384
+ if (changedComponents.has('rules')) {
385
+ recommendations.push('⚙️ Rules updated - improved AI agent behavior');
386
+ }
387
+ if (changedComponents.has('docs')) {
388
+ recommendations.push('📚 Documentation updated - check for new best practices');
389
+ }
361
390
  }
362
391
  }
363
392
 
364
393
  return recommendations;
365
394
  }
366
395
 
367
- // Save manifest after successful installation
368
- async saveManifest(remoteManifest, installedFiles) {
369
- try {
370
- const manifest = {
371
- ...remoteManifest,
372
- installedAt: new Date().toISOString(),
373
- files: {}
374
- };
375
-
376
- // Calculate actual hashes of installed files
377
- for (const file of installedFiles) {
378
- const localHash = await this.calculateFileHash(file.target);
379
396
 
380
- if (localHash) {
381
- // Use remote file info as base, but update with actual installed hash
382
- const remoteFileInfo = remoteManifest.files?.[file.source] || {};
383
-
384
- manifest.files[file.source] = {
385
- ...remoteFileInfo,
386
- hash: `sha256:${localHash}`,
387
- installedAt: new Date().toISOString(),
388
- actualSize: (await fs.stat(file.target).catch(() => ({ size: 0 }))).size
389
- };
390
- }
391
- }
392
-
393
- await fs.ensureDir(path.dirname(this.config.manifestFile));
394
- await fs.writeFile(this.config.manifestFile, JSON.stringify(manifest, null, 2));
395
-
396
- return true;
397
- } catch (error) {
398
- console.warn(chalk.yellow(`Warning: Could not save manifest: ${error.message}`));
399
- return false;
400
- }
401
- }
402
397
 
403
398
  // Auto-detect IDE preference
404
399
  detectIDE() {
@@ -415,7 +410,7 @@ class CodeCaptainInstaller {
415
410
  }
416
411
 
417
412
  // Check for Windsurf
418
- if (fs.pathExistsSync('windsurf') || this.commandExists('windsurf')) {
413
+ if (fs.pathExistsSync('.windsurf') || this.commandExists('windsurf')) {
419
414
  detections.push('windsurf');
420
415
  }
421
416
 
@@ -488,10 +483,16 @@ class CodeCaptainInstaller {
488
483
  console.log('\n' + chalk.bold.blue('🔍 Change Analysis'));
489
484
  console.log(chalk.gray('═'.repeat(50)));
490
485
 
486
+ // Show fallback mode warning if applicable
487
+ if (changeInfo.manifestIsFallback) {
488
+ console.log(chalk.yellow('⚠️ Operating in offline/fallback mode - limited change detection capabilities'));
489
+ console.log(chalk.gray(' Remote manifest unavailable - cannot detect all new files or verify latest versions'));
490
+ }
491
+
491
492
  // Show version information
492
- if (changeInfo.localVersion && changeInfo.remoteVersion) {
493
- console.log(chalk.blue('Current version:'), changeInfo.localVersion);
494
- console.log(chalk.blue('Available version:'), changeInfo.remoteVersion);
493
+ if (changeInfo.remoteVersion) {
494
+ const versionLabel = changeInfo.manifestIsFallback ? 'Local/fallback version:' : 'Available version:';
495
+ console.log(chalk.blue(versionLabel), changeInfo.remoteVersion);
495
496
  }
496
497
 
497
498
  // Show what's changed
@@ -605,16 +606,14 @@ class CodeCaptainInstaller {
605
606
  case 'cursor':
606
607
  return [
607
608
  ...baseChoices,
608
- { name: 'Cursor Rules (.cursor/rules/cc.mdc)', value: 'rules', checked: true },
609
- { name: 'GitHub Integration', value: 'github', checked: true },
610
- { name: 'Azure DevOps Integration', value: 'azure', checked: true }
609
+ { name: 'Cursor Rules (.cursor/rules/cc.mdc)', value: 'rules', checked: true }
611
610
  ];
612
611
 
613
612
  case 'copilot':
614
613
  return [
615
614
  ...baseChoices,
616
- { name: 'GitHub Copilot Chatmodes', value: 'chatmodes', checked: true },
617
- { name: 'GitHub Copilot Prompts', value: 'prompts', checked: true }
615
+ { name: 'Copilot Chatmodes', value: 'chatmodes', checked: true },
616
+ { name: 'Copilot Prompts', value: 'prompts', checked: true }
618
617
  ];
619
618
 
620
619
  case 'windsurf':
@@ -659,7 +658,10 @@ class CodeCaptainInstaller {
659
658
  // Show change summary
660
659
  const { changeInfo } = installOptions;
661
660
  if (changeInfo && (changeInfo.changes.length > 0 || changeInfo.newFiles.length > 0)) {
662
- console.log(chalk.blue('Files to update:'), `${changeInfo.changes.length} changed, ${changeInfo.newFiles.length} new`);
661
+ const modeIndicator = changeInfo.manifestIsFallback ? ' (offline mode)' : '';
662
+ console.log(chalk.blue('Files to update:'), `${changeInfo.changes.length} changed, ${changeInfo.newFiles.length} new${modeIndicator}`);
663
+ } else if (changeInfo && changeInfo.manifestIsFallback) {
664
+ console.log(chalk.blue('Installation mode:'), 'Offline/fallback mode - limited change detection');
663
665
  }
664
666
  } else {
665
667
  console.log(chalk.blue('Installation type:'), 'Full installation (new setup)');
@@ -677,29 +679,50 @@ class CodeCaptainInstaller {
677
679
  return confirmed;
678
680
  }
679
681
 
680
- // Create backup of existing file
681
- async createBackup(targetPath, shouldBackup = true) {
682
+ // Create directory-level backup of target directories
683
+ async createDirectoryBackups(files, shouldBackup = true) {
682
684
  if (!shouldBackup) {
683
- return null;
685
+ return [];
684
686
  }
685
687
 
686
- if (await fs.pathExists(targetPath)) {
687
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
688
- const backupPath = `${targetPath}.backup.${timestamp}`;
688
+ // Extract unique target directories from file list
689
+ const targetDirs = new Set();
690
+ files.forEach(file => {
691
+ const targetPath = file.target;
692
+ const normalizedTarget = path.normalize(targetPath);
693
+ const segments = normalizedTarget.split(path.sep).filter(Boolean);
694
+ const rootDir = segments[0]; // e.g., ".code-captain", ".cursor", ".github"
695
+ if (rootDir && rootDir.startsWith('.')) {
696
+ targetDirs.add(rootDir);
697
+ }
698
+ });
699
+
700
+ const backupPaths = [];
689
701
 
690
- try {
691
- await fs.copy(targetPath, backupPath);
692
- return backupPath;
693
- } catch (error) {
694
- console.warn(chalk.yellow(`Warning: Could not backup ${targetPath}: ${error.message}`));
695
- return null;
702
+ for (const dir of targetDirs) {
703
+ if (await fs.pathExists(dir)) {
704
+ const backupPath = `${dir}.backup`;
705
+
706
+ try {
707
+ // Remove existing backup if it exists
708
+ if (await fs.pathExists(backupPath)) {
709
+ await fs.remove(backupPath);
710
+ }
711
+
712
+ // Create directory backup
713
+ await fs.copy(dir, backupPath);
714
+ backupPaths.push(backupPath);
715
+ } catch (error) {
716
+ console.warn(chalk.yellow(`Warning: Could not backup ${dir}: ${error.message}`));
717
+ }
696
718
  }
697
719
  }
698
- return null;
720
+
721
+ return backupPaths;
699
722
  }
700
723
 
701
724
  // Download file from URL or local source
702
- async downloadFile(relativePath, targetPath, shouldBackup = true) {
725
+ async downloadFile(relativePath, targetPath) {
703
726
  try {
704
727
  let content;
705
728
 
@@ -710,7 +733,7 @@ class CodeCaptainInstaller {
710
733
  } else {
711
734
  // Remote download mode
712
735
  const url = `${this.config.baseUrl}/${relativePath}`;
713
- const response = await fetch(url);
736
+ const response = await this.fetchWithTimeout(url, {}, 20000);
714
737
 
715
738
  if (!response.ok) {
716
739
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
@@ -722,9 +745,6 @@ class CodeCaptainInstaller {
722
745
  // Ensure target directory exists
723
746
  await fs.ensureDir(path.dirname(targetPath));
724
747
 
725
- // Create backup if file exists
726
- await this.createBackup(targetPath, shouldBackup);
727
-
728
748
  // Write file
729
749
  await fs.writeFile(targetPath, content);
730
750
 
@@ -771,38 +791,7 @@ class CodeCaptainInstaller {
771
791
  });
772
792
  }
773
793
 
774
- // GitHub integration
775
- if (includeAll || selectedComponents.includes('github')) {
776
- const githubFiles = [
777
- 'integrations/github/create-github-issues.md',
778
- 'integrations/github/sync-github-issues.md',
779
- 'integrations/github/sync.md'
780
- ];
781
-
782
- githubFiles.forEach(file => {
783
- files.push({
784
- source: `cursor/${file}`,
785
- target: `.code-captain/${file}`,
786
- component: 'github'
787
- });
788
- });
789
- }
790
-
791
- // Azure DevOps integration
792
- if (includeAll || selectedComponents.includes('azure')) {
793
- const azureFiles = [
794
- 'integrations/azure-devops/create-azure-work-items.md',
795
- 'integrations/azure-devops/sync-azure-work-items.md'
796
- ];
797
794
 
798
- azureFiles.forEach(file => {
799
- files.push({
800
- source: `cursor/${file}`,
801
- target: `.code-captain/${file}`,
802
- component: 'azure'
803
- });
804
- });
805
- }
806
795
 
807
796
  // Documentation
808
797
  if (includeAll || selectedComponents.includes('docs')) {
@@ -856,7 +845,7 @@ class CodeCaptainInstaller {
856
845
  // Rules
857
846
  if (includeAll || selectedComponents.includes('rules')) {
858
847
  files.push(
859
- { source: 'windsurf/rules/cc.md', target: 'windsurf/rules/cc.md', component: 'rules' }
848
+ { source: 'windsurf/rules/cc.md', target: '.windsurf/rules/cc.md', component: 'rules' }
860
849
  );
861
850
  }
862
851
 
@@ -870,7 +859,7 @@ class CodeCaptainInstaller {
870
859
  windsurfWorkflows.forEach(workflow => {
871
860
  files.push({
872
861
  source: `windsurf/workflows/${workflow}`,
873
- target: `windsurf/workflows/${workflow}`,
862
+ target: `.windsurf/workflows/${workflow}`,
874
863
  component: 'workflows'
875
864
  });
876
865
  });
@@ -882,14 +871,14 @@ class CodeCaptainInstaller {
882
871
  // Claude agents
883
872
  if (includeAll || selectedComponents.includes('agents')) {
884
873
  const claudeAgents = [
885
- 'code-captain.md', 'spec-generator.md', 'spec-orchestrator.md',
874
+ 'code-captain.md', 'spec-generator.md',
886
875
  'story-creator.md', 'tech-spec.md'
887
876
  ];
888
877
 
889
878
  claudeAgents.forEach(agent => {
890
879
  files.push({
891
880
  source: `claude-code/agents/${agent}`,
892
- target: `.code-captain/claude/agents/${agent}`,
881
+ target: `.claude/agents/${agent}`,
893
882
  component: 'agents'
894
883
  });
895
884
  });
@@ -904,7 +893,7 @@ class CodeCaptainInstaller {
904
893
  claudeCommands.forEach(command => {
905
894
  files.push({
906
895
  source: `claude-code/commands/${command}`,
907
- target: `.code-captain/claude/commands/${command}`,
896
+ target: `.claude/commands/${command}`,
908
897
  component: 'claude-commands'
909
898
  });
910
899
  });
@@ -923,31 +912,35 @@ class CodeCaptainInstaller {
923
912
  const spinner = ora(`Installing ${this.ides[selectedIDE].name} integration...`).start();
924
913
 
925
914
  try {
926
- let completed = 0;
927
- const backupPaths = [];
915
+ // Create directory-level backups upfront if requested
916
+ const shouldBackup = installOptions.createBackups !== false; // Default to true if not specified
917
+ const backupPaths = await this.createDirectoryBackups(files, shouldBackup);
918
+
919
+ if (backupPaths.length > 0) {
920
+ spinner.text = `Created ${backupPaths.length} directory backup(s), installing files...`;
921
+ }
928
922
 
923
+ // Install all files
924
+ let completed = 0;
929
925
  for (const file of files) {
930
- const shouldBackup = installOptions.createBackups !== false; // Default to true if not specified
931
- await this.downloadFile(file.source, file.target, shouldBackup);
926
+ await this.downloadFile(file.source, file.target);
932
927
  completed++;
933
928
  spinner.text = `Installing files... (${completed}/${files.length})`;
934
929
  }
935
930
 
936
- // Save manifest for future change detection
937
- if (installOptions.changeInfo) {
938
- const remoteManifest = await this.getRemoteManifest();
939
- await this.saveManifest(remoteManifest, files);
940
- }
931
+
941
932
 
942
933
  spinner.succeed(`${this.ides[selectedIDE].name} integration installed successfully!`);
943
934
 
944
935
  return {
945
936
  totalFiles: files.length,
946
937
  targetDir: selectedIDE === 'copilot' ? '.github + .code-captain/docs' :
947
- selectedIDE === 'windsurf' ? 'windsurf' :
948
- selectedIDE === 'claude' ? '.code-captain/claude' : '.code-captain (+ .cursor/rules)',
938
+ selectedIDE === 'windsurf' ? '.windsurf' :
939
+ selectedIDE === 'claude' ? '.claude' : '.code-captain (+ .cursor/rules)',
949
940
  components: installOptions.installAll ? 'All components' : installOptions.selectedComponents.join(', '),
950
- changesDetected: installOptions.changeInfo && (installOptions.changeInfo.changes.length > 0 || installOptions.changeInfo.newFiles.length > 0)
941
+ changesDetected: installOptions.changeInfo && (installOptions.changeInfo.changes.length > 0 || installOptions.changeInfo.newFiles.length > 0),
942
+ backupsCreated: backupPaths.length > 0,
943
+ backupPaths: backupPaths
951
944
  };
952
945
  } catch (error) {
953
946
  spinner.fail('Installation failed');
@@ -986,7 +979,7 @@ class CodeCaptainInstaller {
986
979
 
987
980
  case 'copilot':
988
981
  console.log(chalk.blue('1.') + ' Restart VS Code to load chatmodes from ' + chalk.cyan('.github/chatmodes/'));
989
- console.log(chalk.blue('2.') + ' Open GitHub Copilot Chat in VS Code');
982
+ console.log(chalk.blue('2.') + ' Open Copilot Chat in VS Code');
990
983
  console.log(chalk.blue('3.') + ' Type ' + chalk.cyan('@Code Captain') + ' to access the chatmode');
991
984
  console.log(chalk.blue('4.') + ' Use prompts from ' + chalk.cyan('.github/prompts/') + ' for workflows');
992
985
  break;
@@ -998,9 +991,9 @@ class CodeCaptainInstaller {
998
991
  break;
999
992
 
1000
993
  case 'claude':
1001
- console.log(chalk.blue('1.') + ' Claude agents and commands are installed in ' + chalk.cyan('.code-captain/claude/'));
1002
- console.log(chalk.blue('2.') + ' Reference the agents in ' + chalk.cyan('.code-captain/claude/agents/') + ' for specialized workflows');
1003
- console.log(chalk.blue('3.') + ' Use command templates from ' + chalk.cyan('.code-captain/claude/commands/'));
994
+ console.log(chalk.blue('1.') + ' Claude agents and commands are installed in ' + chalk.cyan('.claude/'));
995
+ console.log(chalk.blue('2.') + ' Reference the agents in ' + chalk.cyan('.claude/agents/') + ' for specialized workflows');
996
+ console.log(chalk.blue('3.') + ' Use command templates from ' + chalk.cyan('.claude/commands/'));
1004
997
  console.log(chalk.blue('4.') + ' Import agent contexts directly into Claude conversations');
1005
998
  break;
1006
999
  }
@@ -1009,10 +1002,13 @@ class CodeCaptainInstaller {
1009
1002
  console.log(chalk.gray('Documentation: https://github.com/devobsessed/code-captain'));
1010
1003
 
1011
1004
  // Show backup information if backups were created
1012
- if (installResult.totalFiles > 0) {
1005
+ if (installResult.backupsCreated) {
1013
1006
  console.log('\n' + chalk.yellow('💾 Backup Information:'));
1014
- console.log(chalk.gray('Existing files were backed up with timestamps (e.g., filename.backup.2024-01-01T12-00-00-000Z)'));
1015
- console.log(chalk.gray('You can safely delete backup files once you\'re satisfied with the installation.'));
1007
+ console.log(chalk.gray('The following directories were backed up:'));
1008
+ installResult.backupPaths.forEach(backupPath => {
1009
+ console.log(chalk.gray(` • ${backupPath}/`));
1010
+ });
1011
+ console.log(chalk.gray('You can safely delete these backup directories once you\'re satisfied with the installation.'));
1016
1012
  }
1017
1013
  }
1018
1014