opencode-swarm 7.66.0 → 7.66.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.
@@ -5,6 +5,8 @@ description: >
5
5
  Use when addressing pasted PR feedback, GitHub review comments or threads,
6
6
  requested changes, CI/check failures, merge conflicts, stale PR branches, or
7
7
  PR follow-up work that must close all known issues without dropping findings.
8
+ Supports multi-round bot reviews (the repository's bot posts a new review
9
+ after every push) via the iterative pattern documented in the body.
8
10
  ---
9
11
 
10
12
  # Swarm PR Feedback
@@ -14,6 +16,51 @@ Use this skill to close known PR feedback. This is not a fresh broad PR review.
14
16
  feedback surfaces, verifies each claim, clusters related problems, fixes confirmed
15
17
  issues, validates the branch, and reports closure status for every item.
16
18
 
19
+ ## Multi-Round Bot Reviews (Iterative Pattern)
20
+
21
+ The repository's bot reviewer (`hermes-pr-review` / Qwen3.6 + Gemma-4 dual-model)
22
+ posts a new review comment after **every push** to the PR branch, not just the
23
+ final state. Expect N rounds of review for N pushes, and budget for it.
24
+
25
+ **Round N+1 deltas vs Round N:**
26
+ - Fresh `FB-###` ledger IDs for new findings (do not reuse IDs from earlier rounds)
27
+ - Findings from prior rounds that remain unfixed will reappear with the same evidence
28
+ - Findings you marked DISPROVED with new evidence may reappear if the bot disagrees
29
+ - New findings may be introduced that the prior round did not see (the bot's read scope
30
+ is the new commit, not the full diff history)
31
+
32
+ **Operating principles for multi-round triage:**
33
+
34
+ 1. **Continue the ledger, do not start over.** Append to the same `FB-###` counter
35
+ across rounds. Track each finding's state per round (open, fixed, disproved,
36
+ awaiting-decision, repeated).
37
+ 2. **Carry forward unresolved items.** Findings you marked `PARTIAL` or `NEEDS_USER_DECISION`
38
+ in round N will still be open in round N+1. The closure ledger should show their
39
+ evolution (e.g., "PARTIAL round 1 → CONFIRMED round 2 after evidence collected").
40
+ 3. **Apply the 3-strikes-then-defense-in-depth rule.** When the same finding is
41
+ raised 3+ times across rounds, prefer to add the suggested code change with a
42
+ defense-in-depth rationale comment rather than continue to debate. One extra
43
+ condition is cheap; per-round debate is expensive. Document the parent-vs-inner
44
+ relationship inline so future readers see the rationale.
45
+ 4. **Verify bot fix-direction suggestions against actual file structure.** Bots
46
+ read files linearly and can miss parent-block guards. For any "add an X check"
47
+ suggestion, read the surrounding function/block to confirm the check is genuinely
48
+ missing or already exists at a higher scope.
49
+ 5. **Each round produces its own closure ledger as a PR comment.** Prefix with
50
+ "Round N" so the bot and reviewers can see progression. Maintain a running
51
+ summary table at the end of each comment showing totals across rounds
52
+ (confirmed+fixed / disproved / partial / awaiting-decision).
53
+ 6. **Stop the cycle deliberately.** If a finding is disproved with code evidence 3+
54
+ times and the bot keeps re-raising it, leave the comment, post the closure
55
+ ledger with the cumulative evidence, and surface the disagreement to the user
56
+ rather than continuing to push fixes. The user can resolve persistent
57
+ reviewer-AI disagreement.
58
+
59
+ **Why this matters:** Without the multi-round pattern, each round looks like
60
+ "start over, re-triage everything." With it, the rounds become incremental:
61
+ each round's work is bounded by new findings + carried-forward items only.
62
+ This matches how the bot actually behaves and avoids wasted cycles.
63
+
17
64
  ## Operating Stance
18
65
 
19
66
  Treat every review comment, CI failure, bot summary, PR body claim, and pasted note
package/dist/cli/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "opencode-swarm",
55
- version: "7.66.0",
55
+ version: "7.66.2",
56
56
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
57
57
  main: "dist/index.js",
58
58
  types: "dist/index.d.ts",
@@ -56309,8 +56309,20 @@ import * as fs24 from "fs";
56309
56309
  import * as path50 from "path";
56310
56310
  function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
56311
56311
  if (workingDirectory == null || workingDirectory === "") {
56312
+ if (typeof fallbackDirectory !== "string" || fallbackDirectory === "") {
56313
+ return {
56314
+ success: false,
56315
+ message: "Invalid working_directory: no explicit working_directory was provided and fallbackDirectory is missing or not a string"
56316
+ };
56317
+ }
56312
56318
  return { success: true, directory: fallbackDirectory };
56313
56319
  }
56320
+ if (typeof workingDirectory !== "string") {
56321
+ return {
56322
+ success: false,
56323
+ message: "Invalid working_directory: path must be a string"
56324
+ };
56325
+ }
56314
56326
  if (workingDirectory.includes("\x00")) {
56315
56327
  return {
56316
56328
  success: false,
@@ -56326,14 +56338,14 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
56326
56338
  };
56327
56339
  }
56328
56340
  }
56329
- const normalizedDir = path50.normalize(workingDirectory);
56330
- const pathParts = normalizedDir.split(path50.sep);
56331
- if (pathParts.includes("..")) {
56341
+ const rawPathParts = workingDirectory.split(path50.sep);
56342
+ if (rawPathParts.includes("..")) {
56332
56343
  return {
56333
56344
  success: false,
56334
56345
  message: "Invalid working_directory: path traversal sequences (..) are not allowed"
56335
56346
  };
56336
56347
  }
56348
+ const normalizedDir = path50.normalize(workingDirectory);
56337
56349
  const resolvedDir = path50.resolve(normalizedDir);
56338
56350
  let statResult;
56339
56351
  try {
@@ -56350,6 +56362,9 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
56350
56362
  message: `Invalid working_directory: path "${resolvedDir}" is not a directory`
56351
56363
  };
56352
56364
  }
56365
+ if (typeof fallbackDirectory !== "string" || fallbackDirectory === "") {
56366
+ return { success: true, directory: resolvedDir };
56367
+ }
56353
56368
  const resolvedFallback = path50.resolve(fallbackDirectory);
56354
56369
  let fallbackExists = false;
56355
56370
  try {
@@ -56358,23 +56373,14 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
56358
56373
  } catch {
56359
56374
  fallbackExists = false;
56360
56375
  }
56361
- if (workingDirectory != null && workingDirectory !== "") {
56362
- if (fallbackExists) {
56363
- const isSubdirectory = resolvedDir.startsWith(resolvedFallback + path50.sep);
56364
- if (isSubdirectory) {
56365
- return {
56366
- success: false,
56367
- message: `Invalid working_directory: "${workingDirectory}" resolves to "${resolvedDir}" ` + `which is a subdirectory of fallback "${resolvedFallback}". ` + `Pass the project root path or omit working_directory entirely.`
56368
- };
56369
- }
56376
+ if (fallbackExists) {
56377
+ const isSubdirectory = resolvedDir.startsWith(resolvedFallback + path50.sep);
56378
+ if (isSubdirectory) {
56379
+ return {
56380
+ success: false,
56381
+ message: `Invalid working_directory: "${workingDirectory}" resolves to "${resolvedDir}" ` + `which is a subdirectory of fallback "${resolvedFallback}". ` + `Pass the project root path or omit working_directory entirely.`
56382
+ };
56370
56383
  }
56371
- return { success: true, directory: resolvedDir };
56372
- }
56373
- if (resolvedDir !== resolvedFallback) {
56374
- return {
56375
- success: false,
56376
- message: `Invalid working_directory: path resolves to "${resolvedDir}" but fallbackDirectory ` + `"${resolvedFallback}" is not the project root. ` + `This may indicate CWD mismatch. Pass the project root path explicitly.`
56377
- };
56378
56384
  }
56379
56385
  return { success: true, directory: resolvedDir };
56380
56386
  }
package/dist/index.js CHANGED
@@ -69,7 +69,7 @@ var package_default;
69
69
  var init_package = __esm(() => {
70
70
  package_default = {
71
71
  name: "opencode-swarm",
72
- version: "7.66.0",
72
+ version: "7.66.2",
73
73
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
74
74
  main: "dist/index.js",
75
75
  types: "dist/index.d.ts",
@@ -80614,8 +80614,20 @@ import * as fs35 from "node:fs";
80614
80614
  import * as path67 from "node:path";
80615
80615
  function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
80616
80616
  if (workingDirectory == null || workingDirectory === "") {
80617
+ if (typeof fallbackDirectory !== "string" || fallbackDirectory === "") {
80618
+ return {
80619
+ success: false,
80620
+ message: "Invalid working_directory: no explicit working_directory was provided and fallbackDirectory is missing or not a string"
80621
+ };
80622
+ }
80617
80623
  return { success: true, directory: fallbackDirectory };
80618
80624
  }
80625
+ if (typeof workingDirectory !== "string") {
80626
+ return {
80627
+ success: false,
80628
+ message: "Invalid working_directory: path must be a string"
80629
+ };
80630
+ }
80619
80631
  if (workingDirectory.includes("\x00")) {
80620
80632
  return {
80621
80633
  success: false,
@@ -80631,14 +80643,14 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
80631
80643
  };
80632
80644
  }
80633
80645
  }
80634
- const normalizedDir = path67.normalize(workingDirectory);
80635
- const pathParts = normalizedDir.split(path67.sep);
80636
- if (pathParts.includes("..")) {
80646
+ const rawPathParts = workingDirectory.split(path67.sep);
80647
+ if (rawPathParts.includes("..")) {
80637
80648
  return {
80638
80649
  success: false,
80639
80650
  message: "Invalid working_directory: path traversal sequences (..) are not allowed"
80640
80651
  };
80641
80652
  }
80653
+ const normalizedDir = path67.normalize(workingDirectory);
80642
80654
  const resolvedDir = path67.resolve(normalizedDir);
80643
80655
  let statResult;
80644
80656
  try {
@@ -80655,6 +80667,9 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
80655
80667
  message: `Invalid working_directory: path "${resolvedDir}" is not a directory`
80656
80668
  };
80657
80669
  }
80670
+ if (typeof fallbackDirectory !== "string" || fallbackDirectory === "") {
80671
+ return { success: true, directory: resolvedDir };
80672
+ }
80658
80673
  const resolvedFallback = path67.resolve(fallbackDirectory);
80659
80674
  let fallbackExists = false;
80660
80675
  try {
@@ -80663,23 +80678,14 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
80663
80678
  } catch {
80664
80679
  fallbackExists = false;
80665
80680
  }
80666
- if (workingDirectory != null && workingDirectory !== "") {
80667
- if (fallbackExists) {
80668
- const isSubdirectory = resolvedDir.startsWith(resolvedFallback + path67.sep);
80669
- if (isSubdirectory) {
80670
- return {
80671
- success: false,
80672
- message: `Invalid working_directory: "${workingDirectory}" resolves to "${resolvedDir}" ` + `which is a subdirectory of fallback "${resolvedFallback}". ` + `Pass the project root path or omit working_directory entirely.`
80673
- };
80674
- }
80681
+ if (fallbackExists) {
80682
+ const isSubdirectory = resolvedDir.startsWith(resolvedFallback + path67.sep);
80683
+ if (isSubdirectory) {
80684
+ return {
80685
+ success: false,
80686
+ message: `Invalid working_directory: "${workingDirectory}" resolves to "${resolvedDir}" ` + `which is a subdirectory of fallback "${resolvedFallback}". ` + `Pass the project root path or omit working_directory entirely.`
80687
+ };
80675
80688
  }
80676
- return { success: true, directory: resolvedDir };
80677
- }
80678
- if (resolvedDir !== resolvedFallback) {
80679
- return {
80680
- success: false,
80681
- message: `Invalid working_directory: path resolves to "${resolvedDir}" but fallbackDirectory ` + `"${resolvedFallback}" is not the project root. ` + `This may indicate CWD mismatch. Pass the project root path explicitly.`
80682
- };
80683
80689
  }
80684
80690
  return { success: true, directory: resolvedDir };
80685
80691
  }
@@ -98007,7 +98013,7 @@ var init_design_doc_drift = __esm(() => {
98007
98013
  var exports_project_context = {};
98008
98014
  __export(exports_project_context, {
98009
98015
  buildProjectContext: () => buildProjectContext,
98010
- _internals: () => _internals90,
98016
+ _internals: () => _internals91,
98011
98017
  LANG_BACKEND_DETECTION_TIMEOUT_MS: () => LANG_BACKEND_DETECTION_TIMEOUT_MS
98012
98018
  });
98013
98019
  import * as fs135 from "node:fs";
@@ -98091,7 +98097,7 @@ function selectLintCommand(backend, directory) {
98091
98097
  return null;
98092
98098
  }
98093
98099
  async function buildProjectContext(directory) {
98094
- const backend = await _internals90.pickBackend(directory);
98100
+ const backend = await _internals91.pickBackend(directory);
98095
98101
  if (!backend)
98096
98102
  return null;
98097
98103
  const ctx = emptyProjectContext();
@@ -98130,17 +98136,17 @@ async function buildProjectContext(directory) {
98130
98136
  if (backend.prompts.reviewerChecklist.length > 0) {
98131
98137
  ctx.REVIEWER_CHECKLIST = bulletList(backend.prompts.reviewerChecklist);
98132
98138
  }
98133
- const profiles = _internals90.pickedProfiles(directory);
98139
+ const profiles = _internals91.pickedProfiles(directory);
98134
98140
  if (profiles.length > 1) {
98135
98141
  ctx.PROJECT_CONTEXT_SECONDARY_LANGUAGES = profiles.slice(1).map((p) => p.id).join(", ");
98136
98142
  }
98137
98143
  return ctx;
98138
98144
  }
98139
- var LANG_BACKEND_DETECTION_TIMEOUT_MS = 300, _internals90;
98145
+ var LANG_BACKEND_DETECTION_TIMEOUT_MS = 300, _internals91;
98140
98146
  var init_project_context = __esm(() => {
98141
98147
  init_dispatch();
98142
98148
  init_framework_detector();
98143
- _internals90 = {
98149
+ _internals91 = {
98144
98150
  pickBackend,
98145
98151
  pickedProfiles
98146
98152
  };
@@ -135965,6 +135971,11 @@ function verifyLeanTurboTaskCompletion(directory, taskId, sessionID) {
135965
135971
  init_task_id();
135966
135972
  init_create_tool();
135967
135973
  init_resolve_working_directory();
135974
+ var _internals89 = {
135975
+ tryAcquireLock,
135976
+ updateTaskStatus,
135977
+ resolveWorkingDirectory
135978
+ };
135968
135979
  var VALID_STATUSES2 = [
135969
135980
  "pending",
135970
135981
  "in_progress",
@@ -136057,7 +136068,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
136057
136068
  }
136058
136069
  let resolvedDir;
136059
136070
  if (fallbackDir) {
136060
- const resolveResult = resolveWorkingDirectory(workingDirectory, fallbackDir);
136071
+ const resolveResult = _internals89.resolveWorkingDirectory(workingDirectory, fallbackDir);
136061
136072
  if (resolveResult.success) {
136062
136073
  resolvedDir = resolveResult.directory;
136063
136074
  } else {
@@ -136404,14 +136415,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
136404
136415
  }
136405
136416
  }
136406
136417
  let directory;
136407
- if (!args2.working_directory && !fallbackDir) {
136408
- return {
136409
- success: false,
136410
- message: "No working_directory provided and fallbackDir is undefined",
136411
- errors: ["Cannot resolve directory for task status update"]
136412
- };
136413
- }
136414
- const resolveResult = resolveWorkingDirectory(args2.working_directory ?? fallbackDir, fallbackDir);
136418
+ const resolveResult = _internals89.resolveWorkingDirectory(args2.working_directory, fallbackDir);
136415
136419
  if (!resolveResult.success) {
136416
136420
  return {
136417
136421
  success: false,
@@ -136504,7 +136508,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
136504
136508
  }
136505
136509
  let lockResult;
136506
136510
  try {
136507
- lockResult = await tryAcquireLock(directory, planFilePath, agentName, lockTaskId);
136511
+ lockResult = await _internals89.tryAcquireLock(directory, planFilePath, agentName, lockTaskId);
136508
136512
  } catch (error93) {
136509
136513
  return {
136510
136514
  success: false,
@@ -136523,7 +136527,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
136523
136527
  };
136524
136528
  }
136525
136529
  try {
136526
- const updatedPlan = await updateTaskStatus(directory, args2.task_id, args2.status);
136530
+ const updatedPlan = await _internals89.updateTaskStatus(directory, args2.task_id, args2.status);
136527
136531
  if (args2.status === "completed") {
136528
136532
  for (const [_sessionId, session] of swarmState.agentSessions) {
136529
136533
  if (!(session.taskWorkflowStates instanceof Map)) {
@@ -136947,7 +136951,7 @@ var web_search = createSwarmTool({
136947
136951
  });
136948
136952
  async function captureSearchEvidence(directory, query, results) {
136949
136953
  try {
136950
- const written = await _internals89.writeEvidenceDocuments(directory, results.map((result) => ({
136954
+ const written = await _internals90.writeEvidenceDocuments(directory, results.map((result) => ({
136951
136955
  sourceType: "web_search",
136952
136956
  query,
136953
136957
  title: result.title,
@@ -136975,7 +136979,7 @@ async function captureSearchEvidence(directory, query, results) {
136975
136979
  };
136976
136980
  }
136977
136981
  }
136978
- var _internals89 = {
136982
+ var _internals90 = {
136979
136983
  writeEvidenceDocuments
136980
136984
  };
136981
136985
 
@@ -138029,22 +138033,22 @@ async function initializeOpenCodeSwarm(ctx) {
138029
138033
  const guardrailsFallback = config3.guardrails?.enabled === false ? { ...config3.guardrails, enabled: false } : config3.guardrails ?? {};
138030
138034
  const guardrailsConfig = GuardrailsConfigSchema.parse(guardrailsFallback);
138031
138035
  if (loadedFromFile && guardrailsConfig.enabled === false) {
138032
- console.warn("");
138033
- console.warn("══════════════════════════════════════════════════════════════");
138034
- console.warn("[opencode-swarm] \uD83D\uDD34 SECURITY WARNING: GUARDRAILS ARE DISABLED");
138035
- console.warn("══════════════════════════════════════════════════════════════");
138036
- console.warn("Guardrails have been explicitly disabled in user configuration.");
138037
- console.warn("This disables safety measures including:");
138038
- console.warn(" - Tool call limits");
138039
- console.warn(" - Duration limits");
138040
- console.warn(" - Repetition detection");
138041
- console.warn(" - Error rate limits");
138042
- console.warn(" - Idle timeouts");
138043
- console.warn("");
138044
- console.warn("Only disable guardrails if you fully understand the security implications.");
138045
- console.warn('To re-enable guardrails, set "guardrails.enabled" to true in your config.');
138046
- console.warn("══════════════════════════════════════════════════════════════");
138047
- console.warn("");
138036
+ warn("");
138037
+ warn("══════════════════════════════════════════════════════════════");
138038
+ warn("[opencode-swarm] \uD83D\uDD34 SECURITY WARNING: GUARDRAILS ARE DISABLED");
138039
+ warn("══════════════════════════════════════════════════════════════");
138040
+ warn("Guardrails have been explicitly disabled in user configuration.");
138041
+ warn("This disables safety measures including:");
138042
+ warn(" - Tool call limits");
138043
+ warn(" - Duration limits");
138044
+ warn(" - Repetition detection");
138045
+ warn(" - Error rate limits");
138046
+ warn(" - Idle timeouts");
138047
+ warn("");
138048
+ warn("Only disable guardrails if you fully understand the security implications.");
138049
+ warn('To re-enable guardrails, set "guardrails.enabled" to true in your config.');
138050
+ warn("══════════════════════════════════════════════════════════════");
138051
+ warn("");
138048
138052
  }
138049
138053
  const delegationHandler = createDelegationTrackerHook(config3, guardrailsConfig.enabled);
138050
138054
  const authorityConfig = AuthorityConfigSchema.parse(config3.authority ?? {});
@@ -39,4 +39,4 @@ export interface ResolveError {
39
39
  * @param workingDirectory - Explicit working_directory from tool args (caller-controlled)
40
40
  * @param fallbackDirectory - Injected directory from createSwarmTool (ctx.directory ?? process.cwd())
41
41
  */
42
- export declare function resolveWorkingDirectory(workingDirectory: string | undefined | null, fallbackDirectory: string): ResolveResult | ResolveError;
42
+ export declare function resolveWorkingDirectory(workingDirectory: string | undefined | null, fallbackDirectory?: string | null): ResolveResult | ResolveError;
@@ -3,6 +3,19 @@
3
3
  * Allows agents to mark tasks as pending, in_progress, completed, or blocked.
4
4
  */
5
5
  import type { ToolContext, ToolDefinition } from '@opencode-ai/plugin/tool';
6
+ import { tryAcquireLock } from '../parallel/file-locks.js';
7
+ import { updateTaskStatus } from '../plan/manager';
8
+ import { resolveWorkingDirectory } from './resolve-working-directory';
9
+ /**
10
+ * Internal seams for test injection.
11
+ * Tests should save/restore these in beforeEach/afterEach rather than using
12
+ * module-scope vi.mock, which leaks across files in Bun's shared test runner.
13
+ */
14
+ export declare const _internals: {
15
+ readonly tryAcquireLock: typeof tryAcquireLock;
16
+ readonly updateTaskStatus: typeof updateTaskStatus;
17
+ readonly resolveWorkingDirectory: typeof resolveWorkingDirectory;
18
+ };
6
19
  /**
7
20
  * Arguments for the update_task_status tool
8
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.66.0",
3
+ "version": "7.66.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",