zelari-code 1.31.0 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -395,7 +395,7 @@ async function refreshGrokToken(options) {
395
395
  return parseTokenResponseBody(obj, accessToken);
396
396
  }
397
397
  async function openBrowser(url2) {
398
- const { spawn: spawn13 } = await import("node:child_process");
398
+ const { spawn: spawn14 } = await import("node:child_process");
399
399
  const cmd = (() => {
400
400
  switch (process.platform) {
401
401
  case "darwin":
@@ -408,7 +408,7 @@ async function openBrowser(url2) {
408
408
  })();
409
409
  return new Promise((resolve3, reject) => {
410
410
  try {
411
- const child = spawn13(cmd.bin, cmd.args, {
411
+ const child = spawn14(cmd.bin, cmd.args, {
412
412
  stdio: "ignore",
413
413
  detached: true
414
414
  });
@@ -1829,10 +1829,10 @@ function mergeDefs(...defs) {
1829
1829
  function cloneDef(schema) {
1830
1830
  return mergeDefs(schema._zod.def);
1831
1831
  }
1832
- function getElementAtPath(obj, path48) {
1833
- if (!path48)
1832
+ function getElementAtPath(obj, path51) {
1833
+ if (!path51)
1834
1834
  return obj;
1835
- return path48.reduce((acc, key) => acc?.[key], obj);
1835
+ return path51.reduce((acc, key) => acc?.[key], obj);
1836
1836
  }
1837
1837
  function promiseAllObject(promisesObj) {
1838
1838
  const keys = Object.keys(promisesObj);
@@ -2160,11 +2160,11 @@ function explicitlyAborted(x, startIndex = 0) {
2160
2160
  }
2161
2161
  return false;
2162
2162
  }
2163
- function prefixIssues(path48, issues) {
2163
+ function prefixIssues(path51, issues) {
2164
2164
  return issues.map((iss) => {
2165
2165
  var _a3;
2166
2166
  (_a3 = iss).path ?? (_a3.path = []);
2167
- iss.path.unshift(path48);
2167
+ iss.path.unshift(path51);
2168
2168
  return iss;
2169
2169
  });
2170
2170
  }
@@ -2382,16 +2382,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2382
2382
  }
2383
2383
  function formatError(error51, mapper = (issue2) => issue2.message) {
2384
2384
  const fieldErrors = { _errors: [] };
2385
- const processError = (error52, path48 = []) => {
2385
+ const processError = (error52, path51 = []) => {
2386
2386
  for (const issue2 of error52.issues) {
2387
2387
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2388
- issue2.errors.map((issues) => processError({ issues }, [...path48, ...issue2.path]));
2388
+ issue2.errors.map((issues) => processError({ issues }, [...path51, ...issue2.path]));
2389
2389
  } else if (issue2.code === "invalid_key") {
2390
- processError({ issues: issue2.issues }, [...path48, ...issue2.path]);
2390
+ processError({ issues: issue2.issues }, [...path51, ...issue2.path]);
2391
2391
  } else if (issue2.code === "invalid_element") {
2392
- processError({ issues: issue2.issues }, [...path48, ...issue2.path]);
2392
+ processError({ issues: issue2.issues }, [...path51, ...issue2.path]);
2393
2393
  } else {
2394
- const fullpath = [...path48, ...issue2.path];
2394
+ const fullpath = [...path51, ...issue2.path];
2395
2395
  if (fullpath.length === 0) {
2396
2396
  fieldErrors._errors.push(mapper(issue2));
2397
2397
  } else {
@@ -2418,17 +2418,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2418
2418
  }
2419
2419
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2420
2420
  const result = { errors: [] };
2421
- const processError = (error52, path48 = []) => {
2421
+ const processError = (error52, path51 = []) => {
2422
2422
  var _a3, _b;
2423
2423
  for (const issue2 of error52.issues) {
2424
2424
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2425
- issue2.errors.map((issues) => processError({ issues }, [...path48, ...issue2.path]));
2425
+ issue2.errors.map((issues) => processError({ issues }, [...path51, ...issue2.path]));
2426
2426
  } else if (issue2.code === "invalid_key") {
2427
- processError({ issues: issue2.issues }, [...path48, ...issue2.path]);
2427
+ processError({ issues: issue2.issues }, [...path51, ...issue2.path]);
2428
2428
  } else if (issue2.code === "invalid_element") {
2429
- processError({ issues: issue2.issues }, [...path48, ...issue2.path]);
2429
+ processError({ issues: issue2.issues }, [...path51, ...issue2.path]);
2430
2430
  } else {
2431
- const fullpath = [...path48, ...issue2.path];
2431
+ const fullpath = [...path51, ...issue2.path];
2432
2432
  if (fullpath.length === 0) {
2433
2433
  result.errors.push(mapper(issue2));
2434
2434
  continue;
@@ -2460,8 +2460,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2460
2460
  }
2461
2461
  function toDotPath(_path) {
2462
2462
  const segs = [];
2463
- const path48 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2464
- for (const seg of path48) {
2463
+ const path51 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2464
+ for (const seg of path51) {
2465
2465
  if (typeof seg === "number")
2466
2466
  segs.push(`[${seg}]`);
2467
2467
  else if (typeof seg === "symbol")
@@ -15964,13 +15964,13 @@ function resolveRef(ref, ctx) {
15964
15964
  if (!ref.startsWith("#")) {
15965
15965
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15966
15966
  }
15967
- const path48 = ref.slice(1).split("/").filter(Boolean);
15968
- if (path48.length === 0) {
15967
+ const path51 = ref.slice(1).split("/").filter(Boolean);
15968
+ if (path51.length === 0) {
15969
15969
  return ctx.rootSchema;
15970
15970
  }
15971
15971
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15972
- if (path48[0] === defsKey) {
15973
- const key = path48[1];
15972
+ if (path51[0] === defsKey) {
15973
+ const key = path51[1];
15974
15974
  if (!key || !ctx.defs[key]) {
15975
15975
  throw new Error(`Reference not found: ${ref}`);
15976
15976
  }
@@ -18134,11 +18134,11 @@ var init_tools = __esm({
18134
18134
  if (!ctx.addDocument)
18135
18135
  return "Knowledge vault tool not available.";
18136
18136
  const title = args["title"] || "New Document";
18137
- const path48 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
18137
+ const path51 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
18138
18138
  const content = args["content"] || "";
18139
18139
  const tags = args["tags"] || [];
18140
18140
  ctx.addDocument({
18141
- path: path48,
18141
+ path: path51,
18142
18142
  title,
18143
18143
  content,
18144
18144
  format: "markdown",
@@ -18147,7 +18147,7 @@ var init_tools = __esm({
18147
18147
  workspaceId: ctx.workspaceId
18148
18148
  });
18149
18149
  ctx.addActivity("vault", "created document", title);
18150
- return `Document "${title}" created at "${path48}".`;
18150
+ return `Document "${title}" created at "${path51}".`;
18151
18151
  }
18152
18152
  }
18153
18153
  ];
@@ -21166,23 +21166,303 @@ var init_sessionJsonl = __esm({
21166
21166
  }
21167
21167
  });
21168
21168
 
21169
+ // packages/core/dist/core/hooks/types.js
21170
+ function normalizeToolName(raw) {
21171
+ const n = raw.toLowerCase().replace(/[_-]/g, "");
21172
+ return TOOL_ALIASES[n] ?? n;
21173
+ }
21174
+ function toolMatches(pattern, toolName) {
21175
+ if (pattern === "*")
21176
+ return true;
21177
+ return normalizeToolName(pattern) === normalizeToolName(toolName);
21178
+ }
21179
+ function hookMatches(hook, event, toolName) {
21180
+ if (!hook.match.events.includes(event))
21181
+ return false;
21182
+ if (event === "PreToolUse" || event === "PostToolUse") {
21183
+ if (!toolName)
21184
+ return false;
21185
+ return hook.match.tools.some((t) => toolMatches(t, toolName));
21186
+ }
21187
+ return true;
21188
+ }
21189
+ var TOOL_ALIASES;
21190
+ var init_types2 = __esm({
21191
+ "packages/core/dist/core/hooks/types.js"() {
21192
+ "use strict";
21193
+ TOOL_ALIASES = {
21194
+ read: "read_file",
21195
+ readfile: "read_file",
21196
+ cat: "read_file",
21197
+ write: "write_file",
21198
+ writefile: "write_file",
21199
+ edit: "edit_file",
21200
+ editfile: "edit_file",
21201
+ glob: "list_files",
21202
+ listdir: "list_files",
21203
+ listdirectory: "list_files",
21204
+ ls: "list_files",
21205
+ dir: "list_files",
21206
+ find: "list_files",
21207
+ grep: "grep_content",
21208
+ search: "grep_content",
21209
+ searchrag: "searchDocuments",
21210
+ rag: "searchDocuments",
21211
+ shell: "bash",
21212
+ terminal: "bash",
21213
+ cmd: "bash",
21214
+ run: "bash",
21215
+ exec: "bash"
21216
+ };
21217
+ }
21218
+ });
21219
+
21220
+ // packages/core/dist/core/hooks/lifecycleHookRunner.js
21221
+ import { spawn as spawn2 } from "node:child_process";
21222
+ import { readdirSync, readFileSync as readFileSync4 } from "node:fs";
21223
+ import path12 from "node:path";
21224
+ function parseJson(text) {
21225
+ try {
21226
+ return JSON.parse(text);
21227
+ } catch {
21228
+ return null;
21229
+ }
21230
+ }
21231
+ var DEFAULT_TIMEOUT_MS, LifecycleHookRunner;
21232
+ var init_lifecycleHookRunner = __esm({
21233
+ "packages/core/dist/core/hooks/lifecycleHookRunner.js"() {
21234
+ "use strict";
21235
+ init_types2();
21236
+ DEFAULT_TIMEOUT_MS = 5e3;
21237
+ LifecycleHookRunner = class {
21238
+ hooks = [];
21239
+ logger;
21240
+ defaultTimeoutMs;
21241
+ constructor(options = {}) {
21242
+ this.logger = options.logger ?? ((msg) => console.error(`[hooks] ${msg}`));
21243
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
21244
+ for (const dir of options.dirs ?? []) {
21245
+ this.loadDir(dir);
21246
+ }
21247
+ }
21248
+ /** Add a single hook definition (used by tests / programmatic config). */
21249
+ addHook(hook) {
21250
+ this.hooks.push(hook);
21251
+ }
21252
+ /** Scan a directory for `*.json` hook files and load them. Returns count. */
21253
+ loadDir(dir) {
21254
+ let files;
21255
+ try {
21256
+ files = readdirSync(dir).filter((f) => f.endsWith(".json"));
21257
+ } catch {
21258
+ return 0;
21259
+ }
21260
+ let loaded = 0;
21261
+ for (const f of files) {
21262
+ const full = path12.join(dir, f);
21263
+ try {
21264
+ const parsed = parseJson(readFileSync4(full, "utf8"));
21265
+ if (!parsed || !parsed.name || !parsed.match) {
21266
+ this.logger(`skipping invalid hook file ${full}`);
21267
+ continue;
21268
+ }
21269
+ parsed.name = parsed.name || path12.basename(f, ".json");
21270
+ this.hooks.push(parsed);
21271
+ loaded += 1;
21272
+ } catch (err) {
21273
+ this.logger(`failed to load hook ${full}: ${err instanceof Error ? err.message : String(err)}`);
21274
+ }
21275
+ }
21276
+ return loaded;
21277
+ }
21278
+ listHooks() {
21279
+ return [...this.hooks];
21280
+ }
21281
+ /**
21282
+ * Run matching PreToolUse hooks. Returns `{ ok: false }` ONLY when a hook
21283
+ * explicitly denies. All other outcomes (no match, crash, timeout, invalid
21284
+ * JSON, allow decision) return `{ ok: true }`.
21285
+ */
21286
+ async runPreToolUse(toolName, toolInput, ctx) {
21287
+ for (const hook of this.hooks) {
21288
+ if (!hookMatches(hook, "PreToolUse", toolName))
21289
+ continue;
21290
+ const payload = {
21291
+ event: "PreToolUse",
21292
+ toolName,
21293
+ toolInput,
21294
+ sessionId: ctx.sessionId,
21295
+ cwd: ctx.cwd
21296
+ };
21297
+ const decision = await this.runHookSafely(hook, payload);
21298
+ if (decision.decision === "deny") {
21299
+ return {
21300
+ ok: false,
21301
+ hookName: hook.name,
21302
+ reason: decision.reason || `blocked by hook "${hook.name}"`
21303
+ };
21304
+ }
21305
+ }
21306
+ return { ok: true };
21307
+ }
21308
+ /** Run matching PostToolUse hooks. Fail-open, results are logged only. */
21309
+ async runPostToolUse(toolName, toolInput, toolOutput, ctx) {
21310
+ for (const hook of this.hooks) {
21311
+ if (!hookMatches(hook, "PostToolUse", toolName))
21312
+ continue;
21313
+ const payload = {
21314
+ event: "PostToolUse",
21315
+ toolName,
21316
+ toolInput,
21317
+ toolOutput,
21318
+ ok: ctx.ok ?? true,
21319
+ error: ctx.error,
21320
+ sessionId: ctx.sessionId,
21321
+ cwd: ctx.cwd
21322
+ };
21323
+ await this.runHookSafely(hook, payload);
21324
+ }
21325
+ }
21326
+ /** Run matching SessionStart hooks. Fail-open; deny never throws. */
21327
+ async runSessionStart(ctx) {
21328
+ return this.runSessionHooks("SessionStart", ctx);
21329
+ }
21330
+ /** Run matching SessionEnd hooks. Fail-open. */
21331
+ async runSessionEnd(ctx) {
21332
+ return this.runSessionHooks("SessionEnd", ctx);
21333
+ }
21334
+ async runSessionHooks(event, ctx) {
21335
+ for (const hook of this.hooks) {
21336
+ if (!hookMatches(hook, event, void 0))
21337
+ continue;
21338
+ const payload = {
21339
+ event,
21340
+ sessionId: ctx.sessionId,
21341
+ cwd: ctx.cwd
21342
+ };
21343
+ const decision = await this.runHookSafely(hook, payload);
21344
+ if (decision.decision === "deny") {
21345
+ return {
21346
+ ok: false,
21347
+ hookName: hook.name,
21348
+ reason: decision.reason || `blocked by hook "${hook.name}"`
21349
+ };
21350
+ }
21351
+ }
21352
+ return { ok: true };
21353
+ }
21354
+ /** Execute a single hook, swallowing every failure into an allow. */
21355
+ async runHookSafely(hook, payload) {
21356
+ try {
21357
+ const timeoutMs = hook.timeoutMs ?? this.defaultTimeoutMs;
21358
+ const raw = hook.url ? await this.postHttp(hook.url, payload, timeoutMs) : await this.execCommand(hook.command ?? "", payload, timeoutMs, hook.cwd);
21359
+ const decision = parseJson(raw);
21360
+ if (!decision) {
21361
+ this.logger(`hook "${hook.name}" returned invalid JSON (fail-open): ${raw.slice(0, 200)}`);
21362
+ return { decision: "allow" };
21363
+ }
21364
+ if (decision.decision === "deny" && typeof decision.reason !== "string") {
21365
+ this.logger(`hook "${hook.name}" denied without reason (fail-open)`);
21366
+ return { decision: "allow" };
21367
+ }
21368
+ return decision;
21369
+ } catch (err) {
21370
+ this.logger(`hook "${hook.name}" failed (fail-open): ${err instanceof Error ? err.message : String(err)}`);
21371
+ return { decision: "allow" };
21372
+ }
21373
+ }
21374
+ /** Spawn `command`, write JSON payload to stdin, read stdout until close. */
21375
+ execCommand(command, payload, timeoutMs, cwd) {
21376
+ return new Promise((resolve3, reject) => {
21377
+ const child = spawn2(command, {
21378
+ shell: true,
21379
+ cwd,
21380
+ stdio: ["pipe", "pipe", "pipe"],
21381
+ windowsHide: true
21382
+ });
21383
+ let stdout = "";
21384
+ let stderr = "";
21385
+ const timer = setTimeout(() => {
21386
+ child.kill("SIGKILL");
21387
+ reject(new Error(`timed out after ${timeoutMs}ms`));
21388
+ }, timeoutMs);
21389
+ child.stdout?.on("data", (d) => {
21390
+ stdout += d.toString();
21391
+ });
21392
+ child.stderr?.on("data", (d) => {
21393
+ stderr += d.toString();
21394
+ });
21395
+ child.on("error", (err) => {
21396
+ clearTimeout(timer);
21397
+ reject(err);
21398
+ });
21399
+ child.on("close", (code) => {
21400
+ clearTimeout(timer);
21401
+ if (code !== 0) {
21402
+ reject(new Error(`exited ${code}: ${stderr.trim().slice(0, 200)}`));
21403
+ return;
21404
+ }
21405
+ resolve3(stdout);
21406
+ });
21407
+ child.stdin?.on("error", () => {
21408
+ });
21409
+ child.stdin?.write(JSON.stringify(payload));
21410
+ child.stdin?.end();
21411
+ });
21412
+ }
21413
+ /** POST JSON payload; resolve with the response body text. */
21414
+ async postHttp(url2, payload, timeoutMs) {
21415
+ const ctrl = new AbortController();
21416
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
21417
+ try {
21418
+ const res = await fetch(url2, {
21419
+ method: "POST",
21420
+ headers: { "content-type": "application/json" },
21421
+ body: JSON.stringify(payload),
21422
+ signal: ctrl.signal
21423
+ });
21424
+ if (!res.ok) {
21425
+ throw new Error(`HTTP ${res.status}`);
21426
+ }
21427
+ return await res.text();
21428
+ } finally {
21429
+ clearTimeout(timer);
21430
+ }
21431
+ }
21432
+ };
21433
+ }
21434
+ });
21435
+
21436
+ // packages/core/dist/core/hooks/index.js
21437
+ var init_hooks = __esm({
21438
+ "packages/core/dist/core/hooks/index.js"() {
21439
+ "use strict";
21440
+ init_types2();
21441
+ init_lifecycleHookRunner();
21442
+ }
21443
+ });
21444
+
21169
21445
  // packages/core/dist/harness/index.js
21170
21446
  var harness_exports = {};
21171
21447
  __export(harness_exports, {
21172
21448
  AgentHarness: () => AgentHarness,
21173
21449
  DOOM_LOOP_THRESHOLD: () => DOOM_LOOP_THRESHOLD,
21450
+ LifecycleHookRunner: () => LifecycleHookRunner,
21174
21451
  SessionJsonlWriter: () => SessionJsonlWriter,
21175
21452
  TEXT_LOOP_RECOVERY_SYSTEM: () => TEXT_LOOP_RECOVERY_SYSTEM,
21176
21453
  TEXT_LOOP_RECOVERY_USER_PROMPT: () => TEXT_LOOP_RECOVERY_USER_PROMPT,
21177
21454
  collapseLoopedAssistantText: () => collapseLoopedAssistantText,
21178
21455
  detectAssistantTextLoop: () => detectAssistantTextLoop,
21179
21456
  hashToolCall: () => hashToolCall,
21457
+ hookMatches: () => hookMatches,
21180
21458
  isStatusTheaterUnit: () => isStatusTheaterUnit,
21181
21459
  normalizeLoopUnit: () => normalizeLoopUnit,
21182
21460
  normalizeTextToolArgs: () => normalizeTextToolArgs,
21461
+ normalizeToolName: () => normalizeToolName,
21183
21462
  parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
21184
21463
  parseTextToolCalls: () => parseTextToolCalls,
21185
21464
  readSession: () => readSession,
21465
+ toolMatches: () => toolMatches,
21186
21466
  wrapLegacyStream: () => wrapLegacyStream
21187
21467
  });
21188
21468
  var init_harness = __esm({
@@ -21191,6 +21471,7 @@ var init_harness = __esm({
21191
21471
  init_AgentHarness();
21192
21472
  init_providerStream();
21193
21473
  init_sessionJsonl();
21474
+ init_hooks();
21194
21475
  }
21195
21476
  });
21196
21477
 
@@ -21667,7 +21948,7 @@ var init_parseCssMotion = __esm({
21667
21948
  });
21668
21949
 
21669
21950
  // packages/core/dist/council/verification/citeVerify.js
21670
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
21951
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
21671
21952
  import { join } from "node:path";
21672
21953
  function extractCitations(text) {
21673
21954
  const out = [];
@@ -21704,7 +21985,7 @@ function verifyCitations(projectRoot, synthesisText) {
21704
21985
  });
21705
21986
  continue;
21706
21987
  }
21707
- const lines = readFileSync4(abs, "utf8").split(/\r?\n/);
21988
+ const lines = readFileSync5(abs, "utf8").split(/\r?\n/);
21708
21989
  if (cite.line > lines.length) {
21709
21990
  results.push({
21710
21991
  id: "synthesis.cite-invalid",
@@ -21967,14 +22248,14 @@ var init_synthesisAudit = __esm({
21967
22248
  });
21968
22249
 
21969
22250
  // packages/core/dist/council/verification/runChecks.js
21970
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
22251
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
21971
22252
  import { join as join2 } from "node:path";
21972
22253
  function loadNfrSpec(zelariRoot) {
21973
- const path48 = join2(zelariRoot, "nfr-spec.json");
21974
- if (!existsSync6(path48))
22254
+ const path51 = join2(zelariRoot, "nfr-spec.json");
22255
+ if (!existsSync6(path51))
21975
22256
  return null;
21976
22257
  try {
21977
- const raw = JSON.parse(readFileSync5(path48, "utf8"));
22258
+ const raw = JSON.parse(readFileSync6(path51, "utf8"));
21978
22259
  if (raw.version !== 1 || !Array.isArray(raw.targets))
21979
22260
  return null;
21980
22261
  return raw;
@@ -22049,13 +22330,13 @@ function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
22049
22330
  return [];
22050
22331
  let plan;
22051
22332
  try {
22052
- plan = JSON.parse(readFileSync5(planPath, "utf8"));
22333
+ plan = JSON.parse(readFileSync6(planPath, "utf8"));
22053
22334
  } catch {
22054
22335
  return [];
22055
22336
  }
22056
22337
  const milestoneText = (plan.milestones ?? []).map((m) => `${m.name ?? ""} ${m.description ?? ""}`).join(" ").toLowerCase();
22057
22338
  const results = [];
22058
- const targetContent = targets.map((t) => readFileSync5(join2(projectRoot, t), "utf8").toLowerCase()).join("\n");
22339
+ const targetContent = targets.map((t) => readFileSync6(join2(projectRoot, t), "utf8").toLowerCase()).join("\n");
22059
22340
  for (const kw of keywords) {
22060
22341
  const low = kw.toLowerCase();
22061
22342
  if (!milestoneText.includes(low))
@@ -22087,11 +22368,11 @@ function checkReadmeStale(projectRoot, targets) {
22087
22368
  const readmePath = join2(projectRoot, "README.md");
22088
22369
  if (!existsSync6(readmePath) || targets.length === 0)
22089
22370
  return [];
22090
- const readme = readFileSync5(readmePath, "utf8");
22371
+ const readme = readFileSync6(readmePath, "utf8");
22091
22372
  const htmlPath = join2(projectRoot, targets[0]);
22092
22373
  if (!existsSync6(htmlPath))
22093
22374
  return [];
22094
- const html = readFileSync5(htmlPath, "utf8");
22375
+ const html = readFileSync6(htmlPath, "utf8");
22095
22376
  const sectionCount = (html.match(/<section\s+id=/gi) ?? []).length;
22096
22377
  const readmeSections = readme.match(/(\d+)\s+sezioni/i);
22097
22378
  if (readmeSections) {
@@ -22129,7 +22410,7 @@ function runImplementationVerification(input) {
22129
22410
  forbidLayoutProps: anim.forbidLayoutProps ?? true
22130
22411
  };
22131
22412
  for (const rel2 of targets) {
22132
- const html = readFileSync5(join2(input.projectRoot, rel2), "utf8");
22413
+ const html = readFileSync6(join2(input.projectRoot, rel2), "utf8");
22133
22414
  for (const v of scanKeyframesViolations(html, scanOpts)) {
22134
22415
  results.push({
22135
22416
  id: "motion.keyframes",
@@ -22215,7 +22496,7 @@ var init_runChecks = __esm({
22215
22496
  });
22216
22497
 
22217
22498
  // packages/core/dist/council/verification/microGate.js
22218
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
22499
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
22219
22500
  import { join as join3 } from "node:path";
22220
22501
  function checkDeadHooksInHtml(html) {
22221
22502
  const warnings = [];
@@ -22253,7 +22534,7 @@ function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
22253
22534
  compositorOnly: anim.compositorOnly ?? true,
22254
22535
  forbidLayoutProps: anim.forbidLayoutProps ?? true
22255
22536
  };
22256
- const html = readFileSync6(abs, "utf8");
22537
+ const html = readFileSync7(abs, "utf8");
22257
22538
  const warnings = [];
22258
22539
  for (const v of scanKeyframesViolations(html, scanOpts)) {
22259
22540
  warnings.push({
@@ -22609,7 +22890,7 @@ var init_implementationDelivery = __esm({
22609
22890
  });
22610
22891
 
22611
22892
  // packages/core/dist/council/verification/inlineJsAutofix.js
22612
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
22893
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
22613
22894
  import { join as join4 } from "node:path";
22614
22895
  function minifyInlineJs(js) {
22615
22896
  let out = js.replace(/\/\*[\s\S]*?\*\//g, "");
@@ -22656,7 +22937,7 @@ function applyInlineJsAutofix(projectRoot, report) {
22656
22937
  const abs = join4(projectRoot, rel2);
22657
22938
  let html;
22658
22939
  try {
22659
- html = readFileSync7(abs, "utf8");
22940
+ html = readFileSync8(abs, "utf8");
22660
22941
  } catch {
22661
22942
  continue;
22662
22943
  }
@@ -24048,14 +24329,14 @@ var init_missionBrief = __esm({
24048
24329
  });
24049
24330
 
24050
24331
  // packages/core/dist/council/verification/types.js
24051
- var init_types2 = __esm({
24332
+ var init_types3 = __esm({
24052
24333
  "packages/core/dist/council/verification/types.js"() {
24053
24334
  "use strict";
24054
24335
  }
24055
24336
  });
24056
24337
 
24057
24338
  // packages/core/dist/council/verification/motionAutofix.js
24058
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
24339
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
24059
24340
  import { join as join6 } from "node:path";
24060
24341
  function sanitizeTransitionPart(part) {
24061
24342
  const tokens = part.trim().split(/\s+/);
@@ -24134,7 +24415,7 @@ function applyMotionAutofix(projectRoot, report) {
24134
24415
  const abs = join6(projectRoot, rel2);
24135
24416
  let html;
24136
24417
  try {
24137
- html = readFileSync8(abs, "utf8");
24418
+ html = readFileSync9(abs, "utf8");
24138
24419
  } catch {
24139
24420
  continue;
24140
24421
  }
@@ -24206,7 +24487,7 @@ var init_motionAutofix = __esm({
24206
24487
  });
24207
24488
 
24208
24489
  // packages/core/dist/council/verification/autofix.js
24209
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
24490
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
24210
24491
  import { join as join7 } from "node:path";
24211
24492
  function applyDeterministicAutofix(projectRoot, report) {
24212
24493
  const motion = applyMotionAutofix(projectRoot, report);
@@ -24222,7 +24503,7 @@ function applyDeterministicAutofix(projectRoot, report) {
24222
24503
  if (!m || m[1] === "rm")
24223
24504
  continue;
24224
24505
  const abs = join7(projectRoot, rel2);
24225
- let html = readFileSync9(abs, "utf8");
24506
+ let html = readFileSync10(abs, "utf8");
24226
24507
  const snippet = m[0];
24227
24508
  if (!html.includes(snippet))
24228
24509
  continue;
@@ -24252,7 +24533,7 @@ var init_autofix = __esm({
24252
24533
  var init_verification = __esm({
24253
24534
  "packages/core/dist/council/verification/index.js"() {
24254
24535
  "use strict";
24255
- init_types2();
24536
+ init_types3();
24256
24537
  init_runChecks();
24257
24538
  init_honesty();
24258
24539
  init_parseCssMotion();
@@ -24270,19 +24551,19 @@ var init_verification = __esm({
24270
24551
  });
24271
24552
 
24272
24553
  // packages/core/dist/council/lessons/types.js
24273
- var init_types3 = __esm({
24554
+ var init_types4 = __esm({
24274
24555
  "packages/core/dist/council/lessons/types.js"() {
24275
24556
  "use strict";
24276
24557
  }
24277
24558
  });
24278
24559
 
24279
24560
  // packages/core/dist/council/lessons/io.js
24280
- import { readFileSync as readFileSync10 } from "node:fs";
24561
+ import { readFileSync as readFileSync11 } from "node:fs";
24281
24562
  import { join as join8 } from "node:path";
24282
24563
  function readLessonsDeduped(zelariRoot) {
24283
- const path48 = join8(zelariRoot, LESSONS_FILE);
24564
+ const path51 = join8(zelariRoot, LESSONS_FILE);
24284
24565
  try {
24285
- const raw = readFileSync10(path48, "utf8");
24566
+ const raw = readFileSync11(path51, "utf8");
24286
24567
  const byId = /* @__PURE__ */ new Map();
24287
24568
  for (const line of raw.split(/\r?\n/)) {
24288
24569
  if (!line.trim())
@@ -24383,8 +24664,8 @@ function keywordsFrom(check2, signature) {
24383
24664
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
24384
24665
  }
24385
24666
  function writeLesson(zelariRoot, lesson) {
24386
- const path48 = join9(zelariRoot, LESSONS_FILE);
24387
- appendFileSync(path48, `${JSON.stringify(lesson)}
24667
+ const path51 = join9(zelariRoot, LESSONS_FILE);
24668
+ appendFileSync(path51, `${JSON.stringify(lesson)}
24388
24669
  `, "utf8");
24389
24670
  }
24390
24671
  function findSimilar(lessons, signature) {
@@ -24523,7 +24804,7 @@ var init_recallLessons = __esm({
24523
24804
  var init_lessons = __esm({
24524
24805
  "packages/core/dist/council/lessons/index.js"() {
24525
24806
  "use strict";
24526
- init_types3();
24807
+ init_types4();
24527
24808
  init_io();
24528
24809
  init_isAnswerLeak();
24529
24810
  init_signatures();
@@ -24533,7 +24814,7 @@ var init_lessons = __esm({
24533
24814
  });
24534
24815
 
24535
24816
  // packages/core/dist/council/completion/types.js
24536
- var init_types4 = __esm({
24817
+ var init_types5 = __esm({
24537
24818
  "packages/core/dist/council/completion/types.js"() {
24538
24819
  "use strict";
24539
24820
  }
@@ -24614,7 +24895,7 @@ var init_buildCompletion = __esm({
24614
24895
  var init_completion2 = __esm({
24615
24896
  "packages/core/dist/council/completion/index.js"() {
24616
24897
  "use strict";
24617
- init_types4();
24898
+ init_types5();
24618
24899
  init_buildCompletion();
24619
24900
  }
24620
24901
  });
@@ -24933,7 +25214,7 @@ var init_council = __esm({
24933
25214
  });
24934
25215
 
24935
25216
  // packages/core/dist/memory/types.js
24936
- var init_types5 = __esm({
25217
+ var init_types6 = __esm({
24937
25218
  "packages/core/dist/memory/types.js"() {
24938
25219
  "use strict";
24939
25220
  }
@@ -24997,9 +25278,9 @@ function findCycle(nodes) {
24997
25278
  if (color.get(start) !== WHITE)
24998
25279
  continue;
24999
25280
  const stack = [[start, 0]];
25000
- const path48 = [];
25281
+ const path51 = [];
25001
25282
  color.set(start, GRAY);
25002
- path48.push(start);
25283
+ path51.push(start);
25003
25284
  while (stack.length > 0) {
25004
25285
  const top = stack[stack.length - 1];
25005
25286
  const [id, idx] = top;
@@ -25012,17 +25293,17 @@ function findCycle(nodes) {
25012
25293
  continue;
25013
25294
  const c = color.get(dep);
25014
25295
  if (c === GRAY) {
25015
- const at = path48.indexOf(dep);
25016
- return [...path48.slice(at), dep];
25296
+ const at = path51.indexOf(dep);
25297
+ return [...path51.slice(at), dep];
25017
25298
  }
25018
25299
  if (c === WHITE) {
25019
25300
  color.set(dep, GRAY);
25020
- path48.push(dep);
25301
+ path51.push(dep);
25021
25302
  stack.push([dep, 0]);
25022
25303
  }
25023
25304
  } else {
25024
25305
  color.set(id, BLACK);
25025
- path48.pop();
25306
+ path51.pop();
25026
25307
  stack.pop();
25027
25308
  }
25028
25309
  }
@@ -25649,7 +25930,7 @@ var init_personas = __esm({
25649
25930
 
25650
25931
  // packages/core/dist/kraken/runtime/types.js
25651
25932
  var PlanError;
25652
- var init_types6 = __esm({
25933
+ var init_types7 = __esm({
25653
25934
  "packages/core/dist/kraken/runtime/types.js"() {
25654
25935
  "use strict";
25655
25936
  PlanError = class extends Error {
@@ -25747,7 +26028,7 @@ var MAX_BUNDLE_BYTES, FORBIDDEN_TOKENS;
25747
26028
  var init_sandbox = __esm({
25748
26029
  "packages/core/dist/kraken/runtime/sandbox.js"() {
25749
26030
  "use strict";
25750
- init_types6();
26031
+ init_types7();
25751
26032
  MAX_BUNDLE_BYTES = 256 * 1024;
25752
26033
  FORBIDDEN_TOKENS = [
25753
26034
  /\bprocess\b/,
@@ -25810,7 +26091,7 @@ var init_runner = __esm({
25810
26091
  "use strict";
25811
26092
  init_verdict();
25812
26093
  init_personas();
25813
- init_types6();
26094
+ init_types7();
25814
26095
  DEFAULT_MAX_TENTACLES = 200;
25815
26096
  DEFAULT_PLAN_TIMEOUT_MS = 30 * 6e4;
25816
26097
  ScriptRunner = class {
@@ -25942,8 +26223,8 @@ var init_runner = __esm({
25942
26223
  failed: [...this.tentaclesById.values()].filter((r) => r.status === "error"),
25943
26224
  pending: []
25944
26225
  };
25945
- const path48 = await this.host.saveSnapshot(snapshot, ".zelari/kraken/snapshots");
25946
- this.host.log(`checkpoint: ${label ?? "auto"} \u2192 ${path48}`);
26226
+ const path51 = await this.host.saveSnapshot(snapshot, ".zelari/kraken/snapshots");
26227
+ this.host.log(`checkpoint: ${label ?? "auto"} \u2192 ${path51}`);
25947
26228
  return snapshot;
25948
26229
  }
25949
26230
  callLog(msg, data) {
@@ -25994,7 +26275,7 @@ var init_runner = __esm({
25994
26275
  var init_runtime = __esm({
25995
26276
  "packages/core/dist/kraken/runtime/index.js"() {
25996
26277
  "use strict";
25997
- init_types6();
26278
+ init_types7();
25998
26279
  init_sandbox();
25999
26280
  init_runner();
26000
26281
  }
@@ -26047,6 +26328,7 @@ __export(dist_exports, {
26047
26328
  LANGUAGE_POLICY_MODULE_TYPE: () => LANGUAGE_POLICY_MODULE_TYPE,
26048
26329
  LAYOUT_MOTION_PROPS: () => LAYOUT_MOTION_PROPS,
26049
26330
  LESSONS_FILE: () => LESSONS_FILE,
26331
+ LifecycleHookRunner: () => LifecycleHookRunner,
26050
26332
  MAX_DELIVERY_ATTEMPTS: () => MAX_DELIVERY_ATTEMPTS,
26051
26333
  MAX_FINDINGS_CHARS: () => MAX_FINDINGS_CHARS,
26052
26334
  MAX_RETRY_PER_MEMBER: () => MAX_RETRY_PER_MEMBER,
@@ -26155,6 +26437,7 @@ __export(dist_exports, {
26155
26437
  getToolDescriptions: () => getToolDescriptions,
26156
26438
  hasInteractiveClarification: () => hasInteractiveClarification,
26157
26439
  hashToolCall: () => hashToolCall,
26440
+ hookMatches: () => hookMatches,
26158
26441
  isAnswerLeak: () => isAnswerLeak,
26159
26442
  isBrainAgentEndEvent: () => isBrainAgentEndEvent,
26160
26443
  isBrainAgentStartEvent: () => isBrainAgentStartEvent,
@@ -26187,6 +26470,7 @@ __export(dist_exports, {
26187
26470
  normalizeLoopUnit: () => normalizeLoopUnit,
26188
26471
  normalizeScopePath: () => normalizeScopePath,
26189
26472
  normalizeTextToolArgs: () => normalizeTextToolArgs,
26473
+ normalizeToolName: () => normalizeToolName,
26190
26474
  parseClarificationRequest: () => parseClarificationRequest,
26191
26475
  parseEvidenceTier: () => parseEvidenceTier,
26192
26476
  parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
@@ -26241,6 +26525,7 @@ __export(dist_exports, {
26241
26525
  taskMatchesNfrKeywords: () => taskMatchesNfrKeywords,
26242
26526
  tierAtLeast: () => tierAtLeast,
26243
26527
  tokenizeForSignature: () => tokenizeForSignature,
26528
+ toolMatches: () => toolMatches,
26244
26529
  topoLevels: () => topoLevels,
26245
26530
  unregisterCustomTool: () => unregisterCustomTool,
26246
26531
  unregisterSkill: () => unregisterSkill,
@@ -26263,7 +26548,7 @@ var init_dist = __esm({
26263
26548
  init_harness();
26264
26549
  init_council();
26265
26550
  init_skills2();
26266
- init_types5();
26551
+ init_types6();
26267
26552
  init_state();
26268
26553
  init_kraken();
26269
26554
  }
@@ -26961,9 +27246,9 @@ function spillToolOutput(fullText, meta3) {
26961
27246
  const rnd = randomBytes(3).toString("hex");
26962
27247
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
26963
27248
  const file2 = `${stamp}-${safeTool}-${hash3}-${rnd}.txt`;
26964
- const path48 = join11(dir, file2);
26965
- writeFileSync10(path48, fullText, "utf8");
26966
- return path48;
27249
+ const path51 = join11(dir, file2);
27250
+ writeFileSync10(path51, fullText, "utf8");
27251
+ return path51;
26967
27252
  } catch {
26968
27253
  return null;
26969
27254
  }
@@ -27009,10 +27294,10 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
27009
27294
  ${tail}`;
27010
27295
  }
27011
27296
  if (doSpill) {
27012
- const path48 = spillToolOutput(text, { toolName: opts.toolName });
27013
- if (path48) {
27297
+ const path51 = spillToolOutput(text, { toolName: opts.toolName });
27298
+ if (path51) {
27014
27299
  const spillNote = `
27015
- \u2026 [full output spilled to: ${path48} \u2014 re-read with read_file if you need the complete text] \u2026`;
27300
+ \u2026 [full output spilled to: ${path51} \u2014 re-read with read_file if you need the complete text] \u2026`;
27016
27301
  if (preview.includes("] \u2026\n")) {
27017
27302
  preview = preview.replace("] \u2026\n", `] \u2026${spillNote}
27018
27303
  `);
@@ -27062,12 +27347,21 @@ var init_registry2 = __esm({
27062
27347
  })();
27063
27348
  ToolRegistry = class {
27064
27349
  tools = /* @__PURE__ */ new Map();
27350
+ /** v0.10.0: lifecycle hooks (PreToolUse/PostToolUse). Null = no hooks. */
27351
+ lifecycleHooks = null;
27065
27352
  register(def) {
27066
27353
  this.tools.set(def.name, def);
27067
27354
  }
27068
27355
  get(name) {
27069
27356
  return this.tools.get(name);
27070
27357
  }
27358
+ /** v0.10.0: attach the fail-open lifecycle hook runner (null to disable). */
27359
+ setLifecycleHooks(runner) {
27360
+ this.lifecycleHooks = runner;
27361
+ }
27362
+ getLifecycleHooks() {
27363
+ return this.lifecycleHooks;
27364
+ }
27071
27365
  list() {
27072
27366
  return Array.from(this.tools.keys());
27073
27367
  }
@@ -27100,6 +27394,18 @@ var init_registry2 = __esm({
27100
27394
  },
27101
27395
  sessionId: options.sessionId ?? "default"
27102
27396
  };
27397
+ if (this.lifecycleHooks) {
27398
+ try {
27399
+ const pre = await this.lifecycleHooks.runPreToolUse(name, parsed.data, {
27400
+ sessionId: ctx.sessionId,
27401
+ cwd: ctx.cwd
27402
+ });
27403
+ if (!pre.ok) {
27404
+ return typedErr(`[hook:${pre.hookName ?? "unknown"}] ${pre.reason ?? "denied"}`);
27405
+ }
27406
+ } catch (hookErr) {
27407
+ }
27408
+ }
27103
27409
  try {
27104
27410
  const result = await Promise.race([
27105
27411
  tool.execute(parsed.data, ctx),
@@ -27128,6 +27434,17 @@ var init_registry2 = __esm({
27128
27434
  }
27129
27435
  }
27130
27436
  }
27437
+ if (this.lifecycleHooks) {
27438
+ try {
27439
+ await this.lifecycleHooks.runPostToolUse(name, parsed.data, result, {
27440
+ sessionId: ctx.sessionId,
27441
+ cwd: ctx.cwd,
27442
+ ok: result.ok,
27443
+ error: result.ok ? void 0 : "error" in result ? result.error : void 0
27444
+ });
27445
+ } catch {
27446
+ }
27447
+ }
27131
27448
  return result;
27132
27449
  } catch (err) {
27133
27450
  const error51 = err instanceof Error ? err.message : String(err);
@@ -27150,14 +27467,14 @@ var init_registry2 = __esm({
27150
27467
  });
27151
27468
 
27152
27469
  // src/cli/safety/sandboxPath.ts
27153
- import path14 from "node:path";
27470
+ import path15 from "node:path";
27154
27471
  function resolveSandboxedPath(userPath, options = {}) {
27155
27472
  if (typeof userPath !== "string" || userPath.length === 0) {
27156
27473
  throw new SandboxViolationError("Empty path", userPath, "");
27157
27474
  }
27158
- const root = path14.resolve(options.root ?? process.cwd());
27159
- const resolved = path14.isAbsolute(userPath) ? path14.resolve(userPath) : path14.resolve(root, userPath);
27160
- const rootWithSep = root.endsWith(path14.sep) ? root : root + path14.sep;
27475
+ const root = path15.resolve(options.root ?? process.cwd());
27476
+ const resolved = path15.isAbsolute(userPath) ? path15.resolve(userPath) : path15.resolve(root, userPath);
27477
+ const rootWithSep = root.endsWith(path15.sep) ? root : root + path15.sep;
27161
27478
  if (resolved !== root && !resolved.startsWith(rootWithSep)) {
27162
27479
  throw new SandboxViolationError(
27163
27480
  `Path escapes sandbox root: ${userPath} \u2192 ${resolved} (root: ${root})`,
@@ -27245,12 +27562,12 @@ __export(auditLogger_exports, {
27245
27562
  AuditLogger: () => AuditLogger
27246
27563
  });
27247
27564
  import { promises as fs11 } from "node:fs";
27248
- import path15 from "node:path";
27565
+ import path16 from "node:path";
27249
27566
  import os6 from "node:os";
27250
27567
  function defaultAuditPath() {
27251
27568
  const override = process.env.ANATHEMA_AUDIT_LOG;
27252
27569
  if (override && override.trim().length > 0) return override;
27253
- return path15.join(os6.tmpdir(), "zelari-code", "audit.jsonl");
27570
+ return path16.join(os6.tmpdir(), "zelari-code", "audit.jsonl");
27254
27571
  }
27255
27572
  function redactArgs(args) {
27256
27573
  const redacted = {};
@@ -27293,7 +27610,7 @@ var init_auditLogger = __esm({
27293
27610
  async append(entry) {
27294
27611
  const line = JSON.stringify(entry) + "\n";
27295
27612
  this.writeQueue = this.writeQueue.then(async () => {
27296
- await fs11.mkdir(path15.dirname(this.logPath), { recursive: true });
27613
+ await fs11.mkdir(path16.dirname(this.logPath), { recursive: true });
27297
27614
  await fs11.appendFile(this.logPath, line, "utf-8");
27298
27615
  });
27299
27616
  return this.writeQueue;
@@ -27353,9 +27670,9 @@ var init_cmdline = __esm({
27353
27670
  });
27354
27671
 
27355
27672
  // src/cli/diagnostics/engine.ts
27356
- import { spawn as spawn2 } from "node:child_process";
27673
+ import { spawn as spawn3 } from "node:child_process";
27357
27674
  import { existsSync as existsSync12 } from "node:fs";
27358
- import path16 from "node:path";
27675
+ import path17 from "node:path";
27359
27676
  function parseEslintJson(stdout, _file2) {
27360
27677
  const json2 = safeJson(stdout);
27361
27678
  if (!Array.isArray(json2)) return [];
@@ -27414,7 +27731,7 @@ function safeJson(s) {
27414
27731
  }
27415
27732
  }
27416
27733
  function providerForFile(file2, providers = DEFAULT_PROVIDERS) {
27417
- const ext = path16.extname(file2).toLowerCase();
27734
+ const ext = path17.extname(file2).toLowerCase();
27418
27735
  return providers.find((p3) => p3.extensions.includes(ext)) ?? null;
27419
27736
  }
27420
27737
  function resolveBin(bin, cwd) {
@@ -27422,10 +27739,10 @@ function resolveBin(bin, cwd) {
27422
27739
  let dir = cwd;
27423
27740
  for (let i = 0; i < 6; i += 1) {
27424
27741
  for (const suffix of suffixes) {
27425
- const candidate = path16.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
27742
+ const candidate = path17.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
27426
27743
  if (existsSync12(candidate)) return candidate;
27427
27744
  }
27428
- const parent = path16.dirname(dir);
27745
+ const parent = path17.dirname(dir);
27429
27746
  if (parent === dir) break;
27430
27747
  dir = parent;
27431
27748
  }
@@ -27498,7 +27815,7 @@ var init_engine = __esm({
27498
27815
  };
27499
27816
  let child;
27500
27817
  try {
27501
- child = process.platform === "win32" ? spawn2(buildCmdLine(cmd, args), { cwd: opts.cwd, shell: true }) : spawn2(cmd, args, { cwd: opts.cwd });
27818
+ child = process.platform === "win32" ? spawn3(buildCmdLine(cmd, args), { cwd: opts.cwd, shell: true }) : spawn3(cmd, args, { cwd: opts.cwd });
27502
27819
  } catch {
27503
27820
  done({ code: null, stdout: "", stderr: "" });
27504
27821
  return;
@@ -27529,14 +27846,14 @@ var init_engine = __esm({
27529
27846
  });
27530
27847
 
27531
27848
  // src/cli/tools/krakenRadio.ts
27532
- import { appendFileSync as appendFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync12, readdirSync } from "node:fs";
27533
- import path17 from "node:path";
27849
+ import { appendFileSync as appendFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync13, readdirSync as readdirSync2 } from "node:fs";
27850
+ import path18 from "node:path";
27534
27851
  function radioDir(cwd) {
27535
- return path17.join(cwd, ".zelari", "radio");
27852
+ return path18.join(cwd, ".zelari", "radio");
27536
27853
  }
27537
27854
  function radioPath(cwd, sessionId) {
27538
27855
  const safe = (sessionId || "default").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
27539
- return path17.join(radioDir(cwd), `${safe}.jsonl`);
27856
+ return path18.join(radioDir(cwd), `${safe}.jsonl`);
27540
27857
  }
27541
27858
  function appendKrakenRadio(cwd, sessionId, event) {
27542
27859
  try {
@@ -27563,7 +27880,7 @@ function readKrakenRadio(cwd, sessionId, limit = 50) {
27563
27880
  try {
27564
27881
  const file2 = radioPath(cwd, sessionId);
27565
27882
  if (!existsSync13(file2)) return [];
27566
- const lines = readFileSync12(file2, "utf8").split(/\r?\n/).filter(Boolean);
27883
+ const lines = readFileSync13(file2, "utf8").split(/\r?\n/).filter(Boolean);
27567
27884
  const slice = lines.slice(-Math.max(1, limit));
27568
27885
  const out = [];
27569
27886
  for (const line of slice) {
@@ -27586,7 +27903,7 @@ function formatKrakenRadioStatus(cwd, sessionId, limit = 12) {
27586
27903
  const flag = e.ok === false ? "\u2717" : e.ok === true ? "\u2713" : "\xB7";
27587
27904
  const ms = e.durationMs != null ? ` ${e.durationMs}ms` : "";
27588
27905
  const model = e.model ? ` [${e.model}]` : "";
27589
- const wt = e.worktree ? ` wt=${path17.basename(e.worktree)}` : "";
27906
+ const wt = e.worktree ? ` wt=${path18.basename(e.worktree)}` : "";
27590
27907
  const detail = e.detail ? ` \u2014 ${e.detail.slice(0, 120)}` : "";
27591
27908
  return `${flag} ${e.ts.slice(11, 19)} ${e.kind} ${e.agent} "${e.description}"${model}${wt}${ms}${detail}`;
27592
27909
  });
@@ -27601,7 +27918,7 @@ var init_krakenRadio = __esm({
27601
27918
  // src/cli/tools/krakenWorktree.ts
27602
27919
  import { execFile as execFile2 } from "node:child_process";
27603
27920
  import { existsSync as existsSync14, mkdirSync as mkdirSync8, rmSync } from "node:fs";
27604
- import path18 from "node:path";
27921
+ import path19 from "node:path";
27605
27922
  import { promisify } from "node:util";
27606
27923
  import { randomBytes as randomBytes2 } from "node:crypto";
27607
27924
  function isKrakenWorktreeEnabled(env = process.env) {
@@ -27649,8 +27966,8 @@ async function createKrakenWorktree(cwd, label) {
27649
27966
  const id = `${Date.now().toString(36)}-${randomBytes2(3).toString("hex")}`;
27650
27967
  const slug = (label ?? "task").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "task";
27651
27968
  const branch = `kraken/${slug}-${id}`;
27652
- const wtRoot = path18.join(repoRoot, ".zelari", "worktrees");
27653
- const wtPath = path18.join(wtRoot, `kraken-${id}`);
27969
+ const wtRoot = path19.join(repoRoot, ".zelari", "worktrees");
27970
+ const wtPath = path19.join(wtRoot, `kraken-${id}`);
27654
27971
  try {
27655
27972
  if (!existsSync14(wtRoot)) mkdirSync8(wtRoot, { recursive: true });
27656
27973
  } catch {
@@ -28243,7 +28560,7 @@ var init_askUser = __esm({
28243
28560
  });
28244
28561
 
28245
28562
  // src/cli/skillsMd.ts
28246
- import { existsSync as existsSync15, readdirSync as readdirSync2, readFileSync as readFileSync13 } from "node:fs";
28563
+ import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync14 } from "node:fs";
28247
28564
  import { join as join12 } from "node:path";
28248
28565
  import { homedir as homedir4 } from "node:os";
28249
28566
  function skillMdSearchDirs(projectRoot = process.cwd()) {
@@ -28312,7 +28629,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
28312
28629
  if (!existsSync15(dir)) continue;
28313
28630
  let entries;
28314
28631
  try {
28315
- entries = readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
28632
+ entries = readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
28316
28633
  } catch {
28317
28634
  continue;
28318
28635
  }
@@ -28320,7 +28637,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
28320
28637
  const skillPath = join12(dir, entry, "SKILL.md");
28321
28638
  if (!existsSync15(skillPath)) continue;
28322
28639
  try {
28323
- const parsed = parseSkillMd(readFileSync13(skillPath, "utf8"), skillPath);
28640
+ const parsed = parseSkillMd(readFileSync14(skillPath, "utf8"), skillPath);
28324
28641
  if (!parsed) {
28325
28642
  summary.skipped.push({ path: skillPath, reason: "missing/invalid frontmatter (name, description) or empty body" });
28326
28643
  continue;
@@ -28725,9 +29042,9 @@ var init_client = __esm({
28725
29042
  });
28726
29043
 
28727
29044
  // src/cli/lsp/servers.ts
28728
- import path19 from "node:path";
29045
+ import path20 from "node:path";
28729
29046
  function languageIdForFile(file2) {
28730
- const ext = path19.extname(file2).toLowerCase();
29047
+ const ext = path20.extname(file2).toLowerCase();
28731
29048
  const map2 = {
28732
29049
  ".ts": "typescript",
28733
29050
  ".tsx": "typescriptreact",
@@ -28742,7 +29059,7 @@ function languageIdForFile(file2) {
28742
29059
  return map2[ext] ?? "plaintext";
28743
29060
  }
28744
29061
  function serverForFile(file2, servers = LSP_SERVERS) {
28745
- const ext = path19.extname(file2).toLowerCase();
29062
+ const ext = path20.extname(file2).toLowerCase();
28746
29063
  return servers.find((s) => s.extensions.includes(ext)) ?? null;
28747
29064
  }
28748
29065
  function resolveServerCommand(file2, cwd, servers = LSP_SERVERS) {
@@ -28791,8 +29108,8 @@ var init_servers = __esm({
28791
29108
  });
28792
29109
 
28793
29110
  // src/cli/lsp/manager.ts
28794
- import { spawn as spawn3 } from "node:child_process";
28795
- import { readFileSync as readFileSync14 } from "node:fs";
29111
+ import { spawn as spawn4 } from "node:child_process";
29112
+ import { readFileSync as readFileSync15 } from "node:fs";
28796
29113
  function processTransport(child) {
28797
29114
  return {
28798
29115
  send: (data) => {
@@ -28927,7 +29244,7 @@ var init_manager = __esm({
28927
29244
  // languages already flagged as unavailable
28928
29245
  constructor(options = {}) {
28929
29246
  this.cwd = options.cwd ?? process.cwd();
28930
- this.spawnImpl = options.spawnImpl ?? spawn3;
29247
+ this.spawnImpl = options.spawnImpl ?? spawn4;
28931
29248
  this.timeoutMs = options.timeoutMs ?? 15e3;
28932
29249
  this.onWarn = options.onWarn ?? ((m) => console.error(m));
28933
29250
  }
@@ -28995,7 +29312,7 @@ var init_manager = __esm({
28995
29312
  const uri = pathToUri(file2);
28996
29313
  let text;
28997
29314
  try {
28998
- text = readFileSync14(file2, "utf8");
29315
+ text = readFileSync15(file2, "utf8");
28999
29316
  } catch {
29000
29317
  text = "";
29001
29318
  }
@@ -29110,9 +29427,9 @@ var init_manager = __esm({
29110
29427
 
29111
29428
  // src/cli/ast/engine.ts
29112
29429
  import { readFile } from "node:fs/promises";
29113
- import path20 from "node:path";
29430
+ import path21 from "node:path";
29114
29431
  function isAstSupported(file2) {
29115
- return TS_EXTENSIONS.has(path20.extname(file2).toLowerCase());
29432
+ return TS_EXTENSIONS.has(path21.extname(file2).toLowerCase());
29116
29433
  }
29117
29434
  function loadTs() {
29118
29435
  if (!tsPromise) {
@@ -29132,7 +29449,7 @@ async function parseFileSymbols(file2) {
29132
29449
  }
29133
29450
  let source;
29134
29451
  try {
29135
- source = ts.createSourceFile(path20.basename(file2), text, ts.ScriptTarget.Latest, true);
29452
+ source = ts.createSourceFile(path21.basename(file2), text, ts.ScriptTarget.Latest, true);
29136
29453
  } catch {
29137
29454
  return [];
29138
29455
  }
@@ -29317,13 +29634,13 @@ var init_store = __esm({
29317
29634
  });
29318
29635
 
29319
29636
  // src/cli/semantic/index.ts
29320
- import { promises as fs12, existsSync as existsSync16, readFileSync as readFileSync15 } from "node:fs";
29637
+ import { promises as fs12, existsSync as existsSync16, readFileSync as readFileSync16 } from "node:fs";
29321
29638
  import { homedir as homedir5 } from "node:os";
29322
- import path21 from "node:path";
29639
+ import path22 from "node:path";
29323
29640
  import { createHash as createHash2 } from "node:crypto";
29324
29641
  function getIndexPath(root) {
29325
- const hash3 = createHash2("sha1").update(path21.resolve(root)).digest("hex").slice(0, 16);
29326
- return process.env.ZELARI_SEMANTIC_FILE ?? path21.join(homedir5(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
29642
+ const hash3 = createHash2("sha1").update(path22.resolve(root)).digest("hex").slice(0, 16);
29643
+ return process.env.ZELARI_SEMANTIC_FILE ?? path22.join(homedir5(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
29327
29644
  }
29328
29645
  async function collectSourceFiles(root, maxFiles = 1500) {
29329
29646
  const out = [];
@@ -29341,11 +29658,11 @@ async function collectSourceFiles(root, maxFiles = 1500) {
29341
29658
  if (entry.isDirectory() && IGNORE_DIRS.has(entry.name)) continue;
29342
29659
  if (entry.isDirectory()) continue;
29343
29660
  }
29344
- const full = path21.join(dir, entry.name);
29661
+ const full = path22.join(dir, entry.name);
29345
29662
  if (entry.isDirectory()) {
29346
29663
  if (IGNORE_DIRS.has(entry.name)) continue;
29347
29664
  await walk2(full);
29348
- } else if (SOURCE_EXTENSIONS.has(path21.extname(entry.name).toLowerCase())) {
29665
+ } else if (SOURCE_EXTENSIONS.has(path22.extname(entry.name).toLowerCase())) {
29349
29666
  out.push(full);
29350
29667
  }
29351
29668
  }
@@ -29394,7 +29711,7 @@ async function buildIndex(files, embed, options) {
29394
29711
  }
29395
29712
  async function saveIndex(root, data) {
29396
29713
  const file2 = getIndexPath(root);
29397
- await fs12.mkdir(path21.dirname(file2), { recursive: true });
29714
+ await fs12.mkdir(path22.dirname(file2), { recursive: true });
29398
29715
  const tmp = `${file2}.tmp-${process.pid}`;
29399
29716
  await fs12.writeFile(tmp, JSON.stringify(data), "utf8");
29400
29717
  await fs12.rename(tmp, file2);
@@ -29403,7 +29720,7 @@ function loadIndex(root) {
29403
29720
  const file2 = getIndexPath(root);
29404
29721
  if (!existsSync16(file2)) return null;
29405
29722
  try {
29406
- const parsed = JSON.parse(readFileSync15(file2, "utf8"));
29723
+ const parsed = JSON.parse(readFileSync16(file2, "utf8"));
29407
29724
  if (parsed && Array.isArray(parsed.chunks)) return parsed;
29408
29725
  } catch {
29409
29726
  }
@@ -29546,7 +29863,7 @@ var init_provider = __esm({
29546
29863
  });
29547
29864
 
29548
29865
  // src/cli/semantic/tools.ts
29549
- import path22 from "node:path";
29866
+ import path23 from "node:path";
29550
29867
  function createSemanticTool(deps) {
29551
29868
  const buildEmbedFn = deps.buildEmbedFn ?? buildProviderEmbedFn;
29552
29869
  return {
@@ -29573,7 +29890,7 @@ function createSemanticTool(deps) {
29573
29890
  return typedOk({
29574
29891
  count: res.hits.length,
29575
29892
  results: res.hits.map((h) => ({
29576
- location: `${path22.relative(deps.root, h.file) || h.file}:${h.startLine}-${h.endLine}`,
29893
+ location: `${path23.relative(deps.root, h.file) || h.file}:${h.startLine}-${h.endLine}`,
29577
29894
  score: Number(h.score.toFixed(3)),
29578
29895
  preview: h.text.length > 400 ? `${h.text.slice(0, 400)}\u2026` : h.text
29579
29896
  }))
@@ -29593,7 +29910,7 @@ var init_tools4 = __esm({
29593
29910
 
29594
29911
  // src/cli/browser/driver.ts
29595
29912
  import { createRequire } from "node:module";
29596
- import path23 from "node:path";
29913
+ import path24 from "node:path";
29597
29914
  import { pathToFileURL } from "node:url";
29598
29915
  function asPlaywright(mod) {
29599
29916
  if (!mod || typeof mod !== "object") return null;
@@ -29604,10 +29921,10 @@ function asPlaywright(mod) {
29604
29921
  return null;
29605
29922
  }
29606
29923
  async function loadPlaywright(cwd) {
29607
- const base = cwd && cwd.length > 0 ? path23.resolve(cwd) : void 0;
29924
+ const base = cwd && cwd.length > 0 ? path24.resolve(cwd) : void 0;
29608
29925
  if (base) {
29609
29926
  try {
29610
- const req = createRequire(path23.join(base, "package.json"));
29927
+ const req = createRequire(path24.join(base, "package.json"));
29611
29928
  const resolved = req.resolve("playwright");
29612
29929
  const mod = await import(pathToFileURL(resolved).href);
29613
29930
  const pw = asPlaywright(mod);
@@ -29822,7 +30139,7 @@ var init_driver = __esm({
29822
30139
  });
29823
30140
 
29824
30141
  // src/cli/browser/tools.ts
29825
- import path24 from "node:path";
30142
+ import path25 from "node:path";
29826
30143
  import os7 from "node:os";
29827
30144
  function createBrowserTool(deps = {}) {
29828
30145
  return {
@@ -29841,7 +30158,7 @@ function createBrowserTool(deps = {}) {
29841
30158
  execute: async (args, ctx) => {
29842
30159
  const a = args;
29843
30160
  const dir = deps.screenshotDir ?? os7.tmpdir();
29844
- const screenshotPath = a.screenshot === false ? void 0 : path24.join(dir, `zelari-browser-${Date.now()}.png`);
30161
+ const screenshotPath = a.screenshot === false ? void 0 : path25.join(dir, `zelari-browser-${Date.now()}.png`);
29845
30162
  const result = await runBrowserCheck(
29846
30163
  {
29847
30164
  url: a.url,
@@ -29935,12 +30252,12 @@ import {
29935
30252
  chmodSync,
29936
30253
  existsSync as existsSync17,
29937
30254
  mkdirSync as mkdirSync9,
29938
- readFileSync as readFileSync16,
30255
+ readFileSync as readFileSync17,
29939
30256
  writeFileSync as writeFileSync11
29940
30257
  } from "node:fs";
29941
30258
  import { dirname as dirname2, join as join13 } from "node:path";
29942
30259
  import { homedir as homedir6 } from "node:os";
29943
- import { spawn as spawn4 } from "node:child_process";
30260
+ import { spawn as spawn5 } from "node:child_process";
29944
30261
  function getSshTargetsPath() {
29945
30262
  return join13(homedir6(), ".zelari-code", "ssh-targets.json");
29946
30263
  }
@@ -29953,21 +30270,21 @@ function normalizeAuth(auth) {
29953
30270
  return "agent";
29954
30271
  }
29955
30272
  function readSecrets() {
29956
- const path48 = getSshSecretsPath();
29957
- if (!existsSync17(path48)) return {};
30273
+ const path51 = getSshSecretsPath();
30274
+ if (!existsSync17(path51)) return {};
29958
30275
  try {
29959
- return JSON.parse(readFileSync16(path48, "utf8"));
30276
+ return JSON.parse(readFileSync17(path51, "utf8"));
29960
30277
  } catch {
29961
30278
  return {};
29962
30279
  }
29963
30280
  }
29964
30281
  function writeSecrets(data) {
29965
- const path48 = getSshSecretsPath();
29966
- mkdirSync9(dirname2(path48), { recursive: true });
29967
- writeFileSync11(path48, `${JSON.stringify(data, null, 2)}
30282
+ const path51 = getSshSecretsPath();
30283
+ mkdirSync9(dirname2(path51), { recursive: true });
30284
+ writeFileSync11(path51, `${JSON.stringify(data, null, 2)}
29968
30285
  `, "utf8");
29969
30286
  try {
29970
- chmodSync(path48, 384);
30287
+ chmodSync(path51, 384);
29971
30288
  } catch {
29972
30289
  }
29973
30290
  }
@@ -29996,10 +30313,10 @@ function deleteSshPassword(id) {
29996
30313
  writeSecrets({ passwords });
29997
30314
  }
29998
30315
  function readStore2() {
29999
- const path48 = getSshTargetsPath();
30000
- if (!existsSync17(path48)) return [];
30316
+ const path51 = getSshTargetsPath();
30317
+ if (!existsSync17(path51)) return [];
30001
30318
  try {
30002
- const parsed = JSON.parse(readFileSync16(path48, "utf8"));
30319
+ const parsed = JSON.parse(readFileSync17(path51, "utf8"));
30003
30320
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
30004
30321
  return list.filter(
30005
30322
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -30014,11 +30331,11 @@ function readStore2() {
30014
30331
  }
30015
30332
  }
30016
30333
  function writeStore2(targets) {
30017
- const path48 = getSshTargetsPath();
30018
- mkdirSync9(dirname2(path48), { recursive: true });
30334
+ const path51 = getSshTargetsPath();
30335
+ mkdirSync9(dirname2(path51), { recursive: true });
30019
30336
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
30020
30337
  writeFileSync11(
30021
- path48,
30338
+ path51,
30022
30339
  `${JSON.stringify({ targets: clean }, null, 2)}
30023
30340
  `,
30024
30341
  "utf8"
@@ -30170,7 +30487,7 @@ function runSsh(target, remoteCommand, timeoutMs = 6e4) {
30170
30487
  if (!env.DISPLAY) env.DISPLAY = "1";
30171
30488
  env.ZELARI_SSH_ASKPASS_PASS = pass;
30172
30489
  }
30173
- const child = spawn4("ssh", args, {
30490
+ const child = spawn5("ssh", args, {
30174
30491
  windowsHide: true,
30175
30492
  env,
30176
30493
  stdio: ["ignore", "pipe", "pipe"]
@@ -30213,7 +30530,7 @@ function readSshPublicKey(keyOrPubPath) {
30213
30530
  for (const p3 of candidates) {
30214
30531
  if (!existsSync17(p3)) continue;
30215
30532
  try {
30216
- const content = readFileSync16(p3, "utf8").trim();
30533
+ const content = readFileSync17(p3, "utf8").trim();
30217
30534
  if (!content) continue;
30218
30535
  if (/BEGIN .*PRIVATE KEY/i.test(content)) {
30219
30536
  return {
@@ -30264,11 +30581,11 @@ function formatSshTargetsForPrompt() {
30264
30581
  ];
30265
30582
  for (const t of targets) {
30266
30583
  const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
30267
- const path48 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
30584
+ const path51 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
30268
30585
  const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
30269
30586
  const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
30270
30587
  lines.push(
30271
- `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path48}${tags}${allow}`
30588
+ `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path51}${tags}${allow}`
30272
30589
  );
30273
30590
  }
30274
30591
  return lines.join("\n");
@@ -30395,10 +30712,10 @@ var init_tools6 = __esm({
30395
30712
 
30396
30713
  // src/cli/workspace/worldModel.ts
30397
30714
  import { promises as fs13 } from "node:fs";
30398
- import path25 from "node:path";
30399
- import { spawn as spawn5 } from "node:child_process";
30715
+ import path26 from "node:path";
30716
+ import { spawn as spawn6 } from "node:child_process";
30400
30717
  function worldDir(cwd) {
30401
- return path25.join(cwd, WORLD_DIR_NAME);
30718
+ return path26.join(cwd, WORLD_DIR_NAME);
30402
30719
  }
30403
30720
  async function ensureWorldDir(cwd) {
30404
30721
  const dir = worldDir(cwd);
@@ -30408,10 +30725,10 @@ async function ensureWorldDir(cwd) {
30408
30725
  async function appendTimeline(cwd, entry) {
30409
30726
  const dir = await ensureWorldDir(cwd);
30410
30727
  const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }) + "\n";
30411
- await fs13.appendFile(path25.join(dir, TIMELINE_FILE), line, "utf8");
30728
+ await fs13.appendFile(path26.join(dir, TIMELINE_FILE), line, "utf8");
30412
30729
  }
30413
30730
  async function readChecks(cwd) {
30414
- const p3 = path25.join(worldDir(cwd), CHECKS_FILE);
30731
+ const p3 = path26.join(worldDir(cwd), CHECKS_FILE);
30415
30732
  try {
30416
30733
  const raw = await fs13.readFile(p3, "utf8");
30417
30734
  const parsed = JSON.parse(raw);
@@ -30423,7 +30740,7 @@ async function readChecks(cwd) {
30423
30740
  function runShell(command, cwd, timeoutMs, signal) {
30424
30741
  return new Promise((resolve3) => {
30425
30742
  const isWin = process.platform === "win32";
30426
- const child = spawn5(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
30743
+ const child = spawn6(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
30427
30744
  cwd,
30428
30745
  env: process.env,
30429
30746
  windowsHide: true,
@@ -30486,8 +30803,8 @@ function runShell(command, cwd, timeoutMs, signal) {
30486
30803
  });
30487
30804
  }
30488
30805
  async function runBacktest(cwd, signal) {
30489
- const checksPath = path25.join(worldDir(cwd), CHECKS_FILE);
30490
- const hypothesisPath = path25.join(worldDir(cwd), HYPOTHESIS_FILE);
30806
+ const checksPath = path26.join(worldDir(cwd), CHECKS_FILE);
30807
+ const hypothesisPath = path26.join(worldDir(cwd), HYPOTHESIS_FILE);
30491
30808
  const checks = await readChecks(cwd);
30492
30809
  if (checks.length === 0) {
30493
30810
  return {
@@ -30556,7 +30873,7 @@ var init_worldModel = __esm({
30556
30873
  "use strict";
30557
30874
  init_zod();
30558
30875
  init_toolTypes();
30559
- WORLD_DIR_NAME = path25.join(".zelari", "world");
30876
+ WORLD_DIR_NAME = path26.join(".zelari", "world");
30560
30877
  HYPOTHESIS_FILE = "hypothesis.md";
30561
30878
  CHECKS_FILE = "checks.json";
30562
30879
  TIMELINE_FILE = "timeline.jsonl";
@@ -30573,7 +30890,7 @@ var init_worldModel = __esm({
30573
30890
  execute: async (args, ctx) => {
30574
30891
  try {
30575
30892
  const dir = await ensureWorldDir(ctx.cwd);
30576
- const file2 = path25.join(dir, HYPOTHESIS_FILE);
30893
+ const file2 = path26.join(dir, HYPOTHESIS_FILE);
30577
30894
  if (args.append) {
30578
30895
  const block = `
30579
30896
 
@@ -30612,7 +30929,7 @@ ${args.content}
30612
30929
  execute: async (args, ctx) => {
30613
30930
  try {
30614
30931
  const dir = await ensureWorldDir(ctx.cwd);
30615
- const file2 = path25.join(dir, CHECKS_FILE);
30932
+ const file2 = path26.join(dir, CHECKS_FILE);
30616
30933
  const body = { checks: args.checks };
30617
30934
  await fs13.writeFile(file2, JSON.stringify(body, null, 2) + "\n", "utf8");
30618
30935
  await appendTimeline(ctx.cwd, { kind: "checks_set", count: args.checks.length });
@@ -30651,8 +30968,8 @@ ${args.content}
30651
30968
  stdoutPreview: "(dryRun)",
30652
30969
  mismatch: "dryRun"
30653
30970
  })),
30654
- hypothesisPath: path25.join(worldDir(ctx.cwd), HYPOTHESIS_FILE),
30655
- checksPath: path25.join(worldDir(ctx.cwd), CHECKS_FILE)
30971
+ hypothesisPath: path26.join(worldDir(ctx.cwd), HYPOTHESIS_FILE),
30972
+ checksPath: path26.join(worldDir(ctx.cwd), CHECKS_FILE)
30656
30973
  });
30657
30974
  }
30658
30975
  const result = await runBacktest(ctx.cwd, ctx.signal);
@@ -30676,7 +30993,7 @@ ${args.content}
30676
30993
  execute: async (args, ctx) => {
30677
30994
  try {
30678
30995
  const dir = await ensureWorldDir(ctx.cwd);
30679
- const file2 = path25.join(dir, TIMELINE_FILE);
30996
+ const file2 = path26.join(dir, TIMELINE_FILE);
30680
30997
  await appendTimeline(ctx.cwd, {
30681
30998
  kind: args.kind,
30682
30999
  summary: args.summary,
@@ -30772,6 +31089,138 @@ var init_toolPermissions = __esm({
30772
31089
  }
30773
31090
  });
30774
31091
 
31092
+ // src/cli/safety/folderTrust.ts
31093
+ var folderTrust_exports = {};
31094
+ __export(folderTrust_exports, {
31095
+ _setTrustStorePathForTests: () => _setTrustStorePathForTests,
31096
+ getTrustStorePath: () => getTrustStorePath,
31097
+ hasTrustStore: () => hasTrustStore,
31098
+ isFolderTrusted: () => isFolderTrusted,
31099
+ listTrustedFolders: () => listTrustedFolders,
31100
+ trustFolder: () => trustFolder,
31101
+ untrustFolder: () => untrustFolder
31102
+ });
31103
+ import { homedir as homedir7 } from "node:os";
31104
+ import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "node:fs";
31105
+ import path27 from "node:path";
31106
+ function trustStorePath() {
31107
+ return _overrideStorePath ?? path27.join(homedir7(), ".zelari-code", "trust.json");
31108
+ }
31109
+ function normalize(p3) {
31110
+ const resolved = path27.resolve(p3);
31111
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
31112
+ }
31113
+ function readStore3() {
31114
+ try {
31115
+ const raw = readFileSync18(trustStorePath(), "utf8");
31116
+ const parsed = JSON.parse(raw);
31117
+ if (parsed && Array.isArray(parsed.folders)) return parsed;
31118
+ return DEFAULT_STORE;
31119
+ } catch {
31120
+ return DEFAULT_STORE;
31121
+ }
31122
+ }
31123
+ function writeStore3(store2) {
31124
+ const p3 = trustStorePath();
31125
+ try {
31126
+ mkdirSync10(path27.dirname(p3), { recursive: true });
31127
+ writeFileSync12(p3, JSON.stringify(store2, null, 2), "utf8");
31128
+ } catch (err) {
31129
+ throw new Error(
31130
+ `failed to persist trust store ${p3}: ${err instanceof Error ? err.message : String(err)}`
31131
+ );
31132
+ }
31133
+ }
31134
+ function envTrustedFolder() {
31135
+ const raw = process.env.ZELARI_FOLDER_TRUST?.trim();
31136
+ if (!raw) return null;
31137
+ const v = raw.toLowerCase();
31138
+ if (v === "1" || v === "true" || v === "yes" || v === "all") return "all";
31139
+ if (v === "0" || v === "false" || v === "no" || v === "none") return "none";
31140
+ return raw;
31141
+ }
31142
+ function isFolderTrusted(folderPath) {
31143
+ const env = envTrustedFolder();
31144
+ if (env === "all") return true;
31145
+ if (env === "none") return false;
31146
+ if (env) return normalize(env) === normalize(folderPath);
31147
+ const target = normalize(folderPath);
31148
+ return readStore3().folders.some((f) => normalize(f.path) === target);
31149
+ }
31150
+ function trustFolder(folderPath) {
31151
+ const store2 = readStore3();
31152
+ const normalized = path27.resolve(folderPath);
31153
+ if (!store2.folders.some((f) => normalize(f.path) === normalize(normalized))) {
31154
+ store2.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
31155
+ writeStore3(store2);
31156
+ }
31157
+ return { ok: true, path: normalized };
31158
+ }
31159
+ function untrustFolder(folderPath) {
31160
+ const store2 = readStore3();
31161
+ const target = normalize(folderPath);
31162
+ const before = store2.folders.length;
31163
+ store2.folders = store2.folders.filter((f) => normalize(f.path) !== target);
31164
+ if (store2.folders.length === before) return { ok: true, removed: false };
31165
+ writeStore3(store2);
31166
+ return { ok: true, removed: true };
31167
+ }
31168
+ function listTrustedFolders() {
31169
+ const env = envTrustedFolder();
31170
+ if (env === "all") {
31171
+ return [{ path: "<all>", trustedAt: "env" }];
31172
+ }
31173
+ if (env) {
31174
+ return [{ path: env, trustedAt: "env" }];
31175
+ }
31176
+ return readStore3().folders;
31177
+ }
31178
+ function getTrustStorePath() {
31179
+ return trustStorePath();
31180
+ }
31181
+ function hasTrustStore() {
31182
+ return existsSync18(trustStorePath());
31183
+ }
31184
+ function _setTrustStorePathForTests(p3) {
31185
+ _overrideStorePath = p3;
31186
+ }
31187
+ var DEFAULT_STORE, _overrideStorePath;
31188
+ var init_folderTrust = __esm({
31189
+ "src/cli/safety/folderTrust.ts"() {
31190
+ "use strict";
31191
+ DEFAULT_STORE = { folders: [] };
31192
+ _overrideStorePath = null;
31193
+ }
31194
+ });
31195
+
31196
+ // src/cli/safety/lifecycleHooks.ts
31197
+ import { homedir as homedir8 } from "node:os";
31198
+ import { join as join14 } from "node:path";
31199
+ function globalHooksDir() {
31200
+ return join14(homedir8(), ".zelari-code", "hooks");
31201
+ }
31202
+ function projectHooksDir(projectRoot) {
31203
+ return join14(projectRoot, ".zelari", "hooks");
31204
+ }
31205
+ function createDefaultLifecycleHooks(projectRoot = process.cwd()) {
31206
+ const runner = new LifecycleHookRunner();
31207
+ const dirs = [globalHooksDir()];
31208
+ if (isFolderTrusted(projectRoot)) {
31209
+ dirs.push(projectHooksDir(projectRoot));
31210
+ }
31211
+ for (const dir of dirs) {
31212
+ runner.loadDir(dir);
31213
+ }
31214
+ return runner;
31215
+ }
31216
+ var init_lifecycleHooks = __esm({
31217
+ "src/cli/safety/lifecycleHooks.ts"() {
31218
+ "use strict";
31219
+ init_harness();
31220
+ init_folderTrust();
31221
+ }
31222
+ });
31223
+
30775
31224
  // src/cli/tools/krakenModel.ts
30776
31225
  var krakenModel_exports = {};
30777
31226
  __export(krakenModel_exports, {
@@ -30887,6 +31336,9 @@ function createBuiltinToolRegistry(options = {}) {
30887
31336
  const safeWebSearch = wrapWithAudit(webSearchTool, audit, sessionId);
30888
31337
  const registry4 = new ToolRegistry();
30889
31338
  const profile = options.profile ?? "full";
31339
+ const isParent = !options.profile || options.profile === "full";
31340
+ const hooks = isParent ? options.lifecycleHooks ?? createDefaultLifecycleHooks(root) : null;
31341
+ if (hooks) registry4.setLifecycleHooks(hooks);
30890
31342
  const readOnly = options.readOnly === true || options.planMode === true || profile === "explore";
30891
31343
  const verifyMode = profile === "verify";
30892
31344
  const allowMutators = !readOnly && !verifyMode;
@@ -31291,6 +31743,7 @@ var init_toolRegistry = __esm({
31291
31743
  init_worldModel();
31292
31744
  init_openai_compatible();
31293
31745
  init_toolPermissions();
31746
+ init_lifecycleHooks();
31294
31747
  init_toolTypes();
31295
31748
  init_skills2();
31296
31749
  HARNESS_BUILTIN_NAMES = /* @__PURE__ */ new Set([
@@ -31311,12 +31764,12 @@ var init_toolRegistry = __esm({
31311
31764
  // src/cli/state/fileStateStore.ts
31312
31765
  import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
31313
31766
  import { promises as fs14 } from "node:fs";
31314
- import * as path26 from "node:path";
31767
+ import * as path28 from "node:path";
31315
31768
  function shortId() {
31316
31769
  return randomUUID2().replace(/-/g, "").slice(0, 12);
31317
31770
  }
31318
31771
  async function writeJsonAtomic(filePath, data) {
31319
- await fs14.mkdir(path26.dirname(filePath), { recursive: true });
31772
+ await fs14.mkdir(path28.dirname(filePath), { recursive: true });
31320
31773
  const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
31321
31774
  await fs14.writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
31322
31775
  await fs14.rename(tmp, filePath);
@@ -31377,11 +31830,11 @@ var init_fileStateStore = __esm({
31377
31830
  indexPath = "";
31378
31831
  async init(projectRoot) {
31379
31832
  this.root = projectRoot;
31380
- this.stateDir = path26.join(projectRoot, ".zelari", "state");
31381
- this.commitsDir = path26.join(this.stateDir, "commits");
31382
- this.artifactsDir = path26.join(this.stateDir, "artifacts");
31383
- this.headPath = path26.join(this.stateDir, "HEAD.json");
31384
- this.indexPath = path26.join(this.stateDir, "index.jsonl");
31833
+ this.stateDir = path28.join(projectRoot, ".zelari", "state");
31834
+ this.commitsDir = path28.join(this.stateDir, "commits");
31835
+ this.artifactsDir = path28.join(this.stateDir, "artifacts");
31836
+ this.headPath = path28.join(this.stateDir, "HEAD.json");
31837
+ this.indexPath = path28.join(this.stateDir, "index.jsonl");
31385
31838
  await fs14.mkdir(this.commitsDir, { recursive: true });
31386
31839
  await fs14.mkdir(this.artifactsDir, { recursive: true });
31387
31840
  }
@@ -31394,13 +31847,13 @@ var init_fileStateStore = __esm({
31394
31847
  const discoveries = input.discoveries ?? [];
31395
31848
  const parent = await this.head();
31396
31849
  const id = shortId();
31397
- const artifactRel = path26.join("artifacts", id);
31398
- const artifactAbs = path26.join(this.artifactsDir, id);
31850
+ const artifactRel = path28.join("artifacts", id);
31851
+ const artifactAbs = path28.join(this.artifactsDir, id);
31399
31852
  await fs14.mkdir(artifactAbs, { recursive: true });
31400
31853
  const summary = defaultSummary(input, discoveries);
31401
- await fs14.writeFile(path26.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
31402
- await writeJsonAtomic(path26.join(artifactAbs, "discoveries.json"), discoveries);
31403
- await writeJsonAtomic(path26.join(artifactAbs, "verification.json"), input.verification);
31854
+ await fs14.writeFile(path28.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
31855
+ await writeJsonAtomic(path28.join(artifactAbs, "discoveries.json"), discoveries);
31856
+ await writeJsonAtomic(path28.join(artifactAbs, "verification.json"), input.verification);
31404
31857
  const meta3 = {
31405
31858
  id,
31406
31859
  parentId: parent?.id ?? null,
@@ -31412,14 +31865,14 @@ var init_fileStateStore = __esm({
31412
31865
  workspaceCheckpointId: input.workspaceCheckpointId,
31413
31866
  verification: {
31414
31867
  ...input.verification,
31415
- reportPath: input.verification.reportPath ?? path26.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
31868
+ reportPath: input.verification.reportPath ?? path28.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
31416
31869
  },
31417
31870
  changedPaths: input.changedPaths ?? [],
31418
31871
  stablePromptHash: input.stablePromptHash,
31419
31872
  discoveryCount: discoveries.length,
31420
31873
  artifactDir: artifactRel.replace(/\\/g, "/")
31421
31874
  };
31422
- await writeJsonAtomic(path26.join(this.commitsDir, `${id}.json`), meta3);
31875
+ await writeJsonAtomic(path28.join(this.commitsDir, `${id}.json`), meta3);
31423
31876
  await writeJsonAtomic(this.headPath, { id, updatedAt: meta3.createdAt });
31424
31877
  await fs14.appendFile(this.indexPath, JSON.stringify({ id, createdAt: meta3.createdAt, label: meta3.label }) + "\n", "utf8");
31425
31878
  return stripStored(meta3);
@@ -31430,7 +31883,7 @@ var init_fileStateStore = __esm({
31430
31883
  return this.get(head.id);
31431
31884
  }
31432
31885
  async get(id) {
31433
- const stored = await readJsonFile(path26.join(this.commitsDir, `${id}.json`));
31886
+ const stored = await readJsonFile(path28.join(this.commitsDir, `${id}.json`));
31434
31887
  return stored ? stripStored(stored) : null;
31435
31888
  }
31436
31889
  async list(limit = 20) {
@@ -31469,9 +31922,9 @@ var init_fileStateStore = __esm({
31469
31922
  async loadDiscoveries(id) {
31470
31923
  const meta3 = id ? await this.get(id) : await this.head();
31471
31924
  if (!meta3) return [];
31472
- const stored = await readJsonFile(path26.join(this.commitsDir, `${meta3.id}.json`));
31925
+ const stored = await readJsonFile(path28.join(this.commitsDir, `${meta3.id}.json`));
31473
31926
  if (!stored?.artifactDir) return [];
31474
- const discPath = path26.join(this.stateDir, stored.artifactDir, "discoveries.json");
31927
+ const discPath = path28.join(this.stateDir, stored.artifactDir, "discoveries.json");
31475
31928
  return await readJsonFile(discPath) ?? [];
31476
31929
  }
31477
31930
  async materializeContext(id, maxChars = DEFAULT_MATERIALIZE_CHARS) {
@@ -31917,8 +32370,8 @@ __export(conversationContext_exports, {
31917
32370
  setHistory: () => setHistory,
31918
32371
  setLastClarification: () => setLastClarification
31919
32372
  });
31920
- import { existsSync as existsSync18 } from "node:fs";
31921
- import { join as join15 } from "node:path";
32373
+ import { existsSync as existsSync19 } from "node:fs";
32374
+ import { join as join16 } from "node:path";
31922
32375
  function getHistory() {
31923
32376
  return history;
31924
32377
  }
@@ -31926,7 +32379,7 @@ function setHistory(messages) {
31926
32379
  history = [...messages];
31927
32380
  }
31928
32381
  function compactInPlace(cwd = process.cwd()) {
31929
- const durableStatePresent = existsSync18(join15(cwd, ".zelari", "state", "HEAD.json"));
32382
+ const durableStatePresent = existsSync19(join16(cwd, ".zelari", "state", "HEAD.json"));
31930
32383
  history = compactHistory(history, { durableStatePresent });
31931
32384
  }
31932
32385
  function appendMessages(msgs) {
@@ -32315,7 +32768,7 @@ var claudeProvider_exports = {};
32315
32768
  __export(claudeProvider_exports, {
32316
32769
  createLocalCliProvider: () => createLocalCliProvider
32317
32770
  });
32318
- import { spawn as spawn6 } from "node:child_process";
32771
+ import { spawn as spawn7 } from "node:child_process";
32319
32772
  function waitForExit(child, timeoutMs = 2e3) {
32320
32773
  return new Promise((resolve3) => {
32321
32774
  if (child.exitCode != null) return resolve3(child.exitCode);
@@ -32344,7 +32797,7 @@ function createLocalCliProvider(opts = {}) {
32344
32797
  );
32345
32798
  }
32346
32799
  }
32347
- const spawnFn = opts.spawnFn ?? spawn6;
32800
+ const spawnFn = opts.spawnFn ?? spawn7;
32348
32801
  let child;
32349
32802
  try {
32350
32803
  child = spawnFn(cli, args, {
@@ -32439,14 +32892,14 @@ var init_claudeProvider = __esm({
32439
32892
  });
32440
32893
 
32441
32894
  // src/cli/workspace/projectInstructions.ts
32442
- import { existsSync as existsSync19, readFileSync as readFileSync17 } from "node:fs";
32443
- import { join as join16 } from "node:path";
32895
+ import { existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
32896
+ import { join as join17 } from "node:path";
32444
32897
  function loadProjectInstructions(projectRoot = process.cwd(), maxChars = MAX_CHARS) {
32445
32898
  for (const name of CANDIDATES) {
32446
- const full = join16(projectRoot, name);
32447
- if (!existsSync19(full)) continue;
32899
+ const full = join17(projectRoot, name);
32900
+ if (!existsSync20(full)) continue;
32448
32901
  try {
32449
- let raw = readFileSync17(full, "utf8");
32902
+ let raw = readFileSync19(full, "utf8");
32450
32903
  raw = raw.replace(/\r\n/g, "\n").trim();
32451
32904
  if (!raw) continue;
32452
32905
  if (raw.length <= maxChars) {
@@ -32483,20 +32936,20 @@ var init_projectInstructions = __esm({
32483
32936
 
32484
32937
  // src/cli/workspace/paths.ts
32485
32938
  import {
32486
- mkdirSync as mkdirSync10,
32487
- writeFileSync as writeFileSync12,
32488
- existsSync as existsSync20,
32939
+ mkdirSync as mkdirSync11,
32940
+ writeFileSync as writeFileSync13,
32941
+ existsSync as existsSync21,
32489
32942
  accessSync,
32490
32943
  constants,
32491
32944
  realpathSync
32492
32945
  } from "node:fs";
32493
- import { join as join17, basename } from "node:path";
32494
- import { homedir as homedir7 } from "node:os";
32946
+ import { join as join18, basename } from "node:path";
32947
+ import { homedir as homedir9 } from "node:os";
32495
32948
  import { createHash as createHash4 } from "node:crypto";
32496
32949
  function resolveWorkspaceRoot(projectRoot = process.cwd()) {
32497
32950
  const candidates = [
32498
- join17(projectRoot, ".zelari"),
32499
- join17(homedir7(), ".zelari-code", "workspace", hashProject(projectRoot))
32951
+ join18(projectRoot, ".zelari"),
32952
+ join18(homedir9(), ".zelari-code", "workspace", hashProject(projectRoot))
32500
32953
  ];
32501
32954
  for (const candidate of candidates) {
32502
32955
  if (isWritableDir(projectRoot) || candidate !== candidates[0]) {
@@ -32512,7 +32965,7 @@ function hashProject(projectPath) {
32512
32965
  }
32513
32966
  function isWritableDir(dir) {
32514
32967
  try {
32515
- if (!existsSync20(dir)) return false;
32968
+ if (!existsSync21(dir)) return false;
32516
32969
  accessSync(dir, constants.W_OK);
32517
32970
  return true;
32518
32971
  } catch {
@@ -32520,26 +32973,26 @@ function isWritableDir(dir) {
32520
32973
  }
32521
32974
  }
32522
32975
  function ensureWorkspaceDir(workspaceDir) {
32523
- mkdirSync10(workspaceDir, { recursive: true });
32524
- if (workspaceDir.endsWith("/.zelari") && existsSync20(join17(workspaceDir, "..", ".git"))) {
32525
- const gitignorePath = join17(workspaceDir, ".gitignore");
32526
- if (!existsSync20(gitignorePath)) {
32527
- writeFileSync12(gitignorePath, "*\n!.gitignore\n");
32976
+ mkdirSync11(workspaceDir, { recursive: true });
32977
+ if (workspaceDir.endsWith("/.zelari") && existsSync21(join18(workspaceDir, "..", ".git"))) {
32978
+ const gitignorePath = join18(workspaceDir, ".gitignore");
32979
+ if (!existsSync21(gitignorePath)) {
32980
+ writeFileSync13(gitignorePath, "*\n!.gitignore\n");
32528
32981
  }
32529
32982
  }
32530
32983
  }
32531
32984
  function workspaceFile(rootDir, kind) {
32532
32985
  switch (kind) {
32533
32986
  case "plan":
32534
- return join17(rootDir, "plan.md");
32987
+ return join18(rootDir, "plan.md");
32535
32988
  case "risks":
32536
- return join17(rootDir, "risks.md");
32989
+ return join18(rootDir, "risks.md");
32537
32990
  case "index":
32538
- return join17(rootDir, "workspace.json");
32991
+ return join18(rootDir, "workspace.json");
32539
32992
  }
32540
32993
  }
32541
32994
  function workspaceArtifact(rootDir, subdir, slug) {
32542
- return join17(rootDir, subdir, `${slug}.md`);
32995
+ return join18(rootDir, subdir, `${slug}.md`);
32543
32996
  }
32544
32997
  function projectName(projectRoot = process.cwd()) {
32545
32998
  return basename(realpathSync(projectRoot));
@@ -32559,8 +33012,8 @@ __export(workspaceSummary_exports, {
32559
33012
  buildWorkspaceSummary: () => buildWorkspaceSummary,
32560
33013
  buildZelariReadHint: () => buildZelariReadHint
32561
33014
  });
32562
- import { existsSync as existsSync21, readFileSync as readFileSync18, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
32563
- import { join as join18, relative } from "node:path";
33015
+ import { existsSync as existsSync22, readFileSync as readFileSync20, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
33016
+ import { join as join19, relative } from "node:path";
32564
33017
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
32565
33018
  const { maxEntries = 30, maxChars = 3500, maxDeps = 24, maxScripts = 16 } = options;
32566
33019
  const name = safeProjectName(projectRoot);
@@ -32593,11 +33046,11 @@ function formatTaskLine(t) {
32593
33046
  }
32594
33047
  function buildPlanSummary(projectRoot = process.cwd(), options) {
32595
33048
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
32596
- const planPath = join18(zelariRoot, "plan.json");
32597
- if (!existsSync21(planPath)) return null;
33049
+ const planPath = join19(zelariRoot, "plan.json");
33050
+ if (!existsSync22(planPath)) return null;
32598
33051
  let plan;
32599
33052
  try {
32600
- plan = JSON.parse(readFileSync18(planPath, "utf8"));
33053
+ plan = JSON.parse(readFileSync20(planPath, "utf8"));
32601
33054
  } catch {
32602
33055
  return null;
32603
33056
  }
@@ -32733,8 +33186,8 @@ function pickNextTask(open) {
32733
33186
  )[0];
32734
33187
  }
32735
33188
  function buildZelariReadHint(projectRoot = process.cwd()) {
32736
- const planPath = join18(resolveWorkspaceRoot(projectRoot), "plan.json");
32737
- if (!existsSync21(planPath)) return "";
33189
+ const planPath = join19(resolveWorkspaceRoot(projectRoot), "plan.json");
33190
+ if (!existsSync22(planPath)) return "";
32738
33191
  return [
32739
33192
  "# Council workspace detected (.zelari/) \u2014 DRAFT vault",
32740
33193
  "`.zelari/plan.json` and `.zelari/docs/` hold **design hypotheses**, not verified product state.",
@@ -32749,10 +33202,10 @@ function safeProjectName(root) {
32749
33202
  }
32750
33203
  }
32751
33204
  function readPackageJson(projectRoot) {
32752
- const p3 = join18(projectRoot, "package.json");
32753
- if (!existsSync21(p3)) return null;
33205
+ const p3 = join19(projectRoot, "package.json");
33206
+ if (!existsSync22(p3)) return null;
32754
33207
  try {
32755
- return JSON.parse(readFileSync18(p3, "utf8"));
33208
+ return JSON.parse(readFileSync20(p3, "utf8"));
32756
33209
  } catch {
32757
33210
  return null;
32758
33211
  }
@@ -32793,7 +33246,7 @@ function readBuildScripts(projectRoot, maxScripts = 16) {
32793
33246
  function listShallow(projectRoot, maxEntries) {
32794
33247
  const out = [];
32795
33248
  try {
32796
- const top = readdirSync3(projectRoot, { withFileTypes: true }).filter(
33249
+ const top = readdirSync4(projectRoot, { withFileTypes: true }).filter(
32797
33250
  (e) => !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist"
32798
33251
  ).sort((a, b) => a.name.localeCompare(b.name));
32799
33252
  let count = 0;
@@ -32802,11 +33255,11 @@ function listShallow(projectRoot, maxEntries) {
32802
33255
  out.push(`\u2026 (+${top.length - count} more)`);
32803
33256
  break;
32804
33257
  }
32805
- const rel2 = relative(projectRoot, join18(projectRoot, entry.name));
33258
+ const rel2 = relative(projectRoot, join19(projectRoot, entry.name));
32806
33259
  if (entry.isDirectory()) {
32807
33260
  let inner = "";
32808
33261
  try {
32809
- const sub = readdirSync3(join18(projectRoot, entry.name), {
33262
+ const sub = readdirSync4(join19(projectRoot, entry.name), {
32810
33263
  withFileTypes: true
32811
33264
  }).filter((e) => !e.name.startsWith(".")).slice(0, 4).map((e) => e.name);
32812
33265
  if (sub.length > 0)
@@ -32854,12 +33307,12 @@ var init_workspaceSummary = __esm({
32854
33307
  });
32855
33308
 
32856
33309
  // src/cli/workspace/buildLessonsSummary.ts
32857
- import { existsSync as existsSync22 } from "node:fs";
32858
- import { join as join19 } from "node:path";
33310
+ import { existsSync as existsSync23 } from "node:fs";
33311
+ import { join as join20 } from "node:path";
32859
33312
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
32860
33313
  if (process.env["ZELARI_LESSONS"] === "0") return null;
32861
33314
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
32862
- if (!existsSync22(join19(zelariRoot, "lessons.jsonl"))) return null;
33315
+ if (!existsSync23(join20(zelariRoot, "lessons.jsonl"))) return null;
32863
33316
  const lessons = recallLessons(zelariRoot, {
32864
33317
  maxLessons: 5,
32865
33318
  maxBytes: 2048,
@@ -32880,8 +33333,8 @@ var composeContext_exports = {};
32880
33333
  __export(composeContext_exports, {
32881
33334
  composeProjectContext: () => composeProjectContext
32882
33335
  });
32883
- import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync19 } from "node:fs";
32884
- import { join as join20 } from "node:path";
33336
+ import { existsSync as existsSync24, readdirSync as readdirSync5, readFileSync as readFileSync21 } from "node:fs";
33337
+ import { join as join21 } from "node:path";
32885
33338
  function cap2(text, max, label) {
32886
33339
  if (!text || text.length <= max) return { text: text || "", truncated: false };
32887
33340
  return {
@@ -32893,19 +33346,19 @@ function cap2(text, max, label) {
32893
33346
  }
32894
33347
  function buildDesignIndex(projectRoot, maxChars) {
32895
33348
  const root = resolveWorkspaceRoot(projectRoot);
32896
- if (!existsSync23(root)) return "";
33349
+ if (!existsSync24(root)) return "";
32897
33350
  const lines = [
32898
33351
  "# Design vault index (.zelari/) \u2014 HYPOTHESES only",
32899
33352
  "Full design docs are NOT product source of truth. Open with list_files / read_file / searchDocuments if needed."
32900
33353
  ];
32901
- const docsDir = join20(root, "docs");
32902
- if (existsSync23(docsDir)) {
33354
+ const docsDir = join21(root, "docs");
33355
+ if (existsSync24(docsDir)) {
32903
33356
  try {
32904
- const docs = readdirSync4(docsDir).filter((n) => n.endsWith(".md")).slice(0, 12);
33357
+ const docs = readdirSync5(docsDir).filter((n) => n.endsWith(".md")).slice(0, 12);
32905
33358
  if (docs.length > 0) {
32906
33359
  lines.push("", "## docs/ (titles only)");
32907
33360
  for (const d of docs) lines.push(`- .zelari/docs/${d}`);
32908
- if (readdirSync4(docsDir).filter((n) => n.endsWith(".md")).length > 12) {
33361
+ if (readdirSync5(docsDir).filter((n) => n.endsWith(".md")).length > 12) {
32909
33362
  lines.push("- \u2026 (more under .zelari/docs/)");
32910
33363
  }
32911
33364
  }
@@ -32913,14 +33366,14 @@ function buildDesignIndex(projectRoot, maxChars) {
32913
33366
  }
32914
33367
  }
32915
33368
  for (const name of ["risks.md", "plan.json", "nfr-spec.json"]) {
32916
- if (existsSync23(join20(root, name))) {
33369
+ if (existsSync24(join21(root, name))) {
32917
33370
  lines.push(`- .zelari/${name} present`);
32918
33371
  }
32919
33372
  }
32920
- const decisionsDir = join20(root, "decisions");
32921
- if (existsSync23(decisionsDir)) {
33373
+ const decisionsDir = join21(root, "decisions");
33374
+ if (existsSync24(decisionsDir)) {
32922
33375
  try {
32923
- const n = readdirSync4(decisionsDir).filter((f) => f.endsWith(".md")).length;
33376
+ const n = readdirSync5(decisionsDir).filter((f) => f.endsWith(".md")).length;
32924
33377
  if (n > 0) lines.push(`- .zelari/decisions/ (${n} ADR file(s) \u2014 treat proposed as non-binding)`);
32925
33378
  } catch {
32926
33379
  }
@@ -33019,17 +33472,17 @@ function composeProjectContext(input) {
33019
33472
  }
33020
33473
  function readDurableHeadSync(projectRoot) {
33021
33474
  try {
33022
- const headPath = join20(projectRoot, ".zelari", "state", "HEAD.json");
33023
- if (!existsSync23(headPath)) return "";
33024
- const head = JSON.parse(readFileSync19(headPath, "utf8"));
33475
+ const headPath = join21(projectRoot, ".zelari", "state", "HEAD.json");
33476
+ if (!existsSync24(headPath)) return "";
33477
+ const head = JSON.parse(readFileSync21(headPath, "utf8"));
33025
33478
  if (!head?.id) return "";
33026
- const metaPath = join20(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
33027
- if (!existsSync23(metaPath)) return "";
33028
- const meta3 = JSON.parse(readFileSync19(metaPath, "utf8"));
33029
- const discPath = meta3.artifactDir ? join20(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join20(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
33479
+ const metaPath = join21(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
33480
+ if (!existsSync24(metaPath)) return "";
33481
+ const meta3 = JSON.parse(readFileSync21(metaPath, "utf8"));
33482
+ const discPath = meta3.artifactDir ? join21(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join21(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
33030
33483
  let discoveries = [];
33031
- if (existsSync23(discPath)) {
33032
- discoveries = JSON.parse(readFileSync19(discPath, "utf8"));
33484
+ if (existsSync24(discPath)) {
33485
+ discoveries = JSON.parse(readFileSync21(discPath, "utf8"));
33033
33486
  }
33034
33487
  const reusable = discoveries.filter((d) => d.reusable !== false);
33035
33488
  const lines = [
@@ -33062,13 +33515,13 @@ var planDetect_exports = {};
33062
33515
  __export(planDetect_exports, {
33063
33516
  hasWorkspacePlan: () => hasWorkspacePlan
33064
33517
  });
33065
- import { existsSync as existsSync24, readFileSync as readFileSync20 } from "node:fs";
33066
- import { join as join21 } from "node:path";
33518
+ import { existsSync as existsSync25, readFileSync as readFileSync22 } from "node:fs";
33519
+ import { join as join22 } from "node:path";
33067
33520
  function hasWorkspacePlan(projectRoot = process.cwd()) {
33068
- const planPath = join21(resolveWorkspaceRoot(projectRoot), "plan.json");
33069
- if (!existsSync24(planPath)) return false;
33521
+ const planPath = join22(resolveWorkspaceRoot(projectRoot), "plan.json");
33522
+ if (!existsSync25(planPath)) return false;
33070
33523
  try {
33071
- const parsed = JSON.parse(readFileSync20(planPath, "utf8"));
33524
+ const parsed = JSON.parse(readFileSync22(planPath, "utf8"));
33072
33525
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
33073
33526
  } catch {
33074
33527
  return false;
@@ -33128,14 +33581,14 @@ __export(storage_exports, {
33128
33581
  workspaceMutex: () => workspaceMutex
33129
33582
  });
33130
33583
  import {
33131
- readFileSync as readFileSync21,
33132
- writeFileSync as writeFileSync13,
33133
- existsSync as existsSync25,
33134
- mkdirSync as mkdirSync11,
33135
- readdirSync as readdirSync5,
33584
+ readFileSync as readFileSync23,
33585
+ writeFileSync as writeFileSync14,
33586
+ existsSync as existsSync26,
33587
+ mkdirSync as mkdirSync12,
33588
+ readdirSync as readdirSync6,
33136
33589
  renameSync as renameSync2
33137
33590
  } from "node:fs";
33138
- import { dirname as dirname4, join as join22 } from "node:path";
33591
+ import { dirname as dirname4, join as join23 } from "node:path";
33139
33592
  function parseFrontmatter(md) {
33140
33593
  const m = FRONTMATTER_RE.exec(md);
33141
33594
  if (!m) return { meta: {}, body: md };
@@ -33389,33 +33842,33 @@ var init_storage = __esm({
33389
33842
  VALID_SCALARS = /^(true|false|null|~)$/i;
33390
33843
  Storage = class {
33391
33844
  /** Read a Markdown file with frontmatter. Throws if not found. */
33392
- read(path48) {
33393
- if (!existsSync25(path48)) {
33394
- throw new Error(`File not found: ${path48}`);
33845
+ read(path51) {
33846
+ if (!existsSync26(path51)) {
33847
+ throw new Error(`File not found: ${path51}`);
33395
33848
  }
33396
- const md = readFileSync21(path48, "utf8");
33849
+ const md = readFileSync23(path51, "utf8");
33397
33850
  return parseFrontmatter(md);
33398
33851
  }
33399
33852
  /** Read a Markdown file; returns null if not found. */
33400
- readIfExists(path48) {
33401
- if (!existsSync25(path48)) return null;
33402
- return this.read(path48);
33853
+ readIfExists(path51) {
33854
+ if (!existsSync26(path51)) return null;
33855
+ return this.read(path51);
33403
33856
  }
33404
33857
  /**
33405
33858
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
33406
33859
  * The meta object is serialized as YAML frontmatter; body as Markdown.
33407
33860
  */
33408
- write(path48, meta3, body) {
33409
- mkdirSync11(dirname4(path48), { recursive: true });
33410
- const tmp = path48 + ".tmp-" + process.pid;
33861
+ write(path51, meta3, body) {
33862
+ mkdirSync12(dirname4(path51), { recursive: true });
33863
+ const tmp = path51 + ".tmp-" + process.pid;
33411
33864
  const md = serializeFrontmatter(meta3, body);
33412
- writeFileSync13(tmp, md, "utf8");
33413
- renameSync2(tmp, path48);
33865
+ writeFileSync14(tmp, md, "utf8");
33866
+ renameSync2(tmp, path51);
33414
33867
  }
33415
33868
  /** List all .md files in a directory (non-recursive). */
33416
33869
  listMarkdown(dir) {
33417
- if (!existsSync25(dir)) return [];
33418
- return readdirSync5(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join22(dir, f));
33870
+ if (!existsSync26(dir)) return [];
33871
+ return readdirSync6(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join23(dir, f));
33419
33872
  }
33420
33873
  };
33421
33874
  KeyedMutex = class {
@@ -33454,14 +33907,14 @@ __export(stubs_exports, {
33454
33907
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
33455
33908
  });
33456
33909
  import {
33457
- existsSync as existsSync26,
33458
- readdirSync as readdirSync6,
33459
- writeFileSync as writeFileSync14,
33460
- readFileSync as readFileSync22,
33461
- mkdirSync as mkdirSync12,
33910
+ existsSync as existsSync27,
33911
+ readdirSync as readdirSync7,
33912
+ writeFileSync as writeFileSync15,
33913
+ readFileSync as readFileSync24,
33914
+ mkdirSync as mkdirSync13,
33462
33915
  renameSync as renameSync3
33463
33916
  } from "node:fs";
33464
- import { join as join23, basename as basename2, dirname as dirname5, relative as relative2 } from "node:path";
33917
+ import { join as join24, basename as basename2, dirname as dirname5, relative as relative2 } from "node:path";
33465
33918
  function createWorkspaceContext(projectRoot = process.cwd()) {
33466
33919
  const rootDir = resolveWorkspaceRoot(projectRoot);
33467
33920
  return {
@@ -33471,14 +33924,14 @@ function createWorkspaceContext(projectRoot = process.cwd()) {
33471
33924
  };
33472
33925
  }
33473
33926
  function planJsonPath(ctx) {
33474
- return join23(ctx.rootDir, "plan.json");
33927
+ return join24(ctx.rootDir, "plan.json");
33475
33928
  }
33476
33929
  function readPlan(ctx) {
33477
33930
  const jsonPath = planJsonPath(ctx);
33478
- if (existsSync26(jsonPath)) {
33931
+ if (existsSync27(jsonPath)) {
33479
33932
  try {
33480
33933
  const parsed = JSON.parse(
33481
- readFileSync22(jsonPath, "utf8")
33934
+ readFileSync24(jsonPath, "utf8")
33482
33935
  );
33483
33936
  return {
33484
33937
  phases: Array.isArray(parsed.phases) ? parsed.phases : [],
@@ -33488,8 +33941,8 @@ function readPlan(ctx) {
33488
33941
  } catch {
33489
33942
  }
33490
33943
  }
33491
- const path48 = workspaceFile(ctx.rootDir, "plan");
33492
- const doc = ctx.storage.readIfExists(path48);
33944
+ const path51 = workspaceFile(ctx.rootDir, "plan");
33945
+ const doc = ctx.storage.readIfExists(path51);
33493
33946
  if (!doc) return { phases: [], tasks: [], milestones: [] };
33494
33947
  const meta3 = doc.meta;
33495
33948
  return {
@@ -33500,9 +33953,9 @@ function readPlan(ctx) {
33500
33953
  }
33501
33954
  function writePlan(ctx, summary) {
33502
33955
  const jsonPath = planJsonPath(ctx);
33503
- mkdirSync12(dirname5(jsonPath), { recursive: true });
33956
+ mkdirSync13(dirname5(jsonPath), { recursive: true });
33504
33957
  const tmp = jsonPath + ".tmp-" + process.pid;
33505
- writeFileSync14(tmp, JSON.stringify(summary, null, 2), "utf8");
33958
+ writeFileSync15(tmp, JSON.stringify(summary, null, 2), "utf8");
33506
33959
  renameSync3(tmp, jsonPath);
33507
33960
  const mdPath = workspaceFile(ctx.rootDir, "plan");
33508
33961
  const summaryMeta = {
@@ -33569,9 +34022,9 @@ function renderPlanBody(summary) {
33569
34022
  return lines.join("\n");
33570
34023
  }
33571
34024
  function nextAdrId(ctx) {
33572
- const decisionsDir = join23(ctx.rootDir, "decisions");
33573
- if (!existsSync26(decisionsDir)) return "001";
33574
- const existing = readdirSync6(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
34025
+ const decisionsDir = join24(ctx.rootDir, "decisions");
34026
+ if (!existsSync27(decisionsDir)) return "001";
34027
+ const existing = readdirSync7(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
33575
34028
  const max = existing.length === 0 ? 0 : Math.max(...existing);
33576
34029
  return String(max + 1).padStart(3, "0");
33577
34030
  }
@@ -33612,7 +34065,7 @@ function addTaskRecord(ctx, summary, phaseId, t, options) {
33612
34065
  status: "pending",
33613
34066
  priority: t.priority
33614
34067
  });
33615
- const taskPath = join23(ctx.rootDir, "plan-tasks", `${id}.md`);
34068
+ const taskPath = join24(ctx.rootDir, "plan-tasks", `${id}.md`);
33616
34069
  const meta3 = {
33617
34070
  kind: "task",
33618
34071
  id,
@@ -33654,7 +34107,7 @@ function addMilestoneRecord(ctx, summary, input) {
33654
34107
  dueDate: input.dueDate,
33655
34108
  targetVersion: version2
33656
34109
  });
33657
- const path48 = join23(ctx.rootDir, "milestones", `${id}.md`);
34110
+ const path51 = join24(ctx.rootDir, "milestones", `${id}.md`);
33658
34111
  const meta3 = {
33659
34112
  kind: "milestone",
33660
34113
  id,
@@ -33671,7 +34124,7 @@ function addMilestoneRecord(ctx, summary, input) {
33671
34124
  `Target version: ${version2}`,
33672
34125
  ""
33673
34126
  ].join("\n");
33674
- ctx.storage.write(path48, meta3, body);
34127
+ ctx.storage.write(path51, meta3, body);
33675
34128
  return { id, created: true };
33676
34129
  }
33677
34130
  function readPlanSummary(ctx) {
@@ -33875,7 +34328,7 @@ function addIdeaStub(ctx) {
33875
34328
  const tags = args["tags"] ?? [];
33876
34329
  const category = args["category"] ?? "General";
33877
34330
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
33878
- const path48 = workspaceArtifact(ctx.rootDir, "decisions", id);
34331
+ const path51 = workspaceArtifact(ctx.rootDir, "decisions", id);
33879
34332
  const meta3 = {
33880
34333
  kind: "adr",
33881
34334
  status: "proposed",
@@ -33901,7 +34354,7 @@ function addIdeaStub(ctx) {
33901
34354
  ...consequences.map((c) => `- ${c}`),
33902
34355
  ""
33903
34356
  ].join("\n");
33904
- ctx.storage.write(path48, meta3, body);
34357
+ ctx.storage.write(path51, meta3, body);
33905
34358
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
33906
34359
  });
33907
34360
  }
@@ -33952,8 +34405,8 @@ function createNfrSpecStub(ctx) {
33952
34405
  },
33953
34406
  planFeatureKeywords: Array.isArray(args["planFeatureKeywords"]) ? args["planFeatureKeywords"] : void 0
33954
34407
  };
33955
- const outPath = join23(ctx.rootDir, "nfr-spec.json");
33956
- writeFileSync14(outPath, JSON.stringify(spec, null, 2), "utf8");
34408
+ const outPath = join24(ctx.rootDir, "nfr-spec.json");
34409
+ writeFileSync15(outPath, JSON.stringify(spec, null, 2), "utf8");
33957
34410
  return `NFR spec written to nfr-spec.json (${targets.length} target(s)).`;
33958
34411
  });
33959
34412
  }
@@ -33983,14 +34436,14 @@ function createDocumentStub(ctx) {
33983
34436
  ctx.storage.write(risksPath, riskMeta, content);
33984
34437
  return `Document "${title}" created at risks.md (workspace root).`;
33985
34438
  }
33986
- const path48 = workspaceArtifact(ctx.rootDir, "docs", slug);
34439
+ const path51 = workspaceArtifact(ctx.rootDir, "docs", slug);
33987
34440
  const meta3 = {
33988
34441
  kind: "doc",
33989
34442
  id: slug,
33990
34443
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
33991
34444
  tags
33992
34445
  };
33993
- ctx.storage.write(path48, meta3, content);
34446
+ ctx.storage.write(path51, meta3, content);
33994
34447
  return `Document "${title}" created at docs/${slug}.md.`;
33995
34448
  });
33996
34449
  }
@@ -34016,18 +34469,18 @@ function searchDocumentsStub(ctx) {
34016
34469
  (w) => w.length >= 3 && w !== "or" && w !== "and" && w !== "the"
34017
34470
  );
34018
34471
  const files = [
34019
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "decisions")),
34020
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "docs")),
34021
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "reviews")),
34022
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "plan-tasks")),
34023
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "milestones")),
34472
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "decisions")),
34473
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "docs")),
34474
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "reviews")),
34475
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "plan-tasks")),
34476
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "milestones")),
34024
34477
  workspaceFile(ctx.rootDir, "plan"),
34025
34478
  workspaceFile(ctx.rootDir, "risks")
34026
34479
  ];
34027
34480
  const results = [];
34028
34481
  for (const file2 of files) {
34029
- if (!existsSync26(file2)) continue;
34030
- const raw = readFileSync22(file2, "utf8");
34482
+ if (!existsSync27(file2)) continue;
34483
+ const raw = readFileSync24(file2, "utf8");
34031
34484
  const content = raw.toLowerCase();
34032
34485
  let idx = -1;
34033
34486
  let matchLen = 0;
@@ -34078,9 +34531,9 @@ function linkDocumentsStub(ctx) {
34078
34531
  const toId = args["toId"] ?? args["targetId"] ?? args["targetPathOrTitle"];
34079
34532
  if (!fromId || !toId) return "linkDocuments requires fromId and toId.";
34080
34533
  const allFiles = [
34081
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "decisions")),
34082
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "docs")),
34083
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "reviews"))
34534
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "decisions")),
34535
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "docs")),
34536
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "reviews"))
34084
34537
  ];
34085
34538
  const source = allFiles.find((f) => {
34086
34539
  try {
@@ -34111,9 +34564,9 @@ function getDocumentBacklinksStub(ctx) {
34111
34564
  const targetId = args["targetId"] ?? args["id"];
34112
34565
  if (!targetId) return "getDocumentBacklinks requires targetId.";
34113
34566
  const allFiles = [
34114
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "decisions")),
34115
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "docs")),
34116
- ...ctx.storage.listMarkdown(join23(ctx.rootDir, "reviews"))
34567
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "decisions")),
34568
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "docs")),
34569
+ ...ctx.storage.listMarkdown(join24(ctx.rootDir, "reviews"))
34117
34570
  ];
34118
34571
  const backlinks = [];
34119
34572
  for (const file2 of allFiles) {
@@ -34207,21 +34660,21 @@ __export(updater_exports, {
34207
34660
  resolveBundledNpmCli: () => resolveBundledNpmCli
34208
34661
  });
34209
34662
  import { createRequire as createRequire2 } from "node:module";
34210
- import { spawn as spawn7 } from "node:child_process";
34211
- import { existsSync as existsSync27 } from "node:fs";
34212
- import path27 from "node:path";
34663
+ import { spawn as spawn8 } from "node:child_process";
34664
+ import { existsSync as existsSync28 } from "node:fs";
34665
+ import path29 from "node:path";
34213
34666
  import { fileURLToPath } from "node:url";
34214
34667
  function resolveBundledNpmCli(execPath = process.execPath) {
34215
- const dir = path27.dirname(execPath);
34668
+ const dir = path29.dirname(execPath);
34216
34669
  const candidates = [
34217
34670
  // Windows: C:\...\node.exe → C:\...\node_modules\npm\bin\npm-cli.js
34218
- path27.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
34671
+ path29.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
34219
34672
  // POSIX: <prefix>/bin/node → <prefix>/lib/node_modules/npm/bin/npm-cli.js
34220
- path27.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
34673
+ path29.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
34221
34674
  ];
34222
34675
  for (const candidate of candidates) {
34223
34676
  try {
34224
- if (existsSync27(candidate)) return candidate;
34677
+ if (existsSync28(candidate)) return candidate;
34225
34678
  } catch {
34226
34679
  }
34227
34680
  }
@@ -34234,7 +34687,7 @@ function looksLikeBrokenShim(exitCode, output) {
34234
34687
  }
34235
34688
  function getCurrentVersion() {
34236
34689
  try {
34237
- const pkgPath = path27.resolve(__dirname2, "..", "..", "package.json");
34690
+ const pkgPath = path29.resolve(__dirname2, "..", "..", "package.json");
34238
34691
  const pkg = require2(pkgPath);
34239
34692
  return pkg.version;
34240
34693
  } catch {
@@ -34294,7 +34747,7 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
34294
34747
  updateAvailable: cmp < 0
34295
34748
  };
34296
34749
  }
34297
- async function performUpdate(packageName = "zelari-code", executor = spawn7, resolveNpmCli = resolveBundledNpmCli) {
34750
+ async function performUpdate(packageName = "zelari-code", executor = spawn8, resolveNpmCli = resolveBundledNpmCli) {
34298
34751
  const args = ["install", "-g", `${packageName}@latest`];
34299
34752
  const primary = await runNpm(executor, args, "shim");
34300
34753
  if (primary.ok) return primary;
@@ -34346,13 +34799,13 @@ var init_updater = __esm({
34346
34799
  "use strict";
34347
34800
  init_cmdline();
34348
34801
  require2 = createRequire2(import.meta.url);
34349
- __dirname2 = path27.dirname(fileURLToPath(import.meta.url));
34802
+ __dirname2 = path29.dirname(fileURLToPath(import.meta.url));
34350
34803
  REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
34351
34804
  }
34352
34805
  });
34353
34806
 
34354
34807
  // src/cli/mcp/mcpClient.ts
34355
- import { spawn as spawn8 } from "node:child_process";
34808
+ import { spawn as spawn9 } from "node:child_process";
34356
34809
  var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
34357
34810
  var init_mcpClient = __esm({
34358
34811
  "src/cli/mcp/mcpClient.ts"() {
@@ -34380,10 +34833,10 @@ var init_mcpClient = __esm({
34380
34833
  env: { ...process.env, ...this.config.env ?? {} },
34381
34834
  windowsHide: true
34382
34835
  };
34383
- const child = process.platform === "win32" ? spawn8(buildCmdLine(this.config.command, this.config.args ?? []), {
34836
+ const child = process.platform === "win32" ? spawn9(buildCmdLine(this.config.command, this.config.args ?? []), {
34384
34837
  ...spawnOpts,
34385
34838
  shell: true
34386
- }) : spawn8(this.config.command, this.config.args ?? [], spawnOpts);
34839
+ }) : spawn9(this.config.command, this.config.args ?? [], spawnOpts);
34387
34840
  this.child = child;
34388
34841
  child.stdout.setEncoding("utf8");
34389
34842
  child.stdout.on("data", (chunk) => this.onStdout(chunk));
@@ -34521,23 +34974,23 @@ var init_mcpClient = __esm({
34521
34974
 
34522
34975
  // src/cli/mcp/mcpConfigIo.ts
34523
34976
  import {
34524
- existsSync as existsSync28,
34525
- mkdirSync as mkdirSync13,
34526
- readFileSync as readFileSync23,
34527
- writeFileSync as writeFileSync15
34977
+ existsSync as existsSync29,
34978
+ mkdirSync as mkdirSync14,
34979
+ readFileSync as readFileSync25,
34980
+ writeFileSync as writeFileSync16
34528
34981
  } from "node:fs";
34529
- import { dirname as dirname6, join as join24 } from "node:path";
34530
- import { homedir as homedir8 } from "node:os";
34982
+ import { dirname as dirname6, join as join25 } from "node:path";
34983
+ import { homedir as homedir10 } from "node:os";
34531
34984
  function getUserMcpPath() {
34532
- return join24(homedir8(), ".zelari-code", "mcp.json");
34985
+ return join25(homedir10(), ".zelari-code", "mcp.json");
34533
34986
  }
34534
34987
  function getProjectMcpPath(projectRoot) {
34535
- return join24(projectRoot, ".zelari", "mcp.json");
34988
+ return join25(projectRoot, ".zelari", "mcp.json");
34536
34989
  }
34537
- function readFile2(path48) {
34538
- if (!existsSync28(path48)) return {};
34990
+ function readFile2(path51) {
34991
+ if (!existsSync29(path51)) return {};
34539
34992
  try {
34540
- const parsed = JSON.parse(readFileSync23(path48, "utf8"));
34993
+ const parsed = JSON.parse(readFileSync25(path51, "utf8"));
34541
34994
  const out = {};
34542
34995
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
34543
34996
  if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
@@ -34553,10 +35006,10 @@ function readFile2(path48) {
34553
35006
  return {};
34554
35007
  }
34555
35008
  }
34556
- function writeFile(path48, servers) {
34557
- mkdirSync13(dirname6(path48), { recursive: true });
35009
+ function writeFile(path51, servers) {
35010
+ mkdirSync14(dirname6(path51), { recursive: true });
34558
35011
  const body = { mcpServers: servers };
34559
- writeFileSync15(path48, `${JSON.stringify(body, null, 2)}
35012
+ writeFileSync16(path51, `${JSON.stringify(body, null, 2)}
34560
35013
  `, "utf8");
34561
35014
  }
34562
35015
  function listMcpServers(projectRoot) {
@@ -34589,9 +35042,9 @@ function upsertMcpServer(opts) {
34589
35042
  if (!opts.config.command?.trim()) {
34590
35043
  return { ok: false, error: "command is required" };
34591
35044
  }
34592
- let path48;
35045
+ let path51;
34593
35046
  if (opts.scope === "user") {
34594
- path48 = getUserMcpPath();
35047
+ path51 = getUserMcpPath();
34595
35048
  } else {
34596
35049
  const root = opts.projectRoot?.trim();
34597
35050
  if (!root) {
@@ -34600,30 +35053,30 @@ function upsertMcpServer(opts) {
34600
35053
  error: "projectRoot required for project scope (Open Folder first)"
34601
35054
  };
34602
35055
  }
34603
- path48 = getProjectMcpPath(root);
35056
+ path51 = getProjectMcpPath(root);
34604
35057
  }
34605
- const current = readFile2(path48);
35058
+ const current = readFile2(path51);
34606
35059
  current[name] = {
34607
35060
  command: opts.config.command.trim(),
34608
35061
  args: opts.config.args,
34609
35062
  env: opts.config.env,
34610
35063
  enabled: opts.config.enabled !== false
34611
35064
  };
34612
- writeFile(path48, current);
34613
- return { ok: true, path: path48 };
35065
+ writeFile(path51, current);
35066
+ return { ok: true, path: path51 };
34614
35067
  }
34615
35068
  function removeMcpServer(opts) {
34616
- const path48 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
34617
- if (!path48) {
35069
+ const path51 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
35070
+ if (!path51) {
34618
35071
  return { ok: false, error: "projectRoot required for project scope" };
34619
35072
  }
34620
- const current = readFile2(path48);
35073
+ const current = readFile2(path51);
34621
35074
  if (!(opts.name in current)) {
34622
- return { ok: false, error: `Server "${opts.name}" not found in ${path48}` };
35075
+ return { ok: false, error: `Server "${opts.name}" not found in ${path51}` };
34623
35076
  }
34624
35077
  delete current[opts.name];
34625
- writeFile(path48, current);
34626
- return { ok: true, path: path48 };
35078
+ writeFile(path51, current);
35079
+ return { ok: true, path: path51 };
34627
35080
  }
34628
35081
  var init_mcpConfigIo = __esm({
34629
35082
  "src/cli/mcp/mcpConfigIo.ts"() {
@@ -34654,13 +35107,16 @@ function buildComposioPreset() {
34654
35107
  }
34655
35108
  function buildQwenMmPreset() {
34656
35109
  const plugin = process.env.QWEN_MM_PLUGIN?.trim() || "qwen-mm-plugins-core";
35110
+ const cap3 = plugin.replace(/^qwen-mm-plugins-/, "");
35111
+ const spec = `qwen-mm-plugins[${cap3}] @ git+https://github.com/QwenLM/Qwen-MM-Plugins.git@${plugin}-v1.0.1`;
34657
35112
  return {
34658
35113
  id: "qwen-mm-plugins",
34659
35114
  servers: {
34660
35115
  "qwen-mm-plugins": {
34661
35116
  command: "uvx",
34662
- args: [plugin],
35117
+ args: ["--from", spec, plugin],
34663
35118
  env: {
35119
+ PYTHONIOENCODING: "utf-8",
34664
35120
  ...process.env.DASHSCOPE_API_KEY ? { DASHSCOPE_API_KEY: process.env.DASHSCOPE_API_KEY } : {},
34665
35121
  ...process.env.SERPER_API_KEY ? { SERPER_API_KEY: process.env.SERPER_API_KEY } : {}
34666
35122
  },
@@ -34669,8 +35125,8 @@ function buildQwenMmPreset() {
34669
35125
  },
34670
35126
  notes: [
34671
35127
  "Qwen-MM-Plugins \u2014 multimodal capabilities (core / video-memory / video-edit / blender / freecad) as MCP tools.",
34672
- "Run the upstream guided installer first: curl -fsSL https://raw.githubusercontent.com/QwenLM/Qwen-MM-Plugins/main/install.sh | bash",
34673
- "Windows: use WSL2 (Ubuntu); native Windows is not validated upstream.",
35128
+ "Installs via uvx from the GitHub release tag (qwen-mm-plugins-<cap>-v1.0.1); first launch downloads ~70 packages.",
35129
+ "Windows: verified working natively with PYTHONIOENCODING=utf-8 (WSL2 also supported upstream).",
34674
35130
  "Pick a capability: export QWEN_MM_PLUGIN=qwen-mm-plugins-video-memory (default: qwen-mm-plugins-core).",
34675
35131
  "API keys read at apply time: DASHSCOPE_API_KEY, SERPER_API_KEY (native reading works without them).",
34676
35132
  "Blender/FreeCAD variants: start the host app before invoking tools.",
@@ -34762,20 +35218,22 @@ __export(mcpManager_exports, {
34762
35218
  readMcpConfig: () => readMcpConfig,
34763
35219
  registerMcpTools: () => registerMcpTools
34764
35220
  });
34765
- import { existsSync as existsSync29, readFileSync as readFileSync24 } from "node:fs";
34766
- import { join as join25 } from "node:path";
34767
- import { homedir as homedir9 } from "node:os";
34768
- function readMcpConfig(projectRoot = process.cwd()) {
35221
+ import { existsSync as existsSync30, readFileSync as readFileSync26 } from "node:fs";
35222
+ import { join as join26 } from "node:path";
35223
+ import { homedir as homedir11 } from "node:os";
35224
+ function readMcpConfig(projectRoot = process.cwd(), opts) {
34769
35225
  const merged = {};
34770
35226
  const paths = [];
34771
35227
  if (process.env["ZELARI_MCP_USER"] !== "0") {
34772
- paths.push(join25(homedir9(), ".zelari-code", "mcp.json"));
35228
+ paths.push(join26(homedir11(), ".zelari-code", "mcp.json"));
35229
+ }
35230
+ if (!opts?.skipProjectMcp) {
35231
+ paths.push(join26(projectRoot, ".zelari", "mcp.json"));
34773
35232
  }
34774
- paths.push(join25(projectRoot, ".zelari", "mcp.json"));
34775
35233
  for (const p3 of paths) {
34776
- if (!existsSync29(p3)) continue;
35234
+ if (!existsSync30(p3)) continue;
34777
35235
  try {
34778
- const parsed = JSON.parse(readFileSync24(p3, "utf8"));
35236
+ const parsed = JSON.parse(readFileSync26(p3, "utf8"));
34779
35237
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
34780
35238
  if (!cfg || typeof cfg.command !== "string" || cfg.command.length === 0) continue;
34781
35239
  merged[name] = cfg;
@@ -34788,7 +35246,13 @@ function readMcpConfig(projectRoot = process.cwd()) {
34788
35246
  async function ensureLoaded(projectRoot) {
34789
35247
  if (state2.loaded) return;
34790
35248
  state2.loaded = true;
34791
- const servers = readMcpConfig(projectRoot);
35249
+ const trusted = isFolderTrusted(projectRoot);
35250
+ if (!trusted && existsSync30(join26(projectRoot, ".zelari", "mcp.json"))) {
35251
+ state2.warnings.push(
35252
+ "[mcp] project .zelari/mcp.json ignored \u2014 folder not trusted (run /trust or `zelari-code --trust` to enable project MCP)"
35253
+ );
35254
+ }
35255
+ const servers = readMcpConfig(projectRoot, { skipProjectMcp: !trusted });
34792
35256
  for (const [name, cfg] of Object.entries(servers)) {
34793
35257
  if (cfg.enabled === false) continue;
34794
35258
  if (isCuaDisabled() && isCuaMcpServerName(name)) {
@@ -34871,6 +35335,7 @@ var init_mcpManager = __esm({
34871
35335
  init_toolTypes();
34872
35336
  init_mcpClient();
34873
35337
  init_mcpPresets();
35338
+ init_folderTrust();
34874
35339
  state2 = { loaded: false, tools: [], warnings: [], clients: [] };
34875
35340
  }
34876
35341
  });
@@ -35007,15 +35472,15 @@ __export(agentsMd_exports, {
35007
35472
  serializeAgentsMd: () => serializeAgentsMd,
35008
35473
  updateAgentsMd: () => updateAgentsMd
35009
35474
  });
35010
- import { existsSync as existsSync30, readFileSync as readFileSync25, writeFileSync as writeFileSync16 } from "node:fs";
35475
+ import { existsSync as existsSync31, readFileSync as readFileSync27, writeFileSync as writeFileSync17 } from "node:fs";
35011
35476
  import { createHash as createHash5 } from "node:crypto";
35012
- import { join as join26 } from "node:path";
35477
+ import { join as join27 } from "node:path";
35013
35478
  import { readFile as readFile3 } from "node:fs/promises";
35014
35479
  async function readPackageJson2(projectRoot) {
35015
- const path48 = join26(projectRoot, "package.json");
35016
- if (!existsSync30(path48)) return null;
35480
+ const path51 = join27(projectRoot, "package.json");
35481
+ if (!existsSync31(path51)) return null;
35017
35482
  try {
35018
- return JSON.parse(await readFile3(path48, "utf8"));
35483
+ return JSON.parse(await readFile3(path51, "utf8"));
35019
35484
  } catch {
35020
35485
  return null;
35021
35486
  }
@@ -35037,8 +35502,8 @@ async function genTechStack(ctx) {
35037
35502
  ].join("\n");
35038
35503
  }
35039
35504
  async function genDecisions(ctx) {
35040
- const decisionsDir = join26(ctx.rootDir, "decisions");
35041
- if (!existsSync30(decisionsDir)) return "_No ADRs yet._";
35505
+ const decisionsDir = join27(ctx.rootDir, "decisions");
35506
+ if (!existsSync31(decisionsDir)) return "_No ADRs yet._";
35042
35507
  const files = ctx.storage.listMarkdown(decisionsDir).sort();
35043
35508
  const accepted = [];
35044
35509
  const proposed = [];
@@ -35063,9 +35528,9 @@ async function genDecisions(ctx) {
35063
35528
  }
35064
35529
  async function genConventions(ctx) {
35065
35530
  const lines = [];
35066
- const claudeMd = join26(ctx.projectRoot, "CLAUDE.MD");
35067
- if (existsSync30(claudeMd)) {
35068
- const content = readFileSync25(claudeMd, "utf8");
35531
+ const claudeMd = join27(ctx.projectRoot, "CLAUDE.MD");
35532
+ if (existsSync31(claudeMd)) {
35533
+ const content = readFileSync27(claudeMd, "utf8");
35069
35534
  const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
35070
35535
  if (match) {
35071
35536
  lines.push('<!-- Extracted from CLAUDE.MD "Architecture rules" -->');
@@ -35097,9 +35562,9 @@ async function genBuild(ctx) {
35097
35562
  ].join("\n");
35098
35563
  }
35099
35564
  async function genOpenQuestions(ctx) {
35100
- const path48 = join26(ctx.rootDir, "risks.md");
35101
- if (!existsSync30(path48)) return "_No open questions._";
35102
- const content = readFileSync25(path48, "utf8");
35565
+ const path51 = join27(ctx.rootDir, "risks.md");
35566
+ if (!existsSync31(path51)) return "_No open questions._";
35567
+ const content = readFileSync27(path51, "utf8");
35103
35568
  const lines = content.split("\n");
35104
35569
  const questions = [];
35105
35570
  let currentTitle = "";
@@ -35173,9 +35638,9 @@ function titleCase(id) {
35173
35638
  return id.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
35174
35639
  }
35175
35640
  async function updateAgentsMd(ctx, projectRoot) {
35176
- const agentsPath = join26(projectRoot, "AGENTS.MD");
35177
- if (existsSync30(agentsPath)) {
35178
- const content = readFileSync25(agentsPath, "utf8");
35641
+ const agentsPath = join27(projectRoot, "AGENTS.MD");
35642
+ if (existsSync31(agentsPath)) {
35643
+ const content = readFileSync27(agentsPath, "utf8");
35179
35644
  const hasAnyMarker = AUTO_SECTIONS.some((id) => content.includes(MARKER_OPEN(id)));
35180
35645
  if (!hasAnyMarker) {
35181
35646
  return {
@@ -35190,8 +35655,8 @@ async function updateAgentsMd(ctx, projectRoot) {
35190
35655
  newSections.set(id, await GENERATORS[id](ctx));
35191
35656
  }
35192
35657
  let manualContent = "";
35193
- if (existsSync30(agentsPath)) {
35194
- const { manualBlocks } = parseAgentsMd(readFileSync25(agentsPath, "utf8"));
35658
+ if (existsSync31(agentsPath)) {
35659
+ const { manualBlocks } = parseAgentsMd(readFileSync27(agentsPath, "utf8"));
35195
35660
  manualContent = manualBlocks.after;
35196
35661
  } else {
35197
35662
  const projectName2 = projectName(projectRoot);
@@ -35207,7 +35672,7 @@ async function updateAgentsMd(ctx, projectRoot) {
35207
35672
  ""
35208
35673
  ].join("\n");
35209
35674
  }
35210
- const oldContent = existsSync30(agentsPath) ? readFileSync25(agentsPath, "utf8") : "";
35675
+ const oldContent = existsSync31(agentsPath) ? readFileSync27(agentsPath, "utf8") : "";
35211
35676
  const { sections: oldSections } = parseAgentsMd(oldContent);
35212
35677
  const changedSections = [];
35213
35678
  for (const id of AUTO_SECTIONS) {
@@ -35219,7 +35684,7 @@ async function updateAgentsMd(ctx, projectRoot) {
35219
35684
  return { changed: false, sections: [] };
35220
35685
  }
35221
35686
  const newContent = serializeAgentsMd(manualContent, newSections);
35222
- writeFileSync16(agentsPath, newContent, "utf8");
35687
+ writeFileSync17(agentsPath, newContent, "utf8");
35223
35688
  return { changed: true, sections: changedSections };
35224
35689
  }
35225
35690
  function hash2(s) {
@@ -35344,9 +35809,9 @@ var init_completeDesign = __esm({
35344
35809
  });
35345
35810
 
35346
35811
  // src/cli/workspace/projectSmoke.ts
35347
- import { spawn as spawn9 } from "node:child_process";
35348
- import { existsSync as existsSync31, readFileSync as readFileSync26 } from "node:fs";
35349
- import { join as join27 } from "node:path";
35812
+ import { spawn as spawn10 } from "node:child_process";
35813
+ import { existsSync as existsSync32, readFileSync as readFileSync28 } from "node:fs";
35814
+ import { join as join28 } from "node:path";
35350
35815
  function pickSmokeScript(scripts) {
35351
35816
  if (!scripts) return null;
35352
35817
  for (const name of SMOKE_SCRIPT_PRIORITY) {
@@ -35354,17 +35819,17 @@ function pickSmokeScript(scripts) {
35354
35819
  }
35355
35820
  return null;
35356
35821
  }
35357
- async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
35822
+ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS2) {
35358
35823
  if (process.env["ZELARI_SMOKE"] === "0") {
35359
35824
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
35360
35825
  }
35361
- const pkgPath = join27(projectRoot, "package.json");
35362
- if (!existsSync31(pkgPath)) {
35826
+ const pkgPath = join28(projectRoot, "package.json");
35827
+ if (!existsSync32(pkgPath)) {
35363
35828
  return { ran: false, reason: "no package.json (skipped)" };
35364
35829
  }
35365
35830
  let scripts = {};
35366
35831
  try {
35367
- const pkg = JSON.parse(readFileSync26(pkgPath, "utf8"));
35832
+ const pkg = JSON.parse(readFileSync28(pkgPath, "utf8"));
35368
35833
  scripts = pkg.scripts ?? {};
35369
35834
  } catch {
35370
35835
  return { ran: false, reason: "package.json unreadable (skipped)" };
@@ -35374,12 +35839,12 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
35374
35839
  return { ran: false, reason: "no typecheck/test/build script (skipped)" };
35375
35840
  }
35376
35841
  return await new Promise((resolveRun) => {
35377
- const child = process.platform === "win32" ? spawn9(buildCmdLine("npm.cmd", ["run", script]), {
35842
+ const child = process.platform === "win32" ? spawn10(buildCmdLine("npm.cmd", ["run", script]), {
35378
35843
  cwd: projectRoot,
35379
35844
  stdio: ["ignore", "pipe", "pipe"],
35380
35845
  env: process.env,
35381
35846
  shell: true
35382
- }) : spawn9("npm", ["run", script], {
35847
+ }) : spawn10("npm", ["run", script], {
35383
35848
  cwd: projectRoot,
35384
35849
  stdio: ["ignore", "pipe", "pipe"],
35385
35850
  env: process.env
@@ -35434,13 +35899,13 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
35434
35899
  });
35435
35900
  });
35436
35901
  }
35437
- var SMOKE_SCRIPT_PRIORITY, DEFAULT_TIMEOUT_MS;
35902
+ var SMOKE_SCRIPT_PRIORITY, DEFAULT_TIMEOUT_MS2;
35438
35903
  var init_projectSmoke = __esm({
35439
35904
  "src/cli/workspace/projectSmoke.ts"() {
35440
35905
  "use strict";
35441
35906
  init_cmdline();
35442
35907
  SMOKE_SCRIPT_PRIORITY = ["typecheck", "test", "build"];
35443
- DEFAULT_TIMEOUT_MS = 12e4;
35908
+ DEFAULT_TIMEOUT_MS2 = 12e4;
35444
35909
  }
35445
35910
  });
35446
35911
 
@@ -35451,9 +35916,9 @@ __export(postCouncilHook_exports, {
35451
35916
  runImplementationVerificationHook: () => runImplementationVerificationHook,
35452
35917
  runPostCouncilHook: () => runPostCouncilHook
35453
35918
  });
35454
- import { spawn as spawn10 } from "node:child_process";
35455
- import { existsSync as existsSync32, readFileSync as readFileSync27 } from "node:fs";
35456
- import { join as join28 } from "node:path";
35919
+ import { spawn as spawn11 } from "node:child_process";
35920
+ import { existsSync as existsSync33, readFileSync as readFileSync29 } from "node:fs";
35921
+ import { join as join29 } from "node:path";
35457
35922
  async function runCompleteDesignPostProcessor(ctx, options) {
35458
35923
  if (options?.runMode === "implementation") {
35459
35924
  return {
@@ -35464,9 +35929,9 @@ async function runCompleteDesignPostProcessor(ctx, options) {
35464
35929
  if (process.env["ZELARI_COMPLETE_DESIGN"] === "0") {
35465
35930
  return { ran: false, reason: "ZELARI_COMPLETE_DESIGN=0 (disabled)" };
35466
35931
  }
35467
- const planJsonPath2 = join28(ctx.rootDir, "plan.json");
35468
- const scriptPath = join28(ctx.projectRoot, "complete-design.mjs");
35469
- if (!existsSync32(planJsonPath2)) {
35932
+ const planJsonPath2 = join29(ctx.rootDir, "plan.json");
35933
+ const scriptPath = join29(ctx.projectRoot, "complete-design.mjs");
35934
+ if (!existsSync33(planJsonPath2)) {
35470
35935
  return {
35471
35936
  ran: false,
35472
35937
  reason: ".zelari/plan.json missing (not design-phase)"
@@ -35474,7 +35939,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
35474
35939
  }
35475
35940
  let phaseCount = 0;
35476
35941
  try {
35477
- const parsed = JSON.parse(readFileSync27(planJsonPath2, "utf8"));
35942
+ const parsed = JSON.parse(readFileSync29(planJsonPath2, "utf8"));
35478
35943
  phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
35479
35944
  } catch {
35480
35945
  return { ran: false, reason: ".zelari/plan.json corrupt" };
@@ -35482,7 +35947,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
35482
35947
  if (phaseCount === 0) {
35483
35948
  return { ran: false, reason: ".zelari/plan.json has no phases" };
35484
35949
  }
35485
- if (!existsSync32(scriptPath)) {
35950
+ if (!existsSync33(scriptPath)) {
35486
35951
  try {
35487
35952
  const builtin = await runBuiltinCompleteDesign(ctx);
35488
35953
  return {
@@ -35500,7 +35965,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
35500
35965
  }
35501
35966
  }
35502
35967
  return await new Promise((resolveRun) => {
35503
- const child = spawn10(process.execPath, [scriptPath], {
35968
+ const child = spawn11(process.execPath, [scriptPath], {
35504
35969
  cwd: ctx.projectRoot,
35505
35970
  stdio: ["ignore", "pipe", "pipe"],
35506
35971
  env: process.env
@@ -35666,8 +36131,8 @@ async function runPostCouncilHook(ctx, options) {
35666
36131
  sources: scope.sources
35667
36132
  } : void 0
35668
36133
  });
35669
- const path48 = writeCouncilCompletion(ctx.rootDir, completion);
35670
- completionHook = { ran: true, path: path48, completion };
36134
+ const path51 = writeCouncilCompletion(ctx.rootDir, completion);
36135
+ completionHook = { ran: true, path: path51, completion };
35671
36136
  } catch (err) {
35672
36137
  completionHook = {
35673
36138
  ran: true,
@@ -35705,12 +36170,12 @@ __export(councilFeedback_exports, {
35705
36170
  });
35706
36171
  import {
35707
36172
  promises as fs15,
35708
- existsSync as existsSync33,
35709
- readFileSync as readFileSync28,
35710
- writeFileSync as writeFileSync17,
35711
- mkdirSync as mkdirSync14
36173
+ existsSync as existsSync34,
36174
+ readFileSync as readFileSync30,
36175
+ writeFileSync as writeFileSync18,
36176
+ mkdirSync as mkdirSync15
35712
36177
  } from "node:fs";
35713
- import path28 from "node:path";
36178
+ import path30 from "node:path";
35714
36179
  import os8 from "node:os";
35715
36180
  var FeedbackStore;
35716
36181
  var init_councilFeedback = __esm({
@@ -35721,7 +36186,7 @@ var init_councilFeedback = __esm({
35721
36186
  now;
35722
36187
  entries = [];
35723
36188
  constructor(options = {}) {
35724
- this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path28.join(os8.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
36189
+ this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path30.join(os8.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
35725
36190
  this.now = options.now ?? Date.now;
35726
36191
  this.load();
35727
36192
  }
@@ -35814,9 +36279,9 @@ var init_councilFeedback = __esm({
35814
36279
  }
35815
36280
  // --- persistence ---------------------------------------------------------
35816
36281
  load() {
35817
- if (!existsSync33(this.file)) return;
36282
+ if (!existsSync34(this.file)) return;
35818
36283
  try {
35819
- const raw = readFileSync28(this.file, "utf-8");
36284
+ const raw = readFileSync30(this.file, "utf-8");
35820
36285
  const parsed = JSON.parse(raw);
35821
36286
  if (parsed && Array.isArray(parsed.entries)) {
35822
36287
  this.entries = parsed.entries.filter(
@@ -35827,8 +36292,8 @@ var init_councilFeedback = __esm({
35827
36292
  }
35828
36293
  }
35829
36294
  save() {
35830
- mkdirSync14(path28.dirname(this.file), { recursive: true });
35831
- writeFileSync17(
36295
+ mkdirSync15(path30.dirname(this.file), { recursive: true });
36296
+ writeFileSync18(
35832
36297
  this.file,
35833
36298
  JSON.stringify({ entries: this.entries }, null, 2),
35834
36299
  { encoding: "utf-8", mode: 384 }
@@ -35895,7 +36360,7 @@ import { execFile as execFile3 } from "node:child_process";
35895
36360
  import { promisify as promisify2 } from "node:util";
35896
36361
  import { mkdtempSync, rmSync as rmSync2 } from "node:fs";
35897
36362
  import { tmpdir as tmpdir2 } from "node:os";
35898
- import path29 from "node:path";
36363
+ import path31 from "node:path";
35899
36364
  import { randomUUID as randomUUID3 } from "node:crypto";
35900
36365
  async function git2(cwd, args, env) {
35901
36366
  const { stdout } = await execFileAsync2("git", ["-C", cwd, ...args], {
@@ -35915,8 +36380,8 @@ async function isGitRepo(cwd) {
35915
36380
  return await gitSafe(cwd, ["rev-parse", "--is-inside-work-tree"]) === "true";
35916
36381
  }
35917
36382
  async function withTempIndex(fn) {
35918
- const dir = mkdtempSync(path29.join(tmpdir2(), "zelari-ckpt-"));
35919
- const indexFile = path29.join(dir, "index");
36383
+ const dir = mkdtempSync(path31.join(tmpdir2(), "zelari-ckpt-"));
36384
+ const indexFile = path31.join(dir, "index");
35920
36385
  try {
35921
36386
  return await fn(indexFile);
35922
36387
  } finally {
@@ -36007,7 +36472,7 @@ async function restoreCheckpoint(cwd, id) {
36007
36472
  const deleted = [];
36008
36473
  for (const rel2 of added) {
36009
36474
  try {
36010
- rmSync2(path29.join(cwd, rel2), { force: true });
36475
+ rmSync2(path31.join(cwd, rel2), { force: true });
36011
36476
  deleted.push(rel2);
36012
36477
  } catch {
36013
36478
  }
@@ -36108,7 +36573,7 @@ __export(fileBackend_exports, {
36108
36573
  });
36109
36574
  import { randomUUID as randomUUID4 } from "node:crypto";
36110
36575
  import { promises as fs16 } from "node:fs";
36111
- import * as path30 from "node:path";
36576
+ import * as path32 from "node:path";
36112
36577
  function tokenize(text) {
36113
36578
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 3);
36114
36579
  }
@@ -36152,8 +36617,8 @@ var init_fileBackend = __esm({
36152
36617
  logPath = "";
36153
36618
  memoryDir = "";
36154
36619
  async init(projectRoot) {
36155
- this.memoryDir = path30.join(projectRoot, ".zelari", "memory");
36156
- this.logPath = path30.join(this.memoryDir, "log.jsonl");
36620
+ this.memoryDir = path32.join(projectRoot, ".zelari", "memory");
36621
+ this.logPath = path32.join(this.memoryDir, "log.jsonl");
36157
36622
  await fs16.mkdir(this.memoryDir, { recursive: true });
36158
36623
  }
36159
36624
  async add(content, metadata = {}, graph) {
@@ -36226,12 +36691,12 @@ var init_fileBackend = __esm({
36226
36691
 
36227
36692
  // src/cli/traceStore.ts
36228
36693
  import { promises as fs17 } from "node:fs";
36229
- import * as path31 from "node:path";
36694
+ import * as path33 from "node:path";
36230
36695
  function traceDir(projectRoot) {
36231
- return path31.join(projectRoot, ".zelari", "trace");
36696
+ return path33.join(projectRoot, ".zelari", "trace");
36232
36697
  }
36233
36698
  function tracePath(projectRoot, missionId) {
36234
- return path31.join(traceDir(projectRoot), `${missionId}.json`);
36699
+ return path33.join(traceDir(projectRoot), `${missionId}.json`);
36235
36700
  }
36236
36701
  async function saveTrace(projectRoot, missionId, entries) {
36237
36702
  const dir = traceDir(projectRoot);
@@ -36266,7 +36731,7 @@ __export(zelariMission_exports, {
36266
36731
  });
36267
36732
  import { randomUUID as randomUUID5 } from "node:crypto";
36268
36733
  import { promises as fs18 } from "node:fs";
36269
- import * as path32 from "node:path";
36734
+ import * as path34 from "node:path";
36270
36735
  function resolveMaxIterations(env = process.env) {
36271
36736
  const raw = env.ZELARI_MISSION_MAX_ITER;
36272
36737
  const n = raw ? Number.parseInt(raw, 10) : DEFAULT_MAX_ITER;
@@ -36294,10 +36759,10 @@ function isMissionAutoStart(env = process.env) {
36294
36759
  return env.ZELARI_MISSION_AUTO === "1";
36295
36760
  }
36296
36761
  async function writeMissionState(projectRoot, state3) {
36297
- const dir = path32.join(projectRoot, ".zelari");
36762
+ const dir = path34.join(projectRoot, ".zelari");
36298
36763
  await fs18.mkdir(dir, { recursive: true });
36299
36764
  await fs18.writeFile(
36300
- path32.join(dir, "mission-state.json"),
36765
+ path34.join(dir, "mission-state.json"),
36301
36766
  JSON.stringify(state3, null, 2) + "\n",
36302
36767
  "utf8"
36303
36768
  );
@@ -36897,7 +37362,7 @@ function safeSocketPath(socketPath) {
36897
37362
  return socketPath.trim();
36898
37363
  }
36899
37364
  function startPermissionBroker(socketPath, handlers, opts) {
36900
- const path48 = safeSocketPath(socketPath);
37365
+ const path51 = safeSocketPath(socketPath);
36901
37366
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
36902
37367
  const sockets = /* @__PURE__ */ new Set();
36903
37368
  const server = createServer((socket) => {
@@ -36997,10 +37462,10 @@ function startPermissionBroker(socketPath, handlers, opts) {
36997
37462
  return new Promise((resolve3, reject) => {
36998
37463
  const onError = (err) => reject(err);
36999
37464
  server.once("error", onError);
37000
- server.listen(path48, () => {
37465
+ server.listen(path51, () => {
37001
37466
  server.removeListener("error", onError);
37002
37467
  resolve3({
37003
- socketPath: path48,
37468
+ socketPath: path51,
37004
37469
  stop: () => new Promise((res) => {
37005
37470
  for (const s of sockets) s.destroy();
37006
37471
  sockets.clear();
@@ -37011,7 +37476,7 @@ function startPermissionBroker(socketPath, handlers, opts) {
37011
37476
  if (done) return;
37012
37477
  done = true;
37013
37478
  if (process.platform !== "win32") {
37014
- unlink(path48, () => res());
37479
+ unlink(path51, () => res());
37015
37480
  } else {
37016
37481
  res();
37017
37482
  }
@@ -37024,11 +37489,11 @@ function startPermissionBroker(socketPath, handlers, opts) {
37024
37489
  });
37025
37490
  }
37026
37491
  function requestBrokerAsk(socketPath, ask, opts) {
37027
- const path48 = safeSocketPath(socketPath);
37492
+ const path51 = safeSocketPath(socketPath);
37028
37493
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
37029
37494
  const connectTimeoutMs = opts?.connectTimeoutMs ?? PERMISSION_BROKER_DEFAULT_CONNECT_TIMEOUT_MS;
37030
37495
  return new Promise((resolve3, reject) => {
37031
- const socket = connect(path48);
37496
+ const socket = connect(path51);
37032
37497
  let buffer = "";
37033
37498
  let settled = false;
37034
37499
  const settle = (fn) => {
@@ -37043,7 +37508,7 @@ function requestBrokerAsk(socketPath, ask, opts) {
37043
37508
  settle(
37044
37509
  () => reject(
37045
37510
  new Error(
37046
- `permission broker unavailable at "${path48}" (connect timed out after ${connectTimeoutMs}ms)`
37511
+ `permission broker unavailable at "${path51}" (connect timed out after ${connectTimeoutMs}ms)`
37047
37512
  )
37048
37513
  )
37049
37514
  );
@@ -37776,9 +38241,9 @@ __export(graphMemory_exports, {
37776
38241
  toGraphSnapshot: () => toGraphSnapshot
37777
38242
  });
37778
38243
  import { promises as fs19 } from "node:fs";
37779
- import path34 from "node:path";
38244
+ import path36 from "node:path";
37780
38245
  function snapshotPath(cwd) {
37781
- return path34.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
38246
+ return path36.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
37782
38247
  }
37783
38248
  function toGraphSnapshot(graph, opts) {
37784
38249
  const unresolved = (opts.unresolvedFindings ?? []).map((u) => ({
@@ -37805,7 +38270,7 @@ async function saveGraphSnapshot(cwd, snapshot) {
37805
38270
  try {
37806
38271
  await fs19.access(cwd);
37807
38272
  const file2 = snapshotPath(cwd);
37808
- await fs19.mkdir(path34.dirname(file2), { recursive: true });
38273
+ await fs19.mkdir(path36.dirname(file2), { recursive: true });
37809
38274
  await fs19.writeFile(file2, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
37810
38275
  } catch {
37811
38276
  }
@@ -37872,7 +38337,7 @@ var SNAPSHOT_DIR, SNAPSHOT_FILE, MAX_SNAPSHOT_FINDINGS_CHARS;
37872
38337
  var init_graphMemory = __esm({
37873
38338
  "src/cli/kraken/graphMemory.ts"() {
37874
38339
  "use strict";
37875
- SNAPSHOT_DIR = path34.join(".zelari", "kraken");
38340
+ SNAPSHOT_DIR = path36.join(".zelari", "kraken");
37876
38341
  SNAPSHOT_FILE = "last-graph.json";
37877
38342
  MAX_SNAPSHOT_FINDINGS_CHARS = 400;
37878
38343
  }
@@ -37888,14 +38353,14 @@ var init_tentacle = __esm({
37888
38353
 
37889
38354
  // src/cli/kraken/workbench.ts
37890
38355
  import { promises as fs20 } from "node:fs";
37891
- import path35 from "node:path";
38356
+ import path37 from "node:path";
37892
38357
  function isWorkbenchEnabled(env = process.env) {
37893
38358
  const v = (env.ZELARI_KRAKEN_WORKBENCH ?? "1").trim().toLowerCase();
37894
38359
  if (v === "0" || v === "false" || v === "no" || v === "off") return false;
37895
38360
  return true;
37896
38361
  }
37897
38362
  function workbenchPath(cwd, graphId) {
37898
- return path35.join(cwd, ".zelari", "radio", `workbench-${graphId}.md`);
38363
+ return path37.join(cwd, ".zelari", "radio", `workbench-${graphId}.md`);
37899
38364
  }
37900
38365
  function countByStatus2(nodes) {
37901
38366
  const out = { pending: 0, running: 0, done: 0, error: 0, skipped: 0 };
@@ -38058,7 +38523,7 @@ var init_workbench = __esm({
38058
38523
  if (!this.enabled) return null;
38059
38524
  if (!this.dirty && this.lastWrite) return this.lastWrite;
38060
38525
  const out = workbenchPath(this.cwd, this.graphId);
38061
- await fs20.mkdir(path35.dirname(out), { recursive: true });
38526
+ await fs20.mkdir(path37.dirname(out), { recursive: true });
38062
38527
  const body = this.render();
38063
38528
  const tmp = `${out}.${process.pid}.${Date.now()}.tmp`;
38064
38529
  await fs20.writeFile(tmp, body, "utf8");
@@ -38254,8 +38719,8 @@ __export(executor_exports, {
38254
38719
  resolveNodeTimeoutMs: () => resolveNodeTimeoutMs,
38255
38720
  thoroughnessForKind: () => thoroughnessForKind
38256
38721
  });
38257
- import { existsSync as existsSync34 } from "node:fs";
38258
- import path36 from "node:path";
38722
+ import { existsSync as existsSync35 } from "node:fs";
38723
+ import path38 from "node:path";
38259
38724
  function resolveMaxParallel(env = process.env) {
38260
38725
  const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
38261
38726
  if (raw === void 0 || raw === "") return DEFAULT_MAX_PARALLEL;
@@ -38313,7 +38778,7 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
38313
38778
  }
38314
38779
  function defaultChecksExists(cwd) {
38315
38780
  try {
38316
- return existsSync34(path36.join(cwd, ".zelari", "world", "checks.json"));
38781
+ return existsSync35(path38.join(cwd, ".zelari", "world", "checks.json"));
38317
38782
  } catch {
38318
38783
  return false;
38319
38784
  }
@@ -39280,7 +39745,7 @@ __export(prereqChecks_exports, {
39280
39745
  runPrereqChecks: () => runPrereqChecks
39281
39746
  });
39282
39747
  import { execSync, spawnSync as spawnSync2 } from "node:child_process";
39283
- import { existsSync as existsSync35 } from "node:fs";
39748
+ import { existsSync as existsSync36 } from "node:fs";
39284
39749
  import { dirname as dirname7 } from "node:path";
39285
39750
  function isWslBashPath2(p3) {
39286
39751
  if (!p3 || typeof p3 !== "string") return false;
@@ -39404,7 +39869,7 @@ function agentProbeEnv() {
39404
39869
  }
39405
39870
  function existsSyncSafe2(p3) {
39406
39871
  try {
39407
- return existsSync35(p3);
39872
+ return existsSync36(p3);
39408
39873
  } catch {
39409
39874
  return false;
39410
39875
  }
@@ -39651,17 +40116,17 @@ var init_prereqChecks = __esm({
39651
40116
  });
39652
40117
 
39653
40118
  // src/cli/plugins/prefs.ts
39654
- import { existsSync as existsSync36, readFileSync as readFileSync29, writeFileSync as writeFileSync18, mkdirSync as mkdirSync15 } from "node:fs";
39655
- import path39 from "node:path";
40119
+ import { existsSync as existsSync37, readFileSync as readFileSync31, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16 } from "node:fs";
40120
+ import path41 from "node:path";
39656
40121
  import os9 from "node:os";
39657
40122
  function getPluginPrefsPath() {
39658
- return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path39.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
40123
+ return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path41.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
39659
40124
  }
39660
40125
  function getPluginPrefs() {
39661
40126
  const file2 = getPluginPrefsPath();
39662
40127
  try {
39663
- if (!existsSync36(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
39664
- const raw = readFileSync29(file2, "utf-8");
40128
+ if (!existsSync37(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
40129
+ const raw = readFileSync31(file2, "utf-8");
39665
40130
  const parsed = JSON.parse(raw);
39666
40131
  if (parsed && typeof parsed === "object" && parsed.dontAskAgain && typeof parsed.dontAskAgain === "object") {
39667
40132
  const clean = {};
@@ -39676,8 +40141,8 @@ function getPluginPrefs() {
39676
40141
  }
39677
40142
  function writePluginPrefs(prefs) {
39678
40143
  const file2 = getPluginPrefsPath();
39679
- mkdirSync15(path39.dirname(file2), { recursive: true });
39680
- writeFileSync18(file2, JSON.stringify(prefs, null, 2), {
40144
+ mkdirSync16(path41.dirname(file2), { recursive: true });
40145
+ writeFileSync19(file2, JSON.stringify(prefs, null, 2), {
39681
40146
  encoding: "utf-8",
39682
40147
  mode: 384
39683
40148
  });
@@ -39712,8 +40177,8 @@ __export(registry_exports, {
39712
40177
  findPlugin: () => findPlugin,
39713
40178
  isBinaryOnPath: () => isBinaryOnPath
39714
40179
  });
39715
- import { existsSync as existsSync37 } from "node:fs";
39716
- import path40 from "node:path";
40180
+ import { existsSync as existsSync38 } from "node:fs";
40181
+ import path42 from "node:path";
39717
40182
  function detectLocalBin(bin) {
39718
40183
  return (cwd) => {
39719
40184
  try {
@@ -39729,9 +40194,9 @@ function isBinaryOnPath(bin, opts = {}) {
39729
40194
  return false;
39730
40195
  }
39731
40196
  const platform = opts.platform ?? process.platform;
39732
- const exists = opts.exists ?? existsSync37;
40197
+ const exists = opts.exists ?? existsSync38;
39733
40198
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
39734
- const pathMod = platform === "win32" ? path40.win32 : path40.posix;
40199
+ const pathMod = platform === "win32" ? path42.win32 : path42.posix;
39735
40200
  const sep2 = platform === "win32" ? ";" : ":";
39736
40201
  const dirs = pathEnv.split(sep2).filter((d) => d.length > 0);
39737
40202
  const candidates = [bin];
@@ -39868,7 +40333,7 @@ __export(atMentions_exports, {
39868
40333
  extractAtMentions: () => extractAtMentions,
39869
40334
  hasAtMentions: () => hasAtMentions
39870
40335
  });
39871
- import { existsSync as existsSync40, readFileSync as readFileSync31, statSync as statSync6 } from "node:fs";
40336
+ import { existsSync as existsSync41, readFileSync as readFileSync33, statSync as statSync6 } from "node:fs";
39872
40337
  import { basename as basename3, isAbsolute as isAbsolute2, relative as relative3, resolve, sep } from "node:path";
39873
40338
  function isImagePath(abs) {
39874
40339
  const ext = abs.split(".").pop()?.toLowerCase() ?? "";
@@ -39922,7 +40387,7 @@ function resolveMention(token, cwd) {
39922
40387
  note: "outside project root \u2014 skipped"
39923
40388
  };
39924
40389
  }
39925
- if (!existsSync40(abs)) {
40390
+ if (!existsSync41(abs)) {
39926
40391
  return {
39927
40392
  raw: token,
39928
40393
  path: token,
@@ -39974,7 +40439,7 @@ function resolveMention(token, cwd) {
39974
40439
  note: `image too large (${Math.round(st.size / 1024)} KB) \u2014 path only`
39975
40440
  };
39976
40441
  }
39977
- const dataBase64 = readFileSync31(abs).toString("base64");
40442
+ const dataBase64 = readFileSync33(abs).toString("base64");
39978
40443
  return {
39979
40444
  raw: token,
39980
40445
  path: rel2,
@@ -39985,7 +40450,7 @@ function resolveMention(token, cwd) {
39985
40450
  };
39986
40451
  }
39987
40452
  try {
39988
- const buf = readFileSync31(abs);
40453
+ const buf = readFileSync33(abs);
39989
40454
  const head = buf.subarray(0, 800).toString("utf8");
39990
40455
  if (!isProbablyText(abs, head)) {
39991
40456
  return {
@@ -40083,9 +40548,9 @@ __export(triggerLock_exports, {
40083
40548
  releaseLock: () => releaseLock
40084
40549
  });
40085
40550
  import { promises as fs28 } from "node:fs";
40086
- import * as path45 from "node:path";
40551
+ import * as path47 from "node:path";
40087
40552
  function lockPath(projectRoot) {
40088
- return path45.join(projectRoot, ".zelari", "trigger.lock");
40553
+ return path47.join(projectRoot, ".zelari", "trigger.lock");
40089
40554
  }
40090
40555
  function isPidAlive(pid) {
40091
40556
  try {
@@ -40098,7 +40563,7 @@ function isPidAlive(pid) {
40098
40563
  }
40099
40564
  async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
40100
40565
  const lp = lockPath(projectRoot);
40101
- const dir = path45.dirname(lp);
40566
+ const dir = path47.dirname(lp);
40102
40567
  await fs28.mkdir(dir, { recursive: true });
40103
40568
  try {
40104
40569
  const raw = await fs28.readFile(lp, "utf8");
@@ -40380,6 +40845,269 @@ var init_desktopConfig = __esm({
40380
40845
  }
40381
40846
  });
40382
40847
 
40848
+ // src/cli/skillCategories.ts
40849
+ var CODING_CATEGORIES_LIST, CODING_CATEGORIES2;
40850
+ var init_skillCategories = __esm({
40851
+ "src/cli/skillCategories.ts"() {
40852
+ "use strict";
40853
+ CODING_CATEGORIES_LIST = [
40854
+ "plan",
40855
+ "refactor",
40856
+ "debug",
40857
+ "review",
40858
+ "test",
40859
+ "docs",
40860
+ "ops",
40861
+ "git",
40862
+ "db",
40863
+ "maint"
40864
+ ];
40865
+ CODING_CATEGORIES2 = new Set(CODING_CATEGORIES_LIST);
40866
+ }
40867
+ });
40868
+
40869
+ // src/cli/skillConfigIo.ts
40870
+ import {
40871
+ existsSync as existsSync43,
40872
+ mkdirSync as mkdirSync19,
40873
+ readdirSync as readdirSync8,
40874
+ readFileSync as readFileSync35,
40875
+ rmSync as rmSync4,
40876
+ writeFileSync as writeFileSync21
40877
+ } from "node:fs";
40878
+ import { dirname as dirname9, join as join35 } from "node:path";
40879
+ import { homedir as homedir12 } from "node:os";
40880
+ function ensureBuiltinSkillsLoadedSync() {
40881
+ if (builtinsLoaded) return;
40882
+ for (const spec of BUILTIN_SKILL_MODULES) {
40883
+ try {
40884
+ require?.(spec);
40885
+ } catch {
40886
+ }
40887
+ }
40888
+ }
40889
+ function getUserSkillsDir() {
40890
+ return join35(homedir12(), ".zelari-code", "skills");
40891
+ }
40892
+ function getProjectSkillsDir(projectRoot) {
40893
+ return join35(projectRoot, ".zelari", "skills");
40894
+ }
40895
+ function skillFilePath(dir, name) {
40896
+ return join35(dir, name, "SKILL.md");
40897
+ }
40898
+ function classifyScope(skillPath, projectRoot) {
40899
+ const userDir = getUserSkillsDir().replace(/\\/g, "/");
40900
+ const norm = skillPath.replace(/\\/g, "/");
40901
+ if (norm.startsWith(userDir + "/") || norm === userDir) {
40902
+ return { scope: "user", writable: true };
40903
+ }
40904
+ if (projectRoot) {
40905
+ const projDir = getProjectSkillsDir(projectRoot).replace(/\\/g, "/");
40906
+ if (norm.startsWith(projDir + "/") || norm === projDir) {
40907
+ return { scope: "project", writable: true };
40908
+ }
40909
+ }
40910
+ return { scope: "compat", writable: false };
40911
+ }
40912
+ function entryFromParsed(parsed, projectRoot) {
40913
+ const { scope, writable } = classifyScope(parsed.sourcePath, projectRoot);
40914
+ return {
40915
+ id: parsed.name,
40916
+ name: parsed.name,
40917
+ description: parsed.description,
40918
+ category: parsed.category,
40919
+ estimatedCost: parsed.estimatedCost,
40920
+ requiredTools: parsed.requiredTools,
40921
+ scope,
40922
+ path: parsed.sourcePath,
40923
+ body: parsed.body,
40924
+ builtin: false,
40925
+ writable
40926
+ };
40927
+ }
40928
+ function entryFromBuiltin(skill) {
40929
+ return {
40930
+ id: skill.id,
40931
+ name: skill.name || skill.id,
40932
+ description: skill.description || "",
40933
+ category: skill.category,
40934
+ estimatedCost: skill.estimatedCost,
40935
+ requiredTools: skill.requiredTools,
40936
+ scope: "builtin",
40937
+ path: null,
40938
+ // Expose body so Desktop skill picker can expand like `/skill <id>`.
40939
+ body: skill.systemPromptFragment || void 0,
40940
+ builtin: true,
40941
+ writable: false
40942
+ };
40943
+ }
40944
+ function scanSkillsDir(dir, projectRoot, seen, out) {
40945
+ if (!existsSync43(dir)) return;
40946
+ let entries;
40947
+ try {
40948
+ entries = readdirSync8(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
40949
+ } catch {
40950
+ return;
40951
+ }
40952
+ for (const entry of entries) {
40953
+ const skillPath = skillFilePath(dir, entry);
40954
+ if (!existsSync43(skillPath)) continue;
40955
+ try {
40956
+ const parsed = parseSkillMd(readFileSync35(skillPath, "utf8"), skillPath);
40957
+ if (!parsed) continue;
40958
+ if (seen.has(parsed.name)) continue;
40959
+ seen.add(parsed.name);
40960
+ out.push(entryFromParsed(parsed, projectRoot));
40961
+ } catch {
40962
+ }
40963
+ }
40964
+ }
40965
+ function listSkillsSnapshot(projectRoot) {
40966
+ const root = projectRoot && projectRoot.trim() ? projectRoot.trim() : null;
40967
+ const userSkillsDir = getUserSkillsDir();
40968
+ const projectSkillsDir = root ? getProjectSkillsDir(root) : null;
40969
+ const skills = [];
40970
+ const seen = /* @__PURE__ */ new Set();
40971
+ if (root) {
40972
+ scanSkillsDir(join35(root, ".zelari", "skills"), root, seen, skills);
40973
+ scanSkillsDir(join35(root, ".claude", "skills"), root, seen, skills);
40974
+ scanSkillsDir(join35(root, ".opencode", "skills"), root, seen, skills);
40975
+ }
40976
+ scanSkillsDir(userSkillsDir, root, seen, skills);
40977
+ for (const s of listCodingSkills()) {
40978
+ if (seen.has(s.id)) continue;
40979
+ seen.add(s.id);
40980
+ if (s.builtin) {
40981
+ skills.push(entryFromBuiltin(s));
40982
+ }
40983
+ }
40984
+ skills.sort((a, b) => {
40985
+ const order = (s) => s === "project" ? 0 : s === "user" ? 1 : s === "compat" ? 2 : 3;
40986
+ const d = order(a.scope) - order(b.scope);
40987
+ if (d !== 0) return d;
40988
+ return a.id.localeCompare(b.id);
40989
+ });
40990
+ return { userSkillsDir, projectSkillsDir, skills };
40991
+ }
40992
+ function serializeSkillMd(opts) {
40993
+ const name = opts.name.trim().toLowerCase();
40994
+ const description = opts.description.trim();
40995
+ const body = opts.body.trim();
40996
+ const lines = ["---", `name: ${name}`, `description: ${description}`];
40997
+ const cat = (opts.category ?? "").trim().toLowerCase();
40998
+ if (cat && CODING_CATEGORIES2.has(cat)) {
40999
+ lines.push(`category: ${cat}`);
41000
+ }
41001
+ if (opts.tools && opts.tools.length > 0) {
41002
+ lines.push(`tools: [${opts.tools.map((t) => t.trim()).filter(Boolean).join(", ")}]`);
41003
+ }
41004
+ const cost = (opts.cost ?? "").trim().toLowerCase();
41005
+ if (cost === "low" || cost === "medium" || cost === "high") {
41006
+ lines.push(`cost: ${cost}`);
41007
+ }
41008
+ lines.push("---", "", body, "");
41009
+ return lines.join("\n");
41010
+ }
41011
+ function upsertSkill(opts) {
41012
+ const name = opts.name.trim().toLowerCase();
41013
+ if (!name || !NAME_RE.test(name)) {
41014
+ return {
41015
+ ok: false,
41016
+ error: "Invalid skill name (use lowercase letters, digits, hyphens; max 64 chars)"
41017
+ };
41018
+ }
41019
+ const description = opts.description.trim();
41020
+ if (!description) {
41021
+ return { ok: false, error: "description is required" };
41022
+ }
41023
+ const body = opts.body.trim();
41024
+ if (!body) {
41025
+ return { ok: false, error: "body is required" };
41026
+ }
41027
+ let dir;
41028
+ if (opts.scope === "user") {
41029
+ dir = getUserSkillsDir();
41030
+ } else {
41031
+ const root = opts.projectRoot?.trim();
41032
+ if (!root) {
41033
+ return {
41034
+ ok: false,
41035
+ error: "projectRoot required for project scope (Open Folder first)"
41036
+ };
41037
+ }
41038
+ dir = getProjectSkillsDir(root);
41039
+ }
41040
+ const path51 = skillFilePath(dir, name);
41041
+ const content = serializeSkillMd({
41042
+ name,
41043
+ description,
41044
+ body,
41045
+ category: opts.category,
41046
+ tools: opts.tools,
41047
+ cost: opts.cost
41048
+ });
41049
+ const parsed = parseSkillMd(content, path51);
41050
+ if (!parsed) {
41051
+ return { ok: false, error: "Generated SKILL.md failed validation" };
41052
+ }
41053
+ mkdirSync19(dirname9(path51), { recursive: true });
41054
+ writeFileSync21(path51, content, "utf8");
41055
+ return { ok: true, path: path51 };
41056
+ }
41057
+ function removeSkill(opts) {
41058
+ const name = opts.name.trim().toLowerCase();
41059
+ if (!name) {
41060
+ return { ok: false, error: "name is required" };
41061
+ }
41062
+ let dir;
41063
+ if (opts.scope === "user") {
41064
+ dir = getUserSkillsDir();
41065
+ } else {
41066
+ const root = opts.projectRoot?.trim();
41067
+ if (!root) {
41068
+ return { ok: false, error: "projectRoot required for project scope" };
41069
+ }
41070
+ dir = getProjectSkillsDir(root);
41071
+ }
41072
+ const skillDir = join35(dir, name);
41073
+ const path51 = skillFilePath(dir, name);
41074
+ if (!existsSync43(path51) && !existsSync43(skillDir)) {
41075
+ return { ok: false, error: `Skill "${name}" not found in ${dir}` };
41076
+ }
41077
+ try {
41078
+ rmSync4(skillDir, { recursive: true, force: true });
41079
+ } catch (err) {
41080
+ return {
41081
+ ok: false,
41082
+ error: err instanceof Error ? err.message : String(err)
41083
+ };
41084
+ }
41085
+ return { ok: true, path: path51 };
41086
+ }
41087
+ var NAME_RE, BUILTIN_SKILL_MODULES, builtinsLoaded;
41088
+ var init_skillConfigIo = __esm({
41089
+ "src/cli/skillConfigIo.ts"() {
41090
+ "use strict";
41091
+ init_skills2();
41092
+ init_skillsMd();
41093
+ init_skillCategories();
41094
+ NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
41095
+ BUILTIN_SKILL_MODULES = [
41096
+ "@zelari/core/skills/builtin/debugging",
41097
+ "@zelari/core/skills/builtin/docs",
41098
+ "@zelari/core/skills/builtin/git-ops",
41099
+ "@zelari/core/skills/builtin/planning",
41100
+ "@zelari/core/skills/builtin/refactoring",
41101
+ "@zelari/core/skills/builtin/review",
41102
+ "@zelari/core/skills/builtin/testing",
41103
+ "@zelari/core/skills/builtin/schema-loop",
41104
+ "@zelari/core/skills/builtin/computer-use-cua",
41105
+ "@zelari/core/skills/builtin/qwen-mm-plugins"
41106
+ ];
41107
+ builtinsLoaded = false;
41108
+ }
41109
+ });
41110
+
40383
41111
  // src/cli/mcp/mcpPermissionServer.ts
40384
41112
  import { createInterface } from "node:readline";
40385
41113
  import { randomUUID as randomUUID7 } from "node:crypto";
@@ -40648,36 +41376,36 @@ var init_permissionCli = __esm({
40648
41376
 
40649
41377
  // src/cli/companion/config.ts
40650
41378
  import {
40651
- existsSync as existsSync43,
40652
- mkdirSync as mkdirSync19,
40653
- readFileSync as readFileSync34,
40654
- writeFileSync as writeFileSync21
41379
+ existsSync as existsSync44,
41380
+ mkdirSync as mkdirSync20,
41381
+ readFileSync as readFileSync36,
41382
+ writeFileSync as writeFileSync22
40655
41383
  } from "node:fs";
40656
- import { join as join35 } from "node:path";
40657
- import { homedir as homedir11 } from "node:os";
41384
+ import { join as join36 } from "node:path";
41385
+ import { homedir as homedir13 } from "node:os";
40658
41386
  import { createHash as createHash6, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
40659
41387
  function getZelariHome() {
40660
- return join35(homedir11(), ".zelari-code");
41388
+ return join36(homedir13(), ".zelari-code");
40661
41389
  }
40662
41390
  function getCompanionConfigPath() {
40663
- return join35(getZelariHome(), "companion.json");
41391
+ return join36(getZelariHome(), "companion.json");
40664
41392
  }
40665
41393
  function getCompanionTokenPath() {
40666
- return join35(getZelariHome(), "companion.token");
41394
+ return join36(getZelariHome(), "companion.token");
40667
41395
  }
40668
41396
  function ensureHome() {
40669
41397
  const home = getZelariHome();
40670
- if (!existsSync43(home)) {
40671
- mkdirSync19(home, { recursive: true });
41398
+ if (!existsSync44(home)) {
41399
+ mkdirSync20(home, { recursive: true });
40672
41400
  }
40673
41401
  }
40674
41402
  function loadCompanionConfig() {
40675
- const path48 = getCompanionConfigPath();
40676
- if (!existsSync43(path48)) {
41403
+ const path51 = getCompanionConfigPath();
41404
+ if (!existsSync44(path51)) {
40677
41405
  return { projects: [] };
40678
41406
  }
40679
41407
  try {
40680
- const raw = JSON.parse(readFileSync34(path48, "utf8"));
41408
+ const raw = JSON.parse(readFileSync36(path51, "utf8"));
40681
41409
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
40682
41410
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
40683
41411
  ).map((p3) => ({
@@ -40696,7 +41424,7 @@ function loadCompanionConfig() {
40696
41424
  }
40697
41425
  function saveCompanionConfig(cfg) {
40698
41426
  ensureHome();
40699
- writeFileSync21(
41427
+ writeFileSync22(
40700
41428
  getCompanionConfigPath(),
40701
41429
  JSON.stringify(
40702
41430
  {
@@ -40715,16 +41443,16 @@ function loadOrCreateToken(explicit) {
40715
41443
  return { token: explicit.trim(), created: false };
40716
41444
  }
40717
41445
  ensureHome();
40718
- const path48 = getCompanionTokenPath();
40719
- if (existsSync43(path48)) {
40720
- const t = readFileSync34(path48, "utf8").trim();
41446
+ const path51 = getCompanionTokenPath();
41447
+ if (existsSync44(path51)) {
41448
+ const t = readFileSync36(path51, "utf8").trim();
40721
41449
  if (t) return { token: t, created: false };
40722
41450
  }
40723
41451
  const token = randomBytes4(24).toString("base64url");
40724
- writeFileSync21(path48, token + "\n", "utf8");
41452
+ writeFileSync22(path51, token + "\n", "utf8");
40725
41453
  try {
40726
41454
  const fs30 = __require("node:fs");
40727
- fs30.chmodSync?.(path48, 384);
41455
+ fs30.chmodSync?.(path51, 384);
40728
41456
  } catch {
40729
41457
  }
40730
41458
  return { token, created: true };
@@ -40749,17 +41477,17 @@ function mergeProjects(cfg, extraPaths) {
40749
41477
  byId.set(p3.id, p3);
40750
41478
  }
40751
41479
  for (const raw of extraPaths) {
40752
- const path48 = raw.trim();
40753
- if (!path48) continue;
40754
- let id = slugFromPath(path48);
41480
+ const path51 = raw.trim();
41481
+ if (!path51) continue;
41482
+ let id = slugFromPath(path51);
40755
41483
  let n = 2;
40756
- while (byId.has(id) && byId.get(id).path !== path48) {
40757
- id = `${slugFromPath(path48)}-${n++}`;
41484
+ while (byId.has(id) && byId.get(id).path !== path51) {
41485
+ id = `${slugFromPath(path51)}-${n++}`;
40758
41486
  }
40759
41487
  byId.set(id, {
40760
41488
  id,
40761
- name: slugFromPath(path48),
40762
- path: path48
41489
+ name: slugFromPath(path51),
41490
+ path: path51
40763
41491
  });
40764
41492
  }
40765
41493
  return [...byId.values()];
@@ -40807,11 +41535,11 @@ var init_config = __esm({
40807
41535
  });
40808
41536
 
40809
41537
  // src/cli/companion/runManager.ts
40810
- import { spawn as spawn12 } from "node:child_process";
41538
+ import { spawn as spawn13 } from "node:child_process";
40811
41539
  import { createInterface as createInterface2 } from "node:readline";
40812
41540
  import { randomUUID as randomUUID8 } from "node:crypto";
40813
- import { writeFileSync as writeFileSync22, unlinkSync as unlinkSync2 } from "node:fs";
40814
- import { join as join36 } from "node:path";
41541
+ import { writeFileSync as writeFileSync23, unlinkSync as unlinkSync2 } from "node:fs";
41542
+ import { join as join37 } from "node:path";
40815
41543
  import { tmpdir as tmpdir3 } from "node:os";
40816
41544
  var RunManager;
40817
41545
  var init_runManager = __esm({
@@ -40915,15 +41643,15 @@ var init_runManager = __esm({
40915
41643
  }
40916
41644
  let historyFile;
40917
41645
  if (args.history && Array.isArray(args.history) && args.history.length > 0) {
40918
- historyFile = join36(tmpdir3(), `zelari-companion-hist-${id}.json`);
41646
+ historyFile = join37(tmpdir3(), `zelari-companion-hist-${id}.json`);
40919
41647
  try {
40920
- writeFileSync22(historyFile, JSON.stringify(args.history), "utf8");
41648
+ writeFileSync23(historyFile, JSON.stringify(args.history), "utf8");
40921
41649
  argv.push("--history-file", historyFile);
40922
41650
  } catch {
40923
41651
  historyFile = void 0;
40924
41652
  }
40925
41653
  }
40926
- const child = spawn12(process.execPath, argv, {
41654
+ const child = spawn13(process.execPath, argv, {
40927
41655
  cwd: args.cwd,
40928
41656
  env: {
40929
41657
  ...process.env,
@@ -41042,7 +41770,7 @@ __export(serve_exports, {
41042
41770
  runCompanionServe: () => runCompanionServe
41043
41771
  });
41044
41772
  import { createServer as createServer2 } from "node:http";
41045
- import { existsSync as existsSync44 } from "node:fs";
41773
+ import { existsSync as existsSync45 } from "node:fs";
41046
41774
  import { resolve as resolve2 } from "node:path";
41047
41775
  function readBody(req, max = 2e6) {
41048
41776
  return new Promise((resolveBody, reject) => {
@@ -41090,7 +41818,7 @@ async function runCompanionServe(opts = {}) {
41090
41818
  let projects = mergeProjects(fileCfg, opts.projects ?? []);
41091
41819
  projects = projects.filter((p3) => {
41092
41820
  const abs = resolve2(p3.path);
41093
- if (!existsSync44(abs)) {
41821
+ if (!existsSync45(abs)) {
41094
41822
  process.stderr.write(
41095
41823
  `[zelari-code serve] skip missing project path: ${p3.path}
41096
41824
  `
@@ -41136,9 +41864,9 @@ async function runCompanionServe(opts = {}) {
41136
41864
  return;
41137
41865
  }
41138
41866
  const url2 = parseUrl(req);
41139
- const path48 = url2.pathname.replace(/\/+$/, "") || "/";
41867
+ const path51 = url2.pathname.replace(/\/+$/, "") || "/";
41140
41868
  try {
41141
- if (req.method === "GET" && (path48 === "/health" || path48 === "/v1/health")) {
41869
+ if (req.method === "GET" && (path51 === "/health" || path51 === "/v1/health")) {
41142
41870
  sendJson2(res, 200, {
41143
41871
  ok: true,
41144
41872
  service: "zelari-companion",
@@ -41150,18 +41878,18 @@ async function runCompanionServe(opts = {}) {
41150
41878
  });
41151
41879
  return;
41152
41880
  }
41153
- if (path48.startsWith("/v1")) {
41881
+ if (path51.startsWith("/v1")) {
41154
41882
  if (!tokenMatches(token, getBearer(req))) {
41155
41883
  sendJson2(res, 401, { ok: false, error: "unauthorized" });
41156
41884
  return;
41157
41885
  }
41158
41886
  }
41159
- if (req.method === "GET" && path48 === "/v1/config") {
41887
+ if (req.method === "GET" && path51 === "/v1/config") {
41160
41888
  const snap = buildDesktopConfigSnapshot();
41161
41889
  sendJson2(res, 200, { ok: true, ...snap });
41162
41890
  return;
41163
41891
  }
41164
- if (req.method === "GET" && path48 === "/v1/projects") {
41892
+ if (req.method === "GET" && path51 === "/v1/projects") {
41165
41893
  sendJson2(res, 200, {
41166
41894
  ok: true,
41167
41895
  projects: projects.map((p3) => ({
@@ -41172,7 +41900,7 @@ async function runCompanionServe(opts = {}) {
41172
41900
  });
41173
41901
  return;
41174
41902
  }
41175
- if (req.method === "GET" && path48 === "/v1/runs") {
41903
+ if (req.method === "GET" && path51 === "/v1/runs") {
41176
41904
  sendJson2(res, 200, {
41177
41905
  ok: true,
41178
41906
  active: runs.getActive(),
@@ -41190,7 +41918,7 @@ async function runCompanionServe(opts = {}) {
41190
41918
  });
41191
41919
  return;
41192
41920
  }
41193
- if (req.method === "POST" && path48 === "/v1/runs") {
41921
+ if (req.method === "POST" && path51 === "/v1/runs") {
41194
41922
  const raw = await readBody(req);
41195
41923
  let body = {};
41196
41924
  try {
@@ -41237,7 +41965,7 @@ async function runCompanionServe(opts = {}) {
41237
41965
  });
41238
41966
  return;
41239
41967
  }
41240
- const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path48);
41968
+ const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path51);
41241
41969
  if (req.method === "GET" && eventsMatch) {
41242
41970
  const runId = eventsMatch[1];
41243
41971
  const run = runs.getRun(runId);
@@ -41302,7 +42030,7 @@ async function runCompanionServe(opts = {}) {
41302
42030
  }, 500);
41303
42031
  return;
41304
42032
  }
41305
- const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path48);
42033
+ const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path51);
41306
42034
  if (req.method === "POST" && cancelMatch) {
41307
42035
  const runId = cancelMatch[1];
41308
42036
  const result = runs.cancel(runId);
@@ -41411,26 +42139,26 @@ __export(doctor_exports, {
41411
42139
  runDoctor: () => runDoctor
41412
42140
  });
41413
42141
  import { execSync as execSync2 } from "node:child_process";
41414
- import { existsSync as existsSync45, readFileSync as readFileSync35, readlinkSync, statSync as statSync7 } from "node:fs";
42142
+ import { existsSync as existsSync46, readFileSync as readFileSync37, readlinkSync, statSync as statSync7 } from "node:fs";
41415
42143
  import { createRequire as createRequire3 } from "node:module";
41416
42144
  import { fileURLToPath as fileURLToPath2 } from "node:url";
41417
- import path47 from "node:path";
42145
+ import path49 from "node:path";
41418
42146
  function findPackageRoot(start) {
41419
42147
  let dir = start;
41420
42148
  for (let i = 0; i < 6; i += 1) {
41421
- const candidate = path47.join(dir, "package.json");
41422
- if (existsSync45(candidate)) {
42149
+ const candidate = path49.join(dir, "package.json");
42150
+ if (existsSync46(candidate)) {
41423
42151
  try {
41424
- const pkg = JSON.parse(readFileSync35(candidate, "utf8"));
42152
+ const pkg = JSON.parse(readFileSync37(candidate, "utf8"));
41425
42153
  if (pkg.name === "zelari-code") return dir;
41426
42154
  } catch {
41427
42155
  }
41428
42156
  }
41429
- const parent = path47.dirname(dir);
42157
+ const parent = path49.dirname(dir);
41430
42158
  if (parent === dir) break;
41431
42159
  dir = parent;
41432
42160
  }
41433
- return path47.resolve(__dirname3, "..", "..", "..");
42161
+ return path49.resolve(__dirname3, "..", "..", "..");
41434
42162
  }
41435
42163
  function tryExec(cmd) {
41436
42164
  try {
@@ -41444,8 +42172,8 @@ function tryExec(cmd) {
41444
42172
  }
41445
42173
  function readPackageJson3() {
41446
42174
  try {
41447
- const pkgPath = path47.join(packageRoot, "package.json");
41448
- return JSON.parse(readFileSync35(pkgPath, "utf8"));
42175
+ const pkgPath = path49.join(packageRoot, "package.json");
42176
+ return JSON.parse(readFileSync37(pkgPath, "utf8"));
41449
42177
  } catch {
41450
42178
  return null;
41451
42179
  }
@@ -41460,8 +42188,8 @@ function checkShim(pkgName) {
41460
42188
  }
41461
42189
  const isWin = process.platform === "win32";
41462
42190
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
41463
- const shimPath = path47.join(prefix, shimName);
41464
- if (!existsSync45(shimPath)) {
42191
+ const shimPath = path49.join(prefix, shimName);
42192
+ if (!existsSync46(shimPath)) {
41465
42193
  return FAIL(
41466
42194
  `shim not found at ${shimPath}
41467
42195
  fix: npm install -g ${pkgName}@latest --force`
@@ -41470,7 +42198,7 @@ function checkShim(pkgName) {
41470
42198
  try {
41471
42199
  const st = statSync7(shimPath);
41472
42200
  if (isWin) {
41473
- const content = readFileSync35(shimPath, "utf8");
42201
+ const content = readFileSync37(shimPath, "utf8");
41474
42202
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
41475
42203
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
41476
42204
  }
@@ -41488,8 +42216,8 @@ function checkShim(pkgName) {
41488
42216
  fix: npm install -g ${pkgName}@latest --force`
41489
42217
  );
41490
42218
  }
41491
- const resolved = path47.resolve(path47.dirname(shimPath), target);
41492
- const expected = path47.join(
42219
+ const resolved = path49.resolve(path49.dirname(shimPath), target);
42220
+ const expected = path49.join(
41493
42221
  prefix,
41494
42222
  "node_modules",
41495
42223
  pkgName,
@@ -41528,8 +42256,8 @@ function checkNode(pkg) {
41528
42256
  return OK(`node ${raw}`);
41529
42257
  }
41530
42258
  function checkBundle() {
41531
- const bundle = path47.join(packageRoot, "dist", "cli", "main.bundled.js");
41532
- if (!existsSync45(bundle)) {
42259
+ const bundle = path49.join(packageRoot, "dist", "cli", "main.bundled.js");
42260
+ if (!existsSync46(bundle)) {
41533
42261
  return FAIL(
41534
42262
  `dist/cli/main.bundled.js missing at ${bundle}
41535
42263
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -41549,7 +42277,7 @@ function checkRuntimeDeps() {
41549
42277
  const missing = [];
41550
42278
  for (const dep of required2) {
41551
42279
  try {
41552
- const localReq = createRequire3(path47.join(packageRoot, "package.json"));
42280
+ const localReq = createRequire3(path49.join(packageRoot, "package.json"));
41553
42281
  localReq.resolve(dep);
41554
42282
  } catch {
41555
42283
  missing.push(dep);
@@ -41725,7 +42453,7 @@ var init_doctor = __esm({
41725
42453
  "use strict";
41726
42454
  init_prereqChecks();
41727
42455
  require3 = createRequire3(import.meta.url);
41728
- __dirname3 = path47.dirname(fileURLToPath2(import.meta.url));
42456
+ __dirname3 = path49.dirname(fileURLToPath2(import.meta.url));
41729
42457
  packageRoot = findPackageRoot(__dirname3);
41730
42458
  OK = (message) => ({
41731
42459
  ok: true,
@@ -41903,6 +42631,154 @@ var init_fixBudget = __esm({
41903
42631
  }
41904
42632
  });
41905
42633
 
42634
+ // src/cli/commands/inspect.ts
42635
+ var inspect_exports = {};
42636
+ __export(inspect_exports, {
42637
+ collectInspectReport: () => collectInspectReport,
42638
+ runInspect: () => runInspect
42639
+ });
42640
+ import path50 from "node:path";
42641
+ import { existsSync as existsSync47, readFileSync as readFileSync38, readdirSync as readdirSync9 } from "node:fs";
42642
+ import { homedir as homedir14 } from "node:os";
42643
+ async function collectInspectReport(cwd = process.cwd()) {
42644
+ ensureBuiltinSkillsLoadedSync();
42645
+ const snap = listSkillsSnapshot(cwd);
42646
+ const mcp = listMcpServers(cwd);
42647
+ const userMcpPath = path50.join(homedir14(), ".zelari-code", "mcp.json");
42648
+ const projectMcpPath = path50.join(cwd, ".zelari", "mcp.json");
42649
+ const globalHooks = globalHooksDir();
42650
+ const projectHooks = projectHooksDir(cwd);
42651
+ const projectTrusted = isFolderTrusted(cwd);
42652
+ const skillsByScope = (scope) => snap.skills.filter((s) => s.scope === scope).length;
42653
+ const pluginStates = await Promise.all(
42654
+ PLUGINS.map(async (p3) => ({
42655
+ id: p3.id,
42656
+ label: p3.label ?? p3.id,
42657
+ installed: await p3.detect(cwd)
42658
+ }))
42659
+ );
42660
+ return {
42661
+ schemaVersion: 1,
42662
+ zelariVersion: getCurrentVersion(),
42663
+ cwd,
42664
+ platform: `${process.platform}-${process.arch}`,
42665
+ node: process.version,
42666
+ phase: process.env.ZELARI_PHASE ?? "build",
42667
+ mode: process.env.ZELARI_MODE ?? "kraken",
42668
+ trust: {
42669
+ store: getTrustStorePath(),
42670
+ trusted: projectTrusted,
42671
+ folders: listTrustedFolders()
42672
+ },
42673
+ configSources: [
42674
+ { path: userMcpPath, exists: existsSync47(userMcpPath) },
42675
+ { path: projectMcpPath, exists: existsSync47(projectMcpPath) },
42676
+ { path: path50.join(homedir14(), ".zelari-code", "provider.json"), exists: existsSync47(path50.join(homedir14(), ".zelari-code", "provider.json")) },
42677
+ { path: path50.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync47(path50.join(cwd, ".zelari", "AGENTS.md")) },
42678
+ { path: path50.join(cwd, "AGENTS.md"), exists: existsSync47(path50.join(cwd, "AGENTS.md")) }
42679
+ ],
42680
+ skills: {
42681
+ total: snap.skills.length,
42682
+ builtin: skillsByScope("builtin"),
42683
+ user: skillsByScope("user"),
42684
+ project: skillsByScope("project"),
42685
+ compat: skillsByScope("compat")
42686
+ },
42687
+ mcp: {
42688
+ user: mcp.servers.filter((s) => s.scope === "user").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
42689
+ project: mcp.servers.filter((s) => s.scope === "project").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
42690
+ projectTrusted,
42691
+ projectConfigExists: existsSync47(projectMcpPath)
42692
+ },
42693
+ hooks: {
42694
+ global: {
42695
+ path: globalHooks,
42696
+ active: true,
42697
+ files: listJsonFiles(globalHooks)
42698
+ },
42699
+ project: {
42700
+ path: projectHooks,
42701
+ active: projectTrusted,
42702
+ files: listJsonFiles(projectHooks)
42703
+ }
42704
+ },
42705
+ plugins: pluginStates,
42706
+ agentsMd: findAgentsMd(cwd)
42707
+ };
42708
+ }
42709
+ function listJsonFiles(dir) {
42710
+ try {
42711
+ return readdirSync9(dir).filter((f) => f.endsWith(".json")).sort();
42712
+ } catch {
42713
+ return [];
42714
+ }
42715
+ }
42716
+ function findAgentsMd(cwd) {
42717
+ const candidates = [
42718
+ path50.join(cwd, "AGENTS.md"),
42719
+ path50.join(cwd, ".zelari", "AGENTS.md")
42720
+ ];
42721
+ const found = [];
42722
+ for (const c of candidates) {
42723
+ if (existsSync47(c)) {
42724
+ try {
42725
+ const text = readFileSync38(c, "utf8");
42726
+ found.push(`${c} (${text.length} bytes)`);
42727
+ } catch {
42728
+ found.push(`${c} (unreadable)`);
42729
+ }
42730
+ }
42731
+ }
42732
+ return found;
42733
+ }
42734
+ function formatHuman(r) {
42735
+ const lines = [
42736
+ `zelari-code inspect v${r.zelariVersion}`,
42737
+ ` cwd: ${r.cwd}`,
42738
+ ` platform: ${r.platform} node ${r.node}`,
42739
+ ` phase: ${r.phase} mode: ${r.mode}`,
42740
+ ``,
42741
+ `trust: ${r.trust.trusted ? "TRUSTED \u2714" : "NOT TRUSTED \u2716"} (store: ${r.trust.store})`,
42742
+ ...r.trust.folders.map((f) => ` - ${f.path} (${f.trustedAt})`),
42743
+ ``,
42744
+ `skills: ${r.skills.total} total (builtin ${r.skills.builtin}, user ${r.skills.user}, project ${r.skills.project}, compat ${r.skills.compat})`,
42745
+ ``,
42746
+ `MCP:`,
42747
+ ` user: ${r.mcp.user.length ? r.mcp.user.map((m) => m.name).join(", ") : "(none)"}`,
42748
+ ` project: ${r.mcp.projectConfigExists ? r.mcp.projectTrusted ? r.mcp.project.map((m) => m.name).join(", ") || "(configured, no servers)" : "IGNORED (folder not trusted)" : "(no .zelari/mcp.json)"}`,
42749
+ ``,
42750
+ `hooks:`,
42751
+ ` global: ${r.hooks.global.active ? "active" : "inactive"} @ ${r.hooks.global.path} (${r.hooks.global.files.length} file(s))`,
42752
+ ` project: ${r.hooks.project.active ? "active" : "IGNORED (folder not trusted)"} @ ${r.hooks.project.path} (${r.hooks.project.files.length} file(s))`,
42753
+ ``,
42754
+ `plugins: ${r.plugins.length ? r.plugins.map((p3) => `${p3.id}${p3.installed ? "" : " (missing)"}`).join(", ") : "(none)"}`,
42755
+ ``,
42756
+ `AGENTS.md rules:`,
42757
+ ...r.agentsMd.length ? r.agentsMd.map((a) => ` - ${a}`) : [" (none)"]
42758
+ ];
42759
+ return lines.join("\n");
42760
+ }
42761
+ async function runInspect(opts = {}) {
42762
+ const report = await collectInspectReport(opts.cwd ?? process.cwd());
42763
+ if (opts.json) {
42764
+ console.log(JSON.stringify(report, null, 2));
42765
+ } else {
42766
+ console.log(formatHuman(report));
42767
+ }
42768
+ return 0;
42769
+ }
42770
+ var init_inspect = __esm({
42771
+ "src/cli/commands/inspect.ts"() {
42772
+ "use strict";
42773
+ init_folderTrust();
42774
+ init_lifecycleHooks();
42775
+ init_mcpConfigIo();
42776
+ init_skillConfigIo();
42777
+ init_registry3();
42778
+ init_updater();
42779
+ }
42780
+ });
42781
+
41906
42782
  // src/cli/main.ts
41907
42783
  import React15 from "react";
41908
42784
  import { render } from "ink";
@@ -44767,15 +45643,15 @@ import { useState as useState5, useRef as useRef3, useCallback, useEffect as use
44767
45643
 
44768
45644
  // src/cli/sessionManager.ts
44769
45645
  init_harness();
44770
- import { promises as fs9, existsSync as existsSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync9, mkdirSync as mkdirSync4, unlinkSync, statSync } from "node:fs";
44771
- import path12 from "node:path";
45646
+ import { promises as fs9, existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync4, unlinkSync, statSync } from "node:fs";
45647
+ import path13 from "node:path";
44772
45648
  import os4 from "node:os";
44773
45649
  import { randomUUID } from "node:crypto";
44774
45650
  function getSessionBaseDir() {
44775
- return process.env.ANATHEMA_SESSIONS_DIR ?? path12.join(os4.homedir(), ".tmp", "zelari-code", "sessions");
45651
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path13.join(os4.homedir(), ".tmp", "zelari-code", "sessions");
44776
45652
  }
44777
45653
  function getCurrentSessionFile() {
44778
- return process.env.ANATHEMA_CURRENT_SESSION_FILE ?? path12.join(os4.homedir(), ".tmp", "zelari-code", "current.txt");
45654
+ return process.env.ANATHEMA_CURRENT_SESSION_FILE ?? path13.join(os4.homedir(), ".tmp", "zelari-code", "current.txt");
44779
45655
  }
44780
45656
  async function ensureSessionDir() {
44781
45657
  await fs9.mkdir(getSessionBaseDir(), { recursive: true });
@@ -44784,7 +45660,7 @@ function getCurrentSessionId() {
44784
45660
  const file2 = getCurrentSessionFile();
44785
45661
  if (!existsSync9(file2)) return null;
44786
45662
  try {
44787
- const content = readFileSync11(file2, "utf-8").trim();
45663
+ const content = readFileSync12(file2, "utf-8").trim();
44788
45664
  return content.length > 0 ? content : null;
44789
45665
  } catch {
44790
45666
  return null;
@@ -44792,7 +45668,7 @@ function getCurrentSessionId() {
44792
45668
  }
44793
45669
  function setCurrentSessionId(id) {
44794
45670
  const file2 = getCurrentSessionFile();
44795
- mkdirSync4(path12.dirname(file2), { recursive: true });
45671
+ mkdirSync4(path13.dirname(file2), { recursive: true });
44796
45672
  writeFileSync9(file2, id, "utf-8");
44797
45673
  }
44798
45674
  function clearCurrentSessionId() {
@@ -44804,13 +45680,13 @@ function clearCurrentSessionId() {
44804
45680
  }
44805
45681
  }
44806
45682
  function getCurrentBranchFile() {
44807
- return process.env.ANATHEMA_CURRENT_BRANCH_FILE ?? path12.join(path12.dirname(getCurrentSessionFile()), "currentBranch.txt");
45683
+ return process.env.ANATHEMA_CURRENT_BRANCH_FILE ?? path13.join(path13.dirname(getCurrentSessionFile()), "currentBranch.txt");
44808
45684
  }
44809
45685
  function getCurrentBranch() {
44810
45686
  const file2 = getCurrentBranchFile();
44811
45687
  if (!existsSync9(file2)) return null;
44812
45688
  try {
44813
- const content = readFileSync11(file2, "utf-8").trim();
45689
+ const content = readFileSync12(file2, "utf-8").trim();
44814
45690
  return content.length > 0 ? content : null;
44815
45691
  } catch {
44816
45692
  return null;
@@ -44818,7 +45694,7 @@ function getCurrentBranch() {
44818
45694
  }
44819
45695
  function setCurrentBranch(name) {
44820
45696
  const file2 = getCurrentBranchFile();
44821
- mkdirSync4(path12.dirname(file2), { recursive: true });
45697
+ mkdirSync4(path13.dirname(file2), { recursive: true });
44822
45698
  writeFileSync9(file2, name, "utf-8");
44823
45699
  }
44824
45700
  function newSessionId() {
@@ -44837,7 +45713,7 @@ async function listSessions() {
44837
45713
  for (const entry of entries) {
44838
45714
  if (!entry.endsWith(".jsonl")) continue;
44839
45715
  const id = entry.replace(/\.jsonl$/, "");
44840
- const filePath = path12.join(baseDir, entry);
45716
+ const filePath = path13.join(baseDir, entry);
44841
45717
  try {
44842
45718
  const events = await readSession(filePath);
44843
45719
  let firstTs = 0;
@@ -44884,7 +45760,7 @@ ${lines.join("\n")}`
44884
45760
  return { message: `[${kind}] handled` };
44885
45761
  }
44886
45762
  async function loadSessionEvents(id) {
44887
- const filePath = path12.join(getSessionBaseDir(), `${id}.jsonl`);
45763
+ const filePath = path13.join(getSessionBaseDir(), `${id}.jsonl`);
44888
45764
  return readSession(filePath);
44889
45765
  }
44890
45766
 
@@ -45176,15 +46052,15 @@ import { useState as useState6, useRef as useRef4, useCallback as useCallback2 }
45176
46052
 
45177
46053
  // src/cli/metrics.ts
45178
46054
  import { promises as fs10, existsSync as existsSync10, statSync as statSync2, renameSync, appendFileSync as appendFileSync2, mkdirSync as mkdirSync5 } from "node:fs";
45179
- import path13 from "node:path";
46055
+ import path14 from "node:path";
45180
46056
  import os5 from "node:os";
45181
46057
  var METRICS_ROTATE_BYTES = 10 * 1024 * 1024;
45182
46058
  var MetricsLogger = class {
45183
46059
  file;
45184
46060
  writeQueue = Promise.resolve();
45185
46061
  constructor(file2) {
45186
- this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path13.join(os5.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
45187
- mkdirSync5(path13.dirname(this.file), { recursive: true });
46062
+ this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path14.join(os5.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
46063
+ mkdirSync5(path14.dirname(this.file), { recursive: true });
45188
46064
  }
45189
46065
  /** Fire-and-forget record append. */
45190
46066
  record(rec) {
@@ -47315,6 +48191,8 @@ function handleSlashCommand(text, availableSkills) {
47315
48191
  /index [status] \u2014 build the semantic code index for semantic_search
47316
48192
  /mode [kraken|council|zelari] \u2014 switch dispatch mode (same as shift+tab; agent=alias kraken)
47317
48193
  /kraken [sessionId] \u2014 show Kraken tentacle radio (last spawns)
48194
+ /trust [path] \u2014 trust a folder so project MCP + project hooks load
48195
+ /trust remove [path] \u2014 revoke folder trust
47318
48196
  /help \u2014 show this help
47319
48197
  /exit \u2014 exit the CLI
47320
48198
 
@@ -47803,6 +48681,23 @@ ${formatSkillList(availableSkills)}`
47803
48681
  message: "Usage: /state status | /state commit [label] | /state show [id] | /state restore [id] [--no-tree]\nDurable verified accumulation under .zelari/state/ (not memory RAG)."
47804
48682
  };
47805
48683
  }
48684
+ case "trust": {
48685
+ const first = args[0];
48686
+ if (first === "remove" || first === "untrust") {
48687
+ return {
48688
+ handled: true,
48689
+ kind: "trust_remove",
48690
+ trustPath: args[1],
48691
+ message: "Usage: /trust remove [path]"
48692
+ };
48693
+ }
48694
+ return {
48695
+ handled: true,
48696
+ kind: first ? "trust_add" : "trust_status",
48697
+ trustPath: first && first !== "." ? first : process.cwd(),
48698
+ message: first ? "Usage: /trust [path]" : "Usage: /trust [path] | /trust remove [path]"
48699
+ };
48700
+ }
47806
48701
  case "cache": {
47807
48702
  const sub = (args[0] ?? "stats").toLowerCase();
47808
48703
  if (sub === "stats" || sub === "") {
@@ -47890,7 +48785,7 @@ ${formatSkillList(availableSkills)}`
47890
48785
  // src/cli/gitOps.ts
47891
48786
  import { execFile as execFile4 } from "node:child_process";
47892
48787
  import { promisify as promisify3 } from "node:util";
47893
- import path33 from "node:path";
48788
+ import path35 from "node:path";
47894
48789
  var execFileAsync3 = promisify3(execFile4);
47895
48790
  async function git3(cwd, args) {
47896
48791
  try {
@@ -47936,7 +48831,7 @@ async function undoWorkingChanges(opts = {}) {
47936
48831
  };
47937
48832
  }
47938
48833
  function defaultProjectRoot() {
47939
- return path33.resolve(__dirname, "..", "..", "..");
48834
+ return path35.resolve(__dirname, "..", "..", "..");
47940
48835
  }
47941
48836
 
47942
48837
  // src/cli/slashHandlers/git.ts
@@ -48213,6 +49108,45 @@ function handleCacheStats(ctx) {
48213
49108
  );
48214
49109
  }
48215
49110
 
49111
+ // src/cli/slashHandlers/trust.ts
49112
+ init_folderTrust();
49113
+ function resolvePath(raw) {
49114
+ if (!raw || raw === "." || raw === "./") return process.cwd();
49115
+ return raw;
49116
+ }
49117
+ function handleTrustStatus(ctx, target) {
49118
+ const trusted = isFolderTrusted(target);
49119
+ const all = listTrustedFolders();
49120
+ const lines = [
49121
+ `[trust] ${target}`,
49122
+ ` status: ${trusted ? "trusted \u2714" : "NOT trusted \u2716"}`,
49123
+ ` store: ${getTrustStorePath()}`,
49124
+ ` folders: ${all.length === 0 ? "(none)" : ""}`,
49125
+ ...all.map((f) => ` - ${f.path} (${f.trustedAt})`),
49126
+ "",
49127
+ trusted ? " Project MCP + project hooks are enabled for this folder." : " Project MCP (.zelari/mcp.json) and project hooks (.zelari/hooks/) are IGNORED until you run /trust ."
49128
+ ];
49129
+ appendSystem(ctx.setMessages, lines.join("\n"));
49130
+ }
49131
+ function handleTrust(ctx, rawPath) {
49132
+ const target = resolvePath(rawPath);
49133
+ const res = trustFolder(target);
49134
+ appendSystem(
49135
+ ctx.setMessages,
49136
+ `[trust] trusted ${res.path}
49137
+ Project MCP + project hooks will load on the next session/turn (run /trust to confirm).`
49138
+ );
49139
+ }
49140
+ function handleUntrust(ctx, rawPath) {
49141
+ const target = resolvePath(rawPath);
49142
+ const res = untrustFolder(target);
49143
+ appendSystem(
49144
+ ctx.setMessages,
49145
+ res.removed ? `[trust] removed trust for ${target}
49146
+ Project MCP + project hooks are now ignored for this folder.` : `[trust] ${target} was not trusted (nothing to remove).`
49147
+ );
49148
+ }
49149
+
48216
49150
  // src/cli/slashHandlers/semantic.ts
48217
49151
  init_semantic();
48218
49152
  init_provider();
@@ -48338,7 +49272,7 @@ import { promises as fs22 } from "node:fs";
48338
49272
  init_zod();
48339
49273
  init_taskTool();
48340
49274
  import { promises as fs21 } from "node:fs";
48341
- import path37 from "node:path";
49275
+ import path39 from "node:path";
48342
49276
  import { randomBytes as randomBytes3 } from "node:crypto";
48343
49277
  var CsvFanoutArgsSchema = external_exports.object({
48344
49278
  csv_path: external_exports.string().min(1),
@@ -48432,8 +49366,8 @@ function resolveMaxConcurrency(env = process.env) {
48432
49366
  }
48433
49367
  async function runCsvFanout(args, deps, opts) {
48434
49368
  const start = Date.now();
48435
- const absCsv = path37.isAbsolute(args.csv_path) ? args.csv_path : path37.join(opts.parentCwd, args.csv_path);
48436
- const absOut = path37.isAbsolute(args.output_csv_path) ? args.output_csv_path : path37.join(opts.parentCwd, args.output_csv_path);
49369
+ const absCsv = path39.isAbsolute(args.csv_path) ? args.csv_path : path39.join(opts.parentCwd, args.csv_path);
49370
+ const absOut = path39.isAbsolute(args.output_csv_path) ? args.output_csv_path : path39.join(opts.parentCwd, args.output_csv_path);
48437
49371
  const { headers, rows } = await readCsv(absCsv);
48438
49372
  if (headers.length === 0) {
48439
49373
  throw new Error(`kraken_csv_fanout: ${absCsv} is empty`);
@@ -48489,7 +49423,7 @@ async function runCsvFanout(args, deps, opts) {
48489
49423
  errored += 1;
48490
49424
  errors.push(`${row[args.id_column] ?? i}: ${res.error}`);
48491
49425
  }
48492
- await fs21.mkdir(path37.dirname(absOut), { recursive: true });
49426
+ await fs21.mkdir(path39.dirname(absOut), { recursive: true });
48493
49427
  await queueWrite(serializeCsv(outHeaders, outputRecords));
48494
49428
  }
48495
49429
  }
@@ -48683,7 +49617,7 @@ function splitArgs(s) {
48683
49617
 
48684
49618
  // src/cli/slashHandlers/krakenWorkbench.ts
48685
49619
  import { promises as fs23 } from "node:fs";
48686
- import path38 from "node:path";
49620
+ import path40 from "node:path";
48687
49621
 
48688
49622
  // src/cli/kraken/workbenchView.ts
48689
49623
  var EMPTY = {
@@ -48800,14 +49734,14 @@ function formatWorkbenchForTerminal(p3) {
48800
49734
 
48801
49735
  // src/cli/slashHandlers/krakenWorkbench.ts
48802
49736
  async function handleKrakenWorkbench(ctx) {
48803
- const dir = path38.join(ctx.cwd, ".zelari", "radio");
49737
+ const dir = path40.join(ctx.cwd, ".zelari", "radio");
48804
49738
  let latest = null;
48805
49739
  let latestMtime = 0;
48806
49740
  try {
48807
49741
  const files = await fs23.readdir(dir);
48808
49742
  for (const f of files) {
48809
49743
  if (!f.startsWith("workbench-") || !f.endsWith(".md")) continue;
48810
- const full = path38.join(dir, f);
49744
+ const full = path40.join(dir, f);
48811
49745
  const stat = await fs23.stat(full);
48812
49746
  if (stat.mtimeMs > latestMtime) {
48813
49747
  latestMtime = stat.mtimeMs;
@@ -48824,10 +49758,10 @@ async function handleKrakenWorkbench(ctx) {
48824
49758
  const parsed = parseWorkbench(content);
48825
49759
  const rendered = formatWorkbenchForTerminal(parsed);
48826
49760
  if (!rendered.trim()) {
48827
- appendSystem(ctx.setMessages, `[kraken workbench] ${path38.basename(latest)}: (no nodes / no events yet)`);
49761
+ appendSystem(ctx.setMessages, `[kraken workbench] ${path40.basename(latest)}: (no nodes / no events yet)`);
48828
49762
  return;
48829
49763
  }
48830
- appendSystem(ctx.setMessages, `[kraken workbench] ${path38.basename(latest)}:
49764
+ appendSystem(ctx.setMessages, `[kraken workbench] ${path40.basename(latest)}:
48831
49765
  ${rendered}`);
48832
49766
  }
48833
49767
 
@@ -48979,8 +49913,8 @@ init_registry3();
48979
49913
  // src/cli/plugins/installer.ts
48980
49914
  init_cmdline();
48981
49915
  init_updater();
48982
- import { spawn as spawn11 } from "node:child_process";
48983
- async function installPlugin(spec, cwd, executor = spawn11) {
49916
+ import { spawn as spawn12 } from "node:child_process";
49917
+ async function installPlugin(spec, cwd, executor = spawn12) {
48984
49918
  const scopeFlag = spec.installScope === "global" ? "-g" : "-D";
48985
49919
  const args = ["install", scopeFlag, spec.npmPackage];
48986
49920
  let result = await runNpm2(executor, args, cwd, "shim");
@@ -49129,15 +50063,15 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
49129
50063
 
49130
50064
  // src/cli/slashHandlers/promoteMember.ts
49131
50065
  import { promises as fs24 } from "node:fs";
49132
- import path41 from "node:path";
50066
+ import path43 from "node:path";
49133
50067
  import os10 from "node:os";
49134
50068
  async function handlePromoteMember(ctx, memberId) {
49135
50069
  try {
49136
50070
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
49137
50071
  const { skill, markdown } = promoteMember2(memberId);
49138
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path41.join(os10.homedir(), ".tmp", "zelari-code", "skills");
50072
+ const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path43.join(os10.homedir(), ".tmp", "zelari-code", "skills");
49139
50073
  await fs24.mkdir(skillDir, { recursive: true });
49140
- const filePath = path41.join(skillDir, `${skill.id}.md`);
50074
+ const filePath = path43.join(skillDir, `${skill.id}.md`);
49141
50075
  await fs24.writeFile(filePath, markdown, "utf8");
49142
50076
  appendSystem(
49143
50077
  ctx.setMessages,
@@ -49154,33 +50088,33 @@ async function handlePromoteMember(ctx, memberId) {
49154
50088
  }
49155
50089
 
49156
50090
  // src/cli/branchManager.ts
49157
- import { promises as fs25, existsSync as existsSync38, readFileSync as readFileSync30, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16, statSync as statSync4, rmSync as rmSync3 } from "node:fs";
49158
- import path42 from "node:path";
50091
+ import { promises as fs25, existsSync as existsSync39, readFileSync as readFileSync32, writeFileSync as writeFileSync20, mkdirSync as mkdirSync17, statSync as statSync4, rmSync as rmSync3 } from "node:fs";
50092
+ import path44 from "node:path";
49159
50093
  import os11 from "node:os";
49160
50094
  var META_FILENAME = "meta.json";
49161
50095
  var SESSIONS_SUBDIR = "sessions";
49162
50096
  function getBranchesBaseDir() {
49163
- return process.env.ANATHEMA_BRANCHES_DIR ?? path42.join(os11.homedir(), ".tmp", "zelari-code", "branches");
50097
+ return process.env.ANATHEMA_BRANCHES_DIR ?? path44.join(os11.homedir(), ".tmp", "zelari-code", "branches");
49164
50098
  }
49165
50099
  function getSessionsBaseDir() {
49166
- return process.env.ANATHEMA_SESSIONS_DIR ?? path42.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
50100
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path44.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
49167
50101
  }
49168
50102
  function branchPathFor(name, baseDir) {
49169
- return path42.join(baseDir, name);
50103
+ return path44.join(baseDir, name);
49170
50104
  }
49171
50105
  function metaPathFor(name, baseDir) {
49172
- return path42.join(baseDir, name, META_FILENAME);
50106
+ return path44.join(baseDir, name, META_FILENAME);
49173
50107
  }
49174
50108
  function sessionsPathFor(name, baseDir) {
49175
- return path42.join(baseDir, name, SESSIONS_SUBDIR);
50109
+ return path44.join(baseDir, name, SESSIONS_SUBDIR);
49176
50110
  }
49177
50111
  function readBranchMeta(name, baseDir) {
49178
50112
  const metaPath = metaPathFor(name, baseDir);
49179
- if (!existsSync38(metaPath)) {
50113
+ if (!existsSync39(metaPath)) {
49180
50114
  throw new BranchNotFoundError(`Branch "${name}" not found`);
49181
50115
  }
49182
50116
  try {
49183
- const raw = readFileSync30(metaPath, "utf-8");
50117
+ const raw = readFileSync32(metaPath, "utf-8");
49184
50118
  const parsed = JSON.parse(raw);
49185
50119
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
49186
50120
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -49197,8 +50131,8 @@ function readBranchMeta(name, baseDir) {
49197
50131
  }
49198
50132
  function writeBranchMeta(name, baseDir, meta3) {
49199
50133
  const metaPath = metaPathFor(name, baseDir);
49200
- mkdirSync16(path42.dirname(metaPath), { recursive: true });
49201
- writeFileSync19(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
50134
+ mkdirSync17(path44.dirname(metaPath), { recursive: true });
50135
+ writeFileSync20(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
49202
50136
  }
49203
50137
  async function countSessions(name, baseDir) {
49204
50138
  const sessionsPath = sessionsPathFor(name, baseDir);
@@ -49236,7 +50170,7 @@ var SessionNotFoundError = class extends Error {
49236
50170
  };
49237
50171
  function branchExists(name, baseDir = getBranchesBaseDir()) {
49238
50172
  const bp = branchPathFor(name, baseDir);
49239
- return existsSync38(bp) && existsSync38(metaPathFor(name, baseDir));
50173
+ return existsSync39(bp) && existsSync39(metaPathFor(name, baseDir));
49240
50174
  }
49241
50175
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
49242
50176
  if (!name || name.trim().length === 0) {
@@ -49248,14 +50182,14 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
49248
50182
  if (branchExists(name, baseDir)) {
49249
50183
  throw new BranchAlreadyExistsError(name);
49250
50184
  }
49251
- const sourcePath = path42.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
49252
- if (!existsSync38(sourcePath)) {
50185
+ const sourcePath = path44.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
50186
+ if (!existsSync39(sourcePath)) {
49253
50187
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
49254
50188
  }
49255
50189
  const branchPath = branchPathFor(name, baseDir);
49256
50190
  const branchSessionsPath = sessionsPathFor(name, baseDir);
49257
- mkdirSync16(branchSessionsPath, { recursive: true });
49258
- const destPath = path42.join(branchSessionsPath, `${fromSessionId}.jsonl`);
50191
+ mkdirSync17(branchSessionsPath, { recursive: true });
50192
+ const destPath = path44.join(branchSessionsPath, `${fromSessionId}.jsonl`);
49259
50193
  await fs25.copyFile(sourcePath, destPath);
49260
50194
  const meta3 = {
49261
50195
  name,
@@ -49282,7 +50216,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
49282
50216
  const results = [];
49283
50217
  for (const entry of entries) {
49284
50218
  const metaPath = metaPathFor(entry, baseDir);
49285
- if (!existsSync38(metaPath)) continue;
50219
+ if (!existsSync39(metaPath)) continue;
49286
50220
  try {
49287
50221
  const meta3 = readBranchMeta(entry, baseDir);
49288
50222
  const sessionCount = await countSessions(entry, baseDir);
@@ -49356,14 +50290,14 @@ async function handleBranchCheckout(ctx, branchName) {
49356
50290
 
49357
50291
  // src/cli/slashHandlers/workspace.ts
49358
50292
  import { promises as fs26 } from "node:fs";
49359
- import path43 from "node:path";
50293
+ import path45 from "node:path";
49360
50294
  async function handleWorkspaceShow(ctx, what) {
49361
50295
  try {
49362
- const zelari = path43.join(process.cwd(), ".zelari");
50296
+ const zelari = path45.join(process.cwd(), ".zelari");
49363
50297
  let content;
49364
50298
  switch (what) {
49365
50299
  case "plan": {
49366
- const planPath = path43.join(zelari, "plan.md");
50300
+ const planPath = path45.join(zelari, "plan.md");
49367
50301
  try {
49368
50302
  content = await fs26.readFile(planPath, "utf-8");
49369
50303
  } catch {
@@ -49372,7 +50306,7 @@ async function handleWorkspaceShow(ctx, what) {
49372
50306
  break;
49373
50307
  }
49374
50308
  case "decisions": {
49375
- const decisionsDir = path43.join(zelari, "decisions");
50309
+ const decisionsDir = path45.join(zelari, "decisions");
49376
50310
  try {
49377
50311
  const files = (await fs26.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
49378
50312
  if (files.length === 0) {
@@ -49382,7 +50316,7 @@ async function handleWorkspaceShow(ctx, what) {
49382
50316
  `];
49383
50317
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
49384
50318
  for (const f of files) {
49385
- const raw = await fs26.readFile(path43.join(decisionsDir, f), "utf-8");
50319
+ const raw = await fs26.readFile(path45.join(decisionsDir, f), "utf-8");
49386
50320
  const { meta: meta3, body } = parseFrontmatter2(raw);
49387
50321
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
49388
50322
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -49395,7 +50329,7 @@ async function handleWorkspaceShow(ctx, what) {
49395
50329
  break;
49396
50330
  }
49397
50331
  case "risks": {
49398
- const risksPath = path43.join(zelari, "risks.md");
50332
+ const risksPath = path45.join(zelari, "risks.md");
49399
50333
  try {
49400
50334
  content = await fs26.readFile(risksPath, "utf-8");
49401
50335
  } catch {
@@ -49404,7 +50338,7 @@ async function handleWorkspaceShow(ctx, what) {
49404
50338
  break;
49405
50339
  }
49406
50340
  case "agents": {
49407
- const agentsPath = path43.join(process.cwd(), "AGENTS.MD");
50341
+ const agentsPath = path45.join(process.cwd(), "AGENTS.MD");
49408
50342
  try {
49409
50343
  content = await fs26.readFile(agentsPath, "utf-8");
49410
50344
  } catch {
@@ -49413,7 +50347,7 @@ async function handleWorkspaceShow(ctx, what) {
49413
50347
  break;
49414
50348
  }
49415
50349
  case "docs": {
49416
- const docsDir = path43.join(zelari, "docs");
50350
+ const docsDir = path45.join(zelari, "docs");
49417
50351
  try {
49418
50352
  const files = (await fs26.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
49419
50353
  content = files.length ? `# Docs (${files.length})
@@ -49455,7 +50389,7 @@ async function handleWorkspaceReset(ctx, force) {
49455
50389
  return;
49456
50390
  }
49457
50391
  try {
49458
- const target = path43.join(process.cwd(), ".zelari");
50392
+ const target = path45.join(process.cwd(), ".zelari");
49459
50393
  await fs26.rm(target, { recursive: true, force: true });
49460
50394
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
49461
50395
  } catch (err) {
@@ -49815,11 +50749,11 @@ function handleModelsRefresh(ctx) {
49815
50749
  }
49816
50750
 
49817
50751
  // src/cli/slashHandlers/skills.ts
49818
- import path44 from "node:path";
50752
+ import path46 from "node:path";
49819
50753
  import os12 from "node:os";
49820
50754
 
49821
50755
  // src/cli/skillHistory.ts
49822
- import { promises as fs27, existsSync as existsSync39, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync4, mkdirSync as mkdirSync17 } from "node:fs";
50756
+ import { promises as fs27, existsSync as existsSync40, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync4, mkdirSync as mkdirSync18 } from "node:fs";
49823
50757
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
49824
50758
  async function readSkillHistory(file2) {
49825
50759
  let raw = "";
@@ -49941,7 +50875,7 @@ function handleSkillPicker(ctx, skills, openPicker, fallbackMessage) {
49941
50875
  });
49942
50876
  }
49943
50877
  async function handleSkillStats(ctx, skillId) {
49944
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path44.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
50878
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path46.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
49945
50879
  try {
49946
50880
  const records = await readSkillHistory(historyFile);
49947
50881
  const stats = getSkillStats(records, skillId);
@@ -49957,7 +50891,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
49957
50891
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
49958
50892
  return;
49959
50893
  }
49960
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path44.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
50894
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path46.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
49961
50895
  try {
49962
50896
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
49963
50897
  appendSystem(ctx.setMessages, formatted);
@@ -50347,6 +51281,21 @@ function useSlashDispatch(params) {
50347
51281
  setInput("");
50348
51282
  return;
50349
51283
  }
51284
+ if (result.kind === "trust_status") {
51285
+ handleTrustStatus({ setMessages }, result.trustPath ?? process.cwd());
51286
+ setInput("");
51287
+ return;
51288
+ }
51289
+ if (result.kind === "trust_add") {
51290
+ handleTrust({ setMessages }, result.trustPath ?? process.cwd());
51291
+ setInput("");
51292
+ return;
51293
+ }
51294
+ if (result.kind === "trust_remove") {
51295
+ handleUntrust({ setMessages }, result.trustPath ?? process.cwd());
51296
+ setInput("");
51297
+ return;
51298
+ }
50350
51299
  if (result.kind === "index_build") {
50351
51300
  await handleIndexBuild({ ...baseCtx, cwd: process.cwd() });
50352
51301
  setInput("");
@@ -51114,9 +52063,9 @@ function ContinueKey({ onContinue }) {
51114
52063
  init_providerConfig();
51115
52064
 
51116
52065
  // src/cli/wizard/firstRun.ts
51117
- import { existsSync as existsSync41 } from "node:fs";
52066
+ import { existsSync as existsSync42 } from "node:fs";
51118
52067
  function shouldRunWizard(input) {
51119
- const exists = input.exists ?? existsSync41;
52068
+ const exists = input.exists ?? existsSync42;
51120
52069
  if (input.hasResetConfigFlag) {
51121
52070
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
51122
52071
  }
@@ -51401,7 +52350,7 @@ init_keyStore();
51401
52350
  init_providerConfig();
51402
52351
  init_openai_compatible();
51403
52352
  init_phase();
51404
- import { readFileSync as readFileSync32 } from "node:fs";
52353
+ import { readFileSync as readFileSync34 } from "node:fs";
51405
52354
  function parseHeadlessFlags(argv) {
51406
52355
  if (!argv.includes("--headless")) {
51407
52356
  return { options: null };
@@ -51473,7 +52422,7 @@ function parseHeadlessFlags(argv) {
51473
52422
  let raw = null;
51474
52423
  if (arg === "--history-file") {
51475
52424
  try {
51476
- raw = readFileSync32(next, "utf-8");
52425
+ raw = readFileSync34(next, "utf-8");
51477
52426
  } catch {
51478
52427
  raw = null;
51479
52428
  }
@@ -51619,7 +52568,7 @@ function createStreamScrubber() {
51619
52568
  // src/cli/runHeadless.ts
51620
52569
  init_taskTool();
51621
52570
  import { promises as fs29 } from "node:fs";
51622
- import path46 from "node:path";
52571
+ import path48 from "node:path";
51623
52572
  import { randomUUID as randomUUID6 } from "node:crypto";
51624
52573
  async function runHeadless(opts) {
51625
52574
  resetTaskSpawnCount();
@@ -51767,7 +52716,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
51767
52716
  try {
51768
52717
  let preflightGraph;
51769
52718
  if (opts.runPlan && opts.runPlan.trim() !== "") {
51770
- const planPath = path46.join(cwd, ".zelari", "radio", `plan-${opts.runPlan}.json`);
52719
+ const planPath = path48.join(cwd, ".zelari", "radio", `plan-${opts.runPlan}.json`);
51771
52720
  log(`loading pre-flight plan: ${planPath}`);
51772
52721
  let raw;
51773
52722
  try {
@@ -51807,8 +52756,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
51807
52756
  log(formatKrakenGraphAscii2(graph));
51808
52757
  if (opts.planOnly) {
51809
52758
  const planId = randomUUID6();
51810
- const planDir = path46.join(cwd, ".zelari", "radio");
51811
- const planPath = path46.join(planDir, `plan-${planId}.json`);
52759
+ const planDir = path48.join(cwd, ".zelari", "radio");
52760
+ const planPath = path48.join(planDir, `plan-${planId}.json`);
51812
52761
  await fs29.mkdir(planDir, { recursive: true });
51813
52762
  await fs29.writeFile(planPath, JSON.stringify(graph, null, 2), "utf8");
51814
52763
  log(`plan-only: wrote ${planPath} (${graph.nodes.size} nodes)`);
@@ -52796,263 +53745,13 @@ init_skillsMd();
52796
53745
  init_skills2();
52797
53746
  init_updater();
52798
53747
  init_mcpConfigIo();
52799
-
52800
- // src/cli/skillConfigIo.ts
52801
- init_skills2();
52802
- init_skillsMd();
52803
- import {
52804
- existsSync as existsSync42,
52805
- mkdirSync as mkdirSync18,
52806
- readdirSync as readdirSync7,
52807
- readFileSync as readFileSync33,
52808
- rmSync as rmSync4,
52809
- writeFileSync as writeFileSync20
52810
- } from "node:fs";
52811
- import { dirname as dirname9, join as join34 } from "node:path";
52812
- import { homedir as homedir10 } from "node:os";
52813
-
52814
- // src/cli/skillCategories.ts
52815
- var CODING_CATEGORIES_LIST = [
52816
- "plan",
52817
- "refactor",
52818
- "debug",
52819
- "review",
52820
- "test",
52821
- "docs",
52822
- "ops",
52823
- "git",
52824
- "db",
52825
- "maint"
52826
- ];
52827
- var CODING_CATEGORIES2 = new Set(CODING_CATEGORIES_LIST);
52828
-
52829
- // src/cli/skillConfigIo.ts
52830
- var NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
52831
- var BUILTIN_SKILL_MODULES = [
52832
- "@zelari/core/skills/builtin/debugging",
52833
- "@zelari/core/skills/builtin/docs",
52834
- "@zelari/core/skills/builtin/git-ops",
52835
- "@zelari/core/skills/builtin/planning",
52836
- "@zelari/core/skills/builtin/refactoring",
52837
- "@zelari/core/skills/builtin/review",
52838
- "@zelari/core/skills/builtin/testing",
52839
- "@zelari/core/skills/builtin/schema-loop",
52840
- "@zelari/core/skills/builtin/computer-use-cua",
52841
- "@zelari/core/skills/builtin/qwen-mm-plugins"
52842
- ];
52843
- var builtinsLoaded = false;
52844
- function ensureBuiltinSkillsLoadedSync() {
52845
- if (builtinsLoaded) return;
52846
- for (const spec of BUILTIN_SKILL_MODULES) {
52847
- try {
52848
- require?.(spec);
52849
- } catch {
52850
- }
52851
- }
52852
- }
52853
- function getUserSkillsDir() {
52854
- return join34(homedir10(), ".zelari-code", "skills");
52855
- }
52856
- function getProjectSkillsDir(projectRoot) {
52857
- return join34(projectRoot, ".zelari", "skills");
52858
- }
52859
- function skillFilePath(dir, name) {
52860
- return join34(dir, name, "SKILL.md");
52861
- }
52862
- function classifyScope(skillPath, projectRoot) {
52863
- const userDir = getUserSkillsDir().replace(/\\/g, "/");
52864
- const norm = skillPath.replace(/\\/g, "/");
52865
- if (norm.startsWith(userDir + "/") || norm === userDir) {
52866
- return { scope: "user", writable: true };
52867
- }
52868
- if (projectRoot) {
52869
- const projDir = getProjectSkillsDir(projectRoot).replace(/\\/g, "/");
52870
- if (norm.startsWith(projDir + "/") || norm === projDir) {
52871
- return { scope: "project", writable: true };
52872
- }
52873
- }
52874
- return { scope: "compat", writable: false };
52875
- }
52876
- function entryFromParsed(parsed, projectRoot) {
52877
- const { scope, writable } = classifyScope(parsed.sourcePath, projectRoot);
52878
- return {
52879
- id: parsed.name,
52880
- name: parsed.name,
52881
- description: parsed.description,
52882
- category: parsed.category,
52883
- estimatedCost: parsed.estimatedCost,
52884
- requiredTools: parsed.requiredTools,
52885
- scope,
52886
- path: parsed.sourcePath,
52887
- body: parsed.body,
52888
- builtin: false,
52889
- writable
52890
- };
52891
- }
52892
- function entryFromBuiltin(skill) {
52893
- return {
52894
- id: skill.id,
52895
- name: skill.name || skill.id,
52896
- description: skill.description || "",
52897
- category: skill.category,
52898
- estimatedCost: skill.estimatedCost,
52899
- requiredTools: skill.requiredTools,
52900
- scope: "builtin",
52901
- path: null,
52902
- // Expose body so Desktop skill picker can expand like `/skill <id>`.
52903
- body: skill.systemPromptFragment || void 0,
52904
- builtin: true,
52905
- writable: false
52906
- };
52907
- }
52908
- function scanSkillsDir(dir, projectRoot, seen, out) {
52909
- if (!existsSync42(dir)) return;
52910
- let entries;
52911
- try {
52912
- entries = readdirSync7(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
52913
- } catch {
52914
- return;
52915
- }
52916
- for (const entry of entries) {
52917
- const skillPath = skillFilePath(dir, entry);
52918
- if (!existsSync42(skillPath)) continue;
52919
- try {
52920
- const parsed = parseSkillMd(readFileSync33(skillPath, "utf8"), skillPath);
52921
- if (!parsed) continue;
52922
- if (seen.has(parsed.name)) continue;
52923
- seen.add(parsed.name);
52924
- out.push(entryFromParsed(parsed, projectRoot));
52925
- } catch {
52926
- }
52927
- }
52928
- }
52929
- function listSkillsSnapshot(projectRoot) {
52930
- const root = projectRoot && projectRoot.trim() ? projectRoot.trim() : null;
52931
- const userSkillsDir = getUserSkillsDir();
52932
- const projectSkillsDir = root ? getProjectSkillsDir(root) : null;
52933
- const skills = [];
52934
- const seen = /* @__PURE__ */ new Set();
52935
- if (root) {
52936
- scanSkillsDir(join34(root, ".zelari", "skills"), root, seen, skills);
52937
- scanSkillsDir(join34(root, ".claude", "skills"), root, seen, skills);
52938
- scanSkillsDir(join34(root, ".opencode", "skills"), root, seen, skills);
52939
- }
52940
- scanSkillsDir(userSkillsDir, root, seen, skills);
52941
- for (const s of listCodingSkills()) {
52942
- if (seen.has(s.id)) continue;
52943
- seen.add(s.id);
52944
- if (s.builtin) {
52945
- skills.push(entryFromBuiltin(s));
52946
- }
52947
- }
52948
- skills.sort((a, b) => {
52949
- const order = (s) => s === "project" ? 0 : s === "user" ? 1 : s === "compat" ? 2 : 3;
52950
- const d = order(a.scope) - order(b.scope);
52951
- if (d !== 0) return d;
52952
- return a.id.localeCompare(b.id);
52953
- });
52954
- return { userSkillsDir, projectSkillsDir, skills };
52955
- }
52956
- function serializeSkillMd(opts) {
52957
- const name = opts.name.trim().toLowerCase();
52958
- const description = opts.description.trim();
52959
- const body = opts.body.trim();
52960
- const lines = ["---", `name: ${name}`, `description: ${description}`];
52961
- const cat = (opts.category ?? "").trim().toLowerCase();
52962
- if (cat && CODING_CATEGORIES2.has(cat)) {
52963
- lines.push(`category: ${cat}`);
52964
- }
52965
- if (opts.tools && opts.tools.length > 0) {
52966
- lines.push(`tools: [${opts.tools.map((t) => t.trim()).filter(Boolean).join(", ")}]`);
52967
- }
52968
- const cost = (opts.cost ?? "").trim().toLowerCase();
52969
- if (cost === "low" || cost === "medium" || cost === "high") {
52970
- lines.push(`cost: ${cost}`);
52971
- }
52972
- lines.push("---", "", body, "");
52973
- return lines.join("\n");
52974
- }
52975
- function upsertSkill(opts) {
52976
- const name = opts.name.trim().toLowerCase();
52977
- if (!name || !NAME_RE.test(name)) {
52978
- return {
52979
- ok: false,
52980
- error: "Invalid skill name (use lowercase letters, digits, hyphens; max 64 chars)"
52981
- };
52982
- }
52983
- const description = opts.description.trim();
52984
- if (!description) {
52985
- return { ok: false, error: "description is required" };
52986
- }
52987
- const body = opts.body.trim();
52988
- if (!body) {
52989
- return { ok: false, error: "body is required" };
52990
- }
52991
- let dir;
52992
- if (opts.scope === "user") {
52993
- dir = getUserSkillsDir();
52994
- } else {
52995
- const root = opts.projectRoot?.trim();
52996
- if (!root) {
52997
- return {
52998
- ok: false,
52999
- error: "projectRoot required for project scope (Open Folder first)"
53000
- };
53001
- }
53002
- dir = getProjectSkillsDir(root);
53003
- }
53004
- const path48 = skillFilePath(dir, name);
53005
- const content = serializeSkillMd({
53006
- name,
53007
- description,
53008
- body,
53009
- category: opts.category,
53010
- tools: opts.tools,
53011
- cost: opts.cost
53012
- });
53013
- const parsed = parseSkillMd(content, path48);
53014
- if (!parsed) {
53015
- return { ok: false, error: "Generated SKILL.md failed validation" };
53016
- }
53017
- mkdirSync18(dirname9(path48), { recursive: true });
53018
- writeFileSync20(path48, content, "utf8");
53019
- return { ok: true, path: path48 };
53020
- }
53021
- function removeSkill(opts) {
53022
- const name = opts.name.trim().toLowerCase();
53023
- if (!name) {
53024
- return { ok: false, error: "name is required" };
53025
- }
53026
- let dir;
53027
- if (opts.scope === "user") {
53028
- dir = getUserSkillsDir();
53029
- } else {
53030
- const root = opts.projectRoot?.trim();
53031
- if (!root) {
53032
- return { ok: false, error: "projectRoot required for project scope" };
53033
- }
53034
- dir = getProjectSkillsDir(root);
53035
- }
53036
- const skillDir = join34(dir, name);
53037
- const path48 = skillFilePath(dir, name);
53038
- if (!existsSync42(path48) && !existsSync42(skillDir)) {
53039
- return { ok: false, error: `Skill "${name}" not found in ${dir}` };
53040
- }
53041
- try {
53042
- rmSync4(skillDir, { recursive: true, force: true });
53043
- } catch (err) {
53044
- return {
53045
- ok: false,
53046
- error: err instanceof Error ? err.message : String(err)
53047
- };
53048
- }
53049
- return { ok: true, path: path48 };
53050
- }
53748
+ init_skillConfigIo();
53051
53749
 
53052
53750
  // src/cli/generateSkillFromUrl.ts
53053
53751
  init_providerConfig();
53054
53752
  init_keyStore();
53055
53753
  init_openai_compatible();
53754
+ init_skillCategories();
53056
53755
  var MAX_PAGE_CHARS = 24e3;
53057
53756
  var FETCH_TIMEOUT_MS2 = 25e3;
53058
53757
  var LLM_TIMEOUT_MS = 9e4;
@@ -53136,8 +53835,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
53136
53835
  let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
53137
53836
  if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
53138
53837
  try {
53139
- const path48 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
53140
- name = path48 && /^[a-z0-9]/.test(path48) ? path48 : "imported-skill";
53838
+ const path51 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
53839
+ name = path51 && /^[a-z0-9]/.test(path51) ? path51 : "imported-skill";
53141
53840
  } catch {
53142
53841
  name = "imported-skill";
53143
53842
  }
@@ -53412,9 +54111,30 @@ function pickRootComponent() {
53412
54111
  console.error(`${red}\u2717${reset} ${result.error}`);
53413
54112
  process.exit(1);
53414
54113
  }
54114
+ if (argv.includes("--inspect") || argv.includes("inspect")) {
54115
+ const { runInspect: runInspect2 } = (init_inspect(), __toCommonJS(inspect_exports));
54116
+ const json2 = argv.includes("--json");
54117
+ void runInspect2({ json: json2 }).then((code) => process.exit(code));
54118
+ return { kind: "done" };
54119
+ }
54120
+ if (argv.includes("--trust")) {
54121
+ const { trustFolder: trustFolder2 } = (init_folderTrust(), __toCommonJS(folderTrust_exports));
54122
+ const i = argv.indexOf("--trust");
54123
+ const target = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[i + 1] : process.cwd();
54124
+ try {
54125
+ const res = trustFolder2(target);
54126
+ console.log(JSON.stringify({ ok: true, path: res.path }));
54127
+ process.exit(0);
54128
+ } catch (err) {
54129
+ console.error(
54130
+ `[zelari-code --trust] ${err instanceof Error ? err.message : String(err)}`
54131
+ );
54132
+ process.exit(1);
54133
+ }
54134
+ }
53415
54135
  if (argv.includes("--help") || argv.includes("-h")) {
53416
54136
  console.log(
53417
- "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 --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --print-config Print provider/model config as JSON (no secrets)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable (required)\n --args <json> JSON array of args (optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
54137
+ "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --print-config Print provider/model config as JSON (no secrets)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable (required)\n --args <json> JSON array of args (optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
53418
54138
  );
53419
54139
  process.exit(0);
53420
54140
  }