zelari-code 1.28.0 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/cli/headless.js +14 -0
  2. package/dist/cli/headless.js.map +1 -1
  3. package/dist/cli/hooks/useSlashDispatch.js +11 -0
  4. package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
  5. package/dist/cli/kraken/executor.js +725 -42
  6. package/dist/cli/kraken/executor.js.map +1 -1
  7. package/dist/cli/kraken/graphMemory.js +27 -5
  8. package/dist/cli/kraken/graphMemory.js.map +1 -1
  9. package/dist/cli/kraken/graphStatus.js +59 -0
  10. package/dist/cli/kraken/graphStatus.js.map +1 -1
  11. package/dist/cli/kraken/planner.js +189 -12
  12. package/dist/cli/kraken/planner.js.map +1 -1
  13. package/dist/cli/kraken/planner.test.js +43 -0
  14. package/dist/cli/kraken/planner.test.js.map +1 -0
  15. package/dist/cli/kraken/runtime/compile.js +73 -0
  16. package/dist/cli/kraken/runtime/compile.js.map +1 -0
  17. package/dist/cli/kraken/runtime/runScriptPlan.js +195 -0
  18. package/dist/cli/kraken/runtime/runScriptPlan.js.map +1 -0
  19. package/dist/cli/kraken/scriptPlanner.js +286 -0
  20. package/dist/cli/kraken/scriptPlanner.js.map +1 -0
  21. package/dist/cli/kraken/scriptPlanner.test.js +152 -0
  22. package/dist/cli/kraken/scriptPlanner.test.js.map +1 -0
  23. package/dist/cli/kraken/skillSuggest.js +97 -0
  24. package/dist/cli/kraken/skillSuggest.js.map +1 -0
  25. package/dist/cli/kraken/skillSuggest.test.js +157 -0
  26. package/dist/cli/kraken/skillSuggest.test.js.map +1 -0
  27. package/dist/cli/kraken/weaknessMeter.js +183 -0
  28. package/dist/cli/kraken/weaknessMeter.js.map +1 -0
  29. package/dist/cli/kraken/weaknessMeter.test.js +212 -0
  30. package/dist/cli/kraken/weaknessMeter.test.js.map +1 -0
  31. package/dist/cli/kraken/workbench.js +296 -0
  32. package/dist/cli/kraken/workbench.js.map +1 -0
  33. package/dist/cli/kraken/workbench.test.js +253 -0
  34. package/dist/cli/kraken/workbench.test.js.map +1 -0
  35. package/dist/cli/kraken/workbenchView.js +155 -0
  36. package/dist/cli/kraken/workbenchView.js.map +1 -0
  37. package/dist/cli/kraken/workbenchView.test.js +130 -0
  38. package/dist/cli/kraken/workbenchView.test.js.map +1 -0
  39. package/dist/cli/main.bundled.js +2930 -353
  40. package/dist/cli/main.bundled.js.map +4 -4
  41. package/dist/cli/runHeadless.js +83 -8
  42. package/dist/cli/runHeadless.js.map +1 -1
  43. package/dist/cli/slashCommands.js +16 -0
  44. package/dist/cli/slashCommands.js.map +1 -1
  45. package/dist/cli/slashHandlers/krakenFanout.js +200 -0
  46. package/dist/cli/slashHandlers/krakenFanout.js.map +1 -0
  47. package/dist/cli/slashHandlers/krakenGraph.js +10 -3
  48. package/dist/cli/slashHandlers/krakenGraph.js.map +1 -1
  49. package/dist/cli/slashHandlers/krakenWorkbench.js +49 -0
  50. package/dist/cli/slashHandlers/krakenWorkbench.js.map +1 -0
  51. package/dist/cli/tools/krakenCsvFanout.js +260 -0
  52. package/dist/cli/tools/krakenCsvFanout.js.map +1 -0
  53. package/dist/cli/tools/krakenCsvFanout.test.js +200 -0
  54. package/dist/cli/tools/krakenCsvFanout.test.js.map +1 -0
  55. package/dist/cli/tools/krakenModel.js +32 -0
  56. package/dist/cli/tools/krakenModel.js.map +1 -1
  57. package/dist/cli/tools/krakenRadio.js.map +1 -1
  58. package/dist/cli/tools/taskTool.js +2 -2
  59. package/dist/cli/tools/taskTool.js.map +1 -1
  60. package/package.json +2 -2
@@ -886,8 +886,8 @@ function loadModelsRegistry(file2 = getModelsFilePath()) {
886
886
  }
887
887
  }
888
888
  function getCachedModels(provider, file2 = getModelsFilePath()) {
889
- const registry3 = loadModelsRegistry(file2);
890
- return registry3[provider];
889
+ const registry4 = loadModelsRegistry(file2);
890
+ return registry4[provider];
891
891
  }
892
892
  function isModelsCacheStale(provider, maxAgeMs = 6 * 60 * 60 * 1e3, file2 = getModelsFilePath(), now = Date.now()) {
893
893
  const entry = getCachedModels(provider, file2);
@@ -1829,10 +1829,10 @@ function mergeDefs(...defs) {
1829
1829
  function cloneDef(schema) {
1830
1830
  return mergeDefs(schema._zod.def);
1831
1831
  }
1832
- function getElementAtPath(obj, path44) {
1833
- if (!path44)
1832
+ function getElementAtPath(obj, path47) {
1833
+ if (!path47)
1834
1834
  return obj;
1835
- return path44.reduce((acc, key) => acc?.[key], obj);
1835
+ return path47.reduce((acc, key) => acc?.[key], obj);
1836
1836
  }
1837
1837
  function promiseAllObject(promisesObj) {
1838
1838
  const keys = Object.keys(promisesObj);
@@ -2160,11 +2160,11 @@ function explicitlyAborted(x, startIndex = 0) {
2160
2160
  }
2161
2161
  return false;
2162
2162
  }
2163
- function prefixIssues(path44, issues) {
2163
+ function prefixIssues(path47, issues) {
2164
2164
  return issues.map((iss) => {
2165
2165
  var _a3;
2166
2166
  (_a3 = iss).path ?? (_a3.path = []);
2167
- iss.path.unshift(path44);
2167
+ iss.path.unshift(path47);
2168
2168
  return iss;
2169
2169
  });
2170
2170
  }
@@ -2382,16 +2382,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2382
2382
  }
2383
2383
  function formatError(error51, mapper = (issue2) => issue2.message) {
2384
2384
  const fieldErrors = { _errors: [] };
2385
- const processError = (error52, path44 = []) => {
2385
+ const processError = (error52, path47 = []) => {
2386
2386
  for (const issue2 of error52.issues) {
2387
2387
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2388
- issue2.errors.map((issues) => processError({ issues }, [...path44, ...issue2.path]));
2388
+ issue2.errors.map((issues) => processError({ issues }, [...path47, ...issue2.path]));
2389
2389
  } else if (issue2.code === "invalid_key") {
2390
- processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
2390
+ processError({ issues: issue2.issues }, [...path47, ...issue2.path]);
2391
2391
  } else if (issue2.code === "invalid_element") {
2392
- processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
2392
+ processError({ issues: issue2.issues }, [...path47, ...issue2.path]);
2393
2393
  } else {
2394
- const fullpath = [...path44, ...issue2.path];
2394
+ const fullpath = [...path47, ...issue2.path];
2395
2395
  if (fullpath.length === 0) {
2396
2396
  fieldErrors._errors.push(mapper(issue2));
2397
2397
  } else {
@@ -2418,17 +2418,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2418
2418
  }
2419
2419
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2420
2420
  const result = { errors: [] };
2421
- const processError = (error52, path44 = []) => {
2421
+ const processError = (error52, path47 = []) => {
2422
2422
  var _a3, _b;
2423
2423
  for (const issue2 of error52.issues) {
2424
2424
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2425
- issue2.errors.map((issues) => processError({ issues }, [...path44, ...issue2.path]));
2425
+ issue2.errors.map((issues) => processError({ issues }, [...path47, ...issue2.path]));
2426
2426
  } else if (issue2.code === "invalid_key") {
2427
- processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
2427
+ processError({ issues: issue2.issues }, [...path47, ...issue2.path]);
2428
2428
  } else if (issue2.code === "invalid_element") {
2429
- processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
2429
+ processError({ issues: issue2.issues }, [...path47, ...issue2.path]);
2430
2430
  } else {
2431
- const fullpath = [...path44, ...issue2.path];
2431
+ const fullpath = [...path47, ...issue2.path];
2432
2432
  if (fullpath.length === 0) {
2433
2433
  result.errors.push(mapper(issue2));
2434
2434
  continue;
@@ -2460,8 +2460,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2460
2460
  }
2461
2461
  function toDotPath(_path) {
2462
2462
  const segs = [];
2463
- const path44 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2464
- for (const seg of path44) {
2463
+ const path47 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2464
+ for (const seg of path47) {
2465
2465
  if (typeof seg === "number")
2466
2466
  segs.push(`[${seg}]`);
2467
2467
  else if (typeof seg === "symbol")
@@ -13342,21 +13342,21 @@ var init_to_json_schema = __esm({
13342
13342
  // node_modules/zod/v4/core/json-schema-processors.js
13343
13343
  function toJSONSchema(input, params) {
13344
13344
  if ("_idmap" in input) {
13345
- const registry3 = input;
13345
+ const registry4 = input;
13346
13346
  const ctx2 = initializeContext({ ...params, processors: allProcessors });
13347
13347
  const defs = {};
13348
- for (const entry of registry3._idmap.entries()) {
13348
+ for (const entry of registry4._idmap.entries()) {
13349
13349
  const [_, schema] = entry;
13350
13350
  process2(schema, ctx2);
13351
13351
  }
13352
13352
  const schemas = {};
13353
13353
  const external = {
13354
- registry: registry3,
13354
+ registry: registry4,
13355
13355
  uri: params?.uri,
13356
13356
  defs
13357
13357
  };
13358
13358
  ctx2.external = external;
13359
- for (const entry of registry3._idmap.entries()) {
13359
+ for (const entry of registry4._idmap.entries()) {
13360
13360
  const [key, schema] = entry;
13361
13361
  extractDefs(ctx2, schema);
13362
13362
  schemas[key] = finalize(ctx2, schema);
@@ -15964,13 +15964,13 @@ function resolveRef(ref, ctx) {
15964
15964
  if (!ref.startsWith("#")) {
15965
15965
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15966
15966
  }
15967
- const path44 = ref.slice(1).split("/").filter(Boolean);
15968
- if (path44.length === 0) {
15967
+ const path47 = ref.slice(1).split("/").filter(Boolean);
15968
+ if (path47.length === 0) {
15969
15969
  return ctx.rootSchema;
15970
15970
  }
15971
15971
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15972
- if (path44[0] === defsKey) {
15973
- const key = path44[1];
15972
+ if (path47[0] === defsKey) {
15973
+ const key = path47[1];
15974
15974
  if (!key || !ctx.defs[key]) {
15975
15975
  throw new Error(`Reference not found: ${ref}`);
15976
15976
  }
@@ -18134,11 +18134,11 @@ var init_tools = __esm({
18134
18134
  if (!ctx.addDocument)
18135
18135
  return "Knowledge vault tool not available.";
18136
18136
  const title = args["title"] || "New Document";
18137
- const path44 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
18137
+ const path47 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
18138
18138
  const content = args["content"] || "";
18139
18139
  const tags = args["tags"] || [];
18140
18140
  ctx.addDocument({
18141
- path: path44,
18141
+ path: path47,
18142
18142
  title,
18143
18143
  content,
18144
18144
  format: "markdown",
@@ -18147,7 +18147,7 @@ var init_tools = __esm({
18147
18147
  workspaceId: ctx.workspaceId
18148
18148
  });
18149
18149
  ctx.addActivity("vault", "created document", title);
18150
- return `Document "${title}" created at "${path44}".`;
18150
+ return `Document "${title}" created at "${path47}".`;
18151
18151
  }
18152
18152
  }
18153
18153
  ];
@@ -18922,10 +18922,10 @@ function computeAgentTools(agent, aiConfig) {
18922
18922
  }
18923
18923
  return merged;
18924
18924
  }
18925
- function getToolDescriptions(toolNames, registry3) {
18925
+ function getToolDescriptions(toolNames, registry4) {
18926
18926
  const lines = ["AVAILABLE TOOLS (use ONLY these exact names):"];
18927
18927
  for (const name of toolNames) {
18928
- const tool = registry3.get(name);
18928
+ const tool = registry4.get(name);
18929
18929
  if (!tool)
18930
18930
  continue;
18931
18931
  let paramList;
@@ -18952,7 +18952,7 @@ function buildSystemPromptSplit(agent, options) {
18952
18952
  includeWorkspaceInPrompt = true,
18953
18953
  durableStateContext
18954
18954
  } = options;
18955
- const registry3 = new Map(tools.map((t) => [t.name, t]));
18955
+ const registry4 = new Map(tools.map((t) => [t.name, t]));
18956
18956
  const skills = computeAgentSkills(agent, aiConfig);
18957
18957
  const baseModules = getBasePromptModules(mode).filter((m) => !m.conditional || m.conditional(skills));
18958
18958
  const customModulesRaw = aiConfig?.customPromptModules ?? [];
@@ -18987,7 +18987,7 @@ ${projectInstructions.trim()}`);
18987
18987
  ` + skills.map((s) => `## ${s.name}
18988
18988
  ${s.systemPromptFragment}`).join("\n\n"));
18989
18989
  }
18990
- const toolBlock = getToolDescriptions(toolNames, registry3);
18990
+ const toolBlock = getToolDescriptions(toolNames, registry4);
18991
18991
  if (toolNames.length > 0) {
18992
18992
  stableParts.push(`# Tools
18993
18993
 
@@ -19619,6 +19619,48 @@ var init_krakenLive = __esm({
19619
19619
  });
19620
19620
 
19621
19621
  // packages/core/dist/shared/events.js
19622
+ function isBrainAgentStartEvent(e) {
19623
+ return e.type === "agent_start";
19624
+ }
19625
+ function isBrainAgentEndEvent(e) {
19626
+ return e.type === "agent_end";
19627
+ }
19628
+ function isBrainMessageStartEvent(e) {
19629
+ return e.type === "message_start";
19630
+ }
19631
+ function isBrainMessageDeltaEvent(e) {
19632
+ return e.type === "message_delta";
19633
+ }
19634
+ function isBrainMessageEndEvent(e) {
19635
+ return e.type === "message_end";
19636
+ }
19637
+ function isBrainThinkingDeltaEvent(e) {
19638
+ return e.type === "thinking_delta";
19639
+ }
19640
+ function isBrainToolExecutionStartEvent(e) {
19641
+ return e.type === "tool_execution_start";
19642
+ }
19643
+ function isBrainToolExecutionUpdateEvent(e) {
19644
+ return e.type === "tool_execution_update";
19645
+ }
19646
+ function isBrainToolExecutionEndEvent(e) {
19647
+ return e.type === "tool_execution_end";
19648
+ }
19649
+ function isBrainQueueUpdateEvent(e) {
19650
+ return e.type === "queue_update";
19651
+ }
19652
+ function isBrainSessionCompactedEvent(e) {
19653
+ return e.type === "session_compacted";
19654
+ }
19655
+ function isBrainErrorEvent(e) {
19656
+ return e.type === "error";
19657
+ }
19658
+ function isBrainMemberCostEvent(e) {
19659
+ return e.type === "member_cost";
19660
+ }
19661
+ function isBrainCouncilModeEvent(e) {
19662
+ return e.type === "council_mode";
19663
+ }
19622
19664
  function createBrainEvent(type, sessionId, data) {
19623
19665
  return {
19624
19666
  type,
@@ -19635,9 +19677,90 @@ var init_events = __esm({
19635
19677
  });
19636
19678
 
19637
19679
  // packages/core/dist/shared/eventBus.js
19680
+ var EventBus;
19638
19681
  var init_eventBus = __esm({
19639
19682
  "packages/core/dist/shared/eventBus.js"() {
19640
19683
  "use strict";
19684
+ EventBus = class {
19685
+ /** Per-type subscriber registry. */
19686
+ subscribers;
19687
+ /** Wildcard subscribers (receive every event). */
19688
+ wildcardSubscribers;
19689
+ constructor() {
19690
+ this.subscribers = /* @__PURE__ */ new Map();
19691
+ this.wildcardSubscribers = /* @__PURE__ */ new Set();
19692
+ }
19693
+ /** Subscribe to a specific event type. Returns an unsubscribe function. */
19694
+ subscribe(type, handler) {
19695
+ let set2 = this.subscribers.get(type);
19696
+ if (!set2) {
19697
+ set2 = /* @__PURE__ */ new Set();
19698
+ this.subscribers.set(type, set2);
19699
+ }
19700
+ const stored = handler;
19701
+ set2.add(stored);
19702
+ return () => {
19703
+ const current = this.subscribers.get(type);
19704
+ if (!current)
19705
+ return;
19706
+ current.delete(stored);
19707
+ if (current.size === 0)
19708
+ this.subscribers.delete(type);
19709
+ };
19710
+ }
19711
+ /** Subscribe to all events. Returns an unsubscribe function. */
19712
+ subscribeAll(handler) {
19713
+ this.wildcardSubscribers.add(handler);
19714
+ return () => {
19715
+ this.wildcardSubscribers.delete(handler);
19716
+ };
19717
+ }
19718
+ /**
19719
+ * Emit an event to all matching subscribers (typed first, then wildcard).
19720
+ * Errors in individual subscribers are logged but do not prevent other
19721
+ * subscribers from receiving the event.
19722
+ */
19723
+ emit(event) {
19724
+ const set2 = this.subscribers.get(event.type);
19725
+ if (set2) {
19726
+ for (const handler of set2)
19727
+ this.dispatch(handler, event);
19728
+ }
19729
+ for (const handler of this.wildcardSubscribers)
19730
+ this.dispatch(handler, event);
19731
+ }
19732
+ /** Remove all subscribers (typed + wildcard). */
19733
+ clear() {
19734
+ this.subscribers.clear();
19735
+ this.wildcardSubscribers.clear();
19736
+ }
19737
+ /**
19738
+ * Number of subscribers. With a `type`, returns typed subscribers for that
19739
+ * type; without, returns the total across all types plus wildcard handlers.
19740
+ */
19741
+ listenerCount(type) {
19742
+ if (type !== void 0) {
19743
+ return this.subscribers.get(type)?.size ?? 0;
19744
+ }
19745
+ let total = this.wildcardSubscribers.size;
19746
+ for (const set2 of this.subscribers.values())
19747
+ total += set2.size;
19748
+ return total;
19749
+ }
19750
+ /** Invoke a single subscriber, isolating sync throws and async rejections. */
19751
+ dispatch(handler, event) {
19752
+ try {
19753
+ const result = handler(event);
19754
+ if (result instanceof Promise) {
19755
+ result.catch((err) => {
19756
+ console.error("[eventBus] subscriber error:", err);
19757
+ });
19758
+ }
19759
+ } catch (err) {
19760
+ console.error("[eventBus] subscriber error:", err);
19761
+ }
19762
+ }
19763
+ };
19641
19764
  }
19642
19765
  });
19643
19766
 
@@ -19658,6 +19781,14 @@ var init_context = __esm({
19658
19781
  });
19659
19782
 
19660
19783
  // packages/core/dist/types/systemTypes.js
19784
+ function createDefaultSystemPromptConfig() {
19785
+ return {
19786
+ enabledSkills: [],
19787
+ enabledTools: [],
19788
+ customPromptModules: [],
19789
+ agentSkillConfigs: []
19790
+ };
19791
+ }
19661
19792
  var init_systemTypes = __esm({
19662
19793
  "packages/core/dist/types/systemTypes.js"() {
19663
19794
  "use strict";
@@ -21839,11 +21970,11 @@ var init_synthesisAudit = __esm({
21839
21970
  import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
21840
21971
  import { join as join2 } from "node:path";
21841
21972
  function loadNfrSpec(zelariRoot) {
21842
- const path44 = join2(zelariRoot, "nfr-spec.json");
21843
- if (!existsSync6(path44))
21973
+ const path47 = join2(zelariRoot, "nfr-spec.json");
21974
+ if (!existsSync6(path47))
21844
21975
  return null;
21845
21976
  try {
21846
- const raw = JSON.parse(readFileSync5(path44, "utf8"));
21977
+ const raw = JSON.parse(readFileSync5(path47, "utf8"));
21847
21978
  if (raw.version !== 1 || !Array.isArray(raw.targets))
21848
21979
  return null;
21849
21980
  return raw;
@@ -24149,9 +24280,9 @@ var init_types3 = __esm({
24149
24280
  import { readFileSync as readFileSync10 } from "node:fs";
24150
24281
  import { join as join8 } from "node:path";
24151
24282
  function readLessonsDeduped(zelariRoot) {
24152
- const path44 = join8(zelariRoot, LESSONS_FILE);
24283
+ const path47 = join8(zelariRoot, LESSONS_FILE);
24153
24284
  try {
24154
- const raw = readFileSync10(path44, "utf8");
24285
+ const raw = readFileSync10(path47, "utf8");
24155
24286
  const byId = /* @__PURE__ */ new Map();
24156
24287
  for (const line of raw.split(/\r?\n/)) {
24157
24288
  if (!line.trim())
@@ -24252,8 +24383,8 @@ function keywordsFrom(check2, signature) {
24252
24383
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
24253
24384
  }
24254
24385
  function writeLesson(zelariRoot, lesson) {
24255
- const path44 = join9(zelariRoot, LESSONS_FILE);
24256
- appendFileSync(path44, `${JSON.stringify(lesson)}
24386
+ const path47 = join9(zelariRoot, LESSONS_FILE);
24387
+ appendFileSync(path47, `${JSON.stringify(lesson)}
24257
24388
  `, "utf8");
24258
24389
  }
24259
24390
  function findSimilar(lessons, signature) {
@@ -24866,9 +24997,9 @@ function findCycle(nodes) {
24866
24997
  if (color.get(start) !== WHITE)
24867
24998
  continue;
24868
24999
  const stack = [[start, 0]];
24869
- const path44 = [];
25000
+ const path47 = [];
24870
25001
  color.set(start, GRAY);
24871
- path44.push(start);
25002
+ path47.push(start);
24872
25003
  while (stack.length > 0) {
24873
25004
  const top = stack[stack.length - 1];
24874
25005
  const [id, idx] = top;
@@ -24881,17 +25012,17 @@ function findCycle(nodes) {
24881
25012
  continue;
24882
25013
  const c = color.get(dep);
24883
25014
  if (c === GRAY) {
24884
- const at = path44.indexOf(dep);
24885
- return [...path44.slice(at), dep];
25015
+ const at = path47.indexOf(dep);
25016
+ return [...path47.slice(at), dep];
24886
25017
  }
24887
25018
  if (c === WHITE) {
24888
25019
  color.set(dep, GRAY);
24889
- path44.push(dep);
25020
+ path47.push(dep);
24890
25021
  stack.push([dep, 0]);
24891
25022
  }
24892
25023
  } else {
24893
25024
  color.set(id, BLACK);
24894
- path44.pop();
25025
+ path47.pop();
24895
25026
  stack.pop();
24896
25027
  }
24897
25028
  }
@@ -24978,10 +25109,15 @@ function countByStatus(graph) {
24978
25109
  counts[node.status] += 1;
24979
25110
  return counts;
24980
25111
  }
24981
- var DEFAULT_MAX_NODES;
25112
+ var TERMINAL_STATUSES, DEFAULT_MAX_NODES;
24982
25113
  var init_graph = __esm({
24983
25114
  "packages/core/dist/kraken/graph.js"() {
24984
25115
  "use strict";
25116
+ TERMINAL_STATUSES = [
25117
+ "done",
25118
+ "error",
25119
+ "skipped"
25120
+ ];
24985
25121
  DEFAULT_MAX_NODES = 24;
24986
25122
  }
24987
25123
  });
@@ -25091,16 +25227,1034 @@ var init_conflict = __esm({
25091
25227
  }
25092
25228
  });
25093
25229
 
25230
+ // packages/core/dist/kraken/weakness.js
25231
+ function weaknessFromVerdict(text) {
25232
+ if (typeof text !== "string")
25233
+ return 0;
25234
+ const trimmed = text.trim();
25235
+ if (trimmed === "")
25236
+ return 0;
25237
+ let score = 0;
25238
+ for (const re of SPECIFICITY_MARKERS) {
25239
+ if (re.test(trimmed))
25240
+ score += SPECIFICITY_MARKER_WEIGHT;
25241
+ }
25242
+ const clauses = trimmed.split(/[.!?;]+|\n+/).map((c) => c.trim()).filter((c) => c.length > 0 && /\b\w+ing\b|\b\w+ed\b|\bwill\b|\bcan\b|\bmust\b|\bshould\b|\bmay\b/i.test(c));
25243
+ const clausePenalty = Math.min(clauses.length, SPECIFICITY_CLAUSE_MAX) * SPECIFICITY_CLAUSE_WEIGHT;
25244
+ score += clausePenalty;
25245
+ return clamp01(score);
25246
+ }
25247
+ function weaknessScoreFromText(text) {
25248
+ return 1 - weaknessFromVerdict(text);
25249
+ }
25250
+ function weaknessFromMeter(meter) {
25251
+ return clamp01(1 - clamp01(meter.specificity));
25252
+ }
25253
+ function specificityFromAssumptions(assumptions) {
25254
+ if (!Array.isArray(assumptions) || assumptions.length === 0)
25255
+ return 0;
25256
+ const raw = Math.min(assumptions.length, 6) / 6;
25257
+ return clamp01(raw);
25258
+ }
25259
+ function rankByWeakness(candidates) {
25260
+ if (candidates.length === 0)
25261
+ return [];
25262
+ const raws = candidates.map((c) => computeRaw(c));
25263
+ const extScores = raws.filter((r) => r.source === "extensionSize").map((r) => r.raw);
25264
+ const extMax = extScores.length > 0 ? Math.max(...extScores) : 1;
25265
+ const extMin = extScores.length > 0 ? Math.min(...extScores) : 0;
25266
+ const extRange = extMax - extMin;
25267
+ const scored = raws.map((r) => {
25268
+ let score;
25269
+ switch (r.source) {
25270
+ case "extensionSize":
25271
+ score = extRange > 0 ? (r.raw - extMin) / extRange : 1;
25272
+ break;
25273
+ case "meter":
25274
+ score = clamp01(1 - clamp01(r.raw));
25275
+ break;
25276
+ case "heuristic":
25277
+ score = clamp01(r.raw);
25278
+ break;
25279
+ }
25280
+ return { candidate: r.candidate, score, source: r.source };
25281
+ });
25282
+ const indexed = scored.map((s, i) => ({ ...s, originalIndex: i }));
25283
+ indexed.sort((a, b) => {
25284
+ if (b.score !== a.score)
25285
+ return b.score - a.score;
25286
+ return a.originalIndex - b.originalIndex;
25287
+ });
25288
+ let lastScore;
25289
+ let lastRank = 0;
25290
+ let seen = 0;
25291
+ return indexed.map((s) => {
25292
+ seen += 1;
25293
+ if (lastScore === void 0 || s.score !== lastScore) {
25294
+ lastRank = seen;
25295
+ lastScore = s.score;
25296
+ }
25297
+ return {
25298
+ candidate: s.candidate,
25299
+ weaknessScore: s.score,
25300
+ rank: lastRank,
25301
+ source: s.source
25302
+ };
25303
+ });
25304
+ }
25305
+ function computeRaw(c) {
25306
+ if (typeof c.extensionSize === "number" && Number.isFinite(c.extensionSize) && c.extensionSize >= 0) {
25307
+ return { candidate: c, raw: c.extensionSize, source: "extensionSize" };
25308
+ }
25309
+ if (c.meter && typeof c.meter.specificity === "number" && Number.isFinite(c.meter.specificity)) {
25310
+ return { candidate: c, raw: clamp01(c.meter.specificity), source: "meter" };
25311
+ }
25312
+ return { candidate: c, raw: weaknessScoreFromText(c.text), source: "heuristic" };
25313
+ }
25314
+ function clamp01(n) {
25315
+ if (!Number.isFinite(n))
25316
+ return 0;
25317
+ if (n < 0)
25318
+ return 0;
25319
+ if (n > 1)
25320
+ return 1;
25321
+ return n;
25322
+ }
25323
+ function pickWeakest(candidates) {
25324
+ const ranked = rankByWeakness(candidates);
25325
+ return ranked.length > 0 ? ranked[0].candidate : void 0;
25326
+ }
25327
+ function filterByWeakness(candidates, threshold) {
25328
+ const ranked = rankByWeakness(candidates);
25329
+ return ranked.filter((r) => r.weaknessScore >= threshold).map((r) => r.candidate);
25330
+ }
25331
+ var BENNETTS_RAZOR, BENNETTS_RAZOR_SHORT, SPECIFICITY_MARKERS, SPECIFICITY_MARKER_WEIGHT, SPECIFICITY_CLAUSE_WEIGHT, SPECIFICITY_CLAUSE_MAX, WEAKNESS_METER_PROMPT, WeaknessMeterResponseSchema;
25332
+ var init_weakness = __esm({
25333
+ "packages/core/dist/kraken/weakness.js"() {
25334
+ "use strict";
25335
+ init_zod();
25336
+ BENNETTS_RAZOR = [
25337
+ "Bennett's Razor (arXiv:2301.12987): explanations should be no more specific than necessary.",
25338
+ "When two solutions both satisfy the task, prefer the one that assumes the least.",
25339
+ 'A claim is "more specific" when it pins down an exact value, path, version, or invariant that a more general plan would not have to commit to.',
25340
+ "Specificity is a tie-breaker, not a goal: a plan that is too vague to act on is still useless.",
25341
+ "Weakness ranking only applies among solutions that already meet the bar; do not weaken a passing plan below the bar in the name of weakness."
25342
+ ].join(" ");
25343
+ BENNETTS_RAZOR_SHORT = "Prefer the solution that is no more specific than necessary.";
25344
+ SPECIFICITY_MARKERS = [
25345
+ /\bguarantee[ds]?\b/i,
25346
+ /\bexact(?:ly)?\b/i,
25347
+ /\bmust\b/i,
25348
+ /\bshall\b/i,
25349
+ /\balways\b/i,
25350
+ /\bnever\b/i,
25351
+ /\brequire[ds]?\b/i,
25352
+ /\bmandatory\b/i,
25353
+ /\bline\s+\d+/i,
25354
+ // "line 42"
25355
+ /\bversion\s+[\d.]+/i,
25356
+ // "version 1.2.3"
25357
+ /\bv?\d+\.\d+\.\d+\b/,
25358
+ // bare semver
25359
+ /\b[0-9a-f]{7,40}\b/i,
25360
+ // git short SHA / commit hash
25361
+ /\bthe\s+(?:file|path)\s+(?:is|at)\b/i,
25362
+ /\bprecise(?:ly)?\b/i,
25363
+ /\bassert(?:s|ed|ion)?\b/i,
25364
+ /\bconfirm(?:s|ed)?\b/i,
25365
+ /\bwill\s+(?:definitely|certainly|always)\b/i
25366
+ ];
25367
+ SPECIFICITY_MARKER_WEIGHT = 0.25;
25368
+ SPECIFICITY_CLAUSE_WEIGHT = 0.05;
25369
+ SPECIFICITY_CLAUSE_MAX = 6;
25370
+ WEAKNESS_METER_PROMPT = `You are measuring the SPECIFICITY of a candidate solution to a software task.
25371
+
25372
+ Specificity means: how many specific commitments does this solution make that a more general plan would not have to make? Examples of specific commitments: exact file paths, exact line numbers, exact semver versions, exact function signatures, guarantees about runtime behaviour, assertions about what other agents/users will do.
25373
+
25374
+ A maximally general solution is one that asserts nothing beyond the task itself ("just do the task"). A maximally specific solution is one that pins every possible value, path, and invariant.
25375
+
25376
+ Output ONLY a JSON object of the form:
25377
+ {"specificity": <float in [0,1]>, "assumptions": [<short string>, ...]}
25378
+
25379
+ where
25380
+ - specificity = 0.0 \u2192 solution asserts nothing beyond the task
25381
+ - specificity = 1.0 \u2192 solution pins every value, path, version, and invariant
25382
+ - assumptions = the list of specific commitments you identified, each \u2264 12 words, deduped, sorted by strength (most specific first). Cap the list at 12.
25383
+
25384
+ Do not add prose, do not add a code fence, do not explain your reasoning. JSON only.`;
25385
+ WeaknessMeterResponseSchema = external_exports.object({
25386
+ specificity: external_exports.number().min(0).max(1),
25387
+ assumptions: external_exports.array(external_exports.string().min(1).max(200)).max(12)
25388
+ });
25389
+ }
25390
+ });
25391
+
25392
+ // packages/core/dist/kraken/verdict.js
25393
+ function parseVerifyVerdict(text) {
25394
+ const source = typeof text === "string" ? text : "";
25395
+ if (source.trim() === "")
25396
+ return { verdict: "unknown", findings: "" };
25397
+ VERDICT_LINE.lastIndex = 0;
25398
+ let match;
25399
+ let last = null;
25400
+ while ((match = VERDICT_LINE.exec(source)) !== null) {
25401
+ last = match;
25402
+ if (match.index === VERDICT_LINE.lastIndex)
25403
+ VERDICT_LINE.lastIndex += 1;
25404
+ }
25405
+ if (!last) {
25406
+ return { verdict: "unknown", findings: capFindings(source) };
25407
+ }
25408
+ const verdict = last[1].toUpperCase() === "FAIL" ? "fail" : "pass";
25409
+ const findings = capFindings(source.slice(0, last.index));
25410
+ return { verdict, findings };
25411
+ }
25412
+ function extractRequirementsBlock(text) {
25413
+ if (typeof text !== "string" || text.trim() === "")
25414
+ return [];
25415
+ const re = /```json\s*([\s\S]*?)```/gi;
25416
+ let m;
25417
+ let last = null;
25418
+ while ((m = re.exec(text)) !== null) {
25419
+ last = m[1];
25420
+ if (m.index === re.lastIndex)
25421
+ re.lastIndex += 1;
25422
+ }
25423
+ if (!last)
25424
+ return [];
25425
+ try {
25426
+ const obj = JSON.parse(last);
25427
+ if (!obj || !Array.isArray(obj.requirements))
25428
+ return [];
25429
+ const out = [];
25430
+ for (const r of obj.requirements) {
25431
+ if (!r || typeof r !== "object")
25432
+ continue;
25433
+ const row = r;
25434
+ const req = typeof row.requirement === "string" ? row.requirement : "";
25435
+ const metRaw = typeof row.met === "string" ? row.met.toLowerCase() : "";
25436
+ const met = metRaw === "pass" || metRaw === "true" ? "pass" : metRaw === "fail" || metRaw === "false" ? "fail" : "unknown";
25437
+ const evidence = typeof row.evidence === "string" ? row.evidence : void 0;
25438
+ if (req)
25439
+ out.push({ requirement: req, met, ...evidence ? { evidence } : {} });
25440
+ }
25441
+ return out;
25442
+ } catch {
25443
+ return [];
25444
+ }
25445
+ }
25446
+ function parsePersonaVerdict(text) {
25447
+ const base = parseVerifyVerdict(text);
25448
+ return {
25449
+ verdict: base.verdict,
25450
+ findings: base.findings,
25451
+ requirements: extractRequirementsBlock(text),
25452
+ // Bennett's weakness = "how little the reviewer's free text asserts"
25453
+ // (arXiv:2301.12987). `weaknessScoreFromText` is the weakness form
25454
+ // (1.0 = maximally general / no claims; 0.0 = maximally specific).
25455
+ // The verdict gate is the trailer; weakness is metadata surfaced in
25456
+ // the workbench so a user can see whether a PASS was earned by a
25457
+ // tightly-asserted or loosely-claimed reviewer.
25458
+ weaknessScore: weaknessScoreFromText(text)
25459
+ };
25460
+ }
25461
+ function capFindings(raw) {
25462
+ const trimmed = raw.trim();
25463
+ if (trimmed.length <= MAX_FINDINGS_CHARS)
25464
+ return trimmed;
25465
+ return `${trimmed.slice(0, MAX_FINDINGS_CHARS)}
25466
+ \u2026 [truncated]`;
25467
+ }
25468
+ var MAX_FINDINGS_CHARS, VERDICT_LINE;
25469
+ var init_verdict = __esm({
25470
+ "packages/core/dist/kraken/verdict.js"() {
25471
+ "use strict";
25472
+ init_weakness();
25473
+ MAX_FINDINGS_CHARS = 2800;
25474
+ VERDICT_LINE = /^[\s>*_-]*VERDICT[\s*_]*:[\s*_]*(PASS|FAIL)\b/gim;
25475
+ }
25476
+ });
25477
+
25478
+ // packages/core/dist/kraken/personas/registry.js
25479
+ function registerPersona(p3) {
25480
+ registry3.set(p3.kind, p3);
25481
+ }
25482
+ function getPersona(kind) {
25483
+ return registry3.get(kind);
25484
+ }
25485
+ function listPersonas() {
25486
+ return [...registry3.values()];
25487
+ }
25488
+ function isReviewerKind(kind) {
25489
+ return kind === "verify" || kind === "spec" || kind === "conformance";
25490
+ }
25491
+ function defaultPersonaParse(text) {
25492
+ return parsePersonaVerdict(text);
25493
+ }
25494
+ var registry3;
25495
+ var init_registry = __esm({
25496
+ "packages/core/dist/kraken/personas/registry.js"() {
25497
+ "use strict";
25498
+ init_verdict();
25499
+ registry3 = /* @__PURE__ */ new Map();
25500
+ }
25501
+ });
25502
+
25503
+ // packages/core/dist/kraken/personas/specReviewer.js
25504
+ var SYSTEM_PROMPT, specPersona;
25505
+ var init_specReviewer = __esm({
25506
+ "packages/core/dist/kraken/personas/specReviewer.js"() {
25507
+ "use strict";
25508
+ init_registry();
25509
+ SYSTEM_PROMPT = [
25510
+ "You are the spec-reviewer for Kraken. You compare a writer's delivered",
25511
+ "work against a written spec or plan and judge each requirement.",
25512
+ "",
25513
+ 'Your bias is CONSERVATIVE. When the spec said "must do X" and the writer',
25514
+ "did a reasonable but slightly different thing, that is a FAIL. Specs",
25515
+ "are meant to catch exactly this kind of drift.",
25516
+ "",
25517
+ "## Output format",
25518
+ "",
25519
+ "You MUST end with a verdict trailer on its own line:",
25520
+ "",
25521
+ " VERDICT: PASS",
25522
+ "or",
25523
+ " VERDICT: FAIL",
25524
+ "",
25525
+ "When the verdict is FAIL, you MUST also emit a per-requirement table in",
25526
+ "a JSON code block, BEFORE the trailer:",
25527
+ "",
25528
+ "```json",
25529
+ "{",
25530
+ ' "requirements": [',
25531
+ ' { "requirement": "<verbatim from the spec>", "met": "pass|fail|unknown", "evidence": "<path, line, command output>" },',
25532
+ " ...",
25533
+ " ]",
25534
+ "}",
25535
+ "```",
25536
+ "",
25537
+ "Rules:",
25538
+ " - Cite concrete evidence (file path, function name, command output).",
25539
+ ' "Looks fine" is not evidence.',
25540
+ " - When a requirement is partially met, mark it `fail` with evidence",
25541
+ " describing the gap. The downstream `fix` tentacle will read this.",
25542
+ " - When a requirement is genuinely impossible to assess without running",
25543
+ " code, mark it `unknown` and explain why in evidence.",
25544
+ " - The verdict trailer is the GATE: the executor will not read the",
25545
+ " table if the trailer says PASS. The table is for diagnostics.",
25546
+ " - Keep the table terse. One line per row.",
25547
+ "",
25548
+ "## What you do NOT do",
25549
+ "",
25550
+ " - You do not modify any files. You are a judge, not a fixer.",
25551
+ " - You do not invent requirements that are not in the spec.",
25552
+ " - You do not say PASS when the spec listed requirements you did not",
25553
+ " actually assess. Either assess them or mark them `unknown`."
25554
+ ].join("\n");
25555
+ specPersona = {
25556
+ kind: "spec",
25557
+ label: "spec-reviewer",
25558
+ description: "Compares a writer's output against a written spec, per-requirement.",
25559
+ systemPrompt: SYSTEM_PROMPT
25560
+ // Default parser handles both the trailer and the requirements block.
25561
+ };
25562
+ registerPersona(specPersona);
25563
+ }
25564
+ });
25565
+
25566
+ // packages/core/dist/kraken/personas/conformance.js
25567
+ var SYSTEM_PROMPT2, conformancePersona;
25568
+ var init_conformance = __esm({
25569
+ "packages/core/dist/kraken/personas/conformance.js"() {
25570
+ "use strict";
25571
+ init_registry();
25572
+ SYSTEM_PROMPT2 = [
25573
+ "You are the conformance-reviewer for Kraken. You compare a writer's",
25574
+ "delivered work against the USER'S ORIGINAL VERBATIM PROMPT \u2014 not a",
25575
+ "spec, not a plan, not what the writer decided to do.",
25576
+ "",
25577
+ 'Your bias is LITERAL. When the user said "use JWT", "use session',
25578
+ 'cookies" is a FAIL even if it works. When the user said "ship a CLI",',
25579
+ '"ship a library" is a FAIL even if the library is excellent.',
25580
+ "",
25581
+ "## Decomposition",
25582
+ "",
25583
+ "Before judging, decompose the prompt into the discrete asks the user",
25584
+ "made. A good decomposition:",
25585
+ " - Each user-stated requirement gets one row.",
25586
+ ' - Each user-stated preference ("preferably X", "ideally Y") gets one row.',
25587
+ ' - Each user-stated constraint ("no dependencies", "stay under 100 LOC") gets one row.',
25588
+ " - If the prompt is ambiguous, decompose into the LITERAL reading AND",
25589
+ " note in the evidence that a more permissive reading would have passed.",
25590
+ "",
25591
+ "## Output format",
25592
+ "",
25593
+ "You MUST end with a verdict trailer on its own line:",
25594
+ "",
25595
+ " VERDICT: PASS",
25596
+ "or",
25597
+ " VERDICT: FAIL",
25598
+ "",
25599
+ "When the verdict is FAIL, you MUST also emit a per-requirement table in",
25600
+ "a JSON code block, BEFORE the trailer:",
25601
+ "",
25602
+ "```json",
25603
+ "{",
25604
+ ' "requirements": [',
25605
+ ' { "requirement": "<verbatim from the user prompt>", "met": "pass|fail|unknown", "evidence": "<path, line, output>" },',
25606
+ " ...",
25607
+ " ]",
25608
+ "}",
25609
+ "```",
25610
+ "",
25611
+ "Rules:",
25612
+ " - Cite concrete evidence (file path, line, function name, command",
25613
+ ' output) for every row. "Looks right" is not evidence.',
25614
+ " - When a literal reading would have failed but a more permissive",
25615
+ " reading would have passed, mark it `fail` with evidence explaining",
25616
+ " the gap. The user is the authority on what they meant.",
25617
+ ' - When the prompt is genuinely too vague to decompose (e.g. "make it',
25618
+ ' better"), say so explicitly in your findings and mark all rows',
25619
+ " `unknown`. The downstream fix node will then ask the user.",
25620
+ " - Keep the table terse. One line per row.",
25621
+ "",
25622
+ "## What you do NOT do",
25623
+ "",
25624
+ " - You do not consult any spec, plan, or intermediate artifact. Only the",
25625
+ " original user prompt.",
25626
+ " - You do not modify any files. You are a judge, not a fixer.",
25627
+ " - You do not say PASS to be polite. The user has only one chance to",
25628
+ " see your verdict before shipping; be honest."
25629
+ ].join("\n");
25630
+ conformancePersona = {
25631
+ kind: "conformance",
25632
+ label: "conformance-reviewer",
25633
+ description: "Compares a writer's output against the user's original verbatim prompt.",
25634
+ systemPrompt: SYSTEM_PROMPT2
25635
+ };
25636
+ registerPersona(conformancePersona);
25637
+ }
25638
+ });
25639
+
25640
+ // packages/core/dist/kraken/personas/index.js
25641
+ var init_personas = __esm({
25642
+ "packages/core/dist/kraken/personas/index.js"() {
25643
+ "use strict";
25644
+ init_specReviewer();
25645
+ init_conformance();
25646
+ init_registry();
25647
+ }
25648
+ });
25649
+
25650
+ // packages/core/dist/kraken/runtime/types.js
25651
+ var PlanError;
25652
+ var init_types6 = __esm({
25653
+ "packages/core/dist/kraken/runtime/types.js"() {
25654
+ "use strict";
25655
+ PlanError = class extends Error {
25656
+ name = "PlanError";
25657
+ kind;
25658
+ cause;
25659
+ constructor(kind, message, cause) {
25660
+ super(message);
25661
+ this.kind = kind;
25662
+ if (cause !== void 0)
25663
+ this.cause = cause;
25664
+ }
25665
+ };
25666
+ }
25667
+ });
25668
+
25669
+ // packages/core/dist/kraken/runtime/sandbox.js
25670
+ import vm from "node:vm";
25671
+ function scanForFootguns(bundleCode) {
25672
+ if (bundleCode.length > MAX_BUNDLE_BYTES) {
25673
+ return [
25674
+ `bundle is ${bundleCode.length} bytes; max is ${MAX_BUNDLE_BYTES} (LLM is almost certainly looping)`
25675
+ ];
25676
+ }
25677
+ const hits = [];
25678
+ for (const re of FORBIDDEN_TOKENS) {
25679
+ const m = re.exec(bundleCode);
25680
+ if (m)
25681
+ hits.push(`forbidden token "${m[0]}"`);
25682
+ }
25683
+ return hits;
25684
+ }
25685
+ function deepFreeze(value) {
25686
+ if (value === null || typeof value !== "object")
25687
+ return value;
25688
+ if (Object.isFrozen(value))
25689
+ return value;
25690
+ const obj = value;
25691
+ for (const k of Object.keys(obj))
25692
+ deepFreeze(obj[k]);
25693
+ return Object.freeze(value);
25694
+ }
25695
+ async function runInSandbox(opts) {
25696
+ const { bundleCode, sdk, timeoutMs } = opts;
25697
+ if (!opts.skipFootgunScan) {
25698
+ const footguns = scanForFootguns(bundleCode);
25699
+ if (footguns.length > 0) {
25700
+ throw new PlanError("sandbox_breach", `script bundle failed footgun scan: ${footguns.join("; ")}`);
25701
+ }
25702
+ }
25703
+ const frozen = deepFreeze({ ...sdk });
25704
+ const sandbox = {
25705
+ __zelari_sdk__: frozen,
25706
+ // A console that prefixes lines with the graph id; useful for the
25707
+ // workbench view and harmless to expose (it just logs to stdout).
25708
+ console: makeSandboxConsole()
25709
+ };
25710
+ const context = vm.createContext(sandbox, {
25711
+ name: "kraken-script",
25712
+ codeGeneration: { strings: false, wasm: false }
25713
+ });
25714
+ const start = Date.now();
25715
+ const vmOpts = {
25716
+ timeout: Math.max(1, timeoutMs),
25717
+ displayErrors: true,
25718
+ breakOnSigint: true
25719
+ };
25720
+ try {
25721
+ const value = await vm.runInContext(bundleCode, context, vmOpts);
25722
+ return { value, durationMs: Date.now() - start };
25723
+ } catch (err) {
25724
+ const msg = err && typeof err === "object" && "message" in err ? String(err.message ?? "") : String(err);
25725
+ if (/timed out/i.test(msg)) {
25726
+ throw new PlanError("budget_exceeded", `script exceeded ${timeoutMs}ms budget`);
25727
+ }
25728
+ if (opts.signal?.aborted) {
25729
+ throw new PlanError("cancelled", "script aborted by host");
25730
+ }
25731
+ if (err instanceof PlanError)
25732
+ throw err;
25733
+ throw new PlanError("runtime_error", msg, err);
25734
+ }
25735
+ }
25736
+ function makeSandboxConsole() {
25737
+ const prefix = "[kraken-script]";
25738
+ return {
25739
+ ...console,
25740
+ log: (...args) => console.log(prefix, ...args),
25741
+ info: (...args) => console.info(prefix, ...args),
25742
+ warn: (...args) => console.warn(prefix, ...args),
25743
+ error: (...args) => console.error(prefix, ...args)
25744
+ };
25745
+ }
25746
+ var MAX_BUNDLE_BYTES, FORBIDDEN_TOKENS;
25747
+ var init_sandbox = __esm({
25748
+ "packages/core/dist/kraken/runtime/sandbox.js"() {
25749
+ "use strict";
25750
+ init_types6();
25751
+ MAX_BUNDLE_BYTES = 256 * 1024;
25752
+ FORBIDDEN_TOKENS = [
25753
+ /\bprocess\b/,
25754
+ /\brequire\b/,
25755
+ /\bmodule\b/,
25756
+ /\bexports\b/,
25757
+ /\b__dirname\b/,
25758
+ /\b__filename\b/,
25759
+ /\bglobalThis\b/,
25760
+ /\bglobal\b(?!\s*=)/,
25761
+ /\bBuffer\b/,
25762
+ /\beval\b/,
25763
+ /\bFunction\s*\(/,
25764
+ /\bnew\s+Function\b/
25765
+ ];
25766
+ }
25767
+ });
25768
+
25769
+ // packages/core/dist/kraken/runtime/runner.js
25770
+ function resolveMaxTentacles(env = process.env) {
25771
+ const raw = env.ZELARI_KRAKEN_MAX_TENTACLES;
25772
+ if (raw === void 0 || raw === "")
25773
+ return DEFAULT_MAX_TENTACLES;
25774
+ const n = Number.parseInt(raw, 10);
25775
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_TENTACLES;
25776
+ }
25777
+ function resolvePlanTimeoutMs(env = process.env) {
25778
+ const raw = env.ZELARI_KRAKEN_PLAN_TIMEOUT_MS;
25779
+ if (raw === void 0 || raw === "")
25780
+ return DEFAULT_PLAN_TIMEOUT_MS;
25781
+ const n = Number.parseInt(raw, 10);
25782
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_PLAN_TIMEOUT_MS;
25783
+ }
25784
+ function cap(text, max) {
25785
+ if (text.length <= max)
25786
+ return text;
25787
+ return `${text.slice(0, max)}
25788
+ \u2026 [truncated]`;
25789
+ }
25790
+ function buildRef(id, opts, result) {
25791
+ const findings = cap(result.ok ? (result.result ?? "").trim() : result.error ?? "unknown error", MAX_FINDINGS_CHARS);
25792
+ const ref = {
25793
+ id,
25794
+ kind: opts.kind,
25795
+ label: opts.label,
25796
+ status: result.ok ? "done" : "error",
25797
+ findings,
25798
+ scope: opts.scope,
25799
+ ...result.durationMs !== void 0 ? { durationMs: result.durationMs } : {},
25800
+ ...result.worktree !== void 0 ? { worktree: result.worktree } : {}
25801
+ };
25802
+ if (isReviewerKind(opts.kind)) {
25803
+ ref.verdict = defaultPersonaParse(findings).verdict;
25804
+ }
25805
+ return ref;
25806
+ }
25807
+ var DEFAULT_MAX_TENTACLES, DEFAULT_PLAN_TIMEOUT_MS, ScriptRunner;
25808
+ var init_runner = __esm({
25809
+ "packages/core/dist/kraken/runtime/runner.js"() {
25810
+ "use strict";
25811
+ init_verdict();
25812
+ init_personas();
25813
+ init_types6();
25814
+ DEFAULT_MAX_TENTACLES = 200;
25815
+ DEFAULT_PLAN_TIMEOUT_MS = 30 * 6e4;
25816
+ ScriptRunner = class {
25817
+ host;
25818
+ goal;
25819
+ graphId;
25820
+ parentCwd;
25821
+ sessionId;
25822
+ maxTentacles;
25823
+ planTimeoutMs;
25824
+ startTime;
25825
+ tentaclesById = /* @__PURE__ */ new Map();
25826
+ tentacleCount = 0;
25827
+ mergeCount = 0;
25828
+ cancelled = false;
25829
+ constructor(opts) {
25830
+ this.host = opts.host;
25831
+ this.goal = opts.goal;
25832
+ this.graphId = opts.graphId;
25833
+ this.parentCwd = opts.parentCwd;
25834
+ this.sessionId = opts.sessionId;
25835
+ this.maxTentacles = opts.maxTentacles ?? resolveMaxTentacles();
25836
+ this.planTimeoutMs = opts.planTimeoutMs ?? resolvePlanTimeoutMs();
25837
+ this.startTime = Date.now();
25838
+ }
25839
+ /** Build the `PlanCapabilities` object the sandbox will see. */
25840
+ buildSdk() {
25841
+ return {
25842
+ tentacle: (opts) => this.callTentacle(opts),
25843
+ barrier: (refs) => this.callBarrier(refs),
25844
+ race: (refs) => this.callRace(refs),
25845
+ while_: (cond, body, maxIter) => this.callWhile(cond, body, maxIter),
25846
+ until: (cond, body, maxIter) => this.callUntil(cond, body, maxIter),
25847
+ merge: (refs, opts) => this.callMerge(refs, opts),
25848
+ checkpoint: (label) => this.callCheckpoint(label),
25849
+ log: (msg, data) => this.callLog(msg, data),
25850
+ emit: (payload) => this.callEmit(payload),
25851
+ getContext: () => this.callGetContext(),
25852
+ sendTo: (peerId, payload) => this.callSendTo(peerId, payload)
25853
+ };
25854
+ }
25855
+ /** One tentacle = one `runTentacle` call. The host returns a raw result;
25856
+ * we turn it into a `TentacleRef` and stash it. */
25857
+ async callTentacle(opts) {
25858
+ if (this.cancelled)
25859
+ throw new PlanError("cancelled", "runner is cancelled");
25860
+ if (this.tentacleCount >= this.maxTentacles) {
25861
+ throw new PlanError("budget_exceeded", `tentacle cap (${this.maxTentacles}) reached; raise ZELARI_KRAKEN_MAX_TENTACLES`);
25862
+ }
25863
+ if (this.tentacleCount > 0 && Date.now() - this.startTime > this.planTimeoutMs) {
25864
+ throw new PlanError("budget_exceeded", `plan wall-clock budget (${this.planTimeoutMs}ms) exceeded`);
25865
+ }
25866
+ this.tentacleCount += 1;
25867
+ const id = `t${String(this.tentacleCount).padStart(4, "0")}`;
25868
+ const res = await this.host.runTentacle({
25869
+ node: opts,
25870
+ parentCwd: this.parentCwd,
25871
+ sessionId: this.sessionId
25872
+ });
25873
+ const ref = buildRef(id, opts, res);
25874
+ this.tentaclesById.set(id, ref);
25875
+ return ref;
25876
+ }
25877
+ async callBarrier(refs) {
25878
+ if (refs.some((r) => !this.tentaclesById.has(r.id))) {
25879
+ throw new PlanError("runtime_error", "barrier() received a ref not in this runner");
25880
+ }
25881
+ return refs;
25882
+ }
25883
+ async callRace(refs) {
25884
+ if (refs.length === 0) {
25885
+ throw new PlanError("runtime_error", "race() requires at least one ref");
25886
+ }
25887
+ let winner = refs[0];
25888
+ for (const r of refs) {
25889
+ const a = winner.durationMs ?? Number.POSITIVE_INFINITY;
25890
+ const b = r.durationMs ?? Number.POSITIVE_INFINITY;
25891
+ if (b < a || b === a && r.id < winner.id)
25892
+ winner = r;
25893
+ }
25894
+ return winner;
25895
+ }
25896
+ async callWhile(cond, body, maxIter) {
25897
+ if (maxIter < 1)
25898
+ throw new PlanError("runtime_error", "while_() maxIter must be \u2265 1");
25899
+ const out = [];
25900
+ let iter = 0;
25901
+ while (await cond()) {
25902
+ if (iter >= maxIter) {
25903
+ throw new PlanError("budget_exceeded", `while_() exceeded maxIter=${maxIter} without cond turning false`);
25904
+ }
25905
+ out.push(await body());
25906
+ iter += 1;
25907
+ }
25908
+ return out;
25909
+ }
25910
+ async callUntil(cond, body, maxIter) {
25911
+ return this.callWhile(async () => !await cond(), body, maxIter);
25912
+ }
25913
+ /** One merge per plan. A second call is a structured error. */
25914
+ async callMerge(refs, opts = {}) {
25915
+ if (this.mergeCount > 0) {
25916
+ throw new PlanError("merge_already_done", "merge() called more than once; a plan can merge at most one batch");
25917
+ }
25918
+ if (refs.length === 0) {
25919
+ throw new PlanError("runtime_error", "merge() requires at least one ref");
25920
+ }
25921
+ for (const r of refs) {
25922
+ if (!this.tentaclesById.has(r.id)) {
25923
+ throw new PlanError("runtime_error", `merge() received unknown ref "${r.id}"`);
25924
+ }
25925
+ }
25926
+ this.mergeCount += 1;
25927
+ return this.host.mergeWorktrees({
25928
+ refs,
25929
+ parentCwd: this.parentCwd,
25930
+ strategy: opts.strategy ?? "squash-sequential",
25931
+ ...opts.message ? { message: opts.message } : {},
25932
+ ...opts.cleanup !== void 0 ? { cleanup: opts.cleanup } : {}
25933
+ });
25934
+ }
25935
+ async callCheckpoint(label) {
25936
+ const snapshot = {
25937
+ graphId: this.graphId,
25938
+ goal: this.goal,
25939
+ takenAt: (/* @__PURE__ */ new Date()).toISOString(),
25940
+ completed: [...this.tentaclesById.values()].filter((r) => r.status === "done"),
25941
+ inFlight: [...this.tentaclesById.values()].filter((r) => r.status === "running"),
25942
+ failed: [...this.tentaclesById.values()].filter((r) => r.status === "error"),
25943
+ pending: []
25944
+ };
25945
+ const path47 = await this.host.saveSnapshot(snapshot, ".zelari/kraken/snapshots");
25946
+ this.host.log(`checkpoint: ${label ?? "auto"} \u2192 ${path47}`);
25947
+ return snapshot;
25948
+ }
25949
+ callLog(msg, data) {
25950
+ const line = data ? `${msg} ${JSON.stringify(data)}` : msg;
25951
+ this.host.log(line);
25952
+ }
25953
+ callEmit(payload) {
25954
+ this.host.log(`emit: ${payload.kind}${payload.detail ? ` \u2014 ${payload.detail}` : ""}`);
25955
+ }
25956
+ callGetContext() {
25957
+ return {
25958
+ graphId: this.graphId,
25959
+ goal: this.goal,
25960
+ parentCwd: this.parentCwd,
25961
+ sessionId: this.sessionId,
25962
+ tentacles: this.tentaclesById,
25963
+ maxTentacles: this.maxTentacles,
25964
+ planTimeoutMs: this.planTimeoutMs
25965
+ };
25966
+ }
25967
+ callSendTo(peerId, payload) {
25968
+ this.host.log(`sendTo(${peerId}): ${payload.kind}${payload.detail ? ` \u2014 ${payload.detail}` : ""}`);
25969
+ }
25970
+ /** Called by the host after the sandbox returns, to mark the run. */
25971
+ finalize(opts) {
25972
+ if (opts.cancelled)
25973
+ this.cancelled = true;
25974
+ const failed = [...this.tentaclesById.values()].filter((r) => r.status === "error");
25975
+ return {
25976
+ tentacles: this.tentaclesById,
25977
+ mergeCount: this.mergeCount,
25978
+ converged: opts.converged,
25979
+ cancelled: this.cancelled,
25980
+ durationMs: Date.now() - this.startTime,
25981
+ unresolvedFindings: failed.map((r) => ({
25982
+ nodeId: r.id,
25983
+ label: r.label,
25984
+ reason: r.status === "error" ? "fail" : "unknown",
25985
+ findings: r.findings
25986
+ }))
25987
+ };
25988
+ }
25989
+ };
25990
+ }
25991
+ });
25992
+
25993
+ // packages/core/dist/kraken/runtime/index.js
25994
+ var init_runtime = __esm({
25995
+ "packages/core/dist/kraken/runtime/index.js"() {
25996
+ "use strict";
25997
+ init_types6();
25998
+ init_sandbox();
25999
+ init_runner();
26000
+ }
26001
+ });
26002
+
25094
26003
  // packages/core/dist/kraken/index.js
25095
26004
  var init_kraken = __esm({
25096
26005
  "packages/core/dist/kraken/index.js"() {
25097
26006
  "use strict";
25098
26007
  init_graph();
25099
26008
  init_conflict();
26009
+ init_verdict();
26010
+ init_personas();
26011
+ init_runtime();
26012
+ init_weakness();
25100
26013
  }
25101
26014
  });
25102
26015
 
25103
26016
  // packages/core/dist/index.js
26017
+ var dist_exports = {};
26018
+ __export(dist_exports, {
26019
+ ADVANCED_TOOL_DEFINITIONS: () => ADVANCED_TOOL_DEFINITIONS,
26020
+ AGENT_ROLES: () => AGENT_ROLES,
26021
+ ALL_TOOLS: () => ALL_TOOLS,
26022
+ AgentHarness: () => AgentHarness,
26023
+ BENNETTS_RAZOR: () => BENNETTS_RAZOR,
26024
+ BENNETTS_RAZOR_SHORT: () => BENNETTS_RAZOR_SHORT,
26025
+ CLARIFICATION_PROTOCOL_MODULE: () => CLARIFICATION_PROTOCOL_MODULE,
26026
+ CODING_CATEGORY: () => CODING_CATEGORY,
26027
+ CODING_PRACTICES_MODULE: () => CODING_PRACTICES_MODULE,
26028
+ CODING_SKILL_CATALOG: () => CODING_SKILL_CATALOG,
26029
+ COLLABORATION_DIRECTIVE: () => COLLABORATION_DIRECTIVE,
26030
+ COMPOSITOR_ONLY_PROPS: () => COMPOSITOR_ONLY_PROPS,
26031
+ DEFAULT_MAX_NODES: () => DEFAULT_MAX_NODES,
26032
+ DEFAULT_MAX_TENTACLES: () => DEFAULT_MAX_TENTACLES,
26033
+ DEFAULT_NFR_SPEC: () => DEFAULT_NFR_SPEC,
26034
+ DEFAULT_PLAN_TIMEOUT_MS: () => DEFAULT_PLAN_TIMEOUT_MS,
26035
+ DEGRADED_RUN_BANNER: () => DEGRADED_RUN_BANNER,
26036
+ DESIGN_PHASE_MODE_BANNER: () => DESIGN_PHASE_MODE_BANNER,
26037
+ DESIGN_PHASE_REQUIREMENTS: () => DESIGN_PHASE_REQUIREMENTS,
26038
+ DESIGN_PHASE_REQUIREMENT_SETS: () => DESIGN_PHASE_REQUIREMENT_SETS,
26039
+ DOOM_LOOP_THRESHOLD: () => DOOM_LOOP_THRESHOLD,
26040
+ EventBus: () => EventBus,
26041
+ IMPLEMENTATION_ADVISOR_BANNER: () => IMPLEMENTATION_ADVISOR_BANNER,
26042
+ IMPLEMENTATION_IMPLEMENTER_BANNER: () => IMPLEMENTATION_IMPLEMENTER_BANNER,
26043
+ IMPLEMENTATION_MODE_BANNER: () => IMPLEMENTATION_MODE_BANNER,
26044
+ IMPLEMENTATION_WRITE_REQUIREMENTS: () => IMPLEMENTATION_WRITE_REQUIREMENTS,
26045
+ KRAKEN_IDENTITY_MODULE: () => KRAKEN_IDENTITY_MODULE,
26046
+ KRAKEN_LEAD_PLAYBOOK_MODULE: () => KRAKEN_LEAD_PLAYBOOK_MODULE,
26047
+ LANGUAGE_POLICY_MODULE_TYPE: () => LANGUAGE_POLICY_MODULE_TYPE,
26048
+ LAYOUT_MOTION_PROPS: () => LAYOUT_MOTION_PROPS,
26049
+ LESSONS_FILE: () => LESSONS_FILE,
26050
+ MAX_DELIVERY_ATTEMPTS: () => MAX_DELIVERY_ATTEMPTS,
26051
+ MAX_FINDINGS_CHARS: () => MAX_FINDINGS_CHARS,
26052
+ MAX_RETRY_PER_MEMBER: () => MAX_RETRY_PER_MEMBER,
26053
+ MUTATING_PROJECT_TOOLS: () => MUTATING_PROJECT_TOOLS,
26054
+ NATIVE_TOOL_PROTOCOL_MODULE: () => NATIVE_TOOL_PROTOCOL_MODULE,
26055
+ NFR_KEYWORDS: () => NFR_KEYWORDS,
26056
+ NON_RETRY_AGENTS: () => NON_RETRY_AGENTS,
26057
+ OUTPUT_QUALITY_DIRECTIVE: () => OUTPUT_QUALITY_DIRECTIVE,
26058
+ PROMPT_MODULES: () => PROMPT_MODULES,
26059
+ PROPRIETARY_REFUSAL_TEXT: () => PROPRIETARY_REFUSAL_TEXT,
26060
+ PROPRIETARY_SECRECY_MARKER: () => PROPRIETARY_SECRECY_MARKER,
26061
+ PROPRIETARY_SECRECY_MODULE: () => PROPRIETARY_SECRECY_MODULE,
26062
+ PlanError: () => PlanError,
26063
+ SINGLE_AGENT_IDENTITY_MODULE: () => SINGLE_AGENT_IDENTITY_MODULE,
26064
+ SKILL_CATALOG: () => SKILL_CATALOG,
26065
+ STRUCTURED_REASONING_DIRECTIVE: () => STRUCTURED_REASONING_DIRECTIVE,
26066
+ ScriptRunner: () => ScriptRunner,
26067
+ SessionJsonlWriter: () => SessionJsonlWriter,
26068
+ TERMINAL_STATUSES: () => TERMINAL_STATUSES,
26069
+ TEXT_LOOP_RECOVERY_SYSTEM: () => TEXT_LOOP_RECOVERY_SYSTEM,
26070
+ TEXT_LOOP_RECOVERY_USER_PROMPT: () => TEXT_LOOP_RECOVERY_USER_PROMPT,
26071
+ TIER_RANK: () => TIER_RANK,
26072
+ TOOL_DEFINITIONS: () => TOOL_DEFINITIONS,
26073
+ TOOL_USE_PROTOCOL_DIRECTIVE: () => TOOL_USE_PROTOCOL_DIRECTIVE,
26074
+ TURN_COMPLETION_MODULE: () => TURN_COMPLETION_MODULE,
26075
+ UnknownMemberError: () => UnknownMemberError,
26076
+ VAULT_TOOL_DEFINITIONS: () => VAULT_TOOL_DEFINITIONS,
26077
+ WEAKNESS_METER_PROMPT: () => WEAKNESS_METER_PROMPT,
26078
+ WeaknessMeterResponseSchema: () => WeaknessMeterResponseSchema,
26079
+ applyCompletionRetry: () => applyCompletionRetry,
26080
+ applyDeterministicAutofix: () => applyDeterministicAutofix,
26081
+ applyImplementationWriteRetry: () => applyImplementationWriteRetry,
26082
+ applyInlineJsAutofix: () => applyInlineJsAutofix,
26083
+ applyMotionAutofix: () => applyMotionAutofix,
26084
+ applyRetryIfMissing: () => applyRetryIfMissing,
26085
+ auditDegradedBanner: () => auditDegradedBanner,
26086
+ auditSynthesisTiers: () => auditSynthesisTiers,
26087
+ buildCouncilCompletion: () => buildCouncilCompletion,
26088
+ buildCustomParameters: () => buildCustomParameters,
26089
+ buildDeliveryFixPrompt: () => buildDeliveryFixPrompt,
26090
+ buildImplementationVerifyRetryPrompt: () => buildImplementationVerifyRetryPrompt,
26091
+ buildImplementationWriteRetryPrompt: () => buildImplementationWriteRetryPrompt,
26092
+ buildLanguageDirective: () => buildLanguageDirective,
26093
+ buildLanguagePolicyModule: () => buildLanguagePolicyModule,
26094
+ buildLanguagePolicyModuleFor: () => buildLanguagePolicyModuleFor,
26095
+ buildMissionBrief: () => buildMissionBrief,
26096
+ buildMotionFixPrompt: () => buildMotionFixPrompt,
26097
+ buildRetryPrompt: () => buildRetryPrompt,
26098
+ buildSkillDefinition: () => buildSkillDefinition,
26099
+ buildSystemPrompt: () => buildSystemPrompt,
26100
+ buildSystemPromptSplit: () => buildSystemPromptSplit,
26101
+ canRunParallel: () => canRunParallel,
26102
+ captureFailure: () => captureFailure,
26103
+ checkImplementationCompletion: () => checkImplementationCompletion,
26104
+ checkImplementationDelivery: () => checkImplementationDelivery,
26105
+ checkMemberToolEmissionSets: () => checkMemberToolEmissionSets,
26106
+ checkMemberToolEmissions: () => checkMemberToolEmissions,
26107
+ classifyMission: () => classifyMission,
26108
+ classifyTaskScope: () => classifyTaskScope,
26109
+ cleanAgentContent: () => cleanAgentContent,
26110
+ clearCustomTools: () => clearCustomTools,
26111
+ cliToolToEnhanced: () => cliToolToEnhanced,
26112
+ collapseLoopedAssistantText: () => collapseLoopedAssistantText,
26113
+ computeAgentSkills: () => computeAgentSkills,
26114
+ computeAgentTools: () => computeAgentTools,
26115
+ councilModeBanner: () => councilModeBanner,
26116
+ councilTierFromSize: () => councilTierFromSize,
26117
+ countByStatus: () => countByStatus,
26118
+ countEmittedWriteTools: () => countEmittedWriteTools,
26119
+ createBrainEvent: () => createBrainEvent,
26120
+ createDefaultSystemPromptConfig: () => createDefaultSystemPromptConfig,
26121
+ createGraph: () => createGraph,
26122
+ defaultPersonaParse: () => defaultPersonaParse,
26123
+ detectAssistantTextLoop: () => detectAssistantTextLoop,
26124
+ detectDegradedRun: () => detectDegradedRun,
26125
+ detectResponseLanguage: () => detectResponseLanguage,
26126
+ disjointScopeSets: () => disjointScopeSets,
26127
+ enforceDesignPhaseToolEmissions: () => enforceDesignPhaseToolEmissions,
26128
+ executeTool: () => executeTool,
26129
+ extractCitations: () => extractCitations,
26130
+ extractRequirementsBlock: () => extractRequirementsBlock,
26131
+ extractTaskScope: () => extractTaskScope,
26132
+ failedNodeIds: () => failedNodeIds,
26133
+ filterByWeakness: () => filterByWeakness,
26134
+ filterDeliveryBlockingFails: () => filterDeliveryBlockingFails,
26135
+ findCodingSkillsByCategory: () => findCodingSkillsByCategory,
26136
+ findCodingSkillsByTag: () => findCodingSkillsByTag,
26137
+ findSkillsByIds: () => findSkillsByIds,
26138
+ findSkillsByTag: () => findSkillsByTag,
26139
+ formatLessonsForContext: () => formatLessonsForContext,
26140
+ getAgent: () => getAgent,
26141
+ getAllTools: () => getAllTools,
26142
+ getAvailableTools: () => getAvailableTools,
26143
+ getBasePromptModules: () => getBasePromptModules,
26144
+ getBuiltinSkillIds: () => getBuiltinSkillIds,
26145
+ getCodingSkillById: () => getCodingSkillById,
26146
+ getCouncilAgents: () => getCouncilAgents,
26147
+ getCouncilDirectiveModules: () => getCouncilDirectiveModules,
26148
+ getPersona: () => getPersona,
26149
+ getPromptModule: () => getPromptModule,
26150
+ getProviderTools: () => getProviderTools,
26151
+ getReadyNodes: () => getReadyNodes,
26152
+ getSkillById: () => getSkillById,
26153
+ getSkillMetadata: () => getSkillMetadata,
26154
+ getSkillsByCategory: () => getSkillsByCategory,
26155
+ getToolDescriptions: () => getToolDescriptions,
26156
+ hasInteractiveClarification: () => hasInteractiveClarification,
26157
+ hashToolCall: () => hashToolCall,
26158
+ isAnswerLeak: () => isAnswerLeak,
26159
+ isBrainAgentEndEvent: () => isBrainAgentEndEvent,
26160
+ isBrainAgentStartEvent: () => isBrainAgentStartEvent,
26161
+ isBrainCouncilModeEvent: () => isBrainCouncilModeEvent,
26162
+ isBrainErrorEvent: () => isBrainErrorEvent,
26163
+ isBrainMemberCostEvent: () => isBrainMemberCostEvent,
26164
+ isBrainMessageDeltaEvent: () => isBrainMessageDeltaEvent,
26165
+ isBrainMessageEndEvent: () => isBrainMessageEndEvent,
26166
+ isBrainMessageStartEvent: () => isBrainMessageStartEvent,
26167
+ isBrainQueueUpdateEvent: () => isBrainQueueUpdateEvent,
26168
+ isBrainSessionCompactedEvent: () => isBrainSessionCompactedEvent,
26169
+ isBrainThinkingDeltaEvent: () => isBrainThinkingDeltaEvent,
26170
+ isBrainToolExecutionEndEvent: () => isBrainToolExecutionEndEvent,
26171
+ isBrainToolExecutionStartEvent: () => isBrainToolExecutionStartEvent,
26172
+ isBrainToolExecutionUpdateEvent: () => isBrainToolExecutionUpdateEvent,
26173
+ isConverged: () => isConverged,
26174
+ isReviewerKind: () => isReviewerKind,
26175
+ isSettled: () => isSettled,
26176
+ isStatusTheaterUnit: () => isStatusTheaterUnit,
26177
+ isValidTool: () => isValidTool,
26178
+ isVerifyToolCheckSkipped: () => isVerifyToolCheckSkipped,
26179
+ jaccardSimilarity: () => jaccardSimilarity,
26180
+ lintSynthesisHonesty: () => lintSynthesisHonesty,
26181
+ listCodingSkills: () => listCodingSkills,
26182
+ listPersonas: () => listPersonas,
26183
+ listSkills: () => listSkills,
26184
+ loadNfrSpec: () => loadNfrSpec,
26185
+ matchCheckPrefix: () => matchCheckPrefix,
26186
+ normalizeForSignature: () => normalizeForSignature,
26187
+ normalizeLoopUnit: () => normalizeLoopUnit,
26188
+ normalizeScopePath: () => normalizeScopePath,
26189
+ normalizeTextToolArgs: () => normalizeTextToolArgs,
26190
+ parseClarificationRequest: () => parseClarificationRequest,
26191
+ parseEvidenceTier: () => parseEvidenceTier,
26192
+ parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
26193
+ parsePersonaVerdict: () => parsePersonaVerdict,
26194
+ parseProjectRootFromWorkspaceContext: () => parseProjectRootFromWorkspaceContext,
26195
+ parseTextToolCalls: () => parseTextToolCalls,
26196
+ parseThinking: () => parseThinking,
26197
+ parseVerificationTable: () => parseVerificationTable,
26198
+ parseVerifyVerdict: () => parseVerifyVerdict,
26199
+ pathsOverlap: () => pathsOverlap,
26200
+ pickWeakest: () => pickWeakest,
26201
+ promoteMember: () => promoteMember,
26202
+ rankByWeakness: () => rankByWeakness,
26203
+ readLessonsDeduped: () => readLessonsDeduped,
26204
+ readSession: () => readSession,
26205
+ recallLessons: () => recallLessons,
26206
+ registerCodingSkill: () => registerCodingSkill,
26207
+ registerCustomTool: () => registerCustomTool,
26208
+ registerPersona: () => registerPersona,
26209
+ registerSkill: () => registerSkill,
26210
+ renderSkillMarkdown: () => renderSkillMarkdown,
26211
+ replayChairmanTextTools: () => replayChairmanTextTools,
26212
+ resolveAgentSkills: () => resolveAgentSkills,
26213
+ resolveCouncilRunMode: () => resolveCouncilRunMode,
26214
+ resolveMaxTentacles: () => resolveMaxTentacles,
26215
+ resolvePlanTimeoutMs: () => resolvePlanTimeoutMs,
26216
+ resolveResponseLanguage: () => resolveResponseLanguage,
26217
+ resolveRoleSystemPrompt: () => resolveRoleSystemPrompt,
26218
+ resolveSkillDependencies: () => resolveSkillDependencies,
26219
+ resolveVerifyRetryTool: () => resolveVerifyRetryTool,
26220
+ restrictImplementationWrites: () => restrictImplementationWrites,
26221
+ runChairmanDeliveryLoop: () => runChairmanDeliveryLoop,
26222
+ runChairmanFixLoop: () => runChairmanFixLoop,
26223
+ runChairmanMicroGate: () => runChairmanMicroGate,
26224
+ runCouncilPure: () => runCouncilPure,
26225
+ runImplementationVerification: () => runImplementationVerification,
26226
+ runInSandbox: () => runInSandbox,
26227
+ runMicroVerificationOnFile: () => runMicroVerificationOnFile,
26228
+ runRetryTurnForMember: () => runRetryTurnForMember,
26229
+ scanForFootguns: () => scanForFootguns,
26230
+ scanKeyframesViolations: () => scanKeyframesViolations,
26231
+ scanTransitionViolations: () => scanTransitionViolations,
26232
+ scrubProprietaryLeak: () => scrubProprietaryLeak,
26233
+ selectParallelWave: () => selectParallelWave,
26234
+ setWorkspaceStubs: () => setWorkspaceStubs,
26235
+ shouldRetryMember: () => shouldRetryMember,
26236
+ slugify: () => slugify2,
26237
+ specificityFromAssumptions: () => specificityFromAssumptions,
26238
+ stripClarificationProtocol: () => stripClarificationProtocol,
26239
+ swapMembers: () => swapMembers,
26240
+ systemMessagesFromSplit: () => systemMessagesFromSplit,
26241
+ taskMatchesNfrKeywords: () => taskMatchesNfrKeywords,
26242
+ tierAtLeast: () => tierAtLeast,
26243
+ tokenizeForSignature: () => tokenizeForSignature,
26244
+ topoLevels: () => topoLevels,
26245
+ unregisterCustomTool: () => unregisterCustomTool,
26246
+ unregisterSkill: () => unregisterSkill,
26247
+ validateCodingSkillRequires: () => validateCodingSkillRequires,
26248
+ validateGraph: () => validateGraph,
26249
+ verifyCitations: () => verifyCitations,
26250
+ warnIfNfrSpecMissing: () => warnIfNfrSpecMissing,
26251
+ weaknessFromMeter: () => weaknessFromMeter,
26252
+ weaknessFromVerdict: () => weaknessFromVerdict,
26253
+ weaknessScoreFromText: () => weaknessScoreFromText,
26254
+ wrapLegacyStream: () => wrapLegacyStream,
26255
+ writeCouncilCompletion: () => writeCouncilCompletion,
26256
+ writeVerificationReport: () => writeVerificationReport
26257
+ });
25104
26258
  var init_dist = __esm({
25105
26259
  "packages/core/dist/index.js"() {
25106
26260
  "use strict";
@@ -25118,8 +26272,11 @@ var init_dist = __esm({
25118
26272
  // src/cli/kraken/graphStatus.ts
25119
26273
  var graphStatus_exports = {};
25120
26274
  __export(graphStatus_exports, {
26275
+ DEFAULT_DIGEST_RESULT_CHARS: () => DEFAULT_DIGEST_RESULT_CHARS,
25121
26276
  endKrakenGraphLive: () => endKrakenGraphLive,
26277
+ formatDuration: () => formatDuration2,
25122
26278
  formatKrakenGraphAscii: () => formatKrakenGraphAscii,
26279
+ formatKrakenGraphDigest: () => formatKrakenGraphDigest,
25123
26280
  formatKrakenGraphSummary: () => formatKrakenGraphSummary,
25124
26281
  getKrakenGraphLive: () => getKrakenGraphLive,
25125
26282
  resetKrakenGraphLive: () => resetKrakenGraphLive,
@@ -25147,6 +26304,46 @@ function formatKrakenGraphAscii(graph) {
25147
26304
  if (lines.length === 0) return summary;
25148
26305
  return [summary, ...lines].join("\n");
25149
26306
  }
26307
+ function formatDuration2(ms) {
26308
+ if (!Number.isFinite(ms) || ms < 0) return "?";
26309
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
26310
+ const totalSeconds = Math.round(ms / 1e3);
26311
+ if (totalSeconds < 60) return `${totalSeconds}s`;
26312
+ const m = Math.floor(totalSeconds / 60);
26313
+ const s = totalSeconds % 60;
26314
+ return s === 0 ? `${m}m` : `${m}m${s}s`;
26315
+ }
26316
+ function formatKrakenGraphDigest(graph, opts = {}) {
26317
+ const maxChars = opts.maxResultChars ?? DEFAULT_DIGEST_RESULT_CHARS;
26318
+ const durations = opts.durationsMs ?? {};
26319
+ const ordered = topoLevels(graph).flat();
26320
+ for (const id of graph.nodes.keys()) {
26321
+ if (!ordered.includes(id)) ordered.push(id);
26322
+ }
26323
+ const lines = [];
26324
+ for (const id of ordered) {
26325
+ const n = graph.nodes.get(id);
26326
+ if (!n) continue;
26327
+ const took = durations[id] !== void 0 ? `, ${formatDuration2(durations[id])}` : "";
26328
+ const detail = n.status === "error" ? n.error : n.result;
26329
+ const firstLine2 = (detail ?? "").trim().split("\n")[0] ?? "";
26330
+ const body = firstLine2.length > maxChars ? `${firstLine2.slice(0, maxChars)}\u2026` : firstLine2;
26331
+ lines.push(
26332
+ `[${STATUS_ICON[n.status]}] ${n.id} (${n.kind}${took})${body ? ` \u2014 ${body}` : ""}`
26333
+ );
26334
+ }
26335
+ const unresolved = opts.unresolvedFindings ?? [];
26336
+ if (unresolved.length > 0) {
26337
+ lines.push("", "unresolved verify findings:");
26338
+ for (const u of unresolved) {
26339
+ const why = u.reason === "fail" ? "rejected, rework budget spent" : "no parseable verdict";
26340
+ const detail = u.findings.trim().split("\n")[0]?.trim() ?? "";
26341
+ const body = detail.length > maxChars ? `${detail.slice(0, maxChars)}\u2026` : detail;
26342
+ lines.push(` ${u.nodeId} (${why})${body ? ` \u2014 ${body}` : ""}`);
26343
+ }
26344
+ }
26345
+ return lines.join("\n");
26346
+ }
25150
26347
  function state() {
25151
26348
  return globalThis;
25152
26349
  }
@@ -25195,7 +26392,7 @@ function formatKrakenGraphSummary() {
25195
26392
  if (live.error) parts.push(`${live.error}\u2717`);
25196
26393
  return `graph ${parts.join(" \xB7 ")}`;
25197
26394
  }
25198
- var STATUS_ICON;
26395
+ var STATUS_ICON, DEFAULT_DIGEST_RESULT_CHARS;
25199
26396
  var init_graphStatus = __esm({
25200
26397
  "src/cli/kraken/graphStatus.ts"() {
25201
26398
  "use strict";
@@ -25207,6 +26404,7 @@ var init_graphStatus = __esm({
25207
26404
  error: "\u2717",
25208
26405
  skipped: "\xBB"
25209
26406
  };
26407
+ DEFAULT_DIGEST_RESULT_CHARS = 200;
25210
26408
  }
25211
26409
  });
25212
26410
 
@@ -25700,9 +26898,9 @@ function spillToolOutput(fullText, meta3) {
25700
26898
  const rnd = randomBytes(3).toString("hex");
25701
26899
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
25702
26900
  const file2 = `${stamp}-${safeTool}-${hash3}-${rnd}.txt`;
25703
- const path44 = join11(dir, file2);
25704
- writeFileSync10(path44, fullText, "utf8");
25705
- return path44;
26901
+ const path47 = join11(dir, file2);
26902
+ writeFileSync10(path47, fullText, "utf8");
26903
+ return path47;
25706
26904
  } catch {
25707
26905
  return null;
25708
26906
  }
@@ -25718,21 +26916,21 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
25718
26916
  if (text.length === 0)
25719
26917
  return text;
25720
26918
  const opts = typeof capOrOpts === "number" ? { cap: capOrOpts } : capOrOpts ?? {};
25721
- const cap2 = typeof opts.cap === "number" && Number.isFinite(opts.cap) && opts.cap >= 10 ? opts.cap : TOOL_RESULT_LINE_CAP;
26919
+ const cap3 = typeof opts.cap === "number" && Number.isFinite(opts.cap) && opts.cap >= 10 ? opts.cap : TOOL_RESULT_LINE_CAP;
25722
26920
  const doSpill = opts.spill !== false;
25723
26921
  const lines = text.split("\n");
25724
- const charBudget = cap2 * 80;
25725
- const overLines = lines.length > cap2;
25726
- const overChars = text.length > charBudget && lines.length <= cap2;
26922
+ const charBudget = cap3 * 80;
26923
+ const overLines = lines.length > cap3;
26924
+ const overChars = text.length > charBudget && lines.length <= cap3;
25727
26925
  if (!overLines && !overChars)
25728
26926
  return text;
25729
26927
  let preview;
25730
26928
  let marker;
25731
26929
  if (overLines) {
25732
- const half = Math.floor(cap2 / 2);
26930
+ const half = Math.floor(cap3 / 2);
25733
26931
  const head = lines.slice(0, half);
25734
26932
  const tail = lines.slice(lines.length - half);
25735
- const omitted = lines.length - cap2;
26933
+ const omitted = lines.length - cap3;
25736
26934
  marker = `+${omitted} lines omitted \u2014 showing head:${half}, tail:${half} of ${lines.length} total`;
25737
26935
  preview = head.join("\n") + `
25738
26936
  \u2026 [${marker}] \u2026
@@ -25748,10 +26946,10 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
25748
26946
  ${tail}`;
25749
26947
  }
25750
26948
  if (doSpill) {
25751
- const path44 = spillToolOutput(text, { toolName: opts.toolName });
25752
- if (path44) {
26949
+ const path47 = spillToolOutput(text, { toolName: opts.toolName });
26950
+ if (path47) {
25753
26951
  const spillNote = `
25754
- \u2026 [full output spilled to: ${path44} \u2014 re-read with read_file if you need the complete text] \u2026`;
26952
+ \u2026 [full output spilled to: ${path47} \u2014 re-read with read_file if you need the complete text] \u2026`;
25755
26953
  if (preview.includes("] \u2026\n")) {
25756
26954
  preview = preview.replace("] \u2026\n", `] \u2026${spillNote}
25757
26955
  `);
@@ -25763,7 +26961,7 @@ ${tail}`;
25763
26961
  return preview;
25764
26962
  }
25765
26963
  var TOOL_NAME_ALIASES, TOOL_RESULT_LINE_CAP, ToolRegistry;
25766
- var init_registry = __esm({
26964
+ var init_registry2 = __esm({
25767
26965
  "packages/core/dist/core/tools/registry.js"() {
25768
26966
  "use strict";
25769
26967
  init_zodBridge();
@@ -26621,7 +27819,7 @@ async function runTentacle(opts) {
26621
27819
  const started = Date.now();
26622
27820
  const g = globalThis;
26623
27821
  let worktree = null;
26624
- let effectiveCwd = parentCwd;
27822
+ let effectiveCwd = opts.cwdOverride || parentCwd;
26625
27823
  const wantWt = agent === "general" && deps.allowWorktree !== false && isKrakenWorktreeEnabled();
26626
27824
  if (wantWt) {
26627
27825
  try {
@@ -26697,7 +27895,7 @@ async function runTentacle(opts) {
26697
27895
  model: sub.model,
26698
27896
  provider: sub.provider,
26699
27897
  messages: [
26700
- { role: "system", content: systemPromptForAgent(agent) },
27898
+ { role: "system", content: opts.systemPromptOverride ?? systemPromptForAgent(agent) },
26701
27899
  { role: "user", content: userContent }
26702
27900
  ],
26703
27901
  tools: sub.tools,
@@ -28692,21 +29890,21 @@ function normalizeAuth(auth) {
28692
29890
  return "agent";
28693
29891
  }
28694
29892
  function readSecrets() {
28695
- const path44 = getSshSecretsPath();
28696
- if (!existsSync17(path44)) return {};
29893
+ const path47 = getSshSecretsPath();
29894
+ if (!existsSync17(path47)) return {};
28697
29895
  try {
28698
- return JSON.parse(readFileSync16(path44, "utf8"));
29896
+ return JSON.parse(readFileSync16(path47, "utf8"));
28699
29897
  } catch {
28700
29898
  return {};
28701
29899
  }
28702
29900
  }
28703
29901
  function writeSecrets(data) {
28704
- const path44 = getSshSecretsPath();
28705
- mkdirSync9(dirname2(path44), { recursive: true });
28706
- writeFileSync11(path44, `${JSON.stringify(data, null, 2)}
29902
+ const path47 = getSshSecretsPath();
29903
+ mkdirSync9(dirname2(path47), { recursive: true });
29904
+ writeFileSync11(path47, `${JSON.stringify(data, null, 2)}
28707
29905
  `, "utf8");
28708
29906
  try {
28709
- chmodSync(path44, 384);
29907
+ chmodSync(path47, 384);
28710
29908
  } catch {
28711
29909
  }
28712
29910
  }
@@ -28735,10 +29933,10 @@ function deleteSshPassword(id) {
28735
29933
  writeSecrets({ passwords });
28736
29934
  }
28737
29935
  function readStore2() {
28738
- const path44 = getSshTargetsPath();
28739
- if (!existsSync17(path44)) return [];
29936
+ const path47 = getSshTargetsPath();
29937
+ if (!existsSync17(path47)) return [];
28740
29938
  try {
28741
- const parsed = JSON.parse(readFileSync16(path44, "utf8"));
29939
+ const parsed = JSON.parse(readFileSync16(path47, "utf8"));
28742
29940
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
28743
29941
  return list.filter(
28744
29942
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -28753,11 +29951,11 @@ function readStore2() {
28753
29951
  }
28754
29952
  }
28755
29953
  function writeStore2(targets) {
28756
- const path44 = getSshTargetsPath();
28757
- mkdirSync9(dirname2(path44), { recursive: true });
29954
+ const path47 = getSshTargetsPath();
29955
+ mkdirSync9(dirname2(path47), { recursive: true });
28758
29956
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
28759
29957
  writeFileSync11(
28760
- path44,
29958
+ path47,
28761
29959
  `${JSON.stringify({ targets: clean }, null, 2)}
28762
29960
  `,
28763
29961
  "utf8"
@@ -28916,12 +30114,12 @@ function runSsh(target, remoteCommand, timeoutMs = 6e4) {
28916
30114
  });
28917
30115
  let stdout = "";
28918
30116
  let stderr = "";
28919
- const cap2 = 4e4;
30117
+ const cap3 = 4e4;
28920
30118
  child.stdout?.on("data", (d) => {
28921
- if (stdout.length < cap2) stdout += d.toString("utf8");
30119
+ if (stdout.length < cap3) stdout += d.toString("utf8");
28922
30120
  });
28923
30121
  child.stderr?.on("data", (d) => {
28924
- if (stderr.length < cap2) stderr += d.toString("utf8");
30122
+ if (stderr.length < cap3) stderr += d.toString("utf8");
28925
30123
  });
28926
30124
  const timer = setTimeout(() => {
28927
30125
  child.kill("SIGTERM");
@@ -29003,11 +30201,11 @@ function formatSshTargetsForPrompt() {
29003
30201
  ];
29004
30202
  for (const t of targets) {
29005
30203
  const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
29006
- const path44 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
30204
+ const path47 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
29007
30205
  const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
29008
30206
  const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
29009
30207
  lines.push(
29010
- `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path44}${tags}${allow}`
30208
+ `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path47}${tags}${allow}`
29011
30209
  );
29012
30210
  }
29013
30211
  return lines.join("\n");
@@ -29518,7 +30716,8 @@ __export(krakenModel_exports, {
29518
30716
  isKrakenAutoModelEnabled: () => isKrakenAutoModelEnabled,
29519
30717
  pickCheapModel: () => pickCheapModel,
29520
30718
  resolveKrakenSubModel: () => resolveKrakenSubModel,
29521
- resolveKrakenSubModelAsync: () => resolveKrakenSubModelAsync
30719
+ resolveKrakenSubModelAsync: () => resolveKrakenSubModelAsync,
30720
+ resolvePersonaModel: () => resolvePersonaModel
29522
30721
  });
29523
30722
  function isCheapModelId(id) {
29524
30723
  if (!id) return false;
@@ -29568,6 +30767,12 @@ function resolveKrakenSubModel(agent, parentModel, env = process.env, opts = {})
29568
30767
  }
29569
30768
  return parentModel;
29570
30769
  }
30770
+ function resolvePersonaModel(kind, parentModel, env = process.env, opts = {}) {
30771
+ const personaKey = kind === "spec" ? "ZELARI_KRAKEN_SPEC_MODEL" : kind === "conformance" ? "ZELARI_KRAKEN_CONFORMANCE_MODEL" : kind === "oracle" ? "ZELARI_KRAKEN_ORACLE_MODEL" : "";
30772
+ const specific = personaKey ? env[personaKey]?.trim() : void 0;
30773
+ if (specific) return specific;
30774
+ return resolveKrakenSubModel("verify", parentModel, env, opts);
30775
+ }
29571
30776
  async function resolveKrakenSubModelAsync(agent, parentModel, env = process.env, opts = {}) {
29572
30777
  let candidates = [];
29573
30778
  if (opts.provider) {
@@ -29617,7 +30822,7 @@ function createBuiltinToolRegistry(options = {}) {
29617
30822
  const safeBash = wrapWithShellSafety(bashTool, audit, sessionId);
29618
30823
  const safeFetchUrl = wrapWithAudit(fetchUrlTool, audit, sessionId);
29619
30824
  const safeWebSearch = wrapWithAudit(webSearchTool, audit, sessionId);
29620
- const registry3 = new ToolRegistry();
30825
+ const registry4 = new ToolRegistry();
29621
30826
  const profile = options.profile ?? "full";
29622
30827
  const readOnly = options.readOnly === true || options.planMode === true || profile === "explore";
29623
30828
  const verifyMode = profile === "verify";
@@ -29625,34 +30830,34 @@ function createBuiltinToolRegistry(options = {}) {
29625
30830
  const allowBash = allowMutators || verifyMode;
29626
30831
  const permPolicy = options.permissionPolicy ?? defaultPermissionPolicy();
29627
30832
  const withPerm = (t) => wrapWithPermissions(t, permPolicy, options.onPermissionAsk);
29628
- registry3.register(withPerm(safeReadFile));
29629
- registry3.register(withPerm(safeGrepContent));
29630
- registry3.register(withPerm(safeListFiles));
29631
- registry3.register(withPerm(safeShowDiff));
29632
- registry3.register(withPerm(safeFetchUrl));
29633
- registry3.register(withPerm(safeWebSearch));
30833
+ registry4.register(withPerm(safeReadFile));
30834
+ registry4.register(withPerm(safeGrepContent));
30835
+ registry4.register(withPerm(safeListFiles));
30836
+ registry4.register(withPerm(safeShowDiff));
30837
+ registry4.register(withPerm(safeFetchUrl));
30838
+ registry4.register(withPerm(safeWebSearch));
29634
30839
  if (allowMutators) {
29635
- registry3.register(withPerm(safeWriteFile));
29636
- registry3.register(withPerm(safeEditFile));
29637
- registry3.register(withPerm(safeApplyDiff));
30840
+ registry4.register(withPerm(safeWriteFile));
30841
+ registry4.register(withPerm(safeEditFile));
30842
+ registry4.register(withPerm(safeApplyDiff));
29638
30843
  }
29639
30844
  if (allowBash) {
29640
- registry3.register(withPerm(safeBash));
30845
+ registry4.register(withPerm(safeBash));
29641
30846
  }
29642
30847
  const askUserTool = options.readOnly === true || profile === "explore" || profile === "verify" ? null : createAskUserTool(options.onAskUser);
29643
30848
  if (askUserTool) {
29644
- registry3.register(withPerm(askUserTool));
30849
+ registry4.register(withPerm(askUserTool));
29645
30850
  }
29646
30851
  const enableSkill = options.enableSkill !== false && options.readOnly !== true && profile !== "explore" && profile !== "verify";
29647
30852
  const skillTool = enableSkill ? withPerm(createSkillTool({ cwd: root })) : null;
29648
30853
  if (skillTool) {
29649
- registry3.register(skillTool);
30854
+ registry4.register(skillTool);
29650
30855
  }
29651
30856
  const enableTodos = options.enableTodos !== false && options.readOnly !== true && profile === "full";
29652
30857
  const todoWrite = enableTodos ? withPerm(createTodoWriteTool()) : null;
29653
30858
  const todoRead = enableTodos ? withPerm(createTodoReadTool()) : null;
29654
- if (todoWrite) registry3.register(todoWrite);
29655
- if (todoRead) registry3.register(todoRead);
30859
+ if (todoWrite) registry4.register(todoWrite);
30860
+ if (todoRead) registry4.register(todoRead);
29656
30861
  const summary = [
29657
30862
  safeReadFile,
29658
30863
  safeGrepContent,
@@ -29674,13 +30879,13 @@ function createBuiltinToolRegistry(options = {}) {
29674
30879
  }));
29675
30880
  if (process.env.ZELARI_AST !== "0") {
29676
30881
  for (const t of createAstTools()) {
29677
- registry3.register(t);
30882
+ registry4.register(t);
29678
30883
  tools.push({ name: t.name, description: t.description, permissions: t.permissions ?? [] });
29679
30884
  }
29680
30885
  }
29681
30886
  if (process.env.ZELARI_SEMANTIC !== "0") {
29682
30887
  const semanticTool = createSemanticTool({ root });
29683
- registry3.register(semanticTool);
30888
+ registry4.register(semanticTool);
29684
30889
  tools.push({
29685
30890
  name: semanticTool.name,
29686
30891
  description: semanticTool.description,
@@ -29689,7 +30894,7 @@ function createBuiltinToolRegistry(options = {}) {
29689
30894
  }
29690
30895
  if (!readOnly && process.env.ZELARI_BROWSER !== "0") {
29691
30896
  const browserTool = createBrowserTool();
29692
- registry3.register(browserTool);
30897
+ registry4.register(browserTool);
29693
30898
  tools.push({
29694
30899
  name: browserTool.name,
29695
30900
  description: browserTool.description,
@@ -29698,7 +30903,7 @@ function createBuiltinToolRegistry(options = {}) {
29698
30903
  }
29699
30904
  if (!readOnly && process.env.ZELARI_SSH !== "0") {
29700
30905
  for (const t of createSshTools()) {
29701
- registry3.register(t);
30906
+ registry4.register(t);
29702
30907
  tools.push({
29703
30908
  name: t.name,
29704
30909
  description: t.description,
@@ -29709,7 +30914,7 @@ function createBuiltinToolRegistry(options = {}) {
29709
30914
  if (!readOnly) {
29710
30915
  for (const t of createWorldModelTools()) {
29711
30916
  const safe = wrapWithAudit(t, audit, sessionId);
29712
- registry3.register(safe);
30917
+ registry4.register(safe);
29713
30918
  tools.push({
29714
30919
  name: t.name,
29715
30920
  description: t.description,
@@ -29722,7 +30927,7 @@ function createBuiltinToolRegistry(options = {}) {
29722
30927
  const taskTool = createTaskTool({
29723
30928
  createSubAgentContext: createKrakenSubAgentContextFactory({ root, audit, sessionId })
29724
30929
  });
29725
- registry3.register(withPerm(taskTool));
30930
+ registry4.register(withPerm(taskTool));
29726
30931
  tools.push({
29727
30932
  name: taskTool.name,
29728
30933
  description: taskTool.description,
@@ -29732,17 +30937,17 @@ function createBuiltinToolRegistry(options = {}) {
29732
30937
  if (!readOnly && process.env.ZELARI_LSP !== "0" && options.lspProvider !== null) {
29733
30938
  const lspTools = options.lspProvider ? createLspTools(options.lspProvider, root) : createLspTools(getSharedLspManager(root), root);
29734
30939
  for (const t of lspTools) {
29735
- registry3.register(t);
30940
+ registry4.register(t);
29736
30941
  tools.push({ name: t.name, description: t.description, permissions: t.permissions ?? [] });
29737
30942
  }
29738
30943
  }
29739
- return { registry: registry3, tools };
30944
+ return { registry: registry4, tools };
29740
30945
  }
29741
- function getCliToolCatalogEntries(registry3) {
30946
+ function getCliToolCatalogEntries(registry4) {
29742
30947
  const entries = [];
29743
- for (const name of registry3.list()) {
30948
+ for (const name of registry4.list()) {
29744
30949
  if (HARNESS_BUILTIN_NAMES.has(name)) continue;
29745
- const def = registry3.get(name);
30950
+ const def = registry4.get(name);
29746
30951
  if (!def) continue;
29747
30952
  try {
29748
30953
  entries.push(cliToolToEnhanced(def));
@@ -29751,8 +30956,8 @@ function getCliToolCatalogEntries(registry3) {
29751
30956
  }
29752
30957
  return entries;
29753
30958
  }
29754
- function registerCliToolsIntoCouncilCatalog(registry3) {
29755
- for (const entry of getCliToolCatalogEntries(registry3)) {
30959
+ function registerCliToolsIntoCouncilCatalog(registry4) {
30960
+ for (const entry of getCliToolCatalogEntries(registry4)) {
29756
30961
  try {
29757
30962
  registerCustomTool(entry);
29758
30963
  } catch {
@@ -29999,7 +31204,7 @@ var HARNESS_BUILTIN_NAMES;
29999
31204
  var init_toolRegistry = __esm({
30000
31205
  "src/cli/toolRegistry.ts"() {
30001
31206
  "use strict";
30002
- init_registry();
31207
+ init_registry2();
30003
31208
  init_filesystem();
30004
31209
  init_shell();
30005
31210
  init_search();
@@ -31352,7 +32557,7 @@ __export(composeContext_exports, {
31352
32557
  });
31353
32558
  import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync19 } from "node:fs";
31354
32559
  import { join as join20 } from "node:path";
31355
- function cap(text, max, label) {
32560
+ function cap2(text, max, label) {
31356
32561
  if (!text || text.length <= max) return { text: text || "", truncated: false };
31357
32562
  return {
31358
32563
  text: text.slice(0, max) + `
@@ -31396,7 +32601,7 @@ function buildDesignIndex(projectRoot, maxChars) {
31396
32601
  }
31397
32602
  }
31398
32603
  const raw = lines.join("\n");
31399
- return cap(raw, maxChars, "design-index").text;
32604
+ return cap2(raw, maxChars, "design-index").text;
31400
32605
  }
31401
32606
  function composeProjectContext(input) {
31402
32607
  const cwd = input.cwd ?? process.cwd();
@@ -31450,7 +32655,7 @@ function composeProjectContext(input) {
31450
32655
  default: 12e3,
31451
32656
  min: 2e3
31452
32657
  });
31453
- const totalCapped = cap(workspaceContext, totalCap, "workspaceContext");
32658
+ const totalCapped = cap2(workspaceContext, totalCap, "workspaceContext");
31454
32659
  workspaceContext = totalCapped.text;
31455
32660
  if (totalCapped.truncated) {
31456
32661
  warnings.push(
@@ -31468,14 +32673,14 @@ function composeProjectContext(input) {
31468
32673
  }
31469
32674
  const ragParts = [];
31470
32675
  if (durableRaw) {
31471
- const d = cap(durableRaw, durableMax, "durable-state");
32676
+ const d = cap2(durableRaw, durableMax, "durable-state");
31472
32677
  ragParts.push(d.text);
31473
32678
  if (d.truncated) {
31474
32679
  warnings.push(`[context] durable state truncated to ${durableMax} chars.`);
31475
32680
  }
31476
32681
  }
31477
32682
  if (input.memoryHits?.trim()) {
31478
- const m = cap(input.memoryHits.trim(), memoryMax, "memory");
32683
+ const m = cap2(input.memoryHits.trim(), memoryMax, "memory");
31479
32684
  ragParts.push(m.text);
31480
32685
  if (m.truncated) warnings.push(`[context] memory RAG truncated to ${memoryMax} chars.`);
31481
32686
  }
@@ -31859,28 +33064,28 @@ var init_storage = __esm({
31859
33064
  VALID_SCALARS = /^(true|false|null|~)$/i;
31860
33065
  Storage = class {
31861
33066
  /** Read a Markdown file with frontmatter. Throws if not found. */
31862
- read(path44) {
31863
- if (!existsSync25(path44)) {
31864
- throw new Error(`File not found: ${path44}`);
33067
+ read(path47) {
33068
+ if (!existsSync25(path47)) {
33069
+ throw new Error(`File not found: ${path47}`);
31865
33070
  }
31866
- const md = readFileSync21(path44, "utf8");
33071
+ const md = readFileSync21(path47, "utf8");
31867
33072
  return parseFrontmatter(md);
31868
33073
  }
31869
33074
  /** Read a Markdown file; returns null if not found. */
31870
- readIfExists(path44) {
31871
- if (!existsSync25(path44)) return null;
31872
- return this.read(path44);
33075
+ readIfExists(path47) {
33076
+ if (!existsSync25(path47)) return null;
33077
+ return this.read(path47);
31873
33078
  }
31874
33079
  /**
31875
33080
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
31876
33081
  * The meta object is serialized as YAML frontmatter; body as Markdown.
31877
33082
  */
31878
- write(path44, meta3, body) {
31879
- mkdirSync11(dirname4(path44), { recursive: true });
31880
- const tmp = path44 + ".tmp-" + process.pid;
33083
+ write(path47, meta3, body) {
33084
+ mkdirSync11(dirname4(path47), { recursive: true });
33085
+ const tmp = path47 + ".tmp-" + process.pid;
31881
33086
  const md = serializeFrontmatter(meta3, body);
31882
33087
  writeFileSync13(tmp, md, "utf8");
31883
- renameSync2(tmp, path44);
33088
+ renameSync2(tmp, path47);
31884
33089
  }
31885
33090
  /** List all .md files in a directory (non-recursive). */
31886
33091
  listMarkdown(dir) {
@@ -31958,8 +33163,8 @@ function readPlan(ctx) {
31958
33163
  } catch {
31959
33164
  }
31960
33165
  }
31961
- const path44 = workspaceFile(ctx.rootDir, "plan");
31962
- const doc = ctx.storage.readIfExists(path44);
33166
+ const path47 = workspaceFile(ctx.rootDir, "plan");
33167
+ const doc = ctx.storage.readIfExists(path47);
31963
33168
  if (!doc) return { phases: [], tasks: [], milestones: [] };
31964
33169
  const meta3 = doc.meta;
31965
33170
  return {
@@ -32124,7 +33329,7 @@ function addMilestoneRecord(ctx, summary, input) {
32124
33329
  dueDate: input.dueDate,
32125
33330
  targetVersion: version2
32126
33331
  });
32127
- const path44 = join23(ctx.rootDir, "milestones", `${id}.md`);
33332
+ const path47 = join23(ctx.rootDir, "milestones", `${id}.md`);
32128
33333
  const meta3 = {
32129
33334
  kind: "milestone",
32130
33335
  id,
@@ -32141,7 +33346,7 @@ function addMilestoneRecord(ctx, summary, input) {
32141
33346
  `Target version: ${version2}`,
32142
33347
  ""
32143
33348
  ].join("\n");
32144
- ctx.storage.write(path44, meta3, body);
33349
+ ctx.storage.write(path47, meta3, body);
32145
33350
  return { id, created: true };
32146
33351
  }
32147
33352
  function readPlanSummary(ctx) {
@@ -32345,7 +33550,7 @@ function addIdeaStub(ctx) {
32345
33550
  const tags = args["tags"] ?? [];
32346
33551
  const category = args["category"] ?? "General";
32347
33552
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
32348
- const path44 = workspaceArtifact(ctx.rootDir, "decisions", id);
33553
+ const path47 = workspaceArtifact(ctx.rootDir, "decisions", id);
32349
33554
  const meta3 = {
32350
33555
  kind: "adr",
32351
33556
  status: "proposed",
@@ -32371,7 +33576,7 @@ function addIdeaStub(ctx) {
32371
33576
  ...consequences.map((c) => `- ${c}`),
32372
33577
  ""
32373
33578
  ].join("\n");
32374
- ctx.storage.write(path44, meta3, body);
33579
+ ctx.storage.write(path47, meta3, body);
32375
33580
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
32376
33581
  });
32377
33582
  }
@@ -32453,14 +33658,14 @@ function createDocumentStub(ctx) {
32453
33658
  ctx.storage.write(risksPath, riskMeta, content);
32454
33659
  return `Document "${title}" created at risks.md (workspace root).`;
32455
33660
  }
32456
- const path44 = workspaceArtifact(ctx.rootDir, "docs", slug);
33661
+ const path47 = workspaceArtifact(ctx.rootDir, "docs", slug);
32457
33662
  const meta3 = {
32458
33663
  kind: "doc",
32459
33664
  id: slug,
32460
33665
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
32461
33666
  tags
32462
33667
  };
32463
- ctx.storage.write(path44, meta3, content);
33668
+ ctx.storage.write(path47, meta3, content);
32464
33669
  return `Document "${title}" created at docs/${slug}.md.`;
32465
33670
  });
32466
33671
  }
@@ -32621,12 +33826,12 @@ __export(toolRegistry_exports2, {
32621
33826
  });
32622
33827
  function createWorkspaceToolRegistry(ctx) {
32623
33828
  const stubs = createWorkspaceStubs(ctx);
32624
- const registry3 = new ToolRegistry();
33829
+ const registry4 = new ToolRegistry();
32625
33830
  for (const stub of stubs) {
32626
33831
  const td = adaptStubToToolDefinition(stub, ctx);
32627
- registry3.register(td);
33832
+ registry4.register(td);
32628
33833
  }
32629
- return registry3;
33834
+ return registry4;
32630
33835
  }
32631
33836
  function adaptStubToToolDefinition(stub, workspaceCtx) {
32632
33837
  return {
@@ -32658,7 +33863,7 @@ var init_toolRegistry2 = __esm({
32658
33863
  "src/cli/workspace/toolRegistry.ts"() {
32659
33864
  "use strict";
32660
33865
  init_zod();
32661
- init_registry();
33866
+ init_registry2();
32662
33867
  init_toolTypes();
32663
33868
  init_stubs();
32664
33869
  }
@@ -33004,10 +34209,10 @@ function getUserMcpPath() {
33004
34209
  function getProjectMcpPath(projectRoot) {
33005
34210
  return join24(projectRoot, ".zelari", "mcp.json");
33006
34211
  }
33007
- function readFile2(path44) {
33008
- if (!existsSync28(path44)) return {};
34212
+ function readFile2(path47) {
34213
+ if (!existsSync28(path47)) return {};
33009
34214
  try {
33010
- const parsed = JSON.parse(readFileSync23(path44, "utf8"));
34215
+ const parsed = JSON.parse(readFileSync23(path47, "utf8"));
33011
34216
  const out = {};
33012
34217
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
33013
34218
  if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
@@ -33023,10 +34228,10 @@ function readFile2(path44) {
33023
34228
  return {};
33024
34229
  }
33025
34230
  }
33026
- function writeFile(path44, servers) {
33027
- mkdirSync13(dirname6(path44), { recursive: true });
34231
+ function writeFile(path47, servers) {
34232
+ mkdirSync13(dirname6(path47), { recursive: true });
33028
34233
  const body = { mcpServers: servers };
33029
- writeFileSync15(path44, `${JSON.stringify(body, null, 2)}
34234
+ writeFileSync15(path47, `${JSON.stringify(body, null, 2)}
33030
34235
  `, "utf8");
33031
34236
  }
33032
34237
  function listMcpServers(projectRoot) {
@@ -33059,9 +34264,9 @@ function upsertMcpServer(opts) {
33059
34264
  if (!opts.config.command?.trim()) {
33060
34265
  return { ok: false, error: "command is required" };
33061
34266
  }
33062
- let path44;
34267
+ let path47;
33063
34268
  if (opts.scope === "user") {
33064
- path44 = getUserMcpPath();
34269
+ path47 = getUserMcpPath();
33065
34270
  } else {
33066
34271
  const root = opts.projectRoot?.trim();
33067
34272
  if (!root) {
@@ -33070,30 +34275,30 @@ function upsertMcpServer(opts) {
33070
34275
  error: "projectRoot required for project scope (Open Folder first)"
33071
34276
  };
33072
34277
  }
33073
- path44 = getProjectMcpPath(root);
34278
+ path47 = getProjectMcpPath(root);
33074
34279
  }
33075
- const current = readFile2(path44);
34280
+ const current = readFile2(path47);
33076
34281
  current[name] = {
33077
34282
  command: opts.config.command.trim(),
33078
34283
  args: opts.config.args,
33079
34284
  env: opts.config.env,
33080
34285
  enabled: opts.config.enabled !== false
33081
34286
  };
33082
- writeFile(path44, current);
33083
- return { ok: true, path: path44 };
34287
+ writeFile(path47, current);
34288
+ return { ok: true, path: path47 };
33084
34289
  }
33085
34290
  function removeMcpServer(opts) {
33086
- const path44 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
33087
- if (!path44) {
34291
+ const path47 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
34292
+ if (!path47) {
33088
34293
  return { ok: false, error: "projectRoot required for project scope" };
33089
34294
  }
33090
- const current = readFile2(path44);
34295
+ const current = readFile2(path47);
33091
34296
  if (!(opts.name in current)) {
33092
- return { ok: false, error: `Server "${opts.name}" not found in ${path44}` };
34297
+ return { ok: false, error: `Server "${opts.name}" not found in ${path47}` };
33093
34298
  }
33094
34299
  delete current[opts.name];
33095
- writeFile(path44, current);
33096
- return { ok: true, path: path44 };
34300
+ writeFile(path47, current);
34301
+ return { ok: true, path: path47 };
33097
34302
  }
33098
34303
  var init_mcpConfigIo = __esm({
33099
34304
  "src/cli/mcp/mcpConfigIo.ts"() {
@@ -33237,7 +34442,7 @@ async function ensureLoaded(projectRoot) {
33237
34442
  function sanitizeToolName(raw) {
33238
34443
  return raw.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
33239
34444
  }
33240
- async function registerMcpTools(registry3, projectRoot = process.cwd(), opts) {
34445
+ async function registerMcpTools(registry4, projectRoot = process.cwd(), opts) {
33241
34446
  if (process.env["ZELARI_MCP"] === "0") return { registered: [], warnings: [] };
33242
34447
  await ensureLoaded(projectRoot);
33243
34448
  const skipCuaForCouncil = opts?.councilMode === true && !isCuaAllowedForCouncil();
@@ -33246,7 +34451,7 @@ async function registerMcpTools(registry3, projectRoot = process.cwd(), opts) {
33246
34451
  if (skipCuaForCouncil && isCuaMcpServerName(t.serverName)) {
33247
34452
  continue;
33248
34453
  }
33249
- registry3.register({
34454
+ registry4.register({
33250
34455
  name: t.registryName,
33251
34456
  description: `[MCP:${t.serverName}] ${t.info.description}`.slice(0, 1024),
33252
34457
  // The MCP server owns validation; its JSON Schema is forwarded to the
@@ -33430,10 +34635,10 @@ import { createHash as createHash5 } from "node:crypto";
33430
34635
  import { join as join26 } from "node:path";
33431
34636
  import { readFile as readFile3 } from "node:fs/promises";
33432
34637
  async function readPackageJson2(projectRoot) {
33433
- const path44 = join26(projectRoot, "package.json");
33434
- if (!existsSync30(path44)) return null;
34638
+ const path47 = join26(projectRoot, "package.json");
34639
+ if (!existsSync30(path47)) return null;
33435
34640
  try {
33436
- return JSON.parse(await readFile3(path44, "utf8"));
34641
+ return JSON.parse(await readFile3(path47, "utf8"));
33437
34642
  } catch {
33438
34643
  return null;
33439
34644
  }
@@ -33515,9 +34720,9 @@ async function genBuild(ctx) {
33515
34720
  ].join("\n");
33516
34721
  }
33517
34722
  async function genOpenQuestions(ctx) {
33518
- const path44 = join26(ctx.rootDir, "risks.md");
33519
- if (!existsSync30(path44)) return "_No open questions._";
33520
- const content = readFileSync25(path44, "utf8");
34723
+ const path47 = join26(ctx.rootDir, "risks.md");
34724
+ if (!existsSync30(path47)) return "_No open questions._";
34725
+ const content = readFileSync25(path47, "utf8");
33521
34726
  const lines = content.split("\n");
33522
34727
  const questions = [];
33523
34728
  let currentTitle = "";
@@ -34048,9 +35253,9 @@ async function runPostCouncilHook(ctx, options) {
34048
35253
  lessons = { ran: true, captured: 0, rejected: 0 };
34049
35254
  for (const r of verification.report.results) {
34050
35255
  if (r.ok) continue;
34051
- const cap2 = captureFailure(ctx.rootDir, r);
34052
- if (cap2.rejected) lessons.rejected++;
34053
- else if (cap2.captured) lessons.captured++;
35256
+ const cap3 = captureFailure(ctx.rootDir, r);
35257
+ if (cap3.rejected) lessons.rejected++;
35258
+ else if (cap3.captured) lessons.captured++;
34054
35259
  }
34055
35260
  } else if (process.env["ZELARI_LESSONS"] === "0") {
34056
35261
  lessons = {
@@ -34084,8 +35289,8 @@ async function runPostCouncilHook(ctx, options) {
34084
35289
  sources: scope.sources
34085
35290
  } : void 0
34086
35291
  });
34087
- const path44 = writeCouncilCompletion(ctx.rootDir, completion);
34088
- completionHook = { ran: true, path: path44, completion };
35292
+ const path47 = writeCouncilCompletion(ctx.rootDir, completion);
35293
+ completionHook = { ran: true, path: path47, completion };
34089
35294
  } catch (err) {
34090
35295
  completionHook = {
34091
35296
  ran: true,
@@ -35303,10 +36508,18 @@ __export(planner_exports, {
35303
36508
  KRAKEN_PLANNER_SYSTEM_PROMPT: () => KRAKEN_PLANNER_SYSTEM_PROMPT,
35304
36509
  PlannerTransportError: () => PlannerTransportError,
35305
36510
  buildGraphFromPlan: () => buildGraphFromPlan,
36511
+ buildPlannerSystemPrompt: () => buildPlannerSystemPrompt,
36512
+ buildPlannerUserPrompt: () => buildPlannerUserPrompt,
35306
36513
  extractJsonObject: () => extractJsonObject,
35307
36514
  planTaskGraph: () => planTaskGraph,
35308
36515
  stripReasoningBlocks: () => stripReasoningBlocks
35309
36516
  });
36517
+ function resolvePlannerWorkspaceChars(env = process.env) {
36518
+ const raw = env.ZELARI_KRAKEN_PLANNER_WORKSPACE_CHARS;
36519
+ if (raw === void 0 || raw === "") return DEFAULT_PLANNER_WORKSPACE_CHARS;
36520
+ const n = Number.parseInt(raw, 10);
36521
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_PLANNER_WORKSPACE_CHARS;
36522
+ }
35310
36523
  function resolvePlannerTimeoutMs(env = process.env) {
35311
36524
  const raw = env.ZELARI_KRAKEN_PLANNER_TIMEOUT_MS;
35312
36525
  if (raw === void 0 || raw === "") return DEFAULT_LLM_TIMEOUT_MS;
@@ -35319,6 +36532,15 @@ function resolvePlannerMaxTokens(env = process.env) {
35319
36532
  const n = Number.parseInt(raw, 10);
35320
36533
  return Number.isFinite(n) && n > 0 ? n : DEFAULT_LLM_MAX_TOKENS;
35321
36534
  }
36535
+ function resolveBennettsRazorEnabled(env = process.env) {
36536
+ const raw = env.ZELARI_KRAKEN_PLANNER_BENNETTS_RAZOR;
36537
+ if (raw === void 0 || raw === "") return false;
36538
+ return raw === "1" || raw.toLowerCase() === "true";
36539
+ }
36540
+ function buildPlannerSystemPrompt(env = process.env) {
36541
+ if (!resolveBennettsRazorEnabled(env)) return KRAKEN_PLANNER_SYSTEM_PROMPT;
36542
+ return KRAKEN_PLANNER_SYSTEM_PROMPT + "\n" + KRAKEN_PLANNER_BENNETTS_RAZOR_SECTION;
36543
+ }
35322
36544
  function extractJsonObject(text, opts = {}) {
35323
36545
  const stripped = stripReasoningBlocks(text);
35324
36546
  const usable = findJsonIn(stripped, opts);
@@ -35615,14 +36837,32 @@ async function createDefaultLlmClient(opts) {
35615
36837
  }
35616
36838
  };
35617
36839
  }
35618
- function buildPlannerUserPrompt(prompt, previousAttempt) {
35619
- const prev2 = previousAttempt?.trim() ? `
35620
- ${previousAttempt.trim()}
35621
- ` : "";
35622
- return `Goal:
35623
- ${prompt.trim()}
35624
- ${prev2}
35625
- Return ONLY the JSON object described in the system prompt.`;
36840
+ function resolveWorkspaceListing(opts) {
36841
+ if (opts.workspace !== void 0) return opts.workspace || void 0;
36842
+ if (!opts.cwd) return void 0;
36843
+ const maxChars = resolvePlannerWorkspaceChars();
36844
+ if (maxChars <= 0) return void 0;
36845
+ try {
36846
+ return buildWorkspaceSummary(opts.cwd, { maxEntries: 24, maxChars }) || void 0;
36847
+ } catch {
36848
+ return void 0;
36849
+ }
36850
+ }
36851
+ function buildPlannerUserPrompt(prompt, opts = {}) {
36852
+ const parts = [`Goal:
36853
+ ${prompt.trim()}`];
36854
+ if (opts.workspace?.trim()) {
36855
+ parts.push(
36856
+ "",
36857
+ "## The project this goal is about (real files on disk)",
36858
+ opts.workspace.trim()
36859
+ );
36860
+ }
36861
+ if (opts.previousAttempt?.trim()) {
36862
+ parts.push("", opts.previousAttempt.trim());
36863
+ }
36864
+ parts.push("", "Return ONLY the JSON object described in the system prompt.");
36865
+ return parts.join("\n");
35626
36866
  }
35627
36867
  function uniqueId(base, existing) {
35628
36868
  if (!existing.has(base)) return base;
@@ -35631,11 +36871,40 @@ function uniqueId(base, existing) {
35631
36871
  return `${base}-${i}`;
35632
36872
  }
35633
36873
  function buildAutoVerifyPrompt(general) {
35634
- const acc = general.acceptance && general.acceptance.length > 0 ? `
35635
-
35636
- Acceptance criteria to check:
35637
- ${general.acceptance.map((a) => `- ${a}`).join("\n")}` : "";
35638
- return `Verify the following work was completed correctly on disk: ${general.label}.${acc}`;
36874
+ const taskPrompt = general.prompt.length > MAX_VERIFY_TASK_PROMPT_CHARS ? `${general.prompt.slice(0, MAX_VERIFY_TASK_PROMPT_CHARS)}
36875
+ \u2026 [truncated]` : general.prompt;
36876
+ const parts = [
36877
+ `Verify on disk that this work was actually completed correctly: ${general.label}.`,
36878
+ "",
36879
+ "## The task that was carried out",
36880
+ taskPrompt
36881
+ ];
36882
+ if (general.scope && general.scope.length > 0) {
36883
+ parts.push("", "## Paths the work was scoped to", ...general.scope.map((s) => `- ${s}`));
36884
+ }
36885
+ if (general.acceptance && general.acceptance.length > 0) {
36886
+ parts.push(
36887
+ "",
36888
+ "## Acceptance criteria to check explicitly",
36889
+ ...general.acceptance.map((a) => `- ${a}`)
36890
+ );
36891
+ }
36892
+ parts.push(
36893
+ "",
36894
+ "Read the files involved rather than trusting any summary. Report the commands you ran and every gap you found.",
36895
+ "",
36896
+ "## How to report your verdict",
36897
+ "End your final message with a line of exactly this form, as the LAST line:",
36898
+ "",
36899
+ "VERDICT: PASS",
36900
+ "",
36901
+ "or",
36902
+ "",
36903
+ "VERDICT: FAIL",
36904
+ "",
36905
+ "This line is parsed. FAIL sends the work back to the tentacle that wrote it, together with everything you write above the line \u2014 so state each gap concretely enough to be acted on (file, what is wrong, what it should be). Only report FAIL for a real defect against the task or its acceptance criteria: a rework round is expensive and there is only a small number of them. Stylistic preferences are not a FAIL."
36906
+ );
36907
+ return parts.join("\n");
35639
36908
  }
35640
36909
  function buildGraphFromPlan(graphId, planned) {
35641
36910
  const nodes = planned.map((p3) => ({
@@ -35688,12 +36957,16 @@ async function planTaskGraph(opts) {
35688
36957
  const client = opts.llmClient ?? await createDefaultLlmClient({ provider: opts.provider, model: opts.model });
35689
36958
  const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
35690
36959
  const graphId = opts.graphId ?? `kraken-${Date.now().toString(36)}`;
35691
- const userBase = buildPlannerUserPrompt(opts.prompt, opts.previousAttempt);
36960
+ const workspace = resolveWorkspaceListing(opts);
36961
+ const userBase = buildPlannerUserPrompt(opts.prompt, {
36962
+ ...opts.previousAttempt ? { previousAttempt: opts.previousAttempt } : {},
36963
+ ...workspace ? { workspace } : {}
36964
+ });
35692
36965
  let lastError;
35693
36966
  let userMessage = userBase;
35694
36967
  for (let attempt = 1; attempt <= MAX_PLAN_ATTEMPTS; attempt++) {
35695
36968
  try {
35696
- const text = await client.complete({ system: KRAKEN_PLANNER_SYSTEM_PROMPT, user: userMessage });
36969
+ const text = await client.complete({ system: buildPlannerSystemPrompt(), user: userMessage });
35697
36970
  const parsedJson = extractJsonObject(text, { requireKey: "nodes" });
35698
36971
  const validated = PlannedGraphSchema.parse(parsedJson);
35699
36972
  if (!validated.nodes.some((n) => n.kind === "general")) {
@@ -35719,7 +36992,7 @@ Your previous response was invalid (${lastError}). Return ONLY corrected JSON ma
35719
36992
  `kraken planner: failed to produce a valid task graph after ${MAX_PLAN_ATTEMPTS} attempts \u2014 ${lastError}`
35720
36993
  );
35721
36994
  }
35722
- var MAX_PLAN_ATTEMPTS, DEFAULT_LLM_TIMEOUT_MS, PlannerTransportError, DEFAULT_LLM_MAX_TOKENS, DEFAULT_MAX_RETRIES, KRAKEN_PLANNER_SYSTEM_PROMPT, PlannedNodeSchema, PlannedGraphSchema;
36995
+ var MAX_PLAN_ATTEMPTS, DEFAULT_PLANNER_WORKSPACE_CHARS, DEFAULT_LLM_TIMEOUT_MS, PlannerTransportError, DEFAULT_LLM_MAX_TOKENS, DEFAULT_MAX_RETRIES, KRAKEN_PLANNER_SYSTEM_PROMPT, KRAKEN_PLANNER_BENNETTS_RAZOR_SECTION, PlannedNodeSchema, PlannedGraphSchema, MAX_VERIFY_TASK_PROMPT_CHARS;
35723
36996
  var init_planner = __esm({
35724
36997
  "src/cli/kraken/planner.ts"() {
35725
36998
  "use strict";
@@ -35728,7 +37001,9 @@ var init_planner = __esm({
35728
37001
  init_providerConfig();
35729
37002
  init_keyStore();
35730
37003
  init_openai_compatible();
37004
+ init_workspaceSummary();
35731
37005
  MAX_PLAN_ATTEMPTS = 2;
37006
+ DEFAULT_PLANNER_WORKSPACE_CHARS = 3e3;
35732
37007
  DEFAULT_LLM_TIMEOUT_MS = 3e5;
35733
37008
  PlannerTransportError = class extends Error {
35734
37009
  constructor(message) {
@@ -35742,7 +37017,11 @@ var init_planner = __esm({
35742
37017
  general: 1,
35743
37018
  verify: 1,
35744
37019
  fix: 0,
35745
- merge: 0
37020
+ merge: 0,
37021
+ // Pillar 2 persona kinds: same retry budget as `verify` (the gate is
37022
+ // the same — trailer-based).
37023
+ spec: 1,
37024
+ conformance: 1
35746
37025
  };
35747
37026
  KRAKEN_PLANNER_SYSTEM_PROMPT = [
35748
37027
  "You are the PLANNER for Kraken, a multi-agent graph executor.",
@@ -35754,14 +37033,45 @@ var init_planner = __esm({
35754
37033
  "Rules:",
35755
37034
  '- kind "explore": read-only research (no edits). Use to gather context before edits.',
35756
37035
  '- kind "general": can edit files for one bounded, self-contained unit of work.',
35757
- '- Do NOT emit "verify", "fix", or "merge" nodes \u2014 the executor adds those automatically.',
37036
+ '- Do NOT emit "verify", "spec", "conformance", "fix", or "merge" nodes \u2014 the executor adds those automatically based on the writer kind and the goal shape.',
35758
37037
  '- "id" must be short, unique, kebab-case (e.g. "e1", "g-auth", "g-ui").',
35759
- '- "prompt" must be fully self-contained: the sub-agent sees ONLY this prompt, not this conversation.',
37038
+ '- "prompt" must be self-contained: the sub-agent sees ONLY this prompt, not this conversation.',
35760
37039
  '- "deps" lists ids of nodes that must finish first (topological order); [] if none.',
37040
+ '- A node DOES receive what its "deps" reported when they finished \u2014 the executor injects their conclusions into its prompt. So write a dependent node as "using the findings above, \u2026" rather than duplicating the research its dependency will do.',
35761
37041
  '- When two "general" nodes touch disjoint parts of the codebase, give each a "scope" (path/glob allowlist) so they can run in parallel safely. If scopes might overlap, either omit scope (forces sequential execution) or add a dep between them.',
37042
+ '- The user message includes a listing of the real project on disk. Build every "scope" from paths that appear there (or new paths that clearly belong beside them) \u2014 an invented path makes the parallelism decision meaningless, since scopes are exactly what the executor uses to decide which writers may run at the same time.',
35762
37043
  '- Prefer one "explore" node feeding several parallel "general" nodes over one giant node.',
35763
37044
  "- Keep the graph small: most goals need 3-8 nodes total.",
35764
- '- "acceptance" (optional) lists concrete, checkable criteria for a "general" node.'
37045
+ '- "acceptance" (optional) lists concrete, checkable criteria for a "general" node. These are ENFORCED: the executor adds a verify tentacle that checks them on disk and can send the work back for a rework round when they are not met. Write criteria a reader can settle by opening a file or running a command ("exports slugify(input: string): string", "npm test passes"), never subjective ones ("the code is elegant") \u2014 a criterion nobody can check just burns a rework round.',
37046
+ "",
37047
+ "# Reviewer personas (Pillar 2 spec council)",
37048
+ "",
37049
+ "After every `general` node, the executor auto-injects a `verify` tentacle that checks the `acceptance[]` criteria. For goals that have a written spec or that must literally match the user prompt, the executor also auto-injects two more personas:",
37050
+ ' - `spec` (spec-reviewer) \u2014 compares the writer\'s output against a written spec/plan, per requirement. Conservative: "reasonable but different" is a FAIL.',
37051
+ ' - `conformance` (conformance-reviewer) \u2014 compares the writer\'s output against the USER\'S ORIGINAL VERBATIM PROMPT. Literal: "use session cookies" is a FAIL when the prompt said "use JWT".',
37052
+ "",
37053
+ "You (the JSON planner) do NOT emit these directly; the executor decides when to inject them. To hint that a node needs a `conformance` reviewer, include the user's original ask verbatim in the `prompt`."
37054
+ ].join("\n");
37055
+ KRAKEN_PLANNER_BENNETTS_RAZOR_SECTION = [
37056
+ "",
37057
+ "# Bennett's Razor (tie-breaker)",
37058
+ "",
37059
+ 'When two valid plans cover the goal, prefer the one that "assumes the least".',
37060
+ "A plan is *more specific* when it pins exact paths, exact semver, exact function",
37061
+ "signatures, or hard invariants about external state. A more general plan that",
37062
+ "still satisfies the goal generalises better (Bennett, AGI 2023: 1.1\xD7\u20135\xD7 the rate",
37063
+ "of MDL in his experiments).",
37064
+ "",
37065
+ "Examples of the bias you should apply:",
37066
+ '- "edit the user model" > "edit /src/models/user.ts and add a nullable email field"',
37067
+ " when the goal can be met either way.",
37068
+ '- "use a stable sort" > "use Array.prototype.sort with a custom comparator" unless',
37069
+ " the user pinned the API.",
37070
+ "- Prefer plans that touch fewer files and assume less about runtime versions.",
37071
+ "",
37072
+ "Hard constraint: do NOT weaken a plan below the bar in the name of weakness. The",
37073
+ "goal must be reachable by following the plan as written; weakness is only a",
37074
+ "tie-breaker among plans that do reach the goal."
35765
37075
  ].join("\n");
35766
37076
  PlannedNodeSchema = external_exports.object({
35767
37077
  id: external_exports.string().min(1).max(64).regex(/^[a-zA-Z0-9_-]+$/, "id must be alphanumeric/dash/underscore"),
@@ -35775,6 +37085,7 @@ var init_planner = __esm({
35775
37085
  PlannedGraphSchema = external_exports.object({
35776
37086
  nodes: external_exports.array(PlannedNodeSchema).min(1).max(DEFAULT_MAX_NODES)
35777
37087
  });
37088
+ MAX_VERIFY_TASK_PROMPT_CHARS = 1200;
35778
37089
  }
35779
37090
  });
35780
37091
 
@@ -35792,11 +37103,16 @@ function snapshotPath(cwd) {
35792
37103
  return path34.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
35793
37104
  }
35794
37105
  function toGraphSnapshot(graph, opts) {
37106
+ const unresolved = (opts.unresolvedFindings ?? []).map((u) => ({
37107
+ ...u,
37108
+ findings: u.findings.slice(0, MAX_SNAPSHOT_FINDINGS_CHARS)
37109
+ }));
35795
37110
  return {
35796
37111
  graphId: graph.id,
35797
37112
  goal: opts.goal,
35798
37113
  finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
35799
37114
  converged: opts.converged,
37115
+ ...unresolved.length > 0 ? { unresolvedFindings: unresolved } : {},
35800
37116
  nodes: [...graph.nodes.values()].map((n) => ({
35801
37117
  id: n.id,
35802
37118
  kind: n.kind,
@@ -35831,19 +37147,37 @@ function formatSnapshotForPlanner(snapshot) {
35831
37147
  const done = snapshot.nodes.filter((n) => n.status === "done");
35832
37148
  const failed = snapshot.nodes.filter((n) => n.status === "error");
35833
37149
  const skipped = snapshot.nodes.filter((n) => n.status === "skipped");
35834
- if (failed.length === 0 && skipped.length === 0) return "";
37150
+ const rejected = snapshot.unresolvedFindings ?? [];
37151
+ if (failed.length === 0 && skipped.length === 0 && rejected.length === 0) return "";
35835
37152
  const line = (n) => `- ${n.label}${n.scope ? ` [${n.scope.join(", ")}]` : ""}${n.error ? ` \u2014 ${n.error}` : ""}`;
37153
+ const rejectedIds = new Set(rejected.map((r) => r.nodeId));
37154
+ const outcome = [
37155
+ `${done.length} done`,
37156
+ `${failed.length} failed`,
37157
+ `${skipped.length} never ran`,
37158
+ ...rejected.length > 0 ? [`${rejected.length} rejected by review`] : []
37159
+ ].join(", ");
35836
37160
  const parts = [
35837
37161
  "",
35838
37162
  "## Previous unfinished task graph in this project",
35839
37163
  `Goal it was working on: "${snapshot.goal}"`,
35840
- `It did NOT finish (${done.length} done, ${failed.length} failed, ${skipped.length} never ran).`,
37164
+ `It did NOT finish cleanly (${outcome}).`,
35841
37165
  "",
35842
37166
  "If that goal is unrelated to the one above, ignore this section entirely.",
35843
37167
  ""
35844
37168
  ];
35845
- if (done.length > 0) {
35846
- parts.push("Already completed \u2014 do NOT redo this work:", ...done.map(line), "");
37169
+ const cleanlyDone = done.filter((n) => !rejectedIds.has(n.id));
37170
+ if (cleanlyDone.length > 0) {
37171
+ parts.push("Already completed \u2014 do NOT redo this work:", ...cleanlyDone.map(line), "");
37172
+ }
37173
+ if (rejected.length > 0) {
37174
+ parts.push(
37175
+ "Completed but REJECTED by review \u2014 the code exists, the defects do not fix themselves:",
37176
+ ...rejected.map(
37177
+ (r) => `- ${r.label} \u2014 ${r.findings.trim().split("\n")[0] ?? "no detail"}`
37178
+ ),
37179
+ ""
37180
+ );
35847
37181
  }
35848
37182
  if (failed.length > 0) {
35849
37183
  parts.push("Failed \u2014 needs to be finished or repaired:", ...failed.map(line), "");
@@ -35856,12 +37190,13 @@ function formatSnapshotForPlanner(snapshot) {
35856
37190
  );
35857
37191
  return parts.join("\n");
35858
37192
  }
35859
- var SNAPSHOT_DIR, SNAPSHOT_FILE;
37193
+ var SNAPSHOT_DIR, SNAPSHOT_FILE, MAX_SNAPSHOT_FINDINGS_CHARS;
35860
37194
  var init_graphMemory = __esm({
35861
37195
  "src/cli/kraken/graphMemory.ts"() {
35862
37196
  "use strict";
35863
37197
  SNAPSHOT_DIR = path34.join(".zelari", "kraken");
35864
37198
  SNAPSHOT_FILE = "last-graph.json";
37199
+ MAX_SNAPSHOT_FINDINGS_CHARS = 400;
35865
37200
  }
35866
37201
  });
35867
37202
 
@@ -35873,21 +37208,149 @@ var init_tentacle = __esm({
35873
37208
  }
35874
37209
  });
35875
37210
 
37211
+ // src/cli/kraken/weaknessMeter.ts
37212
+ var weaknessMeter_exports = {};
37213
+ __export(weaknessMeter_exports, {
37214
+ _resetWeaknessMeterCacheForTests: () => _resetWeaknessMeterCacheForTests,
37215
+ isWeaknessMeterEnabled: () => isWeaknessMeterEnabled,
37216
+ measureWeaknessViaLLM: () => measureWeaknessViaLLM,
37217
+ parseMeterContent: () => parseMeterContent
37218
+ });
37219
+ function isWeaknessMeterEnabled(env = process.env) {
37220
+ if (meterEnabledCache !== null) return meterEnabledCache;
37221
+ const raw = env.ZELARI_KRAKEN_WEAKNESS_METER;
37222
+ const enabled = raw === "1" || raw === "true" || raw === "yes";
37223
+ meterEnabledCache = enabled;
37224
+ return enabled;
37225
+ }
37226
+ function _resetWeaknessMeterCacheForTests() {
37227
+ meterEnabledCache = null;
37228
+ }
37229
+ function resolveMeterModel(env = process.env, fallback) {
37230
+ const raw = env.ZELARI_KRAKEN_WEAKNESS_METER_MODEL;
37231
+ if (raw && raw.trim() !== "") return raw.trim();
37232
+ return fallback;
37233
+ }
37234
+ async function measureWeaknessViaLLM(text, options = {}) {
37235
+ const env = options.env ?? process.env;
37236
+ if (!isWeaknessMeterEnabled(env)) return null;
37237
+ if (typeof text !== "string" || text.trim() === "") return null;
37238
+ const provider = options.providerOverride ?? await resolveActiveProvider2(env);
37239
+ if (!provider) return null;
37240
+ const model = options.modelOverride ?? resolveMeterModel(env, provider.model);
37241
+ const userPayload = JSON.stringify({
37242
+ task: "measure weakness of the following reviewer reply",
37243
+ reply: text
37244
+ });
37245
+ const start = Date.now();
37246
+ try {
37247
+ const fetchImpl = options.fetchImpl ?? fetch;
37248
+ const ac = new AbortController();
37249
+ const timer = setTimeout(() => ac.abort(), METER_TIMEOUT_MS);
37250
+ let response;
37251
+ try {
37252
+ response = await fetchImpl(provider.endpoint, {
37253
+ method: "POST",
37254
+ headers: {
37255
+ "Content-Type": "application/json",
37256
+ Authorization: `Bearer ${provider.apiKey}`
37257
+ },
37258
+ body: JSON.stringify({
37259
+ model,
37260
+ messages: [
37261
+ { role: "system", content: WEAKNESS_METER_PROMPT },
37262
+ { role: "user", content: userPayload }
37263
+ ],
37264
+ // The meter is a classification call; no need for a hot
37265
+ // sampling distribution. Most providers accept this pair
37266
+ // without complaint; the few that require it get a stable
37267
+ // answer.
37268
+ temperature: 0,
37269
+ stream: false
37270
+ }),
37271
+ signal: ac.signal
37272
+ });
37273
+ } finally {
37274
+ clearTimeout(timer);
37275
+ }
37276
+ if (!response.ok) return null;
37277
+ const json2 = await response.json();
37278
+ const raw = json2.choices?.[0]?.message?.content;
37279
+ if (typeof raw !== "string") return null;
37280
+ return parseMeterContent(raw, model, Date.now() - start);
37281
+ } catch {
37282
+ return null;
37283
+ }
37284
+ }
37285
+ function parseMeterContent(raw, model, durationMs) {
37286
+ const trimmed = raw.trim();
37287
+ const fenced = /^```(?:json)?\s*([\s\S]+?)\s*```$/i.exec(trimmed);
37288
+ const body = fenced ? fenced[1] : trimmed;
37289
+ let parsed;
37290
+ try {
37291
+ parsed = JSON.parse(body);
37292
+ } catch {
37293
+ return null;
37294
+ }
37295
+ const validated = WeaknessMeterResponseSchema.safeParse(parsed);
37296
+ if (!validated.success) return null;
37297
+ return {
37298
+ meter: validated.data,
37299
+ weakness: weaknessFromMeter(validated.data),
37300
+ durationMs,
37301
+ model
37302
+ };
37303
+ }
37304
+ async function resolveActiveProvider2(env) {
37305
+ const cfg = await getProviderConfig();
37306
+ if (!cfg) return null;
37307
+ const providerId = cfg.activeProviderId;
37308
+ const key = await resolveApiKeyWithMeta(providerId, env);
37309
+ if (!key?.apiKey) return null;
37310
+ const model = getModelForProvider(providerId) ?? cfg.modelByProvider[providerId] ?? "";
37311
+ const endpoint = getCustomEndpoint(providerId) ?? `https://api.openai.com/v1/chat/completions`;
37312
+ return { providerId, model, endpoint, apiKey: key.apiKey };
37313
+ }
37314
+ var meterEnabledCache, METER_TIMEOUT_MS;
37315
+ var init_weaknessMeter = __esm({
37316
+ "src/cli/kraken/weaknessMeter.ts"() {
37317
+ "use strict";
37318
+ init_dist();
37319
+ init_providerConfig();
37320
+ init_keyStore();
37321
+ meterEnabledCache = null;
37322
+ METER_TIMEOUT_MS = (() => {
37323
+ const raw = process.env.ZELARI_KRAKEN_WEAKNESS_METER_TIMEOUT_MS;
37324
+ if (!raw) return 12e3;
37325
+ const n = Number.parseInt(raw, 10);
37326
+ return Number.isFinite(n) && n > 0 ? n : 12e3;
37327
+ })();
37328
+ }
37329
+ });
37330
+
35876
37331
  // src/cli/kraken/executor.ts
35877
37332
  var executor_exports = {};
35878
37333
  __export(executor_exports, {
35879
37334
  DEFAULT_CANCEL_GRACE_MS: () => DEFAULT_CANCEL_GRACE_MS,
35880
37335
  DEFAULT_FIX_BUDGET: () => DEFAULT_FIX_BUDGET,
37336
+ DEFAULT_GRAPH_TIMEOUT_MS: () => DEFAULT_GRAPH_TIMEOUT_MS,
35881
37337
  DEFAULT_MAX_PARALLEL: () => DEFAULT_MAX_PARALLEL,
37338
+ DEFAULT_MAX_REVIEW_ROUNDS: () => DEFAULT_MAX_REVIEW_ROUNDS,
35882
37339
  DEFAULT_NODE_TIMEOUT_MS: () => DEFAULT_NODE_TIMEOUT_MS,
35883
37340
  DEFAULT_WRITER_NODE_TIMEOUT_MS: () => DEFAULT_WRITER_NODE_TIMEOUT_MS,
35884
37341
  KrakenGraphExecutor: () => KrakenGraphExecutor,
37342
+ MAX_UPSTREAM_CHARS_PER_DEP: () => MAX_UPSTREAM_CHARS_PER_DEP,
37343
+ MAX_UPSTREAM_CHARS_TOTAL: () => MAX_UPSTREAM_CHARS_TOTAL,
37344
+ buildUpstreamContext: () => buildUpstreamContext,
35885
37345
  isKrakenGraphEnabled: () => isKrakenGraphEnabled,
35886
37346
  isWorldModelGateEnabled: () => isWorldModelGateEnabled,
35887
37347
  resolveCancelGraceMs: () => resolveCancelGraceMs,
35888
37348
  resolveFixBudget: () => resolveFixBudget,
37349
+ resolveGraphTimeoutMs: () => resolveGraphTimeoutMs,
35889
37350
  resolveMaxParallel: () => resolveMaxParallel,
35890
- resolveNodeTimeoutMs: () => resolveNodeTimeoutMs
37351
+ resolveMaxReviewRounds: () => resolveMaxReviewRounds,
37352
+ resolveNodeTimeoutMs: () => resolveNodeTimeoutMs,
37353
+ thoroughnessForKind: () => thoroughnessForKind
35891
37354
  });
35892
37355
  import { existsSync as existsSync34 } from "node:fs";
35893
37356
  import path35 from "node:path";
@@ -35897,11 +37360,25 @@ function resolveMaxParallel(env = process.env) {
35897
37360
  const n = Number.parseInt(raw, 10);
35898
37361
  return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_PARALLEL;
35899
37362
  }
35900
- function resolveFixBudget(env = process.env) {
37363
+ function resolveFixBudget(env = process.env, nodeCount = 0) {
35901
37364
  const raw = env.ZELARI_KRAKEN_FIX_BUDGET;
35902
- if (raw === void 0 || raw === "") return DEFAULT_FIX_BUDGET;
37365
+ if (raw === void 0 || raw === "") {
37366
+ return Math.max(DEFAULT_FIX_BUDGET, Math.ceil(nodeCount / 2));
37367
+ }
35903
37368
  const n = Number.parseInt(raw, 10);
35904
- return Number.isFinite(n) && n >= 0 ? n : DEFAULT_FIX_BUDGET;
37369
+ return Number.isFinite(n) && n >= 0 ? n : Math.max(DEFAULT_FIX_BUDGET, Math.ceil(nodeCount / 2));
37370
+ }
37371
+ function resolveMaxReviewRounds(env = process.env) {
37372
+ const raw = env.ZELARI_KRAKEN_MAX_REVIEW_ROUNDS;
37373
+ if (raw === void 0 || raw === "") return DEFAULT_MAX_REVIEW_ROUNDS;
37374
+ const n = Number.parseInt(raw, 10);
37375
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_MAX_REVIEW_ROUNDS;
37376
+ }
37377
+ function resolveGraphTimeoutMs(env = process.env) {
37378
+ const raw = env.ZELARI_KRAKEN_GRAPH_TIMEOUT_MS;
37379
+ if (raw === void 0 || raw === "") return DEFAULT_GRAPH_TIMEOUT_MS;
37380
+ const n = Number.parseInt(raw, 10);
37381
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_GRAPH_TIMEOUT_MS;
35905
37382
  }
35906
37383
  function resolveCancelGraceMs(env = process.env) {
35907
37384
  const raw = env.ZELARI_KRAKEN_CANCEL_GRACE_MS;
@@ -35939,7 +37416,51 @@ function defaultChecksExists(cwd) {
35939
37416
  return false;
35940
37417
  }
35941
37418
  }
35942
- var DEFAULT_MAX_PARALLEL, DEFAULT_FIX_BUDGET, DEFAULT_NODE_TIMEOUT_MS, DEFAULT_WRITER_NODE_TIMEOUT_MS, DEFAULT_CANCEL_GRACE_MS, KrakenGraphExecutor;
37419
+ function buildUpstreamContext(graph, node) {
37420
+ const parts = [];
37421
+ const omitted = [];
37422
+ let budget = MAX_UPSTREAM_CHARS_TOTAL;
37423
+ for (const depId of node.deps) {
37424
+ const dep = graph.nodes.get(depId);
37425
+ if (!dep || dep.status !== "done") continue;
37426
+ const raw = (dep.result ?? "").trim();
37427
+ if (!raw) continue;
37428
+ const cap3 = Math.min(MAX_UPSTREAM_CHARS_PER_DEP, budget);
37429
+ if (cap3 <= 0) {
37430
+ omitted.push(dep.label);
37431
+ continue;
37432
+ }
37433
+ const body = raw.length > cap3 ? `${raw.slice(0, cap3)}
37434
+ \u2026 [truncated ${raw.length}\u2192${cap3} chars]` : raw;
37435
+ budget -= Math.min(raw.length, cap3);
37436
+ const scope = dep.scope && dep.scope.length > 0 ? `, scope: ${dep.scope.join(", ")}` : "";
37437
+ parts.push(`### ${dep.label} (${dep.kind}${scope})
37438
+ ${body}`);
37439
+ }
37440
+ if (parts.length === 0) return "";
37441
+ const lines = [
37442
+ "",
37443
+ "## Context from completed upstream tasks",
37444
+ "Results reported by the tasks this one depends on. Treat them as hypotheses \u2014 prefer the actual files on disk where they conflict.",
37445
+ "",
37446
+ ...parts
37447
+ ];
37448
+ if (omitted.length > 0) {
37449
+ lines.push("", `(omitted for context budget: ${omitted.join(", ")})`);
37450
+ }
37451
+ return lines.join("\n");
37452
+ }
37453
+ function thoroughnessForKind(kind) {
37454
+ return kind === "general" || kind === "fix" ? "deep" : "medium";
37455
+ }
37456
+ function firstLine(text, maxChars = 160) {
37457
+ const line = text.trim().split("\n").find((l) => l.trim() !== "")?.trim() ?? "";
37458
+ return line.length > maxChars ? `${line.slice(0, maxChars)}\u2026` : line;
37459
+ }
37460
+ function agentForNode(node) {
37461
+ return node.kind === "explore" || node.kind === "verify" ? node.kind : "general";
37462
+ }
37463
+ var DEFAULT_MAX_PARALLEL, DEFAULT_FIX_BUDGET, DEFAULT_MAX_REVIEW_ROUNDS, DEFAULT_GRAPH_TIMEOUT_MS, DEFAULT_NODE_TIMEOUT_MS, DEFAULT_WRITER_NODE_TIMEOUT_MS, DEFAULT_CANCEL_GRACE_MS, MAX_UPSTREAM_CHARS_PER_DEP, MAX_UPSTREAM_CHARS_TOTAL, KrakenGraphExecutor;
35943
37464
  var init_executor = __esm({
35944
37465
  "src/cli/kraken/executor.ts"() {
35945
37466
  "use strict";
@@ -35952,9 +37473,13 @@ var init_executor = __esm({
35952
37473
  init_graphMemory();
35953
37474
  DEFAULT_MAX_PARALLEL = 12;
35954
37475
  DEFAULT_FIX_BUDGET = 3;
37476
+ DEFAULT_MAX_REVIEW_ROUNDS = 1;
37477
+ DEFAULT_GRAPH_TIMEOUT_MS = 0;
35955
37478
  DEFAULT_NODE_TIMEOUT_MS = 3e5;
35956
37479
  DEFAULT_WRITER_NODE_TIMEOUT_MS = 9e5;
35957
37480
  DEFAULT_CANCEL_GRACE_MS = 3e4;
37481
+ MAX_UPSTREAM_CHARS_PER_DEP = 2800;
37482
+ MAX_UPSTREAM_CHARS_TOTAL = 8e3;
35958
37483
  KrakenGraphExecutor = class {
35959
37484
  deps;
35960
37485
  parentCwd;
@@ -35964,12 +37489,44 @@ var init_executor = __esm({
35964
37489
  /** Explicit all-kinds override; when undefined the budget is per-kind. */
35965
37490
  nodeTimeoutMs;
35966
37491
  cancelGraceMs;
37492
+ /** Explicit override; when undefined the budget scales with the graph size. */
37493
+ fixBudgetOption;
35967
37494
  fixBudgetRemaining;
37495
+ maxReviewRounds;
37496
+ graphTimeoutMs;
35968
37497
  worldModelGateOverride;
35969
37498
  runTentacleFn;
35970
37499
  mergeFn;
35971
37500
  backtestFn;
35972
37501
  nodeRunState = /* @__PURE__ */ new Map();
37502
+ /** fix node id → id of the failed node it was spawned to repair. */
37503
+ repairs = /* @__PURE__ */ new Map();
37504
+ /**
37505
+ * rework node id → id of the writer whose work it is redoing. A rework is a
37506
+ * `fix` node, but unlike a repair it must NOT create a worktree of its own:
37507
+ * it edits the writer's existing one (see {@link spawnReworkPair}).
37508
+ */
37509
+ reworks = /* @__PURE__ */ new Map();
37510
+ /** lineage root writer id → rework rounds already spent on that lineage. */
37511
+ reviewRounds = /* @__PURE__ */ new Map();
37512
+ /**
37513
+ * rework node id → the ORIGINAL writer its lineage started from.
37514
+ *
37515
+ * The budget has to be per lineage, not per node: a rework is itself a
37516
+ * writer, so counting rounds against the node being reworked reset the
37517
+ * counter every round and the graph chained rework → verify → rework
37518
+ * forever, terminating only on the scheduler's iteration cap.
37519
+ */
37520
+ reviewLineage = /* @__PURE__ */ new Map();
37521
+ /** Verify verdicts left unresolved when the run ends. */
37522
+ unresolved = [];
37523
+ /** Live cancellation handles for the tentacles currently running. */
37524
+ nodeControllers = /* @__PURE__ */ new Map();
37525
+ /** Wall-clock duration of each node's last run, by node id. */
37526
+ durationsMs = /* @__PURE__ */ new Map();
37527
+ signal;
37528
+ /** Set once the run has been cancelled: stops admission, retries and fixes. */
37529
+ aborted = false;
35973
37530
  fixCounter = 0;
35974
37531
  constructor(opts) {
35975
37532
  this.deps = opts.taskToolDeps;
@@ -35979,16 +37536,50 @@ var init_executor = __esm({
35979
37536
  this.maxParallel = opts.maxParallel ?? resolveMaxParallel();
35980
37537
  this.nodeTimeoutMs = opts.nodeTimeoutMs;
35981
37538
  this.cancelGraceMs = opts.cancelGraceMs;
37539
+ this.fixBudgetOption = opts.fixBudget;
35982
37540
  this.fixBudgetRemaining = opts.fixBudget ?? resolveFixBudget();
37541
+ this.maxReviewRounds = opts.maxReviewRounds ?? resolveMaxReviewRounds();
37542
+ this.graphTimeoutMs = opts.graphTimeoutMs ?? resolveGraphTimeoutMs();
35983
37543
  this.worldModelGateOverride = opts.worldModelGate;
37544
+ this.signal = opts.signal;
35984
37545
  this.runTentacleFn = opts.runTentacleFn ?? runTentacle;
35985
37546
  this.mergeFn = opts.mergeFn ?? mergeKrakenWorktree;
35986
37547
  this.backtestFn = opts.backtestFn ?? runBacktest;
35987
37548
  }
35988
37549
  /** Execute the graph in place (mutates node statuses) until it settles. */
35989
37550
  async execute(graph) {
37551
+ if (this.fixBudgetOption === void 0) {
37552
+ this.fixBudgetRemaining = resolveFixBudget(process.env, graph.nodes.size);
37553
+ }
37554
+ const onAbort = () => this.cancelRun();
37555
+ if (this.signal) {
37556
+ if (this.signal.aborted) this.aborted = true;
37557
+ else this.signal.addEventListener("abort", onAbort, { once: true });
37558
+ }
37559
+ let graphTimer;
37560
+ if (this.graphTimeoutMs > 0) {
37561
+ graphTimer = setTimeout(() => {
37562
+ this.radio("graph_failed", {
37563
+ description: "graph executor",
37564
+ detail: `graph exceeded its ${this.graphTimeoutMs}ms wall-clock budget \u2014 cancelling`,
37565
+ ok: false
37566
+ });
37567
+ this.cancelRun();
37568
+ }, this.graphTimeoutMs);
37569
+ graphTimer.unref?.();
37570
+ }
37571
+ try {
37572
+ return await this.schedule(graph);
37573
+ } finally {
37574
+ if (graphTimer) clearTimeout(graphTimer);
37575
+ this.signal?.removeEventListener("abort", onAbort);
37576
+ }
37577
+ }
37578
+ /** The scheduling loop proper. See {@link execute} for the cancellation wrapper. */
37579
+ async schedule(graph) {
35990
37580
  const maxIterations = Math.max(64, graph.nodes.size * 8);
35991
37581
  let iterations = 0;
37582
+ const inFlight = /* @__PURE__ */ new Map();
35992
37583
  startKrakenGraphLive(graph);
35993
37584
  while (!isSettled(graph)) {
35994
37585
  iterations += 1;
@@ -36000,24 +37591,39 @@ var init_executor = __esm({
36000
37591
  });
36001
37592
  break;
36002
37593
  }
36003
- const ready = getReadyNodes(graph);
36004
- if (ready.length === 0) {
36005
- const skippedAny = this.skipBlockedNodes(graph);
36006
- if (!skippedAny) {
37594
+ if (this.aborted && inFlight.size === 0) break;
37595
+ const admitted = this.aborted ? [] : this.admit(graph, inFlight);
37596
+ if (admitted.length > 0) {
37597
+ for (const node of admitted) node.status = "running";
37598
+ updateKrakenGraphLive(graph);
37599
+ for (const node of admitted) inFlight.set(node.id, this.runNodeSafely(node, graph));
37600
+ }
37601
+ if (inFlight.size === 0) {
37602
+ if (!this.skipBlockedNodes(graph)) {
36007
37603
  break;
36008
37604
  }
36009
37605
  continue;
36010
37606
  }
36011
- const wave = selectParallelWave(ready).slice(0, this.maxParallel);
36012
- for (const node of wave) node.status = "running";
36013
- const results = await Promise.all(wave.map((node) => this.runNode(node, graph)));
36014
- for (let i = 0; i < wave.length; i++) {
36015
- this.applyResult(graph, wave[i], results[i]);
36016
- }
37607
+ const { id, res } = await Promise.race(inFlight.values());
37608
+ inFlight.delete(id);
37609
+ const settledNode = graph.nodes.get(id);
37610
+ if (settledNode) this.applyResult(graph, settledNode, res);
36017
37611
  updateKrakenGraphLive(graph);
36018
37612
  }
37613
+ if (inFlight.size > 0) {
37614
+ for (const { id, res } of await Promise.all(inFlight.values())) {
37615
+ const node = graph.nodes.get(id);
37616
+ if (node) this.applyResult(graph, node, res);
37617
+ }
37618
+ inFlight.clear();
37619
+ }
37620
+ if (this.aborted) {
37621
+ for (const n of graph.nodes.values()) {
37622
+ if (n.status === "pending") n.status = "skipped";
37623
+ }
37624
+ }
36019
37625
  let backtest;
36020
- const converged = isConverged(graph);
37626
+ const converged = !this.aborted && isConverged(graph);
36021
37627
  if (converged) {
36022
37628
  const gateOn = this.worldModelGateOverride ?? isWorldModelGateEnabled(this.parentCwd);
36023
37629
  if (gateOn) {
@@ -36031,47 +37637,133 @@ var init_executor = __esm({
36031
37637
  } else {
36032
37638
  this.radio("graph_failed", {
36033
37639
  description: "graph executor",
36034
- detail: `failed nodes: ${failedNodeIds(graph).join(", ") || "none"}`,
37640
+ detail: this.aborted ? "cancelled by caller" : `failed nodes: ${failedNodeIds(graph).join(", ") || "none"}`,
36035
37641
  ok: false
36036
37642
  });
36037
37643
  }
36038
37644
  endKrakenGraphLive(graph, converged);
36039
37645
  await saveGraphSnapshot(
36040
37646
  this.parentCwd,
36041
- toGraphSnapshot(graph, { goal: this.goal ?? graph.id, converged })
37647
+ toGraphSnapshot(graph, {
37648
+ goal: this.goal ?? graph.id,
37649
+ converged,
37650
+ unresolvedFindings: this.unresolved
37651
+ })
36042
37652
  );
36043
37653
  return {
36044
37654
  graph,
36045
37655
  converged,
36046
37656
  failedNodeIds: failedNodeIds(graph),
36047
37657
  counts: countByStatus(graph),
37658
+ durationsMs: Object.fromEntries(this.durationsMs),
37659
+ cancelled: this.aborted,
37660
+ unresolvedFindings: [...this.unresolved],
36048
37661
  ...backtest ? { backtest } : {}
36049
37662
  };
36050
37663
  }
37664
+ /**
37665
+ * Stop the run: no further admissions, and every tentacle currently running
37666
+ * is told to unwind. Each node's own timeout/grace machinery then resolves
37667
+ * it, so `execute()` settles instead of leaving orphans behind.
37668
+ */
37669
+ cancelRun() {
37670
+ if (this.aborted) return;
37671
+ this.aborted = true;
37672
+ this.radio("graph_failed", {
37673
+ description: "graph executor",
37674
+ detail: `cancelling ${this.nodeControllers.size} running tentacle(s)`,
37675
+ ok: false
37676
+ });
37677
+ for (const controller of this.nodeControllers.values()) controller.abort();
37678
+ }
37679
+ /**
37680
+ * Pick the ready nodes that may start right now: parallel-safe against every
37681
+ * node already running AND against each other, within the concurrency cap.
37682
+ *
37683
+ * Unlike a wave-at-a-time scheduler this is called on every completion, so a
37684
+ * node becomes eligible the moment its blocker settles instead of waiting
37685
+ * for the slowest member of some earlier batch.
37686
+ */
37687
+ admit(graph, inFlight) {
37688
+ const capacity = this.maxParallel - inFlight.size;
37689
+ if (capacity <= 0) return [];
37690
+ const running = [];
37691
+ for (const id of inFlight.keys()) {
37692
+ const n = graph.nodes.get(id);
37693
+ if (n) running.push(n);
37694
+ }
37695
+ const admitted = [];
37696
+ for (const node of getReadyNodes(graph)) {
37697
+ if (admitted.length >= capacity) break;
37698
+ const safe = running.every((r) => canRunParallel(r, node)) && admitted.every((a) => canRunParallel(a, node));
37699
+ if (safe) admitted.push(node);
37700
+ }
37701
+ return admitted;
37702
+ }
37703
+ /**
37704
+ * Run one node, tagging the result with its id and converting an unexpected
37705
+ * throw into a node failure. The scheduler races these promises, so a
37706
+ * rejection would abandon every other in-flight tentacle mid-write; one
37707
+ * failed node that the retry/fix machinery can reason about is strictly
37708
+ * better than an aborted graph.
37709
+ */
37710
+ runNodeSafely(node, graph) {
37711
+ return this.runNode(node, graph).then(
37712
+ (res) => ({ id: node.id, res }),
37713
+ (err) => ({
37714
+ id: node.id,
37715
+ res: {
37716
+ ok: false,
37717
+ agent: agentForNode(node),
37718
+ error: `tentacle threw: ${err instanceof Error ? err.message : String(err)}`,
37719
+ cancelled: true
37720
+ }
37721
+ })
37722
+ );
37723
+ }
36051
37724
  /** Run one node: dispatch to the merge handler for `merge` nodes, else a tentacle. */
36052
37725
  async runNode(node, graph) {
37726
+ const startedAt = Date.now();
37727
+ try {
37728
+ return await this.runNodeInner(node, graph);
37729
+ } finally {
37730
+ this.durationsMs.set(node.id, Date.now() - startedAt);
37731
+ this.nodeControllers.delete(node.id);
37732
+ }
37733
+ }
37734
+ async runNodeInner(node, graph) {
36053
37735
  this.radio("node_start", { description: node.label, agent: node.kind });
36054
37736
  if (node.kind === "merge") {
36055
37737
  return this.runMergeNode(node, graph);
36056
37738
  }
36057
- const usesWorktree = node.kind === "general" || node.kind === "fix";
36058
- const agent = node.kind === "fix" ? "general" : node.kind;
37739
+ const isRework = this.reworks.has(node.id);
37740
+ const usesWorktree = (node.kind === "general" || node.kind === "fix") && !isRework;
37741
+ const agent = node.kind === "fix" ? "general" : node.kind === "spec" || node.kind === "conformance" ? "verify" : node.kind;
36059
37742
  const controller = new AbortController();
37743
+ this.nodeControllers.set(node.id, controller);
37744
+ if (this.aborted) controller.abort();
37745
+ const upstream = buildUpstreamContext(graph, node);
37746
+ const inheritedCwd = node.kind === "verify" || isRework ? this.inheritedWorktreeCwdFor(node, graph) : void 0;
36060
37747
  const res = await this.withNodeTimeout(
36061
37748
  this.runTentacleFn({
36062
- deps: this.deps,
37749
+ // `allowWorktree: false` is what actually stops a rework from opening
37750
+ // its own worktree: creation is driven by the agent kind ('general')
37751
+ // inside runTentacle, not by anything the executor passes per-call.
37752
+ deps: isRework ? { ...this.deps, allowWorktree: false } : this.deps,
36063
37753
  args: {
36064
37754
  description: node.label,
36065
- prompt: node.prompt,
37755
+ prompt: upstream ? `${node.prompt}
37756
+ ${upstream}` : node.prompt,
36066
37757
  scope: node.scope,
36067
37758
  acceptance: node.acceptance
36068
37759
  },
36069
37760
  agent,
36070
- thoroughness: "medium",
37761
+ thoroughness: thoroughnessForKind(node.kind),
36071
37762
  parentCwd: this.parentCwd,
37763
+ ...inheritedCwd ? { cwdOverride: inheritedCwd } : {},
36072
37764
  sessionId: this.sessionId,
36073
37765
  // Defer merge for writers so the executor controls merge ordering
36074
- // (Correction 4); explore/verify never create a worktree.
37766
+ // (Correction 4); explore/verify/rework never create a worktree.
36075
37767
  deferMerge: usesWorktree,
36076
37768
  graphId: graph.id,
36077
37769
  nodeId: node.id,
@@ -36132,20 +37824,71 @@ var init_executor = __esm({
36132
37824
  };
36133
37825
  }
36134
37826
  /**
36135
- * Sequentially merge every dep node's deferred worktree (in dep order) into
36136
- * parent HEAD. Deps without a recorded worktree handle (worktree isolation
36137
- * disabled, or a read-only node) are a no-op. On conflict the branch is
36138
- * kept and the conflict is surfaced in the merge node's error remaining
36139
- * deps still attempt to merge (independent branches shouldn't be blocked
36140
- * by one conflict).
37827
+ * The worktree a node should run in, inherited from the writer behind it.
37828
+ *
37829
+ * For a `verify`: verification happens BEFORE the merge node, so when its
37830
+ * writer worked in an isolated worktree the changes are not in the parent
37831
+ * tree yet a verify tentacle pointed at `parentCwd` was inspecting a tree
37832
+ * that provably did not contain the work it was asked to check, and reported
37833
+ * it missing.
37834
+ *
37835
+ * For a rework: the same tree, for the stronger reason that writing anywhere
37836
+ * else would strand the round on a second branch.
37837
+ *
37838
+ * Returns undefined when there is no single tree: no worktrees (isolation
37839
+ * disabled — the writers edited the parent tree directly), or several
37840
+ * distinct ones, in which case no single cwd is correct and the parent tree
37841
+ * is the honest default.
37842
+ */
37843
+ inheritedWorktreeCwdFor(node, graph) {
37844
+ const paths = new Set(
37845
+ this.collectWorktreeSources(node, graph).map((s) => s.handle.path)
37846
+ );
37847
+ return paths.size === 1 ? [...paths][0] : void 0;
37848
+ }
37849
+ /**
37850
+ * Resolve the deferred worktrees produced behind a node's dependencies, in
37851
+ * ancestors-first order. Used to decide what a `merge` node must merge, and
37852
+ * which tree a `verify` node should actually inspect.
37853
+ *
37854
+ * A merge node's direct deps are NOT the writers: `buildGraphFromPlan`
37855
+ * injects a `verify` node after every `general` node and points the merge at
37856
+ * those verifies, while worktree handles are recorded against the writer
37857
+ * node ids. Looking only at direct deps therefore found nothing to merge and
37858
+ * silently reported success while every tentacle's work stayed stranded on
37859
+ * its branch. Walk up through non-writer deps until the writers are found.
37860
+ *
37861
+ * Post-order so a writer that depends on another writer merges after it (the
37862
+ * later branch was cut from a HEAD that already contained the earlier work).
37863
+ * `merge` nodes terminate the walk: another merge already owns its subtree.
37864
+ */
37865
+ collectWorktreeSources(node, graph) {
37866
+ const out = [];
37867
+ const seen = /* @__PURE__ */ new Set();
37868
+ const visit = (id) => {
37869
+ if (seen.has(id)) return;
37870
+ seen.add(id);
37871
+ const n = graph.nodes.get(id);
37872
+ if (!n || n.kind === "merge") return;
37873
+ for (const dep of n.deps) visit(dep);
37874
+ const handle = this.nodeRunState.get(id)?.worktreeHandle;
37875
+ if (handle) out.push({ id, handle });
37876
+ };
37877
+ for (const depId of node.deps) visit(depId);
37878
+ return out;
37879
+ }
37880
+ /**
37881
+ * Sequentially merge every deferred worktree this node covers (in
37882
+ * ancestors-first order) into parent HEAD. Nodes without a recorded worktree
37883
+ * handle (worktree isolation disabled, or a read-only node) are a no-op. On
37884
+ * conflict the branch is kept and the conflict is surfaced in the merge
37885
+ * node's error — remaining sources still attempt to merge (independent
37886
+ * branches shouldn't be blocked by one conflict).
36141
37887
  */
36142
37888
  async runMergeNode(node, graph) {
36143
37889
  const conflicts = [];
36144
37890
  const merged = [];
36145
- for (const depId of node.deps) {
36146
- const state3 = this.nodeRunState.get(depId);
36147
- const handle = state3?.worktreeHandle;
36148
- if (!handle) continue;
37891
+ for (const { id: depId, handle } of this.collectWorktreeSources(node, graph)) {
36149
37892
  let result;
36150
37893
  try {
36151
37894
  result = await this.mergeFn(handle, {
@@ -36163,6 +37906,7 @@ var init_executor = __esm({
36163
37906
  if (!result.ok) {
36164
37907
  conflicts.push(`${depId}: ${result.message}`);
36165
37908
  } else {
37909
+ this.nodeRunState.set(depId, { worktreeHandle: null });
36166
37910
  merged.push(depId);
36167
37911
  }
36168
37912
  }
@@ -36190,9 +37934,21 @@ var init_executor = __esm({
36190
37934
  node.status = "done";
36191
37935
  node.result = res.result;
36192
37936
  this.radio("node_end", { description: node.label, agent: node.kind, ok: true });
37937
+ this.reconcileRepairedNode(graph, node);
37938
+ if (node.kind === "verify") this.applyVerifyVerdict(graph, node);
36193
37939
  return;
36194
37940
  }
36195
37941
  node.error = res.error;
37942
+ if (this.aborted) {
37943
+ node.status = "error";
37944
+ this.radio("node_end", {
37945
+ description: node.label,
37946
+ agent: node.kind,
37947
+ detail: res.error,
37948
+ ok: false
37949
+ });
37950
+ return;
37951
+ }
36196
37952
  if (res.cancelled === false) {
36197
37953
  node.status = "error";
36198
37954
  this.radio("node_end", {
@@ -36235,6 +37991,181 @@ var init_executor = __esm({
36235
37991
  ok: false
36236
37992
  });
36237
37993
  }
37994
+ /**
37995
+ * A `fix` node just completed the work its failed predecessor could not.
37996
+ * That unit of work IS done — but the predecessor was left terminally
37997
+ * `error`, and since `isConverged` requires every node to be `done`/
37998
+ * `skipped`, a fully repaired graph reported "did not converge" and listed
37999
+ * the repaired node under `failedNodeIds`. The cross-run snapshot then told
38000
+ * the next planner to redo work the fix had already completed.
38001
+ *
38002
+ * Marking it `done` has no scheduling effect (dependents were re-pointed at
38003
+ * the fix when it was spawned) — it is purely how the run is reported. The
38004
+ * original failure stays visible as the separate `fix: …` node and in the
38005
+ * repaired node's result line.
38006
+ */
38007
+ reconcileRepairedNode(graph, fixNode) {
38008
+ const failedId = this.repairs.get(fixNode.id);
38009
+ if (!failedId) return;
38010
+ const failed = graph.nodes.get(failedId);
38011
+ if (!failed || failed.status !== "error") return;
38012
+ const original = failed.error ? ` (original failure: ${failed.error})` : "";
38013
+ failed.status = "done";
38014
+ failed.result = `repaired by "${fixNode.label}"${original}${fixNode.result ? `: ${fixNode.result}` : ""}`;
38015
+ failed.error = void 0;
38016
+ this.radio("node_end", {
38017
+ description: failed.label,
38018
+ agent: failed.kind,
38019
+ detail: `repaired by ${fixNode.id}`,
38020
+ ok: true
38021
+ });
38022
+ }
38023
+ /**
38024
+ * Act on what a completed `verify` node concluded.
38025
+ *
38026
+ * The verify itself stays `done` either way — it did its job, and doing it
38027
+ * well means being free to say "no". A FAIL instead sends the WRITER back
38028
+ * through a bounded rework round.
38029
+ *
38030
+ * Without this the verdict text was never read: a verify that reported the
38031
+ * work as wrong was recorded exactly like one that reported it correct, the
38032
+ * graph converged over the defect, and the only iteration the engine could
38033
+ * do was on execution failure. An `unknown` verdict (no parseable trailer)
38034
+ * is deliberately non-blocking — a prompt drift must not be able to wedge
38035
+ * every graph — but it is recorded, because a gate that has silently stopped
38036
+ * working is worse than no gate.
38037
+ */
38038
+ applyVerifyVerdict(graph, verify) {
38039
+ const { verdict, findings } = parseVerifyVerdict(verify.result);
38040
+ if (verdict === "pass") return;
38041
+ const writer = this.writerBehind(verify, graph);
38042
+ if (!writer) return;
38043
+ void this.maybeRunWeaknessMeter(verify, verdict);
38044
+ if (verdict === "unknown") {
38045
+ this.unresolved.push({
38046
+ nodeId: writer.id,
38047
+ label: writer.label,
38048
+ reason: "unknown",
38049
+ findings: findings || "(verify produced no parseable VERDICT line)"
38050
+ });
38051
+ return;
38052
+ }
38053
+ const root = this.reviewLineage.get(writer.id) ?? writer.id;
38054
+ const spent = this.reviewRounds.get(root) ?? 0;
38055
+ if (this.aborted || spent >= this.maxReviewRounds) {
38056
+ this.unresolved.push({
38057
+ nodeId: writer.id,
38058
+ label: writer.label,
38059
+ reason: "fail",
38060
+ findings
38061
+ });
38062
+ writer.result = `${writer.result ?? ""}
38063
+
38064
+ [accepted with unresolved verify findings from ` + `"${verify.label}"]${findings ? `: ${firstLine(findings)}` : ""}`.trim();
38065
+ this.radio("node_end", {
38066
+ description: writer.label,
38067
+ agent: writer.kind,
38068
+ detail: this.aborted ? "verify FAIL left unresolved (run cancelled)" : `verify FAIL left unresolved (rework budget ${this.maxReviewRounds} spent)`,
38069
+ ok: false
38070
+ });
38071
+ return;
38072
+ }
38073
+ this.reviewRounds.set(root, spent + 1);
38074
+ this.spawnReworkPair(graph, writer, verify, findings, root, spent + 1);
38075
+ }
38076
+ /**
38077
+ * The writer whose work a `verify` node judged.
38078
+ *
38079
+ * Walks up through non-writer deps, the same shape `collectWorktreeSources`
38080
+ * relies on: a verify's dep is normally its writer directly, but after a
38081
+ * rework round the chain is writer → verify → rework → verify, and the
38082
+ * rework (a `fix` node) is itself the writer to send back.
38083
+ */
38084
+ writerBehind(verify, graph) {
38085
+ const seen = /* @__PURE__ */ new Set();
38086
+ const visit = (id) => {
38087
+ if (seen.has(id)) return void 0;
38088
+ seen.add(id);
38089
+ const n = graph.nodes.get(id);
38090
+ if (!n || n.kind === "merge") return void 0;
38091
+ if (n.kind === "general" || n.kind === "fix") return n;
38092
+ for (const dep of n.deps) {
38093
+ const found = visit(dep);
38094
+ if (found) return found;
38095
+ }
38096
+ return void 0;
38097
+ };
38098
+ for (const depId of verify.deps) {
38099
+ const found = visit(depId);
38100
+ if (found) return found;
38101
+ }
38102
+ return void 0;
38103
+ }
38104
+ /**
38105
+ * Send a writer's work back for one more round: a rework node carrying the
38106
+ * verify's findings, plus a fresh verify to judge the result.
38107
+ *
38108
+ * The rework runs INSIDE the writer's worktree instead of creating one of
38109
+ * its own. Two worktrees for one scope means two branches, and the merge
38110
+ * node walks up to the writer — so a rework on its own branch would be
38111
+ * merged never or twice, exactly the stranded-work failure the merge fix
38112
+ * addressed. `allowWorktree: false` on this node's deps suppresses creation,
38113
+ * and `cwdOverride` (resolved via {@link inheritedWorktreeCwdFor}) points it
38114
+ * at the existing tree; the handle stays registered against the writer.
38115
+ *
38116
+ * Acyclicity is preserved by construction: both new nodes point only at
38117
+ * nodes that already exist, and the rewiring moves an existing edge forward
38118
+ * along the chain rather than back into it.
38119
+ */
38120
+ spawnReworkPair(graph, writer, verify, findings, root, round) {
38121
+ const reworkId = `rework-${root}-${round}`;
38122
+ const reworkNode = {
38123
+ id: reworkId,
38124
+ kind: "fix",
38125
+ label: `rework: ${writer.label}`,
38126
+ prompt: `A reviewer inspected this work on disk and REJECTED it. Address every finding below, then leave the work in a state that satisfies the original task.
38127
+
38128
+ ## Original task
38129
+ ${writer.prompt}
38130
+
38131
+ ## Reviewer findings (these are what must change)
38132
+ ${findings || "(the reviewer reported FAIL without detail)"}`,
38133
+ ...writer.scope ? { scope: writer.scope } : {},
38134
+ ...writer.acceptance ? { acceptance: writer.acceptance } : {},
38135
+ // The verify is already `done`, so the rework is immediately ready.
38136
+ deps: [verify.id],
38137
+ status: "pending",
38138
+ retryCount: 0,
38139
+ maxRetries: 0
38140
+ };
38141
+ graph.nodes.set(reworkId, reworkNode);
38142
+ this.reworks.set(reworkId, writer.id);
38143
+ this.reviewLineage.set(reworkId, root);
38144
+ const reVerifyId = `verify-${reworkId}`;
38145
+ const reVerifyNode = {
38146
+ id: reVerifyId,
38147
+ kind: "verify",
38148
+ label: `verify: ${writer.label} (rework ${round})`,
38149
+ prompt: verify.prompt,
38150
+ deps: [reworkId],
38151
+ status: "pending",
38152
+ retryCount: 0,
38153
+ maxRetries: verify.maxRetries
38154
+ };
38155
+ graph.nodes.set(reVerifyId, reVerifyNode);
38156
+ for (const other of graph.nodes.values()) {
38157
+ if (other.id === reworkId || other.id === reVerifyId) continue;
38158
+ if (other.deps.includes(verify.id)) {
38159
+ other.deps = other.deps.map((d) => d === verify.id ? reVerifyId : d);
38160
+ }
38161
+ }
38162
+ this.radio("node_fix", {
38163
+ description: reworkNode.label,
38164
+ agent: "fix",
38165
+ detail: `verify FAIL on "${writer.label}" \u2014 rework round ${round}/${this.maxReviewRounds}`,
38166
+ ok: false
38167
+ });
38168
+ }
36238
38169
  /**
36239
38170
  * Create a `fix` node that attempts to redo the failed node's work, wired
36240
38171
  * so downstream dependents of the failed node also wait on the fix.
@@ -36261,6 +38192,7 @@ ${failed.error ?? "unknown error"}`,
36261
38192
  // no further retries — one fix attempt per failed node in v1
36262
38193
  };
36263
38194
  graph.nodes.set(fixId, fixNode);
38195
+ this.repairs.set(fixId, failed.id);
36264
38196
  for (const other of graph.nodes.values()) {
36265
38197
  if (other.id === fixId) continue;
36266
38198
  if (other.deps.includes(failed.id)) {
@@ -36303,6 +38235,41 @@ ${failed.error ?? "unknown error"}`,
36303
38235
  ...fields.ok !== void 0 ? { ok: fields.ok } : {}
36304
38236
  });
36305
38237
  }
38238
+ /**
38239
+ * Bennett's Razor meter (Slice L/N+3 wiring): when the env flag is
38240
+ * set, fire a non-blocking LLM call to refine the persona verdict's
38241
+ * weakness score. The local heuristic already produced a score in
38242
+ * `parsePersonaVerdict`; the meter just refines it. Results land in
38243
+ * the radio stream as a `node_meter` event so the desktop / tail
38244
+ * can surface the distinction between a "tightly asserted" PASS
38245
+ * (specificity > 0.6) and a "loosely claimed" one (specificity < 0.3).
38246
+ *
38247
+ * No-op when:
38248
+ * - the meter is disabled (default)
38249
+ * - the result text is empty
38250
+ * - the meter call fails (silent: the local score is good enough)
38251
+ *
38252
+ * @since v1.31.x
38253
+ */
38254
+ async maybeRunWeaknessMeter(verify, verdict) {
38255
+ const text = typeof verify.result === "string" ? verify.result : "";
38256
+ if (text.length === 0) return;
38257
+ let meter;
38258
+ try {
38259
+ ({ measureWeaknessViaLLM: meter } = await Promise.resolve().then(() => (init_weaknessMeter(), weaknessMeter_exports)));
38260
+ } catch {
38261
+ return;
38262
+ }
38263
+ if (!meter) return;
38264
+ const outcome = await meter(text);
38265
+ if (!outcome) return;
38266
+ this.radio("node_meter", {
38267
+ description: `meter: ${verify.id}`,
38268
+ agent: "weakness-meter",
38269
+ detail: `v=${verdict} specificity=${outcome.meter.specificity.toFixed(2)} weakness=${outcome.weakness.toFixed(2)} model=${outcome.model} dur=${outcome.durationMs}ms assumptions=${outcome.meter.assumptions.length}`,
38270
+ ok: true
38271
+ });
38272
+ }
36306
38273
  };
36307
38274
  }
36308
38275
  });
@@ -36691,10 +38658,10 @@ var init_prereqChecks = __esm({
36691
38658
 
36692
38659
  // src/cli/plugins/prefs.ts
36693
38660
  import { existsSync as existsSync36, readFileSync as readFileSync29, writeFileSync as writeFileSync18, mkdirSync as mkdirSync15 } from "node:fs";
36694
- import path36 from "node:path";
38661
+ import path38 from "node:path";
36695
38662
  import os9 from "node:os";
36696
38663
  function getPluginPrefsPath() {
36697
- return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path36.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
38664
+ return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path38.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
36698
38665
  }
36699
38666
  function getPluginPrefs() {
36700
38667
  const file2 = getPluginPrefsPath();
@@ -36715,7 +38682,7 @@ function getPluginPrefs() {
36715
38682
  }
36716
38683
  function writePluginPrefs(prefs) {
36717
38684
  const file2 = getPluginPrefsPath();
36718
- mkdirSync15(path36.dirname(file2), { recursive: true });
38685
+ mkdirSync15(path38.dirname(file2), { recursive: true });
36719
38686
  writeFileSync18(file2, JSON.stringify(prefs, null, 2), {
36720
38687
  encoding: "utf-8",
36721
38688
  mode: 384
@@ -36752,7 +38719,7 @@ __export(registry_exports, {
36752
38719
  isBinaryOnPath: () => isBinaryOnPath
36753
38720
  });
36754
38721
  import { existsSync as existsSync37 } from "node:fs";
36755
- import path37 from "node:path";
38722
+ import path39 from "node:path";
36756
38723
  function detectLocalBin(bin) {
36757
38724
  return (cwd) => {
36758
38725
  try {
@@ -36770,7 +38737,7 @@ function isBinaryOnPath(bin, opts = {}) {
36770
38737
  const platform = opts.platform ?? process.platform;
36771
38738
  const exists = opts.exists ?? existsSync37;
36772
38739
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
36773
- const pathMod = platform === "win32" ? path37.win32 : path37.posix;
38740
+ const pathMod = platform === "win32" ? path39.win32 : path39.posix;
36774
38741
  const sep2 = platform === "win32" ? ";" : ":";
36775
38742
  const dirs = pathEnv.split(sep2).filter((d) => d.length > 0);
36776
38743
  const candidates = [bin];
@@ -36839,7 +38806,7 @@ function findPlugin(id) {
36839
38806
  return PLUGINS.find((p3) => p3.id === id);
36840
38807
  }
36841
38808
  var PLUGINS;
36842
- var init_registry2 = __esm({
38809
+ var init_registry3 = __esm({
36843
38810
  "src/cli/plugins/registry.ts"() {
36844
38811
  "use strict";
36845
38812
  init_engine();
@@ -36908,7 +38875,7 @@ __export(atMentions_exports, {
36908
38875
  hasAtMentions: () => hasAtMentions
36909
38876
  });
36910
38877
  import { existsSync as existsSync40, readFileSync as readFileSync31, statSync as statSync6 } from "node:fs";
36911
- import { isAbsolute, relative as relative3, resolve, sep } from "node:path";
38878
+ import { isAbsolute as isAbsolute2, relative as relative3, resolve, sep } from "node:path";
36912
38879
  function isProbablyText(name, head) {
36913
38880
  if (/\.(txt|md|markdown|json|jsonc|ts|tsx|js|jsx|mjs|cjs|css|scss|html|htm|xml|yml|yaml|toml|ini|cfg|conf|rs|go|py|java|kt|swift|c|cc|cpp|h|hpp|cs|rb|php|sh|bash|zsh|ps1|sql|graphql|env|gitignore|dockerfile|makefile|cmake|lock|svg)$/i.test(
36914
38881
  name
@@ -36939,8 +38906,8 @@ function extractAtMentions(text) {
36939
38906
  return out;
36940
38907
  }
36941
38908
  function resolveMention(token, cwd) {
36942
- const abs = isAbsolute(token) ? resolve(token) : resolve(cwd, token);
36943
- if (!underRoot(abs, cwd) && !isAbsolute(token)) {
38909
+ const abs = isAbsolute2(token) ? resolve(token) : resolve(cwd, token);
38910
+ if (!underRoot(abs, cwd) && !isAbsolute2(token)) {
36944
38911
  if (!underRoot(abs, cwd)) {
36945
38912
  return {
36946
38913
  raw: token,
@@ -36951,7 +38918,7 @@ function resolveMention(token, cwd) {
36951
38918
  };
36952
38919
  }
36953
38920
  }
36954
- if (isAbsolute(token) && !underRoot(abs, cwd)) {
38921
+ if (isAbsolute2(token) && !underRoot(abs, cwd)) {
36955
38922
  return {
36956
38923
  raw: token,
36957
38924
  path: token,
@@ -37079,10 +39046,10 @@ __export(triggerLock_exports, {
37079
39046
  lockPath: () => lockPath,
37080
39047
  releaseLock: () => releaseLock
37081
39048
  });
37082
- import { promises as fs24 } from "node:fs";
37083
- import * as path42 from "node:path";
39049
+ import { promises as fs27 } from "node:fs";
39050
+ import * as path44 from "node:path";
37084
39051
  function lockPath(projectRoot) {
37085
- return path42.join(projectRoot, ".zelari", "trigger.lock");
39052
+ return path44.join(projectRoot, ".zelari", "trigger.lock");
37086
39053
  }
37087
39054
  function isPidAlive(pid) {
37088
39055
  try {
@@ -37095,10 +39062,10 @@ function isPidAlive(pid) {
37095
39062
  }
37096
39063
  async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
37097
39064
  const lp = lockPath(projectRoot);
37098
- const dir = path42.dirname(lp);
37099
- await fs24.mkdir(dir, { recursive: true });
39065
+ const dir = path44.dirname(lp);
39066
+ await fs27.mkdir(dir, { recursive: true });
37100
39067
  try {
37101
- const raw = await fs24.readFile(lp, "utf8");
39068
+ const raw = await fs27.readFile(lp, "utf8");
37102
39069
  const existing = JSON.parse(raw);
37103
39070
  if (existing.pid && isPidAlive(existing.pid)) {
37104
39071
  return { acquired: false, heldBy: existing.pid, lockPath: lp };
@@ -37109,13 +39076,13 @@ async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date())
37109
39076
  pid: process.pid,
37110
39077
  acquiredAt: now().toISOString()
37111
39078
  };
37112
- await fs24.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
39079
+ await fs27.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
37113
39080
  return { acquired: true, lockPath: lp };
37114
39081
  }
37115
39082
  async function releaseLock(projectRoot) {
37116
39083
  const lp = lockPath(projectRoot);
37117
39084
  try {
37118
- await fs24.unlink(lp);
39085
+ await fs27.unlink(lp);
37119
39086
  } catch {
37120
39087
  }
37121
39088
  }
@@ -37386,7 +39353,7 @@ import {
37386
39353
  } from "node:fs";
37387
39354
  import { join as join35 } from "node:path";
37388
39355
  import { homedir as homedir11 } from "node:os";
37389
- import { createHash as createHash6, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
39356
+ import { createHash as createHash6, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
37390
39357
  function getZelariHome() {
37391
39358
  return join35(homedir11(), ".zelari-code");
37392
39359
  }
@@ -37403,12 +39370,12 @@ function ensureHome() {
37403
39370
  }
37404
39371
  }
37405
39372
  function loadCompanionConfig() {
37406
- const path44 = getCompanionConfigPath();
37407
- if (!existsSync43(path44)) {
39373
+ const path47 = getCompanionConfigPath();
39374
+ if (!existsSync43(path47)) {
37408
39375
  return { projects: [] };
37409
39376
  }
37410
39377
  try {
37411
- const raw = JSON.parse(readFileSync34(path44, "utf8"));
39378
+ const raw = JSON.parse(readFileSync34(path47, "utf8"));
37412
39379
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
37413
39380
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
37414
39381
  ).map((p3) => ({
@@ -37446,16 +39413,16 @@ function loadOrCreateToken(explicit) {
37446
39413
  return { token: explicit.trim(), created: false };
37447
39414
  }
37448
39415
  ensureHome();
37449
- const path44 = getCompanionTokenPath();
37450
- if (existsSync43(path44)) {
37451
- const t = readFileSync34(path44, "utf8").trim();
39416
+ const path47 = getCompanionTokenPath();
39417
+ if (existsSync43(path47)) {
39418
+ const t = readFileSync34(path47, "utf8").trim();
37452
39419
  if (t) return { token: t, created: false };
37453
39420
  }
37454
- const token = randomBytes3(24).toString("base64url");
37455
- writeFileSync21(path44, token + "\n", "utf8");
39421
+ const token = randomBytes4(24).toString("base64url");
39422
+ writeFileSync21(path47, token + "\n", "utf8");
37456
39423
  try {
37457
- const fs25 = __require("node:fs");
37458
- fs25.chmodSync?.(path44, 384);
39424
+ const fs29 = __require("node:fs");
39425
+ fs29.chmodSync?.(path47, 384);
37459
39426
  } catch {
37460
39427
  }
37461
39428
  return { token, created: true };
@@ -37480,17 +39447,17 @@ function mergeProjects(cfg, extraPaths) {
37480
39447
  byId.set(p3.id, p3);
37481
39448
  }
37482
39449
  for (const raw of extraPaths) {
37483
- const path44 = raw.trim();
37484
- if (!path44) continue;
37485
- let id = slugFromPath(path44);
39450
+ const path47 = raw.trim();
39451
+ if (!path47) continue;
39452
+ let id = slugFromPath(path47);
37486
39453
  let n = 2;
37487
- while (byId.has(id) && byId.get(id).path !== path44) {
37488
- id = `${slugFromPath(path44)}-${n++}`;
39454
+ while (byId.has(id) && byId.get(id).path !== path47) {
39455
+ id = `${slugFromPath(path47)}-${n++}`;
37489
39456
  }
37490
39457
  byId.set(id, {
37491
39458
  id,
37492
- name: slugFromPath(path44),
37493
- path: path44
39459
+ name: slugFromPath(path47),
39460
+ path: path47
37494
39461
  });
37495
39462
  }
37496
39463
  return [...byId.values()];
@@ -37540,7 +39507,7 @@ var init_config = __esm({
37540
39507
  // src/cli/companion/runManager.ts
37541
39508
  import { spawn as spawn11 } from "node:child_process";
37542
39509
  import { createInterface } from "node:readline";
37543
- import { randomUUID as randomUUID6 } from "node:crypto";
39510
+ import { randomUUID as randomUUID7 } from "node:crypto";
37544
39511
  import { writeFileSync as writeFileSync22, unlinkSync as unlinkSync2 } from "node:fs";
37545
39512
  import { join as join36 } from "node:path";
37546
39513
  import { tmpdir as tmpdir3 } from "node:os";
@@ -37609,7 +39576,7 @@ var init_runManager = __esm({
37609
39576
  }
37610
39577
  const prompt = args.prompt?.trim();
37611
39578
  if (!prompt) return { ok: false, error: "prompt is required" };
37612
- const id = randomUUID6();
39579
+ const id = randomUUID7();
37613
39580
  const mode = (args.mode || "kraken").toLowerCase();
37614
39581
  const phase2 = (args.phase || "build").toLowerCase();
37615
39582
  const run = {
@@ -37867,9 +39834,9 @@ async function runCompanionServe(opts = {}) {
37867
39834
  return;
37868
39835
  }
37869
39836
  const url2 = parseUrl(req);
37870
- const path44 = url2.pathname.replace(/\/+$/, "") || "/";
39837
+ const path47 = url2.pathname.replace(/\/+$/, "") || "/";
37871
39838
  try {
37872
- if (req.method === "GET" && (path44 === "/health" || path44 === "/v1/health")) {
39839
+ if (req.method === "GET" && (path47 === "/health" || path47 === "/v1/health")) {
37873
39840
  sendJson(res, 200, {
37874
39841
  ok: true,
37875
39842
  service: "zelari-companion",
@@ -37881,18 +39848,18 @@ async function runCompanionServe(opts = {}) {
37881
39848
  });
37882
39849
  return;
37883
39850
  }
37884
- if (path44.startsWith("/v1")) {
39851
+ if (path47.startsWith("/v1")) {
37885
39852
  if (!tokenMatches(token, getBearer(req))) {
37886
39853
  sendJson(res, 401, { ok: false, error: "unauthorized" });
37887
39854
  return;
37888
39855
  }
37889
39856
  }
37890
- if (req.method === "GET" && path44 === "/v1/config") {
39857
+ if (req.method === "GET" && path47 === "/v1/config") {
37891
39858
  const snap = buildDesktopConfigSnapshot();
37892
39859
  sendJson(res, 200, { ok: true, ...snap });
37893
39860
  return;
37894
39861
  }
37895
- if (req.method === "GET" && path44 === "/v1/projects") {
39862
+ if (req.method === "GET" && path47 === "/v1/projects") {
37896
39863
  sendJson(res, 200, {
37897
39864
  ok: true,
37898
39865
  projects: projects.map((p3) => ({
@@ -37903,7 +39870,7 @@ async function runCompanionServe(opts = {}) {
37903
39870
  });
37904
39871
  return;
37905
39872
  }
37906
- if (req.method === "GET" && path44 === "/v1/runs") {
39873
+ if (req.method === "GET" && path47 === "/v1/runs") {
37907
39874
  sendJson(res, 200, {
37908
39875
  ok: true,
37909
39876
  active: runs.getActive(),
@@ -37921,7 +39888,7 @@ async function runCompanionServe(opts = {}) {
37921
39888
  });
37922
39889
  return;
37923
39890
  }
37924
- if (req.method === "POST" && path44 === "/v1/runs") {
39891
+ if (req.method === "POST" && path47 === "/v1/runs") {
37925
39892
  const raw = await readBody(req);
37926
39893
  let body = {};
37927
39894
  try {
@@ -37968,7 +39935,7 @@ async function runCompanionServe(opts = {}) {
37968
39935
  });
37969
39936
  return;
37970
39937
  }
37971
- const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path44);
39938
+ const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path47);
37972
39939
  if (req.method === "GET" && eventsMatch) {
37973
39940
  const runId = eventsMatch[1];
37974
39941
  const run = runs.getRun(runId);
@@ -38033,7 +40000,7 @@ async function runCompanionServe(opts = {}) {
38033
40000
  }, 500);
38034
40001
  return;
38035
40002
  }
38036
- const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path44);
40003
+ const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path47);
38037
40004
  if (req.method === "POST" && cancelMatch) {
38038
40005
  const runId = cancelMatch[1];
38039
40006
  const result = runs.cancel(runId);
@@ -38145,11 +40112,11 @@ import { execSync as execSync2 } from "node:child_process";
38145
40112
  import { existsSync as existsSync45, readFileSync as readFileSync35, readlinkSync, statSync as statSync7 } from "node:fs";
38146
40113
  import { createRequire as createRequire3 } from "node:module";
38147
40114
  import { fileURLToPath as fileURLToPath2 } from "node:url";
38148
- import path43 from "node:path";
40115
+ import path46 from "node:path";
38149
40116
  function findPackageRoot(start) {
38150
40117
  let dir = start;
38151
40118
  for (let i = 0; i < 6; i += 1) {
38152
- const candidate = path43.join(dir, "package.json");
40119
+ const candidate = path46.join(dir, "package.json");
38153
40120
  if (existsSync45(candidate)) {
38154
40121
  try {
38155
40122
  const pkg = JSON.parse(readFileSync35(candidate, "utf8"));
@@ -38157,11 +40124,11 @@ function findPackageRoot(start) {
38157
40124
  } catch {
38158
40125
  }
38159
40126
  }
38160
- const parent = path43.dirname(dir);
40127
+ const parent = path46.dirname(dir);
38161
40128
  if (parent === dir) break;
38162
40129
  dir = parent;
38163
40130
  }
38164
- return path43.resolve(__dirname3, "..", "..", "..");
40131
+ return path46.resolve(__dirname3, "..", "..", "..");
38165
40132
  }
38166
40133
  function tryExec(cmd) {
38167
40134
  try {
@@ -38175,7 +40142,7 @@ function tryExec(cmd) {
38175
40142
  }
38176
40143
  function readPackageJson3() {
38177
40144
  try {
38178
- const pkgPath = path43.join(packageRoot, "package.json");
40145
+ const pkgPath = path46.join(packageRoot, "package.json");
38179
40146
  return JSON.parse(readFileSync35(pkgPath, "utf8"));
38180
40147
  } catch {
38181
40148
  return null;
@@ -38191,7 +40158,7 @@ function checkShim(pkgName) {
38191
40158
  }
38192
40159
  const isWin = process.platform === "win32";
38193
40160
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
38194
- const shimPath = path43.join(prefix, shimName);
40161
+ const shimPath = path46.join(prefix, shimName);
38195
40162
  if (!existsSync45(shimPath)) {
38196
40163
  return FAIL(
38197
40164
  `shim not found at ${shimPath}
@@ -38219,8 +40186,8 @@ function checkShim(pkgName) {
38219
40186
  fix: npm install -g ${pkgName}@latest --force`
38220
40187
  );
38221
40188
  }
38222
- const resolved = path43.resolve(path43.dirname(shimPath), target);
38223
- const expected = path43.join(
40189
+ const resolved = path46.resolve(path46.dirname(shimPath), target);
40190
+ const expected = path46.join(
38224
40191
  prefix,
38225
40192
  "node_modules",
38226
40193
  pkgName,
@@ -38259,7 +40226,7 @@ function checkNode(pkg) {
38259
40226
  return OK(`node ${raw}`);
38260
40227
  }
38261
40228
  function checkBundle() {
38262
- const bundle = path43.join(packageRoot, "dist", "cli", "main.bundled.js");
40229
+ const bundle = path46.join(packageRoot, "dist", "cli", "main.bundled.js");
38263
40230
  if (!existsSync45(bundle)) {
38264
40231
  return FAIL(
38265
40232
  `dist/cli/main.bundled.js missing at ${bundle}
@@ -38280,7 +40247,7 @@ function checkRuntimeDeps() {
38280
40247
  const missing = [];
38281
40248
  for (const dep of required2) {
38282
40249
  try {
38283
- const localReq = createRequire3(path43.join(packageRoot, "package.json"));
40250
+ const localReq = createRequire3(path46.join(packageRoot, "package.json"));
38284
40251
  localReq.resolve(dep);
38285
40252
  } catch {
38286
40253
  missing.push(dep);
@@ -38357,7 +40324,7 @@ function prereqToCheckResult(r) {
38357
40324
  return r.severity === "critical" ? FAIL(r.message, "critical") : WARN(r.message);
38358
40325
  }
38359
40326
  async function checkOptionalPlugins() {
38360
- const { detectMissingPlugins: detectMissingPlugins2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports));
40327
+ const { detectMissingPlugins: detectMissingPlugins2 } = await Promise.resolve().then(() => (init_registry3(), registry_exports));
38361
40328
  let missing;
38362
40329
  try {
38363
40330
  missing = await detectMissingPlugins2(packageRoot, { includeMuted: true });
@@ -38456,7 +40423,7 @@ var init_doctor = __esm({
38456
40423
  "use strict";
38457
40424
  init_prereqChecks();
38458
40425
  require3 = createRequire3(import.meta.url);
38459
- __dirname3 = path43.dirname(fileURLToPath2(import.meta.url));
40426
+ __dirname3 = path46.dirname(fileURLToPath2(import.meta.url));
38460
40427
  packageRoot = findPackageRoot(__dirname3);
38461
40428
  OK = (message) => ({
38462
40429
  ok: true,
@@ -38696,9 +40663,9 @@ function tryParseJson(s) {
38696
40663
  }
38697
40664
  }
38698
40665
  function truncateLines(lines) {
38699
- const cap2 = toolOutputLineCap();
38700
- if (lines.length <= cap2) return lines;
38701
- return [...lines.slice(0, cap2), `\u2026 (+${lines.length - cap2} lines)`];
40666
+ const cap3 = toolOutputLineCap();
40667
+ if (lines.length <= cap3) return lines;
40668
+ return [...lines.slice(0, cap3), `\u2026 (+${lines.length - cap3} lines)`];
38702
40669
  }
38703
40670
  function formatBytes(n) {
38704
40671
  if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
@@ -38886,7 +40853,7 @@ function rel(p3) {
38886
40853
  }
38887
40854
  }
38888
40855
  function formatToolSummary(toolName, args, maxWidth) {
38889
- const cap2 = maxWidth && maxWidth > 10 ? maxWidth : 100;
40856
+ const cap3 = maxWidth && maxWidth > 10 ? maxWidth : 100;
38890
40857
  const lower = toolName.toLowerCase();
38891
40858
  const a = args && typeof args === "object" ? args : {};
38892
40859
  let summary;
@@ -38909,7 +40876,7 @@ function formatToolSummary(toolName, args, maxWidth) {
38909
40876
  const json2 = JSON.stringify(args) ?? "";
38910
40877
  summary = json2;
38911
40878
  }
38912
- return summary.length > cap2 ? `${summary.slice(0, cap2 - 1)}\u2026` : summary;
40879
+ return summary.length > cap3 ? `${summary.slice(0, cap3 - 1)}\u2026` : summary;
38913
40880
  }
38914
40881
 
38915
40882
  // src/cli/components/ToolOutput.tsx
@@ -44321,6 +46288,20 @@ ${formatSkillList(availableSkills)}`
44321
46288
  graphPrompt: args.slice(1).join(" ").trim()
44322
46289
  };
44323
46290
  }
46291
+ if (args[0] === "fanout") {
46292
+ return {
46293
+ handled: true,
46294
+ kind: "kraken_fanout",
46295
+ fanoutArgs: args.slice(1).join(" ").trim()
46296
+ };
46297
+ }
46298
+ if (args[0] === "workbench") {
46299
+ return {
46300
+ handled: true,
46301
+ kind: "kraken_workbench",
46302
+ workbenchArgs: args.slice(1).join(" ").trim()
46303
+ };
46304
+ }
44324
46305
  const sessionArg = args.join(" ").trim();
44325
46306
  return {
44326
46307
  handled: true,
@@ -44904,6 +46885,7 @@ async function handleKrakenGraph(ctx, prompt) {
44904
46885
  const graph = await planTaskGraph({
44905
46886
  prompt,
44906
46887
  graphId: `kraken-${Date.now().toString(36)}`,
46888
+ cwd: ctx.cwd,
44907
46889
  ...previousAttempt ? { previousAttempt } : {}
44908
46890
  });
44909
46891
  appendSystem(ctx.setMessages, formatKrakenGraphAscii(graph));
@@ -44914,11 +46896,17 @@ async function handleKrakenGraph(ctx, prompt) {
44914
46896
  goal: prompt
44915
46897
  });
44916
46898
  const summary = await executor.execute(graph);
46899
+ const digest = formatKrakenGraphDigest(summary.graph, {
46900
+ durationsMs: summary.durationsMs,
46901
+ unresolvedFindings: summary.unresolvedFindings
46902
+ });
44917
46903
  appendSystem(
44918
46904
  ctx.setMessages,
44919
46905
  `${formatKrakenGraphAscii(summary.graph)}
44920
46906
 
44921
- ` + (summary.converged ? "[kraken] graph converged." : `[kraken] graph did not converge \u2014 failed: ${summary.failedNodeIds.join(", ") || "none"}`)
46907
+ ${digest}
46908
+
46909
+ ` + (summary.converged ? "[kraken] graph converged." : summary.cancelled ? "[kraken] graph cancelled." : `[kraken] graph did not converge \u2014 failed: ${summary.failedNodeIds.join(", ") || "none"}`)
44922
46910
  );
44923
46911
  } catch (err) {
44924
46912
  appendSystem(
@@ -44928,6 +46916,508 @@ async function handleKrakenGraph(ctx, prompt) {
44928
46916
  }
44929
46917
  }
44930
46918
 
46919
+ // src/cli/slashHandlers/krakenFanout.ts
46920
+ init_auditLogger();
46921
+ init_toolRegistry();
46922
+ import { promises as fs21 } from "node:fs";
46923
+
46924
+ // src/cli/tools/krakenCsvFanout.ts
46925
+ init_zod();
46926
+ init_taskTool();
46927
+ import { promises as fs20 } from "node:fs";
46928
+ import path36 from "node:path";
46929
+ import { randomBytes as randomBytes3 } from "node:crypto";
46930
+ var CsvFanoutArgsSchema = external_exports.object({
46931
+ csv_path: external_exports.string().min(1),
46932
+ id_column: external_exports.string().min(1),
46933
+ output_csv_path: external_exports.string().min(1),
46934
+ /** Template with `{column_name}` placeholders. The tentacle's prompt is
46935
+ * the template with each row's values substituted. */
46936
+ instruction_template: external_exports.string().min(1),
46937
+ /** Default 'verify' — read-mostly workloads are the common case. */
46938
+ agent_kind: external_exports.enum(["explore", "general", "verify"]).default("verify"),
46939
+ thoroughness: external_exports.enum(["quick", "medium", "deep"]).default("medium"),
46940
+ /** Optional: per-row scope template with `{column}` placeholders. The
46941
+ * resulting path/glob list is fed to the scope-overlap check. */
46942
+ scope_template: external_exports.array(external_exports.string()).optional(),
46943
+ /** Default: ZELARI_KRAKEN_MAX_PARALLEL. */
46944
+ max_concurrency: external_exports.number().int().positive().optional(),
46945
+ /** Per-row timeout (ms). Default: 5 min for verify, 15 min for general. */
46946
+ max_runtime_seconds: external_exports.number().int().positive().optional()
46947
+ });
46948
+ async function readCsv(filePath) {
46949
+ const text = await fs20.readFile(filePath, "utf8");
46950
+ return parseCsv(text);
46951
+ }
46952
+ function parseCsv(text) {
46953
+ const records = [];
46954
+ let row = [];
46955
+ let field = "";
46956
+ let inQuotes = false;
46957
+ for (let i = 0; i < text.length; i++) {
46958
+ const ch = text[i];
46959
+ if (inQuotes) {
46960
+ if (ch === '"') {
46961
+ if (text[i + 1] === '"') {
46962
+ field += '"';
46963
+ i += 1;
46964
+ } else inQuotes = false;
46965
+ } else {
46966
+ field += ch;
46967
+ }
46968
+ } else {
46969
+ if (ch === '"') inQuotes = true;
46970
+ else if (ch === ",") {
46971
+ row.push(field);
46972
+ field = "";
46973
+ } else if (ch === "\n") {
46974
+ row.push(field);
46975
+ records.push(row);
46976
+ row = [];
46977
+ field = "";
46978
+ } else if (ch === "\r") {
46979
+ } else field += ch;
46980
+ }
46981
+ }
46982
+ if (field !== "" || row.length > 0) {
46983
+ row.push(field);
46984
+ records.push(row);
46985
+ }
46986
+ while (records.length > 0 && records[records.length - 1].length === 1 && records[records.length - 1][0] === "") {
46987
+ records.pop();
46988
+ }
46989
+ if (records.length === 0) return { headers: [], rows: [] };
46990
+ const headers = records[0];
46991
+ const rows = records.slice(1).map((r) => {
46992
+ const obj = {};
46993
+ for (let i = 0; i < headers.length; i++) obj[headers[i]] = r[i] ?? "";
46994
+ return obj;
46995
+ });
46996
+ return { headers, rows };
46997
+ }
46998
+ function applyTemplate(template, row) {
46999
+ return template.replace(/\{([a-zA-Z_][\w-]*)\}/g, (_, k) => row[k] ?? "");
47000
+ }
47001
+ function serializeCsv(headers, rows) {
47002
+ const escape = (v) => {
47003
+ if (v.includes(",") || v.includes("\n") || v.includes('"')) {
47004
+ return `"${v.replace(/"/g, '""')}"`;
47005
+ }
47006
+ return v;
47007
+ };
47008
+ const out = [headers.map(escape).join(",")];
47009
+ for (const row of rows) {
47010
+ out.push(headers.map((h) => escape(row[h] ?? "")).join(","));
47011
+ }
47012
+ return out.join("\n") + "\n";
47013
+ }
47014
+ function resolveMaxConcurrency(env = process.env) {
47015
+ const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
47016
+ if (raw === void 0 || raw === "") return 12;
47017
+ const n = Number.parseInt(raw, 10);
47018
+ return Number.isFinite(n) && n > 0 ? n : 12;
47019
+ }
47020
+ async function runCsvFanout(args, deps, opts) {
47021
+ const start = Date.now();
47022
+ const absCsv = path36.isAbsolute(args.csv_path) ? args.csv_path : path36.join(opts.parentCwd, args.csv_path);
47023
+ const absOut = path36.isAbsolute(args.output_csv_path) ? args.output_csv_path : path36.join(opts.parentCwd, args.output_csv_path);
47024
+ const { headers, rows } = await readCsv(absCsv);
47025
+ if (headers.length === 0) {
47026
+ throw new Error(`kraken_csv_fanout: ${absCsv} is empty`);
47027
+ }
47028
+ if (!args.id_column) {
47029
+ throw new Error("kraken_csv_fanout: id_column is required");
47030
+ }
47031
+ if (!headers.includes(args.id_column)) {
47032
+ throw new Error(`kraken_csv_fanout: id_column "${args.id_column}" not in CSV header [${headers.join(", ")}]`);
47033
+ }
47034
+ const concurrency = args.max_concurrency ?? resolveMaxConcurrency();
47035
+ opts.onLog?.(`csv fanout: ${rows.length} rows \xD7 ${args.agent_kind} @ concurrency=${concurrency}`);
47036
+ const perRowMs = args.max_runtime_seconds !== void 0 ? args.max_runtime_seconds * 1e3 : args.agent_kind === "general" ? 9e5 : 3e5;
47037
+ const outputRecords = rows.map((r) => ({ ...r, status: "pending", result: "", error: "" }));
47038
+ const outHeaders = [...headers, "status", "result", "error"];
47039
+ let writeChain2 = Promise.resolve();
47040
+ function queueWrite(contents) {
47041
+ const next = writeChain2.then(() => atomicWrite(absOut, contents));
47042
+ writeChain2 = next.catch(() => {
47043
+ });
47044
+ return next;
47045
+ }
47046
+ let nextIndex = 0;
47047
+ let completed = 0;
47048
+ let errored = 0;
47049
+ const errors = [];
47050
+ async function worker() {
47051
+ while (true) {
47052
+ const i = nextIndex;
47053
+ nextIndex += 1;
47054
+ if (i >= rows.length) return;
47055
+ const row = rows[i];
47056
+ const prompt = applyTemplate(args.instruction_template, row);
47057
+ const scope = args.scope_template?.map((t) => applyTemplate(t, row));
47058
+ const res = await runTentacle({
47059
+ deps,
47060
+ args: {
47061
+ description: `csv-row ${row[args.id_column] ?? i}`,
47062
+ prompt,
47063
+ ...scope ? { scope } : {}
47064
+ },
47065
+ agent: args.agent_kind,
47066
+ thoroughness: args.thoroughness,
47067
+ parentCwd: opts.parentCwd,
47068
+ sessionId: opts.sessionId
47069
+ });
47070
+ outputRecords[i].status = res.ok ? "ok" : "error";
47071
+ if (res.ok) {
47072
+ outputRecords[i].result = res.result;
47073
+ completed += 1;
47074
+ } else {
47075
+ outputRecords[i].error = res.error;
47076
+ errored += 1;
47077
+ errors.push(`${row[args.id_column] ?? i}: ${res.error}`);
47078
+ }
47079
+ await fs20.mkdir(path36.dirname(absOut), { recursive: true });
47080
+ await queueWrite(serializeCsv(outHeaders, outputRecords));
47081
+ }
47082
+ }
47083
+ const pool = Array.from({ length: Math.min(concurrency, rows.length) }, () => worker());
47084
+ await Promise.all(pool);
47085
+ const durationMs = Date.now() - start;
47086
+ opts.onLog?.(`csv fanout: ${completed} ok, ${errored} error, ${durationMs}ms`);
47087
+ if (errored > 0) {
47088
+ opts.onLog?.(`csv fanout: first 3 errors:
47089
+ - ${errors.slice(0, 3).join("\n - ")}`);
47090
+ }
47091
+ return {
47092
+ rows: rows.length,
47093
+ completed,
47094
+ errored,
47095
+ output_csv_path: absOut,
47096
+ durationMs
47097
+ };
47098
+ }
47099
+ async function atomicWrite(file2, contents) {
47100
+ const tmp = `${file2}.${process.pid}.${Date.now()}.${randomBytes3(6).toString("hex")}.tmp`;
47101
+ await fs20.writeFile(tmp, contents, "utf8");
47102
+ await fs20.rename(tmp, file2);
47103
+ }
47104
+
47105
+ // src/cli/slashHandlers/krakenFanout.ts
47106
+ function parseFanoutArgs(argv) {
47107
+ if (argv.length === 0) {
47108
+ return { ok: false, usage: USAGE };
47109
+ }
47110
+ const positional = [];
47111
+ const flags = {};
47112
+ const listFlags = {};
47113
+ for (let i = 0; i < argv.length; i++) {
47114
+ const a = argv[i];
47115
+ if (a.startsWith("--")) {
47116
+ const eq = a.indexOf("=");
47117
+ if (eq >= 0) {
47118
+ flags[a.slice(2, eq)] = a.slice(eq + 1);
47119
+ } else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
47120
+ flags[a.slice(2)] = argv[i + 1];
47121
+ i += 1;
47122
+ } else {
47123
+ flags[a.slice(2)] = true;
47124
+ }
47125
+ } else {
47126
+ positional.push(a);
47127
+ }
47128
+ }
47129
+ const csv = positional[0] ?? (typeof flags.csv === "string" ? flags.csv : void 0);
47130
+ const col = typeof flags.col === "string" ? flags.col : void 0;
47131
+ const out = typeof flags.out === "string" ? flags.out : void 0;
47132
+ const instruction = typeof flags.instruction === "string" ? flags.instruction : void 0;
47133
+ if (!csv) return { ok: false, error: "csv_path is required (positional or --csv)" };
47134
+ if (!col) return { ok: false, error: "--col <id_column> is required" };
47135
+ if (!out) return { ok: false, error: "--out <output_csv_path> is required" };
47136
+ if (!instruction) return { ok: false, error: "--instruction <template> is required" };
47137
+ const agentKindRaw = typeof flags.agent === "string" ? flags.agent : "verify";
47138
+ if (agentKindRaw !== "explore" && agentKindRaw !== "verify" && agentKindRaw !== "general") {
47139
+ return { ok: false, error: `--agent must be one of explore, verify, general (got "${agentKindRaw}")` };
47140
+ }
47141
+ const thoroughnessRaw = typeof flags.thoroughness === "string" ? flags.thoroughness : "medium";
47142
+ if (thoroughnessRaw !== "quick" && thoroughnessRaw !== "medium" && thoroughnessRaw !== "deep") {
47143
+ return { ok: false, error: `--thoroughness must be one of quick, medium, deep (got "${thoroughnessRaw}")` };
47144
+ }
47145
+ let concurrency;
47146
+ if (typeof flags.concurrency === "string") {
47147
+ concurrency = Number.parseInt(flags.concurrency, 10);
47148
+ if (!Number.isFinite(concurrency) || concurrency <= 0) {
47149
+ return { ok: false, error: `--concurrency must be a positive integer (got "${flags.concurrency}")` };
47150
+ }
47151
+ }
47152
+ let maxRuntimeSeconds;
47153
+ if (typeof flags["max-runtime"] === "string") {
47154
+ maxRuntimeSeconds = Number.parseInt(flags["max-runtime"], 10);
47155
+ if (!Number.isFinite(maxRuntimeSeconds) || maxRuntimeSeconds <= 0) {
47156
+ return { ok: false, error: `--max-runtime must be a positive integer (got "${flags["max-runtime"]}")` };
47157
+ }
47158
+ }
47159
+ const scopeList = listFlags.scope ?? (typeof flags.scope === "string" ? [flags.scope] : void 0);
47160
+ return {
47161
+ ok: true,
47162
+ args: {
47163
+ csv_path: csv,
47164
+ id_column: col,
47165
+ output_csv_path: out,
47166
+ instruction_template: instruction,
47167
+ agent_kind: agentKindRaw,
47168
+ thoroughness: thoroughnessRaw,
47169
+ ...scopeList ? { scope_template: scopeList } : {},
47170
+ ...concurrency !== void 0 ? { max_concurrency: concurrency } : {},
47171
+ ...maxRuntimeSeconds !== void 0 ? { max_runtime_seconds: maxRuntimeSeconds } : {}
47172
+ }
47173
+ };
47174
+ }
47175
+ var USAGE = `Usage:
47176
+ /kraken fanout <csv_path>
47177
+ --col <id_column>
47178
+ --out <output_csv_path>
47179
+ --instruction "<template with {column} placeholders>"
47180
+ [--agent explore|verify|general] (default: verify)
47181
+ [--thoroughness quick|medium|deep] (default: medium)
47182
+ [--scope "<glob template>"] (repeatable)
47183
+ [--concurrency <n>] (default: ZELARI_KRAKEN_MAX_PARALLEL)
47184
+ [--max-runtime <seconds>]
47185
+
47186
+ Each row in <csv_path> becomes one sub-agent tentacle. The result CSV
47187
+ gets one row per input row, with extra columns \`status\`, \`result\`, \`error\`.`;
47188
+ async function handleKrakenFanout(ctx, raw) {
47189
+ const argv = splitArgs(stripPrefix(raw, "fanout"));
47190
+ const parsed = parseFanoutArgs(argv);
47191
+ if (!parsed.ok || !parsed.args) {
47192
+ appendSystem(ctx.setMessages, parsed.usage ?? `Usage: ${USAGE}`);
47193
+ if (parsed.error) {
47194
+ appendSystem(ctx.setMessages, `[kraken fanout] ${parsed.error}`);
47195
+ }
47196
+ return;
47197
+ }
47198
+ const absCsv = isAbsolute(parsed.args.csv_path) ? parsed.args.csv_path : joinPath(ctx.cwd, parsed.args.csv_path);
47199
+ try {
47200
+ await fs21.access(absCsv);
47201
+ } catch {
47202
+ appendSystem(ctx.setMessages, `[kraken fanout] source CSV not found: ${absCsv}`);
47203
+ return;
47204
+ }
47205
+ appendSystem(
47206
+ ctx.setMessages,
47207
+ `[kraken fanout] starting: ${parsed.args.csv_path} \u2192 ${parsed.args.output_csv_path} (agent=${parsed.args.agent_kind})`
47208
+ );
47209
+ const audit = new AuditLogger();
47210
+ const taskToolDeps = {
47211
+ createSubAgentContext: createKrakenSubAgentContextFactory({
47212
+ root: ctx.cwd,
47213
+ audit,
47214
+ sessionId: ctx.sessionId
47215
+ })
47216
+ };
47217
+ try {
47218
+ const summary = await runCsvFanout(parsed.args, taskToolDeps, {
47219
+ parentCwd: ctx.cwd,
47220
+ sessionId: ctx.sessionId,
47221
+ onLog: (line) => appendSystem(ctx.setMessages, `[kraken fanout] ${line}`)
47222
+ });
47223
+ appendSystem(
47224
+ ctx.setMessages,
47225
+ `[kraken fanout] done: ${summary.completed}/${summary.rows} ok, ${summary.errored} error, ${summary.durationMs}ms \u2192 ${summary.output_csv_path}`
47226
+ );
47227
+ } catch (err) {
47228
+ appendSystem(
47229
+ ctx.setMessages,
47230
+ `[kraken fanout] failed: ${err instanceof Error ? err.message : String(err)}`
47231
+ );
47232
+ }
47233
+ }
47234
+ function isAbsolute(p3) {
47235
+ return /^([A-Za-z]:[\\/]|[\\/])/.test(p3);
47236
+ }
47237
+ function joinPath(a, b) {
47238
+ if (a.endsWith("\\") || a.endsWith("/")) return a + b;
47239
+ return `${a}\\${b}`;
47240
+ }
47241
+ function stripPrefix(s, prefix) {
47242
+ const trimmed = s.trim();
47243
+ if (trimmed.toLowerCase().startsWith(prefix.toLowerCase())) {
47244
+ return trimmed.slice(prefix.length).trim();
47245
+ }
47246
+ return trimmed;
47247
+ }
47248
+ function splitArgs(s) {
47249
+ const out = [];
47250
+ let cur = "";
47251
+ let inQuotes = false;
47252
+ for (let i = 0; i < s.length; i++) {
47253
+ const ch = s[i];
47254
+ if (ch === '"') {
47255
+ inQuotes = !inQuotes;
47256
+ continue;
47257
+ }
47258
+ if (!inQuotes && /\s/.test(ch)) {
47259
+ if (cur !== "") {
47260
+ out.push(cur);
47261
+ cur = "";
47262
+ }
47263
+ continue;
47264
+ }
47265
+ cur += ch;
47266
+ }
47267
+ if (cur !== "") out.push(cur);
47268
+ return out;
47269
+ }
47270
+
47271
+ // src/cli/slashHandlers/krakenWorkbench.ts
47272
+ import { promises as fs22 } from "node:fs";
47273
+ import path37 from "node:path";
47274
+
47275
+ // src/cli/kraken/workbenchView.ts
47276
+ var EMPTY = {
47277
+ goal: "",
47278
+ graphId: "",
47279
+ started: "",
47280
+ elapsed: "",
47281
+ progress: "",
47282
+ nodes: [],
47283
+ events: []
47284
+ };
47285
+ function parseWorkbench(content) {
47286
+ if (!content) return { ...EMPTY, nodes: [], events: [] };
47287
+ const lines = content.split(/\r?\n/);
47288
+ const out = { ...EMPTY, nodes: [], events: [] };
47289
+ let i = 0;
47290
+ while (i < lines.length) {
47291
+ const line = lines[i];
47292
+ const goalM = /^\*\*Goal:\*\*\s*(.*)$/.exec(line);
47293
+ if (goalM) {
47294
+ out.goal = goalM[1];
47295
+ i += 1;
47296
+ continue;
47297
+ }
47298
+ const gidM = /^\*\*Graph id:\*\*\s*`?([^`\s]*)`?/.exec(line);
47299
+ if (gidM) {
47300
+ out.graphId = gidM[1];
47301
+ i += 1;
47302
+ continue;
47303
+ }
47304
+ const startedM = /^\*\*Started:\*\*\s*(.+?)\s*·\s*\*\*Elapsed:\*\*\s*(\S+)/.exec(line);
47305
+ if (startedM) {
47306
+ out.started = startedM[1];
47307
+ out.elapsed = startedM[2];
47308
+ i += 1;
47309
+ continue;
47310
+ }
47311
+ const justStarted = /^\*\*Started:\*\*\s*(.*)$/.exec(line);
47312
+ if (justStarted) {
47313
+ out.started = justStarted[1];
47314
+ i += 1;
47315
+ continue;
47316
+ }
47317
+ const sectionM = /^##\s+([^:\s]+)(?::\s*(.*))?/.exec(line);
47318
+ if (!sectionM) {
47319
+ i += 1;
47320
+ continue;
47321
+ }
47322
+ const section = sectionM[1];
47323
+ const inline = sectionM[2] ?? "";
47324
+ if (section === "Progress") {
47325
+ if (inline) out.progress = inline;
47326
+ i += 1;
47327
+ continue;
47328
+ }
47329
+ if (section === "Wave") {
47330
+ while (i < lines.length && !lines[i].startsWith("| t")) i += 1;
47331
+ while (i < lines.length && lines[i].startsWith("| t")) {
47332
+ const cells = splitRow(lines[i]);
47333
+ if (cells.length >= 7) {
47334
+ out.nodes.push({
47335
+ id: cells[0],
47336
+ label: cells[1],
47337
+ kind: cells[2],
47338
+ scope: cells[3],
47339
+ status: cells[4],
47340
+ model: cells[5],
47341
+ duration: cells[6]
47342
+ });
47343
+ }
47344
+ i += 1;
47345
+ }
47346
+ continue;
47347
+ }
47348
+ if (section === "Events" || section.startsWith("Events")) {
47349
+ while (i < lines.length && !lines[i].startsWith("- ")) i += 1;
47350
+ while (i < lines.length && lines[i].startsWith("- ")) {
47351
+ const m = /^-\s+(\d{2}:\d{2}:\d{2})\s+(.*)$/.exec(lines[i]);
47352
+ if (m) out.events.push({ ts: m[1], text: m[2] });
47353
+ i += 1;
47354
+ }
47355
+ continue;
47356
+ }
47357
+ i += 1;
47358
+ }
47359
+ return out;
47360
+ }
47361
+ function splitRow(line) {
47362
+ return line.replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
47363
+ }
47364
+ function formatWorkbenchForTerminal(p3) {
47365
+ const lines = [];
47366
+ if (p3.goal) lines.push(`[kraken] ${p3.goal}`);
47367
+ if (p3.progress) lines.push(`[kraken] progress: ${p3.progress}`);
47368
+ if (p3.elapsed) lines.push(`[kraken] elapsed: ${p3.elapsed}`);
47369
+ lines.push("");
47370
+ if (p3.nodes.length > 0) {
47371
+ lines.push("[kraken] wave:");
47372
+ for (const n of p3.nodes) {
47373
+ const scope = n.scope ? ` (${n.scope})` : "";
47374
+ const dur = n.duration ? ` ${n.duration}` : "";
47375
+ lines.push(` ${n.status} ${n.id} ${n.label}${scope}${dur ? " " + dur : ""}`);
47376
+ }
47377
+ lines.push("");
47378
+ }
47379
+ if (p3.events.length > 0) {
47380
+ lines.push("[kraken] events (latest):");
47381
+ for (const e of p3.events.slice(-10)) {
47382
+ lines.push(` ${e.ts} ${e.text}`);
47383
+ }
47384
+ }
47385
+ return lines.join("\n");
47386
+ }
47387
+
47388
+ // src/cli/slashHandlers/krakenWorkbench.ts
47389
+ async function handleKrakenWorkbench(ctx) {
47390
+ const dir = path37.join(ctx.cwd, ".zelari", "radio");
47391
+ let latest = null;
47392
+ let latestMtime = 0;
47393
+ try {
47394
+ const files = await fs22.readdir(dir);
47395
+ for (const f of files) {
47396
+ if (!f.startsWith("workbench-") || !f.endsWith(".md")) continue;
47397
+ const full = path37.join(dir, f);
47398
+ const stat = await fs22.stat(full);
47399
+ if (stat.mtimeMs > latestMtime) {
47400
+ latestMtime = stat.mtimeMs;
47401
+ latest = full;
47402
+ }
47403
+ }
47404
+ } catch {
47405
+ }
47406
+ if (!latest) {
47407
+ appendSystem(ctx.setMessages, "[kraken workbench] no workbench file found (.zelari/radio/workbench-*.md)");
47408
+ return;
47409
+ }
47410
+ const content = await fs22.readFile(latest, "utf8");
47411
+ const parsed = parseWorkbench(content);
47412
+ const rendered = formatWorkbenchForTerminal(parsed);
47413
+ if (!rendered.trim()) {
47414
+ appendSystem(ctx.setMessages, `[kraken workbench] ${path37.basename(latest)}: (no nodes / no events yet)`);
47415
+ return;
47416
+ }
47417
+ appendSystem(ctx.setMessages, `[kraken workbench] ${path37.basename(latest)}:
47418
+ ${rendered}`);
47419
+ }
47420
+
44931
47421
  // src/cli/compaction.ts
44932
47422
  function compactTranscript(messages, options = {}) {
44933
47423
  const threshold = options.threshold ?? 50;
@@ -45071,7 +47561,7 @@ ${output}`.toLowerCase();
45071
47561
  }
45072
47562
 
45073
47563
  // src/cli/slashHandlers/plugins.ts
45074
- init_registry2();
47564
+ init_registry3();
45075
47565
 
45076
47566
  // src/cli/plugins/installer.ts
45077
47567
  init_cmdline();
@@ -45225,17 +47715,17 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
45225
47715
  }
45226
47716
 
45227
47717
  // src/cli/slashHandlers/promoteMember.ts
45228
- import { promises as fs20 } from "node:fs";
45229
- import path38 from "node:path";
47718
+ import { promises as fs23 } from "node:fs";
47719
+ import path40 from "node:path";
45230
47720
  import os10 from "node:os";
45231
47721
  async function handlePromoteMember(ctx, memberId) {
45232
47722
  try {
45233
47723
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
45234
47724
  const { skill, markdown } = promoteMember2(memberId);
45235
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path38.join(os10.homedir(), ".tmp", "zelari-code", "skills");
45236
- await fs20.mkdir(skillDir, { recursive: true });
45237
- const filePath = path38.join(skillDir, `${skill.id}.md`);
45238
- await fs20.writeFile(filePath, markdown, "utf8");
47725
+ const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path40.join(os10.homedir(), ".tmp", "zelari-code", "skills");
47726
+ await fs23.mkdir(skillDir, { recursive: true });
47727
+ const filePath = path40.join(skillDir, `${skill.id}.md`);
47728
+ await fs23.writeFile(filePath, markdown, "utf8");
45239
47729
  appendSystem(
45240
47730
  ctx.setMessages,
45241
47731
  `[promote-member] ${skill.name} (${memberId}) \u2192 ${filePath}
@@ -45251,25 +47741,25 @@ async function handlePromoteMember(ctx, memberId) {
45251
47741
  }
45252
47742
 
45253
47743
  // src/cli/branchManager.ts
45254
- import { promises as fs21, existsSync as existsSync38, readFileSync as readFileSync30, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16, statSync as statSync4, rmSync as rmSync3 } from "node:fs";
45255
- import path39 from "node:path";
47744
+ import { promises as fs24, existsSync as existsSync38, readFileSync as readFileSync30, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16, statSync as statSync4, rmSync as rmSync3 } from "node:fs";
47745
+ import path41 from "node:path";
45256
47746
  import os11 from "node:os";
45257
47747
  var META_FILENAME = "meta.json";
45258
47748
  var SESSIONS_SUBDIR = "sessions";
45259
47749
  function getBranchesBaseDir() {
45260
- return process.env.ANATHEMA_BRANCHES_DIR ?? path39.join(os11.homedir(), ".tmp", "zelari-code", "branches");
47750
+ return process.env.ANATHEMA_BRANCHES_DIR ?? path41.join(os11.homedir(), ".tmp", "zelari-code", "branches");
45261
47751
  }
45262
47752
  function getSessionsBaseDir() {
45263
- return process.env.ANATHEMA_SESSIONS_DIR ?? path39.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
47753
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path41.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
45264
47754
  }
45265
47755
  function branchPathFor(name, baseDir) {
45266
- return path39.join(baseDir, name);
47756
+ return path41.join(baseDir, name);
45267
47757
  }
45268
47758
  function metaPathFor(name, baseDir) {
45269
- return path39.join(baseDir, name, META_FILENAME);
47759
+ return path41.join(baseDir, name, META_FILENAME);
45270
47760
  }
45271
47761
  function sessionsPathFor(name, baseDir) {
45272
- return path39.join(baseDir, name, SESSIONS_SUBDIR);
47762
+ return path41.join(baseDir, name, SESSIONS_SUBDIR);
45273
47763
  }
45274
47764
  function readBranchMeta(name, baseDir) {
45275
47765
  const metaPath = metaPathFor(name, baseDir);
@@ -45294,13 +47784,13 @@ function readBranchMeta(name, baseDir) {
45294
47784
  }
45295
47785
  function writeBranchMeta(name, baseDir, meta3) {
45296
47786
  const metaPath = metaPathFor(name, baseDir);
45297
- mkdirSync16(path39.dirname(metaPath), { recursive: true });
47787
+ mkdirSync16(path41.dirname(metaPath), { recursive: true });
45298
47788
  writeFileSync19(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
45299
47789
  }
45300
47790
  async function countSessions(name, baseDir) {
45301
47791
  const sessionsPath = sessionsPathFor(name, baseDir);
45302
47792
  try {
45303
- const entries = await fs21.readdir(sessionsPath);
47793
+ const entries = await fs24.readdir(sessionsPath);
45304
47794
  return entries.filter((e) => e.endsWith(".jsonl")).length;
45305
47795
  } catch (err) {
45306
47796
  if (err.code === "ENOENT") return 0;
@@ -45345,15 +47835,15 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
45345
47835
  if (branchExists(name, baseDir)) {
45346
47836
  throw new BranchAlreadyExistsError(name);
45347
47837
  }
45348
- const sourcePath = path39.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
47838
+ const sourcePath = path41.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
45349
47839
  if (!existsSync38(sourcePath)) {
45350
47840
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
45351
47841
  }
45352
47842
  const branchPath = branchPathFor(name, baseDir);
45353
47843
  const branchSessionsPath = sessionsPathFor(name, baseDir);
45354
47844
  mkdirSync16(branchSessionsPath, { recursive: true });
45355
- const destPath = path39.join(branchSessionsPath, `${fromSessionId}.jsonl`);
45356
- await fs21.copyFile(sourcePath, destPath);
47845
+ const destPath = path41.join(branchSessionsPath, `${fromSessionId}.jsonl`);
47846
+ await fs24.copyFile(sourcePath, destPath);
45357
47847
  const meta3 = {
45358
47848
  name,
45359
47849
  createdAt: Date.now(),
@@ -45371,7 +47861,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
45371
47861
  async function listBranches(baseDir = getBranchesBaseDir()) {
45372
47862
  let entries;
45373
47863
  try {
45374
- entries = await fs21.readdir(baseDir);
47864
+ entries = await fs24.readdir(baseDir);
45375
47865
  } catch (err) {
45376
47866
  if (err.code === "ENOENT") return [];
45377
47867
  throw err;
@@ -45452,26 +47942,26 @@ async function handleBranchCheckout(ctx, branchName) {
45452
47942
  }
45453
47943
 
45454
47944
  // src/cli/slashHandlers/workspace.ts
45455
- import { promises as fs22 } from "node:fs";
45456
- import path40 from "node:path";
47945
+ import { promises as fs25 } from "node:fs";
47946
+ import path42 from "node:path";
45457
47947
  async function handleWorkspaceShow(ctx, what) {
45458
47948
  try {
45459
- const zelari = path40.join(process.cwd(), ".zelari");
47949
+ const zelari = path42.join(process.cwd(), ".zelari");
45460
47950
  let content;
45461
47951
  switch (what) {
45462
47952
  case "plan": {
45463
- const planPath = path40.join(zelari, "plan.md");
47953
+ const planPath = path42.join(zelari, "plan.md");
45464
47954
  try {
45465
- content = await fs22.readFile(planPath, "utf-8");
47955
+ content = await fs25.readFile(planPath, "utf-8");
45466
47956
  } catch {
45467
47957
  content = "(no plan.md yet \u2014 run a council session first)";
45468
47958
  }
45469
47959
  break;
45470
47960
  }
45471
47961
  case "decisions": {
45472
- const decisionsDir = path40.join(zelari, "decisions");
47962
+ const decisionsDir = path42.join(zelari, "decisions");
45473
47963
  try {
45474
- const files = (await fs22.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
47964
+ const files = (await fs25.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
45475
47965
  if (files.length === 0) {
45476
47966
  content = "(no ADRs yet \u2014 invoke /council to generate some)";
45477
47967
  } else {
@@ -45479,7 +47969,7 @@ async function handleWorkspaceShow(ctx, what) {
45479
47969
  `];
45480
47970
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
45481
47971
  for (const f of files) {
45482
- const raw = await fs22.readFile(path40.join(decisionsDir, f), "utf-8");
47972
+ const raw = await fs25.readFile(path42.join(decisionsDir, f), "utf-8");
45483
47973
  const { meta: meta3, body } = parseFrontmatter2(raw);
45484
47974
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
45485
47975
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -45492,27 +47982,27 @@ async function handleWorkspaceShow(ctx, what) {
45492
47982
  break;
45493
47983
  }
45494
47984
  case "risks": {
45495
- const risksPath = path40.join(zelari, "risks.md");
47985
+ const risksPath = path42.join(zelari, "risks.md");
45496
47986
  try {
45497
- content = await fs22.readFile(risksPath, "utf-8");
47987
+ content = await fs25.readFile(risksPath, "utf-8");
45498
47988
  } catch {
45499
47989
  content = "(no risks.md yet)";
45500
47990
  }
45501
47991
  break;
45502
47992
  }
45503
47993
  case "agents": {
45504
- const agentsPath = path40.join(process.cwd(), "AGENTS.MD");
47994
+ const agentsPath = path42.join(process.cwd(), "AGENTS.MD");
45505
47995
  try {
45506
- content = await fs22.readFile(agentsPath, "utf-8");
47996
+ content = await fs25.readFile(agentsPath, "utf-8");
45507
47997
  } catch {
45508
47998
  content = "(no AGENTS.MD yet at project root \u2014 run `/workspace sync` after a council session)";
45509
47999
  }
45510
48000
  break;
45511
48001
  }
45512
48002
  case "docs": {
45513
- const docsDir = path40.join(zelari, "docs");
48003
+ const docsDir = path42.join(zelari, "docs");
45514
48004
  try {
45515
- const files = (await fs22.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
48005
+ const files = (await fs25.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
45516
48006
  content = files.length ? `# Docs (${files.length})
45517
48007
 
45518
48008
  ` + files.map((f) => `- ${f}`).join("\n") : "(no docs drafts yet)";
@@ -45552,8 +48042,8 @@ async function handleWorkspaceReset(ctx, force) {
45552
48042
  return;
45553
48043
  }
45554
48044
  try {
45555
- const target = path40.join(process.cwd(), ".zelari");
45556
- await fs22.rm(target, { recursive: true, force: true });
48045
+ const target = path42.join(process.cwd(), ".zelari");
48046
+ await fs25.rm(target, { recursive: true, force: true });
45557
48047
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
45558
48048
  } catch (err) {
45559
48049
  appendSystem(ctx.setMessages, `[workspace reset error] ${err instanceof Error ? err.message : String(err)}`);
@@ -45912,16 +48402,16 @@ function handleModelsRefresh(ctx) {
45912
48402
  }
45913
48403
 
45914
48404
  // src/cli/slashHandlers/skills.ts
45915
- import path41 from "node:path";
48405
+ import path43 from "node:path";
45916
48406
  import os12 from "node:os";
45917
48407
 
45918
48408
  // src/cli/skillHistory.ts
45919
- import { promises as fs23, existsSync as existsSync39, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync4, mkdirSync as mkdirSync17 } from "node:fs";
48409
+ import { promises as fs26, existsSync as existsSync39, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync4, mkdirSync as mkdirSync17 } from "node:fs";
45920
48410
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
45921
48411
  async function readSkillHistory(file2) {
45922
48412
  let raw = "";
45923
48413
  try {
45924
- raw = await fs23.readFile(file2, "utf-8");
48414
+ raw = await fs26.readFile(file2, "utf-8");
45925
48415
  } catch {
45926
48416
  return [];
45927
48417
  }
@@ -46038,7 +48528,7 @@ function handleSkillPicker(ctx, skills, openPicker, fallbackMessage) {
46038
48528
  });
46039
48529
  }
46040
48530
  async function handleSkillStats(ctx, skillId) {
46041
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path41.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
48531
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path43.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
46042
48532
  try {
46043
48533
  const records = await readSkillHistory(historyFile);
46044
48534
  const stats = getSkillStats(records, skillId);
@@ -46054,7 +48544,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
46054
48544
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
46055
48545
  return;
46056
48546
  }
46057
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path41.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
48547
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path43.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
46058
48548
  try {
46059
48549
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
46060
48550
  appendSystem(ctx.setMessages, formatted);
@@ -46478,6 +48968,18 @@ function useSlashDispatch(params) {
46478
48968
  await handleKrakenGraph({ setMessages, cwd: process.cwd(), sessionId: sid }, result.graphPrompt ?? "");
46479
48969
  return;
46480
48970
  }
48971
+ if (result.kind === "kraken_fanout") {
48972
+ const sid = (sessionId || "default").trim();
48973
+ await handleKrakenFanout(
48974
+ { setMessages, cwd: process.cwd(), sessionId: sid },
48975
+ result.fanoutArgs ?? ""
48976
+ );
48977
+ return;
48978
+ }
48979
+ if (result.kind === "kraken_workbench") {
48980
+ await handleKrakenWorkbench({ setMessages, cwd: process.cwd() });
48981
+ return;
48982
+ }
46481
48983
  if (result.kind === "phase_set" && result.phaseTarget) {
46482
48984
  const { setPhase: setPhase2 } = await Promise.resolve().then(() => (init_phaseState(), phaseState_exports));
46483
48985
  const { describePhase: describePhase2 } = await Promise.resolve().then(() => (init_phase(), phase_exports));
@@ -47061,7 +49563,7 @@ function SplashGate({
47061
49563
  // src/cli/components/PluginGate.tsx
47062
49564
  import React12, { useEffect as useEffect9, useState as useState10, useCallback as useCallback6 } from "react";
47063
49565
  import { Box as Box11, Text as Text12, useStdin as useStdin4 } from "ink";
47064
- init_registry2();
49566
+ init_registry3();
47065
49567
  init_prefs();
47066
49568
  var CHOICE_INSTALL = "__install__";
47067
49569
  var CHOICE_LATER = "__later__";
@@ -47501,6 +50003,8 @@ function parseHeadlessFlags(argv) {
47501
50003
  let history2;
47502
50004
  let once = false;
47503
50005
  let krakenGraph;
50006
+ let planOnly = process.env.ZELARI_KRAKEN_PLAN_ONLY === "1" || process.env.ZELARI_KRAKEN_PLAN_ONLY === "true";
50007
+ let runPlan = process.env.ZELARI_KRAKEN_RUN_PLAN;
47504
50008
  for (let i = 0; i < argv.length; i++) {
47505
50009
  const arg = argv[i];
47506
50010
  if (arg === "--headless") continue;
@@ -47594,6 +50098,11 @@ function parseHeadlessFlags(argv) {
47594
50098
  } else if (arg === "--kraken-graph") {
47595
50099
  krakenGraph = argv[i + 1];
47596
50100
  i++;
50101
+ } else if (arg === "--plan-only") {
50102
+ planOnly = true;
50103
+ } else if (arg === "--run-plan") {
50104
+ runPlan = argv[i + 1];
50105
+ i++;
47597
50106
  }
47598
50107
  }
47599
50108
  if (councilFlag && !modeExplicit) {
@@ -47621,7 +50130,9 @@ function parseHeadlessFlags(argv) {
47621
50130
  model,
47622
50131
  ...history2 && history2.length > 0 ? { history: history2 } : {},
47623
50132
  ...once ? { once: true } : {},
47624
- ...krakenGraph ? { krakenGraph } : {}
50133
+ ...krakenGraph ? { krakenGraph } : {},
50134
+ ...planOnly ? { planOnly: true } : {},
50135
+ ...runPlan ? { runPlan } : {}
47625
50136
  }
47626
50137
  };
47627
50138
  }
@@ -47692,6 +50203,9 @@ function createStreamScrubber() {
47692
50203
 
47693
50204
  // src/cli/runHeadless.ts
47694
50205
  init_taskTool();
50206
+ import { promises as fs28 } from "node:fs";
50207
+ import path45 from "node:path";
50208
+ import { randomUUID as randomUUID6 } from "node:crypto";
47695
50209
  async function runHeadless(opts) {
47696
50210
  resetTaskSpawnCount();
47697
50211
  try {
@@ -47780,7 +50294,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47780
50294
  }
47781
50295
  const { planTaskGraph: planTaskGraph2 } = await Promise.resolve().then(() => (init_planner(), planner_exports));
47782
50296
  const { loadGraphSnapshot: loadGraphSnapshot2, formatSnapshotForPlanner: formatSnapshotForPlanner2 } = await Promise.resolve().then(() => (init_graphMemory(), graphMemory_exports));
47783
- const { formatKrakenGraphAscii: formatKrakenGraphAscii2 } = await Promise.resolve().then(() => (init_graphStatus(), graphStatus_exports));
50297
+ const { formatKrakenGraphAscii: formatKrakenGraphAscii2, formatKrakenGraphDigest: formatKrakenGraphDigest2 } = await Promise.resolve().then(() => (init_graphStatus(), graphStatus_exports));
47784
50298
  const { AuditLogger: AuditLogger2 } = await Promise.resolve().then(() => (init_auditLogger(), auditLogger_exports));
47785
50299
  const { createKrakenSubAgentContextFactory: createKrakenSubAgentContextFactory2 } = await Promise.resolve().then(() => (init_toolRegistry(), toolRegistry_exports));
47786
50300
  const cwd = process.cwd();
@@ -47793,18 +50307,69 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47793
50307
  `);
47794
50308
  }
47795
50309
  };
50310
+ const abort = new AbortController();
50311
+ const onSigint = () => {
50312
+ log("SIGINT \u2014 cancelling the graph; press Ctrl-C again to force quit");
50313
+ abort.abort();
50314
+ };
50315
+ process.once("SIGINT", onSigint);
47796
50316
  try {
50317
+ let preflightGraph;
50318
+ if (opts.runPlan && opts.runPlan.trim() !== "") {
50319
+ const planPath = path45.join(cwd, ".zelari", "radio", `plan-${opts.runPlan}.json`);
50320
+ log(`loading pre-flight plan: ${planPath}`);
50321
+ let raw;
50322
+ try {
50323
+ raw = await fs28.readFile(planPath, "utf8");
50324
+ } catch (e) {
50325
+ log(`plan file not found: ${planPath} (${e.message})`);
50326
+ return 1;
50327
+ }
50328
+ let planJson;
50329
+ try {
50330
+ planJson = JSON.parse(raw);
50331
+ } catch (e) {
50332
+ log(`plan file is malformed JSON: ${e.message}`);
50333
+ return 1;
50334
+ }
50335
+ if (!planJson || !Array.isArray(planJson.nodes)) {
50336
+ log(`plan file is malformed: missing "nodes" array`);
50337
+ return 1;
50338
+ }
50339
+ const { createGraph: createGraph2, validateGraph: validateGraph2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
50340
+ const validated = createGraph2(planJson.graphId ?? opts.runPlan, planJson.nodes);
50341
+ validateGraph2(validated);
50342
+ preflightGraph = validated;
50343
+ log(`pre-flight plan loaded (${validated.nodes.size} nodes); executing`);
50344
+ }
47797
50345
  log(`planning kraken graph: ${prompt}`);
47798
50346
  const previous = await loadGraphSnapshot2(cwd);
47799
50347
  const previousAttempt = formatSnapshotForPlanner2(previous);
47800
50348
  if (previousAttempt) log("resuming from the previous unfinished graph");
47801
- const graph = await planTaskGraph2({
50349
+ const graph = preflightGraph ?? await planTaskGraph2({
47802
50350
  prompt,
47803
50351
  provider,
47804
50352
  model,
50353
+ cwd,
47805
50354
  ...previousAttempt ? { previousAttempt } : {}
47806
50355
  });
47807
50356
  log(formatKrakenGraphAscii2(graph));
50357
+ if (opts.planOnly) {
50358
+ const planId = randomUUID6();
50359
+ const planDir = path45.join(cwd, ".zelari", "radio");
50360
+ const planPath = path45.join(planDir, `plan-${planId}.json`);
50361
+ await fs28.mkdir(planDir, { recursive: true });
50362
+ await fs28.writeFile(planPath, JSON.stringify(graph, null, 2), "utf8");
50363
+ log(`plan-only: wrote ${planPath} (${graph.nodes.size} nodes)`);
50364
+ log(
50365
+ `re-run with ZELARI_KRAKEN_RUN_PLAN=${planId} to execute (or --run-plan <id> when the desktop wiring is in place)`
50366
+ );
50367
+ if (opts.output === "json") {
50368
+ emitEvent({ type: "log", message: `plan_only_id=${planId}` });
50369
+ emitEvent({ type: "log", message: `plan_only_path=${planPath}` });
50370
+ }
50371
+ return 0;
50372
+ }
47808
50373
  const audit = new AuditLogger2();
47809
50374
  const executor = new KrakenGraphExecutor2({
47810
50375
  taskToolDeps: {
@@ -47823,10 +50388,20 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47823
50388
  },
47824
50389
  parentCwd: cwd,
47825
50390
  sessionId,
47826
- goal: prompt
50391
+ goal: prompt,
50392
+ signal: abort.signal
47827
50393
  });
47828
50394
  const summary = await executor.execute(graph);
47829
- const finalAscii = formatKrakenGraphAscii2(summary.graph);
50395
+ if (summary.cancelled) log("graph cancelled \u2014 partial results below");
50396
+ const finalAscii = `${formatKrakenGraphAscii2(summary.graph)}
50397
+
50398
+ ${formatKrakenGraphDigest2(
50399
+ summary.graph,
50400
+ {
50401
+ durationsMs: summary.durationsMs,
50402
+ unresolvedFindings: summary.unresolvedFindings
50403
+ }
50404
+ )}`;
47830
50405
  if (opts.output === "json") {
47831
50406
  emitEvent({ type: "message_start" });
47832
50407
  emitEvent({ type: "message_delta", delta: finalAscii });
@@ -47853,6 +50428,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47853
50428
  `);
47854
50429
  }
47855
50430
  return 2;
50431
+ } finally {
50432
+ process.off("SIGINT", onSigint);
47856
50433
  }
47857
50434
  }
47858
50435
  function planModeFromOpts(opts) {
@@ -48668,7 +51245,7 @@ ${ragContext}` : slicePrompt;
48668
51245
  init_desktopConfig();
48669
51246
 
48670
51247
  // src/cli/plugins/cliFlags.ts
48671
- init_registry2();
51248
+ init_registry3();
48672
51249
  function getArg(argv, flag) {
48673
51250
  const i = argv.indexOf(flag);
48674
51251
  if (i < 0) return void 0;
@@ -48968,7 +51545,7 @@ function upsertSkill(opts) {
48968
51545
  }
48969
51546
  dir = getProjectSkillsDir(root);
48970
51547
  }
48971
- const path44 = skillFilePath(dir, name);
51548
+ const path47 = skillFilePath(dir, name);
48972
51549
  const content = serializeSkillMd({
48973
51550
  name,
48974
51551
  description,
@@ -48977,13 +51554,13 @@ function upsertSkill(opts) {
48977
51554
  tools: opts.tools,
48978
51555
  cost: opts.cost
48979
51556
  });
48980
- const parsed = parseSkillMd(content, path44);
51557
+ const parsed = parseSkillMd(content, path47);
48981
51558
  if (!parsed) {
48982
51559
  return { ok: false, error: "Generated SKILL.md failed validation" };
48983
51560
  }
48984
- mkdirSync18(dirname9(path44), { recursive: true });
48985
- writeFileSync20(path44, content, "utf8");
48986
- return { ok: true, path: path44 };
51561
+ mkdirSync18(dirname9(path47), { recursive: true });
51562
+ writeFileSync20(path47, content, "utf8");
51563
+ return { ok: true, path: path47 };
48987
51564
  }
48988
51565
  function removeSkill(opts) {
48989
51566
  const name = opts.name.trim().toLowerCase();
@@ -49001,8 +51578,8 @@ function removeSkill(opts) {
49001
51578
  dir = getProjectSkillsDir(root);
49002
51579
  }
49003
51580
  const skillDir = join34(dir, name);
49004
- const path44 = skillFilePath(dir, name);
49005
- if (!existsSync42(path44) && !existsSync42(skillDir)) {
51581
+ const path47 = skillFilePath(dir, name);
51582
+ if (!existsSync42(path47) && !existsSync42(skillDir)) {
49006
51583
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
49007
51584
  }
49008
51585
  try {
@@ -49013,7 +51590,7 @@ function removeSkill(opts) {
49013
51590
  error: err instanceof Error ? err.message : String(err)
49014
51591
  };
49015
51592
  }
49016
- return { ok: true, path: path44 };
51593
+ return { ok: true, path: path47 };
49017
51594
  }
49018
51595
 
49019
51596
  // src/cli/generateSkillFromUrl.ts
@@ -49103,8 +51680,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
49103
51680
  let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
49104
51681
  if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
49105
51682
  try {
49106
- const path44 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
49107
- name = path44 && /^[a-z0-9]/.test(path44) ? path44 : "imported-skill";
51683
+ const path47 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
51684
+ name = path47 && /^[a-z0-9]/.test(path47) ? path47 : "imported-skill";
49108
51685
  } catch {
49109
51686
  name = "imported-skill";
49110
51687
  }