majlis 0.4.5 → 0.5.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 +855 -278
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -29,6 +29,27 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
30
|
mod
|
|
31
31
|
));
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
|
+
|
|
34
|
+
// src/shutdown.ts
|
|
35
|
+
var shutdown_exports = {};
|
|
36
|
+
__export(shutdown_exports, {
|
|
37
|
+
isShutdownRequested: () => isShutdownRequested,
|
|
38
|
+
requestShutdown: () => requestShutdown
|
|
39
|
+
});
|
|
40
|
+
function requestShutdown() {
|
|
41
|
+
_requested = true;
|
|
42
|
+
}
|
|
43
|
+
function isShutdownRequested() {
|
|
44
|
+
return _requested;
|
|
45
|
+
}
|
|
46
|
+
var _requested;
|
|
47
|
+
var init_shutdown = __esm({
|
|
48
|
+
"src/shutdown.ts"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
_requested = false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
32
53
|
|
|
33
54
|
// src/db/migrations.ts
|
|
34
55
|
function runMigrations(db) {
|
|
@@ -170,6 +191,34 @@ var init_migrations = __esm({
|
|
|
170
191
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
171
192
|
);
|
|
172
193
|
CREATE INDEX idx_challenges_experiment ON challenges(experiment_id);
|
|
194
|
+
`);
|
|
195
|
+
},
|
|
196
|
+
// Migration 004: v3 → v4 — Reframes, findings tables; dead-end classification
|
|
197
|
+
(db) => {
|
|
198
|
+
db.exec(`
|
|
199
|
+
CREATE TABLE reframes (
|
|
200
|
+
id INTEGER PRIMARY KEY,
|
|
201
|
+
experiment_id INTEGER REFERENCES experiments(id),
|
|
202
|
+
decomposition TEXT NOT NULL,
|
|
203
|
+
divergences TEXT NOT NULL,
|
|
204
|
+
recommendation TEXT NOT NULL,
|
|
205
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
206
|
+
);
|
|
207
|
+
CREATE INDEX idx_reframes_experiment ON reframes(experiment_id);
|
|
208
|
+
|
|
209
|
+
CREATE TABLE findings (
|
|
210
|
+
id INTEGER PRIMARY KEY,
|
|
211
|
+
experiment_id INTEGER REFERENCES experiments(id),
|
|
212
|
+
approach TEXT NOT NULL,
|
|
213
|
+
source TEXT NOT NULL,
|
|
214
|
+
relevance TEXT NOT NULL,
|
|
215
|
+
contradicts_current BOOLEAN NOT NULL DEFAULT 0,
|
|
216
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
217
|
+
);
|
|
218
|
+
CREATE INDEX idx_findings_experiment ON findings(experiment_id);
|
|
219
|
+
|
|
220
|
+
ALTER TABLE dead_ends ADD COLUMN category TEXT DEFAULT 'structural'
|
|
221
|
+
CHECK(category IN ('structural', 'procedural'));
|
|
173
222
|
`);
|
|
174
223
|
}
|
|
175
224
|
];
|
|
@@ -552,11 +601,20 @@ and write up what you learned.
|
|
|
552
601
|
- \`scripts/benchmark.py\` \u2014 the measurement tool. Never change how you're measured.
|
|
553
602
|
- \`.majlis/\` \u2014 framework config. Not your concern.
|
|
554
603
|
|
|
604
|
+
## Confirmed Doubts
|
|
605
|
+
If your context includes confirmedDoubts, these are weaknesses that the verifier has
|
|
606
|
+
confirmed from a previous cycle. You MUST address each one. Do not ignore them \u2014
|
|
607
|
+
the verifier will check again.
|
|
608
|
+
|
|
609
|
+
## Metrics
|
|
610
|
+
The framework captures baseline and post-build metrics automatically. Do NOT claim
|
|
611
|
+
specific metric numbers unless quoting framework output. Do NOT run the benchmark
|
|
612
|
+
yourself unless instructed to. If you need to verify your change works, do a minimal
|
|
613
|
+
targeted test, not a full benchmark run.
|
|
614
|
+
|
|
555
615
|
## During building:
|
|
556
616
|
- Tag EVERY decision: proof / test / strong-consensus / consensus / analogy / judgment
|
|
557
617
|
- When making judgment-level decisions, state: "This is judgment \u2014 reasoning without precedent"
|
|
558
|
-
- Run baseline metrics BEFORE making changes
|
|
559
|
-
- Run comparison metrics AFTER making changes (once)
|
|
560
618
|
|
|
561
619
|
You may NOT verify your own work or mark your own decisions as proven.
|
|
562
620
|
Output your decisions in structured format so they can be recorded in the database.
|
|
@@ -579,8 +637,14 @@ tools: [Read, Glob, Grep]
|
|
|
579
637
|
---
|
|
580
638
|
You are the Critic. You practise constructive doubt.
|
|
581
639
|
|
|
582
|
-
You receive
|
|
583
|
-
|
|
640
|
+
You receive:
|
|
641
|
+
- The builder's experiment document (the artifact, not the reasoning chain)
|
|
642
|
+
- The current synthesis (project state)
|
|
643
|
+
- Dead-ends (approaches that have been tried and failed)
|
|
644
|
+
- The hypothesis and experiment metadata
|
|
645
|
+
|
|
646
|
+
You do NOT see the builder's reasoning chain \u2014 only their documented output.
|
|
647
|
+
Use the experiment doc, synthesis, and dead-ends to find weaknesses.
|
|
584
648
|
|
|
585
649
|
For each doubt:
|
|
586
650
|
- What specific claim, decision, or assumption you doubt
|
|
@@ -611,6 +675,13 @@ tools: [Read, Glob, Grep]
|
|
|
611
675
|
You are the Adversary. You do NOT review code for bugs.
|
|
612
676
|
You reason about problem structure to CONSTRUCT pathological cases.
|
|
613
677
|
|
|
678
|
+
You receive:
|
|
679
|
+
- The git diff of the builder's code changes (the actual code, not prose)
|
|
680
|
+
- The current synthesis (project state)
|
|
681
|
+
- The hypothesis and experiment metadata
|
|
682
|
+
|
|
683
|
+
Study the CODE DIFF carefully \u2014 that is where the builder's assumptions are exposed.
|
|
684
|
+
|
|
614
685
|
For each approach the builder takes, ask:
|
|
615
686
|
- What input would make this fail?
|
|
616
687
|
- What boundary condition was not tested?
|
|
@@ -637,6 +708,12 @@ tools: [Read, Glob, Grep, Bash]
|
|
|
637
708
|
---
|
|
638
709
|
You are the Verifier. Perform dual verification:
|
|
639
710
|
|
|
711
|
+
You receive:
|
|
712
|
+
- All doubts with explicit DOUBT-{id} identifiers (use these in your doubt_resolutions)
|
|
713
|
+
- Challenge documents from the adversary
|
|
714
|
+
- Framework-captured metrics (baseline vs post-build) \u2014 this is GROUND TRUTH
|
|
715
|
+
- The hypothesis and experiment metadata
|
|
716
|
+
|
|
640
717
|
## Scope Constraint (CRITICAL)
|
|
641
718
|
|
|
642
719
|
You must produce your structured output (grades + doubt resolutions) within your turn budget.
|
|
@@ -646,6 +723,11 @@ Reserve your final turns for writing the structured majlis-json output.
|
|
|
646
723
|
|
|
647
724
|
The framework saves your output automatically. Do NOT attempt to write files.
|
|
648
725
|
|
|
726
|
+
## Metrics (GROUND TRUTH)
|
|
727
|
+
If framework-captured metrics are in your context, these are the canonical before/after numbers.
|
|
728
|
+
Do NOT trust numbers claimed by the builder \u2014 compare against the framework metrics.
|
|
729
|
+
If the builder claims improvement but the framework metrics show regression, flag this.
|
|
730
|
+
|
|
649
731
|
## PROVENANCE CHECK:
|
|
650
732
|
- Can every piece of code trace to an experiment or decision?
|
|
651
733
|
- Is the chain unbroken from requirement -> classification -> experiment -> code?
|
|
@@ -660,13 +742,17 @@ Grade each component: sound / good / weak / rejected
|
|
|
660
742
|
Grade each doubt/challenge: confirmed / dismissed (with evidence) / inconclusive
|
|
661
743
|
|
|
662
744
|
## Structured Output Format
|
|
745
|
+
IMPORTANT: For doubt_resolutions, use the DOUBT-{id} numbers from your context.
|
|
746
|
+
Example: if your context lists "DOUBT-7: [critical] The algorithm fails on X",
|
|
747
|
+
use doubt_id: 7 in your output.
|
|
748
|
+
|
|
663
749
|
<!-- majlis-json
|
|
664
750
|
{
|
|
665
751
|
"grades": [
|
|
666
752
|
{ "component": "...", "grade": "sound|good|weak|rejected", "provenance_intact": true, "content_correct": true, "notes": "..." }
|
|
667
753
|
],
|
|
668
754
|
"doubt_resolutions": [
|
|
669
|
-
{ "doubt_id":
|
|
755
|
+
{ "doubt_id": 7, "resolution": "confirmed|dismissed|inconclusive" }
|
|
670
756
|
]
|
|
671
757
|
}
|
|
672
758
|
-->`,
|
|
@@ -692,7 +778,18 @@ Compare your decomposition with the existing classification.
|
|
|
692
778
|
Flag structural divergences \u2014 these are the most valuable signals.
|
|
693
779
|
|
|
694
780
|
Produce your reframe document as output. Do NOT attempt to write files.
|
|
695
|
-
The framework saves your output automatically
|
|
781
|
+
The framework saves your output automatically.
|
|
782
|
+
|
|
783
|
+
## Structured Output Format
|
|
784
|
+
<!-- majlis-json
|
|
785
|
+
{
|
|
786
|
+
"reframe": {
|
|
787
|
+
"decomposition": "How you decomposed the problem",
|
|
788
|
+
"divergences": ["List of structural divergences from current classification"],
|
|
789
|
+
"recommendation": "What should change based on your independent analysis"
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
-->`,
|
|
696
793
|
compressor: `---
|
|
697
794
|
name: compressor
|
|
698
795
|
model: opus
|
|
@@ -700,23 +797,36 @@ tools: [Read, Write, Edit, Glob, Grep]
|
|
|
700
797
|
---
|
|
701
798
|
You are the Compressor. Hold the entire project in view and compress it.
|
|
702
799
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
800
|
+
Your taskPrompt includes a "Structured Data (CANONICAL)" section exported directly
|
|
801
|
+
from the SQLite database. This is the source of truth. docs/ files are agent artifacts
|
|
802
|
+
that may contain stale or incorrect information. Cross-reference everything against
|
|
803
|
+
the database export.
|
|
804
|
+
|
|
805
|
+
1. Read the database export in your context FIRST \u2014 it has all experiments, decisions,
|
|
806
|
+
doubts (with resolutions), verifications (with grades), challenges, and dead-ends.
|
|
807
|
+
2. Read docs/ files for narrative context, but trust the database when they conflict.
|
|
808
|
+
3. Cross-reference: same question in different language? contradicting decisions?
|
|
706
809
|
workaround masking root cause?
|
|
707
|
-
|
|
810
|
+
4. Update fragility map: thin coverage, weak components, untested judgment
|
|
708
811
|
decisions, broken provenance.
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
812
|
+
5. Update dead-end registry: compress rejected experiments into structural constraints.
|
|
813
|
+
Mark each dead-end as [structural] or [procedural].
|
|
814
|
+
6. REWRITE synthesis using the Write tool \u2014 shorter and denser. If it's growing,
|
|
815
|
+
you're accumulating, not compressing. You MUST use the Write tool to update
|
|
816
|
+
docs/synthesis/current.md, docs/synthesis/fragility.md, and docs/synthesis/dead-ends.md.
|
|
817
|
+
The framework does NOT auto-save your output for these files.
|
|
818
|
+
7. Review classification: new sub-types? resolved sub-types?
|
|
713
819
|
|
|
714
820
|
You may NOT write code, make decisions, or run experiments.
|
|
715
821
|
|
|
716
822
|
## Structured Output Format
|
|
717
823
|
<!-- majlis-json
|
|
718
824
|
{
|
|
719
|
-
"
|
|
825
|
+
"compression_report": {
|
|
826
|
+
"synthesis_delta": "What changed in synthesis and why",
|
|
827
|
+
"new_dead_ends": ["List of newly identified dead-end constraints"],
|
|
828
|
+
"fragility_changes": ["List of changes to the fragility map"]
|
|
829
|
+
}
|
|
720
830
|
}
|
|
721
831
|
-->`,
|
|
722
832
|
scout: `---
|
|
@@ -729,6 +839,11 @@ You are the Scout. You practise rihla \u2014 travel in search of knowledge.
|
|
|
729
839
|
Your job is to search externally for alternative approaches, contradictory evidence,
|
|
730
840
|
and perspectives from other fields that could inform the current experiment.
|
|
731
841
|
|
|
842
|
+
You receive:
|
|
843
|
+
- The current synthesis and fragility map
|
|
844
|
+
- Dead-ends (approaches that have been tried and failed) \u2014 search for alternatives that circumvent these
|
|
845
|
+
- The hypothesis and experiment metadata
|
|
846
|
+
|
|
732
847
|
For the given experiment:
|
|
733
848
|
1. Describe the problem in domain-neutral terms
|
|
734
849
|
2. Search for alternative approaches in other fields or frameworks
|
|
@@ -739,13 +854,60 @@ For the given experiment:
|
|
|
739
854
|
Rules:
|
|
740
855
|
- Present findings neutrally. Report each approach on its own terms.
|
|
741
856
|
- Note where external approaches contradict the current one \u2014 these are the most valuable signals.
|
|
857
|
+
- Focus on approaches that CIRCUMVENT known dead-ends \u2014 these are the most valuable.
|
|
742
858
|
- You may NOT modify code or make decisions. Produce your rihla document as output only.
|
|
743
859
|
- Do NOT attempt to write files. The framework saves your output automatically.
|
|
744
860
|
|
|
745
861
|
## Structured Output Format
|
|
746
862
|
<!-- majlis-json
|
|
747
863
|
{
|
|
748
|
-
"
|
|
864
|
+
"findings": [
|
|
865
|
+
{ "approach": "Name of alternative approach", "source": "Where you found it", "relevance": "How it applies", "contradicts_current": true }
|
|
866
|
+
]
|
|
867
|
+
}
|
|
868
|
+
-->`,
|
|
869
|
+
gatekeeper: `---
|
|
870
|
+
name: gatekeeper
|
|
871
|
+
model: sonnet
|
|
872
|
+
tools: [Read, Glob, Grep]
|
|
873
|
+
---
|
|
874
|
+
You are the Gatekeeper. You check hypotheses before expensive build cycles.
|
|
875
|
+
|
|
876
|
+
Your job is a fast quality gate \u2014 prevent wasted Opus builds on hypotheses that
|
|
877
|
+
are stale, redundant with dead-ends, or too vague to produce a focused change.
|
|
878
|
+
|
|
879
|
+
## Checks (in order)
|
|
880
|
+
|
|
881
|
+
### 1. Stale References
|
|
882
|
+
Does the hypothesis reference specific functions, line numbers, or structures that
|
|
883
|
+
may not exist in the current code? Read the relevant files to verify.
|
|
884
|
+
- If references are stale, list them in stale_references.
|
|
885
|
+
|
|
886
|
+
### 2. Dead-End Overlap
|
|
887
|
+
Does this hypothesis repeat an approach already ruled out by structural dead-ends?
|
|
888
|
+
Check each structural dead-end in your context \u2014 if the hypothesis matches the
|
|
889
|
+
approach or violates the structural_constraint, flag it.
|
|
890
|
+
- If overlapping, list the dead-end IDs in overlapping_dead_ends.
|
|
891
|
+
|
|
892
|
+
### 3. Scope Check
|
|
893
|
+
Is this a single focused change? A good hypothesis names ONE function, mechanism,
|
|
894
|
+
or parameter to change. A bad hypothesis says "improve X and also Y and also Z."
|
|
895
|
+
- Flag if the hypothesis tries to do multiple things.
|
|
896
|
+
|
|
897
|
+
## Output
|
|
898
|
+
|
|
899
|
+
gate_decision:
|
|
900
|
+
- **approve** \u2014 all checks pass, proceed to build
|
|
901
|
+
- **flag** \u2014 concerns found but not blocking (warnings only)
|
|
902
|
+
- **reject** \u2014 hypothesis must be revised (stale refs, dead-end repeat, or too vague)
|
|
903
|
+
|
|
904
|
+
## Structured Output Format
|
|
905
|
+
<!-- majlis-json
|
|
906
|
+
{
|
|
907
|
+
"gate_decision": "approve|reject|flag",
|
|
908
|
+
"reason": "Brief explanation of decision",
|
|
909
|
+
"stale_references": ["list of stale references found, if any"],
|
|
910
|
+
"overlapping_dead_ends": [0]
|
|
749
911
|
}
|
|
750
912
|
-->`
|
|
751
913
|
};
|
|
@@ -1235,12 +1397,12 @@ function getMetricHistoryByFixture(db, fixture) {
|
|
|
1235
1397
|
ORDER BY m.captured_at
|
|
1236
1398
|
`).all(fixture);
|
|
1237
1399
|
}
|
|
1238
|
-
function insertDeadEnd(db, experimentId, approach, whyFailed, structuralConstraint, subType) {
|
|
1400
|
+
function insertDeadEnd(db, experimentId, approach, whyFailed, structuralConstraint, subType, category = "structural") {
|
|
1239
1401
|
const stmt = db.prepare(`
|
|
1240
|
-
INSERT INTO dead_ends (experiment_id, approach, why_failed, structural_constraint, sub_type)
|
|
1241
|
-
VALUES (?, ?, ?, ?, ?)
|
|
1402
|
+
INSERT INTO dead_ends (experiment_id, approach, why_failed, structural_constraint, sub_type, category)
|
|
1403
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1242
1404
|
`);
|
|
1243
|
-
const result = stmt.run(experimentId, approach, whyFailed, structuralConstraint, subType);
|
|
1405
|
+
const result = stmt.run(experimentId, approach, whyFailed, structuralConstraint, subType, category);
|
|
1244
1406
|
return db.prepare("SELECT * FROM dead_ends WHERE id = ?").get(result.lastInsertRowid);
|
|
1245
1407
|
}
|
|
1246
1408
|
function listDeadEndsBySubType(db, subType) {
|
|
@@ -1315,6 +1477,9 @@ function insertChallenge(db, experimentId, description, reasoning) {
|
|
|
1315
1477
|
const result = stmt.run(experimentId, description, reasoning);
|
|
1316
1478
|
return db.prepare("SELECT * FROM challenges WHERE id = ?").get(result.lastInsertRowid);
|
|
1317
1479
|
}
|
|
1480
|
+
function getChallengesByExperiment(db, experimentId) {
|
|
1481
|
+
return db.prepare("SELECT * FROM challenges WHERE experiment_id = ? ORDER BY created_at").all(experimentId);
|
|
1482
|
+
}
|
|
1318
1483
|
function incrementSubTypeFailure(db, subType, experimentId, grade) {
|
|
1319
1484
|
db.prepare(`
|
|
1320
1485
|
INSERT INTO sub_type_failures (sub_type, experiment_id, grade)
|
|
@@ -1380,12 +1545,167 @@ function recordCompression(db, sessionCountSinceLast, synthesisSizeBefore, synth
|
|
|
1380
1545
|
const result = stmt.run(sessionCountSinceLast, synthesisSizeBefore, synthesisSizeAfter);
|
|
1381
1546
|
return db.prepare("SELECT * FROM compressions WHERE id = ?").get(result.lastInsertRowid);
|
|
1382
1547
|
}
|
|
1548
|
+
function listStructuralDeadEnds(db) {
|
|
1549
|
+
return db.prepare(`
|
|
1550
|
+
SELECT * FROM dead_ends WHERE category = 'structural' ORDER BY created_at
|
|
1551
|
+
`).all();
|
|
1552
|
+
}
|
|
1553
|
+
function listStructuralDeadEndsBySubType(db, subType) {
|
|
1554
|
+
return db.prepare(`
|
|
1555
|
+
SELECT * FROM dead_ends WHERE category = 'structural' AND sub_type = ? ORDER BY created_at
|
|
1556
|
+
`).all(subType);
|
|
1557
|
+
}
|
|
1558
|
+
function insertReframe(db, experimentId, decomposition, divergences, recommendation) {
|
|
1559
|
+
db.prepare(`
|
|
1560
|
+
INSERT INTO reframes (experiment_id, decomposition, divergences, recommendation)
|
|
1561
|
+
VALUES (?, ?, ?, ?)
|
|
1562
|
+
`).run(experimentId, decomposition, divergences, recommendation);
|
|
1563
|
+
}
|
|
1564
|
+
function insertFinding(db, experimentId, approach, source, relevance, contradictsCurrent) {
|
|
1565
|
+
db.prepare(`
|
|
1566
|
+
INSERT INTO findings (experiment_id, approach, source, relevance, contradicts_current)
|
|
1567
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1568
|
+
`).run(experimentId, approach, source, relevance, contradictsCurrent ? 1 : 0);
|
|
1569
|
+
}
|
|
1570
|
+
function exportForCompressor(db, maxLength = 3e4) {
|
|
1571
|
+
const experiments = listAllExperiments(db);
|
|
1572
|
+
const sections = ["# Structured Data Export (from SQLite)\n"];
|
|
1573
|
+
sections.push("## Experiments");
|
|
1574
|
+
for (const exp of experiments) {
|
|
1575
|
+
sections.push(`### EXP-${String(exp.id).padStart(3, "0")}: ${exp.slug}`);
|
|
1576
|
+
sections.push(`- Status: ${exp.status} | Sub-type: ${exp.sub_type ?? "(none)"}`);
|
|
1577
|
+
sections.push(`- Hypothesis: ${exp.hypothesis ?? "(none)"}`);
|
|
1578
|
+
const decisions = listDecisionsByExperiment(db, exp.id);
|
|
1579
|
+
if (decisions.length > 0) {
|
|
1580
|
+
sections.push(`#### Decisions (${decisions.length})`);
|
|
1581
|
+
for (const d of decisions) {
|
|
1582
|
+
sections.push(`- [${d.evidence_level}] ${d.description} \u2014 ${d.justification} (${d.status})`);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
const doubts = getDoubtsByExperiment(db, exp.id);
|
|
1586
|
+
if (doubts.length > 0) {
|
|
1587
|
+
sections.push(`#### Doubts (${doubts.length})`);
|
|
1588
|
+
for (const d of doubts) {
|
|
1589
|
+
sections.push(`- [${d.severity}] ${d.claim_doubted} (resolution: ${d.resolution ?? "pending"})`);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
const verifications = getVerificationsByExperiment(db, exp.id);
|
|
1593
|
+
if (verifications.length > 0) {
|
|
1594
|
+
sections.push(`#### Verifications (${verifications.length})`);
|
|
1595
|
+
for (const v of verifications) {
|
|
1596
|
+
sections.push(`- ${v.component}: ${v.grade}${v.notes ? ` \u2014 ${v.notes}` : ""}`);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
const challenges = getChallengesByExperiment(db, exp.id);
|
|
1600
|
+
if (challenges.length > 0) {
|
|
1601
|
+
sections.push(`#### Challenges (${challenges.length})`);
|
|
1602
|
+
for (const c of challenges) {
|
|
1603
|
+
sections.push(`- ${c.description}`);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
sections.push("");
|
|
1607
|
+
}
|
|
1608
|
+
const deadEnds = listAllDeadEnds(db);
|
|
1609
|
+
if (deadEnds.length > 0) {
|
|
1610
|
+
sections.push("## Dead Ends");
|
|
1611
|
+
for (const de of deadEnds) {
|
|
1612
|
+
sections.push(`- [${de.category ?? "structural"}] ${de.approach}: ${de.why_failed} \u2192 ${de.structural_constraint}`);
|
|
1613
|
+
}
|
|
1614
|
+
sections.push("");
|
|
1615
|
+
}
|
|
1616
|
+
const unresolvedDoubts = db.prepare(`
|
|
1617
|
+
SELECT d.*, e.slug as experiment_slug
|
|
1618
|
+
FROM doubts d JOIN experiments e ON d.experiment_id = e.id
|
|
1619
|
+
WHERE d.resolution IS NULL
|
|
1620
|
+
ORDER BY d.severity DESC, d.created_at
|
|
1621
|
+
`).all();
|
|
1622
|
+
if (unresolvedDoubts.length > 0) {
|
|
1623
|
+
sections.push("## Unresolved Doubts");
|
|
1624
|
+
for (const d of unresolvedDoubts) {
|
|
1625
|
+
sections.push(`- [${d.severity}] ${d.claim_doubted} (exp: ${d.experiment_slug})`);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
const full = sections.join("\n");
|
|
1629
|
+
if (full.length > maxLength) {
|
|
1630
|
+
return full.slice(0, maxLength) + `
|
|
1631
|
+
|
|
1632
|
+
[TRUNCATED \u2014 full export was ${full.length} chars]`;
|
|
1633
|
+
}
|
|
1634
|
+
return full;
|
|
1635
|
+
}
|
|
1383
1636
|
var init_queries = __esm({
|
|
1384
1637
|
"src/db/queries.ts"() {
|
|
1385
1638
|
"use strict";
|
|
1386
1639
|
}
|
|
1387
1640
|
});
|
|
1388
1641
|
|
|
1642
|
+
// src/config.ts
|
|
1643
|
+
function loadConfig(projectRoot) {
|
|
1644
|
+
if (_cachedConfig && _cachedRoot === projectRoot) return _cachedConfig;
|
|
1645
|
+
const configPath = path3.join(projectRoot, ".majlis", "config.json");
|
|
1646
|
+
if (!fs3.existsSync(configPath)) {
|
|
1647
|
+
_cachedConfig = { ...DEFAULT_CONFIG2 };
|
|
1648
|
+
_cachedRoot = projectRoot;
|
|
1649
|
+
return _cachedConfig;
|
|
1650
|
+
}
|
|
1651
|
+
const loaded = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
1652
|
+
_cachedConfig = {
|
|
1653
|
+
...DEFAULT_CONFIG2,
|
|
1654
|
+
...loaded,
|
|
1655
|
+
project: { ...DEFAULT_CONFIG2.project, ...loaded.project },
|
|
1656
|
+
metrics: { ...DEFAULT_CONFIG2.metrics, ...loaded.metrics },
|
|
1657
|
+
build: { ...DEFAULT_CONFIG2.build, ...loaded.build },
|
|
1658
|
+
cycle: { ...DEFAULT_CONFIG2.cycle, ...loaded.cycle }
|
|
1659
|
+
};
|
|
1660
|
+
_cachedRoot = projectRoot;
|
|
1661
|
+
return _cachedConfig;
|
|
1662
|
+
}
|
|
1663
|
+
function readFileOrEmpty(filePath) {
|
|
1664
|
+
try {
|
|
1665
|
+
return fs3.readFileSync(filePath, "utf-8");
|
|
1666
|
+
} catch {
|
|
1667
|
+
return "";
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
function getFlagValue(args, flag) {
|
|
1671
|
+
const idx = args.indexOf(flag);
|
|
1672
|
+
if (idx < 0 || idx + 1 >= args.length) return void 0;
|
|
1673
|
+
return args[idx + 1];
|
|
1674
|
+
}
|
|
1675
|
+
function truncateContext(content, limit) {
|
|
1676
|
+
if (content.length <= limit) return content;
|
|
1677
|
+
return content.slice(0, limit) + "\n[TRUNCATED]";
|
|
1678
|
+
}
|
|
1679
|
+
var fs3, path3, DEFAULT_CONFIG2, _cachedConfig, _cachedRoot, CONTEXT_LIMITS;
|
|
1680
|
+
var init_config = __esm({
|
|
1681
|
+
"src/config.ts"() {
|
|
1682
|
+
"use strict";
|
|
1683
|
+
fs3 = __toESM(require("fs"));
|
|
1684
|
+
path3 = __toESM(require("path"));
|
|
1685
|
+
DEFAULT_CONFIG2 = {
|
|
1686
|
+
project: { name: "", description: "", objective: "" },
|
|
1687
|
+
metrics: { command: "", fixtures: [], tracked: {} },
|
|
1688
|
+
build: { pre_measure: null, post_measure: null },
|
|
1689
|
+
cycle: {
|
|
1690
|
+
compression_interval: 5,
|
|
1691
|
+
circuit_breaker_threshold: 3,
|
|
1692
|
+
require_doubt_before_verify: true,
|
|
1693
|
+
require_challenge_before_verify: false,
|
|
1694
|
+
auto_baseline_on_new_experiment: true
|
|
1695
|
+
},
|
|
1696
|
+
models: {}
|
|
1697
|
+
};
|
|
1698
|
+
_cachedConfig = null;
|
|
1699
|
+
_cachedRoot = null;
|
|
1700
|
+
CONTEXT_LIMITS = {
|
|
1701
|
+
synthesis: 3e4,
|
|
1702
|
+
fragility: 15e3,
|
|
1703
|
+
experimentDoc: 15e3,
|
|
1704
|
+
deadEnds: 15e3
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
});
|
|
1708
|
+
|
|
1389
1709
|
// src/commands/status.ts
|
|
1390
1710
|
var status_exports = {};
|
|
1391
1711
|
__export(status_exports, {
|
|
@@ -1475,21 +1795,12 @@ function buildSummary(expCount, activeSession, sessionsSinceCompression, config)
|
|
|
1475
1795
|
}
|
|
1476
1796
|
return parts.join(". ");
|
|
1477
1797
|
}
|
|
1478
|
-
function loadConfig(projectRoot) {
|
|
1479
|
-
const configPath = path3.join(projectRoot, ".majlis", "config.json");
|
|
1480
|
-
if (!fs3.existsSync(configPath)) {
|
|
1481
|
-
throw new Error("Missing .majlis/config.json. Run `majlis init` first.");
|
|
1482
|
-
}
|
|
1483
|
-
return JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
1484
|
-
}
|
|
1485
|
-
var fs3, path3;
|
|
1486
1798
|
var init_status = __esm({
|
|
1487
1799
|
"src/commands/status.ts"() {
|
|
1488
1800
|
"use strict";
|
|
1489
|
-
fs3 = __toESM(require("fs"));
|
|
1490
|
-
path3 = __toESM(require("path"));
|
|
1491
1801
|
init_connection();
|
|
1492
1802
|
init_queries();
|
|
1803
|
+
init_config();
|
|
1493
1804
|
init_format();
|
|
1494
1805
|
}
|
|
1495
1806
|
});
|
|
@@ -1571,11 +1882,11 @@ async function captureMetrics(phase, args) {
|
|
|
1571
1882
|
const root = findProjectRoot();
|
|
1572
1883
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
1573
1884
|
const db = getDb(root);
|
|
1574
|
-
const config =
|
|
1575
|
-
const
|
|
1885
|
+
const config = loadConfig(root);
|
|
1886
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
1576
1887
|
let exp;
|
|
1577
|
-
if (
|
|
1578
|
-
exp = getExperimentById(db, Number(
|
|
1888
|
+
if (expIdStr !== void 0) {
|
|
1889
|
+
exp = getExperimentById(db, Number(expIdStr));
|
|
1579
1890
|
} else {
|
|
1580
1891
|
exp = getLatestExperiment(db);
|
|
1581
1892
|
}
|
|
@@ -1623,11 +1934,11 @@ async function compare(args, isJson) {
|
|
|
1623
1934
|
const root = findProjectRoot();
|
|
1624
1935
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
1625
1936
|
const db = getDb(root);
|
|
1626
|
-
const config =
|
|
1627
|
-
const
|
|
1937
|
+
const config = loadConfig(root);
|
|
1938
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
1628
1939
|
let exp;
|
|
1629
|
-
if (
|
|
1630
|
-
exp = getExperimentById(db, Number(
|
|
1940
|
+
if (expIdStr !== void 0) {
|
|
1941
|
+
exp = getExperimentById(db, Number(expIdStr));
|
|
1631
1942
|
} else {
|
|
1632
1943
|
exp = getLatestExperiment(db);
|
|
1633
1944
|
}
|
|
@@ -1664,23 +1975,15 @@ function formatDelta(delta) {
|
|
|
1664
1975
|
const prefix = delta > 0 ? "+" : "";
|
|
1665
1976
|
return `${prefix}${delta.toFixed(4)}`;
|
|
1666
1977
|
}
|
|
1667
|
-
|
|
1668
|
-
const configPath = path4.join(projectRoot, ".majlis", "config.json");
|
|
1669
|
-
if (!fs4.existsSync(configPath)) {
|
|
1670
|
-
throw new Error("Missing .majlis/config.json. Run `majlis init` first.");
|
|
1671
|
-
}
|
|
1672
|
-
return JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
1673
|
-
}
|
|
1674
|
-
var fs4, path4, import_node_child_process;
|
|
1978
|
+
var import_node_child_process;
|
|
1675
1979
|
var init_measure = __esm({
|
|
1676
1980
|
"src/commands/measure.ts"() {
|
|
1677
1981
|
"use strict";
|
|
1678
|
-
fs4 = __toESM(require("fs"));
|
|
1679
|
-
path4 = __toESM(require("path"));
|
|
1680
1982
|
import_node_child_process = require("child_process");
|
|
1681
1983
|
init_connection();
|
|
1682
1984
|
init_queries();
|
|
1683
1985
|
init_metrics();
|
|
1986
|
+
init_config();
|
|
1684
1987
|
init_format();
|
|
1685
1988
|
}
|
|
1686
1989
|
});
|
|
@@ -1699,7 +2002,7 @@ async function newExperiment(args) {
|
|
|
1699
2002
|
throw new Error('Usage: majlis new "hypothesis"');
|
|
1700
2003
|
}
|
|
1701
2004
|
const db = getDb(root);
|
|
1702
|
-
const config =
|
|
2005
|
+
const config = loadConfig(root);
|
|
1703
2006
|
const slug = slugify(hypothesis);
|
|
1704
2007
|
if (getExperimentBySlug(db, slug)) {
|
|
1705
2008
|
throw new Error(`Experiment with slug "${slug}" already exists.`);
|
|
@@ -1718,17 +2021,16 @@ async function newExperiment(args) {
|
|
|
1718
2021
|
} catch (err) {
|
|
1719
2022
|
warn(`Could not create branch ${branch} \u2014 continuing without git branch.`);
|
|
1720
2023
|
}
|
|
1721
|
-
const
|
|
1722
|
-
const subType = subTypeIdx >= 0 ? args[subTypeIdx + 1] : null;
|
|
2024
|
+
const subType = getFlagValue(args, "--sub-type") ?? null;
|
|
1723
2025
|
const exp = createExperiment(db, slug, branch, hypothesis, subType, null);
|
|
1724
2026
|
success(`Created experiment #${exp.id}: ${exp.slug}`);
|
|
1725
|
-
const docsDir =
|
|
1726
|
-
const templatePath =
|
|
1727
|
-
if (
|
|
1728
|
-
const template =
|
|
2027
|
+
const docsDir = path4.join(root, "docs", "experiments");
|
|
2028
|
+
const templatePath = path4.join(docsDir, "_TEMPLATE.md");
|
|
2029
|
+
if (fs4.existsSync(templatePath)) {
|
|
2030
|
+
const template = fs4.readFileSync(templatePath, "utf-8");
|
|
1729
2031
|
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]);
|
|
1730
|
-
const logPath =
|
|
1731
|
-
|
|
2032
|
+
const logPath = path4.join(docsDir, `${paddedNum}-${slug}.md`);
|
|
2033
|
+
fs4.writeFileSync(logPath, logContent);
|
|
1732
2034
|
info(`Created experiment log: docs/experiments/${paddedNum}-${slug}.md`);
|
|
1733
2035
|
}
|
|
1734
2036
|
if (config.cycle.auto_baseline_on_new_experiment && config.metrics.command) {
|
|
@@ -1754,15 +2056,16 @@ async function revert(args) {
|
|
|
1754
2056
|
exp = getLatestExperiment(db);
|
|
1755
2057
|
if (!exp) throw new Error("No active experiments to revert.");
|
|
1756
2058
|
}
|
|
1757
|
-
const
|
|
1758
|
-
const
|
|
2059
|
+
const reason = getFlagValue(args, "--reason") ?? "Manually reverted";
|
|
2060
|
+
const category = args.includes("--structural") ? "structural" : "procedural";
|
|
1759
2061
|
insertDeadEnd(
|
|
1760
2062
|
db,
|
|
1761
2063
|
exp.id,
|
|
1762
2064
|
exp.hypothesis ?? exp.slug,
|
|
1763
2065
|
reason,
|
|
1764
2066
|
`Reverted: ${reason}`,
|
|
1765
|
-
exp.sub_type
|
|
2067
|
+
exp.sub_type,
|
|
2068
|
+
category
|
|
1766
2069
|
);
|
|
1767
2070
|
updateExperimentStatus(db, exp.id, "dead_end");
|
|
1768
2071
|
try {
|
|
@@ -1785,22 +2088,16 @@ async function revert(args) {
|
|
|
1785
2088
|
function slugify(text) {
|
|
1786
2089
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
1787
2090
|
}
|
|
1788
|
-
|
|
1789
|
-
const configPath = path5.join(projectRoot, ".majlis", "config.json");
|
|
1790
|
-
if (!fs5.existsSync(configPath)) {
|
|
1791
|
-
return { cycle: { auto_baseline_on_new_experiment: false } };
|
|
1792
|
-
}
|
|
1793
|
-
return JSON.parse(fs5.readFileSync(configPath, "utf-8"));
|
|
1794
|
-
}
|
|
1795
|
-
var fs5, path5, import_node_child_process2;
|
|
2091
|
+
var fs4, path4, import_node_child_process2;
|
|
1796
2092
|
var init_experiment = __esm({
|
|
1797
2093
|
"src/commands/experiment.ts"() {
|
|
1798
2094
|
"use strict";
|
|
1799
|
-
|
|
1800
|
-
|
|
2095
|
+
fs4 = __toESM(require("fs"));
|
|
2096
|
+
path4 = __toESM(require("path"));
|
|
1801
2097
|
import_node_child_process2 = require("child_process");
|
|
1802
2098
|
init_connection();
|
|
1803
2099
|
init_queries();
|
|
2100
|
+
init_config();
|
|
1804
2101
|
init_format();
|
|
1805
2102
|
}
|
|
1806
2103
|
});
|
|
@@ -1840,12 +2137,9 @@ async function session(args) {
|
|
|
1840
2137
|
if (!active) {
|
|
1841
2138
|
throw new Error("No active session to end.");
|
|
1842
2139
|
}
|
|
1843
|
-
const
|
|
1844
|
-
const
|
|
1845
|
-
const
|
|
1846
|
-
const unfinished = unfinishedIdx >= 0 ? args[unfinishedIdx + 1] : null;
|
|
1847
|
-
const fragilityIdx = args.indexOf("--fragility");
|
|
1848
|
-
const fragility = fragilityIdx >= 0 ? args[fragilityIdx + 1] : null;
|
|
2140
|
+
const accomplished = getFlagValue(args, "--accomplished") ?? null;
|
|
2141
|
+
const unfinished = getFlagValue(args, "--unfinished") ?? null;
|
|
2142
|
+
const fragility = getFlagValue(args, "--fragility") ?? null;
|
|
1849
2143
|
endSession(db, active.id, accomplished, unfinished, fragility);
|
|
1850
2144
|
success(`Session ended: "${active.intent}"`);
|
|
1851
2145
|
if (accomplished) info(`Accomplished: ${accomplished}`);
|
|
@@ -1858,6 +2152,7 @@ var init_session = __esm({
|
|
|
1858
2152
|
"use strict";
|
|
1859
2153
|
init_connection();
|
|
1860
2154
|
init_queries();
|
|
2155
|
+
init_config();
|
|
1861
2156
|
init_format();
|
|
1862
2157
|
}
|
|
1863
2158
|
});
|
|
@@ -1887,10 +2182,9 @@ async function query(command, args, isJson) {
|
|
|
1887
2182
|
}
|
|
1888
2183
|
}
|
|
1889
2184
|
function queryDecisions(db, args, isJson) {
|
|
1890
|
-
const
|
|
1891
|
-
const
|
|
1892
|
-
const
|
|
1893
|
-
const experimentId = expIdx >= 0 ? Number(args[expIdx + 1]) : void 0;
|
|
2185
|
+
const level = getFlagValue(args, "--level");
|
|
2186
|
+
const expIdStr = getFlagValue(args, "--experiment");
|
|
2187
|
+
const experimentId = expIdStr !== void 0 ? Number(expIdStr) : void 0;
|
|
1894
2188
|
const decisions = listAllDecisions(db, level, experimentId);
|
|
1895
2189
|
if (isJson) {
|
|
1896
2190
|
console.log(JSON.stringify(decisions, null, 2));
|
|
@@ -1911,10 +2205,8 @@ function queryDecisions(db, args, isJson) {
|
|
|
1911
2205
|
console.log(table(["ID", "Exp", "Level", "Description", "Status"], rows));
|
|
1912
2206
|
}
|
|
1913
2207
|
function queryDeadEnds(db, args, isJson) {
|
|
1914
|
-
const
|
|
1915
|
-
const
|
|
1916
|
-
const searchIdx = args.indexOf("--search");
|
|
1917
|
-
const searchTerm = searchIdx >= 0 ? args[searchIdx + 1] : void 0;
|
|
2208
|
+
const subType = getFlagValue(args, "--sub-type");
|
|
2209
|
+
const searchTerm = getFlagValue(args, "--search");
|
|
1918
2210
|
let deadEnds;
|
|
1919
2211
|
if (subType) {
|
|
1920
2212
|
deadEnds = listDeadEndsBySubType(db, subType);
|
|
@@ -1941,12 +2233,12 @@ function queryDeadEnds(db, args, isJson) {
|
|
|
1941
2233
|
console.log(table(["ID", "Sub-Type", "Approach", "Constraint"], rows));
|
|
1942
2234
|
}
|
|
1943
2235
|
function queryFragility(root, isJson) {
|
|
1944
|
-
const fragPath =
|
|
1945
|
-
if (!
|
|
2236
|
+
const fragPath = path5.join(root, "docs", "synthesis", "fragility.md");
|
|
2237
|
+
if (!fs5.existsSync(fragPath)) {
|
|
1946
2238
|
info("No fragility map found.");
|
|
1947
2239
|
return;
|
|
1948
2240
|
}
|
|
1949
|
-
const content =
|
|
2241
|
+
const content = fs5.readFileSync(fragPath, "utf-8");
|
|
1950
2242
|
if (isJson) {
|
|
1951
2243
|
console.log(JSON.stringify({ content }, null, 2));
|
|
1952
2244
|
return;
|
|
@@ -1980,7 +2272,7 @@ function queryHistory(db, args, isJson) {
|
|
|
1980
2272
|
console.log(table(["Exp", "Slug", "Phase", "Metric", "Value", "Captured"], rows));
|
|
1981
2273
|
}
|
|
1982
2274
|
function queryCircuitBreakers(db, root, isJson) {
|
|
1983
|
-
const config =
|
|
2275
|
+
const config = loadConfig(root);
|
|
1984
2276
|
const states = getAllCircuitBreakerStates(db, config.cycle.circuit_breaker_threshold);
|
|
1985
2277
|
if (isJson) {
|
|
1986
2278
|
console.log(JSON.stringify(states, null, 2));
|
|
@@ -2002,7 +2294,7 @@ function queryCircuitBreakers(db, root, isJson) {
|
|
|
2002
2294
|
function checkCommit(db) {
|
|
2003
2295
|
let stdinData = "";
|
|
2004
2296
|
try {
|
|
2005
|
-
stdinData =
|
|
2297
|
+
stdinData = fs5.readFileSync(0, "utf-8");
|
|
2006
2298
|
} catch {
|
|
2007
2299
|
}
|
|
2008
2300
|
if (stdinData) {
|
|
@@ -2027,21 +2319,15 @@ function checkCommit(db) {
|
|
|
2027
2319
|
process.exit(1);
|
|
2028
2320
|
}
|
|
2029
2321
|
}
|
|
2030
|
-
|
|
2031
|
-
const configPath = path6.join(projectRoot, ".majlis", "config.json");
|
|
2032
|
-
if (!fs6.existsSync(configPath)) {
|
|
2033
|
-
return { cycle: { circuit_breaker_threshold: 3 } };
|
|
2034
|
-
}
|
|
2035
|
-
return JSON.parse(fs6.readFileSync(configPath, "utf-8"));
|
|
2036
|
-
}
|
|
2037
|
-
var fs6, path6;
|
|
2322
|
+
var fs5, path5;
|
|
2038
2323
|
var init_query = __esm({
|
|
2039
2324
|
"src/commands/query.ts"() {
|
|
2040
2325
|
"use strict";
|
|
2041
|
-
|
|
2042
|
-
|
|
2326
|
+
fs5 = __toESM(require("fs"));
|
|
2327
|
+
path5 = __toESM(require("path"));
|
|
2043
2328
|
init_connection();
|
|
2044
2329
|
init_queries();
|
|
2330
|
+
init_config();
|
|
2045
2331
|
init_format();
|
|
2046
2332
|
}
|
|
2047
2333
|
});
|
|
@@ -2052,8 +2338,10 @@ var init_types = __esm({
|
|
|
2052
2338
|
"src/state/types.ts"() {
|
|
2053
2339
|
"use strict";
|
|
2054
2340
|
TRANSITIONS = {
|
|
2055
|
-
["classified" /* CLASSIFIED */]: ["reframed" /* REFRAMED */, "
|
|
2056
|
-
["reframed" /* REFRAMED */]: ["
|
|
2341
|
+
["classified" /* CLASSIFIED */]: ["reframed" /* REFRAMED */, "gated" /* GATED */],
|
|
2342
|
+
["reframed" /* REFRAMED */]: ["gated" /* GATED */],
|
|
2343
|
+
["gated" /* GATED */]: ["building" /* BUILDING */, "gated" /* GATED */],
|
|
2344
|
+
// self-loop for rejected hypotheses
|
|
2057
2345
|
["building" /* BUILDING */]: ["built" /* BUILT */, "building" /* BUILDING */],
|
|
2058
2346
|
// self-loop for retry after truncation
|
|
2059
2347
|
["built" /* BUILT */]: ["challenged" /* CHALLENGED */, "doubted" /* DOUBTED */],
|
|
@@ -2063,7 +2351,9 @@ var init_types = __esm({
|
|
|
2063
2351
|
["verifying" /* VERIFYING */]: ["verified" /* VERIFIED */],
|
|
2064
2352
|
["verified" /* VERIFIED */]: ["resolved" /* RESOLVED */],
|
|
2065
2353
|
["resolved" /* RESOLVED */]: ["compressed" /* COMPRESSED */, "building" /* BUILDING */],
|
|
2354
|
+
// cycle-back skips gate
|
|
2066
2355
|
["compressed" /* COMPRESSED */]: ["merged" /* MERGED */, "building" /* BUILDING */],
|
|
2356
|
+
// cycle-back skips gate
|
|
2067
2357
|
["merged" /* MERGED */]: [],
|
|
2068
2358
|
["dead_end" /* DEAD_END */]: []
|
|
2069
2359
|
};
|
|
@@ -2092,7 +2382,10 @@ function determineNextStep(exp, valid, hasDoubts2, hasChallenges2) {
|
|
|
2092
2382
|
throw new Error(`Experiment ${exp.slug} is terminal (${exp.status})`);
|
|
2093
2383
|
}
|
|
2094
2384
|
const status2 = exp.status;
|
|
2095
|
-
if (status2 === "classified" /* CLASSIFIED */) {
|
|
2385
|
+
if (status2 === "classified" /* CLASSIFIED */ || status2 === "reframed" /* REFRAMED */) {
|
|
2386
|
+
return valid.includes("gated" /* GATED */) ? "gated" /* GATED */ : valid[0];
|
|
2387
|
+
}
|
|
2388
|
+
if (status2 === "gated" /* GATED */) {
|
|
2096
2389
|
return valid.includes("building" /* BUILDING */) ? "building" /* BUILDING */ : valid[0];
|
|
2097
2390
|
}
|
|
2098
2391
|
if (status2 === "built" /* BUILT */ && !hasDoubts2) {
|
|
@@ -2116,7 +2409,29 @@ var init_machine = __esm({
|
|
|
2116
2409
|
});
|
|
2117
2410
|
|
|
2118
2411
|
// src/agents/types.ts
|
|
2119
|
-
|
|
2412
|
+
function getExtractionSchema(role) {
|
|
2413
|
+
switch (role) {
|
|
2414
|
+
case "builder":
|
|
2415
|
+
return '{"decisions": [{"description": "string", "evidence_level": "proof|test|strong_consensus|consensus|analogy|judgment", "justification": "string"}]}';
|
|
2416
|
+
case "critic":
|
|
2417
|
+
return '{"doubts": [{"claim_doubted": "string", "evidence_level_of_claim": "string", "evidence_for_doubt": "string", "severity": "minor|moderate|critical"}]}';
|
|
2418
|
+
case "adversary":
|
|
2419
|
+
return '{"challenges": [{"description": "string", "reasoning": "string"}]}';
|
|
2420
|
+
case "verifier":
|
|
2421
|
+
return '{"grades": [{"component": "string", "grade": "sound|good|weak|rejected", "provenance_intact": true, "content_correct": true, "notes": "string"}], "doubt_resolutions": [{"doubt_id": 0, "resolution": "confirmed|dismissed|inconclusive"}]}';
|
|
2422
|
+
case "gatekeeper":
|
|
2423
|
+
return '{"gate_decision": "approve|reject|flag", "reason": "string", "stale_references": ["string"], "overlapping_dead_ends": [0]}';
|
|
2424
|
+
case "reframer":
|
|
2425
|
+
return '{"reframe": {"decomposition": "string", "divergences": ["string"], "recommendation": "string"}}';
|
|
2426
|
+
case "scout":
|
|
2427
|
+
return '{"findings": [{"approach": "string", "source": "string", "relevance": "string", "contradicts_current": true}]}';
|
|
2428
|
+
case "compressor":
|
|
2429
|
+
return '{"compression_report": {"synthesis_delta": "string", "new_dead_ends": ["string"], "fragility_changes": ["string"]}}';
|
|
2430
|
+
default:
|
|
2431
|
+
return EXTRACTION_SCHEMA;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
var EXTRACTION_SCHEMA, ROLE_REQUIRED_FIELDS;
|
|
2120
2435
|
var init_types2 = __esm({
|
|
2121
2436
|
"src/agents/types.ts"() {
|
|
2122
2437
|
"use strict";
|
|
@@ -2127,6 +2442,16 @@ var init_types2 = __esm({
|
|
|
2127
2442
|
"guidance": "string (actionable builder guidance)",
|
|
2128
2443
|
"doubt_resolutions": [{ "doubt_id": 0, "resolution": "confirmed|dismissed|inconclusive" }]
|
|
2129
2444
|
}`;
|
|
2445
|
+
ROLE_REQUIRED_FIELDS = {
|
|
2446
|
+
builder: ["decisions"],
|
|
2447
|
+
critic: ["doubts"],
|
|
2448
|
+
adversary: ["challenges"],
|
|
2449
|
+
verifier: ["grades"],
|
|
2450
|
+
gatekeeper: ["gate_decision"],
|
|
2451
|
+
reframer: ["reframe"],
|
|
2452
|
+
scout: ["findings"],
|
|
2453
|
+
compressor: ["compression_report"]
|
|
2454
|
+
};
|
|
2130
2455
|
}
|
|
2131
2456
|
});
|
|
2132
2457
|
|
|
@@ -2214,7 +2539,8 @@ function extractViaPatterns(role, markdown) {
|
|
|
2214
2539
|
while ((match = doubtPattern.exec(markdown)) !== null) {
|
|
2215
2540
|
doubts.push({
|
|
2216
2541
|
claim_doubted: match[1].trim(),
|
|
2217
|
-
evidence_level_of_claim: "
|
|
2542
|
+
evidence_level_of_claim: "unknown",
|
|
2543
|
+
// Don't fabricate — mark as unknown for review
|
|
2218
2544
|
evidence_for_doubt: "Extracted via regex \u2014 review original document",
|
|
2219
2545
|
severity: match[2].toLowerCase().trim()
|
|
2220
2546
|
});
|
|
@@ -2225,7 +2551,8 @@ function extractViaPatterns(role, markdown) {
|
|
|
2225
2551
|
async function extractViaHaiku(role, markdown) {
|
|
2226
2552
|
try {
|
|
2227
2553
|
const truncated = markdown.length > 8e3 ? markdown.slice(0, 8e3) + "\n[truncated]" : markdown;
|
|
2228
|
-
const
|
|
2554
|
+
const schema = getExtractionSchema(role);
|
|
2555
|
+
const prompt = `Extract structured data from this ${role} document as JSON. Follow this schema exactly: ${schema}
|
|
2229
2556
|
|
|
2230
2557
|
Document:
|
|
2231
2558
|
${truncated}`;
|
|
@@ -2258,7 +2585,18 @@ ${truncated}`;
|
|
|
2258
2585
|
}
|
|
2259
2586
|
}
|
|
2260
2587
|
function hasData(output) {
|
|
2261
|
-
return !!(output.decisions && output.decisions.length > 0 || output.grades && output.grades.length > 0 || output.doubts && output.doubts.length > 0 || output.guidance);
|
|
2588
|
+
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);
|
|
2589
|
+
}
|
|
2590
|
+
function validateForRole(role, output) {
|
|
2591
|
+
const required = ROLE_REQUIRED_FIELDS[role];
|
|
2592
|
+
if (!required) return { valid: true, missing: [] };
|
|
2593
|
+
const missing = required.filter((field) => {
|
|
2594
|
+
const value = output[field];
|
|
2595
|
+
if (value === void 0 || value === null) return true;
|
|
2596
|
+
if (Array.isArray(value) && value.length === 0) return true;
|
|
2597
|
+
return false;
|
|
2598
|
+
});
|
|
2599
|
+
return { valid: missing.length === 0, missing };
|
|
2262
2600
|
}
|
|
2263
2601
|
var import_claude_agent_sdk;
|
|
2264
2602
|
var init_parse = __esm({
|
|
@@ -2272,11 +2610,11 @@ var init_parse = __esm({
|
|
|
2272
2610
|
// src/agents/spawn.ts
|
|
2273
2611
|
function loadAgentDefinition(role, projectRoot) {
|
|
2274
2612
|
const root = projectRoot ?? findProjectRoot() ?? process.cwd();
|
|
2275
|
-
const filePath =
|
|
2276
|
-
if (!
|
|
2613
|
+
const filePath = path6.join(root, ".majlis", "agents", `${role}.md`);
|
|
2614
|
+
if (!fs6.existsSync(filePath)) {
|
|
2277
2615
|
throw new Error(`Agent definition not found: ${filePath}`);
|
|
2278
2616
|
}
|
|
2279
|
-
const content =
|
|
2617
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
2280
2618
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
2281
2619
|
if (!frontmatterMatch) {
|
|
2282
2620
|
throw new Error(`Invalid agent definition (missing YAML frontmatter): ${filePath}`);
|
|
@@ -2297,7 +2635,7 @@ async function spawnAgent(role, context, projectRoot) {
|
|
|
2297
2635
|
const agentDef = loadAgentDefinition(role, projectRoot);
|
|
2298
2636
|
const root = projectRoot ?? findProjectRoot() ?? process.cwd();
|
|
2299
2637
|
const taskPrompt = context.taskPrompt ?? `Perform your role as ${agentDef.name}.`;
|
|
2300
|
-
const contextJson = JSON.stringify(context
|
|
2638
|
+
const contextJson = JSON.stringify(context);
|
|
2301
2639
|
const prompt = `Here is your context:
|
|
2302
2640
|
|
|
2303
2641
|
\`\`\`json
|
|
@@ -2322,11 +2660,17 @@ ${taskPrompt}`;
|
|
|
2322
2660
|
console.log(`[${role}] Artifact written to ${artifactPath}`);
|
|
2323
2661
|
}
|
|
2324
2662
|
const structured = await extractStructuredData(role, markdown);
|
|
2663
|
+
if (structured) {
|
|
2664
|
+
const { valid, missing } = validateForRole(role, structured);
|
|
2665
|
+
if (!valid) {
|
|
2666
|
+
console.warn(`[${role}] Output missing expected fields: ${missing.join(", ")}`);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2325
2669
|
return { output: markdown, structured, truncated };
|
|
2326
2670
|
}
|
|
2327
2671
|
async function spawnSynthesiser(context, projectRoot) {
|
|
2328
2672
|
const root = projectRoot ?? findProjectRoot() ?? process.cwd();
|
|
2329
|
-
const contextJson = JSON.stringify(context
|
|
2673
|
+
const contextJson = JSON.stringify(context);
|
|
2330
2674
|
const taskPrompt = context.taskPrompt ?? "Synthesise the findings into actionable builder guidance.";
|
|
2331
2675
|
const prompt = `Here is your context:
|
|
2332
2676
|
|
|
@@ -2339,7 +2683,7 @@ ${taskPrompt}`;
|
|
|
2339
2683
|
console.log(`[synthesiser] Spawning (maxTurns: 5)...`);
|
|
2340
2684
|
const { text: markdown, costUsd, truncated } = await runQuery({
|
|
2341
2685
|
prompt,
|
|
2342
|
-
model: "
|
|
2686
|
+
model: "sonnet",
|
|
2343
2687
|
tools: ["Read", "Glob", "Grep"],
|
|
2344
2688
|
systemPrompt,
|
|
2345
2689
|
cwd: root,
|
|
@@ -2353,15 +2697,15 @@ async function spawnRecovery(role, partialOutput, context, projectRoot) {
|
|
|
2353
2697
|
const root = projectRoot ?? findProjectRoot() ?? process.cwd();
|
|
2354
2698
|
const expSlug = context.experiment?.slug ?? "unknown";
|
|
2355
2699
|
console.log(`[recovery] Cleaning up after truncated ${role} for ${expSlug}...`);
|
|
2356
|
-
const expDocPath =
|
|
2700
|
+
const expDocPath = path6.join(
|
|
2357
2701
|
root,
|
|
2358
2702
|
"docs",
|
|
2359
2703
|
"experiments",
|
|
2360
2704
|
`${String(context.experiment?.id ?? 0).padStart(3, "0")}-${expSlug}.md`
|
|
2361
2705
|
);
|
|
2362
|
-
const templatePath =
|
|
2363
|
-
const template =
|
|
2364
|
-
const currentDoc =
|
|
2706
|
+
const templatePath = path6.join(root, "docs", "experiments", "_TEMPLATE.md");
|
|
2707
|
+
const template = fs6.existsSync(templatePath) ? fs6.readFileSync(templatePath, "utf-8") : "";
|
|
2708
|
+
const currentDoc = fs6.existsSync(expDocPath) ? fs6.readFileSync(expDocPath, "utf-8") : "";
|
|
2365
2709
|
const prompt = `The ${role} agent was truncated (hit max turns) while working on experiment "${expSlug}".
|
|
2366
2710
|
|
|
2367
2711
|
Here is the partial agent output (reasoning + tool calls):
|
|
@@ -2498,23 +2842,23 @@ function writeArtifact(role, context, markdown, projectRoot) {
|
|
|
2498
2842
|
const dir = dirMap[role];
|
|
2499
2843
|
if (!dir) return null;
|
|
2500
2844
|
if (role === "builder" || role === "compressor") return null;
|
|
2501
|
-
const fullDir =
|
|
2502
|
-
if (!
|
|
2503
|
-
|
|
2845
|
+
const fullDir = path6.join(projectRoot, dir);
|
|
2846
|
+
if (!fs6.existsSync(fullDir)) {
|
|
2847
|
+
fs6.mkdirSync(fullDir, { recursive: true });
|
|
2504
2848
|
}
|
|
2505
2849
|
const expSlug = context.experiment?.slug ?? "general";
|
|
2506
2850
|
const nextNum = String(context.experiment?.id ?? 1).padStart(3, "0");
|
|
2507
2851
|
const filename = `${nextNum}-${role}-${expSlug}.md`;
|
|
2508
|
-
const target =
|
|
2509
|
-
|
|
2852
|
+
const target = path6.join(fullDir, filename);
|
|
2853
|
+
fs6.writeFileSync(target, markdown);
|
|
2510
2854
|
return target;
|
|
2511
2855
|
}
|
|
2512
|
-
var
|
|
2856
|
+
var fs6, path6, import_claude_agent_sdk2, ROLE_MAX_TURNS, DIM2, RESET2, CYAN2;
|
|
2513
2857
|
var init_spawn = __esm({
|
|
2514
2858
|
"src/agents/spawn.ts"() {
|
|
2515
2859
|
"use strict";
|
|
2516
|
-
|
|
2517
|
-
|
|
2860
|
+
fs6 = __toESM(require("fs"));
|
|
2861
|
+
path6 = __toESM(require("path"));
|
|
2518
2862
|
import_claude_agent_sdk2 = require("@anthropic-ai/claude-agent-sdk");
|
|
2519
2863
|
init_parse();
|
|
2520
2864
|
init_connection();
|
|
@@ -2525,7 +2869,8 @@ var init_spawn = __esm({
|
|
|
2525
2869
|
verifier: 50,
|
|
2526
2870
|
compressor: 30,
|
|
2527
2871
|
reframer: 20,
|
|
2528
|
-
scout: 20
|
|
2872
|
+
scout: 20,
|
|
2873
|
+
gatekeeper: 10
|
|
2529
2874
|
};
|
|
2530
2875
|
DIM2 = "\x1B[2m";
|
|
2531
2876
|
RESET2 = "\x1B[0m";
|
|
@@ -2587,11 +2932,13 @@ async function resolve(db, exp, projectRoot) {
|
|
|
2587
2932
|
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."
|
|
2588
2933
|
}, projectRoot);
|
|
2589
2934
|
const guidanceText = guidance.structured?.guidance ?? guidance.output;
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2935
|
+
db.transaction(() => {
|
|
2936
|
+
storeBuilderGuidance(db, exp.id, guidanceText);
|
|
2937
|
+
updateExperimentStatus(db, exp.id, "building");
|
|
2938
|
+
if (exp.sub_type) {
|
|
2939
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "weak");
|
|
2940
|
+
}
|
|
2941
|
+
})();
|
|
2595
2942
|
warn(`Experiment ${exp.slug} CYCLING BACK (weak). Guidance generated for builder.`);
|
|
2596
2943
|
break;
|
|
2597
2944
|
}
|
|
@@ -2599,18 +2946,21 @@ async function resolve(db, exp, projectRoot) {
|
|
|
2599
2946
|
gitRevert(exp.branch, projectRoot);
|
|
2600
2947
|
const rejectedComponents = grades.filter((g) => g.grade === "rejected");
|
|
2601
2948
|
const whyFailed = rejectedComponents.map((r) => r.notes ?? "rejected").join("; ");
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2949
|
+
db.transaction(() => {
|
|
2950
|
+
insertDeadEnd(
|
|
2951
|
+
db,
|
|
2952
|
+
exp.id,
|
|
2953
|
+
exp.hypothesis ?? exp.slug,
|
|
2954
|
+
whyFailed,
|
|
2955
|
+
`Approach rejected: ${whyFailed}`,
|
|
2956
|
+
exp.sub_type,
|
|
2957
|
+
"structural"
|
|
2958
|
+
);
|
|
2959
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
2960
|
+
if (exp.sub_type) {
|
|
2961
|
+
incrementSubTypeFailure(db, exp.sub_type, exp.id, "rejected");
|
|
2962
|
+
}
|
|
2963
|
+
})();
|
|
2614
2964
|
info(`Experiment ${exp.slug} DEAD-ENDED (rejected). Constraint recorded.`);
|
|
2615
2965
|
break;
|
|
2616
2966
|
}
|
|
@@ -2649,23 +2999,23 @@ function gitRevert(branch, cwd) {
|
|
|
2649
2999
|
}
|
|
2650
3000
|
}
|
|
2651
3001
|
function appendToFragilityMap(projectRoot, expSlug, gaps) {
|
|
2652
|
-
const fragPath =
|
|
3002
|
+
const fragPath = path7.join(projectRoot, "docs", "synthesis", "fragility.md");
|
|
2653
3003
|
let content = "";
|
|
2654
|
-
if (
|
|
2655
|
-
content =
|
|
3004
|
+
if (fs7.existsSync(fragPath)) {
|
|
3005
|
+
content = fs7.readFileSync(fragPath, "utf-8");
|
|
2656
3006
|
}
|
|
2657
3007
|
const entry = `
|
|
2658
3008
|
## From experiment: ${expSlug}
|
|
2659
3009
|
${gaps}
|
|
2660
3010
|
`;
|
|
2661
|
-
|
|
3011
|
+
fs7.writeFileSync(fragPath, content + entry);
|
|
2662
3012
|
}
|
|
2663
|
-
var
|
|
3013
|
+
var fs7, path7, import_node_child_process3;
|
|
2664
3014
|
var init_resolve = __esm({
|
|
2665
3015
|
"src/resolve.ts"() {
|
|
2666
3016
|
"use strict";
|
|
2667
|
-
|
|
2668
|
-
|
|
3017
|
+
fs7 = __toESM(require("fs"));
|
|
3018
|
+
path7 = __toESM(require("path"));
|
|
2669
3019
|
init_types();
|
|
2670
3020
|
init_queries();
|
|
2671
3021
|
init_spawn();
|
|
@@ -2696,6 +3046,8 @@ async function cycle(step, args) {
|
|
|
2696
3046
|
return doScout(db, exp, root);
|
|
2697
3047
|
case "verify":
|
|
2698
3048
|
return doVerify(db, exp, root);
|
|
3049
|
+
case "gate":
|
|
3050
|
+
return doGate(db, exp, root);
|
|
2699
3051
|
case "compress":
|
|
2700
3052
|
return doCompress(db, root);
|
|
2701
3053
|
}
|
|
@@ -2709,15 +3061,88 @@ async function resolveCmd(args) {
|
|
|
2709
3061
|
await resolve(db, exp, root);
|
|
2710
3062
|
updateExperimentStatus(db, exp.id, "resolved");
|
|
2711
3063
|
}
|
|
3064
|
+
async function doGate(db, exp, root) {
|
|
3065
|
+
transition(exp.status, "gated" /* GATED */);
|
|
3066
|
+
const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3067
|
+
const fragility = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
3068
|
+
const structuralDeadEnds = exp.sub_type ? listStructuralDeadEndsBySubType(db, exp.sub_type) : listStructuralDeadEnds(db);
|
|
3069
|
+
const result = await spawnAgent("gatekeeper", {
|
|
3070
|
+
experiment: {
|
|
3071
|
+
id: exp.id,
|
|
3072
|
+
slug: exp.slug,
|
|
3073
|
+
hypothesis: exp.hypothesis,
|
|
3074
|
+
status: exp.status,
|
|
3075
|
+
sub_type: exp.sub_type,
|
|
3076
|
+
builder_guidance: null
|
|
3077
|
+
},
|
|
3078
|
+
deadEnds: structuralDeadEnds.map((d) => ({
|
|
3079
|
+
approach: d.approach,
|
|
3080
|
+
why_failed: d.why_failed,
|
|
3081
|
+
structural_constraint: d.structural_constraint
|
|
3082
|
+
})),
|
|
3083
|
+
fragility,
|
|
3084
|
+
synthesis,
|
|
3085
|
+
taskPrompt: `Gate-check hypothesis for experiment ${exp.slug}:
|
|
3086
|
+
"${exp.hypothesis}"
|
|
3087
|
+
|
|
3088
|
+
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?
|
|
3089
|
+
|
|
3090
|
+
Output your gate_decision as "approve", "reject", or "flag" with reasoning.`
|
|
3091
|
+
}, root);
|
|
3092
|
+
ingestStructuredOutput(db, exp.id, result.structured);
|
|
3093
|
+
const decision = result.structured?.gate_decision ?? "approve";
|
|
3094
|
+
const reason = result.structured?.reason ?? "";
|
|
3095
|
+
if (decision === "reject") {
|
|
3096
|
+
updateExperimentStatus(db, exp.id, "gated");
|
|
3097
|
+
warn(`Gate REJECTED for ${exp.slug}: ${reason}`);
|
|
3098
|
+
warn(`Revise the hypothesis or run \`majlis revert\` to abandon.`);
|
|
3099
|
+
} else {
|
|
3100
|
+
if (decision === "flag") {
|
|
3101
|
+
warn(`Gate flagged concerns for ${exp.slug}: ${reason}`);
|
|
3102
|
+
}
|
|
3103
|
+
updateExperimentStatus(db, exp.id, "gated");
|
|
3104
|
+
success(`Gate passed for ${exp.slug}. Run \`majlis build\` next.`);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
2712
3107
|
async function doBuild(db, exp, root) {
|
|
2713
3108
|
transition(exp.status, "building" /* BUILDING */);
|
|
2714
3109
|
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
2715
3110
|
const builderGuidance = getBuilderGuidance(db, exp.id);
|
|
2716
|
-
const
|
|
2717
|
-
const
|
|
2718
|
-
const
|
|
2719
|
-
const
|
|
3111
|
+
const fragility = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
3112
|
+
const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3113
|
+
const confirmedDoubts = getConfirmedDoubts(db, exp.id);
|
|
3114
|
+
const config = loadConfig(root);
|
|
3115
|
+
const existingBaseline = getMetricsByExperimentAndPhase(db, exp.id, "before");
|
|
3116
|
+
if (config.metrics?.command && existingBaseline.length === 0) {
|
|
3117
|
+
try {
|
|
3118
|
+
const output = (0, import_node_child_process4.execSync)(config.metrics.command, {
|
|
3119
|
+
cwd: root,
|
|
3120
|
+
encoding: "utf-8",
|
|
3121
|
+
timeout: 6e4,
|
|
3122
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3123
|
+
}).trim();
|
|
3124
|
+
const parsed = parseMetricsOutput(output);
|
|
3125
|
+
for (const m of parsed) {
|
|
3126
|
+
insertMetric(db, exp.id, "before", m.fixture, m.metric_name, m.metric_value);
|
|
3127
|
+
}
|
|
3128
|
+
if (parsed.length > 0) info(`Captured ${parsed.length} baseline metric(s).`);
|
|
3129
|
+
} catch {
|
|
3130
|
+
warn("Could not capture baseline metrics.");
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
2720
3133
|
updateExperimentStatus(db, exp.id, "building");
|
|
3134
|
+
let taskPrompt = builderGuidance ? `Previous attempt was weak. Here is guidance for this attempt:
|
|
3135
|
+
${builderGuidance}
|
|
3136
|
+
|
|
3137
|
+
Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothesis}`;
|
|
3138
|
+
if (confirmedDoubts.length > 0) {
|
|
3139
|
+
taskPrompt += "\n\n## Confirmed Doubts (MUST address)\nThese weaknesses were confirmed by the verifier. Your build MUST address each one:\n";
|
|
3140
|
+
for (const d of confirmedDoubts) {
|
|
3141
|
+
taskPrompt += `- [${d.severity}] ${d.claim_doubted}: ${d.evidence_for_doubt}
|
|
3142
|
+
`;
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
taskPrompt += "\n\nNote: The framework captures metrics automatically. Do NOT claim specific numbers unless quoting framework output.";
|
|
2721
3146
|
const result = await spawnAgent("builder", {
|
|
2722
3147
|
experiment: {
|
|
2723
3148
|
id: exp.id,
|
|
@@ -2734,10 +3159,8 @@ async function doBuild(db, exp, root) {
|
|
|
2734
3159
|
})),
|
|
2735
3160
|
fragility,
|
|
2736
3161
|
synthesis,
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothesis}`
|
|
3162
|
+
confirmedDoubts,
|
|
3163
|
+
taskPrompt
|
|
2741
3164
|
}, root);
|
|
2742
3165
|
ingestStructuredOutput(db, exp.id, result.structured);
|
|
2743
3166
|
if (result.truncated && !result.structured) {
|
|
@@ -2747,6 +3170,23 @@ Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothes
|
|
|
2747
3170
|
}, root);
|
|
2748
3171
|
warn(`Experiment stays at 'building'. Run \`majlis build\` to retry or \`majlis revert\` to abandon.`);
|
|
2749
3172
|
} else {
|
|
3173
|
+
if (config.metrics?.command) {
|
|
3174
|
+
try {
|
|
3175
|
+
const output = (0, import_node_child_process4.execSync)(config.metrics.command, {
|
|
3176
|
+
cwd: root,
|
|
3177
|
+
encoding: "utf-8",
|
|
3178
|
+
timeout: 6e4,
|
|
3179
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3180
|
+
}).trim();
|
|
3181
|
+
const parsed = parseMetricsOutput(output);
|
|
3182
|
+
for (const m of parsed) {
|
|
3183
|
+
insertMetric(db, exp.id, "after", m.fixture, m.metric_name, m.metric_value);
|
|
3184
|
+
}
|
|
3185
|
+
if (parsed.length > 0) info(`Captured ${parsed.length} post-build metric(s).`);
|
|
3186
|
+
} catch {
|
|
3187
|
+
warn("Could not capture post-build metrics.");
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
2750
3190
|
gitCommitBuild(exp, root);
|
|
2751
3191
|
updateExperimentStatus(db, exp.id, "built");
|
|
2752
3192
|
success(`Build complete for ${exp.slug}. Run \`majlis doubt\` or \`majlis challenge\` next.`);
|
|
@@ -2754,6 +3194,26 @@ Build the experiment: ${exp.hypothesis}` : `Build the experiment: ${exp.hypothes
|
|
|
2754
3194
|
}
|
|
2755
3195
|
async function doChallenge(db, exp, root) {
|
|
2756
3196
|
transition(exp.status, "challenged" /* CHALLENGED */);
|
|
3197
|
+
let gitDiff = "";
|
|
3198
|
+
try {
|
|
3199
|
+
gitDiff = (0, import_node_child_process4.execSync)('git diff main -- . ":!.majlis/"', {
|
|
3200
|
+
cwd: root,
|
|
3201
|
+
encoding: "utf-8",
|
|
3202
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3203
|
+
}).trim();
|
|
3204
|
+
} catch {
|
|
3205
|
+
}
|
|
3206
|
+
if (gitDiff.length > 8e3) gitDiff = gitDiff.slice(0, 8e3) + "\n[DIFF TRUNCATED]";
|
|
3207
|
+
const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3208
|
+
let taskPrompt = `Construct adversarial test cases for experiment ${exp.slug}: ${exp.hypothesis}`;
|
|
3209
|
+
if (gitDiff) {
|
|
3210
|
+
taskPrompt += `
|
|
3211
|
+
|
|
3212
|
+
## Code Changes (git diff main)
|
|
3213
|
+
\`\`\`diff
|
|
3214
|
+
${gitDiff}
|
|
3215
|
+
\`\`\``;
|
|
3216
|
+
}
|
|
2757
3217
|
const result = await spawnAgent("adversary", {
|
|
2758
3218
|
experiment: {
|
|
2759
3219
|
id: exp.id,
|
|
@@ -2763,7 +3223,8 @@ async function doChallenge(db, exp, root) {
|
|
|
2763
3223
|
sub_type: exp.sub_type,
|
|
2764
3224
|
builder_guidance: null
|
|
2765
3225
|
},
|
|
2766
|
-
|
|
3226
|
+
synthesis,
|
|
3227
|
+
taskPrompt
|
|
2767
3228
|
}, root);
|
|
2768
3229
|
ingestStructuredOutput(db, exp.id, result.structured);
|
|
2769
3230
|
if (result.truncated && !result.structured) {
|
|
@@ -2775,6 +3236,20 @@ async function doChallenge(db, exp, root) {
|
|
|
2775
3236
|
}
|
|
2776
3237
|
async function doDoubt(db, exp, root) {
|
|
2777
3238
|
transition(exp.status, "doubted" /* DOUBTED */);
|
|
3239
|
+
const paddedNum = String(exp.id).padStart(3, "0");
|
|
3240
|
+
const expDocPath = path8.join(root, "docs", "experiments", `${paddedNum}-${exp.slug}.md`);
|
|
3241
|
+
const experimentDoc = truncateContext(readFileOrEmpty(expDocPath), CONTEXT_LIMITS.experimentDoc);
|
|
3242
|
+
const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3243
|
+
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
3244
|
+
let taskPrompt = `Doubt the work in experiment ${exp.slug}: ${exp.hypothesis}. Produce a doubt document with evidence for each doubt.`;
|
|
3245
|
+
if (experimentDoc) {
|
|
3246
|
+
taskPrompt += `
|
|
3247
|
+
|
|
3248
|
+
## Experiment Document (builder's artifact)
|
|
3249
|
+
<experiment_doc>
|
|
3250
|
+
${experimentDoc}
|
|
3251
|
+
</experiment_doc>`;
|
|
3252
|
+
}
|
|
2778
3253
|
const result = await spawnAgent("critic", {
|
|
2779
3254
|
experiment: {
|
|
2780
3255
|
id: exp.id,
|
|
@@ -2785,7 +3260,13 @@ async function doDoubt(db, exp, root) {
|
|
|
2785
3260
|
builder_guidance: null
|
|
2786
3261
|
// Critic does NOT see builder reasoning
|
|
2787
3262
|
},
|
|
2788
|
-
|
|
3263
|
+
synthesis,
|
|
3264
|
+
deadEnds: deadEnds.map((d) => ({
|
|
3265
|
+
approach: d.approach,
|
|
3266
|
+
why_failed: d.why_failed,
|
|
3267
|
+
structural_constraint: d.structural_constraint
|
|
3268
|
+
})),
|
|
3269
|
+
taskPrompt
|
|
2789
3270
|
}, root);
|
|
2790
3271
|
ingestStructuredOutput(db, exp.id, result.structured);
|
|
2791
3272
|
if (result.truncated && !result.structured) {
|
|
@@ -2797,35 +3278,91 @@ async function doDoubt(db, exp, root) {
|
|
|
2797
3278
|
}
|
|
2798
3279
|
async function doScout(db, exp, root) {
|
|
2799
3280
|
transition(exp.status, "scouted" /* SCOUTED */);
|
|
2800
|
-
const
|
|
2801
|
-
const
|
|
2802
|
-
|
|
3281
|
+
const synthesis = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3282
|
+
const fragility = truncateContext(readFileOrEmpty(path8.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
3283
|
+
const deadEnds = exp.sub_type ? listDeadEndsBySubType(db, exp.sub_type) : listAllDeadEnds(db);
|
|
3284
|
+
const deadEndsSummary = deadEnds.map(
|
|
3285
|
+
(d) => `- [${d.category ?? "structural"}] ${d.approach}: ${d.why_failed}`
|
|
3286
|
+
).join("\n");
|
|
3287
|
+
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.`;
|
|
3288
|
+
if (deadEndsSummary) {
|
|
3289
|
+
taskPrompt += `
|
|
3290
|
+
|
|
3291
|
+
## Known Dead Ends (avoid these approaches)
|
|
3292
|
+
${deadEndsSummary}`;
|
|
3293
|
+
}
|
|
3294
|
+
if (fragility) {
|
|
3295
|
+
taskPrompt += `
|
|
3296
|
+
|
|
3297
|
+
## Fragility Map (target these weak areas)
|
|
3298
|
+
${fragility}`;
|
|
3299
|
+
}
|
|
2803
3300
|
const result = await spawnAgent("scout", {
|
|
2804
3301
|
experiment: {
|
|
2805
3302
|
id: exp.id,
|
|
2806
3303
|
slug: exp.slug,
|
|
2807
3304
|
hypothesis: exp.hypothesis,
|
|
2808
|
-
status:
|
|
3305
|
+
status: exp.status,
|
|
2809
3306
|
sub_type: exp.sub_type,
|
|
2810
3307
|
builder_guidance: null
|
|
2811
3308
|
},
|
|
2812
3309
|
synthesis,
|
|
2813
|
-
|
|
3310
|
+
fragility,
|
|
3311
|
+
deadEnds: deadEnds.map((d) => ({
|
|
3312
|
+
approach: d.approach,
|
|
3313
|
+
why_failed: d.why_failed,
|
|
3314
|
+
structural_constraint: d.structural_constraint
|
|
3315
|
+
})),
|
|
3316
|
+
taskPrompt
|
|
2814
3317
|
}, root);
|
|
2815
3318
|
ingestStructuredOutput(db, exp.id, result.structured);
|
|
3319
|
+
if (result.truncated && !result.structured) {
|
|
3320
|
+
warn(`Scout was truncated without structured output. Experiment stays at current status.`);
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
updateExperimentStatus(db, exp.id, "scouted");
|
|
2816
3324
|
success(`Scout pass complete for ${exp.slug}. Run \`majlis verify\` next.`);
|
|
2817
3325
|
}
|
|
2818
3326
|
async function doVerify(db, exp, root) {
|
|
2819
3327
|
transition(exp.status, "verifying" /* VERIFYING */);
|
|
2820
3328
|
const doubts = getDoubtsByExperiment(db, exp.id);
|
|
2821
|
-
const challengeDir =
|
|
3329
|
+
const challengeDir = path8.join(root, "docs", "challenges");
|
|
2822
3330
|
let challenges = "";
|
|
2823
|
-
if (
|
|
2824
|
-
const files =
|
|
3331
|
+
if (fs8.existsSync(challengeDir)) {
|
|
3332
|
+
const files = fs8.readdirSync(challengeDir).filter((f) => f.includes(exp.slug) && f.endsWith(".md"));
|
|
2825
3333
|
for (const f of files) {
|
|
2826
|
-
challenges +=
|
|
3334
|
+
challenges += fs8.readFileSync(path8.join(challengeDir, f), "utf-8") + "\n\n";
|
|
2827
3335
|
}
|
|
2828
3336
|
}
|
|
3337
|
+
const beforeMetrics = getMetricsByExperimentAndPhase(db, exp.id, "before");
|
|
3338
|
+
const afterMetrics = getMetricsByExperimentAndPhase(db, exp.id, "after");
|
|
3339
|
+
let metricsSection = "";
|
|
3340
|
+
if (beforeMetrics.length > 0 || afterMetrics.length > 0) {
|
|
3341
|
+
metricsSection = "\n\n## Framework-Captured Metrics (GROUND TRUTH \u2014 not self-reported by builder)\n";
|
|
3342
|
+
if (beforeMetrics.length > 0) {
|
|
3343
|
+
metricsSection += "### Before Build\n";
|
|
3344
|
+
for (const m of beforeMetrics) {
|
|
3345
|
+
metricsSection += `- ${m.fixture} / ${m.metric_name}: ${m.metric_value}
|
|
3346
|
+
`;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
if (afterMetrics.length > 0) {
|
|
3350
|
+
metricsSection += "### After Build\n";
|
|
3351
|
+
for (const m of afterMetrics) {
|
|
3352
|
+
metricsSection += `- ${m.fixture} / ${m.metric_name}: ${m.metric_value}
|
|
3353
|
+
`;
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
let doubtReference = "";
|
|
3358
|
+
if (doubts.length > 0) {
|
|
3359
|
+
doubtReference = "\n\n## Doubt Reference (use these IDs in doubt_resolutions)\n";
|
|
3360
|
+
for (const d of doubts) {
|
|
3361
|
+
doubtReference += `- DOUBT-${d.id}: [${d.severity}] ${d.claim_doubted}
|
|
3362
|
+
`;
|
|
3363
|
+
}
|
|
3364
|
+
doubtReference += "\nWhen resolving doubts, use the DOUBT-{id} number as the doubt_id value in your doubt_resolutions output.";
|
|
3365
|
+
}
|
|
2829
3366
|
updateExperimentStatus(db, exp.id, "verifying");
|
|
2830
3367
|
const result = await spawnAgent("verifier", {
|
|
2831
3368
|
experiment: {
|
|
@@ -2838,7 +3375,7 @@ async function doVerify(db, exp, root) {
|
|
|
2838
3375
|
},
|
|
2839
3376
|
doubts,
|
|
2840
3377
|
challenges,
|
|
2841
|
-
taskPrompt: `Verify experiment ${exp.slug}: ${exp.hypothesis}. Check provenance and content. Test the ${doubts.length} doubt(s) and any adversarial challenges.`
|
|
3378
|
+
taskPrompt: `Verify experiment ${exp.slug}: ${exp.hypothesis}. Check provenance and content. Test the ${doubts.length} doubt(s) and any adversarial challenges.` + metricsSection + doubtReference
|
|
2842
3379
|
}, root);
|
|
2843
3380
|
ingestStructuredOutput(db, exp.id, result.structured);
|
|
2844
3381
|
if (result.truncated && !result.structured) {
|
|
@@ -2846,9 +3383,15 @@ async function doVerify(db, exp, root) {
|
|
|
2846
3383
|
return;
|
|
2847
3384
|
}
|
|
2848
3385
|
if (result.structured?.doubt_resolutions) {
|
|
2849
|
-
|
|
2850
|
-
|
|
3386
|
+
const knownDoubtIds = new Set(doubts.map((d) => d.id));
|
|
3387
|
+
for (let i = 0; i < result.structured.doubt_resolutions.length; i++) {
|
|
3388
|
+
const dr = result.structured.doubt_resolutions[i];
|
|
3389
|
+
if (!dr.resolution) continue;
|
|
3390
|
+
if (dr.doubt_id && knownDoubtIds.has(dr.doubt_id)) {
|
|
2851
3391
|
updateDoubtResolution(db, dr.doubt_id, dr.resolution);
|
|
3392
|
+
} else if (doubts[i]) {
|
|
3393
|
+
warn(`Doubt resolution ID ${dr.doubt_id} not found. Using ordinal fallback \u2192 DOUBT-${doubts[i].id}.`);
|
|
3394
|
+
updateDoubtResolution(db, doubts[i].id, dr.resolution);
|
|
2852
3395
|
}
|
|
2853
3396
|
}
|
|
2854
3397
|
}
|
|
@@ -2856,13 +3399,14 @@ async function doVerify(db, exp, root) {
|
|
|
2856
3399
|
success(`Verification complete for ${exp.slug}. Run \`majlis resolve\` next.`);
|
|
2857
3400
|
}
|
|
2858
3401
|
async function doCompress(db, root) {
|
|
2859
|
-
const synthesisPath =
|
|
2860
|
-
const sizeBefore =
|
|
3402
|
+
const synthesisPath = path8.join(root, "docs", "synthesis", "current.md");
|
|
3403
|
+
const sizeBefore = fs8.existsSync(synthesisPath) ? fs8.statSync(synthesisPath).size : 0;
|
|
2861
3404
|
const sessionCount = getSessionsSinceCompression(db);
|
|
3405
|
+
const dbExport = exportForCompressor(db);
|
|
2862
3406
|
const result = await spawnAgent("compressor", {
|
|
2863
|
-
taskPrompt: "
|
|
3407
|
+
taskPrompt: "## Structured Data (CANONICAL \u2014 from SQLite database)\nThe database export below is the source of truth. docs/ files are agent artifacts that may contain stale or incorrect information. Cross-reference everything against this data.\n\n" + dbExport + "\n\n## Your Task\nRead ALL experiments, decisions, doubts, challenges, verification reports, reframes, and recent diffs. Cross-reference for contradictions, redundancies, and patterns. REWRITE docs/synthesis/current.md \u2014 shorter and denser. Update docs/synthesis/fragility.md with current weak areas. Update docs/synthesis/dead-ends.md with structural constraints from rejected experiments."
|
|
2864
3408
|
}, root);
|
|
2865
|
-
const sizeAfter =
|
|
3409
|
+
const sizeAfter = fs8.existsSync(synthesisPath) ? fs8.statSync(synthesisPath).size : 0;
|
|
2866
3410
|
recordCompression(db, sessionCount, sizeBefore, sizeAfter);
|
|
2867
3411
|
success(`Compression complete. Synthesis: ${sizeBefore}B \u2192 ${sizeAfter}B`);
|
|
2868
3412
|
}
|
|
@@ -2936,13 +3480,29 @@ function ingestStructuredOutput(db, experimentId, structured) {
|
|
|
2936
3480
|
}
|
|
2937
3481
|
info(`Ingested ${structured.challenges.length} challenge(s)`);
|
|
2938
3482
|
}
|
|
3483
|
+
if (structured.reframe) {
|
|
3484
|
+
insertReframe(
|
|
3485
|
+
db,
|
|
3486
|
+
experimentId,
|
|
3487
|
+
structured.reframe.decomposition,
|
|
3488
|
+
JSON.stringify(structured.reframe.divergences),
|
|
3489
|
+
structured.reframe.recommendation
|
|
3490
|
+
);
|
|
3491
|
+
info(`Ingested reframe`);
|
|
3492
|
+
}
|
|
3493
|
+
if (structured.findings) {
|
|
3494
|
+
for (const f of structured.findings) {
|
|
3495
|
+
insertFinding(db, experimentId, f.approach, f.source, f.relevance, f.contradicts_current);
|
|
3496
|
+
}
|
|
3497
|
+
info(`Ingested ${structured.findings.length} finding(s)`);
|
|
3498
|
+
}
|
|
2939
3499
|
}
|
|
2940
|
-
var
|
|
3500
|
+
var fs8, path8, import_node_child_process4;
|
|
2941
3501
|
var init_cycle = __esm({
|
|
2942
3502
|
"src/commands/cycle.ts"() {
|
|
2943
3503
|
"use strict";
|
|
2944
|
-
|
|
2945
|
-
|
|
3504
|
+
fs8 = __toESM(require("fs"));
|
|
3505
|
+
path8 = __toESM(require("path"));
|
|
2946
3506
|
import_node_child_process4 = require("child_process");
|
|
2947
3507
|
init_connection();
|
|
2948
3508
|
init_queries();
|
|
@@ -2950,7 +3510,8 @@ var init_cycle = __esm({
|
|
|
2950
3510
|
init_types();
|
|
2951
3511
|
init_spawn();
|
|
2952
3512
|
init_resolve();
|
|
2953
|
-
|
|
3513
|
+
init_config();
|
|
3514
|
+
init_metrics();
|
|
2954
3515
|
init_format();
|
|
2955
3516
|
}
|
|
2956
3517
|
});
|
|
@@ -2968,10 +3529,10 @@ async function classify(args) {
|
|
|
2968
3529
|
if (!domain) {
|
|
2969
3530
|
throw new Error('Usage: majlis classify "domain description"');
|
|
2970
3531
|
}
|
|
2971
|
-
const synthesisPath =
|
|
2972
|
-
const synthesis =
|
|
2973
|
-
const deadEndsPath =
|
|
2974
|
-
const deadEnds =
|
|
3532
|
+
const synthesisPath = path9.join(root, "docs", "synthesis", "current.md");
|
|
3533
|
+
const synthesis = fs9.existsSync(synthesisPath) ? fs9.readFileSync(synthesisPath, "utf-8") : "";
|
|
3534
|
+
const deadEndsPath = path9.join(root, "docs", "synthesis", "dead-ends.md");
|
|
3535
|
+
const deadEnds = fs9.existsSync(deadEndsPath) ? fs9.readFileSync(deadEndsPath, "utf-8") : "";
|
|
2975
3536
|
info(`Classifying problem domain: ${domain}`);
|
|
2976
3537
|
const result = await spawnAgent("builder", {
|
|
2977
3538
|
synthesis,
|
|
@@ -2989,22 +3550,22 @@ Write the classification to docs/classification/ following the template.`
|
|
|
2989
3550
|
async function reframe(args) {
|
|
2990
3551
|
const root = findProjectRoot();
|
|
2991
3552
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
2992
|
-
const classificationDir =
|
|
3553
|
+
const classificationDir = path9.join(root, "docs", "classification");
|
|
2993
3554
|
let classificationContent = "";
|
|
2994
|
-
if (
|
|
2995
|
-
const files =
|
|
3555
|
+
if (fs9.existsSync(classificationDir)) {
|
|
3556
|
+
const files = fs9.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
|
|
2996
3557
|
for (const f of files) {
|
|
2997
|
-
classificationContent +=
|
|
3558
|
+
classificationContent += fs9.readFileSync(path9.join(classificationDir, f), "utf-8") + "\n\n";
|
|
2998
3559
|
}
|
|
2999
3560
|
}
|
|
3000
|
-
const synthesisPath =
|
|
3001
|
-
const synthesis =
|
|
3002
|
-
const deadEndsPath =
|
|
3003
|
-
const deadEnds =
|
|
3004
|
-
const configPath =
|
|
3561
|
+
const synthesisPath = path9.join(root, "docs", "synthesis", "current.md");
|
|
3562
|
+
const synthesis = fs9.existsSync(synthesisPath) ? fs9.readFileSync(synthesisPath, "utf-8") : "";
|
|
3563
|
+
const deadEndsPath = path9.join(root, "docs", "synthesis", "dead-ends.md");
|
|
3564
|
+
const deadEnds = fs9.existsSync(deadEndsPath) ? fs9.readFileSync(deadEndsPath, "utf-8") : "";
|
|
3565
|
+
const configPath = path9.join(root, ".majlis", "config.json");
|
|
3005
3566
|
let problemStatement = "";
|
|
3006
|
-
if (
|
|
3007
|
-
const config = JSON.parse(
|
|
3567
|
+
if (fs9.existsSync(configPath)) {
|
|
3568
|
+
const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
3008
3569
|
problemStatement = `${config.project?.description ?? ""}
|
|
3009
3570
|
Objective: ${config.project?.objective ?? ""}`;
|
|
3010
3571
|
}
|
|
@@ -3028,12 +3589,12 @@ Write to docs/reframes/.`
|
|
|
3028
3589
|
}, root);
|
|
3029
3590
|
success("Reframe complete. Check docs/reframes/ for the output.");
|
|
3030
3591
|
}
|
|
3031
|
-
var
|
|
3592
|
+
var fs9, path9;
|
|
3032
3593
|
var init_classify = __esm({
|
|
3033
3594
|
"src/commands/classify.ts"() {
|
|
3034
3595
|
"use strict";
|
|
3035
|
-
|
|
3036
|
-
|
|
3596
|
+
fs9 = __toESM(require("fs"));
|
|
3597
|
+
path9 = __toESM(require("path"));
|
|
3037
3598
|
init_connection();
|
|
3038
3599
|
init_spawn();
|
|
3039
3600
|
init_format();
|
|
@@ -3050,20 +3611,19 @@ async function audit(args) {
|
|
|
3050
3611
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
3051
3612
|
const db = getDb(root);
|
|
3052
3613
|
const objective = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
3053
|
-
const config =
|
|
3614
|
+
const config = loadConfig(root);
|
|
3054
3615
|
const experiments = listAllExperiments(db);
|
|
3055
3616
|
const deadEnds = listAllDeadEnds(db);
|
|
3056
3617
|
const circuitBreakers = getAllCircuitBreakerStates(db, config.cycle.circuit_breaker_threshold);
|
|
3057
|
-
const classificationDir =
|
|
3618
|
+
const classificationDir = path10.join(root, "docs", "classification");
|
|
3058
3619
|
let classification = "";
|
|
3059
|
-
if (
|
|
3060
|
-
const files =
|
|
3620
|
+
if (fs10.existsSync(classificationDir)) {
|
|
3621
|
+
const files = fs10.readdirSync(classificationDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
|
|
3061
3622
|
for (const f of files) {
|
|
3062
|
-
classification +=
|
|
3623
|
+
classification += fs10.readFileSync(path10.join(classificationDir, f), "utf-8") + "\n\n";
|
|
3063
3624
|
}
|
|
3064
3625
|
}
|
|
3065
|
-
const
|
|
3066
|
-
const synthesis = fs11.existsSync(synthesisPath) ? fs11.readFileSync(synthesisPath, "utf-8") : "";
|
|
3626
|
+
const synthesis = readFileOrEmpty(path10.join(root, "docs", "synthesis", "current.md"));
|
|
3067
3627
|
header("Maqasid Check \u2014 Purpose Audit");
|
|
3068
3628
|
const trippedBreakers = circuitBreakers.filter((cb) => cb.tripped);
|
|
3069
3629
|
if (trippedBreakers.length > 0) {
|
|
@@ -3107,22 +3667,16 @@ Output: either "classification confirmed \u2014 continue" or "re-classify from X
|
|
|
3107
3667
|
}, root);
|
|
3108
3668
|
success("Purpose audit complete. Review the output above.");
|
|
3109
3669
|
}
|
|
3110
|
-
|
|
3111
|
-
const configPath = path11.join(projectRoot, ".majlis", "config.json");
|
|
3112
|
-
if (!fs11.existsSync(configPath)) {
|
|
3113
|
-
return { project: { name: "", description: "", objective: "" }, cycle: { circuit_breaker_threshold: 3 } };
|
|
3114
|
-
}
|
|
3115
|
-
return JSON.parse(fs11.readFileSync(configPath, "utf-8"));
|
|
3116
|
-
}
|
|
3117
|
-
var fs11, path11;
|
|
3670
|
+
var fs10, path10;
|
|
3118
3671
|
var init_audit = __esm({
|
|
3119
3672
|
"src/commands/audit.ts"() {
|
|
3120
3673
|
"use strict";
|
|
3121
|
-
|
|
3122
|
-
|
|
3674
|
+
fs10 = __toESM(require("fs"));
|
|
3675
|
+
path10 = __toESM(require("path"));
|
|
3123
3676
|
init_connection();
|
|
3124
3677
|
init_queries();
|
|
3125
3678
|
init_spawn();
|
|
3679
|
+
init_config();
|
|
3126
3680
|
init_format();
|
|
3127
3681
|
}
|
|
3128
3682
|
});
|
|
@@ -3136,7 +3690,7 @@ async function next(args, isJson) {
|
|
|
3136
3690
|
const root = findProjectRoot();
|
|
3137
3691
|
if (!root) throw new Error("Not in a Majlis project. Run `majlis init` first.");
|
|
3138
3692
|
const db = getDb(root);
|
|
3139
|
-
const config =
|
|
3693
|
+
const config = loadConfig(root);
|
|
3140
3694
|
const slugArg = args.filter((a) => !a.startsWith("--"))[0];
|
|
3141
3695
|
let exp;
|
|
3142
3696
|
if (slugArg) {
|
|
@@ -3168,7 +3722,17 @@ async function runNextStep(db, exp, config, root, isJson) {
|
|
|
3168
3722
|
}
|
|
3169
3723
|
if (exp.sub_type && checkCircuitBreaker(db, exp.sub_type, config.cycle.circuit_breaker_threshold)) {
|
|
3170
3724
|
warn(`Circuit breaker: ${exp.sub_type} has ${config.cycle.circuit_breaker_threshold}+ failures.`);
|
|
3171
|
-
|
|
3725
|
+
insertDeadEnd(
|
|
3726
|
+
db,
|
|
3727
|
+
exp.id,
|
|
3728
|
+
exp.hypothesis ?? exp.slug,
|
|
3729
|
+
`Circuit breaker tripped for ${exp.sub_type}`,
|
|
3730
|
+
`Sub-type ${exp.sub_type} exceeded ${config.cycle.circuit_breaker_threshold} failures`,
|
|
3731
|
+
exp.sub_type,
|
|
3732
|
+
"procedural"
|
|
3733
|
+
);
|
|
3734
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
3735
|
+
warn("Experiment dead-ended. Triggering Maqasid Check (purpose audit).");
|
|
3172
3736
|
await audit([config.project?.objective ?? ""]);
|
|
3173
3737
|
return;
|
|
3174
3738
|
}
|
|
@@ -3208,6 +3772,16 @@ async function runAutoLoop(db, exp, config, root, isJson) {
|
|
|
3208
3772
|
}
|
|
3209
3773
|
if (exp.sub_type && checkCircuitBreaker(db, exp.sub_type, config.cycle.circuit_breaker_threshold)) {
|
|
3210
3774
|
warn(`Circuit breaker tripped for ${exp.sub_type}. Stopping auto mode.`);
|
|
3775
|
+
insertDeadEnd(
|
|
3776
|
+
db,
|
|
3777
|
+
exp.id,
|
|
3778
|
+
exp.hypothesis ?? exp.slug,
|
|
3779
|
+
`Circuit breaker tripped for ${exp.sub_type}`,
|
|
3780
|
+
`Sub-type ${exp.sub_type} exceeded ${config.cycle.circuit_breaker_threshold} failures`,
|
|
3781
|
+
exp.sub_type,
|
|
3782
|
+
"procedural"
|
|
3783
|
+
);
|
|
3784
|
+
updateExperimentStatus(db, exp.id, "dead_end");
|
|
3211
3785
|
await audit([config.project?.objective ?? ""]);
|
|
3212
3786
|
break;
|
|
3213
3787
|
}
|
|
@@ -3249,41 +3823,26 @@ async function executeStep(step, exp, root) {
|
|
|
3249
3823
|
updateExperimentStatus(getDb(root), exp.id, "compressed");
|
|
3250
3824
|
info(`Experiment ${exp.slug} compressed.`);
|
|
3251
3825
|
break;
|
|
3826
|
+
case "gated" /* GATED */:
|
|
3827
|
+
await cycle("gate", expArgs);
|
|
3828
|
+
break;
|
|
3252
3829
|
case "reframed" /* REFRAMED */:
|
|
3253
3830
|
updateExperimentStatus(getDb(root), exp.id, "reframed");
|
|
3254
|
-
info(`Reframe acknowledged for ${exp.slug}. Proceeding to
|
|
3831
|
+
info(`Reframe acknowledged for ${exp.slug}. Proceeding to gate.`);
|
|
3255
3832
|
break;
|
|
3256
3833
|
default:
|
|
3257
3834
|
warn(`Don't know how to execute step: ${step}`);
|
|
3258
3835
|
}
|
|
3259
3836
|
}
|
|
3260
|
-
function loadConfig6(projectRoot) {
|
|
3261
|
-
const configPath = path12.join(projectRoot, ".majlis", "config.json");
|
|
3262
|
-
if (!fs12.existsSync(configPath)) {
|
|
3263
|
-
return {
|
|
3264
|
-
project: { name: "", description: "", objective: "" },
|
|
3265
|
-
cycle: {
|
|
3266
|
-
compression_interval: 5,
|
|
3267
|
-
circuit_breaker_threshold: 3,
|
|
3268
|
-
require_doubt_before_verify: true,
|
|
3269
|
-
require_challenge_before_verify: false,
|
|
3270
|
-
auto_baseline_on_new_experiment: true
|
|
3271
|
-
}
|
|
3272
|
-
};
|
|
3273
|
-
}
|
|
3274
|
-
return JSON.parse(fs12.readFileSync(configPath, "utf-8"));
|
|
3275
|
-
}
|
|
3276
|
-
var fs12, path12;
|
|
3277
3837
|
var init_next = __esm({
|
|
3278
3838
|
"src/commands/next.ts"() {
|
|
3279
3839
|
"use strict";
|
|
3280
|
-
fs12 = __toESM(require("fs"));
|
|
3281
|
-
path12 = __toESM(require("path"));
|
|
3282
3840
|
init_connection();
|
|
3283
3841
|
init_queries();
|
|
3284
3842
|
init_machine();
|
|
3285
3843
|
init_types();
|
|
3286
3844
|
init_queries();
|
|
3845
|
+
init_config();
|
|
3287
3846
|
init_cycle();
|
|
3288
3847
|
init_audit();
|
|
3289
3848
|
init_format();
|
|
@@ -3303,13 +3862,19 @@ async function run(args) {
|
|
|
3303
3862
|
throw new Error('Usage: majlis run "goal description"');
|
|
3304
3863
|
}
|
|
3305
3864
|
const db = getDb(root);
|
|
3306
|
-
const config =
|
|
3865
|
+
const config = loadConfig(root);
|
|
3307
3866
|
const MAX_EXPERIMENTS = 10;
|
|
3308
3867
|
const MAX_STEPS = 200;
|
|
3309
3868
|
let experimentCount = 0;
|
|
3310
3869
|
let stepCount = 0;
|
|
3870
|
+
let consecutiveFailures = 0;
|
|
3871
|
+
const usedHypotheses = /* @__PURE__ */ new Set();
|
|
3311
3872
|
header(`Autonomous Mode \u2014 ${goal}`);
|
|
3312
3873
|
while (stepCount < MAX_STEPS && experimentCount < MAX_EXPERIMENTS) {
|
|
3874
|
+
if (isShutdownRequested()) {
|
|
3875
|
+
warn("Shutdown requested. Stopping autonomous mode.");
|
|
3876
|
+
break;
|
|
3877
|
+
}
|
|
3313
3878
|
stepCount++;
|
|
3314
3879
|
let exp = getLatestExperiment(db);
|
|
3315
3880
|
if (!exp) {
|
|
@@ -3329,6 +3894,11 @@ async function run(args) {
|
|
|
3329
3894
|
success("Planner says the goal has been met. Stopping.");
|
|
3330
3895
|
break;
|
|
3331
3896
|
}
|
|
3897
|
+
if (usedHypotheses.has(hypothesis)) {
|
|
3898
|
+
warn(`Planner returned duplicate hypothesis: "${hypothesis.slice(0, 80)}". Stopping.`);
|
|
3899
|
+
break;
|
|
3900
|
+
}
|
|
3901
|
+
usedHypotheses.add(hypothesis);
|
|
3332
3902
|
info(`Next hypothesis: ${hypothesis}`);
|
|
3333
3903
|
exp = createNewExperiment(db, root, hypothesis);
|
|
3334
3904
|
success(`Created experiment #${exp.id}: ${exp.slug}`);
|
|
@@ -3344,12 +3914,29 @@ async function run(args) {
|
|
|
3344
3914
|
info(`[Step ${stepCount}] ${exp.slug}: ${exp.status}`);
|
|
3345
3915
|
try {
|
|
3346
3916
|
await next([exp.slug], false);
|
|
3917
|
+
consecutiveFailures = 0;
|
|
3347
3918
|
} catch (err) {
|
|
3919
|
+
consecutiveFailures++;
|
|
3348
3920
|
const message = err instanceof Error ? err.message : String(err);
|
|
3349
3921
|
warn(`Step failed for ${exp.slug}: ${message}`);
|
|
3350
3922
|
try {
|
|
3923
|
+
insertDeadEnd(
|
|
3924
|
+
db,
|
|
3925
|
+
exp.id,
|
|
3926
|
+
exp.hypothesis ?? exp.slug,
|
|
3927
|
+
message,
|
|
3928
|
+
`Process failure: ${message}`,
|
|
3929
|
+
exp.sub_type,
|
|
3930
|
+
"procedural"
|
|
3931
|
+
);
|
|
3351
3932
|
updateExperimentStatus(db, exp.id, "dead_end");
|
|
3352
|
-
} catch {
|
|
3933
|
+
} catch (innerErr) {
|
|
3934
|
+
const innerMsg = innerErr instanceof Error ? innerErr.message : String(innerErr);
|
|
3935
|
+
warn(`Could not record dead-end: ${innerMsg}`);
|
|
3936
|
+
}
|
|
3937
|
+
if (consecutiveFailures >= 3) {
|
|
3938
|
+
warn(`${consecutiveFailures} consecutive failures. Stopping autonomous mode.`);
|
|
3939
|
+
break;
|
|
3353
3940
|
}
|
|
3354
3941
|
}
|
|
3355
3942
|
}
|
|
@@ -3362,11 +3949,11 @@ async function run(args) {
|
|
|
3362
3949
|
info("Run `majlis status` to see final state.");
|
|
3363
3950
|
}
|
|
3364
3951
|
async function deriveNextHypothesis(goal, root, db) {
|
|
3365
|
-
const synthesis = readFileOrEmpty(
|
|
3366
|
-
const fragility = readFileOrEmpty(
|
|
3367
|
-
const deadEndsDoc = readFileOrEmpty(
|
|
3952
|
+
const synthesis = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "current.md")), CONTEXT_LIMITS.synthesis);
|
|
3953
|
+
const fragility = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "fragility.md")), CONTEXT_LIMITS.fragility);
|
|
3954
|
+
const deadEndsDoc = truncateContext(readFileOrEmpty(path11.join(root, "docs", "synthesis", "dead-ends.md")), CONTEXT_LIMITS.deadEnds);
|
|
3368
3955
|
const deadEnds = listAllDeadEnds(db);
|
|
3369
|
-
const config =
|
|
3956
|
+
const config = loadConfig(root);
|
|
3370
3957
|
let metricsOutput = "";
|
|
3371
3958
|
if (config.metrics?.command) {
|
|
3372
3959
|
try {
|
|
@@ -3399,7 +3986,10 @@ ${fragility || "(none)"}
|
|
|
3399
3986
|
${deadEndsDoc || "(none)"}
|
|
3400
3987
|
|
|
3401
3988
|
## Dead Ends (from DB \u2014 ${deadEnds.length} total)
|
|
3402
|
-
${deadEnds.map((d) => `- ${d.approach}: ${d.why_failed} [constraint: ${d.structural_constraint}]`).join("\n") || "(none)"}
|
|
3989
|
+
${deadEnds.map((d) => `- [${d.category ?? "structural"}] ${d.approach}: ${d.why_failed} [constraint: ${d.structural_constraint}]`).join("\n") || "(none)"}
|
|
3990
|
+
|
|
3991
|
+
Note: [structural] dead ends are HARD CONSTRAINTS \u2014 your hypothesis MUST NOT repeat these approaches.
|
|
3992
|
+
[procedural] dead ends are process failures \u2014 the approach may still be valid if executed differently.
|
|
3403
3993
|
|
|
3404
3994
|
## Your Task
|
|
3405
3995
|
1. Assess: based on the metrics and synthesis, has the goal been met? Be specific.
|
|
@@ -3473,49 +4063,26 @@ function createNewExperiment(db, root, hypothesis) {
|
|
|
3473
4063
|
const exp = createExperiment(db, finalSlug, branch, hypothesis, null, null);
|
|
3474
4064
|
updateExperimentStatus(db, exp.id, "reframed");
|
|
3475
4065
|
exp.status = "reframed";
|
|
3476
|
-
const docsDir =
|
|
3477
|
-
const templatePath =
|
|
3478
|
-
if (
|
|
3479
|
-
const template =
|
|
4066
|
+
const docsDir = path11.join(root, "docs", "experiments");
|
|
4067
|
+
const templatePath = path11.join(docsDir, "_TEMPLATE.md");
|
|
4068
|
+
if (fs11.existsSync(templatePath)) {
|
|
4069
|
+
const template = fs11.readFileSync(templatePath, "utf-8");
|
|
3480
4070
|
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]);
|
|
3481
|
-
const logPath =
|
|
3482
|
-
|
|
4071
|
+
const logPath = path11.join(docsDir, `${paddedNum}-${finalSlug}.md`);
|
|
4072
|
+
fs11.writeFileSync(logPath, logContent);
|
|
3483
4073
|
info(`Created experiment log: docs/experiments/${paddedNum}-${finalSlug}.md`);
|
|
3484
4074
|
}
|
|
3485
4075
|
return exp;
|
|
3486
4076
|
}
|
|
3487
|
-
function readFileOrEmpty(filePath) {
|
|
3488
|
-
try {
|
|
3489
|
-
return fs13.readFileSync(filePath, "utf-8");
|
|
3490
|
-
} catch {
|
|
3491
|
-
return "";
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
4077
|
function slugify2(text) {
|
|
3495
4078
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
3496
4079
|
}
|
|
3497
|
-
|
|
3498
|
-
const configPath = path13.join(projectRoot, ".majlis", "config.json");
|
|
3499
|
-
if (!fs13.existsSync(configPath)) {
|
|
3500
|
-
return {
|
|
3501
|
-
project: { name: "", description: "", objective: "" },
|
|
3502
|
-
cycle: {
|
|
3503
|
-
compression_interval: 5,
|
|
3504
|
-
circuit_breaker_threshold: 3,
|
|
3505
|
-
require_doubt_before_verify: true,
|
|
3506
|
-
require_challenge_before_verify: false,
|
|
3507
|
-
auto_baseline_on_new_experiment: true
|
|
3508
|
-
}
|
|
3509
|
-
};
|
|
3510
|
-
}
|
|
3511
|
-
return JSON.parse(fs13.readFileSync(configPath, "utf-8"));
|
|
3512
|
-
}
|
|
3513
|
-
var fs13, path13, import_node_child_process5;
|
|
4080
|
+
var fs11, path11, import_node_child_process5;
|
|
3514
4081
|
var init_run = __esm({
|
|
3515
4082
|
"src/commands/run.ts"() {
|
|
3516
4083
|
"use strict";
|
|
3517
|
-
|
|
3518
|
-
|
|
4084
|
+
fs11 = __toESM(require("fs"));
|
|
4085
|
+
path11 = __toESM(require("path"));
|
|
3519
4086
|
import_node_child_process5 = require("child_process");
|
|
3520
4087
|
init_connection();
|
|
3521
4088
|
init_queries();
|
|
@@ -3523,17 +4090,27 @@ var init_run = __esm({
|
|
|
3523
4090
|
init_next();
|
|
3524
4091
|
init_cycle();
|
|
3525
4092
|
init_spawn();
|
|
4093
|
+
init_config();
|
|
4094
|
+
init_shutdown();
|
|
3526
4095
|
init_format();
|
|
3527
4096
|
}
|
|
3528
4097
|
});
|
|
3529
4098
|
|
|
3530
4099
|
// src/cli.ts
|
|
3531
|
-
var
|
|
3532
|
-
var
|
|
4100
|
+
var fs12 = __toESM(require("fs"));
|
|
4101
|
+
var path12 = __toESM(require("path"));
|
|
3533
4102
|
var VERSION = JSON.parse(
|
|
3534
|
-
|
|
4103
|
+
fs12.readFileSync(path12.join(__dirname, "..", "package.json"), "utf-8")
|
|
3535
4104
|
).version;
|
|
3536
4105
|
async function main() {
|
|
4106
|
+
let sigintCount = 0;
|
|
4107
|
+
process.on("SIGINT", () => {
|
|
4108
|
+
sigintCount++;
|
|
4109
|
+
if (sigintCount >= 2) process.exit(130);
|
|
4110
|
+
const { requestShutdown: requestShutdown2 } = (init_shutdown(), __toCommonJS(shutdown_exports));
|
|
4111
|
+
requestShutdown2();
|
|
4112
|
+
console.error("\n\x1B[33m[majlis] Interrupt received. Finishing current step...\x1B[0m");
|
|
4113
|
+
});
|
|
3537
4114
|
const args = process.argv.slice(2);
|
|
3538
4115
|
if (args.includes("--version") || args.includes("-v")) {
|
|
3539
4116
|
console.log(VERSION);
|