opencode-swarm 6.71.1 → 6.72.1
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/agents/architect.d.ts +11 -0
- package/dist/cli/index.js +238 -227
- package/dist/config/schema.d.ts +6 -0
- package/dist/hooks/knowledge-store.d.ts +10 -1
- package/dist/hooks/knowledge-types.d.ts +9 -1
- package/dist/index.js +671 -373
- package/dist/state.d.ts +27 -1
- package/dist/tools/convene-council.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15101,7 +15101,10 @@ var init_schema = __esm(() => {
|
|
|
15101
15101
|
min_encounter_score: exports_external.number().min(0).max(1).default(0.1),
|
|
15102
15102
|
initial_encounter_score: exports_external.number().min(0).max(5).default(1),
|
|
15103
15103
|
encounter_increment: exports_external.number().min(0).max(1).default(0.1),
|
|
15104
|
-
max_encounter_score: exports_external.number().min(1).max(20).default(10)
|
|
15104
|
+
max_encounter_score: exports_external.number().min(1).max(20).default(10),
|
|
15105
|
+
default_max_phases: exports_external.number().int().positive().default(10),
|
|
15106
|
+
todo_max_phases: exports_external.number().int().positive().default(3),
|
|
15107
|
+
sweep_enabled: exports_external.boolean().default(true)
|
|
15105
15108
|
});
|
|
15106
15109
|
CuratorConfigSchema = exports_external.object({
|
|
15107
15110
|
enabled: exports_external.boolean().default(true),
|
|
@@ -17534,6 +17537,220 @@ var init_archive = __esm(() => {
|
|
|
17534
17537
|
init_manager2();
|
|
17535
17538
|
});
|
|
17536
17539
|
|
|
17540
|
+
// src/db/project-db.ts
|
|
17541
|
+
import { Database } from "bun:sqlite";
|
|
17542
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
17543
|
+
import { join as join6, resolve as resolve4 } from "path";
|
|
17544
|
+
function runProjectMigrations(db) {
|
|
17545
|
+
db.run(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
17546
|
+
version INTEGER PRIMARY KEY,
|
|
17547
|
+
name TEXT NOT NULL,
|
|
17548
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
17549
|
+
)`);
|
|
17550
|
+
const row = db.query("SELECT MAX(version) as version FROM schema_migrations").get();
|
|
17551
|
+
const currentVersion = row?.version ?? 0;
|
|
17552
|
+
for (const migration of MIGRATIONS) {
|
|
17553
|
+
if (migration.version <= currentVersion)
|
|
17554
|
+
continue;
|
|
17555
|
+
const apply = db.transaction(() => {
|
|
17556
|
+
db.run(migration.sql);
|
|
17557
|
+
db.run("INSERT INTO schema_migrations (version, name) VALUES (?, ?)", [
|
|
17558
|
+
migration.version,
|
|
17559
|
+
migration.name
|
|
17560
|
+
]);
|
|
17561
|
+
});
|
|
17562
|
+
apply();
|
|
17563
|
+
}
|
|
17564
|
+
}
|
|
17565
|
+
function projectDbPath(directory) {
|
|
17566
|
+
return join6(resolve4(directory), ".swarm", "swarm.db");
|
|
17567
|
+
}
|
|
17568
|
+
function projectDbExists(directory) {
|
|
17569
|
+
return existsSync4(projectDbPath(directory));
|
|
17570
|
+
}
|
|
17571
|
+
function getProjectDb(directory) {
|
|
17572
|
+
const key = resolve4(directory);
|
|
17573
|
+
const existing = _projectDbs.get(key);
|
|
17574
|
+
if (existing)
|
|
17575
|
+
return existing;
|
|
17576
|
+
const swarmDir = join6(key, ".swarm");
|
|
17577
|
+
mkdirSync3(swarmDir, { recursive: true });
|
|
17578
|
+
const db = new Database(join6(swarmDir, "swarm.db"));
|
|
17579
|
+
db.run("PRAGMA journal_mode = WAL;");
|
|
17580
|
+
db.run("PRAGMA synchronous = NORMAL;");
|
|
17581
|
+
db.run("PRAGMA busy_timeout = 5000;");
|
|
17582
|
+
db.run("PRAGMA foreign_keys = ON;");
|
|
17583
|
+
runProjectMigrations(db);
|
|
17584
|
+
_projectDbs.set(key, db);
|
|
17585
|
+
return db;
|
|
17586
|
+
}
|
|
17587
|
+
var MIGRATIONS, _projectDbs;
|
|
17588
|
+
var init_project_db = __esm(() => {
|
|
17589
|
+
MIGRATIONS = [
|
|
17590
|
+
{
|
|
17591
|
+
version: 1,
|
|
17592
|
+
name: "create_project_constraints",
|
|
17593
|
+
sql: `CREATE TABLE project_constraints (
|
|
17594
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17595
|
+
constraint_type TEXT NOT NULL,
|
|
17596
|
+
content TEXT NOT NULL,
|
|
17597
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
17598
|
+
)`
|
|
17599
|
+
},
|
|
17600
|
+
{
|
|
17601
|
+
version: 2,
|
|
17602
|
+
name: "create_qa_gate_profile",
|
|
17603
|
+
sql: `CREATE TABLE qa_gate_profile (
|
|
17604
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17605
|
+
plan_id TEXT NOT NULL UNIQUE,
|
|
17606
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
17607
|
+
project_type TEXT,
|
|
17608
|
+
gates TEXT NOT NULL DEFAULT '{}',
|
|
17609
|
+
locked_at TEXT,
|
|
17610
|
+
locked_by_snapshot_seq INTEGER
|
|
17611
|
+
)`
|
|
17612
|
+
},
|
|
17613
|
+
{
|
|
17614
|
+
version: 3,
|
|
17615
|
+
name: "create_qa_gate_profile_immutability_trigger",
|
|
17616
|
+
sql: `CREATE TRIGGER IF NOT EXISTS trg_qa_gate_profile_no_update_after_lock
|
|
17617
|
+
BEFORE UPDATE ON qa_gate_profile
|
|
17618
|
+
WHEN OLD.locked_at IS NOT NULL
|
|
17619
|
+
BEGIN
|
|
17620
|
+
SELECT RAISE(ABORT, 'qa_gate_profile row is locked and cannot be modified after critic approval');
|
|
17621
|
+
END`
|
|
17622
|
+
}
|
|
17623
|
+
];
|
|
17624
|
+
_projectDbs = new Map;
|
|
17625
|
+
});
|
|
17626
|
+
|
|
17627
|
+
// src/db/qa-gate-profile.ts
|
|
17628
|
+
import { createHash as createHash3 } from "crypto";
|
|
17629
|
+
function rowToProfile(row) {
|
|
17630
|
+
let parsed = {};
|
|
17631
|
+
try {
|
|
17632
|
+
parsed = JSON.parse(row.gates);
|
|
17633
|
+
} catch {
|
|
17634
|
+
parsed = {};
|
|
17635
|
+
}
|
|
17636
|
+
const gates = { ...DEFAULT_QA_GATES, ...parsed };
|
|
17637
|
+
return {
|
|
17638
|
+
id: row.id,
|
|
17639
|
+
plan_id: row.plan_id,
|
|
17640
|
+
created_at: row.created_at,
|
|
17641
|
+
project_type: row.project_type,
|
|
17642
|
+
gates,
|
|
17643
|
+
locked_at: row.locked_at,
|
|
17644
|
+
locked_by_snapshot_seq: row.locked_by_snapshot_seq
|
|
17645
|
+
};
|
|
17646
|
+
}
|
|
17647
|
+
function getProfile(directory, planId) {
|
|
17648
|
+
if (!projectDbExists(directory))
|
|
17649
|
+
return null;
|
|
17650
|
+
const db = getProjectDb(directory);
|
|
17651
|
+
const row = db.query("SELECT * FROM qa_gate_profile WHERE plan_id = ?").get(planId);
|
|
17652
|
+
return row ? rowToProfile(row) : null;
|
|
17653
|
+
}
|
|
17654
|
+
function getOrCreateProfile(directory, planId, projectType) {
|
|
17655
|
+
const existing = getProfile(directory, planId);
|
|
17656
|
+
if (existing)
|
|
17657
|
+
return existing;
|
|
17658
|
+
const db = getProjectDb(directory);
|
|
17659
|
+
const gatesJson = JSON.stringify(DEFAULT_QA_GATES);
|
|
17660
|
+
const insert = db.transaction(() => {
|
|
17661
|
+
db.run("INSERT INTO qa_gate_profile (plan_id, project_type, gates) VALUES (?, ?, ?)", [planId, projectType ?? null, gatesJson]);
|
|
17662
|
+
});
|
|
17663
|
+
try {
|
|
17664
|
+
insert();
|
|
17665
|
+
} catch (err2) {
|
|
17666
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
17667
|
+
if (!msg.toLowerCase().includes("unique")) {
|
|
17668
|
+
throw err2;
|
|
17669
|
+
}
|
|
17670
|
+
}
|
|
17671
|
+
const after = getProfile(directory, planId);
|
|
17672
|
+
if (!after) {
|
|
17673
|
+
throw new Error(`Failed to create or load QA gate profile for plan_id=${planId}`);
|
|
17674
|
+
}
|
|
17675
|
+
return after;
|
|
17676
|
+
}
|
|
17677
|
+
function setGates(directory, planId, gates) {
|
|
17678
|
+
const current = getProfile(directory, planId);
|
|
17679
|
+
if (!current) {
|
|
17680
|
+
throw new Error(`No QA gate profile found for plan_id=${planId} \u2014 call getOrCreateProfile first`);
|
|
17681
|
+
}
|
|
17682
|
+
if (current.locked_at !== null) {
|
|
17683
|
+
throw new Error("Cannot modify gates: QA gate profile is locked after critic approval");
|
|
17684
|
+
}
|
|
17685
|
+
const merged = { ...current.gates };
|
|
17686
|
+
for (const key of Object.keys(gates)) {
|
|
17687
|
+
const incoming = gates[key];
|
|
17688
|
+
if (incoming === undefined)
|
|
17689
|
+
continue;
|
|
17690
|
+
if (incoming === false && current.gates[key] === true) {
|
|
17691
|
+
throw new Error(`Cannot disable gate '${key}': sessions can only ratchet tighter`);
|
|
17692
|
+
}
|
|
17693
|
+
if (incoming === true) {
|
|
17694
|
+
merged[key] = true;
|
|
17695
|
+
}
|
|
17696
|
+
}
|
|
17697
|
+
const db = getProjectDb(directory);
|
|
17698
|
+
db.run("UPDATE qa_gate_profile SET gates = ? WHERE plan_id = ?", [
|
|
17699
|
+
JSON.stringify(merged),
|
|
17700
|
+
planId
|
|
17701
|
+
]);
|
|
17702
|
+
const updated = getProfile(directory, planId);
|
|
17703
|
+
if (!updated) {
|
|
17704
|
+
throw new Error(`Failed to re-read QA gate profile after update for plan_id=${planId}`);
|
|
17705
|
+
}
|
|
17706
|
+
return updated;
|
|
17707
|
+
}
|
|
17708
|
+
function lockProfile(directory, planId, snapshotSeq) {
|
|
17709
|
+
const current = getProfile(directory, planId);
|
|
17710
|
+
if (!current) {
|
|
17711
|
+
throw new Error(`No QA gate profile found for plan_id=${planId} \u2014 cannot lock`);
|
|
17712
|
+
}
|
|
17713
|
+
if (current.locked_at !== null) {
|
|
17714
|
+
return current;
|
|
17715
|
+
}
|
|
17716
|
+
const db = getProjectDb(directory);
|
|
17717
|
+
db.run("UPDATE qa_gate_profile SET locked_at = datetime('now'), locked_by_snapshot_seq = ? WHERE plan_id = ?", [snapshotSeq, planId]);
|
|
17718
|
+
const locked = getProfile(directory, planId);
|
|
17719
|
+
if (!locked) {
|
|
17720
|
+
throw new Error(`Failed to re-read locked QA gate profile for plan_id=${planId}`);
|
|
17721
|
+
}
|
|
17722
|
+
return locked;
|
|
17723
|
+
}
|
|
17724
|
+
function computeProfileHash(profile) {
|
|
17725
|
+
const payload = JSON.stringify({
|
|
17726
|
+
plan_id: profile.plan_id,
|
|
17727
|
+
gates: profile.gates
|
|
17728
|
+
});
|
|
17729
|
+
return createHash3("sha256").update(payload).digest("hex");
|
|
17730
|
+
}
|
|
17731
|
+
function getEffectiveGates(profile, sessionOverrides) {
|
|
17732
|
+
const merged = { ...profile.gates };
|
|
17733
|
+
for (const key of Object.keys(sessionOverrides)) {
|
|
17734
|
+
if (sessionOverrides[key] === true) {
|
|
17735
|
+
merged[key] = true;
|
|
17736
|
+
}
|
|
17737
|
+
}
|
|
17738
|
+
return merged;
|
|
17739
|
+
}
|
|
17740
|
+
var DEFAULT_QA_GATES;
|
|
17741
|
+
var init_qa_gate_profile = __esm(() => {
|
|
17742
|
+
init_project_db();
|
|
17743
|
+
DEFAULT_QA_GATES = {
|
|
17744
|
+
reviewer: true,
|
|
17745
|
+
test_engineer: true,
|
|
17746
|
+
council_mode: false,
|
|
17747
|
+
sme_enabled: true,
|
|
17748
|
+
critic_pre_plan: true,
|
|
17749
|
+
hallucination_guard: false,
|
|
17750
|
+
sast_enabled: true
|
|
17751
|
+
};
|
|
17752
|
+
});
|
|
17753
|
+
|
|
17537
17754
|
// src/environment/profile.ts
|
|
17538
17755
|
function detectHostOS() {
|
|
17539
17756
|
switch (process.platform) {
|
|
@@ -21356,12 +21573,12 @@ var require_adapter = __commonJS((exports, module2) => {
|
|
|
21356
21573
|
return newFs;
|
|
21357
21574
|
}
|
|
21358
21575
|
function toPromise(method) {
|
|
21359
|
-
return (...args2) => new Promise((
|
|
21576
|
+
return (...args2) => new Promise((resolve5, reject) => {
|
|
21360
21577
|
args2.push((err2, result) => {
|
|
21361
21578
|
if (err2) {
|
|
21362
21579
|
reject(err2);
|
|
21363
21580
|
} else {
|
|
21364
|
-
|
|
21581
|
+
resolve5(result);
|
|
21365
21582
|
}
|
|
21366
21583
|
});
|
|
21367
21584
|
method(...args2);
|
|
@@ -24109,7 +24326,7 @@ __export(exports_gate_evidence, {
|
|
|
24109
24326
|
deriveRequiredGates: () => deriveRequiredGates,
|
|
24110
24327
|
DEFAULT_REQUIRED_GATES: () => DEFAULT_REQUIRED_GATES
|
|
24111
24328
|
});
|
|
24112
|
-
import { mkdirSync as
|
|
24329
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync4, renameSync as renameSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
24113
24330
|
import * as path9 from "path";
|
|
24114
24331
|
function isValidTaskId(taskId) {
|
|
24115
24332
|
return isStrictTaskId(taskId);
|
|
@@ -24174,7 +24391,7 @@ async function recordGateEvidence(directory, taskId, gate, sessionId, turbo) {
|
|
|
24174
24391
|
assertValidTaskId(taskId);
|
|
24175
24392
|
const evidenceDir = getEvidenceDir(directory);
|
|
24176
24393
|
const evidencePath = getEvidencePath(directory, taskId);
|
|
24177
|
-
|
|
24394
|
+
mkdirSync6(evidenceDir, { recursive: true });
|
|
24178
24395
|
const existing = readExisting(evidencePath);
|
|
24179
24396
|
const requiredGates = existing ? expandRequiredGates(existing.required_gates, gate) : deriveRequiredGates(gate);
|
|
24180
24397
|
const updated = {
|
|
@@ -24197,7 +24414,7 @@ async function recordAgentDispatch(directory, taskId, agentType, turbo) {
|
|
|
24197
24414
|
assertValidTaskId(taskId);
|
|
24198
24415
|
const evidenceDir = getEvidenceDir(directory);
|
|
24199
24416
|
const evidencePath = getEvidencePath(directory, taskId);
|
|
24200
|
-
|
|
24417
|
+
mkdirSync6(evidenceDir, { recursive: true });
|
|
24201
24418
|
const existing = readExisting(evidencePath);
|
|
24202
24419
|
const requiredGates = existing ? expandRequiredGates(existing.required_gates, agentType) : deriveRequiredGates(agentType);
|
|
24203
24420
|
const updated = {
|
|
@@ -24376,6 +24593,37 @@ function createDelegationGateHook(config2, directory) {
|
|
|
24376
24593
|
if (!session)
|
|
24377
24594
|
return;
|
|
24378
24595
|
const normalized = normalizeToolName(input.tool);
|
|
24596
|
+
const councilActive = await isCouncilGateActive(directory, config2.council);
|
|
24597
|
+
if (normalized === "convene_council") {
|
|
24598
|
+
try {
|
|
24599
|
+
const parsed = typeof _output === "string" ? JSON.parse(_output) : _output;
|
|
24600
|
+
const result = parsed;
|
|
24601
|
+
if (result && typeof result === "object" && result.success === true && typeof result.overallVerdict === "string") {
|
|
24602
|
+
const directArgs = input.args;
|
|
24603
|
+
const storedArgs = getStoredInputArgs(input.callID);
|
|
24604
|
+
const taskIdRaw = directArgs?.taskId ?? storedArgs?.taskId;
|
|
24605
|
+
const taskId = typeof taskIdRaw === "string" ? taskIdRaw : null;
|
|
24606
|
+
if (taskId) {
|
|
24607
|
+
if (!session.taskCouncilApproved)
|
|
24608
|
+
session.taskCouncilApproved = new Map;
|
|
24609
|
+
session.taskCouncilApproved.set(taskId, {
|
|
24610
|
+
verdict: result.overallVerdict,
|
|
24611
|
+
roundNumber: typeof result.roundNumber === "number" ? result.roundNumber : 1
|
|
24612
|
+
});
|
|
24613
|
+
if (councilActive && result.overallVerdict === "APPROVE" && result.allCriteriaMet === true && (result.requiredFixesCount ?? 0) === 0) {
|
|
24614
|
+
try {
|
|
24615
|
+
advanceTaskState(session, taskId, "complete");
|
|
24616
|
+
} catch (err2) {
|
|
24617
|
+
console.warn(`[delegation-gate] toolAfter convene_council: could not advance ${taskId} \u2192 complete: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24618
|
+
}
|
|
24619
|
+
}
|
|
24620
|
+
}
|
|
24621
|
+
}
|
|
24622
|
+
} catch (err2) {
|
|
24623
|
+
console.warn(`[delegation-gate] toolAfter convene_council: failed to parse output: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24624
|
+
}
|
|
24625
|
+
return;
|
|
24626
|
+
}
|
|
24379
24627
|
if (normalized === "Task" || normalized === "task") {
|
|
24380
24628
|
const directArgs = input.args;
|
|
24381
24629
|
const storedArgs = getStoredInputArgs(input.callID);
|
|
@@ -24388,60 +24636,62 @@ function createDelegationGateHook(config2, directory) {
|
|
|
24388
24636
|
hasReviewer = true;
|
|
24389
24637
|
if (targetAgent === "test_engineer")
|
|
24390
24638
|
hasTestEngineer = true;
|
|
24391
|
-
if (
|
|
24392
|
-
|
|
24393
|
-
|
|
24394
|
-
|
|
24395
|
-
|
|
24396
|
-
|
|
24397
|
-
|
|
24639
|
+
if (!councilActive) {
|
|
24640
|
+
if (targetAgent === "reviewer" && session.taskWorkflowStates) {
|
|
24641
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
24642
|
+
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
24643
|
+
try {
|
|
24644
|
+
advanceTaskState(session, taskId, "reviewer_run");
|
|
24645
|
+
} catch (err2) {
|
|
24646
|
+
console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24647
|
+
}
|
|
24398
24648
|
}
|
|
24399
24649
|
}
|
|
24400
24650
|
}
|
|
24401
|
-
|
|
24402
|
-
|
|
24403
|
-
|
|
24404
|
-
|
|
24405
|
-
|
|
24406
|
-
|
|
24407
|
-
|
|
24408
|
-
|
|
24651
|
+
if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
|
|
24652
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
24653
|
+
if (state === "reviewer_run") {
|
|
24654
|
+
try {
|
|
24655
|
+
advanceTaskState(session, taskId, "tests_run");
|
|
24656
|
+
} catch (err2) {
|
|
24657
|
+
console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24658
|
+
}
|
|
24409
24659
|
}
|
|
24410
24660
|
}
|
|
24411
24661
|
}
|
|
24412
|
-
|
|
24413
|
-
|
|
24414
|
-
|
|
24415
|
-
|
|
24416
|
-
|
|
24417
|
-
|
|
24418
|
-
|
|
24419
|
-
|
|
24420
|
-
|
|
24421
|
-
|
|
24422
|
-
|
|
24423
|
-
|
|
24424
|
-
|
|
24425
|
-
|
|
24426
|
-
|
|
24427
|
-
|
|
24428
|
-
|
|
24429
|
-
|
|
24662
|
+
if (targetAgent === "reviewer" || targetAgent === "test_engineer") {
|
|
24663
|
+
for (const [, otherSession] of swarmState.agentSessions) {
|
|
24664
|
+
if (otherSession === session)
|
|
24665
|
+
continue;
|
|
24666
|
+
if (!otherSession.taskWorkflowStates)
|
|
24667
|
+
continue;
|
|
24668
|
+
if (targetAgent === "reviewer") {
|
|
24669
|
+
const seedTaskId = getSeedTaskId(session);
|
|
24670
|
+
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
24671
|
+
otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
|
|
24672
|
+
}
|
|
24673
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
24674
|
+
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
24675
|
+
try {
|
|
24676
|
+
advanceTaskState(otherSession, taskId, "reviewer_run");
|
|
24677
|
+
} catch (err2) {
|
|
24678
|
+
console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24679
|
+
}
|
|
24430
24680
|
}
|
|
24431
24681
|
}
|
|
24432
24682
|
}
|
|
24433
|
-
|
|
24434
|
-
|
|
24435
|
-
|
|
24436
|
-
|
|
24437
|
-
|
|
24438
|
-
|
|
24439
|
-
|
|
24440
|
-
|
|
24441
|
-
|
|
24442
|
-
|
|
24443
|
-
|
|
24444
|
-
|
|
24683
|
+
if (targetAgent === "test_engineer") {
|
|
24684
|
+
const seedTaskId = getSeedTaskId(session);
|
|
24685
|
+
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
24686
|
+
otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
|
|
24687
|
+
}
|
|
24688
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
24689
|
+
if (state === "reviewer_run") {
|
|
24690
|
+
try {
|
|
24691
|
+
advanceTaskState(otherSession, taskId, "tests_run");
|
|
24692
|
+
} catch (err2) {
|
|
24693
|
+
console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24694
|
+
}
|
|
24445
24695
|
}
|
|
24446
24696
|
}
|
|
24447
24697
|
}
|
|
@@ -24500,69 +24750,71 @@ function createDelegationGateHook(config2, directory) {
|
|
|
24500
24750
|
if (target === "test_engineer")
|
|
24501
24751
|
hasTestEngineer = true;
|
|
24502
24752
|
}
|
|
24503
|
-
if (
|
|
24504
|
-
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
if (lastCoderIndex !== -1 && hasReviewer && session.taskWorkflowStates) {
|
|
24508
|
-
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
24509
|
-
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
24510
|
-
try {
|
|
24511
|
-
advanceTaskState(session, taskId, "reviewer_run");
|
|
24512
|
-
} catch (err2) {
|
|
24513
|
-
console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24514
|
-
}
|
|
24515
|
-
}
|
|
24753
|
+
if (!councilActive) {
|
|
24754
|
+
if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
|
|
24755
|
+
session.qaSkipCount = 0;
|
|
24756
|
+
session.qaSkipTaskIds = [];
|
|
24516
24757
|
}
|
|
24517
|
-
|
|
24518
|
-
|
|
24519
|
-
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
24520
|
-
if (state === "reviewer_run") {
|
|
24521
|
-
try {
|
|
24522
|
-
advanceTaskState(session, taskId, "tests_run");
|
|
24523
|
-
} catch (err2) {
|
|
24524
|
-
console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24525
|
-
}
|
|
24526
|
-
}
|
|
24527
|
-
}
|
|
24528
|
-
}
|
|
24529
|
-
if (lastCoderIndex !== -1 && hasReviewer) {
|
|
24530
|
-
for (const [, otherSession] of swarmState.agentSessions) {
|
|
24531
|
-
if (otherSession === session)
|
|
24532
|
-
continue;
|
|
24533
|
-
if (!otherSession.taskWorkflowStates)
|
|
24534
|
-
continue;
|
|
24535
|
-
const seedTaskId = getSeedTaskId(session);
|
|
24536
|
-
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
24537
|
-
otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
|
|
24538
|
-
}
|
|
24539
|
-
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
24758
|
+
if (lastCoderIndex !== -1 && hasReviewer && session.taskWorkflowStates) {
|
|
24759
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
24540
24760
|
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
24541
24761
|
try {
|
|
24542
|
-
advanceTaskState(
|
|
24762
|
+
advanceTaskState(session, taskId, "reviewer_run");
|
|
24543
24763
|
} catch (err2) {
|
|
24544
|
-
console.warn(`[delegation-gate] fallback
|
|
24764
|
+
console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24545
24765
|
}
|
|
24546
24766
|
}
|
|
24547
24767
|
}
|
|
24548
24768
|
}
|
|
24549
|
-
|
|
24550
|
-
|
|
24551
|
-
for (const [, otherSession] of swarmState.agentSessions) {
|
|
24552
|
-
if (otherSession === session)
|
|
24553
|
-
continue;
|
|
24554
|
-
if (!otherSession.taskWorkflowStates)
|
|
24555
|
-
continue;
|
|
24556
|
-
const seedTaskId = getSeedTaskId(session);
|
|
24557
|
-
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
24558
|
-
otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
|
|
24559
|
-
}
|
|
24560
|
-
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
24769
|
+
if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
|
|
24770
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
24561
24771
|
if (state === "reviewer_run") {
|
|
24562
24772
|
try {
|
|
24563
|
-
advanceTaskState(
|
|
24773
|
+
advanceTaskState(session, taskId, "tests_run");
|
|
24564
24774
|
} catch (err2) {
|
|
24565
|
-
console.warn(`[delegation-gate] fallback
|
|
24775
|
+
console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24776
|
+
}
|
|
24777
|
+
}
|
|
24778
|
+
}
|
|
24779
|
+
}
|
|
24780
|
+
if (lastCoderIndex !== -1 && hasReviewer) {
|
|
24781
|
+
for (const [, otherSession] of swarmState.agentSessions) {
|
|
24782
|
+
if (otherSession === session)
|
|
24783
|
+
continue;
|
|
24784
|
+
if (!otherSession.taskWorkflowStates)
|
|
24785
|
+
continue;
|
|
24786
|
+
const seedTaskId = getSeedTaskId(session);
|
|
24787
|
+
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
24788
|
+
otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
|
|
24789
|
+
}
|
|
24790
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
24791
|
+
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
24792
|
+
try {
|
|
24793
|
+
advanceTaskState(otherSession, taskId, "reviewer_run");
|
|
24794
|
+
} catch (err2) {
|
|
24795
|
+
console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24796
|
+
}
|
|
24797
|
+
}
|
|
24798
|
+
}
|
|
24799
|
+
}
|
|
24800
|
+
}
|
|
24801
|
+
if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
|
|
24802
|
+
for (const [, otherSession] of swarmState.agentSessions) {
|
|
24803
|
+
if (otherSession === session)
|
|
24804
|
+
continue;
|
|
24805
|
+
if (!otherSession.taskWorkflowStates)
|
|
24806
|
+
continue;
|
|
24807
|
+
const seedTaskId = getSeedTaskId(session);
|
|
24808
|
+
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
24809
|
+
otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
|
|
24810
|
+
}
|
|
24811
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
24812
|
+
if (state === "reviewer_run") {
|
|
24813
|
+
try {
|
|
24814
|
+
advanceTaskState(otherSession, taskId, "tests_run");
|
|
24815
|
+
} catch (err2) {
|
|
24816
|
+
console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
24817
|
+
}
|
|
24566
24818
|
}
|
|
24567
24819
|
}
|
|
24568
24820
|
}
|
|
@@ -24833,6 +25085,7 @@ __export(exports_state, {
|
|
|
24833
25085
|
rehydrateSessionFromDisk: () => rehydrateSessionFromDisk,
|
|
24834
25086
|
recordPhaseAgentDispatch: () => recordPhaseAgentDispatch,
|
|
24835
25087
|
pruneOldWindows: () => pruneOldWindows,
|
|
25088
|
+
isCouncilGateActive: () => isCouncilGateActive,
|
|
24836
25089
|
hasActiveTurboMode: () => hasActiveTurboMode,
|
|
24837
25090
|
hasActiveFullAuto: () => hasActiveFullAuto,
|
|
24838
25091
|
getTaskState: () => getTaskState,
|
|
@@ -24845,7 +25098,8 @@ __export(exports_state, {
|
|
|
24845
25098
|
buildRehydrationCache: () => buildRehydrationCache,
|
|
24846
25099
|
beginInvocation: () => beginInvocation,
|
|
24847
25100
|
applyRehydrationCache: () => applyRehydrationCache,
|
|
24848
|
-
advanceTaskState: () => advanceTaskState
|
|
25101
|
+
advanceTaskState: () => advanceTaskState,
|
|
25102
|
+
_resetCouncilDisagreementWarnings: () => _resetCouncilDisagreementWarnings
|
|
24849
25103
|
});
|
|
24850
25104
|
import * as fs9 from "fs/promises";
|
|
24851
25105
|
import * as path11 from "path";
|
|
@@ -24865,6 +25119,7 @@ function resetSwarmState() {
|
|
|
24865
25119
|
swarmState.fullAutoEnabledInConfig = false;
|
|
24866
25120
|
swarmState.environmentProfiles.clear();
|
|
24867
25121
|
clearPendingCoderScope();
|
|
25122
|
+
_councilDisagreementWarned.clear();
|
|
24868
25123
|
}
|
|
24869
25124
|
function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, directory) {
|
|
24870
25125
|
const now = Date.now();
|
|
@@ -24903,6 +25158,7 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
|
|
|
24903
25158
|
qaSkipCount: 0,
|
|
24904
25159
|
qaSkipTaskIds: [],
|
|
24905
25160
|
taskWorkflowStates: new Map,
|
|
25161
|
+
taskCouncilApproved: new Map,
|
|
24906
25162
|
lastGateOutcome: null,
|
|
24907
25163
|
declaredCoderScope: null,
|
|
24908
25164
|
lastScopeViolation: null,
|
|
@@ -25017,6 +25273,9 @@ function ensureAgentSession(sessionId, agentName, directory) {
|
|
|
25017
25273
|
if (!session.taskWorkflowStates) {
|
|
25018
25274
|
session.taskWorkflowStates = new Map;
|
|
25019
25275
|
}
|
|
25276
|
+
if (!session.taskCouncilApproved) {
|
|
25277
|
+
session.taskCouncilApproved = new Map;
|
|
25278
|
+
}
|
|
25020
25279
|
if (session.lastGateOutcome === undefined) {
|
|
25021
25280
|
session.lastGateOutcome = null;
|
|
25022
25281
|
}
|
|
@@ -25171,7 +25430,12 @@ function advanceTaskState(session, taskId, newState) {
|
|
|
25171
25430
|
throw new Error(`INVALID_TASK_STATE_TRANSITION: ${taskId} ${current} \u2192 ${newState}`);
|
|
25172
25431
|
}
|
|
25173
25432
|
if (newState === "complete" && current !== "tests_run") {
|
|
25174
|
-
|
|
25433
|
+
const councilEntry = session.taskCouncilApproved?.get(taskId);
|
|
25434
|
+
const councilApproved = councilEntry?.verdict === "APPROVE";
|
|
25435
|
+
const pastPreCheck = currentIndex >= STATE_ORDER.indexOf("pre_check_passed");
|
|
25436
|
+
if (!councilApproved || !pastPreCheck) {
|
|
25437
|
+
throw new Error(`INVALID_TASK_STATE_TRANSITION: ${taskId} cannot reach complete from ${current} \u2014 must pass through tests_run first (or have council APPROVE after pre_check)`);
|
|
25438
|
+
}
|
|
25175
25439
|
}
|
|
25176
25440
|
session.taskWorkflowStates.set(taskId, newState);
|
|
25177
25441
|
telemetry.taskStateChanged(session.agentName, taskId, newState, current);
|
|
@@ -25185,6 +25449,48 @@ function getTaskState(session, taskId) {
|
|
|
25185
25449
|
}
|
|
25186
25450
|
return session.taskWorkflowStates.get(taskId) ?? "idle";
|
|
25187
25451
|
}
|
|
25452
|
+
function derivePlanIdFromPlan(plan) {
|
|
25453
|
+
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
25454
|
+
}
|
|
25455
|
+
async function isCouncilGateActive(directory, council) {
|
|
25456
|
+
const enabled = council?.enabled === true;
|
|
25457
|
+
let plan = null;
|
|
25458
|
+
try {
|
|
25459
|
+
plan = await loadPlanJsonOnly(directory);
|
|
25460
|
+
} catch {
|
|
25461
|
+
plan = null;
|
|
25462
|
+
}
|
|
25463
|
+
if (!plan) {
|
|
25464
|
+
return false;
|
|
25465
|
+
}
|
|
25466
|
+
const planId = derivePlanIdFromPlan(plan);
|
|
25467
|
+
let profile = null;
|
|
25468
|
+
try {
|
|
25469
|
+
profile = getProfile(directory, planId);
|
|
25470
|
+
} catch (err2) {
|
|
25471
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
25472
|
+
const isBenign = msg.includes("SQLITE_CANTOPEN") || msg.includes("ENOENT");
|
|
25473
|
+
if (!isBenign) {
|
|
25474
|
+
console.warn(`[isCouncilGateActive] getProfile threw unexpectedly for plan ${planId}: ${msg}. Treating council as inactive.`);
|
|
25475
|
+
}
|
|
25476
|
+
profile = null;
|
|
25477
|
+
}
|
|
25478
|
+
if (!profile) {
|
|
25479
|
+
return false;
|
|
25480
|
+
}
|
|
25481
|
+
const councilMode = profile.gates.council_mode === true;
|
|
25482
|
+
if (enabled && councilMode) {
|
|
25483
|
+
return true;
|
|
25484
|
+
}
|
|
25485
|
+
if (enabled !== councilMode && !_councilDisagreementWarned.has(planId)) {
|
|
25486
|
+
_councilDisagreementWarned.add(planId);
|
|
25487
|
+
console.warn(`[delegation-gate] Council mode mismatch for plan ${planId}: ` + `pluginConfig.council.enabled=${enabled}, QaGates.council_mode=${councilMode}. ` + "Falling back to Stage B (non-council) advancement.");
|
|
25488
|
+
}
|
|
25489
|
+
return false;
|
|
25490
|
+
}
|
|
25491
|
+
function _resetCouncilDisagreementWarnings() {
|
|
25492
|
+
_councilDisagreementWarned.clear();
|
|
25493
|
+
}
|
|
25188
25494
|
function planStatusToWorkflowState(status) {
|
|
25189
25495
|
switch (status) {
|
|
25190
25496
|
case "in_progress":
|
|
@@ -25270,6 +25576,9 @@ function applyRehydrationCache(session) {
|
|
|
25270
25576
|
if (!session.taskWorkflowStates) {
|
|
25271
25577
|
session.taskWorkflowStates = new Map;
|
|
25272
25578
|
}
|
|
25579
|
+
if (!session.taskCouncilApproved) {
|
|
25580
|
+
session.taskCouncilApproved = new Map;
|
|
25581
|
+
}
|
|
25273
25582
|
const { planTaskStates, evidenceMap } = _rehydrationCache;
|
|
25274
25583
|
const STATE_ORDER = [
|
|
25275
25584
|
"idle",
|
|
@@ -25343,13 +25652,16 @@ function ensureSessionEnvironment(sessionId) {
|
|
|
25343
25652
|
}).catch(() => {});
|
|
25344
25653
|
return profile;
|
|
25345
25654
|
}
|
|
25346
|
-
var _rehydrationCache = null, swarmState;
|
|
25655
|
+
var _rehydrationCache = null, _councilDisagreementWarned, swarmState;
|
|
25347
25656
|
var init_state = __esm(() => {
|
|
25348
25657
|
init_constants();
|
|
25349
25658
|
init_plan_schema();
|
|
25350
25659
|
init_schema();
|
|
25660
|
+
init_qa_gate_profile();
|
|
25351
25661
|
init_delegation_gate();
|
|
25662
|
+
init_manager();
|
|
25352
25663
|
init_telemetry();
|
|
25664
|
+
_councilDisagreementWarned = new Set;
|
|
25353
25665
|
swarmState = {
|
|
25354
25666
|
activeToolCalls: new Map,
|
|
25355
25667
|
toolAggregates: new Map,
|
|
@@ -38876,7 +39188,7 @@ var init_branch = __esm(() => {
|
|
|
38876
39188
|
});
|
|
38877
39189
|
|
|
38878
39190
|
// src/hooks/knowledge-store.ts
|
|
38879
|
-
import { existsSync as
|
|
39191
|
+
import { existsSync as existsSync8 } from "fs";
|
|
38880
39192
|
import { appendFile as appendFile3, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
38881
39193
|
import * as os3 from "os";
|
|
38882
39194
|
import * as path13 from "path";
|
|
@@ -38904,7 +39216,7 @@ function resolveHiveRejectedPath() {
|
|
|
38904
39216
|
return path13.join(path13.dirname(hivePath), "shared-learnings-rejected.jsonl");
|
|
38905
39217
|
}
|
|
38906
39218
|
async function readKnowledge(filePath) {
|
|
38907
|
-
if (!
|
|
39219
|
+
if (!existsSync8(filePath))
|
|
38908
39220
|
return [];
|
|
38909
39221
|
const content = await readFile3(filePath, "utf-8");
|
|
38910
39222
|
const results = [];
|
|
@@ -38935,7 +39247,8 @@ async function rewriteKnowledge(filePath, entries) {
|
|
|
38935
39247
|
let release = null;
|
|
38936
39248
|
try {
|
|
38937
39249
|
release = await import_proper_lockfile2.default.lock(dir, {
|
|
38938
|
-
retries: { retries:
|
|
39250
|
+
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
39251
|
+
stale: 5000
|
|
38939
39252
|
});
|
|
38940
39253
|
const content = entries.map((e) => JSON.stringify(e)).join(`
|
|
38941
39254
|
`) + (entries.length > 0 ? `
|
|
@@ -38956,6 +39269,103 @@ async function enforceKnowledgeCap(filePath, maxEntries) {
|
|
|
38956
39269
|
await rewriteKnowledge(filePath, trimmed);
|
|
38957
39270
|
}
|
|
38958
39271
|
}
|
|
39272
|
+
async function sweepAgedEntries(filePath, defaultMaxPhases) {
|
|
39273
|
+
let release = null;
|
|
39274
|
+
try {
|
|
39275
|
+
const dir = path13.dirname(filePath);
|
|
39276
|
+
await mkdir2(dir, { recursive: true });
|
|
39277
|
+
release = await import_proper_lockfile2.default.lock(dir, {
|
|
39278
|
+
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
39279
|
+
stale: 5000
|
|
39280
|
+
});
|
|
39281
|
+
const entries = await readKnowledge(filePath);
|
|
39282
|
+
const result = {
|
|
39283
|
+
scanned: entries.length,
|
|
39284
|
+
aged: 0,
|
|
39285
|
+
archived: 0,
|
|
39286
|
+
removed: 0,
|
|
39287
|
+
skipped_promoted: 0
|
|
39288
|
+
};
|
|
39289
|
+
if (entries.length === 0)
|
|
39290
|
+
return result;
|
|
39291
|
+
const now = new Date().toISOString();
|
|
39292
|
+
let mutated = false;
|
|
39293
|
+
for (const entry of entries) {
|
|
39294
|
+
if (entry.status === "archived")
|
|
39295
|
+
continue;
|
|
39296
|
+
if (entry.status === "promoted") {
|
|
39297
|
+
result.skipped_promoted++;
|
|
39298
|
+
continue;
|
|
39299
|
+
}
|
|
39300
|
+
entry.phases_alive = (entry.phases_alive ?? 0) + 1;
|
|
39301
|
+
result.aged++;
|
|
39302
|
+
mutated = true;
|
|
39303
|
+
const ttl = entry.max_phases ?? defaultMaxPhases;
|
|
39304
|
+
if (entry.phases_alive > ttl) {
|
|
39305
|
+
entry.status = "archived";
|
|
39306
|
+
entry.updated_at = now;
|
|
39307
|
+
result.archived++;
|
|
39308
|
+
}
|
|
39309
|
+
}
|
|
39310
|
+
if (mutated) {
|
|
39311
|
+
const content = entries.map((e) => JSON.stringify(e)).join(`
|
|
39312
|
+
`) + (entries.length > 0 ? `
|
|
39313
|
+
` : "");
|
|
39314
|
+
await writeFile2(filePath, content, "utf-8");
|
|
39315
|
+
}
|
|
39316
|
+
return result;
|
|
39317
|
+
} finally {
|
|
39318
|
+
if (release) {
|
|
39319
|
+
try {
|
|
39320
|
+
await release();
|
|
39321
|
+
} catch {}
|
|
39322
|
+
}
|
|
39323
|
+
}
|
|
39324
|
+
}
|
|
39325
|
+
async function sweepStaleTodos(filePath, todoMaxPhases) {
|
|
39326
|
+
let release = null;
|
|
39327
|
+
try {
|
|
39328
|
+
const dir = path13.dirname(filePath);
|
|
39329
|
+
await mkdir2(dir, { recursive: true });
|
|
39330
|
+
release = await import_proper_lockfile2.default.lock(dir, {
|
|
39331
|
+
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
39332
|
+
stale: 5000
|
|
39333
|
+
});
|
|
39334
|
+
const entries = await readKnowledge(filePath);
|
|
39335
|
+
const result = {
|
|
39336
|
+
scanned: entries.length,
|
|
39337
|
+
aged: 0,
|
|
39338
|
+
archived: 0,
|
|
39339
|
+
removed: 0,
|
|
39340
|
+
skipped_promoted: 0
|
|
39341
|
+
};
|
|
39342
|
+
if (entries.length === 0)
|
|
39343
|
+
return result;
|
|
39344
|
+
const kept = entries.filter((e) => {
|
|
39345
|
+
if (e.category !== "todo" || e.status === "promoted")
|
|
39346
|
+
return true;
|
|
39347
|
+
const age = e.phases_alive ?? 0;
|
|
39348
|
+
if (age > todoMaxPhases) {
|
|
39349
|
+
result.removed++;
|
|
39350
|
+
return false;
|
|
39351
|
+
}
|
|
39352
|
+
return true;
|
|
39353
|
+
});
|
|
39354
|
+
if (result.removed > 0) {
|
|
39355
|
+
const content = kept.map((e) => JSON.stringify(e)).join(`
|
|
39356
|
+
`) + (kept.length > 0 ? `
|
|
39357
|
+
` : "");
|
|
39358
|
+
await writeFile2(filePath, content, "utf-8");
|
|
39359
|
+
}
|
|
39360
|
+
return result;
|
|
39361
|
+
} finally {
|
|
39362
|
+
if (release) {
|
|
39363
|
+
try {
|
|
39364
|
+
await release();
|
|
39365
|
+
} catch {}
|
|
39366
|
+
}
|
|
39367
|
+
}
|
|
39368
|
+
}
|
|
38959
39369
|
async function appendRejectedLesson(directory, lesson) {
|
|
38960
39370
|
const filePath = resolveSwarmRejectedPath(directory);
|
|
38961
39371
|
const existing = await readRejectedLessons(directory);
|
|
@@ -39004,6 +39414,8 @@ function computeConfidence(confirmedByCount, autoGenerated) {
|
|
|
39004
39414
|
function inferTags(lesson) {
|
|
39005
39415
|
const lower = lesson.toLowerCase();
|
|
39006
39416
|
const tags = [];
|
|
39417
|
+
if (/(^|\s)(?:todo|remember|don't?(?:\s+)?forget)(?:\s|:|,|$)/i.test(lesson))
|
|
39418
|
+
tags.push("todo");
|
|
39007
39419
|
if (/\b(?:typescript|ts)\b/.test(lower))
|
|
39008
39420
|
tags.push("typescript");
|
|
39009
39421
|
if (/\b(?:javascript|js)\b/.test(lower))
|
|
@@ -39040,7 +39452,7 @@ var init_knowledge_store = __esm(() => {
|
|
|
39040
39452
|
});
|
|
39041
39453
|
|
|
39042
39454
|
// src/hooks/knowledge-reader.ts
|
|
39043
|
-
import { existsSync as
|
|
39455
|
+
import { existsSync as existsSync9 } from "fs";
|
|
39044
39456
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
39045
39457
|
import * as path14 from "path";
|
|
39046
39458
|
function inferCategoriesFromPhase(phaseDescription) {
|
|
@@ -39090,7 +39502,7 @@ async function recordLessonsShown(directory, lessonIds, currentPhase) {
|
|
|
39090
39502
|
const shownFile = path14.join(directory, ".swarm", ".knowledge-shown.json");
|
|
39091
39503
|
try {
|
|
39092
39504
|
let shownData = {};
|
|
39093
|
-
if (
|
|
39505
|
+
if (existsSync9(shownFile)) {
|
|
39094
39506
|
const content = await readFile4(shownFile, "utf-8");
|
|
39095
39507
|
shownData = JSON.parse(content);
|
|
39096
39508
|
}
|
|
@@ -39142,7 +39554,7 @@ async function readMergedKnowledge(directory, config3, context) {
|
|
|
39142
39554
|
});
|
|
39143
39555
|
}
|
|
39144
39556
|
const scopeFilter = config3.scope_filter ?? ["global"];
|
|
39145
|
-
const filtered = merged.filter((entry) => scopeFilter.some((pattern) => (entry.scope ?? "global") === pattern));
|
|
39557
|
+
const filtered = merged.filter((entry) => scopeFilter.some((pattern) => (entry.scope ?? "global") === pattern) && entry.status !== "archived");
|
|
39146
39558
|
const ranked = filtered.map((entry) => {
|
|
39147
39559
|
let categoryScore = 0;
|
|
39148
39560
|
if (context?.currentPhase) {
|
|
@@ -39196,7 +39608,7 @@ async function readMergedKnowledge(directory, config3, context) {
|
|
|
39196
39608
|
async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
|
|
39197
39609
|
const shownFile = path14.join(directory, ".swarm", ".knowledge-shown.json");
|
|
39198
39610
|
try {
|
|
39199
|
-
if (!
|
|
39611
|
+
if (!existsSync9(shownFile)) {
|
|
39200
39612
|
return;
|
|
39201
39613
|
}
|
|
39202
39614
|
const content = await readFile4(shownFile, "utf-8");
|
|
@@ -39562,6 +39974,7 @@ var init_knowledge_validator = __esm(() => {
|
|
|
39562
39974
|
"debugging",
|
|
39563
39975
|
"performance",
|
|
39564
39976
|
"integration",
|
|
39977
|
+
"todo",
|
|
39565
39978
|
"other"
|
|
39566
39979
|
]);
|
|
39567
39980
|
TECH_REFERENCE_WORDS = new Set([
|
|
@@ -39759,7 +40172,8 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
39759
40172
|
["debugging", "debugging"],
|
|
39760
40173
|
["performance", "performance"],
|
|
39761
40174
|
["integration", "integration"],
|
|
39762
|
-
["other", "other"]
|
|
40175
|
+
["other", "other"],
|
|
40176
|
+
["todo", "todo"]
|
|
39763
40177
|
]);
|
|
39764
40178
|
for (const lesson of lessons) {
|
|
39765
40179
|
const tags = inferTags(lesson);
|
|
@@ -39967,7 +40381,7 @@ var init_checkpoint3 = __esm(() => {
|
|
|
39967
40381
|
});
|
|
39968
40382
|
|
|
39969
40383
|
// src/session/snapshot-writer.ts
|
|
39970
|
-
import { mkdirSync as
|
|
40384
|
+
import { mkdirSync as mkdirSync8, renameSync as renameSync7 } from "fs";
|
|
39971
40385
|
import * as path17 from "path";
|
|
39972
40386
|
function serializeAgentSession(s) {
|
|
39973
40387
|
const gateLog = {};
|
|
@@ -40058,7 +40472,7 @@ async function writeSnapshot(directory, state) {
|
|
|
40058
40472
|
const content = JSON.stringify(snapshot, null, 2);
|
|
40059
40473
|
const resolvedPath = validateSwarmPath(directory, "session/state.json");
|
|
40060
40474
|
const dir = path17.dirname(resolvedPath);
|
|
40061
|
-
|
|
40475
|
+
mkdirSync8(dir, { recursive: true });
|
|
40062
40476
|
const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
40063
40477
|
await Bun.write(tempPath, content);
|
|
40064
40478
|
renameSync7(tempPath, resolvedPath);
|
|
@@ -42594,7 +43008,7 @@ var init_dark_matter = __esm(() => {
|
|
|
42594
43008
|
|
|
42595
43009
|
// src/services/diagnose-service.ts
|
|
42596
43010
|
import * as child_process4 from "child_process";
|
|
42597
|
-
import { existsSync as
|
|
43011
|
+
import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
42598
43012
|
import path23 from "path";
|
|
42599
43013
|
import { fileURLToPath } from "url";
|
|
42600
43014
|
function validateTaskDag(plan) {
|
|
@@ -42828,7 +43242,7 @@ async function checkConfigBackups(directory) {
|
|
|
42828
43242
|
}
|
|
42829
43243
|
async function checkGitRepository(directory) {
|
|
42830
43244
|
try {
|
|
42831
|
-
if (!
|
|
43245
|
+
if (!existsSync10(directory) || !statSync5(directory).isDirectory()) {
|
|
42832
43246
|
return {
|
|
42833
43247
|
name: "Git Repository",
|
|
42834
43248
|
status: "\u274C",
|
|
@@ -42893,7 +43307,7 @@ async function checkSpecStaleness(directory, plan) {
|
|
|
42893
43307
|
}
|
|
42894
43308
|
async function checkConfigParseability(directory) {
|
|
42895
43309
|
const configPath = path23.join(directory, ".opencode/opencode-swarm.json");
|
|
42896
|
-
if (!
|
|
43310
|
+
if (!existsSync10(configPath)) {
|
|
42897
43311
|
return {
|
|
42898
43312
|
name: "Config Parseability",
|
|
42899
43313
|
status: "\u2705",
|
|
@@ -42943,11 +43357,11 @@ async function checkGrammarWasmFiles() {
|
|
|
42943
43357
|
const isSource = thisDir.replace(/\\/g, "/").endsWith("/src/services");
|
|
42944
43358
|
const grammarDir = isSource ? path23.join(thisDir, "..", "lang", "grammars") : path23.join(thisDir, "lang", "grammars");
|
|
42945
43359
|
const missing = [];
|
|
42946
|
-
if (!
|
|
43360
|
+
if (!existsSync10(path23.join(grammarDir, "tree-sitter.wasm"))) {
|
|
42947
43361
|
missing.push("tree-sitter.wasm (core runtime)");
|
|
42948
43362
|
}
|
|
42949
43363
|
for (const file3 of grammarFiles) {
|
|
42950
|
-
if (!
|
|
43364
|
+
if (!existsSync10(path23.join(grammarDir, file3))) {
|
|
42951
43365
|
missing.push(file3);
|
|
42952
43366
|
}
|
|
42953
43367
|
}
|
|
@@ -42966,7 +43380,7 @@ async function checkGrammarWasmFiles() {
|
|
|
42966
43380
|
}
|
|
42967
43381
|
async function checkCheckpointManifest(directory) {
|
|
42968
43382
|
const manifestPath = path23.join(directory, ".swarm/checkpoints.json");
|
|
42969
|
-
if (!
|
|
43383
|
+
if (!existsSync10(manifestPath)) {
|
|
42970
43384
|
return {
|
|
42971
43385
|
name: "Checkpoint Manifest",
|
|
42972
43386
|
status: "\u2705",
|
|
@@ -43018,7 +43432,7 @@ async function checkCheckpointManifest(directory) {
|
|
|
43018
43432
|
}
|
|
43019
43433
|
async function checkEventStreamIntegrity(directory) {
|
|
43020
43434
|
const eventsPath = path23.join(directory, ".swarm/events.jsonl");
|
|
43021
|
-
if (!
|
|
43435
|
+
if (!existsSync10(eventsPath)) {
|
|
43022
43436
|
return {
|
|
43023
43437
|
name: "Event Stream",
|
|
43024
43438
|
status: "\u2705",
|
|
@@ -43059,7 +43473,7 @@ async function checkEventStreamIntegrity(directory) {
|
|
|
43059
43473
|
}
|
|
43060
43474
|
async function checkSteeringDirectives(directory) {
|
|
43061
43475
|
const eventsPath = path23.join(directory, ".swarm/events.jsonl");
|
|
43062
|
-
if (!
|
|
43476
|
+
if (!existsSync10(eventsPath)) {
|
|
43063
43477
|
return {
|
|
43064
43478
|
name: "Steering Directives",
|
|
43065
43479
|
status: "\u2705",
|
|
@@ -43115,7 +43529,7 @@ async function checkCurator(directory) {
|
|
|
43115
43529
|
};
|
|
43116
43530
|
}
|
|
43117
43531
|
const summaryPath = path23.join(directory, ".swarm/curator-summary.json");
|
|
43118
|
-
if (!
|
|
43532
|
+
if (!existsSync10(summaryPath)) {
|
|
43119
43533
|
return {
|
|
43120
43534
|
name: "Curator",
|
|
43121
43535
|
status: "\u2705",
|
|
@@ -43263,7 +43677,7 @@ async function getDiagnoseData(directory) {
|
|
|
43263
43677
|
checks5.push(await checkCurator(directory));
|
|
43264
43678
|
try {
|
|
43265
43679
|
const evidenceDir = path23.join(directory, ".swarm", "evidence");
|
|
43266
|
-
const snapshotFiles =
|
|
43680
|
+
const snapshotFiles = existsSync10(evidenceDir) ? readdirSync3(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
|
|
43267
43681
|
if (snapshotFiles.length > 0) {
|
|
43268
43682
|
const latest = snapshotFiles.sort().pop();
|
|
43269
43683
|
checks5.push({
|
|
@@ -44928,7 +45342,7 @@ var init_profiles = __esm(() => {
|
|
|
44928
45342
|
|
|
44929
45343
|
// src/lang/detector.ts
|
|
44930
45344
|
import { access as access2, readdir as readdir3 } from "fs/promises";
|
|
44931
|
-
import { extname as extname2, join as
|
|
45345
|
+
import { extname as extname2, join as join22 } from "path";
|
|
44932
45346
|
function getProfileForFile(filePath) {
|
|
44933
45347
|
const ext = extname2(filePath);
|
|
44934
45348
|
if (!ext)
|
|
@@ -44950,7 +45364,7 @@ async function detectProjectLanguages(projectDir) {
|
|
|
44950
45364
|
if (detectFile.includes("*") || detectFile.includes("?"))
|
|
44951
45365
|
continue;
|
|
44952
45366
|
try {
|
|
44953
|
-
await access2(
|
|
45367
|
+
await access2(join22(dir, detectFile));
|
|
44954
45368
|
detected.add(profile.id);
|
|
44955
45369
|
break;
|
|
44956
45370
|
} catch {}
|
|
@@ -44971,7 +45385,7 @@ async function detectProjectLanguages(projectDir) {
|
|
|
44971
45385
|
const topEntries = await readdir3(projectDir, { withFileTypes: true });
|
|
44972
45386
|
for (const entry of topEntries) {
|
|
44973
45387
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
44974
|
-
await scanDir(
|
|
45388
|
+
await scanDir(join22(projectDir, entry.name));
|
|
44975
45389
|
}
|
|
44976
45390
|
}
|
|
44977
45391
|
} catch {}
|
|
@@ -46658,14 +47072,14 @@ var init_history = __esm(() => {
|
|
|
46658
47072
|
|
|
46659
47073
|
// src/hooks/knowledge-migrator.ts
|
|
46660
47074
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
46661
|
-
import { existsSync as
|
|
47075
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
46662
47076
|
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
46663
47077
|
import * as path27 from "path";
|
|
46664
47078
|
async function migrateContextToKnowledge(directory, config3) {
|
|
46665
47079
|
const sentinelPath = path27.join(directory, ".swarm", ".knowledge-migrated");
|
|
46666
47080
|
const contextPath = path27.join(directory, ".swarm", "context.md");
|
|
46667
47081
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
46668
|
-
if (
|
|
47082
|
+
if (existsSync14(sentinelPath)) {
|
|
46669
47083
|
return {
|
|
46670
47084
|
migrated: false,
|
|
46671
47085
|
entriesMigrated: 0,
|
|
@@ -46674,7 +47088,7 @@ async function migrateContextToKnowledge(directory, config3) {
|
|
|
46674
47088
|
skippedReason: "sentinel-exists"
|
|
46675
47089
|
};
|
|
46676
47090
|
}
|
|
46677
|
-
if (!
|
|
47091
|
+
if (!existsSync14(contextPath)) {
|
|
46678
47092
|
return {
|
|
46679
47093
|
migrated: false,
|
|
46680
47094
|
entriesMigrated: 0,
|
|
@@ -46860,7 +47274,7 @@ function truncateLesson(text) {
|
|
|
46860
47274
|
}
|
|
46861
47275
|
function inferProjectName(directory) {
|
|
46862
47276
|
const packageJsonPath = path27.join(directory, "package.json");
|
|
46863
|
-
if (
|
|
47277
|
+
if (existsSync14(packageJsonPath)) {
|
|
46864
47278
|
try {
|
|
46865
47279
|
const pkg = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
|
|
46866
47280
|
if (pkg.name && typeof pkg.name === "string") {
|
|
@@ -47428,7 +47842,7 @@ async function _detectAvailableLinter(_projectDir, biomeBin, eslintBin) {
|
|
|
47428
47842
|
stderr: "pipe"
|
|
47429
47843
|
});
|
|
47430
47844
|
const biomeExit = biomeProc.exited;
|
|
47431
|
-
const timeout = new Promise((
|
|
47845
|
+
const timeout = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), DETECT_TIMEOUT));
|
|
47432
47846
|
const result = await Promise.race([biomeExit, timeout]);
|
|
47433
47847
|
if (result === "timeout") {
|
|
47434
47848
|
biomeProc.kill();
|
|
@@ -47442,7 +47856,7 @@ async function _detectAvailableLinter(_projectDir, biomeBin, eslintBin) {
|
|
|
47442
47856
|
stderr: "pipe"
|
|
47443
47857
|
});
|
|
47444
47858
|
const eslintExit = eslintProc.exited;
|
|
47445
|
-
const timeout = new Promise((
|
|
47859
|
+
const timeout = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), DETECT_TIMEOUT));
|
|
47446
47860
|
const result = await Promise.race([eslintExit, timeout]);
|
|
47447
47861
|
if (result === "timeout") {
|
|
47448
47862
|
eslintProc.kill();
|
|
@@ -48825,15 +49239,15 @@ function appendTestRun(record3, workingDir) {
|
|
|
48825
49239
|
prunedRecords.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
48826
49240
|
try {
|
|
48827
49241
|
const lines = prunedRecords.map((rec) => JSON.stringify(rec));
|
|
48828
|
-
const content = lines.join(`
|
|
48829
|
-
`)
|
|
49242
|
+
const content = `${lines.join(`
|
|
49243
|
+
`)}
|
|
48830
49244
|
`;
|
|
48831
|
-
const tempPath = historyPath
|
|
49245
|
+
const tempPath = `${historyPath}.tmp`;
|
|
48832
49246
|
fs21.writeFileSync(tempPath, content, "utf-8");
|
|
48833
49247
|
fs21.renameSync(tempPath, historyPath);
|
|
48834
49248
|
} catch (err2) {
|
|
48835
49249
|
try {
|
|
48836
|
-
const tempPath = historyPath
|
|
49250
|
+
const tempPath = `${historyPath}.tmp`;
|
|
48837
49251
|
if (fs21.existsSync(tempPath)) {
|
|
48838
49252
|
fs21.unlinkSync(tempPath);
|
|
48839
49253
|
}
|
|
@@ -49641,9 +50055,9 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
|
|
|
49641
50055
|
stderr: "pipe",
|
|
49642
50056
|
cwd
|
|
49643
50057
|
});
|
|
49644
|
-
const timeoutPromise = new Promise((
|
|
50058
|
+
const timeoutPromise = new Promise((resolve13) => setTimeout(() => {
|
|
49645
50059
|
proc.kill();
|
|
49646
|
-
|
|
50060
|
+
resolve13(-1);
|
|
49647
50061
|
}, timeout_ms));
|
|
49648
50062
|
const [exitCode, stdoutResult, stderrResult] = await Promise.all([
|
|
49649
50063
|
Promise.race([proc.exited, timeoutPromise]),
|
|
@@ -50922,220 +51336,6 @@ var init_promote = __esm(() => {
|
|
|
50922
51336
|
init_hive_promoter2();
|
|
50923
51337
|
});
|
|
50924
51338
|
|
|
50925
|
-
// src/db/project-db.ts
|
|
50926
|
-
import { Database } from "bun:sqlite";
|
|
50927
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync11 } from "fs";
|
|
50928
|
-
import { join as join30, resolve as resolve13 } from "path";
|
|
50929
|
-
function runProjectMigrations(db) {
|
|
50930
|
-
db.run(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
50931
|
-
version INTEGER PRIMARY KEY,
|
|
50932
|
-
name TEXT NOT NULL,
|
|
50933
|
-
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
50934
|
-
)`);
|
|
50935
|
-
const row = db.query("SELECT MAX(version) as version FROM schema_migrations").get();
|
|
50936
|
-
const currentVersion = row?.version ?? 0;
|
|
50937
|
-
for (const migration of MIGRATIONS) {
|
|
50938
|
-
if (migration.version <= currentVersion)
|
|
50939
|
-
continue;
|
|
50940
|
-
const apply = db.transaction(() => {
|
|
50941
|
-
db.run(migration.sql);
|
|
50942
|
-
db.run("INSERT INTO schema_migrations (version, name) VALUES (?, ?)", [
|
|
50943
|
-
migration.version,
|
|
50944
|
-
migration.name
|
|
50945
|
-
]);
|
|
50946
|
-
});
|
|
50947
|
-
apply();
|
|
50948
|
-
}
|
|
50949
|
-
}
|
|
50950
|
-
function projectDbPath(directory) {
|
|
50951
|
-
return join30(resolve13(directory), ".swarm", "swarm.db");
|
|
50952
|
-
}
|
|
50953
|
-
function projectDbExists(directory) {
|
|
50954
|
-
return existsSync19(projectDbPath(directory));
|
|
50955
|
-
}
|
|
50956
|
-
function getProjectDb(directory) {
|
|
50957
|
-
const key = resolve13(directory);
|
|
50958
|
-
const existing = _projectDbs.get(key);
|
|
50959
|
-
if (existing)
|
|
50960
|
-
return existing;
|
|
50961
|
-
const swarmDir = join30(key, ".swarm");
|
|
50962
|
-
mkdirSync11(swarmDir, { recursive: true });
|
|
50963
|
-
const db = new Database(join30(swarmDir, "swarm.db"));
|
|
50964
|
-
db.run("PRAGMA journal_mode = WAL;");
|
|
50965
|
-
db.run("PRAGMA synchronous = NORMAL;");
|
|
50966
|
-
db.run("PRAGMA busy_timeout = 5000;");
|
|
50967
|
-
db.run("PRAGMA foreign_keys = ON;");
|
|
50968
|
-
runProjectMigrations(db);
|
|
50969
|
-
_projectDbs.set(key, db);
|
|
50970
|
-
return db;
|
|
50971
|
-
}
|
|
50972
|
-
var MIGRATIONS, _projectDbs;
|
|
50973
|
-
var init_project_db = __esm(() => {
|
|
50974
|
-
MIGRATIONS = [
|
|
50975
|
-
{
|
|
50976
|
-
version: 1,
|
|
50977
|
-
name: "create_project_constraints",
|
|
50978
|
-
sql: `CREATE TABLE project_constraints (
|
|
50979
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50980
|
-
constraint_type TEXT NOT NULL,
|
|
50981
|
-
content TEXT NOT NULL,
|
|
50982
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
50983
|
-
)`
|
|
50984
|
-
},
|
|
50985
|
-
{
|
|
50986
|
-
version: 2,
|
|
50987
|
-
name: "create_qa_gate_profile",
|
|
50988
|
-
sql: `CREATE TABLE qa_gate_profile (
|
|
50989
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50990
|
-
plan_id TEXT NOT NULL UNIQUE,
|
|
50991
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
50992
|
-
project_type TEXT,
|
|
50993
|
-
gates TEXT NOT NULL DEFAULT '{}',
|
|
50994
|
-
locked_at TEXT,
|
|
50995
|
-
locked_by_snapshot_seq INTEGER
|
|
50996
|
-
)`
|
|
50997
|
-
},
|
|
50998
|
-
{
|
|
50999
|
-
version: 3,
|
|
51000
|
-
name: "create_qa_gate_profile_immutability_trigger",
|
|
51001
|
-
sql: `CREATE TRIGGER IF NOT EXISTS trg_qa_gate_profile_no_update_after_lock
|
|
51002
|
-
BEFORE UPDATE ON qa_gate_profile
|
|
51003
|
-
WHEN OLD.locked_at IS NOT NULL
|
|
51004
|
-
BEGIN
|
|
51005
|
-
SELECT RAISE(ABORT, 'qa_gate_profile row is locked and cannot be modified after critic approval');
|
|
51006
|
-
END`
|
|
51007
|
-
}
|
|
51008
|
-
];
|
|
51009
|
-
_projectDbs = new Map;
|
|
51010
|
-
});
|
|
51011
|
-
|
|
51012
|
-
// src/db/qa-gate-profile.ts
|
|
51013
|
-
import { createHash as createHash4 } from "crypto";
|
|
51014
|
-
function rowToProfile(row) {
|
|
51015
|
-
let parsed = {};
|
|
51016
|
-
try {
|
|
51017
|
-
parsed = JSON.parse(row.gates);
|
|
51018
|
-
} catch {
|
|
51019
|
-
parsed = {};
|
|
51020
|
-
}
|
|
51021
|
-
const gates = { ...DEFAULT_QA_GATES, ...parsed };
|
|
51022
|
-
return {
|
|
51023
|
-
id: row.id,
|
|
51024
|
-
plan_id: row.plan_id,
|
|
51025
|
-
created_at: row.created_at,
|
|
51026
|
-
project_type: row.project_type,
|
|
51027
|
-
gates,
|
|
51028
|
-
locked_at: row.locked_at,
|
|
51029
|
-
locked_by_snapshot_seq: row.locked_by_snapshot_seq
|
|
51030
|
-
};
|
|
51031
|
-
}
|
|
51032
|
-
function getProfile(directory, planId) {
|
|
51033
|
-
if (!projectDbExists(directory))
|
|
51034
|
-
return null;
|
|
51035
|
-
const db = getProjectDb(directory);
|
|
51036
|
-
const row = db.query("SELECT * FROM qa_gate_profile WHERE plan_id = ?").get(planId);
|
|
51037
|
-
return row ? rowToProfile(row) : null;
|
|
51038
|
-
}
|
|
51039
|
-
function getOrCreateProfile(directory, planId, projectType) {
|
|
51040
|
-
const existing = getProfile(directory, planId);
|
|
51041
|
-
if (existing)
|
|
51042
|
-
return existing;
|
|
51043
|
-
const db = getProjectDb(directory);
|
|
51044
|
-
const gatesJson = JSON.stringify(DEFAULT_QA_GATES);
|
|
51045
|
-
const insert = db.transaction(() => {
|
|
51046
|
-
db.run("INSERT INTO qa_gate_profile (plan_id, project_type, gates) VALUES (?, ?, ?)", [planId, projectType ?? null, gatesJson]);
|
|
51047
|
-
});
|
|
51048
|
-
try {
|
|
51049
|
-
insert();
|
|
51050
|
-
} catch (err2) {
|
|
51051
|
-
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
51052
|
-
if (!msg.toLowerCase().includes("unique")) {
|
|
51053
|
-
throw err2;
|
|
51054
|
-
}
|
|
51055
|
-
}
|
|
51056
|
-
const after = getProfile(directory, planId);
|
|
51057
|
-
if (!after) {
|
|
51058
|
-
throw new Error(`Failed to create or load QA gate profile for plan_id=${planId}`);
|
|
51059
|
-
}
|
|
51060
|
-
return after;
|
|
51061
|
-
}
|
|
51062
|
-
function setGates(directory, planId, gates) {
|
|
51063
|
-
const current = getProfile(directory, planId);
|
|
51064
|
-
if (!current) {
|
|
51065
|
-
throw new Error(`No QA gate profile found for plan_id=${planId} \u2014 call getOrCreateProfile first`);
|
|
51066
|
-
}
|
|
51067
|
-
if (current.locked_at !== null) {
|
|
51068
|
-
throw new Error("Cannot modify gates: QA gate profile is locked after critic approval");
|
|
51069
|
-
}
|
|
51070
|
-
const merged = { ...current.gates };
|
|
51071
|
-
for (const key of Object.keys(gates)) {
|
|
51072
|
-
const incoming = gates[key];
|
|
51073
|
-
if (incoming === undefined)
|
|
51074
|
-
continue;
|
|
51075
|
-
if (incoming === false && current.gates[key] === true) {
|
|
51076
|
-
throw new Error(`Cannot disable gate '${key}': sessions can only ratchet tighter`);
|
|
51077
|
-
}
|
|
51078
|
-
if (incoming === true) {
|
|
51079
|
-
merged[key] = true;
|
|
51080
|
-
}
|
|
51081
|
-
}
|
|
51082
|
-
const db = getProjectDb(directory);
|
|
51083
|
-
db.run("UPDATE qa_gate_profile SET gates = ? WHERE plan_id = ?", [
|
|
51084
|
-
JSON.stringify(merged),
|
|
51085
|
-
planId
|
|
51086
|
-
]);
|
|
51087
|
-
const updated = getProfile(directory, planId);
|
|
51088
|
-
if (!updated) {
|
|
51089
|
-
throw new Error(`Failed to re-read QA gate profile after update for plan_id=${planId}`);
|
|
51090
|
-
}
|
|
51091
|
-
return updated;
|
|
51092
|
-
}
|
|
51093
|
-
function lockProfile(directory, planId, snapshotSeq) {
|
|
51094
|
-
const current = getProfile(directory, planId);
|
|
51095
|
-
if (!current) {
|
|
51096
|
-
throw new Error(`No QA gate profile found for plan_id=${planId} \u2014 cannot lock`);
|
|
51097
|
-
}
|
|
51098
|
-
if (current.locked_at !== null) {
|
|
51099
|
-
return current;
|
|
51100
|
-
}
|
|
51101
|
-
const db = getProjectDb(directory);
|
|
51102
|
-
db.run("UPDATE qa_gate_profile SET locked_at = datetime('now'), locked_by_snapshot_seq = ? WHERE plan_id = ?", [snapshotSeq, planId]);
|
|
51103
|
-
const locked = getProfile(directory, planId);
|
|
51104
|
-
if (!locked) {
|
|
51105
|
-
throw new Error(`Failed to re-read locked QA gate profile for plan_id=${planId}`);
|
|
51106
|
-
}
|
|
51107
|
-
return locked;
|
|
51108
|
-
}
|
|
51109
|
-
function computeProfileHash(profile) {
|
|
51110
|
-
const payload = JSON.stringify({
|
|
51111
|
-
plan_id: profile.plan_id,
|
|
51112
|
-
gates: profile.gates
|
|
51113
|
-
});
|
|
51114
|
-
return createHash4("sha256").update(payload).digest("hex");
|
|
51115
|
-
}
|
|
51116
|
-
function getEffectiveGates(profile, sessionOverrides) {
|
|
51117
|
-
const merged = { ...profile.gates };
|
|
51118
|
-
for (const key of Object.keys(sessionOverrides)) {
|
|
51119
|
-
if (sessionOverrides[key] === true) {
|
|
51120
|
-
merged[key] = true;
|
|
51121
|
-
}
|
|
51122
|
-
}
|
|
51123
|
-
return merged;
|
|
51124
|
-
}
|
|
51125
|
-
var DEFAULT_QA_GATES;
|
|
51126
|
-
var init_qa_gate_profile = __esm(() => {
|
|
51127
|
-
init_project_db();
|
|
51128
|
-
DEFAULT_QA_GATES = {
|
|
51129
|
-
reviewer: true,
|
|
51130
|
-
test_engineer: true,
|
|
51131
|
-
council_mode: false,
|
|
51132
|
-
sme_enabled: true,
|
|
51133
|
-
critic_pre_plan: true,
|
|
51134
|
-
hallucination_guard: false,
|
|
51135
|
-
sast_enabled: true
|
|
51136
|
-
};
|
|
51137
|
-
});
|
|
51138
|
-
|
|
51139
51339
|
// src/commands/qa-gates.ts
|
|
51140
51340
|
function derivePlanId(plan) {
|
|
51141
51341
|
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
@@ -53145,8 +53345,7 @@ function buildCouncilWorkflow(council) {
|
|
|
53145
53345
|
return `## Work Complete Council (when enabled)
|
|
53146
53346
|
|
|
53147
53347
|
When \`council.enabled\` is true, every task goes through a four-phase verification
|
|
53148
|
-
gate before advancing to \`complete\`.
|
|
53149
|
-
existing precheckbatch / reviewer / test_engineer gate sequence.
|
|
53348
|
+
gate before advancing to \`complete\`. When council is authoritative, this REPLACES Stage B (reviewer + test_engineer as standalone delegations). Stage A (precheckbatch) still runs as the pre-review gate; Phase 1 dispatch of reviewer and test_engineer is the sole review pass for this task.
|
|
53150
53349
|
|
|
53151
53350
|
### Phase 0 \u2014 Pre-declare criteria (at plan time, BEFORE dispatching the coder)
|
|
53152
53351
|
Call \`declare_council_criteria\` for each task with at least 3 concrete,
|
|
@@ -53201,15 +53400,32 @@ architect resolves any \`unresolvedConflicts\` in \`unifiedFeedbackMd\` BEFORE
|
|
|
53201
53400
|
sending it to the coder \u2014 the coder never sees contradictory instructions
|
|
53202
53401
|
from different members.`;
|
|
53203
53402
|
}
|
|
53204
|
-
function buildYourToolsList() {
|
|
53403
|
+
function buildYourToolsList(council) {
|
|
53205
53404
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
53206
53405
|
const sorted = [...tools].sort();
|
|
53207
|
-
|
|
53406
|
+
const filtered = council?.enabled === true ? sorted : sorted.filter((t) => t !== "convene_council" && t !== "declare_council_criteria");
|
|
53407
|
+
return `Task (delegation), ${filtered.join(", ")}.`;
|
|
53408
|
+
}
|
|
53409
|
+
function buildQaGateSelectionDialogue(modeLabel) {
|
|
53410
|
+
const leadIn = modeLabel === "BRAINSTORM" ? "Now ask the user which QA gates to enable for this plan \u2014 do not select on their behalf." : modeLabel === "SPECIFY" ? "Ask the user which QA gates to enable for this plan before suggesting the next step." : "No pending gate selection found in `.swarm/context.md`. Ask the user inline now.";
|
|
53411
|
+
return `${leadIn}
|
|
53412
|
+
|
|
53413
|
+
Present the seven gates with their defaults (DEFAULT_QA_GATES) as a single user-facing question. Offer the user a one-shot choice: accept defaults, or customize. The seven gates are:
|
|
53414
|
+
- reviewer (default: ON) \u2014 code review of coder output
|
|
53415
|
+
- test_engineer (default: ON) \u2014 test verification of coder output
|
|
53416
|
+
- sme_enabled (default: ON) \u2014 SME consultation during planning/clarification
|
|
53417
|
+
- critic_pre_plan (default: ON) \u2014 critic review before plan finalization
|
|
53418
|
+
- sast_enabled (default: ON) \u2014 static security scanning
|
|
53419
|
+
- council_mode (default: OFF) \u2014 multi-member council gate (recommended for high-impact architecture, public APIs, schema/data mutation, security-sensitive code)
|
|
53420
|
+
- hallucination_guard (default: OFF) \u2014 claim verification (recommended for claim-heavy or research-heavy work)
|
|
53421
|
+
|
|
53422
|
+
One question, one message, defaults pre-stated. Wait for the user's answer.`;
|
|
53208
53423
|
}
|
|
53209
|
-
function buildAvailableToolsList() {
|
|
53424
|
+
function buildAvailableToolsList(council) {
|
|
53210
53425
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
53211
53426
|
const sorted = [...tools].sort();
|
|
53212
|
-
|
|
53427
|
+
const filtered = council?.enabled === true ? sorted : sorted.filter((t) => t !== "convene_council" && t !== "declare_council_criteria");
|
|
53428
|
+
return filtered.map((t) => {
|
|
53213
53429
|
const desc = TOOL_DESCRIPTIONS[t];
|
|
53214
53430
|
return desc ? `${t} (${desc})` : t;
|
|
53215
53431
|
}).join(", ");
|
|
@@ -53350,7 +53566,8 @@ function createArchitectAgent(model, customPrompt, customAppendPrompt, adversari
|
|
|
53350
53566
|
|
|
53351
53567
|
${customAppendPrompt}`;
|
|
53352
53568
|
}
|
|
53353
|
-
prompt = prompt?.replace("{{YOUR_TOOLS}}", buildYourToolsList())?.replace("{{AVAILABLE_TOOLS}}", buildAvailableToolsList())?.replace("{{SLASH_COMMANDS}}", buildSlashCommandsList());
|
|
53569
|
+
prompt = prompt?.replace("{{YOUR_TOOLS}}", buildYourToolsList(council))?.replace("{{AVAILABLE_TOOLS}}", buildAvailableToolsList(council))?.replace("{{SLASH_COMMANDS}}", buildSlashCommandsList());
|
|
53570
|
+
prompt = prompt?.replace(/\{\{QA_GATE_DIALOGUE_SPECIFY\}\}/g, buildQaGateSelectionDialogue("SPECIFY"))?.replace(/\{\{QA_GATE_DIALOGUE_BRAINSTORM\}\}/g, buildQaGateSelectionDialogue("BRAINSTORM"))?.replace(/\{\{QA_GATE_DIALOGUE_PLAN\}\}/g, buildQaGateSelectionDialogue("PLAN"));
|
|
53354
53571
|
const councilBlock = buildCouncilWorkflow(council);
|
|
53355
53572
|
const hasPlaceholder = prompt?.includes("{{COUNCIL_WORKFLOW}}") === true;
|
|
53356
53573
|
if (councilBlock === "") {
|
|
@@ -53468,6 +53685,7 @@ If a tool modifies a file, it is a CODER tool. Delegate.
|
|
|
53468
53685
|
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
53469
53686
|
2. ONE agent per message. Send, STOP, wait for response.
|
|
53470
53687
|
3. ONE task per {{AGENT_PREFIX}}coder call. Never batch.
|
|
53688
|
+
3a. PRE-DELEGATION SCOPE CALL (required): BEFORE every {{AGENT_PREFIX}}coder delegation, you MUST call \`declare_scope\` with { taskId, files } listing the exact file(s) this task will modify (including generated/lockfile paths). No \`declare_scope\` call \u2192 no coder delegation. See Rule 1a.
|
|
53471
53689
|
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
53472
53690
|
BATCHING DETECTION \u2014 you are batching if your coder delegation contains ANY of:
|
|
53473
53691
|
- The word "and" connecting two actions ("update X AND add Y")
|
|
@@ -53499,6 +53717,7 @@ Two small delegations with two QA gates > one large delegation with one QA gate.
|
|
|
53499
53717
|
- Print "Coder attempt [N/{{QA_RETRY_LIMIT}}] on task [X.Y]" at every retry
|
|
53500
53718
|
- Reaching {{QA_RETRY_LIMIT}}: escalate to user with full failure history before writing code yourself
|
|
53501
53719
|
If you catch yourself reaching for a code editing tool: STOP. Delegate to {{AGENT_PREFIX}}coder.
|
|
53720
|
+
REQUIRED before that delegation: call \`declare_scope\` first (Rule 1a). No exception for "trivial" one-liners.
|
|
53502
53721
|
Zero {{AGENT_PREFIX}}coder failures on this task = zero justification for self-coding.
|
|
53503
53722
|
Self-coding without {{QA_RETRY_LIMIT}} failures is a Rule 1 violation.
|
|
53504
53723
|
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
@@ -53576,6 +53795,8 @@ TIER 3 \u2014 CRITICAL
|
|
|
53576
53795
|
Pipeline: Full Stage A. Stage B = {{AGENT_PREFIX}}reviewer\xD72 + {{AGENT_PREFIX}}test_engineer\xD72.
|
|
53577
53796
|
Rationale: Security paths need adversarial review.
|
|
53578
53797
|
|
|
53798
|
+
If council is authoritative for the current plan, skip Stage B entries above and use council Phase 1 dispatch as the review pass.
|
|
53799
|
+
|
|
53579
53800
|
CLASSIFICATION RULES:
|
|
53580
53801
|
- Multi-tier \u2192 use HIGHEST tier.
|
|
53581
53802
|
- Format: "Classification: TIER {N} \u2014 {label}"
|
|
@@ -53596,10 +53817,12 @@ VERIFICATION PROTOCOL: After the coder reports DONE, and before running Stage B
|
|
|
53596
53817
|
|
|
53597
53818
|
\u2500\u2500 STAGE B: AGENT REVIEW GATES \u2500\u2500
|
|
53598
53819
|
{{AGENT_PREFIX}}reviewer \u2192 security reviewer (conditional) \u2192 {{AGENT_PREFIX}}test_engineer verification \u2192 {{AGENT_PREFIX}}test_engineer adversarial \u2192 coverage check
|
|
53599
|
-
Stage B
|
|
53820
|
+
Stage B runs by default for TIER 1-3 classifications. Stage A passing does not satisfy Stage B.
|
|
53600
53821
|
Stage B is where logic errors, security flaws, edge cases, and behavioral bugs are caught.
|
|
53601
53822
|
You MUST delegate to each Stage B agent and wait for their response.
|
|
53602
53823
|
|
|
53824
|
+
When council is authoritative for the current plan (\`pluginConfig.council.enabled === true\` AND \`QaGates.council_mode === true\`), Stage B is REPLACED by council Phase 1 \u2014 reviewer and test_engineer are dispatched as council members in the parallel Phase 1 fan-out, not as a separate Stage B sequence. Do not run Stage B a second time after the council has rendered a verdict. Stage A (precheckbatch) still runs as the pre-review gate in both modes.
|
|
53825
|
+
|
|
53603
53826
|
A task is complete ONLY when BOTH stages pass.
|
|
53604
53827
|
|
|
53605
53828
|
6f. **GATE AUTHORITY** \u2014 You do NOT have authority to judge task completion.
|
|
@@ -53699,6 +53922,7 @@ ANTI-RATIONALIZATION GATE \u2014 gates are mandatory for ALL changes, no excepti
|
|
|
53699
53922
|
- Target file is in: pages/, components/, views/, screens/, ui/, layouts/
|
|
53700
53923
|
If triggered: delegate to {{AGENT_PREFIX}}designer FIRST to produce a code scaffold. Then pass the scaffold to {{AGENT_PREFIX}}coder as INPUT alongside the task. The coder implements the TODOs in the scaffold without changing component structure or accessibility attributes.
|
|
53701
53924
|
If not triggered: delegate directly to {{AGENT_PREFIX}}coder as normal.
|
|
53925
|
+
In either branch (scaffold path or direct path), you MUST call \`declare_scope\` BEFORE the {{AGENT_PREFIX}}coder delegation. See Rule 1a.
|
|
53702
53926
|
10. **RETROSPECTIVE TRACKING**: At the end of every phase, record phase metrics in .swarm/context.md under "## Phase Metrics" and write a retrospective evidence entry via write_retro. Track: phase, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned (max 5). Reset Phase Metrics to 0 after writing.
|
|
53703
53927
|
11. **CHECKPOINTS**: Before delegating multi-file refactor tasks (3+ files), create a checkpoint save. On critical failures when redo is faster than iterative fixes, restore from checkpoint. Use checkpoint tool: \`checkpoint save\` before risky operations, \`checkpoint restore\` on failure.
|
|
53704
53928
|
|
|
@@ -53759,6 +53983,8 @@ DOMAIN: ios
|
|
|
53759
53983
|
INPUT: Building a SwiftUI app with offline-first sync
|
|
53760
53984
|
OUTPUT: Recommended patterns, frameworks, gotchas
|
|
53761
53985
|
|
|
53986
|
+
PRE-STEP (required): call \`declare_scope({ taskId, files })\` BEFORE writing any {{AGENT_PREFIX}}coder delegation. See Rule 1a.
|
|
53987
|
+
|
|
53762
53988
|
{{AGENT_PREFIX}}coder
|
|
53763
53989
|
TASK: Add input validation to login
|
|
53764
53990
|
FILE: src/auth/login.ts
|
|
@@ -53881,12 +54107,23 @@ MODE: BRAINSTORM runs seven phases in strict order. Do not skip phases. Do not c
|
|
|
53881
54107
|
- Write the final spec to \`.swarm/spec.md\`.
|
|
53882
54108
|
- Exit when reviewer signs off (or user explicitly accepts remaining disagreements).
|
|
53883
54109
|
|
|
53884
|
-
**Phase 6: QA GATE SELECTION (architect).**
|
|
53885
|
-
|
|
53886
|
-
|
|
53887
|
-
|
|
53888
|
-
|
|
53889
|
-
|
|
54110
|
+
**Phase 6: QA GATE SELECTION (architect, dialogue only).**
|
|
54111
|
+
{{QA_GATE_DIALOGUE_BRAINSTORM}}
|
|
54112
|
+
|
|
54113
|
+
Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this point. Once the user answers, write the elected gates to \`.swarm/context.md\` under a new section:
|
|
54114
|
+
\`\`\`
|
|
54115
|
+
## Pending QA Gate Selection
|
|
54116
|
+
- reviewer: <true|false>
|
|
54117
|
+
- test_engineer: <true|false>
|
|
54118
|
+
- sme_enabled: <true|false>
|
|
54119
|
+
- critic_pre_plan: <true|false>
|
|
54120
|
+
- sast_enabled: <true|false>
|
|
54121
|
+
- council_mode: <true|false>
|
|
54122
|
+
- hallucination_guard: <true|false>
|
|
54123
|
+
- recorded_at: <ISO timestamp>
|
|
54124
|
+
\`\`\`
|
|
54125
|
+
MODE: PLAN applies these after \`save_plan\` succeeds via \`set_qa_gates\`.
|
|
54126
|
+
- Exit with the elected gates recorded in \`.swarm/context.md\` (NOT yet persisted to plan.json).
|
|
53890
54127
|
|
|
53891
54128
|
**Phase 7: TRANSITION.**
|
|
53892
54129
|
- Summarize: (a) chosen approach, (b) design sections produced, (c) spec written, (d) QA gates selected, (e) remaining \`[NEEDS CLARIFICATION]\` markers.
|
|
@@ -53898,7 +54135,7 @@ BRAINSTORM RULES:
|
|
|
53898
54135
|
- One question per message in DIALOGUE \u2014 never batch.
|
|
53899
54136
|
- Always offer an informed default for every question.
|
|
53900
54137
|
- The spec produced in Phase 5 must still satisfy the SPEC CONTENT RULES (no tech stack, no implementation details).
|
|
53901
|
-
- QA gates
|
|
54138
|
+
- QA gates elected in Phase 6 are persisted during MODE: PLAN after \`save_plan\` succeeds and are ratchet-tighter from that point \u2014 once persisted you cannot undo them later in the session.
|
|
53902
54139
|
|
|
53903
54140
|
### MODE: SPECIFY
|
|
53904
54141
|
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.
|
|
@@ -53922,7 +54159,23 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
|
|
|
53922
54159
|
- Edge cases and known failure modes
|
|
53923
54160
|
- \`[NEEDS CLARIFICATION]\` markers (max 3) for items where uncertainty could change scope, security, or core behavior; prefer informed defaults over asking
|
|
53924
54161
|
5. Write the spec to \`.swarm/spec.md\`.
|
|
53925
|
-
|
|
54162
|
+
5b. **QA GATE SELECTION (dialogue only).**
|
|
54163
|
+
{{QA_GATE_DIALOGUE_SPECIFY}}
|
|
54164
|
+
|
|
54165
|
+
Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this point. Once the user answers, write the elected gates to \`.swarm/context.md\` under a new section:
|
|
54166
|
+
\`\`\`
|
|
54167
|
+
## Pending QA Gate Selection
|
|
54168
|
+
- reviewer: <true|false>
|
|
54169
|
+
- test_engineer: <true|false>
|
|
54170
|
+
- sme_enabled: <true|false>
|
|
54171
|
+
- critic_pre_plan: <true|false>
|
|
54172
|
+
- sast_enabled: <true|false>
|
|
54173
|
+
- council_mode: <true|false>
|
|
54174
|
+
- hallucination_guard: <true|false>
|
|
54175
|
+
- recorded_at: <ISO timestamp>
|
|
54176
|
+
\`\`\`
|
|
54177
|
+
MODE: PLAN will read this section after \`save_plan\` succeeds and persist via \`set_qa_gates\`.
|
|
54178
|
+
7. Report a summary to the user (MUST count, SHALL count, scenario count, clarification markers, elected QA gates) and suggest the next step: \`CLARIFY-SPEC\` (if markers exist) or \`PLAN\`.
|
|
53926
54179
|
|
|
53927
54180
|
SPEC CONTENT RULES \u2014 the spec MUST NOT contain:
|
|
53928
54181
|
- Technology stack, framework choices, library names
|
|
@@ -54131,7 +54384,14 @@ Use the \`save_plan\` tool to create the implementation plan. Required parameter
|
|
|
54131
54384
|
Example call:
|
|
54132
54385
|
save_plan({ title: "My Real Project", swarm_id: "mega", phases: [{ id: 1, name: "Setup", tasks: [{ id: "1.1", description: "Install dependencies and configure TypeScript", size: "small" }] }] })
|
|
54133
54386
|
|
|
54387
|
+
**POST-SAVE_PLAN: APPLY QA GATE SELECTION.**
|
|
54388
|
+
After \`save_plan\` succeeds, read \`.swarm/context.md\`:
|
|
54389
|
+
- If a \`## Pending QA Gate Selection\` section exists: parse the gate values, call \`set_qa_gates\` with those flags, confirm with the user ("QA gates applied: <list>"), then remove the section from context.md.
|
|
54390
|
+
- If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}} Then call \`set_qa_gates\` with the user's chosen flags.
|
|
54391
|
+
Either path must yield a persisted QA gate profile before the first task dispatches.
|
|
54392
|
+
|
|
54134
54393
|
\u26A0\uFE0F If \`save_plan\` is unavailable, delegate plan writing to {{AGENT_PREFIX}}coder:
|
|
54394
|
+
\u26A0\uFE0F Even in this fallback, you MUST call \`declare_scope\` for the single file ".swarm/plan.md" BEFORE the coder delegation. Scope discipline applies to plan-writing delegations too. See Rule 1a.
|
|
54135
54395
|
TASK: Write the implementation plan to .swarm/plan.md
|
|
54136
54396
|
FILE: .swarm/plan.md
|
|
54137
54397
|
INPUT: [provide the complete plan content below]
|
|
@@ -54232,6 +54492,7 @@ WRONG responses to gate failure:
|
|
|
54232
54492
|
|
|
54233
54493
|
RIGHT response to gate failure:
|
|
54234
54494
|
\u2713 Print "GATE FAILED: [gate name] | REASON: [details]"
|
|
54495
|
+
\u2713 BEFORE the retry delegation: call \`declare_scope\` with the file list the retry will touch. Re-declare even if the files are identical to the original task \u2014 retry scope persists per-call, not per-task. See Rule 1a.
|
|
54235
54496
|
\u2713 Delegate to {{AGENT_PREFIX}}coder with:
|
|
54236
54497
|
TASK: Fix [gate name] failure
|
|
54237
54498
|
FILE: [affected file(s)]
|
|
@@ -54249,6 +54510,7 @@ All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
|
|
|
54249
54510
|
|
|
54250
54511
|
5a-bis. **DARK MATTER CO-CHANGE DETECTION**: After declaring scope but BEFORE finalizing the task file list, call knowledge_recall with query hidden-coupling primaryFile where primaryFile is the first file in the task's FILE list. Extract primaryFile from the task's FILE list (first file = primary). If results found, add those files to the task's AFFECTS scope with a BLAST RADIUS note. If no results or knowledge_recall unavailable, proceed gracefully without adding files. This is advisory \u2014 the architect may exclude files from scope if they are unrelated to the current task. Delegate to {{AGENT_PREFIX}}coder only after scope is declared.
|
|
54251
54512
|
|
|
54513
|
+
5b-PRE (required): Call \`declare_scope({ taskId, files })\` with the EXACT file list for this task \u2014 including any co-change files surfaced by 5a-bis. Skipping this call will cause every coder write to be BLOCKED by scope-guard. No \`declare_scope\` \u2192 no 5b delegation. See Rule 1a.
|
|
54252
54514
|
5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
|
|
54253
54515
|
5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. If COMPATIBILITY SIGNALS=INCOMPATIBLE or MIGRATION_SURFACE=yes \u2192 coder retry. If COMPATIBILITY SIGNALS=COMPATIBLE and MIGRATION_SURFACE=no \u2192 proceed.
|
|
54254
54516
|
\u2192 REQUIRED: Print "diff: [PASS | CONTRACT CHANGE \u2014 details]"
|
|
@@ -56251,6 +56513,13 @@ function getAgentConfigs(config3, directory, sessionId) {
|
|
|
56251
56513
|
throw new Error(`[opencode-swarm] Conflicting config: council.enabled=true but tool_filter.overrides.architect omits ${missing.join(", ")}. ` + `Either set council.enabled=false, remove the architect override entirely to fall back on AGENT_TOOL_MAP, or add the missing council tools to the override. ` + `Refusing to silently override your explicit tool_filter.overrides.architect.`);
|
|
56252
56514
|
}
|
|
56253
56515
|
}
|
|
56516
|
+
if (baseAgentName === "architect" && config3?.council?.enabled !== true && override !== undefined) {
|
|
56517
|
+
const councilTools = ["declare_council_criteria", "convene_council"];
|
|
56518
|
+
const present = councilTools.filter((t) => override.includes(t));
|
|
56519
|
+
if (present.length > 0) {
|
|
56520
|
+
console.warn(`[opencode-swarm] tool_filter.overrides.architect includes ${present.join(", ")} but council.enabled is not true. ` + `The runtime gate will reject these calls. Either set council.enabled=true, or remove ${present.join(", ")} from the architect override.`);
|
|
56521
|
+
}
|
|
56522
|
+
}
|
|
56254
56523
|
if (!allowedTools && !Object.hasOwn(toolFilterOverrides, baseAgentName)) {
|
|
56255
56524
|
if (!warnedMissingWhitelist.has(baseAgentName)) {
|
|
56256
56525
|
console.warn(`[getAgentConfigs] Unknown agent '${baseAgentName}', defaulting to minimal toolset.`);
|
|
@@ -73586,7 +73855,7 @@ var knowledge_query = createSwarmTool({
|
|
|
73586
73855
|
args: {
|
|
73587
73856
|
tier: tool.schema.string().optional().describe("Knowledge tier to query: 'swarm', 'hive', or 'all' (default: 'all')"),
|
|
73588
73857
|
status: tool.schema.string().optional().describe("Filter by status: 'candidate', 'established', or 'promoted'"),
|
|
73589
|
-
category: tool.schema.string().optional().describe("Filter by category: 'process', 'architecture', 'tooling', 'security', 'testing', 'debugging', 'performance', 'integration', or 'other'"),
|
|
73858
|
+
category: tool.schema.string().optional().describe("Filter by category: 'process', 'architecture', 'tooling', 'security', 'testing', 'debugging', 'performance', 'integration', 'todo', or 'other'"),
|
|
73590
73859
|
min_score: tool.schema.number().optional().describe("Minimum confidence score filter (0.0-1.0)"),
|
|
73591
73860
|
limit: tool.schema.number().optional().describe(`Maximum number of results to return (default: ${DEFAULT_LIMIT}, max: 100)`)
|
|
73592
73861
|
},
|
|
@@ -73749,6 +74018,7 @@ import * as fs60 from "fs";
|
|
|
73749
74018
|
import * as path74 from "path";
|
|
73750
74019
|
init_knowledge_curator();
|
|
73751
74020
|
init_knowledge_reader();
|
|
74021
|
+
init_knowledge_store();
|
|
73752
74022
|
init_review_receipt();
|
|
73753
74023
|
init_utils2();
|
|
73754
74024
|
init_checkpoint3();
|
|
@@ -74058,7 +74328,13 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
74058
74328
|
safeWarn(`[phase_complete] Drift verifier error (non-blocking):`, driftError);
|
|
74059
74329
|
}
|
|
74060
74330
|
}
|
|
74061
|
-
|
|
74331
|
+
let knowledgeConfig;
|
|
74332
|
+
try {
|
|
74333
|
+
knowledgeConfig = KnowledgeConfigSchema.parse(config3.knowledge ?? {});
|
|
74334
|
+
} catch (parseErr) {
|
|
74335
|
+
warnings.push(`Knowledge config validation failed: ${String(parseErr)}`);
|
|
74336
|
+
knowledgeConfig = KnowledgeConfigSchema.parse({});
|
|
74337
|
+
}
|
|
74062
74338
|
if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
|
|
74063
74339
|
try {
|
|
74064
74340
|
const projectName = path74.basename(dir);
|
|
@@ -74280,6 +74556,28 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
74280
74556
|
telemetry.phaseChanged(contributorSessionId, oldPhase ?? 0, phase);
|
|
74281
74557
|
}
|
|
74282
74558
|
}
|
|
74559
|
+
try {
|
|
74560
|
+
if (knowledgeConfig.sweep_enabled) {
|
|
74561
|
+
const swarmPath = resolveSwarmKnowledgePath(dir);
|
|
74562
|
+
await sweepAgedEntries(swarmPath, knowledgeConfig.default_max_phases);
|
|
74563
|
+
await sweepStaleTodos(swarmPath, knowledgeConfig.todo_max_phases);
|
|
74564
|
+
if (knowledgeConfig.hive_enabled) {
|
|
74565
|
+
const hivePath = resolveHiveKnowledgePath();
|
|
74566
|
+
await sweepAgedEntries(hivePath, knowledgeConfig.default_max_phases);
|
|
74567
|
+
await sweepStaleTodos(hivePath, knowledgeConfig.todo_max_phases);
|
|
74568
|
+
}
|
|
74569
|
+
}
|
|
74570
|
+
} catch (err2) {
|
|
74571
|
+
let detail = String(err2);
|
|
74572
|
+
if (detail.includes("ELOCKED")) {
|
|
74573
|
+
detail = "lock timeout (stale lock detected)";
|
|
74574
|
+
} else if (detail.includes("ENOSPC")) {
|
|
74575
|
+
detail = "disk full";
|
|
74576
|
+
} else if (detail.includes("EACCES")) {
|
|
74577
|
+
detail = "permission denied";
|
|
74578
|
+
}
|
|
74579
|
+
warnings.push(`Knowledge sweep failed for phase ${phase}: ${detail}`);
|
|
74580
|
+
}
|
|
74283
74581
|
try {
|
|
74284
74582
|
const plan = await loadPlan(dir);
|
|
74285
74583
|
if (plan === null) {
|