majlis 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1140 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -219,6 +219,36 @@ var init_migrations = __esm({
|
|
|
219
219
|
|
|
220
220
|
ALTER TABLE dead_ends ADD COLUMN category TEXT DEFAULT 'structural'
|
|
221
221
|
CHECK(category IN ('structural', 'procedural'));
|
|
222
|
+
`);
|
|
223
|
+
},
|
|
224
|
+
// Migration 005: v4 → v5 — Swarm tracking tables
|
|
225
|
+
(db) => {
|
|
226
|
+
db.exec(`
|
|
227
|
+
CREATE TABLE swarm_runs (
|
|
228
|
+
id INTEGER PRIMARY KEY,
|
|
229
|
+
goal TEXT NOT NULL,
|
|
230
|
+
parallel_count INTEGER NOT NULL,
|
|
231
|
+
status TEXT NOT NULL DEFAULT 'running'
|
|
232
|
+
CHECK(status IN ('running', 'completed', 'failed')),
|
|
233
|
+
total_cost_usd REAL DEFAULT 0,
|
|
234
|
+
best_experiment_slug TEXT,
|
|
235
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
236
|
+
completed_at DATETIME
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
CREATE TABLE swarm_members (
|
|
240
|
+
id INTEGER PRIMARY KEY,
|
|
241
|
+
swarm_run_id INTEGER REFERENCES swarm_runs(id),
|
|
242
|
+
experiment_slug TEXT NOT NULL,
|
|
243
|
+
worktree_path TEXT NOT NULL,
|
|
244
|
+
final_status TEXT,
|
|
245
|
+
overall_grade TEXT,
|
|
246
|
+
cost_usd REAL DEFAULT 0,
|
|
247
|
+
error TEXT,
|
|
248
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
CREATE INDEX idx_swarm_members_run ON swarm_members(swarm_run_id);
|
|
222
252
|
`);
|
|
223
253
|
}
|
|
224
254
|
];
|
|
@@ -273,6 +303,17 @@ function closeDb() {
|
|
|
273
303
|
function resetDb() {
|
|
274
304
|
_db = null;
|
|
275
305
|
}
|
|
306
|
+
function openDbAt(projectRoot) {
|
|
307
|
+
const majlisDir = path.join(projectRoot, ".majlis");
|
|
308
|
+
if (!fs.existsSync(majlisDir)) {
|
|
309
|
+
fs.mkdirSync(majlisDir, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
const db = new import_better_sqlite3.default(path.join(majlisDir, "majlis.db"));
|
|
312
|
+
db.pragma("journal_mode = WAL");
|
|
313
|
+
db.pragma("foreign_keys = ON");
|
|
314
|
+
runMigrations(db);
|
|
315
|
+
return db;
|
|
316
|
+
}
|
|
276
317
|
var import_better_sqlite3, path, fs, _db;
|
|
277
318
|
var init_connection = __esm({
|
|
278
319
|
"src/db/connection.ts"() {
|
|
@@ -469,7 +510,8 @@ ${cmd.body}
|
|
|
469
510
|
"verification",
|
|
470
511
|
"reframes",
|
|
471
512
|
"rihla",
|
|
472
|
-
"synthesis"
|
|
513
|
+
"synthesis",
|
|
514
|
+
"diagnosis"
|
|
473
515
|
];
|
|
474
516
|
for (const dir of docDirs) {
|
|
475
517
|
mkdirSafe(path2.join(docsDir, dir));
|
|
@@ -923,7 +965,74 @@ gate_decision:
|
|
|
923
965
|
"stale_references": ["list of stale references found, if any"],
|
|
924
966
|
"overlapping_dead_ends": [0]
|
|
925
967
|
}
|
|
926
|
-
|
|
968
|
+
-->`,
|
|
969
|
+
diagnostician: `---
|
|
970
|
+
name: diagnostician
|
|
971
|
+
model: opus
|
|
972
|
+
tools: [Read, Write, Bash, Glob, Grep, WebSearch]
|
|
973
|
+
---
|
|
974
|
+
You are the Diagnostician. You perform deep project-wide analysis.
|
|
975
|
+
|
|
976
|
+
You have the highest turn budget of any agent. Use it for depth, not breadth.
|
|
977
|
+
Your job is pure insight \u2014 you do NOT fix code, you do NOT build, you do NOT
|
|
978
|
+
make decisions. You diagnose.
|
|
979
|
+
|
|
980
|
+
## What You Receive
|
|
981
|
+
- Full database export: every experiment, decision, doubt, challenge, verification,
|
|
982
|
+
dead-end, metric, and compression across the entire project history
|
|
983
|
+
- Current synthesis, fragility map, and dead-end registry
|
|
984
|
+
- Full read access to the entire project codebase
|
|
985
|
+
- Bash access to run tests, profiling, git archaeology, and analysis scripts
|
|
986
|
+
|
|
987
|
+
## What You Can Do
|
|
988
|
+
1. **Read everything** \u2014 source code, docs, git history, test output
|
|
989
|
+
2. **Run analysis** \u2014 execute tests, profilers, git log/blame/bisect, custom scripts
|
|
990
|
+
3. **Write analysis scripts** \u2014 you may write scripts ONLY to \`.majlis/scripts/\`
|
|
991
|
+
4. **Search externally** \u2014 WebSearch for patterns, known issues, relevant techniques
|
|
992
|
+
|
|
993
|
+
## What You CANNOT Do
|
|
994
|
+
- Modify any project files outside \`.majlis/scripts/\`
|
|
995
|
+
- Make code changes, fixes, or patches
|
|
996
|
+
- Create experiments or make decisions
|
|
997
|
+
- Write to docs/, src/, or any other project directory
|
|
998
|
+
|
|
999
|
+
## Your Approach
|
|
1000
|
+
|
|
1001
|
+
Phase 1: Orientation (turns 1-10)
|
|
1002
|
+
- Read the full database export in your context
|
|
1003
|
+
- Read synthesis, fragility, dead-ends
|
|
1004
|
+
- Identify patterns: recurring failures, unresolved doubts, evidence gaps
|
|
1005
|
+
|
|
1006
|
+
Phase 2: Deep Investigation (turns 11-40)
|
|
1007
|
+
- Read source code at critical points identified in Phase 1
|
|
1008
|
+
- Run targeted tests, profiling, git archaeology
|
|
1009
|
+
- Write and execute analysis scripts in .majlis/scripts/
|
|
1010
|
+
- Cross-reference findings across experiments
|
|
1011
|
+
|
|
1012
|
+
Phase 3: Synthesis (turns 41-60)
|
|
1013
|
+
- Compile findings into a diagnostic report
|
|
1014
|
+
- Identify root causes, not symptoms
|
|
1015
|
+
- Rank issues by structural impact
|
|
1016
|
+
- Suggest investigation directions (not fixes)
|
|
1017
|
+
|
|
1018
|
+
## Output Format
|
|
1019
|
+
Produce a diagnostic report as markdown. At the end, include:
|
|
1020
|
+
|
|
1021
|
+
<!-- majlis-json
|
|
1022
|
+
{
|
|
1023
|
+
"diagnosis": {
|
|
1024
|
+
"root_causes": ["List of identified root causes"],
|
|
1025
|
+
"patterns": ["Recurring patterns across experiments"],
|
|
1026
|
+
"evidence_gaps": ["What we don't know but should"],
|
|
1027
|
+
"investigation_directions": ["Suggested directions for next experiments"]
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
-->
|
|
1031
|
+
|
|
1032
|
+
## Safety Reminders
|
|
1033
|
+
- You are READ-ONLY for project code. Write ONLY to .majlis/scripts/.
|
|
1034
|
+
- Focus on diagnosis, not fixing. Your value is insight, not implementation.
|
|
1035
|
+
- Trust the database export over docs/ files when they conflict.`
|
|
927
1036
|
};
|
|
928
1037
|
SLASH_COMMANDS = {
|
|
929
1038
|
classify: {
|
|
@@ -983,6 +1092,14 @@ Produce a rihla document at docs/rihla/.`
|
|
|
983
1092
|
If the CLI is not installed, review: original objective, current classification,
|
|
984
1093
|
recent failures, dead-ends. Ask: is the classification serving the objective?
|
|
985
1094
|
Would we decompose differently with what we now know?`
|
|
1095
|
+
},
|
|
1096
|
+
diagnose: {
|
|
1097
|
+
description: "Deep project-wide diagnostic analysis",
|
|
1098
|
+
body: `Run \`majlis diagnose $ARGUMENTS\` for deep diagnosis.
|
|
1099
|
+
If the CLI is not installed, perform a deep diagnostic analysis.
|
|
1100
|
+
Read docs/synthesis/current.md, fragility.md, dead-ends.md, and all experiments.
|
|
1101
|
+
Identify root causes, recurring patterns, evidence gaps, and investigation directions.
|
|
1102
|
+
Do NOT modify project code \u2014 analysis only.`
|
|
986
1103
|
}
|
|
987
1104
|
};
|
|
988
1105
|
HOOKS_CONFIG = {
|
|
@@ -1581,6 +1698,29 @@ function insertFinding(db, experimentId, approach, source, relevance, contradict
|
|
|
1581
1698
|
VALUES (?, ?, ?, ?, ?)
|
|
1582
1699
|
`).run(experimentId, approach, source, relevance, contradictsCurrent ? 1 : 0);
|
|
1583
1700
|
}
|
|
1701
|
+
function createSwarmRun(db, goal, parallelCount) {
|
|
1702
|
+
const result = db.prepare(`
|
|
1703
|
+
INSERT INTO swarm_runs (goal, parallel_count) VALUES (?, ?)
|
|
1704
|
+
`).run(goal, parallelCount);
|
|
1705
|
+
return { id: result.lastInsertRowid };
|
|
1706
|
+
}
|
|
1707
|
+
function updateSwarmRun(db, id, status2, totalCostUsd, bestSlug) {
|
|
1708
|
+
db.prepare(`
|
|
1709
|
+
UPDATE swarm_runs SET status = ?, total_cost_usd = ?, best_experiment_slug = ?,
|
|
1710
|
+
completed_at = CURRENT_TIMESTAMP WHERE id = ?
|
|
1711
|
+
`).run(status2, totalCostUsd, bestSlug, id);
|
|
1712
|
+
}
|
|
1713
|
+
function addSwarmMember(db, swarmRunId, slug, worktreePath) {
|
|
1714
|
+
db.prepare(`
|
|
1715
|
+
INSERT INTO swarm_members (swarm_run_id, experiment_slug, worktree_path) VALUES (?, ?, ?)
|
|
1716
|
+
`).run(swarmRunId, slug, worktreePath);
|
|
1717
|
+
}
|
|
1718
|
+
function updateSwarmMember(db, swarmRunId, slug, finalStatus, overallGrade, costUsd, error) {
|
|
1719
|
+
db.prepare(`
|
|
1720
|
+
UPDATE swarm_members SET final_status = ?, overall_grade = ?, cost_usd = ?, error = ?
|
|
1721
|
+
WHERE swarm_run_id = ? AND experiment_slug = ?
|
|
1722
|
+
`).run(finalStatus, overallGrade, costUsd, error, swarmRunId, slug);
|
|
1723
|
+
}
|
|
1584
1724
|
function exportForCompressor(db, maxLength = 3e4) {
|
|
1585
1725
|
const experiments = listAllExperiments(db);
|
|
1586
1726
|
const sections = ["# Structured Data Export (from SQLite)\n"];
|
|
@@ -1643,6 +1783,79 @@ function exportForCompressor(db, maxLength = 3e4) {
|
|
|
1643
1783
|
if (full.length > maxLength) {
|
|
1644
1784
|
return full.slice(0, maxLength) + `
|
|
1645
1785
|
|
|
1786
|
+
[TRUNCATED \u2014 full export was ${full.length} chars]`;
|
|
1787
|
+
}
|
|
1788
|
+
return full;
|
|
1789
|
+
}
|
|
1790
|
+
function exportForDiagnostician(db, maxLength = 6e4) {
|
|
1791
|
+
const base = exportForCompressor(db, maxLength);
|
|
1792
|
+
const sections = [base];
|
|
1793
|
+
const metrics = db.prepare(`
|
|
1794
|
+
SELECT m.*, e.slug FROM metrics m
|
|
1795
|
+
JOIN experiments e ON m.experiment_id = e.id
|
|
1796
|
+
ORDER BY m.captured_at
|
|
1797
|
+
`).all();
|
|
1798
|
+
if (metrics.length > 0) {
|
|
1799
|
+
sections.push("\n## Metric History (all experiments)");
|
|
1800
|
+
for (const m of metrics) {
|
|
1801
|
+
sections.push(`- ${m.slug} [${m.phase}] ${m.fixture}/${m.metric_name}: ${m.metric_value}`);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
const sessions = db.prepare("SELECT * FROM sessions ORDER BY started_at").all();
|
|
1805
|
+
if (sessions.length > 0) {
|
|
1806
|
+
sections.push("\n## Session History");
|
|
1807
|
+
for (const s of sessions) {
|
|
1808
|
+
sections.push(`- #${s.id}: "${s.intent}" (${s.ended_at ? "ended" : "active"})`);
|
|
1809
|
+
if (s.accomplished) sections.push(` accomplished: ${s.accomplished}`);
|
|
1810
|
+
if (s.unfinished) sections.push(` unfinished: ${s.unfinished}`);
|
|
1811
|
+
if (s.new_fragility) sections.push(` fragility: ${s.new_fragility}`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
const compressions = db.prepare("SELECT * FROM compressions ORDER BY created_at").all();
|
|
1815
|
+
if (compressions.length > 0) {
|
|
1816
|
+
sections.push("\n## Compression History");
|
|
1817
|
+
for (const c of compressions) {
|
|
1818
|
+
sections.push(`- #${c.id}: ${c.synthesis_size_before}B \u2192 ${c.synthesis_size_after}B (${c.session_count_since_last} sessions)`);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
try {
|
|
1822
|
+
const swarmRuns = db.prepare("SELECT * FROM swarm_runs ORDER BY created_at").all();
|
|
1823
|
+
if (swarmRuns.length > 0) {
|
|
1824
|
+
sections.push("\n## Swarm History");
|
|
1825
|
+
for (const sr of swarmRuns) {
|
|
1826
|
+
sections.push(`- #${sr.id}: "${sr.goal}" (${sr.status}, best: ${sr.best_experiment_slug ?? "none"})`);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
} catch {
|
|
1830
|
+
}
|
|
1831
|
+
const reframes = db.prepare(`
|
|
1832
|
+
SELECT r.*, e.slug FROM reframes r
|
|
1833
|
+
JOIN experiments e ON r.experiment_id = e.id
|
|
1834
|
+
ORDER BY r.created_at
|
|
1835
|
+
`).all();
|
|
1836
|
+
if (reframes.length > 0) {
|
|
1837
|
+
sections.push("\n## Reframe History");
|
|
1838
|
+
for (const r of reframes) {
|
|
1839
|
+
const decomp = String(r.decomposition ?? "").slice(0, 200);
|
|
1840
|
+
sections.push(`- ${r.slug}: ${decomp}`);
|
|
1841
|
+
if (r.recommendation) sections.push(` recommendation: ${String(r.recommendation).slice(0, 200)}`);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
const findings = db.prepare(`
|
|
1845
|
+
SELECT f.*, e.slug FROM findings f
|
|
1846
|
+
JOIN experiments e ON f.experiment_id = e.id
|
|
1847
|
+
ORDER BY f.created_at
|
|
1848
|
+
`).all();
|
|
1849
|
+
if (findings.length > 0) {
|
|
1850
|
+
sections.push("\n## Scout Findings");
|
|
1851
|
+
for (const f of findings) {
|
|
1852
|
+
sections.push(`- ${f.slug}: ${f.approach} (${f.source}) ${f.contradicts_current ? "[CONTRADICTS CURRENT]" : ""}`);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
const full = sections.join("\n");
|
|
1856
|
+
if (full.length > maxLength) {
|
|
1857
|
+
return full.slice(0, maxLength) + `
|
|
1858
|
+
|
|
1646
1859
|
[TRUNCATED \u2014 full export was ${full.length} chars]`;
|
|
1647
1860
|
}
|
|
1648
1861
|
return full;
|
|
@@ -1838,6 +2051,8 @@ function getExtractionSchema(role) {
|
|
|
1838
2051
|
return '{"findings": [{"approach": "string", "source": "string", "relevance": "string", "contradicts_current": true}]}';
|
|
1839
2052
|
case "compressor":
|
|
1840
2053
|
return '{"compression_report": {"synthesis_delta": "string", "new_dead_ends": ["string"], "fragility_changes": ["string"]}}';
|
|
2054
|
+
case "diagnostician":
|
|
2055
|
+
return '{"diagnosis": {"root_causes": ["string"], "patterns": ["string"], "evidence_gaps": ["string"], "investigation_directions": ["string"]}}';
|
|
1841
2056
|
default:
|
|
1842
2057
|
return EXTRACTION_SCHEMA;
|
|
1843
2058
|
}
|
|
@@ -1861,7 +2076,8 @@ var init_types = __esm({
|
|
|
1861
2076
|
gatekeeper: ["gate_decision"],
|
|
1862
2077
|
reframer: ["reframe"],
|
|
1863
2078
|
scout: ["findings"],
|
|
1864
|
-
compressor: ["compression_report"]
|
|
2079
|
+
compressor: ["compression_report"],
|
|
2080
|
+
diagnostician: ["diagnosis"]
|
|
1865
2081
|
};
|
|
1866
2082
|
}
|
|
1867
2083
|
});
|
|
@@ -1996,7 +2212,7 @@ ${truncated}`;
|
|
|
1996
2212
|
}
|
|
1997
2213
|
}
|
|
1998
2214
|
function hasData(output) {
|
|
1999
|
-
return !!(output.decisions && output.decisions.length > 0 || output.grades && output.grades.length > 0 || output.doubts && output.doubts.length > 0 || output.challenges && output.challenges.length > 0 || output.findings && output.findings.length > 0 || output.guidance || output.reframe || output.compression_report || output.gate_decision);
|
|
2215
|
+
return !!(output.decisions && output.decisions.length > 0 || output.grades && output.grades.length > 0 || output.doubts && output.doubts.length > 0 || output.challenges && output.challenges.length > 0 || output.findings && output.findings.length > 0 || output.guidance || output.reframe || output.compression_report || output.gate_decision || output.diagnosis);
|
|
2000
2216
|
}
|
|
2001
2217
|
function validateForRole(role, output) {
|
|
2002
2218
|
const required = ROLE_REQUIRED_FIELDS[role];
|
|
@@ -2069,6 +2285,12 @@ You may ONLY write to docs/synthesis/.
|
|
|
2069
2285
|
- Have you updated current.md, fragility.md, dead-ends.md?
|
|
2070
2286
|
- If yes \u2192 output compression report JSON.
|
|
2071
2287
|
- Do NOT write to MEMORY.md or files outside docs/synthesis/.`;
|
|
2288
|
+
case "diagnostician":
|
|
2289
|
+
return `${header2}
|
|
2290
|
+
You are READ-ONLY for project code. Write ONLY to .majlis/scripts/.
|
|
2291
|
+
Focus on diagnosis, not fixing. Your value is insight, not implementation.
|
|
2292
|
+
Phase 1 (1-10): orientation. Phase 2 (11-40): deep investigation. Phase 3 (41-60): synthesis.
|
|
2293
|
+
If you are past turn 40, begin compiling your diagnostic report.`;
|
|
2072
2294
|
default:
|
|
2073
2295
|
return `${header2}
|
|
2074
2296
|
Check: is your core task done? If yes, wrap up and output JSON.`;
|
|
@@ -2092,6 +2314,36 @@ function buildPreToolUseGuards(role) {
|
|
|
2092
2314
|
{ matcher: "Edit", hooks: [guardHook] }
|
|
2093
2315
|
];
|
|
2094
2316
|
}
|
|
2317
|
+
if (role === "diagnostician") {
|
|
2318
|
+
const writeGuard = async (input) => {
|
|
2319
|
+
const toolInput = input.tool_input ?? {};
|
|
2320
|
+
const filePath = toolInput.file_path ?? "";
|
|
2321
|
+
if (filePath && !filePath.includes("/.majlis/scripts/")) {
|
|
2322
|
+
return {
|
|
2323
|
+
decision: "block",
|
|
2324
|
+
reason: `Diagnostician may only write to .majlis/scripts/. Blocked: ${filePath}`
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
return {};
|
|
2328
|
+
};
|
|
2329
|
+
const bashGuard = async (input) => {
|
|
2330
|
+
const toolInput = input.tool_input ?? {};
|
|
2331
|
+
const command = toolInput.command ?? "";
|
|
2332
|
+
const destructive = /\b(rm\s+-rf|git\s+(checkout|reset|stash|clean|push)|chmod|chown|mkfs|dd\s+if=)\b/i;
|
|
2333
|
+
if (destructive.test(command)) {
|
|
2334
|
+
return {
|
|
2335
|
+
decision: "block",
|
|
2336
|
+
reason: `Diagnostician blocked destructive command: ${command.slice(0, 100)}`
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
return {};
|
|
2340
|
+
};
|
|
2341
|
+
return [
|
|
2342
|
+
{ matcher: "Write", hooks: [writeGuard] },
|
|
2343
|
+
{ matcher: "Edit", hooks: [writeGuard] },
|
|
2344
|
+
{ matcher: "Bash", hooks: [bashGuard] }
|
|
2345
|
+
];
|
|
2346
|
+
}
|
|
2095
2347
|
return void 0;
|
|
2096
2348
|
}
|
|
2097
2349
|
function buildAgentHooks(role, maxTurns) {
|
|
@@ -2365,7 +2617,7 @@ function writeArtifact(role, context, markdown, projectRoot) {
|
|
|
2365
2617
|
};
|
|
2366
2618
|
const dir = dirMap[role];
|
|
2367
2619
|
if (!dir) return null;
|
|
2368
|
-
if (role === "builder" || role === "compressor") return null;
|
|
2620
|
+
if (role === "builder" || role === "compressor" || role === "diagnostician") return null;
|
|
2369
2621
|
const fullDir = path4.join(projectRoot, dir);
|
|
2370
2622
|
if (!fs4.existsSync(fullDir)) {
|
|
2371
2623
|
fs4.mkdirSync(fullDir, { recursive: true });
|
|
@@ -2394,14 +2646,16 @@ var init_spawn = __esm({
|
|
|
2394
2646
|
compressor: 30,
|
|
2395
2647
|
reframer: 20,
|
|
2396
2648
|
scout: 20,
|
|
2397
|
-
gatekeeper: 10
|
|
2649
|
+
gatekeeper: 10,
|
|
2650
|
+
diagnostician: 60
|
|
2398
2651
|
};
|
|
2399
2652
|
CHECKPOINT_INTERVAL = {
|
|
2400
2653
|
builder: 15,
|
|
2401
2654
|
verifier: 12,
|
|
2402
2655
|
critic: 15,
|
|
2403
2656
|
adversary: 15,
|
|
2404
|
-
compressor: 15
|
|
2657
|
+
compressor: 15,
|
|
2658
|
+
diagnostician: 20
|
|
2405
2659
|
};
|
|
2406
2660
|
DIM2 = "\x1B[2m";
|
|
2407
2661
|
RESET2 = "\x1B[0m";
|
|
@@ -3101,6 +3355,84 @@ async function resolve(db, exp, projectRoot) {
|
|
|
3101
3355
|
}
|
|
3102
3356
|
}
|
|
3103
3357
|
}
|
|
3358
|
+
async function resolveDbOnly(db, exp, projectRoot) {
|
|
3359
|
+
let grades = getVerificationsByExperiment(db, exp.id);
|
|
3360
|
+
if (grades.length === 0) {
|
|
3361
|
+
warn(`No verification records for ${exp.slug}. Defaulting to weak.`);
|
|
3362
|
+
insertVerification(
|
|
3363
|
+
db,
|
|
3364
|
+
exp.id,
|
|
3365
|
+
"auto-default",
|
|
3366
|
+
"weak",
|
|
3367
|
+
null,
|
|
3368
|
+
null,
|
|
3369
|
+
"No structured verification output. Auto-defaulted to weak."
|
|
3370
|
+
);
|
|
3371
|
+
grades = getVerificationsByExperiment(db, exp.id);
|
|
3372
|
+
}
|
|
3373
|
+
const overallGrade = worstGrade(grades);
|
|
3374
|
+
switch (overallGrade) {
|
|
3375
|
+
case "sound":
|
|
3376
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
3377
|
+
success(`Experiment ${exp.slug} RESOLVED (sound) \u2014 git merge deferred.`);
|
|
3378
|
+
break;
|
|
3379
|
+
case "good": {
|
|
3380
|
+
const gaps = grades.filter((g) => g.grade === "good").map((g) => `- **${g.component}**: ${g.notes ?? "minor gaps"}`).join("\n");
|
|
3381
|
+
appendToFragilityMap(projectRoot, exp.slug, gaps);
|
|
3382
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
3383
|
+
success(`Experiment ${exp.slug} RESOLVED (good) \u2014 git merge deferred.`);
|
|
3384
|
+
break;
|
|
3385
|
+
}
|
|
3386
|
+
case "weak": {
|
|
3387
|
+
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
3388
|
+
const guidance = await spawnSynthesiser({
|
|
3389
|
+
experiment: {
|
|
3390
|
+
id: exp.id,
|
|
3391
|
+
slug: exp.slug,
|
|
3392
|
+
hypothesis: exp.hypothesis,
|
|
3393
|
+
status: exp.status,
|
|
3394
|
+
sub_type: exp.sub_type,
|
|
3395
|
+
builder_guidance: exp.builder_guidance
|
|
3396
|
+
},
|
|
3397
|
+
verificationReport: grades,
|
|
3398
|
+
confirmedDoubts,
|
|
3399
|
+
taskPrompt: "Synthesise the verification report, confirmed doubts, and adversarial case results into specific, actionable guidance for the builder's next attempt. Be concrete: which specific decisions need revisiting, which assumptions broke, and what constraints must the next approach satisfy."
|
|
3400
|
+
}, projectRoot);
|
|
3401
|
+
const guidanceText = guidance.structured?.guidance ?? guidance.output;
|
|
3402
|
+
db.transaction(() => {
|
|
3403
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
3404
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
3405
|
+
if (exp.sub_type) {
|
|
3406
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
3407
|
+
}
|
|
3408
|
+
})();
|
|
3409
|
+
warn(`Experiment ${exp.slug} CYCLING BACK (weak). Guidance generated.`);
|
|
3410
|
+
break;
|
|
3411
|
+
}
|
|
3412
|
+
case "rejected": {
|
|
3413
|
+
const rejectedComponents = grades.filter((g) => g.grade === "rejected");
|
|
3414
|
+
const whyFailed = rejectedComponents.map((r) => r.notes ?? "rejected").join("; ");
|
|
3415
|
+
db.transaction(() => {
|
|
3416
|
+
insertDeadEnd(
|
|
3417
|
+
db,
|
|
3418
|
+
exp.id,
|
|
3419
|
+
exp.hypothesis ?? exp.slug,
|
|
3420
|
+
whyFailed,
|
|
3421
|
+
`Approach rejected: ${whyFailed}`,
|
|
3422
|
+
exp.sub_type,
|
|
3423
|
+
"structural"
|
|
3424
|
+
);
|
|
3425
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
3426
|
+
if (exp.sub_type) {
|
|
3427
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
|
|
3428
|
+
}
|
|
3429
|
+
})();
|
|
3430
|
+
info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
|
|
3431
|
+
break;
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
return overallGrade;
|
|
3435
|
+
}
|
|
3104
3436
|
function gitMerge(branch, cwd) {
|
|
3105
3437
|
try {
|
|
3106
3438
|
(0, import_node_child_process3.execSync)(`git merge ${branch} --no-ff -m "Merge experiment branch ${branch}"`, {
|
|
@@ -3163,7 +3495,9 @@ var init_resolve = __esm({
|
|
|
3163
3495
|
var cycle_exports = {};
|
|
3164
3496
|
__export(cycle_exports, {
|
|
3165
3497
|
cycle: () => cycle,
|
|
3166
|
-
resolveCmd: () => resolveCmd
|
|
3498
|
+
resolveCmd: () => resolveCmd,
|
|
3499
|
+
runResolve: () => runResolve,
|
|
3500
|
+
runStep: () => runStep
|
|
3167
3501
|
});
|
|
3168
3502
|
async function cycle(step, args) {
|
|
3169
3503
|
const root = findProjectRoot();
|
|
@@ -3195,6 +3529,28 @@ async function resolveCmd(args) {
|
|
|
3195
3529
|
transition(exp.status, "resolved" /* RESOLVED */);
|
|
3196
3530
|
await resolve(db, exp, root);
|
|
3197
3531
|
}
|
|
3532
|
+
async function runStep(step, db, exp, root) {
|
|
3533
|
+
switch (step) {
|
|
3534
|
+
case "build":
|
|
3535
|
+
return doBuild(db, exp, root);
|
|
3536
|
+
case "challenge":
|
|
3537
|
+
return doChallenge(db, exp, root);
|
|
3538
|
+
case "doubt":
|
|
3539
|
+
return doDoubt(db, exp, root);
|
|
3540
|
+
case "scout":
|
|
3541
|
+
return doScout(db, exp, root);
|
|
3542
|
+
case "verify":
|
|
3543
|
+
return doVerify(db, exp, root);
|
|
3544
|
+
case "gate":
|
|
3545
|
+
return doGate(db, exp, root);
|
|
3546
|
+
case "compress":
|
|
3547
|
+
return doCompress(db, root);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
async function runResolve(db, exp, root) {
|
|
3551
|
+
transition(exp.status, "resolved" /* RESOLVED */);
|
|
3552
|
+
await resolve(db, exp, root);
|
|
3553
|
+
}
|
|
3198
3554
|
async function doGate(db, exp, root) {
|
|
3199
3555
|
transition(exp.status, "gated" /* GATED */);
|
|
3200
3556
|
const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
@@ -4005,13 +4361,13 @@ async function run(args) {
|
|
|
4005
4361
|
const db = getDb(root);
|
|
4006
4362
|
const config = loadConfig(root);
|
|
4007
4363
|
const MAX_EXPERIMENTS = 10;
|
|
4008
|
-
const
|
|
4364
|
+
const MAX_STEPS2 = 200;
|
|
4009
4365
|
let experimentCount = 0;
|
|
4010
4366
|
let stepCount = 0;
|
|
4011
4367
|
let consecutiveFailures = 0;
|
|
4012
4368
|
const usedHypotheses = /* @__PURE__ */ new Set();
|
|
4013
4369
|
header(`Autonomous Mode \u2014 ${goal}`);
|
|
4014
|
-
while (stepCount <
|
|
4370
|
+
while (stepCount < MAX_STEPS2 && experimentCount < MAX_EXPERIMENTS) {
|
|
4015
4371
|
if (isShutdownRequested()) {
|
|
4016
4372
|
warn("Shutdown requested. Stopping autonomous mode.");
|
|
4017
4373
|
break;
|
|
@@ -4081,8 +4437,8 @@ async function run(args) {
|
|
|
4081
4437
|
}
|
|
4082
4438
|
}
|
|
4083
4439
|
}
|
|
4084
|
-
if (stepCount >=
|
|
4085
|
-
warn(`Reached max steps (${
|
|
4440
|
+
if (stepCount >= MAX_STEPS2) {
|
|
4441
|
+
warn(`Reached max steps (${MAX_STEPS2}). Stopping autonomous mode.`);
|
|
4086
4442
|
}
|
|
4087
4443
|
header("Autonomous Mode Complete");
|
|
4088
4444
|
info(`Goal: ${goal}`);
|
|
@@ -4234,11 +4590,768 @@ var init_run = __esm({
|
|
|
4234
4590
|
}
|
|
4235
4591
|
});
|
|
4236
4592
|
|
|
4593
|
+
// src/swarm/worktree.ts
|
|
4594
|
+
function createWorktree(mainRoot, slug, paddedNum) {
|
|
4595
|
+
const projectName = path12.basename(mainRoot);
|
|
4596
|
+
const worktreeName = `${projectName}-swarm-${paddedNum}-${slug}`;
|
|
4597
|
+
const worktreePath = path12.join(path12.dirname(mainRoot), worktreeName);
|
|
4598
|
+
const branch = `swarm/${paddedNum}-${slug}`;
|
|
4599
|
+
(0, import_node_child_process6.execSync)(`git worktree add ${JSON.stringify(worktreePath)} -b ${branch}`, {
|
|
4600
|
+
cwd: mainRoot,
|
|
4601
|
+
encoding: "utf-8",
|
|
4602
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4603
|
+
});
|
|
4604
|
+
return {
|
|
4605
|
+
path: worktreePath,
|
|
4606
|
+
branch,
|
|
4607
|
+
slug,
|
|
4608
|
+
hypothesis: "",
|
|
4609
|
+
// filled in by caller
|
|
4610
|
+
paddedNum
|
|
4611
|
+
};
|
|
4612
|
+
}
|
|
4613
|
+
function initializeWorktree(mainRoot, worktreePath) {
|
|
4614
|
+
const majlisDir = path12.join(worktreePath, ".majlis");
|
|
4615
|
+
fs12.mkdirSync(majlisDir, { recursive: true });
|
|
4616
|
+
const configSrc = path12.join(mainRoot, ".majlis", "config.json");
|
|
4617
|
+
if (fs12.existsSync(configSrc)) {
|
|
4618
|
+
fs12.copyFileSync(configSrc, path12.join(majlisDir, "config.json"));
|
|
4619
|
+
}
|
|
4620
|
+
const agentsSrc = path12.join(mainRoot, ".majlis", "agents");
|
|
4621
|
+
if (fs12.existsSync(agentsSrc)) {
|
|
4622
|
+
const agentsDst = path12.join(majlisDir, "agents");
|
|
4623
|
+
fs12.mkdirSync(agentsDst, { recursive: true });
|
|
4624
|
+
for (const file of fs12.readdirSync(agentsSrc)) {
|
|
4625
|
+
fs12.copyFileSync(path12.join(agentsSrc, file), path12.join(agentsDst, file));
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
const synthSrc = path12.join(mainRoot, "docs", "synthesis");
|
|
4629
|
+
if (fs12.existsSync(synthSrc)) {
|
|
4630
|
+
const synthDst = path12.join(worktreePath, "docs", "synthesis");
|
|
4631
|
+
fs12.mkdirSync(synthDst, { recursive: true });
|
|
4632
|
+
for (const file of fs12.readdirSync(synthSrc)) {
|
|
4633
|
+
const srcFile = path12.join(synthSrc, file);
|
|
4634
|
+
if (fs12.statSync(srcFile).isFile()) {
|
|
4635
|
+
fs12.copyFileSync(srcFile, path12.join(synthDst, file));
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
const templateSrc = path12.join(mainRoot, "docs", "experiments", "_TEMPLATE.md");
|
|
4640
|
+
if (fs12.existsSync(templateSrc)) {
|
|
4641
|
+
const expDir = path12.join(worktreePath, "docs", "experiments");
|
|
4642
|
+
fs12.mkdirSync(expDir, { recursive: true });
|
|
4643
|
+
fs12.copyFileSync(templateSrc, path12.join(expDir, "_TEMPLATE.md"));
|
|
4644
|
+
}
|
|
4645
|
+
const db = openDbAt(worktreePath);
|
|
4646
|
+
db.close();
|
|
4647
|
+
}
|
|
4648
|
+
function cleanupWorktree(mainRoot, wt) {
|
|
4649
|
+
try {
|
|
4650
|
+
(0, import_node_child_process6.execSync)(`git worktree remove ${JSON.stringify(wt.path)} --force`, {
|
|
4651
|
+
cwd: mainRoot,
|
|
4652
|
+
encoding: "utf-8",
|
|
4653
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4654
|
+
});
|
|
4655
|
+
} catch {
|
|
4656
|
+
warn(`Could not remove worktree ${wt.path} \u2014 remove manually.`);
|
|
4657
|
+
}
|
|
4658
|
+
try {
|
|
4659
|
+
(0, import_node_child_process6.execSync)(`git branch -D ${wt.branch}`, {
|
|
4660
|
+
cwd: mainRoot,
|
|
4661
|
+
encoding: "utf-8",
|
|
4662
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4663
|
+
});
|
|
4664
|
+
} catch {
|
|
4665
|
+
}
|
|
4666
|
+
try {
|
|
4667
|
+
(0, import_node_child_process6.execSync)("git worktree prune", {
|
|
4668
|
+
cwd: mainRoot,
|
|
4669
|
+
encoding: "utf-8",
|
|
4670
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4671
|
+
});
|
|
4672
|
+
} catch {
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
var fs12, path12, import_node_child_process6;
|
|
4676
|
+
var init_worktree = __esm({
|
|
4677
|
+
"src/swarm/worktree.ts"() {
|
|
4678
|
+
"use strict";
|
|
4679
|
+
fs12 = __toESM(require("fs"));
|
|
4680
|
+
path12 = __toESM(require("path"));
|
|
4681
|
+
import_node_child_process6 = require("child_process");
|
|
4682
|
+
init_connection();
|
|
4683
|
+
init_format();
|
|
4684
|
+
}
|
|
4685
|
+
});
|
|
4686
|
+
|
|
4687
|
+
// src/swarm/runner.ts
|
|
4688
|
+
async function runExperimentInWorktree(wt) {
|
|
4689
|
+
const label = `[swarm:${wt.paddedNum}]`;
|
|
4690
|
+
let db;
|
|
4691
|
+
let exp = null;
|
|
4692
|
+
let overallGrade = null;
|
|
4693
|
+
let stepCount = 0;
|
|
4694
|
+
try {
|
|
4695
|
+
db = openDbAt(wt.path);
|
|
4696
|
+
exp = createExperiment(db, wt.slug, wt.branch, wt.hypothesis, null, null);
|
|
4697
|
+
updateExperimentStatus(db, exp.id, "reframed");
|
|
4698
|
+
exp.status = "reframed";
|
|
4699
|
+
const templatePath = path13.join(wt.path, "docs", "experiments", "_TEMPLATE.md");
|
|
4700
|
+
if (fs13.existsSync(templatePath)) {
|
|
4701
|
+
const template = fs13.readFileSync(templatePath, "utf-8");
|
|
4702
|
+
const logContent = template.replace(/\{\{title\}\}/g, wt.hypothesis).replace(/\{\{hypothesis\}\}/g, wt.hypothesis).replace(/\{\{branch\}\}/g, wt.branch).replace(/\{\{status\}\}/g, "classified").replace(/\{\{sub_type\}\}/g, "unclassified").replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]);
|
|
4703
|
+
const logPath = path13.join(wt.path, "docs", "experiments", `${wt.paddedNum}-${wt.slug}.md`);
|
|
4704
|
+
fs13.writeFileSync(logPath, logContent);
|
|
4705
|
+
}
|
|
4706
|
+
info(`${label} Starting: ${wt.hypothesis}`);
|
|
4707
|
+
while (stepCount < MAX_STEPS) {
|
|
4708
|
+
if (isShutdownRequested()) {
|
|
4709
|
+
warn(`${label} Shutdown requested. Stopping.`);
|
|
4710
|
+
break;
|
|
4711
|
+
}
|
|
4712
|
+
stepCount++;
|
|
4713
|
+
const fresh = getExperimentBySlug(db, wt.slug);
|
|
4714
|
+
if (!fresh) break;
|
|
4715
|
+
exp = fresh;
|
|
4716
|
+
if (isTerminal(exp.status)) {
|
|
4717
|
+
success(`${label} Reached terminal: ${exp.status}`);
|
|
4718
|
+
break;
|
|
4719
|
+
}
|
|
4720
|
+
const valid = validNext(exp.status);
|
|
4721
|
+
if (valid.length === 0) break;
|
|
4722
|
+
const nextStep = determineNextStep(
|
|
4723
|
+
exp,
|
|
4724
|
+
valid,
|
|
4725
|
+
hasDoubts(db, exp.id),
|
|
4726
|
+
hasChallenges(db, exp.id)
|
|
4727
|
+
);
|
|
4728
|
+
info(`${label} [${stepCount}/${MAX_STEPS}] ${exp.status} -> ${nextStep}`);
|
|
4729
|
+
if (nextStep === "resolved" /* RESOLVED */) {
|
|
4730
|
+
overallGrade = await resolveDbOnly(db, exp, wt.path);
|
|
4731
|
+
continue;
|
|
4732
|
+
}
|
|
4733
|
+
if (nextStep === "compressed" /* COMPRESSED */) {
|
|
4734
|
+
await runStep("compress", db, exp, wt.path);
|
|
4735
|
+
updateExperimentStatus(db, exp.id, "compressed");
|
|
4736
|
+
continue;
|
|
4737
|
+
}
|
|
4738
|
+
if (nextStep === "merged" /* MERGED */) {
|
|
4739
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4740
|
+
success(`${label} Merged.`);
|
|
4741
|
+
break;
|
|
4742
|
+
}
|
|
4743
|
+
if (nextStep === "reframed" /* REFRAMED */) {
|
|
4744
|
+
updateExperimentStatus(db, exp.id, "reframed");
|
|
4745
|
+
continue;
|
|
4746
|
+
}
|
|
4747
|
+
const stepName = statusToStepName(nextStep);
|
|
4748
|
+
if (!stepName) {
|
|
4749
|
+
warn(`${label} Unknown step: ${nextStep}`);
|
|
4750
|
+
break;
|
|
4751
|
+
}
|
|
4752
|
+
try {
|
|
4753
|
+
await runStep(stepName, db, exp, wt.path);
|
|
4754
|
+
} catch (err) {
|
|
4755
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4756
|
+
warn(`${label} Step failed: ${message}`);
|
|
4757
|
+
try {
|
|
4758
|
+
insertDeadEnd(
|
|
4759
|
+
db,
|
|
4760
|
+
exp.id,
|
|
4761
|
+
exp.hypothesis ?? exp.slug,
|
|
4762
|
+
message,
|
|
4763
|
+
`Process failure: ${message}`,
|
|
4764
|
+
exp.sub_type,
|
|
4765
|
+
"procedural"
|
|
4766
|
+
);
|
|
4767
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
4768
|
+
} catch {
|
|
4769
|
+
}
|
|
4770
|
+
break;
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
if (stepCount >= MAX_STEPS) {
|
|
4774
|
+
warn(`${label} Hit max steps (${MAX_STEPS}).`);
|
|
4775
|
+
}
|
|
4776
|
+
const finalExp = getExperimentBySlug(db, wt.slug);
|
|
4777
|
+
if (finalExp) exp = finalExp;
|
|
4778
|
+
const finalStatus = exp?.status ?? "error";
|
|
4779
|
+
return {
|
|
4780
|
+
worktree: wt,
|
|
4781
|
+
experiment: exp,
|
|
4782
|
+
finalStatus,
|
|
4783
|
+
overallGrade,
|
|
4784
|
+
costUsd: 0,
|
|
4785
|
+
// TODO: track via SDK when available
|
|
4786
|
+
stepCount
|
|
4787
|
+
};
|
|
4788
|
+
} catch (err) {
|
|
4789
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4790
|
+
warn(`${label} Fatal error: ${message}`);
|
|
4791
|
+
return {
|
|
4792
|
+
worktree: wt,
|
|
4793
|
+
experiment: exp,
|
|
4794
|
+
finalStatus: "error",
|
|
4795
|
+
overallGrade: null,
|
|
4796
|
+
costUsd: 0,
|
|
4797
|
+
stepCount,
|
|
4798
|
+
error: message
|
|
4799
|
+
};
|
|
4800
|
+
} finally {
|
|
4801
|
+
if (db) {
|
|
4802
|
+
try {
|
|
4803
|
+
db.close();
|
|
4804
|
+
} catch {
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
function statusToStepName(status2) {
|
|
4810
|
+
switch (status2) {
|
|
4811
|
+
case "gated" /* GATED */:
|
|
4812
|
+
return "gate";
|
|
4813
|
+
case "building" /* BUILDING */:
|
|
4814
|
+
return "build";
|
|
4815
|
+
case "challenged" /* CHALLENGED */:
|
|
4816
|
+
return "challenge";
|
|
4817
|
+
case "doubted" /* DOUBTED */:
|
|
4818
|
+
return "doubt";
|
|
4819
|
+
case "scouted" /* SCOUTED */:
|
|
4820
|
+
return "scout";
|
|
4821
|
+
case "verifying" /* VERIFYING */:
|
|
4822
|
+
return "verify";
|
|
4823
|
+
default:
|
|
4824
|
+
return null;
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
var fs13, path13, MAX_STEPS;
|
|
4828
|
+
var init_runner = __esm({
|
|
4829
|
+
"src/swarm/runner.ts"() {
|
|
4830
|
+
"use strict";
|
|
4831
|
+
fs13 = __toESM(require("fs"));
|
|
4832
|
+
path13 = __toESM(require("path"));
|
|
4833
|
+
init_connection();
|
|
4834
|
+
init_queries();
|
|
4835
|
+
init_machine();
|
|
4836
|
+
init_types2();
|
|
4837
|
+
init_cycle();
|
|
4838
|
+
init_resolve();
|
|
4839
|
+
init_shutdown();
|
|
4840
|
+
init_format();
|
|
4841
|
+
MAX_STEPS = 20;
|
|
4842
|
+
}
|
|
4843
|
+
});
|
|
4844
|
+
|
|
4845
|
+
// src/swarm/aggregate.ts
|
|
4846
|
+
function importExperimentFromWorktree(sourceDb, targetDb, slug) {
|
|
4847
|
+
const sourceExp = sourceDb.prepare(
|
|
4848
|
+
"SELECT * FROM experiments WHERE slug = ?"
|
|
4849
|
+
).get(slug);
|
|
4850
|
+
if (!sourceExp) {
|
|
4851
|
+
throw new Error(`Experiment ${slug} not found in source DB`);
|
|
4852
|
+
}
|
|
4853
|
+
const sourceId = sourceExp.id;
|
|
4854
|
+
const insertExp = targetDb.prepare(`
|
|
4855
|
+
INSERT INTO experiments (slug, branch, status, classification_ref, sub_type,
|
|
4856
|
+
hypothesis, builder_guidance, created_at, updated_at)
|
|
4857
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4858
|
+
`);
|
|
4859
|
+
const result = insertExp.run(
|
|
4860
|
+
sourceExp.slug,
|
|
4861
|
+
sourceExp.branch,
|
|
4862
|
+
sourceExp.status,
|
|
4863
|
+
sourceExp.classification_ref,
|
|
4864
|
+
sourceExp.sub_type,
|
|
4865
|
+
sourceExp.hypothesis,
|
|
4866
|
+
sourceExp.builder_guidance,
|
|
4867
|
+
sourceExp.created_at,
|
|
4868
|
+
sourceExp.updated_at
|
|
4869
|
+
);
|
|
4870
|
+
const targetId = result.lastInsertRowid;
|
|
4871
|
+
for (const table2 of CHILD_TABLES) {
|
|
4872
|
+
importChildTable(sourceDb, targetDb, table2, sourceId, targetId);
|
|
4873
|
+
}
|
|
4874
|
+
const stfRows = sourceDb.prepare(
|
|
4875
|
+
"SELECT * FROM sub_type_failures WHERE experiment_id = ?"
|
|
4876
|
+
).all(sourceId);
|
|
4877
|
+
for (const row of stfRows) {
|
|
4878
|
+
targetDb.prepare(`
|
|
4879
|
+
INSERT INTO sub_type_failures (sub_type, experiment_id, grade, created_at)
|
|
4880
|
+
VALUES (?, ?, ?, ?)
|
|
4881
|
+
`).run(row.sub_type, targetId, row.grade, row.created_at);
|
|
4882
|
+
}
|
|
4883
|
+
return targetId;
|
|
4884
|
+
}
|
|
4885
|
+
function importChildTable(sourceDb, targetDb, table2, sourceExpId, targetExpId) {
|
|
4886
|
+
const rows = sourceDb.prepare(
|
|
4887
|
+
`SELECT * FROM ${table2} WHERE experiment_id = ?`
|
|
4888
|
+
).all(sourceExpId);
|
|
4889
|
+
if (rows.length === 0) return;
|
|
4890
|
+
const cols = Object.keys(rows[0]).filter((c) => c !== "id");
|
|
4891
|
+
const placeholders = cols.map(() => "?").join(", ");
|
|
4892
|
+
const insert = targetDb.prepare(
|
|
4893
|
+
`INSERT INTO ${table2} (${cols.join(", ")}) VALUES (${placeholders})`
|
|
4894
|
+
);
|
|
4895
|
+
for (const row of rows) {
|
|
4896
|
+
const values = cols.map(
|
|
4897
|
+
(c) => c === "experiment_id" ? targetExpId : row[c]
|
|
4898
|
+
);
|
|
4899
|
+
insert.run(...values);
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
function aggregateSwarmResults(mainRoot, mainDb, results) {
|
|
4903
|
+
let mergedCount = 0;
|
|
4904
|
+
let deadEndCount = 0;
|
|
4905
|
+
let errorCount = 0;
|
|
4906
|
+
let totalCostUsd = 0;
|
|
4907
|
+
for (const r of results) {
|
|
4908
|
+
totalCostUsd += r.costUsd;
|
|
4909
|
+
if (r.error || !r.experiment) {
|
|
4910
|
+
errorCount++;
|
|
4911
|
+
continue;
|
|
4912
|
+
}
|
|
4913
|
+
try {
|
|
4914
|
+
const sourceDb = openDbAt(r.worktree.path);
|
|
4915
|
+
mainDb.transaction(() => {
|
|
4916
|
+
importExperimentFromWorktree(sourceDb, mainDb, r.worktree.slug);
|
|
4917
|
+
})();
|
|
4918
|
+
sourceDb.close();
|
|
4919
|
+
if (r.finalStatus === "merged") mergedCount++;
|
|
4920
|
+
else if (r.finalStatus === "dead_end") deadEndCount++;
|
|
4921
|
+
} catch (err) {
|
|
4922
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4923
|
+
warn(`Failed to import ${r.worktree.slug}: ${msg}`);
|
|
4924
|
+
errorCount++;
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
const ranked = results.filter((r) => r.overallGrade && !r.error).sort((a, b) => {
|
|
4928
|
+
const aRank = GRADE_RANK[a.overallGrade] ?? 99;
|
|
4929
|
+
const bRank = GRADE_RANK[b.overallGrade] ?? 99;
|
|
4930
|
+
return aRank - bRank;
|
|
4931
|
+
});
|
|
4932
|
+
const best = ranked.length > 0 ? ranked[0] : null;
|
|
4933
|
+
return {
|
|
4934
|
+
goal: "",
|
|
4935
|
+
// filled by caller
|
|
4936
|
+
parallelCount: results.length,
|
|
4937
|
+
results,
|
|
4938
|
+
bestExperiment: best,
|
|
4939
|
+
totalCostUsd,
|
|
4940
|
+
mergedCount,
|
|
4941
|
+
deadEndCount,
|
|
4942
|
+
errorCount
|
|
4943
|
+
};
|
|
4944
|
+
}
|
|
4945
|
+
var CHILD_TABLES, GRADE_RANK;
|
|
4946
|
+
var init_aggregate = __esm({
|
|
4947
|
+
"src/swarm/aggregate.ts"() {
|
|
4948
|
+
"use strict";
|
|
4949
|
+
init_connection();
|
|
4950
|
+
init_format();
|
|
4951
|
+
CHILD_TABLES = [
|
|
4952
|
+
"decisions",
|
|
4953
|
+
"doubts",
|
|
4954
|
+
"challenges",
|
|
4955
|
+
"verifications",
|
|
4956
|
+
"metrics",
|
|
4957
|
+
"dead_ends",
|
|
4958
|
+
"reframes",
|
|
4959
|
+
"findings"
|
|
4960
|
+
];
|
|
4961
|
+
GRADE_RANK = {
|
|
4962
|
+
sound: 0,
|
|
4963
|
+
good: 1,
|
|
4964
|
+
weak: 2,
|
|
4965
|
+
rejected: 3
|
|
4966
|
+
};
|
|
4967
|
+
}
|
|
4968
|
+
});
|
|
4969
|
+
|
|
4970
|
+
// src/commands/swarm.ts
|
|
4971
|
+
var swarm_exports = {};
|
|
4972
|
+
__export(swarm_exports, {
|
|
4973
|
+
swarm: () => swarm
|
|
4974
|
+
});
|
|
4975
|
+
async function swarm(args) {
|
|
4976
|
+
const root = findProjectRoot();
|
|
4977
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
4978
|
+
const goal = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
4979
|
+
if (!goal) throw new Error('Usage: majlis swarm "goal description" [--parallel N]');
|
|
4980
|
+
const parallelStr = getFlagValue(args, "--parallel");
|
|
4981
|
+
const parallelCount = Math.min(
|
|
4982
|
+
Math.max(2, parseInt(parallelStr ?? String(DEFAULT_PARALLEL), 10) || DEFAULT_PARALLEL),
|
|
4983
|
+
MAX_PARALLEL
|
|
4984
|
+
);
|
|
4985
|
+
try {
|
|
4986
|
+
const status2 = (0, import_node_child_process7.execSync)("git status --porcelain", {
|
|
4987
|
+
cwd: root,
|
|
4988
|
+
encoding: "utf-8",
|
|
4989
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4990
|
+
}).trim();
|
|
4991
|
+
if (status2) {
|
|
4992
|
+
warn("Working tree has uncommitted changes. Commit or stash before swarming.");
|
|
4993
|
+
throw new Error("Dirty working tree. Commit or stash first.");
|
|
4994
|
+
}
|
|
4995
|
+
} catch (err) {
|
|
4996
|
+
if (err instanceof Error && err.message.includes("Dirty working tree")) throw err;
|
|
4997
|
+
warn("Could not check git status.");
|
|
4998
|
+
}
|
|
4999
|
+
const db = getDb(root);
|
|
5000
|
+
const swarmRun = createSwarmRun(db, goal, parallelCount);
|
|
5001
|
+
header(`Swarm Mode \u2014 ${goal}`);
|
|
5002
|
+
info(`Generating ${parallelCount} diverse hypotheses...`);
|
|
5003
|
+
const hypotheses = await deriveMultipleHypotheses(goal, root, parallelCount);
|
|
5004
|
+
if (hypotheses.length === 0) {
|
|
5005
|
+
success("Planner says the goal has been met. Nothing to swarm.");
|
|
5006
|
+
updateSwarmRun(db, swarmRun.id, "completed", 0, null);
|
|
5007
|
+
return;
|
|
5008
|
+
}
|
|
5009
|
+
info(`Got ${hypotheses.length} hypotheses:`);
|
|
5010
|
+
for (let i = 0; i < hypotheses.length; i++) {
|
|
5011
|
+
info(` ${i + 1}. ${hypotheses[i]}`);
|
|
5012
|
+
}
|
|
5013
|
+
const worktrees = [];
|
|
5014
|
+
for (let i = 0; i < hypotheses.length; i++) {
|
|
5015
|
+
const paddedNum = String(i + 1).padStart(3, "0");
|
|
5016
|
+
const slug = await generateSlug(hypotheses[i], root);
|
|
5017
|
+
try {
|
|
5018
|
+
const wt = createWorktree(root, slug, paddedNum);
|
|
5019
|
+
wt.hypothesis = hypotheses[i];
|
|
5020
|
+
initializeWorktree(root, wt.path);
|
|
5021
|
+
worktrees.push(wt);
|
|
5022
|
+
addSwarmMember(db, swarmRun.id, slug, wt.path);
|
|
5023
|
+
info(`Created worktree ${paddedNum}: ${slug}`);
|
|
5024
|
+
} catch (err) {
|
|
5025
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5026
|
+
warn(`Failed to create worktree for hypothesis ${i + 1}: ${msg}`);
|
|
5027
|
+
}
|
|
5028
|
+
}
|
|
5029
|
+
if (worktrees.length === 0) {
|
|
5030
|
+
warn("No worktrees created. Aborting swarm.");
|
|
5031
|
+
updateSwarmRun(db, swarmRun.id, "failed", 0, null);
|
|
5032
|
+
return;
|
|
5033
|
+
}
|
|
5034
|
+
info(`Running ${worktrees.length} experiments in parallel...`);
|
|
5035
|
+
info("");
|
|
5036
|
+
const settled = await Promise.allSettled(
|
|
5037
|
+
worktrees.map((wt) => runExperimentInWorktree(wt))
|
|
5038
|
+
);
|
|
5039
|
+
const results = settled.map((s, i) => {
|
|
5040
|
+
if (s.status === "fulfilled") return s.value;
|
|
5041
|
+
return {
|
|
5042
|
+
worktree: worktrees[i],
|
|
5043
|
+
experiment: null,
|
|
5044
|
+
finalStatus: "error",
|
|
5045
|
+
overallGrade: null,
|
|
5046
|
+
costUsd: 0,
|
|
5047
|
+
stepCount: 0,
|
|
5048
|
+
error: s.reason instanceof Error ? s.reason.message : String(s.reason)
|
|
5049
|
+
};
|
|
5050
|
+
});
|
|
5051
|
+
for (const r of results) {
|
|
5052
|
+
updateSwarmMember(
|
|
5053
|
+
db,
|
|
5054
|
+
swarmRun.id,
|
|
5055
|
+
r.worktree.slug,
|
|
5056
|
+
r.finalStatus,
|
|
5057
|
+
r.overallGrade,
|
|
5058
|
+
r.costUsd,
|
|
5059
|
+
r.error ?? null
|
|
5060
|
+
);
|
|
5061
|
+
}
|
|
5062
|
+
info("");
|
|
5063
|
+
header("Aggregation");
|
|
5064
|
+
const summary = aggregateSwarmResults(root, db, results);
|
|
5065
|
+
summary.goal = goal;
|
|
5066
|
+
if (summary.bestExperiment && isMergeable(summary.bestExperiment.overallGrade)) {
|
|
5067
|
+
const best = summary.bestExperiment;
|
|
5068
|
+
info(`Best experiment: ${best.worktree.slug} (${best.overallGrade})`);
|
|
5069
|
+
try {
|
|
5070
|
+
(0, import_node_child_process7.execSync)(
|
|
5071
|
+
`git merge ${best.worktree.branch} --no-ff -m "Merge swarm winner: ${best.worktree.slug}"`,
|
|
5072
|
+
{ cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5073
|
+
);
|
|
5074
|
+
success(`Merged ${best.worktree.slug} into main.`);
|
|
5075
|
+
} catch {
|
|
5076
|
+
warn(`Git merge of ${best.worktree.slug} failed. Merge manually with:`);
|
|
5077
|
+
info(` git merge ${best.worktree.branch} --no-ff`);
|
|
5078
|
+
}
|
|
5079
|
+
} else {
|
|
5080
|
+
info("No experiment achieved sound/good grade. Nothing merged.");
|
|
5081
|
+
}
|
|
5082
|
+
for (const r of results) {
|
|
5083
|
+
if (r === summary.bestExperiment || r.error || !r.experiment) continue;
|
|
5084
|
+
const mainExp = getExperimentBySlug(db, r.worktree.slug);
|
|
5085
|
+
if (mainExp && mainExp.status !== "dead_end") {
|
|
5086
|
+
updateExperimentStatus(db, mainExp.id, "dead_end");
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
updateSwarmRun(
|
|
5090
|
+
db,
|
|
5091
|
+
swarmRun.id,
|
|
5092
|
+
summary.errorCount === results.length ? "failed" : "completed",
|
|
5093
|
+
summary.totalCostUsd,
|
|
5094
|
+
summary.bestExperiment?.worktree.slug ?? null
|
|
5095
|
+
);
|
|
5096
|
+
info("Cleaning up worktrees...");
|
|
5097
|
+
for (const wt of worktrees) {
|
|
5098
|
+
cleanupWorktree(root, wt);
|
|
5099
|
+
}
|
|
5100
|
+
info("");
|
|
5101
|
+
header("Swarm Summary");
|
|
5102
|
+
info(`Goal: ${goal}`);
|
|
5103
|
+
info(`Parallel: ${worktrees.length}`);
|
|
5104
|
+
info(`Results:`);
|
|
5105
|
+
for (const r of results) {
|
|
5106
|
+
const grade = r.overallGrade ?? "n/a";
|
|
5107
|
+
const status2 = r.error ? `ERROR: ${r.error.slice(0, 60)}` : r.finalStatus;
|
|
5108
|
+
const marker = r === summary.bestExperiment ? " <-- BEST" : "";
|
|
5109
|
+
info(` ${r.worktree.paddedNum} ${r.worktree.slug}: ${grade} (${status2})${marker}`);
|
|
5110
|
+
}
|
|
5111
|
+
info(`Merged: ${summary.mergedCount} | Dead-ends: ${summary.deadEndCount} | Errors: ${summary.errorCount}`);
|
|
5112
|
+
}
|
|
5113
|
+
function isMergeable(grade) {
|
|
5114
|
+
return grade === "sound" || grade === "good";
|
|
5115
|
+
}
|
|
5116
|
+
async function deriveMultipleHypotheses(goal, root, count) {
|
|
5117
|
+
const synthesis = truncateContext(
|
|
5118
|
+
readFileOrEmpty(path14.join(root, "docs", "synthesis", "current.md")),
|
|
5119
|
+
CONTEXT_LIMITS.synthesis
|
|
5120
|
+
);
|
|
5121
|
+
const fragility = truncateContext(
|
|
5122
|
+
readFileOrEmpty(path14.join(root, "docs", "synthesis", "fragility.md")),
|
|
5123
|
+
CONTEXT_LIMITS.fragility
|
|
5124
|
+
);
|
|
5125
|
+
const deadEndsDoc = truncateContext(
|
|
5126
|
+
readFileOrEmpty(path14.join(root, "docs", "synthesis", "dead-ends.md")),
|
|
5127
|
+
CONTEXT_LIMITS.deadEnds
|
|
5128
|
+
);
|
|
5129
|
+
const db = getDb(root);
|
|
5130
|
+
const deadEnds = listAllDeadEnds(db);
|
|
5131
|
+
const config = loadConfig(root);
|
|
5132
|
+
let metricsOutput = "";
|
|
5133
|
+
if (config.metrics?.command) {
|
|
5134
|
+
try {
|
|
5135
|
+
metricsOutput = (0, import_node_child_process7.execSync)(config.metrics.command, {
|
|
5136
|
+
cwd: root,
|
|
5137
|
+
encoding: "utf-8",
|
|
5138
|
+
timeout: 6e4,
|
|
5139
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5140
|
+
}).trim();
|
|
5141
|
+
} catch {
|
|
5142
|
+
metricsOutput = "(metrics command failed)";
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
5145
|
+
const result = await spawnSynthesiser({
|
|
5146
|
+
taskPrompt: `You are the Planner for a parallel Majlis swarm.
|
|
5147
|
+
|
|
5148
|
+
## Goal
|
|
5149
|
+
${goal}
|
|
5150
|
+
|
|
5151
|
+
## Current Metrics
|
|
5152
|
+
${metricsOutput || "(no metrics configured)"}
|
|
5153
|
+
|
|
5154
|
+
## Synthesis (what we know so far)
|
|
5155
|
+
${synthesis || "(empty \u2014 first experiment)"}
|
|
5156
|
+
|
|
5157
|
+
## Fragility Map (known weak areas)
|
|
5158
|
+
${fragility || "(none)"}
|
|
5159
|
+
|
|
5160
|
+
## Dead-End Registry
|
|
5161
|
+
${deadEndsDoc || "(none)"}
|
|
5162
|
+
|
|
5163
|
+
## Dead Ends (from DB \u2014 ${deadEnds.length} total)
|
|
5164
|
+
${deadEnds.map((d) => `- [${d.category ?? "structural"}] ${d.approach}: ${d.why_failed} [constraint: ${d.structural_constraint}]`).join("\n") || "(none)"}
|
|
5165
|
+
|
|
5166
|
+
Note: [structural] dead ends are HARD CONSTRAINTS \u2014 hypotheses MUST NOT repeat these approaches.
|
|
5167
|
+
[procedural] dead ends are process failures \u2014 the approach may still be valid if executed differently.
|
|
5168
|
+
|
|
5169
|
+
## Your Task
|
|
5170
|
+
1. Assess: based on the metrics and synthesis, has the goal been met? Be specific.
|
|
5171
|
+
2. If YES \u2014 output the JSON block below with goal_met: true.
|
|
5172
|
+
3. If NO \u2014 generate exactly ${count} DIVERSE hypotheses for parallel testing.
|
|
5173
|
+
|
|
5174
|
+
Requirements for hypotheses:
|
|
5175
|
+
- Each must attack the problem from a DIFFERENT angle
|
|
5176
|
+
- They must NOT share the same mechanism, function target, or strategy
|
|
5177
|
+
- At least one should be an unconventional or indirect approach
|
|
5178
|
+
- None may repeat a dead-ended structural approach
|
|
5179
|
+
- Each must be specific and actionable \u2014 name the function or mechanism to change
|
|
5180
|
+
- Do NOT reference specific line numbers \u2014 they shift between experiments
|
|
5181
|
+
|
|
5182
|
+
CRITICAL: Your LAST line of output MUST be EXACTLY this format (on its own line, nothing after it):
|
|
5183
|
+
<!-- majlis-json {"goal_met": false, "hypotheses": ["hypothesis 1", "hypothesis 2", "hypothesis 3"]} -->
|
|
5184
|
+
|
|
5185
|
+
If the goal is met:
|
|
5186
|
+
<!-- majlis-json {"goal_met": true, "hypotheses": []} -->`
|
|
5187
|
+
}, root);
|
|
5188
|
+
if (result.structured?.goal_met === true) return [];
|
|
5189
|
+
if (result.structured?.hypotheses && Array.isArray(result.structured.hypotheses)) {
|
|
5190
|
+
return result.structured.hypotheses.filter(
|
|
5191
|
+
(h) => typeof h === "string" && h.length > 10
|
|
5192
|
+
);
|
|
5193
|
+
}
|
|
5194
|
+
const blockMatch = result.output.match(/<!--\s*majlis-json\s*(\{[\s\S]*?\})\s*-->/);
|
|
5195
|
+
if (blockMatch) {
|
|
5196
|
+
try {
|
|
5197
|
+
const parsed = JSON.parse(blockMatch[1]);
|
|
5198
|
+
if (parsed.goal_met === true) return [];
|
|
5199
|
+
if (Array.isArray(parsed.hypotheses)) {
|
|
5200
|
+
return parsed.hypotheses.filter(
|
|
5201
|
+
(h) => typeof h === "string" && h.length > 10
|
|
5202
|
+
);
|
|
5203
|
+
}
|
|
5204
|
+
} catch {
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5207
|
+
warn("Planner did not return structured hypotheses. Using goal as single hypothesis.");
|
|
5208
|
+
return [goal];
|
|
5209
|
+
}
|
|
5210
|
+
var path14, import_node_child_process7, MAX_PARALLEL, DEFAULT_PARALLEL;
|
|
5211
|
+
var init_swarm = __esm({
|
|
5212
|
+
"src/commands/swarm.ts"() {
|
|
5213
|
+
"use strict";
|
|
5214
|
+
path14 = __toESM(require("path"));
|
|
5215
|
+
import_node_child_process7 = require("child_process");
|
|
5216
|
+
init_connection();
|
|
5217
|
+
init_queries();
|
|
5218
|
+
init_spawn();
|
|
5219
|
+
init_config();
|
|
5220
|
+
init_worktree();
|
|
5221
|
+
init_runner();
|
|
5222
|
+
init_aggregate();
|
|
5223
|
+
init_format();
|
|
5224
|
+
MAX_PARALLEL = 8;
|
|
5225
|
+
DEFAULT_PARALLEL = 3;
|
|
5226
|
+
}
|
|
5227
|
+
});
|
|
5228
|
+
|
|
5229
|
+
// src/commands/diagnose.ts
|
|
5230
|
+
var diagnose_exports = {};
|
|
5231
|
+
__export(diagnose_exports, {
|
|
5232
|
+
diagnose: () => diagnose
|
|
5233
|
+
});
|
|
5234
|
+
async function diagnose(args) {
|
|
5235
|
+
const root = findProjectRoot();
|
|
5236
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5237
|
+
const db = getDb(root);
|
|
5238
|
+
const focus = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
5239
|
+
const keepScripts = args.includes("--keep-scripts");
|
|
5240
|
+
const scriptsDir = path15.join(root, ".majlis", "scripts");
|
|
5241
|
+
if (!fs14.existsSync(scriptsDir)) {
|
|
5242
|
+
fs14.mkdirSync(scriptsDir, { recursive: true });
|
|
5243
|
+
}
|
|
5244
|
+
header("Deep Diagnosis");
|
|
5245
|
+
if (focus) info(`Focus: ${focus}`);
|
|
5246
|
+
const dbExport = exportForDiagnostician(db);
|
|
5247
|
+
const synthesis = readFileOrEmpty(path15.join(root, "docs", "synthesis", "current.md"));
|
|
5248
|
+
const fragility = readFileOrEmpty(path15.join(root, "docs", "synthesis", "fragility.md"));
|
|
5249
|
+
const deadEndsDoc = readFileOrEmpty(path15.join(root, "docs", "synthesis", "dead-ends.md"));
|
|
5250
|
+
const config = loadConfig(root);
|
|
5251
|
+
let metricsOutput = "";
|
|
5252
|
+
if (config.metrics?.command) {
|
|
5253
|
+
try {
|
|
5254
|
+
metricsOutput = (0, import_node_child_process8.execSync)(config.metrics.command, {
|
|
5255
|
+
cwd: root,
|
|
5256
|
+
encoding: "utf-8",
|
|
5257
|
+
timeout: 6e4,
|
|
5258
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5259
|
+
}).trim();
|
|
5260
|
+
} catch {
|
|
5261
|
+
metricsOutput = "(metrics command failed)";
|
|
5262
|
+
}
|
|
5263
|
+
}
|
|
5264
|
+
let taskPrompt = `## Full Database Export (CANONICAL \u2014 source of truth)
|
|
5265
|
+
${dbExport}
|
|
5266
|
+
|
|
5267
|
+
`;
|
|
5268
|
+
taskPrompt += `## Current Synthesis
|
|
5269
|
+
${synthesis || "(empty \u2014 no experiments yet)"}
|
|
5270
|
+
|
|
5271
|
+
`;
|
|
5272
|
+
taskPrompt += `## Fragility Map
|
|
5273
|
+
${fragility || "(none)"}
|
|
5274
|
+
|
|
5275
|
+
`;
|
|
5276
|
+
taskPrompt += `## Dead-End Registry
|
|
5277
|
+
${deadEndsDoc || "(none)"}
|
|
5278
|
+
|
|
5279
|
+
`;
|
|
5280
|
+
taskPrompt += `## Current Metrics
|
|
5281
|
+
${metricsOutput || "(no metrics configured)"}
|
|
5282
|
+
|
|
5283
|
+
`;
|
|
5284
|
+
taskPrompt += `## Project Objective
|
|
5285
|
+
${config.project?.objective || "(not specified)"}
|
|
5286
|
+
|
|
5287
|
+
`;
|
|
5288
|
+
if (focus) {
|
|
5289
|
+
taskPrompt += `## Focus Area
|
|
5290
|
+
The user has asked you to focus your diagnosis on: ${focus}
|
|
5291
|
+
|
|
5292
|
+
`;
|
|
5293
|
+
}
|
|
5294
|
+
taskPrompt += `## Your Task
|
|
5295
|
+
Perform a deep diagnostic analysis of this project. Identify root causes, recurring patterns, evidence gaps, and investigation directions. You have 60 turns \u2014 use them for depth. Write analysis scripts to .majlis/scripts/ as needed.
|
|
5296
|
+
|
|
5297
|
+
Remember: you may write files ONLY to .majlis/scripts/. You cannot modify project code.`;
|
|
5298
|
+
info("Spawning diagnostician (60 turns, full DB access)...");
|
|
5299
|
+
const result = await spawnAgent("diagnostician", { taskPrompt }, root);
|
|
5300
|
+
const diagnosisDir = path15.join(root, "docs", "diagnosis");
|
|
5301
|
+
if (!fs14.existsSync(diagnosisDir)) {
|
|
5302
|
+
fs14.mkdirSync(diagnosisDir, { recursive: true });
|
|
5303
|
+
}
|
|
5304
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
5305
|
+
const artifactPath = path15.join(diagnosisDir, `diagnosis-${timestamp}.md`);
|
|
5306
|
+
fs14.writeFileSync(artifactPath, result.output);
|
|
5307
|
+
info(`Diagnostic report: docs/diagnosis/diagnosis-${timestamp}.md`);
|
|
5308
|
+
if (result.structured?.diagnosis) {
|
|
5309
|
+
const d = result.structured.diagnosis;
|
|
5310
|
+
if (d.root_causes?.length) {
|
|
5311
|
+
info(`Root causes identified: ${d.root_causes.length}`);
|
|
5312
|
+
}
|
|
5313
|
+
if (d.investigation_directions?.length) {
|
|
5314
|
+
info(`Investigation directions: ${d.investigation_directions.length}`);
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
if (!keepScripts) {
|
|
5318
|
+
try {
|
|
5319
|
+
const files = fs14.readdirSync(scriptsDir);
|
|
5320
|
+
for (const f of files) {
|
|
5321
|
+
fs14.unlinkSync(path15.join(scriptsDir, f));
|
|
5322
|
+
}
|
|
5323
|
+
fs14.rmdirSync(scriptsDir);
|
|
5324
|
+
info("Cleaned up .majlis/scripts/");
|
|
5325
|
+
} catch {
|
|
5326
|
+
}
|
|
5327
|
+
} else {
|
|
5328
|
+
info("Scripts preserved in .majlis/scripts/ (--keep-scripts)");
|
|
5329
|
+
}
|
|
5330
|
+
if (result.truncated) {
|
|
5331
|
+
warn("Diagnostician was truncated (hit 60 turn limit).");
|
|
5332
|
+
}
|
|
5333
|
+
success("Diagnosis complete.");
|
|
5334
|
+
}
|
|
5335
|
+
var fs14, path15, import_node_child_process8;
|
|
5336
|
+
var init_diagnose = __esm({
|
|
5337
|
+
"src/commands/diagnose.ts"() {
|
|
5338
|
+
"use strict";
|
|
5339
|
+
fs14 = __toESM(require("fs"));
|
|
5340
|
+
path15 = __toESM(require("path"));
|
|
5341
|
+
import_node_child_process8 = require("child_process");
|
|
5342
|
+
init_connection();
|
|
5343
|
+
init_queries();
|
|
5344
|
+
init_spawn();
|
|
5345
|
+
init_config();
|
|
5346
|
+
init_format();
|
|
5347
|
+
}
|
|
5348
|
+
});
|
|
5349
|
+
|
|
4237
5350
|
// src/cli.ts
|
|
4238
|
-
var
|
|
4239
|
-
var
|
|
5351
|
+
var fs15 = __toESM(require("fs"));
|
|
5352
|
+
var path16 = __toESM(require("path"));
|
|
4240
5353
|
var VERSION = JSON.parse(
|
|
4241
|
-
|
|
5354
|
+
fs15.readFileSync(path16.join(__dirname, "..", "package.json"), "utf-8")
|
|
4242
5355
|
).version;
|
|
4243
5356
|
async function main() {
|
|
4244
5357
|
let sigintCount = 0;
|
|
@@ -4348,11 +5461,21 @@ async function main() {
|
|
|
4348
5461
|
await run2(rest);
|
|
4349
5462
|
break;
|
|
4350
5463
|
}
|
|
5464
|
+
case "swarm": {
|
|
5465
|
+
const { swarm: swarm2 } = await Promise.resolve().then(() => (init_swarm(), swarm_exports));
|
|
5466
|
+
await swarm2(rest);
|
|
5467
|
+
break;
|
|
5468
|
+
}
|
|
4351
5469
|
case "audit": {
|
|
4352
5470
|
const { audit: audit2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4353
5471
|
await audit2(rest);
|
|
4354
5472
|
break;
|
|
4355
5473
|
}
|
|
5474
|
+
case "diagnose": {
|
|
5475
|
+
const { diagnose: diagnose2 } = await Promise.resolve().then(() => (init_diagnose(), diagnose_exports));
|
|
5476
|
+
await diagnose2(rest);
|
|
5477
|
+
break;
|
|
5478
|
+
}
|
|
4356
5479
|
default:
|
|
4357
5480
|
console.error(`Unknown command: ${command}`);
|
|
4358
5481
|
printHelp();
|
|
@@ -4405,6 +5528,7 @@ Queries:
|
|
|
4405
5528
|
|
|
4406
5529
|
Audit:
|
|
4407
5530
|
audit "objective" Maqasid check \u2014 is the frame right?
|
|
5531
|
+
diagnose ["focus area"] Deep diagnosis \u2014 root causes, patterns, gaps
|
|
4408
5532
|
|
|
4409
5533
|
Sessions:
|
|
4410
5534
|
session start "intent" Declare session intent
|
|
@@ -4412,6 +5536,7 @@ Sessions:
|
|
|
4412
5536
|
|
|
4413
5537
|
Orchestration:
|
|
4414
5538
|
run "goal" Autonomous orchestration until goal met
|
|
5539
|
+
swarm "goal" [--parallel N] Run N experiments in parallel worktrees
|
|
4415
5540
|
|
|
4416
5541
|
Flags:
|
|
4417
5542
|
--json Output as JSON
|