majlis 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +80 -29
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -522,24 +522,35 @@ Before building:
522
522
  3. Check docs/classification/ for problem taxonomy
523
523
  4. Check docs/experiments/ for prior work
524
524
 
525
- ## Scope Constraint (CRITICAL)
525
+ Read as much code as you need to understand the problem. Reading is free \u2014 spend
526
+ as many turns as necessary on Read, Grep, and Glob to build full context before
527
+ you touch anything.
528
+
529
+ ## The Rule: ONE Change, Then Document
530
+
531
+ You make ONE code change per cycle. Not two, not "one more quick fix." ONE.
532
+
533
+ The sequence:
534
+ 1. **Read and understand** \u2014 read synthesis, dead-ends, source code. Take your time.
535
+ 2. **Write the experiment doc FIRST** \u2014 before coding, fill in the Approach section
536
+ with what you plan to do and why. This ensures there is always a record.
537
+ 3. **Implement ONE focused change** \u2014 a single coherent edit to the codebase.
538
+ 4. **Run the benchmark ONCE** \u2014 observe the result.
539
+ 5. **Update the experiment doc** \u2014 fill in Results and Metrics with what happened.
540
+ 6. **Output the majlis-json block** \u2014 your structured decisions.
541
+ 7. **STOP.**
526
542
 
527
- You get ONE attempt per cycle. Your job is:
528
- 1. Read and diagnose \u2014 understand the problem thoroughly
529
- 2. Form ONE hypothesis about what to fix
530
- 3. Implement ONE focused change (not a multi-step debug session)
531
- 4. Run the benchmark ONCE to see the result
532
- 5. Update the experiment doc in docs/experiments/ \u2014 fill in Approach, Results, and Metrics sections. This is NOT optional.
533
- 6. Output the structured majlis-json block with your decisions
534
- 7. STOP
543
+ If your change doesn't work, document what happened and STOP. Do NOT try to fix it.
544
+ Do NOT iterate. Do NOT "try one more thing." The adversary, critic, and verifier
545
+ exist to diagnose what went wrong. The cycle comes back to you with their insights.
535
546
 
536
- Do NOT iterate. Do NOT try multiple approaches. Do NOT debug your own fix.
537
- If your change doesn't work, document why and let the cycle continue \u2014
538
- the adversary, critic, and verifier will help diagnose what went wrong.
539
- The cycle will come back to you with their insights.
547
+ If you find yourself wanting to debug your own fix, that's the signal to stop
548
+ and write up what you learned.
540
549
 
541
- If you find yourself wanting to "try one more thing," that's the signal to stop
542
- and write up what you learned. The other agents exist precisely for this reason.
550
+ ## Off-limits (DO NOT modify)
551
+ - \`fixtures/\` \u2014 test data, ground truth, STL files. Read-only.
552
+ - \`scripts/benchmark.py\` \u2014 the measurement tool. Never change how you're measured.
553
+ - \`.majlis/\` \u2014 framework config. Not your concern.
543
554
 
544
555
  ## During building:
545
556
  - Tag EVERY decision: proof / test / strong-consensus / consensus / analogy / judgment
@@ -2293,13 +2304,15 @@ ${contextJson}
2293
2304
  \`\`\`
2294
2305
 
2295
2306
  ${taskPrompt}`;
2296
- console.log(`[majlis] Spawning ${role} agent (model: ${agentDef.model})...`);
2307
+ const turns = ROLE_MAX_TURNS[role] ?? 15;
2308
+ console.log(`[majlis] Spawning ${role} agent (model: ${agentDef.model}, maxTurns: ${turns})...`);
2297
2309
  const { text: markdown, costUsd } = await runQuery({
2298
2310
  prompt,
2299
2311
  model: agentDef.model,
2300
2312
  tools: agentDef.tools,
2301
2313
  systemPrompt: agentDef.systemPrompt,
2302
- cwd: root
2314
+ cwd: root,
2315
+ maxTurns: turns
2303
2316
  });
2304
2317
  console.log(`[majlis] ${role} agent complete (cost: $${costUsd.toFixed(4)})`);
2305
2318
  const artifactPath = writeArtifact(role, context, markdown, root);
@@ -2320,14 +2333,15 @@ ${contextJson}
2320
2333
  \`\`\`
2321
2334
 
2322
2335
  ${taskPrompt}`;
2323
- const systemPrompt = "You are a Synthesis Agent. Your job is to take a verification report, confirmed doubts, and adversarial test results, and compress them into specific, actionable guidance for the builder's next attempt. Be concrete: which decisions failed, which assumptions broke, what constraints must the next approach satisfy. Output a 'guidance' field in JSON wrapped in a <!-- majlis-json --> block.";
2336
+ const systemPrompt = 'You are a Synthesis Agent. Be concrete: which decisions failed, which assumptions broke, what constraints must the next approach satisfy. CRITICAL: Your LAST line of output MUST be a <!-- majlis-json --> block. The framework parses this programmatically \u2014 if you omit it, the pipeline breaks. Format: <!-- majlis-json {"guidance": "your guidance here"} -->';
2324
2337
  console.log(`[majlis] Spawning synthesiser micro-agent...`);
2325
2338
  const { text: markdown, costUsd } = await runQuery({
2326
2339
  prompt,
2327
2340
  model: "opus",
2328
2341
  tools: ["Read", "Glob", "Grep"],
2329
2342
  systemPrompt,
2330
- cwd: root
2343
+ cwd: root,
2344
+ maxTurns: 5
2331
2345
  });
2332
2346
  console.log(`[majlis] Synthesiser complete (cost: $${costUsd.toFixed(4)})`);
2333
2347
  const structured = await extractStructuredData("synthesiser", markdown);
@@ -2347,7 +2361,7 @@ async function runQuery(opts) {
2347
2361
  cwd: opts.cwd,
2348
2362
  permissionMode: "bypassPermissions",
2349
2363
  allowDangerouslySkipPermissions: true,
2350
- maxTurns: 30,
2364
+ maxTurns: opts.maxTurns ?? 15,
2351
2365
  persistSession: false,
2352
2366
  settingSources: ["project"]
2353
2367
  }
@@ -2447,7 +2461,7 @@ function writeArtifact(role, context, markdown, projectRoot) {
2447
2461
  fs7.writeFileSync(target, markdown);
2448
2462
  return target;
2449
2463
  }
2450
- var fs7, path7, import_claude_agent_sdk2, DIM2, RESET2, CYAN2;
2464
+ var fs7, path7, import_claude_agent_sdk2, ROLE_MAX_TURNS, DIM2, RESET2, CYAN2;
2451
2465
  var init_spawn = __esm({
2452
2466
  "src/agents/spawn.ts"() {
2453
2467
  "use strict";
@@ -2456,6 +2470,15 @@ var init_spawn = __esm({
2456
2470
  import_claude_agent_sdk2 = require("@anthropic-ai/claude-agent-sdk");
2457
2471
  init_parse();
2458
2472
  init_connection();
2473
+ ROLE_MAX_TURNS = {
2474
+ builder: 50,
2475
+ critic: 12,
2476
+ adversary: 12,
2477
+ verifier: 15,
2478
+ compressor: 15,
2479
+ reframer: 12,
2480
+ scout: 12
2481
+ };
2459
2482
  DIM2 = "\x1B[2m";
2460
2483
  RESET2 = "\x1B[0m";
2461
2484
  CYAN2 = "\x1B[36m";
@@ -3122,6 +3145,8 @@ async function executeStep(step, exp, root) {
3122
3145
  break;
3123
3146
  case "compressed" /* COMPRESSED */:
3124
3147
  await cycle("compress", []);
3148
+ updateExperimentStatus(getDb(root), exp.id, "compressed");
3149
+ info(`Experiment ${exp.slug} compressed.`);
3125
3150
  break;
3126
3151
  case "reframed" /* REFRAMED */:
3127
3152
  updateExperimentStatus(getDb(root), exp.id, "reframed");
@@ -3268,14 +3293,20 @@ ${deadEnds.map((d) => `- ${d.approach}: ${d.why_failed} [constraint: ${d.structu
3268
3293
 
3269
3294
  ## Your Task
3270
3295
  1. Assess: based on the metrics and synthesis, has the goal been met? Be specific.
3271
- 2. If YES \u2014 output: <!-- majlis-json {"goal_met": true, "hypothesis": null} -->
3296
+ 2. If YES \u2014 output the JSON block below with goal_met: true.
3272
3297
  3. If NO \u2014 propose the SINGLE most promising next experiment hypothesis.
3273
- - It must NOT repeat a dead-ended approach
3298
+ - It must NOT repeat a dead-ended approach (check the dead-end registry!)
3274
3299
  - It should attack the weakest point revealed by synthesis/fragility
3275
- - It should be specific and actionable (not vague)
3276
- - Output: <!-- majlis-json {"goal_met": false, "hypothesis": "your hypothesis here"} -->
3300
+ - It must be specific and actionable \u2014 name the function or mechanism to change
3301
+ - Do NOT reference specific line numbers \u2014 they shift between experiments
3302
+ - The hypothesis should be a single sentence describing what to do, e.g.:
3303
+ "Activate addSeamEdges() in the runEdgeFirst pipeline for full-revolution cylinder faces"
3304
+
3305
+ CRITICAL: Your LAST line of output MUST be EXACTLY this format (on its own line, nothing after it):
3306
+ <!-- majlis-json {"goal_met": false, "hypothesis": "your single-sentence hypothesis here"} -->
3277
3307
 
3278
- IMPORTANT: You MUST output the <!-- majlis-json --> block. This is how the framework reads your decision.`
3308
+ If the goal is met:
3309
+ <!-- majlis-json {"goal_met": true, "hypothesis": null} -->`
3279
3310
  }, root);
3280
3311
  const structured = result.structured;
3281
3312
  if (structured?.goal_met === true) {
@@ -3284,9 +3315,27 @@ IMPORTANT: You MUST output the <!-- majlis-json --> block. This is how the frame
3284
3315
  if (structured?.hypothesis) {
3285
3316
  return structured.hypothesis;
3286
3317
  }
3287
- const match = result.output.match(/hypothesis["\s:]+([^"}\n]+)/i);
3288
- if (match) return match[1].trim();
3289
- warn("Planner did not return a structured hypothesis. Using goal as fallback.");
3318
+ const jsonMatch = result.output.match(/"hypothesis"\s*:\s*"([^"]+)"/);
3319
+ if (jsonMatch && jsonMatch[1].length > 10) return jsonMatch[1].trim();
3320
+ const blockMatch = result.output.match(/<!--\s*majlis-json\s*(\{[\s\S]*?\})\s*-->/);
3321
+ if (blockMatch) {
3322
+ try {
3323
+ const parsed = JSON.parse(blockMatch[1]);
3324
+ if (parsed.goal_met === true) return null;
3325
+ if (parsed.hypothesis) return parsed.hypothesis;
3326
+ } catch {
3327
+ }
3328
+ }
3329
+ warn("Planner did not return structured output. Retrying with focused prompt...");
3330
+ const retry = await spawnSynthesiser({
3331
+ taskPrompt: `Based on this analysis, output ONLY a single-line JSON block:
3332
+
3333
+ ${result.output.slice(-2e3)}
3334
+
3335
+ <!-- majlis-json {"goal_met": false, "hypothesis": "your hypothesis"} -->`
3336
+ }, root);
3337
+ if (retry.structured?.hypothesis) return retry.structured.hypothesis;
3338
+ warn("Could not extract hypothesis. Using goal as fallback.");
3290
3339
  return goal;
3291
3340
  }
3292
3341
  function createNewExperiment(db, root, hypothesis) {
@@ -3312,6 +3361,8 @@ function createNewExperiment(db, root, hypothesis) {
3312
3361
  warn(`Could not create branch ${branch} \u2014 continuing without git branch.`);
3313
3362
  }
3314
3363
  const exp = createExperiment(db, finalSlug, branch, hypothesis, null, null);
3364
+ updateExperimentStatus(db, exp.id, "reframed");
3365
+ exp.status = "reframed";
3315
3366
  const docsDir = path13.join(root, "docs", "experiments");
3316
3367
  const templatePath = path13.join(docsDir, "_TEMPLATE.md");
3317
3368
  if (fs13.existsSync(templatePath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "majlis",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Multi-agent workflow CLI for structured doubt, independent verification, and compressed knowledge",
5
5
  "bin": {
6
6
  "majlis": "./dist/cli.js"