@devobsessed/code-captain 0.3.2 → 0.4.0
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/bin/install.js +176 -15
- package/copilot/Directory.Build.props +5 -5
- package/copilot/code-captain.csproj +14 -0
- package/manifest.json +11 -3
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -454,6 +454,156 @@ class CodeCaptainInstaller {
|
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
+
// Generate a random GUID (uppercase)
|
|
458
|
+
generateGuid() {
|
|
459
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
|
|
460
|
+
.replace(/[xy]/g, (c) => {
|
|
461
|
+
const r = (Math.random() * 16) | 0;
|
|
462
|
+
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
|
|
463
|
+
})
|
|
464
|
+
.toUpperCase();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Add code-captain.csproj to the first .sln file found in the current directory
|
|
468
|
+
async addProjectToSln(slnPath, csprojFileName) {
|
|
469
|
+
const content = await fs.readFile(slnPath, "utf8");
|
|
470
|
+
const projectName = path.basename(csprojFileName, ".csproj");
|
|
471
|
+
const idMarker = `"${csprojFileName}"`;
|
|
472
|
+
|
|
473
|
+
if (content.includes(idMarker)) return "up-to-date";
|
|
474
|
+
|
|
475
|
+
const projectGuid = this.generateGuid();
|
|
476
|
+
// SDK-style C# project type GUID
|
|
477
|
+
const typeGuid = "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}";
|
|
478
|
+
const projectBlock =
|
|
479
|
+
`Project("${typeGuid}") = "${projectName}", "${csprojFileName}", "{${projectGuid}}"\n` +
|
|
480
|
+
`EndProject\n`;
|
|
481
|
+
|
|
482
|
+
// Insert before the Global section
|
|
483
|
+
let updated = content.replace(/^(Global\r?\n)/m, `${projectBlock}$1`);
|
|
484
|
+
|
|
485
|
+
// Parse solution configs and add ActiveCfg entries (but not Build.0, so it's excluded from builds)
|
|
486
|
+
const configSectionMatch = content.match(
|
|
487
|
+
/GlobalSection\(SolutionConfigurationPlatforms\)[^\n]*\n([\s\S]*?)EndGlobalSection/
|
|
488
|
+
);
|
|
489
|
+
if (configSectionMatch) {
|
|
490
|
+
const configLines = configSectionMatch[1]
|
|
491
|
+
.split(/\r?\n/)
|
|
492
|
+
.map((l) => l.trim())
|
|
493
|
+
.filter((l) => l.includes(" = "));
|
|
494
|
+
const configNames = configLines.map((l) => l.split(" = ")[0]);
|
|
495
|
+
|
|
496
|
+
if (configNames.length > 0) {
|
|
497
|
+
const activeCfgEntries = configNames
|
|
498
|
+
.map((c) => `\t\t{${projectGuid}}.${c}.ActiveCfg = ${c}`)
|
|
499
|
+
.join("\n");
|
|
500
|
+
|
|
501
|
+
updated = updated.replace(
|
|
502
|
+
/(GlobalSection\(ProjectConfigurationPlatforms\)[^\n]*\n)([\s\S]*?)(EndGlobalSection)/,
|
|
503
|
+
`$1$2${activeCfgEntries}\n\t$3`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
await fs.copy(slnPath, `${slnPath}.backup`);
|
|
509
|
+
await fs.writeFile(slnPath, updated);
|
|
510
|
+
return "updated";
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Remove Code Captain ItemGroup from Directory.Build.props (migration from old approach)
|
|
514
|
+
async migrateDirectoryBuildProps() {
|
|
515
|
+
const targetPath = "Directory.Build.props";
|
|
516
|
+
if (!(await fs.pathExists(targetPath))) return null;
|
|
517
|
+
|
|
518
|
+
const content = await fs.readFile(targetPath, "utf8");
|
|
519
|
+
if (!content.includes('Label="Code Captain"')) return null;
|
|
520
|
+
|
|
521
|
+
// Remove the comment + ItemGroup block
|
|
522
|
+
let updated = content
|
|
523
|
+
.replace(/[ \t]*<!--[^\n]*Code Captain[^\n]*-->\r?\n/g, "")
|
|
524
|
+
.replace(
|
|
525
|
+
/[ \t]*<ItemGroup Label="Code Captain"[^>]*>[\s\S]*?<\/ItemGroup>\r?\n?/g,
|
|
526
|
+
""
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
await fs.copy(targetPath, `${targetPath}.backup`);
|
|
530
|
+
await fs.writeFile(targetPath, updated);
|
|
531
|
+
return "migrated";
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Install code-captain.csproj and register it in the .sln
|
|
535
|
+
async installCodeCaptainProject() {
|
|
536
|
+
const targetPath = "code-captain.csproj";
|
|
537
|
+
const templateSource = "copilot/code-captain.csproj";
|
|
538
|
+
const marker = 'Label="Code Captain"';
|
|
539
|
+
|
|
540
|
+
// Read template
|
|
541
|
+
let templateContent;
|
|
542
|
+
if (this.config.localSource) {
|
|
543
|
+
const localPath = path.join(this.config.localSource, templateSource);
|
|
544
|
+
if (!(await fs.pathExists(localPath))) {
|
|
545
|
+
throw new Error(`Template not found: ${localPath}`);
|
|
546
|
+
}
|
|
547
|
+
templateContent = await fs.readFile(localPath, "utf8");
|
|
548
|
+
} else {
|
|
549
|
+
const url = `${this.config.baseUrl}/${templateSource}`;
|
|
550
|
+
const response = await this.fetchWithTimeout(url, {}, 20000);
|
|
551
|
+
if (!response.ok) {
|
|
552
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
553
|
+
}
|
|
554
|
+
templateContent = await response.text();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Extract the ItemGroup block from the template (for update comparison)
|
|
558
|
+
const templateBlockMatch = templateContent.match(
|
|
559
|
+
/[ \t]*<ItemGroup Label="Code Captain"[^>]*>[\s\S]*?<\/ItemGroup>/
|
|
560
|
+
);
|
|
561
|
+
if (!templateBlockMatch) {
|
|
562
|
+
throw new Error("Template missing Code Captain ItemGroup");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const exists = await fs.pathExists(targetPath);
|
|
566
|
+
let csprojAction;
|
|
567
|
+
|
|
568
|
+
if (!exists) {
|
|
569
|
+
await fs.writeFile(targetPath, templateContent);
|
|
570
|
+
csprojAction = "created";
|
|
571
|
+
} else {
|
|
572
|
+
const existingContent = await fs.readFile(targetPath, "utf8");
|
|
573
|
+
if (existingContent.includes(marker)) {
|
|
574
|
+
const existingBlock = existingContent.match(
|
|
575
|
+
/[ \t]*<ItemGroup Label="Code Captain"[^>]*>[\s\S]*?<\/ItemGroup>/
|
|
576
|
+
);
|
|
577
|
+
if (existingBlock && existingBlock[0] === templateBlockMatch[0]) {
|
|
578
|
+
csprojAction = "up-to-date";
|
|
579
|
+
} else {
|
|
580
|
+
await fs.copy(targetPath, `${targetPath}.backup`);
|
|
581
|
+
const updatedContent = existingContent.replace(
|
|
582
|
+
/[ \t]*<ItemGroup Label="Code Captain"[^>]*>[\s\S]*?<\/ItemGroup>/,
|
|
583
|
+
templateBlockMatch[0]
|
|
584
|
+
);
|
|
585
|
+
await fs.writeFile(targetPath, updatedContent);
|
|
586
|
+
csprojAction = "updated";
|
|
587
|
+
}
|
|
588
|
+
} else {
|
|
589
|
+
// Not a Code Captain file — don't touch it
|
|
590
|
+
csprojAction = "skipped";
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Register in .sln
|
|
595
|
+
const slnFiles = await this.detectSlnFiles();
|
|
596
|
+
let slnAction = "no-sln";
|
|
597
|
+
if (slnFiles.length > 0) {
|
|
598
|
+
slnAction = await this.addProjectToSln(slnFiles[0], targetPath);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Migrate: remove Code Captain section from Directory.Build.props if present
|
|
602
|
+
const migrationAction = await this.migrateDirectoryBuildProps();
|
|
603
|
+
|
|
604
|
+
return { csprojAction, slnAction, migrationAction };
|
|
605
|
+
}
|
|
606
|
+
|
|
457
607
|
// Auto-detect IDE preference
|
|
458
608
|
detectIDE() {
|
|
459
609
|
const detections = [];
|
|
@@ -553,7 +703,7 @@ class CodeCaptainInstaller {
|
|
|
553
703
|
type: "confirm",
|
|
554
704
|
name: "enableVsSolution",
|
|
555
705
|
message:
|
|
556
|
-
"Install VS Solution View (
|
|
706
|
+
"Install VS Solution View (code-captain.csproj) to make Code Captain files visible in Visual Studio Solution Explorer?",
|
|
557
707
|
default: true,
|
|
558
708
|
},
|
|
559
709
|
]);
|
|
@@ -726,7 +876,7 @@ class CodeCaptainInstaller {
|
|
|
726
876
|
{ name: "Copilot Agents", value: "agents", checked: true },
|
|
727
877
|
{ name: "Copilot Prompts", value: "prompts", checked: true },
|
|
728
878
|
{
|
|
729
|
-
name: "VS Solution View (
|
|
879
|
+
name: "VS Solution View (code-captain.csproj)",
|
|
730
880
|
value: "vs-solution",
|
|
731
881
|
checked: false,
|
|
732
882
|
},
|
|
@@ -916,7 +1066,7 @@ class CodeCaptainInstaller {
|
|
|
916
1066
|
|
|
917
1067
|
// Extract the ItemGroup block from template
|
|
918
1068
|
const itemGroupMatch = templateContent.match(
|
|
919
|
-
/[ \t]*<ItemGroup Label="Code Captain"
|
|
1069
|
+
/[ \t]*<ItemGroup Label="Code Captain"[^>]*>[\s\S]*?<\/ItemGroup>/
|
|
920
1070
|
);
|
|
921
1071
|
if (!itemGroupMatch) {
|
|
922
1072
|
throw new Error("Template missing Code Captain ItemGroup");
|
|
@@ -937,7 +1087,7 @@ class CodeCaptainInstaller {
|
|
|
937
1087
|
if (existingContent.includes(marker)) {
|
|
938
1088
|
// Case 3: Already has Code Captain content — replace if changed
|
|
939
1089
|
const existingBlock = existingContent.match(
|
|
940
|
-
/[ \t]*<ItemGroup Label="Code Captain"
|
|
1090
|
+
/[ \t]*<ItemGroup Label="Code Captain"[^>]*>[\s\S]*?<\/ItemGroup>/
|
|
941
1091
|
);
|
|
942
1092
|
if (existingBlock && existingBlock[0] === itemGroupBlock) {
|
|
943
1093
|
return { action: "up-to-date" };
|
|
@@ -1145,7 +1295,7 @@ class CodeCaptainInstaller {
|
|
|
1145
1295
|
spinner.text = `Installing files... (${completed}/${files.length})`;
|
|
1146
1296
|
}
|
|
1147
1297
|
|
|
1148
|
-
// Handle
|
|
1298
|
+
// Handle vs-solution component (code-captain.csproj + .sln registration)
|
|
1149
1299
|
let vsSolutionResult = null;
|
|
1150
1300
|
const shouldInstallVsSolution =
|
|
1151
1301
|
(installOptions.installAll && installOptions.installVsSolution) ||
|
|
@@ -1154,8 +1304,8 @@ class CodeCaptainInstaller {
|
|
|
1154
1304
|
selectedComponents.includes("vs-solution"));
|
|
1155
1305
|
|
|
1156
1306
|
if (shouldInstallVsSolution) {
|
|
1157
|
-
spinner.text = "Installing
|
|
1158
|
-
vsSolutionResult = await this.
|
|
1307
|
+
spinner.text = "Installing code-captain.csproj...";
|
|
1308
|
+
vsSolutionResult = await this.installCodeCaptainProject();
|
|
1159
1309
|
}
|
|
1160
1310
|
|
|
1161
1311
|
spinner.succeed(
|
|
@@ -1303,16 +1453,27 @@ class CodeCaptainInstaller {
|
|
|
1303
1453
|
break;
|
|
1304
1454
|
}
|
|
1305
1455
|
|
|
1306
|
-
// Show
|
|
1456
|
+
// Show VS Solution View result if applicable
|
|
1307
1457
|
if (installResult.vsSolutionResult) {
|
|
1308
|
-
const
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
updated: "Updated
|
|
1313
|
-
"up-to-date": "
|
|
1458
|
+
const { csprojAction, slnAction, migrationAction } =
|
|
1459
|
+
installResult.vsSolutionResult;
|
|
1460
|
+
const csprojMessages = {
|
|
1461
|
+
created: "Created code-captain.csproj",
|
|
1462
|
+
updated: "Updated code-captain.csproj (backup saved as .backup)",
|
|
1463
|
+
"up-to-date": "code-captain.csproj is already up to date",
|
|
1464
|
+
skipped: "code-captain.csproj skipped (file exists but was not created by Code Captain)",
|
|
1465
|
+
};
|
|
1466
|
+
const slnMessages = {
|
|
1467
|
+
updated: "registered in solution (.sln backup saved as .backup)",
|
|
1468
|
+
"up-to-date": "already registered in solution",
|
|
1469
|
+
"no-sln": "no .sln file found — add code-captain.csproj to your solution manually",
|
|
1314
1470
|
};
|
|
1315
|
-
|
|
1471
|
+
let message = csprojMessages[csprojAction] || csprojAction;
|
|
1472
|
+
if (slnMessages[slnAction]) message += ` — ${slnMessages[slnAction]}`;
|
|
1473
|
+
if (migrationAction === "migrated") {
|
|
1474
|
+
message += " — removed Code Captain section from Directory.Build.props (backup saved)";
|
|
1475
|
+
}
|
|
1476
|
+
console.log(chalk.cyan("\n📁 VS Solution View:"), message);
|
|
1316
1477
|
}
|
|
1317
1478
|
|
|
1318
1479
|
console.log(
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<Project>
|
|
2
2
|
<!-- Code Captain: Make Copilot and Code Captain files visible in VS Solution Explorer -->
|
|
3
3
|
<ItemGroup Label="Code Captain">
|
|
4
|
-
<None Include=".github\copilot-instructions.md" Link="Code Captain\copilot-instructions.md" />
|
|
5
|
-
<None Include=".github\agents\**\*" Link="Code Captain\agents\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
6
|
-
<None Include=".github\prompts\**\*" Link="Code Captain\prompts\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
7
|
-
<None Include=".code-captain\docs\**\*" Link="Code Captain\docs\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
8
|
-
<None Include=".code-captain\specs\**\*" Link="Code Captain\specs\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
4
|
+
<None Include="$(MSBuildThisFileDirectory).github\copilot-instructions.md" Link="Code Captain\copilot-instructions.md" />
|
|
5
|
+
<None Include="$(MSBuildThisFileDirectory).github\agents\**\*" Link="Code Captain\agents\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
6
|
+
<None Include="$(MSBuildThisFileDirectory).github\prompts\**\*" Link="Code Captain\prompts\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
7
|
+
<None Include="$(MSBuildThisFileDirectory).code-captain\docs\**\*" Link="Code Captain\docs\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
8
|
+
<None Include="$(MSBuildThisFileDirectory).code-captain\specs\**\*" Link="Code Captain\specs\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
9
9
|
</ItemGroup>
|
|
10
10
|
</Project>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
<!-- Code Captain: Make Copilot and Code Captain files visible in VS Solution Explorer -->
|
|
3
|
+
<PropertyGroup>
|
|
4
|
+
<TargetFramework>netstandard2.0</TargetFramework>
|
|
5
|
+
<IsPackable>false</IsPackable>
|
|
6
|
+
</PropertyGroup>
|
|
7
|
+
<ItemGroup Label="Code Captain">
|
|
8
|
+
<None Include=".github\copilot-instructions.md" Link="Code Captain\copilot-instructions.md" />
|
|
9
|
+
<None Include=".github\agents\**\*" Link="Code Captain\agents\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
10
|
+
<None Include=".github\prompts\**\*" Link="Code Captain\prompts\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
11
|
+
<None Include=".code-captain\docs\**\*" Link="Code Captain\docs\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
12
|
+
<None Include=".code-captain\specs\**\*" Link="Code Captain\specs\%(RecursiveDir)%(Filename)%(Extension)" />
|
|
13
|
+
</ItemGroup>
|
|
14
|
+
</Project>
|
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"timestamp": "2026-02-
|
|
2
|
+
"version": "0.4.0",
|
|
3
|
+
"timestamp": "2026-02-19T00:00:00.000Z",
|
|
4
4
|
"commit": "a18e9dc145c3990823850611b321d0713e63d657",
|
|
5
5
|
"description": "Code Captain file manifest for change detection",
|
|
6
6
|
"files": {
|
|
@@ -225,9 +225,17 @@
|
|
|
225
225
|
"size": 475,
|
|
226
226
|
"lastModified": "2026-02-11T23:21:47.573Z",
|
|
227
227
|
"version": "0.3.0",
|
|
228
|
-
"component": "vs-solution",
|
|
228
|
+
"component": "vs-solution-legacy",
|
|
229
229
|
"description": "<!-- Code Captain: Make .github/ Copilot files visible in VS Solution Explorer -->"
|
|
230
230
|
},
|
|
231
|
+
"copilot/code-captain.csproj": {
|
|
232
|
+
"hash": "sha256:035c6482ba0c7e80c40669e01c9b557719450fa4be1bd6b07f1ff757f926619c",
|
|
233
|
+
"size": 857,
|
|
234
|
+
"lastModified": "2026-02-19T00:00:00.000Z",
|
|
235
|
+
"version": "0.4.0",
|
|
236
|
+
"component": "vs-solution",
|
|
237
|
+
"description": "<!-- Code Captain: Make Copilot and Code Captain files visible in VS Solution Explorer -->"
|
|
238
|
+
},
|
|
231
239
|
"claude-code/agents/code-captain.md": {
|
|
232
240
|
"hash": "sha256:6d87de22934ab5eecc48a899907dba6c38d2ecc30e96aa871eaf4fe4d500a08f",
|
|
233
241
|
"size": 5832,
|