opencode-swarm 6.72.0 → 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 +227 -224
- package/dist/index.js +531 -367
- package/dist/state.d.ts +27 -1
- package/dist/tools/convene-council.d.ts +1 -1
- package/package.json +1 -1
|
@@ -25,4 +25,15 @@ export interface CouncilWorkflowConfig {
|
|
|
25
25
|
* council feature is off or the config key is absent.
|
|
26
26
|
*/
|
|
27
27
|
export declare function buildCouncilWorkflow(council?: CouncilWorkflowConfig): string;
|
|
28
|
+
/**
|
|
29
|
+
* Build the user-facing QA gate selection dialogue, used by MODE: SPECIFY
|
|
30
|
+
* (step 5b), MODE: BRAINSTORM (Phase 6), and MODE: PLAN (post-`save_plan`
|
|
31
|
+
* inline path). The dialogue is dialogue-only — persistence happens during
|
|
32
|
+
* MODE: PLAN after `save_plan` creates `plan.json`.
|
|
33
|
+
*
|
|
34
|
+
* The lead-in sentence varies per mode, but the body (seven gates with
|
|
35
|
+
* defaults, one-shot accept-or-customize prompt) is shared so SPECIFY,
|
|
36
|
+
* BRAINSTORM, and PLAN inline paths stay in lockstep.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildQaGateSelectionDialogue(modeLabel: 'BRAINSTORM' | 'SPECIFY' | 'PLAN'): string;
|
|
28
39
|
export declare function createArchitectAgent(model: string, customPrompt?: string, customAppendPrompt?: string, adversarialTesting?: AdversarialTestingConfig, council?: CouncilWorkflowConfig): AgentDefinition;
|
package/dist/cli/index.js
CHANGED
|
@@ -17320,12 +17320,12 @@ var require_adapter = __commonJS((exports, module) => {
|
|
|
17320
17320
|
return newFs;
|
|
17321
17321
|
}
|
|
17322
17322
|
function toPromise(method) {
|
|
17323
|
-
return (...args) => new Promise((
|
|
17323
|
+
return (...args) => new Promise((resolve5, reject) => {
|
|
17324
17324
|
args.push((err, result) => {
|
|
17325
17325
|
if (err) {
|
|
17326
17326
|
reject(err);
|
|
17327
17327
|
} else {
|
|
17328
|
-
|
|
17328
|
+
resolve5(result);
|
|
17329
17329
|
}
|
|
17330
17330
|
});
|
|
17331
17331
|
method(...args);
|
|
@@ -19471,6 +19471,199 @@ init_manager2();
|
|
|
19471
19471
|
// src/state.ts
|
|
19472
19472
|
init_plan_schema();
|
|
19473
19473
|
|
|
19474
|
+
// src/db/qa-gate-profile.ts
|
|
19475
|
+
import { createHash as createHash3 } from "crypto";
|
|
19476
|
+
|
|
19477
|
+
// src/db/project-db.ts
|
|
19478
|
+
import { Database } from "bun:sqlite";
|
|
19479
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
19480
|
+
import { join as join6, resolve as resolve4 } from "path";
|
|
19481
|
+
var MIGRATIONS = [
|
|
19482
|
+
{
|
|
19483
|
+
version: 1,
|
|
19484
|
+
name: "create_project_constraints",
|
|
19485
|
+
sql: `CREATE TABLE project_constraints (
|
|
19486
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
19487
|
+
constraint_type TEXT NOT NULL,
|
|
19488
|
+
content TEXT NOT NULL,
|
|
19489
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
19490
|
+
)`
|
|
19491
|
+
},
|
|
19492
|
+
{
|
|
19493
|
+
version: 2,
|
|
19494
|
+
name: "create_qa_gate_profile",
|
|
19495
|
+
sql: `CREATE TABLE qa_gate_profile (
|
|
19496
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
19497
|
+
plan_id TEXT NOT NULL UNIQUE,
|
|
19498
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
19499
|
+
project_type TEXT,
|
|
19500
|
+
gates TEXT NOT NULL DEFAULT '{}',
|
|
19501
|
+
locked_at TEXT,
|
|
19502
|
+
locked_by_snapshot_seq INTEGER
|
|
19503
|
+
)`
|
|
19504
|
+
},
|
|
19505
|
+
{
|
|
19506
|
+
version: 3,
|
|
19507
|
+
name: "create_qa_gate_profile_immutability_trigger",
|
|
19508
|
+
sql: `CREATE TRIGGER IF NOT EXISTS trg_qa_gate_profile_no_update_after_lock
|
|
19509
|
+
BEFORE UPDATE ON qa_gate_profile
|
|
19510
|
+
WHEN OLD.locked_at IS NOT NULL
|
|
19511
|
+
BEGIN
|
|
19512
|
+
SELECT RAISE(ABORT, 'qa_gate_profile row is locked and cannot be modified after critic approval');
|
|
19513
|
+
END`
|
|
19514
|
+
}
|
|
19515
|
+
];
|
|
19516
|
+
var _projectDbs = new Map;
|
|
19517
|
+
function runProjectMigrations(db) {
|
|
19518
|
+
db.run(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
19519
|
+
version INTEGER PRIMARY KEY,
|
|
19520
|
+
name TEXT NOT NULL,
|
|
19521
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
19522
|
+
)`);
|
|
19523
|
+
const row = db.query("SELECT MAX(version) as version FROM schema_migrations").get();
|
|
19524
|
+
const currentVersion = row?.version ?? 0;
|
|
19525
|
+
for (const migration of MIGRATIONS) {
|
|
19526
|
+
if (migration.version <= currentVersion)
|
|
19527
|
+
continue;
|
|
19528
|
+
const apply = db.transaction(() => {
|
|
19529
|
+
db.run(migration.sql);
|
|
19530
|
+
db.run("INSERT INTO schema_migrations (version, name) VALUES (?, ?)", [
|
|
19531
|
+
migration.version,
|
|
19532
|
+
migration.name
|
|
19533
|
+
]);
|
|
19534
|
+
});
|
|
19535
|
+
apply();
|
|
19536
|
+
}
|
|
19537
|
+
}
|
|
19538
|
+
function projectDbPath(directory) {
|
|
19539
|
+
return join6(resolve4(directory), ".swarm", "swarm.db");
|
|
19540
|
+
}
|
|
19541
|
+
function projectDbExists(directory) {
|
|
19542
|
+
return existsSync4(projectDbPath(directory));
|
|
19543
|
+
}
|
|
19544
|
+
function getProjectDb(directory) {
|
|
19545
|
+
const key = resolve4(directory);
|
|
19546
|
+
const existing = _projectDbs.get(key);
|
|
19547
|
+
if (existing)
|
|
19548
|
+
return existing;
|
|
19549
|
+
const swarmDir = join6(key, ".swarm");
|
|
19550
|
+
mkdirSync3(swarmDir, { recursive: true });
|
|
19551
|
+
const db = new Database(join6(swarmDir, "swarm.db"));
|
|
19552
|
+
db.run("PRAGMA journal_mode = WAL;");
|
|
19553
|
+
db.run("PRAGMA synchronous = NORMAL;");
|
|
19554
|
+
db.run("PRAGMA busy_timeout = 5000;");
|
|
19555
|
+
db.run("PRAGMA foreign_keys = ON;");
|
|
19556
|
+
runProjectMigrations(db);
|
|
19557
|
+
_projectDbs.set(key, db);
|
|
19558
|
+
return db;
|
|
19559
|
+
}
|
|
19560
|
+
|
|
19561
|
+
// src/db/qa-gate-profile.ts
|
|
19562
|
+
var DEFAULT_QA_GATES = {
|
|
19563
|
+
reviewer: true,
|
|
19564
|
+
test_engineer: true,
|
|
19565
|
+
council_mode: false,
|
|
19566
|
+
sme_enabled: true,
|
|
19567
|
+
critic_pre_plan: true,
|
|
19568
|
+
hallucination_guard: false,
|
|
19569
|
+
sast_enabled: true
|
|
19570
|
+
};
|
|
19571
|
+
function rowToProfile(row) {
|
|
19572
|
+
let parsed = {};
|
|
19573
|
+
try {
|
|
19574
|
+
parsed = JSON.parse(row.gates);
|
|
19575
|
+
} catch {
|
|
19576
|
+
parsed = {};
|
|
19577
|
+
}
|
|
19578
|
+
const gates = { ...DEFAULT_QA_GATES, ...parsed };
|
|
19579
|
+
return {
|
|
19580
|
+
id: row.id,
|
|
19581
|
+
plan_id: row.plan_id,
|
|
19582
|
+
created_at: row.created_at,
|
|
19583
|
+
project_type: row.project_type,
|
|
19584
|
+
gates,
|
|
19585
|
+
locked_at: row.locked_at,
|
|
19586
|
+
locked_by_snapshot_seq: row.locked_by_snapshot_seq
|
|
19587
|
+
};
|
|
19588
|
+
}
|
|
19589
|
+
function getProfile(directory, planId) {
|
|
19590
|
+
if (!projectDbExists(directory))
|
|
19591
|
+
return null;
|
|
19592
|
+
const db = getProjectDb(directory);
|
|
19593
|
+
const row = db.query("SELECT * FROM qa_gate_profile WHERE plan_id = ?").get(planId);
|
|
19594
|
+
return row ? rowToProfile(row) : null;
|
|
19595
|
+
}
|
|
19596
|
+
function getOrCreateProfile(directory, planId, projectType) {
|
|
19597
|
+
const existing = getProfile(directory, planId);
|
|
19598
|
+
if (existing)
|
|
19599
|
+
return existing;
|
|
19600
|
+
const db = getProjectDb(directory);
|
|
19601
|
+
const gatesJson = JSON.stringify(DEFAULT_QA_GATES);
|
|
19602
|
+
const insert = db.transaction(() => {
|
|
19603
|
+
db.run("INSERT INTO qa_gate_profile (plan_id, project_type, gates) VALUES (?, ?, ?)", [planId, projectType ?? null, gatesJson]);
|
|
19604
|
+
});
|
|
19605
|
+
try {
|
|
19606
|
+
insert();
|
|
19607
|
+
} catch (err) {
|
|
19608
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19609
|
+
if (!msg.toLowerCase().includes("unique")) {
|
|
19610
|
+
throw err;
|
|
19611
|
+
}
|
|
19612
|
+
}
|
|
19613
|
+
const after = getProfile(directory, planId);
|
|
19614
|
+
if (!after) {
|
|
19615
|
+
throw new Error(`Failed to create or load QA gate profile for plan_id=${planId}`);
|
|
19616
|
+
}
|
|
19617
|
+
return after;
|
|
19618
|
+
}
|
|
19619
|
+
function setGates(directory, planId, gates) {
|
|
19620
|
+
const current = getProfile(directory, planId);
|
|
19621
|
+
if (!current) {
|
|
19622
|
+
throw new Error(`No QA gate profile found for plan_id=${planId} \u2014 call getOrCreateProfile first`);
|
|
19623
|
+
}
|
|
19624
|
+
if (current.locked_at !== null) {
|
|
19625
|
+
throw new Error("Cannot modify gates: QA gate profile is locked after critic approval");
|
|
19626
|
+
}
|
|
19627
|
+
const merged = { ...current.gates };
|
|
19628
|
+
for (const key of Object.keys(gates)) {
|
|
19629
|
+
const incoming = gates[key];
|
|
19630
|
+
if (incoming === undefined)
|
|
19631
|
+
continue;
|
|
19632
|
+
if (incoming === false && current.gates[key] === true) {
|
|
19633
|
+
throw new Error(`Cannot disable gate '${key}': sessions can only ratchet tighter`);
|
|
19634
|
+
}
|
|
19635
|
+
if (incoming === true) {
|
|
19636
|
+
merged[key] = true;
|
|
19637
|
+
}
|
|
19638
|
+
}
|
|
19639
|
+
const db = getProjectDb(directory);
|
|
19640
|
+
db.run("UPDATE qa_gate_profile SET gates = ? WHERE plan_id = ?", [
|
|
19641
|
+
JSON.stringify(merged),
|
|
19642
|
+
planId
|
|
19643
|
+
]);
|
|
19644
|
+
const updated = getProfile(directory, planId);
|
|
19645
|
+
if (!updated) {
|
|
19646
|
+
throw new Error(`Failed to re-read QA gate profile after update for plan_id=${planId}`);
|
|
19647
|
+
}
|
|
19648
|
+
return updated;
|
|
19649
|
+
}
|
|
19650
|
+
function computeProfileHash(profile) {
|
|
19651
|
+
const payload = JSON.stringify({
|
|
19652
|
+
plan_id: profile.plan_id,
|
|
19653
|
+
gates: profile.gates
|
|
19654
|
+
});
|
|
19655
|
+
return createHash3("sha256").update(payload).digest("hex");
|
|
19656
|
+
}
|
|
19657
|
+
function getEffectiveGates(profile, sessionOverrides) {
|
|
19658
|
+
const merged = { ...profile.gates };
|
|
19659
|
+
for (const key of Object.keys(sessionOverrides)) {
|
|
19660
|
+
if (sessionOverrides[key] === true) {
|
|
19661
|
+
merged[key] = true;
|
|
19662
|
+
}
|
|
19663
|
+
}
|
|
19664
|
+
return merged;
|
|
19665
|
+
}
|
|
19666
|
+
|
|
19474
19667
|
// src/hooks/delegation-gate.ts
|
|
19475
19668
|
init_telemetry();
|
|
19476
19669
|
|
|
@@ -19924,8 +20117,10 @@ function clearPendingCoderScope() {
|
|
|
19924
20117
|
}
|
|
19925
20118
|
|
|
19926
20119
|
// src/state.ts
|
|
20120
|
+
init_manager();
|
|
19927
20121
|
init_telemetry();
|
|
19928
20122
|
var _rehydrationCache = null;
|
|
20123
|
+
var _councilDisagreementWarned = new Set;
|
|
19929
20124
|
var swarmState = {
|
|
19930
20125
|
activeToolCalls: new Map,
|
|
19931
20126
|
toolAggregates: new Map,
|
|
@@ -19957,6 +20152,7 @@ function resetSwarmState() {
|
|
|
19957
20152
|
swarmState.fullAutoEnabledInConfig = false;
|
|
19958
20153
|
swarmState.environmentProfiles.clear();
|
|
19959
20154
|
clearPendingCoderScope();
|
|
20155
|
+
_councilDisagreementWarned.clear();
|
|
19960
20156
|
}
|
|
19961
20157
|
function getAgentSession(sessionId) {
|
|
19962
20158
|
return swarmState.agentSessions.get(sessionId);
|
|
@@ -33132,7 +33328,7 @@ function hasUncommittedChanges(cwd) {
|
|
|
33132
33328
|
|
|
33133
33329
|
// src/hooks/knowledge-store.ts
|
|
33134
33330
|
var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
|
|
33135
|
-
import { existsSync as
|
|
33331
|
+
import { existsSync as existsSync6 } from "fs";
|
|
33136
33332
|
import { appendFile as appendFile2, mkdir, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
33137
33333
|
import * as os2 from "os";
|
|
33138
33334
|
import * as path8 from "path";
|
|
@@ -33160,7 +33356,7 @@ function resolveHiveRejectedPath() {
|
|
|
33160
33356
|
return path8.join(path8.dirname(hivePath), "shared-learnings-rejected.jsonl");
|
|
33161
33357
|
}
|
|
33162
33358
|
async function readKnowledge(filePath) {
|
|
33163
|
-
if (!
|
|
33359
|
+
if (!existsSync6(filePath))
|
|
33164
33360
|
return [];
|
|
33165
33361
|
const content = await readFile2(filePath, "utf-8");
|
|
33166
33362
|
const results = [];
|
|
@@ -33793,7 +33989,7 @@ async function writeCheckpoint(directory) {
|
|
|
33793
33989
|
|
|
33794
33990
|
// src/session/snapshot-writer.ts
|
|
33795
33991
|
init_utils2();
|
|
33796
|
-
import { mkdirSync as
|
|
33992
|
+
import { mkdirSync as mkdirSync6, renameSync as renameSync5 } from "fs";
|
|
33797
33993
|
import * as path11 from "path";
|
|
33798
33994
|
init_utils();
|
|
33799
33995
|
var _writeInFlight = Promise.resolve();
|
|
@@ -33886,7 +34082,7 @@ async function writeSnapshot(directory, state) {
|
|
|
33886
34082
|
const content = JSON.stringify(snapshot, null, 2);
|
|
33887
34083
|
const resolvedPath = validateSwarmPath(directory, "session/state.json");
|
|
33888
34084
|
const dir = path11.dirname(resolvedPath);
|
|
33889
|
-
|
|
34085
|
+
mkdirSync6(dir, { recursive: true });
|
|
33890
34086
|
const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
33891
34087
|
await Bun.write(tempPath, content);
|
|
33892
34088
|
renameSync5(tempPath, resolvedPath);
|
|
@@ -35384,7 +35580,7 @@ async function handleDarkMatterCommand(directory, args) {
|
|
|
35384
35580
|
|
|
35385
35581
|
// src/services/diagnose-service.ts
|
|
35386
35582
|
import * as child_process4 from "child_process";
|
|
35387
|
-
import { existsSync as
|
|
35583
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
35388
35584
|
import path16 from "path";
|
|
35389
35585
|
import { fileURLToPath } from "url";
|
|
35390
35586
|
init_manager2();
|
|
@@ -35621,7 +35817,7 @@ async function checkConfigBackups(directory) {
|
|
|
35621
35817
|
}
|
|
35622
35818
|
async function checkGitRepository(directory) {
|
|
35623
35819
|
try {
|
|
35624
|
-
if (!
|
|
35820
|
+
if (!existsSync7(directory) || !statSync4(directory).isDirectory()) {
|
|
35625
35821
|
return {
|
|
35626
35822
|
name: "Git Repository",
|
|
35627
35823
|
status: "\u274C",
|
|
@@ -35686,7 +35882,7 @@ async function checkSpecStaleness(directory, plan) {
|
|
|
35686
35882
|
}
|
|
35687
35883
|
async function checkConfigParseability(directory) {
|
|
35688
35884
|
const configPath = path16.join(directory, ".opencode/opencode-swarm.json");
|
|
35689
|
-
if (!
|
|
35885
|
+
if (!existsSync7(configPath)) {
|
|
35690
35886
|
return {
|
|
35691
35887
|
name: "Config Parseability",
|
|
35692
35888
|
status: "\u2705",
|
|
@@ -35736,11 +35932,11 @@ async function checkGrammarWasmFiles() {
|
|
|
35736
35932
|
const isSource = thisDir.replace(/\\/g, "/").endsWith("/src/services");
|
|
35737
35933
|
const grammarDir = isSource ? path16.join(thisDir, "..", "lang", "grammars") : path16.join(thisDir, "lang", "grammars");
|
|
35738
35934
|
const missing = [];
|
|
35739
|
-
if (!
|
|
35935
|
+
if (!existsSync7(path16.join(grammarDir, "tree-sitter.wasm"))) {
|
|
35740
35936
|
missing.push("tree-sitter.wasm (core runtime)");
|
|
35741
35937
|
}
|
|
35742
35938
|
for (const file3 of grammarFiles) {
|
|
35743
|
-
if (!
|
|
35939
|
+
if (!existsSync7(path16.join(grammarDir, file3))) {
|
|
35744
35940
|
missing.push(file3);
|
|
35745
35941
|
}
|
|
35746
35942
|
}
|
|
@@ -35759,7 +35955,7 @@ async function checkGrammarWasmFiles() {
|
|
|
35759
35955
|
}
|
|
35760
35956
|
async function checkCheckpointManifest(directory) {
|
|
35761
35957
|
const manifestPath = path16.join(directory, ".swarm/checkpoints.json");
|
|
35762
|
-
if (!
|
|
35958
|
+
if (!existsSync7(manifestPath)) {
|
|
35763
35959
|
return {
|
|
35764
35960
|
name: "Checkpoint Manifest",
|
|
35765
35961
|
status: "\u2705",
|
|
@@ -35811,7 +36007,7 @@ async function checkCheckpointManifest(directory) {
|
|
|
35811
36007
|
}
|
|
35812
36008
|
async function checkEventStreamIntegrity(directory) {
|
|
35813
36009
|
const eventsPath = path16.join(directory, ".swarm/events.jsonl");
|
|
35814
|
-
if (!
|
|
36010
|
+
if (!existsSync7(eventsPath)) {
|
|
35815
36011
|
return {
|
|
35816
36012
|
name: "Event Stream",
|
|
35817
36013
|
status: "\u2705",
|
|
@@ -35852,7 +36048,7 @@ async function checkEventStreamIntegrity(directory) {
|
|
|
35852
36048
|
}
|
|
35853
36049
|
async function checkSteeringDirectives(directory) {
|
|
35854
36050
|
const eventsPath = path16.join(directory, ".swarm/events.jsonl");
|
|
35855
|
-
if (!
|
|
36051
|
+
if (!existsSync7(eventsPath)) {
|
|
35856
36052
|
return {
|
|
35857
36053
|
name: "Steering Directives",
|
|
35858
36054
|
status: "\u2705",
|
|
@@ -35908,7 +36104,7 @@ async function checkCurator(directory) {
|
|
|
35908
36104
|
};
|
|
35909
36105
|
}
|
|
35910
36106
|
const summaryPath = path16.join(directory, ".swarm/curator-summary.json");
|
|
35911
|
-
if (!
|
|
36107
|
+
if (!existsSync7(summaryPath)) {
|
|
35912
36108
|
return {
|
|
35913
36109
|
name: "Curator",
|
|
35914
36110
|
status: "\u2705",
|
|
@@ -36056,7 +36252,7 @@ async function getDiagnoseData(directory) {
|
|
|
36056
36252
|
checks5.push(await checkCurator(directory));
|
|
36057
36253
|
try {
|
|
36058
36254
|
const evidenceDir = path16.join(directory, ".swarm", "evidence");
|
|
36059
|
-
const snapshotFiles =
|
|
36255
|
+
const snapshotFiles = existsSync7(evidenceDir) ? readdirSync3(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
|
|
36060
36256
|
if (snapshotFiles.length > 0) {
|
|
36061
36257
|
const latest = snapshotFiles.sort().pop();
|
|
36062
36258
|
checks5.push({
|
|
@@ -36116,7 +36312,7 @@ import * as path18 from "path";
|
|
|
36116
36312
|
|
|
36117
36313
|
// src/lang/detector.ts
|
|
36118
36314
|
import { access as access2, readdir as readdir2 } from "fs/promises";
|
|
36119
|
-
import { extname as extname2, join as
|
|
36315
|
+
import { extname as extname2, join as join15 } from "path";
|
|
36120
36316
|
|
|
36121
36317
|
// src/lang/profiles.ts
|
|
36122
36318
|
class LanguageRegistry {
|
|
@@ -37096,7 +37292,7 @@ async function detectProjectLanguages(projectDir) {
|
|
|
37096
37292
|
if (detectFile.includes("*") || detectFile.includes("?"))
|
|
37097
37293
|
continue;
|
|
37098
37294
|
try {
|
|
37099
|
-
await access2(
|
|
37295
|
+
await access2(join15(dir, detectFile));
|
|
37100
37296
|
detected.add(profile.id);
|
|
37101
37297
|
break;
|
|
37102
37298
|
} catch {}
|
|
@@ -37117,7 +37313,7 @@ async function detectProjectLanguages(projectDir) {
|
|
|
37117
37313
|
const topEntries = await readdir2(projectDir, { withFileTypes: true });
|
|
37118
37314
|
for (const entry of topEntries) {
|
|
37119
37315
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
37120
|
-
await scanDir(
|
|
37316
|
+
await scanDir(join15(projectDir, entry.name));
|
|
37121
37317
|
}
|
|
37122
37318
|
}
|
|
37123
37319
|
} catch {}
|
|
@@ -38453,14 +38649,14 @@ async function handleHistoryCommand(directory, _args) {
|
|
|
38453
38649
|
}
|
|
38454
38650
|
// src/hooks/knowledge-migrator.ts
|
|
38455
38651
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
38456
|
-
import { existsSync as
|
|
38652
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
38457
38653
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
38458
38654
|
import * as path20 from "path";
|
|
38459
38655
|
async function migrateContextToKnowledge(directory, config3) {
|
|
38460
38656
|
const sentinelPath = path20.join(directory, ".swarm", ".knowledge-migrated");
|
|
38461
38657
|
const contextPath = path20.join(directory, ".swarm", "context.md");
|
|
38462
38658
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
38463
|
-
if (
|
|
38659
|
+
if (existsSync11(sentinelPath)) {
|
|
38464
38660
|
return {
|
|
38465
38661
|
migrated: false,
|
|
38466
38662
|
entriesMigrated: 0,
|
|
@@ -38469,7 +38665,7 @@ async function migrateContextToKnowledge(directory, config3) {
|
|
|
38469
38665
|
skippedReason: "sentinel-exists"
|
|
38470
38666
|
};
|
|
38471
38667
|
}
|
|
38472
|
-
if (!
|
|
38668
|
+
if (!existsSync11(contextPath)) {
|
|
38473
38669
|
return {
|
|
38474
38670
|
migrated: false,
|
|
38475
38671
|
entriesMigrated: 0,
|
|
@@ -38655,7 +38851,7 @@ function truncateLesson(text) {
|
|
|
38655
38851
|
}
|
|
38656
38852
|
function inferProjectName(directory) {
|
|
38657
38853
|
const packageJsonPath = path20.join(directory, "package.json");
|
|
38658
|
-
if (
|
|
38854
|
+
if (existsSync11(packageJsonPath)) {
|
|
38659
38855
|
try {
|
|
38660
38856
|
const pkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
38661
38857
|
if (pkg.name && typeof pkg.name === "string") {
|
|
@@ -39163,7 +39359,7 @@ async function _detectAvailableLinter(_projectDir, biomeBin, eslintBin) {
|
|
|
39163
39359
|
stderr: "pipe"
|
|
39164
39360
|
});
|
|
39165
39361
|
const biomeExit = biomeProc.exited;
|
|
39166
|
-
const timeout = new Promise((
|
|
39362
|
+
const timeout = new Promise((resolve8) => setTimeout(() => resolve8("timeout"), DETECT_TIMEOUT));
|
|
39167
39363
|
const result = await Promise.race([biomeExit, timeout]);
|
|
39168
39364
|
if (result === "timeout") {
|
|
39169
39365
|
biomeProc.kill();
|
|
@@ -39177,7 +39373,7 @@ async function _detectAvailableLinter(_projectDir, biomeBin, eslintBin) {
|
|
|
39177
39373
|
stderr: "pipe"
|
|
39178
39374
|
});
|
|
39179
39375
|
const eslintExit = eslintProc.exited;
|
|
39180
|
-
const timeout = new Promise((
|
|
39376
|
+
const timeout = new Promise((resolve8) => setTimeout(() => resolve8("timeout"), DETECT_TIMEOUT));
|
|
39181
39377
|
const result = await Promise.race([eslintExit, timeout]);
|
|
39182
39378
|
if (result === "timeout") {
|
|
39183
39379
|
eslintProc.kill();
|
|
@@ -40566,15 +40762,15 @@ function appendTestRun(record3, workingDir) {
|
|
|
40566
40762
|
prunedRecords.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
40567
40763
|
try {
|
|
40568
40764
|
const lines = prunedRecords.map((rec) => JSON.stringify(rec));
|
|
40569
|
-
const content = lines.join(`
|
|
40570
|
-
`)
|
|
40765
|
+
const content = `${lines.join(`
|
|
40766
|
+
`)}
|
|
40571
40767
|
`;
|
|
40572
|
-
const tempPath = historyPath
|
|
40768
|
+
const tempPath = `${historyPath}.tmp`;
|
|
40573
40769
|
fs14.writeFileSync(tempPath, content, "utf-8");
|
|
40574
40770
|
fs14.renameSync(tempPath, historyPath);
|
|
40575
40771
|
} catch (err) {
|
|
40576
40772
|
try {
|
|
40577
|
-
const tempPath = historyPath
|
|
40773
|
+
const tempPath = `${historyPath}.tmp`;
|
|
40578
40774
|
if (fs14.existsSync(tempPath)) {
|
|
40579
40775
|
fs14.unlinkSync(tempPath);
|
|
40580
40776
|
}
|
|
@@ -41396,9 +41592,9 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
|
|
|
41396
41592
|
stderr: "pipe",
|
|
41397
41593
|
cwd
|
|
41398
41594
|
});
|
|
41399
|
-
const timeoutPromise = new Promise((
|
|
41595
|
+
const timeoutPromise = new Promise((resolve11) => setTimeout(() => {
|
|
41400
41596
|
proc.kill();
|
|
41401
|
-
|
|
41597
|
+
resolve11(-1);
|
|
41402
41598
|
}, timeout_ms));
|
|
41403
41599
|
const [exitCode, stdoutResult, stderrResult] = await Promise.all([
|
|
41404
41600
|
Promise.race([proc.exited, timeoutPromise]),
|
|
@@ -42627,199 +42823,6 @@ async function handlePromoteCommand(directory, args) {
|
|
|
42627
42823
|
}
|
|
42628
42824
|
}
|
|
42629
42825
|
|
|
42630
|
-
// src/db/qa-gate-profile.ts
|
|
42631
|
-
import { createHash as createHash4 } from "crypto";
|
|
42632
|
-
|
|
42633
|
-
// src/db/project-db.ts
|
|
42634
|
-
import { Database } from "bun:sqlite";
|
|
42635
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync8 } from "fs";
|
|
42636
|
-
import { join as join23, resolve as resolve11 } from "path";
|
|
42637
|
-
var MIGRATIONS = [
|
|
42638
|
-
{
|
|
42639
|
-
version: 1,
|
|
42640
|
-
name: "create_project_constraints",
|
|
42641
|
-
sql: `CREATE TABLE project_constraints (
|
|
42642
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
42643
|
-
constraint_type TEXT NOT NULL,
|
|
42644
|
-
content TEXT NOT NULL,
|
|
42645
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
42646
|
-
)`
|
|
42647
|
-
},
|
|
42648
|
-
{
|
|
42649
|
-
version: 2,
|
|
42650
|
-
name: "create_qa_gate_profile",
|
|
42651
|
-
sql: `CREATE TABLE qa_gate_profile (
|
|
42652
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
42653
|
-
plan_id TEXT NOT NULL UNIQUE,
|
|
42654
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
42655
|
-
project_type TEXT,
|
|
42656
|
-
gates TEXT NOT NULL DEFAULT '{}',
|
|
42657
|
-
locked_at TEXT,
|
|
42658
|
-
locked_by_snapshot_seq INTEGER
|
|
42659
|
-
)`
|
|
42660
|
-
},
|
|
42661
|
-
{
|
|
42662
|
-
version: 3,
|
|
42663
|
-
name: "create_qa_gate_profile_immutability_trigger",
|
|
42664
|
-
sql: `CREATE TRIGGER IF NOT EXISTS trg_qa_gate_profile_no_update_after_lock
|
|
42665
|
-
BEFORE UPDATE ON qa_gate_profile
|
|
42666
|
-
WHEN OLD.locked_at IS NOT NULL
|
|
42667
|
-
BEGIN
|
|
42668
|
-
SELECT RAISE(ABORT, 'qa_gate_profile row is locked and cannot be modified after critic approval');
|
|
42669
|
-
END`
|
|
42670
|
-
}
|
|
42671
|
-
];
|
|
42672
|
-
var _projectDbs = new Map;
|
|
42673
|
-
function runProjectMigrations(db) {
|
|
42674
|
-
db.run(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
42675
|
-
version INTEGER PRIMARY KEY,
|
|
42676
|
-
name TEXT NOT NULL,
|
|
42677
|
-
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
42678
|
-
)`);
|
|
42679
|
-
const row = db.query("SELECT MAX(version) as version FROM schema_migrations").get();
|
|
42680
|
-
const currentVersion = row?.version ?? 0;
|
|
42681
|
-
for (const migration of MIGRATIONS) {
|
|
42682
|
-
if (migration.version <= currentVersion)
|
|
42683
|
-
continue;
|
|
42684
|
-
const apply = db.transaction(() => {
|
|
42685
|
-
db.run(migration.sql);
|
|
42686
|
-
db.run("INSERT INTO schema_migrations (version, name) VALUES (?, ?)", [
|
|
42687
|
-
migration.version,
|
|
42688
|
-
migration.name
|
|
42689
|
-
]);
|
|
42690
|
-
});
|
|
42691
|
-
apply();
|
|
42692
|
-
}
|
|
42693
|
-
}
|
|
42694
|
-
function projectDbPath(directory) {
|
|
42695
|
-
return join23(resolve11(directory), ".swarm", "swarm.db");
|
|
42696
|
-
}
|
|
42697
|
-
function projectDbExists(directory) {
|
|
42698
|
-
return existsSync16(projectDbPath(directory));
|
|
42699
|
-
}
|
|
42700
|
-
function getProjectDb(directory) {
|
|
42701
|
-
const key = resolve11(directory);
|
|
42702
|
-
const existing = _projectDbs.get(key);
|
|
42703
|
-
if (existing)
|
|
42704
|
-
return existing;
|
|
42705
|
-
const swarmDir = join23(key, ".swarm");
|
|
42706
|
-
mkdirSync8(swarmDir, { recursive: true });
|
|
42707
|
-
const db = new Database(join23(swarmDir, "swarm.db"));
|
|
42708
|
-
db.run("PRAGMA journal_mode = WAL;");
|
|
42709
|
-
db.run("PRAGMA synchronous = NORMAL;");
|
|
42710
|
-
db.run("PRAGMA busy_timeout = 5000;");
|
|
42711
|
-
db.run("PRAGMA foreign_keys = ON;");
|
|
42712
|
-
runProjectMigrations(db);
|
|
42713
|
-
_projectDbs.set(key, db);
|
|
42714
|
-
return db;
|
|
42715
|
-
}
|
|
42716
|
-
|
|
42717
|
-
// src/db/qa-gate-profile.ts
|
|
42718
|
-
var DEFAULT_QA_GATES = {
|
|
42719
|
-
reviewer: true,
|
|
42720
|
-
test_engineer: true,
|
|
42721
|
-
council_mode: false,
|
|
42722
|
-
sme_enabled: true,
|
|
42723
|
-
critic_pre_plan: true,
|
|
42724
|
-
hallucination_guard: false,
|
|
42725
|
-
sast_enabled: true
|
|
42726
|
-
};
|
|
42727
|
-
function rowToProfile(row) {
|
|
42728
|
-
let parsed = {};
|
|
42729
|
-
try {
|
|
42730
|
-
parsed = JSON.parse(row.gates);
|
|
42731
|
-
} catch {
|
|
42732
|
-
parsed = {};
|
|
42733
|
-
}
|
|
42734
|
-
const gates = { ...DEFAULT_QA_GATES, ...parsed };
|
|
42735
|
-
return {
|
|
42736
|
-
id: row.id,
|
|
42737
|
-
plan_id: row.plan_id,
|
|
42738
|
-
created_at: row.created_at,
|
|
42739
|
-
project_type: row.project_type,
|
|
42740
|
-
gates,
|
|
42741
|
-
locked_at: row.locked_at,
|
|
42742
|
-
locked_by_snapshot_seq: row.locked_by_snapshot_seq
|
|
42743
|
-
};
|
|
42744
|
-
}
|
|
42745
|
-
function getProfile(directory, planId) {
|
|
42746
|
-
if (!projectDbExists(directory))
|
|
42747
|
-
return null;
|
|
42748
|
-
const db = getProjectDb(directory);
|
|
42749
|
-
const row = db.query("SELECT * FROM qa_gate_profile WHERE plan_id = ?").get(planId);
|
|
42750
|
-
return row ? rowToProfile(row) : null;
|
|
42751
|
-
}
|
|
42752
|
-
function getOrCreateProfile(directory, planId, projectType) {
|
|
42753
|
-
const existing = getProfile(directory, planId);
|
|
42754
|
-
if (existing)
|
|
42755
|
-
return existing;
|
|
42756
|
-
const db = getProjectDb(directory);
|
|
42757
|
-
const gatesJson = JSON.stringify(DEFAULT_QA_GATES);
|
|
42758
|
-
const insert = db.transaction(() => {
|
|
42759
|
-
db.run("INSERT INTO qa_gate_profile (plan_id, project_type, gates) VALUES (?, ?, ?)", [planId, projectType ?? null, gatesJson]);
|
|
42760
|
-
});
|
|
42761
|
-
try {
|
|
42762
|
-
insert();
|
|
42763
|
-
} catch (err) {
|
|
42764
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
42765
|
-
if (!msg.toLowerCase().includes("unique")) {
|
|
42766
|
-
throw err;
|
|
42767
|
-
}
|
|
42768
|
-
}
|
|
42769
|
-
const after = getProfile(directory, planId);
|
|
42770
|
-
if (!after) {
|
|
42771
|
-
throw new Error(`Failed to create or load QA gate profile for plan_id=${planId}`);
|
|
42772
|
-
}
|
|
42773
|
-
return after;
|
|
42774
|
-
}
|
|
42775
|
-
function setGates(directory, planId, gates) {
|
|
42776
|
-
const current = getProfile(directory, planId);
|
|
42777
|
-
if (!current) {
|
|
42778
|
-
throw new Error(`No QA gate profile found for plan_id=${planId} \u2014 call getOrCreateProfile first`);
|
|
42779
|
-
}
|
|
42780
|
-
if (current.locked_at !== null) {
|
|
42781
|
-
throw new Error("Cannot modify gates: QA gate profile is locked after critic approval");
|
|
42782
|
-
}
|
|
42783
|
-
const merged = { ...current.gates };
|
|
42784
|
-
for (const key of Object.keys(gates)) {
|
|
42785
|
-
const incoming = gates[key];
|
|
42786
|
-
if (incoming === undefined)
|
|
42787
|
-
continue;
|
|
42788
|
-
if (incoming === false && current.gates[key] === true) {
|
|
42789
|
-
throw new Error(`Cannot disable gate '${key}': sessions can only ratchet tighter`);
|
|
42790
|
-
}
|
|
42791
|
-
if (incoming === true) {
|
|
42792
|
-
merged[key] = true;
|
|
42793
|
-
}
|
|
42794
|
-
}
|
|
42795
|
-
const db = getProjectDb(directory);
|
|
42796
|
-
db.run("UPDATE qa_gate_profile SET gates = ? WHERE plan_id = ?", [
|
|
42797
|
-
JSON.stringify(merged),
|
|
42798
|
-
planId
|
|
42799
|
-
]);
|
|
42800
|
-
const updated = getProfile(directory, planId);
|
|
42801
|
-
if (!updated) {
|
|
42802
|
-
throw new Error(`Failed to re-read QA gate profile after update for plan_id=${planId}`);
|
|
42803
|
-
}
|
|
42804
|
-
return updated;
|
|
42805
|
-
}
|
|
42806
|
-
function computeProfileHash(profile) {
|
|
42807
|
-
const payload = JSON.stringify({
|
|
42808
|
-
plan_id: profile.plan_id,
|
|
42809
|
-
gates: profile.gates
|
|
42810
|
-
});
|
|
42811
|
-
return createHash4("sha256").update(payload).digest("hex");
|
|
42812
|
-
}
|
|
42813
|
-
function getEffectiveGates(profile, sessionOverrides) {
|
|
42814
|
-
const merged = { ...profile.gates };
|
|
42815
|
-
for (const key of Object.keys(sessionOverrides)) {
|
|
42816
|
-
if (sessionOverrides[key] === true) {
|
|
42817
|
-
merged[key] = true;
|
|
42818
|
-
}
|
|
42819
|
-
}
|
|
42820
|
-
return merged;
|
|
42821
|
-
}
|
|
42822
|
-
|
|
42823
42826
|
// src/commands/qa-gates.ts
|
|
42824
42827
|
init_manager();
|
|
42825
42828
|
var ALL_GATE_NAMES = [
|