opencode-swarm 6.86.3 → 6.86.5

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
@@ -14230,11 +14230,17 @@ async function readLedgerEvents(directory) {
14230
14230
  const lines = content.trim().split(`
14231
14231
  `).filter((line) => line.trim() !== "");
14232
14232
  const events = [];
14233
+ let skippedCount = 0;
14233
14234
  for (const line of lines) {
14234
14235
  try {
14235
14236
  const event = JSON.parse(line);
14236
14237
  events.push(event);
14237
- } catch {}
14238
+ } catch {
14239
+ skippedCount++;
14240
+ }
14241
+ }
14242
+ if (skippedCount > 0) {
14243
+ console.warn(`[ledger] Skipped ${skippedCount} malformed line(s) in plan-ledger.jsonl`);
14238
14244
  }
14239
14245
  events.sort((a, b) => a.seq - b.seq);
14240
14246
  return events;
@@ -14331,10 +14337,13 @@ async function takeSnapshotEvent(directory, plan, options) {
14331
14337
  }, { planHashAfter: options?.planHashAfter });
14332
14338
  }
14333
14339
  async function replayFromLedger(directory, _options) {
14334
- const events = await readLedgerEvents(directory);
14340
+ const { events, truncated, badSuffix } = await readLedgerEventsWithIntegrity(directory);
14335
14341
  if (events.length === 0) {
14336
14342
  return null;
14337
14343
  }
14344
+ if (truncated && badSuffix !== null) {
14345
+ await quarantineLedgerSuffix(directory, badSuffix);
14346
+ }
14338
14347
  const targetPlanId = events[0].plan_id;
14339
14348
  const relevantEvents = events.filter((e) => e.plan_id === targetPlanId);
14340
14349
  {
@@ -14455,6 +14464,46 @@ function applyEventToPlan(plan, event) {
14455
14464
  throw new Error(`applyEventToPlan: unhandled event type "${event.event_type}" at seq ${event.seq}`);
14456
14465
  }
14457
14466
  }
14467
+ async function readLedgerEventsWithIntegrity(directory) {
14468
+ const ledgerPath = getLedgerPath(directory);
14469
+ if (!fs.existsSync(ledgerPath)) {
14470
+ return { events: [], truncated: false, badSuffix: null };
14471
+ }
14472
+ try {
14473
+ const content = fs.readFileSync(ledgerPath, "utf8");
14474
+ const lines = content.split(`
14475
+ `);
14476
+ const events = [];
14477
+ let truncated = false;
14478
+ let badSuffix = null;
14479
+ for (let i = 0;i < lines.length; i++) {
14480
+ const line = lines[i];
14481
+ if (line.trim() === "") {
14482
+ continue;
14483
+ }
14484
+ try {
14485
+ const event = JSON.parse(line);
14486
+ events.push(event);
14487
+ } catch {
14488
+ truncated = true;
14489
+ badSuffix = lines.slice(i).join(`
14490
+ `);
14491
+ break;
14492
+ }
14493
+ }
14494
+ events.sort((a, b) => a.seq - b.seq);
14495
+ return { events, truncated, badSuffix };
14496
+ } catch {
14497
+ return { events: [], truncated: false, badSuffix: null };
14498
+ }
14499
+ }
14500
+ async function quarantineLedgerSuffix(directory, badSuffix) {
14501
+ try {
14502
+ const quarantinePath = path2.join(directory, ".swarm", "plan-ledger.quarantine");
14503
+ fs.writeFileSync(quarantinePath, badSuffix, "utf8");
14504
+ console.warn(`[ledger] Corrupted suffix quarantined to ${path2.relative(directory, quarantinePath)}`);
14505
+ } catch {}
14506
+ }
14458
14507
  async function loadLastApprovedPlan(directory, expectedPlanId) {
14459
14508
  const events = await readLedgerEvents(directory);
14460
14509
  if (events.length === 0) {
@@ -44379,7 +44428,9 @@ ${error93 instanceof Error ? error93.message : String(error93)}`;
44379
44428
  }
44380
44429
 
44381
44430
  // src/commands/rollback.ts
44431
+ init_plan_schema();
44382
44432
  init_utils2();
44433
+ init_ledger();
44383
44434
  import * as fs21 from "fs";
44384
44435
  import * as path32 from "path";
44385
44436
  async function handleRollbackCommand(directory, args) {
@@ -44436,9 +44487,16 @@ async function handleRollbackCommand(directory, args) {
44436
44487
  return `Error: Checkpoint for phase ${targetPhase} is empty. Cannot rollback.`;
44437
44488
  }
44438
44489
  const swarmDir = validateSwarmPath(directory, "");
44490
+ const EXCLUDE_FILES = new Set([
44491
+ "plan-ledger.jsonl",
44492
+ "plan-ledger.quarantine"
44493
+ ]);
44439
44494
  const successes = [];
44440
44495
  const failures = [];
44441
44496
  for (const file3 of checkpointFiles) {
44497
+ if (EXCLUDE_FILES.has(file3) || file3.startsWith("plan-ledger.archived-")) {
44498
+ continue;
44499
+ }
44442
44500
  const src = path32.join(checkpointDir, file3);
44443
44501
  const dest = path32.join(swarmDir, file3);
44444
44502
  try {
@@ -44449,7 +44507,41 @@ async function handleRollbackCommand(directory, args) {
44449
44507
  }
44450
44508
  }
44451
44509
  if (failures.length > 0) {
44452
- return `Rollback partially completed. Successfully restored ${successes.length} files: ${successes.join(", ") || "none"}. Failed on ${failures.length} files: ${failures.map((f) => f.file).join(", ")}. Check permissions and disk space.`;
44510
+ return [
44511
+ `Rollback partially completed. Successfully restored ${successes.length} files.`,
44512
+ `Failed on ${failures.length} files:`,
44513
+ ...failures.map((f) => ` - ${f.file}: ${f.error}`),
44514
+ "",
44515
+ "Some files could not be restored. The .swarm/ directory may be in an inconsistent state.",
44516
+ "Check permissions and disk space, then retry the rollback."
44517
+ ].join(`
44518
+ `);
44519
+ }
44520
+ const existingLedgerPath = path32.join(swarmDir, "plan-ledger.jsonl");
44521
+ if (fs21.existsSync(existingLedgerPath)) {
44522
+ fs21.unlinkSync(existingLedgerPath);
44523
+ }
44524
+ try {
44525
+ const planJsonPath = path32.join(swarmDir, "plan.json");
44526
+ if (fs21.existsSync(planJsonPath)) {
44527
+ const planRaw = fs21.readFileSync(planJsonPath, "utf-8");
44528
+ const plan = PlanSchema.parse(JSON.parse(planRaw));
44529
+ const planId = `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
44530
+ const planHash = computePlanHash(plan);
44531
+ await initLedger(directory, planId, planHash, plan);
44532
+ await appendLedgerEvent(directory, {
44533
+ event_type: "plan_rebuilt",
44534
+ source: "rollback",
44535
+ plan_id: planId
44536
+ });
44537
+ }
44538
+ } catch (initError) {
44539
+ return [
44540
+ `Rollback restored files but failed to initialize ledger: ${initError instanceof Error ? initError.message : String(initError)}`,
44541
+ "The .swarm/plan.json has been restored but the ledger may be out of sync.",
44542
+ "Run /swarm reset-session to reinitialize the ledger."
44543
+ ].join(`
44544
+ `);
44453
44545
  }
44454
44546
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
44455
44547
  const rollbackEvent = {
package/dist/index.js CHANGED
@@ -16158,11 +16158,17 @@ async function readLedgerEvents(directory) {
16158
16158
  const lines = content.trim().split(`
16159
16159
  `).filter((line) => line.trim() !== "");
16160
16160
  const events = [];
16161
+ let skippedCount = 0;
16161
16162
  for (const line of lines) {
16162
16163
  try {
16163
16164
  const event = JSON.parse(line);
16164
16165
  events.push(event);
16165
- } catch {}
16166
+ } catch {
16167
+ skippedCount++;
16168
+ }
16169
+ }
16170
+ if (skippedCount > 0) {
16171
+ console.warn(`[ledger] Skipped ${skippedCount} malformed line(s) in plan-ledger.jsonl`);
16166
16172
  }
16167
16173
  events.sort((a, b) => a.seq - b.seq);
16168
16174
  return events;
@@ -16259,10 +16265,13 @@ async function takeSnapshotEvent(directory, plan, options) {
16259
16265
  }, { planHashAfter: options?.planHashAfter });
16260
16266
  }
16261
16267
  async function replayFromLedger(directory, _options) {
16262
- const events = await readLedgerEvents(directory);
16268
+ const { events, truncated, badSuffix } = await readLedgerEventsWithIntegrity(directory);
16263
16269
  if (events.length === 0) {
16264
16270
  return null;
16265
16271
  }
16272
+ if (truncated && badSuffix !== null) {
16273
+ await quarantineLedgerSuffix(directory, badSuffix);
16274
+ }
16266
16275
  const targetPlanId = events[0].plan_id;
16267
16276
  const relevantEvents = events.filter((e) => e.plan_id === targetPlanId);
16268
16277
  {
@@ -16383,6 +16392,46 @@ function applyEventToPlan(plan, event) {
16383
16392
  throw new Error(`applyEventToPlan: unhandled event type "${event.event_type}" at seq ${event.seq}`);
16384
16393
  }
16385
16394
  }
16395
+ async function readLedgerEventsWithIntegrity(directory) {
16396
+ const ledgerPath = getLedgerPath(directory);
16397
+ if (!fs4.existsSync(ledgerPath)) {
16398
+ return { events: [], truncated: false, badSuffix: null };
16399
+ }
16400
+ try {
16401
+ const content = fs4.readFileSync(ledgerPath, "utf8");
16402
+ const lines = content.split(`
16403
+ `);
16404
+ const events = [];
16405
+ let truncated = false;
16406
+ let badSuffix = null;
16407
+ for (let i2 = 0;i2 < lines.length; i2++) {
16408
+ const line = lines[i2];
16409
+ if (line.trim() === "") {
16410
+ continue;
16411
+ }
16412
+ try {
16413
+ const event = JSON.parse(line);
16414
+ events.push(event);
16415
+ } catch {
16416
+ truncated = true;
16417
+ badSuffix = lines.slice(i2).join(`
16418
+ `);
16419
+ break;
16420
+ }
16421
+ }
16422
+ events.sort((a, b) => a.seq - b.seq);
16423
+ return { events, truncated, badSuffix };
16424
+ } catch {
16425
+ return { events: [], truncated: false, badSuffix: null };
16426
+ }
16427
+ }
16428
+ async function quarantineLedgerSuffix(directory, badSuffix) {
16429
+ try {
16430
+ const quarantinePath = path4.join(directory, ".swarm", "plan-ledger.quarantine");
16431
+ fs4.writeFileSync(quarantinePath, badSuffix, "utf8");
16432
+ console.warn(`[ledger] Corrupted suffix quarantined to ${path4.relative(directory, quarantinePath)}`);
16433
+ } catch {}
16434
+ }
16386
16435
  async function loadLastApprovedPlan(directory, expectedPlanId) {
16387
16436
  const events = await readLedgerEvents(directory);
16388
16437
  if (events.length === 0) {
@@ -53249,9 +53298,16 @@ async function handleRollbackCommand(directory, args2) {
53249
53298
  return `Error: Checkpoint for phase ${targetPhase} is empty. Cannot rollback.`;
53250
53299
  }
53251
53300
  const swarmDir = validateSwarmPath(directory, "");
53301
+ const EXCLUDE_FILES = new Set([
53302
+ "plan-ledger.jsonl",
53303
+ "plan-ledger.quarantine"
53304
+ ]);
53252
53305
  const successes = [];
53253
53306
  const failures = [];
53254
53307
  for (const file3 of checkpointFiles) {
53308
+ if (EXCLUDE_FILES.has(file3) || file3.startsWith("plan-ledger.archived-")) {
53309
+ continue;
53310
+ }
53255
53311
  const src = path40.join(checkpointDir, file3);
53256
53312
  const dest = path40.join(swarmDir, file3);
53257
53313
  try {
@@ -53262,7 +53318,41 @@ async function handleRollbackCommand(directory, args2) {
53262
53318
  }
53263
53319
  }
53264
53320
  if (failures.length > 0) {
53265
- return `Rollback partially completed. Successfully restored ${successes.length} files: ${successes.join(", ") || "none"}. Failed on ${failures.length} files: ${failures.map((f) => f.file).join(", ")}. Check permissions and disk space.`;
53321
+ return [
53322
+ `Rollback partially completed. Successfully restored ${successes.length} files.`,
53323
+ `Failed on ${failures.length} files:`,
53324
+ ...failures.map((f) => ` - ${f.file}: ${f.error}`),
53325
+ "",
53326
+ "Some files could not be restored. The .swarm/ directory may be in an inconsistent state.",
53327
+ "Check permissions and disk space, then retry the rollback."
53328
+ ].join(`
53329
+ `);
53330
+ }
53331
+ const existingLedgerPath = path40.join(swarmDir, "plan-ledger.jsonl");
53332
+ if (fs28.existsSync(existingLedgerPath)) {
53333
+ fs28.unlinkSync(existingLedgerPath);
53334
+ }
53335
+ try {
53336
+ const planJsonPath = path40.join(swarmDir, "plan.json");
53337
+ if (fs28.existsSync(planJsonPath)) {
53338
+ const planRaw = fs28.readFileSync(planJsonPath, "utf-8");
53339
+ const plan = PlanSchema.parse(JSON.parse(planRaw));
53340
+ const planId = `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
53341
+ const planHash = computePlanHash(plan);
53342
+ await initLedger(directory, planId, planHash, plan);
53343
+ await appendLedgerEvent(directory, {
53344
+ event_type: "plan_rebuilt",
53345
+ source: "rollback",
53346
+ plan_id: planId
53347
+ });
53348
+ }
53349
+ } catch (initError) {
53350
+ return [
53351
+ `Rollback restored files but failed to initialize ledger: ${initError instanceof Error ? initError.message : String(initError)}`,
53352
+ "The .swarm/plan.json has been restored but the ledger may be out of sync.",
53353
+ "Run /swarm reset-session to reinitialize the ledger."
53354
+ ].join(`
53355
+ `);
53266
53356
  }
53267
53357
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
53268
53358
  const rollbackEvent = {
@@ -53280,7 +53370,9 @@ async function handleRollbackCommand(directory, args2) {
53280
53370
  return `Rolled back to phase ${targetPhase}: ${checkpoint2.label || "no label"}`;
53281
53371
  }
53282
53372
  var init_rollback = __esm(() => {
53373
+ init_plan_schema();
53283
53374
  init_utils2();
53375
+ init_ledger();
53284
53376
  });
53285
53377
 
53286
53378
  // src/commands/simulate.ts
@@ -63247,7 +63339,7 @@ init_schema();
63247
63339
  init_state();
63248
63340
  init_utils();
63249
63341
  init_utils2();
63250
- import { renameSync as renameSync11, unlinkSync as unlinkSync7 } from "fs";
63342
+ import { renameSync as renameSync11, unlinkSync as unlinkSync8 } from "fs";
63251
63343
  import * as nodePath2 from "path";
63252
63344
  function createAgentActivityHooks(config3, directory) {
63253
63345
  if (config3.hooks?.agent_activity === false) {
@@ -63324,7 +63416,7 @@ async function doFlush(directory) {
63324
63416
  renameSync11(tempPath, path46);
63325
63417
  } catch (writeError) {
63326
63418
  try {
63327
- unlinkSync7(tempPath);
63419
+ unlinkSync8(tempPath);
63328
63420
  } catch {}
63329
63421
  throw writeError;
63330
63422
  }
@@ -85525,6 +85617,17 @@ init_state();
85525
85617
  function slugify2(str) {
85526
85618
  return str.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_");
85527
85619
  }
85620
+ function extractJsonArray(text) {
85621
+ const trimmed = text.trim();
85622
+ const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
85623
+ if (fenceMatch)
85624
+ return fenceMatch[1].trim();
85625
+ const start2 = trimmed.search(/\[\s*[{["0-9\]tfn]/);
85626
+ const end = trimmed.lastIndexOf("]");
85627
+ if (start2 !== -1 && end > start2)
85628
+ return trimmed.slice(start2, end + 1);
85629
+ return trimmed;
85630
+ }
85528
85631
  async function generateMutants(files, ctx) {
85529
85632
  if (!ctx) {
85530
85633
  console.warn("[generateMutants] No ToolContext \u2014 cannot call LLM; returning empty patch set");
@@ -85569,9 +85672,9 @@ Return a JSON array where each element has:
85569
85672
  - id: unique string like "mut-001"
85570
85673
  - mutationType: one of: ${mutationTypes}
85571
85674
  - patch: unified diff format (--- a/file\\n+++ a/file\\n@@ ... @@\\n-old\\n+new)
85572
- - Generate 5-10 mutations per function
85675
+ - Generate 3-5 mutations per function
85573
85676
 
85574
- Return ONLY valid JSON array, no markdown, no explanation.`;
85677
+ Return ONLY a valid JSON array. No markdown, no code fences, no explanation. Start your response with [ and end with ].`;
85575
85678
  const promptResult = await client.session.prompt({
85576
85679
  path: { id: ephemeralSessionId },
85577
85680
  body: {
@@ -85589,9 +85692,11 @@ Return ONLY valid JSON array, no markdown, no explanation.`;
85589
85692
  `);
85590
85693
  let parsed;
85591
85694
  try {
85592
- parsed = JSON.parse(rawText);
85695
+ parsed = JSON.parse(extractJsonArray(rawText));
85593
85696
  } catch (error93) {
85594
- console.warn(`[generateMutants] Failed to parse LLM response as MutationPatch[]: ${error93 instanceof Error ? error93.message : String(error93)}; returning empty patch set`);
85697
+ const msg = error93 instanceof Error ? error93.message : String(error93);
85698
+ const hint = msg.includes("EOF") || msg.includes("Unexpected end") ? " (response appears truncated \u2014 LLM may have hit an output token limit)" : "";
85699
+ console.warn(`[generateMutants] Failed to parse LLM response as MutationPatch[]: ${msg}${hint}; returning empty patch set`);
85595
85700
  return [];
85596
85701
  }
85597
85702
  if (!Array.isArray(parsed) || parsed.length === 0) {
@@ -85804,7 +85909,7 @@ import * as path95 from "path";
85804
85909
 
85805
85910
  // src/mutation/engine.ts
85806
85911
  import { spawnSync as spawnSync3 } from "child_process";
85807
- import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
85912
+ import { unlinkSync as unlinkSync13, writeFileSync as writeFileSync18 } from "fs";
85808
85913
  import * as path94 from "path";
85809
85914
 
85810
85915
  // src/mutation/equivalence.ts
@@ -86038,7 +86143,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
86038
86143
  revertError = new Error(`Failed to revert mutation ${patch.id}: ${revertErr}. Working tree may be dirty.`);
86039
86144
  }
86040
86145
  try {
86041
- unlinkSync12(patchFile);
86146
+ unlinkSync13(patchFile);
86042
86147
  } catch (_unlinkErr) {}
86043
86148
  }
86044
86149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.86.3",
3
+ "version": "6.86.5",
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",