opencode-swarm 7.81.0 → 7.81.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
@@ -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.81.0",
55
+ version: "7.81.1",
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",
@@ -17194,6 +17194,12 @@ async function handleAcknowledgeSpecDriftCommand(directory, _args, acknowledgedB
17194
17194
  try {
17195
17195
  const plan = await loadPlanJsonOnly(directory);
17196
17196
  if (plan?.specHash) {
17197
+ if (stalenessData.specHash_plan !== plan.specHash) {
17198
+ return `Spec drift acknowledgment rejected: The spec has changed since the staleness was detected.
17199
+ Current plan specHash: ${plan.specHash}
17200
+ Staleness file specHash: ${stalenessData.specHash_plan}
17201
+ Please re-run the relevant phase to detect current drift status.`;
17202
+ }
17197
17203
  currentHash = await computeSpecHash(directory);
17198
17204
  plan.specHash = currentHash ?? undefined;
17199
17205
  await savePlan(directory, plan);
@@ -21443,7 +21449,12 @@ async function tryAcquireLock(directory, filePath, agent, taskId) {
21443
21449
  try {
21444
21450
  release = await import_proper_lockfile.default.lock(lockPath, {
21445
21451
  stale: LOCK_TIMEOUT_MS,
21446
- retries: { retries: 0 },
21452
+ retries: {
21453
+ retries: 5,
21454
+ minTimeout: 10,
21455
+ maxTimeout: 500,
21456
+ factor: 2
21457
+ },
21447
21458
  realpath: false
21448
21459
  });
21449
21460
  } catch (err) {
@@ -5,5 +5,5 @@
5
5
  * read back during council evaluation.
6
6
  */
7
7
  import type { CouncilCriteria, CouncilCriteriaItem } from './types';
8
- export declare function writeCriteria(workingDir: string, taskId: string, criteria: CouncilCriteriaItem[]): void;
8
+ export declare function writeCriteria(workingDir: string, taskId: string, criteria: CouncilCriteriaItem[]): Promise<void>;
9
9
  export declare function readCriteria(workingDir: string, taskId: string): CouncilCriteria | null;
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.81.0",
72
+ version: "7.81.1",
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",
@@ -20253,6 +20253,12 @@ async function handleAcknowledgeSpecDriftCommand(directory, _args, acknowledgedB
20253
20253
  try {
20254
20254
  const plan = await loadPlanJsonOnly(directory);
20255
20255
  if (plan?.specHash) {
20256
+ if (stalenessData.specHash_plan !== plan.specHash) {
20257
+ return `Spec drift acknowledgment rejected: The spec has changed since the staleness was detected.
20258
+ Current plan specHash: ${plan.specHash}
20259
+ Staleness file specHash: ${stalenessData.specHash_plan}
20260
+ Please re-run the relevant phase to detect current drift status.`;
20261
+ }
20256
20262
  currentHash = await computeSpecHash(directory);
20257
20263
  plan.specHash = currentHash ?? undefined;
20258
20264
  await savePlan(directory, plan);
@@ -21956,7 +21962,12 @@ async function tryAcquireLock(directory, filePath, agent, taskId) {
21956
21962
  try {
21957
21963
  release = await import_proper_lockfile.default.lock(lockPath, {
21958
21964
  stale: LOCK_TIMEOUT_MS,
21959
- retries: { retries: 0 },
21965
+ retries: {
21966
+ retries: 5,
21967
+ minTimeout: 10,
21968
+ maxTimeout: 500,
21969
+ factor: 2
21970
+ },
21960
21971
  realpath: false
21961
21972
  });
21962
21973
  } catch (err2) {
@@ -124323,7 +124334,8 @@ function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFi
124323
124334
 
124324
124335
  // src/council/criteria-store.ts
124325
124336
  init_zod();
124326
- import { existsSync as existsSync82, mkdirSync as mkdirSync35, readFileSync as readFileSync56, writeFileSync as writeFileSync23 } from "node:fs";
124337
+ init_task_file();
124338
+ import { existsSync as existsSync82, mkdirSync as mkdirSync35, readFileSync as readFileSync56 } from "node:fs";
124327
124339
  import { join as join114 } from "node:path";
124328
124340
  var COUNCIL_DIR = ".swarm/council";
124329
124341
  var CouncilCriteriaSchema = exports_external.object({
@@ -124335,7 +124347,7 @@ var CouncilCriteriaSchema = exports_external.object({
124335
124347
  })),
124336
124348
  declaredAt: exports_external.string()
124337
124349
  });
124338
- function writeCriteria(workingDir, taskId, criteria) {
124350
+ async function writeCriteria(workingDir, taskId, criteria) {
124339
124351
  const dir = join114(workingDir, COUNCIL_DIR);
124340
124352
  mkdirSync35(dir, { recursive: true });
124341
124353
  const payload = {
@@ -124343,7 +124355,7 @@ function writeCriteria(workingDir, taskId, criteria) {
124343
124355
  criteria,
124344
124356
  declaredAt: new Date().toISOString()
124345
124357
  };
124346
- writeFileSync23(join114(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
124358
+ await atomicWriteFile(join114(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
124347
124359
  }
124348
124360
  function readCriteria(workingDir, taskId) {
124349
124361
  const filePath = join114(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
@@ -125232,7 +125244,7 @@ var declare_council_criteria = createSwarmTool({
125232
125244
  }
125233
125245
  const existing = readCriteria(workingDir, input.taskId);
125234
125246
  const replaced = existing !== null;
125235
- writeCriteria(workingDir, input.taskId, input.criteria);
125247
+ await writeCriteria(workingDir, input.taskId, input.criteria);
125236
125248
  return JSON.stringify({
125237
125249
  success: true,
125238
125250
  taskId: input.taskId,
@@ -133001,7 +133013,7 @@ import * as path157 from "node:path";
133001
133013
 
133002
133014
  // src/mutation/engine.ts
133003
133015
  import { spawnSync as spawnSync11 } from "node:child_process";
133004
- import { unlinkSync as unlinkSync19, writeFileSync as writeFileSync25 } from "node:fs";
133016
+ import { unlinkSync as unlinkSync19, writeFileSync as writeFileSync24 } from "node:fs";
133005
133017
  import * as path156 from "node:path";
133006
133018
 
133007
133019
  // src/mutation/equivalence.ts
@@ -133191,7 +133203,7 @@ async function executeMutation(patch, testCommand, testFiles, workingDir) {
133191
133203
  const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
133192
133204
  patchFile = path156.join(workingDir, `.mutation_patch_${safeId2}.diff`);
133193
133205
  try {
133194
- writeFileSync25(patchFile, patch.patch);
133206
+ writeFileSync24(patchFile, patch.patch);
133195
133207
  } catch (writeErr) {
133196
133208
  error93 = `Failed to write patch file: ${writeErr}`;
133197
133209
  outcome = "error";
@@ -133627,6 +133639,7 @@ init_zod();
133627
133639
  init_config();
133628
133640
  init_schema();
133629
133641
  init_manager2();
133642
+ init_task_file();
133630
133643
  import * as fs111 from "node:fs";
133631
133644
  import * as path167 from "node:path";
133632
133645
 
@@ -135821,6 +135834,39 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
135821
135834
  }
135822
135835
  warnings.push(`Knowledge sweep failed for phase ${phase}: ${detail}`);
135823
135836
  }
135837
+ const planLockTaskId = `phase-complete-plan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
135838
+ const planFilePath = "plan.json";
135839
+ let planLockResult;
135840
+ try {
135841
+ planLockResult = await tryAcquireLock(dir, planFilePath, agentName, planLockTaskId);
135842
+ } catch (error93) {
135843
+ return JSON.stringify({
135844
+ success: false,
135845
+ phase: args2.phase,
135846
+ status: "incomplete",
135847
+ message: `Failed to acquire lock for plan.json: ${error93 instanceof Error ? error93.message : String(error93)}`,
135848
+ agentsDispatched,
135849
+ agentsMissing,
135850
+ warnings,
135851
+ errors: [error93 instanceof Error ? error93.message : String(error93)],
135852
+ recovery_guidance: "Resolve the filesystem issue (permissions, disk space, or .swarm/locks/ directory) and retry phase_complete."
135853
+ });
135854
+ }
135855
+ if (!planLockResult?.acquired) {
135856
+ return JSON.stringify({
135857
+ success: false,
135858
+ phase: args2.phase,
135859
+ status: "incomplete",
135860
+ message: `Plan write blocked: plan.json is locked by ${planLockResult.existing?.agent ?? "another agent"}`,
135861
+ agentsDispatched,
135862
+ agentsMissing,
135863
+ warnings,
135864
+ errors: [
135865
+ `Concurrent plan write detected — lock held by ${planLockResult.existing?.agent ?? "another agent"} (task: ${planLockResult.existing?.taskId ?? "unknown"})`
135866
+ ],
135867
+ recovery_guidance: "Wait a moment and retry phase_complete. The lock will expire automatically if the holding agent fails."
135868
+ });
135869
+ }
135824
135870
  try {
135825
135871
  const plan = await loadPlan(dir);
135826
135872
  if (plan === null) {
@@ -135849,7 +135895,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
135849
135895
  const phaseObj = plan2.phases.find((p) => p.id === phase);
135850
135896
  if (phaseObj) {
135851
135897
  phaseObj.status = "complete";
135852
- fs111.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
135898
+ await atomicWriteFile(planPath, JSON.stringify(plan2, null, 2));
135853
135899
  }
135854
135900
  } catch {}
135855
135901
  } else if (plan) {
@@ -135891,9 +135937,17 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
135891
135937
  const phaseObj = plan.phases.find((p) => p.id === phase);
135892
135938
  if (phaseObj) {
135893
135939
  phaseObj.status = "complete";
135894
- fs111.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
135940
+ await atomicWriteFile(planPath, JSON.stringify(plan, null, 2));
135895
135941
  }
135896
135942
  } catch {}
135943
+ } finally {
135944
+ if (planLockResult?.acquired && planLockResult.lock._release) {
135945
+ try {
135946
+ await planLockResult.lock._release();
135947
+ } catch (releaseError) {
135948
+ warn("[phase_complete] Plan lock release failed (non-blocking):", releaseError instanceof Error ? releaseError.message : String(releaseError));
135949
+ }
135950
+ }
135897
135951
  }
135898
135952
  }
135899
135953
  if (complianceWarnings.length > 0) {
@@ -143588,7 +143642,7 @@ import {
143588
143642
  readFileSync as readFileSync81,
143589
143643
  renameSync as renameSync24,
143590
143644
  unlinkSync as unlinkSync21,
143591
- writeFileSync as writeFileSync31
143645
+ writeFileSync as writeFileSync30
143592
143646
  } from "node:fs";
143593
143647
  import path181 from "node:path";
143594
143648
  init_create_tool();
@@ -143855,7 +143909,7 @@ function writePhaseCouncilEvidence(workingDir, synthesis, provenance) {
143855
143909
  };
143856
143910
  const tempFile = `${evidenceFile}.tmp-${Date.now()}`;
143857
143911
  try {
143858
- writeFileSync31(tempFile, JSON.stringify(evidenceBundle, null, 2), "utf-8");
143912
+ writeFileSync30(tempFile, JSON.stringify(evidenceBundle, null, 2), "utf-8");
143859
143913
  renameSync24(tempFile, evidenceFile);
143860
143914
  } finally {
143861
143915
  if (existsSync103(tempFile)) {
@@ -82,6 +82,12 @@ declare function getPlanJsonPath(directory: string): string;
82
82
  * Compute a SHA-256 hash of the plan state.
83
83
  * Uses deterministic JSON serialization for consistent hashing.
84
84
  *
85
+ * IMPORTANT: Intentionally excludes `specMtime` and `specHash` fields.
86
+ * These fields track changes to spec.md but do not affect plan execution or structure.
87
+ * Including them would cause the plan hash to change whenever spec metadata changes,
88
+ * invalidating cached plan state unnecessarily. Spec changes are tracked separately
89
+ * in the ledger via `spec_updated` and acknowledgment events.
90
+ *
85
91
  * @param plan - The plan to hash
86
92
  * @returns Hex-encoded SHA-256 hash
87
93
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.81.0",
3
+ "version": "7.81.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",