opencode-swarm 6.86.6 → 6.86.8

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/README.md CHANGED
@@ -107,6 +107,34 @@ The 15-minute guide covers:
107
107
 
108
108
  ---
109
109
 
110
+ ## Upgrading
111
+
112
+ **OpenCode caches plugins indefinitely.** A normal OpenCode restart does **not**
113
+ pull newer versions from npm — once a plugin is cached, OpenCode keeps using
114
+ that exact copy on every subsequent launch (issue #675). The cache lives in
115
+ one of two places depending on your platform:
116
+
117
+ - Linux / devcontainers / GitHub Codespaces:
118
+ `~/.config/opencode/node_modules/opencode-swarm/`
119
+ - Some macOS / Windows installs:
120
+ `~/.cache/opencode/packages/opencode-swarm@latest/`
121
+
122
+ To upgrade to the latest published version (clears both layouts automatically):
123
+
124
+ ```bash
125
+ bunx opencode-swarm update # cache-only refresh, then restart opencode
126
+ # or
127
+ bunx opencode-swarm install # full reinstall (re-asserts config), then restart opencode
128
+ ```
129
+
130
+ `/swarm diagnose` shows the running version and, when available, the latest
131
+ version on npm so you can tell at a glance whether your cache is stale.
132
+
133
+ To disable the background staleness check entirely, set `version_check: false`
134
+ in your `opencode-swarm.json`.
135
+
136
+ ---
137
+
110
138
  ## Commands
111
139
 
112
140
  All 41 subcommands at a glance:
package/dist/cli/index.js CHANGED
@@ -18580,7 +18580,7 @@ import * as path32 from "path";
18580
18580
  // package.json
18581
18581
  var package_default = {
18582
18582
  name: "opencode-swarm",
18583
- version: "6.86.6",
18583
+ version: "6.86.8",
18584
18584
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
18585
18585
  main: "dist/index.js",
18586
18586
  types: "dist/index.d.ts",
@@ -18618,7 +18618,7 @@ var package_default = {
18618
18618
  ],
18619
18619
  scripts: {
18620
18620
  clean: `bun -e "require('fs').rmSync('dist',{recursive:true,force:true})"`,
18621
- build: "bun run clean && bun run scripts/copy-grammars.ts && bun build src/index.ts --outdir dist --target bun --format esm && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm && bun run scripts/copy-grammars.ts --to-dist && tsc --emitDeclarationOnly",
18621
+ build: "bun run clean && bun run scripts/copy-grammars.ts && bun build src/index.ts --outdir dist --target node --format esm && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm && bun run scripts/copy-grammars.ts --to-dist && tsc --emitDeclarationOnly",
18622
18622
  typecheck: "tsc --noEmit",
18623
18623
  test: "bun test",
18624
18624
  lint: "biome lint .",
@@ -18742,7 +18742,7 @@ var TOOL_NAMES = [
18742
18742
  "evidence_check",
18743
18743
  "check_gate_status",
18744
18744
  "completion_verify",
18745
- "convene_council",
18745
+ "submit_council_verdicts",
18746
18746
  "declare_council_criteria",
18747
18747
  "sbom_generate",
18748
18748
  "checkpoint",
@@ -18822,7 +18822,7 @@ var AGENT_TOOL_MAP = {
18822
18822
  "check_gate_status",
18823
18823
  "completion_verify",
18824
18824
  "complexity_hotspots",
18825
- "convene_council",
18825
+ "submit_council_verdicts",
18826
18826
  "declare_council_criteria",
18827
18827
  "detect_domains",
18828
18828
  "evidence_check",
@@ -19624,7 +19624,8 @@ var CouncilConfigSchema = exports_external.object({
19624
19624
  maxRounds: exports_external.number().int().min(1).max(10).default(3),
19625
19625
  parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
19626
19626
  vetoPriority: exports_external.boolean().default(true),
19627
- requireAllMembers: exports_external.boolean().default(false).describe("When true, convene_council rejects if fewer than 5 member verdicts are provided."),
19627
+ requireAllMembers: exports_external.boolean().default(false).describe("When true, submit_council_verdicts rejects if fewer than 5 member verdicts are provided. Equivalent to minimumMembers: 5."),
19628
+ minimumMembers: exports_external.number().int().min(1).max(5).default(3).describe("Minimum distinct council member verdicts required for synthesis. Default 3. Set to 1 to disable quorum enforcement. requireAllMembers: true overrides this to 5 (stricter constraint wins)."),
19628
19629
  escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet."),
19629
19630
  general: GeneralCouncilConfigSchema.optional()
19630
19631
  }).strict();
@@ -19690,6 +19691,7 @@ var PluginConfigSchema = exports_external.object({
19690
19691
  parallelization: ParallelizationConfigSchema.optional(),
19691
19692
  turbo_mode: exports_external.boolean().default(false).optional(),
19692
19693
  quiet: exports_external.boolean().default(false).optional(),
19694
+ version_check: exports_external.boolean().default(true).optional(),
19693
19695
  full_auto: exports_external.object({
19694
19696
  enabled: exports_external.boolean().default(false),
19695
19697
  critic_model: exports_external.string().optional(),
@@ -19966,9 +19968,17 @@ init_plan_schema();
19966
19968
  import { createHash as createHash3 } from "crypto";
19967
19969
 
19968
19970
  // src/db/project-db.ts
19969
- import { Database } from "bun:sqlite";
19970
19971
  import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
19972
+ import { createRequire } from "module";
19971
19973
  import { join as join7, resolve as resolve5 } from "path";
19974
+ var _DatabaseCtor = null;
19975
+ function loadDatabaseCtor() {
19976
+ if (_DatabaseCtor)
19977
+ return _DatabaseCtor;
19978
+ const req = createRequire(import.meta.url);
19979
+ _DatabaseCtor = req("bun:sqlite").Database;
19980
+ return _DatabaseCtor;
19981
+ }
19972
19982
  var MIGRATIONS = [
19973
19983
  {
19974
19984
  version: 1,
@@ -20039,7 +20049,8 @@ function getProjectDb(directory) {
20039
20049
  return existing;
20040
20050
  const swarmDir = join7(key, ".swarm");
20041
20051
  mkdirSync4(swarmDir, { recursive: true });
20042
- const db = new Database(join7(swarmDir, "swarm.db"));
20052
+ const Db = loadDatabaseCtor();
20053
+ const db = new Db(join7(swarmDir, "swarm.db"));
20043
20054
  db.run("PRAGMA journal_mode = WAL;");
20044
20055
  db.run("PRAGMA synchronous = NORMAL;");
20045
20056
  db.run("PRAGMA busy_timeout = 5000;");
@@ -36257,12 +36268,65 @@ async function handleDarkMatterCommand(directory, args) {
36257
36268
 
36258
36269
  // src/services/diagnose-service.ts
36259
36270
  import * as child_process4 from "child_process";
36260
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
36271
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
36261
36272
  import path17 from "path";
36262
36273
  import { fileURLToPath } from "url";
36263
36274
  init_manager2();
36264
36275
  init_utils2();
36265
36276
  init_manager();
36277
+
36278
+ // src/services/version-check.ts
36279
+ import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
36280
+ import { homedir as homedir4 } from "os";
36281
+ import { join as join14 } from "path";
36282
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
36283
+ function cacheDir() {
36284
+ const xdg = process.env.XDG_CACHE_HOME;
36285
+ const base = xdg && xdg.length > 0 ? xdg : join14(homedir4(), ".cache");
36286
+ return join14(base, "opencode-swarm");
36287
+ }
36288
+ function cacheFile() {
36289
+ return join14(cacheDir(), "version-check.json");
36290
+ }
36291
+ function readVersionCache() {
36292
+ try {
36293
+ const path17 = cacheFile();
36294
+ if (!existsSync8(path17))
36295
+ return null;
36296
+ const raw = readFileSync5(path17, "utf-8");
36297
+ const parsed = JSON.parse(raw);
36298
+ if (typeof parsed?.checkedAt !== "number")
36299
+ return null;
36300
+ const npmLatest = typeof parsed.npmLatest === "string" ? parsed.npmLatest : null;
36301
+ return { checkedAt: parsed.checkedAt, npmLatest };
36302
+ } catch {
36303
+ return null;
36304
+ }
36305
+ }
36306
+ function compareVersions(a, b) {
36307
+ const [aBase, aPre] = a.split("-", 2);
36308
+ const [bBase, bPre] = b.split("-", 2);
36309
+ const aParts = aBase.split(".").map((n) => Number.parseInt(n, 10) || 0);
36310
+ const bParts = bBase.split(".").map((n) => Number.parseInt(n, 10) || 0);
36311
+ const len = Math.max(aParts.length, bParts.length);
36312
+ for (let i = 0;i < len; i++) {
36313
+ const av = aParts[i] ?? 0;
36314
+ const bv = bParts[i] ?? 0;
36315
+ if (av > bv)
36316
+ return 1;
36317
+ if (av < bv)
36318
+ return -1;
36319
+ }
36320
+ if (aPre && !bPre)
36321
+ return -1;
36322
+ if (!aPre && bPre)
36323
+ return 1;
36324
+ if (aPre && bPre)
36325
+ return aPre < bPre ? -1 : aPre > bPre ? 1 : 0;
36326
+ return 0;
36327
+ }
36328
+
36329
+ // src/services/diagnose-service.ts
36266
36330
  var { version: version3 } = package_default;
36267
36331
  function validateTaskDag(plan) {
36268
36332
  const allTaskIds = new Set;
@@ -36495,7 +36559,7 @@ async function checkConfigBackups(directory) {
36495
36559
  }
36496
36560
  async function checkGitRepository(directory) {
36497
36561
  try {
36498
- if (!existsSync8(directory) || !statSync5(directory).isDirectory()) {
36562
+ if (!existsSync9(directory) || !statSync5(directory).isDirectory()) {
36499
36563
  return {
36500
36564
  name: "Git Repository",
36501
36565
  status: "\u274C",
@@ -36560,7 +36624,7 @@ async function checkSpecStaleness(directory, plan) {
36560
36624
  }
36561
36625
  async function checkConfigParseability(directory) {
36562
36626
  const configPath = path17.join(directory, ".opencode/opencode-swarm.json");
36563
- if (!existsSync8(configPath)) {
36627
+ if (!existsSync9(configPath)) {
36564
36628
  return {
36565
36629
  name: "Config Parseability",
36566
36630
  status: "\u2705",
@@ -36568,7 +36632,7 @@ async function checkConfigParseability(directory) {
36568
36632
  };
36569
36633
  }
36570
36634
  try {
36571
- const content = readFileSync5(configPath, "utf-8");
36635
+ const content = readFileSync6(configPath, "utf-8");
36572
36636
  JSON.parse(content);
36573
36637
  return {
36574
36638
  name: "Config Parseability",
@@ -36615,11 +36679,11 @@ async function checkGrammarWasmFiles() {
36615
36679
  const thisDir = path17.dirname(fileURLToPath(import.meta.url));
36616
36680
  const grammarDir = resolveGrammarDir(thisDir);
36617
36681
  const missing = [];
36618
- if (!existsSync8(path17.join(grammarDir, "tree-sitter.wasm"))) {
36682
+ if (!existsSync9(path17.join(grammarDir, "tree-sitter.wasm"))) {
36619
36683
  missing.push("tree-sitter.wasm (core runtime)");
36620
36684
  }
36621
36685
  for (const file3 of grammarFiles) {
36622
- if (!existsSync8(path17.join(grammarDir, file3))) {
36686
+ if (!existsSync9(path17.join(grammarDir, file3))) {
36623
36687
  missing.push(file3);
36624
36688
  }
36625
36689
  }
@@ -36638,7 +36702,7 @@ async function checkGrammarWasmFiles() {
36638
36702
  }
36639
36703
  async function checkCheckpointManifest(directory) {
36640
36704
  const manifestPath = path17.join(directory, ".swarm/checkpoints.json");
36641
- if (!existsSync8(manifestPath)) {
36705
+ if (!existsSync9(manifestPath)) {
36642
36706
  return {
36643
36707
  name: "Checkpoint Manifest",
36644
36708
  status: "\u2705",
@@ -36646,7 +36710,7 @@ async function checkCheckpointManifest(directory) {
36646
36710
  };
36647
36711
  }
36648
36712
  try {
36649
- const content = readFileSync5(manifestPath, "utf-8");
36713
+ const content = readFileSync6(manifestPath, "utf-8");
36650
36714
  const parsed = JSON.parse(content);
36651
36715
  if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
36652
36716
  return {
@@ -36690,7 +36754,7 @@ async function checkCheckpointManifest(directory) {
36690
36754
  }
36691
36755
  async function checkEventStreamIntegrity(directory) {
36692
36756
  const eventsPath = path17.join(directory, ".swarm/events.jsonl");
36693
- if (!existsSync8(eventsPath)) {
36757
+ if (!existsSync9(eventsPath)) {
36694
36758
  return {
36695
36759
  name: "Event Stream",
36696
36760
  status: "\u2705",
@@ -36698,7 +36762,7 @@ async function checkEventStreamIntegrity(directory) {
36698
36762
  };
36699
36763
  }
36700
36764
  try {
36701
- const content = readFileSync5(eventsPath, "utf-8");
36765
+ const content = readFileSync6(eventsPath, "utf-8");
36702
36766
  const lines = content.split(`
36703
36767
  `).filter((line) => line.trim() !== "");
36704
36768
  let malformedCount = 0;
@@ -36731,7 +36795,7 @@ async function checkEventStreamIntegrity(directory) {
36731
36795
  }
36732
36796
  async function checkSteeringDirectives(directory) {
36733
36797
  const eventsPath = path17.join(directory, ".swarm/events.jsonl");
36734
- if (!existsSync8(eventsPath)) {
36798
+ if (!existsSync9(eventsPath)) {
36735
36799
  return {
36736
36800
  name: "Steering Directives",
36737
36801
  status: "\u2705",
@@ -36739,7 +36803,7 @@ async function checkSteeringDirectives(directory) {
36739
36803
  };
36740
36804
  }
36741
36805
  try {
36742
- const content = readFileSync5(eventsPath, "utf-8");
36806
+ const content = readFileSync6(eventsPath, "utf-8");
36743
36807
  const lines = content.split(`
36744
36808
  `).filter((line) => line.trim() !== "");
36745
36809
  const directivesIssued = [];
@@ -36787,7 +36851,7 @@ async function checkCurator(directory) {
36787
36851
  };
36788
36852
  }
36789
36853
  const summaryPath = path17.join(directory, ".swarm/curator-summary.json");
36790
- if (!existsSync8(summaryPath)) {
36854
+ if (!existsSync9(summaryPath)) {
36791
36855
  return {
36792
36856
  name: "Curator",
36793
36857
  status: "\u2705",
@@ -36795,7 +36859,7 @@ async function checkCurator(directory) {
36795
36859
  };
36796
36860
  }
36797
36861
  try {
36798
- const content = readFileSync5(summaryPath, "utf-8");
36862
+ const content = readFileSync6(summaryPath, "utf-8");
36799
36863
  const parsed = JSON.parse(content);
36800
36864
  if (typeof parsed.schema_version !== "number" || parsed.schema_version !== 1) {
36801
36865
  return {
@@ -36830,10 +36894,23 @@ async function checkCurator(directory) {
36830
36894
  }
36831
36895
  async function getDiagnoseData(directory) {
36832
36896
  const checks5 = [];
36897
+ const versionCache = readVersionCache();
36898
+ let versionDetail = version3;
36899
+ let versionStatus = "\u2705";
36900
+ if (versionCache?.npmLatest) {
36901
+ const ageMs = Date.now() - versionCache.checkedAt;
36902
+ const ageMin = Math.max(0, Math.round(ageMs / 60000));
36903
+ if (compareVersions(versionCache.npmLatest, version3) > 0) {
36904
+ versionStatus = "\u26A0\uFE0F";
36905
+ versionDetail = `${version3} (npm latest: ${versionCache.npmLatest}, checked ${ageMin}m ago) ` + "\u2014 run `bunx opencode-swarm update` to refresh";
36906
+ } else {
36907
+ versionDetail = `${version3} (npm latest: ${versionCache.npmLatest}, checked ${ageMin}m ago)`;
36908
+ }
36909
+ }
36833
36910
  checks5.push({
36834
36911
  name: "Version",
36835
- status: "\u2705",
36836
- detail: version3
36912
+ status: versionStatus,
36913
+ detail: versionDetail
36837
36914
  });
36838
36915
  const plan = await loadPlanJsonOnly(directory);
36839
36916
  if (plan) {
@@ -36940,7 +37017,7 @@ async function getDiagnoseData(directory) {
36940
37017
  checks5.push(await checkCurator(directory));
36941
37018
  try {
36942
37019
  const evidenceDir = path17.join(directory, ".swarm", "evidence");
36943
- const snapshotFiles = existsSync8(evidenceDir) ? readdirSync4(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
37020
+ const snapshotFiles = existsSync9(evidenceDir) ? readdirSync4(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
36944
37021
  if (snapshotFiles.length > 0) {
36945
37022
  const latest = snapshotFiles.sort().pop();
36946
37023
  checks5.push({
@@ -37015,7 +37092,7 @@ import * as path19 from "path";
37015
37092
 
37016
37093
  // src/lang/detector.ts
37017
37094
  import { access as access2, readdir as readdir2 } from "fs/promises";
37018
- import { extname as extname2, join as join15 } from "path";
37095
+ import { extname as extname2, join as join16 } from "path";
37019
37096
 
37020
37097
  // src/lang/profiles.ts
37021
37098
  class LanguageRegistry {
@@ -37995,7 +38072,7 @@ async function detectProjectLanguages(projectDir) {
37995
38072
  if (detectFile.includes("*") || detectFile.includes("?"))
37996
38073
  continue;
37997
38074
  try {
37998
- await access2(join15(dir, detectFile));
38075
+ await access2(join16(dir, detectFile));
37999
38076
  detected.add(profile.id);
38000
38077
  break;
38001
38078
  } catch {}
@@ -38016,7 +38093,7 @@ async function detectProjectLanguages(projectDir) {
38016
38093
  const topEntries = await readdir2(projectDir, { withFileTypes: true });
38017
38094
  for (const entry of topEntries) {
38018
38095
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
38019
- await scanDir(join15(projectDir, entry.name));
38096
+ await scanDir(join16(projectDir, entry.name));
38020
38097
  }
38021
38098
  }
38022
38099
  } catch {}
@@ -39362,14 +39439,14 @@ async function handleHistoryCommand(directory, _args) {
39362
39439
  }
39363
39440
  // src/hooks/knowledge-migrator.ts
39364
39441
  import { randomUUID as randomUUID2 } from "crypto";
39365
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
39442
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
39366
39443
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
39367
39444
  import * as path21 from "path";
39368
39445
  async function migrateContextToKnowledge(directory, config3) {
39369
39446
  const sentinelPath = path21.join(directory, ".swarm", ".knowledge-migrated");
39370
39447
  const contextPath = path21.join(directory, ".swarm", "context.md");
39371
39448
  const knowledgePath = resolveSwarmKnowledgePath(directory);
39372
- if (existsSync12(sentinelPath)) {
39449
+ if (existsSync13(sentinelPath)) {
39373
39450
  return {
39374
39451
  migrated: false,
39375
39452
  entriesMigrated: 0,
@@ -39378,7 +39455,7 @@ async function migrateContextToKnowledge(directory, config3) {
39378
39455
  skippedReason: "sentinel-exists"
39379
39456
  };
39380
39457
  }
39381
- if (!existsSync12(contextPath)) {
39458
+ if (!existsSync13(contextPath)) {
39382
39459
  return {
39383
39460
  migrated: false,
39384
39461
  entriesMigrated: 0,
@@ -39564,9 +39641,9 @@ function truncateLesson(text) {
39564
39641
  }
39565
39642
  function inferProjectName(directory) {
39566
39643
  const packageJsonPath = path21.join(directory, "package.json");
39567
- if (existsSync12(packageJsonPath)) {
39644
+ if (existsSync13(packageJsonPath)) {
39568
39645
  try {
39569
- const pkg = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
39646
+ const pkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
39570
39647
  if (pkg.name && typeof pkg.name === "string") {
39571
39648
  return pkg.name;
39572
39649
  }
@@ -41116,10 +41193,10 @@ async function loadImpactMap(cwd) {
41116
41193
  return buildImpactMap(cwd);
41117
41194
  }
41118
41195
  async function saveImpactMap(cwd, impactMap) {
41119
- const cacheDir = path24.join(cwd, ".swarm", "cache");
41120
- const cachePath = path24.join(cacheDir, "impact-map.json");
41121
- if (!fs13.existsSync(cacheDir)) {
41122
- fs13.mkdirSync(cacheDir, { recursive: true });
41196
+ const cacheDir2 = path24.join(cwd, ".swarm", "cache");
41197
+ const cachePath = path24.join(cacheDir2, "impact-map.json");
41198
+ if (!fs13.existsSync(cacheDir2)) {
41199
+ fs13.mkdirSync(cacheDir2, { recursive: true });
41123
41200
  }
41124
41201
  const data = {
41125
41202
  generatedAt: new Date().toISOString(),
@@ -45287,7 +45364,18 @@ var CONFIG_DIR = path32.join(process.env.XDG_CONFIG_HOME || path32.join(os6.home
45287
45364
  var OPENCODE_CONFIG_PATH = path32.join(CONFIG_DIR, "opencode.json");
45288
45365
  var PLUGIN_CONFIG_PATH = path32.join(CONFIG_DIR, "opencode-swarm.json");
45289
45366
  var PROMPTS_DIR = path32.join(CONFIG_DIR, "opencode-swarm");
45290
- var OPENCODE_PLUGIN_CACHE_PATH = path32.join(process.env.XDG_CACHE_HOME || path32.join(os6.homedir(), ".cache"), "opencode", "packages", "opencode-swarm@latest");
45367
+ var OPENCODE_PLUGIN_CACHE_PATHS = [
45368
+ path32.join(process.env.XDG_CACHE_HOME || path32.join(os6.homedir(), ".cache"), "opencode", "packages", "opencode-swarm@latest"),
45369
+ path32.join(CONFIG_DIR, "node_modules", "opencode-swarm")
45370
+ ];
45371
+ function isSafeCachePath(p) {
45372
+ const resolved = path32.resolve(p);
45373
+ const home = path32.resolve(os6.homedir());
45374
+ if (resolved === "/" || resolved === home || resolved.length <= home.length)
45375
+ return false;
45376
+ const leaf = path32.basename(resolved);
45377
+ return leaf === "opencode-swarm@latest" || leaf === "opencode-swarm";
45378
+ }
45291
45379
  function ensureDir(dir) {
45292
45380
  if (!fs21.existsSync(dir)) {
45293
45381
  fs21.mkdirSync(dir, { recursive: true });
@@ -45358,14 +45446,13 @@ async function install() {
45358
45446
  saveJson(OPENCODE_CONFIG_PATH, opencodeConfig);
45359
45447
  console.log("\u2713 Added opencode-swarm to OpenCode plugins");
45360
45448
  console.log("\u2713 Disabled default OpenCode agents (explore, general)");
45361
- try {
45362
- if (fs21.existsSync(OPENCODE_PLUGIN_CACHE_PATH)) {
45363
- fs21.rmSync(OPENCODE_PLUGIN_CACHE_PATH, { recursive: true, force: true });
45364
- console.log("\u2713 Cleared opencode plugin cache (next start will fetch latest)");
45365
- }
45366
- } catch {
45367
- console.warn("\u26A0 Could not clear opencode plugin cache \u2014 you may need to delete it manually:");
45368
- console.warn(` ${OPENCODE_PLUGIN_CACHE_PATH}`);
45449
+ const evicted = evictPluginCaches();
45450
+ if (evicted.cleared.length > 0) {
45451
+ console.log(`\u2713 Cleared opencode plugin cache (next start will fetch latest): ${evicted.cleared.join(", ")}`);
45452
+ }
45453
+ for (const failed of evicted.failed) {
45454
+ console.warn(`\u26A0 Could not clear opencode plugin cache \u2014 you may need to delete it manually:
45455
+ ${failed}`);
45369
45456
  }
45370
45457
  if (!fs21.existsSync(PLUGIN_CONFIG_PATH)) {
45371
45458
  const defaultConfig = {
@@ -45465,6 +45552,51 @@ Next steps:`);
45465
45552
  console.log(" \u2014 use it as a reference for customizing model assignments.");
45466
45553
  return 0;
45467
45554
  }
45555
+ async function update() {
45556
+ console.log(`\uD83D\uDC1D Refreshing OpenCode Swarm plugin cache...
45557
+ `);
45558
+ const result = evictPluginCaches();
45559
+ if (result.cleared.length > 0) {
45560
+ for (const cleared of result.cleared) {
45561
+ console.log(`\u2713 Cleared: ${cleared}`);
45562
+ }
45563
+ console.log(`
45564
+ Restart OpenCode to fetch the latest version from npm.`);
45565
+ }
45566
+ if (result.cleared.length === 0 && result.failed.length === 0) {
45567
+ console.log("No cached plugin found. Restart OpenCode to fetch the latest version from npm.");
45568
+ console.log("Checked locations:");
45569
+ for (const p of OPENCODE_PLUGIN_CACHE_PATHS) {
45570
+ console.log(` - ${p}`);
45571
+ }
45572
+ }
45573
+ if (result.failed.length > 0) {
45574
+ for (const failed of result.failed) {
45575
+ console.error(`\u2717 Could not clear: ${failed}`);
45576
+ }
45577
+ return 1;
45578
+ }
45579
+ return 0;
45580
+ }
45581
+ function evictPluginCaches() {
45582
+ const cleared = [];
45583
+ const failed = [];
45584
+ for (const cachePath of OPENCODE_PLUGIN_CACHE_PATHS) {
45585
+ if (!fs21.existsSync(cachePath))
45586
+ continue;
45587
+ if (!isSafeCachePath(cachePath)) {
45588
+ failed.push(`${cachePath} (refused: failed safety check)`);
45589
+ continue;
45590
+ }
45591
+ try {
45592
+ fs21.rmSync(cachePath, { recursive: true, force: true });
45593
+ cleared.push(cachePath);
45594
+ } catch (err) {
45595
+ failed.push(`${cachePath} (${err instanceof Error ? err.message : String(err)})`);
45596
+ }
45597
+ }
45598
+ return { cleared, failed };
45599
+ }
45468
45600
  async function uninstall() {
45469
45601
  try {
45470
45602
  console.log(`\uD83D\uDC1D Uninstalling OpenCode Swarm...
@@ -45535,6 +45667,7 @@ Usage: bunx opencode-swarm [command] [OPTIONS]
45535
45667
 
45536
45668
  Commands:
45537
45669
  install Install and configure the plugin (default)
45670
+ update Refresh OpenCode's plugin cache so the next start fetches latest from npm
45538
45671
  uninstall Remove the plugin from OpenCode config
45539
45672
  run Run a plugin command directly (for use outside OpenCode)
45540
45673
 
@@ -45559,6 +45692,7 @@ Custom Prompts:
45559
45692
 
45560
45693
  Examples:
45561
45694
  bunx opencode-swarm install
45695
+ bunx opencode-swarm update
45562
45696
  bunx opencode-swarm uninstall
45563
45697
  bunx opencode-swarm uninstall --clean
45564
45698
  bunx opencode-swarm --help
@@ -45584,6 +45718,9 @@ async function main() {
45584
45718
  if (command === "install") {
45585
45719
  const exitCode = await install();
45586
45720
  process.exit(exitCode);
45721
+ } else if (command === "update") {
45722
+ const exitCode = await update();
45723
+ process.exit(exitCode);
45587
45724
  } else if (command === "uninstall") {
45588
45725
  const exitCode = await uninstall();
45589
45726
  process.exit(exitCode);
@@ -576,6 +576,7 @@ export declare const CouncilConfigSchema: z.ZodObject<{
576
576
  parallelTimeoutMs: z.ZodDefault<z.ZodNumber>;
577
577
  vetoPriority: z.ZodDefault<z.ZodBoolean>;
578
578
  requireAllMembers: z.ZodDefault<z.ZodBoolean>;
579
+ minimumMembers: z.ZodDefault<z.ZodNumber>;
579
580
  escalateOnMaxRounds: z.ZodOptional<z.ZodString>;
580
581
  general: z.ZodOptional<z.ZodObject<{
581
582
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -996,6 +997,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
996
997
  parallelTimeoutMs: z.ZodDefault<z.ZodNumber>;
997
998
  vetoPriority: z.ZodDefault<z.ZodBoolean>;
998
999
  requireAllMembers: z.ZodDefault<z.ZodBoolean>;
1000
+ minimumMembers: z.ZodDefault<z.ZodNumber>;
999
1001
  escalateOnMaxRounds: z.ZodOptional<z.ZodString>;
1000
1002
  general: z.ZodOptional<z.ZodObject<{
1001
1003
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -1046,6 +1048,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
1046
1048
  }, z.core.$strip>>;
1047
1049
  turbo_mode: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
1048
1050
  quiet: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
1051
+ version_check: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
1049
1052
  full_auto: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1050
1053
  enabled: z.ZodDefault<z.ZodBoolean>;
1051
1054
  critic_model: z.ZodOptional<z.ZodString>;
@@ -48,6 +48,8 @@ export interface CouncilSynthesis {
48
48
  /** 1-indexed */
49
49
  roundNumber: number;
50
50
  allCriteriaMet: boolean;
51
+ /** Distinct council members that produced verdicts (deduplicated count). */
52
+ quorumSize: number;
51
53
  /** true when called with an empty verdicts array — the APPROVE is vacuous */
52
54
  emptyVerdictsWarning?: boolean;
53
55
  }
@@ -71,8 +73,10 @@ export interface CouncilConfig {
71
73
  parallelTimeoutMs: number;
72
74
  /** Default true — any REJECT blocks */
73
75
  vetoPriority: boolean;
74
- /** Default false — when true, convene_council rejects unless all 5 member verdicts are provided */
76
+ /** Default false — when true, submit_council_verdicts rejects unless all 5 member verdicts are provided */
75
77
  requireAllMembers: boolean;
78
+ /** Default 3 — minimum distinct council members required for quorum. requireAllMembers: true overrides this to 5. */
79
+ minimumMembers: number;
76
80
  /**
77
81
  * Optional webhook URL or handler name for auto-escalation when maxRounds is
78
82
  * reached without APPROVE. Reserved for forward compatibility — NOT yet
@@ -5,7 +5,7 @@
5
5
  * rules and agent prompt sections. Per-project QA gate profiles live in the
6
6
  * project DB (see `./project-db.ts`), not here.
7
7
  */
8
- import { Database } from 'bun:sqlite';
8
+ import type { Database } from 'bun:sqlite';
9
9
  /**
10
10
  * Run all pending migrations on the provided database.
11
11
  * Idempotent: existing migrations are not re-applied.
@@ -5,7 +5,7 @@
5
5
  * constraints and QA gate profiles. One cached instance per normalized
6
6
  * directory path.
7
7
  */
8
- import { Database } from 'bun:sqlite';
8
+ import type { Database } from 'bun:sqlite';
9
9
  /**
10
10
  * Run all pending migrations on the provided database.
11
11
  * Idempotent: existing migrations are not re-applied.
@@ -85,7 +85,7 @@ export declare function computeProfileHash(profile: QaGateProfile): string;
85
85
  * machine; blocks coder→next-coder advancement until reviewer + test_engineer
86
86
  * delegations observed).
87
87
  * - council_mode — src/state.ts isCouncilGateActive + src/hooks/delegation-gate.ts
88
- * (Stage B replaced by convene_council verdict).
88
+ * (Stage B replaced by submit_council_verdicts verdict).
89
89
  * - sme_enabled — consumed during MODE: BRAINSTORM/SPECIFY architect dialogue.
90
90
  * - critic_pre_plan — consumed by MODE: PLAN critic delegation before save_plan.
91
91
  * - sast_enabled — consumed inside pre_check_batch tool.