opencode-swarm 7.3.4 → 7.3.6

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
@@ -788,6 +788,17 @@ Prefixed agents (e.g., `paid_coder`, `mega_reviewer`, `local_architect`) inherit
788
788
 
789
789
  In this example, `paid_coder` gets its own explicit rule, while other prefixed coders (e.g., `mega_coder`) fall back to `coder`.
790
790
 
791
+ #### Selecting the primary agent in multi-swarm configs (`default_agent`)
792
+
793
+ The top-level `default_agent` field controls which generated agents OpenCode treats as primary. **It is optional.** Behavior:
794
+
795
+ - **Omitted** — every architect-role agent is primary. In a multi-swarm config that means each swarm exposes its own architect (`local_architect`, `mega_architect`, `paid_architect`, `modelrelay_architect`, …) as a selectable session default. This is the v7.0.0-compatible behavior and the recommended setup.
796
+ - **Base role** (e.g. `"coder"`) — every generated agent whose canonical base role matches becomes primary (`local_coder`, `mega_coder`, …).
797
+ - **Exact generated name** (e.g. `"local_architect"`) — only that agent is primary.
798
+ - **Unknown / invalid value** — a one-time warning is logged and the resolver falls back to architect-role primaries (or the first generated agent if architects are disabled). The plugin never produces zero primaries when at least one agent exists.
799
+
800
+ See [`docs/configuration.md`](docs/configuration.md) for the full table.
801
+
791
802
  ### Runtime Enforcement
792
803
 
793
804
  Architect direct writes are enforced at runtime via `toolBefore` hook. This tracks writes to source code paths outside `.swarm/` and protects `.swarm/plan.md` and `.swarm/plan.json` from direct modification.
@@ -34,6 +34,34 @@ export declare function getSwarmAgents(): Record<string, {
34
34
  * Create all agent definitions with configuration applied
35
35
  */
36
36
  export declare function createAgents(config?: PluginConfig): AgentDefinition[];
37
+ /**
38
+ * Resolve the set of generated agent names that should be marked as primary
39
+ * for OpenCode's session-default-agent resolution.
40
+ *
41
+ * Resolution rules (see schema.ts default_agent comment for full semantics):
42
+ * - default_agent omitted ⇒ every architect-role agent is primary
43
+ * (canonical base role === "architect"). This restores v7.0.0 behavior in
44
+ * multi-swarm configs where there is no unprefixed `architect` agent.
45
+ * - default_agent exactly matches a generated agent name ⇒ only that agent.
46
+ * Exact match wins over base-role match — `local_architect` resolves to
47
+ * just `local_architect`, never the entire architect role.
48
+ * - default_agent is a base role in ALL_AGENT_NAMES ⇒ every generated agent
49
+ * whose canonical base role matches that role.
50
+ * - default_agent is invalid (matches nothing) ⇒ fall back to architect-role
51
+ * primaries; if no architect roles exist (architects disabled), fall back
52
+ * to the first generated agent. Always warns. Never returns empty when
53
+ * `agentNames` is non-empty.
54
+ *
55
+ * Important matching detail: a value like "not_an_architect" is NOT treated
56
+ * as a base-role request even though stripKnownSwarmPrefix() returns
57
+ * "architect" for it. Base-role matching only fires when the user-supplied
58
+ * value is itself one of ALL_AGENT_NAMES.
59
+ */
60
+ export declare function resolvePrimaryAgentNames(agentNames: string[], defaultAgent?: string): {
61
+ primaryNames: Set<string>;
62
+ reason: 'implicit-architects' | 'exact' | 'base-role' | 'fallback-architects' | 'fallback-first';
63
+ warning?: string;
64
+ };
37
65
  /**
38
66
  * Get agent configurations formatted for the OpenCode SDK.
39
67
  */
package/dist/cli/index.js CHANGED
@@ -34,7 +34,7 @@ var package_default;
34
34
  var init_package = __esm(() => {
35
35
  package_default = {
36
36
  name: "opencode-swarm",
37
- version: "7.3.4",
37
+ version: "7.3.6",
38
38
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
39
39
  main: "dist/index.js",
40
40
  types: "dist/index.d.ts",
@@ -16915,7 +16915,12 @@ var init_schema = __esm(() => {
16915
16915
  });
16916
16916
  PluginConfigSchema = exports_external.object({
16917
16917
  agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
16918
- default_agent: exports_external.enum(ALL_AGENT_NAMES).default("architect").optional(),
16918
+ default_agent: exports_external.string().optional().transform((v) => {
16919
+ if (v === undefined)
16920
+ return;
16921
+ const trimmed = v.trim();
16922
+ return trimmed === "" ? undefined : trimmed;
16923
+ }),
16919
16924
  swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
16920
16925
  max_iterations: exports_external.number().min(1).max(10).default(5),
16921
16926
  pipeline: PipelineConfigSchema.optional(),
@@ -643,26 +643,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
643
643
  disabled: z.ZodOptional<z.ZodBoolean>;
644
644
  fallback_models: z.ZodOptional<z.ZodArray<z.ZodString>>;
645
645
  }, z.core.$strip>>>;
646
- default_agent: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
647
- architect: "architect";
648
- sme: "sme";
649
- docs: "docs";
650
- designer: "designer";
651
- critic_sounding_board: "critic_sounding_board";
652
- critic_drift_verifier: "critic_drift_verifier";
653
- critic_hallucination_verifier: "critic_hallucination_verifier";
654
- curator_init: "curator_init";
655
- curator_phase: "curator_phase";
656
- council_generalist: "council_generalist";
657
- council_skeptic: "council_skeptic";
658
- council_domain_expert: "council_domain_expert";
659
- reviewer: "reviewer";
660
- critic: "critic";
661
- critic_oversight: "critic_oversight";
662
- explorer: "explorer";
663
- coder: "coder";
664
- test_engineer: "test_engineer";
665
- }>>>;
646
+ default_agent: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
666
647
  swarms: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
667
648
  name: z.ZodOptional<z.ZodString>;
668
649
  agents: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
package/dist/index.js CHANGED
@@ -33,7 +33,7 @@ var package_default;
33
33
  var init_package = __esm(() => {
34
34
  package_default = {
35
35
  name: "opencode-swarm",
36
- version: "7.3.4",
36
+ version: "7.3.6",
37
37
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
38
38
  main: "dist/index.js",
39
39
  types: "dist/index.d.ts",
@@ -15352,7 +15352,12 @@ var init_schema = __esm(() => {
15352
15352
  });
15353
15353
  PluginConfigSchema = exports_external.object({
15354
15354
  agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
15355
- default_agent: exports_external.enum(ALL_AGENT_NAMES).default("architect").optional(),
15355
+ default_agent: exports_external.string().optional().transform((v) => {
15356
+ if (v === undefined)
15357
+ return;
15358
+ const trimmed = v.trim();
15359
+ return trimmed === "" ? undefined : trimmed;
15360
+ }),
15356
15361
  swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
15357
15362
  max_iterations: exports_external.number().min(1).max(10).default(5),
15358
15363
  pipeline: PipelineConfigSchema.optional(),
@@ -59986,6 +59991,52 @@ function createAgents(config3) {
59986
59991
  }
59987
59992
  return allAgents;
59988
59993
  }
59994
+ function resolvePrimaryAgentNames(agentNames, defaultAgent) {
59995
+ const collectArchitectRole = () => agentNames.filter((n) => stripKnownSwarmPrefix(n) === "architect");
59996
+ const trimmed = typeof defaultAgent === "string" ? defaultAgent.trim() : undefined;
59997
+ const value = trimmed === "" ? undefined : trimmed;
59998
+ if (agentNames.length === 0) {
59999
+ return { primaryNames: new Set, reason: "implicit-architects" };
60000
+ }
60001
+ if (value === undefined) {
60002
+ const architects2 = collectArchitectRole();
60003
+ if (architects2.length > 0) {
60004
+ return {
60005
+ primaryNames: new Set(architects2),
60006
+ reason: "implicit-architects"
60007
+ };
60008
+ }
60009
+ const first2 = agentNames[0];
60010
+ return {
60011
+ primaryNames: new Set([first2]),
60012
+ reason: "fallback-first",
60013
+ warning: `[swarm] No architect-role agents are registered and default_agent is unset; falling back to '${first2}' as primary. Re-enable an architect agent or set default_agent to silence this warning.`
60014
+ };
60015
+ }
60016
+ if (ALL_AGENT_NAMES.includes(value)) {
60017
+ const matching = agentNames.filter((n) => stripKnownSwarmPrefix(n) === value);
60018
+ if (matching.length > 0) {
60019
+ return { primaryNames: new Set(matching), reason: "base-role" };
60020
+ }
60021
+ }
60022
+ if (agentNames.includes(value)) {
60023
+ return { primaryNames: new Set([value]), reason: "exact" };
60024
+ }
60025
+ const architects = collectArchitectRole();
60026
+ if (architects.length > 0) {
60027
+ return {
60028
+ primaryNames: new Set(architects),
60029
+ reason: "fallback-architects",
60030
+ warning: `[swarm] default_agent '${value}' did not match any registered agent; falling back to architect-role primaries: ${architects.join(", ")}.`
60031
+ };
60032
+ }
60033
+ const first = agentNames[0];
60034
+ return {
60035
+ primaryNames: new Set([first]),
60036
+ reason: "fallback-first",
60037
+ warning: `[swarm] default_agent '${value}' did not match any registered agent and no architect-role agents are registered; falling back to '${first}' as primary.`
60038
+ };
60039
+ }
59989
60040
  function getAgentConfigs(config3, directory, sessionId) {
59990
60041
  const agents = createAgents(config3);
59991
60042
  const toolFilterEnabled = config3?.tool_filter?.enabled ?? true;
@@ -59993,21 +60044,29 @@ function getAgentConfigs(config3, directory, sessionId) {
59993
60044
  const quiet = config3?.quiet ?? true;
59994
60045
  const warnedMissingWhitelist = new Set;
59995
60046
  const agentToolSnapshot = {};
60047
+ const resolution = resolvePrimaryAgentNames(agents.map((a) => a.name), config3?.default_agent);
60048
+ if (resolution.warning) {
60049
+ if (!quiet) {
60050
+ console.warn(resolution.warning);
60051
+ } else {
60052
+ addDeferredWarning(resolution.warning);
60053
+ }
60054
+ }
60055
+ if (agents.length > 0 && resolution.primaryNames.size === 0) {
60056
+ const generated = agents.map((a) => a.name).join(", ");
60057
+ const diagnostic = `[swarm] DIAGNOSTIC: ${agents.length} generated agents but zero primaries. Likely cause: a regression in resolvePrimaryAgentNames. Generated: ${generated}.`;
60058
+ if (!quiet) {
60059
+ console.warn(diagnostic);
60060
+ } else {
60061
+ addDeferredWarning(diagnostic);
60062
+ }
60063
+ }
59996
60064
  const result = Object.fromEntries(agents.map((agent) => {
59997
60065
  const sdkConfig = {
59998
60066
  ...agent.config,
59999
60067
  description: agent.description
60000
60068
  };
60001
- let defaultAgent = config3?.default_agent ?? "architect";
60002
- if (defaultAgent !== "architect" && !ALL_AGENT_NAMES.includes(defaultAgent)) {
60003
- if (!quiet) {
60004
- console.warn(`[swarm] Invalid default_agent '${defaultAgent}' — falling back to 'architect'. Valid values: ${ALL_AGENT_NAMES.join(", ")}`);
60005
- } else {
60006
- addDeferredWarning(`[swarm] Invalid default_agent '${defaultAgent}' — falling back to 'architect'. Valid values: ${ALL_AGENT_NAMES.join(", ")}`);
60007
- }
60008
- defaultAgent = "architect";
60009
- }
60010
- const isPrimaryAgent = agent.name === defaultAgent;
60069
+ const isPrimaryAgent = resolution.primaryNames.has(agent.name);
60011
60070
  if (isPrimaryAgent) {
60012
60071
  sdkConfig.mode = "primary";
60013
60072
  sdkConfig.permission = { task: "allow" };
@@ -88288,6 +88347,10 @@ init_state();
88288
88347
  function slugify2(str) {
88289
88348
  return str.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_");
88290
88349
  }
88350
+ var GENERATE_MUTANTS_TIMEOUT_MS = 90000;
88351
+ var _internals = {
88352
+ timeoutMs: GENERATE_MUTANTS_TIMEOUT_MS
88353
+ };
88291
88354
  function extractJsonArray(text) {
88292
88355
  const trimmed = text.trim();
88293
88356
  const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
@@ -88319,23 +88382,24 @@ async function generateMutants(files, ctx) {
88319
88382
  }
88320
88383
  };
88321
88384
  try {
88322
- const createResult = await client.session.create({
88323
- query: { directory }
88324
- });
88325
- if (!createResult.data) {
88326
- console.warn(`[generateMutants] Failed to create session: ${JSON.stringify(createResult.error)}; returning empty patch set`);
88327
- return [];
88328
- }
88329
- ephemeralSessionId = createResult.data.id;
88330
- const mutationTypes = [
88331
- "off-by-one",
88332
- "null-substitution",
88333
- "operator-swap",
88334
- "guard-removal",
88335
- "branch-swap",
88336
- "side-effect-deletion"
88337
- ].join(", ");
88338
- const promptText = `Generate mutation testing patches for the following files: ${files.join(", ")}
88385
+ const patches = await withTimeout((async () => {
88386
+ const createResult = await client.session.create({
88387
+ query: { directory }
88388
+ });
88389
+ if (!createResult.data) {
88390
+ console.warn(`[generateMutants] Failed to create session: ${JSON.stringify(createResult.error)}; returning empty patch set`);
88391
+ return [];
88392
+ }
88393
+ ephemeralSessionId = createResult.data.id;
88394
+ const mutationTypes = [
88395
+ "off-by-one",
88396
+ "null-substitution",
88397
+ "operator-swap",
88398
+ "guard-removal",
88399
+ "branch-swap",
88400
+ "side-effect-deletion"
88401
+ ].join(", ");
88402
+ const promptText = `Generate mutation testing patches for the following files: ${files.join(", ")}
88339
88403
 
88340
88404
  Return a JSON array where each element has:
88341
88405
  { id, filePath, functionName, mutationType, patch, lineNumber }
@@ -88346,53 +88410,55 @@ Return a JSON array where each element has:
88346
88410
  - Generate 3-5 mutations per function
88347
88411
 
88348
88412
  Return ONLY a valid JSON array. No markdown, no code fences, no explanation. Start your response with [ and end with ].`;
88349
- const promptResult = await client.session.prompt({
88350
- path: { id: ephemeralSessionId },
88351
- body: {
88352
- agent: undefined,
88353
- tools: { write: false, edit: false, patch: false },
88354
- parts: [{ type: "text", text: promptText }]
88413
+ const promptResult = await client.session.prompt({
88414
+ path: { id: ephemeralSessionId },
88415
+ body: {
88416
+ agent: undefined,
88417
+ tools: { write: false, edit: false, patch: false },
88418
+ parts: [{ type: "text", text: promptText }]
88419
+ }
88420
+ });
88421
+ if (!promptResult.data) {
88422
+ console.warn(`[generateMutants] LLM prompt failed: ${JSON.stringify(promptResult.error)}; returning empty patch set`);
88423
+ return [];
88355
88424
  }
88356
- });
88357
- if (!promptResult.data) {
88358
- console.warn(`[generateMutants] LLM prompt failed: ${JSON.stringify(promptResult.error)}; returning empty patch set`);
88359
- return [];
88360
- }
88361
- const textParts = promptResult.data.parts.filter((p) => p.type === "text");
88362
- const rawText = textParts.map((p) => p.text).join(`
88425
+ const textParts = promptResult.data.parts.filter((p) => p.type === "text");
88426
+ const rawText = textParts.map((p) => p.text).join(`
88363
88427
  `);
88364
- let parsed;
88365
- try {
88366
- parsed = JSON.parse(extractJsonArray(rawText));
88367
- } catch (error93) {
88368
- const msg = error93 instanceof Error ? error93.message : String(error93);
88369
- const hint = msg.includes("EOF") || msg.includes("Unexpected end") ? " (response appears truncated — LLM may have hit an output token limit)" : "";
88370
- console.warn(`[generateMutants] Failed to parse LLM response as MutationPatch[]: ${msg}${hint}; returning empty patch set`);
88371
- return [];
88372
- }
88373
- if (!Array.isArray(parsed) || parsed.length === 0) {
88374
- return [];
88375
- }
88376
- const patches = [];
88377
- for (const item of parsed) {
88378
- if (typeof item !== "object" || item === null || typeof item.filePath !== "string" || typeof item.functionName !== "string" || typeof item.mutationType !== "string" || typeof item.patch !== "string") {
88379
- continue;
88428
+ let parsed;
88429
+ try {
88430
+ parsed = JSON.parse(extractJsonArray(rawText));
88431
+ } catch (error93) {
88432
+ const msg = error93 instanceof Error ? error93.message : String(error93);
88433
+ const hint = msg.includes("EOF") || msg.includes("Unexpected end") ? " (response appears truncated — LLM may have hit an output token limit)" : "";
88434
+ console.warn(`[generateMutants] Failed to parse LLM response as MutationPatch[]: ${msg}${hint}; returning empty patch set`);
88435
+ return [];
88380
88436
  }
88381
- const mutationType = item.mutationType;
88382
- const fileSlug = slugify2(item.filePath);
88383
- const fnSlug = slugify2(item.functionName);
88384
- const typeSlug = slugify2(mutationType);
88385
- const idStr = typeof item.id === "string" ? item.id : "";
88386
- const id = idStr.startsWith("mut-") ? idStr : `mut-${fileSlug}-${fnSlug}-${typeSlug}-${String(patches.length + 1).padStart(3, "0")}`;
88387
- patches.push({
88388
- id,
88389
- filePath: item.filePath,
88390
- functionName: item.functionName,
88391
- mutationType,
88392
- patch: item.patch,
88393
- lineNumber: typeof item.lineNumber === "number" ? item.lineNumber : undefined
88394
- });
88395
- }
88437
+ if (!Array.isArray(parsed) || parsed.length === 0) {
88438
+ return [];
88439
+ }
88440
+ const patches2 = [];
88441
+ for (const item of parsed) {
88442
+ if (typeof item !== "object" || item === null || typeof item.filePath !== "string" || typeof item.functionName !== "string" || typeof item.mutationType !== "string" || typeof item.patch !== "string") {
88443
+ continue;
88444
+ }
88445
+ const mutationType = item.mutationType;
88446
+ const fileSlug = slugify2(item.filePath);
88447
+ const fnSlug = slugify2(item.functionName);
88448
+ const typeSlug = slugify2(mutationType);
88449
+ const idStr = typeof item.id === "string" ? item.id : "";
88450
+ const id = idStr.startsWith("mut-") ? idStr : `mut-${fileSlug}-${fnSlug}-${typeSlug}-${String(patches2.length + 1).padStart(3, "0")}`;
88451
+ patches2.push({
88452
+ id,
88453
+ filePath: item.filePath,
88454
+ functionName: item.functionName,
88455
+ mutationType,
88456
+ patch: item.patch,
88457
+ lineNumber: typeof item.lineNumber === "number" ? item.lineNumber : undefined
88458
+ });
88459
+ }
88460
+ return patches2;
88461
+ })(), _internals.timeoutMs, new Error("generateMutants: LLM call timed out"));
88396
88462
  return patches;
88397
88463
  } catch (error93) {
88398
88464
  console.warn(`[generateMutants] LLM call failed: ${error93 instanceof Error ? error93.message : String(error93)}; returning empty patch set`);
@@ -88707,6 +88773,12 @@ async function batchCheckEquivalence(patches, llmJudge) {
88707
88773
  var MUTATION_TIMEOUT_MS = 30000;
88708
88774
  var TOTAL_BUDGET_MS = 300000;
88709
88775
  var GIT_APPLY_TIMEOUT_MS = 5000;
88776
+ function buildGitApplyArgs(patchFile) {
88777
+ return ["apply", "--ignore-whitespace", "--", patchFile];
88778
+ }
88779
+ function buildGitRevertArgs(patchFile) {
88780
+ return ["apply", "-R", "--ignore-whitespace", "--", patchFile];
88781
+ }
88710
88782
  async function executeMutation(patch, testCommand, _testFiles, workingDir) {
88711
88783
  const startTime = Date.now();
88712
88784
  let outcome = "survived";
@@ -88733,7 +88805,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
88733
88805
  };
88734
88806
  }
88735
88807
  try {
88736
- const applyResult = spawnSync3("git", ["apply", "--", patchFile], {
88808
+ const applyResult = spawnSync3("git", buildGitApplyArgs(patchFile), {
88737
88809
  cwd: workingDir,
88738
88810
  timeout: GIT_APPLY_TIMEOUT_MS,
88739
88811
  stdio: "pipe"
@@ -88795,7 +88867,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
88795
88867
  } finally {
88796
88868
  if (patchFile) {
88797
88869
  try {
88798
- const revertResult = spawnSync3("git", ["apply", "-R", "--", patchFile], {
88870
+ const revertResult = spawnSync3("git", buildGitRevertArgs(patchFile), {
88799
88871
  cwd: workingDir,
88800
88872
  timeout: GIT_APPLY_TIMEOUT_MS,
88801
88873
  stdio: "pipe"
@@ -89622,7 +89694,7 @@ import * as path105 from "node:path";
89622
89694
  init_bun_compat();
89623
89695
  import * as fs84 from "node:fs";
89624
89696
  import * as path104 from "node:path";
89625
- var _internals = { bunSpawn };
89697
+ var _internals2 = { bunSpawn };
89626
89698
  var _swarmGitExcludedChecked = false;
89627
89699
  function fileCoversSwarm(content) {
89628
89700
  for (const rawLine of content.split(`
@@ -89649,7 +89721,7 @@ async function ensureSwarmGitExcluded(directory, options = {}) {
89649
89721
  _swarmGitExcludedChecked = true;
89650
89722
  const { quiet = false } = options;
89651
89723
  try {
89652
- const gitRootProc = _internals.bunSpawn(["git", "-C", directory, "rev-parse", "--show-toplevel"], GIT_SPAWN_OPTIONS);
89724
+ const gitRootProc = _internals2.bunSpawn(["git", "-C", directory, "rev-parse", "--show-toplevel"], GIT_SPAWN_OPTIONS);
89653
89725
  let gitRootExitCode;
89654
89726
  let gitRootOutput;
89655
89727
  try {
@@ -89667,7 +89739,7 @@ async function ensureSwarmGitExcluded(directory, options = {}) {
89667
89739
  const gitRoot = gitRootOutput.trim();
89668
89740
  if (!gitRoot)
89669
89741
  return;
89670
- const excludePathProc = _internals.bunSpawn(["git", "-C", directory, "rev-parse", "--git-path", "info/exclude"], GIT_SPAWN_OPTIONS);
89742
+ const excludePathProc = _internals2.bunSpawn(["git", "-C", directory, "rev-parse", "--git-path", "info/exclude"], GIT_SPAWN_OPTIONS);
89671
89743
  let excludePathExitCode;
89672
89744
  let excludePathRaw;
89673
89745
  try {
@@ -89686,7 +89758,7 @@ async function ensureSwarmGitExcluded(directory, options = {}) {
89686
89758
  if (!excludeRelPath)
89687
89759
  return;
89688
89760
  const excludePath = path104.isAbsolute(excludeRelPath) ? excludeRelPath : path104.join(directory, excludeRelPath);
89689
- const checkIgnoreProc = _internals.bunSpawn(["git", "-C", directory, "check-ignore", "-q", ".swarm/.gitkeep"], GIT_SPAWN_OPTIONS);
89761
+ const checkIgnoreProc = _internals2.bunSpawn(["git", "-C", directory, "check-ignore", "-q", ".swarm/.gitkeep"], GIT_SPAWN_OPTIONS);
89690
89762
  let checkIgnoreExitCode;
89691
89763
  try {
89692
89764
  checkIgnoreExitCode = await checkIgnoreProc.exited;
@@ -89713,7 +89785,7 @@ async function ensureSwarmGitExcluded(directory, options = {}) {
89713
89785
  }
89714
89786
  } catch {}
89715
89787
  }
89716
- const trackedProc = _internals.bunSpawn(["git", "-C", directory, "ls-files", "--", ".swarm"], GIT_SPAWN_OPTIONS);
89788
+ const trackedProc = _internals2.bunSpawn(["git", "-C", directory, "ls-files", "--", ".swarm"], GIT_SPAWN_OPTIONS);
89717
89789
  let trackedExitCode;
89718
89790
  let trackedOutput;
89719
89791
  try {
@@ -89738,7 +89810,7 @@ async function ensureSwarmGitExcluded(directory, options = {}) {
89738
89810
  }
89739
89811
 
89740
89812
  // src/hooks/diff-scope.ts
89741
- var _internals2 = { bunSpawn };
89813
+ var _internals3 = { bunSpawn };
89742
89814
  function getDeclaredScope(taskId, directory) {
89743
89815
  try {
89744
89816
  const planPath = path105.join(directory, ".swarm", "plan.json");
@@ -89773,7 +89845,7 @@ var GIT_DIFF_SPAWN_OPTIONS = {
89773
89845
  };
89774
89846
  async function getChangedFiles(directory) {
89775
89847
  try {
89776
- const proc = _internals2.bunSpawn(["git", "diff", "--name-only", "HEAD~1"], {
89848
+ const proc = _internals3.bunSpawn(["git", "diff", "--name-only", "HEAD~1"], {
89777
89849
  cwd: directory,
89778
89850
  ...GIT_DIFF_SPAWN_OPTIONS
89779
89851
  });
@@ -89790,7 +89862,7 @@ async function getChangedFiles(directory) {
89790
89862
  return stdout.trim().split(`
89791
89863
  `).map((f) => f.trim()).filter((f) => f.length > 0);
89792
89864
  }
89793
- const proc2 = _internals2.bunSpawn(["git", "diff", "--name-only", "HEAD"], {
89865
+ const proc2 = _internals3.bunSpawn(["git", "diff", "--name-only", "HEAD"], {
89794
89866
  cwd: directory,
89795
89867
  ...GIT_DIFF_SPAWN_OPTIONS
89796
89868
  });
@@ -42,6 +42,19 @@ export interface MutationReport {
42
42
  timestamp: string;
43
43
  }
44
44
  export declare const MAX_MUTATIONS_PER_FUNCTION = 10;
45
+ /**
46
+ * Build the argument array for `git apply` with cross-platform whitespace
47
+ * tolerance. `--ignore-whitespace` makes git ignore CRLF/LF differences in
48
+ * context lines, which is required on Windows where `core.autocrlf=true`
49
+ * causes checked-out files to use CRLF while LLM-generated patches use LF.
50
+ * The flag is a no-op on macOS and Linux (files already use LF).
51
+ */
52
+ export declare function buildGitApplyArgs(patchFile: string): string[];
53
+ /**
54
+ * Build the argument array for `git apply -R` (revert) with the same
55
+ * cross-platform whitespace tolerance as `buildGitApplyArgs`.
56
+ */
57
+ export declare function buildGitRevertArgs(patchFile: string): string[];
45
58
  export declare function executeMutation(patch: MutationPatch, testCommand: string[], _testFiles: string[], workingDir: string): Promise<MutationResult>;
46
59
  export declare function computeReport(results: MutationResult[], durationMs: number, budgetMs?: number): MutationReport;
47
60
  export declare function executeMutationSuite(patches: MutationPatch[], testCommand: string[], testFiles: string[], workingDir: string, budgetMs?: number, onProgress?: (completed: number, total: number, result: MutationResult) => void, sourceFiles?: Map<string, string>): Promise<MutationReport>;
@@ -6,6 +6,13 @@
6
6
  */
7
7
  import type { ToolContext } from '@opencode-ai/plugin';
8
8
  import type { MutationPatch } from './engine.js';
9
+ /**
10
+ * Dependency-injection seam. Tests may override `timeoutMs` to a short value
11
+ * to exercise the timeout path without waiting 90 seconds.
12
+ */
13
+ export declare const _internals: {
14
+ timeoutMs: number;
15
+ };
9
16
  /**
10
17
  * Generate mutation testing patches for the given source files using an LLM.
11
18
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.3.4",
3
+ "version": "7.3.6",
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",