opencode-swarm 7.21.0 → 7.21.1

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.21.0",
37
+ version: "7.21.1",
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",
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.21.0",
36
+ version: "7.21.1",
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",
@@ -90226,6 +90226,7 @@ function phaseIsExplicitlyNonCode(directory, phase) {
90226
90226
  }
90227
90227
 
90228
90228
  // src/tools/phase-complete.ts
90229
+ init_gate_evidence();
90229
90230
  init_curator();
90230
90231
  init_knowledge_curator();
90231
90232
  init_knowledge_reader();
@@ -90761,6 +90762,23 @@ function safeWarn(message, error93) {
90761
90762
  warn(message, error93 instanceof Error ? error93.message : String(error93));
90762
90763
  } catch {}
90763
90764
  }
90765
+ var TASK_GATE_INFERABLE_AGENTS = new Set([
90766
+ "coder",
90767
+ "reviewer",
90768
+ "test_engineer"
90769
+ ]);
90770
+ function canInferMissingAgentsFromTaskGates(agentsMissing) {
90771
+ return agentsMissing.every((agent) => TASK_GATE_INFERABLE_AGENTS.has(agent));
90772
+ }
90773
+ async function allCompletedTasksHavePassedGateEvidence(directory, tasks) {
90774
+ for (const task of tasks) {
90775
+ if (task.status !== "completed")
90776
+ return false;
90777
+ if (!await hasPassedAllGates(directory, task.id))
90778
+ return false;
90779
+ }
90780
+ return tasks.length > 0;
90781
+ }
90764
90782
  function collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, callerSessionId) {
90765
90783
  const agents = new Set;
90766
90784
  const contributorSessionIds = [];
@@ -90772,12 +90790,9 @@ function collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, callerSess
90772
90790
  agents.add(agent);
90773
90791
  }
90774
90792
  }
90775
- const callerDelegations = swarmState.delegationChains.get(callerSessionId);
90776
- if (callerDelegations) {
90777
- for (const delegation of callerDelegations) {
90778
- agents.add(stripKnownSwarmPrefix(delegation.from));
90779
- agents.add(stripKnownSwarmPrefix(delegation.to));
90780
- }
90793
+ for (const delegation of _getDelegationsSince(callerSessionId, phaseReferenceTimestamp)) {
90794
+ agents.add(stripKnownSwarmPrefix(delegation.from));
90795
+ agents.add(stripKnownSwarmPrefix(delegation.to));
90781
90796
  }
90782
90797
  }
90783
90798
  for (const [sessionId, session] of swarmState.agentSessions) {
@@ -90796,17 +90811,24 @@ function collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, callerSess
90796
90811
  agents.add(agent);
90797
90812
  }
90798
90813
  }
90799
- const delegations2 = swarmState.delegationChains.get(sessionId);
90800
- if (delegations2) {
90801
- for (const delegation of delegations2) {
90802
- agents.add(stripKnownSwarmPrefix(delegation.from));
90803
- agents.add(stripKnownSwarmPrefix(delegation.to));
90804
- }
90814
+ for (const delegation of _getDelegationsSince(sessionId, phaseReferenceTimestamp)) {
90815
+ agents.add(stripKnownSwarmPrefix(delegation.from));
90816
+ agents.add(stripKnownSwarmPrefix(delegation.to));
90805
90817
  }
90806
90818
  }
90807
90819
  }
90808
90820
  return { agents, contributorSessionIds };
90809
90821
  }
90822
+ function _getDelegationsSince(sessionID, sinceTimestamp) {
90823
+ const chain = swarmState.delegationChains.get(sessionID);
90824
+ if (!chain) {
90825
+ return [];
90826
+ }
90827
+ if (sinceTimestamp === 0) {
90828
+ return chain;
90829
+ }
90830
+ return chain.filter((entry) => entry.timestamp > sinceTimestamp);
90831
+ }
90810
90832
  function isValidRetroEntry(entry, phase) {
90811
90833
  return entry.type === "retrospective" && "phase_number" in entry && entry.phase_number === phase && "verdict" in entry && entry.verdict === "pass";
90812
90834
  }
@@ -91752,8 +91774,8 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
91752
91774
  const planRaw = fs84.readFileSync(planPath, "utf-8");
91753
91775
  const plan = JSON.parse(planRaw);
91754
91776
  const targetPhase = plan.phases.find((p) => p.id === phase);
91755
- if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
91756
- warnings.push(`Agent dispatch fallback: all ${targetPhase.tasks.length} tasks in phase ${phase} are completed in plan.json. Clearing missing agents: ${agentsMissing.join(", ")}.`);
91777
+ if (targetPhase && targetPhase.tasks.length > 0 && canInferMissingAgentsFromTaskGates(agentsMissing) && await allCompletedTasksHavePassedGateEvidence(dir, targetPhase.tasks)) {
91778
+ warnings.push(`Agent dispatch fallback: all ${targetPhase.tasks.length} tasks in phase ${phase} are completed in plan.json and durable gate evidence passed. Clearing missing agents: ${agentsMissing.join(", ")}.`);
91757
91779
  agentsMissing = [];
91758
91780
  }
91759
91781
  } catch {}
@@ -103343,6 +103365,13 @@ function matchesTier3Pattern2(files) {
103343
103365
  }
103344
103366
  return false;
103345
103367
  }
103368
+ function hasPassedDurableGateEvidence(workingDirectory, taskId) {
103369
+ const evidence = readTaskEvidenceRaw(workingDirectory, taskId);
103370
+ if (!evidence || !Array.isArray(evidence.required_gates) || evidence.required_gates.length === 0) {
103371
+ return false;
103372
+ }
103373
+ return evidence.required_gates.every((gate) => evidence.gates?.[gate] != null);
103374
+ }
103346
103375
  function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = false, sessionID) {
103347
103376
  try {
103348
103377
  let skipStandardTurboBypass = false;
@@ -103389,12 +103418,11 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
103389
103418
  try {
103390
103419
  const evidence = readTaskEvidenceRaw(resolvedDir, taskId);
103391
103420
  if (evidence === null) {} else if (evidence.required_gates && Array.isArray(evidence.required_gates) && evidence.gates) {
103392
- const allGatesMet = evidence.required_gates.every((gate) => evidence.gates[gate] != null);
103393
- if (allGatesMet) {
103421
+ if (evidence.required_gates.length > 0 && evidence.required_gates.every((gate) => evidence.gates[gate] != null)) {
103394
103422
  return { blocked: false, reason: "" };
103395
103423
  }
103396
103424
  const missingGates = evidence.required_gates.filter((gate) => evidence.gates[gate] == null);
103397
- evidenceIncompleteReason = `Task ${taskId} is missing required gates: [${missingGates.join(", ")}]. ` + `Required: [${evidence.required_gates.join(", ")}]. ` + `Completed: [${Object.keys(evidence.gates).join(", ")}]. ` + `Delegate the missing gate agents before marking task as completed.`;
103425
+ evidenceIncompleteReason = evidence.required_gates.length === 0 ? `Task ${taskId} has an evidence file with no required gates. Delegate reviewer and test_engineer before marking task as completed.` : `Task ${taskId} is missing required gates: [${missingGates.join(", ")}]. ` + `Required: [${evidence.required_gates.join(", ")}]. ` + `Completed: [${Object.keys(evidence.gates).join(", ")}]. ` + `Delegate the missing gate agents before marking task as completed.`;
103398
103426
  }
103399
103427
  } catch (error93) {
103400
103428
  console.warn(`[gate-evidence] Evidence file for task ${taskId} is corrupt or unreadable:`, error93 instanceof Error ? error93.message : String(error93));
@@ -103404,7 +103432,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
103404
103432
  reason: `Evidence file for task ${taskId} is corrupt or unreadable. ` + `Fix the file at .swarm/evidence/${taskId}.json or delete it to fall through to session state.`
103405
103433
  };
103406
103434
  }
103407
- if (swarmState.agentSessions.size === 0) {
103435
+ if (swarmState.agentSessions.size === 0 && !evidenceIncompleteReason) {
103408
103436
  return { blocked: false, reason: "" };
103409
103437
  }
103410
103438
  let validSessionCount = 0;
@@ -103421,7 +103449,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
103421
103449
  return { blocked: false, reason: "" };
103422
103450
  }
103423
103451
  }
103424
- if (validSessionCount === 0) {
103452
+ if (validSessionCount === 0 && !evidenceIncompleteReason) {
103425
103453
  return { blocked: false, reason: "" };
103426
103454
  }
103427
103455
  const stateEntries = [];
@@ -103438,7 +103466,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
103438
103466
  const plan = JSON.parse(planRaw);
103439
103467
  for (const planPhase of plan.phases ?? []) {
103440
103468
  for (const task of planPhase.tasks ?? []) {
103441
- if (task.id === taskId && task.status === "completed") {
103469
+ if (task.id === taskId && task.status === "completed" && hasPassedDurableGateEvidence(resolvedDir2, taskId)) {
103442
103470
  return { blocked: false, reason: "" };
103443
103471
  }
103444
103472
  }
@@ -103459,26 +103487,6 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
103459
103487
  }
103460
103488
  }
103461
103489
  }
103462
- if (!hasReviewer && !hasTestEngineer) {
103463
- for (const [, chain] of swarmState.delegationChains) {
103464
- let lastCoderIndex = -1;
103465
- for (let i2 = chain.length - 1;i2 >= 0; i2--) {
103466
- const target = stripKnownSwarmPrefix(chain[i2].to);
103467
- if (target === "coder") {
103468
- lastCoderIndex = i2;
103469
- break;
103470
- }
103471
- }
103472
- const searchStart = lastCoderIndex === -1 ? 0 : lastCoderIndex + 1;
103473
- for (let i2 = searchStart;i2 < chain.length; i2++) {
103474
- const target = stripKnownSwarmPrefix(chain[i2].to);
103475
- if (target === "reviewer")
103476
- hasReviewer = true;
103477
- if (target === "test_engineer")
103478
- hasTestEngineer = true;
103479
- }
103480
- }
103481
- }
103482
103490
  if (hasReviewer && hasTestEngineer) {
103483
103491
  return { blocked: false, reason: "" };
103484
103492
  }
@@ -103521,26 +103529,6 @@ function recoverTaskStateFromDelegations(taskId) {
103521
103529
  }
103522
103530
  }
103523
103531
  }
103524
- if (!hasReviewer && !hasTestEngineer) {
103525
- for (const [, chain] of swarmState.delegationChains) {
103526
- let lastCoderIndex = -1;
103527
- for (let i2 = chain.length - 1;i2 >= 0; i2--) {
103528
- const target = stripKnownSwarmPrefix(chain[i2].to);
103529
- if (target === "coder") {
103530
- lastCoderIndex = i2;
103531
- break;
103532
- }
103533
- }
103534
- const searchStart = lastCoderIndex === -1 ? 0 : lastCoderIndex + 1;
103535
- for (let i2 = searchStart;i2 < chain.length; i2++) {
103536
- const target = stripKnownSwarmPrefix(chain[i2].to);
103537
- if (target === "reviewer")
103538
- hasReviewer = true;
103539
- if (target === "test_engineer")
103540
- hasTestEngineer = true;
103541
- }
103542
- }
103543
- }
103544
103532
  if (!hasReviewer && !hasTestEngineer)
103545
103533
  return;
103546
103534
  for (const [, session] of swarmState.agentSessions) {
@@ -66,11 +66,10 @@ export declare function checkReviewerGate(taskId: string, workingDirectory?: str
66
66
  export declare function checkReviewerGateWithScope(taskId: string, workingDirectory?: string, sessionID?: string): Promise<ReviewerGateResult>;
67
67
  /**
68
68
  * Recovery mechanism: reconcile task state with delegation history.
69
- * When reviewer/test_engineer delegations occurred but the state machine
70
- * was not advanced (e.g., toolAfter didn't fire, subagent_type missing,
71
- * cross-session gaps, or pure verification tasks without coder delegation),
72
- * this function walks all delegation chains and advances the task state
73
- * so that checkReviewerGate can make an accurate decision.
69
+ * When task-scoped reviewer/test_engineer delegations occurred but the state
70
+ * machine was not advanced (e.g., toolAfter didn't fire or subagent_type was
71
+ * missing), this function advances the task state so that checkReviewerGate can
72
+ * make an accurate decision without attributing unrelated delegation activity.
74
73
  *
75
74
  * @param taskId - The task ID to recover state for
76
75
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.21.0",
3
+ "version": "7.21.1",
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",