majlis 0.7.3 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1685 -1311
- 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.
|
|
@@ -816,15 +835,20 @@ the database export.
|
|
|
816
835
|
2. Read docs/ files for narrative context, but trust the database when they conflict.
|
|
817
836
|
3. Cross-reference: same question in different language? contradicting decisions?
|
|
818
837
|
workaround masking root cause?
|
|
819
|
-
4.
|
|
838
|
+
4. **VERIFY before claiming code is live.** Dead-ended experiments are REVERTED \u2014
|
|
839
|
+
their code changes do NOT exist on the current branch. Before writing that code
|
|
840
|
+
is "live", "shipping", or "regressing", use Grep/Glob to confirm it actually
|
|
841
|
+
exists in the current codebase. If the code only existed on experiment branches,
|
|
842
|
+
say so explicitly and mark the issue as RESOLVED, not CRITICAL.
|
|
843
|
+
5. Update fragility map: thin coverage, weak components, untested judgment
|
|
820
844
|
decisions, broken provenance.
|
|
821
|
-
|
|
845
|
+
6. Update dead-end registry: compress rejected experiments into structural constraints.
|
|
822
846
|
Mark each dead-end as [structural] or [procedural].
|
|
823
|
-
|
|
847
|
+
7. REWRITE synthesis using the Write tool \u2014 shorter and denser. If it's growing,
|
|
824
848
|
you're accumulating, not compressing. You MUST use the Write tool to update
|
|
825
849
|
docs/synthesis/current.md, docs/synthesis/fragility.md, and docs/synthesis/dead-ends.md.
|
|
826
850
|
The framework does NOT auto-save your output for these files.
|
|
827
|
-
|
|
851
|
+
8. Review classification: new sub-types? resolved sub-types?
|
|
828
852
|
|
|
829
853
|
You may ONLY write to these three files:
|
|
830
854
|
- docs/synthesis/current.md
|
|
@@ -1096,7 +1120,80 @@ Produce a diagnostic report as markdown. At the end, include:
|
|
|
1096
1120
|
## Safety Reminders
|
|
1097
1121
|
- You are READ-ONLY for project code. Write ONLY to .majlis/scripts/.
|
|
1098
1122
|
- Focus on diagnosis, not fixing. Your value is insight, not implementation.
|
|
1099
|
-
- Trust the database export over docs/ files when they conflict
|
|
1123
|
+
- Trust the database export over docs/ files when they conflict.`,
|
|
1124
|
+
postmortem: `---
|
|
1125
|
+
name: postmortem
|
|
1126
|
+
model: opus
|
|
1127
|
+
tools: [Read, Glob, Grep]
|
|
1128
|
+
---
|
|
1129
|
+
You are the Post-Mortem Analyst. You analyze reverted or failed experiments and extract
|
|
1130
|
+
structural learnings that prevent future experiments from repeating the same mistakes.
|
|
1131
|
+
|
|
1132
|
+
You run automatically when an experiment is reverted. Your job is to produce a specific,
|
|
1133
|
+
falsifiable structural constraint that blocks future experiments from repeating the approach.
|
|
1134
|
+
|
|
1135
|
+
## What You Receive
|
|
1136
|
+
|
|
1137
|
+
- The experiment's hypothesis and metadata
|
|
1138
|
+
- Git diff of the experiment branch vs main (what was changed or attempted)
|
|
1139
|
+
- The user's reason for reverting (if provided) \u2014 use as a starting point, not the final answer
|
|
1140
|
+
- Related dead-ends from the registry
|
|
1141
|
+
- Synthesis and fragility docs
|
|
1142
|
+
- Optionally: artifact files (sweep results, build logs, etc.) pointed to by --context
|
|
1143
|
+
|
|
1144
|
+
## Your Process
|
|
1145
|
+
|
|
1146
|
+
1. **Read the context** \u2014 understand what was attempted and why it's being reverted.
|
|
1147
|
+
2. **Examine artifacts** \u2014 if --context files are provided, read them. If sweep results,
|
|
1148
|
+
build logs, or metric outputs exist in the working directory, find and read them.
|
|
1149
|
+
3. **Analyze the failure** \u2014 determine whether this is structural (approach provably wrong)
|
|
1150
|
+
or procedural (approach might work but was executed poorly or abandoned for other reasons).
|
|
1151
|
+
4. **Produce the constraint** \u2014 write a specific, falsifiable structural constraint.
|
|
1152
|
+
|
|
1153
|
+
## Constraint Quality
|
|
1154
|
+
|
|
1155
|
+
Good constraints are specific and block future repetition:
|
|
1156
|
+
- "L6 config space is null \u2014 13-eval Bayesian sweep found all 12 params insensitive (ls=1.27), score ceiling 0.67"
|
|
1157
|
+
- "Relaxing curvature split threshold in recursive_curvature_split causes false splits on pure-surface thin strips (seg_pct 95->72.5)"
|
|
1158
|
+
- "Torus topology prevents genus-0 assumption for manifold extraction"
|
|
1159
|
+
|
|
1160
|
+
Bad constraints are vague and useless:
|
|
1161
|
+
- "Didn't work"
|
|
1162
|
+
- "Manually reverted"
|
|
1163
|
+
- "Needs more investigation"
|
|
1164
|
+
|
|
1165
|
+
## Scope
|
|
1166
|
+
|
|
1167
|
+
The constraint should clearly state what class of approaches it applies to and what it
|
|
1168
|
+
does NOT apply to. For example:
|
|
1169
|
+
- "SCOPE: Applies to split threshold changes in Pass 2. Does NOT apply to post-Pass-1 merge operations."
|
|
1170
|
+
|
|
1171
|
+
## Output Format
|
|
1172
|
+
|
|
1173
|
+
Write a brief analysis (2-5 paragraphs), then output:
|
|
1174
|
+
|
|
1175
|
+
<!-- majlis-json
|
|
1176
|
+
{
|
|
1177
|
+
"postmortem": {
|
|
1178
|
+
"why_failed": "What was tried and why it failed \u2014 specific, evidence-based",
|
|
1179
|
+
"structural_constraint": "What this proves about the solution space \u2014 blocks future repeats. Include scope.",
|
|
1180
|
+
"category": "structural or procedural"
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
-->
|
|
1184
|
+
|
|
1185
|
+
Categories:
|
|
1186
|
+
- **structural** \u2014 the approach is provably wrong or the solution space is null. Future experiments
|
|
1187
|
+
that repeat this approach should be rejected by the gatekeeper.
|
|
1188
|
+
- **procedural** \u2014 the approach was abandoned for process reasons (e.g., time, priority change,
|
|
1189
|
+
execution error). The approach might still be valid if executed differently.
|
|
1190
|
+
|
|
1191
|
+
## Safety Reminders
|
|
1192
|
+
- You are READ-ONLY. Do not modify any files.
|
|
1193
|
+
- Focus on extracting the constraint, not on suggesting fixes.
|
|
1194
|
+
- Trust the evidence in the context over speculation.
|
|
1195
|
+
- If you cannot determine the structural constraint from the available context, say so explicitly
|
|
1196
|
+
and categorize as procedural.`
|
|
1100
1197
|
};
|
|
1101
1198
|
var SLASH_COMMANDS2 = {
|
|
1102
1199
|
classify: {
|
|
@@ -1771,6 +1868,8 @@ function getExtractionSchema(role) {
|
|
|
1771
1868
|
return '{"architecture": {"modules": ["string"], "entry_points": ["string"], "key_abstractions": ["string"], "dependency_graph": "string"}}';
|
|
1772
1869
|
case "toolsmith":
|
|
1773
1870
|
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"]}}';
|
|
1871
|
+
case "postmortem":
|
|
1872
|
+
return '{"postmortem": {"why_failed": "string", "structural_constraint": "string", "category": "structural|procedural"}}';
|
|
1774
1873
|
default:
|
|
1775
1874
|
return EXTRACTION_SCHEMA;
|
|
1776
1875
|
}
|
|
@@ -1797,7 +1896,8 @@ var init_types = __esm({
|
|
|
1797
1896
|
compressor: ["compression_report"],
|
|
1798
1897
|
diagnostician: ["diagnosis"],
|
|
1799
1898
|
cartographer: ["architecture"],
|
|
1800
|
-
toolsmith: ["toolsmith"]
|
|
1899
|
+
toolsmith: ["toolsmith"],
|
|
1900
|
+
postmortem: ["postmortem"]
|
|
1801
1901
|
};
|
|
1802
1902
|
}
|
|
1803
1903
|
});
|
|
@@ -1896,6 +1996,18 @@ function extractViaPatterns(role, markdown) {
|
|
|
1896
1996
|
});
|
|
1897
1997
|
}
|
|
1898
1998
|
if (doubts.length > 0) result.doubts = doubts;
|
|
1999
|
+
if (role === "postmortem") {
|
|
2000
|
+
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;
|
|
2001
|
+
const pmMatch = markdown.match(pmPattern);
|
|
2002
|
+
if (pmMatch) {
|
|
2003
|
+
const categoryMatch = markdown.match(/(?:CATEGORY|Category)\s*[:=]\s*(structural|procedural)/im);
|
|
2004
|
+
result.postmortem = {
|
|
2005
|
+
why_failed: pmMatch[1].trim(),
|
|
2006
|
+
structural_constraint: pmMatch[2].trim(),
|
|
2007
|
+
category: categoryMatch?.[1]?.toLowerCase() ?? "procedural"
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
1899
2011
|
if (role === "builder") {
|
|
1900
2012
|
const abandonPattern = /\[ABANDON\]\s*(.+?)(?:\n|$)[\s\S]*?(?:structural.?constraint|Constraint|CONSTRAINT)\s*[:=]\s*(.+?)(?:\n|$)/im;
|
|
1901
2013
|
const abandonMatch = markdown.match(abandonPattern);
|
|
@@ -1952,7 +2064,7 @@ ${truncated}`;
|
|
|
1952
2064
|
}
|
|
1953
2065
|
}
|
|
1954
2066
|
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);
|
|
2067
|
+
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
2068
|
}
|
|
1957
2069
|
function validateForRole(role, output) {
|
|
1958
2070
|
const required = ROLE_REQUIRED_FIELDS[role];
|
|
@@ -1998,12 +2110,27 @@ function buildCheckpointMessage(role, toolUseCount, maxTurns) {
|
|
|
1998
2110
|
const approxTurn = Math.round(toolUseCount / 2);
|
|
1999
2111
|
const header2 = `[MAJLIS CHECKPOINT \u2014 ~${approxTurn} of ${maxTurns} turns used]`;
|
|
2000
2112
|
switch (role) {
|
|
2001
|
-
case "builder":
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
-
|
|
2113
|
+
case "builder": {
|
|
2114
|
+
if (toolUseCount <= 15) {
|
|
2115
|
+
return `${header2}
|
|
2116
|
+
You should be done reading by now.
|
|
2117
|
+
- Your experiment doc path is in the taskPrompt. Do NOT search for it.
|
|
2118
|
+
- Do NOT query SQLite, explore .majlis/, or glob broadly.
|
|
2119
|
+
- If you haven't started coding, START NOW.`;
|
|
2120
|
+
} else if (toolUseCount <= 40) {
|
|
2121
|
+
return `${header2}
|
|
2122
|
+
ONE code change per cycle.
|
|
2123
|
+
- Have you run the build/benchmark? YES \u2192 document results + output JSON + STOP.
|
|
2124
|
+
- If NO \u2192 finish your change and run it now.
|
|
2006
2125
|
Do NOT start a second change or investigate unrelated failures.`;
|
|
2126
|
+
} else {
|
|
2127
|
+
return `${header2}
|
|
2128
|
+
URGENT: You are running out of turns.
|
|
2129
|
+
1. Update the experiment doc with whatever results you have.
|
|
2130
|
+
2. Output the <!-- majlis-json --> block NOW.
|
|
2131
|
+
The framework CANNOT recover your work without structured output.`;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2007
2134
|
case "verifier":
|
|
2008
2135
|
return `${header2}
|
|
2009
2136
|
AT MOST 3 diagnostic scripts total.
|
|
@@ -2046,6 +2173,11 @@ If you are past turn 30, begin writing current.md and fragility.md NOW.`;
|
|
|
2046
2173
|
You write ONLY to .majlis/scripts/. Verify toolchain, create metrics wrapper.
|
|
2047
2174
|
Phase 1 (1-10): verify toolchain. Phase 2 (11-25): create metrics.sh. Phase 3 (26-30): output config JSON.
|
|
2048
2175
|
If you are past turn 25, output your structured JSON NOW.`;
|
|
2176
|
+
case "postmortem":
|
|
2177
|
+
return `${header2}
|
|
2178
|
+
You are READ-ONLY. Focus on the structural constraint.
|
|
2179
|
+
What does this experiment prove about the solution space?
|
|
2180
|
+
If you have enough context, output your postmortem JSON NOW.`;
|
|
2049
2181
|
default:
|
|
2050
2182
|
return `${header2}
|
|
2051
2183
|
Check: is your core task done? If yes, wrap up and output JSON.`;
|
|
@@ -2154,6 +2286,18 @@ function buildPreToolUseGuards(role, cwd) {
|
|
|
2154
2286
|
{ matcher: "Bash", hooks: [bashGuard] }
|
|
2155
2287
|
];
|
|
2156
2288
|
}
|
|
2289
|
+
if (role === "postmortem") {
|
|
2290
|
+
const blockWrite = async () => {
|
|
2291
|
+
return {
|
|
2292
|
+
decision: "block",
|
|
2293
|
+
reason: "Post-mortem agent is read-only. No file modifications allowed."
|
|
2294
|
+
};
|
|
2295
|
+
};
|
|
2296
|
+
return [
|
|
2297
|
+
{ matcher: "Write", hooks: [blockWrite] },
|
|
2298
|
+
{ matcher: "Edit", hooks: [blockWrite] }
|
|
2299
|
+
];
|
|
2300
|
+
}
|
|
2157
2301
|
if (role === "builder") {
|
|
2158
2302
|
const bashGuard = async (input) => {
|
|
2159
2303
|
const toolInput = input.tool_input ?? {};
|
|
@@ -2476,10 +2620,11 @@ var init_spawn = __esm({
|
|
|
2476
2620
|
gatekeeper: 10,
|
|
2477
2621
|
diagnostician: 60,
|
|
2478
2622
|
cartographer: 40,
|
|
2479
|
-
toolsmith: 30
|
|
2623
|
+
toolsmith: 30,
|
|
2624
|
+
postmortem: 20
|
|
2480
2625
|
};
|
|
2481
2626
|
CHECKPOINT_INTERVAL = {
|
|
2482
|
-
builder:
|
|
2627
|
+
builder: 12,
|
|
2483
2628
|
verifier: 12,
|
|
2484
2629
|
critic: 15,
|
|
2485
2630
|
adversary: 15,
|
|
@@ -2487,7 +2632,8 @@ var init_spawn = __esm({
|
|
|
2487
2632
|
gatekeeper: 2,
|
|
2488
2633
|
diagnostician: 20,
|
|
2489
2634
|
cartographer: 12,
|
|
2490
|
-
toolsmith: 10
|
|
2635
|
+
toolsmith: 10,
|
|
2636
|
+
postmortem: 8
|
|
2491
2637
|
};
|
|
2492
2638
|
}
|
|
2493
2639
|
});
|
|
@@ -3323,6 +3469,56 @@ function autoCommit(root, message) {
|
|
|
3323
3469
|
} catch {
|
|
3324
3470
|
}
|
|
3325
3471
|
}
|
|
3472
|
+
function handleDeadEndGit(exp, root) {
|
|
3473
|
+
try {
|
|
3474
|
+
const currentBranch = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
3475
|
+
cwd: root,
|
|
3476
|
+
encoding: "utf-8"
|
|
3477
|
+
}).trim();
|
|
3478
|
+
if (currentBranch !== exp.branch) return;
|
|
3479
|
+
} catch {
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
try {
|
|
3483
|
+
(0, import_node_child_process2.execSync)('git add -A -- ":!.majlis/"', {
|
|
3484
|
+
cwd: root,
|
|
3485
|
+
encoding: "utf-8",
|
|
3486
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3487
|
+
});
|
|
3488
|
+
const diff = (0, import_node_child_process2.execSync)("git diff --cached --stat", {
|
|
3489
|
+
cwd: root,
|
|
3490
|
+
encoding: "utf-8",
|
|
3491
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3492
|
+
}).trim();
|
|
3493
|
+
if (diff) {
|
|
3494
|
+
const msg = `EXP-${String(exp.id).padStart(3, "0")}: ${exp.slug} [dead-end]`;
|
|
3495
|
+
(0, import_node_child_process2.execFileSync)("git", ["commit", "-m", msg], {
|
|
3496
|
+
cwd: root,
|
|
3497
|
+
encoding: "utf-8",
|
|
3498
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3499
|
+
});
|
|
3500
|
+
info(`Committed builder changes on ${exp.branch} before dead-end.`);
|
|
3501
|
+
}
|
|
3502
|
+
} catch {
|
|
3503
|
+
}
|
|
3504
|
+
try {
|
|
3505
|
+
(0, import_node_child_process2.execFileSync)("git", ["checkout", "main"], {
|
|
3506
|
+
cwd: root,
|
|
3507
|
+
encoding: "utf-8",
|
|
3508
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3509
|
+
});
|
|
3510
|
+
} catch {
|
|
3511
|
+
try {
|
|
3512
|
+
(0, import_node_child_process2.execFileSync)("git", ["checkout", "master"], {
|
|
3513
|
+
cwd: root,
|
|
3514
|
+
encoding: "utf-8",
|
|
3515
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3516
|
+
});
|
|
3517
|
+
} catch {
|
|
3518
|
+
warn(`Could not switch away from ${exp.branch} \u2014 do this manually.`);
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3326
3522
|
var import_node_child_process2;
|
|
3327
3523
|
var init_git = __esm({
|
|
3328
3524
|
"src/git.ts"() {
|
|
@@ -3532,6 +3728,16 @@ function getBuilderGuidance(db, experimentId) {
|
|
|
3532
3728
|
const row = db.prepare("SELECT builder_guidance FROM experiments WHERE id = ?").get(experimentId);
|
|
3533
3729
|
return row?.builder_guidance ?? null;
|
|
3534
3730
|
}
|
|
3731
|
+
function storeGateRejection(db, experimentId, reason) {
|
|
3732
|
+
db.prepare(`
|
|
3733
|
+
UPDATE experiments SET gate_rejection_reason = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
|
3734
|
+
`).run(reason, experimentId);
|
|
3735
|
+
}
|
|
3736
|
+
function clearGateRejection(db, experimentId) {
|
|
3737
|
+
db.prepare(`
|
|
3738
|
+
UPDATE experiments SET gate_rejection_reason = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
|
3739
|
+
`).run(experimentId);
|
|
3740
|
+
}
|
|
3535
3741
|
function insertDecision(db, experimentId, description, evidenceLevel, justification) {
|
|
3536
3742
|
const stmt = db.prepare(`
|
|
3537
3743
|
INSERT INTO decisions (experiment_id, description, evidence_level, justification)
|
|
@@ -4215,7 +4421,7 @@ function determineNextStep(exp, valid, hasDoubts2, hasChallenges2) {
|
|
|
4215
4421
|
}
|
|
4216
4422
|
}
|
|
4217
4423
|
if (status2 === "building" /* BUILDING */) {
|
|
4218
|
-
return
|
|
4424
|
+
return "building" /* BUILDING */;
|
|
4219
4425
|
}
|
|
4220
4426
|
if (status2 === "scouted" /* SCOUTED */) {
|
|
4221
4427
|
return valid.includes("verifying" /* VERIFYING */) ? "verifying" /* VERIFYING */ : valid[0];
|
|
@@ -4223,6 +4429,9 @@ function determineNextStep(exp, valid, hasDoubts2, hasChallenges2) {
|
|
|
4223
4429
|
if (status2 === "verified" /* VERIFIED */) {
|
|
4224
4430
|
return valid.includes("resolved" /* RESOLVED */) ? "resolved" /* RESOLVED */ : valid[0];
|
|
4225
4431
|
}
|
|
4432
|
+
if (status2 === "resolved" /* RESOLVED */) {
|
|
4433
|
+
return valid.includes("compressed" /* COMPRESSED */) ? "compressed" /* COMPRESSED */ : valid[0];
|
|
4434
|
+
}
|
|
4226
4435
|
if (status2 === "compressed" /* COMPRESSED */) {
|
|
4227
4436
|
return valid.includes("merged" /* MERGED */) ? "merged" /* MERGED */ : valid[0];
|
|
4228
4437
|
}
|
|
@@ -4307,869 +4516,701 @@ var init_metrics = __esm({
|
|
|
4307
4516
|
}
|
|
4308
4517
|
});
|
|
4309
4518
|
|
|
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
|
-
}
|
|
4519
|
+
// src/resolve.ts
|
|
4520
|
+
function worstGrade(grades) {
|
|
4521
|
+
if (grades.length === 0) {
|
|
4522
|
+
throw new Error("Cannot determine grade from empty verification set \u2014 this indicates a data integrity issue");
|
|
4343
4523
|
}
|
|
4344
|
-
|
|
4345
|
-
|
|
4524
|
+
for (const grade of GRADE_ORDER) {
|
|
4525
|
+
if (grades.some((g) => g.grade === grade)) return grade;
|
|
4346
4526
|
}
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4527
|
+
return "sound";
|
|
4528
|
+
}
|
|
4529
|
+
async function resolve2(db, exp, projectRoot) {
|
|
4530
|
+
let grades = getVerificationsByExperiment(db, exp.id);
|
|
4531
|
+
if (grades.length === 0) {
|
|
4532
|
+
warn(`No verification records for ${exp.slug}. Defaulting to weak.`);
|
|
4533
|
+
insertVerification(
|
|
4534
|
+
db,
|
|
4535
|
+
exp.id,
|
|
4536
|
+
"auto-default",
|
|
4537
|
+
"weak",
|
|
4538
|
+
null,
|
|
4539
|
+
null,
|
|
4540
|
+
"No structured verification output. Auto-defaulted to weak."
|
|
4541
|
+
);
|
|
4542
|
+
grades = getVerificationsByExperiment(db, exp.id);
|
|
4357
4543
|
}
|
|
4358
|
-
const
|
|
4359
|
-
|
|
4360
|
-
|
|
4544
|
+
const overallGrade = worstGrade(grades);
|
|
4545
|
+
const config = loadConfig(projectRoot);
|
|
4546
|
+
const metricComparisons = compareMetrics(db, exp.id, config);
|
|
4547
|
+
const gateViolations = checkGateViolations(metricComparisons);
|
|
4548
|
+
if (gateViolations.length > 0 && (overallGrade === "sound" || overallGrade === "good")) {
|
|
4549
|
+
warn("Gate fixture regression detected \u2014 blocking merge:");
|
|
4550
|
+
for (const v of gateViolations) {
|
|
4551
|
+
warn(` ${v.fixture} / ${v.metric}: ${v.before} \u2192 ${v.after} (${v.delta > 0 ? "+" : ""}${v.delta})`);
|
|
4552
|
+
}
|
|
4553
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
4554
|
+
const guidanceText = `Gate fixture regression blocks merge. Fix these regressions before re-attempting:
|
|
4555
|
+
` + gateViolations.map((v) => `- ${v.fixture} / ${v.metric}: was ${v.before}, now ${v.after}`).join("\n");
|
|
4556
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4557
|
+
db.transaction(() => {
|
|
4558
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4559
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4560
|
+
if (exp.sub_type) {
|
|
4561
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4562
|
+
}
|
|
4563
|
+
})();
|
|
4564
|
+
warn(`Experiment ${exp.slug} CYCLING BACK \u2014 gate fixture(s) regressed.`);
|
|
4361
4565
|
return;
|
|
4362
4566
|
}
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
(
|
|
4370
|
-
|
|
4371
|
-
|
|
4567
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
4568
|
+
switch (overallGrade) {
|
|
4569
|
+
case "sound": {
|
|
4570
|
+
gitMerge(exp.branch, projectRoot);
|
|
4571
|
+
transition("resolved" /* RESOLVED */, "merged" /* MERGED */);
|
|
4572
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4573
|
+
success(`Experiment ${exp.slug} MERGED (all sound).`);
|
|
4574
|
+
break;
|
|
4575
|
+
}
|
|
4576
|
+
case "good": {
|
|
4577
|
+
gitMerge(exp.branch, projectRoot);
|
|
4578
|
+
const gaps = grades.filter((g) => g.grade === "good").map((g) => `- **${g.component}**: ${g.notes ?? "minor gaps"}`).join("\n");
|
|
4579
|
+
appendToFragilityMap(projectRoot, exp.slug, gaps);
|
|
4580
|
+
autoCommit(projectRoot, `resolve: fragility gaps from ${exp.slug}`);
|
|
4581
|
+
transition("resolved" /* RESOLVED */, "merged" /* MERGED */);
|
|
4582
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4583
|
+
success(`Experiment ${exp.slug} MERGED (good, ${grades.filter((g) => g.grade === "good").length} gaps added to fragility map).`);
|
|
4584
|
+
break;
|
|
4585
|
+
}
|
|
4586
|
+
case "weak": {
|
|
4587
|
+
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
4588
|
+
const guidance = await spawnSynthesiser({
|
|
4589
|
+
experiment: {
|
|
4590
|
+
id: exp.id,
|
|
4591
|
+
slug: exp.slug,
|
|
4592
|
+
hypothesis: exp.hypothesis,
|
|
4593
|
+
status: exp.status,
|
|
4594
|
+
sub_type: exp.sub_type,
|
|
4595
|
+
builder_guidance: exp.builder_guidance
|
|
4596
|
+
},
|
|
4597
|
+
verificationReport: grades,
|
|
4598
|
+
confirmedDoubts,
|
|
4599
|
+
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."
|
|
4600
|
+
}, projectRoot);
|
|
4601
|
+
const guidanceText = guidance.structured?.guidance ?? guidance.output;
|
|
4602
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4603
|
+
db.transaction(() => {
|
|
4604
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4605
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4606
|
+
if (exp.sub_type) {
|
|
4607
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4608
|
+
}
|
|
4609
|
+
})();
|
|
4610
|
+
warn(`Experiment ${exp.slug} CYCLING BACK (weak). Guidance generated for builder.`);
|
|
4611
|
+
break;
|
|
4612
|
+
}
|
|
4613
|
+
case "rejected": {
|
|
4614
|
+
gitRevert(exp.branch, projectRoot);
|
|
4615
|
+
const rejectedComponents = grades.filter((g) => g.grade === "rejected");
|
|
4616
|
+
const whyFailed = rejectedComponents.map((r) => r.notes ?? "rejected").join("; ");
|
|
4617
|
+
transition("resolved" /* RESOLVED */, "dead_end" /* DEAD_END */);
|
|
4618
|
+
db.transaction(() => {
|
|
4619
|
+
insertDeadEnd(
|
|
4620
|
+
db,
|
|
4621
|
+
exp.id,
|
|
4622
|
+
exp.hypothesis ?? exp.slug,
|
|
4623
|
+
whyFailed,
|
|
4624
|
+
`Approach rejected: ${whyFailed}`,
|
|
4625
|
+
exp.sub_type,
|
|
4626
|
+
"structural"
|
|
4627
|
+
);
|
|
4628
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
4629
|
+
if (exp.sub_type) {
|
|
4630
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
|
|
4631
|
+
}
|
|
4632
|
+
})();
|
|
4633
|
+
info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
|
|
4634
|
+
break;
|
|
4372
4635
|
}
|
|
4373
4636
|
}
|
|
4374
4637
|
}
|
|
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;
|
|
4638
|
+
async function resolveDbOnly(db, exp, projectRoot) {
|
|
4639
|
+
let grades = getVerificationsByExperiment(db, exp.id);
|
|
4640
|
+
if (grades.length === 0) {
|
|
4641
|
+
warn(`No verification records for ${exp.slug}. Defaulting to weak.`);
|
|
4642
|
+
insertVerification(
|
|
4643
|
+
db,
|
|
4644
|
+
exp.id,
|
|
4645
|
+
"auto-default",
|
|
4646
|
+
"weak",
|
|
4647
|
+
null,
|
|
4648
|
+
null,
|
|
4649
|
+
"No structured verification output. Auto-defaulted to weak."
|
|
4650
|
+
);
|
|
4651
|
+
grades = getVerificationsByExperiment(db, exp.id);
|
|
4392
4652
|
}
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4653
|
+
const overallGrade = worstGrade(grades);
|
|
4654
|
+
const config = loadConfig(projectRoot);
|
|
4655
|
+
const metricComparisons = compareMetrics(db, exp.id, config);
|
|
4656
|
+
const gateViolations = checkGateViolations(metricComparisons);
|
|
4657
|
+
if (gateViolations.length > 0 && (overallGrade === "sound" || overallGrade === "good")) {
|
|
4658
|
+
warn("Gate fixture regression detected \u2014 blocking merge:");
|
|
4659
|
+
for (const v of gateViolations) {
|
|
4660
|
+
warn(` ${v.fixture} / ${v.metric}: ${v.before} \u2192 ${v.after} (${v.delta > 0 ? "+" : ""}${v.delta})`);
|
|
4661
|
+
}
|
|
4662
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
4663
|
+
const guidanceText = `Gate fixture regression blocks merge. Fix these regressions before re-attempting:
|
|
4664
|
+
` + gateViolations.map((v) => `- ${v.fixture} / ${v.metric}: was ${v.before}, now ${v.after}`).join("\n");
|
|
4665
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4666
|
+
db.transaction(() => {
|
|
4667
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4668
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4669
|
+
if (exp.sub_type) {
|
|
4670
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4671
|
+
}
|
|
4672
|
+
})();
|
|
4673
|
+
warn(`Experiment ${exp.slug} CYCLING BACK \u2014 gate fixture(s) regressed.`);
|
|
4674
|
+
return "weak";
|
|
4396
4675
|
}
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4676
|
+
updateExperimentStatus(db, exp.id, "resolved");
|
|
4677
|
+
switch (overallGrade) {
|
|
4678
|
+
case "sound":
|
|
4679
|
+
transition("resolved" /* RESOLVED */, "merged" /* MERGED */);
|
|
4680
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4681
|
+
success(`Experiment ${exp.slug} RESOLVED (sound) \u2014 git merge deferred.`);
|
|
4682
|
+
break;
|
|
4683
|
+
case "good": {
|
|
4684
|
+
const gaps = grades.filter((g) => g.grade === "good").map((g) => `- **${g.component}**: ${g.notes ?? "minor gaps"}`).join("\n");
|
|
4685
|
+
appendToFragilityMap(projectRoot, exp.slug, gaps);
|
|
4686
|
+
transition("resolved" /* RESOLVED */, "merged" /* MERGED */);
|
|
4687
|
+
updateExperimentStatus(db, exp.id, "merged");
|
|
4688
|
+
success(`Experiment ${exp.slug} RESOLVED (good) \u2014 git merge deferred.`);
|
|
4689
|
+
break;
|
|
4690
|
+
}
|
|
4691
|
+
case "weak": {
|
|
4692
|
+
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
4693
|
+
const guidance = await spawnSynthesiser({
|
|
4694
|
+
experiment: {
|
|
4695
|
+
id: exp.id,
|
|
4696
|
+
slug: exp.slug,
|
|
4697
|
+
hypothesis: exp.hypothesis,
|
|
4698
|
+
status: exp.status,
|
|
4699
|
+
sub_type: exp.sub_type,
|
|
4700
|
+
builder_guidance: exp.builder_guidance
|
|
4701
|
+
},
|
|
4702
|
+
verificationReport: grades,
|
|
4703
|
+
confirmedDoubts,
|
|
4704
|
+
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."
|
|
4705
|
+
}, projectRoot);
|
|
4706
|
+
const guidanceText = guidance.structured?.guidance ?? guidance.output;
|
|
4707
|
+
transition("resolved" /* RESOLVED */, "building" /* BUILDING */);
|
|
4708
|
+
db.transaction(() => {
|
|
4709
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
4710
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4711
|
+
if (exp.sub_type) {
|
|
4712
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
4713
|
+
}
|
|
4714
|
+
})();
|
|
4715
|
+
warn(`Experiment ${exp.slug} CYCLING BACK (weak). Guidance generated.`);
|
|
4716
|
+
break;
|
|
4717
|
+
}
|
|
4718
|
+
case "rejected": {
|
|
4719
|
+
const rejectedComponents = grades.filter((g) => g.grade === "rejected");
|
|
4720
|
+
const whyFailed = rejectedComponents.map((r) => r.notes ?? "rejected").join("; ");
|
|
4721
|
+
transition("resolved" /* RESOLVED */, "dead_end" /* DEAD_END */);
|
|
4722
|
+
db.transaction(() => {
|
|
4723
|
+
insertDeadEnd(
|
|
4724
|
+
db,
|
|
4725
|
+
exp.id,
|
|
4726
|
+
exp.hypothesis ?? exp.slug,
|
|
4727
|
+
whyFailed,
|
|
4728
|
+
`Approach rejected: ${whyFailed}`,
|
|
4729
|
+
exp.sub_type,
|
|
4730
|
+
"structural"
|
|
4731
|
+
);
|
|
4732
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
4733
|
+
if (exp.sub_type) {
|
|
4734
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
|
|
4735
|
+
}
|
|
4736
|
+
})();
|
|
4737
|
+
info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
|
|
4738
|
+
break;
|
|
4739
|
+
}
|
|
4414
4740
|
}
|
|
4741
|
+
return overallGrade;
|
|
4415
4742
|
}
|
|
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}`;
|
|
4743
|
+
function gitMerge(branch, cwd) {
|
|
4456
4744
|
try {
|
|
4457
|
-
|
|
4458
|
-
|
|
4745
|
+
try {
|
|
4746
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "main"], {
|
|
4747
|
+
cwd,
|
|
4748
|
+
encoding: "utf-8",
|
|
4749
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4750
|
+
});
|
|
4751
|
+
} catch {
|
|
4752
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "master"], {
|
|
4753
|
+
cwd,
|
|
4754
|
+
encoding: "utf-8",
|
|
4755
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4756
|
+
});
|
|
4757
|
+
}
|
|
4758
|
+
(0, import_node_child_process4.execFileSync)("git", ["merge", branch, "--no-ff", "-m", `Merge experiment branch ${branch}`], {
|
|
4759
|
+
cwd,
|
|
4459
4760
|
encoding: "utf-8",
|
|
4460
4761
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4461
4762
|
});
|
|
4462
|
-
info(`Created branch: ${branch}`);
|
|
4463
4763
|
} 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
|
-
}
|
|
4764
|
+
warn(`Git merge of ${branch} failed \u2014 you may need to merge manually.`);
|
|
4500
4765
|
}
|
|
4501
4766
|
}
|
|
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");
|
|
4767
|
+
function gitRevert(branch, cwd) {
|
|
4527
4768
|
try {
|
|
4528
|
-
const currentBranch = (0,
|
|
4529
|
-
cwd
|
|
4769
|
+
const currentBranch = (0, import_node_child_process4.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
4770
|
+
cwd,
|
|
4530
4771
|
encoding: "utf-8"
|
|
4531
4772
|
}).trim();
|
|
4532
|
-
if (currentBranch ===
|
|
4773
|
+
if (currentBranch === branch) {
|
|
4533
4774
|
try {
|
|
4534
|
-
(0,
|
|
4535
|
-
|
|
4775
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "--", "."], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
4776
|
+
} catch {
|
|
4777
|
+
}
|
|
4778
|
+
try {
|
|
4779
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "main"], {
|
|
4780
|
+
cwd,
|
|
4536
4781
|
encoding: "utf-8",
|
|
4537
4782
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4538
4783
|
});
|
|
4539
4784
|
} catch {
|
|
4540
|
-
(0,
|
|
4541
|
-
cwd
|
|
4785
|
+
(0, import_node_child_process4.execFileSync)("git", ["checkout", "master"], {
|
|
4786
|
+
cwd,
|
|
4542
4787
|
encoding: "utf-8",
|
|
4543
4788
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4544
4789
|
});
|
|
4545
4790
|
}
|
|
4546
4791
|
}
|
|
4547
4792
|
} catch {
|
|
4548
|
-
warn(
|
|
4793
|
+
warn(`Could not switch away from ${branch} \u2014 you may need to do this manually.`);
|
|
4549
4794
|
}
|
|
4550
|
-
info(`Experiment ${exp.slug} reverted to dead-end. Reason: ${reason}`);
|
|
4551
4795
|
}
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
"
|
|
4555
|
-
|
|
4556
|
-
|
|
4796
|
+
function appendToFragilityMap(projectRoot, expSlug, gaps) {
|
|
4797
|
+
const fragPath = path10.join(projectRoot, "docs", "synthesis", "fragility.md");
|
|
4798
|
+
let content = "";
|
|
4799
|
+
if (fs10.existsSync(fragPath)) {
|
|
4800
|
+
content = fs10.readFileSync(fragPath, "utf-8");
|
|
4801
|
+
}
|
|
4802
|
+
const entry = `
|
|
4803
|
+
## From experiment: ${expSlug}
|
|
4804
|
+
${gaps}
|
|
4805
|
+
`;
|
|
4806
|
+
fs10.writeFileSync(fragPath, content + entry);
|
|
4807
|
+
}
|
|
4808
|
+
var fs10, path10, import_node_child_process4;
|
|
4809
|
+
var init_resolve = __esm({
|
|
4810
|
+
"src/resolve.ts"() {
|
|
4811
|
+
"use strict";
|
|
4812
|
+
fs10 = __toESM(require("fs"));
|
|
4557
4813
|
path10 = __toESM(require("path"));
|
|
4558
|
-
import_node_child_process5 = require("child_process");
|
|
4559
|
-
init_connection();
|
|
4560
|
-
init_queries();
|
|
4561
|
-
init_machine();
|
|
4562
4814
|
init_types2();
|
|
4815
|
+
init_machine();
|
|
4816
|
+
init_queries();
|
|
4817
|
+
init_metrics();
|
|
4563
4818
|
init_config();
|
|
4564
4819
|
init_spawn();
|
|
4820
|
+
import_node_child_process4 = require("child_process");
|
|
4565
4821
|
init_git();
|
|
4566
4822
|
init_format();
|
|
4567
4823
|
}
|
|
4568
4824
|
});
|
|
4569
4825
|
|
|
4570
|
-
// src/commands/
|
|
4571
|
-
var
|
|
4572
|
-
__export(
|
|
4573
|
-
|
|
4826
|
+
// src/commands/cycle.ts
|
|
4827
|
+
var cycle_exports = {};
|
|
4828
|
+
__export(cycle_exports, {
|
|
4829
|
+
cycle: () => cycle,
|
|
4830
|
+
expDocRelPath: () => expDocRelPath,
|
|
4831
|
+
resolveCmd: () => resolveCmd,
|
|
4832
|
+
runResolve: () => runResolve,
|
|
4833
|
+
runStep: () => runStep
|
|
4574
4834
|
});
|
|
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
|
-
}
|
|
4835
|
+
async function cycle(step, args) {
|
|
4580
4836
|
const root = findProjectRoot();
|
|
4581
4837
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
4582
4838
|
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}`);
|
|
4839
|
+
if (step === "compress") return doCompress(db, root);
|
|
4840
|
+
const exp = resolveExperimentArg(db, args);
|
|
4841
|
+
switch (step) {
|
|
4842
|
+
case "build":
|
|
4843
|
+
return doBuild(db, exp, root);
|
|
4844
|
+
case "challenge":
|
|
4845
|
+
return doChallenge(db, exp, root);
|
|
4846
|
+
case "doubt":
|
|
4847
|
+
return doDoubt(db, exp, root);
|
|
4848
|
+
case "scout":
|
|
4849
|
+
return doScout(db, exp, root);
|
|
4850
|
+
case "verify":
|
|
4851
|
+
return doVerify(db, exp, root);
|
|
4852
|
+
case "gate":
|
|
4853
|
+
return doGate(db, exp, root);
|
|
4613
4854
|
}
|
|
4614
4855
|
}
|
|
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) {
|
|
4856
|
+
async function resolveCmd(args) {
|
|
4631
4857
|
const root = findProjectRoot();
|
|
4632
4858
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
4633
4859
|
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
|
-
}
|
|
4860
|
+
const exp = resolveExperimentArg(db, args);
|
|
4861
|
+
transition(exp.status, "resolved" /* RESOLVED */);
|
|
4862
|
+
await resolve2(db, exp, root);
|
|
4648
4863
|
}
|
|
4649
|
-
function
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4864
|
+
async function runStep(step, db, exp, root) {
|
|
4865
|
+
switch (step) {
|
|
4866
|
+
case "build":
|
|
4867
|
+
return doBuild(db, exp, root);
|
|
4868
|
+
case "challenge":
|
|
4869
|
+
return doChallenge(db, exp, root);
|
|
4870
|
+
case "doubt":
|
|
4871
|
+
return doDoubt(db, exp, root);
|
|
4872
|
+
case "scout":
|
|
4873
|
+
return doScout(db, exp, root);
|
|
4874
|
+
case "verify":
|
|
4875
|
+
return doVerify(db, exp, root);
|
|
4876
|
+
case "gate":
|
|
4877
|
+
return doGate(db, exp, root);
|
|
4878
|
+
case "compress":
|
|
4879
|
+
return doCompress(db, root);
|
|
4661
4880
|
}
|
|
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
4881
|
}
|
|
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));
|
|
4882
|
+
async function runResolve(db, exp, root) {
|
|
4883
|
+
transition(exp.status, "resolved" /* RESOLVED */);
|
|
4884
|
+
await resolve2(db, exp, root);
|
|
4699
4885
|
}
|
|
4700
|
-
function
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4886
|
+
async function doGate(db, exp, root) {
|
|
4887
|
+
transition(exp.status, "gated" /* GATED */);
|
|
4888
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
4889
|
+
const fragility = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
4890
|
+
const structuralDeadEnds = exp.sub_type ? listStructuralDeadEndsBySubType(db, exp.sub_type) : listStructuralDeadEnds(db);
|
|
4891
|
+
const result = await spawnAgent("gatekeeper", {
|
|
4892
|
+
experiment: {
|
|
4893
|
+
id: exp.id,
|
|
4894
|
+
slug: exp.slug,
|
|
4895
|
+
hypothesis: exp.hypothesis,
|
|
4896
|
+
status: exp.status,
|
|
4897
|
+
sub_type: exp.sub_type,
|
|
4898
|
+
builder_guidance: null
|
|
4899
|
+
},
|
|
4900
|
+
deadEnds: structuralDeadEnds.map((d) => ({
|
|
4901
|
+
approach: d.approach,
|
|
4902
|
+
why_failed: d.why_failed,
|
|
4903
|
+
structural_constraint: d.structural_constraint
|
|
4904
|
+
})),
|
|
4905
|
+
fragility,
|
|
4906
|
+
synthesis,
|
|
4907
|
+
taskPrompt: `Gate-check hypothesis for experiment ${exp.slug}:
|
|
4908
|
+
"${exp.hypothesis}"
|
|
4909
|
+
|
|
4910
|
+
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.
|
|
4911
|
+
|
|
4912
|
+
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?
|
|
4913
|
+
|
|
4914
|
+
Output your gate_decision as "approve", "reject", or "flag" with reasoning.`
|
|
4915
|
+
}, root);
|
|
4916
|
+
ingestStructuredOutput(db, exp.id, result.structured);
|
|
4917
|
+
const decision = result.structured?.gate_decision ?? "approve";
|
|
4918
|
+
const reason = result.structured?.reason ?? "";
|
|
4919
|
+
if (decision === "reject") {
|
|
4920
|
+
updateExperimentStatus(db, exp.id, "gated");
|
|
4921
|
+
storeGateRejection(db, exp.id, reason);
|
|
4922
|
+
warn(`Gate REJECTED for ${exp.slug}: ${reason}`);
|
|
4923
|
+
info("Run `majlis next --override-gate` to proceed anyway, or `majlis revert` to abandon.");
|
|
4709
4924
|
return;
|
|
4925
|
+
} else {
|
|
4926
|
+
if (decision === "flag") {
|
|
4927
|
+
warn(`Gate flagged concerns for ${exp.slug}: ${reason}`);
|
|
4928
|
+
}
|
|
4929
|
+
updateExperimentStatus(db, exp.id, "gated");
|
|
4930
|
+
success(`Gate passed for ${exp.slug}. Run \`majlis build\` next.`);
|
|
4710
4931
|
}
|
|
4711
|
-
header("Fragility Map");
|
|
4712
|
-
console.log(content);
|
|
4713
4932
|
}
|
|
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;
|
|
4933
|
+
async function doBuild(db, exp, root) {
|
|
4934
|
+
if (exp.depends_on) {
|
|
4935
|
+
const dep = getExperimentBySlug(db, exp.depends_on);
|
|
4936
|
+
if (!dep || dep.status !== "merged") {
|
|
4937
|
+
throw new Error(
|
|
4938
|
+
`Experiment "${exp.slug}" depends on "${exp.depends_on}" which is ${dep ? dep.status : "not found"}. Dependency must be merged before building.`
|
|
4939
|
+
);
|
|
4940
|
+
}
|
|
4727
4941
|
}
|
|
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) {
|
|
4942
|
+
transition(exp.status, "building" /* BUILDING */);
|
|
4943
|
+
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
4944
|
+
const builderGuidance = getBuilderGuidance(db, exp.id);
|
|
4945
|
+
const fragility = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
4946
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
4947
|
+
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
4740
4948
|
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) {
|
|
4949
|
+
const existingBaseline = getMetricsByExperimentAndPhase(db, exp.id, "before");
|
|
4950
|
+
if (config.metrics?.command && existingBaseline.length === 0) {
|
|
4766
4951
|
try {
|
|
4767
|
-
const
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4952
|
+
const output = (0, import_node_child_process5.execSync)(config.metrics.command, {
|
|
4953
|
+
cwd: root,
|
|
4954
|
+
encoding: "utf-8",
|
|
4955
|
+
timeout: 6e4,
|
|
4956
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4957
|
+
}).trim();
|
|
4958
|
+
const parsed = parseMetricsOutput(output);
|
|
4959
|
+
for (const m of parsed) {
|
|
4960
|
+
insertMetric(db, exp.id, "before", m.fixture, m.metric_name, m.metric_value);
|
|
4771
4961
|
}
|
|
4962
|
+
if (parsed.length > 0) info(`Captured ${parsed.length} baseline metric(s).`);
|
|
4772
4963
|
} catch {
|
|
4964
|
+
warn("Could not capture baseline metrics.");
|
|
4773
4965
|
}
|
|
4774
4966
|
}
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4967
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
4968
|
+
let taskPrompt = builderGuidance ? `Previous attempt was weak. Here is guidance for this attempt:
|
|
4969
|
+
${builderGuidance}
|
|
4970
|
+
|
|
4971
|
+
Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothesis}`;
|
|
4972
|
+
if (confirmedDoubts.length > 0) {
|
|
4973
|
+
taskPrompt += "\n\n## Confirmed Doubts (MUST address)\nThese weaknesses were confirmed by the verifier. Your build MUST address each one:\n";
|
|
4974
|
+
for (const d of confirmedDoubts) {
|
|
4975
|
+
taskPrompt += `- [${d.severity}] ${d.claim_doubted}: ${d.evidence_for_doubt}
|
|
4976
|
+
`;
|
|
4783
4977
|
}
|
|
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
4978
|
}
|
|
4798
|
-
|
|
4979
|
+
taskPrompt += `
|
|
4799
4980
|
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
if (grades.some((g) => g.grade === grade)) return grade;
|
|
4981
|
+
Your experiment doc: ${expDocRelPath(exp)}`;
|
|
4982
|
+
taskPrompt += "\n\nNote: The framework captures metrics automatically. Do NOT claim specific numbers unless quoting framework output.";
|
|
4983
|
+
const supplementaryContext = loadExperimentContext(exp, root);
|
|
4984
|
+
const lineage = exportExperimentLineage(db, exp.sub_type);
|
|
4985
|
+
if (lineage) {
|
|
4986
|
+
taskPrompt += "\n\n" + lineage;
|
|
4807
4987
|
}
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4988
|
+
const result = await spawnAgent("builder", {
|
|
4989
|
+
experiment: {
|
|
4990
|
+
id: exp.id,
|
|
4991
|
+
slug: exp.slug,
|
|
4992
|
+
hypothesis: exp.hypothesis,
|
|
4993
|
+
status: "building",
|
|
4994
|
+
sub_type: exp.sub_type,
|
|
4995
|
+
builder_guidance: builderGuidance
|
|
4996
|
+
},
|
|
4997
|
+
deadEnds: deadEnds.map((d) => ({
|
|
4998
|
+
approach: d.approach,
|
|
4999
|
+
why_failed: d.why_failed,
|
|
5000
|
+
structural_constraint: d.structural_constraint
|
|
5001
|
+
})),
|
|
5002
|
+
fragility,
|
|
5003
|
+
synthesis,
|
|
5004
|
+
confirmedDoubts,
|
|
5005
|
+
supplementaryContext: supplementaryContext || void 0,
|
|
5006
|
+
experimentLineage: lineage || void 0,
|
|
5007
|
+
taskPrompt
|
|
5008
|
+
}, root);
|
|
5009
|
+
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5010
|
+
if (result.structured?.abandon) {
|
|
5011
|
+
insertDeadEnd(
|
|
4815
5012
|
db,
|
|
4816
5013
|
exp.id,
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
"
|
|
5014
|
+
exp.hypothesis ?? exp.slug,
|
|
5015
|
+
result.structured.abandon.reason,
|
|
5016
|
+
result.structured.abandon.structural_constraint,
|
|
5017
|
+
exp.sub_type,
|
|
5018
|
+
"structural"
|
|
4822
5019
|
);
|
|
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.`);
|
|
5020
|
+
adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
|
|
5021
|
+
handleDeadEndGit(exp, root);
|
|
5022
|
+
info(`Builder abandoned ${exp.slug}: ${result.structured.abandon.reason}`);
|
|
4846
5023
|
return;
|
|
4847
5024
|
}
|
|
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");
|
|
5025
|
+
if (result.truncated && !result.structured) {
|
|
5026
|
+
warn(`Builder was truncated (hit max turns) without producing structured output.`);
|
|
5027
|
+
const recovery = await extractStructuredData("builder", result.output);
|
|
5028
|
+
if (recovery.data && !recovery.data.abandon) {
|
|
5029
|
+
info(`Recovered structured output from truncated builder (tier ${recovery.tier}).`);
|
|
5030
|
+
ingestStructuredOutput(db, exp.id, recovery.data);
|
|
5031
|
+
if (config.build?.pre_measure) {
|
|
5032
|
+
try {
|
|
5033
|
+
const [cmd, ...cmdArgs] = config.build.pre_measure.split(/\s+/);
|
|
5034
|
+
(0, import_node_child_process5.execFileSync)(cmd, cmdArgs, {
|
|
5035
|
+
cwd: root,
|
|
5036
|
+
encoding: "utf-8",
|
|
5037
|
+
timeout: 3e4,
|
|
5038
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5039
|
+
});
|
|
5040
|
+
} catch (err) {
|
|
5041
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5042
|
+
storeBuilderGuidance(
|
|
5043
|
+
db,
|
|
5044
|
+
exp.id,
|
|
5045
|
+
`Build verification failed after truncated recovery.
|
|
5046
|
+
Error: ${errMsg.slice(0, 500)}`
|
|
5047
|
+
);
|
|
5048
|
+
warn(`Build verification failed for ${exp.slug}. Staying at 'building'.`);
|
|
5049
|
+
return;
|
|
4889
5050
|
}
|
|
4890
|
-
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
5051
|
+
}
|
|
5052
|
+
if (config.metrics?.command) {
|
|
5053
|
+
try {
|
|
5054
|
+
const output = (0, import_node_child_process5.execSync)(config.metrics.command, {
|
|
5055
|
+
cwd: root,
|
|
5056
|
+
encoding: "utf-8",
|
|
5057
|
+
timeout: 6e4,
|
|
5058
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5059
|
+
}).trim();
|
|
5060
|
+
const parsed = parseMetricsOutput(output);
|
|
5061
|
+
for (const m of parsed) {
|
|
5062
|
+
insertMetric(db, exp.id, "after", m.fixture, m.metric_name, m.metric_value);
|
|
5063
|
+
}
|
|
5064
|
+
if (parsed.length > 0) info(`Captured ${parsed.length} post-build metric(s).`);
|
|
5065
|
+
} catch {
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
gitCommitBuild(exp, root);
|
|
5069
|
+
if (recovery.tier === 3) {
|
|
5070
|
+
warn(`Builder output extracted via Haiku (tier 3). Data provenance degraded.`);
|
|
5071
|
+
const existing = getBuilderGuidance(db, exp.id) ?? "";
|
|
5072
|
+
storeBuilderGuidance(
|
|
4901
5073
|
db,
|
|
4902
5074
|
exp.id,
|
|
4903
|
-
|
|
4904
|
-
whyFailed,
|
|
4905
|
-
`Approach rejected: ${whyFailed}`,
|
|
4906
|
-
exp.sub_type,
|
|
4907
|
-
"structural"
|
|
5075
|
+
existing + "\n[PROVENANCE WARNING] Builder structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny."
|
|
4908
5076
|
);
|
|
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
5077
|
}
|
|
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(
|
|
5078
|
+
updateExperimentStatus(db, exp.id, "built");
|
|
5079
|
+
success(`Build complete for ${exp.slug} (recovered from truncation). Run \`majlis doubt\` or \`majlis challenge\` next.`);
|
|
5080
|
+
} else if (recovery.data?.abandon) {
|
|
5081
|
+
insertDeadEnd(
|
|
5082
|
+
db,
|
|
5083
|
+
exp.id,
|
|
5084
|
+
exp.hypothesis ?? exp.slug,
|
|
5085
|
+
recovery.data.abandon.reason,
|
|
5086
|
+
recovery.data.abandon.structural_constraint,
|
|
5087
|
+
exp.sub_type,
|
|
5088
|
+
"structural"
|
|
5089
|
+
);
|
|
5090
|
+
adminTransitionAndPersist(db, exp.id, "building", "dead_end" /* DEAD_END */, "revert");
|
|
5091
|
+
handleDeadEndGit(exp, root);
|
|
5092
|
+
info(`Builder abandoned ${exp.slug} (recovered from truncation): ${recovery.data.abandon.reason}`);
|
|
5093
|
+
} else {
|
|
5094
|
+
const tail = result.output.slice(-2e3).trim();
|
|
5095
|
+
if (tail) {
|
|
5096
|
+
storeBuilderGuidance(
|
|
5005
5097
|
db,
|
|
5006
5098
|
exp.id,
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
`Approach rejected: ${whyFailed}`,
|
|
5010
|
-
exp.sub_type,
|
|
5011
|
-
"structural"
|
|
5099
|
+
`Builder was truncated. Last ~2000 chars of output:
|
|
5100
|
+
${tail}`
|
|
5012
5101
|
);
|
|
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
5102
|
}
|
|
5103
|
+
warn(`Experiment stays at 'building'. Run \`majlis build\` to retry or \`majlis revert\` to abandon.`);
|
|
5104
|
+
}
|
|
5105
|
+
} else {
|
|
5106
|
+
if (config.build?.pre_measure) {
|
|
5059
5107
|
try {
|
|
5060
|
-
|
|
5061
|
-
|
|
5108
|
+
const [cmd, ...cmdArgs] = config.build.pre_measure.split(/\s+/);
|
|
5109
|
+
(0, import_node_child_process5.execFileSync)(cmd, cmdArgs, {
|
|
5110
|
+
cwd: root,
|
|
5062
5111
|
encoding: "utf-8",
|
|
5112
|
+
timeout: 3e4,
|
|
5063
5113
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5064
5114
|
});
|
|
5065
|
-
} catch {
|
|
5066
|
-
|
|
5067
|
-
|
|
5115
|
+
} catch (err) {
|
|
5116
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5117
|
+
const guidance = `Build verification failed after builder completion. Code may be syntactically broken or incomplete.
|
|
5118
|
+
Error: ${errMsg.slice(0, 500)}`;
|
|
5119
|
+
storeBuilderGuidance(db, exp.id, guidance);
|
|
5120
|
+
warn(`Build verification failed for ${exp.slug}. Staying at 'building'.`);
|
|
5121
|
+
warn(`Guidance stored for retry. Run \`majlis build\` to retry.`);
|
|
5122
|
+
return;
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
if (config.metrics?.command) {
|
|
5126
|
+
try {
|
|
5127
|
+
const output = (0, import_node_child_process5.execSync)(config.metrics.command, {
|
|
5128
|
+
cwd: root,
|
|
5068
5129
|
encoding: "utf-8",
|
|
5130
|
+
timeout: 6e4,
|
|
5069
5131
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5070
|
-
});
|
|
5132
|
+
}).trim();
|
|
5133
|
+
const parsed = parseMetricsOutput(output);
|
|
5134
|
+
for (const m of parsed) {
|
|
5135
|
+
insertMetric(db, exp.id, "after", m.fixture, m.metric_name, m.metric_value);
|
|
5136
|
+
}
|
|
5137
|
+
if (parsed.length > 0) info(`Captured ${parsed.length} post-build metric(s).`);
|
|
5138
|
+
} catch {
|
|
5139
|
+
warn("Could not capture post-build metrics.");
|
|
5071
5140
|
}
|
|
5072
5141
|
}
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5142
|
+
gitCommitBuild(exp, root);
|
|
5143
|
+
if (result.extractionTier === 3) {
|
|
5144
|
+
warn(`Builder output extracted via Haiku (tier 3). Data provenance degraded.`);
|
|
5145
|
+
const existing = getBuilderGuidance(db, exp.id) ?? "";
|
|
5146
|
+
storeBuilderGuidance(
|
|
5147
|
+
db,
|
|
5148
|
+
exp.id,
|
|
5149
|
+
existing + "\n[PROVENANCE WARNING] Builder structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny."
|
|
5150
|
+
);
|
|
5151
|
+
}
|
|
5152
|
+
updateExperimentStatus(db, exp.id, "built");
|
|
5153
|
+
success(`Build complete for ${exp.slug}. Run \`majlis doubt\` or \`majlis challenge\` next.`);
|
|
5082
5154
|
}
|
|
5083
|
-
const entry = `
|
|
5084
|
-
## From experiment: ${expSlug}
|
|
5085
|
-
${gaps}
|
|
5086
|
-
`;
|
|
5087
|
-
fs12.writeFileSync(fragPath, content + entry);
|
|
5088
5155
|
}
|
|
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();
|
|
5156
|
+
async function doChallenge(db, exp, root) {
|
|
5157
|
+
transition(exp.status, "challenged" /* CHALLENGED */);
|
|
5158
|
+
let gitDiff = "";
|
|
5159
|
+
try {
|
|
5160
|
+
gitDiff = (0, import_node_child_process5.execSync)('git diff main -- . ":!.majlis/"', {
|
|
5161
|
+
cwd: root,
|
|
5162
|
+
encoding: "utf-8",
|
|
5163
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5164
|
+
}).trim();
|
|
5165
|
+
} catch {
|
|
5104
5166
|
}
|
|
5105
|
-
|
|
5167
|
+
if (gitDiff.length > 8e3) gitDiff = gitDiff.slice(0, 8e3) + "\n[DIFF TRUNCATED]";
|
|
5168
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
5169
|
+
let taskPrompt = `Construct adversarial test cases for experiment ${exp.slug}: ${exp.hypothesis}`;
|
|
5170
|
+
if (gitDiff) {
|
|
5171
|
+
taskPrompt += `
|
|
5106
5172
|
|
|
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);
|
|
5173
|
+
## Code Changes (git diff main)
|
|
5174
|
+
\`\`\`diff
|
|
5175
|
+
${gitDiff}
|
|
5176
|
+
\`\`\``;
|
|
5135
5177
|
}
|
|
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);
|
|
5178
|
+
const result = await spawnAgent("adversary", {
|
|
5179
|
+
experiment: {
|
|
5180
|
+
id: exp.id,
|
|
5181
|
+
slug: exp.slug,
|
|
5182
|
+
hypothesis: exp.hypothesis,
|
|
5183
|
+
status: exp.status,
|
|
5184
|
+
sub_type: exp.sub_type,
|
|
5185
|
+
builder_guidance: null
|
|
5186
|
+
},
|
|
5187
|
+
synthesis,
|
|
5188
|
+
taskPrompt
|
|
5189
|
+
}, root);
|
|
5190
|
+
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5191
|
+
if (result.truncated && !result.structured) {
|
|
5192
|
+
warn(`Adversary was truncated without structured output. Experiment stays at current status.`);
|
|
5193
|
+
} else {
|
|
5194
|
+
updateExperimentStatus(db, exp.id, "challenged");
|
|
5195
|
+
success(`Challenge complete for ${exp.slug}. Run \`majlis doubt\` or \`majlis verify\` next.`);
|
|
5161
5196
|
}
|
|
5162
5197
|
}
|
|
5163
|
-
async function
|
|
5164
|
-
transition(exp.status, "
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5198
|
+
async function doDoubt(db, exp, root) {
|
|
5199
|
+
transition(exp.status, "doubted" /* DOUBTED */);
|
|
5200
|
+
const expDocPath = path11.join(root, expDocRelPath(exp));
|
|
5201
|
+
const experimentDoc = truncateContext(readFileOrEmpty(expDocPath), CONTEXT_LIMITS.experimentDoc);
|
|
5202
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
5203
|
+
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
5204
|
+
let taskPrompt = `Doubt the work in experiment ${exp.slug}: ${exp.hypothesis}. Produce a doubt document with evidence for each doubt.`;
|
|
5205
|
+
if (experimentDoc) {
|
|
5206
|
+
taskPrompt += `
|
|
5207
|
+
|
|
5208
|
+
## Experiment Document (builder's artifact)
|
|
5209
|
+
<experiment_doc>
|
|
5210
|
+
${experimentDoc}
|
|
5211
|
+
</experiment_doc>`;
|
|
5212
|
+
}
|
|
5213
|
+
const result = await spawnAgent("critic", {
|
|
5173
5214
|
experiment: {
|
|
5174
5215
|
id: exp.id,
|
|
5175
5216
|
slug: exp.slug,
|
|
@@ -5177,391 +5218,66 @@ async function doGate(db, exp, root) {
|
|
|
5177
5218
|
status: exp.status,
|
|
5178
5219
|
sub_type: exp.sub_type,
|
|
5179
5220
|
builder_guidance: null
|
|
5221
|
+
// Critic does NOT see builder reasoning
|
|
5180
5222
|
},
|
|
5181
|
-
|
|
5223
|
+
synthesis,
|
|
5224
|
+
deadEnds: deadEnds.map((d) => ({
|
|
5182
5225
|
approach: d.approach,
|
|
5183
5226
|
why_failed: d.why_failed,
|
|
5184
5227
|
structural_constraint: d.structural_constraint
|
|
5185
5228
|
})),
|
|
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.`
|
|
5229
|
+
taskPrompt
|
|
5196
5230
|
}, root);
|
|
5197
5231
|
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;
|
|
5232
|
+
if (result.truncated && !result.structured) {
|
|
5233
|
+
warn(`Critic was truncated without structured output. Experiment stays at current status.`);
|
|
5213
5234
|
} else {
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
}
|
|
5217
|
-
updateExperimentStatus(db, exp.id, "gated");
|
|
5218
|
-
success(`Gate passed for ${exp.slug}. Run \`majlis build\` next.`);
|
|
5235
|
+
updateExperimentStatus(db, exp.id, "doubted");
|
|
5236
|
+
success(`Doubt pass complete for ${exp.slug}. Run \`majlis challenge\` or \`majlis verify\` next.`);
|
|
5219
5237
|
}
|
|
5220
5238
|
}
|
|
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 */);
|
|
5239
|
+
async function doScout(db, exp, root) {
|
|
5240
|
+
transition(exp.status, "scouted" /* SCOUTED */);
|
|
5241
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
5242
|
+
const fragility = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
5231
5243
|
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}
|
|
5244
|
+
const deadEndsSummary = deadEnds.map(
|
|
5245
|
+
(d) => `- [${d.category ?? "structural"}] ${d.approach}: ${d.why_failed}`
|
|
5246
|
+
).join("\n");
|
|
5247
|
+
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.`;
|
|
5248
|
+
if (deadEndsSummary) {
|
|
5249
|
+
taskPrompt += `
|
|
5258
5250
|
|
|
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
|
-
}
|
|
5251
|
+
## Known Dead Ends (avoid these approaches)
|
|
5252
|
+
${deadEndsSummary}`;
|
|
5266
5253
|
}
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5254
|
+
if (fragility) {
|
|
5255
|
+
taskPrompt += `
|
|
5256
|
+
|
|
5257
|
+
## Fragility Map (target these weak areas)
|
|
5258
|
+
${fragility}`;
|
|
5272
5259
|
}
|
|
5273
|
-
const result = await spawnAgent("
|
|
5260
|
+
const result = await spawnAgent("scout", {
|
|
5274
5261
|
experiment: {
|
|
5275
5262
|
id: exp.id,
|
|
5276
5263
|
slug: exp.slug,
|
|
5277
5264
|
hypothesis: exp.hypothesis,
|
|
5278
|
-
status:
|
|
5265
|
+
status: exp.status,
|
|
5279
5266
|
sub_type: exp.sub_type,
|
|
5280
|
-
builder_guidance:
|
|
5267
|
+
builder_guidance: null
|
|
5281
5268
|
},
|
|
5269
|
+
synthesis,
|
|
5270
|
+
fragility,
|
|
5282
5271
|
deadEnds: deadEnds.map((d) => ({
|
|
5283
5272
|
approach: d.approach,
|
|
5284
5273
|
why_failed: d.why_failed,
|
|
5285
5274
|
structural_constraint: d.structural_constraint
|
|
5286
5275
|
})),
|
|
5287
|
-
fragility,
|
|
5288
|
-
synthesis,
|
|
5289
|
-
confirmedDoubts,
|
|
5290
|
-
supplementaryContext: supplementaryContext || void 0,
|
|
5291
|
-
experimentLineage: lineage || void 0,
|
|
5292
5276
|
taskPrompt
|
|
5293
5277
|
}, root);
|
|
5294
5278
|
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.`);
|
|
5279
|
+
if (result.truncated && !result.structured) {
|
|
5280
|
+
warn(`Scout was truncated without structured output. Experiment stays at current status.`);
|
|
5565
5281
|
return;
|
|
5566
5282
|
}
|
|
5567
5283
|
updateExperimentStatus(db, exp.id, "scouted");
|
|
@@ -5570,12 +5286,12 @@ ${fragility}`;
|
|
|
5570
5286
|
async function doVerify(db, exp, root) {
|
|
5571
5287
|
transition(exp.status, "verifying" /* VERIFYING */);
|
|
5572
5288
|
const doubts = getDoubtsByExperiment(db, exp.id);
|
|
5573
|
-
const challengeDir =
|
|
5289
|
+
const challengeDir = path11.join(root, "docs", "challenges");
|
|
5574
5290
|
let challenges = "";
|
|
5575
|
-
if (
|
|
5576
|
-
const files =
|
|
5291
|
+
if (fs11.existsSync(challengeDir)) {
|
|
5292
|
+
const files = fs11.readdirSync(challengeDir).filter((f) => f.includes(exp.slug) && f.endsWith(".md"));
|
|
5577
5293
|
for (const f of files) {
|
|
5578
|
-
challenges +=
|
|
5294
|
+
challenges += fs11.readFileSync(path11.join(challengeDir, f), "utf-8") + "\n\n";
|
|
5579
5295
|
}
|
|
5580
5296
|
}
|
|
5581
5297
|
const config = loadConfig(root);
|
|
@@ -5632,191 +5348,802 @@ async function doVerify(db, exp, root) {
|
|
|
5632
5348
|
if (verifierLineage) {
|
|
5633
5349
|
verifierTaskPrompt += "\n\n" + verifierLineage;
|
|
5634
5350
|
}
|
|
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.";
|
|
5351
|
+
const builderGuidanceForVerifier = getBuilderGuidance(db, exp.id);
|
|
5352
|
+
if (builderGuidanceForVerifier?.includes("[PROVENANCE WARNING]")) {
|
|
5353
|
+
verifierTaskPrompt += "\n\nNote: The builder's structured output was reconstructed by a secondary model (tier 3). Treat reported decisions with additional scrutiny.";
|
|
5354
|
+
}
|
|
5355
|
+
const result = await spawnAgent("verifier", {
|
|
5356
|
+
experiment: {
|
|
5357
|
+
id: exp.id,
|
|
5358
|
+
slug: exp.slug,
|
|
5359
|
+
hypothesis: exp.hypothesis,
|
|
5360
|
+
status: "verifying",
|
|
5361
|
+
sub_type: exp.sub_type,
|
|
5362
|
+
builder_guidance: null
|
|
5363
|
+
},
|
|
5364
|
+
doubts,
|
|
5365
|
+
challenges,
|
|
5366
|
+
metricComparisons: metricComparisons.length > 0 ? metricComparisons : void 0,
|
|
5367
|
+
supplementaryContext: verifierSupplementaryContext || void 0,
|
|
5368
|
+
experimentLineage: verifierLineage || void 0,
|
|
5369
|
+
taskPrompt: verifierTaskPrompt
|
|
5370
|
+
}, root);
|
|
5371
|
+
ingestStructuredOutput(db, exp.id, result.structured);
|
|
5372
|
+
if (result.truncated && !result.structured) {
|
|
5373
|
+
warn(`Verifier was truncated without structured output. Experiment stays at 'verifying'.`);
|
|
5374
|
+
return;
|
|
5375
|
+
}
|
|
5376
|
+
if (result.structured?.doubt_resolutions) {
|
|
5377
|
+
const knownDoubtIds = new Set(doubts.map((d) => d.id));
|
|
5378
|
+
for (let i = 0; i < result.structured.doubt_resolutions.length; i++) {
|
|
5379
|
+
const dr = result.structured.doubt_resolutions[i];
|
|
5380
|
+
if (!dr.resolution) continue;
|
|
5381
|
+
if (dr.doubt_id && knownDoubtIds.has(dr.doubt_id)) {
|
|
5382
|
+
updateDoubtResolution(db, dr.doubt_id, dr.resolution);
|
|
5383
|
+
} else if (doubts[i]) {
|
|
5384
|
+
warn(`Doubt resolution ID ${dr.doubt_id} not found. Using ordinal fallback \u2192 DOUBT-${doubts[i].id}.`);
|
|
5385
|
+
updateDoubtResolution(db, doubts[i].id, dr.resolution);
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
updateExperimentStatus(db, exp.id, "verified");
|
|
5390
|
+
success(`Verification complete for ${exp.slug}. Run \`majlis resolve\` next.`);
|
|
5391
|
+
}
|
|
5392
|
+
async function doCompress(db, root) {
|
|
5393
|
+
const synthesisPath = path11.join(root, "docs", "synthesis", "current.md");
|
|
5394
|
+
const sizeBefore = fs11.existsSync(synthesisPath) ? fs11.statSync(synthesisPath).size : 0;
|
|
5395
|
+
const sessionCount = getSessionsSinceCompression(db);
|
|
5396
|
+
const dbExport = exportForCompressor(db);
|
|
5397
|
+
let currentBranch = "main";
|
|
5398
|
+
try {
|
|
5399
|
+
currentBranch = (0, import_node_child_process5.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
5400
|
+
cwd: root,
|
|
5401
|
+
encoding: "utf-8"
|
|
5402
|
+
}).trim();
|
|
5403
|
+
} catch {
|
|
5404
|
+
}
|
|
5405
|
+
const result = await spawnAgent("compressor", {
|
|
5406
|
+
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 + `
|
|
5407
|
+
|
|
5408
|
+
## Current Branch: ${currentBranch}
|
|
5409
|
+
Dead-ended experiments have been REVERTED \u2014 their code changes do NOT exist on this branch. Before claiming any code is "live" or "regressing", verify with Grep/Glob that it exists in the current codebase.
|
|
5410
|
+
|
|
5411
|
+
## Your Task
|
|
5412
|
+
Read 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.`
|
|
5413
|
+
}, root);
|
|
5414
|
+
const sizeAfter = fs11.existsSync(synthesisPath) ? fs11.statSync(synthesisPath).size : 0;
|
|
5415
|
+
recordCompression(db, sessionCount, sizeBefore, sizeAfter);
|
|
5416
|
+
autoCommit(root, "compress: update synthesis");
|
|
5417
|
+
success(`Compression complete. Synthesis: ${sizeBefore}B \u2192 ${sizeAfter}B`);
|
|
5418
|
+
}
|
|
5419
|
+
function gitCommitBuild(exp, cwd) {
|
|
5420
|
+
try {
|
|
5421
|
+
(0, import_node_child_process5.execSync)('git add -A -- ":!.majlis/"', { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
5422
|
+
const diff = (0, import_node_child_process5.execSync)("git diff --cached --stat", { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
5423
|
+
if (!diff) {
|
|
5424
|
+
info("No code changes to commit.");
|
|
5425
|
+
return;
|
|
5426
|
+
}
|
|
5427
|
+
const msg = `EXP-${String(exp.id).padStart(3, "0")}: ${exp.slug}
|
|
5428
|
+
|
|
5429
|
+
${exp.hypothesis ?? ""}`;
|
|
5430
|
+
(0, import_node_child_process5.execFileSync)("git", ["commit", "-m", msg], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
5431
|
+
info(`Committed builder changes on ${exp.branch}.`);
|
|
5432
|
+
} catch {
|
|
5433
|
+
warn("Could not auto-commit builder changes \u2014 commit manually before resolving.");
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
function loadExperimentContext(exp, root) {
|
|
5437
|
+
if (!exp.context_files) return "";
|
|
5438
|
+
let files;
|
|
5439
|
+
try {
|
|
5440
|
+
files = JSON.parse(exp.context_files);
|
|
5441
|
+
} catch {
|
|
5442
|
+
return "";
|
|
5443
|
+
}
|
|
5444
|
+
if (!Array.isArray(files) || files.length === 0) return "";
|
|
5445
|
+
const sections = ["## Experiment-Scoped Reference Material"];
|
|
5446
|
+
for (const relPath of files) {
|
|
5447
|
+
const absPath = path11.join(root, relPath);
|
|
5448
|
+
try {
|
|
5449
|
+
const content = fs11.readFileSync(absPath, "utf-8");
|
|
5450
|
+
sections.push(`### ${relPath}
|
|
5451
|
+
\`\`\`
|
|
5452
|
+
${content.slice(0, 8e3)}
|
|
5453
|
+
\`\`\``);
|
|
5454
|
+
} catch {
|
|
5455
|
+
sections.push(`### ${relPath}
|
|
5456
|
+
*(file not found)*`);
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
return sections.join("\n\n");
|
|
5460
|
+
}
|
|
5461
|
+
function expDocRelPath(exp) {
|
|
5462
|
+
return `docs/experiments/${String(exp.id).padStart(3, "0")}-${exp.slug}.md`;
|
|
5463
|
+
}
|
|
5464
|
+
function resolveExperimentArg(db, args) {
|
|
5465
|
+
const slugArg = args.filter((a) => !a.startsWith("--"))[0];
|
|
5466
|
+
let exp;
|
|
5467
|
+
if (slugArg) {
|
|
5468
|
+
exp = getExperimentBySlug(db, slugArg);
|
|
5469
|
+
if (!exp) throw new Error(`Experiment not found: ${slugArg}`);
|
|
5470
|
+
} else {
|
|
5471
|
+
exp = getLatestExperiment(db);
|
|
5472
|
+
if (!exp) throw new Error('No active experiments. Run `majlis new "hypothesis"` first.');
|
|
5473
|
+
}
|
|
5474
|
+
return exp;
|
|
5475
|
+
}
|
|
5476
|
+
function ingestStructuredOutput(db, experimentId, structured) {
|
|
5477
|
+
if (!structured) return;
|
|
5478
|
+
db.transaction(() => {
|
|
5479
|
+
if (structured.decisions) {
|
|
5480
|
+
for (const d of structured.decisions) {
|
|
5481
|
+
insertDecision(db, experimentId, d.description, d.evidence_level, d.justification);
|
|
5482
|
+
}
|
|
5483
|
+
info(`Ingested ${structured.decisions.length} decision(s)`);
|
|
5484
|
+
}
|
|
5485
|
+
if (structured.grades) {
|
|
5486
|
+
for (const g of structured.grades) {
|
|
5487
|
+
insertVerification(
|
|
5488
|
+
db,
|
|
5489
|
+
experimentId,
|
|
5490
|
+
g.component,
|
|
5491
|
+
g.grade,
|
|
5492
|
+
g.provenance_intact ?? null,
|
|
5493
|
+
g.content_correct ?? null,
|
|
5494
|
+
g.notes ?? null
|
|
5495
|
+
);
|
|
5496
|
+
}
|
|
5497
|
+
info(`Ingested ${structured.grades.length} verification grade(s)`);
|
|
5498
|
+
}
|
|
5499
|
+
if (structured.doubts) {
|
|
5500
|
+
for (const d of structured.doubts) {
|
|
5501
|
+
insertDoubt(
|
|
5502
|
+
db,
|
|
5503
|
+
experimentId,
|
|
5504
|
+
d.claim_doubted,
|
|
5505
|
+
d.evidence_level_of_claim,
|
|
5506
|
+
d.evidence_for_doubt,
|
|
5507
|
+
d.severity
|
|
5508
|
+
);
|
|
5509
|
+
}
|
|
5510
|
+
info(`Ingested ${structured.doubts.length} doubt(s)`);
|
|
5511
|
+
}
|
|
5512
|
+
if (structured.challenges) {
|
|
5513
|
+
for (const c of structured.challenges) {
|
|
5514
|
+
insertChallenge(db, experimentId, c.description, c.reasoning);
|
|
5515
|
+
}
|
|
5516
|
+
info(`Ingested ${structured.challenges.length} challenge(s)`);
|
|
5517
|
+
}
|
|
5518
|
+
if (structured.reframe) {
|
|
5519
|
+
insertReframe(
|
|
5520
|
+
db,
|
|
5521
|
+
experimentId,
|
|
5522
|
+
structured.reframe.decomposition,
|
|
5523
|
+
JSON.stringify(structured.reframe.divergences),
|
|
5524
|
+
structured.reframe.recommendation
|
|
5525
|
+
);
|
|
5526
|
+
info(`Ingested reframe`);
|
|
5527
|
+
}
|
|
5528
|
+
if (structured.findings) {
|
|
5529
|
+
for (const f of structured.findings) {
|
|
5530
|
+
insertFinding(db, experimentId, f.approach, f.source, f.relevance, f.contradicts_current);
|
|
5531
|
+
}
|
|
5532
|
+
info(`Ingested ${structured.findings.length} finding(s)`);
|
|
5533
|
+
}
|
|
5534
|
+
})();
|
|
5535
|
+
}
|
|
5536
|
+
var fs11, path11, import_node_child_process5;
|
|
5537
|
+
var init_cycle = __esm({
|
|
5538
|
+
"src/commands/cycle.ts"() {
|
|
5539
|
+
"use strict";
|
|
5540
|
+
fs11 = __toESM(require("fs"));
|
|
5541
|
+
path11 = __toESM(require("path"));
|
|
5542
|
+
import_node_child_process5 = require("child_process");
|
|
5543
|
+
init_connection();
|
|
5544
|
+
init_queries();
|
|
5545
|
+
init_machine();
|
|
5546
|
+
init_types2();
|
|
5547
|
+
init_spawn();
|
|
5548
|
+
init_parse();
|
|
5549
|
+
init_resolve();
|
|
5550
|
+
init_config();
|
|
5551
|
+
init_metrics();
|
|
5552
|
+
init_git();
|
|
5553
|
+
init_format();
|
|
5554
|
+
}
|
|
5555
|
+
});
|
|
5556
|
+
|
|
5557
|
+
// src/commands/measure.ts
|
|
5558
|
+
var measure_exports = {};
|
|
5559
|
+
__export(measure_exports, {
|
|
5560
|
+
baseline: () => baseline,
|
|
5561
|
+
compare: () => compare,
|
|
5562
|
+
measure: () => measure
|
|
5563
|
+
});
|
|
5564
|
+
async function baseline(args) {
|
|
5565
|
+
await captureMetrics("before", args);
|
|
5566
|
+
}
|
|
5567
|
+
async function measure(args) {
|
|
5568
|
+
await captureMetrics("after", args);
|
|
5569
|
+
}
|
|
5570
|
+
async function captureMetrics(phase, args) {
|
|
5571
|
+
const root = findProjectRoot();
|
|
5572
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5573
|
+
const db = getDb(root);
|
|
5574
|
+
const config = loadConfig(root);
|
|
5575
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
5576
|
+
let exp;
|
|
5577
|
+
if (expIdStr !== void 0) {
|
|
5578
|
+
exp = getExperimentById(db, Number(expIdStr));
|
|
5579
|
+
} else {
|
|
5580
|
+
exp = getLatestExperiment(db);
|
|
5581
|
+
}
|
|
5582
|
+
if (!exp) throw new Error('No active experiment. Run `majlis new "hypothesis"` first.');
|
|
5583
|
+
if (config.build.pre_measure) {
|
|
5584
|
+
info(`Running pre-measure: ${config.build.pre_measure}`);
|
|
5585
|
+
try {
|
|
5586
|
+
(0, import_node_child_process6.execSync)(config.build.pre_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
|
|
5587
|
+
} catch {
|
|
5588
|
+
warn("Pre-measure command failed \u2014 continuing anyway.");
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
if (!config.metrics.command) {
|
|
5592
|
+
throw new Error("No metrics.command configured in .majlis/config.json");
|
|
5593
|
+
}
|
|
5594
|
+
info(`Running metrics: ${config.metrics.command}`);
|
|
5595
|
+
let metricsOutput;
|
|
5596
|
+
try {
|
|
5597
|
+
metricsOutput = (0, import_node_child_process6.execSync)(config.metrics.command, {
|
|
5598
|
+
cwd: root,
|
|
5599
|
+
encoding: "utf-8",
|
|
5600
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5601
|
+
});
|
|
5602
|
+
} catch (err) {
|
|
5603
|
+
throw new Error(`Metrics command failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5604
|
+
}
|
|
5605
|
+
const parsed = parseMetricsOutput(metricsOutput);
|
|
5606
|
+
if (parsed.length === 0) {
|
|
5607
|
+
warn("Metrics command returned no data.");
|
|
5608
|
+
return;
|
|
5609
|
+
}
|
|
5610
|
+
for (const m of parsed) {
|
|
5611
|
+
insertMetric(db, exp.id, phase, m.fixture, m.metric_name, m.metric_value);
|
|
5612
|
+
}
|
|
5613
|
+
success(`Captured ${parsed.length} metric(s) for ${exp.slug} (phase: ${phase})`);
|
|
5614
|
+
if (config.build.post_measure) {
|
|
5615
|
+
try {
|
|
5616
|
+
(0, import_node_child_process6.execSync)(config.build.post_measure, { cwd: root, encoding: "utf-8", stdio: "inherit" });
|
|
5617
|
+
} catch {
|
|
5618
|
+
warn("Post-measure command failed.");
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
async function compare(args, isJson) {
|
|
5623
|
+
const root = findProjectRoot();
|
|
5624
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5625
|
+
const db = getDb(root);
|
|
5626
|
+
const config = loadConfig(root);
|
|
5627
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
5628
|
+
let exp;
|
|
5629
|
+
if (expIdStr !== void 0) {
|
|
5630
|
+
exp = getExperimentById(db, Number(expIdStr));
|
|
5631
|
+
} else {
|
|
5632
|
+
exp = getLatestExperiment(db);
|
|
5633
|
+
}
|
|
5634
|
+
if (!exp) throw new Error("No active experiment.");
|
|
5635
|
+
const comparisons = compareMetrics(db, exp.id, config);
|
|
5636
|
+
if (comparisons.length === 0) {
|
|
5637
|
+
warn(`No before/after metrics to compare for ${exp.slug}. Run baseline and measure first.`);
|
|
5638
|
+
return;
|
|
5639
|
+
}
|
|
5640
|
+
if (isJson) {
|
|
5641
|
+
console.log(JSON.stringify({ experiment: exp.slug, comparisons }, null, 2));
|
|
5642
|
+
return;
|
|
5643
|
+
}
|
|
5644
|
+
header(`Metric Comparison \u2014 ${exp.slug}`);
|
|
5645
|
+
const regressions = comparisons.filter((c) => c.regression);
|
|
5646
|
+
const rows = comparisons.map((c) => [
|
|
5647
|
+
c.fixture,
|
|
5648
|
+
c.metric,
|
|
5649
|
+
String(c.before),
|
|
5650
|
+
String(c.after),
|
|
5651
|
+
formatDelta(c.delta),
|
|
5652
|
+
c.regression ? red("REGRESSION") : green("OK")
|
|
5653
|
+
]);
|
|
5654
|
+
console.log(table(["Fixture", "Metric", "Before", "After", "Delta", "Status"], rows));
|
|
5655
|
+
if (regressions.length > 0) {
|
|
5656
|
+
console.log();
|
|
5657
|
+
warn(`${regressions.length} regression(s) detected!`);
|
|
5658
|
+
} else {
|
|
5659
|
+
console.log();
|
|
5660
|
+
success("No regressions detected.");
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
function formatDelta(delta) {
|
|
5664
|
+
const prefix = delta > 0 ? "+" : "";
|
|
5665
|
+
return `${prefix}${delta.toFixed(4)}`;
|
|
5666
|
+
}
|
|
5667
|
+
var import_node_child_process6;
|
|
5668
|
+
var init_measure = __esm({
|
|
5669
|
+
"src/commands/measure.ts"() {
|
|
5670
|
+
"use strict";
|
|
5671
|
+
import_node_child_process6 = require("child_process");
|
|
5672
|
+
init_connection();
|
|
5673
|
+
init_queries();
|
|
5674
|
+
init_metrics();
|
|
5675
|
+
init_config();
|
|
5676
|
+
init_format();
|
|
5677
|
+
}
|
|
5678
|
+
});
|
|
5679
|
+
|
|
5680
|
+
// src/commands/experiment.ts
|
|
5681
|
+
var experiment_exports = {};
|
|
5682
|
+
__export(experiment_exports, {
|
|
5683
|
+
newExperiment: () => newExperiment,
|
|
5684
|
+
revert: () => revert
|
|
5685
|
+
});
|
|
5686
|
+
async function newExperiment(args) {
|
|
5687
|
+
const root = findProjectRoot();
|
|
5688
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5689
|
+
const hypothesis = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
5690
|
+
if (!hypothesis) {
|
|
5691
|
+
throw new Error('Usage: majlis new "hypothesis"');
|
|
5692
|
+
}
|
|
5693
|
+
const db = getDb(root);
|
|
5694
|
+
const config = loadConfig(root);
|
|
5695
|
+
let slug = getFlagValue(args, "--slug") ?? await generateSlug(hypothesis, root);
|
|
5696
|
+
let attempt = 0;
|
|
5697
|
+
while (getExperimentBySlug(db, slug + (attempt ? `-${attempt}` : ""))) {
|
|
5698
|
+
attempt++;
|
|
5699
|
+
}
|
|
5700
|
+
if (attempt > 0) {
|
|
5701
|
+
const original = slug;
|
|
5702
|
+
slug = `${slug}-${attempt}`;
|
|
5703
|
+
info(`Slug "${original}" already exists, using "${slug}"`);
|
|
5704
|
+
}
|
|
5705
|
+
const allExps = db.prepare("SELECT COUNT(*) as count FROM experiments").get();
|
|
5706
|
+
const num = allExps.count + 1;
|
|
5707
|
+
const paddedNum = String(num).padStart(3, "0");
|
|
5708
|
+
const branch = `exp/${paddedNum}-${slug}`;
|
|
5709
|
+
try {
|
|
5710
|
+
(0, import_node_child_process7.execFileSync)("git", ["checkout", "-b", branch], {
|
|
5711
|
+
cwd: root,
|
|
5712
|
+
encoding: "utf-8",
|
|
5713
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5714
|
+
});
|
|
5715
|
+
info(`Created branch: ${branch}`);
|
|
5716
|
+
} catch (err) {
|
|
5717
|
+
warn(`Could not create branch ${branch} \u2014 continuing without git branch.`);
|
|
5718
|
+
}
|
|
5719
|
+
const subType = getFlagValue(args, "--sub-type") ?? null;
|
|
5720
|
+
const dependsOn = getFlagValue(args, "--depends-on") ?? null;
|
|
5721
|
+
const contextArg = getFlagValue(args, "--context") ?? null;
|
|
5722
|
+
const contextFiles = contextArg ? contextArg.split(",").map((f) => f.trim()) : null;
|
|
5723
|
+
if (dependsOn) {
|
|
5724
|
+
const depExp = getExperimentBySlug(db, dependsOn);
|
|
5725
|
+
if (!depExp) {
|
|
5726
|
+
throw new Error(`Dependency experiment not found: ${dependsOn}`);
|
|
5727
|
+
}
|
|
5728
|
+
info(`Depends on: ${dependsOn} (status: ${depExp.status})`);
|
|
5729
|
+
}
|
|
5730
|
+
const exp = createExperiment(db, slug, branch, hypothesis, subType, null, dependsOn, contextFiles);
|
|
5731
|
+
if (contextFiles) {
|
|
5732
|
+
info(`Context files: ${contextFiles.join(", ")}`);
|
|
5733
|
+
}
|
|
5734
|
+
success(`Created experiment #${exp.id}: ${exp.slug}`);
|
|
5735
|
+
const docRelPath = expDocRelPath(exp);
|
|
5736
|
+
const docsDir = path12.join(root, "docs", "experiments");
|
|
5737
|
+
const templatePath = path12.join(docsDir, "_TEMPLATE.md");
|
|
5738
|
+
if (fs12.existsSync(templatePath)) {
|
|
5739
|
+
const template = fs12.readFileSync(templatePath, "utf-8");
|
|
5740
|
+
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]);
|
|
5741
|
+
const logPath = path12.join(root, docRelPath);
|
|
5742
|
+
fs12.writeFileSync(logPath, logContent);
|
|
5743
|
+
info(`Created experiment log: ${docRelPath}`);
|
|
5744
|
+
}
|
|
5745
|
+
autoCommit(root, `new: ${slug}`);
|
|
5746
|
+
if (config.cycle.auto_baseline_on_new_experiment && config.metrics.command) {
|
|
5747
|
+
info("Auto-baselining... (run `majlis baseline` to do this manually)");
|
|
5748
|
+
try {
|
|
5749
|
+
const { baseline: baseline2 } = await Promise.resolve().then(() => (init_measure(), measure_exports));
|
|
5750
|
+
await baseline2(["--experiment", String(exp.id)]);
|
|
5751
|
+
} catch (err) {
|
|
5752
|
+
warn("Auto-baseline failed \u2014 run `majlis baseline` manually.");
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
async function revert(args) {
|
|
5757
|
+
const root = findProjectRoot();
|
|
5758
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5759
|
+
const db = getDb(root);
|
|
5760
|
+
let exp;
|
|
5761
|
+
const slugArg = args.filter((a) => !a.startsWith("--"))[0];
|
|
5762
|
+
if (slugArg) {
|
|
5763
|
+
exp = getExperimentBySlug(db, slugArg);
|
|
5764
|
+
if (!exp) throw new Error(`Experiment not found: ${slugArg}`);
|
|
5765
|
+
} else {
|
|
5766
|
+
exp = getLatestExperiment(db);
|
|
5767
|
+
if (!exp) throw new Error("No active experiments to revert.");
|
|
5768
|
+
}
|
|
5769
|
+
const reason = getFlagValue(args, "--reason") ?? "Manually reverted";
|
|
5770
|
+
const contextArg = getFlagValue(args, "--context") ?? null;
|
|
5771
|
+
const contextFiles = contextArg ? contextArg.split(",").map((f) => f.trim()) : [];
|
|
5772
|
+
let whyFailed = reason;
|
|
5773
|
+
let structuralConstraint = `Reverted: ${reason}`;
|
|
5774
|
+
let category = args.includes("--structural") ? "structural" : "procedural";
|
|
5775
|
+
try {
|
|
5776
|
+
const gitDiff = getGitDiff(root, exp.branch);
|
|
5777
|
+
const synthesis = truncateContext(
|
|
5778
|
+
readFileOrEmpty(path12.join(root, "docs", "synthesis", "current.md")),
|
|
5779
|
+
CONTEXT_LIMITS.synthesis
|
|
5780
|
+
);
|
|
5781
|
+
const fragility = truncateContext(
|
|
5782
|
+
readFileOrEmpty(path12.join(root, "docs", "synthesis", "fragility.md")),
|
|
5783
|
+
CONTEXT_LIMITS.fragility
|
|
5784
|
+
);
|
|
5785
|
+
const deadEnds = exp.sub_type ? listStructuralDeadEndsBySubType(db, exp.sub_type) : listStructuralDeadEnds(db);
|
|
5786
|
+
let supplementary = "";
|
|
5787
|
+
if (contextFiles.length > 0) {
|
|
5788
|
+
const sections = ["## Artifact Files (pointed to by --context)"];
|
|
5789
|
+
for (const relPath of contextFiles) {
|
|
5790
|
+
const absPath = path12.join(root, relPath);
|
|
5791
|
+
try {
|
|
5792
|
+
const content = fs12.readFileSync(absPath, "utf-8");
|
|
5793
|
+
sections.push(`### ${relPath}
|
|
5794
|
+
\`\`\`
|
|
5795
|
+
${content.slice(0, 12e3)}
|
|
5796
|
+
\`\`\``);
|
|
5797
|
+
} catch {
|
|
5798
|
+
sections.push(`### ${relPath}
|
|
5799
|
+
*(file not found)*`);
|
|
5800
|
+
}
|
|
5801
|
+
}
|
|
5802
|
+
supplementary = sections.join("\n\n");
|
|
5803
|
+
}
|
|
5804
|
+
let taskPrompt = `Analyze this reverted experiment and produce a structured dead-end record.
|
|
5805
|
+
|
|
5806
|
+
`;
|
|
5807
|
+
taskPrompt += `## Experiment
|
|
5808
|
+
- Slug: ${exp.slug}
|
|
5809
|
+
- Hypothesis: ${exp.hypothesis ?? "(none)"}
|
|
5810
|
+
`;
|
|
5811
|
+
taskPrompt += `- Status at revert: ${exp.status}
|
|
5812
|
+
- Sub-type: ${exp.sub_type ?? "(none)"}
|
|
5813
|
+
|
|
5814
|
+
`;
|
|
5815
|
+
taskPrompt += `## User's Reason for Reverting
|
|
5816
|
+
${reason}
|
|
5817
|
+
|
|
5818
|
+
`;
|
|
5819
|
+
if (gitDiff) {
|
|
5820
|
+
taskPrompt += `## Git Diff (branch vs main)
|
|
5821
|
+
\`\`\`diff
|
|
5822
|
+
${gitDiff.slice(0, 15e3)}
|
|
5823
|
+
\`\`\`
|
|
5824
|
+
|
|
5825
|
+
`;
|
|
5826
|
+
}
|
|
5827
|
+
if (supplementary) {
|
|
5828
|
+
taskPrompt += `${supplementary}
|
|
5829
|
+
|
|
5830
|
+
`;
|
|
5831
|
+
}
|
|
5832
|
+
taskPrompt += "Produce a specific structural constraint. Include scope (what this applies to and does NOT apply to).";
|
|
5833
|
+
info("Running post-mortem analysis...");
|
|
5834
|
+
const result = await spawnAgent("postmortem", {
|
|
5835
|
+
experiment: {
|
|
5836
|
+
id: exp.id,
|
|
5837
|
+
slug: exp.slug,
|
|
5838
|
+
hypothesis: exp.hypothesis,
|
|
5839
|
+
status: exp.status,
|
|
5840
|
+
sub_type: exp.sub_type,
|
|
5841
|
+
builder_guidance: null
|
|
5842
|
+
},
|
|
5843
|
+
deadEnds: deadEnds.map((d) => ({
|
|
5844
|
+
approach: d.approach,
|
|
5845
|
+
why_failed: d.why_failed,
|
|
5846
|
+
structural_constraint: d.structural_constraint
|
|
5847
|
+
})),
|
|
5848
|
+
fragility,
|
|
5849
|
+
synthesis,
|
|
5850
|
+
supplementaryContext: supplementary || void 0,
|
|
5851
|
+
taskPrompt
|
|
5852
|
+
}, root);
|
|
5853
|
+
if (result.structured?.postmortem) {
|
|
5854
|
+
const pm = result.structured.postmortem;
|
|
5855
|
+
whyFailed = pm.why_failed;
|
|
5856
|
+
structuralConstraint = pm.structural_constraint;
|
|
5857
|
+
category = pm.category;
|
|
5858
|
+
success("Post-mortem analysis complete.");
|
|
5859
|
+
} else {
|
|
5860
|
+
warn("Post-mortem agent did not produce structured output. Using --reason text.");
|
|
5861
|
+
}
|
|
5862
|
+
} catch (err) {
|
|
5863
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5864
|
+
warn(`Post-mortem agent failed: ${msg}. Using --reason text.`);
|
|
5865
|
+
}
|
|
5866
|
+
insertDeadEnd(
|
|
5867
|
+
db,
|
|
5868
|
+
exp.id,
|
|
5869
|
+
exp.hypothesis ?? exp.slug,
|
|
5870
|
+
whyFailed,
|
|
5871
|
+
structuralConstraint,
|
|
5872
|
+
exp.sub_type,
|
|
5873
|
+
category
|
|
5874
|
+
);
|
|
5875
|
+
if (exp.gate_rejection_reason) clearGateRejection(db, exp.id);
|
|
5876
|
+
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "revert");
|
|
5877
|
+
handleDeadEndGit(exp, root);
|
|
5878
|
+
info(`Experiment ${exp.slug} reverted to dead-end.`);
|
|
5879
|
+
info(`Constraint: ${structuralConstraint.slice(0, 120)}${structuralConstraint.length > 120 ? "..." : ""}`);
|
|
5880
|
+
}
|
|
5881
|
+
function getGitDiff(root, branch) {
|
|
5882
|
+
try {
|
|
5883
|
+
return (0, import_node_child_process7.execFileSync)("git", ["diff", `main...${branch}`, "--stat", "--patch"], {
|
|
5884
|
+
cwd: root,
|
|
5885
|
+
encoding: "utf-8",
|
|
5886
|
+
maxBuffer: 1024 * 1024,
|
|
5887
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5888
|
+
}).trim();
|
|
5889
|
+
} catch {
|
|
5890
|
+
try {
|
|
5891
|
+
return (0, import_node_child_process7.execFileSync)("git", ["diff", `master...${branch}`, "--stat", "--patch"], {
|
|
5892
|
+
cwd: root,
|
|
5893
|
+
encoding: "utf-8",
|
|
5894
|
+
maxBuffer: 1024 * 1024,
|
|
5895
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5896
|
+
}).trim();
|
|
5897
|
+
} catch {
|
|
5898
|
+
return null;
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
var fs12, path12, import_node_child_process7;
|
|
5903
|
+
var init_experiment = __esm({
|
|
5904
|
+
"src/commands/experiment.ts"() {
|
|
5905
|
+
"use strict";
|
|
5906
|
+
fs12 = __toESM(require("fs"));
|
|
5907
|
+
path12 = __toESM(require("path"));
|
|
5908
|
+
import_node_child_process7 = require("child_process");
|
|
5909
|
+
init_connection();
|
|
5910
|
+
init_queries();
|
|
5911
|
+
init_machine();
|
|
5912
|
+
init_types2();
|
|
5913
|
+
init_config();
|
|
5914
|
+
init_spawn();
|
|
5915
|
+
init_git();
|
|
5916
|
+
init_cycle();
|
|
5917
|
+
init_format();
|
|
5918
|
+
}
|
|
5919
|
+
});
|
|
5920
|
+
|
|
5921
|
+
// src/commands/session.ts
|
|
5922
|
+
var session_exports = {};
|
|
5923
|
+
__export(session_exports, {
|
|
5924
|
+
session: () => session
|
|
5925
|
+
});
|
|
5926
|
+
async function session(args) {
|
|
5927
|
+
const subcommand = args[0];
|
|
5928
|
+
if (!subcommand || subcommand !== "start" && subcommand !== "end") {
|
|
5929
|
+
throw new Error('Usage: majlis session start "intent" | majlis session end');
|
|
5930
|
+
}
|
|
5931
|
+
const root = findProjectRoot();
|
|
5932
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5933
|
+
const db = getDb(root);
|
|
5934
|
+
if (subcommand === "start") {
|
|
5935
|
+
const intent = args.slice(1).filter((a) => !a.startsWith("--")).join(" ");
|
|
5936
|
+
if (!intent) {
|
|
5937
|
+
throw new Error('Usage: majlis session start "intent"');
|
|
5938
|
+
}
|
|
5939
|
+
const existing = getActiveSession(db);
|
|
5940
|
+
if (existing) {
|
|
5941
|
+
warn(`Session already active: "${existing.intent}" (started ${existing.started_at})`);
|
|
5942
|
+
warn("End it first with `majlis session end`.");
|
|
5943
|
+
return;
|
|
5944
|
+
}
|
|
5945
|
+
const latestExp = getLatestExperiment(db);
|
|
5946
|
+
const sess = startSession(db, intent, latestExp?.id ?? null);
|
|
5947
|
+
success(`Session started: "${intent}" (id: ${sess.id})`);
|
|
5948
|
+
if (latestExp) {
|
|
5949
|
+
info(`Linked to experiment: ${latestExp.slug} (${latestExp.status})`);
|
|
5950
|
+
}
|
|
5951
|
+
} else {
|
|
5952
|
+
const active = getActiveSession(db);
|
|
5953
|
+
if (!active) {
|
|
5954
|
+
throw new Error("No active session to end.");
|
|
5955
|
+
}
|
|
5956
|
+
const accomplished = getFlagValue(args, "--accomplished") ?? null;
|
|
5957
|
+
const unfinished = getFlagValue(args, "--unfinished") ?? null;
|
|
5958
|
+
const fragility = getFlagValue(args, "--fragility") ?? null;
|
|
5959
|
+
endSession(db, active.id, accomplished, unfinished, fragility);
|
|
5960
|
+
success(`Session ended: "${active.intent}"`);
|
|
5961
|
+
if (accomplished) info(`Accomplished: ${accomplished}`);
|
|
5962
|
+
if (unfinished) info(`Unfinished: ${unfinished}`);
|
|
5963
|
+
if (fragility) warn(`New fragility: ${fragility}`);
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
var init_session = __esm({
|
|
5967
|
+
"src/commands/session.ts"() {
|
|
5968
|
+
"use strict";
|
|
5969
|
+
init_connection();
|
|
5970
|
+
init_queries();
|
|
5971
|
+
init_config();
|
|
5972
|
+
init_format();
|
|
5973
|
+
}
|
|
5974
|
+
});
|
|
5975
|
+
|
|
5976
|
+
// src/commands/query.ts
|
|
5977
|
+
var query_exports = {};
|
|
5978
|
+
__export(query_exports, {
|
|
5979
|
+
query: () => query3
|
|
5980
|
+
});
|
|
5981
|
+
async function query3(command, args, isJson) {
|
|
5982
|
+
const root = findProjectRoot();
|
|
5983
|
+
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
5984
|
+
const db = getDb(root);
|
|
5985
|
+
switch (command) {
|
|
5986
|
+
case "decisions":
|
|
5987
|
+
return queryDecisions(db, args, isJson);
|
|
5988
|
+
case "dead-ends":
|
|
5989
|
+
return queryDeadEnds(db, args, isJson);
|
|
5990
|
+
case "fragility":
|
|
5991
|
+
return queryFragility(root, isJson);
|
|
5992
|
+
case "history":
|
|
5993
|
+
return queryHistory(db, args, isJson);
|
|
5994
|
+
case "circuit-breakers":
|
|
5995
|
+
return queryCircuitBreakers(db, root, isJson);
|
|
5996
|
+
case "check-commit":
|
|
5997
|
+
return checkCommit(db);
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
6000
|
+
function queryDecisions(db, args, isJson) {
|
|
6001
|
+
const level = getFlagValue(args, "--level");
|
|
6002
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
6003
|
+
const experimentId = expIdStr !== void 0 ? Number(expIdStr) : void 0;
|
|
6004
|
+
const decisions = listAllDecisions(db, level, experimentId);
|
|
6005
|
+
if (isJson) {
|
|
6006
|
+
console.log(JSON.stringify(decisions, null, 2));
|
|
6007
|
+
return;
|
|
6008
|
+
}
|
|
6009
|
+
if (decisions.length === 0) {
|
|
6010
|
+
info("No decisions found.");
|
|
6011
|
+
return;
|
|
6012
|
+
}
|
|
6013
|
+
header("Decisions");
|
|
6014
|
+
const rows = decisions.map((d) => [
|
|
6015
|
+
String(d.id),
|
|
6016
|
+
String(d.experiment_id),
|
|
6017
|
+
evidenceColor(d.evidence_level),
|
|
6018
|
+
d.description.slice(0, 60) + (d.description.length > 60 ? "..." : ""),
|
|
6019
|
+
d.status
|
|
6020
|
+
]);
|
|
6021
|
+
console.log(table(["ID", "Exp", "Level", "Description", "Status"], rows));
|
|
6022
|
+
}
|
|
6023
|
+
function queryDeadEnds(db, args, isJson) {
|
|
6024
|
+
const subType = getFlagValue(args, "--sub-type");
|
|
6025
|
+
const searchTerm = getFlagValue(args, "--search");
|
|
6026
|
+
let deadEnds;
|
|
6027
|
+
if (subType) {
|
|
6028
|
+
deadEnds = listDeadEndsBySubType(db, subType);
|
|
6029
|
+
} else if (searchTerm) {
|
|
6030
|
+
deadEnds = searchDeadEnds(db, searchTerm);
|
|
6031
|
+
} else {
|
|
6032
|
+
deadEnds = listAllDeadEnds(db);
|
|
6033
|
+
}
|
|
6034
|
+
if (isJson) {
|
|
6035
|
+
console.log(JSON.stringify(deadEnds, null, 2));
|
|
6036
|
+
return;
|
|
5638
6037
|
}
|
|
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'.`);
|
|
6038
|
+
if (deadEnds.length === 0) {
|
|
6039
|
+
info("No dead-ends recorded.");
|
|
5658
6040
|
return;
|
|
5659
6041
|
}
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
6042
|
+
header("Dead-End Registry");
|
|
6043
|
+
const rows = deadEnds.map((d) => [
|
|
6044
|
+
String(d.id),
|
|
6045
|
+
d.sub_type ?? "\u2014",
|
|
6046
|
+
d.approach.slice(0, 40) + (d.approach.length > 40 ? "..." : ""),
|
|
6047
|
+
d.structural_constraint.slice(0, 40) + (d.structural_constraint.length > 40 ? "..." : "")
|
|
6048
|
+
]);
|
|
6049
|
+
console.log(table(["ID", "Sub-Type", "Approach", "Constraint"], rows));
|
|
6050
|
+
}
|
|
6051
|
+
function queryFragility(root, isJson) {
|
|
6052
|
+
const fragPath = path13.join(root, "docs", "synthesis", "fragility.md");
|
|
6053
|
+
if (!fs13.existsSync(fragPath)) {
|
|
6054
|
+
info("No fragility map found.");
|
|
6055
|
+
return;
|
|
5672
6056
|
}
|
|
5673
|
-
|
|
5674
|
-
|
|
6057
|
+
const content = fs13.readFileSync(fragPath, "utf-8");
|
|
6058
|
+
if (isJson) {
|
|
6059
|
+
console.log(JSON.stringify({ content }, null, 2));
|
|
6060
|
+
return;
|
|
6061
|
+
}
|
|
6062
|
+
header("Fragility Map");
|
|
6063
|
+
console.log(content);
|
|
5675
6064
|
}
|
|
5676
|
-
|
|
5677
|
-
const
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
const
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
6065
|
+
function queryHistory(db, args, isJson) {
|
|
6066
|
+
const fixture = args.filter((a) => !a.startsWith("--"))[0];
|
|
6067
|
+
if (!fixture) {
|
|
6068
|
+
throw new Error("Usage: majlis history <fixture>");
|
|
6069
|
+
}
|
|
6070
|
+
const history = getMetricHistoryByFixture(db, fixture);
|
|
6071
|
+
if (isJson) {
|
|
6072
|
+
console.log(JSON.stringify(history, null, 2));
|
|
6073
|
+
return;
|
|
6074
|
+
}
|
|
6075
|
+
if (history.length === 0) {
|
|
6076
|
+
info(`No metric history for fixture: ${fixture}`);
|
|
6077
|
+
return;
|
|
6078
|
+
}
|
|
6079
|
+
header(`Metric History \u2014 ${fixture}`);
|
|
6080
|
+
const rows = history.map((h) => [
|
|
6081
|
+
String(h.experiment_id),
|
|
6082
|
+
h.experiment_slug ?? "\u2014",
|
|
6083
|
+
h.phase,
|
|
6084
|
+
h.metric_name,
|
|
6085
|
+
String(h.metric_value),
|
|
6086
|
+
h.captured_at
|
|
6087
|
+
]);
|
|
6088
|
+
console.log(table(["Exp", "Slug", "Phase", "Metric", "Value", "Captured"], rows));
|
|
5688
6089
|
}
|
|
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.");
|
|
6090
|
+
function queryCircuitBreakers(db, root, isJson) {
|
|
6091
|
+
const config = loadConfig(root);
|
|
6092
|
+
const states = getAllCircuitBreakerStates(db, config.cycle.circuit_breaker_threshold);
|
|
6093
|
+
if (isJson) {
|
|
6094
|
+
console.log(JSON.stringify(states, null, 2));
|
|
6095
|
+
return;
|
|
6096
|
+
}
|
|
6097
|
+
if (states.length === 0) {
|
|
6098
|
+
info("No circuit breaker data.");
|
|
6099
|
+
return;
|
|
5704
6100
|
}
|
|
6101
|
+
header("Circuit Breakers");
|
|
6102
|
+
const rows = states.map((s) => [
|
|
6103
|
+
s.sub_type,
|
|
6104
|
+
String(s.failure_count),
|
|
6105
|
+
String(config.cycle.circuit_breaker_threshold),
|
|
6106
|
+
s.tripped ? red("TRIPPED") : green("OK")
|
|
6107
|
+
]);
|
|
6108
|
+
console.log(table(["Sub-Type", "Failures", "Threshold", "Status"], rows));
|
|
5705
6109
|
}
|
|
5706
|
-
function
|
|
5707
|
-
|
|
5708
|
-
let files;
|
|
6110
|
+
function checkCommit(db) {
|
|
6111
|
+
let stdinData = "";
|
|
5709
6112
|
try {
|
|
5710
|
-
|
|
6113
|
+
stdinData = fs13.readFileSync(0, "utf-8");
|
|
5711
6114
|
} catch {
|
|
5712
|
-
return "";
|
|
5713
6115
|
}
|
|
5714
|
-
if (
|
|
5715
|
-
const sections = ["## Experiment-Scoped Reference Material"];
|
|
5716
|
-
for (const relPath of files) {
|
|
5717
|
-
const absPath = path13.join(root, relPath);
|
|
6116
|
+
if (stdinData) {
|
|
5718
6117
|
try {
|
|
5719
|
-
const
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
6118
|
+
const hookInput = JSON.parse(stdinData);
|
|
6119
|
+
const command = hookInput?.tool_input?.command ?? "";
|
|
6120
|
+
if (!command.includes("git commit")) {
|
|
6121
|
+
return;
|
|
6122
|
+
}
|
|
5724
6123
|
} catch {
|
|
5725
|
-
sections.push(`### ${relPath}
|
|
5726
|
-
*(file not found)*`);
|
|
5727
6124
|
}
|
|
5728
6125
|
}
|
|
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)`);
|
|
6126
|
+
const active = listActiveExperiments(db);
|
|
6127
|
+
const unverified = active.filter(
|
|
6128
|
+
(e) => !["merged", "dead_end", "verified", "resolved", "compressed"].includes(e.status)
|
|
6129
|
+
);
|
|
6130
|
+
if (unverified.length > 0) {
|
|
6131
|
+
console.error(`[majlis] ${unverified.length} unverified experiment(s):`);
|
|
6132
|
+
for (const e of unverified) {
|
|
6133
|
+
console.error(` - ${e.slug} (${e.status})`);
|
|
5800
6134
|
}
|
|
5801
|
-
|
|
6135
|
+
process.exit(1);
|
|
6136
|
+
}
|
|
5802
6137
|
}
|
|
5803
|
-
var fs13, path13
|
|
5804
|
-
var
|
|
5805
|
-
"src/commands/
|
|
6138
|
+
var fs13, path13;
|
|
6139
|
+
var init_query = __esm({
|
|
6140
|
+
"src/commands/query.ts"() {
|
|
5806
6141
|
"use strict";
|
|
5807
6142
|
fs13 = __toESM(require("fs"));
|
|
5808
6143
|
path13 = __toESM(require("path"));
|
|
5809
|
-
import_node_child_process7 = require("child_process");
|
|
5810
6144
|
init_connection();
|
|
5811
6145
|
init_queries();
|
|
5812
|
-
init_machine();
|
|
5813
|
-
init_types2();
|
|
5814
|
-
init_spawn();
|
|
5815
|
-
init_parse();
|
|
5816
|
-
init_resolve();
|
|
5817
6146
|
init_config();
|
|
5818
|
-
init_metrics();
|
|
5819
|
-
init_git();
|
|
5820
6147
|
init_format();
|
|
5821
6148
|
}
|
|
5822
6149
|
});
|
|
@@ -6011,13 +6338,14 @@ async function next(args, isJson) {
|
|
|
6011
6338
|
exp = found;
|
|
6012
6339
|
}
|
|
6013
6340
|
const auto = args.includes("--auto");
|
|
6341
|
+
const overrideGate = args.includes("--override-gate");
|
|
6014
6342
|
if (auto) {
|
|
6015
6343
|
await runAutoLoop(db, exp, config, root, isJson);
|
|
6016
6344
|
} else {
|
|
6017
|
-
await runNextStep(db, exp, config, root, isJson);
|
|
6345
|
+
await runNextStep(db, exp, config, root, isJson, overrideGate);
|
|
6018
6346
|
}
|
|
6019
6347
|
}
|
|
6020
|
-
async function runNextStep(db, exp, config, root, isJson) {
|
|
6348
|
+
async function runNextStep(db, exp, config, root, isJson, overrideGate = false) {
|
|
6021
6349
|
const currentStatus = exp.status;
|
|
6022
6350
|
const valid = validNext(currentStatus);
|
|
6023
6351
|
if (valid.length === 0) {
|
|
@@ -6040,10 +6368,21 @@ async function runNextStep(db, exp, config, root, isJson) {
|
|
|
6040
6368
|
"procedural"
|
|
6041
6369
|
);
|
|
6042
6370
|
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "circuit_breaker");
|
|
6371
|
+
handleDeadEndGit(exp, root);
|
|
6043
6372
|
warn("Experiment dead-ended. Triggering Maqasid Check (purpose audit).");
|
|
6044
6373
|
await audit([config.project?.objective ?? ""]);
|
|
6045
6374
|
return;
|
|
6046
6375
|
}
|
|
6376
|
+
if (exp.status === "gated" && exp.gate_rejection_reason) {
|
|
6377
|
+
if (overrideGate) {
|
|
6378
|
+
clearGateRejection(db, exp.id);
|
|
6379
|
+
info(`Gate override accepted for ${exp.slug}. Proceeding to build.`);
|
|
6380
|
+
} else {
|
|
6381
|
+
warn(`Gate rejected: ${exp.gate_rejection_reason}`);
|
|
6382
|
+
info("Run `majlis next --override-gate` to proceed anyway, or `majlis revert` to abandon.");
|
|
6383
|
+
return;
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6047
6386
|
const sessionsSinceCompression = getSessionsSinceCompression(db);
|
|
6048
6387
|
if (sessionsSinceCompression >= config.cycle.compression_interval) {
|
|
6049
6388
|
warn(
|
|
@@ -6074,6 +6413,11 @@ async function runAutoLoop(db, exp, config, root, isJson) {
|
|
|
6074
6413
|
const freshExp = getExperimentBySlug(db, exp.slug);
|
|
6075
6414
|
if (!freshExp) break;
|
|
6076
6415
|
exp = freshExp;
|
|
6416
|
+
if (exp.gate_rejection_reason) {
|
|
6417
|
+
warn(`Gate rejected: ${exp.gate_rejection_reason}`);
|
|
6418
|
+
info("Stopping auto mode. Use `majlis next --override-gate` or `majlis revert`.");
|
|
6419
|
+
break;
|
|
6420
|
+
}
|
|
6077
6421
|
if (isTerminal(exp.status)) {
|
|
6078
6422
|
success(`Experiment ${exp.slug} reached terminal state: ${exp.status}`);
|
|
6079
6423
|
break;
|
|
@@ -6090,6 +6434,7 @@ async function runAutoLoop(db, exp, config, root, isJson) {
|
|
|
6090
6434
|
"procedural"
|
|
6091
6435
|
);
|
|
6092
6436
|
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "circuit_breaker");
|
|
6437
|
+
handleDeadEndGit(exp, root);
|
|
6093
6438
|
await audit([config.project?.objective ?? ""]);
|
|
6094
6439
|
break;
|
|
6095
6440
|
}
|
|
@@ -6166,6 +6511,7 @@ var init_next = __esm({
|
|
|
6166
6511
|
init_config();
|
|
6167
6512
|
init_cycle();
|
|
6168
6513
|
init_audit();
|
|
6514
|
+
init_git();
|
|
6169
6515
|
init_format();
|
|
6170
6516
|
}
|
|
6171
6517
|
});
|
|
@@ -6237,6 +6583,28 @@ async function run(args) {
|
|
|
6237
6583
|
try {
|
|
6238
6584
|
await next([exp.slug], false);
|
|
6239
6585
|
consecutiveFailures = 0;
|
|
6586
|
+
const afterStep = getExperimentBySlug(db, exp.slug);
|
|
6587
|
+
if (afterStep?.gate_rejection_reason) {
|
|
6588
|
+
warn(`Gate rejected in autonomous mode: ${afterStep.gate_rejection_reason}. Dead-ending.`);
|
|
6589
|
+
insertDeadEnd(
|
|
6590
|
+
db,
|
|
6591
|
+
afterStep.id,
|
|
6592
|
+
afterStep.hypothesis ?? afterStep.slug,
|
|
6593
|
+
afterStep.gate_rejection_reason,
|
|
6594
|
+
`Gate rejected: ${afterStep.gate_rejection_reason}`,
|
|
6595
|
+
afterStep.sub_type,
|
|
6596
|
+
"procedural"
|
|
6597
|
+
);
|
|
6598
|
+
adminTransitionAndPersist(
|
|
6599
|
+
db,
|
|
6600
|
+
afterStep.id,
|
|
6601
|
+
afterStep.status,
|
|
6602
|
+
"dead_end" /* DEAD_END */,
|
|
6603
|
+
"revert"
|
|
6604
|
+
);
|
|
6605
|
+
handleDeadEndGit(afterStep, root);
|
|
6606
|
+
continue;
|
|
6607
|
+
}
|
|
6240
6608
|
} catch (err) {
|
|
6241
6609
|
consecutiveFailures++;
|
|
6242
6610
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -6252,6 +6620,7 @@ async function run(args) {
|
|
|
6252
6620
|
"procedural"
|
|
6253
6621
|
);
|
|
6254
6622
|
adminTransitionAndPersist(db, exp.id, exp.status, "dead_end" /* DEAD_END */, "error_recovery");
|
|
6623
|
+
handleDeadEndGit(exp, root);
|
|
6255
6624
|
} catch (innerErr) {
|
|
6256
6625
|
const innerMsg = innerErr instanceof Error ? innerErr.message : String(innerErr);
|
|
6257
6626
|
warn(`Could not record dead-end: ${innerMsg}`);
|
|
@@ -6391,14 +6760,15 @@ async function createNewExperiment(db, root, hypothesis) {
|
|
|
6391
6760
|
const exp = createExperiment(db, finalSlug, branch, hypothesis, null, null);
|
|
6392
6761
|
adminTransitionAndPersist(db, exp.id, exp.status, "reframed" /* REFRAMED */, "bootstrap");
|
|
6393
6762
|
exp.status = "reframed";
|
|
6763
|
+
const docRelPath = expDocRelPath(exp);
|
|
6394
6764
|
const docsDir = path16.join(root, "docs", "experiments");
|
|
6395
6765
|
const templatePath = path16.join(docsDir, "_TEMPLATE.md");
|
|
6396
6766
|
if (fs16.existsSync(templatePath)) {
|
|
6397
6767
|
const template = fs16.readFileSync(templatePath, "utf-8");
|
|
6398
6768
|
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(
|
|
6769
|
+
const logPath = path16.join(root, docRelPath);
|
|
6400
6770
|
fs16.writeFileSync(logPath, logContent);
|
|
6401
|
-
info(`Created experiment log:
|
|
6771
|
+
info(`Created experiment log: ${docRelPath}`);
|
|
6402
6772
|
}
|
|
6403
6773
|
return exp;
|
|
6404
6774
|
}
|
|
@@ -6686,8 +7056,9 @@ function importExperimentFromWorktree(sourceDb, targetDb, slug) {
|
|
|
6686
7056
|
const sourceId = sourceExp.id;
|
|
6687
7057
|
const insertExp = targetDb.prepare(`
|
|
6688
7058
|
INSERT INTO experiments (slug, branch, status, classification_ref, sub_type,
|
|
6689
|
-
hypothesis, builder_guidance,
|
|
6690
|
-
|
|
7059
|
+
hypothesis, builder_guidance, depends_on, context_files,
|
|
7060
|
+
gate_rejection_reason, created_at, updated_at)
|
|
7061
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6691
7062
|
`);
|
|
6692
7063
|
const result = insertExp.run(
|
|
6693
7064
|
sourceExp.slug,
|
|
@@ -6697,6 +7068,9 @@ function importExperimentFromWorktree(sourceDb, targetDb, slug) {
|
|
|
6697
7068
|
sourceExp.sub_type,
|
|
6698
7069
|
sourceExp.hypothesis,
|
|
6699
7070
|
sourceExp.builder_guidance,
|
|
7071
|
+
sourceExp.depends_on ?? null,
|
|
7072
|
+
sourceExp.context_files ?? null,
|
|
7073
|
+
sourceExp.gate_rejection_reason ?? null,
|
|
6700
7074
|
sourceExp.created_at,
|
|
6701
7075
|
sourceExp.updated_at
|
|
6702
7076
|
);
|