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 +363 -6
- package/dist/commands/brainstorm.d.ts +13 -0
- package/dist/commands/brainstorm.test.d.ts +1 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/qa-gates.d.ts +15 -0
- package/dist/commands/qa-gates.test.d.ts +1 -0
- package/dist/commands/registry.d.ts +12 -0
- package/dist/db/global-db.d.ts +22 -0
- package/dist/db/global-db.test.d.ts +7 -0
- package/dist/db/index.d.ts +13 -0
- package/dist/db/project-db.d.ts +40 -0
- package/dist/db/project-db.test.d.ts +4 -0
- package/dist/db/qa-gate-profile.d.ts +89 -0
- package/dist/db/qa-gate-profile.test.d.ts +4 -0
- package/dist/index.js +709 -97
- package/dist/state.d.ts +7 -0
- package/dist/tools/get-approved-plan.d.ts +4 -0
- package/dist/tools/get-qa-gate-profile.d.ts +27 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/set-qa-gates.d.ts +37 -0
- package/dist/tools/tool-names.d.ts +1 -1
- package/package.json +1 -1
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: "
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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)
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -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,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;
|