zelari-code 1.29.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 (54) 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 +63 -1
  6. package/dist/cli/kraken/executor.js.map +1 -1
  7. package/dist/cli/kraken/planner.js +87 -2
  8. package/dist/cli/kraken/planner.js.map +1 -1
  9. package/dist/cli/kraken/planner.test.js +43 -0
  10. package/dist/cli/kraken/planner.test.js.map +1 -0
  11. package/dist/cli/kraken/runtime/compile.js +73 -0
  12. package/dist/cli/kraken/runtime/compile.js.map +1 -0
  13. package/dist/cli/kraken/runtime/runScriptPlan.js +195 -0
  14. package/dist/cli/kraken/runtime/runScriptPlan.js.map +1 -0
  15. package/dist/cli/kraken/scriptPlanner.js +286 -0
  16. package/dist/cli/kraken/scriptPlanner.js.map +1 -0
  17. package/dist/cli/kraken/scriptPlanner.test.js +152 -0
  18. package/dist/cli/kraken/scriptPlanner.test.js.map +1 -0
  19. package/dist/cli/kraken/skillSuggest.js +97 -0
  20. package/dist/cli/kraken/skillSuggest.js.map +1 -0
  21. package/dist/cli/kraken/skillSuggest.test.js +157 -0
  22. package/dist/cli/kraken/skillSuggest.test.js.map +1 -0
  23. package/dist/cli/kraken/weaknessMeter.js +183 -0
  24. package/dist/cli/kraken/weaknessMeter.js.map +1 -0
  25. package/dist/cli/kraken/weaknessMeter.test.js +212 -0
  26. package/dist/cli/kraken/weaknessMeter.test.js.map +1 -0
  27. package/dist/cli/kraken/workbench.js +296 -0
  28. package/dist/cli/kraken/workbench.js.map +1 -0
  29. package/dist/cli/kraken/workbench.test.js +253 -0
  30. package/dist/cli/kraken/workbench.test.js.map +1 -0
  31. package/dist/cli/kraken/workbenchView.js +155 -0
  32. package/dist/cli/kraken/workbenchView.js.map +1 -0
  33. package/dist/cli/kraken/workbenchView.test.js +130 -0
  34. package/dist/cli/kraken/workbenchView.test.js.map +1 -0
  35. package/dist/cli/main.bundled.js +2214 -299
  36. package/dist/cli/main.bundled.js.map +4 -4
  37. package/dist/cli/runHeadless.js +58 -7
  38. package/dist/cli/runHeadless.js.map +1 -1
  39. package/dist/cli/slashCommands.js +16 -0
  40. package/dist/cli/slashCommands.js.map +1 -1
  41. package/dist/cli/slashHandlers/krakenFanout.js +200 -0
  42. package/dist/cli/slashHandlers/krakenFanout.js.map +1 -0
  43. package/dist/cli/slashHandlers/krakenWorkbench.js +49 -0
  44. package/dist/cli/slashHandlers/krakenWorkbench.js.map +1 -0
  45. package/dist/cli/tools/krakenCsvFanout.js +260 -0
  46. package/dist/cli/tools/krakenCsvFanout.js.map +1 -0
  47. package/dist/cli/tools/krakenCsvFanout.test.js +200 -0
  48. package/dist/cli/tools/krakenCsvFanout.test.js.map +1 -0
  49. package/dist/cli/tools/krakenModel.js +32 -0
  50. package/dist/cli/tools/krakenModel.js.map +1 -1
  51. package/dist/cli/tools/krakenRadio.js.map +1 -1
  52. package/dist/cli/tools/taskTool.js +1 -1
  53. package/dist/cli/tools/taskTool.js.map +1 -1
  54. 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
  });
@@ -25074,6 +25210,14 @@ function canRunParallel(a, b) {
25074
25210
  }
25075
25211
  return false;
25076
25212
  }
25213
+ function selectParallelWave(candidates) {
25214
+ const wave = [];
25215
+ for (const node of candidates) {
25216
+ if (wave.every((w) => canRunParallel(w, node)))
25217
+ wave.push(node);
25218
+ }
25219
+ return wave;
25220
+ }
25077
25221
  var READ_ONLY_KINDS, WRITER_KINDS;
25078
25222
  var init_conflict = __esm({
25079
25223
  "packages/core/dist/kraken/conflict.js"() {
@@ -25083,6 +25227,168 @@ var init_conflict = __esm({
25083
25227
  }
25084
25228
  });
25085
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
+
25086
25392
  // packages/core/dist/kraken/verdict.js
25087
25393
  function parseVerifyVerdict(text) {
25088
25394
  const source = typeof text === "string" ? text : "";
@@ -25103,6 +25409,55 @@ function parseVerifyVerdict(text) {
25103
25409
  const findings = capFindings(source.slice(0, last.index));
25104
25410
  return { verdict, findings };
25105
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
+ }
25106
25461
  function capFindings(raw) {
25107
25462
  const trimmed = raw.trim();
25108
25463
  if (trimmed.length <= MAX_FINDINGS_CHARS)
@@ -25114,11 +25469,537 @@ var MAX_FINDINGS_CHARS, VERDICT_LINE;
25114
25469
  var init_verdict = __esm({
25115
25470
  "packages/core/dist/kraken/verdict.js"() {
25116
25471
  "use strict";
25472
+ init_weakness();
25117
25473
  MAX_FINDINGS_CHARS = 2800;
25118
25474
  VERDICT_LINE = /^[\s>*_-]*VERDICT[\s*_]*:[\s*_]*(PASS|FAIL)\b/gim;
25119
25475
  }
25120
25476
  });
25121
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
+
25122
26003
  // packages/core/dist/kraken/index.js
25123
26004
  var init_kraken = __esm({
25124
26005
  "packages/core/dist/kraken/index.js"() {
@@ -25126,10 +26007,254 @@ var init_kraken = __esm({
25126
26007
  init_graph();
25127
26008
  init_conflict();
25128
26009
  init_verdict();
26010
+ init_personas();
26011
+ init_runtime();
26012
+ init_weakness();
25129
26013
  }
25130
26014
  });
25131
26015
 
25132
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
+ });
25133
26258
  var init_dist = __esm({
25134
26259
  "packages/core/dist/index.js"() {
25135
26260
  "use strict";
@@ -25773,9 +26898,9 @@ function spillToolOutput(fullText, meta3) {
25773
26898
  const rnd = randomBytes(3).toString("hex");
25774
26899
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
25775
26900
  const file2 = `${stamp}-${safeTool}-${hash3}-${rnd}.txt`;
25776
- const path44 = join11(dir, file2);
25777
- writeFileSync10(path44, fullText, "utf8");
25778
- return path44;
26901
+ const path47 = join11(dir, file2);
26902
+ writeFileSync10(path47, fullText, "utf8");
26903
+ return path47;
25779
26904
  } catch {
25780
26905
  return null;
25781
26906
  }
@@ -25791,21 +26916,21 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
25791
26916
  if (text.length === 0)
25792
26917
  return text;
25793
26918
  const opts = typeof capOrOpts === "number" ? { cap: capOrOpts } : capOrOpts ?? {};
25794
- 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;
25795
26920
  const doSpill = opts.spill !== false;
25796
26921
  const lines = text.split("\n");
25797
- const charBudget = cap2 * 80;
25798
- const overLines = lines.length > cap2;
25799
- 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;
25800
26925
  if (!overLines && !overChars)
25801
26926
  return text;
25802
26927
  let preview;
25803
26928
  let marker;
25804
26929
  if (overLines) {
25805
- const half = Math.floor(cap2 / 2);
26930
+ const half = Math.floor(cap3 / 2);
25806
26931
  const head = lines.slice(0, half);
25807
26932
  const tail = lines.slice(lines.length - half);
25808
- const omitted = lines.length - cap2;
26933
+ const omitted = lines.length - cap3;
25809
26934
  marker = `+${omitted} lines omitted \u2014 showing head:${half}, tail:${half} of ${lines.length} total`;
25810
26935
  preview = head.join("\n") + `
25811
26936
  \u2026 [${marker}] \u2026
@@ -25821,10 +26946,10 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
25821
26946
  ${tail}`;
25822
26947
  }
25823
26948
  if (doSpill) {
25824
- const path44 = spillToolOutput(text, { toolName: opts.toolName });
25825
- if (path44) {
26949
+ const path47 = spillToolOutput(text, { toolName: opts.toolName });
26950
+ if (path47) {
25826
26951
  const spillNote = `
25827
- \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`;
25828
26953
  if (preview.includes("] \u2026\n")) {
25829
26954
  preview = preview.replace("] \u2026\n", `] \u2026${spillNote}
25830
26955
  `);
@@ -25836,7 +26961,7 @@ ${tail}`;
25836
26961
  return preview;
25837
26962
  }
25838
26963
  var TOOL_NAME_ALIASES, TOOL_RESULT_LINE_CAP, ToolRegistry;
25839
- var init_registry = __esm({
26964
+ var init_registry2 = __esm({
25840
26965
  "packages/core/dist/core/tools/registry.js"() {
25841
26966
  "use strict";
25842
26967
  init_zodBridge();
@@ -26770,7 +27895,7 @@ async function runTentacle(opts) {
26770
27895
  model: sub.model,
26771
27896
  provider: sub.provider,
26772
27897
  messages: [
26773
- { role: "system", content: systemPromptForAgent(agent) },
27898
+ { role: "system", content: opts.systemPromptOverride ?? systemPromptForAgent(agent) },
26774
27899
  { role: "user", content: userContent }
26775
27900
  ],
26776
27901
  tools: sub.tools,
@@ -28765,21 +29890,21 @@ function normalizeAuth(auth) {
28765
29890
  return "agent";
28766
29891
  }
28767
29892
  function readSecrets() {
28768
- const path44 = getSshSecretsPath();
28769
- if (!existsSync17(path44)) return {};
29893
+ const path47 = getSshSecretsPath();
29894
+ if (!existsSync17(path47)) return {};
28770
29895
  try {
28771
- return JSON.parse(readFileSync16(path44, "utf8"));
29896
+ return JSON.parse(readFileSync16(path47, "utf8"));
28772
29897
  } catch {
28773
29898
  return {};
28774
29899
  }
28775
29900
  }
28776
29901
  function writeSecrets(data) {
28777
- const path44 = getSshSecretsPath();
28778
- mkdirSync9(dirname2(path44), { recursive: true });
28779
- 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)}
28780
29905
  `, "utf8");
28781
29906
  try {
28782
- chmodSync(path44, 384);
29907
+ chmodSync(path47, 384);
28783
29908
  } catch {
28784
29909
  }
28785
29910
  }
@@ -28808,10 +29933,10 @@ function deleteSshPassword(id) {
28808
29933
  writeSecrets({ passwords });
28809
29934
  }
28810
29935
  function readStore2() {
28811
- const path44 = getSshTargetsPath();
28812
- if (!existsSync17(path44)) return [];
29936
+ const path47 = getSshTargetsPath();
29937
+ if (!existsSync17(path47)) return [];
28813
29938
  try {
28814
- const parsed = JSON.parse(readFileSync16(path44, "utf8"));
29939
+ const parsed = JSON.parse(readFileSync16(path47, "utf8"));
28815
29940
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
28816
29941
  return list.filter(
28817
29942
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -28826,11 +29951,11 @@ function readStore2() {
28826
29951
  }
28827
29952
  }
28828
29953
  function writeStore2(targets) {
28829
- const path44 = getSshTargetsPath();
28830
- mkdirSync9(dirname2(path44), { recursive: true });
29954
+ const path47 = getSshTargetsPath();
29955
+ mkdirSync9(dirname2(path47), { recursive: true });
28831
29956
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
28832
29957
  writeFileSync11(
28833
- path44,
29958
+ path47,
28834
29959
  `${JSON.stringify({ targets: clean }, null, 2)}
28835
29960
  `,
28836
29961
  "utf8"
@@ -28989,12 +30114,12 @@ function runSsh(target, remoteCommand, timeoutMs = 6e4) {
28989
30114
  });
28990
30115
  let stdout = "";
28991
30116
  let stderr = "";
28992
- const cap2 = 4e4;
30117
+ const cap3 = 4e4;
28993
30118
  child.stdout?.on("data", (d) => {
28994
- if (stdout.length < cap2) stdout += d.toString("utf8");
30119
+ if (stdout.length < cap3) stdout += d.toString("utf8");
28995
30120
  });
28996
30121
  child.stderr?.on("data", (d) => {
28997
- if (stderr.length < cap2) stderr += d.toString("utf8");
30122
+ if (stderr.length < cap3) stderr += d.toString("utf8");
28998
30123
  });
28999
30124
  const timer = setTimeout(() => {
29000
30125
  child.kill("SIGTERM");
@@ -29076,11 +30201,11 @@ function formatSshTargetsForPrompt() {
29076
30201
  ];
29077
30202
  for (const t of targets) {
29078
30203
  const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
29079
- const path44 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
30204
+ const path47 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
29080
30205
  const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
29081
30206
  const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
29082
30207
  lines.push(
29083
- `- 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}`
29084
30209
  );
29085
30210
  }
29086
30211
  return lines.join("\n");
@@ -29591,7 +30716,8 @@ __export(krakenModel_exports, {
29591
30716
  isKrakenAutoModelEnabled: () => isKrakenAutoModelEnabled,
29592
30717
  pickCheapModel: () => pickCheapModel,
29593
30718
  resolveKrakenSubModel: () => resolveKrakenSubModel,
29594
- resolveKrakenSubModelAsync: () => resolveKrakenSubModelAsync
30719
+ resolveKrakenSubModelAsync: () => resolveKrakenSubModelAsync,
30720
+ resolvePersonaModel: () => resolvePersonaModel
29595
30721
  });
29596
30722
  function isCheapModelId(id) {
29597
30723
  if (!id) return false;
@@ -29641,6 +30767,12 @@ function resolveKrakenSubModel(agent, parentModel, env = process.env, opts = {})
29641
30767
  }
29642
30768
  return parentModel;
29643
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
+ }
29644
30776
  async function resolveKrakenSubModelAsync(agent, parentModel, env = process.env, opts = {}) {
29645
30777
  let candidates = [];
29646
30778
  if (opts.provider) {
@@ -29690,7 +30822,7 @@ function createBuiltinToolRegistry(options = {}) {
29690
30822
  const safeBash = wrapWithShellSafety(bashTool, audit, sessionId);
29691
30823
  const safeFetchUrl = wrapWithAudit(fetchUrlTool, audit, sessionId);
29692
30824
  const safeWebSearch = wrapWithAudit(webSearchTool, audit, sessionId);
29693
- const registry3 = new ToolRegistry();
30825
+ const registry4 = new ToolRegistry();
29694
30826
  const profile = options.profile ?? "full";
29695
30827
  const readOnly = options.readOnly === true || options.planMode === true || profile === "explore";
29696
30828
  const verifyMode = profile === "verify";
@@ -29698,34 +30830,34 @@ function createBuiltinToolRegistry(options = {}) {
29698
30830
  const allowBash = allowMutators || verifyMode;
29699
30831
  const permPolicy = options.permissionPolicy ?? defaultPermissionPolicy();
29700
30832
  const withPerm = (t) => wrapWithPermissions(t, permPolicy, options.onPermissionAsk);
29701
- registry3.register(withPerm(safeReadFile));
29702
- registry3.register(withPerm(safeGrepContent));
29703
- registry3.register(withPerm(safeListFiles));
29704
- registry3.register(withPerm(safeShowDiff));
29705
- registry3.register(withPerm(safeFetchUrl));
29706
- 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));
29707
30839
  if (allowMutators) {
29708
- registry3.register(withPerm(safeWriteFile));
29709
- registry3.register(withPerm(safeEditFile));
29710
- registry3.register(withPerm(safeApplyDiff));
30840
+ registry4.register(withPerm(safeWriteFile));
30841
+ registry4.register(withPerm(safeEditFile));
30842
+ registry4.register(withPerm(safeApplyDiff));
29711
30843
  }
29712
30844
  if (allowBash) {
29713
- registry3.register(withPerm(safeBash));
30845
+ registry4.register(withPerm(safeBash));
29714
30846
  }
29715
30847
  const askUserTool = options.readOnly === true || profile === "explore" || profile === "verify" ? null : createAskUserTool(options.onAskUser);
29716
30848
  if (askUserTool) {
29717
- registry3.register(withPerm(askUserTool));
30849
+ registry4.register(withPerm(askUserTool));
29718
30850
  }
29719
30851
  const enableSkill = options.enableSkill !== false && options.readOnly !== true && profile !== "explore" && profile !== "verify";
29720
30852
  const skillTool = enableSkill ? withPerm(createSkillTool({ cwd: root })) : null;
29721
30853
  if (skillTool) {
29722
- registry3.register(skillTool);
30854
+ registry4.register(skillTool);
29723
30855
  }
29724
30856
  const enableTodos = options.enableTodos !== false && options.readOnly !== true && profile === "full";
29725
30857
  const todoWrite = enableTodos ? withPerm(createTodoWriteTool()) : null;
29726
30858
  const todoRead = enableTodos ? withPerm(createTodoReadTool()) : null;
29727
- if (todoWrite) registry3.register(todoWrite);
29728
- if (todoRead) registry3.register(todoRead);
30859
+ if (todoWrite) registry4.register(todoWrite);
30860
+ if (todoRead) registry4.register(todoRead);
29729
30861
  const summary = [
29730
30862
  safeReadFile,
29731
30863
  safeGrepContent,
@@ -29747,13 +30879,13 @@ function createBuiltinToolRegistry(options = {}) {
29747
30879
  }));
29748
30880
  if (process.env.ZELARI_AST !== "0") {
29749
30881
  for (const t of createAstTools()) {
29750
- registry3.register(t);
30882
+ registry4.register(t);
29751
30883
  tools.push({ name: t.name, description: t.description, permissions: t.permissions ?? [] });
29752
30884
  }
29753
30885
  }
29754
30886
  if (process.env.ZELARI_SEMANTIC !== "0") {
29755
30887
  const semanticTool = createSemanticTool({ root });
29756
- registry3.register(semanticTool);
30888
+ registry4.register(semanticTool);
29757
30889
  tools.push({
29758
30890
  name: semanticTool.name,
29759
30891
  description: semanticTool.description,
@@ -29762,7 +30894,7 @@ function createBuiltinToolRegistry(options = {}) {
29762
30894
  }
29763
30895
  if (!readOnly && process.env.ZELARI_BROWSER !== "0") {
29764
30896
  const browserTool = createBrowserTool();
29765
- registry3.register(browserTool);
30897
+ registry4.register(browserTool);
29766
30898
  tools.push({
29767
30899
  name: browserTool.name,
29768
30900
  description: browserTool.description,
@@ -29771,7 +30903,7 @@ function createBuiltinToolRegistry(options = {}) {
29771
30903
  }
29772
30904
  if (!readOnly && process.env.ZELARI_SSH !== "0") {
29773
30905
  for (const t of createSshTools()) {
29774
- registry3.register(t);
30906
+ registry4.register(t);
29775
30907
  tools.push({
29776
30908
  name: t.name,
29777
30909
  description: t.description,
@@ -29782,7 +30914,7 @@ function createBuiltinToolRegistry(options = {}) {
29782
30914
  if (!readOnly) {
29783
30915
  for (const t of createWorldModelTools()) {
29784
30916
  const safe = wrapWithAudit(t, audit, sessionId);
29785
- registry3.register(safe);
30917
+ registry4.register(safe);
29786
30918
  tools.push({
29787
30919
  name: t.name,
29788
30920
  description: t.description,
@@ -29795,7 +30927,7 @@ function createBuiltinToolRegistry(options = {}) {
29795
30927
  const taskTool = createTaskTool({
29796
30928
  createSubAgentContext: createKrakenSubAgentContextFactory({ root, audit, sessionId })
29797
30929
  });
29798
- registry3.register(withPerm(taskTool));
30930
+ registry4.register(withPerm(taskTool));
29799
30931
  tools.push({
29800
30932
  name: taskTool.name,
29801
30933
  description: taskTool.description,
@@ -29805,17 +30937,17 @@ function createBuiltinToolRegistry(options = {}) {
29805
30937
  if (!readOnly && process.env.ZELARI_LSP !== "0" && options.lspProvider !== null) {
29806
30938
  const lspTools = options.lspProvider ? createLspTools(options.lspProvider, root) : createLspTools(getSharedLspManager(root), root);
29807
30939
  for (const t of lspTools) {
29808
- registry3.register(t);
30940
+ registry4.register(t);
29809
30941
  tools.push({ name: t.name, description: t.description, permissions: t.permissions ?? [] });
29810
30942
  }
29811
30943
  }
29812
- return { registry: registry3, tools };
30944
+ return { registry: registry4, tools };
29813
30945
  }
29814
- function getCliToolCatalogEntries(registry3) {
30946
+ function getCliToolCatalogEntries(registry4) {
29815
30947
  const entries = [];
29816
- for (const name of registry3.list()) {
30948
+ for (const name of registry4.list()) {
29817
30949
  if (HARNESS_BUILTIN_NAMES.has(name)) continue;
29818
- const def = registry3.get(name);
30950
+ const def = registry4.get(name);
29819
30951
  if (!def) continue;
29820
30952
  try {
29821
30953
  entries.push(cliToolToEnhanced(def));
@@ -29824,8 +30956,8 @@ function getCliToolCatalogEntries(registry3) {
29824
30956
  }
29825
30957
  return entries;
29826
30958
  }
29827
- function registerCliToolsIntoCouncilCatalog(registry3) {
29828
- for (const entry of getCliToolCatalogEntries(registry3)) {
30959
+ function registerCliToolsIntoCouncilCatalog(registry4) {
30960
+ for (const entry of getCliToolCatalogEntries(registry4)) {
29829
30961
  try {
29830
30962
  registerCustomTool(entry);
29831
30963
  } catch {
@@ -30072,7 +31204,7 @@ var HARNESS_BUILTIN_NAMES;
30072
31204
  var init_toolRegistry = __esm({
30073
31205
  "src/cli/toolRegistry.ts"() {
30074
31206
  "use strict";
30075
- init_registry();
31207
+ init_registry2();
30076
31208
  init_filesystem();
30077
31209
  init_shell();
30078
31210
  init_search();
@@ -31425,7 +32557,7 @@ __export(composeContext_exports, {
31425
32557
  });
31426
32558
  import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync19 } from "node:fs";
31427
32559
  import { join as join20 } from "node:path";
31428
- function cap(text, max, label) {
32560
+ function cap2(text, max, label) {
31429
32561
  if (!text || text.length <= max) return { text: text || "", truncated: false };
31430
32562
  return {
31431
32563
  text: text.slice(0, max) + `
@@ -31469,7 +32601,7 @@ function buildDesignIndex(projectRoot, maxChars) {
31469
32601
  }
31470
32602
  }
31471
32603
  const raw = lines.join("\n");
31472
- return cap(raw, maxChars, "design-index").text;
32604
+ return cap2(raw, maxChars, "design-index").text;
31473
32605
  }
31474
32606
  function composeProjectContext(input) {
31475
32607
  const cwd = input.cwd ?? process.cwd();
@@ -31523,7 +32655,7 @@ function composeProjectContext(input) {
31523
32655
  default: 12e3,
31524
32656
  min: 2e3
31525
32657
  });
31526
- const totalCapped = cap(workspaceContext, totalCap, "workspaceContext");
32658
+ const totalCapped = cap2(workspaceContext, totalCap, "workspaceContext");
31527
32659
  workspaceContext = totalCapped.text;
31528
32660
  if (totalCapped.truncated) {
31529
32661
  warnings.push(
@@ -31541,14 +32673,14 @@ function composeProjectContext(input) {
31541
32673
  }
31542
32674
  const ragParts = [];
31543
32675
  if (durableRaw) {
31544
- const d = cap(durableRaw, durableMax, "durable-state");
32676
+ const d = cap2(durableRaw, durableMax, "durable-state");
31545
32677
  ragParts.push(d.text);
31546
32678
  if (d.truncated) {
31547
32679
  warnings.push(`[context] durable state truncated to ${durableMax} chars.`);
31548
32680
  }
31549
32681
  }
31550
32682
  if (input.memoryHits?.trim()) {
31551
- const m = cap(input.memoryHits.trim(), memoryMax, "memory");
32683
+ const m = cap2(input.memoryHits.trim(), memoryMax, "memory");
31552
32684
  ragParts.push(m.text);
31553
32685
  if (m.truncated) warnings.push(`[context] memory RAG truncated to ${memoryMax} chars.`);
31554
32686
  }
@@ -31932,28 +33064,28 @@ var init_storage = __esm({
31932
33064
  VALID_SCALARS = /^(true|false|null|~)$/i;
31933
33065
  Storage = class {
31934
33066
  /** Read a Markdown file with frontmatter. Throws if not found. */
31935
- read(path44) {
31936
- if (!existsSync25(path44)) {
31937
- throw new Error(`File not found: ${path44}`);
33067
+ read(path47) {
33068
+ if (!existsSync25(path47)) {
33069
+ throw new Error(`File not found: ${path47}`);
31938
33070
  }
31939
- const md = readFileSync21(path44, "utf8");
33071
+ const md = readFileSync21(path47, "utf8");
31940
33072
  return parseFrontmatter(md);
31941
33073
  }
31942
33074
  /** Read a Markdown file; returns null if not found. */
31943
- readIfExists(path44) {
31944
- if (!existsSync25(path44)) return null;
31945
- return this.read(path44);
33075
+ readIfExists(path47) {
33076
+ if (!existsSync25(path47)) return null;
33077
+ return this.read(path47);
31946
33078
  }
31947
33079
  /**
31948
33080
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
31949
33081
  * The meta object is serialized as YAML frontmatter; body as Markdown.
31950
33082
  */
31951
- write(path44, meta3, body) {
31952
- mkdirSync11(dirname4(path44), { recursive: true });
31953
- const tmp = path44 + ".tmp-" + process.pid;
33083
+ write(path47, meta3, body) {
33084
+ mkdirSync11(dirname4(path47), { recursive: true });
33085
+ const tmp = path47 + ".tmp-" + process.pid;
31954
33086
  const md = serializeFrontmatter(meta3, body);
31955
33087
  writeFileSync13(tmp, md, "utf8");
31956
- renameSync2(tmp, path44);
33088
+ renameSync2(tmp, path47);
31957
33089
  }
31958
33090
  /** List all .md files in a directory (non-recursive). */
31959
33091
  listMarkdown(dir) {
@@ -32031,8 +33163,8 @@ function readPlan(ctx) {
32031
33163
  } catch {
32032
33164
  }
32033
33165
  }
32034
- const path44 = workspaceFile(ctx.rootDir, "plan");
32035
- const doc = ctx.storage.readIfExists(path44);
33166
+ const path47 = workspaceFile(ctx.rootDir, "plan");
33167
+ const doc = ctx.storage.readIfExists(path47);
32036
33168
  if (!doc) return { phases: [], tasks: [], milestones: [] };
32037
33169
  const meta3 = doc.meta;
32038
33170
  return {
@@ -32197,7 +33329,7 @@ function addMilestoneRecord(ctx, summary, input) {
32197
33329
  dueDate: input.dueDate,
32198
33330
  targetVersion: version2
32199
33331
  });
32200
- const path44 = join23(ctx.rootDir, "milestones", `${id}.md`);
33332
+ const path47 = join23(ctx.rootDir, "milestones", `${id}.md`);
32201
33333
  const meta3 = {
32202
33334
  kind: "milestone",
32203
33335
  id,
@@ -32214,7 +33346,7 @@ function addMilestoneRecord(ctx, summary, input) {
32214
33346
  `Target version: ${version2}`,
32215
33347
  ""
32216
33348
  ].join("\n");
32217
- ctx.storage.write(path44, meta3, body);
33349
+ ctx.storage.write(path47, meta3, body);
32218
33350
  return { id, created: true };
32219
33351
  }
32220
33352
  function readPlanSummary(ctx) {
@@ -32418,7 +33550,7 @@ function addIdeaStub(ctx) {
32418
33550
  const tags = args["tags"] ?? [];
32419
33551
  const category = args["category"] ?? "General";
32420
33552
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
32421
- const path44 = workspaceArtifact(ctx.rootDir, "decisions", id);
33553
+ const path47 = workspaceArtifact(ctx.rootDir, "decisions", id);
32422
33554
  const meta3 = {
32423
33555
  kind: "adr",
32424
33556
  status: "proposed",
@@ -32444,7 +33576,7 @@ function addIdeaStub(ctx) {
32444
33576
  ...consequences.map((c) => `- ${c}`),
32445
33577
  ""
32446
33578
  ].join("\n");
32447
- ctx.storage.write(path44, meta3, body);
33579
+ ctx.storage.write(path47, meta3, body);
32448
33580
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
32449
33581
  });
32450
33582
  }
@@ -32526,14 +33658,14 @@ function createDocumentStub(ctx) {
32526
33658
  ctx.storage.write(risksPath, riskMeta, content);
32527
33659
  return `Document "${title}" created at risks.md (workspace root).`;
32528
33660
  }
32529
- const path44 = workspaceArtifact(ctx.rootDir, "docs", slug);
33661
+ const path47 = workspaceArtifact(ctx.rootDir, "docs", slug);
32530
33662
  const meta3 = {
32531
33663
  kind: "doc",
32532
33664
  id: slug,
32533
33665
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
32534
33666
  tags
32535
33667
  };
32536
- ctx.storage.write(path44, meta3, content);
33668
+ ctx.storage.write(path47, meta3, content);
32537
33669
  return `Document "${title}" created at docs/${slug}.md.`;
32538
33670
  });
32539
33671
  }
@@ -32694,12 +33826,12 @@ __export(toolRegistry_exports2, {
32694
33826
  });
32695
33827
  function createWorkspaceToolRegistry(ctx) {
32696
33828
  const stubs = createWorkspaceStubs(ctx);
32697
- const registry3 = new ToolRegistry();
33829
+ const registry4 = new ToolRegistry();
32698
33830
  for (const stub of stubs) {
32699
33831
  const td = adaptStubToToolDefinition(stub, ctx);
32700
- registry3.register(td);
33832
+ registry4.register(td);
32701
33833
  }
32702
- return registry3;
33834
+ return registry4;
32703
33835
  }
32704
33836
  function adaptStubToToolDefinition(stub, workspaceCtx) {
32705
33837
  return {
@@ -32731,7 +33863,7 @@ var init_toolRegistry2 = __esm({
32731
33863
  "src/cli/workspace/toolRegistry.ts"() {
32732
33864
  "use strict";
32733
33865
  init_zod();
32734
- init_registry();
33866
+ init_registry2();
32735
33867
  init_toolTypes();
32736
33868
  init_stubs();
32737
33869
  }
@@ -33077,10 +34209,10 @@ function getUserMcpPath() {
33077
34209
  function getProjectMcpPath(projectRoot) {
33078
34210
  return join24(projectRoot, ".zelari", "mcp.json");
33079
34211
  }
33080
- function readFile2(path44) {
33081
- if (!existsSync28(path44)) return {};
34212
+ function readFile2(path47) {
34213
+ if (!existsSync28(path47)) return {};
33082
34214
  try {
33083
- const parsed = JSON.parse(readFileSync23(path44, "utf8"));
34215
+ const parsed = JSON.parse(readFileSync23(path47, "utf8"));
33084
34216
  const out = {};
33085
34217
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
33086
34218
  if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
@@ -33096,10 +34228,10 @@ function readFile2(path44) {
33096
34228
  return {};
33097
34229
  }
33098
34230
  }
33099
- function writeFile(path44, servers) {
33100
- mkdirSync13(dirname6(path44), { recursive: true });
34231
+ function writeFile(path47, servers) {
34232
+ mkdirSync13(dirname6(path47), { recursive: true });
33101
34233
  const body = { mcpServers: servers };
33102
- writeFileSync15(path44, `${JSON.stringify(body, null, 2)}
34234
+ writeFileSync15(path47, `${JSON.stringify(body, null, 2)}
33103
34235
  `, "utf8");
33104
34236
  }
33105
34237
  function listMcpServers(projectRoot) {
@@ -33132,9 +34264,9 @@ function upsertMcpServer(opts) {
33132
34264
  if (!opts.config.command?.trim()) {
33133
34265
  return { ok: false, error: "command is required" };
33134
34266
  }
33135
- let path44;
34267
+ let path47;
33136
34268
  if (opts.scope === "user") {
33137
- path44 = getUserMcpPath();
34269
+ path47 = getUserMcpPath();
33138
34270
  } else {
33139
34271
  const root = opts.projectRoot?.trim();
33140
34272
  if (!root) {
@@ -33143,30 +34275,30 @@ function upsertMcpServer(opts) {
33143
34275
  error: "projectRoot required for project scope (Open Folder first)"
33144
34276
  };
33145
34277
  }
33146
- path44 = getProjectMcpPath(root);
34278
+ path47 = getProjectMcpPath(root);
33147
34279
  }
33148
- const current = readFile2(path44);
34280
+ const current = readFile2(path47);
33149
34281
  current[name] = {
33150
34282
  command: opts.config.command.trim(),
33151
34283
  args: opts.config.args,
33152
34284
  env: opts.config.env,
33153
34285
  enabled: opts.config.enabled !== false
33154
34286
  };
33155
- writeFile(path44, current);
33156
- return { ok: true, path: path44 };
34287
+ writeFile(path47, current);
34288
+ return { ok: true, path: path47 };
33157
34289
  }
33158
34290
  function removeMcpServer(opts) {
33159
- const path44 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
33160
- if (!path44) {
34291
+ const path47 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
34292
+ if (!path47) {
33161
34293
  return { ok: false, error: "projectRoot required for project scope" };
33162
34294
  }
33163
- const current = readFile2(path44);
34295
+ const current = readFile2(path47);
33164
34296
  if (!(opts.name in current)) {
33165
- return { ok: false, error: `Server "${opts.name}" not found in ${path44}` };
34297
+ return { ok: false, error: `Server "${opts.name}" not found in ${path47}` };
33166
34298
  }
33167
34299
  delete current[opts.name];
33168
- writeFile(path44, current);
33169
- return { ok: true, path: path44 };
34300
+ writeFile(path47, current);
34301
+ return { ok: true, path: path47 };
33170
34302
  }
33171
34303
  var init_mcpConfigIo = __esm({
33172
34304
  "src/cli/mcp/mcpConfigIo.ts"() {
@@ -33310,7 +34442,7 @@ async function ensureLoaded(projectRoot) {
33310
34442
  function sanitizeToolName(raw) {
33311
34443
  return raw.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
33312
34444
  }
33313
- async function registerMcpTools(registry3, projectRoot = process.cwd(), opts) {
34445
+ async function registerMcpTools(registry4, projectRoot = process.cwd(), opts) {
33314
34446
  if (process.env["ZELARI_MCP"] === "0") return { registered: [], warnings: [] };
33315
34447
  await ensureLoaded(projectRoot);
33316
34448
  const skipCuaForCouncil = opts?.councilMode === true && !isCuaAllowedForCouncil();
@@ -33319,7 +34451,7 @@ async function registerMcpTools(registry3, projectRoot = process.cwd(), opts) {
33319
34451
  if (skipCuaForCouncil && isCuaMcpServerName(t.serverName)) {
33320
34452
  continue;
33321
34453
  }
33322
- registry3.register({
34454
+ registry4.register({
33323
34455
  name: t.registryName,
33324
34456
  description: `[MCP:${t.serverName}] ${t.info.description}`.slice(0, 1024),
33325
34457
  // The MCP server owns validation; its JSON Schema is forwarded to the
@@ -33503,10 +34635,10 @@ import { createHash as createHash5 } from "node:crypto";
33503
34635
  import { join as join26 } from "node:path";
33504
34636
  import { readFile as readFile3 } from "node:fs/promises";
33505
34637
  async function readPackageJson2(projectRoot) {
33506
- const path44 = join26(projectRoot, "package.json");
33507
- if (!existsSync30(path44)) return null;
34638
+ const path47 = join26(projectRoot, "package.json");
34639
+ if (!existsSync30(path47)) return null;
33508
34640
  try {
33509
- return JSON.parse(await readFile3(path44, "utf8"));
34641
+ return JSON.parse(await readFile3(path47, "utf8"));
33510
34642
  } catch {
33511
34643
  return null;
33512
34644
  }
@@ -33588,9 +34720,9 @@ async function genBuild(ctx) {
33588
34720
  ].join("\n");
33589
34721
  }
33590
34722
  async function genOpenQuestions(ctx) {
33591
- const path44 = join26(ctx.rootDir, "risks.md");
33592
- if (!existsSync30(path44)) return "_No open questions._";
33593
- 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");
33594
34726
  const lines = content.split("\n");
33595
34727
  const questions = [];
33596
34728
  let currentTitle = "";
@@ -34121,9 +35253,9 @@ async function runPostCouncilHook(ctx, options) {
34121
35253
  lessons = { ran: true, captured: 0, rejected: 0 };
34122
35254
  for (const r of verification.report.results) {
34123
35255
  if (r.ok) continue;
34124
- const cap2 = captureFailure(ctx.rootDir, r);
34125
- if (cap2.rejected) lessons.rejected++;
34126
- 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++;
34127
35259
  }
34128
35260
  } else if (process.env["ZELARI_LESSONS"] === "0") {
34129
35261
  lessons = {
@@ -34157,8 +35289,8 @@ async function runPostCouncilHook(ctx, options) {
34157
35289
  sources: scope.sources
34158
35290
  } : void 0
34159
35291
  });
34160
- const path44 = writeCouncilCompletion(ctx.rootDir, completion);
34161
- completionHook = { ran: true, path: path44, completion };
35292
+ const path47 = writeCouncilCompletion(ctx.rootDir, completion);
35293
+ completionHook = { ran: true, path: path47, completion };
34162
35294
  } catch (err) {
34163
35295
  completionHook = {
34164
35296
  ran: true,
@@ -35376,6 +36508,7 @@ __export(planner_exports, {
35376
36508
  KRAKEN_PLANNER_SYSTEM_PROMPT: () => KRAKEN_PLANNER_SYSTEM_PROMPT,
35377
36509
  PlannerTransportError: () => PlannerTransportError,
35378
36510
  buildGraphFromPlan: () => buildGraphFromPlan,
36511
+ buildPlannerSystemPrompt: () => buildPlannerSystemPrompt,
35379
36512
  buildPlannerUserPrompt: () => buildPlannerUserPrompt,
35380
36513
  extractJsonObject: () => extractJsonObject,
35381
36514
  planTaskGraph: () => planTaskGraph,
@@ -35399,6 +36532,15 @@ function resolvePlannerMaxTokens(env = process.env) {
35399
36532
  const n = Number.parseInt(raw, 10);
35400
36533
  return Number.isFinite(n) && n > 0 ? n : DEFAULT_LLM_MAX_TOKENS;
35401
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
+ }
35402
36544
  function extractJsonObject(text, opts = {}) {
35403
36545
  const stripped = stripReasoningBlocks(text);
35404
36546
  const usable = findJsonIn(stripped, opts);
@@ -35824,7 +36966,7 @@ async function planTaskGraph(opts) {
35824
36966
  let userMessage = userBase;
35825
36967
  for (let attempt = 1; attempt <= MAX_PLAN_ATTEMPTS; attempt++) {
35826
36968
  try {
35827
- const text = await client.complete({ system: KRAKEN_PLANNER_SYSTEM_PROMPT, user: userMessage });
36969
+ const text = await client.complete({ system: buildPlannerSystemPrompt(), user: userMessage });
35828
36970
  const parsedJson = extractJsonObject(text, { requireKey: "nodes" });
35829
36971
  const validated = PlannedGraphSchema.parse(parsedJson);
35830
36972
  if (!validated.nodes.some((n) => n.kind === "general")) {
@@ -35850,7 +36992,7 @@ Your previous response was invalid (${lastError}). Return ONLY corrected JSON ma
35850
36992
  `kraken planner: failed to produce a valid task graph after ${MAX_PLAN_ATTEMPTS} attempts \u2014 ${lastError}`
35851
36993
  );
35852
36994
  }
35853
- var MAX_PLAN_ATTEMPTS, DEFAULT_PLANNER_WORKSPACE_CHARS, DEFAULT_LLM_TIMEOUT_MS, PlannerTransportError, DEFAULT_LLM_MAX_TOKENS, DEFAULT_MAX_RETRIES, KRAKEN_PLANNER_SYSTEM_PROMPT, PlannedNodeSchema, PlannedGraphSchema, MAX_VERIFY_TASK_PROMPT_CHARS;
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;
35854
36996
  var init_planner = __esm({
35855
36997
  "src/cli/kraken/planner.ts"() {
35856
36998
  "use strict";
@@ -35875,7 +37017,11 @@ var init_planner = __esm({
35875
37017
  general: 1,
35876
37018
  verify: 1,
35877
37019
  fix: 0,
35878
- 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
35879
37025
  };
35880
37026
  KRAKEN_PLANNER_SYSTEM_PROMPT = [
35881
37027
  "You are the PLANNER for Kraken, a multi-agent graph executor.",
@@ -35887,7 +37033,7 @@ var init_planner = __esm({
35887
37033
  "Rules:",
35888
37034
  '- kind "explore": read-only research (no edits). Use to gather context before edits.',
35889
37035
  '- kind "general": can edit files for one bounded, self-contained unit of work.',
35890
- '- 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.',
35891
37037
  '- "id" must be short, unique, kebab-case (e.g. "e1", "g-auth", "g-ui").',
35892
37038
  '- "prompt" must be self-contained: the sub-agent sees ONLY this prompt, not this conversation.',
35893
37039
  '- "deps" lists ids of nodes that must finish first (topological order); [] if none.',
@@ -35896,7 +37042,36 @@ var init_planner = __esm({
35896
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.',
35897
37043
  '- Prefer one "explore" node feeding several parallel "general" nodes over one giant node.',
35898
37044
  "- Keep the graph small: most goals need 3-8 nodes total.",
35899
- '- "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.'
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."
35900
37075
  ].join("\n");
35901
37076
  PlannedNodeSchema = external_exports.object({
35902
37077
  id: external_exports.string().min(1).max(64).regex(/^[a-zA-Z0-9_-]+$/, "id must be alphanumeric/dash/underscore"),
@@ -36033,6 +37208,126 @@ var init_tentacle = __esm({
36033
37208
  }
36034
37209
  });
36035
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
+
36036
37331
  // src/cli/kraken/executor.ts
36037
37332
  var executor_exports = {};
36038
37333
  __export(executor_exports, {
@@ -36130,14 +37425,14 @@ function buildUpstreamContext(graph, node) {
36130
37425
  if (!dep || dep.status !== "done") continue;
36131
37426
  const raw = (dep.result ?? "").trim();
36132
37427
  if (!raw) continue;
36133
- const cap2 = Math.min(MAX_UPSTREAM_CHARS_PER_DEP, budget);
36134
- if (cap2 <= 0) {
37428
+ const cap3 = Math.min(MAX_UPSTREAM_CHARS_PER_DEP, budget);
37429
+ if (cap3 <= 0) {
36135
37430
  omitted.push(dep.label);
36136
37431
  continue;
36137
37432
  }
36138
- const body = raw.length > cap2 ? `${raw.slice(0, cap2)}
36139
- \u2026 [truncated ${raw.length}\u2192${cap2} chars]` : raw;
36140
- budget -= Math.min(raw.length, cap2);
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);
36141
37436
  const scope = dep.scope && dep.scope.length > 0 ? `, scope: ${dep.scope.join(", ")}` : "";
36142
37437
  parts.push(`### ${dep.label} (${dep.kind}${scope})
36143
37438
  ${body}`);
@@ -36443,7 +37738,7 @@ var init_executor = __esm({
36443
37738
  }
36444
37739
  const isRework = this.reworks.has(node.id);
36445
37740
  const usesWorktree = (node.kind === "general" || node.kind === "fix") && !isRework;
36446
- const agent = node.kind === "fix" ? "general" : node.kind;
37741
+ const agent = node.kind === "fix" ? "general" : node.kind === "spec" || node.kind === "conformance" ? "verify" : node.kind;
36447
37742
  const controller = new AbortController();
36448
37743
  this.nodeControllers.set(node.id, controller);
36449
37744
  if (this.aborted) controller.abort();
@@ -36745,6 +38040,7 @@ ${upstream}` : node.prompt,
36745
38040
  if (verdict === "pass") return;
36746
38041
  const writer = this.writerBehind(verify, graph);
36747
38042
  if (!writer) return;
38043
+ void this.maybeRunWeaknessMeter(verify, verdict);
36748
38044
  if (verdict === "unknown") {
36749
38045
  this.unresolved.push({
36750
38046
  nodeId: writer.id,
@@ -36939,6 +38235,41 @@ ${failed.error ?? "unknown error"}`,
36939
38235
  ...fields.ok !== void 0 ? { ok: fields.ok } : {}
36940
38236
  });
36941
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
+ }
36942
38273
  };
36943
38274
  }
36944
38275
  });
@@ -37327,10 +38658,10 @@ var init_prereqChecks = __esm({
37327
38658
 
37328
38659
  // src/cli/plugins/prefs.ts
37329
38660
  import { existsSync as existsSync36, readFileSync as readFileSync29, writeFileSync as writeFileSync18, mkdirSync as mkdirSync15 } from "node:fs";
37330
- import path36 from "node:path";
38661
+ import path38 from "node:path";
37331
38662
  import os9 from "node:os";
37332
38663
  function getPluginPrefsPath() {
37333
- 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");
37334
38665
  }
37335
38666
  function getPluginPrefs() {
37336
38667
  const file2 = getPluginPrefsPath();
@@ -37351,7 +38682,7 @@ function getPluginPrefs() {
37351
38682
  }
37352
38683
  function writePluginPrefs(prefs) {
37353
38684
  const file2 = getPluginPrefsPath();
37354
- mkdirSync15(path36.dirname(file2), { recursive: true });
38685
+ mkdirSync15(path38.dirname(file2), { recursive: true });
37355
38686
  writeFileSync18(file2, JSON.stringify(prefs, null, 2), {
37356
38687
  encoding: "utf-8",
37357
38688
  mode: 384
@@ -37388,7 +38719,7 @@ __export(registry_exports, {
37388
38719
  isBinaryOnPath: () => isBinaryOnPath
37389
38720
  });
37390
38721
  import { existsSync as existsSync37 } from "node:fs";
37391
- import path37 from "node:path";
38722
+ import path39 from "node:path";
37392
38723
  function detectLocalBin(bin) {
37393
38724
  return (cwd) => {
37394
38725
  try {
@@ -37406,7 +38737,7 @@ function isBinaryOnPath(bin, opts = {}) {
37406
38737
  const platform = opts.platform ?? process.platform;
37407
38738
  const exists = opts.exists ?? existsSync37;
37408
38739
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
37409
- const pathMod = platform === "win32" ? path37.win32 : path37.posix;
38740
+ const pathMod = platform === "win32" ? path39.win32 : path39.posix;
37410
38741
  const sep2 = platform === "win32" ? ";" : ":";
37411
38742
  const dirs = pathEnv.split(sep2).filter((d) => d.length > 0);
37412
38743
  const candidates = [bin];
@@ -37475,7 +38806,7 @@ function findPlugin(id) {
37475
38806
  return PLUGINS.find((p3) => p3.id === id);
37476
38807
  }
37477
38808
  var PLUGINS;
37478
- var init_registry2 = __esm({
38809
+ var init_registry3 = __esm({
37479
38810
  "src/cli/plugins/registry.ts"() {
37480
38811
  "use strict";
37481
38812
  init_engine();
@@ -37544,7 +38875,7 @@ __export(atMentions_exports, {
37544
38875
  hasAtMentions: () => hasAtMentions
37545
38876
  });
37546
38877
  import { existsSync as existsSync40, readFileSync as readFileSync31, statSync as statSync6 } from "node:fs";
37547
- import { isAbsolute, relative as relative3, resolve, sep } from "node:path";
38878
+ import { isAbsolute as isAbsolute2, relative as relative3, resolve, sep } from "node:path";
37548
38879
  function isProbablyText(name, head) {
37549
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(
37550
38881
  name
@@ -37575,8 +38906,8 @@ function extractAtMentions(text) {
37575
38906
  return out;
37576
38907
  }
37577
38908
  function resolveMention(token, cwd) {
37578
- const abs = isAbsolute(token) ? resolve(token) : resolve(cwd, token);
37579
- if (!underRoot(abs, cwd) && !isAbsolute(token)) {
38909
+ const abs = isAbsolute2(token) ? resolve(token) : resolve(cwd, token);
38910
+ if (!underRoot(abs, cwd) && !isAbsolute2(token)) {
37580
38911
  if (!underRoot(abs, cwd)) {
37581
38912
  return {
37582
38913
  raw: token,
@@ -37587,7 +38918,7 @@ function resolveMention(token, cwd) {
37587
38918
  };
37588
38919
  }
37589
38920
  }
37590
- if (isAbsolute(token) && !underRoot(abs, cwd)) {
38921
+ if (isAbsolute2(token) && !underRoot(abs, cwd)) {
37591
38922
  return {
37592
38923
  raw: token,
37593
38924
  path: token,
@@ -37715,10 +39046,10 @@ __export(triggerLock_exports, {
37715
39046
  lockPath: () => lockPath,
37716
39047
  releaseLock: () => releaseLock
37717
39048
  });
37718
- import { promises as fs24 } from "node:fs";
37719
- import * as path42 from "node:path";
39049
+ import { promises as fs27 } from "node:fs";
39050
+ import * as path44 from "node:path";
37720
39051
  function lockPath(projectRoot) {
37721
- return path42.join(projectRoot, ".zelari", "trigger.lock");
39052
+ return path44.join(projectRoot, ".zelari", "trigger.lock");
37722
39053
  }
37723
39054
  function isPidAlive(pid) {
37724
39055
  try {
@@ -37731,10 +39062,10 @@ function isPidAlive(pid) {
37731
39062
  }
37732
39063
  async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
37733
39064
  const lp = lockPath(projectRoot);
37734
- const dir = path42.dirname(lp);
37735
- await fs24.mkdir(dir, { recursive: true });
39065
+ const dir = path44.dirname(lp);
39066
+ await fs27.mkdir(dir, { recursive: true });
37736
39067
  try {
37737
- const raw = await fs24.readFile(lp, "utf8");
39068
+ const raw = await fs27.readFile(lp, "utf8");
37738
39069
  const existing = JSON.parse(raw);
37739
39070
  if (existing.pid && isPidAlive(existing.pid)) {
37740
39071
  return { acquired: false, heldBy: existing.pid, lockPath: lp };
@@ -37745,13 +39076,13 @@ async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date())
37745
39076
  pid: process.pid,
37746
39077
  acquiredAt: now().toISOString()
37747
39078
  };
37748
- await fs24.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
39079
+ await fs27.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
37749
39080
  return { acquired: true, lockPath: lp };
37750
39081
  }
37751
39082
  async function releaseLock(projectRoot) {
37752
39083
  const lp = lockPath(projectRoot);
37753
39084
  try {
37754
- await fs24.unlink(lp);
39085
+ await fs27.unlink(lp);
37755
39086
  } catch {
37756
39087
  }
37757
39088
  }
@@ -38022,7 +39353,7 @@ import {
38022
39353
  } from "node:fs";
38023
39354
  import { join as join35 } from "node:path";
38024
39355
  import { homedir as homedir11 } from "node:os";
38025
- import { createHash as createHash6, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
39356
+ import { createHash as createHash6, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
38026
39357
  function getZelariHome() {
38027
39358
  return join35(homedir11(), ".zelari-code");
38028
39359
  }
@@ -38039,12 +39370,12 @@ function ensureHome() {
38039
39370
  }
38040
39371
  }
38041
39372
  function loadCompanionConfig() {
38042
- const path44 = getCompanionConfigPath();
38043
- if (!existsSync43(path44)) {
39373
+ const path47 = getCompanionConfigPath();
39374
+ if (!existsSync43(path47)) {
38044
39375
  return { projects: [] };
38045
39376
  }
38046
39377
  try {
38047
- const raw = JSON.parse(readFileSync34(path44, "utf8"));
39378
+ const raw = JSON.parse(readFileSync34(path47, "utf8"));
38048
39379
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
38049
39380
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
38050
39381
  ).map((p3) => ({
@@ -38082,16 +39413,16 @@ function loadOrCreateToken(explicit) {
38082
39413
  return { token: explicit.trim(), created: false };
38083
39414
  }
38084
39415
  ensureHome();
38085
- const path44 = getCompanionTokenPath();
38086
- if (existsSync43(path44)) {
38087
- const t = readFileSync34(path44, "utf8").trim();
39416
+ const path47 = getCompanionTokenPath();
39417
+ if (existsSync43(path47)) {
39418
+ const t = readFileSync34(path47, "utf8").trim();
38088
39419
  if (t) return { token: t, created: false };
38089
39420
  }
38090
- const token = randomBytes3(24).toString("base64url");
38091
- writeFileSync21(path44, token + "\n", "utf8");
39421
+ const token = randomBytes4(24).toString("base64url");
39422
+ writeFileSync21(path47, token + "\n", "utf8");
38092
39423
  try {
38093
- const fs25 = __require("node:fs");
38094
- fs25.chmodSync?.(path44, 384);
39424
+ const fs29 = __require("node:fs");
39425
+ fs29.chmodSync?.(path47, 384);
38095
39426
  } catch {
38096
39427
  }
38097
39428
  return { token, created: true };
@@ -38116,17 +39447,17 @@ function mergeProjects(cfg, extraPaths) {
38116
39447
  byId.set(p3.id, p3);
38117
39448
  }
38118
39449
  for (const raw of extraPaths) {
38119
- const path44 = raw.trim();
38120
- if (!path44) continue;
38121
- let id = slugFromPath(path44);
39450
+ const path47 = raw.trim();
39451
+ if (!path47) continue;
39452
+ let id = slugFromPath(path47);
38122
39453
  let n = 2;
38123
- while (byId.has(id) && byId.get(id).path !== path44) {
38124
- id = `${slugFromPath(path44)}-${n++}`;
39454
+ while (byId.has(id) && byId.get(id).path !== path47) {
39455
+ id = `${slugFromPath(path47)}-${n++}`;
38125
39456
  }
38126
39457
  byId.set(id, {
38127
39458
  id,
38128
- name: slugFromPath(path44),
38129
- path: path44
39459
+ name: slugFromPath(path47),
39460
+ path: path47
38130
39461
  });
38131
39462
  }
38132
39463
  return [...byId.values()];
@@ -38176,7 +39507,7 @@ var init_config = __esm({
38176
39507
  // src/cli/companion/runManager.ts
38177
39508
  import { spawn as spawn11 } from "node:child_process";
38178
39509
  import { createInterface } from "node:readline";
38179
- import { randomUUID as randomUUID6 } from "node:crypto";
39510
+ import { randomUUID as randomUUID7 } from "node:crypto";
38180
39511
  import { writeFileSync as writeFileSync22, unlinkSync as unlinkSync2 } from "node:fs";
38181
39512
  import { join as join36 } from "node:path";
38182
39513
  import { tmpdir as tmpdir3 } from "node:os";
@@ -38245,7 +39576,7 @@ var init_runManager = __esm({
38245
39576
  }
38246
39577
  const prompt = args.prompt?.trim();
38247
39578
  if (!prompt) return { ok: false, error: "prompt is required" };
38248
- const id = randomUUID6();
39579
+ const id = randomUUID7();
38249
39580
  const mode = (args.mode || "kraken").toLowerCase();
38250
39581
  const phase2 = (args.phase || "build").toLowerCase();
38251
39582
  const run = {
@@ -38503,9 +39834,9 @@ async function runCompanionServe(opts = {}) {
38503
39834
  return;
38504
39835
  }
38505
39836
  const url2 = parseUrl(req);
38506
- const path44 = url2.pathname.replace(/\/+$/, "") || "/";
39837
+ const path47 = url2.pathname.replace(/\/+$/, "") || "/";
38507
39838
  try {
38508
- if (req.method === "GET" && (path44 === "/health" || path44 === "/v1/health")) {
39839
+ if (req.method === "GET" && (path47 === "/health" || path47 === "/v1/health")) {
38509
39840
  sendJson(res, 200, {
38510
39841
  ok: true,
38511
39842
  service: "zelari-companion",
@@ -38517,18 +39848,18 @@ async function runCompanionServe(opts = {}) {
38517
39848
  });
38518
39849
  return;
38519
39850
  }
38520
- if (path44.startsWith("/v1")) {
39851
+ if (path47.startsWith("/v1")) {
38521
39852
  if (!tokenMatches(token, getBearer(req))) {
38522
39853
  sendJson(res, 401, { ok: false, error: "unauthorized" });
38523
39854
  return;
38524
39855
  }
38525
39856
  }
38526
- if (req.method === "GET" && path44 === "/v1/config") {
39857
+ if (req.method === "GET" && path47 === "/v1/config") {
38527
39858
  const snap = buildDesktopConfigSnapshot();
38528
39859
  sendJson(res, 200, { ok: true, ...snap });
38529
39860
  return;
38530
39861
  }
38531
- if (req.method === "GET" && path44 === "/v1/projects") {
39862
+ if (req.method === "GET" && path47 === "/v1/projects") {
38532
39863
  sendJson(res, 200, {
38533
39864
  ok: true,
38534
39865
  projects: projects.map((p3) => ({
@@ -38539,7 +39870,7 @@ async function runCompanionServe(opts = {}) {
38539
39870
  });
38540
39871
  return;
38541
39872
  }
38542
- if (req.method === "GET" && path44 === "/v1/runs") {
39873
+ if (req.method === "GET" && path47 === "/v1/runs") {
38543
39874
  sendJson(res, 200, {
38544
39875
  ok: true,
38545
39876
  active: runs.getActive(),
@@ -38557,7 +39888,7 @@ async function runCompanionServe(opts = {}) {
38557
39888
  });
38558
39889
  return;
38559
39890
  }
38560
- if (req.method === "POST" && path44 === "/v1/runs") {
39891
+ if (req.method === "POST" && path47 === "/v1/runs") {
38561
39892
  const raw = await readBody(req);
38562
39893
  let body = {};
38563
39894
  try {
@@ -38604,7 +39935,7 @@ async function runCompanionServe(opts = {}) {
38604
39935
  });
38605
39936
  return;
38606
39937
  }
38607
- const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path44);
39938
+ const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path47);
38608
39939
  if (req.method === "GET" && eventsMatch) {
38609
39940
  const runId = eventsMatch[1];
38610
39941
  const run = runs.getRun(runId);
@@ -38669,7 +40000,7 @@ async function runCompanionServe(opts = {}) {
38669
40000
  }, 500);
38670
40001
  return;
38671
40002
  }
38672
- const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path44);
40003
+ const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path47);
38673
40004
  if (req.method === "POST" && cancelMatch) {
38674
40005
  const runId = cancelMatch[1];
38675
40006
  const result = runs.cancel(runId);
@@ -38781,11 +40112,11 @@ import { execSync as execSync2 } from "node:child_process";
38781
40112
  import { existsSync as existsSync45, readFileSync as readFileSync35, readlinkSync, statSync as statSync7 } from "node:fs";
38782
40113
  import { createRequire as createRequire3 } from "node:module";
38783
40114
  import { fileURLToPath as fileURLToPath2 } from "node:url";
38784
- import path43 from "node:path";
40115
+ import path46 from "node:path";
38785
40116
  function findPackageRoot(start) {
38786
40117
  let dir = start;
38787
40118
  for (let i = 0; i < 6; i += 1) {
38788
- const candidate = path43.join(dir, "package.json");
40119
+ const candidate = path46.join(dir, "package.json");
38789
40120
  if (existsSync45(candidate)) {
38790
40121
  try {
38791
40122
  const pkg = JSON.parse(readFileSync35(candidate, "utf8"));
@@ -38793,11 +40124,11 @@ function findPackageRoot(start) {
38793
40124
  } catch {
38794
40125
  }
38795
40126
  }
38796
- const parent = path43.dirname(dir);
40127
+ const parent = path46.dirname(dir);
38797
40128
  if (parent === dir) break;
38798
40129
  dir = parent;
38799
40130
  }
38800
- return path43.resolve(__dirname3, "..", "..", "..");
40131
+ return path46.resolve(__dirname3, "..", "..", "..");
38801
40132
  }
38802
40133
  function tryExec(cmd) {
38803
40134
  try {
@@ -38811,7 +40142,7 @@ function tryExec(cmd) {
38811
40142
  }
38812
40143
  function readPackageJson3() {
38813
40144
  try {
38814
- const pkgPath = path43.join(packageRoot, "package.json");
40145
+ const pkgPath = path46.join(packageRoot, "package.json");
38815
40146
  return JSON.parse(readFileSync35(pkgPath, "utf8"));
38816
40147
  } catch {
38817
40148
  return null;
@@ -38827,7 +40158,7 @@ function checkShim(pkgName) {
38827
40158
  }
38828
40159
  const isWin = process.platform === "win32";
38829
40160
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
38830
- const shimPath = path43.join(prefix, shimName);
40161
+ const shimPath = path46.join(prefix, shimName);
38831
40162
  if (!existsSync45(shimPath)) {
38832
40163
  return FAIL(
38833
40164
  `shim not found at ${shimPath}
@@ -38855,8 +40186,8 @@ function checkShim(pkgName) {
38855
40186
  fix: npm install -g ${pkgName}@latest --force`
38856
40187
  );
38857
40188
  }
38858
- const resolved = path43.resolve(path43.dirname(shimPath), target);
38859
- const expected = path43.join(
40189
+ const resolved = path46.resolve(path46.dirname(shimPath), target);
40190
+ const expected = path46.join(
38860
40191
  prefix,
38861
40192
  "node_modules",
38862
40193
  pkgName,
@@ -38895,7 +40226,7 @@ function checkNode(pkg) {
38895
40226
  return OK(`node ${raw}`);
38896
40227
  }
38897
40228
  function checkBundle() {
38898
- const bundle = path43.join(packageRoot, "dist", "cli", "main.bundled.js");
40229
+ const bundle = path46.join(packageRoot, "dist", "cli", "main.bundled.js");
38899
40230
  if (!existsSync45(bundle)) {
38900
40231
  return FAIL(
38901
40232
  `dist/cli/main.bundled.js missing at ${bundle}
@@ -38916,7 +40247,7 @@ function checkRuntimeDeps() {
38916
40247
  const missing = [];
38917
40248
  for (const dep of required2) {
38918
40249
  try {
38919
- const localReq = createRequire3(path43.join(packageRoot, "package.json"));
40250
+ const localReq = createRequire3(path46.join(packageRoot, "package.json"));
38920
40251
  localReq.resolve(dep);
38921
40252
  } catch {
38922
40253
  missing.push(dep);
@@ -38993,7 +40324,7 @@ function prereqToCheckResult(r) {
38993
40324
  return r.severity === "critical" ? FAIL(r.message, "critical") : WARN(r.message);
38994
40325
  }
38995
40326
  async function checkOptionalPlugins() {
38996
- const { detectMissingPlugins: detectMissingPlugins2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports));
40327
+ const { detectMissingPlugins: detectMissingPlugins2 } = await Promise.resolve().then(() => (init_registry3(), registry_exports));
38997
40328
  let missing;
38998
40329
  try {
38999
40330
  missing = await detectMissingPlugins2(packageRoot, { includeMuted: true });
@@ -39092,7 +40423,7 @@ var init_doctor = __esm({
39092
40423
  "use strict";
39093
40424
  init_prereqChecks();
39094
40425
  require3 = createRequire3(import.meta.url);
39095
- __dirname3 = path43.dirname(fileURLToPath2(import.meta.url));
40426
+ __dirname3 = path46.dirname(fileURLToPath2(import.meta.url));
39096
40427
  packageRoot = findPackageRoot(__dirname3);
39097
40428
  OK = (message) => ({
39098
40429
  ok: true,
@@ -39332,9 +40663,9 @@ function tryParseJson(s) {
39332
40663
  }
39333
40664
  }
39334
40665
  function truncateLines(lines) {
39335
- const cap2 = toolOutputLineCap();
39336
- if (lines.length <= cap2) return lines;
39337
- 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)`];
39338
40669
  }
39339
40670
  function formatBytes(n) {
39340
40671
  if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
@@ -39522,7 +40853,7 @@ function rel(p3) {
39522
40853
  }
39523
40854
  }
39524
40855
  function formatToolSummary(toolName, args, maxWidth) {
39525
- const cap2 = maxWidth && maxWidth > 10 ? maxWidth : 100;
40856
+ const cap3 = maxWidth && maxWidth > 10 ? maxWidth : 100;
39526
40857
  const lower = toolName.toLowerCase();
39527
40858
  const a = args && typeof args === "object" ? args : {};
39528
40859
  let summary;
@@ -39545,7 +40876,7 @@ function formatToolSummary(toolName, args, maxWidth) {
39545
40876
  const json2 = JSON.stringify(args) ?? "";
39546
40877
  summary = json2;
39547
40878
  }
39548
- return summary.length > cap2 ? `${summary.slice(0, cap2 - 1)}\u2026` : summary;
40879
+ return summary.length > cap3 ? `${summary.slice(0, cap3 - 1)}\u2026` : summary;
39549
40880
  }
39550
40881
 
39551
40882
  // src/cli/components/ToolOutput.tsx
@@ -44957,6 +46288,20 @@ ${formatSkillList(availableSkills)}`
44957
46288
  graphPrompt: args.slice(1).join(" ").trim()
44958
46289
  };
44959
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
+ }
44960
46305
  const sessionArg = args.join(" ").trim();
44961
46306
  return {
44962
46307
  handled: true,
@@ -45571,6 +46916,508 @@ ${digest}
45571
46916
  }
45572
46917
  }
45573
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
+
45574
47421
  // src/cli/compaction.ts
45575
47422
  function compactTranscript(messages, options = {}) {
45576
47423
  const threshold = options.threshold ?? 50;
@@ -45714,7 +47561,7 @@ ${output}`.toLowerCase();
45714
47561
  }
45715
47562
 
45716
47563
  // src/cli/slashHandlers/plugins.ts
45717
- init_registry2();
47564
+ init_registry3();
45718
47565
 
45719
47566
  // src/cli/plugins/installer.ts
45720
47567
  init_cmdline();
@@ -45868,17 +47715,17 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
45868
47715
  }
45869
47716
 
45870
47717
  // src/cli/slashHandlers/promoteMember.ts
45871
- import { promises as fs20 } from "node:fs";
45872
- import path38 from "node:path";
47718
+ import { promises as fs23 } from "node:fs";
47719
+ import path40 from "node:path";
45873
47720
  import os10 from "node:os";
45874
47721
  async function handlePromoteMember(ctx, memberId) {
45875
47722
  try {
45876
47723
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
45877
47724
  const { skill, markdown } = promoteMember2(memberId);
45878
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path38.join(os10.homedir(), ".tmp", "zelari-code", "skills");
45879
- await fs20.mkdir(skillDir, { recursive: true });
45880
- const filePath = path38.join(skillDir, `${skill.id}.md`);
45881
- 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");
45882
47729
  appendSystem(
45883
47730
  ctx.setMessages,
45884
47731
  `[promote-member] ${skill.name} (${memberId}) \u2192 ${filePath}
@@ -45894,25 +47741,25 @@ async function handlePromoteMember(ctx, memberId) {
45894
47741
  }
45895
47742
 
45896
47743
  // src/cli/branchManager.ts
45897
- 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";
45898
- 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";
45899
47746
  import os11 from "node:os";
45900
47747
  var META_FILENAME = "meta.json";
45901
47748
  var SESSIONS_SUBDIR = "sessions";
45902
47749
  function getBranchesBaseDir() {
45903
- 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");
45904
47751
  }
45905
47752
  function getSessionsBaseDir() {
45906
- 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");
45907
47754
  }
45908
47755
  function branchPathFor(name, baseDir) {
45909
- return path39.join(baseDir, name);
47756
+ return path41.join(baseDir, name);
45910
47757
  }
45911
47758
  function metaPathFor(name, baseDir) {
45912
- return path39.join(baseDir, name, META_FILENAME);
47759
+ return path41.join(baseDir, name, META_FILENAME);
45913
47760
  }
45914
47761
  function sessionsPathFor(name, baseDir) {
45915
- return path39.join(baseDir, name, SESSIONS_SUBDIR);
47762
+ return path41.join(baseDir, name, SESSIONS_SUBDIR);
45916
47763
  }
45917
47764
  function readBranchMeta(name, baseDir) {
45918
47765
  const metaPath = metaPathFor(name, baseDir);
@@ -45937,13 +47784,13 @@ function readBranchMeta(name, baseDir) {
45937
47784
  }
45938
47785
  function writeBranchMeta(name, baseDir, meta3) {
45939
47786
  const metaPath = metaPathFor(name, baseDir);
45940
- mkdirSync16(path39.dirname(metaPath), { recursive: true });
47787
+ mkdirSync16(path41.dirname(metaPath), { recursive: true });
45941
47788
  writeFileSync19(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
45942
47789
  }
45943
47790
  async function countSessions(name, baseDir) {
45944
47791
  const sessionsPath = sessionsPathFor(name, baseDir);
45945
47792
  try {
45946
- const entries = await fs21.readdir(sessionsPath);
47793
+ const entries = await fs24.readdir(sessionsPath);
45947
47794
  return entries.filter((e) => e.endsWith(".jsonl")).length;
45948
47795
  } catch (err) {
45949
47796
  if (err.code === "ENOENT") return 0;
@@ -45988,15 +47835,15 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
45988
47835
  if (branchExists(name, baseDir)) {
45989
47836
  throw new BranchAlreadyExistsError(name);
45990
47837
  }
45991
- const sourcePath = path39.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
47838
+ const sourcePath = path41.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
45992
47839
  if (!existsSync38(sourcePath)) {
45993
47840
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
45994
47841
  }
45995
47842
  const branchPath = branchPathFor(name, baseDir);
45996
47843
  const branchSessionsPath = sessionsPathFor(name, baseDir);
45997
47844
  mkdirSync16(branchSessionsPath, { recursive: true });
45998
- const destPath = path39.join(branchSessionsPath, `${fromSessionId}.jsonl`);
45999
- await fs21.copyFile(sourcePath, destPath);
47845
+ const destPath = path41.join(branchSessionsPath, `${fromSessionId}.jsonl`);
47846
+ await fs24.copyFile(sourcePath, destPath);
46000
47847
  const meta3 = {
46001
47848
  name,
46002
47849
  createdAt: Date.now(),
@@ -46014,7 +47861,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
46014
47861
  async function listBranches(baseDir = getBranchesBaseDir()) {
46015
47862
  let entries;
46016
47863
  try {
46017
- entries = await fs21.readdir(baseDir);
47864
+ entries = await fs24.readdir(baseDir);
46018
47865
  } catch (err) {
46019
47866
  if (err.code === "ENOENT") return [];
46020
47867
  throw err;
@@ -46095,26 +47942,26 @@ async function handleBranchCheckout(ctx, branchName) {
46095
47942
  }
46096
47943
 
46097
47944
  // src/cli/slashHandlers/workspace.ts
46098
- import { promises as fs22 } from "node:fs";
46099
- import path40 from "node:path";
47945
+ import { promises as fs25 } from "node:fs";
47946
+ import path42 from "node:path";
46100
47947
  async function handleWorkspaceShow(ctx, what) {
46101
47948
  try {
46102
- const zelari = path40.join(process.cwd(), ".zelari");
47949
+ const zelari = path42.join(process.cwd(), ".zelari");
46103
47950
  let content;
46104
47951
  switch (what) {
46105
47952
  case "plan": {
46106
- const planPath = path40.join(zelari, "plan.md");
47953
+ const planPath = path42.join(zelari, "plan.md");
46107
47954
  try {
46108
- content = await fs22.readFile(planPath, "utf-8");
47955
+ content = await fs25.readFile(planPath, "utf-8");
46109
47956
  } catch {
46110
47957
  content = "(no plan.md yet \u2014 run a council session first)";
46111
47958
  }
46112
47959
  break;
46113
47960
  }
46114
47961
  case "decisions": {
46115
- const decisionsDir = path40.join(zelari, "decisions");
47962
+ const decisionsDir = path42.join(zelari, "decisions");
46116
47963
  try {
46117
- 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();
46118
47965
  if (files.length === 0) {
46119
47966
  content = "(no ADRs yet \u2014 invoke /council to generate some)";
46120
47967
  } else {
@@ -46122,7 +47969,7 @@ async function handleWorkspaceShow(ctx, what) {
46122
47969
  `];
46123
47970
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
46124
47971
  for (const f of files) {
46125
- const raw = await fs22.readFile(path40.join(decisionsDir, f), "utf-8");
47972
+ const raw = await fs25.readFile(path42.join(decisionsDir, f), "utf-8");
46126
47973
  const { meta: meta3, body } = parseFrontmatter2(raw);
46127
47974
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
46128
47975
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -46135,27 +47982,27 @@ async function handleWorkspaceShow(ctx, what) {
46135
47982
  break;
46136
47983
  }
46137
47984
  case "risks": {
46138
- const risksPath = path40.join(zelari, "risks.md");
47985
+ const risksPath = path42.join(zelari, "risks.md");
46139
47986
  try {
46140
- content = await fs22.readFile(risksPath, "utf-8");
47987
+ content = await fs25.readFile(risksPath, "utf-8");
46141
47988
  } catch {
46142
47989
  content = "(no risks.md yet)";
46143
47990
  }
46144
47991
  break;
46145
47992
  }
46146
47993
  case "agents": {
46147
- const agentsPath = path40.join(process.cwd(), "AGENTS.MD");
47994
+ const agentsPath = path42.join(process.cwd(), "AGENTS.MD");
46148
47995
  try {
46149
- content = await fs22.readFile(agentsPath, "utf-8");
47996
+ content = await fs25.readFile(agentsPath, "utf-8");
46150
47997
  } catch {
46151
47998
  content = "(no AGENTS.MD yet at project root \u2014 run `/workspace sync` after a council session)";
46152
47999
  }
46153
48000
  break;
46154
48001
  }
46155
48002
  case "docs": {
46156
- const docsDir = path40.join(zelari, "docs");
48003
+ const docsDir = path42.join(zelari, "docs");
46157
48004
  try {
46158
- 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();
46159
48006
  content = files.length ? `# Docs (${files.length})
46160
48007
 
46161
48008
  ` + files.map((f) => `- ${f}`).join("\n") : "(no docs drafts yet)";
@@ -46195,8 +48042,8 @@ async function handleWorkspaceReset(ctx, force) {
46195
48042
  return;
46196
48043
  }
46197
48044
  try {
46198
- const target = path40.join(process.cwd(), ".zelari");
46199
- 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 });
46200
48047
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
46201
48048
  } catch (err) {
46202
48049
  appendSystem(ctx.setMessages, `[workspace reset error] ${err instanceof Error ? err.message : String(err)}`);
@@ -46555,16 +48402,16 @@ function handleModelsRefresh(ctx) {
46555
48402
  }
46556
48403
 
46557
48404
  // src/cli/slashHandlers/skills.ts
46558
- import path41 from "node:path";
48405
+ import path43 from "node:path";
46559
48406
  import os12 from "node:os";
46560
48407
 
46561
48408
  // src/cli/skillHistory.ts
46562
- 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";
46563
48410
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
46564
48411
  async function readSkillHistory(file2) {
46565
48412
  let raw = "";
46566
48413
  try {
46567
- raw = await fs23.readFile(file2, "utf-8");
48414
+ raw = await fs26.readFile(file2, "utf-8");
46568
48415
  } catch {
46569
48416
  return [];
46570
48417
  }
@@ -46681,7 +48528,7 @@ function handleSkillPicker(ctx, skills, openPicker, fallbackMessage) {
46681
48528
  });
46682
48529
  }
46683
48530
  async function handleSkillStats(ctx, skillId) {
46684
- 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");
46685
48532
  try {
46686
48533
  const records = await readSkillHistory(historyFile);
46687
48534
  const stats = getSkillStats(records, skillId);
@@ -46697,7 +48544,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
46697
48544
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
46698
48545
  return;
46699
48546
  }
46700
- 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");
46701
48548
  try {
46702
48549
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
46703
48550
  appendSystem(ctx.setMessages, formatted);
@@ -47121,6 +48968,18 @@ function useSlashDispatch(params) {
47121
48968
  await handleKrakenGraph({ setMessages, cwd: process.cwd(), sessionId: sid }, result.graphPrompt ?? "");
47122
48969
  return;
47123
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
+ }
47124
48983
  if (result.kind === "phase_set" && result.phaseTarget) {
47125
48984
  const { setPhase: setPhase2 } = await Promise.resolve().then(() => (init_phaseState(), phaseState_exports));
47126
48985
  const { describePhase: describePhase2 } = await Promise.resolve().then(() => (init_phase(), phase_exports));
@@ -47704,7 +49563,7 @@ function SplashGate({
47704
49563
  // src/cli/components/PluginGate.tsx
47705
49564
  import React12, { useEffect as useEffect9, useState as useState10, useCallback as useCallback6 } from "react";
47706
49565
  import { Box as Box11, Text as Text12, useStdin as useStdin4 } from "ink";
47707
- init_registry2();
49566
+ init_registry3();
47708
49567
  init_prefs();
47709
49568
  var CHOICE_INSTALL = "__install__";
47710
49569
  var CHOICE_LATER = "__later__";
@@ -48144,6 +50003,8 @@ function parseHeadlessFlags(argv) {
48144
50003
  let history2;
48145
50004
  let once = false;
48146
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;
48147
50008
  for (let i = 0; i < argv.length; i++) {
48148
50009
  const arg = argv[i];
48149
50010
  if (arg === "--headless") continue;
@@ -48237,6 +50098,11 @@ function parseHeadlessFlags(argv) {
48237
50098
  } else if (arg === "--kraken-graph") {
48238
50099
  krakenGraph = argv[i + 1];
48239
50100
  i++;
50101
+ } else if (arg === "--plan-only") {
50102
+ planOnly = true;
50103
+ } else if (arg === "--run-plan") {
50104
+ runPlan = argv[i + 1];
50105
+ i++;
48240
50106
  }
48241
50107
  }
48242
50108
  if (councilFlag && !modeExplicit) {
@@ -48264,7 +50130,9 @@ function parseHeadlessFlags(argv) {
48264
50130
  model,
48265
50131
  ...history2 && history2.length > 0 ? { history: history2 } : {},
48266
50132
  ...once ? { once: true } : {},
48267
- ...krakenGraph ? { krakenGraph } : {}
50133
+ ...krakenGraph ? { krakenGraph } : {},
50134
+ ...planOnly ? { planOnly: true } : {},
50135
+ ...runPlan ? { runPlan } : {}
48268
50136
  }
48269
50137
  };
48270
50138
  }
@@ -48335,6 +50203,9 @@ function createStreamScrubber() {
48335
50203
 
48336
50204
  // src/cli/runHeadless.ts
48337
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";
48338
50209
  async function runHeadless(opts) {
48339
50210
  resetTaskSpawnCount();
48340
50211
  try {
@@ -48443,11 +50314,39 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
48443
50314
  };
48444
50315
  process.once("SIGINT", onSigint);
48445
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
+ }
48446
50345
  log(`planning kraken graph: ${prompt}`);
48447
50346
  const previous = await loadGraphSnapshot2(cwd);
48448
50347
  const previousAttempt = formatSnapshotForPlanner2(previous);
48449
50348
  if (previousAttempt) log("resuming from the previous unfinished graph");
48450
- const graph = await planTaskGraph2({
50349
+ const graph = preflightGraph ?? await planTaskGraph2({
48451
50350
  prompt,
48452
50351
  provider,
48453
50352
  model,
@@ -48455,6 +50354,22 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
48455
50354
  ...previousAttempt ? { previousAttempt } : {}
48456
50355
  });
48457
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
+ }
48458
50373
  const audit = new AuditLogger2();
48459
50374
  const executor = new KrakenGraphExecutor2({
48460
50375
  taskToolDeps: {
@@ -49330,7 +51245,7 @@ ${ragContext}` : slicePrompt;
49330
51245
  init_desktopConfig();
49331
51246
 
49332
51247
  // src/cli/plugins/cliFlags.ts
49333
- init_registry2();
51248
+ init_registry3();
49334
51249
  function getArg(argv, flag) {
49335
51250
  const i = argv.indexOf(flag);
49336
51251
  if (i < 0) return void 0;
@@ -49630,7 +51545,7 @@ function upsertSkill(opts) {
49630
51545
  }
49631
51546
  dir = getProjectSkillsDir(root);
49632
51547
  }
49633
- const path44 = skillFilePath(dir, name);
51548
+ const path47 = skillFilePath(dir, name);
49634
51549
  const content = serializeSkillMd({
49635
51550
  name,
49636
51551
  description,
@@ -49639,13 +51554,13 @@ function upsertSkill(opts) {
49639
51554
  tools: opts.tools,
49640
51555
  cost: opts.cost
49641
51556
  });
49642
- const parsed = parseSkillMd(content, path44);
51557
+ const parsed = parseSkillMd(content, path47);
49643
51558
  if (!parsed) {
49644
51559
  return { ok: false, error: "Generated SKILL.md failed validation" };
49645
51560
  }
49646
- mkdirSync18(dirname9(path44), { recursive: true });
49647
- writeFileSync20(path44, content, "utf8");
49648
- return { ok: true, path: path44 };
51561
+ mkdirSync18(dirname9(path47), { recursive: true });
51562
+ writeFileSync20(path47, content, "utf8");
51563
+ return { ok: true, path: path47 };
49649
51564
  }
49650
51565
  function removeSkill(opts) {
49651
51566
  const name = opts.name.trim().toLowerCase();
@@ -49663,8 +51578,8 @@ function removeSkill(opts) {
49663
51578
  dir = getProjectSkillsDir(root);
49664
51579
  }
49665
51580
  const skillDir = join34(dir, name);
49666
- const path44 = skillFilePath(dir, name);
49667
- if (!existsSync42(path44) && !existsSync42(skillDir)) {
51581
+ const path47 = skillFilePath(dir, name);
51582
+ if (!existsSync42(path47) && !existsSync42(skillDir)) {
49668
51583
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
49669
51584
  }
49670
51585
  try {
@@ -49675,7 +51590,7 @@ function removeSkill(opts) {
49675
51590
  error: err instanceof Error ? err.message : String(err)
49676
51591
  };
49677
51592
  }
49678
- return { ok: true, path: path44 };
51593
+ return { ok: true, path: path47 };
49679
51594
  }
49680
51595
 
49681
51596
  // src/cli/generateSkillFromUrl.ts
@@ -49765,8 +51680,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
49765
51680
  let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
49766
51681
  if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
49767
51682
  try {
49768
- 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);
49769
- 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";
49770
51685
  } catch {
49771
51686
  name = "imported-skill";
49772
51687
  }