opencode-swarm 6.13.3 → 6.14.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/README.md CHANGED
@@ -76,7 +76,7 @@ This checks that everything is wired up correctly.
76
76
 
77
77
  ### Configure Models (Optional)
78
78
 
79
- By default, Swarm uses whatever model OpenCode is configured with. To route different agents to different models (recommended), create `.opencode/swarm.json` in your project:
79
+ By default, Swarm v6.14+ uses free OpenCode Zen models (no API key required). You can override any agent's model by creating `.opencode/swarm.json` in your project. See the [LLM Provider Guide](#llm-provider-guide) for all options.
80
80
 
81
81
  ```json
82
82
  {
@@ -127,6 +127,60 @@ Use `/swarm status` at any time to see where things stand.
127
127
 
128
128
  ---
129
129
 
130
+ ## LLM Provider Guide
131
+
132
+ Swarm works with any LLM provider supported by OpenCode. Different agents benefit from different models — the architect needs strong reasoning, the coder needs strong code generation, and the reviewer benefits from a model different from the coder (to catch blind spots).
133
+
134
+ ### Free Tier (OpenCode Zen Models)
135
+
136
+ OpenCode Zen provides free models via the `opencode/` provider prefix. These are excellent starting points and require no API key:
137
+
138
+ ```json
139
+ {
140
+ "agents": {
141
+ "coder": { "model": "opencode/minimax-m2.5-free" },
142
+ "reviewer": { "model": "opencode/big-pickle" },
143
+ "test_engineer":{ "model": "opencode/gpt-5-nano" },
144
+ "explorer": { "model": "opencode/trinity-large-preview-free" },
145
+ "sme": { "model": "opencode/trinity-large-preview-free" },
146
+ "critic": { "model": "opencode/trinity-large-preview-free" },
147
+ "docs": { "model": "opencode/trinity-large-preview-free" },
148
+ "designer": { "model": "opencode/trinity-large-preview-free" }
149
+ }
150
+ }
151
+ ```
152
+
153
+ > Save this configuration to `.opencode/swarm.json` in your project root (or `~/.config/opencode/opencode-swarm.json` for global config).
154
+
155
+ > **Note:** The `architect` key is intentionally omitted — it inherits whatever model you have selected in the OpenCode UI for maximum reasoning quality.
156
+
157
+ ### Paid Providers
158
+
159
+ For production use, mix providers to maximize quality across writing vs. reviewing:
160
+
161
+ | Agent | Recommended Model | Why |
162
+ |---|---|---|
163
+ | `architect` | OpenCode UI selection | Needs strongest reasoning |
164
+ | `coder` | `minimax-coding-plan/MiniMax-M2.5` | Fast, accurate code generation |
165
+ | `reviewer` | `zai-coding-plan/glm-5` | Different training data from coder |
166
+ | `test_engineer` | `minimax-coding-plan/MiniMax-M2.5` | Same strengths as coder |
167
+ | `explorer` | `google/gemini-2.5-flash` | Fast read-heavy analysis |
168
+ | `sme` | `kimi-for-coding/k2p5` | Strong domain expertise |
169
+ | `critic` | `zai-coding-plan/glm-5` | Independent plan review |
170
+ | `docs` | `zai-coding-plan/glm-4.7-flash` | Fast, cost-effective documentation generation |
171
+ | `designer` | `kimi-for-coding/k2p5` | Strong UI/UX generation capabilities |
172
+
173
+ ### Provider Formats
174
+
175
+ | Provider | Format | Example |
176
+ |---|---|---|
177
+ | OpenCode Zen (free) | `opencode/<model>` | `opencode/trinity-large-preview-free` |
178
+ | Anthropic | `anthropic/<model>` | `anthropic/claude-opus-4-6` |
179
+ | Google | `google/<model>` | `google/gemini-2.5-flash` |
180
+ | Z.ai | `zai-coding-plan/<model>` | `zai-coding-plan/glm-5` |
181
+ | MiniMax | `minimax-coding-plan/<model>` | `minimax-coding-plan/MiniMax-M2.5` |
182
+ | Kimi | `kimi-for-coding/<model>` | `kimi-for-coding/k2p5` |
183
+
130
184
  ## Useful Commands
131
185
 
132
186
  | Command | What It Does |
@@ -381,6 +435,35 @@ Config file location: `~/.config/opencode/opencode-swarm.json` (global) or `.ope
381
435
 
382
436
  ### Automation
383
437
 
438
+ ## Mode Detection (v6.13)
439
+
440
+ Swarm now explicitly distinguishes five architect modes:
441
+
442
+ - **`DISCOVER`** — After the explorer finishes scanning the codebase.
443
+ - **`PLAN`** — When the architect writes or updates the plan.
444
+ - **`EXECUTE`** — During task implementation (the normal pipeline).
445
+ - **`PHASE-WRAP`** — After all tasks in a phase are completed, before docs are updated.
446
+ - **`UNKNOWN`** — Fallback when the current state does not match any known mode.
447
+
448
+ Each mode determines which injection blocks are added to the LLM prompt (e.g., plan cursor is injected in `PLAN`, tool output truncation in `EXECUTE`, etc.).
449
+
450
+ Default mode: `manual`. No background automation — all actions require explicit slash commands.
451
+
452
+ Modes:
453
+
454
+ - `manual` — No background automation. All actions via slash commands (default).
455
+ - `hybrid` — Background automation for safe operations, manual for sensitive ones.
456
+ - `auto` — Full background automation.
457
+
458
+ Capability defaults:
459
+
460
+ - `plan_sync`: `true` — Background plan synchronization using `fs.watch` with debounced writes (300ms) and 2-second polling fallback
461
+ - `phase_preflight`: `false` — Phase preflight checks before agent execution (opt-in)
462
+ - `config_doctor_on_startup`: `false` — Validate configuration on startup
463
+ - `config_doctor_autofix`: `false` — Auto-fix for config doctor (opt-in, security-sensitive)
464
+ - `evidence_auto_summaries`: `true` — Automatic summaries for evidence bundles
465
+ - `decision_drift_detection`: `true` — Detect drift between planned and actual decisions
466
+
384
467
  ## Plan Cursor (v6.13)
385
468
 
386
469
  The `plan_cursor` config compresses the plan that is injected into the LLM context.
@@ -429,37 +512,6 @@ When truncation is active, a footer is appended:
429
512
  [output truncated to {maxLines} lines – use `tool_output.per_tool.<tool>` to adjust]
430
513
  ```
431
514
 
432
- ## Mode Detection (v6.13)
433
-
434
- Swarm now explicitly distinguishes five architect modes:
435
-
436
- | Mode | When Injected |
437
- |------|----------------|
438
- | `DISCOVER` | After the explorer finishes scanning the codebase. |
439
- | `PLAN` | When the architect writes or updates the plan. |
440
- | `EXECUTE` | During task implementation (the normal pipeline). |
441
- | `PHASE-WRAP` | After all tasks in a phase are completed, before docs are updated. |
442
- | `UNKNOWN` | Fallback when the current state does not match any known mode. |
443
-
444
- Each mode determines which injection blocks are added to the LLM prompt (e.g., plan cursor is injected in `PLAN`, tool output truncation in `EXECUTE`, etc.).
445
-
446
- Default mode: `manual`. No background automation — all actions require explicit slash commands.
447
-
448
- Modes:
449
-
450
- - `manual` — No background automation. All actions via slash commands (default).
451
- - `hybrid` — Background automation for safe operations, manual for sensitive ones.
452
- - `auto` — Full background automation.
453
-
454
- Capability defaults:
455
-
456
- - `plan_sync`: `true` — Background plan synchronization using `fs.watch` with debounced writes (300ms) and 2-second polling fallback
457
- - `phase_preflight`: `false` — Phase preflight checks before agent execution (opt-in)
458
- - `config_doctor_on_startup`: `false` — Validate configuration on startup
459
- - `config_doctor_autofix`: `false` — Auto-fix for config doctor (opt-in, security-sensitive)
460
- - `evidence_auto_summaries`: `true` — Automatic summaries for evidence bundles
461
- - `decision_drift_detection`: `true` — Detect drift between planned and actual decisions
462
-
463
515
  ---
464
516
 
465
517
  ### Disabling Agents
@@ -671,6 +723,7 @@ Upcoming: v6.14 focuses on further context optimization and agent coordination i
671
723
  - [Design Rationale](docs/design-rationale.md)
672
724
  - [Installation Guide](docs/installation.md)
673
725
  - [Linux + Docker Desktop Install Guide](docs/installation-linux-docker.md)
726
+ - [LLM Operator Installation Guide](docs/installation-llm-operator.md)
674
727
  - [Pre-Swarm Planning Guide](docs/planning.md)
675
728
  - [Swarm Briefing for LLMs](docs/swarm-briefing.md)
676
729
 
package/dist/cli/index.js CHANGED
@@ -53,11 +53,14 @@ async function install() {
53
53
  if (!fs.existsSync(PLUGIN_CONFIG_PATH)) {
54
54
  const defaultConfig = {
55
55
  agents: {
56
- architect: { model: "anthropic/claude-sonnet-4-20250514" },
57
- coder: { model: "anthropic/claude-sonnet-4-20250514" },
58
- sme: { model: "google/gemini-2.5-flash" },
59
- reviewer: { model: "google/gemini-2.5-flash" },
60
- test_engineer: { model: "google/gemini-2.5-flash" }
56
+ coder: { model: "opencode/minimax-m2.5-free" },
57
+ reviewer: { model: "opencode/big-pickle" },
58
+ test_engineer: { model: "opencode/gpt-5-nano" },
59
+ explorer: { model: "opencode/trinity-large-preview-free" },
60
+ sme: { model: "opencode/trinity-large-preview-free" },
61
+ critic: { model: "opencode/trinity-large-preview-free" },
62
+ docs: { model: "opencode/trinity-large-preview-free" },
63
+ designer: { model: "opencode/trinity-large-preview-free" }
61
64
  },
62
65
  max_iterations: 5
63
66
  };
package/dist/index.js CHANGED
@@ -30200,9 +30200,10 @@ function hasCompoundTestExtension(filename) {
30200
30200
  function getTestFilesFromConvention(sourceFiles) {
30201
30201
  const testFiles = [];
30202
30202
  for (const file3 of sourceFiles) {
30203
+ const normalizedPath = file3.replace(/\\/g, "/");
30203
30204
  const basename2 = path12.basename(file3);
30204
30205
  const dirname4 = path12.dirname(file3);
30205
- if (hasCompoundTestExtension(basename2) || basename2.includes(".spec.") || basename2.includes(".test.")) {
30206
+ if (hasCompoundTestExtension(basename2) || basename2.includes(".spec.") || basename2.includes(".test.") || normalizedPath.includes("/__tests__/") || normalizedPath.includes("/tests/") || normalizedPath.includes("/test/")) {
30206
30207
  if (!testFiles.includes(file3)) {
30207
30208
  testFiles.push(file3);
30208
30209
  }
@@ -31582,16 +31583,15 @@ for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
31582
31583
  }
31583
31584
  }
31584
31585
  var DEFAULT_MODELS = {
31585
- architect: "anthropic/claude-sonnet-4-20250514",
31586
- explorer: "google/gemini-2.5-flash",
31587
- coder: "anthropic/claude-sonnet-4-20250514",
31588
- test_engineer: "google/gemini-2.5-flash",
31589
- sme: "google/gemini-2.5-flash",
31590
- reviewer: "google/gemini-2.5-flash",
31591
- critic: "google/gemini-2.5-flash",
31592
- docs: "google/gemini-2.5-flash",
31593
- designer: "google/gemini-2.5-flash",
31594
- default: "google/gemini-2.5-flash"
31586
+ explorer: "opencode/trinity-large-preview-free",
31587
+ coder: "opencode/minimax-m2.5-free",
31588
+ reviewer: "opencode/big-pickle",
31589
+ test_engineer: "opencode/gpt-5-nano",
31590
+ sme: "opencode/trinity-large-preview-free",
31591
+ critic: "opencode/trinity-large-preview-free",
31592
+ docs: "opencode/trinity-large-preview-free",
31593
+ designer: "opencode/trinity-large-preview-free",
31594
+ default: "opencode/trinity-large-preview-free"
31595
31595
  };
31596
31596
  var DEFAULT_SCORING_CONFIG = {
31597
31597
  enabled: false,
@@ -36890,6 +36890,26 @@ function isOutsideSwarmDir(filePath) {
36890
36890
  const relative2 = path15.relative(swarmDir, resolved);
36891
36891
  return relative2.startsWith("..") || path15.isAbsolute(relative2);
36892
36892
  }
36893
+ function isSourceCodePath(filePath) {
36894
+ if (!filePath)
36895
+ return false;
36896
+ const normalized = filePath.replace(/\\/g, "/");
36897
+ const nonSourcePatterns = [
36898
+ /^README(\..+)?$/i,
36899
+ /\/README(\..+)?$/i,
36900
+ /^CHANGELOG(\..+)?$/i,
36901
+ /\/CHANGELOG(\..+)?$/i,
36902
+ /^package\.json$/,
36903
+ /\/package\.json$/,
36904
+ /^\.github\//,
36905
+ /\/\.github\//,
36906
+ /^docs\//,
36907
+ /\/docs\//,
36908
+ /^\.swarm\//,
36909
+ /\/\.swarm\//
36910
+ ];
36911
+ return !nonSourcePatterns.some((pattern) => pattern.test(normalized));
36912
+ }
36893
36913
  function isGateTool(toolName) {
36894
36914
  const normalized = toolName.replace(/^[^:]+[:.]/, "");
36895
36915
  const gateTools = [
@@ -36935,7 +36955,7 @@ function createGuardrailsHooks(config3) {
36935
36955
  if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
36936
36956
  const args2 = output.args;
36937
36957
  const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
36938
- if (typeof targetPath === "string" && isOutsideSwarmDir(targetPath)) {
36958
+ if (typeof targetPath === "string" && isOutsideSwarmDir(targetPath) && isSourceCodePath(targetPath)) {
36939
36959
  const session2 = swarmState.agentSessions.get(input.sessionID);
36940
36960
  if (session2) {
36941
36961
  session2.architectWriteCount++;
@@ -38758,7 +38778,10 @@ var ECOSYSTEMS = [
38758
38778
  buildFiles: ["build.gradle", "build.gradle.kts", "gradle.properties"],
38759
38779
  toolchainCommands: ["gradle", "gradlew"],
38760
38780
  commands: [
38761
- { command: "./gradlew build", priority: 1 },
38781
+ {
38782
+ command: process.platform === "win32" ? "gradlew.bat build" : "./gradlew build",
38783
+ priority: 1
38784
+ },
38762
38785
  { command: "gradle build", priority: 2 }
38763
38786
  ]
38764
38787
  },
@@ -40824,7 +40847,7 @@ function normalizeAgentsFromDelegations(delegations) {
40824
40847
  function isValidRetroEntry(entry, phase) {
40825
40848
  return entry.type === "retrospective" && "phase_number" in entry && entry.phase_number === phase && "verdict" in entry && entry.verdict === "pass";
40826
40849
  }
40827
- async function executePhaseComplete(args2) {
40850
+ async function executePhaseComplete(args2, workingDirectory) {
40828
40851
  const phase = Number(args2.phase);
40829
40852
  const summary = args2.summary;
40830
40853
  const sessionID = args2.sessionID;
@@ -40855,8 +40878,8 @@ async function executePhaseComplete(args2) {
40855
40878
  const trackedAgents = session.phaseAgentsDispatched ?? new Set;
40856
40879
  const allAgents = new Set([...delegationAgents, ...trackedAgents]);
40857
40880
  const agentsDispatched = Array.from(allAgents).sort();
40858
- const directory = process.cwd();
40859
- const { config: config3 } = loadPluginConfigWithMeta(directory);
40881
+ const dir = workingDirectory ?? process.cwd();
40882
+ const { config: config3 } = loadPluginConfigWithMeta(dir);
40860
40883
  let phaseCompleteConfig;
40861
40884
  try {
40862
40885
  phaseCompleteConfig = PhaseCompleteConfigSchema.parse(config3.phase_complete ?? {});
@@ -40882,16 +40905,16 @@ async function executePhaseComplete(args2) {
40882
40905
  warnings: []
40883
40906
  }, null, 2);
40884
40907
  }
40885
- const retroBundle = await loadEvidence(directory, `retro-${phase}`);
40908
+ const retroBundle = await loadEvidence(dir, `retro-${phase}`);
40886
40909
  let retroFound = false;
40887
40910
  if (retroBundle !== null) {
40888
40911
  retroFound = retroBundle.entries?.some((entry) => isValidRetroEntry(entry, phase)) ?? false;
40889
40912
  }
40890
40913
  if (!retroFound) {
40891
- const allTaskIds = await listEvidenceTaskIds(directory);
40914
+ const allTaskIds = await listEvidenceTaskIds(dir);
40892
40915
  const retroTaskIds = allTaskIds.filter((id) => id.startsWith("retro-"));
40893
40916
  for (const taskId of retroTaskIds) {
40894
- const bundle = await loadEvidence(directory, taskId);
40917
+ const bundle = await loadEvidence(dir, taskId);
40895
40918
  if (bundle === null)
40896
40919
  continue;
40897
40920
  retroFound = bundle.entries?.some((entry) => isValidRetroEntry(entry, phase)) ?? false;
@@ -40945,7 +40968,7 @@ async function executePhaseComplete(args2) {
40945
40968
  summary: safeSummary ?? null
40946
40969
  };
40947
40970
  try {
40948
- const eventsPath = validateSwarmPath(directory, "events.jsonl");
40971
+ const eventsPath = validateSwarmPath(dir, "events.jsonl");
40949
40972
  fs17.appendFileSync(eventsPath, `${JSON.stringify(event)}
40950
40973
  `, "utf-8");
40951
40974
  } catch (writeError) {
@@ -3,6 +3,22 @@
3
3
  * Core implementation - gathers data, enforces policy, writes event, resets state.
4
4
  */
5
5
  import { type ToolDefinition } from '@opencode-ai/plugin/tool';
6
+ /**
7
+ * Arguments for the phase_complete tool
8
+ */
9
+ export interface PhaseCompleteArgs {
10
+ /** The phase number being completed */
11
+ phase: number;
12
+ /** Optional summary of the phase */
13
+ summary?: string;
14
+ /** Session ID to track state (optional, defaults to current session context) */
15
+ sessionID?: string;
16
+ }
17
+ /**
18
+ * Execute the phase_complete tool
19
+ * Gathers data, enforces policy, writes event, resets state
20
+ */
21
+ export declare function executePhaseComplete(args: PhaseCompleteArgs, workingDirectory?: string): Promise<string>;
6
22
  /**
7
23
  * Tool definition for phase_complete
8
24
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.13.3",
3
+ "version": "6.14.0",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -34,9 +34,8 @@
34
34
  "LICENSE"
35
35
  ],
36
36
  "scripts": {
37
- "clean": "rm -rf dist",
38
- "copy-grammars": "bun run scripts/copy-grammars.ts",
39
- "build": "rm -rf dist && bun run copy-grammars && bun build src/index.ts --outdir dist --target bun --format esm && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm && bun run scripts/copy-grammars.ts --to-dist && tsc --emitDeclarationOnly",
37
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
38
+ "build": "bun run clean && bun run scripts/copy-grammars.ts && bun build src/index.ts --outdir dist --target bun --format esm && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm && bun run scripts/copy-grammars.ts --to-dist && tsc --emitDeclarationOnly",
40
39
  "typecheck": "tsc --noEmit",
41
40
  "test": "bun test",
42
41
  "lint": "biome lint .",