opencode-swarm 7.87.0 → 7.87.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.
@@ -60,6 +60,7 @@ import {
60
60
  stripKnownSwarmPrefix
61
61
  } from "./index-q9h0wb04.js";
62
62
  import {
63
+ MAX_TRANSIENT_RETRIES,
63
64
  PlanSchema,
64
65
  RetrospectiveEvidenceSchema,
65
66
  appendLedgerEvent,
@@ -81,6 +82,7 @@ import {
81
82
  isGlobalFile,
82
83
  isProtectedPath,
83
84
  isStateUnreadable,
85
+ isTransientSpawnError,
84
86
  isValidEvidenceType,
85
87
  listEvidenceTaskIds,
86
88
  loadEpicSessionState,
@@ -97,9 +99,10 @@ import {
97
99
  sanitizeTaskId,
98
100
  saveEvidence,
99
101
  savePlan,
102
+ transientBackoff,
100
103
  validateProjectRoot,
101
104
  writeProjectedSpecSync
102
- } from "./index-89xjr3h4.js";
105
+ } from "./index-8y7qetpg.js";
103
106
  import {
104
107
  _internals as _internals2,
105
108
  _internals1 as _internals3,
@@ -894,7 +897,7 @@ var init_executor = __esm(() => {
894
897
  // package.json
895
898
  var package_default = {
896
899
  name: "opencode-swarm",
897
- version: "7.87.0",
900
+ version: "7.87.2",
898
901
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
899
902
  main: "dist/index.js",
900
903
  types: "dist/index.d.ts",
@@ -5231,6 +5234,7 @@ function createSwarmTool(opts) {
5231
5234
  var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
5232
5235
  var MAX_LABEL_LENGTH = 100;
5233
5236
  var GIT_TIMEOUT_MS = 30000;
5237
+ var GIT_MAX_BUFFER_BYTES = 5 * 1024 * 1024;
5234
5238
  var SHELL_METACHARACTERS = /[;|&$`(){}<>!'"]/;
5235
5239
  var SAFE_LABEL_PATTERN = /^[a-zA-Z0-9_ -]+$/;
5236
5240
  var CONTROL_CHAR_PATTERN = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
@@ -5305,20 +5309,31 @@ function writeCheckpointLog(log2, directory) {
5305
5309
  fs7.renameSync(tempPath, logPath);
5306
5310
  }
5307
5311
  function gitExec(args, cwd) {
5308
- const result = child_process.spawnSync("git", args, {
5309
- cwd,
5310
- encoding: "utf-8",
5311
- timeout: GIT_TIMEOUT_MS,
5312
- stdio: ["ignore", "pipe", "pipe"],
5313
- windowsHide: true
5314
- });
5315
- if (result.error) {
5316
- throw new Error(`git failed to start: ${result.error.code ?? "unknown"} \u2014 ${result.error.message}`);
5317
- }
5318
- if (result.status !== 0) {
5312
+ for (let attempt = 0;attempt < MAX_TRANSIENT_RETRIES; attempt++) {
5313
+ const result = child_process.spawnSync("git", args, {
5314
+ cwd,
5315
+ encoding: "utf-8",
5316
+ timeout: GIT_TIMEOUT_MS,
5317
+ stdio: ["ignore", "pipe", "pipe"],
5318
+ windowsHide: true,
5319
+ maxBuffer: GIT_MAX_BUFFER_BYTES
5320
+ });
5321
+ if (result.error) {
5322
+ const code = result.error.code;
5323
+ const message = result.error.message ?? "";
5324
+ const isTransient = isTransientSpawnError(result.error) || /ETIMEDOUT|timed out/i.test(message);
5325
+ if (!isTransient || attempt >= MAX_TRANSIENT_RETRIES - 1) {
5326
+ throw new Error(`git failed to start: ${code ?? "unknown"} \u2014 ${message}`);
5327
+ }
5328
+ transientBackoff(attempt);
5329
+ continue;
5330
+ }
5331
+ if (result.status === 0) {
5332
+ return result.stdout ?? "";
5333
+ }
5319
5334
  throw new Error(result.stderr?.trim() || `git exited with code ${result.status}`);
5320
5335
  }
5321
- return result.stdout;
5336
+ throw new Error("git command failed after transient retries");
5322
5337
  }
5323
5338
  function appendRetentionEvent(directory, event) {
5324
5339
  try {
@@ -5335,9 +5350,20 @@ function getCurrentSha(directory) {
5335
5350
  function isGitRepo2(directory) {
5336
5351
  try {
5337
5352
  gitExec(["rev-parse", "--git-dir"], directory);
5338
- return true;
5339
- } catch {
5340
- return false;
5353
+ return { isRepo: true };
5354
+ } catch (e) {
5355
+ const message = e instanceof Error ? e.message : String(e);
5356
+ const isTransient = /ETIMEDOUT|timed out/i.test(message) && !/not a git repository/i.test(message);
5357
+ if (isTransient) {
5358
+ return {
5359
+ isRepo: false,
5360
+ warning: "git probe failed after retry exhaustion \u2014 treating as not a git repository"
5361
+ };
5362
+ }
5363
+ return {
5364
+ isRepo: false,
5365
+ warning: "git probe failed \u2014 directory may not be a git repository"
5366
+ };
5341
5367
  }
5342
5368
  }
5343
5369
  function handleSave(label, directory) {
@@ -5480,11 +5506,12 @@ var checkpoint = createSwarmTool({
5480
5506
  label: exports_external.string().optional().describe("Checkpoint label (required for save, restore, delete)")
5481
5507
  },
5482
5508
  execute: async (args, directory) => {
5483
- if (!isGitRepo2(directory)) {
5509
+ const repoProbe = isGitRepo2(directory);
5510
+ if (!repoProbe.isRepo) {
5484
5511
  return JSON.stringify({
5485
5512
  action: "unknown",
5486
5513
  success: false,
5487
- error: "not a git repository"
5514
+ error: `${repoProbe.warning ?? "not a git repository"} \u2014 checkpoint tools require a git repository`
5488
5515
  }, null, 2);
5489
5516
  }
5490
5517
  let action;
@@ -5673,6 +5700,7 @@ async function handleClarifyCommand(_directory, args) {
5673
5700
  }
5674
5701
 
5675
5702
  // src/commands/close.ts
5703
+ import { spawnSync as spawnSync5 } from "child_process";
5676
5704
  import * as fsSync2 from "fs";
5677
5705
  import { promises as fs11 } from "fs";
5678
5706
  import path24 from "path";
@@ -6079,7 +6107,8 @@ async function runCuratorPostMortem(directory, options = {}) {
6079
6107
  } catch {
6080
6108
  warnings.push("Failed to load plan data.");
6081
6109
  }
6082
- const reportFilename = `post-mortem-${planId}.md`;
6110
+ const effectivePlanId = planId === "unknown" ? `unknown-${Date.now()}` : planId;
6111
+ const reportFilename = `post-mortem-${effectivePlanId}.md`;
6083
6112
  let reportPath;
6084
6113
  try {
6085
6114
  reportPath = validateSwarmPath(directory, reportFilename);
@@ -6095,22 +6124,22 @@ async function runCuratorPostMortem(directory, options = {}) {
6095
6124
  if (!options.force && isReportValid(reportPath)) {
6096
6125
  return {
6097
6126
  success: true,
6098
- planId,
6127
+ planId: effectivePlanId,
6099
6128
  reportPath,
6100
6129
  summary: "Post-mortem report already exists (idempotent skip).",
6101
6130
  warnings
6102
6131
  };
6103
6132
  }
6104
- const lock = await _internals13.acquirePostMortemLock(directory, planId);
6133
+ const lock = await _internals13.acquirePostMortemLock(directory, effectivePlanId);
6105
6134
  if (!lock.acquired) {
6106
6135
  return {
6107
6136
  success: false,
6108
- planId,
6137
+ planId: effectivePlanId,
6109
6138
  reportPath,
6110
6139
  summary: null,
6111
6140
  warnings: [
6112
6141
  ...warnings,
6113
- `Concurrent post-mortem run in progress for plan ${planId}; skipped.`
6142
+ `Concurrent post-mortem run in progress for plan ${effectivePlanId}; skipped.`
6114
6143
  ]
6115
6144
  };
6116
6145
  }
@@ -6160,7 +6189,7 @@ async function runCuratorPostMortem(directory, options = {}) {
6160
6189
  if (options.llmDelegate) {
6161
6190
  try {
6162
6191
  const { CURATOR_POSTMORTEM_PROMPT: CURATOR_POSTMORTEM_PROMPT2 } = await import("./explorer-gz70sm9b.js");
6163
- const userInput = assembleLLMInput(planId, planSummary, knowledgeSummary, curatorDigest, proposals, unactionable, retrospectives, driftReports);
6192
+ const userInput = assembleLLMInput(effectivePlanId, planSummary, knowledgeSummary, curatorDigest, proposals, unactionable, retrospectives, driftReports);
6164
6193
  const ac = new AbortController;
6165
6194
  const timer = setTimeout(() => ac.abort(), 300000);
6166
6195
  let llmOutput;
@@ -6176,17 +6205,17 @@ async function runCuratorPostMortem(directory, options = {}) {
6176
6205
  } finally {
6177
6206
  clearTimeout(timer);
6178
6207
  }
6179
- reportContent = `# Post-Mortem Report: ${planId}
6208
+ reportContent = `# Post-Mortem Report: ${effectivePlanId}
6180
6209
  Generated: ${new Date().toISOString()}
6181
6210
 
6182
6211
  ${llmOutput}`;
6183
6212
  } catch (err) {
6184
6213
  const msg = err instanceof Error ? err.message : String(err);
6185
6214
  warnings.push(`LLM delegate failed, falling back to data-only report: ${msg}`);
6186
- reportContent = _internals13.buildDataOnlyReport(planId, planSummary, knowledgeSummary, curatorDigest, proposals, unactionable, retrospectives, driftReports);
6215
+ reportContent = _internals13.buildDataOnlyReport(effectivePlanId, planSummary, knowledgeSummary, curatorDigest, proposals, unactionable, retrospectives, driftReports);
6187
6216
  }
6188
6217
  } else {
6189
- reportContent = _internals13.buildDataOnlyReport(planId, planSummary, knowledgeSummary, curatorDigest, proposals, unactionable, retrospectives, driftReports);
6218
+ reportContent = _internals13.buildDataOnlyReport(effectivePlanId, planSummary, knowledgeSummary, curatorDigest, proposals, unactionable, retrospectives, driftReports);
6190
6219
  }
6191
6220
  try {
6192
6221
  const { mkdirSync: mkdirSync6 } = await import("fs");
@@ -6196,7 +6225,7 @@ ${llmOutput}`;
6196
6225
  const msg = err instanceof Error ? err.message : String(err);
6197
6226
  return {
6198
6227
  success: false,
6199
- planId,
6228
+ planId: effectivePlanId,
6200
6229
  reportPath: null,
6201
6230
  summary: null,
6202
6231
  warnings: [...warnings, `Failed to write report: ${msg}`]
@@ -6206,13 +6235,13 @@ ${llmOutput}`;
6206
6235
  const neverAppliedCount = knowledgeSummary.filter((e) => e.applied === 0 && e.violated === 0 && e.ignored === 0).length;
6207
6236
  const totalViolations = knowledgeSummary.reduce((s, e) => s + e.violated, 0);
6208
6237
  const summary = [
6209
- `Post-mortem for plan "${planId}": ${totalEntries} knowledge entries reviewed.`,
6238
+ `Post-mortem for plan "${effectivePlanId}": ${totalEntries} knowledge entries reviewed.`,
6210
6239
  `${neverAppliedCount} never-applied entries flagged; ${totalViolations} total violations recorded.`,
6211
6240
  `${proposals.length} pending proposals, ${unactionable.length} quarantined entries.`
6212
6241
  ].join(" ");
6213
6242
  return {
6214
6243
  success: true,
6215
- planId,
6244
+ planId: effectivePlanId,
6216
6245
  reportPath,
6217
6246
  summary,
6218
6247
  warnings
@@ -11097,8 +11126,9 @@ function countSessionKnowledgeEntries(entries, sessionStart, fallbackCount) {
11097
11126
  return Number.isFinite(createdAtMs) && createdAtMs >= sessionStartMs;
11098
11127
  }).length;
11099
11128
  }
11100
- async function copyDirRecursive(src, dest) {
11129
+ async function copyDirRecursiveWithFailures(src, dest) {
11101
11130
  let count = 0;
11131
+ const failures = [];
11102
11132
  const entries = await fs11.readdir(src);
11103
11133
  await fs11.mkdir(dest, { recursive: true });
11104
11134
  for (const entry of entries) {
@@ -11107,17 +11137,32 @@ async function copyDirRecursive(src, dest) {
11107
11137
  try {
11108
11138
  const stat4 = await fs11.stat(srcEntry);
11109
11139
  if (stat4.isDirectory()) {
11110
- const subCount = await copyDirRecursive(srcEntry, destEntry).catch(() => 0);
11111
- count += subCount;
11140
+ const subResult = await copyDirRecursiveWithFailures(srcEntry, destEntry);
11141
+ count += subResult.copied;
11142
+ failures.push(...subResult.failures);
11112
11143
  } else {
11113
11144
  try {
11114
11145
  await fs11.copyFile(srcEntry, destEntry);
11115
11146
  count++;
11116
- } catch {}
11147
+ } catch (err) {
11148
+ const errno = err?.code;
11149
+ if (errno !== "ENOENT") {
11150
+ failures.push(`${srcEntry}: ${err instanceof Error ? err.message : String(err)}`);
11151
+ }
11152
+ }
11117
11153
  }
11118
- } catch {}
11154
+ } catch (err) {
11155
+ const errno = err?.code;
11156
+ if (errno !== "ENOENT") {
11157
+ failures.push(`${srcEntry}: ${err instanceof Error ? err.message : String(err)}`);
11158
+ }
11159
+ }
11119
11160
  }
11120
- return count;
11161
+ return { copied: count, failures };
11162
+ }
11163
+ async function copyDirRecursive(src, dest) {
11164
+ const result = await copyDirRecursiveWithFailures(src, dest);
11165
+ return result.copied;
11121
11166
  }
11122
11167
  var ARCHIVE_ARTIFACTS = [
11123
11168
  "plan.json",
@@ -11436,31 +11481,145 @@ async function runFinalizeStage(ctx) {
11436
11481
  ctx.warnings.push(`Post-mortem failed: ${msg}`);
11437
11482
  }
11438
11483
  }
11484
+ async function copySqliteSafe(srcPath, destPath) {
11485
+ if (!fsSync2.existsSync(srcPath)) {
11486
+ return {
11487
+ success: true,
11488
+ skipped: true,
11489
+ reason: "source does not exist (ENOENT)"
11490
+ };
11491
+ }
11492
+ let checkpointVerified = false;
11493
+ try {
11494
+ const result = spawnSync5("sqlite3", [srcPath, "PRAGMA wal_checkpoint(TRUNCATE);"], {
11495
+ cwd: path24.dirname(srcPath),
11496
+ encoding: "utf-8",
11497
+ stdio: ["ignore", "pipe", "pipe"],
11498
+ timeout: 1e4,
11499
+ windowsHide: true,
11500
+ maxBuffer: 1024
11501
+ });
11502
+ if (result.error) {
11503
+ const code = result.error.code;
11504
+ if (code === "ENOENT") {
11505
+ try {
11506
+ await fs11.copyFile(srcPath, destPath);
11507
+ return {
11508
+ success: true,
11509
+ reason: "copied without WAL checkpoint (sqlite3 CLI unavailable)"
11510
+ };
11511
+ } catch (copyErr) {
11512
+ return {
11513
+ success: false,
11514
+ reason: `fallback copy failed: ${copyErr instanceof Error ? copyErr.message : String(copyErr)}`
11515
+ };
11516
+ }
11517
+ }
11518
+ return {
11519
+ success: false,
11520
+ reason: `wal_checkpoint failed: ${result.error instanceof Error ? result.error.message : String(result.error)}`
11521
+ };
11522
+ }
11523
+ if (result.status !== 0) {
11524
+ return {
11525
+ success: false,
11526
+ reason: `wal_checkpoint exited with code ${result.status}`
11527
+ };
11528
+ }
11529
+ const output = (result.stdout || "").trim();
11530
+ const lines = output.split(`
11531
+ `).filter((l) => l.trim());
11532
+ if (lines.length >= 1) {
11533
+ const dataLine = lines[0];
11534
+ const columns = dataLine.split("|");
11535
+ const busyFlag = parseInt(columns[0], 10);
11536
+ checkpointVerified = !Number.isNaN(busyFlag) && busyFlag === 0;
11537
+ }
11538
+ } catch (err) {
11539
+ return {
11540
+ success: false,
11541
+ reason: `wal_checkpoint error: ${err instanceof Error ? err.message : String(err)}`
11542
+ };
11543
+ }
11544
+ try {
11545
+ await fs11.copyFile(srcPath, destPath);
11546
+ if (checkpointVerified) {
11547
+ return { success: true };
11548
+ }
11549
+ return {
11550
+ success: true,
11551
+ reason: "WAL checkpoint incomplete (busy) \u2014 archive copy may be stale, original preserved"
11552
+ };
11553
+ } catch (err) {
11554
+ return {
11555
+ success: false,
11556
+ reason: `copy failed: ${err instanceof Error ? err.message : String(err)}`
11557
+ };
11558
+ }
11559
+ }
11439
11560
  async function runArchiveStage(ctx) {
11440
11561
  ctx.timestamp = new Date().toISOString().replace(/[:.]/g, "-");
11441
11562
  ctx.archiveSuffix = Math.random().toString(36).slice(2, 8);
11442
11563
  ctx.archiveDir = path24.join(ctx.swarmDir, "archive", `swarm-${ctx.timestamp}-${ctx.archiveSuffix}`);
11443
11564
  try {
11444
11565
  await fs11.mkdir(ctx.archiveDir, { recursive: true });
11566
+ const WAL_SIDECAR_FILES = new Set(["swarm.db-shm", "swarm.db-wal"]);
11445
11567
  for (const artifact of ARCHIVE_ARTIFACTS) {
11568
+ if (WAL_SIDECAR_FILES.has(artifact)) {
11569
+ continue;
11570
+ }
11446
11571
  const srcPath = path24.join(ctx.swarmDir, artifact);
11447
11572
  const destPath = path24.join(ctx.archiveDir, artifact);
11448
- try {
11449
- await fs11.copyFile(srcPath, destPath);
11450
- ctx.archivedFileCount++;
11451
- if (ACTIVE_STATE_TO_CLEAN.includes(artifact)) {
11452
- ctx.archivedActiveStateFiles.add(artifact);
11573
+ if (artifact === "swarm.db") {
11574
+ const result = await copySqliteSafe(srcPath, destPath);
11575
+ if (result.skipped) {} else if (result.success) {
11576
+ ctx.archivedFileCount++;
11577
+ if (result.reason) {
11578
+ ctx.warnings.push(`Archived ${artifact}: ${result.reason}. Original preserved to prevent data loss.`);
11579
+ } else {
11580
+ ctx.archivedActiveStateFiles.add(artifact);
11581
+ }
11582
+ } else {
11583
+ ctx.archiveFailureReasons.set(artifact, result.reason);
11584
+ ctx.warnings.push(`Failed to archive ${artifact}: ${result.reason}. File preserved (not cleaned up).`);
11453
11585
  }
11454
- } catch {}
11586
+ } else {
11587
+ try {
11588
+ await fs11.copyFile(srcPath, destPath);
11589
+ ctx.archivedFileCount++;
11590
+ if (ACTIVE_STATE_TO_CLEAN.includes(artifact)) {
11591
+ ctx.archivedActiveStateFiles.add(artifact);
11592
+ }
11593
+ } catch (err) {
11594
+ const errno = err?.code;
11595
+ if (errno === "ENOENT") {} else {
11596
+ const reason = err instanceof Error ? err.message : String(err);
11597
+ ctx.archiveFailureReasons.set(artifact, `${errno ?? "unknown"}: ${reason}`);
11598
+ ctx.warnings.push(`Failed to archive ${artifact} [${errno ?? "unknown"}]: ${reason}. File preserved (not cleaned up).`);
11599
+ }
11600
+ }
11601
+ }
11455
11602
  }
11456
11603
  for (const dirName of ACTIVE_STATE_DIRS_TO_CLEAN) {
11457
11604
  const srcDir = path24.join(ctx.swarmDir, dirName);
11458
11605
  const destDir = path24.join(ctx.archiveDir, dirName);
11459
11606
  try {
11460
- const copied = await copyDirRecursive(srcDir, destDir);
11461
- ctx.archivedFileCount += copied;
11462
- ctx.archivedActiveStateDirs.add(dirName);
11463
- } catch {}
11607
+ const result = await copyDirRecursiveWithFailures(srcDir, destDir);
11608
+ ctx.archivedFileCount += result.copied;
11609
+ if (result.failures.length === 0) {
11610
+ ctx.archivedActiveStateDirs.add(dirName);
11611
+ } else {
11612
+ ctx.warnings.push(`Directory ${dirName} not fully archived (${result.failures.length} failure(s)). Source preserved.`);
11613
+ for (const failure of result.failures) {
11614
+ ctx.warnings.push(` - ${failure}`);
11615
+ }
11616
+ }
11617
+ } catch (err) {
11618
+ const code = err.code;
11619
+ if (code !== "ENOENT") {
11620
+ ctx.warnings.push(`Failed to archive directory ${dirName} [${code ?? "unknown"}]: ${err.message}. Source preserved.`);
11621
+ }
11622
+ }
11464
11623
  }
11465
11624
  ctx.archiveResult = `Archived ${ctx.archivedFileCount} artifact(s) to .swarm/archive/swarm-${ctx.timestamp}-${ctx.archiveSuffix}/`;
11466
11625
  } catch (archiveError) {
@@ -11501,14 +11660,21 @@ async function runCleanStage(ctx) {
11501
11660
  if (ctx.archivedActiveStateFiles.size > 0) {
11502
11661
  for (const artifact of ACTIVE_STATE_TO_CLEAN) {
11503
11662
  if (!ctx.archivedActiveStateFiles.has(artifact)) {
11504
- ctx.warnings.push(`Preserved ${artifact} because it was not successfully archived.`);
11663
+ const reason = ctx.archiveFailureReasons?.get(artifact);
11664
+ ctx.warnings.push(reason ? `Preserved ${artifact} because it was not successfully archived: ${reason}.` : `Preserved ${artifact} because it was not successfully archived.`);
11505
11665
  continue;
11506
11666
  }
11507
11667
  const filePath = path24.join(ctx.swarmDir, artifact);
11508
11668
  try {
11509
11669
  await fs11.unlink(filePath);
11510
11670
  cleanedFiles.push(artifact);
11511
- } catch {}
11671
+ } catch (err) {
11672
+ const errno = err?.code;
11673
+ if (errno === "ENOENT") {} else {
11674
+ const reason = err instanceof Error ? err.message : String(err);
11675
+ ctx.warnings.push(`Failed to clean active-state file ${artifact} [${errno ?? "unknown"}]: ${reason}`);
11676
+ }
11677
+ }
11512
11678
  }
11513
11679
  } else {
11514
11680
  ctx.warnings.push("Skipped active-state cleanup because no active-state files were archived. Files preserved to prevent data loss.");
@@ -11530,15 +11696,33 @@ async function runCleanStage(ctx) {
11530
11696
  try {
11531
11697
  await fs11.unlink(path24.join(ctx.swarmDir, backup));
11532
11698
  configBackupsRemoved++;
11533
- } catch {}
11699
+ } catch (err) {
11700
+ const errno = err?.code;
11701
+ if (errno === "ENOENT") {} else {
11702
+ const reason = err instanceof Error ? err.message : String(err);
11703
+ ctx.warnings.push(`Failed to clean config-backup ${backup} [${errno ?? "unknown"}]: ${reason}`);
11704
+ }
11705
+ }
11534
11706
  }
11535
11707
  const ledgerSiblings = swarmFiles.filter((f) => (f.startsWith("plan-ledger.archived-") || f.startsWith("plan-ledger.backup-")) && f.endsWith(".jsonl"));
11536
11708
  for (const sibling of ledgerSiblings) {
11537
11709
  try {
11538
11710
  await fs11.unlink(path24.join(ctx.swarmDir, sibling));
11539
- } catch {}
11711
+ } catch (err) {
11712
+ const errno = err?.code;
11713
+ if (errno === "ENOENT") {} else {
11714
+ const reason = err instanceof Error ? err.message : String(err);
11715
+ ctx.warnings.push(`Failed to clean ledger sibling ${sibling} [${errno ?? "unknown"}]: ${reason}`);
11716
+ }
11717
+ }
11540
11718
  }
11541
- } catch {}
11719
+ } catch (err) {
11720
+ const errno = err?.code;
11721
+ if (errno === "ENOENT") {} else {
11722
+ const reason = err instanceof Error ? err.message : String(err);
11723
+ ctx.warnings.push(`Failed to read ${ctx.swarmDir} for stale-file cleanup [${errno ?? "unknown"}]: ${reason}`);
11724
+ }
11725
+ }
11542
11726
  let swarmPlanFilesRemoved = 0;
11543
11727
  const candidates = [
11544
11728
  path24.join(ctx.directory, ".swarm", "SWARM_PLAN.json"),
@@ -11564,9 +11748,21 @@ async function runCleanStage(ctx) {
11564
11748
  try {
11565
11749
  await fs11.unlink(path24.join(ctx.swarmDir, tmp));
11566
11750
  tmpFilesRemoved++;
11567
- } catch {}
11751
+ } catch (err) {
11752
+ const errno = err?.code;
11753
+ if (errno === "ENOENT") {} else {
11754
+ const reason = err instanceof Error ? err.message : String(err);
11755
+ ctx.warnings.push(`Failed to clean tmp file ${tmp} [${errno ?? "unknown"}]: ${reason}`);
11756
+ }
11757
+ }
11568
11758
  }
11569
- } catch {}
11759
+ } catch (err) {
11760
+ const errno = err?.code;
11761
+ if (errno === "ENOENT") {} else {
11762
+ const reason = err instanceof Error ? err.message : String(err);
11763
+ ctx.warnings.push(`Failed to read ${ctx.swarmDir} for tmp-file cleanup [${errno ?? "unknown"}]: ${reason}`);
11764
+ }
11765
+ }
11570
11766
  if (tmpFilesRemoved > 0) {
11571
11767
  cleanedFiles.push(`${tmpFilesRemoved} .tmp.* file(s)`);
11572
11768
  }
@@ -11583,9 +11779,14 @@ async function runCleanStage(ctx) {
11583
11779
  ""
11584
11780
  ].join(`
11585
11781
  `);
11782
+ const contextTempPath = path24.join(path24.dirname(contextPath), `${path24.basename(contextPath)}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
11586
11783
  try {
11587
- await fs11.writeFile(contextPath, contextContent, "utf-8");
11784
+ await fs11.writeFile(contextTempPath, contextContent, "utf-8");
11785
+ fsSync2.renameSync(contextTempPath, contextPath);
11588
11786
  } catch (error2) {
11787
+ try {
11788
+ fsSync2.unlinkSync(contextTempPath);
11789
+ } catch {}
11589
11790
  const msg = error2 instanceof Error ? error2.message : String(error2);
11590
11791
  ctx.warnings.push(`Failed to reset context.md: ${msg}`);
11591
11792
  console.warn("[close-command] Failed to write context.md:", error2);
@@ -11679,15 +11880,33 @@ async function handleCloseCommand(directory, args, options = {}) {
11679
11880
  if (!finalizeLock.acquired) {
11680
11881
  return `\u274C Another /swarm finalize is already running for this project. If you are certain no other run is active, wait for the lock to expire or remove the stale lock and retry.`;
11681
11882
  }
11682
- const phases = planData.phases ?? [];
11683
- const inProgressPhases = phases.filter((p) => p.status === "in_progress");
11684
- const isForced = args.includes("--force");
11685
- const runSkillReview = args.includes("--skill-review");
11686
- let planAlreadyDone = false;
11687
- if (planExists) {
11688
- planAlreadyDone = phases.length > 0 && phases.every((p) => p.status === "complete" || p.status === "completed" || p.status === "blocked" || p.status === "closed");
11689
- }
11690
11883
  try {
11884
+ if (!planExists) {
11885
+ const archiveDir = path24.join(swarmDir, "archive");
11886
+ try {
11887
+ const archiveEntries = await fs11.readdir(archiveDir);
11888
+ const hasArchiveBundle = archiveEntries.some((entry) => entry.startsWith("swarm-"));
11889
+ if (hasArchiveBundle) {
11890
+ const hasActiveState = [
11891
+ ...ACTIVE_STATE_TO_CLEAN,
11892
+ ...ACTIVE_STATE_DIRS_TO_CLEAN
11893
+ ].some((entry) => fsSync2.existsSync(path24.join(swarmDir, entry)));
11894
+ if (!hasActiveState) {
11895
+ return `\u2705 Already finalized \u2014 nothing to do.
11896
+
11897
+ This project was already finalized in a previous /swarm close run. The plan has been archived and cleaned up. No further action is needed.`;
11898
+ }
11899
+ }
11900
+ } catch {}
11901
+ }
11902
+ const phases = planData.phases ?? [];
11903
+ const inProgressPhases = phases.filter((p) => p.status === "in_progress");
11904
+ const isForced = args.includes("--force");
11905
+ const runSkillReview = args.includes("--skill-review");
11906
+ let planAlreadyDone = false;
11907
+ if (planExists) {
11908
+ planAlreadyDone = phases.length > 0 && phases.every((p) => p.status === "complete" || p.status === "completed" || p.status === "blocked" || p.status === "closed");
11909
+ }
11691
11910
  const { config: loadedConfig } = _internals19.loadPluginConfigWithMeta(directory);
11692
11911
  const config = KnowledgeConfigSchema.parse(loadedConfig.knowledge ?? {});
11693
11912
  const ctx = {
@@ -11724,6 +11943,7 @@ async function handleCloseCommand(directory, args, options = {}) {
11724
11943
  archivedFileCount: 0,
11725
11944
  archivedActiveStateFiles: new Set,
11726
11945
  archivedActiveStateDirs: new Set,
11946
+ archiveFailureReasons: new Map,
11727
11947
  timestamp: "",
11728
11948
  archiveDir: "",
11729
11949
  archiveSuffix: "",
@@ -11786,9 +12006,14 @@ async function handleCloseCommand(directory, args, options = {}) {
11786
12006
  ...ctx.warnings.length > 0 ? ["## Warnings", ...ctx.warnings.map((w) => `- ${w}`), ""] : []
11787
12007
  ].join(`
11788
12008
  `);
12009
+ const closeSummaryTempPath = path24.join(path24.dirname(closeSummaryPath), `${path24.basename(closeSummaryPath)}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
11789
12010
  try {
11790
- await fs11.writeFile(closeSummaryPath, summaryContent, "utf-8");
12011
+ await fs11.writeFile(closeSummaryTempPath, summaryContent, "utf-8");
12012
+ fsSync2.renameSync(closeSummaryTempPath, closeSummaryPath);
11791
12013
  } catch (error2) {
12014
+ try {
12015
+ fsSync2.unlinkSync(closeSummaryTempPath);
12016
+ } catch {}
11792
12017
  const msg = error2 instanceof Error ? error2.message : String(error2);
11793
12018
  ctx.warnings.push(`Failed to write close-summary.md: ${msg}`);
11794
12019
  console.warn("[close-command] Failed to write close-summary.md:", error2);
@@ -12129,7 +12354,7 @@ async function handleConfigCommand(directory, _args) {
12129
12354
  }
12130
12355
 
12131
12356
  // src/services/skill-consolidation.ts
12132
- import { existsSync as existsSync13 } from "fs";
12357
+ import { existsSync as existsSync14 } from "fs";
12133
12358
  import { mkdir as mkdir8, readFile as readFile8, rename as rename4, writeFile as writeFile8 } from "fs/promises";
12134
12359
  import * as path26 from "path";
12135
12360
 
@@ -12160,7 +12385,7 @@ function consolidationStatePath(directory) {
12160
12385
  }
12161
12386
  async function readState2(directory) {
12162
12387
  const filePath = consolidationStatePath(directory);
12163
- if (!existsSync13(filePath))
12388
+ if (!existsSync14(filePath))
12164
12389
  return {};
12165
12390
  try {
12166
12391
  const parsed = JSON.parse(await readFile8(filePath, "utf-8"));
@@ -13607,7 +13832,7 @@ ${USAGE5}`;
13607
13832
 
13608
13833
  // src/services/diagnose-service.ts
13609
13834
  import * as child_process4 from "child_process";
13610
- import { existsSync as existsSync16, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
13835
+ import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
13611
13836
  import path31 from "path";
13612
13837
  import { fileURLToPath } from "url";
13613
13838
 
@@ -13660,11 +13885,11 @@ init_capability_probe();
13660
13885
  init_executor();
13661
13886
 
13662
13887
  // src/services/knowledge-diagnostics.ts
13663
- import { existsSync as existsSync15 } from "fs";
13888
+ import { existsSync as existsSync16 } from "fs";
13664
13889
  import { readFile as readFile10 } from "fs/promises";
13665
13890
 
13666
13891
  // src/services/version-check.ts
13667
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
13892
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
13668
13893
  import { homedir as homedir4 } from "os";
13669
13894
  import { join as join25 } from "path";
13670
13895
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
@@ -13679,7 +13904,7 @@ function cacheFile() {
13679
13904
  function readVersionCache() {
13680
13905
  try {
13681
13906
  const path31 = cacheFile();
13682
- if (!existsSync14(path31))
13907
+ if (!existsSync15(path31))
13683
13908
  return null;
13684
13909
  const raw = readFileSync8(path31, "utf-8");
13685
13910
  const parsed = JSON.parse(raw);
@@ -13720,7 +13945,7 @@ var SEVEN_DAYS_MS2 = 7 * 24 * 60 * 60 * 1000;
13720
13945
  var UNACTIONABLE_BACKLOG_WARN = 100;
13721
13946
  var INSIGHT_BACKLOG_WARN = 50;
13722
13947
  async function readRawLines(filePath) {
13723
- if (!existsSync15(filePath))
13948
+ if (!existsSync16(filePath))
13724
13949
  return { entries: [], corrupt: 0 };
13725
13950
  const content = await readFile10(filePath, "utf-8");
13726
13951
  const entries = [];
@@ -13845,7 +14070,7 @@ async function computeKnowledgeDebug(directory) {
13845
14070
  };
13846
14071
  }
13847
14072
  async function safeJsonlCount(filePath) {
13848
- if (!filePath || !existsSync15(filePath))
14073
+ if (!filePath || !existsSync16(filePath))
13849
14074
  return 0;
13850
14075
  try {
13851
14076
  const content = await readFile10(filePath, "utf-8");
@@ -14166,7 +14391,7 @@ async function checkConfigBackups(directory) {
14166
14391
  }
14167
14392
  async function checkGitRepository(directory) {
14168
14393
  try {
14169
- if (!existsSync16(directory) || !statSync6(directory).isDirectory()) {
14394
+ if (!existsSync17(directory) || !statSync6(directory).isDirectory()) {
14170
14395
  return {
14171
14396
  name: "Git Repository",
14172
14397
  status: "\u274C",
@@ -14231,7 +14456,7 @@ async function checkSpecStaleness(directory, plan) {
14231
14456
  }
14232
14457
  async function checkConfigParseability(directory) {
14233
14458
  const configPath = path31.join(directory, ".opencode/opencode-swarm.json");
14234
- if (!existsSync16(configPath)) {
14459
+ if (!existsSync17(configPath)) {
14235
14460
  return {
14236
14461
  name: "Config Parseability",
14237
14462
  status: "\u2705",
@@ -14286,11 +14511,11 @@ async function checkGrammarWasmFiles() {
14286
14511
  const thisDir = path31.dirname(fileURLToPath(import.meta.url));
14287
14512
  const grammarDir = resolveGrammarDir(thisDir);
14288
14513
  const missing = [];
14289
- if (!existsSync16(path31.join(grammarDir, "tree-sitter.wasm"))) {
14514
+ if (!existsSync17(path31.join(grammarDir, "tree-sitter.wasm"))) {
14290
14515
  missing.push("tree-sitter.wasm (core runtime)");
14291
14516
  }
14292
14517
  for (const file of grammarFiles) {
14293
- if (!existsSync16(path31.join(grammarDir, file))) {
14518
+ if (!existsSync17(path31.join(grammarDir, file))) {
14294
14519
  missing.push(file);
14295
14520
  }
14296
14521
  }
@@ -14309,7 +14534,7 @@ async function checkGrammarWasmFiles() {
14309
14534
  }
14310
14535
  async function checkCheckpointManifest(directory) {
14311
14536
  const manifestPath = path31.join(directory, ".swarm/checkpoints.json");
14312
- if (!existsSync16(manifestPath)) {
14537
+ if (!existsSync17(manifestPath)) {
14313
14538
  return {
14314
14539
  name: "Checkpoint Manifest",
14315
14540
  status: "\u2705",
@@ -14361,7 +14586,7 @@ async function checkCheckpointManifest(directory) {
14361
14586
  }
14362
14587
  async function checkEventStreamIntegrity(directory) {
14363
14588
  const eventsPath = path31.join(directory, ".swarm/events.jsonl");
14364
- if (!existsSync16(eventsPath)) {
14589
+ if (!existsSync17(eventsPath)) {
14365
14590
  return {
14366
14591
  name: "Event Stream",
14367
14592
  status: "\u2705",
@@ -14402,7 +14627,7 @@ async function checkEventStreamIntegrity(directory) {
14402
14627
  }
14403
14628
  async function checkSteeringDirectives(directory) {
14404
14629
  const eventsPath = path31.join(directory, ".swarm/events.jsonl");
14405
- if (!existsSync16(eventsPath)) {
14630
+ if (!existsSync17(eventsPath)) {
14406
14631
  return {
14407
14632
  name: "Steering Directives",
14408
14633
  status: "\u2705",
@@ -14458,7 +14683,7 @@ async function checkCurator(directory) {
14458
14683
  };
14459
14684
  }
14460
14685
  const summaryPath = path31.join(directory, ".swarm/curator-summary.json");
14461
- if (!existsSync16(summaryPath)) {
14686
+ if (!existsSync17(summaryPath)) {
14462
14687
  return {
14463
14688
  name: "Curator",
14464
14689
  status: "\u2705",
@@ -14660,7 +14885,7 @@ async function getDiagnoseData(directory) {
14660
14885
  checks.push(await checkKnowledgeHealth(directory));
14661
14886
  try {
14662
14887
  const evidenceDir = path31.join(directory, ".swarm", "evidence");
14663
- const snapshotFiles = existsSync16(evidenceDir) ? readdirSync3(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
14888
+ const snapshotFiles = existsSync17(evidenceDir) ? readdirSync3(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
14664
14889
  if (snapshotFiles.length > 0) {
14665
14890
  const latest = snapshotFiles.sort().pop();
14666
14891
  checks.push({
@@ -14693,7 +14918,7 @@ async function getDiagnoseData(directory) {
14693
14918
  const cacheRows = [];
14694
14919
  for (const cachePath of cachePaths) {
14695
14920
  try {
14696
- if (!existsSync16(cachePath)) {
14921
+ if (!existsSync17(cachePath)) {
14697
14922
  cacheRows.push(`\u2B1C ${cachePath} \u2014 absent`);
14698
14923
  continue;
14699
14924
  }
@@ -15731,7 +15956,7 @@ async function handleEvidenceCommand(directory, args) {
15731
15956
  return formatTaskEvidenceMarkdown(evidenceData);
15732
15957
  }
15733
15958
  async function handleEvidenceSummaryCommand(directory) {
15734
- const { buildEvidenceSummary } = await import("./evidence-summary-service-gg5m9z57.js");
15959
+ const { buildEvidenceSummary } = await import("./evidence-summary-service-5me91eq8.js");
15735
15960
  const artifact = await buildEvidenceSummary(directory);
15736
15961
  if (!artifact) {
15737
15962
  return "No plan found. Run `/swarm plan` to check plan status.";
@@ -16235,7 +16460,7 @@ function buildStatusReport(directory, sessionID, sessionFlag) {
16235
16460
 
16236
16461
  // src/commands/handoff.ts
16237
16462
  import crypto4 from "crypto";
16238
- import { renameSync as renameSync8, unlinkSync as unlinkSync4 } from "fs";
16463
+ import { renameSync as renameSync9, unlinkSync as unlinkSync5 } from "fs";
16239
16464
 
16240
16465
  // src/services/handoff-service.ts
16241
16466
  var RTL_OVERRIDE_PATTERN = /[\u202e\u202d\u202c\u200f]/g;
@@ -16615,7 +16840,7 @@ var _internals26 = {
16615
16840
  };
16616
16841
 
16617
16842
  // src/session/snapshot-writer.ts
16618
- import { closeSync as closeSync6, fsyncSync as fsyncSync2, mkdirSync as mkdirSync14, openSync as openSync6, renameSync as renameSync7 } from "fs";
16843
+ import { closeSync as closeSync6, fsyncSync as fsyncSync2, mkdirSync as mkdirSync14, openSync as openSync6, renameSync as renameSync8 } from "fs";
16619
16844
  import * as path36 from "path";
16620
16845
  var _writeInFlight = Promise.resolve();
16621
16846
  function serializeAgentSession(s) {
@@ -16735,7 +16960,7 @@ async function writeSnapshot(directory, state) {
16735
16960
  closeSync6(fd);
16736
16961
  }
16737
16962
  } catch {}
16738
- renameSync7(tempPath, resolvedPath);
16963
+ renameSync8(tempPath, resolvedPath);
16739
16964
  } catch (error2) {
16740
16965
  log("[snapshot-writer] write failed", {
16741
16966
  error: error2 instanceof Error ? error2.message : String(error2)
@@ -16767,10 +16992,10 @@ async function handleHandoffCommand(directory, _args) {
16767
16992
  const tempPath = `${resolvedPath}.tmp.${crypto4.randomUUID()}`;
16768
16993
  await bunWrite(tempPath, markdown);
16769
16994
  try {
16770
- renameSync8(tempPath, resolvedPath);
16995
+ renameSync9(tempPath, resolvedPath);
16771
16996
  } catch (renameErr) {
16772
16997
  try {
16773
- unlinkSync4(tempPath);
16998
+ unlinkSync5(tempPath);
16774
16999
  } catch {}
16775
17000
  throw renameErr;
16776
17001
  }
@@ -16779,10 +17004,10 @@ async function handleHandoffCommand(directory, _args) {
16779
17004
  const promptTempPath = `${promptPath}.tmp.${crypto4.randomUUID()}`;
16780
17005
  await bunWrite(promptTempPath, continuationPrompt);
16781
17006
  try {
16782
- renameSync8(promptTempPath, promptPath);
17007
+ renameSync9(promptTempPath, promptPath);
16783
17008
  } catch (renameErr) {
16784
17009
  try {
16785
- unlinkSync4(promptTempPath);
17010
+ unlinkSync5(promptTempPath);
16786
17011
  } catch {}
16787
17012
  throw renameErr;
16788
17013
  }
@@ -16942,7 +17167,7 @@ async function handleHistoryCommand(directory, _args) {
16942
17167
  return formatHistoryMarkdown(historyData);
16943
17168
  }
16944
17169
  // src/commands/_shared/url-security.ts
16945
- import { spawnSync as spawnSync5 } from "child_process";
17170
+ import { spawnSync as spawnSync6 } from "child_process";
16946
17171
  var MAX_URL_LEN = 2048;
16947
17172
  var IPV4_PRIVATE = /^10\./;
16948
17173
  var IPV4_LOOPBACK = /^127\./;
@@ -16952,7 +17177,7 @@ var IPV4_PRIVATE_192 = /^192\.168\./;
16952
17177
  var IPV4_ZERO_NETWORK = /^0\./;
16953
17178
  var IPV6_LINK_LOCAL = /^fe80:/i;
16954
17179
  var IPV6_UNIQUE_LOCAL = /^f[cd][0-9a-f]{2}:/i;
16955
- var _internals28 = { spawnSync: spawnSync5 };
17180
+ var _internals28 = { spawnSync: spawnSync6 };
16956
17181
  function sanitizeUrl(raw) {
16957
17182
  let urlStr = raw.trim();
16958
17183
  urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
@@ -17231,7 +17456,7 @@ import { join as join30 } from "path";
17231
17456
  // src/hooks/knowledge-migrator.ts
17232
17457
  init_logger();
17233
17458
  import { randomUUID as randomUUID4 } from "crypto";
17234
- import { existsSync as existsSync21, readFileSync as readFileSync14 } from "fs";
17459
+ import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
17235
17460
  import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
17236
17461
  import * as os8 from "os";
17237
17462
  import * as path37 from "path";
@@ -17267,7 +17492,7 @@ async function migrateContextToKnowledge(directory, config) {
17267
17492
  const sentinelPath = path37.join(directory, ".swarm", ".knowledge-migrated");
17268
17493
  const contextPath = path37.join(directory, ".swarm", "context.md");
17269
17494
  const knowledgePath = resolveSwarmKnowledgePath(directory);
17270
- if (existsSync21(sentinelPath)) {
17495
+ if (existsSync22(sentinelPath)) {
17271
17496
  return {
17272
17497
  migrated: false,
17273
17498
  entriesMigrated: 0,
@@ -17276,7 +17501,7 @@ async function migrateContextToKnowledge(directory, config) {
17276
17501
  skippedReason: "sentinel-exists"
17277
17502
  };
17278
17503
  }
17279
- if (!existsSync21(contextPath)) {
17504
+ if (!existsSync22(contextPath)) {
17280
17505
  return {
17281
17506
  migrated: false,
17282
17507
  entriesMigrated: 0,
@@ -17368,7 +17593,7 @@ async function migrateHiveKnowledgeLegacy(config) {
17368
17593
  const legacyHivePath = _internals29.resolveLegacyHiveKnowledgePath();
17369
17594
  const canonicalHivePath = resolveHiveKnowledgePath();
17370
17595
  const sentinelPath = path37.join(path37.dirname(canonicalHivePath), ".hive-knowledge-migrated");
17371
- if (existsSync21(sentinelPath)) {
17596
+ if (existsSync22(sentinelPath)) {
17372
17597
  return {
17373
17598
  migrated: false,
17374
17599
  entriesMigrated: 0,
@@ -17377,7 +17602,7 @@ async function migrateHiveKnowledgeLegacy(config) {
17377
17602
  skippedReason: "sentinel-exists"
17378
17603
  };
17379
17604
  }
17380
- if (!existsSync21(legacyHivePath)) {
17605
+ if (!existsSync22(legacyHivePath)) {
17381
17606
  return {
17382
17607
  migrated: false,
17383
17608
  entriesMigrated: 0,
@@ -17579,7 +17804,7 @@ function truncateLesson2(text) {
17579
17804
  }
17580
17805
  function inferProjectName(directory) {
17581
17806
  const packageJsonPath = path37.join(directory, "package.json");
17582
- if (existsSync21(packageJsonPath)) {
17807
+ if (existsSync22(packageJsonPath)) {
17583
17808
  try {
17584
17809
  const pkg = JSON.parse(readFileSync14(packageJsonPath, "utf-8"));
17585
17810
  if (pkg.name && typeof pkg.name === "string") {
@@ -18032,7 +18257,7 @@ ${USAGE7}`;
18032
18257
  }
18033
18258
 
18034
18259
  // src/commands/memory.ts
18035
- import { existsSync as existsSync24 } from "fs";
18260
+ import { existsSync as existsSync25 } from "fs";
18036
18261
  import * as path42 from "path";
18037
18262
  import { fileURLToPath as fileURLToPath2 } from "url";
18038
18263
 
@@ -18130,7 +18355,7 @@ import * as path41 from "path";
18130
18355
 
18131
18356
  // src/memory/local-jsonl-provider.ts
18132
18357
  import { randomUUID as randomUUID5 } from "crypto";
18133
- import { existsSync as existsSync22 } from "fs";
18358
+ import { existsSync as existsSync23 } from "fs";
18134
18359
  import {
18135
18360
  appendFile as appendFile3,
18136
18361
  mkdir as mkdir10,
@@ -18161,7 +18386,26 @@ var SECRET_PATTERNS = [
18161
18386
  },
18162
18387
  {
18163
18388
  type: "env_secret",
18164
- pattern: /\b(?:[A-Z0-9]+_)*(?:KEY|TOKEN|SECRET|PASSWORD)\b\s*=\s*["']?[^\s"'`]{8,}["']?/gi
18389
+ pattern: /\b(?:[A-Z][A-Z0-9]+_)+(?:KEY|TOKEN|SECRET|PASSWORD)\b\s*=\s*["']?[^\s"'`]{8,}["']?/gi
18390
+ },
18391
+ { type: "gitlab_token", pattern: /\bgl(?:pat|ptt)-[A-Za-z0-9_-]{15,}\b/g },
18392
+ { type: "slack_token", pattern: /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g },
18393
+ {
18394
+ type: "jwt_token",
18395
+ pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g
18396
+ },
18397
+ {
18398
+ type: "aws_secret_access_key",
18399
+ pattern: /\b(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)\s*[=:]\s*[A-Za-z0-9/+=]{40}\b/g
18400
+ },
18401
+ {
18402
+ type: "stripe_secret_key",
18403
+ pattern: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g
18404
+ },
18405
+ { type: "google_api_key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
18406
+ {
18407
+ type: "openssh_private_key_block",
18408
+ pattern: /-----BEGIN[ ]OPENSSH[ ]PRIVATE[ ]KEY-----[\s\S]*?-----END[ ]OPENSSH[ ]PRIVATE[ ]KEY-----/g
18165
18409
  }
18166
18410
  ];
18167
18411
  function findSecrets(text) {
@@ -18732,7 +18976,7 @@ function normalizeMemoryAgentRole(agentRole) {
18732
18976
  if (base === "critic" || base === "critic_sounding_board" || base === "critic_drift_verifier" || base === "critic_hallucination_verifier" || base === "critic_architecture_supervisor") {
18733
18977
  return "security";
18734
18978
  }
18735
- if (base === "curator_init" || base === "curator_phase")
18979
+ if (base === "curator_init" || base === "curator_phase" || base === "curator_postmortem")
18736
18980
  return "curator";
18737
18981
  if (base === "docs")
18738
18982
  return "sme";
@@ -18743,6 +18987,17 @@ function normalizeMemoryAgentRole(agentRole) {
18743
18987
  }
18744
18988
 
18745
18989
  // src/memory/scoring.ts
18990
+ var SCORING_WEIGHTS = {
18991
+ textOverlap: 0.38,
18992
+ tagOverlap: 0.16,
18993
+ fileOverlap: 0.12,
18994
+ symbolOverlap: 0.08,
18995
+ taskTermOverlap: 0.08,
18996
+ scopeSpecificityBoost: 0.12,
18997
+ kindProfileBoost: 0.06,
18998
+ roleBoost: 0.05,
18999
+ confidence: 0.08
19000
+ };
18746
19001
  function tokenize(text) {
18747
19002
  return new Set(text.toLowerCase().replace(/[^\w\s-]/g, " ").split(/\s+/).map((token) => token.trim()).filter(Boolean));
18748
19003
  }
@@ -18848,7 +19103,7 @@ function scoreMemoryRecordDetailed(record, request, context) {
18848
19103
  if (request.mode === "injection" && request.requireQuerySignal !== false && !hasQuerySignal) {
18849
19104
  return { item: null, skipReason: "no_signal" };
18850
19105
  }
18851
- const score = textOverlap * 0.38 + tagOverlap * 0.16 + fileOverlap * 0.12 + symbolOverlap * 0.08 + taskTermOverlap * 0.08 + scopeSpecificityBoost(record.scope) * 0.12 + kindProfileBoost(record.kind, request) * 0.06 + roleBoost * 0.05 + record.confidence * 0.08;
19106
+ const score = textOverlap * SCORING_WEIGHTS.textOverlap + tagOverlap * SCORING_WEIGHTS.tagOverlap + fileOverlap * SCORING_WEIGHTS.fileOverlap + symbolOverlap * SCORING_WEIGHTS.symbolOverlap + taskTermOverlap * SCORING_WEIGHTS.taskTermOverlap + scopeSpecificityBoost(record.scope) * SCORING_WEIGHTS.scopeSpecificityBoost + kindProfileBoost(record.kind, request) * SCORING_WEIGHTS.kindProfileBoost + roleBoost * SCORING_WEIGHTS.roleBoost + record.confidence * SCORING_WEIGHTS.confidence;
18852
19107
  const reasonParts = [
18853
19108
  textOverlap > 0 ? `text_overlap=${textOverlap.toFixed(2)}` : null,
18854
19109
  tagOverlap > 0 ? `tag_overlap=${tagOverlap.toFixed(2)}` : null,
@@ -19267,7 +19522,7 @@ function validateLoadedProposals(values, config) {
19267
19522
  return { records, invalidCount };
19268
19523
  }
19269
19524
  async function readJsonl(filePath) {
19270
- if (!existsSync22(filePath))
19525
+ if (!existsSync23(filePath))
19271
19526
  return [];
19272
19527
  const content = await readFile12(filePath, "utf-8");
19273
19528
  const records = [];
@@ -19344,7 +19599,7 @@ import { createRequire as createRequire2 } from "module";
19344
19599
  import * as path40 from "path";
19345
19600
 
19346
19601
  // src/memory/jsonl-migration.ts
19347
- import { existsSync as existsSync23 } from "fs";
19602
+ import { existsSync as existsSync24, renameSync as renameSync10, unlinkSync as unlinkSync6 } from "fs";
19348
19603
  import { copyFile as copyFile2, mkdir as mkdir11, readFile as readFile13, stat as stat5, writeFile as writeFile11 } from "fs/promises";
19349
19604
  import * as path39 from "path";
19350
19605
  var LEGACY_JSONL_MIGRATION_VERSION = 2;
@@ -19378,10 +19633,10 @@ async function backupLegacyJsonl(rootDirectory, config = {}) {
19378
19633
  const results = [];
19379
19634
  for (const filename of ["memories.jsonl", "proposals.jsonl"]) {
19380
19635
  const source = path39.join(storageDir, filename);
19381
- if (!existsSync23(source))
19636
+ if (!existsSync24(source))
19382
19637
  continue;
19383
19638
  const backup = path39.join(backupDir, `${filename}.pre-sqlite-migration`);
19384
- if (existsSync23(backup)) {
19639
+ if (existsSync24(backup)) {
19385
19640
  results.push({ source, backup, created: false });
19386
19641
  continue;
19387
19642
  }
@@ -19395,20 +19650,47 @@ async function writeJsonlExport(rootDirectory, config, memories, proposals) {
19395
19650
  await mkdir11(exportDir, { recursive: true });
19396
19651
  const memoriesPath = path39.join(exportDir, "memories.jsonl");
19397
19652
  const proposalsPath = path39.join(exportDir, "proposals.jsonl");
19398
- await writeFile11(memoriesPath, toJsonl(memories), "utf-8");
19399
- await writeFile11(proposalsPath, toJsonl(proposals), "utf-8");
19653
+ const memoriesTempPath = path39.join(path39.dirname(memoriesPath), `${path39.basename(memoriesPath)}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
19654
+ try {
19655
+ await writeFile11(memoriesTempPath, toJsonl(memories), "utf-8");
19656
+ renameSync10(memoriesTempPath, memoriesPath);
19657
+ } catch (err) {
19658
+ try {
19659
+ unlinkSync6(memoriesTempPath);
19660
+ } catch {}
19661
+ throw err;
19662
+ }
19663
+ const proposalsTempPath = path39.join(path39.dirname(proposalsPath), `${path39.basename(proposalsPath)}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
19664
+ try {
19665
+ await writeFile11(proposalsTempPath, toJsonl(proposals), "utf-8");
19666
+ renameSync10(proposalsTempPath, proposalsPath);
19667
+ } catch (err) {
19668
+ try {
19669
+ unlinkSync6(proposalsTempPath);
19670
+ } catch {}
19671
+ throw err;
19672
+ }
19400
19673
  return { directory: exportDir, memoriesPath, proposalsPath };
19401
19674
  }
19402
19675
  async function writeMigrationReport(rootDirectory, report, config = {}) {
19403
19676
  const reportPath = path39.join(resolveMemoryStorageDir(rootDirectory, config), "migration-report.json");
19404
19677
  await mkdir11(path39.dirname(reportPath), { recursive: true });
19405
- await writeFile11(reportPath, `${JSON.stringify(report, null, 2)}
19678
+ const reportTempPath = path39.join(path39.dirname(reportPath), `${path39.basename(reportPath)}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
19679
+ try {
19680
+ await writeFile11(reportTempPath, `${JSON.stringify(report, null, 2)}
19406
19681
  `, "utf-8");
19682
+ renameSync10(reportTempPath, reportPath);
19683
+ } catch (err) {
19684
+ try {
19685
+ unlinkSync6(reportTempPath);
19686
+ } catch {}
19687
+ throw err;
19688
+ }
19407
19689
  return reportPath;
19408
19690
  }
19409
19691
  async function readMigrationReport(rootDirectory, config = {}) {
19410
19692
  const reportPath = path39.join(resolveMemoryStorageDir(rootDirectory, config), "migration-report.json");
19411
- if (!existsSync23(reportPath))
19693
+ if (!existsSync24(reportPath))
19412
19694
  return null;
19413
19695
  try {
19414
19696
  return JSON.parse(await readFile13(reportPath, "utf-8"));
@@ -19422,13 +19704,13 @@ async function getLegacyJsonlFileStatus(rootDirectory, config = {}) {
19422
19704
  for (const file of ["memories.jsonl", "proposals.jsonl"]) {
19423
19705
  const filePath = path39.join(storageDir, file);
19424
19706
  let sizeBytes = 0;
19425
- if (existsSync23(filePath)) {
19707
+ if (existsSync24(filePath)) {
19426
19708
  sizeBytes = (await stat5(filePath)).size;
19427
19709
  }
19428
19710
  statuses.push({
19429
19711
  file,
19430
19712
  path: filePath,
19431
- exists: existsSync23(filePath),
19713
+ exists: existsSync24(filePath),
19432
19714
  sizeBytes
19433
19715
  });
19434
19716
  }
@@ -19509,7 +19791,7 @@ async function readProposalJsonl(filePath, config) {
19509
19791
  return { records, invalidRows, totalRows: rows.totalRows };
19510
19792
  }
19511
19793
  async function readJsonlRows(filePath) {
19512
- if (!existsSync23(filePath)) {
19794
+ if (!existsSync24(filePath)) {
19513
19795
  return { rows: [], invalidRows: [], totalRows: 0 };
19514
19796
  }
19515
19797
  const content = await readFile13(filePath, "utf-8");
@@ -19548,7 +19830,6 @@ function loadDatabaseCtor2() {
19548
19830
  _DatabaseCtor2 = req("bun:sqlite").Database;
19549
19831
  return _DatabaseCtor2;
19550
19832
  }
19551
- var FTS_SCHEMA_MIGRATION_VERSION = 3;
19552
19833
  var FTS_SCHEMA_MIGRATION_NAME = "create_memory_fts5_shadow_index";
19553
19834
  var FTS_TABLE_NAME = "memory_items_fts";
19554
19835
  var FTS_INDEX_COLUMNS = [
@@ -19637,6 +19918,15 @@ var MIGRATIONS2 = [
19637
19918
  CREATE INDEX IF NOT EXISTS idx_memory_recall_usage_bundle
19638
19919
  ON memory_recall_usage(bundle_id);
19639
19920
  `
19921
+ },
19922
+ {
19923
+ version: 3,
19924
+ name: "create_memory_fts5_shadow_index",
19925
+ sql: `
19926
+ CREATE VIRTUAL TABLE IF NOT EXISTS ${FTS_TABLE_NAME} USING fts5(
19927
+ ${ftsCreateColumnsSql()}
19928
+ );
19929
+ `
19640
19930
  }
19641
19931
  ];
19642
19932
 
@@ -19645,6 +19935,7 @@ class SQLiteMemoryProvider {
19645
19935
  rootDirectory;
19646
19936
  config;
19647
19937
  initialized = false;
19938
+ initPromise = null;
19648
19939
  db = null;
19649
19940
  ftsAvailable = false;
19650
19941
  memories = new Map;
@@ -19688,6 +19979,15 @@ class SQLiteMemoryProvider {
19688
19979
  async initialize() {
19689
19980
  if (this.initialized)
19690
19981
  return;
19982
+ if (!this.initPromise) {
19983
+ this.initPromise = this.doInitialize().catch((err) => {
19984
+ this.initPromise = null;
19985
+ throw err;
19986
+ });
19987
+ }
19988
+ return this.initPromise;
19989
+ }
19990
+ async doInitialize() {
19691
19991
  const dbPath = this.databasePath();
19692
19992
  mkdirSync15(path40.dirname(dbPath), { recursive: true });
19693
19993
  const Db = loadDatabaseCtor2();
@@ -19883,6 +20183,7 @@ class SQLiteMemoryProvider {
19883
20183
  this.db = null;
19884
20184
  this.ftsAvailable = false;
19885
20185
  this.initialized = false;
20186
+ this.initPromise = null;
19886
20187
  this.lastAutomaticJsonlMigration = null;
19887
20188
  }
19888
20189
  async importJsonl() {
@@ -20019,8 +20320,6 @@ class SQLiteMemoryProvider {
20019
20320
  try {
20020
20321
  if (!this.hasMigration(FTS_SCHEMA_MIGRATION_NAME)) {
20021
20322
  this.recreateFtsIndex();
20022
- this.markMigration(FTS_SCHEMA_MIGRATION_VERSION, FTS_SCHEMA_MIGRATION_NAME);
20023
- this.insertEvent("migration", String(FTS_SCHEMA_MIGRATION_VERSION), FTS_SCHEMA_MIGRATION_NAME);
20024
20323
  } else {
20025
20324
  db.run(`CREATE VIRTUAL TABLE IF NOT EXISTS ${FTS_TABLE_NAME} USING fts5(
20026
20325
  ${ftsCreateColumnsSql()}
@@ -20353,12 +20652,60 @@ class SQLiteMemoryProvider {
20353
20652
  }
20354
20653
  requireDb() {
20355
20654
  if (!this.db)
20356
- throw new Error("SQLite memory provider is not initialized");
20655
+ throw new MemoryValidationError("SQLite memory provider is not initialized", "provider_not_initialized");
20357
20656
  return this.db;
20358
20657
  }
20359
20658
  }
20360
20659
  function splitSql(sql) {
20361
- return sql.split(";").map((statement) => statement.trim()).filter(Boolean);
20660
+ const statements = [];
20661
+ let current = "";
20662
+ let inSingleQuote = false;
20663
+ let inLineComment = false;
20664
+ for (let i = 0;i < sql.length; i++) {
20665
+ const char = sql[i];
20666
+ const next = sql[i + 1];
20667
+ if (inLineComment) {
20668
+ if (char === `
20669
+ `) {
20670
+ inLineComment = false;
20671
+ }
20672
+ continue;
20673
+ }
20674
+ if (inSingleQuote) {
20675
+ if (char === "'" && next === "'") {
20676
+ current += "''";
20677
+ i++;
20678
+ continue;
20679
+ }
20680
+ current += char;
20681
+ if (char === "'") {
20682
+ inSingleQuote = false;
20683
+ }
20684
+ continue;
20685
+ }
20686
+ if (char === "-" && next === "-") {
20687
+ inLineComment = true;
20688
+ i++;
20689
+ continue;
20690
+ }
20691
+ if (char === "'") {
20692
+ inSingleQuote = true;
20693
+ current += char;
20694
+ continue;
20695
+ }
20696
+ if (char === ";") {
20697
+ const trimmed2 = current.trim();
20698
+ if (trimmed2)
20699
+ statements.push(trimmed2);
20700
+ current = "";
20701
+ continue;
20702
+ }
20703
+ current += char;
20704
+ }
20705
+ const trimmed = current.trim();
20706
+ if (trimmed)
20707
+ statements.push(trimmed);
20708
+ return statements;
20362
20709
  }
20363
20710
  var FTS_STOP_WORDS = new Set([
20364
20711
  "a",
@@ -20834,7 +21181,7 @@ async function handleMemoryStatusCommand(directory, _args) {
20834
21181
  `- Provider: \`${config.provider}\``,
20835
21182
  `- Storage: \`${storageDir}\``,
20836
21183
  `- SQLite path: \`${sqlitePath}\``,
20837
- `- SQLite database exists: \`${existsSync24(sqlitePath)}\``,
21184
+ `- SQLite database exists: \`${existsSync25(sqlitePath)}\``,
20838
21185
  `- Automatic destructive cleanup: \`disabled\``,
20839
21186
  "",
20840
21187
  "### Legacy JSONL"
@@ -21087,7 +21434,16 @@ function parseEvaluateArgs(directory, args) {
21087
21434
  error: "Usage: /swarm memory evaluate [--json] [--fixtures <directory>]"
21088
21435
  };
21089
21436
  }
21090
- fixtureDirectory = path42.resolve(directory, next);
21437
+ const resolvedFixtures = path42.resolve(directory, next);
21438
+ const canonical = path42.normalize(resolvedFixtures) + path42.sep;
21439
+ const allowedRootA = path42.normalize(directory) + path42.sep;
21440
+ const allowedRootB = path42.normalize(path42.join(PACKAGE_ROOT, "tests", "fixtures", "memory-recall")) + path42.sep;
21441
+ if (!canonical.startsWith(allowedRootA) && !canonical.startsWith(allowedRootB)) {
21442
+ return {
21443
+ error: "--fixtures <directory> must resolve under the project directory or the bundled tests/fixtures/memory-recall directory"
21444
+ };
21445
+ }
21446
+ fixtureDirectory = resolvedFixtures;
21091
21447
  i++;
21092
21448
  continue;
21093
21449
  }
@@ -24373,19 +24729,19 @@ function hasCompoundTestExtension(filename) {
24373
24729
  const lower = filename.toLowerCase();
24374
24730
  return COMPOUND_TEST_EXTENSIONS.some((ext) => lower.endsWith(ext));
24375
24731
  }
24376
- function isLanguageSpecificTestFile(basename8) {
24377
- const lower = basename8.toLowerCase();
24732
+ function isLanguageSpecificTestFile(basename9) {
24733
+ const lower = basename9.toLowerCase();
24378
24734
  if (lower.endsWith("_test.go"))
24379
24735
  return true;
24380
24736
  if (lower.endsWith(".py") && (lower.startsWith("test_") || lower.endsWith("_test.py")))
24381
24737
  return true;
24382
24738
  if (lower.endsWith("_spec.rb"))
24383
24739
  return true;
24384
- if (lower.endsWith(".java") && (/^Test[A-Z]/.test(basename8) || basename8.endsWith("Test.java") || basename8.endsWith("Tests.java") || lower.endsWith("it.java")))
24740
+ if (lower.endsWith(".java") && (/^Test[A-Z]/.test(basename9) || basename9.endsWith("Test.java") || basename9.endsWith("Tests.java") || lower.endsWith("it.java")))
24385
24741
  return true;
24386
24742
  if (lower.endsWith(".cs") && (lower.endsWith("test.cs") || lower.endsWith("tests.cs")))
24387
24743
  return true;
24388
- if (lower.endsWith(".kt") && (/^Test[A-Z]/.test(basename8) || lower.endsWith("test.kt") || lower.endsWith("tests.kt")))
24744
+ if (lower.endsWith(".kt") && (/^Test[A-Z]/.test(basename9) || lower.endsWith("test.kt") || lower.endsWith("tests.kt")))
24389
24745
  return true;
24390
24746
  if (lower.endsWith(".tests.ps1"))
24391
24747
  return true;
@@ -24393,23 +24749,23 @@ function isLanguageSpecificTestFile(basename8) {
24393
24749
  }
24394
24750
  function isConventionTestFilePath(filePath) {
24395
24751
  const normalizedPath = filePath.replace(/\\/g, "/");
24396
- const basename8 = path48.basename(filePath);
24397
- return hasCompoundTestExtension(basename8) || basename8.includes(".spec.") || basename8.includes(".test.") || isLanguageSpecificTestFile(basename8) || isTestDirectoryPath(normalizedPath);
24752
+ const basename9 = path48.basename(filePath);
24753
+ return hasCompoundTestExtension(basename9) || basename9.includes(".spec.") || basename9.includes(".test.") || isLanguageSpecificTestFile(basename9) || isTestDirectoryPath(normalizedPath);
24398
24754
  }
24399
24755
  function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
24400
24756
  const testFiles = [];
24401
24757
  for (const file of sourceFiles) {
24402
24758
  const absoluteFile = resolveWorkspacePath(file, workingDir);
24403
24759
  const relativeFile = path48.relative(workingDir, absoluteFile);
24404
- const basename8 = path48.basename(absoluteFile);
24760
+ const basename9 = path48.basename(absoluteFile);
24405
24761
  const dirname23 = path48.dirname(absoluteFile);
24406
24762
  const preferRelativeOutput = !path48.isAbsolute(file);
24407
24763
  if (isConventionTestFilePath(relativeFile) || isConventionTestFilePath(file)) {
24408
24764
  dedupePush(testFiles, toWorkspaceOutputPath(absoluteFile, workingDir, preferRelativeOutput));
24409
24765
  continue;
24410
24766
  }
24411
- const nameWithoutExt = basename8.replace(/\.[^.]+$/, "");
24412
- const ext = path48.extname(basename8);
24767
+ const nameWithoutExt = basename9.replace(/\.[^.]+$/, "");
24768
+ const ext = path48.extname(basename9);
24413
24769
  const genericTestNames = [
24414
24770
  `${nameWithoutExt}.spec${ext}`,
24415
24771
  `${nameWithoutExt}.test${ext}`
@@ -24420,7 +24776,7 @@ function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
24420
24776
  ...languageSpecificTestNames
24421
24777
  ].map((candidateName) => path48.join(dirname23, candidateName));
24422
24778
  const testDirectoryNames = [
24423
- basename8,
24779
+ basename9,
24424
24780
  ...genericTestNames,
24425
24781
  ...languageSpecificTestNames
24426
24782
  ];
@@ -27258,7 +27614,9 @@ async function handleResetCommand(directory, args) {
27258
27614
  fs25.unlinkSync(rootPath);
27259
27615
  results.push(`- \u2705 Deleted ${filename} (root)`);
27260
27616
  }
27261
- } catch {}
27617
+ } catch (err) {
27618
+ results.push(`- \u274C Failed to delete ${filename}: ${err instanceof Error ? err.message : String(err)}`);
27619
+ }
27262
27620
  }
27263
27621
  try {
27264
27622
  resetAutomationManager();
@@ -27781,6 +28139,11 @@ function resetPrmSessionState(session, sessionId) {
27781
28139
  }
27782
28140
 
27783
28141
  // src/commands/reset-session.ts
28142
+ function errorMessage(err) {
28143
+ if (err instanceof Error)
28144
+ return err.message;
28145
+ return String(err);
28146
+ }
27784
28147
  async function handleResetSessionCommand(directory, _args) {
27785
28148
  const results = [];
27786
28149
  try {
@@ -27794,22 +28157,30 @@ async function handleResetSessionCommand(directory, _args) {
27794
28157
  } catch {
27795
28158
  results.push("\u274C Failed to delete state.json");
27796
28159
  }
27797
- try {
27798
- const sessionDir = path52.dirname(validateSwarmPath(directory, "session/state.json"));
27799
- if (fs27.existsSync(sessionDir)) {
27800
- const files = fs27.readdirSync(sessionDir);
27801
- const otherFiles = files.filter((f) => f !== "state.json");
27802
- let deletedCount = 0;
27803
- for (const file of otherFiles) {
27804
- const filePath = path52.join(sessionDir, file);
27805
- if (fs27.lstatSync(filePath).isFile()) {
27806
- fs27.unlinkSync(filePath);
27807
- deletedCount++;
27808
- }
27809
- }
27810
- results.push(`\u2705 Cleaned ${deletedCount} additional session file(s)`);
28160
+ const sessionDir = path52.dirname(validateSwarmPath(directory, "session/state.json"));
28161
+ let sessionFiles = [];
28162
+ if (fs27.existsSync(sessionDir)) {
28163
+ try {
28164
+ sessionFiles = fs27.readdirSync(sessionDir);
28165
+ } catch (err) {
28166
+ results.push(`\u274C Failed to read session directory: ${errorMessage(err)}`);
27811
28167
  }
27812
- } catch {}
28168
+ }
28169
+ for (const file of sessionFiles) {
28170
+ if (file === "state.json")
28171
+ continue;
28172
+ const filePath = path52.join(sessionDir, file);
28173
+ try {
28174
+ if (!fs27.existsSync(filePath))
28175
+ continue;
28176
+ if (!fs27.lstatSync(filePath).isFile())
28177
+ continue;
28178
+ fs27.unlinkSync(filePath);
28179
+ results.push(`\u2713 Deleted ${file}`);
28180
+ } catch (err) {
28181
+ results.push(`\u274C Failed to delete ${file}: ${errorMessage(err)}`);
28182
+ }
28183
+ }
27813
28184
  const sessionCount = swarmState.agentSessions.size;
27814
28185
  for (const [sessionId, session] of swarmState.agentSessions) {
27815
28186
  resetPrmSessionState(session, sessionId);
@@ -27971,6 +28342,7 @@ async function handleRollbackCommand(directory, args) {
27971
28342
  ]);
27972
28343
  const successes = [];
27973
28344
  const failures = [];
28345
+ const warnings = [];
27974
28346
  for (const file of checkpointFiles) {
27975
28347
  if (EXCLUDE_FILES.has(file) || file.startsWith("plan-ledger.archived-")) {
27976
28348
  continue;
@@ -27996,30 +28368,39 @@ async function handleRollbackCommand(directory, args) {
27996
28368
  `);
27997
28369
  }
27998
28370
  const existingLedgerPath = path54.join(swarmDir, "plan-ledger.jsonl");
28371
+ let ledgerDeletionFailed = false;
27999
28372
  if (fs28.existsSync(existingLedgerPath)) {
28000
- fs28.unlinkSync(existingLedgerPath);
28001
- }
28002
- try {
28003
- const planJsonPath = path54.join(swarmDir, "plan.json");
28004
- if (fs28.existsSync(planJsonPath)) {
28005
- const planRaw = fs28.readFileSync(planJsonPath, "utf-8");
28006
- const plan = PlanSchema.parse(JSON.parse(planRaw));
28007
- const planId = derivePlanId(plan);
28008
- const planHash = computePlanHash(plan);
28009
- await initLedger(directory, planId, planHash, plan);
28010
- await appendLedgerEvent(directory, {
28011
- event_type: "plan_rebuilt",
28012
- source: "rollback",
28013
- plan_id: planId
28014
- });
28373
+ try {
28374
+ fs28.unlinkSync(existingLedgerPath);
28375
+ } catch (err) {
28376
+ ledgerDeletionFailed = true;
28377
+ const errMsg = err instanceof Error ? err.message : String(err);
28378
+ warnings.push(`\u26A0\uFE0F Warning: Could not delete stale ledger (${errMsg}). The ledger may be inconsistent with the restored plan. Run /swarm reset-session to clean up session state.`);
28015
28379
  }
28016
- } catch (initError) {
28017
- return [
28018
- `Rollback restored files but failed to initialize ledger: ${initError instanceof Error ? initError.message : String(initError)}`,
28019
- "The .swarm/plan.json has been restored but the ledger may be out of sync.",
28020
- "Run /swarm reset-session to reinitialize the ledger."
28021
- ].join(`
28380
+ }
28381
+ if (!ledgerDeletionFailed) {
28382
+ try {
28383
+ const planJsonPath = path54.join(swarmDir, "plan.json");
28384
+ if (fs28.existsSync(planJsonPath)) {
28385
+ const planRaw = fs28.readFileSync(planJsonPath, "utf-8");
28386
+ const plan = PlanSchema.parse(JSON.parse(planRaw));
28387
+ const planId = derivePlanId(plan);
28388
+ const planHash = computePlanHash(plan);
28389
+ await initLedger(directory, planId, planHash, plan);
28390
+ await appendLedgerEvent(directory, {
28391
+ event_type: "plan_rebuilt",
28392
+ source: "rollback",
28393
+ plan_id: planId
28394
+ });
28395
+ }
28396
+ } catch (initError) {
28397
+ return [
28398
+ `Rollback restored files but failed to initialize ledger: ${initError instanceof Error ? initError.message : String(initError)}`,
28399
+ "The .swarm/plan.json has been restored but the ledger may be out of sync.",
28400
+ "Run /swarm reset-session to reinitialize the ledger."
28401
+ ].join(`
28022
28402
  `);
28403
+ }
28023
28404
  }
28024
28405
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
28025
28406
  const rollbackEvent = {
@@ -28034,6 +28415,14 @@ async function handleRollbackCommand(directory, args) {
28034
28415
  } catch (error2) {
28035
28416
  console.error("Failed to write rollback event:", error2 instanceof Error ? error2.message : String(error2));
28036
28417
  }
28418
+ if (warnings.length > 0) {
28419
+ return [
28420
+ ...warnings,
28421
+ "",
28422
+ `Rolled back to phase ${targetPhase}: ${checkpoint2.label || "no label"}`
28423
+ ].join(`
28424
+ `);
28425
+ }
28037
28426
  return `Rolled back to phase ${targetPhase}: ${checkpoint2.label || "no label"}`;
28038
28427
  }
28039
28428
 
@@ -28203,6 +28592,7 @@ async function handleSddCommand(_directory, _args) {
28203
28592
  }
28204
28593
 
28205
28594
  // src/commands/simulate.ts
28595
+ import { renameSync as renameSync11, unlinkSync as unlinkSync11 } from "fs";
28206
28596
  async function handleSimulateCommand(directory, args) {
28207
28597
  const thresholdIndex = args.indexOf("--threshold");
28208
28598
  const minCommitsIndex = args.indexOf("--min-commits");
@@ -28254,7 +28644,16 @@ Ensure this is a git repository with commit history.`;
28254
28644
  const path55 = await import("path");
28255
28645
  const reportPath = path55.join(directory, ".swarm", "simulate-report.md");
28256
28646
  await fs29.mkdir(path55.dirname(reportPath), { recursive: true });
28257
- await fs29.writeFile(reportPath, report, "utf-8");
28647
+ const reportTempPath = path55.join(path55.dirname(reportPath), `${path55.basename(reportPath)}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
28648
+ try {
28649
+ await fs29.writeFile(reportTempPath, report, "utf-8");
28650
+ renameSync11(reportTempPath, reportPath);
28651
+ } catch (err) {
28652
+ try {
28653
+ unlinkSync11(reportTempPath);
28654
+ } catch {}
28655
+ throw err;
28656
+ }
28258
28657
  } catch (err) {
28259
28658
  const writeErr = err instanceof Error ? err.message : String(err);
28260
28659
  warn(`simulate: failed to write report to ${directory}/.swarm/simulate-report.md`, writeErr);
@@ -29151,7 +29550,7 @@ function buildDetailedHelp(commandName, entry) {
29151
29550
  async function handleHelpCommand(ctx) {
29152
29551
  const targetCommand = ctx.args.join(" ");
29153
29552
  if (!targetCommand) {
29154
- const { buildHelpText } = await import("./index-0m44n5qv.js");
29553
+ const { buildHelpText } = await import("./index-gjdq4na6.js");
29155
29554
  return buildHelpText();
29156
29555
  }
29157
29556
  const tokens = targetCommand.split(/\s+/);
@@ -29160,7 +29559,7 @@ async function handleHelpCommand(ctx) {
29160
29559
  return _internals44.buildDetailedHelp(resolved.key, resolved.entry);
29161
29560
  }
29162
29561
  const similar = _internals44.findSimilarCommands(targetCommand);
29163
- const { buildHelpText: fullHelp } = await import("./index-0m44n5qv.js");
29562
+ const { buildHelpText: fullHelp } = await import("./index-gjdq4na6.js");
29164
29563
  if (similar.length > 0) {
29165
29564
  return `Command '/swarm ${targetCommand}' not found.
29166
29565
 
@@ -29293,7 +29692,7 @@ var COMMAND_REGISTRY = {
29293
29692
  },
29294
29693
  "guardrail explain": {
29295
29694
  handler: async (ctx) => {
29296
- const { handleGuardrailExplain } = await import("./guardrail-explain-wb1cj312.js");
29695
+ const { handleGuardrailExplain } = await import("./guardrail-explain-9fngqx80.js");
29297
29696
  return handleGuardrailExplain(ctx.directory, ctx.args);
29298
29697
  },
29299
29698
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",