opencode-swarm 6.67.0 → 6.68.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
@@ -18507,7 +18507,9 @@ var TOOL_NAMES = [
18507
18507
  "suggest_patch",
18508
18508
  "req_coverage",
18509
18509
  "get_approved_plan",
18510
- "repo_map"
18510
+ "repo_map",
18511
+ "get_qa_gate_profile",
18512
+ "set_qa_gates"
18511
18513
  ];
18512
18514
  var TOOL_NAME_SET = new Set(TOOL_NAMES);
18513
18515
 
@@ -18577,7 +18579,9 @@ var AGENT_TOOL_MAP = {
18577
18579
  "knowledge_remove",
18578
18580
  "co_change_analyzer",
18579
18581
  "suggest_patch",
18580
- "repo_map"
18582
+ "repo_map",
18583
+ "get_qa_gate_profile",
18584
+ "set_qa_gates"
18581
18585
  ],
18582
18586
  explorer: [
18583
18587
  "complexity_hotspots",
@@ -19818,6 +19822,24 @@ async function handleBenchmarkCommand(directory, args) {
19818
19822
  `);
19819
19823
  }
19820
19824
 
19825
+ // src/commands/brainstorm.ts
19826
+ function sanitizeTopic(raw) {
19827
+ const collapsed = raw.replace(/\s+/g, " ").trim();
19828
+ const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
19829
+ const normalized = stripped.replace(/\s+/g, " ").trim();
19830
+ const MAX_TOPIC_LEN = 2000;
19831
+ if (normalized.length <= MAX_TOPIC_LEN)
19832
+ return normalized;
19833
+ return `${normalized.slice(0, MAX_TOPIC_LEN)}\u2026`;
19834
+ }
19835
+ async function handleBrainstormCommand(_directory, args) {
19836
+ const description = sanitizeTopic(args.join(" "));
19837
+ if (description) {
19838
+ return `[MODE: BRAINSTORM] ${description}`;
19839
+ }
19840
+ return "[MODE: BRAINSTORM] Please enter MODE: BRAINSTORM and begin the structured brainstorm workflow (CONTEXT SCAN \u2192 DIALOGUE \u2192 APPROACHES \u2192 DESIGN SECTIONS \u2192 SPEC WRITE + SELF-REVIEW \u2192 QA GATE SELECTION \u2192 TRANSITION).";
19841
+ }
19842
+
19821
19843
  // src/commands/checkpoint.ts
19822
19844
  init_zod();
19823
19845
 
@@ -37050,7 +37072,7 @@ function checkAgentToolMapAlignment(registeredKeys) {
37050
37072
  id: `agent-tool-map-mismatch-${agentName}-${toolName}`,
37051
37073
  title: "AGENT_TOOL_MAP alignment gap",
37052
37074
  description: `Tool "${toolName}" is assigned to agent "${agentName}" in AGENT_TOOL_MAP but is not registered in the plugin's tool: {} block. The agent will not be able to use this tool.`,
37053
- severity: "warn",
37075
+ severity: "error",
37054
37076
  path: `AGENT_TOOL_MAP.${agentName}`,
37055
37077
  currentValue: toolName,
37056
37078
  autoFixable: false
@@ -37159,6 +37181,11 @@ function formatToolDoctorMarkdown(result) {
37159
37181
  }
37160
37182
  lines.push("");
37161
37183
  }
37184
+ if (result.summary.error > 0) {
37185
+ lines.push("---", "");
37186
+ lines.push(`**BLOCKING**: ${result.summary.error} error-severity finding(s) must be resolved before release. ` + `AGENT_TOOL_MAP alignment errors mean an agent's system prompt instructs the model to call a tool that opencode has not registered \u2014 the agent's workflow will silently fail at runtime.`);
37187
+ lines.push("");
37188
+ }
37162
37189
  }
37163
37190
  return lines.join(`
37164
37191
  `);
@@ -41488,6 +41515,324 @@ async function handlePromoteCommand(directory, args) {
41488
41515
  }
41489
41516
  }
41490
41517
 
41518
+ // src/db/qa-gate-profile.ts
41519
+ import { createHash as createHash4 } from "crypto";
41520
+
41521
+ // src/db/project-db.ts
41522
+ import { Database } from "bun:sqlite";
41523
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7 } from "fs";
41524
+ import { join as join22, resolve as resolve11 } from "path";
41525
+ var MIGRATIONS = [
41526
+ {
41527
+ version: 1,
41528
+ name: "create_project_constraints",
41529
+ sql: `CREATE TABLE project_constraints (
41530
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
41531
+ constraint_type TEXT NOT NULL,
41532
+ content TEXT NOT NULL,
41533
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
41534
+ )`
41535
+ },
41536
+ {
41537
+ version: 2,
41538
+ name: "create_qa_gate_profile",
41539
+ sql: `CREATE TABLE qa_gate_profile (
41540
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
41541
+ plan_id TEXT NOT NULL UNIQUE,
41542
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
41543
+ project_type TEXT,
41544
+ gates TEXT NOT NULL DEFAULT '{}',
41545
+ locked_at TEXT,
41546
+ locked_by_snapshot_seq INTEGER
41547
+ )`
41548
+ },
41549
+ {
41550
+ version: 3,
41551
+ name: "create_qa_gate_profile_immutability_trigger",
41552
+ sql: `CREATE TRIGGER IF NOT EXISTS trg_qa_gate_profile_no_update_after_lock
41553
+ BEFORE UPDATE ON qa_gate_profile
41554
+ WHEN OLD.locked_at IS NOT NULL
41555
+ BEGIN
41556
+ SELECT RAISE(ABORT, 'qa_gate_profile row is locked and cannot be modified after critic approval');
41557
+ END`
41558
+ }
41559
+ ];
41560
+ var _projectDbs = new Map;
41561
+ function runProjectMigrations(db) {
41562
+ db.run(`CREATE TABLE IF NOT EXISTS schema_migrations (
41563
+ version INTEGER PRIMARY KEY,
41564
+ name TEXT NOT NULL,
41565
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
41566
+ )`);
41567
+ const row = db.query("SELECT MAX(version) as version FROM schema_migrations").get();
41568
+ const currentVersion = row?.version ?? 0;
41569
+ for (const migration of MIGRATIONS) {
41570
+ if (migration.version <= currentVersion)
41571
+ continue;
41572
+ const apply = db.transaction(() => {
41573
+ db.run(migration.sql);
41574
+ db.run("INSERT INTO schema_migrations (version, name) VALUES (?, ?)", [
41575
+ migration.version,
41576
+ migration.name
41577
+ ]);
41578
+ });
41579
+ apply();
41580
+ }
41581
+ }
41582
+ function projectDbPath(directory) {
41583
+ return join22(resolve11(directory), ".swarm", "swarm.db");
41584
+ }
41585
+ function projectDbExists(directory) {
41586
+ return existsSync16(projectDbPath(directory));
41587
+ }
41588
+ function getProjectDb(directory) {
41589
+ const key = resolve11(directory);
41590
+ const existing = _projectDbs.get(key);
41591
+ if (existing)
41592
+ return existing;
41593
+ const swarmDir = join22(key, ".swarm");
41594
+ mkdirSync7(swarmDir, { recursive: true });
41595
+ const db = new Database(join22(swarmDir, "swarm.db"));
41596
+ db.run("PRAGMA journal_mode = WAL;");
41597
+ db.run("PRAGMA synchronous = NORMAL;");
41598
+ db.run("PRAGMA busy_timeout = 5000;");
41599
+ db.run("PRAGMA foreign_keys = ON;");
41600
+ runProjectMigrations(db);
41601
+ _projectDbs.set(key, db);
41602
+ return db;
41603
+ }
41604
+
41605
+ // src/db/qa-gate-profile.ts
41606
+ var DEFAULT_QA_GATES = {
41607
+ reviewer: true,
41608
+ test_engineer: true,
41609
+ council_mode: false,
41610
+ sme_enabled: true,
41611
+ critic_pre_plan: true,
41612
+ hallucination_guard: false,
41613
+ sast_enabled: true
41614
+ };
41615
+ function rowToProfile(row) {
41616
+ let parsed = {};
41617
+ try {
41618
+ parsed = JSON.parse(row.gates);
41619
+ } catch {
41620
+ parsed = {};
41621
+ }
41622
+ const gates = { ...DEFAULT_QA_GATES, ...parsed };
41623
+ return {
41624
+ id: row.id,
41625
+ plan_id: row.plan_id,
41626
+ created_at: row.created_at,
41627
+ project_type: row.project_type,
41628
+ gates,
41629
+ locked_at: row.locked_at,
41630
+ locked_by_snapshot_seq: row.locked_by_snapshot_seq
41631
+ };
41632
+ }
41633
+ function getProfile(directory, planId) {
41634
+ if (!projectDbExists(directory))
41635
+ return null;
41636
+ const db = getProjectDb(directory);
41637
+ const row = db.query("SELECT * FROM qa_gate_profile WHERE plan_id = ?").get(planId);
41638
+ return row ? rowToProfile(row) : null;
41639
+ }
41640
+ function getOrCreateProfile(directory, planId, projectType) {
41641
+ const existing = getProfile(directory, planId);
41642
+ if (existing)
41643
+ return existing;
41644
+ const db = getProjectDb(directory);
41645
+ const gatesJson = JSON.stringify(DEFAULT_QA_GATES);
41646
+ const insert = db.transaction(() => {
41647
+ db.run("INSERT INTO qa_gate_profile (plan_id, project_type, gates) VALUES (?, ?, ?)", [planId, projectType ?? null, gatesJson]);
41648
+ });
41649
+ try {
41650
+ insert();
41651
+ } catch (err) {
41652
+ const msg = err instanceof Error ? err.message : String(err);
41653
+ if (!msg.toLowerCase().includes("unique")) {
41654
+ throw err;
41655
+ }
41656
+ }
41657
+ const after = getProfile(directory, planId);
41658
+ if (!after) {
41659
+ throw new Error(`Failed to create or load QA gate profile for plan_id=${planId}`);
41660
+ }
41661
+ return after;
41662
+ }
41663
+ function setGates(directory, planId, gates) {
41664
+ const current = getProfile(directory, planId);
41665
+ if (!current) {
41666
+ throw new Error(`No QA gate profile found for plan_id=${planId} \u2014 call getOrCreateProfile first`);
41667
+ }
41668
+ if (current.locked_at !== null) {
41669
+ throw new Error("Cannot modify gates: QA gate profile is locked after critic approval");
41670
+ }
41671
+ const merged = { ...current.gates };
41672
+ for (const key of Object.keys(gates)) {
41673
+ const incoming = gates[key];
41674
+ if (incoming === undefined)
41675
+ continue;
41676
+ if (incoming === false && current.gates[key] === true) {
41677
+ throw new Error(`Cannot disable gate '${key}': sessions can only ratchet tighter`);
41678
+ }
41679
+ if (incoming === true) {
41680
+ merged[key] = true;
41681
+ }
41682
+ }
41683
+ const db = getProjectDb(directory);
41684
+ db.run("UPDATE qa_gate_profile SET gates = ? WHERE plan_id = ?", [
41685
+ JSON.stringify(merged),
41686
+ planId
41687
+ ]);
41688
+ const updated = getProfile(directory, planId);
41689
+ if (!updated) {
41690
+ throw new Error(`Failed to re-read QA gate profile after update for plan_id=${planId}`);
41691
+ }
41692
+ return updated;
41693
+ }
41694
+ function computeProfileHash(profile) {
41695
+ const payload = JSON.stringify({
41696
+ plan_id: profile.plan_id,
41697
+ gates: profile.gates
41698
+ });
41699
+ return createHash4("sha256").update(payload).digest("hex");
41700
+ }
41701
+ function getEffectiveGates(profile, sessionOverrides) {
41702
+ const merged = { ...profile.gates };
41703
+ for (const key of Object.keys(sessionOverrides)) {
41704
+ if (sessionOverrides[key] === true) {
41705
+ merged[key] = true;
41706
+ }
41707
+ }
41708
+ return merged;
41709
+ }
41710
+
41711
+ // src/commands/qa-gates.ts
41712
+ init_manager();
41713
+ var ALL_GATE_NAMES = [
41714
+ "reviewer",
41715
+ "test_engineer",
41716
+ "council_mode",
41717
+ "sme_enabled",
41718
+ "critic_pre_plan",
41719
+ "hallucination_guard",
41720
+ "sast_enabled"
41721
+ ];
41722
+ function derivePlanId(plan) {
41723
+ return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
41724
+ }
41725
+ function isGateName(name) {
41726
+ return ALL_GATE_NAMES.includes(name);
41727
+ }
41728
+ function formatGates(gates) {
41729
+ return ALL_GATE_NAMES.map((g) => ` - ${g}: ${gates[g] ? "on" : "off"}`).join(`
41730
+ `);
41731
+ }
41732
+ async function handleQaGatesCommand(directory, args, sessionID) {
41733
+ const plan = await loadPlanJsonOnly(directory);
41734
+ if (!plan) {
41735
+ return "Error: plan.json not found or invalid. Create a plan first (e.g. /swarm specify or save_plan).";
41736
+ }
41737
+ const planId = derivePlanId(plan);
41738
+ const subcommand = args[0]?.toLowerCase();
41739
+ const gateArgs = args.slice(1);
41740
+ if (!subcommand || subcommand === "show" || subcommand === "status") {
41741
+ const profile = getProfile(directory, planId);
41742
+ const spec = profile ? profile.gates : DEFAULT_QA_GATES;
41743
+ const session = sessionID ? getAgentSession(sessionID) : null;
41744
+ const overrides = session?.qaGateSessionOverrides ?? {};
41745
+ const effective = profile ? getEffectiveGates(profile, overrides) : { ...DEFAULT_QA_GATES, ...overrides };
41746
+ const lines = [];
41747
+ lines.push(`QA Gate Profile for plan_id=${planId}`);
41748
+ if (!profile) {
41749
+ lines.push(" (no profile persisted yet \u2014 showing defaults)");
41750
+ } else {
41751
+ lines.push(` locked: ${profile.locked_at ? `yes @ ${profile.locked_at} (seq ${profile.locked_by_snapshot_seq ?? "?"})` : "no"}`);
41752
+ lines.push(` profile_hash: ${computeProfileHash(profile)}`);
41753
+ }
41754
+ lines.push("Spec-level gates:");
41755
+ lines.push(formatGates(spec));
41756
+ lines.push("Session overrides (ratchet-tighter only):");
41757
+ if (Object.keys(overrides).length === 0) {
41758
+ lines.push(" (none)");
41759
+ } else {
41760
+ for (const k of ALL_GATE_NAMES) {
41761
+ if (overrides[k] === true)
41762
+ lines.push(` - ${k}: on (override)`);
41763
+ }
41764
+ }
41765
+ lines.push("Effective gates:");
41766
+ lines.push(formatGates(effective));
41767
+ return lines.join(`
41768
+ `);
41769
+ }
41770
+ if (subcommand === "enable") {
41771
+ if (gateArgs.length === 0) {
41772
+ return "Usage: /swarm qa-gates enable <gate> [<gate> ...]";
41773
+ }
41774
+ const invalid = gateArgs.filter((g) => !isGateName(g));
41775
+ if (invalid.length > 0) {
41776
+ return `Error: unknown gate(s): ${invalid.join(", ")}. Valid gates: ${ALL_GATE_NAMES.join(", ")}`;
41777
+ }
41778
+ getOrCreateProfile(directory, planId);
41779
+ const patch = {};
41780
+ for (const g of gateArgs) {
41781
+ if (isGateName(g))
41782
+ patch[g] = true;
41783
+ }
41784
+ try {
41785
+ const updated = setGates(directory, planId, patch);
41786
+ return [
41787
+ `Enabled gates persisted for plan_id=${planId}:`,
41788
+ formatGates(updated.gates),
41789
+ `profile_hash: ${computeProfileHash(updated)}`
41790
+ ].join(`
41791
+ `);
41792
+ } catch (err) {
41793
+ const msg = err instanceof Error ? err.message : String(err);
41794
+ return `Error: ${msg}`;
41795
+ }
41796
+ }
41797
+ if (subcommand === "override") {
41798
+ if (!sessionID) {
41799
+ return "Error: session overrides require an active session context.";
41800
+ }
41801
+ if (gateArgs.length === 0) {
41802
+ return "Usage: /swarm qa-gates override <gate> [<gate> ...]";
41803
+ }
41804
+ const invalid = gateArgs.filter((g) => !isGateName(g));
41805
+ if (invalid.length > 0) {
41806
+ return `Error: unknown gate(s): ${invalid.join(", ")}. Valid gates: ${ALL_GATE_NAMES.join(", ")}`;
41807
+ }
41808
+ const session = getAgentSession(sessionID);
41809
+ if (!session) {
41810
+ return "Error: no active session found for override.";
41811
+ }
41812
+ const current = session.qaGateSessionOverrides ?? {};
41813
+ const next = { ...current };
41814
+ for (const g of gateArgs) {
41815
+ if (isGateName(g))
41816
+ next[g] = true;
41817
+ }
41818
+ session.qaGateSessionOverrides = next;
41819
+ return [
41820
+ `Session overrides updated for plan_id=${planId}:`,
41821
+ Object.keys(next).filter((k) => next[k] === true).map((k) => ` - ${k}: on`).join(`
41822
+ `) || " (none)"
41823
+ ].join(`
41824
+ `);
41825
+ }
41826
+ return [
41827
+ "Usage:",
41828
+ " /swarm qa-gates show current profile + effective gates",
41829
+ " /swarm qa-gates enable <gate>... persist-enable gate(s) (rejected if locked)",
41830
+ " /swarm qa-gates override <gate>... session-only enable (ratchet-tighter)",
41831
+ `Valid gates: ${ALL_GATE_NAMES.join(", ")}`
41832
+ ].join(`
41833
+ `);
41834
+ }
41835
+
41491
41836
  // src/commands/reset.ts
41492
41837
  import * as fs16 from "fs";
41493
41838
 
@@ -41544,13 +41889,13 @@ class CircuitBreaker {
41544
41889
  if (this.config.callTimeoutMs <= 0) {
41545
41890
  return fn();
41546
41891
  }
41547
- return new Promise((resolve11, reject) => {
41892
+ return new Promise((resolve12, reject) => {
41548
41893
  const timeout = setTimeout(() => {
41549
41894
  reject(new Error(`Call timeout after ${this.config.callTimeoutMs}ms`));
41550
41895
  }, this.config.callTimeoutMs);
41551
41896
  fn().then((result) => {
41552
41897
  clearTimeout(timeout);
41553
- resolve11(result);
41898
+ resolve12(result);
41554
41899
  }).catch((error93) => {
41555
41900
  clearTimeout(timeout);
41556
41901
  reject(error93);
@@ -41834,7 +42179,7 @@ class AutomationQueue {
41834
42179
 
41835
42180
  // src/background/worker.ts
41836
42181
  function sleep(ms) {
41837
- return new Promise((resolve11) => setTimeout(resolve11, ms));
42182
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
41838
42183
  }
41839
42184
 
41840
42185
  class WorkerManager {
@@ -42925,6 +43270,18 @@ var COMMAND_REGISTRY = {
42925
43270
  description: "Generate or import a feature specification [description]",
42926
43271
  args: "[description-text]"
42927
43272
  },
43273
+ brainstorm: {
43274
+ handler: (ctx) => handleBrainstormCommand(ctx.directory, ctx.args),
43275
+ description: "Enter architect MODE: BRAINSTORM \u2014 structured seven-phase planning workflow [topic]",
43276
+ args: "[topic-text]",
43277
+ details: "Triggers the architect to run the brainstorm workflow: CONTEXT SCAN, single-question DIALOGUE, APPROACHES, DESIGN SECTIONS, SPEC WRITE + SELF-REVIEW, QA GATE SELECTION, TRANSITION. Use for new plans where requirements need to be drawn out before writing spec.md / plan.md."
43278
+ },
43279
+ "qa-gates": {
43280
+ handler: (ctx) => handleQaGatesCommand(ctx.directory, ctx.args, ctx.sessionID),
43281
+ description: "View or modify QA gate profile for the current plan [enable|override <gate>...]",
43282
+ args: "[show|enable|override] <gate>...",
43283
+ details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled."
43284
+ },
42928
43285
  promote: {
42929
43286
  handler: (ctx) => handlePromoteCommand(ctx.directory, ctx.args),
42930
43287
  description: "Manually promote lesson to hive knowledge",
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Handle /swarm brainstorm command.
3
+ *
4
+ * Returns a trigger prompt instructing the architect to enter
5
+ * MODE: BRAINSTORM — the seven-phase planning workflow defined in the
6
+ * architect prompt: CONTEXT SCAN → DIALOGUE → APPROACHES → DESIGN SECTIONS
7
+ * → SPEC WRITE + SELF-REVIEW → QA GATE SELECTION → TRANSITION.
8
+ *
9
+ * Any arguments become the initial topic/problem statement for the
10
+ * architect to reason about. The topic is sanitized to prevent prompt
11
+ * injection of rival MODE: headers or newline-based control sequences.
12
+ */
13
+ export declare function handleBrainstormCommand(_directory: string, args: string[]): Promise<string>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,10 @@
1
+ import { type ConfigDoctorResult } from '../services/config-doctor';
2
+ /**
3
+ * Format tool doctor result as markdown for command output.
4
+ *
5
+ * Exported for unit testing of the BLOCKING footer enforcement path.
6
+ */
7
+ export declare function formatToolDoctorMarkdown(result: ConfigDoctorResult): string;
1
8
  /**
2
9
  * Handle /swarm config doctor command.
3
10
  * Maps to: config doctor service (runConfigDoctor)
@@ -4,6 +4,7 @@ export { handleAgentsCommand } from './agents';
4
4
  export { handleAnalyzeCommand } from './analyze';
5
5
  export { handleArchiveCommand } from './archive';
6
6
  export { handleBenchmarkCommand } from './benchmark';
7
+ export { handleBrainstormCommand } from './brainstorm';
7
8
  export { handleCheckpointCommand } from './checkpoint';
8
9
  export { handleClarifyCommand } from './clarify';
9
10
  export { handleCloseCommand } from './close';
@@ -21,6 +22,7 @@ export { handleKnowledgeListCommand, handleKnowledgeMigrateCommand, handleKnowle
21
22
  export { handlePlanCommand } from './plan';
22
23
  export { handlePreflightCommand } from './preflight';
23
24
  export { handlePromoteCommand } from './promote';
25
+ export { handleQaGatesCommand } from './qa-gates';
24
26
  export type { CommandContext, CommandEntry, RegisteredCommand, } from './registry.js';
25
27
  export { COMMAND_REGISTRY, resolveCommand, VALID_COMMANDS, } from './registry.js';
26
28
  export { handleResetCommand } from './reset';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * /swarm qa-gates command.
3
+ *
4
+ * View, enable, or add session overrides for QA gates tied to the current
5
+ * plan's QA gate profile. Read-only display when called without arguments;
6
+ * ratchet-tighter enable/override when called with `enable <gate>...` or
7
+ * `override <gate>...`.
8
+ *
9
+ * /swarm qa-gates -> show profile + effective gates
10
+ * /swarm qa-gates enable <gate>... -> persist into profile (architect)
11
+ * /swarm qa-gates override <gate>... -> session-only override
12
+ *
13
+ * Refuses to persist into a locked profile.
14
+ */
15
+ export declare function handleQaGatesCommand(directory: string, args: string[], sessionID: string): Promise<string>;
@@ -0,0 +1 @@
1
+ export {};
@@ -150,6 +150,18 @@ export declare const COMMAND_REGISTRY: {
150
150
  readonly description: "Generate or import a feature specification [description]";
151
151
  readonly args: "[description-text]";
152
152
  };
153
+ readonly brainstorm: {
154
+ readonly handler: (ctx: CommandContext) => Promise<string>;
155
+ readonly description: "Enter architect MODE: BRAINSTORM — structured seven-phase planning workflow [topic]";
156
+ readonly args: "[topic-text]";
157
+ readonly details: "Triggers the architect to run the brainstorm workflow: CONTEXT SCAN, single-question DIALOGUE, APPROACHES, DESIGN SECTIONS, SPEC WRITE + SELF-REVIEW, QA GATE SELECTION, TRANSITION. Use for new plans where requirements need to be drawn out before writing spec.md / plan.md.";
158
+ };
159
+ readonly 'qa-gates': {
160
+ readonly handler: (ctx: CommandContext) => Promise<string>;
161
+ readonly description: "View or modify QA gate profile for the current plan [enable|override <gate>...]";
162
+ readonly args: "[show|enable|override] <gate>...";
163
+ readonly details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled.";
164
+ };
153
165
  readonly promote: {
154
166
  readonly handler: (ctx: CommandContext) => Promise<string>;
155
167
  readonly description: "Manually promote lesson to hive knowledge";
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Global SQLite database singleton for opencode-swarm.
3
+ *
4
+ * Owns `global-rules.db` in the platform config directory. Stores cross-project
5
+ * rules and agent prompt sections. Per-project QA gate profiles live in the
6
+ * project DB (see `./project-db.ts`), not here.
7
+ */
8
+ import { Database } from 'bun:sqlite';
9
+ /**
10
+ * Run all pending migrations on the provided database.
11
+ * Idempotent: existing migrations are not re-applied.
12
+ */
13
+ export declare function runGlobalMigrations(db: Database): void;
14
+ /**
15
+ * Return the process-wide singleton global database, creating it on first call.
16
+ * Directory is created if it does not exist. WAL mode is enabled immediately.
17
+ */
18
+ export declare function getGlobalDb(): Database;
19
+ /**
20
+ * Close and clear the global database singleton. Test-only.
21
+ */
22
+ export declare function closeGlobalDb(): void;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tests for src/db/global-db.ts.
3
+ *
4
+ * Uses XDG_CONFIG_HOME override so getPlatformConfigDir() resolves into
5
+ * a temp directory (Linux only — this test suite is gated accordingly).
6
+ */
7
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Barrel re-exports for the opencode-swarm SQLite database layer.
3
+ *
4
+ * - `global-db`: process-wide singleton for cross-project rules and
5
+ * agent prompt sections (`global-rules.db` in the platform config dir).
6
+ * - `project-db`: per-project database cache (`.swarm/swarm.db`), keyed by
7
+ * normalized directory path.
8
+ * - `qa-gate-profile`: service layer for per-plan QA gate profiles stored
9
+ * in the project DB.
10
+ */
11
+ export { closeGlobalDb, getGlobalDb, runGlobalMigrations, } from './global-db.js';
12
+ export { closeAllProjectDbs, closeProjectDb, getProjectDb, projectDbExists, projectDbPath, runProjectMigrations, } from './project-db.js';
13
+ export { computeProfileHash, DEFAULT_QA_GATES, getEffectiveGates, getOrCreateProfile, getProfile, lockProfile, type QaGateProfile, type QaGates, setGates, } from './qa-gate-profile.js';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Per-project SQLite database for opencode-swarm.
3
+ *
4
+ * Owns `.swarm/swarm.db` in each project directory. Stores per-project
5
+ * constraints and QA gate profiles. One cached instance per normalized
6
+ * directory path.
7
+ */
8
+ import { Database } from 'bun:sqlite';
9
+ /**
10
+ * Run all pending migrations on the provided database.
11
+ * Idempotent: existing migrations are not re-applied.
12
+ */
13
+ export declare function runProjectMigrations(db: Database): void;
14
+ /**
15
+ * Return the absolute path to `.swarm/swarm.db` for the given directory.
16
+ * Does not create the file or any parent directory.
17
+ */
18
+ export declare function projectDbPath(directory: string): string;
19
+ /**
20
+ * Return true iff the project DB file already exists on disk. Does not
21
+ * open the DB, create `.swarm/`, or run migrations. Intended for
22
+ * read-only callers (e.g. `getProfile`) that must avoid mutating the
23
+ * workspace just to check for a missing record.
24
+ */
25
+ export declare function projectDbExists(directory: string): boolean;
26
+ /**
27
+ * Return the cached project database for the given directory, opening it
28
+ * if needed. Creates `.swarm/` if absent and enables WAL + foreign keys.
29
+ */
30
+ export declare function getProjectDb(directory: string): Database;
31
+ /**
32
+ * Close and remove the cached project database for the given directory.
33
+ * Test-only.
34
+ */
35
+ export declare function closeProjectDb(directory: string): void;
36
+ /**
37
+ * Close and remove all cached project databases.
38
+ * Test-only.
39
+ */
40
+ export declare function closeAllProjectDbs(): void;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for src/db/project-db.ts.
3
+ */
4
+ export {};