majlis 0.5.0 → 0.5.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.
Files changed (2) hide show
  1. package/dist/cli.js +299 -265
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -29,6 +29,27 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
30
  mod
31
31
  ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/shutdown.ts
35
+ var shutdown_exports = {};
36
+ __export(shutdown_exports, {
37
+ isShutdownRequested: () => isShutdownRequested,
38
+ requestShutdown: () => requestShutdown
39
+ });
40
+ function requestShutdown() {
41
+ _requested = true;
42
+ }
43
+ function isShutdownRequested() {
44
+ return _requested;
45
+ }
46
+ var _requested;
47
+ var init_shutdown = __esm({
48
+ "src/shutdown.ts"() {
49
+ "use strict";
50
+ _requested = false;
51
+ }
52
+ });
32
53
 
33
54
  // src/db/migrations.ts
34
55
  function runMigrations(db) {
@@ -1618,6 +1639,73 @@ var init_queries = __esm({
1618
1639
  }
1619
1640
  });
1620
1641
 
1642
+ // src/config.ts
1643
+ function loadConfig(projectRoot) {
1644
+ if (_cachedConfig && _cachedRoot === projectRoot) return _cachedConfig;
1645
+ const configPath = path3.join(projectRoot, ".majlis", "config.json");
1646
+ if (!fs3.existsSync(configPath)) {
1647
+ _cachedConfig = { ...DEFAULT_CONFIG2 };
1648
+ _cachedRoot = projectRoot;
1649
+ return _cachedConfig;
1650
+ }
1651
+ const loaded = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1652
+ _cachedConfig = {
1653
+ ...DEFAULT_CONFIG2,
1654
+ ...loaded,
1655
+ project: { ...DEFAULT_CONFIG2.project, ...loaded.project },
1656
+ metrics: { ...DEFAULT_CONFIG2.metrics, ...loaded.metrics },
1657
+ build: { ...DEFAULT_CONFIG2.build, ...loaded.build },
1658
+ cycle: { ...DEFAULT_CONFIG2.cycle, ...loaded.cycle }
1659
+ };
1660
+ _cachedRoot = projectRoot;
1661
+ return _cachedConfig;
1662
+ }
1663
+ function readFileOrEmpty(filePath) {
1664
+ try {
1665
+ return fs3.readFileSync(filePath, "utf-8");
1666
+ } catch {
1667
+ return "";
1668
+ }
1669
+ }
1670
+ function getFlagValue(args, flag) {
1671
+ const idx = args.indexOf(flag);
1672
+ if (idx < 0 || idx + 1 >= args.length) return void 0;
1673
+ return args[idx + 1];
1674
+ }
1675
+ function truncateContext(content, limit) {
1676
+ if (content.length <= limit) return content;
1677
+ return content.slice(0, limit) + "\n[TRUNCATED]";
1678
+ }
1679
+ var fs3, path3, DEFAULT_CONFIG2, _cachedConfig, _cachedRoot, CONTEXT_LIMITS;
1680
+ var init_config = __esm({
1681
+ "src/config.ts"() {
1682
+ "use strict";
1683
+ fs3 = __toESM(require("fs"));
1684
+ path3 = __toESM(require("path"));
1685
+ DEFAULT_CONFIG2 = {
1686
+ project: { name: "", description: "", objective: "" },
1687
+ metrics: { command: "", fixtures: [], tracked: {} },
1688
+ build: { pre_measure: null, post_measure: null },
1689
+ cycle: {
1690
+ compression_interval: 5,
1691
+ circuit_breaker_threshold: 3,
1692
+ require_doubt_before_verify: true,
1693
+ require_challenge_before_verify: false,
1694
+ auto_baseline_on_new_experiment: true
1695
+ },
1696
+ models: {}
1697
+ };
1698
+ _cachedConfig = null;
1699
+ _cachedRoot = null;
1700
+ CONTEXT_LIMITS = {
1701
+ synthesis: 3e4,
1702
+ fragility: 15e3,
1703
+ experimentDoc: 15e3,
1704
+ deadEnds: 15e3
1705
+ };
1706
+ }
1707
+ });
1708
+
1621
1709
  // src/commands/status.ts
1622
1710
  var status_exports = {};
1623
1711
  __export(status_exports, {
@@ -1707,21 +1795,12 @@ function buildSummary(expCount, activeSession, sessionsSinceCompression, config)
1707
1795
  }
1708
1796
  return parts.join(". ");
1709
1797
  }
1710
- function loadConfig(projectRoot) {
1711
- const configPath = path3.join(projectRoot, ".majlis", "config.json");
1712
- if (!fs3.existsSync(configPath)) {
1713
- throw new Error("Missing .majlis/config.json. Run `majlis init` first.");
1714
- }
1715
- return JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1716
- }
1717
- var fs3, path3;
1718
1798
  var init_status = __esm({
1719
1799
  "src/commands/status.ts"() {
1720
1800
  "use strict";
1721
- fs3 = __toESM(require("fs"));
1722
- path3 = __toESM(require("path"));
1723
1801
  init_connection();
1724
1802
  init_queries();
1803
+ init_config();
1725
1804
  init_format();
1726
1805
  }
1727
1806
  });
@@ -1803,11 +1882,11 @@ async function captureMetrics(phase, args) {
1803
1882
  const root = findProjectRoot();
1804
1883
  if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
1805
1884
  const db = getDb(root);
1806
- const config = loadConfig2(root);
1807
- const expIdIdx = args.indexOf("--experiment");
1885
+ const config = loadConfig(root);
1886
+ const expIdStr = getFlagValue(args, "--experiment");
1808
1887
  let exp;
1809
- if (expIdIdx >= 0) {
1810
- exp = getExperimentById(db, Number(args[expIdIdx + 1]));
1888
+ if (expIdStr !== void 0) {
1889
+ exp = getExperimentById(db, Number(expIdStr));
1811
1890
  } else {
1812
1891
  exp = getLatestExperiment(db);
1813
1892
  }
@@ -1855,11 +1934,11 @@ async function compare(args, isJson) {
1855
1934
  const root = findProjectRoot();
1856
1935
  if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
1857
1936
  const db = getDb(root);
1858
- const config = loadConfig2(root);
1859
- const expIdIdx = args.indexOf("--experiment");
1937
+ const config = loadConfig(root);
1938
+ const expIdStr = getFlagValue(args, "--experiment");
1860
1939
  let exp;
1861
- if (expIdIdx >= 0) {
1862
- exp = getExperimentById(db, Number(args[expIdIdx + 1]));
1940
+ if (expIdStr !== void 0) {
1941
+ exp = getExperimentById(db, Number(expIdStr));
1863
1942
  } else {
1864
1943
  exp = getLatestExperiment(db);
1865
1944
  }
@@ -1896,23 +1975,15 @@ function formatDelta(delta) {
1896
1975
  const prefix = delta > 0 ? "+" : "";
1897
1976
  return `${prefix}${delta.toFixed(4)}`;
1898
1977
  }
1899
- function loadConfig2(projectRoot) {
1900
- const configPath = path4.join(projectRoot, ".majlis", "config.json");
1901
- if (!fs4.existsSync(configPath)) {
1902
- throw new Error("Missing .majlis/config.json. Run `majlis init` first.");
1903
- }
1904
- return JSON.parse(fs4.readFileSync(configPath, "utf-8"));
1905
- }
1906
- var fs4, path4, import_node_child_process;
1978
+ var import_node_child_process;
1907
1979
  var init_measure = __esm({
1908
1980
  "src/commands/measure.ts"() {
1909
1981
  "use strict";
1910
- fs4 = __toESM(require("fs"));
1911
- path4 = __toESM(require("path"));
1912
1982
  import_node_child_process = require("child_process");
1913
1983
  init_connection();
1914
1984
  init_queries();
1915
1985
  init_metrics();
1986
+ init_config();
1916
1987
  init_format();
1917
1988
  }
1918
1989
  });
@@ -1931,7 +2002,7 @@ async function newExperiment(args) {
1931
2002
  throw new Error('Usage: majlis new "hypothesis"');
1932
2003
  }
1933
2004
  const db = getDb(root);
1934
- const config = loadConfig3(root);
2005
+ const config = loadConfig(root);
1935
2006
  const slug = slugify(hypothesis);
1936
2007
  if (getExperimentBySlug(db, slug)) {
1937
2008
  throw new Error(`Experiment with slug "${slug}" already exists.`);
@@ -1950,17 +2021,16 @@ async function newExperiment(args) {
1950
2021
  } catch (err) {
1951
2022
  warn(`Could not create branch ${branch} \u2014 continuing without git branch.`);
1952
2023
  }
1953
- const subTypeIdx = args.indexOf("--sub-type");
1954
- const subType = subTypeIdx >= 0 ? args[subTypeIdx + 1] : null;
2024
+ const subType = getFlagValue(args, "--sub-type") ?? null;
1955
2025
  const exp = createExperiment(db, slug, branch, hypothesis, subType, null);
1956
2026
  success(`Created experiment #${exp.id}: ${exp.slug}`);
1957
- const docsDir = path5.join(root, "docs", "experiments");
1958
- const templatePath = path5.join(docsDir, "_TEMPLATE.md");
1959
- if (fs5.existsSync(templatePath)) {
1960
- const template = fs5.readFileSync(templatePath, "utf-8");
2027
+ const docsDir = path4.join(root, "docs", "experiments");
2028
+ const templatePath = path4.join(docsDir, "_TEMPLATE.md");
2029
+ if (fs4.existsSync(templatePath)) {
2030
+ const template = fs4.readFileSync(templatePath, "utf-8");
1961
2031
  const logContent = template.replace(/\{\{title\}\}/g, hypothesis).replace(/\{\{hypothesis\}\}/g, hypothesis).replace(/\{\{branch\}\}/g, branch).replace(/\{\{status\}\}/g, "classified").replace(/\{\{sub_type\}\}/g, subType ?? "unclassified").replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]);
1962
- const logPath = path5.join(docsDir, `${paddedNum}-${slug}.md`);
1963
- fs5.writeFileSync(logPath, logContent);
2032
+ const logPath = path4.join(docsDir, `${paddedNum}-${slug}.md`);
2033
+ fs4.writeFileSync(logPath, logContent);
1964
2034
  info(`Created experiment log: docs/experiments/${paddedNum}-${slug}.md`);
1965
2035
  }
1966
2036
  if (config.cycle.auto_baseline_on_new_experiment && config.metrics.command) {
@@ -1986,8 +2056,7 @@ async function revert(args) {
1986
2056
  exp = getLatestExperiment(db);
1987
2057
  if (!exp) throw new Error("No active experiments to revert.");
1988
2058
  }
1989
- const reasonIdx = args.indexOf("--reason");
1990
- const reason = reasonIdx >= 0 ? args[reasonIdx + 1] : "Manually reverted";
2059
+ const reason = getFlagValue(args, "--reason") ?? "Manually reverted";
1991
2060
  const category = args.includes("--structural") ? "structural" : "procedural";
1992
2061
  insertDeadEnd(
1993
2062
  db,
@@ -2019,22 +2088,16 @@ async function revert(args) {
2019
2088
  function slugify(text) {
2020
2089
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
2021
2090
  }
2022
- function loadConfig3(projectRoot) {
2023
- const configPath = path5.join(projectRoot, ".majlis", "config.json");
2024
- if (!fs5.existsSync(configPath)) {
2025
- return { cycle: { auto_baseline_on_new_experiment: false } };
2026
- }
2027
- return JSON.parse(fs5.readFileSync(configPath, "utf-8"));
2028
- }
2029
- var fs5, path5, import_node_child_process2;
2091
+ var fs4, path4, import_node_child_process2;
2030
2092
  var init_experiment = __esm({
2031
2093
  "src/commands/experiment.ts"() {
2032
2094
  "use strict";
2033
- fs5 = __toESM(require("fs"));
2034
- path5 = __toESM(require("path"));
2095
+ fs4 = __toESM(require("fs"));
2096
+ path4 = __toESM(require("path"));
2035
2097
  import_node_child_process2 = require("child_process");
2036
2098
  init_connection();
2037
2099
  init_queries();
2100
+ init_config();
2038
2101
  init_format();
2039
2102
  }
2040
2103
  });
@@ -2074,12 +2137,9 @@ async function session(args) {
2074
2137
  if (!active) {
2075
2138
  throw new Error("No active session to end.");
2076
2139
  }
2077
- const accomplishedIdx = args.indexOf("--accomplished");
2078
- const accomplished = accomplishedIdx >= 0 ? args[accomplishedIdx + 1] : null;
2079
- const unfinishedIdx = args.indexOf("--unfinished");
2080
- const unfinished = unfinishedIdx >= 0 ? args[unfinishedIdx + 1] : null;
2081
- const fragilityIdx = args.indexOf("--fragility");
2082
- const fragility = fragilityIdx >= 0 ? args[fragilityIdx + 1] : null;
2140
+ const accomplished = getFlagValue(args, "--accomplished") ?? null;
2141
+ const unfinished = getFlagValue(args, "--unfinished") ?? null;
2142
+ const fragility = getFlagValue(args, "--fragility") ?? null;
2083
2143
  endSession(db, active.id, accomplished, unfinished, fragility);
2084
2144
  success(`Session ended: "${active.intent}"`);
2085
2145
  if (accomplished) info(`Accomplished: ${accomplished}`);
@@ -2092,6 +2152,7 @@ var init_session = __esm({
2092
2152
  "use strict";
2093
2153
  init_connection();
2094
2154
  init_queries();
2155
+ init_config();
2095
2156
  init_format();
2096
2157
  }
2097
2158
  });
@@ -2121,10 +2182,9 @@ async function query(command, args, isJson) {
2121
2182
  }
2122
2183
  }
2123
2184
  function queryDecisions(db, args, isJson) {
2124
- const levelIdx = args.indexOf("--level");
2125
- const level = levelIdx >= 0 ? args[levelIdx + 1] : void 0;
2126
- const expIdx = args.indexOf("--experiment");
2127
- const experimentId = expIdx >= 0 ? Number(args[expIdx + 1]) : void 0;
2185
+ const level = getFlagValue(args, "--level");
2186
+ const expIdStr = getFlagValue(args, "--experiment");
2187
+ const experimentId = expIdStr !== void 0 ? Number(expIdStr) : void 0;
2128
2188
  const decisions = listAllDecisions(db, level, experimentId);
2129
2189
  if (isJson) {
2130
2190
  console.log(JSON.stringify(decisions, null, 2));
@@ -2145,10 +2205,8 @@ function queryDecisions(db, args, isJson) {
2145
2205
  console.log(table(["ID", "Exp", "Level", "Description", "Status"], rows));
2146
2206
  }
2147
2207
  function queryDeadEnds(db, args, isJson) {
2148
- const subTypeIdx = args.indexOf("--sub-type");
2149
- const subType = subTypeIdx >= 0 ? args[subTypeIdx + 1] : void 0;
2150
- const searchIdx = args.indexOf("--search");
2151
- const searchTerm = searchIdx >= 0 ? args[searchIdx + 1] : void 0;
2208
+ const subType = getFlagValue(args, "--sub-type");
2209
+ const searchTerm = getFlagValue(args, "--search");
2152
2210
  let deadEnds;
2153
2211
  if (subType) {
2154
2212
  deadEnds = listDeadEndsBySubType(db, subType);
@@ -2175,12 +2233,12 @@ function queryDeadEnds(db, args, isJson) {
2175
2233
  console.log(table(["ID", "Sub-Type", "Approach", "Constraint"], rows));
2176
2234
  }
2177
2235
  function queryFragility(root, isJson) {
2178
- const fragPath = path6.join(root, "docs", "synthesis", "fragility.md");
2179
- if (!fs6.existsSync(fragPath)) {
2236
+ const fragPath = path5.join(root, "docs", "synthesis", "fragility.md");
2237
+ if (!fs5.existsSync(fragPath)) {
2180
2238
  info("No fragility map found.");
2181
2239
  return;
2182
2240
  }
2183
- const content = fs6.readFileSync(fragPath, "utf-8");
2241
+ const content = fs5.readFileSync(fragPath, "utf-8");
2184
2242
  if (isJson) {
2185
2243
  console.log(JSON.stringify({ content }, null, 2));
2186
2244
  return;
@@ -2214,7 +2272,7 @@ function queryHistory(db, args, isJson) {
2214
2272
  console.log(table(["Exp", "Slug", "Phase", "Metric", "Value", "Captured"], rows));
2215
2273
  }
2216
2274
  function queryCircuitBreakers(db, root, isJson) {
2217
- const config = loadConfig4(root);
2275
+ const config = loadConfig(root);
2218
2276
  const states = getAllCircuitBreakerStates(db, config.cycle.circuit_breaker_threshold);
2219
2277
  if (isJson) {
2220
2278
  console.log(JSON.stringify(states, null, 2));
@@ -2236,7 +2294,7 @@ function queryCircuitBreakers(db, root, isJson) {
2236
2294
  function checkCommit(db) {
2237
2295
  let stdinData = "";
2238
2296
  try {
2239
- stdinData = fs6.readFileSync(0, "utf-8");
2297
+ stdinData = fs5.readFileSync(0, "utf-8");
2240
2298
  } catch {
2241
2299
  }
2242
2300
  if (stdinData) {
@@ -2261,21 +2319,15 @@ function checkCommit(db) {
2261
2319
  process.exit(1);
2262
2320
  }
2263
2321
  }
2264
- function loadConfig4(projectRoot) {
2265
- const configPath = path6.join(projectRoot, ".majlis", "config.json");
2266
- if (!fs6.existsSync(configPath)) {
2267
- return { cycle: { circuit_breaker_threshold: 3 } };
2268
- }
2269
- return JSON.parse(fs6.readFileSync(configPath, "utf-8"));
2270
- }
2271
- var fs6, path6;
2322
+ var fs5, path5;
2272
2323
  var init_query = __esm({
2273
2324
  "src/commands/query.ts"() {
2274
2325
  "use strict";
2275
- fs6 = __toESM(require("fs"));
2276
- path6 = __toESM(require("path"));
2326
+ fs5 = __toESM(require("fs"));
2327
+ path5 = __toESM(require("path"));
2277
2328
  init_connection();
2278
2329
  init_queries();
2330
+ init_config();
2279
2331
  init_format();
2280
2332
  }
2281
2333
  });
@@ -2558,11 +2610,11 @@ var init_parse = __esm({
2558
2610
  // src/agents/spawn.ts
2559
2611
  function loadAgentDefinition(role, projectRoot) {
2560
2612
  const root = projectRoot ?? findProjectRoot() ?? process.cwd();
2561
- const filePath = path7.join(root, ".majlis", "agents", `${role}.md`);
2562
- if (!fs7.existsSync(filePath)) {
2613
+ const filePath = path6.join(root, ".majlis", "agents", `${role}.md`);
2614
+ if (!fs6.existsSync(filePath)) {
2563
2615
  throw new Error(`Agent definition not found: ${filePath}`);
2564
2616
  }
2565
- const content = fs7.readFileSync(filePath, "utf-8");
2617
+ const content = fs6.readFileSync(filePath, "utf-8");
2566
2618
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
2567
2619
  if (!frontmatterMatch) {
2568
2620
  throw new Error(`Invalid agent definition (missing YAML frontmatter): ${filePath}`);
@@ -2583,7 +2635,7 @@ async function spawnAgent(role, context, projectRoot) {
2583
2635
  const agentDef = loadAgentDefinition(role, projectRoot);
2584
2636
  const root = projectRoot ?? findProjectRoot() ?? process.cwd();
2585
2637
  const taskPrompt = context.taskPrompt ?? `Perform your role as ${agentDef.name}.`;
2586
- const contextJson = JSON.stringify(context, null, 2);
2638
+ const contextJson = JSON.stringify(context);
2587
2639
  const prompt = `Here is your context:
2588
2640
 
2589
2641
  \`\`\`json
@@ -2618,7 +2670,7 @@ ${taskPrompt}`;
2618
2670
  }
2619
2671
  async function spawnSynthesiser(context, projectRoot) {
2620
2672
  const root = projectRoot ?? findProjectRoot() ?? process.cwd();
2621
- const contextJson = JSON.stringify(context, null, 2);
2673
+ const contextJson = JSON.stringify(context);
2622
2674
  const taskPrompt = context.taskPrompt ?? "Synthesise the findings into actionable builder guidance.";
2623
2675
  const prompt = `Here is your context:
2624
2676
 
@@ -2631,7 +2683,7 @@ ${taskPrompt}`;
2631
2683
  console.log(`[synthesiser] Spawning (maxTurns: 5)...`);
2632
2684
  const { text: markdown, costUsd, truncated } = await runQuery({
2633
2685
  prompt,
2634
- model: "opus",
2686
+ model: "sonnet",
2635
2687
  tools: ["Read", "Glob", "Grep"],
2636
2688
  systemPrompt,
2637
2689
  cwd: root,
@@ -2645,15 +2697,15 @@ async function spawnRecovery(role, partialOutput, context, projectRoot) {
2645
2697
  const root = projectRoot ?? findProjectRoot() ?? process.cwd();
2646
2698
  const expSlug = context.experiment?.slug ?? "unknown";
2647
2699
  console.log(`[recovery] Cleaning up after truncated ${role} for ${expSlug}...`);
2648
- const expDocPath = path7.join(
2700
+ const expDocPath = path6.join(
2649
2701
  root,
2650
2702
  "docs",
2651
2703
  "experiments",
2652
2704
  `${String(context.experiment?.id ?? 0).padStart(3, "0")}-${expSlug}.md`
2653
2705
  );
2654
- const templatePath = path7.join(root, "docs", "experiments", "_TEMPLATE.md");
2655
- const template = fs7.existsSync(templatePath) ? fs7.readFileSync(templatePath, "utf-8") : "";
2656
- const currentDoc = fs7.existsSync(expDocPath) ? fs7.readFileSync(expDocPath, "utf-8") : "";
2706
+ const templatePath = path6.join(root, "docs", "experiments", "_TEMPLATE.md");
2707
+ const template = fs6.existsSync(templatePath) ? fs6.readFileSync(templatePath, "utf-8") : "";
2708
+ const currentDoc = fs6.existsSync(expDocPath) ? fs6.readFileSync(expDocPath, "utf-8") : "";
2657
2709
  const prompt = `The ${role} agent was truncated (hit max turns) while working on experiment "${expSlug}".
2658
2710
 
2659
2711
  Here is the partial agent output (reasoning + tool calls):
@@ -2790,23 +2842,23 @@ function writeArtifact(role, context, markdown, projectRoot) {
2790
2842
  const dir = dirMap[role];
2791
2843
  if (!dir) return null;
2792
2844
  if (role === "builder" || role === "compressor") return null;
2793
- const fullDir = path7.join(projectRoot, dir);
2794
- if (!fs7.existsSync(fullDir)) {
2795
- fs7.mkdirSync(fullDir, { recursive: true });
2845
+ const fullDir = path6.join(projectRoot, dir);
2846
+ if (!fs6.existsSync(fullDir)) {
2847
+ fs6.mkdirSync(fullDir, { recursive: true });
2796
2848
  }
2797
2849
  const expSlug = context.experiment?.slug ?? "general";
2798
2850
  const nextNum = String(context.experiment?.id ?? 1).padStart(3, "0");
2799
2851
  const filename = `${nextNum}-${role}-${expSlug}.md`;
2800
- const target = path7.join(fullDir, filename);
2801
- fs7.writeFileSync(target, markdown);
2852
+ const target = path6.join(fullDir, filename);
2853
+ fs6.writeFileSync(target, markdown);
2802
2854
  return target;
2803
2855
  }
2804
- var fs7, path7, import_claude_agent_sdk2, ROLE_MAX_TURNS, DIM2, RESET2, CYAN2;
2856
+ var fs6, path6, import_claude_agent_sdk2, ROLE_MAX_TURNS, DIM2, RESET2, CYAN2;
2805
2857
  var init_spawn = __esm({
2806
2858
  "src/agents/spawn.ts"() {
2807
2859
  "use strict";
2808
- fs7 = __toESM(require("fs"));
2809
- path7 = __toESM(require("path"));
2860
+ fs6 = __toESM(require("fs"));
2861
+ path6 = __toESM(require("path"));
2810
2862
  import_claude_agent_sdk2 = require("@anthropic-ai/claude-agent-sdk");
2811
2863
  init_parse();
2812
2864
  init_connection();
@@ -2880,11 +2932,13 @@ async function resolve(db, exp, projectRoot) {
2880
2932
  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."
2881
2933
  }, projectRoot);
2882
2934
  const guidanceText = guidance.structured?.guidance ?? guidance.output;
2883
- storeBuilderGuidance(db, exp.id, guidanceText);
2884
- updateExperimentStatus(db, exp.id, "building");
2885
- if (exp.sub_type) {
2886
- incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
2887
- }
2935
+ db.transaction(() => {
2936
+ storeBuilderGuidance(db, exp.id, guidanceText);
2937
+ updateExperimentStatus(db, exp.id, "building");
2938
+ if (exp.sub_type) {
2939
+ incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
2940
+ }
2941
+ })();
2888
2942
  warn(`Experiment ${exp.slug} CYCLING BACK (weak). Guidance generated for builder.`);
2889
2943
  break;
2890
2944
  }
@@ -2892,19 +2946,21 @@ async function resolve(db, exp, projectRoot) {
2892
2946
  gitRevert(exp.branch, projectRoot);
2893
2947
  const rejectedComponents = grades.filter((g) => g.grade === "rejected");
2894
2948
  const whyFailed = rejectedComponents.map((r) => r.notes ?? "rejected").join("; ");
2895
- insertDeadEnd(
2896
- db,
2897
- exp.id,
2898
- exp.hypothesis ?? exp.slug,
2899
- whyFailed,
2900
- `Approach rejected: ${whyFailed}`,
2901
- exp.sub_type,
2902
- "structural"
2903
- );
2904
- updateExperimentStatus(db, exp.id, "dead_end");
2905
- if (exp.sub_type) {
2906
- incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
2907
- }
2949
+ db.transaction(() => {
2950
+ insertDeadEnd(
2951
+ db,
2952
+ exp.id,
2953
+ exp.hypothesis ?? exp.slug,
2954
+ whyFailed,
2955
+ `Approach rejected: ${whyFailed}`,
2956
+ exp.sub_type,
2957
+ "structural"
2958
+ );
2959
+ updateExperimentStatus(db, exp.id, "dead_end");
2960
+ if (exp.sub_type) {
2961
+ incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
2962
+ }
2963
+ })();
2908
2964
  info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
2909
2965
  break;
2910
2966
  }
@@ -2943,23 +2999,23 @@ function gitRevert(branch, cwd) {
2943
2999
  }
2944
3000
  }
2945
3001
  function appendToFragilityMap(projectRoot, expSlug, gaps) {
2946
- const fragPath = path8.join(projectRoot, "docs", "synthesis", "fragility.md");
3002
+ const fragPath = path7.join(projectRoot, "docs", "synthesis", "fragility.md");
2947
3003
  let content = "";
2948
- if (fs8.existsSync(fragPath)) {
2949
- content = fs8.readFileSync(fragPath, "utf-8");
3004
+ if (fs7.existsSync(fragPath)) {
3005
+ content = fs7.readFileSync(fragPath, "utf-8");
2950
3006
  }
2951
3007
  const entry = `
2952
3008
  ## From experiment: ${expSlug}
2953
3009
  ${gaps}
2954
3010
  `;
2955
- fs8.writeFileSync(fragPath, content + entry);
3011
+ fs7.writeFileSync(fragPath, content + entry);
2956
3012
  }
2957
- var fs8, path8, import_node_child_process3;
3013
+ var fs7, path7, import_node_child_process3;
2958
3014
  var init_resolve = __esm({
2959
3015
  "src/resolve.ts"() {
2960
3016
  "use strict";
2961
- fs8 = __toESM(require("fs"));
2962
- path8 = __toESM(require("path"));
3017
+ fs7 = __toESM(require("fs"));
3018
+ path7 = __toESM(require("path"));
2963
3019
  init_types();
2964
3020
  init_queries();
2965
3021
  init_spawn();
@@ -3007,8 +3063,8 @@ async function resolveCmd(args) {
3007
3063
  }
3008
3064
  async function doGate(db, exp, root) {
3009
3065
  transition(exp.status, "gated" /* GATED */);
3010
- const synthesis = readFileOrEmpty(path9.join(root, "docs", "synthesis", "current.md"));
3011
- const fragility = readFileOrEmpty(path9.join(root, "docs", "synthesis", "fragility.md"));
3066
+ const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
3067
+ const fragility = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
3012
3068
  const structuralDeadEnds = exp.sub_type ? listStructuralDeadEndsBySubType(db, exp.sub_type) : listStructuralDeadEnds(db);
3013
3069
  const result = await spawnAgent("gatekeeper", {
3014
3070
  experiment: {
@@ -3052,13 +3108,12 @@ async function doBuild(db, exp, root) {
3052
3108
  transition(exp.status, "building" /* BUILDING */);
3053
3109
  const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
3054
3110
  const builderGuidance = getBuilderGuidance(db, exp.id);
3055
- const fragilityPath = path9.join(root, "docs", "synthesis", "fragility.md");
3056
- const fragility = fs9.existsSync(fragilityPath) ? fs9.readFileSync(fragilityPath, "utf-8") : "";
3057
- const synthesisPath = path9.join(root, "docs", "synthesis", "current.md");
3058
- const synthesis = fs9.existsSync(synthesisPath) ? fs9.readFileSync(synthesisPath, "utf-8") : "";
3111
+ const fragility = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
3112
+ const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
3059
3113
  const confirmedDoubts = getConfirmedDoubts(db, exp.id);
3060
- const config = loadConfig5(root);
3061
- if (config.metrics?.command) {
3114
+ const config = loadConfig(root);
3115
+ const existingBaseline = getMetricsByExperimentAndPhase(db, exp.id, "before");
3116
+ if (config.metrics?.command && existingBaseline.length === 0) {
3062
3117
  try {
3063
3118
  const output = (0, import_node_child_process4.execSync)(config.metrics.command, {
3064
3119
  cwd: root,
@@ -3149,7 +3204,7 @@ async function doChallenge(db, exp, root) {
3149
3204
  } catch {
3150
3205
  }
3151
3206
  if (gitDiff.length > 8e3) gitDiff = gitDiff.slice(0, 8e3) + "\n[DIFF TRUNCATED]";
3152
- const synthesis = readFileOrEmpty(path9.join(root, "docs", "synthesis", "current.md"));
3207
+ const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
3153
3208
  let taskPrompt = `Construct adversarial test cases for experiment ${exp.slug}: ${exp.hypothesis}`;
3154
3209
  if (gitDiff) {
3155
3210
  taskPrompt += `
@@ -3182,9 +3237,9 @@ ${gitDiff}
3182
3237
  async function doDoubt(db, exp, root) {
3183
3238
  transition(exp.status, "doubted" /* DOUBTED */);
3184
3239
  const paddedNum = String(exp.id).padStart(3, "0");
3185
- const expDocPath = path9.join(root, "docs", "experiments", `${paddedNum}-${exp.slug}.md`);
3186
- const experimentDoc = readFileOrEmpty(expDocPath);
3187
- const synthesis = readFileOrEmpty(path9.join(root, "docs", "synthesis", "current.md"));
3240
+ const expDocPath = path8.join(root, "docs", "experiments", `${paddedNum}-${exp.slug}.md`);
3241
+ const experimentDoc = truncateContext(readFileOrEmpty(expDocPath), CONTEXT_LIMITS.experimentDoc);
3242
+ const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
3188
3243
  const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
3189
3244
  let taskPrompt = `Doubt the work in experiment ${exp.slug}: ${exp.hypothesis}. Produce a doubt document with evidence for each doubt.`;
3190
3245
  if (experimentDoc) {
@@ -3223,8 +3278,8 @@ ${experimentDoc}
3223
3278
  }
3224
3279
  async function doScout(db, exp, root) {
3225
3280
  transition(exp.status, "scouted" /* SCOUTED */);
3226
- const synthesis = readFileOrEmpty(path9.join(root, "docs", "synthesis", "current.md"));
3227
- const fragility = readFileOrEmpty(path9.join(root, "docs", "synthesis", "fragility.md"));
3281
+ const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
3282
+ const fragility = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
3228
3283
  const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
3229
3284
  const deadEndsSummary = deadEnds.map(
3230
3285
  (d) => `- [${d.category ?? "structural"}] ${d.approach}: ${d.why_failed}`
@@ -3271,12 +3326,12 @@ ${fragility}`;
3271
3326
  async function doVerify(db, exp, root) {
3272
3327
  transition(exp.status, "verifying" /* VERIFYING */);
3273
3328
  const doubts = getDoubtsByExperiment(db, exp.id);
3274
- const challengeDir = path9.join(root, "docs", "challenges");
3329
+ const challengeDir = path8.join(root, "docs", "challenges");
3275
3330
  let challenges = "";
3276
- if (fs9.existsSync(challengeDir)) {
3277
- const files = fs9.readdirSync(challengeDir).filter((f) => f.includes(exp.slug) && f.endsWith(".md"));
3331
+ if (fs8.existsSync(challengeDir)) {
3332
+ const files = fs8.readdirSync(challengeDir).filter((f) => f.includes(exp.slug) && f.endsWith(".md"));
3278
3333
  for (const f of files) {
3279
- challenges += fs9.readFileSync(path9.join(challengeDir, f), "utf-8") + "\n\n";
3334
+ challenges += fs8.readFileSync(path8.join(challengeDir, f), "utf-8") + "\n\n";
3280
3335
  }
3281
3336
  }
3282
3337
  const beforeMetrics = getMetricsByExperimentAndPhase(db, exp.id, "before");
@@ -3344,14 +3399,14 @@ async function doVerify(db, exp, root) {
3344
3399
  success(`Verification complete for ${exp.slug}. Run \`majlis resolve\` next.`);
3345
3400
  }
3346
3401
  async function doCompress(db, root) {
3347
- const synthesisPath = path9.join(root, "docs", "synthesis", "current.md");
3348
- const sizeBefore = fs9.existsSync(synthesisPath) ? fs9.statSync(synthesisPath).size : 0;
3402
+ const synthesisPath = path8.join(root, "docs", "synthesis", "current.md");
3403
+ const sizeBefore = fs8.existsSync(synthesisPath) ? fs8.statSync(synthesisPath).size : 0;
3349
3404
  const sessionCount = getSessionsSinceCompression(db);
3350
3405
  const dbExport = exportForCompressor(db);
3351
3406
  const result = await spawnAgent("compressor", {
3352
3407
  taskPrompt: "## Structured Data (CANONICAL \u2014 from SQLite database)\nThe database export below is the source of truth. docs/ files are agent artifacts that may contain stale or incorrect information. Cross-reference everything against this data.\n\n" + dbExport + "\n\n## Your Task\nRead ALL experiments, decisions, doubts, challenges, verification reports, reframes, and recent diffs. Cross-reference for contradictions, redundancies, and patterns. REWRITE docs/synthesis/current.md \u2014 shorter and denser. Update docs/synthesis/fragility.md with current weak areas. Update docs/synthesis/dead-ends.md with structural constraints from rejected experiments."
3353
3408
  }, root);
3354
- const sizeAfter = fs9.existsSync(synthesisPath) ? fs9.statSync(synthesisPath).size : 0;
3409
+ const sizeAfter = fs8.existsSync(synthesisPath) ? fs8.statSync(synthesisPath).size : 0;
3355
3410
  recordCompression(db, sessionCount, sizeBefore, sizeAfter);
3356
3411
  success(`Compression complete. Synthesis: ${sizeBefore}B \u2192 ${sizeAfter}B`);
3357
3412
  }
@@ -3442,35 +3497,12 @@ function ingestStructuredOutput(db, experimentId, structured) {
3442
3497
  info(`Ingested ${structured.findings.length} finding(s)`);
3443
3498
  }
3444
3499
  }
3445
- function readFileOrEmpty(filePath) {
3446
- try {
3447
- return fs9.readFileSync(filePath, "utf-8");
3448
- } catch {
3449
- return "";
3450
- }
3451
- }
3452
- function loadConfig5(projectRoot) {
3453
- const configPath = path9.join(projectRoot, ".majlis", "config.json");
3454
- if (!fs9.existsSync(configPath)) {
3455
- return {
3456
- project: { name: "", description: "", objective: "" },
3457
- cycle: {
3458
- compression_interval: 5,
3459
- circuit_breaker_threshold: 3,
3460
- require_doubt_before_verify: true,
3461
- require_challenge_before_verify: false,
3462
- auto_baseline_on_new_experiment: true
3463
- }
3464
- };
3465
- }
3466
- return JSON.parse(fs9.readFileSync(configPath, "utf-8"));
3467
- }
3468
- var fs9, path9, import_node_child_process4;
3500
+ var fs8, path8, import_node_child_process4;
3469
3501
  var init_cycle = __esm({
3470
3502
  "src/commands/cycle.ts"() {
3471
3503
  "use strict";
3472
- fs9 = __toESM(require("fs"));
3473
- path9 = __toESM(require("path"));
3504
+ fs8 = __toESM(require("fs"));
3505
+ path8 = __toESM(require("path"));
3474
3506
  import_node_child_process4 = require("child_process");
3475
3507
  init_connection();
3476
3508
  init_queries();
@@ -3478,6 +3510,7 @@ var init_cycle = __esm({
3478
3510
  init_types();
3479
3511
  init_spawn();
3480
3512
  init_resolve();
3513
+ init_config();
3481
3514
  init_metrics();
3482
3515
  init_format();
3483
3516
  }
@@ -3496,10 +3529,10 @@ async function classify(args) {
3496
3529
  if (!domain) {
3497
3530
  throw new Error('Usage: majlis classify "domain description"');
3498
3531
  }
3499
- const synthesisPath = path10.join(root, "docs", "synthesis", "current.md");
3500
- const synthesis = fs10.existsSync(synthesisPath) ? fs10.readFileSync(synthesisPath, "utf-8") : "";
3501
- const deadEndsPath = path10.join(root, "docs", "synthesis", "dead-ends.md");
3502
- const deadEnds = fs10.existsSync(deadEndsPath) ? fs10.readFileSync(deadEndsPath, "utf-8") : "";
3532
+ const synthesisPath = path9.join(root, "docs", "synthesis", "current.md");
3533
+ const synthesis = fs9.existsSync(synthesisPath) ? fs9.readFileSync(synthesisPath, "utf-8") : "";
3534
+ const deadEndsPath = path9.join(root, "docs", "synthesis", "dead-ends.md");
3535
+ const deadEnds = fs9.existsSync(deadEndsPath) ? fs9.readFileSync(deadEndsPath, "utf-8") : "";
3503
3536
  info(`Classifying problem domain: ${domain}`);
3504
3537
  const result = await spawnAgent("builder", {
3505
3538
  synthesis,
@@ -3517,22 +3550,22 @@ Write the classification to docs/classification/ following the template.`
3517
3550
  async function reframe(args) {
3518
3551
  const root = findProjectRoot();
3519
3552
  if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
3520
- const classificationDir = path10.join(root, "docs", "classification");
3553
+ const classificationDir = path9.join(root, "docs", "classification");
3521
3554
  let classificationContent = "";
3522
- if (fs10.existsSync(classificationDir)) {
3523
- const files = fs10.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
3555
+ if (fs9.existsSync(classificationDir)) {
3556
+ const files = fs9.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
3524
3557
  for (const f of files) {
3525
- classificationContent += fs10.readFileSync(path10.join(classificationDir, f), "utf-8") + "\n\n";
3558
+ classificationContent += fs9.readFileSync(path9.join(classificationDir, f), "utf-8") + "\n\n";
3526
3559
  }
3527
3560
  }
3528
- const synthesisPath = path10.join(root, "docs", "synthesis", "current.md");
3529
- const synthesis = fs10.existsSync(synthesisPath) ? fs10.readFileSync(synthesisPath, "utf-8") : "";
3530
- const deadEndsPath = path10.join(root, "docs", "synthesis", "dead-ends.md");
3531
- const deadEnds = fs10.existsSync(deadEndsPath) ? fs10.readFileSync(deadEndsPath, "utf-8") : "";
3532
- const configPath = path10.join(root, ".majlis", "config.json");
3561
+ const synthesisPath = path9.join(root, "docs", "synthesis", "current.md");
3562
+ const synthesis = fs9.existsSync(synthesisPath) ? fs9.readFileSync(synthesisPath, "utf-8") : "";
3563
+ const deadEndsPath = path9.join(root, "docs", "synthesis", "dead-ends.md");
3564
+ const deadEnds = fs9.existsSync(deadEndsPath) ? fs9.readFileSync(deadEndsPath, "utf-8") : "";
3565
+ const configPath = path9.join(root, ".majlis", "config.json");
3533
3566
  let problemStatement = "";
3534
- if (fs10.existsSync(configPath)) {
3535
- const config = JSON.parse(fs10.readFileSync(configPath, "utf-8"));
3567
+ if (fs9.existsSync(configPath)) {
3568
+ const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
3536
3569
  problemStatement = `${config.project?.description ?? ""}
3537
3570
  Objective: ${config.project?.objective ?? ""}`;
3538
3571
  }
@@ -3556,12 +3589,12 @@ Write to docs/reframes/.`
3556
3589
  }, root);
3557
3590
  success("Reframe complete. Check docs/reframes/ for the output.");
3558
3591
  }
3559
- var fs10, path10;
3592
+ var fs9, path9;
3560
3593
  var init_classify = __esm({
3561
3594
  "src/commands/classify.ts"() {
3562
3595
  "use strict";
3563
- fs10 = __toESM(require("fs"));
3564
- path10 = __toESM(require("path"));
3596
+ fs9 = __toESM(require("fs"));
3597
+ path9 = __toESM(require("path"));
3565
3598
  init_connection();
3566
3599
  init_spawn();
3567
3600
  init_format();
@@ -3578,20 +3611,19 @@ async function audit(args) {
3578
3611
  if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
3579
3612
  const db = getDb(root);
3580
3613
  const objective = args.filter((a) => !a.startsWith("--")).join(" ");
3581
- const config = loadConfig6(root);
3614
+ const config = loadConfig(root);
3582
3615
  const experiments = listAllExperiments(db);
3583
3616
  const deadEnds = listAllDeadEnds(db);
3584
3617
  const circuitBreakers = getAllCircuitBreakerStates(db, config.cycle.circuit_breaker_threshold);
3585
- const classificationDir = path11.join(root, "docs", "classification");
3618
+ const classificationDir = path10.join(root, "docs", "classification");
3586
3619
  let classification = "";
3587
- if (fs11.existsSync(classificationDir)) {
3588
- const files = fs11.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
3620
+ if (fs10.existsSync(classificationDir)) {
3621
+ const files = fs10.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
3589
3622
  for (const f of files) {
3590
- classification += fs11.readFileSync(path11.join(classificationDir, f), "utf-8") + "\n\n";
3623
+ classification += fs10.readFileSync(path10.join(classificationDir, f), "utf-8") + "\n\n";
3591
3624
  }
3592
3625
  }
3593
- const synthesisPath = path11.join(root, "docs", "synthesis", "current.md");
3594
- const synthesis = fs11.existsSync(synthesisPath) ? fs11.readFileSync(synthesisPath, "utf-8") : "";
3626
+ const synthesis = readFileOrEmpty(path10.join(root, "docs", "synthesis", "current.md"));
3595
3627
  header("Maqasid Check \u2014 Purpose Audit");
3596
3628
  const trippedBreakers = circuitBreakers.filter((cb) => cb.tripped);
3597
3629
  if (trippedBreakers.length > 0) {
@@ -3635,22 +3667,16 @@ Output: either "classification confirmed \u2014 continue" or "re-classify from X
3635
3667
  }, root);
3636
3668
  success("Purpose audit complete. Review the output above.");
3637
3669
  }
3638
- function loadConfig6(projectRoot) {
3639
- const configPath = path11.join(projectRoot, ".majlis", "config.json");
3640
- if (!fs11.existsSync(configPath)) {
3641
- return { project: { name: "", description: "", objective: "" }, cycle: { circuit_breaker_threshold: 3 } };
3642
- }
3643
- return JSON.parse(fs11.readFileSync(configPath, "utf-8"));
3644
- }
3645
- var fs11, path11;
3670
+ var fs10, path10;
3646
3671
  var init_audit = __esm({
3647
3672
  "src/commands/audit.ts"() {
3648
3673
  "use strict";
3649
- fs11 = __toESM(require("fs"));
3650
- path11 = __toESM(require("path"));
3674
+ fs10 = __toESM(require("fs"));
3675
+ path10 = __toESM(require("path"));
3651
3676
  init_connection();
3652
3677
  init_queries();
3653
3678
  init_spawn();
3679
+ init_config();
3654
3680
  init_format();
3655
3681
  }
3656
3682
  });
@@ -3664,7 +3690,7 @@ async function next(args, isJson) {
3664
3690
  const root = findProjectRoot();
3665
3691
  if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
3666
3692
  const db = getDb(root);
3667
- const config = loadConfig7(root);
3693
+ const config = loadConfig(root);
3668
3694
  const slugArg = args.filter((a) => !a.startsWith("--"))[0];
3669
3695
  let exp;
3670
3696
  if (slugArg) {
@@ -3696,7 +3722,17 @@ async function runNextStep(db, exp, config, root, isJson) {
3696
3722
  }
3697
3723
  if (exp.sub_type && checkCircuitBreaker(db, exp.sub_type, config.cycle.circuit_breaker_threshold)) {
3698
3724
  warn(`Circuit breaker: ${exp.sub_type} has ${config.cycle.circuit_breaker_threshold}+ failures.`);
3699
- warn("Triggering Maqasid Check (purpose audit).");
3725
+ insertDeadEnd(
3726
+ db,
3727
+ exp.id,
3728
+ exp.hypothesis ?? exp.slug,
3729
+ `Circuit breaker tripped for ${exp.sub_type}`,
3730
+ `Sub-type ${exp.sub_type} exceeded ${config.cycle.circuit_breaker_threshold} failures`,
3731
+ exp.sub_type,
3732
+ "procedural"
3733
+ );
3734
+ updateExperimentStatus(db, exp.id, "dead_end");
3735
+ warn("Experiment dead-ended. Triggering Maqasid Check (purpose audit).");
3700
3736
  await audit([config.project?.objective ?? ""]);
3701
3737
  return;
3702
3738
  }
@@ -3736,6 +3772,16 @@ async function runAutoLoop(db, exp, config, root, isJson) {
3736
3772
  }
3737
3773
  if (exp.sub_type && checkCircuitBreaker(db, exp.sub_type, config.cycle.circuit_breaker_threshold)) {
3738
3774
  warn(`Circuit breaker tripped for ${exp.sub_type}. Stopping auto mode.`);
3775
+ insertDeadEnd(
3776
+ db,
3777
+ exp.id,
3778
+ exp.hypothesis ?? exp.slug,
3779
+ `Circuit breaker tripped for ${exp.sub_type}`,
3780
+ `Sub-type ${exp.sub_type} exceeded ${config.cycle.circuit_breaker_threshold} failures`,
3781
+ exp.sub_type,
3782
+ "procedural"
3783
+ );
3784
+ updateExperimentStatus(db, exp.id, "dead_end");
3739
3785
  await audit([config.project?.objective ?? ""]);
3740
3786
  break;
3741
3787
  }
@@ -3788,33 +3834,15 @@ async function executeStep(step, exp, root) {
3788
3834
  warn(`Don't know how to execute step: ${step}`);
3789
3835
  }
3790
3836
  }
3791
- function loadConfig7(projectRoot) {
3792
- const configPath = path12.join(projectRoot, ".majlis", "config.json");
3793
- if (!fs12.existsSync(configPath)) {
3794
- return {
3795
- project: { name: "", description: "", objective: "" },
3796
- cycle: {
3797
- compression_interval: 5,
3798
- circuit_breaker_threshold: 3,
3799
- require_doubt_before_verify: true,
3800
- require_challenge_before_verify: false,
3801
- auto_baseline_on_new_experiment: true
3802
- }
3803
- };
3804
- }
3805
- return JSON.parse(fs12.readFileSync(configPath, "utf-8"));
3806
- }
3807
- var fs12, path12;
3808
3837
  var init_next = __esm({
3809
3838
  "src/commands/next.ts"() {
3810
3839
  "use strict";
3811
- fs12 = __toESM(require("fs"));
3812
- path12 = __toESM(require("path"));
3813
3840
  init_connection();
3814
3841
  init_queries();
3815
3842
  init_machine();
3816
3843
  init_types();
3817
3844
  init_queries();
3845
+ init_config();
3818
3846
  init_cycle();
3819
3847
  init_audit();
3820
3848
  init_format();
@@ -3834,13 +3862,19 @@ async function run(args) {
3834
3862
  throw new Error('Usage: majlis run "goal description"');
3835
3863
  }
3836
3864
  const db = getDb(root);
3837
- const config = loadConfig8(root);
3865
+ const config = loadConfig(root);
3838
3866
  const MAX_EXPERIMENTS = 10;
3839
3867
  const MAX_STEPS = 200;
3840
3868
  let experimentCount = 0;
3841
3869
  let stepCount = 0;
3870
+ let consecutiveFailures = 0;
3871
+ const usedHypotheses = /* @__PURE__ */ new Set();
3842
3872
  header(`Autonomous Mode \u2014 ${goal}`);
3843
3873
  while (stepCount < MAX_STEPS && experimentCount < MAX_EXPERIMENTS) {
3874
+ if (isShutdownRequested()) {
3875
+ warn("Shutdown requested. Stopping autonomous mode.");
3876
+ break;
3877
+ }
3844
3878
  stepCount++;
3845
3879
  let exp = getLatestExperiment(db);
3846
3880
  if (!exp) {
@@ -3860,6 +3894,11 @@ async function run(args) {
3860
3894
  success("Planner says the goal has been met. Stopping.");
3861
3895
  break;
3862
3896
  }
3897
+ if (usedHypotheses.has(hypothesis)) {
3898
+ warn(`Planner returned duplicate hypothesis: "${hypothesis.slice(0, 80)}". Stopping.`);
3899
+ break;
3900
+ }
3901
+ usedHypotheses.add(hypothesis);
3863
3902
  info(`Next hypothesis: ${hypothesis}`);
3864
3903
  exp = createNewExperiment(db, root, hypothesis);
3865
3904
  success(`Created experiment #${exp.id}: ${exp.slug}`);
@@ -3875,7 +3914,9 @@ async function run(args) {
3875
3914
  info(`[Step ${stepCount}] ${exp.slug}: ${exp.status}`);
3876
3915
  try {
3877
3916
  await next([exp.slug], false);
3917
+ consecutiveFailures = 0;
3878
3918
  } catch (err) {
3919
+ consecutiveFailures++;
3879
3920
  const message = err instanceof Error ? err.message : String(err);
3880
3921
  warn(`Step failed for ${exp.slug}: ${message}`);
3881
3922
  try {
@@ -3889,7 +3930,13 @@ async function run(args) {
3889
3930
  "procedural"
3890
3931
  );
3891
3932
  updateExperimentStatus(db, exp.id, "dead_end");
3892
- } catch {
3933
+ } catch (innerErr) {
3934
+ const innerMsg = innerErr instanceof Error ? innerErr.message : String(innerErr);
3935
+ warn(`Could not record dead-end: ${innerMsg}`);
3936
+ }
3937
+ if (consecutiveFailures >= 3) {
3938
+ warn(`${consecutiveFailures} consecutive failures. Stopping autonomous mode.`);
3939
+ break;
3893
3940
  }
3894
3941
  }
3895
3942
  }
@@ -3902,11 +3949,11 @@ async function run(args) {
3902
3949
  info("Run `majlis status` to see final state.");
3903
3950
  }
3904
3951
  async function deriveNextHypothesis(goal, root, db) {
3905
- const synthesis = readFileOrEmpty2(path13.join(root, "docs", "synthesis", "current.md"));
3906
- const fragility = readFileOrEmpty2(path13.join(root, "docs", "synthesis", "fragility.md"));
3907
- const deadEndsDoc = readFileOrEmpty2(path13.join(root, "docs", "synthesis", "dead-ends.md"));
3952
+ const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
3953
+ const fragility = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
3954
+ const deadEndsDoc = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "dead-ends.md")), CONTEXT_LIMITS.deadEnds);
3908
3955
  const deadEnds = listAllDeadEnds(db);
3909
- const config = loadConfig8(root);
3956
+ const config = loadConfig(root);
3910
3957
  let metricsOutput = "";
3911
3958
  if (config.metrics?.command) {
3912
3959
  try {
@@ -4016,49 +4063,26 @@ function createNewExperiment(db, root, hypothesis) {
4016
4063
  const exp = createExperiment(db, finalSlug, branch, hypothesis, null, null);
4017
4064
  updateExperimentStatus(db, exp.id, "reframed");
4018
4065
  exp.status = "reframed";
4019
- const docsDir = path13.join(root, "docs", "experiments");
4020
- const templatePath = path13.join(docsDir, "_TEMPLATE.md");
4021
- if (fs13.existsSync(templatePath)) {
4022
- const template = fs13.readFileSync(templatePath, "utf-8");
4066
+ const docsDir = path11.join(root, "docs", "experiments");
4067
+ const templatePath = path11.join(docsDir, "_TEMPLATE.md");
4068
+ if (fs11.existsSync(templatePath)) {
4069
+ const template = fs11.readFileSync(templatePath, "utf-8");
4023
4070
  const logContent = template.replace(/\{\{title\}\}/g, hypothesis).replace(/\{\{hypothesis\}\}/g, hypothesis).replace(/\{\{branch\}\}/g, branch).replace(/\{\{status\}\}/g, "classified").replace(/\{\{sub_type\}\}/g, "unclassified").replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]);
4024
- const logPath = path13.join(docsDir, `${paddedNum}-${finalSlug}.md`);
4025
- fs13.writeFileSync(logPath, logContent);
4071
+ const logPath = path11.join(docsDir, `${paddedNum}-${finalSlug}.md`);
4072
+ fs11.writeFileSync(logPath, logContent);
4026
4073
  info(`Created experiment log: docs/experiments/${paddedNum}-${finalSlug}.md`);
4027
4074
  }
4028
4075
  return exp;
4029
4076
  }
4030
- function readFileOrEmpty2(filePath) {
4031
- try {
4032
- return fs13.readFileSync(filePath, "utf-8");
4033
- } catch {
4034
- return "";
4035
- }
4036
- }
4037
4077
  function slugify2(text) {
4038
4078
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
4039
4079
  }
4040
- function loadConfig8(projectRoot) {
4041
- const configPath = path13.join(projectRoot, ".majlis", "config.json");
4042
- if (!fs13.existsSync(configPath)) {
4043
- return {
4044
- project: { name: "", description: "", objective: "" },
4045
- cycle: {
4046
- compression_interval: 5,
4047
- circuit_breaker_threshold: 3,
4048
- require_doubt_before_verify: true,
4049
- require_challenge_before_verify: false,
4050
- auto_baseline_on_new_experiment: true
4051
- }
4052
- };
4053
- }
4054
- return JSON.parse(fs13.readFileSync(configPath, "utf-8"));
4055
- }
4056
- var fs13, path13, import_node_child_process5;
4080
+ var fs11, path11, import_node_child_process5;
4057
4081
  var init_run = __esm({
4058
4082
  "src/commands/run.ts"() {
4059
4083
  "use strict";
4060
- fs13 = __toESM(require("fs"));
4061
- path13 = __toESM(require("path"));
4084
+ fs11 = __toESM(require("fs"));
4085
+ path11 = __toESM(require("path"));
4062
4086
  import_node_child_process5 = require("child_process");
4063
4087
  init_connection();
4064
4088
  init_queries();
@@ -4066,17 +4090,27 @@ var init_run = __esm({
4066
4090
  init_next();
4067
4091
  init_cycle();
4068
4092
  init_spawn();
4093
+ init_config();
4094
+ init_shutdown();
4069
4095
  init_format();
4070
4096
  }
4071
4097
  });
4072
4098
 
4073
4099
  // src/cli.ts
4074
- var fs14 = __toESM(require("fs"));
4075
- var path14 = __toESM(require("path"));
4100
+ var fs12 = __toESM(require("fs"));
4101
+ var path12 = __toESM(require("path"));
4076
4102
  var VERSION = JSON.parse(
4077
- fs14.readFileSync(path14.join(__dirname, "..", "package.json"), "utf-8")
4103
+ fs12.readFileSync(path12.join(__dirname, "..", "package.json"), "utf-8")
4078
4104
  ).version;
4079
4105
  async function main() {
4106
+ let sigintCount = 0;
4107
+ process.on("SIGINT", () => {
4108
+ sigintCount++;
4109
+ if (sigintCount >= 2) process.exit(130);
4110
+ const { requestShutdown: requestShutdown2 } = (init_shutdown(), __toCommonJS(shutdown_exports));
4111
+ requestShutdown2();
4112
+ console.error("\n\x1B[33m[majlis] Interrupt received. Finishing current step...\x1B[0m");
4113
+ });
4080
4114
  const args = process.argv.slice(2);
4081
4115
  if (args.includes("--version") || args.includes("-v")) {
4082
4116
  console.log(VERSION);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "majlis",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Multi-agent workflow CLI for structured doubt, independent verification, and compressed knowledge",
5
5
  "bin": {
6
6
  "majlis": "./dist/cli.js"