zelari-code 1.31.1 → 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"() {
@@ -34765,20 +35218,22 @@ __export(mcpManager_exports, {
34765
35218
  readMcpConfig: () => readMcpConfig,
34766
35219
  registerMcpTools: () => registerMcpTools
34767
35220
  });
34768
- import { existsSync as existsSync29, readFileSync as readFileSync24 } from "node:fs";
34769
- import { join as join25 } from "node:path";
34770
- import { homedir as homedir9 } from "node:os";
34771
- 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) {
34772
35225
  const merged = {};
34773
35226
  const paths = [];
34774
35227
  if (process.env["ZELARI_MCP_USER"] !== "0") {
34775
- 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"));
34776
35232
  }
34777
- paths.push(join25(projectRoot, ".zelari", "mcp.json"));
34778
35233
  for (const p3 of paths) {
34779
- if (!existsSync29(p3)) continue;
35234
+ if (!existsSync30(p3)) continue;
34780
35235
  try {
34781
- const parsed = JSON.parse(readFileSync24(p3, "utf8"));
35236
+ const parsed = JSON.parse(readFileSync26(p3, "utf8"));
34782
35237
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
34783
35238
  if (!cfg || typeof cfg.command !== "string" || cfg.command.length === 0) continue;
34784
35239
  merged[name] = cfg;
@@ -34791,7 +35246,13 @@ function readMcpConfig(projectRoot = process.cwd()) {
34791
35246
  async function ensureLoaded(projectRoot) {
34792
35247
  if (state2.loaded) return;
34793
35248
  state2.loaded = true;
34794
- 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 });
34795
35256
  for (const [name, cfg] of Object.entries(servers)) {
34796
35257
  if (cfg.enabled === false) continue;
34797
35258
  if (isCuaDisabled() && isCuaMcpServerName(name)) {
@@ -34874,6 +35335,7 @@ var init_mcpManager = __esm({
34874
35335
  init_toolTypes();
34875
35336
  init_mcpClient();
34876
35337
  init_mcpPresets();
35338
+ init_folderTrust();
34877
35339
  state2 = { loaded: false, tools: [], warnings: [], clients: [] };
34878
35340
  }
34879
35341
  });
@@ -35010,15 +35472,15 @@ __export(agentsMd_exports, {
35010
35472
  serializeAgentsMd: () => serializeAgentsMd,
35011
35473
  updateAgentsMd: () => updateAgentsMd
35012
35474
  });
35013
- 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";
35014
35476
  import { createHash as createHash5 } from "node:crypto";
35015
- import { join as join26 } from "node:path";
35477
+ import { join as join27 } from "node:path";
35016
35478
  import { readFile as readFile3 } from "node:fs/promises";
35017
35479
  async function readPackageJson2(projectRoot) {
35018
- const path48 = join26(projectRoot, "package.json");
35019
- if (!existsSync30(path48)) return null;
35480
+ const path51 = join27(projectRoot, "package.json");
35481
+ if (!existsSync31(path51)) return null;
35020
35482
  try {
35021
- return JSON.parse(await readFile3(path48, "utf8"));
35483
+ return JSON.parse(await readFile3(path51, "utf8"));
35022
35484
  } catch {
35023
35485
  return null;
35024
35486
  }
@@ -35040,8 +35502,8 @@ async function genTechStack(ctx) {
35040
35502
  ].join("\n");
35041
35503
  }
35042
35504
  async function genDecisions(ctx) {
35043
- const decisionsDir = join26(ctx.rootDir, "decisions");
35044
- if (!existsSync30(decisionsDir)) return "_No ADRs yet._";
35505
+ const decisionsDir = join27(ctx.rootDir, "decisions");
35506
+ if (!existsSync31(decisionsDir)) return "_No ADRs yet._";
35045
35507
  const files = ctx.storage.listMarkdown(decisionsDir).sort();
35046
35508
  const accepted = [];
35047
35509
  const proposed = [];
@@ -35066,9 +35528,9 @@ async function genDecisions(ctx) {
35066
35528
  }
35067
35529
  async function genConventions(ctx) {
35068
35530
  const lines = [];
35069
- const claudeMd = join26(ctx.projectRoot, "CLAUDE.MD");
35070
- if (existsSync30(claudeMd)) {
35071
- const content = readFileSync25(claudeMd, "utf8");
35531
+ const claudeMd = join27(ctx.projectRoot, "CLAUDE.MD");
35532
+ if (existsSync31(claudeMd)) {
35533
+ const content = readFileSync27(claudeMd, "utf8");
35072
35534
  const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
35073
35535
  if (match) {
35074
35536
  lines.push('<!-- Extracted from CLAUDE.MD "Architecture rules" -->');
@@ -35100,9 +35562,9 @@ async function genBuild(ctx) {
35100
35562
  ].join("\n");
35101
35563
  }
35102
35564
  async function genOpenQuestions(ctx) {
35103
- const path48 = join26(ctx.rootDir, "risks.md");
35104
- if (!existsSync30(path48)) return "_No open questions._";
35105
- 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");
35106
35568
  const lines = content.split("\n");
35107
35569
  const questions = [];
35108
35570
  let currentTitle = "";
@@ -35176,9 +35638,9 @@ function titleCase(id) {
35176
35638
  return id.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
35177
35639
  }
35178
35640
  async function updateAgentsMd(ctx, projectRoot) {
35179
- const agentsPath = join26(projectRoot, "AGENTS.MD");
35180
- if (existsSync30(agentsPath)) {
35181
- const content = readFileSync25(agentsPath, "utf8");
35641
+ const agentsPath = join27(projectRoot, "AGENTS.MD");
35642
+ if (existsSync31(agentsPath)) {
35643
+ const content = readFileSync27(agentsPath, "utf8");
35182
35644
  const hasAnyMarker = AUTO_SECTIONS.some((id) => content.includes(MARKER_OPEN(id)));
35183
35645
  if (!hasAnyMarker) {
35184
35646
  return {
@@ -35193,8 +35655,8 @@ async function updateAgentsMd(ctx, projectRoot) {
35193
35655
  newSections.set(id, await GENERATORS[id](ctx));
35194
35656
  }
35195
35657
  let manualContent = "";
35196
- if (existsSync30(agentsPath)) {
35197
- const { manualBlocks } = parseAgentsMd(readFileSync25(agentsPath, "utf8"));
35658
+ if (existsSync31(agentsPath)) {
35659
+ const { manualBlocks } = parseAgentsMd(readFileSync27(agentsPath, "utf8"));
35198
35660
  manualContent = manualBlocks.after;
35199
35661
  } else {
35200
35662
  const projectName2 = projectName(projectRoot);
@@ -35210,7 +35672,7 @@ async function updateAgentsMd(ctx, projectRoot) {
35210
35672
  ""
35211
35673
  ].join("\n");
35212
35674
  }
35213
- const oldContent = existsSync30(agentsPath) ? readFileSync25(agentsPath, "utf8") : "";
35675
+ const oldContent = existsSync31(agentsPath) ? readFileSync27(agentsPath, "utf8") : "";
35214
35676
  const { sections: oldSections } = parseAgentsMd(oldContent);
35215
35677
  const changedSections = [];
35216
35678
  for (const id of AUTO_SECTIONS) {
@@ -35222,7 +35684,7 @@ async function updateAgentsMd(ctx, projectRoot) {
35222
35684
  return { changed: false, sections: [] };
35223
35685
  }
35224
35686
  const newContent = serializeAgentsMd(manualContent, newSections);
35225
- writeFileSync16(agentsPath, newContent, "utf8");
35687
+ writeFileSync17(agentsPath, newContent, "utf8");
35226
35688
  return { changed: true, sections: changedSections };
35227
35689
  }
35228
35690
  function hash2(s) {
@@ -35347,9 +35809,9 @@ var init_completeDesign = __esm({
35347
35809
  });
35348
35810
 
35349
35811
  // src/cli/workspace/projectSmoke.ts
35350
- import { spawn as spawn9 } from "node:child_process";
35351
- import { existsSync as existsSync31, readFileSync as readFileSync26 } from "node:fs";
35352
- 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";
35353
35815
  function pickSmokeScript(scripts) {
35354
35816
  if (!scripts) return null;
35355
35817
  for (const name of SMOKE_SCRIPT_PRIORITY) {
@@ -35357,17 +35819,17 @@ function pickSmokeScript(scripts) {
35357
35819
  }
35358
35820
  return null;
35359
35821
  }
35360
- async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
35822
+ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS2) {
35361
35823
  if (process.env["ZELARI_SMOKE"] === "0") {
35362
35824
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
35363
35825
  }
35364
- const pkgPath = join27(projectRoot, "package.json");
35365
- if (!existsSync31(pkgPath)) {
35826
+ const pkgPath = join28(projectRoot, "package.json");
35827
+ if (!existsSync32(pkgPath)) {
35366
35828
  return { ran: false, reason: "no package.json (skipped)" };
35367
35829
  }
35368
35830
  let scripts = {};
35369
35831
  try {
35370
- const pkg = JSON.parse(readFileSync26(pkgPath, "utf8"));
35832
+ const pkg = JSON.parse(readFileSync28(pkgPath, "utf8"));
35371
35833
  scripts = pkg.scripts ?? {};
35372
35834
  } catch {
35373
35835
  return { ran: false, reason: "package.json unreadable (skipped)" };
@@ -35377,12 +35839,12 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
35377
35839
  return { ran: false, reason: "no typecheck/test/build script (skipped)" };
35378
35840
  }
35379
35841
  return await new Promise((resolveRun) => {
35380
- const child = process.platform === "win32" ? spawn9(buildCmdLine("npm.cmd", ["run", script]), {
35842
+ const child = process.platform === "win32" ? spawn10(buildCmdLine("npm.cmd", ["run", script]), {
35381
35843
  cwd: projectRoot,
35382
35844
  stdio: ["ignore", "pipe", "pipe"],
35383
35845
  env: process.env,
35384
35846
  shell: true
35385
- }) : spawn9("npm", ["run", script], {
35847
+ }) : spawn10("npm", ["run", script], {
35386
35848
  cwd: projectRoot,
35387
35849
  stdio: ["ignore", "pipe", "pipe"],
35388
35850
  env: process.env
@@ -35437,13 +35899,13 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
35437
35899
  });
35438
35900
  });
35439
35901
  }
35440
- var SMOKE_SCRIPT_PRIORITY, DEFAULT_TIMEOUT_MS;
35902
+ var SMOKE_SCRIPT_PRIORITY, DEFAULT_TIMEOUT_MS2;
35441
35903
  var init_projectSmoke = __esm({
35442
35904
  "src/cli/workspace/projectSmoke.ts"() {
35443
35905
  "use strict";
35444
35906
  init_cmdline();
35445
35907
  SMOKE_SCRIPT_PRIORITY = ["typecheck", "test", "build"];
35446
- DEFAULT_TIMEOUT_MS = 12e4;
35908
+ DEFAULT_TIMEOUT_MS2 = 12e4;
35447
35909
  }
35448
35910
  });
35449
35911
 
@@ -35454,9 +35916,9 @@ __export(postCouncilHook_exports, {
35454
35916
  runImplementationVerificationHook: () => runImplementationVerificationHook,
35455
35917
  runPostCouncilHook: () => runPostCouncilHook
35456
35918
  });
35457
- import { spawn as spawn10 } from "node:child_process";
35458
- import { existsSync as existsSync32, readFileSync as readFileSync27 } from "node:fs";
35459
- 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";
35460
35922
  async function runCompleteDesignPostProcessor(ctx, options) {
35461
35923
  if (options?.runMode === "implementation") {
35462
35924
  return {
@@ -35467,9 +35929,9 @@ async function runCompleteDesignPostProcessor(ctx, options) {
35467
35929
  if (process.env["ZELARI_COMPLETE_DESIGN"] === "0") {
35468
35930
  return { ran: false, reason: "ZELARI_COMPLETE_DESIGN=0 (disabled)" };
35469
35931
  }
35470
- const planJsonPath2 = join28(ctx.rootDir, "plan.json");
35471
- const scriptPath = join28(ctx.projectRoot, "complete-design.mjs");
35472
- if (!existsSync32(planJsonPath2)) {
35932
+ const planJsonPath2 = join29(ctx.rootDir, "plan.json");
35933
+ const scriptPath = join29(ctx.projectRoot, "complete-design.mjs");
35934
+ if (!existsSync33(planJsonPath2)) {
35473
35935
  return {
35474
35936
  ran: false,
35475
35937
  reason: ".zelari/plan.json missing (not design-phase)"
@@ -35477,7 +35939,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
35477
35939
  }
35478
35940
  let phaseCount = 0;
35479
35941
  try {
35480
- const parsed = JSON.parse(readFileSync27(planJsonPath2, "utf8"));
35942
+ const parsed = JSON.parse(readFileSync29(planJsonPath2, "utf8"));
35481
35943
  phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
35482
35944
  } catch {
35483
35945
  return { ran: false, reason: ".zelari/plan.json corrupt" };
@@ -35485,7 +35947,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
35485
35947
  if (phaseCount === 0) {
35486
35948
  return { ran: false, reason: ".zelari/plan.json has no phases" };
35487
35949
  }
35488
- if (!existsSync32(scriptPath)) {
35950
+ if (!existsSync33(scriptPath)) {
35489
35951
  try {
35490
35952
  const builtin = await runBuiltinCompleteDesign(ctx);
35491
35953
  return {
@@ -35503,7 +35965,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
35503
35965
  }
35504
35966
  }
35505
35967
  return await new Promise((resolveRun) => {
35506
- const child = spawn10(process.execPath, [scriptPath], {
35968
+ const child = spawn11(process.execPath, [scriptPath], {
35507
35969
  cwd: ctx.projectRoot,
35508
35970
  stdio: ["ignore", "pipe", "pipe"],
35509
35971
  env: process.env
@@ -35669,8 +36131,8 @@ async function runPostCouncilHook(ctx, options) {
35669
36131
  sources: scope.sources
35670
36132
  } : void 0
35671
36133
  });
35672
- const path48 = writeCouncilCompletion(ctx.rootDir, completion);
35673
- completionHook = { ran: true, path: path48, completion };
36134
+ const path51 = writeCouncilCompletion(ctx.rootDir, completion);
36135
+ completionHook = { ran: true, path: path51, completion };
35674
36136
  } catch (err) {
35675
36137
  completionHook = {
35676
36138
  ran: true,
@@ -35708,12 +36170,12 @@ __export(councilFeedback_exports, {
35708
36170
  });
35709
36171
  import {
35710
36172
  promises as fs15,
35711
- existsSync as existsSync33,
35712
- readFileSync as readFileSync28,
35713
- writeFileSync as writeFileSync17,
35714
- mkdirSync as mkdirSync14
36173
+ existsSync as existsSync34,
36174
+ readFileSync as readFileSync30,
36175
+ writeFileSync as writeFileSync18,
36176
+ mkdirSync as mkdirSync15
35715
36177
  } from "node:fs";
35716
- import path28 from "node:path";
36178
+ import path30 from "node:path";
35717
36179
  import os8 from "node:os";
35718
36180
  var FeedbackStore;
35719
36181
  var init_councilFeedback = __esm({
@@ -35724,7 +36186,7 @@ var init_councilFeedback = __esm({
35724
36186
  now;
35725
36187
  entries = [];
35726
36188
  constructor(options = {}) {
35727
- 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"));
35728
36190
  this.now = options.now ?? Date.now;
35729
36191
  this.load();
35730
36192
  }
@@ -35817,9 +36279,9 @@ var init_councilFeedback = __esm({
35817
36279
  }
35818
36280
  // --- persistence ---------------------------------------------------------
35819
36281
  load() {
35820
- if (!existsSync33(this.file)) return;
36282
+ if (!existsSync34(this.file)) return;
35821
36283
  try {
35822
- const raw = readFileSync28(this.file, "utf-8");
36284
+ const raw = readFileSync30(this.file, "utf-8");
35823
36285
  const parsed = JSON.parse(raw);
35824
36286
  if (parsed && Array.isArray(parsed.entries)) {
35825
36287
  this.entries = parsed.entries.filter(
@@ -35830,8 +36292,8 @@ var init_councilFeedback = __esm({
35830
36292
  }
35831
36293
  }
35832
36294
  save() {
35833
- mkdirSync14(path28.dirname(this.file), { recursive: true });
35834
- writeFileSync17(
36295
+ mkdirSync15(path30.dirname(this.file), { recursive: true });
36296
+ writeFileSync18(
35835
36297
  this.file,
35836
36298
  JSON.stringify({ entries: this.entries }, null, 2),
35837
36299
  { encoding: "utf-8", mode: 384 }
@@ -35898,7 +36360,7 @@ import { execFile as execFile3 } from "node:child_process";
35898
36360
  import { promisify as promisify2 } from "node:util";
35899
36361
  import { mkdtempSync, rmSync as rmSync2 } from "node:fs";
35900
36362
  import { tmpdir as tmpdir2 } from "node:os";
35901
- import path29 from "node:path";
36363
+ import path31 from "node:path";
35902
36364
  import { randomUUID as randomUUID3 } from "node:crypto";
35903
36365
  async function git2(cwd, args, env) {
35904
36366
  const { stdout } = await execFileAsync2("git", ["-C", cwd, ...args], {
@@ -35918,8 +36380,8 @@ async function isGitRepo(cwd) {
35918
36380
  return await gitSafe(cwd, ["rev-parse", "--is-inside-work-tree"]) === "true";
35919
36381
  }
35920
36382
  async function withTempIndex(fn) {
35921
- const dir = mkdtempSync(path29.join(tmpdir2(), "zelari-ckpt-"));
35922
- const indexFile = path29.join(dir, "index");
36383
+ const dir = mkdtempSync(path31.join(tmpdir2(), "zelari-ckpt-"));
36384
+ const indexFile = path31.join(dir, "index");
35923
36385
  try {
35924
36386
  return await fn(indexFile);
35925
36387
  } finally {
@@ -36010,7 +36472,7 @@ async function restoreCheckpoint(cwd, id) {
36010
36472
  const deleted = [];
36011
36473
  for (const rel2 of added) {
36012
36474
  try {
36013
- rmSync2(path29.join(cwd, rel2), { force: true });
36475
+ rmSync2(path31.join(cwd, rel2), { force: true });
36014
36476
  deleted.push(rel2);
36015
36477
  } catch {
36016
36478
  }
@@ -36111,7 +36573,7 @@ __export(fileBackend_exports, {
36111
36573
  });
36112
36574
  import { randomUUID as randomUUID4 } from "node:crypto";
36113
36575
  import { promises as fs16 } from "node:fs";
36114
- import * as path30 from "node:path";
36576
+ import * as path32 from "node:path";
36115
36577
  function tokenize(text) {
36116
36578
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 3);
36117
36579
  }
@@ -36155,8 +36617,8 @@ var init_fileBackend = __esm({
36155
36617
  logPath = "";
36156
36618
  memoryDir = "";
36157
36619
  async init(projectRoot) {
36158
- this.memoryDir = path30.join(projectRoot, ".zelari", "memory");
36159
- 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");
36160
36622
  await fs16.mkdir(this.memoryDir, { recursive: true });
36161
36623
  }
36162
36624
  async add(content, metadata = {}, graph) {
@@ -36229,12 +36691,12 @@ var init_fileBackend = __esm({
36229
36691
 
36230
36692
  // src/cli/traceStore.ts
36231
36693
  import { promises as fs17 } from "node:fs";
36232
- import * as path31 from "node:path";
36694
+ import * as path33 from "node:path";
36233
36695
  function traceDir(projectRoot) {
36234
- return path31.join(projectRoot, ".zelari", "trace");
36696
+ return path33.join(projectRoot, ".zelari", "trace");
36235
36697
  }
36236
36698
  function tracePath(projectRoot, missionId) {
36237
- return path31.join(traceDir(projectRoot), `${missionId}.json`);
36699
+ return path33.join(traceDir(projectRoot), `${missionId}.json`);
36238
36700
  }
36239
36701
  async function saveTrace(projectRoot, missionId, entries) {
36240
36702
  const dir = traceDir(projectRoot);
@@ -36269,7 +36731,7 @@ __export(zelariMission_exports, {
36269
36731
  });
36270
36732
  import { randomUUID as randomUUID5 } from "node:crypto";
36271
36733
  import { promises as fs18 } from "node:fs";
36272
- import * as path32 from "node:path";
36734
+ import * as path34 from "node:path";
36273
36735
  function resolveMaxIterations(env = process.env) {
36274
36736
  const raw = env.ZELARI_MISSION_MAX_ITER;
36275
36737
  const n = raw ? Number.parseInt(raw, 10) : DEFAULT_MAX_ITER;
@@ -36297,10 +36759,10 @@ function isMissionAutoStart(env = process.env) {
36297
36759
  return env.ZELARI_MISSION_AUTO === "1";
36298
36760
  }
36299
36761
  async function writeMissionState(projectRoot, state3) {
36300
- const dir = path32.join(projectRoot, ".zelari");
36762
+ const dir = path34.join(projectRoot, ".zelari");
36301
36763
  await fs18.mkdir(dir, { recursive: true });
36302
36764
  await fs18.writeFile(
36303
- path32.join(dir, "mission-state.json"),
36765
+ path34.join(dir, "mission-state.json"),
36304
36766
  JSON.stringify(state3, null, 2) + "\n",
36305
36767
  "utf8"
36306
36768
  );
@@ -36900,7 +37362,7 @@ function safeSocketPath(socketPath) {
36900
37362
  return socketPath.trim();
36901
37363
  }
36902
37364
  function startPermissionBroker(socketPath, handlers, opts) {
36903
- const path48 = safeSocketPath(socketPath);
37365
+ const path51 = safeSocketPath(socketPath);
36904
37366
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
36905
37367
  const sockets = /* @__PURE__ */ new Set();
36906
37368
  const server = createServer((socket) => {
@@ -37000,10 +37462,10 @@ function startPermissionBroker(socketPath, handlers, opts) {
37000
37462
  return new Promise((resolve3, reject) => {
37001
37463
  const onError = (err) => reject(err);
37002
37464
  server.once("error", onError);
37003
- server.listen(path48, () => {
37465
+ server.listen(path51, () => {
37004
37466
  server.removeListener("error", onError);
37005
37467
  resolve3({
37006
- socketPath: path48,
37468
+ socketPath: path51,
37007
37469
  stop: () => new Promise((res) => {
37008
37470
  for (const s of sockets) s.destroy();
37009
37471
  sockets.clear();
@@ -37014,7 +37476,7 @@ function startPermissionBroker(socketPath, handlers, opts) {
37014
37476
  if (done) return;
37015
37477
  done = true;
37016
37478
  if (process.platform !== "win32") {
37017
- unlink(path48, () => res());
37479
+ unlink(path51, () => res());
37018
37480
  } else {
37019
37481
  res();
37020
37482
  }
@@ -37027,11 +37489,11 @@ function startPermissionBroker(socketPath, handlers, opts) {
37027
37489
  });
37028
37490
  }
37029
37491
  function requestBrokerAsk(socketPath, ask, opts) {
37030
- const path48 = safeSocketPath(socketPath);
37492
+ const path51 = safeSocketPath(socketPath);
37031
37493
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
37032
37494
  const connectTimeoutMs = opts?.connectTimeoutMs ?? PERMISSION_BROKER_DEFAULT_CONNECT_TIMEOUT_MS;
37033
37495
  return new Promise((resolve3, reject) => {
37034
- const socket = connect(path48);
37496
+ const socket = connect(path51);
37035
37497
  let buffer = "";
37036
37498
  let settled = false;
37037
37499
  const settle = (fn) => {
@@ -37046,7 +37508,7 @@ function requestBrokerAsk(socketPath, ask, opts) {
37046
37508
  settle(
37047
37509
  () => reject(
37048
37510
  new Error(
37049
- `permission broker unavailable at "${path48}" (connect timed out after ${connectTimeoutMs}ms)`
37511
+ `permission broker unavailable at "${path51}" (connect timed out after ${connectTimeoutMs}ms)`
37050
37512
  )
37051
37513
  )
37052
37514
  );
@@ -37779,9 +38241,9 @@ __export(graphMemory_exports, {
37779
38241
  toGraphSnapshot: () => toGraphSnapshot
37780
38242
  });
37781
38243
  import { promises as fs19 } from "node:fs";
37782
- import path34 from "node:path";
38244
+ import path36 from "node:path";
37783
38245
  function snapshotPath(cwd) {
37784
- return path34.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
38246
+ return path36.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
37785
38247
  }
37786
38248
  function toGraphSnapshot(graph, opts) {
37787
38249
  const unresolved = (opts.unresolvedFindings ?? []).map((u) => ({
@@ -37808,7 +38270,7 @@ async function saveGraphSnapshot(cwd, snapshot) {
37808
38270
  try {
37809
38271
  await fs19.access(cwd);
37810
38272
  const file2 = snapshotPath(cwd);
37811
- await fs19.mkdir(path34.dirname(file2), { recursive: true });
38273
+ await fs19.mkdir(path36.dirname(file2), { recursive: true });
37812
38274
  await fs19.writeFile(file2, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
37813
38275
  } catch {
37814
38276
  }
@@ -37875,7 +38337,7 @@ var SNAPSHOT_DIR, SNAPSHOT_FILE, MAX_SNAPSHOT_FINDINGS_CHARS;
37875
38337
  var init_graphMemory = __esm({
37876
38338
  "src/cli/kraken/graphMemory.ts"() {
37877
38339
  "use strict";
37878
- SNAPSHOT_DIR = path34.join(".zelari", "kraken");
38340
+ SNAPSHOT_DIR = path36.join(".zelari", "kraken");
37879
38341
  SNAPSHOT_FILE = "last-graph.json";
37880
38342
  MAX_SNAPSHOT_FINDINGS_CHARS = 400;
37881
38343
  }
@@ -37891,14 +38353,14 @@ var init_tentacle = __esm({
37891
38353
 
37892
38354
  // src/cli/kraken/workbench.ts
37893
38355
  import { promises as fs20 } from "node:fs";
37894
- import path35 from "node:path";
38356
+ import path37 from "node:path";
37895
38357
  function isWorkbenchEnabled(env = process.env) {
37896
38358
  const v = (env.ZELARI_KRAKEN_WORKBENCH ?? "1").trim().toLowerCase();
37897
38359
  if (v === "0" || v === "false" || v === "no" || v === "off") return false;
37898
38360
  return true;
37899
38361
  }
37900
38362
  function workbenchPath(cwd, graphId) {
37901
- return path35.join(cwd, ".zelari", "radio", `workbench-${graphId}.md`);
38363
+ return path37.join(cwd, ".zelari", "radio", `workbench-${graphId}.md`);
37902
38364
  }
37903
38365
  function countByStatus2(nodes) {
37904
38366
  const out = { pending: 0, running: 0, done: 0, error: 0, skipped: 0 };
@@ -38061,7 +38523,7 @@ var init_workbench = __esm({
38061
38523
  if (!this.enabled) return null;
38062
38524
  if (!this.dirty && this.lastWrite) return this.lastWrite;
38063
38525
  const out = workbenchPath(this.cwd, this.graphId);
38064
- await fs20.mkdir(path35.dirname(out), { recursive: true });
38526
+ await fs20.mkdir(path37.dirname(out), { recursive: true });
38065
38527
  const body = this.render();
38066
38528
  const tmp = `${out}.${process.pid}.${Date.now()}.tmp`;
38067
38529
  await fs20.writeFile(tmp, body, "utf8");
@@ -38257,8 +38719,8 @@ __export(executor_exports, {
38257
38719
  resolveNodeTimeoutMs: () => resolveNodeTimeoutMs,
38258
38720
  thoroughnessForKind: () => thoroughnessForKind
38259
38721
  });
38260
- import { existsSync as existsSync34 } from "node:fs";
38261
- import path36 from "node:path";
38722
+ import { existsSync as existsSync35 } from "node:fs";
38723
+ import path38 from "node:path";
38262
38724
  function resolveMaxParallel(env = process.env) {
38263
38725
  const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
38264
38726
  if (raw === void 0 || raw === "") return DEFAULT_MAX_PARALLEL;
@@ -38316,7 +38778,7 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
38316
38778
  }
38317
38779
  function defaultChecksExists(cwd) {
38318
38780
  try {
38319
- return existsSync34(path36.join(cwd, ".zelari", "world", "checks.json"));
38781
+ return existsSync35(path38.join(cwd, ".zelari", "world", "checks.json"));
38320
38782
  } catch {
38321
38783
  return false;
38322
38784
  }
@@ -39283,7 +39745,7 @@ __export(prereqChecks_exports, {
39283
39745
  runPrereqChecks: () => runPrereqChecks
39284
39746
  });
39285
39747
  import { execSync, spawnSync as spawnSync2 } from "node:child_process";
39286
- import { existsSync as existsSync35 } from "node:fs";
39748
+ import { existsSync as existsSync36 } from "node:fs";
39287
39749
  import { dirname as dirname7 } from "node:path";
39288
39750
  function isWslBashPath2(p3) {
39289
39751
  if (!p3 || typeof p3 !== "string") return false;
@@ -39407,7 +39869,7 @@ function agentProbeEnv() {
39407
39869
  }
39408
39870
  function existsSyncSafe2(p3) {
39409
39871
  try {
39410
- return existsSync35(p3);
39872
+ return existsSync36(p3);
39411
39873
  } catch {
39412
39874
  return false;
39413
39875
  }
@@ -39654,17 +40116,17 @@ var init_prereqChecks = __esm({
39654
40116
  });
39655
40117
 
39656
40118
  // src/cli/plugins/prefs.ts
39657
- import { existsSync as existsSync36, readFileSync as readFileSync29, writeFileSync as writeFileSync18, mkdirSync as mkdirSync15 } from "node:fs";
39658
- 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";
39659
40121
  import os9 from "node:os";
39660
40122
  function getPluginPrefsPath() {
39661
- 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");
39662
40124
  }
39663
40125
  function getPluginPrefs() {
39664
40126
  const file2 = getPluginPrefsPath();
39665
40127
  try {
39666
- if (!existsSync36(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
39667
- const raw = readFileSync29(file2, "utf-8");
40128
+ if (!existsSync37(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
40129
+ const raw = readFileSync31(file2, "utf-8");
39668
40130
  const parsed = JSON.parse(raw);
39669
40131
  if (parsed && typeof parsed === "object" && parsed.dontAskAgain && typeof parsed.dontAskAgain === "object") {
39670
40132
  const clean = {};
@@ -39679,8 +40141,8 @@ function getPluginPrefs() {
39679
40141
  }
39680
40142
  function writePluginPrefs(prefs) {
39681
40143
  const file2 = getPluginPrefsPath();
39682
- mkdirSync15(path39.dirname(file2), { recursive: true });
39683
- writeFileSync18(file2, JSON.stringify(prefs, null, 2), {
40144
+ mkdirSync16(path41.dirname(file2), { recursive: true });
40145
+ writeFileSync19(file2, JSON.stringify(prefs, null, 2), {
39684
40146
  encoding: "utf-8",
39685
40147
  mode: 384
39686
40148
  });
@@ -39715,8 +40177,8 @@ __export(registry_exports, {
39715
40177
  findPlugin: () => findPlugin,
39716
40178
  isBinaryOnPath: () => isBinaryOnPath
39717
40179
  });
39718
- import { existsSync as existsSync37 } from "node:fs";
39719
- import path40 from "node:path";
40180
+ import { existsSync as existsSync38 } from "node:fs";
40181
+ import path42 from "node:path";
39720
40182
  function detectLocalBin(bin) {
39721
40183
  return (cwd) => {
39722
40184
  try {
@@ -39732,9 +40194,9 @@ function isBinaryOnPath(bin, opts = {}) {
39732
40194
  return false;
39733
40195
  }
39734
40196
  const platform = opts.platform ?? process.platform;
39735
- const exists = opts.exists ?? existsSync37;
40197
+ const exists = opts.exists ?? existsSync38;
39736
40198
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
39737
- const pathMod = platform === "win32" ? path40.win32 : path40.posix;
40199
+ const pathMod = platform === "win32" ? path42.win32 : path42.posix;
39738
40200
  const sep2 = platform === "win32" ? ";" : ":";
39739
40201
  const dirs = pathEnv.split(sep2).filter((d) => d.length > 0);
39740
40202
  const candidates = [bin];
@@ -39871,7 +40333,7 @@ __export(atMentions_exports, {
39871
40333
  extractAtMentions: () => extractAtMentions,
39872
40334
  hasAtMentions: () => hasAtMentions
39873
40335
  });
39874
- 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";
39875
40337
  import { basename as basename3, isAbsolute as isAbsolute2, relative as relative3, resolve, sep } from "node:path";
39876
40338
  function isImagePath(abs) {
39877
40339
  const ext = abs.split(".").pop()?.toLowerCase() ?? "";
@@ -39925,7 +40387,7 @@ function resolveMention(token, cwd) {
39925
40387
  note: "outside project root \u2014 skipped"
39926
40388
  };
39927
40389
  }
39928
- if (!existsSync40(abs)) {
40390
+ if (!existsSync41(abs)) {
39929
40391
  return {
39930
40392
  raw: token,
39931
40393
  path: token,
@@ -39977,7 +40439,7 @@ function resolveMention(token, cwd) {
39977
40439
  note: `image too large (${Math.round(st.size / 1024)} KB) \u2014 path only`
39978
40440
  };
39979
40441
  }
39980
- const dataBase64 = readFileSync31(abs).toString("base64");
40442
+ const dataBase64 = readFileSync33(abs).toString("base64");
39981
40443
  return {
39982
40444
  raw: token,
39983
40445
  path: rel2,
@@ -39988,7 +40450,7 @@ function resolveMention(token, cwd) {
39988
40450
  };
39989
40451
  }
39990
40452
  try {
39991
- const buf = readFileSync31(abs);
40453
+ const buf = readFileSync33(abs);
39992
40454
  const head = buf.subarray(0, 800).toString("utf8");
39993
40455
  if (!isProbablyText(abs, head)) {
39994
40456
  return {
@@ -40086,9 +40548,9 @@ __export(triggerLock_exports, {
40086
40548
  releaseLock: () => releaseLock
40087
40549
  });
40088
40550
  import { promises as fs28 } from "node:fs";
40089
- import * as path45 from "node:path";
40551
+ import * as path47 from "node:path";
40090
40552
  function lockPath(projectRoot) {
40091
- return path45.join(projectRoot, ".zelari", "trigger.lock");
40553
+ return path47.join(projectRoot, ".zelari", "trigger.lock");
40092
40554
  }
40093
40555
  function isPidAlive(pid) {
40094
40556
  try {
@@ -40101,7 +40563,7 @@ function isPidAlive(pid) {
40101
40563
  }
40102
40564
  async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
40103
40565
  const lp = lockPath(projectRoot);
40104
- const dir = path45.dirname(lp);
40566
+ const dir = path47.dirname(lp);
40105
40567
  await fs28.mkdir(dir, { recursive: true });
40106
40568
  try {
40107
40569
  const raw = await fs28.readFile(lp, "utf8");
@@ -40383,6 +40845,269 @@ var init_desktopConfig = __esm({
40383
40845
  }
40384
40846
  });
40385
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
+
40386
41111
  // src/cli/mcp/mcpPermissionServer.ts
40387
41112
  import { createInterface } from "node:readline";
40388
41113
  import { randomUUID as randomUUID7 } from "node:crypto";
@@ -40651,36 +41376,36 @@ var init_permissionCli = __esm({
40651
41376
 
40652
41377
  // src/cli/companion/config.ts
40653
41378
  import {
40654
- existsSync as existsSync43,
40655
- mkdirSync as mkdirSync19,
40656
- readFileSync as readFileSync34,
40657
- writeFileSync as writeFileSync21
41379
+ existsSync as existsSync44,
41380
+ mkdirSync as mkdirSync20,
41381
+ readFileSync as readFileSync36,
41382
+ writeFileSync as writeFileSync22
40658
41383
  } from "node:fs";
40659
- import { join as join35 } from "node:path";
40660
- import { homedir as homedir11 } from "node:os";
41384
+ import { join as join36 } from "node:path";
41385
+ import { homedir as homedir13 } from "node:os";
40661
41386
  import { createHash as createHash6, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
40662
41387
  function getZelariHome() {
40663
- return join35(homedir11(), ".zelari-code");
41388
+ return join36(homedir13(), ".zelari-code");
40664
41389
  }
40665
41390
  function getCompanionConfigPath() {
40666
- return join35(getZelariHome(), "companion.json");
41391
+ return join36(getZelariHome(), "companion.json");
40667
41392
  }
40668
41393
  function getCompanionTokenPath() {
40669
- return join35(getZelariHome(), "companion.token");
41394
+ return join36(getZelariHome(), "companion.token");
40670
41395
  }
40671
41396
  function ensureHome() {
40672
41397
  const home = getZelariHome();
40673
- if (!existsSync43(home)) {
40674
- mkdirSync19(home, { recursive: true });
41398
+ if (!existsSync44(home)) {
41399
+ mkdirSync20(home, { recursive: true });
40675
41400
  }
40676
41401
  }
40677
41402
  function loadCompanionConfig() {
40678
- const path48 = getCompanionConfigPath();
40679
- if (!existsSync43(path48)) {
41403
+ const path51 = getCompanionConfigPath();
41404
+ if (!existsSync44(path51)) {
40680
41405
  return { projects: [] };
40681
41406
  }
40682
41407
  try {
40683
- const raw = JSON.parse(readFileSync34(path48, "utf8"));
41408
+ const raw = JSON.parse(readFileSync36(path51, "utf8"));
40684
41409
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
40685
41410
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
40686
41411
  ).map((p3) => ({
@@ -40699,7 +41424,7 @@ function loadCompanionConfig() {
40699
41424
  }
40700
41425
  function saveCompanionConfig(cfg) {
40701
41426
  ensureHome();
40702
- writeFileSync21(
41427
+ writeFileSync22(
40703
41428
  getCompanionConfigPath(),
40704
41429
  JSON.stringify(
40705
41430
  {
@@ -40718,16 +41443,16 @@ function loadOrCreateToken(explicit) {
40718
41443
  return { token: explicit.trim(), created: false };
40719
41444
  }
40720
41445
  ensureHome();
40721
- const path48 = getCompanionTokenPath();
40722
- if (existsSync43(path48)) {
40723
- const t = readFileSync34(path48, "utf8").trim();
41446
+ const path51 = getCompanionTokenPath();
41447
+ if (existsSync44(path51)) {
41448
+ const t = readFileSync36(path51, "utf8").trim();
40724
41449
  if (t) return { token: t, created: false };
40725
41450
  }
40726
41451
  const token = randomBytes4(24).toString("base64url");
40727
- writeFileSync21(path48, token + "\n", "utf8");
41452
+ writeFileSync22(path51, token + "\n", "utf8");
40728
41453
  try {
40729
41454
  const fs30 = __require("node:fs");
40730
- fs30.chmodSync?.(path48, 384);
41455
+ fs30.chmodSync?.(path51, 384);
40731
41456
  } catch {
40732
41457
  }
40733
41458
  return { token, created: true };
@@ -40752,17 +41477,17 @@ function mergeProjects(cfg, extraPaths) {
40752
41477
  byId.set(p3.id, p3);
40753
41478
  }
40754
41479
  for (const raw of extraPaths) {
40755
- const path48 = raw.trim();
40756
- if (!path48) continue;
40757
- let id = slugFromPath(path48);
41480
+ const path51 = raw.trim();
41481
+ if (!path51) continue;
41482
+ let id = slugFromPath(path51);
40758
41483
  let n = 2;
40759
- while (byId.has(id) && byId.get(id).path !== path48) {
40760
- id = `${slugFromPath(path48)}-${n++}`;
41484
+ while (byId.has(id) && byId.get(id).path !== path51) {
41485
+ id = `${slugFromPath(path51)}-${n++}`;
40761
41486
  }
40762
41487
  byId.set(id, {
40763
41488
  id,
40764
- name: slugFromPath(path48),
40765
- path: path48
41489
+ name: slugFromPath(path51),
41490
+ path: path51
40766
41491
  });
40767
41492
  }
40768
41493
  return [...byId.values()];
@@ -40810,11 +41535,11 @@ var init_config = __esm({
40810
41535
  });
40811
41536
 
40812
41537
  // src/cli/companion/runManager.ts
40813
- import { spawn as spawn12 } from "node:child_process";
41538
+ import { spawn as spawn13 } from "node:child_process";
40814
41539
  import { createInterface as createInterface2 } from "node:readline";
40815
41540
  import { randomUUID as randomUUID8 } from "node:crypto";
40816
- import { writeFileSync as writeFileSync22, unlinkSync as unlinkSync2 } from "node:fs";
40817
- 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";
40818
41543
  import { tmpdir as tmpdir3 } from "node:os";
40819
41544
  var RunManager;
40820
41545
  var init_runManager = __esm({
@@ -40918,15 +41643,15 @@ var init_runManager = __esm({
40918
41643
  }
40919
41644
  let historyFile;
40920
41645
  if (args.history && Array.isArray(args.history) && args.history.length > 0) {
40921
- historyFile = join36(tmpdir3(), `zelari-companion-hist-${id}.json`);
41646
+ historyFile = join37(tmpdir3(), `zelari-companion-hist-${id}.json`);
40922
41647
  try {
40923
- writeFileSync22(historyFile, JSON.stringify(args.history), "utf8");
41648
+ writeFileSync23(historyFile, JSON.stringify(args.history), "utf8");
40924
41649
  argv.push("--history-file", historyFile);
40925
41650
  } catch {
40926
41651
  historyFile = void 0;
40927
41652
  }
40928
41653
  }
40929
- const child = spawn12(process.execPath, argv, {
41654
+ const child = spawn13(process.execPath, argv, {
40930
41655
  cwd: args.cwd,
40931
41656
  env: {
40932
41657
  ...process.env,
@@ -41045,7 +41770,7 @@ __export(serve_exports, {
41045
41770
  runCompanionServe: () => runCompanionServe
41046
41771
  });
41047
41772
  import { createServer as createServer2 } from "node:http";
41048
- import { existsSync as existsSync44 } from "node:fs";
41773
+ import { existsSync as existsSync45 } from "node:fs";
41049
41774
  import { resolve as resolve2 } from "node:path";
41050
41775
  function readBody(req, max = 2e6) {
41051
41776
  return new Promise((resolveBody, reject) => {
@@ -41093,7 +41818,7 @@ async function runCompanionServe(opts = {}) {
41093
41818
  let projects = mergeProjects(fileCfg, opts.projects ?? []);
41094
41819
  projects = projects.filter((p3) => {
41095
41820
  const abs = resolve2(p3.path);
41096
- if (!existsSync44(abs)) {
41821
+ if (!existsSync45(abs)) {
41097
41822
  process.stderr.write(
41098
41823
  `[zelari-code serve] skip missing project path: ${p3.path}
41099
41824
  `
@@ -41139,9 +41864,9 @@ async function runCompanionServe(opts = {}) {
41139
41864
  return;
41140
41865
  }
41141
41866
  const url2 = parseUrl(req);
41142
- const path48 = url2.pathname.replace(/\/+$/, "") || "/";
41867
+ const path51 = url2.pathname.replace(/\/+$/, "") || "/";
41143
41868
  try {
41144
- if (req.method === "GET" && (path48 === "/health" || path48 === "/v1/health")) {
41869
+ if (req.method === "GET" && (path51 === "/health" || path51 === "/v1/health")) {
41145
41870
  sendJson2(res, 200, {
41146
41871
  ok: true,
41147
41872
  service: "zelari-companion",
@@ -41153,18 +41878,18 @@ async function runCompanionServe(opts = {}) {
41153
41878
  });
41154
41879
  return;
41155
41880
  }
41156
- if (path48.startsWith("/v1")) {
41881
+ if (path51.startsWith("/v1")) {
41157
41882
  if (!tokenMatches(token, getBearer(req))) {
41158
41883
  sendJson2(res, 401, { ok: false, error: "unauthorized" });
41159
41884
  return;
41160
41885
  }
41161
41886
  }
41162
- if (req.method === "GET" && path48 === "/v1/config") {
41887
+ if (req.method === "GET" && path51 === "/v1/config") {
41163
41888
  const snap = buildDesktopConfigSnapshot();
41164
41889
  sendJson2(res, 200, { ok: true, ...snap });
41165
41890
  return;
41166
41891
  }
41167
- if (req.method === "GET" && path48 === "/v1/projects") {
41892
+ if (req.method === "GET" && path51 === "/v1/projects") {
41168
41893
  sendJson2(res, 200, {
41169
41894
  ok: true,
41170
41895
  projects: projects.map((p3) => ({
@@ -41175,7 +41900,7 @@ async function runCompanionServe(opts = {}) {
41175
41900
  });
41176
41901
  return;
41177
41902
  }
41178
- if (req.method === "GET" && path48 === "/v1/runs") {
41903
+ if (req.method === "GET" && path51 === "/v1/runs") {
41179
41904
  sendJson2(res, 200, {
41180
41905
  ok: true,
41181
41906
  active: runs.getActive(),
@@ -41193,7 +41918,7 @@ async function runCompanionServe(opts = {}) {
41193
41918
  });
41194
41919
  return;
41195
41920
  }
41196
- if (req.method === "POST" && path48 === "/v1/runs") {
41921
+ if (req.method === "POST" && path51 === "/v1/runs") {
41197
41922
  const raw = await readBody(req);
41198
41923
  let body = {};
41199
41924
  try {
@@ -41240,7 +41965,7 @@ async function runCompanionServe(opts = {}) {
41240
41965
  });
41241
41966
  return;
41242
41967
  }
41243
- const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path48);
41968
+ const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path51);
41244
41969
  if (req.method === "GET" && eventsMatch) {
41245
41970
  const runId = eventsMatch[1];
41246
41971
  const run = runs.getRun(runId);
@@ -41305,7 +42030,7 @@ async function runCompanionServe(opts = {}) {
41305
42030
  }, 500);
41306
42031
  return;
41307
42032
  }
41308
- const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path48);
42033
+ const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path51);
41309
42034
  if (req.method === "POST" && cancelMatch) {
41310
42035
  const runId = cancelMatch[1];
41311
42036
  const result = runs.cancel(runId);
@@ -41414,26 +42139,26 @@ __export(doctor_exports, {
41414
42139
  runDoctor: () => runDoctor
41415
42140
  });
41416
42141
  import { execSync as execSync2 } from "node:child_process";
41417
- 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";
41418
42143
  import { createRequire as createRequire3 } from "node:module";
41419
42144
  import { fileURLToPath as fileURLToPath2 } from "node:url";
41420
- import path47 from "node:path";
42145
+ import path49 from "node:path";
41421
42146
  function findPackageRoot(start) {
41422
42147
  let dir = start;
41423
42148
  for (let i = 0; i < 6; i += 1) {
41424
- const candidate = path47.join(dir, "package.json");
41425
- if (existsSync45(candidate)) {
42149
+ const candidate = path49.join(dir, "package.json");
42150
+ if (existsSync46(candidate)) {
41426
42151
  try {
41427
- const pkg = JSON.parse(readFileSync35(candidate, "utf8"));
42152
+ const pkg = JSON.parse(readFileSync37(candidate, "utf8"));
41428
42153
  if (pkg.name === "zelari-code") return dir;
41429
42154
  } catch {
41430
42155
  }
41431
42156
  }
41432
- const parent = path47.dirname(dir);
42157
+ const parent = path49.dirname(dir);
41433
42158
  if (parent === dir) break;
41434
42159
  dir = parent;
41435
42160
  }
41436
- return path47.resolve(__dirname3, "..", "..", "..");
42161
+ return path49.resolve(__dirname3, "..", "..", "..");
41437
42162
  }
41438
42163
  function tryExec(cmd) {
41439
42164
  try {
@@ -41447,8 +42172,8 @@ function tryExec(cmd) {
41447
42172
  }
41448
42173
  function readPackageJson3() {
41449
42174
  try {
41450
- const pkgPath = path47.join(packageRoot, "package.json");
41451
- return JSON.parse(readFileSync35(pkgPath, "utf8"));
42175
+ const pkgPath = path49.join(packageRoot, "package.json");
42176
+ return JSON.parse(readFileSync37(pkgPath, "utf8"));
41452
42177
  } catch {
41453
42178
  return null;
41454
42179
  }
@@ -41463,8 +42188,8 @@ function checkShim(pkgName) {
41463
42188
  }
41464
42189
  const isWin = process.platform === "win32";
41465
42190
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
41466
- const shimPath = path47.join(prefix, shimName);
41467
- if (!existsSync45(shimPath)) {
42191
+ const shimPath = path49.join(prefix, shimName);
42192
+ if (!existsSync46(shimPath)) {
41468
42193
  return FAIL(
41469
42194
  `shim not found at ${shimPath}
41470
42195
  fix: npm install -g ${pkgName}@latest --force`
@@ -41473,7 +42198,7 @@ function checkShim(pkgName) {
41473
42198
  try {
41474
42199
  const st = statSync7(shimPath);
41475
42200
  if (isWin) {
41476
- const content = readFileSync35(shimPath, "utf8");
42201
+ const content = readFileSync37(shimPath, "utf8");
41477
42202
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
41478
42203
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
41479
42204
  }
@@ -41491,8 +42216,8 @@ function checkShim(pkgName) {
41491
42216
  fix: npm install -g ${pkgName}@latest --force`
41492
42217
  );
41493
42218
  }
41494
- const resolved = path47.resolve(path47.dirname(shimPath), target);
41495
- const expected = path47.join(
42219
+ const resolved = path49.resolve(path49.dirname(shimPath), target);
42220
+ const expected = path49.join(
41496
42221
  prefix,
41497
42222
  "node_modules",
41498
42223
  pkgName,
@@ -41531,8 +42256,8 @@ function checkNode(pkg) {
41531
42256
  return OK(`node ${raw}`);
41532
42257
  }
41533
42258
  function checkBundle() {
41534
- const bundle = path47.join(packageRoot, "dist", "cli", "main.bundled.js");
41535
- if (!existsSync45(bundle)) {
42259
+ const bundle = path49.join(packageRoot, "dist", "cli", "main.bundled.js");
42260
+ if (!existsSync46(bundle)) {
41536
42261
  return FAIL(
41537
42262
  `dist/cli/main.bundled.js missing at ${bundle}
41538
42263
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -41552,7 +42277,7 @@ function checkRuntimeDeps() {
41552
42277
  const missing = [];
41553
42278
  for (const dep of required2) {
41554
42279
  try {
41555
- const localReq = createRequire3(path47.join(packageRoot, "package.json"));
42280
+ const localReq = createRequire3(path49.join(packageRoot, "package.json"));
41556
42281
  localReq.resolve(dep);
41557
42282
  } catch {
41558
42283
  missing.push(dep);
@@ -41728,7 +42453,7 @@ var init_doctor = __esm({
41728
42453
  "use strict";
41729
42454
  init_prereqChecks();
41730
42455
  require3 = createRequire3(import.meta.url);
41731
- __dirname3 = path47.dirname(fileURLToPath2(import.meta.url));
42456
+ __dirname3 = path49.dirname(fileURLToPath2(import.meta.url));
41732
42457
  packageRoot = findPackageRoot(__dirname3);
41733
42458
  OK = (message) => ({
41734
42459
  ok: true,
@@ -41906,6 +42631,154 @@ var init_fixBudget = __esm({
41906
42631
  }
41907
42632
  });
41908
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
+
41909
42782
  // src/cli/main.ts
41910
42783
  import React15 from "react";
41911
42784
  import { render } from "ink";
@@ -44770,15 +45643,15 @@ import { useState as useState5, useRef as useRef3, useCallback, useEffect as use
44770
45643
 
44771
45644
  // src/cli/sessionManager.ts
44772
45645
  init_harness();
44773
- import { promises as fs9, existsSync as existsSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync9, mkdirSync as mkdirSync4, unlinkSync, statSync } from "node:fs";
44774
- 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";
44775
45648
  import os4 from "node:os";
44776
45649
  import { randomUUID } from "node:crypto";
44777
45650
  function getSessionBaseDir() {
44778
- 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");
44779
45652
  }
44780
45653
  function getCurrentSessionFile() {
44781
- 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");
44782
45655
  }
44783
45656
  async function ensureSessionDir() {
44784
45657
  await fs9.mkdir(getSessionBaseDir(), { recursive: true });
@@ -44787,7 +45660,7 @@ function getCurrentSessionId() {
44787
45660
  const file2 = getCurrentSessionFile();
44788
45661
  if (!existsSync9(file2)) return null;
44789
45662
  try {
44790
- const content = readFileSync11(file2, "utf-8").trim();
45663
+ const content = readFileSync12(file2, "utf-8").trim();
44791
45664
  return content.length > 0 ? content : null;
44792
45665
  } catch {
44793
45666
  return null;
@@ -44795,7 +45668,7 @@ function getCurrentSessionId() {
44795
45668
  }
44796
45669
  function setCurrentSessionId(id) {
44797
45670
  const file2 = getCurrentSessionFile();
44798
- mkdirSync4(path12.dirname(file2), { recursive: true });
45671
+ mkdirSync4(path13.dirname(file2), { recursive: true });
44799
45672
  writeFileSync9(file2, id, "utf-8");
44800
45673
  }
44801
45674
  function clearCurrentSessionId() {
@@ -44807,13 +45680,13 @@ function clearCurrentSessionId() {
44807
45680
  }
44808
45681
  }
44809
45682
  function getCurrentBranchFile() {
44810
- 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");
44811
45684
  }
44812
45685
  function getCurrentBranch() {
44813
45686
  const file2 = getCurrentBranchFile();
44814
45687
  if (!existsSync9(file2)) return null;
44815
45688
  try {
44816
- const content = readFileSync11(file2, "utf-8").trim();
45689
+ const content = readFileSync12(file2, "utf-8").trim();
44817
45690
  return content.length > 0 ? content : null;
44818
45691
  } catch {
44819
45692
  return null;
@@ -44821,7 +45694,7 @@ function getCurrentBranch() {
44821
45694
  }
44822
45695
  function setCurrentBranch(name) {
44823
45696
  const file2 = getCurrentBranchFile();
44824
- mkdirSync4(path12.dirname(file2), { recursive: true });
45697
+ mkdirSync4(path13.dirname(file2), { recursive: true });
44825
45698
  writeFileSync9(file2, name, "utf-8");
44826
45699
  }
44827
45700
  function newSessionId() {
@@ -44840,7 +45713,7 @@ async function listSessions() {
44840
45713
  for (const entry of entries) {
44841
45714
  if (!entry.endsWith(".jsonl")) continue;
44842
45715
  const id = entry.replace(/\.jsonl$/, "");
44843
- const filePath = path12.join(baseDir, entry);
45716
+ const filePath = path13.join(baseDir, entry);
44844
45717
  try {
44845
45718
  const events = await readSession(filePath);
44846
45719
  let firstTs = 0;
@@ -44887,7 +45760,7 @@ ${lines.join("\n")}`
44887
45760
  return { message: `[${kind}] handled` };
44888
45761
  }
44889
45762
  async function loadSessionEvents(id) {
44890
- const filePath = path12.join(getSessionBaseDir(), `${id}.jsonl`);
45763
+ const filePath = path13.join(getSessionBaseDir(), `${id}.jsonl`);
44891
45764
  return readSession(filePath);
44892
45765
  }
44893
45766
 
@@ -45179,15 +46052,15 @@ import { useState as useState6, useRef as useRef4, useCallback as useCallback2 }
45179
46052
 
45180
46053
  // src/cli/metrics.ts
45181
46054
  import { promises as fs10, existsSync as existsSync10, statSync as statSync2, renameSync, appendFileSync as appendFileSync2, mkdirSync as mkdirSync5 } from "node:fs";
45182
- import path13 from "node:path";
46055
+ import path14 from "node:path";
45183
46056
  import os5 from "node:os";
45184
46057
  var METRICS_ROTATE_BYTES = 10 * 1024 * 1024;
45185
46058
  var MetricsLogger = class {
45186
46059
  file;
45187
46060
  writeQueue = Promise.resolve();
45188
46061
  constructor(file2) {
45189
- this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path13.join(os5.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
45190
- 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 });
45191
46064
  }
45192
46065
  /** Fire-and-forget record append. */
45193
46066
  record(rec) {
@@ -47318,6 +48191,8 @@ function handleSlashCommand(text, availableSkills) {
47318
48191
  /index [status] \u2014 build the semantic code index for semantic_search
47319
48192
  /mode [kraken|council|zelari] \u2014 switch dispatch mode (same as shift+tab; agent=alias kraken)
47320
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
47321
48196
  /help \u2014 show this help
47322
48197
  /exit \u2014 exit the CLI
47323
48198
 
@@ -47806,6 +48681,23 @@ ${formatSkillList(availableSkills)}`
47806
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)."
47807
48682
  };
47808
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
+ }
47809
48701
  case "cache": {
47810
48702
  const sub = (args[0] ?? "stats").toLowerCase();
47811
48703
  if (sub === "stats" || sub === "") {
@@ -47893,7 +48785,7 @@ ${formatSkillList(availableSkills)}`
47893
48785
  // src/cli/gitOps.ts
47894
48786
  import { execFile as execFile4 } from "node:child_process";
47895
48787
  import { promisify as promisify3 } from "node:util";
47896
- import path33 from "node:path";
48788
+ import path35 from "node:path";
47897
48789
  var execFileAsync3 = promisify3(execFile4);
47898
48790
  async function git3(cwd, args) {
47899
48791
  try {
@@ -47939,7 +48831,7 @@ async function undoWorkingChanges(opts = {}) {
47939
48831
  };
47940
48832
  }
47941
48833
  function defaultProjectRoot() {
47942
- return path33.resolve(__dirname, "..", "..", "..");
48834
+ return path35.resolve(__dirname, "..", "..", "..");
47943
48835
  }
47944
48836
 
47945
48837
  // src/cli/slashHandlers/git.ts
@@ -48216,6 +49108,45 @@ function handleCacheStats(ctx) {
48216
49108
  );
48217
49109
  }
48218
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
+
48219
49150
  // src/cli/slashHandlers/semantic.ts
48220
49151
  init_semantic();
48221
49152
  init_provider();
@@ -48341,7 +49272,7 @@ import { promises as fs22 } from "node:fs";
48341
49272
  init_zod();
48342
49273
  init_taskTool();
48343
49274
  import { promises as fs21 } from "node:fs";
48344
- import path37 from "node:path";
49275
+ import path39 from "node:path";
48345
49276
  import { randomBytes as randomBytes3 } from "node:crypto";
48346
49277
  var CsvFanoutArgsSchema = external_exports.object({
48347
49278
  csv_path: external_exports.string().min(1),
@@ -48435,8 +49366,8 @@ function resolveMaxConcurrency(env = process.env) {
48435
49366
  }
48436
49367
  async function runCsvFanout(args, deps, opts) {
48437
49368
  const start = Date.now();
48438
- const absCsv = path37.isAbsolute(args.csv_path) ? args.csv_path : path37.join(opts.parentCwd, args.csv_path);
48439
- 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);
48440
49371
  const { headers, rows } = await readCsv(absCsv);
48441
49372
  if (headers.length === 0) {
48442
49373
  throw new Error(`kraken_csv_fanout: ${absCsv} is empty`);
@@ -48492,7 +49423,7 @@ async function runCsvFanout(args, deps, opts) {
48492
49423
  errored += 1;
48493
49424
  errors.push(`${row[args.id_column] ?? i}: ${res.error}`);
48494
49425
  }
48495
- await fs21.mkdir(path37.dirname(absOut), { recursive: true });
49426
+ await fs21.mkdir(path39.dirname(absOut), { recursive: true });
48496
49427
  await queueWrite(serializeCsv(outHeaders, outputRecords));
48497
49428
  }
48498
49429
  }
@@ -48686,7 +49617,7 @@ function splitArgs(s) {
48686
49617
 
48687
49618
  // src/cli/slashHandlers/krakenWorkbench.ts
48688
49619
  import { promises as fs23 } from "node:fs";
48689
- import path38 from "node:path";
49620
+ import path40 from "node:path";
48690
49621
 
48691
49622
  // src/cli/kraken/workbenchView.ts
48692
49623
  var EMPTY = {
@@ -48803,14 +49734,14 @@ function formatWorkbenchForTerminal(p3) {
48803
49734
 
48804
49735
  // src/cli/slashHandlers/krakenWorkbench.ts
48805
49736
  async function handleKrakenWorkbench(ctx) {
48806
- const dir = path38.join(ctx.cwd, ".zelari", "radio");
49737
+ const dir = path40.join(ctx.cwd, ".zelari", "radio");
48807
49738
  let latest = null;
48808
49739
  let latestMtime = 0;
48809
49740
  try {
48810
49741
  const files = await fs23.readdir(dir);
48811
49742
  for (const f of files) {
48812
49743
  if (!f.startsWith("workbench-") || !f.endsWith(".md")) continue;
48813
- const full = path38.join(dir, f);
49744
+ const full = path40.join(dir, f);
48814
49745
  const stat = await fs23.stat(full);
48815
49746
  if (stat.mtimeMs > latestMtime) {
48816
49747
  latestMtime = stat.mtimeMs;
@@ -48827,10 +49758,10 @@ async function handleKrakenWorkbench(ctx) {
48827
49758
  const parsed = parseWorkbench(content);
48828
49759
  const rendered = formatWorkbenchForTerminal(parsed);
48829
49760
  if (!rendered.trim()) {
48830
- 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)`);
48831
49762
  return;
48832
49763
  }
48833
- appendSystem(ctx.setMessages, `[kraken workbench] ${path38.basename(latest)}:
49764
+ appendSystem(ctx.setMessages, `[kraken workbench] ${path40.basename(latest)}:
48834
49765
  ${rendered}`);
48835
49766
  }
48836
49767
 
@@ -48982,8 +49913,8 @@ init_registry3();
48982
49913
  // src/cli/plugins/installer.ts
48983
49914
  init_cmdline();
48984
49915
  init_updater();
48985
- import { spawn as spawn11 } from "node:child_process";
48986
- async function installPlugin(spec, cwd, executor = spawn11) {
49916
+ import { spawn as spawn12 } from "node:child_process";
49917
+ async function installPlugin(spec, cwd, executor = spawn12) {
48987
49918
  const scopeFlag = spec.installScope === "global" ? "-g" : "-D";
48988
49919
  const args = ["install", scopeFlag, spec.npmPackage];
48989
49920
  let result = await runNpm2(executor, args, cwd, "shim");
@@ -49132,15 +50063,15 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
49132
50063
 
49133
50064
  // src/cli/slashHandlers/promoteMember.ts
49134
50065
  import { promises as fs24 } from "node:fs";
49135
- import path41 from "node:path";
50066
+ import path43 from "node:path";
49136
50067
  import os10 from "node:os";
49137
50068
  async function handlePromoteMember(ctx, memberId) {
49138
50069
  try {
49139
50070
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
49140
50071
  const { skill, markdown } = promoteMember2(memberId);
49141
- 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");
49142
50073
  await fs24.mkdir(skillDir, { recursive: true });
49143
- const filePath = path41.join(skillDir, `${skill.id}.md`);
50074
+ const filePath = path43.join(skillDir, `${skill.id}.md`);
49144
50075
  await fs24.writeFile(filePath, markdown, "utf8");
49145
50076
  appendSystem(
49146
50077
  ctx.setMessages,
@@ -49157,33 +50088,33 @@ async function handlePromoteMember(ctx, memberId) {
49157
50088
  }
49158
50089
 
49159
50090
  // src/cli/branchManager.ts
49160
- 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";
49161
- 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";
49162
50093
  import os11 from "node:os";
49163
50094
  var META_FILENAME = "meta.json";
49164
50095
  var SESSIONS_SUBDIR = "sessions";
49165
50096
  function getBranchesBaseDir() {
49166
- 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");
49167
50098
  }
49168
50099
  function getSessionsBaseDir() {
49169
- 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");
49170
50101
  }
49171
50102
  function branchPathFor(name, baseDir) {
49172
- return path42.join(baseDir, name);
50103
+ return path44.join(baseDir, name);
49173
50104
  }
49174
50105
  function metaPathFor(name, baseDir) {
49175
- return path42.join(baseDir, name, META_FILENAME);
50106
+ return path44.join(baseDir, name, META_FILENAME);
49176
50107
  }
49177
50108
  function sessionsPathFor(name, baseDir) {
49178
- return path42.join(baseDir, name, SESSIONS_SUBDIR);
50109
+ return path44.join(baseDir, name, SESSIONS_SUBDIR);
49179
50110
  }
49180
50111
  function readBranchMeta(name, baseDir) {
49181
50112
  const metaPath = metaPathFor(name, baseDir);
49182
- if (!existsSync38(metaPath)) {
50113
+ if (!existsSync39(metaPath)) {
49183
50114
  throw new BranchNotFoundError(`Branch "${name}" not found`);
49184
50115
  }
49185
50116
  try {
49186
- const raw = readFileSync30(metaPath, "utf-8");
50117
+ const raw = readFileSync32(metaPath, "utf-8");
49187
50118
  const parsed = JSON.parse(raw);
49188
50119
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
49189
50120
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -49200,8 +50131,8 @@ function readBranchMeta(name, baseDir) {
49200
50131
  }
49201
50132
  function writeBranchMeta(name, baseDir, meta3) {
49202
50133
  const metaPath = metaPathFor(name, baseDir);
49203
- mkdirSync16(path42.dirname(metaPath), { recursive: true });
49204
- 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");
49205
50136
  }
49206
50137
  async function countSessions(name, baseDir) {
49207
50138
  const sessionsPath = sessionsPathFor(name, baseDir);
@@ -49239,7 +50170,7 @@ var SessionNotFoundError = class extends Error {
49239
50170
  };
49240
50171
  function branchExists(name, baseDir = getBranchesBaseDir()) {
49241
50172
  const bp = branchPathFor(name, baseDir);
49242
- return existsSync38(bp) && existsSync38(metaPathFor(name, baseDir));
50173
+ return existsSync39(bp) && existsSync39(metaPathFor(name, baseDir));
49243
50174
  }
49244
50175
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
49245
50176
  if (!name || name.trim().length === 0) {
@@ -49251,14 +50182,14 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
49251
50182
  if (branchExists(name, baseDir)) {
49252
50183
  throw new BranchAlreadyExistsError(name);
49253
50184
  }
49254
- const sourcePath = path42.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
49255
- if (!existsSync38(sourcePath)) {
50185
+ const sourcePath = path44.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
50186
+ if (!existsSync39(sourcePath)) {
49256
50187
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
49257
50188
  }
49258
50189
  const branchPath = branchPathFor(name, baseDir);
49259
50190
  const branchSessionsPath = sessionsPathFor(name, baseDir);
49260
- mkdirSync16(branchSessionsPath, { recursive: true });
49261
- const destPath = path42.join(branchSessionsPath, `${fromSessionId}.jsonl`);
50191
+ mkdirSync17(branchSessionsPath, { recursive: true });
50192
+ const destPath = path44.join(branchSessionsPath, `${fromSessionId}.jsonl`);
49262
50193
  await fs25.copyFile(sourcePath, destPath);
49263
50194
  const meta3 = {
49264
50195
  name,
@@ -49285,7 +50216,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
49285
50216
  const results = [];
49286
50217
  for (const entry of entries) {
49287
50218
  const metaPath = metaPathFor(entry, baseDir);
49288
- if (!existsSync38(metaPath)) continue;
50219
+ if (!existsSync39(metaPath)) continue;
49289
50220
  try {
49290
50221
  const meta3 = readBranchMeta(entry, baseDir);
49291
50222
  const sessionCount = await countSessions(entry, baseDir);
@@ -49359,14 +50290,14 @@ async function handleBranchCheckout(ctx, branchName) {
49359
50290
 
49360
50291
  // src/cli/slashHandlers/workspace.ts
49361
50292
  import { promises as fs26 } from "node:fs";
49362
- import path43 from "node:path";
50293
+ import path45 from "node:path";
49363
50294
  async function handleWorkspaceShow(ctx, what) {
49364
50295
  try {
49365
- const zelari = path43.join(process.cwd(), ".zelari");
50296
+ const zelari = path45.join(process.cwd(), ".zelari");
49366
50297
  let content;
49367
50298
  switch (what) {
49368
50299
  case "plan": {
49369
- const planPath = path43.join(zelari, "plan.md");
50300
+ const planPath = path45.join(zelari, "plan.md");
49370
50301
  try {
49371
50302
  content = await fs26.readFile(planPath, "utf-8");
49372
50303
  } catch {
@@ -49375,7 +50306,7 @@ async function handleWorkspaceShow(ctx, what) {
49375
50306
  break;
49376
50307
  }
49377
50308
  case "decisions": {
49378
- const decisionsDir = path43.join(zelari, "decisions");
50309
+ const decisionsDir = path45.join(zelari, "decisions");
49379
50310
  try {
49380
50311
  const files = (await fs26.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
49381
50312
  if (files.length === 0) {
@@ -49385,7 +50316,7 @@ async function handleWorkspaceShow(ctx, what) {
49385
50316
  `];
49386
50317
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
49387
50318
  for (const f of files) {
49388
- const raw = await fs26.readFile(path43.join(decisionsDir, f), "utf-8");
50319
+ const raw = await fs26.readFile(path45.join(decisionsDir, f), "utf-8");
49389
50320
  const { meta: meta3, body } = parseFrontmatter2(raw);
49390
50321
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
49391
50322
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -49398,7 +50329,7 @@ async function handleWorkspaceShow(ctx, what) {
49398
50329
  break;
49399
50330
  }
49400
50331
  case "risks": {
49401
- const risksPath = path43.join(zelari, "risks.md");
50332
+ const risksPath = path45.join(zelari, "risks.md");
49402
50333
  try {
49403
50334
  content = await fs26.readFile(risksPath, "utf-8");
49404
50335
  } catch {
@@ -49407,7 +50338,7 @@ async function handleWorkspaceShow(ctx, what) {
49407
50338
  break;
49408
50339
  }
49409
50340
  case "agents": {
49410
- const agentsPath = path43.join(process.cwd(), "AGENTS.MD");
50341
+ const agentsPath = path45.join(process.cwd(), "AGENTS.MD");
49411
50342
  try {
49412
50343
  content = await fs26.readFile(agentsPath, "utf-8");
49413
50344
  } catch {
@@ -49416,7 +50347,7 @@ async function handleWorkspaceShow(ctx, what) {
49416
50347
  break;
49417
50348
  }
49418
50349
  case "docs": {
49419
- const docsDir = path43.join(zelari, "docs");
50350
+ const docsDir = path45.join(zelari, "docs");
49420
50351
  try {
49421
50352
  const files = (await fs26.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
49422
50353
  content = files.length ? `# Docs (${files.length})
@@ -49458,7 +50389,7 @@ async function handleWorkspaceReset(ctx, force) {
49458
50389
  return;
49459
50390
  }
49460
50391
  try {
49461
- const target = path43.join(process.cwd(), ".zelari");
50392
+ const target = path45.join(process.cwd(), ".zelari");
49462
50393
  await fs26.rm(target, { recursive: true, force: true });
49463
50394
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
49464
50395
  } catch (err) {
@@ -49818,11 +50749,11 @@ function handleModelsRefresh(ctx) {
49818
50749
  }
49819
50750
 
49820
50751
  // src/cli/slashHandlers/skills.ts
49821
- import path44 from "node:path";
50752
+ import path46 from "node:path";
49822
50753
  import os12 from "node:os";
49823
50754
 
49824
50755
  // src/cli/skillHistory.ts
49825
- 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";
49826
50757
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
49827
50758
  async function readSkillHistory(file2) {
49828
50759
  let raw = "";
@@ -49944,7 +50875,7 @@ function handleSkillPicker(ctx, skills, openPicker, fallbackMessage) {
49944
50875
  });
49945
50876
  }
49946
50877
  async function handleSkillStats(ctx, skillId) {
49947
- 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");
49948
50879
  try {
49949
50880
  const records = await readSkillHistory(historyFile);
49950
50881
  const stats = getSkillStats(records, skillId);
@@ -49960,7 +50891,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
49960
50891
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
49961
50892
  return;
49962
50893
  }
49963
- 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");
49964
50895
  try {
49965
50896
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
49966
50897
  appendSystem(ctx.setMessages, formatted);
@@ -50350,6 +51281,21 @@ function useSlashDispatch(params) {
50350
51281
  setInput("");
50351
51282
  return;
50352
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
+ }
50353
51299
  if (result.kind === "index_build") {
50354
51300
  await handleIndexBuild({ ...baseCtx, cwd: process.cwd() });
50355
51301
  setInput("");
@@ -51117,9 +52063,9 @@ function ContinueKey({ onContinue }) {
51117
52063
  init_providerConfig();
51118
52064
 
51119
52065
  // src/cli/wizard/firstRun.ts
51120
- import { existsSync as existsSync41 } from "node:fs";
52066
+ import { existsSync as existsSync42 } from "node:fs";
51121
52067
  function shouldRunWizard(input) {
51122
- const exists = input.exists ?? existsSync41;
52068
+ const exists = input.exists ?? existsSync42;
51123
52069
  if (input.hasResetConfigFlag) {
51124
52070
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
51125
52071
  }
@@ -51404,7 +52350,7 @@ init_keyStore();
51404
52350
  init_providerConfig();
51405
52351
  init_openai_compatible();
51406
52352
  init_phase();
51407
- import { readFileSync as readFileSync32 } from "node:fs";
52353
+ import { readFileSync as readFileSync34 } from "node:fs";
51408
52354
  function parseHeadlessFlags(argv) {
51409
52355
  if (!argv.includes("--headless")) {
51410
52356
  return { options: null };
@@ -51476,7 +52422,7 @@ function parseHeadlessFlags(argv) {
51476
52422
  let raw = null;
51477
52423
  if (arg === "--history-file") {
51478
52424
  try {
51479
- raw = readFileSync32(next, "utf-8");
52425
+ raw = readFileSync34(next, "utf-8");
51480
52426
  } catch {
51481
52427
  raw = null;
51482
52428
  }
@@ -51622,7 +52568,7 @@ function createStreamScrubber() {
51622
52568
  // src/cli/runHeadless.ts
51623
52569
  init_taskTool();
51624
52570
  import { promises as fs29 } from "node:fs";
51625
- import path46 from "node:path";
52571
+ import path48 from "node:path";
51626
52572
  import { randomUUID as randomUUID6 } from "node:crypto";
51627
52573
  async function runHeadless(opts) {
51628
52574
  resetTaskSpawnCount();
@@ -51770,7 +52716,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
51770
52716
  try {
51771
52717
  let preflightGraph;
51772
52718
  if (opts.runPlan && opts.runPlan.trim() !== "") {
51773
- const planPath = path46.join(cwd, ".zelari", "radio", `plan-${opts.runPlan}.json`);
52719
+ const planPath = path48.join(cwd, ".zelari", "radio", `plan-${opts.runPlan}.json`);
51774
52720
  log(`loading pre-flight plan: ${planPath}`);
51775
52721
  let raw;
51776
52722
  try {
@@ -51810,8 +52756,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
51810
52756
  log(formatKrakenGraphAscii2(graph));
51811
52757
  if (opts.planOnly) {
51812
52758
  const planId = randomUUID6();
51813
- const planDir = path46.join(cwd, ".zelari", "radio");
51814
- 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`);
51815
52761
  await fs29.mkdir(planDir, { recursive: true });
51816
52762
  await fs29.writeFile(planPath, JSON.stringify(graph, null, 2), "utf8");
51817
52763
  log(`plan-only: wrote ${planPath} (${graph.nodes.size} nodes)`);
@@ -52799,263 +53745,13 @@ init_skillsMd();
52799
53745
  init_skills2();
52800
53746
  init_updater();
52801
53747
  init_mcpConfigIo();
52802
-
52803
- // src/cli/skillConfigIo.ts
52804
- init_skills2();
52805
- init_skillsMd();
52806
- import {
52807
- existsSync as existsSync42,
52808
- mkdirSync as mkdirSync18,
52809
- readdirSync as readdirSync7,
52810
- readFileSync as readFileSync33,
52811
- rmSync as rmSync4,
52812
- writeFileSync as writeFileSync20
52813
- } from "node:fs";
52814
- import { dirname as dirname9, join as join34 } from "node:path";
52815
- import { homedir as homedir10 } from "node:os";
52816
-
52817
- // src/cli/skillCategories.ts
52818
- var CODING_CATEGORIES_LIST = [
52819
- "plan",
52820
- "refactor",
52821
- "debug",
52822
- "review",
52823
- "test",
52824
- "docs",
52825
- "ops",
52826
- "git",
52827
- "db",
52828
- "maint"
52829
- ];
52830
- var CODING_CATEGORIES2 = new Set(CODING_CATEGORIES_LIST);
52831
-
52832
- // src/cli/skillConfigIo.ts
52833
- var NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
52834
- var BUILTIN_SKILL_MODULES = [
52835
- "@zelari/core/skills/builtin/debugging",
52836
- "@zelari/core/skills/builtin/docs",
52837
- "@zelari/core/skills/builtin/git-ops",
52838
- "@zelari/core/skills/builtin/planning",
52839
- "@zelari/core/skills/builtin/refactoring",
52840
- "@zelari/core/skills/builtin/review",
52841
- "@zelari/core/skills/builtin/testing",
52842
- "@zelari/core/skills/builtin/schema-loop",
52843
- "@zelari/core/skills/builtin/computer-use-cua",
52844
- "@zelari/core/skills/builtin/qwen-mm-plugins"
52845
- ];
52846
- var builtinsLoaded = false;
52847
- function ensureBuiltinSkillsLoadedSync() {
52848
- if (builtinsLoaded) return;
52849
- for (const spec of BUILTIN_SKILL_MODULES) {
52850
- try {
52851
- require?.(spec);
52852
- } catch {
52853
- }
52854
- }
52855
- }
52856
- function getUserSkillsDir() {
52857
- return join34(homedir10(), ".zelari-code", "skills");
52858
- }
52859
- function getProjectSkillsDir(projectRoot) {
52860
- return join34(projectRoot, ".zelari", "skills");
52861
- }
52862
- function skillFilePath(dir, name) {
52863
- return join34(dir, name, "SKILL.md");
52864
- }
52865
- function classifyScope(skillPath, projectRoot) {
52866
- const userDir = getUserSkillsDir().replace(/\\/g, "/");
52867
- const norm = skillPath.replace(/\\/g, "/");
52868
- if (norm.startsWith(userDir + "/") || norm === userDir) {
52869
- return { scope: "user", writable: true };
52870
- }
52871
- if (projectRoot) {
52872
- const projDir = getProjectSkillsDir(projectRoot).replace(/\\/g, "/");
52873
- if (norm.startsWith(projDir + "/") || norm === projDir) {
52874
- return { scope: "project", writable: true };
52875
- }
52876
- }
52877
- return { scope: "compat", writable: false };
52878
- }
52879
- function entryFromParsed(parsed, projectRoot) {
52880
- const { scope, writable } = classifyScope(parsed.sourcePath, projectRoot);
52881
- return {
52882
- id: parsed.name,
52883
- name: parsed.name,
52884
- description: parsed.description,
52885
- category: parsed.category,
52886
- estimatedCost: parsed.estimatedCost,
52887
- requiredTools: parsed.requiredTools,
52888
- scope,
52889
- path: parsed.sourcePath,
52890
- body: parsed.body,
52891
- builtin: false,
52892
- writable
52893
- };
52894
- }
52895
- function entryFromBuiltin(skill) {
52896
- return {
52897
- id: skill.id,
52898
- name: skill.name || skill.id,
52899
- description: skill.description || "",
52900
- category: skill.category,
52901
- estimatedCost: skill.estimatedCost,
52902
- requiredTools: skill.requiredTools,
52903
- scope: "builtin",
52904
- path: null,
52905
- // Expose body so Desktop skill picker can expand like `/skill <id>`.
52906
- body: skill.systemPromptFragment || void 0,
52907
- builtin: true,
52908
- writable: false
52909
- };
52910
- }
52911
- function scanSkillsDir(dir, projectRoot, seen, out) {
52912
- if (!existsSync42(dir)) return;
52913
- let entries;
52914
- try {
52915
- entries = readdirSync7(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
52916
- } catch {
52917
- return;
52918
- }
52919
- for (const entry of entries) {
52920
- const skillPath = skillFilePath(dir, entry);
52921
- if (!existsSync42(skillPath)) continue;
52922
- try {
52923
- const parsed = parseSkillMd(readFileSync33(skillPath, "utf8"), skillPath);
52924
- if (!parsed) continue;
52925
- if (seen.has(parsed.name)) continue;
52926
- seen.add(parsed.name);
52927
- out.push(entryFromParsed(parsed, projectRoot));
52928
- } catch {
52929
- }
52930
- }
52931
- }
52932
- function listSkillsSnapshot(projectRoot) {
52933
- const root = projectRoot && projectRoot.trim() ? projectRoot.trim() : null;
52934
- const userSkillsDir = getUserSkillsDir();
52935
- const projectSkillsDir = root ? getProjectSkillsDir(root) : null;
52936
- const skills = [];
52937
- const seen = /* @__PURE__ */ new Set();
52938
- if (root) {
52939
- scanSkillsDir(join34(root, ".zelari", "skills"), root, seen, skills);
52940
- scanSkillsDir(join34(root, ".claude", "skills"), root, seen, skills);
52941
- scanSkillsDir(join34(root, ".opencode", "skills"), root, seen, skills);
52942
- }
52943
- scanSkillsDir(userSkillsDir, root, seen, skills);
52944
- for (const s of listCodingSkills()) {
52945
- if (seen.has(s.id)) continue;
52946
- seen.add(s.id);
52947
- if (s.builtin) {
52948
- skills.push(entryFromBuiltin(s));
52949
- }
52950
- }
52951
- skills.sort((a, b) => {
52952
- const order = (s) => s === "project" ? 0 : s === "user" ? 1 : s === "compat" ? 2 : 3;
52953
- const d = order(a.scope) - order(b.scope);
52954
- if (d !== 0) return d;
52955
- return a.id.localeCompare(b.id);
52956
- });
52957
- return { userSkillsDir, projectSkillsDir, skills };
52958
- }
52959
- function serializeSkillMd(opts) {
52960
- const name = opts.name.trim().toLowerCase();
52961
- const description = opts.description.trim();
52962
- const body = opts.body.trim();
52963
- const lines = ["---", `name: ${name}`, `description: ${description}`];
52964
- const cat = (opts.category ?? "").trim().toLowerCase();
52965
- if (cat && CODING_CATEGORIES2.has(cat)) {
52966
- lines.push(`category: ${cat}`);
52967
- }
52968
- if (opts.tools && opts.tools.length > 0) {
52969
- lines.push(`tools: [${opts.tools.map((t) => t.trim()).filter(Boolean).join(", ")}]`);
52970
- }
52971
- const cost = (opts.cost ?? "").trim().toLowerCase();
52972
- if (cost === "low" || cost === "medium" || cost === "high") {
52973
- lines.push(`cost: ${cost}`);
52974
- }
52975
- lines.push("---", "", body, "");
52976
- return lines.join("\n");
52977
- }
52978
- function upsertSkill(opts) {
52979
- const name = opts.name.trim().toLowerCase();
52980
- if (!name || !NAME_RE.test(name)) {
52981
- return {
52982
- ok: false,
52983
- error: "Invalid skill name (use lowercase letters, digits, hyphens; max 64 chars)"
52984
- };
52985
- }
52986
- const description = opts.description.trim();
52987
- if (!description) {
52988
- return { ok: false, error: "description is required" };
52989
- }
52990
- const body = opts.body.trim();
52991
- if (!body) {
52992
- return { ok: false, error: "body is required" };
52993
- }
52994
- let dir;
52995
- if (opts.scope === "user") {
52996
- dir = getUserSkillsDir();
52997
- } else {
52998
- const root = opts.projectRoot?.trim();
52999
- if (!root) {
53000
- return {
53001
- ok: false,
53002
- error: "projectRoot required for project scope (Open Folder first)"
53003
- };
53004
- }
53005
- dir = getProjectSkillsDir(root);
53006
- }
53007
- const path48 = skillFilePath(dir, name);
53008
- const content = serializeSkillMd({
53009
- name,
53010
- description,
53011
- body,
53012
- category: opts.category,
53013
- tools: opts.tools,
53014
- cost: opts.cost
53015
- });
53016
- const parsed = parseSkillMd(content, path48);
53017
- if (!parsed) {
53018
- return { ok: false, error: "Generated SKILL.md failed validation" };
53019
- }
53020
- mkdirSync18(dirname9(path48), { recursive: true });
53021
- writeFileSync20(path48, content, "utf8");
53022
- return { ok: true, path: path48 };
53023
- }
53024
- function removeSkill(opts) {
53025
- const name = opts.name.trim().toLowerCase();
53026
- if (!name) {
53027
- return { ok: false, error: "name is required" };
53028
- }
53029
- let dir;
53030
- if (opts.scope === "user") {
53031
- dir = getUserSkillsDir();
53032
- } else {
53033
- const root = opts.projectRoot?.trim();
53034
- if (!root) {
53035
- return { ok: false, error: "projectRoot required for project scope" };
53036
- }
53037
- dir = getProjectSkillsDir(root);
53038
- }
53039
- const skillDir = join34(dir, name);
53040
- const path48 = skillFilePath(dir, name);
53041
- if (!existsSync42(path48) && !existsSync42(skillDir)) {
53042
- return { ok: false, error: `Skill "${name}" not found in ${dir}` };
53043
- }
53044
- try {
53045
- rmSync4(skillDir, { recursive: true, force: true });
53046
- } catch (err) {
53047
- return {
53048
- ok: false,
53049
- error: err instanceof Error ? err.message : String(err)
53050
- };
53051
- }
53052
- return { ok: true, path: path48 };
53053
- }
53748
+ init_skillConfigIo();
53054
53749
 
53055
53750
  // src/cli/generateSkillFromUrl.ts
53056
53751
  init_providerConfig();
53057
53752
  init_keyStore();
53058
53753
  init_openai_compatible();
53754
+ init_skillCategories();
53059
53755
  var MAX_PAGE_CHARS = 24e3;
53060
53756
  var FETCH_TIMEOUT_MS2 = 25e3;
53061
53757
  var LLM_TIMEOUT_MS = 9e4;
@@ -53139,8 +53835,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
53139
53835
  let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
53140
53836
  if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
53141
53837
  try {
53142
- 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);
53143
- 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";
53144
53840
  } catch {
53145
53841
  name = "imported-skill";
53146
53842
  }
@@ -53415,9 +54111,30 @@ function pickRootComponent() {
53415
54111
  console.error(`${red}\u2717${reset} ${result.error}`);
53416
54112
  process.exit(1);
53417
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
+ }
53418
54135
  if (argv.includes("--help") || argv.includes("-h")) {
53419
54136
  console.log(
53420
- "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"
53421
54138
  );
53422
54139
  process.exit(0);
53423
54140
  }