majlis 0.8.4 → 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 +867 -41
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -75,6 +75,20 @@ function statusColor(status2) {
75
75
  return yellow(status2);
76
76
  }
77
77
  }
78
+ function gradeColor(grade) {
79
+ switch (grade) {
80
+ case "sound":
81
+ return green(grade);
82
+ case "good":
83
+ return cyan(grade);
84
+ case "weak":
85
+ return yellow(grade);
86
+ case "rejected":
87
+ return red(grade);
88
+ default:
89
+ return grade;
90
+ }
91
+ }
78
92
  function evidenceColor(level) {
79
93
  switch (level) {
80
94
  case "proof":
@@ -375,6 +389,58 @@ var init_migrations = __esm({
375
389
  (db) => {
376
390
  db.exec(`
377
391
  ALTER TABLE experiments ADD COLUMN gate_rejection_reason TEXT;
392
+ `);
393
+ },
394
+ // Migration 008: v7 → v8 — Pilot integration: notes, journal, hypothesis file, provenance
395
+ (db) => {
396
+ db.exec(`
397
+ CREATE TABLE notes (
398
+ id INTEGER PRIMARY KEY,
399
+ session_id INTEGER REFERENCES sessions(id),
400
+ experiment_id INTEGER REFERENCES experiments(id),
401
+ tag TEXT,
402
+ content TEXT NOT NULL,
403
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
404
+ );
405
+ CREATE INDEX idx_notes_session ON notes(session_id);
406
+ CREATE INDEX idx_notes_experiment ON notes(experiment_id);
407
+
408
+ CREATE TABLE journal_entries (
409
+ id INTEGER PRIMARY KEY,
410
+ session_id INTEGER REFERENCES sessions(id),
411
+ content TEXT NOT NULL,
412
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
413
+ );
414
+ CREATE INDEX idx_journal_session ON journal_entries(session_id);
415
+
416
+ ALTER TABLE experiments ADD COLUMN hypothesis_file TEXT;
417
+ ALTER TABLE experiments ADD COLUMN provenance TEXT DEFAULT 'cycle'
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
+ );
378
444
  `);
379
445
  }
380
446
  ];
@@ -1665,46 +1731,31 @@ See \`docs/workflow.md\` for the full cycle. See \`.claude/agents/\` for role de
1665
1731
 
1666
1732
  ### Session Discipline
1667
1733
  - One intent per session. Declare it with \`majlis session start "intent"\`.
1668
- - Stray thoughts \u2192 Telegram (Scribe) or docs/inbox/.
1734
+ - Stray thoughts \u2192 \`docs/inbox/\`.
1669
1735
  - Every session ends with \`majlis session end\`.
1670
1736
 
1671
1737
  ### Before Building
1672
1738
  - Read \`docs/synthesis/current.md\` for compressed project state.
1673
1739
  - Run \`majlis dead-ends --sub-type <relevant>\` for structural constraints.
1674
1740
  - Run \`majlis decisions --level judgment\` for provisional decisions to challenge.
1741
+ - Run \`majlis brief\` for a context dump of the current experiment state.
1675
1742
 
1676
- ### Compression Trigger
1677
- - Run \`majlis status\` \u2014 it will warn when compression is due.
1678
-
1679
- ### Current State
1680
- Run \`majlis status\` for live experiment state and cycle position.
1681
- `;
1682
- function claudeMdContent(name, objective) {
1683
- return `# ${name}
1684
-
1685
- ${objective ? `**Objective:** ${objective}
1686
- ` : ""}## Majlis Protocol
1687
-
1688
- This project uses the Majlis Framework for structured multi-agent problem solving.
1689
- See \`docs/workflow.md\` for the full cycle. See \`.claude/agents/\` for role definitions (source of truth in \`.majlis/agents/\`).
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.
1690
1747
 
1691
- ### Evidence Hierarchy (tag every decision)
1692
- 1. **Proof** \u2014 mathematical proof. Overturn requires error in proof.
1693
- 2. **Test** \u2014 empirical test. Overturn requires showing test insufficiency.
1694
- 3a. **Strong Consensus** \u2014 convergence across independent approaches.
1695
- 3b. **Consensus** \u2014 agreement from same-model experiments.
1696
- 4. **Analogy** \u2014 justified by similarity to prior work.
1697
- 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.
1698
1751
 
1699
- ### Session Discipline
1700
- - One intent per session. Declare it with \`majlis session start "intent"\`.
1701
- - Stray thoughts \u2192 Telegram (Scribe) or docs/inbox/.
1702
- - 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.
1703
1755
 
1704
- ### Before Building
1705
- - Read \`docs/synthesis/current.md\` for compressed project state.
1706
- - Run \`majlis dead-ends --sub-type <relevant>\` for structural constraints.
1707
- - 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\`.
1708
1759
 
1709
1760
  ### Compression Trigger
1710
1761
  - Run \`majlis status\` \u2014 it will warn when compression is due.
@@ -1712,6 +1763,11 @@ See \`docs/workflow.md\` for the full cycle. See \`.claude/agents/\` for role de
1712
1763
  ### Current State
1713
1764
  Run \`majlis status\` for live experiment state and cycle position.
1714
1765
  `;
1766
+ function claudeMdContent(name, objective) {
1767
+ return `# ${name}
1768
+
1769
+ ${objective ? `**Objective:** ${objective}
1770
+ ` : ""}${CLAUDE_MD_SECTION2}`;
1715
1771
  }
1716
1772
  var DEFAULT_CONFIG3 = {
1717
1773
  project: {
@@ -1781,10 +1837,10 @@ Run \`majlis status\` for live experiment state and cycle position.
1781
1837
  }
1782
1838
  }, null, 2);
1783
1839
  }
1784
- var fs23 = __toESM2(require("fs"));
1840
+ var fs24 = __toESM2(require("fs"));
1785
1841
  function mkdirSafe3(dir) {
1786
- if (!fs23.existsSync(dir)) {
1787
- fs23.mkdirSync(dir, { recursive: true });
1842
+ if (!fs24.existsSync(dir)) {
1843
+ fs24.mkdirSync(dir, { recursive: true });
1788
1844
  }
1789
1845
  }
1790
1846
  function validateProject2(checks) {
@@ -2704,7 +2760,8 @@ var init_config = __esm({
2704
2760
  circuit_breaker_threshold: 3,
2705
2761
  require_doubt_before_verify: true,
2706
2762
  require_challenge_before_verify: false,
2707
- auto_baseline_on_new_experiment: true
2763
+ auto_baseline_on_new_experiment: true,
2764
+ require_human_verify: false
2708
2765
  },
2709
2766
  models: {}
2710
2767
  };
@@ -3641,7 +3698,7 @@ ${cmd.body}
3641
3698
  const existing = fs8.readFileSync(claudeMdPath, "utf-8");
3642
3699
  if (existing.includes("## Majlis Protocol")) {
3643
3700
  const replaced = existing.replace(
3644
- /## Majlis Protocol[\s\S]*?(?=\n## [^M]|\n## $|$)/,
3701
+ /## Majlis Protocol[\s\S]*?(?=\n## (?!#)(?!Majlis Protocol)|$)/,
3645
3702
  import_shared.CLAUDE_MD_SECTION.trim()
3646
3703
  );
3647
3704
  if (replaced !== existing) {
@@ -3738,6 +3795,36 @@ function clearGateRejection(db, experimentId) {
3738
3795
  UPDATE experiments SET gate_rejection_reason = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?
3739
3796
  `).run(experimentId);
3740
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
+ }
3741
3828
  function insertDecision(db, experimentId, description, evidenceLevel, justification) {
3742
3829
  const stmt = db.prepare(`
3743
3830
  INSERT INTO decisions (experiment_id, description, evidence_level, justification)
@@ -3985,6 +4072,59 @@ function updateSwarmMember(db, swarmRunId, slug, finalStatus, overallGrade, cost
3985
4072
  WHERE swarm_run_id = ? AND experiment_slug = ?
3986
4073
  `).run(finalStatus, overallGrade, costUsd, error2, swarmRunId, slug);
3987
4074
  }
4075
+ function insertNote(db, sessionId, experimentId, tag, content) {
4076
+ const stmt = db.prepare(`
4077
+ INSERT INTO notes (session_id, experiment_id, tag, content) VALUES (?, ?, ?, ?)
4078
+ `);
4079
+ const result = stmt.run(sessionId, experimentId, tag, content);
4080
+ return db.prepare("SELECT * FROM notes WHERE id = ?").get(result.lastInsertRowid);
4081
+ }
4082
+ function getNotesBySession(db, sessionId) {
4083
+ return db.prepare("SELECT * FROM notes WHERE session_id = ? ORDER BY created_at").all(sessionId);
4084
+ }
4085
+ function getNotesByExperiment(db, experimentId) {
4086
+ return db.prepare("SELECT * FROM notes WHERE experiment_id = ? ORDER BY created_at").all(experimentId);
4087
+ }
4088
+ function getRecentNotes(db, limit = 20) {
4089
+ return db.prepare("SELECT * FROM notes ORDER BY created_at DESC LIMIT ?").all(limit);
4090
+ }
4091
+ function insertJournalEntry(db, sessionId, content) {
4092
+ const stmt = db.prepare(`
4093
+ INSERT INTO journal_entries (session_id, content) VALUES (?, ?)
4094
+ `);
4095
+ const result = stmt.run(sessionId, content);
4096
+ return db.prepare("SELECT * FROM journal_entries WHERE id = ?").get(result.lastInsertRowid);
4097
+ }
4098
+ function getJournalBySession(db, sessionId) {
4099
+ return db.prepare("SELECT * FROM journal_entries WHERE session_id = ? ORDER BY created_at").all(sessionId);
4100
+ }
4101
+ function storeHypothesisFile(db, experimentId, filePath) {
4102
+ db.prepare(`
4103
+ UPDATE experiments SET hypothesis_file = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
4104
+ `).run(filePath, experimentId);
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
+ }
3988
4128
  function exportForCompressor(db, maxLength = 5e4) {
3989
4129
  const experiments = listAllExperiments(db);
3990
4130
  const sections = ["# Structured Data Export (from SQLite)\n"];
@@ -4043,6 +4183,28 @@ function exportForCompressor(db, maxLength = 5e4) {
4043
4183
  sections.push(`- [${d.severity}] ${d.claim_doubted} (exp: ${d.experiment_slug})`);
4044
4184
  }
4045
4185
  }
4186
+ const notes = db.prepare(`
4187
+ SELECT n.*, s.intent as session_intent FROM notes n
4188
+ LEFT JOIN sessions s ON n.session_id = s.id
4189
+ ORDER BY n.created_at
4190
+ `).all();
4191
+ if (notes.length > 0) {
4192
+ sections.push("\n## Pilot Notes (fold into narrative \u2014 these are human/pilot observations)");
4193
+ for (const n of notes) {
4194
+ sections.push(`- ${n.tag ? `[${n.tag}] ` : ""}${n.content}`);
4195
+ }
4196
+ }
4197
+ const journal2 = db.prepare(`
4198
+ SELECT j.*, s.intent as session_intent FROM journal_entries j
4199
+ LEFT JOIN sessions s ON j.session_id = s.id
4200
+ ORDER BY j.created_at
4201
+ `).all();
4202
+ if (journal2.length > 0) {
4203
+ sections.push("\n## Journal (fold into timeline \u2014 breadcrumbs from manual hacking sessions)");
4204
+ for (const j of journal2) {
4205
+ sections.push(`- [${j.created_at}] ${j.content}`);
4206
+ }
4207
+ }
4046
4208
  const full = sections.join("\n");
4047
4209
  if (full.length > maxLength) {
4048
4210
  return full.slice(0, maxLength) + `
@@ -4180,6 +4342,33 @@ function exportForDiagnostician(db, maxLength = 6e4) {
4180
4342
  sections.push(`- ${f.slug}: ${f.approach} (${f.source}) ${f.contradicts_current ? "[CONTRADICTS CURRENT]" : ""}`);
4181
4343
  }
4182
4344
  }
4345
+ const notes = db.prepare(`
4346
+ SELECT n.*, e.slug as exp_slug, s.intent as session_intent
4347
+ FROM notes n
4348
+ LEFT JOIN experiments e ON n.experiment_id = e.id
4349
+ LEFT JOIN sessions s ON n.session_id = s.id
4350
+ ORDER BY n.created_at
4351
+ `).all();
4352
+ if (notes.length > 0) {
4353
+ sections.push("\n## Pilot Notes");
4354
+ for (const n of notes) {
4355
+ const ctx = n.exp_slug ? ` (exp: ${n.exp_slug})` : n.session_intent ? ` (session: ${n.session_intent})` : "";
4356
+ sections.push(`- ${n.tag ? `[${n.tag}] ` : ""}${n.content}${ctx}`);
4357
+ }
4358
+ }
4359
+ const journal2 = db.prepare(`
4360
+ SELECT j.*, s.intent as session_intent
4361
+ FROM journal_entries j
4362
+ LEFT JOIN sessions s ON j.session_id = s.id
4363
+ ORDER BY j.created_at
4364
+ `).all();
4365
+ if (journal2.length > 0) {
4366
+ sections.push("\n## Journal Entries");
4367
+ for (const j of journal2) {
4368
+ const ctx = j.session_intent ? ` (session: ${j.session_intent})` : "";
4369
+ sections.push(`- [${j.created_at}] ${j.content}${ctx}`);
4370
+ }
4371
+ }
4183
4372
  const full = sections.join("\n");
4184
4373
  if (full.length > maxLength) {
4185
4374
  return full.slice(0, maxLength) + `
@@ -4269,6 +4458,32 @@ async function status(isJson) {
4269
4458
  ]);
4270
4459
  console.log(table(["Sub-Type", "Failures", "Status"], cbRows));
4271
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
+ }
4272
4487
  if (judgmentDecisions.length > 0) {
4273
4488
  console.log(`
4274
4489
  ${yellow(`${judgmentDecisions.length} judgment-level decisions`)} (provisional targets for doubt)`);
@@ -4358,7 +4573,8 @@ var init_types2 = __esm({
4358
4573
  revert: (current, target) => target === "dead_end" /* DEAD_END */ && !isTerminalStatus(current),
4359
4574
  circuit_breaker: (current, target) => target === "dead_end" /* DEAD_END */ && !isTerminalStatus(current),
4360
4575
  error_recovery: (current, target) => target === "dead_end" /* DEAD_END */ && !isTerminalStatus(current),
4361
- bootstrap: (current, target) => current === "classified" /* CLASSIFIED */ && target === "reframed" /* REFRAMED */
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)
4362
4578
  };
4363
4579
  }
4364
4580
  });
@@ -4697,6 +4913,8 @@ async function resolve2(db, exp, projectRoot) {
4697
4913
  incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
4698
4914
  }
4699
4915
  })();
4916
+ const weakened = cascadeChainInvalidation(db, exp.slug);
4917
+ if (weakened > 0) warn(`Weakened chain: ${weakened} downstream experiment(s) depend on ${exp.slug}.`);
4700
4918
  info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
4701
4919
  break;
4702
4920
  }
@@ -4833,6 +5051,8 @@ async function resolveDbOnly(db, exp, projectRoot) {
4833
5051
  incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
4834
5052
  }
4835
5053
  })();
5054
+ const weakened = cascadeChainInvalidation(db, exp.slug);
5055
+ if (weakened > 0) warn(`Weakened chain: ${weakened} downstream experiment(s) depend on ${exp.slug}.`);
4836
5056
  info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
4837
5057
  break;
4838
5058
  }
@@ -4958,6 +5178,31 @@ async function resolveCmd(args) {
4958
5178
  if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
4959
5179
  const db = getDb(root);
4960
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
+ }
4961
5206
  transition(exp.status, "resolved" /* RESOLVED */);
4962
5207
  await resolve2(db, exp, root);
4963
5208
  }
@@ -5014,6 +5259,7 @@ Check: (a) stale references \u2014 does the hypothesis reference specific lines,
5014
5259
  Output your gate_decision as "approve", "reject", or "flag" with reasoning.`
5015
5260
  }, root);
5016
5261
  ingestStructuredOutput(db, exp.id, result.structured);
5262
+ printStepSummary("gate", result.structured);
5017
5263
  const decision = result.structured?.gate_decision ?? "approve";
5018
5264
  const reason = result.structured?.reason ?? "";
5019
5265
  if (decision === "reject") {
@@ -5085,6 +5331,17 @@ Your experiment doc: ${expDocRelPath(exp)}`;
5085
5331
  if (lineage) {
5086
5332
  taskPrompt += "\n\n" + lineage;
5087
5333
  }
5334
+ const pilotNotes = loadPilotNotes(db, exp.id);
5335
+ if (pilotNotes) taskPrompt += pilotNotes;
5336
+ if (exp.hypothesis_file) {
5337
+ const hypoContent = readFileOrEmpty(path11.join(root, exp.hypothesis_file));
5338
+ if (hypoContent) {
5339
+ taskPrompt += `
5340
+
5341
+ ## Structured Hypothesis (from pilot)
5342
+ ${hypoContent.slice(0, 8e3)}`;
5343
+ }
5344
+ }
5088
5345
  const result = await spawnAgent("builder", {
5089
5346
  experiment: {
5090
5347
  id: exp.id,
@@ -5119,6 +5376,8 @@ Your experiment doc: ${expDocRelPath(exp)}`;
5119
5376
  );
5120
5377
  adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
5121
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}.`);
5122
5381
  info(`Builder abandoned ${exp.slug}: ${result.structured.abandon.reason}`);
5123
5382
  return;
5124
5383
  }
@@ -5189,6 +5448,8 @@ Error: ${errMsg.slice(0, 500)}`
5189
5448
  );
5190
5449
  adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
5191
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}.`);
5192
5453
  info(`Builder abandoned ${exp.slug} (recovered from truncation): ${recovery.data.abandon.reason}`);
5193
5454
  } else {
5194
5455
  const tail = result.output.slice(-2e3).trim();
@@ -5288,6 +5549,7 @@ ${gitDiff}
5288
5549
  taskPrompt
5289
5550
  }, root);
5290
5551
  ingestStructuredOutput(db, exp.id, result.structured);
5552
+ printStepSummary("challenge", result.structured);
5291
5553
  if (result.truncated && !result.structured) {
5292
5554
  warn(`Adversary was truncated without structured output. Experiment stays at current status.`);
5293
5555
  } else {
@@ -5329,6 +5591,7 @@ ${experimentDoc}
5329
5591
  taskPrompt
5330
5592
  }, root);
5331
5593
  ingestStructuredOutput(db, exp.id, result.structured);
5594
+ printStepSummary("doubt", result.structured);
5332
5595
  if (result.truncated && !result.structured) {
5333
5596
  warn(`Critic was truncated without structured output. Experiment stays at current status.`);
5334
5597
  } else {
@@ -5452,6 +5715,8 @@ async function doVerify(db, exp, root) {
5452
5715
  if (builderGuidanceForVerifier?.includes("[PROVENANCE WARNING]")) {
5453
5716
  verifierTaskPrompt += "\n\nNote: The builder's structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny.";
5454
5717
  }
5718
+ const verifierPilotNotes = loadPilotNotes(db, exp.id);
5719
+ if (verifierPilotNotes) verifierTaskPrompt += verifierPilotNotes;
5455
5720
  const result = await spawnAgent("verifier", {
5456
5721
  experiment: {
5457
5722
  id: exp.id,
@@ -5469,6 +5734,7 @@ async function doVerify(db, exp, root) {
5469
5734
  taskPrompt: verifierTaskPrompt
5470
5735
  }, root);
5471
5736
  ingestStructuredOutput(db, exp.id, result.structured);
5737
+ printStepSummary("verify", result.structured);
5472
5738
  if (result.truncated && !result.structured) {
5473
5739
  warn(`Verifier was truncated without structured output. Experiment stays at 'verifying'.`);
5474
5740
  return;
@@ -5562,6 +5828,20 @@ ${content.slice(0, 8e3)}
5562
5828
  }
5563
5829
  return sections.join("\n\n");
5564
5830
  }
5831
+ function loadPilotNotes(db, experimentId) {
5832
+ const expNotes = getNotesByExperiment(db, experimentId);
5833
+ const session2 = getActiveSession(db);
5834
+ const sessionNotes = session2 ? getNotesBySession(db, session2.id) : [];
5835
+ const seenIds = new Set(expNotes.map((n) => n.id));
5836
+ const allNotes = [...expNotes, ...sessionNotes.filter((n) => !seenIds.has(n.id))].slice(-10);
5837
+ if (allNotes.length === 0) return "";
5838
+ let section = "\n\n## Pilot Notes\n";
5839
+ for (const n of allNotes) {
5840
+ section += `- ${n.tag ? `[${n.tag}] ` : ""}${n.content}
5841
+ `;
5842
+ }
5843
+ return section;
5844
+ }
5565
5845
  function expDocRelPath(exp) {
5566
5846
  return `docs/experiments/${String(exp.id).padStart(3, "0")}-${exp.slug}.md`;
5567
5847
  }
@@ -5577,6 +5857,38 @@ function resolveExperimentArg(db, args) {
5577
5857
  }
5578
5858
  return exp;
5579
5859
  }
5860
+ function printStepSummary(step, structured) {
5861
+ if (!structured) return;
5862
+ if (step === "gate") {
5863
+ const decision = structured.gate_decision ?? "approve";
5864
+ const reason = structured.reason ?? "";
5865
+ header(`Gate: ${decision}`);
5866
+ if (reason) console.log(` ${reason.slice(0, 150)}`);
5867
+ }
5868
+ if (step === "doubt" && structured.doubts) {
5869
+ const critical = structured.doubts.filter((d) => d.severity === "critical").length;
5870
+ const moderate = structured.doubts.filter((d) => d.severity === "moderate").length;
5871
+ const minor = structured.doubts.filter((d) => d.severity === "minor").length;
5872
+ header(`Doubt Summary: ${structured.doubts.length} doubt(s) \u2014 ${critical} critical, ${moderate} moderate, ${minor} minor`);
5873
+ for (const d of structured.doubts.slice(0, 3)) {
5874
+ console.log(` [${d.severity}] ${d.claim_doubted.slice(0, 100)}`);
5875
+ }
5876
+ }
5877
+ if (step === "challenge" && structured.challenges) {
5878
+ header(`Challenge Summary: ${structured.challenges.length} challenge(s)`);
5879
+ for (const c of structured.challenges.slice(0, 3)) {
5880
+ console.log(` ${c.description.slice(0, 100)}`);
5881
+ }
5882
+ }
5883
+ if (step === "verify" && structured.grades) {
5884
+ const grades = structured.grades.map((g) => g.grade);
5885
+ const worst = ["rejected", "weak", "good", "sound"].find((g) => grades.includes(g)) ?? grades[0] ?? "unknown";
5886
+ header(`Verification Summary: overall=${worst}`);
5887
+ for (const g of structured.grades) {
5888
+ console.log(` ${g.component}: ${gradeColor(g.grade)}`);
5889
+ }
5890
+ }
5891
+ }
5580
5892
  function ingestStructuredOutput(db, experimentId, structured) {
5581
5893
  if (!structured) return;
5582
5894
  db.transaction(() => {
@@ -5824,6 +6136,8 @@ async function newExperiment(args) {
5824
6136
  const dependsOn = getFlagValue(args, "--depends-on") ?? null;
5825
6137
  const contextArg = getFlagValue(args, "--context") ?? null;
5826
6138
  const contextFiles = contextArg ? contextArg.split(",").map((f) => f.trim()) : null;
6139
+ const skipGate = args.includes("--skip-gate");
6140
+ const fromFile = getFlagValue(args, "--from-file") ?? null;
5827
6141
  if (dependsOn) {
5828
6142
  const depExp = getExperimentBySlug(db, dependsOn);
5829
6143
  if (!depExp) {
@@ -5841,7 +6155,18 @@ async function newExperiment(args) {
5841
6155
  const templatePath = path12.join(docsDir, "_TEMPLATE.md");
5842
6156
  if (fs12.existsSync(templatePath)) {
5843
6157
  const template = fs12.readFileSync(templatePath, "utf-8");
5844
- 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]);
6158
+ let 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]);
6159
+ if (fromFile) {
6160
+ const hypoPath = path12.join(root, fromFile);
6161
+ if (fs12.existsSync(hypoPath)) {
6162
+ const hypoContent = fs12.readFileSync(hypoPath, "utf-8");
6163
+ logContent += "\n\n## Structured Hypothesis (from pilot)\n\n" + hypoContent;
6164
+ storeHypothesisFile(db, exp.id, fromFile);
6165
+ info(`Hypothesis file: ${fromFile}`);
6166
+ } else {
6167
+ warn(`Hypothesis file not found: ${fromFile}`);
6168
+ }
6169
+ }
5845
6170
  const logPath = path12.join(root, docRelPath);
5846
6171
  fs12.writeFileSync(logPath, logContent);
5847
6172
  info(`Created experiment log: ${docRelPath}`);
@@ -5856,6 +6181,10 @@ async function newExperiment(args) {
5856
6181
  warn("Auto-baseline failed \u2014 run `majlis baseline` manually.");
5857
6182
  }
5858
6183
  }
6184
+ if (skipGate) {
6185
+ updateExperimentStatus(db, exp.id, "gated");
6186
+ success(`Gate skipped (pilot-verified). Run \`majlis build\` next.`);
6187
+ }
5859
6188
  }
5860
6189
  async function revert(args) {
5861
6190
  const root = findProjectRoot();
@@ -5979,6 +6308,8 @@ ${gitDiff.slice(0, 15e3)}
5979
6308
  if (exp.gate_rejection_reason) clearGateRejection(db, exp.id);
5980
6309
  adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "revert");
5981
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}.`);
5982
6313
  info(`Experiment ${exp.slug} reverted to dead-end.`);
5983
6314
  info(`Constraint: ${structuralConstraint.slice(0, 120)}${structuralConstraint.length > 120 ? "..." : ""}`);
5984
6315
  }
@@ -6349,6 +6680,50 @@ async function audit(args) {
6349
6680
  const root = findProjectRoot();
6350
6681
  if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
6351
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
+ }
6352
6727
  const objective = args.filter((a) => !a.startsWith("--")).join(" ");
6353
6728
  const config = loadConfig(root);
6354
6729
  const experiments = listAllExperiments(db);
@@ -6398,13 +6773,33 @@ Answer these questions:
6398
6773
  2. Is the current classification serving that objective? Or has the taxonomy become self-referential?
6399
6774
  3. What would we do differently if we started from scratch with what we now know?
6400
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.
6401
6779
 
6402
6780
  Output: either "classification confirmed \u2014 continue" or "re-classify from X" with a specific proposal.`;
6403
6781
  const result = await spawnAgent("builder", {
6404
6782
  synthesis,
6405
6783
  taskPrompt: auditPrompt
6406
6784
  }, root);
6407
- 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
+ }
6408
6803
  }
6409
6804
  var fs15, path15;
6410
6805
  var init_audit = __esm({
@@ -6414,8 +6809,12 @@ var init_audit = __esm({
6414
6809
  path15 = __toESM(require("path"));
6415
6810
  init_connection();
6416
6811
  init_queries();
6812
+ init_machine();
6813
+ init_types2();
6417
6814
  init_spawn();
6815
+ init_parse();
6418
6816
  init_config();
6817
+ init_git();
6419
6818
  init_format();
6420
6819
  }
6421
6820
  });
@@ -6473,6 +6872,8 @@ async function runNextStep(db, exp, config, root, isJson, overrideGate = false)
6473
6872
  );
6474
6873
  adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "circuit_breaker");
6475
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}.`);
6476
6877
  warn("Experiment dead-ended. Triggering Maqasid Check (purpose audit).");
6477
6878
  await audit([config.project?.objective ?? ""]);
6478
6879
  return;
@@ -6487,6 +6888,9 @@ async function runNextStep(db, exp, config, root, isJson, overrideGate = false)
6487
6888
  return;
6488
6889
  }
6489
6890
  }
6891
+ if (exp.chain_weakened_by) {
6892
+ warn(`Experiment ${exp.slug} depends on ${exp.chain_weakened_by} (dead-ended). Chain integrity weakened.`);
6893
+ }
6490
6894
  const sessionsSinceCompression = getSessionsSinceCompression(db);
6491
6895
  if (sessionsSinceCompression >= config.cycle.compression_interval) {
6492
6896
  warn(
@@ -6505,6 +6909,11 @@ async function runNextStep(db, exp, config, root, isJson, overrideGate = false)
6505
6909
  }));
6506
6910
  return;
6507
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
+ }
6508
6917
  info(`${exp.slug}: ${exp.status} \u2192 ${nextStep}`);
6509
6918
  await executeStep(nextStep, exp, root);
6510
6919
  }
@@ -6522,6 +6931,21 @@ async function runAutoLoop(db, exp, config, root, isJson) {
6522
6931
  info("Stopping auto mode. Use `majlis next --override-gate` or `majlis revert`.");
6523
6932
  break;
6524
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
+ }
6525
6949
  if (isTerminal(exp.status)) {
6526
6950
  success(`Experiment ${exp.slug} reached terminal state: ${exp.status}`);
6527
6951
  break;
@@ -6539,6 +6963,8 @@ async function runAutoLoop(db, exp, config, root, isJson) {
6539
6963
  );
6540
6964
  adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "circuit_breaker");
6541
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}.`);
6542
6968
  await audit([config.project?.objective ?? ""]);
6543
6969
  break;
6544
6970
  }
@@ -6547,6 +6973,10 @@ async function runAutoLoop(db, exp, config, root, isJson) {
6547
6973
  const expHasDoubts = hasDoubts(db, exp.id);
6548
6974
  const expHasChallenges = hasChallenges(db, exp.id);
6549
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
+ }
6550
6980
  info(`[${iteration}/${MAX_ITERATIONS}] ${exp.slug}: ${exp.status} \u2192 ${nextStep}`);
6551
6981
  await executeStep(nextStep, exp, root);
6552
6982
  }
@@ -6707,6 +7137,8 @@ async function run(args) {
6707
7137
  "revert"
6708
7138
  );
6709
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}.`);
6710
7142
  continue;
6711
7143
  }
6712
7144
  } catch (err) {
@@ -6725,6 +7157,8 @@ async function run(args) {
6725
7157
  );
6726
7158
  adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "error_recovery");
6727
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}.`);
6728
7162
  } catch (innerErr) {
6729
7163
  const innerMsg = innerErr instanceof Error ? innerErr.message : String(innerErr);
6730
7164
  warn(`Could not record dead-end: ${innerMsg}`);
@@ -8193,12 +8627,368 @@ var init_resync = __esm({
8193
8627
  }
8194
8628
  });
8195
8629
 
8630
+ // src/commands/note.ts
8631
+ var note_exports = {};
8632
+ __export(note_exports, {
8633
+ note: () => note
8634
+ });
8635
+ async function note(args) {
8636
+ const root = findProjectRoot();
8637
+ if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
8638
+ const db = getDb(root);
8639
+ const content = args.filter((a) => !a.startsWith("--")).join(" ");
8640
+ if (!content) {
8641
+ throw new Error(
8642
+ 'Usage: majlis note "text" [--tag <tag>] [--experiment <slug>]'
8643
+ );
8644
+ }
8645
+ const tag = getFlagValue(args, "--tag");
8646
+ const expSlug = getFlagValue(args, "--experiment");
8647
+ const session2 = getActiveSession(db);
8648
+ let experimentId = null;
8649
+ if (expSlug) {
8650
+ const exp = getExperimentBySlug(db, expSlug);
8651
+ if (!exp) throw new Error(`Experiment not found: ${expSlug}`);
8652
+ experimentId = exp.id;
8653
+ } else {
8654
+ const latest = getLatestExperiment(db);
8655
+ experimentId = latest?.id ?? null;
8656
+ }
8657
+ insertNote(db, session2?.id ?? null, experimentId, tag ?? null, content);
8658
+ success(`Note saved${tag ? ` [${tag}]` : ""}${expSlug ? ` \u2192 ${expSlug}` : ""}`);
8659
+ }
8660
+ var init_note = __esm({
8661
+ "src/commands/note.ts"() {
8662
+ "use strict";
8663
+ init_connection();
8664
+ init_queries();
8665
+ init_config();
8666
+ init_format();
8667
+ }
8668
+ });
8669
+
8670
+ // src/commands/journal.ts
8671
+ var journal_exports = {};
8672
+ __export(journal_exports, {
8673
+ journal: () => journal
8674
+ });
8675
+ async function journal(args) {
8676
+ const root = findProjectRoot();
8677
+ if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
8678
+ const db = getDb(root);
8679
+ const content = args.filter((a) => !a.startsWith("--")).join(" ");
8680
+ if (!content) {
8681
+ throw new Error('Usage: majlis journal "text"');
8682
+ }
8683
+ const session2 = getActiveSession(db);
8684
+ insertJournalEntry(db, session2?.id ?? null, content);
8685
+ success(`Journal entry saved (${(/* @__PURE__ */ new Date()).toLocaleTimeString()})`);
8686
+ }
8687
+ var init_journal = __esm({
8688
+ "src/commands/journal.ts"() {
8689
+ "use strict";
8690
+ init_connection();
8691
+ init_queries();
8692
+ init_format();
8693
+ }
8694
+ });
8695
+
8696
+ // src/commands/brief.ts
8697
+ var brief_exports = {};
8698
+ __export(brief_exports, {
8699
+ brief: () => brief
8700
+ });
8701
+ async function brief(args, isJson) {
8702
+ const root = findProjectRoot();
8703
+ if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
8704
+ const db = getDb(root);
8705
+ const plain = args.includes("--plain");
8706
+ const short = args.includes("--short");
8707
+ const exp = getLatestExperiment(db);
8708
+ const session2 = getActiveSession(db);
8709
+ const deadEnds = exp?.sub_type ? listStructuralDeadEndsBySubType(db, exp.sub_type) : listStructuralDeadEnds(db);
8710
+ const doubts = exp ? getDoubtsByExperiment(db, exp.id) : [];
8711
+ const challenges = exp ? getChallengesByExperiment(db, exp.id) : [];
8712
+ const verifications = exp ? getVerificationsByExperiment(db, exp.id) : [];
8713
+ const beforeMetrics = exp ? getMetricsByExperimentAndPhase(db, exp.id, "before") : [];
8714
+ const afterMetrics = exp ? getMetricsByExperimentAndPhase(db, exp.id, "after") : [];
8715
+ const notes = getRecentNotes(db, 5);
8716
+ if (isJson) {
8717
+ const data = {
8718
+ experiment: exp ? {
8719
+ slug: exp.slug,
8720
+ status: exp.status,
8721
+ hypothesis: exp.hypothesis,
8722
+ sub_type: exp.sub_type
8723
+ } : null,
8724
+ dead_ends: deadEnds.slice(0, 5).map((d) => ({
8725
+ id: d.id,
8726
+ structural_constraint: d.structural_constraint
8727
+ })),
8728
+ doubts: doubts.map((d) => ({
8729
+ claim: d.claim_doubted,
8730
+ severity: d.severity,
8731
+ resolution: d.resolution
8732
+ })),
8733
+ challenges: challenges.map((c) => ({
8734
+ description: c.description
8735
+ })),
8736
+ verifications: verifications.map((v) => ({
8737
+ component: v.component,
8738
+ grade: v.grade,
8739
+ notes: v.notes
8740
+ })),
8741
+ metrics: beforeMetrics.map((bm) => {
8742
+ const am = afterMetrics.find(
8743
+ (a) => a.fixture === bm.fixture && a.metric_name === bm.metric_name
8744
+ );
8745
+ return {
8746
+ fixture: bm.fixture,
8747
+ metric: bm.metric_name,
8748
+ before: bm.metric_value,
8749
+ after: am?.metric_value ?? null
8750
+ };
8751
+ }),
8752
+ notes: notes.map((n) => ({
8753
+ tag: n.tag,
8754
+ content: n.content
8755
+ })),
8756
+ session: session2 ? { intent: session2.intent } : null
8757
+ };
8758
+ console.log(JSON.stringify(data, null, 2));
8759
+ return;
8760
+ }
8761
+ const lines = [];
8762
+ lines.push(bold("[majlis] Context Brief"));
8763
+ lines.push("");
8764
+ if (exp) {
8765
+ lines.push(bold("Experiment"));
8766
+ lines.push(` Slug: ${cyan(exp.slug)}`);
8767
+ lines.push(` Status: ${statusColor(exp.status)}`);
8768
+ if (exp.hypothesis) lines.push(` Hypothesis: ${exp.hypothesis}`);
8769
+ if (exp.sub_type) lines.push(` Sub-type: ${dim(exp.sub_type)}`);
8770
+ } else {
8771
+ lines.push(dim("No active experiment."));
8772
+ }
8773
+ if (deadEnds.length > 0) {
8774
+ lines.push("");
8775
+ lines.push(bold("Dead Ends"));
8776
+ for (const d of deadEnds.slice(0, 5)) {
8777
+ lines.push(` - ${dim(`[DE-${d.id}]`)} ${d.structural_constraint}`);
8778
+ }
8779
+ if (deadEnds.length > 5) {
8780
+ lines.push(dim(` ... and ${deadEnds.length - 5} more`));
8781
+ }
8782
+ }
8783
+ if (exp && POST_DOUBT_STATES.has(exp.status)) {
8784
+ if (doubts.length > 0) {
8785
+ lines.push("");
8786
+ lines.push(bold("Doubts"));
8787
+ for (const d of doubts) {
8788
+ const res = d.resolution ? dim(` (${d.resolution})`) : yellow(" (pending)");
8789
+ lines.push(` - [${d.severity}] ${d.claim_doubted}${res}`);
8790
+ }
8791
+ }
8792
+ if (challenges.length > 0) {
8793
+ lines.push("");
8794
+ lines.push(bold("Challenges"));
8795
+ for (const c of challenges) {
8796
+ lines.push(` - ${c.description}`);
8797
+ }
8798
+ }
8799
+ if (verifications.length > 0) {
8800
+ lines.push("");
8801
+ lines.push(bold("Verifications"));
8802
+ for (const v of verifications) {
8803
+ const note2 = v.notes ? dim(` \u2014 ${v.notes}`) : "";
8804
+ lines.push(` - ${v.component}: ${gradeColor(v.grade)}${note2}`);
8805
+ }
8806
+ }
8807
+ }
8808
+ if (notes.length > 0) {
8809
+ lines.push("");
8810
+ lines.push(bold("Recent Notes"));
8811
+ for (const n of notes) {
8812
+ const tag = n.tag ? dim(`[${n.tag}] `) : "";
8813
+ lines.push(` - ${tag}${n.content}`);
8814
+ }
8815
+ }
8816
+ if (beforeMetrics.length > 0) {
8817
+ lines.push("");
8818
+ lines.push(bold("Metrics"));
8819
+ for (const bm of beforeMetrics) {
8820
+ const am = afterMetrics.find(
8821
+ (a) => a.fixture === bm.fixture && a.metric_name === bm.metric_name
8822
+ );
8823
+ if (am) {
8824
+ const delta = am.metric_value - bm.metric_value;
8825
+ const sign = delta >= 0 ? "+" : "";
8826
+ const color = delta >= 0 ? green : red;
8827
+ lines.push(` - ${bm.fixture}/${bm.metric_name}: ${bm.metric_value} -> ${am.metric_value} ${color(`(${sign}${delta.toFixed(4)})`)}`);
8828
+ } else {
8829
+ lines.push(` - ${bm.fixture}/${bm.metric_name}: ${bm.metric_value} ${dim("(no after)")}`);
8830
+ }
8831
+ }
8832
+ }
8833
+ if (session2) {
8834
+ lines.push("");
8835
+ lines.push(bold("Session"));
8836
+ lines.push(` Intent: ${session2.intent}`);
8837
+ }
8838
+ lines.push("");
8839
+ let output = lines.join("\n");
8840
+ if (plain) {
8841
+ output = stripAnsi(output);
8842
+ }
8843
+ if (short && output.length > 3e3) {
8844
+ output = output.slice(0, 2986) + "\n[TRUNCATED]";
8845
+ }
8846
+ process.stdout.write(output);
8847
+ }
8848
+ var POST_DOUBT_STATES;
8849
+ var init_brief = __esm({
8850
+ "src/commands/brief.ts"() {
8851
+ "use strict";
8852
+ init_connection();
8853
+ init_queries();
8854
+ init_format();
8855
+ POST_DOUBT_STATES = /* @__PURE__ */ new Set([
8856
+ "doubted",
8857
+ "challenged",
8858
+ "scouted",
8859
+ "verifying",
8860
+ "verified",
8861
+ "resolved",
8862
+ "compressed",
8863
+ "merged",
8864
+ "dead_end"
8865
+ ]);
8866
+ }
8867
+ });
8868
+
8869
+ // src/commands/catchup.ts
8870
+ var catchup_exports = {};
8871
+ __export(catchup_exports, {
8872
+ catchUp: () => catchUp
8873
+ });
8874
+ async function catchUp(args) {
8875
+ const root = findProjectRoot();
8876
+ if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
8877
+ const db = getDb(root);
8878
+ const description = args.filter((a) => !a.startsWith("--")).join(" ");
8879
+ if (!description) {
8880
+ throw new Error('Usage: majlis catch-up "description of what was done"');
8881
+ }
8882
+ const subType = getFlagValue(args, "--sub-type") ?? null;
8883
+ const diffRange = getFlagValue(args, "--diff");
8884
+ if (!diffRange) {
8885
+ throw new Error("--diff is required for catch-up. Example: --diff HEAD~3..HEAD or --diff main..my-branch");
8886
+ }
8887
+ let diffStat = "";
8888
+ try {
8889
+ diffStat = (0, import_node_child_process13.execFileSync)("git", ["diff", "--stat", diffRange], {
8890
+ cwd: root,
8891
+ encoding: "utf-8",
8892
+ stdio: ["pipe", "pipe", "pipe"]
8893
+ }).trim();
8894
+ } catch {
8895
+ warn(`Could not get diff stat for range ${diffRange}.`);
8896
+ }
8897
+ let slug = await generateSlug(description, root);
8898
+ let attempt = 0;
8899
+ while (getExperimentBySlug(db, slug + (attempt ? `-${attempt}` : ""))) {
8900
+ attempt++;
8901
+ }
8902
+ if (attempt > 0) slug = `${slug}-${attempt}`;
8903
+ const allExps = db.prepare("SELECT COUNT(*) as count FROM experiments").get();
8904
+ const num = allExps.count + 1;
8905
+ const exp = createExperiment(db, slug, "catch-up", description, subType, null, null, null);
8906
+ db.prepare("UPDATE experiments SET provenance = ? WHERE id = ?").run("catch-up", exp.id);
8907
+ let journalSection = "";
8908
+ const session2 = getActiveSession(db);
8909
+ if (session2) {
8910
+ const entries = getJournalBySession(db, session2.id);
8911
+ if (entries.length > 0) {
8912
+ journalSection = entries.map((e) => `- [${e.created_at}] ${e.content}`).join("\n");
8913
+ }
8914
+ }
8915
+ const docContent = [
8916
+ `# ${description}`,
8917
+ "",
8918
+ `- Branch: catch-up (retroactive)`,
8919
+ `- Status: built (catch-up)`,
8920
+ `- Sub-type: ${subType ?? "unclassified"}`,
8921
+ `- Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
8922
+ `- Provenance: catch-up`,
8923
+ "",
8924
+ "## Hypothesis",
8925
+ description,
8926
+ "",
8927
+ "## Approach",
8928
+ `Implemented manually (catch-up). Changes captured from \`${diffRange}\`.`,
8929
+ "",
8930
+ "## Files Changed",
8931
+ diffStat || "(no diff stat available)",
8932
+ "",
8933
+ "## Journal",
8934
+ journalSection || "(no journal entries)",
8935
+ ""
8936
+ ].join("\n");
8937
+ const docRelPath = `docs/experiments/${String(exp.id).padStart(3, "0")}-${slug}.md`;
8938
+ const docFullPath = path23.join(root, docRelPath);
8939
+ const docsDir = path23.dirname(docFullPath);
8940
+ if (!fs22.existsSync(docsDir)) {
8941
+ fs22.mkdirSync(docsDir, { recursive: true });
8942
+ }
8943
+ fs22.writeFileSync(docFullPath, docContent);
8944
+ autoCommit(root, `catch-up: ${slug}`);
8945
+ const config = loadConfig(root);
8946
+ if (config.metrics?.command) {
8947
+ try {
8948
+ const output = (0, import_node_child_process13.execSync)(config.metrics.command, {
8949
+ cwd: root,
8950
+ encoding: "utf-8",
8951
+ timeout: 6e4,
8952
+ stdio: ["pipe", "pipe", "pipe"]
8953
+ }).trim();
8954
+ const parsed = parseMetricsOutput(output);
8955
+ for (const m of parsed) {
8956
+ insertMetric(db, exp.id, "after", m.fixture, m.metric_name, m.metric_value);
8957
+ }
8958
+ if (parsed.length > 0) info(`Captured ${parsed.length} metric(s).`);
8959
+ } catch {
8960
+ warn("Could not capture metrics.");
8961
+ }
8962
+ }
8963
+ adminTransitionAndPersist(db, exp.id, "classified" /* CLASSIFIED */, "built" /* BUILT */, "bootstrap");
8964
+ success(`Catch-up experiment created: ${slug} (now at 'built')`);
8965
+ info(`Run \`majlis doubt\` when ready to evaluate.`);
8966
+ }
8967
+ var fs22, path23, import_node_child_process13;
8968
+ var init_catchup = __esm({
8969
+ "src/commands/catchup.ts"() {
8970
+ "use strict";
8971
+ fs22 = __toESM(require("fs"));
8972
+ path23 = __toESM(require("path"));
8973
+ import_node_child_process13 = require("child_process");
8974
+ init_connection();
8975
+ init_queries();
8976
+ init_machine();
8977
+ init_types2();
8978
+ init_config();
8979
+ init_spawn();
8980
+ init_metrics();
8981
+ init_git();
8982
+ init_format();
8983
+ }
8984
+ });
8985
+
8196
8986
  // src/cli.ts
8197
- var fs22 = __toESM(require("fs"));
8198
- var path23 = __toESM(require("path"));
8987
+ var fs23 = __toESM(require("fs"));
8988
+ var path24 = __toESM(require("path"));
8199
8989
  init_format();
8200
8990
  var VERSION2 = JSON.parse(
8201
- fs22.readFileSync(path23.join(__dirname, "..", "package.json"), "utf-8")
8991
+ fs23.readFileSync(path24.join(__dirname, "..", "package.json"), "utf-8")
8202
8992
  ).version;
8203
8993
  async function main() {
8204
8994
  let sigintCount = 0;
@@ -8339,6 +9129,26 @@ async function main() {
8339
9129
  await resync2(rest);
8340
9130
  break;
8341
9131
  }
9132
+ case "note": {
9133
+ const { note: note2 } = await Promise.resolve().then(() => (init_note(), note_exports));
9134
+ await note2(rest);
9135
+ break;
9136
+ }
9137
+ case "journal": {
9138
+ const { journal: journal2 } = await Promise.resolve().then(() => (init_journal(), journal_exports));
9139
+ await journal2(rest);
9140
+ break;
9141
+ }
9142
+ case "brief": {
9143
+ const { brief: brief2 } = await Promise.resolve().then(() => (init_brief(), brief_exports));
9144
+ await brief2(rest, isJson);
9145
+ break;
9146
+ }
9147
+ case "catch-up": {
9148
+ const { catchUp: catchUp2 } = await Promise.resolve().then(() => (init_catchup(), catchup_exports));
9149
+ await catchUp2(rest);
9150
+ break;
9151
+ }
8342
9152
  default:
8343
9153
  console.error(`Unknown command: ${command}`);
8344
9154
  printHelp();
@@ -8368,10 +9178,15 @@ Experiments:
8368
9178
  --sub-type TYPE Classify by problem sub-type
8369
9179
  --depends-on SLUG Block building until dependency is merged
8370
9180
  --context FILE,FILE Inject domain-specific docs into agent context
9181
+ --skip-gate Skip gatekeeper (pilot-verified hypothesis)
9182
+ --from-file FILE Inject structured hypothesis from markdown file
8371
9183
  baseline Capture metrics snapshot (before)
8372
9184
  measure Capture metrics snapshot (after)
8373
9185
  compare [--json] Compare before/after, detect regressions
8374
9186
  revert Revert experiment, log to dead-end
9187
+ catch-up "description" Create experiment retroactively from manual work
9188
+ --diff RANGE Git diff range (required, e.g. HEAD~3..HEAD)
9189
+ --sub-type TYPE Classify by problem sub-type
8375
9190
 
8376
9191
  Cycle:
8377
9192
  next [experiment] [--auto] Determine and execute next cycle step
@@ -8382,6 +9197,8 @@ Cycle:
8382
9197
  verify [experiment] Spawn verifier agent
8383
9198
  gate [experiment] Spawn gatekeeper agent
8384
9199
  resolve [experiment] Route based on verification grades
9200
+ --reject Force dead-end (human override)
9201
+ --reason "text" Reason for rejection (with --reject)
8385
9202
  compress Spawn compressor agent
8386
9203
 
8387
9204
  Classification:
@@ -8398,12 +9215,21 @@ Queries:
8398
9215
 
8399
9216
  Audit:
8400
9217
  audit "objective" Maqasid check \u2014 is the frame right?
9218
+ --accept Accept pending objective rewrite
9219
+ --reject Reject pending objective rewrite
8401
9220
  diagnose ["focus area"] Deep diagnosis \u2014 root causes, patterns, gaps
8402
9221
 
8403
9222
  Sessions:
8404
9223
  session start "intent" Declare session intent
8405
9224
  session end Log accomplished/unfinished/fragility
8406
9225
 
9226
+ Pilot:
9227
+ note "text" Save an observation to the DB
9228
+ --tag TAG Tag the note (hypothesis, code-pointer, etc.)
9229
+ --experiment SLUG Attach to a specific experiment
9230
+ journal "text" Timestamped breadcrumb during manual hacking
9231
+ brief [--plain] [--short] Pilot-friendly context dump for Claude Code
9232
+
8407
9233
  Orchestration:
8408
9234
  run "goal" Autonomous orchestration until goal met
8409
9235
  swarm "goal" [--parallel N] Run N experiments in parallel worktrees
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "majlis",
3
- "version": "0.8.4",
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"