opencode-swarm 6.67.1 → 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 +357 -5
- package/dist/commands/brainstorm.d.ts +13 -0
- package/dist/commands/brainstorm.test.d.ts +1 -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 +685 -90
- 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
|
|
|
@@ -41493,6 +41515,324 @@ async function handlePromoteCommand(directory, args) {
|
|
|
41493
41515
|
}
|
|
41494
41516
|
}
|
|
41495
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
|
+
|
|
41496
41836
|
// src/commands/reset.ts
|
|
41497
41837
|
import * as fs16 from "fs";
|
|
41498
41838
|
|
|
@@ -41549,13 +41889,13 @@ class CircuitBreaker {
|
|
|
41549
41889
|
if (this.config.callTimeoutMs <= 0) {
|
|
41550
41890
|
return fn();
|
|
41551
41891
|
}
|
|
41552
|
-
return new Promise((
|
|
41892
|
+
return new Promise((resolve12, reject) => {
|
|
41553
41893
|
const timeout = setTimeout(() => {
|
|
41554
41894
|
reject(new Error(`Call timeout after ${this.config.callTimeoutMs}ms`));
|
|
41555
41895
|
}, this.config.callTimeoutMs);
|
|
41556
41896
|
fn().then((result) => {
|
|
41557
41897
|
clearTimeout(timeout);
|
|
41558
|
-
|
|
41898
|
+
resolve12(result);
|
|
41559
41899
|
}).catch((error93) => {
|
|
41560
41900
|
clearTimeout(timeout);
|
|
41561
41901
|
reject(error93);
|
|
@@ -41839,7 +42179,7 @@ class AutomationQueue {
|
|
|
41839
42179
|
|
|
41840
42180
|
// src/background/worker.ts
|
|
41841
42181
|
function sleep(ms) {
|
|
41842
|
-
return new Promise((
|
|
42182
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
41843
42183
|
}
|
|
41844
42184
|
|
|
41845
42185
|
class WorkerManager {
|
|
@@ -42930,6 +43270,18 @@ var COMMAND_REGISTRY = {
|
|
|
42930
43270
|
description: "Generate or import a feature specification [description]",
|
|
42931
43271
|
args: "[description-text]"
|
|
42932
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
|
+
},
|
|
42933
43285
|
promote: {
|
|
42934
43286
|
handler: (ctx) => handlePromoteCommand(ctx.directory, ctx.args),
|
|
42935
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 {};
|
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;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service layer for the `qa_gate_profile` table in the per-project database.
|
|
3
|
+
*
|
|
4
|
+
* A QA gate profile is keyed by plan_id and captures which QA gates are
|
|
5
|
+
* enabled for that plan. Profiles are locked after critic approval; once
|
|
6
|
+
* locked, row updates are rejected by a SQLite trigger and by this service.
|
|
7
|
+
* Sessions can only ratchet gates tighter (enable more), never disable them.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* QA gate flags. All seven gates are tracked explicitly.
|
|
11
|
+
*/
|
|
12
|
+
export interface QaGates {
|
|
13
|
+
reviewer: boolean;
|
|
14
|
+
test_engineer: boolean;
|
|
15
|
+
council_mode: boolean;
|
|
16
|
+
sme_enabled: boolean;
|
|
17
|
+
critic_pre_plan: boolean;
|
|
18
|
+
hallucination_guard: boolean;
|
|
19
|
+
sast_enabled: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Default QA gate configuration for newly-created profiles.
|
|
23
|
+
*/
|
|
24
|
+
export declare const DEFAULT_QA_GATES: QaGates;
|
|
25
|
+
/**
|
|
26
|
+
* Row-level representation of a persisted QA gate profile.
|
|
27
|
+
*/
|
|
28
|
+
export interface QaGateProfile {
|
|
29
|
+
id: number;
|
|
30
|
+
plan_id: string;
|
|
31
|
+
created_at: string;
|
|
32
|
+
project_type: string | null;
|
|
33
|
+
gates: QaGates;
|
|
34
|
+
locked_at: string | null;
|
|
35
|
+
locked_by_snapshot_seq: number | null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fetch the profile for `planId` or return null if none exists.
|
|
39
|
+
*
|
|
40
|
+
* Read-only: if `.swarm/swarm.db` does not exist yet, returns null
|
|
41
|
+
* without creating the DB file or running migrations. This keeps callers
|
|
42
|
+
* on read-only paths (`get_approved_plan`, `get_qa_gate_profile`, the
|
|
43
|
+
* `qa-gates show` command) from silently mutating the workspace just by
|
|
44
|
+
* looking for a profile. Write paths (`getOrCreateProfile`, `setGates`,
|
|
45
|
+
* `lockProfile`) continue to initialize the DB on demand.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getProfile(directory: string, planId: string): QaGateProfile | null;
|
|
48
|
+
/**
|
|
49
|
+
* Return the existing profile for `planId`, or create a new one seeded with
|
|
50
|
+
* `DEFAULT_QA_GATES` if none exists. Tolerates races on the UNIQUE index.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getOrCreateProfile(directory: string, planId: string, projectType?: string): QaGateProfile;
|
|
53
|
+
/**
|
|
54
|
+
* Update gates for `planId`. Gates can only be ratcheted tighter —
|
|
55
|
+
* attempting to disable a currently-enabled gate throws. Throws if the
|
|
56
|
+
* profile is locked.
|
|
57
|
+
*/
|
|
58
|
+
export declare function setGates(directory: string, planId: string, gates: Partial<QaGates>): QaGateProfile;
|
|
59
|
+
/**
|
|
60
|
+
* Lock the profile for `planId`, recording the snapshot seq that anchors it.
|
|
61
|
+
* Idempotent: locking an already-locked profile returns it unchanged.
|
|
62
|
+
*/
|
|
63
|
+
export declare function lockProfile(directory: string, planId: string, snapshotSeq: number): QaGateProfile;
|
|
64
|
+
/**
|
|
65
|
+
* Compute a SHA-256 hex digest over the stable identity of a profile.
|
|
66
|
+
* Used by `get_approved_plan` for drift detection.
|
|
67
|
+
*/
|
|
68
|
+
export declare function computeProfileHash(profile: QaGateProfile): string;
|
|
69
|
+
/**
|
|
70
|
+
* Merge session-level gate overrides on top of the spec-level profile.
|
|
71
|
+
* Session overrides can only ratchet gates tighter (set to true); false
|
|
72
|
+
* values in overrides are ignored.
|
|
73
|
+
*
|
|
74
|
+
* IMPORTANT — caller responsibility: this function is the *computation*
|
|
75
|
+
* of effective gates, not an enforcement point. Enforcement consumers
|
|
76
|
+
* (reviewer dispatch, SAST runner, council convene paths, etc.) must
|
|
77
|
+
* call this at their own check sites, passing the current profile from
|
|
78
|
+
* `getProfile` and the agent session's `qaGateSessionOverrides ?? {}`.
|
|
79
|
+
* Reading raw `profile.gates` directly from an enforcement site will
|
|
80
|
+
* silently ignore operator-applied session overrides. Session overrides
|
|
81
|
+
* are currently surfaced via `/swarm qa-gates show`; wiring additional
|
|
82
|
+
* enforcement consumers is tracked as follow-up work and does not affect
|
|
83
|
+
* spec-level gate correctness on the approved-plan path.
|
|
84
|
+
*
|
|
85
|
+
* Session overrides are intentionally ephemeral — they live only in
|
|
86
|
+
* in-memory `AgentSessionState.qaGateSessionOverrides` and are NOT
|
|
87
|
+
* persisted to the session snapshot. Process restart clears them.
|
|
88
|
+
*/
|
|
89
|
+
export declare function getEffectiveGates(profile: QaGateProfile, sessionOverrides: Partial<QaGates>): QaGates;
|