@devobsessed/code-captain 0.0.5 → 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 +186 -187
  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,29 +34,32 @@ 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
  }
55
53
 
56
54
  // Display welcome banner
57
- showWelcome() {
55
+ async showWelcome() {
56
+ const version = await this.getPackageVersion();
58
57
  const banner = boxen(
59
- chalk.bold.green('Code Captain 2.0') + '\n' +
60
- chalk.gray('Unified AI Development Agent System') + '\n\n' +
61
- chalk.blue('🚀 Interactive Installation Wizard'),
58
+ chalk.bold.green(`Code Captain ${version}`) + '\n' +
59
+ chalk.gray('AI Development Agent System') + '\n' +
60
+ chalk.dim('brought to you by DevObsessed') + '\n' +
61
+ chalk.dim.blue('https://www.devobsessed.com/') + '\n\n' +
62
+ chalk.blue('⚓ Interactive Installation Wizard'),
62
63
  {
63
64
  padding: 1,
64
65
  margin: 1,
@@ -116,7 +117,7 @@ class CodeCaptainInstaller {
116
117
  'Code Captain Core': ['.code-captain/'],
117
118
  'Cursor Integration': ['.cursor/rules/cc.mdc', '.cursor/rules/'],
118
119
  'Copilot Integration': ['.github/chatmodes/', '.github/prompts/'],
119
- 'Windsurf Integration': ['windsurf/rules/', 'windsurf/workflows/'],
120
+ 'Windsurf Integration': ['.windsurf/rules/', '.windsurf/workflows/'],
120
121
  'Claude Integration': ['.code-captain/claude/', 'claude-code/', '.claude/'],
121
122
  'Legacy Structure': ['cursor/', 'copilot/', 'windsurf/']
122
123
  };
@@ -153,13 +154,24 @@ class CodeCaptainInstaller {
153
154
  async calculateFileHash(filePath) {
154
155
  try {
155
156
  const crypto = await import('crypto');
156
- const content = await fs.readFile(filePath, 'utf8');
157
+ const content = await fs.readFile(filePath); // Buffer
157
158
  return crypto.default.createHash('sha256').update(content).digest('hex');
158
159
  } catch (error) {
159
160
  return null;
160
161
  }
161
162
  }
162
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
+
163
175
  // Get remote manifest with file versions/hashes
164
176
  async getRemoteManifest() {
165
177
  try {
@@ -169,20 +181,24 @@ class CodeCaptainInstaller {
169
181
  const localManifestPath = path.join(this.config.localSource, 'manifest.json');
170
182
  if (await fs.pathExists(localManifestPath)) {
171
183
  const content = await fs.readFile(localManifestPath, 'utf8');
172
- return JSON.parse(content);
184
+ const manifest = JSON.parse(content);
185
+ return { manifest, isFallback: false };
173
186
  }
174
187
  } else {
175
- const response = await fetch(manifestUrl);
188
+ const response = await this.fetchWithTimeout(manifestUrl, {}, 15000);
176
189
  if (response.ok) {
177
- return await response.json();
190
+ const manifest = await response.json();
191
+ return { manifest, isFallback: false };
178
192
  }
179
193
  }
180
194
 
181
195
  // Fallback: generate manifest from current commit
182
- return await this.generateFallbackManifest();
196
+ const fallbackManifest = await this.generateFallbackManifest();
197
+ return { manifest: fallbackManifest, isFallback: true };
183
198
  } catch (error) {
184
199
  console.warn(chalk.yellow('Warning: Could not fetch remote manifest, using fallback'));
185
- return await this.generateFallbackManifest();
200
+ const fallbackManifest = await this.generateFallbackManifest();
201
+ return { manifest: fallbackManifest, isFallback: true };
186
202
  }
187
203
  }
188
204
 
@@ -197,47 +213,43 @@ class CodeCaptainInstaller {
197
213
  };
198
214
  }
199
215
 
200
- // Get local manifest if it exists
201
- async getLocalManifest() {
202
- try {
203
- if (await fs.pathExists(this.config.manifestFile)) {
204
- const content = await fs.readFile(this.config.manifestFile, 'utf8');
205
- return JSON.parse(content);
206
- }
207
- } catch (error) {
208
- console.warn(chalk.yellow('Warning: Could not read local manifest'));
209
- }
210
- return null;
211
- }
212
216
 
213
- // Compare manifests and detect changes
217
+
218
+ // Compare current files with remote manifest to detect changes
214
219
  async detectChanges(selectedIDE) {
215
220
  const spinner = ora('Analyzing file changes...').start();
216
221
 
217
222
  try {
218
- const [remoteManifest, localManifest] = await Promise.all([
219
- this.getRemoteManifest(),
220
- this.getLocalManifest()
221
- ]);
223
+ const { manifest: remoteManifest, isFallback: manifestIsFallback } = await this.getRemoteManifest();
224
+ const files = this.getIDEFiles(selectedIDE);
222
225
 
223
- if (!localManifest) {
224
- 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
+ }
225
233
 
226
- // Get proper version information for first install
227
- const currentVersion = this.config.localSource ? await this.getPackageVersion() : 'unknown';
228
- 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
+ }
229
240
 
241
+ const availableVersion = remoteManifest.version;
230
242
  return {
231
243
  isFirstInstall: true,
232
- localVersion: currentVersion,
233
244
  remoteVersion: availableVersion,
234
245
  changes: [],
235
246
  newFiles: [],
236
- recommendations: ['Full installation recommended (no change tracking available)']
247
+ recommendations: ['Full installation recommended'],
248
+ manifestIsFallback
237
249
  };
238
250
  }
239
251
 
240
- const files = this.getIDEFiles(selectedIDE);
252
+ // Analyze existing files for changes
241
253
  const changes = [];
242
254
  const newFiles = [];
243
255
  let filesAnalyzed = 0;
@@ -283,7 +295,6 @@ class CodeCaptainInstaller {
283
295
  changes.push({
284
296
  file: remotePath,
285
297
  component: file.component,
286
- localVersion: localManifest.files?.[remotePath]?.version || 'unknown',
287
298
  remoteVersion: remoteFileInfo.version || 'latest',
288
299
  reason: 'File content has changed',
289
300
  localHash: localFileHash.substring(0, 8),
@@ -291,30 +302,39 @@ class CodeCaptainInstaller {
291
302
  });
292
303
  }
293
304
  } else {
294
- // No remote file info - treat as new in remote
295
- newFiles.push({
296
- file: remotePath,
297
- component: file.component,
298
- reason: 'New file in remote repository'
299
- });
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
+ }
300
318
  }
301
319
  }
302
320
 
303
- const recommendations = this.generateUpdateRecommendations(changes, newFiles);
321
+ const recommendations = this.generateUpdateRecommendations(changes, newFiles, manifestIsFallback);
304
322
 
305
- 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
+ }
306
328
 
307
- // Get proper version information
308
- const currentVersion = this.config.localSource ? await this.getPackageVersion() : 'unknown';
309
329
  const availableVersion = remoteManifest.version;
310
330
 
311
331
  return {
312
332
  isFirstInstall: false,
313
- localVersion: currentVersion,
314
333
  remoteVersion: availableVersion,
315
334
  changes,
316
335
  newFiles,
317
- recommendations
336
+ recommendations,
337
+ manifestIsFallback
318
338
  };
319
339
 
320
340
  } catch (error) {
@@ -324,13 +344,14 @@ class CodeCaptainInstaller {
324
344
  changes: [],
325
345
  newFiles: [],
326
346
  recommendations: ['Unable to detect changes - consider full update'],
347
+ manifestIsFallback: true, // Assume fallback mode on error
327
348
  error: error.message
328
349
  };
329
350
  }
330
351
  }
331
352
 
332
353
  // Generate smart update recommendations
333
- generateUpdateRecommendations(changes, newFiles) {
354
+ generateUpdateRecommendations(changes, newFiles, manifestIsFallback = false) {
334
355
  const recommendations = [];
335
356
  const changedComponents = new Set();
336
357
 
@@ -341,61 +362,38 @@ class CodeCaptainInstaller {
341
362
  }
342
363
  });
343
364
 
344
- if (changedComponents.size === 0) {
345
- recommendations.push(' All files are up to date!');
346
- } else {
347
- 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');
348
368
 
349
- // Specific recommendations
350
- if (changedComponents.has('commands')) {
351
- recommendations.push('🚀 Commands updated - new features or bug fixes available');
352
- }
353
- if (changedComponents.has('rules')) {
354
- 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(', ')}`);
355
373
  }
356
- if (changedComponents.has('docs')) {
357
- 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
+ }
358
390
  }
359
391
  }
360
392
 
361
393
  return recommendations;
362
394
  }
363
395
 
364
- // Save manifest after successful installation
365
- async saveManifest(remoteManifest, installedFiles) {
366
- try {
367
- const manifest = {
368
- ...remoteManifest,
369
- installedAt: new Date().toISOString(),
370
- files: {}
371
- };
372
-
373
- // Calculate actual hashes of installed files
374
- for (const file of installedFiles) {
375
- const localHash = await this.calculateFileHash(file.target);
376
396
 
377
- if (localHash) {
378
- // Use remote file info as base, but update with actual installed hash
379
- const remoteFileInfo = remoteManifest.files?.[file.source] || {};
380
-
381
- manifest.files[file.source] = {
382
- ...remoteFileInfo,
383
- hash: `sha256:${localHash}`,
384
- installedAt: new Date().toISOString(),
385
- actualSize: (await fs.stat(file.target).catch(() => ({ size: 0 }))).size
386
- };
387
- }
388
- }
389
-
390
- await fs.ensureDir(path.dirname(this.config.manifestFile));
391
- await fs.writeFile(this.config.manifestFile, JSON.stringify(manifest, null, 2));
392
-
393
- return true;
394
- } catch (error) {
395
- console.warn(chalk.yellow(`Warning: Could not save manifest: ${error.message}`));
396
- return false;
397
- }
398
- }
399
397
 
400
398
  // Auto-detect IDE preference
401
399
  detectIDE() {
@@ -412,7 +410,7 @@ class CodeCaptainInstaller {
412
410
  }
413
411
 
414
412
  // Check for Windsurf
415
- if (fs.pathExistsSync('windsurf') || this.commandExists('windsurf')) {
413
+ if (fs.pathExistsSync('.windsurf') || this.commandExists('windsurf')) {
416
414
  detections.push('windsurf');
417
415
  }
418
416
 
@@ -485,10 +483,16 @@ class CodeCaptainInstaller {
485
483
  console.log('\n' + chalk.bold.blue('🔍 Change Analysis'));
486
484
  console.log(chalk.gray('═'.repeat(50)));
487
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
+
488
492
  // Show version information
489
- if (changeInfo.localVersion && changeInfo.remoteVersion) {
490
- console.log(chalk.blue('Current version:'), changeInfo.localVersion);
491
- 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);
492
496
  }
493
497
 
494
498
  // Show what's changed
@@ -602,16 +606,14 @@ class CodeCaptainInstaller {
602
606
  case 'cursor':
603
607
  return [
604
608
  ...baseChoices,
605
- { name: 'Cursor Rules (.cursor/rules/cc.mdc)', value: 'rules', checked: true },
606
- { name: 'GitHub Integration', value: 'github', checked: true },
607
- { name: 'Azure DevOps Integration', value: 'azure', checked: true }
609
+ { name: 'Cursor Rules (.cursor/rules/cc.mdc)', value: 'rules', checked: true }
608
610
  ];
609
611
 
610
612
  case 'copilot':
611
613
  return [
612
614
  ...baseChoices,
613
- { name: 'GitHub Copilot Chatmodes', value: 'chatmodes', checked: true },
614
- { name: 'GitHub Copilot Prompts', value: 'prompts', checked: true }
615
+ { name: 'Copilot Chatmodes', value: 'chatmodes', checked: true },
616
+ { name: 'Copilot Prompts', value: 'prompts', checked: true }
615
617
  ];
616
618
 
617
619
  case 'windsurf':
@@ -656,7 +658,10 @@ class CodeCaptainInstaller {
656
658
  // Show change summary
657
659
  const { changeInfo } = installOptions;
658
660
  if (changeInfo && (changeInfo.changes.length > 0 || changeInfo.newFiles.length > 0)) {
659
- 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');
660
665
  }
661
666
  } else {
662
667
  console.log(chalk.blue('Installation type:'), 'Full installation (new setup)');
@@ -674,29 +679,50 @@ class CodeCaptainInstaller {
674
679
  return confirmed;
675
680
  }
676
681
 
677
- // Create backup of existing file
678
- async createBackup(targetPath, shouldBackup = true) {
682
+ // Create directory-level backup of target directories
683
+ async createDirectoryBackups(files, shouldBackup = true) {
679
684
  if (!shouldBackup) {
680
- return null;
685
+ return [];
681
686
  }
682
687
 
683
- if (await fs.pathExists(targetPath)) {
684
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
685
- 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 = [];
686
701
 
687
- try {
688
- await fs.copy(targetPath, backupPath);
689
- return backupPath;
690
- } catch (error) {
691
- console.warn(chalk.yellow(`Warning: Could not backup ${targetPath}: ${error.message}`));
692
- 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
+ }
693
718
  }
694
719
  }
695
- return null;
720
+
721
+ return backupPaths;
696
722
  }
697
723
 
698
724
  // Download file from URL or local source
699
- async downloadFile(relativePath, targetPath, shouldBackup = true) {
725
+ async downloadFile(relativePath, targetPath) {
700
726
  try {
701
727
  let content;
702
728
 
@@ -707,7 +733,7 @@ class CodeCaptainInstaller {
707
733
  } else {
708
734
  // Remote download mode
709
735
  const url = `${this.config.baseUrl}/${relativePath}`;
710
- const response = await fetch(url);
736
+ const response = await this.fetchWithTimeout(url, {}, 20000);
711
737
 
712
738
  if (!response.ok) {
713
739
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
@@ -719,9 +745,6 @@ class CodeCaptainInstaller {
719
745
  // Ensure target directory exists
720
746
  await fs.ensureDir(path.dirname(targetPath));
721
747
 
722
- // Create backup if file exists
723
- await this.createBackup(targetPath, shouldBackup);
724
-
725
748
  // Write file
726
749
  await fs.writeFile(targetPath, content);
727
750
 
@@ -768,38 +791,7 @@ class CodeCaptainInstaller {
768
791
  });
769
792
  }
770
793
 
771
- // GitHub integration
772
- if (includeAll || selectedComponents.includes('github')) {
773
- const githubFiles = [
774
- 'integrations/github/create-github-issues.md',
775
- 'integrations/github/sync-github-issues.md',
776
- 'integrations/github/sync.md'
777
- ];
778
-
779
- githubFiles.forEach(file => {
780
- files.push({
781
- source: `cursor/${file}`,
782
- target: `.code-captain/${file}`,
783
- component: 'github'
784
- });
785
- });
786
- }
787
-
788
- // Azure DevOps integration
789
- if (includeAll || selectedComponents.includes('azure')) {
790
- const azureFiles = [
791
- 'integrations/azure-devops/create-azure-work-items.md',
792
- 'integrations/azure-devops/sync-azure-work-items.md'
793
- ];
794
794
 
795
- azureFiles.forEach(file => {
796
- files.push({
797
- source: `cursor/${file}`,
798
- target: `.code-captain/${file}`,
799
- component: 'azure'
800
- });
801
- });
802
- }
803
795
 
804
796
  // Documentation
805
797
  if (includeAll || selectedComponents.includes('docs')) {
@@ -853,7 +845,7 @@ class CodeCaptainInstaller {
853
845
  // Rules
854
846
  if (includeAll || selectedComponents.includes('rules')) {
855
847
  files.push(
856
- { 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' }
857
849
  );
858
850
  }
859
851
 
@@ -867,7 +859,7 @@ class CodeCaptainInstaller {
867
859
  windsurfWorkflows.forEach(workflow => {
868
860
  files.push({
869
861
  source: `windsurf/workflows/${workflow}`,
870
- target: `windsurf/workflows/${workflow}`,
862
+ target: `.windsurf/workflows/${workflow}`,
871
863
  component: 'workflows'
872
864
  });
873
865
  });
@@ -879,14 +871,14 @@ class CodeCaptainInstaller {
879
871
  // Claude agents
880
872
  if (includeAll || selectedComponents.includes('agents')) {
881
873
  const claudeAgents = [
882
- 'code-captain.md', 'spec-generator.md', 'spec-orchestrator.md',
874
+ 'code-captain.md', 'spec-generator.md',
883
875
  'story-creator.md', 'tech-spec.md'
884
876
  ];
885
877
 
886
878
  claudeAgents.forEach(agent => {
887
879
  files.push({
888
880
  source: `claude-code/agents/${agent}`,
889
- target: `.code-captain/claude/agents/${agent}`,
881
+ target: `.claude/agents/${agent}`,
890
882
  component: 'agents'
891
883
  });
892
884
  });
@@ -901,7 +893,7 @@ class CodeCaptainInstaller {
901
893
  claudeCommands.forEach(command => {
902
894
  files.push({
903
895
  source: `claude-code/commands/${command}`,
904
- target: `.code-captain/claude/commands/${command}`,
896
+ target: `.claude/commands/${command}`,
905
897
  component: 'claude-commands'
906
898
  });
907
899
  });
@@ -920,31 +912,35 @@ class CodeCaptainInstaller {
920
912
  const spinner = ora(`Installing ${this.ides[selectedIDE].name} integration...`).start();
921
913
 
922
914
  try {
923
- let completed = 0;
924
- 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
+ }
925
922
 
923
+ // Install all files
924
+ let completed = 0;
926
925
  for (const file of files) {
927
- const shouldBackup = installOptions.createBackups !== false; // Default to true if not specified
928
- await this.downloadFile(file.source, file.target, shouldBackup);
926
+ await this.downloadFile(file.source, file.target);
929
927
  completed++;
930
928
  spinner.text = `Installing files... (${completed}/${files.length})`;
931
929
  }
932
930
 
933
- // Save manifest for future change detection
934
- if (installOptions.changeInfo) {
935
- const remoteManifest = await this.getRemoteManifest();
936
- await this.saveManifest(remoteManifest, files);
937
- }
931
+
938
932
 
939
933
  spinner.succeed(`${this.ides[selectedIDE].name} integration installed successfully!`);
940
934
 
941
935
  return {
942
936
  totalFiles: files.length,
943
937
  targetDir: selectedIDE === 'copilot' ? '.github + .code-captain/docs' :
944
- selectedIDE === 'windsurf' ? 'windsurf' :
945
- selectedIDE === 'claude' ? '.code-captain/claude' : '.code-captain (+ .cursor/rules)',
938
+ selectedIDE === 'windsurf' ? '.windsurf' :
939
+ selectedIDE === 'claude' ? '.claude' : '.code-captain (+ .cursor/rules)',
946
940
  components: installOptions.installAll ? 'All components' : installOptions.selectedComponents.join(', '),
947
- 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
948
944
  };
949
945
  } catch (error) {
950
946
  spinner.fail('Installation failed');
@@ -983,7 +979,7 @@ class CodeCaptainInstaller {
983
979
 
984
980
  case 'copilot':
985
981
  console.log(chalk.blue('1.') + ' Restart VS Code to load chatmodes from ' + chalk.cyan('.github/chatmodes/'));
986
- console.log(chalk.blue('2.') + ' Open GitHub Copilot Chat in VS Code');
982
+ console.log(chalk.blue('2.') + ' Open Copilot Chat in VS Code');
987
983
  console.log(chalk.blue('3.') + ' Type ' + chalk.cyan('@Code Captain') + ' to access the chatmode');
988
984
  console.log(chalk.blue('4.') + ' Use prompts from ' + chalk.cyan('.github/prompts/') + ' for workflows');
989
985
  break;
@@ -995,9 +991,9 @@ class CodeCaptainInstaller {
995
991
  break;
996
992
 
997
993
  case 'claude':
998
- console.log(chalk.blue('1.') + ' Claude agents and commands are installed in ' + chalk.cyan('.code-captain/claude/'));
999
- console.log(chalk.blue('2.') + ' Reference the agents in ' + chalk.cyan('.code-captain/claude/agents/') + ' for specialized workflows');
1000
- 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/'));
1001
997
  console.log(chalk.blue('4.') + ' Import agent contexts directly into Claude conversations');
1002
998
  break;
1003
999
  }
@@ -1006,10 +1002,13 @@ class CodeCaptainInstaller {
1006
1002
  console.log(chalk.gray('Documentation: https://github.com/devobsessed/code-captain'));
1007
1003
 
1008
1004
  // Show backup information if backups were created
1009
- if (installResult.totalFiles > 0) {
1005
+ if (installResult.backupsCreated) {
1010
1006
  console.log('\n' + chalk.yellow('💾 Backup Information:'));
1011
- console.log(chalk.gray('Existing files were backed up with timestamps (e.g., filename.backup.2024-01-01T12-00-00-000Z)'));
1012
- 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.'));
1013
1012
  }
1014
1013
  }
1015
1014
 
@@ -1034,7 +1033,7 @@ class CodeCaptainInstaller {
1034
1033
  async run() {
1035
1034
  try {
1036
1035
  // Show welcome
1037
- this.showWelcome();
1036
+ await this.showWelcome();
1038
1037
 
1039
1038
  // Check compatibility
1040
1039
  const systemInfo = await this.checkCompatibility();