majlis 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +113 -129
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Multi-agent workflow CLI for structured doubt, independent verification, and compressed knowledge.
4
4
 
5
- This is the CLI package. For full documentation, see the [root README](../../README.md).
5
+ This is the CLI package. For full documentation, see the [root README](https://github.com/raihaan123/majlis#readme).
6
6
 
7
7
  ## Installation
8
8
 
package/dist/cli.js CHANGED
@@ -501,9 +501,9 @@ var init_init = __esm({
501
501
  },
502
502
  models: {
503
503
  builder: "opus",
504
- critic: "sonnet",
505
- adversary: "sonnet",
506
- verifier: "sonnet",
504
+ critic: "opus",
505
+ adversary: "opus",
506
+ verifier: "opus",
507
507
  reframer: "opus",
508
508
  compressor: "opus"
509
509
  }
@@ -546,7 +546,7 @@ At the end of your work, include a <!-- majlis-json --> block with your decision
546
546
  \`\`\``,
547
547
  critic: `---
548
548
  name: critic
549
- model: sonnet
549
+ model: opus
550
550
  tools: [Read, Glob, Grep]
551
551
  ---
552
552
  You are the Critic. You practise constructive doubt.
@@ -578,7 +578,7 @@ Write to docs/doubts/NNN-against-experiment-NNN.md
578
578
  -->`,
579
579
  adversary: `---
580
580
  name: adversary
581
- model: sonnet
581
+ model: opus
582
582
  tools: [Read, Glob, Grep]
583
583
  ---
584
584
  You are the Adversary. You do NOT review code for bugs.
@@ -606,7 +606,7 @@ Write to docs/challenges/NNN-against-experiment-NNN.md
606
606
  -->`,
607
607
  verifier: `---
608
608
  name: verifier
609
- model: sonnet
609
+ model: opus
610
610
  tools: [Read, Glob, Grep, Bash]
611
611
  ---
612
612
  You are the Verifier. Perform dual verification:
@@ -687,7 +687,7 @@ You may NOT write code, make decisions, or run experiments.
687
687
  -->`,
688
688
  scout: `---
689
689
  name: scout
690
- model: sonnet
690
+ model: opus
691
691
  tools: [Read, Glob, Grep, WebSearch]
692
692
  ---
693
693
  You are the Scout. You practise rihla \u2014 travel in search of knowledge.
@@ -2094,7 +2094,7 @@ var init_types2 = __esm({
2094
2094
  });
2095
2095
 
2096
2096
  // src/agents/parse.ts
2097
- function extractStructuredData(role, markdown) {
2097
+ async function extractStructuredData(role, markdown) {
2098
2098
  const tier1 = extractMajlisJsonBlock(markdown);
2099
2099
  if (tier1) {
2100
2100
  const parsed = tryParseJson(tier1);
@@ -2109,7 +2109,7 @@ function extractStructuredData(role, markdown) {
2109
2109
  return tier2;
2110
2110
  }
2111
2111
  console.warn(`[majlis] Regex fallback insufficient for ${role}. Using Haiku extraction.`);
2112
- const tier3 = extractViaHaiku(role, markdown);
2112
+ const tier3 = await extractViaHaiku(role, markdown);
2113
2113
  if (tier3) return tier3;
2114
2114
  console.error(
2115
2115
  `[majlis] FAILED to extract structured data from ${role} output. State machine will continue but data is missing. Manual review required.`
@@ -2185,22 +2185,36 @@ function extractViaPatterns(role, markdown) {
2185
2185
  if (doubts.length > 0) result.doubts = doubts;
2186
2186
  return result;
2187
2187
  }
2188
- function extractViaHaiku(role, markdown) {
2188
+ async function extractViaHaiku(role, markdown) {
2189
2189
  try {
2190
2190
  const truncated = markdown.length > 8e3 ? markdown.slice(0, 8e3) + "\n[truncated]" : markdown;
2191
2191
  const prompt = `Extract all decisions, evidence levels, grades, doubts, and guidance from this ${role} document as JSON. Follow this schema exactly: ${EXTRACTION_SCHEMA}
2192
2192
 
2193
2193
  Document:
2194
2194
  ${truncated}`;
2195
- const result = (0, import_node_child_process3.execSync)(
2196
- `claude --print --model haiku --output-format json -p ${JSON.stringify(prompt)}`,
2197
- {
2198
- encoding: "utf-8",
2199
- timeout: 3e4,
2200
- stdio: ["pipe", "pipe", "pipe"]
2195
+ const conversation = (0, import_claude_agent_sdk.query)({
2196
+ prompt,
2197
+ options: {
2198
+ model: "haiku",
2199
+ tools: [],
2200
+ systemPrompt: "You are a JSON extraction assistant. Output only valid JSON matching the requested schema. No markdown, no explanation, just JSON.",
2201
+ permissionMode: "bypassPermissions",
2202
+ allowDangerouslySkipPermissions: true,
2203
+ maxTurns: 1,
2204
+ persistSession: false
2201
2205
  }
2202
- );
2203
- return tryParseJson(result.trim());
2206
+ });
2207
+ let resultText = "";
2208
+ for await (const message of conversation) {
2209
+ if (message.type === "assistant") {
2210
+ for (const block of message.message.content) {
2211
+ if (block.type === "text") {
2212
+ resultText += block.text;
2213
+ }
2214
+ }
2215
+ }
2216
+ }
2217
+ return tryParseJson(resultText.trim());
2204
2218
  } catch (err) {
2205
2219
  console.warn(`[majlis] Haiku extraction failed for ${role}: ${err instanceof Error ? err.message : String(err)}`);
2206
2220
  return null;
@@ -2209,12 +2223,12 @@ ${truncated}`;
2209
2223
  function hasData(output) {
2210
2224
  return !!(output.decisions && output.decisions.length > 0 || output.grades && output.grades.length > 0 || output.doubts && output.doubts.length > 0 || output.guidance);
2211
2225
  }
2212
- var import_node_child_process3;
2226
+ var import_claude_agent_sdk;
2213
2227
  var init_parse = __esm({
2214
2228
  "src/agents/parse.ts"() {
2215
2229
  "use strict";
2216
2230
  init_types2();
2217
- import_node_child_process3 = require("child_process");
2231
+ import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
2218
2232
  }
2219
2233
  });
2220
2234
 
@@ -2233,7 +2247,7 @@ function loadAgentDefinition(role, projectRoot) {
2233
2247
  const frontmatter = frontmatterMatch[1];
2234
2248
  const body = frontmatterMatch[2].trim();
2235
2249
  const name = extractYamlField(frontmatter, "name") ?? role;
2236
- const model = extractYamlField(frontmatter, "model") ?? "sonnet";
2250
+ const model = extractYamlField(frontmatter, "model") ?? "opus";
2237
2251
  const toolsStr = extractYamlField(frontmatter, "tools") ?? "[]";
2238
2252
  const tools = toolsStr.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean);
2239
2253
  return { name, model, tools, systemPrompt: body };
@@ -2242,125 +2256,96 @@ function extractYamlField(yaml, field) {
2242
2256
  const match = yaml.match(new RegExp(`^${field}:\\s*(.+)$`, "m"));
2243
2257
  return match ? match[1].trim() : null;
2244
2258
  }
2245
- function writeTempContext(context) {
2246
- const tmpDir = os.tmpdir();
2247
- const tmpFile = path7.join(tmpDir, `majlis-context-${Date.now()}.json`);
2248
- fs7.writeFileSync(tmpFile, JSON.stringify(context, null, 2));
2249
- return tmpFile;
2250
- }
2251
2259
  async function spawnAgent(role, context, projectRoot) {
2252
2260
  const agentDef = loadAgentDefinition(role, projectRoot);
2253
- const contextFile = writeTempContext(context);
2254
2261
  const root = projectRoot ?? findProjectRoot() ?? process.cwd();
2255
2262
  const taskPrompt = context.taskPrompt ?? `Perform your role as ${agentDef.name}.`;
2256
- const prompt = `Read the context at ${contextFile}. ${taskPrompt}`;
2257
- const args = [
2258
- "--print",
2259
- "--output-format",
2260
- "stream-json",
2261
- "--model",
2262
- agentDef.model,
2263
- "--allowedTools",
2264
- ...agentDef.tools,
2265
- "--append-system-prompt",
2266
- agentDef.systemPrompt,
2267
- "-p",
2268
- prompt
2269
- ];
2263
+ const contextJson = JSON.stringify(context, null, 2);
2264
+ const prompt = `Here is your context:
2265
+
2266
+ \`\`\`json
2267
+ ${contextJson}
2268
+ \`\`\`
2269
+
2270
+ ${taskPrompt}`;
2270
2271
  console.log(`[majlis] Spawning ${role} agent (model: ${agentDef.model})...`);
2271
- const output = await runClaude(args, root);
2272
- const markdown = parseStreamJsonOutput(output);
2272
+ const { text: markdown, costUsd } = await runQuery({
2273
+ prompt,
2274
+ model: agentDef.model,
2275
+ tools: agentDef.tools,
2276
+ systemPrompt: agentDef.systemPrompt,
2277
+ cwd: root
2278
+ });
2279
+ console.log(`[majlis] ${role} agent complete (cost: $${costUsd.toFixed(4)})`);
2273
2280
  const artifactPath = writeArtifact(role, context, markdown, root);
2274
2281
  if (artifactPath) {
2275
2282
  console.log(`[majlis] ${role} artifact written to ${artifactPath}`);
2276
2283
  }
2277
- const structured = extractStructuredData(role, markdown);
2278
- try {
2279
- fs7.unlinkSync(contextFile);
2280
- } catch {
2281
- }
2284
+ const structured = await extractStructuredData(role, markdown);
2282
2285
  return { output: markdown, structured };
2283
2286
  }
2284
2287
  async function spawnSynthesiser(context, projectRoot) {
2285
- const contextFile = writeTempContext(context);
2286
2288
  const root = projectRoot ?? findProjectRoot() ?? process.cwd();
2287
- const prompt = `Read the context at ${contextFile}. ${context.taskPrompt ?? "Synthesise the findings into actionable builder guidance."}`;
2288
- const args = [
2289
- "--print",
2290
- "--output-format",
2291
- "stream-json",
2292
- "--model",
2293
- "sonnet",
2294
- "--allowedTools",
2295
- "Read",
2296
- "Glob",
2297
- "Grep",
2298
- "--append-system-prompt",
2299
- "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.",
2300
- "-p",
2301
- prompt
2302
- ];
2289
+ const contextJson = JSON.stringify(context, null, 2);
2290
+ const taskPrompt = context.taskPrompt ?? "Synthesise the findings into actionable builder guidance.";
2291
+ const prompt = `Here is your context:
2292
+
2293
+ \`\`\`json
2294
+ ${contextJson}
2295
+ \`\`\`
2296
+
2297
+ ${taskPrompt}`;
2298
+ 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.";
2303
2299
  console.log(`[majlis] Spawning synthesiser micro-agent...`);
2304
- const output = await runClaude(args, root);
2305
- const markdown = parseStreamJsonOutput(output);
2306
- const structured = extractStructuredData("synthesiser", markdown);
2307
- try {
2308
- fs7.unlinkSync(contextFile);
2309
- } catch {
2310
- }
2300
+ const { text: markdown, costUsd } = await runQuery({
2301
+ prompt,
2302
+ model: "opus",
2303
+ tools: ["Read", "Glob", "Grep"],
2304
+ systemPrompt,
2305
+ cwd: root
2306
+ });
2307
+ console.log(`[majlis] Synthesiser complete (cost: $${costUsd.toFixed(4)})`);
2308
+ const structured = await extractStructuredData("synthesiser", markdown);
2311
2309
  return { output: markdown, structured };
2312
2310
  }
2313
- function runClaude(args, cwd) {
2314
- return new Promise((resolve2, reject) => {
2315
- const proc = (0, import_node_child_process4.spawn)("claude", args, {
2316
- cwd,
2317
- stdio: ["pipe", "pipe", "pipe"],
2318
- env: { ...process.env }
2319
- });
2320
- let stdout = "";
2321
- let stderr = "";
2322
- proc.stdout.on("data", (chunk) => {
2323
- stdout += chunk.toString();
2324
- });
2325
- proc.stderr.on("data", (chunk) => {
2326
- stderr += chunk.toString();
2327
- });
2328
- proc.on("close", (code) => {
2329
- if (code !== 0) {
2330
- reject(new Error(`claude exited with code ${code}: ${stderr}`));
2331
- } else {
2332
- resolve2(stdout);
2333
- }
2334
- });
2335
- proc.on("error", (err) => {
2336
- reject(new Error(`Failed to spawn claude: ${err.message}`));
2337
- });
2311
+ async function runQuery(opts) {
2312
+ const conversation = (0, import_claude_agent_sdk2.query)({
2313
+ prompt: opts.prompt,
2314
+ options: {
2315
+ model: opts.model,
2316
+ tools: opts.tools,
2317
+ systemPrompt: {
2318
+ type: "preset",
2319
+ preset: "claude_code",
2320
+ append: opts.systemPrompt
2321
+ },
2322
+ cwd: opts.cwd,
2323
+ permissionMode: "bypassPermissions",
2324
+ allowDangerouslySkipPermissions: true,
2325
+ maxTurns: 50,
2326
+ persistSession: false,
2327
+ settingSources: ["project"]
2328
+ }
2338
2329
  });
2339
- }
2340
- function parseStreamJsonOutput(raw) {
2341
- const parts = [];
2342
- for (const line of raw.split("\n")) {
2343
- if (!line.trim()) continue;
2344
- try {
2345
- const event = JSON.parse(line);
2346
- if (event.type === "assistant" && event.message?.content) {
2347
- for (const block of event.message.content) {
2348
- if (block.type === "text") {
2349
- parts.push(block.text);
2350
- }
2351
- }
2352
- } else if (event.type === "content_block_delta" && event.delta?.text) {
2353
- parts.push(event.delta.text);
2354
- } else if (event.type === "result" && event.result) {
2355
- if (typeof event.result === "string") {
2356
- parts.push(event.result);
2330
+ const textParts = [];
2331
+ let costUsd = 0;
2332
+ for await (const message of conversation) {
2333
+ if (message.type === "assistant") {
2334
+ for (const block of message.message.content) {
2335
+ if (block.type === "text") {
2336
+ textParts.push(block.text);
2357
2337
  }
2358
2338
  }
2359
- } catch {
2360
- if (line.trim()) parts.push(line);
2339
+ } else if (message.type === "result") {
2340
+ if (message.subtype === "success") {
2341
+ costUsd = message.total_cost_usd;
2342
+ } else {
2343
+ const errors = "errors" in message ? message.errors?.join("; ") ?? "Unknown error" : "Unknown error";
2344
+ throw new Error(`Agent query failed (${message.subtype}): ${errors}`);
2345
+ }
2361
2346
  }
2362
2347
  }
2363
- return parts.join("");
2348
+ return { text: textParts.join("\n\n"), costUsd };
2364
2349
  }
2365
2350
  function writeArtifact(role, context, markdown, projectRoot) {
2366
2351
  const dirMap = {
@@ -2391,14 +2376,13 @@ function writeArtifact(role, context, markdown, projectRoot) {
2391
2376
  fs7.writeFileSync(target, markdown);
2392
2377
  return target;
2393
2378
  }
2394
- var fs7, path7, os, import_node_child_process4;
2379
+ var fs7, path7, import_claude_agent_sdk2;
2395
2380
  var init_spawn = __esm({
2396
2381
  "src/agents/spawn.ts"() {
2397
2382
  "use strict";
2398
2383
  fs7 = __toESM(require("fs"));
2399
2384
  path7 = __toESM(require("path"));
2400
- os = __toESM(require("os"));
2401
- import_node_child_process4 = require("child_process");
2385
+ import_claude_agent_sdk2 = require("@anthropic-ai/claude-agent-sdk");
2402
2386
  init_parse();
2403
2387
  init_connection();
2404
2388
  }
@@ -2479,7 +2463,7 @@ async function resolve(db, exp, projectRoot) {
2479
2463
  }
2480
2464
  function gitMerge(branch, cwd) {
2481
2465
  try {
2482
- (0, import_node_child_process5.execSync)(`git merge ${branch} --no-ff -m "Merge experiment branch ${branch}"`, {
2466
+ (0, import_node_child_process3.execSync)(`git merge ${branch} --no-ff -m "Merge experiment branch ${branch}"`, {
2483
2467
  cwd,
2484
2468
  encoding: "utf-8",
2485
2469
  stdio: ["pipe", "pipe", "pipe"]
@@ -2490,12 +2474,12 @@ function gitMerge(branch, cwd) {
2490
2474
  }
2491
2475
  function gitRevert(branch, cwd) {
2492
2476
  try {
2493
- const currentBranch = (0, import_node_child_process5.execSync)("git rev-parse --abbrev-ref HEAD", {
2477
+ const currentBranch = (0, import_node_child_process3.execSync)("git rev-parse --abbrev-ref HEAD", {
2494
2478
  cwd,
2495
2479
  encoding: "utf-8"
2496
2480
  }).trim();
2497
2481
  if (currentBranch === branch) {
2498
- (0, import_node_child_process5.execSync)("git checkout main 2>/dev/null || git checkout master", {
2482
+ (0, import_node_child_process3.execSync)("git checkout main 2>/dev/null || git checkout master", {
2499
2483
  cwd,
2500
2484
  encoding: "utf-8",
2501
2485
  stdio: ["pipe", "pipe", "pipe"]
@@ -2517,7 +2501,7 @@ ${gaps}
2517
2501
  `;
2518
2502
  fs8.writeFileSync(fragPath, content + entry);
2519
2503
  }
2520
- var fs8, path8, import_node_child_process5;
2504
+ var fs8, path8, import_node_child_process3;
2521
2505
  var init_resolve = __esm({
2522
2506
  "src/resolve.ts"() {
2523
2507
  "use strict";
@@ -2526,7 +2510,7 @@ var init_resolve = __esm({
2526
2510
  init_types();
2527
2511
  init_queries();
2528
2512
  init_spawn();
2529
- import_node_child_process5 = require("child_process");
2513
+ import_node_child_process3 = require("child_process");
2530
2514
  init_format();
2531
2515
  }
2532
2516
  });
@@ -3233,8 +3217,8 @@ async function main() {
3233
3217
  case "history":
3234
3218
  case "circuit-breakers":
3235
3219
  case "check-commit": {
3236
- const { query: query2 } = await Promise.resolve().then(() => (init_query(), query_exports));
3237
- await query2(command, rest, isJson);
3220
+ const { query: query4 } = await Promise.resolve().then(() => (init_query(), query_exports));
3221
+ await query4(command, rest, isJson);
3238
3222
  break;
3239
3223
  }
3240
3224
  case "build":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "majlis",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Multi-agent workflow CLI for structured doubt, independent verification, and compressed knowledge",
5
5
  "bin": {
6
6
  "majlis": "./dist/cli.js"
@@ -10,6 +10,7 @@
10
10
  "test": "tsx --test src/test/*.test.ts"
11
11
  },
12
12
  "dependencies": {
13
+ "@anthropic-ai/claude-agent-sdk": "^0.2.42",
13
14
  "better-sqlite3": "^11.0.0"
14
15
  },
15
16
  "devDependencies": {