majlis 0.7.3 → 0.8.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.
- package/dist/cli.js +1662 -1307
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -369,6 +369,12 @@ var init_migrations = __esm({
|
|
|
369
369
|
db.exec(`
|
|
370
370
|
ALTER TABLE experiments ADD COLUMN depends_on TEXT;
|
|
371
371
|
ALTER TABLE experiments ADD COLUMN context_files TEXT;
|
|
372
|
+
`);
|
|
373
|
+
},
|
|
374
|
+
// Migration 007: v6 → v7 — Gate rejection reason (pause instead of auto-kill)
|
|
375
|
+
(db) => {
|
|
376
|
+
db.exec(`
|
|
377
|
+
ALTER TABLE experiments ADD COLUMN gate_rejection_reason TEXT;
|
|
372
378
|
`);
|
|
373
379
|
}
|
|
374
380
|
];
|
|
@@ -512,7 +518,9 @@ You are the Builder. You write code, run experiments, and make technical decisio
|
|
|
512
518
|
Before building:
|
|
513
519
|
1. Read docs/synthesis/current.md for project state \u2014 this IS ground truth. Trust it.
|
|
514
520
|
2. Read the dead-ends provided in your context \u2014 these are structural constraints.
|
|
515
|
-
3. Read
|
|
521
|
+
3. Read your experiment doc \u2014 its path is in your taskPrompt. It already exists
|
|
522
|
+
(the framework created it from a template). Read it, then fill in the Approach
|
|
523
|
+
section before you start coding. Do NOT search for it with glob or ls.
|
|
516
524
|
|
|
517
525
|
The synthesis already contains the diagnosis. Do NOT re-diagnose. Do NOT run
|
|
518
526
|
exploratory scripts to "understand the problem." The classify/doubt/challenge
|
|
@@ -527,6 +535,17 @@ Do NOT read raw data files (fixtures/, ground truth JSON/STL). The synthesis
|
|
|
527
535
|
has the relevant facts. Reading raw data wastes turns re-deriving what the
|
|
528
536
|
doubt/challenge/verify cycle already established.
|
|
529
537
|
|
|
538
|
+
## Anti-patterns (DO NOT \u2014 these waste turns and produce zero value)
|
|
539
|
+
- Do NOT query SQLite or explore \`.majlis/\`. The framework manages its own state.
|
|
540
|
+
- Do NOT use \`ls\`, \`find\`, or broad globs (\`**/*\`) to discover project structure.
|
|
541
|
+
The synthesis has the architecture. Read the specific files named in your hypothesis.
|
|
542
|
+
- Do NOT pipe commands through \`head\`, \`tail\`, or \`| grep\`. The tools handle
|
|
543
|
+
output truncation automatically. Run the command directly.
|
|
544
|
+
- Do NOT create or run exploratory/diagnostic scripts (Python, shell, etc.).
|
|
545
|
+
Diagnosis is the diagnostician's job, not yours.
|
|
546
|
+
- Do NOT spend your reading turns on framework internals, CI config, or build
|
|
547
|
+
system files unless your hypothesis specifically targets them.
|
|
548
|
+
|
|
530
549
|
## The Rule: ONE Change, Then Document
|
|
531
550
|
|
|
532
551
|
You make ONE code change per cycle. Not two, not "one more quick fix." ONE.
|
|
@@ -584,8 +603,8 @@ If you are running low on turns, STOP coding and immediately:
|
|
|
584
603
|
The framework CANNOT recover your work if you get truncated without structured output.
|
|
585
604
|
An incomplete experiment doc with honest "did not finish" notes is infinitely better
|
|
586
605
|
than a truncated run with no output. Budget your turns: ~8 turns for reading,
|
|
587
|
-
~
|
|
588
|
-
turns, wrap up NOW regardless of where you are.
|
|
606
|
+
~20 turns for coding + build verification, ~10 turns for benchmark + documentation.
|
|
607
|
+
If you've used 40+ turns, wrap up NOW regardless of where you are.
|
|
589
608
|
|
|
590
609
|
You may NOT verify your own work or mark your own decisions as proven.
|
|
591
610
|
Output your decisions in structured format so they can be recorded in the database.
|
|
@@ -1096,7 +1115,80 @@ Produce a diagnostic report as markdown. At the end, include:
|
|
|
1096
1115
|
## Safety Reminders
|
|
1097
1116
|
- You are READ-ONLY for project code. Write ONLY to .majlis/scripts/.
|
|
1098
1117
|
- Focus on diagnosis, not fixing. Your value is insight, not implementation.
|
|
1099
|
-
- Trust the database export over docs/ files when they conflict
|
|
1118
|
+
- Trust the database export over docs/ files when they conflict.`,
|
|
1119
|
+
postmortem: `---
|
|
1120
|
+
name: postmortem
|
|
1121
|
+
model: opus
|
|
1122
|
+
tools: [Read, Glob, Grep]
|
|
1123
|
+
---
|
|
1124
|
+
You are the Post-Mortem Analyst. You analyze reverted or failed experiments and extract
|
|
1125
|
+
structural learnings that prevent future experiments from repeating the same mistakes.
|
|
1126
|
+
|
|
1127
|
+
You run automatically when an experiment is reverted. Your job is to produce a specific,
|
|
1128
|
+
falsifiable structural constraint that blocks future experiments from repeating the approach.
|
|
1129
|
+
|
|
1130
|
+
## What You Receive
|
|
1131
|
+
|
|
1132
|
+
- The experiment's hypothesis and metadata
|
|
1133
|
+
- Git diff of the experiment branch vs main (what was changed or attempted)
|
|
1134
|
+
- The user's reason for reverting (if provided) \u2014 use as a starting point, not the final answer
|
|
1135
|
+
- Related dead-ends from the registry
|
|
1136
|
+
- Synthesis and fragility docs
|
|
1137
|
+
- Optionally: artifact files (sweep results, build logs, etc.) pointed to by --context
|
|
1138
|
+
|
|
1139
|
+
## Your Process
|
|
1140
|
+
|
|
1141
|
+
1. **Read the context** \u2014 understand what was attempted and why it's being reverted.
|
|
1142
|
+
2. **Examine artifacts** \u2014 if --context files are provided, read them. If sweep results,
|
|
1143
|
+
build logs, or metric outputs exist in the working directory, find and read them.
|
|
1144
|
+
3. **Analyze the failure** \u2014 determine whether this is structural (approach provably wrong)
|
|
1145
|
+
or procedural (approach might work but was executed poorly or abandoned for other reasons).
|
|
1146
|
+
4. **Produce the constraint** \u2014 write a specific, falsifiable structural constraint.
|
|
1147
|
+
|
|
1148
|
+
## Constraint Quality
|
|
1149
|
+
|
|
1150
|
+
Good constraints are specific and block future repetition:
|
|
1151
|
+
- "L6 config space is null \u2014 13-eval Bayesian sweep found all 12 params insensitive (ls=1.27), score ceiling 0.67"
|
|
1152
|
+
- "Relaxing curvature split threshold in recursive_curvature_split causes false splits on pure-surface thin strips (seg_pct 95->72.5)"
|
|
1153
|
+
- "Torus topology prevents genus-0 assumption for manifold extraction"
|
|
1154
|
+
|
|
1155
|
+
Bad constraints are vague and useless:
|
|
1156
|
+
- "Didn't work"
|
|
1157
|
+
- "Manually reverted"
|
|
1158
|
+
- "Needs more investigation"
|
|
1159
|
+
|
|
1160
|
+
## Scope
|
|
1161
|
+
|
|
1162
|
+
The constraint should clearly state what class of approaches it applies to and what it
|
|
1163
|
+
does NOT apply to. For example:
|
|
1164
|
+
- "SCOPE: Applies to split threshold changes in Pass 2. Does NOT apply to post-Pass-1 merge operations."
|
|
1165
|
+
|
|
1166
|
+
## Output Format
|
|
1167
|
+
|
|
1168
|
+
Write a brief analysis (2-5 paragraphs), then output:
|
|
1169
|
+
|
|
1170
|
+
<!-- majlis-json
|
|
1171
|
+
{
|
|
1172
|
+
"postmortem": {
|
|
1173
|
+
"why_failed": "What was tried and why it failed \u2014 specific, evidence-based",
|
|
1174
|
+
"structural_constraint": "What this proves about the solution space \u2014 blocks future repeats. Include scope.",
|
|
1175
|
+
"category": "structural or procedural"
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
-->
|
|
1179
|
+
|
|
1180
|
+
Categories:
|
|
1181
|
+
- **structural** \u2014 the approach is provably wrong or the solution space is null. Future experiments
|
|
1182
|
+
that repeat this approach should be rejected by the gatekeeper.
|
|
1183
|
+
- **procedural** \u2014 the approach was abandoned for process reasons (e.g., time, priority change,
|
|
1184
|
+
execution error). The approach might still be valid if executed differently.
|
|
1185
|
+
|
|
1186
|
+
## Safety Reminders
|
|
1187
|
+
- You are READ-ONLY. Do not modify any files.
|
|
1188
|
+
- Focus on extracting the constraint, not on suggesting fixes.
|
|
1189
|
+
- Trust the evidence in the context over speculation.
|
|
1190
|
+
- If you cannot determine the structural constraint from the available context, say so explicitly
|
|
1191
|
+
and categorize as procedural.`
|
|
1100
1192
|
};
|
|
1101
1193
|
var SLASH_COMMANDS2 = {
|
|
1102
1194
|
classify: {
|
|
@@ -1771,6 +1863,8 @@ function getExtractionSchema(role) {
|
|
|
1771
1863
|
return '{"architecture": {"modules": ["string"], "entry_points": ["string"], "key_abstractions": ["string"], "dependency_graph": "string"}}';
|
|
1772
1864
|
case "toolsmith":
|
|
1773
1865
|
return '{"toolsmith": {"metrics_command": "string|null", "build_command": "string|null", "test_command": "string|null", "test_framework": "string|null", "pre_measure": "string|null", "post_measure": "string|null", "fixtures": {}, "tracked": {}, "verification_output": "string", "issues": ["string"]}}';
|
|
1866
|
+
case "postmortem":
|
|
1867
|
+
return '{"postmortem": {"why_failed": "string", "structural_constraint": "string", "category": "structural|procedural"}}';
|
|
1774
1868
|
default:
|
|
1775
1869
|
return EXTRACTION_SCHEMA;
|
|
1776
1870
|
}
|
|
@@ -1797,7 +1891,8 @@ var init_types = __esm({
|
|
|
1797
1891
|
compressor: ["compression_report"],
|
|
1798
1892
|
diagnostician: ["diagnosis"],
|
|
1799
1893
|
cartographer: ["architecture"],
|
|
1800
|
-
toolsmith: ["toolsmith"]
|
|
1894
|
+
toolsmith: ["toolsmith"],
|
|
1895
|
+
postmortem: ["postmortem"]
|
|
1801
1896
|
};
|
|
1802
1897
|
}
|
|
1803
1898
|
});
|
|
@@ -1896,6 +1991,18 @@ function extractViaPatterns(role, markdown) {
|
|
|
1896
1991
|
});
|
|
1897
1992
|
}
|
|
1898
1993
|
if (doubts.length > 0) result.doubts = doubts;
|
|
1994
|
+
if (role === "postmortem") {
|
|
1995
|
+
const pmPattern = /(?:WHY\s*FAILED|Why\s*Failed|Failure)\s*[:=]\s*(.+?)(?:\n|$)[\s\S]*?(?:STRUCTURAL\s*CONSTRAINT|Structural\s*Constraint|Constraint)\s*[:=]\s*(.+?)(?:\n|$)/im;
|
|
1996
|
+
const pmMatch = markdown.match(pmPattern);
|
|
1997
|
+
if (pmMatch) {
|
|
1998
|
+
const categoryMatch = markdown.match(/(?:CATEGORY|Category)\s*[:=]\s*(structural|procedural)/im);
|
|
1999
|
+
result.postmortem = {
|
|
2000
|
+
why_failed: pmMatch[1].trim(),
|
|
2001
|
+
structural_constraint: pmMatch[2].trim(),
|
|
2002
|
+
category: categoryMatch?.[1]?.toLowerCase() ?? "procedural"
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
1899
2006
|
if (role === "builder") {
|
|
1900
2007
|
const abandonPattern = /\[ABANDON\]\s*(.+?)(?:\n|$)[\s\S]*?(?:structural.?constraint|Constraint|CONSTRAINT)\s*[:=]\s*(.+?)(?:\n|$)/im;
|
|
1901
2008
|
const abandonMatch = markdown.match(abandonPattern);
|
|
@@ -1952,7 +2059,7 @@ ${truncated}`;
|
|
|
1952
2059
|
}
|
|
1953
2060
|
}
|
|
1954
2061
|
function hasData(output) {
|
|
1955
|
-
return !!(output.decisions && output.decisions.length > 0 || output.grades && output.grades.length > 0 || output.doubts && output.doubts.length > 0 || output.challenges && output.challenges.length > 0 || output.findings && output.findings.length > 0 || output.guidance || output.reframe || output.compression_report || output.gate_decision || output.diagnosis || output.abandon);
|
|
2062
|
+
return !!(output.decisions && output.decisions.length > 0 || output.grades && output.grades.length > 0 || output.doubts && output.doubts.length > 0 || output.challenges && output.challenges.length > 0 || output.findings && output.findings.length > 0 || output.guidance || output.reframe || output.compression_report || output.gate_decision || output.diagnosis || output.abandon || output.postmortem);
|
|
1956
2063
|
}
|
|
1957
2064
|
function validateForRole(role, output) {
|
|
1958
2065
|
const required = ROLE_REQUIRED_FIELDS[role];
|
|
@@ -1998,12 +2105,27 @@ function buildCheckpointMessage(role, toolUseCount, maxTurns) {
|
|
|
1998
2105
|
const approxTurn = Math.round(toolUseCount / 2);
|
|
1999
2106
|
const header2 = `[MAJLIS CHECKPOINT \u2014 ~${approxTurn} of ${maxTurns} turns used]`;
|
|
2000
2107
|
switch (role) {
|
|
2001
|
-
case "builder":
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
-
|
|
2108
|
+
case "builder": {
|
|
2109
|
+
if (toolUseCount <= 15) {
|
|
2110
|
+
return `${header2}
|
|
2111
|
+
You should be done reading by now.
|
|
2112
|
+
- Your experiment doc path is in the taskPrompt. Do NOT search for it.
|
|
2113
|
+
- Do NOT query SQLite, explore .majlis/, or glob broadly.
|
|
2114
|
+
- If you haven't started coding, START NOW.`;
|
|
2115
|
+
} else if (toolUseCount <= 40) {
|
|
2116
|
+
return `${header2}
|
|
2117
|
+
ONE code change per cycle.
|
|
2118
|
+
- Have you run the build/benchmark? YES \u2192 document results + output JSON + STOP.
|
|
2119
|
+
- If NO \u2192 finish your change and run it now.
|
|
2006
2120
|
Do NOT start a second change or investigate unrelated failures.`;
|
|
2121
|
+
} else {
|
|
2122
|
+
return `${header2}
|
|
2123
|
+
URGENT: You are running out of turns.
|
|
2124
|
+
1. Update the experiment doc with whatever results you have.
|
|
2125
|
+
2. Output the <!-- majlis-json --> block NOW.
|
|
2126
|
+
The framework CANNOT recover your work without structured output.`;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2007
2129
|
case "verifier":
|
|
2008
2130
|
return `${header2}
|
|
2009
2131
|
AT MOST 3 diagnostic scripts total.
|
|
@@ -2046,6 +2168,11 @@ If you are past turn 30, begin writing current.md and fragility.md NOW.`;
|
|
|
2046
2168
|
You write ONLY to .majlis/scripts/. Verify toolchain, create metrics wrapper.
|
|
2047
2169
|
Phase 1 (1-10): verify toolchain. Phase 2 (11-25): create metrics.sh. Phase 3 (26-30): output config JSON.
|
|
2048
2170
|
If you are past turn 25, output your structured JSON NOW.`;
|
|
2171
|
+
case "postmortem":
|
|
2172
|
+
return `${header2}
|
|
2173
|
+
You are READ-ONLY. Focus on the structural constraint.
|
|
2174
|
+
What does this experiment prove about the solution space?
|
|
2175
|
+
If you have enough context, output your postmortem JSON NOW.`;
|
|
2049
2176
|
default:
|
|
2050
2177
|
return `${header2}
|
|
2051
2178
|
Check: is your core task done? If yes, wrap up and output JSON.`;
|
|
@@ -2154,6 +2281,18 @@ function buildPreToolUseGuards(role, cwd) {
|
|
|
2154
2281
|
{ matcher: "Bash", hooks: [bashGuard] }
|
|
2155
2282
|
];
|
|
2156
2283
|
}
|
|
2284
|
+
if (role === "postmortem") {
|
|
2285
|
+
const blockWrite = async () => {
|
|
2286
|
+
return {
|
|
2287
|
+
decision: "block",
|
|
2288
|
+
reason: "Post-mortem agent is read-only. No file modifications allowed."
|
|
2289
|
+
};
|
|
2290
|
+
};
|
|
2291
|
+
return [
|
|
2292
|
+
{ matcher: "Write", hooks: [blockWrite] },
|
|
2293
|
+
{ matcher: "Edit", hooks: [blockWrite] }
|
|
2294
|
+
];
|
|
2295
|
+
}
|
|
2157
2296
|
if (role === "builder") {
|
|
2158
2297
|
const bashGuard = async (input) => {
|
|
2159
2298
|
const toolInput = input.tool_input ?? {};
|
|
@@ -2476,10 +2615,11 @@ var init_spawn = __esm({
|
|
|
2476
2615
|
gatekeeper: 10,
|
|
2477
2616
|
diagnostician: 60,
|
|
2478
2617
|
cartographer: 40,
|
|
2479
|
-
toolsmith: 30
|
|
2618
|
+
toolsmith: 30,
|
|
2619
|
+
postmortem: 20
|
|
2480
2620
|
};
|
|
2481
2621
|
CHECKPOINT_INTERVAL = {
|
|
2482
|
-
builder:
|
|
2622
|
+
builder: 12,
|
|
2483
2623
|
verifier: 12,
|
|
2484
2624
|
critic: 15,
|
|
2485
2625
|
adversary: 15,
|
|
@@ -2487,7 +2627,8 @@ var init_spawn = __esm({
|
|
|
2487
2627
|
gatekeeper: 2,
|
|
2488
2628
|
diagnostician: 20,
|
|
2489
2629
|
cartographer: 12,
|
|
2490
|
-
toolsmith: 10
|
|
2630
|
+
toolsmith: 10,
|
|
2631
|
+
postmortem: 8
|
|
2491
2632
|
};
|
|
2492
2633
|
}
|
|
2493
2634
|
});
|
|
@@ -3323,6 +3464,56 @@ function autoCommit(root, message) {
|
|
|
3323
3464
|
} catch {
|
|
3324
3465
|
}
|
|
3325
3466
|
}
|
|
3467
|
+
function handleDeadEndGit(exp, root) {
|
|
3468
|
+
try {
|
|
3469
|
+
const currentBranch = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
3470
|
+
cwd: root,
|
|
3471
|
+
encoding: "utf-8"
|
|
3472
|
+
}).trim();
|
|
3473
|
+
if (currentBranch !== exp.branch) return;
|
|
3474
|
+
} catch {
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
try {
|
|
3478
|
+
(0, import_node_child_process2.execSync)('git add -A -- ":!.majlis/"', {
|
|
3479
|
+
cwd: root,
|
|
3480
|
+
encoding: "utf-8",
|
|
3481
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3482
|
+
});
|
|
3483
|
+
const diff = (0, import_node_child_process2.execSync)("git diff --cached --stat", {
|
|
3484
|
+
cwd: root,
|
|
3485
|
+
encoding: "utf-8",
|
|
3486
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3487
|
+
}).trim();
|
|
3488
|
+
if (diff) {
|
|
3489
|
+
const msg = `EXP-${String(exp.id).padStart(3, "0")}: ${exp.slug} [dead-end]`;
|
|
3490
|
+
(0, import_node_child_process2.execFileSync)("git", ["commit", "-m", msg], {
|
|
3491
|
+
cwd: root,
|
|
3492
|
+
encoding: "utf-8",
|
|
3493
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3494
|
+
});
|
|
3495
|
+
info(`Committed builder changes on ${exp.branch} before dead-end.`);
|
|
3496
|
+
}
|
|
3497
|
+
} catch {
|
|
3498
|
+
}
|
|
3499
|
+
try {
|
|
3500
|
+
(0, import_node_child_process2.execFileSync)("git", ["checkout", "main"], {
|
|
3501
|
+
cwd: root,
|
|
3502
|
+
encoding: "utf-8",
|
|
3503
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3504
|
+
});
|
|
3505
|
+
} catch {
|
|
3506
|
+
try {
|
|
3507
|
+
(0, import_node_child_process2.execFileSync)("git", ["checkout", "master"], {
|
|
3508
|
+
cwd: root,
|
|
3509
|
+
encoding: "utf-8",
|
|
3510
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3511
|
+
});
|
|
3512
|
+
} catch {
|
|
3513
|
+
warn(`Could not switch away from ${exp.branch} \u2014 do this manually.`);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3326
3517
|
var import_node_child_process2;
|
|
3327
3518
|
var init_git = __esm({
|
|
3328
3519
|
"src/git.ts"() {
|
|
@@ -3532,6 +3723,16 @@ function getBuilderGuidance(db, experimentId) {
|
|
|
3532
3723
|
const row = db.prepare("SELECT builder_guidance FROM experiments WHERE id = ?").get(experimentId);
|
|
3533
3724
|
return row?.builder_guidance ?? null;
|
|
3534
3725
|
}
|
|
3726
|
+
function storeGateRejection(db, experimentId, reason) {
|
|
3727
|
+
db.prepare(`
|
|
3728
|
+
UPDATE experiments SET gate_rejection_reason = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
|
3729
|
+
`).run(reason, experimentId);
|
|
3730
|
+
}
|
|
3731
|
+
function clearGateRejection(db, experimentId) {
|
|
3732
|
+
db.prepare(`
|
|
3733
|
+
UPDATE experiments SET gate_rejection_reason = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
|
3734
|
+
`).run(experimentId);
|
|
3735
|
+
}
|
|
3535
3736
|
function insertDecision(db, experimentId, description, evidenceLevel, justification) {
|
|
3536
3737
|
const stmt = db.prepare(`
|
|
3537
3738
|
INSERT INTO decisions (experiment_id, description, evidence_level, justification)
|
|
@@ -4215,7 +4416,7 @@ function determineNextStep(exp, valid, hasDoubts2, hasChallenges2) {
|
|
|
4215
4416
|
}
|
|
4216
4417
|
}
|
|
4217
4418
|
if (status2 === "building" /* BUILDING */) {
|
|
4218
|
-
return
|
|
4419
|
+
return "building" /* BUILDING */;
|
|
4219
4420
|
}
|
|
4220
4421
|
if (status2 === "scouted" /* SCOUTED */) {
|
|
4221
4422
|
return valid.includes("verifying" /* VERIFYING */) ? "verifying" /* VERIFYING */ : valid[0];
|
|
@@ -4223,6 +4424,9 @@ function determineNextStep(exp, valid, hasDoubts2, hasChallenges2) {
|
|
|
4223
4424
|
if (status2 === "verified" /* VERIFIED */) {
|
|
4224
4425
|
return valid.includes("resolved" /* RESOLVED */) ? "resolved" /* RESOLVED */ : valid[0];
|
|
4225
4426
|
}
|
|
4427
|
+
if (status2 === "resolved" /* RESOLVED */) {
|
|
4428
|
+
return valid.includes("compressed" /* COMPRESSED */) ? "compressed" /* COMPRESSED */ : valid[0];
|
|
4429
|
+
}
|
|
4226
4430
|
if (status2 === "compressed" /* COMPRESSED */) {
|
|
4227
4431
|
return valid.includes("merged" /* MERGED */) ? "merged" /* MERGED */ : valid[0];
|
|
4228
4432
|
}
|
|
@@ -4307,869 +4511,701 @@ var init_metrics = __esm({
|
|
|
4307
4511
|
}
|
|
4308
4512
|
});
|
|
4309
4513
|
|
|
4310
|
-
// src/
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
compare: () => compare,
|
|
4315
|
-
measure: () => measure
|
|
4316
|
-
});
|
|
4317
|
-
async function baseline(args) {
|
|
4318
|
-
await captureMetrics("before", args);
|
|
4319
|
-
}
|
|
4320
|
-
async function measure(args) {
|
|
4321
|
-
await captureMetrics("after", args);
|
|
4322
|
-
}
|
|
4323
|
-
async function captureMetrics(phase, args) {
|
|
4324
|
-
const root = findProjectRoot();
|
|
4325
|
-
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
4326
|
-
const db = getDb(root);
|
|
4327
|
-
const config = loadConfig(root);
|
|
4328
|
-
const expIdStr = getFlagValue(args, "--experiment");
|
|
4329
|
-
let exp;
|
|
4330
|
-
if (expIdStr !== void 0) {
|
|
4331
|
-
exp = getExperimentById(db, Number(expIdStr));
|
|
4332
|
-
} else {
|
|
4333
|
-
exp = getLatestExperiment(db);
|
|
4334
|
-
}
|
|
4335
|
-
if (!exp) throw new Error('No active experiment. Run `majlis new "hypothesis"` first.');
|
|
4336
|
-
if (config.build.pre_measure) {
|
|
4337
|
-
info(`Running pre-measure: ${config.build.pre_measure}`);
|
|
4338
|
-
try {
|
|
4339
|
-
(0, import_node_child_process4.execSync)(config.build.pre_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
|
|
4340
|
-
} catch {
|
|
4341
|
-
warn("Pre-measure command failed \u2014 continuing anyway.");
|
|
4342
|
-
}
|
|
4514
|
+
// src/resolve.ts
|
|
4515
|
+
function worstGrade(grades) {
|
|
4516
|
+
if (grades.length === 0) {
|
|
4517
|
+
throw new Error("Cannot determine grade from empty verification set \u2014 this indicates a data integrity issue");
|
|
4343
4518
|
}
|
|
4344
|
-
|
|
4345
|
-
|
|
4519
|
+
for (const grade of GRADE_ORDER) {
|
|
4520
|
+
if (grades.some((g) => g.grade === grade)) return grade;
|
|
4346
4521
|
}
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4522
|
+
return "sound";
|
|
4523
|
+
}
|
|
4524
|
+
async function resolve2(db, exp, projectRoot) {
|
|
4525
|
+
let grades = getVerificationsByExperiment(db, exp.id);
|
|
4526
|
+
if (grades.length === 0) {
|
|
4527
|
+
warn(`No verification records for ${exp.slug}. Defaulting to weak.`);
|
|
4528
|
+
insertVerification(
|
|
4529
|
+
db,
|
|
4530
|
+
exp.id,
|
|
4531
|
+
"auto-default",
|
|
4532
|
+
"weak",
|
|
4533
|
+
null,
|
|
4534
|
+
null,
|
|
4535
|
+
"No structured verification output. Auto-defaulted to weak."
|
|
4536
|
+
);
|
|
4537
|
+
grades = getVerificationsByExperiment(db, exp.id);
|
|
4357
4538
|
}
|
|
4358
|
-
const
|
|
4359
|
-
|
|
4360
|
-
|
|
4539
|
+
const overallGrade = worstGrade(grades);
|
|
4540
|
+
const config = loadConfig(projectRoot);
|
|
4541
|
+
const metricComparisons = compareMetrics(db, exp.id, config);
|
|
4542
|
+
const gateViolations = checkGateViolations(metricComparisons);
|
|
4543
|
+
if (gateViolations.length > 0 && (overallGrade === "sound" || overallGrade === "good")) {
|
|
4544
|
+
warn("Gate fixture regression detected \u2014 blocking merge:");
|
|
4545
|
+
for (const v of gateViolations) {
|
|
4546
|
+
warn(` ${v.fixture} / ${v.metric}: ${v.before} \u2192 ${v.after} (${v.delta > 0 ? "+" : ""}${v.delta})`);
|
|
4547
|
+
}
|
|
4548
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
4549
|
+
const guidanceText = `Gate fixture regression blocks merge. Fix these regressions before re-attempting:
|
|
4550
|
+
` + gateViolations.map((v) => `- ${v.fixture} / ${v.metric}: was ${v.before}, now ${v.after}`).join("\n");
|
|
4551
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4552
|
+
db.transaction(() => {
|
|
4553
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4554
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4555
|
+
if (exp.sub_type) {
|
|
4556
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4557
|
+
}
|
|
4558
|
+
})();
|
|
4559
|
+
warn(`Experiment ${exp.slug} CYCLING BACK \u2014 gate fixture(s) regressed.`);
|
|
4361
4560
|
return;
|
|
4362
4561
|
}
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
(
|
|
4370
|
-
|
|
4371
|
-
|
|
4562
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
4563
|
+
switch (overallGrade) {
|
|
4564
|
+
case "sound": {
|
|
4565
|
+
gitMerge(exp.branch, projectRoot);
|
|
4566
|
+
transition("resolved" /* RESOLVED */, "merged" /* MERGED */);
|
|
4567
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4568
|
+
success(`Experiment ${exp.slug} MERGED (all sound).`);
|
|
4569
|
+
break;
|
|
4570
|
+
}
|
|
4571
|
+
case "good": {
|
|
4572
|
+
gitMerge(exp.branch, projectRoot);
|
|
4573
|
+
const gaps = grades.filter((g) => g.grade === "good").map((g) => `- **${g.component}**: ${g.notes ?? "minor gaps"}`).join("\n");
|
|
4574
|
+
appendToFragilityMap(projectRoot, exp.slug, gaps);
|
|
4575
|
+
autoCommit(projectRoot, `resolve: fragility gaps from ${exp.slug}`);
|
|
4576
|
+
transition("resolved" /* RESOLVED */, "merged" /* MERGED */);
|
|
4577
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4578
|
+
success(`Experiment ${exp.slug} MERGED (good, ${grades.filter((g) => g.grade === "good").length} gaps added to fragility map).`);
|
|
4579
|
+
break;
|
|
4580
|
+
}
|
|
4581
|
+
case "weak": {
|
|
4582
|
+
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
4583
|
+
const guidance = await spawnSynthesiser({
|
|
4584
|
+
experiment: {
|
|
4585
|
+
id: exp.id,
|
|
4586
|
+
slug: exp.slug,
|
|
4587
|
+
hypothesis: exp.hypothesis,
|
|
4588
|
+
status: exp.status,
|
|
4589
|
+
sub_type: exp.sub_type,
|
|
4590
|
+
builder_guidance: exp.builder_guidance
|
|
4591
|
+
},
|
|
4592
|
+
verificationReport: grades,
|
|
4593
|
+
confirmedDoubts,
|
|
4594
|
+
taskPrompt: "Synthesise the verification report, confirmed doubts, and adversarial case results into specific, actionable guidance for the builder's next attempt. Be concrete: which specific decisions need revisiting, which assumptions broke, and what constraints must the next approach satisfy."
|
|
4595
|
+
}, projectRoot);
|
|
4596
|
+
const guidanceText = guidance.structured?.guidance ?? guidance.output;
|
|
4597
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4598
|
+
db.transaction(() => {
|
|
4599
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4600
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4601
|
+
if (exp.sub_type) {
|
|
4602
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4603
|
+
}
|
|
4604
|
+
})();
|
|
4605
|
+
warn(`Experiment ${exp.slug} CYCLING BACK (weak). Guidance generated for builder.`);
|
|
4606
|
+
break;
|
|
4607
|
+
}
|
|
4608
|
+
case "rejected": {
|
|
4609
|
+
gitRevert(exp.branch, projectRoot);
|
|
4610
|
+
const rejectedComponents = grades.filter((g) => g.grade === "rejected");
|
|
4611
|
+
const whyFailed = rejectedComponents.map((r) => r.notes ?? "rejected").join("; ");
|
|
4612
|
+
transition("resolved" /* RESOLVED */, "dead_end" /* DEAD_END */);
|
|
4613
|
+
db.transaction(() => {
|
|
4614
|
+
insertDeadEnd(
|
|
4615
|
+
db,
|
|
4616
|
+
exp.id,
|
|
4617
|
+
exp.hypothesis ?? exp.slug,
|
|
4618
|
+
whyFailed,
|
|
4619
|
+
`Approach rejected: ${whyFailed}`,
|
|
4620
|
+
exp.sub_type,
|
|
4621
|
+
"structural"
|
|
4622
|
+
);
|
|
4623
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
4624
|
+
if (exp.sub_type) {
|
|
4625
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
|
|
4626
|
+
}
|
|
4627
|
+
})();
|
|
4628
|
+
info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
|
|
4629
|
+
break;
|
|
4372
4630
|
}
|
|
4373
4631
|
}
|
|
4374
4632
|
}
|
|
4375
|
-
async function
|
|
4376
|
-
|
|
4377
|
-
if (
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
if (comparisons.length === 0) {
|
|
4390
|
-
warn(`No before/after metrics to compare for ${exp.slug}. Run baseline and measure first.`);
|
|
4391
|
-
return;
|
|
4633
|
+
async function resolveDbOnly(db, exp, projectRoot) {
|
|
4634
|
+
let grades = getVerificationsByExperiment(db, exp.id);
|
|
4635
|
+
if (grades.length === 0) {
|
|
4636
|
+
warn(`No verification records for ${exp.slug}. Defaulting to weak.`);
|
|
4637
|
+
insertVerification(
|
|
4638
|
+
db,
|
|
4639
|
+
exp.id,
|
|
4640
|
+
"auto-default",
|
|
4641
|
+
"weak",
|
|
4642
|
+
null,
|
|
4643
|
+
null,
|
|
4644
|
+
"No structured verification output. Auto-defaulted to weak."
|
|
4645
|
+
);
|
|
4646
|
+
grades = getVerificationsByExperiment(db, exp.id);
|
|
4392
4647
|
}
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4648
|
+
const overallGrade = worstGrade(grades);
|
|
4649
|
+
const config = loadConfig(projectRoot);
|
|
4650
|
+
const metricComparisons = compareMetrics(db, exp.id, config);
|
|
4651
|
+
const gateViolations = checkGateViolations(metricComparisons);
|
|
4652
|
+
if (gateViolations.length > 0 && (overallGrade === "sound" || overallGrade === "good")) {
|
|
4653
|
+
warn("Gate fixture regression detected \u2014 blocking merge:");
|
|
4654
|
+
for (const v of gateViolations) {
|
|
4655
|
+
warn(` ${v.fixture} / ${v.metric}: ${v.before} \u2192 ${v.after} (${v.delta > 0 ? "+" : ""}${v.delta})`);
|
|
4656
|
+
}
|
|
4657
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
4658
|
+
const guidanceText = `Gate fixture regression blocks merge. Fix these regressions before re-attempting:
|
|
4659
|
+
` + gateViolations.map((v) => `- ${v.fixture} / ${v.metric}: was ${v.before}, now ${v.after}`).join("\n");
|
|
4660
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4661
|
+
db.transaction(() => {
|
|
4662
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4663
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4664
|
+
if (exp.sub_type) {
|
|
4665
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4666
|
+
}
|
|
4667
|
+
})();
|
|
4668
|
+
warn(`Experiment ${exp.slug} CYCLING BACK \u2014 gate fixture(s) regressed.`);
|
|
4669
|
+
return "weak";
|
|
4396
4670
|
}
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4671
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
4672
|
+
switch (overallGrade) {
|
|
4673
|
+
case "sound":
|
|
4674
|
+
transition("resolved" /* RESOLVED */, "merged" /* MERGED */);
|
|
4675
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4676
|
+
success(`Experiment ${exp.slug} RESOLVED (sound) \u2014 git merge deferred.`);
|
|
4677
|
+
break;
|
|
4678
|
+
case "good": {
|
|
4679
|
+
const gaps = grades.filter((g) => g.grade === "good").map((g) => `- **${g.component}**: ${g.notes ?? "minor gaps"}`).join("\n");
|
|
4680
|
+
appendToFragilityMap(projectRoot, exp.slug, gaps);
|
|
4681
|
+
transition("resolved" /* RESOLVED */, "merged" /* MERGED */);
|
|
4682
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4683
|
+
success(`Experiment ${exp.slug} RESOLVED (good) \u2014 git merge deferred.`);
|
|
4684
|
+
break;
|
|
4685
|
+
}
|
|
4686
|
+
case "weak": {
|
|
4687
|
+
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
4688
|
+
const guidance = await spawnSynthesiser({
|
|
4689
|
+
experiment: {
|
|
4690
|
+
id: exp.id,
|
|
4691
|
+
slug: exp.slug,
|
|
4692
|
+
hypothesis: exp.hypothesis,
|
|
4693
|
+
status: exp.status,
|
|
4694
|
+
sub_type: exp.sub_type,
|
|
4695
|
+
builder_guidance: exp.builder_guidance
|
|
4696
|
+
},
|
|
4697
|
+
verificationReport: grades,
|
|
4698
|
+
confirmedDoubts,
|
|
4699
|
+
taskPrompt: "Synthesise the verification report, confirmed doubts, and adversarial case results into specific, actionable guidance for the builder's next attempt. Be concrete: which specific decisions need revisiting, which assumptions broke, and what constraints must the next approach satisfy."
|
|
4700
|
+
}, projectRoot);
|
|
4701
|
+
const guidanceText = guidance.structured?.guidance ?? guidance.output;
|
|
4702
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4703
|
+
db.transaction(() => {
|
|
4704
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4705
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4706
|
+
if (exp.sub_type) {
|
|
4707
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4708
|
+
}
|
|
4709
|
+
})();
|
|
4710
|
+
warn(`Experiment ${exp.slug} CYCLING BACK (weak). Guidance generated.`);
|
|
4711
|
+
break;
|
|
4712
|
+
}
|
|
4713
|
+
case "rejected": {
|
|
4714
|
+
const rejectedComponents = grades.filter((g) => g.grade === "rejected");
|
|
4715
|
+
const whyFailed = rejectedComponents.map((r) => r.notes ?? "rejected").join("; ");
|
|
4716
|
+
transition("resolved" /* RESOLVED */, "dead_end" /* DEAD_END */);
|
|
4717
|
+
db.transaction(() => {
|
|
4718
|
+
insertDeadEnd(
|
|
4719
|
+
db,
|
|
4720
|
+
exp.id,
|
|
4721
|
+
exp.hypothesis ?? exp.slug,
|
|
4722
|
+
whyFailed,
|
|
4723
|
+
`Approach rejected: ${whyFailed}`,
|
|
4724
|
+
exp.sub_type,
|
|
4725
|
+
"structural"
|
|
4726
|
+
);
|
|
4727
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
4728
|
+
if (exp.sub_type) {
|
|
4729
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
|
|
4730
|
+
}
|
|
4731
|
+
})();
|
|
4732
|
+
info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
|
|
4733
|
+
break;
|
|
4734
|
+
}
|
|
4414
4735
|
}
|
|
4736
|
+
return overallGrade;
|
|
4415
4737
|
}
|
|
4416
|
-
function
|
|
4417
|
-
const prefix = delta > 0 ? "+" : "";
|
|
4418
|
-
return `${prefix}${delta.toFixed(4)}`;
|
|
4419
|
-
}
|
|
4420
|
-
var import_node_child_process4;
|
|
4421
|
-
var init_measure = __esm({
|
|
4422
|
-
"src/commands/measure.ts"() {
|
|
4423
|
-
"use strict";
|
|
4424
|
-
import_node_child_process4 = require("child_process");
|
|
4425
|
-
init_connection();
|
|
4426
|
-
init_queries();
|
|
4427
|
-
init_metrics();
|
|
4428
|
-
init_config();
|
|
4429
|
-
init_format();
|
|
4430
|
-
}
|
|
4431
|
-
});
|
|
4432
|
-
|
|
4433
|
-
// src/commands/experiment.ts
|
|
4434
|
-
var experiment_exports = {};
|
|
4435
|
-
__export(experiment_exports, {
|
|
4436
|
-
newExperiment: () => newExperiment,
|
|
4437
|
-
revert: () => revert
|
|
4438
|
-
});
|
|
4439
|
-
async function newExperiment(args) {
|
|
4440
|
-
const root = findProjectRoot();
|
|
4441
|
-
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
4442
|
-
const hypothesis = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
4443
|
-
if (!hypothesis) {
|
|
4444
|
-
throw new Error('Usage: majlis new "hypothesis"');
|
|
4445
|
-
}
|
|
4446
|
-
const db = getDb(root);
|
|
4447
|
-
const config = loadConfig(root);
|
|
4448
|
-
const slug = getFlagValue(args, "--slug") ?? await generateSlug(hypothesis, root);
|
|
4449
|
-
if (getExperimentBySlug(db, slug)) {
|
|
4450
|
-
throw new Error(`Experiment with slug "${slug}" already exists.`);
|
|
4451
|
-
}
|
|
4452
|
-
const allExps = db.prepare("SELECT COUNT(*) as count FROM experiments").get();
|
|
4453
|
-
const num = allExps.count + 1;
|
|
4454
|
-
const paddedNum = String(num).padStart(3, "0");
|
|
4455
|
-
const branch = `exp/${paddedNum}-${slug}`;
|
|
4738
|
+
function gitMerge(branch, cwd) {
|
|
4456
4739
|
try {
|
|
4457
|
-
|
|
4458
|
-
|
|
4740
|
+
try {
|
|
4741
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "main"], {
|
|
4742
|
+
cwd,
|
|
4743
|
+
encoding: "utf-8",
|
|
4744
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4745
|
+
});
|
|
4746
|
+
} catch {
|
|
4747
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "master"], {
|
|
4748
|
+
cwd,
|
|
4749
|
+
encoding: "utf-8",
|
|
4750
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4751
|
+
});
|
|
4752
|
+
}
|
|
4753
|
+
(0, import_node_child_process4.execFileSync)("git", ["merge", branch, "--no-ff", "-m", `Merge experiment branch ${branch}`], {
|
|
4754
|
+
cwd,
|
|
4459
4755
|
encoding: "utf-8",
|
|
4460
4756
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4461
4757
|
});
|
|
4462
|
-
info(`Created branch: ${branch}`);
|
|
4463
4758
|
} catch (err) {
|
|
4464
|
-
warn(`
|
|
4465
|
-
}
|
|
4466
|
-
const subType = getFlagValue(args, "--sub-type") ?? null;
|
|
4467
|
-
const dependsOn = getFlagValue(args, "--depends-on") ?? null;
|
|
4468
|
-
const contextArg = getFlagValue(args, "--context") ?? null;
|
|
4469
|
-
const contextFiles = contextArg ? contextArg.split(",").map((f) => f.trim()) : null;
|
|
4470
|
-
if (dependsOn) {
|
|
4471
|
-
const depExp = getExperimentBySlug(db, dependsOn);
|
|
4472
|
-
if (!depExp) {
|
|
4473
|
-
throw new Error(`Dependency experiment not found: ${dependsOn}`);
|
|
4474
|
-
}
|
|
4475
|
-
info(`Depends on: ${dependsOn} (status: ${depExp.status})`);
|
|
4476
|
-
}
|
|
4477
|
-
const exp = createExperiment(db, slug, branch, hypothesis, subType, null, dependsOn, contextFiles);
|
|
4478
|
-
if (contextFiles) {
|
|
4479
|
-
info(`Context files: ${contextFiles.join(", ")}`);
|
|
4480
|
-
}
|
|
4481
|
-
success(`Created experiment #${exp.id}: ${exp.slug}`);
|
|
4482
|
-
const docsDir = path10.join(root, "docs", "experiments");
|
|
4483
|
-
const templatePath = path10.join(docsDir, "_TEMPLATE.md");
|
|
4484
|
-
if (fs10.existsSync(templatePath)) {
|
|
4485
|
-
const template = fs10.readFileSync(templatePath, "utf-8");
|
|
4486
|
-
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]);
|
|
4487
|
-
const logPath = path10.join(docsDir, `${paddedNum}-${slug}.md`);
|
|
4488
|
-
fs10.writeFileSync(logPath, logContent);
|
|
4489
|
-
info(`Created experiment log: docs/experiments/${paddedNum}-${slug}.md`);
|
|
4490
|
-
}
|
|
4491
|
-
autoCommit(root, `new: ${slug}`);
|
|
4492
|
-
if (config.cycle.auto_baseline_on_new_experiment && config.metrics.command) {
|
|
4493
|
-
info("Auto-baselining... (run `majlis baseline` to do this manually)");
|
|
4494
|
-
try {
|
|
4495
|
-
const { baseline: baseline2 } = await Promise.resolve().then(() => (init_measure(), measure_exports));
|
|
4496
|
-
await baseline2(["--experiment", String(exp.id)]);
|
|
4497
|
-
} catch (err) {
|
|
4498
|
-
warn("Auto-baseline failed \u2014 run `majlis baseline` manually.");
|
|
4499
|
-
}
|
|
4759
|
+
warn(`Git merge of ${branch} failed \u2014 you may need to merge manually.`);
|
|
4500
4760
|
}
|
|
4501
4761
|
}
|
|
4502
|
-
|
|
4503
|
-
const root = findProjectRoot();
|
|
4504
|
-
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
4505
|
-
const db = getDb(root);
|
|
4506
|
-
let exp;
|
|
4507
|
-
const slugArg = args.filter((a) => !a.startsWith("--"))[0];
|
|
4508
|
-
if (slugArg) {
|
|
4509
|
-
exp = getExperimentBySlug(db, slugArg);
|
|
4510
|
-
if (!exp) throw new Error(`Experiment not found: ${slugArg}`);
|
|
4511
|
-
} else {
|
|
4512
|
-
exp = getLatestExperiment(db);
|
|
4513
|
-
if (!exp) throw new Error("No active experiments to revert.");
|
|
4514
|
-
}
|
|
4515
|
-
const reason = getFlagValue(args, "--reason") ?? "Manually reverted";
|
|
4516
|
-
const category = args.includes("--structural") ? "structural" : "procedural";
|
|
4517
|
-
insertDeadEnd(
|
|
4518
|
-
db,
|
|
4519
|
-
exp.id,
|
|
4520
|
-
exp.hypothesis ?? exp.slug,
|
|
4521
|
-
reason,
|
|
4522
|
-
`Reverted: ${reason}`,
|
|
4523
|
-
exp.sub_type,
|
|
4524
|
-
category
|
|
4525
|
-
);
|
|
4526
|
-
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "revert");
|
|
4762
|
+
function gitRevert(branch, cwd) {
|
|
4527
4763
|
try {
|
|
4528
|
-
const currentBranch = (0,
|
|
4529
|
-
cwd
|
|
4764
|
+
const currentBranch = (0, import_node_child_process4.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
4765
|
+
cwd,
|
|
4530
4766
|
encoding: "utf-8"
|
|
4531
4767
|
}).trim();
|
|
4532
|
-
if (currentBranch ===
|
|
4768
|
+
if (currentBranch === branch) {
|
|
4533
4769
|
try {
|
|
4534
|
-
(0,
|
|
4535
|
-
|
|
4770
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "--", "."], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
4771
|
+
} catch {
|
|
4772
|
+
}
|
|
4773
|
+
try {
|
|
4774
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "main"], {
|
|
4775
|
+
cwd,
|
|
4536
4776
|
encoding: "utf-8",
|
|
4537
4777
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4538
4778
|
});
|
|
4539
4779
|
} catch {
|
|
4540
|
-
(0,
|
|
4541
|
-
cwd
|
|
4780
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "master"], {
|
|
4781
|
+
cwd,
|
|
4542
4782
|
encoding: "utf-8",
|
|
4543
4783
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4544
4784
|
});
|
|
4545
4785
|
}
|
|
4546
4786
|
}
|
|
4547
4787
|
} catch {
|
|
4548
|
-
warn(
|
|
4788
|
+
warn(`Could not switch away from ${branch} \u2014 you may need to do this manually.`);
|
|
4549
4789
|
}
|
|
4550
|
-
info(`Experiment ${exp.slug} reverted to dead-end. Reason: ${reason}`);
|
|
4551
4790
|
}
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
"
|
|
4555
|
-
|
|
4556
|
-
|
|
4791
|
+
function appendToFragilityMap(projectRoot, expSlug, gaps) {
|
|
4792
|
+
const fragPath = path10.join(projectRoot, "docs", "synthesis", "fragility.md");
|
|
4793
|
+
let content = "";
|
|
4794
|
+
if (fs10.existsSync(fragPath)) {
|
|
4795
|
+
content = fs10.readFileSync(fragPath, "utf-8");
|
|
4796
|
+
}
|
|
4797
|
+
const entry = `
|
|
4798
|
+
## From experiment: ${expSlug}
|
|
4799
|
+
${gaps}
|
|
4800
|
+
`;
|
|
4801
|
+
fs10.writeFileSync(fragPath, content + entry);
|
|
4802
|
+
}
|
|
4803
|
+
var fs10, path10, import_node_child_process4;
|
|
4804
|
+
var init_resolve = __esm({
|
|
4805
|
+
"src/resolve.ts"() {
|
|
4806
|
+
"use strict";
|
|
4807
|
+
fs10 = __toESM(require("fs"));
|
|
4557
4808
|
path10 = __toESM(require("path"));
|
|
4558
|
-
import_node_child_process5 = require("child_process");
|
|
4559
|
-
init_connection();
|
|
4560
|
-
init_queries();
|
|
4561
|
-
init_machine();
|
|
4562
4809
|
init_types2();
|
|
4810
|
+
init_machine();
|
|
4811
|
+
init_queries();
|
|
4812
|
+
init_metrics();
|
|
4563
4813
|
init_config();
|
|
4564
4814
|
init_spawn();
|
|
4815
|
+
import_node_child_process4 = require("child_process");
|
|
4565
4816
|
init_git();
|
|
4566
4817
|
init_format();
|
|
4567
4818
|
}
|
|
4568
4819
|
});
|
|
4569
4820
|
|
|
4570
|
-
// src/commands/
|
|
4571
|
-
var
|
|
4572
|
-
__export(
|
|
4573
|
-
|
|
4821
|
+
// src/commands/cycle.ts
|
|
4822
|
+
var cycle_exports = {};
|
|
4823
|
+
__export(cycle_exports, {
|
|
4824
|
+
cycle: () => cycle,
|
|
4825
|
+
expDocRelPath: () => expDocRelPath,
|
|
4826
|
+
resolveCmd: () => resolveCmd,
|
|
4827
|
+
runResolve: () => runResolve,
|
|
4828
|
+
runStep: () => runStep
|
|
4574
4829
|
});
|
|
4575
|
-
async function
|
|
4576
|
-
const subcommand = args[0];
|
|
4577
|
-
if (!subcommand || subcommand !== "start" && subcommand !== "end") {
|
|
4578
|
-
throw new Error('Usage: majlis session start "intent" | majlis session end');
|
|
4579
|
-
}
|
|
4830
|
+
async function cycle(step, args) {
|
|
4580
4831
|
const root = findProjectRoot();
|
|
4581
4832
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
4582
4833
|
const db = getDb(root);
|
|
4583
|
-
if (
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
info(`Linked to experiment: ${latestExp.slug} (${latestExp.status})`);
|
|
4599
|
-
}
|
|
4600
|
-
} else {
|
|
4601
|
-
const active = getActiveSession(db);
|
|
4602
|
-
if (!active) {
|
|
4603
|
-
throw new Error("No active session to end.");
|
|
4604
|
-
}
|
|
4605
|
-
const accomplished = getFlagValue(args, "--accomplished") ?? null;
|
|
4606
|
-
const unfinished = getFlagValue(args, "--unfinished") ?? null;
|
|
4607
|
-
const fragility = getFlagValue(args, "--fragility") ?? null;
|
|
4608
|
-
endSession(db, active.id, accomplished, unfinished, fragility);
|
|
4609
|
-
success(`Session ended: "${active.intent}"`);
|
|
4610
|
-
if (accomplished) info(`Accomplished: ${accomplished}`);
|
|
4611
|
-
if (unfinished) info(`Unfinished: ${unfinished}`);
|
|
4612
|
-
if (fragility) warn(`New fragility: ${fragility}`);
|
|
4834
|
+
if (step === "compress") return doCompress(db, root);
|
|
4835
|
+
const exp = resolveExperimentArg(db, args);
|
|
4836
|
+
switch (step) {
|
|
4837
|
+
case "build":
|
|
4838
|
+
return doBuild(db, exp, root);
|
|
4839
|
+
case "challenge":
|
|
4840
|
+
return doChallenge(db, exp, root);
|
|
4841
|
+
case "doubt":
|
|
4842
|
+
return doDoubt(db, exp, root);
|
|
4843
|
+
case "scout":
|
|
4844
|
+
return doScout(db, exp, root);
|
|
4845
|
+
case "verify":
|
|
4846
|
+
return doVerify(db, exp, root);
|
|
4847
|
+
case "gate":
|
|
4848
|
+
return doGate(db, exp, root);
|
|
4613
4849
|
}
|
|
4614
4850
|
}
|
|
4615
|
-
|
|
4616
|
-
"src/commands/session.ts"() {
|
|
4617
|
-
"use strict";
|
|
4618
|
-
init_connection();
|
|
4619
|
-
init_queries();
|
|
4620
|
-
init_config();
|
|
4621
|
-
init_format();
|
|
4622
|
-
}
|
|
4623
|
-
});
|
|
4624
|
-
|
|
4625
|
-
// src/commands/query.ts
|
|
4626
|
-
var query_exports = {};
|
|
4627
|
-
__export(query_exports, {
|
|
4628
|
-
query: () => query3
|
|
4629
|
-
});
|
|
4630
|
-
async function query3(command, args, isJson) {
|
|
4851
|
+
async function resolveCmd(args) {
|
|
4631
4852
|
const root = findProjectRoot();
|
|
4632
4853
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
4633
4854
|
const db = getDb(root);
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
case "dead-ends":
|
|
4638
|
-
return queryDeadEnds(db, args, isJson);
|
|
4639
|
-
case "fragility":
|
|
4640
|
-
return queryFragility(root, isJson);
|
|
4641
|
-
case "history":
|
|
4642
|
-
return queryHistory(db, args, isJson);
|
|
4643
|
-
case "circuit-breakers":
|
|
4644
|
-
return queryCircuitBreakers(db, root, isJson);
|
|
4645
|
-
case "check-commit":
|
|
4646
|
-
return checkCommit(db);
|
|
4647
|
-
}
|
|
4855
|
+
const exp = resolveExperimentArg(db, args);
|
|
4856
|
+
transition(exp.status, "resolved" /* RESOLVED */);
|
|
4857
|
+
await resolve2(db, exp, root);
|
|
4648
4858
|
}
|
|
4649
|
-
function
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4859
|
+
async function runStep(step, db, exp, root) {
|
|
4860
|
+
switch (step) {
|
|
4861
|
+
case "build":
|
|
4862
|
+
return doBuild(db, exp, root);
|
|
4863
|
+
case "challenge":
|
|
4864
|
+
return doChallenge(db, exp, root);
|
|
4865
|
+
case "doubt":
|
|
4866
|
+
return doDoubt(db, exp, root);
|
|
4867
|
+
case "scout":
|
|
4868
|
+
return doScout(db, exp, root);
|
|
4869
|
+
case "verify":
|
|
4870
|
+
return doVerify(db, exp, root);
|
|
4871
|
+
case "gate":
|
|
4872
|
+
return doGate(db, exp, root);
|
|
4873
|
+
case "compress":
|
|
4874
|
+
return doCompress(db, root);
|
|
4661
4875
|
}
|
|
4662
|
-
header("Decisions");
|
|
4663
|
-
const rows = decisions.map((d) => [
|
|
4664
|
-
String(d.id),
|
|
4665
|
-
String(d.experiment_id),
|
|
4666
|
-
evidenceColor(d.evidence_level),
|
|
4667
|
-
d.description.slice(0, 60) + (d.description.length > 60 ? "..." : ""),
|
|
4668
|
-
d.status
|
|
4669
|
-
]);
|
|
4670
|
-
console.log(table(["ID", "Exp", "Level", "Description", "Status"], rows));
|
|
4671
4876
|
}
|
|
4672
|
-
function
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
let deadEnds;
|
|
4676
|
-
if (subType) {
|
|
4677
|
-
deadEnds = listDeadEndsBySubType(db, subType);
|
|
4678
|
-
} else if (searchTerm) {
|
|
4679
|
-
deadEnds = searchDeadEnds(db, searchTerm);
|
|
4680
|
-
} else {
|
|
4681
|
-
deadEnds = listAllDeadEnds(db);
|
|
4682
|
-
}
|
|
4683
|
-
if (isJson) {
|
|
4684
|
-
console.log(JSON.stringify(deadEnds, null, 2));
|
|
4685
|
-
return;
|
|
4686
|
-
}
|
|
4687
|
-
if (deadEnds.length === 0) {
|
|
4688
|
-
info("No dead-ends recorded.");
|
|
4689
|
-
return;
|
|
4690
|
-
}
|
|
4691
|
-
header("Dead-End Registry");
|
|
4692
|
-
const rows = deadEnds.map((d) => [
|
|
4693
|
-
String(d.id),
|
|
4694
|
-
d.sub_type ?? "\u2014",
|
|
4695
|
-
d.approach.slice(0, 40) + (d.approach.length > 40 ? "..." : ""),
|
|
4696
|
-
d.structural_constraint.slice(0, 40) + (d.structural_constraint.length > 40 ? "..." : "")
|
|
4697
|
-
]);
|
|
4698
|
-
console.log(table(["ID", "Sub-Type", "Approach", "Constraint"], rows));
|
|
4877
|
+
async function runResolve(db, exp, root) {
|
|
4878
|
+
transition(exp.status, "resolved" /* RESOLVED */);
|
|
4879
|
+
await resolve2(db, exp, root);
|
|
4699
4880
|
}
|
|
4700
|
-
function
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4881
|
+
async function doGate(db, exp, root) {
|
|
4882
|
+
transition(exp.status, "gated" /* GATED */);
|
|
4883
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
4884
|
+
const fragility = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
4885
|
+
const structuralDeadEnds = exp.sub_type ? listStructuralDeadEndsBySubType(db, exp.sub_type) : listStructuralDeadEnds(db);
|
|
4886
|
+
const result = await spawnAgent("gatekeeper", {
|
|
4887
|
+
experiment: {
|
|
4888
|
+
id: exp.id,
|
|
4889
|
+
slug: exp.slug,
|
|
4890
|
+
hypothesis: exp.hypothesis,
|
|
4891
|
+
status: exp.status,
|
|
4892
|
+
sub_type: exp.sub_type,
|
|
4893
|
+
builder_guidance: null
|
|
4894
|
+
},
|
|
4895
|
+
deadEnds: structuralDeadEnds.map((d) => ({
|
|
4896
|
+
approach: d.approach,
|
|
4897
|
+
why_failed: d.why_failed,
|
|
4898
|
+
structural_constraint: d.structural_constraint
|
|
4899
|
+
})),
|
|
4900
|
+
fragility,
|
|
4901
|
+
synthesis,
|
|
4902
|
+
taskPrompt: `Gate-check hypothesis for experiment ${exp.slug}:
|
|
4903
|
+
"${exp.hypothesis}"
|
|
4904
|
+
|
|
4905
|
+
This is a FAST gate \u2014 decide in 1-2 turns. Do NOT read source code or large files. Use the synthesis, dead-ends, and fragility provided in your context. At most, do one targeted grep to verify a function name exists.
|
|
4906
|
+
|
|
4907
|
+
Check: (a) stale references \u2014 does the hypothesis reference specific lines, functions, or structures that may not exist? (b) dead-end overlap \u2014 does this hypothesis repeat an approach already ruled out by structural dead-ends? (c) scope \u2014 is this a single focused change, or does it try to do multiple things?
|
|
4908
|
+
|
|
4909
|
+
Output your gate_decision as "approve", "reject", or "flag" with reasoning.`
|
|
4910
|
+
}, root);
|
|
4911
|
+
ingestStructuredOutput(db, exp.id, result.structured);
|
|
4912
|
+
const decision = result.structured?.gate_decision ?? "approve";
|
|
4913
|
+
const reason = result.structured?.reason ?? "";
|
|
4914
|
+
if (decision === "reject") {
|
|
4915
|
+
updateExperimentStatus(db, exp.id, "gated");
|
|
4916
|
+
storeGateRejection(db, exp.id, reason);
|
|
4917
|
+
warn(`Gate REJECTED for ${exp.slug}: ${reason}`);
|
|
4918
|
+
info("Run `majlis next --override-gate` to proceed anyway, or `majlis revert` to abandon.");
|
|
4709
4919
|
return;
|
|
4920
|
+
} else {
|
|
4921
|
+
if (decision === "flag") {
|
|
4922
|
+
warn(`Gate flagged concerns for ${exp.slug}: ${reason}`);
|
|
4923
|
+
}
|
|
4924
|
+
updateExperimentStatus(db, exp.id, "gated");
|
|
4925
|
+
success(`Gate passed for ${exp.slug}. Run \`majlis build\` next.`);
|
|
4710
4926
|
}
|
|
4711
|
-
header("Fragility Map");
|
|
4712
|
-
console.log(content);
|
|
4713
4927
|
}
|
|
4714
|
-
function
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
return;
|
|
4723
|
-
}
|
|
4724
|
-
if (history.length === 0) {
|
|
4725
|
-
info(`No metric history for fixture: ${fixture}`);
|
|
4726
|
-
return;
|
|
4928
|
+
async function doBuild(db, exp, root) {
|
|
4929
|
+
if (exp.depends_on) {
|
|
4930
|
+
const dep = getExperimentBySlug(db, exp.depends_on);
|
|
4931
|
+
if (!dep || dep.status !== "merged") {
|
|
4932
|
+
throw new Error(
|
|
4933
|
+
`Experiment "${exp.slug}" depends on "${exp.depends_on}" which is ${dep ? dep.status : "not found"}. Dependency must be merged before building.`
|
|
4934
|
+
);
|
|
4935
|
+
}
|
|
4727
4936
|
}
|
|
4728
|
-
|
|
4729
|
-
const
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
String(h.metric_value),
|
|
4735
|
-
h.captured_at
|
|
4736
|
-
]);
|
|
4737
|
-
console.log(table(["Exp", "Slug", "Phase", "Metric", "Value", "Captured"], rows));
|
|
4738
|
-
}
|
|
4739
|
-
function queryCircuitBreakers(db, root, isJson) {
|
|
4937
|
+
transition(exp.status, "building" /* BUILDING */);
|
|
4938
|
+
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
4939
|
+
const builderGuidance = getBuilderGuidance(db, exp.id);
|
|
4940
|
+
const fragility = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
4941
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
4942
|
+
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
4740
4943
|
const config = loadConfig(root);
|
|
4741
|
-
const
|
|
4742
|
-
if (
|
|
4743
|
-
console.log(JSON.stringify(states, null, 2));
|
|
4744
|
-
return;
|
|
4745
|
-
}
|
|
4746
|
-
if (states.length === 0) {
|
|
4747
|
-
info("No circuit breaker data.");
|
|
4748
|
-
return;
|
|
4749
|
-
}
|
|
4750
|
-
header("Circuit Breakers");
|
|
4751
|
-
const rows = states.map((s) => [
|
|
4752
|
-
s.sub_type,
|
|
4753
|
-
String(s.failure_count),
|
|
4754
|
-
String(config.cycle.circuit_breaker_threshold),
|
|
4755
|
-
s.tripped ? red("TRIPPED") : green("OK")
|
|
4756
|
-
]);
|
|
4757
|
-
console.log(table(["Sub-Type", "Failures", "Threshold", "Status"], rows));
|
|
4758
|
-
}
|
|
4759
|
-
function checkCommit(db) {
|
|
4760
|
-
let stdinData = "";
|
|
4761
|
-
try {
|
|
4762
|
-
stdinData = fs11.readFileSync(0, "utf-8");
|
|
4763
|
-
} catch {
|
|
4764
|
-
}
|
|
4765
|
-
if (stdinData) {
|
|
4944
|
+
const existingBaseline = getMetricsByExperimentAndPhase(db, exp.id, "before");
|
|
4945
|
+
if (config.metrics?.command && existingBaseline.length === 0) {
|
|
4766
4946
|
try {
|
|
4767
|
-
const
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4947
|
+
const output = (0, import_node_child_process5.execSync)(config.metrics.command, {
|
|
4948
|
+
cwd: root,
|
|
4949
|
+
encoding: "utf-8",
|
|
4950
|
+
timeout: 6e4,
|
|
4951
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4952
|
+
}).trim();
|
|
4953
|
+
const parsed = parseMetricsOutput(output);
|
|
4954
|
+
for (const m of parsed) {
|
|
4955
|
+
insertMetric(db, exp.id, "before", m.fixture, m.metric_name, m.metric_value);
|
|
4771
4956
|
}
|
|
4957
|
+
if (parsed.length > 0) info(`Captured ${parsed.length} baseline metric(s).`);
|
|
4772
4958
|
} catch {
|
|
4959
|
+
warn("Could not capture baseline metrics.");
|
|
4773
4960
|
}
|
|
4774
4961
|
}
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4962
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4963
|
+
let taskPrompt = builderGuidance ? `Previous attempt was weak. Here is guidance for this attempt:
|
|
4964
|
+
${builderGuidance}
|
|
4965
|
+
|
|
4966
|
+
Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothesis}`;
|
|
4967
|
+
if (confirmedDoubts.length > 0) {
|
|
4968
|
+
taskPrompt += "\n\n## Confirmed Doubts (MUST address)\nThese weaknesses were confirmed by the verifier. Your build MUST address each one:\n";
|
|
4969
|
+
for (const d of confirmedDoubts) {
|
|
4970
|
+
taskPrompt += `- [${d.severity}] ${d.claim_doubted}: ${d.evidence_for_doubt}
|
|
4971
|
+
`;
|
|
4783
4972
|
}
|
|
4784
|
-
process.exit(1);
|
|
4785
|
-
}
|
|
4786
|
-
}
|
|
4787
|
-
var fs11, path11;
|
|
4788
|
-
var init_query = __esm({
|
|
4789
|
-
"src/commands/query.ts"() {
|
|
4790
|
-
"use strict";
|
|
4791
|
-
fs11 = __toESM(require("fs"));
|
|
4792
|
-
path11 = __toESM(require("path"));
|
|
4793
|
-
init_connection();
|
|
4794
|
-
init_queries();
|
|
4795
|
-
init_config();
|
|
4796
|
-
init_format();
|
|
4797
4973
|
}
|
|
4798
|
-
|
|
4974
|
+
taskPrompt += `
|
|
4799
4975
|
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
if (grades.some((g) => g.grade === grade)) return grade;
|
|
4976
|
+
Your experiment doc: ${expDocRelPath(exp)}`;
|
|
4977
|
+
taskPrompt += "\n\nNote: The framework captures metrics automatically. Do NOT claim specific numbers unless quoting framework output.";
|
|
4978
|
+
const supplementaryContext = loadExperimentContext(exp, root);
|
|
4979
|
+
const lineage = exportExperimentLineage(db, exp.sub_type);
|
|
4980
|
+
if (lineage) {
|
|
4981
|
+
taskPrompt += "\n\n" + lineage;
|
|
4807
4982
|
}
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4983
|
+
const result = await spawnAgent("builder", {
|
|
4984
|
+
experiment: {
|
|
4985
|
+
id: exp.id,
|
|
4986
|
+
slug: exp.slug,
|
|
4987
|
+
hypothesis: exp.hypothesis,
|
|
4988
|
+
status: "building",
|
|
4989
|
+
sub_type: exp.sub_type,
|
|
4990
|
+
builder_guidance: builderGuidance
|
|
4991
|
+
},
|
|
4992
|
+
deadEnds: deadEnds.map((d) => ({
|
|
4993
|
+
approach: d.approach,
|
|
4994
|
+
why_failed: d.why_failed,
|
|
4995
|
+
structural_constraint: d.structural_constraint
|
|
4996
|
+
})),
|
|
4997
|
+
fragility,
|
|
4998
|
+
synthesis,
|
|
4999
|
+
confirmedDoubts,
|
|
5000
|
+
supplementaryContext: supplementaryContext || void 0,
|
|
5001
|
+
experimentLineage: lineage || void 0,
|
|
5002
|
+
taskPrompt
|
|
5003
|
+
}, root);
|
|
5004
|
+
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5005
|
+
if (result.structured?.abandon) {
|
|
5006
|
+
insertDeadEnd(
|
|
4815
5007
|
db,
|
|
4816
5008
|
exp.id,
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
"
|
|
5009
|
+
exp.hypothesis ?? exp.slug,
|
|
5010
|
+
result.structured.abandon.reason,
|
|
5011
|
+
result.structured.abandon.structural_constraint,
|
|
5012
|
+
exp.sub_type,
|
|
5013
|
+
"structural"
|
|
4822
5014
|
);
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
const config = loadConfig(projectRoot);
|
|
4827
|
-
const metricComparisons = compareMetrics(db, exp.id, config);
|
|
4828
|
-
const gateViolations = checkGateViolations(metricComparisons);
|
|
4829
|
-
if (gateViolations.length > 0 && (overallGrade === "sound" || overallGrade === "good")) {
|
|
4830
|
-
warn("Gate fixture regression detected \u2014 blocking merge:");
|
|
4831
|
-
for (const v of gateViolations) {
|
|
4832
|
-
warn(` ${v.fixture} / ${v.metric}: ${v.before} \u2192 ${v.after} (${v.delta > 0 ? "+" : ""}${v.delta})`);
|
|
4833
|
-
}
|
|
4834
|
-
updateExperimentStatus(db, exp.id, "resolved");
|
|
4835
|
-
const guidanceText = `Gate fixture regression blocks merge. Fix these regressions before re-attempting:
|
|
4836
|
-
` + gateViolations.map((v) => `- ${v.fixture} / ${v.metric}: was ${v.before}, now ${v.after}`).join("\n");
|
|
4837
|
-
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4838
|
-
db.transaction(() => {
|
|
4839
|
-
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4840
|
-
updateExperimentStatus(db, exp.id, "building");
|
|
4841
|
-
if (exp.sub_type) {
|
|
4842
|
-
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4843
|
-
}
|
|
4844
|
-
})();
|
|
4845
|
-
warn(`Experiment ${exp.slug} CYCLING BACK \u2014 gate fixture(s) regressed.`);
|
|
5015
|
+
adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
|
|
5016
|
+
handleDeadEndGit(exp, root);
|
|
5017
|
+
info(`Builder abandoned ${exp.slug}: ${result.structured.abandon.reason}`);
|
|
4846
5018
|
return;
|
|
4847
5019
|
}
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
hypothesis: exp.hypothesis,
|
|
4874
|
-
status: exp.status,
|
|
4875
|
-
sub_type: exp.sub_type,
|
|
4876
|
-
builder_guidance: exp.builder_guidance
|
|
4877
|
-
},
|
|
4878
|
-
verificationReport: grades,
|
|
4879
|
-
confirmedDoubts,
|
|
4880
|
-
taskPrompt: "Synthesise the verification report, confirmed doubts, and adversarial case results into specific, actionable guidance for the builder's next attempt. Be concrete: which specific decisions need revisiting, which assumptions broke, and what constraints must the next approach satisfy."
|
|
4881
|
-
}, projectRoot);
|
|
4882
|
-
const guidanceText = guidance.structured?.guidance ?? guidance.output;
|
|
4883
|
-
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4884
|
-
db.transaction(() => {
|
|
4885
|
-
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4886
|
-
updateExperimentStatus(db, exp.id, "building");
|
|
4887
|
-
if (exp.sub_type) {
|
|
4888
|
-
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
5020
|
+
if (result.truncated && !result.structured) {
|
|
5021
|
+
warn(`Builder was truncated (hit max turns) without producing structured output.`);
|
|
5022
|
+
const recovery = await extractStructuredData("builder", result.output);
|
|
5023
|
+
if (recovery.data && !recovery.data.abandon) {
|
|
5024
|
+
info(`Recovered structured output from truncated builder (tier ${recovery.tier}).`);
|
|
5025
|
+
ingestStructuredOutput(db, exp.id, recovery.data);
|
|
5026
|
+
if (config.build?.pre_measure) {
|
|
5027
|
+
try {
|
|
5028
|
+
const [cmd, ...cmdArgs] = config.build.pre_measure.split(/\s+/);
|
|
5029
|
+
(0, import_node_child_process5.execFileSync)(cmd, cmdArgs, {
|
|
5030
|
+
cwd: root,
|
|
5031
|
+
encoding: "utf-8",
|
|
5032
|
+
timeout: 3e4,
|
|
5033
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5034
|
+
});
|
|
5035
|
+
} catch (err) {
|
|
5036
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5037
|
+
storeBuilderGuidance(
|
|
5038
|
+
db,
|
|
5039
|
+
exp.id,
|
|
5040
|
+
`Build verification failed after truncated recovery.
|
|
5041
|
+
Error: ${errMsg.slice(0, 500)}`
|
|
5042
|
+
);
|
|
5043
|
+
warn(`Build verification failed for ${exp.slug}. Staying at 'building'.`);
|
|
5044
|
+
return;
|
|
4889
5045
|
}
|
|
4890
|
-
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
5046
|
+
}
|
|
5047
|
+
if (config.metrics?.command) {
|
|
5048
|
+
try {
|
|
5049
|
+
const output = (0, import_node_child_process5.execSync)(config.metrics.command, {
|
|
5050
|
+
cwd: root,
|
|
5051
|
+
encoding: "utf-8",
|
|
5052
|
+
timeout: 6e4,
|
|
5053
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5054
|
+
}).trim();
|
|
5055
|
+
const parsed = parseMetricsOutput(output);
|
|
5056
|
+
for (const m of parsed) {
|
|
5057
|
+
insertMetric(db, exp.id, "after", m.fixture, m.metric_name, m.metric_value);
|
|
5058
|
+
}
|
|
5059
|
+
if (parsed.length > 0) info(`Captured ${parsed.length} post-build metric(s).`);
|
|
5060
|
+
} catch {
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
gitCommitBuild(exp, root);
|
|
5064
|
+
if (recovery.tier === 3) {
|
|
5065
|
+
warn(`Builder output extracted via Haiku (tier 3). Data provenance degraded.`);
|
|
5066
|
+
const existing = getBuilderGuidance(db, exp.id) ?? "";
|
|
5067
|
+
storeBuilderGuidance(
|
|
4901
5068
|
db,
|
|
4902
5069
|
exp.id,
|
|
4903
|
-
|
|
4904
|
-
whyFailed,
|
|
4905
|
-
`Approach rejected: ${whyFailed}`,
|
|
4906
|
-
exp.sub_type,
|
|
4907
|
-
"structural"
|
|
5070
|
+
existing + "\n[PROVENANCE WARNING] Builder structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny."
|
|
4908
5071
|
);
|
|
4909
|
-
updateExperimentStatus(db, exp.id, "dead_end");
|
|
4910
|
-
if (exp.sub_type) {
|
|
4911
|
-
incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
|
|
4912
|
-
}
|
|
4913
|
-
})();
|
|
4914
|
-
info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
|
|
4915
|
-
break;
|
|
4916
|
-
}
|
|
4917
|
-
}
|
|
4918
|
-
}
|
|
4919
|
-
async function resolveDbOnly(db, exp, projectRoot) {
|
|
4920
|
-
let grades = getVerificationsByExperiment(db, exp.id);
|
|
4921
|
-
if (grades.length === 0) {
|
|
4922
|
-
warn(`No verification records for ${exp.slug}. Defaulting to weak.`);
|
|
4923
|
-
insertVerification(
|
|
4924
|
-
db,
|
|
4925
|
-
exp.id,
|
|
4926
|
-
"auto-default",
|
|
4927
|
-
"weak",
|
|
4928
|
-
null,
|
|
4929
|
-
null,
|
|
4930
|
-
"No structured verification output. Auto-defaulted to weak."
|
|
4931
|
-
);
|
|
4932
|
-
grades = getVerificationsByExperiment(db, exp.id);
|
|
4933
|
-
}
|
|
4934
|
-
const overallGrade = worstGrade(grades);
|
|
4935
|
-
const config = loadConfig(projectRoot);
|
|
4936
|
-
const metricComparisons = compareMetrics(db, exp.id, config);
|
|
4937
|
-
const gateViolations = checkGateViolations(metricComparisons);
|
|
4938
|
-
if (gateViolations.length > 0 && (overallGrade === "sound" || overallGrade === "good")) {
|
|
4939
|
-
warn("Gate fixture regression detected \u2014 blocking merge:");
|
|
4940
|
-
for (const v of gateViolations) {
|
|
4941
|
-
warn(` ${v.fixture} / ${v.metric}: ${v.before} \u2192 ${v.after} (${v.delta > 0 ? "+" : ""}${v.delta})`);
|
|
4942
|
-
}
|
|
4943
|
-
updateExperimentStatus(db, exp.id, "resolved");
|
|
4944
|
-
const guidanceText = `Gate fixture regression blocks merge. Fix these regressions before re-attempting:
|
|
4945
|
-
` + gateViolations.map((v) => `- ${v.fixture} / ${v.metric}: was ${v.before}, now ${v.after}`).join("\n");
|
|
4946
|
-
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4947
|
-
db.transaction(() => {
|
|
4948
|
-
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4949
|
-
updateExperimentStatus(db, exp.id, "building");
|
|
4950
|
-
if (exp.sub_type) {
|
|
4951
|
-
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4952
5072
|
}
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
case "weak": {
|
|
4973
|
-
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
4974
|
-
const guidance = await spawnSynthesiser({
|
|
4975
|
-
experiment: {
|
|
4976
|
-
id: exp.id,
|
|
4977
|
-
slug: exp.slug,
|
|
4978
|
-
hypothesis: exp.hypothesis,
|
|
4979
|
-
status: exp.status,
|
|
4980
|
-
sub_type: exp.sub_type,
|
|
4981
|
-
builder_guidance: exp.builder_guidance
|
|
4982
|
-
},
|
|
4983
|
-
verificationReport: grades,
|
|
4984
|
-
confirmedDoubts,
|
|
4985
|
-
taskPrompt: "Synthesise the verification report, confirmed doubts, and adversarial case results into specific, actionable guidance for the builder's next attempt. Be concrete: which specific decisions need revisiting, which assumptions broke, and what constraints must the next approach satisfy."
|
|
4986
|
-
}, projectRoot);
|
|
4987
|
-
const guidanceText = guidance.structured?.guidance ?? guidance.output;
|
|
4988
|
-
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4989
|
-
db.transaction(() => {
|
|
4990
|
-
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4991
|
-
updateExperimentStatus(db, exp.id, "building");
|
|
4992
|
-
if (exp.sub_type) {
|
|
4993
|
-
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4994
|
-
}
|
|
4995
|
-
})();
|
|
4996
|
-
warn(`Experiment ${exp.slug} CYCLING BACK (weak). Guidance generated.`);
|
|
4997
|
-
break;
|
|
4998
|
-
}
|
|
4999
|
-
case "rejected": {
|
|
5000
|
-
const rejectedComponents = grades.filter((g) => g.grade === "rejected");
|
|
5001
|
-
const whyFailed = rejectedComponents.map((r) => r.notes ?? "rejected").join("; ");
|
|
5002
|
-
transition("resolved" /* RESOLVED */, "dead_end" /* DEAD_END */);
|
|
5003
|
-
db.transaction(() => {
|
|
5004
|
-
insertDeadEnd(
|
|
5073
|
+
updateExperimentStatus(db, exp.id, "built");
|
|
5074
|
+
success(`Build complete for ${exp.slug} (recovered from truncation). Run \`majlis doubt\` or \`majlis challenge\` next.`);
|
|
5075
|
+
} else if (recovery.data?.abandon) {
|
|
5076
|
+
insertDeadEnd(
|
|
5077
|
+
db,
|
|
5078
|
+
exp.id,
|
|
5079
|
+
exp.hypothesis ?? exp.slug,
|
|
5080
|
+
recovery.data.abandon.reason,
|
|
5081
|
+
recovery.data.abandon.structural_constraint,
|
|
5082
|
+
exp.sub_type,
|
|
5083
|
+
"structural"
|
|
5084
|
+
);
|
|
5085
|
+
adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
|
|
5086
|
+
handleDeadEndGit(exp, root);
|
|
5087
|
+
info(`Builder abandoned ${exp.slug} (recovered from truncation): ${recovery.data.abandon.reason}`);
|
|
5088
|
+
} else {
|
|
5089
|
+
const tail = result.output.slice(-2e3).trim();
|
|
5090
|
+
if (tail) {
|
|
5091
|
+
storeBuilderGuidance(
|
|
5005
5092
|
db,
|
|
5006
5093
|
exp.id,
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
`Approach rejected: ${whyFailed}`,
|
|
5010
|
-
exp.sub_type,
|
|
5011
|
-
"structural"
|
|
5094
|
+
`Builder was truncated. Last ~2000 chars of output:
|
|
5095
|
+
${tail}`
|
|
5012
5096
|
);
|
|
5013
|
-
updateExperimentStatus(db, exp.id, "dead_end");
|
|
5014
|
-
if (exp.sub_type) {
|
|
5015
|
-
incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
|
|
5016
|
-
}
|
|
5017
|
-
})();
|
|
5018
|
-
info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
|
|
5019
|
-
break;
|
|
5020
|
-
}
|
|
5021
|
-
}
|
|
5022
|
-
return overallGrade;
|
|
5023
|
-
}
|
|
5024
|
-
function gitMerge(branch, cwd) {
|
|
5025
|
-
try {
|
|
5026
|
-
try {
|
|
5027
|
-
(0, import_node_child_process6.execFileSync)("git", ["checkout", "main"], {
|
|
5028
|
-
cwd,
|
|
5029
|
-
encoding: "utf-8",
|
|
5030
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5031
|
-
});
|
|
5032
|
-
} catch {
|
|
5033
|
-
(0, import_node_child_process6.execFileSync)("git", ["checkout", "master"], {
|
|
5034
|
-
cwd,
|
|
5035
|
-
encoding: "utf-8",
|
|
5036
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5037
|
-
});
|
|
5038
|
-
}
|
|
5039
|
-
(0, import_node_child_process6.execFileSync)("git", ["merge", branch, "--no-ff", "-m", `Merge experiment branch ${branch}`], {
|
|
5040
|
-
cwd,
|
|
5041
|
-
encoding: "utf-8",
|
|
5042
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5043
|
-
});
|
|
5044
|
-
} catch (err) {
|
|
5045
|
-
warn(`Git merge of ${branch} failed \u2014 you may need to merge manually.`);
|
|
5046
|
-
}
|
|
5047
|
-
}
|
|
5048
|
-
function gitRevert(branch, cwd) {
|
|
5049
|
-
try {
|
|
5050
|
-
const currentBranch = (0, import_node_child_process6.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
5051
|
-
cwd,
|
|
5052
|
-
encoding: "utf-8"
|
|
5053
|
-
}).trim();
|
|
5054
|
-
if (currentBranch === branch) {
|
|
5055
|
-
try {
|
|
5056
|
-
(0, import_node_child_process6.execFileSync)("git", ["checkout", "--", "."], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
5057
|
-
} catch {
|
|
5058
5097
|
}
|
|
5098
|
+
warn(`Experiment stays at 'building'. Run \`majlis build\` to retry or \`majlis revert\` to abandon.`);
|
|
5099
|
+
}
|
|
5100
|
+
} else {
|
|
5101
|
+
if (config.build?.pre_measure) {
|
|
5059
5102
|
try {
|
|
5060
|
-
|
|
5061
|
-
|
|
5103
|
+
const [cmd, ...cmdArgs] = config.build.pre_measure.split(/\s+/);
|
|
5104
|
+
(0, import_node_child_process5.execFileSync)(cmd, cmdArgs, {
|
|
5105
|
+
cwd: root,
|
|
5062
5106
|
encoding: "utf-8",
|
|
5107
|
+
timeout: 3e4,
|
|
5063
5108
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5064
5109
|
});
|
|
5065
|
-
} catch {
|
|
5066
|
-
|
|
5067
|
-
|
|
5110
|
+
} catch (err) {
|
|
5111
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5112
|
+
const guidance = `Build verification failed after builder completion. Code may be syntactically broken or incomplete.
|
|
5113
|
+
Error: ${errMsg.slice(0, 500)}`;
|
|
5114
|
+
storeBuilderGuidance(db, exp.id, guidance);
|
|
5115
|
+
warn(`Build verification failed for ${exp.slug}. Staying at 'building'.`);
|
|
5116
|
+
warn(`Guidance stored for retry. Run \`majlis build\` to retry.`);
|
|
5117
|
+
return;
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
if (config.metrics?.command) {
|
|
5121
|
+
try {
|
|
5122
|
+
const output = (0, import_node_child_process5.execSync)(config.metrics.command, {
|
|
5123
|
+
cwd: root,
|
|
5068
5124
|
encoding: "utf-8",
|
|
5125
|
+
timeout: 6e4,
|
|
5069
5126
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5070
|
-
});
|
|
5127
|
+
}).trim();
|
|
5128
|
+
const parsed = parseMetricsOutput(output);
|
|
5129
|
+
for (const m of parsed) {
|
|
5130
|
+
insertMetric(db, exp.id, "after", m.fixture, m.metric_name, m.metric_value);
|
|
5131
|
+
}
|
|
5132
|
+
if (parsed.length > 0) info(`Captured ${parsed.length} post-build metric(s).`);
|
|
5133
|
+
} catch {
|
|
5134
|
+
warn("Could not capture post-build metrics.");
|
|
5071
5135
|
}
|
|
5072
5136
|
}
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5137
|
+
gitCommitBuild(exp, root);
|
|
5138
|
+
if (result.extractionTier === 3) {
|
|
5139
|
+
warn(`Builder output extracted via Haiku (tier 3). Data provenance degraded.`);
|
|
5140
|
+
const existing = getBuilderGuidance(db, exp.id) ?? "";
|
|
5141
|
+
storeBuilderGuidance(
|
|
5142
|
+
db,
|
|
5143
|
+
exp.id,
|
|
5144
|
+
existing + "\n[PROVENANCE WARNING] Builder structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny."
|
|
5145
|
+
);
|
|
5146
|
+
}
|
|
5147
|
+
updateExperimentStatus(db, exp.id, "built");
|
|
5148
|
+
success(`Build complete for ${exp.slug}. Run \`majlis doubt\` or \`majlis challenge\` next.`);
|
|
5082
5149
|
}
|
|
5083
|
-
const entry = `
|
|
5084
|
-
## From experiment: ${expSlug}
|
|
5085
|
-
${gaps}
|
|
5086
|
-
`;
|
|
5087
|
-
fs12.writeFileSync(fragPath, content + entry);
|
|
5088
5150
|
}
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
"
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
init_config();
|
|
5100
|
-
init_spawn();
|
|
5101
|
-
import_node_child_process6 = require("child_process");
|
|
5102
|
-
init_git();
|
|
5103
|
-
init_format();
|
|
5151
|
+
async function doChallenge(db, exp, root) {
|
|
5152
|
+
transition(exp.status, "challenged" /* CHALLENGED */);
|
|
5153
|
+
let gitDiff = "";
|
|
5154
|
+
try {
|
|
5155
|
+
gitDiff = (0, import_node_child_process5.execSync)('git diff main -- . ":!.majlis/"', {
|
|
5156
|
+
cwd: root,
|
|
5157
|
+
encoding: "utf-8",
|
|
5158
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5159
|
+
}).trim();
|
|
5160
|
+
} catch {
|
|
5104
5161
|
}
|
|
5105
|
-
|
|
5162
|
+
if (gitDiff.length > 8e3) gitDiff = gitDiff.slice(0, 8e3) + "\n[DIFF TRUNCATED]";
|
|
5163
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
5164
|
+
let taskPrompt = `Construct adversarial test cases for experiment ${exp.slug}: ${exp.hypothesis}`;
|
|
5165
|
+
if (gitDiff) {
|
|
5166
|
+
taskPrompt += `
|
|
5106
5167
|
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
resolveCmd: () => resolveCmd,
|
|
5112
|
-
runResolve: () => runResolve,
|
|
5113
|
-
runStep: () => runStep
|
|
5114
|
-
});
|
|
5115
|
-
async function cycle(step, args) {
|
|
5116
|
-
const root = findProjectRoot();
|
|
5117
|
-
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5118
|
-
const db = getDb(root);
|
|
5119
|
-
const exp = resolveExperimentArg(db, args);
|
|
5120
|
-
switch (step) {
|
|
5121
|
-
case "build":
|
|
5122
|
-
return doBuild(db, exp, root);
|
|
5123
|
-
case "challenge":
|
|
5124
|
-
return doChallenge(db, exp, root);
|
|
5125
|
-
case "doubt":
|
|
5126
|
-
return doDoubt(db, exp, root);
|
|
5127
|
-
case "scout":
|
|
5128
|
-
return doScout(db, exp, root);
|
|
5129
|
-
case "verify":
|
|
5130
|
-
return doVerify(db, exp, root);
|
|
5131
|
-
case "gate":
|
|
5132
|
-
return doGate(db, exp, root);
|
|
5133
|
-
case "compress":
|
|
5134
|
-
return doCompress(db, root);
|
|
5168
|
+
## Code Changes (git diff main)
|
|
5169
|
+
\`\`\`diff
|
|
5170
|
+
${gitDiff}
|
|
5171
|
+
\`\`\``;
|
|
5135
5172
|
}
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
}
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
return doScout(db, exp, root);
|
|
5155
|
-
case "verify":
|
|
5156
|
-
return doVerify(db, exp, root);
|
|
5157
|
-
case "gate":
|
|
5158
|
-
return doGate(db, exp, root);
|
|
5159
|
-
case "compress":
|
|
5160
|
-
return doCompress(db, root);
|
|
5173
|
+
const result = await spawnAgent("adversary", {
|
|
5174
|
+
experiment: {
|
|
5175
|
+
id: exp.id,
|
|
5176
|
+
slug: exp.slug,
|
|
5177
|
+
hypothesis: exp.hypothesis,
|
|
5178
|
+
status: exp.status,
|
|
5179
|
+
sub_type: exp.sub_type,
|
|
5180
|
+
builder_guidance: null
|
|
5181
|
+
},
|
|
5182
|
+
synthesis,
|
|
5183
|
+
taskPrompt
|
|
5184
|
+
}, root);
|
|
5185
|
+
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5186
|
+
if (result.truncated && !result.structured) {
|
|
5187
|
+
warn(`Adversary was truncated without structured output. Experiment stays at current status.`);
|
|
5188
|
+
} else {
|
|
5189
|
+
updateExperimentStatus(db, exp.id, "challenged");
|
|
5190
|
+
success(`Challenge complete for ${exp.slug}. Run \`majlis doubt\` or \`majlis verify\` next.`);
|
|
5161
5191
|
}
|
|
5162
5192
|
}
|
|
5163
|
-
async function
|
|
5164
|
-
transition(exp.status, "
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5193
|
+
async function doDoubt(db, exp, root) {
|
|
5194
|
+
transition(exp.status, "doubted" /* DOUBTED */);
|
|
5195
|
+
const expDocPath = path11.join(root, expDocRelPath(exp));
|
|
5196
|
+
const experimentDoc = truncateContext(readFileOrEmpty(expDocPath), CONTEXT_LIMITS.experimentDoc);
|
|
5197
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
5198
|
+
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
5199
|
+
let taskPrompt = `Doubt the work in experiment ${exp.slug}: ${exp.hypothesis}. Produce a doubt document with evidence for each doubt.`;
|
|
5200
|
+
if (experimentDoc) {
|
|
5201
|
+
taskPrompt += `
|
|
5202
|
+
|
|
5203
|
+
## Experiment Document (builder's artifact)
|
|
5204
|
+
<experiment_doc>
|
|
5205
|
+
${experimentDoc}
|
|
5206
|
+
</experiment_doc>`;
|
|
5207
|
+
}
|
|
5208
|
+
const result = await spawnAgent("critic", {
|
|
5173
5209
|
experiment: {
|
|
5174
5210
|
id: exp.id,
|
|
5175
5211
|
slug: exp.slug,
|
|
@@ -5177,391 +5213,66 @@ async function doGate(db, exp, root) {
|
|
|
5177
5213
|
status: exp.status,
|
|
5178
5214
|
sub_type: exp.sub_type,
|
|
5179
5215
|
builder_guidance: null
|
|
5216
|
+
// Critic does NOT see builder reasoning
|
|
5180
5217
|
},
|
|
5181
|
-
|
|
5218
|
+
synthesis,
|
|
5219
|
+
deadEnds: deadEnds.map((d) => ({
|
|
5182
5220
|
approach: d.approach,
|
|
5183
5221
|
why_failed: d.why_failed,
|
|
5184
5222
|
structural_constraint: d.structural_constraint
|
|
5185
5223
|
})),
|
|
5186
|
-
|
|
5187
|
-
synthesis,
|
|
5188
|
-
taskPrompt: `Gate-check hypothesis for experiment ${exp.slug}:
|
|
5189
|
-
"${exp.hypothesis}"
|
|
5190
|
-
|
|
5191
|
-
This is a FAST gate \u2014 decide in 1-2 turns. Do NOT read source code or large files. Use the synthesis, dead-ends, and fragility provided in your context. At most, do one targeted grep to verify a function name exists.
|
|
5192
|
-
|
|
5193
|
-
Check: (a) stale references \u2014 does the hypothesis reference specific lines, functions, or structures that may not exist? (b) dead-end overlap \u2014 does this hypothesis repeat an approach already ruled out by structural dead-ends? (c) scope \u2014 is this a single focused change, or does it try to do multiple things?
|
|
5194
|
-
|
|
5195
|
-
Output your gate_decision as "approve", "reject", or "flag" with reasoning.`
|
|
5224
|
+
taskPrompt
|
|
5196
5225
|
}, root);
|
|
5197
5226
|
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
if (decision === "reject") {
|
|
5201
|
-
insertDeadEnd(
|
|
5202
|
-
db,
|
|
5203
|
-
exp.id,
|
|
5204
|
-
exp.hypothesis ?? exp.slug,
|
|
5205
|
-
reason,
|
|
5206
|
-
`Gate rejected: ${reason}`,
|
|
5207
|
-
exp.sub_type,
|
|
5208
|
-
"procedural"
|
|
5209
|
-
);
|
|
5210
|
-
adminTransitionAndPersist(db, exp.id, "gated", "dead_end" /* DEAD_END */, "revert");
|
|
5211
|
-
warn(`Gate REJECTED for ${exp.slug}: ${reason}. Dead-ended.`);
|
|
5212
|
-
return;
|
|
5227
|
+
if (result.truncated && !result.structured) {
|
|
5228
|
+
warn(`Critic was truncated without structured output. Experiment stays at current status.`);
|
|
5213
5229
|
} else {
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
}
|
|
5217
|
-
updateExperimentStatus(db, exp.id, "gated");
|
|
5218
|
-
success(`Gate passed for ${exp.slug}. Run \`majlis build\` next.`);
|
|
5230
|
+
updateExperimentStatus(db, exp.id, "doubted");
|
|
5231
|
+
success(`Doubt pass complete for ${exp.slug}. Run \`majlis challenge\` or \`majlis verify\` next.`);
|
|
5219
5232
|
}
|
|
5220
5233
|
}
|
|
5221
|
-
async function
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
throw new Error(
|
|
5226
|
-
`Experiment "${exp.slug}" depends on "${exp.depends_on}" which is ${dep ? dep.status : "not found"}. Dependency must be merged before building.`
|
|
5227
|
-
);
|
|
5228
|
-
}
|
|
5229
|
-
}
|
|
5230
|
-
transition(exp.status, "building" /* BUILDING */);
|
|
5234
|
+
async function doScout(db, exp, root) {
|
|
5235
|
+
transition(exp.status, "scouted" /* SCOUTED */);
|
|
5236
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
5237
|
+
const fragility = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
5231
5238
|
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
5232
|
-
const
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
if (config.metrics?.command && existingBaseline.length === 0) {
|
|
5239
|
-
try {
|
|
5240
|
-
const output = (0, import_node_child_process7.execSync)(config.metrics.command, {
|
|
5241
|
-
cwd: root,
|
|
5242
|
-
encoding: "utf-8",
|
|
5243
|
-
timeout: 6e4,
|
|
5244
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5245
|
-
}).trim();
|
|
5246
|
-
const parsed = parseMetricsOutput(output);
|
|
5247
|
-
for (const m of parsed) {
|
|
5248
|
-
insertMetric(db, exp.id, "before", m.fixture, m.metric_name, m.metric_value);
|
|
5249
|
-
}
|
|
5250
|
-
if (parsed.length > 0) info(`Captured ${parsed.length} baseline metric(s).`);
|
|
5251
|
-
} catch {
|
|
5252
|
-
warn("Could not capture baseline metrics.");
|
|
5253
|
-
}
|
|
5254
|
-
}
|
|
5255
|
-
updateExperimentStatus(db, exp.id, "building");
|
|
5256
|
-
let taskPrompt = builderGuidance ? `Previous attempt was weak. Here is guidance for this attempt:
|
|
5257
|
-
${builderGuidance}
|
|
5239
|
+
const deadEndsSummary = deadEnds.map(
|
|
5240
|
+
(d) => `- [${d.category ?? "structural"}] ${d.approach}: ${d.why_failed}`
|
|
5241
|
+
).join("\n");
|
|
5242
|
+
let taskPrompt = `Search for alternative approaches to the problem in experiment ${exp.slug}: ${exp.hypothesis}. Look for contradictory approaches, solutions from other fields, and known limitations of the current approach.`;
|
|
5243
|
+
if (deadEndsSummary) {
|
|
5244
|
+
taskPrompt += `
|
|
5258
5245
|
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
taskPrompt += "\n\n## Confirmed Doubts (MUST address)\nThese weaknesses were confirmed by the verifier. Your build MUST address each one:\n";
|
|
5262
|
-
for (const d of confirmedDoubts) {
|
|
5263
|
-
taskPrompt += `- [${d.severity}] ${d.claim_doubted}: ${d.evidence_for_doubt}
|
|
5264
|
-
`;
|
|
5265
|
-
}
|
|
5246
|
+
## Known Dead Ends (avoid these approaches)
|
|
5247
|
+
${deadEndsSummary}`;
|
|
5266
5248
|
}
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5249
|
+
if (fragility) {
|
|
5250
|
+
taskPrompt += `
|
|
5251
|
+
|
|
5252
|
+
## Fragility Map (target these weak areas)
|
|
5253
|
+
${fragility}`;
|
|
5272
5254
|
}
|
|
5273
|
-
const result = await spawnAgent("
|
|
5255
|
+
const result = await spawnAgent("scout", {
|
|
5274
5256
|
experiment: {
|
|
5275
5257
|
id: exp.id,
|
|
5276
5258
|
slug: exp.slug,
|
|
5277
5259
|
hypothesis: exp.hypothesis,
|
|
5278
|
-
status:
|
|
5260
|
+
status: exp.status,
|
|
5279
5261
|
sub_type: exp.sub_type,
|
|
5280
|
-
builder_guidance:
|
|
5262
|
+
builder_guidance: null
|
|
5281
5263
|
},
|
|
5264
|
+
synthesis,
|
|
5265
|
+
fragility,
|
|
5282
5266
|
deadEnds: deadEnds.map((d) => ({
|
|
5283
5267
|
approach: d.approach,
|
|
5284
5268
|
why_failed: d.why_failed,
|
|
5285
5269
|
structural_constraint: d.structural_constraint
|
|
5286
5270
|
})),
|
|
5287
|
-
fragility,
|
|
5288
|
-
synthesis,
|
|
5289
|
-
confirmedDoubts,
|
|
5290
|
-
supplementaryContext: supplementaryContext || void 0,
|
|
5291
|
-
experimentLineage: lineage || void 0,
|
|
5292
5271
|
taskPrompt
|
|
5293
5272
|
}, root);
|
|
5294
5273
|
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5295
|
-
if (result.structured
|
|
5296
|
-
|
|
5297
|
-
db,
|
|
5298
|
-
exp.id,
|
|
5299
|
-
exp.hypothesis ?? exp.slug,
|
|
5300
|
-
result.structured.abandon.reason,
|
|
5301
|
-
result.structured.abandon.structural_constraint,
|
|
5302
|
-
exp.sub_type,
|
|
5303
|
-
"structural"
|
|
5304
|
-
);
|
|
5305
|
-
adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
|
|
5306
|
-
info(`Builder abandoned ${exp.slug}: ${result.structured.abandon.reason}`);
|
|
5307
|
-
return;
|
|
5308
|
-
}
|
|
5309
|
-
if (result.truncated && !result.structured) {
|
|
5310
|
-
warn(`Builder was truncated (hit max turns) without producing structured output.`);
|
|
5311
|
-
const recovery = await extractStructuredData("builder", result.output);
|
|
5312
|
-
if (recovery.data && !recovery.data.abandon) {
|
|
5313
|
-
info(`Recovered structured output from truncated builder (tier ${recovery.tier}).`);
|
|
5314
|
-
ingestStructuredOutput(db, exp.id, recovery.data);
|
|
5315
|
-
if (config.build?.pre_measure) {
|
|
5316
|
-
try {
|
|
5317
|
-
const [cmd, ...cmdArgs] = config.build.pre_measure.split(/\s+/);
|
|
5318
|
-
(0, import_node_child_process7.execFileSync)(cmd, cmdArgs, {
|
|
5319
|
-
cwd: root,
|
|
5320
|
-
encoding: "utf-8",
|
|
5321
|
-
timeout: 3e4,
|
|
5322
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5323
|
-
});
|
|
5324
|
-
} catch (err) {
|
|
5325
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5326
|
-
storeBuilderGuidance(
|
|
5327
|
-
db,
|
|
5328
|
-
exp.id,
|
|
5329
|
-
`Build verification failed after truncated recovery.
|
|
5330
|
-
Error: ${errMsg.slice(0, 500)}`
|
|
5331
|
-
);
|
|
5332
|
-
warn(`Build verification failed for ${exp.slug}. Staying at 'building'.`);
|
|
5333
|
-
return;
|
|
5334
|
-
}
|
|
5335
|
-
}
|
|
5336
|
-
if (config.metrics?.command) {
|
|
5337
|
-
try {
|
|
5338
|
-
const output = (0, import_node_child_process7.execSync)(config.metrics.command, {
|
|
5339
|
-
cwd: root,
|
|
5340
|
-
encoding: "utf-8",
|
|
5341
|
-
timeout: 6e4,
|
|
5342
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5343
|
-
}).trim();
|
|
5344
|
-
const parsed = parseMetricsOutput(output);
|
|
5345
|
-
for (const m of parsed) {
|
|
5346
|
-
insertMetric(db, exp.id, "after", m.fixture, m.metric_name, m.metric_value);
|
|
5347
|
-
}
|
|
5348
|
-
if (parsed.length > 0) info(`Captured ${parsed.length} post-build metric(s).`);
|
|
5349
|
-
} catch {
|
|
5350
|
-
}
|
|
5351
|
-
}
|
|
5352
|
-
gitCommitBuild(exp, root);
|
|
5353
|
-
if (recovery.tier === 3) {
|
|
5354
|
-
warn(`Builder output extracted via Haiku (tier 3). Data provenance degraded.`);
|
|
5355
|
-
const existing = getBuilderGuidance(db, exp.id) ?? "";
|
|
5356
|
-
storeBuilderGuidance(
|
|
5357
|
-
db,
|
|
5358
|
-
exp.id,
|
|
5359
|
-
existing + "\n[PROVENANCE WARNING] Builder structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny."
|
|
5360
|
-
);
|
|
5361
|
-
}
|
|
5362
|
-
updateExperimentStatus(db, exp.id, "built");
|
|
5363
|
-
success(`Build complete for ${exp.slug} (recovered from truncation). Run \`majlis doubt\` or \`majlis challenge\` next.`);
|
|
5364
|
-
} else if (recovery.data?.abandon) {
|
|
5365
|
-
insertDeadEnd(
|
|
5366
|
-
db,
|
|
5367
|
-
exp.id,
|
|
5368
|
-
exp.hypothesis ?? exp.slug,
|
|
5369
|
-
recovery.data.abandon.reason,
|
|
5370
|
-
recovery.data.abandon.structural_constraint,
|
|
5371
|
-
exp.sub_type,
|
|
5372
|
-
"structural"
|
|
5373
|
-
);
|
|
5374
|
-
adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
|
|
5375
|
-
info(`Builder abandoned ${exp.slug} (recovered from truncation): ${recovery.data.abandon.reason}`);
|
|
5376
|
-
} else {
|
|
5377
|
-
const tail = result.output.slice(-2e3).trim();
|
|
5378
|
-
if (tail) {
|
|
5379
|
-
storeBuilderGuidance(
|
|
5380
|
-
db,
|
|
5381
|
-
exp.id,
|
|
5382
|
-
`Builder was truncated. Last ~2000 chars of output:
|
|
5383
|
-
${tail}`
|
|
5384
|
-
);
|
|
5385
|
-
}
|
|
5386
|
-
warn(`Experiment stays at 'building'. Run \`majlis build\` to retry or \`majlis revert\` to abandon.`);
|
|
5387
|
-
}
|
|
5388
|
-
} else {
|
|
5389
|
-
if (config.build?.pre_measure) {
|
|
5390
|
-
try {
|
|
5391
|
-
const [cmd, ...cmdArgs] = config.build.pre_measure.split(/\s+/);
|
|
5392
|
-
(0, import_node_child_process7.execFileSync)(cmd, cmdArgs, {
|
|
5393
|
-
cwd: root,
|
|
5394
|
-
encoding: "utf-8",
|
|
5395
|
-
timeout: 3e4,
|
|
5396
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5397
|
-
});
|
|
5398
|
-
} catch (err) {
|
|
5399
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5400
|
-
const guidance = `Build verification failed after builder completion. Code may be syntactically broken or incomplete.
|
|
5401
|
-
Error: ${errMsg.slice(0, 500)}`;
|
|
5402
|
-
storeBuilderGuidance(db, exp.id, guidance);
|
|
5403
|
-
warn(`Build verification failed for ${exp.slug}. Staying at 'building'.`);
|
|
5404
|
-
warn(`Guidance stored for retry. Run \`majlis build\` to retry.`);
|
|
5405
|
-
return;
|
|
5406
|
-
}
|
|
5407
|
-
}
|
|
5408
|
-
if (config.metrics?.command) {
|
|
5409
|
-
try {
|
|
5410
|
-
const output = (0, import_node_child_process7.execSync)(config.metrics.command, {
|
|
5411
|
-
cwd: root,
|
|
5412
|
-
encoding: "utf-8",
|
|
5413
|
-
timeout: 6e4,
|
|
5414
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5415
|
-
}).trim();
|
|
5416
|
-
const parsed = parseMetricsOutput(output);
|
|
5417
|
-
for (const m of parsed) {
|
|
5418
|
-
insertMetric(db, exp.id, "after", m.fixture, m.metric_name, m.metric_value);
|
|
5419
|
-
}
|
|
5420
|
-
if (parsed.length > 0) info(`Captured ${parsed.length} post-build metric(s).`);
|
|
5421
|
-
} catch {
|
|
5422
|
-
warn("Could not capture post-build metrics.");
|
|
5423
|
-
}
|
|
5424
|
-
}
|
|
5425
|
-
gitCommitBuild(exp, root);
|
|
5426
|
-
if (result.extractionTier === 3) {
|
|
5427
|
-
warn(`Builder output extracted via Haiku (tier 3). Data provenance degraded.`);
|
|
5428
|
-
const existing = getBuilderGuidance(db, exp.id) ?? "";
|
|
5429
|
-
storeBuilderGuidance(
|
|
5430
|
-
db,
|
|
5431
|
-
exp.id,
|
|
5432
|
-
existing + "\n[PROVENANCE WARNING] Builder structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny."
|
|
5433
|
-
);
|
|
5434
|
-
}
|
|
5435
|
-
updateExperimentStatus(db, exp.id, "built");
|
|
5436
|
-
success(`Build complete for ${exp.slug}. Run \`majlis doubt\` or \`majlis challenge\` next.`);
|
|
5437
|
-
}
|
|
5438
|
-
}
|
|
5439
|
-
async function doChallenge(db, exp, root) {
|
|
5440
|
-
transition(exp.status, "challenged" /* CHALLENGED */);
|
|
5441
|
-
let gitDiff = "";
|
|
5442
|
-
try {
|
|
5443
|
-
gitDiff = (0, import_node_child_process7.execSync)('git diff main -- . ":!.majlis/"', {
|
|
5444
|
-
cwd: root,
|
|
5445
|
-
encoding: "utf-8",
|
|
5446
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5447
|
-
}).trim();
|
|
5448
|
-
} catch {
|
|
5449
|
-
}
|
|
5450
|
-
if (gitDiff.length > 8e3) gitDiff = gitDiff.slice(0, 8e3) + "\n[DIFF TRUNCATED]";
|
|
5451
|
-
const synthesis = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
5452
|
-
let taskPrompt = `Construct adversarial test cases for experiment ${exp.slug}: ${exp.hypothesis}`;
|
|
5453
|
-
if (gitDiff) {
|
|
5454
|
-
taskPrompt += `
|
|
5455
|
-
|
|
5456
|
-
## Code Changes (git diff main)
|
|
5457
|
-
\`\`\`diff
|
|
5458
|
-
${gitDiff}
|
|
5459
|
-
\`\`\``;
|
|
5460
|
-
}
|
|
5461
|
-
const result = await spawnAgent("adversary", {
|
|
5462
|
-
experiment: {
|
|
5463
|
-
id: exp.id,
|
|
5464
|
-
slug: exp.slug,
|
|
5465
|
-
hypothesis: exp.hypothesis,
|
|
5466
|
-
status: exp.status,
|
|
5467
|
-
sub_type: exp.sub_type,
|
|
5468
|
-
builder_guidance: null
|
|
5469
|
-
},
|
|
5470
|
-
synthesis,
|
|
5471
|
-
taskPrompt
|
|
5472
|
-
}, root);
|
|
5473
|
-
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5474
|
-
if (result.truncated && !result.structured) {
|
|
5475
|
-
warn(`Adversary was truncated without structured output. Experiment stays at current status.`);
|
|
5476
|
-
} else {
|
|
5477
|
-
updateExperimentStatus(db, exp.id, "challenged");
|
|
5478
|
-
success(`Challenge complete for ${exp.slug}. Run \`majlis doubt\` or \`majlis verify\` next.`);
|
|
5479
|
-
}
|
|
5480
|
-
}
|
|
5481
|
-
async function doDoubt(db, exp, root) {
|
|
5482
|
-
transition(exp.status, "doubted" /* DOUBTED */);
|
|
5483
|
-
const paddedNum = String(exp.id).padStart(3, "0");
|
|
5484
|
-
const expDocPath = path13.join(root, "docs", "experiments", `${paddedNum}-${exp.slug}.md`);
|
|
5485
|
-
const experimentDoc = truncateContext(readFileOrEmpty(expDocPath), CONTEXT_LIMITS.experimentDoc);
|
|
5486
|
-
const synthesis = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
5487
|
-
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
5488
|
-
let taskPrompt = `Doubt the work in experiment ${exp.slug}: ${exp.hypothesis}. Produce a doubt document with evidence for each doubt.`;
|
|
5489
|
-
if (experimentDoc) {
|
|
5490
|
-
taskPrompt += `
|
|
5491
|
-
|
|
5492
|
-
## Experiment Document (builder's artifact)
|
|
5493
|
-
<experiment_doc>
|
|
5494
|
-
${experimentDoc}
|
|
5495
|
-
</experiment_doc>`;
|
|
5496
|
-
}
|
|
5497
|
-
const result = await spawnAgent("critic", {
|
|
5498
|
-
experiment: {
|
|
5499
|
-
id: exp.id,
|
|
5500
|
-
slug: exp.slug,
|
|
5501
|
-
hypothesis: exp.hypothesis,
|
|
5502
|
-
status: exp.status,
|
|
5503
|
-
sub_type: exp.sub_type,
|
|
5504
|
-
builder_guidance: null
|
|
5505
|
-
// Critic does NOT see builder reasoning
|
|
5506
|
-
},
|
|
5507
|
-
synthesis,
|
|
5508
|
-
deadEnds: deadEnds.map((d) => ({
|
|
5509
|
-
approach: d.approach,
|
|
5510
|
-
why_failed: d.why_failed,
|
|
5511
|
-
structural_constraint: d.structural_constraint
|
|
5512
|
-
})),
|
|
5513
|
-
taskPrompt
|
|
5514
|
-
}, root);
|
|
5515
|
-
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5516
|
-
if (result.truncated && !result.structured) {
|
|
5517
|
-
warn(`Critic was truncated without structured output. Experiment stays at current status.`);
|
|
5518
|
-
} else {
|
|
5519
|
-
updateExperimentStatus(db, exp.id, "doubted");
|
|
5520
|
-
success(`Doubt pass complete for ${exp.slug}. Run \`majlis challenge\` or \`majlis verify\` next.`);
|
|
5521
|
-
}
|
|
5522
|
-
}
|
|
5523
|
-
async function doScout(db, exp, root) {
|
|
5524
|
-
transition(exp.status, "scouted" /* SCOUTED */);
|
|
5525
|
-
const synthesis = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
5526
|
-
const fragility = truncateContext(readFileOrEmpty(path13.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
5527
|
-
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
5528
|
-
const deadEndsSummary = deadEnds.map(
|
|
5529
|
-
(d) => `- [${d.category ?? "structural"}] ${d.approach}: ${d.why_failed}`
|
|
5530
|
-
).join("\n");
|
|
5531
|
-
let taskPrompt = `Search for alternative approaches to the problem in experiment ${exp.slug}: ${exp.hypothesis}. Look for contradictory approaches, solutions from other fields, and known limitations of the current approach.`;
|
|
5532
|
-
if (deadEndsSummary) {
|
|
5533
|
-
taskPrompt += `
|
|
5534
|
-
|
|
5535
|
-
## Known Dead Ends (avoid these approaches)
|
|
5536
|
-
${deadEndsSummary}`;
|
|
5537
|
-
}
|
|
5538
|
-
if (fragility) {
|
|
5539
|
-
taskPrompt += `
|
|
5540
|
-
|
|
5541
|
-
## Fragility Map (target these weak areas)
|
|
5542
|
-
${fragility}`;
|
|
5543
|
-
}
|
|
5544
|
-
const result = await spawnAgent("scout", {
|
|
5545
|
-
experiment: {
|
|
5546
|
-
id: exp.id,
|
|
5547
|
-
slug: exp.slug,
|
|
5548
|
-
hypothesis: exp.hypothesis,
|
|
5549
|
-
status: exp.status,
|
|
5550
|
-
sub_type: exp.sub_type,
|
|
5551
|
-
builder_guidance: null
|
|
5552
|
-
},
|
|
5553
|
-
synthesis,
|
|
5554
|
-
fragility,
|
|
5555
|
-
deadEnds: deadEnds.map((d) => ({
|
|
5556
|
-
approach: d.approach,
|
|
5557
|
-
why_failed: d.why_failed,
|
|
5558
|
-
structural_constraint: d.structural_constraint
|
|
5559
|
-
})),
|
|
5560
|
-
taskPrompt
|
|
5561
|
-
}, root);
|
|
5562
|
-
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5563
|
-
if (result.truncated && !result.structured) {
|
|
5564
|
-
warn(`Scout was truncated without structured output. Experiment stays at current status.`);
|
|
5274
|
+
if (result.truncated && !result.structured) {
|
|
5275
|
+
warn(`Scout was truncated without structured output. Experiment stays at current status.`);
|
|
5565
5276
|
return;
|
|
5566
5277
|
}
|
|
5567
5278
|
updateExperimentStatus(db, exp.id, "scouted");
|
|
@@ -5570,12 +5281,12 @@ ${fragility}`;
|
|
|
5570
5281
|
async function doVerify(db, exp, root) {
|
|
5571
5282
|
transition(exp.status, "verifying" /* VERIFYING */);
|
|
5572
5283
|
const doubts = getDoubtsByExperiment(db, exp.id);
|
|
5573
|
-
const challengeDir =
|
|
5284
|
+
const challengeDir = path11.join(root, "docs", "challenges");
|
|
5574
5285
|
let challenges = "";
|
|
5575
|
-
if (
|
|
5576
|
-
const files =
|
|
5286
|
+
if (fs11.existsSync(challengeDir)) {
|
|
5287
|
+
const files = fs11.readdirSync(challengeDir).filter((f) => f.includes(exp.slug) && f.endsWith(".md"));
|
|
5577
5288
|
for (const f of files) {
|
|
5578
|
-
challenges +=
|
|
5289
|
+
challenges += fs11.readFileSync(path11.join(challengeDir, f), "utf-8") + "\n\n";
|
|
5579
5290
|
}
|
|
5580
5291
|
}
|
|
5581
5292
|
const config = loadConfig(root);
|
|
@@ -5632,191 +5343,788 @@ async function doVerify(db, exp, root) {
|
|
|
5632
5343
|
if (verifierLineage) {
|
|
5633
5344
|
verifierTaskPrompt += "\n\n" + verifierLineage;
|
|
5634
5345
|
}
|
|
5635
|
-
const builderGuidanceForVerifier = getBuilderGuidance(db, exp.id);
|
|
5636
|
-
if (builderGuidanceForVerifier?.includes("[PROVENANCE WARNING]")) {
|
|
5637
|
-
verifierTaskPrompt += "\n\nNote: The builder's structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny.";
|
|
5346
|
+
const builderGuidanceForVerifier = getBuilderGuidance(db, exp.id);
|
|
5347
|
+
if (builderGuidanceForVerifier?.includes("[PROVENANCE WARNING]")) {
|
|
5348
|
+
verifierTaskPrompt += "\n\nNote: The builder's structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny.";
|
|
5349
|
+
}
|
|
5350
|
+
const result = await spawnAgent("verifier", {
|
|
5351
|
+
experiment: {
|
|
5352
|
+
id: exp.id,
|
|
5353
|
+
slug: exp.slug,
|
|
5354
|
+
hypothesis: exp.hypothesis,
|
|
5355
|
+
status: "verifying",
|
|
5356
|
+
sub_type: exp.sub_type,
|
|
5357
|
+
builder_guidance: null
|
|
5358
|
+
},
|
|
5359
|
+
doubts,
|
|
5360
|
+
challenges,
|
|
5361
|
+
metricComparisons: metricComparisons.length > 0 ? metricComparisons : void 0,
|
|
5362
|
+
supplementaryContext: verifierSupplementaryContext || void 0,
|
|
5363
|
+
experimentLineage: verifierLineage || void 0,
|
|
5364
|
+
taskPrompt: verifierTaskPrompt
|
|
5365
|
+
}, root);
|
|
5366
|
+
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5367
|
+
if (result.truncated && !result.structured) {
|
|
5368
|
+
warn(`Verifier was truncated without structured output. Experiment stays at 'verifying'.`);
|
|
5369
|
+
return;
|
|
5370
|
+
}
|
|
5371
|
+
if (result.structured?.doubt_resolutions) {
|
|
5372
|
+
const knownDoubtIds = new Set(doubts.map((d) => d.id));
|
|
5373
|
+
for (let i = 0; i < result.structured.doubt_resolutions.length; i++) {
|
|
5374
|
+
const dr = result.structured.doubt_resolutions[i];
|
|
5375
|
+
if (!dr.resolution) continue;
|
|
5376
|
+
if (dr.doubt_id && knownDoubtIds.has(dr.doubt_id)) {
|
|
5377
|
+
updateDoubtResolution(db, dr.doubt_id, dr.resolution);
|
|
5378
|
+
} else if (doubts[i]) {
|
|
5379
|
+
warn(`Doubt resolution ID ${dr.doubt_id} not found. Using ordinal fallback \u2192 DOUBT-${doubts[i].id}.`);
|
|
5380
|
+
updateDoubtResolution(db, doubts[i].id, dr.resolution);
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
updateExperimentStatus(db, exp.id, "verified");
|
|
5385
|
+
success(`Verification complete for ${exp.slug}. Run \`majlis resolve\` next.`);
|
|
5386
|
+
}
|
|
5387
|
+
async function doCompress(db, root) {
|
|
5388
|
+
const synthesisPath = path11.join(root, "docs", "synthesis", "current.md");
|
|
5389
|
+
const sizeBefore = fs11.existsSync(synthesisPath) ? fs11.statSync(synthesisPath).size : 0;
|
|
5390
|
+
const sessionCount = getSessionsSinceCompression(db);
|
|
5391
|
+
const dbExport = exportForCompressor(db);
|
|
5392
|
+
const result = await spawnAgent("compressor", {
|
|
5393
|
+
taskPrompt: "## Structured Data (CANONICAL \u2014 from SQLite database)\nThe database export below is the source of truth. docs/ files are agent artifacts that may contain stale or incorrect information. Cross-reference everything against this data.\n\n" + dbExport + "\n\n## Your Task\nRead ALL experiments, decisions, doubts, challenges, verification reports, reframes, and recent diffs. Cross-reference for contradictions, redundancies, and patterns. REWRITE docs/synthesis/current.md \u2014 shorter and denser. Update docs/synthesis/fragility.md with current weak areas. Update docs/synthesis/dead-ends.md with structural constraints from rejected experiments."
|
|
5394
|
+
}, root);
|
|
5395
|
+
const sizeAfter = fs11.existsSync(synthesisPath) ? fs11.statSync(synthesisPath).size : 0;
|
|
5396
|
+
recordCompression(db, sessionCount, sizeBefore, sizeAfter);
|
|
5397
|
+
autoCommit(root, "compress: update synthesis");
|
|
5398
|
+
success(`Compression complete. Synthesis: ${sizeBefore}B \u2192 ${sizeAfter}B`);
|
|
5399
|
+
}
|
|
5400
|
+
function gitCommitBuild(exp, cwd) {
|
|
5401
|
+
try {
|
|
5402
|
+
(0, import_node_child_process5.execSync)('git add -A -- ":!.majlis/"', { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
5403
|
+
const diff = (0, import_node_child_process5.execSync)("git diff --cached --stat", { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
5404
|
+
if (!diff) {
|
|
5405
|
+
info("No code changes to commit.");
|
|
5406
|
+
return;
|
|
5407
|
+
}
|
|
5408
|
+
const msg = `EXP-${String(exp.id).padStart(3, "0")}: ${exp.slug}
|
|
5409
|
+
|
|
5410
|
+
${exp.hypothesis ?? ""}`;
|
|
5411
|
+
(0, import_node_child_process5.execFileSync)("git", ["commit", "-m", msg], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
5412
|
+
info(`Committed builder changes on ${exp.branch}.`);
|
|
5413
|
+
} catch {
|
|
5414
|
+
warn("Could not auto-commit builder changes \u2014 commit manually before resolving.");
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
function loadExperimentContext(exp, root) {
|
|
5418
|
+
if (!exp.context_files) return "";
|
|
5419
|
+
let files;
|
|
5420
|
+
try {
|
|
5421
|
+
files = JSON.parse(exp.context_files);
|
|
5422
|
+
} catch {
|
|
5423
|
+
return "";
|
|
5424
|
+
}
|
|
5425
|
+
if (!Array.isArray(files) || files.length === 0) return "";
|
|
5426
|
+
const sections = ["## Experiment-Scoped Reference Material"];
|
|
5427
|
+
for (const relPath of files) {
|
|
5428
|
+
const absPath = path11.join(root, relPath);
|
|
5429
|
+
try {
|
|
5430
|
+
const content = fs11.readFileSync(absPath, "utf-8");
|
|
5431
|
+
sections.push(`### ${relPath}
|
|
5432
|
+
\`\`\`
|
|
5433
|
+
${content.slice(0, 8e3)}
|
|
5434
|
+
\`\`\``);
|
|
5435
|
+
} catch {
|
|
5436
|
+
sections.push(`### ${relPath}
|
|
5437
|
+
*(file not found)*`);
|
|
5438
|
+
}
|
|
5439
|
+
}
|
|
5440
|
+
return sections.join("\n\n");
|
|
5441
|
+
}
|
|
5442
|
+
function expDocRelPath(exp) {
|
|
5443
|
+
return `docs/experiments/${String(exp.id).padStart(3, "0")}-${exp.slug}.md`;
|
|
5444
|
+
}
|
|
5445
|
+
function resolveExperimentArg(db, args) {
|
|
5446
|
+
const slugArg = args.filter((a) => !a.startsWith("--"))[0];
|
|
5447
|
+
let exp;
|
|
5448
|
+
if (slugArg) {
|
|
5449
|
+
exp = getExperimentBySlug(db, slugArg);
|
|
5450
|
+
if (!exp) throw new Error(`Experiment not found: ${slugArg}`);
|
|
5451
|
+
} else {
|
|
5452
|
+
exp = getLatestExperiment(db);
|
|
5453
|
+
if (!exp) throw new Error('No active experiments. Run `majlis new "hypothesis"` first.');
|
|
5454
|
+
}
|
|
5455
|
+
return exp;
|
|
5456
|
+
}
|
|
5457
|
+
function ingestStructuredOutput(db, experimentId, structured) {
|
|
5458
|
+
if (!structured) return;
|
|
5459
|
+
db.transaction(() => {
|
|
5460
|
+
if (structured.decisions) {
|
|
5461
|
+
for (const d of structured.decisions) {
|
|
5462
|
+
insertDecision(db, experimentId, d.description, d.evidence_level, d.justification);
|
|
5463
|
+
}
|
|
5464
|
+
info(`Ingested ${structured.decisions.length} decision(s)`);
|
|
5465
|
+
}
|
|
5466
|
+
if (structured.grades) {
|
|
5467
|
+
for (const g of structured.grades) {
|
|
5468
|
+
insertVerification(
|
|
5469
|
+
db,
|
|
5470
|
+
experimentId,
|
|
5471
|
+
g.component,
|
|
5472
|
+
g.grade,
|
|
5473
|
+
g.provenance_intact ?? null,
|
|
5474
|
+
g.content_correct ?? null,
|
|
5475
|
+
g.notes ?? null
|
|
5476
|
+
);
|
|
5477
|
+
}
|
|
5478
|
+
info(`Ingested ${structured.grades.length} verification grade(s)`);
|
|
5479
|
+
}
|
|
5480
|
+
if (structured.doubts) {
|
|
5481
|
+
for (const d of structured.doubts) {
|
|
5482
|
+
insertDoubt(
|
|
5483
|
+
db,
|
|
5484
|
+
experimentId,
|
|
5485
|
+
d.claim_doubted,
|
|
5486
|
+
d.evidence_level_of_claim,
|
|
5487
|
+
d.evidence_for_doubt,
|
|
5488
|
+
d.severity
|
|
5489
|
+
);
|
|
5490
|
+
}
|
|
5491
|
+
info(`Ingested ${structured.doubts.length} doubt(s)`);
|
|
5492
|
+
}
|
|
5493
|
+
if (structured.challenges) {
|
|
5494
|
+
for (const c of structured.challenges) {
|
|
5495
|
+
insertChallenge(db, experimentId, c.description, c.reasoning);
|
|
5496
|
+
}
|
|
5497
|
+
info(`Ingested ${structured.challenges.length} challenge(s)`);
|
|
5498
|
+
}
|
|
5499
|
+
if (structured.reframe) {
|
|
5500
|
+
insertReframe(
|
|
5501
|
+
db,
|
|
5502
|
+
experimentId,
|
|
5503
|
+
structured.reframe.decomposition,
|
|
5504
|
+
JSON.stringify(structured.reframe.divergences),
|
|
5505
|
+
structured.reframe.recommendation
|
|
5506
|
+
);
|
|
5507
|
+
info(`Ingested reframe`);
|
|
5508
|
+
}
|
|
5509
|
+
if (structured.findings) {
|
|
5510
|
+
for (const f of structured.findings) {
|
|
5511
|
+
insertFinding(db, experimentId, f.approach, f.source, f.relevance, f.contradicts_current);
|
|
5512
|
+
}
|
|
5513
|
+
info(`Ingested ${structured.findings.length} finding(s)`);
|
|
5514
|
+
}
|
|
5515
|
+
})();
|
|
5516
|
+
}
|
|
5517
|
+
var fs11, path11, import_node_child_process5;
|
|
5518
|
+
var init_cycle = __esm({
|
|
5519
|
+
"src/commands/cycle.ts"() {
|
|
5520
|
+
"use strict";
|
|
5521
|
+
fs11 = __toESM(require("fs"));
|
|
5522
|
+
path11 = __toESM(require("path"));
|
|
5523
|
+
import_node_child_process5 = require("child_process");
|
|
5524
|
+
init_connection();
|
|
5525
|
+
init_queries();
|
|
5526
|
+
init_machine();
|
|
5527
|
+
init_types2();
|
|
5528
|
+
init_spawn();
|
|
5529
|
+
init_parse();
|
|
5530
|
+
init_resolve();
|
|
5531
|
+
init_config();
|
|
5532
|
+
init_metrics();
|
|
5533
|
+
init_git();
|
|
5534
|
+
init_format();
|
|
5535
|
+
}
|
|
5536
|
+
});
|
|
5537
|
+
|
|
5538
|
+
// src/commands/measure.ts
|
|
5539
|
+
var measure_exports = {};
|
|
5540
|
+
__export(measure_exports, {
|
|
5541
|
+
baseline: () => baseline,
|
|
5542
|
+
compare: () => compare,
|
|
5543
|
+
measure: () => measure
|
|
5544
|
+
});
|
|
5545
|
+
async function baseline(args) {
|
|
5546
|
+
await captureMetrics("before", args);
|
|
5547
|
+
}
|
|
5548
|
+
async function measure(args) {
|
|
5549
|
+
await captureMetrics("after", args);
|
|
5550
|
+
}
|
|
5551
|
+
async function captureMetrics(phase, args) {
|
|
5552
|
+
const root = findProjectRoot();
|
|
5553
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5554
|
+
const db = getDb(root);
|
|
5555
|
+
const config = loadConfig(root);
|
|
5556
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
5557
|
+
let exp;
|
|
5558
|
+
if (expIdStr !== void 0) {
|
|
5559
|
+
exp = getExperimentById(db, Number(expIdStr));
|
|
5560
|
+
} else {
|
|
5561
|
+
exp = getLatestExperiment(db);
|
|
5562
|
+
}
|
|
5563
|
+
if (!exp) throw new Error('No active experiment. Run `majlis new "hypothesis"` first.');
|
|
5564
|
+
if (config.build.pre_measure) {
|
|
5565
|
+
info(`Running pre-measure: ${config.build.pre_measure}`);
|
|
5566
|
+
try {
|
|
5567
|
+
(0, import_node_child_process6.execSync)(config.build.pre_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
|
|
5568
|
+
} catch {
|
|
5569
|
+
warn("Pre-measure command failed \u2014 continuing anyway.");
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
if (!config.metrics.command) {
|
|
5573
|
+
throw new Error("No metrics.command configured in .majlis/config.json");
|
|
5574
|
+
}
|
|
5575
|
+
info(`Running metrics: ${config.metrics.command}`);
|
|
5576
|
+
let metricsOutput;
|
|
5577
|
+
try {
|
|
5578
|
+
metricsOutput = (0, import_node_child_process6.execSync)(config.metrics.command, {
|
|
5579
|
+
cwd: root,
|
|
5580
|
+
encoding: "utf-8",
|
|
5581
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5582
|
+
});
|
|
5583
|
+
} catch (err) {
|
|
5584
|
+
throw new Error(`Metrics command failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5585
|
+
}
|
|
5586
|
+
const parsed = parseMetricsOutput(metricsOutput);
|
|
5587
|
+
if (parsed.length === 0) {
|
|
5588
|
+
warn("Metrics command returned no data.");
|
|
5589
|
+
return;
|
|
5590
|
+
}
|
|
5591
|
+
for (const m of parsed) {
|
|
5592
|
+
insertMetric(db, exp.id, phase, m.fixture, m.metric_name, m.metric_value);
|
|
5593
|
+
}
|
|
5594
|
+
success(`Captured ${parsed.length} metric(s) for ${exp.slug} (phase: ${phase})`);
|
|
5595
|
+
if (config.build.post_measure) {
|
|
5596
|
+
try {
|
|
5597
|
+
(0, import_node_child_process6.execSync)(config.build.post_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
|
|
5598
|
+
} catch {
|
|
5599
|
+
warn("Post-measure command failed.");
|
|
5600
|
+
}
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
async function compare(args, isJson) {
|
|
5604
|
+
const root = findProjectRoot();
|
|
5605
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5606
|
+
const db = getDb(root);
|
|
5607
|
+
const config = loadConfig(root);
|
|
5608
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
5609
|
+
let exp;
|
|
5610
|
+
if (expIdStr !== void 0) {
|
|
5611
|
+
exp = getExperimentById(db, Number(expIdStr));
|
|
5612
|
+
} else {
|
|
5613
|
+
exp = getLatestExperiment(db);
|
|
5614
|
+
}
|
|
5615
|
+
if (!exp) throw new Error("No active experiment.");
|
|
5616
|
+
const comparisons = compareMetrics(db, exp.id, config);
|
|
5617
|
+
if (comparisons.length === 0) {
|
|
5618
|
+
warn(`No before/after metrics to compare for ${exp.slug}. Run baseline and measure first.`);
|
|
5619
|
+
return;
|
|
5620
|
+
}
|
|
5621
|
+
if (isJson) {
|
|
5622
|
+
console.log(JSON.stringify({ experiment: exp.slug, comparisons }, null, 2));
|
|
5623
|
+
return;
|
|
5624
|
+
}
|
|
5625
|
+
header(`Metric Comparison \u2014 ${exp.slug}`);
|
|
5626
|
+
const regressions = comparisons.filter((c) => c.regression);
|
|
5627
|
+
const rows = comparisons.map((c) => [
|
|
5628
|
+
c.fixture,
|
|
5629
|
+
c.metric,
|
|
5630
|
+
String(c.before),
|
|
5631
|
+
String(c.after),
|
|
5632
|
+
formatDelta(c.delta),
|
|
5633
|
+
c.regression ? red("REGRESSION") : green("OK")
|
|
5634
|
+
]);
|
|
5635
|
+
console.log(table(["Fixture", "Metric", "Before", "After", "Delta", "Status"], rows));
|
|
5636
|
+
if (regressions.length > 0) {
|
|
5637
|
+
console.log();
|
|
5638
|
+
warn(`${regressions.length} regression(s) detected!`);
|
|
5639
|
+
} else {
|
|
5640
|
+
console.log();
|
|
5641
|
+
success("No regressions detected.");
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
function formatDelta(delta) {
|
|
5645
|
+
const prefix = delta > 0 ? "+" : "";
|
|
5646
|
+
return `${prefix}${delta.toFixed(4)}`;
|
|
5647
|
+
}
|
|
5648
|
+
var import_node_child_process6;
|
|
5649
|
+
var init_measure = __esm({
|
|
5650
|
+
"src/commands/measure.ts"() {
|
|
5651
|
+
"use strict";
|
|
5652
|
+
import_node_child_process6 = require("child_process");
|
|
5653
|
+
init_connection();
|
|
5654
|
+
init_queries();
|
|
5655
|
+
init_metrics();
|
|
5656
|
+
init_config();
|
|
5657
|
+
init_format();
|
|
5658
|
+
}
|
|
5659
|
+
});
|
|
5660
|
+
|
|
5661
|
+
// src/commands/experiment.ts
|
|
5662
|
+
var experiment_exports = {};
|
|
5663
|
+
__export(experiment_exports, {
|
|
5664
|
+
newExperiment: () => newExperiment,
|
|
5665
|
+
revert: () => revert
|
|
5666
|
+
});
|
|
5667
|
+
async function newExperiment(args) {
|
|
5668
|
+
const root = findProjectRoot();
|
|
5669
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5670
|
+
const hypothesis = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
5671
|
+
if (!hypothesis) {
|
|
5672
|
+
throw new Error('Usage: majlis new "hypothesis"');
|
|
5673
|
+
}
|
|
5674
|
+
const db = getDb(root);
|
|
5675
|
+
const config = loadConfig(root);
|
|
5676
|
+
let slug = getFlagValue(args, "--slug") ?? await generateSlug(hypothesis, root);
|
|
5677
|
+
let attempt = 0;
|
|
5678
|
+
while (getExperimentBySlug(db, slug + (attempt ? `-${attempt}` : ""))) {
|
|
5679
|
+
attempt++;
|
|
5680
|
+
}
|
|
5681
|
+
if (attempt > 0) {
|
|
5682
|
+
const original = slug;
|
|
5683
|
+
slug = `${slug}-${attempt}`;
|
|
5684
|
+
info(`Slug "${original}" already exists, using "${slug}"`);
|
|
5685
|
+
}
|
|
5686
|
+
const allExps = db.prepare("SELECT COUNT(*) as count FROM experiments").get();
|
|
5687
|
+
const num = allExps.count + 1;
|
|
5688
|
+
const paddedNum = String(num).padStart(3, "0");
|
|
5689
|
+
const branch = `exp/${paddedNum}-${slug}`;
|
|
5690
|
+
try {
|
|
5691
|
+
(0, import_node_child_process7.execFileSync)("git", ["checkout", "-b", branch], {
|
|
5692
|
+
cwd: root,
|
|
5693
|
+
encoding: "utf-8",
|
|
5694
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5695
|
+
});
|
|
5696
|
+
info(`Created branch: ${branch}`);
|
|
5697
|
+
} catch (err) {
|
|
5698
|
+
warn(`Could not create branch ${branch} \u2014 continuing without git branch.`);
|
|
5699
|
+
}
|
|
5700
|
+
const subType = getFlagValue(args, "--sub-type") ?? null;
|
|
5701
|
+
const dependsOn = getFlagValue(args, "--depends-on") ?? null;
|
|
5702
|
+
const contextArg = getFlagValue(args, "--context") ?? null;
|
|
5703
|
+
const contextFiles = contextArg ? contextArg.split(",").map((f) => f.trim()) : null;
|
|
5704
|
+
if (dependsOn) {
|
|
5705
|
+
const depExp = getExperimentBySlug(db, dependsOn);
|
|
5706
|
+
if (!depExp) {
|
|
5707
|
+
throw new Error(`Dependency experiment not found: ${dependsOn}`);
|
|
5708
|
+
}
|
|
5709
|
+
info(`Depends on: ${dependsOn} (status: ${depExp.status})`);
|
|
5710
|
+
}
|
|
5711
|
+
const exp = createExperiment(db, slug, branch, hypothesis, subType, null, dependsOn, contextFiles);
|
|
5712
|
+
if (contextFiles) {
|
|
5713
|
+
info(`Context files: ${contextFiles.join(", ")}`);
|
|
5714
|
+
}
|
|
5715
|
+
success(`Created experiment #${exp.id}: ${exp.slug}`);
|
|
5716
|
+
const docRelPath = expDocRelPath(exp);
|
|
5717
|
+
const docsDir = path12.join(root, "docs", "experiments");
|
|
5718
|
+
const templatePath = path12.join(docsDir, "_TEMPLATE.md");
|
|
5719
|
+
if (fs12.existsSync(templatePath)) {
|
|
5720
|
+
const template = fs12.readFileSync(templatePath, "utf-8");
|
|
5721
|
+
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]);
|
|
5722
|
+
const logPath = path12.join(root, docRelPath);
|
|
5723
|
+
fs12.writeFileSync(logPath, logContent);
|
|
5724
|
+
info(`Created experiment log: ${docRelPath}`);
|
|
5725
|
+
}
|
|
5726
|
+
autoCommit(root, `new: ${slug}`);
|
|
5727
|
+
if (config.cycle.auto_baseline_on_new_experiment && config.metrics.command) {
|
|
5728
|
+
info("Auto-baselining... (run `majlis baseline` to do this manually)");
|
|
5729
|
+
try {
|
|
5730
|
+
const { baseline: baseline2 } = await Promise.resolve().then(() => (init_measure(), measure_exports));
|
|
5731
|
+
await baseline2(["--experiment", String(exp.id)]);
|
|
5732
|
+
} catch (err) {
|
|
5733
|
+
warn("Auto-baseline failed \u2014 run `majlis baseline` manually.");
|
|
5734
|
+
}
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
async function revert(args) {
|
|
5738
|
+
const root = findProjectRoot();
|
|
5739
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5740
|
+
const db = getDb(root);
|
|
5741
|
+
let exp;
|
|
5742
|
+
const slugArg = args.filter((a) => !a.startsWith("--"))[0];
|
|
5743
|
+
if (slugArg) {
|
|
5744
|
+
exp = getExperimentBySlug(db, slugArg);
|
|
5745
|
+
if (!exp) throw new Error(`Experiment not found: ${slugArg}`);
|
|
5746
|
+
} else {
|
|
5747
|
+
exp = getLatestExperiment(db);
|
|
5748
|
+
if (!exp) throw new Error("No active experiments to revert.");
|
|
5749
|
+
}
|
|
5750
|
+
const reason = getFlagValue(args, "--reason") ?? "Manually reverted";
|
|
5751
|
+
const contextArg = getFlagValue(args, "--context") ?? null;
|
|
5752
|
+
const contextFiles = contextArg ? contextArg.split(",").map((f) => f.trim()) : [];
|
|
5753
|
+
let whyFailed = reason;
|
|
5754
|
+
let structuralConstraint = `Reverted: ${reason}`;
|
|
5755
|
+
let category = args.includes("--structural") ? "structural" : "procedural";
|
|
5756
|
+
try {
|
|
5757
|
+
const gitDiff = getGitDiff(root, exp.branch);
|
|
5758
|
+
const synthesis = truncateContext(
|
|
5759
|
+
readFileOrEmpty(path12.join(root, "docs", "synthesis", "current.md")),
|
|
5760
|
+
CONTEXT_LIMITS.synthesis
|
|
5761
|
+
);
|
|
5762
|
+
const fragility = truncateContext(
|
|
5763
|
+
readFileOrEmpty(path12.join(root, "docs", "synthesis", "fragility.md")),
|
|
5764
|
+
CONTEXT_LIMITS.fragility
|
|
5765
|
+
);
|
|
5766
|
+
const deadEnds = exp.sub_type ? listStructuralDeadEndsBySubType(db, exp.sub_type) : listStructuralDeadEnds(db);
|
|
5767
|
+
let supplementary = "";
|
|
5768
|
+
if (contextFiles.length > 0) {
|
|
5769
|
+
const sections = ["## Artifact Files (pointed to by --context)"];
|
|
5770
|
+
for (const relPath of contextFiles) {
|
|
5771
|
+
const absPath = path12.join(root, relPath);
|
|
5772
|
+
try {
|
|
5773
|
+
const content = fs12.readFileSync(absPath, "utf-8");
|
|
5774
|
+
sections.push(`### ${relPath}
|
|
5775
|
+
\`\`\`
|
|
5776
|
+
${content.slice(0, 12e3)}
|
|
5777
|
+
\`\`\``);
|
|
5778
|
+
} catch {
|
|
5779
|
+
sections.push(`### ${relPath}
|
|
5780
|
+
*(file not found)*`);
|
|
5781
|
+
}
|
|
5782
|
+
}
|
|
5783
|
+
supplementary = sections.join("\n\n");
|
|
5784
|
+
}
|
|
5785
|
+
let taskPrompt = `Analyze this reverted experiment and produce a structured dead-end record.
|
|
5786
|
+
|
|
5787
|
+
`;
|
|
5788
|
+
taskPrompt += `## Experiment
|
|
5789
|
+
- Slug: ${exp.slug}
|
|
5790
|
+
- Hypothesis: ${exp.hypothesis ?? "(none)"}
|
|
5791
|
+
`;
|
|
5792
|
+
taskPrompt += `- Status at revert: ${exp.status}
|
|
5793
|
+
- Sub-type: ${exp.sub_type ?? "(none)"}
|
|
5794
|
+
|
|
5795
|
+
`;
|
|
5796
|
+
taskPrompt += `## User's Reason for Reverting
|
|
5797
|
+
${reason}
|
|
5798
|
+
|
|
5799
|
+
`;
|
|
5800
|
+
if (gitDiff) {
|
|
5801
|
+
taskPrompt += `## Git Diff (branch vs main)
|
|
5802
|
+
\`\`\`diff
|
|
5803
|
+
${gitDiff.slice(0, 15e3)}
|
|
5804
|
+
\`\`\`
|
|
5805
|
+
|
|
5806
|
+
`;
|
|
5807
|
+
}
|
|
5808
|
+
if (supplementary) {
|
|
5809
|
+
taskPrompt += `${supplementary}
|
|
5810
|
+
|
|
5811
|
+
`;
|
|
5812
|
+
}
|
|
5813
|
+
taskPrompt += "Produce a specific structural constraint. Include scope (what this applies to and does NOT apply to).";
|
|
5814
|
+
info("Running post-mortem analysis...");
|
|
5815
|
+
const result = await spawnAgent("postmortem", {
|
|
5816
|
+
experiment: {
|
|
5817
|
+
id: exp.id,
|
|
5818
|
+
slug: exp.slug,
|
|
5819
|
+
hypothesis: exp.hypothesis,
|
|
5820
|
+
status: exp.status,
|
|
5821
|
+
sub_type: exp.sub_type,
|
|
5822
|
+
builder_guidance: null
|
|
5823
|
+
},
|
|
5824
|
+
deadEnds: deadEnds.map((d) => ({
|
|
5825
|
+
approach: d.approach,
|
|
5826
|
+
why_failed: d.why_failed,
|
|
5827
|
+
structural_constraint: d.structural_constraint
|
|
5828
|
+
})),
|
|
5829
|
+
fragility,
|
|
5830
|
+
synthesis,
|
|
5831
|
+
supplementaryContext: supplementary || void 0,
|
|
5832
|
+
taskPrompt
|
|
5833
|
+
}, root);
|
|
5834
|
+
if (result.structured?.postmortem) {
|
|
5835
|
+
const pm = result.structured.postmortem;
|
|
5836
|
+
whyFailed = pm.why_failed;
|
|
5837
|
+
structuralConstraint = pm.structural_constraint;
|
|
5838
|
+
category = pm.category;
|
|
5839
|
+
success("Post-mortem analysis complete.");
|
|
5840
|
+
} else {
|
|
5841
|
+
warn("Post-mortem agent did not produce structured output. Using --reason text.");
|
|
5842
|
+
}
|
|
5843
|
+
} catch (err) {
|
|
5844
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5845
|
+
warn(`Post-mortem agent failed: ${msg}. Using --reason text.`);
|
|
5846
|
+
}
|
|
5847
|
+
insertDeadEnd(
|
|
5848
|
+
db,
|
|
5849
|
+
exp.id,
|
|
5850
|
+
exp.hypothesis ?? exp.slug,
|
|
5851
|
+
whyFailed,
|
|
5852
|
+
structuralConstraint,
|
|
5853
|
+
exp.sub_type,
|
|
5854
|
+
category
|
|
5855
|
+
);
|
|
5856
|
+
if (exp.gate_rejection_reason) clearGateRejection(db, exp.id);
|
|
5857
|
+
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "revert");
|
|
5858
|
+
handleDeadEndGit(exp, root);
|
|
5859
|
+
info(`Experiment ${exp.slug} reverted to dead-end.`);
|
|
5860
|
+
info(`Constraint: ${structuralConstraint.slice(0, 120)}${structuralConstraint.length > 120 ? "..." : ""}`);
|
|
5861
|
+
}
|
|
5862
|
+
function getGitDiff(root, branch) {
|
|
5863
|
+
try {
|
|
5864
|
+
return (0, import_node_child_process7.execFileSync)("git", ["diff", `main...${branch}`, "--stat", "--patch"], {
|
|
5865
|
+
cwd: root,
|
|
5866
|
+
encoding: "utf-8",
|
|
5867
|
+
maxBuffer: 1024 * 1024,
|
|
5868
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5869
|
+
}).trim();
|
|
5870
|
+
} catch {
|
|
5871
|
+
try {
|
|
5872
|
+
return (0, import_node_child_process7.execFileSync)("git", ["diff", `master...${branch}`, "--stat", "--patch"], {
|
|
5873
|
+
cwd: root,
|
|
5874
|
+
encoding: "utf-8",
|
|
5875
|
+
maxBuffer: 1024 * 1024,
|
|
5876
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5877
|
+
}).trim();
|
|
5878
|
+
} catch {
|
|
5879
|
+
return null;
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
var fs12, path12, import_node_child_process7;
|
|
5884
|
+
var init_experiment = __esm({
|
|
5885
|
+
"src/commands/experiment.ts"() {
|
|
5886
|
+
"use strict";
|
|
5887
|
+
fs12 = __toESM(require("fs"));
|
|
5888
|
+
path12 = __toESM(require("path"));
|
|
5889
|
+
import_node_child_process7 = require("child_process");
|
|
5890
|
+
init_connection();
|
|
5891
|
+
init_queries();
|
|
5892
|
+
init_machine();
|
|
5893
|
+
init_types2();
|
|
5894
|
+
init_config();
|
|
5895
|
+
init_spawn();
|
|
5896
|
+
init_git();
|
|
5897
|
+
init_cycle();
|
|
5898
|
+
init_format();
|
|
5899
|
+
}
|
|
5900
|
+
});
|
|
5901
|
+
|
|
5902
|
+
// src/commands/session.ts
|
|
5903
|
+
var session_exports = {};
|
|
5904
|
+
__export(session_exports, {
|
|
5905
|
+
session: () => session
|
|
5906
|
+
});
|
|
5907
|
+
async function session(args) {
|
|
5908
|
+
const subcommand = args[0];
|
|
5909
|
+
if (!subcommand || subcommand !== "start" && subcommand !== "end") {
|
|
5910
|
+
throw new Error('Usage: majlis session start "intent" | majlis session end');
|
|
5911
|
+
}
|
|
5912
|
+
const root = findProjectRoot();
|
|
5913
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5914
|
+
const db = getDb(root);
|
|
5915
|
+
if (subcommand === "start") {
|
|
5916
|
+
const intent = args.slice(1).filter((a) => !a.startsWith("--")).join(" ");
|
|
5917
|
+
if (!intent) {
|
|
5918
|
+
throw new Error('Usage: majlis session start "intent"');
|
|
5919
|
+
}
|
|
5920
|
+
const existing = getActiveSession(db);
|
|
5921
|
+
if (existing) {
|
|
5922
|
+
warn(`Session already active: "${existing.intent}" (started ${existing.started_at})`);
|
|
5923
|
+
warn("End it first with `majlis session end`.");
|
|
5924
|
+
return;
|
|
5925
|
+
}
|
|
5926
|
+
const latestExp = getLatestExperiment(db);
|
|
5927
|
+
const sess = startSession(db, intent, latestExp?.id ?? null);
|
|
5928
|
+
success(`Session started: "${intent}" (id: ${sess.id})`);
|
|
5929
|
+
if (latestExp) {
|
|
5930
|
+
info(`Linked to experiment: ${latestExp.slug} (${latestExp.status})`);
|
|
5931
|
+
}
|
|
5932
|
+
} else {
|
|
5933
|
+
const active = getActiveSession(db);
|
|
5934
|
+
if (!active) {
|
|
5935
|
+
throw new Error("No active session to end.");
|
|
5936
|
+
}
|
|
5937
|
+
const accomplished = getFlagValue(args, "--accomplished") ?? null;
|
|
5938
|
+
const unfinished = getFlagValue(args, "--unfinished") ?? null;
|
|
5939
|
+
const fragility = getFlagValue(args, "--fragility") ?? null;
|
|
5940
|
+
endSession(db, active.id, accomplished, unfinished, fragility);
|
|
5941
|
+
success(`Session ended: "${active.intent}"`);
|
|
5942
|
+
if (accomplished) info(`Accomplished: ${accomplished}`);
|
|
5943
|
+
if (unfinished) info(`Unfinished: ${unfinished}`);
|
|
5944
|
+
if (fragility) warn(`New fragility: ${fragility}`);
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
var init_session = __esm({
|
|
5948
|
+
"src/commands/session.ts"() {
|
|
5949
|
+
"use strict";
|
|
5950
|
+
init_connection();
|
|
5951
|
+
init_queries();
|
|
5952
|
+
init_config();
|
|
5953
|
+
init_format();
|
|
5954
|
+
}
|
|
5955
|
+
});
|
|
5956
|
+
|
|
5957
|
+
// src/commands/query.ts
|
|
5958
|
+
var query_exports = {};
|
|
5959
|
+
__export(query_exports, {
|
|
5960
|
+
query: () => query3
|
|
5961
|
+
});
|
|
5962
|
+
async function query3(command, args, isJson) {
|
|
5963
|
+
const root = findProjectRoot();
|
|
5964
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5965
|
+
const db = getDb(root);
|
|
5966
|
+
switch (command) {
|
|
5967
|
+
case "decisions":
|
|
5968
|
+
return queryDecisions(db, args, isJson);
|
|
5969
|
+
case "dead-ends":
|
|
5970
|
+
return queryDeadEnds(db, args, isJson);
|
|
5971
|
+
case "fragility":
|
|
5972
|
+
return queryFragility(root, isJson);
|
|
5973
|
+
case "history":
|
|
5974
|
+
return queryHistory(db, args, isJson);
|
|
5975
|
+
case "circuit-breakers":
|
|
5976
|
+
return queryCircuitBreakers(db, root, isJson);
|
|
5977
|
+
case "check-commit":
|
|
5978
|
+
return checkCommit(db);
|
|
5979
|
+
}
|
|
5980
|
+
}
|
|
5981
|
+
function queryDecisions(db, args, isJson) {
|
|
5982
|
+
const level = getFlagValue(args, "--level");
|
|
5983
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
5984
|
+
const experimentId = expIdStr !== void 0 ? Number(expIdStr) : void 0;
|
|
5985
|
+
const decisions = listAllDecisions(db, level, experimentId);
|
|
5986
|
+
if (isJson) {
|
|
5987
|
+
console.log(JSON.stringify(decisions, null, 2));
|
|
5988
|
+
return;
|
|
5989
|
+
}
|
|
5990
|
+
if (decisions.length === 0) {
|
|
5991
|
+
info("No decisions found.");
|
|
5992
|
+
return;
|
|
5993
|
+
}
|
|
5994
|
+
header("Decisions");
|
|
5995
|
+
const rows = decisions.map((d) => [
|
|
5996
|
+
String(d.id),
|
|
5997
|
+
String(d.experiment_id),
|
|
5998
|
+
evidenceColor(d.evidence_level),
|
|
5999
|
+
d.description.slice(0, 60) + (d.description.length > 60 ? "..." : ""),
|
|
6000
|
+
d.status
|
|
6001
|
+
]);
|
|
6002
|
+
console.log(table(["ID", "Exp", "Level", "Description", "Status"], rows));
|
|
6003
|
+
}
|
|
6004
|
+
function queryDeadEnds(db, args, isJson) {
|
|
6005
|
+
const subType = getFlagValue(args, "--sub-type");
|
|
6006
|
+
const searchTerm = getFlagValue(args, "--search");
|
|
6007
|
+
let deadEnds;
|
|
6008
|
+
if (subType) {
|
|
6009
|
+
deadEnds = listDeadEndsBySubType(db, subType);
|
|
6010
|
+
} else if (searchTerm) {
|
|
6011
|
+
deadEnds = searchDeadEnds(db, searchTerm);
|
|
6012
|
+
} else {
|
|
6013
|
+
deadEnds = listAllDeadEnds(db);
|
|
6014
|
+
}
|
|
6015
|
+
if (isJson) {
|
|
6016
|
+
console.log(JSON.stringify(deadEnds, null, 2));
|
|
6017
|
+
return;
|
|
5638
6018
|
}
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
id: exp.id,
|
|
5642
|
-
slug: exp.slug,
|
|
5643
|
-
hypothesis: exp.hypothesis,
|
|
5644
|
-
status: "verifying",
|
|
5645
|
-
sub_type: exp.sub_type,
|
|
5646
|
-
builder_guidance: null
|
|
5647
|
-
},
|
|
5648
|
-
doubts,
|
|
5649
|
-
challenges,
|
|
5650
|
-
metricComparisons: metricComparisons.length > 0 ? metricComparisons : void 0,
|
|
5651
|
-
supplementaryContext: verifierSupplementaryContext || void 0,
|
|
5652
|
-
experimentLineage: verifierLineage || void 0,
|
|
5653
|
-
taskPrompt: verifierTaskPrompt
|
|
5654
|
-
}, root);
|
|
5655
|
-
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5656
|
-
if (result.truncated && !result.structured) {
|
|
5657
|
-
warn(`Verifier was truncated without structured output. Experiment stays at 'verifying'.`);
|
|
6019
|
+
if (deadEnds.length === 0) {
|
|
6020
|
+
info("No dead-ends recorded.");
|
|
5658
6021
|
return;
|
|
5659
6022
|
}
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
6023
|
+
header("Dead-End Registry");
|
|
6024
|
+
const rows = deadEnds.map((d) => [
|
|
6025
|
+
String(d.id),
|
|
6026
|
+
d.sub_type ?? "\u2014",
|
|
6027
|
+
d.approach.slice(0, 40) + (d.approach.length > 40 ? "..." : ""),
|
|
6028
|
+
d.structural_constraint.slice(0, 40) + (d.structural_constraint.length > 40 ? "..." : "")
|
|
6029
|
+
]);
|
|
6030
|
+
console.log(table(["ID", "Sub-Type", "Approach", "Constraint"], rows));
|
|
6031
|
+
}
|
|
6032
|
+
function queryFragility(root, isJson) {
|
|
6033
|
+
const fragPath = path13.join(root, "docs", "synthesis", "fragility.md");
|
|
6034
|
+
if (!fs13.existsSync(fragPath)) {
|
|
6035
|
+
info("No fragility map found.");
|
|
6036
|
+
return;
|
|
5672
6037
|
}
|
|
5673
|
-
|
|
5674
|
-
|
|
6038
|
+
const content = fs13.readFileSync(fragPath, "utf-8");
|
|
6039
|
+
if (isJson) {
|
|
6040
|
+
console.log(JSON.stringify({ content }, null, 2));
|
|
6041
|
+
return;
|
|
6042
|
+
}
|
|
6043
|
+
header("Fragility Map");
|
|
6044
|
+
console.log(content);
|
|
5675
6045
|
}
|
|
5676
|
-
|
|
5677
|
-
const
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
const
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
6046
|
+
function queryHistory(db, args, isJson) {
|
|
6047
|
+
const fixture = args.filter((a) => !a.startsWith("--"))[0];
|
|
6048
|
+
if (!fixture) {
|
|
6049
|
+
throw new Error("Usage: majlis history <fixture>");
|
|
6050
|
+
}
|
|
6051
|
+
const history = getMetricHistoryByFixture(db, fixture);
|
|
6052
|
+
if (isJson) {
|
|
6053
|
+
console.log(JSON.stringify(history, null, 2));
|
|
6054
|
+
return;
|
|
6055
|
+
}
|
|
6056
|
+
if (history.length === 0) {
|
|
6057
|
+
info(`No metric history for fixture: ${fixture}`);
|
|
6058
|
+
return;
|
|
6059
|
+
}
|
|
6060
|
+
header(`Metric History \u2014 ${fixture}`);
|
|
6061
|
+
const rows = history.map((h) => [
|
|
6062
|
+
String(h.experiment_id),
|
|
6063
|
+
h.experiment_slug ?? "\u2014",
|
|
6064
|
+
h.phase,
|
|
6065
|
+
h.metric_name,
|
|
6066
|
+
String(h.metric_value),
|
|
6067
|
+
h.captured_at
|
|
6068
|
+
]);
|
|
6069
|
+
console.log(table(["Exp", "Slug", "Phase", "Metric", "Value", "Captured"], rows));
|
|
5688
6070
|
}
|
|
5689
|
-
function
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
${exp.hypothesis ?? ""}`;
|
|
5700
|
-
(0, import_node_child_process7.execFileSync)("git", ["commit", "-m", msg], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
5701
|
-
info(`Committed builder changes on ${exp.branch}.`);
|
|
5702
|
-
} catch {
|
|
5703
|
-
warn("Could not auto-commit builder changes \u2014 commit manually before resolving.");
|
|
6071
|
+
function queryCircuitBreakers(db, root, isJson) {
|
|
6072
|
+
const config = loadConfig(root);
|
|
6073
|
+
const states = getAllCircuitBreakerStates(db, config.cycle.circuit_breaker_threshold);
|
|
6074
|
+
if (isJson) {
|
|
6075
|
+
console.log(JSON.stringify(states, null, 2));
|
|
6076
|
+
return;
|
|
6077
|
+
}
|
|
6078
|
+
if (states.length === 0) {
|
|
6079
|
+
info("No circuit breaker data.");
|
|
6080
|
+
return;
|
|
5704
6081
|
}
|
|
6082
|
+
header("Circuit Breakers");
|
|
6083
|
+
const rows = states.map((s) => [
|
|
6084
|
+
s.sub_type,
|
|
6085
|
+
String(s.failure_count),
|
|
6086
|
+
String(config.cycle.circuit_breaker_threshold),
|
|
6087
|
+
s.tripped ? red("TRIPPED") : green("OK")
|
|
6088
|
+
]);
|
|
6089
|
+
console.log(table(["Sub-Type", "Failures", "Threshold", "Status"], rows));
|
|
5705
6090
|
}
|
|
5706
|
-
function
|
|
5707
|
-
|
|
5708
|
-
let files;
|
|
6091
|
+
function checkCommit(db) {
|
|
6092
|
+
let stdinData = "";
|
|
5709
6093
|
try {
|
|
5710
|
-
|
|
6094
|
+
stdinData = fs13.readFileSync(0, "utf-8");
|
|
5711
6095
|
} catch {
|
|
5712
|
-
return "";
|
|
5713
6096
|
}
|
|
5714
|
-
if (
|
|
5715
|
-
const sections = ["## Experiment-Scoped Reference Material"];
|
|
5716
|
-
for (const relPath of files) {
|
|
5717
|
-
const absPath = path13.join(root, relPath);
|
|
6097
|
+
if (stdinData) {
|
|
5718
6098
|
try {
|
|
5719
|
-
const
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
6099
|
+
const hookInput = JSON.parse(stdinData);
|
|
6100
|
+
const command = hookInput?.tool_input?.command ?? "";
|
|
6101
|
+
if (!command.includes("git commit")) {
|
|
6102
|
+
return;
|
|
6103
|
+
}
|
|
5724
6104
|
} catch {
|
|
5725
|
-
sections.push(`### ${relPath}
|
|
5726
|
-
*(file not found)*`);
|
|
5727
6105
|
}
|
|
5728
6106
|
}
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
} else {
|
|
5738
|
-
exp = getLatestExperiment(db);
|
|
5739
|
-
if (!exp) throw new Error('No active experiments. Run `majlis new "hypothesis"` first.');
|
|
5740
|
-
}
|
|
5741
|
-
return exp;
|
|
5742
|
-
}
|
|
5743
|
-
function ingestStructuredOutput(db, experimentId, structured) {
|
|
5744
|
-
if (!structured) return;
|
|
5745
|
-
db.transaction(() => {
|
|
5746
|
-
if (structured.decisions) {
|
|
5747
|
-
for (const d of structured.decisions) {
|
|
5748
|
-
insertDecision(db, experimentId, d.description, d.evidence_level, d.justification);
|
|
5749
|
-
}
|
|
5750
|
-
info(`Ingested ${structured.decisions.length} decision(s)`);
|
|
5751
|
-
}
|
|
5752
|
-
if (structured.grades) {
|
|
5753
|
-
for (const g of structured.grades) {
|
|
5754
|
-
insertVerification(
|
|
5755
|
-
db,
|
|
5756
|
-
experimentId,
|
|
5757
|
-
g.component,
|
|
5758
|
-
g.grade,
|
|
5759
|
-
g.provenance_intact ?? null,
|
|
5760
|
-
g.content_correct ?? null,
|
|
5761
|
-
g.notes ?? null
|
|
5762
|
-
);
|
|
5763
|
-
}
|
|
5764
|
-
info(`Ingested ${structured.grades.length} verification grade(s)`);
|
|
5765
|
-
}
|
|
5766
|
-
if (structured.doubts) {
|
|
5767
|
-
for (const d of structured.doubts) {
|
|
5768
|
-
insertDoubt(
|
|
5769
|
-
db,
|
|
5770
|
-
experimentId,
|
|
5771
|
-
d.claim_doubted,
|
|
5772
|
-
d.evidence_level_of_claim,
|
|
5773
|
-
d.evidence_for_doubt,
|
|
5774
|
-
d.severity
|
|
5775
|
-
);
|
|
5776
|
-
}
|
|
5777
|
-
info(`Ingested ${structured.doubts.length} doubt(s)`);
|
|
5778
|
-
}
|
|
5779
|
-
if (structured.challenges) {
|
|
5780
|
-
for (const c of structured.challenges) {
|
|
5781
|
-
insertChallenge(db, experimentId, c.description, c.reasoning);
|
|
5782
|
-
}
|
|
5783
|
-
info(`Ingested ${structured.challenges.length} challenge(s)`);
|
|
5784
|
-
}
|
|
5785
|
-
if (structured.reframe) {
|
|
5786
|
-
insertReframe(
|
|
5787
|
-
db,
|
|
5788
|
-
experimentId,
|
|
5789
|
-
structured.reframe.decomposition,
|
|
5790
|
-
JSON.stringify(structured.reframe.divergences),
|
|
5791
|
-
structured.reframe.recommendation
|
|
5792
|
-
);
|
|
5793
|
-
info(`Ingested reframe`);
|
|
5794
|
-
}
|
|
5795
|
-
if (structured.findings) {
|
|
5796
|
-
for (const f of structured.findings) {
|
|
5797
|
-
insertFinding(db, experimentId, f.approach, f.source, f.relevance, f.contradicts_current);
|
|
5798
|
-
}
|
|
5799
|
-
info(`Ingested ${structured.findings.length} finding(s)`);
|
|
6107
|
+
const active = listActiveExperiments(db);
|
|
6108
|
+
const unverified = active.filter(
|
|
6109
|
+
(e) => !["merged", "dead_end", "verified", "resolved", "compressed"].includes(e.status)
|
|
6110
|
+
);
|
|
6111
|
+
if (unverified.length > 0) {
|
|
6112
|
+
console.error(`[majlis] ${unverified.length} unverified experiment(s):`);
|
|
6113
|
+
for (const e of unverified) {
|
|
6114
|
+
console.error(` - ${e.slug} (${e.status})`);
|
|
5800
6115
|
}
|
|
5801
|
-
|
|
6116
|
+
process.exit(1);
|
|
6117
|
+
}
|
|
5802
6118
|
}
|
|
5803
|
-
var fs13, path13
|
|
5804
|
-
var
|
|
5805
|
-
"src/commands/
|
|
6119
|
+
var fs13, path13;
|
|
6120
|
+
var init_query = __esm({
|
|
6121
|
+
"src/commands/query.ts"() {
|
|
5806
6122
|
"use strict";
|
|
5807
6123
|
fs13 = __toESM(require("fs"));
|
|
5808
6124
|
path13 = __toESM(require("path"));
|
|
5809
|
-
import_node_child_process7 = require("child_process");
|
|
5810
6125
|
init_connection();
|
|
5811
6126
|
init_queries();
|
|
5812
|
-
init_machine();
|
|
5813
|
-
init_types2();
|
|
5814
|
-
init_spawn();
|
|
5815
|
-
init_parse();
|
|
5816
|
-
init_resolve();
|
|
5817
6127
|
init_config();
|
|
5818
|
-
init_metrics();
|
|
5819
|
-
init_git();
|
|
5820
6128
|
init_format();
|
|
5821
6129
|
}
|
|
5822
6130
|
});
|
|
@@ -6011,13 +6319,14 @@ async function next(args, isJson) {
|
|
|
6011
6319
|
exp = found;
|
|
6012
6320
|
}
|
|
6013
6321
|
const auto = args.includes("--auto");
|
|
6322
|
+
const overrideGate = args.includes("--override-gate");
|
|
6014
6323
|
if (auto) {
|
|
6015
6324
|
await runAutoLoop(db, exp, config, root, isJson);
|
|
6016
6325
|
} else {
|
|
6017
|
-
await runNextStep(db, exp, config, root, isJson);
|
|
6326
|
+
await runNextStep(db, exp, config, root, isJson, overrideGate);
|
|
6018
6327
|
}
|
|
6019
6328
|
}
|
|
6020
|
-
async function runNextStep(db, exp, config, root, isJson) {
|
|
6329
|
+
async function runNextStep(db, exp, config, root, isJson, overrideGate = false) {
|
|
6021
6330
|
const currentStatus = exp.status;
|
|
6022
6331
|
const valid = validNext(currentStatus);
|
|
6023
6332
|
if (valid.length === 0) {
|
|
@@ -6040,10 +6349,21 @@ async function runNextStep(db, exp, config, root, isJson) {
|
|
|
6040
6349
|
"procedural"
|
|
6041
6350
|
);
|
|
6042
6351
|
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "circuit_breaker");
|
|
6352
|
+
handleDeadEndGit(exp, root);
|
|
6043
6353
|
warn("Experiment dead-ended. Triggering Maqasid Check (purpose audit).");
|
|
6044
6354
|
await audit([config.project?.objective ?? ""]);
|
|
6045
6355
|
return;
|
|
6046
6356
|
}
|
|
6357
|
+
if (exp.status === "gated" && exp.gate_rejection_reason) {
|
|
6358
|
+
if (overrideGate) {
|
|
6359
|
+
clearGateRejection(db, exp.id);
|
|
6360
|
+
info(`Gate override accepted for ${exp.slug}. Proceeding to build.`);
|
|
6361
|
+
} else {
|
|
6362
|
+
warn(`Gate rejected: ${exp.gate_rejection_reason}`);
|
|
6363
|
+
info("Run `majlis next --override-gate` to proceed anyway, or `majlis revert` to abandon.");
|
|
6364
|
+
return;
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6047
6367
|
const sessionsSinceCompression = getSessionsSinceCompression(db);
|
|
6048
6368
|
if (sessionsSinceCompression >= config.cycle.compression_interval) {
|
|
6049
6369
|
warn(
|
|
@@ -6074,6 +6394,11 @@ async function runAutoLoop(db, exp, config, root, isJson) {
|
|
|
6074
6394
|
const freshExp = getExperimentBySlug(db, exp.slug);
|
|
6075
6395
|
if (!freshExp) break;
|
|
6076
6396
|
exp = freshExp;
|
|
6397
|
+
if (exp.gate_rejection_reason) {
|
|
6398
|
+
warn(`Gate rejected: ${exp.gate_rejection_reason}`);
|
|
6399
|
+
info("Stopping auto mode. Use `majlis next --override-gate` or `majlis revert`.");
|
|
6400
|
+
break;
|
|
6401
|
+
}
|
|
6077
6402
|
if (isTerminal(exp.status)) {
|
|
6078
6403
|
success(`Experiment ${exp.slug} reached terminal state: ${exp.status}`);
|
|
6079
6404
|
break;
|
|
@@ -6090,6 +6415,7 @@ async function runAutoLoop(db, exp, config, root, isJson) {
|
|
|
6090
6415
|
"procedural"
|
|
6091
6416
|
);
|
|
6092
6417
|
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "circuit_breaker");
|
|
6418
|
+
handleDeadEndGit(exp, root);
|
|
6093
6419
|
await audit([config.project?.objective ?? ""]);
|
|
6094
6420
|
break;
|
|
6095
6421
|
}
|
|
@@ -6166,6 +6492,7 @@ var init_next = __esm({
|
|
|
6166
6492
|
init_config();
|
|
6167
6493
|
init_cycle();
|
|
6168
6494
|
init_audit();
|
|
6495
|
+
init_git();
|
|
6169
6496
|
init_format();
|
|
6170
6497
|
}
|
|
6171
6498
|
});
|
|
@@ -6237,6 +6564,28 @@ async function run(args) {
|
|
|
6237
6564
|
try {
|
|
6238
6565
|
await next([exp.slug], false);
|
|
6239
6566
|
consecutiveFailures = 0;
|
|
6567
|
+
const afterStep = getExperimentBySlug(db, exp.slug);
|
|
6568
|
+
if (afterStep?.gate_rejection_reason) {
|
|
6569
|
+
warn(`Gate rejected in autonomous mode: ${afterStep.gate_rejection_reason}. Dead-ending.`);
|
|
6570
|
+
insertDeadEnd(
|
|
6571
|
+
db,
|
|
6572
|
+
afterStep.id,
|
|
6573
|
+
afterStep.hypothesis ?? afterStep.slug,
|
|
6574
|
+
afterStep.gate_rejection_reason,
|
|
6575
|
+
`Gate rejected: ${afterStep.gate_rejection_reason}`,
|
|
6576
|
+
afterStep.sub_type,
|
|
6577
|
+
"procedural"
|
|
6578
|
+
);
|
|
6579
|
+
adminTransitionAndPersist(
|
|
6580
|
+
db,
|
|
6581
|
+
afterStep.id,
|
|
6582
|
+
afterStep.status,
|
|
6583
|
+
"dead_end" /* DEAD_END */,
|
|
6584
|
+
"revert"
|
|
6585
|
+
);
|
|
6586
|
+
handleDeadEndGit(afterStep, root);
|
|
6587
|
+
continue;
|
|
6588
|
+
}
|
|
6240
6589
|
} catch (err) {
|
|
6241
6590
|
consecutiveFailures++;
|
|
6242
6591
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -6252,6 +6601,7 @@ async function run(args) {
|
|
|
6252
6601
|
"procedural"
|
|
6253
6602
|
);
|
|
6254
6603
|
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "error_recovery");
|
|
6604
|
+
handleDeadEndGit(exp, root);
|
|
6255
6605
|
} catch (innerErr) {
|
|
6256
6606
|
const innerMsg = innerErr instanceof Error ? innerErr.message : String(innerErr);
|
|
6257
6607
|
warn(`Could not record dead-end: ${innerMsg}`);
|
|
@@ -6391,14 +6741,15 @@ async function createNewExperiment(db, root, hypothesis) {
|
|
|
6391
6741
|
const exp = createExperiment(db, finalSlug, branch, hypothesis, null, null);
|
|
6392
6742
|
adminTransitionAndPersist(db, exp.id, exp.status, "reframed" /* REFRAMED */, "bootstrap");
|
|
6393
6743
|
exp.status = "reframed";
|
|
6744
|
+
const docRelPath = expDocRelPath(exp);
|
|
6394
6745
|
const docsDir = path16.join(root, "docs", "experiments");
|
|
6395
6746
|
const templatePath = path16.join(docsDir, "_TEMPLATE.md");
|
|
6396
6747
|
if (fs16.existsSync(templatePath)) {
|
|
6397
6748
|
const template = fs16.readFileSync(templatePath, "utf-8");
|
|
6398
6749
|
const logContent = template.replace(/\{\{title\}\}/g, hypothesis).replace(/\{\{hypothesis\}\}/g, hypothesis).replace(/\{\{branch\}\}/g, branch).replace(/\{\{status\}\}/g, "classified").replace(/\{\{sub_type\}\}/g, "unclassified").replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]);
|
|
6399
|
-
const logPath = path16.join(
|
|
6750
|
+
const logPath = path16.join(root, docRelPath);
|
|
6400
6751
|
fs16.writeFileSync(logPath, logContent);
|
|
6401
|
-
info(`Created experiment log:
|
|
6752
|
+
info(`Created experiment log: ${docRelPath}`);
|
|
6402
6753
|
}
|
|
6403
6754
|
return exp;
|
|
6404
6755
|
}
|
|
@@ -6686,8 +7037,9 @@ function importExperimentFromWorktree(sourceDb, targetDb, slug) {
|
|
|
6686
7037
|
const sourceId = sourceExp.id;
|
|
6687
7038
|
const insertExp = targetDb.prepare(`
|
|
6688
7039
|
INSERT INTO experiments (slug, branch, status, classification_ref, sub_type,
|
|
6689
|
-
hypothesis, builder_guidance,
|
|
6690
|
-
|
|
7040
|
+
hypothesis, builder_guidance, depends_on, context_files,
|
|
7041
|
+
gate_rejection_reason, created_at, updated_at)
|
|
7042
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6691
7043
|
`);
|
|
6692
7044
|
const result = insertExp.run(
|
|
6693
7045
|
sourceExp.slug,
|
|
@@ -6697,6 +7049,9 @@ function importExperimentFromWorktree(sourceDb, targetDb, slug) {
|
|
|
6697
7049
|
sourceExp.sub_type,
|
|
6698
7050
|
sourceExp.hypothesis,
|
|
6699
7051
|
sourceExp.builder_guidance,
|
|
7052
|
+
sourceExp.depends_on ?? null,
|
|
7053
|
+
sourceExp.context_files ?? null,
|
|
7054
|
+
sourceExp.gate_rejection_reason ?? null,
|
|
6700
7055
|
sourceExp.created_at,
|
|
6701
7056
|
sourceExp.updated_at
|
|
6702
7057
|
);
|