kairn-cli 2.10.0 → 2.11.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.
Files changed (4) hide show
  1. package/README.md +171 -483
  2. package/dist/cli.js +1721 -851
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -181,6 +181,101 @@ var init_providers = __esm({
181
181
  }
182
182
  });
183
183
 
184
+ // src/compiler/agents/types.ts
185
+ function validatePlan(plan) {
186
+ if (plan == null || typeof plan !== "object") {
187
+ throw new Error("CompilationPlan must be a non-null object");
188
+ }
189
+ const obj = plan;
190
+ if (typeof obj["project_context"] !== "string") {
191
+ throw new Error(
192
+ "CompilationPlan.project_context must be a string"
193
+ );
194
+ }
195
+ if (obj["project_context"].length === 0) {
196
+ throw new Error(
197
+ "CompilationPlan.project_context must not be empty"
198
+ );
199
+ }
200
+ if (!Array.isArray(obj["phases"])) {
201
+ throw new Error("CompilationPlan.phases must be an array");
202
+ }
203
+ if (obj["phases"].length === 0) {
204
+ throw new Error(
205
+ "CompilationPlan.phases must contain at least one phase"
206
+ );
207
+ }
208
+ for (let pi = 0; pi < obj["phases"].length; pi++) {
209
+ validatePhase(obj["phases"][pi], pi);
210
+ }
211
+ return plan;
212
+ }
213
+ function validatePhase(phase, index) {
214
+ if (phase == null || typeof phase !== "object") {
215
+ throw new Error(`phases[${index}] must be a non-null object`);
216
+ }
217
+ const obj = phase;
218
+ if (typeof obj["id"] !== "string") {
219
+ throw new Error(`phases[${index}].id must be a string`);
220
+ }
221
+ if (!Array.isArray(obj["agents"])) {
222
+ throw new Error(`phases[${index}].agents must be an array`);
223
+ }
224
+ for (let ai = 0; ai < obj["agents"].length; ai++) {
225
+ validateAgentTask(obj["agents"][ai], index, ai);
226
+ }
227
+ if (!Array.isArray(obj["dependsOn"])) {
228
+ throw new Error(`phases[${index}].dependsOn must be an array`);
229
+ }
230
+ }
231
+ function validateAgentTask(task, phaseIndex, taskIndex) {
232
+ if (task == null || typeof task !== "object") {
233
+ throw new Error(
234
+ `phases[${phaseIndex}].agents[${taskIndex}] must be a non-null object`
235
+ );
236
+ }
237
+ const obj = task;
238
+ const prefix = `phases[${phaseIndex}].agents[${taskIndex}]`;
239
+ if (typeof obj["agent"] !== "string") {
240
+ throw new Error(`${prefix}.agent must be a string`);
241
+ }
242
+ if (!VALID_AGENT_NAMES.includes(obj["agent"])) {
243
+ throw new Error(
244
+ `${prefix}.agent "${obj["agent"]}" is not a valid agent name. Valid names: ${VALID_AGENT_NAMES.join(", ")}`
245
+ );
246
+ }
247
+ if (!Array.isArray(obj["items"])) {
248
+ throw new Error(`${prefix}.items must be an array`);
249
+ }
250
+ if (typeof obj["max_tokens"] !== "number") {
251
+ throw new Error(`${prefix}.max_tokens must be a number`);
252
+ }
253
+ }
254
+ var VALID_AGENT_NAMES, TruncationError;
255
+ var init_types = __esm({
256
+ "src/compiler/agents/types.ts"() {
257
+ "use strict";
258
+ VALID_AGENT_NAMES = [
259
+ "sections-writer",
260
+ "command-writer",
261
+ "agent-writer",
262
+ "rule-writer",
263
+ "doc-writer",
264
+ "skill-writer"
265
+ ];
266
+ TruncationError = class extends Error {
267
+ agentName;
268
+ tokensUsed;
269
+ constructor(message, options) {
270
+ super(message);
271
+ this.name = "TruncationError";
272
+ this.agentName = options.agentName;
273
+ this.tokensUsed = options.tokensUsed;
274
+ }
275
+ };
276
+ }
277
+ });
278
+
184
279
  // src/llm.ts
185
280
  import Anthropic2 from "@anthropic-ai/sdk";
186
281
  import OpenAI2 from "openai";
@@ -242,12 +337,22 @@ async function callLLM(config, userMessage, options) {
242
337
  system: cacheControl ? [{ type: "text", text: systemPrompt, cache_control: { type: "ephemeral" } }] : systemPrompt,
243
338
  messages
244
339
  });
340
+ if (response.stop_reason === "max_tokens") {
341
+ const agentLabel = options.agentName ?? "unknown";
342
+ throw new TruncationError(
343
+ `Response truncated at ${maxTokens} tokens. Agent: ${agentLabel}`,
344
+ { agentName: agentLabel, tokensUsed: maxTokens }
345
+ );
346
+ }
245
347
  const textBlock = response.content.find((block) => block.type === "text");
246
348
  if (!textBlock || textBlock.type !== "text") {
247
349
  throw new Error("No text response from compiler LLM");
248
350
  }
249
351
  return textBlock.text;
250
352
  } catch (err) {
353
+ if (err instanceof TruncationError) {
354
+ throw err;
355
+ }
251
356
  throw new Error(classifyError(err, providerName));
252
357
  }
253
358
  }
@@ -265,12 +370,22 @@ async function callLLM(config, userMessage, options) {
265
370
  ],
266
371
  ...jsonMode ? { response_format: { type: "json_object" } } : {}
267
372
  });
373
+ if (response.choices[0]?.finish_reason === "length") {
374
+ const agentLabel = options.agentName ?? "unknown";
375
+ throw new TruncationError(
376
+ `Response truncated at ${maxTokens} tokens. Agent: ${agentLabel}`,
377
+ { agentName: agentLabel, tokensUsed: maxTokens }
378
+ );
379
+ }
268
380
  const text = response.choices[0]?.message?.content;
269
381
  if (!text) {
270
382
  throw new Error("No text response from compiler LLM");
271
383
  }
272
384
  return text;
273
385
  } catch (err) {
386
+ if (err instanceof TruncationError) {
387
+ throw err;
388
+ }
274
389
  throw new Error(classifyError(err, providerName));
275
390
  }
276
391
  }
@@ -279,6 +394,273 @@ var init_llm = __esm({
279
394
  "use strict";
280
395
  init_providers();
281
396
  init_keychain();
397
+ init_types();
398
+ }
399
+ });
400
+
401
+ // src/ir/types.ts
402
+ function createEmptyIR() {
403
+ return {
404
+ meta: {
405
+ name: "",
406
+ purpose: "",
407
+ techStack: { language: "" },
408
+ autonomyLevel: 2
409
+ },
410
+ sections: [],
411
+ commands: [],
412
+ rules: [],
413
+ agents: [],
414
+ skills: [],
415
+ docs: [],
416
+ hooks: [],
417
+ settings: createEmptySettings(),
418
+ mcpServers: [],
419
+ intents: []
420
+ };
421
+ }
422
+ function createEmptySettings() {
423
+ return { hooks: {}, raw: {} };
424
+ }
425
+ function createSection(id, heading, content, order) {
426
+ return { id, heading, content, order };
427
+ }
428
+ function createCommandNode(name, content, description) {
429
+ return { name, description: description ?? "", content };
430
+ }
431
+ function createRuleNode(name, content, paths) {
432
+ const node = { name, content };
433
+ if (paths !== void 0) {
434
+ node.paths = paths;
435
+ }
436
+ return node;
437
+ }
438
+ function createAgentNode(name, content, model) {
439
+ const node = { name, content };
440
+ if (model !== void 0) {
441
+ node.model = model;
442
+ }
443
+ return node;
444
+ }
445
+ function createEmptyDiff() {
446
+ return {
447
+ sections: {
448
+ added: [],
449
+ removed: [],
450
+ modified: [],
451
+ reordered: []
452
+ },
453
+ commands: {
454
+ added: [],
455
+ removed: [],
456
+ modified: []
457
+ },
458
+ rules: {
459
+ added: [],
460
+ removed: [],
461
+ modified: []
462
+ },
463
+ agents: {
464
+ added: [],
465
+ removed: [],
466
+ modified: []
467
+ },
468
+ mcpServers: {
469
+ added: [],
470
+ removed: []
471
+ },
472
+ settings: {
473
+ changes: []
474
+ }
475
+ };
476
+ }
477
+ var init_types2 = __esm({
478
+ "src/ir/types.ts"() {
479
+ "use strict";
480
+ }
481
+ });
482
+
483
+ // src/ir/renderer.ts
484
+ function renderClaudeMd(_meta, sections) {
485
+ const sorted = [...sections].sort((a, b) => a.order - b.order);
486
+ const blocks = [];
487
+ for (const section of sorted) {
488
+ if (section.heading && section.content) {
489
+ blocks.push(`${section.heading}
490
+
491
+ ${section.content}`);
492
+ } else if (section.heading) {
493
+ blocks.push(section.heading);
494
+ } else if (section.content) {
495
+ blocks.push(section.content);
496
+ }
497
+ }
498
+ if (blocks.length === 0) {
499
+ return "\n";
500
+ }
501
+ return blocks.join("\n\n") + "\n";
502
+ }
503
+ function renderSettings(settings) {
504
+ const result = JSON.parse(
505
+ JSON.stringify(settings.raw)
506
+ );
507
+ if (settings.denyPatterns && settings.denyPatterns.length > 0) {
508
+ const permissions = result["permissions"] ?? {};
509
+ permissions["deny"] = settings.denyPatterns;
510
+ result["permissions"] = permissions;
511
+ }
512
+ if (settings.statusLine) {
513
+ result["statusLine"] = settings.statusLine;
514
+ }
515
+ const hookEvents = [
516
+ "PreToolUse",
517
+ "PostToolUse",
518
+ "UserPromptSubmit",
519
+ "SessionStart",
520
+ "PostCompact"
521
+ ];
522
+ const hooksObj = {};
523
+ let hasHooks = false;
524
+ for (const event of hookEvents) {
525
+ const entries = settings.hooks[event];
526
+ if (entries && entries.length > 0) {
527
+ hooksObj[event] = entries;
528
+ hasHooks = true;
529
+ }
530
+ }
531
+ if (hasHooks) {
532
+ result["hooks"] = hooksObj;
533
+ }
534
+ return JSON.stringify(result, null, 2) + "\n";
535
+ }
536
+ function renderMcpConfig(servers) {
537
+ if (servers.length === 0) {
538
+ return "";
539
+ }
540
+ const mcpServers = {};
541
+ for (const server of servers) {
542
+ const entry = {
543
+ command: server.command,
544
+ args: server.args
545
+ };
546
+ if (server.env && Object.keys(server.env).length > 0) {
547
+ entry["env"] = server.env;
548
+ }
549
+ mcpServers[server.id] = entry;
550
+ }
551
+ return JSON.stringify({ mcpServers }, null, 2) + "\n";
552
+ }
553
+ function renderRuleWithFrontmatter(rule) {
554
+ if (!rule.paths || rule.paths.length === 0) {
555
+ return rule.content;
556
+ }
557
+ const yamlLines = ["---", "paths:"];
558
+ for (const p of rule.paths) {
559
+ yamlLines.push(` - ${p}`);
560
+ }
561
+ yamlLines.push("---");
562
+ return yamlLines.join("\n") + "\n\n" + rule.content;
563
+ }
564
+ function renderAgentWithFrontmatter(agent) {
565
+ const hasModel = agent.model !== void 0;
566
+ const hasDisallowed = agent.disallowedTools !== void 0 && agent.disallowedTools.length > 0;
567
+ const hasRouting = agent.modelRouting !== void 0;
568
+ const hasExtra = agent.extraFrontmatter !== void 0 && Object.keys(agent.extraFrontmatter).length > 0;
569
+ if (!hasModel && !hasDisallowed && !hasRouting && !hasExtra) {
570
+ return agent.content;
571
+ }
572
+ const yamlLines = ["---"];
573
+ if (hasModel) {
574
+ yamlLines.push(`model: ${agent.model}`);
575
+ }
576
+ if (hasDisallowed) {
577
+ yamlLines.push("disallowedTools:");
578
+ for (const tool of agent.disallowedTools) {
579
+ yamlLines.push(` - ${tool}`);
580
+ }
581
+ }
582
+ if (hasRouting) {
583
+ yamlLines.push("modelRouting:");
584
+ yamlLines.push(` default: ${agent.modelRouting.default}`);
585
+ if (agent.modelRouting.escalateTo) {
586
+ yamlLines.push(` escalateTo: ${agent.modelRouting.escalateTo}`);
587
+ }
588
+ if (agent.modelRouting.escalateWhen) {
589
+ yamlLines.push(` escalateWhen: ${agent.modelRouting.escalateWhen}`);
590
+ }
591
+ }
592
+ if (hasExtra) {
593
+ for (const [key, value] of Object.entries(agent.extraFrontmatter)) {
594
+ if (Array.isArray(value)) {
595
+ yamlLines.push(`${key}:`);
596
+ for (const item of value) {
597
+ yamlLines.push(` - ${String(item)}`);
598
+ }
599
+ } else if (typeof value === "object" && value !== null) {
600
+ yamlLines.push(`${key}:`);
601
+ for (const [subKey, subVal] of Object.entries(value)) {
602
+ yamlLines.push(` ${subKey}: ${String(subVal)}`);
603
+ }
604
+ } else {
605
+ yamlLines.push(`${key}: ${String(value)}`);
606
+ }
607
+ }
608
+ }
609
+ yamlLines.push("---");
610
+ return yamlLines.join("\n") + "\n\n" + agent.content;
611
+ }
612
+ function settingsHasContent(settings) {
613
+ if (settings.statusLine) return true;
614
+ if (settings.denyPatterns && settings.denyPatterns.length > 0) return true;
615
+ if (Object.keys(settings.raw).length > 0) return true;
616
+ const hookEvents = [
617
+ "PreToolUse",
618
+ "PostToolUse",
619
+ "UserPromptSubmit",
620
+ "SessionStart",
621
+ "PostCompact"
622
+ ];
623
+ for (const event of hookEvents) {
624
+ const entries = settings.hooks[event];
625
+ if (entries && entries.length > 0) return true;
626
+ }
627
+ return false;
628
+ }
629
+ function renderHarness(ir) {
630
+ const files = /* @__PURE__ */ new Map();
631
+ if (ir.sections.length > 0 || ir.meta.name) {
632
+ files.set("CLAUDE.md", renderClaudeMd(ir.meta, ir.sections));
633
+ }
634
+ if (settingsHasContent(ir.settings)) {
635
+ files.set("settings.json", renderSettings(ir.settings));
636
+ }
637
+ for (const cmd of ir.commands) {
638
+ files.set(`commands/${cmd.name}.md`, cmd.content);
639
+ }
640
+ for (const rule of ir.rules) {
641
+ files.set(`rules/${rule.name}.md`, renderRuleWithFrontmatter(rule));
642
+ }
643
+ for (const agent of ir.agents) {
644
+ files.set(`agents/${agent.name}.md`, renderAgentWithFrontmatter(agent));
645
+ }
646
+ for (const skill of ir.skills) {
647
+ files.set(`skills/${skill.name}.md`, skill.content);
648
+ }
649
+ for (const doc of ir.docs) {
650
+ files.set(`docs/${doc.name}.md`, doc.content);
651
+ }
652
+ for (const hook of ir.hooks) {
653
+ files.set(`hooks/${hook.name}.mjs`, hook.content);
654
+ }
655
+ const mcpContent = renderMcpConfig(ir.mcpServers);
656
+ if (mcpContent) {
657
+ files.set(".mcp.json", mcpContent);
658
+ }
659
+ return files;
660
+ }
661
+ var init_renderer = __esm({
662
+ "src/ir/renderer.ts"() {
663
+ "use strict";
282
664
  }
283
665
  });
284
666
 
@@ -1011,7 +1393,7 @@ function parseToolCalls(stdout) {
1011
1393
  return [];
1012
1394
  }
1013
1395
  }
1014
- async function runWithConcurrency(tasks, limit) {
1396
+ async function runWithConcurrency2(tasks, limit) {
1015
1397
  const results = new Array(tasks.length);
1016
1398
  const executing = /* @__PURE__ */ new Set();
1017
1399
  const errors = [];
@@ -1113,7 +1495,7 @@ async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config,
1113
1495
  });
1114
1496
  return { id: task.id, score: finalScore };
1115
1497
  };
1116
- const taskResults = await runWithConcurrency(
1498
+ const taskResults = await runWithConcurrency2(
1117
1499
  tasks.map((task) => () => evaluateTask(task)),
1118
1500
  concurrency
1119
1501
  );
@@ -1512,115 +1894,33 @@ Return a JSON object:
1512
1894
  { "file": "CLAUDE.md", "action": "replace", "old_text": "...", "new_text": "...", "rationale": "..." },
1513
1895
  { "file": "commands/develop.md", "action": "add_section", "new_text": "...", "rationale": "..." },
1514
1896
  { "file": "rules/obsolete.md", "action": "delete_file", "rationale": "..." }
1515
- ],
1516
- "expected_impact": { "task-id": "+15% \u2014 explanation" }
1517
- }
1518
-
1519
- ## MCP Configuration
1520
- You can also mutate .mcp.json to add, remove, or reconfigure MCP servers.
1521
- Treat .mcp.json like any other harness file \u2014 propose changes when traces show
1522
- the agent lacks a tool it needs, or has tools that add noise without benefit.
1523
-
1524
- ## Rules
1525
- - Propose AT MOST 3 mutations per iteration. Fewer, targeted mutations are more stable than many broad ones.
1526
- - Each mutation must have a clear rationale tied to a specific trace observation.
1527
- - Never remove something that's working for another task.
1528
- - If a previous iteration's change caused a regression, REVERT it.
1529
- - Consider both additions AND removals. Remove sections that add noise without improving task performance.
1530
- - Bloated harnesses hurt performance \u2014 trim what isn't earning its keep.
1531
-
1532
- ## Anti-Gaming (CRITICAL)
1533
- - Mutations must improve GENERAL-PURPOSE development quality, not target specific eval criteria.
1534
- - You do NOT have access to scoring rubrics or expected outcomes. Diagnose problems from traces only.
1535
- - Do NOT add over-specified rules that restate existing conventions with stronger emphasis (e.g., changing "use chalk.green for success" to "MUST use chalk.green, no exceptions"). If a convention already exists, trust it.
1536
- - Do NOT add rules that only apply to a narrow eval scenario (e.g., write permissions for a specific directory just because one task needed it).
1537
- - Ask: "Would this mutation help a developer working on ANY task in this project?" If not, don't propose it.
1538
-
1539
- Return ONLY valid JSON.`;
1540
- STDOUT_TRUNCATION_LIMIT = 1e3;
1541
- MAX_CONTEXT_CHARS = 1e5;
1542
- }
1543
- });
1544
-
1545
- // src/ir/types.ts
1546
- function createEmptyIR() {
1547
- return {
1548
- meta: {
1549
- name: "",
1550
- purpose: "",
1551
- techStack: { language: "" },
1552
- autonomyLevel: 2
1553
- },
1554
- sections: [],
1555
- commands: [],
1556
- rules: [],
1557
- agents: [],
1558
- skills: [],
1559
- docs: [],
1560
- hooks: [],
1561
- settings: createEmptySettings(),
1562
- mcpServers: [],
1563
- intents: []
1564
- };
1565
- }
1566
- function createEmptySettings() {
1567
- return { hooks: {}, raw: {} };
1568
- }
1569
- function createSection(id, heading, content, order) {
1570
- return { id, heading, content, order };
1571
- }
1572
- function createCommandNode(name, content, description) {
1573
- return { name, description: description ?? "", content };
1574
- }
1575
- function createRuleNode(name, content, paths) {
1576
- const node = { name, content };
1577
- if (paths !== void 0) {
1578
- node.paths = paths;
1579
- }
1580
- return node;
1581
- }
1582
- function createAgentNode(name, content, model) {
1583
- const node = { name, content };
1584
- if (model !== void 0) {
1585
- node.model = model;
1586
- }
1587
- return node;
1588
- }
1589
- function createEmptyDiff() {
1590
- return {
1591
- sections: {
1592
- added: [],
1593
- removed: [],
1594
- modified: [],
1595
- reordered: []
1596
- },
1597
- commands: {
1598
- added: [],
1599
- removed: [],
1600
- modified: []
1601
- },
1602
- rules: {
1603
- added: [],
1604
- removed: [],
1605
- modified: []
1606
- },
1607
- agents: {
1608
- added: [],
1609
- removed: [],
1610
- modified: []
1611
- },
1612
- mcpServers: {
1613
- added: [],
1614
- removed: []
1615
- },
1616
- settings: {
1617
- changes: []
1618
- }
1619
- };
1897
+ ],
1898
+ "expected_impact": { "task-id": "+15% \u2014 explanation" }
1620
1899
  }
1621
- var init_types = __esm({
1622
- "src/ir/types.ts"() {
1623
- "use strict";
1900
+
1901
+ ## MCP Configuration
1902
+ You can also mutate .mcp.json to add, remove, or reconfigure MCP servers.
1903
+ Treat .mcp.json like any other harness file \u2014 propose changes when traces show
1904
+ the agent lacks a tool it needs, or has tools that add noise without benefit.
1905
+
1906
+ ## Rules
1907
+ - Propose AT MOST 3 mutations per iteration. Fewer, targeted mutations are more stable than many broad ones.
1908
+ - Each mutation must have a clear rationale tied to a specific trace observation.
1909
+ - Never remove something that's working for another task.
1910
+ - If a previous iteration's change caused a regression, REVERT it.
1911
+ - Consider both additions AND removals. Remove sections that add noise without improving task performance.
1912
+ - Bloated harnesses hurt performance \u2014 trim what isn't earning its keep.
1913
+
1914
+ ## Anti-Gaming (CRITICAL)
1915
+ - Mutations must improve GENERAL-PURPOSE development quality, not target specific eval criteria.
1916
+ - You do NOT have access to scoring rubrics or expected outcomes. Diagnose problems from traces only.
1917
+ - Do NOT add over-specified rules that restate existing conventions with stronger emphasis (e.g., changing "use chalk.green for success" to "MUST use chalk.green, no exceptions"). If a convention already exists, trust it.
1918
+ - Do NOT add rules that only apply to a narrow eval scenario (e.g., write permissions for a specific directory just because one task needed it).
1919
+ - Ask: "Would this mutation help a developer working on ANY task in this project?" If not, don't propose it.
1920
+
1921
+ Return ONLY valid JSON.`;
1922
+ STDOUT_TRUNCATION_LIMIT = 1e3;
1923
+ MAX_CONTEXT_CHARS = 1e5;
1624
1924
  }
1625
1925
  });
1626
1926
 
@@ -1718,10 +2018,10 @@ function parseClaudeMd(content) {
1718
2018
  order: 0
1719
2019
  });
1720
2020
  for (let i = 1; i < chunks.length; i++) {
1721
- const chunk = chunks[i];
1722
- const newlineIdx = chunk.indexOf("\n");
1723
- const heading = newlineIdx >= 0 ? chunk.slice(0, newlineIdx).trim() : chunk.trim();
1724
- const sectionContent = newlineIdx >= 0 ? chunk.slice(newlineIdx + 1).trim() : "";
2021
+ const chunk3 = chunks[i];
2022
+ const newlineIdx = chunk3.indexOf("\n");
2023
+ const heading = newlineIdx >= 0 ? chunk3.slice(0, newlineIdx).trim() : chunk3.trim();
2024
+ const sectionContent = newlineIdx >= 0 ? chunk3.slice(newlineIdx + 1).trim() : "";
1725
2025
  const sectionId = resolveSectionId(heading);
1726
2026
  sections.push({
1727
2027
  id: sectionId,
@@ -2106,7 +2406,7 @@ var SECTION_ID_MAP;
2106
2406
  var init_parser = __esm({
2107
2407
  "src/ir/parser.ts"() {
2108
2408
  "use strict";
2109
- init_types();
2409
+ init_types2();
2110
2410
  SECTION_ID_MAP = [
2111
2411
  { pattern: /^(purpose|about|what)\b/i, id: "purpose" },
2112
2412
  { pattern: /^(tech\s*stack|technology|stack)\b/i, id: "tech-stack" },
@@ -2292,7 +2592,7 @@ var COMMANDS_PATH_RE, RULES_PATH_RE, AGENTS_PATH_RE;
2292
2592
  var init_translate = __esm({
2293
2593
  "src/ir/translate.ts"() {
2294
2594
  "use strict";
2295
- init_types();
2595
+ init_types2();
2296
2596
  init_parser();
2297
2597
  COMMANDS_PATH_RE = /^commands\/([^/]+?)(?:\.md)?$/;
2298
2598
  RULES_PATH_RE = /^rules\/([^/]+?)(?:\.md)?$/;
@@ -2471,237 +2771,53 @@ function applyIRMutation(ir, mutation) {
2471
2771
  }
2472
2772
  case "remove_agent": {
2473
2773
  if (!agentExists(ir, mutation.name)) {
2474
- throw new Error(`Agent '${mutation.name}' not found`);
2475
- }
2476
- return {
2477
- ...ir,
2478
- agents: ir.agents.filter((a) => a.name !== mutation.name)
2479
- };
2480
- }
2481
- // -- MCP Servers -------------------------------------------------------
2482
- case "add_mcp_server": {
2483
- if (mcpServerExists(ir, mutation.server.id)) {
2484
- throw new Error(`MCP server '${mutation.server.id}' already exists`);
2485
- }
2486
- return {
2487
- ...ir,
2488
- mcpServers: [...ir.mcpServers, { ...mutation.server }]
2489
- };
2490
- }
2491
- case "remove_mcp_server": {
2492
- if (!mcpServerExists(ir, mutation.id)) {
2493
- throw new Error(`MCP server '${mutation.id}' not found`);
2494
- }
2495
- return {
2496
- ...ir,
2497
- mcpServers: ir.mcpServers.filter((s) => s.id !== mutation.id)
2498
- };
2499
- }
2500
- // -- Settings ----------------------------------------------------------
2501
- case "update_settings": {
2502
- return {
2503
- ...ir,
2504
- settings: applySettingsUpdate(ir.settings, mutation.path, mutation.value)
2505
- };
2506
- }
2507
- // -- Raw text (legacy fallback) ----------------------------------------
2508
- case "raw_text": {
2509
- console.warn(
2510
- "raw_text mutation is a legacy fallback \u2014 the text operation will be applied during rendering"
2511
- );
2512
- return { ...ir };
2513
- }
2514
- }
2515
- }
2516
- var STRUCTURED_SETTINGS_KEYS;
2517
- var init_mutations = __esm({
2518
- "src/ir/mutations.ts"() {
2519
- "use strict";
2520
- STRUCTURED_SETTINGS_KEYS = /* @__PURE__ */ new Set(["statusLine", "hooks", "denyPatterns"]);
2521
- }
2522
- });
2523
-
2524
- // src/ir/renderer.ts
2525
- function renderClaudeMd(_meta, sections) {
2526
- const sorted = [...sections].sort((a, b) => a.order - b.order);
2527
- const blocks = [];
2528
- for (const section of sorted) {
2529
- if (section.heading && section.content) {
2530
- blocks.push(`${section.heading}
2531
-
2532
- ${section.content}`);
2533
- } else if (section.heading) {
2534
- blocks.push(section.heading);
2535
- } else if (section.content) {
2536
- blocks.push(section.content);
2537
- }
2538
- }
2539
- if (blocks.length === 0) {
2540
- return "\n";
2541
- }
2542
- return blocks.join("\n\n") + "\n";
2543
- }
2544
- function renderSettings(settings) {
2545
- const result = JSON.parse(
2546
- JSON.stringify(settings.raw)
2547
- );
2548
- if (settings.denyPatterns && settings.denyPatterns.length > 0) {
2549
- const permissions = result["permissions"] ?? {};
2550
- permissions["deny"] = settings.denyPatterns;
2551
- result["permissions"] = permissions;
2552
- }
2553
- if (settings.statusLine) {
2554
- result["statusLine"] = settings.statusLine;
2555
- }
2556
- const hookEvents = [
2557
- "PreToolUse",
2558
- "PostToolUse",
2559
- "UserPromptSubmit",
2560
- "SessionStart",
2561
- "PostCompact"
2562
- ];
2563
- const hooksObj = {};
2564
- let hasHooks = false;
2565
- for (const event of hookEvents) {
2566
- const entries = settings.hooks[event];
2567
- if (entries && entries.length > 0) {
2568
- hooksObj[event] = entries;
2569
- hasHooks = true;
2570
- }
2571
- }
2572
- if (hasHooks) {
2573
- result["hooks"] = hooksObj;
2574
- }
2575
- return JSON.stringify(result, null, 2) + "\n";
2576
- }
2577
- function renderMcpConfig(servers) {
2578
- if (servers.length === 0) {
2579
- return "";
2580
- }
2581
- const mcpServers = {};
2582
- for (const server of servers) {
2583
- const entry = {
2584
- command: server.command,
2585
- args: server.args
2586
- };
2587
- if (server.env && Object.keys(server.env).length > 0) {
2588
- entry["env"] = server.env;
2589
- }
2590
- mcpServers[server.id] = entry;
2591
- }
2592
- return JSON.stringify({ mcpServers }, null, 2) + "\n";
2593
- }
2594
- function renderRuleWithFrontmatter(rule) {
2595
- if (!rule.paths || rule.paths.length === 0) {
2596
- return rule.content;
2597
- }
2598
- const yamlLines = ["---", "paths:"];
2599
- for (const p of rule.paths) {
2600
- yamlLines.push(` - ${p}`);
2601
- }
2602
- yamlLines.push("---");
2603
- return yamlLines.join("\n") + "\n\n" + rule.content;
2604
- }
2605
- function renderAgentWithFrontmatter(agent) {
2606
- const hasModel = agent.model !== void 0;
2607
- const hasDisallowed = agent.disallowedTools !== void 0 && agent.disallowedTools.length > 0;
2608
- const hasRouting = agent.modelRouting !== void 0;
2609
- const hasExtra = agent.extraFrontmatter !== void 0 && Object.keys(agent.extraFrontmatter).length > 0;
2610
- if (!hasModel && !hasDisallowed && !hasRouting && !hasExtra) {
2611
- return agent.content;
2612
- }
2613
- const yamlLines = ["---"];
2614
- if (hasModel) {
2615
- yamlLines.push(`model: ${agent.model}`);
2616
- }
2617
- if (hasDisallowed) {
2618
- yamlLines.push("disallowedTools:");
2619
- for (const tool of agent.disallowedTools) {
2620
- yamlLines.push(` - ${tool}`);
2621
- }
2622
- }
2623
- if (hasRouting) {
2624
- yamlLines.push("modelRouting:");
2625
- yamlLines.push(` default: ${agent.modelRouting.default}`);
2626
- if (agent.modelRouting.escalateTo) {
2627
- yamlLines.push(` escalateTo: ${agent.modelRouting.escalateTo}`);
2628
- }
2629
- if (agent.modelRouting.escalateWhen) {
2630
- yamlLines.push(` escalateWhen: ${agent.modelRouting.escalateWhen}`);
2631
- }
2632
- }
2633
- if (hasExtra) {
2634
- for (const [key, value] of Object.entries(agent.extraFrontmatter)) {
2635
- if (Array.isArray(value)) {
2636
- yamlLines.push(`${key}:`);
2637
- for (const item of value) {
2638
- yamlLines.push(` - ${String(item)}`);
2639
- }
2640
- } else if (typeof value === "object" && value !== null) {
2641
- yamlLines.push(`${key}:`);
2642
- for (const [subKey, subVal] of Object.entries(value)) {
2643
- yamlLines.push(` ${subKey}: ${String(subVal)}`);
2644
- }
2645
- } else {
2646
- yamlLines.push(`${key}: ${String(value)}`);
2647
- }
2648
- }
2649
- }
2650
- yamlLines.push("---");
2651
- return yamlLines.join("\n") + "\n\n" + agent.content;
2652
- }
2653
- function settingsHasContent(settings) {
2654
- if (settings.statusLine) return true;
2655
- if (settings.denyPatterns && settings.denyPatterns.length > 0) return true;
2656
- if (Object.keys(settings.raw).length > 0) return true;
2657
- const hookEvents = [
2658
- "PreToolUse",
2659
- "PostToolUse",
2660
- "UserPromptSubmit",
2661
- "SessionStart",
2662
- "PostCompact"
2663
- ];
2664
- for (const event of hookEvents) {
2665
- const entries = settings.hooks[event];
2666
- if (entries && entries.length > 0) return true;
2667
- }
2668
- return false;
2669
- }
2670
- function renderHarness(ir) {
2671
- const files = /* @__PURE__ */ new Map();
2672
- if (ir.sections.length > 0 || ir.meta.name) {
2673
- files.set("CLAUDE.md", renderClaudeMd(ir.meta, ir.sections));
2674
- }
2675
- if (settingsHasContent(ir.settings)) {
2676
- files.set("settings.json", renderSettings(ir.settings));
2677
- }
2678
- for (const cmd of ir.commands) {
2679
- files.set(`commands/${cmd.name}.md`, cmd.content);
2680
- }
2681
- for (const rule of ir.rules) {
2682
- files.set(`rules/${rule.name}.md`, renderRuleWithFrontmatter(rule));
2683
- }
2684
- for (const agent of ir.agents) {
2685
- files.set(`agents/${agent.name}.md`, renderAgentWithFrontmatter(agent));
2686
- }
2687
- for (const skill of ir.skills) {
2688
- files.set(`skills/${skill.name}.md`, skill.content);
2689
- }
2690
- for (const doc of ir.docs) {
2691
- files.set(`docs/${doc.name}.md`, doc.content);
2692
- }
2693
- for (const hook of ir.hooks) {
2694
- files.set(`hooks/${hook.name}.mjs`, hook.content);
2695
- }
2696
- const mcpContent = renderMcpConfig(ir.mcpServers);
2697
- if (mcpContent) {
2698
- files.set(".mcp.json", mcpContent);
2774
+ throw new Error(`Agent '${mutation.name}' not found`);
2775
+ }
2776
+ return {
2777
+ ...ir,
2778
+ agents: ir.agents.filter((a) => a.name !== mutation.name)
2779
+ };
2780
+ }
2781
+ // -- MCP Servers -------------------------------------------------------
2782
+ case "add_mcp_server": {
2783
+ if (mcpServerExists(ir, mutation.server.id)) {
2784
+ throw new Error(`MCP server '${mutation.server.id}' already exists`);
2785
+ }
2786
+ return {
2787
+ ...ir,
2788
+ mcpServers: [...ir.mcpServers, { ...mutation.server }]
2789
+ };
2790
+ }
2791
+ case "remove_mcp_server": {
2792
+ if (!mcpServerExists(ir, mutation.id)) {
2793
+ throw new Error(`MCP server '${mutation.id}' not found`);
2794
+ }
2795
+ return {
2796
+ ...ir,
2797
+ mcpServers: ir.mcpServers.filter((s) => s.id !== mutation.id)
2798
+ };
2799
+ }
2800
+ // -- Settings ----------------------------------------------------------
2801
+ case "update_settings": {
2802
+ return {
2803
+ ...ir,
2804
+ settings: applySettingsUpdate(ir.settings, mutation.path, mutation.value)
2805
+ };
2806
+ }
2807
+ // -- Raw text (legacy fallback) ----------------------------------------
2808
+ case "raw_text": {
2809
+ console.warn(
2810
+ "raw_text mutation is a legacy fallback \u2014 the text operation will be applied during rendering"
2811
+ );
2812
+ return { ...ir };
2813
+ }
2699
2814
  }
2700
- return files;
2701
2815
  }
2702
- var init_renderer = __esm({
2703
- "src/ir/renderer.ts"() {
2816
+ var STRUCTURED_SETTINGS_KEYS;
2817
+ var init_mutations = __esm({
2818
+ "src/ir/mutations.ts"() {
2704
2819
  "use strict";
2820
+ STRUCTURED_SETTINGS_KEYS = /* @__PURE__ */ new Set(["statusLine", "hooks", "denyPatterns"]);
2705
2821
  }
2706
2822
  });
2707
2823
 
@@ -2964,7 +3080,7 @@ function deepEqual(a, b) {
2964
3080
  var init_diff = __esm({
2965
3081
  "src/ir/diff.ts"() {
2966
3082
  "use strict";
2967
- init_types();
3083
+ init_types2();
2968
3084
  }
2969
3085
  });
2970
3086
 
@@ -4586,7 +4702,7 @@ function estimateTime(model, intent) {
4586
4702
  "qwen": 10
4587
4703
  };
4588
4704
  const basePerPass = Object.entries(perPass).find(([k]) => model.toLowerCase().includes(k))?.[1] ?? 20;
4589
- const totalBase = basePerPass * 2;
4705
+ const totalBase = basePerPass * 3;
4590
4706
  if (isComplex) {
4591
4707
  const low = Math.floor(totalBase * 1.5);
4592
4708
  const high = Math.floor(totalBase * 4);
@@ -4636,13 +4752,7 @@ function createProgressRenderer() {
4636
4752
  }
4637
4753
  currentPhase = "";
4638
4754
  } else if (progress.status === "warning") {
4639
- const lastIdx = lines.length - 1;
4640
- if (lastIdx >= 0) {
4641
- lines[lastIdx] = ` ${chalk.yellow("\u26A0")} ${progress.message}`;
4642
- }
4643
- currentPhase = progress.phase;
4644
- phaseStart = Date.now();
4645
- lines.push(` ${warmStone("\u25D0")} Retrying in concise mode... ${chalk.dim("[0s]")}`);
4755
+ lines.push(` ${chalk.yellow("\u26A0")} ${progress.message}`);
4646
4756
  }
4647
4757
  render();
4648
4758
  },
@@ -4910,372 +5020,10 @@ You must output a JSON object matching the SkeletonSpec schema.
4910
5020
  - MCP servers: maximum 6. Prefer fewer.
4911
5021
  - Skills: maximum 3. Only include directly relevant ones.
4912
5022
  - Agents: maximum 5. Orchestration pipeline (/develop) agents.
4913
- - Hooks: maximum 5 (auto-format, block-destructive, PostCompact, memory-persistence, plus one contextual).
4914
-
4915
- If the workflow doesn't clearly need a tool, DO NOT include it.
4916
- Each MCP server costs 500-2000 tokens of context window.
4917
-
4918
- ## Output Schema
4919
-
4920
- Return ONLY valid JSON matching this structure:
4921
-
4922
- \`\`\`json
4923
- {
4924
- "name": "short-kebab-case-name",
4925
- "description": "One-line description",
4926
- "tools": [
4927
- { "tool_id": "id-from-registry", "reason": "why this tool fits" }
4928
- ],
4929
- "outline": {
4930
- "tech_stack": ["Python", "pandas"],
4931
- "workflow_type": "data-analysis",
4932
- "key_commands": ["ingest", "analyze", "report"],
4933
- "custom_rules": ["data-integrity"],
4934
- "custom_agents": ["data-reviewer"],
4935
- "custom_skills": ["ms-data-analysis"]
4936
- }
4937
- }
4938
- \`\`\`
4939
-
4940
- Return ONLY valid JSON. No markdown fences. No text outside the JSON.`;
4941
- var HARNESS_PROMPT = `You are the Kairn harness compiler. Your job is to generate the full environment content from a project skeleton.
4942
-
4943
- You will receive:
4944
- 1. The skeleton (tool selections + project outline)
4945
- 2. The user's original intent
4946
-
4947
- You must generate all harness content: CLAUDE.md, commands, rules, agents, skills, and docs.
4948
-
4949
- ## Core Principles
4950
-
4951
- - **Workflow-specific, not generic.** Every instruction, command, and rule must relate to the user's actual workflow.
4952
- - **Concise CLAUDE.md.** Under 150 lines. No generic text like "be helpful." Include build/test commands, reference docs/ and skills/.
4953
- - **Security by default.** Always include deny rules for destructive commands and secret file access.
4954
-
4955
- ## CLAUDE.md Template (mandatory structure)
4956
-
4957
- The \`claude_md\` field MUST follow this exact structure (max 150 lines):
4958
-
4959
- \`\`\`
4960
- # {Project Name}
4961
-
4962
- ## Purpose
4963
- {one-line description}
4964
-
4965
- ## Tech Stack
4966
- {bullet list of frameworks/languages}
4967
-
4968
- ## Commands
4969
- {concrete build/test/lint/dev commands}
4970
-
4971
- ## Architecture
4972
- {brief folder structure, max 10 lines}
4973
-
4974
- ## Conventions
4975
- {3-5 specific coding rules}
4976
-
4977
- ## Key Commands
4978
- {list /project: commands with descriptions}
4979
-
4980
- ## Output
4981
- {where results go, key files}
4982
-
4983
- ## Verification
4984
- After implementing any change, verify it works:
4985
- - {build command} \u2014 must pass with no errors
4986
- - {test command} \u2014 all tests must pass
4987
- - {lint command} \u2014 no warnings or errors
4988
- - {type check command} \u2014 no type errors
4989
-
4990
- If any verification step fails, fix the issue before moving on.
4991
- Do NOT skip verification steps.
4992
-
4993
- ## Known Gotchas
4994
- <!-- After any correction, add it here: "Update CLAUDE.md so you don't make that mistake again." -->
4995
- <!-- Prune this section when it exceeds 10 items \u2014 keep only the recurring ones. -->
4996
- - (none yet \u2014 this section grows as you work)
4997
-
4998
- ## Debugging
4999
- When debugging, paste raw error output. Don't summarize \u2014 Claude works better with raw data.
5000
- Use subagents for deep investigation to keep main context clean.
5001
-
5002
- ## Git Workflow
5003
- - Prefer small, focused commits (one feature or fix per commit)
5004
- - Use conventional commits: feat:, fix:, docs:, refactor:, test:
5005
- - Target < 200 lines per PR when possible
5006
-
5007
- ## Engineering Standards
5008
- - Lead with answers over reasoning. Be concise.
5009
- - Use absolute file paths in all references.
5010
- - No filler, no inner monologue, no time estimates.
5011
- - Produce load-bearing code \u2014 every line of output should be actionable.
5012
-
5013
- ## Tool Usage Policy
5014
- - Prefer Edit tool over sed/awk for file modifications
5015
- - Prefer Grep tool over rg for searching
5016
- - Prefer Read tool over cat for file reading
5017
- - Reserve Bash for: builds, installs, git, network, processes
5018
- - Read and understand existing code before modifying
5019
- - Delete unused code completely \u2014 no compatibility shims
5020
-
5021
- ## Code Philosophy
5022
- - Do not create abstractions for one-time operations
5023
- - Complete the task fully \u2014 don't gold-plate, but don't leave it half-done
5024
- - Prefer editing existing files over creating new ones
5025
-
5026
- ## First Turn Protocol
5027
-
5028
- At the start of every session, before doing ANY work:
5029
- 1. Run \`pwd && ls -la && git status --short\` to orient yourself
5030
- 2. Check relevant runtimes (e.g. \`node --version\`, \`python3 --version\` \u2014 pick what fits this project)
5031
- 3. Read any task-tracking files (docs/SPRINT.md, docs/DECISIONS.md)
5032
- 4. Summarize what you see in 2-3 lines, then proceed
5033
-
5034
- This saves 2-5 exploratory turns. Never ask "what files are here?" \u2014 look first.
5035
-
5036
- ## Sprint Contract
5037
-
5038
- Before implementing, confirm acceptance criteria exist in docs/SPRINT.md.
5039
- Each criterion must be numbered, testable, and independently verifiable.
5040
- After implementing, verify EACH criterion individually. Do not mark done until all pass.
5041
-
5042
- ## Completion Standards
5043
-
5044
- Never mark a task "done" without running the Completion Verification checklist.
5045
- Tests passing is necessary but not sufficient \u2014 also verify requirements coverage,
5046
- state cleanliness, and review changes from the perspective of a test engineer,
5047
- code reviewer, and the requesting user.
5048
- \`\`\`
5049
-
5050
- Do not add generic filler. Every line must be specific to the user's workflow.
5051
-
5052
- ## What You Must Always Include
5053
-
5054
- 1. A concise, workflow-specific \`claude_md\` (the CLAUDE.md content)
5055
- 2. A \`/project:help\` command that explains the environment
5056
- 3. A \`docs/DECISIONS.md\` file for architectural decisions
5057
- 4. A \`docs/LEARNINGS.md\` file for non-obvious discoveries
5058
- 5. A \`rules/continuity.md\` rule encouraging updates to DECISIONS.md and LEARNINGS.md
5059
- 6. A \`rules/security.md\` rule with essential security instructions
5060
- 7. settings.json with deny rules for \`rm -rf\`, \`curl|sh\`, reading \`.env\` and \`secrets/\`
5061
- 8. A \`/project:status\` command for code projects (uses ! for live git/SPRINT.md output)
5062
- 9. A \`/project:fix\` command for code projects (uses $ARGUMENTS for issue number)
5063
- 10. A \`docs/SPRINT.md\` file as the living spec/plan (replaces TODO.md \u2014 acceptance criteria, verification steps)
5064
- 11. A "Verification" section in CLAUDE.md with concrete verify commands for the project
5065
- 12. A "Known Gotchas" section in CLAUDE.md (starts empty, grows with corrections)
5066
- 13. A "Debugging" section in CLAUDE.md (2 lines: paste raw errors, use subagents)
5067
- 14. A "Git Workflow" section in CLAUDE.md (3 rules: small commits, conventional format, <200 lines PR)
5068
- 15. "Engineering Standards", "Tool Usage Policy", and "Code Philosophy" sections in CLAUDE.md
5069
- 16. A "First Turn Protocol" section in CLAUDE.md (orient before working: pwd, ls, git status, check relevant runtimes, read task files)
5070
- 17. A "Completion Standards" section in CLAUDE.md (never mark done without verifying: requirements met, tests passing, no debug artifacts, reviewed from 3 perspectives)
5071
- 18. A "Sprint Contract" section in CLAUDE.md (confirm acceptance criteria exist before implementing, verify each criterion after)
5072
-
5073
- ## Shell-Integrated Commands
5074
-
5075
- Commands that reference live project state should use Claude Code's \`!\` prefix for shell output:
5076
-
5077
- \`\`\`markdown
5078
- # Example: .claude/commands/review.md
5079
- Review the staged changes for quality and security:
5080
-
5081
- !git diff --staged
5082
-
5083
- Run tests and check for failures:
5084
-
5085
- !npm test 2>&1 | tail -20
5086
-
5087
- Focus on: security, error handling, test coverage.
5088
- \`\`\`
5089
-
5090
- Use \`!\` when a command needs: git status, test results, build output, or file listings.
5091
-
5092
- ## Path-Scoped Rules
5093
-
5094
- For code projects with multiple domains (API, frontend, tests), generate path-scoped rules using YAML frontmatter:
5095
-
5096
- \`\`\`markdown
5097
- # Example: rules/api.md
5098
- ---
5099
- paths:
5100
- - "src/api/**"
5101
- - "src/routes/**"
5102
- ---
5103
- - All handlers return { data, error } shape
5104
- - Use Zod for request validation
5105
- - Log errors with request ID context
5106
- \`\`\`
5107
-
5108
- \`\`\`markdown
5109
- # Example: rules/testing.md
5110
- ---
5111
- paths:
5112
- - "tests/**"
5113
- - "**/*.test.*"
5114
- - "**/*.spec.*"
5115
- ---
5116
- - Use AAA pattern: Arrange-Act-Assert
5117
- - One assertion per test when possible
5118
- - Mock external dependencies, never real APIs
5119
- \`\`\`
5120
-
5121
- Keep \`security.md\` and \`continuity.md\` as unconditional (no paths frontmatter).
5122
- Only generate scoped rules when the workflow involves multiple code domains.
5123
-
5124
- ## Hooks
5125
-
5126
- Generate hooks in settings.json based on project type:
5127
-
5128
- **All code projects** \u2014 block destructive commands, credential leaks, injection, and network exfiltration:
5129
- \`\`\`json
5130
- {
5131
- "hooks": {
5132
- "PreToolUse": [
5133
- {
5134
- "matcher": "Bash",
5135
- "hooks": [{
5136
- "type": "command",
5137
- "command": "CMD=$(cat | jq -r '.tool_input.command // empty') && echo \\"$CMD\\" | grep -qiE 'rm\\\\s+-rf\\\\s+/|DROP\\\\s+(TABLE|DATABASE)|curl.*\\\\|\\\\s*sh|:(){ :|:& };:|git\\\\s+push.*--force(?!-with-lease)|ch(mod|own).*-R\\\\s+/|npm\\\\s+publish(?!.*--dry-run)|(api[_-]?key|secret|token|password)\\\\s*[:=]|AKIA[0-9A-Z]{16}|BEGIN.*PRIVATE\\\\s+KEY|;\\\\s*(DROP|DELETE|ALTER|TRUNCATE)\\\\s+|\\\\.\\\\./\\\\.\\\\./\\\\.\\\\./|nc\\\\s+.*-e|/dev/tcp/|bash\\\\s+-i|curl.*-d.*@|wget.*--post-file' && echo 'Blocked dangerous command' >&2 && exit 2 || true"
5138
- }]
5139
- }
5140
- ]
5141
- }
5142
- }
5143
- \`\`\`
5144
-
5145
- **Projects with Prettier/ESLint/Black** \u2014 auto-format on write:
5146
- \`\`\`json
5147
- {
5148
- "hooks": {
5149
- "PostToolUse": [{
5150
- "matcher": "Edit|Write",
5151
- "hooks": [{
5152
- "type": "command",
5153
- "command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && [ -n \\"$FILE\\" ] && npx prettier --write \\"$FILE\\" 2>/dev/null || true"
5154
- }]
5155
- }]
5156
- }
5157
- }
5158
- \`\`\`
5159
-
5160
- Merge hooks into the \`settings\` object alongside permissions. Choose the formatter hook based on detected dependencies (Prettier \u2192 prettier, ESLint \u2192 eslint, Black \u2192 black).
5161
-
5162
- ## PostCompact Hook
5163
-
5164
- All projects should include a PostCompact hook to restore context after compaction:
5165
-
5166
- \`\`\`json
5167
- {
5168
- "hooks": {
5169
- "PostCompact": [{
5170
- "matcher": "",
5171
- "hooks": [{
5172
- "type": "prompt",
5173
- "prompt": "Re-read CLAUDE.md and docs/SPRINT.md (if it exists) to restore project context after compaction."
5174
- }]
5175
- }]
5176
- }
5177
- }
5178
- \`\`\`
5023
+ - Hooks: maximum 5 (auto-format, block-destructive, PostCompact, memory-persistence, plus one contextual).
5179
5024
 
5180
- Merge this into the settings hooks alongside the PreToolUse and PostToolUse hooks.
5181
-
5182
- For long-running sessions (>2 hours or >3 compactions), prefer "Full Reset" over re-inject:
5183
- replace the prompt-type PostCompact hook with a command-type hook that pipes CLAUDE.md + SPRINT.md + DECISIONS.md content directly into additionalContext.
5184
-
5185
- ## Memory Persistence Hooks
5186
-
5187
- For projects with multi-session workflows, include SessionStart/End hooks that persist context to \`.claude/memory.json\`:
5188
- - **SessionEnd:** Save recent decisions, sprint status, and known gotchas to \`.claude/memory.json\`
5189
- - **SessionStart:** Load \`.claude/memory.json\` and inject as additionalContext
5190
-
5191
- This ensures accumulated project knowledge survives session boundaries.
5192
-
5193
- ## Persistence Routing Hook
5194
-
5195
- For code projects with autonomy level 3+, include a \`UserPromptSubmit\` hook that routes complex tasks through \`/project:persist\`.
5196
- Generate \`.claude/hooks/persist-router.mjs\` (the routing logic) and add a \`UserPromptSubmit\` hook entry in settings.json that invokes it.
5197
- Also add \`"persistence_routing": "auto"\` (level 3-4) or \`"persistence_routing": "manual"\` (level 1-2) to settings.json.
5198
-
5199
- ## For Code Projects, Additionally Include
5200
-
5201
- - \`/project:plan\` command (plan before coding)
5202
- - \`/project:review\` command (review changes)
5203
- - \`/project:test\` command (run and fix tests)
5204
- - \`/project:commit\` command (conventional commits)
5205
- - \`/project:status\` command (live git status, recent commits, SPRINT.md overview using ! prefix)
5206
- - \`/project:fix\` command (takes $ARGUMENTS as issue number, plans fix, implements, tests, commits)
5207
- - \`/project:sprint\` command (define acceptance criteria before coding, writes to docs/SPRINT.md)
5208
- - \`/project:develop\` command (full development pipeline \u2014 orchestrates @architect \u2192 @planner \u2192 @implementer \u2192 @verifier \u2192 @fixer \u2192 @grill \u2192 @doc-updater through spec, plan, TDD implement, review, and doc update phases). Phase 4 (Verify) MUST validate EACH acceptance criterion from docs/SPRINT.md individually, reporting PASS/FAIL per item as a contract scorecard. MUST include a Phase 7 "Completion Gate" that runs a Completion Verification checklist before marking the feature done: re-read original requirements, confirm each is met with evidence, run test suite + lint/typecheck, review git diff for unexpected changes or debug artifacts, answer 3 perspective questions (test engineer, code reviewer, requesting user). If ANY check fails, loop back to fix before completing.
5209
- - A TDD skill using the 3-phase isolation pattern (RED \u2192 GREEN \u2192 REFACTOR):
5210
- - RED: Write failing test only. Verify it FAILS.
5211
- - GREEN: Write MINIMUM code to pass. Nothing extra.
5212
- - REFACTOR: Improve while keeping tests green.
5213
- Rules: never write tests and implementation in same step, AAA pattern, one assertion per test.
5214
- - A multi-agent QA pipeline:
5215
- - \`@qa-orchestrator\` (sonnet) \u2014 delegates to linter and e2e-tester, compiles QA report
5216
- - \`@linter\` (haiku) \u2014 runs formatters, linters, security scanners
5217
- - \`@e2e-tester\` (sonnet, only when Playwright is in tools) \u2014 browser-based QA via Playwright
5218
- - A "Model Selection" section in generated agents:
5219
- \`\`\`
5220
- ## Model Selection (all agents)
5221
- - Haiku: simple file edits, linting, formatting, doc updates (<50 lines changed)
5222
- - Sonnet: implementation, testing, debugging, code review (50-500 lines)
5223
- - Opus: architecture decisions, spec writing, complex refactors (>500 lines or cross-cutting)
5224
- Default: Sonnet. Only escalate to Opus when the task involves multi-file architecture or ambiguous requirements.
5225
- \`\`\`
5226
- - Development pipeline agents (used by /project:develop). Each agent should include a modelRouting field in its YAML frontmatter:
5227
- - \`@architect\` (default: opus) \u2014 conducts spec interview with user, writes confirmed spec to docs/SPRINT.md with numbered acceptance criteria. Your spec is a CONTRACT \u2014 the verifier will check every criterion. Vague criteria = guaranteed rework.
5228
- - \`@planner\` (default: sonnet, escalate to opus for cross-cutting changes) \u2014 reads spec and codebase, creates step-by-step implementation plan in docs/PLAN.md
5229
- - \`@implementer\` (default: sonnet, escalate to opus for cross-cutting changes) \u2014 TDD-focused implementation, writes failing tests then minimum code to pass
5230
- - \`@fixer\` (default: sonnet, use haiku for single-file fixes) \u2014 targeted bug fixing from verifier/review feedback
5231
- - \`@doc-updater\` (default: haiku) \u2014 extracts decisions and learnings from completed work, updates docs/DECISIONS.md and docs/LEARNINGS.md
5232
- - \`/project:spec\` command (interview-based spec creation \u2014 asks 5-8 questions one at a time, writes structured spec to docs/SPRINT.md with ## Acceptance Criteria containing 3-8 numbered, testable conditions. Each criterion must be independently verifiable. Does NOT start coding until confirmed)
5233
- - \`/project:prove\` command (runs tests, shows git diff vs main, rates confidence HIGH/MEDIUM/LOW with evidence)
5234
- - \`/project:grill\` command (adversarial code review \u2014 challenges each change with "why this approach?", "what if X input?", rates BLOCKER/SHOULD-FIX/NITPICK, blocks until BLOCKERs resolved)
5235
- - \`/project:reset\` command (reads DECISIONS.md and LEARNINGS.md, proposes clean restart, stashes current work, implements elegant solution)
5236
- - \`/project:persist\` command (persistent execution loop \u2014 reads acceptance criteria from docs/SPRINT.md, works criterion-by-criterion with structured progress tracking in .claude/progress.json, auto-retries on verification failure up to 3 times per criterion, delegates to @grill for review gate before completion, resumes from progress.json if session was interrupted). The command protocol:
5237
- 1. Load or initialize .claude/progress.json from docs/SPRINT.md numbered acceptance criteria
5238
- 2. For each incomplete criterion: implement, run verification (build/test/typecheck/lint), mark PASSED or retry (max 3 attempts per criterion, mark BLOCKED after 3 failures)
5239
- 3. After all criteria attempted: if any BLOCKED report which and why; if all PASSED proceed to review gate
5240
- 4. Review gate: delegate to @grill for adversarial review; fix blockers if found (max 1 fix cycle)
5241
- 5. Persist state: write final progress.json; include progress summary in memory.json for session resume
5242
- Resume protocol: when progress.json exists, skip PASSED criteria, resume from first non-PASSED criterion, carry forward failure notes from prior attempts.
5243
-
5244
- ## For Research Projects, Additionally Include
5245
-
5246
- - \`/project:research\` command (deep research on a topic)
5247
- - \`/project:summarize\` command (summarize findings)
5248
- - A research-synthesis skill
5249
- - A researcher agent
5250
- - Note: the Verification section in CLAUDE.md should adapt for research \u2014 e.g. "Verify all sources are cited" instead of build/test commands
5251
-
5252
- ## For Content/Writing Projects, Additionally Include
5253
-
5254
- - \`/project:draft\` command (write first draft)
5255
- - \`/project:edit\` command (review and improve writing)
5256
- - A writing-workflow skill
5257
-
5258
- ## Hermes Runtime
5259
-
5260
- When generating for Hermes runtime, the same EnvironmentSpec JSON is produced. The adapter layer handles conversion:
5261
- - MCP config entries \u2192 Hermes config.yaml mcp_servers
5262
- - Commands and skills \u2192 ~/.hermes/skills/ markdown files
5263
- - Rules \u2192 ~/.hermes/skills/rule-*.md files
5264
-
5265
- The LLM output format does not change. Adapter-level conversion happens post-compilation.
5266
-
5267
- ## Autonomy Levels
5268
-
5269
- The user may specify an autonomy level (1-4). This affects CLAUDE.md content:
5270
-
5271
- - **Level 1 (Guided):** Add a "Workflow" section showing recommended command flow (e.g., spec \u2192 sprint \u2192 plan \u2192 code \u2192 prove \u2192 grill \u2192 commit) and a "When to Use What" reference table.
5272
- - **Level 2 (Assisted):** Level 1 content + mention /project:loop in the workflow section and @pm in the agents section of CLAUDE.md.
5273
- - **Level 3 (Autonomous):** Level 2 content + mention /project:auto and worktree-based PR delivery workflow.
5274
- - **Level 4 (Full Auto):** Level 3 content + add a prominent warning section about autonomous operation.
5275
-
5276
- The autonomy-specific commands, agents, and hooks are injected post-compilation. Focus on tailoring the CLAUDE.md content and workflow guidance for the selected level.
5277
-
5278
- If no autonomy level is specified, assume Level 1 (Guided).
5025
+ If the workflow doesn't clearly need a tool, DO NOT include it.
5026
+ Each MCP server costs 500-2000 tokens of context window.
5279
5027
 
5280
5028
  ## Output Schema
5281
5029
 
@@ -5283,12 +5031,19 @@ Return ONLY valid JSON matching this structure:
5283
5031
 
5284
5032
  \`\`\`json
5285
5033
  {
5286
- "claude_md": "Full CLAUDE.md content (under 150 lines)",
5287
- "commands": { "help": "...", "develop": "...", "status": "...", "fix": "...", "sprint": "...", "spec": "...", "prove": "...", "grill": "...", "reset": "...", "persist": "..." },
5288
- "rules": { "continuity": "...", "security": "..." },
5289
- "agents": { "architect": "...", "planner": "...", "implementer": "...", "fixer": "...", "doc-updater": "...", "qa-orchestrator": "...", "linter": "...", "e2e-tester": "..." },
5290
- "skills": { "skill-name/SKILL": "..." },
5291
- "docs": { "DECISIONS": "...", "LEARNINGS": "...", "SPRINT": "..." }
5034
+ "name": "short-kebab-case-name",
5035
+ "description": "One-line description",
5036
+ "tools": [
5037
+ { "tool_id": "id-from-registry", "reason": "why this tool fits" }
5038
+ ],
5039
+ "outline": {
5040
+ "tech_stack": ["Python", "pandas"],
5041
+ "workflow_type": "data-analysis",
5042
+ "key_commands": ["ingest", "analyze", "report"],
5043
+ "custom_rules": ["data-integrity"],
5044
+ "custom_agents": ["data-reviewer"],
5045
+ "custom_skills": ["ms-data-analysis"]
5046
+ }
5292
5047
  }
5293
5048
  \`\`\`
5294
5049
 
@@ -5496,82 +5251,1192 @@ Return ONLY valid JSON matching this structure:
5496
5251
  }
5497
5252
  }
5498
5253
  }
5499
- \`\`\`
5254
+ \`\`\`
5255
+
5256
+ Do not include any text outside the JSON object. Do not wrap in markdown code fences.`;
5257
+ var CLARIFICATION_PROMPT = `You are helping a user define their project for environment compilation.
5258
+
5259
+ Given their initial description, generate 3-5 clarifying questions to understand:
5260
+ 1. Language and framework
5261
+ 2. What the project specifically does (be precise)
5262
+ 3. Primary workflow (build, research, write, analyze?)
5263
+ 4. Key dependencies or integrations
5264
+ 5. Target audience
5265
+
5266
+ For each question, provide a reasonable suggestion based on the description.
5267
+
5268
+ Output ONLY a JSON array:
5269
+ [
5270
+ { "question": "Language/framework?", "suggestion": "TypeScript + Node.js" },
5271
+ ...
5272
+ ]
5273
+
5274
+ Rules:
5275
+ - Suggestions should be reasonable guesses, clearly marked as suggestions
5276
+ - Keep questions short (under 10 words)
5277
+ - Maximum 5 questions
5278
+ - If the description is already very detailed, ask fewer questions`;
5279
+
5280
+ // src/registry/loader.ts
5281
+ import fs3 from "fs/promises";
5282
+ import path3 from "path";
5283
+ import { fileURLToPath as fileURLToPath2 } from "url";
5284
+ var __filename2 = fileURLToPath2(import.meta.url);
5285
+ var __dirname2 = path3.dirname(__filename2);
5286
+ async function loadBundledRegistry() {
5287
+ const candidates = [
5288
+ path3.resolve(__dirname2, "../registry/tools.json"),
5289
+ path3.resolve(__dirname2, "../src/registry/tools.json"),
5290
+ path3.resolve(__dirname2, "../../src/registry/tools.json")
5291
+ ];
5292
+ for (const candidate of candidates) {
5293
+ try {
5294
+ const data = await fs3.readFile(candidate, "utf-8");
5295
+ return JSON.parse(data);
5296
+ } catch {
5297
+ continue;
5298
+ }
5299
+ }
5300
+ throw new Error("Could not find tools.json registry");
5301
+ }
5302
+ async function loadUserRegistry() {
5303
+ try {
5304
+ const data = await fs3.readFile(getUserRegistryPath(), "utf-8");
5305
+ return JSON.parse(data);
5306
+ } catch {
5307
+ return [];
5308
+ }
5309
+ }
5310
+ async function saveUserRegistry(tools) {
5311
+ await fs3.writeFile(getUserRegistryPath(), JSON.stringify(tools, null, 2), "utf-8");
5312
+ }
5313
+ async function loadRegistry() {
5314
+ const bundled = await loadBundledRegistry();
5315
+ const user = await loadUserRegistry();
5316
+ if (user.length === 0) return bundled;
5317
+ const merged = /* @__PURE__ */ new Map();
5318
+ for (const tool of bundled) {
5319
+ merged.set(tool.id, tool);
5320
+ }
5321
+ for (const tool of user) {
5322
+ merged.set(tool.id, tool);
5323
+ }
5324
+ return Array.from(merged.values());
5325
+ }
5326
+
5327
+ // src/compiler/compile.ts
5328
+ init_providers();
5329
+ init_llm();
5330
+
5331
+ // src/compiler/plan.ts
5332
+ init_llm();
5333
+ init_types();
5334
+ var ORCHESTRATOR_PROMPT = `You are the Kairn compilation planner. Given a project skeleton and user intent, produce a CompilationPlan JSON that determines what to generate and in what order.
5335
+
5336
+ ## Agent Types
5337
+ - sections-writer: generates CLAUDE.md sections (Purpose, Tech Stack, Commands, Architecture, Conventions, Key Commands, Output, Verification, Known Gotchas, Debugging, Git Workflow, Engineering Standards)
5338
+ - rule-writer: generates .claude/rules/ files (security, continuity, plus project-specific)
5339
+ - doc-writer: generates .claude/docs/ files (DECISIONS, LEARNINGS, SPRINT)
5340
+ - command-writer: generates .claude/commands/ files (help, build, test, status, fix, develop, sprint, spec, prove, grill, persist, etc.)
5341
+ - agent-writer: generates .claude/agents/ files (architect, planner, implementer, fixer, doc-updater, qa-orchestrator, linter, e2e-tester)
5342
+ - skill-writer: generates .claude/skills/ files (tdd, etc.)
5343
+
5344
+ ## Phase Rules
5345
+ - Phase A (no dependencies): sections-writer, rule-writer, doc-writer
5346
+ - Phase B (depends on Phase A): command-writer, agent-writer, skill-writer (optional)
5347
+ - Phase C (depends on Phase B): reserved for linker (NOT included in plan \u2014 it runs separately)
5348
+
5349
+ ## Token Budgets
5350
+ - sections-writer: 4096, command-writer: 4096, agent-writer: 4096
5351
+ - rule-writer: 2048, doc-writer: 2048, skill-writer: 2048
5352
+
5353
+ ## Output Format
5354
+ Return ONLY valid JSON:
5355
+ {
5356
+ "project_context": "2-3 sentence project summary",
5357
+ "phases": [
5358
+ {
5359
+ "id": "phase-a",
5360
+ "agents": [
5361
+ { "agent": "sections-writer", "items": ["purpose", "tech-stack", "commands", ...], "max_tokens": 4096 },
5362
+ { "agent": "rule-writer", "items": ["security", "continuity", ...], "max_tokens": 2048 },
5363
+ { "agent": "doc-writer", "items": ["DECISIONS", "LEARNINGS", "SPRINT"], "max_tokens": 2048 }
5364
+ ],
5365
+ "dependsOn": []
5366
+ },
5367
+ {
5368
+ "id": "phase-b",
5369
+ "agents": [...],
5370
+ "dependsOn": ["phase-a"]
5371
+ }
5372
+ ]
5373
+ }`;
5374
+ var STANDARD_SECTION_ITEMS = [
5375
+ "purpose",
5376
+ "tech-stack",
5377
+ "commands",
5378
+ "architecture",
5379
+ "conventions",
5380
+ "key-commands",
5381
+ "output",
5382
+ "verification",
5383
+ "gotchas",
5384
+ "debugging",
5385
+ "git-workflow"
5386
+ ];
5387
+ var STANDARD_DOC_ITEMS = ["DECISIONS", "LEARNINGS", "SPRINT"];
5388
+ var TOKEN_BUDGETS = {
5389
+ "sections-writer": 4096,
5390
+ "command-writer": 4096,
5391
+ "agent-writer": 4096,
5392
+ "rule-writer": 2048,
5393
+ "doc-writer": 2048,
5394
+ "skill-writer": 2048
5395
+ };
5396
+ async function generatePlan(intent, skeleton, config) {
5397
+ try {
5398
+ const userMessage = buildPlanMessage(intent, skeleton);
5399
+ const response = await callLLM(config, userMessage, {
5400
+ systemPrompt: ORCHESTRATOR_PROMPT,
5401
+ maxTokens: 2048,
5402
+ cacheControl: true
5403
+ });
5404
+ const parsed = parsePlanResponse(response);
5405
+ return validatePlan(parsed);
5406
+ } catch {
5407
+ return generateDefaultPlan(skeleton);
5408
+ }
5409
+ }
5410
+ function generateDefaultPlan(skeleton) {
5411
+ const projectContext = `${skeleton.name}: ${skeleton.description}`;
5412
+ const sectionItems = [...STANDARD_SECTION_ITEMS];
5413
+ const ruleItems = ["security", "continuity", ...skeleton.outline.custom_rules];
5414
+ const docItems = [...STANDARD_DOC_ITEMS];
5415
+ const phaseA = {
5416
+ id: "phase-a",
5417
+ agents: [
5418
+ { agent: "sections-writer", items: sectionItems, max_tokens: TOKEN_BUDGETS["sections-writer"] },
5419
+ { agent: "rule-writer", items: ruleItems, max_tokens: TOKEN_BUDGETS["rule-writer"] },
5420
+ { agent: "doc-writer", items: docItems, max_tokens: TOKEN_BUDGETS["doc-writer"] }
5421
+ ],
5422
+ dependsOn: []
5423
+ };
5424
+ const commandItems = ["help", ...skeleton.outline.key_commands];
5425
+ const phaseBAgents = [
5426
+ { agent: "command-writer", items: commandItems, max_tokens: TOKEN_BUDGETS["command-writer"] }
5427
+ ];
5428
+ if (skeleton.outline.custom_agents.length > 0) {
5429
+ phaseBAgents.push({
5430
+ agent: "agent-writer",
5431
+ items: skeleton.outline.custom_agents,
5432
+ max_tokens: TOKEN_BUDGETS["agent-writer"]
5433
+ });
5434
+ }
5435
+ if (skeleton.outline.custom_skills.length > 0) {
5436
+ phaseBAgents.push({
5437
+ agent: "skill-writer",
5438
+ items: skeleton.outline.custom_skills,
5439
+ max_tokens: TOKEN_BUDGETS["skill-writer"]
5440
+ });
5441
+ }
5442
+ const phaseB = {
5443
+ id: "phase-b",
5444
+ agents: phaseBAgents,
5445
+ dependsOn: ["phase-a"]
5446
+ };
5447
+ return {
5448
+ project_context: projectContext,
5449
+ phases: [phaseA, phaseB]
5450
+ };
5451
+ }
5452
+ function buildPlanMessage(intent, skeleton) {
5453
+ return [
5454
+ "## Intent",
5455
+ intent,
5456
+ "",
5457
+ "## Skeleton",
5458
+ JSON.stringify(skeleton, null, 2),
5459
+ "",
5460
+ "Generate the CompilationPlan JSON now."
5461
+ ].join("\n");
5462
+ }
5463
+ function parsePlanResponse(text) {
5464
+ let cleaned = text.trim();
5465
+ if (cleaned.startsWith("```")) {
5466
+ cleaned = cleaned.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
5467
+ }
5468
+ const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
5469
+ if (!jsonMatch) {
5470
+ throw new Error("Orchestrator did not return valid JSON");
5471
+ }
5472
+ return JSON.parse(jsonMatch[0]);
5473
+ }
5474
+
5475
+ // src/compiler/batch.ts
5476
+ init_types();
5477
+ init_types2();
5478
+ function mergeIntoIR(ir, result) {
5479
+ switch (result.agent) {
5480
+ case "sections-writer":
5481
+ ir.sections.push(...result.sections);
5482
+ break;
5483
+ case "command-writer":
5484
+ ir.commands.push(...result.commands);
5485
+ break;
5486
+ case "agent-writer":
5487
+ ir.agents.push(...result.agents);
5488
+ break;
5489
+ case "rule-writer":
5490
+ ir.rules.push(...result.rules);
5491
+ break;
5492
+ case "doc-writer":
5493
+ ir.docs.push(...result.docs);
5494
+ break;
5495
+ case "skill-writer":
5496
+ ir.skills.push(...result.skills);
5497
+ break;
5498
+ }
5499
+ }
5500
+ async function runWithConcurrency(tasks, limit) {
5501
+ if (tasks.length === 0) return [];
5502
+ const results = new Array(tasks.length);
5503
+ let nextIndex = 0;
5504
+ let firstError = void 0;
5505
+ let hasError = false;
5506
+ async function runNext() {
5507
+ while (nextIndex < tasks.length) {
5508
+ const index = nextIndex++;
5509
+ try {
5510
+ results[index] = await tasks[index]();
5511
+ } catch (err) {
5512
+ if (!hasError) {
5513
+ hasError = true;
5514
+ firstError = err;
5515
+ }
5516
+ return;
5517
+ }
5518
+ }
5519
+ }
5520
+ const workers = [];
5521
+ const workerCount = Math.min(limit, tasks.length);
5522
+ for (let i = 0; i < workerCount; i++) {
5523
+ workers.push(runNext());
5524
+ }
5525
+ await Promise.all(workers);
5526
+ if (hasError) {
5527
+ throw firstError;
5528
+ }
5529
+ return results;
5530
+ }
5531
+ async function executePlan(plan, executeAgent, concurrency, onProgress) {
5532
+ if (plan.phases.length === 0) {
5533
+ return createEmptyIR();
5534
+ }
5535
+ const phaseIds = new Set(plan.phases.map((p) => p.id));
5536
+ for (const phase of plan.phases) {
5537
+ for (const dep of phase.dependsOn) {
5538
+ if (!phaseIds.has(dep)) {
5539
+ throw new Error(
5540
+ `Phase "${phase.id}" depends on unknown phase "${dep}"`
5541
+ );
5542
+ }
5543
+ }
5544
+ }
5545
+ const phaseIndex = /* @__PURE__ */ new Map();
5546
+ for (let i = 0; i < plan.phases.length; i++) {
5547
+ phaseIndex.set(plan.phases[i].id, i);
5548
+ }
5549
+ for (const phase of plan.phases) {
5550
+ const myIdx = phaseIndex.get(phase.id);
5551
+ for (const dep of phase.dependsOn) {
5552
+ const depIdx = phaseIndex.get(dep);
5553
+ if (depIdx !== void 0 && depIdx >= myIdx) {
5554
+ throw new Error(
5555
+ `Phase "${phase.id}" has a dependency ordering violation: depends on "${dep}" which is not in an earlier position`
5556
+ );
5557
+ }
5558
+ }
5559
+ }
5560
+ const ir = createEmptyIR();
5561
+ const completed = /* @__PURE__ */ new Set();
5562
+ for (const phase of plan.phases) {
5563
+ for (const dep of phase.dependsOn) {
5564
+ if (!completed.has(dep)) {
5565
+ throw new Error(
5566
+ `Phase "${phase.id}" depends on incomplete phase "${dep}"`
5567
+ );
5568
+ }
5569
+ }
5570
+ onProgress?.({
5571
+ phaseId: phase.id,
5572
+ status: "start",
5573
+ agentCount: phase.agents.length
5574
+ });
5575
+ const agentTasks = phase.agents.map((task) => async () => {
5576
+ try {
5577
+ return await executeAgent(task);
5578
+ } catch (err) {
5579
+ if (err instanceof TruncationError) {
5580
+ const retryTask = {
5581
+ ...task,
5582
+ max_tokens: task.max_tokens * 2
5583
+ };
5584
+ return await executeAgent(retryTask);
5585
+ }
5586
+ throw err;
5587
+ }
5588
+ });
5589
+ const results = await runWithConcurrency(agentTasks, concurrency);
5590
+ for (const result of results) {
5591
+ mergeIntoIR(ir, result);
5592
+ }
5593
+ completed.add(phase.id);
5594
+ onProgress?.({
5595
+ phaseId: phase.id,
5596
+ status: "complete",
5597
+ agentCount: phase.agents.length,
5598
+ completedCount: phase.agents.length
5599
+ });
5600
+ }
5601
+ return ir;
5602
+ }
5603
+
5604
+ // src/compiler/linker.ts
5605
+ init_types2();
5606
+ var DEFAULT_HELP_CONTENT = "Show available commands and their descriptions.\n\nList all /project: commands with brief descriptions.";
5607
+ var DEFAULT_HELP_DESCRIPTION = "Show available commands";
5608
+ var DEFAULT_SECURITY_CONTENT = [
5609
+ "# Security Rules",
5610
+ "",
5611
+ "- NEVER log or echo API keys, tokens, or secrets",
5612
+ "- NEVER write secrets to files",
5613
+ "- NEVER execute user-provided strings as shell commands",
5614
+ "- Validate all inputs before use"
5615
+ ].join("\n");
5616
+ var DEFAULT_CONTINUITY_CONTENT = [
5617
+ "# Continuity",
5618
+ "",
5619
+ "After every significant decision or discovery:",
5620
+ "",
5621
+ "1. Update docs/DECISIONS.md",
5622
+ "2. Update docs/LEARNINGS.md",
5623
+ "3. Update docs/TODO.md task status"
5624
+ ].join("\n");
5625
+ var AGENT_REF_PATTERN = /@([\w-]+)/g;
5626
+ var COMMAND_REF_PATTERN = /\/project:([\w-]+)/g;
5627
+ function validateAgentReferences(patched, agentNames, report) {
5628
+ for (const cmd of patched.commands) {
5629
+ const refs = cmd.content.matchAll(AGENT_REF_PATTERN);
5630
+ for (const match of refs) {
5631
+ const name = match[1];
5632
+ if (!agentNames.has(name)) {
5633
+ report.warnings.push(
5634
+ `Command "${cmd.name}" references non-existent agent "${name}"`
5635
+ );
5636
+ cmd.content = cmd.content.replace(
5637
+ new RegExp(`@${escapeRegExp(name)}\\b`, "g"),
5638
+ name
5639
+ );
5640
+ report.autoFixes.push(
5641
+ `Removed @${name} mention from command "${cmd.name}"`
5642
+ );
5643
+ }
5644
+ }
5645
+ }
5646
+ }
5647
+ function validateCommandReferences(patched, commandNames, report) {
5648
+ for (const agent of patched.agents) {
5649
+ const refs = agent.content.matchAll(COMMAND_REF_PATTERN);
5650
+ for (const match of refs) {
5651
+ const name = match[1];
5652
+ if (!commandNames.has(name)) {
5653
+ report.warnings.push(
5654
+ `Agent "${agent.name}" references non-existent command "${name}"`
5655
+ );
5656
+ }
5657
+ }
5658
+ }
5659
+ }
5660
+ function injectHelpCommand(patched, report) {
5661
+ const commandNames = new Set(patched.commands.map((c) => c.name));
5662
+ if (!commandNames.has("help")) {
5663
+ patched.commands.push(
5664
+ createCommandNode("help", DEFAULT_HELP_CONTENT, DEFAULT_HELP_DESCRIPTION)
5665
+ );
5666
+ report.autoFixes.push("Injected default /project:help command");
5667
+ }
5668
+ }
5669
+ function injectSecurityRule(patched, report) {
5670
+ const ruleNames = new Set(patched.rules.map((r) => r.name));
5671
+ if (!ruleNames.has("security")) {
5672
+ patched.rules.push(createRuleNode("security", DEFAULT_SECURITY_CONTENT));
5673
+ report.autoFixes.push("Injected default security rule");
5674
+ }
5675
+ }
5676
+ function injectContinuityRule(patched, report) {
5677
+ const ruleNames = new Set(patched.rules.map((r) => r.name));
5678
+ if (!ruleNames.has("continuity")) {
5679
+ patched.rules.push(createRuleNode("continuity", DEFAULT_CONTINUITY_CONTENT));
5680
+ report.autoFixes.push("Injected default continuity rule");
5681
+ }
5682
+ }
5683
+ function escapeRegExp(s) {
5684
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5685
+ }
5686
+ function linkHarness(ir) {
5687
+ const patched = JSON.parse(JSON.stringify(ir));
5688
+ const report = { warnings: [], autoFixes: [] };
5689
+ const agentNames = new Set(patched.agents.map((a) => a.name));
5690
+ const commandNames = new Set(patched.commands.map((c) => c.name));
5691
+ validateAgentReferences(patched, agentNames, report);
5692
+ validateCommandReferences(patched, commandNames, report);
5693
+ injectHelpCommand(patched, report);
5694
+ injectSecurityRule(patched, report);
5695
+ injectContinuityRule(patched, report);
5696
+ return { ir: patched, report };
5697
+ }
5698
+
5699
+ // src/compiler/agents/sections-writer.ts
5700
+ init_llm();
5701
+ init_types2();
5702
+ var SECTIONS_SYSTEM_PROMPT = `You are the Kairn sections writer. Generate CLAUDE.md sections for a development environment.
5703
+
5704
+ You will receive a project description and a list of section IDs to generate. Each section should be well-structured markdown.
5705
+
5706
+ ## Standard Sections (generate those requested)
5707
+ - purpose: Project purpose and goals (## Purpose heading, but use project-specific title like "# ProjectName Development")
5708
+ - tech-stack: Languages, frameworks, tools (## Tech Stack)
5709
+ - commands: Build/dev/test commands (## Commands, use code blocks)
5710
+ - architecture: Project structure (## Architecture, use code blocks for tree)
5711
+ - conventions: Coding conventions (## Conventions, bullet points)
5712
+ - key-commands: Slash commands reference (## Key Commands, bullet list)
5713
+ - output: Build output paths (## Output)
5714
+ - verification: Post-edit verification steps (## Verification)
5715
+ - gotchas: Known issues and footguns (## Known Gotchas)
5716
+ - debugging: Debugging tips (## Debugging)
5717
+ - git-workflow: Git conventions (## Git Workflow)
5718
+ - engineering-standards: Code quality standards (## Engineering Standards)
5719
+
5720
+ ## Rules
5721
+ - Each section: 5-20 lines of content
5722
+ - Use project-specific details, not generic advice
5723
+ - Markdown formatting: headers, bullets, code blocks
5724
+ - Be concise but informative
5725
+
5726
+ ## Output Format
5727
+ Return a JSON array:
5728
+ [
5729
+ { "id": "purpose", "heading": "# ProjectName Development", "content": "..." },
5730
+ { "id": "tech-stack", "heading": "## Tech Stack", "content": "..." }
5731
+ ]`;
5732
+ async function generateSections(intent, skeleton, task, config) {
5733
+ if (task.items.length === 0) {
5734
+ return { agent: "sections-writer", sections: [] };
5735
+ }
5736
+ const userMessage = buildUserMessage(intent, skeleton, task);
5737
+ const response = await callLLM(config, userMessage, {
5738
+ systemPrompt: SECTIONS_SYSTEM_PROMPT,
5739
+ maxTokens: task.max_tokens,
5740
+ agentName: "sections-writer",
5741
+ cacheControl: true
5742
+ });
5743
+ const sections = parseSectionsResponse(response);
5744
+ return { agent: "sections-writer", sections };
5745
+ }
5746
+ function buildUserMessage(intent, skeleton, task) {
5747
+ const parts = [
5748
+ `## Project
5749
+ ${intent}`,
5750
+ `## Tech Stack
5751
+ ${skeleton.outline.tech_stack.join(", ")}`,
5752
+ `## Workflow
5753
+ ${skeleton.outline.workflow_type}`,
5754
+ `## Sections to Generate
5755
+ ${task.items.join(", ")}`
5756
+ ];
5757
+ if (task.context_hint) {
5758
+ parts.push(`## Additional Context
5759
+ ${task.context_hint}`);
5760
+ }
5761
+ parts.push("Generate the sections JSON array now.");
5762
+ return parts.join("\n\n");
5763
+ }
5764
+ function parseSectionsResponse(text) {
5765
+ let cleaned = text.trim();
5766
+ if (cleaned.startsWith("```")) {
5767
+ cleaned = cleaned.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
5768
+ }
5769
+ const jsonMatch = cleaned.match(/\[[\s\S]*\]/);
5770
+ if (!jsonMatch) {
5771
+ throw new Error(
5772
+ "sections-writer: response did not contain a JSON array"
5773
+ );
5774
+ }
5775
+ const parsed = JSON.parse(jsonMatch[0]);
5776
+ if (!Array.isArray(parsed)) {
5777
+ throw new Error("sections-writer: expected JSON array");
5778
+ }
5779
+ return parsed.map((item, index) => {
5780
+ const obj = item;
5781
+ return createSection(
5782
+ String(obj.id ?? `section-${index}`),
5783
+ String(obj.heading ?? ""),
5784
+ String(obj.content ?? ""),
5785
+ index
5786
+ );
5787
+ });
5788
+ }
5789
+
5790
+ // src/compiler/agents/rule-writer.ts
5791
+ init_llm();
5792
+ init_types2();
5793
+ var SYSTEM_PROMPT2 = `You are the @rule-writer specialist inside the Kairn compilation pipeline.
5794
+
5795
+ Your job is to generate Claude Code rule files (.claude/rules/*.md) for a project.
5796
+
5797
+ Each rule file may be **global** (applies everywhere) or **path-scoped** (applies only
5798
+ when the user edits files matching certain globs).
5799
+
5800
+ ## Output format
5801
+
5802
+ Return a JSON array. Each element:
5803
+
5804
+ {
5805
+ "name": "rule-slug",
5806
+ "content": "Markdown content of the rule file.",
5807
+ "paths": null
5808
+ }
5809
+
5810
+ - **name**: kebab-case slug (e.g. "security", "api-conventions", "testing").
5811
+ - **content**: The full Markdown body of the rule. Be specific, actionable, and concise.
5812
+ Write imperative statements ("Do X", "Never Y"). Avoid vague advice.
5813
+ - **paths**: Either null (global rule) or a string array of glob patterns
5814
+ (e.g. ["src/api/**", "src/routes/**"]).
5815
+
5816
+ ## Required rules
5817
+
5818
+ Every project MUST include:
5819
+ 1. **security** -- baseline security constraints (no secrets in code, input validation,
5820
+ safe file I/O, no dynamic code execution, deny dangerous shell patterns).
5821
+ 2. **continuity** -- project memory rules (update decision logs, learning docs, track
5822
+ TODO progress, document gotchas).
5823
+
5824
+ If the user's rule list doesn't mention these, generate them anyway.
5825
+
5826
+ ## Guidelines
5827
+
5828
+ - Rules should be 5-20 lines of Markdown each.
5829
+ - Use bullet points for lists of constraints.
5830
+ - Path-scoped rules are for conventions that only matter in specific directories
5831
+ (e.g. API conventions for src/api/**, test rules for **/*.test.ts).
5832
+ - Global rules apply to the whole project (security, continuity, git workflow).
5833
+ - Do NOT include YAML frontmatter in the content -- the paths field handles scoping.
5834
+ - Return ONLY the JSON array. No explanation, no wrapping text.`;
5835
+ var DEFAULT_SECURITY_CONTENT2 = [
5836
+ "# Security Rules",
5837
+ "",
5838
+ "- NEVER log or echo API keys, tokens, or secrets",
5839
+ "- NEVER write secrets to files outside designated config locations",
5840
+ "- NEVER execute user-provided strings as shell commands",
5841
+ "- NEVER use dynamic code execution with untrusted input",
5842
+ "- Validate all external input before processing",
5843
+ "- Sanitize all file paths -- prevent path traversal (../)",
5844
+ "- Deny dangerous shell patterns: rm -rf /, curl|sh, wget|sh"
5845
+ ].join("\n");
5846
+ var DEFAULT_CONTINUITY_CONTENT2 = [
5847
+ "# Continuity",
5848
+ "",
5849
+ "After every significant decision or discovery:",
5850
+ "",
5851
+ "1. Update decision logs with what was decided and why",
5852
+ "2. Document non-obvious behavior, gotchas, and footguns",
5853
+ "3. Update task status as work progresses",
5854
+ "4. If a mistake is corrected, add it to the known gotchas section",
5855
+ "",
5856
+ "These files are the project memory. Keep them current."
5857
+ ].join("\n");
5858
+ function parseRulesJSON(raw) {
5859
+ let cleaned = raw.trim();
5860
+ const fenceStart = /^```(?:json)?\s*\n?/;
5861
+ const fenceEnd = /\n?```\s*$/;
5862
+ if (fenceStart.test(cleaned)) {
5863
+ cleaned = cleaned.replace(fenceStart, "").replace(fenceEnd, "");
5864
+ }
5865
+ const parsed = JSON.parse(cleaned);
5866
+ if (!Array.isArray(parsed)) {
5867
+ throw new Error("Expected JSON array from rule-writer LLM response");
5868
+ }
5869
+ return parsed;
5870
+ }
5871
+ function buildUserMessage2(intent, skeleton, task) {
5872
+ const lines = [
5873
+ "## Project intent",
5874
+ intent,
5875
+ "",
5876
+ "## Rules to generate",
5877
+ ...task.items.map((item) => `- ${item}`),
5878
+ "",
5879
+ "## Project context",
5880
+ JSON.stringify(skeleton.outline, null, 2)
5881
+ ];
5882
+ return lines.join("\n");
5883
+ }
5884
+ async function generateRules(intent, skeleton, task, config) {
5885
+ if (task.items.length === 0) {
5886
+ return { agent: "rule-writer", rules: [] };
5887
+ }
5888
+ const userMessage = buildUserMessage2(intent, skeleton, task);
5889
+ const raw = await callLLM(config, userMessage, {
5890
+ systemPrompt: SYSTEM_PROMPT2,
5891
+ cacheControl: true,
5892
+ maxTokens: task.max_tokens
5893
+ });
5894
+ const parsedRules = parseRulesJSON(raw);
5895
+ const rules = parsedRules.map(
5896
+ (r) => createRuleNode(
5897
+ r.name,
5898
+ r.content,
5899
+ r.paths !== null ? r.paths : void 0
5900
+ )
5901
+ );
5902
+ ensureRequiredRule(rules, "security", DEFAULT_SECURITY_CONTENT2);
5903
+ ensureRequiredRule(rules, "continuity", DEFAULT_CONTINUITY_CONTENT2);
5904
+ return { agent: "rule-writer", rules };
5905
+ }
5906
+ function ensureRequiredRule(rules, name, defaultContent) {
5907
+ const exists = rules.some((r) => r.name === name);
5908
+ if (!exists) {
5909
+ rules.unshift(createRuleNode(name, defaultContent));
5910
+ }
5911
+ }
5500
5912
 
5501
- Do not include any text outside the JSON object. Do not wrap in markdown code fences.`;
5502
- var CLARIFICATION_PROMPT = `You are helping a user define their project for environment compilation.
5913
+ // src/compiler/agents/doc-writer.ts
5914
+ init_llm();
5915
+ var DEFAULT_DECISIONS = `# Decisions
5503
5916
 
5504
- Given their initial description, generate 3-5 clarifying questions to understand:
5505
- 1. Language and framework
5506
- 2. What the project specifically does (be precise)
5507
- 3. Primary workflow (build, research, write, analyze?)
5508
- 4. Key dependencies or integrations
5509
- 5. Target audience
5917
+ | Date | Decision | Rationale |
5918
+ |------|----------|-----------|`;
5919
+ var DEFAULT_LEARNINGS = `# Learnings
5510
5920
 
5511
- For each question, provide a reasonable suggestion based on the description.
5921
+ | Date | Learning | Impact |
5922
+ |------|----------|--------|`;
5923
+ var DEFAULT_SPRINT = `# Sprint
5512
5924
 
5513
- Output ONLY a JSON array:
5925
+ ## Acceptance Criteria
5926
+
5927
+ - [ ] Criterion 1
5928
+
5929
+ ## Status
5930
+
5931
+ Not started`;
5932
+ var REQUIRED_DOCS = [
5933
+ { name: "DECISIONS", defaultContent: DEFAULT_DECISIONS },
5934
+ { name: "LEARNINGS", defaultContent: DEFAULT_LEARNINGS },
5935
+ { name: "SPRINT", defaultContent: DEFAULT_SPRINT }
5936
+ ];
5937
+ var DOC_WRITER_SYSTEM_PROMPT = `You are the doc-writer specialist agent in a multi-agent compilation pipeline.
5938
+
5939
+ Your role: generate documentation files for a Claude Code agent environment's \`.claude/docs/\` directory.
5940
+
5941
+ ## Output Format
5942
+
5943
+ Return a JSON array of objects, each with "name" (string) and "content" (string):
5944
+
5945
+ \`\`\`json
5514
5946
  [
5515
- { "question": "Language/framework?", "suggestion": "TypeScript + Node.js" },
5516
- ...
5947
+ { "name": "DECISIONS", "content": "# Decisions\\n\\n| Date | Decision | Rationale |\\n|------|----------|-----------|" },
5948
+ { "name": "LEARNINGS", "content": "# Learnings\\n\\n| Date | Learning | Impact |\\n|------|----------|--------|" }
5517
5949
  ]
5950
+ \`\`\`
5518
5951
 
5519
- Rules:
5520
- - Suggestions should be reasonable guesses, clearly marked as suggestions
5521
- - Keep questions short (under 10 words)
5522
- - Maximum 5 questions
5523
- - If the description is already very detailed, ask fewer questions`;
5952
+ ## Document Templates
5524
5953
 
5525
- // src/registry/loader.ts
5526
- import fs3 from "fs/promises";
5527
- import path3 from "path";
5528
- import { fileURLToPath as fileURLToPath2 } from "url";
5529
- var __filename2 = fileURLToPath2(import.meta.url);
5530
- var __dirname2 = path3.dirname(__filename2);
5531
- async function loadBundledRegistry() {
5532
- const candidates = [
5533
- path3.resolve(__dirname2, "../registry/tools.json"),
5534
- path3.resolve(__dirname2, "../src/registry/tools.json"),
5535
- path3.resolve(__dirname2, "../../src/registry/tools.json")
5954
+ Each doc should follow these structural patterns:
5955
+
5956
+ - **DECISIONS**: Markdown table with Date, Decision, Rationale columns. Track architectural and design choices.
5957
+ - **LEARNINGS**: Markdown table with Date, Learning, Impact columns. Track non-obvious discoveries and gotchas.
5958
+ - **SPRINT**: Must include an "## Acceptance Criteria" section with checkbox items (\`- [ ] ...\`) and a "## Status" section. Track current sprint goals.
5959
+
5960
+ ## Guidelines
5961
+
5962
+ - Content should be tailored to the project intent provided
5963
+ - Use Markdown formatting with clear headers
5964
+ - Acceptance Criteria in SPRINT docs must use checkbox format: \`- [ ] Criterion\`
5965
+ - Keep templates practical \u2014 they'll be filled in during development
5966
+ - Return ONLY the JSON array, no surrounding text`;
5967
+ function stripCodeFences(raw) {
5968
+ let text = raw.trim();
5969
+ const openFence = /^```(?:json)?\s*\n/;
5970
+ if (openFence.test(text)) {
5971
+ text = text.replace(openFence, "");
5972
+ }
5973
+ const closeFence = /\n```\s*$/;
5974
+ if (closeFence.test(text)) {
5975
+ text = text.replace(closeFence, "");
5976
+ }
5977
+ return text.trim();
5978
+ }
5979
+ async function generateDocs(intent, skeleton, task, config) {
5980
+ if (task.items.length === 0) {
5981
+ return { agent: "doc-writer", docs: [] };
5982
+ }
5983
+ const userMessage = buildUserMessage3(intent, skeleton, task);
5984
+ const rawResponse = await callLLM(config, userMessage, {
5985
+ systemPrompt: DOC_WRITER_SYSTEM_PROMPT,
5986
+ cacheControl: true,
5987
+ maxTokens: task.max_tokens
5988
+ });
5989
+ const parsedDocs = parseDocResponse(rawResponse);
5990
+ const docs = ensureRequiredDocs(parsedDocs);
5991
+ return { agent: "doc-writer", docs };
5992
+ }
5993
+ function buildUserMessage3(intent, _skeleton, task) {
5994
+ const itemList = task.items.map((item) => `- ${item}`).join("\n");
5995
+ return `Project intent: ${intent}
5996
+
5997
+ Generate the following documentation files:
5998
+ ${itemList}
5999
+
6000
+ Return a JSON array of { "name": string, "content": string } objects.`;
6001
+ }
6002
+ function isDocShape(value) {
6003
+ if (typeof value !== "object" || value === null) return false;
6004
+ const obj = value;
6005
+ return typeof obj.name === "string" && typeof obj.content === "string";
6006
+ }
6007
+ function parseDocResponse(raw) {
6008
+ const cleaned = stripCodeFences(raw);
6009
+ const parsed = JSON.parse(cleaned);
6010
+ if (!Array.isArray(parsed)) {
6011
+ return [];
6012
+ }
6013
+ return parsed.filter(isDocShape).map(({ name, content }) => ({ name, content }));
6014
+ }
6015
+ function ensureRequiredDocs(docs) {
6016
+ const result = [...docs];
6017
+ const existingNames = new Set(result.map((d) => d.name));
6018
+ for (const required of REQUIRED_DOCS) {
6019
+ if (!existingNames.has(required.name)) {
6020
+ result.push({
6021
+ name: required.name,
6022
+ content: required.defaultContent
6023
+ });
6024
+ }
6025
+ }
6026
+ return result;
6027
+ }
6028
+
6029
+ // src/compiler/agents/command-writer.ts
6030
+ init_llm();
6031
+ init_types2();
6032
+ var BATCH_SIZE = 8;
6033
+ var BATCH_THRESHOLD = 10;
6034
+ var DEFAULT_HELP_CONTENT2 = `Show available /project: commands and their descriptions.
6035
+
6036
+ List all slash commands with a brief description of what each does.`;
6037
+ var DEFAULT_HELP_DESCRIPTION2 = "Show available commands and their descriptions";
6038
+ var SYSTEM_PROMPT3 = `You are @command-writer, a specialist agent that generates Claude Code slash commands.
6039
+
6040
+ ## Output Format
6041
+ Return a JSON array of command objects. Each object has:
6042
+ - "name": the command name (no /project: prefix, just the bare name like "build", "test")
6043
+ - "description": a one-line description of what the command does
6044
+ - "content": the full command body (markdown text with optional shell integration)
6045
+
6046
+ ## Shell Integration
6047
+ Commands can execute shell commands using the ! prefix:
6048
+ - \`!npm run build\` \u2014 runs the command directly
6049
+ - \`!$ARGUMENTS\` \u2014 passes user arguments to a shell command
6050
+ - Multiple ! lines are run in sequence
6051
+
6052
+ ## Command Patterns
6053
+ - **Build/Test**: Direct shell execution with !
6054
+ - **Workflow**: Multi-step orchestration instructions in natural language
6055
+ - **Review**: Instructions for Claude to analyze code
6056
+ - **Deploy**: Safety checks + shell execution
6057
+
6058
+ ## Example Output
6059
+ \`\`\`json
6060
+ [
6061
+ {
6062
+ "name": "build",
6063
+ "description": "Build the project",
6064
+ "content": "Run the full build pipeline.\\n\\n!npm run build"
6065
+ },
6066
+ {
6067
+ "name": "test",
6068
+ "description": "Run the test suite",
6069
+ "content": "Execute all tests and report results.\\n\\n!npm test"
6070
+ },
6071
+ {
6072
+ "name": "review",
6073
+ "description": "Review staged changes",
6074
+ "content": "Review all staged git changes for:\\n- Code quality issues\\n- Security concerns\\n- Missing tests\\n\\nProvide actionable feedback."
6075
+ }
6076
+ ]
6077
+ \`\`\`
6078
+
6079
+ ## Rules
6080
+ - Command names are kebab-case, lowercase
6081
+ - Content should be actionable and specific to the project
6082
+ - Include shell commands (!) where appropriate for automation
6083
+ - Keep descriptions under 80 characters
6084
+ - Return ONLY the JSON array, no surrounding text`;
6085
+ function parseCommandResponse(text) {
6086
+ let cleaned = text.trim();
6087
+ if (cleaned.startsWith("```")) {
6088
+ cleaned = cleaned.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
6089
+ }
6090
+ const arrayMatch = cleaned.match(/\[[\s\S]*\]/);
6091
+ if (!arrayMatch) {
6092
+ throw new Error("@command-writer: LLM response did not contain a JSON array.");
6093
+ }
6094
+ const parsed = JSON.parse(arrayMatch[0]);
6095
+ if (!Array.isArray(parsed)) {
6096
+ throw new Error("@command-writer: parsed response is not an array.");
6097
+ }
6098
+ return parsed.map((item) => {
6099
+ const obj = item;
6100
+ if (typeof obj.name !== "string" || typeof obj.content !== "string") {
6101
+ throw new Error("@command-writer: each command must have 'name' and 'content' strings.");
6102
+ }
6103
+ return {
6104
+ name: obj.name,
6105
+ description: typeof obj.description === "string" ? obj.description : "",
6106
+ content: obj.content
6107
+ };
6108
+ });
6109
+ }
6110
+ function buildUserMessage4(intent, skeleton, batchItems, phaseAContext) {
6111
+ const lines = [];
6112
+ lines.push("## Project Context");
6113
+ lines.push(`Intent: ${intent}`);
6114
+ lines.push(`Tech stack: ${skeleton.outline.tech_stack.join(", ")}`);
6115
+ lines.push(`Workflow type: ${skeleton.outline.workflow_type}`);
6116
+ lines.push("");
6117
+ if (phaseAContext) {
6118
+ lines.push("## Reference (from Phase A)");
6119
+ lines.push(phaseAContext);
6120
+ lines.push("");
6121
+ }
6122
+ lines.push("## Commands to Generate");
6123
+ for (const item of batchItems) {
6124
+ lines.push(`- ${item}`);
6125
+ }
6126
+ lines.push("");
6127
+ lines.push("Generate the JSON array of command objects now.");
6128
+ return lines.join("\n");
6129
+ }
6130
+ function chunk(arr, size) {
6131
+ const chunks = [];
6132
+ for (let i = 0; i < arr.length; i += size) {
6133
+ chunks.push(arr.slice(i, i + size));
6134
+ }
6135
+ return chunks;
6136
+ }
6137
+ function ensureHelpCommand(commands) {
6138
+ const hasHelp = commands.some((c) => c.name === "help");
6139
+ if (hasHelp) {
6140
+ return commands;
6141
+ }
6142
+ return [
6143
+ ...commands,
6144
+ createCommandNode("help", DEFAULT_HELP_CONTENT2, DEFAULT_HELP_DESCRIPTION2)
5536
6145
  ];
5537
- for (const candidate of candidates) {
5538
- try {
5539
- const data = await fs3.readFile(candidate, "utf-8");
5540
- return JSON.parse(data);
5541
- } catch {
5542
- continue;
6146
+ }
6147
+ function deduplicateCommands(commands) {
6148
+ const seen = /* @__PURE__ */ new Set();
6149
+ const result = [];
6150
+ for (const cmd of commands) {
6151
+ if (!seen.has(cmd.name)) {
6152
+ seen.add(cmd.name);
6153
+ result.push(cmd);
5543
6154
  }
5544
6155
  }
5545
- throw new Error("Could not find tools.json registry");
6156
+ return result;
5546
6157
  }
5547
- async function loadUserRegistry() {
6158
+ async function generateBatch(intent, skeleton, batchItems, config, maxTokens, phaseAContext) {
6159
+ const userMessage = buildUserMessage4(intent, skeleton, batchItems, phaseAContext);
6160
+ const responseText = await callLLM(config, userMessage, {
6161
+ systemPrompt: SYSTEM_PROMPT3,
6162
+ cacheControl: true,
6163
+ maxTokens
6164
+ });
6165
+ const rawCommands = parseCommandResponse(responseText);
6166
+ return rawCommands.map(
6167
+ (c) => createCommandNode(c.name, c.content, c.description)
6168
+ );
6169
+ }
6170
+ async function generateCommands(intent, skeleton, task, config) {
6171
+ if (task.items.length === 0) {
6172
+ return { agent: "command-writer", commands: [] };
6173
+ }
6174
+ let allCommands;
6175
+ if (task.items.length > BATCH_THRESHOLD) {
6176
+ const batches = chunk(task.items, BATCH_SIZE);
6177
+ const batchResults = [];
6178
+ for (const batch of batches) {
6179
+ const nodes = await generateBatch(intent, skeleton, batch, config, task.max_tokens, task.context_hint);
6180
+ batchResults.push(nodes);
6181
+ }
6182
+ allCommands = deduplicateCommands(batchResults.flat());
6183
+ } else {
6184
+ allCommands = await generateBatch(intent, skeleton, task.items, config, task.max_tokens, task.context_hint);
6185
+ }
6186
+ allCommands = ensureHelpCommand(allCommands);
6187
+ return { agent: "command-writer", commands: allCommands };
6188
+ }
6189
+
6190
+ // src/compiler/agents/agent-writer.ts
6191
+ init_llm();
6192
+ var BATCH_THRESHOLD2 = 8;
6193
+ var BATCH_SIZE2 = 6;
6194
+ var AGENT_WRITER_SYSTEM_PROMPT = `You are an expert at designing Claude Code agent personas for the .claude/agents/ directory.
6195
+
6196
+ Each agent file uses YAML frontmatter followed by Markdown persona content.
6197
+
6198
+ ## YAML Frontmatter Conventions
6199
+ - \`model\`: optional model hint \u2014 "opus" for complex reasoning, "sonnet" for balanced, "haiku" for fast/cheap
6200
+ - \`disallowedTools\`: optional string array of tools the agent should NOT use (e.g. ["Bash", "Write"])
6201
+ - \`modelRouting\`: optional object for dynamic model selection:
6202
+ - \`default\`: base model tier ("haiku", "sonnet", or "opus")
6203
+ - \`escalateTo\`: higher tier to escalate to ("sonnet" or "opus")
6204
+ - \`escalateWhen\`: description of when to escalate
6205
+
6206
+ ## Persona Design Principles
6207
+ - Each agent has a clear, focused role (single responsibility)
6208
+ - Persona should describe expertise, approach, and boundaries
6209
+ - Include specific instructions for the agent's domain
6210
+ - Use second person ("You are...")
6211
+ - Be concrete about what the agent should and should not do
6212
+ - Include relevant workflow steps or checklists where appropriate
6213
+
6214
+ ## Model Tiering Guidelines
6215
+ - "haiku": formatting, linting, simple lookups, boilerplate generation
6216
+ - "sonnet": most development tasks, code review, testing, refactoring
6217
+ - "opus": architecture decisions, complex debugging, cross-cutting changes, security audits
6218
+
6219
+ ## Output Format
6220
+ Return a JSON array. Each element:
6221
+ {
6222
+ "name": "agent-name-kebab-case",
6223
+ "content": "You are the ... (full persona markdown)",
6224
+ "model": "sonnet",
6225
+ "disallowedTools": ["Bash"],
6226
+ "modelRouting": { "default": "sonnet", "escalateTo": "opus", "escalateWhen": "cross-cutting changes" }
6227
+ }
6228
+
6229
+ Only include model, disallowedTools, and modelRouting when they add value. Not every agent needs all fields.
6230
+
6231
+ Return ONLY the JSON array, no surrounding text or code fences.`;
6232
+ function parseAgentResponse(text) {
6233
+ let cleaned = text.trim();
6234
+ if (cleaned.startsWith("```")) {
6235
+ cleaned = cleaned.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
6236
+ }
6237
+ const arrayMatch = cleaned.match(/\[[\s\S]*\]/);
6238
+ if (!arrayMatch) {
6239
+ return [];
6240
+ }
5548
6241
  try {
5549
- const data = await fs3.readFile(getUserRegistryPath(), "utf-8");
5550
- return JSON.parse(data);
6242
+ const parsed = JSON.parse(arrayMatch[0]);
6243
+ if (!Array.isArray(parsed)) {
6244
+ return [];
6245
+ }
6246
+ return parsed;
5551
6247
  } catch {
5552
6248
  return [];
5553
6249
  }
5554
6250
  }
5555
- async function saveUserRegistry(tools) {
5556
- await fs3.writeFile(getUserRegistryPath(), JSON.stringify(tools, null, 2), "utf-8");
6251
+ function toAgentNode(raw) {
6252
+ if (typeof raw !== "object" || raw === null) {
6253
+ return null;
6254
+ }
6255
+ const obj = raw;
6256
+ if (typeof obj["name"] !== "string" || !obj["name"]) {
6257
+ return null;
6258
+ }
6259
+ if (typeof obj["content"] !== "string" || !obj["content"]) {
6260
+ return null;
6261
+ }
6262
+ const node = {
6263
+ name: obj["name"],
6264
+ content: obj["content"]
6265
+ };
6266
+ if (typeof obj["model"] === "string" && obj["model"]) {
6267
+ node.model = obj["model"];
6268
+ }
6269
+ if (Array.isArray(obj["disallowedTools"])) {
6270
+ const tools = obj["disallowedTools"].filter(
6271
+ (t) => typeof t === "string" && t.length > 0
6272
+ );
6273
+ if (tools.length > 0) {
6274
+ node.disallowedTools = tools;
6275
+ }
6276
+ }
6277
+ if (typeof obj["modelRouting"] === "object" && obj["modelRouting"] !== null) {
6278
+ const routing = obj["modelRouting"];
6279
+ const defaultModel = routing["default"];
6280
+ if (defaultModel === "haiku" || defaultModel === "sonnet" || defaultModel === "opus") {
6281
+ const modelRouting = {
6282
+ default: defaultModel
6283
+ };
6284
+ const escalateTo = routing["escalateTo"];
6285
+ if (escalateTo === "sonnet" || escalateTo === "opus") {
6286
+ modelRouting.escalateTo = escalateTo;
6287
+ }
6288
+ const escalateWhen = routing["escalateWhen"];
6289
+ if (typeof escalateWhen === "string" && escalateWhen) {
6290
+ modelRouting.escalateWhen = escalateWhen;
6291
+ }
6292
+ node.modelRouting = modelRouting;
6293
+ }
6294
+ }
6295
+ return node;
5557
6296
  }
5558
- async function loadRegistry() {
5559
- const bundled = await loadBundledRegistry();
5560
- const user = await loadUserRegistry();
5561
- if (user.length === 0) return bundled;
5562
- const merged = /* @__PURE__ */ new Map();
5563
- for (const tool of bundled) {
5564
- merged.set(tool.id, tool);
6297
+ function buildUserMessage5(items, intent, phaseAContext) {
6298
+ const parts = [];
6299
+ parts.push(`## User Intent
6300
+
6301
+ ${intent}`);
6302
+ if (phaseAContext) {
6303
+ parts.push(`## Project Context (from Phase A)
6304
+
6305
+ ${phaseAContext}`);
5565
6306
  }
5566
- for (const tool of user) {
5567
- merged.set(tool.id, tool);
6307
+ parts.push(
6308
+ `## Agents to Generate
6309
+
6310
+ Create agent persona definitions for each of these agents:
6311
+ ${items.map((item) => `- ${item}`).join("\n")}`
6312
+ );
6313
+ parts.push(
6314
+ "Generate the JSON array now. One object per agent listed above."
6315
+ );
6316
+ return parts.join("\n\n");
6317
+ }
6318
+ function chunk2(arr, size) {
6319
+ const chunks = [];
6320
+ for (let i = 0; i < arr.length; i += size) {
6321
+ chunks.push(arr.slice(i, i + size));
5568
6322
  }
5569
- return Array.from(merged.values());
6323
+ return chunks;
6324
+ }
6325
+ async function generateAgents(intent, _skeleton, task, config) {
6326
+ if (task.items.length === 0) {
6327
+ return { agent: "agent-writer", agents: [] };
6328
+ }
6329
+ const needsBatching = task.items.length > BATCH_THRESHOLD2;
6330
+ const batches = needsBatching ? chunk2(task.items, BATCH_SIZE2) : [task.items];
6331
+ const allAgents = [];
6332
+ for (const batch of batches) {
6333
+ const userMessage = buildUserMessage5(batch, intent, task.context_hint);
6334
+ const response = await callLLM(config, userMessage, {
6335
+ systemPrompt: AGENT_WRITER_SYSTEM_PROMPT,
6336
+ cacheControl: true,
6337
+ maxTokens: task.max_tokens
6338
+ });
6339
+ const rawAgents = parseAgentResponse(response);
6340
+ for (const raw of rawAgents) {
6341
+ const node = toAgentNode(raw);
6342
+ if (node !== null) {
6343
+ allAgents.push(node);
6344
+ }
6345
+ }
6346
+ }
6347
+ return { agent: "agent-writer", agents: allAgents };
5570
6348
  }
5571
6349
 
5572
- // src/compiler/compile.ts
5573
- init_providers();
6350
+ // src/compiler/agents/skill-writer.ts
5574
6351
  init_llm();
6352
+ var SYSTEM_PROMPT4 = `You are a specialist agent that writes SKILL.md files for Claude Code environments.
6353
+
6354
+ Each skill is a structured markdown document that teaches Claude Code a repeatable workflow pattern.
6355
+
6356
+ Output format: a JSON array of objects with "name" (string) and "content" (string) fields.
6357
+
6358
+ Rules:
6359
+ - Each skill must have a clear title heading (# Skill Name)
6360
+ - Use numbered phases (## Phase 1: NAME, ## Phase 2: NAME, etc.) for multi-step workflows
6361
+ - Content should be actionable instructions, not theory
6362
+ - Keep each skill concise: 200-400 words
6363
+ - For TDD skills, always use the 3-phase pattern: RED (write failing test), GREEN (minimal implementation), REFACTOR (clean up)
6364
+ - Output ONLY the JSON array, no surrounding text
6365
+
6366
+ Example:
6367
+ [
6368
+ {
6369
+ "name": "tdd",
6370
+ "content": "# TDD Skill\\n\\n## Phase 1: RED\\nWrite a failing test first...\\n## Phase 2: GREEN\\nWrite minimal code to make the test pass...\\n## Phase 3: REFACTOR\\nClean up duplication and improve naming..."
6371
+ }
6372
+ ]`;
6373
+ function stripCodeFences2(raw) {
6374
+ const trimmed = raw.trim();
6375
+ const fencePattern = /^```(?:json|JSON)?\s*\n?([\s\S]*?)\n?\s*```$/;
6376
+ const match = trimmed.match(fencePattern);
6377
+ if (match) {
6378
+ return match[1].trim();
6379
+ }
6380
+ return trimmed;
6381
+ }
6382
+ function parseSkillNodes(raw) {
6383
+ const cleaned = stripCodeFences2(raw);
6384
+ const parsed = JSON.parse(cleaned);
6385
+ if (!Array.isArray(parsed)) {
6386
+ throw new Error("Expected JSON array of skills from LLM response");
6387
+ }
6388
+ const skills = [];
6389
+ for (const item of parsed) {
6390
+ if (typeof item !== "object" || item === null || typeof item.name !== "string" || typeof item.content !== "string") {
6391
+ throw new Error(
6392
+ "Each skill must have a string 'name' and string 'content' field"
6393
+ );
6394
+ }
6395
+ skills.push({
6396
+ name: item.name,
6397
+ content: item.content
6398
+ });
6399
+ }
6400
+ return skills;
6401
+ }
6402
+ async function generateSkills(_intent, _skeleton, task, config) {
6403
+ if (task.items.length === 0) {
6404
+ return { agent: "skill-writer", skills: [] };
6405
+ }
6406
+ const userMessage = `Generate SKILL.md content for the following skills:
6407
+
6408
+ ${task.items.map((name) => `- ${name}`).join("\n")}`;
6409
+ const raw = await callLLM(config, userMessage, {
6410
+ systemPrompt: SYSTEM_PROMPT4,
6411
+ cacheControl: true,
6412
+ maxTokens: task.max_tokens
6413
+ });
6414
+ const skills = parseSkillNodes(raw);
6415
+ return { agent: "skill-writer", skills };
6416
+ }
6417
+
6418
+ // src/compiler/agents/dispatch.ts
6419
+ async function dispatchAgent(task, config, intent, skeleton) {
6420
+ switch (task.agent) {
6421
+ case "sections-writer":
6422
+ return generateSections(intent, skeleton, task, config);
6423
+ case "rule-writer":
6424
+ return generateRules(intent, skeleton, task, config);
6425
+ case "doc-writer":
6426
+ return generateDocs(intent, skeleton, task, config);
6427
+ case "command-writer":
6428
+ return generateCommands(intent, skeleton, task, config);
6429
+ case "agent-writer":
6430
+ return generateAgents(intent, skeleton, task, config);
6431
+ case "skill-writer":
6432
+ return generateSkills(intent, skeleton, task, config);
6433
+ default:
6434
+ throw new Error(`Unknown agent: ${task.agent}`);
6435
+ }
6436
+ }
6437
+
6438
+ // src/compiler/compile.ts
6439
+ init_renderer();
5575
6440
 
5576
6441
  // src/intent/patterns.ts
5577
6442
  var SYNONYM_MAP = {
@@ -5943,19 +6808,6 @@ ${registrySummary}
5943
6808
 
5944
6809
  Generate the skeleton JSON now.`;
5945
6810
  }
5946
- function buildHarnessMessage(intent, skeleton, concise) {
5947
- const skeletonJson = JSON.stringify(skeleton, null, 2);
5948
- const conciseNote = concise ? "\n\nIMPORTANT: Be concise. Maximum 80 lines for claude_md. Maximum 5 commands. Keep all content brief." : "";
5949
- return `## User Intent
5950
-
5951
- ${intent}
5952
-
5953
- ## Project Skeleton
5954
-
5955
- ${skeletonJson}
5956
-
5957
- Generate the harness content JSON now.${conciseNote}`;
5958
- }
5959
6811
  function parseSkeletonResponse(text) {
5960
6812
  let cleaned = text.trim();
5961
6813
  if (cleaned.startsWith("```")) {
@@ -5977,29 +6829,8 @@ function parseSkeletonResponse(text) {
5977
6829
  );
5978
6830
  }
5979
6831
  }
5980
- function parseHarnessResponse(text) {
5981
- let cleaned = text.trim();
5982
- if (cleaned.startsWith("```")) {
5983
- cleaned = cleaned.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
5984
- }
5985
- const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
5986
- if (!jsonMatch) {
5987
- throw new Error("Pass 2 (harness) did not return valid JSON.");
5988
- }
5989
- try {
5990
- const parsed = JSON.parse(jsonMatch[0]);
5991
- if (!parsed.claude_md || !parsed.commands) {
5992
- throw new Error("Harness missing required fields: claude_md, commands");
5993
- }
5994
- return parsed;
5995
- } catch (err) {
5996
- throw new Error(
5997
- `Failed to parse harness JSON: ${err instanceof Error ? err.message : String(err)}`
5998
- );
5999
- }
6000
- }
6001
6832
  function buildSettings(skeleton, registry) {
6002
- const selectedTools = skeleton.tools.map((t) => registry.find((r) => r.id === t.tool_id)).filter(Boolean);
6833
+ const _selectedTools = skeleton.tools.map((t) => registry.find((r) => r.id === t.tool_id)).filter(Boolean);
6003
6834
  const allow = ["Read", "Write", "Edit", "Bash(npm run *)", "Bash(npx *)"];
6004
6835
  const deny = [
6005
6836
  "Bash(rm -rf *)",
@@ -6098,50 +6929,60 @@ async function compile(intent, onProgress) {
6098
6929
  detail: toolNames,
6099
6930
  elapsed: (Date.now() - startTime) / 1e3
6100
6931
  });
6101
- onProgress?.({ phase: "pass2", status: "running", message: "Pass 2: Generating CLAUDE.md, commands, agents..." });
6102
- const harnessMsg = buildHarnessMessage(intent, skeleton);
6103
- let harness;
6104
- try {
6105
- const harnessText = await callLLM(config, harnessMsg, {
6106
- maxTokens: 8192,
6107
- systemPrompt: HARNESS_PROMPT
6108
- });
6109
- harness = parseHarnessResponse(harnessText);
6110
- } catch {
6111
- onProgress?.({ phase: "pass2-retry", status: "warning", message: "Pass 2: Response too large, retrying in concise mode..." });
6112
- const retryMsg = buildHarnessMessage(intent, skeleton, true);
6113
- const retryText = await callLLM(config, retryMsg, {
6114
- maxTokens: 8192,
6115
- systemPrompt: HARNESS_PROMPT
6116
- });
6117
- harness = parseHarnessResponse(retryText);
6118
- }
6119
- const cmdCount = Object.keys(harness.commands).length;
6120
- const agentCount = Object.keys(harness.agents ?? {}).length;
6121
- const ruleCount = Object.keys(harness.rules).length;
6932
+ onProgress?.({ phase: "plan", status: "running", message: "Pass 2: Planning compilation..." });
6933
+ const plan = await generatePlan(intent, skeleton, config);
6934
+ const agentCount = plan.phases.reduce((sum, p) => sum + p.agents.length, 0);
6122
6935
  onProgress?.({
6123
- phase: "pass2",
6936
+ phase: "plan",
6124
6937
  status: "success",
6125
- message: `Pass 2: Generated ${cmdCount} commands, ${agentCount} agents, ${ruleCount} rules`,
6938
+ message: `Pass 2: Compilation plan \u2014 ${agentCount} agents across ${plan.phases.length} phases`,
6126
6939
  elapsed: (Date.now() - startTime) / 1e3
6127
6940
  });
6128
- onProgress?.({ phase: "pass3", status: "running", message: "Pass 3: Configuring MCP servers & settings..." });
6941
+ const concurrency = config.auth_type === "claude-code-oauth" ? 2 : 3;
6942
+ const executeAgent = (task) => dispatchAgent(task, config, intent, skeleton);
6943
+ const batchProgress = (bp) => {
6944
+ if (bp.status === "start") {
6945
+ const phaseLabel = bp.phaseId;
6946
+ onProgress?.({ phase: phaseLabel, status: "running", message: `Pass 3 (${bp.phaseId}): Running ${bp.agentCount} agents...` });
6947
+ } else if (bp.status === "complete") {
6948
+ const phaseLabel = bp.phaseId;
6949
+ onProgress?.({ phase: phaseLabel, status: "success", message: `Pass 3 (${bp.phaseId}): Complete`, elapsed: (Date.now() - startTime) / 1e3 });
6950
+ }
6951
+ };
6952
+ const rawIR = await executePlan(plan, executeAgent, concurrency, batchProgress);
6953
+ onProgress?.({ phase: "phase-c", status: "running", message: "Pass 3c: Cross-reference validation..." });
6954
+ const { ir: linkedIR, report } = linkHarness(rawIR);
6955
+ const ir = linkedIR;
6956
+ if (report.warnings.length > 0) {
6957
+ for (const w of report.warnings) {
6958
+ onProgress?.({ phase: "phase-c", status: "warning", message: `\u26A0 ${w}` });
6959
+ }
6960
+ }
6961
+ onProgress?.({ phase: "phase-c", status: "success", message: "Pass 3c: Cross-reference validation", elapsed: (Date.now() - startTime) / 1e3 });
6962
+ onProgress?.({ phase: "assembly", status: "running", message: "Pass 4: Configuring MCP servers & settings..." });
6129
6963
  const settings = buildSettings(skeleton, registry);
6130
6964
  const mcpConfig = buildMcpConfig(skeleton, registry);
6965
+ const commandsRecord = {};
6966
+ for (const cmd of ir.commands) {
6967
+ commandsRecord[cmd.name] = cmd.content;
6968
+ }
6969
+ const agentsRecord = {};
6970
+ for (const agent of ir.agents) {
6971
+ agentsRecord[agent.name] = agent.content;
6972
+ }
6131
6973
  const projectProfile = {
6132
6974
  language: skeleton.outline.tech_stack[0] ?? "unknown",
6133
6975
  framework: skeleton.outline.tech_stack[1] ?? "none",
6134
6976
  scripts: {}
6135
- // scripts come from project scanning, not compilation
6136
6977
  };
6137
6978
  const intentPatterns = generateIntentPatterns(
6138
- harness.commands,
6139
- harness.agents ?? {},
6979
+ commandsRecord,
6980
+ agentsRecord,
6140
6981
  projectProfile
6141
6982
  );
6142
6983
  const intentPromptTemplate = compileIntentPrompt(
6143
- harness.commands,
6144
- harness.agents ?? {}
6984
+ commandsRecord,
6985
+ agentsRecord
6145
6986
  );
6146
6987
  const generationTimestamp = (/* @__PURE__ */ new Date()).toISOString();
6147
6988
  const intentHooks = {};
@@ -6149,7 +6990,27 @@ async function compile(intent, onProgress) {
6149
6990
  intentHooks["intent-router"] = renderIntentRouter(intentPatterns, generationTimestamp);
6150
6991
  intentHooks["intent-learner"] = renderIntentLearner();
6151
6992
  }
6152
- onProgress?.({ phase: "pass3", status: "success", message: "Pass 3: Configured MCP servers & settings" });
6993
+ onProgress?.({ phase: "assembly", status: "success", message: "Pass 4: Configured MCP servers & settings" });
6994
+ const commands = {};
6995
+ for (const cmd of ir.commands) {
6996
+ commands[cmd.name] = cmd.content;
6997
+ }
6998
+ const rules = {};
6999
+ for (const rule of ir.rules) {
7000
+ rules[rule.name] = rule.content;
7001
+ }
7002
+ const agents = {};
7003
+ for (const agent of ir.agents) {
7004
+ agents[agent.name] = agent.content;
7005
+ }
7006
+ const skills = {};
7007
+ for (const skill of ir.skills) {
7008
+ skills[skill.name] = skill.content;
7009
+ }
7010
+ const docs = {};
7011
+ for (const doc of ir.docs) {
7012
+ docs[doc.name] = doc.content;
7013
+ }
6153
7014
  const spec = {
6154
7015
  id: `env_${crypto.randomUUID()}`,
6155
7016
  intent,
@@ -6158,15 +7019,16 @@ async function compile(intent, onProgress) {
6158
7019
  description: skeleton.description,
6159
7020
  autonomy_level: 1,
6160
7021
  tools: skeleton.tools,
7022
+ ir,
6161
7023
  harness: {
6162
- claude_md: harness.claude_md,
7024
+ claude_md: renderClaudeMd(ir.meta, ir.sections),
6163
7025
  settings,
6164
7026
  mcp_config: mcpConfig,
6165
- commands: harness.commands,
6166
- rules: harness.rules,
6167
- skills: harness.skills ?? {},
6168
- agents: harness.agents ?? {},
6169
- docs: harness.docs,
7027
+ commands,
7028
+ rules,
7029
+ skills,
7030
+ agents,
7031
+ docs,
6170
7032
  hooks: intentHooks,
6171
7033
  intent_patterns: intentPatterns,
6172
7034
  intent_prompt_template: intentPromptTemplate
@@ -6914,12 +7776,20 @@ function summarizeSpec(spec, registry) {
6914
7776
  }
6915
7777
  }
6916
7778
  }
6917
- return {
6918
- toolCount: spec.tools.length,
7779
+ const counts = spec.ir ? {
7780
+ commandCount: spec.ir.commands.length,
7781
+ ruleCount: spec.ir.rules.length,
7782
+ skillCount: spec.ir.skills.length,
7783
+ agentCount: spec.ir.agents.length
7784
+ } : {
6919
7785
  commandCount: Object.keys(spec.harness.commands || {}).length,
6920
7786
  ruleCount: Object.keys(spec.harness.rules || {}).length,
6921
7787
  skillCount: Object.keys(spec.harness.skills || {}).length,
6922
- agentCount: Object.keys(spec.harness.agents || {}).length,
7788
+ agentCount: Object.keys(spec.harness.agents || {}).length
7789
+ };
7790
+ return {
7791
+ toolCount: spec.tools.length,
7792
+ ...counts,
6923
7793
  pluginCommands,
6924
7794
  envSetup
6925
7795
  };