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.
Files changed (2) hide show
  1. package/dist/cli.js +646 -250
  2. 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) return 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
- return { output: markdown, structured, truncated };
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: [], tracked: {} },
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 result = stmt.run(slug, branch, hypothesis, subType, classificationRef);
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 = 3e4) {
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, import_node_child_process3.execSync)(config.build.pre_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
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, import_node_child_process3.execSync)(config.metrics.command, {
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, import_node_child_process3.execSync)(config.build.post_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
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 import_node_child_process3;
3170
+ var import_node_child_process4;
3024
3171
  var init_measure = __esm({
3025
3172
  "src/commands/measure.ts"() {
3026
3173
  "use strict";
3027
- import_node_child_process3 = require("child_process");
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, import_node_child_process4.execFileSync)("git", ["checkout", "-b", branch], {
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 exp = createExperiment(db, slug, branch, hypothesis, subType, null);
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 = path9.join(root, "docs", "experiments");
3073
- const templatePath = path9.join(docsDir, "_TEMPLATE.md");
3074
- if (fs9.existsSync(templatePath)) {
3075
- const template = fs9.readFileSync(templatePath, "utf-8");
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 = path9.join(docsDir, `${paddedNum}-${slug}.md`);
3078
- fs9.writeFileSync(logPath, logContent);
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, import_node_child_process4.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
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, import_node_child_process4.execFileSync)("git", ["checkout", "main"], {
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, import_node_child_process4.execFileSync)("git", ["checkout", "master"], {
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 fs9, path9, import_node_child_process4;
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
- fs9 = __toESM(require("fs"));
3147
- path9 = __toESM(require("path"));
3148
- import_node_child_process4 = require("child_process");
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 = path10.join(root, "docs", "synthesis", "fragility.md");
3292
- if (!fs10.existsSync(fragPath)) {
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 = fs10.readFileSync(fragPath, "utf-8");
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 = fs10.readFileSync(0, "utf-8");
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 fs10, path10;
3537
+ var fs11, path11;
3378
3538
  var init_query = __esm({
3379
3539
  "src/commands/query.ts"() {
3380
3540
  "use strict";
3381
- fs10 = __toESM(require("fs"));
3382
- path10 = __toESM(require("path"));
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, import_node_child_process5.execFileSync)("git", ["checkout", "main"], {
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, import_node_child_process5.execFileSync)("git", ["checkout", "master"], {
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, import_node_child_process5.execFileSync)("git", ["merge", branch, "--no-ff", "-m", `Merge experiment branch ${branch}`], {
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, import_node_child_process5.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
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, import_node_child_process5.execFileSync)("git", ["checkout", "--", "."], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
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, import_node_child_process5.execFileSync)("git", ["checkout", "main"], {
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, import_node_child_process5.execFileSync)("git", ["checkout", "master"], {
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 = path11.join(projectRoot, "docs", "synthesis", "fragility.md");
3828
+ const fragPath = path12.join(projectRoot, "docs", "synthesis", "fragility.md");
3625
3829
  let content = "";
3626
- if (fs11.existsSync(fragPath)) {
3627
- content = fs11.readFileSync(fragPath, "utf-8");
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
- fs11.writeFileSync(fragPath, content + entry);
3837
+ fs12.writeFileSync(fragPath, content + entry);
3634
3838
  }
3635
- var fs11, path11, import_node_child_process5;
3839
+ var fs12, path12, import_node_child_process6;
3636
3840
  var init_resolve = __esm({
3637
3841
  "src/resolve.ts"() {
3638
3842
  "use strict";
3639
- fs11 = __toESM(require("fs"));
3640
- path11 = __toESM(require("path"));
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
- import_node_child_process5 = require("child_process");
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(path12.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
3714
- const fragility = truncateContext(readFileOrEmpty(path12.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
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
- updateExperimentStatus(db, exp.id, "gated");
3746
- warn(`Gate REJECTED for ${exp.slug}: ${reason}`);
3747
- warn(`Revise the hypothesis or run \`majlis revert\` to abandon.`);
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(path12.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
3761
- const synthesis = truncateContext(readFileOrEmpty(path12.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
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, import_node_child_process6.execSync)(config.metrics.command, {
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, import_node_child_process6.execSync)(config.metrics.command, {
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, import_node_child_process6.execSync)('git diff main -- . ":!.majlis/"', {
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(path12.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
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 = path12.join(root, "docs", "experiments", `${paddedNum}-${exp.slug}.md`);
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(path12.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
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(path12.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
3931
- const fragility = truncateContext(readFileOrEmpty(path12.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
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 = path12.join(root, "docs", "challenges");
4250
+ const challengeDir = path13.join(root, "docs", "challenges");
3979
4251
  let challenges = "";
3980
- if (fs12.existsSync(challengeDir)) {
3981
- const files = fs12.readdirSync(challengeDir).filter((f) => f.includes(exp.slug) && f.endsWith(".md"));
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 += fs12.readFileSync(path12.join(challengeDir, f), "utf-8") + "\n\n";
4255
+ challenges += fs13.readFileSync(path13.join(challengeDir, f), "utf-8") + "\n\n";
3984
4256
  }
3985
4257
  }
3986
- const beforeMetrics = getMetricsByExperimentAndPhase(db, exp.id, "before");
3987
- const afterMetrics = getMetricsByExperimentAndPhase(db, exp.id, "after");
4258
+ const config = loadConfig(root);
4259
+ const metricComparisons = compareMetrics(db, exp.id, config);
3988
4260
  let metricsSection = "";
3989
- if (beforeMetrics.length > 0 || afterMetrics.length > 0) {
4261
+ if (metricComparisons.length > 0) {
3990
4262
  metricsSection = "\n\n## Framework-Captured Metrics (GROUND TRUTH \u2014 not self-reported by builder)\n";
3991
- if (beforeMetrics.length > 0) {
3992
- metricsSection += "### Before Build\n";
3993
- for (const m of beforeMetrics) {
3994
- metricsSection += `- ${m.fixture} / ${m.metric_name}: ${m.metric_value}
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
- if (afterMetrics.length > 0) {
3999
- metricsSection += "### After Build\n";
4000
- for (const m of afterMetrics) {
4001
- metricsSection += `- ${m.fixture} / ${m.metric_name}: ${m.metric_value}
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
- taskPrompt: `Verify experiment ${exp.slug}: ${exp.hypothesis}. Check provenance and content. Test the ${doubts.length} doubt(s) and any adversarial challenges.` + metricsSection + doubtReference
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 = path12.join(root, "docs", "synthesis", "current.md");
4052
- const sizeBefore = fs12.existsSync(synthesisPath) ? fs12.statSync(synthesisPath).size : 0;
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 = fs12.existsSync(synthesisPath) ? fs12.statSync(synthesisPath).size : 0;
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, import_node_child_process6.execSync)('git add -A -- ":!.majlis/"', { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
4066
- const diff = (0, import_node_child_process6.execSync)("git diff --cached --stat", { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
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, import_node_child_process6.execFileSync)("git", ["commit", "-m", msg], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
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 fs12, path12, import_node_child_process6;
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
- fs12 = __toESM(require("fs"));
4157
- path12 = __toESM(require("path"));
4158
- import_node_child_process6 = require("child_process");
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 = path13.join(root, "docs", "synthesis", "current.md");
4186
- const synthesis = fs13.existsSync(synthesisPath) ? fs13.readFileSync(synthesisPath, "utf-8") : "";
4187
- const deadEndsPath = path13.join(root, "docs", "synthesis", "dead-ends.md");
4188
- const deadEnds = fs13.existsSync(deadEndsPath) ? fs13.readFileSync(deadEndsPath, "utf-8") : "";
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 = path13.join(root, "docs", "classification");
4535
+ const classificationDir = path14.join(root, "docs", "classification");
4208
4536
  let classificationContent = "";
4209
- if (fs13.existsSync(classificationDir)) {
4210
- const files = fs13.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
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 += fs13.readFileSync(path13.join(classificationDir, f), "utf-8") + "\n\n";
4540
+ classificationContent += fs14.readFileSync(path14.join(classificationDir, f), "utf-8") + "\n\n";
4213
4541
  }
4214
4542
  }
4215
- const synthesisPath = path13.join(root, "docs", "synthesis", "current.md");
4216
- const synthesis = fs13.existsSync(synthesisPath) ? fs13.readFileSync(synthesisPath, "utf-8") : "";
4217
- const deadEndsPath = path13.join(root, "docs", "synthesis", "dead-ends.md");
4218
- const deadEnds = fs13.existsSync(deadEndsPath) ? fs13.readFileSync(deadEndsPath, "utf-8") : "";
4219
- const configPath = path13.join(root, ".majlis", "config.json");
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 (fs13.existsSync(configPath)) {
4222
- const config = JSON.parse(fs13.readFileSync(configPath, "utf-8"));
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 fs13, path13;
4575
+ var fs14, path14;
4248
4576
  var init_classify = __esm({
4249
4577
  "src/commands/classify.ts"() {
4250
4578
  "use strict";
4251
- fs13 = __toESM(require("fs"));
4252
- path13 = __toESM(require("path"));
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 = path14.join(root, "docs", "classification");
4602
+ const classificationDir = path15.join(root, "docs", "classification");
4275
4603
  let classification = "";
4276
- if (fs14.existsSync(classificationDir)) {
4277
- const files = fs14.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
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 += fs14.readFileSync(path14.join(classificationDir, f), "utf-8") + "\n\n";
4607
+ classification += fs15.readFileSync(path15.join(classificationDir, f), "utf-8") + "\n\n";
4280
4608
  }
4281
4609
  }
4282
- const synthesis = readFileOrEmpty(path14.join(root, "docs", "synthesis", "current.md"));
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 fs14, path14;
4654
+ var fs15, path15;
4327
4655
  var init_audit = __esm({
4328
4656
  "src/commands/audit.ts"() {
4329
4657
  "use strict";
4330
- fs14 = __toESM(require("fs"));
4331
- path14 = __toESM(require("path"));
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(path15.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
4623
- const fragility = truncateContext(readFileOrEmpty(path15.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
4624
- const deadEndsDoc = truncateContext(readFileOrEmpty(path15.join(root, "docs", "synthesis", "dead-ends.md")), CONTEXT_LIMITS.deadEnds);
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, import_node_child_process7.execSync)(config.metrics.command, {
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, import_node_child_process7.execFileSync)("git", ["checkout", "-b", branch], {
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 = path15.join(root, "docs", "experiments");
4743
- const templatePath = path15.join(docsDir, "_TEMPLATE.md");
4744
- if (fs15.existsSync(templatePath)) {
4745
- const template = fs15.readFileSync(templatePath, "utf-8");
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 = path15.join(docsDir, `${paddedNum}-${finalSlug}.md`);
4748
- fs15.writeFileSync(logPath, logContent);
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 fs15, path15, import_node_child_process7;
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
- fs15 = __toESM(require("fs"));
4758
- path15 = __toESM(require("path"));
4759
- import_node_child_process7 = require("child_process");
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 = path16.basename(mainRoot);
5104
+ const projectName = path17.basename(mainRoot);
4777
5105
  const worktreeName = `${projectName}-swarm-${paddedNum}-${slug}`;
4778
- const worktreePath = path16.join(path16.dirname(mainRoot), worktreeName);
5106
+ const worktreePath = path17.join(path17.dirname(mainRoot), worktreeName);
4779
5107
  const branch = `swarm/${paddedNum}-${slug}`;
4780
- (0, import_node_child_process8.execFileSync)("git", ["worktree", "add", worktreePath, "-b", branch], {
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 = path16.join(worktreePath, ".majlis");
4796
- fs16.mkdirSync(majlisDir, { recursive: true });
4797
- const configSrc = path16.join(mainRoot, ".majlis", "config.json");
4798
- if (fs16.existsSync(configSrc)) {
4799
- fs16.copyFileSync(configSrc, path16.join(majlisDir, "config.json"));
4800
- }
4801
- const agentsSrc = path16.join(mainRoot, ".majlis", "agents");
4802
- if (fs16.existsSync(agentsSrc)) {
4803
- const agentsDst = path16.join(majlisDir, "agents");
4804
- fs16.mkdirSync(agentsDst, { recursive: true });
4805
- for (const file of fs16.readdirSync(agentsSrc)) {
4806
- fs16.copyFileSync(path16.join(agentsSrc, file), path16.join(agentsDst, file));
4807
- }
4808
- }
4809
- const synthSrc = path16.join(mainRoot, "docs", "synthesis");
4810
- if (fs16.existsSync(synthSrc)) {
4811
- const synthDst = path16.join(worktreePath, "docs", "synthesis");
4812
- fs16.mkdirSync(synthDst, { recursive: true });
4813
- for (const file of fs16.readdirSync(synthSrc)) {
4814
- const srcFile = path16.join(synthSrc, file);
4815
- if (fs16.statSync(srcFile).isFile()) {
4816
- fs16.copyFileSync(srcFile, path16.join(synthDst, file));
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 = path16.join(mainRoot, "docs", "experiments", "_TEMPLATE.md");
4821
- if (fs16.existsSync(templateSrc)) {
4822
- const expDir = path16.join(worktreePath, "docs", "experiments");
4823
- fs16.mkdirSync(expDir, { recursive: true });
4824
- fs16.copyFileSync(templateSrc, path16.join(expDir, "_TEMPLATE.md"));
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, import_node_child_process8.execFileSync)("git", ["worktree", "remove", wt.path, "--force"], {
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, import_node_child_process8.execFileSync)("git", ["branch", "-D", wt.branch], {
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, import_node_child_process8.execSync)("git worktree prune", {
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 fs16, path16, import_node_child_process8;
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
- fs16 = __toESM(require("fs"));
4861
- path16 = __toESM(require("path"));
4862
- import_node_child_process8 = require("child_process");
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 = path17.join(wt.path, "docs", "experiments", "_TEMPLATE.md");
4881
- if (fs17.existsSync(templatePath)) {
4882
- const template = fs17.readFileSync(templatePath, "utf-8");
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 = path17.join(wt.path, "docs", "experiments", `${wt.paddedNum}-${wt.slug}.md`);
4885
- fs17.writeFileSync(logPath, logContent);
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 fs17, path17, MAX_STEPS;
5336
+ var fs18, path18, MAX_STEPS;
5009
5337
  var init_runner = __esm({
5010
5338
  "src/swarm/runner.ts"() {
5011
5339
  "use strict";
5012
- fs17 = __toESM(require("fs"));
5013
- path17 = __toESM(require("path"));
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, import_node_child_process9.execSync)("git status --porcelain", {
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, import_node_child_process9.execFileSync)("git", ["worktree", "list", "--porcelain"], {
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, import_node_child_process9.execFileSync)("git", ["worktree", "remove", orphanPath, "--force"], { cwd: root, encoding: "utf-8" });
5203
- info(`Cleaned up orphaned worktree: ${path18.basename(orphanPath)}`);
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, import_node_child_process9.execFileSync)("git", ["worktree", "prune"], { cwd: root, encoding: "utf-8" });
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, import_node_child_process9.execFileSync)(
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. Merge manually with:`);
5280
- info(` git merge ${best.worktree.branch} --no-ff`);
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(path18.join(root, "docs", "synthesis", "current.md")),
5715
+ readFileOrEmpty(path19.join(root, "docs", "synthesis", "current.md")),
5324
5716
  CONTEXT_LIMITS.synthesis
5325
5717
  );
5326
5718
  const fragility = truncateContext(
5327
- readFileOrEmpty(path18.join(root, "docs", "synthesis", "fragility.md")),
5719
+ readFileOrEmpty(path19.join(root, "docs", "synthesis", "fragility.md")),
5328
5720
  CONTEXT_LIMITS.fragility
5329
5721
  );
5330
5722
  const deadEndsDoc = truncateContext(
5331
- readFileOrEmpty(path18.join(root, "docs", "synthesis", "dead-ends.md")),
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, import_node_child_process9.execSync)(config.metrics.command, {
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 path18, import_node_child_process9, MAX_PARALLEL, DEFAULT_PARALLEL;
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
- path18 = __toESM(require("path"));
5426
- import_node_child_process9 = require("child_process");
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 = path19.join(root, ".majlis", "scripts");
5454
- if (!fs18.existsSync(scriptsDir)) {
5455
- fs18.mkdirSync(scriptsDir, { recursive: true });
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(path19.join(root, "docs", "synthesis", "current.md"));
5461
- const fragility = readFileOrEmpty(path19.join(root, "docs", "synthesis", "fragility.md"));
5462
- const deadEndsDoc = readFileOrEmpty(path19.join(root, "docs", "synthesis", "dead-ends.md"));
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, import_node_child_process10.execSync)(config.metrics.command, {
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 = path19.join(root, "docs", "diagnosis");
5514
- if (!fs18.existsSync(diagnosisDir)) {
5515
- fs18.mkdirSync(diagnosisDir, { recursive: true });
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 = path19.join(diagnosisDir, `diagnosis-${timestamp}.md`);
5519
- fs18.writeFileSync(artifactPath, result.output);
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 = fs18.readdirSync(scriptsDir);
5925
+ const files = fs19.readdirSync(scriptsDir);
5533
5926
  for (const f of files) {
5534
- fs18.unlinkSync(path19.join(scriptsDir, f));
5927
+ fs19.unlinkSync(path20.join(scriptsDir, f));
5535
5928
  }
5536
- fs18.rmdirSync(scriptsDir);
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 fs18, path19, import_node_child_process10;
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
- fs18 = __toESM(require("fs"));
5554
- path19 = __toESM(require("path"));
5555
- import_node_child_process10 = require("child_process");
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, import_node_child_process11.execFileSync)(
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, import_node_child_process11.execFileSync)(
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, import_node_child_process11.execFileSync)(
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 = path20.join(root, command);
5651
- if (command.includes("/") && !fs19.existsSync(scriptPath)) {
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, import_node_child_process11.execSync)(command, {
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 = path20.join(root, "docs", "synthesis", "current.md");
6075
+ const synthesisPath = path21.join(root, "docs", "synthesis", "current.md");
5683
6076
  let synthesisSize = 0;
5684
6077
  try {
5685
- synthesisSize = fs19.statSync(synthesisPath).size;
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 fs19, path20, import_node_child_process11;
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
- fs19 = __toESM(require("fs"));
5748
- path20 = __toESM(require("path"));
5749
- import_node_child_process11 = require("child_process");
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 = path21.join(root, "docs", "synthesis");
5800
- const scriptsDir = path21.join(root, ".majlis", "scripts");
5801
- if (!fs20.existsSync(synthesisDir)) fs20.mkdirSync(synthesisDir, { recursive: true });
5802
- if (!fs20.existsSync(scriptsDir)) fs20.mkdirSync(scriptsDir, { recursive: true });
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(path21.join(root, "docs", "synthesis", "current.md"));
5805
- const oldFragility = readFileOrEmpty(path21.join(root, "docs", "synthesis", "fragility.md"));
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 = path21.join(root, toolsmithOutput.metrics_command);
5932
- if (fs20.existsSync(metricsPath)) {
6324
+ const metricsPath = path22.join(root, toolsmithOutput.metrics_command);
6325
+ if (fs21.existsSync(metricsPath)) {
5933
6326
  try {
5934
- fs20.chmodSync(metricsPath, 493);
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 = path21.join(root, "docs", "synthesis", "current.md");
5972
- const newSynthesisSize = fs20.existsSync(newSynthesisPath) ? fs20.statSync(newSynthesisPath).size : 0;
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 fs20, path21;
6375
+ var fs21, path22;
5983
6376
  var init_resync = __esm({
5984
6377
  "src/commands/resync.ts"() {
5985
6378
  "use strict";
5986
- fs20 = __toESM(require("fs"));
5987
- path21 = __toESM(require("path"));
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 fs21 = __toESM(require("fs"));
6003
- var path22 = __toESM(require("path"));
6395
+ var fs22 = __toESM(require("fs"));
6396
+ var path23 = __toESM(require("path"));
6004
6397
  var VERSION2 = JSON.parse(
6005
- fs21.readFileSync(path22.join(__dirname, "..", "package.json"), "utf-8")
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