opencode-swarm 7.39.0 → 7.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -34,7 +34,7 @@ var package_default;
34
34
  var init_package = __esm(() => {
35
35
  package_default = {
36
36
  name: "opencode-swarm",
37
- version: "7.39.0",
37
+ version: "7.41.0",
38
38
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
39
39
  main: "dist/index.js",
40
40
  types: "dist/index.d.ts",
@@ -17643,6 +17643,13 @@ var init_schema = __esm(() => {
17643
17643
  redaction: exports_external.object({
17644
17644
  rejectDurableSecrets: exports_external.boolean().default(true)
17645
17645
  }).default({ rejectDurableSecrets: true }),
17646
+ maintenance: exports_external.object({
17647
+ lowUtilityMaxConfidence: exports_external.number().min(0).max(1).default(0.45),
17648
+ lowUtilityMinAgeDays: exports_external.number().int().min(1).max(3650).default(30)
17649
+ }).default({
17650
+ lowUtilityMaxConfidence: 0.45,
17651
+ lowUtilityMinAgeDays: 30
17652
+ }),
17646
17653
  hardDelete: exports_external.boolean().default(false)
17647
17654
  });
17648
17655
  CuratorConfigSchema = exports_external.object({
@@ -45408,6 +45415,10 @@ function resolveMemoryConfig(input) {
45408
45415
  redaction: {
45409
45416
  ...DEFAULT_MEMORY_CONFIG.redaction,
45410
45417
  ...input?.redaction ?? {}
45418
+ },
45419
+ maintenance: {
45420
+ ...DEFAULT_MEMORY_CONFIG.maintenance,
45421
+ ...input?.maintenance ?? {}
45411
45422
  }
45412
45423
  };
45413
45424
  }
@@ -45439,6 +45450,10 @@ var init_config3 = __esm(() => {
45439
45450
  redaction: {
45440
45451
  rejectDurableSecrets: true
45441
45452
  },
45453
+ maintenance: {
45454
+ lowUtilityMaxConfidence: 0.45,
45455
+ lowUtilityMinAgeDays: 30
45456
+ },
45442
45457
  hardDelete: false
45443
45458
  };
45444
45459
  DURABLE_MEMORY_KINDS = new Set([
@@ -45760,6 +45775,17 @@ function validateDecisionMatchesProposal(decision, proposal) {
45760
45775
  throw new MemoryValidationError("curator supersede decision target does not match proposal target");
45761
45776
  }
45762
45777
  }
45778
+ function validateCuratorPromotableMemory(record3) {
45779
+ if (record3.stability !== "durable") {
45780
+ throw new MemoryValidationError("curator memory promotions must be durable facts");
45781
+ }
45782
+ if (!DURABLE_MEMORY_KINDS.has(record3.kind)) {
45783
+ throw new MemoryValidationError("curator memory promotions must use durable fact kinds; store raw docs, search results, and other bulky source material as evidence records instead");
45784
+ }
45785
+ if (normalizeMemoryText(record3.text).length > CURATOR_PROMOTED_MEMORY_MAX_TEXT_LENGTH) {
45786
+ throw new MemoryValidationError(`curator memory promotions must be concise durable facts under ${CURATOR_PROMOTED_MEMORY_MAX_TEXT_LENGTH} characters`);
45787
+ }
45788
+ }
45763
45789
  function applyPatchToMemory(existing, patch, updatedAt) {
45764
45790
  const base = {
45765
45791
  scope: patch.scope ?? existing.scope,
@@ -45817,11 +45843,174 @@ function buildCuratorDecisionEvent(change, proposal) {
45817
45843
  function normalizeTags(tags) {
45818
45844
  return Array.from(new Set(tags.map((tag) => tag.toLowerCase().replace(/[^\w-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")).filter(Boolean))).slice(0, 32);
45819
45845
  }
45846
+ var CURATOR_PROMOTED_MEMORY_MAX_TEXT_LENGTH = 500;
45820
45847
  var init_curator_decision_helpers = __esm(() => {
45848
+ init_config3();
45821
45849
  init_errors6();
45822
45850
  init_schema2();
45823
45851
  });
45824
45852
 
45853
+ // src/memory/maintenance.ts
45854
+ async function buildMemoryMaintenanceReport(provider, options = {}) {
45855
+ const now = options.now ?? new Date;
45856
+ const limit = Math.max(1, Math.trunc(options.limit ?? 20));
45857
+ const memories = await provider.list({
45858
+ includeExpired: true,
45859
+ includeInactive: true
45860
+ });
45861
+ const proposals = await loadMaintenanceProposals(provider, limit);
45862
+ const recallUsage = provider.listRecallUsage ? await provider.listRecallUsage() : [];
45863
+ const usageByMemory = summarizeRecallByMemory(recallUsage);
45864
+ const usageByRole = summarizeRecallByRole(recallUsage);
45865
+ const activeMemories = memories.filter((memory) => isActiveMemory(memory, now));
45866
+ const deletedMemories = memories.filter((memory) => memory.metadata.deleted === true);
45867
+ const expiredScratchMemories = memories.filter((memory) => memory.kind === "scratch" && isExpired(memory, now));
45868
+ const supersededMemories = memories.filter((memory) => Boolean(memory.supersededBy));
45869
+ const lowUtilityMemories = activeMemories.filter((memory) => isLowUtility(memory, usageByMemory, now, {
45870
+ maxConfidence: options.lowUtilityMaxConfidence ?? DEFAULT_LOW_UTILITY_MAX_CONFIDENCE,
45871
+ minAgeDays: options.lowUtilityMinAgeDays ?? DEFAULT_LOW_UTILITY_MIN_AGE_DAYS
45872
+ })).sort(memorySort);
45873
+ const neverRecalledMemories = activeMemories.filter((memory) => !usageByMemory.has(memory.id)).sort(memorySort);
45874
+ const rejectedProposalReasons = proposals.filter((proposal) => proposal.status === "rejected").sort(proposalSort);
45875
+ const pendingProposals = proposals.filter((proposal) => proposal.status === "pending").sort(proposalSort);
45876
+ return {
45877
+ generatedAt: now.toISOString(),
45878
+ totalMemories: memories.length,
45879
+ activeMemories: activeMemories.length,
45880
+ deletedMemories: deletedMemories.slice(0, limit),
45881
+ expiredScratchMemories: expiredScratchMemories.slice(0, limit),
45882
+ supersededMemories: supersededMemories.slice(0, limit),
45883
+ supersededChains: buildSupersededChains(memories).slice(0, limit),
45884
+ lowUtilityMemories: lowUtilityMemories.slice(0, limit),
45885
+ neverRecalledMemories: neverRecalledMemories.slice(0, limit),
45886
+ mostRecalledMemories: Array.from(usageByMemory.values()).sort((a, b) => b.count - a.count || b.lastRecalledAt.localeCompare(a.lastRecalledAt) || a.memoryId.localeCompare(b.memoryId)).slice(0, limit),
45887
+ recallByAgentRole: Array.from(usageByRole.values()).sort((a, b) => b.count - a.count || a.agentRole.localeCompare(b.agentRole)).slice(0, limit),
45888
+ rejectedProposalReasons: rejectedProposalReasons.slice(0, limit),
45889
+ pendingProposals: pendingProposals.slice(0, limit),
45890
+ recallEventCount: recallUsage.length
45891
+ };
45892
+ }
45893
+ function shouldCompactMemory(memory, now = new Date) {
45894
+ if (memory.metadata.deleted === true)
45895
+ return "deleted";
45896
+ if (memory.supersededBy)
45897
+ return "superseded";
45898
+ if (memory.kind === "scratch" && isExpired(memory, now)) {
45899
+ return "expired_scratch";
45900
+ }
45901
+ return null;
45902
+ }
45903
+ function isActiveMemory(memory, now) {
45904
+ return memory.metadata.deleted !== true && !memory.supersededBy && !isExpired(memory, now);
45905
+ }
45906
+ function isLowUtility(memory, usageByMemory, now, options) {
45907
+ if (usageByMemory.has(memory.id))
45908
+ return false;
45909
+ const updated = Date.parse(memory.updatedAt);
45910
+ const ageDays = Number.isFinite(updated) ? (now.getTime() - updated) / (24 * 60 * 60 * 1000) : 0;
45911
+ return memory.confidence <= options.maxConfidence || ageDays >= options.minAgeDays;
45912
+ }
45913
+ function summarizeRecallByMemory(usageEvents) {
45914
+ const byMemory = new Map;
45915
+ for (const event of usageEvents) {
45916
+ event.memoryIds.forEach((memoryId, index) => {
45917
+ const role = event.agentRole ?? "unknown";
45918
+ const existing = byMemory.get(memoryId) ?? {
45919
+ memoryId,
45920
+ count: 0,
45921
+ lastRecalledAt: event.timestamp,
45922
+ agentRoles: {},
45923
+ averageScore: 0,
45924
+ scoreTotal: 0,
45925
+ scoreCount: 0
45926
+ };
45927
+ existing.count++;
45928
+ existing.lastRecalledAt = event.timestamp > existing.lastRecalledAt ? event.timestamp : existing.lastRecalledAt;
45929
+ existing.agentRoles[role] = (existing.agentRoles[role] ?? 0) + 1;
45930
+ const score = event.scores[index];
45931
+ if (typeof score === "number" && Number.isFinite(score)) {
45932
+ existing.scoreTotal += score;
45933
+ existing.scoreCount++;
45934
+ existing.averageScore = existing.scoreTotal / existing.scoreCount;
45935
+ }
45936
+ byMemory.set(memoryId, existing);
45937
+ });
45938
+ }
45939
+ return new Map(Array.from(byMemory, ([memoryId, value]) => [
45940
+ memoryId,
45941
+ {
45942
+ memoryId,
45943
+ count: value.count,
45944
+ lastRecalledAt: value.lastRecalledAt,
45945
+ agentRoles: value.agentRoles,
45946
+ averageScore: value.averageScore
45947
+ }
45948
+ ]));
45949
+ }
45950
+ async function loadMaintenanceProposals(provider, limit) {
45951
+ if (!provider.listProposals)
45952
+ return [];
45953
+ const [pending, rejected, recent] = await Promise.all([
45954
+ provider.listProposals({ status: "pending", limit }),
45955
+ provider.listProposals({ status: "rejected", limit }),
45956
+ provider.listProposals({ limit: Math.max(limit * 4, 100) })
45957
+ ]);
45958
+ const byId = new Map;
45959
+ for (const proposal of [...pending, ...rejected, ...recent]) {
45960
+ byId.set(proposal.id, proposal);
45961
+ }
45962
+ return Array.from(byId.values());
45963
+ }
45964
+ function summarizeRecallByRole(usageEvents) {
45965
+ const byRole = new Map;
45966
+ for (const event of usageEvents) {
45967
+ const role = event.agentRole ?? "unknown";
45968
+ const existing = byRole.get(role) ?? {
45969
+ agentRole: role,
45970
+ count: 0,
45971
+ memoryIds: {}
45972
+ };
45973
+ existing.count++;
45974
+ for (const memoryId of event.memoryIds) {
45975
+ existing.memoryIds[memoryId] = (existing.memoryIds[memoryId] ?? 0) + 1;
45976
+ }
45977
+ byRole.set(role, existing);
45978
+ }
45979
+ return byRole;
45980
+ }
45981
+ function buildSupersededChains(memories) {
45982
+ const byId = new Map(memories.map((memory) => [memory.id, memory]));
45983
+ const supersededIds = new Set(memories.filter((memory) => memory.supersededBy).map((memory) => memory.id));
45984
+ const roots = memories.filter((memory) => memory.supersededBy && !(memory.supersedes ?? []).some((id) => supersededIds.has(id)));
45985
+ return roots.map((root) => {
45986
+ const chain = [root.id];
45987
+ const seen = new Set(chain);
45988
+ let cursor = root;
45989
+ while (cursor?.supersededBy && !seen.has(cursor.supersededBy)) {
45990
+ chain.push(cursor.supersededBy);
45991
+ seen.add(cursor.supersededBy);
45992
+ cursor = byId.get(cursor.supersededBy);
45993
+ }
45994
+ return {
45995
+ rootId: root.id,
45996
+ chain,
45997
+ reason: typeof root.metadata.supersedeReason === "string" ? root.metadata.supersedeReason : undefined
45998
+ };
45999
+ });
46000
+ }
46001
+ function memorySort(a, b) {
46002
+ return b.updatedAt.localeCompare(a.updatedAt) || a.id.localeCompare(b.id);
46003
+ }
46004
+ function proposalSort(a, b) {
46005
+ const aTime = a.reviewedAt ?? a.createdAt;
46006
+ const bTime = b.reviewedAt ?? b.createdAt;
46007
+ return bTime.localeCompare(aTime) || a.id.localeCompare(b.id);
46008
+ }
46009
+ var DEFAULT_LOW_UTILITY_MAX_CONFIDENCE = 0.45, DEFAULT_LOW_UTILITY_MIN_AGE_DAYS = 30;
46010
+ var init_maintenance = __esm(() => {
46011
+ init_schema2();
46012
+ });
46013
+
45825
46014
  // src/memory/role-profiles.ts
45826
46015
  function resolveMemoryRecallProfile(agentRole) {
45827
46016
  const role = normalizeMemoryAgentRole(agentRole);
@@ -46209,16 +46398,21 @@ class LocalJsonlMemoryProvider {
46209
46398
  }
46210
46399
  async recordRecallUsage(event) {
46211
46400
  await this.initialize();
46212
- await this.audit("recall", event.bundleId, JSON.stringify({
46213
- query: event.query,
46214
- scopes: event.scopes,
46215
- kinds: event.kinds,
46216
- memoryIds: event.memoryIds,
46217
- scores: event.scores,
46218
- tokenEstimate: event.tokenEstimate,
46219
- agentRole: event.agentRole,
46220
- runId: event.runId
46221
- }));
46401
+ await this.audit("recall", event.bundleId, undefined, event);
46402
+ }
46403
+ async listRecallUsage(filter = {}) {
46404
+ await this.initialize();
46405
+ const events = await readAuditEvents(this.pathFor("audit"));
46406
+ const usage = [];
46407
+ for (const event of events) {
46408
+ if (event.operation !== "recall")
46409
+ continue;
46410
+ const parsed = parseRecallUsageEvent(event);
46411
+ if (parsed)
46412
+ usage.push(parsed);
46413
+ }
46414
+ usage.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
46415
+ return usage.slice(0, filter.limit ?? usage.length);
46222
46416
  }
46223
46417
  async list(filter = {}) {
46224
46418
  await this.initialize();
@@ -46238,7 +46432,9 @@ class LocalJsonlMemoryProvider {
46238
46432
  return !Number.isFinite(expires) || expires > now;
46239
46433
  });
46240
46434
  }
46241
- records = records.filter((record3) => !record3.supersededBy && record3.metadata.deleted !== true);
46435
+ if (!filter.includeInactive) {
46436
+ records = records.filter((record3) => !record3.supersededBy && record3.metadata.deleted !== true);
46437
+ }
46242
46438
  records.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
46243
46439
  return records.slice(0, filter.limit ?? records.length);
46244
46440
  }
@@ -46279,12 +46475,14 @@ class LocalJsonlMemoryProvider {
46279
46475
  ...decision.memory,
46280
46476
  updatedAt: appliedAt
46281
46477
  });
46478
+ validateCuratorPromotableMemory(memory);
46282
46479
  this.memories.set(memory.id, memory);
46283
46480
  await appendJsonl(this.pathFor("memories"), memory);
46284
46481
  memoryId = memory.id;
46285
46482
  } else if (decision.action === "update") {
46286
46483
  const existing = this.activeMemory(decision.targetMemoryId);
46287
46484
  const updated = this.validateDecisionMemory(applyPatchToMemory(existing, decision.patch, appliedAt));
46485
+ validateCuratorPromotableMemory(updated);
46288
46486
  if (updated.id !== existing.id) {
46289
46487
  const tombstone = this.validateDecisionMemory({
46290
46488
  ...existing,
@@ -46310,6 +46508,7 @@ class LocalJsonlMemoryProvider {
46310
46508
  updatedAt: appliedAt,
46311
46509
  supersedes: Array.from(new Set([...decision.replacement.supersedes ?? [], oldMemory.id]))
46312
46510
  });
46511
+ validateCuratorPromotableMemory(replacement);
46313
46512
  const superseded = this.validateDecisionMemory({
46314
46513
  ...oldMemory,
46315
46514
  updatedAt: appliedAt,
@@ -46350,6 +46549,41 @@ class LocalJsonlMemoryProvider {
46350
46549
  await writeJsonlAtomic(this.pathFor("memories"), Array.from(this.memories.values()));
46351
46550
  await this.audit("compact", "memories");
46352
46551
  }
46552
+ async compactMaintenance(options = {}) {
46553
+ await this.initialize();
46554
+ const now = options.now ? new Date(options.now) : new Date;
46555
+ const kept = [];
46556
+ const result = {
46557
+ dryRun: options.dryRun !== false,
46558
+ removedDeleted: 0,
46559
+ removedSuperseded: 0,
46560
+ removedExpiredScratch: 0,
46561
+ remaining: 0
46562
+ };
46563
+ for (const memory of this.memories.values()) {
46564
+ const compactReason = shouldCompactMemory(memory, now);
46565
+ if (compactReason === "deleted") {
46566
+ result.removedDeleted++;
46567
+ continue;
46568
+ }
46569
+ if (compactReason === "superseded") {
46570
+ result.removedSuperseded++;
46571
+ continue;
46572
+ }
46573
+ if (compactReason === "expired_scratch") {
46574
+ result.removedExpiredScratch++;
46575
+ continue;
46576
+ }
46577
+ kept.push(memory);
46578
+ }
46579
+ result.remaining = kept.length;
46580
+ if (result.dryRun)
46581
+ return result;
46582
+ this.memories = new Map(kept.map((memory) => [memory.id, memory]));
46583
+ await writeJsonlAtomic(this.pathFor("memories"), kept);
46584
+ await this.audit("compact", "memories", "removed deleted, superseded, and expired scratch memories", result);
46585
+ return result;
46586
+ }
46353
46587
  async audit(operation, targetId, reason, eventJson) {
46354
46588
  const event = {
46355
46589
  id: randomUUID3(),
@@ -46428,6 +46662,46 @@ async function readJsonl(filePath) {
46428
46662
  }
46429
46663
  return records;
46430
46664
  }
46665
+ async function readAuditEvents(filePath) {
46666
+ const values = await readJsonl(filePath);
46667
+ const events = [];
46668
+ for (const value of values) {
46669
+ if (!value || typeof value !== "object")
46670
+ continue;
46671
+ const candidate = value;
46672
+ if (typeof candidate.id !== "string" || typeof candidate.operation !== "string" || typeof candidate.targetId !== "string" || typeof candidate.timestamp !== "string") {
46673
+ continue;
46674
+ }
46675
+ events.push(candidate);
46676
+ }
46677
+ return events;
46678
+ }
46679
+ function parseRecallUsageEvent(event) {
46680
+ const raw = event.eventJson ?? event.reason;
46681
+ if (typeof raw !== "string" && (!raw || typeof raw !== "object")) {
46682
+ return null;
46683
+ }
46684
+ try {
46685
+ const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
46686
+ if (!Array.isArray(parsed.memoryIds) || typeof parsed.query !== "string") {
46687
+ return null;
46688
+ }
46689
+ return {
46690
+ bundleId: typeof parsed.bundleId === "string" ? parsed.bundleId : event.targetId,
46691
+ query: parsed.query,
46692
+ scopes: Array.isArray(parsed.scopes) ? parsed.scopes : [],
46693
+ kinds: Array.isArray(parsed.kinds) ? parsed.kinds : undefined,
46694
+ memoryIds: parsed.memoryIds.filter((memoryId) => typeof memoryId === "string"),
46695
+ scores: Array.isArray(parsed.scores) ? parsed.scores.filter((score) => typeof score === "number" && Number.isFinite(score)) : [],
46696
+ tokenEstimate: typeof parsed.tokenEstimate === "number" ? parsed.tokenEstimate : 0,
46697
+ agentRole: typeof parsed.agentRole === "string" ? parsed.agentRole : undefined,
46698
+ runId: typeof parsed.runId === "string" ? parsed.runId : undefined,
46699
+ timestamp: typeof parsed.timestamp === "string" ? parsed.timestamp : event.timestamp
46700
+ };
46701
+ } catch {
46702
+ return null;
46703
+ }
46704
+ }
46431
46705
  async function appendJsonl(filePath, value) {
46432
46706
  await mkdir8(path29.dirname(filePath), { recursive: true });
46433
46707
  await appendFile4(filePath, `${JSON.stringify(value)}
@@ -46447,6 +46721,7 @@ var init_local_jsonl_provider = __esm(() => {
46447
46721
  init_config3();
46448
46722
  init_curator_decision_helpers();
46449
46723
  init_errors6();
46724
+ init_maintenance();
46450
46725
  init_schema2();
46451
46726
  init_scoring();
46452
46727
  });
@@ -46704,6 +46979,10 @@ class SQLiteMemoryProvider {
46704
46979
  redaction: {
46705
46980
  ...DEFAULT_MEMORY_CONFIG.redaction,
46706
46981
  ...config3.redaction ?? {}
46982
+ },
46983
+ maintenance: {
46984
+ ...DEFAULT_MEMORY_CONFIG.maintenance,
46985
+ ...config3.maintenance ?? {}
46707
46986
  }
46708
46987
  };
46709
46988
  }
@@ -46809,6 +47088,26 @@ class SQLiteMemoryProvider {
46809
47088
  ) VALUES (?, ?, ?, ?)`, [randomUUID4(), event.bundleId, event.timestamp, JSON.stringify(event)]);
46810
47089
  await this.event("recall", event.bundleId, JSON.stringify(event));
46811
47090
  }
47091
+ async listRecallUsage(filter = {}) {
47092
+ await this.initialize();
47093
+ const rows = typeof filter.limit === "number" ? this.requireDb().query(`SELECT usage_json
47094
+ FROM memory_recall_usage
47095
+ ORDER BY timestamp DESC
47096
+ LIMIT ?`).all(Math.max(1, Math.trunc(filter.limit))) : this.requireDb().query(`SELECT usage_json
47097
+ FROM memory_recall_usage
47098
+ ORDER BY timestamp DESC
47099
+ `).all();
47100
+ const events = [];
47101
+ for (const row of rows) {
47102
+ try {
47103
+ const parsed = JSON.parse(row.usage_json);
47104
+ if (Array.isArray(parsed.memoryIds) && typeof parsed.query === "string") {
47105
+ events.push(parsed);
47106
+ }
47107
+ } catch {}
47108
+ }
47109
+ return events;
47110
+ }
46812
47111
  async list(filter = {}) {
46813
47112
  await this.initialize();
46814
47113
  let records = Array.from(this.memories.values());
@@ -46827,7 +47126,9 @@ class SQLiteMemoryProvider {
46827
47126
  return !Number.isFinite(expires) || expires > now;
46828
47127
  });
46829
47128
  }
46830
- records = records.filter((record3) => !record3.supersededBy && record3.metadata.deleted !== true);
47129
+ if (!filter.includeInactive) {
47130
+ records = records.filter((record3) => !record3.supersededBy && record3.metadata.deleted !== true);
47131
+ }
46831
47132
  records.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
46832
47133
  return records.slice(0, filter.limit ?? records.length);
46833
47134
  }
@@ -46908,6 +47209,52 @@ class SQLiteMemoryProvider {
46908
47209
  proposals: proposals.length
46909
47210
  };
46910
47211
  }
47212
+ async compactMaintenance(options = {}) {
47213
+ await this.initialize();
47214
+ const now = options.now ? new Date(options.now) : new Date;
47215
+ const kept = [];
47216
+ const removeIds = [];
47217
+ const result = {
47218
+ dryRun: options.dryRun !== false,
47219
+ removedDeleted: 0,
47220
+ removedSuperseded: 0,
47221
+ removedExpiredScratch: 0,
47222
+ remaining: 0
47223
+ };
47224
+ for (const memory of this.memories.values()) {
47225
+ const compactReason = shouldCompactMemory(memory, now);
47226
+ if (compactReason === "deleted") {
47227
+ result.removedDeleted++;
47228
+ removeIds.push(memory.id);
47229
+ continue;
47230
+ }
47231
+ if (compactReason === "superseded") {
47232
+ result.removedSuperseded++;
47233
+ removeIds.push(memory.id);
47234
+ continue;
47235
+ }
47236
+ if (compactReason === "expired_scratch") {
47237
+ result.removedExpiredScratch++;
47238
+ removeIds.push(memory.id);
47239
+ continue;
47240
+ }
47241
+ kept.push(memory);
47242
+ }
47243
+ result.remaining = kept.length;
47244
+ if (result.dryRun)
47245
+ return result;
47246
+ const db = this.requireDb();
47247
+ const compact = db.transaction(() => {
47248
+ for (const id of removeIds) {
47249
+ db.run("DELETE FROM memory_items WHERE id = ?", [id]);
47250
+ this.deleteMemoryFts(id);
47251
+ }
47252
+ this.insertEvent("compact", "memory_items", "removed deleted, superseded, and expired scratch memories", JSON.stringify(result));
47253
+ });
47254
+ compact();
47255
+ this.memories = new Map(kept.map((memory) => [memory.id, memory]));
47256
+ return result;
47257
+ }
46911
47258
  hasMigration(name) {
46912
47259
  const row = this.requireDb().query("SELECT version, name FROM schema_migrations WHERE name = ? LIMIT 1").get(name);
46913
47260
  return Boolean(row);
@@ -47141,12 +47488,14 @@ class SQLiteMemoryProvider {
47141
47488
  ...decision.memory,
47142
47489
  updatedAt: appliedAt
47143
47490
  });
47491
+ validateCuratorPromotableMemory(memory);
47144
47492
  this.writeMemory(memory);
47145
47493
  memories.push(memory);
47146
47494
  memoryId = memory.id;
47147
47495
  } else if (decision.action === "update") {
47148
47496
  const existing = this.readActiveMemory(decision.targetMemoryId);
47149
47497
  const updated = this.validateDecisionMemory(applyPatchToMemory(existing, decision.patch, appliedAt));
47498
+ validateCuratorPromotableMemory(updated);
47150
47499
  if (updated.id !== existing.id) {
47151
47500
  const tombstone = this.validateDecisionMemory({
47152
47501
  ...existing,
@@ -47172,6 +47521,7 @@ class SQLiteMemoryProvider {
47172
47521
  updatedAt: appliedAt,
47173
47522
  supersedes: Array.from(new Set([...decision.replacement.supersedes ?? [], oldMemory.id]))
47174
47523
  });
47524
+ validateCuratorPromotableMemory(replacement);
47175
47525
  const superseded = this.validateDecisionMemory({
47176
47526
  ...oldMemory,
47177
47527
  updatedAt: appliedAt,
@@ -47384,6 +47734,7 @@ var init_sqlite_provider = __esm(() => {
47384
47734
  init_curator_decision_helpers();
47385
47735
  init_errors6();
47386
47736
  init_jsonl_migration();
47737
+ init_maintenance();
47387
47738
  init_schema2();
47388
47739
  init_scoring();
47389
47740
  FTS_INDEX_COLUMNS = [
@@ -47911,6 +48262,7 @@ var init_memory = __esm(() => {
47911
48262
  init_injector();
47912
48263
  init_jsonl_migration();
47913
48264
  init_local_jsonl_provider();
48265
+ init_maintenance();
47914
48266
  init_prompt_block();
47915
48267
  init_recall_planner();
47916
48268
  init_redaction();
@@ -47929,6 +48281,10 @@ async function handleMemoryCommand(_directory, _args) {
47929
48281
  "## Swarm Memory",
47930
48282
  "",
47931
48283
  "- `/swarm memory status` - show provider, SQLite path, JSONL files, and last migration report",
48284
+ "- `/swarm memory pending` - show pending proposals and recent rejection reasons",
48285
+ "- `/swarm memory recall-log` - summarize recall usage by agent role and memory ID",
48286
+ "- `/swarm memory stale` - list expired scratch, superseded, deleted, and low-utility memories",
48287
+ "- `/swarm memory compact` - dry-run compaction; pass `--confirm` to remove deleted, superseded, and expired scratch records",
47932
48288
  "- `/swarm memory export` - export current memory and proposals to `.swarm/memory/export/*.jsonl`",
47933
48289
  "- `/swarm memory import` - import `.swarm/memory/{memories,proposals}.jsonl` into SQLite",
47934
48290
  "- `/swarm memory migrate` - run the one-time legacy JSONL to SQLite migration",
@@ -47950,6 +48306,7 @@ async function handleMemoryStatusCommand(directory, _args) {
47950
48306
  `- Storage: \`${storageDir}\``,
47951
48307
  `- SQLite path: \`${sqlitePath}\``,
47952
48308
  `- SQLite database exists: \`${existsSync20(sqlitePath)}\``,
48309
+ `- Automatic destructive cleanup: \`disabled\``,
47953
48310
  "",
47954
48311
  "### Legacy JSONL"
47955
48312
  ];
@@ -47974,6 +48331,119 @@ async function handleMemoryStatusCommand(directory, _args) {
47974
48331
  return lines.join(`
47975
48332
  `);
47976
48333
  }
48334
+ async function handleMemoryPendingCommand(directory, args) {
48335
+ const parsed = parseMaintenanceArgs(args, {
48336
+ usage: "Usage: /swarm memory pending [--limit <n>]",
48337
+ allowConfirm: false
48338
+ });
48339
+ if ("error" in parsed)
48340
+ return parsed.error;
48341
+ const config3 = resolveCommandMemoryConfig(directory);
48342
+ const provider = createMaintenanceProvider(directory, config3);
48343
+ try {
48344
+ await provider.initialize?.();
48345
+ const report = await buildMemoryMaintenanceReport(provider, {
48346
+ ...maintenanceReportOptions(config3, parsed.limit)
48347
+ });
48348
+ const lines = [
48349
+ "## Swarm Memory Pending",
48350
+ "",
48351
+ `- Pending proposals shown: \`${report.pendingProposals.length}\``,
48352
+ `- Rejected proposal reasons shown: \`${report.rejectedProposalReasons.length}\``
48353
+ ];
48354
+ appendProposalLines(lines, "Pending proposals", report.pendingProposals);
48355
+ appendProposalLines(lines, "Rejected proposal reasons", report.rejectedProposalReasons);
48356
+ return lines.join(`
48357
+ `);
48358
+ } finally {
48359
+ await provider.close?.();
48360
+ }
48361
+ }
48362
+ async function handleMemoryRecallLogCommand(directory, args) {
48363
+ const parsed = parseMaintenanceArgs(args, {
48364
+ usage: "Usage: /swarm memory recall-log [--limit <n>]",
48365
+ allowConfirm: false
48366
+ });
48367
+ if ("error" in parsed)
48368
+ return parsed.error;
48369
+ const config3 = resolveCommandMemoryConfig(directory);
48370
+ const provider = createMaintenanceProvider(directory, config3);
48371
+ try {
48372
+ await provider.initialize?.();
48373
+ const report = await buildMemoryMaintenanceReport(provider, {
48374
+ ...maintenanceReportOptions(config3, parsed.limit)
48375
+ });
48376
+ const lines = [
48377
+ "## Swarm Memory Recall Log",
48378
+ "",
48379
+ `- Recall events scanned: \`${report.recallEventCount}\``,
48380
+ `- Most-recalled memories shown: \`${report.mostRecalledMemories.length}\``,
48381
+ `- Never-recalled memories shown: \`${report.neverRecalledMemories.length}\``
48382
+ ];
48383
+ appendRecallRoleLines(lines, report.recallByAgentRole);
48384
+ appendRecallMemoryLines(lines, report.mostRecalledMemories);
48385
+ appendMemoryLines(lines, "Never-recalled memories", report.neverRecalledMemories);
48386
+ return lines.join(`
48387
+ `);
48388
+ } finally {
48389
+ await provider.close?.();
48390
+ }
48391
+ }
48392
+ async function handleMemoryStaleCommand(directory, args) {
48393
+ const parsed = parseMaintenanceArgs(args, {
48394
+ usage: "Usage: /swarm memory stale [--limit <n>]",
48395
+ allowConfirm: false
48396
+ });
48397
+ if ("error" in parsed)
48398
+ return parsed.error;
48399
+ const config3 = resolveCommandMemoryConfig(directory);
48400
+ const provider = createMaintenanceProvider(directory, config3);
48401
+ try {
48402
+ await provider.initialize?.();
48403
+ const report = await buildMemoryMaintenanceReport(provider, {
48404
+ ...maintenanceReportOptions(config3, parsed.limit)
48405
+ });
48406
+ const lines = [
48407
+ "## Swarm Memory Stale",
48408
+ "",
48409
+ `- Active memories: \`${report.activeMemories}\``,
48410
+ `- Expired scratch memories shown: \`${report.expiredScratchMemories.length}\``,
48411
+ `- Deleted tombstones shown: \`${report.deletedMemories.length}\``,
48412
+ `- Superseded memories shown: \`${report.supersededMemories.length}\``,
48413
+ `- Low-utility memories shown: \`${report.lowUtilityMemories.length}\``
48414
+ ];
48415
+ appendMemoryLines(lines, "Expired scratch memories", report.expiredScratchMemories);
48416
+ appendMemoryLines(lines, "Deleted tombstones", report.deletedMemories);
48417
+ appendSupersededChains(lines, report);
48418
+ appendMemoryLines(lines, "Low-utility memories", report.lowUtilityMemories);
48419
+ return lines.join(`
48420
+ `);
48421
+ } finally {
48422
+ await provider.close?.();
48423
+ }
48424
+ }
48425
+ async function handleMemoryCompactCommand(directory, args) {
48426
+ const parsed = parseMaintenanceArgs(args, {
48427
+ usage: "Usage: /swarm memory compact [--confirm]",
48428
+ allowConfirm: true,
48429
+ allowLimit: false
48430
+ });
48431
+ if ("error" in parsed)
48432
+ return parsed.error;
48433
+ const provider = createMaintenanceProvider(directory, resolveCommandMemoryConfig(directory));
48434
+ try {
48435
+ await provider.initialize?.();
48436
+ if (!provider.compactMaintenance) {
48437
+ return "Memory provider does not support compaction.";
48438
+ }
48439
+ const result = await provider.compactMaintenance({
48440
+ dryRun: !parsed.confirm
48441
+ });
48442
+ return formatCompactResult(result, parsed.confirm);
48443
+ } finally {
48444
+ await provider.close?.();
48445
+ }
48446
+ }
47977
48447
  async function handleMemoryMigrateCommand(directory, _args) {
47978
48448
  const config3 = {
47979
48449
  ...resolveCommandMemoryConfig(directory),
@@ -48030,6 +48500,16 @@ async function handleMemoryExportCommand(directory, _args) {
48030
48500
  await provider.close?.();
48031
48501
  }
48032
48502
  }
48503
+ function createMaintenanceProvider(directory, config3) {
48504
+ return createConfiguredMemoryProvider(directory, config3);
48505
+ }
48506
+ function maintenanceReportOptions(config3, limit) {
48507
+ return {
48508
+ limit,
48509
+ lowUtilityMaxConfidence: config3.maintenance.lowUtilityMaxConfidence,
48510
+ lowUtilityMinAgeDays: config3.maintenance.lowUtilityMinAgeDays
48511
+ };
48512
+ }
48033
48513
  async function handleMemoryEvaluateCommand(directory, args) {
48034
48514
  const parsed = parseEvaluateArgs(directory, args);
48035
48515
  if ("error" in parsed)
@@ -48088,6 +48568,28 @@ function parseEvaluateArgs(directory, args) {
48088
48568
  }
48089
48569
  return { json: json3, fixtureDirectory };
48090
48570
  }
48571
+ function parseMaintenanceArgs(args, options) {
48572
+ let limit = 20;
48573
+ let confirm = false;
48574
+ const allowLimit = options.allowLimit ?? true;
48575
+ for (let i = 0;i < args.length; i++) {
48576
+ const arg = args[i];
48577
+ if (arg === "--confirm" && options.allowConfirm) {
48578
+ confirm = true;
48579
+ continue;
48580
+ }
48581
+ if (arg === "--limit" && allowLimit) {
48582
+ const next = args[i + 1];
48583
+ if (!next || !/^\d+$/.test(next))
48584
+ return { error: options.usage };
48585
+ limit = Math.min(100, Math.max(1, Number(next)));
48586
+ i++;
48587
+ continue;
48588
+ }
48589
+ return { error: options.usage };
48590
+ }
48591
+ return { limit, confirm };
48592
+ }
48091
48593
  function resolvePackageRootFromModule(modulePath) {
48092
48594
  const moduleDir = path33.dirname(modulePath);
48093
48595
  const leaf = path33.basename(moduleDir);
@@ -48138,6 +48640,80 @@ function appendInvalidRows(lines, invalidRows) {
48138
48640
  lines.push(`- ... ${invalidRows.length - 20} more`);
48139
48641
  }
48140
48642
  }
48643
+ function appendProposalLines(lines, title, proposals) {
48644
+ lines.push("", `### ${title}`);
48645
+ if (proposals.length === 0) {
48646
+ lines.push("- none");
48647
+ return;
48648
+ }
48649
+ for (const proposal of proposals) {
48650
+ const reason = proposal.status === "rejected" ? ` - ${proposal.rejectionReason ?? "no reason recorded"}` : "";
48651
+ lines.push(`- \`${proposal.id}\` ${proposal.operation} ${proposal.targetMemoryId ?? proposal.proposedRecord?.id ?? "new"} (${proposal.status})${reason}`);
48652
+ }
48653
+ }
48654
+ function appendMemoryLines(lines, title, memories) {
48655
+ lines.push("", `### ${title}`);
48656
+ if (memories.length === 0) {
48657
+ lines.push("- none");
48658
+ return;
48659
+ }
48660
+ for (const memory of memories) {
48661
+ lines.push(`- \`${memory.id}\` ${memory.kind} confidence=${memory.confidence.toFixed(2)} updated=${memory.updatedAt} - ${truncate(memory.text, 100)}`);
48662
+ }
48663
+ }
48664
+ function appendRecallRoleLines(lines, roles) {
48665
+ lines.push("", "### Recall by agent role");
48666
+ if (roles.length === 0) {
48667
+ lines.push("- none");
48668
+ return;
48669
+ }
48670
+ for (const role of roles) {
48671
+ const memoryCount = Object.keys(role.memoryIds).length;
48672
+ lines.push(`- \`${role.agentRole}\`: ${role.count} recall event(s), ${memoryCount} memory ID(s)`);
48673
+ }
48674
+ }
48675
+ function appendRecallMemoryLines(lines, memories) {
48676
+ lines.push("", "### Most-recalled memories");
48677
+ if (memories.length === 0) {
48678
+ lines.push("- none");
48679
+ return;
48680
+ }
48681
+ for (const memory of memories) {
48682
+ lines.push(`- \`${memory.memoryId}\`: ${memory.count} hit(s), last=${memory.lastRecalledAt}, avgScore=${memory.averageScore.toFixed(3)}`);
48683
+ }
48684
+ }
48685
+ function appendSupersededChains(lines, report) {
48686
+ lines.push("", "### Superseded chains");
48687
+ if (report.supersededChains.length === 0) {
48688
+ lines.push("- none");
48689
+ return;
48690
+ }
48691
+ for (const chain of report.supersededChains) {
48692
+ const reason = chain.reason ? ` - ${chain.reason}` : "";
48693
+ lines.push(`- ${chain.chain.map((id) => `\`${id}\``).join(" -> ")}${reason}`);
48694
+ }
48695
+ }
48696
+ function formatCompactResult(result, confirmed) {
48697
+ const lines = [
48698
+ "## Swarm Memory Compact",
48699
+ "",
48700
+ `- Mode: \`${confirmed ? "confirmed" : "dry-run"}\``,
48701
+ `- Deleted tombstones: \`${result.removedDeleted}\``,
48702
+ `- Superseded records: \`${result.removedSuperseded}\``,
48703
+ `- Expired scratch records: \`${result.removedExpiredScratch}\``,
48704
+ `- Remaining memories: \`${result.remaining}\``
48705
+ ];
48706
+ if (!confirmed) {
48707
+ lines.push("", "No records were removed. Re-run `/swarm memory compact --confirm` to apply this compaction.");
48708
+ }
48709
+ return lines.join(`
48710
+ `);
48711
+ }
48712
+ function truncate(value, maxLength) {
48713
+ if (value.length <= maxLength)
48714
+ return value;
48715
+ return `${value.slice(0, maxLength - 3)}...`;
48716
+ }
48141
48717
  var PACKAGE_ROOT;
48142
48718
  var init_memory2 = __esm(() => {
48143
48719
  init_loader();
@@ -56197,7 +56773,7 @@ function classifySwarmCommandToolUse(resolved) {
56197
56773
  return { allowed: true };
56198
56774
  return {
56199
56775
  allowed: false,
56200
- message: "Use `/swarm memory status`, `/swarm memory export`, or `/swarm memory evaluate --json` through swarm_command. Memory import and migrate are intentionally excluded from chat-tool execution."
56776
+ message: "Use `/swarm memory status`, `/swarm memory pending`, `/swarm memory recall-log`, `/swarm memory stale`, `/swarm memory export`, or `/swarm memory evaluate --json` through swarm_command. Memory import, migrate, and compact are intentionally excluded from chat-tool execution."
56201
56777
  };
56202
56778
  }
56203
56779
  if (canonicalKey === "memory evaluate") {
@@ -56210,6 +56786,17 @@ function classifySwarmCommandToolUse(resolved) {
56210
56786
  message: "Usage through swarm_command: `/swarm memory evaluate --json`. Custom fixture directories are only available through direct user command execution."
56211
56787
  };
56212
56788
  }
56789
+ if (canonicalKey === "memory pending" || canonicalKey === "memory recall-log" || canonicalKey === "memory stale") {
56790
+ if (args.length === 0)
56791
+ return { allowed: true };
56792
+ if (args.length === 2 && args[0] === "--limit" && /^\d+$/.test(args[1])) {
56793
+ return { allowed: true };
56794
+ }
56795
+ return {
56796
+ allowed: false,
56797
+ message: `Usage through swarm_command: \`/swarm ${canonicalKey}\` or ` + `\`/swarm ${canonicalKey} --limit <n>\`.`
56798
+ };
56799
+ }
56213
56800
  if (canonicalKey === "retrieve") {
56214
56801
  if (args.length !== 1 || !SUMMARY_ID_PATTERN.test(args[0])) {
56215
56802
  return {
@@ -56261,7 +56848,7 @@ function classifySwarmCommandChatFallbackUse(resolved) {
56261
56848
  message: "/swarm config doctor --fix is not available through chat fallback because it can modify configuration files. Run the CLI command directly when you intend to apply fixes."
56262
56849
  };
56263
56850
  }
56264
- if (canonicalKey === "knowledge migrate" || canonicalKey === "knowledge quarantine" || canonicalKey === "knowledge restore" || canonicalKey === "memory import" || canonicalKey === "memory migrate") {
56851
+ if (canonicalKey === "knowledge migrate" || canonicalKey === "knowledge quarantine" || canonicalKey === "knowledge restore" || canonicalKey === "memory import" || canonicalKey === "memory migrate" || canonicalKey === "memory compact") {
56265
56852
  return {
56266
56853
  allowed: false,
56267
56854
  message: `/swarm ${canonicalKey} is not available through chat fallback because it mutates .swarm state. ` + "Run the CLI command directly after confirming the intended state change."
@@ -56294,6 +56881,10 @@ var init_tool_policy = __esm(() => {
56294
56881
  "knowledge",
56295
56882
  "memory",
56296
56883
  "memory status",
56884
+ "memory pending",
56885
+ "memory recall-log",
56886
+ "memory compact",
56887
+ "memory stale",
56297
56888
  "memory export",
56298
56889
  "memory evaluate",
56299
56890
  "memory import",
@@ -56320,6 +56911,9 @@ var init_tool_policy = __esm(() => {
56320
56911
  "knowledge",
56321
56912
  "memory",
56322
56913
  "memory status",
56914
+ "memory pending",
56915
+ "memory recall-log",
56916
+ "memory stale",
56323
56917
  "memory export",
56324
56918
  "memory evaluate",
56325
56919
  "sync-plan",
@@ -56332,7 +56926,8 @@ var init_tool_policy = __esm(() => {
56332
56926
  "rollback",
56333
56927
  "checkpoint",
56334
56928
  "memory import",
56335
- "memory migrate"
56929
+ "memory migrate",
56930
+ "memory compact"
56336
56931
  ]);
56337
56932
  NO_ARGS = new Set([
56338
56933
  "agents",
@@ -57327,6 +57922,34 @@ Subcommands:
57327
57922
  args: "",
57328
57923
  category: "diagnostics"
57329
57924
  },
57925
+ "memory pending": {
57926
+ handler: (ctx) => handleMemoryPendingCommand(ctx.directory, ctx.args),
57927
+ description: "Show pending Swarm memory proposals and rejection reasons",
57928
+ subcommandOf: "memory",
57929
+ args: "--limit <n>",
57930
+ category: "diagnostics"
57931
+ },
57932
+ "memory recall-log": {
57933
+ handler: (ctx) => handleMemoryRecallLogCommand(ctx.directory, ctx.args),
57934
+ description: "Summarize Swarm memory recall usage",
57935
+ subcommandOf: "memory",
57936
+ args: "--limit <n>",
57937
+ category: "diagnostics"
57938
+ },
57939
+ "memory compact": {
57940
+ handler: (ctx) => handleMemoryCompactCommand(ctx.directory, ctx.args),
57941
+ description: "Compact deleted, superseded, and expired scratch memories",
57942
+ subcommandOf: "memory",
57943
+ args: "--confirm",
57944
+ category: "utility"
57945
+ },
57946
+ "memory stale": {
57947
+ handler: (ctx) => handleMemoryStaleCommand(ctx.directory, ctx.args),
57948
+ description: "List stale and low-utility Swarm memories",
57949
+ subcommandOf: "memory",
57950
+ args: "--limit <n>",
57951
+ category: "diagnostics"
57952
+ },
57330
57953
  "memory export": {
57331
57954
  handler: (ctx) => handleMemoryExportCommand(ctx.directory, ctx.args),
57332
57955
  description: "Export current Swarm memory to JSONL files",