@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.
- package/README.md +1 -10
- package/bin/install.js +178 -182
- package/claude-code/agents/code-captain.md +17 -20
- package/copilot/README.md +26 -16
- package/copilot/chatmodes/Code Captain.chatmode.md +11 -16
- package/copilot/prompts/create-spec.prompt.md +5 -8
- package/copilot/prompts/explain-code.prompt.md +5 -8
- package/copilot/prompts/new-command.prompt.md +60 -21
- package/copilot/prompts/research.prompt.md +5 -8
- package/copilot/prompts/status.prompt.md +13 -2
- package/copilot/prompts/swab.prompt.md +1 -0
- package/cursor/README.md +8 -23
- package/cursor/cc.md +2 -29
- package/cursor/cc.mdc +3 -10
- package/cursor/commands/create-adr.md +1 -1
- package/cursor/commands/create-spec.md +9 -12
- package/cursor/commands/explain-code.md +5 -8
- package/cursor/commands/initialize.md +1 -1
- package/cursor/commands/new-command.md +5 -4
- package/cursor/commands/research.md +6 -9
- package/cursor/commands/status.md +13 -2
- package/cursor/commands/swab.md +61 -2
- package/manifest.json +150 -166
- package/package.json +12 -2
- package/windsurf/workflows/explain-code.md +4 -8
- package/windsurf/workflows/plan-product.md +330 -0
- package/windsurf/workflows/research.md +240 -0
- package/windsurf/workflows/swab.md +212 -0
- package/cursor/integrations/azure-devops/create-azure-work-items.md +0 -403
- package/cursor/integrations/azure-devops/sync-azure-work-items.md +0 -486
- package/cursor/integrations/github/create-github-issues.md +0 -765
- package/cursor/integrations/github/scripts/create-issues-batch.sh +0 -272
- package/cursor/integrations/github/sync-github-issues.md +0 -237
- 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: '
|
|
40
|
-
description: 'Visual Studio Code with
|
|
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 .
|
|
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
|
|
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
|
-
|
|
184
|
+
const manifest = JSON.parse(content);
|
|
185
|
+
return { manifest, isFallback: false };
|
|
176
186
|
}
|
|
177
187
|
} else {
|
|
178
|
-
const response = await
|
|
188
|
+
const response = await this.fetchWithTimeout(manifestUrl, {}, 15000);
|
|
179
189
|
if (response.ok) {
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
222
|
-
|
|
223
|
-
this.getLocalManifest()
|
|
224
|
-
]);
|
|
223
|
+
const { manifest: remoteManifest, isFallback: manifestIsFallback } = await this.getRemoteManifest();
|
|
224
|
+
const files = this.getIDEFiles(selectedIDE);
|
|
225
225
|
|
|
226
|
-
if (
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
247
|
+
recommendations: ['Full installation recommended'],
|
|
248
|
+
manifestIsFallback
|
|
240
249
|
};
|
|
241
250
|
}
|
|
242
251
|
|
|
243
|
-
|
|
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 -
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
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 (
|
|
348
|
-
recommendations.push('
|
|
349
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
360
|
-
|
|
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.
|
|
493
|
-
|
|
494
|
-
console.log(chalk.blue(
|
|
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: '
|
|
617
|
-
{ name: '
|
|
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
|
-
|
|
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
|
|
681
|
-
async
|
|
682
|
+
// Create directory-level backup of target directories
|
|
683
|
+
async createDirectoryBackups(files, shouldBackup = true) {
|
|
682
684
|
if (!shouldBackup) {
|
|
683
|
-
return
|
|
685
|
+
return [];
|
|
684
686
|
}
|
|
685
687
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
-
|
|
720
|
+
|
|
721
|
+
return backupPaths;
|
|
699
722
|
}
|
|
700
723
|
|
|
701
724
|
// Download file from URL or local source
|
|
702
|
-
async downloadFile(relativePath, targetPath
|
|
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
|
|
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:
|
|
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',
|
|
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: `.
|
|
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: `.
|
|
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
|
-
|
|
927
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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' ? '.
|
|
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
|
|
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('.
|
|
1002
|
-
console.log(chalk.blue('2.') + ' Reference the agents in ' + chalk.cyan('.
|
|
1003
|
-
console.log(chalk.blue('3.') + ' Use command templates from ' + chalk.cyan('.
|
|
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.
|
|
1005
|
+
if (installResult.backupsCreated) {
|
|
1013
1006
|
console.log('\n' + chalk.yellow('💾 Backup Information:'));
|
|
1014
|
-
console.log(chalk.gray('
|
|
1015
|
-
|
|
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
|
|