opencode-swarm 7.8.1 → 7.9.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/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.8.1",
37
+ version: "7.9.0",
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",
@@ -19738,7 +19738,13 @@ async function handleArchiveCommand(directory, args) {
19738
19738
  const wouldArchiveAge = [];
19739
19739
  const remainingBundles = [];
19740
19740
  for (const taskId of beforeTaskIds) {
19741
- const result = await loadEvidence(directory, taskId);
19741
+ let result;
19742
+ try {
19743
+ result = await loadEvidence(directory, taskId);
19744
+ } catch (_evidenceErr) {
19745
+ warn("archive: skipping corrupt or unreadable evidence for task", taskId);
19746
+ continue;
19747
+ }
19742
19748
  if (result.status !== "found") {
19743
19749
  continue;
19744
19750
  }
@@ -19793,6 +19799,7 @@ async function handleArchiveCommand(directory, args) {
19793
19799
  var init_archive = __esm(() => {
19794
19800
  init_loader();
19795
19801
  init_manager2();
19802
+ init_utils();
19796
19803
  });
19797
19804
 
19798
19805
  // src/db/project-db.ts
@@ -20799,7 +20806,13 @@ async function handleBenchmarkCommand(directory, args) {
20799
20806
  let totalTestToCodeRatio = 0;
20800
20807
  let qualityEvidenceCount = 0;
20801
20808
  for (const tid of await listEvidenceTaskIds(directory)) {
20802
- const result = await loadEvidence(directory, tid);
20809
+ let result;
20810
+ try {
20811
+ result = await loadEvidence(directory, tid);
20812
+ } catch (_evidenceErr) {
20813
+ warn("benchmark: skipping corrupt or unreadable evidence for task", tid);
20814
+ continue;
20815
+ }
20803
20816
  if (result.status !== "found")
20804
20817
  continue;
20805
20818
  for (const e of result.bundle.entries) {
@@ -37201,7 +37214,17 @@ async function handleDarkMatterCommand(directory, args) {
37201
37214
  i++;
37202
37215
  }
37203
37216
  }
37204
- const pairs = await detectDarkMatter(directory, options);
37217
+ let pairs;
37218
+ try {
37219
+ pairs = await _internals10.detectDarkMatter(directory, options);
37220
+ } catch (err) {
37221
+ const errMsg = err instanceof Error ? err.message : String(err);
37222
+ return `## Dark Matter Analysis Failed
37223
+
37224
+ Error analyzing git history: ${errMsg}
37225
+
37226
+ Ensure this is a git repository with commit history.`;
37227
+ }
37205
37228
  const output = formatDarkMatterOutput(pairs);
37206
37229
  if (pairs.length > 0) {
37207
37230
  try {
@@ -41607,22 +41630,37 @@ var init_handoff_service = __esm(() => {
41607
41630
 
41608
41631
  // src/commands/handoff.ts
41609
41632
  import crypto4 from "crypto";
41610
- import { renameSync as renameSync7 } from "fs";
41633
+ import { renameSync as renameSync7, unlinkSync as unlinkSync4 } from "fs";
41611
41634
  async function handleHandoffCommand(directory, _args) {
41612
41635
  const handoffData = await getHandoffData(directory);
41613
41636
  const markdown = formatHandoffMarkdown(handoffData);
41614
- const resolvedPath = validateSwarmPath(directory, "handoff.md");
41615
- const tempPath = `${resolvedPath}.tmp.${crypto4.randomUUID()}`;
41616
- await bunWrite(tempPath, markdown);
41617
- renameSync7(tempPath, resolvedPath);
41618
- const continuationPrompt = formatContinuationPrompt(handoffData);
41619
- const promptPath = validateSwarmPath(directory, "handoff-prompt.md");
41620
- const promptTempPath = `${promptPath}.tmp.${crypto4.randomUUID()}`;
41621
- await bunWrite(promptTempPath, continuationPrompt);
41622
- renameSync7(promptTempPath, promptPath);
41623
- await writeSnapshot(directory, swarmState);
41624
- await flushPendingSnapshot(directory);
41625
- return `## Handoff Brief Written
41637
+ try {
41638
+ const resolvedPath = validateSwarmPath(directory, "handoff.md");
41639
+ const tempPath = `${resolvedPath}.tmp.${crypto4.randomUUID()}`;
41640
+ await bunWrite(tempPath, markdown);
41641
+ try {
41642
+ renameSync7(tempPath, resolvedPath);
41643
+ } catch (renameErr) {
41644
+ try {
41645
+ unlinkSync4(tempPath);
41646
+ } catch {}
41647
+ throw renameErr;
41648
+ }
41649
+ const continuationPrompt = formatContinuationPrompt(handoffData);
41650
+ const promptPath = validateSwarmPath(directory, "handoff-prompt.md");
41651
+ const promptTempPath = `${promptPath}.tmp.${crypto4.randomUUID()}`;
41652
+ await bunWrite(promptTempPath, continuationPrompt);
41653
+ try {
41654
+ renameSync7(promptTempPath, promptPath);
41655
+ } catch (renameErr) {
41656
+ try {
41657
+ unlinkSync4(promptTempPath);
41658
+ } catch {}
41659
+ throw renameErr;
41660
+ }
41661
+ await writeSnapshot(directory, swarmState);
41662
+ await flushPendingSnapshot(directory);
41663
+ return `## Handoff Brief Written
41626
41664
 
41627
41665
  Brief written to \`.swarm/handoff.md\`.
41628
41666
  Continuation prompt written to \`.swarm/handoff-prompt.md\`.
@@ -41636,6 +41674,16 @@ ${markdown}
41636
41674
  Copy and paste the block below into your next session to resume cleanly:
41637
41675
 
41638
41676
  ${continuationPrompt}`;
41677
+ } catch (err) {
41678
+ const errMsg = err instanceof Error ? err.message : String(err);
41679
+ return `## Handoff Generated (file write failed)
41680
+
41681
+ Handoff data was generated but could not be written to disk: ${errMsg}
41682
+
41683
+ The handoff content is included below for manual copy:
41684
+
41685
+ ${markdown}`;
41686
+ }
41639
41687
  }
41640
41688
  var init_handoff = __esm(() => {
41641
41689
  init_utils2();
@@ -47722,7 +47770,19 @@ async function handleSimulateCommand(directory, args) {
47722
47770
  options.minCommits = val;
47723
47771
  }
47724
47772
  }
47725
- const darkMatterPairs = await detectDarkMatter(directory, options);
47773
+ let darkMatterPairs;
47774
+ try {
47775
+ darkMatterPairs = await _internals10.detectDarkMatter(directory, options);
47776
+ } catch (err) {
47777
+ const errMsg = err instanceof Error ? err.message : String(err);
47778
+ return `## Simulate Report
47779
+
47780
+ ### Error
47781
+
47782
+ Error analyzing git history: ${errMsg}
47783
+
47784
+ Ensure this is a git repository with commit history.`;
47785
+ }
47726
47786
  const reportLines = [
47727
47787
  "# Simulate Report",
47728
47788
  "",
@@ -47740,15 +47800,21 @@ async function handleSimulateCommand(directory, args) {
47740
47800
  ];
47741
47801
  const report = reportLines.filter(Boolean).join(`
47742
47802
  `);
47743
- const fs22 = await import("fs/promises");
47744
- const path37 = await import("path");
47745
- const reportPath = path37.join(directory, ".swarm", "simulate-report.md");
47746
- await fs22.mkdir(path37.dirname(reportPath), { recursive: true });
47747
- await fs22.writeFile(reportPath, report, "utf-8");
47803
+ try {
47804
+ const fs22 = await import("fs/promises");
47805
+ const path37 = await import("path");
47806
+ const reportPath = path37.join(directory, ".swarm", "simulate-report.md");
47807
+ await fs22.mkdir(path37.dirname(reportPath), { recursive: true });
47808
+ await fs22.writeFile(reportPath, report, "utf-8");
47809
+ } catch (err) {
47810
+ const writeErr = err instanceof Error ? err.message : String(err);
47811
+ warn(`simulate: failed to write report to ${directory}/.swarm/simulate-report.md`, writeErr);
47812
+ }
47748
47813
  return `${darkMatterPairs.length} hidden coupling pairs detected`;
47749
47814
  }
47750
47815
  var init_simulate = __esm(() => {
47751
47816
  init_co_change_analyzer();
47817
+ init_utils();
47752
47818
  });
47753
47819
 
47754
47820
  // src/commands/specify.ts
@@ -48195,12 +48261,6 @@ function buildHelpText() {
48195
48261
  return lines.join(`
48196
48262
  `);
48197
48263
  }
48198
- function getHelpText() {
48199
- if (!_helpText) {
48200
- _helpText = buildHelpText();
48201
- }
48202
- return _helpText;
48203
- }
48204
48264
  function createSwarmCommandHandler(directory, agents) {
48205
48265
  return async (input, output) => {
48206
48266
  if (input.command !== "swarm" && !input.command.startsWith("swarm-")) {
@@ -48226,7 +48286,22 @@ function createSwarmCommandHandler(directory, agents) {
48226
48286
  let text;
48227
48287
  const resolved = resolveCommand(tokens);
48228
48288
  if (!resolved) {
48229
- text = getHelpText();
48289
+ if (tokens.length === 0) {
48290
+ text = buildHelpText();
48291
+ } else {
48292
+ const attemptedCommand = tokens[0] || "";
48293
+ const MAX_DISPLAY = 100;
48294
+ const displayCommand = attemptedCommand.length > MAX_DISPLAY ? `${attemptedCommand.slice(0, MAX_DISPLAY)}...` : attemptedCommand;
48295
+ const similar = _internals19.findSimilarCommands(attemptedCommand);
48296
+ const header = `Command \`/swarm ${displayCommand}\` not found.`;
48297
+ const suggestions = similar.length > 0 ? `Did you mean:
48298
+ ${similar.map((cmd) => ` \u2022 /swarm ${cmd}`).join(`
48299
+ `)}` : "";
48300
+ const footer = "Run `/swarm help` for all commands.";
48301
+ text = [header, suggestions, footer].filter(Boolean).join(`
48302
+
48303
+ `);
48304
+ }
48230
48305
  } else {
48231
48306
  try {
48232
48307
  text = await resolved.entry.handler({
@@ -48258,7 +48333,6 @@ ${text}`;
48258
48333
  ];
48259
48334
  };
48260
48335
  }
48261
- var _helpText;
48262
48336
  var init_commands = __esm(() => {
48263
48337
  init_registry();
48264
48338
  init_acknowledge_spec_drift();
@@ -48318,14 +48392,38 @@ function levenshteinDistance(a, b) {
48318
48392
  }
48319
48393
  function findSimilarCommands(query) {
48320
48394
  const q = query.toLowerCase();
48321
- const scored = VALID_COMMANDS.filter((cmd) => {
48322
- if (cmd.includes(" ") || cmd.includes("-"))
48323
- return false;
48324
- return cmd.toLowerCase().includes(q) || q.includes(cmd.toLowerCase());
48325
- }).map((cmd) => ({
48326
- cmd,
48327
- score: cmd.length < q.length ? q.length - cmd.length : _internals19.levenshteinDistance(q, cmd)
48328
- }));
48395
+ if (q.length > 500) {
48396
+ return [];
48397
+ }
48398
+ const scored = VALID_COMMANDS.map((cmd) => {
48399
+ const cmdLower = cmd.toLowerCase();
48400
+ const fullScore = _internals19.levenshteinDistance(q, cmdLower);
48401
+ let tokenScore = Infinity;
48402
+ if (cmd.includes(" ") || cmd.includes("-")) {
48403
+ const qTokens = q.split(/[\s-]+/);
48404
+ const cmdTokens = cmdLower.split(/[\s-]+/);
48405
+ let totalTokenDist = 0;
48406
+ for (const qt of qTokens) {
48407
+ if (qt.length === 0)
48408
+ continue;
48409
+ let minDist = Infinity;
48410
+ for (const ct of cmdTokens) {
48411
+ if (ct.length === 0)
48412
+ continue;
48413
+ const dist = _internals19.levenshteinDistance(qt, ct);
48414
+ if (dist < minDist)
48415
+ minDist = dist;
48416
+ }
48417
+ totalTokenDist += minDist;
48418
+ }
48419
+ tokenScore = totalTokenDist;
48420
+ }
48421
+ const dashStrippedQ = q.replace(/-/g, "");
48422
+ const dashStrippedCmd = cmdLower.replace(/-/g, "");
48423
+ const dashScore = _internals19.levenshteinDistance(dashStrippedQ, dashStrippedCmd);
48424
+ const score = Math.min(fullScore, tokenScore, dashScore);
48425
+ return { cmd, score };
48426
+ });
48329
48427
  scored.sort((a, b) => a.score - b.score);
48330
48428
  return scored.slice(0, 3).map((s) => s.cmd);
48331
48429
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tests for archive.ts graceful handling of corrupt evidence files (Task 1.6)
3
+ *
4
+ * Verifies that the try/catch in handleArchiveCommand's dry-run loop
5
+ * catches exceptions from loadEvidence and skips corrupt/unreadable files.
6
+ *
7
+ * Uses real filesystem operations like existing archive tests.
8
+ */
9
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tests for benchmark.ts graceful handling of corrupt evidence files.
3
+ *
4
+ * Verifies that the try/catch in handleBenchmarkCommand's cumulative loop
5
+ * catches exceptions from loadEvidence and skips corrupt/unreadable files.
6
+ */
7
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Adversarial security tests for command-not-found UX in createSwarmCommandHandler.
3
+ *
4
+ * Attack vectors covered:
5
+ * 1. Very long command name (10000+ chars) — does it hang or crash?
6
+ * 2. Command with special characters (script injection, shell injection, template literals)
7
+ * 3. Command with newlines/embedded control chars — does it break output format?
8
+ * 4. Command with unicode/emoji — handled gracefully?
9
+ * 5. Extremely deep tokens array (1000 elements) — does findSimilarCommands handle it?
10
+ * 6. Null bytes in command name
11
+ */
12
+ export {};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tests for command-not-found UX improvement in createSwarmCommandHandler.
3
+ *
4
+ * Covers:
5
+ * - Unknown single-word command shows "Command not found" + suggestions + footer
6
+ * - Unknown compound command shows header with command name
7
+ * - Empty tokens (empty array) → returns buildHelpText() output
8
+ * - Command with no similar matches → shows header + footer only (no "Did you mean" section)
9
+ * - Multiple similar commands returned → all shown with bullet format
10
+ */
11
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
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.8.1",
36
+ version: "7.9.0",
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",
@@ -20405,7 +20405,13 @@ async function handleArchiveCommand(directory, args2) {
20405
20405
  const wouldArchiveAge = [];
20406
20406
  const remainingBundles = [];
20407
20407
  for (const taskId of beforeTaskIds) {
20408
- const result = await loadEvidence(directory, taskId);
20408
+ let result;
20409
+ try {
20410
+ result = await loadEvidence(directory, taskId);
20411
+ } catch (_evidenceErr) {
20412
+ warn("archive: skipping corrupt or unreadable evidence for task", taskId);
20413
+ continue;
20414
+ }
20409
20415
  if (result.status !== "found") {
20410
20416
  continue;
20411
20417
  }
@@ -20460,6 +20466,7 @@ async function handleArchiveCommand(directory, args2) {
20460
20466
  var init_archive = __esm(() => {
20461
20467
  init_loader();
20462
20468
  init_manager2();
20469
+ init_utils();
20463
20470
  });
20464
20471
 
20465
20472
  // src/db/project-db.ts
@@ -27228,7 +27235,13 @@ async function handleBenchmarkCommand(directory, args2) {
27228
27235
  let totalTestToCodeRatio = 0;
27229
27236
  let qualityEvidenceCount = 0;
27230
27237
  for (const tid of await listEvidenceTaskIds(directory)) {
27231
- const result = await loadEvidence(directory, tid);
27238
+ let result;
27239
+ try {
27240
+ result = await loadEvidence(directory, tid);
27241
+ } catch (_evidenceErr) {
27242
+ warn("benchmark: skipping corrupt or unreadable evidence for task", tid);
27243
+ continue;
27244
+ }
27232
27245
  if (result.status !== "found")
27233
27246
  continue;
27234
27247
  for (const e of result.bundle.entries) {
@@ -44904,7 +44917,17 @@ async function handleDarkMatterCommand(directory, args2) {
44904
44917
  i2++;
44905
44918
  }
44906
44919
  }
44907
- const pairs = await detectDarkMatter(directory, options);
44920
+ let pairs;
44921
+ try {
44922
+ pairs = await _internals15.detectDarkMatter(directory, options);
44923
+ } catch (err2) {
44924
+ const errMsg = err2 instanceof Error ? err2.message : String(err2);
44925
+ return `## Dark Matter Analysis Failed
44926
+
44927
+ Error analyzing git history: ${errMsg}
44928
+
44929
+ Ensure this is a git repository with commit history.`;
44930
+ }
44908
44931
  const output = formatDarkMatterOutput(pairs);
44909
44932
  if (pairs.length > 0) {
44910
44933
  try {
@@ -49487,22 +49510,37 @@ var init_handoff_service = __esm(() => {
49487
49510
 
49488
49511
  // src/commands/handoff.ts
49489
49512
  import crypto4 from "node:crypto";
49490
- import { renameSync as renameSync10 } from "node:fs";
49513
+ import { renameSync as renameSync10, unlinkSync as unlinkSync5 } from "node:fs";
49491
49514
  async function handleHandoffCommand(directory, _args) {
49492
49515
  const handoffData = await getHandoffData(directory);
49493
49516
  const markdown = formatHandoffMarkdown(handoffData);
49494
- const resolvedPath = validateSwarmPath(directory, "handoff.md");
49495
- const tempPath = `${resolvedPath}.tmp.${crypto4.randomUUID()}`;
49496
- await bunWrite(tempPath, markdown);
49497
- renameSync10(tempPath, resolvedPath);
49498
- const continuationPrompt = formatContinuationPrompt(handoffData);
49499
- const promptPath = validateSwarmPath(directory, "handoff-prompt.md");
49500
- const promptTempPath = `${promptPath}.tmp.${crypto4.randomUUID()}`;
49501
- await bunWrite(promptTempPath, continuationPrompt);
49502
- renameSync10(promptTempPath, promptPath);
49503
- await writeSnapshot(directory, swarmState);
49504
- await flushPendingSnapshot(directory);
49505
- return `## Handoff Brief Written
49517
+ try {
49518
+ const resolvedPath = validateSwarmPath(directory, "handoff.md");
49519
+ const tempPath = `${resolvedPath}.tmp.${crypto4.randomUUID()}`;
49520
+ await bunWrite(tempPath, markdown);
49521
+ try {
49522
+ renameSync10(tempPath, resolvedPath);
49523
+ } catch (renameErr) {
49524
+ try {
49525
+ unlinkSync5(tempPath);
49526
+ } catch {}
49527
+ throw renameErr;
49528
+ }
49529
+ const continuationPrompt = formatContinuationPrompt(handoffData);
49530
+ const promptPath = validateSwarmPath(directory, "handoff-prompt.md");
49531
+ const promptTempPath = `${promptPath}.tmp.${crypto4.randomUUID()}`;
49532
+ await bunWrite(promptTempPath, continuationPrompt);
49533
+ try {
49534
+ renameSync10(promptTempPath, promptPath);
49535
+ } catch (renameErr) {
49536
+ try {
49537
+ unlinkSync5(promptTempPath);
49538
+ } catch {}
49539
+ throw renameErr;
49540
+ }
49541
+ await writeSnapshot(directory, swarmState);
49542
+ await flushPendingSnapshot(directory);
49543
+ return `## Handoff Brief Written
49506
49544
 
49507
49545
  Brief written to \`.swarm/handoff.md\`.
49508
49546
  Continuation prompt written to \`.swarm/handoff-prompt.md\`.
@@ -49516,6 +49554,16 @@ ${markdown}
49516
49554
  Copy and paste the block below into your next session to resume cleanly:
49517
49555
 
49518
49556
  ${continuationPrompt}`;
49557
+ } catch (err2) {
49558
+ const errMsg = err2 instanceof Error ? err2.message : String(err2);
49559
+ return `## Handoff Generated (file write failed)
49560
+
49561
+ Handoff data was generated but could not be written to disk: ${errMsg}
49562
+
49563
+ The handoff content is included below for manual copy:
49564
+
49565
+ ${markdown}`;
49566
+ }
49519
49567
  }
49520
49568
  var init_handoff = __esm(() => {
49521
49569
  init_utils2();
@@ -55712,7 +55760,19 @@ async function handleSimulateCommand(directory, args2) {
55712
55760
  options.minCommits = val;
55713
55761
  }
55714
55762
  }
55715
- const darkMatterPairs = await detectDarkMatter(directory, options);
55763
+ let darkMatterPairs;
55764
+ try {
55765
+ darkMatterPairs = await _internals15.detectDarkMatter(directory, options);
55766
+ } catch (err2) {
55767
+ const errMsg = err2 instanceof Error ? err2.message : String(err2);
55768
+ return `## Simulate Report
55769
+
55770
+ ### Error
55771
+
55772
+ Error analyzing git history: ${errMsg}
55773
+
55774
+ Ensure this is a git repository with commit history.`;
55775
+ }
55716
55776
  const reportLines = [
55717
55777
  "# Simulate Report",
55718
55778
  "",
@@ -55730,15 +55790,21 @@ async function handleSimulateCommand(directory, args2) {
55730
55790
  ];
55731
55791
  const report = reportLines.filter(Boolean).join(`
55732
55792
  `);
55733
- const fs29 = await import("node:fs/promises");
55734
- const path44 = await import("node:path");
55735
- const reportPath = path44.join(directory, ".swarm", "simulate-report.md");
55736
- await fs29.mkdir(path44.dirname(reportPath), { recursive: true });
55737
- await fs29.writeFile(reportPath, report, "utf-8");
55793
+ try {
55794
+ const fs29 = await import("node:fs/promises");
55795
+ const path44 = await import("node:path");
55796
+ const reportPath = path44.join(directory, ".swarm", "simulate-report.md");
55797
+ await fs29.mkdir(path44.dirname(reportPath), { recursive: true });
55798
+ await fs29.writeFile(reportPath, report, "utf-8");
55799
+ } catch (err2) {
55800
+ const writeErr = err2 instanceof Error ? err2.message : String(err2);
55801
+ warn(`simulate: failed to write report to ${directory}/.swarm/simulate-report.md`, writeErr);
55802
+ }
55738
55803
  return `${darkMatterPairs.length} hidden coupling pairs detected`;
55739
55804
  }
55740
55805
  var init_simulate = __esm(() => {
55741
55806
  init_co_change_analyzer();
55807
+ init_utils();
55742
55808
  });
55743
55809
 
55744
55810
  // src/commands/specify.ts
@@ -56400,12 +56466,6 @@ function buildHelpText() {
56400
56466
  return lines.join(`
56401
56467
  `);
56402
56468
  }
56403
- function getHelpText() {
56404
- if (!_helpText) {
56405
- _helpText = buildHelpText();
56406
- }
56407
- return _helpText;
56408
- }
56409
56469
  function createSwarmCommandHandler(directory, agents) {
56410
56470
  return async (input, output) => {
56411
56471
  if (input.command !== "swarm" && !input.command.startsWith("swarm-")) {
@@ -56431,7 +56491,22 @@ function createSwarmCommandHandler(directory, agents) {
56431
56491
  let text;
56432
56492
  const resolved = resolveCommand(tokens);
56433
56493
  if (!resolved) {
56434
- text = getHelpText();
56494
+ if (tokens.length === 0) {
56495
+ text = buildHelpText();
56496
+ } else {
56497
+ const attemptedCommand = tokens[0] || "";
56498
+ const MAX_DISPLAY = 100;
56499
+ const displayCommand = attemptedCommand.length > MAX_DISPLAY ? `${attemptedCommand.slice(0, MAX_DISPLAY)}...` : attemptedCommand;
56500
+ const similar = _internals24.findSimilarCommands(attemptedCommand);
56501
+ const header = `Command \`/swarm ${displayCommand}\` not found.`;
56502
+ const suggestions = similar.length > 0 ? `Did you mean:
56503
+ ${similar.map((cmd) => ` • /swarm ${cmd}`).join(`
56504
+ `)}` : "";
56505
+ const footer = "Run `/swarm help` for all commands.";
56506
+ text = [header, suggestions, footer].filter(Boolean).join(`
56507
+
56508
+ `);
56509
+ }
56435
56510
  } else {
56436
56511
  try {
56437
56512
  text = await resolved.entry.handler({
@@ -56463,7 +56538,6 @@ ${text}`;
56463
56538
  ];
56464
56539
  };
56465
56540
  }
56466
- var _helpText;
56467
56541
  var init_commands = __esm(() => {
56468
56542
  init_registry();
56469
56543
  init_acknowledge_spec_drift();
@@ -56523,14 +56597,38 @@ function levenshteinDistance(a, b) {
56523
56597
  }
56524
56598
  function findSimilarCommands(query) {
56525
56599
  const q = query.toLowerCase();
56526
- const scored = VALID_COMMANDS.filter((cmd) => {
56527
- if (cmd.includes(" ") || cmd.includes("-"))
56528
- return false;
56529
- return cmd.toLowerCase().includes(q) || q.includes(cmd.toLowerCase());
56530
- }).map((cmd) => ({
56531
- cmd,
56532
- score: cmd.length < q.length ? q.length - cmd.length : _internals24.levenshteinDistance(q, cmd)
56533
- }));
56600
+ if (q.length > 500) {
56601
+ return [];
56602
+ }
56603
+ const scored = VALID_COMMANDS.map((cmd) => {
56604
+ const cmdLower = cmd.toLowerCase();
56605
+ const fullScore = _internals24.levenshteinDistance(q, cmdLower);
56606
+ let tokenScore = Infinity;
56607
+ if (cmd.includes(" ") || cmd.includes("-")) {
56608
+ const qTokens = q.split(/[\s-]+/);
56609
+ const cmdTokens = cmdLower.split(/[\s-]+/);
56610
+ let totalTokenDist = 0;
56611
+ for (const qt of qTokens) {
56612
+ if (qt.length === 0)
56613
+ continue;
56614
+ let minDist = Infinity;
56615
+ for (const ct of cmdTokens) {
56616
+ if (ct.length === 0)
56617
+ continue;
56618
+ const dist = _internals24.levenshteinDistance(qt, ct);
56619
+ if (dist < minDist)
56620
+ minDist = dist;
56621
+ }
56622
+ totalTokenDist += minDist;
56623
+ }
56624
+ tokenScore = totalTokenDist;
56625
+ }
56626
+ const dashStrippedQ = q.replace(/-/g, "");
56627
+ const dashStrippedCmd = cmdLower.replace(/-/g, "");
56628
+ const dashScore = _internals24.levenshteinDistance(dashStrippedQ, dashStrippedCmd);
56629
+ const score = Math.min(fullScore, tokenScore, dashScore);
56630
+ return { cmd, score };
56631
+ });
56534
56632
  scored.sort((a, b) => a.score - b.score);
56535
56633
  return scored.slice(0, 3).map((s) => s.cmd);
56536
56634
  }
@@ -67090,7 +67188,7 @@ init_state();
67090
67188
  init_utils();
67091
67189
  init_bun_compat();
67092
67190
  init_utils2();
67093
- import { renameSync as renameSync12, unlinkSync as unlinkSync8 } from "node:fs";
67191
+ import { renameSync as renameSync12, unlinkSync as unlinkSync9 } from "node:fs";
67094
67192
  import * as nodePath2 from "node:path";
67095
67193
  function createAgentActivityHooks(config3, directory) {
67096
67194
  if (config3.hooks?.agent_activity === false) {
@@ -67168,7 +67266,7 @@ async function doFlush(directory) {
67168
67266
  renameSync12(tempPath, path52);
67169
67267
  } catch (writeError) {
67170
67268
  try {
67171
- unlinkSync8(tempPath);
67269
+ unlinkSync9(tempPath);
67172
67270
  } catch {}
67173
67271
  throw writeError;
67174
67272
  }
@@ -92458,7 +92556,7 @@ import * as path110 from "node:path";
92458
92556
 
92459
92557
  // src/mutation/engine.ts
92460
92558
  import { spawnSync as spawnSync3 } from "node:child_process";
92461
- import { unlinkSync as unlinkSync13, writeFileSync as writeFileSync22 } from "node:fs";
92559
+ import { unlinkSync as unlinkSync14, writeFileSync as writeFileSync22 } from "node:fs";
92462
92560
  import * as path109 from "node:path";
92463
92561
 
92464
92562
  // src/mutation/equivalence.ts
@@ -92697,7 +92795,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
92697
92795
  revertError = new Error(`Failed to revert mutation ${patch.id}: ${revertErr}. Working tree may be dirty.`);
92698
92796
  }
92699
92797
  try {
92700
- unlinkSync13(patchFile);
92798
+ unlinkSync14(patchFile);
92701
92799
  } catch (_unlinkErr) {}
92702
92800
  }
92703
92801
  }
@@ -95366,7 +95464,7 @@ async function initializeOpenCodeSwarm(ctx) {
95366
95464
  ...opencodeConfig.command || {},
95367
95465
  swarm: {
95368
95466
  template: "/swarm $ARGUMENTS",
95369
- description: "Swarm management commands: /swarm [status|plan|agents|history|config|evidence|handoff|archive|diagnose|diagnosis|preflight|sync-plan|benchmark|export|reset|rollback|retrieve|clarify|analyze|specify|brainstorm|council|qa-gates|dark-matter|knowledge|curate|turbo|full-auto|write-retro|reset-session|simulate|promote|checkpoint|acknowledge-spec-drift|doctor-tools|close]"
95467
+ description: "Swarm management commands: /swarm [status|plan|agents|history|config|evidence|handoff|archive|diagnose|diagnosis|preflight|sync-plan|benchmark|export|reset|rollback|retrieve|clarify|analyze|specify|brainstorm|council|pr-review|issue|qa-gates|dark-matter|knowledge|curate|turbo|full-auto|write-retro|reset-session|simulate|promote|checkpoint|acknowledge-spec-drift|doctor-tools|close]"
95370
95468
  },
95371
95469
  "swarm-status": {
95372
95470
  template: "/swarm status",
@@ -95456,6 +95554,14 @@ async function initializeOpenCodeSwarm(ctx) {
95456
95554
  template: "/swarm council $ARGUMENTS",
95457
95555
  description: "Use /swarm council <question> to convene a multi-model General Council deliberation (generalist / skeptic / domain expert) [--spec-review]"
95458
95556
  },
95557
+ "swarm-pr-review": {
95558
+ template: "/swarm pr-review $ARGUMENTS",
95559
+ description: "Use /swarm pr-review to launch deep PR review with multi-lane analysis"
95560
+ },
95561
+ "swarm-issue": {
95562
+ template: "/swarm issue $ARGUMENTS",
95563
+ description: "Use /swarm issue to ingest a GitHub issue into the swarm workflow"
95564
+ },
95459
95565
  "swarm-qa-gates": {
95460
95566
  template: "/swarm qa-gates $ARGUMENTS",
95461
95567
  description: "Use /swarm qa-gates to view or modify QA gate profile for the current plan"
@@ -95477,7 +95583,7 @@ async function initializeOpenCodeSwarm(ctx) {
95477
95583
  description: "Use /swarm turbo to enable turbo mode for faster execution"
95478
95584
  },
95479
95585
  "swarm-full-auto": {
95480
- template: "/swarm-full-auto $ARGUMENTS",
95586
+ template: "/swarm full-auto $ARGUMENTS",
95481
95587
  description: "Toggle Full-Auto Mode for the active session [on|off]"
95482
95588
  },
95483
95589
  "swarm-write-retro": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.8.1",
3
+ "version": "7.9.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",