majlis 0.8.4 → 0.9.0

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 +597 -8
  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,33 @@ 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'));
378
419
  `);
379
420
  }
380
421
  ];
@@ -1781,10 +1822,10 @@ Run \`majlis status\` for live experiment state and cycle position.
1781
1822
  }
1782
1823
  }, null, 2);
1783
1824
  }
1784
- var fs23 = __toESM2(require("fs"));
1825
+ var fs24 = __toESM2(require("fs"));
1785
1826
  function mkdirSafe3(dir) {
1786
- if (!fs23.existsSync(dir)) {
1787
- fs23.mkdirSync(dir, { recursive: true });
1827
+ if (!fs24.existsSync(dir)) {
1828
+ fs24.mkdirSync(dir, { recursive: true });
1788
1829
  }
1789
1830
  }
1790
1831
  function validateProject2(checks) {
@@ -3985,6 +4026,37 @@ function updateSwarmMember(db, swarmRunId, slug, finalStatus, overallGrade, cost
3985
4026
  WHERE swarm_run_id = ? AND experiment_slug = ?
3986
4027
  `).run(finalStatus, overallGrade, costUsd, error2, swarmRunId, slug);
3987
4028
  }
4029
+ function insertNote(db, sessionId, experimentId, tag, content) {
4030
+ const stmt = db.prepare(`
4031
+ INSERT INTO notes (session_id, experiment_id, tag, content) VALUES (?, ?, ?, ?)
4032
+ `);
4033
+ const result = stmt.run(sessionId, experimentId, tag, content);
4034
+ return db.prepare("SELECT * FROM notes WHERE id = ?").get(result.lastInsertRowid);
4035
+ }
4036
+ function getNotesBySession(db, sessionId) {
4037
+ return db.prepare("SELECT * FROM notes WHERE session_id = ? ORDER BY created_at").all(sessionId);
4038
+ }
4039
+ function getNotesByExperiment(db, experimentId) {
4040
+ return db.prepare("SELECT * FROM notes WHERE experiment_id = ? ORDER BY created_at").all(experimentId);
4041
+ }
4042
+ function getRecentNotes(db, limit = 20) {
4043
+ return db.prepare("SELECT * FROM notes ORDER BY created_at DESC LIMIT ?").all(limit);
4044
+ }
4045
+ function insertJournalEntry(db, sessionId, content) {
4046
+ const stmt = db.prepare(`
4047
+ INSERT INTO journal_entries (session_id, content) VALUES (?, ?)
4048
+ `);
4049
+ const result = stmt.run(sessionId, content);
4050
+ return db.prepare("SELECT * FROM journal_entries WHERE id = ?").get(result.lastInsertRowid);
4051
+ }
4052
+ function getJournalBySession(db, sessionId) {
4053
+ return db.prepare("SELECT * FROM journal_entries WHERE session_id = ? ORDER BY created_at").all(sessionId);
4054
+ }
4055
+ function storeHypothesisFile(db, experimentId, filePath) {
4056
+ db.prepare(`
4057
+ UPDATE experiments SET hypothesis_file = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
4058
+ `).run(filePath, experimentId);
4059
+ }
3988
4060
  function exportForCompressor(db, maxLength = 5e4) {
3989
4061
  const experiments = listAllExperiments(db);
3990
4062
  const sections = ["# Structured Data Export (from SQLite)\n"];
@@ -4043,6 +4115,28 @@ function exportForCompressor(db, maxLength = 5e4) {
4043
4115
  sections.push(`- [${d.severity}] ${d.claim_doubted} (exp: ${d.experiment_slug})`);
4044
4116
  }
4045
4117
  }
4118
+ const notes = db.prepare(`
4119
+ SELECT n.*, s.intent as session_intent FROM notes n
4120
+ LEFT JOIN sessions s ON n.session_id = s.id
4121
+ ORDER BY n.created_at
4122
+ `).all();
4123
+ if (notes.length > 0) {
4124
+ sections.push("\n## Pilot Notes (fold into narrative \u2014 these are human/pilot observations)");
4125
+ for (const n of notes) {
4126
+ sections.push(`- ${n.tag ? `[${n.tag}] ` : ""}${n.content}`);
4127
+ }
4128
+ }
4129
+ const journal2 = db.prepare(`
4130
+ SELECT j.*, s.intent as session_intent FROM journal_entries j
4131
+ LEFT JOIN sessions s ON j.session_id = s.id
4132
+ ORDER BY j.created_at
4133
+ `).all();
4134
+ if (journal2.length > 0) {
4135
+ sections.push("\n## Journal (fold into timeline \u2014 breadcrumbs from manual hacking sessions)");
4136
+ for (const j of journal2) {
4137
+ sections.push(`- [${j.created_at}] ${j.content}`);
4138
+ }
4139
+ }
4046
4140
  const full = sections.join("\n");
4047
4141
  if (full.length > maxLength) {
4048
4142
  return full.slice(0, maxLength) + `
@@ -4180,6 +4274,33 @@ function exportForDiagnostician(db, maxLength = 6e4) {
4180
4274
  sections.push(`- ${f.slug}: ${f.approach} (${f.source}) ${f.contradicts_current ? "[CONTRADICTS CURRENT]" : ""}`);
4181
4275
  }
4182
4276
  }
4277
+ const notes = db.prepare(`
4278
+ SELECT n.*, e.slug as exp_slug, s.intent as session_intent
4279
+ FROM notes n
4280
+ LEFT JOIN experiments e ON n.experiment_id = e.id
4281
+ LEFT JOIN sessions s ON n.session_id = s.id
4282
+ ORDER BY n.created_at
4283
+ `).all();
4284
+ if (notes.length > 0) {
4285
+ sections.push("\n## Pilot Notes");
4286
+ for (const n of notes) {
4287
+ const ctx = n.exp_slug ? ` (exp: ${n.exp_slug})` : n.session_intent ? ` (session: ${n.session_intent})` : "";
4288
+ sections.push(`- ${n.tag ? `[${n.tag}] ` : ""}${n.content}${ctx}`);
4289
+ }
4290
+ }
4291
+ const journal2 = db.prepare(`
4292
+ SELECT j.*, s.intent as session_intent
4293
+ FROM journal_entries j
4294
+ LEFT JOIN sessions s ON j.session_id = s.id
4295
+ ORDER BY j.created_at
4296
+ `).all();
4297
+ if (journal2.length > 0) {
4298
+ sections.push("\n## Journal Entries");
4299
+ for (const j of journal2) {
4300
+ const ctx = j.session_intent ? ` (session: ${j.session_intent})` : "";
4301
+ sections.push(`- [${j.created_at}] ${j.content}${ctx}`);
4302
+ }
4303
+ }
4183
4304
  const full = sections.join("\n");
4184
4305
  if (full.length > maxLength) {
4185
4306
  return full.slice(0, maxLength) + `
@@ -4358,7 +4479,7 @@ var init_types2 = __esm({
4358
4479
  revert: (current, target) => target === "dead_end" /* DEAD_END */ && !isTerminalStatus(current),
4359
4480
  circuit_breaker: (current, target) => target === "dead_end" /* DEAD_END */ && !isTerminalStatus(current),
4360
4481
  error_recovery: (current, target) => target === "dead_end" /* DEAD_END */ && !isTerminalStatus(current),
4361
- bootstrap: (current, target) => current === "classified" /* CLASSIFIED */ && target === "reframed" /* REFRAMED */
4482
+ bootstrap: (current, target) => current === "classified" /* CLASSIFIED */ && target === "reframed" /* REFRAMED */ || current === "classified" /* CLASSIFIED */ && target === "built" /* BUILT */
4362
4483
  };
4363
4484
  }
4364
4485
  });
@@ -5014,6 +5135,7 @@ Check: (a) stale references \u2014 does the hypothesis reference specific lines,
5014
5135
  Output your gate_decision as "approve", "reject", or "flag" with reasoning.`
5015
5136
  }, root);
5016
5137
  ingestStructuredOutput(db, exp.id, result.structured);
5138
+ printStepSummary("gate", result.structured);
5017
5139
  const decision = result.structured?.gate_decision ?? "approve";
5018
5140
  const reason = result.structured?.reason ?? "";
5019
5141
  if (decision === "reject") {
@@ -5085,6 +5207,17 @@ Your experiment doc: ${expDocRelPath(exp)}`;
5085
5207
  if (lineage) {
5086
5208
  taskPrompt += "\n\n" + lineage;
5087
5209
  }
5210
+ const pilotNotes = loadPilotNotes(db, exp.id);
5211
+ if (pilotNotes) taskPrompt += pilotNotes;
5212
+ if (exp.hypothesis_file) {
5213
+ const hypoContent = readFileOrEmpty(path11.join(root, exp.hypothesis_file));
5214
+ if (hypoContent) {
5215
+ taskPrompt += `
5216
+
5217
+ ## Structured Hypothesis (from pilot)
5218
+ ${hypoContent.slice(0, 8e3)}`;
5219
+ }
5220
+ }
5088
5221
  const result = await spawnAgent("builder", {
5089
5222
  experiment: {
5090
5223
  id: exp.id,
@@ -5288,6 +5421,7 @@ ${gitDiff}
5288
5421
  taskPrompt
5289
5422
  }, root);
5290
5423
  ingestStructuredOutput(db, exp.id, result.structured);
5424
+ printStepSummary("challenge", result.structured);
5291
5425
  if (result.truncated && !result.structured) {
5292
5426
  warn(`Adversary was truncated without structured output. Experiment stays at current status.`);
5293
5427
  } else {
@@ -5329,6 +5463,7 @@ ${experimentDoc}
5329
5463
  taskPrompt
5330
5464
  }, root);
5331
5465
  ingestStructuredOutput(db, exp.id, result.structured);
5466
+ printStepSummary("doubt", result.structured);
5332
5467
  if (result.truncated && !result.structured) {
5333
5468
  warn(`Critic was truncated without structured output. Experiment stays at current status.`);
5334
5469
  } else {
@@ -5452,6 +5587,8 @@ async function doVerify(db, exp, root) {
5452
5587
  if (builderGuidanceForVerifier?.includes("[PROVENANCE WARNING]")) {
5453
5588
  verifierTaskPrompt += "\n\nNote: The builder's structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny.";
5454
5589
  }
5590
+ const verifierPilotNotes = loadPilotNotes(db, exp.id);
5591
+ if (verifierPilotNotes) verifierTaskPrompt += verifierPilotNotes;
5455
5592
  const result = await spawnAgent("verifier", {
5456
5593
  experiment: {
5457
5594
  id: exp.id,
@@ -5469,6 +5606,7 @@ async function doVerify(db, exp, root) {
5469
5606
  taskPrompt: verifierTaskPrompt
5470
5607
  }, root);
5471
5608
  ingestStructuredOutput(db, exp.id, result.structured);
5609
+ printStepSummary("verify", result.structured);
5472
5610
  if (result.truncated && !result.structured) {
5473
5611
  warn(`Verifier was truncated without structured output. Experiment stays at 'verifying'.`);
5474
5612
  return;
@@ -5562,6 +5700,20 @@ ${content.slice(0, 8e3)}
5562
5700
  }
5563
5701
  return sections.join("\n\n");
5564
5702
  }
5703
+ function loadPilotNotes(db, experimentId) {
5704
+ const expNotes = getNotesByExperiment(db, experimentId);
5705
+ const session2 = getActiveSession(db);
5706
+ const sessionNotes = session2 ? getNotesBySession(db, session2.id) : [];
5707
+ const seenIds = new Set(expNotes.map((n) => n.id));
5708
+ const allNotes = [...expNotes, ...sessionNotes.filter((n) => !seenIds.has(n.id))].slice(-10);
5709
+ if (allNotes.length === 0) return "";
5710
+ let section = "\n\n## Pilot Notes\n";
5711
+ for (const n of allNotes) {
5712
+ section += `- ${n.tag ? `[${n.tag}] ` : ""}${n.content}
5713
+ `;
5714
+ }
5715
+ return section;
5716
+ }
5565
5717
  function expDocRelPath(exp) {
5566
5718
  return `docs/experiments/${String(exp.id).padStart(3, "0")}-${exp.slug}.md`;
5567
5719
  }
@@ -5577,6 +5729,38 @@ function resolveExperimentArg(db, args) {
5577
5729
  }
5578
5730
  return exp;
5579
5731
  }
5732
+ function printStepSummary(step, structured) {
5733
+ if (!structured) return;
5734
+ if (step === "gate") {
5735
+ const decision = structured.gate_decision ?? "approve";
5736
+ const reason = structured.reason ?? "";
5737
+ header(`Gate: ${decision}`);
5738
+ if (reason) console.log(` ${reason.slice(0, 150)}`);
5739
+ }
5740
+ if (step === "doubt" && structured.doubts) {
5741
+ const critical = structured.doubts.filter((d) => d.severity === "critical").length;
5742
+ const moderate = structured.doubts.filter((d) => d.severity === "moderate").length;
5743
+ const minor = structured.doubts.filter((d) => d.severity === "minor").length;
5744
+ header(`Doubt Summary: ${structured.doubts.length} doubt(s) \u2014 ${critical} critical, ${moderate} moderate, ${minor} minor`);
5745
+ for (const d of structured.doubts.slice(0, 3)) {
5746
+ console.log(` [${d.severity}] ${d.claim_doubted.slice(0, 100)}`);
5747
+ }
5748
+ }
5749
+ if (step === "challenge" && structured.challenges) {
5750
+ header(`Challenge Summary: ${structured.challenges.length} challenge(s)`);
5751
+ for (const c of structured.challenges.slice(0, 3)) {
5752
+ console.log(` ${c.description.slice(0, 100)}`);
5753
+ }
5754
+ }
5755
+ if (step === "verify" && structured.grades) {
5756
+ const grades = structured.grades.map((g) => g.grade);
5757
+ const worst = ["rejected", "weak", "good", "sound"].find((g) => grades.includes(g)) ?? grades[0] ?? "unknown";
5758
+ header(`Verification Summary: overall=${worst}`);
5759
+ for (const g of structured.grades) {
5760
+ console.log(` ${g.component}: ${gradeColor(g.grade)}`);
5761
+ }
5762
+ }
5763
+ }
5580
5764
  function ingestStructuredOutput(db, experimentId, structured) {
5581
5765
  if (!structured) return;
5582
5766
  db.transaction(() => {
@@ -5824,6 +6008,8 @@ async function newExperiment(args) {
5824
6008
  const dependsOn = getFlagValue(args, "--depends-on") ?? null;
5825
6009
  const contextArg = getFlagValue(args, "--context") ?? null;
5826
6010
  const contextFiles = contextArg ? contextArg.split(",").map((f) => f.trim()) : null;
6011
+ const skipGate = args.includes("--skip-gate");
6012
+ const fromFile = getFlagValue(args, "--from-file") ?? null;
5827
6013
  if (dependsOn) {
5828
6014
  const depExp = getExperimentBySlug(db, dependsOn);
5829
6015
  if (!depExp) {
@@ -5841,7 +6027,18 @@ async function newExperiment(args) {
5841
6027
  const templatePath = path12.join(docsDir, "_TEMPLATE.md");
5842
6028
  if (fs12.existsSync(templatePath)) {
5843
6029
  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]);
6030
+ 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]);
6031
+ if (fromFile) {
6032
+ const hypoPath = path12.join(root, fromFile);
6033
+ if (fs12.existsSync(hypoPath)) {
6034
+ const hypoContent = fs12.readFileSync(hypoPath, "utf-8");
6035
+ logContent += "\n\n## Structured Hypothesis (from pilot)\n\n" + hypoContent;
6036
+ storeHypothesisFile(db, exp.id, fromFile);
6037
+ info(`Hypothesis file: ${fromFile}`);
6038
+ } else {
6039
+ warn(`Hypothesis file not found: ${fromFile}`);
6040
+ }
6041
+ }
5845
6042
  const logPath = path12.join(root, docRelPath);
5846
6043
  fs12.writeFileSync(logPath, logContent);
5847
6044
  info(`Created experiment log: ${docRelPath}`);
@@ -5856,6 +6053,10 @@ async function newExperiment(args) {
5856
6053
  warn("Auto-baseline failed \u2014 run `majlis baseline` manually.");
5857
6054
  }
5858
6055
  }
6056
+ if (skipGate) {
6057
+ updateExperimentStatus(db, exp.id, "gated");
6058
+ success(`Gate skipped (pilot-verified). Run \`majlis build\` next.`);
6059
+ }
5859
6060
  }
5860
6061
  async function revert(args) {
5861
6062
  const root = findProjectRoot();
@@ -8193,12 +8394,368 @@ var init_resync = __esm({
8193
8394
  }
8194
8395
  });
8195
8396
 
8397
+ // src/commands/note.ts
8398
+ var note_exports = {};
8399
+ __export(note_exports, {
8400
+ note: () => note
8401
+ });
8402
+ async function note(args) {
8403
+ const root = findProjectRoot();
8404
+ if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
8405
+ const db = getDb(root);
8406
+ const content = args.filter((a) => !a.startsWith("--")).join(" ");
8407
+ if (!content) {
8408
+ throw new Error(
8409
+ 'Usage: majlis note "text" [--tag <tag>] [--experiment <slug>]'
8410
+ );
8411
+ }
8412
+ const tag = getFlagValue(args, "--tag");
8413
+ const expSlug = getFlagValue(args, "--experiment");
8414
+ const session2 = getActiveSession(db);
8415
+ let experimentId = null;
8416
+ if (expSlug) {
8417
+ const exp = getExperimentBySlug(db, expSlug);
8418
+ if (!exp) throw new Error(`Experiment not found: ${expSlug}`);
8419
+ experimentId = exp.id;
8420
+ } else {
8421
+ const latest = getLatestExperiment(db);
8422
+ experimentId = latest?.id ?? null;
8423
+ }
8424
+ insertNote(db, session2?.id ?? null, experimentId, tag ?? null, content);
8425
+ success(`Note saved${tag ? ` [${tag}]` : ""}${expSlug ? ` \u2192 ${expSlug}` : ""}`);
8426
+ }
8427
+ var init_note = __esm({
8428
+ "src/commands/note.ts"() {
8429
+ "use strict";
8430
+ init_connection();
8431
+ init_queries();
8432
+ init_config();
8433
+ init_format();
8434
+ }
8435
+ });
8436
+
8437
+ // src/commands/journal.ts
8438
+ var journal_exports = {};
8439
+ __export(journal_exports, {
8440
+ journal: () => journal
8441
+ });
8442
+ async function journal(args) {
8443
+ const root = findProjectRoot();
8444
+ if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
8445
+ const db = getDb(root);
8446
+ const content = args.filter((a) => !a.startsWith("--")).join(" ");
8447
+ if (!content) {
8448
+ throw new Error('Usage: majlis journal "text"');
8449
+ }
8450
+ const session2 = getActiveSession(db);
8451
+ insertJournalEntry(db, session2?.id ?? null, content);
8452
+ success(`Journal entry saved (${(/* @__PURE__ */ new Date()).toLocaleTimeString()})`);
8453
+ }
8454
+ var init_journal = __esm({
8455
+ "src/commands/journal.ts"() {
8456
+ "use strict";
8457
+ init_connection();
8458
+ init_queries();
8459
+ init_format();
8460
+ }
8461
+ });
8462
+
8463
+ // src/commands/brief.ts
8464
+ var brief_exports = {};
8465
+ __export(brief_exports, {
8466
+ brief: () => brief
8467
+ });
8468
+ async function brief(args, isJson) {
8469
+ const root = findProjectRoot();
8470
+ if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
8471
+ const db = getDb(root);
8472
+ const plain = args.includes("--plain");
8473
+ const short = args.includes("--short");
8474
+ const exp = getLatestExperiment(db);
8475
+ const session2 = getActiveSession(db);
8476
+ const deadEnds = exp?.sub_type ? listStructuralDeadEndsBySubType(db, exp.sub_type) : listStructuralDeadEnds(db);
8477
+ const doubts = exp ? getDoubtsByExperiment(db, exp.id) : [];
8478
+ const challenges = exp ? getChallengesByExperiment(db, exp.id) : [];
8479
+ const verifications = exp ? getVerificationsByExperiment(db, exp.id) : [];
8480
+ const beforeMetrics = exp ? getMetricsByExperimentAndPhase(db, exp.id, "before") : [];
8481
+ const afterMetrics = exp ? getMetricsByExperimentAndPhase(db, exp.id, "after") : [];
8482
+ const notes = getRecentNotes(db, 5);
8483
+ if (isJson) {
8484
+ const data = {
8485
+ experiment: exp ? {
8486
+ slug: exp.slug,
8487
+ status: exp.status,
8488
+ hypothesis: exp.hypothesis,
8489
+ sub_type: exp.sub_type
8490
+ } : null,
8491
+ dead_ends: deadEnds.slice(0, 5).map((d) => ({
8492
+ id: d.id,
8493
+ structural_constraint: d.structural_constraint
8494
+ })),
8495
+ doubts: doubts.map((d) => ({
8496
+ claim: d.claim_doubted,
8497
+ severity: d.severity,
8498
+ resolution: d.resolution
8499
+ })),
8500
+ challenges: challenges.map((c) => ({
8501
+ description: c.description
8502
+ })),
8503
+ verifications: verifications.map((v) => ({
8504
+ component: v.component,
8505
+ grade: v.grade,
8506
+ notes: v.notes
8507
+ })),
8508
+ metrics: beforeMetrics.map((bm) => {
8509
+ const am = afterMetrics.find(
8510
+ (a) => a.fixture === bm.fixture && a.metric_name === bm.metric_name
8511
+ );
8512
+ return {
8513
+ fixture: bm.fixture,
8514
+ metric: bm.metric_name,
8515
+ before: bm.metric_value,
8516
+ after: am?.metric_value ?? null
8517
+ };
8518
+ }),
8519
+ notes: notes.map((n) => ({
8520
+ tag: n.tag,
8521
+ content: n.content
8522
+ })),
8523
+ session: session2 ? { intent: session2.intent } : null
8524
+ };
8525
+ console.log(JSON.stringify(data, null, 2));
8526
+ return;
8527
+ }
8528
+ const lines = [];
8529
+ lines.push(bold("[majlis] Context Brief"));
8530
+ lines.push("");
8531
+ if (exp) {
8532
+ lines.push(bold("Experiment"));
8533
+ lines.push(` Slug: ${cyan(exp.slug)}`);
8534
+ lines.push(` Status: ${statusColor(exp.status)}`);
8535
+ if (exp.hypothesis) lines.push(` Hypothesis: ${exp.hypothesis}`);
8536
+ if (exp.sub_type) lines.push(` Sub-type: ${dim(exp.sub_type)}`);
8537
+ } else {
8538
+ lines.push(dim("No active experiment."));
8539
+ }
8540
+ if (deadEnds.length > 0) {
8541
+ lines.push("");
8542
+ lines.push(bold("Dead Ends"));
8543
+ for (const d of deadEnds.slice(0, 5)) {
8544
+ lines.push(` - ${dim(`[DE-${d.id}]`)} ${d.structural_constraint}`);
8545
+ }
8546
+ if (deadEnds.length > 5) {
8547
+ lines.push(dim(` ... and ${deadEnds.length - 5} more`));
8548
+ }
8549
+ }
8550
+ if (exp && POST_DOUBT_STATES.has(exp.status)) {
8551
+ if (doubts.length > 0) {
8552
+ lines.push("");
8553
+ lines.push(bold("Doubts"));
8554
+ for (const d of doubts) {
8555
+ const res = d.resolution ? dim(` (${d.resolution})`) : yellow(" (pending)");
8556
+ lines.push(` - [${d.severity}] ${d.claim_doubted}${res}`);
8557
+ }
8558
+ }
8559
+ if (challenges.length > 0) {
8560
+ lines.push("");
8561
+ lines.push(bold("Challenges"));
8562
+ for (const c of challenges) {
8563
+ lines.push(` - ${c.description}`);
8564
+ }
8565
+ }
8566
+ if (verifications.length > 0) {
8567
+ lines.push("");
8568
+ lines.push(bold("Verifications"));
8569
+ for (const v of verifications) {
8570
+ const note2 = v.notes ? dim(` \u2014 ${v.notes}`) : "";
8571
+ lines.push(` - ${v.component}: ${gradeColor(v.grade)}${note2}`);
8572
+ }
8573
+ }
8574
+ }
8575
+ if (notes.length > 0) {
8576
+ lines.push("");
8577
+ lines.push(bold("Recent Notes"));
8578
+ for (const n of notes) {
8579
+ const tag = n.tag ? dim(`[${n.tag}] `) : "";
8580
+ lines.push(` - ${tag}${n.content}`);
8581
+ }
8582
+ }
8583
+ if (beforeMetrics.length > 0) {
8584
+ lines.push("");
8585
+ lines.push(bold("Metrics"));
8586
+ for (const bm of beforeMetrics) {
8587
+ const am = afterMetrics.find(
8588
+ (a) => a.fixture === bm.fixture && a.metric_name === bm.metric_name
8589
+ );
8590
+ if (am) {
8591
+ const delta = am.metric_value - bm.metric_value;
8592
+ const sign = delta >= 0 ? "+" : "";
8593
+ const color = delta >= 0 ? green : red;
8594
+ lines.push(` - ${bm.fixture}/${bm.metric_name}: ${bm.metric_value} -> ${am.metric_value} ${color(`(${sign}${delta.toFixed(4)})`)}`);
8595
+ } else {
8596
+ lines.push(` - ${bm.fixture}/${bm.metric_name}: ${bm.metric_value} ${dim("(no after)")}`);
8597
+ }
8598
+ }
8599
+ }
8600
+ if (session2) {
8601
+ lines.push("");
8602
+ lines.push(bold("Session"));
8603
+ lines.push(` Intent: ${session2.intent}`);
8604
+ }
8605
+ lines.push("");
8606
+ let output = lines.join("\n");
8607
+ if (plain) {
8608
+ output = stripAnsi(output);
8609
+ }
8610
+ if (short && output.length > 3e3) {
8611
+ output = output.slice(0, 2986) + "\n[TRUNCATED]";
8612
+ }
8613
+ process.stdout.write(output);
8614
+ }
8615
+ var POST_DOUBT_STATES;
8616
+ var init_brief = __esm({
8617
+ "src/commands/brief.ts"() {
8618
+ "use strict";
8619
+ init_connection();
8620
+ init_queries();
8621
+ init_format();
8622
+ POST_DOUBT_STATES = /* @__PURE__ */ new Set([
8623
+ "doubted",
8624
+ "challenged",
8625
+ "scouted",
8626
+ "verifying",
8627
+ "verified",
8628
+ "resolved",
8629
+ "compressed",
8630
+ "merged",
8631
+ "dead_end"
8632
+ ]);
8633
+ }
8634
+ });
8635
+
8636
+ // src/commands/catchup.ts
8637
+ var catchup_exports = {};
8638
+ __export(catchup_exports, {
8639
+ catchUp: () => catchUp
8640
+ });
8641
+ async function catchUp(args) {
8642
+ const root = findProjectRoot();
8643
+ if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
8644
+ const db = getDb(root);
8645
+ const description = args.filter((a) => !a.startsWith("--")).join(" ");
8646
+ if (!description) {
8647
+ throw new Error('Usage: majlis catch-up "description of what was done"');
8648
+ }
8649
+ const subType = getFlagValue(args, "--sub-type") ?? null;
8650
+ const diffRange = getFlagValue(args, "--diff");
8651
+ if (!diffRange) {
8652
+ throw new Error("--diff is required for catch-up. Example: --diff HEAD~3..HEAD or --diff main..my-branch");
8653
+ }
8654
+ let diffStat = "";
8655
+ try {
8656
+ diffStat = (0, import_node_child_process13.execFileSync)("git", ["diff", "--stat", diffRange], {
8657
+ cwd: root,
8658
+ encoding: "utf-8",
8659
+ stdio: ["pipe", "pipe", "pipe"]
8660
+ }).trim();
8661
+ } catch {
8662
+ warn(`Could not get diff stat for range ${diffRange}.`);
8663
+ }
8664
+ let slug = await generateSlug(description, root);
8665
+ let attempt = 0;
8666
+ while (getExperimentBySlug(db, slug + (attempt ? `-${attempt}` : ""))) {
8667
+ attempt++;
8668
+ }
8669
+ if (attempt > 0) slug = `${slug}-${attempt}`;
8670
+ const allExps = db.prepare("SELECT COUNT(*) as count FROM experiments").get();
8671
+ const num = allExps.count + 1;
8672
+ const exp = createExperiment(db, slug, "catch-up", description, subType, null, null, null);
8673
+ db.prepare("UPDATE experiments SET provenance = ? WHERE id = ?").run("catch-up", exp.id);
8674
+ let journalSection = "";
8675
+ const session2 = getActiveSession(db);
8676
+ if (session2) {
8677
+ const entries = getJournalBySession(db, session2.id);
8678
+ if (entries.length > 0) {
8679
+ journalSection = entries.map((e) => `- [${e.created_at}] ${e.content}`).join("\n");
8680
+ }
8681
+ }
8682
+ const docContent = [
8683
+ `# ${description}`,
8684
+ "",
8685
+ `- Branch: catch-up (retroactive)`,
8686
+ `- Status: built (catch-up)`,
8687
+ `- Sub-type: ${subType ?? "unclassified"}`,
8688
+ `- Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
8689
+ `- Provenance: catch-up`,
8690
+ "",
8691
+ "## Hypothesis",
8692
+ description,
8693
+ "",
8694
+ "## Approach",
8695
+ `Implemented manually (catch-up). Changes captured from \`${diffRange}\`.`,
8696
+ "",
8697
+ "## Files Changed",
8698
+ diffStat || "(no diff stat available)",
8699
+ "",
8700
+ "## Journal",
8701
+ journalSection || "(no journal entries)",
8702
+ ""
8703
+ ].join("\n");
8704
+ const docRelPath = `docs/experiments/${String(exp.id).padStart(3, "0")}-${slug}.md`;
8705
+ const docFullPath = path23.join(root, docRelPath);
8706
+ const docsDir = path23.dirname(docFullPath);
8707
+ if (!fs22.existsSync(docsDir)) {
8708
+ fs22.mkdirSync(docsDir, { recursive: true });
8709
+ }
8710
+ fs22.writeFileSync(docFullPath, docContent);
8711
+ autoCommit(root, `catch-up: ${slug}`);
8712
+ const config = loadConfig(root);
8713
+ if (config.metrics?.command) {
8714
+ try {
8715
+ const output = (0, import_node_child_process13.execSync)(config.metrics.command, {
8716
+ cwd: root,
8717
+ encoding: "utf-8",
8718
+ timeout: 6e4,
8719
+ stdio: ["pipe", "pipe", "pipe"]
8720
+ }).trim();
8721
+ const parsed = parseMetricsOutput(output);
8722
+ for (const m of parsed) {
8723
+ insertMetric(db, exp.id, "after", m.fixture, m.metric_name, m.metric_value);
8724
+ }
8725
+ if (parsed.length > 0) info(`Captured ${parsed.length} metric(s).`);
8726
+ } catch {
8727
+ warn("Could not capture metrics.");
8728
+ }
8729
+ }
8730
+ adminTransitionAndPersist(db, exp.id, "classified" /* CLASSIFIED */, "built" /* BUILT */, "bootstrap");
8731
+ success(`Catch-up experiment created: ${slug} (now at 'built')`);
8732
+ info(`Run \`majlis doubt\` when ready to evaluate.`);
8733
+ }
8734
+ var fs22, path23, import_node_child_process13;
8735
+ var init_catchup = __esm({
8736
+ "src/commands/catchup.ts"() {
8737
+ "use strict";
8738
+ fs22 = __toESM(require("fs"));
8739
+ path23 = __toESM(require("path"));
8740
+ import_node_child_process13 = require("child_process");
8741
+ init_connection();
8742
+ init_queries();
8743
+ init_machine();
8744
+ init_types2();
8745
+ init_config();
8746
+ init_spawn();
8747
+ init_metrics();
8748
+ init_git();
8749
+ init_format();
8750
+ }
8751
+ });
8752
+
8196
8753
  // src/cli.ts
8197
- var fs22 = __toESM(require("fs"));
8198
- var path23 = __toESM(require("path"));
8754
+ var fs23 = __toESM(require("fs"));
8755
+ var path24 = __toESM(require("path"));
8199
8756
  init_format();
8200
8757
  var VERSION2 = JSON.parse(
8201
- fs22.readFileSync(path23.join(__dirname, "..", "package.json"), "utf-8")
8758
+ fs23.readFileSync(path24.join(__dirname, "..", "package.json"), "utf-8")
8202
8759
  ).version;
8203
8760
  async function main() {
8204
8761
  let sigintCount = 0;
@@ -8339,6 +8896,26 @@ async function main() {
8339
8896
  await resync2(rest);
8340
8897
  break;
8341
8898
  }
8899
+ case "note": {
8900
+ const { note: note2 } = await Promise.resolve().then(() => (init_note(), note_exports));
8901
+ await note2(rest);
8902
+ break;
8903
+ }
8904
+ case "journal": {
8905
+ const { journal: journal2 } = await Promise.resolve().then(() => (init_journal(), journal_exports));
8906
+ await journal2(rest);
8907
+ break;
8908
+ }
8909
+ case "brief": {
8910
+ const { brief: brief2 } = await Promise.resolve().then(() => (init_brief(), brief_exports));
8911
+ await brief2(rest, isJson);
8912
+ break;
8913
+ }
8914
+ case "catch-up": {
8915
+ const { catchUp: catchUp2 } = await Promise.resolve().then(() => (init_catchup(), catchup_exports));
8916
+ await catchUp2(rest);
8917
+ break;
8918
+ }
8342
8919
  default:
8343
8920
  console.error(`Unknown command: ${command}`);
8344
8921
  printHelp();
@@ -8368,10 +8945,15 @@ Experiments:
8368
8945
  --sub-type TYPE Classify by problem sub-type
8369
8946
  --depends-on SLUG Block building until dependency is merged
8370
8947
  --context FILE,FILE Inject domain-specific docs into agent context
8948
+ --skip-gate Skip gatekeeper (pilot-verified hypothesis)
8949
+ --from-file FILE Inject structured hypothesis from markdown file
8371
8950
  baseline Capture metrics snapshot (before)
8372
8951
  measure Capture metrics snapshot (after)
8373
8952
  compare [--json] Compare before/after, detect regressions
8374
8953
  revert Revert experiment, log to dead-end
8954
+ catch-up "description" Create experiment retroactively from manual work
8955
+ --diff RANGE Git diff range (required, e.g. HEAD~3..HEAD)
8956
+ --sub-type TYPE Classify by problem sub-type
8375
8957
 
8376
8958
  Cycle:
8377
8959
  next [experiment] [--auto] Determine and execute next cycle step
@@ -8404,6 +8986,13 @@ Sessions:
8404
8986
  session start "intent" Declare session intent
8405
8987
  session end Log accomplished/unfinished/fragility
8406
8988
 
8989
+ Pilot:
8990
+ note "text" Save an observation to the DB
8991
+ --tag TAG Tag the note (hypothesis, code-pointer, etc.)
8992
+ --experiment SLUG Attach to a specific experiment
8993
+ journal "text" Timestamped breadcrumb during manual hacking
8994
+ brief [--plain] [--short] Pilot-friendly context dump for Claude Code
8995
+
8407
8996
  Orchestration:
8408
8997
  run "goal" Autonomous orchestration until goal met
8409
8998
  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.0",
4
4
  "description": "Multi-agent workflow CLI for structured doubt, independent verification, and compressed knowledge",
5
5
  "bin": {
6
6
  "majlis": "./dist/cli.js"