opencode-swarm 6.17.1 → 6.17.3

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.
@@ -8,7 +8,7 @@ export { handleConfigCommand } from './config';
8
8
  export { handleDarkMatterCommand } from './dark-matter';
9
9
  export { handleDiagnoseCommand } from './diagnose';
10
10
  export { handleDoctorCommand } from './doctor';
11
- export { handleEvidenceCommand } from './evidence';
11
+ export { handleEvidenceCommand, handleEvidenceSummaryCommand, } from './evidence';
12
12
  export { handleExportCommand } from './export';
13
13
  export { handleHistoryCommand } from './history';
14
14
  export { handleKnowledgeMigrateCommand, handleKnowledgeQuarantineCommand, handleKnowledgeRestoreCommand, } from './knowledge';
@@ -1 +1,2 @@
1
+ export type { LoadEvidenceResult } from './manager';
1
2
  export { archiveEvidence, deleteEvidence, listEvidenceTaskIds, loadEvidence, sanitizeTaskId, saveEvidence, } from './manager';
@@ -1,4 +1,19 @@
1
1
  import { type BuildEvidence, type Evidence, type EvidenceBundle, type PlaceholderEvidence, type QualityBudgetEvidence, type SastEvidence, type SbomEvidence, type SyntaxEvidence } from '../config/evidence-schema';
2
+ /**
3
+ * Discriminated union returned by loadEvidence.
4
+ * - 'found': file exists and passed Zod schema validation
5
+ * - 'not_found': file does not exist on disk
6
+ * - 'invalid_schema': file exists but failed Zod validation; errors contains field names
7
+ */
8
+ export type LoadEvidenceResult = {
9
+ status: 'found';
10
+ bundle: EvidenceBundle;
11
+ } | {
12
+ status: 'not_found';
13
+ } | {
14
+ status: 'invalid_schema';
15
+ errors: string[];
16
+ };
2
17
  /**
3
18
  * All valid evidence types (12 total)
4
19
  */
@@ -33,9 +48,9 @@ export declare function sanitizeTaskId(taskId: string): string;
33
48
  export declare function saveEvidence(directory: string, taskId: string, evidence: Evidence): Promise<EvidenceBundle>;
34
49
  /**
35
50
  * Load evidence bundle for a task.
36
- * Returns null if file doesn't exist or validation fails.
51
+ * Returns a LoadEvidenceResult discriminated union.
37
52
  */
38
- export declare function loadEvidence(directory: string, taskId: string): Promise<EvidenceBundle | null>;
53
+ export declare function loadEvidence(directory: string, taskId: string): Promise<LoadEvidenceResult>;
39
54
  /**
40
55
  * List all task IDs that have evidence bundles.
41
56
  * Returns sorted array of valid task IDs.
package/dist/index.js CHANGED
@@ -14496,15 +14496,16 @@ async function loadEvidence(directory, taskId) {
14496
14496
  validateSwarmPath(directory, relativePath);
14497
14497
  const content = await readSwarmFileAsync(directory, relativePath);
14498
14498
  if (content === null) {
14499
- return null;
14499
+ return { status: "not_found" };
14500
14500
  }
14501
14501
  try {
14502
14502
  const parsed = JSON.parse(content);
14503
14503
  const validated = EvidenceBundleSchema.parse(parsed);
14504
- return validated;
14504
+ return { status: "found", bundle: validated };
14505
14505
  } catch (error49) {
14506
14506
  warn(`Evidence bundle validation failed for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
14507
- return null;
14507
+ const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => e.path.join(".") + ": " + e.message) : [String(error49)];
14508
+ return { status: "invalid_schema", errors: errors3 };
14508
14509
  }
14509
14510
  }
14510
14511
  async function listEvidenceTaskIds(directory) {
@@ -14563,11 +14564,11 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
14563
14564
  const archived = [];
14564
14565
  const remainingBundles = [];
14565
14566
  for (const taskId of taskIds) {
14566
- const bundle = await loadEvidence(directory, taskId);
14567
- if (!bundle) {
14567
+ const result = await loadEvidence(directory, taskId);
14568
+ if (result.status !== "found") {
14568
14569
  continue;
14569
14570
  }
14570
- if (bundle.updated_at < cutoffIso) {
14571
+ if (result.bundle.updated_at < cutoffIso) {
14571
14572
  const deleted = await deleteEvidence(directory, taskId);
14572
14573
  if (deleted) {
14573
14574
  archived.push(taskId);
@@ -14575,7 +14576,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
14575
14576
  } else {
14576
14577
  remainingBundles.push({
14577
14578
  taskId,
14578
- updatedAt: bundle.updated_at
14579
+ updatedAt: result.bundle.updated_at
14579
14580
  });
14580
14581
  }
14581
14582
  }
@@ -14593,6 +14594,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
14593
14594
  }
14594
14595
  var VALID_EVIDENCE_TYPES, TASK_ID_REGEX;
14595
14596
  var init_manager = __esm(() => {
14597
+ init_zod();
14596
14598
  init_evidence_schema();
14597
14599
  init_utils2();
14598
14600
  init_utils();
@@ -15108,7 +15110,8 @@ function getTaskBlockers(task, summary, status) {
15108
15110
  return blockers;
15109
15111
  }
15110
15112
  async function buildTaskSummary(task, taskId) {
15111
- const bundle = await loadEvidence(".", taskId);
15113
+ const result = await loadEvidence(".", taskId);
15114
+ const bundle = result.status === "found" ? result.bundle : null;
15112
15115
  const phase = task?.phase ?? 0;
15113
15116
  const status = getTaskStatus(task, bundle);
15114
15117
  const evidenceCheck = isEvidenceComplete(bundle);
@@ -38923,6 +38926,7 @@ Available commands via /swarm: status, plan, agents, history, config, config doc
38923
38926
  evidence, evidence summary, archive, diagnose, preflight, sync-plan, benchmark, export,
38924
38927
  reset, retrieve, clarify, analyze, specify, dark-matter, knowledge quarantine, knowledge restore, knowledge migrate.
38925
38928
  Type /swarm (no arguments) for full help.
38929
+ Outside OpenCode, invoke any plugin command via: \`bunx opencode-swarm run <command> [args]\` (e.g. \`bunx opencode-swarm run knowledge migrate\`). Do not use \`bun -e\` or look for \`src/commands/\` \u2014 those paths are internal to the plugin source and do not exist in user project directories.
38926
38930
 
38927
38931
  SMEs advise only. Reviewer and critic review only. None of them write code.
38928
38932
 
@@ -41295,11 +41299,14 @@ async function handleArchiveCommand(directory, args2) {
41295
41299
  const wouldArchiveAge = [];
41296
41300
  const remainingBundles = [];
41297
41301
  for (const taskId of beforeTaskIds) {
41298
- const bundle = await loadEvidence(directory, taskId);
41299
- if (bundle && bundle.updated_at < cutoffIso) {
41302
+ const result = await loadEvidence(directory, taskId);
41303
+ if (result.status !== "found") {
41304
+ continue;
41305
+ }
41306
+ if (result.bundle.updated_at < cutoffIso) {
41300
41307
  wouldArchiveAge.push(taskId);
41301
- } else if (bundle) {
41302
- remainingBundles.push({ taskId, updatedAt: bundle.updated_at });
41308
+ } else {
41309
+ remainingBundles.push({ taskId, updatedAt: result.bundle.updated_at });
41303
41310
  }
41304
41311
  }
41305
41312
  const wouldArchiveMaxBundles = [];
@@ -41597,10 +41604,10 @@ async function handleBenchmarkCommand(directory, args2) {
41597
41604
  let totalTestToCodeRatio = 0;
41598
41605
  let qualityEvidenceCount = 0;
41599
41606
  for (const tid of await listEvidenceTaskIds(directory)) {
41600
- const b = await loadEvidence(directory, tid);
41601
- if (!b)
41607
+ const result = await loadEvidence(directory, tid);
41608
+ if (result.status !== "found")
41602
41609
  continue;
41603
- for (const e of b.entries) {
41610
+ for (const e of result.bundle.entries) {
41604
41611
  if (!isValidEvidenceType(e.type)) {
41605
41612
  warn(`Unknown evidence type '${e.type}' in task ${tid}, skipping`);
41606
41613
  continue;
@@ -42905,8 +42912,8 @@ function getVerdictEmoji(verdict) {
42905
42912
  return getVerdictIcon(verdict);
42906
42913
  }
42907
42914
  async function getTaskEvidenceData(directory, taskId) {
42908
- const bundle = await loadEvidence(directory, taskId);
42909
- if (!bundle) {
42915
+ const result = await loadEvidence(directory, taskId);
42916
+ if (result.status !== "found") {
42910
42917
  return {
42911
42918
  hasEvidence: false,
42912
42919
  taskId,
@@ -42916,14 +42923,14 @@ async function getTaskEvidenceData(directory, taskId) {
42916
42923
  };
42917
42924
  }
42918
42925
  const entries = [];
42919
- for (let i2 = 0;i2 < bundle.entries.length; i2++) {
42920
- entries.push(formatEvidenceEntry(i2 + 1, bundle.entries[i2]));
42926
+ for (let i2 = 0;i2 < result.bundle.entries.length; i2++) {
42927
+ entries.push(formatEvidenceEntry(i2 + 1, result.bundle.entries[i2]));
42921
42928
  }
42922
42929
  return {
42923
42930
  hasEvidence: true,
42924
42931
  taskId,
42925
- createdAt: bundle.created_at,
42926
- updatedAt: bundle.updated_at,
42932
+ createdAt: result.bundle.created_at,
42933
+ updatedAt: result.bundle.updated_at,
42927
42934
  entries
42928
42935
  };
42929
42936
  }
@@ -42934,12 +42941,12 @@ async function getEvidenceListData(directory) {
42934
42941
  }
42935
42942
  const tasks = [];
42936
42943
  for (const taskId of taskIds) {
42937
- const bundle = await loadEvidence(directory, taskId);
42938
- if (bundle) {
42944
+ const result = await loadEvidence(directory, taskId);
42945
+ if (result.status === "found") {
42939
42946
  tasks.push({
42940
42947
  taskId,
42941
- entryCount: bundle.entries.length,
42942
- lastUpdated: bundle.updated_at
42948
+ entryCount: result.bundle.entries.length,
42949
+ lastUpdated: result.bundle.updated_at
42943
42950
  });
42944
42951
  } else {
42945
42952
  tasks.push({
@@ -44616,7 +44623,8 @@ var HELP_TEXT = [
44616
44623
  "- `/swarm specify [description]` \u2014 Generate or import a feature specification",
44617
44624
  "- `/swarm dark-matter` \u2014 Detect hidden file couplings via co-change NPMI analysis",
44618
44625
  "- `/swarm knowledge quarantine <id> [reason]` \u2014 Move a knowledge entry to quarantine",
44619
- "- `/swarm knowledge restore <id>` \u2014 Restore a quarantined knowledge entry"
44626
+ "- `/swarm knowledge restore <id>` \u2014 Restore a quarantined knowledge entry",
44627
+ "- `/swarm knowledge migrate` \u2014 Migrate knowledge entries to the current format"
44620
44628
  ].join(`
44621
44629
  `);
44622
44630
  function createSwarmCommandHandler(directory, agents) {
@@ -45106,7 +45114,7 @@ function logFirstCall(modelID, providerID, source, limit) {
45106
45114
  const key = `${modelID || "unknown"}::${providerID || "unknown"}`;
45107
45115
  if (!loggedFirstCalls.has(key)) {
45108
45116
  loggedFirstCalls.add(key);
45109
- warn(`[model-limits] Resolved limit for ${modelID || "(no model)"}@${providerID || "(no provider)"}: ${limit} (source: ${source})`);
45117
+ log(`[model-limits] Resolved limit for ${modelID || "(no model)"}@${providerID || "(no provider)"}: ${limit} (source: ${source})`);
45110
45118
  }
45111
45119
  }
45112
45120
 
@@ -45131,7 +45139,7 @@ function createContextBudgetHandler(config3) {
45131
45139
  const cacheKey = `${modelID || "unknown"}::${providerID || "unknown"}`;
45132
45140
  if (!loggedLimits.has(cacheKey)) {
45133
45141
  loggedLimits.add(cacheKey);
45134
- warn(`[swarm] Context budget: model=${modelID || "unknown"} provider=${providerID || "unknown"} limit=${modelLimit}`);
45142
+ log(`[swarm] Context budget: model=${modelID || "unknown"} provider=${providerID || "unknown"} limit=${modelLimit}`);
45135
45143
  }
45136
45144
  let totalTokens = 0;
45137
45145
  for (const message of messages) {
@@ -46608,9 +46616,9 @@ async function buildRetroInjection(directory, currentPhaseNumber, currentPlanTit
46608
46616
  try {
46609
46617
  const prevPhase = currentPhaseNumber - 1;
46610
46618
  if (prevPhase >= 1) {
46611
- const bundle = await loadEvidence(directory, `retro-${prevPhase}`);
46612
- if (bundle && bundle.entries.length > 0) {
46613
- const retroEntry = bundle.entries.find((entry) => entry.type === "retrospective");
46619
+ const result1 = await loadEvidence(directory, `retro-${prevPhase}`);
46620
+ if (result1.status === "found" && result1.bundle.entries.length > 0) {
46621
+ const retroEntry = result1.bundle.entries.find((entry) => entry.type === "retrospective");
46614
46622
  if (retroEntry && retroEntry.verdict !== "fail") {
46615
46623
  const lessons = retroEntry.lessons_learned ?? [];
46616
46624
  const rejections = retroEntry.top_rejection_reasons ?? [];
@@ -46638,9 +46646,9 @@ ${top5.map((d) => `- [${d.category}] ${d.directive}`).join(`
46638
46646
  const retroIds = taskIds.filter((id) => id.startsWith("retro-"));
46639
46647
  let latestRetro = null;
46640
46648
  for (const taskId of retroIds) {
46641
- const b = await loadEvidence(directory, taskId);
46642
- if (b && b.entries.length > 0) {
46643
- for (const entry of b.entries) {
46649
+ const r = await loadEvidence(directory, taskId);
46650
+ if (r.status === "found" && r.bundle.entries.length > 0) {
46651
+ for (const entry of r.bundle.entries) {
46644
46652
  if (entry.type === "retrospective") {
46645
46653
  const retro = entry;
46646
46654
  if (retro.verdict !== "fail") {
@@ -46687,16 +46695,16 @@ ${top5.map((d) => `- [${d.category}] ${d.directive}`).join(`
46687
46695
  const now = Date.now();
46688
46696
  for (const taskId of allRetroIds) {
46689
46697
  const b = await loadEvidence(directory, taskId);
46690
- if (!b)
46698
+ if (b.status !== "found")
46691
46699
  continue;
46692
- for (const e of b.entries) {
46700
+ for (const e of b.bundle.entries) {
46693
46701
  if (e.type === "retrospective") {
46694
46702
  const retro = e;
46695
46703
  if (retro.verdict === "fail")
46696
46704
  continue;
46697
46705
  if (currentPlanTitle && typeof retro.metadata === "object" && retro.metadata !== null && "plan_id" in retro.metadata && retro.metadata.plan_id === currentPlanTitle)
46698
46706
  continue;
46699
- const ts = retro.timestamp ?? b.created_at;
46707
+ const ts = retro.timestamp ?? b.bundle.created_at;
46700
46708
  const ageMs = now - new Date(ts).getTime();
46701
46709
  if (isNaN(ageMs) || ageMs > cutoffMs)
46702
46710
  continue;
@@ -46752,10 +46760,10 @@ async function buildCoderRetroInjection(directory, currentPhaseNumber) {
46752
46760
  const prevPhase = currentPhaseNumber - 1;
46753
46761
  if (prevPhase < 1)
46754
46762
  return null;
46755
- const bundle = await loadEvidence(directory, `retro-${prevPhase}`);
46756
- if (!bundle || bundle.entries.length === 0)
46763
+ const result = await loadEvidence(directory, `retro-${prevPhase}`);
46764
+ if (result.status !== "found" || result.bundle.entries.length === 0)
46757
46765
  return null;
46758
- const retroEntry = bundle.entries.find((entry) => entry.type === "retrospective");
46766
+ const retroEntry = result.bundle.entries.find((entry) => entry.type === "retrospective");
46759
46767
  if (!retroEntry || retroEntry.verdict === "fail")
46760
46768
  return null;
46761
46769
  const lessons = retroEntry.lessons_learned ?? [];
@@ -50176,34 +50184,69 @@ async function executePhaseComplete(args2, workingDirectory) {
50176
50184
  warnings: []
50177
50185
  }, null, 2);
50178
50186
  }
50179
- const retroBundle = await loadEvidence(dir, `retro-${phase}`);
50187
+ const retroResult = await loadEvidence(dir, `retro-${phase}`);
50180
50188
  let retroFound = false;
50181
- if (retroBundle !== null) {
50182
- retroFound = retroBundle.entries?.some((entry) => isValidRetroEntry(entry, phase)) ?? false;
50189
+ let invalidSchemaErrors = [];
50190
+ if (retroResult.status === "found") {
50191
+ retroFound = retroResult.bundle.entries?.some((entry) => isValidRetroEntry(entry, phase)) ?? false;
50192
+ } else if (retroResult.status === "invalid_schema") {
50193
+ invalidSchemaErrors = retroResult.errors;
50183
50194
  }
50184
50195
  if (!retroFound) {
50185
50196
  const allTaskIds = await listEvidenceTaskIds(dir);
50186
50197
  const retroTaskIds = allTaskIds.filter((id) => id.startsWith("retro-"));
50187
50198
  for (const taskId of retroTaskIds) {
50188
- const bundle = await loadEvidence(dir, taskId);
50189
- if (bundle === null)
50199
+ const bundleResult = await loadEvidence(dir, taskId);
50200
+ if (bundleResult.status !== "found") {
50201
+ if (bundleResult.status === "invalid_schema") {
50202
+ invalidSchemaErrors.push(...bundleResult.errors);
50203
+ }
50190
50204
  continue;
50191
- retroFound = bundle.entries?.some((entry) => isValidRetroEntry(entry, phase)) ?? false;
50205
+ }
50206
+ retroFound = bundleResult.bundle.entries?.some((entry) => isValidRetroEntry(entry, phase)) ?? false;
50192
50207
  if (retroFound)
50193
50208
  break;
50194
50209
  }
50195
50210
  }
50196
50211
  if (!retroFound) {
50212
+ const schemaErrorDetail = invalidSchemaErrors.length > 0 ? ` Schema validation failed: ${invalidSchemaErrors.join("; ")}.` : "";
50197
50213
  return JSON.stringify({
50198
50214
  success: false,
50199
50215
  phase,
50200
50216
  status: "blocked",
50201
50217
  reason: "RETROSPECTIVE_MISSING",
50202
- message: `Phase ${phase} cannot be completed: no valid retrospective evidence found. Write a retrospective bundle at .swarm/evidence/retro-${phase}/evidence.json with type='retrospective', phase_number=${phase}, verdict='pass' before calling phase_complete.`,
50218
+ message: `Phase ${phase} cannot be completed: no valid retrospective evidence found.${schemaErrorDetail} Write a retrospective bundle at .swarm/evidence/retro-${phase}/evidence.json before calling phase_complete.`,
50203
50219
  agentsDispatched: [],
50204
50220
  agentsMissing: [],
50205
50221
  warnings: [
50206
- `Retrospective missing for phase ${phase}. Write a retro bundle with verdict='pass' at .swarm/evidence/retro-${phase}/evidence.json`
50222
+ `Retrospective missing for phase ${phase}.${schemaErrorDetail} Use this template:`,
50223
+ JSON.stringify({
50224
+ schema_version: "1.0.0",
50225
+ task_id: `retro-${phase}`,
50226
+ created_at: new Date().toISOString(),
50227
+ updated_at: new Date().toISOString(),
50228
+ entries: [
50229
+ {
50230
+ task_id: `retro-${phase}`,
50231
+ type: "retrospective",
50232
+ timestamp: new Date().toISOString(),
50233
+ agent: "architect",
50234
+ verdict: "pass",
50235
+ summary: `Phase ${phase} completed.`,
50236
+ phase_number: phase,
50237
+ total_tool_calls: 0,
50238
+ coder_revisions: 0,
50239
+ reviewer_rejections: 0,
50240
+ test_failures: 0,
50241
+ security_findings: 0,
50242
+ integration_issues: 0,
50243
+ task_count: 1,
50244
+ task_complexity: "simple",
50245
+ top_rejection_reasons: [],
50246
+ lessons_learned: []
50247
+ }
50248
+ ]
50249
+ }, null, 2)
50207
50250
  ]
50208
50251
  }, null, 2);
50209
50252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.17.1",
3
+ "version": "6.17.3",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",