zelari-code 2.30.0 → 2.31.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.
@@ -37549,7 +37549,7 @@ var CORE_VERSION;
37549
37549
  var init_version = __esm({
37550
37550
  "packages/core/dist/version.js"() {
37551
37551
  "use strict";
37552
- CORE_VERSION = "2.30.0";
37552
+ CORE_VERSION = "2.31.0";
37553
37553
  }
37554
37554
  });
37555
37555
 
@@ -57039,6 +57039,113 @@ var init_claudeProvider = __esm({
57039
57039
  }
57040
57040
  });
57041
57041
 
57042
+ // src/cli/costBudget.ts
57043
+ var costBudget_exports = {};
57044
+ __export(costBudget_exports, {
57045
+ SessionBudgetTracker: () => SessionBudgetTracker,
57046
+ budgetChip: () => budgetChip,
57047
+ processSessionBudget: () => processSessionBudget,
57048
+ resetProcessSessionBudget: () => resetProcessSessionBudget,
57049
+ resolveSessionBudget: () => resolveSessionBudget,
57050
+ sessionBudgetHoldNotice: () => sessionBudgetHoldNotice
57051
+ });
57052
+ function resolveSessionBudget(env = process.env) {
57053
+ const maxUsd = parsePositiveFloat(env.ZELARI_SESSION_BUDGET_USD);
57054
+ const maxTokens = parsePositiveInt(env.ZELARI_SESSION_BUDGET_TOKENS);
57055
+ if (maxUsd === void 0 && maxTokens === void 0) return {};
57056
+ return { ...maxUsd !== void 0 ? { maxUsd } : {}, ...maxTokens !== void 0 ? { maxTokens } : {} };
57057
+ }
57058
+ function parsePositiveFloat(raw) {
57059
+ if (!raw) return void 0;
57060
+ const n = Number.parseFloat(raw);
57061
+ return Number.isFinite(n) && n > 0 ? n : void 0;
57062
+ }
57063
+ function parsePositiveInt(raw) {
57064
+ if (!raw) return void 0;
57065
+ const n = Number.parseInt(raw, 10);
57066
+ return Number.isFinite(n) && n > 0 ? n : void 0;
57067
+ }
57068
+ function round6(value) {
57069
+ return Math.round(value * 1e6) / 1e6;
57070
+ }
57071
+ function processSessionBudget(env = process.env) {
57072
+ if (!processTracker) processTracker = new SessionBudgetTracker(resolveSessionBudget(env));
57073
+ return processTracker;
57074
+ }
57075
+ function resetProcessSessionBudget() {
57076
+ processTracker = void 0;
57077
+ }
57078
+ function sessionBudgetHoldNotice(env = process.env) {
57079
+ const budget = processSessionBudget(env);
57080
+ if (!budget.isHold()) return null;
57081
+ const s = budget.status();
57082
+ 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.`;
57083
+ }
57084
+ function budgetChip(status) {
57085
+ if (status.state === "off") return null;
57086
+ const pct = Math.max(status.pctUsd ?? 0, status.pctTokens ?? 0);
57087
+ const label = status.state === "hold" ? "budget HOLD" : `budget ${Math.min(999, Math.round(pct * 100))}%`;
57088
+ const tone = status.state === "hold" ? "red" : status.state === "warn" ? "yellow" : "green";
57089
+ return { label, tone };
57090
+ }
57091
+ var SessionBudgetTracker, processTracker;
57092
+ var init_costBudget = __esm({
57093
+ "src/cli/costBudget.ts"() {
57094
+ "use strict";
57095
+ SessionBudgetTracker = class {
57096
+ constructor(budget = {}) {
57097
+ this.budget = budget;
57098
+ }
57099
+ usedUsd = 0;
57100
+ usedTokens = 0;
57101
+ get enabled() {
57102
+ return this.budget.maxUsd !== void 0 || this.budget.maxTokens !== void 0;
57103
+ }
57104
+ /** Idempotent per-turn accumulation; ignores NaN/negative inputs. */
57105
+ record(delta) {
57106
+ if (Number.isFinite(delta.costUsd) && (delta.costUsd ?? 0) > 0) this.usedUsd += delta.costUsd ?? 0;
57107
+ if (Number.isFinite(delta.tokens) && (delta.tokens ?? 0) > 0) this.usedTokens += Math.round(delta.tokens ?? 0);
57108
+ }
57109
+ status() {
57110
+ if (!this.enabled) return { state: "off", usedUsd: this.usedUsd, usedTokens: this.usedTokens, pctUsd: null, pctTokens: null };
57111
+ const pctUsd = this.budget.maxUsd !== void 0 ? this.usedUsd / this.budget.maxUsd : null;
57112
+ const pctTokens = this.budget.maxTokens !== void 0 ? this.usedTokens / this.budget.maxTokens : null;
57113
+ const worst = Math.max(pctUsd ?? 0, pctTokens ?? 0);
57114
+ const state3 = worst >= 1 ? "hold" : worst >= 0.8 ? "warn" : "ok";
57115
+ return { state: state3, usedUsd: round6(this.usedUsd), usedTokens: this.usedTokens, pctUsd, pctTokens };
57116
+ }
57117
+ /** True when no further provider turn should start. */
57118
+ isHold() {
57119
+ return this.status().state === "hold";
57120
+ }
57121
+ };
57122
+ }
57123
+ });
57124
+
57125
+ // src/cli/workspace/planDetect.ts
57126
+ var planDetect_exports = {};
57127
+ __export(planDetect_exports, {
57128
+ hasWorkspacePlan: () => hasWorkspacePlan
57129
+ });
57130
+ import { existsSync as existsSync35, readFileSync as readFileSync25 } from "node:fs";
57131
+ import { join as join23 } from "node:path";
57132
+ function hasWorkspacePlan(projectRoot = process.cwd()) {
57133
+ const planPath = join23(resolveWorkspaceRoot(projectRoot), "plan.json");
57134
+ if (!existsSync35(planPath)) return false;
57135
+ try {
57136
+ const parsed = JSON.parse(readFileSync25(planPath, "utf8"));
57137
+ return Array.isArray(parsed.phases) && parsed.phases.length > 0;
57138
+ } catch {
57139
+ return false;
57140
+ }
57141
+ }
57142
+ var init_planDetect = __esm({
57143
+ "src/cli/workspace/planDetect.ts"() {
57144
+ "use strict";
57145
+ init_paths3();
57146
+ }
57147
+ });
57148
+
57042
57149
  // src/cli/memory/legacyImport.ts
57043
57150
  import { createHash as createHash18 } from "node:crypto";
57044
57151
  import { promises as fs27 } from "node:fs";
@@ -57241,7 +57348,7 @@ var init_sqliteCodec = __esm({
57241
57348
  });
57242
57349
 
57243
57350
  // src/cli/memory/sqliteRpc.ts
57244
- import { existsSync as existsSync35 } from "node:fs";
57351
+ import { existsSync as existsSync36 } from "node:fs";
57245
57352
  import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
57246
57353
  import * as path67 from "node:path";
57247
57354
  import { Worker } from "node:worker_threads";
@@ -57254,7 +57361,7 @@ function isBusy(error51) {
57254
57361
  function resolveWorkerUrl() {
57255
57362
  const here = path67.dirname(fileURLToPath2(import.meta.url));
57256
57363
  const direct = path67.join(here, "sqliteWorker.mjs");
57257
- if (existsSync35(direct)) return pathToFileURL2(direct);
57364
+ if (existsSync36(direct)) return pathToFileURL2(direct);
57258
57365
  return pathToFileURL2(path67.join(here, "memory", "sqliteWorker.mjs"));
57259
57366
  }
57260
57367
  var SqliteWorkerRpc;
@@ -58261,14 +58368,14 @@ var init_serviceFactory = __esm({
58261
58368
  });
58262
58369
 
58263
58370
  // src/cli/workspace/projectInstructions.ts
58264
- import { existsSync as existsSync36, readFileSync as readFileSync25 } from "node:fs";
58265
- import { join as join26 } from "node:path";
58371
+ import { existsSync as existsSync37, readFileSync as readFileSync26 } from "node:fs";
58372
+ import { join as join27 } from "node:path";
58266
58373
  function loadProjectInstructions(projectRoot = process.cwd(), maxChars = MAX_CHARS) {
58267
58374
  for (const name of CANDIDATES) {
58268
- const full = join26(projectRoot, name);
58269
- if (!existsSync36(full)) continue;
58375
+ const full = join27(projectRoot, name);
58376
+ if (!existsSync37(full)) continue;
58270
58377
  try {
58271
- let raw = readFileSync25(full, "utf8");
58378
+ let raw = readFileSync26(full, "utf8");
58272
58379
  raw = raw.replace(/\r\n/g, "\n").trim();
58273
58380
  if (!raw) continue;
58274
58381
  if (raw.length <= maxChars) {
@@ -58312,8 +58419,8 @@ __export(workspaceSummary_exports, {
58312
58419
  buildWorkspaceSummary: () => buildWorkspaceSummary,
58313
58420
  buildZelariReadHint: () => buildZelariReadHint
58314
58421
  });
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";
58422
+ import { existsSync as existsSync38, readFileSync as readFileSync27, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
58423
+ import { join as join28, relative as relative2 } from "node:path";
58317
58424
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
58318
58425
  const { maxEntries = 30, maxChars = 3500, maxDeps = 24, maxScripts = 16 } = options;
58319
58426
  const name = safeProjectName(projectRoot);
@@ -58346,11 +58453,11 @@ function formatTaskLine(t) {
58346
58453
  }
58347
58454
  function buildPlanSummary(projectRoot = process.cwd(), options) {
58348
58455
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
58349
- const planPath = join27(zelariRoot, "plan.json");
58350
- if (!existsSync37(planPath)) return null;
58456
+ const planPath = join28(zelariRoot, "plan.json");
58457
+ if (!existsSync38(planPath)) return null;
58351
58458
  let plan;
58352
58459
  try {
58353
- plan = JSON.parse(readFileSync26(planPath, "utf8"));
58460
+ plan = JSON.parse(readFileSync27(planPath, "utf8"));
58354
58461
  } catch {
58355
58462
  return null;
58356
58463
  }
@@ -58489,8 +58596,8 @@ function pickNextTask(open2) {
58489
58596
  )[0];
58490
58597
  }
58491
58598
  function buildZelariReadHint(projectRoot = process.cwd()) {
58492
- const planPath = join27(resolveWorkspaceRoot(projectRoot), "plan.json");
58493
- if (!existsSync37(planPath)) return "";
58599
+ const planPath = join28(resolveWorkspaceRoot(projectRoot), "plan.json");
58600
+ if (!existsSync38(planPath)) return "";
58494
58601
  return [
58495
58602
  "# Council workspace detected (.zelari/) \u2014 DRAFT vault",
58496
58603
  "`.zelari/plan.json` and `.zelari/docs/` hold **design hypotheses**, not verified product state.",
@@ -58505,10 +58612,10 @@ function safeProjectName(root) {
58505
58612
  }
58506
58613
  }
58507
58614
  function readPackageJson2(projectRoot) {
58508
- const p3 = join27(projectRoot, "package.json");
58509
- if (!existsSync37(p3)) return null;
58615
+ const p3 = join28(projectRoot, "package.json");
58616
+ if (!existsSync38(p3)) return null;
58510
58617
  try {
58511
- return JSON.parse(readFileSync26(p3, "utf8"));
58618
+ return JSON.parse(readFileSync27(p3, "utf8"));
58512
58619
  } catch {
58513
58620
  return null;
58514
58621
  }
@@ -58558,11 +58665,11 @@ function listShallow(projectRoot, maxEntries) {
58558
58665
  out.push(`\u2026 (+${top.length - count} more)`);
58559
58666
  break;
58560
58667
  }
58561
- const rel2 = relative2(projectRoot, join27(projectRoot, entry.name));
58668
+ const rel2 = relative2(projectRoot, join28(projectRoot, entry.name));
58562
58669
  if (entry.isDirectory()) {
58563
58670
  let inner = "";
58564
58671
  try {
58565
- const sub = readdirSync7(join27(projectRoot, entry.name), {
58672
+ const sub = readdirSync7(join28(projectRoot, entry.name), {
58566
58673
  withFileTypes: true
58567
58674
  }).filter((e) => !e.name.startsWith(".")).slice(0, 4).map((e) => e.name);
58568
58675
  if (sub.length > 0)
@@ -58610,12 +58717,12 @@ var init_workspaceSummary = __esm({
58610
58717
  });
58611
58718
 
58612
58719
  // src/cli/workspace/buildLessonsSummary.ts
58613
- import { existsSync as existsSync38 } from "node:fs";
58614
- import { join as join28 } from "node:path";
58720
+ import { existsSync as existsSync39 } from "node:fs";
58721
+ import { join as join29 } from "node:path";
58615
58722
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
58616
58723
  if (process.env["ZELARI_LESSONS"] === "0") return null;
58617
58724
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
58618
- if (!existsSync38(join28(zelariRoot, "lessons.jsonl"))) return null;
58725
+ if (!existsSync39(join29(zelariRoot, "lessons.jsonl"))) return null;
58619
58726
  const lessons = recallLessons(zelariRoot, {
58620
58727
  maxLessons: 5,
58621
58728
  maxBytes: 2048,
@@ -58636,8 +58743,8 @@ var composeContext_exports = {};
58636
58743
  __export(composeContext_exports, {
58637
58744
  composeProjectContext: () => composeProjectContext
58638
58745
  });
58639
- import { existsSync as existsSync39, readdirSync as readdirSync8, readFileSync as readFileSync27 } from "node:fs";
58640
- import { join as join29 } from "node:path";
58746
+ import { existsSync as existsSync40, readdirSync as readdirSync8, readFileSync as readFileSync28 } from "node:fs";
58747
+ import { join as join30 } from "node:path";
58641
58748
  function cap2(text, max, label) {
58642
58749
  if (!text || text.length <= max) return { text: text || "", truncated: false };
58643
58750
  return {
@@ -58649,13 +58756,13 @@ function cap2(text, max, label) {
58649
58756
  }
58650
58757
  function buildDesignIndex(projectRoot, maxChars) {
58651
58758
  const root = resolveWorkspaceRoot(projectRoot);
58652
- if (!existsSync39(root)) return "";
58759
+ if (!existsSync40(root)) return "";
58653
58760
  const lines = [
58654
58761
  "# Design vault index (.zelari/) \u2014 HYPOTHESES only",
58655
58762
  "Full design docs are NOT product source of truth. Open with list_files / read_file / searchDocuments if needed."
58656
58763
  ];
58657
- const docsDir = join29(root, "docs");
58658
- if (existsSync39(docsDir)) {
58764
+ const docsDir = join30(root, "docs");
58765
+ if (existsSync40(docsDir)) {
58659
58766
  try {
58660
58767
  const docs = readdirSync8(docsDir).filter((n) => n.endsWith(".md")).slice(0, 12);
58661
58768
  if (docs.length > 0) {
@@ -58669,12 +58776,12 @@ function buildDesignIndex(projectRoot, maxChars) {
58669
58776
  }
58670
58777
  }
58671
58778
  for (const name of ["risks.md", "plan.json", "nfr-spec.json"]) {
58672
- if (existsSync39(join29(root, name))) {
58779
+ if (existsSync40(join30(root, name))) {
58673
58780
  lines.push(`- .zelari/${name} present`);
58674
58781
  }
58675
58782
  }
58676
- const decisionsDir = join29(root, "decisions");
58677
- if (existsSync39(decisionsDir)) {
58783
+ const decisionsDir = join30(root, "decisions");
58784
+ if (existsSync40(decisionsDir)) {
58678
58785
  try {
58679
58786
  const n = readdirSync8(decisionsDir).filter((f) => f.endsWith(".md")).length;
58680
58787
  if (n > 0) lines.push(`- .zelari/decisions/ (${n} ADR file(s) \u2014 treat proposed as non-binding)`);
@@ -58775,17 +58882,17 @@ function composeProjectContext(input) {
58775
58882
  }
58776
58883
  function readDurableHeadSync(projectRoot) {
58777
58884
  try {
58778
- const headPath = join29(projectRoot, ".zelari", "state", "HEAD.json");
58779
- if (!existsSync39(headPath)) return "";
58780
- const head = JSON.parse(readFileSync27(headPath, "utf8"));
58885
+ const headPath = join30(projectRoot, ".zelari", "state", "HEAD.json");
58886
+ if (!existsSync40(headPath)) return "";
58887
+ const head = JSON.parse(readFileSync28(headPath, "utf8"));
58781
58888
  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");
58889
+ const metaPath = join30(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
58890
+ if (!existsSync40(metaPath)) return "";
58891
+ const meta3 = JSON.parse(readFileSync28(metaPath, "utf8"));
58892
+ const discPath = meta3.artifactDir ? join30(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join30(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
58786
58893
  let discoveries = [];
58787
- if (existsSync39(discPath)) {
58788
- discoveries = JSON.parse(readFileSync27(discPath, "utf8"));
58894
+ if (existsSync40(discPath)) {
58895
+ discoveries = JSON.parse(readFileSync28(discPath, "utf8"));
58789
58896
  }
58790
58897
  const reusable = discoveries.filter((d) => d.reusable !== false);
58791
58898
  const lines = [
@@ -58813,30 +58920,6 @@ var init_composeContext = __esm({
58813
58920
  }
58814
58921
  });
58815
58922
 
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
58923
  // src/cli/state/loadDurableContext.ts
58841
58924
  var loadDurableContext_exports = {};
58842
58925
  __export(loadDurableContext_exports, {
@@ -60499,82 +60582,6 @@ var init_mcpManager = __esm({
60499
60582
  }
60500
60583
  });
60501
60584
 
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
60585
  // src/cli/councilConfig.ts
60579
60586
  function resolveCouncilTier(opts) {
60580
60587
  const env = opts?.env ?? process.env;
@@ -69025,7 +69032,10 @@ ${ragContext}` : slicePrompt;
69025
69032
  userMessage: opts.task,
69026
69033
  synthesisText: synthesisText || void 0,
69027
69034
  degradedRun: d.degraded,
69028
- degradedReasons: d.reasons
69035
+ degradedReasons: d.reasons,
69036
+ // 2.31 A1: without sessionId the spine-evidence gate in the hook
69037
+ // never fires, leaving headless with the legacy lint heuristic.
69038
+ sessionId: spine.sessionId
69029
69039
  });
69030
69040
  completionOk = hook.completion?.completion?.ok ?? false;
69031
69041
  if (completionOk) {
@@ -69131,7 +69141,9 @@ ${ragContext}` : slicePrompt;
69131
69141
  userMessage: opts.task,
69132
69142
  synthesisText: synthesisText || void 0,
69133
69143
  degradedRun: d.degraded,
69134
- degradedReasons: d.reasons
69144
+ degradedReasons: d.reasons,
69145
+ // 2.31 A1: same fix as the council path — evidence gate needs it.
69146
+ sessionId: spine.sessionId
69135
69147
  });
69136
69148
  if (hook.completion?.completion?.ok) {
69137
69149
  emit(`[zelari] slice completion ok`);
@@ -72138,6 +72150,7 @@ var init_contextGrowthSummary = __esm({
72138
72150
  // src/cli/utils/doctor.ts
72139
72151
  var doctor_exports = {};
72140
72152
  __export(doctor_exports, {
72153
+ collectDoctorReport: () => collectDoctorReport,
72141
72154
  runDoctor: () => runDoctor
72142
72155
  });
72143
72156
  import { execSync as execSync2 } from "node:child_process";
@@ -72403,10 +72416,8 @@ async function checkContextGrowth() {
72403
72416
  return WARN(`metrics unreadable: ${err instanceof Error ? err.message : String(err)}`);
72404
72417
  }
72405
72418
  }
72406
- async function runDoctor() {
72407
- const pkg = readPackageJson4();
72408
- const pkgName = pkg?.name ?? "zelari-code";
72409
- const checks = [
72419
+ function buildDoctorChecks(pkg, pkgName) {
72420
+ return [
72410
72421
  // --- install-health checks (main-process probes) ---
72411
72422
  { name: "node", run: () => checkNode(pkg) },
72412
72423
  { name: "bin shim", run: () => checkShim(pkgName) },
@@ -72485,6 +72496,34 @@ async function runDoctor() {
72485
72496
  // --- optional desktop computer-use (Cua Driver, trycua) ---
72486
72497
  { name: "cua-driver", run: () => checkCuaDriver() }
72487
72498
  ];
72499
+ }
72500
+ async function collectDoctorReport() {
72501
+ const pkg = readPackageJson4();
72502
+ const checks = buildDoctorChecks(pkg, pkg?.name ?? "zelari-code");
72503
+ const entries = [];
72504
+ for (const c of checks) {
72505
+ let result;
72506
+ try {
72507
+ result = await c.run();
72508
+ } catch (err) {
72509
+ result = FAIL(
72510
+ `unexpected error: ${err instanceof Error ? err.message : String(err)}`
72511
+ );
72512
+ }
72513
+ entries.push({
72514
+ name: c.name,
72515
+ ok: result.ok,
72516
+ severity: result.ok ? "none" : result.severity ?? "warn",
72517
+ message: result.message
72518
+ });
72519
+ }
72520
+ const firstRed = entries.find((e) => !e.ok) ?? null;
72521
+ return { entries, firstRed, healthy: entries.every((e) => e.ok) };
72522
+ }
72523
+ async function runDoctor() {
72524
+ const pkg = readPackageJson4();
72525
+ const pkgName = pkg?.name ?? "zelari-code";
72526
+ const checks = buildDoctorChecks(pkg, pkgName);
72488
72527
  console.log(`zelari-code doctor (v${pkg?.version ?? "unknown"})`);
72489
72528
  console.log("platform:", process.platform, process.arch);
72490
72529
  console.log("node: ", process.version);
@@ -73657,6 +73696,10 @@ function formatStrictBlockExplanation(evaluation) {
73657
73696
  if (unsatisfied.length > 8) {
73658
73697
  lines.push(` \u2022 \u2026 e altri ${unsatisfied.length - 8}`);
73659
73698
  }
73699
+ const first = byId.get(unsatisfied[0].criterionId) ?? unsatisfied[0].criterionId;
73700
+ lines.push(
73701
+ 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.`
73702
+ );
73660
73703
  }
73661
73704
  }
73662
73705
  lines.push(
@@ -76468,6 +76511,28 @@ function useChatTurn(params) {
76468
76511
  return;
76469
76512
  }
76470
76513
  }
76514
+ {
76515
+ const { sessionBudgetHoldNotice: sessionBudgetHoldNotice2 } = await Promise.resolve().then(() => (init_costBudget(), costBudget_exports));
76516
+ const hold = sessionBudgetHoldNotice2();
76517
+ if (hold) {
76518
+ appendSystem(setMessages, hold, Date.now());
76519
+ return;
76520
+ }
76521
+ }
76522
+ {
76523
+ const { hasWorkspacePlan: hasWorkspacePlan2 } = await Promise.resolve().then(() => (init_planDetect(), planDetect_exports));
76524
+ const phaseMod = await Promise.resolve().then(() => (init_phaseState(), phaseState_exports));
76525
+ const g = globalThis;
76526
+ if (phaseMod.getPhase() === "build" && !hasWorkspacePlan2(process.cwd()) && !g.__zelariPlanFirstNotice) {
76527
+ g.__zelariPlanFirstNotice = true;
76528
+ phaseMod.setPhase("plan");
76529
+ appendSystem(
76530
+ setMessages,
76531
+ `[permessi] No workspace plan found \u2014 first turn forced to PLAN (no surprise writes). Type /build to switch to BUILD explicitly.`,
76532
+ Date.now()
76533
+ );
76534
+ }
76535
+ }
76471
76536
  setBusy(true);
76472
76537
  const workPhase = getPhase();
76473
76538
  try {
@@ -83913,19 +83978,57 @@ function main() {
83913
83978
  });
83914
83979
  return;
83915
83980
  }
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();
83981
+ void (async () => {
83982
+ await runFirstRunDoctorGate();
83983
+ const { waitUntilExit, unmount } = render(picked.element);
83984
+ process.on("SIGINT", () => {
83985
+ unmount();
83986
+ void shutdown();
83987
+ });
83988
+ process.on("SIGTERM", () => {
83989
+ unmount();
83990
+ void shutdown();
83991
+ });
83992
+ void backgroundUpdateCheck();
83993
+ waitUntilExit().then(() => {
83994
+ void shutdown();
83995
+ });
83996
+ })();
83997
+ }
83998
+ async function runFirstRunDoctorGate() {
83999
+ try {
84000
+ const { getActiveProvider: getActiveProvider2 } = await Promise.resolve().then(() => (init_providerConfig(), providerConfig_exports));
84001
+ const { resolveApiKey: resolveApiKey2, getOAuthToken: getOAuthToken2 } = await Promise.resolve().then(() => (init_keyStore(), keyStore_exports));
84002
+ const active = getActiveProvider2();
84003
+ if (resolveApiKey2(active.id) || getOAuthToken2(active.id)) return;
84004
+ } catch {
84005
+ return;
84006
+ }
84007
+ const { collectDoctorReport: collectDoctorReport2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
84008
+ const report = await collectDoctorReport2();
84009
+ if (report.healthy) return;
84010
+ const red = report.firstRed;
84011
+ const line = (s) => process.stderr.write(s + "\n");
84012
+ line("");
84013
+ line("\u250C\u2500 first run: doctor");
84014
+ line(`\u2502 \u2717 ${red.name} \u2014 ${red.message.replace(/\n/g, "\n\u2502 ")}`);
84015
+ line("\u2502 Fix the red above (its message names the exact command), then re-run:");
84016
+ line("\u2502 zelari-code --doctor");
84017
+ line("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
84018
+ if (!process.stdin.isTTY) {
84019
+ line("[wizard] non-interactive session: continuing with a RED doctor (dichiarato).");
84020
+ return;
84021
+ }
84022
+ const rl = (await import("node:readline/promises")).createInterface({
84023
+ input: process.stdin,
84024
+ output: process.stdout
83928
84025
  });
84026
+ const answer = (await rl.question('Continue anyway with a RED doctor? Type "si" to continue: ')).trim().toLowerCase();
84027
+ rl.close();
84028
+ if (answer !== "si" && answer !== "s\xEC" && answer !== "yes" && answer !== "y") {
84029
+ process.exit(1);
84030
+ }
84031
+ line("[wizard] continue-anyway dichiarato \u2014 doctor is still red.");
83929
84032
  }
83930
84033
  main();
83931
84034
  export {