compound-workflow 1.7.1 → 1.7.3
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/package.json
CHANGED
package/scripts/install-cli.mjs
CHANGED
|
@@ -34,7 +34,7 @@ When Cursor is detected (~/.cursor), registers the plugin so skills/commands app
|
|
|
34
34
|
--root <dir> Project directory (default: cwd)
|
|
35
35
|
--dry-run Print planned changes only
|
|
36
36
|
--no-config Skip Repo Config Block reminder
|
|
37
|
-
--no-register-cursor Do not register plugin with Cursor
|
|
37
|
+
--no-register-cursor Do not register plugin with Cursor
|
|
38
38
|
--register-cursor Force registration with Cursor even if ~/.cursor not found
|
|
39
39
|
`;
|
|
40
40
|
(exitCode === 0 ? console.log : console.error)(msg.trimStart());
|
|
@@ -42,14 +42,13 @@ When Cursor is detected (~/.cursor), registers the plugin so skills/commands app
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function parseArgs(argv) {
|
|
45
|
-
const out = { root: process.cwd(), dryRun: false, noConfig: false, noRegisterCursor: false, registerCursor: false
|
|
45
|
+
const out = { root: process.cwd(), dryRun: false, noConfig: false, noRegisterCursor: false, registerCursor: false };
|
|
46
46
|
for (let i = 2; i < argv.length; i++) {
|
|
47
47
|
const arg = argv[i];
|
|
48
48
|
if (arg === "--dry-run") out.dryRun = true;
|
|
49
49
|
else if (arg === "--no-config") out.noConfig = true;
|
|
50
50
|
else if (arg === "--no-register-cursor") out.noRegisterCursor = true;
|
|
51
51
|
else if (arg === "--register-cursor") out.registerCursor = true;
|
|
52
|
-
else if (arg === "--verify") out.verify = true;
|
|
53
52
|
else if (arg === "--root") {
|
|
54
53
|
const value = argv[i + 1];
|
|
55
54
|
if (!value) usage(1);
|
|
@@ -342,7 +341,8 @@ function writePluginManifests(targetRoot, dryRun, isSelfInstall) {
|
|
|
342
341
|
const claudeManifest = readJsonMaybe(claudeSrc);
|
|
343
342
|
if (!cursorManifest || !claudeManifest) return;
|
|
344
343
|
|
|
345
|
-
// Cursor
|
|
344
|
+
// All Cursor paths point directly at the package source — no symlink indirection.
|
|
345
|
+
// This ensures frontmatter (descriptions) is parsed correctly by Cursor for all components.
|
|
346
346
|
const cursorOut = {
|
|
347
347
|
...cursorManifest,
|
|
348
348
|
commands: `${pathsBase}/commands`,
|
|
@@ -426,411 +426,24 @@ function writePluginManifests(targetRoot, dryRun, isSelfInstall) {
|
|
|
426
426
|
console.log("Wrote: .cursor-plugin/plugin.json, .claude-plugin/plugin.json, .cursor-plugin/registration.json" + (isSelfInstall ? "" : ", .claude-plugin/marketplace.json"));
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
/**
|
|
430
|
-
* Cursor discovers skills only from .agents/skills, .cursor/skills, ~/.cursor/skills.
|
|
431
|
-
* Populate .cursor/skills/ with symlinks to the package skills so Cursor finds them.
|
|
432
|
-
*/
|
|
433
|
-
function syncCursorSkills(targetRoot, dryRun, isSelfInstall) {
|
|
434
|
-
const packageSkillsAbs = isSelfInstall
|
|
435
|
-
? path.join(PACKAGE_ROOT, "src", ".agents", "skills")
|
|
436
|
-
: path.join(targetRoot, "node_modules", "compound-workflow", "src", ".agents", "skills");
|
|
437
|
-
if (!fs.existsSync(packageSkillsAbs)) return;
|
|
438
|
-
|
|
439
|
-
const cursorSkillsDir = path.join(targetRoot, ".cursor", "skills");
|
|
440
|
-
let entries;
|
|
441
|
-
try {
|
|
442
|
-
entries = fs.readdirSync(packageSkillsAbs, { withFileTypes: true });
|
|
443
|
-
} catch {
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const skillDirs = entries.filter((e) => e.isDirectory() && fs.existsSync(path.join(packageSkillsAbs, e.name, "SKILL.md"))).map((e) => e.name);
|
|
448
|
-
if (skillDirs.length === 0) return;
|
|
449
|
-
|
|
450
|
-
if (dryRun) {
|
|
451
|
-
console.log("[dry-run] Would symlink", skillDirs.length, "skills into .cursor/skills/");
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
fs.mkdirSync(cursorSkillsDir, { recursive: true });
|
|
456
|
-
const packageSkillsReal = realpathSafe(packageSkillsAbs);
|
|
457
|
-
const skillSet = new Set(skillDirs);
|
|
458
|
-
|
|
459
|
-
// Prune: remove symlinks that point at our package but are no longer in the package
|
|
460
|
-
try {
|
|
461
|
-
for (const entry of fs.readdirSync(cursorSkillsDir, { withFileTypes: true })) {
|
|
462
|
-
if (!entry.isSymbolicLink()) continue;
|
|
463
|
-
const linkPath = path.join(cursorSkillsDir, entry.name);
|
|
464
|
-
try {
|
|
465
|
-
const resolved = realpathSafe(linkPath);
|
|
466
|
-
if (!resolved.startsWith(packageSkillsReal + path.sep) && resolved !== packageSkillsReal) continue;
|
|
467
|
-
const base = path.basename(resolved);
|
|
468
|
-
if (skillSet.has(base)) continue;
|
|
469
|
-
fs.rmSync(linkPath);
|
|
470
|
-
} catch {
|
|
471
|
-
/* ignore broken symlinks or permission errors */
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
} catch {
|
|
475
|
-
/* .cursor/skills not readable */
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
for (const name of skillDirs) {
|
|
479
|
-
const linkPath = path.join(cursorSkillsDir, name);
|
|
480
|
-
const targetPath = path.join(packageSkillsAbs, name);
|
|
481
|
-
try {
|
|
482
|
-
if (fs.existsSync(linkPath)) {
|
|
483
|
-
const stat = fs.lstatSync(linkPath);
|
|
484
|
-
if (!stat.isSymbolicLink()) continue;
|
|
485
|
-
try {
|
|
486
|
-
if (realpathSafe(linkPath) !== realpathSafe(targetPath)) continue;
|
|
487
|
-
} catch {
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
|
-
fs.rmSync(linkPath);
|
|
491
|
-
}
|
|
492
|
-
fs.symlinkSync(targetPath, linkPath, "dir");
|
|
493
|
-
} catch (err) {
|
|
494
|
-
if (err.code === "EPERM" && process.platform === "win32") {
|
|
495
|
-
try {
|
|
496
|
-
fs.symlinkSync(targetPath, linkPath, "junction");
|
|
497
|
-
} catch {
|
|
498
|
-
console.warn("[cursor] Could not symlink skill", name, err.message);
|
|
499
|
-
}
|
|
500
|
-
} else {
|
|
501
|
-
console.warn("[cursor] Could not symlink skill", name, err.message);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
console.log("Synced", skillDirs.length, "skills to .cursor/skills/");
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Cursor discovers commands from .cursor/commands.
|
|
510
|
-
* Populate .cursor/commands/ with symlinks to the package commands so Cursor finds them.
|
|
511
|
-
*/
|
|
512
|
-
function syncCursorCommands(targetRoot, dryRun, isSelfInstall) {
|
|
513
|
-
const packageCommandsAbs = isSelfInstall
|
|
514
|
-
? path.join(PACKAGE_ROOT, "src", ".agents", "commands")
|
|
515
|
-
: path.join(targetRoot, "node_modules", "compound-workflow", "src", ".agents", "commands");
|
|
516
|
-
if (!fs.existsSync(packageCommandsAbs)) return;
|
|
517
|
-
|
|
518
|
-
const cursorCommandsDir = path.join(targetRoot, ".cursor", "commands");
|
|
519
|
-
let entries;
|
|
520
|
-
try {
|
|
521
|
-
entries = fs.readdirSync(packageCommandsAbs, { withFileTypes: true });
|
|
522
|
-
} catch {
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Filter .md files that are commands
|
|
527
|
-
const commandFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name);
|
|
528
|
-
if (commandFiles.length === 0) return;
|
|
529
|
-
|
|
530
|
-
if (dryRun) {
|
|
531
|
-
console.log("[dry-run] Would symlink", commandFiles.length, "commands into .cursor/commands/");
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
fs.mkdirSync(cursorCommandsDir, { recursive: true });
|
|
536
|
-
const packageCommandsReal = realpathSafe(packageCommandsAbs);
|
|
537
|
-
const commandSet = new Set(commandFiles);
|
|
538
|
-
|
|
539
|
-
// Prune: remove symlinks that point at our package but are no longer in the package
|
|
540
|
-
try {
|
|
541
|
-
for (const entry of fs.readdirSync(cursorCommandsDir, { withFileTypes: true })) {
|
|
542
|
-
if (!entry.isSymbolicLink()) continue;
|
|
543
|
-
const linkPath = path.join(cursorCommandsDir, entry.name);
|
|
544
|
-
try {
|
|
545
|
-
const resolved = realpathSafe(linkPath);
|
|
546
|
-
if (!resolved.startsWith(packageCommandsReal + path.sep) && resolved !== packageCommandsReal) continue;
|
|
547
|
-
const base = path.basename(resolved);
|
|
548
|
-
if (commandSet.has(base)) continue;
|
|
549
|
-
fs.rmSync(linkPath);
|
|
550
|
-
} catch {
|
|
551
|
-
/* ignore broken symlinks or permission errors */
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
} catch {
|
|
555
|
-
/* .cursor/commands not readable */
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
for (const name of commandFiles) {
|
|
559
|
-
const linkPath = path.join(cursorCommandsDir, name);
|
|
560
|
-
const targetPath = path.join(packageCommandsAbs, name);
|
|
561
|
-
try {
|
|
562
|
-
if (fs.existsSync(linkPath)) {
|
|
563
|
-
const stat = fs.lstatSync(linkPath);
|
|
564
|
-
if (!stat.isSymbolicLink()) continue;
|
|
565
|
-
try {
|
|
566
|
-
if (realpathSafe(linkPath) !== realpathSafe(targetPath)) continue;
|
|
567
|
-
} catch {
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
fs.rmSync(linkPath);
|
|
571
|
-
}
|
|
572
|
-
fs.symlinkSync(targetPath, linkPath, "file");
|
|
573
|
-
} catch (err) {
|
|
574
|
-
console.warn("[cursor] Could not symlink command", name, err.message);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
console.log("Synced", commandFiles.length, "commands to .cursor/commands/");
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Cursor discovers agents from .cursor/agents.
|
|
582
|
-
* Populate .cursor/agents/ with symlinks to the package agents so Cursor finds them.
|
|
583
|
-
* Preserves subdirectory structure (research/, workflow/, review/).
|
|
584
|
-
*/
|
|
585
|
-
function syncCursorAgents(targetRoot, dryRun, isSelfInstall) {
|
|
586
|
-
const packageAgentsAbs = isSelfInstall
|
|
587
|
-
? path.join(PACKAGE_ROOT, "src", ".agents", "agents")
|
|
588
|
-
: path.join(targetRoot, "node_modules", "compound-workflow", "src", ".agents", "agents");
|
|
589
|
-
if (!fs.existsSync(packageAgentsAbs)) return;
|
|
590
|
-
|
|
591
|
-
const cursorAgentsDir = path.join(targetRoot, ".cursor", "agents");
|
|
592
|
-
|
|
593
|
-
// Get all agent files from manifest (these include subdir paths like "research/repo-research-analyst.md")
|
|
594
|
-
const agentRels = GENERATED_MANIFEST.agents.map((a) => a.rel);
|
|
595
|
-
if (agentRels.length === 0) return;
|
|
596
|
-
|
|
597
|
-
if (dryRun) {
|
|
598
|
-
console.log("[dry-run] Would symlink", agentRels.length, "agents into .cursor/agents/");
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
fs.mkdirSync(cursorAgentsDir, { recursive: true });
|
|
603
|
-
const packageAgentsReal = realpathSafe(packageAgentsAbs);
|
|
604
|
-
const agentSet = new Set(agentRels);
|
|
605
|
-
|
|
606
|
-
// Build set of valid subdirectories to preserve structure
|
|
607
|
-
const validSubdirs = new Set();
|
|
608
|
-
for (const rel of agentRels) {
|
|
609
|
-
const subdir = path.dirname(rel);
|
|
610
|
-
if (subdir !== ".") validSubdirs.add(subdir);
|
|
611
|
-
}
|
|
612
429
|
|
|
613
|
-
// Prune: remove symlinks that point at our package but are no longer in the manifest
|
|
614
|
-
try {
|
|
615
|
-
for (const entry of fs.readdirSync(cursorAgentsDir, { withFileTypes: true })) {
|
|
616
|
-
if (entry.isDirectory()) {
|
|
617
|
-
// Check if this subdir is still valid
|
|
618
|
-
if (!validSubdirs.has(entry.name)) {
|
|
619
|
-
// Remove the entire stale subdirectory
|
|
620
|
-
fs.rmSync(path.join(cursorAgentsDir, entry.name), { recursive: true, force: true });
|
|
621
|
-
continue;
|
|
622
|
-
}
|
|
623
|
-
// Prune stale symlinks within valid subdirectories
|
|
624
|
-
const subdirPath = path.join(cursorAgentsDir, entry.name);
|
|
625
|
-
for (const subEntry of fs.readdirSync(subdirPath, { withFileTypes: true })) {
|
|
626
|
-
if (!subEntry.isSymbolicLink()) continue;
|
|
627
|
-
const linkPath = path.join(subdirPath, subEntry.name);
|
|
628
|
-
try {
|
|
629
|
-
const resolved = realpathSafe(linkPath);
|
|
630
|
-
if (!resolved.startsWith(packageAgentsReal + path.sep) && resolved !== packageAgentsReal) continue;
|
|
631
|
-
const relFromPackage = path.relative(packageAgentsAbs, resolved);
|
|
632
|
-
if (agentSet.has(relFromPackage)) continue;
|
|
633
|
-
fs.rmSync(linkPath);
|
|
634
|
-
} catch {
|
|
635
|
-
/* ignore broken symlinks */
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
} else if (entry.isSymbolicLink()) {
|
|
639
|
-
// Handle flat symlinks (if any were created at root level)
|
|
640
|
-
const linkPath = path.join(cursorAgentsDir, entry.name);
|
|
641
|
-
try {
|
|
642
|
-
const resolved = realpathSafe(linkPath);
|
|
643
|
-
if (!resolved.startsWith(packageAgentsReal + path.sep) && resolved !== packageAgentsReal) continue;
|
|
644
|
-
const relFromPackage = path.relative(packageAgentsAbs, resolved);
|
|
645
|
-
if (agentSet.has(relFromPackage)) continue;
|
|
646
|
-
fs.rmSync(linkPath);
|
|
647
|
-
} catch {
|
|
648
|
-
/* ignore broken symlinks */
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
} catch {
|
|
653
|
-
/* .cursor/agents not readable */
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// Create symlinks preserving subdirectory structure
|
|
657
|
-
for (const rel of agentRels) {
|
|
658
|
-
const targetPath = path.join(packageAgentsAbs, rel);
|
|
659
|
-
const linkPath = path.join(cursorAgentsDir, rel);
|
|
660
|
-
|
|
661
|
-
// Ensure subdirectory exists
|
|
662
|
-
const subdir = path.dirname(rel);
|
|
663
|
-
if (subdir !== ".") {
|
|
664
|
-
fs.mkdirSync(path.join(cursorAgentsDir, subdir), { recursive: true });
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
try {
|
|
668
|
-
if (fs.existsSync(linkPath)) {
|
|
669
|
-
const stat = fs.lstatSync(linkPath);
|
|
670
|
-
if (!stat.isSymbolicLink()) continue;
|
|
671
|
-
try {
|
|
672
|
-
if (realpathSafe(linkPath) !== realpathSafe(targetPath)) continue;
|
|
673
|
-
} catch {
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
fs.rmSync(linkPath);
|
|
677
|
-
}
|
|
678
|
-
fs.symlinkSync(targetPath, linkPath, "file");
|
|
679
|
-
} catch (err) {
|
|
680
|
-
console.warn("[cursor] Could not symlink agent", rel, err.message);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
console.log("Synced", agentRels.length, "agents to .cursor/agents/");
|
|
684
|
-
}
|
|
685
430
|
|
|
686
431
|
function cursorDetected() {
|
|
687
432
|
return fs.existsSync(path.join(os.homedir(), ".cursor"));
|
|
688
433
|
}
|
|
689
434
|
|
|
690
|
-
/**
|
|
691
|
-
* Verifies plugin integrity by checking all symlinks in .cursor/ directories
|
|
692
|
-
* match the expected state from GENERATED_MANIFEST.
|
|
693
|
-
* Returns { ok: boolean, issues: string[] }
|
|
694
|
-
*/
|
|
695
|
-
function verifyPluginIntegrity(targetRoot, isSelfInstall) {
|
|
696
|
-
const issues = [];
|
|
697
|
-
const packageRoot = isSelfInstall
|
|
698
|
-
? PACKAGE_ROOT
|
|
699
|
-
: path.join(targetRoot, "node_modules", "compound-workflow");
|
|
700
|
-
|
|
701
|
-
// Verify skills
|
|
702
|
-
const cursorSkillsDir = path.join(targetRoot, ".cursor", "skills");
|
|
703
|
-
const packageSkillsDir = path.join(packageRoot, "src", ".agents", "skills");
|
|
704
|
-
if (fs.existsSync(packageSkillsDir)) {
|
|
705
|
-
const expectedSkills = new Set(
|
|
706
|
-
fs.readdirSync(packageSkillsDir, { withFileTypes: true })
|
|
707
|
-
.filter((e) => e.isDirectory() && fs.existsSync(path.join(packageSkillsDir, e.name, "SKILL.md")))
|
|
708
|
-
.map((e) => e.name)
|
|
709
|
-
);
|
|
710
|
-
if (fs.existsSync(cursorSkillsDir)) {
|
|
711
|
-
const actualSkills = fs.readdirSync(cursorSkillsDir, { withFileTypes: true })
|
|
712
|
-
.filter((e) => e.isSymbolicLink())
|
|
713
|
-
.map((e) => e.name);
|
|
714
|
-
for (const skill of expectedSkills) {
|
|
715
|
-
if (!actualSkills.includes(skill)) {
|
|
716
|
-
issues.push(`Missing skill symlink: .cursor/skills/${skill}`);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
for (const skill of actualSkills) {
|
|
720
|
-
if (!expectedSkills.has(skill)) {
|
|
721
|
-
issues.push(`Stale skill symlink: .cursor/skills/${skill}`);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
} else if (expectedSkills.size > 0) {
|
|
725
|
-
issues.push(`Missing .cursor/skills/ directory (${expectedSkills.size} expected)`);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// Verify commands
|
|
730
|
-
const cursorCommandsDir = path.join(targetRoot, ".cursor", "commands");
|
|
731
|
-
const packageCommandsDir = path.join(packageRoot, "src", ".agents", "commands");
|
|
732
|
-
if (fs.existsSync(packageCommandsDir)) {
|
|
733
|
-
const expectedCommands = new Set(
|
|
734
|
-
fs.readdirSync(packageCommandsDir, { withFileTypes: true })
|
|
735
|
-
.filter((e) => e.isFile() && e.name.endsWith(".md"))
|
|
736
|
-
.map((e) => e.name)
|
|
737
|
-
);
|
|
738
|
-
if (fs.existsSync(cursorCommandsDir)) {
|
|
739
|
-
const actualCommands = fs.readdirSync(cursorCommandsDir, { withFileTypes: true })
|
|
740
|
-
.filter((e) => e.isSymbolicLink())
|
|
741
|
-
.map((e) => e.name);
|
|
742
|
-
for (const cmd of expectedCommands) {
|
|
743
|
-
if (!actualCommands.includes(cmd)) {
|
|
744
|
-
issues.push(`Missing command symlink: .cursor/commands/${cmd}`);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
for (const cmd of actualCommands) {
|
|
748
|
-
if (!expectedCommands.has(cmd)) {
|
|
749
|
-
issues.push(`Stale command symlink: .cursor/commands/${cmd}`);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
} else if (expectedCommands.size > 0) {
|
|
753
|
-
issues.push(`Missing .cursor/commands/ directory (${expectedCommands.size} expected)`);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Verify agents
|
|
758
|
-
const cursorAgentsDir = path.join(targetRoot, ".cursor", "agents");
|
|
759
|
-
const packageAgentsDir = path.join(packageRoot, "src", ".agents", "agents");
|
|
760
|
-
if (fs.existsSync(packageAgentsDir)) {
|
|
761
|
-
// Recursively get all .md files from package agents dir
|
|
762
|
-
const expectedAgents = [];
|
|
763
|
-
function collectAgents(dir, prefix = "") {
|
|
764
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
765
|
-
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
766
|
-
if (entry.isDirectory()) {
|
|
767
|
-
collectAgents(path.join(dir, entry.name), relPath);
|
|
768
|
-
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
769
|
-
expectedAgents.push(relPath);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
collectAgents(packageAgentsDir);
|
|
774
|
-
const expectedSet = new Set(expectedAgents);
|
|
775
|
-
|
|
776
|
-
if (fs.existsSync(cursorAgentsDir)) {
|
|
777
|
-
const actualAgents = [];
|
|
778
|
-
function collectActualAgents(dir, prefix = "") {
|
|
779
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
780
|
-
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
781
|
-
if (entry.isDirectory()) {
|
|
782
|
-
collectActualAgents(path.join(dir, entry.name), relPath);
|
|
783
|
-
} else if (entry.isSymbolicLink()) {
|
|
784
|
-
actualAgents.push(relPath);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
collectActualAgents(cursorAgentsDir);
|
|
789
|
-
const actualSet = new Set(actualAgents);
|
|
790
|
-
|
|
791
|
-
for (const agent of expectedAgents) {
|
|
792
|
-
if (!actualSet.has(agent)) {
|
|
793
|
-
issues.push(`Missing agent symlink: .cursor/agents/${agent}`);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
for (const agent of actualAgents) {
|
|
797
|
-
if (!expectedSet.has(agent)) {
|
|
798
|
-
issues.push(`Stale agent symlink: .cursor/agents/${agent}`);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
} else if (expectedAgents.length > 0) {
|
|
802
|
-
issues.push(`Missing .cursor/agents/ directory (${expectedAgents.length} expected)`);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
return { ok: issues.length === 0, issues };
|
|
807
|
-
}
|
|
808
435
|
|
|
809
436
|
function applyCursorRegistration(targetRoot, dryRun, noRegisterCursor, forceRegister, isSelfInstall) {
|
|
810
|
-
const claudePluginsDir = path.join(os.homedir(), ".claude", "plugins");
|
|
811
|
-
const installedPath = path.join(claudePluginsDir, "installed_plugins.json");
|
|
812
|
-
const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
813
|
-
|
|
814
|
-
const pluginVersion = (() => {
|
|
815
|
-
try {
|
|
816
|
-
const pkgPath = path.join(PACKAGE_ROOT, "package.json");
|
|
817
|
-
return JSON.parse(fs.readFileSync(pkgPath, "utf8")).version || "0.0.0";
|
|
818
|
-
} catch {
|
|
819
|
-
return "0.0.0";
|
|
820
|
-
}
|
|
821
|
-
})();
|
|
822
|
-
|
|
823
437
|
const projectRoot = isSelfInstall ? PACKAGE_ROOT : targetRoot;
|
|
824
|
-
const pluginId = "compound-workflow@local";
|
|
438
|
+
const pluginId = "compound-workflow@compound-workflow-local";
|
|
825
439
|
|
|
826
440
|
if (dryRun) {
|
|
827
441
|
console.log("[dry-run] Would register Claude plugin (project-scoped) at:", projectRoot);
|
|
828
442
|
return;
|
|
829
443
|
}
|
|
830
444
|
|
|
831
|
-
// Registration is
|
|
832
|
-
// Claude Code manages
|
|
833
|
-
// writing to user-level files causes "unregistered local marketplace" errors on startup.
|
|
445
|
+
// Registration is strictly project-scoped: write only to <project>/.claude/settings.json.
|
|
446
|
+
// Never touch ~/.claude — Claude Code manages user-level plugin state itself.
|
|
834
447
|
const projectSettingsPath = path.join(projectRoot, ".claude", "settings.json");
|
|
835
448
|
let projectSettings = {};
|
|
836
449
|
if (fs.existsSync(projectSettingsPath)) {
|
|
@@ -844,31 +457,11 @@ function applyCursorRegistration(targetRoot, dryRun, noRegisterCursor, forceRegi
|
|
|
844
457
|
}
|
|
845
458
|
projectSettings.extraKnownMarketplaces = ensureObject(projectSettings.extraKnownMarketplaces);
|
|
846
459
|
projectSettings.extraKnownMarketplaces["compound-workflow-local"] = {
|
|
847
|
-
source: { source: "file", path: ".
|
|
460
|
+
source: { source: "file", path: "." },
|
|
848
461
|
};
|
|
849
462
|
fs.mkdirSync(path.join(projectRoot, ".claude"), { recursive: true });
|
|
850
463
|
fs.writeFileSync(projectSettingsPath, JSON.stringify(projectSettings, null, 2) + "\n", "utf8");
|
|
851
464
|
|
|
852
|
-
// Clean up any stale user-level enabledPlugins entries left by previous install versions.
|
|
853
|
-
// These cause "unregistered local marketplace" errors on every Claude Code startup.
|
|
854
|
-
if (fs.existsSync(settingsPath)) {
|
|
855
|
-
try {
|
|
856
|
-
let userSettings = readJsonMaybe(settingsPath) ?? {};
|
|
857
|
-
const staleIds = ["compound-workflow@local", "compound-workflow@compound-workflow-local"];
|
|
858
|
-
let changed = false;
|
|
859
|
-
for (const id of staleIds) {
|
|
860
|
-
if (userSettings?.enabledPlugins?.[id] !== undefined) {
|
|
861
|
-
delete userSettings.enabledPlugins[id];
|
|
862
|
-
changed = true;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
if (changed) {
|
|
866
|
-
fs.writeFileSync(settingsPath, JSON.stringify(userSettings, null, 2) + "\n", "utf8");
|
|
867
|
-
console.log("Cleaned up stale compound-workflow entries from ~/.claude/settings.json");
|
|
868
|
-
}
|
|
869
|
-
} catch { /* ignore */ }
|
|
870
|
-
}
|
|
871
|
-
|
|
872
465
|
console.log("Registered compound-workflow with Claude Code (project-scoped).");
|
|
873
466
|
if (!isSelfInstall) {
|
|
874
467
|
console.log(" Claude Code 2.1+: open /plugin, go to Discover; install 'compound-workflow' from marketplace 'compound-workflow-local', or run: claude --plugin-dir ./node_modules/compound-workflow");
|
|
@@ -908,31 +501,6 @@ function main() {
|
|
|
908
501
|
const args = parseArgs(process.argv);
|
|
909
502
|
const targetRoot = realpathSafe(args.root);
|
|
910
503
|
|
|
911
|
-
// Handle verification mode early (no manifest needed)
|
|
912
|
-
if (args.verify) {
|
|
913
|
-
console.log("Verifying plugin integrity...");
|
|
914
|
-
const isSelfInstall = realpathSafe(targetRoot) === realpathSafe(PACKAGE_ROOT);
|
|
915
|
-
// Try to read manifest for accurate verification, but continue without it
|
|
916
|
-
try {
|
|
917
|
-
GENERATED_MANIFEST = readGeneratedManifest();
|
|
918
|
-
} catch {
|
|
919
|
-
console.warn("Warning: Could not read generated manifest, using filesystem scan only");
|
|
920
|
-
GENERATED_MANIFEST = { commands: [], agents: [] };
|
|
921
|
-
}
|
|
922
|
-
const result = verifyPluginIntegrity(targetRoot, isSelfInstall);
|
|
923
|
-
if (result.ok) {
|
|
924
|
-
console.log("Plugin integrity: OK (all symlinks present and valid)");
|
|
925
|
-
process.exit(0);
|
|
926
|
-
} else {
|
|
927
|
-
console.error("Plugin integrity issues found:");
|
|
928
|
-
for (const issue of result.issues) {
|
|
929
|
-
console.error(` - ${issue}`);
|
|
930
|
-
}
|
|
931
|
-
console.error("\nRun 'npx compound-workflow install' to fix.");
|
|
932
|
-
process.exit(1);
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
|
|
936
504
|
const genScript = path.join(PACKAGE_ROOT, "scripts", "generate-platform-artifacts.mjs");
|
|
937
505
|
if (fs.existsSync(genScript)) {
|
|
938
506
|
console.log("[compound-workflow] Regenerating manifest from package source...");
|
|
@@ -975,9 +543,6 @@ function main() {
|
|
|
975
543
|
|
|
976
544
|
writeOpenCodeJson(targetRoot, args.dryRun, isSelfInstall);
|
|
977
545
|
writePluginManifests(targetRoot, args.dryRun, isSelfInstall);
|
|
978
|
-
syncCursorSkills(targetRoot, args.dryRun, isSelfInstall);
|
|
979
|
-
syncCursorCommands(targetRoot, args.dryRun, isSelfInstall);
|
|
980
|
-
syncCursorAgents(targetRoot, args.dryRun, isSelfInstall);
|
|
981
546
|
applyCursorRegistration(targetRoot, args.dryRun, args.noRegisterCursor, args.registerCursor, isSelfInstall);
|
|
982
547
|
reportOpenCodeIntegration(targetRoot, args.dryRun);
|
|
983
548
|
writeAgentsMd(targetRoot, args.dryRun);
|
|
@@ -6,7 +6,7 @@ argument-hint: "[PR number, branch name, or 'current' for current branch]"
|
|
|
6
6
|
|
|
7
7
|
# Browser Test Command
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Run end-to-end browser tests on pages affected by a PR or branch changes using agent-browser CLI.
|
|
10
10
|
|
|
11
11
|
## CRITICAL: Use agent-browser CLI Only
|
|
12
12
|
|
|
@@ -7,8 +7,9 @@ argument-hint: "[feature idea or problem to explore]"
|
|
|
7
7
|
|
|
8
8
|
# Brainstorm a Feature or Improvement
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Explore requirements and approaches through collaborative dialogue before planning implementation.
|
|
11
|
+
|
|
12
|
+
**Note: The current year is 2026.** Use this when dating brainstorm documents.
|
|
12
13
|
|
|
13
14
|
Brainstorming helps answer **WHAT** to build through collaborative
|
|
14
15
|
dialogue. It precedes `/workflow:plan`, which answers **HOW** to build
|
|
@@ -7,6 +7,8 @@ argument-hint: "[feature description, bug report, improvement idea, or brainstor
|
|
|
7
7
|
|
|
8
8
|
# Create a plan for a new feature or bug fix
|
|
9
9
|
|
|
10
|
+
Transform feature descriptions into well-structured project plans using an explicit fidelity and confidence model.
|
|
11
|
+
|
|
10
12
|
## Introduction
|
|
11
13
|
|
|
12
14
|
**Note: The current year is 2026.** Use this when dating plans and searching for recent documentation.
|