zelari-code 1.42.0 → 1.43.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.
@@ -21063,6 +21063,12 @@ function isBrainMemberCostEvent(e) {
21063
21063
  function isBrainCouncilModeEvent(e) {
21064
21064
  return e.type === "council_mode";
21065
21065
  }
21066
+ function isBrainTaskUpdateEvent(e) {
21067
+ return e.type === "task_update";
21068
+ }
21069
+ function isBrainTaskSnapshotEvent(e) {
21070
+ return e.type === "task_snapshot";
21071
+ }
21066
21072
  function createBrainEvent(type, sessionId, data) {
21067
21073
  return {
21068
21074
  type,
@@ -28073,6 +28079,8 @@ __export(dist_exports, {
28073
28079
  isBrainMessageStartEvent: () => isBrainMessageStartEvent,
28074
28080
  isBrainQueueUpdateEvent: () => isBrainQueueUpdateEvent,
28075
28081
  isBrainSessionCompactedEvent: () => isBrainSessionCompactedEvent,
28082
+ isBrainTaskSnapshotEvent: () => isBrainTaskSnapshotEvent,
28083
+ isBrainTaskUpdateEvent: () => isBrainTaskUpdateEvent,
28076
28084
  isBrainThinkingDeltaEvent: () => isBrainThinkingDeltaEvent,
28077
28085
  isBrainToolExecutionEndEvent: () => isBrainToolExecutionEndEvent,
28078
28086
  isBrainToolExecutionStartEvent: () => isBrainToolExecutionStartEvent,
@@ -31026,6 +31034,771 @@ var init_todoTools = __esm({
31026
31034
  }
31027
31035
  });
31028
31036
 
31037
+ // src/cli/workspace/paths.ts
31038
+ import {
31039
+ mkdirSync as mkdirSync10,
31040
+ writeFileSync as writeFileSync12,
31041
+ existsSync as existsSync17,
31042
+ accessSync,
31043
+ constants,
31044
+ realpathSync
31045
+ } from "node:fs";
31046
+ import { join as join13, basename } from "node:path";
31047
+ import { homedir as homedir5 } from "node:os";
31048
+ import { createHash as createHash4 } from "node:crypto";
31049
+ function resolveWorkspaceRoot(projectRoot = process.cwd()) {
31050
+ const candidates = [
31051
+ join13(projectRoot, ".zelari"),
31052
+ join13(homedir5(), ".zelari-code", "workspace", hashProject(projectRoot))
31053
+ ];
31054
+ for (const candidate of candidates) {
31055
+ if (isWritableDir(projectRoot) || candidate !== candidates[0]) {
31056
+ ensureWorkspaceDir(candidate);
31057
+ return candidate;
31058
+ }
31059
+ }
31060
+ ensureWorkspaceDir(candidates[0]);
31061
+ return candidates[0];
31062
+ }
31063
+ function hashProject(projectPath) {
31064
+ return createHash4("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
31065
+ }
31066
+ function isWritableDir(dir) {
31067
+ try {
31068
+ if (!existsSync17(dir)) return false;
31069
+ accessSync(dir, constants.W_OK);
31070
+ return true;
31071
+ } catch {
31072
+ return false;
31073
+ }
31074
+ }
31075
+ function ensureWorkspaceDir(workspaceDir) {
31076
+ mkdirSync10(workspaceDir, { recursive: true });
31077
+ if (workspaceDir.endsWith("/.zelari") && existsSync17(join13(workspaceDir, "..", ".git"))) {
31078
+ const gitignorePath = join13(workspaceDir, ".gitignore");
31079
+ if (!existsSync17(gitignorePath)) {
31080
+ writeFileSync12(gitignorePath, "*\n!.gitignore\n");
31081
+ }
31082
+ }
31083
+ }
31084
+ function workspaceFile(rootDir, kind) {
31085
+ switch (kind) {
31086
+ case "plan":
31087
+ return join13(rootDir, "plan.md");
31088
+ case "risks":
31089
+ return join13(rootDir, "risks.md");
31090
+ case "index":
31091
+ return join13(rootDir, "workspace.json");
31092
+ }
31093
+ }
31094
+ function workspaceArtifact(rootDir, subdir, slug) {
31095
+ return join13(rootDir, subdir, `${slug}.md`);
31096
+ }
31097
+ function projectName(projectRoot = process.cwd()) {
31098
+ return basename(realpathSync(projectRoot));
31099
+ }
31100
+ var init_paths2 = __esm({
31101
+ "src/cli/workspace/paths.ts"() {
31102
+ "use strict";
31103
+ }
31104
+ });
31105
+
31106
+ // src/cli/workspace/storage.ts
31107
+ var storage_exports = {};
31108
+ __export(storage_exports, {
31109
+ Storage: () => Storage,
31110
+ parseFrontmatter: () => parseFrontmatter,
31111
+ parseYaml: () => parseYaml,
31112
+ serializeFrontmatter: () => serializeFrontmatter,
31113
+ serializeYaml: () => serializeYaml,
31114
+ workspaceMutex: () => workspaceMutex
31115
+ });
31116
+ import {
31117
+ readFileSync as readFileSync16,
31118
+ writeFileSync as writeFileSync13,
31119
+ existsSync as existsSync18,
31120
+ mkdirSync as mkdirSync11,
31121
+ readdirSync as readdirSync4,
31122
+ renameSync as renameSync2
31123
+ } from "node:fs";
31124
+ import { dirname as dirname2, join as join14 } from "node:path";
31125
+ function parseFrontmatter(md) {
31126
+ const m = FRONTMATTER_RE.exec(md);
31127
+ if (!m) return { meta: {}, body: md };
31128
+ const meta3 = parseYaml(m[1]);
31129
+ const body = m[2];
31130
+ return { meta: meta3, body };
31131
+ }
31132
+ function serializeFrontmatter(meta3, body) {
31133
+ const yamlStr = serializeYaml(meta3);
31134
+ return `---
31135
+ ${yamlStr}
31136
+ ---
31137
+ ${body}`;
31138
+ }
31139
+ function parseYaml(input) {
31140
+ const lines = input.split(/\r?\n/);
31141
+ const ctx = { lines, i: 0 };
31142
+ return parseNode(ctx, 0);
31143
+ }
31144
+ function parseNode(ctx, indent) {
31145
+ while (ctx.i < ctx.lines.length) {
31146
+ const line2 = ctx.lines[ctx.i];
31147
+ if (line2.trim() === "" || line2.trim().startsWith("#")) {
31148
+ ctx.i++;
31149
+ continue;
31150
+ }
31151
+ break;
31152
+ }
31153
+ if (ctx.i >= ctx.lines.length) return null;
31154
+ const line = ctx.lines[ctx.i];
31155
+ const lineIndent = countIndent(line);
31156
+ if (/^\s*-\s+/.test(line)) {
31157
+ return parseBlockSequence(ctx, indent);
31158
+ }
31159
+ if (/^\s*\[.*\]\s*$/.test(line)) {
31160
+ const flow = line.trim().replace(/^\[/, "").replace(/\]$/, "");
31161
+ return parseFlowSequence(flow);
31162
+ }
31163
+ if (/^\s*\{.*\}\s*$/.test(line)) {
31164
+ const flow = line.trim().replace(/^\{/, "").replace(/\}$/, "");
31165
+ return parseFlowMap(flow);
31166
+ }
31167
+ return parseBlockMap(ctx, indent);
31168
+ }
31169
+ function parseBlockMap(ctx, indent) {
31170
+ const out = {};
31171
+ while (ctx.i < ctx.lines.length) {
31172
+ const line = ctx.lines[ctx.i];
31173
+ if (line.trim() === "" || line.trim().startsWith("#")) {
31174
+ ctx.i++;
31175
+ continue;
31176
+ }
31177
+ const lineIndent = countIndent(line);
31178
+ if (lineIndent < indent) break;
31179
+ if (lineIndent > indent) {
31180
+ ctx.i++;
31181
+ continue;
31182
+ }
31183
+ const m = /^([^:]+):\s*(.*)$/.exec(line);
31184
+ if (!m) {
31185
+ ctx.i++;
31186
+ continue;
31187
+ }
31188
+ const key = m[1].trim();
31189
+ const valuePart = m[2].trim();
31190
+ if (valuePart === "" || valuePart === "|" || valuePart === ">") {
31191
+ ctx.i++;
31192
+ const nested = parseNode(ctx, indent + 2);
31193
+ out[key] = nested;
31194
+ } else {
31195
+ if (valuePart.startsWith("[")) {
31196
+ out[key] = parseFlowSequence(stripFlow(valuePart, "[", "]"));
31197
+ } else if (valuePart.startsWith("{")) {
31198
+ out[key] = parseFlowMap(stripFlow(valuePart, "{", "}"));
31199
+ } else {
31200
+ out[key] = parseScalar(valuePart);
31201
+ }
31202
+ ctx.i++;
31203
+ }
31204
+ }
31205
+ return out;
31206
+ }
31207
+ function parseBlockSequence(ctx, indent) {
31208
+ const out = [];
31209
+ while (ctx.i < ctx.lines.length) {
31210
+ const line = ctx.lines[ctx.i];
31211
+ if (line.trim() === "") {
31212
+ ctx.i++;
31213
+ continue;
31214
+ }
31215
+ const lineIndent = countIndent(line);
31216
+ if (lineIndent < indent) break;
31217
+ if (lineIndent > indent) break;
31218
+ const m = /^-\s*(.*)$/.exec(line);
31219
+ if (!m) break;
31220
+ const rest = m[1];
31221
+ if (rest === "") {
31222
+ ctx.i++;
31223
+ out.push(parseNode(ctx, indent + 2));
31224
+ } else if (rest.startsWith("[") || rest.startsWith("{")) {
31225
+ let buffer = rest;
31226
+ let depthSq = (buffer.match(/\[/g) || []).length - (buffer.match(/\]/g) || []).length;
31227
+ let depthCu = (buffer.match(/\{/g) || []).length - (buffer.match(/\}/g) || []).length;
31228
+ while ((depthSq > 0 || depthCu > 0) && ctx.i + 1 < ctx.lines.length) {
31229
+ ctx.i++;
31230
+ const next = ctx.lines[ctx.i].trim();
31231
+ buffer += " " + next;
31232
+ depthSq = (buffer.match(/\[/g) || []).length - (buffer.match(/\]/g) || []).length;
31233
+ depthCu = (buffer.match(/\{/g) || []).length - (buffer.match(/\}/g) || []).length;
31234
+ }
31235
+ if (buffer.startsWith("[")) {
31236
+ out.push(parseFlowSequence(buffer.slice(1).replace(/\]$/, "")));
31237
+ } else {
31238
+ out.push(parseFlowMap(buffer.slice(1).replace(/\}$/, "")));
31239
+ }
31240
+ ctx.i++;
31241
+ } else if (rest.includes(":")) {
31242
+ ctx.i++;
31243
+ const mapCtx = { lines: [" ".repeat(indent + 2) + rest, ...ctx.lines.slice(ctx.i)], i: 0 };
31244
+ const val = parseBlockMap(mapCtx, indent + 2);
31245
+ ctx.i += mapCtx.i - 1;
31246
+ out.push(val);
31247
+ } else {
31248
+ out.push(parseScalar(rest));
31249
+ ctx.i++;
31250
+ }
31251
+ }
31252
+ return out;
31253
+ }
31254
+ function parseFlowSequence(input) {
31255
+ const parts = splitFlow(input);
31256
+ return parts.map((p3) => {
31257
+ const trimmed = p3.trim();
31258
+ if (trimmed.startsWith("{")) {
31259
+ return parseFlowMap(stripFlow(trimmed, "{", "}"));
31260
+ }
31261
+ return parseScalar(trimmed);
31262
+ });
31263
+ }
31264
+ function stripFlow(s, open, close) {
31265
+ let out = s.trim();
31266
+ if (out.startsWith(open)) out = out.slice(1);
31267
+ if (out.endsWith(close)) out = out.slice(0, -1);
31268
+ return out;
31269
+ }
31270
+ function parseFlowMap(input) {
31271
+ const parts = splitFlow(input);
31272
+ const out = {};
31273
+ for (const p3 of parts) {
31274
+ const colonIdx = p3.indexOf(":");
31275
+ if (colonIdx < 0) continue;
31276
+ const key = p3.slice(0, colonIdx).trim();
31277
+ const value = p3.slice(colonIdx + 1).trim();
31278
+ out[key] = parseScalar(value);
31279
+ }
31280
+ return out;
31281
+ }
31282
+ function splitFlow(input) {
31283
+ const out = [];
31284
+ let depthSq = 0, depthCu = 0, depthQu = 0;
31285
+ let buffer = "";
31286
+ for (let i = 0; i < input.length; i++) {
31287
+ const c = input[i];
31288
+ if (c === '"' || c === "'") {
31289
+ depthQu = depthQu === 0 ? depthQu + 1 : 0;
31290
+ buffer += c;
31291
+ } else if (depthQu === 0) {
31292
+ if (c === "[") depthSq++;
31293
+ else if (c === "]") depthSq--;
31294
+ else if (c === "{") depthCu++;
31295
+ else if (c === "}") depthCu--;
31296
+ else if (c === "," && depthSq === 0 && depthCu === 0) {
31297
+ out.push(buffer);
31298
+ buffer = "";
31299
+ continue;
31300
+ }
31301
+ buffer += c;
31302
+ } else {
31303
+ buffer += c;
31304
+ }
31305
+ }
31306
+ if (buffer.trim()) out.push(buffer);
31307
+ return out;
31308
+ }
31309
+ function parseScalar(s) {
31310
+ if (s === "" || s === "null" || s === "~") return null;
31311
+ if (VALID_SCALARS.test(s)) return s.toLowerCase() === "true";
31312
+ if (/^-?\d+(\.\d+)?$/.test(s)) return Number(s);
31313
+ if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
31314
+ return s.slice(1, -1);
31315
+ }
31316
+ return s;
31317
+ }
31318
+ function serializeYaml(value, indent = 0) {
31319
+ if (value === null || value === void 0) return "";
31320
+ if (typeof value === "string") {
31321
+ if (/[:#\n\[\]\{\},&*!|>'"%@`]/.test(value)) {
31322
+ return JSON.stringify(value);
31323
+ }
31324
+ return value;
31325
+ }
31326
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
31327
+ if (Array.isArray(value)) {
31328
+ if (value.length === 0) return "[]";
31329
+ if (value.every((v) => v === null || typeof v !== "object")) {
31330
+ return `[${value.map(serializeScalarInline).join(", ")}]`;
31331
+ }
31332
+ return `[${value.map((v) => "{" + serializeInlineObject(v) + "}").join(", ")}]`;
31333
+ }
31334
+ if (typeof value === "object") {
31335
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0);
31336
+ return entries.map(([k, v]) => {
31337
+ if (v === null || v === void 0) return `${k}:`;
31338
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
31339
+ return `${k}: ${serializeScalarInline(v)}`;
31340
+ }
31341
+ if (Array.isArray(v)) {
31342
+ if (v.length === 0) return `${k}: []`;
31343
+ if (v.every((x) => x === null || typeof x !== "object")) {
31344
+ return `${k}: [${v.map(serializeScalarInline).join(", ")}]`;
31345
+ }
31346
+ return `${k}: [${v.map((x) => "{" + serializeInlineObject(x) + "}").join(", ")}]`;
31347
+ }
31348
+ return `${k}:
31349
+ ${serializeYaml(v, indent + 2)}`;
31350
+ }).map((line) => `${" ".repeat(indent)}${line}`).join("\n");
31351
+ }
31352
+ return String(value);
31353
+ }
31354
+ function serializeScalarInline(v) {
31355
+ if (typeof v === "string" && /[:#\n\[\]\{\},&*!|>'"%@`]/.test(v)) return JSON.stringify(v);
31356
+ return String(v);
31357
+ }
31358
+ function serializeInlineObject(v) {
31359
+ if (v === null || typeof v !== "object" || Array.isArray(v)) {
31360
+ return serializeScalarInline(v);
31361
+ }
31362
+ const entries = Object.entries(v).filter(([, val]) => val !== void 0);
31363
+ return entries.map(([k, val]) => `${k}: ${serializeYaml(val)}`).join(", ");
31364
+ }
31365
+ function countIndent(line) {
31366
+ let i = 0;
31367
+ while (i < line.length && line[i] === " ") i++;
31368
+ return i;
31369
+ }
31370
+ var FRONTMATTER_RE, VALID_SCALARS, Storage, KeyedMutex, workspaceMutex;
31371
+ var init_storage = __esm({
31372
+ "src/cli/workspace/storage.ts"() {
31373
+ "use strict";
31374
+ FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
31375
+ VALID_SCALARS = /^(true|false|null|~)$/i;
31376
+ Storage = class {
31377
+ /** Read a Markdown file with frontmatter. Throws if not found. */
31378
+ read(path53) {
31379
+ if (!existsSync18(path53)) {
31380
+ throw new Error(`File not found: ${path53}`);
31381
+ }
31382
+ const md = readFileSync16(path53, "utf8");
31383
+ return parseFrontmatter(md);
31384
+ }
31385
+ /** Read a Markdown file; returns null if not found. */
31386
+ readIfExists(path53) {
31387
+ if (!existsSync18(path53)) return null;
31388
+ return this.read(path53);
31389
+ }
31390
+ /**
31391
+ * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
31392
+ * The meta object is serialized as YAML frontmatter; body as Markdown.
31393
+ */
31394
+ write(path53, meta3, body) {
31395
+ mkdirSync11(dirname2(path53), { recursive: true });
31396
+ const tmp = path53 + ".tmp-" + process.pid;
31397
+ const md = serializeFrontmatter(meta3, body);
31398
+ writeFileSync13(tmp, md, "utf8");
31399
+ renameSync2(tmp, path53);
31400
+ }
31401
+ /** List all .md files in a directory (non-recursive). */
31402
+ listMarkdown(dir) {
31403
+ if (!existsSync18(dir)) return [];
31404
+ return readdirSync4(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join14(dir, f));
31405
+ }
31406
+ };
31407
+ KeyedMutex = class {
31408
+ chains = /* @__PURE__ */ new Map();
31409
+ async run(key, fn) {
31410
+ const prev2 = this.chains.get(key) ?? Promise.resolve();
31411
+ let release = () => {
31412
+ };
31413
+ const next = new Promise((resolve3) => {
31414
+ release = resolve3;
31415
+ });
31416
+ const chained = prev2.then(() => next);
31417
+ this.chains.set(key, chained);
31418
+ await prev2;
31419
+ try {
31420
+ return await fn();
31421
+ } finally {
31422
+ release();
31423
+ if (this.chains.get(key) === chained) {
31424
+ this.chains.delete(key);
31425
+ }
31426
+ }
31427
+ }
31428
+ };
31429
+ workspaceMutex = new KeyedMutex();
31430
+ }
31431
+ });
31432
+
31433
+ // src/cli/workspace/planStore.ts
31434
+ import {
31435
+ copyFileSync,
31436
+ existsSync as existsSync19,
31437
+ mkdirSync as mkdirSync12,
31438
+ readFileSync as readFileSync17,
31439
+ renameSync as renameSync3,
31440
+ writeFileSync as writeFileSync14
31441
+ } from "node:fs";
31442
+ import { dirname as dirname3, join as join15 } from "node:path";
31443
+ async function withPlanStore(projectRoot, fn) {
31444
+ const rootDir = resolveWorkspaceRoot(projectRoot);
31445
+ return workspaceMutex.run(`${rootDir}:plan`, () => {
31446
+ const handle = loadHandle(rootDir);
31447
+ const out = fn(handle);
31448
+ saveHandle(rootDir, handle);
31449
+ return out;
31450
+ });
31451
+ }
31452
+ function nextPlanTaskId(store4) {
31453
+ const maxExisting = store4.tasks.reduce((max, t) => {
31454
+ const m = /^t(\d+)$/.exec(t.id);
31455
+ return m ? Math.max(max, parseInt(m[1], 10)) : max;
31456
+ }, 0);
31457
+ store4.counter = Math.max(store4.counter, maxExisting) + 1;
31458
+ return `t${store4.counter}`;
31459
+ }
31460
+ function writePlanTaskArtifact(rootDir, task) {
31461
+ const path53 = join15(rootDir, "plan-tasks", `${task.id}.md`);
31462
+ mkdirSync12(dirname3(path53), { recursive: true });
31463
+ const meta3 = {
31464
+ kind: "task",
31465
+ id: task.id,
31466
+ name: task.title,
31467
+ phaseId: task.phaseId,
31468
+ status: task.status,
31469
+ priority: task.priority ?? "medium",
31470
+ updatedAt: task.updatedAt
31471
+ };
31472
+ const body = [
31473
+ `# Task ${task.id}: ${task.title}`,
31474
+ "",
31475
+ `- Status: **${task.status}**`,
31476
+ `- Priority: ${task.priority ?? "medium"}`,
31477
+ task.phaseId ? `- Phase: ${task.phaseId}` : null,
31478
+ task.agent ? `- Agent: ${task.agent}` : null,
31479
+ "",
31480
+ task.notes?.trim() ? task.notes.trim() : "_(no notes)_",
31481
+ ""
31482
+ ].filter((l) => l !== null).join("\n");
31483
+ new Storage().write(path53, meta3, body);
31484
+ }
31485
+ function loadHandle(rootDir) {
31486
+ const jsonPath = join15(rootDir, "plan.json");
31487
+ if (!existsSync19(jsonPath)) {
31488
+ return { rootDir, tasks: [], counter: 0, rootFields: {} };
31489
+ }
31490
+ let parsed;
31491
+ try {
31492
+ const raw = JSON.parse(readFileSync17(jsonPath, "utf8"));
31493
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
31494
+ throw new Error("root is not a JSON object");
31495
+ }
31496
+ parsed = raw;
31497
+ } catch {
31498
+ throw new PlanStoreError(
31499
+ `PLAN_CORRUPT: ${jsonPath} is not valid JSON \u2014 refusing to overwrite a possibly hand-edited or crashed write. Fix or remove the file (a .bak may exist), then retry.`,
31500
+ "PLAN_CORRUPT"
31501
+ );
31502
+ }
31503
+ const {
31504
+ tasks: rawTasks,
31505
+ counter,
31506
+ schemaVersion: _schemaVersion,
31507
+ ...rootFields
31508
+ } = parsed;
31509
+ const tasks = (Array.isArray(rawTasks) ? rawTasks : []).map(normalizeTask).filter((t) => typeof t.id === "string" && t.id.length > 0);
31510
+ const numericCounter = typeof counter === "number" && Number.isFinite(counter) && counter >= 0 ? Math.floor(counter) : 0;
31511
+ return { rootDir, tasks, counter: numericCounter, rootFields };
31512
+ }
31513
+ function saveHandle(rootDir, handle) {
31514
+ if (handle.tasks.length > PLAN_MAX_TASKS) {
31515
+ throw new PlanStoreError(
31516
+ `PLAN_TOO_MANY_TASKS: plan.json would exceed ${PLAN_MAX_TASKS} tasks (${handle.tasks.length}) \u2014 cancel or complete tasks first.`,
31517
+ "PLAN_TOO_MANY_TASKS"
31518
+ );
31519
+ }
31520
+ const jsonPath = join15(rootDir, "plan.json");
31521
+ mkdirSync12(rootDir, { recursive: true });
31522
+ if (existsSync19(jsonPath)) {
31523
+ copyFileSync(jsonPath, `${jsonPath}.bak`);
31524
+ }
31525
+ const file2 = {
31526
+ ...handle.rootFields,
31527
+ schemaVersion: PLAN_SCHEMA_VERSION,
31528
+ counter: handle.counter,
31529
+ tasks: handle.tasks
31530
+ };
31531
+ const tmp = `${jsonPath}.tmp-${process.pid}`;
31532
+ writeFileSync14(tmp, JSON.stringify(file2, null, 2) + "\n", "utf8");
31533
+ renameSync3(tmp, jsonPath);
31534
+ }
31535
+ function normalizeTask(raw) {
31536
+ const t = raw !== null && typeof raw === "object" ? { ...raw } : {};
31537
+ if (typeof t.id === "string") {
31538
+ t.id = t.id.trim().slice(0, 64);
31539
+ }
31540
+ const titleSource = firstString(t.title) ?? firstString(t.name) ?? firstString(t.description) ?? "";
31541
+ t.title = titleSource.slice(0, PLAN_TITLE_MAX);
31542
+ if (t.title && !firstString(t.name)) {
31543
+ t.name = t.title;
31544
+ }
31545
+ t.status = normalizeStatus(t.status);
31546
+ if (typeof t.notes === "string") {
31547
+ t.notes = t.notes.slice(0, PLAN_NOTES_MAX);
31548
+ }
31549
+ if (typeof t.phaseId === "string") {
31550
+ t.phaseId = t.phaseId.slice(0, PLAN_TAG_MAX);
31551
+ }
31552
+ if (typeof t.agent === "string") {
31553
+ t.agent = t.agent.slice(0, PLAN_TAG_MAX);
31554
+ }
31555
+ return t;
31556
+ }
31557
+ function normalizeStatus(raw) {
31558
+ const s = typeof raw === "string" ? raw.trim().toLowerCase() : "";
31559
+ switch (s) {
31560
+ case "in_progress":
31561
+ case "in-progress":
31562
+ case "doing":
31563
+ case "started":
31564
+ case "active":
31565
+ return "in_progress";
31566
+ case "completed":
31567
+ case "complete":
31568
+ case "done":
31569
+ case "finished":
31570
+ return "completed";
31571
+ case "cancelled":
31572
+ case "canceled":
31573
+ case "closed":
31574
+ return "cancelled";
31575
+ case "blocked":
31576
+ case "on-hold":
31577
+ case "on hold":
31578
+ return "blocked";
31579
+ default:
31580
+ return "pending";
31581
+ }
31582
+ }
31583
+ function firstString(v) {
31584
+ return typeof v === "string" && v.trim().length > 0 ? v : null;
31585
+ }
31586
+ var PLAN_SCHEMA_VERSION, PLAN_MAX_TASKS, PLAN_TITLE_MAX, PLAN_NOTES_MAX, PLAN_TAG_MAX, PlanStoreError;
31587
+ var init_planStore = __esm({
31588
+ "src/cli/workspace/planStore.ts"() {
31589
+ "use strict";
31590
+ init_paths2();
31591
+ init_storage();
31592
+ PLAN_SCHEMA_VERSION = 1;
31593
+ PLAN_MAX_TASKS = 100;
31594
+ PLAN_TITLE_MAX = 200;
31595
+ PLAN_NOTES_MAX = 2e3;
31596
+ PLAN_TAG_MAX = 64;
31597
+ PlanStoreError = class extends Error {
31598
+ constructor(message, code) {
31599
+ super(message);
31600
+ this.code = code;
31601
+ this.name = "PlanStoreError";
31602
+ }
31603
+ };
31604
+ }
31605
+ });
31606
+
31607
+ // src/cli/tools/planTaskTools.ts
31608
+ function taskSummaryLine(t) {
31609
+ return `- ${t.id}: ${t.title} (${t.status}${t.priority ? `, ${t.priority}` : ""})`;
31610
+ }
31611
+ function toTaskPayload(t) {
31612
+ return {
31613
+ id: t.id,
31614
+ title: t.title,
31615
+ status: t.status,
31616
+ phaseId: t.phaseId,
31617
+ priority: t.priority
31618
+ };
31619
+ }
31620
+ function safeEmit(sink, event) {
31621
+ if (!sink) return;
31622
+ try {
31623
+ sink(event);
31624
+ } catch {
31625
+ }
31626
+ }
31627
+ function createPlanTaskTools(opts) {
31628
+ const projectRoot = opts.projectRoot;
31629
+ const onTaskEvent = opts.onTaskEvent;
31630
+ const taskCreate = {
31631
+ name: "task_create",
31632
+ description: "Create a durable workspace task in .zelari/plan.json (multi-session, shared with the Desktop Live Tasks panel). Use for project work that must survive this session. For volatile per-session tracking use todo_write instead. Returns the assigned id (t<N>).",
31633
+ permissions: ["write"],
31634
+ timeoutMs: 5e3,
31635
+ inputSchema: CreateSchema,
31636
+ execute: async (input) => {
31637
+ try {
31638
+ const res = await withPlanStore(projectRoot, (store4) => {
31639
+ if (store4.tasks.length >= PLAN_MAX_TASKS) {
31640
+ return typedErr(
31641
+ `PLAN_TOO_MANY_TASKS: plan.json already holds ${store4.tasks.length} tasks (max ${PLAN_MAX_TASKS}).`
31642
+ );
31643
+ }
31644
+ const now = (/* @__PURE__ */ new Date()).toISOString();
31645
+ const id = nextPlanTaskId(store4);
31646
+ const task = {
31647
+ id,
31648
+ title: input.title.trim().slice(0, PLAN_TITLE_MAX),
31649
+ // council readers (buildPlanSummary) render `name` — keep the
31650
+ // alias in sync from creation, not just on reload.
31651
+ name: input.title.trim().slice(0, PLAN_TITLE_MAX),
31652
+ status: "pending",
31653
+ priority: input.priority,
31654
+ phaseId: input.phaseId?.trim().slice(0, 64),
31655
+ notes: input.notes?.trim().slice(0, PLAN_NOTES_MAX),
31656
+ createdAt: now,
31657
+ updatedAt: now
31658
+ };
31659
+ store4.tasks.push(task);
31660
+ writePlanTaskArtifact(store4.rootDir, task);
31661
+ return typedOk({ id, task });
31662
+ });
31663
+ if (res.ok) {
31664
+ safeEmit(onTaskEvent, {
31665
+ type: "task_update",
31666
+ source: "workspace_plan",
31667
+ task: toTaskPayload(res.value.task)
31668
+ });
31669
+ }
31670
+ return res;
31671
+ } catch (err) {
31672
+ return typedErr(planStoreErrorMessage(err, "task_create"));
31673
+ }
31674
+ }
31675
+ };
31676
+ const taskUpdate = {
31677
+ name: "task_update",
31678
+ description: "Update a durable workspace task in .zelari/plan.json (status, title, priority, phaseId, notes, or appendNote). Accepts council-created ids too (task_list shows them). Errors with PLAN_TASK_NOT_FOUND on unknown ids.",
31679
+ permissions: ["write"],
31680
+ timeoutMs: 5e3,
31681
+ inputSchema: UpdateSchema,
31682
+ execute: async (input) => {
31683
+ try {
31684
+ const res = await withPlanStore(projectRoot, (store4) => {
31685
+ const task = store4.tasks.find((t) => t.id === input.id);
31686
+ if (!task) {
31687
+ return typedErr(
31688
+ `PLAN_TASK_NOT_FOUND: no task with id "${input.id}" in .zelari/plan.json (call task_list for current ids).`
31689
+ );
31690
+ }
31691
+ if (input.title !== void 0) {
31692
+ task.title = input.title.trim().slice(0, PLAN_TITLE_MAX);
31693
+ task.name = task.title;
31694
+ }
31695
+ if (input.status !== void 0) task.status = input.status;
31696
+ if (input.priority !== void 0) task.priority = input.priority;
31697
+ if (input.phaseId !== void 0) task.phaseId = input.phaseId.slice(0, 64);
31698
+ if (input.notes !== void 0) {
31699
+ task.notes = input.notes.slice(0, PLAN_NOTES_MAX);
31700
+ }
31701
+ if (input.appendNote !== void 0) {
31702
+ const prev2 = typeof task.notes === "string" ? task.notes : "";
31703
+ const merged = prev2 ? `${prev2}
31704
+ ${input.appendNote}` : input.appendNote;
31705
+ task.notes = merged.slice(-PLAN_NOTES_MAX);
31706
+ }
31707
+ task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
31708
+ writePlanTaskArtifact(store4.rootDir, task);
31709
+ return typedOk({ task });
31710
+ });
31711
+ if (res.ok) {
31712
+ safeEmit(onTaskEvent, {
31713
+ type: "task_update",
31714
+ source: "workspace_plan",
31715
+ task: toTaskPayload(res.value.task)
31716
+ });
31717
+ }
31718
+ return res;
31719
+ } catch (err) {
31720
+ return typedErr(planStoreErrorMessage(err, "task_update"));
31721
+ }
31722
+ }
31723
+ };
31724
+ const taskList = {
31725
+ name: "task_list",
31726
+ description: "List durable workspace tasks from .zelari/plan.json (both t<N> agent tasks and council-created plan tasks). Optional status/phaseId filters. Use before task_update to discover ids.",
31727
+ permissions: ["read"],
31728
+ timeoutMs: 5e3,
31729
+ inputSchema: ListSchema,
31730
+ execute: async (input) => {
31731
+ try {
31732
+ let allPayloads = [];
31733
+ const res = await withPlanStore(projectRoot, (store4) => {
31734
+ allPayloads = store4.tasks.map(toTaskPayload);
31735
+ const filtered = store4.tasks.filter(
31736
+ (t) => (input.status === void 0 || t.status === input.status) && (input.phaseId === void 0 || t.phaseId === input.phaseId)
31737
+ );
31738
+ const done = store4.tasks.filter(
31739
+ (t) => t.status === "completed" || t.status === "cancelled"
31740
+ ).length;
31741
+ const formatted = filtered.length === 0 ? "(no matching workspace tasks)" : filtered.map(taskSummaryLine).join("\n");
31742
+ return typedOk({
31743
+ tasks: filtered,
31744
+ total: store4.tasks.length,
31745
+ done,
31746
+ formatted: `${formatted}
31747
+ (done/total: ${done}/${store4.tasks.length})`
31748
+ });
31749
+ });
31750
+ if (res.ok) {
31751
+ safeEmit(onTaskEvent, {
31752
+ type: "task_snapshot",
31753
+ source: "workspace_plan",
31754
+ tasks: allPayloads
31755
+ });
31756
+ }
31757
+ return res;
31758
+ } catch (err) {
31759
+ return typedErr(planStoreErrorMessage(err, "task_list"));
31760
+ }
31761
+ }
31762
+ };
31763
+ return [taskCreate, taskUpdate, taskList];
31764
+ }
31765
+ function planStoreErrorMessage(err, tool) {
31766
+ if (err instanceof PlanStoreError) return err.message;
31767
+ return `[${tool}] ${err instanceof Error ? err.message : String(err)}`;
31768
+ }
31769
+ var StatusSchema2, PrioritySchema, CreateSchema, UpdateSchema, ListSchema;
31770
+ var init_planTaskTools = __esm({
31771
+ "src/cli/tools/planTaskTools.ts"() {
31772
+ "use strict";
31773
+ init_zod();
31774
+ init_toolTypes();
31775
+ init_planStore();
31776
+ StatusSchema2 = external_exports.enum(["pending", "in_progress", "completed", "cancelled", "blocked"]).describe(
31777
+ "blocked exists only here (session todos have no blocked); no rigid FSM \u2014 corrections like completed \u2192 in_progress are allowed"
31778
+ );
31779
+ PrioritySchema = external_exports.enum(["low", "medium", "high", "critical"]);
31780
+ CreateSchema = external_exports.object({
31781
+ title: external_exports.string().min(1).max(PLAN_TITLE_MAX).describe("Short task description"),
31782
+ priority: PrioritySchema.optional().describe("Default medium"),
31783
+ phaseId: external_exports.string().min(1).max(64).optional().describe("Existing plan phase id (see .zelari/plan.json phases)"),
31784
+ notes: external_exports.string().max(PLAN_NOTES_MAX).optional().describe("Optional context/acceptance notes")
31785
+ });
31786
+ UpdateSchema = external_exports.object({
31787
+ id: external_exports.string().min(1).max(64).describe("Task id from task_create/task_list"),
31788
+ status: StatusSchema2.optional(),
31789
+ title: external_exports.string().min(1).max(PLAN_TITLE_MAX).optional(),
31790
+ priority: PrioritySchema.optional(),
31791
+ phaseId: external_exports.string().min(1).max(64).optional(),
31792
+ notes: external_exports.string().max(PLAN_NOTES_MAX).optional(),
31793
+ appendNote: external_exports.string().max(PLAN_NOTES_MAX).optional().describe("Append to existing notes (kept within the size cap)")
31794
+ }).describe("At least one field besides id must be present");
31795
+ ListSchema = external_exports.object({
31796
+ status: StatusSchema2.optional().describe("Filter by status"),
31797
+ phaseId: external_exports.string().min(1).max(64).optional().describe("Filter by phase")
31798
+ });
31799
+ }
31800
+ });
31801
+
31029
31802
  // src/cli/lsp/protocol.ts
31030
31803
  function encodeMessage(message) {
31031
31804
  const json2 = JSON.stringify(message);
@@ -31329,7 +32102,7 @@ var init_servers = __esm({
31329
32102
 
31330
32103
  // src/cli/lsp/manager.ts
31331
32104
  import { spawn as spawn4 } from "node:child_process";
31332
- import { readFileSync as readFileSync16 } from "node:fs";
32105
+ import { readFileSync as readFileSync18 } from "node:fs";
31333
32106
  function processTransport(child) {
31334
32107
  return {
31335
32108
  send: (data) => {
@@ -31532,7 +32305,7 @@ var init_manager = __esm({
31532
32305
  const uri = pathToUri(file2);
31533
32306
  let text;
31534
32307
  try {
31535
- text = readFileSync16(file2, "utf8");
32308
+ text = readFileSync18(file2, "utf8");
31536
32309
  } catch {
31537
32310
  text = "";
31538
32311
  }
@@ -31854,13 +32627,13 @@ var init_store = __esm({
31854
32627
  });
31855
32628
 
31856
32629
  // src/cli/semantic/index.ts
31857
- import { promises as fs12, existsSync as existsSync17, readFileSync as readFileSync17 } from "node:fs";
31858
- import { homedir as homedir5 } from "node:os";
32630
+ import { promises as fs12, existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
32631
+ import { homedir as homedir6 } from "node:os";
31859
32632
  import path23 from "node:path";
31860
- import { createHash as createHash4 } from "node:crypto";
32633
+ import { createHash as createHash5 } from "node:crypto";
31861
32634
  function getIndexPath(root) {
31862
- const hash3 = createHash4("sha1").update(path23.resolve(root)).digest("hex").slice(0, 16);
31863
- return process.env.ZELARI_SEMANTIC_FILE ?? path23.join(homedir5(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
32635
+ const hash3 = createHash5("sha1").update(path23.resolve(root)).digest("hex").slice(0, 16);
32636
+ return process.env.ZELARI_SEMANTIC_FILE ?? path23.join(homedir6(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
31864
32637
  }
31865
32638
  async function collectSourceFiles(root, maxFiles = 1500) {
31866
32639
  const out = [];
@@ -31938,9 +32711,9 @@ async function saveIndex(root, data) {
31938
32711
  }
31939
32712
  function loadIndex(root) {
31940
32713
  const file2 = getIndexPath(root);
31941
- if (!existsSync17(file2)) return null;
32714
+ if (!existsSync20(file2)) return null;
31942
32715
  try {
31943
- const parsed = JSON.parse(readFileSync17(file2, "utf8"));
32716
+ const parsed = JSON.parse(readFileSync19(file2, "utf8"));
31944
32717
  if (parsed && Array.isArray(parsed.chunks)) return parsed;
31945
32718
  } catch {
31946
32719
  }
@@ -32470,19 +33243,19 @@ __export(targets_exports, {
32470
33243
  });
32471
33244
  import {
32472
33245
  chmodSync,
32473
- existsSync as existsSync18,
32474
- mkdirSync as mkdirSync10,
32475
- readFileSync as readFileSync18,
32476
- writeFileSync as writeFileSync12
33246
+ existsSync as existsSync21,
33247
+ mkdirSync as mkdirSync13,
33248
+ readFileSync as readFileSync20,
33249
+ writeFileSync as writeFileSync15
32477
33250
  } from "node:fs";
32478
- import { dirname as dirname2, join as join13 } from "node:path";
32479
- import { homedir as homedir6 } from "node:os";
33251
+ import { dirname as dirname4, join as join16 } from "node:path";
33252
+ import { homedir as homedir7 } from "node:os";
32480
33253
  import { spawn as spawn5 } from "node:child_process";
32481
33254
  function getSshTargetsPath() {
32482
- return join13(homedir6(), ".zelari-code", "ssh-targets.json");
33255
+ return join16(homedir7(), ".zelari-code", "ssh-targets.json");
32483
33256
  }
32484
33257
  function getSshSecretsPath() {
32485
- return join13(homedir6(), ".zelari-code", "ssh-secrets.json");
33258
+ return join16(homedir7(), ".zelari-code", "ssh-secrets.json");
32486
33259
  }
32487
33260
  function normalizeAuth(auth) {
32488
33261
  if (auth === "keyPath") return "keyPath";
@@ -32491,17 +33264,17 @@ function normalizeAuth(auth) {
32491
33264
  }
32492
33265
  function readSecrets() {
32493
33266
  const path53 = getSshSecretsPath();
32494
- if (!existsSync18(path53)) return {};
33267
+ if (!existsSync21(path53)) return {};
32495
33268
  try {
32496
- return JSON.parse(readFileSync18(path53, "utf8"));
33269
+ return JSON.parse(readFileSync20(path53, "utf8"));
32497
33270
  } catch {
32498
33271
  return {};
32499
33272
  }
32500
33273
  }
32501
33274
  function writeSecrets(data) {
32502
33275
  const path53 = getSshSecretsPath();
32503
- mkdirSync10(dirname2(path53), { recursive: true });
32504
- writeFileSync12(path53, `${JSON.stringify(data, null, 2)}
33276
+ mkdirSync13(dirname4(path53), { recursive: true });
33277
+ writeFileSync15(path53, `${JSON.stringify(data, null, 2)}
32505
33278
  `, "utf8");
32506
33279
  try {
32507
33280
  chmodSync(path53, 384);
@@ -32534,9 +33307,9 @@ function deleteSshPassword(id) {
32534
33307
  }
32535
33308
  function readStore2() {
32536
33309
  const path53 = getSshTargetsPath();
32537
- if (!existsSync18(path53)) return [];
33310
+ if (!existsSync21(path53)) return [];
32538
33311
  try {
32539
- const parsed = JSON.parse(readFileSync18(path53, "utf8"));
33312
+ const parsed = JSON.parse(readFileSync20(path53, "utf8"));
32540
33313
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
32541
33314
  return list.filter(
32542
33315
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -32552,9 +33325,9 @@ function readStore2() {
32552
33325
  }
32553
33326
  function writeStore2(targets) {
32554
33327
  const path53 = getSshTargetsPath();
32555
- mkdirSync10(dirname2(path53), { recursive: true });
33328
+ mkdirSync13(dirname4(path53), { recursive: true });
32556
33329
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
32557
- writeFileSync12(
33330
+ writeFileSync15(
32558
33331
  path53,
32559
33332
  `${JSON.stringify({ targets: clean }, null, 2)}
32560
33333
  `,
@@ -32655,17 +33428,17 @@ function buildSshBaseArgs(target) {
32655
33428
  return args;
32656
33429
  }
32657
33430
  function ensureAskpassHelper() {
32658
- const dir = join13(homedir6(), ".zelari-code", "ssh-helpers");
32659
- mkdirSync10(dir, { recursive: true });
32660
- const cjs = join13(dir, "askpass.cjs");
32661
- writeFileSync12(
33431
+ const dir = join16(homedir7(), ".zelari-code", "ssh-helpers");
33432
+ mkdirSync13(dir, { recursive: true });
33433
+ const cjs = join16(dir, "askpass.cjs");
33434
+ writeFileSync15(
32662
33435
  cjs,
32663
33436
  "process.stdout.write(process.env.ZELARI_SSH_ASKPASS_PASS || '');\n",
32664
33437
  "utf8"
32665
33438
  );
32666
33439
  if (process.platform === "win32") {
32667
- const cmd = join13(dir, "askpass.cmd");
32668
- writeFileSync12(
33440
+ const cmd = join16(dir, "askpass.cmd");
33441
+ writeFileSync15(
32669
33442
  cmd,
32670
33443
  `@echo off\r
32671
33444
  node "%~dp0askpass.cjs"\r
@@ -32674,8 +33447,8 @@ node "%~dp0askpass.cjs"\r
32674
33447
  );
32675
33448
  return cmd;
32676
33449
  }
32677
- const sh = join13(dir, "askpass.sh");
32678
- writeFileSync12(
33450
+ const sh = join16(dir, "askpass.sh");
33451
+ writeFileSync15(
32679
33452
  sh,
32680
33453
  `#!/bin/sh
32681
33454
  exec node "$(dirname "$0")/askpass.cjs"
@@ -32748,9 +33521,9 @@ function readSshPublicKey(keyOrPubPath) {
32748
33521
  if (!raw) return { ok: false, error: "Empty path" };
32749
33522
  const candidates = raw.endsWith(".pub") ? [raw] : [`${raw}.pub`, raw];
32750
33523
  for (const p3 of candidates) {
32751
- if (!existsSync18(p3)) continue;
33524
+ if (!existsSync21(p3)) continue;
32752
33525
  try {
32753
- const content = readFileSync18(p3, "utf8").trim();
33526
+ const content = readFileSync20(p3, "utf8").trim();
32754
33527
  if (!content) continue;
32755
33528
  if (/BEGIN .*PRIVATE KEY/i.test(content)) {
32756
33529
  return {
@@ -33320,11 +34093,11 @@ __export(folderTrust_exports, {
33320
34093
  trustFolder: () => trustFolder,
33321
34094
  untrustFolder: () => untrustFolder
33322
34095
  });
33323
- import { homedir as homedir7 } from "node:os";
33324
- import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "node:fs";
34096
+ import { homedir as homedir8 } from "node:os";
34097
+ import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "node:fs";
33325
34098
  import path28 from "node:path";
33326
34099
  function trustStorePath() {
33327
- return _overrideStorePath ?? path28.join(homedir7(), ".zelari-code", "trust.json");
34100
+ return _overrideStorePath ?? path28.join(homedir8(), ".zelari-code", "trust.json");
33328
34101
  }
33329
34102
  function normalize(p3) {
33330
34103
  const resolved = path28.resolve(p3);
@@ -33332,7 +34105,7 @@ function normalize(p3) {
33332
34105
  }
33333
34106
  function readStore3() {
33334
34107
  try {
33335
- const raw = readFileSync19(trustStorePath(), "utf8");
34108
+ const raw = readFileSync21(trustStorePath(), "utf8");
33336
34109
  const parsed = JSON.parse(raw);
33337
34110
  if (parsed && Array.isArray(parsed.folders)) return parsed;
33338
34111
  return DEFAULT_STORE;
@@ -33343,8 +34116,8 @@ function readStore3() {
33343
34116
  function writeStore3(store4) {
33344
34117
  const p3 = trustStorePath();
33345
34118
  try {
33346
- mkdirSync11(path28.dirname(p3), { recursive: true });
33347
- writeFileSync13(p3, JSON.stringify(store4, null, 2), "utf8");
34119
+ mkdirSync14(path28.dirname(p3), { recursive: true });
34120
+ writeFileSync16(p3, JSON.stringify(store4, null, 2), "utf8");
33348
34121
  } catch (err) {
33349
34122
  throw new Error(
33350
34123
  `failed to persist trust store ${p3}: ${err instanceof Error ? err.message : String(err)}`
@@ -33399,7 +34172,7 @@ function getTrustStorePath() {
33399
34172
  return trustStorePath();
33400
34173
  }
33401
34174
  function hasTrustStore() {
33402
- return existsSync19(trustStorePath());
34175
+ return existsSync22(trustStorePath());
33403
34176
  }
33404
34177
  function _setTrustStorePathForTests(p3) {
33405
34178
  _overrideStorePath = p3;
@@ -33414,21 +34187,21 @@ var init_folderTrust = __esm({
33414
34187
  });
33415
34188
 
33416
34189
  // src/cli/safety/lifecycleHooks.ts
33417
- import { homedir as homedir8 } from "node:os";
33418
- import { join as join14 } from "node:path";
33419
- import { readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
34190
+ import { homedir as homedir9 } from "node:os";
34191
+ import { join as join17 } from "node:path";
34192
+ import { readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
33420
34193
  function globalHooksDir() {
33421
- return join14(homedir8(), ".zelari-code", "hooks");
34194
+ return join17(homedir9(), ".zelari-code", "hooks");
33422
34195
  }
33423
34196
  function projectHooksDir(projectRoot) {
33424
- return join14(projectRoot, ".zelari", "hooks");
34197
+ return join17(projectRoot, ".zelari", "hooks");
33425
34198
  }
33426
34199
  function fingerprintHookDirs(dirs) {
33427
34200
  const parts = [];
33428
34201
  for (const dir of dirs) {
33429
34202
  let names;
33430
34203
  try {
33431
- names = readdirSync4(dir).filter((f) => f.endsWith(".json")).sort();
34204
+ names = readdirSync5(dir).filter((f) => f.endsWith(".json")).sort();
33432
34205
  } catch {
33433
34206
  parts.push(`${dir}:missing`);
33434
34207
  continue;
@@ -33442,7 +34215,7 @@ function fingerprintHookDirs(dirs) {
33442
34215
  continue;
33443
34216
  }
33444
34217
  for (const name of names) {
33445
- const full = join14(dir, name);
34218
+ const full = join17(dir, name);
33446
34219
  try {
33447
34220
  const st = statSync3(full);
33448
34221
  parts.push(`${full}:${st.mtimeMs}:${st.size}`);
@@ -33483,7 +34256,7 @@ var init_lifecycleHooks = __esm({
33483
34256
  });
33484
34257
 
33485
34258
  // src/cli/toolResultCache.ts
33486
- import { createHash as createHash5 } from "node:crypto";
34259
+ import { createHash as createHash6 } from "node:crypto";
33487
34260
  import { promises as fs14 } from "node:fs";
33488
34261
  import path29 from "node:path";
33489
34262
  function isToolCacheEnabled() {
@@ -33496,7 +34269,7 @@ function resolveToolCacheTtlMs() {
33496
34269
  return Number.isFinite(n) && n >= 0 ? n : TOOL_CACHE_DEFAULT_TTL_MS;
33497
34270
  }
33498
34271
  function hashKey(parts) {
33499
- return createHash5("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
34272
+ return createHash6("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
33500
34273
  }
33501
34274
  function resultBytes(result) {
33502
34275
  try {
@@ -33786,6 +34559,11 @@ function createBuiltinToolRegistry(options = {}) {
33786
34559
  const todoRead = enableTodos ? withPerm(createTodoReadTool()) : null;
33787
34560
  if (todoWrite) registry4.register(todoWrite);
33788
34561
  if (todoRead) registry4.register(todoRead);
34562
+ const enablePlanTasks = options.enablePlanTasks !== false && options.readOnly !== true && (profile === "full" || options.planMode === true) && profile !== "explore" && profile !== "verify" && profile !== "general";
34563
+ const planTaskToolsWrapped = (enablePlanTasks ? createPlanTaskTools({ projectRoot: root, onTaskEvent: options.onTaskEvent }) : []).map((t) => withPerm(t));
34564
+ for (const t of planTaskToolsWrapped) {
34565
+ registry4.register(t);
34566
+ }
33789
34567
  const summary = [
33790
34568
  safeReadFile,
33791
34569
  safeGrepContent,
@@ -33798,7 +34576,8 @@ function createBuiltinToolRegistry(options = {}) {
33798
34576
  ...askUserTool ? [askUserTool] : [],
33799
34577
  ...skillTool ? [skillTool] : [],
33800
34578
  ...todoWrite ? [todoWrite] : [],
33801
- ...todoRead ? [todoRead] : []
34579
+ ...todoRead ? [todoRead] : [],
34580
+ ...planTaskToolsWrapped.length > 0 ? planTaskToolsWrapped : []
33802
34581
  ];
33803
34582
  const tools = summary.map((t) => ({
33804
34583
  name: t.name,
@@ -34147,6 +34926,7 @@ var init_toolRegistry = __esm({
34147
34926
  init_askUser();
34148
34927
  init_skillTool();
34149
34928
  init_todoTools();
34929
+ init_planTaskTools();
34150
34930
  init_tools2();
34151
34931
  init_manager();
34152
34932
  init_tools3();
@@ -34177,7 +34957,7 @@ var init_toolRegistry = __esm({
34177
34957
  });
34178
34958
 
34179
34959
  // src/cli/state/fileStateStore.ts
34180
- import { createHash as createHash6, randomUUID as randomUUID2 } from "node:crypto";
34960
+ import { createHash as createHash7, randomUUID as randomUUID2 } from "node:crypto";
34181
34961
  import { promises as fs15 } from "node:fs";
34182
34962
  import * as path30 from "node:path";
34183
34963
  function shortId() {
@@ -34229,7 +35009,7 @@ async function getStateStore(projectRoot, env = process.env) {
34229
35009
  }
34230
35010
  }
34231
35011
  function hashStablePrompt(stable) {
34232
- return createHash6("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
35012
+ return createHash7("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
34233
35013
  }
34234
35014
  var DEFAULT_MATERIALIZE_CHARS, FileDurableStateStore, NoopDurableStateStore;
34235
35015
  var init_fileStateStore = __esm({
@@ -34942,8 +35722,8 @@ __export(conversationContext_exports, {
34942
35722
  setHistory: () => setHistory,
34943
35723
  setLastClarification: () => setLastClarification
34944
35724
  });
34945
- import { existsSync as existsSync20 } from "node:fs";
34946
- import { join as join16 } from "node:path";
35725
+ import { existsSync as existsSync23 } from "node:fs";
35726
+ import { join as join19 } from "node:path";
34947
35727
  function getHistory() {
34948
35728
  return history;
34949
35729
  }
@@ -34951,7 +35731,7 @@ function setHistory(messages) {
34951
35731
  history = [...messages];
34952
35732
  }
34953
35733
  function compactInPlace(cwd = process.cwd()) {
34954
- const durableStatePresent = existsSync20(join16(cwd, ".zelari", "state", "HEAD.json"));
35734
+ const durableStatePresent = existsSync23(join19(cwd, ".zelari", "state", "HEAD.json"));
34955
35735
  history = compactHistory(history, { durableStatePresent });
34956
35736
  }
34957
35737
  function appendMessages(msgs) {
@@ -35466,14 +36246,14 @@ var init_claudeProvider = __esm({
35466
36246
  });
35467
36247
 
35468
36248
  // src/cli/workspace/projectInstructions.ts
35469
- import { existsSync as existsSync21, readFileSync as readFileSync20 } from "node:fs";
35470
- import { join as join17 } from "node:path";
36249
+ import { existsSync as existsSync24, readFileSync as readFileSync22 } from "node:fs";
36250
+ import { join as join20 } from "node:path";
35471
36251
  function loadProjectInstructions(projectRoot = process.cwd(), maxChars = MAX_CHARS) {
35472
36252
  for (const name of CANDIDATES) {
35473
- const full = join17(projectRoot, name);
35474
- if (!existsSync21(full)) continue;
36253
+ const full = join20(projectRoot, name);
36254
+ if (!existsSync24(full)) continue;
35475
36255
  try {
35476
- let raw = readFileSync20(full, "utf8");
36256
+ let raw = readFileSync22(full, "utf8");
35477
36257
  raw = raw.replace(/\r\n/g, "\n").trim();
35478
36258
  if (!raw) continue;
35479
36259
  if (raw.length <= maxChars) {
@@ -35508,75 +36288,6 @@ var init_projectInstructions = __esm({
35508
36288
  }
35509
36289
  });
35510
36290
 
35511
- // src/cli/workspace/paths.ts
35512
- import {
35513
- mkdirSync as mkdirSync12,
35514
- writeFileSync as writeFileSync14,
35515
- existsSync as existsSync22,
35516
- accessSync,
35517
- constants,
35518
- realpathSync
35519
- } from "node:fs";
35520
- import { join as join18, basename } from "node:path";
35521
- import { homedir as homedir9 } from "node:os";
35522
- import { createHash as createHash7 } from "node:crypto";
35523
- function resolveWorkspaceRoot(projectRoot = process.cwd()) {
35524
- const candidates = [
35525
- join18(projectRoot, ".zelari"),
35526
- join18(homedir9(), ".zelari-code", "workspace", hashProject(projectRoot))
35527
- ];
35528
- for (const candidate of candidates) {
35529
- if (isWritableDir(projectRoot) || candidate !== candidates[0]) {
35530
- ensureWorkspaceDir(candidate);
35531
- return candidate;
35532
- }
35533
- }
35534
- ensureWorkspaceDir(candidates[0]);
35535
- return candidates[0];
35536
- }
35537
- function hashProject(projectPath) {
35538
- return createHash7("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
35539
- }
35540
- function isWritableDir(dir) {
35541
- try {
35542
- if (!existsSync22(dir)) return false;
35543
- accessSync(dir, constants.W_OK);
35544
- return true;
35545
- } catch {
35546
- return false;
35547
- }
35548
- }
35549
- function ensureWorkspaceDir(workspaceDir) {
35550
- mkdirSync12(workspaceDir, { recursive: true });
35551
- if (workspaceDir.endsWith("/.zelari") && existsSync22(join18(workspaceDir, "..", ".git"))) {
35552
- const gitignorePath = join18(workspaceDir, ".gitignore");
35553
- if (!existsSync22(gitignorePath)) {
35554
- writeFileSync14(gitignorePath, "*\n!.gitignore\n");
35555
- }
35556
- }
35557
- }
35558
- function workspaceFile(rootDir, kind) {
35559
- switch (kind) {
35560
- case "plan":
35561
- return join18(rootDir, "plan.md");
35562
- case "risks":
35563
- return join18(rootDir, "risks.md");
35564
- case "index":
35565
- return join18(rootDir, "workspace.json");
35566
- }
35567
- }
35568
- function workspaceArtifact(rootDir, subdir, slug) {
35569
- return join18(rootDir, subdir, `${slug}.md`);
35570
- }
35571
- function projectName(projectRoot = process.cwd()) {
35572
- return basename(realpathSync(projectRoot));
35573
- }
35574
- var init_paths2 = __esm({
35575
- "src/cli/workspace/paths.ts"() {
35576
- "use strict";
35577
- }
35578
- });
35579
-
35580
36291
  // src/cli/workspace/workspaceSummary.ts
35581
36292
  var workspaceSummary_exports = {};
35582
36293
  __export(workspaceSummary_exports, {
@@ -35586,8 +36297,8 @@ __export(workspaceSummary_exports, {
35586
36297
  buildWorkspaceSummary: () => buildWorkspaceSummary,
35587
36298
  buildZelariReadHint: () => buildZelariReadHint
35588
36299
  });
35589
- import { existsSync as existsSync23, readFileSync as readFileSync21, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
35590
- import { join as join19, relative } from "node:path";
36300
+ import { existsSync as existsSync25, readFileSync as readFileSync23, readdirSync as readdirSync6, statSync as statSync4 } from "node:fs";
36301
+ import { join as join21, relative } from "node:path";
35591
36302
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
35592
36303
  const { maxEntries = 30, maxChars = 3500, maxDeps = 24, maxScripts = 16 } = options;
35593
36304
  const name = safeProjectName(projectRoot);
@@ -35620,11 +36331,11 @@ function formatTaskLine(t) {
35620
36331
  }
35621
36332
  function buildPlanSummary(projectRoot = process.cwd(), options) {
35622
36333
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
35623
- const planPath = join19(zelariRoot, "plan.json");
35624
- if (!existsSync23(planPath)) return null;
36334
+ const planPath = join21(zelariRoot, "plan.json");
36335
+ if (!existsSync25(planPath)) return null;
35625
36336
  let plan;
35626
36337
  try {
35627
- plan = JSON.parse(readFileSync21(planPath, "utf8"));
36338
+ plan = JSON.parse(readFileSync23(planPath, "utf8"));
35628
36339
  } catch {
35629
36340
  return null;
35630
36341
  }
@@ -35638,7 +36349,10 @@ function buildPlanSummary(projectRoot = process.cwd(), options) {
35638
36349
  "# Plan ops (DRAFT \u2014 .zelari/plan.json)",
35639
36350
  "_This is operational task list from design, not product law. Ground every change in the real source tree._"
35640
36351
  ];
35641
- const open = tasks.filter((t) => t.status !== "done");
36352
+ const open = tasks.filter((t) => {
36353
+ const s = t.status;
36354
+ return s !== "done" && s !== "completed" && s !== "cancelled";
36355
+ });
35642
36356
  const done = tasks.length - open.length;
35643
36357
  const userMessage = options?.userMessage?.trim();
35644
36358
  let scopedOpen = open;
@@ -35760,8 +36474,8 @@ function pickNextTask(open) {
35760
36474
  )[0];
35761
36475
  }
35762
36476
  function buildZelariReadHint(projectRoot = process.cwd()) {
35763
- const planPath = join19(resolveWorkspaceRoot(projectRoot), "plan.json");
35764
- if (!existsSync23(planPath)) return "";
36477
+ const planPath = join21(resolveWorkspaceRoot(projectRoot), "plan.json");
36478
+ if (!existsSync25(planPath)) return "";
35765
36479
  return [
35766
36480
  "# Council workspace detected (.zelari/) \u2014 DRAFT vault",
35767
36481
  "`.zelari/plan.json` and `.zelari/docs/` hold **design hypotheses**, not verified product state.",
@@ -35776,10 +36490,10 @@ function safeProjectName(root) {
35776
36490
  }
35777
36491
  }
35778
36492
  function readPackageJson(projectRoot) {
35779
- const p3 = join19(projectRoot, "package.json");
35780
- if (!existsSync23(p3)) return null;
36493
+ const p3 = join21(projectRoot, "package.json");
36494
+ if (!existsSync25(p3)) return null;
35781
36495
  try {
35782
- return JSON.parse(readFileSync21(p3, "utf8"));
36496
+ return JSON.parse(readFileSync23(p3, "utf8"));
35783
36497
  } catch {
35784
36498
  return null;
35785
36499
  }
@@ -35820,7 +36534,7 @@ function readBuildScripts(projectRoot, maxScripts = 16) {
35820
36534
  function listShallow(projectRoot, maxEntries) {
35821
36535
  const out = [];
35822
36536
  try {
35823
- const top = readdirSync5(projectRoot, { withFileTypes: true }).filter(
36537
+ const top = readdirSync6(projectRoot, { withFileTypes: true }).filter(
35824
36538
  (e) => !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist"
35825
36539
  ).sort((a, b) => a.name.localeCompare(b.name));
35826
36540
  let count = 0;
@@ -35829,11 +36543,11 @@ function listShallow(projectRoot, maxEntries) {
35829
36543
  out.push(`\u2026 (+${top.length - count} more)`);
35830
36544
  break;
35831
36545
  }
35832
- const rel2 = relative(projectRoot, join19(projectRoot, entry.name));
36546
+ const rel2 = relative(projectRoot, join21(projectRoot, entry.name));
35833
36547
  if (entry.isDirectory()) {
35834
36548
  let inner = "";
35835
36549
  try {
35836
- const sub = readdirSync5(join19(projectRoot, entry.name), {
36550
+ const sub = readdirSync6(join21(projectRoot, entry.name), {
35837
36551
  withFileTypes: true
35838
36552
  }).filter((e) => !e.name.startsWith(".")).slice(0, 4).map((e) => e.name);
35839
36553
  if (sub.length > 0)
@@ -35881,12 +36595,12 @@ var init_workspaceSummary = __esm({
35881
36595
  });
35882
36596
 
35883
36597
  // src/cli/workspace/buildLessonsSummary.ts
35884
- import { existsSync as existsSync24 } from "node:fs";
35885
- import { join as join20 } from "node:path";
36598
+ import { existsSync as existsSync26 } from "node:fs";
36599
+ import { join as join22 } from "node:path";
35886
36600
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
35887
36601
  if (process.env["ZELARI_LESSONS"] === "0") return null;
35888
36602
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
35889
- if (!existsSync24(join20(zelariRoot, "lessons.jsonl"))) return null;
36603
+ if (!existsSync26(join22(zelariRoot, "lessons.jsonl"))) return null;
35890
36604
  const lessons = recallLessons(zelariRoot, {
35891
36605
  maxLessons: 5,
35892
36606
  maxBytes: 2048,
@@ -35907,8 +36621,8 @@ var composeContext_exports = {};
35907
36621
  __export(composeContext_exports, {
35908
36622
  composeProjectContext: () => composeProjectContext
35909
36623
  });
35910
- import { existsSync as existsSync25, readdirSync as readdirSync6, readFileSync as readFileSync22 } from "node:fs";
35911
- import { join as join21 } from "node:path";
36624
+ import { existsSync as existsSync27, readdirSync as readdirSync7, readFileSync as readFileSync24 } from "node:fs";
36625
+ import { join as join23 } from "node:path";
35912
36626
  function cap2(text, max, label) {
35913
36627
  if (!text || text.length <= max) return { text: text || "", truncated: false };
35914
36628
  return {
@@ -35920,19 +36634,19 @@ function cap2(text, max, label) {
35920
36634
  }
35921
36635
  function buildDesignIndex(projectRoot, maxChars) {
35922
36636
  const root = resolveWorkspaceRoot(projectRoot);
35923
- if (!existsSync25(root)) return "";
36637
+ if (!existsSync27(root)) return "";
35924
36638
  const lines = [
35925
36639
  "# Design vault index (.zelari/) \u2014 HYPOTHESES only",
35926
36640
  "Full design docs are NOT product source of truth. Open with list_files / read_file / searchDocuments if needed."
35927
36641
  ];
35928
- const docsDir = join21(root, "docs");
35929
- if (existsSync25(docsDir)) {
36642
+ const docsDir = join23(root, "docs");
36643
+ if (existsSync27(docsDir)) {
35930
36644
  try {
35931
- const docs = readdirSync6(docsDir).filter((n) => n.endsWith(".md")).slice(0, 12);
36645
+ const docs = readdirSync7(docsDir).filter((n) => n.endsWith(".md")).slice(0, 12);
35932
36646
  if (docs.length > 0) {
35933
36647
  lines.push("", "## docs/ (titles only)");
35934
36648
  for (const d of docs) lines.push(`- .zelari/docs/${d}`);
35935
- if (readdirSync6(docsDir).filter((n) => n.endsWith(".md")).length > 12) {
36649
+ if (readdirSync7(docsDir).filter((n) => n.endsWith(".md")).length > 12) {
35936
36650
  lines.push("- \u2026 (more under .zelari/docs/)");
35937
36651
  }
35938
36652
  }
@@ -35940,14 +36654,14 @@ function buildDesignIndex(projectRoot, maxChars) {
35940
36654
  }
35941
36655
  }
35942
36656
  for (const name of ["risks.md", "plan.json", "nfr-spec.json"]) {
35943
- if (existsSync25(join21(root, name))) {
36657
+ if (existsSync27(join23(root, name))) {
35944
36658
  lines.push(`- .zelari/${name} present`);
35945
36659
  }
35946
36660
  }
35947
- const decisionsDir = join21(root, "decisions");
35948
- if (existsSync25(decisionsDir)) {
36661
+ const decisionsDir = join23(root, "decisions");
36662
+ if (existsSync27(decisionsDir)) {
35949
36663
  try {
35950
- const n = readdirSync6(decisionsDir).filter((f) => f.endsWith(".md")).length;
36664
+ const n = readdirSync7(decisionsDir).filter((f) => f.endsWith(".md")).length;
35951
36665
  if (n > 0) lines.push(`- .zelari/decisions/ (${n} ADR file(s) \u2014 treat proposed as non-binding)`);
35952
36666
  } catch {
35953
36667
  }
@@ -36046,17 +36760,17 @@ function composeProjectContext(input) {
36046
36760
  }
36047
36761
  function readDurableHeadSync(projectRoot) {
36048
36762
  try {
36049
- const headPath = join21(projectRoot, ".zelari", "state", "HEAD.json");
36050
- if (!existsSync25(headPath)) return "";
36051
- const head = JSON.parse(readFileSync22(headPath, "utf8"));
36763
+ const headPath = join23(projectRoot, ".zelari", "state", "HEAD.json");
36764
+ if (!existsSync27(headPath)) return "";
36765
+ const head = JSON.parse(readFileSync24(headPath, "utf8"));
36052
36766
  if (!head?.id) return "";
36053
- const metaPath = join21(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
36054
- if (!existsSync25(metaPath)) return "";
36055
- const meta3 = JSON.parse(readFileSync22(metaPath, "utf8"));
36056
- const discPath = meta3.artifactDir ? join21(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join21(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
36767
+ const metaPath = join23(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
36768
+ if (!existsSync27(metaPath)) return "";
36769
+ const meta3 = JSON.parse(readFileSync24(metaPath, "utf8"));
36770
+ const discPath = meta3.artifactDir ? join23(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join23(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
36057
36771
  let discoveries = [];
36058
- if (existsSync25(discPath)) {
36059
- discoveries = JSON.parse(readFileSync22(discPath, "utf8"));
36772
+ if (existsSync27(discPath)) {
36773
+ discoveries = JSON.parse(readFileSync24(discPath, "utf8"));
36060
36774
  }
36061
36775
  const reusable = discoveries.filter((d) => d.reusable !== false);
36062
36776
  const lines = [
@@ -36089,13 +36803,13 @@ var planDetect_exports = {};
36089
36803
  __export(planDetect_exports, {
36090
36804
  hasWorkspacePlan: () => hasWorkspacePlan
36091
36805
  });
36092
- import { existsSync as existsSync26, readFileSync as readFileSync23 } from "node:fs";
36093
- import { join as join22 } from "node:path";
36806
+ import { existsSync as existsSync28, readFileSync as readFileSync25 } from "node:fs";
36807
+ import { join as join24 } from "node:path";
36094
36808
  function hasWorkspacePlan(projectRoot = process.cwd()) {
36095
- const planPath = join22(resolveWorkspaceRoot(projectRoot), "plan.json");
36096
- if (!existsSync26(planPath)) return false;
36809
+ const planPath = join24(resolveWorkspaceRoot(projectRoot), "plan.json");
36810
+ if (!existsSync28(planPath)) return false;
36097
36811
  try {
36098
- const parsed = JSON.parse(readFileSync23(planPath, "utf8"));
36812
+ const parsed = JSON.parse(readFileSync25(planPath, "utf8"));
36099
36813
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
36100
36814
  } catch {
36101
36815
  return false;
@@ -36144,333 +36858,6 @@ var init_loadDurableContext = __esm({
36144
36858
  }
36145
36859
  });
36146
36860
 
36147
- // src/cli/workspace/storage.ts
36148
- var storage_exports = {};
36149
- __export(storage_exports, {
36150
- Storage: () => Storage,
36151
- parseFrontmatter: () => parseFrontmatter,
36152
- parseYaml: () => parseYaml,
36153
- serializeFrontmatter: () => serializeFrontmatter,
36154
- serializeYaml: () => serializeYaml,
36155
- workspaceMutex: () => workspaceMutex
36156
- });
36157
- import {
36158
- readFileSync as readFileSync24,
36159
- writeFileSync as writeFileSync15,
36160
- existsSync as existsSync27,
36161
- mkdirSync as mkdirSync13,
36162
- readdirSync as readdirSync7,
36163
- renameSync as renameSync2
36164
- } from "node:fs";
36165
- import { dirname as dirname4, join as join23 } from "node:path";
36166
- function parseFrontmatter(md) {
36167
- const m = FRONTMATTER_RE.exec(md);
36168
- if (!m) return { meta: {}, body: md };
36169
- const meta3 = parseYaml(m[1]);
36170
- const body = m[2];
36171
- return { meta: meta3, body };
36172
- }
36173
- function serializeFrontmatter(meta3, body) {
36174
- const yamlStr = serializeYaml(meta3);
36175
- return `---
36176
- ${yamlStr}
36177
- ---
36178
- ${body}`;
36179
- }
36180
- function parseYaml(input) {
36181
- const lines = input.split(/\r?\n/);
36182
- const ctx = { lines, i: 0 };
36183
- return parseNode(ctx, 0);
36184
- }
36185
- function parseNode(ctx, indent) {
36186
- while (ctx.i < ctx.lines.length) {
36187
- const line2 = ctx.lines[ctx.i];
36188
- if (line2.trim() === "" || line2.trim().startsWith("#")) {
36189
- ctx.i++;
36190
- continue;
36191
- }
36192
- break;
36193
- }
36194
- if (ctx.i >= ctx.lines.length) return null;
36195
- const line = ctx.lines[ctx.i];
36196
- const lineIndent = countIndent(line);
36197
- if (/^\s*-\s+/.test(line)) {
36198
- return parseBlockSequence(ctx, indent);
36199
- }
36200
- if (/^\s*\[.*\]\s*$/.test(line)) {
36201
- const flow = line.trim().replace(/^\[/, "").replace(/\]$/, "");
36202
- return parseFlowSequence(flow);
36203
- }
36204
- if (/^\s*\{.*\}\s*$/.test(line)) {
36205
- const flow = line.trim().replace(/^\{/, "").replace(/\}$/, "");
36206
- return parseFlowMap(flow);
36207
- }
36208
- return parseBlockMap(ctx, indent);
36209
- }
36210
- function parseBlockMap(ctx, indent) {
36211
- const out = {};
36212
- while (ctx.i < ctx.lines.length) {
36213
- const line = ctx.lines[ctx.i];
36214
- if (line.trim() === "" || line.trim().startsWith("#")) {
36215
- ctx.i++;
36216
- continue;
36217
- }
36218
- const lineIndent = countIndent(line);
36219
- if (lineIndent < indent) break;
36220
- if (lineIndent > indent) {
36221
- ctx.i++;
36222
- continue;
36223
- }
36224
- const m = /^([^:]+):\s*(.*)$/.exec(line);
36225
- if (!m) {
36226
- ctx.i++;
36227
- continue;
36228
- }
36229
- const key = m[1].trim();
36230
- const valuePart = m[2].trim();
36231
- if (valuePart === "" || valuePart === "|" || valuePart === ">") {
36232
- ctx.i++;
36233
- const nested = parseNode(ctx, indent + 2);
36234
- out[key] = nested;
36235
- } else {
36236
- if (valuePart.startsWith("[")) {
36237
- out[key] = parseFlowSequence(stripFlow(valuePart, "[", "]"));
36238
- } else if (valuePart.startsWith("{")) {
36239
- out[key] = parseFlowMap(stripFlow(valuePart, "{", "}"));
36240
- } else {
36241
- out[key] = parseScalar(valuePart);
36242
- }
36243
- ctx.i++;
36244
- }
36245
- }
36246
- return out;
36247
- }
36248
- function parseBlockSequence(ctx, indent) {
36249
- const out = [];
36250
- while (ctx.i < ctx.lines.length) {
36251
- const line = ctx.lines[ctx.i];
36252
- if (line.trim() === "") {
36253
- ctx.i++;
36254
- continue;
36255
- }
36256
- const lineIndent = countIndent(line);
36257
- if (lineIndent < indent) break;
36258
- if (lineIndent > indent) break;
36259
- const m = /^-\s*(.*)$/.exec(line);
36260
- if (!m) break;
36261
- const rest = m[1];
36262
- if (rest === "") {
36263
- ctx.i++;
36264
- out.push(parseNode(ctx, indent + 2));
36265
- } else if (rest.startsWith("[") || rest.startsWith("{")) {
36266
- let buffer = rest;
36267
- let depthSq = (buffer.match(/\[/g) || []).length - (buffer.match(/\]/g) || []).length;
36268
- let depthCu = (buffer.match(/\{/g) || []).length - (buffer.match(/\}/g) || []).length;
36269
- while ((depthSq > 0 || depthCu > 0) && ctx.i + 1 < ctx.lines.length) {
36270
- ctx.i++;
36271
- const next = ctx.lines[ctx.i].trim();
36272
- buffer += " " + next;
36273
- depthSq = (buffer.match(/\[/g) || []).length - (buffer.match(/\]/g) || []).length;
36274
- depthCu = (buffer.match(/\{/g) || []).length - (buffer.match(/\}/g) || []).length;
36275
- }
36276
- if (buffer.startsWith("[")) {
36277
- out.push(parseFlowSequence(buffer.slice(1).replace(/\]$/, "")));
36278
- } else {
36279
- out.push(parseFlowMap(buffer.slice(1).replace(/\}$/, "")));
36280
- }
36281
- ctx.i++;
36282
- } else if (rest.includes(":")) {
36283
- ctx.i++;
36284
- const mapCtx = { lines: [" ".repeat(indent + 2) + rest, ...ctx.lines.slice(ctx.i)], i: 0 };
36285
- const val = parseBlockMap(mapCtx, indent + 2);
36286
- ctx.i += mapCtx.i - 1;
36287
- out.push(val);
36288
- } else {
36289
- out.push(parseScalar(rest));
36290
- ctx.i++;
36291
- }
36292
- }
36293
- return out;
36294
- }
36295
- function parseFlowSequence(input) {
36296
- const parts = splitFlow(input);
36297
- return parts.map((p3) => {
36298
- const trimmed = p3.trim();
36299
- if (trimmed.startsWith("{")) {
36300
- return parseFlowMap(stripFlow(trimmed, "{", "}"));
36301
- }
36302
- return parseScalar(trimmed);
36303
- });
36304
- }
36305
- function stripFlow(s, open, close) {
36306
- let out = s.trim();
36307
- if (out.startsWith(open)) out = out.slice(1);
36308
- if (out.endsWith(close)) out = out.slice(0, -1);
36309
- return out;
36310
- }
36311
- function parseFlowMap(input) {
36312
- const parts = splitFlow(input);
36313
- const out = {};
36314
- for (const p3 of parts) {
36315
- const colonIdx = p3.indexOf(":");
36316
- if (colonIdx < 0) continue;
36317
- const key = p3.slice(0, colonIdx).trim();
36318
- const value = p3.slice(colonIdx + 1).trim();
36319
- out[key] = parseScalar(value);
36320
- }
36321
- return out;
36322
- }
36323
- function splitFlow(input) {
36324
- const out = [];
36325
- let depthSq = 0, depthCu = 0, depthQu = 0;
36326
- let buffer = "";
36327
- for (let i = 0; i < input.length; i++) {
36328
- const c = input[i];
36329
- if (c === '"' || c === "'") {
36330
- depthQu = depthQu === 0 ? depthQu + 1 : 0;
36331
- buffer += c;
36332
- } else if (depthQu === 0) {
36333
- if (c === "[") depthSq++;
36334
- else if (c === "]") depthSq--;
36335
- else if (c === "{") depthCu++;
36336
- else if (c === "}") depthCu--;
36337
- else if (c === "," && depthSq === 0 && depthCu === 0) {
36338
- out.push(buffer);
36339
- buffer = "";
36340
- continue;
36341
- }
36342
- buffer += c;
36343
- } else {
36344
- buffer += c;
36345
- }
36346
- }
36347
- if (buffer.trim()) out.push(buffer);
36348
- return out;
36349
- }
36350
- function parseScalar(s) {
36351
- if (s === "" || s === "null" || s === "~") return null;
36352
- if (VALID_SCALARS.test(s)) return s.toLowerCase() === "true";
36353
- if (/^-?\d+(\.\d+)?$/.test(s)) return Number(s);
36354
- if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
36355
- return s.slice(1, -1);
36356
- }
36357
- return s;
36358
- }
36359
- function serializeYaml(value, indent = 0) {
36360
- if (value === null || value === void 0) return "";
36361
- if (typeof value === "string") {
36362
- if (/[:#\n\[\]\{\},&*!|>'"%@`]/.test(value)) {
36363
- return JSON.stringify(value);
36364
- }
36365
- return value;
36366
- }
36367
- if (typeof value === "number" || typeof value === "boolean") return String(value);
36368
- if (Array.isArray(value)) {
36369
- if (value.length === 0) return "[]";
36370
- if (value.every((v) => v === null || typeof v !== "object")) {
36371
- return `[${value.map(serializeScalarInline).join(", ")}]`;
36372
- }
36373
- return `[${value.map((v) => "{" + serializeInlineObject(v) + "}").join(", ")}]`;
36374
- }
36375
- if (typeof value === "object") {
36376
- const entries = Object.entries(value).filter(([, v]) => v !== void 0);
36377
- return entries.map(([k, v]) => {
36378
- if (v === null || v === void 0) return `${k}:`;
36379
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
36380
- return `${k}: ${serializeScalarInline(v)}`;
36381
- }
36382
- if (Array.isArray(v)) {
36383
- if (v.length === 0) return `${k}: []`;
36384
- if (v.every((x) => x === null || typeof x !== "object")) {
36385
- return `${k}: [${v.map(serializeScalarInline).join(", ")}]`;
36386
- }
36387
- return `${k}: [${v.map((x) => "{" + serializeInlineObject(x) + "}").join(", ")}]`;
36388
- }
36389
- return `${k}:
36390
- ${serializeYaml(v, indent + 2)}`;
36391
- }).map((line) => `${" ".repeat(indent)}${line}`).join("\n");
36392
- }
36393
- return String(value);
36394
- }
36395
- function serializeScalarInline(v) {
36396
- if (typeof v === "string" && /[:#\n\[\]\{\},&*!|>'"%@`]/.test(v)) return JSON.stringify(v);
36397
- return String(v);
36398
- }
36399
- function serializeInlineObject(v) {
36400
- if (v === null || typeof v !== "object" || Array.isArray(v)) {
36401
- return serializeScalarInline(v);
36402
- }
36403
- const entries = Object.entries(v).filter(([, val]) => val !== void 0);
36404
- return entries.map(([k, val]) => `${k}: ${serializeYaml(val)}`).join(", ");
36405
- }
36406
- function countIndent(line) {
36407
- let i = 0;
36408
- while (i < line.length && line[i] === " ") i++;
36409
- return i;
36410
- }
36411
- var FRONTMATTER_RE, VALID_SCALARS, Storage, KeyedMutex, workspaceMutex;
36412
- var init_storage = __esm({
36413
- "src/cli/workspace/storage.ts"() {
36414
- "use strict";
36415
- FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
36416
- VALID_SCALARS = /^(true|false|null|~)$/i;
36417
- Storage = class {
36418
- /** Read a Markdown file with frontmatter. Throws if not found. */
36419
- read(path53) {
36420
- if (!existsSync27(path53)) {
36421
- throw new Error(`File not found: ${path53}`);
36422
- }
36423
- const md = readFileSync24(path53, "utf8");
36424
- return parseFrontmatter(md);
36425
- }
36426
- /** Read a Markdown file; returns null if not found. */
36427
- readIfExists(path53) {
36428
- if (!existsSync27(path53)) return null;
36429
- return this.read(path53);
36430
- }
36431
- /**
36432
- * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
36433
- * The meta object is serialized as YAML frontmatter; body as Markdown.
36434
- */
36435
- write(path53, meta3, body) {
36436
- mkdirSync13(dirname4(path53), { recursive: true });
36437
- const tmp = path53 + ".tmp-" + process.pid;
36438
- const md = serializeFrontmatter(meta3, body);
36439
- writeFileSync15(tmp, md, "utf8");
36440
- renameSync2(tmp, path53);
36441
- }
36442
- /** List all .md files in a directory (non-recursive). */
36443
- listMarkdown(dir) {
36444
- if (!existsSync27(dir)) return [];
36445
- return readdirSync7(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join23(dir, f));
36446
- }
36447
- };
36448
- KeyedMutex = class {
36449
- chains = /* @__PURE__ */ new Map();
36450
- async run(key, fn) {
36451
- const prev2 = this.chains.get(key) ?? Promise.resolve();
36452
- let release = () => {
36453
- };
36454
- const next = new Promise((resolve3) => {
36455
- release = resolve3;
36456
- });
36457
- const chained = prev2.then(() => next);
36458
- this.chains.set(key, chained);
36459
- await prev2;
36460
- try {
36461
- return await fn();
36462
- } finally {
36463
- release();
36464
- if (this.chains.get(key) === chained) {
36465
- this.chains.delete(key);
36466
- }
36467
- }
36468
- }
36469
- };
36470
- workspaceMutex = new KeyedMutex();
36471
- }
36472
- });
36473
-
36474
36861
  // src/cli/workspace/stubs.ts
36475
36862
  var stubs_exports = {};
36476
36863
  __export(stubs_exports, {
@@ -36481,14 +36868,14 @@ __export(stubs_exports, {
36481
36868
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
36482
36869
  });
36483
36870
  import {
36484
- existsSync as existsSync28,
36871
+ existsSync as existsSync29,
36485
36872
  readdirSync as readdirSync8,
36486
- writeFileSync as writeFileSync16,
36487
- readFileSync as readFileSync25,
36488
- mkdirSync as mkdirSync14,
36489
- renameSync as renameSync3
36873
+ writeFileSync as writeFileSync17,
36874
+ readFileSync as readFileSync26,
36875
+ mkdirSync as mkdirSync15,
36876
+ renameSync as renameSync4
36490
36877
  } from "node:fs";
36491
- import { join as join24, basename as basename2, dirname as dirname5, relative as relative2 } from "node:path";
36878
+ import { join as join25, basename as basename2, dirname as dirname6, relative as relative2 } from "node:path";
36492
36879
  function createWorkspaceContext(projectRoot = process.cwd()) {
36493
36880
  const rootDir = resolveWorkspaceRoot(projectRoot);
36494
36881
  return {
@@ -36498,19 +36885,21 @@ function createWorkspaceContext(projectRoot = process.cwd()) {
36498
36885
  };
36499
36886
  }
36500
36887
  function planJsonPath(ctx) {
36501
- return join24(ctx.rootDir, "plan.json");
36888
+ return join25(ctx.rootDir, "plan.json");
36502
36889
  }
36503
36890
  function readPlan(ctx) {
36504
36891
  const jsonPath = planJsonPath(ctx);
36505
- if (existsSync28(jsonPath)) {
36892
+ if (existsSync29(jsonPath)) {
36506
36893
  try {
36507
36894
  const parsed = JSON.parse(
36508
- readFileSync25(jsonPath, "utf8")
36895
+ readFileSync26(jsonPath, "utf8")
36509
36896
  );
36897
+ const { phases, tasks, milestones, ...root } = parsed;
36510
36898
  return {
36511
- phases: Array.isArray(parsed.phases) ? parsed.phases : [],
36512
- tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
36513
- milestones: Array.isArray(parsed.milestones) ? parsed.milestones : []
36899
+ phases: Array.isArray(phases) ? phases : [],
36900
+ tasks: Array.isArray(tasks) ? tasks : [],
36901
+ milestones: Array.isArray(milestones) ? milestones : [],
36902
+ root
36514
36903
  };
36515
36904
  } catch {
36516
36905
  }
@@ -36527,10 +36916,23 @@ function readPlan(ctx) {
36527
36916
  }
36528
36917
  function writePlan(ctx, summary) {
36529
36918
  const jsonPath = planJsonPath(ctx);
36530
- mkdirSync14(dirname5(jsonPath), { recursive: true });
36919
+ mkdirSync15(dirname6(jsonPath), { recursive: true });
36531
36920
  const tmp = jsonPath + ".tmp-" + process.pid;
36532
- writeFileSync16(tmp, JSON.stringify(summary, null, 2), "utf8");
36533
- renameSync3(tmp, jsonPath);
36921
+ writeFileSync17(
36922
+ tmp,
36923
+ JSON.stringify(
36924
+ {
36925
+ ...summary.root ?? {},
36926
+ phases: summary.phases,
36927
+ tasks: summary.tasks,
36928
+ milestones: summary.milestones
36929
+ },
36930
+ null,
36931
+ 2
36932
+ ),
36933
+ "utf8"
36934
+ );
36935
+ renameSync4(tmp, jsonPath);
36534
36936
  const mdPath = workspaceFile(ctx.rootDir, "plan");
36535
36937
  const summaryMeta = {
36536
36938
  kind: "plan-summary",
@@ -36596,8 +36998,8 @@ function renderPlanBody(summary) {
36596
36998
  return lines.join("\n");
36597
36999
  }
36598
37000
  function nextAdrId(ctx) {
36599
- const decisionsDir = join24(ctx.rootDir, "decisions");
36600
- if (!existsSync28(decisionsDir)) return "001";
37001
+ const decisionsDir = join25(ctx.rootDir, "decisions");
37002
+ if (!existsSync29(decisionsDir)) return "001";
36601
37003
  const existing = readdirSync8(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
36602
37004
  const max = existing.length === 0 ? 0 : Math.max(...existing);
36603
37005
  return String(max + 1).padStart(3, "0");
@@ -36639,7 +37041,7 @@ function addTaskRecord(ctx, summary, phaseId, t, options) {
36639
37041
  status: "pending",
36640
37042
  priority: t.priority
36641
37043
  });
36642
- const taskPath = join24(ctx.rootDir, "plan-tasks", `${id}.md`);
37044
+ const taskPath = join25(ctx.rootDir, "plan-tasks", `${id}.md`);
36643
37045
  const meta3 = {
36644
37046
  kind: "task",
36645
37047
  id,
@@ -36681,7 +37083,7 @@ function addMilestoneRecord(ctx, summary, input) {
36681
37083
  dueDate: input.dueDate,
36682
37084
  targetVersion: version2
36683
37085
  });
36684
- const path53 = join24(ctx.rootDir, "milestones", `${id}.md`);
37086
+ const path53 = join25(ctx.rootDir, "milestones", `${id}.md`);
36685
37087
  const meta3 = {
36686
37088
  kind: "milestone",
36687
37089
  id,
@@ -36979,8 +37381,8 @@ function createNfrSpecStub(ctx) {
36979
37381
  },
36980
37382
  planFeatureKeywords: Array.isArray(args["planFeatureKeywords"]) ? args["planFeatureKeywords"] : void 0
36981
37383
  };
36982
- const outPath = join24(ctx.rootDir, "nfr-spec.json");
36983
- writeFileSync16(outPath, JSON.stringify(spec, null, 2), "utf8");
37384
+ const outPath = join25(ctx.rootDir, "nfr-spec.json");
37385
+ writeFileSync17(outPath, JSON.stringify(spec, null, 2), "utf8");
36984
37386
  return `NFR spec written to nfr-spec.json (${targets.length} target(s)).`;
36985
37387
  });
36986
37388
  }
@@ -37043,18 +37445,18 @@ function searchDocumentsStub(ctx) {
37043
37445
  (w) => w.length >= 3 && w !== "or" && w !== "and" && w !== "the"
37044
37446
  );
37045
37447
  const files = [
37046
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "decisions")),
37047
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "docs")),
37048
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "reviews")),
37049
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "plan-tasks")),
37050
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "milestones")),
37448
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "decisions")),
37449
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "docs")),
37450
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "reviews")),
37451
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "plan-tasks")),
37452
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "milestones")),
37051
37453
  workspaceFile(ctx.rootDir, "plan"),
37052
37454
  workspaceFile(ctx.rootDir, "risks")
37053
37455
  ];
37054
37456
  const results = [];
37055
37457
  for (const file2 of files) {
37056
- if (!existsSync28(file2)) continue;
37057
- const raw = readFileSync25(file2, "utf8");
37458
+ if (!existsSync29(file2)) continue;
37459
+ const raw = readFileSync26(file2, "utf8");
37058
37460
  const content = raw.toLowerCase();
37059
37461
  let idx = -1;
37060
37462
  let matchLen = 0;
@@ -37105,9 +37507,9 @@ function linkDocumentsStub(ctx) {
37105
37507
  const toId = args["toId"] ?? args["targetId"] ?? args["targetPathOrTitle"];
37106
37508
  if (!fromId || !toId) return "linkDocuments requires fromId and toId.";
37107
37509
  const allFiles = [
37108
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "decisions")),
37109
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "docs")),
37110
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "reviews"))
37510
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "decisions")),
37511
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "docs")),
37512
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "reviews"))
37111
37513
  ];
37112
37514
  const source = allFiles.find((f) => {
37113
37515
  try {
@@ -37138,9 +37540,9 @@ function getDocumentBacklinksStub(ctx) {
37138
37540
  const targetId = args["targetId"] ?? args["id"];
37139
37541
  if (!targetId) return "getDocumentBacklinks requires targetId.";
37140
37542
  const allFiles = [
37141
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "decisions")),
37142
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "docs")),
37143
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "reviews"))
37543
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "decisions")),
37544
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "docs")),
37545
+ ...ctx.storage.listMarkdown(join25(ctx.rootDir, "reviews"))
37144
37546
  ];
37145
37547
  const backlinks = [];
37146
37548
  for (const file2 of allFiles) {
@@ -37235,7 +37637,7 @@ __export(updater_exports, {
37235
37637
  });
37236
37638
  import { createRequire as createRequire2 } from "node:module";
37237
37639
  import { spawn as spawn8 } from "node:child_process";
37238
- import { existsSync as existsSync29 } from "node:fs";
37640
+ import { existsSync as existsSync30 } from "node:fs";
37239
37641
  import path31 from "node:path";
37240
37642
  import { fileURLToPath } from "node:url";
37241
37643
  function resolveBundledNpmCli(execPath = process.execPath) {
@@ -37248,7 +37650,7 @@ function resolveBundledNpmCli(execPath = process.execPath) {
37248
37650
  ];
37249
37651
  for (const candidate of candidates) {
37250
37652
  try {
37251
- if (existsSync29(candidate)) return candidate;
37653
+ if (existsSync30(candidate)) return candidate;
37252
37654
  } catch {
37253
37655
  }
37254
37656
  }
@@ -37548,23 +37950,23 @@ var init_mcpClient = __esm({
37548
37950
 
37549
37951
  // src/cli/mcp/mcpConfigIo.ts
37550
37952
  import {
37551
- existsSync as existsSync30,
37552
- mkdirSync as mkdirSync15,
37553
- readFileSync as readFileSync26,
37554
- writeFileSync as writeFileSync17
37953
+ existsSync as existsSync31,
37954
+ mkdirSync as mkdirSync16,
37955
+ readFileSync as readFileSync27,
37956
+ writeFileSync as writeFileSync18
37555
37957
  } from "node:fs";
37556
- import { dirname as dirname6, join as join25 } from "node:path";
37958
+ import { dirname as dirname7, join as join26 } from "node:path";
37557
37959
  import { homedir as homedir10 } from "node:os";
37558
37960
  function getUserMcpPath() {
37559
- return join25(homedir10(), ".zelari-code", "mcp.json");
37961
+ return join26(homedir10(), ".zelari-code", "mcp.json");
37560
37962
  }
37561
37963
  function getProjectMcpPath(projectRoot) {
37562
- return join25(projectRoot, ".zelari", "mcp.json");
37964
+ return join26(projectRoot, ".zelari", "mcp.json");
37563
37965
  }
37564
37966
  function readFile2(path53) {
37565
- if (!existsSync30(path53)) return {};
37967
+ if (!existsSync31(path53)) return {};
37566
37968
  try {
37567
- const parsed = JSON.parse(readFileSync26(path53, "utf8"));
37969
+ const parsed = JSON.parse(readFileSync27(path53, "utf8"));
37568
37970
  const out = {};
37569
37971
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
37570
37972
  if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
@@ -37581,9 +37983,9 @@ function readFile2(path53) {
37581
37983
  }
37582
37984
  }
37583
37985
  function writeFile(path53, servers) {
37584
- mkdirSync15(dirname6(path53), { recursive: true });
37986
+ mkdirSync16(dirname7(path53), { recursive: true });
37585
37987
  const body = { mcpServers: servers };
37586
- writeFileSync17(path53, `${JSON.stringify(body, null, 2)}
37988
+ writeFileSync18(path53, `${JSON.stringify(body, null, 2)}
37587
37989
  `, "utf8");
37588
37990
  }
37589
37991
  function listMcpServers(projectRoot) {
@@ -37792,22 +38194,22 @@ __export(mcpManager_exports, {
37792
38194
  readMcpConfig: () => readMcpConfig,
37793
38195
  registerMcpTools: () => registerMcpTools
37794
38196
  });
37795
- import { existsSync as existsSync31, readFileSync as readFileSync27 } from "node:fs";
37796
- import { join as join26 } from "node:path";
38197
+ import { existsSync as existsSync32, readFileSync as readFileSync28 } from "node:fs";
38198
+ import { join as join27 } from "node:path";
37797
38199
  import { homedir as homedir11 } from "node:os";
37798
38200
  function readMcpConfig(projectRoot = process.cwd(), opts) {
37799
38201
  const merged = {};
37800
38202
  const paths = [];
37801
38203
  if (process.env["ZELARI_MCP_USER"] !== "0") {
37802
- paths.push(join26(homedir11(), ".zelari-code", "mcp.json"));
38204
+ paths.push(join27(homedir11(), ".zelari-code", "mcp.json"));
37803
38205
  }
37804
38206
  if (!opts?.skipProjectMcp) {
37805
- paths.push(join26(projectRoot, ".zelari", "mcp.json"));
38207
+ paths.push(join27(projectRoot, ".zelari", "mcp.json"));
37806
38208
  }
37807
38209
  for (const p3 of paths) {
37808
- if (!existsSync31(p3)) continue;
38210
+ if (!existsSync32(p3)) continue;
37809
38211
  try {
37810
- const parsed = JSON.parse(readFileSync27(p3, "utf8"));
38212
+ const parsed = JSON.parse(readFileSync28(p3, "utf8"));
37811
38213
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
37812
38214
  if (!cfg || typeof cfg.command !== "string" || cfg.command.length === 0) continue;
37813
38215
  merged[name] = cfg;
@@ -37821,7 +38223,7 @@ async function ensureLoaded(projectRoot) {
37821
38223
  if (state2.loaded) return;
37822
38224
  state2.loaded = true;
37823
38225
  const trusted = isFolderTrusted(projectRoot);
37824
- if (!trusted && existsSync31(join26(projectRoot, ".zelari", "mcp.json"))) {
38226
+ if (!trusted && existsSync32(join27(projectRoot, ".zelari", "mcp.json"))) {
37825
38227
  state2.warnings.push(
37826
38228
  "[mcp] project .zelari/mcp.json ignored \u2014 folder not trusted (run /trust or `zelari-code --trust` to enable project MCP)"
37827
38229
  );
@@ -38046,13 +38448,13 @@ __export(agentsMd_exports, {
38046
38448
  serializeAgentsMd: () => serializeAgentsMd,
38047
38449
  updateAgentsMd: () => updateAgentsMd
38048
38450
  });
38049
- import { existsSync as existsSync32, readFileSync as readFileSync28, writeFileSync as writeFileSync18 } from "node:fs";
38451
+ import { existsSync as existsSync33, readFileSync as readFileSync29, writeFileSync as writeFileSync19 } from "node:fs";
38050
38452
  import { createHash as createHash8 } from "node:crypto";
38051
- import { join as join27 } from "node:path";
38453
+ import { join as join28 } from "node:path";
38052
38454
  import { readFile as readFile3 } from "node:fs/promises";
38053
38455
  async function readPackageJson2(projectRoot) {
38054
- const path53 = join27(projectRoot, "package.json");
38055
- if (!existsSync32(path53)) return null;
38456
+ const path53 = join28(projectRoot, "package.json");
38457
+ if (!existsSync33(path53)) return null;
38056
38458
  try {
38057
38459
  return JSON.parse(await readFile3(path53, "utf8"));
38058
38460
  } catch {
@@ -38076,8 +38478,8 @@ async function genTechStack(ctx) {
38076
38478
  ].join("\n");
38077
38479
  }
38078
38480
  async function genDecisions(ctx) {
38079
- const decisionsDir = join27(ctx.rootDir, "decisions");
38080
- if (!existsSync32(decisionsDir)) return "_No ADRs yet._";
38481
+ const decisionsDir = join28(ctx.rootDir, "decisions");
38482
+ if (!existsSync33(decisionsDir)) return "_No ADRs yet._";
38081
38483
  const files = ctx.storage.listMarkdown(decisionsDir).sort();
38082
38484
  const accepted = [];
38083
38485
  const proposed = [];
@@ -38102,9 +38504,9 @@ async function genDecisions(ctx) {
38102
38504
  }
38103
38505
  async function genConventions(ctx) {
38104
38506
  const lines = [];
38105
- const claudeMd = join27(ctx.projectRoot, "CLAUDE.MD");
38106
- if (existsSync32(claudeMd)) {
38107
- const content = readFileSync28(claudeMd, "utf8");
38507
+ const claudeMd = join28(ctx.projectRoot, "CLAUDE.MD");
38508
+ if (existsSync33(claudeMd)) {
38509
+ const content = readFileSync29(claudeMd, "utf8");
38108
38510
  const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
38109
38511
  if (match) {
38110
38512
  lines.push('<!-- Extracted from CLAUDE.MD "Architecture rules" -->');
@@ -38136,9 +38538,9 @@ async function genBuild(ctx) {
38136
38538
  ].join("\n");
38137
38539
  }
38138
38540
  async function genOpenQuestions(ctx) {
38139
- const path53 = join27(ctx.rootDir, "risks.md");
38140
- if (!existsSync32(path53)) return "_No open questions._";
38141
- const content = readFileSync28(path53, "utf8");
38541
+ const path53 = join28(ctx.rootDir, "risks.md");
38542
+ if (!existsSync33(path53)) return "_No open questions._";
38543
+ const content = readFileSync29(path53, "utf8");
38142
38544
  const lines = content.split("\n");
38143
38545
  const questions = [];
38144
38546
  let currentTitle = "";
@@ -38212,9 +38614,9 @@ function titleCase(id) {
38212
38614
  return id.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
38213
38615
  }
38214
38616
  async function updateAgentsMd(ctx, projectRoot) {
38215
- const agentsPath = join27(projectRoot, "AGENTS.MD");
38216
- if (existsSync32(agentsPath)) {
38217
- const content = readFileSync28(agentsPath, "utf8");
38617
+ const agentsPath = join28(projectRoot, "AGENTS.MD");
38618
+ if (existsSync33(agentsPath)) {
38619
+ const content = readFileSync29(agentsPath, "utf8");
38218
38620
  const hasAnyMarker = AUTO_SECTIONS.some((id) => content.includes(MARKER_OPEN(id)));
38219
38621
  if (!hasAnyMarker) {
38220
38622
  return {
@@ -38229,8 +38631,8 @@ async function updateAgentsMd(ctx, projectRoot) {
38229
38631
  newSections.set(id, await GENERATORS[id](ctx));
38230
38632
  }
38231
38633
  let manualContent = "";
38232
- if (existsSync32(agentsPath)) {
38233
- const { manualBlocks } = parseAgentsMd(readFileSync28(agentsPath, "utf8"));
38634
+ if (existsSync33(agentsPath)) {
38635
+ const { manualBlocks } = parseAgentsMd(readFileSync29(agentsPath, "utf8"));
38234
38636
  manualContent = manualBlocks.after;
38235
38637
  } else {
38236
38638
  const projectName2 = projectName(projectRoot);
@@ -38246,7 +38648,7 @@ async function updateAgentsMd(ctx, projectRoot) {
38246
38648
  ""
38247
38649
  ].join("\n");
38248
38650
  }
38249
- const oldContent = existsSync32(agentsPath) ? readFileSync28(agentsPath, "utf8") : "";
38651
+ const oldContent = existsSync33(agentsPath) ? readFileSync29(agentsPath, "utf8") : "";
38250
38652
  const { sections: oldSections } = parseAgentsMd(oldContent);
38251
38653
  const changedSections = [];
38252
38654
  for (const id of AUTO_SECTIONS) {
@@ -38258,7 +38660,7 @@ async function updateAgentsMd(ctx, projectRoot) {
38258
38660
  return { changed: false, sections: [] };
38259
38661
  }
38260
38662
  const newContent = serializeAgentsMd(manualContent, newSections);
38261
- writeFileSync18(agentsPath, newContent, "utf8");
38663
+ writeFileSync19(agentsPath, newContent, "utf8");
38262
38664
  return { changed: true, sections: changedSections };
38263
38665
  }
38264
38666
  function hash2(s) {
@@ -38384,8 +38786,8 @@ var init_completeDesign = __esm({
38384
38786
 
38385
38787
  // src/cli/workspace/projectSmoke.ts
38386
38788
  import { spawn as spawn10 } from "node:child_process";
38387
- import { existsSync as existsSync33, readFileSync as readFileSync29 } from "node:fs";
38388
- import { join as join28 } from "node:path";
38789
+ import { existsSync as existsSync34, readFileSync as readFileSync30 } from "node:fs";
38790
+ import { join as join29 } from "node:path";
38389
38791
  function pickSmokeScript(scripts) {
38390
38792
  if (!scripts) return null;
38391
38793
  for (const name of SMOKE_SCRIPT_PRIORITY) {
@@ -38397,13 +38799,13 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS2) {
38397
38799
  if (process.env["ZELARI_SMOKE"] === "0") {
38398
38800
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
38399
38801
  }
38400
- const pkgPath = join28(projectRoot, "package.json");
38401
- if (!existsSync33(pkgPath)) {
38802
+ const pkgPath = join29(projectRoot, "package.json");
38803
+ if (!existsSync34(pkgPath)) {
38402
38804
  return { ran: false, reason: "no package.json (skipped)" };
38403
38805
  }
38404
38806
  let scripts = {};
38405
38807
  try {
38406
- const pkg = JSON.parse(readFileSync29(pkgPath, "utf8"));
38808
+ const pkg = JSON.parse(readFileSync30(pkgPath, "utf8"));
38407
38809
  scripts = pkg.scripts ?? {};
38408
38810
  } catch {
38409
38811
  return { ran: false, reason: "package.json unreadable (skipped)" };
@@ -38491,8 +38893,8 @@ __export(postCouncilHook_exports, {
38491
38893
  runPostCouncilHook: () => runPostCouncilHook
38492
38894
  });
38493
38895
  import { spawn as spawn11 } from "node:child_process";
38494
- import { existsSync as existsSync34, readFileSync as readFileSync30 } from "node:fs";
38495
- import { join as join29 } from "node:path";
38896
+ import { existsSync as existsSync35, readFileSync as readFileSync31 } from "node:fs";
38897
+ import { join as join30 } from "node:path";
38496
38898
  async function runCompleteDesignPostProcessor(ctx, options) {
38497
38899
  if (options?.runMode === "implementation") {
38498
38900
  return {
@@ -38503,9 +38905,9 @@ async function runCompleteDesignPostProcessor(ctx, options) {
38503
38905
  if (process.env["ZELARI_COMPLETE_DESIGN"] === "0") {
38504
38906
  return { ran: false, reason: "ZELARI_COMPLETE_DESIGN=0 (disabled)" };
38505
38907
  }
38506
- const planJsonPath2 = join29(ctx.rootDir, "plan.json");
38507
- const scriptPath = join29(ctx.projectRoot, "complete-design.mjs");
38508
- if (!existsSync34(planJsonPath2)) {
38908
+ const planJsonPath2 = join30(ctx.rootDir, "plan.json");
38909
+ const scriptPath = join30(ctx.projectRoot, "complete-design.mjs");
38910
+ if (!existsSync35(planJsonPath2)) {
38509
38911
  return {
38510
38912
  ran: false,
38511
38913
  reason: ".zelari/plan.json missing (not design-phase)"
@@ -38513,7 +38915,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
38513
38915
  }
38514
38916
  let phaseCount = 0;
38515
38917
  try {
38516
- const parsed = JSON.parse(readFileSync30(planJsonPath2, "utf8"));
38918
+ const parsed = JSON.parse(readFileSync31(planJsonPath2, "utf8"));
38517
38919
  phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
38518
38920
  } catch {
38519
38921
  return { ran: false, reason: ".zelari/plan.json corrupt" };
@@ -38521,7 +38923,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
38521
38923
  if (phaseCount === 0) {
38522
38924
  return { ran: false, reason: ".zelari/plan.json has no phases" };
38523
38925
  }
38524
- if (!existsSync34(scriptPath)) {
38926
+ if (!existsSync35(scriptPath)) {
38525
38927
  try {
38526
38928
  const builtin = await runBuiltinCompleteDesign(ctx);
38527
38929
  return {
@@ -38744,10 +39146,10 @@ __export(councilFeedback_exports, {
38744
39146
  });
38745
39147
  import {
38746
39148
  promises as fs16,
38747
- existsSync as existsSync35,
38748
- readFileSync as readFileSync31,
38749
- writeFileSync as writeFileSync19,
38750
- mkdirSync as mkdirSync16
39149
+ existsSync as existsSync36,
39150
+ readFileSync as readFileSync32,
39151
+ writeFileSync as writeFileSync20,
39152
+ mkdirSync as mkdirSync17
38751
39153
  } from "node:fs";
38752
39154
  import path32 from "node:path";
38753
39155
  import os9 from "node:os";
@@ -38853,9 +39255,9 @@ var init_councilFeedback = __esm({
38853
39255
  }
38854
39256
  // --- persistence ---------------------------------------------------------
38855
39257
  load() {
38856
- if (!existsSync35(this.file)) return;
39258
+ if (!existsSync36(this.file)) return;
38857
39259
  try {
38858
- const raw = readFileSync31(this.file, "utf-8");
39260
+ const raw = readFileSync32(this.file, "utf-8");
38859
39261
  const parsed = JSON.parse(raw);
38860
39262
  if (parsed && Array.isArray(parsed.entries)) {
38861
39263
  this.entries = parsed.entries.filter(
@@ -38866,8 +39268,8 @@ var init_councilFeedback = __esm({
38866
39268
  }
38867
39269
  }
38868
39270
  save() {
38869
- mkdirSync16(path32.dirname(this.file), { recursive: true });
38870
- writeFileSync19(
39271
+ mkdirSync17(path32.dirname(this.file), { recursive: true });
39272
+ writeFileSync20(
38871
39273
  this.file,
38872
39274
  JSON.stringify({ entries: this.entries }, null, 2),
38873
39275
  { encoding: "utf-8", mode: 384 }
@@ -41293,7 +41695,7 @@ __export(executor_exports, {
41293
41695
  resolveNodeTimeoutMs: () => resolveNodeTimeoutMs,
41294
41696
  thoroughnessForKind: () => thoroughnessForKind
41295
41697
  });
41296
- import { existsSync as existsSync36 } from "node:fs";
41698
+ import { existsSync as existsSync37 } from "node:fs";
41297
41699
  import path40 from "node:path";
41298
41700
  function resolveMaxParallel(env = process.env) {
41299
41701
  const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
@@ -41352,7 +41754,7 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
41352
41754
  }
41353
41755
  function defaultChecksExists(cwd) {
41354
41756
  try {
41355
- return existsSync36(path40.join(cwd, ".zelari", "world", "checks.json"));
41757
+ return existsSync37(path40.join(cwd, ".zelari", "world", "checks.json"));
41356
41758
  } catch {
41357
41759
  return false;
41358
41760
  }
@@ -42319,8 +42721,8 @@ __export(prereqChecks_exports, {
42319
42721
  runPrereqChecks: () => runPrereqChecks
42320
42722
  });
42321
42723
  import { execSync, spawnSync as spawnSync2 } from "node:child_process";
42322
- import { existsSync as existsSync37 } from "node:fs";
42323
- import { dirname as dirname7 } from "node:path";
42724
+ import { existsSync as existsSync38 } from "node:fs";
42725
+ import { dirname as dirname8 } from "node:path";
42324
42726
  function isWslBashPath2(p3) {
42325
42727
  if (!p3 || typeof p3 !== "string") return false;
42326
42728
  const n = p3.replace(/\//g, "\\").toLowerCase();
@@ -42426,7 +42828,7 @@ function resolveAgentShellSync() {
42426
42828
  function agentProbeEnv() {
42427
42829
  const env = { ...process.env };
42428
42830
  try {
42429
- const nodeDir = dirname7(process.execPath);
42831
+ const nodeDir = dirname8(process.execPath);
42430
42832
  if (!nodeDir) return env;
42431
42833
  const sep2 = process.platform === "win32" ? ";" : ":";
42432
42834
  const current = env.PATH ?? env.Path ?? "";
@@ -42443,7 +42845,7 @@ function agentProbeEnv() {
42443
42845
  }
42444
42846
  function existsSyncSafe2(p3) {
42445
42847
  try {
42446
- return existsSync37(p3);
42848
+ return existsSync38(p3);
42447
42849
  } catch {
42448
42850
  return false;
42449
42851
  }
@@ -42690,7 +43092,7 @@ var init_prereqChecks = __esm({
42690
43092
  });
42691
43093
 
42692
43094
  // src/cli/plugins/prefs.ts
42693
- import { existsSync as existsSync38, readFileSync as readFileSync32, writeFileSync as writeFileSync20, mkdirSync as mkdirSync17 } from "node:fs";
43095
+ import { existsSync as existsSync39, readFileSync as readFileSync33, writeFileSync as writeFileSync21, mkdirSync as mkdirSync18 } from "node:fs";
42694
43096
  import path43 from "node:path";
42695
43097
  import os10 from "node:os";
42696
43098
  function getPluginPrefsPath() {
@@ -42699,8 +43101,8 @@ function getPluginPrefsPath() {
42699
43101
  function getPluginPrefs() {
42700
43102
  const file2 = getPluginPrefsPath();
42701
43103
  try {
42702
- if (!existsSync38(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
42703
- const raw = readFileSync32(file2, "utf-8");
43104
+ if (!existsSync39(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
43105
+ const raw = readFileSync33(file2, "utf-8");
42704
43106
  const parsed = JSON.parse(raw);
42705
43107
  if (parsed && typeof parsed === "object" && parsed.dontAskAgain && typeof parsed.dontAskAgain === "object") {
42706
43108
  const clean = {};
@@ -42715,8 +43117,8 @@ function getPluginPrefs() {
42715
43117
  }
42716
43118
  function writePluginPrefs(prefs) {
42717
43119
  const file2 = getPluginPrefsPath();
42718
- mkdirSync17(path43.dirname(file2), { recursive: true });
42719
- writeFileSync20(file2, JSON.stringify(prefs, null, 2), {
43120
+ mkdirSync18(path43.dirname(file2), { recursive: true });
43121
+ writeFileSync21(file2, JSON.stringify(prefs, null, 2), {
42720
43122
  encoding: "utf-8",
42721
43123
  mode: 384
42722
43124
  });
@@ -42751,7 +43153,7 @@ __export(registry_exports, {
42751
43153
  findPlugin: () => findPlugin,
42752
43154
  isBinaryOnPath: () => isBinaryOnPath
42753
43155
  });
42754
- import { existsSync as existsSync39 } from "node:fs";
43156
+ import { existsSync as existsSync40 } from "node:fs";
42755
43157
  import path44 from "node:path";
42756
43158
  function detectLocalBin(bin) {
42757
43159
  return (cwd) => {
@@ -42768,7 +43170,7 @@ function isBinaryOnPath(bin, opts = {}) {
42768
43170
  return false;
42769
43171
  }
42770
43172
  const platform = opts.platform ?? process.platform;
42771
- const exists = opts.exists ?? existsSync39;
43173
+ const exists = opts.exists ?? existsSync40;
42772
43174
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
42773
43175
  const pathMod = platform === "win32" ? path44.win32 : path44.posix;
42774
43176
  const sep2 = platform === "win32" ? ";" : ":";
@@ -43502,7 +43904,7 @@ __export(atMentions_exports, {
43502
43904
  extractAtMentions: () => extractAtMentions,
43503
43905
  hasAtMentions: () => hasAtMentions
43504
43906
  });
43505
- import { existsSync as existsSync42, readFileSync as readFileSync34, statSync as statSync7 } from "node:fs";
43907
+ import { existsSync as existsSync43, readFileSync as readFileSync35, statSync as statSync7 } from "node:fs";
43506
43908
  import { basename as basename3, isAbsolute as isAbsolute2, relative as relative3, resolve, sep } from "node:path";
43507
43909
  function isImagePath(abs) {
43508
43910
  const ext = abs.split(".").pop()?.toLowerCase() ?? "";
@@ -43556,7 +43958,7 @@ function resolveMention(token, cwd) {
43556
43958
  note: "outside project root \u2014 skipped"
43557
43959
  };
43558
43960
  }
43559
- if (!existsSync42(abs)) {
43961
+ if (!existsSync43(abs)) {
43560
43962
  return {
43561
43963
  raw: token,
43562
43964
  path: token,
@@ -43608,7 +44010,7 @@ function resolveMention(token, cwd) {
43608
44010
  note: `image too large (${Math.round(st.size / 1024)} KB) \u2014 path only`
43609
44011
  };
43610
44012
  }
43611
- const dataBase64 = readFileSync34(abs).toString("base64");
44013
+ const dataBase64 = readFileSync35(abs).toString("base64");
43612
44014
  return {
43613
44015
  raw: token,
43614
44016
  path: rel2,
@@ -43619,7 +44021,7 @@ function resolveMention(token, cwd) {
43619
44021
  };
43620
44022
  }
43621
44023
  try {
43622
- const buf = readFileSync34(abs);
44024
+ const buf = readFileSync35(abs);
43623
44025
  const head = buf.subarray(0, 800).toString("utf8");
43624
44026
  if (!isProbablyText(abs, head)) {
43625
44027
  return {
@@ -44116,14 +44518,14 @@ var init_skillCategories = __esm({
44116
44518
 
44117
44519
  // src/cli/skillConfigIo.ts
44118
44520
  import {
44119
- existsSync as existsSync44,
44120
- mkdirSync as mkdirSync20,
44521
+ existsSync as existsSync45,
44522
+ mkdirSync as mkdirSync21,
44121
44523
  readdirSync as readdirSync9,
44122
- readFileSync as readFileSync36,
44524
+ readFileSync as readFileSync37,
44123
44525
  rmSync as rmSync4,
44124
- writeFileSync as writeFileSync22
44526
+ writeFileSync as writeFileSync23
44125
44527
  } from "node:fs";
44126
- import { dirname as dirname9, join as join35 } from "node:path";
44528
+ import { dirname as dirname10, join as join36 } from "node:path";
44127
44529
  import { homedir as homedir12 } from "node:os";
44128
44530
  function ensureBuiltinSkillsLoadedSync() {
44129
44531
  if (builtinsLoaded) return;
@@ -44135,13 +44537,13 @@ function ensureBuiltinSkillsLoadedSync() {
44135
44537
  }
44136
44538
  }
44137
44539
  function getUserSkillsDir() {
44138
- return join35(homedir12(), ".zelari-code", "skills");
44540
+ return join36(homedir12(), ".zelari-code", "skills");
44139
44541
  }
44140
44542
  function getProjectSkillsDir(projectRoot) {
44141
- return join35(projectRoot, ".zelari", "skills");
44543
+ return join36(projectRoot, ".zelari", "skills");
44142
44544
  }
44143
44545
  function skillFilePath(dir, name) {
44144
- return join35(dir, name, "SKILL.md");
44546
+ return join36(dir, name, "SKILL.md");
44145
44547
  }
44146
44548
  function classifyScope(skillPath, projectRoot) {
44147
44549
  const userDir = getUserSkillsDir().replace(/\\/g, "/");
@@ -44190,7 +44592,7 @@ function entryFromBuiltin(skill) {
44190
44592
  };
44191
44593
  }
44192
44594
  function scanSkillsDir(dir, projectRoot, seen, out) {
44193
- if (!existsSync44(dir)) return;
44595
+ if (!existsSync45(dir)) return;
44194
44596
  let entries;
44195
44597
  try {
44196
44598
  entries = readdirSync9(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -44199,9 +44601,9 @@ function scanSkillsDir(dir, projectRoot, seen, out) {
44199
44601
  }
44200
44602
  for (const entry of entries) {
44201
44603
  const skillPath = skillFilePath(dir, entry);
44202
- if (!existsSync44(skillPath)) continue;
44604
+ if (!existsSync45(skillPath)) continue;
44203
44605
  try {
44204
- const parsed = parseSkillMd(readFileSync36(skillPath, "utf8"), skillPath);
44606
+ const parsed = parseSkillMd(readFileSync37(skillPath, "utf8"), skillPath);
44205
44607
  if (!parsed) continue;
44206
44608
  if (seen.has(parsed.name)) continue;
44207
44609
  seen.add(parsed.name);
@@ -44217,9 +44619,9 @@ function listSkillsSnapshot(projectRoot) {
44217
44619
  const skills = [];
44218
44620
  const seen = /* @__PURE__ */ new Set();
44219
44621
  if (root) {
44220
- scanSkillsDir(join35(root, ".zelari", "skills"), root, seen, skills);
44221
- scanSkillsDir(join35(root, ".claude", "skills"), root, seen, skills);
44222
- scanSkillsDir(join35(root, ".opencode", "skills"), root, seen, skills);
44622
+ scanSkillsDir(join36(root, ".zelari", "skills"), root, seen, skills);
44623
+ scanSkillsDir(join36(root, ".claude", "skills"), root, seen, skills);
44624
+ scanSkillsDir(join36(root, ".opencode", "skills"), root, seen, skills);
44223
44625
  }
44224
44626
  scanSkillsDir(userSkillsDir, root, seen, skills);
44225
44627
  for (const s of listCodingSkills()) {
@@ -44298,8 +44700,8 @@ function upsertSkill(opts) {
44298
44700
  if (!parsed) {
44299
44701
  return { ok: false, error: "Generated SKILL.md failed validation" };
44300
44702
  }
44301
- mkdirSync20(dirname9(path53), { recursive: true });
44302
- writeFileSync22(path53, content, "utf8");
44703
+ mkdirSync21(dirname10(path53), { recursive: true });
44704
+ writeFileSync23(path53, content, "utf8");
44303
44705
  return { ok: true, path: path53 };
44304
44706
  }
44305
44707
  function removeSkill(opts) {
@@ -44317,9 +44719,9 @@ function removeSkill(opts) {
44317
44719
  }
44318
44720
  dir = getProjectSkillsDir(root);
44319
44721
  }
44320
- const skillDir = join35(dir, name);
44722
+ const skillDir = join36(dir, name);
44321
44723
  const path53 = skillFilePath(dir, name);
44322
- if (!existsSync44(path53) && !existsSync44(skillDir)) {
44724
+ if (!existsSync45(path53) && !existsSync45(skillDir)) {
44323
44725
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
44324
44726
  }
44325
44727
  try {
@@ -44624,36 +45026,36 @@ var init_permissionCli = __esm({
44624
45026
 
44625
45027
  // src/cli/companion/config.ts
44626
45028
  import {
44627
- existsSync as existsSync45,
44628
- mkdirSync as mkdirSync21,
44629
- readFileSync as readFileSync37,
44630
- writeFileSync as writeFileSync23
45029
+ existsSync as existsSync46,
45030
+ mkdirSync as mkdirSync22,
45031
+ readFileSync as readFileSync38,
45032
+ writeFileSync as writeFileSync24
44631
45033
  } from "node:fs";
44632
- import { join as join36 } from "node:path";
45034
+ import { join as join37 } from "node:path";
44633
45035
  import { homedir as homedir13 } from "node:os";
44634
45036
  import { createHash as createHash9, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
44635
45037
  function getZelariHome() {
44636
- return join36(homedir13(), ".zelari-code");
45038
+ return join37(homedir13(), ".zelari-code");
44637
45039
  }
44638
45040
  function getCompanionConfigPath() {
44639
- return join36(getZelariHome(), "companion.json");
45041
+ return join37(getZelariHome(), "companion.json");
44640
45042
  }
44641
45043
  function getCompanionTokenPath() {
44642
- return join36(getZelariHome(), "companion.token");
45044
+ return join37(getZelariHome(), "companion.token");
44643
45045
  }
44644
45046
  function ensureHome() {
44645
45047
  const home = getZelariHome();
44646
- if (!existsSync45(home)) {
44647
- mkdirSync21(home, { recursive: true });
45048
+ if (!existsSync46(home)) {
45049
+ mkdirSync22(home, { recursive: true });
44648
45050
  }
44649
45051
  }
44650
45052
  function loadCompanionConfig() {
44651
45053
  const path53 = getCompanionConfigPath();
44652
- if (!existsSync45(path53)) {
45054
+ if (!existsSync46(path53)) {
44653
45055
  return { projects: [] };
44654
45056
  }
44655
45057
  try {
44656
- const raw = JSON.parse(readFileSync37(path53, "utf8"));
45058
+ const raw = JSON.parse(readFileSync38(path53, "utf8"));
44657
45059
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
44658
45060
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
44659
45061
  ).map((p3) => ({
@@ -44672,7 +45074,7 @@ function loadCompanionConfig() {
44672
45074
  }
44673
45075
  function saveCompanionConfig(cfg) {
44674
45076
  ensureHome();
44675
- writeFileSync23(
45077
+ writeFileSync24(
44676
45078
  getCompanionConfigPath(),
44677
45079
  JSON.stringify(
44678
45080
  {
@@ -44692,12 +45094,12 @@ function loadOrCreateToken(explicit) {
44692
45094
  }
44693
45095
  ensureHome();
44694
45096
  const path53 = getCompanionTokenPath();
44695
- if (existsSync45(path53)) {
44696
- const t = readFileSync37(path53, "utf8").trim();
45097
+ if (existsSync46(path53)) {
45098
+ const t = readFileSync38(path53, "utf8").trim();
44697
45099
  if (t) return { token: t, created: false };
44698
45100
  }
44699
45101
  const token = randomBytes5(24).toString("base64url");
44700
- writeFileSync23(path53, token + "\n", "utf8");
45102
+ writeFileSync24(path53, token + "\n", "utf8");
44701
45103
  try {
44702
45104
  const fs31 = __require("node:fs");
44703
45105
  fs31.chmodSync?.(path53, 384);
@@ -44786,8 +45188,8 @@ var init_config = __esm({
44786
45188
  import { spawn as spawn13 } from "node:child_process";
44787
45189
  import { createInterface as createInterface2 } from "node:readline";
44788
45190
  import { randomUUID as randomUUID8 } from "node:crypto";
44789
- import { writeFileSync as writeFileSync24, unlinkSync as unlinkSync3 } from "node:fs";
44790
- import { join as join37 } from "node:path";
45191
+ import { writeFileSync as writeFileSync25, unlinkSync as unlinkSync3 } from "node:fs";
45192
+ import { join as join38 } from "node:path";
44791
45193
  import { tmpdir as tmpdir3 } from "node:os";
44792
45194
  var RunManager;
44793
45195
  var init_runManager = __esm({
@@ -44891,9 +45293,9 @@ var init_runManager = __esm({
44891
45293
  }
44892
45294
  let historyFile;
44893
45295
  if (args.history && Array.isArray(args.history) && args.history.length > 0) {
44894
- historyFile = join37(tmpdir3(), `zelari-companion-hist-${id}.json`);
45296
+ historyFile = join38(tmpdir3(), `zelari-companion-hist-${id}.json`);
44895
45297
  try {
44896
- writeFileSync24(historyFile, JSON.stringify(args.history), "utf8");
45298
+ writeFileSync25(historyFile, JSON.stringify(args.history), "utf8");
44897
45299
  argv.push("--history-file", historyFile);
44898
45300
  } catch {
44899
45301
  historyFile = void 0;
@@ -45018,7 +45420,7 @@ __export(serve_exports, {
45018
45420
  runCompanionServe: () => runCompanionServe
45019
45421
  });
45020
45422
  import { createServer as createServer3 } from "node:http";
45021
- import { existsSync as existsSync46 } from "node:fs";
45423
+ import { existsSync as existsSync47 } from "node:fs";
45022
45424
  import { resolve as resolve2 } from "node:path";
45023
45425
  function readBody(req, max = 2e6) {
45024
45426
  return new Promise((resolveBody, reject) => {
@@ -45066,7 +45468,7 @@ async function runCompanionServe(opts = {}) {
45066
45468
  let projects = mergeProjects(fileCfg, opts.projects ?? []);
45067
45469
  projects = projects.filter((p3) => {
45068
45470
  const abs = resolve2(p3.path);
45069
- if (!existsSync46(abs)) {
45471
+ if (!existsSync47(abs)) {
45070
45472
  process.stderr.write(
45071
45473
  `[zelari-code serve] skip missing project path: ${p3.path}
45072
45474
  `
@@ -45388,7 +45790,7 @@ __export(doctor_exports, {
45388
45790
  runDoctor: () => runDoctor
45389
45791
  });
45390
45792
  import { execSync as execSync2 } from "node:child_process";
45391
- import { existsSync as existsSync47, readFileSync as readFileSync38, readlinkSync, statSync as statSync8 } from "node:fs";
45793
+ import { existsSync as existsSync48, readFileSync as readFileSync39, readlinkSync, statSync as statSync8 } from "node:fs";
45392
45794
  import { createRequire as createRequire3 } from "node:module";
45393
45795
  import { fileURLToPath as fileURLToPath2 } from "node:url";
45394
45796
  import path51 from "node:path";
@@ -45396,9 +45798,9 @@ function findPackageRoot(start) {
45396
45798
  let dir = start;
45397
45799
  for (let i = 0; i < 6; i += 1) {
45398
45800
  const candidate = path51.join(dir, "package.json");
45399
- if (existsSync47(candidate)) {
45801
+ if (existsSync48(candidate)) {
45400
45802
  try {
45401
- const pkg = JSON.parse(readFileSync38(candidate, "utf8"));
45803
+ const pkg = JSON.parse(readFileSync39(candidate, "utf8"));
45402
45804
  if (pkg.name === "zelari-code") return dir;
45403
45805
  } catch {
45404
45806
  }
@@ -45422,7 +45824,7 @@ function tryExec(cmd) {
45422
45824
  function readPackageJson3() {
45423
45825
  try {
45424
45826
  const pkgPath = path51.join(packageRoot, "package.json");
45425
- return JSON.parse(readFileSync38(pkgPath, "utf8"));
45827
+ return JSON.parse(readFileSync39(pkgPath, "utf8"));
45426
45828
  } catch {
45427
45829
  return null;
45428
45830
  }
@@ -45438,7 +45840,7 @@ function checkShim(pkgName) {
45438
45840
  const isWin = process.platform === "win32";
45439
45841
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
45440
45842
  const shimPath = path51.join(prefix, shimName);
45441
- if (!existsSync47(shimPath)) {
45843
+ if (!existsSync48(shimPath)) {
45442
45844
  return FAIL(
45443
45845
  `shim not found at ${shimPath}
45444
45846
  fix: npm install -g ${pkgName}@latest --force`
@@ -45447,7 +45849,7 @@ function checkShim(pkgName) {
45447
45849
  try {
45448
45850
  const st = statSync8(shimPath);
45449
45851
  if (isWin) {
45450
- const content = readFileSync38(shimPath, "utf8");
45852
+ const content = readFileSync39(shimPath, "utf8");
45451
45853
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
45452
45854
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
45453
45855
  }
@@ -45506,7 +45908,7 @@ function checkNode(pkg) {
45506
45908
  }
45507
45909
  function checkBundle() {
45508
45910
  const bundle = path51.join(packageRoot, "dist", "cli", "main.bundled.js");
45509
- if (!existsSync47(bundle)) {
45911
+ if (!existsSync48(bundle)) {
45510
45912
  return FAIL(
45511
45913
  `dist/cli/main.bundled.js missing at ${bundle}
45512
45914
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -45887,7 +46289,7 @@ __export(inspect_exports, {
45887
46289
  runInspect: () => runInspect
45888
46290
  });
45889
46291
  import path52 from "node:path";
45890
- import { existsSync as existsSync48, readFileSync as readFileSync39, readdirSync as readdirSync10 } from "node:fs";
46292
+ import { existsSync as existsSync49, readFileSync as readFileSync40, readdirSync as readdirSync10 } from "node:fs";
45891
46293
  import { homedir as homedir14 } from "node:os";
45892
46294
  async function collectInspectReport(cwd = process.cwd()) {
45893
46295
  ensureBuiltinSkillsLoadedSync();
@@ -45920,11 +46322,11 @@ async function collectInspectReport(cwd = process.cwd()) {
45920
46322
  folders: listTrustedFolders()
45921
46323
  },
45922
46324
  configSources: [
45923
- { path: userMcpPath, exists: existsSync48(userMcpPath) },
45924
- { path: projectMcpPath, exists: existsSync48(projectMcpPath) },
45925
- { path: path52.join(homedir14(), ".zelari-code", "provider.json"), exists: existsSync48(path52.join(homedir14(), ".zelari-code", "provider.json")) },
45926
- { path: path52.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync48(path52.join(cwd, ".zelari", "AGENTS.md")) },
45927
- { path: path52.join(cwd, "AGENTS.md"), exists: existsSync48(path52.join(cwd, "AGENTS.md")) }
46325
+ { path: userMcpPath, exists: existsSync49(userMcpPath) },
46326
+ { path: projectMcpPath, exists: existsSync49(projectMcpPath) },
46327
+ { path: path52.join(homedir14(), ".zelari-code", "provider.json"), exists: existsSync49(path52.join(homedir14(), ".zelari-code", "provider.json")) },
46328
+ { path: path52.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync49(path52.join(cwd, ".zelari", "AGENTS.md")) },
46329
+ { path: path52.join(cwd, "AGENTS.md"), exists: existsSync49(path52.join(cwd, "AGENTS.md")) }
45928
46330
  ],
45929
46331
  skills: {
45930
46332
  total: snap.skills.length,
@@ -45937,7 +46339,7 @@ async function collectInspectReport(cwd = process.cwd()) {
45937
46339
  user: mcp.servers.filter((s) => s.scope === "user").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
45938
46340
  project: mcp.servers.filter((s) => s.scope === "project").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
45939
46341
  projectTrusted,
45940
- projectConfigExists: existsSync48(projectMcpPath)
46342
+ projectConfigExists: existsSync49(projectMcpPath)
45941
46343
  },
45942
46344
  hooks: {
45943
46345
  global: {
@@ -45969,9 +46371,9 @@ function findAgentsMd(cwd) {
45969
46371
  ];
45970
46372
  const found = [];
45971
46373
  for (const c of candidates) {
45972
- if (existsSync48(c)) {
46374
+ if (existsSync49(c)) {
45973
46375
  try {
45974
- const text = readFileSync39(c, "utf8");
46376
+ const text = readFileSync40(c, "utf8");
45975
46377
  found.push(`${c} (${text.length} bytes)`);
45976
46378
  } catch {
45977
46379
  found.push(`${c} (unreadable)`);
@@ -53165,7 +53567,7 @@ async function handlePromoteMember(ctx, memberId) {
53165
53567
  }
53166
53568
 
53167
53569
  // src/cli/branchManager.ts
53168
- import { promises as fs26, existsSync as existsSync40, readFileSync as readFileSync33, writeFileSync as writeFileSync21, mkdirSync as mkdirSync18, statSync as statSync5, rmSync as rmSync3 } from "node:fs";
53570
+ import { promises as fs26, existsSync as existsSync41, readFileSync as readFileSync34, writeFileSync as writeFileSync22, mkdirSync as mkdirSync19, statSync as statSync5, rmSync as rmSync3 } from "node:fs";
53169
53571
  import path46 from "node:path";
53170
53572
  import os12 from "node:os";
53171
53573
  var META_FILENAME = "meta.json";
@@ -53187,11 +53589,11 @@ function sessionsPathFor(name, baseDir) {
53187
53589
  }
53188
53590
  function readBranchMeta(name, baseDir) {
53189
53591
  const metaPath = metaPathFor(name, baseDir);
53190
- if (!existsSync40(metaPath)) {
53592
+ if (!existsSync41(metaPath)) {
53191
53593
  throw new BranchNotFoundError(`Branch "${name}" not found`);
53192
53594
  }
53193
53595
  try {
53194
- const raw = readFileSync33(metaPath, "utf-8");
53596
+ const raw = readFileSync34(metaPath, "utf-8");
53195
53597
  const parsed = JSON.parse(raw);
53196
53598
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
53197
53599
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -53208,8 +53610,8 @@ function readBranchMeta(name, baseDir) {
53208
53610
  }
53209
53611
  function writeBranchMeta(name, baseDir, meta3) {
53210
53612
  const metaPath = metaPathFor(name, baseDir);
53211
- mkdirSync18(path46.dirname(metaPath), { recursive: true });
53212
- writeFileSync21(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
53613
+ mkdirSync19(path46.dirname(metaPath), { recursive: true });
53614
+ writeFileSync22(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
53213
53615
  }
53214
53616
  async function countSessions(name, baseDir) {
53215
53617
  const sessionsPath = sessionsPathFor(name, baseDir);
@@ -53247,7 +53649,7 @@ var SessionNotFoundError = class extends Error {
53247
53649
  };
53248
53650
  function branchExists(name, baseDir = getBranchesBaseDir()) {
53249
53651
  const bp = branchPathFor(name, baseDir);
53250
- return existsSync40(bp) && existsSync40(metaPathFor(name, baseDir));
53652
+ return existsSync41(bp) && existsSync41(metaPathFor(name, baseDir));
53251
53653
  }
53252
53654
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
53253
53655
  if (!name || name.trim().length === 0) {
@@ -53260,12 +53662,12 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
53260
53662
  throw new BranchAlreadyExistsError(name);
53261
53663
  }
53262
53664
  const sourcePath = path46.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
53263
- if (!existsSync40(sourcePath)) {
53665
+ if (!existsSync41(sourcePath)) {
53264
53666
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
53265
53667
  }
53266
53668
  const branchPath = branchPathFor(name, baseDir);
53267
53669
  const branchSessionsPath = sessionsPathFor(name, baseDir);
53268
- mkdirSync18(branchSessionsPath, { recursive: true });
53670
+ mkdirSync19(branchSessionsPath, { recursive: true });
53269
53671
  const destPath = path46.join(branchSessionsPath, `${fromSessionId}.jsonl`);
53270
53672
  await fs26.copyFile(sourcePath, destPath);
53271
53673
  const meta3 = {
@@ -53293,7 +53695,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
53293
53695
  const results = [];
53294
53696
  for (const entry of entries) {
53295
53697
  const metaPath = metaPathFor(entry, baseDir);
53296
- if (!existsSync40(metaPath)) continue;
53698
+ if (!existsSync41(metaPath)) continue;
53297
53699
  try {
53298
53700
  const meta3 = readBranchMeta(entry, baseDir);
53299
53701
  const sessionCount = await countSessions(entry, baseDir);
@@ -53484,7 +53886,7 @@ import path48 from "node:path";
53484
53886
  import os13 from "node:os";
53485
53887
 
53486
53888
  // src/cli/skillHistory.ts
53487
- import { promises as fs28, existsSync as existsSync41, statSync as statSync6, renameSync as renameSync4, appendFileSync as appendFileSync4, mkdirSync as mkdirSync19 } from "node:fs";
53889
+ import { promises as fs28, existsSync as existsSync42, statSync as statSync6, renameSync as renameSync5, appendFileSync as appendFileSync4, mkdirSync as mkdirSync20 } from "node:fs";
53488
53890
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
53489
53891
  async function readSkillHistory(file2) {
53490
53892
  let raw = "";
@@ -54812,9 +55214,9 @@ function ContinueKey({ onContinue }) {
54812
55214
  init_providerConfig();
54813
55215
 
54814
55216
  // src/cli/wizard/firstRun.ts
54815
- import { existsSync as existsSync43 } from "node:fs";
55217
+ import { existsSync as existsSync44 } from "node:fs";
54816
55218
  function shouldRunWizard(input) {
54817
- const exists = input.exists ?? existsSync43;
55219
+ const exists = input.exists ?? existsSync44;
54818
55220
  if (input.hasResetConfigFlag) {
54819
55221
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
54820
55222
  }
@@ -55099,7 +55501,7 @@ init_keyStore();
55099
55501
  init_providerConfig();
55100
55502
  init_openai_compatible();
55101
55503
  init_phase();
55102
- import { readFileSync as readFileSync35 } from "node:fs";
55504
+ import { readFileSync as readFileSync36 } from "node:fs";
55103
55505
  function parseHeadlessFlags(argv) {
55104
55506
  if (!argv.includes("--headless")) {
55105
55507
  return { options: null };
@@ -55172,7 +55574,7 @@ function parseHeadlessFlags(argv) {
55172
55574
  let raw = null;
55173
55575
  if (arg === "--history-file") {
55174
55576
  try {
55175
- raw = readFileSync35(next, "utf-8");
55577
+ raw = readFileSync36(next, "utf-8");
55176
55578
  } catch {
55177
55579
  raw = null;
55178
55580
  }
@@ -55659,6 +56061,20 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
55659
56061
  const sessionId = crypto.randomUUID();
55660
56062
  const { registry: toolRegistry } = createBuiltinToolRegistry({
55661
56063
  planMode: planModeFromOpts(opts),
56064
+ // ADR-0018 3b: upgrade plan-task domain events to first-class NDJSON
56065
+ // BrainEvents. Rust envelopes every stdout line with runId/conversationId,
56066
+ // so task events ride the same multiplexed channel as the rest.
56067
+ onTaskEvent: (ev) => {
56068
+ if (opts.output !== "json") return;
56069
+ emitEvent({
56070
+ type: ev.type,
56071
+ id: crypto.randomUUID(),
56072
+ ts: Date.now(),
56073
+ sessionId,
56074
+ source: ev.source,
56075
+ ...ev.type === "task_update" ? { task: ev.task } : { tasks: ev.tasks }
56076
+ });
56077
+ },
55662
56078
  permissionPolicy: {
55663
56079
  read: "allow",
55664
56080
  write: "allow",