@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.
- package/README.md +1 -10
- package/bin/install.js +186 -187
- 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,29 +34,32 @@ 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
|
}
|
|
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(
|
|
60
|
-
chalk.gray('
|
|
61
|
-
chalk.
|
|
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
|
|
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
|
-
|
|
184
|
+
const manifest = JSON.parse(content);
|
|
185
|
+
return { manifest, isFallback: false };
|
|
173
186
|
}
|
|
174
187
|
} else {
|
|
175
|
-
const response = await
|
|
188
|
+
const response = await this.fetchWithTimeout(manifestUrl, {}, 15000);
|
|
176
189
|
if (response.ok) {
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
219
|
-
|
|
220
|
-
this.getLocalManifest()
|
|
221
|
-
]);
|
|
223
|
+
const { manifest: remoteManifest, isFallback: manifestIsFallback } = await this.getRemoteManifest();
|
|
224
|
+
const files = this.getIDEFiles(selectedIDE);
|
|
222
225
|
|
|
223
|
-
if (
|
|
224
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
247
|
+
recommendations: ['Full installation recommended'],
|
|
248
|
+
manifestIsFallback
|
|
237
249
|
};
|
|
238
250
|
}
|
|
239
251
|
|
|
240
|
-
|
|
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 -
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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 (
|
|
345
|
-
recommendations.push('
|
|
346
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
357
|
-
|
|
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.
|
|
490
|
-
|
|
491
|
-
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);
|
|
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: '
|
|
614
|
-
{ name: '
|
|
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
|
-
|
|
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
|
|
678
|
-
async
|
|
682
|
+
// Create directory-level backup of target directories
|
|
683
|
+
async createDirectoryBackups(files, shouldBackup = true) {
|
|
679
684
|
if (!shouldBackup) {
|
|
680
|
-
return
|
|
685
|
+
return [];
|
|
681
686
|
}
|
|
682
687
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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
|
-
|
|
720
|
+
|
|
721
|
+
return backupPaths;
|
|
696
722
|
}
|
|
697
723
|
|
|
698
724
|
// Download file from URL or local source
|
|
699
|
-
async downloadFile(relativePath, targetPath
|
|
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
|
|
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:
|
|
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',
|
|
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: `.
|
|
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: `.
|
|
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
|
-
|
|
924
|
-
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
|
+
}
|
|
925
922
|
|
|
923
|
+
// Install all files
|
|
924
|
+
let completed = 0;
|
|
926
925
|
for (const file of files) {
|
|
927
|
-
|
|
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
|
-
|
|
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' ? '.
|
|
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
|
|
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('.
|
|
999
|
-
console.log(chalk.blue('2.') + ' Reference the agents in ' + chalk.cyan('.
|
|
1000
|
-
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/'));
|
|
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.
|
|
1005
|
+
if (installResult.backupsCreated) {
|
|
1010
1006
|
console.log('\n' + chalk.yellow('💾 Backup Information:'));
|
|
1011
|
-
console.log(chalk.gray('
|
|
1012
|
-
|
|
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();
|