zelari-code 1.41.0 → 1.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cli/budget/llmCompact.js +97 -83
  2. package/dist/cli/budget/llmCompact.js.map +1 -1
  3. package/dist/cli/budget/requestMeter.js +138 -0
  4. package/dist/cli/budget/requestMeter.js.map +1 -0
  5. package/dist/cli/budget/requestSnapshotStore.js +55 -0
  6. package/dist/cli/budget/requestSnapshotStore.js.map +1 -0
  7. package/dist/cli/budget/tokenBudget.js +147 -15
  8. package/dist/cli/budget/tokenBudget.js.map +1 -1
  9. package/dist/cli/hooks/conversationContext.js +4 -0
  10. package/dist/cli/hooks/conversationContext.js.map +1 -1
  11. package/dist/cli/hooks/historyCompaction.js +76 -21
  12. package/dist/cli/hooks/historyCompaction.js.map +1 -1
  13. package/dist/cli/hooks/useChatTurn.js +90 -23
  14. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  15. package/dist/cli/main.bundled.js +1555 -890
  16. package/dist/cli/main.bundled.js.map +4 -4
  17. package/dist/cli/provider/openai-compatible.js +11 -1
  18. package/dist/cli/provider/openai-compatible.js.map +1 -1
  19. package/dist/cli/runHeadless.js +15 -0
  20. package/dist/cli/runHeadless.js.map +1 -1
  21. package/dist/cli/toolRegistry.js +16 -0
  22. package/dist/cli/toolRegistry.js.map +1 -1
  23. package/dist/cli/tools/planTaskTools.js +236 -0
  24. package/dist/cli/tools/planTaskTools.js.map +1 -0
  25. package/dist/cli/workspace/planStore.js +213 -0
  26. package/dist/cli/workspace/planStore.js.map +1 -0
  27. package/dist/cli/workspace/planStore.test.js +295 -0
  28. package/dist/cli/workspace/planStore.test.js.map +1 -0
  29. package/dist/cli/workspace/stubs.js +14 -4
  30. package/dist/cli/workspace/stubs.js.map +1 -1
  31. package/dist/cli/workspace/workspaceSummary.js +7 -1
  32. package/dist/cli/workspace/workspaceSummary.js.map +1 -1
  33. package/package.json +2 -2
@@ -1468,27 +1468,27 @@ function readStore() {
1468
1468
  }
1469
1469
  return { providers: {} };
1470
1470
  }
1471
- function writeStore(store3) {
1471
+ function writeStore(store4) {
1472
1472
  const file2 = getKeyStorePath();
1473
1473
  mkdirSync2(path3.dirname(file2), { recursive: true });
1474
- writeFileSync2(file2, JSON.stringify(store3, null, 2), { encoding: "utf-8", mode: 384 });
1474
+ writeFileSync2(file2, JSON.stringify(store4, null, 2), { encoding: "utf-8", mode: 384 });
1475
1475
  }
1476
1476
  function setApiKey(providerId, key) {
1477
- const store3 = readStore();
1478
- store3.providers[providerId] = { apiKey: key };
1479
- writeStore(store3);
1477
+ const store4 = readStore();
1478
+ store4.providers[providerId] = { apiKey: key };
1479
+ writeStore(store4);
1480
1480
  }
1481
1481
  function clearApiKey(providerId) {
1482
- const store3 = readStore();
1483
- delete store3.providers[providerId];
1484
- writeStore(store3);
1482
+ const store4 = readStore();
1483
+ delete store4.providers[providerId];
1484
+ writeStore(store4);
1485
1485
  }
1486
1486
  function getStoredApiKey(providerId) {
1487
- const store3 = readStore();
1488
- return store3.providers[providerId]?.apiKey ?? null;
1487
+ const store4 = readStore();
1488
+ return store4.providers[providerId]?.apiKey ?? null;
1489
1489
  }
1490
1490
  function setOAuthToken(providerId, token) {
1491
- const store3 = readStore();
1491
+ const store4 = readStore();
1492
1492
  const entry = { apiKey: token.apiKey };
1493
1493
  if (typeof token.expiresAt === "number" && Number.isFinite(token.expiresAt)) {
1494
1494
  entry.expiresAt = token.expiresAt;
@@ -1502,12 +1502,12 @@ function setOAuthToken(providerId, token) {
1502
1502
  if (typeof token.idToken === "string" && token.idToken.length > 0) {
1503
1503
  entry.idToken = token.idToken;
1504
1504
  }
1505
- store3.providers[providerId] = entry;
1506
- writeStore(store3);
1505
+ store4.providers[providerId] = entry;
1506
+ writeStore(store4);
1507
1507
  }
1508
1508
  function getOAuthToken(providerId) {
1509
- const store3 = readStore();
1510
- return store3.providers[providerId] ?? null;
1509
+ const store4 = readStore();
1510
+ return store4.providers[providerId] ?? null;
1511
1511
  }
1512
1512
  function resolveApiKey(providerId) {
1513
1513
  const spec = getProviderSpec(providerId);
@@ -1566,8 +1566,8 @@ async function forceRefreshOAuth(providerId, options = {}) {
1566
1566
  function readStoreDirect() {
1567
1567
  return readStore();
1568
1568
  }
1569
- function writeStoreDirect(store3) {
1570
- writeStore(store3);
1569
+ function writeStoreDirect(store4) {
1570
+ writeStore(store4);
1571
1571
  }
1572
1572
  function maskKey(key) {
1573
1573
  if (key.length <= 12) return "****";
@@ -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,
@@ -21214,6 +21220,79 @@ var init_types = __esm({
21214
21220
  }
21215
21221
  });
21216
21222
 
21223
+ // packages/core/dist/core/requestSnapshot.js
21224
+ import { createHash as createHash2 } from "node:crypto";
21225
+ function stableStringify(value) {
21226
+ if (value === null || typeof value !== "object")
21227
+ return JSON.stringify(value) ?? "null";
21228
+ if (Array.isArray(value)) {
21229
+ const items = value.map((v) => stableStringify(v));
21230
+ return `[${items.join(",")}]`;
21231
+ }
21232
+ const obj = value;
21233
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
21234
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
21235
+ return `{${parts.join(",")}}`;
21236
+ }
21237
+ function sha256Hex(input) {
21238
+ return createHash2("sha256").update(input, "utf8").digest("hex").slice(0, 32);
21239
+ }
21240
+ function cloneMessages(messages) {
21241
+ return structuredClone(messages);
21242
+ }
21243
+ function canonicalTools(tools) {
21244
+ return [...tools].sort((a, b) => a.name.localeCompare(b.name));
21245
+ }
21246
+ function createRoutedRequestSnapshot(params) {
21247
+ let split = 0;
21248
+ while (split < params.messages.length && params.messages[split].role === "system") {
21249
+ split++;
21250
+ }
21251
+ const systemMessages = cloneMessages(params.messages.slice(0, split));
21252
+ const conversation = cloneMessages(params.messages.slice(split));
21253
+ const tools = canonicalTools(params.tools).map((t) => structuredClone(t));
21254
+ const header = stableStringify({
21255
+ provider: params.provider,
21256
+ model: params.model,
21257
+ systemMessages,
21258
+ tools
21259
+ });
21260
+ const request = stableStringify({
21261
+ provider: params.provider,
21262
+ model: params.model,
21263
+ systemMessages,
21264
+ tools,
21265
+ conversation
21266
+ });
21267
+ return {
21268
+ provider: params.provider,
21269
+ model: params.model,
21270
+ systemMessages,
21271
+ conversation,
21272
+ tools,
21273
+ headerFingerprint: sha256Hex(header),
21274
+ requestFingerprint: sha256Hex(request),
21275
+ createdAt: Date.now()
21276
+ };
21277
+ }
21278
+ function compareReplayPrefix(snapshot, messages) {
21279
+ const base = snapshot.conversation;
21280
+ const n = Math.min(base.length, messages.length);
21281
+ let matching = 0;
21282
+ for (let i = 0; i < n; i++) {
21283
+ if (stableStringify(base[i]) !== stableStringify(messages[i])) {
21284
+ return { exact: false, matchingMessages: matching, mismatchIndex: i };
21285
+ }
21286
+ matching++;
21287
+ }
21288
+ return { exact: true, matchingMessages: matching };
21289
+ }
21290
+ var init_requestSnapshot = __esm({
21291
+ "packages/core/dist/core/requestSnapshot.js"() {
21292
+ "use strict";
21293
+ }
21294
+ });
21295
+
21217
21296
  // packages/core/dist/core/textLoopDetect.js
21218
21297
  function isStatusTheaterUnit(unit) {
21219
21298
  const u = normalizeLoopUnit(unit).toLowerCase();
@@ -21412,10 +21491,10 @@ var init_textLoopDetect = __esm({
21412
21491
 
21413
21492
  // packages/core/dist/core/AgentHarness.js
21414
21493
  function hashToolCall(toolName, args) {
21415
- const canonical = stableStringify(args);
21494
+ const canonical = stableStringify2(args);
21416
21495
  return `${toolName}::${canonical}`;
21417
21496
  }
21418
- function stableStringify(value) {
21497
+ function stableStringify2(value) {
21419
21498
  return JSON.stringify(value, (_k, v) => {
21420
21499
  if (v && typeof v === "object" && !Array.isArray(v)) {
21421
21500
  const sorted = {};
@@ -21648,6 +21727,7 @@ var init_AgentHarness = __esm({
21648
21727
  "packages/core/dist/core/AgentHarness.js"() {
21649
21728
  "use strict";
21650
21729
  init_events();
21730
+ init_requestSnapshot();
21651
21731
  init_textLoopDetect();
21652
21732
  init_textLoopDetect();
21653
21733
  AgentHarness = class {
@@ -22084,6 +22164,23 @@ ${shared2.content}`,
22084
22164
  yield agentEnd;
22085
22165
  this.activeController = null;
22086
22166
  }
22167
+ /**
22168
+ * v1.36.0: capture a deterministic snapshot of the routed request just
22169
+ * before it goes out. Never throws into the request path.
22170
+ */
22171
+ emitSnapshot(tools, generation) {
22172
+ if (!this.config.onRequestSnapshot)
22173
+ return;
22174
+ try {
22175
+ this.config.onRequestSnapshot(createRoutedRequestSnapshot({
22176
+ messages: this.config.messages,
22177
+ model: this.config.model,
22178
+ provider: this.config.provider,
22179
+ tools
22180
+ }), generation);
22181
+ } catch {
22182
+ }
22183
+ }
22087
22184
  /**
22088
22185
  * Run a single provider turn for the current message buffer.
22089
22186
  * Streams from the provider, dispatches deltas to events, executes
@@ -22101,6 +22198,7 @@ ${shared2.content}`,
22101
22198
  */
22102
22199
  async *runSingleTurn(messageId, finishRef, usageRef) {
22103
22200
  try {
22201
+ this.emitSnapshot(this.config.tools);
22104
22202
  const stream = this.config.providerStream({
22105
22203
  messages: this.config.messages,
22106
22204
  model: this.config.model,
@@ -22391,6 +22489,7 @@ ${cached2}`
22391
22489
  const finishRef = { value: "stop" };
22392
22490
  const usageRef = { value: null };
22393
22491
  try {
22492
+ this.emitSnapshot([]);
22394
22493
  const stream = this.config.providerStream({
22395
22494
  messages: this.config.messages,
22396
22495
  model: this.config.model,
@@ -22971,7 +23070,10 @@ __export(harness_exports, {
22971
23070
  SessionJsonlWriter: () => SessionJsonlWriter,
22972
23071
  TEXT_LOOP_RECOVERY_SYSTEM: () => TEXT_LOOP_RECOVERY_SYSTEM,
22973
23072
  TEXT_LOOP_RECOVERY_USER_PROMPT: () => TEXT_LOOP_RECOVERY_USER_PROMPT,
23073
+ canonicalTools: () => canonicalTools,
22974
23074
  collapseLoopedAssistantText: () => collapseLoopedAssistantText,
23075
+ compareReplayPrefix: () => compareReplayPrefix,
23076
+ createRoutedRequestSnapshot: () => createRoutedRequestSnapshot,
22975
23077
  detectAssistantTextLoop: () => detectAssistantTextLoop,
22976
23078
  detectAssistantTextLoopWindow: () => detectAssistantTextLoopWindow,
22977
23079
  hashToolCall: () => hashToolCall,
@@ -22983,6 +23085,8 @@ __export(harness_exports, {
22983
23085
  parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
22984
23086
  parseTextToolCalls: () => parseTextToolCalls,
22985
23087
  readSession: () => readSession,
23088
+ sha256Hex: () => sha256Hex,
23089
+ stableStringify: () => stableStringify,
22986
23090
  toolMatches: () => toolMatches,
22987
23091
  wrapLegacyStream: () => wrapLegacyStream
22988
23092
  });
@@ -22991,6 +23095,7 @@ var init_harness = __esm({
22991
23095
  "use strict";
22992
23096
  init_AgentHarness();
22993
23097
  init_providerStream();
23098
+ init_requestSnapshot();
22994
23099
  init_sessionJsonl();
22995
23100
  init_hooks();
22996
23101
  }
@@ -27902,6 +28007,7 @@ __export(dist_exports, {
27902
28007
  buildSystemPrompt: () => buildSystemPrompt,
27903
28008
  buildSystemPromptSplit: () => buildSystemPromptSplit,
27904
28009
  canRunParallel: () => canRunParallel,
28010
+ canonicalTools: () => canonicalTools,
27905
28011
  captureFailure: () => captureFailure,
27906
28012
  checkImplementationCompletion: () => checkImplementationCompletion,
27907
28013
  checkImplementationDelivery: () => checkImplementationDelivery,
@@ -27913,6 +28019,7 @@ __export(dist_exports, {
27913
28019
  clearCustomTools: () => clearCustomTools,
27914
28020
  cliToolToEnhanced: () => cliToolToEnhanced,
27915
28021
  collapseLoopedAssistantText: () => collapseLoopedAssistantText,
28022
+ compareReplayPrefix: () => compareReplayPrefix,
27916
28023
  computeAgentSkills: () => computeAgentSkills,
27917
28024
  computeAgentTools: () => computeAgentTools,
27918
28025
  councilModeBanner: () => councilModeBanner,
@@ -27922,6 +28029,7 @@ __export(dist_exports, {
27922
28029
  createBrainEvent: () => createBrainEvent,
27923
28030
  createDefaultSystemPromptConfig: () => createDefaultSystemPromptConfig,
27924
28031
  createGraph: () => createGraph,
28032
+ createRoutedRequestSnapshot: () => createRoutedRequestSnapshot,
27925
28033
  defaultPersonaParse: () => defaultPersonaParse,
27926
28034
  detectAssistantTextLoop: () => detectAssistantTextLoop,
27927
28035
  detectAssistantTextLoopWindow: () => detectAssistantTextLoopWindow,
@@ -27971,6 +28079,8 @@ __export(dist_exports, {
27971
28079
  isBrainMessageStartEvent: () => isBrainMessageStartEvent,
27972
28080
  isBrainQueueUpdateEvent: () => isBrainQueueUpdateEvent,
27973
28081
  isBrainSessionCompactedEvent: () => isBrainSessionCompactedEvent,
28082
+ isBrainTaskSnapshotEvent: () => isBrainTaskSnapshotEvent,
28083
+ isBrainTaskUpdateEvent: () => isBrainTaskUpdateEvent,
27974
28084
  isBrainThinkingDeltaEvent: () => isBrainThinkingDeltaEvent,
27975
28085
  isBrainToolExecutionEndEvent: () => isBrainToolExecutionEndEvent,
27976
28086
  isBrainToolExecutionStartEvent: () => isBrainToolExecutionStartEvent,
@@ -28038,9 +28148,11 @@ __export(dist_exports, {
28038
28148
  scrubProprietaryLeak: () => scrubProprietaryLeak,
28039
28149
  selectParallelWave: () => selectParallelWave,
28040
28150
  setWorkspaceStubs: () => setWorkspaceStubs,
28151
+ sha256Hex: () => sha256Hex,
28041
28152
  shouldRetryMember: () => shouldRetryMember,
28042
28153
  slugify: () => slugify2,
28043
28154
  specificityFromAssumptions: () => specificityFromAssumptions,
28155
+ stableStringify: () => stableStringify,
28044
28156
  stripClarificationProtocol: () => stripClarificationProtocol,
28045
28157
  swapMembers: () => swapMembers,
28046
28158
  systemMessagesFromSplit: () => systemMessagesFromSplit,
@@ -28403,6 +28515,7 @@ function openaiCompatibleProvider(config2) {
28403
28515
  if (cacheable) messageMappingCache.set(m, mapped);
28404
28516
  return mapped;
28405
28517
  });
28518
+ const generation = params.generation;
28406
28519
  const body = {
28407
28520
  // Use `params.model` (per-call override from AgentHarness, e.g. for
28408
28521
  // `agentModels` config) rather than the closed-over `config.model`
@@ -28410,7 +28523,7 @@ function openaiCompatibleProvider(config2) {
28410
28523
  model: params.model,
28411
28524
  messages,
28412
28525
  stream: true,
28413
- temperature: 0.7,
28526
+ temperature: generation?.temperature ?? 0.7,
28414
28527
  // Task G.4.2 — request the provider to send real token usage in
28415
28528
  // the final chunk (gated by `stream_options.include_usage` on the
28416
28529
  // OpenAI-compatible API). Providers that don't honor this (some
@@ -28418,6 +28531,9 @@ function openaiCompatibleProvider(config2) {
28418
28531
  // the harness will fall back to the ~4-char/token approximation.
28419
28532
  stream_options: { include_usage: true }
28420
28533
  };
28534
+ if (typeof generation?.maxTokens === "number" && generation.maxTokens > 0) {
28535
+ body.max_tokens = generation.maxTokens;
28536
+ }
28421
28537
  const thinkingSpec = config2.thinking ?? "auto";
28422
28538
  if (config2.providerId === "deepseek" && thinkingSpec === "auto") {
28423
28539
  const thinking = resolveDeepSeekThinking();
@@ -29312,7 +29428,7 @@ var init_resolveStream = __esm({
29312
29428
  });
29313
29429
 
29314
29430
  // packages/core/dist/core/tools/toolOutputSpill.js
29315
- import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
29431
+ import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
29316
29432
  import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
29317
29433
  import { homedir as homedir3, tmpdir } from "node:os";
29318
29434
  import { join as join11 } from "node:path";
@@ -29342,7 +29458,7 @@ function spillToolOutput(fullText, meta3) {
29342
29458
  if (!existsSync12(dir)) {
29343
29459
  mkdirSync7(dir, { recursive: true });
29344
29460
  }
29345
- const hash3 = createHash2("sha256").update(fullText).digest("hex").slice(0, 12);
29461
+ const hash3 = createHash3("sha256").update(fullText).digest("hex").slice(0, 12);
29346
29462
  const stamp = Date.now().toString(36);
29347
29463
  const rnd = randomBytes2(3).toString("hex");
29348
29464
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
@@ -30918,6 +31034,771 @@ var init_todoTools = __esm({
30918
31034
  }
30919
31035
  });
30920
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
+
30921
31802
  // src/cli/lsp/protocol.ts
30922
31803
  function encodeMessage(message) {
30923
31804
  const json2 = JSON.stringify(message);
@@ -31221,7 +32102,7 @@ var init_servers = __esm({
31221
32102
 
31222
32103
  // src/cli/lsp/manager.ts
31223
32104
  import { spawn as spawn4 } from "node:child_process";
31224
- import { readFileSync as readFileSync16 } from "node:fs";
32105
+ import { readFileSync as readFileSync18 } from "node:fs";
31225
32106
  function processTransport(child) {
31226
32107
  return {
31227
32108
  send: (data) => {
@@ -31424,7 +32305,7 @@ var init_manager = __esm({
31424
32305
  const uri = pathToUri(file2);
31425
32306
  let text;
31426
32307
  try {
31427
- text = readFileSync16(file2, "utf8");
32308
+ text = readFileSync18(file2, "utf8");
31428
32309
  } catch {
31429
32310
  text = "";
31430
32311
  }
@@ -31746,13 +32627,13 @@ var init_store = __esm({
31746
32627
  });
31747
32628
 
31748
32629
  // src/cli/semantic/index.ts
31749
- import { promises as fs12, existsSync as existsSync17, readFileSync as readFileSync17 } from "node:fs";
31750
- 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";
31751
32632
  import path23 from "node:path";
31752
- import { createHash as createHash3 } from "node:crypto";
32633
+ import { createHash as createHash5 } from "node:crypto";
31753
32634
  function getIndexPath(root) {
31754
- const hash3 = createHash3("sha1").update(path23.resolve(root)).digest("hex").slice(0, 16);
31755
- 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`);
31756
32637
  }
31757
32638
  async function collectSourceFiles(root, maxFiles = 1500) {
31758
32639
  const out = [];
@@ -31830,9 +32711,9 @@ async function saveIndex(root, data) {
31830
32711
  }
31831
32712
  function loadIndex(root) {
31832
32713
  const file2 = getIndexPath(root);
31833
- if (!existsSync17(file2)) return null;
32714
+ if (!existsSync20(file2)) return null;
31834
32715
  try {
31835
- const parsed = JSON.parse(readFileSync17(file2, "utf8"));
32716
+ const parsed = JSON.parse(readFileSync19(file2, "utf8"));
31836
32717
  if (parsed && Array.isArray(parsed.chunks)) return parsed;
31837
32718
  } catch {
31838
32719
  }
@@ -32362,19 +33243,19 @@ __export(targets_exports, {
32362
33243
  });
32363
33244
  import {
32364
33245
  chmodSync,
32365
- existsSync as existsSync18,
32366
- mkdirSync as mkdirSync10,
32367
- readFileSync as readFileSync18,
32368
- writeFileSync as writeFileSync12
33246
+ existsSync as existsSync21,
33247
+ mkdirSync as mkdirSync13,
33248
+ readFileSync as readFileSync20,
33249
+ writeFileSync as writeFileSync15
32369
33250
  } from "node:fs";
32370
- import { dirname as dirname2, join as join13 } from "node:path";
32371
- 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";
32372
33253
  import { spawn as spawn5 } from "node:child_process";
32373
33254
  function getSshTargetsPath() {
32374
- return join13(homedir6(), ".zelari-code", "ssh-targets.json");
33255
+ return join16(homedir7(), ".zelari-code", "ssh-targets.json");
32375
33256
  }
32376
33257
  function getSshSecretsPath() {
32377
- return join13(homedir6(), ".zelari-code", "ssh-secrets.json");
33258
+ return join16(homedir7(), ".zelari-code", "ssh-secrets.json");
32378
33259
  }
32379
33260
  function normalizeAuth(auth) {
32380
33261
  if (auth === "keyPath") return "keyPath";
@@ -32383,17 +33264,17 @@ function normalizeAuth(auth) {
32383
33264
  }
32384
33265
  function readSecrets() {
32385
33266
  const path53 = getSshSecretsPath();
32386
- if (!existsSync18(path53)) return {};
33267
+ if (!existsSync21(path53)) return {};
32387
33268
  try {
32388
- return JSON.parse(readFileSync18(path53, "utf8"));
33269
+ return JSON.parse(readFileSync20(path53, "utf8"));
32389
33270
  } catch {
32390
33271
  return {};
32391
33272
  }
32392
33273
  }
32393
33274
  function writeSecrets(data) {
32394
33275
  const path53 = getSshSecretsPath();
32395
- mkdirSync10(dirname2(path53), { recursive: true });
32396
- writeFileSync12(path53, `${JSON.stringify(data, null, 2)}
33276
+ mkdirSync13(dirname4(path53), { recursive: true });
33277
+ writeFileSync15(path53, `${JSON.stringify(data, null, 2)}
32397
33278
  `, "utf8");
32398
33279
  try {
32399
33280
  chmodSync(path53, 384);
@@ -32426,9 +33307,9 @@ function deleteSshPassword(id) {
32426
33307
  }
32427
33308
  function readStore2() {
32428
33309
  const path53 = getSshTargetsPath();
32429
- if (!existsSync18(path53)) return [];
33310
+ if (!existsSync21(path53)) return [];
32430
33311
  try {
32431
- const parsed = JSON.parse(readFileSync18(path53, "utf8"));
33312
+ const parsed = JSON.parse(readFileSync20(path53, "utf8"));
32432
33313
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
32433
33314
  return list.filter(
32434
33315
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -32444,9 +33325,9 @@ function readStore2() {
32444
33325
  }
32445
33326
  function writeStore2(targets) {
32446
33327
  const path53 = getSshTargetsPath();
32447
- mkdirSync10(dirname2(path53), { recursive: true });
33328
+ mkdirSync13(dirname4(path53), { recursive: true });
32448
33329
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
32449
- writeFileSync12(
33330
+ writeFileSync15(
32450
33331
  path53,
32451
33332
  `${JSON.stringify({ targets: clean }, null, 2)}
32452
33333
  `,
@@ -32547,17 +33428,17 @@ function buildSshBaseArgs(target) {
32547
33428
  return args;
32548
33429
  }
32549
33430
  function ensureAskpassHelper() {
32550
- const dir = join13(homedir6(), ".zelari-code", "ssh-helpers");
32551
- mkdirSync10(dir, { recursive: true });
32552
- const cjs = join13(dir, "askpass.cjs");
32553
- writeFileSync12(
33431
+ const dir = join16(homedir7(), ".zelari-code", "ssh-helpers");
33432
+ mkdirSync13(dir, { recursive: true });
33433
+ const cjs = join16(dir, "askpass.cjs");
33434
+ writeFileSync15(
32554
33435
  cjs,
32555
33436
  "process.stdout.write(process.env.ZELARI_SSH_ASKPASS_PASS || '');\n",
32556
33437
  "utf8"
32557
33438
  );
32558
33439
  if (process.platform === "win32") {
32559
- const cmd = join13(dir, "askpass.cmd");
32560
- writeFileSync12(
33440
+ const cmd = join16(dir, "askpass.cmd");
33441
+ writeFileSync15(
32561
33442
  cmd,
32562
33443
  `@echo off\r
32563
33444
  node "%~dp0askpass.cjs"\r
@@ -32566,8 +33447,8 @@ node "%~dp0askpass.cjs"\r
32566
33447
  );
32567
33448
  return cmd;
32568
33449
  }
32569
- const sh = join13(dir, "askpass.sh");
32570
- writeFileSync12(
33450
+ const sh = join16(dir, "askpass.sh");
33451
+ writeFileSync15(
32571
33452
  sh,
32572
33453
  `#!/bin/sh
32573
33454
  exec node "$(dirname "$0")/askpass.cjs"
@@ -32640,9 +33521,9 @@ function readSshPublicKey(keyOrPubPath) {
32640
33521
  if (!raw) return { ok: false, error: "Empty path" };
32641
33522
  const candidates = raw.endsWith(".pub") ? [raw] : [`${raw}.pub`, raw];
32642
33523
  for (const p3 of candidates) {
32643
- if (!existsSync18(p3)) continue;
33524
+ if (!existsSync21(p3)) continue;
32644
33525
  try {
32645
- const content = readFileSync18(p3, "utf8").trim();
33526
+ const content = readFileSync20(p3, "utf8").trim();
32646
33527
  if (!content) continue;
32647
33528
  if (/BEGIN .*PRIVATE KEY/i.test(content)) {
32648
33529
  return {
@@ -33212,11 +34093,11 @@ __export(folderTrust_exports, {
33212
34093
  trustFolder: () => trustFolder,
33213
34094
  untrustFolder: () => untrustFolder
33214
34095
  });
33215
- import { homedir as homedir7 } from "node:os";
33216
- 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";
33217
34098
  import path28 from "node:path";
33218
34099
  function trustStorePath() {
33219
- return _overrideStorePath ?? path28.join(homedir7(), ".zelari-code", "trust.json");
34100
+ return _overrideStorePath ?? path28.join(homedir8(), ".zelari-code", "trust.json");
33220
34101
  }
33221
34102
  function normalize(p3) {
33222
34103
  const resolved = path28.resolve(p3);
@@ -33224,7 +34105,7 @@ function normalize(p3) {
33224
34105
  }
33225
34106
  function readStore3() {
33226
34107
  try {
33227
- const raw = readFileSync19(trustStorePath(), "utf8");
34108
+ const raw = readFileSync21(trustStorePath(), "utf8");
33228
34109
  const parsed = JSON.parse(raw);
33229
34110
  if (parsed && Array.isArray(parsed.folders)) return parsed;
33230
34111
  return DEFAULT_STORE;
@@ -33232,11 +34113,11 @@ function readStore3() {
33232
34113
  return DEFAULT_STORE;
33233
34114
  }
33234
34115
  }
33235
- function writeStore3(store3) {
34116
+ function writeStore3(store4) {
33236
34117
  const p3 = trustStorePath();
33237
34118
  try {
33238
- mkdirSync11(path28.dirname(p3), { recursive: true });
33239
- writeFileSync13(p3, JSON.stringify(store3, null, 2), "utf8");
34119
+ mkdirSync14(path28.dirname(p3), { recursive: true });
34120
+ writeFileSync16(p3, JSON.stringify(store4, null, 2), "utf8");
33240
34121
  } catch (err) {
33241
34122
  throw new Error(
33242
34123
  `failed to persist trust store ${p3}: ${err instanceof Error ? err.message : String(err)}`
@@ -33260,21 +34141,21 @@ function isFolderTrusted(folderPath) {
33260
34141
  return readStore3().folders.some((f) => normalize(f.path) === target);
33261
34142
  }
33262
34143
  function trustFolder(folderPath) {
33263
- const store3 = readStore3();
34144
+ const store4 = readStore3();
33264
34145
  const normalized = path28.resolve(folderPath);
33265
- if (!store3.folders.some((f) => normalize(f.path) === normalize(normalized))) {
33266
- store3.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
33267
- writeStore3(store3);
34146
+ if (!store4.folders.some((f) => normalize(f.path) === normalize(normalized))) {
34147
+ store4.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
34148
+ writeStore3(store4);
33268
34149
  }
33269
34150
  return { ok: true, path: normalized };
33270
34151
  }
33271
34152
  function untrustFolder(folderPath) {
33272
- const store3 = readStore3();
34153
+ const store4 = readStore3();
33273
34154
  const target = normalize(folderPath);
33274
- const before = store3.folders.length;
33275
- store3.folders = store3.folders.filter((f) => normalize(f.path) !== target);
33276
- if (store3.folders.length === before) return { ok: true, removed: false };
33277
- writeStore3(store3);
34155
+ const before = store4.folders.length;
34156
+ store4.folders = store4.folders.filter((f) => normalize(f.path) !== target);
34157
+ if (store4.folders.length === before) return { ok: true, removed: false };
34158
+ writeStore3(store4);
33278
34159
  return { ok: true, removed: true };
33279
34160
  }
33280
34161
  function listTrustedFolders() {
@@ -33291,7 +34172,7 @@ function getTrustStorePath() {
33291
34172
  return trustStorePath();
33292
34173
  }
33293
34174
  function hasTrustStore() {
33294
- return existsSync19(trustStorePath());
34175
+ return existsSync22(trustStorePath());
33295
34176
  }
33296
34177
  function _setTrustStorePathForTests(p3) {
33297
34178
  _overrideStorePath = p3;
@@ -33306,21 +34187,21 @@ var init_folderTrust = __esm({
33306
34187
  });
33307
34188
 
33308
34189
  // src/cli/safety/lifecycleHooks.ts
33309
- import { homedir as homedir8 } from "node:os";
33310
- import { join as join14 } from "node:path";
33311
- 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";
33312
34193
  function globalHooksDir() {
33313
- return join14(homedir8(), ".zelari-code", "hooks");
34194
+ return join17(homedir9(), ".zelari-code", "hooks");
33314
34195
  }
33315
34196
  function projectHooksDir(projectRoot) {
33316
- return join14(projectRoot, ".zelari", "hooks");
34197
+ return join17(projectRoot, ".zelari", "hooks");
33317
34198
  }
33318
34199
  function fingerprintHookDirs(dirs) {
33319
34200
  const parts = [];
33320
34201
  for (const dir of dirs) {
33321
34202
  let names;
33322
34203
  try {
33323
- names = readdirSync4(dir).filter((f) => f.endsWith(".json")).sort();
34204
+ names = readdirSync5(dir).filter((f) => f.endsWith(".json")).sort();
33324
34205
  } catch {
33325
34206
  parts.push(`${dir}:missing`);
33326
34207
  continue;
@@ -33334,7 +34215,7 @@ function fingerprintHookDirs(dirs) {
33334
34215
  continue;
33335
34216
  }
33336
34217
  for (const name of names) {
33337
- const full = join14(dir, name);
34218
+ const full = join17(dir, name);
33338
34219
  try {
33339
34220
  const st = statSync3(full);
33340
34221
  parts.push(`${full}:${st.mtimeMs}:${st.size}`);
@@ -33375,7 +34256,7 @@ var init_lifecycleHooks = __esm({
33375
34256
  });
33376
34257
 
33377
34258
  // src/cli/toolResultCache.ts
33378
- import { createHash as createHash4 } from "node:crypto";
34259
+ import { createHash as createHash6 } from "node:crypto";
33379
34260
  import { promises as fs14 } from "node:fs";
33380
34261
  import path29 from "node:path";
33381
34262
  function isToolCacheEnabled() {
@@ -33388,7 +34269,7 @@ function resolveToolCacheTtlMs() {
33388
34269
  return Number.isFinite(n) && n >= 0 ? n : TOOL_CACHE_DEFAULT_TTL_MS;
33389
34270
  }
33390
34271
  function hashKey(parts) {
33391
- return createHash4("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
34272
+ return createHash6("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
33392
34273
  }
33393
34274
  function resultBytes(result) {
33394
34275
  try {
@@ -33678,6 +34559,11 @@ function createBuiltinToolRegistry(options = {}) {
33678
34559
  const todoRead = enableTodos ? withPerm(createTodoReadTool()) : null;
33679
34560
  if (todoWrite) registry4.register(todoWrite);
33680
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
+ }
33681
34567
  const summary = [
33682
34568
  safeReadFile,
33683
34569
  safeGrepContent,
@@ -33690,7 +34576,8 @@ function createBuiltinToolRegistry(options = {}) {
33690
34576
  ...askUserTool ? [askUserTool] : [],
33691
34577
  ...skillTool ? [skillTool] : [],
33692
34578
  ...todoWrite ? [todoWrite] : [],
33693
- ...todoRead ? [todoRead] : []
34579
+ ...todoRead ? [todoRead] : [],
34580
+ ...planTaskToolsWrapped.length > 0 ? planTaskToolsWrapped : []
33694
34581
  ];
33695
34582
  const tools = summary.map((t) => ({
33696
34583
  name: t.name,
@@ -34039,6 +34926,7 @@ var init_toolRegistry = __esm({
34039
34926
  init_askUser();
34040
34927
  init_skillTool();
34041
34928
  init_todoTools();
34929
+ init_planTaskTools();
34042
34930
  init_tools2();
34043
34931
  init_manager();
34044
34932
  init_tools3();
@@ -34069,7 +34957,7 @@ var init_toolRegistry = __esm({
34069
34957
  });
34070
34958
 
34071
34959
  // src/cli/state/fileStateStore.ts
34072
- import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
34960
+ import { createHash as createHash7, randomUUID as randomUUID2 } from "node:crypto";
34073
34961
  import { promises as fs15 } from "node:fs";
34074
34962
  import * as path30 from "node:path";
34075
34963
  function shortId() {
@@ -34112,16 +35000,16 @@ function isStateEnabled(env = process.env) {
34112
35000
  }
34113
35001
  async function getStateStore(projectRoot, env = process.env) {
34114
35002
  if (!isStateEnabled(env)) return new NoopDurableStateStore();
34115
- const store3 = new FileDurableStateStore();
35003
+ const store4 = new FileDurableStateStore();
34116
35004
  try {
34117
- await store3.init(projectRoot);
34118
- return store3;
35005
+ await store4.init(projectRoot);
35006
+ return store4;
34119
35007
  } catch {
34120
35008
  return new NoopDurableStateStore();
34121
35009
  }
34122
35010
  }
34123
35011
  function hashStablePrompt(stable) {
34124
- return createHash5("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
35012
+ return createHash7("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
34125
35013
  }
34126
35014
  var DEFAULT_MATERIALIZE_CHARS, FileDurableStateStore, NoopDurableStateStore;
34127
35015
  var init_fileStateStore = __esm({
@@ -34452,29 +35340,6 @@ function extractiveHistorySummary(dropped, opts) {
34452
35340
  }
34453
35341
  return out;
34454
35342
  }
34455
- function formatDroppedForLlm(dropped) {
34456
- const lines = [];
34457
- for (const m of dropped) {
34458
- if (m.role === "user") {
34459
- lines.push(`USER: ${oneLine(m.content, 400)}`);
34460
- } else if (m.role === "assistant") {
34461
- const tools = m.toolCalls?.map((t) => t.name).join(",") || "";
34462
- const body2 = oneLine(m.content, 300);
34463
- lines.push(
34464
- tools ? `ASSISTANT(tools=${tools}): ${body2}` : `ASSISTANT: ${body2}`
34465
- );
34466
- } else if (m.role === "tool") {
34467
- lines.push(`TOOL(${m.toolCallId ?? "?"}): ${oneLine(m.content, 160)}`);
34468
- } else if (m.role === "system") {
34469
- lines.push(`SYSTEM: ${oneLine(m.content, 200)}`);
34470
- }
34471
- }
34472
- let body = lines.join("\n");
34473
- if (body.length > MAX_LLM_INPUT_CHARS) {
34474
- body = body.slice(body.length - MAX_LLM_INPUT_CHARS);
34475
- }
34476
- return body;
34477
- }
34478
35343
  function oneLine(s, max) {
34479
35344
  const t = s.replace(/\s+/g, " ").trim();
34480
35345
  if (t.length <= max) return t;
@@ -34500,12 +35365,11 @@ function collectPathsFromText(text, out) {
34500
35365
  n += 1;
34501
35366
  }
34502
35367
  }
34503
- var MAX_SUMMARY_CHARS, MAX_LLM_INPUT_CHARS;
35368
+ var MAX_SUMMARY_CHARS;
34504
35369
  var init_historySummary = __esm({
34505
35370
  "src/cli/budget/historySummary.ts"() {
34506
35371
  "use strict";
34507
35372
  MAX_SUMMARY_CHARS = 3500;
34508
- MAX_LLM_INPUT_CHARS = 24e3;
34509
35373
  }
34510
35374
  });
34511
35375
 
@@ -34515,92 +35379,87 @@ function isLlmCompactEnabled() {
34515
35379
  if (v === "0" || v === "false" || v === "off" || v === "no") return false;
34516
35380
  return true;
34517
35381
  }
34518
- async function llmSummarizeHistory(input) {
34519
- if (!isLlmCompactEnabled()) return null;
34520
- if (!input.droppedTranscript.trim()) return null;
34521
- let config2 = null;
34522
- try {
34523
- config2 = await resolveCompactProviderConfig();
34524
- } catch {
34525
- return null;
35382
+ function compactModelOverride() {
35383
+ const v = process.env.ZELARI_COMPACT_MODEL?.trim();
35384
+ return v ? v : void 0;
35385
+ }
35386
+ async function llmSummarizeHistoryReplay(input) {
35387
+ const override = input.overrideModel ?? compactModelOverride();
35388
+ const model = override ?? input.model;
35389
+ const cacheReuseExpected = !override;
35390
+ if (!isLlmCompactEnabled()) return { summary: null, model, cacheReuseExpected };
35391
+ if (input.droppedMessages.length === 0) {
35392
+ return { summary: null, model, cacheReuseExpected };
34526
35393
  }
34527
- if (!config2) return null;
34528
- const model = process.env.ZELARI_COMPACT_MODEL?.trim() || config2.model;
35394
+ const messages = [
35395
+ ...input.systemMessages,
35396
+ ...input.droppedMessages,
35397
+ {
35398
+ role: "user",
35399
+ content: COMPACTION_INSTRUCTION
35400
+ }
35401
+ ];
34529
35402
  const controller = new AbortController();
34530
- const timeout = setTimeout(() => controller.abort(), 45e3);
35403
+ const timeout = setTimeout(() => controller.abort(), REPLAY_TIMEOUT_MS);
34531
35404
  const onOuterAbort = () => controller.abort();
34532
35405
  input.signal?.addEventListener("abort", onOuterAbort, { once: true });
34533
35406
  try {
34534
- const url2 = `${config2.baseUrl.replace(/\/$/, "")}/chat/completions`;
34535
- const res = await fetch(url2, {
34536
- method: "POST",
35407
+ let text = "";
35408
+ let emittedToolCall = false;
35409
+ for await (const delta of input.providerStream({
35410
+ provider: input.provider,
35411
+ model,
35412
+ messages,
35413
+ // Tools stay advertised: dropping them would change the prefix token
35414
+ // sequence and destroy cache reuse (explicit DSH decision). They are
35415
+ // sorted canonically (same discipline as the live routed request and
35416
+ // the snapshot fingerprints) so the replay prefix is byte-identical.
35417
+ tools: [...input.tools].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
34537
35418
  signal: controller.signal,
34538
- headers: {
34539
- "content-type": "application/json",
34540
- authorization: `Bearer ${config2.apiKey}`
34541
- },
34542
- body: JSON.stringify({
34543
- model,
35419
+ generation: {
35420
+ purpose: "compaction",
34544
35421
  temperature: 0.1,
34545
- max_tokens: 900,
34546
- stream: false,
34547
- messages: [
34548
- { role: "system", content: COMPACT_SYSTEM },
34549
- {
34550
- role: "user",
34551
- content: `Extractive sketch (may be incomplete):
34552
- ${input.extractive.slice(0, 2500)}
34553
-
34554
- Transcript of dropped turns:
34555
- ${input.droppedTranscript}`
34556
- }
34557
- ]
34558
- })
34559
- });
34560
- if (!res.ok) return null;
34561
- const json2 = await res.json();
34562
- const text = json2.choices?.[0]?.message?.content?.trim();
34563
- if (!text) return null;
34564
- return "[history-summary \xB7 llm]\n" + text + "\n\nContinue from the recent messages below; honor decisions already made above.";
35422
+ maxTokens: 900
35423
+ }
35424
+ })) {
35425
+ if (delta.kind === "text") text += delta.delta;
35426
+ if (delta.kind === "tool_call") emittedToolCall = true;
35427
+ }
35428
+ if (emittedToolCall) return { summary: null, model, cacheReuseExpected };
35429
+ if (!text.trim()) return { summary: null, model, cacheReuseExpected };
35430
+ return { summary: text.trim(), model, cacheReuseExpected };
34565
35431
  } catch {
34566
- return null;
35432
+ return { summary: null, model, cacheReuseExpected };
34567
35433
  } finally {
34568
35434
  clearTimeout(timeout);
34569
35435
  input.signal?.removeEventListener("abort", onOuterAbort);
34570
35436
  }
34571
35437
  }
34572
- async function resolveCompactProviderConfig() {
34573
- const active = getProviderConfig().activeProviderId;
34574
- const meta3 = await resolveApiKeyWithMeta(active);
34575
- const apiKey = meta3?.apiKey;
34576
- if (!apiKey) return null;
34577
- const custom2 = getCustomEndpoint(active);
34578
- let baseUrl = custom2 || (active === "openai-compatible" || active === "custom" ? process.env.OPENAI_BASE_URL ?? PROVIDER_ENDPOINTS[active] : PROVIDER_ENDPOINTS[active]);
34579
- if (!baseUrl) return null;
34580
- const model = getModelForProvider(active);
34581
- return {
34582
- apiKey,
34583
- baseUrl,
34584
- model,
34585
- providerId: active
34586
- };
34587
- }
34588
- var COMPACT_SYSTEM;
35438
+ var COMPACTION_INSTRUCTION, REPLAY_TIMEOUT_MS;
34589
35439
  var init_llmCompact = __esm({
34590
35440
  "src/cli/budget/llmCompact.ts"() {
34591
35441
  "use strict";
34592
- init_providerConfig();
34593
- init_keyStore();
34594
- init_openai_compatible();
34595
- COMPACT_SYSTEM = `You compress earlier turns of a coding-agent session into a dense continuity brief.
34596
- Output plain text (no markdown fences) with these sections:
34597
- 1) Goal \u2014 what the user wants
34598
- 2) Decisions \u2014 choices already made
34599
- 3) Done \u2014 completed work / files changed
34600
- 4) Open \u2014 remaining tasks / blockers
34601
- 5) Constraints \u2014 important rules the agent must keep
35442
+ COMPACTION_INSTRUCTION = `
35443
+ You are now acting as a compaction engine for this coding-agent session.
35444
+
35445
+ Condense the conversation ABOVE into a compact checkpoint sufficient to continue the task.
35446
+
35447
+ Preserve:
35448
+ - user's goal and evolving intent
35449
+ - decisions already made
35450
+ - exact file paths and identifiers
35451
+ - code changes already completed
35452
+ - commands/errors that still matter
35453
+ - constraints
35454
+ - unfinished work
35455
+ - the single most likely next action
34602
35456
 
34603
- Be factual and concise. Max ~400 words. Do not invent work that was not present.`;
35457
+ Do not call tools.
35458
+ Do not mention this summarization request.
35459
+ Output only the checkpoint.
35460
+ Be concise.
35461
+ `.trim();
35462
+ REPLAY_TIMEOUT_MS = 6e4;
34604
35463
  }
34605
35464
  });
34606
35465
 
@@ -34639,6 +35498,7 @@ function resolveMaxMessages(opts) {
34639
35498
  turns = Math.min(turns, 3);
34640
35499
  }
34641
35500
  if (turns <= 0) return 0;
35501
+ if (opts?.force && opts?.maxMessages) return Math.max(1, opts.maxMessages);
34642
35502
  return turns * 4;
34643
35503
  }
34644
35504
  function findValidCutIndex(messages, naiveCut) {
@@ -34704,12 +35564,18 @@ function pruneToolResultsDetailed(messages, opts) {
34704
35564
  function compactHistory(messages, opts) {
34705
35565
  return compactHistoryDetailed(messages, opts).messages;
34706
35566
  }
35567
+ function buildCheckpointMessage(summaryText) {
35568
+ return {
35569
+ role: "user",
35570
+ content: CHECKPOINT_WRAPPER_PREFIX + "\n\n<compacted-summary>\n" + summaryText + "\n</compacted-summary>"
35571
+ };
35572
+ }
34707
35573
  function compactHistoryDetailed(messages, opts) {
34708
35574
  const maxMessages = resolveMaxMessages(opts);
34709
35575
  if (maxMessages === 0) {
34710
35576
  return { messages: [], compacted: true, messagesRemoved: messages.length, summary: "" };
34711
35577
  }
34712
- if (messages.length <= maxMessages * 2) {
35578
+ if (messages.length <= maxMessages * 2 && !opts?.force) {
34713
35579
  return {
34714
35580
  messages,
34715
35581
  compacted: false,
@@ -34717,7 +35583,7 @@ function compactHistoryDetailed(messages, opts) {
34717
35583
  summary: ""
34718
35584
  };
34719
35585
  }
34720
- const naiveCut = messages.length - maxMessages;
35586
+ const naiveCut = Math.max(0, messages.length - maxMessages);
34721
35587
  const cut = findValidCutIndex(messages, naiveCut);
34722
35588
  if (cut === 0) {
34723
35589
  return {
@@ -34731,10 +35597,9 @@ function compactHistoryDetailed(messages, opts) {
34731
35597
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
34732
35598
  const kept = pruned.messages;
34733
35599
  const summaryText = extractiveHistorySummary(droppedMsgs);
34734
- const summary = {
34735
- role: "system",
34736
- content: summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`
34737
- };
35600
+ const summary = buildCheckpointMessage(
35601
+ summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`
35602
+ );
34738
35603
  return {
34739
35604
  messages: [summary, ...kept],
34740
35605
  compacted: true,
@@ -34749,29 +35614,58 @@ async function compactHistoryAsync(messages, opts) {
34749
35614
  const cut = base.messagesRemoved;
34750
35615
  const droppedMsgs = messages.slice(0, cut);
34751
35616
  const extractive = extractiveHistorySummary(droppedMsgs);
34752
- const droppedTranscript = formatDroppedForLlm(droppedMsgs);
34753
35617
  let summaryText = extractive;
34754
- try {
34755
- const llm = await llmSummarizeHistory({
34756
- extractive,
34757
- droppedTranscript,
34758
- signal: opts?.signal
34759
- });
34760
- if (llm && llm.trim().length > 40) summaryText = llm.trim();
34761
- } catch {
35618
+ let cacheReuseExpected;
35619
+ let replayExactPrefix;
35620
+ const canReplay = !!(opts?.providerStream && opts?.requestSnapshot);
35621
+ if (canReplay) {
35622
+ try {
35623
+ const replay = await llmSummarizeHistoryReplay({
35624
+ providerStream: opts.providerStream,
35625
+ provider: opts.requestSnapshot.provider,
35626
+ model: opts.requestSnapshot.model,
35627
+ systemMessages: opts.requestSnapshot.systemMessages,
35628
+ tools: opts.requestSnapshot.tools,
35629
+ droppedMessages: droppedMsgs,
35630
+ signal: opts?.signal
35631
+ });
35632
+ cacheReuseExpected = replay.cacheReuseExpected;
35633
+ if (replay.summary && replay.summary.trim().length > 40) {
35634
+ const sourceTokens = roughTokens(droppedMsgs);
35635
+ const summaryTok = Math.ceil(replay.summary.length / 4);
35636
+ if (summaryTok < sourceTokens) {
35637
+ summaryText = replay.summary.trim();
35638
+ }
35639
+ }
35640
+ } catch {
35641
+ }
34762
35642
  }
34763
35643
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
34764
35644
  const kept = pruned.messages;
34765
- const summary = { role: "system", content: summaryText };
35645
+ const summary = buildCheckpointMessage(summaryText);
34766
35646
  return {
34767
35647
  messages: [summary, ...kept],
34768
35648
  compacted: true,
34769
35649
  messagesRemoved: cut,
34770
35650
  summary: summaryText,
34771
- prunedToolResults: pruned.stats.pruned
35651
+ prunedToolResults: pruned.stats.pruned,
35652
+ cacheReuseExpected,
35653
+ replayExactPrefix
34772
35654
  };
34773
35655
  }
34774
- var COMPACT_MARKER;
35656
+ function roughTokens(msgs) {
35657
+ let n = 0;
35658
+ for (const m of msgs) {
35659
+ n += Math.ceil((m.content ?? "").length / 4);
35660
+ if (m.toolCalls) {
35661
+ for (const tc of m.toolCalls) {
35662
+ n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
35663
+ }
35664
+ }
35665
+ }
35666
+ return Math.max(1, n);
35667
+ }
35668
+ var COMPACT_MARKER, CHECKPOINT_WRAPPER_PREFIX;
34775
35669
  var init_historyCompaction = __esm({
34776
35670
  "src/cli/hooks/historyCompaction.ts"() {
34777
35671
  "use strict";
@@ -34779,6 +35673,30 @@ var init_historyCompaction = __esm({
34779
35673
  init_llmCompact();
34780
35674
  init_envNumber();
34781
35675
  COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
35676
+ CHECKPOINT_WRAPPER_PREFIX = "This is an automatically generated checkpoint of earlier conversation. Treat it as established context and continue directly.";
35677
+ }
35678
+ });
35679
+
35680
+ // src/cli/budget/requestSnapshotStore.ts
35681
+ function recordRequestSnapshot(sessionId, snapshot) {
35682
+ store3.set(sessionId, { snapshot });
35683
+ }
35684
+ function recordRequestUsage(sessionId, usage) {
35685
+ const entry = store3.get(sessionId);
35686
+ if (!entry) return;
35687
+ entry.usage = usage;
35688
+ }
35689
+ function getRequestSnapshotWithUsage(sessionId) {
35690
+ return store3.get(sessionId) ?? null;
35691
+ }
35692
+ function clearAllRequestSnapshots() {
35693
+ store3.clear();
35694
+ }
35695
+ var store3;
35696
+ var init_requestSnapshotStore = __esm({
35697
+ "src/cli/budget/requestSnapshotStore.ts"() {
35698
+ "use strict";
35699
+ store3 = /* @__PURE__ */ new Map();
34782
35700
  }
34783
35701
  });
34784
35702
 
@@ -34804,8 +35722,8 @@ __export(conversationContext_exports, {
34804
35722
  setHistory: () => setHistory,
34805
35723
  setLastClarification: () => setLastClarification
34806
35724
  });
34807
- import { existsSync as existsSync20 } from "node:fs";
34808
- import { join as join16 } from "node:path";
35725
+ import { existsSync as existsSync23 } from "node:fs";
35726
+ import { join as join19 } from "node:path";
34809
35727
  function getHistory() {
34810
35728
  return history;
34811
35729
  }
@@ -34813,7 +35731,7 @@ function setHistory(messages) {
34813
35731
  history = [...messages];
34814
35732
  }
34815
35733
  function compactInPlace(cwd = process.cwd()) {
34816
- const durableStatePresent = existsSync20(join16(cwd, ".zelari", "state", "HEAD.json"));
35734
+ const durableStatePresent = existsSync23(join19(cwd, ".zelari", "state", "HEAD.json"));
34817
35735
  history = compactHistory(history, { durableStatePresent });
34818
35736
  }
34819
35737
  function appendMessages(msgs) {
@@ -34823,6 +35741,7 @@ function appendMessages(msgs) {
34823
35741
  function clearHistory() {
34824
35742
  history = [];
34825
35743
  lastClarification = null;
35744
+ clearAllRequestSnapshots();
34826
35745
  clearSessionTodos();
34827
35746
  clearSessionPermissionGrants();
34828
35747
  }
@@ -34981,6 +35900,7 @@ var init_conversationContext = __esm({
34981
35900
  init_toolPermissions();
34982
35901
  init_sessionTodos();
34983
35902
  init_historyCompaction();
35903
+ init_requestSnapshotStore();
34984
35904
  history = [];
34985
35905
  lastClarification = null;
34986
35906
  SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed|conferma|confermo|applica|fai|scrivi|esegui|implementa|vai pure|fai pure|ok procedi|sì procedi|si procedi)$/i;
@@ -35326,14 +36246,14 @@ var init_claudeProvider = __esm({
35326
36246
  });
35327
36247
 
35328
36248
  // src/cli/workspace/projectInstructions.ts
35329
- import { existsSync as existsSync21, readFileSync as readFileSync20 } from "node:fs";
35330
- 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";
35331
36251
  function loadProjectInstructions(projectRoot = process.cwd(), maxChars = MAX_CHARS) {
35332
36252
  for (const name of CANDIDATES) {
35333
- const full = join17(projectRoot, name);
35334
- if (!existsSync21(full)) continue;
36253
+ const full = join20(projectRoot, name);
36254
+ if (!existsSync24(full)) continue;
35335
36255
  try {
35336
- let raw = readFileSync20(full, "utf8");
36256
+ let raw = readFileSync22(full, "utf8");
35337
36257
  raw = raw.replace(/\r\n/g, "\n").trim();
35338
36258
  if (!raw) continue;
35339
36259
  if (raw.length <= maxChars) {
@@ -35368,75 +36288,6 @@ var init_projectInstructions = __esm({
35368
36288
  }
35369
36289
  });
35370
36290
 
35371
- // src/cli/workspace/paths.ts
35372
- import {
35373
- mkdirSync as mkdirSync12,
35374
- writeFileSync as writeFileSync14,
35375
- existsSync as existsSync22,
35376
- accessSync,
35377
- constants,
35378
- realpathSync
35379
- } from "node:fs";
35380
- import { join as join18, basename } from "node:path";
35381
- import { homedir as homedir9 } from "node:os";
35382
- import { createHash as createHash6 } from "node:crypto";
35383
- function resolveWorkspaceRoot(projectRoot = process.cwd()) {
35384
- const candidates = [
35385
- join18(projectRoot, ".zelari"),
35386
- join18(homedir9(), ".zelari-code", "workspace", hashProject(projectRoot))
35387
- ];
35388
- for (const candidate of candidates) {
35389
- if (isWritableDir(projectRoot) || candidate !== candidates[0]) {
35390
- ensureWorkspaceDir(candidate);
35391
- return candidate;
35392
- }
35393
- }
35394
- ensureWorkspaceDir(candidates[0]);
35395
- return candidates[0];
35396
- }
35397
- function hashProject(projectPath) {
35398
- return createHash6("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
35399
- }
35400
- function isWritableDir(dir) {
35401
- try {
35402
- if (!existsSync22(dir)) return false;
35403
- accessSync(dir, constants.W_OK);
35404
- return true;
35405
- } catch {
35406
- return false;
35407
- }
35408
- }
35409
- function ensureWorkspaceDir(workspaceDir) {
35410
- mkdirSync12(workspaceDir, { recursive: true });
35411
- if (workspaceDir.endsWith("/.zelari") && existsSync22(join18(workspaceDir, "..", ".git"))) {
35412
- const gitignorePath = join18(workspaceDir, ".gitignore");
35413
- if (!existsSync22(gitignorePath)) {
35414
- writeFileSync14(gitignorePath, "*\n!.gitignore\n");
35415
- }
35416
- }
35417
- }
35418
- function workspaceFile(rootDir, kind) {
35419
- switch (kind) {
35420
- case "plan":
35421
- return join18(rootDir, "plan.md");
35422
- case "risks":
35423
- return join18(rootDir, "risks.md");
35424
- case "index":
35425
- return join18(rootDir, "workspace.json");
35426
- }
35427
- }
35428
- function workspaceArtifact(rootDir, subdir, slug) {
35429
- return join18(rootDir, subdir, `${slug}.md`);
35430
- }
35431
- function projectName(projectRoot = process.cwd()) {
35432
- return basename(realpathSync(projectRoot));
35433
- }
35434
- var init_paths2 = __esm({
35435
- "src/cli/workspace/paths.ts"() {
35436
- "use strict";
35437
- }
35438
- });
35439
-
35440
36291
  // src/cli/workspace/workspaceSummary.ts
35441
36292
  var workspaceSummary_exports = {};
35442
36293
  __export(workspaceSummary_exports, {
@@ -35446,8 +36297,8 @@ __export(workspaceSummary_exports, {
35446
36297
  buildWorkspaceSummary: () => buildWorkspaceSummary,
35447
36298
  buildZelariReadHint: () => buildZelariReadHint
35448
36299
  });
35449
- import { existsSync as existsSync23, readFileSync as readFileSync21, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
35450
- 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";
35451
36302
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
35452
36303
  const { maxEntries = 30, maxChars = 3500, maxDeps = 24, maxScripts = 16 } = options;
35453
36304
  const name = safeProjectName(projectRoot);
@@ -35480,11 +36331,11 @@ function formatTaskLine(t) {
35480
36331
  }
35481
36332
  function buildPlanSummary(projectRoot = process.cwd(), options) {
35482
36333
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
35483
- const planPath = join19(zelariRoot, "plan.json");
35484
- if (!existsSync23(planPath)) return null;
36334
+ const planPath = join21(zelariRoot, "plan.json");
36335
+ if (!existsSync25(planPath)) return null;
35485
36336
  let plan;
35486
36337
  try {
35487
- plan = JSON.parse(readFileSync21(planPath, "utf8"));
36338
+ plan = JSON.parse(readFileSync23(planPath, "utf8"));
35488
36339
  } catch {
35489
36340
  return null;
35490
36341
  }
@@ -35498,7 +36349,10 @@ function buildPlanSummary(projectRoot = process.cwd(), options) {
35498
36349
  "# Plan ops (DRAFT \u2014 .zelari/plan.json)",
35499
36350
  "_This is operational task list from design, not product law. Ground every change in the real source tree._"
35500
36351
  ];
35501
- 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
+ });
35502
36356
  const done = tasks.length - open.length;
35503
36357
  const userMessage = options?.userMessage?.trim();
35504
36358
  let scopedOpen = open;
@@ -35620,8 +36474,8 @@ function pickNextTask(open) {
35620
36474
  )[0];
35621
36475
  }
35622
36476
  function buildZelariReadHint(projectRoot = process.cwd()) {
35623
- const planPath = join19(resolveWorkspaceRoot(projectRoot), "plan.json");
35624
- if (!existsSync23(planPath)) return "";
36477
+ const planPath = join21(resolveWorkspaceRoot(projectRoot), "plan.json");
36478
+ if (!existsSync25(planPath)) return "";
35625
36479
  return [
35626
36480
  "# Council workspace detected (.zelari/) \u2014 DRAFT vault",
35627
36481
  "`.zelari/plan.json` and `.zelari/docs/` hold **design hypotheses**, not verified product state.",
@@ -35636,10 +36490,10 @@ function safeProjectName(root) {
35636
36490
  }
35637
36491
  }
35638
36492
  function readPackageJson(projectRoot) {
35639
- const p3 = join19(projectRoot, "package.json");
35640
- if (!existsSync23(p3)) return null;
36493
+ const p3 = join21(projectRoot, "package.json");
36494
+ if (!existsSync25(p3)) return null;
35641
36495
  try {
35642
- return JSON.parse(readFileSync21(p3, "utf8"));
36496
+ return JSON.parse(readFileSync23(p3, "utf8"));
35643
36497
  } catch {
35644
36498
  return null;
35645
36499
  }
@@ -35680,7 +36534,7 @@ function readBuildScripts(projectRoot, maxScripts = 16) {
35680
36534
  function listShallow(projectRoot, maxEntries) {
35681
36535
  const out = [];
35682
36536
  try {
35683
- const top = readdirSync5(projectRoot, { withFileTypes: true }).filter(
36537
+ const top = readdirSync6(projectRoot, { withFileTypes: true }).filter(
35684
36538
  (e) => !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist"
35685
36539
  ).sort((a, b) => a.name.localeCompare(b.name));
35686
36540
  let count = 0;
@@ -35689,11 +36543,11 @@ function listShallow(projectRoot, maxEntries) {
35689
36543
  out.push(`\u2026 (+${top.length - count} more)`);
35690
36544
  break;
35691
36545
  }
35692
- const rel2 = relative(projectRoot, join19(projectRoot, entry.name));
36546
+ const rel2 = relative(projectRoot, join21(projectRoot, entry.name));
35693
36547
  if (entry.isDirectory()) {
35694
36548
  let inner = "";
35695
36549
  try {
35696
- const sub = readdirSync5(join19(projectRoot, entry.name), {
36550
+ const sub = readdirSync6(join21(projectRoot, entry.name), {
35697
36551
  withFileTypes: true
35698
36552
  }).filter((e) => !e.name.startsWith(".")).slice(0, 4).map((e) => e.name);
35699
36553
  if (sub.length > 0)
@@ -35741,12 +36595,12 @@ var init_workspaceSummary = __esm({
35741
36595
  });
35742
36596
 
35743
36597
  // src/cli/workspace/buildLessonsSummary.ts
35744
- import { existsSync as existsSync24 } from "node:fs";
35745
- import { join as join20 } from "node:path";
36598
+ import { existsSync as existsSync26 } from "node:fs";
36599
+ import { join as join22 } from "node:path";
35746
36600
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
35747
36601
  if (process.env["ZELARI_LESSONS"] === "0") return null;
35748
36602
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
35749
- if (!existsSync24(join20(zelariRoot, "lessons.jsonl"))) return null;
36603
+ if (!existsSync26(join22(zelariRoot, "lessons.jsonl"))) return null;
35750
36604
  const lessons = recallLessons(zelariRoot, {
35751
36605
  maxLessons: 5,
35752
36606
  maxBytes: 2048,
@@ -35767,8 +36621,8 @@ var composeContext_exports = {};
35767
36621
  __export(composeContext_exports, {
35768
36622
  composeProjectContext: () => composeProjectContext
35769
36623
  });
35770
- import { existsSync as existsSync25, readdirSync as readdirSync6, readFileSync as readFileSync22 } from "node:fs";
35771
- 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";
35772
36626
  function cap2(text, max, label) {
35773
36627
  if (!text || text.length <= max) return { text: text || "", truncated: false };
35774
36628
  return {
@@ -35780,19 +36634,19 @@ function cap2(text, max, label) {
35780
36634
  }
35781
36635
  function buildDesignIndex(projectRoot, maxChars) {
35782
36636
  const root = resolveWorkspaceRoot(projectRoot);
35783
- if (!existsSync25(root)) return "";
36637
+ if (!existsSync27(root)) return "";
35784
36638
  const lines = [
35785
36639
  "# Design vault index (.zelari/) \u2014 HYPOTHESES only",
35786
36640
  "Full design docs are NOT product source of truth. Open with list_files / read_file / searchDocuments if needed."
35787
36641
  ];
35788
- const docsDir = join21(root, "docs");
35789
- if (existsSync25(docsDir)) {
36642
+ const docsDir = join23(root, "docs");
36643
+ if (existsSync27(docsDir)) {
35790
36644
  try {
35791
- 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);
35792
36646
  if (docs.length > 0) {
35793
36647
  lines.push("", "## docs/ (titles only)");
35794
36648
  for (const d of docs) lines.push(`- .zelari/docs/${d}`);
35795
- if (readdirSync6(docsDir).filter((n) => n.endsWith(".md")).length > 12) {
36649
+ if (readdirSync7(docsDir).filter((n) => n.endsWith(".md")).length > 12) {
35796
36650
  lines.push("- \u2026 (more under .zelari/docs/)");
35797
36651
  }
35798
36652
  }
@@ -35800,14 +36654,14 @@ function buildDesignIndex(projectRoot, maxChars) {
35800
36654
  }
35801
36655
  }
35802
36656
  for (const name of ["risks.md", "plan.json", "nfr-spec.json"]) {
35803
- if (existsSync25(join21(root, name))) {
36657
+ if (existsSync27(join23(root, name))) {
35804
36658
  lines.push(`- .zelari/${name} present`);
35805
36659
  }
35806
36660
  }
35807
- const decisionsDir = join21(root, "decisions");
35808
- if (existsSync25(decisionsDir)) {
36661
+ const decisionsDir = join23(root, "decisions");
36662
+ if (existsSync27(decisionsDir)) {
35809
36663
  try {
35810
- const n = readdirSync6(decisionsDir).filter((f) => f.endsWith(".md")).length;
36664
+ const n = readdirSync7(decisionsDir).filter((f) => f.endsWith(".md")).length;
35811
36665
  if (n > 0) lines.push(`- .zelari/decisions/ (${n} ADR file(s) \u2014 treat proposed as non-binding)`);
35812
36666
  } catch {
35813
36667
  }
@@ -35906,17 +36760,17 @@ function composeProjectContext(input) {
35906
36760
  }
35907
36761
  function readDurableHeadSync(projectRoot) {
35908
36762
  try {
35909
- const headPath = join21(projectRoot, ".zelari", "state", "HEAD.json");
35910
- if (!existsSync25(headPath)) return "";
35911
- 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"));
35912
36766
  if (!head?.id) return "";
35913
- const metaPath = join21(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
35914
- if (!existsSync25(metaPath)) return "";
35915
- const meta3 = JSON.parse(readFileSync22(metaPath, "utf8"));
35916
- 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");
35917
36771
  let discoveries = [];
35918
- if (existsSync25(discPath)) {
35919
- discoveries = JSON.parse(readFileSync22(discPath, "utf8"));
36772
+ if (existsSync27(discPath)) {
36773
+ discoveries = JSON.parse(readFileSync24(discPath, "utf8"));
35920
36774
  }
35921
36775
  const reusable = discoveries.filter((d) => d.reusable !== false);
35922
36776
  const lines = [
@@ -35949,13 +36803,13 @@ var planDetect_exports = {};
35949
36803
  __export(planDetect_exports, {
35950
36804
  hasWorkspacePlan: () => hasWorkspacePlan
35951
36805
  });
35952
- import { existsSync as existsSync26, readFileSync as readFileSync23 } from "node:fs";
35953
- 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";
35954
36808
  function hasWorkspacePlan(projectRoot = process.cwd()) {
35955
- const planPath = join22(resolveWorkspaceRoot(projectRoot), "plan.json");
35956
- if (!existsSync26(planPath)) return false;
36809
+ const planPath = join24(resolveWorkspaceRoot(projectRoot), "plan.json");
36810
+ if (!existsSync28(planPath)) return false;
35957
36811
  try {
35958
- const parsed = JSON.parse(readFileSync23(planPath, "utf8"));
36812
+ const parsed = JSON.parse(readFileSync25(planPath, "utf8"));
35959
36813
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
35960
36814
  } catch {
35961
36815
  return false;
@@ -35986,8 +36840,8 @@ async function loadDurableContext(projectRoot, opts) {
35986
36840
  return cache.text;
35987
36841
  }
35988
36842
  try {
35989
- const store3 = await getStateStore(projectRoot, env);
35990
- const text = await store3.materializeContext(void 0, opts?.maxChars);
36843
+ const store4 = await getStateStore(projectRoot, env);
36844
+ const text = await store4.materializeContext(void 0, opts?.maxChars);
35991
36845
  cache = { text: text || "", at: now, projectRoot };
35992
36846
  return cache.text;
35993
36847
  } catch {
@@ -36004,333 +36858,6 @@ var init_loadDurableContext = __esm({
36004
36858
  }
36005
36859
  });
36006
36860
 
36007
- // src/cli/workspace/storage.ts
36008
- var storage_exports = {};
36009
- __export(storage_exports, {
36010
- Storage: () => Storage,
36011
- parseFrontmatter: () => parseFrontmatter,
36012
- parseYaml: () => parseYaml,
36013
- serializeFrontmatter: () => serializeFrontmatter,
36014
- serializeYaml: () => serializeYaml,
36015
- workspaceMutex: () => workspaceMutex
36016
- });
36017
- import {
36018
- readFileSync as readFileSync24,
36019
- writeFileSync as writeFileSync15,
36020
- existsSync as existsSync27,
36021
- mkdirSync as mkdirSync13,
36022
- readdirSync as readdirSync7,
36023
- renameSync as renameSync2
36024
- } from "node:fs";
36025
- import { dirname as dirname4, join as join23 } from "node:path";
36026
- function parseFrontmatter(md) {
36027
- const m = FRONTMATTER_RE.exec(md);
36028
- if (!m) return { meta: {}, body: md };
36029
- const meta3 = parseYaml(m[1]);
36030
- const body = m[2];
36031
- return { meta: meta3, body };
36032
- }
36033
- function serializeFrontmatter(meta3, body) {
36034
- const yamlStr = serializeYaml(meta3);
36035
- return `---
36036
- ${yamlStr}
36037
- ---
36038
- ${body}`;
36039
- }
36040
- function parseYaml(input) {
36041
- const lines = input.split(/\r?\n/);
36042
- const ctx = { lines, i: 0 };
36043
- return parseNode(ctx, 0);
36044
- }
36045
- function parseNode(ctx, indent) {
36046
- while (ctx.i < ctx.lines.length) {
36047
- const line2 = ctx.lines[ctx.i];
36048
- if (line2.trim() === "" || line2.trim().startsWith("#")) {
36049
- ctx.i++;
36050
- continue;
36051
- }
36052
- break;
36053
- }
36054
- if (ctx.i >= ctx.lines.length) return null;
36055
- const line = ctx.lines[ctx.i];
36056
- const lineIndent = countIndent(line);
36057
- if (/^\s*-\s+/.test(line)) {
36058
- return parseBlockSequence(ctx, indent);
36059
- }
36060
- if (/^\s*\[.*\]\s*$/.test(line)) {
36061
- const flow = line.trim().replace(/^\[/, "").replace(/\]$/, "");
36062
- return parseFlowSequence(flow);
36063
- }
36064
- if (/^\s*\{.*\}\s*$/.test(line)) {
36065
- const flow = line.trim().replace(/^\{/, "").replace(/\}$/, "");
36066
- return parseFlowMap(flow);
36067
- }
36068
- return parseBlockMap(ctx, indent);
36069
- }
36070
- function parseBlockMap(ctx, indent) {
36071
- const out = {};
36072
- while (ctx.i < ctx.lines.length) {
36073
- const line = ctx.lines[ctx.i];
36074
- if (line.trim() === "" || line.trim().startsWith("#")) {
36075
- ctx.i++;
36076
- continue;
36077
- }
36078
- const lineIndent = countIndent(line);
36079
- if (lineIndent < indent) break;
36080
- if (lineIndent > indent) {
36081
- ctx.i++;
36082
- continue;
36083
- }
36084
- const m = /^([^:]+):\s*(.*)$/.exec(line);
36085
- if (!m) {
36086
- ctx.i++;
36087
- continue;
36088
- }
36089
- const key = m[1].trim();
36090
- const valuePart = m[2].trim();
36091
- if (valuePart === "" || valuePart === "|" || valuePart === ">") {
36092
- ctx.i++;
36093
- const nested = parseNode(ctx, indent + 2);
36094
- out[key] = nested;
36095
- } else {
36096
- if (valuePart.startsWith("[")) {
36097
- out[key] = parseFlowSequence(stripFlow(valuePart, "[", "]"));
36098
- } else if (valuePart.startsWith("{")) {
36099
- out[key] = parseFlowMap(stripFlow(valuePart, "{", "}"));
36100
- } else {
36101
- out[key] = parseScalar(valuePart);
36102
- }
36103
- ctx.i++;
36104
- }
36105
- }
36106
- return out;
36107
- }
36108
- function parseBlockSequence(ctx, indent) {
36109
- const out = [];
36110
- while (ctx.i < ctx.lines.length) {
36111
- const line = ctx.lines[ctx.i];
36112
- if (line.trim() === "") {
36113
- ctx.i++;
36114
- continue;
36115
- }
36116
- const lineIndent = countIndent(line);
36117
- if (lineIndent < indent) break;
36118
- if (lineIndent > indent) break;
36119
- const m = /^-\s*(.*)$/.exec(line);
36120
- if (!m) break;
36121
- const rest = m[1];
36122
- if (rest === "") {
36123
- ctx.i++;
36124
- out.push(parseNode(ctx, indent + 2));
36125
- } else if (rest.startsWith("[") || rest.startsWith("{")) {
36126
- let buffer = rest;
36127
- let depthSq = (buffer.match(/\[/g) || []).length - (buffer.match(/\]/g) || []).length;
36128
- let depthCu = (buffer.match(/\{/g) || []).length - (buffer.match(/\}/g) || []).length;
36129
- while ((depthSq > 0 || depthCu > 0) && ctx.i + 1 < ctx.lines.length) {
36130
- ctx.i++;
36131
- const next = ctx.lines[ctx.i].trim();
36132
- buffer += " " + next;
36133
- depthSq = (buffer.match(/\[/g) || []).length - (buffer.match(/\]/g) || []).length;
36134
- depthCu = (buffer.match(/\{/g) || []).length - (buffer.match(/\}/g) || []).length;
36135
- }
36136
- if (buffer.startsWith("[")) {
36137
- out.push(parseFlowSequence(buffer.slice(1).replace(/\]$/, "")));
36138
- } else {
36139
- out.push(parseFlowMap(buffer.slice(1).replace(/\}$/, "")));
36140
- }
36141
- ctx.i++;
36142
- } else if (rest.includes(":")) {
36143
- ctx.i++;
36144
- const mapCtx = { lines: [" ".repeat(indent + 2) + rest, ...ctx.lines.slice(ctx.i)], i: 0 };
36145
- const val = parseBlockMap(mapCtx, indent + 2);
36146
- ctx.i += mapCtx.i - 1;
36147
- out.push(val);
36148
- } else {
36149
- out.push(parseScalar(rest));
36150
- ctx.i++;
36151
- }
36152
- }
36153
- return out;
36154
- }
36155
- function parseFlowSequence(input) {
36156
- const parts = splitFlow(input);
36157
- return parts.map((p3) => {
36158
- const trimmed = p3.trim();
36159
- if (trimmed.startsWith("{")) {
36160
- return parseFlowMap(stripFlow(trimmed, "{", "}"));
36161
- }
36162
- return parseScalar(trimmed);
36163
- });
36164
- }
36165
- function stripFlow(s, open, close) {
36166
- let out = s.trim();
36167
- if (out.startsWith(open)) out = out.slice(1);
36168
- if (out.endsWith(close)) out = out.slice(0, -1);
36169
- return out;
36170
- }
36171
- function parseFlowMap(input) {
36172
- const parts = splitFlow(input);
36173
- const out = {};
36174
- for (const p3 of parts) {
36175
- const colonIdx = p3.indexOf(":");
36176
- if (colonIdx < 0) continue;
36177
- const key = p3.slice(0, colonIdx).trim();
36178
- const value = p3.slice(colonIdx + 1).trim();
36179
- out[key] = parseScalar(value);
36180
- }
36181
- return out;
36182
- }
36183
- function splitFlow(input) {
36184
- const out = [];
36185
- let depthSq = 0, depthCu = 0, depthQu = 0;
36186
- let buffer = "";
36187
- for (let i = 0; i < input.length; i++) {
36188
- const c = input[i];
36189
- if (c === '"' || c === "'") {
36190
- depthQu = depthQu === 0 ? depthQu + 1 : 0;
36191
- buffer += c;
36192
- } else if (depthQu === 0) {
36193
- if (c === "[") depthSq++;
36194
- else if (c === "]") depthSq--;
36195
- else if (c === "{") depthCu++;
36196
- else if (c === "}") depthCu--;
36197
- else if (c === "," && depthSq === 0 && depthCu === 0) {
36198
- out.push(buffer);
36199
- buffer = "";
36200
- continue;
36201
- }
36202
- buffer += c;
36203
- } else {
36204
- buffer += c;
36205
- }
36206
- }
36207
- if (buffer.trim()) out.push(buffer);
36208
- return out;
36209
- }
36210
- function parseScalar(s) {
36211
- if (s === "" || s === "null" || s === "~") return null;
36212
- if (VALID_SCALARS.test(s)) return s.toLowerCase() === "true";
36213
- if (/^-?\d+(\.\d+)?$/.test(s)) return Number(s);
36214
- if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
36215
- return s.slice(1, -1);
36216
- }
36217
- return s;
36218
- }
36219
- function serializeYaml(value, indent = 0) {
36220
- if (value === null || value === void 0) return "";
36221
- if (typeof value === "string") {
36222
- if (/[:#\n\[\]\{\},&*!|>'"%@`]/.test(value)) {
36223
- return JSON.stringify(value);
36224
- }
36225
- return value;
36226
- }
36227
- if (typeof value === "number" || typeof value === "boolean") return String(value);
36228
- if (Array.isArray(value)) {
36229
- if (value.length === 0) return "[]";
36230
- if (value.every((v) => v === null || typeof v !== "object")) {
36231
- return `[${value.map(serializeScalarInline).join(", ")}]`;
36232
- }
36233
- return `[${value.map((v) => "{" + serializeInlineObject(v) + "}").join(", ")}]`;
36234
- }
36235
- if (typeof value === "object") {
36236
- const entries = Object.entries(value).filter(([, v]) => v !== void 0);
36237
- return entries.map(([k, v]) => {
36238
- if (v === null || v === void 0) return `${k}:`;
36239
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
36240
- return `${k}: ${serializeScalarInline(v)}`;
36241
- }
36242
- if (Array.isArray(v)) {
36243
- if (v.length === 0) return `${k}: []`;
36244
- if (v.every((x) => x === null || typeof x !== "object")) {
36245
- return `${k}: [${v.map(serializeScalarInline).join(", ")}]`;
36246
- }
36247
- return `${k}: [${v.map((x) => "{" + serializeInlineObject(x) + "}").join(", ")}]`;
36248
- }
36249
- return `${k}:
36250
- ${serializeYaml(v, indent + 2)}`;
36251
- }).map((line) => `${" ".repeat(indent)}${line}`).join("\n");
36252
- }
36253
- return String(value);
36254
- }
36255
- function serializeScalarInline(v) {
36256
- if (typeof v === "string" && /[:#\n\[\]\{\},&*!|>'"%@`]/.test(v)) return JSON.stringify(v);
36257
- return String(v);
36258
- }
36259
- function serializeInlineObject(v) {
36260
- if (v === null || typeof v !== "object" || Array.isArray(v)) {
36261
- return serializeScalarInline(v);
36262
- }
36263
- const entries = Object.entries(v).filter(([, val]) => val !== void 0);
36264
- return entries.map(([k, val]) => `${k}: ${serializeYaml(val)}`).join(", ");
36265
- }
36266
- function countIndent(line) {
36267
- let i = 0;
36268
- while (i < line.length && line[i] === " ") i++;
36269
- return i;
36270
- }
36271
- var FRONTMATTER_RE, VALID_SCALARS, Storage, KeyedMutex, workspaceMutex;
36272
- var init_storage = __esm({
36273
- "src/cli/workspace/storage.ts"() {
36274
- "use strict";
36275
- FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
36276
- VALID_SCALARS = /^(true|false|null|~)$/i;
36277
- Storage = class {
36278
- /** Read a Markdown file with frontmatter. Throws if not found. */
36279
- read(path53) {
36280
- if (!existsSync27(path53)) {
36281
- throw new Error(`File not found: ${path53}`);
36282
- }
36283
- const md = readFileSync24(path53, "utf8");
36284
- return parseFrontmatter(md);
36285
- }
36286
- /** Read a Markdown file; returns null if not found. */
36287
- readIfExists(path53) {
36288
- if (!existsSync27(path53)) return null;
36289
- return this.read(path53);
36290
- }
36291
- /**
36292
- * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
36293
- * The meta object is serialized as YAML frontmatter; body as Markdown.
36294
- */
36295
- write(path53, meta3, body) {
36296
- mkdirSync13(dirname4(path53), { recursive: true });
36297
- const tmp = path53 + ".tmp-" + process.pid;
36298
- const md = serializeFrontmatter(meta3, body);
36299
- writeFileSync15(tmp, md, "utf8");
36300
- renameSync2(tmp, path53);
36301
- }
36302
- /** List all .md files in a directory (non-recursive). */
36303
- listMarkdown(dir) {
36304
- if (!existsSync27(dir)) return [];
36305
- return readdirSync7(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join23(dir, f));
36306
- }
36307
- };
36308
- KeyedMutex = class {
36309
- chains = /* @__PURE__ */ new Map();
36310
- async run(key, fn) {
36311
- const prev2 = this.chains.get(key) ?? Promise.resolve();
36312
- let release = () => {
36313
- };
36314
- const next = new Promise((resolve3) => {
36315
- release = resolve3;
36316
- });
36317
- const chained = prev2.then(() => next);
36318
- this.chains.set(key, chained);
36319
- await prev2;
36320
- try {
36321
- return await fn();
36322
- } finally {
36323
- release();
36324
- if (this.chains.get(key) === chained) {
36325
- this.chains.delete(key);
36326
- }
36327
- }
36328
- }
36329
- };
36330
- workspaceMutex = new KeyedMutex();
36331
- }
36332
- });
36333
-
36334
36861
  // src/cli/workspace/stubs.ts
36335
36862
  var stubs_exports = {};
36336
36863
  __export(stubs_exports, {
@@ -36341,14 +36868,14 @@ __export(stubs_exports, {
36341
36868
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
36342
36869
  });
36343
36870
  import {
36344
- existsSync as existsSync28,
36871
+ existsSync as existsSync29,
36345
36872
  readdirSync as readdirSync8,
36346
- writeFileSync as writeFileSync16,
36347
- readFileSync as readFileSync25,
36348
- mkdirSync as mkdirSync14,
36349
- renameSync as renameSync3
36873
+ writeFileSync as writeFileSync17,
36874
+ readFileSync as readFileSync26,
36875
+ mkdirSync as mkdirSync15,
36876
+ renameSync as renameSync4
36350
36877
  } from "node:fs";
36351
- 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";
36352
36879
  function createWorkspaceContext(projectRoot = process.cwd()) {
36353
36880
  const rootDir = resolveWorkspaceRoot(projectRoot);
36354
36881
  return {
@@ -36358,19 +36885,21 @@ function createWorkspaceContext(projectRoot = process.cwd()) {
36358
36885
  };
36359
36886
  }
36360
36887
  function planJsonPath(ctx) {
36361
- return join24(ctx.rootDir, "plan.json");
36888
+ return join25(ctx.rootDir, "plan.json");
36362
36889
  }
36363
36890
  function readPlan(ctx) {
36364
36891
  const jsonPath = planJsonPath(ctx);
36365
- if (existsSync28(jsonPath)) {
36892
+ if (existsSync29(jsonPath)) {
36366
36893
  try {
36367
36894
  const parsed = JSON.parse(
36368
- readFileSync25(jsonPath, "utf8")
36895
+ readFileSync26(jsonPath, "utf8")
36369
36896
  );
36897
+ const { phases, tasks, milestones, ...root } = parsed;
36370
36898
  return {
36371
- phases: Array.isArray(parsed.phases) ? parsed.phases : [],
36372
- tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
36373
- 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
36374
36903
  };
36375
36904
  } catch {
36376
36905
  }
@@ -36387,10 +36916,23 @@ function readPlan(ctx) {
36387
36916
  }
36388
36917
  function writePlan(ctx, summary) {
36389
36918
  const jsonPath = planJsonPath(ctx);
36390
- mkdirSync14(dirname5(jsonPath), { recursive: true });
36919
+ mkdirSync15(dirname6(jsonPath), { recursive: true });
36391
36920
  const tmp = jsonPath + ".tmp-" + process.pid;
36392
- writeFileSync16(tmp, JSON.stringify(summary, null, 2), "utf8");
36393
- 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);
36394
36936
  const mdPath = workspaceFile(ctx.rootDir, "plan");
36395
36937
  const summaryMeta = {
36396
36938
  kind: "plan-summary",
@@ -36456,8 +36998,8 @@ function renderPlanBody(summary) {
36456
36998
  return lines.join("\n");
36457
36999
  }
36458
37000
  function nextAdrId(ctx) {
36459
- const decisionsDir = join24(ctx.rootDir, "decisions");
36460
- if (!existsSync28(decisionsDir)) return "001";
37001
+ const decisionsDir = join25(ctx.rootDir, "decisions");
37002
+ if (!existsSync29(decisionsDir)) return "001";
36461
37003
  const existing = readdirSync8(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
36462
37004
  const max = existing.length === 0 ? 0 : Math.max(...existing);
36463
37005
  return String(max + 1).padStart(3, "0");
@@ -36499,7 +37041,7 @@ function addTaskRecord(ctx, summary, phaseId, t, options) {
36499
37041
  status: "pending",
36500
37042
  priority: t.priority
36501
37043
  });
36502
- const taskPath = join24(ctx.rootDir, "plan-tasks", `${id}.md`);
37044
+ const taskPath = join25(ctx.rootDir, "plan-tasks", `${id}.md`);
36503
37045
  const meta3 = {
36504
37046
  kind: "task",
36505
37047
  id,
@@ -36541,7 +37083,7 @@ function addMilestoneRecord(ctx, summary, input) {
36541
37083
  dueDate: input.dueDate,
36542
37084
  targetVersion: version2
36543
37085
  });
36544
- const path53 = join24(ctx.rootDir, "milestones", `${id}.md`);
37086
+ const path53 = join25(ctx.rootDir, "milestones", `${id}.md`);
36545
37087
  const meta3 = {
36546
37088
  kind: "milestone",
36547
37089
  id,
@@ -36839,8 +37381,8 @@ function createNfrSpecStub(ctx) {
36839
37381
  },
36840
37382
  planFeatureKeywords: Array.isArray(args["planFeatureKeywords"]) ? args["planFeatureKeywords"] : void 0
36841
37383
  };
36842
- const outPath = join24(ctx.rootDir, "nfr-spec.json");
36843
- 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");
36844
37386
  return `NFR spec written to nfr-spec.json (${targets.length} target(s)).`;
36845
37387
  });
36846
37388
  }
@@ -36903,18 +37445,18 @@ function searchDocumentsStub(ctx) {
36903
37445
  (w) => w.length >= 3 && w !== "or" && w !== "and" && w !== "the"
36904
37446
  );
36905
37447
  const files = [
36906
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "decisions")),
36907
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "docs")),
36908
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "reviews")),
36909
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "plan-tasks")),
36910
- ...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")),
36911
37453
  workspaceFile(ctx.rootDir, "plan"),
36912
37454
  workspaceFile(ctx.rootDir, "risks")
36913
37455
  ];
36914
37456
  const results = [];
36915
37457
  for (const file2 of files) {
36916
- if (!existsSync28(file2)) continue;
36917
- const raw = readFileSync25(file2, "utf8");
37458
+ if (!existsSync29(file2)) continue;
37459
+ const raw = readFileSync26(file2, "utf8");
36918
37460
  const content = raw.toLowerCase();
36919
37461
  let idx = -1;
36920
37462
  let matchLen = 0;
@@ -36965,9 +37507,9 @@ function linkDocumentsStub(ctx) {
36965
37507
  const toId = args["toId"] ?? args["targetId"] ?? args["targetPathOrTitle"];
36966
37508
  if (!fromId || !toId) return "linkDocuments requires fromId and toId.";
36967
37509
  const allFiles = [
36968
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "decisions")),
36969
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "docs")),
36970
- ...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"))
36971
37513
  ];
36972
37514
  const source = allFiles.find((f) => {
36973
37515
  try {
@@ -36998,9 +37540,9 @@ function getDocumentBacklinksStub(ctx) {
36998
37540
  const targetId = args["targetId"] ?? args["id"];
36999
37541
  if (!targetId) return "getDocumentBacklinks requires targetId.";
37000
37542
  const allFiles = [
37001
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "decisions")),
37002
- ...ctx.storage.listMarkdown(join24(ctx.rootDir, "docs")),
37003
- ...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"))
37004
37546
  ];
37005
37547
  const backlinks = [];
37006
37548
  for (const file2 of allFiles) {
@@ -37095,7 +37637,7 @@ __export(updater_exports, {
37095
37637
  });
37096
37638
  import { createRequire as createRequire2 } from "node:module";
37097
37639
  import { spawn as spawn8 } from "node:child_process";
37098
- import { existsSync as existsSync29 } from "node:fs";
37640
+ import { existsSync as existsSync30 } from "node:fs";
37099
37641
  import path31 from "node:path";
37100
37642
  import { fileURLToPath } from "node:url";
37101
37643
  function resolveBundledNpmCli(execPath = process.execPath) {
@@ -37108,7 +37650,7 @@ function resolveBundledNpmCli(execPath = process.execPath) {
37108
37650
  ];
37109
37651
  for (const candidate of candidates) {
37110
37652
  try {
37111
- if (existsSync29(candidate)) return candidate;
37653
+ if (existsSync30(candidate)) return candidate;
37112
37654
  } catch {
37113
37655
  }
37114
37656
  }
@@ -37408,23 +37950,23 @@ var init_mcpClient = __esm({
37408
37950
 
37409
37951
  // src/cli/mcp/mcpConfigIo.ts
37410
37952
  import {
37411
- existsSync as existsSync30,
37412
- mkdirSync as mkdirSync15,
37413
- readFileSync as readFileSync26,
37414
- writeFileSync as writeFileSync17
37953
+ existsSync as existsSync31,
37954
+ mkdirSync as mkdirSync16,
37955
+ readFileSync as readFileSync27,
37956
+ writeFileSync as writeFileSync18
37415
37957
  } from "node:fs";
37416
- import { dirname as dirname6, join as join25 } from "node:path";
37958
+ import { dirname as dirname7, join as join26 } from "node:path";
37417
37959
  import { homedir as homedir10 } from "node:os";
37418
37960
  function getUserMcpPath() {
37419
- return join25(homedir10(), ".zelari-code", "mcp.json");
37961
+ return join26(homedir10(), ".zelari-code", "mcp.json");
37420
37962
  }
37421
37963
  function getProjectMcpPath(projectRoot) {
37422
- return join25(projectRoot, ".zelari", "mcp.json");
37964
+ return join26(projectRoot, ".zelari", "mcp.json");
37423
37965
  }
37424
37966
  function readFile2(path53) {
37425
- if (!existsSync30(path53)) return {};
37967
+ if (!existsSync31(path53)) return {};
37426
37968
  try {
37427
- const parsed = JSON.parse(readFileSync26(path53, "utf8"));
37969
+ const parsed = JSON.parse(readFileSync27(path53, "utf8"));
37428
37970
  const out = {};
37429
37971
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
37430
37972
  if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
@@ -37441,9 +37983,9 @@ function readFile2(path53) {
37441
37983
  }
37442
37984
  }
37443
37985
  function writeFile(path53, servers) {
37444
- mkdirSync15(dirname6(path53), { recursive: true });
37986
+ mkdirSync16(dirname7(path53), { recursive: true });
37445
37987
  const body = { mcpServers: servers };
37446
- writeFileSync17(path53, `${JSON.stringify(body, null, 2)}
37988
+ writeFileSync18(path53, `${JSON.stringify(body, null, 2)}
37447
37989
  `, "utf8");
37448
37990
  }
37449
37991
  function listMcpServers(projectRoot) {
@@ -37652,22 +38194,22 @@ __export(mcpManager_exports, {
37652
38194
  readMcpConfig: () => readMcpConfig,
37653
38195
  registerMcpTools: () => registerMcpTools
37654
38196
  });
37655
- import { existsSync as existsSync31, readFileSync as readFileSync27 } from "node:fs";
37656
- 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";
37657
38199
  import { homedir as homedir11 } from "node:os";
37658
38200
  function readMcpConfig(projectRoot = process.cwd(), opts) {
37659
38201
  const merged = {};
37660
38202
  const paths = [];
37661
38203
  if (process.env["ZELARI_MCP_USER"] !== "0") {
37662
- paths.push(join26(homedir11(), ".zelari-code", "mcp.json"));
38204
+ paths.push(join27(homedir11(), ".zelari-code", "mcp.json"));
37663
38205
  }
37664
38206
  if (!opts?.skipProjectMcp) {
37665
- paths.push(join26(projectRoot, ".zelari", "mcp.json"));
38207
+ paths.push(join27(projectRoot, ".zelari", "mcp.json"));
37666
38208
  }
37667
38209
  for (const p3 of paths) {
37668
- if (!existsSync31(p3)) continue;
38210
+ if (!existsSync32(p3)) continue;
37669
38211
  try {
37670
- const parsed = JSON.parse(readFileSync27(p3, "utf8"));
38212
+ const parsed = JSON.parse(readFileSync28(p3, "utf8"));
37671
38213
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
37672
38214
  if (!cfg || typeof cfg.command !== "string" || cfg.command.length === 0) continue;
37673
38215
  merged[name] = cfg;
@@ -37681,7 +38223,7 @@ async function ensureLoaded(projectRoot) {
37681
38223
  if (state2.loaded) return;
37682
38224
  state2.loaded = true;
37683
38225
  const trusted = isFolderTrusted(projectRoot);
37684
- if (!trusted && existsSync31(join26(projectRoot, ".zelari", "mcp.json"))) {
38226
+ if (!trusted && existsSync32(join27(projectRoot, ".zelari", "mcp.json"))) {
37685
38227
  state2.warnings.push(
37686
38228
  "[mcp] project .zelari/mcp.json ignored \u2014 folder not trusted (run /trust or `zelari-code --trust` to enable project MCP)"
37687
38229
  );
@@ -37906,13 +38448,13 @@ __export(agentsMd_exports, {
37906
38448
  serializeAgentsMd: () => serializeAgentsMd,
37907
38449
  updateAgentsMd: () => updateAgentsMd
37908
38450
  });
37909
- import { existsSync as existsSync32, readFileSync as readFileSync28, writeFileSync as writeFileSync18 } from "node:fs";
37910
- import { createHash as createHash7 } from "node:crypto";
37911
- import { join as join27 } from "node:path";
38451
+ import { existsSync as existsSync33, readFileSync as readFileSync29, writeFileSync as writeFileSync19 } from "node:fs";
38452
+ import { createHash as createHash8 } from "node:crypto";
38453
+ import { join as join28 } from "node:path";
37912
38454
  import { readFile as readFile3 } from "node:fs/promises";
37913
38455
  async function readPackageJson2(projectRoot) {
37914
- const path53 = join27(projectRoot, "package.json");
37915
- if (!existsSync32(path53)) return null;
38456
+ const path53 = join28(projectRoot, "package.json");
38457
+ if (!existsSync33(path53)) return null;
37916
38458
  try {
37917
38459
  return JSON.parse(await readFile3(path53, "utf8"));
37918
38460
  } catch {
@@ -37936,8 +38478,8 @@ async function genTechStack(ctx) {
37936
38478
  ].join("\n");
37937
38479
  }
37938
38480
  async function genDecisions(ctx) {
37939
- const decisionsDir = join27(ctx.rootDir, "decisions");
37940
- if (!existsSync32(decisionsDir)) return "_No ADRs yet._";
38481
+ const decisionsDir = join28(ctx.rootDir, "decisions");
38482
+ if (!existsSync33(decisionsDir)) return "_No ADRs yet._";
37941
38483
  const files = ctx.storage.listMarkdown(decisionsDir).sort();
37942
38484
  const accepted = [];
37943
38485
  const proposed = [];
@@ -37962,9 +38504,9 @@ async function genDecisions(ctx) {
37962
38504
  }
37963
38505
  async function genConventions(ctx) {
37964
38506
  const lines = [];
37965
- const claudeMd = join27(ctx.projectRoot, "CLAUDE.MD");
37966
- if (existsSync32(claudeMd)) {
37967
- const content = readFileSync28(claudeMd, "utf8");
38507
+ const claudeMd = join28(ctx.projectRoot, "CLAUDE.MD");
38508
+ if (existsSync33(claudeMd)) {
38509
+ const content = readFileSync29(claudeMd, "utf8");
37968
38510
  const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
37969
38511
  if (match) {
37970
38512
  lines.push('<!-- Extracted from CLAUDE.MD "Architecture rules" -->');
@@ -37996,9 +38538,9 @@ async function genBuild(ctx) {
37996
38538
  ].join("\n");
37997
38539
  }
37998
38540
  async function genOpenQuestions(ctx) {
37999
- const path53 = join27(ctx.rootDir, "risks.md");
38000
- if (!existsSync32(path53)) return "_No open questions._";
38001
- 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");
38002
38544
  const lines = content.split("\n");
38003
38545
  const questions = [];
38004
38546
  let currentTitle = "";
@@ -38072,9 +38614,9 @@ function titleCase(id) {
38072
38614
  return id.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
38073
38615
  }
38074
38616
  async function updateAgentsMd(ctx, projectRoot) {
38075
- const agentsPath = join27(projectRoot, "AGENTS.MD");
38076
- if (existsSync32(agentsPath)) {
38077
- const content = readFileSync28(agentsPath, "utf8");
38617
+ const agentsPath = join28(projectRoot, "AGENTS.MD");
38618
+ if (existsSync33(agentsPath)) {
38619
+ const content = readFileSync29(agentsPath, "utf8");
38078
38620
  const hasAnyMarker = AUTO_SECTIONS.some((id) => content.includes(MARKER_OPEN(id)));
38079
38621
  if (!hasAnyMarker) {
38080
38622
  return {
@@ -38089,8 +38631,8 @@ async function updateAgentsMd(ctx, projectRoot) {
38089
38631
  newSections.set(id, await GENERATORS[id](ctx));
38090
38632
  }
38091
38633
  let manualContent = "";
38092
- if (existsSync32(agentsPath)) {
38093
- const { manualBlocks } = parseAgentsMd(readFileSync28(agentsPath, "utf8"));
38634
+ if (existsSync33(agentsPath)) {
38635
+ const { manualBlocks } = parseAgentsMd(readFileSync29(agentsPath, "utf8"));
38094
38636
  manualContent = manualBlocks.after;
38095
38637
  } else {
38096
38638
  const projectName2 = projectName(projectRoot);
@@ -38106,7 +38648,7 @@ async function updateAgentsMd(ctx, projectRoot) {
38106
38648
  ""
38107
38649
  ].join("\n");
38108
38650
  }
38109
- const oldContent = existsSync32(agentsPath) ? readFileSync28(agentsPath, "utf8") : "";
38651
+ const oldContent = existsSync33(agentsPath) ? readFileSync29(agentsPath, "utf8") : "";
38110
38652
  const { sections: oldSections } = parseAgentsMd(oldContent);
38111
38653
  const changedSections = [];
38112
38654
  for (const id of AUTO_SECTIONS) {
@@ -38118,11 +38660,11 @@ async function updateAgentsMd(ctx, projectRoot) {
38118
38660
  return { changed: false, sections: [] };
38119
38661
  }
38120
38662
  const newContent = serializeAgentsMd(manualContent, newSections);
38121
- writeFileSync18(agentsPath, newContent, "utf8");
38663
+ writeFileSync19(agentsPath, newContent, "utf8");
38122
38664
  return { changed: true, sections: changedSections };
38123
38665
  }
38124
38666
  function hash2(s) {
38125
- return createHash7("sha256").update(s).digest("hex").slice(0, 16);
38667
+ return createHash8("sha256").update(s).digest("hex").slice(0, 16);
38126
38668
  }
38127
38669
  var AUTO_SECTIONS, MARKER_OPEN, MARKER_CLOSE, GENERATORS;
38128
38670
  var init_agentsMd = __esm({
@@ -38244,8 +38786,8 @@ var init_completeDesign = __esm({
38244
38786
 
38245
38787
  // src/cli/workspace/projectSmoke.ts
38246
38788
  import { spawn as spawn10 } from "node:child_process";
38247
- import { existsSync as existsSync33, readFileSync as readFileSync29 } from "node:fs";
38248
- import { join as join28 } from "node:path";
38789
+ import { existsSync as existsSync34, readFileSync as readFileSync30 } from "node:fs";
38790
+ import { join as join29 } from "node:path";
38249
38791
  function pickSmokeScript(scripts) {
38250
38792
  if (!scripts) return null;
38251
38793
  for (const name of SMOKE_SCRIPT_PRIORITY) {
@@ -38257,13 +38799,13 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS2) {
38257
38799
  if (process.env["ZELARI_SMOKE"] === "0") {
38258
38800
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
38259
38801
  }
38260
- const pkgPath = join28(projectRoot, "package.json");
38261
- if (!existsSync33(pkgPath)) {
38802
+ const pkgPath = join29(projectRoot, "package.json");
38803
+ if (!existsSync34(pkgPath)) {
38262
38804
  return { ran: false, reason: "no package.json (skipped)" };
38263
38805
  }
38264
38806
  let scripts = {};
38265
38807
  try {
38266
- const pkg = JSON.parse(readFileSync29(pkgPath, "utf8"));
38808
+ const pkg = JSON.parse(readFileSync30(pkgPath, "utf8"));
38267
38809
  scripts = pkg.scripts ?? {};
38268
38810
  } catch {
38269
38811
  return { ran: false, reason: "package.json unreadable (skipped)" };
@@ -38351,8 +38893,8 @@ __export(postCouncilHook_exports, {
38351
38893
  runPostCouncilHook: () => runPostCouncilHook
38352
38894
  });
38353
38895
  import { spawn as spawn11 } from "node:child_process";
38354
- import { existsSync as existsSync34, readFileSync as readFileSync30 } from "node:fs";
38355
- import { join as join29 } from "node:path";
38896
+ import { existsSync as existsSync35, readFileSync as readFileSync31 } from "node:fs";
38897
+ import { join as join30 } from "node:path";
38356
38898
  async function runCompleteDesignPostProcessor(ctx, options) {
38357
38899
  if (options?.runMode === "implementation") {
38358
38900
  return {
@@ -38363,9 +38905,9 @@ async function runCompleteDesignPostProcessor(ctx, options) {
38363
38905
  if (process.env["ZELARI_COMPLETE_DESIGN"] === "0") {
38364
38906
  return { ran: false, reason: "ZELARI_COMPLETE_DESIGN=0 (disabled)" };
38365
38907
  }
38366
- const planJsonPath2 = join29(ctx.rootDir, "plan.json");
38367
- const scriptPath = join29(ctx.projectRoot, "complete-design.mjs");
38368
- if (!existsSync34(planJsonPath2)) {
38908
+ const planJsonPath2 = join30(ctx.rootDir, "plan.json");
38909
+ const scriptPath = join30(ctx.projectRoot, "complete-design.mjs");
38910
+ if (!existsSync35(planJsonPath2)) {
38369
38911
  return {
38370
38912
  ran: false,
38371
38913
  reason: ".zelari/plan.json missing (not design-phase)"
@@ -38373,7 +38915,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
38373
38915
  }
38374
38916
  let phaseCount = 0;
38375
38917
  try {
38376
- const parsed = JSON.parse(readFileSync30(planJsonPath2, "utf8"));
38918
+ const parsed = JSON.parse(readFileSync31(planJsonPath2, "utf8"));
38377
38919
  phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
38378
38920
  } catch {
38379
38921
  return { ran: false, reason: ".zelari/plan.json corrupt" };
@@ -38381,7 +38923,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
38381
38923
  if (phaseCount === 0) {
38382
38924
  return { ran: false, reason: ".zelari/plan.json has no phases" };
38383
38925
  }
38384
- if (!existsSync34(scriptPath)) {
38926
+ if (!existsSync35(scriptPath)) {
38385
38927
  try {
38386
38928
  const builtin = await runBuiltinCompleteDesign(ctx);
38387
38929
  return {
@@ -38604,10 +39146,10 @@ __export(councilFeedback_exports, {
38604
39146
  });
38605
39147
  import {
38606
39148
  promises as fs16,
38607
- existsSync as existsSync35,
38608
- readFileSync as readFileSync31,
38609
- writeFileSync as writeFileSync19,
38610
- mkdirSync as mkdirSync16
39149
+ existsSync as existsSync36,
39150
+ readFileSync as readFileSync32,
39151
+ writeFileSync as writeFileSync20,
39152
+ mkdirSync as mkdirSync17
38611
39153
  } from "node:fs";
38612
39154
  import path32 from "node:path";
38613
39155
  import os9 from "node:os";
@@ -38713,9 +39255,9 @@ var init_councilFeedback = __esm({
38713
39255
  }
38714
39256
  // --- persistence ---------------------------------------------------------
38715
39257
  load() {
38716
- if (!existsSync35(this.file)) return;
39258
+ if (!existsSync36(this.file)) return;
38717
39259
  try {
38718
- const raw = readFileSync31(this.file, "utf-8");
39260
+ const raw = readFileSync32(this.file, "utf-8");
38719
39261
  const parsed = JSON.parse(raw);
38720
39262
  if (parsed && Array.isArray(parsed.entries)) {
38721
39263
  this.entries = parsed.entries.filter(
@@ -38726,8 +39268,8 @@ var init_councilFeedback = __esm({
38726
39268
  }
38727
39269
  }
38728
39270
  save() {
38729
- mkdirSync16(path32.dirname(this.file), { recursive: true });
38730
- writeFileSync19(
39271
+ mkdirSync17(path32.dirname(this.file), { recursive: true });
39272
+ writeFileSync20(
38731
39273
  this.file,
38732
39274
  JSON.stringify({ entries: this.entries }, null, 2),
38733
39275
  { encoding: "utf-8", mode: 384 }
@@ -38933,7 +39475,7 @@ __export(commitHelpers_exports, {
38933
39475
  });
38934
39476
  async function tryStateCommit(args) {
38935
39477
  try {
38936
- const store3 = args.store ?? await getStateStore(args.projectRoot, args.env);
39478
+ const store4 = args.store ?? await getStateStore(args.projectRoot, args.env);
38937
39479
  let workspaceCheckpointId = args.workspaceCheckpointId;
38938
39480
  if (!workspaceCheckpointId && args.withCheckpoint && (args.env ?? process.env).ZELARI_CHECKPOINT !== "0") {
38939
39481
  const cp = await createCheckpoint(
@@ -38942,7 +39484,7 @@ async function tryStateCommit(args) {
38942
39484
  );
38943
39485
  if (cp.ok) workspaceCheckpointId = cp.value.id;
38944
39486
  }
38945
- const meta3 = await store3.commit({
39487
+ const meta3 = await store4.commit({
38946
39488
  mode: args.mode,
38947
39489
  label: args.label,
38948
39490
  layer: args.layer,
@@ -41153,7 +41695,7 @@ __export(executor_exports, {
41153
41695
  resolveNodeTimeoutMs: () => resolveNodeTimeoutMs,
41154
41696
  thoroughnessForKind: () => thoroughnessForKind
41155
41697
  });
41156
- import { existsSync as existsSync36 } from "node:fs";
41698
+ import { existsSync as existsSync37 } from "node:fs";
41157
41699
  import path40 from "node:path";
41158
41700
  function resolveMaxParallel(env = process.env) {
41159
41701
  const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
@@ -41212,7 +41754,7 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
41212
41754
  }
41213
41755
  function defaultChecksExists(cwd) {
41214
41756
  try {
41215
- return existsSync36(path40.join(cwd, ".zelari", "world", "checks.json"));
41757
+ return existsSync37(path40.join(cwd, ".zelari", "world", "checks.json"));
41216
41758
  } catch {
41217
41759
  return false;
41218
41760
  }
@@ -42179,8 +42721,8 @@ __export(prereqChecks_exports, {
42179
42721
  runPrereqChecks: () => runPrereqChecks
42180
42722
  });
42181
42723
  import { execSync, spawnSync as spawnSync2 } from "node:child_process";
42182
- import { existsSync as existsSync37 } from "node:fs";
42183
- import { dirname as dirname7 } from "node:path";
42724
+ import { existsSync as existsSync38 } from "node:fs";
42725
+ import { dirname as dirname8 } from "node:path";
42184
42726
  function isWslBashPath2(p3) {
42185
42727
  if (!p3 || typeof p3 !== "string") return false;
42186
42728
  const n = p3.replace(/\//g, "\\").toLowerCase();
@@ -42286,7 +42828,7 @@ function resolveAgentShellSync() {
42286
42828
  function agentProbeEnv() {
42287
42829
  const env = { ...process.env };
42288
42830
  try {
42289
- const nodeDir = dirname7(process.execPath);
42831
+ const nodeDir = dirname8(process.execPath);
42290
42832
  if (!nodeDir) return env;
42291
42833
  const sep2 = process.platform === "win32" ? ";" : ":";
42292
42834
  const current = env.PATH ?? env.Path ?? "";
@@ -42303,7 +42845,7 @@ function agentProbeEnv() {
42303
42845
  }
42304
42846
  function existsSyncSafe2(p3) {
42305
42847
  try {
42306
- return existsSync37(p3);
42848
+ return existsSync38(p3);
42307
42849
  } catch {
42308
42850
  return false;
42309
42851
  }
@@ -42550,7 +43092,7 @@ var init_prereqChecks = __esm({
42550
43092
  });
42551
43093
 
42552
43094
  // src/cli/plugins/prefs.ts
42553
- import { existsSync as existsSync38, readFileSync as readFileSync32, writeFileSync as writeFileSync20, mkdirSync as mkdirSync17 } from "node:fs";
43095
+ import { existsSync as existsSync39, readFileSync as readFileSync33, writeFileSync as writeFileSync21, mkdirSync as mkdirSync18 } from "node:fs";
42554
43096
  import path43 from "node:path";
42555
43097
  import os10 from "node:os";
42556
43098
  function getPluginPrefsPath() {
@@ -42559,8 +43101,8 @@ function getPluginPrefsPath() {
42559
43101
  function getPluginPrefs() {
42560
43102
  const file2 = getPluginPrefsPath();
42561
43103
  try {
42562
- if (!existsSync38(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
42563
- const raw = readFileSync32(file2, "utf-8");
43104
+ if (!existsSync39(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
43105
+ const raw = readFileSync33(file2, "utf-8");
42564
43106
  const parsed = JSON.parse(raw);
42565
43107
  if (parsed && typeof parsed === "object" && parsed.dontAskAgain && typeof parsed.dontAskAgain === "object") {
42566
43108
  const clean = {};
@@ -42575,8 +43117,8 @@ function getPluginPrefs() {
42575
43117
  }
42576
43118
  function writePluginPrefs(prefs) {
42577
43119
  const file2 = getPluginPrefsPath();
42578
- mkdirSync17(path43.dirname(file2), { recursive: true });
42579
- writeFileSync20(file2, JSON.stringify(prefs, null, 2), {
43120
+ mkdirSync18(path43.dirname(file2), { recursive: true });
43121
+ writeFileSync21(file2, JSON.stringify(prefs, null, 2), {
42580
43122
  encoding: "utf-8",
42581
43123
  mode: 384
42582
43124
  });
@@ -42611,7 +43153,7 @@ __export(registry_exports, {
42611
43153
  findPlugin: () => findPlugin,
42612
43154
  isBinaryOnPath: () => isBinaryOnPath
42613
43155
  });
42614
- import { existsSync as existsSync39 } from "node:fs";
43156
+ import { existsSync as existsSync40 } from "node:fs";
42615
43157
  import path44 from "node:path";
42616
43158
  function detectLocalBin(bin) {
42617
43159
  return (cwd) => {
@@ -42628,7 +43170,7 @@ function isBinaryOnPath(bin, opts = {}) {
42628
43170
  return false;
42629
43171
  }
42630
43172
  const platform = opts.platform ?? process.platform;
42631
- const exists = opts.exists ?? existsSync39;
43173
+ const exists = opts.exists ?? existsSync40;
42632
43174
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
42633
43175
  const pathMod = platform === "win32" ? path44.win32 : path44.posix;
42634
43176
  const sep2 = platform === "win32" ? ";" : ":";
@@ -43362,7 +43904,7 @@ __export(atMentions_exports, {
43362
43904
  extractAtMentions: () => extractAtMentions,
43363
43905
  hasAtMentions: () => hasAtMentions
43364
43906
  });
43365
- import { existsSync as existsSync42, readFileSync as readFileSync34, statSync as statSync7 } from "node:fs";
43907
+ import { existsSync as existsSync43, readFileSync as readFileSync35, statSync as statSync7 } from "node:fs";
43366
43908
  import { basename as basename3, isAbsolute as isAbsolute2, relative as relative3, resolve, sep } from "node:path";
43367
43909
  function isImagePath(abs) {
43368
43910
  const ext = abs.split(".").pop()?.toLowerCase() ?? "";
@@ -43416,7 +43958,7 @@ function resolveMention(token, cwd) {
43416
43958
  note: "outside project root \u2014 skipped"
43417
43959
  };
43418
43960
  }
43419
- if (!existsSync42(abs)) {
43961
+ if (!existsSync43(abs)) {
43420
43962
  return {
43421
43963
  raw: token,
43422
43964
  path: token,
@@ -43468,7 +44010,7 @@ function resolveMention(token, cwd) {
43468
44010
  note: `image too large (${Math.round(st.size / 1024)} KB) \u2014 path only`
43469
44011
  };
43470
44012
  }
43471
- const dataBase64 = readFileSync34(abs).toString("base64");
44013
+ const dataBase64 = readFileSync35(abs).toString("base64");
43472
44014
  return {
43473
44015
  raw: token,
43474
44016
  path: rel2,
@@ -43479,7 +44021,7 @@ function resolveMention(token, cwd) {
43479
44021
  };
43480
44022
  }
43481
44023
  try {
43482
- const buf = readFileSync34(abs);
44024
+ const buf = readFileSync35(abs);
43483
44025
  const head = buf.subarray(0, 800).toString("utf8");
43484
44026
  if (!isProbablyText(abs, head)) {
43485
44027
  return {
@@ -43976,14 +44518,14 @@ var init_skillCategories = __esm({
43976
44518
 
43977
44519
  // src/cli/skillConfigIo.ts
43978
44520
  import {
43979
- existsSync as existsSync44,
43980
- mkdirSync as mkdirSync20,
44521
+ existsSync as existsSync45,
44522
+ mkdirSync as mkdirSync21,
43981
44523
  readdirSync as readdirSync9,
43982
- readFileSync as readFileSync36,
44524
+ readFileSync as readFileSync37,
43983
44525
  rmSync as rmSync4,
43984
- writeFileSync as writeFileSync22
44526
+ writeFileSync as writeFileSync23
43985
44527
  } from "node:fs";
43986
- import { dirname as dirname9, join as join35 } from "node:path";
44528
+ import { dirname as dirname10, join as join36 } from "node:path";
43987
44529
  import { homedir as homedir12 } from "node:os";
43988
44530
  function ensureBuiltinSkillsLoadedSync() {
43989
44531
  if (builtinsLoaded) return;
@@ -43995,13 +44537,13 @@ function ensureBuiltinSkillsLoadedSync() {
43995
44537
  }
43996
44538
  }
43997
44539
  function getUserSkillsDir() {
43998
- return join35(homedir12(), ".zelari-code", "skills");
44540
+ return join36(homedir12(), ".zelari-code", "skills");
43999
44541
  }
44000
44542
  function getProjectSkillsDir(projectRoot) {
44001
- return join35(projectRoot, ".zelari", "skills");
44543
+ return join36(projectRoot, ".zelari", "skills");
44002
44544
  }
44003
44545
  function skillFilePath(dir, name) {
44004
- return join35(dir, name, "SKILL.md");
44546
+ return join36(dir, name, "SKILL.md");
44005
44547
  }
44006
44548
  function classifyScope(skillPath, projectRoot) {
44007
44549
  const userDir = getUserSkillsDir().replace(/\\/g, "/");
@@ -44050,7 +44592,7 @@ function entryFromBuiltin(skill) {
44050
44592
  };
44051
44593
  }
44052
44594
  function scanSkillsDir(dir, projectRoot, seen, out) {
44053
- if (!existsSync44(dir)) return;
44595
+ if (!existsSync45(dir)) return;
44054
44596
  let entries;
44055
44597
  try {
44056
44598
  entries = readdirSync9(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -44059,9 +44601,9 @@ function scanSkillsDir(dir, projectRoot, seen, out) {
44059
44601
  }
44060
44602
  for (const entry of entries) {
44061
44603
  const skillPath = skillFilePath(dir, entry);
44062
- if (!existsSync44(skillPath)) continue;
44604
+ if (!existsSync45(skillPath)) continue;
44063
44605
  try {
44064
- const parsed = parseSkillMd(readFileSync36(skillPath, "utf8"), skillPath);
44606
+ const parsed = parseSkillMd(readFileSync37(skillPath, "utf8"), skillPath);
44065
44607
  if (!parsed) continue;
44066
44608
  if (seen.has(parsed.name)) continue;
44067
44609
  seen.add(parsed.name);
@@ -44077,9 +44619,9 @@ function listSkillsSnapshot(projectRoot) {
44077
44619
  const skills = [];
44078
44620
  const seen = /* @__PURE__ */ new Set();
44079
44621
  if (root) {
44080
- scanSkillsDir(join35(root, ".zelari", "skills"), root, seen, skills);
44081
- scanSkillsDir(join35(root, ".claude", "skills"), root, seen, skills);
44082
- scanSkillsDir(join35(root, ".opencode", "skills"), root, seen, skills);
44622
+ scanSkillsDir(join36(root, ".zelari", "skills"), root, seen, skills);
44623
+ scanSkillsDir(join36(root, ".claude", "skills"), root, seen, skills);
44624
+ scanSkillsDir(join36(root, ".opencode", "skills"), root, seen, skills);
44083
44625
  }
44084
44626
  scanSkillsDir(userSkillsDir, root, seen, skills);
44085
44627
  for (const s of listCodingSkills()) {
@@ -44158,8 +44700,8 @@ function upsertSkill(opts) {
44158
44700
  if (!parsed) {
44159
44701
  return { ok: false, error: "Generated SKILL.md failed validation" };
44160
44702
  }
44161
- mkdirSync20(dirname9(path53), { recursive: true });
44162
- writeFileSync22(path53, content, "utf8");
44703
+ mkdirSync21(dirname10(path53), { recursive: true });
44704
+ writeFileSync23(path53, content, "utf8");
44163
44705
  return { ok: true, path: path53 };
44164
44706
  }
44165
44707
  function removeSkill(opts) {
@@ -44177,9 +44719,9 @@ function removeSkill(opts) {
44177
44719
  }
44178
44720
  dir = getProjectSkillsDir(root);
44179
44721
  }
44180
- const skillDir = join35(dir, name);
44722
+ const skillDir = join36(dir, name);
44181
44723
  const path53 = skillFilePath(dir, name);
44182
- if (!existsSync44(path53) && !existsSync44(skillDir)) {
44724
+ if (!existsSync45(path53) && !existsSync45(skillDir)) {
44183
44725
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
44184
44726
  }
44185
44727
  try {
@@ -44484,36 +45026,36 @@ var init_permissionCli = __esm({
44484
45026
 
44485
45027
  // src/cli/companion/config.ts
44486
45028
  import {
44487
- existsSync as existsSync45,
44488
- mkdirSync as mkdirSync21,
44489
- readFileSync as readFileSync37,
44490
- writeFileSync as writeFileSync23
45029
+ existsSync as existsSync46,
45030
+ mkdirSync as mkdirSync22,
45031
+ readFileSync as readFileSync38,
45032
+ writeFileSync as writeFileSync24
44491
45033
  } from "node:fs";
44492
- import { join as join36 } from "node:path";
45034
+ import { join as join37 } from "node:path";
44493
45035
  import { homedir as homedir13 } from "node:os";
44494
- import { createHash as createHash8, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
45036
+ import { createHash as createHash9, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
44495
45037
  function getZelariHome() {
44496
- return join36(homedir13(), ".zelari-code");
45038
+ return join37(homedir13(), ".zelari-code");
44497
45039
  }
44498
45040
  function getCompanionConfigPath() {
44499
- return join36(getZelariHome(), "companion.json");
45041
+ return join37(getZelariHome(), "companion.json");
44500
45042
  }
44501
45043
  function getCompanionTokenPath() {
44502
- return join36(getZelariHome(), "companion.token");
45044
+ return join37(getZelariHome(), "companion.token");
44503
45045
  }
44504
45046
  function ensureHome() {
44505
45047
  const home = getZelariHome();
44506
- if (!existsSync45(home)) {
44507
- mkdirSync21(home, { recursive: true });
45048
+ if (!existsSync46(home)) {
45049
+ mkdirSync22(home, { recursive: true });
44508
45050
  }
44509
45051
  }
44510
45052
  function loadCompanionConfig() {
44511
45053
  const path53 = getCompanionConfigPath();
44512
- if (!existsSync45(path53)) {
45054
+ if (!existsSync46(path53)) {
44513
45055
  return { projects: [] };
44514
45056
  }
44515
45057
  try {
44516
- const raw = JSON.parse(readFileSync37(path53, "utf8"));
45058
+ const raw = JSON.parse(readFileSync38(path53, "utf8"));
44517
45059
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
44518
45060
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
44519
45061
  ).map((p3) => ({
@@ -44532,7 +45074,7 @@ function loadCompanionConfig() {
44532
45074
  }
44533
45075
  function saveCompanionConfig(cfg) {
44534
45076
  ensureHome();
44535
- writeFileSync23(
45077
+ writeFileSync24(
44536
45078
  getCompanionConfigPath(),
44537
45079
  JSON.stringify(
44538
45080
  {
@@ -44552,12 +45094,12 @@ function loadOrCreateToken(explicit) {
44552
45094
  }
44553
45095
  ensureHome();
44554
45096
  const path53 = getCompanionTokenPath();
44555
- if (existsSync45(path53)) {
44556
- const t = readFileSync37(path53, "utf8").trim();
45097
+ if (existsSync46(path53)) {
45098
+ const t = readFileSync38(path53, "utf8").trim();
44557
45099
  if (t) return { token: t, created: false };
44558
45100
  }
44559
45101
  const token = randomBytes5(24).toString("base64url");
44560
- writeFileSync23(path53, token + "\n", "utf8");
45102
+ writeFileSync24(path53, token + "\n", "utf8");
44561
45103
  try {
44562
45104
  const fs31 = __require("node:fs");
44563
45105
  fs31.chmodSync?.(path53, 384);
@@ -44567,8 +45109,8 @@ function loadOrCreateToken(explicit) {
44567
45109
  }
44568
45110
  function tokenMatches(expected, provided) {
44569
45111
  if (!provided) return false;
44570
- const a = createHash8("sha256").update(expected).digest();
44571
- const b = createHash8("sha256").update(provided).digest();
45112
+ const a = createHash9("sha256").update(expected).digest();
45113
+ const b = createHash9("sha256").update(provided).digest();
44572
45114
  try {
44573
45115
  return timingSafeEqual(a, b);
44574
45116
  } catch {
@@ -44646,8 +45188,8 @@ var init_config = __esm({
44646
45188
  import { spawn as spawn13 } from "node:child_process";
44647
45189
  import { createInterface as createInterface2 } from "node:readline";
44648
45190
  import { randomUUID as randomUUID8 } from "node:crypto";
44649
- import { writeFileSync as writeFileSync24, unlinkSync as unlinkSync3 } from "node:fs";
44650
- import { join as join37 } from "node:path";
45191
+ import { writeFileSync as writeFileSync25, unlinkSync as unlinkSync3 } from "node:fs";
45192
+ import { join as join38 } from "node:path";
44651
45193
  import { tmpdir as tmpdir3 } from "node:os";
44652
45194
  var RunManager;
44653
45195
  var init_runManager = __esm({
@@ -44751,9 +45293,9 @@ var init_runManager = __esm({
44751
45293
  }
44752
45294
  let historyFile;
44753
45295
  if (args.history && Array.isArray(args.history) && args.history.length > 0) {
44754
- historyFile = join37(tmpdir3(), `zelari-companion-hist-${id}.json`);
45296
+ historyFile = join38(tmpdir3(), `zelari-companion-hist-${id}.json`);
44755
45297
  try {
44756
- writeFileSync24(historyFile, JSON.stringify(args.history), "utf8");
45298
+ writeFileSync25(historyFile, JSON.stringify(args.history), "utf8");
44757
45299
  argv.push("--history-file", historyFile);
44758
45300
  } catch {
44759
45301
  historyFile = void 0;
@@ -44878,7 +45420,7 @@ __export(serve_exports, {
44878
45420
  runCompanionServe: () => runCompanionServe
44879
45421
  });
44880
45422
  import { createServer as createServer3 } from "node:http";
44881
- import { existsSync as existsSync46 } from "node:fs";
45423
+ import { existsSync as existsSync47 } from "node:fs";
44882
45424
  import { resolve as resolve2 } from "node:path";
44883
45425
  function readBody(req, max = 2e6) {
44884
45426
  return new Promise((resolveBody, reject) => {
@@ -44926,7 +45468,7 @@ async function runCompanionServe(opts = {}) {
44926
45468
  let projects = mergeProjects(fileCfg, opts.projects ?? []);
44927
45469
  projects = projects.filter((p3) => {
44928
45470
  const abs = resolve2(p3.path);
44929
- if (!existsSync46(abs)) {
45471
+ if (!existsSync47(abs)) {
44930
45472
  process.stderr.write(
44931
45473
  `[zelari-code serve] skip missing project path: ${p3.path}
44932
45474
  `
@@ -45248,7 +45790,7 @@ __export(doctor_exports, {
45248
45790
  runDoctor: () => runDoctor
45249
45791
  });
45250
45792
  import { execSync as execSync2 } from "node:child_process";
45251
- import { existsSync as existsSync47, readFileSync as readFileSync38, readlinkSync, statSync as statSync8 } from "node:fs";
45793
+ import { existsSync as existsSync48, readFileSync as readFileSync39, readlinkSync, statSync as statSync8 } from "node:fs";
45252
45794
  import { createRequire as createRequire3 } from "node:module";
45253
45795
  import { fileURLToPath as fileURLToPath2 } from "node:url";
45254
45796
  import path51 from "node:path";
@@ -45256,9 +45798,9 @@ function findPackageRoot(start) {
45256
45798
  let dir = start;
45257
45799
  for (let i = 0; i < 6; i += 1) {
45258
45800
  const candidate = path51.join(dir, "package.json");
45259
- if (existsSync47(candidate)) {
45801
+ if (existsSync48(candidate)) {
45260
45802
  try {
45261
- const pkg = JSON.parse(readFileSync38(candidate, "utf8"));
45803
+ const pkg = JSON.parse(readFileSync39(candidate, "utf8"));
45262
45804
  if (pkg.name === "zelari-code") return dir;
45263
45805
  } catch {
45264
45806
  }
@@ -45282,7 +45824,7 @@ function tryExec(cmd) {
45282
45824
  function readPackageJson3() {
45283
45825
  try {
45284
45826
  const pkgPath = path51.join(packageRoot, "package.json");
45285
- return JSON.parse(readFileSync38(pkgPath, "utf8"));
45827
+ return JSON.parse(readFileSync39(pkgPath, "utf8"));
45286
45828
  } catch {
45287
45829
  return null;
45288
45830
  }
@@ -45298,7 +45840,7 @@ function checkShim(pkgName) {
45298
45840
  const isWin = process.platform === "win32";
45299
45841
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
45300
45842
  const shimPath = path51.join(prefix, shimName);
45301
- if (!existsSync47(shimPath)) {
45843
+ if (!existsSync48(shimPath)) {
45302
45844
  return FAIL(
45303
45845
  `shim not found at ${shimPath}
45304
45846
  fix: npm install -g ${pkgName}@latest --force`
@@ -45307,7 +45849,7 @@ function checkShim(pkgName) {
45307
45849
  try {
45308
45850
  const st = statSync8(shimPath);
45309
45851
  if (isWin) {
45310
- const content = readFileSync38(shimPath, "utf8");
45852
+ const content = readFileSync39(shimPath, "utf8");
45311
45853
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
45312
45854
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
45313
45855
  }
@@ -45366,7 +45908,7 @@ function checkNode(pkg) {
45366
45908
  }
45367
45909
  function checkBundle() {
45368
45910
  const bundle = path51.join(packageRoot, "dist", "cli", "main.bundled.js");
45369
- if (!existsSync47(bundle)) {
45911
+ if (!existsSync48(bundle)) {
45370
45912
  return FAIL(
45371
45913
  `dist/cli/main.bundled.js missing at ${bundle}
45372
45914
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -45747,7 +46289,7 @@ __export(inspect_exports, {
45747
46289
  runInspect: () => runInspect
45748
46290
  });
45749
46291
  import path52 from "node:path";
45750
- import { existsSync as existsSync48, readFileSync as readFileSync39, readdirSync as readdirSync10 } from "node:fs";
46292
+ import { existsSync as existsSync49, readFileSync as readFileSync40, readdirSync as readdirSync10 } from "node:fs";
45751
46293
  import { homedir as homedir14 } from "node:os";
45752
46294
  async function collectInspectReport(cwd = process.cwd()) {
45753
46295
  ensureBuiltinSkillsLoadedSync();
@@ -45780,11 +46322,11 @@ async function collectInspectReport(cwd = process.cwd()) {
45780
46322
  folders: listTrustedFolders()
45781
46323
  },
45782
46324
  configSources: [
45783
- { path: userMcpPath, exists: existsSync48(userMcpPath) },
45784
- { path: projectMcpPath, exists: existsSync48(projectMcpPath) },
45785
- { path: path52.join(homedir14(), ".zelari-code", "provider.json"), exists: existsSync48(path52.join(homedir14(), ".zelari-code", "provider.json")) },
45786
- { path: path52.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync48(path52.join(cwd, ".zelari", "AGENTS.md")) },
45787
- { path: path52.join(cwd, "AGENTS.md"), exists: existsSync48(path52.join(cwd, "AGENTS.md")) }
46325
+ { path: userMcpPath, exists: existsSync49(userMcpPath) },
46326
+ { path: projectMcpPath, exists: existsSync49(projectMcpPath) },
46327
+ { path: path52.join(homedir14(), ".zelari-code", "provider.json"), exists: existsSync49(path52.join(homedir14(), ".zelari-code", "provider.json")) },
46328
+ { path: path52.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync49(path52.join(cwd, ".zelari", "AGENTS.md")) },
46329
+ { path: path52.join(cwd, "AGENTS.md"), exists: existsSync49(path52.join(cwd, "AGENTS.md")) }
45788
46330
  ],
45789
46331
  skills: {
45790
46332
  total: snap.skills.length,
@@ -45797,7 +46339,7 @@ async function collectInspectReport(cwd = process.cwd()) {
45797
46339
  user: mcp.servers.filter((s) => s.scope === "user").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
45798
46340
  project: mcp.servers.filter((s) => s.scope === "project").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
45799
46341
  projectTrusted,
45800
- projectConfigExists: existsSync48(projectMcpPath)
46342
+ projectConfigExists: existsSync49(projectMcpPath)
45801
46343
  },
45802
46344
  hooks: {
45803
46345
  global: {
@@ -45829,9 +46371,9 @@ function findAgentsMd(cwd) {
45829
46371
  ];
45830
46372
  const found = [];
45831
46373
  for (const c of candidates) {
45832
- if (existsSync48(c)) {
46374
+ if (existsSync49(c)) {
45833
46375
  try {
45834
- const text = readFileSync39(c, "utf8");
46376
+ const text = readFileSync40(c, "utf8");
45835
46377
  found.push(`${c} (${text.length} bytes)`);
45836
46378
  } catch {
45837
46379
  found.push(`${c} (unreadable)`);
@@ -49235,34 +49777,58 @@ function phaseKnobs(phase2) {
49235
49777
  })
49236
49778
  };
49237
49779
  }
49238
- function occupancyOf(hist, sessionExtra, contextLimit) {
49239
- const estimated = estimateHistoryTokens(hist);
49240
- const occupancy = Math.min(1, (estimated + sessionExtra) / contextLimit);
49241
- return { estimated, occupancy };
49242
- }
49780
+ var RESERVED_OUTPUT_TOKENS = 8192;
49243
49781
  async function applyBudgetPolicyAsync(history2, phase2, opts) {
49244
49782
  const contextLimit = resolveContextLimit(opts?.model);
49245
49783
  const sessionExtra = opts?.sessionTokens ?? 0;
49246
49784
  const warnings = [];
49247
49785
  let { historyTurns, maxToolLoopIterations } = phaseKnobs(phase2);
49786
+ const envelope = opts?.requestSnapshot ?? null;
49787
+ const replayBase = envelope ? {
49788
+ provider: envelope.snapshot.provider,
49789
+ model: envelope.snapshot.model,
49790
+ systemMessages: envelope.snapshot.systemMessages,
49791
+ tools: envelope.snapshot.tools
49792
+ } : opts?.providerStream ? { provider: "local", model: opts?.model ?? "unknown", systemMessages: [], tools: [] } : null;
49793
+ const headerTokens = envelope ? estimateSystemTokensLite(envelope.snapshot.systemMessages) + estimateToolSchemaTokensLite(envelope.snapshot.tools) : 0;
49794
+ const convTokensOf = (h) => estimateConversationTokensLite(h);
49248
49795
  let hist = history2;
49249
- let { estimated, occupancy } = occupancyOf(hist, sessionExtra, contextLimit);
49796
+ let estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
49797
+ let occupancy = Math.min(1, estimated / contextLimit);
49250
49798
  let compactSummary = "";
49251
49799
  let messagesRemoved = 0;
49800
+ let cacheReuseExpected;
49801
+ let prunedTotal = 0;
49252
49802
  if (occupancy >= 0.7 && occupancy < 0.85) {
49253
49803
  warnings.push(
49254
- `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated + sessionExtra}/${contextLimit} tok est.) \u2014 consider /compact or shorter replies.`
49804
+ `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated}/${contextLimit} tok full-request est.) \u2014 consider /compact or shorter replies.`
49255
49805
  );
49256
49806
  }
49807
+ if (occupancy >= 0.8) {
49808
+ const pruned = pruneToolResultsDetailed(hist);
49809
+ if (pruned.stats.pruned > 0) {
49810
+ hist = pruned.messages;
49811
+ prunedTotal += pruned.stats.pruned;
49812
+ estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
49813
+ occupancy = Math.min(1, estimated / contextLimit);
49814
+ warnings.push(
49815
+ `[budget] pruned ${pruned.stats.pruned} oversized tool result(s) \u2192 ${Math.round(occupancy * 100)}% (${estimated} tok).`
49816
+ );
49817
+ }
49818
+ }
49257
49819
  const fold = (r, label, forcedTurns) => {
49258
49820
  hist = r.messages;
49259
49821
  if (r.compacted) {
49260
49822
  messagesRemoved += r.messagesRemoved;
49261
49823
  if (r.summary) compactSummary = r.summary;
49824
+ if (r.cacheReuseExpected !== void 0) {
49825
+ cacheReuseExpected = r.cacheReuseExpected;
49826
+ }
49262
49827
  }
49263
- ({ estimated, occupancy } = occupancyOf(hist, sessionExtra, contextLimit));
49828
+ estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
49829
+ occupancy = Math.min(1, estimated / contextLimit);
49264
49830
  warnings.push(
49265
- `[budget] ${label} at ${forcedTurns === 2 ? "95%" : "85%"} \u2014 kept ~${forcedTurns} turns (${estimated} tok history est.` + (r.messagesRemoved ? `, removed ${r.messagesRemoved} msgs` : "") + `).`
49831
+ `[budget] ${label} \u2014 kept ~${forcedTurns} turns (${estimated} tok est.` + (r.messagesRemoved ? `, removed ${r.messagesRemoved} msgs` : "") + (r.cacheReuseExpected === false ? ", cache reuse NOT expected (model override)" : "") + ")."
49266
49832
  );
49267
49833
  };
49268
49834
  if (occupancy >= 0.85) {
@@ -49272,39 +49838,91 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
49272
49838
  maxToolLoopIterations,
49273
49839
  phase2 === "plan" ? 24 : 40
49274
49840
  );
49275
- const r = await compactHistoryAsync(hist, {
49276
- maxMessages: forcedTurns * 4,
49277
- signal: opts?.signal
49278
- });
49279
- const label = r.summary.includes("\xB7 llm") ? "llm-compact" : "auto-compact";
49841
+ let r = await compactHistoryAsync(hist, {
49842
+ maxMessages: Math.max(2, forcedTurns * 4),
49843
+ force: true,
49844
+ signal: opts?.signal,
49845
+ ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
49846
+ });
49847
+ if (!r.compacted) {
49848
+ r = await compactHistoryAsync(hist, {
49849
+ maxMessages: 2,
49850
+ force: true,
49851
+ signal: opts?.signal,
49852
+ ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
49853
+ });
49854
+ }
49855
+ const label = r.cacheReuseExpected === false ? "llm-compact (model override)" : "auto-compact";
49280
49856
  fold(r, label, forcedTurns);
49281
49857
  }
49282
49858
  if (occupancy >= 0.95) {
49283
49859
  const hard = await compactHistoryAsync(hist, {
49284
- maxMessages: 8,
49860
+ maxMessages: 2,
49861
+ force: true,
49285
49862
  signal: opts?.signal
49286
49863
  });
49287
- fold(hard, hard.summary.includes("\xB7 llm") ? "llm-compact" : "auto-compact", 2);
49864
+ fold(hard, hard.summary.includes("\xB7 llm") ? "llm-compact" : "HARD trim", 2);
49288
49865
  historyTurns = 2;
49289
49866
  maxToolLoopIterations = Math.min(maxToolLoopIterations, 16);
49290
49867
  warnings.push(
49291
- `[budget] HARD context pressure (\u226595%) \u2014 prefer /clear or a new session if quality drops.`
49868
+ "[budget] HARD context pressure (\u226595%) \u2014 prefer /clear or a new session if quality drops."
49292
49869
  );
49293
49870
  }
49871
+ const cacheMetricsLine = envelope ? [
49872
+ "compaction meter:",
49873
+ `provider/model: ${envelope.snapshot.provider}/${envelope.snapshot.model}`,
49874
+ `headerFingerprint: ${envelope.snapshot.headerFingerprint.slice(0, 12)}`,
49875
+ `occupancy: ${Math.round(occupancy * 100)}% (${estimated}/${contextLimit})`,
49876
+ ...envelope.usage?.cachedPromptTokens !== void 0 ? [`cachedPromptTokens: ${envelope.usage.cachedPromptTokens}`] : [],
49877
+ ...cacheReuseExpected !== void 0 ? [`cacheReuseExpected: ${cacheReuseExpected}`] : []
49878
+ ].join(" | ") : void 0;
49294
49879
  return {
49295
49880
  history: hist,
49296
49881
  warnings,
49297
49882
  maxToolLoopIterations,
49298
49883
  historyTurns,
49299
- estimatedHistoryTokens: estimated,
49884
+ estimatedHistoryTokens: envelope ? convTokensOf(hist) : estimated,
49300
49885
  contextLimit,
49301
49886
  occupancy,
49302
49887
  compactSummary: compactSummary || void 0,
49303
- messagesRemoved: messagesRemoved || void 0
49888
+ messagesRemoved: messagesRemoved || void 0,
49889
+ ...envelope ? { contextPressureTokens: estimated } : {},
49890
+ ...cacheReuseExpected !== void 0 ? { cacheReuseExpected } : {},
49891
+ ...cacheMetricsLine ? { cacheMetricsLine } : {}
49304
49892
  };
49305
49893
  }
49894
+ function estimateSystemTokensLite(systemMessages) {
49895
+ let n = 0;
49896
+ for (const m of systemMessages) n += 4 + Math.ceil((m.content ?? "").length / 4);
49897
+ return n;
49898
+ }
49899
+ function estimateToolSchemaTokensLite(tools) {
49900
+ let n = 0;
49901
+ for (const t of tools) {
49902
+ n += Math.ceil((t.name ?? "").length / 4);
49903
+ n += Math.ceil((t.description ?? "").length / 4);
49904
+ n += Math.ceil(JSON.stringify(t.parameters ?? {}).length / 4);
49905
+ }
49906
+ return n + tools.length * 4;
49907
+ }
49908
+ function estimateConversationTokensLite(messages) {
49909
+ let n = 0;
49910
+ for (const m of messages) {
49911
+ n += 4 + Math.ceil((m.content ?? "").length / 4);
49912
+ if (m.toolCalls) {
49913
+ for (const tc of m.toolCalls) {
49914
+ n += Math.ceil(tc.name.length / 4) + Math.ceil(tc.id.length / 4);
49915
+ n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
49916
+ }
49917
+ }
49918
+ if (m.reasoningContent) n += Math.ceil(m.reasoningContent.length / 4);
49919
+ if (m.toolCallId) n += Math.ceil(m.toolCallId.length / 4);
49920
+ }
49921
+ return n;
49922
+ }
49306
49923
 
49307
49924
  // src/cli/hooks/useChatTurn.ts
49925
+ init_requestSnapshotStore();
49308
49926
  function useChatTurn(params) {
49309
49927
  const {
49310
49928
  sessionId,
@@ -49331,17 +49949,9 @@ function useChatTurn(params) {
49331
49949
  let envConfig;
49332
49950
  let harness;
49333
49951
  let historySeedLen = 0;
49952
+ let systemPrefixLen = 0;
49334
49953
  let turnSucceeded = false;
49335
49954
  try {
49336
- compactInPlace();
49337
- const budget = await applyBudgetPolicyAsync(getHistory(), getPhase(), {
49338
- model: getActiveModel()
49339
- });
49340
- setHistory(budget.history);
49341
- for (const w of budget.warnings) {
49342
- appendSystem(setMessages, w, Date.now());
49343
- }
49344
- historySeedLen = getHistory().length;
49345
49955
  const anchored = maybeAnchorShortAnswer(userText);
49346
49956
  const effectiveUserText = anchored ?? userText;
49347
49957
  const localCli = (process.env.ZELARI_LOCAL_CLI ?? "").trim();
@@ -49436,6 +50046,33 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49436
50046
  });
49437
50047
  }
49438
50048
  const cwd = process.cwd();
50049
+ const budget = await applyBudgetPolicyAsync(getHistory(), getPhase(), {
50050
+ model: getActiveModel(),
50051
+ sessionId,
50052
+ // v1.36.0: envelope for full-request metering + cache-aware
50053
+ // compaction replay (last warm prefix + provider usage anchor).
50054
+ requestSnapshot: getRequestSnapshotWithUsage(sessionId),
50055
+ providerStream
50056
+ });
50057
+ setHistory(budget.history);
50058
+ for (const w of budget.warnings) {
50059
+ appendSystem(setMessages, w, Date.now());
50060
+ }
50061
+ if ((budget.messagesRemoved ?? 0) > 0) {
50062
+ const envelope = getRequestSnapshotWithUsage(sessionId);
50063
+ const compactionEvent = createBrainEvent("session_compacted", sessionId, {
50064
+ summary: budget.compactSummary ?? "",
50065
+ messagesRemoved: budget.messagesRemoved ?? 0,
50066
+ ...envelope ? {
50067
+ sourceRequestFingerprint: envelope.snapshot.requestFingerprint,
50068
+ headerFingerprint: envelope.snapshot.headerFingerprint
50069
+ } : {},
50070
+ ...budget.contextPressureTokens !== void 0 ? { sourceEstimatedTokens: budget.contextPressureTokens } : {},
50071
+ ...budget.cacheReuseExpected !== void 0 ? { cacheReuseExpected: budget.cacheReuseExpected } : {}
50072
+ });
50073
+ void writerRef.current?.append(compactionEvent);
50074
+ }
50075
+ historySeedLen = getHistory().length;
49439
50076
  let composedWorkspace = "";
49440
50077
  let composedInstructions = "";
49441
50078
  let hasPlan = false;
@@ -49593,6 +50230,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49593
50230
  lastStableHash = hashStablePrompt(fallback);
49594
50231
  systemMessages = [{ role: "system", content: fallback }];
49595
50232
  }
50233
+ systemPrefixLen = systemMessages.length;
49596
50234
  const maxToolCallsPerTurn = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
49597
50235
  default: 25,
49598
50236
  min: 1
@@ -49605,7 +50243,10 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49605
50243
  });
49606
50244
  const harness2 = new AgentHarness({
49607
50245
  model: envConfig.model,
49608
- provider: "openai-compatible",
50246
+ // v1.36.0 (P0.2): real provider identity — the harness used to
50247
+ // hardcode "openai-compatible" (the transport family) so snapshots
50248
+ // and telemetry mislabeled deepseek/glm/minimax routing.
50249
+ provider: envConfig.providerId,
49609
50250
  messages: [
49610
50251
  ...systemMessages,
49611
50252
  // v1.8.0: shared rolling history (agent/council/zelari) so short
@@ -49624,6 +50265,9 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49624
50265
  cwd,
49625
50266
  maxToolCallsPerTurn,
49626
50267
  maxToolLoopIterations,
50268
+ // v1.36.0: routed-request snapshots feed the meter (occupancy) and
50269
+ // the cache-aware compaction replay (last warm prefix).
50270
+ onRequestSnapshot: (snap) => recordRequestSnapshot(sessionId, snap),
49627
50271
  ...maxToolLoopHardCap > 0 ? { maxToolLoopHardCap } : {}
49628
50272
  });
49629
50273
  harnessRef.current = harness2;
@@ -49638,6 +50282,14 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49638
50282
  for await (const event of harness2.run()) {
49639
50283
  if (event.type === "message_end") {
49640
50284
  if (event.usage) realUsage = event.usage;
50285
+ if (event.usage) {
50286
+ recordRequestUsage(sessionId, {
50287
+ promptTokens: event.usage.promptTokens,
50288
+ completionTokens: event.usage.completionTokens,
50289
+ totalTokens: event.usage.totalTokens,
50290
+ cachedPromptTokens: event.usage.cachedPromptTokens
50291
+ });
50292
+ }
49641
50293
  if (streamContent) {
49642
50294
  const sealed = streamScrub.finalize(streamContent);
49643
50295
  if (useLiveModel) setStreaming(commitStreaming, sealed, event.ts);
@@ -49797,18 +50449,17 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49797
50449
  const h = harnessRef.current;
49798
50450
  if (h && turnSucceeded) {
49799
50451
  const all = h.getMessages();
49800
- const seedLen = 1 + historySeedLen + 1;
50452
+ const seedLen = systemPrefixLen + historySeedLen + 1;
49801
50453
  if (all.length > seedLen) {
49802
50454
  appendMessages(
49803
- all.slice(seedLen).map(
49804
- (m) => m.role === "assistant" && m.content ? {
49805
- ...m,
49806
- content: cleanAgentContent(m.content, {
49807
- stripQuestion: false,
49808
- stripThink: false
49809
- })
49810
- } : m
49811
- )
50455
+ all.slice(seedLen).map((m) => {
50456
+ if (m.role !== "assistant" || !m.content) return m;
50457
+ const cleaned = cleanAgentContent(m.content, {
50458
+ stripQuestion: false,
50459
+ stripThink: false
50460
+ });
50461
+ return cleaned === m.content ? m : { ...m, content: cleaned };
50462
+ })
49812
50463
  );
49813
50464
  }
49814
50465
  }
@@ -51751,12 +52402,12 @@ init_fileStateStore();
51751
52402
  async function restoreDurableState(opts) {
51752
52403
  const restoreTree = opts.restoreTree !== false;
51753
52404
  try {
51754
- const store3 = opts.store ?? await getStateStore(opts.projectRoot);
52405
+ const store4 = opts.store ?? await getStateStore(opts.projectRoot);
51755
52406
  let meta3;
51756
52407
  if (opts.commitId) {
51757
- meta3 = await store3.setHead(opts.commitId);
52408
+ meta3 = await store4.setHead(opts.commitId);
51758
52409
  } else {
51759
- meta3 = await store3.head();
52410
+ meta3 = await store4.head();
51760
52411
  if (!meta3) {
51761
52412
  return {
51762
52413
  ok: false,
@@ -51809,8 +52460,8 @@ function ago2(ms) {
51809
52460
  return `${Math.round(s / 3600)}h ago`;
51810
52461
  }
51811
52462
  async function handleStateStatus(ctx) {
51812
- const store3 = await getStateStore(ctx.cwd);
51813
- const head = await store3.head();
52463
+ const store4 = await getStateStore(ctx.cwd);
52464
+ const head = await store4.head();
51814
52465
  if (!head) {
51815
52466
  appendSystem(
51816
52467
  ctx.setMessages,
@@ -51818,9 +52469,9 @@ async function handleStateStatus(ctx) {
51818
52469
  );
51819
52470
  return;
51820
52471
  }
51821
- const discoveries = await store3.loadDiscoveries(head.id);
52472
+ const discoveries = await store4.loadDiscoveries(head.id);
51822
52473
  const reusable = discoveries.filter((d) => d.reusable).length;
51823
- const recent = await store3.list(8);
52474
+ const recent = await store4.list(8);
51824
52475
  const lines = recent.map((c, i) => {
51825
52476
  const ver2 = c.verification.ran ? c.verification.ok ? "ok" : "fail" : "n/a";
51826
52477
  return ` ${i === 0 ? "\u2192" : " "} ${c.id} ${ago2(c.createdAt)} ${c.label} ver=${ver2}` + (c.layer ? ` [${c.layer}]` : "") + (c.stablePromptHash ? ` hash=${c.stablePromptHash.slice(0, 8)}` : "");
@@ -51839,9 +52490,9 @@ async function handleStateStatus(ctx) {
51839
52490
  );
51840
52491
  }
51841
52492
  async function handleStateCommit(ctx, label) {
51842
- const store3 = await getStateStore(ctx.cwd);
52493
+ const store4 = await getStateStore(ctx.cwd);
51843
52494
  try {
51844
- const meta3 = await store3.commit({
52495
+ const meta3 = await store4.commit({
51845
52496
  mode: "agent",
51846
52497
  label: label?.trim() || "manual state commit",
51847
52498
  layer: "manual",
@@ -51868,8 +52519,8 @@ async function handleStateCommit(ctx, label) {
51868
52519
  }
51869
52520
  }
51870
52521
  async function handleStateShow(ctx, id) {
51871
- const store3 = await getStateStore(ctx.cwd);
51872
- const meta3 = id ? await store3.get(id) : await store3.head();
52522
+ const store4 = await getStateStore(ctx.cwd);
52523
+ const meta3 = id ? await store4.get(id) : await store4.head();
51873
52524
  if (!meta3) {
51874
52525
  appendSystem(
51875
52526
  ctx.setMessages,
@@ -51877,7 +52528,7 @@ async function handleStateShow(ctx, id) {
51877
52528
  );
51878
52529
  return;
51879
52530
  }
51880
- const text = await store3.materializeContext(meta3.id, 6e3);
52531
+ const text = await store4.materializeContext(meta3.id, 6e3);
51881
52532
  appendSystem(ctx.setMessages, `[state] show ${meta3.id}
51882
52533
  ${text}`);
51883
52534
  }
@@ -52916,7 +53567,7 @@ async function handlePromoteMember(ctx, memberId) {
52916
53567
  }
52917
53568
 
52918
53569
  // src/cli/branchManager.ts
52919
- import { promises as fs26, existsSync as existsSync40, readFileSync as readFileSync33, writeFileSync as writeFileSync21, mkdirSync as mkdirSync18, statSync as statSync5, rmSync as rmSync3 } from "node:fs";
53570
+ import { promises as fs26, existsSync as existsSync41, readFileSync as readFileSync34, writeFileSync as writeFileSync22, mkdirSync as mkdirSync19, statSync as statSync5, rmSync as rmSync3 } from "node:fs";
52920
53571
  import path46 from "node:path";
52921
53572
  import os12 from "node:os";
52922
53573
  var META_FILENAME = "meta.json";
@@ -52938,11 +53589,11 @@ function sessionsPathFor(name, baseDir) {
52938
53589
  }
52939
53590
  function readBranchMeta(name, baseDir) {
52940
53591
  const metaPath = metaPathFor(name, baseDir);
52941
- if (!existsSync40(metaPath)) {
53592
+ if (!existsSync41(metaPath)) {
52942
53593
  throw new BranchNotFoundError(`Branch "${name}" not found`);
52943
53594
  }
52944
53595
  try {
52945
- const raw = readFileSync33(metaPath, "utf-8");
53596
+ const raw = readFileSync34(metaPath, "utf-8");
52946
53597
  const parsed = JSON.parse(raw);
52947
53598
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
52948
53599
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -52959,8 +53610,8 @@ function readBranchMeta(name, baseDir) {
52959
53610
  }
52960
53611
  function writeBranchMeta(name, baseDir, meta3) {
52961
53612
  const metaPath = metaPathFor(name, baseDir);
52962
- mkdirSync18(path46.dirname(metaPath), { recursive: true });
52963
- writeFileSync21(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
53613
+ mkdirSync19(path46.dirname(metaPath), { recursive: true });
53614
+ writeFileSync22(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
52964
53615
  }
52965
53616
  async function countSessions(name, baseDir) {
52966
53617
  const sessionsPath = sessionsPathFor(name, baseDir);
@@ -52998,7 +53649,7 @@ var SessionNotFoundError = class extends Error {
52998
53649
  };
52999
53650
  function branchExists(name, baseDir = getBranchesBaseDir()) {
53000
53651
  const bp = branchPathFor(name, baseDir);
53001
- return existsSync40(bp) && existsSync40(metaPathFor(name, baseDir));
53652
+ return existsSync41(bp) && existsSync41(metaPathFor(name, baseDir));
53002
53653
  }
53003
53654
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
53004
53655
  if (!name || name.trim().length === 0) {
@@ -53011,12 +53662,12 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
53011
53662
  throw new BranchAlreadyExistsError(name);
53012
53663
  }
53013
53664
  const sourcePath = path46.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
53014
- if (!existsSync40(sourcePath)) {
53665
+ if (!existsSync41(sourcePath)) {
53015
53666
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
53016
53667
  }
53017
53668
  const branchPath = branchPathFor(name, baseDir);
53018
53669
  const branchSessionsPath = sessionsPathFor(name, baseDir);
53019
- mkdirSync18(branchSessionsPath, { recursive: true });
53670
+ mkdirSync19(branchSessionsPath, { recursive: true });
53020
53671
  const destPath = path46.join(branchSessionsPath, `${fromSessionId}.jsonl`);
53021
53672
  await fs26.copyFile(sourcePath, destPath);
53022
53673
  const meta3 = {
@@ -53044,7 +53695,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
53044
53695
  const results = [];
53045
53696
  for (const entry of entries) {
53046
53697
  const metaPath = metaPathFor(entry, baseDir);
53047
- if (!existsSync40(metaPath)) continue;
53698
+ if (!existsSync41(metaPath)) continue;
53048
53699
  try {
53049
53700
  const meta3 = readBranchMeta(entry, baseDir);
53050
53701
  const sessionCount = await countSessions(entry, baseDir);
@@ -53235,7 +53886,7 @@ import path48 from "node:path";
53235
53886
  import os13 from "node:os";
53236
53887
 
53237
53888
  // src/cli/skillHistory.ts
53238
- import { promises as fs28, existsSync as existsSync41, statSync as statSync6, renameSync as renameSync4, appendFileSync as appendFileSync4, mkdirSync as mkdirSync19 } from "node:fs";
53889
+ import { promises as fs28, existsSync as existsSync42, statSync as statSync6, renameSync as renameSync5, appendFileSync as appendFileSync4, mkdirSync as mkdirSync20 } from "node:fs";
53239
53890
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
53240
53891
  async function readSkillHistory(file2) {
53241
53892
  let raw = "";
@@ -53386,14 +54037,14 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
53386
54037
  }
53387
54038
  function handleCouncilFeedback(ctx, memberId, score, note) {
53388
54039
  try {
53389
- const store3 = new FeedbackStore();
53390
- const entry = store3.record({
54040
+ const store4 = new FeedbackStore();
54041
+ const entry = store4.record({
53391
54042
  memberId,
53392
54043
  score,
53393
54044
  ...note ? { note } : {},
53394
54045
  ...ctx.sessionId ? { sessionId: ctx.sessionId } : {}
53395
54046
  });
53396
- const stats = store3.getStats(memberId);
54047
+ const stats = store4.getStats(memberId);
53397
54048
  appendSystem(
53398
54049
  ctx.setMessages,
53399
54050
  `[council-feedback] ${memberId} rated ${entry.score}/5 \u2014 running avg ${stats.avg.toFixed(2)} over ${stats.count} rating(s).`
@@ -54563,9 +55214,9 @@ function ContinueKey({ onContinue }) {
54563
55214
  init_providerConfig();
54564
55215
 
54565
55216
  // src/cli/wizard/firstRun.ts
54566
- import { existsSync as existsSync43 } from "node:fs";
55217
+ import { existsSync as existsSync44 } from "node:fs";
54567
55218
  function shouldRunWizard(input) {
54568
- const exists = input.exists ?? existsSync43;
55219
+ const exists = input.exists ?? existsSync44;
54569
55220
  if (input.hasResetConfigFlag) {
54570
55221
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
54571
55222
  }
@@ -54850,7 +55501,7 @@ init_keyStore();
54850
55501
  init_providerConfig();
54851
55502
  init_openai_compatible();
54852
55503
  init_phase();
54853
- import { readFileSync as readFileSync35 } from "node:fs";
55504
+ import { readFileSync as readFileSync36 } from "node:fs";
54854
55505
  function parseHeadlessFlags(argv) {
54855
55506
  if (!argv.includes("--headless")) {
54856
55507
  return { options: null };
@@ -54923,7 +55574,7 @@ function parseHeadlessFlags(argv) {
54923
55574
  let raw = null;
54924
55575
  if (arg === "--history-file") {
54925
55576
  try {
54926
- raw = readFileSync35(next, "utf-8");
55577
+ raw = readFileSync36(next, "utf-8");
54927
55578
  } catch {
54928
55579
  raw = null;
54929
55580
  }
@@ -55410,6 +56061,20 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
55410
56061
  const sessionId = crypto.randomUUID();
55411
56062
  const { registry: toolRegistry } = createBuiltinToolRegistry({
55412
56063
  planMode: planModeFromOpts(opts),
56064
+ // ADR-0018 3b: upgrade plan-task domain events to first-class NDJSON
56065
+ // BrainEvents. Rust envelopes every stdout line with runId/conversationId,
56066
+ // so task events ride the same multiplexed channel as the rest.
56067
+ onTaskEvent: (ev) => {
56068
+ if (opts.output !== "json") return;
56069
+ emitEvent({
56070
+ type: ev.type,
56071
+ id: crypto.randomUUID(),
56072
+ ts: Date.now(),
56073
+ sessionId,
56074
+ source: ev.source,
56075
+ ...ev.type === "task_update" ? { task: ev.task } : { tasks: ev.tasks }
56076
+ });
56077
+ },
55413
56078
  permissionPolicy: {
55414
56079
  read: "allow",
55415
56080
  write: "allow",