opencode-swarm 6.20.0 → 6.20.2

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
@@ -852,7 +852,7 @@ This release adds runtime detection hooks to catch and warn about architect work
852
852
  - **Partial gate tracking**: Detects when QA gates are skipped
853
853
  - **Self-fix detection**: Warns when an agent fixes its own gate failure (should delegate to fresh agent)
854
854
  - **Batch detection**: Catches "implement X and add Y" batching in task requests
855
- - **Zero-delegation detection**: Warns when tasks complete without any coder delegation
855
+ - **Zero-delegation detection**: Warns when tasks complete without any coder delegation; supports parsing delegation envelopes from JSON or KEY: VALUE text format for validation.
856
856
 
857
857
  These hooks are advisory (warnings only) and help maintain workflow discipline during long sessions.
858
858
 
package/dist/cli/index.js CHANGED
@@ -14186,6 +14186,8 @@ function log(message, data) {
14186
14186
  }
14187
14187
  }
14188
14188
  function warn(message, data) {
14189
+ if (!DEBUG)
14190
+ return;
14189
14191
  const timestamp = new Date().toISOString();
14190
14192
  if (data !== undefined) {
14191
14193
  console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`, data);
@@ -32646,9 +32648,9 @@ function validateArgs(args) {
32646
32648
  return false;
32647
32649
  return true;
32648
32650
  }
32649
- function getLinterCommand(linter, mode) {
32651
+ function getLinterCommand(linter, mode, projectDir) {
32650
32652
  const isWindows = process.platform === "win32";
32651
- const binDir = path11.join(process.cwd(), "node_modules", ".bin");
32653
+ const binDir = path11.join(projectDir, "node_modules", ".bin");
32652
32654
  const biomeBin = isWindows ? path11.join(binDir, "biome.EXE") : path11.join(binDir, "biome");
32653
32655
  const eslintBin = isWindows ? path11.join(binDir, "eslint.cmd") : path11.join(binDir, "eslint");
32654
32656
  switch (linter) {
@@ -32825,8 +32827,8 @@ async function detectAvailableLinter() {
32825
32827
  } catch {}
32826
32828
  return null;
32827
32829
  }
32828
- async function runLint(linter, mode) {
32829
- const command = getLinterCommand(linter, mode);
32830
+ async function runLint(linter, mode, directory) {
32831
+ const command = getLinterCommand(linter, mode, directory);
32830
32832
  const commandStr = command.join(" ");
32831
32833
  if (commandStr.length > MAX_COMMAND_LENGTH) {
32832
32834
  return {
@@ -32840,7 +32842,8 @@ async function runLint(linter, mode) {
32840
32842
  try {
32841
32843
  const proc = Bun.spawn(command, {
32842
32844
  stdout: "pipe",
32843
- stderr: "pipe"
32845
+ stderr: "pipe",
32846
+ cwd: directory
32844
32847
  });
32845
32848
  const [stdout, stderr] = await Promise.all([
32846
32849
  new Response(proc.stdout).text(),
@@ -32954,11 +32957,19 @@ var lint = createSwarmTool({
32954
32957
  };
32955
32958
  return JSON.stringify(errorResult2, null, 2);
32956
32959
  }
32960
+ if (!directory || typeof directory !== "string" || directory.trim() === "") {
32961
+ const errorResult2 = {
32962
+ success: false,
32963
+ mode: "check",
32964
+ error: "project directory is required but was not provided"
32965
+ };
32966
+ return JSON.stringify(errorResult2, null, 2);
32967
+ }
32957
32968
  const { mode } = args;
32958
32969
  const cwd = directory;
32959
32970
  const linter = await detectAvailableLinter();
32960
32971
  if (linter) {
32961
- const result = await runLint(linter, mode);
32972
+ const result = await runLint(linter, mode, directory);
32962
32973
  return JSON.stringify(result, null, 2);
32963
32974
  }
32964
32975
  const additionalLinter = detectAdditionalLinter(cwd);
@@ -5,13 +5,22 @@
5
5
  * Uses experimental.chat.messages.transform to provide non-blocking guidance.
6
6
  */
7
7
  import type { PluginConfig } from '../config';
8
- import type { DelegationEnvelope } from '../types/delegation.js';
8
+ import type { DelegationEnvelope, EnvelopeValidationResult } from '../types/delegation.js';
9
9
  /**
10
10
  * Parses a string to extract a DelegationEnvelope.
11
11
  * Returns null if no valid envelope is found.
12
12
  * Never throws - all errors are caught and result in null.
13
13
  */
14
14
  export declare function parseDelegationEnvelope(content: string): DelegationEnvelope | null;
15
+ interface ValidationContext {
16
+ planTasks: string[];
17
+ validAgents: string[];
18
+ }
19
+ /**
20
+ * Validates a DelegationEnvelope against the current plan and agent list.
21
+ * Returns { valid: true } on success, or { valid: false; reason: string } on failure.
22
+ */
23
+ export declare function validateDelegationEnvelope(envelope: unknown, context: ValidationContext): EnvelopeValidationResult;
15
24
  interface MessageInfo {
16
25
  role: string;
17
26
  agent?: string;
@@ -9,11 +9,11 @@
9
9
  import { type GuardrailsConfig } from '../config/schema';
10
10
  /**
11
11
  * Creates guardrails hooks for circuit breaker protection
12
- * @param directory Working directory (from plugin init context)
13
- * @param config Guardrails configuration
12
+ * @param directoryOrConfig Working directory (from plugin init context) OR legacy config object for backward compatibility
13
+ * @param config Guardrails configuration (optional if first arg is config)
14
14
  * @returns Tool before/after hooks and messages transform hook
15
15
  */
16
- export declare function createGuardrailsHooks(directory: string, config: GuardrailsConfig): {
16
+ export declare function createGuardrailsHooks(directoryOrConfig?: string | GuardrailsConfig, config?: GuardrailsConfig): {
17
17
  toolBefore: (input: {
18
18
  tool: string;
19
19
  sessionID: string;
package/dist/index.js CHANGED
@@ -14248,6 +14248,8 @@ function log(message, data) {
14248
14248
  }
14249
14249
  }
14250
14250
  function warn(message, data) {
14251
+ if (!DEBUG)
14252
+ return;
14251
14253
  const timestamp = new Date().toISOString();
14252
14254
  if (data !== undefined) {
14253
14255
  console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`, data);
@@ -32236,9 +32238,9 @@ function validateArgs(args2) {
32236
32238
  return false;
32237
32239
  return true;
32238
32240
  }
32239
- function getLinterCommand(linter, mode) {
32241
+ function getLinterCommand(linter, mode, projectDir) {
32240
32242
  const isWindows = process.platform === "win32";
32241
- const binDir = path18.join(process.cwd(), "node_modules", ".bin");
32243
+ const binDir = path18.join(projectDir, "node_modules", ".bin");
32242
32244
  const biomeBin = isWindows ? path18.join(binDir, "biome.EXE") : path18.join(binDir, "biome");
32243
32245
  const eslintBin = isWindows ? path18.join(binDir, "eslint.cmd") : path18.join(binDir, "eslint");
32244
32246
  switch (linter) {
@@ -32415,8 +32417,8 @@ async function detectAvailableLinter() {
32415
32417
  } catch {}
32416
32418
  return null;
32417
32419
  }
32418
- async function runLint(linter, mode) {
32419
- const command = getLinterCommand(linter, mode);
32420
+ async function runLint(linter, mode, directory) {
32421
+ const command = getLinterCommand(linter, mode, directory);
32420
32422
  const commandStr = command.join(" ");
32421
32423
  if (commandStr.length > MAX_COMMAND_LENGTH) {
32422
32424
  return {
@@ -32430,7 +32432,8 @@ async function runLint(linter, mode) {
32430
32432
  try {
32431
32433
  const proc = Bun.spawn(command, {
32432
32434
  stdout: "pipe",
32433
- stderr: "pipe"
32435
+ stderr: "pipe",
32436
+ cwd: directory
32434
32437
  });
32435
32438
  const [stdout, stderr] = await Promise.all([
32436
32439
  new Response(proc.stdout).text(),
@@ -32550,11 +32553,19 @@ var init_lint = __esm(() => {
32550
32553
  };
32551
32554
  return JSON.stringify(errorResult2, null, 2);
32552
32555
  }
32556
+ if (!directory || typeof directory !== "string" || directory.trim() === "") {
32557
+ const errorResult2 = {
32558
+ success: false,
32559
+ mode: "check",
32560
+ error: "project directory is required but was not provided"
32561
+ };
32562
+ return JSON.stringify(errorResult2, null, 2);
32563
+ }
32553
32564
  const { mode } = args2;
32554
32565
  const cwd = directory;
32555
32566
  const linter = await detectAvailableLinter();
32556
32567
  if (linter) {
32557
- const result = await runLint(linter, mode);
32568
+ const result = await runLint(linter, mode, directory);
32558
32569
  return JSON.stringify(result, null, 2);
32559
32570
  }
32560
32571
  const additionalLinter = detectAdditionalLinter(cwd);
@@ -34357,10 +34368,10 @@ async function runVersionCheck(dir, _timeoutMs) {
34357
34368
  };
34358
34369
  }
34359
34370
  }
34360
- async function runLintCheck(_dir, linter, timeoutMs) {
34371
+ async function runLintCheck(dir, linter, timeoutMs) {
34361
34372
  const startTime = Date.now();
34362
34373
  try {
34363
- const lintPromise = runLint(linter, "check");
34374
+ const lintPromise = runLint(linter, "check", dir);
34364
34375
  const timeoutPromise = new Promise((_, reject) => {
34365
34376
  setTimeout(() => {
34366
34377
  reject(new Error(`Lint check timed out after ${timeoutMs}ms`));
@@ -39092,6 +39103,8 @@ If you are about to edit a source file: STOP. You are violating protocol.
39092
39103
  writeCount > 0 on source files from the Architect is equivalent to GATE_DELEGATION_BYPASS.
39093
39104
 
39094
39105
  PLAN STATE PROTECTION
39106
+ WHY: plan.md is auto-regenerated by PlanSyncWorker from plan.json. Any direct write to plan.md will be silently overwritten within seconds. If you see plan.md reverting after your edit, this is the cause \u2014 the worker detected a plan.json change and regenerated plan.md from it.
39107
+ The correct tools: save_plan to create or restructure a plan (writes plan.json \u2192 triggers regeneration); update_task_status() for task completion status; phase_complete() for phase-level transitions.
39095
39108
  .swarm/plan.md and .swarm/plan.json are READABLE but NOT DIRECTLY WRITABLE for state transitions.
39096
39109
  Task-level status changes (marking individual tasks as "completed") must use update_task_status().
39097
39110
  Phase-level completion (marking an entire phase as done) must use phase_complete().
@@ -44993,7 +45006,7 @@ async function handleKnowledgeListCommand(directory, _args) {
44993
45006
  "|------|----------|------------|---------------------|"
44994
45007
  ];
44995
45008
  for (const entry of entries) {
44996
- const truncatedLesson = entry.lesson.length > 60 ? entry.lesson.slice(0, 57) + "..." : entry.lesson;
45009
+ const truncatedLesson = entry.lesson.length > 60 ? `${entry.lesson.slice(0, 57)}...` : entry.lesson;
44997
45010
  const confidencePct = Math.round(entry.confidence * 100);
44998
45011
  lines.push(`| ${entry.id.slice(0, 8)}... | ${entry.category} | ${confidencePct}% | ${truncatedLesson} |`);
44999
45012
  }
@@ -45605,7 +45618,7 @@ async function handleRollbackCommand(directory, args2) {
45605
45618
  `);
45606
45619
  }
45607
45620
  const targetPhase = parseInt(phaseArg, 10);
45608
- if (isNaN(targetPhase) || targetPhase < 1) {
45621
+ if (Number.isNaN(targetPhase) || targetPhase < 1) {
45609
45622
  return "Error: Phase number must be a positive integer.";
45610
45623
  }
45611
45624
  const manifestPath = validateSwarmPath(directory, "checkpoints/manifest.json");
@@ -45650,7 +45663,7 @@ async function handleRollbackCommand(directory, args2) {
45650
45663
  timestamp: new Date().toISOString()
45651
45664
  };
45652
45665
  try {
45653
- fs12.appendFileSync(eventsPath, JSON.stringify(rollbackEvent) + `
45666
+ fs12.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
45654
45667
  `);
45655
45668
  } catch (error93) {
45656
45669
  console.error("Failed to write rollback event:", error93);
@@ -47493,14 +47506,24 @@ function getCurrentTaskId(sessionId) {
47493
47506
  const session = swarmState.agentSessions.get(sessionId);
47494
47507
  return session?.currentTaskId ?? `${sessionId}:unknown`;
47495
47508
  }
47496
- function createGuardrailsHooks(directory, config3) {
47497
- if (config3.enabled === false) {
47509
+ function createGuardrailsHooks(directoryOrConfig, config3) {
47510
+ let directory;
47511
+ let guardrailsConfig;
47512
+ if (directoryOrConfig && typeof directoryOrConfig === "object" && "enabled" in directoryOrConfig) {
47513
+ directory = process.cwd();
47514
+ guardrailsConfig = directoryOrConfig;
47515
+ } else {
47516
+ directory = directoryOrConfig ?? process.cwd();
47517
+ guardrailsConfig = config3;
47518
+ }
47519
+ if (guardrailsConfig?.enabled === false) {
47498
47520
  return {
47499
47521
  toolBefore: async () => {},
47500
47522
  toolAfter: async () => {},
47501
47523
  messagesTransform: async () => {}
47502
47524
  };
47503
47525
  }
47526
+ const cfg = guardrailsConfig;
47504
47527
  const inputArgsByCallID = new Map;
47505
47528
  return {
47506
47529
  toolBefore: async (input, output) => {
@@ -47508,6 +47531,13 @@ function createGuardrailsHooks(directory, config3) {
47508
47531
  if (currentSession?.delegationActive) {} else if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
47509
47532
  const args2 = output.args;
47510
47533
  const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
47534
+ if (typeof targetPath === "string" && targetPath.length > 0) {
47535
+ const resolvedTarget = path25.resolve(directory, targetPath).toLowerCase();
47536
+ const planMdPath = path25.resolve(directory, ".swarm", "plan.md").toLowerCase();
47537
+ if (resolvedTarget === planMdPath) {
47538
+ throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
47539
+ }
47540
+ }
47511
47541
  if (!targetPath && (input.tool === "apply_patch" || input.tool === "patch")) {
47512
47542
  const patchText = args2?.input ?? args2?.patch ?? (Array.isArray(args2?.cmd) ? args2.cmd[1] : undefined);
47513
47543
  if (typeof patchText === "string") {
@@ -47523,6 +47553,11 @@ function createGuardrailsHooks(directory, config3) {
47523
47553
  paths.add(p);
47524
47554
  }
47525
47555
  for (const p of paths) {
47556
+ const resolvedP = path25.resolve(directory, p);
47557
+ const planMdPath = path25.resolve(directory, ".swarm", "plan.md");
47558
+ if (resolvedP.toLowerCase() === planMdPath.toLowerCase()) {
47559
+ throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
47560
+ }
47526
47561
  if (isOutsideSwarmDir(p, directory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
47527
47562
  const session2 = swarmState.agentSessions.get(input.sessionID);
47528
47563
  if (session2) {
@@ -47581,7 +47616,7 @@ function createGuardrailsHooks(directory, config3) {
47581
47616
  if (resolvedName === ORCHESTRATOR_NAME) {
47582
47617
  return;
47583
47618
  }
47584
- const agentConfig = resolveGuardrailsConfig(config3, session.agentName);
47619
+ const agentConfig = resolveGuardrailsConfig(cfg, session.agentName);
47585
47620
  if (agentConfig.max_duration_minutes === 0 && agentConfig.max_tool_calls === 0) {
47586
47621
  return;
47587
47622
  }
@@ -51240,7 +51275,7 @@ function validateExtensions(extensions) {
51240
51275
  }
51241
51276
  return { valid: true, value: extensions, error: null };
51242
51277
  }
51243
- async function getGitChurn(days) {
51278
+ async function getGitChurn(days, directory) {
51244
51279
  const churnMap = new Map;
51245
51280
  const proc = Bun.spawn([
51246
51281
  "git",
@@ -51250,7 +51285,8 @@ async function getGitChurn(days) {
51250
51285
  "--pretty=format:"
51251
51286
  ], {
51252
51287
  stdout: "pipe",
51253
- stderr: "pipe"
51288
+ stderr: "pipe",
51289
+ cwd: directory
51254
51290
  });
51255
51291
  const stdout = await new Response(proc.stdout).text();
51256
51292
  await proc.exited;
@@ -51315,8 +51351,8 @@ function getComplexityForFile(filePath) {
51315
51351
  return null;
51316
51352
  }
51317
51353
  }
51318
- async function analyzeHotspots(days, topN, extensions) {
51319
- const churnMap = await getGitChurn(days);
51354
+ async function analyzeHotspots(days, topN, extensions, directory) {
51355
+ const churnMap = await getGitChurn(days, directory);
51320
51356
  const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
51321
51357
  const filteredChurn = new Map;
51322
51358
  for (const [file3, count] of churnMap) {
@@ -51326,7 +51362,7 @@ async function analyzeHotspots(days, topN, extensions) {
51326
51362
  }
51327
51363
  }
51328
51364
  const hotspots = [];
51329
- const cwd = process.cwd();
51365
+ const cwd = directory;
51330
51366
  let analyzedFiles = 0;
51331
51367
  for (const [file3, churnCount] of filteredChurn) {
51332
51368
  let fullPath = file3;
@@ -51378,7 +51414,22 @@ var complexity_hotspots = createSwarmTool({
51378
51414
  top_n: tool.schema.number().optional().describe("Number of top hotspots to return (default: 20, valid range: 1-100)"),
51379
51415
  extensions: tool.schema.string().optional().describe('Comma-separated extensions to include (default: "ts,tsx,js,jsx,py,rs,ps1")')
51380
51416
  },
51381
- async execute(args2, _directory) {
51417
+ async execute(args2, directory) {
51418
+ if (!directory || typeof directory !== "string" || directory.trim() === "") {
51419
+ const errorResult = {
51420
+ error: "project directory is required but was not provided",
51421
+ analyzedFiles: 0,
51422
+ period: "0 days",
51423
+ hotspots: [],
51424
+ summary: {
51425
+ fullGates: 0,
51426
+ securityReview: 0,
51427
+ enhancedReview: 0,
51428
+ standard: 0
51429
+ }
51430
+ };
51431
+ return JSON.stringify(errorResult, null, 2);
51432
+ }
51382
51433
  let daysInput;
51383
51434
  let topNInput;
51384
51435
  let extensionsInput;
@@ -51442,7 +51493,7 @@ var complexity_hotspots = createSwarmTool({
51442
51493
  const topN = topNValidation.value;
51443
51494
  const extensions = extensionsValidation.value.split(",").map((e) => e.trim());
51444
51495
  try {
51445
- const result = await analyzeHotspots(days, topN, extensions);
51496
+ const result = await analyzeHotspots(days, topN, extensions, directory);
51446
51497
  return JSON.stringify(result, null, 2);
51447
51498
  } catch (e) {
51448
51499
  const errorResult = {
@@ -51477,7 +51528,7 @@ var SAFE_REF_PATTERN = /^[a-zA-Z0-9._\-/~^@{}]+$/;
51477
51528
  var MAX_REF_LENGTH = 256;
51478
51529
  var MAX_PATH_LENGTH = 500;
51479
51530
  var SHELL_METACHARACTERS2 = /[;|&$`(){}<>!'"]/;
51480
- var CONTROL_CHAR_PATTERN2 = new RegExp("[\\u0000-\\u001F\\u007F]");
51531
+ var CONTROL_CHAR_PATTERN2 = /[\u0000-\u001F\u007F]/;
51481
51532
  function validateBase(base) {
51482
51533
  if (base.length > MAX_REF_LENGTH) {
51483
51534
  return `base ref exceeds maximum length of ${MAX_REF_LENGTH}`;
@@ -51515,8 +51566,17 @@ var diff = tool({
51515
51566
  base: tool.schema.string().optional().describe('Base ref to diff against (default: HEAD). Use "staged" for staged changes, "unstaged" for working tree changes.'),
51516
51567
  paths: tool.schema.array(tool.schema.string()).optional().describe("Optional file paths to restrict diff scope.")
51517
51568
  },
51518
- async execute(args2, _context) {
51569
+ async execute(args2, context) {
51519
51570
  try {
51571
+ if (!context.directory || typeof context.directory !== "string" || context.directory.trim() === "") {
51572
+ const errorResult = {
51573
+ error: "project directory is required but was not provided",
51574
+ files: [],
51575
+ contractChanges: [],
51576
+ hasContractChanges: false
51577
+ };
51578
+ return JSON.stringify(errorResult, null, 2);
51579
+ }
51520
51580
  const base = args2.base ?? "HEAD";
51521
51581
  const baseValidationError = validateBase(base);
51522
51582
  if (baseValidationError) {
@@ -51555,12 +51615,14 @@ var diff = tool({
51555
51615
  const numstatOutput = execFileSync("git", numstatArgs, {
51556
51616
  encoding: "utf-8",
51557
51617
  timeout: DIFF_TIMEOUT_MS,
51558
- maxBuffer: MAX_BUFFER_BYTES
51618
+ maxBuffer: MAX_BUFFER_BYTES,
51619
+ cwd: context.directory
51559
51620
  });
51560
51621
  const fullDiffOutput = execFileSync("git", fullDiffArgs, {
51561
51622
  encoding: "utf-8",
51562
51623
  timeout: DIFF_TIMEOUT_MS,
51563
- maxBuffer: MAX_BUFFER_BYTES
51624
+ maxBuffer: MAX_BUFFER_BYTES,
51625
+ cwd: context.directory
51564
51626
  });
51565
51627
  const files = [];
51566
51628
  const numstatLines = numstatOutput.split(`
@@ -51610,7 +51672,7 @@ var diff = tool({
51610
51672
  return JSON.stringify(result, null, 2);
51611
51673
  } catch (e) {
51612
51674
  const errorResult = {
51613
- error: e instanceof Error ? `git diff failed: ${e.constructor.name}` : "git diff failed: unknown error",
51675
+ error: e instanceof Error ? `git diff failed: ${e.message}` : "git diff failed: unknown error",
51614
51676
  files: [],
51615
51677
  contractChanges: [],
51616
51678
  hasContractChanges: false
@@ -52906,9 +52968,9 @@ function validateArgs3(args2) {
52906
52968
  }
52907
52969
  return true;
52908
52970
  }
52909
- function detectEcosystems() {
52971
+ function detectEcosystems(directory) {
52910
52972
  const ecosystems = [];
52911
- const cwd = process.cwd();
52973
+ const cwd = directory;
52912
52974
  if (fs23.existsSync(path34.join(cwd, "package.json"))) {
52913
52975
  ecosystems.push("npm");
52914
52976
  }
@@ -52935,13 +52997,13 @@ function detectEcosystems() {
52935
52997
  }
52936
52998
  return ecosystems;
52937
52999
  }
52938
- async function runNpmAudit() {
53000
+ async function runNpmAudit(directory) {
52939
53001
  const command = ["npm", "audit", "--json"];
52940
53002
  try {
52941
53003
  const proc = Bun.spawn(command, {
52942
53004
  stdout: "pipe",
52943
53005
  stderr: "pipe",
52944
- cwd: process.cwd()
53006
+ cwd: directory
52945
53007
  });
52946
53008
  const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
52947
53009
  const result = await Promise.race([
@@ -53058,13 +53120,13 @@ function mapNpmSeverity(severity) {
53058
53120
  return "info";
53059
53121
  }
53060
53122
  }
53061
- async function runPipAudit() {
53123
+ async function runPipAudit(directory) {
53062
53124
  const command = ["pip-audit", "--format=json"];
53063
53125
  try {
53064
53126
  const proc = Bun.spawn(command, {
53065
53127
  stdout: "pipe",
53066
53128
  stderr: "pipe",
53067
- cwd: process.cwd()
53129
+ cwd: directory
53068
53130
  });
53069
53131
  const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53070
53132
  const result = await Promise.race([
@@ -53189,13 +53251,13 @@ async function runPipAudit() {
53189
53251
  };
53190
53252
  }
53191
53253
  }
53192
- async function runCargoAudit() {
53254
+ async function runCargoAudit(directory) {
53193
53255
  const command = ["cargo", "audit", "--json"];
53194
53256
  try {
53195
53257
  const proc = Bun.spawn(command, {
53196
53258
  stdout: "pipe",
53197
53259
  stderr: "pipe",
53198
- cwd: process.cwd()
53260
+ cwd: directory
53199
53261
  });
53200
53262
  const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53201
53263
  const result = await Promise.race([
@@ -53303,7 +53365,7 @@ function mapCargoSeverity(cvss) {
53303
53365
  return "moderate";
53304
53366
  return "low";
53305
53367
  }
53306
- async function runGoAudit() {
53368
+ async function runGoAudit(directory) {
53307
53369
  const command = ["govulncheck", "-json", "./..."];
53308
53370
  if (!isCommandAvailable("govulncheck")) {
53309
53371
  warn("[pkg-audit] govulncheck not found, skipping Go audit");
@@ -53322,7 +53384,7 @@ async function runGoAudit() {
53322
53384
  const proc = Bun.spawn(command, {
53323
53385
  stdout: "pipe",
53324
53386
  stderr: "pipe",
53325
- cwd: process.cwd()
53387
+ cwd: directory
53326
53388
  });
53327
53389
  const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53328
53390
  const result = await Promise.race([
@@ -53433,7 +53495,7 @@ async function runGoAudit() {
53433
53495
  };
53434
53496
  }
53435
53497
  }
53436
- async function runDotnetAudit() {
53498
+ async function runDotnetAudit(directory) {
53437
53499
  const command = [
53438
53500
  "dotnet",
53439
53501
  "list",
@@ -53458,7 +53520,7 @@ async function runDotnetAudit() {
53458
53520
  const proc = Bun.spawn(command, {
53459
53521
  stdout: "pipe",
53460
53522
  stderr: "pipe",
53461
- cwd: process.cwd()
53523
+ cwd: directory
53462
53524
  });
53463
53525
  const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53464
53526
  const result = await Promise.race([
@@ -53557,7 +53619,7 @@ function mapDotnetSeverity(severity) {
53557
53619
  return "info";
53558
53620
  }
53559
53621
  }
53560
- async function runBundleAudit() {
53622
+ async function runBundleAudit(directory) {
53561
53623
  const useBundleExec = !isCommandAvailable("bundle-audit") && isCommandAvailable("bundle");
53562
53624
  if (!isCommandAvailable("bundle-audit") && !isCommandAvailable("bundle")) {
53563
53625
  warn("[pkg-audit] bundle-audit not found, skipping Ruby audit");
@@ -53577,7 +53639,7 @@ async function runBundleAudit() {
53577
53639
  const proc = Bun.spawn(command, {
53578
53640
  stdout: "pipe",
53579
53641
  stderr: "pipe",
53580
- cwd: process.cwd()
53642
+ cwd: directory
53581
53643
  });
53582
53644
  const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53583
53645
  const result = await Promise.race([
@@ -53704,7 +53766,7 @@ function mapBundleSeverity(adv) {
53704
53766
  return "moderate";
53705
53767
  return "low";
53706
53768
  }
53707
- async function runDartAudit() {
53769
+ async function runDartAudit(directory) {
53708
53770
  const dartBin = isCommandAvailable("dart") ? "dart" : isCommandAvailable("flutter") ? "flutter" : null;
53709
53771
  if (!dartBin) {
53710
53772
  warn("[pkg-audit] dart/flutter not found, skipping Dart audit");
@@ -53724,7 +53786,7 @@ async function runDartAudit() {
53724
53786
  const proc = Bun.spawn(command, {
53725
53787
  stdout: "pipe",
53726
53788
  stderr: "pipe",
53727
- cwd: process.cwd()
53789
+ cwd: directory
53728
53790
  });
53729
53791
  const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53730
53792
  const result = await Promise.race([
@@ -53823,8 +53885,8 @@ async function runDartAudit() {
53823
53885
  };
53824
53886
  }
53825
53887
  }
53826
- async function runAutoAudit() {
53827
- const ecosystems = detectEcosystems();
53888
+ async function runAutoAudit(directory) {
53889
+ const ecosystems = detectEcosystems(directory);
53828
53890
  if (ecosystems.length === 0) {
53829
53891
  return {
53830
53892
  ecosystems: [],
@@ -53839,25 +53901,25 @@ async function runAutoAudit() {
53839
53901
  for (const eco of ecosystems) {
53840
53902
  switch (eco) {
53841
53903
  case "npm":
53842
- results.push(await runNpmAudit());
53904
+ results.push(await runNpmAudit(directory));
53843
53905
  break;
53844
53906
  case "pip":
53845
- results.push(await runPipAudit());
53907
+ results.push(await runPipAudit(directory));
53846
53908
  break;
53847
53909
  case "cargo":
53848
- results.push(await runCargoAudit());
53910
+ results.push(await runCargoAudit(directory));
53849
53911
  break;
53850
53912
  case "go":
53851
- results.push(await runGoAudit());
53913
+ results.push(await runGoAudit(directory));
53852
53914
  break;
53853
53915
  case "dotnet":
53854
- results.push(await runDotnetAudit());
53916
+ results.push(await runDotnetAudit(directory));
53855
53917
  break;
53856
53918
  case "ruby":
53857
- results.push(await runBundleAudit());
53919
+ results.push(await runBundleAudit(directory));
53858
53920
  break;
53859
53921
  case "dart":
53860
- results.push(await runDartAudit());
53922
+ results.push(await runDartAudit(directory));
53861
53923
  break;
53862
53924
  }
53863
53925
  }
@@ -53883,40 +53945,46 @@ var pkg_audit = createSwarmTool({
53883
53945
  args: {
53884
53946
  ecosystem: tool.schema.enum(["auto", "npm", "pip", "cargo", "go", "dotnet", "ruby", "dart"]).default("auto").describe('Package ecosystem to audit: "auto" (detect from project files), "npm", "pip", "cargo", "go" (govulncheck), "dotnet" (dotnet list package), "ruby" (bundle-audit), or "dart" (dart pub outdated)')
53885
53947
  },
53886
- async execute(args2, _directory) {
53948
+ async execute(args2, directory) {
53887
53949
  if (!validateArgs3(args2)) {
53888
53950
  const errorResult = {
53889
53951
  error: 'Invalid arguments: ecosystem must be "auto", "npm", "pip", "cargo", "go", "dotnet", "ruby", or "dart"'
53890
53952
  };
53891
53953
  return JSON.stringify(errorResult, null, 2);
53892
53954
  }
53955
+ if (!directory || typeof directory !== "string" || directory.trim() === "") {
53956
+ const errorResult = {
53957
+ error: "project directory is required but was not provided"
53958
+ };
53959
+ return JSON.stringify(errorResult, null, 2);
53960
+ }
53893
53961
  const obj = args2;
53894
53962
  const ecosystem = obj.ecosystem || "auto";
53895
53963
  let result;
53896
53964
  switch (ecosystem) {
53897
53965
  case "auto":
53898
- result = await runAutoAudit();
53966
+ result = await runAutoAudit(directory);
53899
53967
  break;
53900
53968
  case "npm":
53901
- result = await runNpmAudit();
53969
+ result = await runNpmAudit(directory);
53902
53970
  break;
53903
53971
  case "pip":
53904
- result = await runPipAudit();
53972
+ result = await runPipAudit(directory);
53905
53973
  break;
53906
53974
  case "cargo":
53907
- result = await runCargoAudit();
53975
+ result = await runCargoAudit(directory);
53908
53976
  break;
53909
53977
  case "go":
53910
- result = await runGoAudit();
53978
+ result = await runGoAudit(directory);
53911
53979
  break;
53912
53980
  case "dotnet":
53913
- result = await runDotnetAudit();
53981
+ result = await runDotnetAudit(directory);
53914
53982
  break;
53915
53983
  case "ruby":
53916
- result = await runBundleAudit();
53984
+ result = await runBundleAudit(directory);
53917
53985
  break;
53918
53986
  case "dart":
53919
- result = await runDartAudit();
53987
+ result = await runDartAudit(directory);
53920
53988
  break;
53921
53989
  }
53922
53990
  return JSON.stringify(result, null, 2);
@@ -56088,7 +56156,7 @@ async function runLintWrapped(files, directory, _config) {
56088
56156
  duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
56089
56157
  };
56090
56158
  }
56091
- const result = await runWithTimeout(runLint(linter, "check"), TOOL_TIMEOUT_MS);
56159
+ const result = await runWithTimeout(runLint(linter, "check", directory), TOOL_TIMEOUT_MS);
56092
56160
  return {
56093
56161
  ran: true,
56094
56162
  result,
@@ -56104,7 +56172,7 @@ async function runLintWrapped(files, directory, _config) {
56104
56172
  }
56105
56173
  async function runLintOnFiles(linter, files, workspaceDir) {
56106
56174
  const isWindows = process.platform === "win32";
56107
- const binDir = path37.join(process.cwd(), "node_modules", ".bin");
56175
+ const binDir = path37.join(workspaceDir, "node_modules", ".bin");
56108
56176
  const validatedFiles = [];
56109
56177
  for (const file3 of files) {
56110
56178
  if (typeof file3 !== "string") {
@@ -56137,7 +56205,8 @@ async function runLintOnFiles(linter, files, workspaceDir) {
56137
56205
  try {
56138
56206
  const proc = Bun.spawn(command, {
56139
56207
  stdout: "pipe",
56140
- stderr: "pipe"
56208
+ stderr: "pipe",
56209
+ cwd: workspaceDir
56141
56210
  });
56142
56211
  const [stdout, stderr] = await Promise.all([
56143
56212
  new Response(proc.stdout).text(),
@@ -56528,7 +56597,7 @@ var pre_check_batch = createSwarmTool({
56528
56597
  directory: tool.schema.string().describe('Directory to run checks in (e.g., "." or "./src")'),
56529
56598
  sast_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity for SAST findings to cause failure (default: medium)")
56530
56599
  },
56531
- async execute(args2, _directory) {
56600
+ async execute(args2, directory) {
56532
56601
  if (!args2 || typeof args2 !== "object") {
56533
56602
  const errorResult = {
56534
56603
  gates_passed: false,
@@ -56544,6 +56613,33 @@ var pre_check_batch = createSwarmTool({
56544
56613
  };
56545
56614
  return JSON.stringify(errorResult, null, 2);
56546
56615
  }
56616
+ if (!directory || typeof directory !== "string" || directory.trim() === "") {
56617
+ const errorResult = {
56618
+ gates_passed: false,
56619
+ lint: {
56620
+ ran: false,
56621
+ error: "project directory is required but was not provided",
56622
+ duration_ms: 0
56623
+ },
56624
+ secretscan: {
56625
+ ran: false,
56626
+ error: "project directory is required but was not provided",
56627
+ duration_ms: 0
56628
+ },
56629
+ sast_scan: {
56630
+ ran: false,
56631
+ error: "project directory is required but was not provided",
56632
+ duration_ms: 0
56633
+ },
56634
+ quality_budget: {
56635
+ ran: false,
56636
+ error: "project directory is required but was not provided",
56637
+ duration_ms: 0
56638
+ },
56639
+ total_duration_ms: 0
56640
+ };
56641
+ return JSON.stringify(errorResult, null, 2);
56642
+ }
56547
56643
  const typedArgs = args2;
56548
56644
  if (!typedArgs.directory) {
56549
56645
  const errorResult = {
@@ -29,7 +29,7 @@ export declare function containsControlChars(str: string): boolean;
29
29
  export declare function validateArgs(args: unknown): args is {
30
30
  mode: 'fix' | 'check';
31
31
  };
32
- export declare function getLinterCommand(linter: SupportedLinter, mode: 'fix' | 'check'): string[];
32
+ export declare function getLinterCommand(linter: SupportedLinter, mode: 'fix' | 'check', projectDir: string): string[];
33
33
  /**
34
34
  * Build the shell command for an additional (non-JS/TS) linter.
35
35
  * cppcheck has no --fix mode; csharp and some others behave differently.
@@ -41,7 +41,7 @@ export declare function getAdditionalLinterCommand(linter: AdditionalLinter, mod
41
41
  */
42
42
  export declare function detectAdditionalLinter(cwd: string): 'ruff' | 'clippy' | 'golangci-lint' | 'checkstyle' | 'ktlint' | 'dotnet-format' | 'cppcheck' | 'swiftlint' | 'dart-analyze' | 'rubocop' | null;
43
43
  export declare function detectAvailableLinter(): Promise<SupportedLinter | null>;
44
- export declare function runLint(linter: SupportedLinter, mode: 'fix' | 'check'): Promise<LintResult>;
44
+ export declare function runLint(linter: SupportedLinter, mode: 'fix' | 'check', directory: string): Promise<LintResult>;
45
45
  /**
46
46
  * Run an additional (non-JS/TS) linter.
47
47
  * Follows the same structure as runLint() but uses getAdditionalLinterCommand().
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.20.0",
3
+ "version": "6.20.2",
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",