zelari-code 1.42.0 → 1.44.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) {
@@ -38382,10 +38784,181 @@ var init_completeDesign = __esm({
38382
38784
  }
38383
38785
  });
38384
38786
 
38787
+ // src/cli/workspace/planDriftCheck.ts
38788
+ import { existsSync as existsSync34, readFileSync as readFileSync30, readdirSync as readdirSync9, statSync as statSync5, writeFileSync as writeFileSync20 } from "node:fs";
38789
+ import { join as join29 } from "node:path";
38790
+ function findCanonicalDoc(rootDir) {
38791
+ const docsDir = join29(rootDir, "docs");
38792
+ if (!existsSync34(docsDir)) return null;
38793
+ const candidates = readdirSync9(docsDir).filter((f) => /^plan-canonical.*\.md$/i.test(f)).map((f) => ({ f, mtime: statSync5(join29(docsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
38794
+ return candidates.length > 0 ? candidates[0].f : null;
38795
+ }
38796
+ function parseCanonicalDoc(text) {
38797
+ const activePhases = /* @__PURE__ */ new Set();
38798
+ const blockedPrefixes = /* @__PURE__ */ new Set();
38799
+ const headingPhase = /^#{2,6}[^\n]*?`([a-z0-9][a-z0-9-]*)`/gm;
38800
+ for (const m of text.matchAll(headingPhase)) activePhases.add(m[1]);
38801
+ const blocked = /`([a-z0-9][a-z0-9-]*)-\*`/g;
38802
+ for (const m of text.matchAll(blocked)) blockedPrefixes.add(`${m[1]}-`);
38803
+ return { activePhases, blockedPrefixes };
38804
+ }
38805
+ function normalizeTitle(value) {
38806
+ if (typeof value !== "string") return "";
38807
+ return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
38808
+ }
38809
+ function versionKey(value) {
38810
+ if (typeof value !== "string") return "";
38811
+ const m = value.toLowerCase().match(/v?\d+(?:\.\d+)*/);
38812
+ return m ? m[0].replace(/^v/, "") : value.trim().toLowerCase();
38813
+ }
38814
+ function firstString2(v) {
38815
+ return typeof v === "string" && v.trim().length > 0 ? v : null;
38816
+ }
38817
+ function readFileSyncSafe(path53) {
38818
+ try {
38819
+ return readFileSync30(path53, "utf8");
38820
+ } catch {
38821
+ return null;
38822
+ }
38823
+ }
38824
+ async function runPlanDriftCheck(rootDir) {
38825
+ if (process.env["ZELARI_DRIFT_CHECK"] === "0") {
38826
+ return { ran: false, reason: "ZELARI_DRIFT_CHECK=0 (disabled)" };
38827
+ }
38828
+ const planPath = join29(rootDir, "plan.json");
38829
+ if (!existsSync34(planPath)) {
38830
+ return { ran: false, reason: ".zelari/plan.json missing (not design-phase)" };
38831
+ }
38832
+ let plan;
38833
+ try {
38834
+ plan = JSON.parse(readFileSync30(planPath, "utf8"));
38835
+ } catch {
38836
+ return { ran: false, reason: ".zelari/plan.json corrupt" };
38837
+ }
38838
+ const findings = [];
38839
+ const phases = Array.isArray(plan.phases) ? plan.phases : [];
38840
+ const tasks = Array.isArray(plan.tasks) ? plan.tasks : [];
38841
+ const milestones = Array.isArray(plan.milestones) ? plan.milestones : [];
38842
+ const phaseIds = new Set(phases.map((p3) => typeof p3.id === "string" ? p3.id : "").filter(Boolean));
38843
+ const canonicalName = findCanonicalDoc(rootDir);
38844
+ const canonicalText = canonicalName ? readFileSyncSafe(join29(rootDir, "docs", canonicalName)) : null;
38845
+ if (canonicalText !== null) {
38846
+ const { activePhases, blockedPrefixes } = parseCanonicalDoc(canonicalText);
38847
+ for (const id of activePhases) {
38848
+ if (!phaseIds.has(id)) {
38849
+ findings.push({
38850
+ code: "CANONICAL_PHASE_MISSING",
38851
+ severity: "error",
38852
+ message: `canonical phase \`${id}\` (${canonicalName}) is missing from plan.json`
38853
+ });
38854
+ }
38855
+ }
38856
+ for (const id of phaseIds) {
38857
+ const hit = [...blockedPrefixes].find((p3) => id.startsWith(p3));
38858
+ if (hit) {
38859
+ findings.push({
38860
+ code: "PHASE_IN_CANONICAL_BLOCKLIST",
38861
+ severity: "error",
38862
+ message: `phase \`${id}\` matches canonical duplicate/descope prefix \`${hit}*\``
38863
+ });
38864
+ }
38865
+ }
38866
+ for (const t of tasks) {
38867
+ const id = typeof t.id === "string" ? t.id : "";
38868
+ const hit = id ? [...blockedPrefixes].find((p3) => id.startsWith(p3)) : void 0;
38869
+ if (hit) {
38870
+ findings.push({
38871
+ code: "TASK_IN_CANONICAL_BLOCKLIST",
38872
+ severity: "error",
38873
+ message: `task \`${id}\` matches canonical duplicate/descope prefix \`${hit}*\``
38874
+ });
38875
+ }
38876
+ }
38877
+ }
38878
+ const byVersion = /* @__PURE__ */ new Map();
38879
+ for (const m of milestones) {
38880
+ const key = versionKey(m.targetVersion);
38881
+ if (!key) continue;
38882
+ const id = typeof m.id === "string" ? m.id : "(no id)";
38883
+ byVersion.set(key, [...byVersion.get(key) ?? [], id]);
38884
+ }
38885
+ for (const [version2, ids] of byVersion) {
38886
+ if (ids.length > 1) {
38887
+ findings.push({
38888
+ code: "DUPLICATE_MILESTONE",
38889
+ severity: "error",
38890
+ message: `${ids.length} milestones target ${version2}: ${ids.join(", ")} \u2014 keep exactly one canonical`
38891
+ });
38892
+ }
38893
+ }
38894
+ const byTitle = /* @__PURE__ */ new Map();
38895
+ for (const t of tasks) {
38896
+ const title = normalizeTitle(
38897
+ firstString2(t.title) ?? firstString2(t.name) ?? firstString2(t.description)
38898
+ );
38899
+ const id = typeof t.id === "string" ? t.id : "(no id)";
38900
+ if (title) byTitle.set(title, [...byTitle.get(title) ?? [], id]);
38901
+ const phaseId = firstString2(t.phaseId);
38902
+ if (phaseId && !phaseIds.has(phaseId)) {
38903
+ findings.push({
38904
+ code: "TASK_IN_UNKNOWN_PHASE",
38905
+ severity: "warning",
38906
+ message: `task \`${id}\` references unknown phaseId \`${phaseId}\``
38907
+ });
38908
+ }
38909
+ }
38910
+ for (const [title, ids] of byTitle) {
38911
+ if (ids.length > 1) {
38912
+ findings.push({
38913
+ code: "DUPLICATE_TASK_TITLE",
38914
+ severity: "warning",
38915
+ message: `duplicate task title "${title.slice(0, 60)}": ${ids.join(", ")}`
38916
+ });
38917
+ }
38918
+ }
38919
+ const ok = findings.every((f) => f.severity !== "error");
38920
+ const canonicalParsed = canonicalName !== null && canonicalText !== null;
38921
+ let reportPath;
38922
+ try {
38923
+ reportPath = join29(rootDir, "drift-report.json");
38924
+ writeFileSync20(
38925
+ reportPath,
38926
+ JSON.stringify(
38927
+ {
38928
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
38929
+ ok,
38930
+ canonicalDoc: canonicalName ?? null,
38931
+ counts: { phases: phases.length, tasks: tasks.length, milestones: milestones.length },
38932
+ findings
38933
+ },
38934
+ null,
38935
+ 2
38936
+ ) + "\n",
38937
+ "utf8"
38938
+ );
38939
+ } catch {
38940
+ reportPath = void 0;
38941
+ }
38942
+ return {
38943
+ ran: true,
38944
+ ok,
38945
+ findings,
38946
+ ...canonicalParsed ? { canonicalDoc: canonicalName } : {
38947
+ reason: canonicalName ? `canonical doc ${canonicalName} unreadable (structural checks only)` : "no canonical doc (structural checks only)"
38948
+ },
38949
+ ...reportPath ? { reportPath } : {}
38950
+ };
38951
+ }
38952
+ var init_planDriftCheck = __esm({
38953
+ "src/cli/workspace/planDriftCheck.ts"() {
38954
+ "use strict";
38955
+ }
38956
+ });
38957
+
38385
38958
  // src/cli/workspace/projectSmoke.ts
38386
38959
  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";
38960
+ import { existsSync as existsSync35, readFileSync as readFileSync31 } from "node:fs";
38961
+ import { join as join30 } from "node:path";
38389
38962
  function pickSmokeScript(scripts) {
38390
38963
  if (!scripts) return null;
38391
38964
  for (const name of SMOKE_SCRIPT_PRIORITY) {
@@ -38397,13 +38970,13 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS2) {
38397
38970
  if (process.env["ZELARI_SMOKE"] === "0") {
38398
38971
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
38399
38972
  }
38400
- const pkgPath = join28(projectRoot, "package.json");
38401
- if (!existsSync33(pkgPath)) {
38973
+ const pkgPath = join30(projectRoot, "package.json");
38974
+ if (!existsSync35(pkgPath)) {
38402
38975
  return { ran: false, reason: "no package.json (skipped)" };
38403
38976
  }
38404
38977
  let scripts = {};
38405
38978
  try {
38406
- const pkg = JSON.parse(readFileSync29(pkgPath, "utf8"));
38979
+ const pkg = JSON.parse(readFileSync31(pkgPath, "utf8"));
38407
38980
  scripts = pkg.scripts ?? {};
38408
38981
  } catch {
38409
38982
  return { ran: false, reason: "package.json unreadable (skipped)" };
@@ -38491,8 +39064,8 @@ __export(postCouncilHook_exports, {
38491
39064
  runPostCouncilHook: () => runPostCouncilHook
38492
39065
  });
38493
39066
  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";
39067
+ import { existsSync as existsSync36, readFileSync as readFileSync32 } from "node:fs";
39068
+ import { join as join31 } from "node:path";
38496
39069
  async function runCompleteDesignPostProcessor(ctx, options) {
38497
39070
  if (options?.runMode === "implementation") {
38498
39071
  return {
@@ -38503,9 +39076,9 @@ async function runCompleteDesignPostProcessor(ctx, options) {
38503
39076
  if (process.env["ZELARI_COMPLETE_DESIGN"] === "0") {
38504
39077
  return { ran: false, reason: "ZELARI_COMPLETE_DESIGN=0 (disabled)" };
38505
39078
  }
38506
- const planJsonPath2 = join29(ctx.rootDir, "plan.json");
38507
- const scriptPath = join29(ctx.projectRoot, "complete-design.mjs");
38508
- if (!existsSync34(planJsonPath2)) {
39079
+ const planJsonPath2 = join31(ctx.rootDir, "plan.json");
39080
+ const scriptPath = join31(ctx.projectRoot, "complete-design.mjs");
39081
+ if (!existsSync36(planJsonPath2)) {
38509
39082
  return {
38510
39083
  ran: false,
38511
39084
  reason: ".zelari/plan.json missing (not design-phase)"
@@ -38513,7 +39086,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
38513
39086
  }
38514
39087
  let phaseCount = 0;
38515
39088
  try {
38516
- const parsed = JSON.parse(readFileSync30(planJsonPath2, "utf8"));
39089
+ const parsed = JSON.parse(readFileSync32(planJsonPath2, "utf8"));
38517
39090
  phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
38518
39091
  } catch {
38519
39092
  return { ran: false, reason: ".zelari/plan.json corrupt" };
@@ -38521,7 +39094,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
38521
39094
  if (phaseCount === 0) {
38522
39095
  return { ran: false, reason: ".zelari/plan.json has no phases" };
38523
39096
  }
38524
- if (!existsSync34(scriptPath)) {
39097
+ if (!existsSync36(scriptPath)) {
38525
39098
  try {
38526
39099
  const builtin = await runBuiltinCompleteDesign(ctx);
38527
39100
  return {
@@ -38624,6 +39197,7 @@ async function runPostCouncilHook(ctx, options) {
38624
39197
  }
38625
39198
  }
38626
39199
  const completeDesign = await runCompleteDesignPostProcessor(ctx, options);
39200
+ const driftCheck = await runPlanDriftCheck(ctx.rootDir);
38627
39201
  let verification = await runImplementationVerificationHook(ctx, options);
38628
39202
  let autofix = { ran: false };
38629
39203
  if (process.env["ZELARI_VERIFY_AUTOFIX"] !== "0" && verification.ran && verification.ok === false && verification.report) {
@@ -38715,7 +39289,7 @@ async function runPostCouncilHook(ctx, options) {
38715
39289
  }
38716
39290
  }
38717
39291
  return {
38718
- ran: agentsMdResult.ran || completeDesign.ran || verification.ran || lessons.ran || smoke.ran || completionHook.ran,
39292
+ ran: agentsMdResult.ran || completeDesign.ran || driftCheck.ran || verification.ran || lessons.ran || smoke.ran || completionHook.ran,
38719
39293
  changed: agentsMdResult.changed,
38720
39294
  sections: agentsMdResult.sections,
38721
39295
  ...agentsMdResult.reason ? { reason: agentsMdResult.reason } : {},
@@ -38724,7 +39298,8 @@ async function runPostCouncilHook(ctx, options) {
38724
39298
  autofix,
38725
39299
  lessons,
38726
39300
  smoke,
38727
- completion: completionHook
39301
+ completion: completionHook,
39302
+ driftCheck
38728
39303
  };
38729
39304
  }
38730
39305
  var init_postCouncilHook = __esm({
@@ -38733,6 +39308,7 @@ var init_postCouncilHook = __esm({
38733
39308
  init_council();
38734
39309
  init_agentsMd();
38735
39310
  init_completeDesign();
39311
+ init_planDriftCheck();
38736
39312
  init_projectSmoke();
38737
39313
  }
38738
39314
  });
@@ -38744,10 +39320,10 @@ __export(councilFeedback_exports, {
38744
39320
  });
38745
39321
  import {
38746
39322
  promises as fs16,
38747
- existsSync as existsSync35,
38748
- readFileSync as readFileSync31,
38749
- writeFileSync as writeFileSync19,
38750
- mkdirSync as mkdirSync16
39323
+ existsSync as existsSync37,
39324
+ readFileSync as readFileSync33,
39325
+ writeFileSync as writeFileSync21,
39326
+ mkdirSync as mkdirSync17
38751
39327
  } from "node:fs";
38752
39328
  import path32 from "node:path";
38753
39329
  import os9 from "node:os";
@@ -38853,9 +39429,9 @@ var init_councilFeedback = __esm({
38853
39429
  }
38854
39430
  // --- persistence ---------------------------------------------------------
38855
39431
  load() {
38856
- if (!existsSync35(this.file)) return;
39432
+ if (!existsSync37(this.file)) return;
38857
39433
  try {
38858
- const raw = readFileSync31(this.file, "utf-8");
39434
+ const raw = readFileSync33(this.file, "utf-8");
38859
39435
  const parsed = JSON.parse(raw);
38860
39436
  if (parsed && Array.isArray(parsed.entries)) {
38861
39437
  this.entries = parsed.entries.filter(
@@ -38866,8 +39442,8 @@ var init_councilFeedback = __esm({
38866
39442
  }
38867
39443
  }
38868
39444
  save() {
38869
- mkdirSync16(path32.dirname(this.file), { recursive: true });
38870
- writeFileSync19(
39445
+ mkdirSync17(path32.dirname(this.file), { recursive: true });
39446
+ writeFileSync21(
38871
39447
  this.file,
38872
39448
  JSON.stringify({ entries: this.entries }, null, 2),
38873
39449
  { encoding: "utf-8", mode: 384 }
@@ -41293,7 +41869,7 @@ __export(executor_exports, {
41293
41869
  resolveNodeTimeoutMs: () => resolveNodeTimeoutMs,
41294
41870
  thoroughnessForKind: () => thoroughnessForKind
41295
41871
  });
41296
- import { existsSync as existsSync36 } from "node:fs";
41872
+ import { existsSync as existsSync38 } from "node:fs";
41297
41873
  import path40 from "node:path";
41298
41874
  function resolveMaxParallel(env = process.env) {
41299
41875
  const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
@@ -41352,7 +41928,7 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
41352
41928
  }
41353
41929
  function defaultChecksExists(cwd) {
41354
41930
  try {
41355
- return existsSync36(path40.join(cwd, ".zelari", "world", "checks.json"));
41931
+ return existsSync38(path40.join(cwd, ".zelari", "world", "checks.json"));
41356
41932
  } catch {
41357
41933
  return false;
41358
41934
  }
@@ -42319,8 +42895,8 @@ __export(prereqChecks_exports, {
42319
42895
  runPrereqChecks: () => runPrereqChecks
42320
42896
  });
42321
42897
  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";
42898
+ import { existsSync as existsSync39 } from "node:fs";
42899
+ import { dirname as dirname8 } from "node:path";
42324
42900
  function isWslBashPath2(p3) {
42325
42901
  if (!p3 || typeof p3 !== "string") return false;
42326
42902
  const n = p3.replace(/\//g, "\\").toLowerCase();
@@ -42426,7 +43002,7 @@ function resolveAgentShellSync() {
42426
43002
  function agentProbeEnv() {
42427
43003
  const env = { ...process.env };
42428
43004
  try {
42429
- const nodeDir = dirname7(process.execPath);
43005
+ const nodeDir = dirname8(process.execPath);
42430
43006
  if (!nodeDir) return env;
42431
43007
  const sep2 = process.platform === "win32" ? ";" : ":";
42432
43008
  const current = env.PATH ?? env.Path ?? "";
@@ -42443,7 +43019,7 @@ function agentProbeEnv() {
42443
43019
  }
42444
43020
  function existsSyncSafe2(p3) {
42445
43021
  try {
42446
- return existsSync37(p3);
43022
+ return existsSync39(p3);
42447
43023
  } catch {
42448
43024
  return false;
42449
43025
  }
@@ -42690,7 +43266,7 @@ var init_prereqChecks = __esm({
42690
43266
  });
42691
43267
 
42692
43268
  // src/cli/plugins/prefs.ts
42693
- import { existsSync as existsSync38, readFileSync as readFileSync32, writeFileSync as writeFileSync20, mkdirSync as mkdirSync17 } from "node:fs";
43269
+ import { existsSync as existsSync40, readFileSync as readFileSync34, writeFileSync as writeFileSync22, mkdirSync as mkdirSync18 } from "node:fs";
42694
43270
  import path43 from "node:path";
42695
43271
  import os10 from "node:os";
42696
43272
  function getPluginPrefsPath() {
@@ -42699,8 +43275,8 @@ function getPluginPrefsPath() {
42699
43275
  function getPluginPrefs() {
42700
43276
  const file2 = getPluginPrefsPath();
42701
43277
  try {
42702
- if (!existsSync38(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
42703
- const raw = readFileSync32(file2, "utf-8");
43278
+ if (!existsSync40(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
43279
+ const raw = readFileSync34(file2, "utf-8");
42704
43280
  const parsed = JSON.parse(raw);
42705
43281
  if (parsed && typeof parsed === "object" && parsed.dontAskAgain && typeof parsed.dontAskAgain === "object") {
42706
43282
  const clean = {};
@@ -42715,8 +43291,8 @@ function getPluginPrefs() {
42715
43291
  }
42716
43292
  function writePluginPrefs(prefs) {
42717
43293
  const file2 = getPluginPrefsPath();
42718
- mkdirSync17(path43.dirname(file2), { recursive: true });
42719
- writeFileSync20(file2, JSON.stringify(prefs, null, 2), {
43294
+ mkdirSync18(path43.dirname(file2), { recursive: true });
43295
+ writeFileSync22(file2, JSON.stringify(prefs, null, 2), {
42720
43296
  encoding: "utf-8",
42721
43297
  mode: 384
42722
43298
  });
@@ -42751,7 +43327,7 @@ __export(registry_exports, {
42751
43327
  findPlugin: () => findPlugin,
42752
43328
  isBinaryOnPath: () => isBinaryOnPath
42753
43329
  });
42754
- import { existsSync as existsSync39 } from "node:fs";
43330
+ import { existsSync as existsSync41 } from "node:fs";
42755
43331
  import path44 from "node:path";
42756
43332
  function detectLocalBin(bin) {
42757
43333
  return (cwd) => {
@@ -42768,7 +43344,7 @@ function isBinaryOnPath(bin, opts = {}) {
42768
43344
  return false;
42769
43345
  }
42770
43346
  const platform = opts.platform ?? process.platform;
42771
- const exists = opts.exists ?? existsSync39;
43347
+ const exists = opts.exists ?? existsSync41;
42772
43348
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
42773
43349
  const pathMod = platform === "win32" ? path44.win32 : path44.posix;
42774
43350
  const sep2 = platform === "win32" ? ";" : ":";
@@ -43502,7 +44078,7 @@ __export(atMentions_exports, {
43502
44078
  extractAtMentions: () => extractAtMentions,
43503
44079
  hasAtMentions: () => hasAtMentions
43504
44080
  });
43505
- import { existsSync as existsSync42, readFileSync as readFileSync34, statSync as statSync7 } from "node:fs";
44081
+ import { existsSync as existsSync44, readFileSync as readFileSync36, statSync as statSync8 } from "node:fs";
43506
44082
  import { basename as basename3, isAbsolute as isAbsolute2, relative as relative3, resolve, sep } from "node:path";
43507
44083
  function isImagePath(abs) {
43508
44084
  const ext = abs.split(".").pop()?.toLowerCase() ?? "";
@@ -43556,7 +44132,7 @@ function resolveMention(token, cwd) {
43556
44132
  note: "outside project root \u2014 skipped"
43557
44133
  };
43558
44134
  }
43559
- if (!existsSync42(abs)) {
44135
+ if (!existsSync44(abs)) {
43560
44136
  return {
43561
44137
  raw: token,
43562
44138
  path: token,
@@ -43567,7 +44143,7 @@ function resolveMention(token, cwd) {
43567
44143
  }
43568
44144
  let st;
43569
44145
  try {
43570
- st = statSync7(abs);
44146
+ st = statSync8(abs);
43571
44147
  } catch {
43572
44148
  return {
43573
44149
  raw: token,
@@ -43608,7 +44184,7 @@ function resolveMention(token, cwd) {
43608
44184
  note: `image too large (${Math.round(st.size / 1024)} KB) \u2014 path only`
43609
44185
  };
43610
44186
  }
43611
- const dataBase64 = readFileSync34(abs).toString("base64");
44187
+ const dataBase64 = readFileSync36(abs).toString("base64");
43612
44188
  return {
43613
44189
  raw: token,
43614
44190
  path: rel2,
@@ -43619,7 +44195,7 @@ function resolveMention(token, cwd) {
43619
44195
  };
43620
44196
  }
43621
44197
  try {
43622
- const buf = readFileSync34(abs);
44198
+ const buf = readFileSync36(abs);
43623
44199
  const head = buf.subarray(0, 800).toString("utf8");
43624
44200
  if (!isProbablyText(abs, head)) {
43625
44201
  return {
@@ -44116,14 +44692,14 @@ var init_skillCategories = __esm({
44116
44692
 
44117
44693
  // src/cli/skillConfigIo.ts
44118
44694
  import {
44119
- existsSync as existsSync44,
44120
- mkdirSync as mkdirSync20,
44121
- readdirSync as readdirSync9,
44122
- readFileSync as readFileSync36,
44695
+ existsSync as existsSync46,
44696
+ mkdirSync as mkdirSync21,
44697
+ readdirSync as readdirSync10,
44698
+ readFileSync as readFileSync38,
44123
44699
  rmSync as rmSync4,
44124
- writeFileSync as writeFileSync22
44700
+ writeFileSync as writeFileSync24
44125
44701
  } from "node:fs";
44126
- import { dirname as dirname9, join as join35 } from "node:path";
44702
+ import { dirname as dirname10, join as join37 } from "node:path";
44127
44703
  import { homedir as homedir12 } from "node:os";
44128
44704
  function ensureBuiltinSkillsLoadedSync() {
44129
44705
  if (builtinsLoaded) return;
@@ -44135,13 +44711,13 @@ function ensureBuiltinSkillsLoadedSync() {
44135
44711
  }
44136
44712
  }
44137
44713
  function getUserSkillsDir() {
44138
- return join35(homedir12(), ".zelari-code", "skills");
44714
+ return join37(homedir12(), ".zelari-code", "skills");
44139
44715
  }
44140
44716
  function getProjectSkillsDir(projectRoot) {
44141
- return join35(projectRoot, ".zelari", "skills");
44717
+ return join37(projectRoot, ".zelari", "skills");
44142
44718
  }
44143
44719
  function skillFilePath(dir, name) {
44144
- return join35(dir, name, "SKILL.md");
44720
+ return join37(dir, name, "SKILL.md");
44145
44721
  }
44146
44722
  function classifyScope(skillPath, projectRoot) {
44147
44723
  const userDir = getUserSkillsDir().replace(/\\/g, "/");
@@ -44190,18 +44766,18 @@ function entryFromBuiltin(skill) {
44190
44766
  };
44191
44767
  }
44192
44768
  function scanSkillsDir(dir, projectRoot, seen, out) {
44193
- if (!existsSync44(dir)) return;
44769
+ if (!existsSync46(dir)) return;
44194
44770
  let entries;
44195
44771
  try {
44196
- entries = readdirSync9(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
44772
+ entries = readdirSync10(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
44197
44773
  } catch {
44198
44774
  return;
44199
44775
  }
44200
44776
  for (const entry of entries) {
44201
44777
  const skillPath = skillFilePath(dir, entry);
44202
- if (!existsSync44(skillPath)) continue;
44778
+ if (!existsSync46(skillPath)) continue;
44203
44779
  try {
44204
- const parsed = parseSkillMd(readFileSync36(skillPath, "utf8"), skillPath);
44780
+ const parsed = parseSkillMd(readFileSync38(skillPath, "utf8"), skillPath);
44205
44781
  if (!parsed) continue;
44206
44782
  if (seen.has(parsed.name)) continue;
44207
44783
  seen.add(parsed.name);
@@ -44217,9 +44793,9 @@ function listSkillsSnapshot(projectRoot) {
44217
44793
  const skills = [];
44218
44794
  const seen = /* @__PURE__ */ new Set();
44219
44795
  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);
44796
+ scanSkillsDir(join37(root, ".zelari", "skills"), root, seen, skills);
44797
+ scanSkillsDir(join37(root, ".claude", "skills"), root, seen, skills);
44798
+ scanSkillsDir(join37(root, ".opencode", "skills"), root, seen, skills);
44223
44799
  }
44224
44800
  scanSkillsDir(userSkillsDir, root, seen, skills);
44225
44801
  for (const s of listCodingSkills()) {
@@ -44298,8 +44874,8 @@ function upsertSkill(opts) {
44298
44874
  if (!parsed) {
44299
44875
  return { ok: false, error: "Generated SKILL.md failed validation" };
44300
44876
  }
44301
- mkdirSync20(dirname9(path53), { recursive: true });
44302
- writeFileSync22(path53, content, "utf8");
44877
+ mkdirSync21(dirname10(path53), { recursive: true });
44878
+ writeFileSync24(path53, content, "utf8");
44303
44879
  return { ok: true, path: path53 };
44304
44880
  }
44305
44881
  function removeSkill(opts) {
@@ -44317,9 +44893,9 @@ function removeSkill(opts) {
44317
44893
  }
44318
44894
  dir = getProjectSkillsDir(root);
44319
44895
  }
44320
- const skillDir = join35(dir, name);
44896
+ const skillDir = join37(dir, name);
44321
44897
  const path53 = skillFilePath(dir, name);
44322
- if (!existsSync44(path53) && !existsSync44(skillDir)) {
44898
+ if (!existsSync46(path53) && !existsSync46(skillDir)) {
44323
44899
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
44324
44900
  }
44325
44901
  try {
@@ -44624,36 +45200,36 @@ var init_permissionCli = __esm({
44624
45200
 
44625
45201
  // src/cli/companion/config.ts
44626
45202
  import {
44627
- existsSync as existsSync45,
44628
- mkdirSync as mkdirSync21,
44629
- readFileSync as readFileSync37,
44630
- writeFileSync as writeFileSync23
45203
+ existsSync as existsSync47,
45204
+ mkdirSync as mkdirSync22,
45205
+ readFileSync as readFileSync39,
45206
+ writeFileSync as writeFileSync25
44631
45207
  } from "node:fs";
44632
- import { join as join36 } from "node:path";
45208
+ import { join as join38 } from "node:path";
44633
45209
  import { homedir as homedir13 } from "node:os";
44634
45210
  import { createHash as createHash9, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
44635
45211
  function getZelariHome() {
44636
- return join36(homedir13(), ".zelari-code");
45212
+ return join38(homedir13(), ".zelari-code");
44637
45213
  }
44638
45214
  function getCompanionConfigPath() {
44639
- return join36(getZelariHome(), "companion.json");
45215
+ return join38(getZelariHome(), "companion.json");
44640
45216
  }
44641
45217
  function getCompanionTokenPath() {
44642
- return join36(getZelariHome(), "companion.token");
45218
+ return join38(getZelariHome(), "companion.token");
44643
45219
  }
44644
45220
  function ensureHome() {
44645
45221
  const home = getZelariHome();
44646
- if (!existsSync45(home)) {
44647
- mkdirSync21(home, { recursive: true });
45222
+ if (!existsSync47(home)) {
45223
+ mkdirSync22(home, { recursive: true });
44648
45224
  }
44649
45225
  }
44650
45226
  function loadCompanionConfig() {
44651
45227
  const path53 = getCompanionConfigPath();
44652
- if (!existsSync45(path53)) {
45228
+ if (!existsSync47(path53)) {
44653
45229
  return { projects: [] };
44654
45230
  }
44655
45231
  try {
44656
- const raw = JSON.parse(readFileSync37(path53, "utf8"));
45232
+ const raw = JSON.parse(readFileSync39(path53, "utf8"));
44657
45233
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
44658
45234
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
44659
45235
  ).map((p3) => ({
@@ -44672,7 +45248,7 @@ function loadCompanionConfig() {
44672
45248
  }
44673
45249
  function saveCompanionConfig(cfg) {
44674
45250
  ensureHome();
44675
- writeFileSync23(
45251
+ writeFileSync25(
44676
45252
  getCompanionConfigPath(),
44677
45253
  JSON.stringify(
44678
45254
  {
@@ -44692,12 +45268,12 @@ function loadOrCreateToken(explicit) {
44692
45268
  }
44693
45269
  ensureHome();
44694
45270
  const path53 = getCompanionTokenPath();
44695
- if (existsSync45(path53)) {
44696
- const t = readFileSync37(path53, "utf8").trim();
45271
+ if (existsSync47(path53)) {
45272
+ const t = readFileSync39(path53, "utf8").trim();
44697
45273
  if (t) return { token: t, created: false };
44698
45274
  }
44699
45275
  const token = randomBytes5(24).toString("base64url");
44700
- writeFileSync23(path53, token + "\n", "utf8");
45276
+ writeFileSync25(path53, token + "\n", "utf8");
44701
45277
  try {
44702
45278
  const fs31 = __require("node:fs");
44703
45279
  fs31.chmodSync?.(path53, 384);
@@ -44786,8 +45362,8 @@ var init_config = __esm({
44786
45362
  import { spawn as spawn13 } from "node:child_process";
44787
45363
  import { createInterface as createInterface2 } from "node:readline";
44788
45364
  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";
45365
+ import { writeFileSync as writeFileSync26, unlinkSync as unlinkSync3 } from "node:fs";
45366
+ import { join as join39 } from "node:path";
44791
45367
  import { tmpdir as tmpdir3 } from "node:os";
44792
45368
  var RunManager;
44793
45369
  var init_runManager = __esm({
@@ -44891,9 +45467,9 @@ var init_runManager = __esm({
44891
45467
  }
44892
45468
  let historyFile;
44893
45469
  if (args.history && Array.isArray(args.history) && args.history.length > 0) {
44894
- historyFile = join37(tmpdir3(), `zelari-companion-hist-${id}.json`);
45470
+ historyFile = join39(tmpdir3(), `zelari-companion-hist-${id}.json`);
44895
45471
  try {
44896
- writeFileSync24(historyFile, JSON.stringify(args.history), "utf8");
45472
+ writeFileSync26(historyFile, JSON.stringify(args.history), "utf8");
44897
45473
  argv.push("--history-file", historyFile);
44898
45474
  } catch {
44899
45475
  historyFile = void 0;
@@ -45018,7 +45594,7 @@ __export(serve_exports, {
45018
45594
  runCompanionServe: () => runCompanionServe
45019
45595
  });
45020
45596
  import { createServer as createServer3 } from "node:http";
45021
- import { existsSync as existsSync46 } from "node:fs";
45597
+ import { existsSync as existsSync48 } from "node:fs";
45022
45598
  import { resolve as resolve2 } from "node:path";
45023
45599
  function readBody(req, max = 2e6) {
45024
45600
  return new Promise((resolveBody, reject) => {
@@ -45066,7 +45642,7 @@ async function runCompanionServe(opts = {}) {
45066
45642
  let projects = mergeProjects(fileCfg, opts.projects ?? []);
45067
45643
  projects = projects.filter((p3) => {
45068
45644
  const abs = resolve2(p3.path);
45069
- if (!existsSync46(abs)) {
45645
+ if (!existsSync48(abs)) {
45070
45646
  process.stderr.write(
45071
45647
  `[zelari-code serve] skip missing project path: ${p3.path}
45072
45648
  `
@@ -45388,7 +45964,7 @@ __export(doctor_exports, {
45388
45964
  runDoctor: () => runDoctor
45389
45965
  });
45390
45966
  import { execSync as execSync2 } from "node:child_process";
45391
- import { existsSync as existsSync47, readFileSync as readFileSync38, readlinkSync, statSync as statSync8 } from "node:fs";
45967
+ import { existsSync as existsSync49, readFileSync as readFileSync40, readlinkSync, statSync as statSync9 } from "node:fs";
45392
45968
  import { createRequire as createRequire3 } from "node:module";
45393
45969
  import { fileURLToPath as fileURLToPath2 } from "node:url";
45394
45970
  import path51 from "node:path";
@@ -45396,9 +45972,9 @@ function findPackageRoot(start) {
45396
45972
  let dir = start;
45397
45973
  for (let i = 0; i < 6; i += 1) {
45398
45974
  const candidate = path51.join(dir, "package.json");
45399
- if (existsSync47(candidate)) {
45975
+ if (existsSync49(candidate)) {
45400
45976
  try {
45401
- const pkg = JSON.parse(readFileSync38(candidate, "utf8"));
45977
+ const pkg = JSON.parse(readFileSync40(candidate, "utf8"));
45402
45978
  if (pkg.name === "zelari-code") return dir;
45403
45979
  } catch {
45404
45980
  }
@@ -45422,7 +45998,7 @@ function tryExec(cmd) {
45422
45998
  function readPackageJson3() {
45423
45999
  try {
45424
46000
  const pkgPath = path51.join(packageRoot, "package.json");
45425
- return JSON.parse(readFileSync38(pkgPath, "utf8"));
46001
+ return JSON.parse(readFileSync40(pkgPath, "utf8"));
45426
46002
  } catch {
45427
46003
  return null;
45428
46004
  }
@@ -45438,16 +46014,16 @@ function checkShim(pkgName) {
45438
46014
  const isWin = process.platform === "win32";
45439
46015
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
45440
46016
  const shimPath = path51.join(prefix, shimName);
45441
- if (!existsSync47(shimPath)) {
46017
+ if (!existsSync49(shimPath)) {
45442
46018
  return FAIL(
45443
46019
  `shim not found at ${shimPath}
45444
46020
  fix: npm install -g ${pkgName}@latest --force`
45445
46021
  );
45446
46022
  }
45447
46023
  try {
45448
- const st = statSync8(shimPath);
46024
+ const st = statSync9(shimPath);
45449
46025
  if (isWin) {
45450
- const content = readFileSync38(shimPath, "utf8");
46026
+ const content = readFileSync40(shimPath, "utf8");
45451
46027
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
45452
46028
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
45453
46029
  }
@@ -45506,14 +46082,14 @@ function checkNode(pkg) {
45506
46082
  }
45507
46083
  function checkBundle() {
45508
46084
  const bundle = path51.join(packageRoot, "dist", "cli", "main.bundled.js");
45509
- if (!existsSync47(bundle)) {
46085
+ if (!existsSync49(bundle)) {
45510
46086
  return FAIL(
45511
46087
  `dist/cli/main.bundled.js missing at ${bundle}
45512
46088
  fix: npm run build:cli (then reinstall or run via tsx)`
45513
46089
  );
45514
46090
  }
45515
46091
  try {
45516
- const st = statSync8(bundle);
46092
+ const st = statSync9(bundle);
45517
46093
  return OK(`bundle OK (${(st.size / 1024 / 1024).toFixed(2)} MB)`);
45518
46094
  } catch (err) {
45519
46095
  return FAIL(
@@ -45887,7 +46463,7 @@ __export(inspect_exports, {
45887
46463
  runInspect: () => runInspect
45888
46464
  });
45889
46465
  import path52 from "node:path";
45890
- import { existsSync as existsSync48, readFileSync as readFileSync39, readdirSync as readdirSync10 } from "node:fs";
46466
+ import { existsSync as existsSync50, readFileSync as readFileSync41, readdirSync as readdirSync11 } from "node:fs";
45891
46467
  import { homedir as homedir14 } from "node:os";
45892
46468
  async function collectInspectReport(cwd = process.cwd()) {
45893
46469
  ensureBuiltinSkillsLoadedSync();
@@ -45920,11 +46496,11 @@ async function collectInspectReport(cwd = process.cwd()) {
45920
46496
  folders: listTrustedFolders()
45921
46497
  },
45922
46498
  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")) }
46499
+ { path: userMcpPath, exists: existsSync50(userMcpPath) },
46500
+ { path: projectMcpPath, exists: existsSync50(projectMcpPath) },
46501
+ { path: path52.join(homedir14(), ".zelari-code", "provider.json"), exists: existsSync50(path52.join(homedir14(), ".zelari-code", "provider.json")) },
46502
+ { path: path52.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync50(path52.join(cwd, ".zelari", "AGENTS.md")) },
46503
+ { path: path52.join(cwd, "AGENTS.md"), exists: existsSync50(path52.join(cwd, "AGENTS.md")) }
45928
46504
  ],
45929
46505
  skills: {
45930
46506
  total: snap.skills.length,
@@ -45937,7 +46513,7 @@ async function collectInspectReport(cwd = process.cwd()) {
45937
46513
  user: mcp.servers.filter((s) => s.scope === "user").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
45938
46514
  project: mcp.servers.filter((s) => s.scope === "project").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
45939
46515
  projectTrusted,
45940
- projectConfigExists: existsSync48(projectMcpPath)
46516
+ projectConfigExists: existsSync50(projectMcpPath)
45941
46517
  },
45942
46518
  hooks: {
45943
46519
  global: {
@@ -45957,7 +46533,7 @@ async function collectInspectReport(cwd = process.cwd()) {
45957
46533
  }
45958
46534
  function listJsonFiles(dir) {
45959
46535
  try {
45960
- return readdirSync10(dir).filter((f) => f.endsWith(".json")).sort();
46536
+ return readdirSync11(dir).filter((f) => f.endsWith(".json")).sort();
45961
46537
  } catch {
45962
46538
  return [];
45963
46539
  }
@@ -45969,9 +46545,9 @@ function findAgentsMd(cwd) {
45969
46545
  ];
45970
46546
  const found = [];
45971
46547
  for (const c of candidates) {
45972
- if (existsSync48(c)) {
46548
+ if (existsSync50(c)) {
45973
46549
  try {
45974
- const text = readFileSync39(c, "utf8");
46550
+ const text = readFileSync41(c, "utf8");
45975
46551
  found.push(`${c} (${text.length} bytes)`);
45976
46552
  } catch {
45977
46553
  found.push(`${c} (unreadable)`);
@@ -53165,7 +53741,7 @@ async function handlePromoteMember(ctx, memberId) {
53165
53741
  }
53166
53742
 
53167
53743
  // 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";
53744
+ import { promises as fs26, existsSync as existsSync42, readFileSync as readFileSync35, writeFileSync as writeFileSync23, mkdirSync as mkdirSync19, statSync as statSync6, rmSync as rmSync3 } from "node:fs";
53169
53745
  import path46 from "node:path";
53170
53746
  import os12 from "node:os";
53171
53747
  var META_FILENAME = "meta.json";
@@ -53187,11 +53763,11 @@ function sessionsPathFor(name, baseDir) {
53187
53763
  }
53188
53764
  function readBranchMeta(name, baseDir) {
53189
53765
  const metaPath = metaPathFor(name, baseDir);
53190
- if (!existsSync40(metaPath)) {
53766
+ if (!existsSync42(metaPath)) {
53191
53767
  throw new BranchNotFoundError(`Branch "${name}" not found`);
53192
53768
  }
53193
53769
  try {
53194
- const raw = readFileSync33(metaPath, "utf-8");
53770
+ const raw = readFileSync35(metaPath, "utf-8");
53195
53771
  const parsed = JSON.parse(raw);
53196
53772
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
53197
53773
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -53208,8 +53784,8 @@ function readBranchMeta(name, baseDir) {
53208
53784
  }
53209
53785
  function writeBranchMeta(name, baseDir, meta3) {
53210
53786
  const metaPath = metaPathFor(name, baseDir);
53211
- mkdirSync18(path46.dirname(metaPath), { recursive: true });
53212
- writeFileSync21(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
53787
+ mkdirSync19(path46.dirname(metaPath), { recursive: true });
53788
+ writeFileSync23(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
53213
53789
  }
53214
53790
  async function countSessions(name, baseDir) {
53215
53791
  const sessionsPath = sessionsPathFor(name, baseDir);
@@ -53247,7 +53823,7 @@ var SessionNotFoundError = class extends Error {
53247
53823
  };
53248
53824
  function branchExists(name, baseDir = getBranchesBaseDir()) {
53249
53825
  const bp = branchPathFor(name, baseDir);
53250
- return existsSync40(bp) && existsSync40(metaPathFor(name, baseDir));
53826
+ return existsSync42(bp) && existsSync42(metaPathFor(name, baseDir));
53251
53827
  }
53252
53828
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
53253
53829
  if (!name || name.trim().length === 0) {
@@ -53260,12 +53836,12 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
53260
53836
  throw new BranchAlreadyExistsError(name);
53261
53837
  }
53262
53838
  const sourcePath = path46.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
53263
- if (!existsSync40(sourcePath)) {
53839
+ if (!existsSync42(sourcePath)) {
53264
53840
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
53265
53841
  }
53266
53842
  const branchPath = branchPathFor(name, baseDir);
53267
53843
  const branchSessionsPath = sessionsPathFor(name, baseDir);
53268
- mkdirSync18(branchSessionsPath, { recursive: true });
53844
+ mkdirSync19(branchSessionsPath, { recursive: true });
53269
53845
  const destPath = path46.join(branchSessionsPath, `${fromSessionId}.jsonl`);
53270
53846
  await fs26.copyFile(sourcePath, destPath);
53271
53847
  const meta3 = {
@@ -53293,7 +53869,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
53293
53869
  const results = [];
53294
53870
  for (const entry of entries) {
53295
53871
  const metaPath = metaPathFor(entry, baseDir);
53296
- if (!existsSync40(metaPath)) continue;
53872
+ if (!existsSync42(metaPath)) continue;
53297
53873
  try {
53298
53874
  const meta3 = readBranchMeta(entry, baseDir);
53299
53875
  const sessionCount = await countSessions(entry, baseDir);
@@ -53484,7 +54060,7 @@ import path48 from "node:path";
53484
54060
  import os13 from "node:os";
53485
54061
 
53486
54062
  // 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";
54063
+ import { promises as fs28, existsSync as existsSync43, statSync as statSync7, renameSync as renameSync5, appendFileSync as appendFileSync4, mkdirSync as mkdirSync20 } from "node:fs";
53488
54064
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
53489
54065
  async function readSkillHistory(file2) {
53490
54066
  let raw = "";
@@ -54812,9 +55388,9 @@ function ContinueKey({ onContinue }) {
54812
55388
  init_providerConfig();
54813
55389
 
54814
55390
  // src/cli/wizard/firstRun.ts
54815
- import { existsSync as existsSync43 } from "node:fs";
55391
+ import { existsSync as existsSync45 } from "node:fs";
54816
55392
  function shouldRunWizard(input) {
54817
- const exists = input.exists ?? existsSync43;
55393
+ const exists = input.exists ?? existsSync45;
54818
55394
  if (input.hasResetConfigFlag) {
54819
55395
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
54820
55396
  }
@@ -55099,7 +55675,7 @@ init_keyStore();
55099
55675
  init_providerConfig();
55100
55676
  init_openai_compatible();
55101
55677
  init_phase();
55102
- import { readFileSync as readFileSync35 } from "node:fs";
55678
+ import { readFileSync as readFileSync37 } from "node:fs";
55103
55679
  function parseHeadlessFlags(argv) {
55104
55680
  if (!argv.includes("--headless")) {
55105
55681
  return { options: null };
@@ -55172,7 +55748,7 @@ function parseHeadlessFlags(argv) {
55172
55748
  let raw = null;
55173
55749
  if (arg === "--history-file") {
55174
55750
  try {
55175
- raw = readFileSync35(next, "utf-8");
55751
+ raw = readFileSync37(next, "utf-8");
55176
55752
  } catch {
55177
55753
  raw = null;
55178
55754
  }
@@ -55659,6 +56235,20 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
55659
56235
  const sessionId = crypto.randomUUID();
55660
56236
  const { registry: toolRegistry } = createBuiltinToolRegistry({
55661
56237
  planMode: planModeFromOpts(opts),
56238
+ // ADR-0018 3b: upgrade plan-task domain events to first-class NDJSON
56239
+ // BrainEvents. Rust envelopes every stdout line with runId/conversationId,
56240
+ // so task events ride the same multiplexed channel as the rest.
56241
+ onTaskEvent: (ev) => {
56242
+ if (opts.output !== "json") return;
56243
+ emitEvent({
56244
+ type: ev.type,
56245
+ id: crypto.randomUUID(),
56246
+ ts: Date.now(),
56247
+ sessionId,
56248
+ source: ev.source,
56249
+ ...ev.type === "task_update" ? { task: ev.task } : { tasks: ev.tasks }
56250
+ });
56251
+ },
55662
56252
  permissionPolicy: {
55663
56253
  read: "allow",
55664
56254
  write: "allow",