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/index.js
CHANGED
|
@@ -79,7 +79,9 @@ var init_tool_names = __esm(() => {
|
|
|
79
79
|
"suggest_patch",
|
|
80
80
|
"req_coverage",
|
|
81
81
|
"get_approved_plan",
|
|
82
|
-
"repo_map"
|
|
82
|
+
"repo_map",
|
|
83
|
+
"get_qa_gate_profile",
|
|
84
|
+
"set_qa_gates"
|
|
83
85
|
];
|
|
84
86
|
TOOL_NAME_SET = new Set(TOOL_NAMES);
|
|
85
87
|
});
|
|
@@ -217,7 +219,9 @@ var init_constants = __esm(() => {
|
|
|
217
219
|
"knowledge_remove",
|
|
218
220
|
"co_change_analyzer",
|
|
219
221
|
"suggest_patch",
|
|
220
|
-
"repo_map"
|
|
222
|
+
"repo_map",
|
|
223
|
+
"get_qa_gate_profile",
|
|
224
|
+
"set_qa_gates"
|
|
221
225
|
],
|
|
222
226
|
explorer: [
|
|
223
227
|
"complexity_hotspots",
|
|
@@ -409,7 +413,9 @@ var init_constants = __esm(() => {
|
|
|
409
413
|
suggest_patch: "Reviewer-safe structured patch suggestion tool. Produces context-anchored patch artifacts without file modification. Returns structured diagnostics on context mismatch.",
|
|
410
414
|
lint_spec: "validate .swarm/spec.md format and required fields",
|
|
411
415
|
get_approved_plan: "retrieve the last critic-approved immutable plan snapshot for baseline drift comparison",
|
|
412
|
-
repo_map: "query the repo code graph: importers, dependencies, blast radius, and localization context for structural awareness before refactoring"
|
|
416
|
+
repo_map: "query the repo code graph: importers, dependencies, blast radius, and localization context for structural awareness before refactoring",
|
|
417
|
+
get_qa_gate_profile: "retrieve the QA gate profile for the current plan: gates, lock state, and profile hash. Read-only.",
|
|
418
|
+
set_qa_gates: "configure the QA gate profile for the current plan. Architect-only. Ratchet-tighter only \u2014 rejected once the profile is locked after critic approval."
|
|
413
419
|
};
|
|
414
420
|
for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
|
|
415
421
|
const invalidTools = tools.filter((tool) => !TOOL_NAME_SET.has(tool));
|
|
@@ -17692,6 +17698,7 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
|
|
|
17692
17698
|
scopeViolationDetected: false,
|
|
17693
17699
|
modifiedFilesThisCoderTask: [],
|
|
17694
17700
|
turboMode: false,
|
|
17701
|
+
qaGateSessionOverrides: {},
|
|
17695
17702
|
fullAutoMode: false,
|
|
17696
17703
|
fullAutoInteractionCount: 0,
|
|
17697
17704
|
fullAutoDeadlockCount: 0,
|
|
@@ -17817,6 +17824,9 @@ function ensureAgentSession(sessionId, agentName, directory) {
|
|
|
17817
17824
|
if (session.turboMode === undefined) {
|
|
17818
17825
|
session.turboMode = false;
|
|
17819
17826
|
}
|
|
17827
|
+
if (session.qaGateSessionOverrides === undefined) {
|
|
17828
|
+
session.qaGateSessionOverrides = {};
|
|
17829
|
+
}
|
|
17820
17830
|
if (session.model_fallback_index === undefined) {
|
|
17821
17831
|
session.model_fallback_index = 0;
|
|
17822
17832
|
}
|
|
@@ -38750,12 +38760,12 @@ __export(exports_evidence_summary_integration, {
|
|
|
38750
38760
|
createEvidenceSummaryIntegration: () => createEvidenceSummaryIntegration,
|
|
38751
38761
|
EvidenceSummaryIntegration: () => EvidenceSummaryIntegration
|
|
38752
38762
|
});
|
|
38753
|
-
import { existsSync as
|
|
38763
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
38754
38764
|
import * as path36 from "path";
|
|
38755
38765
|
function persistSummary(swarmDir, artifact, filename) {
|
|
38756
38766
|
const swarmPath = path36.join(swarmDir, ".swarm");
|
|
38757
|
-
if (!
|
|
38758
|
-
|
|
38767
|
+
if (!existsSync22(swarmPath)) {
|
|
38768
|
+
mkdirSync11(swarmPath, { recursive: true });
|
|
38759
38769
|
}
|
|
38760
38770
|
const artifactPath = path36.join(swarmPath, filename);
|
|
38761
38771
|
const content = JSON.stringify(artifact, null, 2);
|
|
@@ -41023,7 +41033,7 @@ __export(exports_gate_evidence, {
|
|
|
41023
41033
|
deriveRequiredGates: () => deriveRequiredGates,
|
|
41024
41034
|
DEFAULT_REQUIRED_GATES: () => DEFAULT_REQUIRED_GATES
|
|
41025
41035
|
});
|
|
41026
|
-
import { mkdirSync as
|
|
41036
|
+
import { mkdirSync as mkdirSync13, readFileSync as readFileSync18, renameSync as renameSync10, unlinkSync as unlinkSync5 } from "fs";
|
|
41027
41037
|
import * as path40 from "path";
|
|
41028
41038
|
function isValidTaskId2(taskId) {
|
|
41029
41039
|
return isStrictTaskId(taskId);
|
|
@@ -41088,7 +41098,7 @@ async function recordGateEvidence(directory, taskId, gate, sessionId, turbo) {
|
|
|
41088
41098
|
assertValidTaskId(taskId);
|
|
41089
41099
|
const evidenceDir = getEvidenceDir(directory);
|
|
41090
41100
|
const evidencePath = getEvidencePath(directory, taskId);
|
|
41091
|
-
|
|
41101
|
+
mkdirSync13(evidenceDir, { recursive: true });
|
|
41092
41102
|
const existing = readExisting(evidencePath);
|
|
41093
41103
|
const requiredGates = existing ? expandRequiredGates(existing.required_gates, gate) : deriveRequiredGates(gate);
|
|
41094
41104
|
const updated = {
|
|
@@ -41111,7 +41121,7 @@ async function recordAgentDispatch(directory, taskId, agentType, turbo) {
|
|
|
41111
41121
|
assertValidTaskId(taskId);
|
|
41112
41122
|
const evidenceDir = getEvidenceDir(directory);
|
|
41113
41123
|
const evidencePath = getEvidencePath(directory, taskId);
|
|
41114
|
-
|
|
41124
|
+
mkdirSync13(evidenceDir, { recursive: true });
|
|
41115
41125
|
const existing = readExisting(evidencePath);
|
|
41116
41126
|
const requiredGates = existing ? expandRequiredGates(existing.required_gates, agentType) : deriveRequiredGates(agentType);
|
|
41117
41127
|
const updated = {
|
|
@@ -43534,8 +43544,8 @@ ${JSON.stringify(symbolNames, null, 2)}`);
|
|
|
43534
43544
|
var moduleRtn;
|
|
43535
43545
|
var Module = moduleArg;
|
|
43536
43546
|
var readyPromiseResolve, readyPromiseReject;
|
|
43537
|
-
var readyPromise = new Promise((
|
|
43538
|
-
readyPromiseResolve =
|
|
43547
|
+
var readyPromise = new Promise((resolve27, reject) => {
|
|
43548
|
+
readyPromiseResolve = resolve27;
|
|
43539
43549
|
readyPromiseReject = reject;
|
|
43540
43550
|
});
|
|
43541
43551
|
var ENVIRONMENT_IS_WEB = typeof window == "object";
|
|
@@ -43615,13 +43625,13 @@ ${JSON.stringify(symbolNames, null, 2)}`);
|
|
|
43615
43625
|
}
|
|
43616
43626
|
readAsync = /* @__PURE__ */ __name(async (url3) => {
|
|
43617
43627
|
if (isFileURI(url3)) {
|
|
43618
|
-
return new Promise((
|
|
43628
|
+
return new Promise((resolve27, reject) => {
|
|
43619
43629
|
var xhr = new XMLHttpRequest;
|
|
43620
43630
|
xhr.open("GET", url3, true);
|
|
43621
43631
|
xhr.responseType = "arraybuffer";
|
|
43622
43632
|
xhr.onload = () => {
|
|
43623
43633
|
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
|
|
43624
|
-
|
|
43634
|
+
resolve27(xhr.response);
|
|
43625
43635
|
return;
|
|
43626
43636
|
}
|
|
43627
43637
|
reject(xhr.status);
|
|
@@ -43841,10 +43851,10 @@ ${JSON.stringify(symbolNames, null, 2)}`);
|
|
|
43841
43851
|
__name(receiveInstantiationResult, "receiveInstantiationResult");
|
|
43842
43852
|
var info2 = getWasmImports();
|
|
43843
43853
|
if (Module["instantiateWasm"]) {
|
|
43844
|
-
return new Promise((
|
|
43854
|
+
return new Promise((resolve27, reject) => {
|
|
43845
43855
|
Module["instantiateWasm"](info2, (mod, inst) => {
|
|
43846
43856
|
receiveInstance(mod, inst);
|
|
43847
|
-
|
|
43857
|
+
resolve27(mod.exports);
|
|
43848
43858
|
});
|
|
43849
43859
|
});
|
|
43850
43860
|
}
|
|
@@ -45364,8 +45374,8 @@ async function loadGrammar(languageId) {
|
|
|
45364
45374
|
const parser = new Parser;
|
|
45365
45375
|
const wasmFileName = getWasmFileName(normalizedId);
|
|
45366
45376
|
const wasmPath = path66.join(getGrammarsDirAbsolute(), wasmFileName);
|
|
45367
|
-
const { existsSync:
|
|
45368
|
-
if (!
|
|
45377
|
+
const { existsSync: existsSync40 } = await import("fs");
|
|
45378
|
+
if (!existsSync40(wasmPath)) {
|
|
45369
45379
|
throw new Error(`Grammar file not found for ${languageId}: ${wasmPath}
|
|
45370
45380
|
Make sure to run 'bun run build' to copy grammar files to dist/lang/grammars/`);
|
|
45371
45381
|
}
|
|
@@ -45981,6 +45991,24 @@ async function handleBenchmarkCommand(directory, args2) {
|
|
|
45981
45991
|
`);
|
|
45982
45992
|
}
|
|
45983
45993
|
|
|
45994
|
+
// src/commands/brainstorm.ts
|
|
45995
|
+
function sanitizeTopic(raw) {
|
|
45996
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
45997
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
45998
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
45999
|
+
const MAX_TOPIC_LEN = 2000;
|
|
46000
|
+
if (normalized.length <= MAX_TOPIC_LEN)
|
|
46001
|
+
return normalized;
|
|
46002
|
+
return `${normalized.slice(0, MAX_TOPIC_LEN)}\u2026`;
|
|
46003
|
+
}
|
|
46004
|
+
async function handleBrainstormCommand(_directory, args2) {
|
|
46005
|
+
const description = sanitizeTopic(args2.join(" "));
|
|
46006
|
+
if (description) {
|
|
46007
|
+
return `[MODE: BRAINSTORM] ${description}`;
|
|
46008
|
+
}
|
|
46009
|
+
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).";
|
|
46010
|
+
}
|
|
46011
|
+
|
|
45984
46012
|
// src/commands/checkpoint.ts
|
|
45985
46013
|
init_zod();
|
|
45986
46014
|
init_checkpoint();
|
|
@@ -51497,6 +51525,341 @@ async function handlePromoteCommand(directory, args2) {
|
|
|
51497
51525
|
}
|
|
51498
51526
|
}
|
|
51499
51527
|
|
|
51528
|
+
// src/db/qa-gate-profile.ts
|
|
51529
|
+
import { createHash as createHash4 } from "crypto";
|
|
51530
|
+
|
|
51531
|
+
// src/db/project-db.ts
|
|
51532
|
+
import { Database } from "bun:sqlite";
|
|
51533
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9 } from "fs";
|
|
51534
|
+
import { join as join26, resolve as resolve11 } from "path";
|
|
51535
|
+
var MIGRATIONS = [
|
|
51536
|
+
{
|
|
51537
|
+
version: 1,
|
|
51538
|
+
name: "create_project_constraints",
|
|
51539
|
+
sql: `CREATE TABLE project_constraints (
|
|
51540
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
51541
|
+
constraint_type TEXT NOT NULL,
|
|
51542
|
+
content TEXT NOT NULL,
|
|
51543
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
51544
|
+
)`
|
|
51545
|
+
},
|
|
51546
|
+
{
|
|
51547
|
+
version: 2,
|
|
51548
|
+
name: "create_qa_gate_profile",
|
|
51549
|
+
sql: `CREATE TABLE qa_gate_profile (
|
|
51550
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
51551
|
+
plan_id TEXT NOT NULL UNIQUE,
|
|
51552
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
51553
|
+
project_type TEXT,
|
|
51554
|
+
gates TEXT NOT NULL DEFAULT '{}',
|
|
51555
|
+
locked_at TEXT,
|
|
51556
|
+
locked_by_snapshot_seq INTEGER
|
|
51557
|
+
)`
|
|
51558
|
+
},
|
|
51559
|
+
{
|
|
51560
|
+
version: 3,
|
|
51561
|
+
name: "create_qa_gate_profile_immutability_trigger",
|
|
51562
|
+
sql: `CREATE TRIGGER IF NOT EXISTS trg_qa_gate_profile_no_update_after_lock
|
|
51563
|
+
BEFORE UPDATE ON qa_gate_profile
|
|
51564
|
+
WHEN OLD.locked_at IS NOT NULL
|
|
51565
|
+
BEGIN
|
|
51566
|
+
SELECT RAISE(ABORT, 'qa_gate_profile row is locked and cannot be modified after critic approval');
|
|
51567
|
+
END`
|
|
51568
|
+
}
|
|
51569
|
+
];
|
|
51570
|
+
var _projectDbs = new Map;
|
|
51571
|
+
function runProjectMigrations(db) {
|
|
51572
|
+
db.run(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
51573
|
+
version INTEGER PRIMARY KEY,
|
|
51574
|
+
name TEXT NOT NULL,
|
|
51575
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
51576
|
+
)`);
|
|
51577
|
+
const row = db.query("SELECT MAX(version) as version FROM schema_migrations").get();
|
|
51578
|
+
const currentVersion = row?.version ?? 0;
|
|
51579
|
+
for (const migration of MIGRATIONS) {
|
|
51580
|
+
if (migration.version <= currentVersion)
|
|
51581
|
+
continue;
|
|
51582
|
+
const apply = db.transaction(() => {
|
|
51583
|
+
db.run(migration.sql);
|
|
51584
|
+
db.run("INSERT INTO schema_migrations (version, name) VALUES (?, ?)", [
|
|
51585
|
+
migration.version,
|
|
51586
|
+
migration.name
|
|
51587
|
+
]);
|
|
51588
|
+
});
|
|
51589
|
+
apply();
|
|
51590
|
+
}
|
|
51591
|
+
}
|
|
51592
|
+
function projectDbPath(directory) {
|
|
51593
|
+
return join26(resolve11(directory), ".swarm", "swarm.db");
|
|
51594
|
+
}
|
|
51595
|
+
function projectDbExists(directory) {
|
|
51596
|
+
return existsSync18(projectDbPath(directory));
|
|
51597
|
+
}
|
|
51598
|
+
function getProjectDb(directory) {
|
|
51599
|
+
const key = resolve11(directory);
|
|
51600
|
+
const existing = _projectDbs.get(key);
|
|
51601
|
+
if (existing)
|
|
51602
|
+
return existing;
|
|
51603
|
+
const swarmDir = join26(key, ".swarm");
|
|
51604
|
+
mkdirSync9(swarmDir, { recursive: true });
|
|
51605
|
+
const db = new Database(join26(swarmDir, "swarm.db"));
|
|
51606
|
+
db.run("PRAGMA journal_mode = WAL;");
|
|
51607
|
+
db.run("PRAGMA synchronous = NORMAL;");
|
|
51608
|
+
db.run("PRAGMA busy_timeout = 5000;");
|
|
51609
|
+
db.run("PRAGMA foreign_keys = ON;");
|
|
51610
|
+
runProjectMigrations(db);
|
|
51611
|
+
_projectDbs.set(key, db);
|
|
51612
|
+
return db;
|
|
51613
|
+
}
|
|
51614
|
+
|
|
51615
|
+
// src/db/qa-gate-profile.ts
|
|
51616
|
+
var DEFAULT_QA_GATES = {
|
|
51617
|
+
reviewer: true,
|
|
51618
|
+
test_engineer: true,
|
|
51619
|
+
council_mode: false,
|
|
51620
|
+
sme_enabled: true,
|
|
51621
|
+
critic_pre_plan: true,
|
|
51622
|
+
hallucination_guard: false,
|
|
51623
|
+
sast_enabled: true
|
|
51624
|
+
};
|
|
51625
|
+
function rowToProfile(row) {
|
|
51626
|
+
let parsed = {};
|
|
51627
|
+
try {
|
|
51628
|
+
parsed = JSON.parse(row.gates);
|
|
51629
|
+
} catch {
|
|
51630
|
+
parsed = {};
|
|
51631
|
+
}
|
|
51632
|
+
const gates = { ...DEFAULT_QA_GATES, ...parsed };
|
|
51633
|
+
return {
|
|
51634
|
+
id: row.id,
|
|
51635
|
+
plan_id: row.plan_id,
|
|
51636
|
+
created_at: row.created_at,
|
|
51637
|
+
project_type: row.project_type,
|
|
51638
|
+
gates,
|
|
51639
|
+
locked_at: row.locked_at,
|
|
51640
|
+
locked_by_snapshot_seq: row.locked_by_snapshot_seq
|
|
51641
|
+
};
|
|
51642
|
+
}
|
|
51643
|
+
function getProfile(directory, planId) {
|
|
51644
|
+
if (!projectDbExists(directory))
|
|
51645
|
+
return null;
|
|
51646
|
+
const db = getProjectDb(directory);
|
|
51647
|
+
const row = db.query("SELECT * FROM qa_gate_profile WHERE plan_id = ?").get(planId);
|
|
51648
|
+
return row ? rowToProfile(row) : null;
|
|
51649
|
+
}
|
|
51650
|
+
function getOrCreateProfile(directory, planId, projectType) {
|
|
51651
|
+
const existing = getProfile(directory, planId);
|
|
51652
|
+
if (existing)
|
|
51653
|
+
return existing;
|
|
51654
|
+
const db = getProjectDb(directory);
|
|
51655
|
+
const gatesJson = JSON.stringify(DEFAULT_QA_GATES);
|
|
51656
|
+
const insert = db.transaction(() => {
|
|
51657
|
+
db.run("INSERT INTO qa_gate_profile (plan_id, project_type, gates) VALUES (?, ?, ?)", [planId, projectType ?? null, gatesJson]);
|
|
51658
|
+
});
|
|
51659
|
+
try {
|
|
51660
|
+
insert();
|
|
51661
|
+
} catch (err2) {
|
|
51662
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
51663
|
+
if (!msg.toLowerCase().includes("unique")) {
|
|
51664
|
+
throw err2;
|
|
51665
|
+
}
|
|
51666
|
+
}
|
|
51667
|
+
const after = getProfile(directory, planId);
|
|
51668
|
+
if (!after) {
|
|
51669
|
+
throw new Error(`Failed to create or load QA gate profile for plan_id=${planId}`);
|
|
51670
|
+
}
|
|
51671
|
+
return after;
|
|
51672
|
+
}
|
|
51673
|
+
function setGates(directory, planId, gates) {
|
|
51674
|
+
const current = getProfile(directory, planId);
|
|
51675
|
+
if (!current) {
|
|
51676
|
+
throw new Error(`No QA gate profile found for plan_id=${planId} \u2014 call getOrCreateProfile first`);
|
|
51677
|
+
}
|
|
51678
|
+
if (current.locked_at !== null) {
|
|
51679
|
+
throw new Error("Cannot modify gates: QA gate profile is locked after critic approval");
|
|
51680
|
+
}
|
|
51681
|
+
const merged = { ...current.gates };
|
|
51682
|
+
for (const key of Object.keys(gates)) {
|
|
51683
|
+
const incoming = gates[key];
|
|
51684
|
+
if (incoming === undefined)
|
|
51685
|
+
continue;
|
|
51686
|
+
if (incoming === false && current.gates[key] === true) {
|
|
51687
|
+
throw new Error(`Cannot disable gate '${key}': sessions can only ratchet tighter`);
|
|
51688
|
+
}
|
|
51689
|
+
if (incoming === true) {
|
|
51690
|
+
merged[key] = true;
|
|
51691
|
+
}
|
|
51692
|
+
}
|
|
51693
|
+
const db = getProjectDb(directory);
|
|
51694
|
+
db.run("UPDATE qa_gate_profile SET gates = ? WHERE plan_id = ?", [
|
|
51695
|
+
JSON.stringify(merged),
|
|
51696
|
+
planId
|
|
51697
|
+
]);
|
|
51698
|
+
const updated = getProfile(directory, planId);
|
|
51699
|
+
if (!updated) {
|
|
51700
|
+
throw new Error(`Failed to re-read QA gate profile after update for plan_id=${planId}`);
|
|
51701
|
+
}
|
|
51702
|
+
return updated;
|
|
51703
|
+
}
|
|
51704
|
+
function lockProfile(directory, planId, snapshotSeq) {
|
|
51705
|
+
const current = getProfile(directory, planId);
|
|
51706
|
+
if (!current) {
|
|
51707
|
+
throw new Error(`No QA gate profile found for plan_id=${planId} \u2014 cannot lock`);
|
|
51708
|
+
}
|
|
51709
|
+
if (current.locked_at !== null) {
|
|
51710
|
+
return current;
|
|
51711
|
+
}
|
|
51712
|
+
const db = getProjectDb(directory);
|
|
51713
|
+
db.run("UPDATE qa_gate_profile SET locked_at = datetime('now'), locked_by_snapshot_seq = ? WHERE plan_id = ?", [snapshotSeq, planId]);
|
|
51714
|
+
const locked = getProfile(directory, planId);
|
|
51715
|
+
if (!locked) {
|
|
51716
|
+
throw new Error(`Failed to re-read locked QA gate profile for plan_id=${planId}`);
|
|
51717
|
+
}
|
|
51718
|
+
return locked;
|
|
51719
|
+
}
|
|
51720
|
+
function computeProfileHash(profile) {
|
|
51721
|
+
const payload = JSON.stringify({
|
|
51722
|
+
plan_id: profile.plan_id,
|
|
51723
|
+
gates: profile.gates
|
|
51724
|
+
});
|
|
51725
|
+
return createHash4("sha256").update(payload).digest("hex");
|
|
51726
|
+
}
|
|
51727
|
+
function getEffectiveGates(profile, sessionOverrides) {
|
|
51728
|
+
const merged = { ...profile.gates };
|
|
51729
|
+
for (const key of Object.keys(sessionOverrides)) {
|
|
51730
|
+
if (sessionOverrides[key] === true) {
|
|
51731
|
+
merged[key] = true;
|
|
51732
|
+
}
|
|
51733
|
+
}
|
|
51734
|
+
return merged;
|
|
51735
|
+
}
|
|
51736
|
+
|
|
51737
|
+
// src/commands/qa-gates.ts
|
|
51738
|
+
init_manager();
|
|
51739
|
+
init_state();
|
|
51740
|
+
var ALL_GATE_NAMES = [
|
|
51741
|
+
"reviewer",
|
|
51742
|
+
"test_engineer",
|
|
51743
|
+
"council_mode",
|
|
51744
|
+
"sme_enabled",
|
|
51745
|
+
"critic_pre_plan",
|
|
51746
|
+
"hallucination_guard",
|
|
51747
|
+
"sast_enabled"
|
|
51748
|
+
];
|
|
51749
|
+
function derivePlanId(plan) {
|
|
51750
|
+
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
51751
|
+
}
|
|
51752
|
+
function isGateName(name2) {
|
|
51753
|
+
return ALL_GATE_NAMES.includes(name2);
|
|
51754
|
+
}
|
|
51755
|
+
function formatGates(gates) {
|
|
51756
|
+
return ALL_GATE_NAMES.map((g) => ` - ${g}: ${gates[g] ? "on" : "off"}`).join(`
|
|
51757
|
+
`);
|
|
51758
|
+
}
|
|
51759
|
+
async function handleQaGatesCommand(directory, args2, sessionID) {
|
|
51760
|
+
const plan = await loadPlanJsonOnly(directory);
|
|
51761
|
+
if (!plan) {
|
|
51762
|
+
return "Error: plan.json not found or invalid. Create a plan first (e.g. /swarm specify or save_plan).";
|
|
51763
|
+
}
|
|
51764
|
+
const planId = derivePlanId(plan);
|
|
51765
|
+
const subcommand = args2[0]?.toLowerCase();
|
|
51766
|
+
const gateArgs = args2.slice(1);
|
|
51767
|
+
if (!subcommand || subcommand === "show" || subcommand === "status") {
|
|
51768
|
+
const profile = getProfile(directory, planId);
|
|
51769
|
+
const spec = profile ? profile.gates : DEFAULT_QA_GATES;
|
|
51770
|
+
const session = sessionID ? getAgentSession(sessionID) : null;
|
|
51771
|
+
const overrides = session?.qaGateSessionOverrides ?? {};
|
|
51772
|
+
const effective = profile ? getEffectiveGates(profile, overrides) : { ...DEFAULT_QA_GATES, ...overrides };
|
|
51773
|
+
const lines = [];
|
|
51774
|
+
lines.push(`QA Gate Profile for plan_id=${planId}`);
|
|
51775
|
+
if (!profile) {
|
|
51776
|
+
lines.push(" (no profile persisted yet \u2014 showing defaults)");
|
|
51777
|
+
} else {
|
|
51778
|
+
lines.push(` locked: ${profile.locked_at ? `yes @ ${profile.locked_at} (seq ${profile.locked_by_snapshot_seq ?? "?"})` : "no"}`);
|
|
51779
|
+
lines.push(` profile_hash: ${computeProfileHash(profile)}`);
|
|
51780
|
+
}
|
|
51781
|
+
lines.push("Spec-level gates:");
|
|
51782
|
+
lines.push(formatGates(spec));
|
|
51783
|
+
lines.push("Session overrides (ratchet-tighter only):");
|
|
51784
|
+
if (Object.keys(overrides).length === 0) {
|
|
51785
|
+
lines.push(" (none)");
|
|
51786
|
+
} else {
|
|
51787
|
+
for (const k of ALL_GATE_NAMES) {
|
|
51788
|
+
if (overrides[k] === true)
|
|
51789
|
+
lines.push(` - ${k}: on (override)`);
|
|
51790
|
+
}
|
|
51791
|
+
}
|
|
51792
|
+
lines.push("Effective gates:");
|
|
51793
|
+
lines.push(formatGates(effective));
|
|
51794
|
+
return lines.join(`
|
|
51795
|
+
`);
|
|
51796
|
+
}
|
|
51797
|
+
if (subcommand === "enable") {
|
|
51798
|
+
if (gateArgs.length === 0) {
|
|
51799
|
+
return "Usage: /swarm qa-gates enable <gate> [<gate> ...]";
|
|
51800
|
+
}
|
|
51801
|
+
const invalid = gateArgs.filter((g) => !isGateName(g));
|
|
51802
|
+
if (invalid.length > 0) {
|
|
51803
|
+
return `Error: unknown gate(s): ${invalid.join(", ")}. Valid gates: ${ALL_GATE_NAMES.join(", ")}`;
|
|
51804
|
+
}
|
|
51805
|
+
getOrCreateProfile(directory, planId);
|
|
51806
|
+
const patch = {};
|
|
51807
|
+
for (const g of gateArgs) {
|
|
51808
|
+
if (isGateName(g))
|
|
51809
|
+
patch[g] = true;
|
|
51810
|
+
}
|
|
51811
|
+
try {
|
|
51812
|
+
const updated = setGates(directory, planId, patch);
|
|
51813
|
+
return [
|
|
51814
|
+
`Enabled gates persisted for plan_id=${planId}:`,
|
|
51815
|
+
formatGates(updated.gates),
|
|
51816
|
+
`profile_hash: ${computeProfileHash(updated)}`
|
|
51817
|
+
].join(`
|
|
51818
|
+
`);
|
|
51819
|
+
} catch (err2) {
|
|
51820
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
51821
|
+
return `Error: ${msg}`;
|
|
51822
|
+
}
|
|
51823
|
+
}
|
|
51824
|
+
if (subcommand === "override") {
|
|
51825
|
+
if (!sessionID) {
|
|
51826
|
+
return "Error: session overrides require an active session context.";
|
|
51827
|
+
}
|
|
51828
|
+
if (gateArgs.length === 0) {
|
|
51829
|
+
return "Usage: /swarm qa-gates override <gate> [<gate> ...]";
|
|
51830
|
+
}
|
|
51831
|
+
const invalid = gateArgs.filter((g) => !isGateName(g));
|
|
51832
|
+
if (invalid.length > 0) {
|
|
51833
|
+
return `Error: unknown gate(s): ${invalid.join(", ")}. Valid gates: ${ALL_GATE_NAMES.join(", ")}`;
|
|
51834
|
+
}
|
|
51835
|
+
const session = getAgentSession(sessionID);
|
|
51836
|
+
if (!session) {
|
|
51837
|
+
return "Error: no active session found for override.";
|
|
51838
|
+
}
|
|
51839
|
+
const current = session.qaGateSessionOverrides ?? {};
|
|
51840
|
+
const next = { ...current };
|
|
51841
|
+
for (const g of gateArgs) {
|
|
51842
|
+
if (isGateName(g))
|
|
51843
|
+
next[g] = true;
|
|
51844
|
+
}
|
|
51845
|
+
session.qaGateSessionOverrides = next;
|
|
51846
|
+
return [
|
|
51847
|
+
`Session overrides updated for plan_id=${planId}:`,
|
|
51848
|
+
Object.keys(next).filter((k) => next[k] === true).map((k) => ` - ${k}: on`).join(`
|
|
51849
|
+
`) || " (none)"
|
|
51850
|
+
].join(`
|
|
51851
|
+
`);
|
|
51852
|
+
}
|
|
51853
|
+
return [
|
|
51854
|
+
"Usage:",
|
|
51855
|
+
" /swarm qa-gates show current profile + effective gates",
|
|
51856
|
+
" /swarm qa-gates enable <gate>... persist-enable gate(s) (rejected if locked)",
|
|
51857
|
+
" /swarm qa-gates override <gate>... session-only enable (ratchet-tighter)",
|
|
51858
|
+
`Valid gates: ${ALL_GATE_NAMES.join(", ")}`
|
|
51859
|
+
].join(`
|
|
51860
|
+
`);
|
|
51861
|
+
}
|
|
51862
|
+
|
|
51500
51863
|
// src/commands/reset.ts
|
|
51501
51864
|
import * as fs21 from "fs";
|
|
51502
51865
|
|
|
@@ -51553,13 +51916,13 @@ class CircuitBreaker {
|
|
|
51553
51916
|
if (this.config.callTimeoutMs <= 0) {
|
|
51554
51917
|
return fn();
|
|
51555
51918
|
}
|
|
51556
|
-
return new Promise((
|
|
51919
|
+
return new Promise((resolve12, reject) => {
|
|
51557
51920
|
const timeout = setTimeout(() => {
|
|
51558
51921
|
reject(new Error(`Call timeout after ${this.config.callTimeoutMs}ms`));
|
|
51559
51922
|
}, this.config.callTimeoutMs);
|
|
51560
51923
|
fn().then((result) => {
|
|
51561
51924
|
clearTimeout(timeout);
|
|
51562
|
-
|
|
51925
|
+
resolve12(result);
|
|
51563
51926
|
}).catch((error93) => {
|
|
51564
51927
|
clearTimeout(timeout);
|
|
51565
51928
|
reject(error93);
|
|
@@ -51705,7 +52068,7 @@ init_queue();
|
|
|
51705
52068
|
// src/background/worker.ts
|
|
51706
52069
|
init_event_bus();
|
|
51707
52070
|
function sleep(ms) {
|
|
51708
|
-
return new Promise((
|
|
52071
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
51709
52072
|
}
|
|
51710
52073
|
|
|
51711
52074
|
class WorkerManager {
|
|
@@ -52174,7 +52537,7 @@ async function handleResetSessionCommand(directory, _args) {
|
|
|
52174
52537
|
// src/summaries/manager.ts
|
|
52175
52538
|
init_utils2();
|
|
52176
52539
|
init_utils();
|
|
52177
|
-
import { mkdirSync as
|
|
52540
|
+
import { mkdirSync as mkdirSync10, readdirSync as readdirSync9, renameSync as renameSync8, rmSync as rmSync3, statSync as statSync7 } from "fs";
|
|
52178
52541
|
import * as path32 from "path";
|
|
52179
52542
|
var SUMMARY_ID_REGEX = /^S\d+$/;
|
|
52180
52543
|
function sanitizeSummaryId(id) {
|
|
@@ -52221,7 +52584,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
52221
52584
|
originalBytes: Buffer.byteLength(fullOutput, "utf8")
|
|
52222
52585
|
};
|
|
52223
52586
|
const entryJson = JSON.stringify(entry);
|
|
52224
|
-
|
|
52587
|
+
mkdirSync10(summaryDir, { recursive: true });
|
|
52225
52588
|
const tempPath = path32.join(summaryDir, `${sanitizedId}.json.tmp.${Date.now()}.${process.pid}`);
|
|
52226
52589
|
try {
|
|
52227
52590
|
await Bun.write(tempPath, entryJson);
|
|
@@ -53375,6 +53738,18 @@ var COMMAND_REGISTRY = {
|
|
|
53375
53738
|
description: "Generate or import a feature specification [description]",
|
|
53376
53739
|
args: "[description-text]"
|
|
53377
53740
|
},
|
|
53741
|
+
brainstorm: {
|
|
53742
|
+
handler: (ctx) => handleBrainstormCommand(ctx.directory, ctx.args),
|
|
53743
|
+
description: "Enter architect MODE: BRAINSTORM \u2014 structured seven-phase planning workflow [topic]",
|
|
53744
|
+
args: "[topic-text]",
|
|
53745
|
+
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."
|
|
53746
|
+
},
|
|
53747
|
+
"qa-gates": {
|
|
53748
|
+
handler: (ctx) => handleQaGatesCommand(ctx.directory, ctx.args, ctx.sessionID),
|
|
53749
|
+
description: "View or modify QA gate profile for the current plan [enable|override <gate>...]",
|
|
53750
|
+
args: "[show|enable|override] <gate>...",
|
|
53751
|
+
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."
|
|
53752
|
+
},
|
|
53378
53753
|
promote: {
|
|
53379
53754
|
handler: (ctx) => handlePromoteCommand(ctx.directory, ctx.args),
|
|
53380
53755
|
description: "Manually promote lesson to hive knowledge",
|
|
@@ -53918,7 +54293,7 @@ OUTPUT: Code scaffold for src/pages/Settings.tsx with component tree, typed prop
|
|
|
53918
54293
|
### MODE DETECTION (Priority Order)
|
|
53919
54294
|
Evaluate the user's request and context in this exact order \u2014 the FIRST matching rule wins:
|
|
53920
54295
|
|
|
53921
|
-
0. **EXPLICIT COMMAND OVERRIDE** \u2014 User explicitly invokes \`/swarm specify\`, \`/swarm clarify\`, or uses the phrases "specify [something about spec/requirements]", "write a spec", "create a spec", "define requirements", "list requirements", "define a feature", "I have requirements" \u2192 Enter MODE: SPECIFY
|
|
54296
|
+
0. **EXPLICIT COMMAND OVERRIDE** \u2014 User explicitly invokes \`/swarm specify\`, \`/swarm clarify\`, \`/swarm brainstorm\`, or uses the phrases "specify [something about spec/requirements]", "write a spec", "create a spec", "define requirements", "list requirements", "define a feature", "I have requirements", "brainstorm", "let's think through", "think this through with me", "workshop this idea" \u2192 Enter MODE: SPECIFY, MODE: CLARIFY-SPEC, or MODE: BRAINSTORM as appropriate. This override fires BEFORE RESUME \u2014 an explicit spec command always wins, even if plan.md has incomplete tasks. \`/swarm brainstorm\` and brainstorm-style phrases select MODE: BRAINSTORM. Note: bare "specify" in an ambiguous context (e.g., "specify what this does") should resolve via CLARIFY (priority 4) rather than this override \u2014 use context to determine intent.
|
|
53922
54297
|
1. **RESUME** \u2014 \`.swarm/plan.md\` exists and contains incomplete (unchecked) tasks AND the user has NOT issued an explicit spec command (see priority 0) \u2192 Resume at current task.
|
|
53923
54298
|
2. **SPECIFY** \u2014 No \`.swarm/spec.md\` exists AND no \`.swarm/plan.md\` exists \u2192 Enter MODE: SPECIFY.
|
|
53924
54299
|
3. **CLARIFY-SPEC** \u2014 \`.swarm/spec.md\` exists AND contains \`[NEEDS CLARIFICATION]\` markers; OR user explicitly asks to clarify or refine the spec; OR \`/swarm clarify\` is invoked \u2192 Enter MODE: CLARIFY-SPEC.
|
|
@@ -53927,12 +54302,72 @@ Evaluate the user's request and context in this exact order \u2014 the FIRST mat
|
|
|
53927
54302
|
6. All other modes (CONSULT, PLAN, CRITIC-GATE, EXECUTE, PHASE-WRAP) \u2014 Follow their respective sections below.
|
|
53928
54303
|
|
|
53929
54304
|
PRIORITY RULES:
|
|
53930
|
-
- EXPLICIT COMMAND OVERRIDE (priority 0) wins over everything \u2014 an explicit \`/swarm specify
|
|
54305
|
+
- EXPLICIT COMMAND OVERRIDE (priority 0) wins over everything \u2014 an explicit \`/swarm specify\`, \`/swarm clarify\`, or \`/swarm brainstorm\` command, or explicit spec-creation / brainstorming language ("specify", "write a spec", "create a spec", "define requirements", "define a feature", "brainstorm", "think through with me") always overrides RESUME.
|
|
54306
|
+
- BRAINSTORM is selected via the EXPLICIT COMMAND OVERRIDE when \`/swarm brainstorm\` is invoked or the user asks to "brainstorm" / "think through" / "workshop" a problem before committing to a spec. Use BRAINSTORM when the problem is still fuzzy \u2014 it produces both spec.md and a QA gate profile. Use SPECIFY when requirements are clear enough to write directly.
|
|
53931
54307
|
- RESUME wins over SPECIFY (priority 2) and all other modes when no explicit spec command is present \u2014 a user continuing existing work is never accidentally routed to SPECIFY.
|
|
53932
54308
|
- SPECIFY (priority 2) fires only for new projects with no spec and no plan.
|
|
53933
54309
|
- CLARIFY-SPEC fires between SPECIFY and CLARIFY; it only activates when no explicit spec command is present and no incomplete (unchecked) tasks exist in plan.md \u2014 RESUME takes priority if they do.
|
|
53934
54310
|
- CLARIFY fires only when user input is genuinely needed (not as a substitute for informed defaults).
|
|
53935
54311
|
|
|
54312
|
+
### MODE: BRAINSTORM
|
|
54313
|
+
Activates when: user invokes \`/swarm brainstorm\`; OR uses phrases like "brainstorm", "let's think through", "think this through with me", "workshop this idea"; OR the problem is fuzzy/exploratory and the user has not yet written (or does not want to directly dictate) a spec.
|
|
54314
|
+
|
|
54315
|
+
Use BRAINSTORM when requirements need to be drawn out through structured dialogue before committing to a spec. Use SPECIFY when the user has already articulated clear requirements.
|
|
54316
|
+
|
|
54317
|
+
MODE: BRAINSTORM runs seven phases in strict order. Do not skip phases. Do not collapse phases. Each phase has a clear entry signal and a clear exit signal.
|
|
54318
|
+
|
|
54319
|
+
**Phase 1: CONTEXT SCAN (architect + explorer, parallel).**
|
|
54320
|
+
- Delegate to \`{{AGENT_PREFIX}}explorer\` to map the relevant portion of the codebase. Scope the explorer to the area most likely affected by the topic.
|
|
54321
|
+
- In parallel, read any existing \`.swarm/spec.md\`, \`.swarm/plan.md\`, and \`.swarm/knowledge.jsonl\` entries that are relevant.
|
|
54322
|
+
- Run CODEBASE REALITY CHECK on any claims the user made in their topic statement. Surface discrepancies before moving forward.
|
|
54323
|
+
- Exit when you have a confident map of: (a) existing code and patterns, (b) relevant prior decisions, (c) what is actually unknown.
|
|
54324
|
+
|
|
54325
|
+
**Phase 2: DIALOGUE (architect \u2194 user).**
|
|
54326
|
+
- Ask EXACTLY ONE focused question per message. Wait for the user's answer before asking the next.
|
|
54327
|
+
- Prioritize questions that materially change scope, risk, or architecture. Skip questions whose answers can be responsibly defaulted \u2014 use informed defaults and say so.
|
|
54328
|
+
- Hard cap: no more than SIX questions total in this phase. Stop sooner if uncertainty has collapsed.
|
|
54329
|
+
- Each question must include: (a) why it matters, (b) the default you will use if the user doesn't answer, (c) the concrete options you're weighing.
|
|
54330
|
+
- Exit when: remaining ambiguity can be defaulted safely, or the user explicitly says "good, move on" or equivalent.
|
|
54331
|
+
|
|
54332
|
+
**Phase 3: APPROACHES (architect, optionally with SME).**
|
|
54333
|
+
- Produce 2-4 distinct candidate approaches. Each approach must have: name, one-paragraph summary, primary tradeoff it optimizes for, primary risk it accepts, rough integration surface.
|
|
54334
|
+
- For high-risk domains (auth, payments, data mutation, public API, schema, concurrency, security-sensitive parsing), delegate to \`{{AGENT_PREFIX}}sme\` for domain research first.
|
|
54335
|
+
- Present the approaches to the user and recommend one with explicit reasoning. The user can pick, modify, or reject.
|
|
54336
|
+
- Exit when the user has chosen (or agreed to your recommended) approach.
|
|
54337
|
+
|
|
54338
|
+
**Phase 4: DESIGN SECTIONS (architect).**
|
|
54339
|
+
- Draft the structural design of the chosen approach. Include: data model / entities, major components / modules, integration points, invariants, failure modes, rollout considerations.
|
|
54340
|
+
- Keep design technology-aware (this is NOT the spec \u2014 BRAINSTORM design notes can reference frameworks and patterns).
|
|
54341
|
+
- Name the design sections explicitly so you can reference them in the spec without duplicating.
|
|
54342
|
+
- Exit with a design outline the user can skim in under two minutes.
|
|
54343
|
+
|
|
54344
|
+
**Phase 5: SPEC WRITE + SELF-REVIEW (architect + reviewer).**
|
|
54345
|
+
- Generate \`.swarm/spec.md\` following the same SPEC CONTENT RULES that MODE: SPECIFY uses: WHAT/WHY only, no tech stack, no implementation details, FR-### / SC-### numbering, Given/When/Then scenarios, \`[NEEDS CLARIFICATION]\` markers (max 3).
|
|
54346
|
+
- Cross-reference design sections by name where relevant context helps (but keep HOW out of the spec).
|
|
54347
|
+
- Delegate to \`{{AGENT_PREFIX}}reviewer\` for an independent review of the draft spec. Reviewer must flag: requirements that encode HOW, untestable requirements, missing edge cases, silent assumptions.
|
|
54348
|
+
- Apply reviewer feedback. If reviewer rejects, iterate once and re-review. After two rounds, surface remaining disagreements to the user.
|
|
54349
|
+
- Write the final spec to \`.swarm/spec.md\`.
|
|
54350
|
+
- Exit when reviewer signs off (or user explicitly accepts remaining disagreements).
|
|
54351
|
+
|
|
54352
|
+
**Phase 6: QA GATE SELECTION (architect).**
|
|
54353
|
+
- Read the current QA gate profile for this plan via \`get_qa_gate_profile\`. If none exists, the tool returns \`success: false, reason: 'no_profile'\` \u2014 this is expected for a new plan.
|
|
54354
|
+
- Based on risk tier of the work (see "High-risk work" list in the quality policy), choose which gates to enable. Default profile enables reviewer, test_engineer, sme_enabled, critic_pre_plan, and sast_enabled. Consider enabling council_mode for high-impact architecture and hallucination_guard for claim-heavy work.
|
|
54355
|
+
- Apply the chosen gates via \`set_qa_gates\`. The tool ratchets tighter only \u2014 it cannot disable gates that are already on. It rejects writes once the profile is locked by critic approval.
|
|
54356
|
+
- Briefly explain to the user which gates you selected and why.
|
|
54357
|
+
- Exit with a QA gate profile persisted for this plan.
|
|
54358
|
+
|
|
54359
|
+
**Phase 7: TRANSITION.**
|
|
54360
|
+
- Summarize: (a) chosen approach, (b) design sections produced, (c) spec written, (d) QA gates selected, (e) remaining \`[NEEDS CLARIFICATION]\` markers.
|
|
54361
|
+
- Offer the user two next steps: \`PLAN\` (go to MODE: PLAN and write plan.md) or \`CLARIFY-SPEC\` (resolve remaining markers first).
|
|
54362
|
+
- Do NOT proceed to PLAN or CLARIFY-SPEC automatically \u2014 wait for user direction.
|
|
54363
|
+
|
|
54364
|
+
BRAINSTORM RULES:
|
|
54365
|
+
- No skipping phases. Each phase's exit condition must be met before moving on.
|
|
54366
|
+
- One question per message in DIALOGUE \u2014 never batch.
|
|
54367
|
+
- Always offer an informed default for every question.
|
|
54368
|
+
- The spec produced in Phase 5 must still satisfy the SPEC CONTENT RULES (no tech stack, no implementation details).
|
|
54369
|
+
- QA gates set in Phase 6 are ratchet-tighter \u2014 you cannot undo them later in the session.
|
|
54370
|
+
|
|
53936
54371
|
### MODE: SPECIFY
|
|
53937
54372
|
Activates when: user asks to "specify", "define requirements", "write a spec", or "define a feature"; OR \`/swarm specify\` is invoked; OR no \`.swarm/spec.md\` exists and no \`.swarm/plan.md\` exists.
|
|
53938
54373
|
|
|
@@ -56814,13 +57249,13 @@ class PlanSyncWorker {
|
|
|
56814
57249
|
} catch {}
|
|
56815
57250
|
}
|
|
56816
57251
|
withTimeout(promise3, ms, timeoutMessage) {
|
|
56817
|
-
return new Promise((
|
|
57252
|
+
return new Promise((resolve13, reject) => {
|
|
56818
57253
|
const timer = setTimeout(() => {
|
|
56819
57254
|
reject(new Error(`${timeoutMessage} (${ms}ms)`));
|
|
56820
57255
|
}, ms);
|
|
56821
57256
|
promise3.then((result) => {
|
|
56822
57257
|
clearTimeout(timer);
|
|
56823
|
-
|
|
57258
|
+
resolve13(result);
|
|
56824
57259
|
}).catch((error93) => {
|
|
56825
57260
|
clearTimeout(timer);
|
|
56826
57261
|
reject(error93);
|
|
@@ -57047,7 +57482,7 @@ ${content.substring(endIndex + 1)}`;
|
|
|
57047
57482
|
// src/hooks/compaction-customizer.ts
|
|
57048
57483
|
init_manager();
|
|
57049
57484
|
import * as fs27 from "fs";
|
|
57050
|
-
import { join as
|
|
57485
|
+
import { join as join36 } from "path";
|
|
57051
57486
|
init_utils2();
|
|
57052
57487
|
function createCompactionCustomizerHook(config3, directory) {
|
|
57053
57488
|
const enabled = config3.hooks?.compaction !== false;
|
|
@@ -57093,7 +57528,7 @@ function createCompactionCustomizerHook(config3, directory) {
|
|
|
57093
57528
|
}
|
|
57094
57529
|
}
|
|
57095
57530
|
try {
|
|
57096
|
-
const summariesDir =
|
|
57531
|
+
const summariesDir = join36(directory, ".swarm", "summaries");
|
|
57097
57532
|
const files = await fs27.promises.readdir(summariesDir);
|
|
57098
57533
|
if (files.length > 0) {
|
|
57099
57534
|
const count = files.length;
|
|
@@ -61006,7 +61441,7 @@ import * as path47 from "path";
|
|
|
61006
61441
|
init_utils2();
|
|
61007
61442
|
init_path_security();
|
|
61008
61443
|
import * as fsSync2 from "fs";
|
|
61009
|
-
import { constants as constants2, existsSync as
|
|
61444
|
+
import { constants as constants2, existsSync as existsSync28, realpathSync as realpathSync6 } from "fs";
|
|
61010
61445
|
import * as fsPromises3 from "fs/promises";
|
|
61011
61446
|
import * as path46 from "path";
|
|
61012
61447
|
|
|
@@ -61468,7 +61903,7 @@ function resolveModuleSpecifier(workspaceRoot, sourceFile, specifier) {
|
|
|
61468
61903
|
} catch {
|
|
61469
61904
|
realRoot = path46.normalize(workspaceRoot);
|
|
61470
61905
|
}
|
|
61471
|
-
if (!
|
|
61906
|
+
if (!existsSync28(resolved)) {
|
|
61472
61907
|
const EXTENSIONS = [
|
|
61473
61908
|
".ts",
|
|
61474
61909
|
".tsx",
|
|
@@ -61482,7 +61917,7 @@ function resolveModuleSpecifier(workspaceRoot, sourceFile, specifier) {
|
|
|
61482
61917
|
let found = null;
|
|
61483
61918
|
for (const ext of EXTENSIONS) {
|
|
61484
61919
|
const candidate = resolved + ext;
|
|
61485
|
-
if (
|
|
61920
|
+
if (existsSync28(candidate)) {
|
|
61486
61921
|
found = candidate;
|
|
61487
61922
|
break;
|
|
61488
61923
|
}
|
|
@@ -61579,7 +62014,7 @@ async function loadGraph(workspace) {
|
|
|
61579
62014
|
if (cached3 && !isDirty(normalized)) {
|
|
61580
62015
|
try {
|
|
61581
62016
|
const graphPath = getGraphPath(workspace);
|
|
61582
|
-
if (
|
|
62017
|
+
if (existsSync28(graphPath)) {
|
|
61583
62018
|
const stats = await fsPromises3.stat(graphPath);
|
|
61584
62019
|
const cachedMtime = mtimeCache.get(normalized);
|
|
61585
62020
|
if (cachedMtime !== undefined && stats.mtimeMs !== cachedMtime) {
|
|
@@ -61596,7 +62031,7 @@ async function loadGraph(workspace) {
|
|
|
61596
62031
|
}
|
|
61597
62032
|
try {
|
|
61598
62033
|
const graphPath = getGraphPath(workspace);
|
|
61599
|
-
if (!
|
|
62034
|
+
if (!existsSync28(graphPath)) {
|
|
61600
62035
|
return null;
|
|
61601
62036
|
}
|
|
61602
62037
|
const stats = await fsPromises3.stat(graphPath);
|
|
@@ -61722,7 +62157,7 @@ async function saveGraph(workspace, graph, options) {
|
|
|
61722
62157
|
lastError = error93 instanceof Error ? error93 : new Error(String(error93));
|
|
61723
62158
|
if (lastError instanceof Error && "code" in lastError && lastError.code === "EEXIST" && retries < WINDOWS_RENAME_MAX_RETRIES - 1) {
|
|
61724
62159
|
retries++;
|
|
61725
|
-
await new Promise((
|
|
62160
|
+
await new Promise((resolve18) => setTimeout(resolve18, WINDOWS_RENAME_RETRY_DELAY_MS));
|
|
61726
62161
|
continue;
|
|
61727
62162
|
}
|
|
61728
62163
|
throw lastError;
|
|
@@ -61855,7 +62290,7 @@ function buildWorkspaceGraph(workspaceRoot, options) {
|
|
|
61855
62290
|
const maxFileSize = options?.maxFileSizeBytes ?? 1024 * 1024;
|
|
61856
62291
|
const maxFiles = options?.maxFiles ?? 1e4;
|
|
61857
62292
|
const absoluteRoot = path46.resolve(workspaceRoot);
|
|
61858
|
-
if (!
|
|
62293
|
+
if (!existsSync28(absoluteRoot)) {
|
|
61859
62294
|
throw new Error(`Workspace directory does not exist: ${workspaceRoot}`);
|
|
61860
62295
|
}
|
|
61861
62296
|
const graph = createEmptyGraph(workspaceRoot);
|
|
@@ -62009,7 +62444,7 @@ async function updateGraphForFiles(workspaceRoot, filePaths, options) {
|
|
|
62009
62444
|
const updatedPaths = new Set;
|
|
62010
62445
|
for (const rawFilePath of filePaths) {
|
|
62011
62446
|
const normalizedPath = normalizeGraphPath(rawFilePath);
|
|
62012
|
-
const fileExists =
|
|
62447
|
+
const fileExists = existsSync28(rawFilePath);
|
|
62013
62448
|
if (fileExists) {
|
|
62014
62449
|
graph.edges = graph.edges.filter((e) => normalizeGraphPath(e.source) !== normalizedPath);
|
|
62015
62450
|
const result = scanFile(rawFilePath, absoluteRoot, maxFileSize);
|
|
@@ -62862,26 +63297,26 @@ function pLimit(concurrency) {
|
|
|
62862
63297
|
activeCount--;
|
|
62863
63298
|
resumeNext();
|
|
62864
63299
|
};
|
|
62865
|
-
const run2 = async (function_,
|
|
63300
|
+
const run2 = async (function_, resolve19, arguments_2) => {
|
|
62866
63301
|
const result = (async () => function_(...arguments_2))();
|
|
62867
|
-
|
|
63302
|
+
resolve19(result);
|
|
62868
63303
|
try {
|
|
62869
63304
|
await result;
|
|
62870
63305
|
} catch {}
|
|
62871
63306
|
next();
|
|
62872
63307
|
};
|
|
62873
|
-
const enqueue = (function_,
|
|
63308
|
+
const enqueue = (function_, resolve19, reject, arguments_2) => {
|
|
62874
63309
|
const queueItem = { reject };
|
|
62875
63310
|
new Promise((internalResolve) => {
|
|
62876
63311
|
queueItem.run = internalResolve;
|
|
62877
63312
|
queue.enqueue(queueItem);
|
|
62878
|
-
}).then(run2.bind(undefined, function_,
|
|
63313
|
+
}).then(run2.bind(undefined, function_, resolve19, arguments_2));
|
|
62879
63314
|
if (activeCount < concurrency) {
|
|
62880
63315
|
resumeNext();
|
|
62881
63316
|
}
|
|
62882
63317
|
};
|
|
62883
|
-
const generator = (function_, ...arguments_2) => new Promise((
|
|
62884
|
-
enqueue(function_,
|
|
63318
|
+
const generator = (function_, ...arguments_2) => new Promise((resolve19, reject) => {
|
|
63319
|
+
enqueue(function_, resolve19, reject, arguments_2);
|
|
62885
63320
|
});
|
|
62886
63321
|
Object.defineProperties(generator, {
|
|
62887
63322
|
activeCount: {
|
|
@@ -65610,7 +66045,7 @@ import * as path56 from "path";
|
|
|
65610
66045
|
import * as child_process5 from "child_process";
|
|
65611
66046
|
var WIN32_CMD_BINARIES = new Set(["npm", "npx", "pnpm", "yarn"]);
|
|
65612
66047
|
function spawnAsync(command, cwd, timeoutMs) {
|
|
65613
|
-
return new Promise((
|
|
66048
|
+
return new Promise((resolve22) => {
|
|
65614
66049
|
try {
|
|
65615
66050
|
const [rawCmd, ...args2] = command;
|
|
65616
66051
|
const cmd = process.platform === "win32" && WIN32_CMD_BINARIES.has(rawCmd) && !rawCmd.includes(".") ? `${rawCmd}.cmd` : rawCmd;
|
|
@@ -65657,24 +66092,24 @@ function spawnAsync(command, cwd, timeoutMs) {
|
|
|
65657
66092
|
try {
|
|
65658
66093
|
proc.kill();
|
|
65659
66094
|
} catch {}
|
|
65660
|
-
|
|
66095
|
+
resolve22(null);
|
|
65661
66096
|
}, timeoutMs);
|
|
65662
66097
|
proc.on("close", (code) => {
|
|
65663
66098
|
if (done)
|
|
65664
66099
|
return;
|
|
65665
66100
|
done = true;
|
|
65666
66101
|
clearTimeout(timer);
|
|
65667
|
-
|
|
66102
|
+
resolve22({ exitCode: code ?? 1, stdout, stderr });
|
|
65668
66103
|
});
|
|
65669
66104
|
proc.on("error", () => {
|
|
65670
66105
|
if (done)
|
|
65671
66106
|
return;
|
|
65672
66107
|
done = true;
|
|
65673
66108
|
clearTimeout(timer);
|
|
65674
|
-
|
|
66109
|
+
resolve22(null);
|
|
65675
66110
|
});
|
|
65676
66111
|
} catch {
|
|
65677
|
-
|
|
66112
|
+
resolve22(null);
|
|
65678
66113
|
}
|
|
65679
66114
|
});
|
|
65680
66115
|
}
|
|
@@ -68386,12 +68821,12 @@ ${body2}`);
|
|
|
68386
68821
|
// src/council/council-evidence-writer.ts
|
|
68387
68822
|
import {
|
|
68388
68823
|
appendFileSync as appendFileSync7,
|
|
68389
|
-
existsSync as
|
|
68390
|
-
mkdirSync as
|
|
68824
|
+
existsSync as existsSync37,
|
|
68825
|
+
mkdirSync as mkdirSync17,
|
|
68391
68826
|
readFileSync as readFileSync35,
|
|
68392
68827
|
writeFileSync as writeFileSync11
|
|
68393
68828
|
} from "fs";
|
|
68394
|
-
import { join as
|
|
68829
|
+
import { join as join60 } from "path";
|
|
68395
68830
|
var EVIDENCE_DIR2 = ".swarm/evidence";
|
|
68396
68831
|
var VALID_TASK_ID = /^\d+\.\d+(\.\d+)*$/;
|
|
68397
68832
|
var COUNCIL_GATE_NAME = "council";
|
|
@@ -68425,11 +68860,11 @@ function writeCouncilEvidence(workingDir, synthesis) {
|
|
|
68425
68860
|
if (!VALID_TASK_ID.test(synthesis.taskId)) {
|
|
68426
68861
|
throw new Error(`writeCouncilEvidence: invalid taskId "${synthesis.taskId}" \u2014 must match N.M or N.M.P format`);
|
|
68427
68862
|
}
|
|
68428
|
-
const dir =
|
|
68429
|
-
|
|
68430
|
-
const filePath =
|
|
68863
|
+
const dir = join60(workingDir, EVIDENCE_DIR2);
|
|
68864
|
+
mkdirSync17(dir, { recursive: true });
|
|
68865
|
+
const filePath = join60(dir, `${synthesis.taskId}.json`);
|
|
68431
68866
|
const existingRoot = Object.create(null);
|
|
68432
|
-
if (
|
|
68867
|
+
if (existsSync37(filePath)) {
|
|
68433
68868
|
try {
|
|
68434
68869
|
const parsed = JSON.parse(readFileSync35(filePath, "utf-8"));
|
|
68435
68870
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
@@ -68456,15 +68891,15 @@ function writeCouncilEvidence(workingDir, synthesis) {
|
|
|
68456
68891
|
updated.gates = mergedGates;
|
|
68457
68892
|
writeFileSync11(filePath, JSON.stringify(updated, null, 2));
|
|
68458
68893
|
try {
|
|
68459
|
-
const councilDir =
|
|
68460
|
-
|
|
68894
|
+
const councilDir = join60(workingDir, ".swarm", "council");
|
|
68895
|
+
mkdirSync17(councilDir, { recursive: true });
|
|
68461
68896
|
const auditLine = JSON.stringify({
|
|
68462
68897
|
round: synthesis.roundNumber,
|
|
68463
68898
|
verdict: synthesis.overallVerdict,
|
|
68464
68899
|
timestamp: synthesis.timestamp,
|
|
68465
68900
|
vetoedBy: synthesis.vetoedBy
|
|
68466
68901
|
});
|
|
68467
|
-
appendFileSync7(
|
|
68902
|
+
appendFileSync7(join60(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
|
|
68468
68903
|
`);
|
|
68469
68904
|
} catch (auditError) {
|
|
68470
68905
|
console.warn(`writeCouncilEvidence: failed to append round-history audit log: ${auditError instanceof Error ? auditError.message : String(auditError)}`);
|
|
@@ -68593,22 +69028,22 @@ function buildUnifiedFeedback(taskId, verdict, vetoedBy, requiredFixes, advisory
|
|
|
68593
69028
|
}
|
|
68594
69029
|
|
|
68595
69030
|
// src/council/criteria-store.ts
|
|
68596
|
-
import { existsSync as
|
|
68597
|
-
import { join as
|
|
69031
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync18, readFileSync as readFileSync36, writeFileSync as writeFileSync12 } from "fs";
|
|
69032
|
+
import { join as join61 } from "path";
|
|
68598
69033
|
var COUNCIL_DIR = ".swarm/council";
|
|
68599
69034
|
function writeCriteria(workingDir, taskId, criteria) {
|
|
68600
|
-
const dir =
|
|
68601
|
-
|
|
69035
|
+
const dir = join61(workingDir, COUNCIL_DIR);
|
|
69036
|
+
mkdirSync18(dir, { recursive: true });
|
|
68602
69037
|
const payload = {
|
|
68603
69038
|
taskId,
|
|
68604
69039
|
criteria,
|
|
68605
69040
|
declaredAt: new Date().toISOString()
|
|
68606
69041
|
};
|
|
68607
|
-
writeFileSync12(
|
|
69042
|
+
writeFileSync12(join61(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
|
|
68608
69043
|
}
|
|
68609
69044
|
function readCriteria(workingDir, taskId) {
|
|
68610
|
-
const filePath =
|
|
68611
|
-
if (!
|
|
69045
|
+
const filePath = join61(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
|
|
69046
|
+
if (!existsSync38(filePath))
|
|
68612
69047
|
return null;
|
|
68613
69048
|
try {
|
|
68614
69049
|
const parsed = JSON.parse(readFileSync36(filePath, "utf-8"));
|
|
@@ -70176,7 +70611,7 @@ function summarizePlan(plan) {
|
|
|
70176
70611
|
}))
|
|
70177
70612
|
};
|
|
70178
70613
|
}
|
|
70179
|
-
function
|
|
70614
|
+
function derivePlanId2(plan) {
|
|
70180
70615
|
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
70181
70616
|
}
|
|
70182
70617
|
async function executeGetApprovedPlan(args2, directory) {
|
|
@@ -70197,7 +70632,9 @@ async function executeGetApprovedPlan(args2, directory) {
|
|
|
70197
70632
|
reason: "no_approved_snapshot"
|
|
70198
70633
|
};
|
|
70199
70634
|
}
|
|
70200
|
-
const expectedPlanId =
|
|
70635
|
+
const expectedPlanId = derivePlanId2(currentPlan);
|
|
70636
|
+
const profile = getProfile(directory, expectedPlanId);
|
|
70637
|
+
const qaProfileHash = profile ? computeProfileHash(profile) : null;
|
|
70201
70638
|
const approved = await loadLastApprovedPlan(directory, expectedPlanId);
|
|
70202
70639
|
if (!approved) {
|
|
70203
70640
|
const unscopedSnapshot = await loadLastApprovedPlan(directory);
|
|
@@ -70207,12 +70644,14 @@ async function executeGetApprovedPlan(args2, directory) {
|
|
|
70207
70644
|
approved_plan: undefined,
|
|
70208
70645
|
current_plan: null,
|
|
70209
70646
|
drift_detected: true,
|
|
70210
|
-
current_plan_error: "Plan identity (swarm/title) was mutated after approval \u2014 " + `expected plan_id '${expectedPlanId}' but approved snapshot has a different identity. ` + "This is a form of plan tampering."
|
|
70647
|
+
current_plan_error: "Plan identity (swarm/title) was mutated after approval \u2014 " + `expected plan_id '${expectedPlanId}' but approved snapshot has a different identity. ` + "This is a form of plan tampering.",
|
|
70648
|
+
qa_profile_hash: qaProfileHash
|
|
70211
70649
|
};
|
|
70212
70650
|
}
|
|
70213
70651
|
return {
|
|
70214
70652
|
success: false,
|
|
70215
|
-
reason: "no_approved_snapshot"
|
|
70653
|
+
reason: "no_approved_snapshot",
|
|
70654
|
+
qa_profile_hash: qaProfileHash
|
|
70216
70655
|
};
|
|
70217
70656
|
}
|
|
70218
70657
|
const summaryOnly = args2.summary_only === true;
|
|
@@ -70233,7 +70672,8 @@ async function executeGetApprovedPlan(args2, directory) {
|
|
|
70233
70672
|
success: true,
|
|
70234
70673
|
approved_plan: approvedPayload,
|
|
70235
70674
|
current_plan: currentPayload,
|
|
70236
|
-
drift_detected: driftDetected
|
|
70675
|
+
drift_detected: driftDetected,
|
|
70676
|
+
qa_profile_hash: qaProfileHash
|
|
70237
70677
|
};
|
|
70238
70678
|
}
|
|
70239
70679
|
var get_approved_plan = createSwarmTool({
|
|
@@ -70246,13 +70686,58 @@ var get_approved_plan = createSwarmTool({
|
|
|
70246
70686
|
return JSON.stringify(await executeGetApprovedPlan(typedArgs, directory), null, 2);
|
|
70247
70687
|
}
|
|
70248
70688
|
});
|
|
70689
|
+
// src/tools/get-qa-gate-profile.ts
|
|
70690
|
+
init_manager();
|
|
70691
|
+
init_create_tool();
|
|
70692
|
+
function derivePlanId3(plan) {
|
|
70693
|
+
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
70694
|
+
}
|
|
70695
|
+
async function executeGetQaGateProfile(_args, directory) {
|
|
70696
|
+
const plan = await loadPlanJsonOnly(directory);
|
|
70697
|
+
if (!plan) {
|
|
70698
|
+
return {
|
|
70699
|
+
success: false,
|
|
70700
|
+
reason: "plan_json_unavailable"
|
|
70701
|
+
};
|
|
70702
|
+
}
|
|
70703
|
+
const planId = derivePlanId3(plan);
|
|
70704
|
+
const profile = getProfile(directory, planId);
|
|
70705
|
+
if (!profile) {
|
|
70706
|
+
return {
|
|
70707
|
+
success: false,
|
|
70708
|
+
reason: "no_profile",
|
|
70709
|
+
plan_id: planId
|
|
70710
|
+
};
|
|
70711
|
+
}
|
|
70712
|
+
return {
|
|
70713
|
+
success: true,
|
|
70714
|
+
plan_id: planId,
|
|
70715
|
+
profile: {
|
|
70716
|
+
plan_id: profile.plan_id,
|
|
70717
|
+
project_type: profile.project_type,
|
|
70718
|
+
gates: { ...profile.gates },
|
|
70719
|
+
locked_at: profile.locked_at,
|
|
70720
|
+
locked_by_snapshot_seq: profile.locked_by_snapshot_seq,
|
|
70721
|
+
created_at: profile.created_at,
|
|
70722
|
+
profile_hash: computeProfileHash(profile)
|
|
70723
|
+
}
|
|
70724
|
+
};
|
|
70725
|
+
}
|
|
70726
|
+
var get_qa_gate_profile = createSwarmTool({
|
|
70727
|
+
description: "Retrieve the QA gate profile for the current plan. Returns the spec-level " + "gates, lock state, and a SHA-256 profile hash. Read-only \u2014 does not " + "create a profile if none exists. plan_id is derived automatically from " + "plan.json (swarm + title).",
|
|
70728
|
+
args: {},
|
|
70729
|
+
execute: async (args2, directory) => {
|
|
70730
|
+
const typedArgs = args2 ?? {};
|
|
70731
|
+
return JSON.stringify(await executeGetQaGateProfile(typedArgs, directory), null, 2);
|
|
70732
|
+
}
|
|
70733
|
+
});
|
|
70249
70734
|
// src/tools/gitingest.ts
|
|
70250
70735
|
init_dist();
|
|
70251
70736
|
init_create_tool();
|
|
70252
70737
|
var GITINGEST_TIMEOUT_MS = 1e4;
|
|
70253
70738
|
var GITINGEST_MAX_RESPONSE_BYTES = 5242880;
|
|
70254
70739
|
var GITINGEST_MAX_RETRIES = 2;
|
|
70255
|
-
var delay = (ms) => new Promise((
|
|
70740
|
+
var delay = (ms) => new Promise((resolve28) => setTimeout(resolve28, ms));
|
|
70256
70741
|
async function fetchGitingest(args2) {
|
|
70257
70742
|
for (let attempt = 0;attempt <= GITINGEST_MAX_RETRIES; attempt++) {
|
|
70258
70743
|
try {
|
|
@@ -70829,7 +71314,7 @@ init_dist();
|
|
|
70829
71314
|
init_config();
|
|
70830
71315
|
init_knowledge_store();
|
|
70831
71316
|
init_create_tool();
|
|
70832
|
-
import { existsSync as
|
|
71317
|
+
import { existsSync as existsSync43 } from "fs";
|
|
70833
71318
|
var DEFAULT_LIMIT = 10;
|
|
70834
71319
|
var MAX_LESSON_LENGTH = 200;
|
|
70835
71320
|
var VALID_CATEGORIES3 = [
|
|
@@ -70898,14 +71383,14 @@ function validateLimit(limit) {
|
|
|
70898
71383
|
}
|
|
70899
71384
|
async function readSwarmKnowledge(directory) {
|
|
70900
71385
|
const swarmPath = resolveSwarmKnowledgePath(directory);
|
|
70901
|
-
if (!
|
|
71386
|
+
if (!existsSync43(swarmPath)) {
|
|
70902
71387
|
return [];
|
|
70903
71388
|
}
|
|
70904
71389
|
return readKnowledge(swarmPath);
|
|
70905
71390
|
}
|
|
70906
71391
|
async function readHiveKnowledge() {
|
|
70907
71392
|
const hivePath = resolveHiveKnowledgePath();
|
|
70908
|
-
if (!
|
|
71393
|
+
if (!existsSync43(hivePath)) {
|
|
70909
71394
|
return [];
|
|
70910
71395
|
}
|
|
70911
71396
|
return readKnowledge(hivePath);
|
|
@@ -71862,7 +72347,7 @@ async function runNpmAudit(directory) {
|
|
|
71862
72347
|
stderr: "pipe",
|
|
71863
72348
|
cwd: directory
|
|
71864
72349
|
});
|
|
71865
|
-
const timeoutPromise = new Promise((
|
|
72350
|
+
const timeoutPromise = new Promise((resolve29) => setTimeout(() => resolve29("timeout"), AUDIT_TIMEOUT_MS));
|
|
71866
72351
|
const result = await Promise.race([
|
|
71867
72352
|
Promise.all([
|
|
71868
72353
|
new Response(proc.stdout).text(),
|
|
@@ -71985,7 +72470,7 @@ async function runPipAudit(directory) {
|
|
|
71985
72470
|
stderr: "pipe",
|
|
71986
72471
|
cwd: directory
|
|
71987
72472
|
});
|
|
71988
|
-
const timeoutPromise = new Promise((
|
|
72473
|
+
const timeoutPromise = new Promise((resolve29) => setTimeout(() => resolve29("timeout"), AUDIT_TIMEOUT_MS));
|
|
71989
72474
|
const result = await Promise.race([
|
|
71990
72475
|
Promise.all([
|
|
71991
72476
|
new Response(proc.stdout).text(),
|
|
@@ -72116,7 +72601,7 @@ async function runCargoAudit(directory) {
|
|
|
72116
72601
|
stderr: "pipe",
|
|
72117
72602
|
cwd: directory
|
|
72118
72603
|
});
|
|
72119
|
-
const timeoutPromise = new Promise((
|
|
72604
|
+
const timeoutPromise = new Promise((resolve29) => setTimeout(() => resolve29("timeout"), AUDIT_TIMEOUT_MS));
|
|
72120
72605
|
const result = await Promise.race([
|
|
72121
72606
|
Promise.all([
|
|
72122
72607
|
new Response(proc.stdout).text(),
|
|
@@ -72243,7 +72728,7 @@ async function runGoAudit(directory) {
|
|
|
72243
72728
|
stderr: "pipe",
|
|
72244
72729
|
cwd: directory
|
|
72245
72730
|
});
|
|
72246
|
-
const timeoutPromise = new Promise((
|
|
72731
|
+
const timeoutPromise = new Promise((resolve29) => setTimeout(() => resolve29("timeout"), AUDIT_TIMEOUT_MS));
|
|
72247
72732
|
const result = await Promise.race([
|
|
72248
72733
|
Promise.all([
|
|
72249
72734
|
new Response(proc.stdout).text(),
|
|
@@ -72379,7 +72864,7 @@ async function runDotnetAudit(directory) {
|
|
|
72379
72864
|
stderr: "pipe",
|
|
72380
72865
|
cwd: directory
|
|
72381
72866
|
});
|
|
72382
|
-
const timeoutPromise = new Promise((
|
|
72867
|
+
const timeoutPromise = new Promise((resolve29) => setTimeout(() => resolve29("timeout"), AUDIT_TIMEOUT_MS));
|
|
72383
72868
|
const result = await Promise.race([
|
|
72384
72869
|
Promise.all([
|
|
72385
72870
|
new Response(proc.stdout).text(),
|
|
@@ -72498,7 +72983,7 @@ async function runBundleAudit(directory) {
|
|
|
72498
72983
|
stderr: "pipe",
|
|
72499
72984
|
cwd: directory
|
|
72500
72985
|
});
|
|
72501
|
-
const timeoutPromise = new Promise((
|
|
72986
|
+
const timeoutPromise = new Promise((resolve29) => setTimeout(() => resolve29("timeout"), AUDIT_TIMEOUT_MS));
|
|
72502
72987
|
const result = await Promise.race([
|
|
72503
72988
|
Promise.all([
|
|
72504
72989
|
new Response(proc.stdout).text(),
|
|
@@ -72646,7 +73131,7 @@ async function runDartAudit(directory) {
|
|
|
72646
73131
|
stderr: "pipe",
|
|
72647
73132
|
cwd: directory
|
|
72648
73133
|
});
|
|
72649
|
-
const timeoutPromise = new Promise((
|
|
73134
|
+
const timeoutPromise = new Promise((resolve29) => setTimeout(() => resolve29("timeout"), AUDIT_TIMEOUT_MS));
|
|
72650
73135
|
const result = await Promise.race([
|
|
72651
73136
|
Promise.all([
|
|
72652
73137
|
new Response(proc.stdout).text(),
|
|
@@ -72764,7 +73249,7 @@ async function runComposerAudit(directory) {
|
|
|
72764
73249
|
stderr: "pipe",
|
|
72765
73250
|
cwd: directory
|
|
72766
73251
|
});
|
|
72767
|
-
const timeoutPromise = new Promise((
|
|
73252
|
+
const timeoutPromise = new Promise((resolve29) => setTimeout(() => resolve29("timeout"), AUDIT_TIMEOUT_MS));
|
|
72768
73253
|
const result = await Promise.race([
|
|
72769
73254
|
Promise.all([
|
|
72770
73255
|
new Response(proc.stdout).text(),
|
|
@@ -74406,7 +74891,7 @@ function mapSemgrepSeverity(severity) {
|
|
|
74406
74891
|
}
|
|
74407
74892
|
}
|
|
74408
74893
|
async function executeWithTimeout(command, args2, options) {
|
|
74409
|
-
return new Promise((
|
|
74894
|
+
return new Promise((resolve30) => {
|
|
74410
74895
|
const child = child_process7.spawn(command, args2, {
|
|
74411
74896
|
shell: false,
|
|
74412
74897
|
cwd: options.cwd
|
|
@@ -74415,7 +74900,7 @@ async function executeWithTimeout(command, args2, options) {
|
|
|
74415
74900
|
let stderr = "";
|
|
74416
74901
|
const timeout = setTimeout(() => {
|
|
74417
74902
|
child.kill("SIGTERM");
|
|
74418
|
-
|
|
74903
|
+
resolve30({
|
|
74419
74904
|
stdout,
|
|
74420
74905
|
stderr: "Process timed out",
|
|
74421
74906
|
exitCode: 124
|
|
@@ -74429,7 +74914,7 @@ async function executeWithTimeout(command, args2, options) {
|
|
|
74429
74914
|
});
|
|
74430
74915
|
child.on("close", (code) => {
|
|
74431
74916
|
clearTimeout(timeout);
|
|
74432
|
-
|
|
74917
|
+
resolve30({
|
|
74433
74918
|
stdout,
|
|
74434
74919
|
stderr,
|
|
74435
74920
|
exitCode: code ?? 0
|
|
@@ -74437,7 +74922,7 @@ async function executeWithTimeout(command, args2, options) {
|
|
|
74437
74922
|
});
|
|
74438
74923
|
child.on("error", (err2) => {
|
|
74439
74924
|
clearTimeout(timeout);
|
|
74440
|
-
|
|
74925
|
+
resolve30({
|
|
74441
74926
|
stdout,
|
|
74442
74927
|
stderr: err2.message,
|
|
74443
74928
|
exitCode: 1
|
|
@@ -77881,7 +78366,7 @@ async function ripgrepSearch(opts) {
|
|
|
77881
78366
|
stderr: "pipe",
|
|
77882
78367
|
cwd: opts.workspace
|
|
77883
78368
|
});
|
|
77884
|
-
const timeout = new Promise((
|
|
78369
|
+
const timeout = new Promise((resolve35) => setTimeout(() => resolve35("timeout"), REGEX_TIMEOUT_MS));
|
|
77885
78370
|
const exitPromise = proc.exited;
|
|
77886
78371
|
const result = await Promise.race([exitPromise, timeout]);
|
|
77887
78372
|
if (result === "timeout") {
|
|
@@ -78165,6 +78650,84 @@ var search = createSwarmTool({
|
|
|
78165
78650
|
// src/tools/index.ts
|
|
78166
78651
|
init_secretscan();
|
|
78167
78652
|
|
|
78653
|
+
// src/tools/set-qa-gates.ts
|
|
78654
|
+
init_dist();
|
|
78655
|
+
init_manager();
|
|
78656
|
+
init_create_tool();
|
|
78657
|
+
function derivePlanId4(plan) {
|
|
78658
|
+
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
78659
|
+
}
|
|
78660
|
+
async function executeSetQaGates(args2, directory) {
|
|
78661
|
+
const plan = await loadPlanJsonOnly(directory);
|
|
78662
|
+
if (!plan) {
|
|
78663
|
+
return {
|
|
78664
|
+
success: false,
|
|
78665
|
+
reason: "plan_json_unavailable",
|
|
78666
|
+
message: "Cannot configure QA gates: plan.json is missing or invalid. " + "Create a plan first (e.g. via /swarm specify or save_plan)."
|
|
78667
|
+
};
|
|
78668
|
+
}
|
|
78669
|
+
const planId = derivePlanId4(plan);
|
|
78670
|
+
getOrCreateProfile(directory, planId, args2.project_type);
|
|
78671
|
+
const partial3 = {};
|
|
78672
|
+
for (const key of [
|
|
78673
|
+
"reviewer",
|
|
78674
|
+
"test_engineer",
|
|
78675
|
+
"council_mode",
|
|
78676
|
+
"sme_enabled",
|
|
78677
|
+
"critic_pre_plan",
|
|
78678
|
+
"hallucination_guard",
|
|
78679
|
+
"sast_enabled"
|
|
78680
|
+
]) {
|
|
78681
|
+
if (args2[key] !== undefined)
|
|
78682
|
+
partial3[key] = args2[key];
|
|
78683
|
+
}
|
|
78684
|
+
try {
|
|
78685
|
+
const updated = setGates(directory, planId, partial3);
|
|
78686
|
+
return {
|
|
78687
|
+
success: true,
|
|
78688
|
+
plan_id: planId,
|
|
78689
|
+
message: `QA gates updated for plan_id=${planId}`,
|
|
78690
|
+
profile: {
|
|
78691
|
+
plan_id: updated.plan_id,
|
|
78692
|
+
gates: { ...updated.gates },
|
|
78693
|
+
locked_at: updated.locked_at,
|
|
78694
|
+
locked_by_snapshot_seq: updated.locked_by_snapshot_seq,
|
|
78695
|
+
profile_hash: computeProfileHash(updated)
|
|
78696
|
+
}
|
|
78697
|
+
};
|
|
78698
|
+
} catch (err3) {
|
|
78699
|
+
const msg = err3 instanceof Error ? err3.message : String(err3);
|
|
78700
|
+
const lower = msg.toLowerCase();
|
|
78701
|
+
let reason = "set_gates_failed";
|
|
78702
|
+
if (lower.includes("locked"))
|
|
78703
|
+
reason = "profile_locked";
|
|
78704
|
+
else if (lower.includes("ratchet"))
|
|
78705
|
+
reason = "ratchet_violation";
|
|
78706
|
+
return {
|
|
78707
|
+
success: false,
|
|
78708
|
+
reason,
|
|
78709
|
+
message: msg,
|
|
78710
|
+
plan_id: planId
|
|
78711
|
+
};
|
|
78712
|
+
}
|
|
78713
|
+
}
|
|
78714
|
+
var set_qa_gates = createSwarmTool({
|
|
78715
|
+
description: "Configure the QA gate profile for the current plan. Architect-only. " + "Ratchet-tighter: can enable additional gates but cannot disable gates " + "that are already enabled. Rejects all writes once the profile is " + "locked (after critic approval). Creates the profile with defaults if " + "none exists. plan_id is derived automatically from plan.json.",
|
|
78716
|
+
args: {
|
|
78717
|
+
reviewer: tool.schema.boolean().optional().describe("Enable the reviewer gate (true) \u2014 cannot be disabled."),
|
|
78718
|
+
test_engineer: tool.schema.boolean().optional().describe("Enable the test_engineer gate (true) \u2014 cannot be disabled once on."),
|
|
78719
|
+
council_mode: tool.schema.boolean().optional().describe("Enable council mode (multi-SME consensus on high-risk phases)."),
|
|
78720
|
+
sme_enabled: tool.schema.boolean().optional().describe("Enable SME consultation."),
|
|
78721
|
+
critic_pre_plan: tool.schema.boolean().optional().describe("Enable critic_pre_plan review before plan approval."),
|
|
78722
|
+
hallucination_guard: tool.schema.boolean().optional().describe("Enable hallucination_guard checks on plan and implementation claims."),
|
|
78723
|
+
sast_enabled: tool.schema.boolean().optional().describe("Enable SAST scanning as a required QA gate."),
|
|
78724
|
+
project_type: tool.schema.string().optional().describe('Project type label (e.g. "ts", "python"). Only applied when the profile is being created for the first time.')
|
|
78725
|
+
},
|
|
78726
|
+
execute: async (args2, directory) => {
|
|
78727
|
+
const typedArgs = args2 ?? {};
|
|
78728
|
+
return JSON.stringify(await executeSetQaGates(typedArgs, directory), null, 2);
|
|
78729
|
+
}
|
|
78730
|
+
});
|
|
78168
78731
|
// src/tools/suggest-patch.ts
|
|
78169
78732
|
init_tool();
|
|
78170
78733
|
init_path_security();
|
|
@@ -79672,12 +80235,15 @@ var update_task_status = createSwarmTool({
|
|
|
79672
80235
|
});
|
|
79673
80236
|
// src/tools/write-drift-evidence.ts
|
|
79674
80237
|
init_tool();
|
|
80238
|
+
import fs71 from "fs";
|
|
80239
|
+
import path87 from "path";
|
|
79675
80240
|
init_utils2();
|
|
79676
80241
|
init_ledger();
|
|
79677
80242
|
init_manager();
|
|
79678
80243
|
init_create_tool();
|
|
79679
|
-
|
|
79680
|
-
|
|
80244
|
+
function derivePlanId5(plan) {
|
|
80245
|
+
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
80246
|
+
}
|
|
79681
80247
|
function normalizeVerdict(verdict) {
|
|
79682
80248
|
switch (verdict) {
|
|
79683
80249
|
case "APPROVED":
|
|
@@ -79744,6 +80310,8 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
79744
80310
|
await fs71.promises.rename(tempPath, validatedPath);
|
|
79745
80311
|
let snapshotInfo;
|
|
79746
80312
|
let snapshotError;
|
|
80313
|
+
let qaProfileLocked;
|
|
80314
|
+
let qaProfileLockError;
|
|
79747
80315
|
if (normalizedVerdict === "approved") {
|
|
79748
80316
|
try {
|
|
79749
80317
|
const currentPlan = await loadPlanJsonOnly(directory);
|
|
@@ -79761,6 +80329,21 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
79761
80329
|
seq: snapshotEvent.seq,
|
|
79762
80330
|
timestamp: snapshotEvent.timestamp
|
|
79763
80331
|
};
|
|
80332
|
+
try {
|
|
80333
|
+
const planId = derivePlanId5(currentPlan);
|
|
80334
|
+
const locked = lockProfile(directory, planId, snapshotEvent.seq);
|
|
80335
|
+
qaProfileLocked = {
|
|
80336
|
+
plan_id: planId,
|
|
80337
|
+
locked_at: locked.locked_at ?? "",
|
|
80338
|
+
locked_by_snapshot_seq: locked.locked_by_snapshot_seq ?? -1
|
|
80339
|
+
};
|
|
80340
|
+
} catch (lockErr) {
|
|
80341
|
+
const msg = lockErr instanceof Error ? lockErr.message : String(lockErr);
|
|
80342
|
+
if (!/No QA gate profile/i.test(msg)) {
|
|
80343
|
+
qaProfileLockError = msg;
|
|
80344
|
+
console.warn("[write_drift_evidence] QA gate profile lock failed:", msg);
|
|
80345
|
+
}
|
|
80346
|
+
}
|
|
79764
80347
|
} else {
|
|
79765
80348
|
snapshotError = "plan.json not available for snapshot";
|
|
79766
80349
|
}
|
|
@@ -79775,7 +80358,9 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
79775
80358
|
verdict: normalizedVerdict,
|
|
79776
80359
|
message: `Drift evidence written to .swarm/evidence/${phase}/drift-verifier.json`,
|
|
79777
80360
|
approvedSnapshot: snapshotInfo,
|
|
79778
|
-
snapshotError
|
|
80361
|
+
snapshotError,
|
|
80362
|
+
qaProfileLocked,
|
|
80363
|
+
qaProfileLockError
|
|
79779
80364
|
}, null, 2);
|
|
79780
80365
|
} catch (error93) {
|
|
79781
80366
|
return JSON.stringify({
|
|
@@ -80093,6 +80678,8 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
80093
80678
|
evidence_check,
|
|
80094
80679
|
extract_code_blocks,
|
|
80095
80680
|
get_approved_plan,
|
|
80681
|
+
get_qa_gate_profile,
|
|
80682
|
+
set_qa_gates,
|
|
80096
80683
|
gitingest,
|
|
80097
80684
|
imports,
|
|
80098
80685
|
knowledge_query,
|
|
@@ -80135,7 +80722,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
80135
80722
|
...opencodeConfig.command || {},
|
|
80136
80723
|
swarm: {
|
|
80137
80724
|
template: "/swarm $ARGUMENTS",
|
|
80138
|
-
description: "Swarm management commands: /swarm [status|plan|agents|history|config|evidence|handoff|archive|diagnose|preflight|sync-plan|benchmark|export|reset|rollback|retrieve|clarify|analyze|specify|dark-matter|knowledge|curate|turbo|full-auto|write-retro|reset-session|simulate|promote|checkpoint|close]"
|
|
80725
|
+
description: "Swarm management commands: /swarm [status|plan|agents|history|config|evidence|handoff|archive|diagnose|preflight|sync-plan|benchmark|export|reset|rollback|retrieve|clarify|analyze|specify|brainstorm|qa-gates|dark-matter|knowledge|curate|turbo|full-auto|write-retro|reset-session|simulate|promote|checkpoint|close]"
|
|
80139
80726
|
},
|
|
80140
80727
|
"swarm-status": {
|
|
80141
80728
|
template: "/swarm status",
|
|
@@ -80213,6 +80800,14 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
80213
80800
|
template: "/swarm specify $ARGUMENTS",
|
|
80214
80801
|
description: "Use /swarm specify to generate or import a feature specification"
|
|
80215
80802
|
},
|
|
80803
|
+
"swarm-brainstorm": {
|
|
80804
|
+
template: "/swarm brainstorm $ARGUMENTS",
|
|
80805
|
+
description: "Use /swarm brainstorm to enter the architect MODE: BRAINSTORM planning workflow"
|
|
80806
|
+
},
|
|
80807
|
+
"swarm-qa-gates": {
|
|
80808
|
+
template: "/swarm qa-gates $ARGUMENTS",
|
|
80809
|
+
description: "Use /swarm qa-gates to view or modify QA gate profile for the current plan"
|
|
80810
|
+
},
|
|
80216
80811
|
"swarm-dark-matter": {
|
|
80217
80812
|
template: "/swarm dark-matter",
|
|
80218
80813
|
description: "Use /swarm dark-matter to detect hidden file couplings"
|