majlis 0.5.2 → 0.6.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/cli.js +1249 -50
- 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;
|
|
@@ -1690,6 +1903,16 @@ function truncateContext(content, limit) {
|
|
|
1690
1903
|
if (content.length <= limit) return content;
|
|
1691
1904
|
return content.slice(0, limit) + "\n[TRUNCATED]";
|
|
1692
1905
|
}
|
|
1906
|
+
function readLatestDiagnosis(projectRoot) {
|
|
1907
|
+
const dir = path3.join(projectRoot, "docs", "diagnosis");
|
|
1908
|
+
try {
|
|
1909
|
+
const files = fs3.readdirSync(dir).filter((f) => f.startsWith("diagnosis-") && f.endsWith(".md")).sort().reverse();
|
|
1910
|
+
if (files.length === 0) return "";
|
|
1911
|
+
return fs3.readFileSync(path3.join(dir, files[0]), "utf-8");
|
|
1912
|
+
} catch {
|
|
1913
|
+
return "";
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1693
1916
|
var fs3, path3, DEFAULT_CONFIG2, _cachedConfig, _cachedRoot, CONTEXT_LIMITS;
|
|
1694
1917
|
var init_config = __esm({
|
|
1695
1918
|
"src/config.ts"() {
|
|
@@ -1838,6 +2061,8 @@ function getExtractionSchema(role) {
|
|
|
1838
2061
|
return '{"findings": [{"approach": "string", "source": "string", "relevance": "string", "contradicts_current": true}]}';
|
|
1839
2062
|
case "compressor":
|
|
1840
2063
|
return '{"compression_report": {"synthesis_delta": "string", "new_dead_ends": ["string"], "fragility_changes": ["string"]}}';
|
|
2064
|
+
case "diagnostician":
|
|
2065
|
+
return '{"diagnosis": {"root_causes": ["string"], "patterns": ["string"], "evidence_gaps": ["string"], "investigation_directions": ["string"]}}';
|
|
1841
2066
|
default:
|
|
1842
2067
|
return EXTRACTION_SCHEMA;
|
|
1843
2068
|
}
|
|
@@ -1861,7 +2086,8 @@ var init_types = __esm({
|
|
|
1861
2086
|
gatekeeper: ["gate_decision"],
|
|
1862
2087
|
reframer: ["reframe"],
|
|
1863
2088
|
scout: ["findings"],
|
|
1864
|
-
compressor: ["compression_report"]
|
|
2089
|
+
compressor: ["compression_report"],
|
|
2090
|
+
diagnostician: ["diagnosis"]
|
|
1865
2091
|
};
|
|
1866
2092
|
}
|
|
1867
2093
|
});
|
|
@@ -1996,7 +2222,7 @@ ${truncated}`;
|
|
|
1996
2222
|
}
|
|
1997
2223
|
}
|
|
1998
2224
|
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);
|
|
2225
|
+
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
2226
|
}
|
|
2001
2227
|
function validateForRole(role, output) {
|
|
2002
2228
|
const required = ROLE_REQUIRED_FIELDS[role];
|
|
@@ -2069,6 +2295,12 @@ You may ONLY write to docs/synthesis/.
|
|
|
2069
2295
|
- Have you updated current.md, fragility.md, dead-ends.md?
|
|
2070
2296
|
- If yes \u2192 output compression report JSON.
|
|
2071
2297
|
- Do NOT write to MEMORY.md or files outside docs/synthesis/.`;
|
|
2298
|
+
case "diagnostician":
|
|
2299
|
+
return `${header2}
|
|
2300
|
+
You are READ-ONLY for project code. Write ONLY to .majlis/scripts/.
|
|
2301
|
+
Focus on diagnosis, not fixing. Your value is insight, not implementation.
|
|
2302
|
+
Phase 1 (1-10): orientation. Phase 2 (11-40): deep investigation. Phase 3 (41-60): synthesis.
|
|
2303
|
+
If you are past turn 40, begin compiling your diagnostic report.`;
|
|
2072
2304
|
default:
|
|
2073
2305
|
return `${header2}
|
|
2074
2306
|
Check: is your core task done? If yes, wrap up and output JSON.`;
|
|
@@ -2092,6 +2324,36 @@ function buildPreToolUseGuards(role) {
|
|
|
2092
2324
|
{ matcher: "Edit", hooks: [guardHook] }
|
|
2093
2325
|
];
|
|
2094
2326
|
}
|
|
2327
|
+
if (role === "diagnostician") {
|
|
2328
|
+
const writeGuard = async (input) => {
|
|
2329
|
+
const toolInput = input.tool_input ?? {};
|
|
2330
|
+
const filePath = toolInput.file_path ?? "";
|
|
2331
|
+
if (filePath && !filePath.includes("/.majlis/scripts/")) {
|
|
2332
|
+
return {
|
|
2333
|
+
decision: "block",
|
|
2334
|
+
reason: `Diagnostician may only write to .majlis/scripts/. Blocked: ${filePath}`
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
return {};
|
|
2338
|
+
};
|
|
2339
|
+
const bashGuard = async (input) => {
|
|
2340
|
+
const toolInput = input.tool_input ?? {};
|
|
2341
|
+
const command = toolInput.command ?? "";
|
|
2342
|
+
const destructive = /\b(rm\s+-rf|git\s+(checkout|reset|stash|clean|push)|chmod|chown|mkfs|dd\s+if=)\b/i;
|
|
2343
|
+
if (destructive.test(command)) {
|
|
2344
|
+
return {
|
|
2345
|
+
decision: "block",
|
|
2346
|
+
reason: `Diagnostician blocked destructive command: ${command.slice(0, 100)}`
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
return {};
|
|
2350
|
+
};
|
|
2351
|
+
return [
|
|
2352
|
+
{ matcher: "Write", hooks: [writeGuard] },
|
|
2353
|
+
{ matcher: "Edit", hooks: [writeGuard] },
|
|
2354
|
+
{ matcher: "Bash", hooks: [bashGuard] }
|
|
2355
|
+
];
|
|
2356
|
+
}
|
|
2095
2357
|
return void 0;
|
|
2096
2358
|
}
|
|
2097
2359
|
function buildAgentHooks(role, maxTurns) {
|
|
@@ -2165,8 +2427,10 @@ ${taskPrompt}`;
|
|
|
2165
2427
|
}
|
|
2166
2428
|
return { output: markdown, structured, truncated };
|
|
2167
2429
|
}
|
|
2168
|
-
async function spawnSynthesiser(context, projectRoot) {
|
|
2430
|
+
async function spawnSynthesiser(context, projectRoot, opts) {
|
|
2169
2431
|
const root = projectRoot ?? findProjectRoot() ?? process.cwd();
|
|
2432
|
+
const maxTurns = opts?.maxTurns ?? 5;
|
|
2433
|
+
const tools = opts?.tools ?? ["Read", "Glob", "Grep"];
|
|
2170
2434
|
const contextJson = JSON.stringify(context);
|
|
2171
2435
|
const taskPrompt = context.taskPrompt ?? "Synthesise the findings into actionable builder guidance.";
|
|
2172
2436
|
const prompt = `Here is your context:
|
|
@@ -2177,14 +2441,14 @@ ${contextJson}
|
|
|
2177
2441
|
|
|
2178
2442
|
${taskPrompt}`;
|
|
2179
2443
|
const systemPrompt = 'You are a Synthesis Agent. Be concrete: which decisions failed, which assumptions broke, what constraints must the next approach satisfy. CRITICAL: Your LAST line of output MUST be a <!-- majlis-json --> block. The framework parses this programmatically \u2014 if you omit it, the pipeline breaks. Format: <!-- majlis-json {"guidance": "your guidance here"} -->';
|
|
2180
|
-
console.log(`[synthesiser] Spawning (maxTurns:
|
|
2444
|
+
console.log(`[synthesiser] Spawning (maxTurns: ${maxTurns})...`);
|
|
2181
2445
|
const { text: markdown, costUsd, truncated } = await runQuery({
|
|
2182
2446
|
prompt,
|
|
2183
2447
|
model: "sonnet",
|
|
2184
|
-
tools
|
|
2448
|
+
tools,
|
|
2185
2449
|
systemPrompt,
|
|
2186
2450
|
cwd: root,
|
|
2187
|
-
maxTurns
|
|
2451
|
+
maxTurns,
|
|
2188
2452
|
label: "synthesiser",
|
|
2189
2453
|
role: "synthesiser"
|
|
2190
2454
|
});
|
|
@@ -2365,7 +2629,7 @@ function writeArtifact(role, context, markdown, projectRoot) {
|
|
|
2365
2629
|
};
|
|
2366
2630
|
const dir = dirMap[role];
|
|
2367
2631
|
if (!dir) return null;
|
|
2368
|
-
if (role === "builder" || role === "compressor") return null;
|
|
2632
|
+
if (role === "builder" || role === "compressor" || role === "diagnostician") return null;
|
|
2369
2633
|
const fullDir = path4.join(projectRoot, dir);
|
|
2370
2634
|
if (!fs4.existsSync(fullDir)) {
|
|
2371
2635
|
fs4.mkdirSync(fullDir, { recursive: true });
|
|
@@ -2394,14 +2658,16 @@ var init_spawn = __esm({
|
|
|
2394
2658
|
compressor: 30,
|
|
2395
2659
|
reframer: 20,
|
|
2396
2660
|
scout: 20,
|
|
2397
|
-
gatekeeper: 10
|
|
2661
|
+
gatekeeper: 10,
|
|
2662
|
+
diagnostician: 60
|
|
2398
2663
|
};
|
|
2399
2664
|
CHECKPOINT_INTERVAL = {
|
|
2400
2665
|
builder: 15,
|
|
2401
2666
|
verifier: 12,
|
|
2402
2667
|
critic: 15,
|
|
2403
2668
|
adversary: 15,
|
|
2404
|
-
compressor: 15
|
|
2669
|
+
compressor: 15,
|
|
2670
|
+
diagnostician: 20
|
|
2405
2671
|
};
|
|
2406
2672
|
DIM2 = "\x1B[2m";
|
|
2407
2673
|
RESET2 = "\x1B[0m";
|
|
@@ -2409,6 +2675,38 @@ var init_spawn = __esm({
|
|
|
2409
2675
|
}
|
|
2410
2676
|
});
|
|
2411
2677
|
|
|
2678
|
+
// src/git.ts
|
|
2679
|
+
function autoCommit(root, message) {
|
|
2680
|
+
try {
|
|
2681
|
+
(0, import_node_child_process.execSync)("git add docs/ .majlis/scripts/ 2>/dev/null; true", {
|
|
2682
|
+
cwd: root,
|
|
2683
|
+
encoding: "utf-8",
|
|
2684
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2685
|
+
});
|
|
2686
|
+
const diff = (0, import_node_child_process.execSync)("git diff --cached --stat", {
|
|
2687
|
+
cwd: root,
|
|
2688
|
+
encoding: "utf-8",
|
|
2689
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2690
|
+
}).trim();
|
|
2691
|
+
if (!diff) return;
|
|
2692
|
+
(0, import_node_child_process.execSync)(`git commit -m ${JSON.stringify(`[majlis] ${message}`)}`, {
|
|
2693
|
+
cwd: root,
|
|
2694
|
+
encoding: "utf-8",
|
|
2695
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2696
|
+
});
|
|
2697
|
+
info(`Auto-committed: ${message}`);
|
|
2698
|
+
} catch {
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
var import_node_child_process;
|
|
2702
|
+
var init_git = __esm({
|
|
2703
|
+
"src/git.ts"() {
|
|
2704
|
+
"use strict";
|
|
2705
|
+
import_node_child_process = require("child_process");
|
|
2706
|
+
init_format();
|
|
2707
|
+
}
|
|
2708
|
+
});
|
|
2709
|
+
|
|
2412
2710
|
// src/metrics.ts
|
|
2413
2711
|
function compareMetrics(db, experimentId, config) {
|
|
2414
2712
|
const before = getMetricsByExperimentAndPhase(db, experimentId, "before");
|
|
@@ -2498,7 +2796,7 @@ async function captureMetrics(phase, args) {
|
|
|
2498
2796
|
if (config.build.pre_measure) {
|
|
2499
2797
|
info(`Running pre-measure: ${config.build.pre_measure}`);
|
|
2500
2798
|
try {
|
|
2501
|
-
(0,
|
|
2799
|
+
(0, import_node_child_process2.execSync)(config.build.pre_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
|
|
2502
2800
|
} catch {
|
|
2503
2801
|
warn("Pre-measure command failed \u2014 continuing anyway.");
|
|
2504
2802
|
}
|
|
@@ -2509,7 +2807,7 @@ async function captureMetrics(phase, args) {
|
|
|
2509
2807
|
info(`Running metrics: ${config.metrics.command}`);
|
|
2510
2808
|
let metricsOutput;
|
|
2511
2809
|
try {
|
|
2512
|
-
metricsOutput = (0,
|
|
2810
|
+
metricsOutput = (0, import_node_child_process2.execSync)(config.metrics.command, {
|
|
2513
2811
|
cwd: root,
|
|
2514
2812
|
encoding: "utf-8",
|
|
2515
2813
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2528,7 +2826,7 @@ async function captureMetrics(phase, args) {
|
|
|
2528
2826
|
success(`Captured ${parsed.length} metric(s) for ${exp.slug} (phase: ${phase})`);
|
|
2529
2827
|
if (config.build.post_measure) {
|
|
2530
2828
|
try {
|
|
2531
|
-
(0,
|
|
2829
|
+
(0, import_node_child_process2.execSync)(config.build.post_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
|
|
2532
2830
|
} catch {
|
|
2533
2831
|
warn("Post-measure command failed.");
|
|
2534
2832
|
}
|
|
@@ -2579,11 +2877,11 @@ function formatDelta(delta) {
|
|
|
2579
2877
|
const prefix = delta > 0 ? "+" : "";
|
|
2580
2878
|
return `${prefix}${delta.toFixed(4)}`;
|
|
2581
2879
|
}
|
|
2582
|
-
var
|
|
2880
|
+
var import_node_child_process2;
|
|
2583
2881
|
var init_measure = __esm({
|
|
2584
2882
|
"src/commands/measure.ts"() {
|
|
2585
2883
|
"use strict";
|
|
2586
|
-
|
|
2884
|
+
import_node_child_process2 = require("child_process");
|
|
2587
2885
|
init_connection();
|
|
2588
2886
|
init_queries();
|
|
2589
2887
|
init_metrics();
|
|
@@ -2616,7 +2914,7 @@ async function newExperiment(args) {
|
|
|
2616
2914
|
const paddedNum = String(num).padStart(3, "0");
|
|
2617
2915
|
const branch = `exp/${paddedNum}-${slug}`;
|
|
2618
2916
|
try {
|
|
2619
|
-
(0,
|
|
2917
|
+
(0, import_node_child_process3.execSync)(`git checkout -b ${branch}`, {
|
|
2620
2918
|
cwd: root,
|
|
2621
2919
|
encoding: "utf-8",
|
|
2622
2920
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2637,6 +2935,7 @@ async function newExperiment(args) {
|
|
|
2637
2935
|
fs5.writeFileSync(logPath, logContent);
|
|
2638
2936
|
info(`Created experiment log: docs/experiments/${paddedNum}-${slug}.md`);
|
|
2639
2937
|
}
|
|
2938
|
+
autoCommit(root, `new: ${slug}`);
|
|
2640
2939
|
if (config.cycle.auto_baseline_on_new_experiment && config.metrics.command) {
|
|
2641
2940
|
info("Auto-baselining... (run `majlis baseline` to do this manually)");
|
|
2642
2941
|
try {
|
|
@@ -2673,12 +2972,12 @@ async function revert(args) {
|
|
|
2673
2972
|
);
|
|
2674
2973
|
updateExperimentStatus(db, exp.id, "dead_end");
|
|
2675
2974
|
try {
|
|
2676
|
-
const currentBranch = (0,
|
|
2975
|
+
const currentBranch = (0, import_node_child_process3.execSync)("git rev-parse --abbrev-ref HEAD", {
|
|
2677
2976
|
cwd: root,
|
|
2678
2977
|
encoding: "utf-8"
|
|
2679
2978
|
}).trim();
|
|
2680
2979
|
if (currentBranch === exp.branch) {
|
|
2681
|
-
(0,
|
|
2980
|
+
(0, import_node_child_process3.execSync)("git checkout main 2>/dev/null || git checkout master", {
|
|
2682
2981
|
cwd: root,
|
|
2683
2982
|
encoding: "utf-8",
|
|
2684
2983
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2689,17 +2988,18 @@ async function revert(args) {
|
|
|
2689
2988
|
}
|
|
2690
2989
|
info(`Experiment ${exp.slug} reverted to dead-end. Reason: ${reason}`);
|
|
2691
2990
|
}
|
|
2692
|
-
var fs5, path5,
|
|
2991
|
+
var fs5, path5, import_node_child_process3;
|
|
2693
2992
|
var init_experiment = __esm({
|
|
2694
2993
|
"src/commands/experiment.ts"() {
|
|
2695
2994
|
"use strict";
|
|
2696
2995
|
fs5 = __toESM(require("fs"));
|
|
2697
2996
|
path5 = __toESM(require("path"));
|
|
2698
|
-
|
|
2997
|
+
import_node_child_process3 = require("child_process");
|
|
2699
2998
|
init_connection();
|
|
2700
2999
|
init_queries();
|
|
2701
3000
|
init_config();
|
|
2702
3001
|
init_spawn();
|
|
3002
|
+
init_git();
|
|
2703
3003
|
init_format();
|
|
2704
3004
|
}
|
|
2705
3005
|
});
|
|
@@ -3047,6 +3347,7 @@ async function resolve(db, exp, projectRoot) {
|
|
|
3047
3347
|
gitMerge(exp.branch, projectRoot);
|
|
3048
3348
|
const gaps = grades.filter((g) => g.grade === "good").map((g) => `- **${g.component}**: ${g.notes ?? "minor gaps"}`).join("\n");
|
|
3049
3349
|
appendToFragilityMap(projectRoot, exp.slug, gaps);
|
|
3350
|
+
autoCommit(projectRoot, `resolve: fragility gaps from ${exp.slug}`);
|
|
3050
3351
|
updateExperimentStatus(db, exp.id, "merged");
|
|
3051
3352
|
success(`Experiment ${exp.slug} MERGED (good, ${grades.filter((g) => g.grade === "good").length} gaps added to fragility map).`);
|
|
3052
3353
|
break;
|
|
@@ -3101,9 +3402,92 @@ async function resolve(db, exp, projectRoot) {
|
|
|
3101
3402
|
}
|
|
3102
3403
|
}
|
|
3103
3404
|
}
|
|
3405
|
+
async function resolveDbOnly(db, exp, projectRoot) {
|
|
3406
|
+
let grades = getVerificationsByExperiment(db, exp.id);
|
|
3407
|
+
if (grades.length === 0) {
|
|
3408
|
+
warn(`No verification records for ${exp.slug}. Defaulting to weak.`);
|
|
3409
|
+
insertVerification(
|
|
3410
|
+
db,
|
|
3411
|
+
exp.id,
|
|
3412
|
+
"auto-default",
|
|
3413
|
+
"weak",
|
|
3414
|
+
null,
|
|
3415
|
+
null,
|
|
3416
|
+
"No structured verification output. Auto-defaulted to weak."
|
|
3417
|
+
);
|
|
3418
|
+
grades = getVerificationsByExperiment(db, exp.id);
|
|
3419
|
+
}
|
|
3420
|
+
const overallGrade = worstGrade(grades);
|
|
3421
|
+
switch (overallGrade) {
|
|
3422
|
+
case "sound":
|
|
3423
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
3424
|
+
success(`Experiment ${exp.slug} RESOLVED (sound) \u2014 git merge deferred.`);
|
|
3425
|
+
break;
|
|
3426
|
+
case "good": {
|
|
3427
|
+
const gaps = grades.filter((g) => g.grade === "good").map((g) => `- **${g.component}**: ${g.notes ?? "minor gaps"}`).join("\n");
|
|
3428
|
+
appendToFragilityMap(projectRoot, exp.slug, gaps);
|
|
3429
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
3430
|
+
success(`Experiment ${exp.slug} RESOLVED (good) \u2014 git merge deferred.`);
|
|
3431
|
+
break;
|
|
3432
|
+
}
|
|
3433
|
+
case "weak": {
|
|
3434
|
+
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
3435
|
+
const guidance = await spawnSynthesiser({
|
|
3436
|
+
experiment: {
|
|
3437
|
+
id: exp.id,
|
|
3438
|
+
slug: exp.slug,
|
|
3439
|
+
hypothesis: exp.hypothesis,
|
|
3440
|
+
status: exp.status,
|
|
3441
|
+
sub_type: exp.sub_type,
|
|
3442
|
+
builder_guidance: exp.builder_guidance
|
|
3443
|
+
},
|
|
3444
|
+
verificationReport: grades,
|
|
3445
|
+
confirmedDoubts,
|
|
3446
|
+
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."
|
|
3447
|
+
}, projectRoot);
|
|
3448
|
+
const guidanceText = guidance.structured?.guidance ?? guidance.output;
|
|
3449
|
+
db.transaction(() => {
|
|
3450
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
3451
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
3452
|
+
if (exp.sub_type) {
|
|
3453
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
3454
|
+
}
|
|
3455
|
+
})();
|
|
3456
|
+
warn(`Experiment ${exp.slug} CYCLING BACK (weak). Guidance generated.`);
|
|
3457
|
+
break;
|
|
3458
|
+
}
|
|
3459
|
+
case "rejected": {
|
|
3460
|
+
const rejectedComponents = grades.filter((g) => g.grade === "rejected");
|
|
3461
|
+
const whyFailed = rejectedComponents.map((r) => r.notes ?? "rejected").join("; ");
|
|
3462
|
+
db.transaction(() => {
|
|
3463
|
+
insertDeadEnd(
|
|
3464
|
+
db,
|
|
3465
|
+
exp.id,
|
|
3466
|
+
exp.hypothesis ?? exp.slug,
|
|
3467
|
+
whyFailed,
|
|
3468
|
+
`Approach rejected: ${whyFailed}`,
|
|
3469
|
+
exp.sub_type,
|
|
3470
|
+
"structural"
|
|
3471
|
+
);
|
|
3472
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
3473
|
+
if (exp.sub_type) {
|
|
3474
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
|
|
3475
|
+
}
|
|
3476
|
+
})();
|
|
3477
|
+
info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
|
|
3478
|
+
break;
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
return overallGrade;
|
|
3482
|
+
}
|
|
3104
3483
|
function gitMerge(branch, cwd) {
|
|
3105
3484
|
try {
|
|
3106
|
-
(0,
|
|
3485
|
+
(0, import_node_child_process4.execSync)("git checkout main 2>/dev/null || git checkout master", {
|
|
3486
|
+
cwd,
|
|
3487
|
+
encoding: "utf-8",
|
|
3488
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3489
|
+
});
|
|
3490
|
+
(0, import_node_child_process4.execSync)(`git merge ${branch} --no-ff -m "Merge experiment branch ${branch}"`, {
|
|
3107
3491
|
cwd,
|
|
3108
3492
|
encoding: "utf-8",
|
|
3109
3493
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3114,16 +3498,16 @@ function gitMerge(branch, cwd) {
|
|
|
3114
3498
|
}
|
|
3115
3499
|
function gitRevert(branch, cwd) {
|
|
3116
3500
|
try {
|
|
3117
|
-
const currentBranch = (0,
|
|
3501
|
+
const currentBranch = (0, import_node_child_process4.execSync)("git rev-parse --abbrev-ref HEAD", {
|
|
3118
3502
|
cwd,
|
|
3119
3503
|
encoding: "utf-8"
|
|
3120
3504
|
}).trim();
|
|
3121
3505
|
if (currentBranch === branch) {
|
|
3122
3506
|
try {
|
|
3123
|
-
(0,
|
|
3507
|
+
(0, import_node_child_process4.execSync)("git checkout -- .", { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3124
3508
|
} catch {
|
|
3125
3509
|
}
|
|
3126
|
-
(0,
|
|
3510
|
+
(0, import_node_child_process4.execSync)("git checkout main 2>/dev/null || git checkout master", {
|
|
3127
3511
|
cwd,
|
|
3128
3512
|
encoding: "utf-8",
|
|
3129
3513
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3145,7 +3529,7 @@ ${gaps}
|
|
|
3145
3529
|
`;
|
|
3146
3530
|
fs7.writeFileSync(fragPath, content + entry);
|
|
3147
3531
|
}
|
|
3148
|
-
var fs7, path7,
|
|
3532
|
+
var fs7, path7, import_node_child_process4;
|
|
3149
3533
|
var init_resolve = __esm({
|
|
3150
3534
|
"src/resolve.ts"() {
|
|
3151
3535
|
"use strict";
|
|
@@ -3154,7 +3538,8 @@ var init_resolve = __esm({
|
|
|
3154
3538
|
init_types2();
|
|
3155
3539
|
init_queries();
|
|
3156
3540
|
init_spawn();
|
|
3157
|
-
|
|
3541
|
+
import_node_child_process4 = require("child_process");
|
|
3542
|
+
init_git();
|
|
3158
3543
|
init_format();
|
|
3159
3544
|
}
|
|
3160
3545
|
});
|
|
@@ -3163,7 +3548,9 @@ var init_resolve = __esm({
|
|
|
3163
3548
|
var cycle_exports = {};
|
|
3164
3549
|
__export(cycle_exports, {
|
|
3165
3550
|
cycle: () => cycle,
|
|
3166
|
-
resolveCmd: () => resolveCmd
|
|
3551
|
+
resolveCmd: () => resolveCmd,
|
|
3552
|
+
runResolve: () => runResolve,
|
|
3553
|
+
runStep: () => runStep
|
|
3167
3554
|
});
|
|
3168
3555
|
async function cycle(step, args) {
|
|
3169
3556
|
const root = findProjectRoot();
|
|
@@ -3195,6 +3582,28 @@ async function resolveCmd(args) {
|
|
|
3195
3582
|
transition(exp.status, "resolved" /* RESOLVED */);
|
|
3196
3583
|
await resolve(db, exp, root);
|
|
3197
3584
|
}
|
|
3585
|
+
async function runStep(step, db, exp, root) {
|
|
3586
|
+
switch (step) {
|
|
3587
|
+
case "build":
|
|
3588
|
+
return doBuild(db, exp, root);
|
|
3589
|
+
case "challenge":
|
|
3590
|
+
return doChallenge(db, exp, root);
|
|
3591
|
+
case "doubt":
|
|
3592
|
+
return doDoubt(db, exp, root);
|
|
3593
|
+
case "scout":
|
|
3594
|
+
return doScout(db, exp, root);
|
|
3595
|
+
case "verify":
|
|
3596
|
+
return doVerify(db, exp, root);
|
|
3597
|
+
case "gate":
|
|
3598
|
+
return doGate(db, exp, root);
|
|
3599
|
+
case "compress":
|
|
3600
|
+
return doCompress(db, root);
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
async function runResolve(db, exp, root) {
|
|
3604
|
+
transition(exp.status, "resolved" /* RESOLVED */);
|
|
3605
|
+
await resolve(db, exp, root);
|
|
3606
|
+
}
|
|
3198
3607
|
async function doGate(db, exp, root) {
|
|
3199
3608
|
transition(exp.status, "gated" /* GATED */);
|
|
3200
3609
|
const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
@@ -3249,7 +3658,7 @@ async function doBuild(db, exp, root) {
|
|
|
3249
3658
|
const existingBaseline = getMetricsByExperimentAndPhase(db, exp.id, "before");
|
|
3250
3659
|
if (config.metrics?.command && existingBaseline.length === 0) {
|
|
3251
3660
|
try {
|
|
3252
|
-
const output = (0,
|
|
3661
|
+
const output = (0, import_node_child_process5.execSync)(config.metrics.command, {
|
|
3253
3662
|
cwd: root,
|
|
3254
3663
|
encoding: "utf-8",
|
|
3255
3664
|
timeout: 6e4,
|
|
@@ -3306,7 +3715,7 @@ Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothes
|
|
|
3306
3715
|
} else {
|
|
3307
3716
|
if (config.metrics?.command) {
|
|
3308
3717
|
try {
|
|
3309
|
-
const output = (0,
|
|
3718
|
+
const output = (0, import_node_child_process5.execSync)(config.metrics.command, {
|
|
3310
3719
|
cwd: root,
|
|
3311
3720
|
encoding: "utf-8",
|
|
3312
3721
|
timeout: 6e4,
|
|
@@ -3330,7 +3739,7 @@ async function doChallenge(db, exp, root) {
|
|
|
3330
3739
|
transition(exp.status, "challenged" /* CHALLENGED */);
|
|
3331
3740
|
let gitDiff = "";
|
|
3332
3741
|
try {
|
|
3333
|
-
gitDiff = (0,
|
|
3742
|
+
gitDiff = (0, import_node_child_process5.execSync)('git diff main -- . ":!.majlis/"', {
|
|
3334
3743
|
cwd: root,
|
|
3335
3744
|
encoding: "utf-8",
|
|
3336
3745
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3542,12 +3951,13 @@ async function doCompress(db, root) {
|
|
|
3542
3951
|
}, root);
|
|
3543
3952
|
const sizeAfter = fs8.existsSync(synthesisPath) ? fs8.statSync(synthesisPath).size : 0;
|
|
3544
3953
|
recordCompression(db, sessionCount, sizeBefore, sizeAfter);
|
|
3954
|
+
autoCommit(root, "compress: update synthesis");
|
|
3545
3955
|
success(`Compression complete. Synthesis: ${sizeBefore}B \u2192 ${sizeAfter}B`);
|
|
3546
3956
|
}
|
|
3547
3957
|
function gitCommitBuild(exp, cwd) {
|
|
3548
3958
|
try {
|
|
3549
|
-
(0,
|
|
3550
|
-
const diff = (0,
|
|
3959
|
+
(0, import_node_child_process5.execSync)('git add -A -- ":!.majlis/"', { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3960
|
+
const diff = (0, import_node_child_process5.execSync)("git diff --cached --stat", { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3551
3961
|
if (!diff) {
|
|
3552
3962
|
info("No code changes to commit.");
|
|
3553
3963
|
return;
|
|
@@ -3555,7 +3965,7 @@ function gitCommitBuild(exp, cwd) {
|
|
|
3555
3965
|
const msg = `EXP-${String(exp.id).padStart(3, "0")}: ${exp.slug}
|
|
3556
3966
|
|
|
3557
3967
|
${exp.hypothesis ?? ""}`;
|
|
3558
|
-
(0,
|
|
3968
|
+
(0, import_node_child_process5.execSync)(`git commit -m ${JSON.stringify(msg)}`, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3559
3969
|
info(`Committed builder changes on ${exp.branch}.`);
|
|
3560
3970
|
} catch {
|
|
3561
3971
|
warn("Could not auto-commit builder changes \u2014 commit manually before resolving.");
|
|
@@ -3631,13 +4041,13 @@ function ingestStructuredOutput(db, experimentId, structured) {
|
|
|
3631
4041
|
info(`Ingested ${structured.findings.length} finding(s)`);
|
|
3632
4042
|
}
|
|
3633
4043
|
}
|
|
3634
|
-
var fs8, path8,
|
|
4044
|
+
var fs8, path8, import_node_child_process5;
|
|
3635
4045
|
var init_cycle = __esm({
|
|
3636
4046
|
"src/commands/cycle.ts"() {
|
|
3637
4047
|
"use strict";
|
|
3638
4048
|
fs8 = __toESM(require("fs"));
|
|
3639
4049
|
path8 = __toESM(require("path"));
|
|
3640
|
-
|
|
4050
|
+
import_node_child_process5 = require("child_process");
|
|
3641
4051
|
init_connection();
|
|
3642
4052
|
init_queries();
|
|
3643
4053
|
init_machine();
|
|
@@ -3646,6 +4056,7 @@ var init_cycle = __esm({
|
|
|
3646
4056
|
init_resolve();
|
|
3647
4057
|
init_config();
|
|
3648
4058
|
init_metrics();
|
|
4059
|
+
init_git();
|
|
3649
4060
|
init_format();
|
|
3650
4061
|
}
|
|
3651
4062
|
});
|
|
@@ -3679,6 +4090,7 @@ ${deadEnds}
|
|
|
3679
4090
|
|
|
3680
4091
|
Write the classification to docs/classification/ following the template.`
|
|
3681
4092
|
}, root);
|
|
4093
|
+
autoCommit(root, `classify: ${domain.slice(0, 60)}`);
|
|
3682
4094
|
success("Classification complete. Check docs/classification/ for the output.");
|
|
3683
4095
|
}
|
|
3684
4096
|
async function reframe(args) {
|
|
@@ -3721,6 +4133,7 @@ ${deadEnds}
|
|
|
3721
4133
|
Independently propose a decomposition. Compare with the existing classification. Flag structural divergences \u2014 these are the most valuable signals.
|
|
3722
4134
|
Write to docs/reframes/.`
|
|
3723
4135
|
}, root);
|
|
4136
|
+
autoCommit(root, `reframe: ${target.slice(0, 60)}`);
|
|
3724
4137
|
success("Reframe complete. Check docs/reframes/ for the output.");
|
|
3725
4138
|
}
|
|
3726
4139
|
var fs9, path9;
|
|
@@ -3731,6 +4144,7 @@ var init_classify = __esm({
|
|
|
3731
4144
|
path9 = __toESM(require("path"));
|
|
3732
4145
|
init_connection();
|
|
3733
4146
|
init_spawn();
|
|
4147
|
+
init_git();
|
|
3734
4148
|
init_format();
|
|
3735
4149
|
}
|
|
3736
4150
|
});
|
|
@@ -4005,13 +4419,13 @@ async function run(args) {
|
|
|
4005
4419
|
const db = getDb(root);
|
|
4006
4420
|
const config = loadConfig(root);
|
|
4007
4421
|
const MAX_EXPERIMENTS = 10;
|
|
4008
|
-
const
|
|
4422
|
+
const MAX_STEPS2 = 200;
|
|
4009
4423
|
let experimentCount = 0;
|
|
4010
4424
|
let stepCount = 0;
|
|
4011
4425
|
let consecutiveFailures = 0;
|
|
4012
4426
|
const usedHypotheses = /* @__PURE__ */ new Set();
|
|
4013
4427
|
header(`Autonomous Mode \u2014 ${goal}`);
|
|
4014
|
-
while (stepCount <
|
|
4428
|
+
while (stepCount < MAX_STEPS2 && experimentCount < MAX_EXPERIMENTS) {
|
|
4015
4429
|
if (isShutdownRequested()) {
|
|
4016
4430
|
warn("Shutdown requested. Stopping autonomous mode.");
|
|
4017
4431
|
break;
|
|
@@ -4042,6 +4456,7 @@ async function run(args) {
|
|
|
4042
4456
|
usedHypotheses.add(hypothesis);
|
|
4043
4457
|
info(`Next hypothesis: ${hypothesis}`);
|
|
4044
4458
|
exp = await createNewExperiment(db, root, hypothesis);
|
|
4459
|
+
autoCommit(root, `new: ${exp.slug}`);
|
|
4045
4460
|
success(`Created experiment #${exp.id}: ${exp.slug}`);
|
|
4046
4461
|
}
|
|
4047
4462
|
if (isTerminal(exp.status)) {
|
|
@@ -4081,8 +4496,8 @@ async function run(args) {
|
|
|
4081
4496
|
}
|
|
4082
4497
|
}
|
|
4083
4498
|
}
|
|
4084
|
-
if (stepCount >=
|
|
4085
|
-
warn(`Reached max steps (${
|
|
4499
|
+
if (stepCount >= MAX_STEPS2) {
|
|
4500
|
+
warn(`Reached max steps (${MAX_STEPS2}). Stopping autonomous mode.`);
|
|
4086
4501
|
}
|
|
4087
4502
|
header("Autonomous Mode Complete");
|
|
4088
4503
|
info(`Goal: ${goal}`);
|
|
@@ -4093,12 +4508,13 @@ async function deriveNextHypothesis(goal, root, db) {
|
|
|
4093
4508
|
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
4094
4509
|
const fragility = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
4095
4510
|
const deadEndsDoc = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "dead-ends.md")), CONTEXT_LIMITS.deadEnds);
|
|
4511
|
+
const diagnosis = truncateContext(readLatestDiagnosis(root), CONTEXT_LIMITS.synthesis);
|
|
4096
4512
|
const deadEnds = listAllDeadEnds(db);
|
|
4097
4513
|
const config = loadConfig(root);
|
|
4098
4514
|
let metricsOutput = "";
|
|
4099
4515
|
if (config.metrics?.command) {
|
|
4100
4516
|
try {
|
|
4101
|
-
metricsOutput = (0,
|
|
4517
|
+
metricsOutput = (0, import_node_child_process6.execSync)(config.metrics.command, {
|
|
4102
4518
|
cwd: root,
|
|
4103
4519
|
encoding: "utf-8",
|
|
4104
4520
|
timeout: 6e4,
|
|
@@ -4113,7 +4529,10 @@ async function deriveNextHypothesis(goal, root, db) {
|
|
|
4113
4529
|
|
|
4114
4530
|
## Goal
|
|
4115
4531
|
${goal}
|
|
4116
|
-
|
|
4532
|
+
${diagnosis ? `
|
|
4533
|
+
## Latest Diagnosis Report (PRIORITISE \u2014 deep analysis from diagnostician agent)
|
|
4534
|
+
${diagnosis}
|
|
4535
|
+
` : ""}
|
|
4117
4536
|
## Current Metrics
|
|
4118
4537
|
${metricsOutput || "(no metrics configured)"}
|
|
4119
4538
|
|
|
@@ -4133,6 +4552,8 @@ Note: [structural] dead ends are HARD CONSTRAINTS \u2014 your hypothesis MUST NO
|
|
|
4133
4552
|
[procedural] dead ends are process failures \u2014 the approach may still be valid if executed differently.
|
|
4134
4553
|
|
|
4135
4554
|
## Your Task
|
|
4555
|
+
DO NOT read source code or use tools. All context you need is above. Plan from the synthesis and dead-end registry.
|
|
4556
|
+
|
|
4136
4557
|
1. Assess: based on the metrics and synthesis, has the goal been met? Be specific.
|
|
4137
4558
|
2. If YES \u2014 output the JSON block below with goal_met: true.
|
|
4138
4559
|
3. If NO \u2014 propose the SINGLE most promising next experiment hypothesis.
|
|
@@ -4148,7 +4569,7 @@ CRITICAL: Your LAST line of output MUST be EXACTLY this format (on its own line,
|
|
|
4148
4569
|
|
|
4149
4570
|
If the goal is met:
|
|
4150
4571
|
<!-- majlis-json {"goal_met": true, "hypothesis": null} -->`
|
|
4151
|
-
}, root);
|
|
4572
|
+
}, root, { maxTurns: 2, tools: [] });
|
|
4152
4573
|
const structured = result.structured;
|
|
4153
4574
|
if (structured?.goal_met === true) {
|
|
4154
4575
|
return null;
|
|
@@ -4174,7 +4595,7 @@ If the goal is met:
|
|
|
4174
4595
|
${result.output.slice(-2e3)}
|
|
4175
4596
|
|
|
4176
4597
|
<!-- majlis-json {"goal_met": false, "hypothesis": "your hypothesis"} -->`
|
|
4177
|
-
}, root);
|
|
4598
|
+
}, root, { maxTurns: 1, tools: [] });
|
|
4178
4599
|
if (retry.structured?.hypothesis) return retry.structured.hypothesis;
|
|
4179
4600
|
warn("Could not extract hypothesis. Using goal as fallback.");
|
|
4180
4601
|
return goal;
|
|
@@ -4192,7 +4613,7 @@ async function createNewExperiment(db, root, hypothesis) {
|
|
|
4192
4613
|
const paddedNum = String(num).padStart(3, "0");
|
|
4193
4614
|
const branch = `exp/${paddedNum}-${finalSlug}`;
|
|
4194
4615
|
try {
|
|
4195
|
-
(0,
|
|
4616
|
+
(0, import_node_child_process6.execSync)(`git checkout -b ${branch}`, {
|
|
4196
4617
|
cwd: root,
|
|
4197
4618
|
encoding: "utf-8",
|
|
4198
4619
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4215,13 +4636,13 @@ async function createNewExperiment(db, root, hypothesis) {
|
|
|
4215
4636
|
}
|
|
4216
4637
|
return exp;
|
|
4217
4638
|
}
|
|
4218
|
-
var fs11, path11,
|
|
4639
|
+
var fs11, path11, import_node_child_process6;
|
|
4219
4640
|
var init_run = __esm({
|
|
4220
4641
|
"src/commands/run.ts"() {
|
|
4221
4642
|
"use strict";
|
|
4222
4643
|
fs11 = __toESM(require("fs"));
|
|
4223
4644
|
path11 = __toESM(require("path"));
|
|
4224
|
-
|
|
4645
|
+
import_node_child_process6 = require("child_process");
|
|
4225
4646
|
init_connection();
|
|
4226
4647
|
init_queries();
|
|
4227
4648
|
init_machine();
|
|
@@ -4230,15 +4651,781 @@ var init_run = __esm({
|
|
|
4230
4651
|
init_spawn();
|
|
4231
4652
|
init_config();
|
|
4232
4653
|
init_shutdown();
|
|
4654
|
+
init_git();
|
|
4655
|
+
init_format();
|
|
4656
|
+
}
|
|
4657
|
+
});
|
|
4658
|
+
|
|
4659
|
+
// src/swarm/worktree.ts
|
|
4660
|
+
function createWorktree(mainRoot, slug, paddedNum) {
|
|
4661
|
+
const projectName = path12.basename(mainRoot);
|
|
4662
|
+
const worktreeName = `${projectName}-swarm-${paddedNum}-${slug}`;
|
|
4663
|
+
const worktreePath = path12.join(path12.dirname(mainRoot), worktreeName);
|
|
4664
|
+
const branch = `swarm/${paddedNum}-${slug}`;
|
|
4665
|
+
(0, import_node_child_process7.execSync)(`git worktree add ${JSON.stringify(worktreePath)} -b ${branch}`, {
|
|
4666
|
+
cwd: mainRoot,
|
|
4667
|
+
encoding: "utf-8",
|
|
4668
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4669
|
+
});
|
|
4670
|
+
return {
|
|
4671
|
+
path: worktreePath,
|
|
4672
|
+
branch,
|
|
4673
|
+
slug,
|
|
4674
|
+
hypothesis: "",
|
|
4675
|
+
// filled in by caller
|
|
4676
|
+
paddedNum
|
|
4677
|
+
};
|
|
4678
|
+
}
|
|
4679
|
+
function initializeWorktree(mainRoot, worktreePath) {
|
|
4680
|
+
const majlisDir = path12.join(worktreePath, ".majlis");
|
|
4681
|
+
fs12.mkdirSync(majlisDir, { recursive: true });
|
|
4682
|
+
const configSrc = path12.join(mainRoot, ".majlis", "config.json");
|
|
4683
|
+
if (fs12.existsSync(configSrc)) {
|
|
4684
|
+
fs12.copyFileSync(configSrc, path12.join(majlisDir, "config.json"));
|
|
4685
|
+
}
|
|
4686
|
+
const agentsSrc = path12.join(mainRoot, ".majlis", "agents");
|
|
4687
|
+
if (fs12.existsSync(agentsSrc)) {
|
|
4688
|
+
const agentsDst = path12.join(majlisDir, "agents");
|
|
4689
|
+
fs12.mkdirSync(agentsDst, { recursive: true });
|
|
4690
|
+
for (const file of fs12.readdirSync(agentsSrc)) {
|
|
4691
|
+
fs12.copyFileSync(path12.join(agentsSrc, file), path12.join(agentsDst, file));
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
const synthSrc = path12.join(mainRoot, "docs", "synthesis");
|
|
4695
|
+
if (fs12.existsSync(synthSrc)) {
|
|
4696
|
+
const synthDst = path12.join(worktreePath, "docs", "synthesis");
|
|
4697
|
+
fs12.mkdirSync(synthDst, { recursive: true });
|
|
4698
|
+
for (const file of fs12.readdirSync(synthSrc)) {
|
|
4699
|
+
const srcFile = path12.join(synthSrc, file);
|
|
4700
|
+
if (fs12.statSync(srcFile).isFile()) {
|
|
4701
|
+
fs12.copyFileSync(srcFile, path12.join(synthDst, file));
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
const templateSrc = path12.join(mainRoot, "docs", "experiments", "_TEMPLATE.md");
|
|
4706
|
+
if (fs12.existsSync(templateSrc)) {
|
|
4707
|
+
const expDir = path12.join(worktreePath, "docs", "experiments");
|
|
4708
|
+
fs12.mkdirSync(expDir, { recursive: true });
|
|
4709
|
+
fs12.copyFileSync(templateSrc, path12.join(expDir, "_TEMPLATE.md"));
|
|
4710
|
+
}
|
|
4711
|
+
const db = openDbAt(worktreePath);
|
|
4712
|
+
db.close();
|
|
4713
|
+
}
|
|
4714
|
+
function cleanupWorktree(mainRoot, wt) {
|
|
4715
|
+
try {
|
|
4716
|
+
(0, import_node_child_process7.execSync)(`git worktree remove ${JSON.stringify(wt.path)} --force`, {
|
|
4717
|
+
cwd: mainRoot,
|
|
4718
|
+
encoding: "utf-8",
|
|
4719
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4720
|
+
});
|
|
4721
|
+
} catch {
|
|
4722
|
+
warn(`Could not remove worktree ${wt.path} \u2014 remove manually.`);
|
|
4723
|
+
}
|
|
4724
|
+
try {
|
|
4725
|
+
(0, import_node_child_process7.execSync)(`git branch -D ${wt.branch}`, {
|
|
4726
|
+
cwd: mainRoot,
|
|
4727
|
+
encoding: "utf-8",
|
|
4728
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4729
|
+
});
|
|
4730
|
+
} catch {
|
|
4731
|
+
}
|
|
4732
|
+
try {
|
|
4733
|
+
(0, import_node_child_process7.execSync)("git worktree prune", {
|
|
4734
|
+
cwd: mainRoot,
|
|
4735
|
+
encoding: "utf-8",
|
|
4736
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4737
|
+
});
|
|
4738
|
+
} catch {
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
var fs12, path12, import_node_child_process7;
|
|
4742
|
+
var init_worktree = __esm({
|
|
4743
|
+
"src/swarm/worktree.ts"() {
|
|
4744
|
+
"use strict";
|
|
4745
|
+
fs12 = __toESM(require("fs"));
|
|
4746
|
+
path12 = __toESM(require("path"));
|
|
4747
|
+
import_node_child_process7 = require("child_process");
|
|
4748
|
+
init_connection();
|
|
4749
|
+
init_format();
|
|
4750
|
+
}
|
|
4751
|
+
});
|
|
4752
|
+
|
|
4753
|
+
// src/swarm/runner.ts
|
|
4754
|
+
async function runExperimentInWorktree(wt) {
|
|
4755
|
+
const label = `[swarm:${wt.paddedNum}]`;
|
|
4756
|
+
let db;
|
|
4757
|
+
let exp = null;
|
|
4758
|
+
let overallGrade = null;
|
|
4759
|
+
let stepCount = 0;
|
|
4760
|
+
try {
|
|
4761
|
+
db = openDbAt(wt.path);
|
|
4762
|
+
exp = createExperiment(db, wt.slug, wt.branch, wt.hypothesis, null, null);
|
|
4763
|
+
updateExperimentStatus(db, exp.id, "reframed");
|
|
4764
|
+
exp.status = "reframed";
|
|
4765
|
+
const templatePath = path13.join(wt.path, "docs", "experiments", "_TEMPLATE.md");
|
|
4766
|
+
if (fs13.existsSync(templatePath)) {
|
|
4767
|
+
const template = fs13.readFileSync(templatePath, "utf-8");
|
|
4768
|
+
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]);
|
|
4769
|
+
const logPath = path13.join(wt.path, "docs", "experiments", `${wt.paddedNum}-${wt.slug}.md`);
|
|
4770
|
+
fs13.writeFileSync(logPath, logContent);
|
|
4771
|
+
}
|
|
4772
|
+
info(`${label} Starting: ${wt.hypothesis}`);
|
|
4773
|
+
while (stepCount < MAX_STEPS) {
|
|
4774
|
+
if (isShutdownRequested()) {
|
|
4775
|
+
warn(`${label} Shutdown requested. Stopping.`);
|
|
4776
|
+
break;
|
|
4777
|
+
}
|
|
4778
|
+
stepCount++;
|
|
4779
|
+
const fresh = getExperimentBySlug(db, wt.slug);
|
|
4780
|
+
if (!fresh) break;
|
|
4781
|
+
exp = fresh;
|
|
4782
|
+
if (isTerminal(exp.status)) {
|
|
4783
|
+
success(`${label} Reached terminal: ${exp.status}`);
|
|
4784
|
+
break;
|
|
4785
|
+
}
|
|
4786
|
+
const valid = validNext(exp.status);
|
|
4787
|
+
if (valid.length === 0) break;
|
|
4788
|
+
const nextStep = determineNextStep(
|
|
4789
|
+
exp,
|
|
4790
|
+
valid,
|
|
4791
|
+
hasDoubts(db, exp.id),
|
|
4792
|
+
hasChallenges(db, exp.id)
|
|
4793
|
+
);
|
|
4794
|
+
info(`${label} [${stepCount}/${MAX_STEPS}] ${exp.status} -> ${nextStep}`);
|
|
4795
|
+
if (nextStep === "resolved" /* RESOLVED */) {
|
|
4796
|
+
overallGrade = await resolveDbOnly(db, exp, wt.path);
|
|
4797
|
+
continue;
|
|
4798
|
+
}
|
|
4799
|
+
if (nextStep === "compressed" /* COMPRESSED */) {
|
|
4800
|
+
await runStep("compress", db, exp, wt.path);
|
|
4801
|
+
updateExperimentStatus(db, exp.id, "compressed");
|
|
4802
|
+
continue;
|
|
4803
|
+
}
|
|
4804
|
+
if (nextStep === "merged" /* MERGED */) {
|
|
4805
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4806
|
+
success(`${label} Merged.`);
|
|
4807
|
+
break;
|
|
4808
|
+
}
|
|
4809
|
+
if (nextStep === "reframed" /* REFRAMED */) {
|
|
4810
|
+
updateExperimentStatus(db, exp.id, "reframed");
|
|
4811
|
+
continue;
|
|
4812
|
+
}
|
|
4813
|
+
const stepName = statusToStepName(nextStep);
|
|
4814
|
+
if (!stepName) {
|
|
4815
|
+
warn(`${label} Unknown step: ${nextStep}`);
|
|
4816
|
+
break;
|
|
4817
|
+
}
|
|
4818
|
+
try {
|
|
4819
|
+
await runStep(stepName, db, exp, wt.path);
|
|
4820
|
+
} catch (err) {
|
|
4821
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4822
|
+
warn(`${label} Step failed: ${message}`);
|
|
4823
|
+
try {
|
|
4824
|
+
insertDeadEnd(
|
|
4825
|
+
db,
|
|
4826
|
+
exp.id,
|
|
4827
|
+
exp.hypothesis ?? exp.slug,
|
|
4828
|
+
message,
|
|
4829
|
+
`Process failure: ${message}`,
|
|
4830
|
+
exp.sub_type,
|
|
4831
|
+
"procedural"
|
|
4832
|
+
);
|
|
4833
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
4834
|
+
} catch {
|
|
4835
|
+
}
|
|
4836
|
+
break;
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
if (stepCount >= MAX_STEPS) {
|
|
4840
|
+
warn(`${label} Hit max steps (${MAX_STEPS}).`);
|
|
4841
|
+
}
|
|
4842
|
+
const finalExp = getExperimentBySlug(db, wt.slug);
|
|
4843
|
+
if (finalExp) exp = finalExp;
|
|
4844
|
+
const finalStatus = exp?.status ?? "error";
|
|
4845
|
+
return {
|
|
4846
|
+
worktree: wt,
|
|
4847
|
+
experiment: exp,
|
|
4848
|
+
finalStatus,
|
|
4849
|
+
overallGrade,
|
|
4850
|
+
costUsd: 0,
|
|
4851
|
+
// TODO: track via SDK when available
|
|
4852
|
+
stepCount
|
|
4853
|
+
};
|
|
4854
|
+
} catch (err) {
|
|
4855
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4856
|
+
warn(`${label} Fatal error: ${message}`);
|
|
4857
|
+
return {
|
|
4858
|
+
worktree: wt,
|
|
4859
|
+
experiment: exp,
|
|
4860
|
+
finalStatus: "error",
|
|
4861
|
+
overallGrade: null,
|
|
4862
|
+
costUsd: 0,
|
|
4863
|
+
stepCount,
|
|
4864
|
+
error: message
|
|
4865
|
+
};
|
|
4866
|
+
} finally {
|
|
4867
|
+
if (db) {
|
|
4868
|
+
try {
|
|
4869
|
+
db.close();
|
|
4870
|
+
} catch {
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
function statusToStepName(status2) {
|
|
4876
|
+
switch (status2) {
|
|
4877
|
+
case "gated" /* GATED */:
|
|
4878
|
+
return "gate";
|
|
4879
|
+
case "building" /* BUILDING */:
|
|
4880
|
+
return "build";
|
|
4881
|
+
case "challenged" /* CHALLENGED */:
|
|
4882
|
+
return "challenge";
|
|
4883
|
+
case "doubted" /* DOUBTED */:
|
|
4884
|
+
return "doubt";
|
|
4885
|
+
case "scouted" /* SCOUTED */:
|
|
4886
|
+
return "scout";
|
|
4887
|
+
case "verifying" /* VERIFYING */:
|
|
4888
|
+
return "verify";
|
|
4889
|
+
default:
|
|
4890
|
+
return null;
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
var fs13, path13, MAX_STEPS;
|
|
4894
|
+
var init_runner = __esm({
|
|
4895
|
+
"src/swarm/runner.ts"() {
|
|
4896
|
+
"use strict";
|
|
4897
|
+
fs13 = __toESM(require("fs"));
|
|
4898
|
+
path13 = __toESM(require("path"));
|
|
4899
|
+
init_connection();
|
|
4900
|
+
init_queries();
|
|
4901
|
+
init_machine();
|
|
4902
|
+
init_types2();
|
|
4903
|
+
init_cycle();
|
|
4904
|
+
init_resolve();
|
|
4905
|
+
init_shutdown();
|
|
4906
|
+
init_format();
|
|
4907
|
+
MAX_STEPS = 20;
|
|
4908
|
+
}
|
|
4909
|
+
});
|
|
4910
|
+
|
|
4911
|
+
// src/swarm/aggregate.ts
|
|
4912
|
+
function importExperimentFromWorktree(sourceDb, targetDb, slug) {
|
|
4913
|
+
const sourceExp = sourceDb.prepare(
|
|
4914
|
+
"SELECT * FROM experiments WHERE slug = ?"
|
|
4915
|
+
).get(slug);
|
|
4916
|
+
if (!sourceExp) {
|
|
4917
|
+
throw new Error(`Experiment ${slug} not found in source DB`);
|
|
4918
|
+
}
|
|
4919
|
+
const sourceId = sourceExp.id;
|
|
4920
|
+
const insertExp = targetDb.prepare(`
|
|
4921
|
+
INSERT INTO experiments (slug, branch, status, classification_ref, sub_type,
|
|
4922
|
+
hypothesis, builder_guidance, created_at, updated_at)
|
|
4923
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4924
|
+
`);
|
|
4925
|
+
const result = insertExp.run(
|
|
4926
|
+
sourceExp.slug,
|
|
4927
|
+
sourceExp.branch,
|
|
4928
|
+
sourceExp.status,
|
|
4929
|
+
sourceExp.classification_ref,
|
|
4930
|
+
sourceExp.sub_type,
|
|
4931
|
+
sourceExp.hypothesis,
|
|
4932
|
+
sourceExp.builder_guidance,
|
|
4933
|
+
sourceExp.created_at,
|
|
4934
|
+
sourceExp.updated_at
|
|
4935
|
+
);
|
|
4936
|
+
const targetId = result.lastInsertRowid;
|
|
4937
|
+
for (const table2 of CHILD_TABLES) {
|
|
4938
|
+
importChildTable(sourceDb, targetDb, table2, sourceId, targetId);
|
|
4939
|
+
}
|
|
4940
|
+
const stfRows = sourceDb.prepare(
|
|
4941
|
+
"SELECT * FROM sub_type_failures WHERE experiment_id = ?"
|
|
4942
|
+
).all(sourceId);
|
|
4943
|
+
for (const row of stfRows) {
|
|
4944
|
+
targetDb.prepare(`
|
|
4945
|
+
INSERT INTO sub_type_failures (sub_type, experiment_id, grade, created_at)
|
|
4946
|
+
VALUES (?, ?, ?, ?)
|
|
4947
|
+
`).run(row.sub_type, targetId, row.grade, row.created_at);
|
|
4948
|
+
}
|
|
4949
|
+
return targetId;
|
|
4950
|
+
}
|
|
4951
|
+
function importChildTable(sourceDb, targetDb, table2, sourceExpId, targetExpId) {
|
|
4952
|
+
const rows = sourceDb.prepare(
|
|
4953
|
+
`SELECT * FROM ${table2} WHERE experiment_id = ?`
|
|
4954
|
+
).all(sourceExpId);
|
|
4955
|
+
if (rows.length === 0) return;
|
|
4956
|
+
const cols = Object.keys(rows[0]).filter((c) => c !== "id");
|
|
4957
|
+
const placeholders = cols.map(() => "?").join(", ");
|
|
4958
|
+
const insert = targetDb.prepare(
|
|
4959
|
+
`INSERT INTO ${table2} (${cols.join(", ")}) VALUES (${placeholders})`
|
|
4960
|
+
);
|
|
4961
|
+
for (const row of rows) {
|
|
4962
|
+
const values = cols.map(
|
|
4963
|
+
(c) => c === "experiment_id" ? targetExpId : row[c]
|
|
4964
|
+
);
|
|
4965
|
+
insert.run(...values);
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
function aggregateSwarmResults(mainRoot, mainDb, results) {
|
|
4969
|
+
let mergedCount = 0;
|
|
4970
|
+
let deadEndCount = 0;
|
|
4971
|
+
let errorCount = 0;
|
|
4972
|
+
let totalCostUsd = 0;
|
|
4973
|
+
for (const r of results) {
|
|
4974
|
+
totalCostUsd += r.costUsd;
|
|
4975
|
+
if (r.error || !r.experiment) {
|
|
4976
|
+
errorCount++;
|
|
4977
|
+
continue;
|
|
4978
|
+
}
|
|
4979
|
+
try {
|
|
4980
|
+
const sourceDb = openDbAt(r.worktree.path);
|
|
4981
|
+
mainDb.transaction(() => {
|
|
4982
|
+
importExperimentFromWorktree(sourceDb, mainDb, r.worktree.slug);
|
|
4983
|
+
})();
|
|
4984
|
+
sourceDb.close();
|
|
4985
|
+
if (r.finalStatus === "merged") mergedCount++;
|
|
4986
|
+
else if (r.finalStatus === "dead_end") deadEndCount++;
|
|
4987
|
+
} catch (err) {
|
|
4988
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4989
|
+
warn(`Failed to import ${r.worktree.slug}: ${msg}`);
|
|
4990
|
+
errorCount++;
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
const ranked = results.filter((r) => r.overallGrade && !r.error).sort((a, b) => {
|
|
4994
|
+
const aRank = GRADE_RANK[a.overallGrade] ?? 99;
|
|
4995
|
+
const bRank = GRADE_RANK[b.overallGrade] ?? 99;
|
|
4996
|
+
return aRank - bRank;
|
|
4997
|
+
});
|
|
4998
|
+
const best = ranked.length > 0 ? ranked[0] : null;
|
|
4999
|
+
return {
|
|
5000
|
+
goal: "",
|
|
5001
|
+
// filled by caller
|
|
5002
|
+
parallelCount: results.length,
|
|
5003
|
+
results,
|
|
5004
|
+
bestExperiment: best,
|
|
5005
|
+
totalCostUsd,
|
|
5006
|
+
mergedCount,
|
|
5007
|
+
deadEndCount,
|
|
5008
|
+
errorCount
|
|
5009
|
+
};
|
|
5010
|
+
}
|
|
5011
|
+
var CHILD_TABLES, GRADE_RANK;
|
|
5012
|
+
var init_aggregate = __esm({
|
|
5013
|
+
"src/swarm/aggregate.ts"() {
|
|
5014
|
+
"use strict";
|
|
5015
|
+
init_connection();
|
|
5016
|
+
init_format();
|
|
5017
|
+
CHILD_TABLES = [
|
|
5018
|
+
"decisions",
|
|
5019
|
+
"doubts",
|
|
5020
|
+
"challenges",
|
|
5021
|
+
"verifications",
|
|
5022
|
+
"metrics",
|
|
5023
|
+
"dead_ends",
|
|
5024
|
+
"reframes",
|
|
5025
|
+
"findings"
|
|
5026
|
+
];
|
|
5027
|
+
GRADE_RANK = {
|
|
5028
|
+
sound: 0,
|
|
5029
|
+
good: 1,
|
|
5030
|
+
weak: 2,
|
|
5031
|
+
rejected: 3
|
|
5032
|
+
};
|
|
5033
|
+
}
|
|
5034
|
+
});
|
|
5035
|
+
|
|
5036
|
+
// src/commands/swarm.ts
|
|
5037
|
+
var swarm_exports = {};
|
|
5038
|
+
__export(swarm_exports, {
|
|
5039
|
+
swarm: () => swarm
|
|
5040
|
+
});
|
|
5041
|
+
async function swarm(args) {
|
|
5042
|
+
const root = findProjectRoot();
|
|
5043
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5044
|
+
const goal = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
5045
|
+
if (!goal) throw new Error('Usage: majlis swarm "goal description" [--parallel N]');
|
|
5046
|
+
const parallelStr = getFlagValue(args, "--parallel");
|
|
5047
|
+
const parallelCount = Math.min(
|
|
5048
|
+
Math.max(2, parseInt(parallelStr ?? String(DEFAULT_PARALLEL), 10) || DEFAULT_PARALLEL),
|
|
5049
|
+
MAX_PARALLEL
|
|
5050
|
+
);
|
|
5051
|
+
try {
|
|
5052
|
+
const status2 = (0, import_node_child_process8.execSync)("git status --porcelain", {
|
|
5053
|
+
cwd: root,
|
|
5054
|
+
encoding: "utf-8",
|
|
5055
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5056
|
+
}).trim();
|
|
5057
|
+
if (status2) {
|
|
5058
|
+
warn("Working tree has uncommitted changes. Commit or stash before swarming.");
|
|
5059
|
+
throw new Error("Dirty working tree. Commit or stash first.");
|
|
5060
|
+
}
|
|
5061
|
+
} catch (err) {
|
|
5062
|
+
if (err instanceof Error && err.message.includes("Dirty working tree")) throw err;
|
|
5063
|
+
warn("Could not check git status.");
|
|
5064
|
+
}
|
|
5065
|
+
const db = getDb(root);
|
|
5066
|
+
const swarmRun = createSwarmRun(db, goal, parallelCount);
|
|
5067
|
+
header(`Swarm Mode \u2014 ${goal}`);
|
|
5068
|
+
info(`Generating ${parallelCount} diverse hypotheses...`);
|
|
5069
|
+
const hypotheses = await deriveMultipleHypotheses(goal, root, parallelCount);
|
|
5070
|
+
if (hypotheses.length === 0) {
|
|
5071
|
+
success("Planner says the goal has been met. Nothing to swarm.");
|
|
5072
|
+
updateSwarmRun(db, swarmRun.id, "completed", 0, null);
|
|
5073
|
+
return;
|
|
5074
|
+
}
|
|
5075
|
+
info(`Got ${hypotheses.length} hypotheses:`);
|
|
5076
|
+
for (let i = 0; i < hypotheses.length; i++) {
|
|
5077
|
+
info(` ${i + 1}. ${hypotheses[i]}`);
|
|
5078
|
+
}
|
|
5079
|
+
const worktrees = [];
|
|
5080
|
+
for (let i = 0; i < hypotheses.length; i++) {
|
|
5081
|
+
const paddedNum = String(i + 1).padStart(3, "0");
|
|
5082
|
+
const slug = await generateSlug(hypotheses[i], root);
|
|
5083
|
+
try {
|
|
5084
|
+
const wt = createWorktree(root, slug, paddedNum);
|
|
5085
|
+
wt.hypothesis = hypotheses[i];
|
|
5086
|
+
initializeWorktree(root, wt.path);
|
|
5087
|
+
worktrees.push(wt);
|
|
5088
|
+
addSwarmMember(db, swarmRun.id, slug, wt.path);
|
|
5089
|
+
info(`Created worktree ${paddedNum}: ${slug}`);
|
|
5090
|
+
} catch (err) {
|
|
5091
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5092
|
+
warn(`Failed to create worktree for hypothesis ${i + 1}: ${msg}`);
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
if (worktrees.length === 0) {
|
|
5096
|
+
warn("No worktrees created. Aborting swarm.");
|
|
5097
|
+
updateSwarmRun(db, swarmRun.id, "failed", 0, null);
|
|
5098
|
+
return;
|
|
5099
|
+
}
|
|
5100
|
+
info(`Running ${worktrees.length} experiments in parallel...`);
|
|
5101
|
+
info("");
|
|
5102
|
+
const settled = await Promise.allSettled(
|
|
5103
|
+
worktrees.map((wt) => runExperimentInWorktree(wt))
|
|
5104
|
+
);
|
|
5105
|
+
const results = settled.map((s, i) => {
|
|
5106
|
+
if (s.status === "fulfilled") return s.value;
|
|
5107
|
+
return {
|
|
5108
|
+
worktree: worktrees[i],
|
|
5109
|
+
experiment: null,
|
|
5110
|
+
finalStatus: "error",
|
|
5111
|
+
overallGrade: null,
|
|
5112
|
+
costUsd: 0,
|
|
5113
|
+
stepCount: 0,
|
|
5114
|
+
error: s.reason instanceof Error ? s.reason.message : String(s.reason)
|
|
5115
|
+
};
|
|
5116
|
+
});
|
|
5117
|
+
for (const r of results) {
|
|
5118
|
+
updateSwarmMember(
|
|
5119
|
+
db,
|
|
5120
|
+
swarmRun.id,
|
|
5121
|
+
r.worktree.slug,
|
|
5122
|
+
r.finalStatus,
|
|
5123
|
+
r.overallGrade,
|
|
5124
|
+
r.costUsd,
|
|
5125
|
+
r.error ?? null
|
|
5126
|
+
);
|
|
5127
|
+
}
|
|
5128
|
+
info("");
|
|
5129
|
+
header("Aggregation");
|
|
5130
|
+
const summary = aggregateSwarmResults(root, db, results);
|
|
5131
|
+
summary.goal = goal;
|
|
5132
|
+
if (summary.bestExperiment && isMergeable(summary.bestExperiment.overallGrade)) {
|
|
5133
|
+
const best = summary.bestExperiment;
|
|
5134
|
+
info(`Best experiment: ${best.worktree.slug} (${best.overallGrade})`);
|
|
5135
|
+
try {
|
|
5136
|
+
(0, import_node_child_process8.execSync)(
|
|
5137
|
+
`git merge ${best.worktree.branch} --no-ff -m "Merge swarm winner: ${best.worktree.slug}"`,
|
|
5138
|
+
{ cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5139
|
+
);
|
|
5140
|
+
success(`Merged ${best.worktree.slug} into main.`);
|
|
5141
|
+
} catch {
|
|
5142
|
+
warn(`Git merge of ${best.worktree.slug} failed. Merge manually with:`);
|
|
5143
|
+
info(` git merge ${best.worktree.branch} --no-ff`);
|
|
5144
|
+
}
|
|
5145
|
+
} else {
|
|
5146
|
+
info("No experiment achieved sound/good grade. Nothing merged.");
|
|
5147
|
+
}
|
|
5148
|
+
for (const r of results) {
|
|
5149
|
+
if (r === summary.bestExperiment || r.error || !r.experiment) continue;
|
|
5150
|
+
const mainExp = getExperimentBySlug(db, r.worktree.slug);
|
|
5151
|
+
if (mainExp && mainExp.status !== "dead_end") {
|
|
5152
|
+
updateExperimentStatus(db, mainExp.id, "dead_end");
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
updateSwarmRun(
|
|
5156
|
+
db,
|
|
5157
|
+
swarmRun.id,
|
|
5158
|
+
summary.errorCount === results.length ? "failed" : "completed",
|
|
5159
|
+
summary.totalCostUsd,
|
|
5160
|
+
summary.bestExperiment?.worktree.slug ?? null
|
|
5161
|
+
);
|
|
5162
|
+
info("Cleaning up worktrees...");
|
|
5163
|
+
for (const wt of worktrees) {
|
|
5164
|
+
cleanupWorktree(root, wt);
|
|
5165
|
+
}
|
|
5166
|
+
info("");
|
|
5167
|
+
header("Swarm Summary");
|
|
5168
|
+
info(`Goal: ${goal}`);
|
|
5169
|
+
info(`Parallel: ${worktrees.length}`);
|
|
5170
|
+
info(`Results:`);
|
|
5171
|
+
for (const r of results) {
|
|
5172
|
+
const grade = r.overallGrade ?? "n/a";
|
|
5173
|
+
const status2 = r.error ? `ERROR: ${r.error.slice(0, 60)}` : r.finalStatus;
|
|
5174
|
+
const marker = r === summary.bestExperiment ? " <-- BEST" : "";
|
|
5175
|
+
info(` ${r.worktree.paddedNum} ${r.worktree.slug}: ${grade} (${status2})${marker}`);
|
|
5176
|
+
}
|
|
5177
|
+
info(`Merged: ${summary.mergedCount} | Dead-ends: ${summary.deadEndCount} | Errors: ${summary.errorCount}`);
|
|
5178
|
+
}
|
|
5179
|
+
function isMergeable(grade) {
|
|
5180
|
+
return grade === "sound" || grade === "good";
|
|
5181
|
+
}
|
|
5182
|
+
async function deriveMultipleHypotheses(goal, root, count) {
|
|
5183
|
+
const synthesis = truncateContext(
|
|
5184
|
+
readFileOrEmpty(path14.join(root, "docs", "synthesis", "current.md")),
|
|
5185
|
+
CONTEXT_LIMITS.synthesis
|
|
5186
|
+
);
|
|
5187
|
+
const fragility = truncateContext(
|
|
5188
|
+
readFileOrEmpty(path14.join(root, "docs", "synthesis", "fragility.md")),
|
|
5189
|
+
CONTEXT_LIMITS.fragility
|
|
5190
|
+
);
|
|
5191
|
+
const deadEndsDoc = truncateContext(
|
|
5192
|
+
readFileOrEmpty(path14.join(root, "docs", "synthesis", "dead-ends.md")),
|
|
5193
|
+
CONTEXT_LIMITS.deadEnds
|
|
5194
|
+
);
|
|
5195
|
+
const diagnosis = truncateContext(readLatestDiagnosis(root), CONTEXT_LIMITS.synthesis);
|
|
5196
|
+
const db = getDb(root);
|
|
5197
|
+
const deadEnds = listAllDeadEnds(db);
|
|
5198
|
+
const config = loadConfig(root);
|
|
5199
|
+
let metricsOutput = "";
|
|
5200
|
+
if (config.metrics?.command) {
|
|
5201
|
+
try {
|
|
5202
|
+
metricsOutput = (0, import_node_child_process8.execSync)(config.metrics.command, {
|
|
5203
|
+
cwd: root,
|
|
5204
|
+
encoding: "utf-8",
|
|
5205
|
+
timeout: 6e4,
|
|
5206
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5207
|
+
}).trim();
|
|
5208
|
+
} catch {
|
|
5209
|
+
metricsOutput = "(metrics command failed)";
|
|
5210
|
+
}
|
|
5211
|
+
}
|
|
5212
|
+
const result = await spawnSynthesiser({
|
|
5213
|
+
taskPrompt: `You are the Planner for a parallel Majlis swarm.
|
|
5214
|
+
|
|
5215
|
+
## Goal
|
|
5216
|
+
${goal}
|
|
5217
|
+
${diagnosis ? `
|
|
5218
|
+
## Latest Diagnosis Report (PRIORITISE \u2014 deep analysis from diagnostician agent)
|
|
5219
|
+
${diagnosis}
|
|
5220
|
+
` : ""}
|
|
5221
|
+
## Current Metrics
|
|
5222
|
+
${metricsOutput || "(no metrics configured)"}
|
|
5223
|
+
|
|
5224
|
+
## Synthesis (what we know so far)
|
|
5225
|
+
${synthesis || "(empty \u2014 first experiment)"}
|
|
5226
|
+
|
|
5227
|
+
## Fragility Map (known weak areas)
|
|
5228
|
+
${fragility || "(none)"}
|
|
5229
|
+
|
|
5230
|
+
## Dead-End Registry
|
|
5231
|
+
${deadEndsDoc || "(none)"}
|
|
5232
|
+
|
|
5233
|
+
## Dead Ends (from DB \u2014 ${deadEnds.length} total)
|
|
5234
|
+
${deadEnds.map((d) => `- [${d.category ?? "structural"}] ${d.approach}: ${d.why_failed} [constraint: ${d.structural_constraint}]`).join("\n") || "(none)"}
|
|
5235
|
+
|
|
5236
|
+
Note: [structural] dead ends are HARD CONSTRAINTS \u2014 hypotheses MUST NOT repeat these approaches.
|
|
5237
|
+
[procedural] dead ends are process failures \u2014 the approach may still be valid if executed differently.
|
|
5238
|
+
|
|
5239
|
+
## Your Task
|
|
5240
|
+
DO NOT read source code or use tools. All context you need is above. Plan from the synthesis and dead-end registry.
|
|
5241
|
+
|
|
5242
|
+
1. Assess: based on the metrics and synthesis, has the goal been met? Be specific.
|
|
5243
|
+
2. If YES \u2014 output the JSON block below with goal_met: true.
|
|
5244
|
+
3. If NO \u2014 generate exactly ${count} DIVERSE hypotheses for parallel testing.
|
|
5245
|
+
|
|
5246
|
+
Requirements for hypotheses:
|
|
5247
|
+
- Each must attack the problem from a DIFFERENT angle
|
|
5248
|
+
- They must NOT share the same mechanism, function target, or strategy
|
|
5249
|
+
- At least one should be an unconventional or indirect approach
|
|
5250
|
+
- None may repeat a dead-ended structural approach
|
|
5251
|
+
- Each must be specific and actionable \u2014 name the function or mechanism to change
|
|
5252
|
+
- Do NOT reference specific line numbers \u2014 they shift between experiments
|
|
5253
|
+
|
|
5254
|
+
CRITICAL: Your LAST line of output MUST be EXACTLY this format (on its own line, nothing after it):
|
|
5255
|
+
<!-- majlis-json {"goal_met": false, "hypotheses": ["hypothesis 1", "hypothesis 2", "hypothesis 3"]} -->
|
|
5256
|
+
|
|
5257
|
+
If the goal is met:
|
|
5258
|
+
<!-- majlis-json {"goal_met": true, "hypotheses": []} -->`
|
|
5259
|
+
}, root, { maxTurns: 2, tools: [] });
|
|
5260
|
+
if (result.structured?.goal_met === true) return [];
|
|
5261
|
+
if (result.structured?.hypotheses && Array.isArray(result.structured.hypotheses)) {
|
|
5262
|
+
return result.structured.hypotheses.filter(
|
|
5263
|
+
(h) => typeof h === "string" && h.length > 10
|
|
5264
|
+
);
|
|
5265
|
+
}
|
|
5266
|
+
const blockMatch = result.output.match(/<!--\s*majlis-json\s*(\{[\s\S]*?\})\s*-->/);
|
|
5267
|
+
if (blockMatch) {
|
|
5268
|
+
try {
|
|
5269
|
+
const parsed = JSON.parse(blockMatch[1]);
|
|
5270
|
+
if (parsed.goal_met === true) return [];
|
|
5271
|
+
if (Array.isArray(parsed.hypotheses)) {
|
|
5272
|
+
return parsed.hypotheses.filter(
|
|
5273
|
+
(h) => typeof h === "string" && h.length > 10
|
|
5274
|
+
);
|
|
5275
|
+
}
|
|
5276
|
+
} catch {
|
|
5277
|
+
}
|
|
5278
|
+
}
|
|
5279
|
+
warn("Planner did not return structured hypotheses. Using goal as single hypothesis.");
|
|
5280
|
+
return [goal];
|
|
5281
|
+
}
|
|
5282
|
+
var path14, import_node_child_process8, MAX_PARALLEL, DEFAULT_PARALLEL;
|
|
5283
|
+
var init_swarm = __esm({
|
|
5284
|
+
"src/commands/swarm.ts"() {
|
|
5285
|
+
"use strict";
|
|
5286
|
+
path14 = __toESM(require("path"));
|
|
5287
|
+
import_node_child_process8 = require("child_process");
|
|
5288
|
+
init_connection();
|
|
5289
|
+
init_queries();
|
|
5290
|
+
init_spawn();
|
|
5291
|
+
init_config();
|
|
5292
|
+
init_worktree();
|
|
5293
|
+
init_runner();
|
|
5294
|
+
init_aggregate();
|
|
5295
|
+
init_format();
|
|
5296
|
+
MAX_PARALLEL = 8;
|
|
5297
|
+
DEFAULT_PARALLEL = 3;
|
|
5298
|
+
}
|
|
5299
|
+
});
|
|
5300
|
+
|
|
5301
|
+
// src/commands/diagnose.ts
|
|
5302
|
+
var diagnose_exports = {};
|
|
5303
|
+
__export(diagnose_exports, {
|
|
5304
|
+
diagnose: () => diagnose
|
|
5305
|
+
});
|
|
5306
|
+
async function diagnose(args) {
|
|
5307
|
+
const root = findProjectRoot();
|
|
5308
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5309
|
+
const db = getDb(root);
|
|
5310
|
+
const focus = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
5311
|
+
const keepScripts = args.includes("--keep-scripts");
|
|
5312
|
+
const scriptsDir = path15.join(root, ".majlis", "scripts");
|
|
5313
|
+
if (!fs14.existsSync(scriptsDir)) {
|
|
5314
|
+
fs14.mkdirSync(scriptsDir, { recursive: true });
|
|
5315
|
+
}
|
|
5316
|
+
header("Deep Diagnosis");
|
|
5317
|
+
if (focus) info(`Focus: ${focus}`);
|
|
5318
|
+
const dbExport = exportForDiagnostician(db);
|
|
5319
|
+
const synthesis = readFileOrEmpty(path15.join(root, "docs", "synthesis", "current.md"));
|
|
5320
|
+
const fragility = readFileOrEmpty(path15.join(root, "docs", "synthesis", "fragility.md"));
|
|
5321
|
+
const deadEndsDoc = readFileOrEmpty(path15.join(root, "docs", "synthesis", "dead-ends.md"));
|
|
5322
|
+
const config = loadConfig(root);
|
|
5323
|
+
let metricsOutput = "";
|
|
5324
|
+
if (config.metrics?.command) {
|
|
5325
|
+
try {
|
|
5326
|
+
metricsOutput = (0, import_node_child_process9.execSync)(config.metrics.command, {
|
|
5327
|
+
cwd: root,
|
|
5328
|
+
encoding: "utf-8",
|
|
5329
|
+
timeout: 6e4,
|
|
5330
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5331
|
+
}).trim();
|
|
5332
|
+
} catch {
|
|
5333
|
+
metricsOutput = "(metrics command failed)";
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
let taskPrompt = `## Full Database Export (CANONICAL \u2014 source of truth)
|
|
5337
|
+
${dbExport}
|
|
5338
|
+
|
|
5339
|
+
`;
|
|
5340
|
+
taskPrompt += `## Current Synthesis
|
|
5341
|
+
${synthesis || "(empty \u2014 no experiments yet)"}
|
|
5342
|
+
|
|
5343
|
+
`;
|
|
5344
|
+
taskPrompt += `## Fragility Map
|
|
5345
|
+
${fragility || "(none)"}
|
|
5346
|
+
|
|
5347
|
+
`;
|
|
5348
|
+
taskPrompt += `## Dead-End Registry
|
|
5349
|
+
${deadEndsDoc || "(none)"}
|
|
5350
|
+
|
|
5351
|
+
`;
|
|
5352
|
+
taskPrompt += `## Current Metrics
|
|
5353
|
+
${metricsOutput || "(no metrics configured)"}
|
|
5354
|
+
|
|
5355
|
+
`;
|
|
5356
|
+
taskPrompt += `## Project Objective
|
|
5357
|
+
${config.project?.objective || "(not specified)"}
|
|
5358
|
+
|
|
5359
|
+
`;
|
|
5360
|
+
if (focus) {
|
|
5361
|
+
taskPrompt += `## Focus Area
|
|
5362
|
+
The user has asked you to focus your diagnosis on: ${focus}
|
|
5363
|
+
|
|
5364
|
+
`;
|
|
5365
|
+
}
|
|
5366
|
+
taskPrompt += `## Your Task
|
|
5367
|
+
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.
|
|
5368
|
+
|
|
5369
|
+
Remember: you may write files ONLY to .majlis/scripts/. You cannot modify project code.`;
|
|
5370
|
+
info("Spawning diagnostician (60 turns, full DB access)...");
|
|
5371
|
+
const result = await spawnAgent("diagnostician", { taskPrompt }, root);
|
|
5372
|
+
const diagnosisDir = path15.join(root, "docs", "diagnosis");
|
|
5373
|
+
if (!fs14.existsSync(diagnosisDir)) {
|
|
5374
|
+
fs14.mkdirSync(diagnosisDir, { recursive: true });
|
|
5375
|
+
}
|
|
5376
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
5377
|
+
const artifactPath = path15.join(diagnosisDir, `diagnosis-${timestamp}.md`);
|
|
5378
|
+
fs14.writeFileSync(artifactPath, result.output);
|
|
5379
|
+
info(`Diagnostic report: docs/diagnosis/diagnosis-${timestamp}.md`);
|
|
5380
|
+
if (result.structured?.diagnosis) {
|
|
5381
|
+
const d = result.structured.diagnosis;
|
|
5382
|
+
if (d.root_causes?.length) {
|
|
5383
|
+
info(`Root causes identified: ${d.root_causes.length}`);
|
|
5384
|
+
}
|
|
5385
|
+
if (d.investigation_directions?.length) {
|
|
5386
|
+
info(`Investigation directions: ${d.investigation_directions.length}`);
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
if (!keepScripts) {
|
|
5390
|
+
try {
|
|
5391
|
+
const files = fs14.readdirSync(scriptsDir);
|
|
5392
|
+
for (const f of files) {
|
|
5393
|
+
fs14.unlinkSync(path15.join(scriptsDir, f));
|
|
5394
|
+
}
|
|
5395
|
+
fs14.rmdirSync(scriptsDir);
|
|
5396
|
+
info("Cleaned up .majlis/scripts/");
|
|
5397
|
+
} catch {
|
|
5398
|
+
}
|
|
5399
|
+
} else {
|
|
5400
|
+
info("Scripts preserved in .majlis/scripts/ (--keep-scripts)");
|
|
5401
|
+
}
|
|
5402
|
+
if (result.truncated) {
|
|
5403
|
+
warn("Diagnostician was truncated (hit 60 turn limit).");
|
|
5404
|
+
}
|
|
5405
|
+
autoCommit(root, `diagnosis: ${focus || "general"}`);
|
|
5406
|
+
success("Diagnosis complete.");
|
|
5407
|
+
}
|
|
5408
|
+
var fs14, path15, import_node_child_process9;
|
|
5409
|
+
var init_diagnose = __esm({
|
|
5410
|
+
"src/commands/diagnose.ts"() {
|
|
5411
|
+
"use strict";
|
|
5412
|
+
fs14 = __toESM(require("fs"));
|
|
5413
|
+
path15 = __toESM(require("path"));
|
|
5414
|
+
import_node_child_process9 = require("child_process");
|
|
5415
|
+
init_connection();
|
|
5416
|
+
init_queries();
|
|
5417
|
+
init_spawn();
|
|
5418
|
+
init_config();
|
|
5419
|
+
init_git();
|
|
4233
5420
|
init_format();
|
|
4234
5421
|
}
|
|
4235
5422
|
});
|
|
4236
5423
|
|
|
4237
5424
|
// src/cli.ts
|
|
4238
|
-
var
|
|
4239
|
-
var
|
|
5425
|
+
var fs15 = __toESM(require("fs"));
|
|
5426
|
+
var path16 = __toESM(require("path"));
|
|
4240
5427
|
var VERSION = JSON.parse(
|
|
4241
|
-
|
|
5428
|
+
fs15.readFileSync(path16.join(__dirname, "..", "package.json"), "utf-8")
|
|
4242
5429
|
).version;
|
|
4243
5430
|
async function main() {
|
|
4244
5431
|
let sigintCount = 0;
|
|
@@ -4348,11 +5535,21 @@ async function main() {
|
|
|
4348
5535
|
await run2(rest);
|
|
4349
5536
|
break;
|
|
4350
5537
|
}
|
|
5538
|
+
case "swarm": {
|
|
5539
|
+
const { swarm: swarm2 } = await Promise.resolve().then(() => (init_swarm(), swarm_exports));
|
|
5540
|
+
await swarm2(rest);
|
|
5541
|
+
break;
|
|
5542
|
+
}
|
|
4351
5543
|
case "audit": {
|
|
4352
5544
|
const { audit: audit2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4353
5545
|
await audit2(rest);
|
|
4354
5546
|
break;
|
|
4355
5547
|
}
|
|
5548
|
+
case "diagnose": {
|
|
5549
|
+
const { diagnose: diagnose2 } = await Promise.resolve().then(() => (init_diagnose(), diagnose_exports));
|
|
5550
|
+
await diagnose2(rest);
|
|
5551
|
+
break;
|
|
5552
|
+
}
|
|
4356
5553
|
default:
|
|
4357
5554
|
console.error(`Unknown command: ${command}`);
|
|
4358
5555
|
printHelp();
|
|
@@ -4405,6 +5602,7 @@ Queries:
|
|
|
4405
5602
|
|
|
4406
5603
|
Audit:
|
|
4407
5604
|
audit "objective" Maqasid check \u2014 is the frame right?
|
|
5605
|
+
diagnose ["focus area"] Deep diagnosis \u2014 root causes, patterns, gaps
|
|
4408
5606
|
|
|
4409
5607
|
Sessions:
|
|
4410
5608
|
session start "intent" Declare session intent
|
|
@@ -4412,6 +5610,7 @@ Sessions:
|
|
|
4412
5610
|
|
|
4413
5611
|
Orchestration:
|
|
4414
5612
|
run "goal" Autonomous orchestration until goal met
|
|
5613
|
+
swarm "goal" [--parallel N] Run N experiments in parallel worktrees
|
|
4415
5614
|
|
|
4416
5615
|
Flags:
|
|
4417
5616
|
--json Output as JSON
|