majlis 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +271 -34
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -416,6 +416,31 @@ var init_migrations = __esm({
416
416
  ALTER TABLE experiments ADD COLUMN hypothesis_file TEXT;
417
417
  ALTER TABLE experiments ADD COLUMN provenance TEXT DEFAULT 'cycle'
418
418
  CHECK(provenance IN ('cycle', 'catch-up', 'absorb'));
419
+ `);
420
+ },
421
+ // Migration 009: v8 → v9 — Chain invalidation, objective history, audit proposals
422
+ (db) => {
423
+ db.exec(`
424
+ ALTER TABLE experiments ADD COLUMN chain_weakened_by TEXT;
425
+
426
+ CREATE TABLE objective_history (
427
+ id INTEGER PRIMARY KEY,
428
+ objective_text TEXT NOT NULL,
429
+ previous_text TEXT,
430
+ reason TEXT NOT NULL,
431
+ source TEXT NOT NULL DEFAULT 'manual' CHECK(source IN ('manual', 'audit')),
432
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
433
+ );
434
+
435
+ CREATE TABLE audit_proposals (
436
+ id INTEGER PRIMARY KEY,
437
+ proposed_objective TEXT NOT NULL,
438
+ reason TEXT NOT NULL,
439
+ audit_output TEXT,
440
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'accepted', 'rejected')),
441
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
442
+ resolved_at DATETIME
443
+ );
419
444
  `);
420
445
  }
421
446
  ];
@@ -1706,46 +1731,31 @@ See \`docs/workflow.md\` for the full cycle. See \`.claude/agents/\` for role de
1706
1731
 
1707
1732
  ### Session Discipline
1708
1733
  - One intent per session. Declare it with \`majlis session start "intent"\`.
1709
- - Stray thoughts \u2192 Telegram (Scribe) or docs/inbox/.
1734
+ - Stray thoughts \u2192 \`docs/inbox/\`.
1710
1735
  - Every session ends with \`majlis session end\`.
1711
1736
 
1712
1737
  ### Before Building
1713
1738
  - Read \`docs/synthesis/current.md\` for compressed project state.
1714
1739
  - Run \`majlis dead-ends --sub-type <relevant>\` for structural constraints.
1715
1740
  - Run \`majlis decisions --level judgment\` for provisional decisions to challenge.
1741
+ - Run \`majlis brief\` for a context dump of the current experiment state.
1716
1742
 
1717
- ### Compression Trigger
1718
- - Run \`majlis status\` \u2014 it will warn when compression is due.
1719
-
1720
- ### Current State
1721
- Run \`majlis status\` for live experiment state and cycle position.
1722
- `;
1723
- function claudeMdContent(name, objective) {
1724
- return `# ${name}
1725
-
1726
- ${objective ? `**Objective:** ${objective}
1727
- ` : ""}## Majlis Protocol
1743
+ ### Capturing Observations
1744
+ - \`majlis note "text" --tag hypothesis\` \u2014 save observations to the DB (injected into agent contexts).
1745
+ - \`majlis journal "text"\` \u2014 timestamped breadcrumbs during manual hacking.
1746
+ - \`majlis catch-up "description" --diff HEAD~3..HEAD\` \u2014 create experiment retroactively from manual work.
1728
1747
 
1729
- This project uses the Majlis Framework for structured multi-agent problem solving.
1730
- See \`docs/workflow.md\` for the full cycle. See \`.claude/agents/\` for role definitions (source of truth in \`.majlis/agents/\`).
1731
-
1732
- ### Evidence Hierarchy (tag every decision)
1733
- 1. **Proof** \u2014 mathematical proof. Overturn requires error in proof.
1734
- 2. **Test** \u2014 empirical test. Overturn requires showing test insufficiency.
1735
- 3a. **Strong Consensus** \u2014 convergence across independent approaches.
1736
- 3b. **Consensus** \u2014 agreement from same-model experiments.
1737
- 4. **Analogy** \u2014 justified by similarity to prior work.
1738
- 5. **Judgment** \u2014 independent reasoning without precedent.
1748
+ ### Chain Integrity
1749
+ - If an experiment you depend on is dead-ended, your experiment is flagged as "weakened chain".
1750
+ - Run \`majlis status\` to see chain warnings. Revert or proceed at your own risk.
1739
1751
 
1740
- ### Session Discipline
1741
- - One intent per session. Declare it with \`majlis session start "intent"\`.
1742
- - Stray thoughts \u2192 Telegram (Scribe) or docs/inbox/.
1743
- - Every session ends with \`majlis session end\`.
1752
+ ### Verification Review
1753
+ - If \`require_human_verify\` is enabled, experiments pause at \`verified\` for human review.
1754
+ - Run \`majlis resolve\` to proceed or \`majlis resolve --reject\` to dead-end.
1744
1755
 
1745
- ### Before Building
1746
- - Read \`docs/synthesis/current.md\` for compressed project state.
1747
- - Run \`majlis dead-ends --sub-type <relevant>\` for structural constraints.
1748
- - Run \`majlis decisions --level judgment\` for provisional decisions to challenge.
1756
+ ### Purpose Audit
1757
+ - Circuit breaker trips (3+ failures on a sub-type) trigger a Maqasid Check.
1758
+ - If the audit proposes an objective rewrite, run \`majlis audit --accept\` or \`--reject\`.
1749
1759
 
1750
1760
  ### Compression Trigger
1751
1761
  - Run \`majlis status\` \u2014 it will warn when compression is due.
@@ -1753,6 +1763,11 @@ See \`docs/workflow.md\` for the full cycle. See \`.claude/agents/\` for role de
1753
1763
  ### Current State
1754
1764
  Run \`majlis status\` for live experiment state and cycle position.
1755
1765
  `;
1766
+ function claudeMdContent(name, objective) {
1767
+ return `# ${name}
1768
+
1769
+ ${objective ? `**Objective:** ${objective}
1770
+ ` : ""}${CLAUDE_MD_SECTION2}`;
1756
1771
  }
1757
1772
  var DEFAULT_CONFIG3 = {
1758
1773
  project: {
@@ -2745,7 +2760,8 @@ var init_config = __esm({
2745
2760
  circuit_breaker_threshold: 3,
2746
2761
  require_doubt_before_verify: true,
2747
2762
  require_challenge_before_verify: false,
2748
- auto_baseline_on_new_experiment: true
2763
+ auto_baseline_on_new_experiment: true,
2764
+ require_human_verify: false
2749
2765
  },
2750
2766
  models: {}
2751
2767
  };
@@ -3682,7 +3698,7 @@ ${cmd.body}
3682
3698
  const existing = fs8.readFileSync(claudeMdPath, "utf-8");
3683
3699
  if (existing.includes("## Majlis Protocol")) {
3684
3700
  const replaced = existing.replace(
3685
- /## Majlis Protocol[\s\S]*?(?=\n## [^M]|\n## $|$)/,
3701
+ /## Majlis Protocol[\s\S]*?(?=\n## (?!#)(?!Majlis Protocol)|$)/,
3686
3702
  import_shared.CLAUDE_MD_SECTION.trim()
3687
3703
  );
3688
3704
  if (replaced !== existing) {
@@ -3779,6 +3795,36 @@ function clearGateRejection(db, experimentId) {
3779
3795
  UPDATE experiments SET gate_rejection_reason = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?
3780
3796
  `).run(experimentId);
3781
3797
  }
3798
+ function findDependents(db, slug) {
3799
+ const dependents = [];
3800
+ const visited = /* @__PURE__ */ new Set();
3801
+ const queue = [slug];
3802
+ while (queue.length > 0) {
3803
+ const current = queue.shift();
3804
+ if (visited.has(current)) continue;
3805
+ visited.add(current);
3806
+ const rows = db.prepare(
3807
+ `SELECT * FROM experiments WHERE depends_on = ? AND status NOT IN ('merged', 'dead_end')`
3808
+ ).all(current);
3809
+ for (const row of rows) {
3810
+ dependents.push(row);
3811
+ queue.push(row.slug);
3812
+ }
3813
+ }
3814
+ return dependents;
3815
+ }
3816
+ function setChainWeakened(db, experimentId, slug) {
3817
+ db.prepare(`
3818
+ UPDATE experiments SET chain_weakened_by = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
3819
+ `).run(slug, experimentId);
3820
+ }
3821
+ function cascadeChainInvalidation(db, slug) {
3822
+ const dependents = findDependents(db, slug);
3823
+ for (const dep of dependents) {
3824
+ setChainWeakened(db, dep.id, slug);
3825
+ }
3826
+ return dependents.length;
3827
+ }
3782
3828
  function insertDecision(db, experimentId, description, evidenceLevel, justification) {
3783
3829
  const stmt = db.prepare(`
3784
3830
  INSERT INTO decisions (experiment_id, description, evidence_level, justification)
@@ -4057,6 +4103,28 @@ function storeHypothesisFile(db, experimentId, filePath) {
4057
4103
  UPDATE experiments SET hypothesis_file = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
4058
4104
  `).run(filePath, experimentId);
4059
4105
  }
4106
+ function insertObjectiveHistory(db, objectiveText, previousText, reason, source) {
4107
+ db.prepare(`
4108
+ INSERT INTO objective_history (objective_text, previous_text, reason, source)
4109
+ VALUES (?, ?, ?, ?)
4110
+ `).run(objectiveText, previousText, reason, source);
4111
+ }
4112
+ function insertAuditProposal(db, proposedObjective, reason, auditOutput) {
4113
+ db.prepare(`
4114
+ INSERT INTO audit_proposals (proposed_objective, reason, audit_output)
4115
+ VALUES (?, ?, ?)
4116
+ `).run(proposedObjective, reason, auditOutput);
4117
+ }
4118
+ function getPendingAuditProposal(db) {
4119
+ return db.prepare(
4120
+ `SELECT * FROM audit_proposals WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1`
4121
+ ).get();
4122
+ }
4123
+ function resolveAuditProposal(db, id, status2) {
4124
+ db.prepare(`
4125
+ UPDATE audit_proposals SET status = ?, resolved_at = CURRENT_TIMESTAMP WHERE id = ?
4126
+ `).run(status2, id);
4127
+ }
4060
4128
  function exportForCompressor(db, maxLength = 5e4) {
4061
4129
  const experiments = listAllExperiments(db);
4062
4130
  const sections = ["# Structured Data Export (from SQLite)\n"];
@@ -4390,6 +4458,32 @@ async function status(isJson) {
4390
4458
  ]);
4391
4459
  console.log(table(["Sub-Type", "Failures", "Status"], cbRows));
4392
4460
  }
4461
+ const weakenedExps = experiments.filter((e) => e.chain_weakened_by);
4462
+ if (weakenedExps.length > 0) {
4463
+ console.log();
4464
+ for (const e of weakenedExps) {
4465
+ warn(`${e.slug} depends on ${e.chain_weakened_by} (dead-ended)`);
4466
+ }
4467
+ console.log(` Use \`majlis revert <slug>\` to abandon, or proceed at your own risk.`);
4468
+ }
4469
+ if (config.cycle.require_human_verify) {
4470
+ const verifiedExps = experiments.filter((e) => e.status === "verified");
4471
+ for (const e of verifiedExps) {
4472
+ const grades = getVerificationsByExperiment(db, e.id);
4473
+ console.log(`
4474
+ ${bold(e.slug)}: awaiting human review (verified)`);
4475
+ for (const g of grades) {
4476
+ console.log(` ${g.component}: ${gradeColor(g.grade)}${g.notes ? ` \u2014 ${g.notes}` : ""}`);
4477
+ }
4478
+ console.log(` Run \`majlis resolve\` or \`majlis resolve --reject\``);
4479
+ }
4480
+ }
4481
+ const pendingProposal = getPendingAuditProposal(db);
4482
+ if (pendingProposal) {
4483
+ console.log();
4484
+ warn(`Pending objective rewrite: "${pendingProposal.proposed_objective}"`);
4485
+ console.log(` Run \`majlis audit --accept\` or \`majlis audit --reject\``);
4486
+ }
4393
4487
  if (judgmentDecisions.length > 0) {
4394
4488
  console.log(`
4395
4489
  ${yellow(`${judgmentDecisions.length} judgment-level decisions`)} (provisional targets for doubt)`);
@@ -4479,7 +4573,8 @@ var init_types2 = __esm({
4479
4573
  revert: (current, target) => target === "dead_end" /* DEAD_END */ && !isTerminalStatus(current),
4480
4574
  circuit_breaker: (current, target) => target === "dead_end" /* DEAD_END */ && !isTerminalStatus(current),
4481
4575
  error_recovery: (current, target) => target === "dead_end" /* DEAD_END */ && !isTerminalStatus(current),
4482
- bootstrap: (current, target) => current === "classified" /* CLASSIFIED */ && target === "reframed" /* REFRAMED */ || current === "classified" /* CLASSIFIED */ && target === "built" /* BUILT */
4576
+ bootstrap: (current, target) => current === "classified" /* CLASSIFIED */ && target === "reframed" /* REFRAMED */ || current === "classified" /* CLASSIFIED */ && target === "built" /* BUILT */,
4577
+ objective_reset: (current, target) => target === "classified" /* CLASSIFIED */ && !isTerminalStatus(current)
4483
4578
  };
4484
4579
  }
4485
4580
  });
@@ -4818,6 +4913,8 @@ async function resolve2(db, exp, projectRoot) {
4818
4913
  incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
4819
4914
  }
4820
4915
  })();
4916
+ const weakened = cascadeChainInvalidation(db, exp.slug);
4917
+ if (weakened > 0) warn(`Weakened chain: ${weakened} downstream experiment(s) depend on ${exp.slug}.`);
4821
4918
  info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
4822
4919
  break;
4823
4920
  }
@@ -4954,6 +5051,8 @@ async function resolveDbOnly(db, exp, projectRoot) {
4954
5051
  incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
4955
5052
  }
4956
5053
  })();
5054
+ const weakened = cascadeChainInvalidation(db, exp.slug);
5055
+ if (weakened > 0) warn(`Weakened chain: ${weakened} downstream experiment(s) depend on ${exp.slug}.`);
4957
5056
  info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
4958
5057
  break;
4959
5058
  }
@@ -5079,6 +5178,31 @@ async function resolveCmd(args) {
5079
5178
  if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
5080
5179
  const db = getDb(root);
5081
5180
  const exp = resolveExperimentArg(db, args);
5181
+ if (args.includes("--reject")) {
5182
+ transition(exp.status, "resolved" /* RESOLVED */);
5183
+ const reason = getFlagValue(args, "--reason") ?? "Human rejected after verification review";
5184
+ db.transaction(() => {
5185
+ insertDeadEnd(
5186
+ db,
5187
+ exp.id,
5188
+ exp.hypothesis ?? exp.slug,
5189
+ reason,
5190
+ `Human rejection: ${reason}`,
5191
+ exp.sub_type,
5192
+ "procedural"
5193
+ );
5194
+ updateExperimentStatus(db, exp.id, "resolved");
5195
+ updateExperimentStatus(db, exp.id, "dead_end");
5196
+ if (exp.sub_type) {
5197
+ incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
5198
+ }
5199
+ })();
5200
+ handleDeadEndGit(exp, root);
5201
+ const weakened = cascadeChainInvalidation(db, exp.slug);
5202
+ if (weakened > 0) warn(`Weakened chain: ${weakened} downstream experiment(s) depend on ${exp.slug}.`);
5203
+ info(`Experiment ${exp.slug} dead-ended by human rejection: ${reason}`);
5204
+ return;
5205
+ }
5082
5206
  transition(exp.status, "resolved" /* RESOLVED */);
5083
5207
  await resolve2(db, exp, root);
5084
5208
  }
@@ -5252,6 +5376,8 @@ ${hypoContent.slice(0, 8e3)}`;
5252
5376
  );
5253
5377
  adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
5254
5378
  handleDeadEndGit(exp, root);
5379
+ const weakened = cascadeChainInvalidation(db, exp.slug);
5380
+ if (weakened > 0) warn(`Weakened chain: ${weakened} downstream experiment(s) depend on ${exp.slug}.`);
5255
5381
  info(`Builder abandoned ${exp.slug}: ${result.structured.abandon.reason}`);
5256
5382
  return;
5257
5383
  }
@@ -5322,6 +5448,8 @@ Error: ${errMsg.slice(0, 500)}`
5322
5448
  );
5323
5449
  adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
5324
5450
  handleDeadEndGit(exp, root);
5451
+ const weakenedTrunc = cascadeChainInvalidation(db, exp.slug);
5452
+ if (weakenedTrunc > 0) warn(`Weakened chain: ${weakenedTrunc} downstream experiment(s) depend on ${exp.slug}.`);
5325
5453
  info(`Builder abandoned ${exp.slug} (recovered from truncation): ${recovery.data.abandon.reason}`);
5326
5454
  } else {
5327
5455
  const tail = result.output.slice(-2e3).trim();
@@ -6180,6 +6308,8 @@ ${gitDiff.slice(0, 15e3)}
6180
6308
  if (exp.gate_rejection_reason) clearGateRejection(db, exp.id);
6181
6309
  adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "revert");
6182
6310
  handleDeadEndGit(exp, root);
6311
+ const weakened = cascadeChainInvalidation(db, exp.slug);
6312
+ if (weakened > 0) warn(`Weakened chain: ${weakened} downstream experiment(s) depend on ${exp.slug}.`);
6183
6313
  info(`Experiment ${exp.slug} reverted to dead-end.`);
6184
6314
  info(`Constraint: ${structuralConstraint.slice(0, 120)}${structuralConstraint.length > 120 ? "..." : ""}`);
6185
6315
  }
@@ -6550,6 +6680,50 @@ async function audit(args) {
6550
6680
  const root = findProjectRoot();
6551
6681
  if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
6552
6682
  const db = getDb(root);
6683
+ if (args.includes("--accept")) {
6684
+ const proposal = getPendingAuditProposal(db);
6685
+ if (!proposal) {
6686
+ warn("No pending audit proposal to accept.");
6687
+ return;
6688
+ }
6689
+ const config2 = loadConfig(root);
6690
+ const previousObjective = config2.project?.objective ?? "";
6691
+ const configPath = path15.join(root, ".majlis", "config.json");
6692
+ const rawConfig = JSON.parse(fs15.readFileSync(configPath, "utf-8"));
6693
+ rawConfig.project = rawConfig.project || {};
6694
+ rawConfig.project.objective = proposal.proposed_objective;
6695
+ fs15.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
6696
+ resetConfigCache();
6697
+ insertObjectiveHistory(db, proposal.proposed_objective, previousObjective, proposal.reason, "audit");
6698
+ const activeExps = listActiveExperiments(db);
6699
+ for (const exp of activeExps) {
6700
+ insertNote(
6701
+ db,
6702
+ null,
6703
+ exp.id,
6704
+ "objective-change",
6705
+ `Objective changed: "${previousObjective}" \u2192 "${proposal.proposed_objective}"`
6706
+ );
6707
+ adminTransitionAndPersist(db, exp.id, exp.status, "classified" /* CLASSIFIED */, "objective_reset");
6708
+ }
6709
+ resolveAuditProposal(db, proposal.id, "accepted");
6710
+ autoCommit(root, `audit: accept objective rewrite`);
6711
+ success(`Objective updated: "${proposal.proposed_objective}"`);
6712
+ if (activeExps.length > 0) {
6713
+ info(`${activeExps.length} active experiment(s) reset to classified.`);
6714
+ }
6715
+ return;
6716
+ }
6717
+ if (args.includes("--reject")) {
6718
+ const proposal = getPendingAuditProposal(db);
6719
+ if (!proposal) {
6720
+ warn("No pending audit proposal to reject.");
6721
+ return;
6722
+ }
6723
+ resolveAuditProposal(db, proposal.id, "rejected");
6724
+ info("Audit proposal rejected.");
6725
+ return;
6726
+ }
6553
6727
  const objective = args.filter((a) => !a.startsWith("--")).join(" ");
6554
6728
  const config = loadConfig(root);
6555
6729
  const experiments = listAllExperiments(db);
@@ -6599,13 +6773,33 @@ Answer these questions:
6599
6773
  2. Is the current classification serving that objective? Or has the taxonomy become self-referential?
6600
6774
  3. What would we do differently if we started from scratch with what we now know?
6601
6775
  4. Is there a simpler formulation? If the classification has grown complex, something may be wrong.
6776
+ 5. If the classification is fundamentally misaligned, propose a rewrite.
6777
+ Include: <!-- majlis-json {"objective_rewrite": {"proposed_objective": "...", "reason": "..."}} -->
6778
+ Only include objective_rewrite if genuinely needed.
6602
6779
 
6603
6780
  Output: either "classification confirmed \u2014 continue" or "re-classify from X" with a specific proposal.`;
6604
6781
  const result = await spawnAgent("builder", {
6605
6782
  synthesis,
6606
6783
  taskPrompt: auditPrompt
6607
6784
  }, root);
6608
- success("Purpose audit complete. Review the output above.");
6785
+ const structured = result.structured;
6786
+ let rewrite = structured?.objective_rewrite;
6787
+ if (!rewrite && result.output) {
6788
+ const extracted = await extractStructuredData("builder", result.output);
6789
+ rewrite = extracted.data?.objective_rewrite;
6790
+ }
6791
+ if (rewrite) {
6792
+ insertAuditProposal(db, rewrite.proposed_objective, rewrite.reason, result.output.slice(0, 5e3));
6793
+ console.log();
6794
+ header("Objective Rewrite Proposed");
6795
+ console.log(` Current: ${config.project?.objective ?? "(none)"}`);
6796
+ console.log(` Proposed: ${rewrite.proposed_objective}`);
6797
+ console.log(` Reason: ${rewrite.reason}`);
6798
+ console.log();
6799
+ info("Run `majlis audit --accept` or `majlis audit --reject`.");
6800
+ } else {
6801
+ success("Purpose audit complete. Review the output above.");
6802
+ }
6609
6803
  }
6610
6804
  var fs15, path15;
6611
6805
  var init_audit = __esm({
@@ -6615,8 +6809,12 @@ var init_audit = __esm({
6615
6809
  path15 = __toESM(require("path"));
6616
6810
  init_connection();
6617
6811
  init_queries();
6812
+ init_machine();
6813
+ init_types2();
6618
6814
  init_spawn();
6815
+ init_parse();
6619
6816
  init_config();
6817
+ init_git();
6620
6818
  init_format();
6621
6819
  }
6622
6820
  });
@@ -6674,6 +6872,8 @@ async function runNextStep(db, exp, config, root, isJson, overrideGate = false)
6674
6872
  );
6675
6873
  adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "circuit_breaker");
6676
6874
  handleDeadEndGit(exp, root);
6875
+ const weakened = cascadeChainInvalidation(db, exp.slug);
6876
+ if (weakened > 0) warn(`Weakened chain: ${weakened} downstream experiment(s) depend on ${exp.slug}.`);
6677
6877
  warn("Experiment dead-ended. Triggering Maqasid Check (purpose audit).");
6678
6878
  await audit([config.project?.objective ?? ""]);
6679
6879
  return;
@@ -6688,6 +6888,9 @@ async function runNextStep(db, exp, config, root, isJson, overrideGate = false)
6688
6888
  return;
6689
6889
  }
6690
6890
  }
6891
+ if (exp.chain_weakened_by) {
6892
+ warn(`Experiment ${exp.slug} depends on ${exp.chain_weakened_by} (dead-ended). Chain integrity weakened.`);
6893
+ }
6691
6894
  const sessionsSinceCompression = getSessionsSinceCompression(db);
6692
6895
  if (sessionsSinceCompression >= config.cycle.compression_interval) {
6693
6896
  warn(
@@ -6706,6 +6909,11 @@ async function runNextStep(db, exp, config, root, isJson, overrideGate = false)
6706
6909
  }));
6707
6910
  return;
6708
6911
  }
6912
+ if (nextStep === "resolved" /* RESOLVED */ && config.cycle.require_human_verify) {
6913
+ info(`${exp.slug} verified. Human review required.`);
6914
+ info("Run `majlis resolve` to proceed or `majlis resolve --reject` to dead-end.");
6915
+ return;
6916
+ }
6709
6917
  info(`${exp.slug}: ${exp.status} \u2192 ${nextStep}`);
6710
6918
  await executeStep(nextStep, exp, root);
6711
6919
  }
@@ -6723,6 +6931,21 @@ async function runAutoLoop(db, exp, config, root, isJson) {
6723
6931
  info("Stopping auto mode. Use `majlis next --override-gate` or `majlis revert`.");
6724
6932
  break;
6725
6933
  }
6934
+ if (exp.chain_weakened_by) {
6935
+ warn(`Experiment ${exp.slug} has weakened chain (depends on dead-ended ${exp.chain_weakened_by}). Auto-dead-ending.`);
6936
+ insertDeadEnd(
6937
+ db,
6938
+ exp.id,
6939
+ exp.hypothesis ?? exp.slug,
6940
+ `Upstream dependency ${exp.chain_weakened_by} is dead-ended`,
6941
+ `Chain invalidated: depends on ${exp.chain_weakened_by}`,
6942
+ exp.sub_type,
6943
+ "procedural"
6944
+ );
6945
+ adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "circuit_breaker");
6946
+ handleDeadEndGit(exp, root);
6947
+ continue;
6948
+ }
6726
6949
  if (isTerminal(exp.status)) {
6727
6950
  success(`Experiment ${exp.slug} reached terminal state: ${exp.status}`);
6728
6951
  break;
@@ -6740,6 +6963,8 @@ async function runAutoLoop(db, exp, config, root, isJson) {
6740
6963
  );
6741
6964
  adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "circuit_breaker");
6742
6965
  handleDeadEndGit(exp, root);
6966
+ const weakened = cascadeChainInvalidation(db, exp.slug);
6967
+ if (weakened > 0) warn(`Weakened chain: ${weakened} downstream experiment(s) depend on ${exp.slug}.`);
6743
6968
  await audit([config.project?.objective ?? ""]);
6744
6969
  break;
6745
6970
  }
@@ -6748,6 +6973,10 @@ async function runAutoLoop(db, exp, config, root, isJson) {
6748
6973
  const expHasDoubts = hasDoubts(db, exp.id);
6749
6974
  const expHasChallenges = hasChallenges(db, exp.id);
6750
6975
  const nextStep = determineNextStep(exp, valid, expHasDoubts, expHasChallenges);
6976
+ if (nextStep === "resolved" /* RESOLVED */ && config.cycle.require_human_verify) {
6977
+ info("Pausing at verified \u2014 human review required.");
6978
+ break;
6979
+ }
6751
6980
  info(`[${iteration}/${MAX_ITERATIONS}] ${exp.slug}: ${exp.status} \u2192 ${nextStep}`);
6752
6981
  await executeStep(nextStep, exp, root);
6753
6982
  }
@@ -6908,6 +7137,8 @@ async function run(args) {
6908
7137
  "revert"
6909
7138
  );
6910
7139
  handleDeadEndGit(afterStep, root);
7140
+ const w1 = cascadeChainInvalidation(db, afterStep.slug);
7141
+ if (w1 > 0) warn(`Weakened chain: ${w1} downstream experiment(s) depend on ${afterStep.slug}.`);
6911
7142
  continue;
6912
7143
  }
6913
7144
  } catch (err) {
@@ -6926,6 +7157,8 @@ async function run(args) {
6926
7157
  );
6927
7158
  adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "error_recovery");
6928
7159
  handleDeadEndGit(exp, root);
7160
+ const w2 = cascadeChainInvalidation(db, exp.slug);
7161
+ if (w2 > 0) warn(`Weakened chain: ${w2} downstream experiment(s) depend on ${exp.slug}.`);
6929
7162
  } catch (innerErr) {
6930
7163
  const innerMsg = innerErr instanceof Error ? innerErr.message : String(innerErr);
6931
7164
  warn(`Could not record dead-end: ${innerMsg}`);
@@ -8964,6 +9197,8 @@ Cycle:
8964
9197
  verify [experiment] Spawn verifier agent
8965
9198
  gate [experiment] Spawn gatekeeper agent
8966
9199
  resolve [experiment] Route based on verification grades
9200
+ --reject Force dead-end (human override)
9201
+ --reason "text" Reason for rejection (with --reject)
8967
9202
  compress Spawn compressor agent
8968
9203
 
8969
9204
  Classification:
@@ -8980,6 +9215,8 @@ Queries:
8980
9215
 
8981
9216
  Audit:
8982
9217
  audit "objective" Maqasid check \u2014 is the frame right?
9218
+ --accept Accept pending objective rewrite
9219
+ --reject Reject pending objective rewrite
8983
9220
  diagnose ["focus area"] Deep diagnosis \u2014 root causes, patterns, gaps
8984
9221
 
8985
9222
  Sessions:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "majlis",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Multi-agent workflow CLI for structured doubt, independent verification, and compressed knowledge",
5
5
  "bin": {
6
6
  "majlis": "./dist/cli.js"