opencode-swarm-plugin 0.12.20 → 0.12.23

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/src/swarm.ts CHANGED
@@ -48,6 +48,11 @@ import {
48
48
  formatToolAvailability,
49
49
  type ToolName,
50
50
  } from "./tool-availability";
51
+ import {
52
+ getSkillsContextForSwarm,
53
+ findRelevantSkills,
54
+ listSkills,
55
+ } from "./skills";
51
56
 
52
57
  // ============================================================================
53
58
  // Conflict Detection
@@ -708,16 +713,27 @@ Only modify these files. Need others? Message the coordinator.
708
713
  ### Agent Mail
709
714
  - agentmail_send (thread_id: {epic_id})
710
715
 
716
+ ### Skills (if available)
717
+ - skills_list (discover available skills)
718
+ - skills_use (activate a skill for specialized guidance)
719
+
711
720
  ### Completion
712
721
  - swarm_complete (REQUIRED when done)
713
722
 
723
+ ## [LEARNING]
724
+ As you work, note reusable patterns, best practices, or domain insights:
725
+ - If you discover something that would help future agents, consider creating a skill
726
+ - Use skills_create to codify patterns for the project
727
+ - Good skills have clear "when to use" descriptions with actionable instructions
728
+ - Skills make swarms smarter over time
729
+
714
730
  ## [OUTPUT]
715
731
  1. Read files first
716
732
  2. Implement changes
717
733
  3. Verify (typecheck)
718
734
  4. Complete with swarm_complete
719
735
 
720
- Return: Summary of changes made
736
+ Return: Summary of changes made. Note any patterns worth preserving as skills.
721
737
 
722
738
  **Never work silently.** Communicate progress and blockers immediately.
723
739
 
@@ -1157,6 +1173,8 @@ const STRATEGY_DECOMPOSITION_PROMPT = `You are decomposing a task into paralleli
1157
1173
 
1158
1174
  {cass_history}
1159
1175
 
1176
+ {skills_context}
1177
+
1160
1178
  ## MANDATORY: Beads Issue Tracking
1161
1179
 
1162
1180
  **Every subtask MUST become a bead.** This is non-negotiable.
@@ -1240,6 +1258,10 @@ export const swarm_plan_prompt = tool({
1240
1258
 
1241
1259
  .optional()
1242
1260
  .describe("Max CASS results to include (default: 3)"),
1261
+ include_skills: tool.schema
1262
+ .boolean()
1263
+ .optional()
1264
+ .describe("Include available skills in context (default: true)"),
1243
1265
  },
1244
1266
  async execute(args) {
1245
1267
  // Select strategy
@@ -1288,6 +1310,30 @@ export const swarm_plan_prompt = tool({
1288
1310
  cassResultInfo = { queried: false, reason: "disabled" };
1289
1311
  }
1290
1312
 
1313
+ // Fetch skills context
1314
+ let skillsContext = "";
1315
+ let skillsInfo: { included: boolean; count?: number; relevant?: string[] } = {
1316
+ included: false,
1317
+ };
1318
+
1319
+ if (args.include_skills !== false) {
1320
+ const allSkills = await listSkills();
1321
+ if (allSkills.length > 0) {
1322
+ skillsContext = await getSkillsContextForSwarm();
1323
+ const relevantSkills = await findRelevantSkills(args.task);
1324
+ skillsInfo = {
1325
+ included: true,
1326
+ count: allSkills.length,
1327
+ relevant: relevantSkills,
1328
+ };
1329
+
1330
+ // Add suggestion for relevant skills
1331
+ if (relevantSkills.length > 0) {
1332
+ skillsContext += `\n\n**Suggested skills for this task**: ${relevantSkills.join(", ")}`;
1333
+ }
1334
+ }
1335
+ }
1336
+
1291
1337
  // Format strategy guidelines
1292
1338
  const strategyGuidelines = formatStrategyGuidelines(selectedStrategy);
1293
1339
 
@@ -1301,6 +1347,7 @@ export const swarm_plan_prompt = tool({
1301
1347
  .replace("{strategy_guidelines}", strategyGuidelines)
1302
1348
  .replace("{context_section}", contextSection)
1303
1349
  .replace("{cass_history}", cassContext || "")
1350
+ .replace("{skills_context}", skillsContext || "")
1304
1351
  .replace("{max_subtasks}", (args.max_subtasks ?? 5).toString());
1305
1352
 
1306
1353
  return JSON.stringify(
@@ -1328,6 +1375,7 @@ export const swarm_plan_prompt = tool({
1328
1375
  validation_note:
1329
1376
  "Parse agent response as JSON and validate with swarm_validate_decomposition",
1330
1377
  cass_history: cassResultInfo,
1378
+ skills: skillsInfo,
1331
1379
  },
1332
1380
  null,
1333
1381
  2,
@@ -2091,6 +2139,18 @@ export const swarm_complete = tool({
2091
2139
  ? "skipped"
2092
2140
  : "no files or ubs unavailable",
2093
2141
  },
2142
+ learning_prompt: `## Reflection
2143
+
2144
+ Did you learn anything reusable during this subtask? Consider:
2145
+
2146
+ 1. **Patterns**: Any code patterns or approaches that worked well?
2147
+ 2. **Gotchas**: Edge cases or pitfalls to warn future agents about?
2148
+ 3. **Best Practices**: Domain-specific guidelines worth documenting?
2149
+ 4. **Tool Usage**: Effective ways to use tools for this type of task?
2150
+
2151
+ If you discovered something valuable, use \`swarm_learn\` or \`skills_create\` to preserve it as a skill for future swarms.
2152
+
2153
+ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
2094
2154
  },
2095
2155
  null,
2096
2156
  2,
@@ -2562,6 +2622,196 @@ export const swarm_evaluation_prompt = tool({
2562
2622
  },
2563
2623
  });
2564
2624
 
2625
+ // ============================================================================
2626
+ // Swarm Learning
2627
+ // ============================================================================
2628
+
2629
+ /**
2630
+ * Learn from completed work and optionally create a skill
2631
+ *
2632
+ * This tool helps agents reflect on patterns, best practices, or domain
2633
+ * knowledge discovered during task execution and codify them into reusable
2634
+ * skills for future swarms.
2635
+ *
2636
+ * Implements the "learning swarm" pattern where swarms get smarter over time.
2637
+ */
2638
+ export const swarm_learn = tool({
2639
+ description: `Analyze completed work and optionally create a skill from learned patterns.
2640
+
2641
+ Use after completing a subtask when you've discovered:
2642
+ - Reusable code patterns or approaches
2643
+ - Domain-specific best practices
2644
+ - Gotchas or edge cases to warn about
2645
+ - Effective tool usage patterns
2646
+
2647
+ This tool helps you formalize learnings into a skill that future agents can discover and use.`,
2648
+ args: {
2649
+ summary: tool.schema
2650
+ .string()
2651
+ .describe("Brief summary of what was learned (1-2 sentences)"),
2652
+ pattern_type: tool.schema
2653
+ .enum(["code-pattern", "best-practice", "gotcha", "tool-usage", "domain-knowledge", "workflow"])
2654
+ .describe("Category of the learning"),
2655
+ details: tool.schema
2656
+ .string()
2657
+ .describe("Detailed explanation of the pattern or practice"),
2658
+ example: tool.schema
2659
+ .string()
2660
+ .optional()
2661
+ .describe("Code example or concrete illustration"),
2662
+ when_to_use: tool.schema
2663
+ .string()
2664
+ .describe("When should an agent apply this knowledge?"),
2665
+ files_context: tool.schema
2666
+ .array(tool.schema.string())
2667
+ .optional()
2668
+ .describe("Files that exemplify this pattern"),
2669
+ create_skill: tool.schema
2670
+ .boolean()
2671
+ .optional()
2672
+ .describe("Create a skill from this learning (default: false, just document)"),
2673
+ skill_name: tool.schema
2674
+ .string()
2675
+ .regex(/^[a-z0-9-]+$/)
2676
+ .max(64)
2677
+ .optional()
2678
+ .describe("Skill name if creating (required if create_skill=true)"),
2679
+ skill_tags: tool.schema
2680
+ .array(tool.schema.string())
2681
+ .optional()
2682
+ .describe("Tags for the skill if creating"),
2683
+ },
2684
+ async execute(args) {
2685
+ // Format the learning as structured documentation
2686
+ const learning = {
2687
+ summary: args.summary,
2688
+ type: args.pattern_type,
2689
+ details: args.details,
2690
+ example: args.example,
2691
+ when_to_use: args.when_to_use,
2692
+ files_context: args.files_context,
2693
+ recorded_at: new Date().toISOString(),
2694
+ };
2695
+
2696
+ // If creating a skill, generate and create it
2697
+ if (args.create_skill) {
2698
+ if (!args.skill_name) {
2699
+ return JSON.stringify(
2700
+ {
2701
+ success: false,
2702
+ error: "skill_name is required when create_skill=true",
2703
+ learning: learning,
2704
+ },
2705
+ null,
2706
+ 2,
2707
+ );
2708
+ }
2709
+
2710
+ // Build skill body from learning
2711
+ const skillBody = `# ${args.summary}
2712
+
2713
+ ## When to Use
2714
+ ${args.when_to_use}
2715
+
2716
+ ## ${args.pattern_type.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}
2717
+
2718
+ ${args.details}
2719
+
2720
+ ${args.example ? `## Example\n\n\`\`\`\n${args.example}\n\`\`\`\n` : ""}
2721
+ ${args.files_context && args.files_context.length > 0 ? `## Reference Files\n\n${args.files_context.map((f) => `- \`${f}\``).join("\n")}\n` : ""}
2722
+
2723
+ ---
2724
+ *Learned from swarm execution on ${new Date().toISOString().split("T")[0]}*`;
2725
+
2726
+ // Import skills_create functionality
2727
+ const { getSkill, invalidateSkillsCache } = await import("./skills");
2728
+ const { mkdir, writeFile } = await import("fs/promises");
2729
+ const { join } = await import("path");
2730
+
2731
+ // Check if skill exists
2732
+ const existing = await getSkill(args.skill_name);
2733
+ if (existing) {
2734
+ return JSON.stringify(
2735
+ {
2736
+ success: false,
2737
+ error: `Skill '${args.skill_name}' already exists`,
2738
+ existing_path: existing.path,
2739
+ learning: learning,
2740
+ suggestion: "Use skills_update to add to existing skill, or choose a different name",
2741
+ },
2742
+ null,
2743
+ 2,
2744
+ );
2745
+ }
2746
+
2747
+ // Create skill directory and file
2748
+ const skillDir = join(process.cwd(), ".opencode", "skills", args.skill_name);
2749
+ const skillPath = join(skillDir, "SKILL.md");
2750
+
2751
+ const frontmatter = [
2752
+ "---",
2753
+ `name: ${args.skill_name}`,
2754
+ `description: ${args.when_to_use.slice(0, 200)}${args.when_to_use.length > 200 ? "..." : ""}`,
2755
+ "tags:",
2756
+ ` - ${args.pattern_type}`,
2757
+ ` - learned`,
2758
+ ...(args.skill_tags || []).map((t) => ` - ${t}`),
2759
+ "---",
2760
+ ].join("\n");
2761
+
2762
+ try {
2763
+ await mkdir(skillDir, { recursive: true });
2764
+ await writeFile(skillPath, `${frontmatter}\n\n${skillBody}`, "utf-8");
2765
+ invalidateSkillsCache();
2766
+
2767
+ return JSON.stringify(
2768
+ {
2769
+ success: true,
2770
+ skill_created: true,
2771
+ skill: {
2772
+ name: args.skill_name,
2773
+ path: skillPath,
2774
+ type: args.pattern_type,
2775
+ },
2776
+ learning: learning,
2777
+ message: `Created skill '${args.skill_name}' from learned pattern. Future agents can discover it with skills_list.`,
2778
+ },
2779
+ null,
2780
+ 2,
2781
+ );
2782
+ } catch (error) {
2783
+ return JSON.stringify(
2784
+ {
2785
+ success: false,
2786
+ error: `Failed to create skill: ${error instanceof Error ? error.message : String(error)}`,
2787
+ learning: learning,
2788
+ },
2789
+ null,
2790
+ 2,
2791
+ );
2792
+ }
2793
+ }
2794
+
2795
+ // Just document the learning without creating a skill
2796
+ return JSON.stringify(
2797
+ {
2798
+ success: true,
2799
+ skill_created: false,
2800
+ learning: learning,
2801
+ message: "Learning documented. Use create_skill=true to persist as a skill for future agents.",
2802
+ suggested_skill_name: args.skill_name ||
2803
+ args.summary
2804
+ .toLowerCase()
2805
+ .replace(/[^a-z0-9\s-]/g, "")
2806
+ .replace(/\s+/g, "-")
2807
+ .slice(0, 64),
2808
+ },
2809
+ null,
2810
+ 2,
2811
+ );
2812
+ },
2813
+ });
2814
+
2565
2815
  // ============================================================================
2566
2816
  // Error Accumulator
2567
2817
  // ============================================================================
@@ -2703,12 +2953,17 @@ export const swarm_resolve_error = tool({
2703
2953
  /**
2704
2954
  * Initialize swarm and check tool availability
2705
2955
  *
2706
- * Call this at the start of a swarm session to see what tools are available
2707
- * and what features will be degraded.
2956
+ * Call this at the start of a swarm session to see what tools are available,
2957
+ * what skills exist in the project, and what features will be degraded.
2958
+ *
2959
+ * Skills are automatically discovered from:
2960
+ * - .opencode/skills/
2961
+ * - .claude/skills/
2962
+ * - skills/
2708
2963
  */
2709
2964
  export const swarm_init = tool({
2710
2965
  description:
2711
- "Initialize swarm session and check tool availability. Call at swarm start to see what features are available.",
2966
+ "Initialize swarm session: discovers available skills, checks tool availability. ALWAYS call at swarm start.",
2712
2967
  args: {
2713
2968
  project_path: tool.schema
2714
2969
  .string()
@@ -2757,6 +3012,26 @@ export const swarm_init = tool({
2757
3012
  degradedFeatures.push("persistent learning (using in-memory fallback)");
2758
3013
  }
2759
3014
 
3015
+ // Discover available skills
3016
+ const availableSkills = await listSkills();
3017
+ const skillsInfo = {
3018
+ count: availableSkills.length,
3019
+ available: availableSkills.length > 0,
3020
+ skills: availableSkills.map((s) => ({
3021
+ name: s.name,
3022
+ description: s.description,
3023
+ hasScripts: s.hasScripts,
3024
+ })),
3025
+ };
3026
+
3027
+ // Add skills guidance if available
3028
+ let skillsGuidance: string | undefined;
3029
+ if (availableSkills.length > 0) {
3030
+ skillsGuidance = `Found ${availableSkills.length} skill(s). Use skills_list to see details, skills_use to activate.`;
3031
+ } else {
3032
+ skillsGuidance = "No skills found. Add skills to .opencode/skills/ or .claude/skills/ for specialized guidance.";
3033
+ }
3034
+
2760
3035
  return JSON.stringify(
2761
3036
  {
2762
3037
  ready: true,
@@ -2769,10 +3044,12 @@ export const swarm_init = tool({
2769
3044
  },
2770
3045
  ]),
2771
3046
  ),
3047
+ skills: skillsInfo,
2772
3048
  warnings: warnings.length > 0 ? warnings : undefined,
2773
3049
  degraded_features:
2774
3050
  degradedFeatures.length > 0 ? degradedFeatures : undefined,
2775
3051
  recommendations: {
3052
+ skills: skillsGuidance,
2776
3053
  beads: beadsAvailable
2777
3054
  ? "✓ Use beads for all task tracking"
2778
3055
  : "Install beads: npm i -g @joelhooks/beads",
@@ -2802,6 +3079,7 @@ export const swarmTools = {
2802
3079
  swarm_progress: swarm_progress,
2803
3080
  swarm_broadcast: swarm_broadcast,
2804
3081
  swarm_complete: swarm_complete,
3082
+ swarm_learn: swarm_learn,
2805
3083
  swarm_record_outcome: swarm_record_outcome,
2806
3084
  swarm_subtask_prompt: swarm_subtask_prompt,
2807
3085
  swarm_spawn_subtask: swarm_spawn_subtask,