opencode-swarm-plugin 0.23.6 → 0.25.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/swarm.ts CHANGED
@@ -13,9 +13,19 @@
13
13
  */
14
14
 
15
15
  import * as p from "@clack/prompts";
16
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
16
+ import {
17
+ chmodSync,
18
+ copyFileSync,
19
+ existsSync,
20
+ mkdirSync,
21
+ readFileSync,
22
+ readdirSync,
23
+ rmSync,
24
+ statSync,
25
+ writeFileSync,
26
+ } from "fs";
17
27
  import { homedir } from "os";
18
- import { dirname, join } from "path";
28
+ import { basename, dirname, join } from "path";
19
29
  import { fileURLToPath } from "url";
20
30
 
21
31
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -454,6 +464,390 @@ async function checkAllDependencies(): Promise<CheckResult[]> {
454
464
  return results;
455
465
  }
456
466
 
467
+ // ============================================================================
468
+ // Skills Sync Utilities
469
+ // ============================================================================
470
+
471
+ const BUNDLED_SKILL_MARKER_FILENAME = ".swarm-bundled-skill.json";
472
+
473
+ function listDirectoryNames(dirPath: string): string[] {
474
+ if (!existsSync(dirPath)) return [];
475
+ try {
476
+ return readdirSync(dirPath, { withFileTypes: true })
477
+ .filter((d) => d.isDirectory())
478
+ .map((d) => d.name)
479
+ .sort();
480
+ } catch {
481
+ return [];
482
+ }
483
+ }
484
+
485
+ function copyDirRecursiveSync(srcDir: string, destDir: string): void {
486
+ mkdirSync(destDir, { recursive: true });
487
+ const entries = readdirSync(srcDir, { withFileTypes: true });
488
+ for (const entry of entries) {
489
+ const srcPath = join(srcDir, entry.name);
490
+ const destPath = join(destDir, entry.name);
491
+
492
+ if (entry.isDirectory()) {
493
+ copyDirRecursiveSync(srcPath, destPath);
494
+ continue;
495
+ }
496
+
497
+ if (entry.isFile()) {
498
+ copyFileSync(srcPath, destPath);
499
+ try {
500
+ chmodSync(destPath, statSync(srcPath).mode);
501
+ } catch {
502
+ // Best effort
503
+ }
504
+ }
505
+ }
506
+ }
507
+
508
+ function writeBundledSkillMarker(
509
+ skillDir: string,
510
+ info: { version: string },
511
+ ): void {
512
+ const markerPath = join(skillDir, BUNDLED_SKILL_MARKER_FILENAME);
513
+ writeFileSync(
514
+ markerPath,
515
+ JSON.stringify(
516
+ {
517
+ managed_by: "opencode-swarm-plugin",
518
+ version: info.version,
519
+ synced_at: new Date().toISOString(),
520
+ },
521
+ null,
522
+ 2,
523
+ ),
524
+ );
525
+ }
526
+
527
+ function syncBundledSkillsToGlobal({
528
+ bundledSkillsPath,
529
+ globalSkillsPath,
530
+ version,
531
+ }: {
532
+ bundledSkillsPath: string;
533
+ globalSkillsPath: string;
534
+ version: string;
535
+ }): { installed: string[]; updated: string[]; skipped: string[] } {
536
+ const bundledSkills = listDirectoryNames(bundledSkillsPath);
537
+
538
+ const installed: string[] = [];
539
+ const updated: string[] = [];
540
+ const skipped: string[] = [];
541
+
542
+ for (const name of bundledSkills) {
543
+ const srcSkillDir = join(bundledSkillsPath, name);
544
+ const destSkillDir = join(globalSkillsPath, name);
545
+ const markerPath = join(destSkillDir, BUNDLED_SKILL_MARKER_FILENAME);
546
+
547
+ if (!existsSync(destSkillDir)) {
548
+ copyDirRecursiveSync(srcSkillDir, destSkillDir);
549
+ writeBundledSkillMarker(destSkillDir, { version });
550
+ installed.push(name);
551
+ continue;
552
+ }
553
+
554
+ // Only overwrite skills that we previously installed/managed
555
+ if (existsSync(markerPath)) {
556
+ rmSync(destSkillDir, { recursive: true, force: true });
557
+ copyDirRecursiveSync(srcSkillDir, destSkillDir);
558
+ writeBundledSkillMarker(destSkillDir, { version });
559
+ updated.push(name);
560
+ continue;
561
+ }
562
+
563
+ skipped.push(name);
564
+ }
565
+
566
+ return { installed, updated, skipped };
567
+ }
568
+
569
+ // ============================================================================
570
+ // AGENTS.md Update Utilities
571
+ // ============================================================================
572
+
573
+ function detectNewline(content: string): "\r\n" | "\n" {
574
+ return content.includes("\r\n") ? "\r\n" : "\n";
575
+ }
576
+
577
+ function backupFileWithTimestamp(filePath: string): string | null {
578
+ try {
579
+ const dir = dirname(filePath);
580
+ const base = basename(filePath);
581
+ const timestamp = new Date()
582
+ .toISOString()
583
+ .replace(/[:.]/g, "")
584
+ .replace(/Z$/, "Z");
585
+ const backupPath = join(dir, `${base}.swarm-backup-${timestamp}`);
586
+ copyFileSync(filePath, backupPath);
587
+ return backupPath;
588
+ } catch {
589
+ return null;
590
+ }
591
+ }
592
+
593
+ function buildAgentsSkillsSection(
594
+ bundledSkillsCsv: string,
595
+ newline: string,
596
+ ): string {
597
+ return [
598
+ "## Skills - Knowledge Injection",
599
+ "",
600
+ "Skills are reusable knowledge packages. Load them on-demand for specialized tasks.",
601
+ "",
602
+ "### When to Use",
603
+ "",
604
+ "- Before unfamiliar work - check if a skill exists",
605
+ "- When you need domain-specific patterns",
606
+ "- For complex workflows that benefit from guidance",
607
+ "",
608
+ "### Usage",
609
+ "",
610
+ "```bash",
611
+ "skills_list() # See available skills",
612
+ 'skills_use(name="swarm-coordination") # Load a skill',
613
+ 'skills_use(name="cli-builder", context="building a new CLI") # With context',
614
+ "```",
615
+ "",
616
+ `**Bundled Skills:** ${bundledSkillsCsv}`,
617
+ ].join(newline);
618
+ }
619
+
620
+ function buildAgentsCassSection(newline: string): string {
621
+ return [
622
+ "## CASS - Cross-Agent Session Search",
623
+ "",
624
+ "Search across ALL your AI coding agent histories before solving problems from scratch.",
625
+ "",
626
+ "### When to Use",
627
+ "",
628
+ '- **BEFORE implementing anything**: check if any agent solved it before',
629
+ '- **Debugging**: "what did I try last time this error happened?"',
630
+ '- **Learning patterns**: "how did Cursor handle this API?"',
631
+ "",
632
+ "### Usage",
633
+ "",
634
+ "```bash",
635
+ "# Search all agents",
636
+ 'cass_search(query="authentication token refresh", limit=5)',
637
+ "",
638
+ "# Filter by agent/time",
639
+ 'cass_search(query="useEffect cleanup", agent="claude", days=7)',
640
+ "",
641
+ "# View specific result",
642
+ 'cass_view(path="/path/from/search", line=42)',
643
+ "",
644
+ "# Expand context around match",
645
+ 'cass_expand(path="/path", line=42, context=10)',
646
+ "```",
647
+ "",
648
+ "**Pro tip:** Query CASS at the START of complex tasks. Past solutions save time.",
649
+ ].join(newline);
650
+ }
651
+
652
+ function buildAgentsSemanticMemorySection(newline: string): string {
653
+ return [
654
+ "## Semantic Memory - Persistent Learning",
655
+ "",
656
+ "Store and retrieve learnings across sessions. Memories persist and are searchable.",
657
+ "",
658
+ "### When to Use",
659
+ "",
660
+ "- After solving a tricky problem - store the solution",
661
+ "- After making architectural decisions - store the reasoning",
662
+ "- Before starting work - search for relevant past learnings",
663
+ "- When you discover project-specific patterns",
664
+ "",
665
+ "### Usage",
666
+ "",
667
+ "```bash",
668
+ "# Store a learning",
669
+ 'semantic-memory_store(information="OAuth refresh tokens need 5min buffer before expiry", metadata="auth, tokens")',
670
+ "",
671
+ "# Search for relevant memories",
672
+ 'semantic-memory_find(query="token refresh", limit=5)',
673
+ "",
674
+ "# Validate a memory is still accurate (resets decay timer)",
675
+ 'semantic-memory_validate(id="mem_123")',
676
+ "```",
677
+ "",
678
+ "**Pro tip:** Store the WHY, not just the WHAT. Future you needs context.",
679
+ ].join(newline);
680
+ }
681
+
682
+ function updateAgentsToolPreferencesBlock(
683
+ content: string,
684
+ newline: string,
685
+ ): { content: string; changed: boolean } {
686
+ const lower = content.toLowerCase();
687
+ const openTag = "<tool_preferences>";
688
+ const closeTag = "</tool_preferences>";
689
+ const openIdx = lower.indexOf(openTag);
690
+ const closeIdx = lower.indexOf(closeTag);
691
+
692
+ if (openIdx === -1 || closeIdx === -1 || closeIdx <= openIdx) {
693
+ return { content, changed: false };
694
+ }
695
+
696
+ const blockStart = openIdx;
697
+ const blockEnd = closeIdx + closeTag.length;
698
+ const before = content.slice(0, blockStart);
699
+ const block = content.slice(blockStart, blockEnd);
700
+ const after = content.slice(blockEnd);
701
+
702
+ const hasSkillsTools =
703
+ /skills_list/i.test(block) &&
704
+ /skills_use/i.test(block) &&
705
+ /skills_read/i.test(block);
706
+ const hasCassTools =
707
+ /cass_search/i.test(block) &&
708
+ /cass_view/i.test(block) &&
709
+ /cass_expand/i.test(block);
710
+ const hasSemanticTools =
711
+ /semantic-memory_find/i.test(block) &&
712
+ /semantic-memory_store/i.test(block);
713
+
714
+ const linesToAdd: string[] = [];
715
+ if (!hasSkillsTools) {
716
+ linesToAdd.push(
717
+ "- **skills_list, skills_use, skills_read** - Knowledge injection (load reusable skills)",
718
+ );
719
+ }
720
+ if (!hasCassTools) {
721
+ linesToAdd.push(
722
+ "- **cass_search, cass_view, cass_expand** - Search past agent sessions",
723
+ );
724
+ }
725
+ if (!hasSemanticTools) {
726
+ linesToAdd.push(
727
+ "- **semantic-memory_find, semantic-memory_store, semantic-memory_validate** - Persistent learning across sessions",
728
+ );
729
+ }
730
+
731
+ if (linesToAdd.length === 0) {
732
+ return { content, changed: false };
733
+ }
734
+
735
+ const headingRe = /^###\s+Other Custom Tools.*$/m;
736
+ const headingMatch = headingRe.exec(block);
737
+
738
+ let updatedBlock: string;
739
+ const insertion = newline + newline + linesToAdd.join(newline) + newline;
740
+
741
+ if (headingMatch) {
742
+ const insertAt = headingMatch.index + headingMatch[0].length;
743
+ updatedBlock = block.slice(0, insertAt) + insertion + block.slice(insertAt);
744
+ } else {
745
+ const closeInBlock = block.toLowerCase().lastIndexOf(closeTag);
746
+ updatedBlock =
747
+ block.slice(0, closeInBlock) + insertion + block.slice(closeInBlock);
748
+ }
749
+
750
+ return { content: before + updatedBlock + after, changed: true };
751
+ }
752
+
753
+ function updateAgentsMdContent({
754
+ content,
755
+ bundledSkillsCsv,
756
+ }: {
757
+ content: string;
758
+ bundledSkillsCsv: string;
759
+ }): { updated: string; changed: boolean; changes: string[] } {
760
+ const newline = detectNewline(content);
761
+ const changes: string[] = [];
762
+ let updated = content;
763
+
764
+ // Update bundled skills line (common formats)
765
+ const beforeBundled = updated;
766
+ updated = updated.replace(
767
+ /^\*\*Bundled Skills:\*\*.*$/gm,
768
+ `**Bundled Skills:** ${bundledSkillsCsv}`,
769
+ );
770
+ updated = updated.replace(
771
+ /^\*\*Bundled:\*\*.*$/gm,
772
+ `**Bundled:** ${bundledSkillsCsv}`,
773
+ );
774
+ if (updated !== beforeBundled) {
775
+ changes.push("Updated bundled skills list");
776
+ }
777
+
778
+ // Update tool preferences block if present
779
+ const toolPrefsResult = updateAgentsToolPreferencesBlock(updated, newline);
780
+ if (toolPrefsResult.changed) {
781
+ updated = toolPrefsResult.content;
782
+ changes.push("Updated tool_preferences tool list");
783
+ }
784
+
785
+ // Add missing sections (append at end)
786
+ const hasSkillsSection =
787
+ /^#{1,6}\s+Skills\b/im.test(updated) || /skills_list\(\)/.test(updated);
788
+ const hasCassSection =
789
+ /^#{1,6}\s+.*CASS\b/im.test(updated) || /cass_search\(/.test(updated);
790
+ const hasSemanticMemorySection =
791
+ /^#{1,6}\s+Semantic Memory\b/im.test(updated) ||
792
+ /semantic-memory_store\(/.test(updated);
793
+
794
+ const sectionsToAppend: string[] = [];
795
+ if (!hasSkillsSection) {
796
+ sectionsToAppend.push(
797
+ buildAgentsSkillsSection(bundledSkillsCsv, newline),
798
+ );
799
+ changes.push("Added Skills section");
800
+ }
801
+ if (!hasCassSection) {
802
+ sectionsToAppend.push(buildAgentsCassSection(newline));
803
+ changes.push("Added CASS section");
804
+ }
805
+ if (!hasSemanticMemorySection) {
806
+ sectionsToAppend.push(buildAgentsSemanticMemorySection(newline));
807
+ changes.push("Added Semantic Memory section");
808
+ }
809
+
810
+ if (sectionsToAppend.length > 0) {
811
+ const trimmed = updated.replace(/\s+$/g, "");
812
+ const needsRule = !/^\s*---\s*$/m.test(trimmed.slice(-3000));
813
+ updated =
814
+ trimmed +
815
+ newline +
816
+ newline +
817
+ (needsRule ? `---${newline}${newline}` : "") +
818
+ sectionsToAppend.join(newline + newline);
819
+ }
820
+
821
+ // Ensure trailing newline
822
+ if (!updated.endsWith(newline)) {
823
+ updated += newline;
824
+ }
825
+
826
+ return { updated, changed: updated !== content, changes };
827
+ }
828
+
829
+ function updateAgentsMdFile({
830
+ agentsPath,
831
+ bundledSkillsCsv,
832
+ }: {
833
+ agentsPath: string;
834
+ bundledSkillsCsv: string;
835
+ }): { changed: boolean; backupPath?: string; changes: string[] } {
836
+ const original = readFileSync(agentsPath, "utf-8");
837
+ const { updated, changed, changes } = updateAgentsMdContent({
838
+ content: original,
839
+ bundledSkillsCsv,
840
+ });
841
+
842
+ if (!changed) {
843
+ return { changed: false, changes: ["No changes needed"] };
844
+ }
845
+
846
+ const backupPath = backupFileWithTimestamp(agentsPath) || undefined;
847
+ writeFileSync(agentsPath, updated, "utf-8");
848
+ return { changed: true, backupPath, changes };
849
+ }
850
+
457
851
  // ============================================================================
458
852
  // File Templates
459
853
  // ============================================================================
@@ -489,7 +883,7 @@ const SWARM_COMMAND = `---
489
883
  description: Decompose task into parallel subtasks and coordinate agents
490
884
  ---
491
885
 
492
- You are a swarm coordinator. Decompose the task into beads and spawn parallel agents.
886
+ You are a swarm coordinator. Your job is to clarify the task, decompose it into beads, and spawn parallel agents.
493
887
 
494
888
  ## Task
495
889
 
@@ -497,42 +891,84 @@ $ARGUMENTS
497
891
 
498
892
  ## Workflow
499
893
 
500
- ### 1. Initialize
894
+ ### Phase 0: Socratic Planning (INTERACTIVE - unless --fast)
895
+
896
+ **Before decomposing, clarify the task with the user.**
897
+
898
+ Check for flags in the task:
899
+ - \`--fast\` → Skip questions, use reasonable defaults
900
+ - \`--auto\` → Zero interaction, heuristic decisions
901
+ - \`--confirm-only\` → Show plan, get yes/no only
902
+
903
+ **Default (no flags): Full Socratic Mode**
904
+
905
+ 1. **Analyze task for ambiguity:**
906
+ - Scope unclear? (what's included/excluded)
907
+ - Strategy unclear? (file-based vs feature-based)
908
+ - Dependencies unclear? (what needs to exist first)
909
+ - Success criteria unclear? (how do we know it's done)
910
+
911
+ 2. **If clarification needed, ask ONE question at a time:**
912
+ \`\`\`
913
+ The task "<task>" needs clarification before I can decompose it.
914
+
915
+ **Question:** <specific question>
916
+
917
+ Options:
918
+ a) <option 1> - <tradeoff>
919
+ b) <option 2> - <tradeoff>
920
+ c) <option 3> - <tradeoff>
921
+
922
+ I'd recommend (b) because <reason>. Which approach?
923
+ \`\`\`
924
+
925
+ 3. **Wait for user response before proceeding**
926
+
927
+ 4. **Iterate if needed** (max 2-3 questions)
928
+
929
+ **Rules:**
930
+ - ONE question at a time - don't overwhelm
931
+ - Offer concrete options - not open-ended
932
+ - Lead with recommendation - save cognitive load
933
+ - Wait for answer - don't assume
934
+
935
+ ### Phase 1: Initialize
501
936
  \`swarmmail_init(project_path="$PWD", task_description="Swarm: <task>")\`
502
937
 
503
- ### 2. Knowledge Gathering (MANDATORY)
938
+ ### Phase 2: Knowledge Gathering (MANDATORY)
504
939
 
505
940
  **Before decomposing, query ALL knowledge sources:**
506
941
 
507
942
  \`\`\`
508
943
  semantic-memory_find(query="<task keywords>", limit=5) # Past learnings
509
944
  cass_search(query="<task description>", limit=5) # Similar past tasks
510
- pdf-brain_search(query="<domain concepts>", limit=5) # Design patterns
511
945
  skills_list() # Available skills
512
946
  \`\`\`
513
947
 
514
948
  Synthesize findings into shared_context for workers.
515
949
 
516
- ### 3. Decompose
950
+ ### Phase 3: Decompose
517
951
  \`\`\`
518
952
  swarm_select_strategy(task="<task>")
519
953
  swarm_plan_prompt(task="<task>", context="<synthesized knowledge>")
520
954
  swarm_validate_decomposition(response="<BeadTree JSON>")
521
955
  \`\`\`
522
956
 
523
- ### 4. Create Beads
957
+ ### Phase 4: Create Beads
524
958
  \`beads_create_epic(epic_title="<task>", subtasks=[...])\`
525
959
 
526
- ### 5. Reserve Files
960
+ ### Phase 5: Reserve Files
527
961
  \`swarmmail_reserve(paths=[...], reason="<bead-id>: <desc>")\`
528
962
 
529
- ### 6. Spawn Agents (ALL in single message)
963
+ ### Phase 6: Spawn Agents (ALL in single message)
530
964
  \`\`\`
531
- swarm_spawn_subtask(bead_id, epic_id, subtask_title, files, shared_context)
965
+ swarm_spawn_subtask(bead_id, epic_id, subtask_title, files, shared_context, project_path="$PWD")
532
966
  Task(subagent_type="swarm/worker", prompt="<from above>")
533
967
  \`\`\`
534
968
 
535
- ### 7. Monitor
969
+ **IMPORTANT:** Pass \`project_path\` to \`swarm_spawn_subtask\` so workers can call \`swarmmail_init\`.
970
+
971
+ ### Phase 7: Monitor
536
972
  \`\`\`
537
973
  swarm_status(epic_id, project_key)
538
974
  swarmmail_inbox()
@@ -540,7 +976,7 @@ swarmmail_inbox()
540
976
 
541
977
  Intervene if: blocked >5min, file conflicts, scope creep.
542
978
 
543
- ### 8. Complete
979
+ ### Phase 8: Complete
544
980
  \`\`\`
545
981
  swarm_complete(...)
546
982
  beads_sync()
@@ -555,7 +991,15 @@ beads_sync()
555
991
  | risk-based | Bug fixes, security | fix, bug, security, critical, urgent |
556
992
  | research-based | Investigation, discovery | research, investigate, explore, learn |
557
993
 
558
- Begin with knowledge gathering now.
994
+ ## Flag Reference
995
+
996
+ | Flag | Effect |
997
+ |------|--------|
998
+ | \`--fast\` | Skip Socratic questions, use defaults |
999
+ | \`--auto\` | Zero interaction, heuristic decisions |
1000
+ | \`--confirm-only\` | Show plan, get yes/no only |
1001
+
1002
+ Begin with Phase 0 (Socratic Planning) unless \`--fast\` or \`--auto\` flag is present.
559
1003
  `;
560
1004
 
561
1005
  const getPlannerAgent = (model: string) => `---
@@ -623,47 +1067,61 @@ description: Executes subtasks in a swarm - fast, focused, cost-effective
623
1067
  model: ${model}
624
1068
  ---
625
1069
 
626
- You are a swarm worker agent. Execute your assigned subtask efficiently.
1070
+ You are a swarm worker agent. Your prompt contains a **MANDATORY SURVIVAL CHECKLIST** - follow it IN ORDER.
627
1071
 
628
- ## Context
1072
+ ## CRITICAL: Read Your Prompt Carefully
629
1073
 
630
- Your prompt includes shared_context from the coordinator's knowledge gathering:
631
- - Relevant patterns from pdf-brain
632
- - Similar past approaches from CASS
633
- - Project-specific learnings from semantic-memory
1074
+ Your Task prompt contains detailed instructions including:
1075
+ - 9-step survival checklist (FOLLOW IN ORDER)
1076
+ - File reservations (YOU reserve, not coordinator)
1077
+ - Progress reporting requirements
1078
+ - Completion protocol
634
1079
 
635
- **Use this context** - it contains patterns and prior art relevant to your task.
1080
+ **DO NOT skip steps.** The checklist exists because skipping steps causes:
1081
+ - Lost work (no tracking)
1082
+ - Edit conflicts (no reservations)
1083
+ - Wasted time (no semantic memory query)
1084
+ - Silent failures (no progress reports)
636
1085
 
637
- ## Workflow
1086
+ ## Step Summary (details in your prompt)
638
1087
 
639
- 1. **Read** assigned files to understand current state
640
- 2. **Check skills** if you need domain guidance: \`skills_use(name="<relevant-skill>")\`
641
- 3. **Implement** changes following patterns from shared_context
642
- 4. **Verify** (typecheck, lint if applicable)
643
- 5. **Complete** with \`swarm_complete\`
1088
+ 1. **swarmmail_init()** - FIRST, before anything else
1089
+ 2. **semantic-memory_find()** - Check past learnings
1090
+ 3. **skills_list() / skills_use()** - Load relevant skills
1091
+ 4. **swarmmail_reserve()** - YOU reserve your files
1092
+ 5. **Do the work** - Read, implement, verify
1093
+ 6. **swarm_progress()** - Report at 25/50/75%
1094
+ 7. **swarm_checkpoint()** - Before risky operations
1095
+ 8. **semantic-memory_store()** - Store learnings
1096
+ 9. **swarm_complete()** - NOT beads_close
644
1097
 
645
- ## Rules
1098
+ ## Non-Negotiables
646
1099
 
647
- - Focus ONLY on your assigned files
648
- - Report blockers immediately via Swarm Mail (don't spin)
649
- - Use beads_update if blocked
650
- - Call swarm_complete when done - it handles bead closure and file release
1100
+ - **Step 1 is MANDATORY** - swarm_complete fails without init
1101
+ - **Step 2 saves time** - past agents may have solved this
1102
+ - **Step 4 prevents conflicts** - workers reserve, not coordinator
1103
+ - **Step 6 prevents silent failure** - report progress
1104
+ - **Step 9 is the ONLY way to close** - releases reservations, records learning
651
1105
 
652
- ## Communication
1106
+ ## When Blocked
653
1107
 
654
1108
  \`\`\`
655
1109
  swarmmail_send(
656
1110
  to=["coordinator"],
657
- subject="Progress/Blocker",
658
- body="...",
659
- thread_id="<epic_id>"
1111
+ subject="BLOCKED: <bead-id>",
1112
+ body="<what you need>",
1113
+ importance="high"
660
1114
  )
1115
+ beads_update(id="<bead-id>", status="blocked")
661
1116
  \`\`\`
662
1117
 
663
- ## Learning
1118
+ ## Focus
664
1119
 
665
- If you discover a reusable pattern worth preserving:
666
- \`semantic-memory_store(information="<pattern + why it matters>")\`
1120
+ - Only modify your assigned files
1121
+ - Don't fix other agents' code - coordinate instead
1122
+ - Report scope changes before expanding
1123
+
1124
+ Begin by reading your full prompt and executing Step 1.
667
1125
  `;
668
1126
 
669
1127
  // ============================================================================
@@ -834,6 +1292,8 @@ async function setup() {
834
1292
 
835
1293
  p.intro("opencode-swarm-plugin v" + VERSION);
836
1294
 
1295
+ let isReinstall = false;
1296
+
837
1297
  // Check if already configured FIRST
838
1298
  const configDir = join(homedir(), ".config", "opencode");
839
1299
  const pluginDir = join(configDir, "plugin");
@@ -842,14 +1302,20 @@ async function setup() {
842
1302
 
843
1303
  const pluginPath = join(pluginDir, "swarm.ts");
844
1304
  const commandPath = join(commandDir, "swarm.md");
845
- const plannerAgentPath = join(agentDir, "swarm-planner.md");
846
- const workerAgentPath = join(agentDir, "swarm-worker.md");
1305
+ const swarmAgentDir = join(agentDir, "swarm");
1306
+ const plannerAgentPath = join(swarmAgentDir, "planner.md");
1307
+ const workerAgentPath = join(swarmAgentDir, "worker.md");
1308
+ // Legacy flat paths (for detection/cleanup)
1309
+ const legacyPlannerPath = join(agentDir, "swarm-planner.md");
1310
+ const legacyWorkerPath = join(agentDir, "swarm-worker.md");
847
1311
 
848
1312
  const existingFiles = [
849
1313
  pluginPath,
850
1314
  commandPath,
851
1315
  plannerAgentPath,
852
1316
  workerAgentPath,
1317
+ legacyPlannerPath,
1318
+ legacyWorkerPath,
853
1319
  ].filter((f) => existsSync(f));
854
1320
 
855
1321
  if (existingFiles.length > 0) {
@@ -872,7 +1338,7 @@ async function setup() {
872
1338
  {
873
1339
  value: "reinstall",
874
1340
  label: "Reinstall everything",
875
- hint: "Check deps and regenerate all config files",
1341
+ hint: "Check deps, sync bundled skills, regenerate config files",
876
1342
  },
877
1343
  ],
878
1344
  });
@@ -906,28 +1372,40 @@ async function setup() {
906
1372
  process.exit(0);
907
1373
  }
908
1374
 
909
- // Update model lines in agent files
910
- if (existsSync(plannerAgentPath)) {
911
- const content = readFileSync(plannerAgentPath, "utf-8");
1375
+ // Update model lines in agent files (check both nested and legacy paths)
1376
+ const plannerPaths = [plannerAgentPath, legacyPlannerPath].filter(existsSync);
1377
+ const workerPaths = [workerAgentPath, legacyWorkerPath].filter(existsSync);
1378
+
1379
+ for (const path of plannerPaths) {
1380
+ const content = readFileSync(path, "utf-8");
912
1381
  const updated = content.replace(
913
1382
  /^model: .+$/m,
914
1383
  `model: ${coordinatorModel}`,
915
1384
  );
916
- writeFileSync(plannerAgentPath, updated);
1385
+ writeFileSync(path, updated);
1386
+ }
1387
+ if (plannerPaths.length > 0) {
917
1388
  p.log.success("Planner: " + coordinatorModel);
918
1389
  }
919
- if (existsSync(workerAgentPath)) {
920
- const content = readFileSync(workerAgentPath, "utf-8");
1390
+
1391
+ for (const path of workerPaths) {
1392
+ const content = readFileSync(path, "utf-8");
921
1393
  const updated = content.replace(
922
1394
  /^model: .+$/m,
923
1395
  `model: ${workerModel}`,
924
1396
  );
925
- writeFileSync(workerAgentPath, updated);
1397
+ writeFileSync(path, updated);
1398
+ }
1399
+ if (workerPaths.length > 0) {
926
1400
  p.log.success("Worker: " + workerModel);
927
1401
  }
1402
+
928
1403
  p.outro("Models updated! Your customizations are preserved.");
929
1404
  return;
930
1405
  }
1406
+ if (action === "reinstall") {
1407
+ isReinstall = true;
1408
+ }
931
1409
  // action === "reinstall" - fall through to full setup
932
1410
  }
933
1411
 
@@ -1157,7 +1635,7 @@ async function setup() {
1157
1635
 
1158
1636
  // Create directories if needed
1159
1637
  const skillsDir = join(configDir, "skills");
1160
- for (const dir of [pluginDir, commandDir, agentDir, skillsDir]) {
1638
+ for (const dir of [pluginDir, commandDir, agentDir, swarmAgentDir, skillsDir]) {
1161
1639
  if (!existsSync(dir)) {
1162
1640
  mkdirSync(dir, { recursive: true });
1163
1641
  }
@@ -1169,25 +1647,94 @@ async function setup() {
1169
1647
  writeFileSync(commandPath, SWARM_COMMAND);
1170
1648
  p.log.success("Command: " + commandPath);
1171
1649
 
1650
+ // Write nested agent files (swarm/planner.md, swarm/worker.md)
1651
+ // This is the format used by Task(subagent_type="swarm/worker")
1172
1652
  writeFileSync(plannerAgentPath, getPlannerAgent(coordinatorModel as string));
1173
1653
  p.log.success("Planner agent: " + plannerAgentPath);
1174
1654
 
1175
1655
  writeFileSync(workerAgentPath, getWorkerAgent(workerModel as string));
1176
1656
  p.log.success("Worker agent: " + workerAgentPath);
1177
1657
 
1658
+ // Clean up legacy flat agent files if they exist
1659
+ if (existsSync(legacyPlannerPath)) {
1660
+ rmSync(legacyPlannerPath);
1661
+ p.log.message(dim(" Removed legacy: " + legacyPlannerPath));
1662
+ }
1663
+ if (existsSync(legacyWorkerPath)) {
1664
+ rmSync(legacyWorkerPath);
1665
+ p.log.message(dim(" Removed legacy: " + legacyWorkerPath));
1666
+ }
1667
+
1178
1668
  p.log.success("Skills directory: " + skillsDir);
1179
1669
 
1180
- // Show bundled skills info
1670
+ // Show bundled skills info (and optionally sync to global skills dir)
1181
1671
  const bundledSkillsPath = join(__dirname, "..", "global-skills");
1672
+ const bundledSkills = listDirectoryNames(bundledSkillsPath);
1182
1673
  if (existsSync(bundledSkillsPath)) {
1183
- try {
1184
- const { readdirSync } = require("fs");
1185
- const bundled = readdirSync(bundledSkillsPath, { withFileTypes: true })
1186
- .filter((d: { isDirectory: () => boolean }) => d.isDirectory())
1187
- .map((d: { name: string }) => d.name);
1188
- p.log.message(dim(" Bundled skills: " + bundled.join(", ")));
1189
- } catch {
1190
- // Ignore
1674
+ if (bundledSkills.length > 0) {
1675
+ p.log.message(dim(" Bundled skills: " + bundledSkills.join(", ")));
1676
+ }
1677
+ }
1678
+
1679
+ // If the user keeps their skills in ~/.config/opencode/skills, offer to sync the bundled set
1680
+ if (bundledSkills.length > 0) {
1681
+ const globalSkills = listDirectoryNames(skillsDir);
1682
+ const managedBundled = globalSkills.filter((name) =>
1683
+ existsSync(join(skillsDir, name, BUNDLED_SKILL_MARKER_FILENAME)),
1684
+ );
1685
+ const missingBundled = bundledSkills.filter(
1686
+ (name) => !globalSkills.includes(name),
1687
+ );
1688
+
1689
+ if (missingBundled.length > 0 || managedBundled.length > 0) {
1690
+ const shouldSync = await p.confirm({
1691
+ message:
1692
+ "Sync bundled skills into your global skills directory? " +
1693
+ (missingBundled.length > 0
1694
+ ? `(${missingBundled.length} missing)`
1695
+ : "(update managed skills)"),
1696
+ initialValue: isReinstall || missingBundled.length > 0,
1697
+ });
1698
+
1699
+ if (p.isCancel(shouldSync)) {
1700
+ p.cancel("Setup cancelled");
1701
+ process.exit(0);
1702
+ }
1703
+
1704
+ if (shouldSync) {
1705
+ const syncSpinner = p.spinner();
1706
+ syncSpinner.start("Syncing bundled skills...");
1707
+ try {
1708
+ const { installed, updated, skipped } = syncBundledSkillsToGlobal({
1709
+ bundledSkillsPath,
1710
+ globalSkillsPath: skillsDir,
1711
+ version: VERSION,
1712
+ });
1713
+ syncSpinner.stop("Bundled skills synced");
1714
+
1715
+ if (installed.length > 0) {
1716
+ p.log.success("Installed: " + installed.join(", "));
1717
+ }
1718
+ if (updated.length > 0) {
1719
+ p.log.success("Updated: " + updated.join(", "));
1720
+ }
1721
+ if (skipped.length > 0) {
1722
+ p.log.message(
1723
+ dim(
1724
+ "Skipped (already exists, not managed): " + skipped.join(", "),
1725
+ ),
1726
+ );
1727
+ }
1728
+ } catch (error) {
1729
+ syncSpinner.stop("Could not sync bundled skills");
1730
+ p.log.warn(
1731
+ "Bundled skills are still available from the package via skills_list.",
1732
+ );
1733
+ p.log.message(
1734
+ dim(error instanceof Error ? error.message : String(error)),
1735
+ );
1736
+ }
1737
+ }
1191
1738
  }
1192
1739
  }
1193
1740
 
@@ -1203,111 +1750,28 @@ async function setup() {
1203
1750
  const s = p.spinner();
1204
1751
  s.start("Updating AGENTS.md...");
1205
1752
 
1206
- const agentsPrompt = `You are updating the user's AGENTS.md file to add swarm plugin awareness.
1207
-
1208
- ## Task
1209
- Read ${agentsPath} and add sections for Skills, CASS, and Semantic Memory if they don't exist. Update existing sections if present.
1210
-
1211
- ## What to Add
1212
-
1213
- 1. **Tool Preferences** - If there's a tool_preferences section, add these tools:
1214
- - skills_list, skills_use, skills_read - knowledge injection
1215
- - cass_search, cass_view, cass_expand - search past agent sessions
1216
- - semantic-memory_find, semantic-memory_store - persistent learning
1217
-
1218
- 2. **Skills Section** - Add this (adapt style to match the file):
1219
-
1220
- ### Skills (Knowledge Injection)
1221
-
1222
- Skills are reusable knowledge packages. Load them on-demand for specialized tasks.
1223
-
1224
- **When to Use:**
1225
- - Before unfamiliar work - check if a skill exists
1226
- - When you need domain-specific patterns
1227
- - For complex workflows that benefit from guidance
1228
-
1229
- **Usage:**
1230
- \`\`\`
1231
- skills_list() # See available skills
1232
- skills_use(name="swarm-coordination") # Load a skill
1233
- skills_use(name="cli-builder", context="building a new CLI") # With context
1234
- \`\`\`
1235
-
1236
- **Bundled Skills:** cli-builder, learning-systems, mcp-tool-authoring, skill-creator, swarm-coordination
1237
-
1238
- 3. **CASS Section** - Add this:
1239
-
1240
- ### CASS (Cross-Agent Session Search)
1241
-
1242
- Search across ALL your AI coding agent histories before solving problems from scratch.
1243
-
1244
- **When to Use:**
1245
- - BEFORE implementing anything - check if any agent solved it before
1246
- - When debugging - "what did I try last time this error happened?"
1247
- - When learning patterns - "how did Cursor handle this API?"
1248
-
1249
- **Usage:**
1250
- \`\`\`
1251
- cass_search(query="authentication token refresh", limit=5) # Search all agents
1252
- cass_search(query="useEffect cleanup", agent="claude", days=7) # Filter by agent/time
1253
- cass_view(path="/path/from/search", line=42) # View specific result
1254
- cass_expand(path="/path", line=42, context=10) # Expand context around match
1255
- \`\`\`
1256
-
1257
- **Pro tip:** Query CASS at the START of complex tasks. Past solutions save time.
1258
-
1259
- 4. **Semantic Memory Section** - Add this:
1260
-
1261
- ### Semantic Memory (Persistent Learning)
1262
-
1263
- Store and retrieve learnings across sessions. Memories persist and are searchable.
1264
-
1265
- **When to Use:**
1266
- - After solving a tricky problem - store the solution
1267
- - After making architectural decisions - store the reasoning
1268
- - Before starting work - search for relevant past learnings
1269
- - When you discover project-specific patterns
1270
-
1271
- **Usage:**
1272
- \`\`\`
1273
- # Store a learning
1274
- semantic-memory_store(information="OAuth refresh tokens need 5min buffer before expiry", metadata="auth, tokens")
1275
-
1276
- # Search for relevant memories
1277
- semantic-memory_find(query="token refresh", limit=5)
1278
-
1279
- # Validate a memory is still accurate (resets decay timer)
1280
- semantic-memory_validate(id="mem_123")
1281
- \`\`\`
1282
-
1283
- **Pro tip:** Store the WHY, not just the WHAT. Future you needs context.
1284
-
1285
- ## Rules
1286
- - Preserve existing content and style
1287
- - Don't duplicate - update existing sections if present
1288
- - Keep tone consistent with the rest of the file
1289
- - Place sections in logical order (Skills, CASS, Semantic Memory)
1290
- - If there's a tool_preferences section, add the tools there too
1291
-
1292
- Edit the file now.`;
1293
-
1294
1753
  try {
1295
- const result = Bun.spawnSync(["opencode", "run", agentsPrompt], {
1296
- stdout: "pipe",
1297
- stderr: "pipe",
1298
- timeout: 120000,
1299
- });
1754
+ const bundledSkillsCsv =
1755
+ bundledSkills.length > 0
1756
+ ? bundledSkills.join(", ")
1757
+ : "cli-builder, learning-systems, skill-creator, swarm-coordination, system-design, testing-patterns";
1758
+
1759
+ const result = updateAgentsMdFile({ agentsPath, bundledSkillsCsv });
1300
1760
 
1301
- if (result.exitCode === 0) {
1761
+ if (result.changed) {
1302
1762
  s.stop("AGENTS.md updated");
1303
- p.log.success("Added skill awareness to AGENTS.md");
1763
+ p.log.success("Updated: " + agentsPath);
1764
+ if (result.backupPath) {
1765
+ p.log.message(dim(" Backup: " + result.backupPath));
1766
+ }
1304
1767
  } else {
1305
- s.stop("Could not update AGENTS.md");
1306
- p.log.warn("You can manually add skills section later");
1768
+ s.stop("AGENTS.md already up to date");
1307
1769
  }
1308
- } catch {
1770
+ } catch (error) {
1309
1771
  s.stop("Could not update AGENTS.md");
1310
- p.log.warn("You can manually add skills section later");
1772
+ p.log.error(
1773
+ error instanceof Error ? error.message : "Unknown error updating file",
1774
+ );
1311
1775
  }
1312
1776
  }
1313
1777
  }
@@ -1786,15 +2250,6 @@ async function agents() {
1786
2250
  return;
1787
2251
  }
1788
2252
 
1789
- // Check if opencode is available
1790
- const opencode = await checkCommand("opencode", ["--version"]);
1791
- if (!opencode.available) {
1792
- p.log.error("OpenCode not found");
1793
- p.log.message(dim("Install: npm install -g opencode"));
1794
- p.outro("Aborted");
1795
- return;
1796
- }
1797
-
1798
2253
  const confirm = await p.confirm({
1799
2254
  message: "Update AGENTS.md with skill awareness?",
1800
2255
  initialValue: true,
@@ -1808,82 +2263,28 @@ async function agents() {
1808
2263
  const s = p.spinner();
1809
2264
  s.start("Updating AGENTS.md with skill awareness...");
1810
2265
 
1811
- const prompt = `You are updating the user's AGENTS.md file to add swarm plugin awareness.
1812
-
1813
- ## Task
1814
- Read ~/.config/opencode/AGENTS.md and add sections for Skills, CASS, and Semantic Memory if they don't exist.
1815
-
1816
- ## What to Add
1817
-
1818
- 1. **Tool Preferences** - Add these tools to any tool_preferences section:
1819
- - skills_list, skills_use, skills_read - knowledge injection
1820
- - cass_search, cass_view, cass_expand - search past agent sessions
1821
- - semantic-memory_find, semantic-memory_store - persistent learning
1822
-
1823
- 2. **Skills Section**:
1824
-
1825
- ### Skills (Knowledge Injection)
1826
-
1827
- Skills are reusable knowledge packages. Load on-demand for specialized tasks.
1828
-
1829
- **When to Use:** Before unfamiliar work, when you need domain patterns, for complex workflows.
1830
-
1831
- \`\`\`
1832
- skills_list() # See available skills
1833
- skills_use(name="swarm-coordination") # Load a skill
1834
- \`\`\`
1835
-
1836
- **Bundled:** cli-builder, learning-systems, mcp-tool-authoring, skill-creator, swarm-coordination
1837
-
1838
- 3. **CASS Section**:
1839
-
1840
- ### CASS (Cross-Agent Session Search)
1841
-
1842
- Search ALL your AI coding agent histories before solving from scratch.
1843
-
1844
- **When to Use:** BEFORE implementing - check if solved before. When debugging - what worked last time?
1845
-
1846
- \`\`\`
1847
- cass_search(query="auth token refresh", limit=5) # Search all agents
1848
- cass_view(path="/path/from/search", line=42) # View result
1849
- \`\`\`
1850
-
1851
- 4. **Semantic Memory Section**:
1852
-
1853
- ### Semantic Memory (Persistent Learning)
1854
-
1855
- Store and retrieve learnings across sessions.
1856
-
1857
- **When to Use:** After solving tricky problems, after architectural decisions, before starting work.
1858
-
1859
- \`\`\`
1860
- semantic-memory_store(information="OAuth needs 5min buffer", metadata="auth")
1861
- semantic-memory_find(query="token refresh", limit=5)
1862
- \`\`\`
1863
-
1864
- ## Rules
1865
- - Preserve existing content and style
1866
- - Don't duplicate - update if sections exist
1867
- - Keep tone consistent
1868
-
1869
- Edit the file now.`;
2266
+ const bundledSkillsPath = join(__dirname, "..", "global-skills");
2267
+ const bundledSkills = listDirectoryNames(bundledSkillsPath);
1870
2268
 
1871
2269
  try {
1872
- const result = Bun.spawnSync(["opencode", "run", prompt], {
1873
- stdio: ["inherit", "pipe", "pipe"],
1874
- timeout: 120000, // 2 minute timeout
1875
- });
2270
+ const bundledSkillsCsv =
2271
+ bundledSkills.length > 0
2272
+ ? bundledSkills.join(", ")
2273
+ : "cli-builder, learning-systems, skill-creator, swarm-coordination, system-design, testing-patterns";
2274
+
2275
+ const result = updateAgentsMdFile({ agentsPath, bundledSkillsCsv });
1876
2276
 
1877
- if (result.exitCode === 0) {
2277
+ if (result.changed) {
1878
2278
  s.stop("AGENTS.md updated with skill awareness");
1879
2279
  p.log.success("Skills section added to " + agentsPath);
1880
2280
  p.log.message(
1881
2281
  dim("Skills available: skills_list, skills_use, skills_read"),
1882
2282
  );
2283
+ if (result.backupPath) {
2284
+ p.log.message(dim("Backup: " + result.backupPath));
2285
+ }
1883
2286
  } else {
1884
- const stderr = result.stderr?.toString() || "";
1885
- s.stop("Failed to update AGENTS.md");
1886
- p.log.error(stderr || "Unknown error");
2287
+ s.stop("AGENTS.md already up to date");
1887
2288
  }
1888
2289
  } catch (error) {
1889
2290
  s.stop("Failed to update AGENTS.md");