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.
- package/dist/cli.js +299 -265
- 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 =
|
|
1807
|
-
const
|
|
1885
|
+
const config = loadConfig(root);
|
|
1886
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
1808
1887
|
let exp;
|
|
1809
|
-
if (
|
|
1810
|
-
exp = getExperimentById(db, Number(
|
|
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 =
|
|
1859
|
-
const
|
|
1937
|
+
const config = loadConfig(root);
|
|
1938
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
1860
1939
|
let exp;
|
|
1861
|
-
if (
|
|
1862
|
-
exp = getExperimentById(db, Number(
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
1958
|
-
const templatePath =
|
|
1959
|
-
if (
|
|
1960
|
-
const template =
|
|
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 =
|
|
1963
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2034
|
-
|
|
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
|
|
2078
|
-
const
|
|
2079
|
-
const
|
|
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
|
|
2125
|
-
const
|
|
2126
|
-
const
|
|
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
|
|
2149
|
-
const
|
|
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 =
|
|
2179
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
2276
|
-
|
|
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 =
|
|
2562
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
|
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: "
|
|
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 =
|
|
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 =
|
|
2655
|
-
const template =
|
|
2656
|
-
const currentDoc =
|
|
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 =
|
|
2794
|
-
if (!
|
|
2795
|
-
|
|
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 =
|
|
2801
|
-
|
|
2852
|
+
const target = path6.join(fullDir, filename);
|
|
2853
|
+
fs6.writeFileSync(target, markdown);
|
|
2802
2854
|
return target;
|
|
2803
2855
|
}
|
|
2804
|
-
var
|
|
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
|
-
|
|
2809
|
-
|
|
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
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
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
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
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 =
|
|
3002
|
+
const fragPath = path7.join(projectRoot, "docs", "synthesis", "fragility.md");
|
|
2947
3003
|
let content = "";
|
|
2948
|
-
if (
|
|
2949
|
-
content =
|
|
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
|
-
|
|
3011
|
+
fs7.writeFileSync(fragPath, content + entry);
|
|
2956
3012
|
}
|
|
2957
|
-
var
|
|
3013
|
+
var fs7, path7, import_node_child_process3;
|
|
2958
3014
|
var init_resolve = __esm({
|
|
2959
3015
|
"src/resolve.ts"() {
|
|
2960
3016
|
"use strict";
|
|
2961
|
-
|
|
2962
|
-
|
|
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(
|
|
3011
|
-
const fragility = readFileOrEmpty(
|
|
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
|
|
3056
|
-
const
|
|
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 =
|
|
3061
|
-
|
|
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(
|
|
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 =
|
|
3186
|
-
const experimentDoc = readFileOrEmpty(expDocPath);
|
|
3187
|
-
const synthesis = readFileOrEmpty(
|
|
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(
|
|
3227
|
-
const fragility = readFileOrEmpty(
|
|
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 =
|
|
3329
|
+
const challengeDir = path8.join(root, "docs", "challenges");
|
|
3275
3330
|
let challenges = "";
|
|
3276
|
-
if (
|
|
3277
|
-
const files =
|
|
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 +=
|
|
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 =
|
|
3348
|
-
const sizeBefore =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
3473
|
-
|
|
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 =
|
|
3500
|
-
const synthesis =
|
|
3501
|
-
const deadEndsPath =
|
|
3502
|
-
const deadEnds =
|
|
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 =
|
|
3553
|
+
const classificationDir = path9.join(root, "docs", "classification");
|
|
3521
3554
|
let classificationContent = "";
|
|
3522
|
-
if (
|
|
3523
|
-
const files =
|
|
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 +=
|
|
3558
|
+
classificationContent += fs9.readFileSync(path9.join(classificationDir, f), "utf-8") + "\n\n";
|
|
3526
3559
|
}
|
|
3527
3560
|
}
|
|
3528
|
-
const synthesisPath =
|
|
3529
|
-
const synthesis =
|
|
3530
|
-
const deadEndsPath =
|
|
3531
|
-
const deadEnds =
|
|
3532
|
-
const configPath =
|
|
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 (
|
|
3535
|
-
const config = JSON.parse(
|
|
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
|
|
3592
|
+
var fs9, path9;
|
|
3560
3593
|
var init_classify = __esm({
|
|
3561
3594
|
"src/commands/classify.ts"() {
|
|
3562
3595
|
"use strict";
|
|
3563
|
-
|
|
3564
|
-
|
|
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 =
|
|
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 =
|
|
3618
|
+
const classificationDir = path10.join(root, "docs", "classification");
|
|
3586
3619
|
let classification = "";
|
|
3587
|
-
if (
|
|
3588
|
-
const files =
|
|
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 +=
|
|
3623
|
+
classification += fs10.readFileSync(path10.join(classificationDir, f), "utf-8") + "\n\n";
|
|
3591
3624
|
}
|
|
3592
3625
|
}
|
|
3593
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
3650
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
3906
|
-
const fragility =
|
|
3907
|
-
const deadEndsDoc =
|
|
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 =
|
|
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 =
|
|
4020
|
-
const templatePath =
|
|
4021
|
-
if (
|
|
4022
|
-
const template =
|
|
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 =
|
|
4025
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4061
|
-
|
|
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
|
|
4075
|
-
var
|
|
4100
|
+
var fs12 = __toESM(require("fs"));
|
|
4101
|
+
var path12 = __toESM(require("path"));
|
|
4076
4102
|
var VERSION = JSON.parse(
|
|
4077
|
-
|
|
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);
|