zelari-code 1.2.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +67 -3
  2. package/dist/cli/app.js +3 -1
  3. package/dist/cli/app.js.map +1 -1
  4. package/dist/cli/ast/engine.js +126 -0
  5. package/dist/cli/ast/engine.js.map +1 -0
  6. package/dist/cli/ast/tools.js +71 -0
  7. package/dist/cli/ast/tools.js.map +1 -0
  8. package/dist/cli/browser/driver.js +125 -0
  9. package/dist/cli/browser/driver.js.map +1 -0
  10. package/dist/cli/browser/tools.js +71 -0
  11. package/dist/cli/browser/tools.js.map +1 -0
  12. package/dist/cli/hooks/useSlashDispatch.js +33 -2
  13. package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
  14. package/dist/cli/lsp/client.js +89 -0
  15. package/dist/cli/lsp/client.js.map +1 -0
  16. package/dist/cli/lsp/manager.js +287 -0
  17. package/dist/cli/lsp/manager.js.map +1 -0
  18. package/dist/cli/lsp/protocol.js +83 -0
  19. package/dist/cli/lsp/protocol.js.map +1 -0
  20. package/dist/cli/lsp/servers.js +80 -0
  21. package/dist/cli/lsp/servers.js.map +1 -0
  22. package/dist/cli/lsp/tools.js +125 -0
  23. package/dist/cli/lsp/tools.js.map +1 -0
  24. package/dist/cli/main.bundled.js +1884 -281
  25. package/dist/cli/main.bundled.js.map +4 -4
  26. package/dist/cli/main.js +75 -3
  27. package/dist/cli/main.js.map +1 -1
  28. package/dist/cli/mode.js +32 -0
  29. package/dist/cli/mode.js.map +1 -0
  30. package/dist/cli/semantic/embeddings.js +71 -0
  31. package/dist/cli/semantic/embeddings.js.map +1 -0
  32. package/dist/cli/semantic/index.js +147 -0
  33. package/dist/cli/semantic/index.js.map +1 -0
  34. package/dist/cli/semantic/provider.js +29 -0
  35. package/dist/cli/semantic/provider.js.map +1 -0
  36. package/dist/cli/semantic/store.js +71 -0
  37. package/dist/cli/semantic/store.js.map +1 -0
  38. package/dist/cli/semantic/tools.js +54 -0
  39. package/dist/cli/semantic/tools.js.map +1 -0
  40. package/dist/cli/slashCommands.js +22 -1
  41. package/dist/cli/slashCommands.js.map +1 -1
  42. package/dist/cli/slashHandlers/semantic.js +37 -0
  43. package/dist/cli/slashHandlers/semantic.js.map +1 -0
  44. package/dist/cli/slashHandlers/updater.js +27 -3
  45. package/dist/cli/slashHandlers/updater.js.map +1 -1
  46. package/dist/cli/toolRegistry.js +50 -0
  47. package/dist/cli/toolRegistry.js.map +1 -1
  48. package/dist/cli/utils/doctor.js +23 -0
  49. package/dist/cli/utils/doctor.js.map +1 -1
  50. package/dist/cli/utils/prereqChecks.js +362 -0
  51. package/dist/cli/utils/prereqChecks.js.map +1 -0
  52. package/package.json +3 -3
  53. package/scripts/postinstall.mjs +46 -1
@@ -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: spawn7 } = await import("node:child_process");
311
+ const { spawn: spawn8 } = 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 = spawn7(cmd.bin, cmd.args, {
324
+ const child = spawn8(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, path27) {
1539
- if (!path27)
1538
+ function getElementAtPath(obj, path33) {
1539
+ if (!path33)
1540
1540
  return obj;
1541
- return path27.reduce((acc, key) => acc?.[key], obj);
1541
+ return path33.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(path27, issues) {
1869
+ function prefixIssues(path33, issues) {
1870
1870
  return issues.map((iss) => {
1871
1871
  var _a3;
1872
1872
  (_a3 = iss).path ?? (_a3.path = []);
1873
- iss.path.unshift(path27);
1873
+ iss.path.unshift(path33);
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, path27 = []) => {
2091
+ const processError = (error52, path33 = []) => {
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 }, [...path27, ...issue2.path]));
2094
+ issue2.errors.map((issues) => processError({ issues }, [...path33, ...issue2.path]));
2095
2095
  } else if (issue2.code === "invalid_key") {
2096
- processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
2096
+ processError({ issues: issue2.issues }, [...path33, ...issue2.path]);
2097
2097
  } else if (issue2.code === "invalid_element") {
2098
- processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
2098
+ processError({ issues: issue2.issues }, [...path33, ...issue2.path]);
2099
2099
  } else {
2100
- const fullpath = [...path27, ...issue2.path];
2100
+ const fullpath = [...path33, ...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, path27 = []) => {
2127
+ const processError = (error52, path33 = []) => {
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 }, [...path27, ...issue2.path]));
2131
+ issue2.errors.map((issues) => processError({ issues }, [...path33, ...issue2.path]));
2132
2132
  } else if (issue2.code === "invalid_key") {
2133
- processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
2133
+ processError({ issues: issue2.issues }, [...path33, ...issue2.path]);
2134
2134
  } else if (issue2.code === "invalid_element") {
2135
- processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
2135
+ processError({ issues: issue2.issues }, [...path33, ...issue2.path]);
2136
2136
  } else {
2137
- const fullpath = [...path27, ...issue2.path];
2137
+ const fullpath = [...path33, ...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 path27 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2170
- for (const seg of path27) {
2169
+ const path33 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2170
+ for (const seg of path33) {
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 path27 = ref.slice(1).split("/").filter(Boolean);
15674
- if (path27.length === 0) {
15673
+ const path33 = ref.slice(1).split("/").filter(Boolean);
15674
+ if (path33.length === 0) {
15675
15675
  return ctx.rootSchema;
15676
15676
  }
15677
15677
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15678
- if (path27[0] === defsKey) {
15679
- const key = path27[1];
15678
+ if (path33[0] === defsKey) {
15679
+ const key = path33[1];
15680
15680
  if (!key || !ctx.defs[key]) {
15681
15681
  throw new Error(`Reference not found: ${ref}`);
15682
15682
  }
@@ -16836,7 +16836,7 @@ var init_walk = __esm({
16836
16836
  // packages/core/dist/core/tools/builtin/search.js
16837
16837
  import { promises as fs6 } from "node:fs";
16838
16838
  import path7 from "node:path";
16839
- async function searchFile(absPath, relPath, regex, contextLines, remainingSlots) {
16839
+ async function searchFile(absPath, relPath2, regex, contextLines, remainingSlots) {
16840
16840
  let buf;
16841
16841
  try {
16842
16842
  buf = await fs6.readFile(absPath, "utf-8");
@@ -16856,7 +16856,7 @@ async function searchFile(absPath, relPath, regex, contextLines, remainingSlots)
16856
16856
  const endAfter = Math.min(lines.length - 1, i + contextLines);
16857
16857
  matches.push({
16858
16858
  file: absPath,
16859
- relPath,
16859
+ relPath: relPath2,
16860
16860
  line: i + 1,
16861
16861
  text: lines[i],
16862
16862
  context: {
@@ -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 path27 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17709
+ const path33 = 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: path27,
17713
+ path: path33,
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 "${path27}".`;
17722
+ return `Document "${title}" created at "${path33}".`;
17723
17723
  }
17724
17724
  }
17725
17725
  ];
@@ -20105,7 +20105,7 @@ var init_parseCssMotion = __esm({
20105
20105
  });
20106
20106
 
20107
20107
  // packages/core/dist/council/verification/citeVerify.js
20108
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
20108
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
20109
20109
  import { join } from "node:path";
20110
20110
  function extractCitations(text) {
20111
20111
  const out = [];
@@ -20129,7 +20129,7 @@ function verifyCitations(projectRoot, synthesisText) {
20129
20129
  const results = [];
20130
20130
  for (const cite of extractCitations(synthesisText)) {
20131
20131
  const abs = join(projectRoot, cite.file);
20132
- if (!existsSync8(abs)) {
20132
+ if (!existsSync9(abs)) {
20133
20133
  results.push({
20134
20134
  id: "synthesis.cite-invalid",
20135
20135
  severity: "error",
@@ -20142,7 +20142,7 @@ function verifyCitations(projectRoot, synthesisText) {
20142
20142
  });
20143
20143
  continue;
20144
20144
  }
20145
- const lines = readFileSync5(abs, "utf8").split(/\r?\n/);
20145
+ const lines = readFileSync7(abs, "utf8").split(/\r?\n/);
20146
20146
  if (cite.line > lines.length) {
20147
20147
  results.push({
20148
20148
  id: "synthesis.cite-invalid",
@@ -20405,14 +20405,14 @@ var init_synthesisAudit = __esm({
20405
20405
  });
20406
20406
 
20407
20407
  // packages/core/dist/council/verification/runChecks.js
20408
- import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
20408
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
20409
20409
  import { join as join2 } from "node:path";
20410
20410
  function loadNfrSpec(zelariRoot) {
20411
- const path27 = join2(zelariRoot, "nfr-spec.json");
20412
- if (!existsSync9(path27))
20411
+ const path33 = join2(zelariRoot, "nfr-spec.json");
20412
+ if (!existsSync10(path33))
20413
20413
  return null;
20414
20414
  try {
20415
- const raw = JSON.parse(readFileSync6(path27, "utf8"));
20415
+ const raw = JSON.parse(readFileSync8(path33, "utf8"));
20416
20416
  if (raw.version !== 1 || !Array.isArray(raw.targets))
20417
20417
  return null;
20418
20418
  return raw;
@@ -20423,11 +20423,11 @@ function loadNfrSpec(zelariRoot) {
20423
20423
  function resolveTargets(projectRoot, spec) {
20424
20424
  const found = [];
20425
20425
  for (const rel2 of spec.targets) {
20426
- if (existsSync9(join2(projectRoot, rel2))) {
20426
+ if (existsSync10(join2(projectRoot, rel2))) {
20427
20427
  found.push(rel2);
20428
20428
  }
20429
20429
  }
20430
- if (found.length === 0 && existsSync9(join2(projectRoot, "index.html"))) {
20430
+ if (found.length === 0 && existsSync10(join2(projectRoot, "index.html"))) {
20431
20431
  return ["index.html"];
20432
20432
  }
20433
20433
  return found;
@@ -20483,17 +20483,17 @@ function checkDeadCssHooks(html, relFile) {
20483
20483
  }
20484
20484
  function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
20485
20485
  const planPath = join2(zelariRoot, "plan.json");
20486
- if (!existsSync9(planPath) || keywords.length === 0)
20486
+ if (!existsSync10(planPath) || keywords.length === 0)
20487
20487
  return [];
20488
20488
  let plan;
20489
20489
  try {
20490
- plan = JSON.parse(readFileSync6(planPath, "utf8"));
20490
+ plan = JSON.parse(readFileSync8(planPath, "utf8"));
20491
20491
  } catch {
20492
20492
  return [];
20493
20493
  }
20494
20494
  const milestoneText = (plan.milestones ?? []).map((m) => `${m.name ?? ""} ${m.description ?? ""}`).join(" ").toLowerCase();
20495
20495
  const results = [];
20496
- const targetContent = targets.map((t) => readFileSync6(join2(projectRoot, t), "utf8").toLowerCase()).join("\n");
20496
+ const targetContent = targets.map((t) => readFileSync8(join2(projectRoot, t), "utf8").toLowerCase()).join("\n");
20497
20497
  for (const kw of keywords) {
20498
20498
  const low = kw.toLowerCase();
20499
20499
  if (!milestoneText.includes(low))
@@ -20523,13 +20523,13 @@ function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
20523
20523
  }
20524
20524
  function checkReadmeStale(projectRoot, targets) {
20525
20525
  const readmePath = join2(projectRoot, "README.md");
20526
- if (!existsSync9(readmePath) || targets.length === 0)
20526
+ if (!existsSync10(readmePath) || targets.length === 0)
20527
20527
  return [];
20528
- const readme = readFileSync6(readmePath, "utf8");
20528
+ const readme = readFileSync8(readmePath, "utf8");
20529
20529
  const htmlPath = join2(projectRoot, targets[0]);
20530
- if (!existsSync9(htmlPath))
20530
+ if (!existsSync10(htmlPath))
20531
20531
  return [];
20532
- const html = readFileSync6(htmlPath, "utf8");
20532
+ const html = readFileSync8(htmlPath, "utf8");
20533
20533
  const sectionCount = (html.match(/<section\s+id=/gi) ?? []).length;
20534
20534
  const readmeSections = readme.match(/(\d+)\s+sezioni/i);
20535
20535
  if (readmeSections) {
@@ -20567,7 +20567,7 @@ function runImplementationVerification(input) {
20567
20567
  forbidLayoutProps: anim.forbidLayoutProps ?? true
20568
20568
  };
20569
20569
  for (const rel2 of targets) {
20570
- const html = readFileSync6(join2(input.projectRoot, rel2), "utf8");
20570
+ const html = readFileSync8(join2(input.projectRoot, rel2), "utf8");
20571
20571
  for (const v of scanKeyframesViolations(html, scanOpts)) {
20572
20572
  results.push({
20573
20573
  id: "motion.keyframes",
@@ -20653,7 +20653,7 @@ var init_runChecks = __esm({
20653
20653
  });
20654
20654
 
20655
20655
  // packages/core/dist/council/verification/microGate.js
20656
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
20656
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
20657
20657
  import { join as join3 } from "node:path";
20658
20658
  function checkDeadHooksInHtml(html) {
20659
20659
  const warnings = [];
@@ -20681,9 +20681,9 @@ function checkDeadHooksInHtml(html) {
20681
20681
  }
20682
20682
  return warnings;
20683
20683
  }
20684
- function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
20685
- const abs = join3(projectRoot, relPath);
20686
- if (!existsSync10(abs) || !/\.html?$/i.test(relPath))
20684
+ function runMicroVerificationOnFile(projectRoot, relPath2, zelariRoot) {
20685
+ const abs = join3(projectRoot, relPath2);
20686
+ if (!existsSync11(abs) || !/\.html?$/i.test(relPath2))
20687
20687
  return [];
20688
20688
  const spec = (zelariRoot ? loadNfrSpec(zelariRoot) : null) ?? DEFAULT_NFR_SPEC;
20689
20689
  const anim = spec.animation ?? { compositorOnly: true, forbidLayoutProps: true };
@@ -20691,13 +20691,13 @@ function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
20691
20691
  compositorOnly: anim.compositorOnly ?? true,
20692
20692
  forbidLayoutProps: anim.forbidLayoutProps ?? true
20693
20693
  };
20694
- const html = readFileSync7(abs, "utf8");
20694
+ const html = readFileSync9(abs, "utf8");
20695
20695
  const warnings = [];
20696
20696
  for (const v of scanKeyframesViolations(html, scanOpts)) {
20697
20697
  warnings.push({
20698
20698
  id: "motion.keyframes",
20699
20699
  message: `@keyframes uses non-allowed property "${v.property}"`,
20700
- file: relPath,
20700
+ file: relPath2,
20701
20701
  line: v.line
20702
20702
  });
20703
20703
  }
@@ -20705,12 +20705,12 @@ function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
20705
20705
  warnings.push({
20706
20706
  id: "motion.transitions",
20707
20707
  message: `transition uses non-allowed property "${v.property}"`,
20708
- file: relPath,
20708
+ file: relPath2,
20709
20709
  line: v.line
20710
20710
  });
20711
20711
  }
20712
20712
  for (const w of checkDeadHooksInHtml(html)) {
20713
- warnings.push({ ...w, file: relPath });
20713
+ warnings.push({ ...w, file: relPath2 });
20714
20714
  }
20715
20715
  return warnings;
20716
20716
  }
@@ -21047,7 +21047,7 @@ var init_implementationDelivery = __esm({
21047
21047
  });
21048
21048
 
21049
21049
  // packages/core/dist/council/verification/inlineJsAutofix.js
21050
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
21050
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
21051
21051
  import { join as join4 } from "node:path";
21052
21052
  function minifyInlineJs(js) {
21053
21053
  let out = js.replace(/\/\*[\s\S]*?\*\//g, "");
@@ -21094,7 +21094,7 @@ function applyInlineJsAutofix(projectRoot, report) {
21094
21094
  const abs = join4(projectRoot, rel2);
21095
21095
  let html;
21096
21096
  try {
21097
- html = readFileSync8(abs, "utf8");
21097
+ html = readFileSync10(abs, "utf8");
21098
21098
  } catch {
21099
21099
  continue;
21100
21100
  }
@@ -21127,7 +21127,7 @@ var init_inlineJsAutofix = __esm({
21127
21127
  });
21128
21128
 
21129
21129
  // packages/core/dist/agents/councilApi.js
21130
- import { existsSync as existsSync11 } from "node:fs";
21130
+ import { existsSync as existsSync12 } from "node:fs";
21131
21131
  import { join as join5 } from "node:path";
21132
21132
  function parseClarificationRequest(text) {
21133
21133
  const start = text.indexOf(QUESTION_MARKER);
@@ -21648,7 +21648,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
21648
21648
  const zelariRoot = `${chairmanProjectRoot}/.zelari`;
21649
21649
  const spec = loadNfrSpec(zelariRoot) ?? DEFAULT_NFR_SPEC;
21650
21650
  for (const rel2 of spec.targets) {
21651
- if (!existsSync11(join5(chairmanProjectRoot, rel2)))
21651
+ if (!existsSync12(join5(chairmanProjectRoot, rel2)))
21652
21652
  continue;
21653
21653
  changedTargetFiles.add(rel2);
21654
21654
  for (const w of runChairmanMicroGate({ projectRoot: chairmanProjectRoot, relPath: rel2, zelariRoot })) {
@@ -21668,7 +21668,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
21668
21668
  const zelariRootReplay = `${chairmanProjectRoot}/.zelari`;
21669
21669
  const specReplay = loadNfrSpec(zelariRootReplay) ?? DEFAULT_NFR_SPEC;
21670
21670
  for (const rel2 of specReplay.targets) {
21671
- if (!existsSync11(join5(chairmanProjectRoot, rel2)))
21671
+ if (!existsSync12(join5(chairmanProjectRoot, rel2)))
21672
21672
  continue;
21673
21673
  changedTargetFiles.add(rel2);
21674
21674
  for (const w of runChairmanMicroGate({
@@ -22039,8 +22039,8 @@ async function* runChairmanFixLoop(args) {
22039
22039
  break;
22040
22040
  }
22041
22041
  const rescanned = /* @__PURE__ */ new Map();
22042
- for (const relPath of args.changedFiles) {
22043
- for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath, zelariRoot })) {
22042
+ for (const relPath2 of args.changedFiles) {
22043
+ for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath: relPath2, zelariRoot })) {
22044
22044
  rescanned.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
22045
22045
  }
22046
22046
  }
@@ -22376,7 +22376,7 @@ var init_types = __esm({
22376
22376
  });
22377
22377
 
22378
22378
  // packages/core/dist/council/verification/motionAutofix.js
22379
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
22379
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
22380
22380
  import { join as join6 } from "node:path";
22381
22381
  function sanitizeTransitionPart(part) {
22382
22382
  const tokens = part.trim().split(/\s+/);
@@ -22455,7 +22455,7 @@ function applyMotionAutofix(projectRoot, report) {
22455
22455
  const abs = join6(projectRoot, rel2);
22456
22456
  let html;
22457
22457
  try {
22458
- html = readFileSync9(abs, "utf8");
22458
+ html = readFileSync11(abs, "utf8");
22459
22459
  } catch {
22460
22460
  continue;
22461
22461
  }
@@ -22527,7 +22527,7 @@ var init_motionAutofix = __esm({
22527
22527
  });
22528
22528
 
22529
22529
  // packages/core/dist/council/verification/autofix.js
22530
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "node:fs";
22530
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
22531
22531
  import { join as join7 } from "node:path";
22532
22532
  function applyDeterministicAutofix(projectRoot, report) {
22533
22533
  const motion = applyMotionAutofix(projectRoot, report);
@@ -22543,7 +22543,7 @@ function applyDeterministicAutofix(projectRoot, report) {
22543
22543
  if (!m || m[1] === "rm")
22544
22544
  continue;
22545
22545
  const abs = join7(projectRoot, rel2);
22546
- let html = readFileSync10(abs, "utf8");
22546
+ let html = readFileSync12(abs, "utf8");
22547
22547
  const snippet = m[0];
22548
22548
  if (!html.includes(snippet))
22549
22549
  continue;
@@ -22598,12 +22598,12 @@ var init_types2 = __esm({
22598
22598
  });
22599
22599
 
22600
22600
  // packages/core/dist/council/lessons/io.js
22601
- import { readFileSync as readFileSync11 } from "node:fs";
22601
+ import { readFileSync as readFileSync13 } from "node:fs";
22602
22602
  import { join as join8 } from "node:path";
22603
22603
  function readLessonsDeduped(zelariRoot) {
22604
- const path27 = join8(zelariRoot, LESSONS_FILE);
22604
+ const path33 = join8(zelariRoot, LESSONS_FILE);
22605
22605
  try {
22606
- const raw = readFileSync11(path27, "utf8");
22606
+ const raw = readFileSync13(path33, "utf8");
22607
22607
  const byId = /* @__PURE__ */ new Map();
22608
22608
  for (const line of raw.split(/\r?\n/)) {
22609
22609
  if (!line.trim())
@@ -22704,8 +22704,8 @@ function keywordsFrom(check2, signature) {
22704
22704
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
22705
22705
  }
22706
22706
  function writeLesson(zelariRoot, lesson) {
22707
- const path27 = join9(zelariRoot, LESSONS_FILE);
22708
- appendFileSync2(path27, `${JSON.stringify(lesson)}
22707
+ const path33 = join9(zelariRoot, LESSONS_FILE);
22708
+ appendFileSync2(path33, `${JSON.stringify(lesson)}
22709
22709
  `, "utf8");
22710
22710
  }
22711
22711
  function findSimilar(lessons, signature) {
@@ -23242,18 +23242,18 @@ var init_council = __esm({
23242
23242
  import {
23243
23243
  mkdirSync as mkdirSync6,
23244
23244
  writeFileSync as writeFileSync10,
23245
- existsSync as existsSync12,
23245
+ existsSync as existsSync13,
23246
23246
  accessSync,
23247
23247
  constants,
23248
23248
  realpathSync
23249
23249
  } from "node:fs";
23250
23250
  import { join as join11, basename } from "node:path";
23251
- import { homedir as homedir3 } from "node:os";
23252
- import { createHash } from "node:crypto";
23251
+ import { homedir as homedir4 } from "node:os";
23252
+ import { createHash as createHash2 } from "node:crypto";
23253
23253
  function resolveWorkspaceRoot(projectRoot = process.cwd()) {
23254
23254
  const candidates = [
23255
23255
  join11(projectRoot, ".zelari"),
23256
- join11(homedir3(), ".zelari-code", "workspace", hashProject(projectRoot))
23256
+ join11(homedir4(), ".zelari-code", "workspace", hashProject(projectRoot))
23257
23257
  ];
23258
23258
  for (const candidate of candidates) {
23259
23259
  if (isWritableDir(projectRoot) || candidate !== candidates[0]) {
@@ -23265,11 +23265,11 @@ function resolveWorkspaceRoot(projectRoot = process.cwd()) {
23265
23265
  return candidates[0];
23266
23266
  }
23267
23267
  function hashProject(projectPath) {
23268
- return createHash("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
23268
+ return createHash2("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
23269
23269
  }
23270
23270
  function isWritableDir(dir) {
23271
23271
  try {
23272
- if (!existsSync12(dir)) return false;
23272
+ if (!existsSync13(dir)) return false;
23273
23273
  accessSync(dir, constants.W_OK);
23274
23274
  return true;
23275
23275
  } catch {
@@ -23278,9 +23278,9 @@ function isWritableDir(dir) {
23278
23278
  }
23279
23279
  function ensureWorkspaceDir(workspaceDir) {
23280
23280
  mkdirSync6(workspaceDir, { recursive: true });
23281
- if (workspaceDir.endsWith("/.zelari") && existsSync12(join11(workspaceDir, "..", ".git"))) {
23281
+ if (workspaceDir.endsWith("/.zelari") && existsSync13(join11(workspaceDir, "..", ".git"))) {
23282
23282
  const gitignorePath = join11(workspaceDir, ".gitignore");
23283
- if (!existsSync12(gitignorePath)) {
23283
+ if (!existsSync13(gitignorePath)) {
23284
23284
  writeFileSync10(gitignorePath, "*\n!.gitignore\n");
23285
23285
  }
23286
23286
  }
@@ -23315,7 +23315,7 @@ __export(workspaceSummary_exports, {
23315
23315
  buildWorkspaceSummary: () => buildWorkspaceSummary,
23316
23316
  buildZelariReadHint: () => buildZelariReadHint
23317
23317
  });
23318
- import { existsSync as existsSync13, readFileSync as readFileSync12, readdirSync, statSync as statSync3 } from "node:fs";
23318
+ import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync, statSync as statSync3 } from "node:fs";
23319
23319
  import { join as join12, relative as relative2 } from "node:path";
23320
23320
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
23321
23321
  const { maxEntries = 30 } = options;
@@ -23344,10 +23344,10 @@ function formatTaskLine(t) {
23344
23344
  function buildPlanSummary(projectRoot = process.cwd(), options) {
23345
23345
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
23346
23346
  const planPath = join12(zelariRoot, "plan.json");
23347
- if (!existsSync13(planPath)) return null;
23347
+ if (!existsSync14(planPath)) return null;
23348
23348
  let plan;
23349
23349
  try {
23350
- plan = JSON.parse(readFileSync12(planPath, "utf8"));
23350
+ plan = JSON.parse(readFileSync14(planPath, "utf8"));
23351
23351
  } catch {
23352
23352
  return null;
23353
23353
  }
@@ -23477,7 +23477,7 @@ function pickNextTask(open) {
23477
23477
  }
23478
23478
  function buildZelariReadHint(projectRoot = process.cwd()) {
23479
23479
  const planPath = join12(resolveWorkspaceRoot(projectRoot), "plan.json");
23480
- if (!existsSync13(planPath)) return "";
23480
+ if (!existsSync14(planPath)) return "";
23481
23481
  return [
23482
23482
  "# Council workspace detected (.zelari/)",
23483
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.",
@@ -23493,9 +23493,9 @@ function safeProjectName(root) {
23493
23493
  }
23494
23494
  function readPackageJson(projectRoot) {
23495
23495
  const p3 = join12(projectRoot, "package.json");
23496
- if (!existsSync13(p3)) return null;
23496
+ if (!existsSync14(p3)) return null;
23497
23497
  try {
23498
- return JSON.parse(readFileSync12(p3, "utf8"));
23498
+ return JSON.parse(readFileSync14(p3, "utf8"));
23499
23499
  } catch {
23500
23500
  return null;
23501
23501
  }
@@ -23584,9 +23584,9 @@ __export(storage_exports, {
23584
23584
  workspaceMutex: () => workspaceMutex
23585
23585
  });
23586
23586
  import {
23587
- readFileSync as readFileSync13,
23587
+ readFileSync as readFileSync15,
23588
23588
  writeFileSync as writeFileSync11,
23589
- existsSync as existsSync14,
23589
+ existsSync as existsSync15,
23590
23590
  mkdirSync as mkdirSync7,
23591
23591
  readdirSync as readdirSync2,
23592
23592
  renameSync as renameSync2
@@ -23845,32 +23845,32 @@ var init_storage = __esm({
23845
23845
  VALID_SCALARS = /^(true|false|null|~)$/i;
23846
23846
  Storage = class {
23847
23847
  /** Read a Markdown file with frontmatter. Throws if not found. */
23848
- read(path27) {
23849
- if (!existsSync14(path27)) {
23850
- throw new Error(`File not found: ${path27}`);
23848
+ read(path33) {
23849
+ if (!existsSync15(path33)) {
23850
+ throw new Error(`File not found: ${path33}`);
23851
23851
  }
23852
- const md = readFileSync13(path27, "utf8");
23852
+ const md = readFileSync15(path33, "utf8");
23853
23853
  return parseFrontmatter(md);
23854
23854
  }
23855
23855
  /** Read a Markdown file; returns null if not found. */
23856
- readIfExists(path27) {
23857
- if (!existsSync14(path27)) return null;
23858
- return this.read(path27);
23856
+ readIfExists(path33) {
23857
+ if (!existsSync15(path33)) return null;
23858
+ return this.read(path33);
23859
23859
  }
23860
23860
  /**
23861
23861
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
23862
23862
  * The meta object is serialized as YAML frontmatter; body as Markdown.
23863
23863
  */
23864
- write(path27, meta3, body) {
23865
- mkdirSync7(dirname(path27), { recursive: true });
23866
- const tmp = path27 + ".tmp-" + process.pid;
23864
+ write(path33, meta3, body) {
23865
+ mkdirSync7(dirname(path33), { recursive: true });
23866
+ const tmp = path33 + ".tmp-" + process.pid;
23867
23867
  const md = serializeFrontmatter(meta3, body);
23868
23868
  writeFileSync11(tmp, md, "utf8");
23869
- renameSync2(tmp, path27);
23869
+ renameSync2(tmp, path33);
23870
23870
  }
23871
23871
  /** List all .md files in a directory (non-recursive). */
23872
23872
  listMarkdown(dir) {
23873
- if (!existsSync14(dir)) return [];
23873
+ if (!existsSync15(dir)) return [];
23874
23874
  return readdirSync2(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join13(dir, f));
23875
23875
  }
23876
23876
  };
@@ -23910,10 +23910,10 @@ __export(stubs_exports, {
23910
23910
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
23911
23911
  });
23912
23912
  import {
23913
- existsSync as existsSync15,
23913
+ existsSync as existsSync16,
23914
23914
  readdirSync as readdirSync3,
23915
23915
  writeFileSync as writeFileSync12,
23916
- readFileSync as readFileSync14,
23916
+ readFileSync as readFileSync16,
23917
23917
  mkdirSync as mkdirSync8,
23918
23918
  renameSync as renameSync3
23919
23919
  } from "node:fs";
@@ -23931,10 +23931,10 @@ function planJsonPath(ctx) {
23931
23931
  }
23932
23932
  function readPlan(ctx) {
23933
23933
  const jsonPath = planJsonPath(ctx);
23934
- if (existsSync15(jsonPath)) {
23934
+ if (existsSync16(jsonPath)) {
23935
23935
  try {
23936
23936
  const parsed = JSON.parse(
23937
- readFileSync14(jsonPath, "utf8")
23937
+ readFileSync16(jsonPath, "utf8")
23938
23938
  );
23939
23939
  return {
23940
23940
  phases: Array.isArray(parsed.phases) ? parsed.phases : [],
@@ -23944,8 +23944,8 @@ function readPlan(ctx) {
23944
23944
  } catch {
23945
23945
  }
23946
23946
  }
23947
- const path27 = workspaceFile(ctx.rootDir, "plan");
23948
- const doc = ctx.storage.readIfExists(path27);
23947
+ const path33 = workspaceFile(ctx.rootDir, "plan");
23948
+ const doc = ctx.storage.readIfExists(path33);
23949
23949
  if (!doc) return { phases: [], tasks: [], milestones: [] };
23950
23950
  const meta3 = doc.meta;
23951
23951
  return {
@@ -24026,7 +24026,7 @@ function renderPlanBody(summary) {
24026
24026
  }
24027
24027
  function nextAdrId(ctx) {
24028
24028
  const decisionsDir = join14(ctx.rootDir, "decisions");
24029
- if (!existsSync15(decisionsDir)) return "001";
24029
+ if (!existsSync16(decisionsDir)) return "001";
24030
24030
  const existing = readdirSync3(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
24031
24031
  const max = existing.length === 0 ? 0 : Math.max(...existing);
24032
24032
  return String(max + 1).padStart(3, "0");
@@ -24110,7 +24110,7 @@ function addMilestoneRecord(ctx, summary, input) {
24110
24110
  dueDate: input.dueDate,
24111
24111
  targetVersion: version2
24112
24112
  });
24113
- const path27 = join14(ctx.rootDir, "milestones", `${id}.md`);
24113
+ const path33 = join14(ctx.rootDir, "milestones", `${id}.md`);
24114
24114
  const meta3 = {
24115
24115
  kind: "milestone",
24116
24116
  id,
@@ -24127,7 +24127,7 @@ function addMilestoneRecord(ctx, summary, input) {
24127
24127
  `Target version: ${version2}`,
24128
24128
  ""
24129
24129
  ].join("\n");
24130
- ctx.storage.write(path27, meta3, body);
24130
+ ctx.storage.write(path33, meta3, body);
24131
24131
  return { id, created: true };
24132
24132
  }
24133
24133
  function readPlanSummary(ctx) {
@@ -24331,7 +24331,7 @@ function addIdeaStub(ctx) {
24331
24331
  const tags = args["tags"] ?? [];
24332
24332
  const category = args["category"] ?? "General";
24333
24333
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
24334
- const path27 = workspaceArtifact(ctx.rootDir, "decisions", id);
24334
+ const path33 = workspaceArtifact(ctx.rootDir, "decisions", id);
24335
24335
  const meta3 = {
24336
24336
  kind: "adr",
24337
24337
  status: "proposed",
@@ -24357,7 +24357,7 @@ function addIdeaStub(ctx) {
24357
24357
  ...consequences.map((c) => `- ${c}`),
24358
24358
  ""
24359
24359
  ].join("\n");
24360
- ctx.storage.write(path27, meta3, body);
24360
+ ctx.storage.write(path33, meta3, body);
24361
24361
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
24362
24362
  });
24363
24363
  }
@@ -24439,14 +24439,14 @@ function createDocumentStub(ctx) {
24439
24439
  ctx.storage.write(risksPath, riskMeta, content);
24440
24440
  return `Document "${title}" created at risks.md (workspace root).`;
24441
24441
  }
24442
- const path27 = workspaceArtifact(ctx.rootDir, "docs", slug);
24442
+ const path33 = workspaceArtifact(ctx.rootDir, "docs", slug);
24443
24443
  const meta3 = {
24444
24444
  kind: "doc",
24445
24445
  id: slug,
24446
24446
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
24447
24447
  tags
24448
24448
  };
24449
- ctx.storage.write(path27, meta3, content);
24449
+ ctx.storage.write(path33, meta3, content);
24450
24450
  return `Document "${title}" created at docs/${slug}.md.`;
24451
24451
  });
24452
24452
  }
@@ -24482,8 +24482,8 @@ function searchDocumentsStub(ctx) {
24482
24482
  ];
24483
24483
  const results = [];
24484
24484
  for (const file2 of files) {
24485
- if (!existsSync15(file2)) continue;
24486
- const raw = readFileSync14(file2, "utf8");
24485
+ if (!existsSync16(file2)) continue;
24486
+ const raw = readFileSync16(file2, "utf8");
24487
24487
  const content = raw.toLowerCase();
24488
24488
  let idx = -1;
24489
24489
  let matchLen = 0;
@@ -24677,21 +24677,21 @@ __export(updater_exports, {
24677
24677
  resolveBundledNpmCli: () => resolveBundledNpmCli
24678
24678
  });
24679
24679
  import { createRequire } from "node:module";
24680
- import { spawn as spawn3 } from "node:child_process";
24681
- import { existsSync as existsSync16 } from "node:fs";
24682
- import path16 from "node:path";
24680
+ import { spawn as spawn4 } from "node:child_process";
24681
+ import { existsSync as existsSync17 } from "node:fs";
24682
+ import path22 from "node:path";
24683
24683
  import { fileURLToPath } from "node:url";
24684
24684
  function resolveBundledNpmCli(execPath = process.execPath) {
24685
- const dir = path16.dirname(execPath);
24685
+ const dir = path22.dirname(execPath);
24686
24686
  const candidates = [
24687
24687
  // Windows: C:\...\node.exe → C:\...\node_modules\npm\bin\npm-cli.js
24688
- path16.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
24688
+ path22.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
24689
24689
  // POSIX: <prefix>/bin/node → <prefix>/lib/node_modules/npm/bin/npm-cli.js
24690
- path16.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
24690
+ path22.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
24691
24691
  ];
24692
24692
  for (const candidate of candidates) {
24693
24693
  try {
24694
- if (existsSync16(candidate)) return candidate;
24694
+ if (existsSync17(candidate)) return candidate;
24695
24695
  } catch {
24696
24696
  }
24697
24697
  }
@@ -24704,7 +24704,7 @@ function looksLikeBrokenShim(exitCode, output) {
24704
24704
  }
24705
24705
  function getCurrentVersion() {
24706
24706
  try {
24707
- const pkgPath = path16.resolve(__dirname2, "..", "..", "package.json");
24707
+ const pkgPath = path22.resolve(__dirname2, "..", "..", "package.json");
24708
24708
  const pkg = require2(pkgPath);
24709
24709
  return pkg.version;
24710
24710
  } catch {
@@ -24764,7 +24764,7 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
24764
24764
  updateAvailable: cmp < 0
24765
24765
  };
24766
24766
  }
24767
- async function performUpdate(packageName = "zelari-code", executor = spawn3, resolveNpmCli = resolveBundledNpmCli) {
24767
+ async function performUpdate(packageName = "zelari-code", executor = spawn4, resolveNpmCli = resolveBundledNpmCli) {
24768
24768
  const args = ["install", "-g", `${packageName}@latest`];
24769
24769
  const primary = await runNpm(executor, args, "shim");
24770
24770
  if (primary.ok) return primary;
@@ -24816,13 +24816,13 @@ var init_updater = __esm({
24816
24816
  "use strict";
24817
24817
  init_cmdline();
24818
24818
  require2 = createRequire(import.meta.url);
24819
- __dirname2 = path16.dirname(fileURLToPath(import.meta.url));
24819
+ __dirname2 = path22.dirname(fileURLToPath(import.meta.url));
24820
24820
  REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
24821
24821
  }
24822
24822
  });
24823
24823
 
24824
24824
  // src/cli/mcp/mcpClient.ts
24825
- import { spawn as spawn4 } from "node:child_process";
24825
+ import { spawn as spawn5 } from "node:child_process";
24826
24826
  var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
24827
24827
  var init_mcpClient = __esm({
24828
24828
  "src/cli/mcp/mcpClient.ts"() {
@@ -24850,10 +24850,10 @@ var init_mcpClient = __esm({
24850
24850
  env: { ...process.env, ...this.config.env ?? {} },
24851
24851
  windowsHide: true
24852
24852
  };
24853
- const child = process.platform === "win32" ? spawn4(buildCmdLine(this.config.command, this.config.args ?? []), {
24853
+ const child = process.platform === "win32" ? spawn5(buildCmdLine(this.config.command, this.config.args ?? []), {
24854
24854
  ...spawnOpts,
24855
24855
  shell: true
24856
- }) : spawn4(this.config.command, this.config.args ?? [], spawnOpts);
24856
+ }) : spawn5(this.config.command, this.config.args ?? [], spawnOpts);
24857
24857
  this.child = child;
24858
24858
  child.stdout.setEncoding("utf8");
24859
24859
  child.stdout.on("data", (chunk) => this.onStdout(chunk));
@@ -24997,20 +24997,20 @@ __export(mcpManager_exports, {
24997
24997
  readMcpConfig: () => readMcpConfig,
24998
24998
  registerMcpTools: () => registerMcpTools
24999
24999
  });
25000
- import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
25000
+ import { existsSync as existsSync18, readFileSync as readFileSync17 } from "node:fs";
25001
25001
  import { join as join15 } from "node:path";
25002
- import { homedir as homedir4 } from "node:os";
25002
+ import { homedir as homedir5 } from "node:os";
25003
25003
  function readMcpConfig(projectRoot = process.cwd()) {
25004
25004
  const merged = {};
25005
25005
  const paths = [
25006
- join15(homedir4(), ".zelari-code", "mcp.json"),
25006
+ join15(homedir5(), ".zelari-code", "mcp.json"),
25007
25007
  join15(projectRoot, ".zelari", "mcp.json")
25008
25008
  // later = higher precedence
25009
25009
  ];
25010
25010
  for (const p3 of paths) {
25011
- if (!existsSync17(p3)) continue;
25011
+ if (!existsSync18(p3)) continue;
25012
25012
  try {
25013
- const parsed = JSON.parse(readFileSync15(p3, "utf8"));
25013
+ const parsed = JSON.parse(readFileSync17(p3, "utf8"));
25014
25014
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
25015
25015
  if (!cfg || typeof cfg.command !== "string" || cfg.command.length === 0) continue;
25016
25016
  merged[name] = cfg;
@@ -25142,13 +25142,13 @@ var planDetect_exports = {};
25142
25142
  __export(planDetect_exports, {
25143
25143
  hasWorkspacePlan: () => hasWorkspacePlan
25144
25144
  });
25145
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
25145
+ import { existsSync as existsSync19, readFileSync as readFileSync18 } from "node:fs";
25146
25146
  import { join as join16 } from "node:path";
25147
25147
  function hasWorkspacePlan(projectRoot = process.cwd()) {
25148
25148
  const planPath = join16(resolveWorkspaceRoot(projectRoot), "plan.json");
25149
- if (!existsSync18(planPath)) return false;
25149
+ if (!existsSync19(planPath)) return false;
25150
25150
  try {
25151
- const parsed = JSON.parse(readFileSync16(planPath, "utf8"));
25151
+ const parsed = JSON.parse(readFileSync18(planPath, "utf8"));
25152
25152
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
25153
25153
  } catch {
25154
25154
  return false;
@@ -25245,15 +25245,15 @@ __export(agentsMd_exports, {
25245
25245
  serializeAgentsMd: () => serializeAgentsMd,
25246
25246
  updateAgentsMd: () => updateAgentsMd
25247
25247
  });
25248
- import { existsSync as existsSync19, readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "node:fs";
25249
- import { createHash as createHash2 } from "node:crypto";
25248
+ import { existsSync as existsSync20, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "node:fs";
25249
+ import { createHash as createHash3 } from "node:crypto";
25250
25250
  import { join as join17 } from "node:path";
25251
- import { readFile } from "node:fs/promises";
25251
+ import { readFile as readFile2 } from "node:fs/promises";
25252
25252
  async function readPackageJson2(projectRoot) {
25253
- const path27 = join17(projectRoot, "package.json");
25254
- if (!existsSync19(path27)) return null;
25253
+ const path33 = join17(projectRoot, "package.json");
25254
+ if (!existsSync20(path33)) return null;
25255
25255
  try {
25256
- return JSON.parse(await readFile(path27, "utf8"));
25256
+ return JSON.parse(await readFile2(path33, "utf8"));
25257
25257
  } catch {
25258
25258
  return null;
25259
25259
  }
@@ -25276,7 +25276,7 @@ async function genTechStack(ctx) {
25276
25276
  }
25277
25277
  async function genDecisions(ctx) {
25278
25278
  const decisionsDir = join17(ctx.rootDir, "decisions");
25279
- if (!existsSync19(decisionsDir)) return "_No ADRs yet._";
25279
+ if (!existsSync20(decisionsDir)) return "_No ADRs yet._";
25280
25280
  const files = ctx.storage.listMarkdown(decisionsDir).sort();
25281
25281
  const accepted = [];
25282
25282
  const proposed = [];
@@ -25302,8 +25302,8 @@ async function genDecisions(ctx) {
25302
25302
  async function genConventions(ctx) {
25303
25303
  const lines = [];
25304
25304
  const claudeMd = join17(ctx.projectRoot, "CLAUDE.MD");
25305
- if (existsSync19(claudeMd)) {
25306
- const content = readFileSync17(claudeMd, "utf8");
25305
+ if (existsSync20(claudeMd)) {
25306
+ const content = readFileSync19(claudeMd, "utf8");
25307
25307
  const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
25308
25308
  if (match) {
25309
25309
  lines.push('<!-- Extracted from CLAUDE.MD "Architecture rules" -->');
@@ -25335,9 +25335,9 @@ async function genBuild(ctx) {
25335
25335
  ].join("\n");
25336
25336
  }
25337
25337
  async function genOpenQuestions(ctx) {
25338
- const path27 = join17(ctx.rootDir, "risks.md");
25339
- if (!existsSync19(path27)) return "_No open questions._";
25340
- const content = readFileSync17(path27, "utf8");
25338
+ const path33 = join17(ctx.rootDir, "risks.md");
25339
+ if (!existsSync20(path33)) return "_No open questions._";
25340
+ const content = readFileSync19(path33, "utf8");
25341
25341
  const lines = content.split("\n");
25342
25342
  const questions = [];
25343
25343
  let currentTitle = "";
@@ -25412,8 +25412,8 @@ function titleCase(id) {
25412
25412
  }
25413
25413
  async function updateAgentsMd(ctx, projectRoot) {
25414
25414
  const agentsPath = join17(projectRoot, "AGENTS.MD");
25415
- if (existsSync19(agentsPath)) {
25416
- const content = readFileSync17(agentsPath, "utf8");
25415
+ if (existsSync20(agentsPath)) {
25416
+ const content = readFileSync19(agentsPath, "utf8");
25417
25417
  const hasAnyMarker = AUTO_SECTIONS.some((id) => content.includes(MARKER_OPEN(id)));
25418
25418
  if (!hasAnyMarker) {
25419
25419
  return {
@@ -25428,8 +25428,8 @@ async function updateAgentsMd(ctx, projectRoot) {
25428
25428
  newSections.set(id, await GENERATORS[id](ctx));
25429
25429
  }
25430
25430
  let manualContent = "";
25431
- if (existsSync19(agentsPath)) {
25432
- const { manualBlocks } = parseAgentsMd(readFileSync17(agentsPath, "utf8"));
25431
+ if (existsSync20(agentsPath)) {
25432
+ const { manualBlocks } = parseAgentsMd(readFileSync19(agentsPath, "utf8"));
25433
25433
  manualContent = manualBlocks.after;
25434
25434
  } else {
25435
25435
  const projectName2 = projectName(projectRoot);
@@ -25445,7 +25445,7 @@ async function updateAgentsMd(ctx, projectRoot) {
25445
25445
  ""
25446
25446
  ].join("\n");
25447
25447
  }
25448
- const oldContent = existsSync19(agentsPath) ? readFileSync17(agentsPath, "utf8") : "";
25448
+ const oldContent = existsSync20(agentsPath) ? readFileSync19(agentsPath, "utf8") : "";
25449
25449
  const { sections: oldSections } = parseAgentsMd(oldContent);
25450
25450
  const changedSections = [];
25451
25451
  for (const id of AUTO_SECTIONS) {
@@ -25461,7 +25461,7 @@ async function updateAgentsMd(ctx, projectRoot) {
25461
25461
  return { changed: true, sections: changedSections };
25462
25462
  }
25463
25463
  function hash2(s) {
25464
- return createHash2("sha256").update(s).digest("hex").slice(0, 16);
25464
+ return createHash3("sha256").update(s).digest("hex").slice(0, 16);
25465
25465
  }
25466
25466
  var AUTO_SECTIONS, MARKER_OPEN, MARKER_CLOSE, GENERATORS;
25467
25467
  var init_agentsMd = __esm({
@@ -25582,8 +25582,8 @@ var init_completeDesign = __esm({
25582
25582
  });
25583
25583
 
25584
25584
  // src/cli/workspace/projectSmoke.ts
25585
- import { spawn as spawn5 } from "node:child_process";
25586
- import { existsSync as existsSync20, readFileSync as readFileSync18 } from "node:fs";
25585
+ import { spawn as spawn6 } from "node:child_process";
25586
+ import { existsSync as existsSync21, readFileSync as readFileSync20 } from "node:fs";
25587
25587
  import { join as join18 } from "node:path";
25588
25588
  function pickSmokeScript(scripts) {
25589
25589
  if (!scripts) return null;
@@ -25597,12 +25597,12 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
25597
25597
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
25598
25598
  }
25599
25599
  const pkgPath = join18(projectRoot, "package.json");
25600
- if (!existsSync20(pkgPath)) {
25600
+ if (!existsSync21(pkgPath)) {
25601
25601
  return { ran: false, reason: "no package.json (skipped)" };
25602
25602
  }
25603
25603
  let scripts = {};
25604
25604
  try {
25605
- const pkg = JSON.parse(readFileSync18(pkgPath, "utf8"));
25605
+ const pkg = JSON.parse(readFileSync20(pkgPath, "utf8"));
25606
25606
  scripts = pkg.scripts ?? {};
25607
25607
  } catch {
25608
25608
  return { ran: false, reason: "package.json unreadable (skipped)" };
@@ -25613,7 +25613,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
25613
25613
  }
25614
25614
  return await new Promise((resolveRun) => {
25615
25615
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
25616
- const child = spawn5(npmCmd, ["run", script], {
25616
+ const child = spawn6(npmCmd, ["run", script], {
25617
25617
  cwd: projectRoot,
25618
25618
  stdio: ["ignore", "pipe", "pipe"],
25619
25619
  env: process.env,
@@ -25685,8 +25685,8 @@ __export(postCouncilHook_exports, {
25685
25685
  runImplementationVerificationHook: () => runImplementationVerificationHook,
25686
25686
  runPostCouncilHook: () => runPostCouncilHook
25687
25687
  });
25688
- import { spawn as spawn6 } from "node:child_process";
25689
- import { existsSync as existsSync21, readFileSync as readFileSync19 } from "node:fs";
25688
+ import { spawn as spawn7 } from "node:child_process";
25689
+ import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
25690
25690
  import { join as join19 } from "node:path";
25691
25691
  async function runCompleteDesignPostProcessor(ctx, options) {
25692
25692
  if (options?.runMode === "implementation") {
@@ -25700,7 +25700,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
25700
25700
  }
25701
25701
  const planJsonPath2 = join19(ctx.rootDir, "plan.json");
25702
25702
  const scriptPath = join19(ctx.projectRoot, "complete-design.mjs");
25703
- if (!existsSync21(planJsonPath2)) {
25703
+ if (!existsSync22(planJsonPath2)) {
25704
25704
  return {
25705
25705
  ran: false,
25706
25706
  reason: ".zelari/plan.json missing (not design-phase)"
@@ -25708,7 +25708,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
25708
25708
  }
25709
25709
  let phaseCount = 0;
25710
25710
  try {
25711
- const parsed = JSON.parse(readFileSync19(planJsonPath2, "utf8"));
25711
+ const parsed = JSON.parse(readFileSync21(planJsonPath2, "utf8"));
25712
25712
  phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
25713
25713
  } catch {
25714
25714
  return { ran: false, reason: ".zelari/plan.json corrupt" };
@@ -25716,7 +25716,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
25716
25716
  if (phaseCount === 0) {
25717
25717
  return { ran: false, reason: ".zelari/plan.json has no phases" };
25718
25718
  }
25719
- if (!existsSync21(scriptPath)) {
25719
+ if (!existsSync22(scriptPath)) {
25720
25720
  try {
25721
25721
  const builtin = await runBuiltinCompleteDesign(ctx);
25722
25722
  return {
@@ -25734,7 +25734,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
25734
25734
  }
25735
25735
  }
25736
25736
  return await new Promise((resolveRun) => {
25737
- const child = spawn6(process.execPath, [scriptPath], {
25737
+ const child = spawn7(process.execPath, [scriptPath], {
25738
25738
  cwd: ctx.projectRoot,
25739
25739
  stdio: ["ignore", "pipe", "pipe"],
25740
25740
  env: process.env
@@ -25900,8 +25900,8 @@ async function runPostCouncilHook(ctx, options) {
25900
25900
  sources: scope.sources
25901
25901
  } : void 0
25902
25902
  });
25903
- const path27 = writeCouncilCompletion(ctx.rootDir, completion);
25904
- completionHook = { ran: true, path: path27, completion };
25903
+ const path33 = writeCouncilCompletion(ctx.rootDir, completion);
25904
+ completionHook = { ran: true, path: path33, completion };
25905
25905
  } catch (err) {
25906
25906
  completionHook = {
25907
25907
  ran: true,
@@ -25937,12 +25937,12 @@ var buildLessonsSummary_exports = {};
25937
25937
  __export(buildLessonsSummary_exports, {
25938
25938
  buildLessonsSummary: () => buildLessonsSummary
25939
25939
  });
25940
- import { existsSync as existsSync22 } from "node:fs";
25940
+ import { existsSync as existsSync23 } from "node:fs";
25941
25941
  import { join as join20 } from "node:path";
25942
25942
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
25943
25943
  if (process.env["ZELARI_LESSONS"] === "0") return null;
25944
25944
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
25945
- if (!existsSync22(join20(zelariRoot, "lessons.jsonl"))) return null;
25945
+ if (!existsSync23(join20(zelariRoot, "lessons.jsonl"))) return null;
25946
25946
  const lessons = recallLessons(zelariRoot, {
25947
25947
  maxLessons: 5,
25948
25948
  maxBytes: 2048,
@@ -25964,14 +25964,14 @@ __export(councilFeedback_exports, {
25964
25964
  FeedbackStore: () => FeedbackStore
25965
25965
  });
25966
25966
  import {
25967
- promises as fs12,
25968
- existsSync as existsSync23,
25969
- readFileSync as readFileSync20,
25967
+ promises as fs13,
25968
+ existsSync as existsSync24,
25969
+ readFileSync as readFileSync22,
25970
25970
  writeFileSync as writeFileSync14,
25971
25971
  mkdirSync as mkdirSync9
25972
25972
  } from "node:fs";
25973
- import path17 from "node:path";
25974
- import os7 from "node:os";
25973
+ import path23 from "node:path";
25974
+ import os8 from "node:os";
25975
25975
  var FeedbackStore;
25976
25976
  var init_councilFeedback = __esm({
25977
25977
  "src/cli/councilFeedback.ts"() {
@@ -25981,7 +25981,7 @@ var init_councilFeedback = __esm({
25981
25981
  now;
25982
25982
  entries = [];
25983
25983
  constructor(options = {}) {
25984
- this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path17.join(os7.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
25984
+ this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path23.join(os8.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
25985
25985
  this.now = options.now ?? Date.now;
25986
25986
  this.load();
25987
25987
  }
@@ -26074,9 +26074,9 @@ var init_councilFeedback = __esm({
26074
26074
  }
26075
26075
  // --- persistence ---------------------------------------------------------
26076
26076
  load() {
26077
- if (!existsSync23(this.file)) return;
26077
+ if (!existsSync24(this.file)) return;
26078
26078
  try {
26079
- const raw = readFileSync20(this.file, "utf-8");
26079
+ const raw = readFileSync22(this.file, "utf-8");
26080
26080
  const parsed = JSON.parse(raw);
26081
26081
  if (parsed && Array.isArray(parsed.entries)) {
26082
26082
  this.entries = parsed.entries.filter(
@@ -26087,7 +26087,7 @@ var init_councilFeedback = __esm({
26087
26087
  }
26088
26088
  }
26089
26089
  save() {
26090
- mkdirSync9(path17.dirname(this.file), { recursive: true });
26090
+ mkdirSync9(path23.dirname(this.file), { recursive: true });
26091
26091
  writeFileSync14(
26092
26092
  this.file,
26093
26093
  JSON.stringify({ entries: this.entries }, null, 2),
@@ -26097,7 +26097,7 @@ var init_councilFeedback = __esm({
26097
26097
  /** Async variant of load for callers that prefer async IO. */
26098
26098
  async loadAsync() {
26099
26099
  try {
26100
- const raw = await fs12.readFile(this.file, "utf-8");
26100
+ const raw = await fs13.readFile(this.file, "utf-8");
26101
26101
  const parsed = JSON.parse(raw);
26102
26102
  if (parsed && Array.isArray(parsed.entries)) {
26103
26103
  this.entries = parsed.entries.filter(
@@ -26121,8 +26121,8 @@ __export(fileBackend_exports, {
26121
26121
  isMemoryEnabled: () => isMemoryEnabled
26122
26122
  });
26123
26123
  import { randomUUID as randomUUID2 } from "node:crypto";
26124
- import { promises as fs13 } from "node:fs";
26125
- import * as path18 from "node:path";
26124
+ import { promises as fs14 } from "node:fs";
26125
+ import * as path24 from "node:path";
26126
26126
  function tokenize(text) {
26127
26127
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 3);
26128
26128
  }
@@ -26160,9 +26160,9 @@ var init_fileBackend = __esm({
26160
26160
  logPath = "";
26161
26161
  memoryDir = "";
26162
26162
  async init(projectRoot) {
26163
- this.memoryDir = path18.join(projectRoot, ".zelari", "memory");
26164
- this.logPath = path18.join(this.memoryDir, "log.jsonl");
26165
- await fs13.mkdir(this.memoryDir, { recursive: true });
26163
+ this.memoryDir = path24.join(projectRoot, ".zelari", "memory");
26164
+ this.logPath = path24.join(this.memoryDir, "log.jsonl");
26165
+ await fs14.mkdir(this.memoryDir, { recursive: true });
26166
26166
  }
26167
26167
  async add(content, metadata = {}, graph) {
26168
26168
  const fact = {
@@ -26172,7 +26172,7 @@ var init_fileBackend = __esm({
26172
26172
  ...graph ? { graph } : {},
26173
26173
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
26174
26174
  };
26175
- await fs13.appendFile(this.logPath, JSON.stringify(fact) + "\n", "utf8");
26175
+ await fs14.appendFile(this.logPath, JSON.stringify(fact) + "\n", "utf8");
26176
26176
  return fact.id;
26177
26177
  }
26178
26178
  async search(query, options = {}) {
@@ -26200,7 +26200,7 @@ var init_fileBackend = __esm({
26200
26200
  async readAll() {
26201
26201
  let raw;
26202
26202
  try {
26203
- raw = await fs13.readFile(this.logPath, "utf8");
26203
+ raw = await fs14.readFile(this.logPath, "utf8");
26204
26204
  } catch {
26205
26205
  return [];
26206
26206
  }
@@ -26236,7 +26236,7 @@ import { execFile as execFile2 } from "node:child_process";
26236
26236
  import { promisify } from "node:util";
26237
26237
  import { mkdtempSync, rmSync } from "node:fs";
26238
26238
  import { tmpdir } from "node:os";
26239
- import path19 from "node:path";
26239
+ import path25 from "node:path";
26240
26240
  import { randomUUID as randomUUID3 } from "node:crypto";
26241
26241
  async function git(cwd, args, env) {
26242
26242
  const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
@@ -26256,8 +26256,8 @@ async function isGitRepo(cwd) {
26256
26256
  return await gitSafe(cwd, ["rev-parse", "--is-inside-work-tree"]) === "true";
26257
26257
  }
26258
26258
  async function withTempIndex(fn) {
26259
- const dir = mkdtempSync(path19.join(tmpdir(), "zelari-ckpt-"));
26260
- const indexFile = path19.join(dir, "index");
26259
+ const dir = mkdtempSync(path25.join(tmpdir(), "zelari-ckpt-"));
26260
+ const indexFile = path25.join(dir, "index");
26261
26261
  try {
26262
26262
  return await fn(indexFile);
26263
26263
  } finally {
@@ -26348,7 +26348,7 @@ async function restoreCheckpoint(cwd, id) {
26348
26348
  const deleted = [];
26349
26349
  for (const rel2 of added) {
26350
26350
  try {
26351
- rmSync(path19.join(cwd, rel2), { force: true });
26351
+ rmSync(path25.join(cwd, rel2), { force: true });
26352
26352
  deleted.push(rel2);
26353
26353
  } catch {
26354
26354
  }
@@ -26377,8 +26377,8 @@ __export(zelariMission_exports, {
26377
26377
  runZelariMission: () => runZelariMission
26378
26378
  });
26379
26379
  import { randomUUID as randomUUID4 } from "node:crypto";
26380
- import { promises as fs14 } from "node:fs";
26381
- import * as path20 from "node:path";
26380
+ import { promises as fs15 } from "node:fs";
26381
+ import * as path26 from "node:path";
26382
26382
  function resolveMaxIterations(env = process.env) {
26383
26383
  const raw = env.ZELARI_MISSION_MAX_ITER;
26384
26384
  const n = raw ? Number.parseInt(raw, 10) : DEFAULT_MAX_ITER;
@@ -26394,10 +26394,10 @@ function isMissionAutoStart(env = process.env) {
26394
26394
  return env.ZELARI_MISSION_AUTO === "1";
26395
26395
  }
26396
26396
  async function writeMissionState(projectRoot, state2) {
26397
- const dir = path20.join(projectRoot, ".zelari");
26398
- await fs14.mkdir(dir, { recursive: true });
26399
- await fs14.writeFile(
26400
- path20.join(dir, "mission-state.json"),
26397
+ const dir = path26.join(projectRoot, ".zelari");
26398
+ await fs15.mkdir(dir, { recursive: true });
26399
+ await fs15.writeFile(
26400
+ path26.join(dir, "mission-state.json"),
26401
26401
  JSON.stringify(state2, null, 2) + "\n",
26402
26402
  "utf8"
26403
26403
  );
@@ -26534,36 +26534,296 @@ var init_zelariMission = __esm({
26534
26534
  }
26535
26535
  });
26536
26536
 
26537
+ // src/cli/utils/prereqChecks.ts
26538
+ var prereqChecks_exports = {};
26539
+ __export(prereqChecks_exports, {
26540
+ checkAgentBash: () => checkAgentBash,
26541
+ checkAgentGit: () => checkAgentGit,
26542
+ checkAgentNode: () => checkAgentNode,
26543
+ checkMainNode: () => checkMainNode,
26544
+ runPrereqChecks: () => runPrereqChecks
26545
+ });
26546
+ import { execSync, spawnSync as spawnSync2 } from "node:child_process";
26547
+ import { existsSync as existsSync25 } from "node:fs";
26548
+ function resolveAgentShellSync() {
26549
+ if (process.platform !== "win32") {
26550
+ return { bashPath: null, isBash: true, via: "/bin/sh" };
26551
+ }
26552
+ const envShell = process.env.ZELARI_SHELL;
26553
+ if (envShell && envShell.trim().length > 0 && existsSyncSafe2(envShell)) {
26554
+ return { bashPath: envShell, isBash: true, via: `bash (${envShell})` };
26555
+ }
26556
+ const sessionShell = process.env.SHELL;
26557
+ if (sessionShell && sessionShell.trim().length > 0 && existsSyncSafe2(sessionShell)) {
26558
+ return {
26559
+ bashPath: sessionShell,
26560
+ isBash: true,
26561
+ via: `bash (${sessionShell})`
26562
+ };
26563
+ }
26564
+ for (const p3 of STANDARD_BASH_PATHS2) {
26565
+ if (existsSyncSafe2(p3)) {
26566
+ return { bashPath: p3, isBash: true, via: `bash (${p3})` };
26567
+ }
26568
+ }
26569
+ try {
26570
+ const result = spawnSync2("where", ["bash"], {
26571
+ encoding: "utf8",
26572
+ windowsHide: true
26573
+ });
26574
+ if (result.status === 0 && result.stdout) {
26575
+ const first = result.stdout.split(/\r?\n/).find((l) => l.trim().length > 0);
26576
+ if (first && existsSyncSafe2(first)) {
26577
+ const trimmed = first.trim();
26578
+ return { bashPath: trimmed, isBash: true, via: `bash (${trimmed})` };
26579
+ }
26580
+ }
26581
+ } catch {
26582
+ }
26583
+ return { bashPath: null, isBash: false, via: "cmd.exe" };
26584
+ }
26585
+ function existsSyncSafe2(p3) {
26586
+ try {
26587
+ return existsSync25(p3);
26588
+ } catch {
26589
+ return false;
26590
+ }
26591
+ }
26592
+ function probeTool(tool) {
26593
+ const shell = resolveAgentShellSync();
26594
+ let stdout = "";
26595
+ if (shell.bashPath) {
26596
+ try {
26597
+ const r = spawnSync2(shell.bashPath, ["-c", `${tool} --version`], {
26598
+ encoding: "utf8",
26599
+ stdio: ["ignore", "pipe", "ignore"],
26600
+ windowsHide: true
26601
+ });
26602
+ if (r.status === 0) stdout = (r.stdout || "").trim();
26603
+ } catch {
26604
+ }
26605
+ } else if (process.platform === "win32") {
26606
+ try {
26607
+ stdout = execSync(`${tool} --version`, {
26608
+ encoding: "utf8",
26609
+ stdio: ["ignore", "pipe", "ignore"]
26610
+ }).trim();
26611
+ } catch {
26612
+ }
26613
+ } else {
26614
+ try {
26615
+ stdout = execSync(`${tool} --version`, {
26616
+ encoding: "utf8",
26617
+ stdio: ["ignore", "pipe", "ignore"]
26618
+ }).trim();
26619
+ } catch {
26620
+ }
26621
+ }
26622
+ const m = stdout.match(/(\d+)\.(\d+)\.(\d+)/);
26623
+ return {
26624
+ found: stdout.length > 0,
26625
+ version: m ? `${m[1]}.${m[2]}.${m[3]}` : "",
26626
+ raw: stdout
26627
+ };
26628
+ }
26629
+ function nodeMissingHint() {
26630
+ const shell = resolveAgentShellSync();
26631
+ if (process.platform === "win32") {
26632
+ return `node is not reachable from the agent's shell (${shell.via}).
26633
+ This usually means Node was installed for "current user" only,
26634
+ while Git Bash inherits the SYSTEM Path. Fix (pick one):
26635
+ - Reinstall Node (https://nodejs.org) and choose
26636
+ "Add to PATH for all users", OR
26637
+ - Add C:\\Program Files\\nodejs\\ to the SYSTEM Path
26638
+ (System Properties \u2192 Environment Variables \u2192 Path), OR
26639
+ - Set ZELARI_SHELL to a bash that already sees node.`;
26640
+ }
26641
+ return `node is not on the agent's shell PATH.
26642
+ Install Node >= ${MIN_NODE_MAJOR} (https://nodejs.org) or, if you use
26643
+ nvm, run \`nvm use <version>\` and reopen this terminal.`;
26644
+ }
26645
+ function checkAgentNode() {
26646
+ const probe = probeTool("node");
26647
+ if (!probe.found) {
26648
+ return {
26649
+ ok: false,
26650
+ severity: "critical",
26651
+ tool: "node",
26652
+ message: nodeMissingHint()
26653
+ };
26654
+ }
26655
+ const major = Number(probe.version.split(".")[0]);
26656
+ if (!probe.version || Number.isNaN(major)) {
26657
+ return {
26658
+ ok: false,
26659
+ severity: "warn",
26660
+ tool: "node",
26661
+ message: `could not parse node version from "${probe.raw}" (node is reachable, but version check skipped)`
26662
+ };
26663
+ }
26664
+ if (major < MIN_NODE_MAJOR) {
26665
+ return {
26666
+ ok: false,
26667
+ severity: "critical",
26668
+ tool: "node",
26669
+ message: `node ${probe.version} is older than the required >= ${MIN_NODE_MAJOR}.0.0 (from engines.node). Upgrade: https://nodejs.org`
26670
+ };
26671
+ }
26672
+ return {
26673
+ ok: true,
26674
+ severity: "critical",
26675
+ tool: "node",
26676
+ message: `node ${probe.version} (agent shell)`
26677
+ };
26678
+ }
26679
+ function checkAgentGit() {
26680
+ const probe = probeTool("git");
26681
+ if (!probe.found) {
26682
+ const hint = process.platform === "win32" ? `Install Git for Windows: https://git-scm.com/download/win` : `Install git (e.g. \`brew install git\` on macOS, \`apt install git\` on Debian)`;
26683
+ return {
26684
+ ok: false,
26685
+ severity: "warn",
26686
+ tool: "git",
26687
+ message: `git not found on the agent's shell PATH \u2014 /diff, /undo and the git sidebar will be disabled. ${hint}`
26688
+ };
26689
+ }
26690
+ return {
26691
+ ok: true,
26692
+ severity: "warn",
26693
+ tool: "git",
26694
+ message: `git ${probe.version} (agent shell)`
26695
+ };
26696
+ }
26697
+ function checkAgentBash() {
26698
+ if (process.platform !== "win32") {
26699
+ return {
26700
+ ok: true,
26701
+ severity: "warn",
26702
+ tool: "bash",
26703
+ message: "POSIX shell always available on this platform"
26704
+ };
26705
+ }
26706
+ const shell = resolveAgentShellSync();
26707
+ if (shell.isBash) {
26708
+ return {
26709
+ ok: true,
26710
+ severity: "warn",
26711
+ tool: "bash",
26712
+ message: `real bash available (${shell.via})`
26713
+ };
26714
+ }
26715
+ return {
26716
+ ok: false,
26717
+ severity: "warn",
26718
+ tool: "bash",
26719
+ message: `no Git Bash found \u2014 the agent's \`bash\` tool falls back to cmd.exe,
26720
+ where POSIX commands (ls, which, $VAR, &&) may fail. Install Git
26721
+ for Windows (https://git-scm.com/download/win) or set ZELARI_SHELL
26722
+ to your bash binary.`
26723
+ };
26724
+ }
26725
+ function checkMainNode() {
26726
+ try {
26727
+ const raw = execSync("node --version", {
26728
+ encoding: "utf8",
26729
+ stdio: ["ignore", "pipe", "ignore"]
26730
+ }).trim();
26731
+ const m = raw.match(/(\d+)\.(\d+)\.(\d+)/);
26732
+ const version2 = m ? `${m[1]}.${m[2]}.${m[3]}` : "";
26733
+ const major = Number(version2.split(".")[0]);
26734
+ if (version2 && !Number.isNaN(major) && major >= MIN_NODE_MAJOR) {
26735
+ return {
26736
+ ok: true,
26737
+ severity: "critical",
26738
+ tool: "node",
26739
+ message: `node ${version2} (main process)`
26740
+ };
26741
+ }
26742
+ return {
26743
+ ok: false,
26744
+ severity: "warn",
26745
+ tool: "node",
26746
+ message: `node present in main process but version unparseable or < ${MIN_NODE_MAJOR}: "${raw}"`
26747
+ };
26748
+ } catch {
26749
+ return {
26750
+ ok: false,
26751
+ severity: "critical",
26752
+ tool: "node",
26753
+ message: "node not found on the main process PATH (zelari-code itself runs on node \u2014 this is unexpected)"
26754
+ };
26755
+ }
26756
+ }
26757
+ function runPrereqChecks(opts = { mode: "preflight" }) {
26758
+ const checks = [
26759
+ () => checkAgentNode(),
26760
+ () => checkAgentGit(),
26761
+ () => checkAgentBash()
26762
+ ];
26763
+ const results = [];
26764
+ for (const run of checks) {
26765
+ try {
26766
+ results.push(run());
26767
+ } catch (err) {
26768
+ results.push({
26769
+ ok: false,
26770
+ severity: "warn",
26771
+ tool: "node",
26772
+ message: `prereq check crashed: ${err instanceof Error ? err.message : String(err)}`
26773
+ });
26774
+ }
26775
+ }
26776
+ const hasCriticalFail = results.some(
26777
+ (r) => !r.ok && r.severity === "critical"
26778
+ );
26779
+ const warnings = results.filter((r) => !r.ok && r.severity === "warn");
26780
+ void opts.mode;
26781
+ return { results, hasCriticalFail, warnings };
26782
+ }
26783
+ var MIN_NODE_MAJOR, STANDARD_BASH_PATHS2;
26784
+ var init_prereqChecks = __esm({
26785
+ "src/cli/utils/prereqChecks.ts"() {
26786
+ "use strict";
26787
+ MIN_NODE_MAJOR = 20;
26788
+ STANDARD_BASH_PATHS2 = [
26789
+ "C:\\Program Files\\Git\\bin\\bash.exe",
26790
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
26791
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
26792
+ "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
26793
+ ];
26794
+ }
26795
+ });
26796
+
26537
26797
  // src/cli/utils/doctor.ts
26538
26798
  var doctor_exports = {};
26539
26799
  __export(doctor_exports, {
26540
26800
  runDoctor: () => runDoctor
26541
26801
  });
26542
- import { execSync } from "node:child_process";
26543
- import { existsSync as existsSync28, readFileSync as readFileSync23, readlinkSync, statSync as statSync6 } from "node:fs";
26802
+ import { execSync as execSync2 } from "node:child_process";
26803
+ import { existsSync as existsSync30, readFileSync as readFileSync25, readlinkSync, statSync as statSync6 } from "node:fs";
26544
26804
  import { createRequire as createRequire2 } from "node:module";
26545
26805
  import { fileURLToPath as fileURLToPath2 } from "node:url";
26546
- import path26 from "node:path";
26806
+ import path32 from "node:path";
26547
26807
  function findPackageRoot(start) {
26548
26808
  let dir = start;
26549
26809
  for (let i = 0; i < 6; i += 1) {
26550
- const candidate = path26.join(dir, "package.json");
26551
- if (existsSync28(candidate)) {
26810
+ const candidate = path32.join(dir, "package.json");
26811
+ if (existsSync30(candidate)) {
26552
26812
  try {
26553
- const pkg = JSON.parse(readFileSync23(candidate, "utf8"));
26813
+ const pkg = JSON.parse(readFileSync25(candidate, "utf8"));
26554
26814
  if (pkg.name === "zelari-code") return dir;
26555
26815
  } catch {
26556
26816
  }
26557
26817
  }
26558
- const parent = path26.dirname(dir);
26818
+ const parent = path32.dirname(dir);
26559
26819
  if (parent === dir) break;
26560
26820
  dir = parent;
26561
26821
  }
26562
- return path26.resolve(__dirname3, "..", "..", "..");
26822
+ return path32.resolve(__dirname3, "..", "..", "..");
26563
26823
  }
26564
26824
  function tryExec(cmd) {
26565
26825
  try {
26566
- return execSync(cmd, {
26826
+ return execSync2(cmd, {
26567
26827
  encoding: "utf8",
26568
26828
  stdio: ["ignore", "pipe", "ignore"]
26569
26829
  }).trim();
@@ -26573,8 +26833,8 @@ function tryExec(cmd) {
26573
26833
  }
26574
26834
  function readPackageJson3() {
26575
26835
  try {
26576
- const pkgPath = path26.join(packageRoot, "package.json");
26577
- return JSON.parse(readFileSync23(pkgPath, "utf8"));
26836
+ const pkgPath = path32.join(packageRoot, "package.json");
26837
+ return JSON.parse(readFileSync25(pkgPath, "utf8"));
26578
26838
  } catch {
26579
26839
  return null;
26580
26840
  }
@@ -26589,8 +26849,8 @@ function checkShim(pkgName) {
26589
26849
  }
26590
26850
  const isWin = process.platform === "win32";
26591
26851
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
26592
- const shimPath = path26.join(prefix, shimName);
26593
- if (!existsSync28(shimPath)) {
26852
+ const shimPath = path32.join(prefix, shimName);
26853
+ if (!existsSync30(shimPath)) {
26594
26854
  return FAIL(
26595
26855
  `shim not found at ${shimPath}
26596
26856
  fix: npm install -g ${pkgName}@latest --force`
@@ -26599,7 +26859,7 @@ function checkShim(pkgName) {
26599
26859
  try {
26600
26860
  const st = statSync6(shimPath);
26601
26861
  if (isWin) {
26602
- const content = readFileSync23(shimPath, "utf8");
26862
+ const content = readFileSync25(shimPath, "utf8");
26603
26863
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
26604
26864
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
26605
26865
  }
@@ -26617,8 +26877,8 @@ function checkShim(pkgName) {
26617
26877
  fix: npm install -g ${pkgName}@latest --force`
26618
26878
  );
26619
26879
  }
26620
- const resolved = path26.resolve(path26.dirname(shimPath), target);
26621
- const expected = path26.join(
26880
+ const resolved = path32.resolve(path32.dirname(shimPath), target);
26881
+ const expected = path32.join(
26622
26882
  prefix,
26623
26883
  "node_modules",
26624
26884
  pkgName,
@@ -26657,8 +26917,8 @@ function checkNode(pkg) {
26657
26917
  return OK(`node ${raw}`);
26658
26918
  }
26659
26919
  function checkBundle() {
26660
- const bundle = path26.join(packageRoot, "dist", "cli", "main.bundled.js");
26661
- if (!existsSync28(bundle)) {
26920
+ const bundle = path32.join(packageRoot, "dist", "cli", "main.bundled.js");
26921
+ if (!existsSync30(bundle)) {
26662
26922
  return FAIL(
26663
26923
  `dist/cli/main.bundled.js missing at ${bundle}
26664
26924
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -26678,7 +26938,7 @@ function checkRuntimeDeps() {
26678
26938
  const missing = [];
26679
26939
  for (const dep of required2) {
26680
26940
  try {
26681
- const localReq = createRequire2(path26.join(packageRoot, "package.json"));
26941
+ const localReq = createRequire2(path32.join(packageRoot, "package.json"));
26682
26942
  localReq.resolve(dep);
26683
26943
  } catch {
26684
26944
  missing.push(dep);
@@ -26709,15 +26969,28 @@ function checkPath() {
26709
26969
  fix (Windows): $env:Path = "$(npm prefix -g);$env:Path"`
26710
26970
  );
26711
26971
  }
26972
+ function prereqToCheckResult(r) {
26973
+ if (r.ok) return OK(r.message);
26974
+ return r.severity === "critical" ? FAIL(r.message, "critical") : WARN(r.message);
26975
+ }
26712
26976
  function runDoctor() {
26713
26977
  const pkg = readPackageJson3();
26714
26978
  const pkgName = pkg?.name ?? "zelari-code";
26715
26979
  const checks = [
26980
+ // --- install-health checks (main-process probes) ---
26716
26981
  { name: "node", run: () => checkNode(pkg) },
26717
26982
  { name: "bin shim", run: () => checkShim(pkgName) },
26718
26983
  { name: "cli bundle", run: () => checkBundle() },
26719
26984
  { name: "runtime deps", run: () => checkRuntimeDeps() },
26720
- { name: "PATH", run: () => checkPath() }
26985
+ { name: "PATH", run: () => checkPath() },
26986
+ // --- agent-shell checks (v1.4.0) ---
26987
+ // These probe node/git/bash THROUGH the resolved shell the agent uses.
26988
+ // A pass on "node" + a FAIL on "node (agent shell)" is the tell-tale
26989
+ // signature of the PATH-mismatch bug (node visible to the main process,
26990
+ // invisible to Git Bash) that silently breaks council builds.
26991
+ { name: "node (agent shell)", run: () => prereqToCheckResult(checkAgentNode()) },
26992
+ { name: "git (agent shell)", run: () => prereqToCheckResult(checkAgentGit()) },
26993
+ { name: "bash", run: () => prereqToCheckResult(checkAgentBash()) }
26721
26994
  ];
26722
26995
  console.log(`zelari-code doctor (v${pkg?.version ?? "unknown"})`);
26723
26996
  console.log("platform:", process.platform, process.arch);
@@ -26759,8 +27032,9 @@ var require3, __dirname3, packageRoot, OK, FAIL, WARN;
26759
27032
  var init_doctor = __esm({
26760
27033
  "src/cli/utils/doctor.ts"() {
26761
27034
  "use strict";
27035
+ init_prereqChecks();
26762
27036
  require3 = createRequire2(import.meta.url);
26763
- __dirname3 = path26.dirname(fileURLToPath2(import.meta.url));
27037
+ __dirname3 = path32.dirname(fileURLToPath2(import.meta.url));
26764
27038
  packageRoot = findPackageRoot(__dirname3);
26765
27039
  OK = (message) => ({
26766
27040
  ok: true,
@@ -30741,6 +31015,1166 @@ function createTaskTool(deps) {
30741
31015
  };
30742
31016
  }
30743
31017
 
31018
+ // src/cli/lsp/tools.ts
31019
+ init_zod();
31020
+ init_toolTypes();
31021
+ import path16 from "node:path";
31022
+
31023
+ // src/cli/lsp/protocol.ts
31024
+ function encodeMessage(message) {
31025
+ const json2 = JSON.stringify(message);
31026
+ const contentLength = Buffer.byteLength(json2, "utf8");
31027
+ return `Content-Length: ${contentLength}\r
31028
+ \r
31029
+ ${json2}`;
31030
+ }
31031
+ function createMessageParser() {
31032
+ let buffer = "";
31033
+ return {
31034
+ push(chunk) {
31035
+ buffer += chunk;
31036
+ const out = [];
31037
+ for (; ; ) {
31038
+ const headerEnd = buffer.indexOf("\r\n\r\n");
31039
+ if (headerEnd === -1) break;
31040
+ const header = buffer.slice(0, headerEnd);
31041
+ const match = /Content-Length:\s*(\d+)/i.exec(header);
31042
+ if (!match) {
31043
+ buffer = buffer.slice(headerEnd + 4);
31044
+ continue;
31045
+ }
31046
+ const length = Number(match[1]);
31047
+ const bodyStart = headerEnd + 4;
31048
+ const rest = Buffer.from(buffer.slice(bodyStart), "utf8");
31049
+ if (rest.length < length) break;
31050
+ const body = rest.subarray(0, length).toString("utf8");
31051
+ buffer = rest.subarray(length).toString("utf8");
31052
+ try {
31053
+ out.push(JSON.parse(body));
31054
+ } catch {
31055
+ }
31056
+ }
31057
+ return out;
31058
+ }
31059
+ };
31060
+ }
31061
+ function pathToUri(filePath) {
31062
+ let p3 = filePath.replace(/\\/g, "/");
31063
+ if (!p3.startsWith("/")) p3 = `/${p3}`;
31064
+ const encoded = p3.split("/").map((seg) => encodeURIComponent(seg)).join("/");
31065
+ return `file://${encoded}`;
31066
+ }
31067
+ function uriToPath(uri) {
31068
+ if (!uri.startsWith("file://")) return uri;
31069
+ const withoutScheme = decodeURIComponent(uri.slice("file://".length));
31070
+ return /^\/[A-Za-z]:/.test(withoutScheme) ? withoutScheme.slice(1) : withoutScheme;
31071
+ }
31072
+
31073
+ // src/cli/lsp/tools.ts
31074
+ function fmtLocation(loc, relativeTo) {
31075
+ const file2 = uriToPath(loc.uri);
31076
+ const rel2 = relativeTo ? relPath(relativeTo, file2) : file2;
31077
+ const line = (loc.range?.start?.line ?? 0) + 1;
31078
+ const col = (loc.range?.start?.character ?? 0) + 1;
31079
+ return `${rel2}:${line}:${col}`;
31080
+ }
31081
+ function relPath(from, to) {
31082
+ try {
31083
+ const r = path16.relative(from, to);
31084
+ return r && !r.startsWith("..") ? r : to;
31085
+ } catch {
31086
+ return to;
31087
+ }
31088
+ }
31089
+ var PosArgs = external_exports.object({
31090
+ path: external_exports.string().min(1).describe("File path (relative to the project root or absolute)."),
31091
+ line: external_exports.number().int().positive().describe("1-based line number of the symbol."),
31092
+ column: external_exports.number().int().positive().describe("1-based column of the symbol.")
31093
+ });
31094
+ function createLspTools(provider, root = process.cwd()) {
31095
+ const goToDefinition = {
31096
+ name: "go_to_definition",
31097
+ description: "Jump to where the symbol at a position is defined (via the language server). Returns the defining file:line:col \u2014 use it instead of guessing with grep.",
31098
+ permissions: ["read"],
31099
+ inputSchema: PosArgs,
31100
+ execute: async (args) => {
31101
+ const a = args;
31102
+ const locs = await provider.definition(a.path, a.line - 1, a.column - 1);
31103
+ return typedOk({
31104
+ definitions: locs.map((l) => fmtLocation(l, root)),
31105
+ count: locs.length
31106
+ });
31107
+ }
31108
+ };
31109
+ const findReferences = {
31110
+ name: "find_references",
31111
+ description: "Find every reference to the symbol at a position across the workspace (via the language server). Returns a list of file:line:col \u2014 reliable where a text grep would miss shadowed names or match strings/comments.",
31112
+ permissions: ["read"],
31113
+ inputSchema: PosArgs,
31114
+ execute: async (args) => {
31115
+ const a = args;
31116
+ const locs = await provider.references(a.path, a.line - 1, a.column - 1);
31117
+ return typedOk({
31118
+ references: locs.map((l) => fmtLocation(l, root)),
31119
+ count: locs.length
31120
+ });
31121
+ }
31122
+ };
31123
+ const hoverType = {
31124
+ name: "hover_type",
31125
+ description: "Get the resolved type signature and documentation for the symbol at a position (via the language server) \u2014 the real type the compiler sees.",
31126
+ permissions: ["read"],
31127
+ inputSchema: PosArgs,
31128
+ execute: async (args) => {
31129
+ const a = args;
31130
+ const text = await provider.hover(a.path, a.line - 1, a.column - 1);
31131
+ return typedOk({ hover: text ?? "(no hover information)" });
31132
+ }
31133
+ };
31134
+ const documentSymbols = {
31135
+ name: "document_symbols",
31136
+ description: "List the symbols (functions, classes, methods, variables) declared in a file with their line numbers \u2014 a fast structural outline via the language server.",
31137
+ permissions: ["read"],
31138
+ inputSchema: external_exports.object({
31139
+ path: external_exports.string().min(1).describe("File path to outline.")
31140
+ }),
31141
+ execute: async (args) => {
31142
+ const a = args;
31143
+ const symbols = await provider.documentSymbols(a.path);
31144
+ return typedOk({
31145
+ symbols: symbols.map((s) => `${s.kind} ${s.name} (line ${s.line})`),
31146
+ count: symbols.length
31147
+ });
31148
+ }
31149
+ };
31150
+ const renameSymbol = {
31151
+ name: "rename_symbol",
31152
+ description: "PREVIEW a safe, workspace-wide rename of the symbol at a position (via the language server): returns which files change and how many edits each gets, so you know the blast radius before touching anything. It does NOT write files \u2014 apply the change yourself with edit_file once the scope looks right.",
31153
+ permissions: ["read"],
31154
+ inputSchema: PosArgs.extend({
31155
+ newName: external_exports.string().min(1).describe("The new symbol name.")
31156
+ }),
31157
+ execute: async (args) => {
31158
+ const a = args;
31159
+ const result = await provider.rename(a.path, a.line - 1, a.column - 1, a.newName);
31160
+ if (!result) {
31161
+ return typedOk({ preview: "no rename available at this position (symbol not found or not renameable)" });
31162
+ }
31163
+ return typedOk({
31164
+ totalEdits: result.totalEdits,
31165
+ files: result.files.map((f) => `${relPath(root, f.file)} (${f.count} edit${f.count === 1 ? "" : "s"})`)
31166
+ });
31167
+ }
31168
+ };
31169
+ return [goToDefinition, findReferences, hoverType, documentSymbols, renameSymbol];
31170
+ }
31171
+
31172
+ // src/cli/lsp/manager.ts
31173
+ import { spawn as spawn3 } from "node:child_process";
31174
+ import { readFileSync as readFileSync5 } from "node:fs";
31175
+
31176
+ // src/cli/lsp/client.ts
31177
+ var LspClient = class {
31178
+ constructor(transport, options = {}) {
31179
+ this.transport = transport;
31180
+ this.timeoutMs = options.timeoutMs ?? 15e3;
31181
+ transport.onData((chunk) => this.onData(chunk));
31182
+ transport.onClose(() => this.onClose());
31183
+ }
31184
+ nextId = 1;
31185
+ pending = /* @__PURE__ */ new Map();
31186
+ parser = createMessageParser();
31187
+ closed = false;
31188
+ timeoutMs;
31189
+ /** Send a request and await the matching response. */
31190
+ request(method, params) {
31191
+ if (this.closed) return Promise.reject(new Error("LSP transport is closed"));
31192
+ const id = this.nextId++;
31193
+ const msg = { jsonrpc: "2.0", id, method, ...params !== void 0 ? { params } : {} };
31194
+ return new Promise((resolve, reject) => {
31195
+ const timer = setTimeout(() => {
31196
+ this.pending.delete(id);
31197
+ reject(new Error(`LSP request "${method}" timed out after ${this.timeoutMs}ms`));
31198
+ }, this.timeoutMs);
31199
+ this.pending.set(id, { resolve, reject, timer });
31200
+ this.transport.send(encodeMessage(msg));
31201
+ });
31202
+ }
31203
+ /** Fire a notification (no response expected). */
31204
+ notify(method, params) {
31205
+ if (this.closed) return;
31206
+ const msg = { jsonrpc: "2.0", method, ...params !== void 0 ? { params } : {} };
31207
+ this.transport.send(encodeMessage(msg));
31208
+ }
31209
+ onData(chunk) {
31210
+ for (const message of this.parser.push(chunk)) {
31211
+ this.handleMessage(message);
31212
+ }
31213
+ }
31214
+ handleMessage(message) {
31215
+ if (message.method === void 0 && message.id !== void 0) {
31216
+ const entry = this.pending.get(message.id);
31217
+ if (!entry) return;
31218
+ this.pending.delete(message.id);
31219
+ clearTimeout(entry.timer);
31220
+ if (message.error) {
31221
+ entry.reject(new Error(`LSP error ${message.error.code}: ${message.error.message}`));
31222
+ } else {
31223
+ entry.resolve(message.result);
31224
+ }
31225
+ return;
31226
+ }
31227
+ if (message.method !== void 0 && message.id !== void 0) {
31228
+ this.transport.send(
31229
+ encodeMessage({ jsonrpc: "2.0", id: message.id, result: null })
31230
+ );
31231
+ return;
31232
+ }
31233
+ }
31234
+ onClose() {
31235
+ this.closed = true;
31236
+ for (const [, entry] of this.pending) {
31237
+ clearTimeout(entry.timer);
31238
+ entry.reject(new Error("LSP transport closed before response"));
31239
+ }
31240
+ this.pending.clear();
31241
+ }
31242
+ dispose() {
31243
+ this.onClose();
31244
+ this.transport.dispose();
31245
+ }
31246
+ };
31247
+
31248
+ // src/cli/lsp/servers.ts
31249
+ import path17 from "node:path";
31250
+ var LSP_SERVERS = [
31251
+ {
31252
+ language: "typescript",
31253
+ bin: "typescript-language-server",
31254
+ args: ["--stdio"],
31255
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]
31256
+ },
31257
+ {
31258
+ language: "python",
31259
+ bin: "pyright-langserver",
31260
+ args: ["--stdio"],
31261
+ extensions: [".py"]
31262
+ },
31263
+ {
31264
+ language: "go",
31265
+ bin: "gopls",
31266
+ args: [],
31267
+ extensions: [".go"]
31268
+ },
31269
+ {
31270
+ language: "rust",
31271
+ bin: "rust-analyzer",
31272
+ args: [],
31273
+ extensions: [".rs"]
31274
+ }
31275
+ ];
31276
+ function languageIdForFile(file2) {
31277
+ const ext = path17.extname(file2).toLowerCase();
31278
+ const map2 = {
31279
+ ".ts": "typescript",
31280
+ ".tsx": "typescriptreact",
31281
+ ".js": "javascript",
31282
+ ".jsx": "javascriptreact",
31283
+ ".mjs": "javascript",
31284
+ ".cjs": "javascript",
31285
+ ".py": "python",
31286
+ ".go": "go",
31287
+ ".rs": "rust"
31288
+ };
31289
+ return map2[ext] ?? "plaintext";
31290
+ }
31291
+ function serverForFile(file2, servers = LSP_SERVERS) {
31292
+ const ext = path17.extname(file2).toLowerCase();
31293
+ return servers.find((s) => s.extensions.includes(ext)) ?? null;
31294
+ }
31295
+ function resolveServerCommand(file2, cwd, servers = LSP_SERVERS) {
31296
+ const spec = serverForFile(file2, servers);
31297
+ if (!spec) return null;
31298
+ const command = resolveBin(spec.bin, cwd);
31299
+ return {
31300
+ language: spec.language,
31301
+ command,
31302
+ args: spec.args,
31303
+ resolved: command !== spec.bin
31304
+ };
31305
+ }
31306
+
31307
+ // src/cli/lsp/manager.ts
31308
+ function processTransport(child) {
31309
+ return {
31310
+ send: (data) => {
31311
+ if (child.stdin.writable) child.stdin.write(data);
31312
+ },
31313
+ onData: (cb) => child.stdout.on("data", (b) => cb(b.toString("utf8"))),
31314
+ onClose: (cb) => child.on("exit", cb),
31315
+ dispose: () => {
31316
+ try {
31317
+ child.kill();
31318
+ } catch {
31319
+ }
31320
+ }
31321
+ };
31322
+ }
31323
+ var SYMBOL_KINDS = {
31324
+ 1: "File",
31325
+ 2: "Module",
31326
+ 3: "Namespace",
31327
+ 4: "Package",
31328
+ 5: "Class",
31329
+ 6: "Method",
31330
+ 7: "Property",
31331
+ 8: "Field",
31332
+ 9: "Constructor",
31333
+ 10: "Enum",
31334
+ 11: "Interface",
31335
+ 12: "Function",
31336
+ 13: "Variable",
31337
+ 14: "Constant",
31338
+ 15: "String",
31339
+ 16: "Number",
31340
+ 17: "Boolean",
31341
+ 18: "Array",
31342
+ 19: "Object",
31343
+ 20: "Key",
31344
+ 21: "Null",
31345
+ 22: "EnumMember",
31346
+ 23: "Struct",
31347
+ 24: "Event",
31348
+ 25: "Operator",
31349
+ 26: "TypeParameter"
31350
+ };
31351
+ var LspManager = class {
31352
+ cwd;
31353
+ spawnImpl;
31354
+ timeoutMs;
31355
+ servers = /* @__PURE__ */ new Map();
31356
+ // language → entry (null = unavailable)
31357
+ constructor(options = {}) {
31358
+ this.cwd = options.cwd ?? process.cwd();
31359
+ this.spawnImpl = options.spawnImpl ?? spawn3;
31360
+ this.timeoutMs = options.timeoutMs ?? 15e3;
31361
+ }
31362
+ /** Lazily start (or reuse) the server for a file's language. Null if none. */
31363
+ getServer(file2) {
31364
+ const cmd = resolveServerCommand(file2, this.cwd);
31365
+ if (!cmd) return null;
31366
+ const cached2 = this.servers.get(cmd.language);
31367
+ if (cached2 !== void 0) return cached2;
31368
+ let entry = null;
31369
+ try {
31370
+ const child = this.spawnImpl(cmd.command, cmd.args, {
31371
+ cwd: this.cwd
31372
+ });
31373
+ const client = new LspClient(processTransport(child), { timeoutMs: this.timeoutMs });
31374
+ const initialized = client.request("initialize", {
31375
+ processId: process.pid,
31376
+ rootUri: pathToUri(this.cwd),
31377
+ capabilities: {},
31378
+ workspaceFolders: [{ uri: pathToUri(this.cwd), name: "root" }]
31379
+ }).then(() => {
31380
+ client.notify("initialized", {});
31381
+ });
31382
+ entry = { client, initialized, opened: /* @__PURE__ */ new Map(), dispose: () => client.dispose() };
31383
+ } catch {
31384
+ entry = null;
31385
+ }
31386
+ this.servers.set(cmd.language, entry);
31387
+ return entry;
31388
+ }
31389
+ /** Ensure a document is open (or synced) on the server. */
31390
+ async openDoc(entry, file2) {
31391
+ await entry.initialized;
31392
+ const uri = pathToUri(file2);
31393
+ let text;
31394
+ try {
31395
+ text = readFileSync5(file2, "utf8");
31396
+ } catch {
31397
+ text = "";
31398
+ }
31399
+ const prev2 = entry.opened.get(uri);
31400
+ if (prev2 === void 0) {
31401
+ entry.client.notify("textDocument/didOpen", {
31402
+ textDocument: { uri, languageId: languageIdForFile(file2), version: 1, text }
31403
+ });
31404
+ entry.opened.set(uri, 1);
31405
+ } else {
31406
+ const version2 = prev2 + 1;
31407
+ entry.client.notify("textDocument/didChange", {
31408
+ textDocument: { uri, version: version2 },
31409
+ contentChanges: [{ text }]
31410
+ });
31411
+ entry.opened.set(uri, version2);
31412
+ }
31413
+ return uri;
31414
+ }
31415
+ async withDoc(file2, fn, fallback) {
31416
+ const entry = this.getServer(file2);
31417
+ if (!entry) return fallback;
31418
+ try {
31419
+ const uri = await this.openDoc(entry, file2);
31420
+ return await fn(entry, uri);
31421
+ } catch {
31422
+ return fallback;
31423
+ }
31424
+ }
31425
+ async definition(file2, line, character) {
31426
+ return this.withDoc(
31427
+ file2,
31428
+ async (entry, uri) => {
31429
+ const res = await entry.client.request("textDocument/definition", {
31430
+ textDocument: { uri },
31431
+ position: { line, character }
31432
+ });
31433
+ return normalizeLocations(res);
31434
+ },
31435
+ []
31436
+ );
31437
+ }
31438
+ async references(file2, line, character) {
31439
+ return this.withDoc(
31440
+ file2,
31441
+ async (entry, uri) => {
31442
+ const res = await entry.client.request("textDocument/references", {
31443
+ textDocument: { uri },
31444
+ position: { line, character },
31445
+ context: { includeDeclaration: true }
31446
+ });
31447
+ return normalizeLocations(res);
31448
+ },
31449
+ []
31450
+ );
31451
+ }
31452
+ async hover(file2, line, character) {
31453
+ return this.withDoc(
31454
+ file2,
31455
+ async (entry, uri) => {
31456
+ const res = await entry.client.request("textDocument/hover", {
31457
+ textDocument: { uri },
31458
+ position: { line, character }
31459
+ });
31460
+ return extractHoverText(res);
31461
+ },
31462
+ null
31463
+ );
31464
+ }
31465
+ async documentSymbols(file2) {
31466
+ return this.withDoc(
31467
+ file2,
31468
+ async (entry, uri) => {
31469
+ const res = await entry.client.request("textDocument/documentSymbol", {
31470
+ textDocument: { uri }
31471
+ });
31472
+ return normalizeSymbols(res);
31473
+ },
31474
+ []
31475
+ );
31476
+ }
31477
+ async rename(file2, line, character, newName) {
31478
+ return this.withDoc(
31479
+ file2,
31480
+ async (entry, uri) => {
31481
+ const res = await entry.client.request("textDocument/rename", {
31482
+ textDocument: { uri },
31483
+ position: { line, character },
31484
+ newName
31485
+ });
31486
+ return normalizeRename(res);
31487
+ },
31488
+ null
31489
+ );
31490
+ }
31491
+ dispose() {
31492
+ for (const entry of this.servers.values()) entry?.dispose();
31493
+ this.servers.clear();
31494
+ }
31495
+ };
31496
+ var shared = null;
31497
+ function getSharedLspManager(cwd = process.cwd()) {
31498
+ if (shared && shared.cwd === cwd) return shared.manager;
31499
+ shared?.manager.dispose();
31500
+ const manager = new LspManager({ cwd });
31501
+ shared = { cwd, manager };
31502
+ return manager;
31503
+ }
31504
+ if (typeof process !== "undefined" && typeof process.once === "function") {
31505
+ process.once("exit", () => {
31506
+ try {
31507
+ shared?.manager.dispose();
31508
+ } catch {
31509
+ }
31510
+ });
31511
+ }
31512
+ function normalizeLocations(res) {
31513
+ if (!res) return [];
31514
+ const arr = Array.isArray(res) ? res : [res];
31515
+ const out = [];
31516
+ for (const item of arr) {
31517
+ if (!item || typeof item !== "object") continue;
31518
+ const loc = item;
31519
+ const uri = loc.uri ?? loc.targetUri;
31520
+ const range = loc.range ?? loc.targetRange;
31521
+ if (typeof uri === "string" && range) out.push({ uri, range });
31522
+ }
31523
+ return out;
31524
+ }
31525
+ function extractHoverText(res) {
31526
+ if (!res || !res.contents) return null;
31527
+ const c = res.contents;
31528
+ if (typeof c === "string") return c.trim() || null;
31529
+ if (Array.isArray(c)) {
31530
+ return c.map((x) => typeof x === "string" ? x : x?.value ?? "").filter(Boolean).join("\n").trim() || null;
31531
+ }
31532
+ if (typeof c === "object" && "value" in c) {
31533
+ return (c.value ?? "").trim() || null;
31534
+ }
31535
+ return null;
31536
+ }
31537
+ function normalizeSymbols(res) {
31538
+ if (!Array.isArray(res)) return [];
31539
+ const out = [];
31540
+ const visit = (nodes) => {
31541
+ for (const n of nodes) {
31542
+ if (!n || typeof n !== "object") continue;
31543
+ const s = n;
31544
+ const line0 = s.range?.start?.line ?? s.location?.range?.start?.line;
31545
+ if (typeof s.name === "string" && typeof line0 === "number") {
31546
+ out.push({ name: s.name, kind: SYMBOL_KINDS[s.kind ?? 0] ?? "Symbol", line: line0 + 1 });
31547
+ }
31548
+ if (Array.isArray(s.children)) visit(s.children);
31549
+ }
31550
+ };
31551
+ visit(res);
31552
+ return out;
31553
+ }
31554
+ function normalizeRename(res) {
31555
+ if (!res) return null;
31556
+ const files = [];
31557
+ let total = 0;
31558
+ if (res.changes && typeof res.changes === "object") {
31559
+ for (const [uri, edits] of Object.entries(res.changes)) {
31560
+ const count = Array.isArray(edits) ? edits.length : 0;
31561
+ files.push({ file: uriToPath(uri), count });
31562
+ total += count;
31563
+ }
31564
+ }
31565
+ if (Array.isArray(res.documentChanges)) {
31566
+ for (const dc of res.documentChanges) {
31567
+ const d = dc;
31568
+ if (d?.textDocument?.uri) {
31569
+ const count = Array.isArray(d.edits) ? d.edits.length : 0;
31570
+ files.push({ file: uriToPath(d.textDocument.uri), count });
31571
+ total += count;
31572
+ }
31573
+ }
31574
+ }
31575
+ if (files.length === 0) return null;
31576
+ return { files, totalEdits: total };
31577
+ }
31578
+
31579
+ // src/cli/ast/tools.ts
31580
+ init_zod();
31581
+ init_toolTypes();
31582
+
31583
+ // src/cli/ast/engine.ts
31584
+ import { readFile } from "node:fs/promises";
31585
+ import path18 from "node:path";
31586
+ var TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
31587
+ function isAstSupported(file2) {
31588
+ return TS_EXTENSIONS.has(path18.extname(file2).toLowerCase());
31589
+ }
31590
+ var tsPromise;
31591
+ function loadTs() {
31592
+ if (!tsPromise) {
31593
+ tsPromise = import("typescript").then((m) => m.default ?? m).catch(() => null);
31594
+ }
31595
+ return tsPromise;
31596
+ }
31597
+ async function parseFileSymbols(file2) {
31598
+ if (!isAstSupported(file2)) return [];
31599
+ const ts = await loadTs();
31600
+ if (!ts) return [];
31601
+ let text;
31602
+ try {
31603
+ text = await readFile(file2, "utf8");
31604
+ } catch {
31605
+ return [];
31606
+ }
31607
+ let source;
31608
+ try {
31609
+ source = ts.createSourceFile(path18.basename(file2), text, ts.ScriptTarget.Latest, true);
31610
+ } catch {
31611
+ return [];
31612
+ }
31613
+ const out = [];
31614
+ const lineOf = (pos) => source.getLineAndCharacterOfPosition(pos).line + 1;
31615
+ const hasExport = (node) => {
31616
+ const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0;
31617
+ return !!mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
31618
+ };
31619
+ const record2 = (name, kind, node, exported) => {
31620
+ out.push({
31621
+ name,
31622
+ kind,
31623
+ line: lineOf(node.getStart(source)),
31624
+ endLine: lineOf(node.getEnd()),
31625
+ exported,
31626
+ text: node.getText(source)
31627
+ });
31628
+ };
31629
+ const visit = (node) => {
31630
+ if (ts.isFunctionDeclaration(node) && node.name) {
31631
+ record2(node.name.text, "function", node, hasExport(node));
31632
+ } else if (ts.isClassDeclaration(node) && node.name) {
31633
+ record2(node.name.text, "class", node, hasExport(node));
31634
+ for (const member of node.members) {
31635
+ if (ts.isMethodDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
31636
+ record2(member.name.text, "method", member, false);
31637
+ }
31638
+ }
31639
+ } else if (ts.isInterfaceDeclaration(node)) {
31640
+ record2(node.name.text, "interface", node, hasExport(node));
31641
+ } else if (ts.isTypeAliasDeclaration(node)) {
31642
+ record2(node.name.text, "type", node, hasExport(node));
31643
+ } else if (ts.isEnumDeclaration(node)) {
31644
+ record2(node.name.text, "enum", node, hasExport(node));
31645
+ } else if (ts.isVariableStatement(node)) {
31646
+ const exported = hasExport(node);
31647
+ for (const decl of node.declarationList.declarations) {
31648
+ if (!ts.isIdentifier(decl.name)) continue;
31649
+ const init = decl.initializer;
31650
+ const isFn = !!init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init));
31651
+ record2(decl.name.text, isFn ? "function" : "variable", node, exported);
31652
+ }
31653
+ }
31654
+ ts.forEachChild(node, visit);
31655
+ };
31656
+ visit(source);
31657
+ return out;
31658
+ }
31659
+ async function astOutline(file2) {
31660
+ const symbols = await parseFileSymbols(file2);
31661
+ return symbols.map(({ text: _text, ...rest }) => rest);
31662
+ }
31663
+ async function findSymbol(file2, name) {
31664
+ const symbols = await parseFileSymbols(file2);
31665
+ return symbols.find((s) => s.name === name) ?? null;
31666
+ }
31667
+
31668
+ // src/cli/ast/tools.ts
31669
+ var MAX_TEXT_CHARS = 4e3;
31670
+ function createAstTools() {
31671
+ const outline = {
31672
+ name: "ast_outline",
31673
+ description: "Structural outline of a TS/JS file: every declaration (function, class, method, interface, type, enum, variable) with its line range and whether it's exported. Faster and more precise than reading the whole file to find where things are. TS/JS only.",
31674
+ permissions: ["read"],
31675
+ inputSchema: external_exports.object({
31676
+ path: external_exports.string().min(1).describe("Path to the TS/JS file to outline.")
31677
+ }),
31678
+ execute: async (args) => {
31679
+ const { path: file2 } = args;
31680
+ const symbols = await astOutline(file2);
31681
+ if (symbols.length === 0) {
31682
+ return typedOk({ symbols: [], note: "no declarations found (or not a TS/JS file / TypeScript unavailable)" });
31683
+ }
31684
+ return typedOk({
31685
+ count: symbols.length,
31686
+ symbols: symbols.map(
31687
+ (s) => `${s.exported ? "export " : ""}${s.kind} ${s.name} (lines ${s.line}-${s.endLine})`
31688
+ )
31689
+ });
31690
+ }
31691
+ };
31692
+ const findSymbolTool = {
31693
+ name: "find_symbol",
31694
+ description: "Locate a named declaration in a TS/JS file and return its EXACT source text and line range. Use this to grab a function/class/method verbatim so you can edit_file it reliably (node-accurate) instead of guessing the surrounding text. TS/JS only.",
31695
+ permissions: ["read"],
31696
+ inputSchema: external_exports.object({
31697
+ path: external_exports.string().min(1).describe("Path to the TS/JS file."),
31698
+ name: external_exports.string().min(1).describe("The declaration name to find (function/class/method/etc).")
31699
+ }),
31700
+ execute: async (args) => {
31701
+ const { path: file2, name } = args;
31702
+ const sym = await findSymbol(file2, name);
31703
+ if (!sym) {
31704
+ return typedOk({ found: false, note: `no declaration named "${name}" found in ${file2}` });
31705
+ }
31706
+ const truncated = sym.text.length > MAX_TEXT_CHARS;
31707
+ return typedOk({
31708
+ found: true,
31709
+ kind: sym.kind,
31710
+ exported: sym.exported,
31711
+ line: sym.line,
31712
+ endLine: sym.endLine,
31713
+ text: truncated ? `${sym.text.slice(0, MAX_TEXT_CHARS)}
31714
+ \u2026 (truncated, ${sym.text.length} chars total)` : sym.text
31715
+ });
31716
+ }
31717
+ };
31718
+ return [outline, findSymbolTool];
31719
+ }
31720
+
31721
+ // src/cli/semantic/tools.ts
31722
+ init_zod();
31723
+ init_toolTypes();
31724
+ import path20 from "node:path";
31725
+
31726
+ // src/cli/semantic/index.ts
31727
+ import { promises as fs12, existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
31728
+ import { homedir as homedir3 } from "node:os";
31729
+ import path19 from "node:path";
31730
+ import { createHash } from "node:crypto";
31731
+
31732
+ // src/cli/semantic/store.ts
31733
+ function chunkFile(file2, text, opts = {}) {
31734
+ const maxLines = Math.max(1, opts.maxLines ?? 40);
31735
+ const overlap = Math.max(0, Math.min(opts.overlap ?? 8, maxLines - 1));
31736
+ const lines = text.split("\n");
31737
+ const step = maxLines - overlap;
31738
+ const chunks = [];
31739
+ for (let start = 0; start < lines.length; start += step) {
31740
+ const end = Math.min(start + maxLines, lines.length);
31741
+ const slice = lines.slice(start, end);
31742
+ if (slice.join("").trim().length > 0) {
31743
+ chunks.push({
31744
+ file: file2,
31745
+ startLine: start + 1,
31746
+ endLine: end,
31747
+ text: slice.join("\n")
31748
+ });
31749
+ }
31750
+ if (end >= lines.length) break;
31751
+ }
31752
+ return chunks;
31753
+ }
31754
+ function cosineSimilarity(a, b) {
31755
+ if (a.length === 0 || a.length !== b.length) return 0;
31756
+ let dot = 0;
31757
+ let na = 0;
31758
+ let nb = 0;
31759
+ for (let i = 0; i < a.length; i += 1) {
31760
+ dot += a[i] * b[i];
31761
+ na += a[i] * a[i];
31762
+ nb += b[i] * b[i];
31763
+ }
31764
+ if (na === 0 || nb === 0) return 0;
31765
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
31766
+ }
31767
+ function searchIndex(data, queryEmbedding, k = 8) {
31768
+ const scored = [];
31769
+ for (const chunk of data.chunks) {
31770
+ const score = cosineSimilarity(queryEmbedding, chunk.embedding);
31771
+ scored.push({
31772
+ file: chunk.file,
31773
+ startLine: chunk.startLine,
31774
+ endLine: chunk.endLine,
31775
+ text: chunk.text,
31776
+ score
31777
+ });
31778
+ }
31779
+ scored.sort((a, b) => b.score - a.score);
31780
+ return scored.slice(0, Math.max(0, k));
31781
+ }
31782
+
31783
+ // src/cli/semantic/index.ts
31784
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
31785
+ ".ts",
31786
+ ".tsx",
31787
+ ".js",
31788
+ ".jsx",
31789
+ ".mjs",
31790
+ ".cjs",
31791
+ ".py",
31792
+ ".go",
31793
+ ".rs",
31794
+ ".java",
31795
+ ".rb",
31796
+ ".php",
31797
+ ".c",
31798
+ ".h",
31799
+ ".cc",
31800
+ ".cpp",
31801
+ ".hpp",
31802
+ ".cs",
31803
+ ".swift",
31804
+ ".kt",
31805
+ ".scala",
31806
+ ".sh",
31807
+ ".md"
31808
+ ]);
31809
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([
31810
+ "node_modules",
31811
+ ".git",
31812
+ "dist",
31813
+ "build",
31814
+ "out",
31815
+ "coverage",
31816
+ ".next",
31817
+ ".turbo",
31818
+ ".cache",
31819
+ "vendor",
31820
+ "__pycache__",
31821
+ ".venv",
31822
+ "venv",
31823
+ ".tmp"
31824
+ ]);
31825
+ function getIndexPath(root) {
31826
+ const hash3 = createHash("sha1").update(path19.resolve(root)).digest("hex").slice(0, 16);
31827
+ return process.env.ZELARI_SEMANTIC_FILE ?? path19.join(homedir3(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
31828
+ }
31829
+ async function collectSourceFiles(root, maxFiles = 1500) {
31830
+ const out = [];
31831
+ const walk2 = async (dir) => {
31832
+ if (out.length >= maxFiles) return;
31833
+ let entries;
31834
+ try {
31835
+ entries = await fs12.readdir(dir, { withFileTypes: true });
31836
+ } catch {
31837
+ return;
31838
+ }
31839
+ for (const entry of entries) {
31840
+ if (out.length >= maxFiles) return;
31841
+ if (entry.name.startsWith(".") && entry.name !== ".") {
31842
+ if (entry.isDirectory() && IGNORE_DIRS.has(entry.name)) continue;
31843
+ if (entry.isDirectory()) continue;
31844
+ }
31845
+ const full = path19.join(dir, entry.name);
31846
+ if (entry.isDirectory()) {
31847
+ if (IGNORE_DIRS.has(entry.name)) continue;
31848
+ await walk2(full);
31849
+ } else if (SOURCE_EXTENSIONS.has(path19.extname(entry.name).toLowerCase())) {
31850
+ out.push(full);
31851
+ }
31852
+ }
31853
+ };
31854
+ await walk2(root);
31855
+ return out;
31856
+ }
31857
+ async function buildIndex(files, embed, options) {
31858
+ const batchSize = options.batchSize ?? 64;
31859
+ const maxChunkChars = options.maxChunkChars ?? 8e3;
31860
+ const chunks = [];
31861
+ let filesIndexed = 0;
31862
+ for (const file2 of files) {
31863
+ let text;
31864
+ try {
31865
+ text = await fs12.readFile(file2, "utf8");
31866
+ } catch {
31867
+ continue;
31868
+ }
31869
+ const fileChunks = chunkFile(file2, text, options).filter((c) => c.text.length <= maxChunkChars);
31870
+ if (fileChunks.length > 0) filesIndexed += 1;
31871
+ chunks.push(...fileChunks);
31872
+ }
31873
+ if (chunks.length === 0) {
31874
+ return { error: "no indexable source found", filesIndexed: 0, chunksIndexed: 0 };
31875
+ }
31876
+ const indexed = [];
31877
+ for (let i = 0; i < chunks.length; i += batchSize) {
31878
+ const batch = chunks.slice(i, i + batchSize);
31879
+ const res = await embed(batch.map((c) => c.text));
31880
+ if ("error" in res) {
31881
+ return { error: res.error, filesIndexed, chunksIndexed: 0 };
31882
+ }
31883
+ if (res.length !== batch.length) {
31884
+ return { error: "embedding count mismatch", filesIndexed, chunksIndexed: 0 };
31885
+ }
31886
+ batch.forEach((c, j) => indexed.push({ ...c, embedding: res[j] }));
31887
+ }
31888
+ const data = {
31889
+ model: options.model,
31890
+ dim: indexed[0]?.embedding.length ?? 0,
31891
+ chunks: indexed,
31892
+ builtAt: Date.now()
31893
+ };
31894
+ return { data, filesIndexed, chunksIndexed: indexed.length };
31895
+ }
31896
+ async function saveIndex(root, data) {
31897
+ const file2 = getIndexPath(root);
31898
+ await fs12.mkdir(path19.dirname(file2), { recursive: true });
31899
+ const tmp = `${file2}.tmp-${process.pid}`;
31900
+ await fs12.writeFile(tmp, JSON.stringify(data), "utf8");
31901
+ await fs12.rename(tmp, file2);
31902
+ }
31903
+ function loadIndex(root) {
31904
+ const file2 = getIndexPath(root);
31905
+ if (!existsSync8(file2)) return null;
31906
+ try {
31907
+ const parsed = JSON.parse(readFileSync6(file2, "utf8"));
31908
+ if (parsed && Array.isArray(parsed.chunks)) return parsed;
31909
+ } catch {
31910
+ }
31911
+ return null;
31912
+ }
31913
+ async function semanticSearch(root, query, embed, k = 8) {
31914
+ const data = loadIndex(root);
31915
+ if (!data) return { error: "no semantic index \u2014 run /index first" };
31916
+ const res = await embed([query]);
31917
+ if ("error" in res) return { error: res.error };
31918
+ const vector = res[0];
31919
+ if (!vector) return { error: "query embedding failed" };
31920
+ return { hits: searchIndex(data, vector, k) };
31921
+ }
31922
+
31923
+ // src/cli/semantic/provider.ts
31924
+ init_openai_compatible();
31925
+
31926
+ // src/cli/semantic/embeddings.ts
31927
+ function parseEmbeddingsResponse(json2, expected) {
31928
+ if (!json2 || typeof json2 !== "object") return null;
31929
+ const data = json2.data;
31930
+ if (!Array.isArray(data)) return null;
31931
+ const rows = [];
31932
+ for (const item of data) {
31933
+ if (!item || typeof item !== "object") continue;
31934
+ const it = item;
31935
+ if (!Array.isArray(it.embedding)) continue;
31936
+ const embedding = it.embedding.filter((n) => typeof n === "number");
31937
+ if (embedding.length === 0) continue;
31938
+ rows.push({ index: typeof it.index === "number" ? it.index : rows.length, embedding });
31939
+ }
31940
+ if (rows.length === 0) return null;
31941
+ rows.sort((a, b) => a.index - b.index);
31942
+ const out = rows.map((r) => r.embedding);
31943
+ return out.length === expected ? out : null;
31944
+ }
31945
+ async function embedTexts(texts, config2, fetchImpl = fetch) {
31946
+ if (texts.length === 0) return { embeddings: [] };
31947
+ const url2 = `${config2.baseUrl.replace(/\/$/, "")}/embeddings`;
31948
+ let response;
31949
+ try {
31950
+ response = await fetchImpl(url2, {
31951
+ method: "POST",
31952
+ headers: {
31953
+ "Content-Type": "application/json",
31954
+ Authorization: `Bearer ${config2.apiKey}`
31955
+ },
31956
+ body: JSON.stringify({ model: config2.model, input: texts })
31957
+ });
31958
+ } catch (err) {
31959
+ return { error: `network error contacting ${url2}: ${err instanceof Error ? err.message : String(err)}` };
31960
+ }
31961
+ if (!response.ok) {
31962
+ const body = await response.text().catch(() => "");
31963
+ return { error: `HTTP ${response.status} from ${url2}: ${body.slice(0, 160)}` };
31964
+ }
31965
+ let json2;
31966
+ try {
31967
+ json2 = await response.json();
31968
+ } catch (err) {
31969
+ return { error: `invalid JSON from ${url2}: ${err instanceof Error ? err.message : String(err)}` };
31970
+ }
31971
+ const embeddings = parseEmbeddingsResponse(json2, texts.length);
31972
+ if (!embeddings) return { error: `unexpected embeddings response shape from ${url2}` };
31973
+ return { embeddings };
31974
+ }
31975
+
31976
+ // src/cli/semantic/provider.ts
31977
+ var DEFAULT_EMBED_MODEL = "text-embedding-3-small";
31978
+ function embedModel() {
31979
+ return process.env.ZELARI_EMBED_MODEL ?? DEFAULT_EMBED_MODEL;
31980
+ }
31981
+ async function buildProviderEmbedFn() {
31982
+ const cfg = await providerFromEnv();
31983
+ if (!cfg) return null;
31984
+ const embedCfg = { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, model: embedModel() };
31985
+ return async (texts) => {
31986
+ const res = await embedTexts(texts, embedCfg);
31987
+ return "error" in res ? { error: res.error } : res.embeddings;
31988
+ };
31989
+ }
31990
+
31991
+ // src/cli/semantic/tools.ts
31992
+ function createSemanticTool(deps) {
31993
+ const buildEmbedFn = deps.buildEmbedFn ?? buildProviderEmbedFn;
31994
+ return {
31995
+ name: "semantic_search",
31996
+ description: 'Concept-level search over the indexed codebase: describe what you are looking for in plain language ("where is rate-limit backoff handled?") and get the most relevant code chunks (file:line + snippet), even when they share no exact keyword with your query. Requires an index \u2014 if none exists, ask the user to run /index. Complements grep_content (exact matches).',
31997
+ permissions: ["read"],
31998
+ inputSchema: external_exports.object({
31999
+ query: external_exports.string().min(1).describe("Natural-language description of the code you want."),
32000
+ k: external_exports.number().int().positive().max(25).optional().describe("Max results (default 8).")
32001
+ }),
32002
+ execute: async (args) => {
32003
+ const { query, k } = args;
32004
+ if (!loadIndex(deps.root)) {
32005
+ return typedOk({ results: [], note: "no semantic index yet \u2014 run /index to build one, then retry" });
32006
+ }
32007
+ const embed = await buildEmbedFn();
32008
+ if (!embed) {
32009
+ return typedOk({ results: [], note: "no provider/API key configured for embeddings" });
32010
+ }
32011
+ const res = await semanticSearch(deps.root, query, embed, k ?? 8);
32012
+ if ("error" in res) {
32013
+ return typedOk({ results: [], note: `semantic search unavailable: ${res.error}` });
32014
+ }
32015
+ return typedOk({
32016
+ count: res.hits.length,
32017
+ results: res.hits.map((h) => ({
32018
+ location: `${path20.relative(deps.root, h.file) || h.file}:${h.startLine}-${h.endLine}`,
32019
+ score: Number(h.score.toFixed(3)),
32020
+ preview: h.text.length > 400 ? `${h.text.slice(0, 400)}\u2026` : h.text
32021
+ }))
32022
+ });
32023
+ }
32024
+ };
32025
+ }
32026
+
32027
+ // src/cli/browser/tools.ts
32028
+ init_zod();
32029
+ init_toolTypes();
32030
+ import path21 from "node:path";
32031
+ import os7 from "node:os";
32032
+
32033
+ // src/cli/browser/driver.ts
32034
+ var defaultPlaywrightLoader = async () => {
32035
+ try {
32036
+ const pkg = "playwright";
32037
+ const mod = await import(pkg);
32038
+ if (mod && mod.chromium && typeof mod.chromium.launch === "function") return mod;
32039
+ return null;
32040
+ } catch {
32041
+ return null;
32042
+ }
32043
+ };
32044
+ async function runBrowserCheck(options, loader = defaultPlaywrightLoader) {
32045
+ const consoleErrors = [];
32046
+ const pageErrors = [];
32047
+ const failedRequests = [];
32048
+ const base = { ok: false, consoleErrors, pageErrors, failedRequests };
32049
+ const pw = await loader();
32050
+ if (!pw) {
32051
+ return {
32052
+ ...base,
32053
+ error: "browser automation unavailable \u2014 install Playwright (`npm i -D playwright && npx playwright install chromium`) to enable browser_check"
32054
+ };
32055
+ }
32056
+ const timeout = options.timeoutMs ?? 15e3;
32057
+ let browser;
32058
+ try {
32059
+ browser = await pw.chromium.launch({ headless: true });
32060
+ const page = await browser.newPage();
32061
+ page.on("console", (msg) => {
32062
+ if (msg.type() === "error") consoleErrors.push(msg.text());
32063
+ });
32064
+ page.on("pageerror", (err) => pageErrors.push(err.message));
32065
+ page.on("requestfailed", (req) => {
32066
+ const f = req.failure();
32067
+ failedRequests.push(`${req.url()}${f ? ` (${f.errorText})` : ""}`);
32068
+ });
32069
+ await page.goto(options.url, { waitUntil: "load", timeout });
32070
+ for (const action of options.actions ?? []) {
32071
+ switch (action.type) {
32072
+ case "click":
32073
+ await page.click(action.selector, { timeout });
32074
+ break;
32075
+ case "fill":
32076
+ await page.fill(action.selector, action.value, { timeout });
32077
+ break;
32078
+ case "goto":
32079
+ await page.goto(action.url, { waitUntil: "load", timeout });
32080
+ break;
32081
+ case "wait":
32082
+ await page.waitForTimeout(action.ms);
32083
+ break;
32084
+ default:
32085
+ break;
32086
+ }
32087
+ }
32088
+ let selectorFound;
32089
+ if (options.waitForSelector) {
32090
+ try {
32091
+ await page.waitForSelector(options.waitForSelector, { timeout });
32092
+ selectorFound = true;
32093
+ } catch {
32094
+ selectorFound = false;
32095
+ }
32096
+ }
32097
+ let screenshotPath;
32098
+ if (options.screenshotPath) {
32099
+ try {
32100
+ await page.screenshot({ path: options.screenshotPath });
32101
+ screenshotPath = options.screenshotPath;
32102
+ } catch {
32103
+ }
32104
+ }
32105
+ const title = await page.title().catch(() => void 0);
32106
+ return {
32107
+ ok: true,
32108
+ consoleErrors,
32109
+ pageErrors,
32110
+ failedRequests,
32111
+ url: page.url(),
32112
+ ...title !== void 0 ? { title } : {},
32113
+ ...selectorFound !== void 0 ? { selectorFound } : {},
32114
+ ...screenshotPath ? { screenshotPath } : {}
32115
+ };
32116
+ } catch (err) {
32117
+ return { ...base, error: err instanceof Error ? err.message : String(err) };
32118
+ } finally {
32119
+ try {
32120
+ await browser?.close();
32121
+ } catch {
32122
+ }
32123
+ }
32124
+ }
32125
+
32126
+ // src/cli/browser/tools.ts
32127
+ var ActionSchema = external_exports.discriminatedUnion("type", [
32128
+ external_exports.object({ type: external_exports.literal("click"), selector: external_exports.string().min(1) }),
32129
+ external_exports.object({ type: external_exports.literal("fill"), selector: external_exports.string().min(1), value: external_exports.string() }),
32130
+ external_exports.object({ type: external_exports.literal("wait"), ms: external_exports.number().int().positive().max(3e4) }),
32131
+ external_exports.object({ type: external_exports.literal("goto"), url: external_exports.string().min(1) })
32132
+ ]);
32133
+ function createBrowserTool(deps = {}) {
32134
+ return {
32135
+ name: "browser_check",
32136
+ description: 'Open a URL in a headless browser to VERIFY a web change: optionally run click/fill/goto/wait actions, then report console errors, uncaught page exceptions, failed network requests, the final title/URL, whether an expected selector appeared, and a screenshot path. Use it to confirm UI edits actually render and run \u2014 stronger than "tests pass" for front-end. Requires Playwright (optional dependency).',
32137
+ permissions: ["network"],
32138
+ // A browser launch + navigation can take a while.
32139
+ timeoutMs: 6e4,
32140
+ inputSchema: external_exports.object({
32141
+ url: external_exports.string().min(1).describe("URL to open (e.g. http://localhost:3000)."),
32142
+ actions: external_exports.array(ActionSchema).optional().describe("Optional sequence of interactions before checking."),
32143
+ waitForSelector: external_exports.string().optional().describe("Assert this CSS selector is present after actions."),
32144
+ screenshot: external_exports.boolean().optional().describe("Save a screenshot (default true).")
32145
+ }),
32146
+ execute: async (args) => {
32147
+ const a = args;
32148
+ const dir = deps.screenshotDir ?? os7.tmpdir();
32149
+ const screenshotPath = a.screenshot === false ? void 0 : path21.join(dir, `zelari-browser-${Date.now()}.png`);
32150
+ const result = await runBrowserCheck(
32151
+ {
32152
+ url: a.url,
32153
+ ...a.actions ? { actions: a.actions } : {},
32154
+ ...a.waitForSelector ? { waitForSelector: a.waitForSelector } : {},
32155
+ ...screenshotPath ? { screenshotPath } : {}
32156
+ },
32157
+ deps.loader
32158
+ );
32159
+ if (!result.ok) {
32160
+ return typedOk({ ok: false, note: result.error ?? "browser check failed" });
32161
+ }
32162
+ const clean = result.consoleErrors.length === 0 && result.pageErrors.length === 0 && result.failedRequests.length === 0 && result.selectorFound !== false;
32163
+ return typedOk({
32164
+ ok: true,
32165
+ clean,
32166
+ title: result.title,
32167
+ url: result.url,
32168
+ consoleErrors: result.consoleErrors,
32169
+ pageErrors: result.pageErrors,
32170
+ failedRequests: result.failedRequests,
32171
+ ...result.selectorFound !== void 0 ? { selectorFound: result.selectorFound } : {},
32172
+ ...result.screenshotPath ? { screenshotPath: result.screenshotPath } : {}
32173
+ });
32174
+ }
32175
+ };
32176
+ }
32177
+
30744
32178
  // src/cli/toolRegistry.ts
30745
32179
  init_openai_compatible();
30746
32180
  function createBuiltinToolRegistry(options = {}) {
@@ -30790,6 +32224,30 @@ function createBuiltinToolRegistry(options = {}) {
30790
32224
  description: t.description,
30791
32225
  permissions: t.permissions ?? []
30792
32226
  }));
32227
+ if (process.env.ZELARI_AST !== "0") {
32228
+ for (const t of createAstTools()) {
32229
+ registry3.register(t);
32230
+ tools.push({ name: t.name, description: t.description, permissions: t.permissions ?? [] });
32231
+ }
32232
+ }
32233
+ if (process.env.ZELARI_SEMANTIC !== "0") {
32234
+ const semanticTool = createSemanticTool({ root });
32235
+ registry3.register(semanticTool);
32236
+ tools.push({
32237
+ name: semanticTool.name,
32238
+ description: semanticTool.description,
32239
+ permissions: semanticTool.permissions ?? []
32240
+ });
32241
+ }
32242
+ if (!readOnly && process.env.ZELARI_BROWSER !== "0") {
32243
+ const browserTool = createBrowserTool();
32244
+ registry3.register(browserTool);
32245
+ tools.push({
32246
+ name: browserTool.name,
32247
+ description: browserTool.description,
32248
+ permissions: browserTool.permissions ?? []
32249
+ });
32250
+ }
30793
32251
  if (!readOnly && options.enableTask !== false) {
30794
32252
  const taskTool = createTaskTool({
30795
32253
  createSubAgentContext: async () => {
@@ -30822,6 +32280,13 @@ function createBuiltinToolRegistry(options = {}) {
30822
32280
  permissions: taskTool.permissions ?? []
30823
32281
  });
30824
32282
  }
32283
+ if (!readOnly && process.env.ZELARI_LSP !== "0" && options.lspProvider !== null) {
32284
+ const lspTools = options.lspProvider ? createLspTools(options.lspProvider, root) : createLspTools(getSharedLspManager(root), root);
32285
+ for (const t of lspTools) {
32286
+ registry3.register(t);
32287
+ tools.push({ name: t.name, description: t.description, permissions: t.permissions ?? [] });
32288
+ }
32289
+ }
30825
32290
  return { registry: registry3, tools };
30826
32291
  }
30827
32292
  function wrapWithSandbox(original, pathArgs, root, audit, sessionId) {
@@ -32021,6 +33486,8 @@ function handleSlashCommand(text, availableSkills) {
32021
33486
  /undo [--yes] \u2014 revert working-tree changes (destructive! requires --yes)
32022
33487
  /checkpoint [label] \u2014 snapshot the working tree as a restore point
32023
33488
  /rollback [id|latest] \u2014 restore the working tree to a checkpoint (no arg: list)
33489
+ /index [status] \u2014 build the semantic code index for semantic_search
33490
+ /mode [agent|council|zelari] \u2014 switch dispatch mode (same as shift+tab; no arg cycles)
32024
33491
  /help \u2014 show this help
32025
33492
  /exit \u2014 exit the CLI
32026
33493
 
@@ -32358,6 +33825,19 @@ ${formatSkillList(availableSkills)}`
32358
33825
  message: "\u26A0 /undo is DESTRUCTIVE \u2014 it reverts all unstaged modifications and unstages everything.\nUse `/undo --yes` (or `/undo -y`) to confirm."
32359
33826
  };
32360
33827
  }
33828
+ case "mode": {
33829
+ const target = args[0]?.trim().toLowerCase();
33830
+ if (target && ["agent", "council", "zelari"].includes(target)) {
33831
+ return { handled: true, kind: "mode_set", modeTarget: target };
33832
+ }
33833
+ if (target) {
33834
+ return { handled: true, kind: "mode_set", message: `[mode] unknown: ${target}. Use agent, council, or zelari (or /mode to cycle).` };
33835
+ }
33836
+ return { handled: true, kind: "mode_set" };
33837
+ }
33838
+ case "index": {
33839
+ return args[0] === "status" ? { handled: true, kind: "index_status" } : { handled: true, kind: "index_build" };
33840
+ }
32361
33841
  case "checkpoint": {
32362
33842
  const label = args.join(" ").trim();
32363
33843
  return {
@@ -32442,7 +33922,7 @@ ${formatSkillList(availableSkills)}`
32442
33922
  // src/cli/gitOps.ts
32443
33923
  import { execFile as execFile3 } from "node:child_process";
32444
33924
  import { promisify as promisify2 } from "node:util";
32445
- import path21 from "node:path";
33925
+ import path27 from "node:path";
32446
33926
  var execFileAsync2 = promisify2(execFile3);
32447
33927
  async function git2(cwd, args) {
32448
33928
  try {
@@ -32488,7 +33968,7 @@ async function undoWorkingChanges(opts = {}) {
32488
33968
  };
32489
33969
  }
32490
33970
  function defaultProjectRoot() {
32491
- return path21.resolve(__dirname, "..", "..", "..");
33971
+ return path27.resolve(__dirname, "..", "..", "..");
32492
33972
  }
32493
33973
 
32494
33974
  // src/cli/slashHandlers/git.ts
@@ -32583,6 +34063,66 @@ async function handleRollback(ctx, id) {
32583
34063
  }
32584
34064
  }
32585
34065
 
34066
+ // src/cli/slashHandlers/semantic.ts
34067
+ async function handleIndexBuild(ctx) {
34068
+ const embed = await buildProviderEmbedFn();
34069
+ if (!embed) {
34070
+ appendSystem(ctx.setMessages, "[index] no provider/API key configured \u2014 run /login first.");
34071
+ return;
34072
+ }
34073
+ appendSystem(
34074
+ ctx.setMessages,
34075
+ `[index] scanning source files and embedding with "${embedModel()}"\u2026 (this can take a moment)`
34076
+ );
34077
+ const files = await collectSourceFiles(ctx.cwd);
34078
+ if (files.length === 0) {
34079
+ appendSystem(ctx.setMessages, "[index] no source files found to index.");
34080
+ return;
34081
+ }
34082
+ const result = await buildIndex(files, embed, { model: embedModel() });
34083
+ if (result.error || !result.data) {
34084
+ appendSystem(
34085
+ ctx.setMessages,
34086
+ `[index] \u2717 ${result.error ?? "build failed"}` + (result.error?.includes("HTTP") || result.error?.includes("shape") ? "\n (does this provider expose /embeddings? set ZELARI_EMBED_MODEL or switch provider.)" : "")
34087
+ );
34088
+ return;
34089
+ }
34090
+ await saveIndex(ctx.cwd, result.data);
34091
+ appendSystem(
34092
+ ctx.setMessages,
34093
+ `[index] \u2713 indexed ${result.chunksIndexed} chunks from ${result.filesIndexed} files (dim ${result.data.dim}). Use semantic_search now. Saved to ${getIndexPath(ctx.cwd)}`
34094
+ );
34095
+ }
34096
+ function handleIndexStatus(ctx) {
34097
+ const data = loadIndex(ctx.cwd);
34098
+ if (!data) {
34099
+ appendSystem(ctx.setMessages, "[index] no semantic index yet \u2014 run /index to build one.");
34100
+ return;
34101
+ }
34102
+ const ageMin = Math.round((Date.now() - data.builtAt) / 6e4);
34103
+ appendSystem(
34104
+ ctx.setMessages,
34105
+ `[index] ${data.chunks.length} chunks, model "${data.model}", dim ${data.dim}, built ${ageMin}m ago.`
34106
+ );
34107
+ }
34108
+
34109
+ // src/cli/mode.ts
34110
+ var MODES = ["agent", "council", "zelari"];
34111
+ function nextMode(current) {
34112
+ const i = MODES.indexOf(current);
34113
+ return MODES[(i + 1) % MODES.length] ?? "agent";
34114
+ }
34115
+ function describeMode(mode) {
34116
+ switch (mode) {
34117
+ case "council":
34118
+ return "council \u2014 6-member pipeline (Caronte\u2026Lucifero)";
34119
+ case "zelari":
34120
+ return "zelari \u2014 autonomous multi-run mission";
34121
+ default:
34122
+ return "agent \u2014 single LLM turn";
34123
+ }
34124
+ }
34125
+
32586
34126
  // src/cli/compaction.ts
32587
34127
  function compactTranscript(messages, options = {}) {
32588
34128
  const threshold = options.threshold ?? 50;
@@ -32665,12 +34205,18 @@ async function handleUpdatePerform(ctx) {
32665
34205
  const { performUpdate: performUpdate2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
32666
34206
  const res = await performUpdate2();
32667
34207
  if (res.ok) {
34208
+ let prereqBlock = "";
34209
+ try {
34210
+ const { runPrereqChecks: runPrereqChecks2 } = await Promise.resolve().then(() => (init_prereqChecks(), prereqChecks_exports));
34211
+ const { warnings } = runPrereqChecks2({ mode: "preflight" });
34212
+ if (warnings.length > 0) {
34213
+ prereqBlock = "\n\n\u26A0 Prerequisite warnings (the agent may be limited after restart):\n" + warnings.map((w) => ` - ${w.tool}: ${w.message.replace(/\n/g, "\n ")}`).join("\n") + "\n Run `zelari-code --doctor` after restart for the full report.";
34214
+ }
34215
+ } catch {
34216
+ }
32668
34217
  appendSystem(
32669
34218
  ctx.setMessages,
32670
- `[update] \u2705 installed successfully
32671
-
32672
- Please restart zelari-code manually to use the new version.
32673
- (exit with /exit or Ctrl+C, then run \`zelari-code\` again)`
34219
+ "[update] \u2705 installed successfully\n\nPlease restart zelari-code manually to use the new version.\n(exit with /exit or Ctrl+C, then run `zelari-code` again)" + prereqBlock
32674
34220
  );
32675
34221
  } else {
32676
34222
  const output = res.output?.trim() || "(empty)";
@@ -32720,17 +34266,17 @@ ${output}`.toLowerCase();
32720
34266
  }
32721
34267
 
32722
34268
  // src/cli/slashHandlers/promoteMember.ts
32723
- import { promises as fs15 } from "node:fs";
32724
- import path22 from "node:path";
32725
- import os8 from "node:os";
34269
+ import { promises as fs16 } from "node:fs";
34270
+ import path28 from "node:path";
34271
+ import os9 from "node:os";
32726
34272
  async function handlePromoteMember(ctx, memberId) {
32727
34273
  try {
32728
34274
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
32729
34275
  const { skill, markdown } = promoteMember2(memberId);
32730
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path22.join(os8.homedir(), ".tmp", "zelari-code", "skills");
32731
- await fs15.mkdir(skillDir, { recursive: true });
32732
- const filePath = path22.join(skillDir, `${skill.id}.md`);
32733
- await fs15.writeFile(filePath, markdown, "utf8");
34276
+ const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path28.join(os9.homedir(), ".tmp", "zelari-code", "skills");
34277
+ await fs16.mkdir(skillDir, { recursive: true });
34278
+ const filePath = path28.join(skillDir, `${skill.id}.md`);
34279
+ await fs16.writeFile(filePath, markdown, "utf8");
32734
34280
  appendSystem(
32735
34281
  ctx.setMessages,
32736
34282
  `[promote-member] ${skill.name} (${memberId}) \u2192 ${filePath}
@@ -32746,33 +34292,33 @@ async function handlePromoteMember(ctx, memberId) {
32746
34292
  }
32747
34293
 
32748
34294
  // src/cli/branchManager.ts
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";
32751
- import os9 from "node:os";
34295
+ import { promises as fs17, existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
34296
+ import path29 from "node:path";
34297
+ import os10 from "node:os";
32752
34298
  var META_FILENAME = "meta.json";
32753
34299
  var SESSIONS_SUBDIR = "sessions";
32754
34300
  function getBranchesBaseDir() {
32755
- return process.env.ANATHEMA_BRANCHES_DIR ?? path23.join(os9.homedir(), ".tmp", "zelari-code", "branches");
34301
+ return process.env.ANATHEMA_BRANCHES_DIR ?? path29.join(os10.homedir(), ".tmp", "zelari-code", "branches");
32756
34302
  }
32757
34303
  function getSessionsBaseDir() {
32758
- return process.env.ANATHEMA_SESSIONS_DIR ?? path23.join(os9.homedir(), ".tmp", "zelari-code", "sessions");
34304
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path29.join(os10.homedir(), ".tmp", "zelari-code", "sessions");
32759
34305
  }
32760
34306
  function branchPathFor(name, baseDir) {
32761
- return path23.join(baseDir, name);
34307
+ return path29.join(baseDir, name);
32762
34308
  }
32763
34309
  function metaPathFor(name, baseDir) {
32764
- return path23.join(baseDir, name, META_FILENAME);
34310
+ return path29.join(baseDir, name, META_FILENAME);
32765
34311
  }
32766
34312
  function sessionsPathFor(name, baseDir) {
32767
- return path23.join(baseDir, name, SESSIONS_SUBDIR);
34313
+ return path29.join(baseDir, name, SESSIONS_SUBDIR);
32768
34314
  }
32769
34315
  function readBranchMeta(name, baseDir) {
32770
34316
  const metaPath = metaPathFor(name, baseDir);
32771
- if (!existsSync24(metaPath)) {
34317
+ if (!existsSync26(metaPath)) {
32772
34318
  throw new BranchNotFoundError(`Branch "${name}" not found`);
32773
34319
  }
32774
34320
  try {
32775
- const raw = readFileSync21(metaPath, "utf-8");
34321
+ const raw = readFileSync23(metaPath, "utf-8");
32776
34322
  const parsed = JSON.parse(raw);
32777
34323
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
32778
34324
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -32789,13 +34335,13 @@ function readBranchMeta(name, baseDir) {
32789
34335
  }
32790
34336
  function writeBranchMeta(name, baseDir, meta3) {
32791
34337
  const metaPath = metaPathFor(name, baseDir);
32792
- mkdirSync10(path23.dirname(metaPath), { recursive: true });
34338
+ mkdirSync10(path29.dirname(metaPath), { recursive: true });
32793
34339
  writeFileSync15(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
32794
34340
  }
32795
34341
  async function countSessions(name, baseDir) {
32796
34342
  const sessionsPath = sessionsPathFor(name, baseDir);
32797
34343
  try {
32798
- const entries = await fs16.readdir(sessionsPath);
34344
+ const entries = await fs17.readdir(sessionsPath);
32799
34345
  return entries.filter((e) => e.endsWith(".jsonl")).length;
32800
34346
  } catch (err) {
32801
34347
  if (err.code === "ENOENT") return 0;
@@ -32828,7 +34374,7 @@ var SessionNotFoundError = class extends Error {
32828
34374
  };
32829
34375
  function branchExists(name, baseDir = getBranchesBaseDir()) {
32830
34376
  const bp = branchPathFor(name, baseDir);
32831
- return existsSync24(bp) && existsSync24(metaPathFor(name, baseDir));
34377
+ return existsSync26(bp) && existsSync26(metaPathFor(name, baseDir));
32832
34378
  }
32833
34379
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
32834
34380
  if (!name || name.trim().length === 0) {
@@ -32840,15 +34386,15 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
32840
34386
  if (branchExists(name, baseDir)) {
32841
34387
  throw new BranchAlreadyExistsError(name);
32842
34388
  }
32843
- const sourcePath = path23.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
32844
- if (!existsSync24(sourcePath)) {
34389
+ const sourcePath = path29.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
34390
+ if (!existsSync26(sourcePath)) {
32845
34391
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
32846
34392
  }
32847
34393
  const branchPath = branchPathFor(name, baseDir);
32848
34394
  const branchSessionsPath = sessionsPathFor(name, baseDir);
32849
34395
  mkdirSync10(branchSessionsPath, { recursive: true });
32850
- const destPath = path23.join(branchSessionsPath, `${fromSessionId}.jsonl`);
32851
- await fs16.copyFile(sourcePath, destPath);
34396
+ const destPath = path29.join(branchSessionsPath, `${fromSessionId}.jsonl`);
34397
+ await fs17.copyFile(sourcePath, destPath);
32852
34398
  const meta3 = {
32853
34399
  name,
32854
34400
  createdAt: Date.now(),
@@ -32866,7 +34412,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
32866
34412
  async function listBranches(baseDir = getBranchesBaseDir()) {
32867
34413
  let entries;
32868
34414
  try {
32869
- entries = await fs16.readdir(baseDir);
34415
+ entries = await fs17.readdir(baseDir);
32870
34416
  } catch (err) {
32871
34417
  if (err.code === "ENOENT") return [];
32872
34418
  throw err;
@@ -32874,7 +34420,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
32874
34420
  const results = [];
32875
34421
  for (const entry of entries) {
32876
34422
  const metaPath = metaPathFor(entry, baseDir);
32877
- if (!existsSync24(metaPath)) continue;
34423
+ if (!existsSync26(metaPath)) continue;
32878
34424
  try {
32879
34425
  const meta3 = readBranchMeta(entry, baseDir);
32880
34426
  const sessionCount = await countSessions(entry, baseDir);
@@ -32947,26 +34493,26 @@ async function handleBranchCheckout(ctx, branchName) {
32947
34493
  }
32948
34494
 
32949
34495
  // src/cli/slashHandlers/workspace.ts
32950
- import { promises as fs17 } from "node:fs";
32951
- import path24 from "node:path";
34496
+ import { promises as fs18 } from "node:fs";
34497
+ import path30 from "node:path";
32952
34498
  async function handleWorkspaceShow(ctx, what) {
32953
34499
  try {
32954
- const zelari = path24.join(process.cwd(), ".zelari");
34500
+ const zelari = path30.join(process.cwd(), ".zelari");
32955
34501
  let content;
32956
34502
  switch (what) {
32957
34503
  case "plan": {
32958
- const planPath = path24.join(zelari, "plan.md");
34504
+ const planPath = path30.join(zelari, "plan.md");
32959
34505
  try {
32960
- content = await fs17.readFile(planPath, "utf-8");
34506
+ content = await fs18.readFile(planPath, "utf-8");
32961
34507
  } catch {
32962
34508
  content = "(no plan.md yet \u2014 run a council session first)";
32963
34509
  }
32964
34510
  break;
32965
34511
  }
32966
34512
  case "decisions": {
32967
- const decisionsDir = path24.join(zelari, "decisions");
34513
+ const decisionsDir = path30.join(zelari, "decisions");
32968
34514
  try {
32969
- const files = (await fs17.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
34515
+ const files = (await fs18.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
32970
34516
  if (files.length === 0) {
32971
34517
  content = "(no ADRs yet \u2014 invoke /council to generate some)";
32972
34518
  } else {
@@ -32974,7 +34520,7 @@ async function handleWorkspaceShow(ctx, what) {
32974
34520
  `];
32975
34521
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
32976
34522
  for (const f of files) {
32977
- const raw = await fs17.readFile(path24.join(decisionsDir, f), "utf-8");
34523
+ const raw = await fs18.readFile(path30.join(decisionsDir, f), "utf-8");
32978
34524
  const { meta: meta3, body } = parseFrontmatter2(raw);
32979
34525
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
32980
34526
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -32987,27 +34533,27 @@ async function handleWorkspaceShow(ctx, what) {
32987
34533
  break;
32988
34534
  }
32989
34535
  case "risks": {
32990
- const risksPath = path24.join(zelari, "risks.md");
34536
+ const risksPath = path30.join(zelari, "risks.md");
32991
34537
  try {
32992
- content = await fs17.readFile(risksPath, "utf-8");
34538
+ content = await fs18.readFile(risksPath, "utf-8");
32993
34539
  } catch {
32994
34540
  content = "(no risks.md yet)";
32995
34541
  }
32996
34542
  break;
32997
34543
  }
32998
34544
  case "agents": {
32999
- const agentsPath = path24.join(process.cwd(), "AGENTS.MD");
34545
+ const agentsPath = path30.join(process.cwd(), "AGENTS.MD");
33000
34546
  try {
33001
- content = await fs17.readFile(agentsPath, "utf-8");
34547
+ content = await fs18.readFile(agentsPath, "utf-8");
33002
34548
  } catch {
33003
34549
  content = "(no AGENTS.MD yet at project root \u2014 run `/workspace sync` after a council session)";
33004
34550
  }
33005
34551
  break;
33006
34552
  }
33007
34553
  case "docs": {
33008
- const docsDir = path24.join(zelari, "docs");
34554
+ const docsDir = path30.join(zelari, "docs");
33009
34555
  try {
33010
- const files = (await fs17.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
34556
+ const files = (await fs18.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
33011
34557
  content = files.length ? `# Docs (${files.length})
33012
34558
 
33013
34559
  ` + files.map((f) => `- ${f}`).join("\n") : "(no docs drafts yet)";
@@ -33047,8 +34593,8 @@ async function handleWorkspaceReset(ctx, force) {
33047
34593
  return;
33048
34594
  }
33049
34595
  try {
33050
- const target = path24.join(process.cwd(), ".zelari");
33051
- await fs17.rm(target, { recursive: true, force: true });
34596
+ const target = path30.join(process.cwd(), ".zelari");
34597
+ await fs18.rm(target, { recursive: true, force: true });
33052
34598
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
33053
34599
  } catch (err) {
33054
34600
  appendSystem(ctx.setMessages, `[workspace reset error] ${err instanceof Error ? err.message : String(err)}`);
@@ -33406,16 +34952,16 @@ function handleModelsRefresh(ctx) {
33406
34952
  }
33407
34953
 
33408
34954
  // src/cli/slashHandlers/skills.ts
33409
- import path25 from "node:path";
33410
- import os10 from "node:os";
34955
+ import path31 from "node:path";
34956
+ import os11 from "node:os";
33411
34957
 
33412
34958
  // src/cli/skillHistory.ts
33413
- import { promises as fs18, existsSync as existsSync25, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync11 } from "node:fs";
34959
+ import { promises as fs19, existsSync as existsSync27, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync11 } from "node:fs";
33414
34960
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
33415
34961
  async function readSkillHistory(file2) {
33416
34962
  let raw = "";
33417
34963
  try {
33418
- raw = await fs18.readFile(file2, "utf-8");
34964
+ raw = await fs19.readFile(file2, "utf-8");
33419
34965
  } catch {
33420
34966
  return [];
33421
34967
  }
@@ -33508,7 +35054,7 @@ async function applySteerInterrupt(options) {
33508
35054
 
33509
35055
  // src/cli/slashHandlers/skills.ts
33510
35056
  async function handleSkillStats(ctx, skillId) {
33511
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path25.join(os10.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
35057
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path31.join(os11.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
33512
35058
  try {
33513
35059
  const records = await readSkillHistory(historyFile);
33514
35060
  const stats = getSkillStats(records, skillId);
@@ -33524,7 +35070,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
33524
35070
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
33525
35071
  return;
33526
35072
  }
33527
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path25.join(os10.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
35073
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path31.join(os11.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
33528
35074
  try {
33529
35075
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
33530
35076
  appendSystem(ctx.setMessages, formatted);
@@ -33599,7 +35145,8 @@ function useSlashDispatch(params) {
33599
35145
  dispatchPrompt,
33600
35146
  dispatchCouncilPrompt,
33601
35147
  dispatchZelariPrompt,
33602
- mode = "agent"
35148
+ mode = "agent",
35149
+ setMode
33603
35150
  } = params;
33604
35151
  return useCallback3(async (value) => {
33605
35152
  if (!value.trim()) return;
@@ -33840,6 +35387,29 @@ function useSlashDispatch(params) {
33840
35387
  setInput("");
33841
35388
  return;
33842
35389
  }
35390
+ if (result.kind === "index_build") {
35391
+ await handleIndexBuild({ ...baseCtx, cwd: process.cwd() });
35392
+ setInput("");
35393
+ return;
35394
+ }
35395
+ if (result.kind === "index_status") {
35396
+ handleIndexStatus({ ...baseCtx, cwd: process.cwd() });
35397
+ setInput("");
35398
+ return;
35399
+ }
35400
+ if (result.kind === "mode_set") {
35401
+ if (result.message) {
35402
+ appendSystem(setMessages, result.message);
35403
+ } else if (setMode) {
35404
+ const target = result.modeTarget ?? nextMode(mode);
35405
+ setMode(target);
35406
+ appendSystem(setMessages, `[mode] ${describeMode(target)}`);
35407
+ } else {
35408
+ appendSystem(setMessages, "[mode] switching unavailable in this context");
35409
+ }
35410
+ setInput("");
35411
+ return;
35412
+ }
33843
35413
  if (result.kind === "promote_member" && result.promoteMemberId) {
33844
35414
  await handlePromoteMember(baseCtx, result.promoteMemberId);
33845
35415
  setInput("");
@@ -33906,6 +35476,7 @@ function useSlashDispatch(params) {
33906
35476
  dispatchCouncilPrompt,
33907
35477
  dispatchZelariPrompt,
33908
35478
  mode,
35479
+ setMode,
33909
35480
  params
33910
35481
  ]);
33911
35482
  }
@@ -34034,9 +35605,7 @@ function App() {
34034
35605
  useInput2(
34035
35606
  (_input, key) => {
34036
35607
  if (key.tab && key.shift) {
34037
- setMode(
34038
- (m) => m === "agent" ? "council" : m === "council" ? "zelari" : "agent"
34039
- );
35608
+ setMode(nextMode);
34040
35609
  }
34041
35610
  },
34042
35611
  { isActive: isRawModeSupported === true }
@@ -34094,6 +35663,7 @@ function App() {
34094
35663
  dispatchCouncilPrompt: chatTurn.dispatchCouncilPrompt,
34095
35664
  dispatchZelariPrompt: chatTurn.dispatchZelariPrompt,
34096
35665
  mode,
35666
+ setMode,
34097
35667
  openPicker: setPicker,
34098
35668
  onNewSession,
34099
35669
  onExit,
@@ -34330,9 +35900,9 @@ function SplashGate({
34330
35900
  init_providerConfig();
34331
35901
 
34332
35902
  // src/cli/wizard/firstRun.ts
34333
- import { existsSync as existsSync26 } from "node:fs";
35903
+ import { existsSync as existsSync28 } from "node:fs";
34334
35904
  function shouldRunWizard(input) {
34335
- const exists = input.exists ?? existsSync26;
35905
+ const exists = input.exists ?? existsSync28;
34336
35906
  if (input.hasResetConfigFlag) {
34337
35907
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
34338
35908
  }
@@ -34822,9 +36392,9 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
34822
36392
 
34823
36393
  // src/cli/skillsMd.ts
34824
36394
  init_skills2();
34825
- import { existsSync as existsSync27, readdirSync as readdirSync4, readFileSync as readFileSync22 } from "node:fs";
36395
+ import { existsSync as existsSync29, readdirSync as readdirSync4, readFileSync as readFileSync24 } from "node:fs";
34826
36396
  import { join as join23 } from "node:path";
34827
- import { homedir as homedir5 } from "node:os";
36397
+ import { homedir as homedir6 } from "node:os";
34828
36398
  var CODING_CATEGORIES = /* @__PURE__ */ new Set([
34829
36399
  "plan",
34830
36400
  "refactor",
@@ -34842,7 +36412,7 @@ function skillMdSearchDirs(projectRoot = process.cwd()) {
34842
36412
  join23(projectRoot, ".zelari", "skills"),
34843
36413
  join23(projectRoot, ".claude", "skills"),
34844
36414
  join23(projectRoot, ".opencode", "skills"),
34845
- join23(homedir5(), ".zelari-code", "skills")
36415
+ join23(homedir6(), ".zelari-code", "skills")
34846
36416
  ];
34847
36417
  }
34848
36418
  function parseSkillMd(content, sourcePath) {
@@ -34900,7 +36470,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
34900
36470
  const summary = { loaded: [], skipped: [] };
34901
36471
  const seen = new Set(options.existingIds ?? []);
34902
36472
  for (const dir of skillMdSearchDirs(projectRoot)) {
34903
- if (!existsSync27(dir)) continue;
36473
+ if (!existsSync29(dir)) continue;
34904
36474
  let entries;
34905
36475
  try {
34906
36476
  entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -34909,9 +36479,9 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
34909
36479
  }
34910
36480
  for (const entry of entries) {
34911
36481
  const skillPath = join23(dir, entry, "SKILL.md");
34912
- if (!existsSync27(skillPath)) continue;
36482
+ if (!existsSync29(skillPath)) continue;
34913
36483
  try {
34914
- const parsed = parseSkillMd(readFileSync22(skillPath, "utf8"), skillPath);
36484
+ const parsed = parseSkillMd(readFileSync24(skillPath, "utf8"), skillPath);
34915
36485
  if (!parsed) {
34916
36486
  summary.skipped.push({ path: skillPath, reason: "missing/invalid frontmatter (name, description) or empty body" });
34917
36487
  continue;
@@ -34935,6 +36505,38 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
34935
36505
  init_skills2();
34936
36506
  init_updater();
34937
36507
  var VERSION = getCurrentVersion();
36508
+ function runPreflight() {
36509
+ if (process.env.ZELARI_SKIP_PREFLIGHT === "1") return;
36510
+ if (process.argv.includes("--skip-checks")) return;
36511
+ if (process.env.ANATHEMA_DEV === "1") return;
36512
+ const { runPrereqChecks: runPrereqChecks2 } = (
36513
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
36514
+ (init_prereqChecks(), __toCommonJS(prereqChecks_exports))
36515
+ );
36516
+ const { results, hasCriticalFail, warnings } = runPrereqChecks2({
36517
+ mode: "preflight"
36518
+ });
36519
+ for (const w of warnings) {
36520
+ console.error(`\x1B[33m[zelari-code] \u26A0 ${w.tool}: ${w.message}\x1B[0m`);
36521
+ }
36522
+ if (hasCriticalFail) {
36523
+ const critical = results.find(
36524
+ (r) => !r.ok && r.severity === "critical"
36525
+ );
36526
+ console.error("");
36527
+ console.error(
36528
+ "\x1B[31m==============================================================\n zelari-code cannot start: a critical prerequisite is missing.\n==============================================================\x1B[0m"
36529
+ );
36530
+ if (critical) {
36531
+ console.error(`
36532
+ ${critical.tool}: ${critical.message}`);
36533
+ }
36534
+ console.error(
36535
+ "\n Run `zelari-code --doctor` for the full diagnostic report.\n Bypass this check with ZELARI_SKIP_PREFLIGHT=1 (NOT recommended \u2014\n the agent will still fail when it tries to run npm/build/tsc)."
36536
+ );
36537
+ process.exit(1);
36538
+ }
36539
+ }
34938
36540
  async function backgroundUpdateCheck() {
34939
36541
  if (process.env.ANATHEMA_DEV === "1") return;
34940
36542
  await new Promise((resolve) => setTimeout(resolve, 3e3));
@@ -34974,7 +36576,7 @@ function pickRootComponent() {
34974
36576
  }
34975
36577
  if (argv.includes("--help") || argv.includes("-h")) {
34976
36578
  console.log(
34977
- "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --council Use the 6-member council pipeline\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ANATHEMA_DEV=1 Disable background update check\n"
36579
+ "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --council Use the 6-member council pipeline\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
34978
36580
  );
34979
36581
  process.exit(0);
34980
36582
  }
@@ -35024,6 +36626,7 @@ function loadUserSkills() {
35024
36626
  function main() {
35025
36627
  const picked = pickRootComponent();
35026
36628
  if (picked.kind === "done") return;
36629
+ runPreflight();
35027
36630
  loadUserSkills();
35028
36631
  if (picked.kind === "headless") {
35029
36632
  void runHeadless(picked.headlessOpts).then((code) => {