zelari-code 1.1.1 → 1.2.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.
@@ -308,7 +308,7 @@ async function refreshGrokToken(options) {
308
308
  return parseTokenResponseBody(obj, accessToken);
309
309
  }
310
310
  async function openBrowser(url2) {
311
- const { spawn: spawn6 } = await import("node:child_process");
311
+ const { spawn: spawn7 } = await import("node:child_process");
312
312
  const cmd = (() => {
313
313
  switch (process.platform) {
314
314
  case "darwin":
@@ -321,7 +321,7 @@ async function openBrowser(url2) {
321
321
  })();
322
322
  return new Promise((resolve, reject) => {
323
323
  try {
324
- const child = spawn6(cmd.bin, cmd.args, {
324
+ const child = spawn7(cmd.bin, cmd.args, {
325
325
  stdio: "ignore",
326
326
  detached: true
327
327
  });
@@ -1535,10 +1535,10 @@ function mergeDefs(...defs) {
1535
1535
  function cloneDef(schema) {
1536
1536
  return mergeDefs(schema._zod.def);
1537
1537
  }
1538
- function getElementAtPath(obj, path25) {
1539
- if (!path25)
1538
+ function getElementAtPath(obj, path27) {
1539
+ if (!path27)
1540
1540
  return obj;
1541
- return path25.reduce((acc, key) => acc?.[key], obj);
1541
+ return path27.reduce((acc, key) => acc?.[key], obj);
1542
1542
  }
1543
1543
  function promiseAllObject(promisesObj) {
1544
1544
  const keys = Object.keys(promisesObj);
@@ -1866,11 +1866,11 @@ function explicitlyAborted(x, startIndex = 0) {
1866
1866
  }
1867
1867
  return false;
1868
1868
  }
1869
- function prefixIssues(path25, issues) {
1869
+ function prefixIssues(path27, issues) {
1870
1870
  return issues.map((iss) => {
1871
1871
  var _a3;
1872
1872
  (_a3 = iss).path ?? (_a3.path = []);
1873
- iss.path.unshift(path25);
1873
+ iss.path.unshift(path27);
1874
1874
  return iss;
1875
1875
  });
1876
1876
  }
@@ -2088,16 +2088,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2088
2088
  }
2089
2089
  function formatError(error51, mapper = (issue2) => issue2.message) {
2090
2090
  const fieldErrors = { _errors: [] };
2091
- const processError = (error52, path25 = []) => {
2091
+ const processError = (error52, path27 = []) => {
2092
2092
  for (const issue2 of error52.issues) {
2093
2093
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2094
- issue2.errors.map((issues) => processError({ issues }, [...path25, ...issue2.path]));
2094
+ issue2.errors.map((issues) => processError({ issues }, [...path27, ...issue2.path]));
2095
2095
  } else if (issue2.code === "invalid_key") {
2096
- processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
2096
+ processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
2097
2097
  } else if (issue2.code === "invalid_element") {
2098
- processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
2098
+ processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
2099
2099
  } else {
2100
- const fullpath = [...path25, ...issue2.path];
2100
+ const fullpath = [...path27, ...issue2.path];
2101
2101
  if (fullpath.length === 0) {
2102
2102
  fieldErrors._errors.push(mapper(issue2));
2103
2103
  } else {
@@ -2124,17 +2124,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2124
2124
  }
2125
2125
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2126
2126
  const result = { errors: [] };
2127
- const processError = (error52, path25 = []) => {
2127
+ const processError = (error52, path27 = []) => {
2128
2128
  var _a3, _b;
2129
2129
  for (const issue2 of error52.issues) {
2130
2130
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2131
- issue2.errors.map((issues) => processError({ issues }, [...path25, ...issue2.path]));
2131
+ issue2.errors.map((issues) => processError({ issues }, [...path27, ...issue2.path]));
2132
2132
  } else if (issue2.code === "invalid_key") {
2133
- processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
2133
+ processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
2134
2134
  } else if (issue2.code === "invalid_element") {
2135
- processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
2135
+ processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
2136
2136
  } else {
2137
- const fullpath = [...path25, ...issue2.path];
2137
+ const fullpath = [...path27, ...issue2.path];
2138
2138
  if (fullpath.length === 0) {
2139
2139
  result.errors.push(mapper(issue2));
2140
2140
  continue;
@@ -2166,8 +2166,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2166
2166
  }
2167
2167
  function toDotPath(_path) {
2168
2168
  const segs = [];
2169
- const path25 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2170
- for (const seg of path25) {
2169
+ const path27 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2170
+ for (const seg of path27) {
2171
2171
  if (typeof seg === "number")
2172
2172
  segs.push(`[${seg}]`);
2173
2173
  else if (typeof seg === "symbol")
@@ -15670,13 +15670,13 @@ function resolveRef(ref, ctx) {
15670
15670
  if (!ref.startsWith("#")) {
15671
15671
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15672
15672
  }
15673
- const path25 = ref.slice(1).split("/").filter(Boolean);
15674
- if (path25.length === 0) {
15673
+ const path27 = ref.slice(1).split("/").filter(Boolean);
15674
+ if (path27.length === 0) {
15675
15675
  return ctx.rootSchema;
15676
15676
  }
15677
15677
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15678
- if (path25[0] === defsKey) {
15679
- const key = path25[1];
15678
+ if (path27[0] === defsKey) {
15679
+ const key = path27[1];
15680
15680
  if (!key || !ctx.defs[key]) {
15681
15681
  throw new Error(`Reference not found: ${ref}`);
15682
15682
  }
@@ -17706,11 +17706,11 @@ var init_tools = __esm({
17706
17706
  if (!ctx.addDocument)
17707
17707
  return "Knowledge vault tool not available.";
17708
17708
  const title = args["title"] || "New Document";
17709
- const path25 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17709
+ const path27 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17710
17710
  const content = args["content"] || "";
17711
17711
  const tags = args["tags"] || [];
17712
17712
  ctx.addDocument({
17713
- path: path25,
17713
+ path: path27,
17714
17714
  title,
17715
17715
  content,
17716
17716
  format: "markdown",
@@ -17719,7 +17719,7 @@ var init_tools = __esm({
17719
17719
  workspaceId: ctx.workspaceId
17720
17720
  });
17721
17721
  ctx.addActivity("vault", "created document", title);
17722
- return `Document "${title}" created at "${path25}".`;
17722
+ return `Document "${title}" created at "${path27}".`;
17723
17723
  }
17724
17724
  }
17725
17725
  ];
@@ -18725,11 +18725,174 @@ ${cached2}`;
18725
18725
  }
18726
18726
  });
18727
18727
 
18728
+ // packages/core/dist/core/providerStream.js
18729
+ function toLegacyMessages(messages) {
18730
+ return messages.map((m) => ({ role: m.role, content: m.content }));
18731
+ }
18732
+ function toLegacyTools(tools) {
18733
+ return tools.map((t) => ({
18734
+ type: "function",
18735
+ function: { name: t.name, description: t.description, parameters: t.parameters }
18736
+ }));
18737
+ }
18738
+ function wrapLegacyStream(legacyStream, params, _messages, _tools) {
18739
+ return async function* (providerParams) {
18740
+ const accumulatedChunks = [];
18741
+ const legacyParams = {
18742
+ apiKey: params.apiKey,
18743
+ model: providerParams.model,
18744
+ provider: providerParams.provider,
18745
+ messages: toLegacyMessages(providerParams.messages),
18746
+ tools: toLegacyTools(providerParams.tools),
18747
+ temperature: 0.7,
18748
+ stream: true,
18749
+ customBaseUrl: params.customBaseUrl,
18750
+ customAuthStyle: params.customAuthStyle,
18751
+ onChunk: (chunk) => {
18752
+ accumulatedChunks.push(chunk);
18753
+ },
18754
+ onRequestId: void 0,
18755
+ signal: providerParams.signal
18756
+ };
18757
+ const queue = [];
18758
+ let resolveNext = null;
18759
+ let done = false;
18760
+ let error51 = null;
18761
+ const promise2 = legacyStream(legacyParams).then((result) => {
18762
+ for (const tc of result.toolCalls) {
18763
+ queue.push({ kind: "tool_call", toolCallId: tc.id, toolName: tc.name, args: tc.args });
18764
+ }
18765
+ queue.push({ kind: "finish", reason: "stop" });
18766
+ }).catch((err) => {
18767
+ error51 = err;
18768
+ }).finally(() => {
18769
+ done = true;
18770
+ if (resolveNext) {
18771
+ resolveNext();
18772
+ resolveNext = null;
18773
+ }
18774
+ });
18775
+ let lastEmittedIndex = 0;
18776
+ while (!done || queue.length > 0 || lastEmittedIndex < accumulatedChunks.length) {
18777
+ while (lastEmittedIndex < accumulatedChunks.length) {
18778
+ yield { kind: "text", delta: accumulatedChunks[lastEmittedIndex] };
18779
+ lastEmittedIndex++;
18780
+ }
18781
+ while (queue.length > 0) {
18782
+ const d = queue.shift();
18783
+ yield d;
18784
+ }
18785
+ if (error51) {
18786
+ throw error51;
18787
+ }
18788
+ if (done)
18789
+ break;
18790
+ await new Promise((resolve) => {
18791
+ resolveNext = resolve;
18792
+ });
18793
+ }
18794
+ await promise2;
18795
+ };
18796
+ }
18797
+ var init_providerStream = __esm({
18798
+ "packages/core/dist/core/providerStream.js"() {
18799
+ "use strict";
18800
+ }
18801
+ });
18802
+
18803
+ // packages/core/dist/core/sessionJsonl.js
18804
+ import { promises as fs8 } from "node:fs";
18805
+ import path10 from "node:path";
18806
+ import os3 from "node:os";
18807
+ async function readSession(filePath) {
18808
+ try {
18809
+ const content = await fs8.readFile(filePath, "utf-8");
18810
+ const events = [];
18811
+ for (const line of content.split("\n")) {
18812
+ const trimmed = line.trim();
18813
+ if (!trimmed)
18814
+ continue;
18815
+ try {
18816
+ const parsed = JSON.parse(trimmed);
18817
+ if (parsed && typeof parsed === "object" && "event" in parsed) {
18818
+ events.push(parsed.event);
18819
+ }
18820
+ } catch {
18821
+ }
18822
+ }
18823
+ return events;
18824
+ } catch (err) {
18825
+ if (err.code === "ENOENT")
18826
+ return [];
18827
+ throw err;
18828
+ }
18829
+ }
18830
+ function defaultBaseDir() {
18831
+ return path10.join(os3.tmpdir(), "zelari-code", "sessions");
18832
+ }
18833
+ var SessionJsonlWriter;
18834
+ var init_sessionJsonl = __esm({
18835
+ "packages/core/dist/core/sessionJsonl.js"() {
18836
+ "use strict";
18837
+ SessionJsonlWriter = class {
18838
+ filePath;
18839
+ onError;
18840
+ constructor(sessionId, options = {}) {
18841
+ const baseDir = options.baseDir ?? defaultBaseDir();
18842
+ this.filePath = path10.join(baseDir, `${sessionId}.jsonl`);
18843
+ this.onError = options.onError ?? console.error;
18844
+ }
18845
+ /** Absolute path to the session JSONL file. */
18846
+ get path() {
18847
+ return this.filePath;
18848
+ }
18849
+ /** Append a BrainEvent as one JSON line. Creates the file + parent dirs if missing. */
18850
+ async append(event) {
18851
+ try {
18852
+ await fs8.mkdir(path10.dirname(this.filePath), { recursive: true });
18853
+ const line = JSON.stringify({
18854
+ ts: event.ts,
18855
+ sessionId: event.sessionId,
18856
+ event
18857
+ }) + "\n";
18858
+ await fs8.appendFile(this.filePath, line, { encoding: "utf-8", mode: 420 });
18859
+ } catch (err) {
18860
+ this.onError(`[sessionJsonl] failed to append event to ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`);
18861
+ }
18862
+ }
18863
+ /** Close the writer (no-op currently, but reserved for future buffered mode). */
18864
+ async close() {
18865
+ }
18866
+ };
18867
+ }
18868
+ });
18869
+
18870
+ // packages/core/dist/harness/index.js
18871
+ var harness_exports = {};
18872
+ __export(harness_exports, {
18873
+ AgentHarness: () => AgentHarness,
18874
+ SessionJsonlWriter: () => SessionJsonlWriter,
18875
+ hashToolCall: () => hashToolCall,
18876
+ normalizeTextToolArgs: () => normalizeTextToolArgs,
18877
+ parseTextToolCalls: () => parseTextToolCalls,
18878
+ readSession: () => readSession,
18879
+ wrapLegacyStream: () => wrapLegacyStream
18880
+ });
18881
+ var init_harness = __esm({
18882
+ "packages/core/dist/harness/index.js"() {
18883
+ "use strict";
18884
+ init_AgentHarness();
18885
+ init_providerStream();
18886
+ init_sessionJsonl();
18887
+ }
18888
+ });
18889
+
18728
18890
  // src/cli/provider/openai-compatible.ts
18729
18891
  var openai_compatible_exports = {};
18730
18892
  __export(openai_compatible_exports, {
18731
18893
  PROVIDER_ENDPOINTS: () => PROVIDER_ENDPOINTS,
18732
18894
  openaiCompatibleProvider: () => openaiCompatibleProvider,
18895
+ parseCachedPromptTokens: () => parseCachedPromptTokens,
18733
18896
  providerConfigFor: () => providerConfigFor,
18734
18897
  providerFromEnv: () => providerFromEnv,
18735
18898
  resolveActiveProvider: () => resolveActiveProvider,
@@ -18738,6 +18901,17 @@ __export(openai_compatible_exports, {
18738
18901
  function resolveActiveProvider() {
18739
18902
  return getProviderConfig().activeProviderId;
18740
18903
  }
18904
+ function parseCachedPromptTokens(usage) {
18905
+ if (!usage || typeof usage !== "object") return 0;
18906
+ const candidates = [
18907
+ usage.prompt_tokens_details?.cached_tokens,
18908
+ usage.prompt_cache_hit_tokens
18909
+ ];
18910
+ for (const c of candidates) {
18911
+ if (typeof c === "number" && Number.isFinite(c) && c >= 0) return c;
18912
+ }
18913
+ return 0;
18914
+ }
18741
18915
  function resolveBaseUrl(providerId) {
18742
18916
  const custom2 = getCustomEndpoint(providerId);
18743
18917
  if (custom2) return custom2;
@@ -18849,9 +19023,15 @@ function openaiCompatibleProvider(config2) {
18849
19023
  const promptTokens = typeof parsed.usage.prompt_tokens === "number" ? parsed.usage.prompt_tokens : 0;
18850
19024
  const completionTokens = typeof parsed.usage.completion_tokens === "number" ? parsed.usage.completion_tokens : 0;
18851
19025
  const totalTokens = typeof parsed.usage.total_tokens === "number" ? parsed.usage.total_tokens : promptTokens + completionTokens;
19026
+ const cachedPromptTokens = parseCachedPromptTokens(parsed.usage);
18852
19027
  yield {
18853
19028
  kind: "usage",
18854
- usage: { promptTokens, completionTokens, totalTokens }
19029
+ usage: {
19030
+ promptTokens,
19031
+ completionTokens,
19032
+ totalTokens,
19033
+ ...cachedPromptTokens > 0 ? { cachedPromptTokens } : {}
19034
+ }
18855
19035
  };
18856
19036
  }
18857
19037
  if (typeof delta?.content === "string" && delta.content.length > 0) {
@@ -19925,7 +20105,7 @@ var init_parseCssMotion = __esm({
19925
20105
  });
19926
20106
 
19927
20107
  // packages/core/dist/council/verification/citeVerify.js
19928
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
20108
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
19929
20109
  import { join } from "node:path";
19930
20110
  function extractCitations(text) {
19931
20111
  const out = [];
@@ -19949,7 +20129,7 @@ function verifyCitations(projectRoot, synthesisText) {
19949
20129
  const results = [];
19950
20130
  for (const cite of extractCitations(synthesisText)) {
19951
20131
  const abs = join(projectRoot, cite.file);
19952
- if (!existsSync7(abs)) {
20132
+ if (!existsSync8(abs)) {
19953
20133
  results.push({
19954
20134
  id: "synthesis.cite-invalid",
19955
20135
  severity: "error",
@@ -20225,14 +20405,14 @@ var init_synthesisAudit = __esm({
20225
20405
  });
20226
20406
 
20227
20407
  // packages/core/dist/council/verification/runChecks.js
20228
- import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
20408
+ import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
20229
20409
  import { join as join2 } from "node:path";
20230
20410
  function loadNfrSpec(zelariRoot) {
20231
- const path25 = join2(zelariRoot, "nfr-spec.json");
20232
- if (!existsSync8(path25))
20411
+ const path27 = join2(zelariRoot, "nfr-spec.json");
20412
+ if (!existsSync9(path27))
20233
20413
  return null;
20234
20414
  try {
20235
- const raw = JSON.parse(readFileSync6(path25, "utf8"));
20415
+ const raw = JSON.parse(readFileSync6(path27, "utf8"));
20236
20416
  if (raw.version !== 1 || !Array.isArray(raw.targets))
20237
20417
  return null;
20238
20418
  return raw;
@@ -20243,11 +20423,11 @@ function loadNfrSpec(zelariRoot) {
20243
20423
  function resolveTargets(projectRoot, spec) {
20244
20424
  const found = [];
20245
20425
  for (const rel2 of spec.targets) {
20246
- if (existsSync8(join2(projectRoot, rel2))) {
20426
+ if (existsSync9(join2(projectRoot, rel2))) {
20247
20427
  found.push(rel2);
20248
20428
  }
20249
20429
  }
20250
- if (found.length === 0 && existsSync8(join2(projectRoot, "index.html"))) {
20430
+ if (found.length === 0 && existsSync9(join2(projectRoot, "index.html"))) {
20251
20431
  return ["index.html"];
20252
20432
  }
20253
20433
  return found;
@@ -20303,7 +20483,7 @@ function checkDeadCssHooks(html, relFile) {
20303
20483
  }
20304
20484
  function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
20305
20485
  const planPath = join2(zelariRoot, "plan.json");
20306
- if (!existsSync8(planPath) || keywords.length === 0)
20486
+ if (!existsSync9(planPath) || keywords.length === 0)
20307
20487
  return [];
20308
20488
  let plan;
20309
20489
  try {
@@ -20343,11 +20523,11 @@ function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
20343
20523
  }
20344
20524
  function checkReadmeStale(projectRoot, targets) {
20345
20525
  const readmePath = join2(projectRoot, "README.md");
20346
- if (!existsSync8(readmePath) || targets.length === 0)
20526
+ if (!existsSync9(readmePath) || targets.length === 0)
20347
20527
  return [];
20348
20528
  const readme = readFileSync6(readmePath, "utf8");
20349
20529
  const htmlPath = join2(projectRoot, targets[0]);
20350
- if (!existsSync8(htmlPath))
20530
+ if (!existsSync9(htmlPath))
20351
20531
  return [];
20352
20532
  const html = readFileSync6(htmlPath, "utf8");
20353
20533
  const sectionCount = (html.match(/<section\s+id=/gi) ?? []).length;
@@ -20473,7 +20653,7 @@ var init_runChecks = __esm({
20473
20653
  });
20474
20654
 
20475
20655
  // packages/core/dist/council/verification/microGate.js
20476
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
20656
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
20477
20657
  import { join as join3 } from "node:path";
20478
20658
  function checkDeadHooksInHtml(html) {
20479
20659
  const warnings = [];
@@ -20503,7 +20683,7 @@ function checkDeadHooksInHtml(html) {
20503
20683
  }
20504
20684
  function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
20505
20685
  const abs = join3(projectRoot, relPath);
20506
- if (!existsSync9(abs) || !/\.html?$/i.test(relPath))
20686
+ if (!existsSync10(abs) || !/\.html?$/i.test(relPath))
20507
20687
  return [];
20508
20688
  const spec = (zelariRoot ? loadNfrSpec(zelariRoot) : null) ?? DEFAULT_NFR_SPEC;
20509
20689
  const anim = spec.animation ?? { compositorOnly: true, forbidLayoutProps: true };
@@ -20947,7 +21127,7 @@ var init_inlineJsAutofix = __esm({
20947
21127
  });
20948
21128
 
20949
21129
  // packages/core/dist/agents/councilApi.js
20950
- import { existsSync as existsSync10 } from "node:fs";
21130
+ import { existsSync as existsSync11 } from "node:fs";
20951
21131
  import { join as join5 } from "node:path";
20952
21132
  function parseClarificationRequest(text) {
20953
21133
  const start = text.indexOf(QUESTION_MARKER);
@@ -21468,7 +21648,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
21468
21648
  const zelariRoot = `${chairmanProjectRoot}/.zelari`;
21469
21649
  const spec = loadNfrSpec(zelariRoot) ?? DEFAULT_NFR_SPEC;
21470
21650
  for (const rel2 of spec.targets) {
21471
- if (!existsSync10(join5(chairmanProjectRoot, rel2)))
21651
+ if (!existsSync11(join5(chairmanProjectRoot, rel2)))
21472
21652
  continue;
21473
21653
  changedTargetFiles.add(rel2);
21474
21654
  for (const w of runChairmanMicroGate({ projectRoot: chairmanProjectRoot, relPath: rel2, zelariRoot })) {
@@ -21488,7 +21668,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
21488
21668
  const zelariRootReplay = `${chairmanProjectRoot}/.zelari`;
21489
21669
  const specReplay = loadNfrSpec(zelariRootReplay) ?? DEFAULT_NFR_SPEC;
21490
21670
  for (const rel2 of specReplay.targets) {
21491
- if (!existsSync10(join5(chairmanProjectRoot, rel2)))
21671
+ if (!existsSync11(join5(chairmanProjectRoot, rel2)))
21492
21672
  continue;
21493
21673
  changedTargetFiles.add(rel2);
21494
21674
  for (const w of runChairmanMicroGate({
@@ -22421,9 +22601,9 @@ var init_types2 = __esm({
22421
22601
  import { readFileSync as readFileSync11 } from "node:fs";
22422
22602
  import { join as join8 } from "node:path";
22423
22603
  function readLessonsDeduped(zelariRoot) {
22424
- const path25 = join8(zelariRoot, LESSONS_FILE);
22604
+ const path27 = join8(zelariRoot, LESSONS_FILE);
22425
22605
  try {
22426
- const raw = readFileSync11(path25, "utf8");
22606
+ const raw = readFileSync11(path27, "utf8");
22427
22607
  const byId = /* @__PURE__ */ new Map();
22428
22608
  for (const line of raw.split(/\r?\n/)) {
22429
22609
  if (!line.trim())
@@ -22524,8 +22704,8 @@ function keywordsFrom(check2, signature) {
22524
22704
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
22525
22705
  }
22526
22706
  function writeLesson(zelariRoot, lesson) {
22527
- const path25 = join9(zelariRoot, LESSONS_FILE);
22528
- appendFileSync2(path25, `${JSON.stringify(lesson)}
22707
+ const path27 = join9(zelariRoot, LESSONS_FILE);
22708
+ appendFileSync2(path27, `${JSON.stringify(lesson)}
22529
22709
  `, "utf8");
22530
22710
  }
22531
22711
  function findSimilar(lessons, signature) {
@@ -23062,7 +23242,7 @@ var init_council = __esm({
23062
23242
  import {
23063
23243
  mkdirSync as mkdirSync6,
23064
23244
  writeFileSync as writeFileSync10,
23065
- existsSync as existsSync11,
23245
+ existsSync as existsSync12,
23066
23246
  accessSync,
23067
23247
  constants,
23068
23248
  realpathSync
@@ -23089,7 +23269,7 @@ function hashProject(projectPath) {
23089
23269
  }
23090
23270
  function isWritableDir(dir) {
23091
23271
  try {
23092
- if (!existsSync11(dir)) return false;
23272
+ if (!existsSync12(dir)) return false;
23093
23273
  accessSync(dir, constants.W_OK);
23094
23274
  return true;
23095
23275
  } catch {
@@ -23098,9 +23278,9 @@ function isWritableDir(dir) {
23098
23278
  }
23099
23279
  function ensureWorkspaceDir(workspaceDir) {
23100
23280
  mkdirSync6(workspaceDir, { recursive: true });
23101
- if (workspaceDir.endsWith("/.zelari") && existsSync11(join11(workspaceDir, "..", ".git"))) {
23281
+ if (workspaceDir.endsWith("/.zelari") && existsSync12(join11(workspaceDir, "..", ".git"))) {
23102
23282
  const gitignorePath = join11(workspaceDir, ".gitignore");
23103
- if (!existsSync11(gitignorePath)) {
23283
+ if (!existsSync12(gitignorePath)) {
23104
23284
  writeFileSync10(gitignorePath, "*\n!.gitignore\n");
23105
23285
  }
23106
23286
  }
@@ -23135,8 +23315,8 @@ __export(workspaceSummary_exports, {
23135
23315
  buildWorkspaceSummary: () => buildWorkspaceSummary,
23136
23316
  buildZelariReadHint: () => buildZelariReadHint
23137
23317
  });
23138
- import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync, statSync as statSync3 } from "node:fs";
23139
- import { join as join12, relative } from "node:path";
23318
+ import { existsSync as existsSync13, readFileSync as readFileSync12, readdirSync, statSync as statSync3 } from "node:fs";
23319
+ import { join as join12, relative as relative2 } from "node:path";
23140
23320
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
23141
23321
  const { maxEntries = 30 } = options;
23142
23322
  const name = safeProjectName(projectRoot);
@@ -23164,7 +23344,7 @@ function formatTaskLine(t) {
23164
23344
  function buildPlanSummary(projectRoot = process.cwd(), options) {
23165
23345
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
23166
23346
  const planPath = join12(zelariRoot, "plan.json");
23167
- if (!existsSync12(planPath)) return null;
23347
+ if (!existsSync13(planPath)) return null;
23168
23348
  let plan;
23169
23349
  try {
23170
23350
  plan = JSON.parse(readFileSync12(planPath, "utf8"));
@@ -23297,7 +23477,7 @@ function pickNextTask(open) {
23297
23477
  }
23298
23478
  function buildZelariReadHint(projectRoot = process.cwd()) {
23299
23479
  const planPath = join12(resolveWorkspaceRoot(projectRoot), "plan.json");
23300
- if (!existsSync12(planPath)) return "";
23480
+ if (!existsSync13(planPath)) return "";
23301
23481
  return [
23302
23482
  "# Council workspace detected (.zelari/)",
23303
23483
  "This project has a council workspace: .zelari/plan.json holds the plan (phases, tasks, milestones) and .zelari/plan-tasks/ holds one detail file per task.",
@@ -23313,7 +23493,7 @@ function safeProjectName(root) {
23313
23493
  }
23314
23494
  function readPackageJson(projectRoot) {
23315
23495
  const p3 = join12(projectRoot, "package.json");
23316
- if (!existsSync12(p3)) return null;
23496
+ if (!existsSync13(p3)) return null;
23317
23497
  try {
23318
23498
  return JSON.parse(readFileSync12(p3, "utf8"));
23319
23499
  } catch {
@@ -23349,7 +23529,7 @@ function listShallow(projectRoot, maxEntries) {
23349
23529
  out.push(`\u2026 (+${top.length - count} more)`);
23350
23530
  break;
23351
23531
  }
23352
- const rel2 = relative(projectRoot, join12(projectRoot, entry.name));
23532
+ const rel2 = relative2(projectRoot, join12(projectRoot, entry.name));
23353
23533
  if (entry.isDirectory()) {
23354
23534
  let inner = "";
23355
23535
  try {
@@ -23406,7 +23586,7 @@ __export(storage_exports, {
23406
23586
  import {
23407
23587
  readFileSync as readFileSync13,
23408
23588
  writeFileSync as writeFileSync11,
23409
- existsSync as existsSync13,
23589
+ existsSync as existsSync14,
23410
23590
  mkdirSync as mkdirSync7,
23411
23591
  readdirSync as readdirSync2,
23412
23592
  renameSync as renameSync2
@@ -23665,32 +23845,32 @@ var init_storage = __esm({
23665
23845
  VALID_SCALARS = /^(true|false|null|~)$/i;
23666
23846
  Storage = class {
23667
23847
  /** Read a Markdown file with frontmatter. Throws if not found. */
23668
- read(path25) {
23669
- if (!existsSync13(path25)) {
23670
- throw new Error(`File not found: ${path25}`);
23848
+ read(path27) {
23849
+ if (!existsSync14(path27)) {
23850
+ throw new Error(`File not found: ${path27}`);
23671
23851
  }
23672
- const md = readFileSync13(path25, "utf8");
23852
+ const md = readFileSync13(path27, "utf8");
23673
23853
  return parseFrontmatter(md);
23674
23854
  }
23675
23855
  /** Read a Markdown file; returns null if not found. */
23676
- readIfExists(path25) {
23677
- if (!existsSync13(path25)) return null;
23678
- return this.read(path25);
23856
+ readIfExists(path27) {
23857
+ if (!existsSync14(path27)) return null;
23858
+ return this.read(path27);
23679
23859
  }
23680
23860
  /**
23681
23861
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
23682
23862
  * The meta object is serialized as YAML frontmatter; body as Markdown.
23683
23863
  */
23684
- write(path25, meta3, body) {
23685
- mkdirSync7(dirname(path25), { recursive: true });
23686
- const tmp = path25 + ".tmp-" + process.pid;
23864
+ write(path27, meta3, body) {
23865
+ mkdirSync7(dirname(path27), { recursive: true });
23866
+ const tmp = path27 + ".tmp-" + process.pid;
23687
23867
  const md = serializeFrontmatter(meta3, body);
23688
23868
  writeFileSync11(tmp, md, "utf8");
23689
- renameSync2(tmp, path25);
23869
+ renameSync2(tmp, path27);
23690
23870
  }
23691
23871
  /** List all .md files in a directory (non-recursive). */
23692
23872
  listMarkdown(dir) {
23693
- if (!existsSync13(dir)) return [];
23873
+ if (!existsSync14(dir)) return [];
23694
23874
  return readdirSync2(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join13(dir, f));
23695
23875
  }
23696
23876
  };
@@ -23730,14 +23910,14 @@ __export(stubs_exports, {
23730
23910
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
23731
23911
  });
23732
23912
  import {
23733
- existsSync as existsSync14,
23913
+ existsSync as existsSync15,
23734
23914
  readdirSync as readdirSync3,
23735
23915
  writeFileSync as writeFileSync12,
23736
23916
  readFileSync as readFileSync14,
23737
23917
  mkdirSync as mkdirSync8,
23738
23918
  renameSync as renameSync3
23739
23919
  } from "node:fs";
23740
- import { join as join14, basename as basename2, dirname as dirname2, relative as relative2 } from "node:path";
23920
+ import { join as join14, basename as basename2, dirname as dirname2, relative as relative3 } from "node:path";
23741
23921
  function createWorkspaceContext(projectRoot = process.cwd()) {
23742
23922
  const rootDir = resolveWorkspaceRoot(projectRoot);
23743
23923
  return {
@@ -23751,7 +23931,7 @@ function planJsonPath(ctx) {
23751
23931
  }
23752
23932
  function readPlan(ctx) {
23753
23933
  const jsonPath = planJsonPath(ctx);
23754
- if (existsSync14(jsonPath)) {
23934
+ if (existsSync15(jsonPath)) {
23755
23935
  try {
23756
23936
  const parsed = JSON.parse(
23757
23937
  readFileSync14(jsonPath, "utf8")
@@ -23764,8 +23944,8 @@ function readPlan(ctx) {
23764
23944
  } catch {
23765
23945
  }
23766
23946
  }
23767
- const path25 = workspaceFile(ctx.rootDir, "plan");
23768
- const doc = ctx.storage.readIfExists(path25);
23947
+ const path27 = workspaceFile(ctx.rootDir, "plan");
23948
+ const doc = ctx.storage.readIfExists(path27);
23769
23949
  if (!doc) return { phases: [], tasks: [], milestones: [] };
23770
23950
  const meta3 = doc.meta;
23771
23951
  return {
@@ -23846,7 +24026,7 @@ function renderPlanBody(summary) {
23846
24026
  }
23847
24027
  function nextAdrId(ctx) {
23848
24028
  const decisionsDir = join14(ctx.rootDir, "decisions");
23849
- if (!existsSync14(decisionsDir)) return "001";
24029
+ if (!existsSync15(decisionsDir)) return "001";
23850
24030
  const existing = readdirSync3(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
23851
24031
  const max = existing.length === 0 ? 0 : Math.max(...existing);
23852
24032
  return String(max + 1).padStart(3, "0");
@@ -23930,7 +24110,7 @@ function addMilestoneRecord(ctx, summary, input) {
23930
24110
  dueDate: input.dueDate,
23931
24111
  targetVersion: version2
23932
24112
  });
23933
- const path25 = join14(ctx.rootDir, "milestones", `${id}.md`);
24113
+ const path27 = join14(ctx.rootDir, "milestones", `${id}.md`);
23934
24114
  const meta3 = {
23935
24115
  kind: "milestone",
23936
24116
  id,
@@ -23947,7 +24127,7 @@ function addMilestoneRecord(ctx, summary, input) {
23947
24127
  `Target version: ${version2}`,
23948
24128
  ""
23949
24129
  ].join("\n");
23950
- ctx.storage.write(path25, meta3, body);
24130
+ ctx.storage.write(path27, meta3, body);
23951
24131
  return { id, created: true };
23952
24132
  }
23953
24133
  function readPlanSummary(ctx) {
@@ -24151,7 +24331,7 @@ function addIdeaStub(ctx) {
24151
24331
  const tags = args["tags"] ?? [];
24152
24332
  const category = args["category"] ?? "General";
24153
24333
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
24154
- const path25 = workspaceArtifact(ctx.rootDir, "decisions", id);
24334
+ const path27 = workspaceArtifact(ctx.rootDir, "decisions", id);
24155
24335
  const meta3 = {
24156
24336
  kind: "adr",
24157
24337
  status: "proposed",
@@ -24177,7 +24357,7 @@ function addIdeaStub(ctx) {
24177
24357
  ...consequences.map((c) => `- ${c}`),
24178
24358
  ""
24179
24359
  ].join("\n");
24180
- ctx.storage.write(path25, meta3, body);
24360
+ ctx.storage.write(path27, meta3, body);
24181
24361
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
24182
24362
  });
24183
24363
  }
@@ -24259,14 +24439,14 @@ function createDocumentStub(ctx) {
24259
24439
  ctx.storage.write(risksPath, riskMeta, content);
24260
24440
  return `Document "${title}" created at risks.md (workspace root).`;
24261
24441
  }
24262
- const path25 = workspaceArtifact(ctx.rootDir, "docs", slug);
24442
+ const path27 = workspaceArtifact(ctx.rootDir, "docs", slug);
24263
24443
  const meta3 = {
24264
24444
  kind: "doc",
24265
24445
  id: slug,
24266
24446
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
24267
24447
  tags
24268
24448
  };
24269
- ctx.storage.write(path25, meta3, content);
24449
+ ctx.storage.write(path27, meta3, content);
24270
24450
  return `Document "${title}" created at docs/${slug}.md.`;
24271
24451
  });
24272
24452
  }
@@ -24302,7 +24482,7 @@ function searchDocumentsStub(ctx) {
24302
24482
  ];
24303
24483
  const results = [];
24304
24484
  for (const file2 of files) {
24305
- if (!existsSync14(file2)) continue;
24485
+ if (!existsSync15(file2)) continue;
24306
24486
  const raw = readFileSync14(file2, "utf8");
24307
24487
  const content = raw.toLowerCase();
24308
24488
  let idx = -1;
@@ -24330,7 +24510,7 @@ function searchDocumentsStub(ctx) {
24330
24510
  const end = Math.min(raw.length, idx + matchLen + 40);
24331
24511
  const snippet = "\u2026" + raw.slice(start, end).replace(/\n/g, " ").trim() + "\u2026";
24332
24512
  results.push({
24333
- path: relative2(ctx.rootDir, file2) || basename2(file2),
24513
+ path: relative3(ctx.rootDir, file2) || basename2(file2),
24334
24514
  snippet
24335
24515
  });
24336
24516
  if (results.length >= limit) break;
@@ -24497,21 +24677,21 @@ __export(updater_exports, {
24497
24677
  resolveBundledNpmCli: () => resolveBundledNpmCli
24498
24678
  });
24499
24679
  import { createRequire } from "node:module";
24500
- import { spawn as spawn2 } from "node:child_process";
24501
- import { existsSync as existsSync15 } from "node:fs";
24502
- import path15 from "node:path";
24680
+ import { spawn as spawn3 } from "node:child_process";
24681
+ import { existsSync as existsSync16 } from "node:fs";
24682
+ import path16 from "node:path";
24503
24683
  import { fileURLToPath } from "node:url";
24504
24684
  function resolveBundledNpmCli(execPath = process.execPath) {
24505
- const dir = path15.dirname(execPath);
24685
+ const dir = path16.dirname(execPath);
24506
24686
  const candidates = [
24507
24687
  // Windows: C:\...\node.exe → C:\...\node_modules\npm\bin\npm-cli.js
24508
- path15.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
24688
+ path16.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
24509
24689
  // POSIX: <prefix>/bin/node → <prefix>/lib/node_modules/npm/bin/npm-cli.js
24510
- path15.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
24690
+ path16.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
24511
24691
  ];
24512
24692
  for (const candidate of candidates) {
24513
24693
  try {
24514
- if (existsSync15(candidate)) return candidate;
24694
+ if (existsSync16(candidate)) return candidate;
24515
24695
  } catch {
24516
24696
  }
24517
24697
  }
@@ -24524,7 +24704,7 @@ function looksLikeBrokenShim(exitCode, output) {
24524
24704
  }
24525
24705
  function getCurrentVersion() {
24526
24706
  try {
24527
- const pkgPath = path15.resolve(__dirname2, "..", "..", "package.json");
24707
+ const pkgPath = path16.resolve(__dirname2, "..", "..", "package.json");
24528
24708
  const pkg = require2(pkgPath);
24529
24709
  return pkg.version;
24530
24710
  } catch {
@@ -24584,7 +24764,7 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
24584
24764
  updateAvailable: cmp < 0
24585
24765
  };
24586
24766
  }
24587
- async function performUpdate(packageName = "zelari-code", executor = spawn2, resolveNpmCli = resolveBundledNpmCli) {
24767
+ async function performUpdate(packageName = "zelari-code", executor = spawn3, resolveNpmCli = resolveBundledNpmCli) {
24588
24768
  const args = ["install", "-g", `${packageName}@latest`];
24589
24769
  const primary = await runNpm(executor, args, "shim");
24590
24770
  if (primary.ok) return primary;
@@ -24636,13 +24816,13 @@ var init_updater = __esm({
24636
24816
  "use strict";
24637
24817
  init_cmdline();
24638
24818
  require2 = createRequire(import.meta.url);
24639
- __dirname2 = path15.dirname(fileURLToPath(import.meta.url));
24819
+ __dirname2 = path16.dirname(fileURLToPath(import.meta.url));
24640
24820
  REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
24641
24821
  }
24642
24822
  });
24643
24823
 
24644
24824
  // src/cli/mcp/mcpClient.ts
24645
- import { spawn as spawn3 } from "node:child_process";
24825
+ import { spawn as spawn4 } from "node:child_process";
24646
24826
  var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
24647
24827
  var init_mcpClient = __esm({
24648
24828
  "src/cli/mcp/mcpClient.ts"() {
@@ -24670,10 +24850,10 @@ var init_mcpClient = __esm({
24670
24850
  env: { ...process.env, ...this.config.env ?? {} },
24671
24851
  windowsHide: true
24672
24852
  };
24673
- const child = process.platform === "win32" ? spawn3(buildCmdLine(this.config.command, this.config.args ?? []), {
24853
+ const child = process.platform === "win32" ? spawn4(buildCmdLine(this.config.command, this.config.args ?? []), {
24674
24854
  ...spawnOpts,
24675
24855
  shell: true
24676
- }) : spawn3(this.config.command, this.config.args ?? [], spawnOpts);
24856
+ }) : spawn4(this.config.command, this.config.args ?? [], spawnOpts);
24677
24857
  this.child = child;
24678
24858
  child.stdout.setEncoding("utf8");
24679
24859
  child.stdout.on("data", (chunk) => this.onStdout(chunk));
@@ -24817,7 +24997,7 @@ __export(mcpManager_exports, {
24817
24997
  readMcpConfig: () => readMcpConfig,
24818
24998
  registerMcpTools: () => registerMcpTools
24819
24999
  });
24820
- import { existsSync as existsSync16, readFileSync as readFileSync15 } from "node:fs";
25000
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
24821
25001
  import { join as join15 } from "node:path";
24822
25002
  import { homedir as homedir4 } from "node:os";
24823
25003
  function readMcpConfig(projectRoot = process.cwd()) {
@@ -24828,7 +25008,7 @@ function readMcpConfig(projectRoot = process.cwd()) {
24828
25008
  // later = higher precedence
24829
25009
  ];
24830
25010
  for (const p3 of paths) {
24831
- if (!existsSync16(p3)) continue;
25011
+ if (!existsSync17(p3)) continue;
24832
25012
  try {
24833
25013
  const parsed = JSON.parse(readFileSync15(p3, "utf8"));
24834
25014
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
@@ -24962,11 +25142,11 @@ var planDetect_exports = {};
24962
25142
  __export(planDetect_exports, {
24963
25143
  hasWorkspacePlan: () => hasWorkspacePlan
24964
25144
  });
24965
- import { existsSync as existsSync17, readFileSync as readFileSync16 } from "node:fs";
25145
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
24966
25146
  import { join as join16 } from "node:path";
24967
25147
  function hasWorkspacePlan(projectRoot = process.cwd()) {
24968
25148
  const planPath = join16(resolveWorkspaceRoot(projectRoot), "plan.json");
24969
- if (!existsSync17(planPath)) return false;
25149
+ if (!existsSync18(planPath)) return false;
24970
25150
  try {
24971
25151
  const parsed = JSON.parse(readFileSync16(planPath, "utf8"));
24972
25152
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
@@ -25065,15 +25245,15 @@ __export(agentsMd_exports, {
25065
25245
  serializeAgentsMd: () => serializeAgentsMd,
25066
25246
  updateAgentsMd: () => updateAgentsMd
25067
25247
  });
25068
- import { existsSync as existsSync18, readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "node:fs";
25248
+ import { existsSync as existsSync19, readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "node:fs";
25069
25249
  import { createHash as createHash2 } from "node:crypto";
25070
25250
  import { join as join17 } from "node:path";
25071
25251
  import { readFile } from "node:fs/promises";
25072
25252
  async function readPackageJson2(projectRoot) {
25073
- const path25 = join17(projectRoot, "package.json");
25074
- if (!existsSync18(path25)) return null;
25253
+ const path27 = join17(projectRoot, "package.json");
25254
+ if (!existsSync19(path27)) return null;
25075
25255
  try {
25076
- return JSON.parse(await readFile(path25, "utf8"));
25256
+ return JSON.parse(await readFile(path27, "utf8"));
25077
25257
  } catch {
25078
25258
  return null;
25079
25259
  }
@@ -25096,7 +25276,7 @@ async function genTechStack(ctx) {
25096
25276
  }
25097
25277
  async function genDecisions(ctx) {
25098
25278
  const decisionsDir = join17(ctx.rootDir, "decisions");
25099
- if (!existsSync18(decisionsDir)) return "_No ADRs yet._";
25279
+ if (!existsSync19(decisionsDir)) return "_No ADRs yet._";
25100
25280
  const files = ctx.storage.listMarkdown(decisionsDir).sort();
25101
25281
  const accepted = [];
25102
25282
  const proposed = [];
@@ -25122,7 +25302,7 @@ async function genDecisions(ctx) {
25122
25302
  async function genConventions(ctx) {
25123
25303
  const lines = [];
25124
25304
  const claudeMd = join17(ctx.projectRoot, "CLAUDE.MD");
25125
- if (existsSync18(claudeMd)) {
25305
+ if (existsSync19(claudeMd)) {
25126
25306
  const content = readFileSync17(claudeMd, "utf8");
25127
25307
  const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
25128
25308
  if (match) {
@@ -25155,9 +25335,9 @@ async function genBuild(ctx) {
25155
25335
  ].join("\n");
25156
25336
  }
25157
25337
  async function genOpenQuestions(ctx) {
25158
- const path25 = join17(ctx.rootDir, "risks.md");
25159
- if (!existsSync18(path25)) return "_No open questions._";
25160
- const content = readFileSync17(path25, "utf8");
25338
+ const path27 = join17(ctx.rootDir, "risks.md");
25339
+ if (!existsSync19(path27)) return "_No open questions._";
25340
+ const content = readFileSync17(path27, "utf8");
25161
25341
  const lines = content.split("\n");
25162
25342
  const questions = [];
25163
25343
  let currentTitle = "";
@@ -25232,7 +25412,7 @@ function titleCase(id) {
25232
25412
  }
25233
25413
  async function updateAgentsMd(ctx, projectRoot) {
25234
25414
  const agentsPath = join17(projectRoot, "AGENTS.MD");
25235
- if (existsSync18(agentsPath)) {
25415
+ if (existsSync19(agentsPath)) {
25236
25416
  const content = readFileSync17(agentsPath, "utf8");
25237
25417
  const hasAnyMarker = AUTO_SECTIONS.some((id) => content.includes(MARKER_OPEN(id)));
25238
25418
  if (!hasAnyMarker) {
@@ -25248,7 +25428,7 @@ async function updateAgentsMd(ctx, projectRoot) {
25248
25428
  newSections.set(id, await GENERATORS[id](ctx));
25249
25429
  }
25250
25430
  let manualContent = "";
25251
- if (existsSync18(agentsPath)) {
25431
+ if (existsSync19(agentsPath)) {
25252
25432
  const { manualBlocks } = parseAgentsMd(readFileSync17(agentsPath, "utf8"));
25253
25433
  manualContent = manualBlocks.after;
25254
25434
  } else {
@@ -25265,7 +25445,7 @@ async function updateAgentsMd(ctx, projectRoot) {
25265
25445
  ""
25266
25446
  ].join("\n");
25267
25447
  }
25268
- const oldContent = existsSync18(agentsPath) ? readFileSync17(agentsPath, "utf8") : "";
25448
+ const oldContent = existsSync19(agentsPath) ? readFileSync17(agentsPath, "utf8") : "";
25269
25449
  const { sections: oldSections } = parseAgentsMd(oldContent);
25270
25450
  const changedSections = [];
25271
25451
  for (const id of AUTO_SECTIONS) {
@@ -25402,8 +25582,8 @@ var init_completeDesign = __esm({
25402
25582
  });
25403
25583
 
25404
25584
  // src/cli/workspace/projectSmoke.ts
25405
- import { spawn as spawn4 } from "node:child_process";
25406
- import { existsSync as existsSync19, readFileSync as readFileSync18 } from "node:fs";
25585
+ import { spawn as spawn5 } from "node:child_process";
25586
+ import { existsSync as existsSync20, readFileSync as readFileSync18 } from "node:fs";
25407
25587
  import { join as join18 } from "node:path";
25408
25588
  function pickSmokeScript(scripts) {
25409
25589
  if (!scripts) return null;
@@ -25417,7 +25597,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
25417
25597
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
25418
25598
  }
25419
25599
  const pkgPath = join18(projectRoot, "package.json");
25420
- if (!existsSync19(pkgPath)) {
25600
+ if (!existsSync20(pkgPath)) {
25421
25601
  return { ran: false, reason: "no package.json (skipped)" };
25422
25602
  }
25423
25603
  let scripts = {};
@@ -25433,7 +25613,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
25433
25613
  }
25434
25614
  return await new Promise((resolveRun) => {
25435
25615
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
25436
- const child = spawn4(npmCmd, ["run", script], {
25616
+ const child = spawn5(npmCmd, ["run", script], {
25437
25617
  cwd: projectRoot,
25438
25618
  stdio: ["ignore", "pipe", "pipe"],
25439
25619
  env: process.env,
@@ -25505,8 +25685,8 @@ __export(postCouncilHook_exports, {
25505
25685
  runImplementationVerificationHook: () => runImplementationVerificationHook,
25506
25686
  runPostCouncilHook: () => runPostCouncilHook
25507
25687
  });
25508
- import { spawn as spawn5 } from "node:child_process";
25509
- import { existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
25688
+ import { spawn as spawn6 } from "node:child_process";
25689
+ import { existsSync as existsSync21, readFileSync as readFileSync19 } from "node:fs";
25510
25690
  import { join as join19 } from "node:path";
25511
25691
  async function runCompleteDesignPostProcessor(ctx, options) {
25512
25692
  if (options?.runMode === "implementation") {
@@ -25520,7 +25700,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
25520
25700
  }
25521
25701
  const planJsonPath2 = join19(ctx.rootDir, "plan.json");
25522
25702
  const scriptPath = join19(ctx.projectRoot, "complete-design.mjs");
25523
- if (!existsSync20(planJsonPath2)) {
25703
+ if (!existsSync21(planJsonPath2)) {
25524
25704
  return {
25525
25705
  ran: false,
25526
25706
  reason: ".zelari/plan.json missing (not design-phase)"
@@ -25536,7 +25716,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
25536
25716
  if (phaseCount === 0) {
25537
25717
  return { ran: false, reason: ".zelari/plan.json has no phases" };
25538
25718
  }
25539
- if (!existsSync20(scriptPath)) {
25719
+ if (!existsSync21(scriptPath)) {
25540
25720
  try {
25541
25721
  const builtin = await runBuiltinCompleteDesign(ctx);
25542
25722
  return {
@@ -25554,7 +25734,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
25554
25734
  }
25555
25735
  }
25556
25736
  return await new Promise((resolveRun) => {
25557
- const child = spawn5(process.execPath, [scriptPath], {
25737
+ const child = spawn6(process.execPath, [scriptPath], {
25558
25738
  cwd: ctx.projectRoot,
25559
25739
  stdio: ["ignore", "pipe", "pipe"],
25560
25740
  env: process.env
@@ -25720,8 +25900,8 @@ async function runPostCouncilHook(ctx, options) {
25720
25900
  sources: scope.sources
25721
25901
  } : void 0
25722
25902
  });
25723
- const path25 = writeCouncilCompletion(ctx.rootDir, completion);
25724
- completionHook = { ran: true, path: path25, completion };
25903
+ const path27 = writeCouncilCompletion(ctx.rootDir, completion);
25904
+ completionHook = { ran: true, path: path27, completion };
25725
25905
  } catch (err) {
25726
25906
  completionHook = {
25727
25907
  ran: true,
@@ -25757,12 +25937,12 @@ var buildLessonsSummary_exports = {};
25757
25937
  __export(buildLessonsSummary_exports, {
25758
25938
  buildLessonsSummary: () => buildLessonsSummary
25759
25939
  });
25760
- import { existsSync as existsSync21 } from "node:fs";
25940
+ import { existsSync as existsSync22 } from "node:fs";
25761
25941
  import { join as join20 } from "node:path";
25762
25942
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
25763
25943
  if (process.env["ZELARI_LESSONS"] === "0") return null;
25764
25944
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
25765
- if (!existsSync21(join20(zelariRoot, "lessons.jsonl"))) return null;
25945
+ if (!existsSync22(join20(zelariRoot, "lessons.jsonl"))) return null;
25766
25946
  const lessons = recallLessons(zelariRoot, {
25767
25947
  maxLessons: 5,
25768
25948
  maxBytes: 2048,
@@ -25785,12 +25965,12 @@ __export(councilFeedback_exports, {
25785
25965
  });
25786
25966
  import {
25787
25967
  promises as fs12,
25788
- existsSync as existsSync22,
25968
+ existsSync as existsSync23,
25789
25969
  readFileSync as readFileSync20,
25790
25970
  writeFileSync as writeFileSync14,
25791
25971
  mkdirSync as mkdirSync9
25792
25972
  } from "node:fs";
25793
- import path16 from "node:path";
25973
+ import path17 from "node:path";
25794
25974
  import os7 from "node:os";
25795
25975
  var FeedbackStore;
25796
25976
  var init_councilFeedback = __esm({
@@ -25801,7 +25981,7 @@ var init_councilFeedback = __esm({
25801
25981
  now;
25802
25982
  entries = [];
25803
25983
  constructor(options = {}) {
25804
- this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path16.join(os7.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
25984
+ this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path17.join(os7.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
25805
25985
  this.now = options.now ?? Date.now;
25806
25986
  this.load();
25807
25987
  }
@@ -25894,7 +26074,7 @@ var init_councilFeedback = __esm({
25894
26074
  }
25895
26075
  // --- persistence ---------------------------------------------------------
25896
26076
  load() {
25897
- if (!existsSync22(this.file)) return;
26077
+ if (!existsSync23(this.file)) return;
25898
26078
  try {
25899
26079
  const raw = readFileSync20(this.file, "utf-8");
25900
26080
  const parsed = JSON.parse(raw);
@@ -25907,7 +26087,7 @@ var init_councilFeedback = __esm({
25907
26087
  }
25908
26088
  }
25909
26089
  save() {
25910
- mkdirSync9(path16.dirname(this.file), { recursive: true });
26090
+ mkdirSync9(path17.dirname(this.file), { recursive: true });
25911
26091
  writeFileSync14(
25912
26092
  this.file,
25913
26093
  JSON.stringify({ entries: this.entries }, null, 2),
@@ -25942,7 +26122,7 @@ __export(fileBackend_exports, {
25942
26122
  });
25943
26123
  import { randomUUID as randomUUID2 } from "node:crypto";
25944
26124
  import { promises as fs13 } from "node:fs";
25945
- import * as path17 from "node:path";
26125
+ import * as path18 from "node:path";
25946
26126
  function tokenize(text) {
25947
26127
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 3);
25948
26128
  }
@@ -25980,8 +26160,8 @@ var init_fileBackend = __esm({
25980
26160
  logPath = "";
25981
26161
  memoryDir = "";
25982
26162
  async init(projectRoot) {
25983
- this.memoryDir = path17.join(projectRoot, ".zelari", "memory");
25984
- this.logPath = path17.join(this.memoryDir, "log.jsonl");
26163
+ this.memoryDir = path18.join(projectRoot, ".zelari", "memory");
26164
+ this.logPath = path18.join(this.memoryDir, "log.jsonl");
25985
26165
  await fs13.mkdir(this.memoryDir, { recursive: true });
25986
26166
  }
25987
26167
  async add(content, metadata = {}, graph) {
@@ -26051,6 +26231,142 @@ var init_fileBackend = __esm({
26051
26231
  }
26052
26232
  });
26053
26233
 
26234
+ // src/cli/checkpoint/checkpointManager.ts
26235
+ import { execFile as execFile2 } from "node:child_process";
26236
+ import { promisify } from "node:util";
26237
+ import { mkdtempSync, rmSync } from "node:fs";
26238
+ import { tmpdir } from "node:os";
26239
+ import path19 from "node:path";
26240
+ import { randomUUID as randomUUID3 } from "node:crypto";
26241
+ async function git(cwd, args, env) {
26242
+ const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
26243
+ maxBuffer: 64 * 1024 * 1024,
26244
+ env: env ? { ...process.env, ...env } : process.env
26245
+ });
26246
+ return stdout;
26247
+ }
26248
+ async function gitSafe(cwd, args, env) {
26249
+ try {
26250
+ return (await git(cwd, args, env)).trim();
26251
+ } catch {
26252
+ return null;
26253
+ }
26254
+ }
26255
+ async function isGitRepo(cwd) {
26256
+ return await gitSafe(cwd, ["rev-parse", "--is-inside-work-tree"]) === "true";
26257
+ }
26258
+ async function withTempIndex(fn) {
26259
+ const dir = mkdtempSync(path19.join(tmpdir(), "zelari-ckpt-"));
26260
+ const indexFile = path19.join(dir, "index");
26261
+ try {
26262
+ return await fn(indexFile);
26263
+ } finally {
26264
+ rmSync(dir, { recursive: true, force: true });
26265
+ }
26266
+ }
26267
+ async function snapshotTree(cwd) {
26268
+ const head = await gitSafe(cwd, ["rev-parse", "HEAD"]);
26269
+ return withTempIndex(async (indexFile) => {
26270
+ const env = { GIT_INDEX_FILE: indexFile };
26271
+ if (head) await git(cwd, ["read-tree", "HEAD"], env);
26272
+ await git(cwd, ["add", "-A"], env);
26273
+ const tree = (await git(cwd, ["write-tree"], env)).trim();
26274
+ return { tree, head };
26275
+ });
26276
+ }
26277
+ async function createCheckpoint(cwd, label = "checkpoint") {
26278
+ if (!await isGitRepo(cwd)) {
26279
+ return { ok: false, error: "not a git repository \u2014 checkpoints require git" };
26280
+ }
26281
+ try {
26282
+ const { tree, head } = await snapshotTree(cwd);
26283
+ const id = randomUUID3().slice(0, 8);
26284
+ const createdAt = Date.now();
26285
+ const message = `zelari-checkpoint ${id}: ${label}`;
26286
+ const commitArgs = ["commit-tree", tree, "-m", message];
26287
+ if (head) commitArgs.push("-p", head);
26288
+ const commit = (await git(cwd, commitArgs)).trim();
26289
+ await git(cwd, ["update-ref", `${REF_PREFIX}${id}`, commit]);
26290
+ return { ok: true, value: { id, label, tree, head, commit, createdAt } };
26291
+ } catch (err) {
26292
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
26293
+ }
26294
+ }
26295
+ async function listCheckpoints(cwd) {
26296
+ const out = await gitSafe(cwd, [
26297
+ "for-each-ref",
26298
+ "--sort=-creatordate",
26299
+ "--format=%(refname) %(objectname) %(creatordate:unix) %(contents:subject)",
26300
+ REF_PREFIX
26301
+ ]);
26302
+ if (!out) return [];
26303
+ const checkpoints = [];
26304
+ for (const line of out.split("\n")) {
26305
+ if (!line.trim()) continue;
26306
+ const [refname, commit, unix, ...subjectParts] = line.split(" ");
26307
+ if (!refname?.startsWith(REF_PREFIX)) continue;
26308
+ const id = refname.slice(REF_PREFIX.length);
26309
+ const subject = subjectParts.join(" ");
26310
+ const label = subject.replace(/^zelari-checkpoint\s+\S+:\s*/, "") || "checkpoint";
26311
+ const tree = await gitSafe(cwd, ["rev-parse", `${commit}^{tree}`]) ?? "";
26312
+ checkpoints.push({
26313
+ id,
26314
+ label,
26315
+ tree,
26316
+ head: null,
26317
+ commit: commit ?? "",
26318
+ createdAt: (Number(unix) || 0) * 1e3
26319
+ });
26320
+ }
26321
+ return checkpoints;
26322
+ }
26323
+ async function latestCheckpoint(cwd) {
26324
+ return (await listCheckpoints(cwd))[0] ?? null;
26325
+ }
26326
+ async function restoreCheckpoint(cwd, id) {
26327
+ if (!await isGitRepo(cwd)) {
26328
+ return { ok: false, error: "not a git repository \u2014 checkpoints require git" };
26329
+ }
26330
+ try {
26331
+ const target = id ? (await listCheckpoints(cwd)).find((c) => c.id === id) : await latestCheckpoint(cwd);
26332
+ if (!target) {
26333
+ return { ok: false, error: id ? `checkpoint "${id}" not found` : "no checkpoints to restore" };
26334
+ }
26335
+ const snapTree = target.tree || await gitSafe(cwd, ["rev-parse", `${target.commit}^{tree}`]);
26336
+ if (!snapTree) return { ok: false, error: "could not resolve checkpoint tree" };
26337
+ const current = await snapshotTree(cwd);
26338
+ const addedOut = await gitSafe(cwd, [
26339
+ "diff",
26340
+ "--name-only",
26341
+ "--diff-filter=A",
26342
+ snapTree,
26343
+ current.tree
26344
+ ]);
26345
+ const added = addedOut ? addedOut.split("\n").filter((l) => l.trim()) : [];
26346
+ await git(cwd, ["read-tree", snapTree]);
26347
+ await git(cwd, ["checkout-index", "-a", "-f"]);
26348
+ const deleted = [];
26349
+ for (const rel2 of added) {
26350
+ try {
26351
+ rmSync(path19.join(cwd, rel2), { force: true });
26352
+ deleted.push(rel2);
26353
+ } catch {
26354
+ }
26355
+ }
26356
+ return { ok: true, value: { id: target.id, deleted } };
26357
+ } catch (err) {
26358
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
26359
+ }
26360
+ }
26361
+ var execFileAsync, REF_PREFIX;
26362
+ var init_checkpointManager = __esm({
26363
+ "src/cli/checkpoint/checkpointManager.ts"() {
26364
+ "use strict";
26365
+ execFileAsync = promisify(execFile2);
26366
+ REF_PREFIX = "refs/zelari/checkpoints/";
26367
+ }
26368
+ });
26369
+
26054
26370
  // src/cli/zelariMission.ts
26055
26371
  var zelariMission_exports = {};
26056
26372
  __export(zelariMission_exports, {
@@ -26060,9 +26376,9 @@ __export(zelariMission_exports, {
26060
26376
  resolveMaxStall: () => resolveMaxStall,
26061
26377
  runZelariMission: () => runZelariMission
26062
26378
  });
26063
- import { randomUUID as randomUUID3 } from "node:crypto";
26379
+ import { randomUUID as randomUUID4 } from "node:crypto";
26064
26380
  import { promises as fs14 } from "node:fs";
26065
- import * as path18 from "node:path";
26381
+ import * as path20 from "node:path";
26066
26382
  function resolveMaxIterations(env = process.env) {
26067
26383
  const raw = env.ZELARI_MISSION_MAX_ITER;
26068
26384
  const n = raw ? Number.parseInt(raw, 10) : DEFAULT_MAX_ITER;
@@ -26078,10 +26394,10 @@ function isMissionAutoStart(env = process.env) {
26078
26394
  return env.ZELARI_MISSION_AUTO === "1";
26079
26395
  }
26080
26396
  async function writeMissionState(projectRoot, state2) {
26081
- const dir = path18.join(projectRoot, ".zelari");
26397
+ const dir = path20.join(projectRoot, ".zelari");
26082
26398
  await fs14.mkdir(dir, { recursive: true });
26083
26399
  await fs14.writeFile(
26084
- path18.join(dir, "mission-state.json"),
26400
+ path20.join(dir, "mission-state.json"),
26085
26401
  JSON.stringify(state2, null, 2) + "\n",
26086
26402
  "utf8"
26087
26403
  );
@@ -26122,7 +26438,7 @@ async function runZelariMission(userMessage, brief, deps) {
26122
26438
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
26123
26439
  const maxIter = deps.maxIterations ?? resolveMaxIterations(deps.env);
26124
26440
  const maxStall = resolveMaxStall(deps.env);
26125
- const missionId = deps.missionId ?? `m_${randomUUID3().slice(0, 8)}`;
26441
+ const missionId = deps.missionId ?? `m_${randomUUID4().slice(0, 8)}`;
26126
26442
  const startedAt = now().toISOString();
26127
26443
  const state2 = {
26128
26444
  missionId,
@@ -26137,6 +26453,14 @@ async function runZelariMission(userMessage, brief, deps) {
26137
26453
  };
26138
26454
  await deps.memory.init(deps.projectRoot);
26139
26455
  await writeMissionState(deps.projectRoot, state2);
26456
+ if ((deps.env ?? process.env).ZELARI_CHECKPOINT !== "0") {
26457
+ const cp = await createCheckpoint(deps.projectRoot, `zelari mission ${missionId}`);
26458
+ if (cp.ok) {
26459
+ deps.emit(
26460
+ `[zelari] checkpoint ${cp.value.id} creato \u2014 se la missione va storta ripristina con \`/rollback ${cp.value.id}\`.`
26461
+ );
26462
+ }
26463
+ }
26140
26464
  const designFirst = brief.phases[0]?.mode === "design-phase";
26141
26465
  let noWriteStreak = 0;
26142
26466
  for (let i = 1; i <= maxIter; i++) {
@@ -26204,6 +26528,7 @@ var init_zelariMission = __esm({
26204
26528
  "src/cli/zelariMission.ts"() {
26205
26529
  "use strict";
26206
26530
  init_fileBackend();
26531
+ init_checkpointManager();
26207
26532
  DEFAULT_MAX_ITER = 10;
26208
26533
  DEFAULT_MAX_STALL = 2;
26209
26534
  }
@@ -26215,26 +26540,26 @@ __export(doctor_exports, {
26215
26540
  runDoctor: () => runDoctor
26216
26541
  });
26217
26542
  import { execSync } from "node:child_process";
26218
- import { existsSync as existsSync27, readFileSync as readFileSync23, readlinkSync, statSync as statSync6 } from "node:fs";
26543
+ import { existsSync as existsSync28, readFileSync as readFileSync23, readlinkSync, statSync as statSync6 } from "node:fs";
26219
26544
  import { createRequire as createRequire2 } from "node:module";
26220
26545
  import { fileURLToPath as fileURLToPath2 } from "node:url";
26221
- import path24 from "node:path";
26546
+ import path26 from "node:path";
26222
26547
  function findPackageRoot(start) {
26223
26548
  let dir = start;
26224
26549
  for (let i = 0; i < 6; i += 1) {
26225
- const candidate = path24.join(dir, "package.json");
26226
- if (existsSync27(candidate)) {
26550
+ const candidate = path26.join(dir, "package.json");
26551
+ if (existsSync28(candidate)) {
26227
26552
  try {
26228
26553
  const pkg = JSON.parse(readFileSync23(candidate, "utf8"));
26229
26554
  if (pkg.name === "zelari-code") return dir;
26230
26555
  } catch {
26231
26556
  }
26232
26557
  }
26233
- const parent = path24.dirname(dir);
26558
+ const parent = path26.dirname(dir);
26234
26559
  if (parent === dir) break;
26235
26560
  dir = parent;
26236
26561
  }
26237
- return path24.resolve(__dirname3, "..", "..", "..");
26562
+ return path26.resolve(__dirname3, "..", "..", "..");
26238
26563
  }
26239
26564
  function tryExec(cmd) {
26240
26565
  try {
@@ -26248,7 +26573,7 @@ function tryExec(cmd) {
26248
26573
  }
26249
26574
  function readPackageJson3() {
26250
26575
  try {
26251
- const pkgPath = path24.join(packageRoot, "package.json");
26576
+ const pkgPath = path26.join(packageRoot, "package.json");
26252
26577
  return JSON.parse(readFileSync23(pkgPath, "utf8"));
26253
26578
  } catch {
26254
26579
  return null;
@@ -26264,8 +26589,8 @@ function checkShim(pkgName) {
26264
26589
  }
26265
26590
  const isWin = process.platform === "win32";
26266
26591
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
26267
- const shimPath = path24.join(prefix, shimName);
26268
- if (!existsSync27(shimPath)) {
26592
+ const shimPath = path26.join(prefix, shimName);
26593
+ if (!existsSync28(shimPath)) {
26269
26594
  return FAIL(
26270
26595
  `shim not found at ${shimPath}
26271
26596
  fix: npm install -g ${pkgName}@latest --force`
@@ -26292,8 +26617,8 @@ function checkShim(pkgName) {
26292
26617
  fix: npm install -g ${pkgName}@latest --force`
26293
26618
  );
26294
26619
  }
26295
- const resolved = path24.resolve(path24.dirname(shimPath), target);
26296
- const expected = path24.join(
26620
+ const resolved = path26.resolve(path26.dirname(shimPath), target);
26621
+ const expected = path26.join(
26297
26622
  prefix,
26298
26623
  "node_modules",
26299
26624
  pkgName,
@@ -26332,8 +26657,8 @@ function checkNode(pkg) {
26332
26657
  return OK(`node ${raw}`);
26333
26658
  }
26334
26659
  function checkBundle() {
26335
- const bundle = path24.join(packageRoot, "dist", "cli", "main.bundled.js");
26336
- if (!existsSync27(bundle)) {
26660
+ const bundle = path26.join(packageRoot, "dist", "cli", "main.bundled.js");
26661
+ if (!existsSync28(bundle)) {
26337
26662
  return FAIL(
26338
26663
  `dist/cli/main.bundled.js missing at ${bundle}
26339
26664
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -26353,7 +26678,7 @@ function checkRuntimeDeps() {
26353
26678
  const missing = [];
26354
26679
  for (const dep of required2) {
26355
26680
  try {
26356
- const localReq = createRequire2(path24.join(packageRoot, "package.json"));
26681
+ const localReq = createRequire2(path26.join(packageRoot, "package.json"));
26357
26682
  localReq.resolve(dep);
26358
26683
  } catch {
26359
26684
  missing.push(dep);
@@ -26435,7 +26760,7 @@ var init_doctor = __esm({
26435
26760
  "src/cli/utils/doctor.ts"() {
26436
26761
  "use strict";
26437
26762
  require3 = createRequire2(import.meta.url);
26438
- __dirname3 = path24.dirname(fileURLToPath2(import.meta.url));
26763
+ __dirname3 = path26.dirname(fileURLToPath2(import.meta.url));
26439
26764
  packageRoot = findPackageRoot(__dirname3);
26440
26765
  OK = (message) => ({
26441
26766
  ok: true,
@@ -26882,6 +27207,81 @@ function StreamingTail(m) {
26882
27207
  // src/cli/components/StatusBar.tsx
26883
27208
  import React6 from "react";
26884
27209
  import { Box as Box5, Text as Text6 } from "ink";
27210
+
27211
+ // src/cli/modelPricing.ts
27212
+ var PRICES_PER_MILLION = {
27213
+ // xAI Grok
27214
+ "grok-4": { input: 3, output: 15 },
27215
+ "grok-4-fast": { input: 0.2, output: 0.5 },
27216
+ "grok-3": { input: 3, output: 15 },
27217
+ "grok-3-mini": { input: 0.3, output: 0.5 },
27218
+ "grok-2-vision": { input: 2, output: 10 },
27219
+ // GLM / Z.AI
27220
+ "glm-4.6": { input: 0.6, output: 2.2 },
27221
+ "glm-4.5": { input: 0.5, output: 2 },
27222
+ "glm-4.5-air": { input: 0.1, output: 0.6 },
27223
+ "glm-z1": { input: 0.5, output: 2 },
27224
+ // MiniMax
27225
+ "MiniMax-M2.5": { input: 0.2, output: 1.1 },
27226
+ "MiniMax-M2": { input: 0.2, output: 1.1 },
27227
+ "MiniMax-M2-her": { input: 0.3, output: 1.2 },
27228
+ // DeepSeek (global platform) — estimated list prices; override via
27229
+ // ANATHEMA_PRICE_DEEPSEEK_V4_FLASH / ANATHEMA_PRICE_DEEPSEEK_V4_PRO.
27230
+ // DeepSeek prompt-cache hits are ~10× cheaper than a cache miss.
27231
+ "deepseek-v4-flash": { input: 0.14, output: 0.28, cachedInput: 0.014 },
27232
+ "deepseek-v4-pro": { input: 0.55, output: 2.19, cachedInput: 0.055 },
27233
+ // OpenAI (for openai-compatible fallback)
27234
+ "gpt-4o": { input: 2.5, output: 10 },
27235
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
27236
+ "gpt-4-turbo": { input: 10, output: 30 },
27237
+ "o1-preview": { input: 15, output: 60 },
27238
+ "o1-mini": { input: 3, output: 12 }
27239
+ };
27240
+ var DEFAULT_RATE = { input: 1, output: 3 };
27241
+ var DEFAULT_CACHE_DISCOUNT = 0.25;
27242
+ function envOverride(model) {
27243
+ const key = `ANATHEMA_PRICE_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
27244
+ const raw = process.env[key] ?? process.env.ANATHEMA_PRICE_DEFAULT;
27245
+ return parseRateOverride(raw);
27246
+ }
27247
+ function parseRateOverride(raw) {
27248
+ if (!raw) return null;
27249
+ const [inStr, outStr] = raw.split("/");
27250
+ const input = Number.parseFloat(inStr);
27251
+ const output = outStr !== void 0 ? Number.parseFloat(outStr) : input;
27252
+ if (!Number.isFinite(input) || !Number.isFinite(output)) return null;
27253
+ return { input, output };
27254
+ }
27255
+ function getModelRate(model) {
27256
+ if (!model) return DEFAULT_RATE;
27257
+ return envOverride(model) ?? PRICES_PER_MILLION[model] ?? DEFAULT_RATE;
27258
+ }
27259
+ function calculateCost(model, promptTokens, completionTokens, cachedPromptTokens = 0) {
27260
+ if (!Number.isFinite(promptTokens) || promptTokens < 0) return 0;
27261
+ if (!Number.isFinite(completionTokens) || completionTokens < 0) return 0;
27262
+ const rate = getModelRate(model);
27263
+ const cached2 = Number.isFinite(cachedPromptTokens) && cachedPromptTokens > 0 ? Math.min(cachedPromptTokens, promptTokens) : 0;
27264
+ const uncachedPrompt = promptTokens - cached2;
27265
+ const cachedRate = rate.cachedInput ?? rate.input * DEFAULT_CACHE_DISCOUNT;
27266
+ const inputCost = uncachedPrompt / 1e6 * rate.input + cached2 / 1e6 * cachedRate;
27267
+ const outputCost = completionTokens / 1e6 * rate.output;
27268
+ return Number((inputCost + outputCost).toFixed(6));
27269
+ }
27270
+ function formatCost(usd) {
27271
+ if (!Number.isFinite(usd) || usd < 0) return "$0.0000";
27272
+ if (usd === 0) return "$0.0000";
27273
+ if (usd < 1e-4) return "<$0.0001";
27274
+ return `$${usd.toFixed(4)}`;
27275
+ }
27276
+ function formatTokens(tokens) {
27277
+ if (!Number.isFinite(tokens) || tokens < 0) return "0";
27278
+ if (tokens < 1e3) return `${tokens}`;
27279
+ if (tokens < 1e6) return `${(tokens / 1e3).toFixed(1)}k`;
27280
+ if (tokens < 1e9) return `${(tokens / 1e6).toFixed(2)}M`;
27281
+ return `${(tokens / 1e9).toFixed(2)}B`;
27282
+ }
27283
+
27284
+ // src/cli/components/StatusBar.tsx
26885
27285
  function StatusBar({
26886
27286
  model,
26887
27287
  provider,
@@ -26892,7 +27292,9 @@ function StatusBar({
26892
27292
  mode = "agent",
26893
27293
  cwd,
26894
27294
  elapsedMs = null,
26895
- lastMs = null
27295
+ lastMs = null,
27296
+ costUsd = 0,
27297
+ cachedTokens = 0
26896
27298
  }) {
26897
27299
  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(
26898
27300
  Text6,
@@ -26901,7 +27303,7 @@ function StatusBar({
26901
27303
  color: mode === "council" ? "magenta" : mode === "zelari" ? "green" : "cyan"
26902
27304
  },
26903
27305
  mode === "council" ? "\u26EC council" : mode === "zelari" ? "\u26A1 zelari" : "\u23F5 agent"
26904
- ), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (shift+tab)"), /* @__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, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "session ", sessionId))));
27306
+ ), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (shift+tab)"), /* @__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, 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, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "session ", sessionId))));
26905
27307
  }
26906
27308
 
26907
27309
  // src/cli/components/SelectList.tsx
@@ -29307,75 +29709,13 @@ registerCodingSkill(regressionTest);
29307
29709
 
29308
29710
  // src/cli/app.tsx
29309
29711
  init_providerConfig();
29310
-
29311
- // packages/core/dist/harness/index.js
29312
- init_AgentHarness();
29313
-
29314
- // packages/core/dist/core/sessionJsonl.js
29315
- import { promises as fs8 } from "node:fs";
29316
- import path10 from "node:path";
29317
- import os3 from "node:os";
29318
- var SessionJsonlWriter = class {
29319
- filePath;
29320
- onError;
29321
- constructor(sessionId, options = {}) {
29322
- const baseDir = options.baseDir ?? defaultBaseDir();
29323
- this.filePath = path10.join(baseDir, `${sessionId}.jsonl`);
29324
- this.onError = options.onError ?? console.error;
29325
- }
29326
- /** Absolute path to the session JSONL file. */
29327
- get path() {
29328
- return this.filePath;
29329
- }
29330
- /** Append a BrainEvent as one JSON line. Creates the file + parent dirs if missing. */
29331
- async append(event) {
29332
- try {
29333
- await fs8.mkdir(path10.dirname(this.filePath), { recursive: true });
29334
- const line = JSON.stringify({
29335
- ts: event.ts,
29336
- sessionId: event.sessionId,
29337
- event
29338
- }) + "\n";
29339
- await fs8.appendFile(this.filePath, line, { encoding: "utf-8", mode: 420 });
29340
- } catch (err) {
29341
- this.onError(`[sessionJsonl] failed to append event to ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`);
29342
- }
29343
- }
29344
- /** Close the writer (no-op currently, but reserved for future buffered mode). */
29345
- async close() {
29346
- }
29347
- };
29348
- async function readSession(filePath) {
29349
- try {
29350
- const content = await fs8.readFile(filePath, "utf-8");
29351
- const events = [];
29352
- for (const line of content.split("\n")) {
29353
- const trimmed = line.trim();
29354
- if (!trimmed)
29355
- continue;
29356
- try {
29357
- const parsed = JSON.parse(trimmed);
29358
- if (parsed && typeof parsed === "object" && "event" in parsed) {
29359
- events.push(parsed.event);
29360
- }
29361
- } catch {
29362
- }
29363
- }
29364
- return events;
29365
- } catch (err) {
29366
- if (err.code === "ENOENT")
29367
- return [];
29368
- throw err;
29369
- }
29370
- }
29371
- function defaultBaseDir() {
29372
- return path10.join(os3.tmpdir(), "zelari-code", "sessions");
29373
- }
29712
+ init_harness();
29374
29713
 
29375
29714
  // src/cli/hooks/useSession.ts
29376
29715
  import { useState as useState5, useRef as useRef3, useCallback, useEffect as useEffect4 } from "react";
29377
29716
 
29378
29717
  // src/cli/sessionManager.ts
29718
+ init_harness();
29379
29719
  import { promises as fs9, existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, unlinkSync, statSync } from "node:fs";
29380
29720
  import path11 from "node:path";
29381
29721
  import os4 from "node:os";
@@ -29497,6 +29837,9 @@ async function loadSessionEvents(id) {
29497
29837
  return readSession(filePath);
29498
29838
  }
29499
29839
 
29840
+ // src/cli/hooks/useSession.ts
29841
+ init_harness();
29842
+
29500
29843
  // src/cli/hooks/eventsToMessages.ts
29501
29844
  function eventsToMessages(events) {
29502
29845
  const out = [];
@@ -29777,6 +30120,7 @@ ${lines.join("\n")}`;
29777
30120
  }
29778
30121
 
29779
30122
  // src/cli/hooks/useChatTurn.ts
30123
+ init_harness();
29780
30124
  import { useState as useState6, useRef as useRef4, useCallback as useCallback2 } from "react";
29781
30125
 
29782
30126
  // src/cli/metrics.ts
@@ -30119,33 +30463,317 @@ function safeStringify(value) {
30119
30463
  }
30120
30464
  }
30121
30465
 
30466
+ // src/cli/diagnostics/engine.ts
30467
+ import { spawn as spawn2 } from "node:child_process";
30468
+ import { existsSync as existsSync7 } from "node:fs";
30469
+ import path15 from "node:path";
30470
+ function parseEslintJson(stdout, _file2) {
30471
+ const json2 = safeJson(stdout);
30472
+ if (!Array.isArray(json2)) return [];
30473
+ const out = [];
30474
+ for (const fileResult of json2) {
30475
+ if (!fileResult || typeof fileResult !== "object") continue;
30476
+ const fr = fileResult;
30477
+ const filePath = typeof fr.filePath === "string" ? fr.filePath : _file2;
30478
+ if (!Array.isArray(fr.messages)) continue;
30479
+ for (const m of fr.messages) {
30480
+ if (!m || typeof m !== "object") continue;
30481
+ const msg = m;
30482
+ out.push({
30483
+ file: filePath,
30484
+ line: typeof msg.line === "number" ? msg.line : 0,
30485
+ ...typeof msg.column === "number" ? { column: msg.column } : {},
30486
+ // ESLint severity: 2 = error, 1 = warning.
30487
+ severity: msg.severity === 2 ? "error" : "warning",
30488
+ message: typeof msg.message === "string" ? msg.message : "(no message)",
30489
+ ...typeof msg.ruleId === "string" && msg.ruleId ? { code: msg.ruleId } : {},
30490
+ source: "eslint"
30491
+ });
30492
+ }
30493
+ }
30494
+ return out;
30495
+ }
30496
+ function parseRuffJson(stdout, _file2) {
30497
+ const json2 = safeJson(stdout);
30498
+ if (!Array.isArray(json2)) return [];
30499
+ const out = [];
30500
+ for (const issue2 of json2) {
30501
+ if (!issue2 || typeof issue2 !== "object") continue;
30502
+ const it = issue2;
30503
+ const code = typeof it.code === "string" ? it.code : void 0;
30504
+ out.push({
30505
+ file: typeof it.filename === "string" ? it.filename : _file2,
30506
+ line: typeof it.location?.row === "number" ? it.location.row : 0,
30507
+ ...typeof it.location?.column === "number" ? { column: it.location.column } : {},
30508
+ // Ruff has no severity field; E999 is a syntax error, everything else
30509
+ // is a lint warning.
30510
+ severity: code === "E999" ? "error" : "warning",
30511
+ message: typeof it.message === "string" ? it.message : "(no message)",
30512
+ ...code ? { code } : {},
30513
+ source: "ruff"
30514
+ });
30515
+ }
30516
+ return out;
30517
+ }
30518
+ function safeJson(s) {
30519
+ const trimmed = s.trim();
30520
+ if (!trimmed) return null;
30521
+ try {
30522
+ return JSON.parse(trimmed);
30523
+ } catch {
30524
+ return null;
30525
+ }
30526
+ }
30527
+ var DEFAULT_PROVIDERS = [
30528
+ {
30529
+ name: "eslint",
30530
+ bin: "eslint",
30531
+ extensions: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"],
30532
+ args: (file2) => ["--format", "json", file2],
30533
+ parse: parseEslintJson
30534
+ },
30535
+ {
30536
+ name: "ruff",
30537
+ bin: "ruff",
30538
+ extensions: [".py"],
30539
+ args: (file2) => ["check", "--output-format", "json", file2],
30540
+ parse: parseRuffJson
30541
+ }
30542
+ ];
30543
+ function providerForFile(file2, providers = DEFAULT_PROVIDERS) {
30544
+ const ext = path15.extname(file2).toLowerCase();
30545
+ return providers.find((p3) => p3.extensions.includes(ext)) ?? null;
30546
+ }
30547
+ function resolveBin(bin, cwd) {
30548
+ const suffixes = process.platform === "win32" ? [".cmd", ".exe", ""] : [""];
30549
+ let dir = cwd;
30550
+ for (let i = 0; i < 6; i += 1) {
30551
+ for (const suffix of suffixes) {
30552
+ const candidate = path15.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
30553
+ if (existsSync7(candidate)) return candidate;
30554
+ }
30555
+ const parent = path15.dirname(dir);
30556
+ if (parent === dir) break;
30557
+ dir = parent;
30558
+ }
30559
+ return bin;
30560
+ }
30561
+ var defaultRunner = (cmd, args, opts) => new Promise((resolve) => {
30562
+ let stdout = "";
30563
+ let stderr = "";
30564
+ let settled = false;
30565
+ const done = (r) => {
30566
+ if (settled) return;
30567
+ settled = true;
30568
+ resolve(r);
30569
+ };
30570
+ let child;
30571
+ try {
30572
+ child = process.platform === "win32" ? spawn2(`${cmd} ${args.join(" ")}`, { cwd: opts.cwd, shell: true }) : spawn2(cmd, args, { cwd: opts.cwd });
30573
+ } catch {
30574
+ done({ code: null, stdout: "", stderr: "" });
30575
+ return;
30576
+ }
30577
+ const timer = setTimeout(() => {
30578
+ try {
30579
+ child.kill();
30580
+ } catch {
30581
+ }
30582
+ done({ code: null, stdout, stderr });
30583
+ }, opts.timeoutMs);
30584
+ child.stdout?.on("data", (c) => {
30585
+ stdout += c.toString();
30586
+ });
30587
+ child.stderr?.on("data", (c) => {
30588
+ stderr += c.toString();
30589
+ });
30590
+ child.on("error", () => {
30591
+ clearTimeout(timer);
30592
+ done({ code: null, stdout: "", stderr: "" });
30593
+ });
30594
+ child.on("close", (code) => {
30595
+ clearTimeout(timer);
30596
+ done({ code, stdout, stderr });
30597
+ });
30598
+ });
30599
+ async function runDiagnosticsForFile(file2, options = {}) {
30600
+ const provider = providerForFile(file2, options.providers);
30601
+ if (!provider) return [];
30602
+ const cwd = options.cwd ?? process.cwd();
30603
+ const timeoutMs = options.timeoutMs ?? 5e3;
30604
+ const runner = options.runner ?? defaultRunner;
30605
+ try {
30606
+ const bin = options.runner ? provider.bin : resolveBin(provider.bin, cwd);
30607
+ const result = await runner(bin, provider.args(file2), { cwd, timeoutMs });
30608
+ return provider.parse(result.stdout, file2);
30609
+ } catch {
30610
+ return [];
30611
+ }
30612
+ }
30613
+ function formatDiagnostics(diagnostics, opts = {}) {
30614
+ if (diagnostics.length === 0) return "";
30615
+ const maxLines = opts.maxLines ?? 20;
30616
+ const rank = { error: 0, warning: 1, info: 2 };
30617
+ const sorted = [...diagnostics].sort(
30618
+ (a, b) => rank[a.severity] - rank[b.severity] || a.line - b.line
30619
+ );
30620
+ const errors = sorted.filter((d) => d.severity === "error").length;
30621
+ const warnings = sorted.filter((d) => d.severity === "warning").length;
30622
+ const header = `\u26A0 ${diagnostics.length} diagnostic${diagnostics.length === 1 ? "" : "s"} (${errors} error${errors === 1 ? "" : "s"}, ${warnings} warning${warnings === 1 ? "" : "s"}) \u2014 fix before continuing:`;
30623
+ const shown = sorted.slice(0, maxLines).map((d) => {
30624
+ const loc = opts.relativeTo ? relative(opts.relativeTo, d.file) : d.file;
30625
+ const pos = d.column ? `${d.line}:${d.column}` : `${d.line}`;
30626
+ const tag = d.severity === "error" ? "error" : d.severity === "warning" ? "warn" : "info";
30627
+ const code = d.code ? ` [${d.code}]` : "";
30628
+ return ` ${loc}:${pos} ${tag}${code}: ${d.message} (${d.source})`;
30629
+ });
30630
+ const overflow = sorted.length > maxLines ? [` \u2026 and ${sorted.length - maxLines} more`] : [];
30631
+ return [header, ...shown, ...overflow].join("\n");
30632
+ }
30633
+ function relative(from, to) {
30634
+ try {
30635
+ const rel2 = path15.relative(from, to);
30636
+ return rel2 && !rel2.startsWith("..") ? rel2 : to;
30637
+ } catch {
30638
+ return to;
30639
+ }
30640
+ }
30641
+
30642
+ // src/cli/tools/taskTool.ts
30643
+ init_zod();
30644
+ init_toolTypes();
30645
+ var SUBAGENT_SYSTEM_PROMPT = [
30646
+ "You are a focused sub-agent spawned to answer ONE bounded question or",
30647
+ "complete ONE small research task for a parent coding agent.",
30648
+ "",
30649
+ "You have READ-ONLY tools (read files, list files, search, fetch URLs).",
30650
+ "You cannot edit files or run shell commands \u2014 do not attempt to.",
30651
+ "",
30652
+ "Work efficiently: gather only what you need, then STOP and reply with a",
30653
+ "concise, self-contained conclusion the parent can act on. Include concrete",
30654
+ "references (file paths, line numbers, symbol names) but do not paste large",
30655
+ "file dumps. Do not ask the parent follow-up questions \u2014 deliver your best",
30656
+ "answer with what you can find."
30657
+ ].join("\n");
30658
+ var TaskArgsSchema = external_exports.object({
30659
+ description: external_exports.string().min(1).describe("A 3-6 word label for the sub-task (for logs/UI)."),
30660
+ prompt: external_exports.string().min(1).describe(
30661
+ "The full, self-contained instruction for the sub-agent. It has no access to this conversation, so include all context it needs."
30662
+ )
30663
+ });
30664
+ async function runSubAgent(harness) {
30665
+ let current = "";
30666
+ let lastCompleted = "";
30667
+ let error51;
30668
+ for await (const ev of harness.run()) {
30669
+ switch (ev.type) {
30670
+ case "message_start":
30671
+ current = "";
30672
+ break;
30673
+ case "message_delta":
30674
+ current += ev.delta;
30675
+ break;
30676
+ case "message_end":
30677
+ if (current.trim()) lastCompleted = current;
30678
+ current = "";
30679
+ break;
30680
+ case "error":
30681
+ error51 = ev.message;
30682
+ break;
30683
+ default:
30684
+ break;
30685
+ }
30686
+ }
30687
+ const result = (lastCompleted || current).trim();
30688
+ return { result, ...error51 ? { error: error51 } : {} };
30689
+ }
30690
+ function createTaskTool(deps) {
30691
+ return {
30692
+ name: "task",
30693
+ description: 'Delegate a focused, READ-ONLY research/exploration sub-task to an isolated sub-agent that has its own fresh context and returns only a concise conclusion. Use it to keep your own context lean \u2014 e.g. "find where feature X is implemented and summarize how it works" \u2014 on large codebases. The sub-agent cannot edit files or run shell commands. Provide a fully self-contained `prompt` (it cannot see this conversation).',
30694
+ // The sub-agent reads the workspace and may hit the network (fetch/search),
30695
+ // but never writes or runs shell commands.
30696
+ permissions: ["read", "network"],
30697
+ // Sub-agents run a full (bounded) agent loop — allow generous wall time.
30698
+ timeoutMs: 3e5,
30699
+ inputSchema: TaskArgsSchema,
30700
+ execute: async (args, ctx) => {
30701
+ let sub;
30702
+ try {
30703
+ sub = await deps.createSubAgentContext();
30704
+ } catch (err) {
30705
+ return typedErr(`task: could not initialize sub-agent \u2014 ${err instanceof Error ? err.message : String(err)}`);
30706
+ }
30707
+ if (!sub) {
30708
+ return typedErr("task: no provider configured for the sub-agent (set an API key / run /login).");
30709
+ }
30710
+ const config2 = {
30711
+ model: sub.model,
30712
+ provider: sub.provider,
30713
+ messages: [
30714
+ { role: "system", content: SUBAGENT_SYSTEM_PROMPT },
30715
+ { role: "user", content: args.prompt }
30716
+ ],
30717
+ tools: sub.tools,
30718
+ toolRegistry: sub.registry,
30719
+ providerStream: sub.providerStream,
30720
+ cwd: ctx.cwd,
30721
+ // Guard against a single turn ballooning context on "explore" tasks.
30722
+ maxToolCallsPerTurn: 5
30723
+ };
30724
+ let harness;
30725
+ try {
30726
+ if (deps.harnessFactory) {
30727
+ harness = deps.harnessFactory(config2);
30728
+ } else {
30729
+ const { AgentHarness: AgentHarness2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
30730
+ harness = new AgentHarness2(config2);
30731
+ }
30732
+ } catch (err) {
30733
+ return typedErr(`task: failed to start sub-agent \u2014 ${err instanceof Error ? err.message : String(err)}`);
30734
+ }
30735
+ const { result, error: error51 } = await runSubAgent(harness);
30736
+ if (!result) {
30737
+ return typedErr(`task: sub-agent produced no output${error51 ? ` (${error51})` : ""}.`);
30738
+ }
30739
+ return typedOk({ result });
30740
+ }
30741
+ };
30742
+ }
30743
+
30122
30744
  // src/cli/toolRegistry.ts
30745
+ init_openai_compatible();
30123
30746
  function createBuiltinToolRegistry(options = {}) {
30124
30747
  const root = options.root ?? process.cwd();
30125
30748
  const audit = options.audit ?? new AuditLogger();
30126
30749
  const sessionId = options.sessionId ?? "cli";
30750
+ const diagnosticsOn = options.diagnostics ?? process.env.ZELARI_DIAGNOSTICS !== "0";
30751
+ const withDiag = (t) => diagnosticsOn ? wrapWithDiagnostics(t, root, options.diagnosticsRunner) : t;
30127
30752
  const safeReadFile = wrapWithSandbox(readFileTool, ["path"], root, audit, sessionId);
30128
- const safeWriteFile = wrapWithSandbox(writeFileTool, ["path"], root, audit, sessionId);
30129
- const safeEditFile = wrapWithSandbox(editFileTool, ["path"], root, audit, sessionId);
30753
+ const safeWriteFile = withDiag(wrapWithSandbox(writeFileTool, ["path"], root, audit, sessionId));
30754
+ const safeEditFile = withDiag(wrapWithSandbox(editFileTool, ["path"], root, audit, sessionId));
30130
30755
  const safeGrepContent = wrapWithSandbox(grepContentTool, ["path"], root, audit, sessionId);
30131
30756
  const safeListFiles = wrapWithSandbox(listFilesTool, ["path"], root, audit, sessionId);
30132
30757
  const safeShowDiff = wrapWithSandbox(showDiffTool, ["path"], root, audit, sessionId);
30133
- const safeApplyDiff = wrapWithSandbox(applyDiffTool, ["path"], root, audit, sessionId);
30758
+ const safeApplyDiff = withDiag(wrapWithSandbox(applyDiffTool, ["path"], root, audit, sessionId));
30134
30759
  const safeBash = wrapWithShellSafety(bashTool, audit, sessionId);
30135
30760
  const safeFetchUrl = wrapWithAudit(fetchUrlTool, audit, sessionId);
30136
30761
  const safeWebSearch = wrapWithAudit(webSearchTool, audit, sessionId);
30137
30762
  const registry3 = new ToolRegistry();
30763
+ const readOnly = options.readOnly === true;
30138
30764
  registry3.register(safeReadFile);
30139
- registry3.register(safeWriteFile);
30140
- registry3.register(safeEditFile);
30141
- registry3.register(safeBash);
30142
30765
  registry3.register(safeGrepContent);
30143
30766
  registry3.register(safeListFiles);
30144
30767
  registry3.register(safeShowDiff);
30145
- registry3.register(safeApplyDiff);
30146
30768
  registry3.register(safeFetchUrl);
30147
30769
  registry3.register(safeWebSearch);
30148
- const tools = [
30770
+ if (!readOnly) {
30771
+ registry3.register(safeWriteFile);
30772
+ registry3.register(safeEditFile);
30773
+ registry3.register(safeBash);
30774
+ registry3.register(safeApplyDiff);
30775
+ }
30776
+ const summary = readOnly ? [safeReadFile, safeGrepContent, safeListFiles, safeShowDiff, safeFetchUrl, safeWebSearch] : [
30149
30777
  safeReadFile,
30150
30778
  safeWriteFile,
30151
30779
  safeEditFile,
@@ -30156,11 +30784,44 @@ function createBuiltinToolRegistry(options = {}) {
30156
30784
  safeApplyDiff,
30157
30785
  safeFetchUrl,
30158
30786
  safeWebSearch
30159
- ].map((t) => ({
30787
+ ];
30788
+ const tools = summary.map((t) => ({
30160
30789
  name: t.name,
30161
30790
  description: t.description,
30162
30791
  permissions: t.permissions ?? []
30163
30792
  }));
30793
+ if (!readOnly && options.enableTask !== false) {
30794
+ const taskTool = createTaskTool({
30795
+ createSubAgentContext: async () => {
30796
+ const cfg = await providerFromEnv();
30797
+ if (!cfg) return null;
30798
+ const { registry: subRegistry } = createBuiltinToolRegistry({
30799
+ root,
30800
+ audit,
30801
+ sessionId,
30802
+ readOnly: true,
30803
+ diagnostics: false
30804
+ });
30805
+ return {
30806
+ providerStream: openaiCompatibleProvider(cfg),
30807
+ model: cfg.model,
30808
+ provider: "openai-compatible",
30809
+ registry: subRegistry,
30810
+ tools: subRegistry.toOpenAITools().map((t) => ({
30811
+ name: t.function.name,
30812
+ description: t.function.description,
30813
+ parameters: t.function.parameters
30814
+ }))
30815
+ };
30816
+ }
30817
+ });
30818
+ registry3.register(taskTool);
30819
+ tools.push({
30820
+ name: taskTool.name,
30821
+ description: taskTool.description,
30822
+ permissions: taskTool.permissions ?? []
30823
+ });
30824
+ }
30164
30825
  return { registry: registry3, tools };
30165
30826
  }
30166
30827
  function wrapWithSandbox(original, pathArgs, root, audit, sessionId) {
@@ -30210,6 +30871,36 @@ function wrapWithSandbox(original, pathArgs, root, audit, sessionId) {
30210
30871
  }
30211
30872
  };
30212
30873
  }
30874
+ function wrapWithDiagnostics(original, root, runner) {
30875
+ return {
30876
+ ...original,
30877
+ execute: async (rawArgs, ctx) => {
30878
+ const result = await original.execute(rawArgs, ctx);
30879
+ if (!result.ok) return result;
30880
+ if (rawArgs.dryRun === true) return result;
30881
+ const value = result.value;
30882
+ const filePath = value && typeof value === "object" && typeof value.path === "string" ? value.path : void 0;
30883
+ if (!filePath) return result;
30884
+ try {
30885
+ const timeoutMs = Number(process.env.ZELARI_DIAGNOSTICS_TIMEOUT_MS) || 5e3;
30886
+ const diags = await runDiagnosticsForFile(filePath, {
30887
+ cwd: root,
30888
+ timeoutMs,
30889
+ ...runner ? { runner } : {}
30890
+ });
30891
+ const formatted = formatDiagnostics(diags, { relativeTo: root });
30892
+ if (formatted) {
30893
+ return {
30894
+ ok: true,
30895
+ value: { ...value, diagnostics: formatted }
30896
+ };
30897
+ }
30898
+ } catch {
30899
+ }
30900
+ return result;
30901
+ }
30902
+ };
30903
+ }
30213
30904
  function wrapWithAudit(original, audit, sessionId) {
30214
30905
  return {
30215
30906
  ...original,
@@ -30374,69 +31065,16 @@ function finalizeStreamingAssistant(setMessages) {
30374
31065
  });
30375
31066
  }
30376
31067
 
30377
- // src/cli/modelPricing.ts
30378
- var PRICES_PER_MILLION = {
30379
- // xAI Grok
30380
- "grok-4": { input: 3, output: 15 },
30381
- "grok-4-fast": { input: 0.2, output: 0.5 },
30382
- "grok-3": { input: 3, output: 15 },
30383
- "grok-3-mini": { input: 0.3, output: 0.5 },
30384
- "grok-2-vision": { input: 2, output: 10 },
30385
- // GLM / Z.AI
30386
- "glm-4.6": { input: 0.6, output: 2.2 },
30387
- "glm-4.5": { input: 0.5, output: 2 },
30388
- "glm-4.5-air": { input: 0.1, output: 0.6 },
30389
- "glm-z1": { input: 0.5, output: 2 },
30390
- // MiniMax
30391
- "MiniMax-M2.5": { input: 0.2, output: 1.1 },
30392
- "MiniMax-M2": { input: 0.2, output: 1.1 },
30393
- "MiniMax-M2-her": { input: 0.3, output: 1.2 },
30394
- // DeepSeek (global platform) — estimated list prices; override via
30395
- // ANATHEMA_PRICE_DEEPSEEK_V4_FLASH / ANATHEMA_PRICE_DEEPSEEK_V4_PRO.
30396
- "deepseek-v4-flash": { input: 0.14, output: 0.28 },
30397
- "deepseek-v4-pro": { input: 0.55, output: 2.19 },
30398
- // OpenAI (for openai-compatible fallback)
30399
- "gpt-4o": { input: 2.5, output: 10 },
30400
- "gpt-4o-mini": { input: 0.15, output: 0.6 },
30401
- "gpt-4-turbo": { input: 10, output: 30 },
30402
- "o1-preview": { input: 15, output: 60 },
30403
- "o1-mini": { input: 3, output: 12 }
30404
- };
30405
- var DEFAULT_RATE = { input: 1, output: 3 };
30406
- function envOverride(model) {
30407
- const key = `ANATHEMA_PRICE_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
30408
- const raw = process.env[key] ?? process.env.ANATHEMA_PRICE_DEFAULT;
30409
- return parseRateOverride(raw);
30410
- }
30411
- function parseRateOverride(raw) {
30412
- if (!raw) return null;
30413
- const [inStr, outStr] = raw.split("/");
30414
- const input = Number.parseFloat(inStr);
30415
- const output = outStr !== void 0 ? Number.parseFloat(outStr) : input;
30416
- if (!Number.isFinite(input) || !Number.isFinite(output)) return null;
30417
- return { input, output };
30418
- }
30419
- function getModelRate(model) {
30420
- if (!model) return DEFAULT_RATE;
30421
- return envOverride(model) ?? PRICES_PER_MILLION[model] ?? DEFAULT_RATE;
30422
- }
30423
- function calculateCost(model, promptTokens, completionTokens) {
30424
- if (!Number.isFinite(promptTokens) || promptTokens < 0) return 0;
30425
- if (!Number.isFinite(completionTokens) || completionTokens < 0) return 0;
30426
- const rate = getModelRate(model);
30427
- const inputCost = promptTokens / 1e6 * rate.input;
30428
- const outputCost = completionTokens / 1e6 * rate.output;
30429
- return Number((inputCost + outputCost).toFixed(6));
30430
- }
30431
-
30432
31068
  // src/cli/hooks/chatStats.ts
30433
31069
  function computeSessionStatsDelta(realUsage, userText, assistantContent, model, prev2) {
30434
31070
  const promptTokens = realUsage ? realUsage.promptTokens : Math.ceil(userText.length / 4);
30435
31071
  const completionTokens = realUsage ? realUsage.completionTokens : Math.ceil(assistantContent.length / 4);
30436
- const turnCost = calculateCost(model, promptTokens, completionTokens);
31072
+ const cachedPromptTokens = realUsage?.cachedPromptTokens ?? 0;
31073
+ const turnCost = calculateCost(model, promptTokens, completionTokens, cachedPromptTokens);
30437
31074
  return {
30438
31075
  totalTokens: prev2.totalTokens + promptTokens + completionTokens,
30439
- totalCostUsd: prev2.totalCostUsd + turnCost
31076
+ totalCostUsd: prev2.totalCostUsd + turnCost,
31077
+ cachedTokens: (prev2.cachedTokens ?? 0) + cachedPromptTokens
30440
31078
  };
30441
31079
  }
30442
31080
 
@@ -31381,6 +32019,8 @@ function handleSlashCommand(text, availableSkills) {
31381
32019
  /new \u2014 start a fresh session
31382
32020
  /diff [--staged] \u2014 show uncommitted changes (or staged with --staged)
31383
32021
  /undo [--yes] \u2014 revert working-tree changes (destructive! requires --yes)
32022
+ /checkpoint [label] \u2014 snapshot the working tree as a restore point
32023
+ /rollback [id|latest] \u2014 restore the working tree to a checkpoint (no arg: list)
31384
32024
  /help \u2014 show this help
31385
32025
  /exit \u2014 exit the CLI
31386
32026
 
@@ -31718,6 +32358,25 @@ ${formatSkillList(availableSkills)}`
31718
32358
  message: "\u26A0 /undo is DESTRUCTIVE \u2014 it reverts all unstaged modifications and unstages everything.\nUse `/undo --yes` (or `/undo -y`) to confirm."
31719
32359
  };
31720
32360
  }
32361
+ case "checkpoint": {
32362
+ const label = args.join(" ").trim();
32363
+ return {
32364
+ handled: true,
32365
+ kind: "checkpoint_create",
32366
+ ...label ? { checkpointLabel: label } : {}
32367
+ };
32368
+ }
32369
+ case "rollback": {
32370
+ const target = args[0]?.trim();
32371
+ if (!target) {
32372
+ return { handled: true, kind: "rollback_list" };
32373
+ }
32374
+ return {
32375
+ handled: true,
32376
+ kind: "rollback",
32377
+ ...target === "latest" ? {} : { rollbackId: target }
32378
+ };
32379
+ }
31721
32380
  case "workspace": {
31722
32381
  const sub = args[0];
31723
32382
  if (!sub || sub === "--help" || sub === "help") {
@@ -31781,13 +32440,13 @@ ${formatSkillList(availableSkills)}`
31781
32440
  }
31782
32441
 
31783
32442
  // src/cli/gitOps.ts
31784
- import { execFile as execFile2 } from "node:child_process";
31785
- import { promisify } from "node:util";
31786
- import path19 from "node:path";
31787
- var execFileAsync = promisify(execFile2);
31788
- async function git(cwd, args) {
32443
+ import { execFile as execFile3 } from "node:child_process";
32444
+ import { promisify as promisify2 } from "node:util";
32445
+ import path21 from "node:path";
32446
+ var execFileAsync2 = promisify2(execFile3);
32447
+ async function git2(cwd, args) {
31789
32448
  try {
31790
- const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
32449
+ const { stdout } = await execFileAsync2("git", ["-C", cwd, ...args], {
31791
32450
  maxBuffer: 16 * 1024 * 1024
31792
32451
  });
31793
32452
  return stdout;
@@ -31795,15 +32454,15 @@ async function git(cwd, args) {
31795
32454
  return null;
31796
32455
  }
31797
32456
  }
31798
- async function isGitRepo(cwd = process.cwd()) {
31799
- const out = await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
32457
+ async function isGitRepo2(cwd = process.cwd()) {
32458
+ const out = await git2(cwd, ["rev-parse", "--is-inside-work-tree"]);
31800
32459
  return out?.trim() === "true";
31801
32460
  }
31802
32461
  async function getWorkingDiff(opts = {}) {
31803
32462
  const cwd = opts.cwd ?? process.cwd();
31804
32463
  const maxChars = opts.maxChars ?? 5e4;
31805
- const unstaged = await git(cwd, ["diff"]) ?? "";
31806
- const staged = opts.staged ? await git(cwd, ["diff", "--cached"]) ?? "" : "";
32464
+ const unstaged = await git2(cwd, ["diff"]) ?? "";
32465
+ const staged = opts.staged ? await git2(cwd, ["diff", "--cached"]) ?? "" : "";
31807
32466
  const combined = [staged, unstaged].filter((s) => s.length > 0).join("\n");
31808
32467
  if (combined.length === 0) {
31809
32468
  return { diff: "", truncated: false, empty: true };
@@ -31816,11 +32475,11 @@ async function getWorkingDiff(opts = {}) {
31816
32475
  async function undoWorkingChanges(opts = {}) {
31817
32476
  const cwd = opts.cwd ?? process.cwd();
31818
32477
  const unstage = opts.unstage !== false;
31819
- const statusBefore = await git(cwd, ["status", "--porcelain"]) ?? "";
32478
+ const statusBefore = await git2(cwd, ["status", "--porcelain"]) ?? "";
31820
32479
  const modified = statusBefore.split("\n").filter((line) => line.length > 0).filter((line) => !line.startsWith("??") && !line.startsWith("!!")).map((line) => line.slice(3).trim());
31821
- await git(cwd, ["checkout", "--", "."]);
32480
+ await git2(cwd, ["checkout", "--", "."]);
31822
32481
  if (unstage) {
31823
- await git(cwd, ["reset", "HEAD", "--", "."]);
32482
+ await git2(cwd, ["reset", "HEAD", "--", "."]);
31824
32483
  }
31825
32484
  return {
31826
32485
  reverted: modified,
@@ -31829,14 +32488,14 @@ async function undoWorkingChanges(opts = {}) {
31829
32488
  };
31830
32489
  }
31831
32490
  function defaultProjectRoot() {
31832
- return path19.resolve(__dirname, "..", "..", "..");
32491
+ return path21.resolve(__dirname, "..", "..", "..");
31833
32492
  }
31834
32493
 
31835
32494
  // src/cli/slashHandlers/git.ts
31836
32495
  async function handleDiff(ctx, diffStaged) {
31837
32496
  try {
31838
32497
  const repoRoot = defaultProjectRoot();
31839
- if (!await isGitRepo(repoRoot)) {
32498
+ if (!await isGitRepo2(repoRoot)) {
31840
32499
  appendSystem(ctx.setMessages, "[diff] not a git repository \u2014 nothing to show");
31841
32500
  return;
31842
32501
  }
@@ -31857,7 +32516,7 @@ async function handleUndo(ctx, warningMessage, doConfirm) {
31857
32516
  if (!doConfirm) return;
31858
32517
  try {
31859
32518
  const repoRoot = defaultProjectRoot();
31860
- if (!await isGitRepo(repoRoot)) {
32519
+ if (!await isGitRepo2(repoRoot)) {
31861
32520
  appendSystem(ctx.setMessages, "[undo] not a git repository \u2014 nothing to revert");
31862
32521
  return;
31863
32522
  }
@@ -31873,6 +32532,57 @@ async function handleUndo(ctx, warningMessage, doConfirm) {
31873
32532
  }
31874
32533
  }
31875
32534
 
32535
+ // src/cli/slashHandlers/checkpoint.ts
32536
+ init_checkpointManager();
32537
+ function ago(ms) {
32538
+ const s = Math.max(0, Math.round((Date.now() - ms) / 1e3));
32539
+ if (s < 60) return `${s}s ago`;
32540
+ if (s < 3600) return `${Math.round(s / 60)}m ago`;
32541
+ return `${Math.round(s / 3600)}h ago`;
32542
+ }
32543
+ async function handleCheckpointCreate(ctx, label) {
32544
+ const res = await createCheckpoint(ctx.cwd, label ?? "manual checkpoint");
32545
+ if (res.ok) {
32546
+ appendSystem(
32547
+ ctx.setMessages,
32548
+ `[checkpoint] \u2713 ${res.value.id} created${label ? ` (\u201C${label}\u201D)` : ""} \u2014 restore with \`/rollback ${res.value.id}\``
32549
+ );
32550
+ } else {
32551
+ appendSystem(ctx.setMessages, `[checkpoint] \u2717 ${res.error}`);
32552
+ }
32553
+ }
32554
+ async function handleRollbackList(ctx) {
32555
+ const list = await listCheckpoints(ctx.cwd);
32556
+ if (list.length === 0) {
32557
+ appendSystem(
32558
+ ctx.setMessages,
32559
+ "[rollback] no checkpoints yet. Create one with `/checkpoint [label]` (Zelari missions create one automatically)."
32560
+ );
32561
+ return;
32562
+ }
32563
+ const lines = list.map(
32564
+ (c, i) => ` ${i === 0 ? "\u2192" : " "} ${c.id} ${ago(c.createdAt)} ${c.label}`
32565
+ );
32566
+ appendSystem(
32567
+ ctx.setMessages,
32568
+ `[rollback] ${list.length} checkpoint${list.length === 1 ? "" : "s"} (newest first):
32569
+ ${lines.join("\n")}
32570
+ Restore with \`/rollback <id>\` or \`/rollback latest\`.`
32571
+ );
32572
+ }
32573
+ async function handleRollback(ctx, id) {
32574
+ const res = await restoreCheckpoint(ctx.cwd, id);
32575
+ if (res.ok) {
32576
+ const removed = res.value.deleted.length > 0 ? ` (removed ${res.value.deleted.length} file${res.value.deleted.length === 1 ? "" : "s"} created after the checkpoint)` : "";
32577
+ appendSystem(
32578
+ ctx.setMessages,
32579
+ `[rollback] \u2713 working tree restored to checkpoint ${res.value.id}${removed}.`
32580
+ );
32581
+ } else {
32582
+ appendSystem(ctx.setMessages, `[rollback] \u2717 ${res.error}`);
32583
+ }
32584
+ }
32585
+
31876
32586
  // src/cli/compaction.ts
31877
32587
  function compactTranscript(messages, options = {}) {
31878
32588
  const threshold = options.threshold ?? 50;
@@ -32011,15 +32721,15 @@ ${output}`.toLowerCase();
32011
32721
 
32012
32722
  // src/cli/slashHandlers/promoteMember.ts
32013
32723
  import { promises as fs15 } from "node:fs";
32014
- import path20 from "node:path";
32724
+ import path22 from "node:path";
32015
32725
  import os8 from "node:os";
32016
32726
  async function handlePromoteMember(ctx, memberId) {
32017
32727
  try {
32018
32728
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
32019
32729
  const { skill, markdown } = promoteMember2(memberId);
32020
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path20.join(os8.homedir(), ".tmp", "zelari-code", "skills");
32730
+ const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path22.join(os8.homedir(), ".tmp", "zelari-code", "skills");
32021
32731
  await fs15.mkdir(skillDir, { recursive: true });
32022
- const filePath = path20.join(skillDir, `${skill.id}.md`);
32732
+ const filePath = path22.join(skillDir, `${skill.id}.md`);
32023
32733
  await fs15.writeFile(filePath, markdown, "utf8");
32024
32734
  appendSystem(
32025
32735
  ctx.setMessages,
@@ -32036,29 +32746,29 @@ async function handlePromoteMember(ctx, memberId) {
32036
32746
  }
32037
32747
 
32038
32748
  // src/cli/branchManager.ts
32039
- import { promises as fs16, existsSync as existsSync23, readFileSync as readFileSync21, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10, statSync as statSync4, rmSync } from "node:fs";
32040
- import path21 from "node:path";
32749
+ import { promises as fs16, existsSync as existsSync24, readFileSync as readFileSync21, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
32750
+ import path23 from "node:path";
32041
32751
  import os9 from "node:os";
32042
32752
  var META_FILENAME = "meta.json";
32043
32753
  var SESSIONS_SUBDIR = "sessions";
32044
32754
  function getBranchesBaseDir() {
32045
- return process.env.ANATHEMA_BRANCHES_DIR ?? path21.join(os9.homedir(), ".tmp", "zelari-code", "branches");
32755
+ return process.env.ANATHEMA_BRANCHES_DIR ?? path23.join(os9.homedir(), ".tmp", "zelari-code", "branches");
32046
32756
  }
32047
32757
  function getSessionsBaseDir() {
32048
- return process.env.ANATHEMA_SESSIONS_DIR ?? path21.join(os9.homedir(), ".tmp", "zelari-code", "sessions");
32758
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path23.join(os9.homedir(), ".tmp", "zelari-code", "sessions");
32049
32759
  }
32050
32760
  function branchPathFor(name, baseDir) {
32051
- return path21.join(baseDir, name);
32761
+ return path23.join(baseDir, name);
32052
32762
  }
32053
32763
  function metaPathFor(name, baseDir) {
32054
- return path21.join(baseDir, name, META_FILENAME);
32764
+ return path23.join(baseDir, name, META_FILENAME);
32055
32765
  }
32056
32766
  function sessionsPathFor(name, baseDir) {
32057
- return path21.join(baseDir, name, SESSIONS_SUBDIR);
32767
+ return path23.join(baseDir, name, SESSIONS_SUBDIR);
32058
32768
  }
32059
32769
  function readBranchMeta(name, baseDir) {
32060
32770
  const metaPath = metaPathFor(name, baseDir);
32061
- if (!existsSync23(metaPath)) {
32771
+ if (!existsSync24(metaPath)) {
32062
32772
  throw new BranchNotFoundError(`Branch "${name}" not found`);
32063
32773
  }
32064
32774
  try {
@@ -32079,7 +32789,7 @@ function readBranchMeta(name, baseDir) {
32079
32789
  }
32080
32790
  function writeBranchMeta(name, baseDir, meta3) {
32081
32791
  const metaPath = metaPathFor(name, baseDir);
32082
- mkdirSync10(path21.dirname(metaPath), { recursive: true });
32792
+ mkdirSync10(path23.dirname(metaPath), { recursive: true });
32083
32793
  writeFileSync15(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
32084
32794
  }
32085
32795
  async function countSessions(name, baseDir) {
@@ -32118,7 +32828,7 @@ var SessionNotFoundError = class extends Error {
32118
32828
  };
32119
32829
  function branchExists(name, baseDir = getBranchesBaseDir()) {
32120
32830
  const bp = branchPathFor(name, baseDir);
32121
- return existsSync23(bp) && existsSync23(metaPathFor(name, baseDir));
32831
+ return existsSync24(bp) && existsSync24(metaPathFor(name, baseDir));
32122
32832
  }
32123
32833
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
32124
32834
  if (!name || name.trim().length === 0) {
@@ -32130,14 +32840,14 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
32130
32840
  if (branchExists(name, baseDir)) {
32131
32841
  throw new BranchAlreadyExistsError(name);
32132
32842
  }
32133
- const sourcePath = path21.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
32134
- if (!existsSync23(sourcePath)) {
32843
+ const sourcePath = path23.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
32844
+ if (!existsSync24(sourcePath)) {
32135
32845
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
32136
32846
  }
32137
32847
  const branchPath = branchPathFor(name, baseDir);
32138
32848
  const branchSessionsPath = sessionsPathFor(name, baseDir);
32139
32849
  mkdirSync10(branchSessionsPath, { recursive: true });
32140
- const destPath = path21.join(branchSessionsPath, `${fromSessionId}.jsonl`);
32850
+ const destPath = path23.join(branchSessionsPath, `${fromSessionId}.jsonl`);
32141
32851
  await fs16.copyFile(sourcePath, destPath);
32142
32852
  const meta3 = {
32143
32853
  name,
@@ -32164,7 +32874,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
32164
32874
  const results = [];
32165
32875
  for (const entry of entries) {
32166
32876
  const metaPath = metaPathFor(entry, baseDir);
32167
- if (!existsSync23(metaPath)) continue;
32877
+ if (!existsSync24(metaPath)) continue;
32168
32878
  try {
32169
32879
  const meta3 = readBranchMeta(entry, baseDir);
32170
32880
  const sessionCount = await countSessions(entry, baseDir);
@@ -32238,14 +32948,14 @@ async function handleBranchCheckout(ctx, branchName) {
32238
32948
 
32239
32949
  // src/cli/slashHandlers/workspace.ts
32240
32950
  import { promises as fs17 } from "node:fs";
32241
- import path22 from "node:path";
32951
+ import path24 from "node:path";
32242
32952
  async function handleWorkspaceShow(ctx, what) {
32243
32953
  try {
32244
- const zelari = path22.join(process.cwd(), ".zelari");
32954
+ const zelari = path24.join(process.cwd(), ".zelari");
32245
32955
  let content;
32246
32956
  switch (what) {
32247
32957
  case "plan": {
32248
- const planPath = path22.join(zelari, "plan.md");
32958
+ const planPath = path24.join(zelari, "plan.md");
32249
32959
  try {
32250
32960
  content = await fs17.readFile(planPath, "utf-8");
32251
32961
  } catch {
@@ -32254,7 +32964,7 @@ async function handleWorkspaceShow(ctx, what) {
32254
32964
  break;
32255
32965
  }
32256
32966
  case "decisions": {
32257
- const decisionsDir = path22.join(zelari, "decisions");
32967
+ const decisionsDir = path24.join(zelari, "decisions");
32258
32968
  try {
32259
32969
  const files = (await fs17.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
32260
32970
  if (files.length === 0) {
@@ -32264,7 +32974,7 @@ async function handleWorkspaceShow(ctx, what) {
32264
32974
  `];
32265
32975
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
32266
32976
  for (const f of files) {
32267
- const raw = await fs17.readFile(path22.join(decisionsDir, f), "utf-8");
32977
+ const raw = await fs17.readFile(path24.join(decisionsDir, f), "utf-8");
32268
32978
  const { meta: meta3, body } = parseFrontmatter2(raw);
32269
32979
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
32270
32980
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -32277,7 +32987,7 @@ async function handleWorkspaceShow(ctx, what) {
32277
32987
  break;
32278
32988
  }
32279
32989
  case "risks": {
32280
- const risksPath = path22.join(zelari, "risks.md");
32990
+ const risksPath = path24.join(zelari, "risks.md");
32281
32991
  try {
32282
32992
  content = await fs17.readFile(risksPath, "utf-8");
32283
32993
  } catch {
@@ -32286,7 +32996,7 @@ async function handleWorkspaceShow(ctx, what) {
32286
32996
  break;
32287
32997
  }
32288
32998
  case "agents": {
32289
- const agentsPath = path22.join(process.cwd(), "AGENTS.MD");
32999
+ const agentsPath = path24.join(process.cwd(), "AGENTS.MD");
32290
33000
  try {
32291
33001
  content = await fs17.readFile(agentsPath, "utf-8");
32292
33002
  } catch {
@@ -32295,7 +33005,7 @@ async function handleWorkspaceShow(ctx, what) {
32295
33005
  break;
32296
33006
  }
32297
33007
  case "docs": {
32298
- const docsDir = path22.join(zelari, "docs");
33008
+ const docsDir = path24.join(zelari, "docs");
32299
33009
  try {
32300
33010
  const files = (await fs17.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
32301
33011
  content = files.length ? `# Docs (${files.length})
@@ -32337,7 +33047,7 @@ async function handleWorkspaceReset(ctx, force) {
32337
33047
  return;
32338
33048
  }
32339
33049
  try {
32340
- const target = path22.join(process.cwd(), ".zelari");
33050
+ const target = path24.join(process.cwd(), ".zelari");
32341
33051
  await fs17.rm(target, { recursive: true, force: true });
32342
33052
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
32343
33053
  } catch (err) {
@@ -32696,11 +33406,11 @@ function handleModelsRefresh(ctx) {
32696
33406
  }
32697
33407
 
32698
33408
  // src/cli/slashHandlers/skills.ts
32699
- import path23 from "node:path";
33409
+ import path25 from "node:path";
32700
33410
  import os10 from "node:os";
32701
33411
 
32702
33412
  // src/cli/skillHistory.ts
32703
- import { promises as fs18, existsSync as existsSync24, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync11 } from "node:fs";
33413
+ import { promises as fs18, existsSync as existsSync25, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync11 } from "node:fs";
32704
33414
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
32705
33415
  async function readSkillHistory(file2) {
32706
33416
  let raw = "";
@@ -32798,7 +33508,7 @@ async function applySteerInterrupt(options) {
32798
33508
 
32799
33509
  // src/cli/slashHandlers/skills.ts
32800
33510
  async function handleSkillStats(ctx, skillId) {
32801
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path23.join(os10.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
33511
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path25.join(os10.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
32802
33512
  try {
32803
33513
  const records = await readSkillHistory(historyFile);
32804
33514
  const stats = getSkillStats(records, skillId);
@@ -32814,7 +33524,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
32814
33524
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
32815
33525
  return;
32816
33526
  }
32817
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path23.join(os10.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
33527
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path25.join(os10.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
32818
33528
  try {
32819
33529
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
32820
33530
  appendSystem(ctx.setMessages, formatted);
@@ -33115,6 +33825,21 @@ function useSlashDispatch(params) {
33115
33825
  setInput("");
33116
33826
  return;
33117
33827
  }
33828
+ if (result.kind === "checkpoint_create") {
33829
+ await handleCheckpointCreate({ ...baseCtx, cwd: process.cwd() }, result.checkpointLabel);
33830
+ setInput("");
33831
+ return;
33832
+ }
33833
+ if (result.kind === "rollback_list") {
33834
+ await handleRollbackList({ ...baseCtx, cwd: process.cwd() });
33835
+ setInput("");
33836
+ return;
33837
+ }
33838
+ if (result.kind === "rollback") {
33839
+ await handleRollback({ ...baseCtx, cwd: process.cwd() }, result.rollbackId);
33840
+ setInput("");
33841
+ return;
33842
+ }
33118
33843
  if (result.kind === "promote_member" && result.promoteMemberId) {
33119
33844
  await handlePromoteMember(baseCtx, result.promoteMemberId);
33120
33845
  setInput("");
@@ -33294,7 +34019,7 @@ function App() {
33294
34019
  const [input, setInput] = useState8("");
33295
34020
  const [busy, setBusy] = useState8(false);
33296
34021
  const [providerConfig, setProviderConfig] = useState8(() => getProviderConfig());
33297
- const [sessionStats, setSessionStats] = useState8({ totalTokens: 0, totalCostUsd: 0 });
34022
+ const [sessionStats, setSessionStats] = useState8({ totalTokens: 0, totalCostUsd: 0, cachedTokens: 0 });
33298
34023
  const [clearEpoch, setClearEpoch] = useState8(0);
33299
34024
  const [mode, setMode] = useState8("agent");
33300
34025
  const [picker, setPicker] = useState8(null);
@@ -33423,7 +34148,9 @@ cwd: ${cwd}
33423
34148
  mode,
33424
34149
  cwd,
33425
34150
  elapsedMs: timer.elapsedMs,
33426
- lastMs: timer.lastMs
34151
+ lastMs: timer.lastMs,
34152
+ costUsd: sessionStats.totalCostUsd,
34153
+ cachedTokens: sessionStats.cachedTokens
33427
34154
  }
33428
34155
  )), showSidebar && /* @__PURE__ */ React9.createElement(Sidebar, { version: VERSION, changes: gitChanges, rows: size.rows })));
33429
34156
  }
@@ -33603,9 +34330,9 @@ function SplashGate({
33603
34330
  init_providerConfig();
33604
34331
 
33605
34332
  // src/cli/wizard/firstRun.ts
33606
- import { existsSync as existsSync25 } from "node:fs";
34333
+ import { existsSync as existsSync26 } from "node:fs";
33607
34334
  function shouldRunWizard(input) {
33608
- const exists = input.exists ?? existsSync25;
34335
+ const exists = input.exists ?? existsSync26;
33609
34336
  if (input.hasResetConfigFlag) {
33610
34337
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
33611
34338
  }
@@ -33960,6 +34687,7 @@ function emitEvent(event) {
33960
34687
  }
33961
34688
 
33962
34689
  // src/cli/runHeadless.ts
34690
+ init_harness();
33963
34691
  async function runHeadless(opts) {
33964
34692
  const { provider, model } = resolveHeadlessProvider(opts);
33965
34693
  const key = await resolveHeadlessKey(provider);
@@ -34094,7 +34822,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
34094
34822
 
34095
34823
  // src/cli/skillsMd.ts
34096
34824
  init_skills2();
34097
- import { existsSync as existsSync26, readdirSync as readdirSync4, readFileSync as readFileSync22 } from "node:fs";
34825
+ import { existsSync as existsSync27, readdirSync as readdirSync4, readFileSync as readFileSync22 } from "node:fs";
34098
34826
  import { join as join23 } from "node:path";
34099
34827
  import { homedir as homedir5 } from "node:os";
34100
34828
  var CODING_CATEGORIES = /* @__PURE__ */ new Set([
@@ -34172,7 +34900,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
34172
34900
  const summary = { loaded: [], skipped: [] };
34173
34901
  const seen = new Set(options.existingIds ?? []);
34174
34902
  for (const dir of skillMdSearchDirs(projectRoot)) {
34175
- if (!existsSync26(dir)) continue;
34903
+ if (!existsSync27(dir)) continue;
34176
34904
  let entries;
34177
34905
  try {
34178
34906
  entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -34181,7 +34909,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
34181
34909
  }
34182
34910
  for (const entry of entries) {
34183
34911
  const skillPath = join23(dir, entry, "SKILL.md");
34184
- if (!existsSync26(skillPath)) continue;
34912
+ if (!existsSync27(skillPath)) continue;
34185
34913
  try {
34186
34914
  const parsed = parseSkillMd(readFileSync22(skillPath, "utf8"), skillPath);
34187
34915
  if (!parsed) {