zelari-code 2.30.0 → 2.32.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 (38) hide show
  1. package/README.md +6 -4
  2. package/dist/cli/app.js +22 -1
  3. package/dist/cli/app.js.map +1 -1
  4. package/dist/cli/budget/historySummary.js +41 -0
  5. package/dist/cli/budget/historySummary.js.map +1 -1
  6. package/dist/cli/budget/tokenBudget.js +6 -2
  7. package/dist/cli/budget/tokenBudget.js.map +1 -1
  8. package/dist/cli/components/StatusBar.js +4 -1
  9. package/dist/cli/components/StatusBar.js.map +1 -1
  10. package/dist/cli/costBudget.js +16 -0
  11. package/dist/cli/costBudget.js.map +1 -1
  12. package/dist/cli/headless/runOneTurn.js +9 -11
  13. package/dist/cli/headless/runOneTurn.js.map +1 -1
  14. package/dist/cli/hooks/historyCompaction.js +59 -18
  15. package/dist/cli/hooks/historyCompaction.js.map +1 -1
  16. package/dist/cli/hooks/useChatTurn.js +24 -0
  17. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  18. package/dist/cli/hooks/useSlashDispatch.js +16 -0
  19. package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
  20. package/dist/cli/kraken/verifyStatus.js +6 -0
  21. package/dist/cli/kraken/verifyStatus.js.map +1 -1
  22. package/dist/cli/main.bundled.js +1117 -819
  23. package/dist/cli/main.bundled.js.map +4 -4
  24. package/dist/cli/main.js +82 -14
  25. package/dist/cli/main.js.map +1 -1
  26. package/dist/cli/runHeadless.js +18 -27
  27. package/dist/cli/runHeadless.js.map +1 -1
  28. package/dist/cli/safety/destructiveCommands.js +72 -0
  29. package/dist/cli/safety/destructiveCommands.js.map +1 -0
  30. package/dist/cli/safety/osJail.js +7 -1
  31. package/dist/cli/safety/osJail.js.map +1 -1
  32. package/dist/cli/slashCommands.js +6 -1
  33. package/dist/cli/slashCommands.js.map +1 -1
  34. package/dist/cli/toolRegistry.js +21 -1
  35. package/dist/cli/toolRegistry.js.map +1 -1
  36. package/dist/cli/utils/doctor.js +29 -4
  37. package/dist/cli/utils/doctor.js.map +1 -1
  38. package/package.json +2 -2
@@ -27111,6 +27111,19 @@ var init_contractCompiler = __esm({
27111
27111
  });
27112
27112
 
27113
27113
  // src/cli/kraken/verificationBridge.ts
27114
+ var verificationBridge_exports = {};
27115
+ __export(verificationBridge_exports, {
27116
+ STRICT_DONE_EXIT_CODE: () => STRICT_DONE_EXIT_CODE,
27117
+ anchorSelectionEvidence: () => anchorSelectionEvidence,
27118
+ evaluateStrictBuildGate: () => evaluateStrictBuildGate,
27119
+ evaluateStrictBuildGateFromSession: () => evaluateStrictBuildGateFromSession,
27120
+ krakenResultsToContract: () => krakenResultsToContract,
27121
+ matchNoteToToolTrace: () => matchNoteToToolTrace,
27122
+ strictDoneEnabled: () => strictDoneEnabled,
27123
+ strictEnvOverlay: () => strictEnvOverlay,
27124
+ strictGateEventPayload: () => strictGateEventPayload,
27125
+ strictGateExitCode: () => strictGateExitCode
27126
+ });
27114
27127
  import { createHash as createHash9 } from "node:crypto";
27115
27128
  function strictDoneEnabled(surface = "kraken", env = process.env) {
27116
27129
  if (surface === "mission") {
@@ -27392,6 +27405,20 @@ function strictGateEventPayload(evaluation) {
27392
27405
  summary: evaluation.summary
27393
27406
  };
27394
27407
  }
27408
+ function evaluateStrictBuildGateFromSession(mode, snapshot) {
27409
+ const gate = evaluateKrakenCompletionGate(mode);
27410
+ const evaluation = snapshot ? snapshotToCompletionEvaluation(snapshot) : null;
27411
+ const blocked = evaluation ? evaluation.verdict !== "PASS" : false;
27412
+ return {
27413
+ gate,
27414
+ strict: evaluation !== null,
27415
+ evaluation,
27416
+ native: null,
27417
+ // session replay carries the payload, not in-process results
27418
+ blocked,
27419
+ summary: evaluation ? blocked ? `blocked (strict ${evaluation.verdict} from session log seq=${snapshot?.seq}): ${evaluation.summary}` : `open (strict PASS from session log seq=${snapshot?.seq}): ${evaluation.summary}` : "open (no strict verification record in session log)"
27420
+ };
27421
+ }
27395
27422
  var STRICT_DONE_EXIT_CODE;
27396
27423
  var init_verificationBridge = __esm({
27397
27424
  "src/cli/kraken/verificationBridge.ts"() {
@@ -27405,6 +27432,461 @@ var init_verificationBridge = __esm({
27405
27432
  }
27406
27433
  });
27407
27434
 
27435
+ // src/cli/kraken/verifyStatus.ts
27436
+ var verifyStatus_exports = {};
27437
+ __export(verifyStatus_exports, {
27438
+ formatStrictBlockExplanation: () => formatStrictBlockExplanation,
27439
+ formatVerifyChip: () => formatVerifyChip,
27440
+ getVerifyChip: () => getVerifyChip,
27441
+ permissionsChip: () => permissionsChip,
27442
+ recordStrictGateEvaluation: () => recordStrictGateEvaluation,
27443
+ verifyStateFromGate: () => verifyStateFromGate
27444
+ });
27445
+ function verifyStateFromGate(evaluation) {
27446
+ if (!evaluation.blocked) return "pass";
27447
+ const verdict = evaluation.evaluation?.verdict;
27448
+ if (verdict === "BLOCKED") return "blocked";
27449
+ return "repair";
27450
+ }
27451
+ function formatVerifyChip(state3) {
27452
+ if (state3 === "pass") return { label: "prova: PASS", tone: "green" };
27453
+ if (state3 === "blocked") return { label: "prova: BLOCCATO", tone: "red" };
27454
+ return { label: "prova: RIPARA", tone: "yellow" };
27455
+ }
27456
+ function permissionsChip(phase2, strictOn = strictDoneEnabled()) {
27457
+ if (phase2 === "plan") return { label: "scrive: no (piano)", tone: "yellow" };
27458
+ return strictOn ? { label: "scrive: s\xEC \xB7 prova obbligatoria", tone: "green" } : { label: "scrive: s\xEC \xB7 senza prova (dichiarato)", tone: "yellow" };
27459
+ }
27460
+ function formatStrictBlockExplanation(evaluation) {
27461
+ const state3 = verifyStateFromGate(evaluation);
27462
+ const gate = evaluation.gate;
27463
+ if (state3 === "pass") {
27464
+ return `[verifica] PASS \u2014 ${gate.passed}/${gate.total} prove superate.`;
27465
+ }
27466
+ const word = state3 === "blocked" ? "BLOCCATO" : "RIPARA";
27467
+ const lines = [
27468
+ `[verifica] ${word} \u2014 ${gate.passed}/${gate.total} prove superate; il turno non pu\xF2 dichiararsi finito senza prova.`
27469
+ ];
27470
+ const evalr = evaluation.evaluation ?? null;
27471
+ if (evalr) {
27472
+ const byId = /* @__PURE__ */ new Map();
27473
+ for (const c of evalr.criteria ?? []) byId.set(c.id, c.text);
27474
+ const unsatisfied = (evalr.results ?? []).filter((r) => r.status !== "pass");
27475
+ if (unsatisfied.length > 0) {
27476
+ lines.push("Mancano:");
27477
+ for (const r of unsatisfied.slice(0, 8)) {
27478
+ lines.push(` \u2022 ${byId.get(r.criterionId) ?? r.criterionId} [${r.status}]`);
27479
+ }
27480
+ if (unsatisfied.length > 8) {
27481
+ lines.push(` \u2022 \u2026 e altri ${unsatisfied.length - 8}`);
27482
+ }
27483
+ const first = byId.get(unsatisfied[0].criterionId) ?? unsatisfied[0].criterionId;
27484
+ lines.push(
27485
+ state3 === "blocked" ? `Prossimo comando: /verify \u2014 poi porta evidenza per \xAB${first}\xBB e riprova.` : `Prossimo comando: ripara \xAB${first}\xBB, poi /verify per ricontrollare.`
27486
+ );
27487
+ }
27488
+ }
27489
+ lines.push(
27490
+ "La prova \xE8 obbligatoria: chiudo il turno solo quando ogni criterio richiesto ha evidenza."
27491
+ );
27492
+ return lines.join("\n");
27493
+ }
27494
+ function recordStrictGateEvaluation(evaluation) {
27495
+ const g = globalThis;
27496
+ g.__zelariVerifyState = {
27497
+ state: verifyStateFromGate(evaluation),
27498
+ at: Date.now(),
27499
+ passed: evaluation.gate.passed,
27500
+ total: evaluation.gate.total
27501
+ };
27502
+ }
27503
+ function getVerifyChip() {
27504
+ const g = globalThis;
27505
+ const s = g.__zelariVerifyState;
27506
+ if (!s) return null;
27507
+ return formatVerifyChip(s.state);
27508
+ }
27509
+ var init_verifyStatus = __esm({
27510
+ "src/cli/kraken/verifyStatus.ts"() {
27511
+ "use strict";
27512
+ init_verificationBridge();
27513
+ }
27514
+ });
27515
+
27516
+ // src/cli/safety/policyLoadMode.ts
27517
+ var policyLoadMode_exports = {};
27518
+ __export(policyLoadMode_exports, {
27519
+ POLICY_LOAD_BLOCK_REASON: () => POLICY_LOAD_BLOCK_REASON,
27520
+ POLICY_LOAD_EXIT_CODE: () => POLICY_LOAD_EXIT_CODE,
27521
+ POLICY_LOAD_MODE_ENV: () => POLICY_LOAD_MODE_ENV,
27522
+ activePolicyLoadMode: () => activePolicyLoadMode,
27523
+ activePolicyLoadSurface: () => activePolicyLoadSurface,
27524
+ resolvePolicyLoadMode: () => resolvePolicyLoadMode,
27525
+ setActivePolicyLoadSurface: () => setActivePolicyLoadSurface
27526
+ });
27527
+ function isTruthyFlag(v) {
27528
+ const n = v?.trim().toLowerCase();
27529
+ return n === "1" || n === "true" || n === "yes" || n === "on";
27530
+ }
27531
+ function resolvePolicyLoadMode(input) {
27532
+ const v = input.override?.trim().toLowerCase();
27533
+ if (v === "strict") return "strict";
27534
+ if (v === "permissive") return "permissive";
27535
+ if (input.surface === "headless" || input.surface === "mission") return "strict";
27536
+ if (isTruthyFlag(input.ci)) return "strict";
27537
+ return "permissive";
27538
+ }
27539
+ function setActivePolicyLoadSurface(surface) {
27540
+ activeSurface = surface;
27541
+ }
27542
+ function activePolicyLoadSurface() {
27543
+ return activeSurface;
27544
+ }
27545
+ function activePolicyLoadMode(env = process.env) {
27546
+ return resolvePolicyLoadMode({
27547
+ surface: activeSurface,
27548
+ override: env[POLICY_LOAD_MODE_ENV],
27549
+ ci: typeof env.CI === "string" ? env.CI : void 0
27550
+ });
27551
+ }
27552
+ var POLICY_LOAD_MODE_ENV, POLICY_LOAD_BLOCK_REASON, POLICY_LOAD_EXIT_CODE, activeSurface;
27553
+ var init_policyLoadMode = __esm({
27554
+ "src/cli/safety/policyLoadMode.ts"() {
27555
+ "use strict";
27556
+ POLICY_LOAD_MODE_ENV = "ZELARI_POLICY_LOAD_MODE";
27557
+ POLICY_LOAD_BLOCK_REASON = "policy-load-failed";
27558
+ POLICY_LOAD_EXIT_CODE = 2;
27559
+ activeSurface = "tui";
27560
+ }
27561
+ });
27562
+
27563
+ // src/cli/safety/jails/darwin.ts
27564
+ import { existsSync as existsSync7 } from "node:fs";
27565
+ function sbplQuote(p3) {
27566
+ return `"${p3.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
27567
+ }
27568
+ function buildSeatbeltProfile(spec) {
27569
+ const lines = [
27570
+ "(version 1)",
27571
+ `; zelari osJail (t28) \u2014 generated from JailSpec root=${spec.root}`,
27572
+ "(allow default)",
27573
+ "(deny file-write*)",
27574
+ "(allow file-write*"
27575
+ ];
27576
+ for (const dir of spec.writable) {
27577
+ lines.push(` (subpath ${sbplQuote(dir)})`);
27578
+ }
27579
+ lines.push(")");
27580
+ switch (spec.network.mode) {
27581
+ case "deny":
27582
+ lines.push("(deny network*)");
27583
+ break;
27584
+ case "allow":
27585
+ lines.push("(allow network*)");
27586
+ break;
27587
+ case "allow-list":
27588
+ lines.push(
27589
+ `; allow-list hosts (${spec.network.hosts.join(", ")}) NOT kernel-filtered \u2014 degraded to allow`,
27590
+ "(allow network*)"
27591
+ );
27592
+ break;
27593
+ }
27594
+ return lines.join("\n");
27595
+ }
27596
+ function darwinProbe(platform, exists) {
27597
+ if (platform !== "darwin") {
27598
+ return {
27599
+ backend: "seatbelt",
27600
+ available: false,
27601
+ reason: `platform ${platform} is not darwin`
27602
+ };
27603
+ }
27604
+ if (!exists(SANDBOX_EXEC_PATH)) {
27605
+ return {
27606
+ backend: "seatbelt",
27607
+ available: false,
27608
+ reason: `${SANDBOX_EXEC_PATH} not found`
27609
+ };
27610
+ }
27611
+ return {
27612
+ backend: "seatbelt",
27613
+ available: true,
27614
+ reason: `sandbox-exec available at ${SANDBOX_EXEC_PATH}`
27615
+ };
27616
+ }
27617
+ var SANDBOX_EXEC_PATH, darwinBackend;
27618
+ var init_darwin = __esm({
27619
+ "src/cli/safety/jails/darwin.ts"() {
27620
+ "use strict";
27621
+ SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
27622
+ darwinBackend = {
27623
+ id: "seatbelt",
27624
+ probe: () => darwinProbe(process.platform, (p3) => existsSync7(p3)),
27625
+ wrap: (spec, program, argv) => ({
27626
+ program: SANDBOX_EXEC_PATH,
27627
+ argv: ["-p", buildSeatbeltProfile(spec), program, ...argv]
27628
+ })
27629
+ };
27630
+ }
27631
+ });
27632
+
27633
+ // src/cli/safety/jails/linux.ts
27634
+ import { existsSync as existsSync8 } from "node:fs";
27635
+ function findBinaryOnPath(bin, pathValue, exists) {
27636
+ for (const dir of pathValue.split(/[:;]/)) {
27637
+ if (!dir) continue;
27638
+ const full = `${dir}/${bin}`;
27639
+ if (exists(full)) return full;
27640
+ }
27641
+ return null;
27642
+ }
27643
+ function linuxProbe(platform, pathValue, exists) {
27644
+ if (platform !== "linux") {
27645
+ return { backend: "bwrap", available: false, reason: `platform ${platform} is not linux` };
27646
+ }
27647
+ const found = findBinaryOnPath(BWRAP_BIN, pathValue, exists);
27648
+ if (!found) {
27649
+ return {
27650
+ backend: "bwrap",
27651
+ available: false,
27652
+ reason: `${BWRAP_BIN} not found on PATH (landlock needs native syscalls Node does not expose)`
27653
+ };
27654
+ }
27655
+ return { backend: "bwrap", available: true, reason: `${BWRAP_BIN} found at ${found}` };
27656
+ }
27657
+ function buildBwrapArgs(spec, program, argv) {
27658
+ const args = ["--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc"];
27659
+ for (const dir of spec.writable) {
27660
+ args.push("--bind-try", dir, dir);
27661
+ }
27662
+ if (spec.network.mode === "deny") {
27663
+ args.push("--unshare-net");
27664
+ }
27665
+ args.push("--", program, ...argv);
27666
+ return args;
27667
+ }
27668
+ var BWRAP_BIN, linuxBackend;
27669
+ var init_linux = __esm({
27670
+ "src/cli/safety/jails/linux.ts"() {
27671
+ "use strict";
27672
+ BWRAP_BIN = "bwrap";
27673
+ linuxBackend = {
27674
+ id: "bwrap",
27675
+ probe: () => linuxProbe(process.platform, process.env.PATH ?? "", (p3) => existsSync8(p3)),
27676
+ wrap: (spec, program, argv) => ({
27677
+ program: BWRAP_BIN,
27678
+ argv: buildBwrapArgs(spec, program, argv)
27679
+ })
27680
+ };
27681
+ }
27682
+ });
27683
+
27684
+ // src/cli/safety/jails/win32.ts
27685
+ function win32Probe(platform) {
27686
+ if (platform !== "win32") {
27687
+ return { backend: "win32-restricted-token", available: false, reason: `platform ${platform} is not win32` };
27688
+ }
27689
+ return { backend: "win32-restricted-token", available: false, reason: WIN32_UNAVAILABLE_REASON };
27690
+ }
27691
+ var WIN32_UNAVAILABLE_REASON, win32Backend;
27692
+ var init_win32 = __esm({
27693
+ "src/cli/safety/jails/win32.ts"() {
27694
+ "use strict";
27695
+ WIN32_UNAVAILABLE_REASON = "restricted-token + Job Object require native Windows APIs that pure Node does not expose (no native npm deps allowed, P5) \u2014 honest unavailable; see src/cli/safety/jails/win32.ts";
27696
+ win32Backend = {
27697
+ id: "win32-restricted-token",
27698
+ probe: () => win32Probe(process.platform),
27699
+ wrap: () => {
27700
+ throw new Error(`win32 jail backend unavailable: ${WIN32_UNAVAILABLE_REASON}`);
27701
+ }
27702
+ };
27703
+ }
27704
+ });
27705
+
27706
+ // src/cli/safety/osJail.ts
27707
+ import { spawn as spawn4 } from "node:child_process";
27708
+ import { homedir as homedir4, tmpdir } from "node:os";
27709
+ import path27 from "node:path";
27710
+ function resolveJailMode(override, env = process.env) {
27711
+ const v = override?.trim().toLowerCase();
27712
+ if (v === "off") return "off";
27713
+ if (v === "advisory") return "advisory";
27714
+ if (v === "required") return "required";
27715
+ const strictIntent = activePolicyLoadMode(env) === "strict" || (env.ZELARI_PERMISSION_PRESET ?? "").trim().toLowerCase() === "strict";
27716
+ if (!strictIntent) return "advisory";
27717
+ return probeJailBackend().available ? "required" : "advisory";
27718
+ }
27719
+ function activeJailMode(env = process.env) {
27720
+ return resolveJailMode(env[OS_JAIL_ENV], env);
27721
+ }
27722
+ function platformBackend(platform = process.platform) {
27723
+ switch (platform) {
27724
+ case "darwin":
27725
+ return darwinBackend;
27726
+ case "linux":
27727
+ return linuxBackend;
27728
+ default:
27729
+ return win32Backend;
27730
+ }
27731
+ }
27732
+ function currentBackend(platform = process.platform) {
27733
+ return testBackend ?? platformBackend(platform);
27734
+ }
27735
+ function probeJailBackend(platform = process.platform) {
27736
+ if (testBackend) return testBackend.probe();
27737
+ if (!probeCache || probeCache.platform !== platform) {
27738
+ probeCache = { platform, result: platformBackend(platform).probe() };
27739
+ }
27740
+ return probeCache.result;
27741
+ }
27742
+ function defaultEnvAllowlist(platform = process.platform) {
27743
+ return platform === "win32" ? [...BASE_ENV_ALLOWLIST, ...WIN32_ENV_ALLOWLIST] : BASE_ENV_ALLOWLIST;
27744
+ }
27745
+ function defaultWritable(root, home = homedir4(), tmp = tmpdir(), platform = process.platform) {
27746
+ const raw = [path27.resolve(root), path27.resolve(tmp), path27.join(home, ".zelari-code")];
27747
+ const out = [];
27748
+ for (const p3 of raw) {
27749
+ const key = platform === "win32" ? p3.toLowerCase() : p3;
27750
+ if (!out.some((q) => (platform === "win32" ? q.toLowerCase() : q) === key)) out.push(p3);
27751
+ }
27752
+ return out;
27753
+ }
27754
+ function buildJailSpec(opts) {
27755
+ return {
27756
+ root: path27.resolve(opts.root),
27757
+ network: opts.network ?? { mode: "deny" },
27758
+ envAllowlist: opts.envAllowlist ?? defaultEnvAllowlist(),
27759
+ writable: opts.writable ?? defaultWritable(opts.root)
27760
+ };
27761
+ }
27762
+ function networkSpecFromClaimHosts(allowedHosts) {
27763
+ if (!allowedHosts || allowedHosts.length === 0) return { mode: "deny" };
27764
+ return { mode: "allow-list", hosts: [...new Set(allowedHosts)] };
27765
+ }
27766
+ function sanitizeEnv(env, allowlist, platform = process.platform) {
27767
+ const wanted = new Set(allowlist.map((k) => platform === "win32" ? k.toLowerCase() : k));
27768
+ const out = {};
27769
+ for (const [k, v] of Object.entries(env)) {
27770
+ if (v === void 0) continue;
27771
+ const key = platform === "win32" ? k.toLowerCase() : k;
27772
+ if (wanted.has(key)) out[k] = v;
27773
+ }
27774
+ return out;
27775
+ }
27776
+ function jailDenyReason(probe, mode) {
27777
+ return `OS jail backend unavailable (${probe.backend}: ${probe.reason}) with ZELARI_OS_JAIL=${mode} \u2014 execution is DENIED instead of running unjailed (t28 golden rule: missing backend + required \u21D2 deny, never warn/skip). Set ZELARI_OS_JAIL=advisory (visible fail-open) or =off to explicitly allow unjailed execution.`;
27778
+ }
27779
+ function jailAdvisoryNotice(probe) {
27780
+ return `advisory: OS jail backend unavailable (${probe.backend}: ${probe.reason}) \u2014 this process will run UNJAILED (visible fail-open). Set ZELARI_OS_JAIL=required to deny instead.`;
27781
+ }
27782
+ function decideJailSpawn(spec, input) {
27783
+ const mode = input.mode ?? activeJailMode();
27784
+ const merged = { ...input.env ?? process.env, ...input.envExtras ?? {} };
27785
+ if (mode === "off") return { action: "spawn-plain", env: merged };
27786
+ const probe = probeJailBackend();
27787
+ if (!probe.available) {
27788
+ if (mode === "required") return { action: "deny", reason: jailDenyReason(probe, mode) };
27789
+ return { action: "spawn-plain", env: sanitizeEnv(merged, spec.envAllowlist), notice: jailAdvisoryNotice(probe) };
27790
+ }
27791
+ const wrapped = currentBackend().wrap(spec, input.program, input.argv);
27792
+ return {
27793
+ action: "spawn-jailed",
27794
+ probe,
27795
+ program: wrapped.program,
27796
+ argv: wrapped.argv,
27797
+ env: sanitizeEnv(merged, spec.envAllowlist)
27798
+ };
27799
+ }
27800
+ function spawnJailed(spec, req) {
27801
+ const decision = decideJailSpawn(spec, {
27802
+ program: req.program,
27803
+ argv: req.argv,
27804
+ env: req.env,
27805
+ envExtras: req.envExtras
27806
+ });
27807
+ if (decision.action === "deny") {
27808
+ return { outcome: "denied", reason: `[jail] ${decision.reason}` };
27809
+ }
27810
+ const notice = decision.action === "spawn-plain" ? decision.notice : void 0;
27811
+ if (notice) {
27812
+ console.error(`[os-jail] ${notice}`);
27813
+ req.onNotice?.(notice);
27814
+ }
27815
+ const jailed = decision.action === "spawn-jailed";
27816
+ const program = jailed ? decision.program : req.program;
27817
+ const argv = jailed ? decision.argv : [...req.argv];
27818
+ try {
27819
+ const child = spawn4(program, argv, {
27820
+ cwd: req.cwd,
27821
+ signal: req.signal,
27822
+ shell: false,
27823
+ env: decision.env,
27824
+ stdio: ["ignore", "pipe", "pipe"]
27825
+ // non-interactive by construction
27826
+ });
27827
+ return {
27828
+ outcome: "spawned",
27829
+ child,
27830
+ backend: jailed ? decision.probe.backend : "none",
27831
+ jailed,
27832
+ ...notice ? { notice } : {}
27833
+ };
27834
+ } catch (err) {
27835
+ return { outcome: "failed", reason: err instanceof Error ? err.message : String(err) };
27836
+ }
27837
+ }
27838
+ function jailAvailability(env = process.env) {
27839
+ const mode = activeJailMode(env);
27840
+ const probe = mode === "off" ? { backend: "none", available: false, reason: "jail disabled (ZELARI_OS_JAIL=off)" } : probeJailBackend();
27841
+ return { mode, probe };
27842
+ }
27843
+ var OS_JAIL_ENV, testBackend, probeCache, BASE_ENV_ALLOWLIST, WIN32_ENV_ALLOWLIST;
27844
+ var init_osJail = __esm({
27845
+ "src/cli/safety/osJail.ts"() {
27846
+ "use strict";
27847
+ init_policyLoadMode();
27848
+ init_darwin();
27849
+ init_linux();
27850
+ init_win32();
27851
+ OS_JAIL_ENV = "ZELARI_OS_JAIL";
27852
+ testBackend = null;
27853
+ probeCache = null;
27854
+ BASE_ENV_ALLOWLIST = [
27855
+ "PATH",
27856
+ "HOME",
27857
+ "USER",
27858
+ "LANG",
27859
+ "CI",
27860
+ "TERM",
27861
+ "NO_COLOR"
27862
+ ];
27863
+ WIN32_ENV_ALLOWLIST = [
27864
+ "PATH",
27865
+ "Path",
27866
+ "HOMEDRIVE",
27867
+ "HOMEPATH",
27868
+ "SystemRoot",
27869
+ "SystemDrive",
27870
+ "ComSpec",
27871
+ "PATHEXT",
27872
+ "TEMP",
27873
+ "TMP",
27874
+ "APPDATA",
27875
+ "LOCALAPPDATA",
27876
+ "USERPROFILE",
27877
+ "USERNAME",
27878
+ "MSYSTEM",
27879
+ "PROGRAMFILES",
27880
+ "ProgramFiles",
27881
+ "ProgramData",
27882
+ "OS",
27883
+ "NUMBER_OF_PROCESSORS",
27884
+ "PROCESSOR_ARCHITECTURE",
27885
+ "WINDIR"
27886
+ ];
27887
+ }
27888
+ });
27889
+
27408
27890
  // packages/core/dist/shared/events.js
27409
27891
  function isBrainAgentStartEvent(e) {
27410
27892
  return e.type === "agent_start";
@@ -29461,7 +29943,7 @@ var init_providerStream = __esm({
29461
29943
 
29462
29944
  // packages/core/dist/core/sessionJsonl.js
29463
29945
  import { promises as fs13 } from "node:fs";
29464
- import path27 from "node:path";
29946
+ import path28 from "node:path";
29465
29947
  import os from "node:os";
29466
29948
  async function readSession(filePath) {
29467
29949
  try {
@@ -29487,7 +29969,7 @@ async function readSession(filePath) {
29487
29969
  }
29488
29970
  }
29489
29971
  function defaultBaseDir() {
29490
- return path27.join(os.tmpdir(), "zelari-code", "sessions");
29972
+ return path28.join(os.tmpdir(), "zelari-code", "sessions");
29491
29973
  }
29492
29974
  var MAX_PENDING_EVENTS, MAX_PENDING_BYTES, DEFAULT_FLUSH_INTERVAL_MS, SessionJsonlWriter;
29493
29975
  var init_sessionJsonl = __esm({
@@ -29513,7 +29995,7 @@ var init_sessionJsonl = __esm({
29513
29995
  idleWaiters = [];
29514
29996
  constructor(sessionId2, options = {}) {
29515
29997
  const baseDir = options.baseDir ?? defaultBaseDir();
29516
- this.filePath = path27.join(baseDir, `${sessionId2}.jsonl`);
29998
+ this.filePath = path28.join(baseDir, `${sessionId2}.jsonl`);
29517
29999
  this.onError = options.onError ?? console.error;
29518
30000
  this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
29519
30001
  }
@@ -29524,7 +30006,7 @@ var init_sessionJsonl = __esm({
29524
30006
  /** Ensure the parent directory exists — once per writer lifetime. */
29525
30007
  ensureDir() {
29526
30008
  if (!this.dirEnsured) {
29527
- this.dirEnsured = fs13.mkdir(path27.dirname(this.filePath), { recursive: true }).then(() => void 0).catch((err) => {
30009
+ this.dirEnsured = fs13.mkdir(path28.dirname(this.filePath), { recursive: true }).then(() => void 0).catch((err) => {
29528
30010
  this.dirEnsured = null;
29529
30011
  throw err;
29530
30012
  });
@@ -29686,9 +30168,9 @@ var init_types6 = __esm({
29686
30168
  });
29687
30169
 
29688
30170
  // packages/core/dist/core/hooks/lifecycleHookRunner.js
29689
- import { spawn as spawn4 } from "node:child_process";
30171
+ import { spawn as spawn5 } from "node:child_process";
29690
30172
  import { readdirSync as readdirSync2, readFileSync as readFileSync7 } from "node:fs";
29691
- import path28 from "node:path";
30173
+ import path29 from "node:path";
29692
30174
  function parseJson(text) {
29693
30175
  try {
29694
30176
  return JSON.parse(text);
@@ -29768,14 +30250,14 @@ var init_lifecycleHookRunner = __esm({
29768
30250
  }
29769
30251
  let loaded = 0;
29770
30252
  for (const f of files) {
29771
- const full = path28.join(dir, f);
30253
+ const full = path29.join(dir, f);
29772
30254
  try {
29773
30255
  const parsed = parseJson(readFileSync7(full, "utf8"));
29774
30256
  if (!parsed || !parsed.name || !parsed.match) {
29775
30257
  this.logger(`skipping invalid hook file ${full}`);
29776
30258
  continue;
29777
30259
  }
29778
- parsed.name = parsed.name || path28.basename(f, ".json");
30260
+ parsed.name = parsed.name || path29.basename(f, ".json");
29779
30261
  this.hooks.push(parsed);
29780
30262
  loaded += 1;
29781
30263
  } catch (err) {
@@ -29897,7 +30379,7 @@ var init_lifecycleHookRunner = __esm({
29897
30379
  reject(new Error("empty hook command"));
29898
30380
  return;
29899
30381
  }
29900
- const child = spawn4(argv[0], argv.slice(1), {
30382
+ const child = spawn5(argv[0], argv.slice(1), {
29901
30383
  shell: false,
29902
30384
  cwd,
29903
30385
  stdio: ["pipe", "pipe", "pipe"],
@@ -30726,7 +31208,7 @@ var init_parseCssMotion = __esm({
30726
31208
  });
30727
31209
 
30728
31210
  // packages/core/dist/council/verification/citeVerify.js
30729
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
31211
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
30730
31212
  import { join as join4 } from "node:path";
30731
31213
  function extractCitations(text) {
30732
31214
  const out = [];
@@ -30750,7 +31232,7 @@ function verifyCitations(projectRoot, synthesisText) {
30750
31232
  const results = [];
30751
31233
  for (const cite of extractCitations(synthesisText)) {
30752
31234
  const abs = join4(projectRoot, cite.file);
30753
- if (!existsSync7(abs)) {
31235
+ if (!existsSync9(abs)) {
30754
31236
  results.push({
30755
31237
  id: "synthesis.cite-invalid",
30756
31238
  severity: "error",
@@ -31069,11 +31551,11 @@ var init_synthesisAudit = __esm({
31069
31551
  });
31070
31552
 
31071
31553
  // packages/core/dist/council/verification/runChecks.js
31072
- import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
31554
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
31073
31555
  import { join as join5 } from "node:path";
31074
31556
  function loadNfrSpec(zelariRoot) {
31075
31557
  const path99 = join5(zelariRoot, "nfr-spec.json");
31076
- if (!existsSync8(path99))
31558
+ if (!existsSync10(path99))
31077
31559
  return null;
31078
31560
  try {
31079
31561
  const raw = JSON.parse(readFileSync9(path99, "utf8"));
@@ -31087,11 +31569,11 @@ function loadNfrSpec(zelariRoot) {
31087
31569
  function resolveTargets(projectRoot, spec) {
31088
31570
  const found = [];
31089
31571
  for (const rel2 of spec.targets) {
31090
- if (existsSync8(join5(projectRoot, rel2))) {
31572
+ if (existsSync10(join5(projectRoot, rel2))) {
31091
31573
  found.push(rel2);
31092
31574
  }
31093
31575
  }
31094
- if (found.length === 0 && existsSync8(join5(projectRoot, "index.html"))) {
31576
+ if (found.length === 0 && existsSync10(join5(projectRoot, "index.html"))) {
31095
31577
  return ["index.html"];
31096
31578
  }
31097
31579
  return found;
@@ -31147,7 +31629,7 @@ function checkDeadCssHooks(html, relFile) {
31147
31629
  }
31148
31630
  function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
31149
31631
  const planPath = join5(zelariRoot, "plan.json");
31150
- if (!existsSync8(planPath) || keywords.length === 0)
31632
+ if (!existsSync10(planPath) || keywords.length === 0)
31151
31633
  return [];
31152
31634
  let plan;
31153
31635
  try {
@@ -31187,11 +31669,11 @@ function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
31187
31669
  }
31188
31670
  function checkReadmeStale(projectRoot, targets) {
31189
31671
  const readmePath = join5(projectRoot, "README.md");
31190
- if (!existsSync8(readmePath) || targets.length === 0)
31672
+ if (!existsSync10(readmePath) || targets.length === 0)
31191
31673
  return [];
31192
31674
  const readme = readFileSync9(readmePath, "utf8");
31193
31675
  const htmlPath = join5(projectRoot, targets[0]);
31194
- if (!existsSync8(htmlPath))
31676
+ if (!existsSync10(htmlPath))
31195
31677
  return [];
31196
31678
  const html = readFileSync9(htmlPath, "utf8");
31197
31679
  const sectionCount = (html.match(/<section\s+id=/gi) ?? []).length;
@@ -31317,7 +31799,7 @@ var init_runChecks = __esm({
31317
31799
  });
31318
31800
 
31319
31801
  // packages/core/dist/council/verification/microGate.js
31320
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
31802
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
31321
31803
  import { join as join6 } from "node:path";
31322
31804
  function checkDeadHooksInHtml(html) {
31323
31805
  const warnings = [];
@@ -31347,7 +31829,7 @@ function checkDeadHooksInHtml(html) {
31347
31829
  }
31348
31830
  function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
31349
31831
  const abs = join6(projectRoot, relPath);
31350
- if (!existsSync9(abs) || !/\.html?$/i.test(relPath))
31832
+ if (!existsSync11(abs) || !/\.html?$/i.test(relPath))
31351
31833
  return [];
31352
31834
  const spec = (zelariRoot ? loadNfrSpec(zelariRoot) : null) ?? DEFAULT_NFR_SPEC;
31353
31835
  const anim = spec.animation ?? { compositorOnly: true, forbidLayoutProps: true };
@@ -31791,7 +32273,7 @@ var init_inlineJsAutofix = __esm({
31791
32273
  });
31792
32274
 
31793
32275
  // packages/core/dist/agents/councilApi.js
31794
- import { existsSync as existsSync10 } from "node:fs";
32276
+ import { existsSync as existsSync12 } from "node:fs";
31795
32277
  import { join as join8 } from "node:path";
31796
32278
  function extractBalancedJsonObject(s) {
31797
32279
  const start = s.indexOf("{");
@@ -32419,7 +32901,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32419
32901
  const zelariRoot = `${chairmanProjectRoot}/.zelari`;
32420
32902
  const spec = loadNfrSpec(zelariRoot) ?? DEFAULT_NFR_SPEC;
32421
32903
  for (const rel2 of spec.targets) {
32422
- if (!existsSync10(join8(chairmanProjectRoot, rel2)))
32904
+ if (!existsSync12(join8(chairmanProjectRoot, rel2)))
32423
32905
  continue;
32424
32906
  changedTargetFiles.add(rel2);
32425
32907
  for (const w of runChairmanMicroGate({ projectRoot: chairmanProjectRoot, relPath: rel2, zelariRoot })) {
@@ -32439,7 +32921,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32439
32921
  const zelariRootReplay = `${chairmanProjectRoot}/.zelari`;
32440
32922
  const specReplay = loadNfrSpec(zelariRootReplay) ?? DEFAULT_NFR_SPEC;
32441
32923
  for (const rel2 of specReplay.targets) {
32442
- if (!existsSync10(join8(chairmanProjectRoot, rel2)))
32924
+ if (!existsSync12(join8(chairmanProjectRoot, rel2)))
32443
32925
  continue;
32444
32926
  changedTargetFiles.add(rel2);
32445
32927
  for (const w of runChairmanMicroGate({
@@ -37549,7 +38031,7 @@ var CORE_VERSION;
37549
38031
  var init_version = __esm({
37550
38032
  "packages/core/dist/version.js"() {
37551
38033
  "use strict";
37552
- CORE_VERSION = "2.30.0";
38034
+ CORE_VERSION = "2.32.0";
37553
38035
  }
37554
38036
  });
37555
38037
 
@@ -38252,8 +38734,8 @@ var init_graphStatus = __esm({
38252
38734
  });
38253
38735
 
38254
38736
  // src/cli/sessionManager.ts
38255
- import { promises as fs14, existsSync as existsSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11, mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, statSync } from "node:fs";
38256
- import path29 from "node:path";
38737
+ import { promises as fs14, existsSync as existsSync13, readFileSync as readFileSync15, writeFileSync as writeFileSync11, mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, statSync } from "node:fs";
38738
+ import path30 from "node:path";
38257
38739
  import { randomUUID as randomUUID2 } from "node:crypto";
38258
38740
  function getSessionBaseDir() {
38259
38741
  return sessionsDir();
@@ -38266,7 +38748,7 @@ async function ensureSessionDir() {
38266
38748
  }
38267
38749
  function getCurrentSessionId() {
38268
38750
  const file2 = getCurrentSessionFile();
38269
- if (!existsSync11(file2)) return null;
38751
+ if (!existsSync13(file2)) return null;
38270
38752
  try {
38271
38753
  const content = readFileSync15(file2, "utf-8").trim();
38272
38754
  return content.length > 0 ? content : null;
@@ -38276,7 +38758,7 @@ function getCurrentSessionId() {
38276
38758
  }
38277
38759
  function setCurrentSessionId(id3) {
38278
38760
  const file2 = getCurrentSessionFile();
38279
- mkdirSync6(path29.dirname(file2), { recursive: true });
38761
+ mkdirSync6(path30.dirname(file2), { recursive: true });
38280
38762
  writeFileSync11(file2, id3, "utf-8");
38281
38763
  }
38282
38764
  function clearCurrentSessionId() {
@@ -38288,11 +38770,11 @@ function clearCurrentSessionId() {
38288
38770
  }
38289
38771
  }
38290
38772
  function getCurrentBranchFile() {
38291
- return process.env.ANATHEMA_CURRENT_BRANCH_FILE ?? path29.join(path29.dirname(getCurrentSessionFile()), "currentBranch.txt");
38773
+ return process.env.ANATHEMA_CURRENT_BRANCH_FILE ?? path30.join(path30.dirname(getCurrentSessionFile()), "currentBranch.txt");
38292
38774
  }
38293
38775
  function getCurrentBranch() {
38294
38776
  const file2 = getCurrentBranchFile();
38295
- if (!existsSync11(file2)) return null;
38777
+ if (!existsSync13(file2)) return null;
38296
38778
  try {
38297
38779
  const content = readFileSync15(file2, "utf-8").trim();
38298
38780
  return content.length > 0 ? content : null;
@@ -38302,7 +38784,7 @@ function getCurrentBranch() {
38302
38784
  }
38303
38785
  function setCurrentBranch(name) {
38304
38786
  const file2 = getCurrentBranchFile();
38305
- mkdirSync6(path29.dirname(file2), { recursive: true });
38787
+ mkdirSync6(path30.dirname(file2), { recursive: true });
38306
38788
  writeFileSync11(file2, name, "utf-8");
38307
38789
  }
38308
38790
  function newSessionId() {
@@ -38321,7 +38803,7 @@ async function listSessions() {
38321
38803
  for (const entry of entries) {
38322
38804
  if (!entry.endsWith(".jsonl")) continue;
38323
38805
  const id3 = entry.replace(/\.jsonl$/, "");
38324
- const filePath = path29.join(baseDir, entry);
38806
+ const filePath = path30.join(baseDir, entry);
38325
38807
  try {
38326
38808
  const events = await readSession(filePath);
38327
38809
  let firstTs = 0;
@@ -38368,7 +38850,7 @@ ${lines.join("\n")}`
38368
38850
  return { message: `[${kind2}] handled` };
38369
38851
  }
38370
38852
  async function loadSessionEvents(id3) {
38371
- const filePath = path29.join(getSessionBaseDir(), `${id3}.jsonl`);
38853
+ const filePath = path30.join(getSessionBaseDir(), `${id3}.jsonl`);
38372
38854
  return readSession(filePath);
38373
38855
  }
38374
38856
  var init_sessionManager = __esm({
@@ -38701,16 +39183,16 @@ var init_budgetRuntime = __esm({
38701
39183
  });
38702
39184
 
38703
39185
  // src/cli/budget/restoreRuntime.ts
38704
- import path30 from "node:path";
39186
+ import path31 from "node:path";
38705
39187
  async function restoreBudgetRuntimeFromSession(budget, sessionId2, baseDir) {
38706
- const eventsPath = path30.join(resolveSessionsDir({ baseDir }), sessionId2, "events.jsonl");
39188
+ const eventsPath = path31.join(resolveSessionsDir({ baseDir }), sessionId2, "events.jsonl");
38707
39189
  const report = await readSessionLog(eventsPath).catch(() => null);
38708
39190
  if (!report || report.events.length === 0) return false;
38709
39191
  budget.adoptLedgerFromEvents(report.events);
38710
39192
  return true;
38711
39193
  }
38712
39194
  async function lastHarnessManifestHash(sessionId2, baseDir) {
38713
- const eventsPath = path30.join(resolveSessionsDir({ baseDir }), sessionId2, "events.jsonl");
39195
+ const eventsPath = path31.join(resolveSessionsDir({ baseDir }), sessionId2, "events.jsonl");
38714
39196
  const report = await readSessionLog(eventsPath).catch(() => null);
38715
39197
  if (!report) return null;
38716
39198
  for (let i = report.events.length - 1; i >= 0; i--) {
@@ -38759,21 +39241,21 @@ __export(updater_exports, {
38759
39241
  resolveBundledNpmCli: () => resolveBundledNpmCli
38760
39242
  });
38761
39243
  import { createRequire } from "node:module";
38762
- import { spawn as spawn5 } from "node:child_process";
38763
- import { existsSync as existsSync12 } from "node:fs";
38764
- import path31 from "node:path";
39244
+ import { spawn as spawn6 } from "node:child_process";
39245
+ import { existsSync as existsSync14 } from "node:fs";
39246
+ import path32 from "node:path";
38765
39247
  import { fileURLToPath } from "node:url";
38766
39248
  function resolveBundledNpmCli(execPath = process.execPath) {
38767
- const dir = path31.dirname(execPath);
39249
+ const dir = path32.dirname(execPath);
38768
39250
  const candidates = [
38769
39251
  // Windows: C:\...\node.exe → C:\...\node_modules\npm\bin\npm-cli.js
38770
- path31.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
39252
+ path32.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
38771
39253
  // POSIX: <prefix>/bin/node → <prefix>/lib/node_modules/npm/bin/npm-cli.js
38772
- path31.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
39254
+ path32.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
38773
39255
  ];
38774
39256
  for (const candidate of candidates) {
38775
39257
  try {
38776
- if (existsSync12(candidate)) return candidate;
39258
+ if (existsSync14(candidate)) return candidate;
38777
39259
  } catch {
38778
39260
  }
38779
39261
  }
@@ -38786,7 +39268,7 @@ function looksLikeBrokenShim(exitCode, output) {
38786
39268
  }
38787
39269
  function getCurrentVersion() {
38788
39270
  try {
38789
- const pkgPath = path31.resolve(__dirname2, "..", "..", "package.json");
39271
+ const pkgPath = path32.resolve(__dirname2, "..", "..", "package.json");
38790
39272
  const pkg = require2(pkgPath);
38791
39273
  return pkg.version;
38792
39274
  } catch {
@@ -38856,7 +39338,7 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
38856
39338
  updateAvailable: cmp < 0
38857
39339
  };
38858
39340
  }
38859
- async function performUpdate(packageName = "zelari-code", executor = spawn5, resolveNpmCli = resolveBundledNpmCli, channel) {
39341
+ async function performUpdate(packageName = "zelari-code", executor = spawn6, resolveNpmCli = resolveBundledNpmCli, channel) {
38860
39342
  const tag = channel ?? distTagForVersion(getCurrentVersion());
38861
39343
  const args = ["install", "-g", `${packageName}@${tag}`];
38862
39344
  const primary = await runNpm(executor, args, "shim");
@@ -38909,7 +39391,7 @@ var init_updater = __esm({
38909
39391
  "use strict";
38910
39392
  init_cmdline();
38911
39393
  require2 = createRequire(import.meta.url);
38912
- __dirname2 = path31.dirname(fileURLToPath(import.meta.url));
39394
+ __dirname2 = path32.dirname(fileURLToPath(import.meta.url));
38913
39395
  REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
38914
39396
  }
38915
39397
  });
@@ -39068,7 +39550,7 @@ var init_spineFileEvents = __esm({
39068
39550
  });
39069
39551
 
39070
39552
  // src/cli/sessionSpine.ts
39071
- import path32 from "node:path";
39553
+ import path33 from "node:path";
39072
39554
  function spineEnabled() {
39073
39555
  return process.env.ZELARI_SESSION_SPINE !== "0";
39074
39556
  }
@@ -39267,8 +39749,8 @@ var init_sessionSpine = __esm({
39267
39749
  const mirror = new _SessionSpineMirror(sessionId2, options);
39268
39750
  if (!spineEnabled()) return mirror;
39269
39751
  try {
39270
- const sessionDir = path32.join(mirror.sessionsDir, sessionId2);
39271
- const report = await readSessionLog(path32.join(sessionDir, "events.jsonl"));
39752
+ const sessionDir = path33.join(mirror.sessionsDir, sessionId2);
39753
+ const report = await readSessionLog(path33.join(sessionDir, "events.jsonl"));
39272
39754
  const existed = report.events.length > 0 || report.issues.length > 0;
39273
39755
  if (report.events.some((e) => e.kind === "task.contract" || e.kind === "user.message")) {
39274
39756
  mirror.contractSeeded = true;
@@ -39444,7 +39926,7 @@ var init_sessionSpine = __esm({
39444
39926
  async derivedPriorTurns() {
39445
39927
  if (this.status !== "active" && this.status !== "closed") return null;
39446
39928
  const report = await readSessionLog(
39447
- path32.join(this.sessionsDir, this.sessionId, "events.jsonl")
39929
+ path33.join(this.sessionsDir, this.sessionId, "events.jsonl")
39448
39930
  ).catch(() => null);
39449
39931
  if (!report || report.events.length === 0) return null;
39450
39932
  return deriveMessages(report.events);
@@ -39454,7 +39936,7 @@ var init_sessionSpine = __esm({
39454
39936
  if (this.status !== "active" && this.status !== "closed") return null;
39455
39937
  await this.flush();
39456
39938
  const report = await readSessionLog(
39457
- path32.join(this.sessionsDir, this.sessionId, "events.jsonl")
39939
+ path33.join(this.sessionsDir, this.sessionId, "events.jsonl")
39458
39940
  ).catch(() => null);
39459
39941
  if (!report || report.events.length === 0) return null;
39460
39942
  return buildCompactionStateSnapshot(report.events, toSeq);
@@ -39467,7 +39949,7 @@ var init_sessionSpine = __esm({
39467
39949
  async lastVerificationRun() {
39468
39950
  if (this.status !== "active" && this.status !== "closed") return null;
39469
39951
  const report = await readSessionLog(
39470
- path32.join(this.sessionsDir, this.sessionId, "events.jsonl")
39952
+ path33.join(this.sessionsDir, this.sessionId, "events.jsonl")
39471
39953
  ).catch(() => null);
39472
39954
  if (!report) return null;
39473
39955
  return lastVerificationRun(report.events);
@@ -39602,7 +40084,7 @@ var init_sessionSpine = __esm({
39602
40084
  if (steerText) {
39603
40085
  seq = seq.then(async (s) => {
39604
40086
  try {
39605
- const eventsPath = path32.join(this.sessionsDir, this.sessionId, "events.jsonl");
40087
+ const eventsPath = path33.join(this.sessionsDir, this.sessionId, "events.jsonl");
39606
40088
  const report = await readSessionLog(eventsPath).catch(() => null);
39607
40089
  if (!report || typeof s !== "number") return s;
39608
40090
  const current = latestTaskContract(report.events);
@@ -39849,10 +40331,10 @@ var init_sessionSurface = __esm({
39849
40331
  });
39850
40332
 
39851
40333
  // src/cli/hooks/observationStore.ts
39852
- import { existsSync as existsSync13, statSync as statSync2 } from "node:fs";
39853
- import path33 from "node:path";
40334
+ import { existsSync as existsSync15, statSync as statSync2 } from "node:fs";
40335
+ import path34 from "node:path";
39854
40336
  function sessionFilePath(sessionId2, baseDir) {
39855
- return path33.join(baseDir ?? getSessionBaseDir(), `${sessionId2}.jsonl`);
40337
+ return path34.join(baseDir ?? getSessionBaseDir(), `${sessionId2}.jsonl`);
39856
40338
  }
39857
40339
  function isToolEnd(e) {
39858
40340
  return e.type === "tool_execution_end";
@@ -39862,7 +40344,7 @@ function isToolStart(e) {
39862
40344
  }
39863
40345
  async function loadObservationIndex(sessionId2, baseDir) {
39864
40346
  const filePath = sessionFilePath(sessionId2, baseDir);
39865
- const exists = existsSync13(filePath);
40347
+ const exists = existsSync15(filePath);
39866
40348
  let mtimeMs = 0;
39867
40349
  if (exists) {
39868
40350
  try {
@@ -39874,7 +40356,7 @@ async function loadObservationIndex(sessionId2, baseDir) {
39874
40356
  const hit = cache.get(sessionId2);
39875
40357
  if (hit && !exists) return hit;
39876
40358
  if (hit && hit.filePath === filePath && hit.mtimeMs === mtimeMs) return hit;
39877
- const events = existsSync13(filePath) ? await readSession(filePath) : [];
40359
+ const events = existsSync15(filePath) ? await readSession(filePath) : [];
39878
40360
  const names = /* @__PURE__ */ new Map();
39879
40361
  const bySeq = /* @__PURE__ */ new Map();
39880
40362
  const byToolCallId = /* @__PURE__ */ new Map();
@@ -39972,17 +40454,17 @@ var init_observationStore = __esm({
39972
40454
 
39973
40455
  // packages/core/dist/core/tools/toolOutputSpill.js
39974
40456
  import { createHash as createHash11, randomBytes as randomBytes3 } from "node:crypto";
39975
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync12 } from "node:fs";
39976
- import { homedir as homedir4, tmpdir } from "node:os";
40457
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, writeFileSync as writeFileSync12 } from "node:fs";
40458
+ import { homedir as homedir5, tmpdir as tmpdir2 } from "node:os";
39977
40459
  import { join as join14 } from "node:path";
39978
40460
  function resolveToolOutputDir() {
39979
40461
  const fromEnv = process.env.ZELARI_TOOL_OUTPUT_DIR?.trim();
39980
40462
  if (fromEnv)
39981
40463
  return fromEnv;
39982
40464
  try {
39983
- return join14(homedir4(), ".tmp", "zelari-code", "tool-output");
40465
+ return join14(homedir5(), ".tmp", "zelari-code", "tool-output");
39984
40466
  } catch {
39985
- return join14(tmpdir(), "zelari-code", "tool-output");
40467
+ return join14(tmpdir2(), "zelari-code", "tool-output");
39986
40468
  }
39987
40469
  }
39988
40470
  function isToolSpillEnabled() {
@@ -39998,7 +40480,7 @@ function spillToolOutput(fullText, meta3) {
39998
40480
  return null;
39999
40481
  try {
40000
40482
  const dir = resolveToolOutputDir();
40001
- if (!existsSync14(dir)) {
40483
+ if (!existsSync16(dir)) {
40002
40484
  mkdirSync7(dir, { recursive: true });
40003
40485
  }
40004
40486
  const hash3 = createHash11("sha256").update(fullText).digest("hex").slice(0, 12);
@@ -40303,7 +40785,7 @@ var init_registry2 = __esm({
40303
40785
  });
40304
40786
 
40305
40787
  // src/cli/safety/sandboxPath.ts
40306
- import path34 from "node:path";
40788
+ import path35 from "node:path";
40307
40789
  import fs15 from "node:fs";
40308
40790
  function normalizedCase(p3) {
40309
40791
  return IS_CASE_FOLDING ? p3.toLowerCase() : p3;
@@ -40312,7 +40794,7 @@ function isWithin(candidate, base2) {
40312
40794
  const c = normalizedCase(candidate);
40313
40795
  const b = normalizedCase(base2);
40314
40796
  if (c === b) return true;
40315
- const baseWithSep = b.endsWith(path34.sep) ? b : b + path34.sep;
40797
+ const baseWithSep = b.endsWith(path35.sep) ? b : b + path35.sep;
40316
40798
  return c.startsWith(baseWithSep);
40317
40799
  }
40318
40800
  function escapeError(userPath, attempted, real, realRoot) {
@@ -40323,7 +40805,7 @@ function escapeError(userPath, attempted, real, realRoot) {
40323
40805
  );
40324
40806
  }
40325
40807
  function assertLexicalContainment(resolved, root, userPath) {
40326
- const rootWithSep = root.endsWith(path34.sep) ? root : root + path34.sep;
40808
+ const rootWithSep = root.endsWith(path35.sep) ? root : root + path35.sep;
40327
40809
  if (resolved !== root && !resolved.startsWith(rootWithSep)) {
40328
40810
  throw new SandboxViolationError(
40329
40811
  `Path escapes sandbox root: ${userPath} \u2192 ${resolved} (root: ${root})`,
@@ -40336,13 +40818,13 @@ function assertRealAncestorContained(resolvedLexical, realRoot, userPath) {
40336
40818
  let probe = resolvedLexical;
40337
40819
  let realProbe = null;
40338
40820
  for (; ; ) {
40339
- if (probe === path34.dirname(probe)) break;
40821
+ if (probe === path35.dirname(probe)) break;
40340
40822
  try {
40341
40823
  realProbe = fs15.realpathSync(probe);
40342
40824
  break;
40343
40825
  } catch (err) {
40344
40826
  if (err?.code === "ENOENT") {
40345
- probe = path34.dirname(probe);
40827
+ probe = path35.dirname(probe);
40346
40828
  continue;
40347
40829
  }
40348
40830
  return;
@@ -40361,8 +40843,8 @@ function resolveSandboxedCore(userPath, options = {}) {
40361
40843
  if (typeof userPath !== "string" || userPath.length === 0) {
40362
40844
  throw new SandboxViolationError("Empty path", userPath, "");
40363
40845
  }
40364
- const root = path34.resolve(options.root ?? process.cwd());
40365
- const resolved = path34.isAbsolute(userPath) ? path34.resolve(userPath) : path34.resolve(root, userPath);
40846
+ const root = path35.resolve(options.root ?? process.cwd());
40847
+ const resolved = path35.isAbsolute(userPath) ? path35.resolve(userPath) : path35.resolve(root, userPath);
40366
40848
  assertLexicalContainment(resolved, root, userPath);
40367
40849
  let realRoot = null;
40368
40850
  try {
@@ -40380,8 +40862,8 @@ function verifyContainment(resolvedAbsolute, options = {}) {
40380
40862
  if (typeof resolvedAbsolute !== "string" || resolvedAbsolute.length === 0) {
40381
40863
  throw new SandboxViolationError("Empty path", resolvedAbsolute, "");
40382
40864
  }
40383
- const root = path34.resolve(options.root ?? process.cwd());
40384
- const resolved = path34.resolve(resolvedAbsolute);
40865
+ const root = path35.resolve(options.root ?? process.cwd());
40866
+ const resolved = path35.resolve(resolvedAbsolute);
40385
40867
  assertLexicalContainment(resolved, root, resolvedAbsolute);
40386
40868
  let realRoot = null;
40387
40869
  try {
@@ -40409,19 +40891,19 @@ var init_sandboxPath = __esm({
40409
40891
  });
40410
40892
 
40411
40893
  // src/cli/tools/krakenRadio.ts
40412
- import { appendFileSync as appendFileSync2, existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync16, readdirSync as readdirSync3 } from "node:fs";
40413
- import path35 from "node:path";
40894
+ import { appendFileSync as appendFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync16, readdirSync as readdirSync3 } from "node:fs";
40895
+ import path36 from "node:path";
40414
40896
  function radioDir(cwd) {
40415
- return path35.join(cwd, ".zelari", "radio");
40897
+ return path36.join(cwd, ".zelari", "radio");
40416
40898
  }
40417
40899
  function radioPath(cwd, sessionId2) {
40418
40900
  const safe = (sessionId2 || "default").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
40419
- return path35.join(radioDir(cwd), `${safe}.jsonl`);
40901
+ return path36.join(radioDir(cwd), `${safe}.jsonl`);
40420
40902
  }
40421
40903
  function appendKrakenRadio(cwd, sessionId2, event) {
40422
40904
  try {
40423
40905
  const dir = radioDir(cwd);
40424
- if (!existsSync15(dir)) mkdirSync8(dir, { recursive: true });
40906
+ if (!existsSync17(dir)) mkdirSync8(dir, { recursive: true });
40425
40907
  const row = {
40426
40908
  ts: event.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
40427
40909
  kind: event.kind,
@@ -40451,7 +40933,7 @@ function appendKrakenRadio(cwd, sessionId2, event) {
40451
40933
  function readKrakenRadio(cwd, sessionId2, limit = 50) {
40452
40934
  try {
40453
40935
  const file2 = radioPath(cwd, sessionId2);
40454
- if (!existsSync15(file2)) return [];
40936
+ if (!existsSync17(file2)) return [];
40455
40937
  const lines = readFileSync16(file2, "utf8").split(/\r?\n/).filter(Boolean);
40456
40938
  const slice = lines.slice(-Math.max(1, limit));
40457
40939
  const out = [];
@@ -40475,7 +40957,7 @@ function formatKrakenRadioStatus(cwd, sessionId2, limit = 12) {
40475
40957
  const flag = e.ok === false ? "\u2717" : e.ok === true ? "\u2713" : "\xB7";
40476
40958
  const ms = e.durationMs != null ? ` ${e.durationMs}ms` : "";
40477
40959
  const model = e.model ? ` [${e.model}]` : "";
40478
- const wt = e.worktree ? ` wt=${path35.basename(e.worktree)}` : "";
40960
+ const wt = e.worktree ? ` wt=${path36.basename(e.worktree)}` : "";
40479
40961
  const detail = e.detail ? ` \u2014 ${e.detail.slice(0, 120)}` : "";
40480
40962
  return `${flag} ${e.ts.slice(11, 19)} ${e.kind} ${e.agent} "${e.description}"${model}${wt}${ms}${detail}`;
40481
40963
  });
@@ -40491,7 +40973,7 @@ var init_krakenRadio = __esm({
40491
40973
  import {
40492
40974
  mkdirSync as mkdirSync9,
40493
40975
  writeFileSync as writeFileSync13,
40494
- existsSync as existsSync16,
40976
+ existsSync as existsSync18,
40495
40977
  accessSync,
40496
40978
  constants,
40497
40979
  realpathSync
@@ -40517,7 +40999,7 @@ function hashProject(projectPath) {
40517
40999
  }
40518
41000
  function isWritableDir(dir) {
40519
41001
  try {
40520
- if (!existsSync16(dir)) return false;
41002
+ if (!existsSync18(dir)) return false;
40521
41003
  accessSync(dir, constants.W_OK);
40522
41004
  return true;
40523
41005
  } catch {
@@ -40526,9 +41008,9 @@ function isWritableDir(dir) {
40526
41008
  }
40527
41009
  function ensureWorkspaceDir(workspaceDir) {
40528
41010
  mkdirSync9(workspaceDir, { recursive: true });
40529
- if (workspaceDir.endsWith("/.zelari") && existsSync16(join15(workspaceDir, "..", ".git"))) {
41011
+ if (workspaceDir.endsWith("/.zelari") && existsSync18(join15(workspaceDir, "..", ".git"))) {
40530
41012
  const gitignorePath = join15(workspaceDir, ".gitignore");
40531
- if (!existsSync16(gitignorePath)) {
41013
+ if (!existsSync18(gitignorePath)) {
40532
41014
  writeFileSync13(gitignorePath, "*\n!.gitignore\n");
40533
41015
  }
40534
41016
  }
@@ -40569,7 +41051,7 @@ __export(storage_exports, {
40569
41051
  import {
40570
41052
  readFileSync as readFileSync17,
40571
41053
  writeFileSync as writeFileSync14,
40572
- existsSync as existsSync17,
41054
+ existsSync as existsSync19,
40573
41055
  mkdirSync as mkdirSync10,
40574
41056
  readdirSync as readdirSync4,
40575
41057
  renameSync as renameSync2
@@ -40829,7 +41311,7 @@ var init_storage = __esm({
40829
41311
  Storage = class {
40830
41312
  /** Read a Markdown file with frontmatter. Throws if not found. */
40831
41313
  read(path99) {
40832
- if (!existsSync17(path99)) {
41314
+ if (!existsSync19(path99)) {
40833
41315
  throw new Error(`File not found: ${path99}`);
40834
41316
  }
40835
41317
  const md = readFileSync17(path99, "utf8");
@@ -40837,7 +41319,7 @@ var init_storage = __esm({
40837
41319
  }
40838
41320
  /** Read a Markdown file; returns null if not found. */
40839
41321
  readIfExists(path99) {
40840
- if (!existsSync17(path99)) return null;
41322
+ if (!existsSync19(path99)) return null;
40841
41323
  return this.read(path99);
40842
41324
  }
40843
41325
  /**
@@ -40853,7 +41335,7 @@ var init_storage = __esm({
40853
41335
  }
40854
41336
  /** List all .md files in a directory (non-recursive). */
40855
41337
  listMarkdown(dir) {
40856
- if (!existsSync17(dir)) return [];
41338
+ if (!existsSync19(dir)) return [];
40857
41339
  return readdirSync4(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join16(dir, f));
40858
41340
  }
40859
41341
  };
@@ -40886,7 +41368,7 @@ var init_storage = __esm({
40886
41368
  // src/cli/workspace/planStore.ts
40887
41369
  import {
40888
41370
  copyFileSync,
40889
- existsSync as existsSync18,
41371
+ existsSync as existsSync20,
40890
41372
  mkdirSync as mkdirSync11,
40891
41373
  readFileSync as readFileSync18,
40892
41374
  renameSync as renameSync3,
@@ -40950,7 +41432,7 @@ function writePlanTaskArtifact(rootDir, task) {
40950
41432
  }
40951
41433
  function loadHandle(rootDir) {
40952
41434
  const jsonPath = join17(rootDir, "plan.json");
40953
- if (!existsSync18(jsonPath)) {
41435
+ if (!existsSync20(jsonPath)) {
40954
41436
  return { rootDir, tasks: [], counter: 0, rootFields: {} };
40955
41437
  }
40956
41438
  let parsed;
@@ -40985,7 +41467,7 @@ function saveHandle(rootDir, handle) {
40985
41467
  }
40986
41468
  const jsonPath = join17(rootDir, "plan.json");
40987
41469
  mkdirSync11(rootDir, { recursive: true });
40988
- if (existsSync18(jsonPath)) {
41470
+ if (existsSync20(jsonPath)) {
40989
41471
  copyFileSync(jsonPath, `${jsonPath}.bak`);
40990
41472
  }
40991
41473
  const file2 = {
@@ -41073,8 +41555,8 @@ var init_planStore = __esm({
41073
41555
  });
41074
41556
 
41075
41557
  // src/cli/workspace/taskTouchGuard.ts
41076
- import { existsSync as existsSync19 } from "node:fs";
41077
- import path36 from "node:path";
41558
+ import { existsSync as existsSync21 } from "node:fs";
41559
+ import path37 from "node:path";
41078
41560
  function isMutatingToolName(name) {
41079
41561
  return NATIVE_WRITE_TOOLS.has(name) || MCP_FS_WRITE_RE.test(name);
41080
41562
  }
@@ -41083,11 +41565,11 @@ function toPosixRel(projectRoot, p3) {
41083
41565
  if (raw.startsWith("/") || /^[a-zA-Z]:\//.test(raw)) {
41084
41566
  let rel2;
41085
41567
  try {
41086
- rel2 = path36.relative(projectRoot, p3);
41568
+ rel2 = path37.relative(projectRoot, p3);
41087
41569
  } catch {
41088
41570
  return null;
41089
41571
  }
41090
- if (!rel2 || rel2.startsWith("..") || path36.isAbsolute(rel2)) return null;
41572
+ if (!rel2 || rel2.startsWith("..") || path37.isAbsolute(rel2)) return null;
41091
41573
  return rel2.replace(/\\/g, "/");
41092
41574
  }
41093
41575
  return raw.replace(/^\.\//, "");
@@ -41142,7 +41624,7 @@ function createTaskTouchGuard(opts) {
41142
41624
  async function inspectWrite(rel2) {
41143
41625
  try {
41144
41626
  const planPath = planJsonPathFor(root);
41145
- if (!existsSync19(planPath)) return;
41627
+ if (!existsSync21(planPath)) return;
41146
41628
  const tasks = await withPlanStore(root, (store6) => store6.tasks);
41147
41629
  for (const task of tasks) {
41148
41630
  if (task.status !== "completed" || !task.files?.length || !task.completedAt) continue;
@@ -41197,7 +41679,7 @@ var init_taskTouchGuard = __esm({
41197
41679
  // src/cli/gitOps.ts
41198
41680
  import { execFile as execFile2 } from "node:child_process";
41199
41681
  import { promisify } from "node:util";
41200
- import path37 from "node:path";
41682
+ import path38 from "node:path";
41201
41683
  async function git2(cwd, args) {
41202
41684
  try {
41203
41685
  const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
@@ -41250,7 +41732,7 @@ async function undoWorkingChanges(opts = {}) {
41250
41732
  };
41251
41733
  }
41252
41734
  function defaultProjectRoot() {
41253
- return path37.resolve(__dirname, "..", "..", "..");
41735
+ return path38.resolve(__dirname, "..", "..", "..");
41254
41736
  }
41255
41737
  var execFileAsync;
41256
41738
  var init_gitOps = __esm({
@@ -41261,7 +41743,7 @@ var init_gitOps = __esm({
41261
41743
  });
41262
41744
 
41263
41745
  // src/cli/workspace/taskStaleness.ts
41264
- import { existsSync as existsSync20 } from "node:fs";
41746
+ import { existsSync as existsSync22 } from "node:fs";
41265
41747
  function taskStaleHours() {
41266
41748
  const raw = process.env.ZELARI_TASK_STALE_HOURS;
41267
41749
  if (raw === void 0 || raw.trim() === "") return TASK_STALE_HOURS_DEFAULT;
@@ -41273,7 +41755,7 @@ async function runTaskStalenessCheck(opts) {
41273
41755
  const root = opts.projectRoot;
41274
41756
  const nowMs = (opts.now ?? Date.now)();
41275
41757
  try {
41276
- if (!existsSync20(planJsonPathFor(root))) return [];
41758
+ if (!existsSync22(planJsonPathFor(root))) return [];
41277
41759
  if (!await isGitRepo(root)) return [];
41278
41760
  const hours = opts.hoursOverride ?? taskStaleHours();
41279
41761
  const cutoff = nowMs - hours * 36e5;
@@ -41392,7 +41874,7 @@ __export(auditLogger_exports, {
41392
41874
  AuditLogger: () => AuditLogger
41393
41875
  });
41394
41876
  import { promises as fs16 } from "node:fs";
41395
- import path38 from "node:path";
41877
+ import path39 from "node:path";
41396
41878
  function defaultAuditPath() {
41397
41879
  return auditLogPath();
41398
41880
  }
@@ -41438,7 +41920,7 @@ var init_auditLogger = __esm({
41438
41920
  async append(entry) {
41439
41921
  const line = JSON.stringify(entry) + "\n";
41440
41922
  this.writeQueue = this.writeQueue.then(async () => {
41441
- await fs16.mkdir(path38.dirname(this.logPath), { recursive: true });
41923
+ await fs16.mkdir(path39.dirname(this.logPath), { recursive: true });
41442
41924
  await fs16.appendFile(this.logPath, line, "utf-8");
41443
41925
  });
41444
41926
  return this.writeQueue;
@@ -41483,9 +41965,9 @@ var init_auditLogger = __esm({
41483
41965
  });
41484
41966
 
41485
41967
  // src/cli/diagnostics/engine.ts
41486
- import { spawn as spawn6 } from "node:child_process";
41487
- import { existsSync as existsSync21 } from "node:fs";
41488
- import path39 from "node:path";
41968
+ import { spawn as spawn7 } from "node:child_process";
41969
+ import { existsSync as existsSync23 } from "node:fs";
41970
+ import path40 from "node:path";
41489
41971
  function parseEslintJson(stdout, _file2) {
41490
41972
  const json3 = safeJson(stdout);
41491
41973
  if (!Array.isArray(json3)) return [];
@@ -41544,7 +42026,7 @@ function safeJson(s) {
41544
42026
  }
41545
42027
  }
41546
42028
  function providerForFile(file2, providers = DEFAULT_PROVIDERS) {
41547
- const ext = path39.extname(file2).toLowerCase();
42029
+ const ext = path40.extname(file2).toLowerCase();
41548
42030
  return providers.find((p3) => p3.extensions.includes(ext)) ?? null;
41549
42031
  }
41550
42032
  function resolveBin(bin, cwd) {
@@ -41552,10 +42034,10 @@ function resolveBin(bin, cwd) {
41552
42034
  let dir = cwd;
41553
42035
  for (let i = 0; i < 6; i += 1) {
41554
42036
  for (const suffix of suffixes) {
41555
- const candidate = path39.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
41556
- if (existsSync21(candidate)) return candidate;
42037
+ const candidate = path40.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
42038
+ if (existsSync23(candidate)) return candidate;
41557
42039
  }
41558
- const parent = path39.dirname(dir);
42040
+ const parent = path40.dirname(dir);
41559
42041
  if (parent === dir) break;
41560
42042
  dir = parent;
41561
42043
  }
@@ -41628,7 +42110,7 @@ var init_engine2 = __esm({
41628
42110
  };
41629
42111
  let child;
41630
42112
  try {
41631
- child = process.platform === "win32" ? spawn6(buildCmdLine(cmd, args), { cwd: opts.cwd, shell: true }) : spawn6(cmd, args, { cwd: opts.cwd });
42113
+ child = process.platform === "win32" ? spawn7(buildCmdLine(cmd, args), { cwd: opts.cwd, shell: true }) : spawn7(cmd, args, { cwd: opts.cwd });
41632
42114
  } catch {
41633
42115
  done({ code: null, stdout: "", stderr: "" });
41634
42116
  return;
@@ -41660,8 +42142,8 @@ var init_engine2 = __esm({
41660
42142
 
41661
42143
  // src/cli/tools/krakenWorktree.ts
41662
42144
  import { execFile as execFile3 } from "node:child_process";
41663
- import { existsSync as existsSync22, mkdirSync as mkdirSync12, rmSync } from "node:fs";
41664
- import path40 from "node:path";
42145
+ import { existsSync as existsSync24, mkdirSync as mkdirSync12, rmSync } from "node:fs";
42146
+ import path41 from "node:path";
41665
42147
  import { promisify as promisify2 } from "node:util";
41666
42148
  import { randomBytes as randomBytes4 } from "node:crypto";
41667
42149
  function isKrakenWorktreeEnabled(env = process.env) {
@@ -41709,10 +42191,10 @@ async function createKrakenWorktree(cwd, label) {
41709
42191
  const id3 = `${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`;
41710
42192
  const slug = (label ?? "task").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "task";
41711
42193
  const branch = `kraken/${slug}-${id3}`;
41712
- const wtRoot = path40.join(repoRoot, ".zelari", "worktrees");
41713
- const wtPath = path40.join(wtRoot, `kraken-${id3}`);
42194
+ const wtRoot = path41.join(repoRoot, ".zelari", "worktrees");
42195
+ const wtPath = path41.join(wtRoot, `kraken-${id3}`);
41714
42196
  try {
41715
- if (!existsSync22(wtRoot)) mkdirSync12(wtRoot, { recursive: true });
42197
+ if (!existsSync24(wtRoot)) mkdirSync12(wtRoot, { recursive: true });
41716
42198
  } catch {
41717
42199
  return null;
41718
42200
  }
@@ -41824,7 +42306,7 @@ async function cleanupKrakenWorktree(handle, env = process.env) {
41824
42306
  if (shouldKeepWorktree(env)) return;
41825
42307
  await git3(handle.repoRoot, ["worktree", "remove", "--force", handle.path]);
41826
42308
  try {
41827
- if (existsSync22(handle.path)) {
42309
+ if (existsSync24(handle.path)) {
41828
42310
  rmSync(handle.path, { recursive: true, force: true });
41829
42311
  }
41830
42312
  } catch {
@@ -42192,8 +42674,8 @@ var init_tentacle = __esm({
42192
42674
  import { execFile as execFile4 } from "node:child_process";
42193
42675
  import { promisify as promisify3 } from "node:util";
42194
42676
  import { mkdtempSync, rmSync as rmSync2 } from "node:fs";
42195
- import { tmpdir as tmpdir2 } from "node:os";
42196
- import path41 from "node:path";
42677
+ import { tmpdir as tmpdir3 } from "node:os";
42678
+ import path42 from "node:path";
42197
42679
  import { randomUUID as randomUUID3 } from "node:crypto";
42198
42680
  async function git4(cwd, args, env) {
42199
42681
  const { stdout } = await execFileAsync3("git", ["-C", cwd, ...args], {
@@ -42213,8 +42695,8 @@ async function isGitRepo2(cwd) {
42213
42695
  return await gitSafe(cwd, ["rev-parse", "--is-inside-work-tree"]) === "true";
42214
42696
  }
42215
42697
  async function withTempIndex(fn) {
42216
- const dir = mkdtempSync(path41.join(tmpdir2(), "zelari-ckpt-"));
42217
- const indexFile = path41.join(dir, "index");
42698
+ const dir = mkdtempSync(path42.join(tmpdir3(), "zelari-ckpt-"));
42699
+ const indexFile = path42.join(dir, "index");
42218
42700
  try {
42219
42701
  return await fn(indexFile);
42220
42702
  } finally {
@@ -42305,7 +42787,7 @@ async function restoreCheckpoint(cwd, id3) {
42305
42787
  const deleted = [];
42306
42788
  for (const rel2 of added) {
42307
42789
  try {
42308
- rmSync2(path41.join(cwd, rel2), { force: true });
42790
+ rmSync2(path42.join(cwd, rel2), { force: true });
42309
42791
  deleted.push(rel2);
42310
42792
  } catch {
42311
42793
  }
@@ -42385,10 +42867,10 @@ var init_transactional = __esm({
42385
42867
 
42386
42868
  // src/cli/workspace/worldModel.ts
42387
42869
  import { promises as fs17 } from "node:fs";
42388
- import path42 from "node:path";
42389
- import { spawn as spawn7 } from "node:child_process";
42870
+ import path43 from "node:path";
42871
+ import { spawn as spawn8 } from "node:child_process";
42390
42872
  function worldDir(cwd) {
42391
- return path42.join(cwd, WORLD_DIR_NAME);
42873
+ return path43.join(cwd, WORLD_DIR_NAME);
42392
42874
  }
42393
42875
  async function ensureWorldDir(cwd) {
42394
42876
  const dir = worldDir(cwd);
@@ -42398,10 +42880,10 @@ async function ensureWorldDir(cwd) {
42398
42880
  async function appendTimeline(cwd, entry) {
42399
42881
  const dir = await ensureWorldDir(cwd);
42400
42882
  const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }) + "\n";
42401
- await fs17.appendFile(path42.join(dir, TIMELINE_FILE), line, "utf8");
42883
+ await fs17.appendFile(path43.join(dir, TIMELINE_FILE), line, "utf8");
42402
42884
  }
42403
42885
  async function readChecks(cwd) {
42404
- const p3 = path42.join(worldDir(cwd), CHECKS_FILE);
42886
+ const p3 = path43.join(worldDir(cwd), CHECKS_FILE);
42405
42887
  try {
42406
42888
  const raw = await fs17.readFile(p3, "utf8");
42407
42889
  const parsed = JSON.parse(raw);
@@ -42413,7 +42895,7 @@ async function readChecks(cwd) {
42413
42895
  function runShell(command, cwd, timeoutMs2, signal) {
42414
42896
  return new Promise((resolve9) => {
42415
42897
  const isWin = process.platform === "win32";
42416
- const child = spawn7(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
42898
+ const child = spawn8(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
42417
42899
  cwd,
42418
42900
  env: process.env,
42419
42901
  windowsHide: true,
@@ -42476,8 +42958,8 @@ function runShell(command, cwd, timeoutMs2, signal) {
42476
42958
  });
42477
42959
  }
42478
42960
  async function runBacktest(cwd, signal) {
42479
- const checksPath = path42.join(worldDir(cwd), CHECKS_FILE);
42480
- const hypothesisPath = path42.join(worldDir(cwd), HYPOTHESIS_FILE);
42961
+ const checksPath = path43.join(worldDir(cwd), CHECKS_FILE);
42962
+ const hypothesisPath = path43.join(worldDir(cwd), HYPOTHESIS_FILE);
42481
42963
  const checks = await readChecks(cwd);
42482
42964
  if (checks.length === 0) {
42483
42965
  return {
@@ -42546,7 +43028,7 @@ var init_worldModel = __esm({
42546
43028
  "use strict";
42547
43029
  init_zod();
42548
43030
  init_toolTypes();
42549
- WORLD_DIR_NAME = path42.join(".zelari", "world");
43031
+ WORLD_DIR_NAME = path43.join(".zelari", "world");
42550
43032
  HYPOTHESIS_FILE = "hypothesis.md";
42551
43033
  CHECKS_FILE = "checks.json";
42552
43034
  TIMELINE_FILE = "timeline.jsonl";
@@ -42563,7 +43045,7 @@ var init_worldModel = __esm({
42563
43045
  execute: async (args, ctx) => {
42564
43046
  try {
42565
43047
  const dir = await ensureWorldDir(ctx.cwd);
42566
- const file2 = path42.join(dir, HYPOTHESIS_FILE);
43048
+ const file2 = path43.join(dir, HYPOTHESIS_FILE);
42567
43049
  if (args.append) {
42568
43050
  const block = `
42569
43051
 
@@ -42602,7 +43084,7 @@ ${args.content}
42602
43084
  execute: async (args, ctx) => {
42603
43085
  try {
42604
43086
  const dir = await ensureWorldDir(ctx.cwd);
42605
- const file2 = path42.join(dir, CHECKS_FILE);
43087
+ const file2 = path43.join(dir, CHECKS_FILE);
42606
43088
  const body = { checks: args.checks };
42607
43089
  await fs17.writeFile(file2, JSON.stringify(body, null, 2) + "\n", "utf8");
42608
43090
  await appendTimeline(ctx.cwd, { kind: "checks_set", count: args.checks.length });
@@ -42641,8 +43123,8 @@ ${args.content}
42641
43123
  stdoutPreview: "(dryRun)",
42642
43124
  mismatch: "dryRun"
42643
43125
  })),
42644
- hypothesisPath: path42.join(worldDir(ctx.cwd), HYPOTHESIS_FILE),
42645
- checksPath: path42.join(worldDir(ctx.cwd), CHECKS_FILE)
43126
+ hypothesisPath: path43.join(worldDir(ctx.cwd), HYPOTHESIS_FILE),
43127
+ checksPath: path43.join(worldDir(ctx.cwd), CHECKS_FILE)
42646
43128
  });
42647
43129
  }
42648
43130
  const result = await runBacktest(ctx.cwd, ctx.signal);
@@ -42666,7 +43148,7 @@ ${args.content}
42666
43148
  execute: async (args, ctx) => {
42667
43149
  try {
42668
43150
  const dir = await ensureWorldDir(ctx.cwd);
42669
- const file2 = path42.join(dir, TIMELINE_FILE);
43151
+ const file2 = path43.join(dir, TIMELINE_FILE);
42670
43152
  await appendTimeline(ctx.cwd, {
42671
43153
  kind: args.kind,
42672
43154
  summary: args.summary,
@@ -42690,9 +43172,9 @@ __export(graphMemory_exports, {
42690
43172
  toGraphSnapshot: () => toGraphSnapshot
42691
43173
  });
42692
43174
  import { promises as fs18 } from "node:fs";
42693
- import path43 from "node:path";
43175
+ import path44 from "node:path";
42694
43176
  function snapshotPath(cwd) {
42695
- return path43.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
43177
+ return path44.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
42696
43178
  }
42697
43179
  function toGraphSnapshot(graph, opts) {
42698
43180
  const unresolved = (opts.unresolvedFindings ?? []).map((u) => ({
@@ -42719,7 +43201,7 @@ async function saveGraphSnapshot(cwd, snapshot) {
42719
43201
  try {
42720
43202
  await fs18.access(cwd);
42721
43203
  const file2 = snapshotPath(cwd);
42722
- await fs18.mkdir(path43.dirname(file2), { recursive: true });
43204
+ await fs18.mkdir(path44.dirname(file2), { recursive: true });
42723
43205
  await fs18.writeFile(file2, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
42724
43206
  } catch {
42725
43207
  }
@@ -42786,7 +43268,7 @@ var SNAPSHOT_DIR, SNAPSHOT_FILE, MAX_SNAPSHOT_FINDINGS_CHARS;
42786
43268
  var init_graphMemory = __esm({
42787
43269
  "src/cli/kraken/graphMemory.ts"() {
42788
43270
  "use strict";
42789
- SNAPSHOT_DIR = path43.join(".zelari", "kraken");
43271
+ SNAPSHOT_DIR = path44.join(".zelari", "kraken");
42790
43272
  SNAPSHOT_FILE = "last-graph.json";
42791
43273
  MAX_SNAPSHOT_FINDINGS_CHARS = 400;
42792
43274
  }
@@ -42794,14 +43276,14 @@ var init_graphMemory = __esm({
42794
43276
 
42795
43277
  // src/cli/kraken/workbench.ts
42796
43278
  import { promises as fs19 } from "node:fs";
42797
- import path44 from "node:path";
43279
+ import path45 from "node:path";
42798
43280
  function isWorkbenchEnabled(env = process.env) {
42799
43281
  const v = (env.ZELARI_KRAKEN_WORKBENCH ?? "1").trim().toLowerCase();
42800
43282
  if (v === "0" || v === "false" || v === "no" || v === "off") return false;
42801
43283
  return true;
42802
43284
  }
42803
43285
  function workbenchPath(cwd, graphId) {
42804
- return path44.join(cwd, ".zelari", "radio", `workbench-${graphId}.md`);
43286
+ return path45.join(cwd, ".zelari", "radio", `workbench-${graphId}.md`);
42805
43287
  }
42806
43288
  function countByStatus2(nodes) {
42807
43289
  const out = { pending: 0, running: 0, done: 0, error: 0, skipped: 0 };
@@ -42964,7 +43446,7 @@ var init_workbench = __esm({
42964
43446
  if (!this.enabled) return null;
42965
43447
  if (!this.dirty && this.lastWrite) return this.lastWrite;
42966
43448
  const out = workbenchPath(this.cwd, this.graphId);
42967
- await fs19.mkdir(path44.dirname(out), { recursive: true });
43449
+ await fs19.mkdir(path45.dirname(out), { recursive: true });
42968
43450
  const body = this.render();
42969
43451
  const tmp = `${out}.${process.pid}.${Date.now()}.tmp`;
42970
43452
  await fs19.writeFile(tmp, body, "utf8");
@@ -43110,9 +43592,9 @@ var init_semanticOwnership = __esm({
43110
43592
 
43111
43593
  // src/cli/ast/engine.ts
43112
43594
  import { readFile as readFile5 } from "node:fs/promises";
43113
- import path45 from "node:path";
43595
+ import path46 from "node:path";
43114
43596
  function isAstSupported(file2) {
43115
- return TS_EXTENSIONS.has(path45.extname(file2).toLowerCase());
43597
+ return TS_EXTENSIONS.has(path46.extname(file2).toLowerCase());
43116
43598
  }
43117
43599
  function loadTs() {
43118
43600
  if (!tsPromise) {
@@ -43124,8 +43606,8 @@ function errMessage(err) {
43124
43606
  return err instanceof Error ? err.message : String(err);
43125
43607
  }
43126
43608
  async function parseFileSymbolsDiag(file2, cwd) {
43127
- const resolvedPath = path45.isAbsolute(file2) ? file2 : path45.join(cwd ?? process.cwd(), file2);
43128
- const extension = path45.extname(resolvedPath).toLowerCase();
43609
+ const resolvedPath = path46.isAbsolute(file2) ? file2 : path46.join(cwd ?? process.cwd(), file2);
43610
+ const extension = path46.extname(resolvedPath).toLowerCase();
43129
43611
  if (!TS_EXTENSIONS.has(extension)) {
43130
43612
  return {
43131
43613
  status: "unsupported-extension",
@@ -43167,7 +43649,7 @@ async function parseFileSymbolsDiag(file2, cwd) {
43167
43649
  }
43168
43650
  let source2;
43169
43651
  try {
43170
- source2 = ts.createSourceFile(path45.basename(resolvedPath), text, ts.ScriptTarget.Latest, true);
43652
+ source2 = ts.createSourceFile(path46.basename(resolvedPath), text, ts.ScriptTarget.Latest, true);
43171
43653
  } catch (err) {
43172
43654
  return {
43173
43655
  status: "parse-error",
@@ -43460,14 +43942,14 @@ var init_spawnRoi = __esm({
43460
43942
 
43461
43943
  // src/cli/kraken/reputationStore.ts
43462
43944
  import { appendFile as appendFile2, mkdir as mkdir2, readFile as readFile6, rename, writeFile as writeFile2 } from "node:fs/promises";
43463
- import path46 from "node:path";
43945
+ import path47 from "node:path";
43464
43946
  function resolveReputationStorePath(cwd = process.cwd(), env = process.env) {
43465
43947
  const override = env[REPUTATION_STORE_ENV]?.trim();
43466
43948
  if (override) return override;
43467
- return path46.join(cwd, ".zelari", "reputation.jsonl");
43949
+ return path47.join(cwd, ".zelari", "reputation.jsonl");
43468
43950
  }
43469
43951
  async function appendRecord(storePath, record2) {
43470
- await mkdir2(path46.dirname(storePath), { recursive: true });
43952
+ await mkdir2(path47.dirname(storePath), { recursive: true });
43471
43953
  await appendFile2(storePath, `${JSON.stringify(record2)}
43472
43954
  `, "utf8");
43473
43955
  }
@@ -44019,8 +44501,8 @@ __export(executor_exports, {
44019
44501
  resolveTransactional: () => resolveTransactional,
44020
44502
  thoroughnessForKind: () => thoroughnessForKind
44021
44503
  });
44022
- import { existsSync as existsSync23 } from "node:fs";
44023
- import path47 from "node:path";
44504
+ import { existsSync as existsSync25 } from "node:fs";
44505
+ import path48 from "node:path";
44024
44506
  async function defaultSymbolExtractor(file2) {
44025
44507
  if (!isAstSupported(file2)) return null;
44026
44508
  const r = await parseFileSymbolsDiag(file2);
@@ -44086,7 +44568,7 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
44086
44568
  }
44087
44569
  function defaultChecksExists(cwd) {
44088
44570
  try {
44089
- return existsSync23(path47.join(cwd, ".zelari", "world", "checks.json"));
44571
+ return existsSync25(path48.join(cwd, ".zelari", "world", "checks.json"));
44090
44572
  } catch {
44091
44573
  return false;
44092
44574
  }
@@ -44594,7 +45076,7 @@ var init_executor = __esm({
44594
45076
  try {
44595
45077
  const summary = aggregate(
44596
45078
  records,
44597
- { repo: path47.basename(this.parentCwd), role: agentForNode(node) },
45079
+ { repo: path48.basename(this.parentCwd), role: agentForNode(node) },
44598
45080
  now
44599
45081
  );
44600
45082
  sample = summary.sample;
@@ -45071,7 +45553,7 @@ ${upstream}` : node.prompt,
45071
45553
  const model = res.ok && res.model && res.model !== "n/a" ? res.model : null;
45072
45554
  const reviewerVerdict = res.ok && this.isReviewerKind(node.kind) && typeof node.result === "string" && node.result.length > 0 ? parseVerifyVerdict(node.result).verdict : null;
45073
45555
  const record2 = reputationRecordFromNodeRun({
45074
- repo: path47.basename(this.parentCwd),
45556
+ repo: path48.basename(this.parentCwd),
45075
45557
  role: agentForNode(node),
45076
45558
  kind: node.kind,
45077
45559
  ok: res.ok,
@@ -45459,7 +45941,7 @@ ${failed.error ?? "unknown error"}`,
45459
45941
  });
45460
45942
 
45461
45943
  // src/cli/tools/taskTool.ts
45462
- import { existsSync as existsSync24 } from "node:fs";
45944
+ import { existsSync as existsSync26 } from "node:fs";
45463
45945
  import { randomUUID as randomUUID4 } from "node:crypto";
45464
45946
  function resetTaskSpawnCount() {
45465
45947
  const g = globalThis;
@@ -45527,7 +46009,7 @@ ${findings || "(the reviewer reported FAIL without detail)"}`;
45527
46009
  async function runAutoVerifyAfterGeneral(opts) {
45528
46010
  const g = globalThis;
45529
46011
  g.__zelariGeneralVerifyDebt = { description: opts.original.description };
45530
- const inheritedCwd = opts.general.worktreePath && existsSync24(opts.general.worktreePath) ? opts.general.worktreePath : void 0;
46012
+ const inheritedCwd = opts.general.worktreePath && existsSync26(opts.general.worktreePath) ? opts.general.worktreePath : void 0;
45531
46013
  const runVerify = (label) => runTentacle({
45532
46014
  deps: opts.deps,
45533
46015
  args: {
@@ -46653,7 +47135,7 @@ var init_askUser = __esm({
46653
47135
  });
46654
47136
 
46655
47137
  // src/cli/skillsMd.ts
46656
- import { existsSync as existsSync25, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "node:fs";
47138
+ import { existsSync as existsSync27, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "node:fs";
46657
47139
  import { join as join18 } from "node:path";
46658
47140
  function skillMdSearchDirs(projectRoot = process.cwd()) {
46659
47141
  return [
@@ -46718,7 +47200,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
46718
47200
  const summary = { loaded: [], skipped: [] };
46719
47201
  const seen = new Set(options.existingIds ?? []);
46720
47202
  for (const dir of skillMdSearchDirs(projectRoot)) {
46721
- if (!existsSync25(dir)) continue;
47203
+ if (!existsSync27(dir)) continue;
46722
47204
  let entries;
46723
47205
  try {
46724
47206
  entries = readdirSync5(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -46727,7 +47209,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
46727
47209
  }
46728
47210
  for (const entry of entries) {
46729
47211
  const skillPath = join18(dir, entry, "SKILL.md");
46730
- if (!existsSync25(skillPath)) continue;
47212
+ if (!existsSync27(skillPath)) continue;
46731
47213
  try {
46732
47214
  const parsed = parseSkillMd(readFileSync19(skillPath, "utf8"), skillPath);
46733
47215
  if (!parsed) {
@@ -46770,6 +47252,21 @@ var init_skillsMd = __esm({
46770
47252
  });
46771
47253
 
46772
47254
  // src/cli/budget/historySummary.ts
47255
+ function collectOpenObligations(text, out) {
47256
+ for (const rawLine of text.split("\n")) {
47257
+ const line = rawLine.trim();
47258
+ if (!line || line.length > 240) continue;
47259
+ if (OPEN_OBLIGATION_LINE.test(line) || OPEN_OBLIGATION_KEYWORD.test(line)) {
47260
+ out.push(oneLine(line, 200));
47261
+ }
47262
+ }
47263
+ }
47264
+ function collectEvidenceRefs(text, out) {
47265
+ for (const re of EVIDENCE_PATTERNS) {
47266
+ const matches = text.match(re);
47267
+ if (matches) out.push(...matches);
47268
+ }
47269
+ }
46773
47270
  function extractiveHistorySummary(dropped, opts) {
46774
47271
  const maxChars = opts?.maxChars ?? MAX_SUMMARY_CHARS;
46775
47272
  if (dropped.length === 0) return "No prior turns.";
@@ -46781,8 +47278,12 @@ function extractiveHistorySummary(dropped, opts) {
46781
47278
  const decisions = [];
46782
47279
  const tools = /* @__PURE__ */ new Map();
46783
47280
  const files = /* @__PURE__ */ new Set();
47281
+ const openObligations = [];
47282
+ const evidenceRefs = [];
46784
47283
  let toolResults = 0;
46785
47284
  for (const m of dropped) {
47285
+ collectOpenObligations(m.content ?? "", openObligations);
47286
+ collectEvidenceRefs(m.content ?? "", evidenceRefs);
46786
47287
  if (m.role === "user" && m.content.trim()) {
46787
47288
  const goal = oneLine(m.content, 220);
46788
47289
  userGoals.push(goal);
@@ -46820,6 +47321,14 @@ function extractiveHistorySummary(dropped, opts) {
46820
47321
  parts.push("## User constraints (preserve exactly)");
46821
47322
  for (const item of [...new Set(userConstraints)].slice(-8)) parts.push(`- ${item}`);
46822
47323
  }
47324
+ if (openObligations.length) {
47325
+ parts.push("## Open obligations (still owed \u2014 do not silently drop)");
47326
+ for (const item of [...new Set(openObligations)].slice(-10)) parts.push(`- ${item}`);
47327
+ }
47328
+ if (evidenceRefs.length) {
47329
+ parts.push("## Evidence anchors (verbatim refs, keep provenance)");
47330
+ for (const item of [...new Set(evidenceRefs)].slice(-12)) parts.push(`- ${oneLine(item, 160)}`);
47331
+ }
46823
47332
  if (unresolved.length) {
46824
47333
  parts.push("## Unresolved failures / pending repair");
46825
47334
  for (const item of [...new Set(unresolved)].slice(-8)) parts.push(`- ${item}`);
@@ -46878,11 +47387,19 @@ function collectPathsFromText(text, out) {
46878
47387
  n += 1;
46879
47388
  }
46880
47389
  }
46881
- var MAX_SUMMARY_CHARS;
47390
+ var MAX_SUMMARY_CHARS, OPEN_OBLIGATION_LINE, OPEN_OBLIGATION_KEYWORD, EVIDENCE_PATTERNS;
46882
47391
  var init_historySummary = __esm({
46883
47392
  "src/cli/budget/historySummary.ts"() {
46884
47393
  "use strict";
46885
47394
  MAX_SUMMARY_CHARS = 3500;
47395
+ OPEN_OBLIGATION_LINE = /^\s*[-*]\s+\[\s\]/;
47396
+ OPEN_OBLIGATION_KEYWORD = /\b(TODO|PENDING|FIX ME|STILL OWED|DA FARE)\b\s*[:.]/;
47397
+ EVIDENCE_PATTERNS = [
47398
+ /OBSERVATION\s+ref=#\d+(?:[^,\n]{0,40})?/g,
47399
+ /\bcommit\s+[0-9a-f]{7,40}\b/gi,
47400
+ /\bpushed\s+[0-9a-f]{7,40}\b/gi,
47401
+ /\bexit code\s+\d+\b/gi
47402
+ ];
46886
47403
  }
46887
47404
  });
46888
47405
 
@@ -47008,15 +47525,25 @@ function withDroppedRange(result, dropped, strategy) {
47008
47525
  return { ...result, ...range, strategy };
47009
47526
  }
47010
47527
  function resolveMaxMessages(opts) {
47011
- const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
47528
+ const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 40, min: 0 });
47012
47529
  let turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
47013
- if (opts?.durableStatePresent && !opts?.maxMessages && process.env.ZELARI_HISTORY_TURNS === void 0) {
47014
- turns = Math.min(turns, 3);
47015
- }
47016
47530
  if (turns <= 0) return 0;
47017
47531
  if (opts?.force && opts?.maxMessages) return Math.max(1, opts.maxMessages);
47018
47532
  return turns * 4;
47019
47533
  }
47534
+ function resolveHistoryCharBudget(opts) {
47535
+ const halveForDurable = !!opts?.durableStatePresent && process.env.ZELARI_HISTORY_BUDGET_CHARS === void 0;
47536
+ return envNumber(process.env.ZELARI_HISTORY_BUDGET_CHARS, {
47537
+ default: halveForDurable ? 1e4 : 2e4,
47538
+ min: 2e3,
47539
+ max: 2e5
47540
+ });
47541
+ }
47542
+ function historyChars(messages) {
47543
+ let n = 0;
47544
+ for (const m of messages) n += (m.content ?? "").length;
47545
+ return n;
47546
+ }
47020
47547
  function findValidCutIndex(messages, naiveCut) {
47021
47548
  let cut = naiveCut;
47022
47549
  while (cut < messages.length) {
@@ -47100,7 +47627,12 @@ function compactHistoryDetailed(messages, opts) {
47100
47627
  "extractive"
47101
47628
  );
47102
47629
  }
47103
- if (messages.length <= maxMessages * 2 && !opts?.force) {
47630
+ const charBudget = resolveHistoryCharBudget(opts);
47631
+ const explicitCount = opts?.maxMessages !== void 0 || process.env.ZELARI_HISTORY_TURNS !== void 0;
47632
+ const overChars = historyChars(messages) > charBudget;
47633
+ const overCount = messages.length > maxMessages * 2;
47634
+ const shouldCompact = overChars || explicitCount && overCount || !!opts?.force;
47635
+ if (!shouldCompact) {
47104
47636
  return {
47105
47637
  messages,
47106
47638
  compacted: false,
@@ -47108,7 +47640,18 @@ function compactHistoryDetailed(messages, opts) {
47108
47640
  summary: ""
47109
47641
  };
47110
47642
  }
47111
- const naiveCut = Math.max(0, messages.length - maxMessages);
47643
+ let naiveCut;
47644
+ if (overChars) {
47645
+ let cut2 = 0;
47646
+ let keptChars = historyChars(messages);
47647
+ while (cut2 < messages.length && keptChars > charBudget) {
47648
+ keptChars -= (messages[cut2].content ?? "").length;
47649
+ cut2 += 1;
47650
+ }
47651
+ naiveCut = Math.max(cut2, messages.length - maxMessages);
47652
+ } else {
47653
+ naiveCut = Math.max(0, messages.length - maxMessages);
47654
+ }
47112
47655
  const cut = findValidCutIndex(messages, naiveCut);
47113
47656
  if (cut === 0) {
47114
47657
  return {
@@ -47372,7 +47915,11 @@ function resolveContextLimit(model, provider) {
47372
47915
  }
47373
47916
  function phaseKnobs(phase2) {
47374
47917
  return {
47375
- historyTurns: phase2 === "plan" ? envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 8, min: 0 }) : envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 }),
47918
+ // v2.32.0 (S3): count defaults raised to a runaway safety net the
47919
+ // primary history gate is the adaptive char budget (see
47920
+ // historyCompaction.resolveHistoryCharBudget). An explicit
47921
+ // ZELARI_HISTORY_TURNS stays a user contract and is honored as before.
47922
+ historyTurns: phase2 === "plan" ? envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 40, min: 0 }) : envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 40, min: 0 }),
47376
47923
  maxToolLoopIterations: phase2 === "plan" ? envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, { default: 60, min: 1 }) : envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
47377
47924
  default: 120,
47378
47925
  min: 1
@@ -48059,8 +48606,8 @@ var init_planTaskTools = __esm({
48059
48606
 
48060
48607
  // src/cli/tools/inspectTypecheckSafety.ts
48061
48608
  import { promises as fs20 } from "node:fs";
48062
- import path48 from "node:path";
48063
- import { spawn as spawn8 } from "node:child_process";
48609
+ import path49 from "node:path";
48610
+ import { spawn as spawn9 } from "node:child_process";
48064
48611
  async function scanTsbuildinfo(root) {
48065
48612
  const found = [];
48066
48613
  const stack = [root];
@@ -48073,11 +48620,11 @@ async function scanTsbuildinfo(root) {
48073
48620
  continue;
48074
48621
  }
48075
48622
  for (const entry of entries) {
48076
- const p3 = path48.join(dir, entry.name);
48623
+ const p3 = path49.join(dir, entry.name);
48077
48624
  if (entry.isDirectory()) {
48078
48625
  if (!SCAN_SKIP.has(entry.name)) stack.push(p3);
48079
48626
  } else if (entry.name.endsWith(".tsbuildinfo")) {
48080
- found.push(path48.relative(root, p3).split(path48.sep).join("/"));
48627
+ found.push(path49.relative(root, p3).split(path49.sep).join("/"));
48081
48628
  }
48082
48629
  }
48083
48630
  }
@@ -48086,7 +48633,7 @@ async function scanTsbuildinfo(root) {
48086
48633
  }
48087
48634
  async function gitStatusPorcelain(root) {
48088
48635
  return new Promise((resolve9) => {
48089
- const child = spawn8("git", ["status", "--porcelain"], { cwd: root, shell: false });
48636
+ const child = spawn9("git", ["status", "--porcelain"], { cwd: root, shell: false });
48090
48637
  let out = "";
48091
48638
  child.stdout.on("data", (d) => out += d.toString());
48092
48639
  child.stderr.on("data", (d) => out += d.toString());
@@ -48113,7 +48660,7 @@ async function cleanupArtifacts(root, relPaths) {
48113
48660
  const failed = [];
48114
48661
  for (const rel2 of relPaths) {
48115
48662
  try {
48116
- await fs20.unlink(path48.join(root, rel2));
48663
+ await fs20.unlink(path49.join(root, rel2));
48117
48664
  cleaned.push(rel2);
48118
48665
  } catch {
48119
48666
  failed.push(rel2);
@@ -48136,17 +48683,17 @@ var init_inspectTypecheckSafety = __esm({
48136
48683
  });
48137
48684
 
48138
48685
  // src/cli/tools/inspectCommand.ts
48139
- import { spawn as spawn9 } from "node:child_process";
48686
+ import { spawn as spawn10 } from "node:child_process";
48140
48687
  import { createHash as createHash13 } from "node:crypto";
48141
- import { existsSync as existsSync26, promises as fs21 } from "node:fs";
48688
+ import { existsSync as existsSync28, promises as fs21 } from "node:fs";
48142
48689
  import os2 from "node:os";
48143
- import path49 from "node:path";
48690
+ import path50 from "node:path";
48144
48691
  function resolveNodeModuleBin(start, rel2) {
48145
- let dir = path49.resolve(start);
48692
+ let dir = path50.resolve(start);
48146
48693
  for (; ; ) {
48147
- const candidate = path49.join(dir, "node_modules", rel2);
48148
- if (existsSync26(candidate)) return candidate;
48149
- const parent = path49.dirname(dir);
48694
+ const candidate = path50.join(dir, "node_modules", rel2);
48695
+ if (existsSync28(candidate)) return candidate;
48696
+ const parent = path50.dirname(dir);
48150
48697
  if (parent === dir) return void 0;
48151
48698
  dir = parent;
48152
48699
  }
@@ -48214,7 +48761,7 @@ function buildInspectCommand(op, ctx) {
48214
48761
  case "npm_ls":
48215
48762
  case "npm_outdated":
48216
48763
  case "npm_view": {
48217
- const npmCli = ctx.npmCliPath ?? resolveNodeModuleBin(ctx.root, path49.join("npm", "bin", "npm-cli.js")) ?? path49.join(ctx.root, "node_modules", "npm", "bin", "npm-cli.js");
48764
+ const npmCli = ctx.npmCliPath ?? resolveNodeModuleBin(ctx.root, path50.join("npm", "bin", "npm-cli.js")) ?? path50.join(ctx.root, "node_modules", "npm", "bin", "npm-cli.js");
48218
48765
  if (op.operation === "npm_view") {
48219
48766
  const err = rejectFlagLike("package", op.package);
48220
48767
  if (err) return { ok: false, reason: err };
@@ -48223,14 +48770,14 @@ function buildInspectCommand(op, ctx) {
48223
48770
  return { ok: true, command: process.execPath, argv: [npmCli, ...sub], inspectionClass: "env-info" };
48224
48771
  }
48225
48772
  case "typecheck": {
48226
- const project = path49.resolve(ctx.cwd, op.project ?? "tsconfig.json");
48773
+ const project = path50.resolve(ctx.cwd, op.project ?? "tsconfig.json");
48227
48774
  const hash3 = createHash13("sha256").update(project).digest("hex").slice(0, 16);
48228
- const tsBuildInfoFile = path49.join(os2.tmpdir(), "zelari-inspect", `${hash3}.tsbuildinfo`);
48775
+ const tsBuildInfoFile = path50.join(os2.tmpdir(), "zelari-inspect", `${hash3}.tsbuildinfo`);
48229
48776
  return {
48230
48777
  ok: true,
48231
48778
  command: process.execPath,
48232
48779
  argv: [
48233
- ctx.tscPath ?? resolveNodeModuleBin(ctx.root, path49.join("typescript", "bin", "tsc")) ?? resolveNodeModuleBin(ctx.cwd, path49.join("typescript", "bin", "tsc")) ?? path49.join(ctx.root, "node_modules", "typescript", "bin", "tsc"),
48780
+ ctx.tscPath ?? resolveNodeModuleBin(ctx.root, path50.join("typescript", "bin", "tsc")) ?? resolveNodeModuleBin(ctx.cwd, path50.join("typescript", "bin", "tsc")) ?? path50.join(ctx.root, "node_modules", "typescript", "bin", "tsc"),
48234
48781
  "--noEmit",
48235
48782
  // S3.5 primary mechanism: redirect, never disable — composite forces
48236
48783
  // incremental (TS#30661), so --incremental false would break on the
@@ -48252,7 +48799,7 @@ function runSpawn(command, argv, opts) {
48252
48799
  return new Promise((resolve9) => {
48253
48800
  let child;
48254
48801
  try {
48255
- child = spawn9(command, argv, { cwd: opts.cwd, shell: false });
48802
+ child = spawn10(command, argv, { cwd: opts.cwd, shell: false });
48256
48803
  } catch (err) {
48257
48804
  resolve9({ code: null, stdout: "", stderr: String(err), timedOut: false, spawnError: String(err) });
48258
48805
  return;
@@ -48538,379 +49085,6 @@ var init_selfKillGuard = __esm({
48538
49085
  }
48539
49086
  });
48540
49087
 
48541
- // src/cli/safety/policyLoadMode.ts
48542
- var policyLoadMode_exports = {};
48543
- __export(policyLoadMode_exports, {
48544
- POLICY_LOAD_BLOCK_REASON: () => POLICY_LOAD_BLOCK_REASON,
48545
- POLICY_LOAD_EXIT_CODE: () => POLICY_LOAD_EXIT_CODE,
48546
- POLICY_LOAD_MODE_ENV: () => POLICY_LOAD_MODE_ENV,
48547
- activePolicyLoadMode: () => activePolicyLoadMode,
48548
- activePolicyLoadSurface: () => activePolicyLoadSurface,
48549
- resolvePolicyLoadMode: () => resolvePolicyLoadMode,
48550
- setActivePolicyLoadSurface: () => setActivePolicyLoadSurface
48551
- });
48552
- function isTruthyFlag(v) {
48553
- const n = v?.trim().toLowerCase();
48554
- return n === "1" || n === "true" || n === "yes" || n === "on";
48555
- }
48556
- function resolvePolicyLoadMode(input) {
48557
- const v = input.override?.trim().toLowerCase();
48558
- if (v === "strict") return "strict";
48559
- if (v === "permissive") return "permissive";
48560
- if (input.surface === "headless" || input.surface === "mission") return "strict";
48561
- if (isTruthyFlag(input.ci)) return "strict";
48562
- return "permissive";
48563
- }
48564
- function setActivePolicyLoadSurface(surface) {
48565
- activeSurface = surface;
48566
- }
48567
- function activePolicyLoadSurface() {
48568
- return activeSurface;
48569
- }
48570
- function activePolicyLoadMode(env = process.env) {
48571
- return resolvePolicyLoadMode({
48572
- surface: activeSurface,
48573
- override: env[POLICY_LOAD_MODE_ENV],
48574
- ci: typeof env.CI === "string" ? env.CI : void 0
48575
- });
48576
- }
48577
- var POLICY_LOAD_MODE_ENV, POLICY_LOAD_BLOCK_REASON, POLICY_LOAD_EXIT_CODE, activeSurface;
48578
- var init_policyLoadMode = __esm({
48579
- "src/cli/safety/policyLoadMode.ts"() {
48580
- "use strict";
48581
- POLICY_LOAD_MODE_ENV = "ZELARI_POLICY_LOAD_MODE";
48582
- POLICY_LOAD_BLOCK_REASON = "policy-load-failed";
48583
- POLICY_LOAD_EXIT_CODE = 2;
48584
- activeSurface = "tui";
48585
- }
48586
- });
48587
-
48588
- // src/cli/safety/jails/darwin.ts
48589
- import { existsSync as existsSync27 } from "node:fs";
48590
- function sbplQuote(p3) {
48591
- return `"${p3.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
48592
- }
48593
- function buildSeatbeltProfile(spec) {
48594
- const lines = [
48595
- "(version 1)",
48596
- `; zelari osJail (t28) \u2014 generated from JailSpec root=${spec.root}`,
48597
- "(allow default)",
48598
- "(deny file-write*)",
48599
- "(allow file-write*"
48600
- ];
48601
- for (const dir of spec.writable) {
48602
- lines.push(` (subpath ${sbplQuote(dir)})`);
48603
- }
48604
- lines.push(")");
48605
- switch (spec.network.mode) {
48606
- case "deny":
48607
- lines.push("(deny network*)");
48608
- break;
48609
- case "allow":
48610
- lines.push("(allow network*)");
48611
- break;
48612
- case "allow-list":
48613
- lines.push(
48614
- `; allow-list hosts (${spec.network.hosts.join(", ")}) NOT kernel-filtered \u2014 degraded to allow`,
48615
- "(allow network*)"
48616
- );
48617
- break;
48618
- }
48619
- return lines.join("\n");
48620
- }
48621
- function darwinProbe(platform, exists) {
48622
- if (platform !== "darwin") {
48623
- return {
48624
- backend: "seatbelt",
48625
- available: false,
48626
- reason: `platform ${platform} is not darwin`
48627
- };
48628
- }
48629
- if (!exists(SANDBOX_EXEC_PATH)) {
48630
- return {
48631
- backend: "seatbelt",
48632
- available: false,
48633
- reason: `${SANDBOX_EXEC_PATH} not found`
48634
- };
48635
- }
48636
- return {
48637
- backend: "seatbelt",
48638
- available: true,
48639
- reason: `sandbox-exec available at ${SANDBOX_EXEC_PATH}`
48640
- };
48641
- }
48642
- var SANDBOX_EXEC_PATH, darwinBackend;
48643
- var init_darwin = __esm({
48644
- "src/cli/safety/jails/darwin.ts"() {
48645
- "use strict";
48646
- SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
48647
- darwinBackend = {
48648
- id: "seatbelt",
48649
- probe: () => darwinProbe(process.platform, (p3) => existsSync27(p3)),
48650
- wrap: (spec, program, argv) => ({
48651
- program: SANDBOX_EXEC_PATH,
48652
- argv: ["-p", buildSeatbeltProfile(spec), program, ...argv]
48653
- })
48654
- };
48655
- }
48656
- });
48657
-
48658
- // src/cli/safety/jails/linux.ts
48659
- import { existsSync as existsSync28 } from "node:fs";
48660
- function findBinaryOnPath(bin, pathValue, exists) {
48661
- for (const dir of pathValue.split(/[:;]/)) {
48662
- if (!dir) continue;
48663
- const full = `${dir}/${bin}`;
48664
- if (exists(full)) return full;
48665
- }
48666
- return null;
48667
- }
48668
- function linuxProbe(platform, pathValue, exists) {
48669
- if (platform !== "linux") {
48670
- return { backend: "bwrap", available: false, reason: `platform ${platform} is not linux` };
48671
- }
48672
- const found = findBinaryOnPath(BWRAP_BIN, pathValue, exists);
48673
- if (!found) {
48674
- return {
48675
- backend: "bwrap",
48676
- available: false,
48677
- reason: `${BWRAP_BIN} not found on PATH (landlock needs native syscalls Node does not expose)`
48678
- };
48679
- }
48680
- return { backend: "bwrap", available: true, reason: `${BWRAP_BIN} found at ${found}` };
48681
- }
48682
- function buildBwrapArgs(spec, program, argv) {
48683
- const args = ["--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc"];
48684
- for (const dir of spec.writable) {
48685
- args.push("--bind-try", dir, dir);
48686
- }
48687
- if (spec.network.mode === "deny") {
48688
- args.push("--unshare-net");
48689
- }
48690
- args.push("--", program, ...argv);
48691
- return args;
48692
- }
48693
- var BWRAP_BIN, linuxBackend;
48694
- var init_linux = __esm({
48695
- "src/cli/safety/jails/linux.ts"() {
48696
- "use strict";
48697
- BWRAP_BIN = "bwrap";
48698
- linuxBackend = {
48699
- id: "bwrap",
48700
- probe: () => linuxProbe(process.platform, process.env.PATH ?? "", (p3) => existsSync28(p3)),
48701
- wrap: (spec, program, argv) => ({
48702
- program: BWRAP_BIN,
48703
- argv: buildBwrapArgs(spec, program, argv)
48704
- })
48705
- };
48706
- }
48707
- });
48708
-
48709
- // src/cli/safety/jails/win32.ts
48710
- function win32Probe(platform) {
48711
- if (platform !== "win32") {
48712
- return { backend: "win32-restricted-token", available: false, reason: `platform ${platform} is not win32` };
48713
- }
48714
- return { backend: "win32-restricted-token", available: false, reason: WIN32_UNAVAILABLE_REASON };
48715
- }
48716
- var WIN32_UNAVAILABLE_REASON, win32Backend;
48717
- var init_win32 = __esm({
48718
- "src/cli/safety/jails/win32.ts"() {
48719
- "use strict";
48720
- WIN32_UNAVAILABLE_REASON = "restricted-token + Job Object require native Windows APIs that pure Node does not expose (no native npm deps allowed, P5) \u2014 honest unavailable; see src/cli/safety/jails/win32.ts";
48721
- win32Backend = {
48722
- id: "win32-restricted-token",
48723
- probe: () => win32Probe(process.platform),
48724
- wrap: () => {
48725
- throw new Error(`win32 jail backend unavailable: ${WIN32_UNAVAILABLE_REASON}`);
48726
- }
48727
- };
48728
- }
48729
- });
48730
-
48731
- // src/cli/safety/osJail.ts
48732
- import { spawn as spawn10 } from "node:child_process";
48733
- import { homedir as homedir5, tmpdir as tmpdir3 } from "node:os";
48734
- import path50 from "node:path";
48735
- function resolveJailMode(override, env = process.env) {
48736
- const v = override?.trim().toLowerCase();
48737
- if (v === "off") return "off";
48738
- if (v === "advisory") return "advisory";
48739
- if (v === "required") return "required";
48740
- if (activePolicyLoadMode(env) !== "strict") return "advisory";
48741
- return probeJailBackend().available ? "required" : "advisory";
48742
- }
48743
- function activeJailMode(env = process.env) {
48744
- return resolveJailMode(env[OS_JAIL_ENV], env);
48745
- }
48746
- function platformBackend(platform = process.platform) {
48747
- switch (platform) {
48748
- case "darwin":
48749
- return darwinBackend;
48750
- case "linux":
48751
- return linuxBackend;
48752
- default:
48753
- return win32Backend;
48754
- }
48755
- }
48756
- function currentBackend(platform = process.platform) {
48757
- return testBackend ?? platformBackend(platform);
48758
- }
48759
- function probeJailBackend(platform = process.platform) {
48760
- if (testBackend) return testBackend.probe();
48761
- if (!probeCache || probeCache.platform !== platform) {
48762
- probeCache = { platform, result: platformBackend(platform).probe() };
48763
- }
48764
- return probeCache.result;
48765
- }
48766
- function defaultEnvAllowlist(platform = process.platform) {
48767
- return platform === "win32" ? [...BASE_ENV_ALLOWLIST, ...WIN32_ENV_ALLOWLIST] : BASE_ENV_ALLOWLIST;
48768
- }
48769
- function defaultWritable(root, home = homedir5(), tmp = tmpdir3(), platform = process.platform) {
48770
- const raw = [path50.resolve(root), path50.resolve(tmp), path50.join(home, ".zelari-code")];
48771
- const out = [];
48772
- for (const p3 of raw) {
48773
- const key = platform === "win32" ? p3.toLowerCase() : p3;
48774
- if (!out.some((q) => (platform === "win32" ? q.toLowerCase() : q) === key)) out.push(p3);
48775
- }
48776
- return out;
48777
- }
48778
- function buildJailSpec(opts) {
48779
- return {
48780
- root: path50.resolve(opts.root),
48781
- network: opts.network ?? { mode: "deny" },
48782
- envAllowlist: opts.envAllowlist ?? defaultEnvAllowlist(),
48783
- writable: opts.writable ?? defaultWritable(opts.root)
48784
- };
48785
- }
48786
- function networkSpecFromClaimHosts(allowedHosts) {
48787
- if (!allowedHosts || allowedHosts.length === 0) return { mode: "deny" };
48788
- return { mode: "allow-list", hosts: [...new Set(allowedHosts)] };
48789
- }
48790
- function sanitizeEnv(env, allowlist, platform = process.platform) {
48791
- const wanted = new Set(allowlist.map((k) => platform === "win32" ? k.toLowerCase() : k));
48792
- const out = {};
48793
- for (const [k, v] of Object.entries(env)) {
48794
- if (v === void 0) continue;
48795
- const key = platform === "win32" ? k.toLowerCase() : k;
48796
- if (wanted.has(key)) out[k] = v;
48797
- }
48798
- return out;
48799
- }
48800
- function jailDenyReason(probe, mode) {
48801
- return `OS jail backend unavailable (${probe.backend}: ${probe.reason}) with ZELARI_OS_JAIL=${mode} \u2014 execution is DENIED instead of running unjailed (t28 golden rule: missing backend + required \u21D2 deny, never warn/skip). Set ZELARI_OS_JAIL=advisory (visible fail-open) or =off to explicitly allow unjailed execution.`;
48802
- }
48803
- function jailAdvisoryNotice(probe) {
48804
- return `advisory: OS jail backend unavailable (${probe.backend}: ${probe.reason}) \u2014 this process will run UNJAILED (visible fail-open). Set ZELARI_OS_JAIL=required to deny instead.`;
48805
- }
48806
- function decideJailSpawn(spec, input) {
48807
- const mode = input.mode ?? activeJailMode();
48808
- const merged = { ...input.env ?? process.env, ...input.envExtras ?? {} };
48809
- if (mode === "off") return { action: "spawn-plain", env: merged };
48810
- const probe = probeJailBackend();
48811
- if (!probe.available) {
48812
- if (mode === "required") return { action: "deny", reason: jailDenyReason(probe, mode) };
48813
- return { action: "spawn-plain", env: sanitizeEnv(merged, spec.envAllowlist), notice: jailAdvisoryNotice(probe) };
48814
- }
48815
- const wrapped = currentBackend().wrap(spec, input.program, input.argv);
48816
- return {
48817
- action: "spawn-jailed",
48818
- probe,
48819
- program: wrapped.program,
48820
- argv: wrapped.argv,
48821
- env: sanitizeEnv(merged, spec.envAllowlist)
48822
- };
48823
- }
48824
- function spawnJailed(spec, req) {
48825
- const decision = decideJailSpawn(spec, {
48826
- program: req.program,
48827
- argv: req.argv,
48828
- env: req.env,
48829
- envExtras: req.envExtras
48830
- });
48831
- if (decision.action === "deny") {
48832
- return { outcome: "denied", reason: `[jail] ${decision.reason}` };
48833
- }
48834
- const notice = decision.action === "spawn-plain" ? decision.notice : void 0;
48835
- if (notice) {
48836
- console.error(`[os-jail] ${notice}`);
48837
- req.onNotice?.(notice);
48838
- }
48839
- const jailed = decision.action === "spawn-jailed";
48840
- const program = jailed ? decision.program : req.program;
48841
- const argv = jailed ? decision.argv : [...req.argv];
48842
- try {
48843
- const child = spawn10(program, argv, {
48844
- cwd: req.cwd,
48845
- signal: req.signal,
48846
- shell: false,
48847
- env: decision.env,
48848
- stdio: ["ignore", "pipe", "pipe"]
48849
- // non-interactive by construction
48850
- });
48851
- return {
48852
- outcome: "spawned",
48853
- child,
48854
- backend: jailed ? decision.probe.backend : "none",
48855
- jailed,
48856
- ...notice ? { notice } : {}
48857
- };
48858
- } catch (err) {
48859
- return { outcome: "failed", reason: err instanceof Error ? err.message : String(err) };
48860
- }
48861
- }
48862
- function jailAvailability(env = process.env) {
48863
- const mode = activeJailMode(env);
48864
- const probe = mode === "off" ? { backend: "none", available: false, reason: "jail disabled (ZELARI_OS_JAIL=off)" } : probeJailBackend();
48865
- return { mode, probe };
48866
- }
48867
- var OS_JAIL_ENV, testBackend, probeCache, BASE_ENV_ALLOWLIST, WIN32_ENV_ALLOWLIST;
48868
- var init_osJail = __esm({
48869
- "src/cli/safety/osJail.ts"() {
48870
- "use strict";
48871
- init_policyLoadMode();
48872
- init_darwin();
48873
- init_linux();
48874
- init_win32();
48875
- OS_JAIL_ENV = "ZELARI_OS_JAIL";
48876
- testBackend = null;
48877
- probeCache = null;
48878
- BASE_ENV_ALLOWLIST = [
48879
- "PATH",
48880
- "HOME",
48881
- "USER",
48882
- "LANG",
48883
- "CI",
48884
- "TERM",
48885
- "NO_COLOR"
48886
- ];
48887
- WIN32_ENV_ALLOWLIST = [
48888
- "PATH",
48889
- "Path",
48890
- "HOMEDRIVE",
48891
- "HOMEPATH",
48892
- "SystemRoot",
48893
- "SystemDrive",
48894
- "ComSpec",
48895
- "PATHEXT",
48896
- "TEMP",
48897
- "TMP",
48898
- "APPDATA",
48899
- "LOCALAPPDATA",
48900
- "USERPROFILE",
48901
- "USERNAME",
48902
- "MSYSTEM",
48903
- "PROGRAMFILES",
48904
- "ProgramFiles",
48905
- "ProgramData",
48906
- "OS",
48907
- "NUMBER_OF_PROCESSORS",
48908
- "PROCESSOR_ARCHITECTURE",
48909
- "WINDIR"
48910
- ];
48911
- }
48912
- });
48913
-
48914
49088
  // src/cli/tools/execProcess.ts
48915
49089
  function createExecProcessTool(root, opts = {}) {
48916
49090
  const baseSpec = buildJailSpec({ root });
@@ -52425,6 +52599,21 @@ var init_resolveStream = __esm({
52425
52599
  });
52426
52600
 
52427
52601
  // src/cli/safety/toolPermissions.ts
52602
+ var toolPermissions_exports = {};
52603
+ __export(toolPermissions_exports, {
52604
+ PERMISSION_PRESETS: () => PERMISSION_PRESETS,
52605
+ activePermissionPreset: () => activePermissionPreset,
52606
+ clearSessionPermissionGrants: () => clearSessionPermissionGrants,
52607
+ defaultPermissionPolicy: () => defaultPermissionPolicy,
52608
+ grantSessionCategory: () => grantSessionCategory,
52609
+ grantSessionTool: () => grantSessionTool,
52610
+ intersectPermissionPolicy: () => intersectPermissionPolicy,
52611
+ isAutoPermissions: () => isAutoPermissions,
52612
+ isSessionGranted: () => isSessionGranted,
52613
+ listSessionPermissionGrants: () => listSessionPermissionGrants,
52614
+ parsePermissionPreset: () => parsePermissionPreset,
52615
+ resolveToolPermission: () => resolveToolPermission
52616
+ });
52428
52617
  function grantSessionTool(toolName) {
52429
52618
  const n = toolName.trim();
52430
52619
  if (n) sessionToolGrants.add(n);
@@ -52436,6 +52625,12 @@ function clearSessionPermissionGrants() {
52436
52625
  sessionToolGrants.clear();
52437
52626
  sessionCategoryGrants.clear();
52438
52627
  }
52628
+ function listSessionPermissionGrants() {
52629
+ return {
52630
+ tools: [...sessionToolGrants].sort(),
52631
+ categories: [...sessionCategoryGrants]
52632
+ };
52633
+ }
52439
52634
  function isSessionGranted(toolName, required2) {
52440
52635
  if (sessionToolGrants.has(toolName)) return true;
52441
52636
  if (!required2.length) return false;
@@ -52532,6 +52727,49 @@ var init_toolPermissions = __esm({
52532
52727
  }
52533
52728
  });
52534
52729
 
52730
+ // src/cli/safety/destructiveCommands.ts
52731
+ function commandTextFrom(input) {
52732
+ const parts = [];
52733
+ if (typeof input.command === "string" && input.command) parts.push(input.command);
52734
+ if (typeof input.program === "string" && input.program) {
52735
+ const args = Array.isArray(input.args) ? input.args.filter((a) => typeof a === "string") : [];
52736
+ parts.push([input.program, ...args].join(" "));
52737
+ }
52738
+ return parts.join(" ; ");
52739
+ }
52740
+ function destructiveCommandHit(input) {
52741
+ const text = commandTextFrom(input);
52742
+ if (!text) return null;
52743
+ for (const rule of DESTRUCTIVE_RULES) {
52744
+ if (rule.pattern.test(text)) return rule.label;
52745
+ }
52746
+ return null;
52747
+ }
52748
+ var DESTRUCTIVE_RULES;
52749
+ var init_destructiveCommands = __esm({
52750
+ "src/cli/safety/destructiveCommands.ts"() {
52751
+ "use strict";
52752
+ DESTRUCTIVE_RULES = [
52753
+ // `rm` with BOTH recursive and force flags, any combination (-rf, -fr, -rvf).
52754
+ { id: "rm-recursive-force", pattern: /\brm\s+(?:-{1,2}[a-z]*r[a-z]*f|-{1,2}[a-z]*f[a-z]*r)\b/i, label: "'rm' recursive+force delete" },
52755
+ // Windows del/rd with the /s (subdirectories) switch.
52756
+ { id: "del-subtree", pattern: /\bdel\b[^&|;]*\/s\b/i, label: "'del /s' subtree delete" },
52757
+ { id: "rd-subtree", pattern: /\brd\b[^&|;]*\/s\b/i, label: "'rd /s' subtree delete" },
52758
+ // PowerShell recursive delete.
52759
+ { id: "remove-item-recurse", pattern: /\bremove-item\b[^&|;]*-recurse\b/i, label: "'Remove-Item -Recurse' recursive delete" },
52760
+ // Volume format (DOS/Windows drive letter form).
52761
+ { id: "format-volume", pattern: /\bformat\s+[a-z]:/i, label: "'format <volume>:'" },
52762
+ // History rewrite on a shared ref.
52763
+ { id: "git-push-force", pattern: /\bgit\s+push\b[^&|;]*--force\b/i, label: "'git push --force'" },
52764
+ // Filesystem-level destroyers.
52765
+ { id: "mkfs", pattern: /\bmkfs(?:\.\w+)?\b/i, label: "'mkfs' filesystem format" },
52766
+ { id: "dd-raw-device", pattern: /\bdd\b[^&|;]*of=\/dev\//i, label: "'dd' raw device write" },
52767
+ // Root-wide permission wipe.
52768
+ { id: "chmod-root-777", pattern: /\bchmod\s+-R\s+777\s+\/(?:\s|$)/i, label: "'chmod -R 777 /' root permission wipe" }
52769
+ ];
52770
+ }
52771
+ });
52772
+
52535
52773
  // src/cli/safety/provenance.ts
52536
52774
  function provenanceEnabled() {
52537
52775
  return process.env.ZELARI_PROVENANCE !== "0";
@@ -53907,6 +54145,13 @@ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence =
53907
54145
  }
53908
54146
  }
53909
54147
  }
54148
+ if (action === "allow" && required2.includes("execute") && activePermissionPreset() !== "yolo" && !isSessionGranted(original.name, required2)) {
54149
+ const destructiveHit = destructiveCommandHit(input ?? {});
54150
+ if (destructiveHit) {
54151
+ action = "ask";
54152
+ actionReason = `[destructive] ${destructiveHit} - confirm before execute`;
54153
+ }
54154
+ }
53910
54155
  const claimHit = claims && claims.effect !== void 0 ? claims.matchedRules.find((x) => x.effect === claims.effect) : void 0;
53911
54156
  const rulePrefix = contractRule ? `[contract] rule '${contractRule.match}'${contractRule.reason ? ` \u2014 ${contractRule.reason}` : ""}` : rule ? `[policy] rule '${rule.match}'${rule.reason ? ` \u2014 ${rule.reason}` : ""}` : claimHit ? `[policy] claim '${claimHit.match}'${claimHit.reason ? ` \u2014 ${claimHit.reason}` : ""}` : "";
53912
54157
  if (action === "deny") {
@@ -54328,6 +54573,7 @@ var init_toolRegistry = __esm({
54328
54573
  init_resolveStream();
54329
54574
  init_providerConfig();
54330
54575
  init_toolPermissions();
54576
+ init_destructiveCommands();
54331
54577
  init_provenance();
54332
54578
  init_lifecycleHooks();
54333
54579
  init_astGate();
@@ -57039,6 +57285,113 @@ var init_claudeProvider = __esm({
57039
57285
  }
57040
57286
  });
57041
57287
 
57288
+ // src/cli/costBudget.ts
57289
+ var costBudget_exports = {};
57290
+ __export(costBudget_exports, {
57291
+ SessionBudgetTracker: () => SessionBudgetTracker,
57292
+ budgetChip: () => budgetChip,
57293
+ processSessionBudget: () => processSessionBudget,
57294
+ resetProcessSessionBudget: () => resetProcessSessionBudget,
57295
+ resolveSessionBudget: () => resolveSessionBudget,
57296
+ sessionBudgetHoldNotice: () => sessionBudgetHoldNotice
57297
+ });
57298
+ function resolveSessionBudget(env = process.env) {
57299
+ const maxUsd = parsePositiveFloat(env.ZELARI_SESSION_BUDGET_USD);
57300
+ const maxTokens = parsePositiveInt(env.ZELARI_SESSION_BUDGET_TOKENS);
57301
+ if (maxUsd === void 0 && maxTokens === void 0) return {};
57302
+ return { ...maxUsd !== void 0 ? { maxUsd } : {}, ...maxTokens !== void 0 ? { maxTokens } : {} };
57303
+ }
57304
+ function parsePositiveFloat(raw) {
57305
+ if (!raw) return void 0;
57306
+ const n = Number.parseFloat(raw);
57307
+ return Number.isFinite(n) && n > 0 ? n : void 0;
57308
+ }
57309
+ function parsePositiveInt(raw) {
57310
+ if (!raw) return void 0;
57311
+ const n = Number.parseInt(raw, 10);
57312
+ return Number.isFinite(n) && n > 0 ? n : void 0;
57313
+ }
57314
+ function round6(value) {
57315
+ return Math.round(value * 1e6) / 1e6;
57316
+ }
57317
+ function processSessionBudget(env = process.env) {
57318
+ if (!processTracker) processTracker = new SessionBudgetTracker(resolveSessionBudget(env));
57319
+ return processTracker;
57320
+ }
57321
+ function resetProcessSessionBudget() {
57322
+ processTracker = void 0;
57323
+ }
57324
+ function sessionBudgetHoldNotice(env = process.env) {
57325
+ const budget = processSessionBudget(env);
57326
+ if (!budget.isHold()) return null;
57327
+ const s = budget.status();
57328
+ return `[budget] HOLD \u2014 session budget exhausted (${s.usedUsd.toFixed(2)} USD \xB7 ${s.usedTokens} tokens). Raise ZELARI_SESSION_BUDGET_USD / ZELARI_SESSION_BUDGET_TOKENS or start /new. State preserved; no provider call was made.`;
57329
+ }
57330
+ function budgetChip(status) {
57331
+ if (status.state === "off") return null;
57332
+ const pct = Math.max(status.pctUsd ?? 0, status.pctTokens ?? 0);
57333
+ const label = status.state === "hold" ? "budget HOLD" : `budget ${Math.min(999, Math.round(pct * 100))}%`;
57334
+ const tone = status.state === "hold" ? "red" : status.state === "warn" ? "yellow" : "green";
57335
+ return { label, tone };
57336
+ }
57337
+ var SessionBudgetTracker, processTracker;
57338
+ var init_costBudget = __esm({
57339
+ "src/cli/costBudget.ts"() {
57340
+ "use strict";
57341
+ SessionBudgetTracker = class {
57342
+ constructor(budget = {}) {
57343
+ this.budget = budget;
57344
+ }
57345
+ usedUsd = 0;
57346
+ usedTokens = 0;
57347
+ get enabled() {
57348
+ return this.budget.maxUsd !== void 0 || this.budget.maxTokens !== void 0;
57349
+ }
57350
+ /** Idempotent per-turn accumulation; ignores NaN/negative inputs. */
57351
+ record(delta) {
57352
+ if (Number.isFinite(delta.costUsd) && (delta.costUsd ?? 0) > 0) this.usedUsd += delta.costUsd ?? 0;
57353
+ if (Number.isFinite(delta.tokens) && (delta.tokens ?? 0) > 0) this.usedTokens += Math.round(delta.tokens ?? 0);
57354
+ }
57355
+ status() {
57356
+ if (!this.enabled) return { state: "off", usedUsd: this.usedUsd, usedTokens: this.usedTokens, pctUsd: null, pctTokens: null };
57357
+ const pctUsd = this.budget.maxUsd !== void 0 ? this.usedUsd / this.budget.maxUsd : null;
57358
+ const pctTokens = this.budget.maxTokens !== void 0 ? this.usedTokens / this.budget.maxTokens : null;
57359
+ const worst = Math.max(pctUsd ?? 0, pctTokens ?? 0);
57360
+ const state3 = worst >= 1 ? "hold" : worst >= 0.8 ? "warn" : "ok";
57361
+ return { state: state3, usedUsd: round6(this.usedUsd), usedTokens: this.usedTokens, pctUsd, pctTokens };
57362
+ }
57363
+ /** True when no further provider turn should start. */
57364
+ isHold() {
57365
+ return this.status().state === "hold";
57366
+ }
57367
+ };
57368
+ }
57369
+ });
57370
+
57371
+ // src/cli/workspace/planDetect.ts
57372
+ var planDetect_exports = {};
57373
+ __export(planDetect_exports, {
57374
+ hasWorkspacePlan: () => hasWorkspacePlan
57375
+ });
57376
+ import { existsSync as existsSync35, readFileSync as readFileSync25 } from "node:fs";
57377
+ import { join as join23 } from "node:path";
57378
+ function hasWorkspacePlan(projectRoot = process.cwd()) {
57379
+ const planPath = join23(resolveWorkspaceRoot(projectRoot), "plan.json");
57380
+ if (!existsSync35(planPath)) return false;
57381
+ try {
57382
+ const parsed = JSON.parse(readFileSync25(planPath, "utf8"));
57383
+ return Array.isArray(parsed.phases) && parsed.phases.length > 0;
57384
+ } catch {
57385
+ return false;
57386
+ }
57387
+ }
57388
+ var init_planDetect = __esm({
57389
+ "src/cli/workspace/planDetect.ts"() {
57390
+ "use strict";
57391
+ init_paths3();
57392
+ }
57393
+ });
57394
+
57042
57395
  // src/cli/memory/legacyImport.ts
57043
57396
  import { createHash as createHash18 } from "node:crypto";
57044
57397
  import { promises as fs27 } from "node:fs";
@@ -57241,7 +57594,7 @@ var init_sqliteCodec = __esm({
57241
57594
  });
57242
57595
 
57243
57596
  // src/cli/memory/sqliteRpc.ts
57244
- import { existsSync as existsSync35 } from "node:fs";
57597
+ import { existsSync as existsSync36 } from "node:fs";
57245
57598
  import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
57246
57599
  import * as path67 from "node:path";
57247
57600
  import { Worker } from "node:worker_threads";
@@ -57254,7 +57607,7 @@ function isBusy(error51) {
57254
57607
  function resolveWorkerUrl() {
57255
57608
  const here = path67.dirname(fileURLToPath2(import.meta.url));
57256
57609
  const direct = path67.join(here, "sqliteWorker.mjs");
57257
- if (existsSync35(direct)) return pathToFileURL2(direct);
57610
+ if (existsSync36(direct)) return pathToFileURL2(direct);
57258
57611
  return pathToFileURL2(path67.join(here, "memory", "sqliteWorker.mjs"));
57259
57612
  }
57260
57613
  var SqliteWorkerRpc;
@@ -58261,14 +58614,14 @@ var init_serviceFactory = __esm({
58261
58614
  });
58262
58615
 
58263
58616
  // src/cli/workspace/projectInstructions.ts
58264
- import { existsSync as existsSync36, readFileSync as readFileSync25 } from "node:fs";
58265
- import { join as join26 } from "node:path";
58617
+ import { existsSync as existsSync37, readFileSync as readFileSync26 } from "node:fs";
58618
+ import { join as join27 } from "node:path";
58266
58619
  function loadProjectInstructions(projectRoot = process.cwd(), maxChars = MAX_CHARS) {
58267
58620
  for (const name of CANDIDATES) {
58268
- const full = join26(projectRoot, name);
58269
- if (!existsSync36(full)) continue;
58621
+ const full = join27(projectRoot, name);
58622
+ if (!existsSync37(full)) continue;
58270
58623
  try {
58271
- let raw = readFileSync25(full, "utf8");
58624
+ let raw = readFileSync26(full, "utf8");
58272
58625
  raw = raw.replace(/\r\n/g, "\n").trim();
58273
58626
  if (!raw) continue;
58274
58627
  if (raw.length <= maxChars) {
@@ -58312,8 +58665,8 @@ __export(workspaceSummary_exports, {
58312
58665
  buildWorkspaceSummary: () => buildWorkspaceSummary,
58313
58666
  buildZelariReadHint: () => buildZelariReadHint
58314
58667
  });
58315
- import { existsSync as existsSync37, readFileSync as readFileSync26, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
58316
- import { join as join27, relative as relative2 } from "node:path";
58668
+ import { existsSync as existsSync38, readFileSync as readFileSync27, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
58669
+ import { join as join28, relative as relative2 } from "node:path";
58317
58670
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
58318
58671
  const { maxEntries = 30, maxChars = 3500, maxDeps = 24, maxScripts = 16 } = options;
58319
58672
  const name = safeProjectName(projectRoot);
@@ -58346,11 +58699,11 @@ function formatTaskLine(t) {
58346
58699
  }
58347
58700
  function buildPlanSummary(projectRoot = process.cwd(), options) {
58348
58701
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
58349
- const planPath = join27(zelariRoot, "plan.json");
58350
- if (!existsSync37(planPath)) return null;
58702
+ const planPath = join28(zelariRoot, "plan.json");
58703
+ if (!existsSync38(planPath)) return null;
58351
58704
  let plan;
58352
58705
  try {
58353
- plan = JSON.parse(readFileSync26(planPath, "utf8"));
58706
+ plan = JSON.parse(readFileSync27(planPath, "utf8"));
58354
58707
  } catch {
58355
58708
  return null;
58356
58709
  }
@@ -58489,8 +58842,8 @@ function pickNextTask(open2) {
58489
58842
  )[0];
58490
58843
  }
58491
58844
  function buildZelariReadHint(projectRoot = process.cwd()) {
58492
- const planPath = join27(resolveWorkspaceRoot(projectRoot), "plan.json");
58493
- if (!existsSync37(planPath)) return "";
58845
+ const planPath = join28(resolveWorkspaceRoot(projectRoot), "plan.json");
58846
+ if (!existsSync38(planPath)) return "";
58494
58847
  return [
58495
58848
  "# Council workspace detected (.zelari/) \u2014 DRAFT vault",
58496
58849
  "`.zelari/plan.json` and `.zelari/docs/` hold **design hypotheses**, not verified product state.",
@@ -58505,10 +58858,10 @@ function safeProjectName(root) {
58505
58858
  }
58506
58859
  }
58507
58860
  function readPackageJson2(projectRoot) {
58508
- const p3 = join27(projectRoot, "package.json");
58509
- if (!existsSync37(p3)) return null;
58861
+ const p3 = join28(projectRoot, "package.json");
58862
+ if (!existsSync38(p3)) return null;
58510
58863
  try {
58511
- return JSON.parse(readFileSync26(p3, "utf8"));
58864
+ return JSON.parse(readFileSync27(p3, "utf8"));
58512
58865
  } catch {
58513
58866
  return null;
58514
58867
  }
@@ -58558,11 +58911,11 @@ function listShallow(projectRoot, maxEntries) {
58558
58911
  out.push(`\u2026 (+${top.length - count} more)`);
58559
58912
  break;
58560
58913
  }
58561
- const rel2 = relative2(projectRoot, join27(projectRoot, entry.name));
58914
+ const rel2 = relative2(projectRoot, join28(projectRoot, entry.name));
58562
58915
  if (entry.isDirectory()) {
58563
58916
  let inner = "";
58564
58917
  try {
58565
- const sub = readdirSync7(join27(projectRoot, entry.name), {
58918
+ const sub = readdirSync7(join28(projectRoot, entry.name), {
58566
58919
  withFileTypes: true
58567
58920
  }).filter((e) => !e.name.startsWith(".")).slice(0, 4).map((e) => e.name);
58568
58921
  if (sub.length > 0)
@@ -58610,12 +58963,12 @@ var init_workspaceSummary = __esm({
58610
58963
  });
58611
58964
 
58612
58965
  // src/cli/workspace/buildLessonsSummary.ts
58613
- import { existsSync as existsSync38 } from "node:fs";
58614
- import { join as join28 } from "node:path";
58966
+ import { existsSync as existsSync39 } from "node:fs";
58967
+ import { join as join29 } from "node:path";
58615
58968
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
58616
58969
  if (process.env["ZELARI_LESSONS"] === "0") return null;
58617
58970
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
58618
- if (!existsSync38(join28(zelariRoot, "lessons.jsonl"))) return null;
58971
+ if (!existsSync39(join29(zelariRoot, "lessons.jsonl"))) return null;
58619
58972
  const lessons = recallLessons(zelariRoot, {
58620
58973
  maxLessons: 5,
58621
58974
  maxBytes: 2048,
@@ -58636,8 +58989,8 @@ var composeContext_exports = {};
58636
58989
  __export(composeContext_exports, {
58637
58990
  composeProjectContext: () => composeProjectContext
58638
58991
  });
58639
- import { existsSync as existsSync39, readdirSync as readdirSync8, readFileSync as readFileSync27 } from "node:fs";
58640
- import { join as join29 } from "node:path";
58992
+ import { existsSync as existsSync40, readdirSync as readdirSync8, readFileSync as readFileSync28 } from "node:fs";
58993
+ import { join as join30 } from "node:path";
58641
58994
  function cap2(text, max, label) {
58642
58995
  if (!text || text.length <= max) return { text: text || "", truncated: false };
58643
58996
  return {
@@ -58649,13 +59002,13 @@ function cap2(text, max, label) {
58649
59002
  }
58650
59003
  function buildDesignIndex(projectRoot, maxChars) {
58651
59004
  const root = resolveWorkspaceRoot(projectRoot);
58652
- if (!existsSync39(root)) return "";
59005
+ if (!existsSync40(root)) return "";
58653
59006
  const lines = [
58654
59007
  "# Design vault index (.zelari/) \u2014 HYPOTHESES only",
58655
59008
  "Full design docs are NOT product source of truth. Open with list_files / read_file / searchDocuments if needed."
58656
59009
  ];
58657
- const docsDir = join29(root, "docs");
58658
- if (existsSync39(docsDir)) {
59010
+ const docsDir = join30(root, "docs");
59011
+ if (existsSync40(docsDir)) {
58659
59012
  try {
58660
59013
  const docs = readdirSync8(docsDir).filter((n) => n.endsWith(".md")).slice(0, 12);
58661
59014
  if (docs.length > 0) {
@@ -58669,12 +59022,12 @@ function buildDesignIndex(projectRoot, maxChars) {
58669
59022
  }
58670
59023
  }
58671
59024
  for (const name of ["risks.md", "plan.json", "nfr-spec.json"]) {
58672
- if (existsSync39(join29(root, name))) {
59025
+ if (existsSync40(join30(root, name))) {
58673
59026
  lines.push(`- .zelari/${name} present`);
58674
59027
  }
58675
59028
  }
58676
- const decisionsDir = join29(root, "decisions");
58677
- if (existsSync39(decisionsDir)) {
59029
+ const decisionsDir = join30(root, "decisions");
59030
+ if (existsSync40(decisionsDir)) {
58678
59031
  try {
58679
59032
  const n = readdirSync8(decisionsDir).filter((f) => f.endsWith(".md")).length;
58680
59033
  if (n > 0) lines.push(`- .zelari/decisions/ (${n} ADR file(s) \u2014 treat proposed as non-binding)`);
@@ -58775,17 +59128,17 @@ function composeProjectContext(input) {
58775
59128
  }
58776
59129
  function readDurableHeadSync(projectRoot) {
58777
59130
  try {
58778
- const headPath = join29(projectRoot, ".zelari", "state", "HEAD.json");
58779
- if (!existsSync39(headPath)) return "";
58780
- const head = JSON.parse(readFileSync27(headPath, "utf8"));
59131
+ const headPath = join30(projectRoot, ".zelari", "state", "HEAD.json");
59132
+ if (!existsSync40(headPath)) return "";
59133
+ const head = JSON.parse(readFileSync28(headPath, "utf8"));
58781
59134
  if (!head?.id) return "";
58782
- const metaPath = join29(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
58783
- if (!existsSync39(metaPath)) return "";
58784
- const meta3 = JSON.parse(readFileSync27(metaPath, "utf8"));
58785
- const discPath = meta3.artifactDir ? join29(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join29(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
59135
+ const metaPath = join30(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
59136
+ if (!existsSync40(metaPath)) return "";
59137
+ const meta3 = JSON.parse(readFileSync28(metaPath, "utf8"));
59138
+ const discPath = meta3.artifactDir ? join30(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join30(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
58786
59139
  let discoveries = [];
58787
- if (existsSync39(discPath)) {
58788
- discoveries = JSON.parse(readFileSync27(discPath, "utf8"));
59140
+ if (existsSync40(discPath)) {
59141
+ discoveries = JSON.parse(readFileSync28(discPath, "utf8"));
58789
59142
  }
58790
59143
  const reusable = discoveries.filter((d) => d.reusable !== false);
58791
59144
  const lines = [
@@ -58813,30 +59166,6 @@ var init_composeContext = __esm({
58813
59166
  }
58814
59167
  });
58815
59168
 
58816
- // src/cli/workspace/planDetect.ts
58817
- var planDetect_exports = {};
58818
- __export(planDetect_exports, {
58819
- hasWorkspacePlan: () => hasWorkspacePlan
58820
- });
58821
- import { existsSync as existsSync40, readFileSync as readFileSync28 } from "node:fs";
58822
- import { join as join30 } from "node:path";
58823
- function hasWorkspacePlan(projectRoot = process.cwd()) {
58824
- const planPath = join30(resolveWorkspaceRoot(projectRoot), "plan.json");
58825
- if (!existsSync40(planPath)) return false;
58826
- try {
58827
- const parsed = JSON.parse(readFileSync28(planPath, "utf8"));
58828
- return Array.isArray(parsed.phases) && parsed.phases.length > 0;
58829
- } catch {
58830
- return false;
58831
- }
58832
- }
58833
- var init_planDetect = __esm({
58834
- "src/cli/workspace/planDetect.ts"() {
58835
- "use strict";
58836
- init_paths3();
58837
- }
58838
- });
58839
-
58840
59169
  // src/cli/state/loadDurableContext.ts
58841
59170
  var loadDurableContext_exports = {};
58842
59171
  __export(loadDurableContext_exports, {
@@ -60499,82 +60828,6 @@ var init_mcpManager = __esm({
60499
60828
  }
60500
60829
  });
60501
60830
 
60502
- // src/cli/costBudget.ts
60503
- var costBudget_exports = {};
60504
- __export(costBudget_exports, {
60505
- SessionBudgetTracker: () => SessionBudgetTracker,
60506
- budgetChip: () => budgetChip,
60507
- processSessionBudget: () => processSessionBudget,
60508
- resetProcessSessionBudget: () => resetProcessSessionBudget,
60509
- resolveSessionBudget: () => resolveSessionBudget
60510
- });
60511
- function resolveSessionBudget(env = process.env) {
60512
- const maxUsd = parsePositiveFloat(env.ZELARI_SESSION_BUDGET_USD);
60513
- const maxTokens = parsePositiveInt(env.ZELARI_SESSION_BUDGET_TOKENS);
60514
- if (maxUsd === void 0 && maxTokens === void 0) return {};
60515
- return { ...maxUsd !== void 0 ? { maxUsd } : {}, ...maxTokens !== void 0 ? { maxTokens } : {} };
60516
- }
60517
- function parsePositiveFloat(raw) {
60518
- if (!raw) return void 0;
60519
- const n = Number.parseFloat(raw);
60520
- return Number.isFinite(n) && n > 0 ? n : void 0;
60521
- }
60522
- function parsePositiveInt(raw) {
60523
- if (!raw) return void 0;
60524
- const n = Number.parseInt(raw, 10);
60525
- return Number.isFinite(n) && n > 0 ? n : void 0;
60526
- }
60527
- function round6(value) {
60528
- return Math.round(value * 1e6) / 1e6;
60529
- }
60530
- function processSessionBudget(env = process.env) {
60531
- if (!processTracker) processTracker = new SessionBudgetTracker(resolveSessionBudget(env));
60532
- return processTracker;
60533
- }
60534
- function resetProcessSessionBudget() {
60535
- processTracker = void 0;
60536
- }
60537
- function budgetChip(status) {
60538
- if (status.state === "off") return null;
60539
- const pct = Math.max(status.pctUsd ?? 0, status.pctTokens ?? 0);
60540
- const label = status.state === "hold" ? "budget HOLD" : `budget ${Math.min(999, Math.round(pct * 100))}%`;
60541
- const tone = status.state === "hold" ? "red" : status.state === "warn" ? "yellow" : "green";
60542
- return { label, tone };
60543
- }
60544
- var SessionBudgetTracker, processTracker;
60545
- var init_costBudget = __esm({
60546
- "src/cli/costBudget.ts"() {
60547
- "use strict";
60548
- SessionBudgetTracker = class {
60549
- constructor(budget = {}) {
60550
- this.budget = budget;
60551
- }
60552
- usedUsd = 0;
60553
- usedTokens = 0;
60554
- get enabled() {
60555
- return this.budget.maxUsd !== void 0 || this.budget.maxTokens !== void 0;
60556
- }
60557
- /** Idempotent per-turn accumulation; ignores NaN/negative inputs. */
60558
- record(delta) {
60559
- if (Number.isFinite(delta.costUsd) && (delta.costUsd ?? 0) > 0) this.usedUsd += delta.costUsd ?? 0;
60560
- if (Number.isFinite(delta.tokens) && (delta.tokens ?? 0) > 0) this.usedTokens += Math.round(delta.tokens ?? 0);
60561
- }
60562
- status() {
60563
- if (!this.enabled) return { state: "off", usedUsd: this.usedUsd, usedTokens: this.usedTokens, pctUsd: null, pctTokens: null };
60564
- const pctUsd = this.budget.maxUsd !== void 0 ? this.usedUsd / this.budget.maxUsd : null;
60565
- const pctTokens = this.budget.maxTokens !== void 0 ? this.usedTokens / this.budget.maxTokens : null;
60566
- const worst = Math.max(pctUsd ?? 0, pctTokens ?? 0);
60567
- const state3 = worst >= 1 ? "hold" : worst >= 0.8 ? "warn" : "ok";
60568
- return { state: state3, usedUsd: round6(this.usedUsd), usedTokens: this.usedTokens, pctUsd, pctTokens };
60569
- }
60570
- /** True when no further provider turn should start. */
60571
- isHold() {
60572
- return this.status().state === "hold";
60573
- }
60574
- };
60575
- }
60576
- });
60577
-
60578
60831
  // src/cli/councilConfig.ts
60579
60832
  function resolveCouncilTier(opts) {
60580
60833
  const env = opts?.env ?? process.env;
@@ -66631,6 +66884,7 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
66631
66884
  emitExtLog(`[extensions] strict load failed: ${extLoad.error.message} \u2014 continuing WITHOUT extensions`);
66632
66885
  }
66633
66886
  }
66887
+ const { defaultPermissionPolicy: defaultPermissionPolicy2 } = await Promise.resolve().then(() => (init_toolPermissions(), toolPermissions_exports));
66634
66888
  const { registry: toolRegistry } = createBuiltinToolRegistry({
66635
66889
  root: cwd,
66636
66890
  onTentacleEvent: (ev) => emitEvent(ev),
@@ -66658,14 +66912,7 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
66658
66912
  ...ev.type === "task_update" ? { task: ev.task } : { tasks: ev.tasks }
66659
66913
  });
66660
66914
  },
66661
- permissionPolicy: {
66662
- read: "allow",
66663
- write: "allow",
66664
- execute: "allow",
66665
- network: "allow",
66666
- ui: "allow",
66667
- auto: true
66668
- },
66915
+ permissionPolicy: defaultPermissionPolicy2(),
66669
66916
  ...nativeMemory ? { memoryService: nativeMemory } : {},
66670
66917
  memoryAutoWrite,
66671
66918
  ...extensionRuntime ? { extensions: extensionRuntime } : {},
@@ -68420,6 +68667,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
68420
68667
  }
68421
68668
  const audit = new AuditLogger2();
68422
68669
  const { runTentacle: runTentacle2 } = await Promise.resolve().then(() => (init_tentacle(), tentacle_exports));
68670
+ const { defaultPermissionPolicy: defaultPermissionPolicy2 } = await Promise.resolve().then(() => (init_toolPermissions(), toolPermissions_exports));
68423
68671
  const executor = new KrakenGraphExecutor2({
68424
68672
  taskToolDeps: {
68425
68673
  createSubAgentContext: createKrakenSubAgentContextFactory2({
@@ -68427,17 +68675,11 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
68427
68675
  audit,
68428
68676
  sessionId: sessionId2,
68429
68677
  // P0.4 capability inheritance: tentacles intersect the headless
68430
- // parent policy. Headless runs are auto-allow (the same literal
68431
- // the main headless registry below uses), so this is a no-op
68432
- // today wired for correctness if that default ever tightens.
68433
- parentPolicy: {
68434
- read: "allow",
68435
- write: "allow",
68436
- execute: "allow",
68437
- network: "allow",
68438
- ui: "allow",
68439
- auto: true
68440
- },
68678
+ // parent policy. Headless now uses the shared preset engine
68679
+ // (defaultPermissionPolicy standard = execute/network ask, and
68680
+ // ask without a UI fails closed), so this intersection actually
68681
+ // bites: tentacles can never exceed the preset.
68682
+ parentPolicy: defaultPermissionPolicy2(),
68441
68683
  // Anchor every tentacle to the SAME provider/model this run
68442
68684
  // resolved (Desktop's selector, or --provider/--model), instead
68443
68685
  // of the persisted provider.json default the factory falls back
@@ -68548,18 +68790,12 @@ function nodeSpineEnvelopeRun(spine, runOpts, run) {
68548
68790
  }
68549
68791
  async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAutoWrite = false, extras) {
68550
68792
  const cwd = opts ? resolveHeadlessCwd(opts) : process.cwd();
68793
+ const { defaultPermissionPolicy: defaultPermissionPolicy2 } = await Promise.resolve().then(() => (init_toolPermissions(), toolPermissions_exports));
68551
68794
  const { registry: toolRegistry } = createBuiltinToolRegistry({
68552
68795
  root: cwd,
68553
68796
  planMode,
68554
68797
  ...extras?.lspProvider ? { lspProvider: extras.lspProvider } : {},
68555
- permissionPolicy: {
68556
- read: "allow",
68557
- write: "allow",
68558
- execute: "allow",
68559
- network: "allow",
68560
- ui: "allow",
68561
- auto: true
68562
- },
68798
+ permissionPolicy: defaultPermissionPolicy2(),
68563
68799
  ...memoryService ? { memoryService } : {},
68564
68800
  memoryAutoWrite
68565
68801
  });
@@ -69025,7 +69261,10 @@ ${ragContext}` : slicePrompt;
69025
69261
  userMessage: opts.task,
69026
69262
  synthesisText: synthesisText || void 0,
69027
69263
  degradedRun: d.degraded,
69028
- degradedReasons: d.reasons
69264
+ degradedReasons: d.reasons,
69265
+ // 2.31 A1: without sessionId the spine-evidence gate in the hook
69266
+ // never fires, leaving headless with the legacy lint heuristic.
69267
+ sessionId: spine.sessionId
69029
69268
  });
69030
69269
  completionOk = hook.completion?.completion?.ok ?? false;
69031
69270
  if (completionOk) {
@@ -69074,17 +69313,11 @@ ${ragContext}` : slicePrompt;
69074
69313
  const { composeProjectContext: composeProjectContext2 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
69075
69314
  const { loadDurableContext: loadDurableContext2 } = await Promise.resolve().then(() => (init_loadDurableContext(), loadDurableContext_exports));
69076
69315
  const { detectDegradedRun: detectDegradedRun2 } = await Promise.resolve().then(() => (init_council(), council_exports));
69316
+ const { defaultPermissionPolicy: defaultPermissionPolicy2 } = await Promise.resolve().then(() => (init_toolPermissions(), toolPermissions_exports));
69077
69317
  const { registry: agentRegistry } = createBuiltinToolRegistry2({
69078
69318
  root: projectRoot,
69079
69319
  planMode: false,
69080
- permissionPolicy: {
69081
- read: "allow",
69082
- write: "allow",
69083
- execute: "allow",
69084
- network: "allow",
69085
- ui: "allow",
69086
- auto: true
69087
- }
69320
+ permissionPolicy: defaultPermissionPolicy2()
69088
69321
  });
69089
69322
  await registerHeadlessMcp(agentRegistry, opts);
69090
69323
  const durableState = await loadDurableContext2(projectRoot);
@@ -69131,7 +69364,9 @@ ${ragContext}` : slicePrompt;
69131
69364
  userMessage: opts.task,
69132
69365
  synthesisText: synthesisText || void 0,
69133
69366
  degradedRun: d.degraded,
69134
- degradedReasons: d.reasons
69367
+ degradedReasons: d.reasons,
69368
+ // 2.31 A1: same fix as the council path — evidence gate needs it.
69369
+ sessionId: spine.sessionId
69135
69370
  });
69136
69371
  if (hook.completion?.completion?.ok) {
69137
69372
  emit(`[zelari] slice completion ok`);
@@ -72138,6 +72373,7 @@ var init_contextGrowthSummary = __esm({
72138
72373
  // src/cli/utils/doctor.ts
72139
72374
  var doctor_exports = {};
72140
72375
  __export(doctor_exports, {
72376
+ collectDoctorReport: () => collectDoctorReport,
72141
72377
  runDoctor: () => runDoctor
72142
72378
  });
72143
72379
  import { execSync as execSync2 } from "node:child_process";
@@ -72403,10 +72639,8 @@ async function checkContextGrowth() {
72403
72639
  return WARN(`metrics unreadable: ${err instanceof Error ? err.message : String(err)}`);
72404
72640
  }
72405
72641
  }
72406
- async function runDoctor() {
72407
- const pkg = readPackageJson4();
72408
- const pkgName = pkg?.name ?? "zelari-code";
72409
- const checks = [
72642
+ function buildDoctorChecks(pkg, pkgName) {
72643
+ return [
72410
72644
  // --- install-health checks (main-process probes) ---
72411
72645
  { name: "node", run: () => checkNode(pkg) },
72412
72646
  { name: "bin shim", run: () => checkShim(pkgName) },
@@ -72485,6 +72719,34 @@ async function runDoctor() {
72485
72719
  // --- optional desktop computer-use (Cua Driver, trycua) ---
72486
72720
  { name: "cua-driver", run: () => checkCuaDriver() }
72487
72721
  ];
72722
+ }
72723
+ async function collectDoctorReport() {
72724
+ const pkg = readPackageJson4();
72725
+ const checks = buildDoctorChecks(pkg, pkg?.name ?? "zelari-code");
72726
+ const entries = [];
72727
+ for (const c of checks) {
72728
+ let result;
72729
+ try {
72730
+ result = await c.run();
72731
+ } catch (err) {
72732
+ result = FAIL(
72733
+ `unexpected error: ${err instanceof Error ? err.message : String(err)}`
72734
+ );
72735
+ }
72736
+ entries.push({
72737
+ name: c.name,
72738
+ ok: result.ok,
72739
+ severity: result.ok ? "none" : result.severity ?? "warn",
72740
+ message: result.message
72741
+ });
72742
+ }
72743
+ const firstRed = entries.find((e) => !e.ok) ?? null;
72744
+ return { entries, firstRed, healthy: entries.every((e) => e.ok) };
72745
+ }
72746
+ async function runDoctor() {
72747
+ const pkg = readPackageJson4();
72748
+ const pkgName = pkg?.name ?? "zelari-code";
72749
+ const checks = buildDoctorChecks(pkg, pkgName);
72488
72750
  console.log(`zelari-code doctor (v${pkg?.version ?? "unknown"})`);
72489
72751
  console.log("platform:", process.platform, process.arch);
72490
72752
  console.log("node: ", process.version);
@@ -73342,12 +73604,13 @@ function StatusBar({
73342
73604
  krakenLive = null,
73343
73605
  krakenGraph = null,
73344
73606
  verify = null,
73345
- permissions = null
73607
+ permissions = null,
73608
+ jail = null
73346
73609
  }) {
73347
73610
  const ctxLabel = contextLimit > 0 ? `${formatTokens(contextUsed)}/${formatTokens(contextLimit)}` : contextUsed > 0 ? formatTokens(contextUsed) : null;
73348
73611
  const modeLabel = mode === "council" ? "council" : mode === "zelari" ? "zelari" : "kraken";
73349
73612
  const modeColor = mode === "council" ? "magenta" : mode === "zelari" ? "green" : "red";
73350
- return /* @__PURE__ */ React6.createElement(Box5, { paddingX: 1, width: "100%", justifyContent: "space-between", gap: 2 }, /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 2 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, /* @__PURE__ */ React6.createElement(Text6, { color: sessionActive ? "green" : "gray" }, sessionActive ? "\u25CF" : "\u25CB"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: phase2 === "plan" ? "yellow" : "green" }, phase2 === "plan" ? "\u25C7 plan" : "\u25C6 build"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: modeColor }, modeLabel), verify ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: verify.tone }, verify.label)) : null, permissions ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: permissions.tone }, permissions.label)) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, provider), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, null, model), cwd ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, cwd)) : null)), /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 1 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, busy && elapsedMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Spinner, { color: "yellow" }), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " ", formatDuration(elapsedMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : lastMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "last ", formatDuration(lastMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, queueCount > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, "queue ", queueCount), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, todoSummary ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, todoSummary), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, krakenLive ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, krakenLive), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, krakenGraph ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, krakenGraph), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, ctxLabel ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, ctxLabel), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, costUsd > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, formatCost(costUsd)), cachedTokens > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (", formatTokens(cachedTokens), " cached)") : null, cacheHitRate > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " ", Math.round(cacheHitRate * 100), "% hit") : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "session ", sessionId2))));
73613
+ return /* @__PURE__ */ React6.createElement(Box5, { paddingX: 1, width: "100%", justifyContent: "space-between", gap: 2 }, /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 2 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, /* @__PURE__ */ React6.createElement(Text6, { color: sessionActive ? "green" : "gray" }, sessionActive ? "\u25CF" : "\u25CB"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: phase2 === "plan" ? "yellow" : "green" }, phase2 === "plan" ? "\u25C7 plan" : "\u25C6 build"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: modeColor }, modeLabel), verify ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: verify.tone }, verify.label)) : null, permissions ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: permissions.tone }, permissions.label)) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), jail ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: jail.tone }, jail.label)) : null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, provider), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, null, model), cwd ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, cwd)) : null)), /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 1 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, busy && elapsedMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Spinner, { color: "yellow" }), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " ", formatDuration(elapsedMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : lastMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "last ", formatDuration(lastMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, queueCount > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, "queue ", queueCount), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, todoSummary ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, todoSummary), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, krakenLive ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, krakenLive), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, krakenGraph ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, krakenGraph), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, ctxLabel ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, ctxLabel), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, costUsd > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, formatCost(costUsd)), cachedTokens > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (", formatTokens(cachedTokens), " cached)") : null, cacheHitRate > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " ", Math.round(cacheHitRate * 100), "% hit") : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "session ", sessionId2))));
73351
73614
  }
73352
73615
 
73353
73616
  // src/cli/components/SelectList.tsx
@@ -73616,71 +73879,8 @@ function useExecutionTimer(busy, tickMs = 1e3) {
73616
73879
  init_paths2();
73617
73880
  init_sessionTodos();
73618
73881
  init_krakenLive();
73619
-
73620
- // src/cli/kraken/verifyStatus.ts
73621
- init_verificationBridge();
73622
- function verifyStateFromGate(evaluation) {
73623
- if (!evaluation.blocked) return "pass";
73624
- const verdict = evaluation.evaluation?.verdict;
73625
- if (verdict === "BLOCKED") return "blocked";
73626
- return "repair";
73627
- }
73628
- function formatVerifyChip(state3) {
73629
- if (state3 === "pass") return { label: "prova: PASS", tone: "green" };
73630
- if (state3 === "blocked") return { label: "prova: BLOCCATO", tone: "red" };
73631
- return { label: "prova: RIPARA", tone: "yellow" };
73632
- }
73633
- function permissionsChip(phase2, strictOn = strictDoneEnabled()) {
73634
- if (phase2 === "plan") return { label: "scrive: no (piano)", tone: "yellow" };
73635
- return strictOn ? { label: "scrive: s\xEC \xB7 prova obbligatoria", tone: "green" } : { label: "scrive: s\xEC \xB7 senza prova (dichiarato)", tone: "yellow" };
73636
- }
73637
- function formatStrictBlockExplanation(evaluation) {
73638
- const state3 = verifyStateFromGate(evaluation);
73639
- const gate = evaluation.gate;
73640
- if (state3 === "pass") {
73641
- return `[verifica] PASS \u2014 ${gate.passed}/${gate.total} prove superate.`;
73642
- }
73643
- const word = state3 === "blocked" ? "BLOCCATO" : "RIPARA";
73644
- const lines = [
73645
- `[verifica] ${word} \u2014 ${gate.passed}/${gate.total} prove superate; il turno non pu\xF2 dichiararsi finito senza prova.`
73646
- ];
73647
- const evalr = evaluation.evaluation ?? null;
73648
- if (evalr) {
73649
- const byId = /* @__PURE__ */ new Map();
73650
- for (const c of evalr.criteria ?? []) byId.set(c.id, c.text);
73651
- const unsatisfied = (evalr.results ?? []).filter((r) => r.status !== "pass");
73652
- if (unsatisfied.length > 0) {
73653
- lines.push("Mancano:");
73654
- for (const r of unsatisfied.slice(0, 8)) {
73655
- lines.push(` \u2022 ${byId.get(r.criterionId) ?? r.criterionId} [${r.status}]`);
73656
- }
73657
- if (unsatisfied.length > 8) {
73658
- lines.push(` \u2022 \u2026 e altri ${unsatisfied.length - 8}`);
73659
- }
73660
- }
73661
- }
73662
- lines.push(
73663
- "La prova \xE8 obbligatoria: chiudo il turno solo quando ogni criterio richiesto ha evidenza."
73664
- );
73665
- return lines.join("\n");
73666
- }
73667
- function recordStrictGateEvaluation(evaluation) {
73668
- const g = globalThis;
73669
- g.__zelariVerifyState = {
73670
- state: verifyStateFromGate(evaluation),
73671
- at: Date.now(),
73672
- passed: evaluation.gate.passed,
73673
- total: evaluation.gate.total
73674
- };
73675
- }
73676
- function getVerifyChip() {
73677
- const g = globalThis;
73678
- const s = g.__zelariVerifyState;
73679
- if (!s) return null;
73680
- return formatVerifyChip(s.state);
73681
- }
73682
-
73683
- // src/cli/app.tsx
73882
+ init_verifyStatus();
73883
+ init_osJail();
73684
73884
  init_graphStatus();
73685
73885
 
73686
73886
  // packages/core/dist/agents/skills/builtin/debugging.js
@@ -76261,6 +76461,7 @@ init_delegationPolicy();
76261
76461
  init_completionGate();
76262
76462
  init_verificationBridge();
76263
76463
  init_completionProof();
76464
+ init_verifyStatus();
76264
76465
  init_nativeVerification();
76265
76466
  init_spineTelemetry();
76266
76467
 
@@ -76468,6 +76669,28 @@ function useChatTurn(params) {
76468
76669
  return;
76469
76670
  }
76470
76671
  }
76672
+ {
76673
+ const { sessionBudgetHoldNotice: sessionBudgetHoldNotice2 } = await Promise.resolve().then(() => (init_costBudget(), costBudget_exports));
76674
+ const hold = sessionBudgetHoldNotice2();
76675
+ if (hold) {
76676
+ appendSystem(setMessages, hold, Date.now());
76677
+ return;
76678
+ }
76679
+ }
76680
+ {
76681
+ const { hasWorkspacePlan: hasWorkspacePlan2 } = await Promise.resolve().then(() => (init_planDetect(), planDetect_exports));
76682
+ const phaseMod = await Promise.resolve().then(() => (init_phaseState(), phaseState_exports));
76683
+ const g = globalThis;
76684
+ if (phaseMod.getPhase() === "build" && !hasWorkspacePlan2(process.cwd()) && !g.__zelariPlanFirstNotice) {
76685
+ g.__zelariPlanFirstNotice = true;
76686
+ phaseMod.setPhase("plan");
76687
+ appendSystem(
76688
+ setMessages,
76689
+ `[permessi] No workspace plan found \u2014 first turn forced to PLAN (no surprise writes). Type /build to switch to BUILD explicitly.`,
76690
+ Date.now()
76691
+ );
76692
+ }
76693
+ }
76471
76694
  setBusy(true);
76472
76695
  const workPhase = getPhase();
76473
76696
  try {
@@ -78516,6 +78739,7 @@ function handleSlashCommand(text, availableSkills) {
78516
78739
  /state restore [id] [--no-tree] \u2014 set HEAD + optional git checkpoint restore
78517
78740
  /cache stats \u2014 prompt-cache hit rate, premium vs cached, stable busts
78518
78741
  /index [status] \u2014 build the semantic code index for semantic_search
78742
+ /verify \u2014 re-evaluate the strict completion gate (deterministic, no LLM; criteria + next command)
78519
78743
  /mode [kraken|council|zelari] \u2014 switch dispatch mode (same as shift+tab; agent=alias kraken)
78520
78744
  /kraken [sessionId] \u2014 show Kraken tentacle radio (last spawns)
78521
78745
  /trust [path] \u2014 trust a folder so project MCP + project hooks load
@@ -79017,6 +79241,9 @@ ${formatSkillList(availableSkills)}`
79017
79241
  case "view-plan": {
79018
79242
  return { handled: true, kind: "view_plan" };
79019
79243
  }
79244
+ case "verify": {
79245
+ return { handled: true, kind: "verify" };
79246
+ }
79020
79247
  case "index": {
79021
79248
  return args[0] === "status" ? { handled: true, kind: "index_status" } : { handled: true, kind: "index_build" };
79022
79249
  }
@@ -81752,6 +81979,21 @@ function useSlashDispatch(params) {
81752
81979
  setInput("");
81753
81980
  return;
81754
81981
  }
81982
+ if (result.kind === "verify") {
81983
+ try {
81984
+ const { evaluateStrictBuildGate: evaluateStrictBuildGate2 } = await Promise.resolve().then(() => (init_verificationBridge(), verificationBridge_exports));
81985
+ const { formatStrictBlockExplanation: formatStrictBlockExplanation2 } = await Promise.resolve().then(() => (init_verifyStatus(), verifyStatus_exports));
81986
+ const evaluation = await evaluateStrictBuildGate2("build");
81987
+ appendSystem(setMessages, `[verify] ${formatStrictBlockExplanation2(evaluation)}`);
81988
+ } catch (err) {
81989
+ appendSystem(
81990
+ setMessages,
81991
+ `[verify] gate unavailable: ${err instanceof Error ? err.message : String(err)}`
81992
+ );
81993
+ }
81994
+ setInput("");
81995
+ return;
81996
+ }
81755
81997
  if (result.kind === "promote_member" && result.promoteMemberId) {
81756
81998
  await handlePromoteMember(baseCtx, result.promoteMemberId);
81757
81999
  setInput("");
@@ -81940,6 +82182,15 @@ function useTerminalSize(options = {}) {
81940
82182
  // src/cli/app.tsx
81941
82183
  init_chatStats();
81942
82184
  init_duration();
82185
+ function jailStatusChip() {
82186
+ const mode = activeJailMode();
82187
+ if (mode === "off") return null;
82188
+ const probe = probeJailBackend();
82189
+ if (!probe.available) {
82190
+ return { label: `jail: advisory (${probe.backend})`, tone: "yellow" };
82191
+ }
82192
+ return mode === "required" ? { label: `jail: on (${probe.backend})`, tone: "green" } : { label: `jail: advisory (${probe.backend})`, tone: "yellow" };
82193
+ }
81943
82194
  var MODEL = process.env.OPENAI_MODEL ?? "grok-4.5";
81944
82195
  var providerDefaults = {
81945
82196
  "openai-compatible": "grok-4.5",
@@ -82155,7 +82406,8 @@ function App() {
82155
82406
  krakenLive: formatKrakenLiveSummary() ?? void 0,
82156
82407
  krakenGraph: formatKrakenGraphSummary() ?? void 0,
82157
82408
  verify: getVerifyChip() ?? void 0,
82158
- permissions: permissionsChip(phase2)
82409
+ permissions: permissionsChip(phase2),
82410
+ jail: jailStatusChip()
82159
82411
  }
82160
82412
  )), sidebarOpen && /* @__PURE__ */ React10.createElement(Sidebar, { version: VERSION, changes: gitChanges, rows: size.rows })));
82161
82413
  }
@@ -83234,6 +83486,14 @@ function pickRootComponent() {
83234
83486
  return { kind: "done" };
83235
83487
  }
83236
83488
  if (argv.includes("--doctor") || argv.includes("doctor")) {
83489
+ if (argv.includes("--json")) {
83490
+ const { collectDoctorReport: collectDoctorReport2 } = (init_doctor(), __toCommonJS(doctor_exports));
83491
+ void collectDoctorReport2().then((report) => {
83492
+ console.log(JSON.stringify(report));
83493
+ process.exit(report.healthy ? 0 : 1);
83494
+ });
83495
+ return { kind: "done" };
83496
+ }
83237
83497
  const { runDoctor: runDoctor2 } = (init_doctor(), __toCommonJS(doctor_exports));
83238
83498
  void runDoctor2().then((healthy) => process.exit(healthy ? 0 : 1));
83239
83499
  return { kind: "done" };
@@ -83353,7 +83613,7 @@ proposals: npm run evolve:propose \u2014 decisions in npm run evolve:decide (P1:
83353
83613
  }
83354
83614
  if (argv.includes("--help") || argv.includes("-h")) {
83355
83615
  console.log(
83356
- "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --task-file <path> Read the task prompt from a file (Windows argv cap)\n --once Single-cycle run (cron/git-hook triggers, ADR-0014)\n --kraken-graph <goal> Plan + execute a Kraken task graph\n (variant: --kraken-graph-file <path>; kill-switch\n ZELARI_KRAKEN_GRAPH=0)\n --plan-only Serialize the graph plan to .zelari/radio/ and exit 0\n --run-plan <id> Execute a pre-built .zelari/radio/plan-<id>.json\n --gauntlet Host-driven gauntlet loop (builder/critic tentacles)\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --serve-harness Long-lived harness kernel for hosts (NDJSON JSON-RPC\n on stdin/stdout; Desktop/companion transport)\n --print-config Print provider/model config as JSON (no secrets)\n --print-settings Print zelari.config.json values + the origin of\n each (default < user < project < env)\n --permissions <p> Permission preset: strict | standard | yolo \u2014 changes\n category DEFAULTS only (env vars and policy files win)\n --evolve-status Evolution ledger stats (read-only; ADR-0036; the\n ledger is written only when ZELARI_EVOLUTION=shadow)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable for stdio servers\n --url <endpoint> Streamable HTTP endpoint (e.g. UE 5.8 editor)\n --args <json> JSON array of args (stdio, optional)\n --timeout <ms> Per-server request timeout (http, optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --preset unreal-mcp Unreal Engine 5.8+ editor (MCP over HTTP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ZELARI_MEMORY_MCP=1 Enable the optional external memory MCP server\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
83616
+ "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor [--json] Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --task-file <path> Read the task prompt from a file (Windows argv cap)\n --once Single-cycle run (cron/git-hook triggers, ADR-0014)\n --kraken-graph <goal> Plan + execute a Kraken task graph\n (variant: --kraken-graph-file <path>; kill-switch\n ZELARI_KRAKEN_GRAPH=0)\n --plan-only Serialize the graph plan to .zelari/radio/ and exit 0\n --run-plan <id> Execute a pre-built .zelari/radio/plan-<id>.json\n --gauntlet Host-driven gauntlet loop (builder/critic tentacles)\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --serve-harness Long-lived harness kernel for hosts (NDJSON JSON-RPC\n on stdin/stdout; Desktop/companion transport)\n --print-config Print provider/model config as JSON (no secrets)\n --print-settings Print zelari.config.json values + the origin of\n each (default < user < project < env)\n --permissions <p> Permission preset: strict | standard | yolo \u2014 changes\n category DEFAULTS only (env vars and policy files win)\n --evolve-status Evolution ledger stats (read-only; ADR-0036; the\n ledger is written only when ZELARI_EVOLUTION=shadow)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable for stdio servers\n --url <endpoint> Streamable HTTP endpoint (e.g. UE 5.8 editor)\n --args <json> JSON array of args (stdio, optional)\n --timeout <ms> Per-server request timeout (http, optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --preset unreal-mcp Unreal Engine 5.8+ editor (MCP over HTTP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ZELARI_MEMORY_MCP=1 Enable the optional external memory MCP server\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
83357
83617
  );
83358
83618
  process.exit(0);
83359
83619
  }
@@ -83913,19 +84173,57 @@ function main() {
83913
84173
  });
83914
84174
  return;
83915
84175
  }
83916
- const { waitUntilExit, unmount } = render(picked.element);
83917
- process.on("SIGINT", () => {
83918
- unmount();
83919
- void shutdown();
83920
- });
83921
- process.on("SIGTERM", () => {
83922
- unmount();
83923
- void shutdown();
83924
- });
83925
- void backgroundUpdateCheck();
83926
- waitUntilExit().then(() => {
83927
- void shutdown();
84176
+ void (async () => {
84177
+ await runFirstRunDoctorGate();
84178
+ const { waitUntilExit, unmount } = render(picked.element);
84179
+ process.on("SIGINT", () => {
84180
+ unmount();
84181
+ void shutdown();
84182
+ });
84183
+ process.on("SIGTERM", () => {
84184
+ unmount();
84185
+ void shutdown();
84186
+ });
84187
+ void backgroundUpdateCheck();
84188
+ waitUntilExit().then(() => {
84189
+ void shutdown();
84190
+ });
84191
+ })();
84192
+ }
84193
+ async function runFirstRunDoctorGate() {
84194
+ try {
84195
+ const { getActiveProvider: getActiveProvider2 } = await Promise.resolve().then(() => (init_providerConfig(), providerConfig_exports));
84196
+ const { resolveApiKey: resolveApiKey2, getOAuthToken: getOAuthToken2 } = await Promise.resolve().then(() => (init_keyStore(), keyStore_exports));
84197
+ const active = getActiveProvider2();
84198
+ if (resolveApiKey2(active.id) || getOAuthToken2(active.id)) return;
84199
+ } catch {
84200
+ return;
84201
+ }
84202
+ const { collectDoctorReport: collectDoctorReport2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
84203
+ const report = await collectDoctorReport2();
84204
+ if (report.healthy) return;
84205
+ const red = report.firstRed;
84206
+ const line = (s) => process.stderr.write(s + "\n");
84207
+ line("");
84208
+ line("\u250C\u2500 first run: doctor");
84209
+ line(`\u2502 \u2717 ${red.name} \u2014 ${red.message.replace(/\n/g, "\n\u2502 ")}`);
84210
+ line("\u2502 Fix the red above (its message names the exact command), then re-run:");
84211
+ line("\u2502 zelari-code --doctor");
84212
+ line("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
84213
+ if (!process.stdin.isTTY) {
84214
+ line("[wizard] non-interactive session: continuing with a RED doctor (dichiarato).");
84215
+ return;
84216
+ }
84217
+ const rl = (await import("node:readline/promises")).createInterface({
84218
+ input: process.stdin,
84219
+ output: process.stdout
83928
84220
  });
84221
+ const answer = (await rl.question('Continue anyway with a RED doctor? Type "si" to continue: ')).trim().toLowerCase();
84222
+ rl.close();
84223
+ if (answer !== "si" && answer !== "s\xEC" && answer !== "yes" && answer !== "y") {
84224
+ process.exit(1);
84225
+ }
84226
+ line("[wizard] continue-anyway dichiarato \u2014 doctor is still red.");
83929
84227
  }
83930
84228
  main();
83931
84229
  export {