zelari-code 2.11.2 → 2.13.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 (33) hide show
  1. package/dist/cli/headless.js +16 -5
  2. package/dist/cli/headless.js.map +1 -1
  3. package/dist/cli/hooks/useChatTurn.js +17 -4
  4. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  5. package/dist/cli/kraken/completionProof.js +176 -0
  6. package/dist/cli/kraken/completionProof.js.map +1 -0
  7. package/dist/cli/kraken/nativeVerification.js +12 -5
  8. package/dist/cli/kraken/nativeVerification.js.map +1 -1
  9. package/dist/cli/kraken/verificationBridge.js +5 -1
  10. package/dist/cli/kraken/verificationBridge.js.map +1 -1
  11. package/dist/cli/kraken/verifierLifecycle.js +72 -8
  12. package/dist/cli/kraken/verifierLifecycle.js.map +1 -1
  13. package/dist/cli/main.bundled.js +1141 -433
  14. package/dist/cli/main.bundled.js.map +4 -4
  15. package/dist/cli/orchestration/policy.js +88 -0
  16. package/dist/cli/orchestration/policy.js.map +1 -0
  17. package/dist/cli/runHeadless.js +49 -0
  18. package/dist/cli/runHeadless.js.map +1 -1
  19. package/dist/cli/safety/policyEngine.js +315 -0
  20. package/dist/cli/safety/policyEngine.js.map +1 -0
  21. package/dist/cli/safety/policyLayers.js +55 -0
  22. package/dist/cli/safety/policyLayers.js.map +1 -0
  23. package/dist/cli/safety/toolPermissions.js +38 -0
  24. package/dist/cli/safety/toolPermissions.js.map +1 -1
  25. package/dist/cli/toolRegistry.js +49 -9
  26. package/dist/cli/toolRegistry.js.map +1 -1
  27. package/dist/cli/tools/krakenCsvFanout.js +4 -4
  28. package/dist/cli/tools/krakenCsvFanout.js.map +1 -1
  29. package/dist/cli/tools/krakenModel.js +75 -0
  30. package/dist/cli/tools/krakenModel.js.map +1 -1
  31. package/dist/cli/tools/taskTool.js +4 -3
  32. package/dist/cli/tools/taskTool.js.map +1 -1
  33. package/package.json +2 -2
@@ -3288,10 +3288,10 @@ function mergeDefs(...defs) {
3288
3288
  function cloneDef(schema) {
3289
3289
  return mergeDefs(schema._zod.def);
3290
3290
  }
3291
- function getElementAtPath(obj, path72) {
3292
- if (!path72)
3291
+ function getElementAtPath(obj, path74) {
3292
+ if (!path74)
3293
3293
  return obj;
3294
- return path72.reduce((acc, key) => acc?.[key], obj);
3294
+ return path74.reduce((acc, key) => acc?.[key], obj);
3295
3295
  }
3296
3296
  function promiseAllObject(promisesObj) {
3297
3297
  const keys = Object.keys(promisesObj);
@@ -3619,11 +3619,11 @@ function explicitlyAborted(x, startIndex = 0) {
3619
3619
  }
3620
3620
  return false;
3621
3621
  }
3622
- function prefixIssues(path72, issues) {
3622
+ function prefixIssues(path74, issues) {
3623
3623
  return issues.map((iss) => {
3624
3624
  var _a3;
3625
3625
  (_a3 = iss).path ?? (_a3.path = []);
3626
- iss.path.unshift(path72);
3626
+ iss.path.unshift(path74);
3627
3627
  return iss;
3628
3628
  });
3629
3629
  }
@@ -3841,16 +3841,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
3841
3841
  }
3842
3842
  function formatError(error51, mapper = (issue2) => issue2.message) {
3843
3843
  const fieldErrors = { _errors: [] };
3844
- const processError = (error52, path72 = []) => {
3844
+ const processError = (error52, path74 = []) => {
3845
3845
  for (const issue2 of error52.issues) {
3846
3846
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3847
- issue2.errors.map((issues) => processError({ issues }, [...path72, ...issue2.path]));
3847
+ issue2.errors.map((issues) => processError({ issues }, [...path74, ...issue2.path]));
3848
3848
  } else if (issue2.code === "invalid_key") {
3849
- processError({ issues: issue2.issues }, [...path72, ...issue2.path]);
3849
+ processError({ issues: issue2.issues }, [...path74, ...issue2.path]);
3850
3850
  } else if (issue2.code === "invalid_element") {
3851
- processError({ issues: issue2.issues }, [...path72, ...issue2.path]);
3851
+ processError({ issues: issue2.issues }, [...path74, ...issue2.path]);
3852
3852
  } else {
3853
- const fullpath = [...path72, ...issue2.path];
3853
+ const fullpath = [...path74, ...issue2.path];
3854
3854
  if (fullpath.length === 0) {
3855
3855
  fieldErrors._errors.push(mapper(issue2));
3856
3856
  } else {
@@ -3877,17 +3877,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
3877
3877
  }
3878
3878
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
3879
3879
  const result = { errors: [] };
3880
- const processError = (error52, path72 = []) => {
3880
+ const processError = (error52, path74 = []) => {
3881
3881
  var _a3, _b;
3882
3882
  for (const issue2 of error52.issues) {
3883
3883
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3884
- issue2.errors.map((issues) => processError({ issues }, [...path72, ...issue2.path]));
3884
+ issue2.errors.map((issues) => processError({ issues }, [...path74, ...issue2.path]));
3885
3885
  } else if (issue2.code === "invalid_key") {
3886
- processError({ issues: issue2.issues }, [...path72, ...issue2.path]);
3886
+ processError({ issues: issue2.issues }, [...path74, ...issue2.path]);
3887
3887
  } else if (issue2.code === "invalid_element") {
3888
- processError({ issues: issue2.issues }, [...path72, ...issue2.path]);
3888
+ processError({ issues: issue2.issues }, [...path74, ...issue2.path]);
3889
3889
  } else {
3890
- const fullpath = [...path72, ...issue2.path];
3890
+ const fullpath = [...path74, ...issue2.path];
3891
3891
  if (fullpath.length === 0) {
3892
3892
  result.errors.push(mapper(issue2));
3893
3893
  continue;
@@ -3919,8 +3919,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
3919
3919
  }
3920
3920
  function toDotPath(_path) {
3921
3921
  const segs = [];
3922
- const path72 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
3923
- for (const seg of path72) {
3922
+ const path74 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
3923
+ for (const seg of path74) {
3924
3924
  if (typeof seg === "number")
3925
3925
  segs.push(`[${seg}]`);
3926
3926
  else if (typeof seg === "symbol")
@@ -17423,13 +17423,13 @@ function resolveRef(ref, ctx) {
17423
17423
  if (!ref.startsWith("#")) {
17424
17424
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
17425
17425
  }
17426
- const path72 = ref.slice(1).split("/").filter(Boolean);
17427
- if (path72.length === 0) {
17426
+ const path74 = ref.slice(1).split("/").filter(Boolean);
17427
+ if (path74.length === 0) {
17428
17428
  return ctx.rootSchema;
17429
17429
  }
17430
17430
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
17431
- if (path72[0] === defsKey) {
17432
- const key = path72[1];
17431
+ if (path74[0] === defsKey) {
17432
+ const key = path74[1];
17433
17433
  if (!key || !ctx.defs[key]) {
17434
17434
  throw new Error(`Reference not found: ${ref}`);
17435
17435
  }
@@ -18235,15 +18235,66 @@ var init_toolTypes = __esm({
18235
18235
  }
18236
18236
  });
18237
18237
 
18238
+ // packages/core/dist/core/tools/builtin/newlines.js
18239
+ function detectNewline(text) {
18240
+ if (text.includes("\r\n"))
18241
+ return "\r\n";
18242
+ if (text.includes("\r"))
18243
+ return "\r";
18244
+ return "\n";
18245
+ }
18246
+ function toLF(text) {
18247
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
18248
+ }
18249
+ function fromLF(text, nl) {
18250
+ if (nl === "\n")
18251
+ return text;
18252
+ return text.replace(/\n/g, nl);
18253
+ }
18254
+ function splitLinesLF(text) {
18255
+ return toLF(text).split("\n");
18256
+ }
18257
+ var init_newlines = __esm({
18258
+ "packages/core/dist/core/tools/builtin/newlines.js"() {
18259
+ "use strict";
18260
+ }
18261
+ });
18262
+
18238
18263
  // packages/core/dist/core/tools/builtin/filesystem.js
18239
18264
  import { promises as fs4 } from "node:fs";
18240
18265
  import path6 from "node:path";
18266
+ function replaceFileString(text, oldString, newString, replaceAll) {
18267
+ const exact = replaceOnceOrAll(text, oldString, newString, replaceAll);
18268
+ if (exact.occurrences > 0)
18269
+ return exact;
18270
+ const nl = detectNewline(text);
18271
+ const normalized = replaceOnceOrAll(toLF(text), toLF(oldString), toLF(newString), replaceAll);
18272
+ if (normalized.occurrences === 0)
18273
+ return normalized;
18274
+ return { occurrences: normalized.occurrences, newContent: fromLF(normalized.newContent, nl) };
18275
+ }
18276
+ function replaceOnceOrAll(text, oldString, newString, replaceAll) {
18277
+ if (replaceAll) {
18278
+ if (!text.includes(oldString))
18279
+ return { occurrences: 0, newContent: text };
18280
+ const parts = text.split(oldString);
18281
+ return { occurrences: parts.length - 1, newContent: parts.join(newString) };
18282
+ }
18283
+ const idx = text.indexOf(oldString);
18284
+ if (idx === -1)
18285
+ return { occurrences: 0, newContent: text };
18286
+ return {
18287
+ occurrences: 1,
18288
+ newContent: text.slice(0, idx) + newString + text.slice(idx + oldString.length)
18289
+ };
18290
+ }
18241
18291
  var ReadFileArgsSchema, readFileTool, WriteFileArgsSchema, writeFileTool, EditFileArgsSchema, editFileTool;
18242
18292
  var init_filesystem = __esm({
18243
18293
  "packages/core/dist/core/tools/builtin/filesystem.js"() {
18244
18294
  "use strict";
18245
18295
  init_zod();
18246
18296
  init_toolTypes();
18297
+ init_newlines();
18247
18298
  ReadFileArgsSchema = external_exports.object({
18248
18299
  path: external_exports.string().min(1),
18249
18300
  startLine: external_exports.number().int().nonnegative().optional().describe("0-based first line to include. Range is applied to the full file before maxBytes."),
@@ -18337,7 +18388,7 @@ var init_filesystem = __esm({
18337
18388
  });
18338
18389
  editFileTool = {
18339
18390
  name: "edit_file",
18340
- description: "Replace exact string match in a file. Idempotent: returns 0 occurrences if no match.",
18391
+ description: "Replace a string in a file. Matching ignores CRLF vs LF; the file keeps its original line endings. Returns an error if oldString is not found.",
18341
18392
  permissions: ["write"],
18342
18393
  sideEffect: "local",
18343
18394
  timeoutMs: 1e4,
@@ -18347,22 +18398,7 @@ var init_filesystem = __esm({
18347
18398
  const absPath = path6.isAbsolute(args.path) ? args.path : path6.join(ctx.cwd, args.path);
18348
18399
  const content = await fs4.readFile(absPath, { encoding: "utf-8", signal: ctx.signal });
18349
18400
  const text = typeof content === "string" ? content : content.toString("utf-8");
18350
- let occurrences = 0;
18351
- let newContent;
18352
- if (args.replaceAll) {
18353
- const parts = text.split(args.oldString);
18354
- occurrences = parts.length - 1;
18355
- newContent = parts.join(args.newString);
18356
- } else {
18357
- const idx = text.indexOf(args.oldString);
18358
- if (idx === -1) {
18359
- occurrences = 0;
18360
- newContent = text;
18361
- } else {
18362
- occurrences = 1;
18363
- newContent = text.slice(0, idx) + args.newString + text.slice(idx + args.oldString.length);
18364
- }
18365
- }
18401
+ const { occurrences, newContent } = replaceFileString(text, args.oldString, args.newString, args.replaceAll);
18366
18402
  if (occurrences === 0) {
18367
18403
  return typedErr(`edit_file: no match for oldString in ${args.path}. Use read_file to copy the exact text (whitespace included) and retry.`);
18368
18404
  }
@@ -19114,7 +19150,7 @@ function formatUnified(hunks, oldLabel, newLabel) {
19114
19150
  return lines.join("\n");
19115
19151
  }
19116
19152
  function parseUnified(raw) {
19117
- const lines = raw.split("\n");
19153
+ const lines = splitLinesLF(raw);
19118
19154
  if (lines.length < 2 || !lines[0].startsWith("--- ") || !lines[1].startsWith("+++ ")) {
19119
19155
  throw new Error("Invalid unified diff: missing --- / +++ headers");
19120
19156
  }
@@ -19170,6 +19206,72 @@ function parseUnified(raw) {
19170
19206
  function normalizeWhitespace(s) {
19171
19207
  return s.replace(/\s+/g, " ").trim();
19172
19208
  }
19209
+ function linesEqual(fileLine, opText, fuzzy) {
19210
+ if (fileLine === opText)
19211
+ return true;
19212
+ if (fuzzy && normalizeWhitespace(fileLine) === normalizeWhitespace(opText))
19213
+ return true;
19214
+ return false;
19215
+ }
19216
+ function hunkOldNeedle(hunk) {
19217
+ return hunk.ops.filter((op) => op.kind !== "+").map((op) => op.text);
19218
+ }
19219
+ function matchAt(lines, at, needle, fuzzy) {
19220
+ if (needle.length === 0)
19221
+ return at >= 0 && at <= lines.length;
19222
+ if (at < 0 || at + needle.length > lines.length)
19223
+ return false;
19224
+ for (let i = 0; i < needle.length; i++) {
19225
+ if (!linesEqual(lines[at + i] ?? "", needle[i], fuzzy))
19226
+ return false;
19227
+ }
19228
+ return true;
19229
+ }
19230
+ function locateHunk(lines, hunk, fileIdx, fuzzy) {
19231
+ const needle = hunkOldNeedle(hunk);
19232
+ const preferred = hunk.oldStart - 1;
19233
+ if (needle.length === 0) {
19234
+ const start = Math.max(fileIdx, preferred >= 0 ? preferred : fileIdx);
19235
+ return { start };
19236
+ }
19237
+ if (preferred >= fileIdx && matchAt(lines, preferred, needle, fuzzy)) {
19238
+ return { start: preferred };
19239
+ }
19240
+ const hits = [];
19241
+ for (let i = fileIdx; i <= lines.length - needle.length; i++) {
19242
+ if (matchAt(lines, i, needle, fuzzy))
19243
+ hits.push(i);
19244
+ }
19245
+ if (hits.length === 0) {
19246
+ if (preferred >= fileIdx) {
19247
+ let fileAt = preferred;
19248
+ for (const op of hunk.ops) {
19249
+ if (op.kind === "+")
19250
+ continue;
19251
+ const fileLine = lines[fileAt] ?? "";
19252
+ if (!linesEqual(fileLine, op.text, fuzzy)) {
19253
+ const label = op.kind === "-" ? "Delete mismatch" : "Context mismatch";
19254
+ return {
19255
+ error: `${label} at line ${fileAt + 1}: expected "${op.text.slice(0, 60)}", got "${fileLine.slice(0, 60)}"`
19256
+ };
19257
+ }
19258
+ fileAt++;
19259
+ }
19260
+ }
19261
+ return { error: `Hunk context not found (oldStart ${hunk.oldStart})` };
19262
+ }
19263
+ if (hits.length === 1)
19264
+ return { start: hits[0] };
19265
+ hits.sort((a, b) => Math.abs(a - preferred) - Math.abs(b - preferred) || a - b);
19266
+ const bestDist = Math.abs(hits[0] - preferred);
19267
+ const tied = hits.filter((h) => Math.abs(h - preferred) === bestDist);
19268
+ if (tied.length > 1) {
19269
+ return {
19270
+ error: `Ambiguous hunk at oldStart ${hunk.oldStart}: ${tied.length} equally close matches (refusing to guess)`
19271
+ };
19272
+ }
19273
+ return { start: hits[0] };
19274
+ }
19173
19275
  function applyAllHunks(originalLines, hunks, fuzzy) {
19174
19276
  const out = [];
19175
19277
  let fileIdx = 0;
@@ -19177,17 +19279,13 @@ function applyAllHunks(originalLines, hunks, fuzzy) {
19177
19279
  let hunksSkipped = 0;
19178
19280
  let lastReason;
19179
19281
  for (const hunk of hunks) {
19180
- const startIdx = hunk.oldStart - 1;
19181
- if (startIdx < 0) {
19182
- hunksSkipped++;
19183
- lastReason = `oldStart ${hunk.oldStart} out of range (must be >= 1)`;
19184
- continue;
19185
- }
19186
- if (startIdx > originalLines.length) {
19282
+ const located = locateHunk(originalLines, hunk, fileIdx, fuzzy);
19283
+ if ("error" in located) {
19187
19284
  hunksSkipped++;
19188
- lastReason = `oldStart ${hunk.oldStart} beyond file end (length ${originalLines.length})`;
19189
- continue;
19285
+ lastReason = located.error;
19286
+ break;
19190
19287
  }
19288
+ const startIdx = located.start;
19191
19289
  while (fileIdx < startIdx) {
19192
19290
  out.push(originalLines[fileIdx]);
19193
19291
  fileIdx++;
@@ -19200,7 +19298,7 @@ function applyAllHunks(originalLines, hunks, fuzzy) {
19200
19298
  const op = hunk.ops[hunkIdx];
19201
19299
  if (op.kind === " ") {
19202
19300
  const fileLine = originalLines[hunkFileIdx] ?? "";
19203
- if (fileLine === op.text || fuzzy && normalizeWhitespace(fileLine) === normalizeWhitespace(op.text)) {
19301
+ if (linesEqual(fileLine, op.text, fuzzy)) {
19204
19302
  hunkOut.push(fileLine);
19205
19303
  hunkFileIdx++;
19206
19304
  hunkIdx++;
@@ -19211,7 +19309,7 @@ function applyAllHunks(originalLines, hunks, fuzzy) {
19211
19309
  }
19212
19310
  } else if (op.kind === "-") {
19213
19311
  const fileLine = originalLines[hunkFileIdx] ?? "";
19214
- if (fileLine === op.text || fuzzy && normalizeWhitespace(fileLine) === normalizeWhitespace(op.text)) {
19312
+ if (linesEqual(fileLine, op.text, fuzzy)) {
19215
19313
  hunkFileIdx++;
19216
19314
  hunkIdx++;
19217
19315
  } else {
@@ -19244,6 +19342,7 @@ var init_diff = __esm({
19244
19342
  "use strict";
19245
19343
  init_zod();
19246
19344
  init_toolTypes();
19345
+ init_newlines();
19247
19346
  LCS_MAX_CELLS = 4e6;
19248
19347
  ShowDiffArgsSchema = external_exports.object({
19249
19348
  path: external_exports.string().min(1),
@@ -19268,8 +19367,8 @@ var init_diff = __esm({
19268
19367
  return typedErr(err instanceof Error ? err.message : String(err));
19269
19368
  }
19270
19369
  }
19271
- const a = current.split("\n");
19272
- const b = args.proposedContent.split("\n");
19370
+ const a = splitLinesLF(current);
19371
+ const b = splitLinesLF(args.proposedContent);
19273
19372
  const CONTEXT = args.contextLines;
19274
19373
  const rawOps = diffOps(a, b);
19275
19374
  if (rawOps === null) {
@@ -19355,7 +19454,7 @@ var init_diff = __esm({
19355
19454
  });
19356
19455
  applyDiffTool = {
19357
19456
  name: "apply_diff",
19358
- description: "Apply a unified diff patch to a file. Parses ---/+++/@@ headers and applies each hunk sequentially. With fuzzyMatch=true, tolerates whitespace differences. With dryRun=true, returns the final content without writing. Atomic: if any hunk fails, no partial write occurs.",
19457
+ description: "Apply a unified diff patch to a file. Parses ---/+++/@@ headers and applies each hunk sequentially. CRLF vs LF is ignored; the file keeps its original line endings. Hunks whose @@ line numbers drifted (common after an earlier insert) are relocated by matching context. With fuzzyMatch=true, also tolerates whitespace differences. With dryRun=true, returns the final content without writing. Atomic: if any hunk fails, no partial write occurs.",
19359
19458
  permissions: ["write"],
19360
19459
  sideEffect: "local",
19361
19460
  timeoutMs: 15e3,
@@ -19380,9 +19479,10 @@ var init_diff = __esm({
19380
19479
  if (parsed.hunks.length === 0) {
19381
19480
  return typedErr("No hunks found in diff");
19382
19481
  }
19383
- const originalLines = current.split("\n");
19482
+ const nl = detectNewline(current);
19483
+ const originalLines = splitLinesLF(current);
19384
19484
  const result = applyAllHunks(originalLines, parsed.hunks, args.fuzzyMatch);
19385
- const finalContent = result.lines.join("\n");
19485
+ const finalContent = fromLF(result.lines.join("\n"), nl);
19386
19486
  const ok = result.hunksSkipped === 0;
19387
19487
  if (ok && !args.dryRun) {
19388
19488
  await fs7.mkdir(path10.dirname(absPath), { recursive: true });
@@ -19765,11 +19865,11 @@ var init_tools = __esm({
19765
19865
  if (!ctx.addDocument)
19766
19866
  return "Knowledge vault tool not available.";
19767
19867
  const title = args["title"] || "New Document";
19768
- const path72 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
19868
+ const path74 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
19769
19869
  const content = args["content"] || "";
19770
19870
  const tags = args["tags"] || [];
19771
19871
  ctx.addDocument({
19772
- path: path72,
19872
+ path: path74,
19773
19873
  title,
19774
19874
  content,
19775
19875
  format: "markdown",
@@ -19778,7 +19878,7 @@ var init_tools = __esm({
19778
19878
  workspaceId: ctx.workspaceId
19779
19879
  });
19780
19880
  ctx.addActivity("vault", "created document", title);
19781
- return `Document "${title}" created at "${path72}".`;
19881
+ return `Document "${title}" created at "${path74}".`;
19782
19882
  }
19783
19883
  }
19784
19884
  ];
@@ -25734,11 +25834,11 @@ var init_synthesisAudit = __esm({
25734
25834
  import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
25735
25835
  import { join as join4 } from "node:path";
25736
25836
  function loadNfrSpec(zelariRoot) {
25737
- const path72 = join4(zelariRoot, "nfr-spec.json");
25738
- if (!existsSync7(path72))
25837
+ const path74 = join4(zelariRoot, "nfr-spec.json");
25838
+ if (!existsSync7(path74))
25739
25839
  return null;
25740
25840
  try {
25741
- const raw = JSON.parse(readFileSync7(path72, "utf8"));
25841
+ const raw = JSON.parse(readFileSync7(path74, "utf8"));
25742
25842
  if (raw.version !== 1 || !Array.isArray(raw.targets))
25743
25843
  return null;
25744
25844
  return raw;
@@ -28044,9 +28144,9 @@ var init_types5 = __esm({
28044
28144
  import { readFileSync as readFileSync12 } from "node:fs";
28045
28145
  import { join as join10 } from "node:path";
28046
28146
  function readLessonsDeduped(zelariRoot) {
28047
- const path72 = join10(zelariRoot, LESSONS_FILE);
28147
+ const path74 = join10(zelariRoot, LESSONS_FILE);
28048
28148
  try {
28049
- const raw = readFileSync12(path72, "utf8");
28149
+ const raw = readFileSync12(path74, "utf8");
28050
28150
  const byId = /* @__PURE__ */ new Map();
28051
28151
  for (const line of raw.split(/\r?\n/)) {
28052
28152
  if (!line.trim())
@@ -28147,8 +28247,8 @@ function keywordsFrom(check2, signature) {
28147
28247
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
28148
28248
  }
28149
28249
  function writeLesson(zelariRoot, lesson) {
28150
- const path72 = join11(zelariRoot, LESSONS_FILE);
28151
- appendFileSync(path72, `${JSON.stringify(lesson)}
28250
+ const path74 = join11(zelariRoot, LESSONS_FILE);
28251
+ appendFileSync(path74, `${JSON.stringify(lesson)}
28152
28252
  `, "utf8");
28153
28253
  }
28154
28254
  function findSimilar(lessons, signature) {
@@ -30111,9 +30211,9 @@ function findCycle(nodes) {
30111
30211
  if (color.get(start) !== WHITE)
30112
30212
  continue;
30113
30213
  const stack = [[start, 0]];
30114
- const path72 = [];
30214
+ const path74 = [];
30115
30215
  color.set(start, GRAY);
30116
- path72.push(start);
30216
+ path74.push(start);
30117
30217
  while (stack.length > 0) {
30118
30218
  const top = stack[stack.length - 1];
30119
30219
  const [id3, idx] = top;
@@ -30126,17 +30226,17 @@ function findCycle(nodes) {
30126
30226
  continue;
30127
30227
  const c = color.get(dep);
30128
30228
  if (c === GRAY) {
30129
- const at = path72.indexOf(dep);
30130
- return [...path72.slice(at), dep];
30229
+ const at = path74.indexOf(dep);
30230
+ return [...path74.slice(at), dep];
30131
30231
  }
30132
30232
  if (c === WHITE) {
30133
30233
  color.set(dep, GRAY);
30134
- path72.push(dep);
30234
+ path74.push(dep);
30135
30235
  stack.push([dep, 0]);
30136
30236
  }
30137
30237
  } else {
30138
30238
  color.set(id3, BLACK);
30139
- path72.pop();
30239
+ path74.pop();
30140
30240
  stack.pop();
30141
30241
  }
30142
30242
  }
@@ -31056,8 +31156,8 @@ var init_runner = __esm({
31056
31156
  failed: [...this.tentaclesById.values()].filter((r) => r.status === "error"),
31057
31157
  pending: []
31058
31158
  };
31059
- const path72 = await this.host.saveSnapshot(snapshot, ".zelari/kraken/snapshots");
31060
- this.host.log(`checkpoint: ${label ?? "auto"} \u2192 ${path72}`);
31159
+ const path74 = await this.host.saveSnapshot(snapshot, ".zelari/kraken/snapshots");
31160
+ this.host.log(`checkpoint: ${label ?? "auto"} \u2192 ${path74}`);
31061
31161
  return snapshot;
31062
31162
  }
31063
31163
  callLog(msg, data) {
@@ -33499,16 +33599,16 @@ function runRetentionFromEnv() {
33499
33599
  maxTotalBytes: Number.isFinite(parseMb) && parseMb > 0 ? Math.round(parseMb * 1024 * 1024) : DEFAULT_RUN_RETENTION_MAX_MB * 1024 * 1024
33500
33600
  };
33501
33601
  }
33502
- async function dirSize(path72) {
33602
+ async function dirSize(path74) {
33503
33603
  let total = 0;
33504
33604
  let entries;
33505
33605
  try {
33506
- entries = await readdir(path72, { withFileTypes: true });
33606
+ entries = await readdir(path74, { withFileTypes: true });
33507
33607
  } catch {
33508
33608
  return 0;
33509
33609
  }
33510
33610
  for (const entry of entries) {
33511
- const child = join13(path72, entry.name);
33611
+ const child = join13(path74, entry.name);
33512
33612
  if (entry.isDirectory())
33513
33613
  total += await dirSize(child);
33514
33614
  else {
@@ -33535,19 +33635,19 @@ async function enforceRunRetention(runsDir, options = {}) {
33535
33635
  for (const entry of entries) {
33536
33636
  if (!entry.isDirectory())
33537
33637
  continue;
33538
- const path72 = join13(runsDir, entry.name);
33638
+ const path74 = join13(runsDir, entry.name);
33539
33639
  let startedAt = 0;
33540
33640
  let endedAt;
33541
33641
  let completed = false;
33542
33642
  try {
33543
- const manifest = JSON.parse(await readFile(join13(path72, "manifest.json"), "utf8"));
33643
+ const manifest = JSON.parse(await readFile(join13(path74, "manifest.json"), "utf8"));
33544
33644
  startedAt = manifest.startedAt ?? 0;
33545
33645
  endedAt = manifest.endedAt;
33546
33646
  completed = Boolean(endedAt) && manifest.status !== "running";
33547
33647
  } catch {
33548
33648
  completed = false;
33549
33649
  }
33550
- infos.push({ name: entry.name, path: path72, startedAt, endedAt, completed, bytes: await dirSize(path72) });
33650
+ infos.push({ name: entry.name, path: path74, startedAt, endedAt, completed, bytes: await dirSize(path74) });
33551
33651
  }
33552
33652
  const remove = async (info) => {
33553
33653
  await rm(info.path, { recursive: true, force: true });
@@ -34319,12 +34419,12 @@ var init_engine = __esm({
34319
34419
  * content digest) and the returned ref carries the event seq when the
34320
34420
  * emitter resolved one.
34321
34421
  */
34322
- async fsEvidence(observation, path72, sha256, content, extra = {}) {
34422
+ async fsEvidence(observation, path74, sha256, content, extra = {}) {
34323
34423
  const digest = sha256 && content !== void 0 ? sha256(content) : void 0;
34324
- const seq = await this.emitEvidence({ observation, path: path72, ...extra, ...digest ? { digest } : {} });
34424
+ const seq = await this.emitEvidence({ observation, path: path74, ...extra, ...digest ? { digest } : {} });
34325
34425
  return {
34326
34426
  tier: "fs-observation",
34327
- ref: path72,
34427
+ ref: path74,
34328
34428
  capturedAt: Date.now(),
34329
34429
  ...digest ? { digest } : {},
34330
34430
  ...seq !== void 0 ? { seq } : {}
@@ -34706,7 +34806,8 @@ var init_verifier = __esm({
34706
34806
  });
34707
34807
  VERIFIER_SYSTEM_PROMPT = [
34708
34808
  "You are an independent completion verifier.",
34709
- "You receive a work summary and deterministic verification results.",
34809
+ "You receive the original task, a git diff summary, a test output excerpt,",
34810
+ "and deterministic verification results \u2014 never the builder narration.",
34710
34811
  "Answer with a single JSON object and nothing else:",
34711
34812
  '{"verdict":"confirmed|rejected|unknown","score":0..1,"rationale":"..."}',
34712
34813
  "Rules: never confirm when a required deterministic check failed or is unknown;",
@@ -34741,8 +34842,14 @@ var init_verifier = __esm({
34741
34842
  try {
34742
34843
  response = await this.deps.callModel({
34743
34844
  system: VERIFIER_SYSTEM_PROMPT,
34845
+ // Blind review payload: evidence only (task, diff summary, test output
34846
+ // excerpt, deterministic results). Builder narration/reasoning is
34847
+ // structurally excluded — undefined keys are omitted.
34744
34848
  user: JSON.stringify({
34849
+ ...request.task === void 0 ? {} : { task: request.task },
34745
34850
  summary: request.summary,
34851
+ ...request.diffSummary === void 0 ? {} : { diffSummary: request.diffSummary },
34852
+ ...request.testOutputExcerpt === void 0 ? {} : { testOutputExcerpt: request.testOutputExcerpt },
34746
34853
  deterministicResults: request.results.map((r) => ({
34747
34854
  criterionId: r.criterionId,
34748
34855
  status: r.status,
@@ -37457,9 +37564,9 @@ function spillToolOutput(fullText, meta3) {
37457
37564
  const rnd = randomBytes3(3).toString("hex");
37458
37565
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
37459
37566
  const file2 = `${stamp}-${safeTool}-${hash3}-${rnd}.txt`;
37460
- const path72 = join14(dir, file2);
37461
- writeFileSync11(path72, fullText, "utf8");
37462
- return path72;
37567
+ const path74 = join14(dir, file2);
37568
+ writeFileSync11(path74, fullText, "utf8");
37569
+ return path74;
37463
37570
  } catch {
37464
37571
  return null;
37465
37572
  }
@@ -37505,10 +37612,10 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
37505
37612
  ${tail2}`;
37506
37613
  }
37507
37614
  if (doSpill) {
37508
- const path72 = spillToolOutput(text, { toolName: opts.toolName });
37509
- if (path72) {
37615
+ const path74 = spillToolOutput(text, { toolName: opts.toolName });
37616
+ if (path74) {
37510
37617
  const spillNote = `
37511
- \u2026 [full output spilled to: ${path72} \u2014 re-read with read_file if you need the complete text] \u2026`;
37618
+ \u2026 [full output spilled to: ${path74} \u2014 re-read with read_file if you need the complete text] \u2026`;
37512
37619
  if (preview.includes("] \u2026\n")) {
37513
37620
  preview = preview.replace("] \u2026\n", `] \u2026${spillNote}
37514
37621
  `);
@@ -39305,7 +39412,7 @@ var init_taskTool = __esm({
39305
39412
  init_candidateRegistry();
39306
39413
  init_verifyReport();
39307
39414
  init_metrics2();
39308
- TASK_TOOL_TIMEOUT_MS = 9e5;
39415
+ TASK_TOOL_TIMEOUT_MS = 27e5;
39309
39416
  EXPLORE_PROMPT = [
39310
39417
  "You are a focused EXPLORE tentacle of Kraken (parent super-agent).",
39311
39418
  "READ-ONLY tools only (read, list, grep, fetch). No edits, no shell.",
@@ -40340,28 +40447,28 @@ var init_storage = __esm({
40340
40447
  VALID_SCALARS = /^(true|false|null|~)$/i;
40341
40448
  Storage = class {
40342
40449
  /** Read a Markdown file with frontmatter. Throws if not found. */
40343
- read(path72) {
40344
- if (!existsSync19(path72)) {
40345
- throw new Error(`File not found: ${path72}`);
40450
+ read(path74) {
40451
+ if (!existsSync19(path74)) {
40452
+ throw new Error(`File not found: ${path74}`);
40346
40453
  }
40347
- const md = readFileSync16(path72, "utf8");
40454
+ const md = readFileSync16(path74, "utf8");
40348
40455
  return parseFrontmatter(md);
40349
40456
  }
40350
40457
  /** Read a Markdown file; returns null if not found. */
40351
- readIfExists(path72) {
40352
- if (!existsSync19(path72)) return null;
40353
- return this.read(path72);
40458
+ readIfExists(path74) {
40459
+ if (!existsSync19(path74)) return null;
40460
+ return this.read(path74);
40354
40461
  }
40355
40462
  /**
40356
40463
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
40357
40464
  * The meta object is serialized as YAML frontmatter; body as Markdown.
40358
40465
  */
40359
- write(path72, meta3, body) {
40360
- mkdirSync10(dirname2(path72), { recursive: true });
40361
- const tmp = path72 + ".tmp-" + process.pid;
40466
+ write(path74, meta3, body) {
40467
+ mkdirSync10(dirname2(path74), { recursive: true });
40468
+ const tmp = path74 + ".tmp-" + process.pid;
40362
40469
  const md = serializeFrontmatter(meta3, body);
40363
40470
  writeFileSync13(tmp, md, "utf8");
40364
- renameSync(tmp, path72);
40471
+ renameSync(tmp, path74);
40365
40472
  }
40366
40473
  /** List all .md files in a directory (non-recursive). */
40367
40474
  listMarkdown(dir) {
@@ -40423,8 +40530,8 @@ function nextPlanTaskId(store6) {
40423
40530
  return `t${store6.counter}`;
40424
40531
  }
40425
40532
  function writePlanTaskArtifact(rootDir, task) {
40426
- const path72 = join18(rootDir, "plan-tasks", `${task.id}.md`);
40427
- mkdirSync11(dirname3(path72), { recursive: true });
40533
+ const path74 = join18(rootDir, "plan-tasks", `${task.id}.md`);
40534
+ mkdirSync11(dirname3(path74), { recursive: true });
40428
40535
  const meta3 = {
40429
40536
  kind: "task",
40430
40537
  id: task.id,
@@ -40445,7 +40552,7 @@ function writePlanTaskArtifact(rootDir, task) {
40445
40552
  task.notes?.trim() ? task.notes.trim() : "_(no notes)_",
40446
40553
  ""
40447
40554
  ].filter((l) => l !== null).join("\n");
40448
- new Storage().write(path72, meta3, body);
40555
+ new Storage().write(path74, meta3, body);
40449
40556
  }
40450
40557
  function loadHandle(rootDir) {
40451
40558
  const jsonPath = join18(rootDir, "plan.json");
@@ -43733,21 +43840,21 @@ function normalizeAuth(auth) {
43733
43840
  return "agent";
43734
43841
  }
43735
43842
  function readSecrets() {
43736
- const path72 = getSshSecretsPath();
43737
- if (!existsSync23(path72)) return {};
43843
+ const path74 = getSshSecretsPath();
43844
+ if (!existsSync23(path74)) return {};
43738
43845
  try {
43739
- return JSON.parse(readFileSync20(path72, "utf8"));
43846
+ return JSON.parse(readFileSync20(path74, "utf8"));
43740
43847
  } catch {
43741
43848
  return {};
43742
43849
  }
43743
43850
  }
43744
43851
  function writeSecrets(data) {
43745
- const path72 = getSshSecretsPath();
43746
- mkdirSync12(dirname4(path72), { recursive: true });
43747
- writeFileSync15(path72, `${JSON.stringify(data, null, 2)}
43852
+ const path74 = getSshSecretsPath();
43853
+ mkdirSync12(dirname4(path74), { recursive: true });
43854
+ writeFileSync15(path74, `${JSON.stringify(data, null, 2)}
43748
43855
  `, "utf8");
43749
43856
  try {
43750
- chmodSync(path72, 384);
43857
+ chmodSync(path74, 384);
43751
43858
  } catch {
43752
43859
  }
43753
43860
  }
@@ -43776,10 +43883,10 @@ function deleteSshPassword(id3) {
43776
43883
  writeSecrets({ passwords });
43777
43884
  }
43778
43885
  function readStore2() {
43779
- const path72 = getSshTargetsPath();
43780
- if (!existsSync23(path72)) return [];
43886
+ const path74 = getSshTargetsPath();
43887
+ if (!existsSync23(path74)) return [];
43781
43888
  try {
43782
- const parsed = JSON.parse(readFileSync20(path72, "utf8"));
43889
+ const parsed = JSON.parse(readFileSync20(path74, "utf8"));
43783
43890
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
43784
43891
  return list.filter(
43785
43892
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -43794,11 +43901,11 @@ function readStore2() {
43794
43901
  }
43795
43902
  }
43796
43903
  function writeStore2(targets) {
43797
- const path72 = getSshTargetsPath();
43798
- mkdirSync12(dirname4(path72), { recursive: true });
43904
+ const path74 = getSshTargetsPath();
43905
+ mkdirSync12(dirname4(path74), { recursive: true });
43799
43906
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
43800
43907
  writeFileSync15(
43801
- path72,
43908
+ path74,
43802
43909
  `${JSON.stringify({ targets: clean }, null, 2)}
43803
43910
  `,
43804
43911
  "utf8"
@@ -44044,11 +44151,11 @@ function formatSshTargetsForPrompt() {
44044
44151
  ];
44045
44152
  for (const t of targets) {
44046
44153
  const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
44047
- const path72 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
44154
+ const path74 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
44048
44155
  const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
44049
44156
  const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
44050
44157
  lines.push(
44051
- `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path72}${tags}${allow}`
44158
+ `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path74}${tags}${allow}`
44052
44159
  );
44053
44160
  }
44054
44161
  return lines.join("\n");
@@ -45048,6 +45155,19 @@ function defaultPermissionPolicy(overrides) {
45048
45155
  ...overrides
45049
45156
  };
45050
45157
  }
45158
+ function moreRestrictive(a, b) {
45159
+ return ACTION_RANK2[a] >= ACTION_RANK2[b] ? a : b;
45160
+ }
45161
+ function intersectPermissionPolicy(parent, child) {
45162
+ return {
45163
+ read: moreRestrictive(parent.read, child.read),
45164
+ write: moreRestrictive(parent.write, child.write),
45165
+ execute: moreRestrictive(parent.execute, child.execute),
45166
+ network: moreRestrictive(parent.network, child.network),
45167
+ ui: moreRestrictive(parent.ui, child.ui),
45168
+ auto: parent.auto && child.auto
45169
+ };
45170
+ }
45051
45171
  function resolveToolPermission(toolName, required2, policy) {
45052
45172
  if (!required2.length) {
45053
45173
  return { action: "allow", reason: "", categories: [] };
@@ -45083,12 +45203,13 @@ function resolveToolPermission(toolName, required2, policy) {
45083
45203
  categories: hit
45084
45204
  };
45085
45205
  }
45086
- var sessionToolGrants, sessionCategoryGrants;
45206
+ var sessionToolGrants, sessionCategoryGrants, ACTION_RANK2;
45087
45207
  var init_toolPermissions = __esm({
45088
45208
  "src/cli/safety/toolPermissions.ts"() {
45089
45209
  "use strict";
45090
45210
  sessionToolGrants = /* @__PURE__ */ new Set();
45091
45211
  sessionCategoryGrants = /* @__PURE__ */ new Set();
45212
+ ACTION_RANK2 = { allow: 0, ask: 1, deny: 2 };
45092
45213
  }
45093
45214
  });
45094
45215
 
@@ -45265,10 +45386,239 @@ var init_lifecycleHooks = __esm({
45265
45386
  }
45266
45387
  });
45267
45388
 
45389
+ // src/cli/safety/policyEngine.ts
45390
+ import { readFileSync as readFileSync22 } from "node:fs";
45391
+ import { homedir as homedir10 } from "node:os";
45392
+ import path40 from "node:path";
45393
+ function emptyPolicySet() {
45394
+ return { agents: /* @__PURE__ */ new Map(), warnings: [], precedence: policyPrecedenceFromEnv() };
45395
+ }
45396
+ function agentLayersFor(set2, agent) {
45397
+ return set2.agents.get(agent) ?? EMPTY_POLICY_LAYERS;
45398
+ }
45399
+ function globToRegExp(pattern) {
45400
+ let src = "^";
45401
+ for (let i = 0; i < pattern.length; i++) {
45402
+ const ch = pattern[i];
45403
+ if (ch === "*") {
45404
+ while (pattern[i + 1] === "*") i++;
45405
+ src += ".*";
45406
+ } else if ("\\^$.|?+()[]{}".includes(ch)) {
45407
+ src += "\\" + ch;
45408
+ } else {
45409
+ src += ch;
45410
+ }
45411
+ }
45412
+ return new RegExp(src + "$");
45413
+ }
45414
+ function normalizeForMatch(value) {
45415
+ return value.replace(/\\/g, "/");
45416
+ }
45417
+ function resolvePolicyRule(rules, value) {
45418
+ const v = normalizeForMatch(value);
45419
+ for (const rule of rules) {
45420
+ if (globToRegExp(normalizeForMatch(rule.match)).test(v)) return rule;
45421
+ }
45422
+ return null;
45423
+ }
45424
+ function mergeRuleEffect(base2, rule) {
45425
+ if (!rule) return base2;
45426
+ return EFFECT_RANK[rule.effect] > EFFECT_RANK[base2] ? rule.effect : base2;
45427
+ }
45428
+ function pathCandidates(value, root) {
45429
+ const norm = normalizeForMatch(value);
45430
+ if (!root) return [norm];
45431
+ const prefix = normalizeForMatch(root).replace(/\/+$/, "") + "/";
45432
+ const stripped = norm.toLowerCase().startsWith(prefix.toLowerCase()) ? norm.slice(prefix.length) : null;
45433
+ return stripped !== null ? [stripped, norm] : [norm];
45434
+ }
45435
+ function matchAgentPolicyRule(rules, required2, args, root) {
45436
+ if (!rules) return null;
45437
+ const a = args !== null && typeof args === "object" ? args : {};
45438
+ if (required2.includes("execute")) {
45439
+ const cmd = a["command"];
45440
+ if (typeof cmd === "string" && cmd !== "") {
45441
+ const hit = resolvePolicyRule(rules.shell, cmd);
45442
+ if (hit) return hit;
45443
+ }
45444
+ }
45445
+ if (required2.includes("write")) {
45446
+ const p3 = typeof a["path"] === "string" ? a["path"] : typeof a["file_path"] === "string" ? a["file_path"] : "";
45447
+ if (p3 !== "") {
45448
+ for (const candidate of pathCandidates(p3, root)) {
45449
+ const hit = resolvePolicyRule(rules.edit, candidate);
45450
+ if (hit) return hit;
45451
+ }
45452
+ }
45453
+ }
45454
+ return null;
45455
+ }
45456
+ function isPolicyEngineDisabled() {
45457
+ const v = process.env.ZELARI_POLICY?.trim().toLowerCase();
45458
+ return v === "0" || v === "false" || v === "no" || v === "off";
45459
+ }
45460
+ function policyPrecedenceFromEnv() {
45461
+ const v = process.env.ZELARI_POLICY_PRECEDENCE?.trim().toLowerCase();
45462
+ return v === "legacy" ? "legacy" : "restrict-only";
45463
+ }
45464
+ function isPlainObject2(v) {
45465
+ return typeof v === "object" && v !== null && !Array.isArray(v);
45466
+ }
45467
+ function parseRuleList(raw, origin, warnings) {
45468
+ if (raw === void 0) return [];
45469
+ if (!Array.isArray(raw)) {
45470
+ warnings.push(`${origin}: expected an array of rules, got ${typeof raw} \u2014 section ignored.`);
45471
+ return [];
45472
+ }
45473
+ const out = [];
45474
+ raw.forEach((item, i) => {
45475
+ const where = `${origin}[${i}]`;
45476
+ if (!isPlainObject2(item)) {
45477
+ warnings.push(`${where}: rule is not an object \u2014 skipped.`);
45478
+ return;
45479
+ }
45480
+ const match = item["match"];
45481
+ const effect = item["effect"];
45482
+ const reason = item["reason"];
45483
+ if (typeof match !== "string" || match.trim() === "") {
45484
+ warnings.push(`${where}: missing or empty "match" \u2014 skipped.`);
45485
+ return;
45486
+ }
45487
+ if (effect !== "allow" && effect !== "ask" && effect !== "deny") {
45488
+ warnings.push(`${where} ("${match}"): "effect" must be allow|ask|deny \u2014 skipped.`);
45489
+ return;
45490
+ }
45491
+ out.push(
45492
+ typeof reason === "string" && reason.trim() !== "" ? { match, effect, reason } : { match, effect }
45493
+ );
45494
+ });
45495
+ return out;
45496
+ }
45497
+ function parsePolicyFile(raw, origin, warnings) {
45498
+ if (!isPlainObject2(raw)) {
45499
+ warnings.push(`${origin}: policy file is not a JSON object \u2014 file ignored.`);
45500
+ return null;
45501
+ }
45502
+ const version2 = raw["version"];
45503
+ if (version2 !== void 0 && version2 !== 1) {
45504
+ warnings.push(`${origin}: unsupported "version" ${JSON.stringify(version2)} (expected 1) \u2014 file ignored.`);
45505
+ return null;
45506
+ }
45507
+ const agentsRaw = raw["agents"];
45508
+ if (agentsRaw === void 0) {
45509
+ warnings.push(`${origin}: no "agents" key \u2014 file ignored.`);
45510
+ return null;
45511
+ }
45512
+ if (!isPlainObject2(agentsRaw)) {
45513
+ warnings.push(`${origin}: "agents" is not an object \u2014 file ignored.`);
45514
+ return null;
45515
+ }
45516
+ const out = /* @__PURE__ */ new Map();
45517
+ for (const [key, val] of Object.entries(agentsRaw)) {
45518
+ const agent = key.trim().toLowerCase();
45519
+ if (!KNOWN_AGENTS.has(agent)) {
45520
+ warnings.push(`${origin}: unknown agent "${key}" (known: ${POLICY_AGENTS.join(" | ")}) \u2014 ignored.`);
45521
+ continue;
45522
+ }
45523
+ if (!isPlainObject2(val)) {
45524
+ warnings.push(`${origin}: agents.${key} is not an object \u2014 ignored.`);
45525
+ continue;
45526
+ }
45527
+ const shell = parseRuleList(val["shell"], `${origin} agents.${key}.shell`, warnings);
45528
+ const edit = parseRuleList(val["edit"], `${origin} agents.${key}.edit`, warnings);
45529
+ out.set(agent, { shell, edit });
45530
+ }
45531
+ return out;
45532
+ }
45533
+ function readPolicyFile(file2, warnings) {
45534
+ let text;
45535
+ try {
45536
+ text = readFileSync22(file2, "utf8");
45537
+ } catch {
45538
+ return /* @__PURE__ */ new Map();
45539
+ }
45540
+ let parsed;
45541
+ try {
45542
+ parsed = JSON.parse(text);
45543
+ } catch (err) {
45544
+ warnings.push(
45545
+ `${file2}: invalid JSON (${err instanceof Error ? err.message : String(err)}) \u2014 file ignored.`
45546
+ );
45547
+ return /* @__PURE__ */ new Map();
45548
+ }
45549
+ return parsePolicyFile(parsed, file2, warnings) ?? /* @__PURE__ */ new Map();
45550
+ }
45551
+ function loadPolicySet(root, opts = {}) {
45552
+ if (isPolicyEngineDisabled()) return emptyPolicySet();
45553
+ const warnings = [];
45554
+ const precedence = policyPrecedenceFromEnv();
45555
+ const project = readPolicyFile(path40.join(root, ".zelari", "policy.json"), warnings);
45556
+ const global = readPolicyFile(path40.join(opts.homeDir ?? homedir10(), ".zelari", "policy.json"), warnings);
45557
+ const agents = /* @__PURE__ */ new Map();
45558
+ for (const [agent, p3] of project) {
45559
+ agents.set(agent, { project: p3, global: EMPTY_POLICY_RULE_SET });
45560
+ }
45561
+ for (const [agent, g] of global) {
45562
+ const l = agents.get(agent);
45563
+ agents.set(agent, l ? { ...l, global: g } : { project: EMPTY_POLICY_RULE_SET, global: g });
45564
+ }
45565
+ return { agents, warnings, precedence };
45566
+ }
45567
+ var POLICY_AGENTS, KNOWN_AGENTS, EMPTY_POLICY_RULE_SET, EMPTY_POLICY_LAYERS, EFFECT_RANK;
45568
+ var init_policyEngine = __esm({
45569
+ "src/cli/safety/policyEngine.ts"() {
45570
+ "use strict";
45571
+ POLICY_AGENTS = ["lead", "explore", "general", "verify"];
45572
+ KNOWN_AGENTS = new Set(POLICY_AGENTS);
45573
+ EMPTY_POLICY_RULE_SET = { shell: [], edit: [] };
45574
+ EMPTY_POLICY_LAYERS = {
45575
+ global: EMPTY_POLICY_RULE_SET,
45576
+ project: EMPTY_POLICY_RULE_SET
45577
+ };
45578
+ EFFECT_RANK = { allow: 0, ask: 1, deny: 2 };
45579
+ }
45580
+ });
45581
+
45582
+ // src/cli/safety/policyLayers.ts
45583
+ function intersectEffects(...effects) {
45584
+ let best;
45585
+ for (const effect of effects) {
45586
+ if (effect === void 0) continue;
45587
+ if (best === void 0 || EFFECT_RANK[effect] > EFFECT_RANK[best]) best = effect;
45588
+ }
45589
+ return best ?? "allow";
45590
+ }
45591
+ function matchAgentPolicyRuleLayered(layers, precedence, required2, args, root) {
45592
+ if (!layers) return null;
45593
+ if (precedence === "legacy") {
45594
+ return matchAgentPolicyRule(
45595
+ {
45596
+ shell: [...layers.project.shell, ...layers.global.shell],
45597
+ edit: [...layers.project.edit, ...layers.global.edit]
45598
+ },
45599
+ required2,
45600
+ args,
45601
+ root
45602
+ );
45603
+ }
45604
+ const g = matchAgentPolicyRule(layers.global, required2, args, root);
45605
+ const p3 = matchAgentPolicyRule(layers.project, required2, args, root);
45606
+ if (!g) return p3;
45607
+ if (!p3) return g;
45608
+ const win = intersectEffects(p3.effect, g.effect);
45609
+ return p3.effect === win ? p3 : g;
45610
+ }
45611
+ var init_policyLayers = __esm({
45612
+ "src/cli/safety/policyLayers.ts"() {
45613
+ "use strict";
45614
+ init_policyEngine();
45615
+ }
45616
+ });
45617
+
45268
45618
  // src/cli/toolResultCache.ts
45269
45619
  import { createHash as createHash13 } from "node:crypto";
45270
45620
  import { promises as fs19 } from "node:fs";
45271
- import path40 from "node:path";
45621
+ import path41 from "node:path";
45272
45622
  function isToolCacheEnabled() {
45273
45623
  const raw = process.env.ZELARI_TOOL_CACHE;
45274
45624
  return raw !== "0" && raw !== "false" && raw !== "off";
@@ -45353,7 +45703,7 @@ async function statKey(toolName, input, ctx) {
45353
45703
  if (!input || typeof input !== "object") return null;
45354
45704
  const rawPath = input.path;
45355
45705
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
45356
- const abs = path40.isAbsolute(rawPath) ? rawPath : path40.join(ctx.cwd, rawPath);
45706
+ const abs = path41.isAbsolute(rawPath) ? rawPath : path41.join(ctx.cwd, rawPath);
45357
45707
  try {
45358
45708
  const st = await fs19.stat(abs);
45359
45709
  return hashKey({
@@ -45402,10 +45752,13 @@ var init_toolResultCache = __esm({
45402
45752
  // src/cli/tools/krakenModel.ts
45403
45753
  var krakenModel_exports = {};
45404
45754
  __export(krakenModel_exports, {
45755
+ inferModelFamily: () => inferModelFamily,
45405
45756
  isCheapModelId: () => isCheapModelId,
45406
45757
  isKrakenAutoModelEnabled: () => isKrakenAutoModelEnabled,
45407
45758
  parseQualifiedModelRef: () => parseQualifiedModelRef,
45408
45759
  pickCheapModel: () => pickCheapModel,
45760
+ pickDifferentFamily: () => pickDifferentFamily,
45761
+ resolveCrossModelVerifier: () => resolveCrossModelVerifier,
45409
45762
  resolveKrakenPlannerModel: () => resolveKrakenPlannerModel,
45410
45763
  resolveKrakenSubModel: () => resolveKrakenSubModel,
45411
45764
  resolveKrakenSubModelAsync: () => resolveKrakenSubModelAsync,
@@ -45450,6 +45803,44 @@ function parseQualifiedModelRef(ref) {
45450
45803
  if (!provider || !model) return null;
45451
45804
  return { provider, model };
45452
45805
  }
45806
+ function inferModelFamily(provider, model) {
45807
+ const p3 = (provider ?? "").trim().toLowerCase();
45808
+ const m = (model ?? "").trim().toLowerCase();
45809
+ const hay = `${p3} ${m}`.trim();
45810
+ if (!hay) return "other";
45811
+ if (/\b(zhipu|glm)\b/.test(hay)) return "zhipu";
45812
+ if (/\b(google|gemini)\b/.test(hay)) return "google";
45813
+ if (/\b(xai|grok)\b/.test(hay)) return "xai";
45814
+ if (/\b(anthropic|claude)\b/.test(hay)) return "anthropic";
45815
+ if (/\b(openai|chatgpt|codex)\b/.test(hay) || /\bgpt\b/.test(hay) || /(^|\s)o[134]($|[-_.])/.test(hay)) {
45816
+ return "openai";
45817
+ }
45818
+ return p3 || "other";
45819
+ }
45820
+ function pickDifferentFamily(builder, candidates) {
45821
+ const builderFamily = inferModelFamily(builder.provider, builder.model);
45822
+ for (const c of candidates) {
45823
+ const provider = c.provider?.trim() ?? "";
45824
+ const model = c.model?.trim() ?? "";
45825
+ if (!provider || !model) continue;
45826
+ if (inferModelFamily(provider, model) !== builderFamily) {
45827
+ return { provider, model };
45828
+ }
45829
+ }
45830
+ return null;
45831
+ }
45832
+ function resolveCrossModelVerifier(builder, candidates, env = process.env) {
45833
+ const cross = (env.ZELARI_KRAKEN_CROSS_MODEL ?? "").trim().toLowerCase();
45834
+ if (cross === "0" || cross === "false" || cross === "off") return null;
45835
+ const specific = env.ZELARI_KRAKEN_VERIFY_MODEL?.trim();
45836
+ if (specific) {
45837
+ const qualified = parseQualifiedModelRef(specific);
45838
+ if (qualified) return qualified;
45839
+ const provider = builder.provider?.trim();
45840
+ return provider ? { provider, model: specific } : null;
45841
+ }
45842
+ return pickDifferentFamily(builder, candidates);
45843
+ }
45453
45844
  function resolveKrakenSubModel(agent, parentModel, env = process.env, opts = {}) {
45454
45845
  const kindKey = agent === "explore" ? "ZELARI_KRAKEN_EXPLORE_MODEL" : agent === "verify" ? "ZELARI_KRAKEN_VERIFY_MODEL" : "ZELARI_KRAKEN_GENERAL_MODEL";
45455
45846
  const specific = env[kindKey]?.trim();
@@ -45462,6 +45853,13 @@ function resolveKrakenSubModel(agent, parentModel, env = process.env, opts = {})
45462
45853
  }
45463
45854
  return shared2;
45464
45855
  }
45856
+ if (agent === "verify" && opts.familyCandidates && opts.familyCandidates.length > 0) {
45857
+ const picked = pickDifferentFamily(
45858
+ { provider: opts.provider ?? "", model: parentModel },
45859
+ opts.familyCandidates
45860
+ );
45861
+ if (picked) return `${picked.provider}/${picked.model}`;
45862
+ }
45465
45863
  if ((agent === "explore" || agent === "verify") && isKrakenAutoModelEnabled(env) && opts.candidates && opts.candidates.length > 0) {
45466
45864
  const picked = pickCheapModel(parentModel, opts.candidates);
45467
45865
  if (picked) return picked;
@@ -45557,7 +45955,19 @@ function createBuiltinToolRegistry(options = {}) {
45557
45955
  const allowMutators = !readOnly && !verifyMode && !gauntletParent;
45558
45956
  const allowBash = (allowMutators || verifyMode) && !gauntletParent;
45559
45957
  const permPolicy = options.permissionPolicy ?? defaultPermissionPolicy();
45560
- const withPerm = (t) => wrapWithPermissions(t, permPolicy, options.onPermissionAsk);
45958
+ const agentPolicySet = loadPolicySet(root);
45959
+ const agentPolicyLayers = agentLayersFor(
45960
+ agentPolicySet,
45961
+ options.policyAgent ?? "lead"
45962
+ );
45963
+ const withPerm = (t) => wrapWithPermissions(
45964
+ t,
45965
+ permPolicy,
45966
+ options.onPermissionAsk,
45967
+ agentPolicyLayers,
45968
+ agentPolicySet.precedence,
45969
+ root
45970
+ );
45561
45971
  registry4.register(withPerm(safeReadFile));
45562
45972
  registry4.register(withPerm(safeGrepContent));
45563
45973
  registry4.register(withPerm(safeListFiles));
@@ -45691,6 +46101,10 @@ function createBuiltinToolRegistry(options = {}) {
45691
46101
  root,
45692
46102
  audit,
45693
46103
  sessionId: sessionId2,
46104
+ // P0.4 capability inheritance: tentacles intersect THIS
46105
+ // registry's own policy (permPolicy above) — they can never
46106
+ // exceed it.
46107
+ parentPolicy: permPolicy,
45694
46108
  ...options.subAgentProvider ? { provider: options.subAgentProvider } : {},
45695
46109
  ...options.subAgentModel ? { model: options.subAgentModel } : {}
45696
46110
  }),
@@ -45768,7 +46182,7 @@ function taskAgentToProfile(agent) {
45768
46182
  return "explore";
45769
46183
  }
45770
46184
  function createKrakenSubAgentContextFactory(opts) {
45771
- const { root, audit, sessionId: sessionId2, provider: providerOverride, model: modelOverride } = opts;
46185
+ const { root, audit, sessionId: sessionId2, provider: providerOverride, model: modelOverride, parentPolicy } = opts;
45772
46186
  return async ({ agent, cwd: subCwd }) => {
45773
46187
  const cfg = providerOverride ? await providerConfigFor(providerOverride) : await providerFromEnv();
45774
46188
  if (!cfg) return null;
@@ -45787,6 +46201,11 @@ function createKrakenSubAgentContextFactory(opts) {
45787
46201
  const subCfg = { ...effCfg, model };
45788
46202
  const subProfile = taskAgentToProfile(agent);
45789
46203
  const subRoot = subCwd || root;
46204
+ const agentPolicyForSubProfile = defaultPermissionPolicy({ auto: true });
46205
+ const effectiveSubPolicy = intersectPermissionPolicy(
46206
+ parentPolicy ?? agentPolicyForSubProfile,
46207
+ agentPolicyForSubProfile
46208
+ );
45790
46209
  const { registry: subRegistry } = createBuiltinToolRegistry({
45791
46210
  root: subRoot,
45792
46211
  audit,
@@ -45796,7 +46215,9 @@ function createKrakenSubAgentContextFactory(opts) {
45796
46215
  enableSkill: agent === "general",
45797
46216
  diagnostics: false,
45798
46217
  lspProvider: null,
45799
- permissionPolicy: defaultPermissionPolicy({ auto: true })
46218
+ permissionPolicy: effectiveSubPolicy,
46219
+ // P0.5: the tentacle's agent identity drives per-agent policy rules.
46220
+ policyAgent: agent
45800
46221
  });
45801
46222
  return {
45802
46223
  providerStream: buildProviderStream(subCfg),
@@ -45813,7 +46234,7 @@ function createKrakenSubAgentContextFactory(opts) {
45813
46234
  };
45814
46235
  };
45815
46236
  }
45816
- function wrapWithPermissions(original, policy, onAsk) {
46237
+ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence = "restrict-only", root) {
45817
46238
  const required2 = original.permissions ?? [];
45818
46239
  const decisionProbe = resolveToolPermission(original.name, required2, policy);
45819
46240
  if (decisionProbe.action === "allow" && !required2.includes("write") && !required2.includes("execute")) {
@@ -45822,13 +46243,22 @@ function wrapWithPermissions(original, policy, onAsk) {
45822
46243
  ...original,
45823
46244
  execute: async (input, ctx) => {
45824
46245
  const decision = resolveToolPermission(original.name, required2, policy);
45825
- if (decision.action === "deny") {
45826
- return typedErr(`[permission] ${decision.reason}`);
45827
- }
45828
- if (decision.action === "ask") {
46246
+ const rule = agentLayers ? matchAgentPolicyRuleLayered(
46247
+ agentLayers,
46248
+ precedence,
46249
+ required2,
46250
+ input ?? {},
46251
+ root ?? process.cwd()
46252
+ ) : null;
46253
+ const action = mergeRuleEffect(decision.action, rule);
46254
+ const rulePrefix = rule ? `[policy] rule '${rule.match}'${rule.reason ? ` \u2014 ${rule.reason}` : ""}` : "";
46255
+ if (action === "deny") {
46256
+ return typedErr(`[permission] ${rulePrefix || decision.reason}`);
46257
+ }
46258
+ if (action === "ask") {
45829
46259
  if (!onAsk) {
45830
46260
  return typedErr(
45831
- `[permission] ${decision.reason} No interactive approval available (set ZELARI_AUTO=1 to auto-allow, or configure onPermissionAsk).`
46261
+ `[permission] ${rulePrefix ? `${rulePrefix} ` : ""}${decision.reason} No interactive approval available (set ZELARI_AUTO=1 to auto-allow, or configure onPermissionAsk).`
45832
46262
  );
45833
46263
  }
45834
46264
  try {
@@ -46044,6 +46474,8 @@ var init_toolRegistry = __esm({
46044
46474
  init_providerConfig();
46045
46475
  init_toolPermissions();
46046
46476
  init_lifecycleHooks();
46477
+ init_policyEngine();
46478
+ init_policyLayers();
46047
46479
  init_toolResultCache();
46048
46480
  init_toolTypes();
46049
46481
  init_skills2();
@@ -46064,7 +46496,7 @@ var init_toolRegistry = __esm({
46064
46496
 
46065
46497
  // src/cli/metrics.ts
46066
46498
  import { promises as fs20, existsSync as existsSync25, statSync as statSync4, renameSync as renameSync3, appendFileSync as appendFileSync3, mkdirSync as mkdirSync14 } from "node:fs";
46067
- import path41 from "node:path";
46499
+ import path42 from "node:path";
46068
46500
  import os9 from "node:os";
46069
46501
  async function readMetrics(file2) {
46070
46502
  let raw = "";
@@ -46113,8 +46545,8 @@ var init_metrics3 = __esm({
46113
46545
  file;
46114
46546
  writeQueue = Promise.resolve();
46115
46547
  constructor(file2) {
46116
- this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path41.join(os9.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
46117
- mkdirSync14(path41.dirname(this.file), { recursive: true });
46548
+ this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path42.join(os9.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
46549
+ mkdirSync14(path42.dirname(this.file), { recursive: true });
46118
46550
  }
46119
46551
  /** Metrics file path — doctor/summary readers use this. */
46120
46552
  get filePath() {
@@ -46246,12 +46678,12 @@ Current policy: **lead only**.
46246
46678
  // src/cli/state/fileStateStore.ts
46247
46679
  import { createHash as createHash15, randomUUID as randomUUID3 } from "node:crypto";
46248
46680
  import { promises as fs21 } from "node:fs";
46249
- import * as path43 from "node:path";
46681
+ import * as path45 from "node:path";
46250
46682
  function shortId() {
46251
46683
  return randomUUID3().replace(/-/g, "").slice(0, 12);
46252
46684
  }
46253
46685
  async function writeJsonAtomic(filePath, data) {
46254
- await fs21.mkdir(path43.dirname(filePath), { recursive: true });
46686
+ await fs21.mkdir(path45.dirname(filePath), { recursive: true });
46255
46687
  const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
46256
46688
  await fs21.writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
46257
46689
  await fs21.rename(tmp, filePath);
@@ -46312,11 +46744,11 @@ var init_fileStateStore = __esm({
46312
46744
  indexPath = "";
46313
46745
  async init(projectRoot) {
46314
46746
  this.root = projectRoot;
46315
- this.stateDir = path43.join(projectRoot, ".zelari", "state");
46316
- this.commitsDir = path43.join(this.stateDir, "commits");
46317
- this.artifactsDir = path43.join(this.stateDir, "artifacts");
46318
- this.headPath = path43.join(this.stateDir, "HEAD.json");
46319
- this.indexPath = path43.join(this.stateDir, "index.jsonl");
46747
+ this.stateDir = path45.join(projectRoot, ".zelari", "state");
46748
+ this.commitsDir = path45.join(this.stateDir, "commits");
46749
+ this.artifactsDir = path45.join(this.stateDir, "artifacts");
46750
+ this.headPath = path45.join(this.stateDir, "HEAD.json");
46751
+ this.indexPath = path45.join(this.stateDir, "index.jsonl");
46320
46752
  await fs21.mkdir(this.commitsDir, { recursive: true });
46321
46753
  await fs21.mkdir(this.artifactsDir, { recursive: true });
46322
46754
  }
@@ -46329,13 +46761,13 @@ var init_fileStateStore = __esm({
46329
46761
  const discoveries = input.discoveries ?? [];
46330
46762
  const parent = await this.head();
46331
46763
  const id3 = shortId();
46332
- const artifactRel = path43.join("artifacts", id3);
46333
- const artifactAbs = path43.join(this.artifactsDir, id3);
46764
+ const artifactRel = path45.join("artifacts", id3);
46765
+ const artifactAbs = path45.join(this.artifactsDir, id3);
46334
46766
  await fs21.mkdir(artifactAbs, { recursive: true });
46335
46767
  const summary = defaultSummary(input, discoveries);
46336
- await fs21.writeFile(path43.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
46337
- await writeJsonAtomic(path43.join(artifactAbs, "discoveries.json"), discoveries);
46338
- await writeJsonAtomic(path43.join(artifactAbs, "verification.json"), input.verification);
46768
+ await fs21.writeFile(path45.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
46769
+ await writeJsonAtomic(path45.join(artifactAbs, "discoveries.json"), discoveries);
46770
+ await writeJsonAtomic(path45.join(artifactAbs, "verification.json"), input.verification);
46339
46771
  const meta3 = {
46340
46772
  id: id3,
46341
46773
  parentId: parent?.id ?? null,
@@ -46347,14 +46779,14 @@ var init_fileStateStore = __esm({
46347
46779
  workspaceCheckpointId: input.workspaceCheckpointId,
46348
46780
  verification: {
46349
46781
  ...input.verification,
46350
- reportPath: input.verification.reportPath ?? path43.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
46782
+ reportPath: input.verification.reportPath ?? path45.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
46351
46783
  },
46352
46784
  changedPaths: input.changedPaths ?? [],
46353
46785
  stablePromptHash: input.stablePromptHash,
46354
46786
  discoveryCount: discoveries.length,
46355
46787
  artifactDir: artifactRel.replace(/\\/g, "/")
46356
46788
  };
46357
- await writeJsonAtomic(path43.join(this.commitsDir, `${id3}.json`), meta3);
46789
+ await writeJsonAtomic(path45.join(this.commitsDir, `${id3}.json`), meta3);
46358
46790
  await writeJsonAtomic(this.headPath, { id: id3, updatedAt: meta3.createdAt });
46359
46791
  await fs21.appendFile(this.indexPath, JSON.stringify({ id: id3, createdAt: meta3.createdAt, label: meta3.label }) + "\n", "utf8");
46360
46792
  return stripStored(meta3);
@@ -46365,7 +46797,7 @@ var init_fileStateStore = __esm({
46365
46797
  return this.get(head.id);
46366
46798
  }
46367
46799
  async get(id3) {
46368
- const stored = await readJsonFile(path43.join(this.commitsDir, `${id3}.json`));
46800
+ const stored = await readJsonFile(path45.join(this.commitsDir, `${id3}.json`));
46369
46801
  return stored ? stripStored(stored) : null;
46370
46802
  }
46371
46803
  async list(limit = 20) {
@@ -46404,9 +46836,9 @@ var init_fileStateStore = __esm({
46404
46836
  async loadDiscoveries(id3) {
46405
46837
  const meta3 = id3 ? await this.get(id3) : await this.head();
46406
46838
  if (!meta3) return [];
46407
- const stored = await readJsonFile(path43.join(this.commitsDir, `${meta3.id}.json`));
46839
+ const stored = await readJsonFile(path45.join(this.commitsDir, `${meta3.id}.json`));
46408
46840
  if (!stored?.artifactDir) return [];
46409
- const discPath = path43.join(this.stateDir, stored.artifactDir, "discoveries.json");
46841
+ const discPath = path45.join(this.stateDir, stored.artifactDir, "discoveries.json");
46410
46842
  return await readJsonFile(discPath) ?? [];
46411
46843
  }
46412
46844
  async materializeContext(id3, maxChars = DEFAULT_MATERIALIZE_CHARS) {
@@ -47357,7 +47789,7 @@ var init_mode = __esm({
47357
47789
  });
47358
47790
 
47359
47791
  // src/cli/headless.ts
47360
- import { readFileSync as readFileSync22 } from "node:fs";
47792
+ import { readFileSync as readFileSync23 } from "node:fs";
47361
47793
  function defaultProfileForMode(mode) {
47362
47794
  switch (mode) {
47363
47795
  case "council":
@@ -47378,6 +47810,7 @@ function parseHeadlessFlags(argv) {
47378
47810
  let phase2 = "build";
47379
47811
  let modeExplicit = false;
47380
47812
  let councilFlag = false;
47813
+ let sawModeAuto = false;
47381
47814
  let provider;
47382
47815
  let model;
47383
47816
  let history2;
@@ -47412,7 +47845,7 @@ function parseHeadlessFlags(argv) {
47412
47845
  const next = argv[i + 1];
47413
47846
  if (next) {
47414
47847
  try {
47415
- const fromFile = readFileSync22(next, "utf-8");
47848
+ const fromFile = readFileSync23(next, "utf-8");
47416
47849
  if (fromFile.trim()) task = fromFile;
47417
47850
  } catch {
47418
47851
  }
@@ -47422,11 +47855,17 @@ function parseHeadlessFlags(argv) {
47422
47855
  councilFlag = true;
47423
47856
  } else if (arg === "--mode") {
47424
47857
  const next = argv[i + 1];
47858
+ if (next !== void 0 && next.trim().toLowerCase() === "auto") {
47859
+ sawModeAuto = true;
47860
+ modeExplicit = true;
47861
+ i++;
47862
+ continue;
47863
+ }
47425
47864
  const parsed = next ? parseMode(next) : null;
47426
47865
  if (!parsed) {
47427
47866
  return {
47428
47867
  options: null,
47429
- error: `--mode requires 'kraken', 'council', or 'zelari' (agent=alias), got '${next ?? "(missing)"}'`
47868
+ error: `--mode requires 'kraken', 'council', 'zelari', or 'auto' (agent=alias), got '${next ?? "(missing)"}'`
47430
47869
  };
47431
47870
  }
47432
47871
  mode = parsed;
@@ -47455,7 +47894,7 @@ function parseHeadlessFlags(argv) {
47455
47894
  let raw = null;
47456
47895
  if (arg === "--history-file") {
47457
47896
  try {
47458
- raw = readFileSync22(next, "utf-8");
47897
+ raw = readFileSync23(next, "utf-8");
47459
47898
  } catch {
47460
47899
  raw = null;
47461
47900
  }
@@ -47550,7 +47989,7 @@ function parseHeadlessFlags(argv) {
47550
47989
  const next = argv[i + 1];
47551
47990
  if (next) {
47552
47991
  try {
47553
- const fromFile = readFileSync22(next, "utf-8");
47992
+ const fromFile = readFileSync23(next, "utf-8");
47554
47993
  if (fromFile.trim()) krakenGraph = fromFile;
47555
47994
  } catch {
47556
47995
  }
@@ -47588,6 +48027,7 @@ function parseHeadlessFlags(argv) {
47588
48027
  mode,
47589
48028
  phase: phase2,
47590
48029
  useCouncil: mode === "council",
48030
+ ...sawModeAuto ? { orchestrationAuto: true } : {},
47591
48031
  provider,
47592
48032
  model,
47593
48033
  ...history2 && history2.length > 0 ? { history: history2 } : {},
@@ -48115,7 +48555,7 @@ var init_claudeProvider = __esm({
48115
48555
  // src/cli/memory/legacyImport.ts
48116
48556
  import { createHash as createHash16 } from "node:crypto";
48117
48557
  import { promises as fs22 } from "node:fs";
48118
- import * as path44 from "node:path";
48558
+ import * as path46 from "node:path";
48119
48559
  function sourceId(fact, line) {
48120
48560
  return `jsonl:${fact.id ?? createHash16("sha256").update(line).digest("hex")}`;
48121
48561
  }
@@ -48133,7 +48573,7 @@ function timestamp(value) {
48133
48573
  }
48134
48574
  async function importLegacyMemoryLog(backend, service) {
48135
48575
  const result = { found: 0, imported: 0, skipped: 0, corrupt: 0 };
48136
- const logPath = path44.join(path44.dirname(backend.databasePath), "log.jsonl");
48576
+ const logPath = path46.join(path46.dirname(backend.databasePath), "log.jsonl");
48137
48577
  let raw;
48138
48578
  try {
48139
48579
  raw = await fs22.readFile(logPath, "utf8");
@@ -48316,7 +48756,7 @@ var init_sqliteCodec = __esm({
48316
48756
  // src/cli/memory/sqliteRpc.ts
48317
48757
  import { existsSync as existsSync27 } from "node:fs";
48318
48758
  import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
48319
- import * as path45 from "node:path";
48759
+ import * as path47 from "node:path";
48320
48760
  import { Worker } from "node:worker_threads";
48321
48761
  function isBusy(error51) {
48322
48762
  const candidate = error51;
@@ -48325,10 +48765,10 @@ function isBusy(error51) {
48325
48765
  );
48326
48766
  }
48327
48767
  function resolveWorkerUrl() {
48328
- const here = path45.dirname(fileURLToPath2(import.meta.url));
48329
- const direct = path45.join(here, "sqliteWorker.mjs");
48768
+ const here = path47.dirname(fileURLToPath2(import.meta.url));
48769
+ const direct = path47.join(here, "sqliteWorker.mjs");
48330
48770
  if (existsSync27(direct)) return pathToFileURL2(direct);
48331
- return pathToFileURL2(path45.join(here, "memory", "sqliteWorker.mjs"));
48771
+ return pathToFileURL2(path47.join(here, "memory", "sqliteWorker.mjs"));
48332
48772
  }
48333
48773
  var SqliteWorkerRpc;
48334
48774
  var init_sqliteRpc = __esm({
@@ -48617,7 +49057,7 @@ WHERE NOT EXISTS (SELECT 1 FROM memory_fts f WHERE f.node_id = n.id);
48617
49057
  // src/cli/memory/sqliteBackend.ts
48618
49058
  import { createHash as createHash17, randomUUID as randomUUID4 } from "node:crypto";
48619
49059
  import { promises as fs23 } from "node:fs";
48620
- import * as path46 from "node:path";
49060
+ import * as path48 from "node:path";
48621
49061
  function boundedLimit(value, fallback = 50) {
48622
49062
  return Math.max(1, Math.min(Math.floor(value ?? fallback), 1e5));
48623
49063
  }
@@ -48679,16 +49119,16 @@ VALUES (${Array.from({ length: 19 }, () => "?").join(",")})`;
48679
49119
  try {
48680
49120
  resolved = await fs23.realpath(projectRoot);
48681
49121
  } catch {
48682
- resolved = path46.resolve(projectRoot);
49122
+ resolved = path48.resolve(projectRoot);
48683
49123
  }
48684
49124
  if (this.initialized && resolved === this.projectRoot) return;
48685
49125
  if (this.initialized) await this.close();
48686
49126
  const filename = this.options.filename ?? "memory.db";
48687
- if (path46.basename(filename) !== filename || filename === "." || filename === "..") {
49127
+ if (path48.basename(filename) !== filename || filename === "." || filename === "..") {
48688
49128
  throw new Error("SQLite memory filename must not contain a path.");
48689
49129
  }
48690
- const zelariDirectory = path46.join(resolved, ".zelari");
48691
- const directory = path46.join(zelariDirectory, "memory");
49130
+ const zelariDirectory = path48.join(resolved, ".zelari");
49131
+ const directory = path48.join(zelariDirectory, "memory");
48692
49132
  for (const candidate of [zelariDirectory, directory]) {
48693
49133
  let stat2;
48694
49134
  try {
@@ -48707,12 +49147,12 @@ VALUES (${Array.from({ length: 19 }, () => "?").join(",")})`;
48707
49147
  }
48708
49148
  }
48709
49149
  const canonicalDirectory = await fs23.realpath(directory);
48710
- const relativeDirectory = path46.relative(resolved, canonicalDirectory);
48711
- if (relativeDirectory.startsWith("..") || path46.isAbsolute(relativeDirectory)) {
49150
+ const relativeDirectory = path48.relative(resolved, canonicalDirectory);
49151
+ if (relativeDirectory.startsWith("..") || path48.isAbsolute(relativeDirectory)) {
48712
49152
  throw new Error("SQLite memory directory resolves outside the active project.");
48713
49153
  }
48714
49154
  this.projectRoot = resolved;
48715
- this.databasePath = path46.join(canonicalDirectory, filename);
49155
+ this.databasePath = path48.join(canonicalDirectory, filename);
48716
49156
  const opened = await this.rpc.open({
48717
49157
  dbPath: this.databasePath,
48718
49158
  schemaSql: SQLITE_MEMORY_BASE_SCHEMA,
@@ -49245,7 +49685,7 @@ __export(serviceFactory_exports, {
49245
49685
  });
49246
49686
  import { createHash as createHash18 } from "node:crypto";
49247
49687
  import { promises as fs24 } from "node:fs";
49248
- import * as path47 from "node:path";
49688
+ import * as path49 from "node:path";
49249
49689
  function isMemoryV2Enabled(env = process.env) {
49250
49690
  if (env.ZELARI_MEMORY === "0") return false;
49251
49691
  if (env.ZELARI_MEMORY_BACKEND === "file" || env.ZELARI_MEMORY_BACKEND === "jsonl") return false;
@@ -49266,7 +49706,7 @@ async function canonicalProjectId(projectRoot) {
49266
49706
  try {
49267
49707
  canonical = await fs24.realpath(projectRoot);
49268
49708
  } catch {
49269
- canonical = path47.resolve(projectRoot);
49709
+ canonical = path49.resolve(projectRoot);
49270
49710
  }
49271
49711
  canonical = canonical.replace(/\\/g, "/").replace(/\/$/, "");
49272
49712
  if (process.platform === "win32") canonical = canonical.toLocaleLowerCase("en-US");
@@ -49334,14 +49774,14 @@ var init_serviceFactory = __esm({
49334
49774
  });
49335
49775
 
49336
49776
  // src/cli/workspace/projectInstructions.ts
49337
- import { existsSync as existsSync28, readFileSync as readFileSync23 } from "node:fs";
49777
+ import { existsSync as existsSync28, readFileSync as readFileSync24 } from "node:fs";
49338
49778
  import { join as join26 } from "node:path";
49339
49779
  function loadProjectInstructions(projectRoot = process.cwd(), maxChars = MAX_CHARS) {
49340
49780
  for (const name of CANDIDATES) {
49341
49781
  const full = join26(projectRoot, name);
49342
49782
  if (!existsSync28(full)) continue;
49343
49783
  try {
49344
- let raw = readFileSync23(full, "utf8");
49784
+ let raw = readFileSync24(full, "utf8");
49345
49785
  raw = raw.replace(/\r\n/g, "\n").trim();
49346
49786
  if (!raw) continue;
49347
49787
  if (raw.length <= maxChars) {
@@ -49385,7 +49825,7 @@ __export(workspaceSummary_exports, {
49385
49825
  buildWorkspaceSummary: () => buildWorkspaceSummary,
49386
49826
  buildZelariReadHint: () => buildZelariReadHint
49387
49827
  });
49388
- import { existsSync as existsSync29, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
49828
+ import { existsSync as existsSync29, readFileSync as readFileSync25, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
49389
49829
  import { join as join27, relative as relative2 } from "node:path";
49390
49830
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
49391
49831
  const { maxEntries = 30, maxChars = 3500, maxDeps = 24, maxScripts = 16 } = options;
@@ -49423,7 +49863,7 @@ function buildPlanSummary(projectRoot = process.cwd(), options) {
49423
49863
  if (!existsSync29(planPath)) return null;
49424
49864
  let plan;
49425
49865
  try {
49426
- plan = JSON.parse(readFileSync24(planPath, "utf8"));
49866
+ plan = JSON.parse(readFileSync25(planPath, "utf8"));
49427
49867
  } catch {
49428
49868
  return null;
49429
49869
  }
@@ -49581,7 +50021,7 @@ function readPackageJson(projectRoot) {
49581
50021
  const p3 = join27(projectRoot, "package.json");
49582
50022
  if (!existsSync29(p3)) return null;
49583
50023
  try {
49584
- return JSON.parse(readFileSync24(p3, "utf8"));
50024
+ return JSON.parse(readFileSync25(p3, "utf8"));
49585
50025
  } catch {
49586
50026
  return null;
49587
50027
  }
@@ -49709,7 +50149,7 @@ var composeContext_exports = {};
49709
50149
  __export(composeContext_exports, {
49710
50150
  composeProjectContext: () => composeProjectContext
49711
50151
  });
49712
- import { existsSync as existsSync31, readdirSync as readdirSync7, readFileSync as readFileSync25 } from "node:fs";
50152
+ import { existsSync as existsSync31, readdirSync as readdirSync7, readFileSync as readFileSync26 } from "node:fs";
49713
50153
  import { join as join29 } from "node:path";
49714
50154
  function cap2(text, max, label) {
49715
50155
  if (!text || text.length <= max) return { text: text || "", truncated: false };
@@ -49850,15 +50290,15 @@ function readDurableHeadSync(projectRoot) {
49850
50290
  try {
49851
50291
  const headPath = join29(projectRoot, ".zelari", "state", "HEAD.json");
49852
50292
  if (!existsSync31(headPath)) return "";
49853
- const head = JSON.parse(readFileSync25(headPath, "utf8"));
50293
+ const head = JSON.parse(readFileSync26(headPath, "utf8"));
49854
50294
  if (!head?.id) return "";
49855
50295
  const metaPath = join29(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
49856
50296
  if (!existsSync31(metaPath)) return "";
49857
- const meta3 = JSON.parse(readFileSync25(metaPath, "utf8"));
50297
+ const meta3 = JSON.parse(readFileSync26(metaPath, "utf8"));
49858
50298
  const discPath = meta3.artifactDir ? join29(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join29(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
49859
50299
  let discoveries = [];
49860
50300
  if (existsSync31(discPath)) {
49861
- discoveries = JSON.parse(readFileSync25(discPath, "utf8"));
50301
+ discoveries = JSON.parse(readFileSync26(discPath, "utf8"));
49862
50302
  }
49863
50303
  const reusable = discoveries.filter((d) => d.reusable !== false);
49864
50304
  const lines = [
@@ -49891,13 +50331,13 @@ var planDetect_exports = {};
49891
50331
  __export(planDetect_exports, {
49892
50332
  hasWorkspacePlan: () => hasWorkspacePlan
49893
50333
  });
49894
- import { existsSync as existsSync32, readFileSync as readFileSync26 } from "node:fs";
50334
+ import { existsSync as existsSync32, readFileSync as readFileSync27 } from "node:fs";
49895
50335
  import { join as join30 } from "node:path";
49896
50336
  function hasWorkspacePlan(projectRoot = process.cwd()) {
49897
50337
  const planPath = join30(resolveWorkspaceRoot(projectRoot), "plan.json");
49898
50338
  if (!existsSync32(planPath)) return false;
49899
50339
  try {
49900
- const parsed = JSON.parse(readFileSync26(planPath, "utf8"));
50340
+ const parsed = JSON.parse(readFileSync27(planPath, "utf8"));
49901
50341
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
49902
50342
  } catch {
49903
50343
  return false;
@@ -49959,7 +50399,7 @@ import {
49959
50399
  existsSync as existsSync33,
49960
50400
  readdirSync as readdirSync8,
49961
50401
  writeFileSync as writeFileSync17,
49962
- readFileSync as readFileSync27,
50402
+ readFileSync as readFileSync28,
49963
50403
  mkdirSync as mkdirSync15,
49964
50404
  renameSync as renameSync4
49965
50405
  } from "node:fs";
@@ -49980,7 +50420,7 @@ function readPlan(ctx) {
49980
50420
  if (existsSync33(jsonPath)) {
49981
50421
  try {
49982
50422
  const parsed = JSON.parse(
49983
- readFileSync27(jsonPath, "utf8")
50423
+ readFileSync28(jsonPath, "utf8")
49984
50424
  );
49985
50425
  const { phases, tasks, milestones, ...root } = parsed;
49986
50426
  return {
@@ -49992,8 +50432,8 @@ function readPlan(ctx) {
49992
50432
  } catch {
49993
50433
  }
49994
50434
  }
49995
- const path72 = workspaceFile(ctx.rootDir, "plan");
49996
- const doc = ctx.storage.readIfExists(path72);
50435
+ const path74 = workspaceFile(ctx.rootDir, "plan");
50436
+ const doc = ctx.storage.readIfExists(path74);
49997
50437
  if (!doc) return { phases: [], tasks: [], milestones: [] };
49998
50438
  const meta3 = doc.meta;
49999
50439
  return {
@@ -50171,7 +50611,7 @@ function addMilestoneRecord(ctx, summary, input) {
50171
50611
  dueDate: input.dueDate,
50172
50612
  targetVersion: version2
50173
50613
  });
50174
- const path72 = join31(ctx.rootDir, "milestones", `${id3}.md`);
50614
+ const path74 = join31(ctx.rootDir, "milestones", `${id3}.md`);
50175
50615
  const meta3 = {
50176
50616
  kind: "milestone",
50177
50617
  id: id3,
@@ -50188,7 +50628,7 @@ function addMilestoneRecord(ctx, summary, input) {
50188
50628
  `Target version: ${version2}`,
50189
50629
  ""
50190
50630
  ].join("\n");
50191
- ctx.storage.write(path72, meta3, body);
50631
+ ctx.storage.write(path74, meta3, body);
50192
50632
  return { id: id3, created: true };
50193
50633
  }
50194
50634
  function readPlanSummary(ctx) {
@@ -50392,7 +50832,7 @@ function addIdeaStub(ctx) {
50392
50832
  const tags = args["tags"] ?? [];
50393
50833
  const category = args["category"] ?? "General";
50394
50834
  const id3 = `${nextAdrId(ctx)}-${slugify3(title)}`;
50395
- const path72 = workspaceArtifact(ctx.rootDir, "decisions", id3);
50835
+ const path74 = workspaceArtifact(ctx.rootDir, "decisions", id3);
50396
50836
  const meta3 = {
50397
50837
  kind: "adr",
50398
50838
  status: "proposed",
@@ -50418,7 +50858,7 @@ function addIdeaStub(ctx) {
50418
50858
  ...consequences.map((c) => `- ${c}`),
50419
50859
  ""
50420
50860
  ].join("\n");
50421
- ctx.storage.write(path72, meta3, body);
50861
+ ctx.storage.write(path74, meta3, body);
50422
50862
  return `ADR ${id3} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
50423
50863
  });
50424
50864
  }
@@ -50500,14 +50940,14 @@ function createDocumentStub(ctx) {
50500
50940
  ctx.storage.write(risksPath, riskMeta, content);
50501
50941
  return `Document "${title}" created at risks.md (workspace root).`;
50502
50942
  }
50503
- const path72 = workspaceArtifact(ctx.rootDir, "docs", slug);
50943
+ const path74 = workspaceArtifact(ctx.rootDir, "docs", slug);
50504
50944
  const meta3 = {
50505
50945
  kind: "doc",
50506
50946
  id: slug,
50507
50947
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
50508
50948
  tags
50509
50949
  };
50510
- ctx.storage.write(path72, meta3, content);
50950
+ ctx.storage.write(path74, meta3, content);
50511
50951
  return `Document "${title}" created at docs/${slug}.md.`;
50512
50952
  });
50513
50953
  }
@@ -50544,7 +50984,7 @@ function searchDocumentsStub(ctx) {
50544
50984
  const results = [];
50545
50985
  for (const file2 of files) {
50546
50986
  if (!existsSync33(file2)) continue;
50547
- const raw = readFileSync27(file2, "utf8");
50987
+ const raw = readFileSync28(file2, "utf8");
50548
50988
  const content = raw.toLowerCase();
50549
50989
  let idx = -1;
50550
50990
  let matchLen = 0;
@@ -50883,21 +51323,21 @@ var init_mcpClient = __esm({
50883
51323
  import {
50884
51324
  existsSync as existsSync34,
50885
51325
  mkdirSync as mkdirSync16,
50886
- readFileSync as readFileSync28,
51326
+ readFileSync as readFileSync29,
50887
51327
  writeFileSync as writeFileSync18
50888
51328
  } from "node:fs";
50889
51329
  import { dirname as dirname9, join as join32 } from "node:path";
50890
- import { homedir as homedir10 } from "node:os";
51330
+ import { homedir as homedir11 } from "node:os";
50891
51331
  function getUserMcpPath() {
50892
- return join32(homedir10(), ".zelari-code", "mcp.json");
51332
+ return join32(homedir11(), ".zelari-code", "mcp.json");
50893
51333
  }
50894
51334
  function getProjectMcpPath(projectRoot) {
50895
51335
  return join32(projectRoot, ".zelari", "mcp.json");
50896
51336
  }
50897
- function readFile4(path72) {
50898
- if (!existsSync34(path72)) return {};
51337
+ function readFile4(path74) {
51338
+ if (!existsSync34(path74)) return {};
50899
51339
  try {
50900
- const parsed = JSON.parse(readFileSync28(path72, "utf8"));
51340
+ const parsed = JSON.parse(readFileSync29(path74, "utf8"));
50901
51341
  const out = {};
50902
51342
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
50903
51343
  if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
@@ -50913,10 +51353,10 @@ function readFile4(path72) {
50913
51353
  return {};
50914
51354
  }
50915
51355
  }
50916
- function writeFile2(path72, servers) {
50917
- mkdirSync16(dirname9(path72), { recursive: true });
51356
+ function writeFile3(path74, servers) {
51357
+ mkdirSync16(dirname9(path74), { recursive: true });
50918
51358
  const body = { mcpServers: servers };
50919
- writeFileSync18(path72, `${JSON.stringify(body, null, 2)}
51359
+ writeFileSync18(path74, `${JSON.stringify(body, null, 2)}
50920
51360
  `, "utf8");
50921
51361
  }
50922
51362
  function listMcpServers(projectRoot) {
@@ -50949,9 +51389,9 @@ function upsertMcpServer(opts) {
50949
51389
  if (!opts.config.command?.trim()) {
50950
51390
  return { ok: false, error: "command is required" };
50951
51391
  }
50952
- let path72;
51392
+ let path74;
50953
51393
  if (opts.scope === "user") {
50954
- path72 = getUserMcpPath();
51394
+ path74 = getUserMcpPath();
50955
51395
  } else {
50956
51396
  const root = opts.projectRoot?.trim();
50957
51397
  if (!root) {
@@ -50960,30 +51400,30 @@ function upsertMcpServer(opts) {
50960
51400
  error: "projectRoot required for project scope (Open Folder first)"
50961
51401
  };
50962
51402
  }
50963
- path72 = getProjectMcpPath(root);
51403
+ path74 = getProjectMcpPath(root);
50964
51404
  }
50965
- const current = readFile4(path72);
51405
+ const current = readFile4(path74);
50966
51406
  current[name] = {
50967
51407
  command: opts.config.command.trim(),
50968
51408
  args: opts.config.args,
50969
51409
  env: opts.config.env,
50970
51410
  enabled: opts.config.enabled !== false
50971
51411
  };
50972
- writeFile2(path72, current);
50973
- return { ok: true, path: path72 };
51412
+ writeFile3(path74, current);
51413
+ return { ok: true, path: path74 };
50974
51414
  }
50975
51415
  function removeMcpServer(opts) {
50976
- const path72 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
50977
- if (!path72) {
51416
+ const path74 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
51417
+ if (!path74) {
50978
51418
  return { ok: false, error: "projectRoot required for project scope" };
50979
51419
  }
50980
- const current = readFile4(path72);
51420
+ const current = readFile4(path74);
50981
51421
  if (!(opts.name in current)) {
50982
- return { ok: false, error: `Server "${opts.name}" not found in ${path72}` };
51422
+ return { ok: false, error: `Server "${opts.name}" not found in ${path74}` };
50983
51423
  }
50984
51424
  delete current[opts.name];
50985
- writeFile2(path72, current);
50986
- return { ok: true, path: path72 };
51425
+ writeFile3(path74, current);
51426
+ return { ok: true, path: path74 };
50987
51427
  }
50988
51428
  var init_mcpConfigIo = __esm({
50989
51429
  "src/cli/mcp/mcpConfigIo.ts"() {
@@ -51125,14 +51565,14 @@ __export(mcpManager_exports, {
51125
51565
  readMcpConfig: () => readMcpConfig,
51126
51566
  registerMcpTools: () => registerMcpTools
51127
51567
  });
51128
- import { existsSync as existsSync35, readFileSync as readFileSync29 } from "node:fs";
51568
+ import { existsSync as existsSync35, readFileSync as readFileSync30 } from "node:fs";
51129
51569
  import { join as join33 } from "node:path";
51130
- import { homedir as homedir11 } from "node:os";
51570
+ import { homedir as homedir12 } from "node:os";
51131
51571
  function readMcpConfig(projectRoot = process.cwd(), opts) {
51132
51572
  const merged = {};
51133
51573
  const paths = [];
51134
51574
  if (process.env["ZELARI_MCP_USER"] !== "0") {
51135
- paths.push(join33(homedir11(), ".zelari-code", "mcp.json"));
51575
+ paths.push(join33(homedir12(), ".zelari-code", "mcp.json"));
51136
51576
  }
51137
51577
  if (!opts?.skipProjectMcp) {
51138
51578
  paths.push(join33(projectRoot, ".zelari", "mcp.json"));
@@ -51140,7 +51580,7 @@ function readMcpConfig(projectRoot = process.cwd(), opts) {
51140
51580
  for (const p3 of paths) {
51141
51581
  if (!existsSync35(p3)) continue;
51142
51582
  try {
51143
- const parsed = JSON.parse(readFileSync29(p3, "utf8"));
51583
+ const parsed = JSON.parse(readFileSync30(p3, "utf8"));
51144
51584
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
51145
51585
  if (!cfg || typeof cfg.command !== "string" || cfg.command.length === 0) continue;
51146
51586
  merged[name] = cfg;
@@ -51379,15 +51819,15 @@ __export(agentsMd_exports, {
51379
51819
  serializeAgentsMd: () => serializeAgentsMd,
51380
51820
  updateAgentsMd: () => updateAgentsMd
51381
51821
  });
51382
- import { existsSync as existsSync36, readFileSync as readFileSync30, writeFileSync as writeFileSync19 } from "node:fs";
51822
+ import { existsSync as existsSync36, readFileSync as readFileSync31, writeFileSync as writeFileSync19 } from "node:fs";
51383
51823
  import { createHash as createHash19 } from "node:crypto";
51384
51824
  import { join as join34 } from "node:path";
51385
51825
  import { readFile as readFile5 } from "node:fs/promises";
51386
51826
  async function readPackageJson2(projectRoot) {
51387
- const path72 = join34(projectRoot, "package.json");
51388
- if (!existsSync36(path72)) return null;
51827
+ const path74 = join34(projectRoot, "package.json");
51828
+ if (!existsSync36(path74)) return null;
51389
51829
  try {
51390
- return JSON.parse(await readFile5(path72, "utf8"));
51830
+ return JSON.parse(await readFile5(path74, "utf8"));
51391
51831
  } catch {
51392
51832
  return null;
51393
51833
  }
@@ -51437,7 +51877,7 @@ async function genConventions(ctx) {
51437
51877
  const lines = [];
51438
51878
  const claudeMd = join34(ctx.projectRoot, "CLAUDE.MD");
51439
51879
  if (existsSync36(claudeMd)) {
51440
- const content = readFileSync30(claudeMd, "utf8");
51880
+ const content = readFileSync31(claudeMd, "utf8");
51441
51881
  const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
51442
51882
  if (match) {
51443
51883
  lines.push('<!-- Extracted from CLAUDE.MD "Architecture rules" -->');
@@ -51469,9 +51909,9 @@ async function genBuild(ctx) {
51469
51909
  ].join("\n");
51470
51910
  }
51471
51911
  async function genOpenQuestions(ctx) {
51472
- const path72 = join34(ctx.rootDir, "risks.md");
51473
- if (!existsSync36(path72)) return "_No open questions._";
51474
- const content = readFileSync30(path72, "utf8");
51912
+ const path74 = join34(ctx.rootDir, "risks.md");
51913
+ if (!existsSync36(path74)) return "_No open questions._";
51914
+ const content = readFileSync31(path74, "utf8");
51475
51915
  const lines = content.split("\n");
51476
51916
  const questions = [];
51477
51917
  let currentTitle = "";
@@ -51547,7 +51987,7 @@ function titleCase(id3) {
51547
51987
  async function updateAgentsMd(ctx, projectRoot) {
51548
51988
  const agentsPath = join34(projectRoot, "AGENTS.MD");
51549
51989
  if (existsSync36(agentsPath)) {
51550
- const content = readFileSync30(agentsPath, "utf8");
51990
+ const content = readFileSync31(agentsPath, "utf8");
51551
51991
  const hasAnyMarker = AUTO_SECTIONS.some((id3) => content.includes(MARKER_OPEN(id3)));
51552
51992
  if (!hasAnyMarker) {
51553
51993
  return {
@@ -51563,7 +52003,7 @@ async function updateAgentsMd(ctx, projectRoot) {
51563
52003
  }
51564
52004
  let manualContent = "";
51565
52005
  if (existsSync36(agentsPath)) {
51566
- const { manualBlocks } = parseAgentsMd(readFileSync30(agentsPath, "utf8"));
52006
+ const { manualBlocks } = parseAgentsMd(readFileSync31(agentsPath, "utf8"));
51567
52007
  manualContent = manualBlocks.after;
51568
52008
  } else {
51569
52009
  const projectName2 = projectName(projectRoot);
@@ -51579,7 +52019,7 @@ async function updateAgentsMd(ctx, projectRoot) {
51579
52019
  ""
51580
52020
  ].join("\n");
51581
52021
  }
51582
- const oldContent = existsSync36(agentsPath) ? readFileSync30(agentsPath, "utf8") : "";
52022
+ const oldContent = existsSync36(agentsPath) ? readFileSync31(agentsPath, "utf8") : "";
51583
52023
  const { sections: oldSections } = parseAgentsMd(oldContent);
51584
52024
  const changedSections = [];
51585
52025
  for (const id3 of AUTO_SECTIONS) {
@@ -51716,7 +52156,7 @@ var init_completeDesign = __esm({
51716
52156
  });
51717
52157
 
51718
52158
  // src/cli/workspace/planDriftCheck.ts
51719
- import { existsSync as existsSync37, readFileSync as readFileSync31, readdirSync as readdirSync9, statSync as statSync6, writeFileSync as writeFileSync20 } from "node:fs";
52159
+ import { existsSync as existsSync37, readFileSync as readFileSync32, readdirSync as readdirSync9, statSync as statSync6, writeFileSync as writeFileSync20 } from "node:fs";
51720
52160
  import { join as join35 } from "node:path";
51721
52161
  function findCanonicalDoc(rootDir) {
51722
52162
  const docsDir = join35(rootDir, "docs");
@@ -51745,9 +52185,9 @@ function versionKey(value) {
51745
52185
  function firstString2(v) {
51746
52186
  return typeof v === "string" && v.trim().length > 0 ? v : null;
51747
52187
  }
51748
- function readFileSyncSafe(path72) {
52188
+ function readFileSyncSafe(path74) {
51749
52189
  try {
51750
- return readFileSync31(path72, "utf8");
52190
+ return readFileSync32(path74, "utf8");
51751
52191
  } catch {
51752
52192
  return null;
51753
52193
  }
@@ -51762,7 +52202,7 @@ async function runPlanDriftCheck(rootDir) {
51762
52202
  }
51763
52203
  let plan;
51764
52204
  try {
51765
- plan = JSON.parse(readFileSync31(planPath, "utf8"));
52205
+ plan = JSON.parse(readFileSync32(planPath, "utf8"));
51766
52206
  } catch {
51767
52207
  return { ran: false, reason: ".zelari/plan.json corrupt" };
51768
52208
  }
@@ -51888,7 +52328,7 @@ var init_planDriftCheck = __esm({
51888
52328
 
51889
52329
  // src/cli/workspace/projectSmoke.ts
51890
52330
  import { spawn as spawn14 } from "node:child_process";
51891
- import { existsSync as existsSync38, readFileSync as readFileSync32 } from "node:fs";
52331
+ import { existsSync as existsSync38, readFileSync as readFileSync33 } from "node:fs";
51892
52332
  import { join as join36 } from "node:path";
51893
52333
  function pickSmokeScript(scripts) {
51894
52334
  if (!scripts) return null;
@@ -51907,7 +52347,7 @@ async function runProjectSmoke(projectRoot, timeoutMs2 = DEFAULT_TIMEOUT_MS3) {
51907
52347
  }
51908
52348
  let scripts = {};
51909
52349
  try {
51910
- const pkg = JSON.parse(readFileSync32(pkgPath, "utf8"));
52350
+ const pkg = JSON.parse(readFileSync33(pkgPath, "utf8"));
51911
52351
  scripts = pkg.scripts ?? {};
51912
52352
  } catch {
51913
52353
  return { ran: false, reason: "package.json unreadable (skipped)" };
@@ -51995,7 +52435,7 @@ __export(postCouncilHook_exports, {
51995
52435
  runPostCouncilHook: () => runPostCouncilHook
51996
52436
  });
51997
52437
  import { spawn as spawn15 } from "node:child_process";
51998
- import { existsSync as existsSync39, readFileSync as readFileSync33 } from "node:fs";
52438
+ import { existsSync as existsSync39, readFileSync as readFileSync34 } from "node:fs";
51999
52439
  import { join as join37 } from "node:path";
52000
52440
  async function runCompleteDesignPostProcessor(ctx, options) {
52001
52441
  if (options?.runMode === "implementation") {
@@ -52017,7 +52457,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
52017
52457
  }
52018
52458
  let phaseCount = 0;
52019
52459
  try {
52020
- const parsed = JSON.parse(readFileSync33(planJsonPath2, "utf8"));
52460
+ const parsed = JSON.parse(readFileSync34(planJsonPath2, "utf8"));
52021
52461
  phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
52022
52462
  } catch {
52023
52463
  return { ran: false, reason: ".zelari/plan.json corrupt" };
@@ -52210,8 +52650,8 @@ async function runPostCouncilHook(ctx, options) {
52210
52650
  sources: scope.sources
52211
52651
  } : void 0
52212
52652
  });
52213
- const path72 = writeCouncilCompletion(ctx.rootDir, completion);
52214
- completionHook = { ran: true, path: path72, completion };
52653
+ const path74 = writeCouncilCompletion(ctx.rootDir, completion);
52654
+ completionHook = { ran: true, path: path74, completion };
52215
52655
  } catch (err) {
52216
52656
  completionHook = {
52217
52657
  ran: true,
@@ -52252,11 +52692,11 @@ __export(councilFeedback_exports, {
52252
52692
  import {
52253
52693
  promises as fs25,
52254
52694
  existsSync as existsSync40,
52255
- readFileSync as readFileSync34,
52695
+ readFileSync as readFileSync35,
52256
52696
  writeFileSync as writeFileSync21,
52257
52697
  mkdirSync as mkdirSync17
52258
52698
  } from "node:fs";
52259
- import path48 from "node:path";
52699
+ import path50 from "node:path";
52260
52700
  import os10 from "node:os";
52261
52701
  var FeedbackStore;
52262
52702
  var init_councilFeedback = __esm({
@@ -52267,7 +52707,7 @@ var init_councilFeedback = __esm({
52267
52707
  now;
52268
52708
  entries = [];
52269
52709
  constructor(options = {}) {
52270
- this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path48.join(os10.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
52710
+ this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path50.join(os10.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
52271
52711
  this.now = options.now ?? Date.now;
52272
52712
  this.load();
52273
52713
  }
@@ -52362,7 +52802,7 @@ var init_councilFeedback = __esm({
52362
52802
  load() {
52363
52803
  if (!existsSync40(this.file)) return;
52364
52804
  try {
52365
- const raw = readFileSync34(this.file, "utf-8");
52805
+ const raw = readFileSync35(this.file, "utf-8");
52366
52806
  const parsed = JSON.parse(raw);
52367
52807
  if (parsed && Array.isArray(parsed.entries)) {
52368
52808
  this.entries = parsed.entries.filter(
@@ -52373,7 +52813,7 @@ var init_councilFeedback = __esm({
52373
52813
  }
52374
52814
  }
52375
52815
  save() {
52376
- mkdirSync17(path48.dirname(this.file), { recursive: true });
52816
+ mkdirSync17(path50.dirname(this.file), { recursive: true });
52377
52817
  writeFileSync21(
52378
52818
  this.file,
52379
52819
  JSON.stringify({ entries: this.entries }, null, 2),
@@ -52441,7 +52881,7 @@ import { execFile as execFile3 } from "node:child_process";
52441
52881
  import { promisify as promisify2 } from "node:util";
52442
52882
  import { mkdtempSync, rmSync as rmSync2 } from "node:fs";
52443
52883
  import { tmpdir as tmpdir2 } from "node:os";
52444
- import path49 from "node:path";
52884
+ import path51 from "node:path";
52445
52885
  import { randomUUID as randomUUID5 } from "node:crypto";
52446
52886
  async function git3(cwd, args, env) {
52447
52887
  const { stdout } = await execFileAsync2("git", ["-C", cwd, ...args], {
@@ -52461,8 +52901,8 @@ async function isGitRepo(cwd) {
52461
52901
  return await gitSafe(cwd, ["rev-parse", "--is-inside-work-tree"]) === "true";
52462
52902
  }
52463
52903
  async function withTempIndex(fn) {
52464
- const dir = mkdtempSync(path49.join(tmpdir2(), "zelari-ckpt-"));
52465
- const indexFile = path49.join(dir, "index");
52904
+ const dir = mkdtempSync(path51.join(tmpdir2(), "zelari-ckpt-"));
52905
+ const indexFile = path51.join(dir, "index");
52466
52906
  try {
52467
52907
  return await fn(indexFile);
52468
52908
  } finally {
@@ -52553,7 +52993,7 @@ async function restoreCheckpoint(cwd, id3) {
52553
52993
  const deleted = [];
52554
52994
  for (const rel2 of added) {
52555
52995
  try {
52556
- rmSync2(path49.join(cwd, rel2), { force: true });
52996
+ rmSync2(path51.join(cwd, rel2), { force: true });
52557
52997
  deleted.push(rel2);
52558
52998
  } catch {
52559
52999
  }
@@ -52654,7 +53094,7 @@ __export(fileBackend_exports, {
52654
53094
  });
52655
53095
  import { randomUUID as randomUUID6 } from "node:crypto";
52656
53096
  import { promises as fs26 } from "node:fs";
52657
- import * as path50 from "node:path";
53097
+ import * as path52 from "node:path";
52658
53098
  function tokenize(text) {
52659
53099
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 3);
52660
53100
  }
@@ -52705,8 +53145,8 @@ var init_fileBackend = __esm({
52705
53145
  logPath = "";
52706
53146
  memoryDir = "";
52707
53147
  async init(projectRoot) {
52708
- this.memoryDir = path50.join(projectRoot, ".zelari", "memory");
52709
- this.logPath = path50.join(this.memoryDir, "log.jsonl");
53148
+ this.memoryDir = path52.join(projectRoot, ".zelari", "memory");
53149
+ this.logPath = path52.join(this.memoryDir, "log.jsonl");
52710
53150
  await fs26.mkdir(this.memoryDir, { recursive: true });
52711
53151
  }
52712
53152
  async add(content, metadata2 = {}, graph) {
@@ -52779,12 +53219,12 @@ var init_fileBackend = __esm({
52779
53219
 
52780
53220
  // src/cli/traceStore.ts
52781
53221
  import { promises as fs27 } from "node:fs";
52782
- import * as path51 from "node:path";
53222
+ import * as path53 from "node:path";
52783
53223
  function traceDir(projectRoot) {
52784
- return path51.join(projectRoot, ".zelari", "trace");
53224
+ return path53.join(projectRoot, ".zelari", "trace");
52785
53225
  }
52786
53226
  function tracePath(projectRoot, missionId) {
52787
- return path51.join(traceDir(projectRoot), `${missionId}.json`);
53227
+ return path53.join(traceDir(projectRoot), `${missionId}.json`);
52788
53228
  }
52789
53229
  async function saveTrace(projectRoot, missionId, entries) {
52790
53230
  const dir = traceDir(projectRoot);
@@ -52821,7 +53261,7 @@ __export(zelariMission_exports, {
52821
53261
  });
52822
53262
  import { randomUUID as randomUUID7 } from "node:crypto";
52823
53263
  import { promises as fs28 } from "node:fs";
52824
- import * as path52 from "node:path";
53264
+ import * as path54 from "node:path";
52825
53265
  function resolveMaxIterations(env = process.env) {
52826
53266
  const raw = env.ZELARI_MISSION_MAX_ITER;
52827
53267
  const n = raw ? Number.parseInt(raw, 10) : DEFAULT_MAX_ITER;
@@ -52864,10 +53304,10 @@ function isMissionAutoStart(env = process.env) {
52864
53304
  return env.ZELARI_MISSION_AUTO === "1";
52865
53305
  }
52866
53306
  async function writeMissionState(projectRoot, state3) {
52867
- const dir = path52.join(projectRoot, ".zelari");
53307
+ const dir = path54.join(projectRoot, ".zelari");
52868
53308
  await fs28.mkdir(dir, { recursive: true });
52869
53309
  await fs28.writeFile(
52870
- path52.join(dir, "mission-state.json"),
53310
+ path54.join(dir, "mission-state.json"),
52871
53311
  JSON.stringify(state3, null, 2) + "\n",
52872
53312
  "utf8"
52873
53313
  );
@@ -53542,7 +53982,7 @@ function safeSocketPath(socketPath) {
53542
53982
  return socketPath.trim();
53543
53983
  }
53544
53984
  function startPermissionBroker(socketPath, handlers, opts) {
53545
- const path72 = safeSocketPath(socketPath);
53985
+ const path74 = safeSocketPath(socketPath);
53546
53986
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
53547
53987
  const sockets = /* @__PURE__ */ new Set();
53548
53988
  const server = createServer2((socket) => {
@@ -53642,10 +54082,10 @@ function startPermissionBroker(socketPath, handlers, opts) {
53642
54082
  return new Promise((resolve7, reject) => {
53643
54083
  const onError = (err) => reject(err);
53644
54084
  server.once("error", onError);
53645
- server.listen(path72, () => {
54085
+ server.listen(path74, () => {
53646
54086
  server.removeListener("error", onError);
53647
54087
  resolve7({
53648
- socketPath: path72,
54088
+ socketPath: path74,
53649
54089
  stop: () => new Promise((res) => {
53650
54090
  for (const s of sockets) s.destroy();
53651
54091
  sockets.clear();
@@ -53656,7 +54096,7 @@ function startPermissionBroker(socketPath, handlers, opts) {
53656
54096
  if (done) return;
53657
54097
  done = true;
53658
54098
  if (process.platform !== "win32") {
53659
- unlink(path72, () => res());
54099
+ unlink(path74, () => res());
53660
54100
  } else {
53661
54101
  res();
53662
54102
  }
@@ -53669,11 +54109,11 @@ function startPermissionBroker(socketPath, handlers, opts) {
53669
54109
  });
53670
54110
  }
53671
54111
  function requestBrokerAsk(socketPath, ask, opts) {
53672
- const path72 = safeSocketPath(socketPath);
54112
+ const path74 = safeSocketPath(socketPath);
53673
54113
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
53674
54114
  const connectTimeoutMs = opts?.connectTimeoutMs ?? PERMISSION_BROKER_DEFAULT_CONNECT_TIMEOUT_MS;
53675
54115
  return new Promise((resolve7, reject) => {
53676
- const socket = connect(path72);
54116
+ const socket = connect(path74);
53677
54117
  let buffer = "";
53678
54118
  let settled = false;
53679
54119
  const settle = (fn) => {
@@ -53688,7 +54128,7 @@ function requestBrokerAsk(socketPath, ask, opts) {
53688
54128
  settle(
53689
54129
  () => reject(
53690
54130
  new Error(
53691
- `permission broker unavailable at "${path72}" (connect timed out after ${connectTimeoutMs}ms)`
54131
+ `permission broker unavailable at "${path74}" (connect timed out after ${connectTimeoutMs}ms)`
53692
54132
  )
53693
54133
  )
53694
54134
  );
@@ -54431,9 +54871,9 @@ __export(graphMemory_exports, {
54431
54871
  toGraphSnapshot: () => toGraphSnapshot
54432
54872
  });
54433
54873
  import { promises as fs31 } from "node:fs";
54434
- import path56 from "node:path";
54874
+ import path58 from "node:path";
54435
54875
  function snapshotPath(cwd) {
54436
- return path56.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
54876
+ return path58.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
54437
54877
  }
54438
54878
  function toGraphSnapshot(graph, opts) {
54439
54879
  const unresolved = (opts.unresolvedFindings ?? []).map((u) => ({
@@ -54460,7 +54900,7 @@ async function saveGraphSnapshot(cwd, snapshot) {
54460
54900
  try {
54461
54901
  await fs31.access(cwd);
54462
54902
  const file2 = snapshotPath(cwd);
54463
- await fs31.mkdir(path56.dirname(file2), { recursive: true });
54903
+ await fs31.mkdir(path58.dirname(file2), { recursive: true });
54464
54904
  await fs31.writeFile(file2, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
54465
54905
  } catch {
54466
54906
  }
@@ -54527,7 +54967,7 @@ var SNAPSHOT_DIR, SNAPSHOT_FILE, MAX_SNAPSHOT_FINDINGS_CHARS;
54527
54967
  var init_graphMemory = __esm({
54528
54968
  "src/cli/kraken/graphMemory.ts"() {
54529
54969
  "use strict";
54530
- SNAPSHOT_DIR = path56.join(".zelari", "kraken");
54970
+ SNAPSHOT_DIR = path58.join(".zelari", "kraken");
54531
54971
  SNAPSHOT_FILE = "last-graph.json";
54532
54972
  MAX_SNAPSHOT_FINDINGS_CHARS = 400;
54533
54973
  }
@@ -54543,14 +54983,14 @@ var init_tentacle = __esm({
54543
54983
 
54544
54984
  // src/cli/kraken/workbench.ts
54545
54985
  import { promises as fs32 } from "node:fs";
54546
- import path57 from "node:path";
54986
+ import path59 from "node:path";
54547
54987
  function isWorkbenchEnabled(env = process.env) {
54548
54988
  const v = (env.ZELARI_KRAKEN_WORKBENCH ?? "1").trim().toLowerCase();
54549
54989
  if (v === "0" || v === "false" || v === "no" || v === "off") return false;
54550
54990
  return true;
54551
54991
  }
54552
54992
  function workbenchPath(cwd, graphId) {
54553
- return path57.join(cwd, ".zelari", "radio", `workbench-${graphId}.md`);
54993
+ return path59.join(cwd, ".zelari", "radio", `workbench-${graphId}.md`);
54554
54994
  }
54555
54995
  function countByStatus2(nodes) {
54556
54996
  const out = { pending: 0, running: 0, done: 0, error: 0, skipped: 0 };
@@ -54713,7 +55153,7 @@ var init_workbench = __esm({
54713
55153
  if (!this.enabled) return null;
54714
55154
  if (!this.dirty && this.lastWrite) return this.lastWrite;
54715
55155
  const out = workbenchPath(this.cwd, this.graphId);
54716
- await fs32.mkdir(path57.dirname(out), { recursive: true });
55156
+ await fs32.mkdir(path59.dirname(out), { recursive: true });
54717
55157
  const body = this.render();
54718
55158
  const tmp = `${out}.${process.pid}.${Date.now()}.tmp`;
54719
55159
  await fs32.writeFile(tmp, body, "utf8");
@@ -54910,7 +55350,7 @@ __export(executor_exports, {
54910
55350
  thoroughnessForKind: () => thoroughnessForKind
54911
55351
  });
54912
55352
  import { existsSync as existsSync41 } from "node:fs";
54913
- import path58 from "node:path";
55353
+ import path60 from "node:path";
54914
55354
  function resolveMaxParallel(env = process.env) {
54915
55355
  const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
54916
55356
  if (raw === void 0 || raw === "") return DEFAULT_MAX_PARALLEL;
@@ -54968,7 +55408,7 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
54968
55408
  }
54969
55409
  function defaultChecksExists(cwd) {
54970
55410
  try {
54971
- return existsSync41(path58.join(cwd, ".zelari", "world", "checks.json"));
55411
+ return existsSync41(path60.join(cwd, ".zelari", "world", "checks.json"));
54972
55412
  } catch {
54973
55413
  return false;
54974
55414
  }
@@ -56364,17 +56804,17 @@ var init_prereqChecks = __esm({
56364
56804
  });
56365
56805
 
56366
56806
  // src/cli/plugins/prefs.ts
56367
- import { existsSync as existsSync43, readFileSync as readFileSync35, writeFileSync as writeFileSync22, mkdirSync as mkdirSync18 } from "node:fs";
56368
- import path61 from "node:path";
56807
+ import { existsSync as existsSync43, readFileSync as readFileSync36, writeFileSync as writeFileSync22, mkdirSync as mkdirSync18 } from "node:fs";
56808
+ import path63 from "node:path";
56369
56809
  import os11 from "node:os";
56370
56810
  function getPluginPrefsPath() {
56371
- return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path61.join(os11.homedir(), ".tmp", "zelari-code", "plugins.json");
56811
+ return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path63.join(os11.homedir(), ".tmp", "zelari-code", "plugins.json");
56372
56812
  }
56373
56813
  function getPluginPrefs() {
56374
56814
  const file2 = getPluginPrefsPath();
56375
56815
  try {
56376
56816
  if (!existsSync43(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
56377
- const raw = readFileSync35(file2, "utf-8");
56817
+ const raw = readFileSync36(file2, "utf-8");
56378
56818
  const parsed = JSON.parse(raw);
56379
56819
  if (parsed && typeof parsed === "object" && parsed.dontAskAgain && typeof parsed.dontAskAgain === "object") {
56380
56820
  const clean = {};
@@ -56389,7 +56829,7 @@ function getPluginPrefs() {
56389
56829
  }
56390
56830
  function writePluginPrefs(prefs) {
56391
56831
  const file2 = getPluginPrefsPath();
56392
- mkdirSync18(path61.dirname(file2), { recursive: true });
56832
+ mkdirSync18(path63.dirname(file2), { recursive: true });
56393
56833
  writeFileSync22(file2, JSON.stringify(prefs, null, 2), {
56394
56834
  encoding: "utf-8",
56395
56835
  mode: 384
@@ -56426,7 +56866,7 @@ __export(registry_exports, {
56426
56866
  isBinaryOnPath: () => isBinaryOnPath
56427
56867
  });
56428
56868
  import { existsSync as existsSync44 } from "node:fs";
56429
- import path62 from "node:path";
56869
+ import path64 from "node:path";
56430
56870
  function detectLocalBin(bin) {
56431
56871
  return (cwd) => {
56432
56872
  try {
@@ -56444,7 +56884,7 @@ function isBinaryOnPath(bin, opts = {}) {
56444
56884
  const platform = opts.platform ?? process.platform;
56445
56885
  const exists = opts.exists ?? existsSync44;
56446
56886
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
56447
- const pathMod = platform === "win32" ? path62.win32 : path62.posix;
56887
+ const pathMod = platform === "win32" ? path64.win32 : path64.posix;
56448
56888
  const sep4 = platform === "win32" ? ";" : ":";
56449
56889
  const dirs = pathEnv.split(sep4).filter((d) => d.length > 0);
56450
56890
  const candidates = [bin];
@@ -57176,7 +57616,7 @@ __export(atMentions_exports, {
57176
57616
  extractAtMentions: () => extractAtMentions,
57177
57617
  hasAtMentions: () => hasAtMentions
57178
57618
  });
57179
- import { existsSync as existsSync47, readFileSync as readFileSync37, statSync as statSync9 } from "node:fs";
57619
+ import { existsSync as existsSync47, readFileSync as readFileSync38, statSync as statSync9 } from "node:fs";
57180
57620
  import { basename as basename4, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve5, sep as sep2 } from "node:path";
57181
57621
  function isImagePath(abs) {
57182
57622
  const ext = abs.split(".").pop()?.toLowerCase() ?? "";
@@ -57282,7 +57722,7 @@ function resolveMention(token, cwd) {
57282
57722
  note: `image too large (${Math.round(st.size / 1024)} KB) \u2014 path only`
57283
57723
  };
57284
57724
  }
57285
- const dataBase64 = readFileSync37(abs).toString("base64");
57725
+ const dataBase64 = readFileSync38(abs).toString("base64");
57286
57726
  return {
57287
57727
  raw: token,
57288
57728
  path: rel2,
@@ -57293,7 +57733,7 @@ function resolveMention(token, cwd) {
57293
57733
  };
57294
57734
  }
57295
57735
  try {
57296
- const buf = readFileSync37(abs);
57736
+ const buf = readFileSync38(abs);
57297
57737
  const head = buf.subarray(0, 800).toString("utf8");
57298
57738
  if (!isProbablyText(abs, head)) {
57299
57739
  return {
@@ -58278,9 +58718,9 @@ __export(triggerLock_exports, {
58278
58718
  releaseLock: () => releaseLock
58279
58719
  });
58280
58720
  import { promises as fs40 } from "node:fs";
58281
- import * as path67 from "node:path";
58721
+ import * as path69 from "node:path";
58282
58722
  function lockPath(projectRoot) {
58283
- return path67.join(projectRoot, ".zelari", "trigger.lock");
58723
+ return path69.join(projectRoot, ".zelari", "trigger.lock");
58284
58724
  }
58285
58725
  function isPidAlive(pid) {
58286
58726
  try {
@@ -58293,7 +58733,7 @@ function isPidAlive(pid) {
58293
58733
  }
58294
58734
  async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
58295
58735
  const lp = lockPath(projectRoot);
58296
- const dir = path67.dirname(lp);
58736
+ const dir = path69.dirname(lp);
58297
58737
  await fs40.mkdir(dir, { recursive: true });
58298
58738
  try {
58299
58739
  const raw = await fs40.readFile(lp, "utf8");
@@ -58721,12 +59161,12 @@ import {
58721
59161
  existsSync as existsSync49,
58722
59162
  mkdirSync as mkdirSync21,
58723
59163
  readdirSync as readdirSync10,
58724
- readFileSync as readFileSync38,
59164
+ readFileSync as readFileSync39,
58725
59165
  rmSync as rmSync4,
58726
59166
  writeFileSync as writeFileSync24
58727
59167
  } from "node:fs";
58728
59168
  import { dirname as dirname13, join as join45 } from "node:path";
58729
- import { homedir as homedir12 } from "node:os";
59169
+ import { homedir as homedir13 } from "node:os";
58730
59170
  function ensureBuiltinSkillsLoadedSync() {
58731
59171
  if (builtinsLoaded) return;
58732
59172
  for (const spec of BUILTIN_SKILL_MODULES) {
@@ -58737,7 +59177,7 @@ function ensureBuiltinSkillsLoadedSync() {
58737
59177
  }
58738
59178
  }
58739
59179
  function getUserSkillsDir() {
58740
- return join45(homedir12(), ".zelari-code", "skills");
59180
+ return join45(homedir13(), ".zelari-code", "skills");
58741
59181
  }
58742
59182
  function getProjectSkillsDir(projectRoot) {
58743
59183
  return join45(projectRoot, ".zelari", "skills");
@@ -58803,7 +59243,7 @@ function scanSkillsDir(dir, projectRoot, seen, out) {
58803
59243
  const skillPath = skillFilePath(dir, entry);
58804
59244
  if (!existsSync49(skillPath)) continue;
58805
59245
  try {
58806
- const parsed = parseSkillMd(readFileSync38(skillPath, "utf8"), skillPath);
59246
+ const parsed = parseSkillMd(readFileSync39(skillPath, "utf8"), skillPath);
58807
59247
  if (!parsed) continue;
58808
59248
  if (seen.has(parsed.name)) continue;
58809
59249
  seen.add(parsed.name);
@@ -58887,7 +59327,7 @@ function upsertSkill(opts) {
58887
59327
  }
58888
59328
  dir = getProjectSkillsDir(root);
58889
59329
  }
58890
- const path72 = skillFilePath(dir, name);
59330
+ const path74 = skillFilePath(dir, name);
58891
59331
  const content = serializeSkillMd({
58892
59332
  name,
58893
59333
  description,
@@ -58896,13 +59336,13 @@ function upsertSkill(opts) {
58896
59336
  tools: opts.tools,
58897
59337
  cost: opts.cost
58898
59338
  });
58899
- const parsed = parseSkillMd(content, path72);
59339
+ const parsed = parseSkillMd(content, path74);
58900
59340
  if (!parsed) {
58901
59341
  return { ok: false, error: "Generated SKILL.md failed validation" };
58902
59342
  }
58903
- mkdirSync21(dirname13(path72), { recursive: true });
58904
- writeFileSync24(path72, content, "utf8");
58905
- return { ok: true, path: path72 };
59343
+ mkdirSync21(dirname13(path74), { recursive: true });
59344
+ writeFileSync24(path74, content, "utf8");
59345
+ return { ok: true, path: path74 };
58906
59346
  }
58907
59347
  function removeSkill(opts) {
58908
59348
  const name = opts.name.trim().toLowerCase();
@@ -58920,8 +59360,8 @@ function removeSkill(opts) {
58920
59360
  dir = getProjectSkillsDir(root);
58921
59361
  }
58922
59362
  const skillDir = join45(dir, name);
58923
- const path72 = skillFilePath(dir, name);
58924
- if (!existsSync49(path72) && !existsSync49(skillDir)) {
59363
+ const path74 = skillFilePath(dir, name);
59364
+ if (!existsSync49(path74) && !existsSync49(skillDir)) {
58925
59365
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
58926
59366
  }
58927
59367
  try {
@@ -58932,7 +59372,7 @@ function removeSkill(opts) {
58932
59372
  error: err instanceof Error ? err.message : String(err)
58933
59373
  };
58934
59374
  }
58935
- return { ok: true, path: path72 };
59375
+ return { ok: true, path: path74 };
58936
59376
  }
58937
59377
  var NAME_RE, BUILTIN_SKILL_MODULES, builtinsLoaded;
58938
59378
  var init_skillConfigIo = __esm({
@@ -59051,7 +59491,7 @@ var init_jsonApi = __esm({
59051
59491
  });
59052
59492
 
59053
59493
  // src/cli/memory/mcpAdapter.ts
59054
- import * as path69 from "node:path";
59494
+ import * as path71 from "node:path";
59055
59495
  var id2, projectId, source, SearchSchema, AddSchema, LinkSchema, RetractSchema, MEMORY_MCP_TOOLS, MemoryMcpAdapter;
59056
59496
  var init_mcpAdapter = __esm({
59057
59497
  "src/cli/memory/mcpAdapter.ts"() {
@@ -59221,8 +59661,8 @@ var init_mcpAdapter = __esm({
59221
59661
  this.takeWrite();
59222
59662
  const externalFile = args.source?.file;
59223
59663
  if (externalFile) {
59224
- const normalized = path69.normalize(externalFile);
59225
- if (path69.isAbsolute(normalized) || normalized === ".." || normalized.startsWith(`..${path69.sep}`)) {
59664
+ const normalized = path71.normalize(externalFile);
59665
+ if (path71.isAbsolute(normalized) || normalized === ".." || normalized.startsWith(`..${path71.sep}`)) {
59226
59666
  throw new Error("source.file must be project-relative and cannot escape the project");
59227
59667
  }
59228
59668
  }
@@ -59728,14 +60168,14 @@ var init_permissionCli = __esm({
59728
60168
  import {
59729
60169
  existsSync as existsSync50,
59730
60170
  mkdirSync as mkdirSync22,
59731
- readFileSync as readFileSync39,
60171
+ readFileSync as readFileSync40,
59732
60172
  writeFileSync as writeFileSync25
59733
60173
  } from "node:fs";
59734
60174
  import { join as join46 } from "node:path";
59735
- import { homedir as homedir13 } from "node:os";
60175
+ import { homedir as homedir14 } from "node:os";
59736
60176
  import { createHash as createHash20, randomBytes as randomBytes6, timingSafeEqual } from "node:crypto";
59737
60177
  function getZelariHome() {
59738
- return join46(homedir13(), ".zelari-code");
60178
+ return join46(homedir14(), ".zelari-code");
59739
60179
  }
59740
60180
  function getCompanionConfigPath() {
59741
60181
  return join46(getZelariHome(), "companion.json");
@@ -59750,12 +60190,12 @@ function ensureHome() {
59750
60190
  }
59751
60191
  }
59752
60192
  function loadCompanionConfig() {
59753
- const path72 = getCompanionConfigPath();
59754
- if (!existsSync50(path72)) {
60193
+ const path74 = getCompanionConfigPath();
60194
+ if (!existsSync50(path74)) {
59755
60195
  return { projects: [] };
59756
60196
  }
59757
60197
  try {
59758
- const raw = JSON.parse(readFileSync39(path72, "utf8"));
60198
+ const raw = JSON.parse(readFileSync40(path74, "utf8"));
59759
60199
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
59760
60200
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
59761
60201
  ).map((p3) => ({
@@ -59793,16 +60233,16 @@ function loadOrCreateToken(explicit) {
59793
60233
  return { token: explicit.trim(), created: false };
59794
60234
  }
59795
60235
  ensureHome();
59796
- const path72 = getCompanionTokenPath();
59797
- if (existsSync50(path72)) {
59798
- const t = readFileSync39(path72, "utf8").trim();
60236
+ const path74 = getCompanionTokenPath();
60237
+ if (existsSync50(path74)) {
60238
+ const t = readFileSync40(path74, "utf8").trim();
59799
60239
  if (t) return { token: t, created: false };
59800
60240
  }
59801
60241
  const token = randomBytes6(24).toString("base64url");
59802
- writeFileSync25(path72, token + "\n", "utf8");
60242
+ writeFileSync25(path74, token + "\n", "utf8");
59803
60243
  try {
59804
60244
  const fs42 = __require("node:fs");
59805
- fs42.chmodSync?.(path72, 384);
60245
+ fs42.chmodSync?.(path74, 384);
59806
60246
  } catch {
59807
60247
  }
59808
60248
  return { token, created: true };
@@ -59827,17 +60267,17 @@ function mergeProjects(cfg, extraPaths) {
59827
60267
  byId.set(p3.id, p3);
59828
60268
  }
59829
60269
  for (const raw of extraPaths) {
59830
- const path72 = raw.trim();
59831
- if (!path72) continue;
59832
- let id3 = slugFromPath(path72);
60270
+ const path74 = raw.trim();
60271
+ if (!path74) continue;
60272
+ let id3 = slugFromPath(path74);
59833
60273
  let n = 2;
59834
- while (byId.has(id3) && byId.get(id3).path !== path72) {
59835
- id3 = `${slugFromPath(path72)}-${n++}`;
60274
+ while (byId.has(id3) && byId.get(id3).path !== path74) {
60275
+ id3 = `${slugFromPath(path74)}-${n++}`;
59836
60276
  }
59837
60277
  byId.set(id3, {
59838
60278
  id: id3,
59839
- name: slugFromPath(path72),
59840
- path: path72
60279
+ name: slugFromPath(path74),
60280
+ path: path74
59841
60281
  });
59842
60282
  }
59843
60283
  return [...byId.values()];
@@ -60214,9 +60654,9 @@ async function runCompanionServe(opts = {}) {
60214
60654
  return;
60215
60655
  }
60216
60656
  const url2 = parseUrl(req);
60217
- const path72 = url2.pathname.replace(/\/+$/, "") || "/";
60657
+ const path74 = url2.pathname.replace(/\/+$/, "") || "/";
60218
60658
  try {
60219
- if (req.method === "GET" && (path72 === "/health" || path72 === "/v1/health")) {
60659
+ if (req.method === "GET" && (path74 === "/health" || path74 === "/v1/health")) {
60220
60660
  sendJson2(res, 200, {
60221
60661
  ok: true,
60222
60662
  service: "zelari-companion",
@@ -60228,18 +60668,18 @@ async function runCompanionServe(opts = {}) {
60228
60668
  });
60229
60669
  return;
60230
60670
  }
60231
- if (path72.startsWith("/v1")) {
60671
+ if (path74.startsWith("/v1")) {
60232
60672
  if (!tokenMatches(token, getBearer(req))) {
60233
60673
  sendJson2(res, 401, { ok: false, error: "unauthorized" });
60234
60674
  return;
60235
60675
  }
60236
60676
  }
60237
- if (req.method === "GET" && path72 === "/v1/config") {
60677
+ if (req.method === "GET" && path74 === "/v1/config") {
60238
60678
  const snap = buildDesktopConfigSnapshot();
60239
60679
  sendJson2(res, 200, { ok: true, ...snap });
60240
60680
  return;
60241
60681
  }
60242
- if (req.method === "GET" && path72 === "/v1/projects") {
60682
+ if (req.method === "GET" && path74 === "/v1/projects") {
60243
60683
  sendJson2(res, 200, {
60244
60684
  ok: true,
60245
60685
  projects: projects.map((p3) => ({
@@ -60250,7 +60690,7 @@ async function runCompanionServe(opts = {}) {
60250
60690
  });
60251
60691
  return;
60252
60692
  }
60253
- if (req.method === "GET" && path72 === "/v1/runs") {
60693
+ if (req.method === "GET" && path74 === "/v1/runs") {
60254
60694
  sendJson2(res, 200, {
60255
60695
  ok: true,
60256
60696
  active: runs.getActive(),
@@ -60268,7 +60708,7 @@ async function runCompanionServe(opts = {}) {
60268
60708
  });
60269
60709
  return;
60270
60710
  }
60271
- if (req.method === "POST" && path72 === "/v1/runs") {
60711
+ if (req.method === "POST" && path74 === "/v1/runs") {
60272
60712
  const raw = await readBody(req);
60273
60713
  let body = {};
60274
60714
  try {
@@ -60315,7 +60755,7 @@ async function runCompanionServe(opts = {}) {
60315
60755
  });
60316
60756
  return;
60317
60757
  }
60318
- const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path72);
60758
+ const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path74);
60319
60759
  if (req.method === "GET" && eventsMatch) {
60320
60760
  const runId = eventsMatch[1];
60321
60761
  const run = runs.getRun(runId);
@@ -60380,7 +60820,7 @@ async function runCompanionServe(opts = {}) {
60380
60820
  }, 500);
60381
60821
  return;
60382
60822
  }
60383
- const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path72);
60823
+ const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path74);
60384
60824
  if (req.method === "POST" && cancelMatch) {
60385
60825
  const runId = cancelMatch[1];
60386
60826
  const result = runs.cancel(runId);
@@ -60530,26 +60970,26 @@ __export(doctor_exports, {
60530
60970
  runDoctor: () => runDoctor
60531
60971
  });
60532
60972
  import { execSync as execSync2 } from "node:child_process";
60533
- import { existsSync as existsSync52, readFileSync as readFileSync40, readlinkSync, statSync as statSync10 } from "node:fs";
60973
+ import { existsSync as existsSync52, readFileSync as readFileSync41, readlinkSync, statSync as statSync10 } from "node:fs";
60534
60974
  import { createRequire as createRequire3 } from "node:module";
60535
60975
  import { fileURLToPath as fileURLToPath3 } from "node:url";
60536
- import path70 from "node:path";
60976
+ import path72 from "node:path";
60537
60977
  function findPackageRoot(start) {
60538
60978
  let dir = start;
60539
60979
  for (let i = 0; i < 6; i += 1) {
60540
- const candidate = path70.join(dir, "package.json");
60980
+ const candidate = path72.join(dir, "package.json");
60541
60981
  if (existsSync52(candidate)) {
60542
60982
  try {
60543
- const pkg = JSON.parse(readFileSync40(candidate, "utf8"));
60983
+ const pkg = JSON.parse(readFileSync41(candidate, "utf8"));
60544
60984
  if (pkg.name === "zelari-code") return dir;
60545
60985
  } catch {
60546
60986
  }
60547
60987
  }
60548
- const parent = path70.dirname(dir);
60988
+ const parent = path72.dirname(dir);
60549
60989
  if (parent === dir) break;
60550
60990
  dir = parent;
60551
60991
  }
60552
- return path70.resolve(__dirname3, "..", "..", "..");
60992
+ return path72.resolve(__dirname3, "..", "..", "..");
60553
60993
  }
60554
60994
  function tryExec(cmd) {
60555
60995
  try {
@@ -60563,8 +61003,8 @@ function tryExec(cmd) {
60563
61003
  }
60564
61004
  function readPackageJson3() {
60565
61005
  try {
60566
- const pkgPath = path70.join(packageRoot, "package.json");
60567
- return JSON.parse(readFileSync40(pkgPath, "utf8"));
61006
+ const pkgPath = path72.join(packageRoot, "package.json");
61007
+ return JSON.parse(readFileSync41(pkgPath, "utf8"));
60568
61008
  } catch {
60569
61009
  return null;
60570
61010
  }
@@ -60579,7 +61019,7 @@ function checkShim(pkgName) {
60579
61019
  }
60580
61020
  const isWin = process.platform === "win32";
60581
61021
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
60582
- const shimPath = path70.join(prefix, shimName);
61022
+ const shimPath = path72.join(prefix, shimName);
60583
61023
  if (!existsSync52(shimPath)) {
60584
61024
  return FAIL(
60585
61025
  `shim not found at ${shimPath}
@@ -60589,7 +61029,7 @@ function checkShim(pkgName) {
60589
61029
  try {
60590
61030
  const st = statSync10(shimPath);
60591
61031
  if (isWin) {
60592
- const content = readFileSync40(shimPath, "utf8");
61032
+ const content = readFileSync41(shimPath, "utf8");
60593
61033
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
60594
61034
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
60595
61035
  }
@@ -60607,8 +61047,8 @@ function checkShim(pkgName) {
60607
61047
  fix: npm install -g ${pkgName}@latest --force`
60608
61048
  );
60609
61049
  }
60610
- const resolved = path70.resolve(path70.dirname(shimPath), target);
60611
- const expected = path70.join(
61050
+ const resolved = path72.resolve(path72.dirname(shimPath), target);
61051
+ const expected = path72.join(
60612
61052
  prefix,
60613
61053
  "node_modules",
60614
61054
  pkgName,
@@ -60647,7 +61087,7 @@ function checkNode(pkg) {
60647
61087
  return OK(`node ${raw}`);
60648
61088
  }
60649
61089
  function checkBundle() {
60650
- const bundle = path70.join(packageRoot, "dist", "cli", "main.bundled.js");
61090
+ const bundle = path72.join(packageRoot, "dist", "cli", "main.bundled.js");
60651
61091
  if (!existsSync52(bundle)) {
60652
61092
  return FAIL(
60653
61093
  `dist/cli/main.bundled.js missing at ${bundle}
@@ -60668,7 +61108,7 @@ function checkRuntimeDeps() {
60668
61108
  const missing = [];
60669
61109
  for (const dep of required2) {
60670
61110
  try {
60671
- const localReq = createRequire3(path70.join(packageRoot, "package.json"));
61111
+ const localReq = createRequire3(path72.join(packageRoot, "package.json"));
60672
61112
  localReq.resolve(dep);
60673
61113
  } catch {
60674
61114
  missing.push(dep);
@@ -60868,7 +61308,7 @@ var init_doctor = __esm({
60868
61308
  init_metrics3();
60869
61309
  init_contextGrowthSummary();
60870
61310
  require3 = createRequire3(import.meta.url);
60871
- __dirname3 = path70.dirname(fileURLToPath3(import.meta.url));
61311
+ __dirname3 = path72.dirname(fileURLToPath3(import.meta.url));
60872
61312
  packageRoot = findPackageRoot(__dirname3);
60873
61313
  OK = (message) => ({
60874
61314
  ok: true,
@@ -61052,15 +61492,15 @@ __export(inspect_exports, {
61052
61492
  collectInspectReport: () => collectInspectReport,
61053
61493
  runInspect: () => runInspect
61054
61494
  });
61055
- import path71 from "node:path";
61056
- import { existsSync as existsSync53, readFileSync as readFileSync41, readdirSync as readdirSync11 } from "node:fs";
61057
- import { homedir as homedir14 } from "node:os";
61495
+ import path73 from "node:path";
61496
+ import { existsSync as existsSync53, readFileSync as readFileSync42, readdirSync as readdirSync11 } from "node:fs";
61497
+ import { homedir as homedir15 } from "node:os";
61058
61498
  async function collectInspectReport(cwd = process.cwd()) {
61059
61499
  ensureBuiltinSkillsLoadedSync();
61060
61500
  const snap = listSkillsSnapshot(cwd);
61061
61501
  const mcp = listMcpServers(cwd);
61062
- const userMcpPath = path71.join(homedir14(), ".zelari-code", "mcp.json");
61063
- const projectMcpPath = path71.join(cwd, ".zelari", "mcp.json");
61502
+ const userMcpPath = path73.join(homedir15(), ".zelari-code", "mcp.json");
61503
+ const projectMcpPath = path73.join(cwd, ".zelari", "mcp.json");
61064
61504
  const globalHooks = globalHooksDir();
61065
61505
  const projectHooks = projectHooksDir(cwd);
61066
61506
  const projectTrusted = isFolderTrusted(cwd);
@@ -61088,9 +61528,9 @@ async function collectInspectReport(cwd = process.cwd()) {
61088
61528
  configSources: [
61089
61529
  { path: userMcpPath, exists: existsSync53(userMcpPath) },
61090
61530
  { path: projectMcpPath, exists: existsSync53(projectMcpPath) },
61091
- { path: path71.join(homedir14(), ".zelari-code", "provider.json"), exists: existsSync53(path71.join(homedir14(), ".zelari-code", "provider.json")) },
61092
- { path: path71.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync53(path71.join(cwd, ".zelari", "AGENTS.md")) },
61093
- { path: path71.join(cwd, "AGENTS.md"), exists: existsSync53(path71.join(cwd, "AGENTS.md")) }
61531
+ { path: path73.join(homedir15(), ".zelari-code", "provider.json"), exists: existsSync53(path73.join(homedir15(), ".zelari-code", "provider.json")) },
61532
+ { path: path73.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync53(path73.join(cwd, ".zelari", "AGENTS.md")) },
61533
+ { path: path73.join(cwd, "AGENTS.md"), exists: existsSync53(path73.join(cwd, "AGENTS.md")) }
61094
61534
  ],
61095
61535
  skills: {
61096
61536
  total: snap.skills.length,
@@ -61130,14 +61570,14 @@ function listJsonFiles(dir) {
61130
61570
  }
61131
61571
  function findAgentsMd(cwd) {
61132
61572
  const candidates = [
61133
- path71.join(cwd, "AGENTS.md"),
61134
- path71.join(cwd, ".zelari", "AGENTS.md")
61573
+ path73.join(cwd, "AGENTS.md"),
61574
+ path73.join(cwd, ".zelari", "AGENTS.md")
61135
61575
  ];
61136
61576
  const found = [];
61137
61577
  for (const c of candidates) {
61138
61578
  if (existsSync53(c)) {
61139
61579
  try {
61140
- const text = readFileSync41(c, "utf8");
61580
+ const text = readFileSync42(c, "utf8");
61141
61581
  found.push(`${c} (${text.length} bytes)`);
61142
61582
  } catch {
61143
61583
  found.push(`${c} (unreadable)`);
@@ -64431,10 +64871,11 @@ import { createHash as createHash14 } from "node:crypto";
64431
64871
  init_runtime2();
64432
64872
  init_verification2();
64433
64873
  import { readFile as readFile3 } from "node:fs/promises";
64434
- import path42 from "node:path";
64874
+ import path43 from "node:path";
64435
64875
  function nativePackEnabled(env = process.env) {
64436
64876
  const v = env.ZELARI_VERIFY_PACK?.toLowerCase();
64437
- return v === "1" || v === "on" || v === "true";
64877
+ if (v === "0" || v === "off" || v === "false") return false;
64878
+ return true;
64438
64879
  }
64439
64880
  function resolvePackCommands(env, scripts) {
64440
64881
  const pick2 = (override, scriptName) => {
@@ -64458,7 +64899,7 @@ function packTimeoutMs(env = process.env) {
64458
64899
  }
64459
64900
  async function readPackageScripts(cwd = process.cwd()) {
64460
64901
  try {
64461
- const raw = await readFile3(path42.join(cwd, "package.json"), "utf-8");
64902
+ const raw = await readFile3(path43.join(cwd, "package.json"), "utf-8");
64462
64903
  const parsed = JSON.parse(raw);
64463
64904
  if (parsed && typeof parsed === "object" && typeof parsed.scripts === "object") {
64464
64905
  return parsed.scripts;
@@ -64497,7 +64938,8 @@ function strictDoneEnabled(surface = "kraken") {
64497
64938
  return true;
64498
64939
  }
64499
64940
  const v = process.env.ZELARI_STRICT_DONE;
64500
- return v === "1" || v === "true";
64941
+ if (v === "0" || v === "false") return false;
64942
+ return true;
64501
64943
  }
64502
64944
  function criterionId(check2, index) {
64503
64945
  const slug = check2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
@@ -64734,6 +65176,145 @@ function strictGateEventPayload(evaluation) {
64734
65176
  };
64735
65177
  }
64736
65178
 
65179
+ // src/cli/kraken/completionProof.ts
65180
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
65181
+ import path44 from "node:path";
65182
+ function verdictOf(evaluation) {
65183
+ return evaluation.evaluation?.verdict ?? (evaluation.blocked ? "BLOCKED" : "PASS");
65184
+ }
65185
+ function cell(text, max = 200) {
65186
+ const flat = text.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
65187
+ return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
65188
+ }
65189
+ function evidenceCell(result) {
65190
+ if (!result || result.evidence.length === 0) return "\u2014";
65191
+ return result.evidence.map((e) => {
65192
+ const parts = [e.tier];
65193
+ if (e.seq !== void 0) parts.push(`seq ${e.seq}`);
65194
+ if (e.digest) parts.push(`digest ${e.digest.slice(0, 8)}\u2026`);
65195
+ return parts.join(" \xB7 ");
65196
+ }).join("; ");
65197
+ }
65198
+ function proofRows(evaluation) {
65199
+ const nativeById = new Map(
65200
+ (evaluation.native?.criteria ?? []).map((c) => [c.id, c])
65201
+ );
65202
+ const rows = /* @__PURE__ */ new Map();
65203
+ for (const result of evaluation.results ?? []) {
65204
+ const criterion = nativeById.get(result.criterionId);
65205
+ rows.set(result.criterionId, {
65206
+ id: result.criterionId,
65207
+ text: criterion?.text ?? null,
65208
+ required: criterion ? criterion.required : true,
65209
+ status: result.status,
65210
+ result
65211
+ });
65212
+ }
65213
+ for (const unsatisfied of evaluation.evaluation?.unsatisfied ?? []) {
65214
+ if (rows.has(unsatisfied.id)) continue;
65215
+ const criterion = nativeById.get(unsatisfied.id);
65216
+ rows.set(unsatisfied.id, {
65217
+ id: unsatisfied.id,
65218
+ text: criterion?.text ?? null,
65219
+ required: true,
65220
+ // satisfied/unsatisfied lists cover required criteria only
65221
+ status: unsatisfied.status,
65222
+ result: null
65223
+ });
65224
+ }
65225
+ return [...rows.values()];
65226
+ }
65227
+ function renderMarkdown(evaluation, meta3) {
65228
+ const lines = [
65229
+ "# Completion Proof",
65230
+ "",
65231
+ "Strict build-gate proof-of-work (ADR-0023). The machine-readable twin of",
65232
+ "this document \u2014 `completion-proof.json` \u2014 is the exact `verification.run`",
65233
+ "payload sent to the session spine.",
65234
+ "",
65235
+ `- **Verdict**: **${verdictOf(evaluation)}**`,
65236
+ `- **Strict gate**: ${evaluation.strict ? "on" : "off"}`,
65237
+ `- **Turn blocked**: ${evaluation.blocked ? "yes" : "no"}`,
65238
+ `- **Summary**: ${cell(evaluation.summary, 400)}`,
65239
+ `- **Legacy selection gate**: ${evaluation.gate.passed}/${evaluation.gate.total} passed` + (evaluation.gate.failedChecks.length > 0 ? `; failed: ${evaluation.gate.failedChecks.length}` : "") + (evaluation.gate.unknownChecks.length > 0 ? `; unknown: ${evaluation.gate.unknownChecks.length}` : "")
65240
+ ];
65241
+ if (meta3.surface) lines.push(`- **Surface**: ${meta3.surface}`);
65242
+ if (meta3.sessionId) lines.push(`- **Session**: ${meta3.sessionId} (session spine)`);
65243
+ if (meta3.generatedAt !== void 0) {
65244
+ lines.push(`- **Generated**: ${new Date(meta3.generatedAt).toISOString()}`);
65245
+ }
65246
+ const rows = proofRows(evaluation);
65247
+ lines.push("", "## Criteria", "", "| Criterion | Required | Status | Evidence |", "| --- | --- | --- | --- |");
65248
+ if (rows.length === 0) {
65249
+ lines.push("| _none \u2014 no criteria joined this evaluation_ | \u2014 | \u2014 | \u2014 |");
65250
+ }
65251
+ for (const row of rows) {
65252
+ const label = row.text ? `\`${row.id}\` \u2014 ${cell(row.text)}` : `\`${row.id}\``;
65253
+ lines.push(`| ${label} | ${row.required ? "yes" : "no"} | ${row.status} | ${evidenceCell(row.result)} |`);
65254
+ }
65255
+ const unsatisfied = evaluation.evaluation?.unsatisfied ?? [];
65256
+ if (unsatisfied.length > 0) {
65257
+ lines.push("", "### Unsatisfied", "");
65258
+ for (const u of unsatisfied) lines.push(`- \`${u.id}\`: **${u.status}** \u2014 ${cell(u.reason, 300)}`);
65259
+ }
65260
+ const native = evaluation.native;
65261
+ if (native) {
65262
+ lines.push(
65263
+ "",
65264
+ `## Native criteria pack \u2014 ${native.packId}`,
65265
+ "",
65266
+ "| Criterion | Command | Status | Detail |",
65267
+ "| --- | --- | --- | --- |"
65268
+ );
65269
+ for (const result of native.results) {
65270
+ const criterion = native.criteria.find((c) => c.id === result.criterionId);
65271
+ const check2 = criterion?.check;
65272
+ const command = check2?.kind === "command" ? check2.command : "\u2014";
65273
+ const label = criterion?.text ? `\`${result.criterionId}\` \u2014 ${cell(criterion.text, 120)}` : `\`${result.criterionId}\``;
65274
+ lines.push(`| ${label} | \`${cell(command, 120)}\` | ${result.status} | ${cell(result.detail ?? "\u2014")} |`);
65275
+ }
65276
+ }
65277
+ const review = evaluation.review;
65278
+ if (review) {
65279
+ lines.push(
65280
+ "",
65281
+ "## Advisory verifier review",
65282
+ "",
65283
+ "_Advisory only \u2014 this review cannot change the gate verdict above._",
65284
+ "",
65285
+ `- **Verdict**: ${review.verdict}`
65286
+ );
65287
+ if (review.score !== void 0) lines.push(`- **Score**: ${review.score}`);
65288
+ const model = review.effectiveModel.model ?? review.effectiveModel.provider ?? review.effectiveModel.mode;
65289
+ lines.push(`- **Model**: ${model}${review.usedLogprobs ? " (logprobs)" : ""}`);
65290
+ if (review.fallback) lines.push(`- **Fallback**: ${review.fallback}`);
65291
+ if (review.rationale) lines.push(`- **Rationale**: ${cell(review.rationale, 400)}`);
65292
+ }
65293
+ lines.push("", "---", "", "Machine record: `completion-proof.json` (same directory).");
65294
+ return `${lines.join("\n")}
65295
+ `;
65296
+ }
65297
+ function renderCompletionProof(evaluation, meta3 = {}) {
65298
+ return {
65299
+ markdown: renderMarkdown(evaluation, meta3),
65300
+ json: JSON.stringify(strictGateEventPayload(evaluation), null, 2)
65301
+ };
65302
+ }
65303
+ async function writeCompletionProof(evaluation, options = {}) {
65304
+ try {
65305
+ const dir = path44.join(options.baseDir ?? process.cwd(), ".zelari");
65306
+ await mkdir2(dir, { recursive: true });
65307
+ const { markdown, json: json3 } = renderCompletionProof(evaluation, options.meta);
65308
+ const markdownPath = path44.join(dir, "completion-proof.md");
65309
+ const jsonPath = path44.join(dir, "completion-proof.json");
65310
+ await writeFile2(markdownPath, markdown, "utf8");
65311
+ await writeFile2(jsonPath, json3, "utf8");
65312
+ return { markdownPath, jsonPath };
65313
+ } catch {
65314
+ return null;
65315
+ }
65316
+ }
65317
+
64737
65318
  // src/cli/hooks/permissionPicker.ts
64738
65319
  init_toolPermissions();
64739
65320
 
@@ -65774,10 +66355,15 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
65774
66355
  if (event.type === "agent_end") {
65775
66356
  let krakenSuppressFinish = false;
65776
66357
  const krakenSpineEmit = (input) => writerRef.current?.spine?.appendEvent(input) ?? Promise.resolve(null);
66358
+ const writeProofSafe2 = (gate) => writeCompletionProof(gate, { meta: { surface: "kraken", sessionId: sessionId2 } }).then(
66359
+ () => void 0,
66360
+ () => void 0
66361
+ );
65777
66362
  if (event.reason === "completed" && !krakenRepairEnqueued && (isKrakenSelectionEnabled() || nativePackEnabled()) && workPhase === "build") {
65778
66363
  const strictGate = await evaluateStrictBuildGate("build", { emit: krakenSpineEmit });
65779
66364
  const krakenGate = strictGate.gate;
65780
66365
  writerRef.current?.spine?.verificationRun(strictGateEventPayload(strictGate));
66366
+ await writeProofSafe2(strictGate);
65781
66367
  if (krakenGate.blocked) {
65782
66368
  krakenRepairEnqueued = true;
65783
66369
  markRepairTriggered();
@@ -65791,10 +66377,13 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
65791
66377
  krakenSuppressFinish = true;
65792
66378
  }
65793
66379
  }
65794
- if (event.reason === "completed" && krakenRepairEnqueued && !(await evaluateStrictBuildGate("build", { emit: krakenSpineEmit })).blocked) {
66380
+ const repairCheck = event.reason === "completed" && krakenRepairEnqueued ? await evaluateStrictBuildGate("build", { emit: krakenSpineEmit }) : null;
66381
+ if (repairCheck) await writeProofSafe2(repairCheck);
66382
+ if (repairCheck && !repairCheck.blocked) {
65795
66383
  markRepairSucceeded();
65796
66384
  } else if (event.reason === "completed" && krakenRepairEnqueued) {
65797
66385
  const still = await evaluateStrictBuildGate("build", { emit: krakenSpineEmit });
66386
+ await writeProofSafe2(still);
65798
66387
  if (still.blocked) {
65799
66388
  appendSystem(
65800
66389
  setMessages,
@@ -67910,7 +68499,7 @@ init_sessionManager();
67910
68499
  // src/cli/gitOps.ts
67911
68500
  import { execFile as execFile4 } from "node:child_process";
67912
68501
  import { promisify as promisify3 } from "node:util";
67913
- import path53 from "node:path";
68502
+ import path55 from "node:path";
67914
68503
  var execFileAsync3 = promisify3(execFile4);
67915
68504
  async function git4(cwd, args) {
67916
68505
  try {
@@ -67956,7 +68545,7 @@ async function undoWorkingChanges(opts = {}) {
67956
68545
  };
67957
68546
  }
67958
68547
  function defaultProjectRoot() {
67959
- return path53.resolve(__dirname, "..", "..", "..");
68548
+ return path55.resolve(__dirname, "..", "..", "..");
67960
68549
  }
67961
68550
 
67962
68551
  // src/cli/slashHandlers/git.ts
@@ -68243,11 +68832,11 @@ function handleCacheStats(ctx) {
68243
68832
  init_messageHelpers();
68244
68833
  init_serviceFactory();
68245
68834
  import { promises as fs30 } from "node:fs";
68246
- import * as path55 from "node:path";
68835
+ import * as path57 from "node:path";
68247
68836
 
68248
68837
  // src/cli/memory/promotion.ts
68249
68838
  import { promises as fs29 } from "node:fs";
68250
- import * as path54 from "node:path";
68839
+ import * as path56 from "node:path";
68251
68840
  var START = "<!-- zelari:memory-promotions:start -->";
68252
68841
  var END = "<!-- zelari:memory-promotions:end -->";
68253
68842
  var DURABLE_KINDS = /* @__PURE__ */ new Set(["fact", "decision", "constraint", "preference", "procedure"]);
@@ -68258,13 +68847,13 @@ function lineFor(node) {
68258
68847
  }
68259
68848
  async function promoteMemoryToAgentsMd(projectRoot, node) {
68260
68849
  if (node.status !== "active") {
68261
- return { added: false, path: path54.join(projectRoot, "AGENTS.md"), reason: `memory is ${node.status}` };
68850
+ return { added: false, path: path56.join(projectRoot, "AGENTS.md"), reason: `memory is ${node.status}` };
68262
68851
  }
68263
68852
  if (!DURABLE_KINDS.has(node.kind)) {
68264
- return { added: false, path: path54.join(projectRoot, "AGENTS.md"), reason: `${node.kind} is not a durable instruction kind` };
68853
+ return { added: false, path: path56.join(projectRoot, "AGENTS.md"), reason: `${node.kind} is not a durable instruction kind` };
68265
68854
  }
68266
- const root = await fs29.realpath(projectRoot).catch(() => path54.resolve(projectRoot));
68267
- const target = path54.join(root, "AGENTS.md");
68855
+ const root = await fs29.realpath(projectRoot).catch(() => path56.resolve(projectRoot));
68856
+ const target = path56.join(root, "AGENTS.md");
68268
68857
  try {
68269
68858
  const stat2 = await fs29.lstat(target);
68270
68859
  if (stat2.isSymbolicLink() || !stat2.isFile()) throw new Error("AGENTS.md must be a regular project file.");
@@ -68329,22 +68918,22 @@ function sourceLine(source2) {
68329
68918
  return entries.length ? entries.map(([key, value]) => `${key}=${value}`).join(" \xB7 ") : "unknown";
68330
68919
  }
68331
68920
  function isInside(root, target) {
68332
- const relative6 = path55.relative(root, target);
68333
- return relative6 === "" || !relative6.startsWith("..") && !path55.isAbsolute(relative6);
68921
+ const relative6 = path57.relative(root, target);
68922
+ return relative6 === "" || !relative6.startsWith("..") && !path57.isAbsolute(relative6);
68334
68923
  }
68335
68924
  async function safeExportPath(cwd, requested) {
68336
- const lexicalRoot = path55.resolve(cwd);
68925
+ const lexicalRoot = path57.resolve(cwd);
68337
68926
  const root = await fs30.realpath(lexicalRoot).catch(() => lexicalRoot);
68338
- const fallback = path55.join(root, ".zelari", "memory", `export-${Date.now()}.json`);
68339
- const target = requested?.trim() ? path55.resolve(root, requested.trim()) : fallback;
68927
+ const fallback = path57.join(root, ".zelari", "memory", `export-${Date.now()}.json`);
68928
+ const target = requested?.trim() ? path57.resolve(root, requested.trim()) : fallback;
68340
68929
  if (!isInside(root, target)) {
68341
68930
  throw new Error("Export path must stay inside the active project.");
68342
68931
  }
68343
- const parent = path55.dirname(target);
68344
- const relativeParent = path55.relative(root, parent);
68932
+ const parent = path57.dirname(target);
68933
+ const relativeParent = path57.relative(root, parent);
68345
68934
  let cursor = root;
68346
- for (const segment of relativeParent.split(path55.sep).filter(Boolean)) {
68347
- cursor = path55.join(cursor, segment);
68935
+ for (const segment of relativeParent.split(path57.sep).filter(Boolean)) {
68936
+ cursor = path57.join(cursor, segment);
68348
68937
  try {
68349
68938
  const stat2 = await fs30.lstat(cursor);
68350
68939
  if (stat2.isSymbolicLink()) {
@@ -68516,9 +69105,9 @@ ${message}` : message
68516
69105
  }
68517
69106
  case "export": {
68518
69107
  const target = await safeExportPath(ctx.cwd, args.join(" ").trim() || void 0);
68519
- await fs30.mkdir(path55.dirname(target), { recursive: true });
68520
- const root = await fs30.realpath(ctx.cwd).catch(() => path55.resolve(ctx.cwd));
68521
- const realParent = await fs30.realpath(path55.dirname(target));
69108
+ await fs30.mkdir(path57.dirname(target), { recursive: true });
69109
+ const root = await fs30.realpath(ctx.cwd).catch(() => path57.resolve(ctx.cwd));
69110
+ const realParent = await fs30.realpath(path57.dirname(target));
68522
69111
  if (!isInside(root, realParent)) {
68523
69112
  throw new Error("Export path resolves outside the active project.");
68524
69113
  }
@@ -68714,7 +69303,7 @@ import { promises as fs34 } from "node:fs";
68714
69303
  init_zod();
68715
69304
  init_taskTool();
68716
69305
  import { promises as fs33 } from "node:fs";
68717
- import path59 from "node:path";
69306
+ import path61 from "node:path";
68718
69307
  import { randomBytes as randomBytes5 } from "node:crypto";
68719
69308
  var CsvFanoutArgsSchema = external_exports.object({
68720
69309
  csv_path: external_exports.string().min(1),
@@ -68731,7 +69320,7 @@ var CsvFanoutArgsSchema = external_exports.object({
68731
69320
  scope_template: external_exports.array(external_exports.string()).optional(),
68732
69321
  /** Default: ZELARI_KRAKEN_MAX_PARALLEL. */
68733
69322
  max_concurrency: external_exports.number().int().positive().optional(),
68734
- /** Per-row timeout (ms). Default: 5 min for verify, 15 min for general. */
69323
+ /** Per-row timeout (ms). Default: 5 min for verify, 45 min for general. */
68735
69324
  max_runtime_seconds: external_exports.number().int().positive().optional()
68736
69325
  });
68737
69326
  async function readCsv(filePath) {
@@ -68808,8 +69397,8 @@ function resolveMaxConcurrency(env = process.env) {
68808
69397
  }
68809
69398
  async function runCsvFanout(args, deps, opts) {
68810
69399
  const start = Date.now();
68811
- const absCsv = path59.isAbsolute(args.csv_path) ? args.csv_path : path59.join(opts.parentCwd, args.csv_path);
68812
- const absOut = path59.isAbsolute(args.output_csv_path) ? args.output_csv_path : path59.join(opts.parentCwd, args.output_csv_path);
69400
+ const absCsv = path61.isAbsolute(args.csv_path) ? args.csv_path : path61.join(opts.parentCwd, args.csv_path);
69401
+ const absOut = path61.isAbsolute(args.output_csv_path) ? args.output_csv_path : path61.join(opts.parentCwd, args.output_csv_path);
68813
69402
  const { headers: headers2, rows } = await readCsv(absCsv);
68814
69403
  if (headers2.length === 0) {
68815
69404
  throw new Error(`kraken_csv_fanout: ${absCsv} is empty`);
@@ -68822,7 +69411,7 @@ async function runCsvFanout(args, deps, opts) {
68822
69411
  }
68823
69412
  const concurrency = args.max_concurrency ?? resolveMaxConcurrency();
68824
69413
  opts.onLog?.(`csv fanout: ${rows.length} rows \xD7 ${args.agent_kind} @ concurrency=${concurrency}`);
68825
- const perRowMs = args.max_runtime_seconds !== void 0 ? args.max_runtime_seconds * 1e3 : args.agent_kind === "general" ? 9e5 : 3e5;
69414
+ const perRowMs = args.max_runtime_seconds !== void 0 ? args.max_runtime_seconds * 1e3 : args.agent_kind === "general" ? TASK_TOOL_TIMEOUT_MS : 3e5;
68826
69415
  const outputRecords = rows.map((r) => ({ ...r, status: "pending", result: "", error: "" }));
68827
69416
  const outHeaders = [...headers2, "status", "result", "error"];
68828
69417
  let writeChain2 = Promise.resolve();
@@ -68865,7 +69454,7 @@ async function runCsvFanout(args, deps, opts) {
68865
69454
  errored += 1;
68866
69455
  errors.push(`${row[args.id_column] ?? i}: ${res.error}`);
68867
69456
  }
68868
- await fs33.mkdir(path59.dirname(absOut), { recursive: true });
69457
+ await fs33.mkdir(path61.dirname(absOut), { recursive: true });
68869
69458
  await queueWrite(serializeCsv(outHeaders, outputRecords));
68870
69459
  }
68871
69460
  }
@@ -69060,7 +69649,7 @@ function splitArgs(s) {
69060
69649
  // src/cli/slashHandlers/krakenWorkbench.ts
69061
69650
  init_messageHelpers();
69062
69651
  import { promises as fs35 } from "node:fs";
69063
- import path60 from "node:path";
69652
+ import path62 from "node:path";
69064
69653
 
69065
69654
  // src/cli/kraken/workbenchView.ts
69066
69655
  var EMPTY = {
@@ -69177,14 +69766,14 @@ function formatWorkbenchForTerminal(p3) {
69177
69766
 
69178
69767
  // src/cli/slashHandlers/krakenWorkbench.ts
69179
69768
  async function handleKrakenWorkbench(ctx) {
69180
- const dir = path60.join(ctx.cwd, ".zelari", "radio");
69769
+ const dir = path62.join(ctx.cwd, ".zelari", "radio");
69181
69770
  let latest = null;
69182
69771
  let latestMtime = 0;
69183
69772
  try {
69184
69773
  const files = await fs35.readdir(dir);
69185
69774
  for (const f of files) {
69186
69775
  if (!f.startsWith("workbench-") || !f.endsWith(".md")) continue;
69187
- const full = path60.join(dir, f);
69776
+ const full = path62.join(dir, f);
69188
69777
  const stat2 = await fs35.stat(full);
69189
69778
  if (stat2.mtimeMs > latestMtime) {
69190
69779
  latestMtime = stat2.mtimeMs;
@@ -69201,10 +69790,10 @@ async function handleKrakenWorkbench(ctx) {
69201
69790
  const parsed = parseWorkbench(content);
69202
69791
  const rendered = formatWorkbenchForTerminal(parsed);
69203
69792
  if (!rendered.trim()) {
69204
- appendSystem(ctx.setMessages, `[kraken workbench] ${path60.basename(latest)}: (no nodes / no events yet)`);
69793
+ appendSystem(ctx.setMessages, `[kraken workbench] ${path62.basename(latest)}: (no nodes / no events yet)`);
69205
69794
  return;
69206
69795
  }
69207
- appendSystem(ctx.setMessages, `[kraken workbench] ${path60.basename(latest)}:
69796
+ appendSystem(ctx.setMessages, `[kraken workbench] ${path62.basename(latest)}:
69208
69797
  ${rendered}`);
69209
69798
  }
69210
69799
 
@@ -69511,15 +70100,15 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
69511
70100
  // src/cli/slashHandlers/promoteMember.ts
69512
70101
  init_messageHelpers();
69513
70102
  import { promises as fs36 } from "node:fs";
69514
- import path63 from "node:path";
70103
+ import path65 from "node:path";
69515
70104
  import os12 from "node:os";
69516
70105
  async function handlePromoteMember(ctx, memberId) {
69517
70106
  try {
69518
70107
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
69519
70108
  const { skill, markdown } = promoteMember2(memberId);
69520
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path63.join(os12.homedir(), ".tmp", "zelari-code", "skills");
70109
+ const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path65.join(os12.homedir(), ".tmp", "zelari-code", "skills");
69521
70110
  await fs36.mkdir(skillDir, { recursive: true });
69522
- const filePath = path63.join(skillDir, `${skill.id}.md`);
70111
+ const filePath = path65.join(skillDir, `${skill.id}.md`);
69523
70112
  await fs36.writeFile(filePath, markdown, "utf8");
69524
70113
  appendSystem(
69525
70114
  ctx.setMessages,
@@ -69536,25 +70125,25 @@ async function handlePromoteMember(ctx, memberId) {
69536
70125
  }
69537
70126
 
69538
70127
  // src/cli/branchManager.ts
69539
- import { promises as fs37, existsSync as existsSync45, readFileSync as readFileSync36, writeFileSync as writeFileSync23, mkdirSync as mkdirSync19, statSync as statSync7, rmSync as rmSync3 } from "node:fs";
69540
- import path64 from "node:path";
70128
+ import { promises as fs37, existsSync as existsSync45, readFileSync as readFileSync37, writeFileSync as writeFileSync23, mkdirSync as mkdirSync19, statSync as statSync7, rmSync as rmSync3 } from "node:fs";
70129
+ import path66 from "node:path";
69541
70130
  import os13 from "node:os";
69542
70131
  var META_FILENAME = "meta.json";
69543
70132
  var SESSIONS_SUBDIR = "sessions";
69544
70133
  function getBranchesBaseDir() {
69545
- return process.env.ANATHEMA_BRANCHES_DIR ?? path64.join(os13.homedir(), ".tmp", "zelari-code", "branches");
70134
+ return process.env.ANATHEMA_BRANCHES_DIR ?? path66.join(os13.homedir(), ".tmp", "zelari-code", "branches");
69546
70135
  }
69547
70136
  function getSessionsBaseDir() {
69548
- return process.env.ANATHEMA_SESSIONS_DIR ?? path64.join(os13.homedir(), ".tmp", "zelari-code", "sessions");
70137
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path66.join(os13.homedir(), ".tmp", "zelari-code", "sessions");
69549
70138
  }
69550
70139
  function branchPathFor(name, baseDir) {
69551
- return path64.join(baseDir, name);
70140
+ return path66.join(baseDir, name);
69552
70141
  }
69553
70142
  function metaPathFor(name, baseDir) {
69554
- return path64.join(baseDir, name, META_FILENAME);
70143
+ return path66.join(baseDir, name, META_FILENAME);
69555
70144
  }
69556
70145
  function sessionsPathFor(name, baseDir) {
69557
- return path64.join(baseDir, name, SESSIONS_SUBDIR);
70146
+ return path66.join(baseDir, name, SESSIONS_SUBDIR);
69558
70147
  }
69559
70148
  function readBranchMeta(name, baseDir) {
69560
70149
  const metaPath = metaPathFor(name, baseDir);
@@ -69562,7 +70151,7 @@ function readBranchMeta(name, baseDir) {
69562
70151
  throw new BranchNotFoundError(`Branch "${name}" not found`);
69563
70152
  }
69564
70153
  try {
69565
- const raw = readFileSync36(metaPath, "utf-8");
70154
+ const raw = readFileSync37(metaPath, "utf-8");
69566
70155
  const parsed = JSON.parse(raw);
69567
70156
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
69568
70157
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -69579,7 +70168,7 @@ function readBranchMeta(name, baseDir) {
69579
70168
  }
69580
70169
  function writeBranchMeta(name, baseDir, meta3) {
69581
70170
  const metaPath = metaPathFor(name, baseDir);
69582
- mkdirSync19(path64.dirname(metaPath), { recursive: true });
70171
+ mkdirSync19(path66.dirname(metaPath), { recursive: true });
69583
70172
  writeFileSync23(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
69584
70173
  }
69585
70174
  async function countSessions(name, baseDir) {
@@ -69630,14 +70219,14 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
69630
70219
  if (branchExists(name, baseDir)) {
69631
70220
  throw new BranchAlreadyExistsError(name);
69632
70221
  }
69633
- const sourcePath = path64.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
70222
+ const sourcePath = path66.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
69634
70223
  if (!existsSync45(sourcePath)) {
69635
70224
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
69636
70225
  }
69637
70226
  const branchPath = branchPathFor(name, baseDir);
69638
70227
  const branchSessionsPath = sessionsPathFor(name, baseDir);
69639
70228
  mkdirSync19(branchSessionsPath, { recursive: true });
69640
- const destPath = path64.join(branchSessionsPath, `${fromSessionId}.jsonl`);
70229
+ const destPath = path66.join(branchSessionsPath, `${fromSessionId}.jsonl`);
69641
70230
  await fs37.copyFile(sourcePath, destPath);
69642
70231
  const meta3 = {
69643
70232
  name,
@@ -69741,14 +70330,14 @@ async function handleBranchCheckout(ctx, branchName) {
69741
70330
  // src/cli/slashHandlers/workspace.ts
69742
70331
  init_messageHelpers();
69743
70332
  import { promises as fs38 } from "node:fs";
69744
- import path65 from "node:path";
70333
+ import path67 from "node:path";
69745
70334
  async function handleWorkspaceShow(ctx, what) {
69746
70335
  try {
69747
- const zelari = path65.join(process.cwd(), ".zelari");
70336
+ const zelari = path67.join(process.cwd(), ".zelari");
69748
70337
  let content;
69749
70338
  switch (what) {
69750
70339
  case "plan": {
69751
- const planPath = path65.join(zelari, "plan.md");
70340
+ const planPath = path67.join(zelari, "plan.md");
69752
70341
  try {
69753
70342
  content = await fs38.readFile(planPath, "utf-8");
69754
70343
  } catch {
@@ -69757,7 +70346,7 @@ async function handleWorkspaceShow(ctx, what) {
69757
70346
  break;
69758
70347
  }
69759
70348
  case "decisions": {
69760
- const decisionsDir = path65.join(zelari, "decisions");
70349
+ const decisionsDir = path67.join(zelari, "decisions");
69761
70350
  try {
69762
70351
  const files = (await fs38.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
69763
70352
  if (files.length === 0) {
@@ -69767,7 +70356,7 @@ async function handleWorkspaceShow(ctx, what) {
69767
70356
  `];
69768
70357
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
69769
70358
  for (const f of files) {
69770
- const raw = await fs38.readFile(path65.join(decisionsDir, f), "utf-8");
70359
+ const raw = await fs38.readFile(path67.join(decisionsDir, f), "utf-8");
69771
70360
  const { meta: meta3, body } = parseFrontmatter2(raw);
69772
70361
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
69773
70362
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -69780,7 +70369,7 @@ async function handleWorkspaceShow(ctx, what) {
69780
70369
  break;
69781
70370
  }
69782
70371
  case "risks": {
69783
- const risksPath = path65.join(zelari, "risks.md");
70372
+ const risksPath = path67.join(zelari, "risks.md");
69784
70373
  try {
69785
70374
  content = await fs38.readFile(risksPath, "utf-8");
69786
70375
  } catch {
@@ -69789,7 +70378,7 @@ async function handleWorkspaceShow(ctx, what) {
69789
70378
  break;
69790
70379
  }
69791
70380
  case "agents": {
69792
- const agentsPath = path65.join(process.cwd(), "AGENTS.MD");
70381
+ const agentsPath = path67.join(process.cwd(), "AGENTS.MD");
69793
70382
  try {
69794
70383
  content = await fs38.readFile(agentsPath, "utf-8");
69795
70384
  } catch {
@@ -69798,7 +70387,7 @@ async function handleWorkspaceShow(ctx, what) {
69798
70387
  break;
69799
70388
  }
69800
70389
  case "docs": {
69801
- const docsDir = path65.join(zelari, "docs");
70390
+ const docsDir = path67.join(zelari, "docs");
69802
70391
  try {
69803
70392
  const files = (await fs38.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
69804
70393
  content = files.length ? `# Docs (${files.length})
@@ -69840,7 +70429,7 @@ async function handleWorkspaceReset(ctx, force) {
69840
70429
  return;
69841
70430
  }
69842
70431
  try {
69843
- const target = path65.join(process.cwd(), ".zelari");
70432
+ const target = path67.join(process.cwd(), ".zelari");
69844
70433
  await fs38.rm(target, { recursive: true, force: true });
69845
70434
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
69846
70435
  } catch (err) {
@@ -69852,7 +70441,7 @@ async function handleWorkspaceReset(ctx, force) {
69852
70441
  init_provider2();
69853
70442
 
69854
70443
  // src/cli/slashHandlers/skills.ts
69855
- import path66 from "node:path";
70444
+ import path68 from "node:path";
69856
70445
  import os14 from "node:os";
69857
70446
 
69858
70447
  // src/cli/skillHistory.ts
@@ -69981,7 +70570,7 @@ function handleSkillPicker(ctx, skills, openPicker, fallbackMessage) {
69981
70570
  });
69982
70571
  }
69983
70572
  async function handleSkillStats(ctx, skillId) {
69984
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path66.join(os14.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
70573
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path68.join(os14.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
69985
70574
  try {
69986
70575
  const records = await readSkillHistory(historyFile);
69987
70576
  const stats = getSkillStats(records, skillId);
@@ -69997,7 +70586,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
69997
70586
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
69998
70587
  return;
69999
70588
  }
70000
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path66.join(os14.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
70589
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path68.join(os14.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
70001
70590
  try {
70002
70591
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
70003
70592
  appendSystem(ctx.setMessages, formatted);
@@ -71512,6 +72101,54 @@ init_candidateRegistry();
71512
72101
  init_metrics2();
71513
72102
  init_completionGate();
71514
72103
  init_delegationPolicy();
72104
+
72105
+ // src/cli/orchestration/policy.ts
72106
+ var DEFAULT_MAX_SOLO_CHARS = 300;
72107
+ var KRAKEN_SIGNALS = [
72108
+ { re: /\bimplement(?:s|ed|ing)?\b/i, reason: "implementation signal" },
72109
+ { re: /\brefactor(?:ing|ed)?\b/i, reason: "refactor signal" },
72110
+ { re: /\bmigrat(?:e|es|ed|ion|ing)\b/i, reason: "migration signal" },
72111
+ {
72112
+ re: /\b(?:add|build|create|introduce)\b[^.?!]{0,48}\b(?:feature|endpoint|module|service|command|api|registry|runtime)\b/i,
72113
+ reason: "new-capability signal"
72114
+ },
72115
+ {
72116
+ re: /\b(?:unit|integration|e2e|end-to-end)\s+(?:tests?|testing|specs?)\b|\bwrite\s+(?:the\s+|some\s+)?tests?\b/i,
72117
+ reason: "test-writing signal"
72118
+ },
72119
+ {
72120
+ re: /\b\d+\s*-?\s*(?:files?|modules?|packages?|components?|services?|endpoints?|worktrees?)\b/i,
72121
+ reason: "multi-artifact count"
72122
+ },
72123
+ {
72124
+ re: /\b(?:across|spanning|between|touching)\s+(?:all\s+|the\s+)?(?:\w+\s+){0,2}(?:files|modules|packages|layers|surfaces)\b/i,
72125
+ reason: "cross-cutting scope"
72126
+ }
72127
+ ];
72128
+ var QUESTION_RE = /\?\s*$|^(?:who|what|why|when|where|which|how|is|are|was|were|does|do|did|can|could|should|would|will)\b/i;
72129
+ var EXPLAIN_RE = /\b(?:explain|describe|summarize|summarise|clarify|walk\W*me\W*through)\b/i;
72130
+ var READONLY_RE = /\b(?:find|show|list|grep|search|locate|inspect|read|check|review|where\W+is)\b/i;
72131
+ function chooseOrchestration(task, opts = {}) {
72132
+ const text = String(task ?? "").trim();
72133
+ const failClosed = () => ({
72134
+ surface: "solo",
72135
+ reason: "fail-closed default"
72136
+ });
72137
+ if (!text) return failClosed();
72138
+ for (const { re, reason } of KRAKEN_SIGNALS) {
72139
+ if (re.test(text)) return { surface: "kraken", reason };
72140
+ }
72141
+ if (QUESTION_RE.test(text)) return { surface: "solo", reason: "question-shaped task" };
72142
+ if (EXPLAIN_RE.test(text)) return { surface: "solo", reason: "explanation request" };
72143
+ if (READONLY_RE.test(text)) return { surface: "solo", reason: "read-only request" };
72144
+ const budget = opts.maxSoloChars ?? DEFAULT_MAX_SOLO_CHARS;
72145
+ if (text.length <= budget) {
72146
+ return { surface: "solo", reason: "small task, no heavy signals" };
72147
+ }
72148
+ return failClosed();
72149
+ }
72150
+
72151
+ // src/cli/runHeadless.ts
71515
72152
  init_headless();
71516
72153
  init_claudeProvider();
71517
72154
  init_skills2();
@@ -71550,12 +72187,13 @@ function createStreamScrubber2() {
71550
72187
  init_taskTool();
71551
72188
  init_sessionTodos();
71552
72189
  import { promises as fs41 } from "node:fs";
71553
- import path68 from "node:path";
72190
+ import path70 from "node:path";
71554
72191
  import { randomUUID as randomUUID8 } from "node:crypto";
71555
72192
 
71556
72193
  // src/cli/kraken/verifierLifecycle.ts
71557
72194
  init_verification2();
71558
72195
  init_krakenSelectTool();
72196
+ init_krakenModel();
71559
72197
 
71560
72198
  // src/cli/kraken/verifierResolution.ts
71561
72199
  init_providerConfig();
@@ -71599,18 +72237,61 @@ function makeVerifierCallModel(loadStream, identity, timeoutMs2 = 12e4) {
71599
72237
  return { text, provider: identity.provider, model: identity.model };
71600
72238
  };
71601
72239
  }
71602
- function resolveIdentity(selection, session) {
72240
+ function resolveIdentity(selection, session, familyCandidates, env = process.env) {
71603
72241
  if (selection.mode === "fixed") {
71604
72242
  return { provider: selection.provider, model: selection.model };
71605
72243
  }
71606
- return session && session.provider && session.model ? session : null;
72244
+ if (!session || !session.provider || !session.model) return null;
72245
+ if (familyCandidates && familyCandidates.length > 0) {
72246
+ const cross = resolveCrossModelVerifier(session, familyCandidates, env);
72247
+ if (cross) return { provider: cross.provider, model: cross.model };
72248
+ }
72249
+ return session;
72250
+ }
72251
+ function isTestEvidenceCriterion(criterionId2) {
72252
+ const id3 = criterionId2.toLowerCase();
72253
+ return ["test", "typecheck", "build", "lint"].some((k) => id3.includes(k));
72254
+ }
72255
+ function extractTestOutputExcerpt(results, maxChars = 4e3) {
72256
+ const lines = [];
72257
+ for (const r of results) {
72258
+ if (!isTestEvidenceCriterion(r.criterionId)) continue;
72259
+ lines.push([r.criterionId, r.status, r.detail].filter(Boolean).join(" \u2014 "));
72260
+ }
72261
+ if (lines.length === 0) return "";
72262
+ return lines.join("\n").slice(0, maxChars);
72263
+ }
72264
+ async function buildBlindReviewInput(evaluation, deps) {
72265
+ const results = evaluation.results ?? [];
72266
+ const passed = results.filter((r) => r.status === "pass").length;
72267
+ const verdict = evaluation.evaluation?.verdict ?? "UNKNOWN";
72268
+ const summary = `Kraken BUILD turn \u2014 deterministic evidence: ${passed}/${results.length} criteria pass, completion verdict ${verdict}.`;
72269
+ const task = deps.task?.trim();
72270
+ const testOutputExcerpt = extractTestOutputExcerpt(results);
72271
+ let diffSummary;
72272
+ try {
72273
+ const res = await (deps.getDiff ?? getWorkingDiff)({
72274
+ cwd: deps.cwd ?? process.cwd(),
72275
+ maxChars: 8e3,
72276
+ staged: true
72277
+ });
72278
+ if (res && !res.empty && res.diff) diffSummary = res.diff;
72279
+ } catch {
72280
+ }
72281
+ return {
72282
+ ...task ? { task } : {},
72283
+ summary,
72284
+ ...diffSummary !== void 0 ? { diffSummary } : {},
72285
+ ...testOutputExcerpt ? { testOutputExcerpt } : {},
72286
+ results
72287
+ };
71607
72288
  }
71608
72289
  async function runAdvisoryVerifierReview(evaluation, deps = {}) {
71609
72290
  if (!evaluation.evaluation || !evaluation.results) return null;
71610
72291
  const env = deps.env ?? process.env;
71611
72292
  const selection = deps.selection ?? loadVerifierModelSelection();
71612
72293
  if (!verifierReviewEnabled(selection, env)) return null;
71613
- const identity = resolveIdentity(selection, deps.session);
72294
+ const identity = resolveIdentity(selection, deps.session, deps.familyCandidates, env);
71614
72295
  let callModel = deps.callModel;
71615
72296
  if (!callModel) {
71616
72297
  if (!identity || !deps.loadStream) return null;
@@ -71627,11 +72308,9 @@ async function runAdvisoryVerifierReview(evaluation, deps = {}) {
71627
72308
  emit: deps.emit,
71628
72309
  env
71629
72310
  });
71630
- const passed = evaluation.results.filter((r) => r.status === "pass").length;
71631
- const summary = `Kraken BUILD turn \u2014 deterministic evidence: ${passed}/${evaluation.results.length} criteria pass, completion verdict ${evaluation.evaluation.verdict}.`;
72311
+ const blind = await buildBlindReviewInput(evaluation, deps);
71632
72312
  const review = await service.reviewCompletion({
71633
- summary,
71634
- results: evaluation.results,
72313
+ ...blind,
71635
72314
  session: deps.session
71636
72315
  });
71637
72316
  evaluation.review = review;
@@ -71945,6 +72624,13 @@ ${err.stack}` : "";
71945
72624
  model
71946
72625
  });
71947
72626
  }
72627
+ if (opts.orchestrationAuto) {
72628
+ const verdict = chooseOrchestration(opts.task ?? "");
72629
+ const line = `[orchestration] --mode auto -> surface=${verdict.surface} (${verdict.reason})`;
72630
+ if (opts.output === "json") emitEvent({ type: "log", message: line });
72631
+ else process.stderr.write(`[zelari-code --headless] ${line}
72632
+ `);
72633
+ }
71948
72634
  const mode = opts.mode ?? (opts.useCouncil ? "council" : "kraken");
71949
72635
  const profileId = resolveHeadlessProfileId(mode, opts.profile);
71950
72636
  if (opts.output === "json") {
@@ -72018,7 +72704,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
72018
72704
  try {
72019
72705
  let preflightGraph;
72020
72706
  if (opts.runPlan && opts.runPlan.trim() !== "") {
72021
- const planPath = path68.join(cwd, ".zelari", "radio", `plan-${opts.runPlan}.json`);
72707
+ const planPath = path70.join(cwd, ".zelari", "radio", `plan-${opts.runPlan}.json`);
72022
72708
  log(`loading pre-flight plan: ${planPath}`);
72023
72709
  let raw;
72024
72710
  try {
@@ -72058,8 +72744,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
72058
72744
  log(formatKrakenGraphAscii2(graph));
72059
72745
  if (opts.planOnly) {
72060
72746
  const planId = randomUUID8();
72061
- const planDir = path68.join(cwd, ".zelari", "radio");
72062
- const planPath = path68.join(planDir, `plan-${planId}.json`);
72747
+ const planDir = path70.join(cwd, ".zelari", "radio");
72748
+ const planPath = path70.join(planDir, `plan-${planId}.json`);
72063
72749
  await fs41.mkdir(planDir, { recursive: true });
72064
72750
  await fs41.writeFile(
72065
72751
  planPath,
@@ -72087,6 +72773,18 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
72087
72773
  root: cwd,
72088
72774
  audit,
72089
72775
  sessionId: sessionId2,
72776
+ // P0.4 capability inheritance: tentacles intersect the headless
72777
+ // parent policy. Headless runs are auto-allow (the same literal
72778
+ // the main headless registry below uses), so this is a no-op
72779
+ // today — wired for correctness if that default ever tightens.
72780
+ parentPolicy: {
72781
+ read: "allow",
72782
+ write: "allow",
72783
+ execute: "allow",
72784
+ network: "allow",
72785
+ ui: "allow",
72786
+ auto: true
72787
+ },
72090
72788
  // Anchor every tentacle to the SAME provider/model this run
72091
72789
  // resolved (Desktop's selector, or --provider/--model), instead
72092
72790
  // of the persisted provider.json default the factory falls back
@@ -72179,6 +72877,12 @@ async function registerHeadlessMcp(toolRegistry, opts) {
72179
72877
  }
72180
72878
  }
72181
72879
  }
72880
+ function writeProofSafe(gate, meta3, baseDir = process.cwd()) {
72881
+ return writeCompletionProof(gate, { baseDir, meta: meta3 }).then(
72882
+ () => void 0,
72883
+ () => void 0
72884
+ );
72885
+ }
72182
72886
  async function runHeadlessSingle(opts, provider, model, providerStream) {
72183
72887
  const sessionId2 = crypto.randomUUID();
72184
72888
  const memoryFactory = await Promise.resolve().then(() => (init_serviceFactory(), serviceFactory_exports));
@@ -72517,6 +73221,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
72517
73221
  let strictExit = 0;
72518
73222
  const verifierReviewDeps = {
72519
73223
  session: { provider, model },
73224
+ task: effectiveTask,
72520
73225
  loadStream: async (providerId, modelId) => {
72521
73226
  if (providerId === provider) return providerStream;
72522
73227
  try {
@@ -72544,6 +73249,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
72544
73249
  if (opts.output === "json") {
72545
73250
  emitEvent({ type: "verification_run", ...verificationPayload });
72546
73251
  }
73252
+ await writeProofSafe(strictGate, { surface: "kraken", sessionId: spine.sessionId });
72547
73253
  if (strictGate.blocked) {
72548
73254
  const repairPrompt = buildKrakenRepairPrompt(gate);
72549
73255
  if (opts.output === "json") {
@@ -72577,6 +73283,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
72577
73283
  if (opts.output === "json") {
72578
73284
  emitEvent({ type: "verification_run", ...afterPayload });
72579
73285
  }
73286
+ await writeProofSafe(after, { surface: "kraken", sessionId: spine.sessionId });
72580
73287
  if (!after.blocked) markRepairSucceeded();
72581
73288
  else {
72582
73289
  strictExit = strictGateExitCode(after);
@@ -72610,7 +73317,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
72610
73317
  if (json3) {
72611
73318
  if (opts.exportSessionPath === "-") process.stdout.write(json3 + "\n");
72612
73319
  else {
72613
- await fs41.mkdir(path68.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
73320
+ await fs41.mkdir(path70.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
72614
73321
  await fs41.writeFile(opts.exportSessionPath, json3, "utf8");
72615
73322
  }
72616
73323
  }
@@ -72839,7 +73546,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
72839
73546
  if (json3) {
72840
73547
  if (opts.exportSessionPath === "-") process.stdout.write(json3 + "\n");
72841
73548
  else {
72842
- await fs41.mkdir(path68.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
73549
+ await fs41.mkdir(path70.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
72843
73550
  await fs41.writeFile(opts.exportSessionPath, json3, "utf8");
72844
73551
  }
72845
73552
  }
@@ -73219,6 +73926,7 @@ ${ragContext}` : slicePrompt;
73219
73926
  if (opts.output === "json") {
73220
73927
  emitEvent({ type: "verification_run", ...missionVerificationPayload });
73221
73928
  }
73929
+ await writeProofSafe(missionGate, { surface: "mission", sessionId: spine.sessionId }, projectRoot);
73222
73930
  if (missionGate.blocked) {
73223
73931
  exitCode = strictGateExitCode(missionGate);
73224
73932
  spine.missionPhase("verification", "mission-strict-blocked");
@@ -73253,7 +73961,7 @@ ${ragContext}` : slicePrompt;
73253
73961
  if (json3) {
73254
73962
  if (opts.exportSessionPath === "-") process.stdout.write(json3 + "\n");
73255
73963
  else {
73256
- await fs41.mkdir(path68.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
73964
+ await fs41.mkdir(path70.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
73257
73965
  await fs41.writeFile(opts.exportSessionPath, json3, "utf8");
73258
73966
  }
73259
73967
  }
@@ -73455,8 +74163,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
73455
74163
  let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
73456
74164
  if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
73457
74165
  try {
73458
- const path72 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
73459
- name = path72 && /^[a-z0-9]/.test(path72) ? path72 : "imported-skill";
74166
+ const path74 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
74167
+ name = path74 && /^[a-z0-9]/.test(path74) ? path74 : "imported-skill";
73460
74168
  } catch {
73461
74169
  name = "imported-skill";
73462
74170
  }