majlis 0.7.0 → 0.7.2
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 +646 -250
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -249,6 +249,13 @@ var init_migrations = __esm({
|
|
|
249
249
|
);
|
|
250
250
|
|
|
251
251
|
CREATE INDEX idx_swarm_members_run ON swarm_members(swarm_run_id);
|
|
252
|
+
`);
|
|
253
|
+
},
|
|
254
|
+
// Migration 006: v5 → v6 — Experiment dependencies and scoped context
|
|
255
|
+
(db) => {
|
|
256
|
+
db.exec(`
|
|
257
|
+
ALTER TABLE experiments ADD COLUMN depends_on TEXT;
|
|
258
|
+
ALTER TABLE experiments ADD COLUMN context_files TEXT;
|
|
252
259
|
`);
|
|
253
260
|
}
|
|
254
261
|
];
|
|
@@ -442,7 +449,7 @@ var init_format = __esm({
|
|
|
442
449
|
function getExtractionSchema(role) {
|
|
443
450
|
switch (role) {
|
|
444
451
|
case "builder":
|
|
445
|
-
return '{"decisions": [{"description": "string", "evidence_level": "proof|test|strong_consensus|consensus|analogy|judgment", "justification": "string"}]}';
|
|
452
|
+
return '{"decisions": [{"description": "string", "evidence_level": "proof|test|strong_consensus|consensus|analogy|judgment", "justification": "string"}], "abandon": {"reason": "string", "structural_constraint": "string"}}';
|
|
446
453
|
case "critic":
|
|
447
454
|
return '{"doubts": [{"claim_doubted": "string", "evidence_level_of_claim": "string", "evidence_for_doubt": "string", "severity": "minor|moderate|critical"}]}';
|
|
448
455
|
case "adversary":
|
|
@@ -499,7 +506,7 @@ async function extractStructuredData(role, markdown) {
|
|
|
499
506
|
const tier1 = extractMajlisJsonBlock(markdown);
|
|
500
507
|
if (tier1) {
|
|
501
508
|
const parsed = tryParseJson(tier1);
|
|
502
|
-
if (parsed) return parsed;
|
|
509
|
+
if (parsed) return { data: parsed, tier: 1 };
|
|
503
510
|
console.warn(`[majlis] Malformed JSON in <!-- majlis-json --> block for ${role}. Falling back.`);
|
|
504
511
|
} else {
|
|
505
512
|
console.warn(`[majlis] No <!-- majlis-json --> block found in ${role} output. Falling back.`);
|
|
@@ -507,15 +514,18 @@ async function extractStructuredData(role, markdown) {
|
|
|
507
514
|
const tier2 = extractViaPatterns(role, markdown);
|
|
508
515
|
if (tier2 && hasData(tier2)) {
|
|
509
516
|
console.warn(`[majlis] Used regex fallback for ${role}. Review extracted data.`);
|
|
510
|
-
return tier2;
|
|
517
|
+
return { data: tier2, tier: 2 };
|
|
511
518
|
}
|
|
512
519
|
console.warn(`[majlis] Regex fallback insufficient for ${role}. Using Haiku extraction.`);
|
|
513
520
|
const tier3 = await extractViaHaiku(role, markdown);
|
|
514
|
-
if (tier3)
|
|
521
|
+
if (tier3) {
|
|
522
|
+
console.warn(`[majlis] Tier 3 (Haiku) extraction used for ${role}. Data provenance degraded.`);
|
|
523
|
+
return { data: tier3, tier: 3 };
|
|
524
|
+
}
|
|
515
525
|
console.error(
|
|
516
526
|
`[majlis] FAILED to extract structured data from ${role} output. State machine will continue but data is missing. Manual review required.`
|
|
517
527
|
);
|
|
518
|
-
return null;
|
|
528
|
+
return { data: null, tier: null };
|
|
519
529
|
}
|
|
520
530
|
function extractMajlisJsonBlock(markdown) {
|
|
521
531
|
const match = markdown.match(/<!--\s*majlis-json\s*\n?([\s\S]*?)-->/);
|
|
@@ -585,6 +595,23 @@ function extractViaPatterns(role, markdown) {
|
|
|
585
595
|
});
|
|
586
596
|
}
|
|
587
597
|
if (doubts.length > 0) result.doubts = doubts;
|
|
598
|
+
if (role === "builder") {
|
|
599
|
+
const abandonPattern = /\[ABANDON\]\s*(.+?)(?:\n|$)[\s\S]*?(?:structural.?constraint|Constraint|CONSTRAINT)\s*[:=]\s*(.+?)(?:\n|$)/im;
|
|
600
|
+
const abandonMatch = markdown.match(abandonPattern);
|
|
601
|
+
if (abandonMatch) {
|
|
602
|
+
result.abandon = {
|
|
603
|
+
reason: abandonMatch[1].trim(),
|
|
604
|
+
structural_constraint: abandonMatch[2].trim()
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
const invalidMatch = markdown.match(/(?:HYPOTHESIS\s+INVALID|HYPOTHESIS\s+IMPOSSIBLE)\s*[:.\-—]\s*(.+?)(?:\n|$)/im);
|
|
608
|
+
if (invalidMatch && !result.abandon) {
|
|
609
|
+
result.abandon = {
|
|
610
|
+
reason: invalidMatch[1].trim(),
|
|
611
|
+
structural_constraint: "Extracted via regex \u2014 review original document"
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
588
615
|
return result;
|
|
589
616
|
}
|
|
590
617
|
async function extractViaHaiku(role, markdown) {
|
|
@@ -624,7 +651,7 @@ ${truncated}`;
|
|
|
624
651
|
}
|
|
625
652
|
}
|
|
626
653
|
function hasData(output) {
|
|
627
|
-
return !!(output.decisions && output.decisions.length > 0 || output.grades && output.grades.length > 0 || output.doubts && output.doubts.length > 0 || output.challenges && output.challenges.length > 0 || output.findings && output.findings.length > 0 || output.guidance || output.reframe || output.compression_report || output.gate_decision || output.diagnosis);
|
|
654
|
+
return !!(output.decisions && output.decisions.length > 0 || output.grades && output.grades.length > 0 || output.doubts && output.doubts.length > 0 || output.challenges && output.challenges.length > 0 || output.findings && output.findings.length > 0 || output.guidance || output.reframe || output.compression_report || output.gate_decision || output.diagnosis || output.abandon);
|
|
628
655
|
}
|
|
629
656
|
function validateForRole(role, output) {
|
|
630
657
|
const required = ROLE_REQUIRED_FIELDS[role];
|
|
@@ -839,6 +866,8 @@ function buildPreToolUseGuards(role, cwd) {
|
|
|
839
866
|
const configFile = path2.resolve(cwd, ".majlis", "config.json");
|
|
840
867
|
const dbFile = path2.resolve(cwd, ".majlis", "majlis.db");
|
|
841
868
|
const settingsFile = path2.resolve(cwd, ".claude", "settings.json");
|
|
869
|
+
const claudeDir = path2.resolve(cwd, ".claude");
|
|
870
|
+
const agentsDir = path2.resolve(cwd, ".majlis", "agents");
|
|
842
871
|
const configGuard = async (input) => {
|
|
843
872
|
const toolInput = input.tool_input ?? {};
|
|
844
873
|
const filePath = toolInput.file_path ?? "";
|
|
@@ -847,6 +876,9 @@ function buildPreToolUseGuards(role, cwd) {
|
|
|
847
876
|
if (resolved === configFile || resolved === dbFile || resolved === settingsFile) {
|
|
848
877
|
return { decision: "block", reason: `Builder may not modify framework files: ${filePath}` };
|
|
849
878
|
}
|
|
879
|
+
if (isInsideDir(resolved, claudeDir) || isInsideDir(resolved, agentsDir)) {
|
|
880
|
+
return { decision: "block", reason: `Builder may not modify agent definitions or framework settings: ${filePath}` };
|
|
881
|
+
}
|
|
850
882
|
}
|
|
851
883
|
return {};
|
|
852
884
|
};
|
|
@@ -860,6 +892,8 @@ function buildPreToolUseGuards(role, cwd) {
|
|
|
860
892
|
const configFile = path2.resolve(cwd, ".majlis", "config.json");
|
|
861
893
|
const dbFile = path2.resolve(cwd, ".majlis", "majlis.db");
|
|
862
894
|
const settingsFile = path2.resolve(cwd, ".claude", "settings.json");
|
|
895
|
+
const claudeDir = path2.resolve(cwd, ".claude");
|
|
896
|
+
const agentsDir = path2.resolve(cwd, ".majlis", "agents");
|
|
863
897
|
const configGuard = async (input) => {
|
|
864
898
|
const toolInput = input.tool_input ?? {};
|
|
865
899
|
const filePath = toolInput.file_path ?? "";
|
|
@@ -868,6 +902,9 @@ function buildPreToolUseGuards(role, cwd) {
|
|
|
868
902
|
if (resolved === configFile || resolved === dbFile || resolved === settingsFile) {
|
|
869
903
|
return { decision: "block", reason: `Verifier may not modify framework files: ${filePath}` };
|
|
870
904
|
}
|
|
905
|
+
if (isInsideDir(resolved, claudeDir) || isInsideDir(resolved, agentsDir)) {
|
|
906
|
+
return { decision: "block", reason: `Verifier may not modify agent definitions or framework settings: ${filePath}` };
|
|
907
|
+
}
|
|
871
908
|
}
|
|
872
909
|
return {};
|
|
873
910
|
};
|
|
@@ -940,14 +977,17 @@ ${taskPrompt}`;
|
|
|
940
977
|
if (artifactPath) {
|
|
941
978
|
console.log(`[${role}] Artifact written to ${artifactPath}`);
|
|
942
979
|
}
|
|
943
|
-
const structured = await extractStructuredData(role, markdown);
|
|
980
|
+
const { data: structured, tier: extractionTier } = await extractStructuredData(role, markdown);
|
|
944
981
|
if (structured) {
|
|
945
982
|
const { valid, missing } = validateForRole(role, structured);
|
|
946
983
|
if (!valid) {
|
|
947
984
|
console.warn(`[${role}] Output missing expected fields: ${missing.join(", ")}`);
|
|
948
985
|
}
|
|
949
986
|
}
|
|
950
|
-
|
|
987
|
+
if (extractionTier === 3) {
|
|
988
|
+
console.warn(`[${role}] WARNING: Structured output was reconstructed by Haiku (tier 3). Data provenance degraded.`);
|
|
989
|
+
}
|
|
990
|
+
return { output: markdown, structured, truncated, extractionTier };
|
|
951
991
|
}
|
|
952
992
|
async function spawnSynthesiser(context, projectRoot, opts) {
|
|
953
993
|
const root = projectRoot ?? findProjectRoot() ?? process.cwd();
|
|
@@ -975,7 +1015,7 @@ ${taskPrompt}`;
|
|
|
975
1015
|
role: "synthesiser"
|
|
976
1016
|
});
|
|
977
1017
|
console.log(`[synthesiser] Complete (cost: $${costUsd.toFixed(4)})`);
|
|
978
|
-
return { output: markdown, structured: { guidance: markdown }, truncated };
|
|
1018
|
+
return { output: markdown, structured: { guidance: markdown }, truncated, extractionTier: null };
|
|
979
1019
|
}
|
|
980
1020
|
async function spawnRecovery(role, partialOutput, context, projectRoot) {
|
|
981
1021
|
const root = projectRoot ?? findProjectRoot() ?? process.cwd();
|
|
@@ -1261,7 +1301,7 @@ var init_config = __esm({
|
|
|
1261
1301
|
path3 = __toESM(require("path"));
|
|
1262
1302
|
DEFAULT_CONFIG = {
|
|
1263
1303
|
project: { name: "", description: "", objective: "" },
|
|
1264
|
-
metrics: { command: "", fixtures:
|
|
1304
|
+
metrics: { command: "", fixtures: {}, tracked: {} },
|
|
1265
1305
|
build: { pre_measure: null, post_measure: null },
|
|
1266
1306
|
cycle: {
|
|
1267
1307
|
compression_interval: 5,
|
|
@@ -1278,7 +1318,8 @@ var init_config = __esm({
|
|
|
1278
1318
|
synthesis: 3e4,
|
|
1279
1319
|
fragility: 15e3,
|
|
1280
1320
|
experimentDoc: 15e3,
|
|
1281
|
-
deadEnds: 15e3
|
|
1321
|
+
deadEnds: 15e3,
|
|
1322
|
+
experimentLineage: 15e3
|
|
1282
1323
|
};
|
|
1283
1324
|
}
|
|
1284
1325
|
});
|
|
@@ -2197,12 +2238,13 @@ var init_upgrade = __esm({
|
|
|
2197
2238
|
});
|
|
2198
2239
|
|
|
2199
2240
|
// src/db/queries.ts
|
|
2200
|
-
function createExperiment(db, slug, branch, hypothesis, subType, classificationRef) {
|
|
2241
|
+
function createExperiment(db, slug, branch, hypothesis, subType, classificationRef, dependsOn = null, contextFiles = null) {
|
|
2201
2242
|
const stmt = db.prepare(`
|
|
2202
|
-
INSERT INTO experiments (slug, branch, hypothesis, sub_type, classification_ref, status)
|
|
2203
|
-
VALUES (?, ?, ?, ?, ?, 'classified')
|
|
2243
|
+
INSERT INTO experiments (slug, branch, hypothesis, sub_type, classification_ref, status, depends_on, context_files)
|
|
2244
|
+
VALUES (?, ?, ?, ?, ?, 'classified', ?, ?)
|
|
2204
2245
|
`);
|
|
2205
|
-
const
|
|
2246
|
+
const contextJson = contextFiles && contextFiles.length > 0 ? JSON.stringify(contextFiles) : null;
|
|
2247
|
+
const result = stmt.run(slug, branch, hypothesis, subType, classificationRef, dependsOn, contextJson);
|
|
2206
2248
|
return getExperimentById(db, result.lastInsertRowid);
|
|
2207
2249
|
}
|
|
2208
2250
|
function getExperimentById(db, id) {
|
|
@@ -2487,7 +2529,7 @@ function updateSwarmMember(db, swarmRunId, slug, finalStatus, overallGrade, cost
|
|
|
2487
2529
|
WHERE swarm_run_id = ? AND experiment_slug = ?
|
|
2488
2530
|
`).run(finalStatus, overallGrade, costUsd, error, swarmRunId, slug);
|
|
2489
2531
|
}
|
|
2490
|
-
function exportForCompressor(db, maxLength =
|
|
2532
|
+
function exportForCompressor(db, maxLength = 5e4) {
|
|
2491
2533
|
const experiments = listAllExperiments(db);
|
|
2492
2534
|
const sections = ["# Structured Data Export (from SQLite)\n"];
|
|
2493
2535
|
sections.push("## Experiments");
|
|
@@ -2553,6 +2595,70 @@ function exportForCompressor(db, maxLength = 3e4) {
|
|
|
2553
2595
|
}
|
|
2554
2596
|
return full;
|
|
2555
2597
|
}
|
|
2598
|
+
function exportExperimentLineage(db, subType, maxLength = 15e3) {
|
|
2599
|
+
const experiments = subType ? db.prepare(`SELECT * FROM experiments WHERE sub_type = ? ORDER BY created_at`).all(subType) : listAllExperiments(db);
|
|
2600
|
+
if (experiments.length === 0) return "";
|
|
2601
|
+
const sections = ["## Experiment Lineage (from DB \u2014 canonical, not from synthesis)\n"];
|
|
2602
|
+
for (const exp of experiments) {
|
|
2603
|
+
sections.push(`### ${exp.slug} [${exp.status}]`);
|
|
2604
|
+
if (exp.hypothesis) sections.push(`Hypothesis: ${exp.hypothesis}`);
|
|
2605
|
+
const decisions = listDecisionsByExperiment(db, exp.id);
|
|
2606
|
+
if (decisions.length > 0) {
|
|
2607
|
+
sections.push("Decisions:");
|
|
2608
|
+
for (const d of decisions) {
|
|
2609
|
+
sections.push(` - [${d.evidence_level}/${d.status}] ${d.description}`);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
const beforeMetrics = getMetricsByExperimentAndPhase(db, exp.id, "before");
|
|
2613
|
+
const afterMetrics = getMetricsByExperimentAndPhase(db, exp.id, "after");
|
|
2614
|
+
if (beforeMetrics.length > 0 && afterMetrics.length > 0) {
|
|
2615
|
+
sections.push("Metrics:");
|
|
2616
|
+
for (const bm of beforeMetrics) {
|
|
2617
|
+
const am = afterMetrics.find((a) => a.fixture === bm.fixture && a.metric_name === bm.metric_name);
|
|
2618
|
+
if (am) {
|
|
2619
|
+
const delta = am.metric_value - bm.metric_value;
|
|
2620
|
+
const sign = delta >= 0 ? "+" : "";
|
|
2621
|
+
sections.push(` - ${bm.fixture}/${bm.metric_name}: ${bm.metric_value} \u2192 ${am.metric_value} (${sign}${delta.toFixed(4)})`);
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
const doubts = getDoubtsByExperiment(db, exp.id);
|
|
2626
|
+
const resolved = doubts.filter((d) => d.resolution);
|
|
2627
|
+
if (resolved.length > 0) {
|
|
2628
|
+
sections.push("Doubt resolutions:");
|
|
2629
|
+
for (const d of resolved) {
|
|
2630
|
+
sections.push(` - [${d.resolution}] ${d.claim_doubted}`);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
const verifications = getVerificationsByExperiment(db, exp.id);
|
|
2634
|
+
if (verifications.length > 0) {
|
|
2635
|
+
sections.push("Grades:");
|
|
2636
|
+
for (const v of verifications) {
|
|
2637
|
+
sections.push(` - ${v.component}: ${v.grade}${v.notes ? ` \u2014 ${v.notes}` : ""}`);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
sections.push("");
|
|
2641
|
+
const current = sections.join("\n");
|
|
2642
|
+
if (current.length > maxLength - 500) {
|
|
2643
|
+
sections.push(`[LINEAGE TRUNCATED \u2014 ${experiments.length - experiments.indexOf(exp) - 1} experiments omitted]`);
|
|
2644
|
+
break;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
const deadEnds = subType ? listDeadEndsBySubType(db, subType) : listAllDeadEnds(db);
|
|
2648
|
+
if (deadEnds.length > 0) {
|
|
2649
|
+
sections.push("### Dead Ends (structural constraints)");
|
|
2650
|
+
for (const de of deadEnds) {
|
|
2651
|
+
sections.push(`- [${de.category ?? "structural"}] ${de.approach}: ${de.structural_constraint}`);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
const full = sections.join("\n");
|
|
2655
|
+
if (full.length > maxLength) {
|
|
2656
|
+
return full.slice(0, maxLength) + `
|
|
2657
|
+
|
|
2658
|
+
[LINEAGE TRUNCATED at ${maxLength} chars]`;
|
|
2659
|
+
}
|
|
2660
|
+
return full;
|
|
2661
|
+
}
|
|
2556
2662
|
function exportForDiagnostician(db, maxLength = 6e4) {
|
|
2557
2663
|
const base = exportForCompressor(db, maxLength);
|
|
2558
2664
|
const sections = [base];
|
|
@@ -2711,6 +2817,34 @@ async function status(isJson) {
|
|
|
2711
2817
|
console.log(`
|
|
2712
2818
|
${yellow(`${judgmentDecisions.length} judgment-level decisions`)} (provisional targets for doubt)`);
|
|
2713
2819
|
}
|
|
2820
|
+
console.log();
|
|
2821
|
+
header("Project Readiness");
|
|
2822
|
+
const validation = (0, import_shared2.validateProject)({
|
|
2823
|
+
hasGitRepo: fs9.existsSync(path9.join(root, ".git")),
|
|
2824
|
+
hasClaudeMd: fs9.existsSync(path9.join(root, "CLAUDE.md")),
|
|
2825
|
+
metricsCommand: config.metrics.command,
|
|
2826
|
+
metricsCommandRunnable: checkCommandRunnable(config.metrics.command, root),
|
|
2827
|
+
fixtures: config.metrics.fixtures,
|
|
2828
|
+
tracked: config.metrics.tracked,
|
|
2829
|
+
preMeasure: config.build.pre_measure,
|
|
2830
|
+
hasObjective: !!(config.project.objective && config.project.objective.length > 0),
|
|
2831
|
+
hasSynthesis: (() => {
|
|
2832
|
+
const sp = path9.join(root, "docs", "synthesis", "current.md");
|
|
2833
|
+
if (!fs9.existsSync(sp)) return false;
|
|
2834
|
+
const content = fs9.readFileSync(sp, "utf-8");
|
|
2835
|
+
return content.length > 100 && !content.includes("No experiments yet");
|
|
2836
|
+
})()
|
|
2837
|
+
});
|
|
2838
|
+
console.log((0, import_shared2.formatValidation)(validation));
|
|
2839
|
+
}
|
|
2840
|
+
function checkCommandRunnable(command, cwd) {
|
|
2841
|
+
if (!command || command.includes(`echo '{"fixtures":{}}'`)) return false;
|
|
2842
|
+
try {
|
|
2843
|
+
(0, import_node_child_process3.execSync)(command, { cwd, encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
2844
|
+
return true;
|
|
2845
|
+
} catch {
|
|
2846
|
+
return false;
|
|
2847
|
+
}
|
|
2714
2848
|
}
|
|
2715
2849
|
function buildSummary(expCount, activeSession, sessionsSinceCompression, config) {
|
|
2716
2850
|
const parts = [];
|
|
@@ -2721,12 +2855,17 @@ function buildSummary(expCount, activeSession, sessionsSinceCompression, config)
|
|
|
2721
2855
|
}
|
|
2722
2856
|
return parts.join(". ");
|
|
2723
2857
|
}
|
|
2858
|
+
var fs9, path9, import_node_child_process3, import_shared2;
|
|
2724
2859
|
var init_status = __esm({
|
|
2725
2860
|
"src/commands/status.ts"() {
|
|
2726
2861
|
"use strict";
|
|
2862
|
+
fs9 = __toESM(require("fs"));
|
|
2863
|
+
path9 = __toESM(require("path"));
|
|
2864
|
+
import_node_child_process3 = require("child_process");
|
|
2727
2865
|
init_connection();
|
|
2728
2866
|
init_queries();
|
|
2729
2867
|
init_config();
|
|
2868
|
+
import_shared2 = require("@majlis/shared");
|
|
2730
2869
|
init_format();
|
|
2731
2870
|
}
|
|
2732
2871
|
});
|
|
@@ -2848,6 +2987,10 @@ var init_machine = __esm({
|
|
|
2848
2987
|
});
|
|
2849
2988
|
|
|
2850
2989
|
// src/metrics.ts
|
|
2990
|
+
function isGateFixture(fixtures, fixtureName) {
|
|
2991
|
+
if (Array.isArray(fixtures)) return false;
|
|
2992
|
+
return fixtures[fixtureName]?.gate === true;
|
|
2993
|
+
}
|
|
2851
2994
|
function compareMetrics(db, experimentId, config) {
|
|
2852
2995
|
const before = getMetricsByExperimentAndPhase(db, experimentId, "before");
|
|
2853
2996
|
const after = getMetricsByExperimentAndPhase(db, experimentId, "after");
|
|
@@ -2869,13 +3012,17 @@ function compareMetrics(db, experimentId, config) {
|
|
|
2869
3012
|
before: b.metric_value,
|
|
2870
3013
|
after: a.metric_value,
|
|
2871
3014
|
delta: a.metric_value - b.metric_value,
|
|
2872
|
-
regression
|
|
3015
|
+
regression,
|
|
3016
|
+
gate: isGateFixture(config.metrics.fixtures, fixture)
|
|
2873
3017
|
});
|
|
2874
3018
|
}
|
|
2875
3019
|
}
|
|
2876
3020
|
}
|
|
2877
3021
|
return comparisons;
|
|
2878
3022
|
}
|
|
3023
|
+
function checkGateViolations(comparisons) {
|
|
3024
|
+
return comparisons.filter((c) => c.gate && c.regression);
|
|
3025
|
+
}
|
|
2879
3026
|
function isRegression(before, after, direction, target) {
|
|
2880
3027
|
switch (direction) {
|
|
2881
3028
|
case "lower_is_better":
|
|
@@ -2939,7 +3086,7 @@ async function captureMetrics(phase, args) {
|
|
|
2939
3086
|
if (config.build.pre_measure) {
|
|
2940
3087
|
info(`Running pre-measure: ${config.build.pre_measure}`);
|
|
2941
3088
|
try {
|
|
2942
|
-
(0,
|
|
3089
|
+
(0, import_node_child_process4.execSync)(config.build.pre_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
|
|
2943
3090
|
} catch {
|
|
2944
3091
|
warn("Pre-measure command failed \u2014 continuing anyway.");
|
|
2945
3092
|
}
|
|
@@ -2950,7 +3097,7 @@ async function captureMetrics(phase, args) {
|
|
|
2950
3097
|
info(`Running metrics: ${config.metrics.command}`);
|
|
2951
3098
|
let metricsOutput;
|
|
2952
3099
|
try {
|
|
2953
|
-
metricsOutput = (0,
|
|
3100
|
+
metricsOutput = (0, import_node_child_process4.execSync)(config.metrics.command, {
|
|
2954
3101
|
cwd: root,
|
|
2955
3102
|
encoding: "utf-8",
|
|
2956
3103
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2969,7 +3116,7 @@ async function captureMetrics(phase, args) {
|
|
|
2969
3116
|
success(`Captured ${parsed.length} metric(s) for ${exp.slug} (phase: ${phase})`);
|
|
2970
3117
|
if (config.build.post_measure) {
|
|
2971
3118
|
try {
|
|
2972
|
-
(0,
|
|
3119
|
+
(0, import_node_child_process4.execSync)(config.build.post_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
|
|
2973
3120
|
} catch {
|
|
2974
3121
|
warn("Post-measure command failed.");
|
|
2975
3122
|
}
|
|
@@ -3020,11 +3167,11 @@ function formatDelta(delta) {
|
|
|
3020
3167
|
const prefix = delta > 0 ? "+" : "";
|
|
3021
3168
|
return `${prefix}${delta.toFixed(4)}`;
|
|
3022
3169
|
}
|
|
3023
|
-
var
|
|
3170
|
+
var import_node_child_process4;
|
|
3024
3171
|
var init_measure = __esm({
|
|
3025
3172
|
"src/commands/measure.ts"() {
|
|
3026
3173
|
"use strict";
|
|
3027
|
-
|
|
3174
|
+
import_node_child_process4 = require("child_process");
|
|
3028
3175
|
init_connection();
|
|
3029
3176
|
init_queries();
|
|
3030
3177
|
init_metrics();
|
|
@@ -3057,7 +3204,7 @@ async function newExperiment(args) {
|
|
|
3057
3204
|
const paddedNum = String(num).padStart(3, "0");
|
|
3058
3205
|
const branch = `exp/${paddedNum}-${slug}`;
|
|
3059
3206
|
try {
|
|
3060
|
-
(0,
|
|
3207
|
+
(0, import_node_child_process5.execFileSync)("git", ["checkout", "-b", branch], {
|
|
3061
3208
|
cwd: root,
|
|
3062
3209
|
encoding: "utf-8",
|
|
3063
3210
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3067,15 +3214,28 @@ async function newExperiment(args) {
|
|
|
3067
3214
|
warn(`Could not create branch ${branch} \u2014 continuing without git branch.`);
|
|
3068
3215
|
}
|
|
3069
3216
|
const subType = getFlagValue(args, "--sub-type") ?? null;
|
|
3070
|
-
const
|
|
3217
|
+
const dependsOn = getFlagValue(args, "--depends-on") ?? null;
|
|
3218
|
+
const contextArg = getFlagValue(args, "--context") ?? null;
|
|
3219
|
+
const contextFiles = contextArg ? contextArg.split(",").map((f) => f.trim()) : null;
|
|
3220
|
+
if (dependsOn) {
|
|
3221
|
+
const depExp = getExperimentBySlug(db, dependsOn);
|
|
3222
|
+
if (!depExp) {
|
|
3223
|
+
throw new Error(`Dependency experiment not found: ${dependsOn}`);
|
|
3224
|
+
}
|
|
3225
|
+
info(`Depends on: ${dependsOn} (status: ${depExp.status})`);
|
|
3226
|
+
}
|
|
3227
|
+
const exp = createExperiment(db, slug, branch, hypothesis, subType, null, dependsOn, contextFiles);
|
|
3228
|
+
if (contextFiles) {
|
|
3229
|
+
info(`Context files: ${contextFiles.join(", ")}`);
|
|
3230
|
+
}
|
|
3071
3231
|
success(`Created experiment #${exp.id}: ${exp.slug}`);
|
|
3072
|
-
const docsDir =
|
|
3073
|
-
const templatePath =
|
|
3074
|
-
if (
|
|
3075
|
-
const template =
|
|
3232
|
+
const docsDir = path10.join(root, "docs", "experiments");
|
|
3233
|
+
const templatePath = path10.join(docsDir, "_TEMPLATE.md");
|
|
3234
|
+
if (fs10.existsSync(templatePath)) {
|
|
3235
|
+
const template = fs10.readFileSync(templatePath, "utf-8");
|
|
3076
3236
|
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]);
|
|
3077
|
-
const logPath =
|
|
3078
|
-
|
|
3237
|
+
const logPath = path10.join(docsDir, `${paddedNum}-${slug}.md`);
|
|
3238
|
+
fs10.writeFileSync(logPath, logContent);
|
|
3079
3239
|
info(`Created experiment log: docs/experiments/${paddedNum}-${slug}.md`);
|
|
3080
3240
|
}
|
|
3081
3241
|
autoCommit(root, `new: ${slug}`);
|
|
@@ -3115,19 +3275,19 @@ async function revert(args) {
|
|
|
3115
3275
|
);
|
|
3116
3276
|
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "revert");
|
|
3117
3277
|
try {
|
|
3118
|
-
const currentBranch = (0,
|
|
3278
|
+
const currentBranch = (0, import_node_child_process5.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
3119
3279
|
cwd: root,
|
|
3120
3280
|
encoding: "utf-8"
|
|
3121
3281
|
}).trim();
|
|
3122
3282
|
if (currentBranch === exp.branch) {
|
|
3123
3283
|
try {
|
|
3124
|
-
(0,
|
|
3284
|
+
(0, import_node_child_process5.execFileSync)("git", ["checkout", "main"], {
|
|
3125
3285
|
cwd: root,
|
|
3126
3286
|
encoding: "utf-8",
|
|
3127
3287
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3128
3288
|
});
|
|
3129
3289
|
} catch {
|
|
3130
|
-
(0,
|
|
3290
|
+
(0, import_node_child_process5.execFileSync)("git", ["checkout", "master"], {
|
|
3131
3291
|
cwd: root,
|
|
3132
3292
|
encoding: "utf-8",
|
|
3133
3293
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3139,13 +3299,13 @@ async function revert(args) {
|
|
|
3139
3299
|
}
|
|
3140
3300
|
info(`Experiment ${exp.slug} reverted to dead-end. Reason: ${reason}`);
|
|
3141
3301
|
}
|
|
3142
|
-
var
|
|
3302
|
+
var fs10, path10, import_node_child_process5;
|
|
3143
3303
|
var init_experiment = __esm({
|
|
3144
3304
|
"src/commands/experiment.ts"() {
|
|
3145
3305
|
"use strict";
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3306
|
+
fs10 = __toESM(require("fs"));
|
|
3307
|
+
path10 = __toESM(require("path"));
|
|
3308
|
+
import_node_child_process5 = require("child_process");
|
|
3149
3309
|
init_connection();
|
|
3150
3310
|
init_queries();
|
|
3151
3311
|
init_machine();
|
|
@@ -3288,12 +3448,12 @@ function queryDeadEnds(db, args, isJson) {
|
|
|
3288
3448
|
console.log(table(["ID", "Sub-Type", "Approach", "Constraint"], rows));
|
|
3289
3449
|
}
|
|
3290
3450
|
function queryFragility(root, isJson) {
|
|
3291
|
-
const fragPath =
|
|
3292
|
-
if (!
|
|
3451
|
+
const fragPath = path11.join(root, "docs", "synthesis", "fragility.md");
|
|
3452
|
+
if (!fs11.existsSync(fragPath)) {
|
|
3293
3453
|
info("No fragility map found.");
|
|
3294
3454
|
return;
|
|
3295
3455
|
}
|
|
3296
|
-
const content =
|
|
3456
|
+
const content = fs11.readFileSync(fragPath, "utf-8");
|
|
3297
3457
|
if (isJson) {
|
|
3298
3458
|
console.log(JSON.stringify({ content }, null, 2));
|
|
3299
3459
|
return;
|
|
@@ -3349,7 +3509,7 @@ function queryCircuitBreakers(db, root, isJson) {
|
|
|
3349
3509
|
function checkCommit(db) {
|
|
3350
3510
|
let stdinData = "";
|
|
3351
3511
|
try {
|
|
3352
|
-
stdinData =
|
|
3512
|
+
stdinData = fs11.readFileSync(0, "utf-8");
|
|
3353
3513
|
} catch {
|
|
3354
3514
|
}
|
|
3355
3515
|
if (stdinData) {
|
|
@@ -3374,12 +3534,12 @@ function checkCommit(db) {
|
|
|
3374
3534
|
process.exit(1);
|
|
3375
3535
|
}
|
|
3376
3536
|
}
|
|
3377
|
-
var
|
|
3537
|
+
var fs11, path11;
|
|
3378
3538
|
var init_query = __esm({
|
|
3379
3539
|
"src/commands/query.ts"() {
|
|
3380
3540
|
"use strict";
|
|
3381
|
-
|
|
3382
|
-
|
|
3541
|
+
fs11 = __toESM(require("fs"));
|
|
3542
|
+
path11 = __toESM(require("path"));
|
|
3383
3543
|
init_connection();
|
|
3384
3544
|
init_queries();
|
|
3385
3545
|
init_config();
|
|
@@ -3413,6 +3573,28 @@ async function resolve2(db, exp, projectRoot) {
|
|
|
3413
3573
|
grades = getVerificationsByExperiment(db, exp.id);
|
|
3414
3574
|
}
|
|
3415
3575
|
const overallGrade = worstGrade(grades);
|
|
3576
|
+
const config = loadConfig(projectRoot);
|
|
3577
|
+
const metricComparisons = compareMetrics(db, exp.id, config);
|
|
3578
|
+
const gateViolations = checkGateViolations(metricComparisons);
|
|
3579
|
+
if (gateViolations.length > 0 && (overallGrade === "sound" || overallGrade === "good")) {
|
|
3580
|
+
warn("Gate fixture regression detected \u2014 blocking merge:");
|
|
3581
|
+
for (const v of gateViolations) {
|
|
3582
|
+
warn(` ${v.fixture} / ${v.metric}: ${v.before} \u2192 ${v.after} (${v.delta > 0 ? "+" : ""}${v.delta})`);
|
|
3583
|
+
}
|
|
3584
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
3585
|
+
const guidanceText = `Gate fixture regression blocks merge. Fix these regressions before re-attempting:
|
|
3586
|
+
` + gateViolations.map((v) => `- ${v.fixture} / ${v.metric}: was ${v.before}, now ${v.after}`).join("\n");
|
|
3587
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
3588
|
+
db.transaction(() => {
|
|
3589
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
3590
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
3591
|
+
if (exp.sub_type) {
|
|
3592
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
3593
|
+
}
|
|
3594
|
+
})();
|
|
3595
|
+
warn(`Experiment ${exp.slug} CYCLING BACK \u2014 gate fixture(s) regressed.`);
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3416
3598
|
updateExperimentStatus(db, exp.id, "resolved");
|
|
3417
3599
|
switch (overallGrade) {
|
|
3418
3600
|
case "sound": {
|
|
@@ -3500,6 +3682,28 @@ async function resolveDbOnly(db, exp, projectRoot) {
|
|
|
3500
3682
|
grades = getVerificationsByExperiment(db, exp.id);
|
|
3501
3683
|
}
|
|
3502
3684
|
const overallGrade = worstGrade(grades);
|
|
3685
|
+
const config = loadConfig(projectRoot);
|
|
3686
|
+
const metricComparisons = compareMetrics(db, exp.id, config);
|
|
3687
|
+
const gateViolations = checkGateViolations(metricComparisons);
|
|
3688
|
+
if (gateViolations.length > 0 && (overallGrade === "sound" || overallGrade === "good")) {
|
|
3689
|
+
warn("Gate fixture regression detected \u2014 blocking merge:");
|
|
3690
|
+
for (const v of gateViolations) {
|
|
3691
|
+
warn(` ${v.fixture} / ${v.metric}: ${v.before} \u2192 ${v.after} (${v.delta > 0 ? "+" : ""}${v.delta})`);
|
|
3692
|
+
}
|
|
3693
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
3694
|
+
const guidanceText = `Gate fixture regression blocks merge. Fix these regressions before re-attempting:
|
|
3695
|
+
` + gateViolations.map((v) => `- ${v.fixture} / ${v.metric}: was ${v.before}, now ${v.after}`).join("\n");
|
|
3696
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
3697
|
+
db.transaction(() => {
|
|
3698
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
3699
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
3700
|
+
if (exp.sub_type) {
|
|
3701
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
3702
|
+
}
|
|
3703
|
+
})();
|
|
3704
|
+
warn(`Experiment ${exp.slug} CYCLING BACK \u2014 gate fixture(s) regressed.`);
|
|
3705
|
+
return "weak";
|
|
3706
|
+
}
|
|
3503
3707
|
updateExperimentStatus(db, exp.id, "resolved");
|
|
3504
3708
|
switch (overallGrade) {
|
|
3505
3709
|
case "sound":
|
|
@@ -3570,19 +3774,19 @@ async function resolveDbOnly(db, exp, projectRoot) {
|
|
|
3570
3774
|
function gitMerge(branch, cwd) {
|
|
3571
3775
|
try {
|
|
3572
3776
|
try {
|
|
3573
|
-
(0,
|
|
3777
|
+
(0, import_node_child_process6.execFileSync)("git", ["checkout", "main"], {
|
|
3574
3778
|
cwd,
|
|
3575
3779
|
encoding: "utf-8",
|
|
3576
3780
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3577
3781
|
});
|
|
3578
3782
|
} catch {
|
|
3579
|
-
(0,
|
|
3783
|
+
(0, import_node_child_process6.execFileSync)("git", ["checkout", "master"], {
|
|
3580
3784
|
cwd,
|
|
3581
3785
|
encoding: "utf-8",
|
|
3582
3786
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3583
3787
|
});
|
|
3584
3788
|
}
|
|
3585
|
-
(0,
|
|
3789
|
+
(0, import_node_child_process6.execFileSync)("git", ["merge", branch, "--no-ff", "-m", `Merge experiment branch ${branch}`], {
|
|
3586
3790
|
cwd,
|
|
3587
3791
|
encoding: "utf-8",
|
|
3588
3792
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3593,23 +3797,23 @@ function gitMerge(branch, cwd) {
|
|
|
3593
3797
|
}
|
|
3594
3798
|
function gitRevert(branch, cwd) {
|
|
3595
3799
|
try {
|
|
3596
|
-
const currentBranch = (0,
|
|
3800
|
+
const currentBranch = (0, import_node_child_process6.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
3597
3801
|
cwd,
|
|
3598
3802
|
encoding: "utf-8"
|
|
3599
3803
|
}).trim();
|
|
3600
3804
|
if (currentBranch === branch) {
|
|
3601
3805
|
try {
|
|
3602
|
-
(0,
|
|
3806
|
+
(0, import_node_child_process6.execFileSync)("git", ["checkout", "--", "."], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3603
3807
|
} catch {
|
|
3604
3808
|
}
|
|
3605
3809
|
try {
|
|
3606
|
-
(0,
|
|
3810
|
+
(0, import_node_child_process6.execFileSync)("git", ["checkout", "main"], {
|
|
3607
3811
|
cwd,
|
|
3608
3812
|
encoding: "utf-8",
|
|
3609
3813
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3610
3814
|
});
|
|
3611
3815
|
} catch {
|
|
3612
|
-
(0,
|
|
3816
|
+
(0, import_node_child_process6.execFileSync)("git", ["checkout", "master"], {
|
|
3613
3817
|
cwd,
|
|
3614
3818
|
encoding: "utf-8",
|
|
3615
3819
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3621,28 +3825,30 @@ function gitRevert(branch, cwd) {
|
|
|
3621
3825
|
}
|
|
3622
3826
|
}
|
|
3623
3827
|
function appendToFragilityMap(projectRoot, expSlug, gaps) {
|
|
3624
|
-
const fragPath =
|
|
3828
|
+
const fragPath = path12.join(projectRoot, "docs", "synthesis", "fragility.md");
|
|
3625
3829
|
let content = "";
|
|
3626
|
-
if (
|
|
3627
|
-
content =
|
|
3830
|
+
if (fs12.existsSync(fragPath)) {
|
|
3831
|
+
content = fs12.readFileSync(fragPath, "utf-8");
|
|
3628
3832
|
}
|
|
3629
3833
|
const entry = `
|
|
3630
3834
|
## From experiment: ${expSlug}
|
|
3631
3835
|
${gaps}
|
|
3632
3836
|
`;
|
|
3633
|
-
|
|
3837
|
+
fs12.writeFileSync(fragPath, content + entry);
|
|
3634
3838
|
}
|
|
3635
|
-
var
|
|
3839
|
+
var fs12, path12, import_node_child_process6;
|
|
3636
3840
|
var init_resolve = __esm({
|
|
3637
3841
|
"src/resolve.ts"() {
|
|
3638
3842
|
"use strict";
|
|
3639
|
-
|
|
3640
|
-
|
|
3843
|
+
fs12 = __toESM(require("fs"));
|
|
3844
|
+
path12 = __toESM(require("path"));
|
|
3641
3845
|
init_types2();
|
|
3642
3846
|
init_machine();
|
|
3643
3847
|
init_queries();
|
|
3848
|
+
init_metrics();
|
|
3849
|
+
init_config();
|
|
3644
3850
|
init_spawn();
|
|
3645
|
-
|
|
3851
|
+
import_node_child_process6 = require("child_process");
|
|
3646
3852
|
init_git();
|
|
3647
3853
|
init_format();
|
|
3648
3854
|
}
|
|
@@ -3710,8 +3916,8 @@ async function runResolve(db, exp, root) {
|
|
|
3710
3916
|
}
|
|
3711
3917
|
async function doGate(db, exp, root) {
|
|
3712
3918
|
transition(exp.status, "gated" /* GATED */);
|
|
3713
|
-
const synthesis = truncateContext(readFileOrEmpty(
|
|
3714
|
-
const fragility = truncateContext(readFileOrEmpty(
|
|
3919
|
+
const synthesis = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3920
|
+
const fragility = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
3715
3921
|
const structuralDeadEnds = exp.sub_type ? listStructuralDeadEndsBySubType(db, exp.sub_type) : listStructuralDeadEnds(db);
|
|
3716
3922
|
const result = await spawnAgent("gatekeeper", {
|
|
3717
3923
|
experiment: {
|
|
@@ -3742,9 +3948,18 @@ Output your gate_decision as "approve", "reject", or "flag" with reasoning.`
|
|
|
3742
3948
|
const decision = result.structured?.gate_decision ?? "approve";
|
|
3743
3949
|
const reason = result.structured?.reason ?? "";
|
|
3744
3950
|
if (decision === "reject") {
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3951
|
+
insertDeadEnd(
|
|
3952
|
+
db,
|
|
3953
|
+
exp.id,
|
|
3954
|
+
exp.hypothesis ?? exp.slug,
|
|
3955
|
+
reason,
|
|
3956
|
+
`Gate rejected: ${reason}`,
|
|
3957
|
+
exp.sub_type,
|
|
3958
|
+
"procedural"
|
|
3959
|
+
);
|
|
3960
|
+
adminTransitionAndPersist(db, exp.id, "gated", "dead_end" /* DEAD_END */, "revert");
|
|
3961
|
+
warn(`Gate REJECTED for ${exp.slug}: ${reason}. Dead-ended.`);
|
|
3962
|
+
return;
|
|
3748
3963
|
} else {
|
|
3749
3964
|
if (decision === "flag") {
|
|
3750
3965
|
warn(`Gate flagged concerns for ${exp.slug}: ${reason}`);
|
|
@@ -3754,17 +3969,25 @@ Output your gate_decision as "approve", "reject", or "flag" with reasoning.`
|
|
|
3754
3969
|
}
|
|
3755
3970
|
}
|
|
3756
3971
|
async function doBuild(db, exp, root) {
|
|
3972
|
+
if (exp.depends_on) {
|
|
3973
|
+
const dep = getExperimentBySlug(db, exp.depends_on);
|
|
3974
|
+
if (!dep || dep.status !== "merged") {
|
|
3975
|
+
throw new Error(
|
|
3976
|
+
`Experiment "${exp.slug}" depends on "${exp.depends_on}" which is ${dep ? dep.status : "not found"}. Dependency must be merged before building.`
|
|
3977
|
+
);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3757
3980
|
transition(exp.status, "building" /* BUILDING */);
|
|
3758
3981
|
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
3759
3982
|
const builderGuidance = getBuilderGuidance(db, exp.id);
|
|
3760
|
-
const fragility = truncateContext(readFileOrEmpty(
|
|
3761
|
-
const synthesis = truncateContext(readFileOrEmpty(
|
|
3983
|
+
const fragility = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
3984
|
+
const synthesis = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3762
3985
|
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
3763
3986
|
const config = loadConfig(root);
|
|
3764
3987
|
const existingBaseline = getMetricsByExperimentAndPhase(db, exp.id, "before");
|
|
3765
3988
|
if (config.metrics?.command && existingBaseline.length === 0) {
|
|
3766
3989
|
try {
|
|
3767
|
-
const output = (0,
|
|
3990
|
+
const output = (0, import_node_child_process7.execSync)(config.metrics.command, {
|
|
3768
3991
|
cwd: root,
|
|
3769
3992
|
encoding: "utf-8",
|
|
3770
3993
|
timeout: 6e4,
|
|
@@ -3792,6 +4015,11 @@ Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothes
|
|
|
3792
4015
|
}
|
|
3793
4016
|
}
|
|
3794
4017
|
taskPrompt += "\n\nNote: The framework captures metrics automatically. Do NOT claim specific numbers unless quoting framework output.";
|
|
4018
|
+
const supplementaryContext = loadExperimentContext(exp, root);
|
|
4019
|
+
const lineage = exportExperimentLineage(db, exp.sub_type);
|
|
4020
|
+
if (lineage) {
|
|
4021
|
+
taskPrompt += "\n\n" + lineage;
|
|
4022
|
+
}
|
|
3795
4023
|
const result = await spawnAgent("builder", {
|
|
3796
4024
|
experiment: {
|
|
3797
4025
|
id: exp.id,
|
|
@@ -3809,9 +4037,25 @@ Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothes
|
|
|
3809
4037
|
fragility,
|
|
3810
4038
|
synthesis,
|
|
3811
4039
|
confirmedDoubts,
|
|
4040
|
+
supplementaryContext: supplementaryContext || void 0,
|
|
4041
|
+
experimentLineage: lineage || void 0,
|
|
3812
4042
|
taskPrompt
|
|
3813
4043
|
}, root);
|
|
3814
4044
|
ingestStructuredOutput(db, exp.id, result.structured);
|
|
4045
|
+
if (result.structured?.abandon) {
|
|
4046
|
+
insertDeadEnd(
|
|
4047
|
+
db,
|
|
4048
|
+
exp.id,
|
|
4049
|
+
exp.hypothesis ?? exp.slug,
|
|
4050
|
+
result.structured.abandon.reason,
|
|
4051
|
+
result.structured.abandon.structural_constraint,
|
|
4052
|
+
exp.sub_type,
|
|
4053
|
+
"structural"
|
|
4054
|
+
);
|
|
4055
|
+
adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
|
|
4056
|
+
info(`Builder abandoned ${exp.slug}: ${result.structured.abandon.reason}`);
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
3815
4059
|
if (result.truncated && !result.structured) {
|
|
3816
4060
|
warn(`Builder was truncated (hit max turns) without producing structured output.`);
|
|
3817
4061
|
await spawnRecovery("builder", result.output, {
|
|
@@ -3819,9 +4063,28 @@ Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothes
|
|
|
3819
4063
|
}, root);
|
|
3820
4064
|
warn(`Experiment stays at 'building'. Run \`majlis build\` to retry or \`majlis revert\` to abandon.`);
|
|
3821
4065
|
} else {
|
|
4066
|
+
if (config.build?.pre_measure) {
|
|
4067
|
+
try {
|
|
4068
|
+
const [cmd, ...cmdArgs] = config.build.pre_measure.split(/\s+/);
|
|
4069
|
+
(0, import_node_child_process7.execFileSync)(cmd, cmdArgs, {
|
|
4070
|
+
cwd: root,
|
|
4071
|
+
encoding: "utf-8",
|
|
4072
|
+
timeout: 3e4,
|
|
4073
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4074
|
+
});
|
|
4075
|
+
} catch (err) {
|
|
4076
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4077
|
+
const guidance = `Build verification failed after builder completion. Code may be syntactically broken or incomplete.
|
|
4078
|
+
Error: ${errMsg.slice(0, 500)}`;
|
|
4079
|
+
storeBuilderGuidance(db, exp.id, guidance);
|
|
4080
|
+
warn(`Build verification failed for ${exp.slug}. Staying at 'building'.`);
|
|
4081
|
+
warn(`Guidance stored for retry. Run \`majlis build\` to retry.`);
|
|
4082
|
+
return;
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
3822
4085
|
if (config.metrics?.command) {
|
|
3823
4086
|
try {
|
|
3824
|
-
const output = (0,
|
|
4087
|
+
const output = (0, import_node_child_process7.execSync)(config.metrics.command, {
|
|
3825
4088
|
cwd: root,
|
|
3826
4089
|
encoding: "utf-8",
|
|
3827
4090
|
timeout: 6e4,
|
|
@@ -3837,6 +4100,15 @@ Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothes
|
|
|
3837
4100
|
}
|
|
3838
4101
|
}
|
|
3839
4102
|
gitCommitBuild(exp, root);
|
|
4103
|
+
if (result.extractionTier === 3) {
|
|
4104
|
+
warn(`Builder output extracted via Haiku (tier 3). Data provenance degraded.`);
|
|
4105
|
+
const existing = getBuilderGuidance(db, exp.id) ?? "";
|
|
4106
|
+
storeBuilderGuidance(
|
|
4107
|
+
db,
|
|
4108
|
+
exp.id,
|
|
4109
|
+
existing + "\n[PROVENANCE WARNING] Builder structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny."
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
3840
4112
|
updateExperimentStatus(db, exp.id, "built");
|
|
3841
4113
|
success(`Build complete for ${exp.slug}. Run \`majlis doubt\` or \`majlis challenge\` next.`);
|
|
3842
4114
|
}
|
|
@@ -3845,7 +4117,7 @@ async function doChallenge(db, exp, root) {
|
|
|
3845
4117
|
transition(exp.status, "challenged" /* CHALLENGED */);
|
|
3846
4118
|
let gitDiff = "";
|
|
3847
4119
|
try {
|
|
3848
|
-
gitDiff = (0,
|
|
4120
|
+
gitDiff = (0, import_node_child_process7.execSync)('git diff main -- . ":!.majlis/"', {
|
|
3849
4121
|
cwd: root,
|
|
3850
4122
|
encoding: "utf-8",
|
|
3851
4123
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3853,7 +4125,7 @@ async function doChallenge(db, exp, root) {
|
|
|
3853
4125
|
} catch {
|
|
3854
4126
|
}
|
|
3855
4127
|
if (gitDiff.length > 8e3) gitDiff = gitDiff.slice(0, 8e3) + "\n[DIFF TRUNCATED]";
|
|
3856
|
-
const synthesis = truncateContext(readFileOrEmpty(
|
|
4128
|
+
const synthesis = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3857
4129
|
let taskPrompt = `Construct adversarial test cases for experiment ${exp.slug}: ${exp.hypothesis}`;
|
|
3858
4130
|
if (gitDiff) {
|
|
3859
4131
|
taskPrompt += `
|
|
@@ -3886,9 +4158,9 @@ ${gitDiff}
|
|
|
3886
4158
|
async function doDoubt(db, exp, root) {
|
|
3887
4159
|
transition(exp.status, "doubted" /* DOUBTED */);
|
|
3888
4160
|
const paddedNum = String(exp.id).padStart(3, "0");
|
|
3889
|
-
const expDocPath =
|
|
4161
|
+
const expDocPath = path13.join(root, "docs", "experiments", `${paddedNum}-${exp.slug}.md`);
|
|
3890
4162
|
const experimentDoc = truncateContext(readFileOrEmpty(expDocPath), CONTEXT_LIMITS.experimentDoc);
|
|
3891
|
-
const synthesis = truncateContext(readFileOrEmpty(
|
|
4163
|
+
const synthesis = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3892
4164
|
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
3893
4165
|
let taskPrompt = `Doubt the work in experiment ${exp.slug}: ${exp.hypothesis}. Produce a doubt document with evidence for each doubt.`;
|
|
3894
4166
|
if (experimentDoc) {
|
|
@@ -3927,8 +4199,8 @@ ${experimentDoc}
|
|
|
3927
4199
|
}
|
|
3928
4200
|
async function doScout(db, exp, root) {
|
|
3929
4201
|
transition(exp.status, "scouted" /* SCOUTED */);
|
|
3930
|
-
const synthesis = truncateContext(readFileOrEmpty(
|
|
3931
|
-
const fragility = truncateContext(readFileOrEmpty(
|
|
4202
|
+
const synthesis = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
4203
|
+
const fragility = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
3932
4204
|
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
3933
4205
|
const deadEndsSummary = deadEnds.map(
|
|
3934
4206
|
(d) => `- [${d.category ?? "structural"}] ${d.approach}: ${d.why_failed}`
|
|
@@ -3975,31 +4247,49 @@ ${fragility}`;
|
|
|
3975
4247
|
async function doVerify(db, exp, root) {
|
|
3976
4248
|
transition(exp.status, "verifying" /* VERIFYING */);
|
|
3977
4249
|
const doubts = getDoubtsByExperiment(db, exp.id);
|
|
3978
|
-
const challengeDir =
|
|
4250
|
+
const challengeDir = path13.join(root, "docs", "challenges");
|
|
3979
4251
|
let challenges = "";
|
|
3980
|
-
if (
|
|
3981
|
-
const files =
|
|
4252
|
+
if (fs13.existsSync(challengeDir)) {
|
|
4253
|
+
const files = fs13.readdirSync(challengeDir).filter((f) => f.includes(exp.slug) && f.endsWith(".md"));
|
|
3982
4254
|
for (const f of files) {
|
|
3983
|
-
challenges +=
|
|
4255
|
+
challenges += fs13.readFileSync(path13.join(challengeDir, f), "utf-8") + "\n\n";
|
|
3984
4256
|
}
|
|
3985
4257
|
}
|
|
3986
|
-
const
|
|
3987
|
-
const
|
|
4258
|
+
const config = loadConfig(root);
|
|
4259
|
+
const metricComparisons = compareMetrics(db, exp.id, config);
|
|
3988
4260
|
let metricsSection = "";
|
|
3989
|
-
if (
|
|
4261
|
+
if (metricComparisons.length > 0) {
|
|
3990
4262
|
metricsSection = "\n\n## Framework-Captured Metrics (GROUND TRUTH \u2014 not self-reported by builder)\n";
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
4263
|
+
metricsSection += "| Fixture | Metric | Before | After | Delta | Regression | Gate |\n";
|
|
4264
|
+
metricsSection += "|---------|--------|--------|-------|-------|------------|------|\n";
|
|
4265
|
+
for (const c of metricComparisons) {
|
|
4266
|
+
metricsSection += `| ${c.fixture} | ${c.metric} | ${c.before} | ${c.after} | ${c.delta > 0 ? "+" : ""}${c.delta} | ${c.regression ? "YES" : "no"} | ${c.gate ? "GATE" : "-"} |
|
|
4267
|
+
`;
|
|
4268
|
+
}
|
|
4269
|
+
const gateViolations = metricComparisons.filter((c) => c.gate && c.regression);
|
|
4270
|
+
if (gateViolations.length > 0) {
|
|
4271
|
+
metricsSection += `
|
|
4272
|
+
**GATE VIOLATION**: ${gateViolations.length} gate fixture(s) regressed. This MUST be addressed \u2014 gate regressions block merge.
|
|
3995
4273
|
`;
|
|
3996
|
-
}
|
|
3997
4274
|
}
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4275
|
+
} else {
|
|
4276
|
+
const beforeMetrics = getMetricsByExperimentAndPhase(db, exp.id, "before");
|
|
4277
|
+
const afterMetrics = getMetricsByExperimentAndPhase(db, exp.id, "after");
|
|
4278
|
+
if (beforeMetrics.length > 0 || afterMetrics.length > 0) {
|
|
4279
|
+
metricsSection = "\n\n## Framework-Captured Metrics (GROUND TRUTH \u2014 not self-reported by builder)\n";
|
|
4280
|
+
if (beforeMetrics.length > 0) {
|
|
4281
|
+
metricsSection += "### Before Build\n";
|
|
4282
|
+
for (const m of beforeMetrics) {
|
|
4283
|
+
metricsSection += `- ${m.fixture} / ${m.metric_name}: ${m.metric_value}
|
|
4284
|
+
`;
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
if (afterMetrics.length > 0) {
|
|
4288
|
+
metricsSection += "### After Build\n";
|
|
4289
|
+
for (const m of afterMetrics) {
|
|
4290
|
+
metricsSection += `- ${m.fixture} / ${m.metric_name}: ${m.metric_value}
|
|
4002
4291
|
`;
|
|
4292
|
+
}
|
|
4003
4293
|
}
|
|
4004
4294
|
}
|
|
4005
4295
|
}
|
|
@@ -4013,6 +4303,16 @@ async function doVerify(db, exp, root) {
|
|
|
4013
4303
|
doubtReference += "\nWhen resolving doubts, use the DOUBT-{id} number as the doubt_id value in your doubt_resolutions output.";
|
|
4014
4304
|
}
|
|
4015
4305
|
updateExperimentStatus(db, exp.id, "verifying");
|
|
4306
|
+
const verifierSupplementaryContext = loadExperimentContext(exp, root);
|
|
4307
|
+
const verifierLineage = exportExperimentLineage(db, exp.sub_type);
|
|
4308
|
+
let verifierTaskPrompt = `Verify experiment ${exp.slug}: ${exp.hypothesis}. Check provenance and content. Test the ${doubts.length} doubt(s) and any adversarial challenges.` + metricsSection + doubtReference;
|
|
4309
|
+
if (verifierLineage) {
|
|
4310
|
+
verifierTaskPrompt += "\n\n" + verifierLineage;
|
|
4311
|
+
}
|
|
4312
|
+
const builderGuidanceForVerifier = getBuilderGuidance(db, exp.id);
|
|
4313
|
+
if (builderGuidanceForVerifier?.includes("[PROVENANCE WARNING]")) {
|
|
4314
|
+
verifierTaskPrompt += "\n\nNote: The builder's structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny.";
|
|
4315
|
+
}
|
|
4016
4316
|
const result = await spawnAgent("verifier", {
|
|
4017
4317
|
experiment: {
|
|
4018
4318
|
id: exp.id,
|
|
@@ -4024,7 +4324,10 @@ async function doVerify(db, exp, root) {
|
|
|
4024
4324
|
},
|
|
4025
4325
|
doubts,
|
|
4026
4326
|
challenges,
|
|
4027
|
-
|
|
4327
|
+
metricComparisons: metricComparisons.length > 0 ? metricComparisons : void 0,
|
|
4328
|
+
supplementaryContext: verifierSupplementaryContext || void 0,
|
|
4329
|
+
experimentLineage: verifierLineage || void 0,
|
|
4330
|
+
taskPrompt: verifierTaskPrompt
|
|
4028
4331
|
}, root);
|
|
4029
4332
|
ingestStructuredOutput(db, exp.id, result.structured);
|
|
4030
4333
|
if (result.truncated && !result.structured) {
|
|
@@ -4048,22 +4351,22 @@ async function doVerify(db, exp, root) {
|
|
|
4048
4351
|
success(`Verification complete for ${exp.slug}. Run \`majlis resolve\` next.`);
|
|
4049
4352
|
}
|
|
4050
4353
|
async function doCompress(db, root) {
|
|
4051
|
-
const synthesisPath =
|
|
4052
|
-
const sizeBefore =
|
|
4354
|
+
const synthesisPath = path13.join(root, "docs", "synthesis", "current.md");
|
|
4355
|
+
const sizeBefore = fs13.existsSync(synthesisPath) ? fs13.statSync(synthesisPath).size : 0;
|
|
4053
4356
|
const sessionCount = getSessionsSinceCompression(db);
|
|
4054
4357
|
const dbExport = exportForCompressor(db);
|
|
4055
4358
|
const result = await spawnAgent("compressor", {
|
|
4056
4359
|
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."
|
|
4057
4360
|
}, root);
|
|
4058
|
-
const sizeAfter =
|
|
4361
|
+
const sizeAfter = fs13.existsSync(synthesisPath) ? fs13.statSync(synthesisPath).size : 0;
|
|
4059
4362
|
recordCompression(db, sessionCount, sizeBefore, sizeAfter);
|
|
4060
4363
|
autoCommit(root, "compress: update synthesis");
|
|
4061
4364
|
success(`Compression complete. Synthesis: ${sizeBefore}B \u2192 ${sizeAfter}B`);
|
|
4062
4365
|
}
|
|
4063
4366
|
function gitCommitBuild(exp, cwd) {
|
|
4064
4367
|
try {
|
|
4065
|
-
(0,
|
|
4066
|
-
const diff = (0,
|
|
4368
|
+
(0, import_node_child_process7.execSync)('git add -A -- ":!.majlis/"', { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
4369
|
+
const diff = (0, import_node_child_process7.execSync)("git diff --cached --stat", { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
4067
4370
|
if (!diff) {
|
|
4068
4371
|
info("No code changes to commit.");
|
|
4069
4372
|
return;
|
|
@@ -4071,12 +4374,37 @@ function gitCommitBuild(exp, cwd) {
|
|
|
4071
4374
|
const msg = `EXP-${String(exp.id).padStart(3, "0")}: ${exp.slug}
|
|
4072
4375
|
|
|
4073
4376
|
${exp.hypothesis ?? ""}`;
|
|
4074
|
-
(0,
|
|
4377
|
+
(0, import_node_child_process7.execFileSync)("git", ["commit", "-m", msg], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
4075
4378
|
info(`Committed builder changes on ${exp.branch}.`);
|
|
4076
4379
|
} catch {
|
|
4077
4380
|
warn("Could not auto-commit builder changes \u2014 commit manually before resolving.");
|
|
4078
4381
|
}
|
|
4079
4382
|
}
|
|
4383
|
+
function loadExperimentContext(exp, root) {
|
|
4384
|
+
if (!exp.context_files) return "";
|
|
4385
|
+
let files;
|
|
4386
|
+
try {
|
|
4387
|
+
files = JSON.parse(exp.context_files);
|
|
4388
|
+
} catch {
|
|
4389
|
+
return "";
|
|
4390
|
+
}
|
|
4391
|
+
if (!Array.isArray(files) || files.length === 0) return "";
|
|
4392
|
+
const sections = ["## Experiment-Scoped Reference Material"];
|
|
4393
|
+
for (const relPath of files) {
|
|
4394
|
+
const absPath = path13.join(root, relPath);
|
|
4395
|
+
try {
|
|
4396
|
+
const content = fs13.readFileSync(absPath, "utf-8");
|
|
4397
|
+
sections.push(`### ${relPath}
|
|
4398
|
+
\`\`\`
|
|
4399
|
+
${content.slice(0, 8e3)}
|
|
4400
|
+
\`\`\``);
|
|
4401
|
+
} catch {
|
|
4402
|
+
sections.push(`### ${relPath}
|
|
4403
|
+
*(file not found)*`);
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4406
|
+
return sections.join("\n\n");
|
|
4407
|
+
}
|
|
4080
4408
|
function resolveExperimentArg(db, args) {
|
|
4081
4409
|
const slugArg = args.filter((a) => !a.startsWith("--"))[0];
|
|
4082
4410
|
let exp;
|
|
@@ -4149,13 +4477,13 @@ function ingestStructuredOutput(db, experimentId, structured) {
|
|
|
4149
4477
|
}
|
|
4150
4478
|
})();
|
|
4151
4479
|
}
|
|
4152
|
-
var
|
|
4480
|
+
var fs13, path13, import_node_child_process7;
|
|
4153
4481
|
var init_cycle = __esm({
|
|
4154
4482
|
"src/commands/cycle.ts"() {
|
|
4155
4483
|
"use strict";
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4484
|
+
fs13 = __toESM(require("fs"));
|
|
4485
|
+
path13 = __toESM(require("path"));
|
|
4486
|
+
import_node_child_process7 = require("child_process");
|
|
4159
4487
|
init_connection();
|
|
4160
4488
|
init_queries();
|
|
4161
4489
|
init_machine();
|
|
@@ -4182,10 +4510,10 @@ async function classify(args) {
|
|
|
4182
4510
|
if (!domain) {
|
|
4183
4511
|
throw new Error('Usage: majlis classify "domain description"');
|
|
4184
4512
|
}
|
|
4185
|
-
const synthesisPath =
|
|
4186
|
-
const synthesis =
|
|
4187
|
-
const deadEndsPath =
|
|
4188
|
-
const deadEnds =
|
|
4513
|
+
const synthesisPath = path14.join(root, "docs", "synthesis", "current.md");
|
|
4514
|
+
const synthesis = fs14.existsSync(synthesisPath) ? fs14.readFileSync(synthesisPath, "utf-8") : "";
|
|
4515
|
+
const deadEndsPath = path14.join(root, "docs", "synthesis", "dead-ends.md");
|
|
4516
|
+
const deadEnds = fs14.existsSync(deadEndsPath) ? fs14.readFileSync(deadEndsPath, "utf-8") : "";
|
|
4189
4517
|
info(`Classifying problem domain: ${domain}`);
|
|
4190
4518
|
const result = await spawnAgent("builder", {
|
|
4191
4519
|
synthesis,
|
|
@@ -4204,22 +4532,22 @@ Write the classification to docs/classification/ following the template.`
|
|
|
4204
4532
|
async function reframe(args) {
|
|
4205
4533
|
const root = findProjectRoot();
|
|
4206
4534
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
4207
|
-
const classificationDir =
|
|
4535
|
+
const classificationDir = path14.join(root, "docs", "classification");
|
|
4208
4536
|
let classificationContent = "";
|
|
4209
|
-
if (
|
|
4210
|
-
const files =
|
|
4537
|
+
if (fs14.existsSync(classificationDir)) {
|
|
4538
|
+
const files = fs14.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
|
|
4211
4539
|
for (const f of files) {
|
|
4212
|
-
classificationContent +=
|
|
4540
|
+
classificationContent += fs14.readFileSync(path14.join(classificationDir, f), "utf-8") + "\n\n";
|
|
4213
4541
|
}
|
|
4214
4542
|
}
|
|
4215
|
-
const synthesisPath =
|
|
4216
|
-
const synthesis =
|
|
4217
|
-
const deadEndsPath =
|
|
4218
|
-
const deadEnds =
|
|
4219
|
-
const configPath =
|
|
4543
|
+
const synthesisPath = path14.join(root, "docs", "synthesis", "current.md");
|
|
4544
|
+
const synthesis = fs14.existsSync(synthesisPath) ? fs14.readFileSync(synthesisPath, "utf-8") : "";
|
|
4545
|
+
const deadEndsPath = path14.join(root, "docs", "synthesis", "dead-ends.md");
|
|
4546
|
+
const deadEnds = fs14.existsSync(deadEndsPath) ? fs14.readFileSync(deadEndsPath, "utf-8") : "";
|
|
4547
|
+
const configPath = path14.join(root, ".majlis", "config.json");
|
|
4220
4548
|
let problemStatement = "";
|
|
4221
|
-
if (
|
|
4222
|
-
const config = JSON.parse(
|
|
4549
|
+
if (fs14.existsSync(configPath)) {
|
|
4550
|
+
const config = JSON.parse(fs14.readFileSync(configPath, "utf-8"));
|
|
4223
4551
|
problemStatement = `${config.project?.description ?? ""}
|
|
4224
4552
|
Objective: ${config.project?.objective ?? ""}`;
|
|
4225
4553
|
}
|
|
@@ -4244,12 +4572,12 @@ Write to docs/reframes/.`
|
|
|
4244
4572
|
autoCommit(root, `reframe: ${target.slice(0, 60)}`);
|
|
4245
4573
|
success("Reframe complete. Check docs/reframes/ for the output.");
|
|
4246
4574
|
}
|
|
4247
|
-
var
|
|
4575
|
+
var fs14, path14;
|
|
4248
4576
|
var init_classify = __esm({
|
|
4249
4577
|
"src/commands/classify.ts"() {
|
|
4250
4578
|
"use strict";
|
|
4251
|
-
|
|
4252
|
-
|
|
4579
|
+
fs14 = __toESM(require("fs"));
|
|
4580
|
+
path14 = __toESM(require("path"));
|
|
4253
4581
|
init_connection();
|
|
4254
4582
|
init_spawn();
|
|
4255
4583
|
init_git();
|
|
@@ -4271,15 +4599,15 @@ async function audit(args) {
|
|
|
4271
4599
|
const experiments = listAllExperiments(db);
|
|
4272
4600
|
const deadEnds = listAllDeadEnds(db);
|
|
4273
4601
|
const circuitBreakers = getAllCircuitBreakerStates(db, config.cycle.circuit_breaker_threshold);
|
|
4274
|
-
const classificationDir =
|
|
4602
|
+
const classificationDir = path15.join(root, "docs", "classification");
|
|
4275
4603
|
let classification = "";
|
|
4276
|
-
if (
|
|
4277
|
-
const files =
|
|
4604
|
+
if (fs15.existsSync(classificationDir)) {
|
|
4605
|
+
const files = fs15.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
|
|
4278
4606
|
for (const f of files) {
|
|
4279
|
-
classification +=
|
|
4607
|
+
classification += fs15.readFileSync(path15.join(classificationDir, f), "utf-8") + "\n\n";
|
|
4280
4608
|
}
|
|
4281
4609
|
}
|
|
4282
|
-
const synthesis = readFileOrEmpty(
|
|
4610
|
+
const synthesis = readFileOrEmpty(path15.join(root, "docs", "synthesis", "current.md"));
|
|
4283
4611
|
header("Maqasid Check \u2014 Purpose Audit");
|
|
4284
4612
|
const trippedBreakers = circuitBreakers.filter((cb) => cb.tripped);
|
|
4285
4613
|
if (trippedBreakers.length > 0) {
|
|
@@ -4323,12 +4651,12 @@ Output: either "classification confirmed \u2014 continue" or "re-classify from X
|
|
|
4323
4651
|
}, root);
|
|
4324
4652
|
success("Purpose audit complete. Review the output above.");
|
|
4325
4653
|
}
|
|
4326
|
-
var
|
|
4654
|
+
var fs15, path15;
|
|
4327
4655
|
var init_audit = __esm({
|
|
4328
4656
|
"src/commands/audit.ts"() {
|
|
4329
4657
|
"use strict";
|
|
4330
|
-
|
|
4331
|
-
|
|
4658
|
+
fs15 = __toESM(require("fs"));
|
|
4659
|
+
path15 = __toESM(require("path"));
|
|
4332
4660
|
init_connection();
|
|
4333
4661
|
init_queries();
|
|
4334
4662
|
init_spawn();
|
|
@@ -4619,16 +4947,16 @@ async function run(args) {
|
|
|
4619
4947
|
info("Run `majlis status` to see final state.");
|
|
4620
4948
|
}
|
|
4621
4949
|
async function deriveNextHypothesis(goal, root, db) {
|
|
4622
|
-
const synthesis = truncateContext(readFileOrEmpty(
|
|
4623
|
-
const fragility = truncateContext(readFileOrEmpty(
|
|
4624
|
-
const deadEndsDoc = truncateContext(readFileOrEmpty(
|
|
4950
|
+
const synthesis = truncateContext(readFileOrEmpty(path16.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
4951
|
+
const fragility = truncateContext(readFileOrEmpty(path16.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
4952
|
+
const deadEndsDoc = truncateContext(readFileOrEmpty(path16.join(root, "docs", "synthesis", "dead-ends.md")), CONTEXT_LIMITS.deadEnds);
|
|
4625
4953
|
const diagnosis = truncateContext(readLatestDiagnosis(root), CONTEXT_LIMITS.synthesis);
|
|
4626
4954
|
const deadEnds = listAllDeadEnds(db);
|
|
4627
4955
|
const config = loadConfig(root);
|
|
4628
4956
|
let metricsOutput = "";
|
|
4629
4957
|
if (config.metrics?.command) {
|
|
4630
4958
|
try {
|
|
4631
|
-
metricsOutput = (0,
|
|
4959
|
+
metricsOutput = (0, import_node_child_process8.execSync)(config.metrics.command, {
|
|
4632
4960
|
cwd: root,
|
|
4633
4961
|
encoding: "utf-8",
|
|
4634
4962
|
timeout: 6e4,
|
|
@@ -4727,7 +5055,7 @@ async function createNewExperiment(db, root, hypothesis) {
|
|
|
4727
5055
|
const paddedNum = String(num).padStart(3, "0");
|
|
4728
5056
|
const branch = `exp/${paddedNum}-${finalSlug}`;
|
|
4729
5057
|
try {
|
|
4730
|
-
(0,
|
|
5058
|
+
(0, import_node_child_process8.execFileSync)("git", ["checkout", "-b", branch], {
|
|
4731
5059
|
cwd: root,
|
|
4732
5060
|
encoding: "utf-8",
|
|
4733
5061
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4739,24 +5067,24 @@ async function createNewExperiment(db, root, hypothesis) {
|
|
|
4739
5067
|
const exp = createExperiment(db, finalSlug, branch, hypothesis, null, null);
|
|
4740
5068
|
adminTransitionAndPersist(db, exp.id, exp.status, "reframed" /* REFRAMED */, "bootstrap");
|
|
4741
5069
|
exp.status = "reframed";
|
|
4742
|
-
const docsDir =
|
|
4743
|
-
const templatePath =
|
|
4744
|
-
if (
|
|
4745
|
-
const template =
|
|
5070
|
+
const docsDir = path16.join(root, "docs", "experiments");
|
|
5071
|
+
const templatePath = path16.join(docsDir, "_TEMPLATE.md");
|
|
5072
|
+
if (fs16.existsSync(templatePath)) {
|
|
5073
|
+
const template = fs16.readFileSync(templatePath, "utf-8");
|
|
4746
5074
|
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]);
|
|
4747
|
-
const logPath =
|
|
4748
|
-
|
|
5075
|
+
const logPath = path16.join(docsDir, `${paddedNum}-${finalSlug}.md`);
|
|
5076
|
+
fs16.writeFileSync(logPath, logContent);
|
|
4749
5077
|
info(`Created experiment log: docs/experiments/${paddedNum}-${finalSlug}.md`);
|
|
4750
5078
|
}
|
|
4751
5079
|
return exp;
|
|
4752
5080
|
}
|
|
4753
|
-
var
|
|
5081
|
+
var fs16, path16, import_node_child_process8;
|
|
4754
5082
|
var init_run = __esm({
|
|
4755
5083
|
"src/commands/run.ts"() {
|
|
4756
5084
|
"use strict";
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
5085
|
+
fs16 = __toESM(require("fs"));
|
|
5086
|
+
path16 = __toESM(require("path"));
|
|
5087
|
+
import_node_child_process8 = require("child_process");
|
|
4760
5088
|
init_connection();
|
|
4761
5089
|
init_queries();
|
|
4762
5090
|
init_machine();
|
|
@@ -4773,11 +5101,11 @@ var init_run = __esm({
|
|
|
4773
5101
|
|
|
4774
5102
|
// src/swarm/worktree.ts
|
|
4775
5103
|
function createWorktree(mainRoot, slug, paddedNum) {
|
|
4776
|
-
const projectName =
|
|
5104
|
+
const projectName = path17.basename(mainRoot);
|
|
4777
5105
|
const worktreeName = `${projectName}-swarm-${paddedNum}-${slug}`;
|
|
4778
|
-
const worktreePath =
|
|
5106
|
+
const worktreePath = path17.join(path17.dirname(mainRoot), worktreeName);
|
|
4779
5107
|
const branch = `swarm/${paddedNum}-${slug}`;
|
|
4780
|
-
(0,
|
|
5108
|
+
(0, import_node_child_process9.execFileSync)("git", ["worktree", "add", worktreePath, "-b", branch], {
|
|
4781
5109
|
cwd: mainRoot,
|
|
4782
5110
|
encoding: "utf-8",
|
|
4783
5111
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4792,43 +5120,43 @@ function createWorktree(mainRoot, slug, paddedNum) {
|
|
|
4792
5120
|
};
|
|
4793
5121
|
}
|
|
4794
5122
|
function initializeWorktree(mainRoot, worktreePath) {
|
|
4795
|
-
const majlisDir =
|
|
4796
|
-
|
|
4797
|
-
const configSrc =
|
|
4798
|
-
if (
|
|
4799
|
-
|
|
4800
|
-
}
|
|
4801
|
-
const agentsSrc =
|
|
4802
|
-
if (
|
|
4803
|
-
const agentsDst =
|
|
4804
|
-
|
|
4805
|
-
for (const file of
|
|
4806
|
-
|
|
4807
|
-
}
|
|
4808
|
-
}
|
|
4809
|
-
const synthSrc =
|
|
4810
|
-
if (
|
|
4811
|
-
const synthDst =
|
|
4812
|
-
|
|
4813
|
-
for (const file of
|
|
4814
|
-
const srcFile =
|
|
4815
|
-
if (
|
|
4816
|
-
|
|
5123
|
+
const majlisDir = path17.join(worktreePath, ".majlis");
|
|
5124
|
+
fs17.mkdirSync(majlisDir, { recursive: true });
|
|
5125
|
+
const configSrc = path17.join(mainRoot, ".majlis", "config.json");
|
|
5126
|
+
if (fs17.existsSync(configSrc)) {
|
|
5127
|
+
fs17.copyFileSync(configSrc, path17.join(majlisDir, "config.json"));
|
|
5128
|
+
}
|
|
5129
|
+
const agentsSrc = path17.join(mainRoot, ".majlis", "agents");
|
|
5130
|
+
if (fs17.existsSync(agentsSrc)) {
|
|
5131
|
+
const agentsDst = path17.join(majlisDir, "agents");
|
|
5132
|
+
fs17.mkdirSync(agentsDst, { recursive: true });
|
|
5133
|
+
for (const file of fs17.readdirSync(agentsSrc)) {
|
|
5134
|
+
fs17.copyFileSync(path17.join(agentsSrc, file), path17.join(agentsDst, file));
|
|
5135
|
+
}
|
|
5136
|
+
}
|
|
5137
|
+
const synthSrc = path17.join(mainRoot, "docs", "synthesis");
|
|
5138
|
+
if (fs17.existsSync(synthSrc)) {
|
|
5139
|
+
const synthDst = path17.join(worktreePath, "docs", "synthesis");
|
|
5140
|
+
fs17.mkdirSync(synthDst, { recursive: true });
|
|
5141
|
+
for (const file of fs17.readdirSync(synthSrc)) {
|
|
5142
|
+
const srcFile = path17.join(synthSrc, file);
|
|
5143
|
+
if (fs17.statSync(srcFile).isFile()) {
|
|
5144
|
+
fs17.copyFileSync(srcFile, path17.join(synthDst, file));
|
|
4817
5145
|
}
|
|
4818
5146
|
}
|
|
4819
5147
|
}
|
|
4820
|
-
const templateSrc =
|
|
4821
|
-
if (
|
|
4822
|
-
const expDir =
|
|
4823
|
-
|
|
4824
|
-
|
|
5148
|
+
const templateSrc = path17.join(mainRoot, "docs", "experiments", "_TEMPLATE.md");
|
|
5149
|
+
if (fs17.existsSync(templateSrc)) {
|
|
5150
|
+
const expDir = path17.join(worktreePath, "docs", "experiments");
|
|
5151
|
+
fs17.mkdirSync(expDir, { recursive: true });
|
|
5152
|
+
fs17.copyFileSync(templateSrc, path17.join(expDir, "_TEMPLATE.md"));
|
|
4825
5153
|
}
|
|
4826
5154
|
const db = openDbAt(worktreePath);
|
|
4827
5155
|
db.close();
|
|
4828
5156
|
}
|
|
4829
5157
|
function cleanupWorktree(mainRoot, wt) {
|
|
4830
5158
|
try {
|
|
4831
|
-
(0,
|
|
5159
|
+
(0, import_node_child_process9.execFileSync)("git", ["worktree", "remove", wt.path, "--force"], {
|
|
4832
5160
|
cwd: mainRoot,
|
|
4833
5161
|
encoding: "utf-8",
|
|
4834
5162
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4837,7 +5165,7 @@ function cleanupWorktree(mainRoot, wt) {
|
|
|
4837
5165
|
warn(`Could not remove worktree ${wt.path} \u2014 remove manually.`);
|
|
4838
5166
|
}
|
|
4839
5167
|
try {
|
|
4840
|
-
(0,
|
|
5168
|
+
(0, import_node_child_process9.execFileSync)("git", ["branch", "-D", wt.branch], {
|
|
4841
5169
|
cwd: mainRoot,
|
|
4842
5170
|
encoding: "utf-8",
|
|
4843
5171
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4845,7 +5173,7 @@ function cleanupWorktree(mainRoot, wt) {
|
|
|
4845
5173
|
} catch {
|
|
4846
5174
|
}
|
|
4847
5175
|
try {
|
|
4848
|
-
(0,
|
|
5176
|
+
(0, import_node_child_process9.execSync)("git worktree prune", {
|
|
4849
5177
|
cwd: mainRoot,
|
|
4850
5178
|
encoding: "utf-8",
|
|
4851
5179
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4853,13 +5181,13 @@ function cleanupWorktree(mainRoot, wt) {
|
|
|
4853
5181
|
} catch {
|
|
4854
5182
|
}
|
|
4855
5183
|
}
|
|
4856
|
-
var
|
|
5184
|
+
var fs17, path17, import_node_child_process9;
|
|
4857
5185
|
var init_worktree = __esm({
|
|
4858
5186
|
"src/swarm/worktree.ts"() {
|
|
4859
5187
|
"use strict";
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
5188
|
+
fs17 = __toESM(require("fs"));
|
|
5189
|
+
path17 = __toESM(require("path"));
|
|
5190
|
+
import_node_child_process9 = require("child_process");
|
|
4863
5191
|
init_connection();
|
|
4864
5192
|
init_format();
|
|
4865
5193
|
}
|
|
@@ -4877,12 +5205,12 @@ async function runExperimentInWorktree(wt) {
|
|
|
4877
5205
|
exp = createExperiment(db, wt.slug, wt.branch, wt.hypothesis, null, null);
|
|
4878
5206
|
adminTransitionAndPersist(db, exp.id, exp.status, "reframed" /* REFRAMED */, "bootstrap");
|
|
4879
5207
|
exp.status = "reframed";
|
|
4880
|
-
const templatePath =
|
|
4881
|
-
if (
|
|
4882
|
-
const template =
|
|
5208
|
+
const templatePath = path18.join(wt.path, "docs", "experiments", "_TEMPLATE.md");
|
|
5209
|
+
if (fs18.existsSync(templatePath)) {
|
|
5210
|
+
const template = fs18.readFileSync(templatePath, "utf-8");
|
|
4883
5211
|
const logContent = template.replace(/\{\{title\}\}/g, wt.hypothesis).replace(/\{\{hypothesis\}\}/g, wt.hypothesis).replace(/\{\{branch\}\}/g, wt.branch).replace(/\{\{status\}\}/g, "classified").replace(/\{\{sub_type\}\}/g, "unclassified").replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]);
|
|
4884
|
-
const logPath =
|
|
4885
|
-
|
|
5212
|
+
const logPath = path18.join(wt.path, "docs", "experiments", `${wt.paddedNum}-${wt.slug}.md`);
|
|
5213
|
+
fs18.writeFileSync(logPath, logContent);
|
|
4886
5214
|
}
|
|
4887
5215
|
info(`${label} Starting: ${wt.hypothesis}`);
|
|
4888
5216
|
while (stepCount < MAX_STEPS) {
|
|
@@ -5005,12 +5333,12 @@ function statusToStepName(status2) {
|
|
|
5005
5333
|
return null;
|
|
5006
5334
|
}
|
|
5007
5335
|
}
|
|
5008
|
-
var
|
|
5336
|
+
var fs18, path18, MAX_STEPS;
|
|
5009
5337
|
var init_runner = __esm({
|
|
5010
5338
|
"src/swarm/runner.ts"() {
|
|
5011
5339
|
"use strict";
|
|
5012
|
-
|
|
5013
|
-
|
|
5340
|
+
fs18 = __toESM(require("fs"));
|
|
5341
|
+
path18 = __toESM(require("path"));
|
|
5014
5342
|
init_connection();
|
|
5015
5343
|
init_queries();
|
|
5016
5344
|
init_machine();
|
|
@@ -5164,7 +5492,7 @@ async function swarm(args) {
|
|
|
5164
5492
|
MAX_PARALLEL
|
|
5165
5493
|
);
|
|
5166
5494
|
try {
|
|
5167
|
-
const status2 = (0,
|
|
5495
|
+
const status2 = (0, import_node_child_process10.execSync)("git status --porcelain", {
|
|
5168
5496
|
cwd: root,
|
|
5169
5497
|
encoding: "utf-8",
|
|
5170
5498
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -5192,20 +5520,20 @@ async function swarm(args) {
|
|
|
5192
5520
|
info(` ${i + 1}. ${hypotheses[i]}`);
|
|
5193
5521
|
}
|
|
5194
5522
|
try {
|
|
5195
|
-
const worktreeList = (0,
|
|
5523
|
+
const worktreeList = (0, import_node_child_process10.execFileSync)("git", ["worktree", "list", "--porcelain"], {
|
|
5196
5524
|
cwd: root,
|
|
5197
5525
|
encoding: "utf-8"
|
|
5198
5526
|
});
|
|
5199
5527
|
const orphaned = worktreeList.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.replace("worktree ", "")).filter((p) => p.includes("-swarm-"));
|
|
5200
5528
|
for (const orphanPath of orphaned) {
|
|
5201
5529
|
try {
|
|
5202
|
-
(0,
|
|
5203
|
-
info(`Cleaned up orphaned worktree: ${
|
|
5530
|
+
(0, import_node_child_process10.execFileSync)("git", ["worktree", "remove", orphanPath, "--force"], { cwd: root, encoding: "utf-8" });
|
|
5531
|
+
info(`Cleaned up orphaned worktree: ${path19.basename(orphanPath)}`);
|
|
5204
5532
|
} catch {
|
|
5205
5533
|
}
|
|
5206
5534
|
}
|
|
5207
5535
|
if (orphaned.length > 0) {
|
|
5208
|
-
(0,
|
|
5536
|
+
(0, import_node_child_process10.execFileSync)("git", ["worktree", "prune"], { cwd: root, encoding: "utf-8" });
|
|
5209
5537
|
}
|
|
5210
5538
|
} catch {
|
|
5211
5539
|
}
|
|
@@ -5268,16 +5596,80 @@ async function swarm(args) {
|
|
|
5268
5596
|
if (summary.bestExperiment && isMergeable(summary.bestExperiment.overallGrade)) {
|
|
5269
5597
|
const best = summary.bestExperiment;
|
|
5270
5598
|
info(`Best experiment: ${best.worktree.slug} (${best.overallGrade})`);
|
|
5599
|
+
let merged = false;
|
|
5271
5600
|
try {
|
|
5272
|
-
(0,
|
|
5601
|
+
(0, import_node_child_process10.execFileSync)(
|
|
5273
5602
|
"git",
|
|
5274
5603
|
["merge", best.worktree.branch, "--no-ff", "-m", `Merge swarm winner: ${best.worktree.slug}`],
|
|
5275
5604
|
{ cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5276
5605
|
);
|
|
5277
5606
|
success(`Merged ${best.worktree.slug} into main.`);
|
|
5607
|
+
merged = true;
|
|
5278
5608
|
} catch {
|
|
5279
|
-
warn(`Git merge of ${best.worktree.slug} failed.
|
|
5280
|
-
|
|
5609
|
+
warn(`Git merge of ${best.worktree.slug} failed (conflict). Attempting rebase...`);
|
|
5610
|
+
try {
|
|
5611
|
+
(0, import_node_child_process10.execFileSync)("git", ["merge", "--abort"], { cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
5612
|
+
} catch {
|
|
5613
|
+
}
|
|
5614
|
+
try {
|
|
5615
|
+
(0, import_node_child_process10.execFileSync)(
|
|
5616
|
+
"git",
|
|
5617
|
+
["rebase", "main", best.worktree.branch],
|
|
5618
|
+
{ cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5619
|
+
);
|
|
5620
|
+
info(`Rebase of ${best.worktree.slug} onto main succeeded. Re-verifying gates...`);
|
|
5621
|
+
const config = loadConfig(root);
|
|
5622
|
+
let gatesHold = true;
|
|
5623
|
+
if (config.metrics?.command && best.experiment) {
|
|
5624
|
+
try {
|
|
5625
|
+
const output = (0, import_node_child_process10.execSync)(config.metrics.command, {
|
|
5626
|
+
cwd: root,
|
|
5627
|
+
encoding: "utf-8",
|
|
5628
|
+
timeout: 6e4,
|
|
5629
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5630
|
+
}).trim();
|
|
5631
|
+
const parsed = parseMetricsOutput(output);
|
|
5632
|
+
for (const m of parsed) {
|
|
5633
|
+
insertMetric(db, best.experiment.id, "after", m.fixture, m.metric_name, m.metric_value);
|
|
5634
|
+
}
|
|
5635
|
+
const comparisons = compareMetrics(db, best.experiment.id, config);
|
|
5636
|
+
const gateViolations = checkGateViolations(comparisons);
|
|
5637
|
+
if (gateViolations.length > 0) {
|
|
5638
|
+
gatesHold = false;
|
|
5639
|
+
warn(`Gate violations after rebase:`);
|
|
5640
|
+
for (const v of gateViolations) {
|
|
5641
|
+
warn(` - ${v.fixture}/${v.metric}: ${v.before} \u2192 ${v.after} (delta: ${v.delta})`);
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
} catch {
|
|
5645
|
+
warn("Could not re-capture metrics after rebase. Proceeding cautiously.");
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
if (gatesHold) {
|
|
5649
|
+
(0, import_node_child_process10.execFileSync)("git", ["checkout", "main"], { cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
5650
|
+
(0, import_node_child_process10.execFileSync)(
|
|
5651
|
+
"git",
|
|
5652
|
+
["merge", "--ff-only", best.worktree.branch],
|
|
5653
|
+
{ cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5654
|
+
);
|
|
5655
|
+
success(`Merged ${best.worktree.slug} into main (via rebase + ff).`);
|
|
5656
|
+
merged = true;
|
|
5657
|
+
} else {
|
|
5658
|
+
warn(`Gate violations after rebase. NOT merging ${best.worktree.slug}.`);
|
|
5659
|
+
info(`Manual resolution needed:`);
|
|
5660
|
+
info(` git checkout main && git merge ${best.worktree.branch} --no-ff`);
|
|
5661
|
+
}
|
|
5662
|
+
} catch {
|
|
5663
|
+
try {
|
|
5664
|
+
(0, import_node_child_process10.execFileSync)("git", ["rebase", "--abort"], { cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
5665
|
+
} catch {
|
|
5666
|
+
}
|
|
5667
|
+
warn(`Rebase of ${best.worktree.slug} also failed. Manual merge required:`);
|
|
5668
|
+
info(` git merge ${best.worktree.branch} --no-ff`);
|
|
5669
|
+
}
|
|
5670
|
+
}
|
|
5671
|
+
if (!merged) {
|
|
5672
|
+
info(`${best.worktree.slug} was NOT merged automatically.`);
|
|
5281
5673
|
}
|
|
5282
5674
|
} else {
|
|
5283
5675
|
info("No experiment achieved sound/good grade. Nothing merged.");
|
|
@@ -5320,15 +5712,15 @@ function isMergeable(grade) {
|
|
|
5320
5712
|
}
|
|
5321
5713
|
async function deriveMultipleHypotheses(goal, root, count) {
|
|
5322
5714
|
const synthesis = truncateContext(
|
|
5323
|
-
readFileOrEmpty(
|
|
5715
|
+
readFileOrEmpty(path19.join(root, "docs", "synthesis", "current.md")),
|
|
5324
5716
|
CONTEXT_LIMITS.synthesis
|
|
5325
5717
|
);
|
|
5326
5718
|
const fragility = truncateContext(
|
|
5327
|
-
readFileOrEmpty(
|
|
5719
|
+
readFileOrEmpty(path19.join(root, "docs", "synthesis", "fragility.md")),
|
|
5328
5720
|
CONTEXT_LIMITS.fragility
|
|
5329
5721
|
);
|
|
5330
5722
|
const deadEndsDoc = truncateContext(
|
|
5331
|
-
readFileOrEmpty(
|
|
5723
|
+
readFileOrEmpty(path19.join(root, "docs", "synthesis", "dead-ends.md")),
|
|
5332
5724
|
CONTEXT_LIMITS.deadEnds
|
|
5333
5725
|
);
|
|
5334
5726
|
const diagnosis = truncateContext(readLatestDiagnosis(root), CONTEXT_LIMITS.synthesis);
|
|
@@ -5338,7 +5730,7 @@ async function deriveMultipleHypotheses(goal, root, count) {
|
|
|
5338
5730
|
let metricsOutput = "";
|
|
5339
5731
|
if (config.metrics?.command) {
|
|
5340
5732
|
try {
|
|
5341
|
-
metricsOutput = (0,
|
|
5733
|
+
metricsOutput = (0, import_node_child_process10.execSync)(config.metrics.command, {
|
|
5342
5734
|
cwd: root,
|
|
5343
5735
|
encoding: "utf-8",
|
|
5344
5736
|
timeout: 6e4,
|
|
@@ -5418,18 +5810,19 @@ If the goal is met:
|
|
|
5418
5810
|
warn("Planner did not return structured hypotheses. Using goal as single hypothesis.");
|
|
5419
5811
|
return [goal];
|
|
5420
5812
|
}
|
|
5421
|
-
var
|
|
5813
|
+
var path19, import_node_child_process10, MAX_PARALLEL, DEFAULT_PARALLEL;
|
|
5422
5814
|
var init_swarm = __esm({
|
|
5423
5815
|
"src/commands/swarm.ts"() {
|
|
5424
5816
|
"use strict";
|
|
5425
|
-
|
|
5426
|
-
|
|
5817
|
+
path19 = __toESM(require("path"));
|
|
5818
|
+
import_node_child_process10 = require("child_process");
|
|
5427
5819
|
init_connection();
|
|
5428
5820
|
init_queries();
|
|
5429
5821
|
init_machine();
|
|
5430
5822
|
init_types2();
|
|
5431
5823
|
init_spawn();
|
|
5432
5824
|
init_config();
|
|
5825
|
+
init_metrics();
|
|
5433
5826
|
init_worktree();
|
|
5434
5827
|
init_runner();
|
|
5435
5828
|
init_aggregate();
|
|
@@ -5450,21 +5843,21 @@ async function diagnose(args) {
|
|
|
5450
5843
|
const db = getDb(root);
|
|
5451
5844
|
const focus = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
5452
5845
|
const keepScripts = args.includes("--keep-scripts");
|
|
5453
|
-
const scriptsDir =
|
|
5454
|
-
if (!
|
|
5455
|
-
|
|
5846
|
+
const scriptsDir = path20.join(root, ".majlis", "scripts");
|
|
5847
|
+
if (!fs19.existsSync(scriptsDir)) {
|
|
5848
|
+
fs19.mkdirSync(scriptsDir, { recursive: true });
|
|
5456
5849
|
}
|
|
5457
5850
|
header("Deep Diagnosis");
|
|
5458
5851
|
if (focus) info(`Focus: ${focus}`);
|
|
5459
5852
|
const dbExport = exportForDiagnostician(db);
|
|
5460
|
-
const synthesis = readFileOrEmpty(
|
|
5461
|
-
const fragility = readFileOrEmpty(
|
|
5462
|
-
const deadEndsDoc = readFileOrEmpty(
|
|
5853
|
+
const synthesis = readFileOrEmpty(path20.join(root, "docs", "synthesis", "current.md"));
|
|
5854
|
+
const fragility = readFileOrEmpty(path20.join(root, "docs", "synthesis", "fragility.md"));
|
|
5855
|
+
const deadEndsDoc = readFileOrEmpty(path20.join(root, "docs", "synthesis", "dead-ends.md"));
|
|
5463
5856
|
const config = loadConfig(root);
|
|
5464
5857
|
let metricsOutput = "";
|
|
5465
5858
|
if (config.metrics?.command) {
|
|
5466
5859
|
try {
|
|
5467
|
-
metricsOutput = (0,
|
|
5860
|
+
metricsOutput = (0, import_node_child_process11.execSync)(config.metrics.command, {
|
|
5468
5861
|
cwd: root,
|
|
5469
5862
|
encoding: "utf-8",
|
|
5470
5863
|
timeout: 6e4,
|
|
@@ -5510,13 +5903,13 @@ Perform a deep diagnostic analysis of this project. Identify root causes, recurr
|
|
|
5510
5903
|
Remember: you may write files ONLY to .majlis/scripts/. You cannot modify project code.`;
|
|
5511
5904
|
info("Spawning diagnostician (60 turns, full DB access)...");
|
|
5512
5905
|
const result = await spawnAgent("diagnostician", { taskPrompt }, root);
|
|
5513
|
-
const diagnosisDir =
|
|
5514
|
-
if (!
|
|
5515
|
-
|
|
5906
|
+
const diagnosisDir = path20.join(root, "docs", "diagnosis");
|
|
5907
|
+
if (!fs19.existsSync(diagnosisDir)) {
|
|
5908
|
+
fs19.mkdirSync(diagnosisDir, { recursive: true });
|
|
5516
5909
|
}
|
|
5517
5910
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
5518
|
-
const artifactPath =
|
|
5519
|
-
|
|
5911
|
+
const artifactPath = path20.join(diagnosisDir, `diagnosis-${timestamp}.md`);
|
|
5912
|
+
fs19.writeFileSync(artifactPath, result.output);
|
|
5520
5913
|
info(`Diagnostic report: docs/diagnosis/diagnosis-${timestamp}.md`);
|
|
5521
5914
|
if (result.structured?.diagnosis) {
|
|
5522
5915
|
const d = result.structured.diagnosis;
|
|
@@ -5529,11 +5922,11 @@ Remember: you may write files ONLY to .majlis/scripts/. You cannot modify projec
|
|
|
5529
5922
|
}
|
|
5530
5923
|
if (!keepScripts) {
|
|
5531
5924
|
try {
|
|
5532
|
-
const files =
|
|
5925
|
+
const files = fs19.readdirSync(scriptsDir);
|
|
5533
5926
|
for (const f of files) {
|
|
5534
|
-
|
|
5927
|
+
fs19.unlinkSync(path20.join(scriptsDir, f));
|
|
5535
5928
|
}
|
|
5536
|
-
|
|
5929
|
+
fs19.rmdirSync(scriptsDir);
|
|
5537
5930
|
info("Cleaned up .majlis/scripts/");
|
|
5538
5931
|
} catch {
|
|
5539
5932
|
}
|
|
@@ -5546,13 +5939,13 @@ Remember: you may write files ONLY to .majlis/scripts/. You cannot modify projec
|
|
|
5546
5939
|
autoCommit(root, `diagnosis: ${focus || "general"}`);
|
|
5547
5940
|
success("Diagnosis complete.");
|
|
5548
5941
|
}
|
|
5549
|
-
var
|
|
5942
|
+
var fs19, path20, import_node_child_process11;
|
|
5550
5943
|
var init_diagnose = __esm({
|
|
5551
5944
|
"src/commands/diagnose.ts"() {
|
|
5552
5945
|
"use strict";
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5946
|
+
fs19 = __toESM(require("fs"));
|
|
5947
|
+
path20 = __toESM(require("path"));
|
|
5948
|
+
import_node_child_process11 = require("child_process");
|
|
5556
5949
|
init_connection();
|
|
5557
5950
|
init_queries();
|
|
5558
5951
|
init_spawn();
|
|
@@ -5591,7 +5984,7 @@ function getLastActivityTimestamp(db) {
|
|
|
5591
5984
|
}
|
|
5592
5985
|
function getCommitsSince(root, timestamp) {
|
|
5593
5986
|
try {
|
|
5594
|
-
const output = (0,
|
|
5987
|
+
const output = (0, import_node_child_process12.execFileSync)(
|
|
5595
5988
|
"git",
|
|
5596
5989
|
["log", `--since=${timestamp}`, "--oneline", "--", ".", ":!.majlis/", ":!docs/"],
|
|
5597
5990
|
{ cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -5604,13 +5997,13 @@ function getCommitsSince(root, timestamp) {
|
|
|
5604
5997
|
}
|
|
5605
5998
|
function getGitDiffStat(root, timestamp) {
|
|
5606
5999
|
try {
|
|
5607
|
-
const baseRef = (0,
|
|
6000
|
+
const baseRef = (0, import_node_child_process12.execFileSync)(
|
|
5608
6001
|
"git",
|
|
5609
6002
|
["rev-list", "-1", `--before=${timestamp}`, "HEAD"],
|
|
5610
6003
|
{ cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5611
6004
|
).trim();
|
|
5612
6005
|
if (!baseRef) return { stat: "", filesChanged: 0 };
|
|
5613
|
-
const stat = (0,
|
|
6006
|
+
const stat = (0, import_node_child_process12.execFileSync)(
|
|
5614
6007
|
"git",
|
|
5615
6008
|
["diff", "--stat", baseRef, "--", ".", ":!.majlis/", ":!docs/"],
|
|
5616
6009
|
{ cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -5647,11 +6040,11 @@ function checkMetrics(root, config) {
|
|
|
5647
6040
|
const command = config.metrics?.command;
|
|
5648
6041
|
if (!command) return { working: false, error: "No metrics command configured" };
|
|
5649
6042
|
try {
|
|
5650
|
-
const scriptPath =
|
|
5651
|
-
if (command.includes("/") && !
|
|
6043
|
+
const scriptPath = path21.join(root, command);
|
|
6044
|
+
if (command.includes("/") && !fs20.existsSync(scriptPath)) {
|
|
5652
6045
|
return { working: false, error: `Script not found: ${command}` };
|
|
5653
6046
|
}
|
|
5654
|
-
const output = (0,
|
|
6047
|
+
const output = (0, import_node_child_process12.execSync)(command, {
|
|
5655
6048
|
cwd: root,
|
|
5656
6049
|
encoding: "utf-8",
|
|
5657
6050
|
timeout: 6e4,
|
|
@@ -5679,10 +6072,10 @@ function assessStaleness(db, root, profile, config) {
|
|
|
5679
6072
|
filesChanged = diffResult.filesChanged;
|
|
5680
6073
|
}
|
|
5681
6074
|
const configDrift = detectConfigDrift(config, profile);
|
|
5682
|
-
const synthesisPath =
|
|
6075
|
+
const synthesisPath = path21.join(root, "docs", "synthesis", "current.md");
|
|
5683
6076
|
let synthesisSize = 0;
|
|
5684
6077
|
try {
|
|
5685
|
-
synthesisSize =
|
|
6078
|
+
synthesisSize = fs20.statSync(synthesisPath).size;
|
|
5686
6079
|
} catch {
|
|
5687
6080
|
}
|
|
5688
6081
|
const unresolvedDoubts = db.prepare(`
|
|
@@ -5740,13 +6133,13 @@ function printStalenessReport(report) {
|
|
|
5740
6133
|
success(" Already up to date.");
|
|
5741
6134
|
}
|
|
5742
6135
|
}
|
|
5743
|
-
var
|
|
6136
|
+
var fs20, path21, import_node_child_process12;
|
|
5744
6137
|
var init_staleness = __esm({
|
|
5745
6138
|
"src/scan/staleness.ts"() {
|
|
5746
6139
|
"use strict";
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
6140
|
+
fs20 = __toESM(require("fs"));
|
|
6141
|
+
path21 = __toESM(require("path"));
|
|
6142
|
+
import_node_child_process12 = require("child_process");
|
|
5750
6143
|
init_queries();
|
|
5751
6144
|
init_format();
|
|
5752
6145
|
}
|
|
@@ -5796,13 +6189,13 @@ async function resync(args) {
|
|
|
5796
6189
|
return;
|
|
5797
6190
|
}
|
|
5798
6191
|
info("Phase 1: Deep re-scan...");
|
|
5799
|
-
const synthesisDir =
|
|
5800
|
-
const scriptsDir =
|
|
5801
|
-
if (!
|
|
5802
|
-
if (!
|
|
6192
|
+
const synthesisDir = path22.join(root, "docs", "synthesis");
|
|
6193
|
+
const scriptsDir = path22.join(root, ".majlis", "scripts");
|
|
6194
|
+
if (!fs21.existsSync(synthesisDir)) fs21.mkdirSync(synthesisDir, { recursive: true });
|
|
6195
|
+
if (!fs21.existsSync(scriptsDir)) fs21.mkdirSync(scriptsDir, { recursive: true });
|
|
5803
6196
|
const profileJson = JSON.stringify(profile, null, 2);
|
|
5804
|
-
const oldSynthesis = readFileOrEmpty(
|
|
5805
|
-
const oldFragility = readFileOrEmpty(
|
|
6197
|
+
const oldSynthesis = readFileOrEmpty(path22.join(root, "docs", "synthesis", "current.md"));
|
|
6198
|
+
const oldFragility = readFileOrEmpty(path22.join(root, "docs", "synthesis", "fragility.md"));
|
|
5806
6199
|
const dbExport = exportForCompressor(db);
|
|
5807
6200
|
const stalenessSummary = `Last Majlis activity: ${report.daysSinceActivity} days ago (${report.lastActivitySource}).
|
|
5808
6201
|
Commits since: ${report.commitsSinceActivity}. Files changed: ${report.filesChanged}.
|
|
@@ -5928,10 +6321,10 @@ You may ONLY write to .majlis/scripts/. Output your structured JSON when done.`;
|
|
|
5928
6321
|
info("Updated .majlis/config.json with resync results.");
|
|
5929
6322
|
if (toolsmithOutput.metrics_command) {
|
|
5930
6323
|
try {
|
|
5931
|
-
const metricsPath =
|
|
5932
|
-
if (
|
|
6324
|
+
const metricsPath = path22.join(root, toolsmithOutput.metrics_command);
|
|
6325
|
+
if (fs21.existsSync(metricsPath)) {
|
|
5933
6326
|
try {
|
|
5934
|
-
|
|
6327
|
+
fs21.chmodSync(metricsPath, 493);
|
|
5935
6328
|
} catch {
|
|
5936
6329
|
}
|
|
5937
6330
|
}
|
|
@@ -5968,8 +6361,8 @@ You may ONLY write to .majlis/scripts/. Output your structured JSON when done.`;
|
|
|
5968
6361
|
}
|
|
5969
6362
|
if (cartographerOk) {
|
|
5970
6363
|
const sessionCount = getSessionsSinceCompression(db);
|
|
5971
|
-
const newSynthesisPath =
|
|
5972
|
-
const newSynthesisSize =
|
|
6364
|
+
const newSynthesisPath = path22.join(root, "docs", "synthesis", "current.md");
|
|
6365
|
+
const newSynthesisSize = fs21.existsSync(newSynthesisPath) ? fs21.statSync(newSynthesisPath).size : 0;
|
|
5973
6366
|
recordCompression(db, sessionCount, report.synthesisSize, newSynthesisSize);
|
|
5974
6367
|
info("Recorded compression in DB.");
|
|
5975
6368
|
}
|
|
@@ -5979,12 +6372,12 @@ You may ONLY write to .majlis/scripts/. Output your structured JSON when done.`;
|
|
|
5979
6372
|
if (toolsmithOk) info(" \u2192 .majlis/scripts/metrics.sh + .majlis/config.json");
|
|
5980
6373
|
info("Run `majlis status` to see project state.");
|
|
5981
6374
|
}
|
|
5982
|
-
var
|
|
6375
|
+
var fs21, path22;
|
|
5983
6376
|
var init_resync = __esm({
|
|
5984
6377
|
"src/commands/resync.ts"() {
|
|
5985
6378
|
"use strict";
|
|
5986
|
-
|
|
5987
|
-
|
|
6379
|
+
fs21 = __toESM(require("fs"));
|
|
6380
|
+
path22 = __toESM(require("path"));
|
|
5988
6381
|
init_connection();
|
|
5989
6382
|
init_connection();
|
|
5990
6383
|
init_queries();
|
|
@@ -5999,10 +6392,10 @@ var init_resync = __esm({
|
|
|
5999
6392
|
});
|
|
6000
6393
|
|
|
6001
6394
|
// src/cli.ts
|
|
6002
|
-
var
|
|
6003
|
-
var
|
|
6395
|
+
var fs22 = __toESM(require("fs"));
|
|
6396
|
+
var path23 = __toESM(require("path"));
|
|
6004
6397
|
var VERSION2 = JSON.parse(
|
|
6005
|
-
|
|
6398
|
+
fs22.readFileSync(path23.join(__dirname, "..", "package.json"), "utf-8")
|
|
6006
6399
|
).version;
|
|
6007
6400
|
async function main() {
|
|
6008
6401
|
let sigintCount = 0;
|
|
@@ -6169,6 +6562,9 @@ Lifecycle:
|
|
|
6169
6562
|
|
|
6170
6563
|
Experiments:
|
|
6171
6564
|
new "hypothesis" Create experiment, branch, log, DB entry
|
|
6565
|
+
--sub-type TYPE Classify by problem sub-type
|
|
6566
|
+
--depends-on SLUG Block building until dependency is merged
|
|
6567
|
+
--context FILE,FILE Inject domain-specific docs into agent context
|
|
6172
6568
|
baseline Capture metrics snapshot (before)
|
|
6173
6569
|
measure Capture metrics snapshot (after)
|
|
6174
6570
|
compare [--json] Compare before/after, detect regressions
|