zelari-code 2.6.0 → 2.6.2

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.
@@ -28340,6 +28340,9 @@ var init_types8 = __esm({
28340
28340
  // once at session start / manifest change. State-only (never model-surface):
28341
28341
  // data = {manifest, manifestHash}. Schema review per ADR-0021.
28342
28342
  "session.harness_manifest",
28343
+ // 2.6.1 (closure plan §6): resume-time harness drift record. State-only:
28344
+ // data = {originalManifestHash, currentManifestHash}. Non-blocking signal.
28345
+ "session.harness_drift",
28343
28346
  "user.message",
28344
28347
  "assistant.message",
28345
28348
  "tool.call",
@@ -28370,9 +28373,12 @@ var init_types8 = __esm({
28370
28373
  // 2.6 Track B (resource-aware execution, doc §9-§12): host-owned resource
28371
28374
  // state. `resource.snapshot` is model-surface with LATEST-ONLY projection
28372
28375
  // (doc §10.2 — see modelSurface.ts); limit/reserve events are state-only.
28376
+ "resource.epoch_started",
28373
28377
  "resource.snapshot",
28374
28378
  "resource.limit_reached",
28375
28379
  "resource.reserve_entered",
28380
+ // 2.6.1 (closure plan §9): hard-limit overrun telemetry — state-only.
28381
+ "resource.overrun",
28376
28382
  "note"
28377
28383
  ];
28378
28384
  SessionEventEnvelopeSchema = external_exports.object({
@@ -28703,11 +28709,19 @@ function asNumber(value) {
28703
28709
  function formatResourceSnapshot(data) {
28704
28710
  const used = asNumber(data.toolCallsUsed) ?? 0;
28705
28711
  const remaining = asNumber(data.toolCallsRemaining) ?? 0;
28712
+ const limit = asNumber(data.toolCallsLimit) ?? used + remaining;
28706
28713
  const lines = [
28707
28714
  "RESOURCE STATUS",
28708
- `Tool calls: ${used} / ${used + remaining}`,
28715
+ `Tool calls: ${used} / ${limit}`,
28709
28716
  `Remaining: ${remaining}`
28710
28717
  ];
28718
+ const sessionUsed = asNumber(data.sessionToolCallsUsed);
28719
+ if (sessionUsed !== void 0 && sessionUsed !== used) {
28720
+ lines.push(`Session total: ${sessionUsed}`);
28721
+ }
28722
+ const overrun = asNumber(data.overrun);
28723
+ if (overrun !== void 0 && overrun > 0)
28724
+ lines.push(`Overrun: ${overrun}`);
28711
28725
  const wall = asNumber(data.wallMsRemaining);
28712
28726
  if (wall !== void 0)
28713
28727
  lines.push(`Wall clock remaining: ${Math.max(0, Math.round(wall / 1e3))}s`);
@@ -29595,7 +29609,24 @@ function pushCompactionViolations(events, knownSeq, pairs, violations) {
29595
29609
  function validateResourceAndContractEvents(events) {
29596
29610
  const violations = [];
29597
29611
  let lastToolCallsUsed = -1;
29612
+ let lastEpoch = 0;
29613
+ let activeEpoch = 0;
29614
+ let lastSessionToolCallsUsed = -1;
29598
29615
  for (const e of events) {
29616
+ if (e.kind === "resource.epoch_started") {
29617
+ const epoch = e.data.epoch;
29618
+ if (typeof epoch !== "number" || !Number.isInteger(epoch) || epoch <= lastEpoch) {
29619
+ violations.push({
29620
+ code: "RESOURCE_EPOCH_MONOTONIC",
29621
+ seq: e.seq,
29622
+ message: `resource epoch ${String(epoch)} did not increase past ${lastEpoch}`
29623
+ });
29624
+ } else {
29625
+ lastEpoch = epoch;
29626
+ activeEpoch = epoch;
29627
+ }
29628
+ lastToolCallsUsed = -1;
29629
+ }
29599
29630
  if (e.kind === "resource.snapshot") {
29600
29631
  const used = e.data.toolCallsUsed;
29601
29632
  const remaining = e.data.toolCallsRemaining;
@@ -29607,9 +29638,41 @@ function validateResourceAndContractEvents(events) {
29607
29638
  violations.push({ code: "RESOURCE_USED_MONOTONIC", seq: e.seq, message: `toolCallsUsed went ${lastToolCallsUsed} -> ${used}` });
29608
29639
  }
29609
29640
  lastToolCallsUsed = used;
29641
+ const epoch = e.data.epoch;
29642
+ if (epoch !== void 0 && (typeof epoch !== "number" || !Number.isInteger(epoch) || epoch !== activeEpoch)) {
29643
+ violations.push({
29644
+ code: "RESOURCE_SNAPSHOT_EPOCH_MISMATCH",
29645
+ seq: e.seq,
29646
+ message: `snapshot epoch ${String(epoch)} does not match active epoch ${activeEpoch}`
29647
+ });
29648
+ }
29649
+ const sessionUsed = e.data.sessionToolCallsUsed;
29650
+ if (sessionUsed !== void 0) {
29651
+ if (typeof sessionUsed !== "number" || !Number.isInteger(sessionUsed) || sessionUsed < used) {
29652
+ violations.push({
29653
+ code: "RESOURCE_SESSION_USAGE_INVALID",
29654
+ seq: e.seq,
29655
+ message: `sessionToolCallsUsed(${String(sessionUsed)}) must be an integer >= epoch used(${used})`
29656
+ });
29657
+ } else if (sessionUsed < lastSessionToolCallsUsed) {
29658
+ violations.push({
29659
+ code: "RESOURCE_SESSION_USAGE_MONOTONIC",
29660
+ seq: e.seq,
29661
+ message: `sessionToolCallsUsed went ${lastSessionToolCallsUsed} -> ${sessionUsed}`
29662
+ });
29663
+ }
29664
+ if (typeof sessionUsed === "number")
29665
+ lastSessionToolCallsUsed = sessionUsed;
29666
+ }
29610
29667
  const limit = typeof e.data.toolCallsLimit === "number" ? e.data.toolCallsLimit : used + remaining;
29611
- if (used + remaining !== limit) {
29612
- violations.push({ code: "RESOURCE_REMAINING_COHERENT", seq: e.seq, message: `used(${used}) + remaining(${remaining}) != limit(${limit})` });
29668
+ if (remaining !== Math.max(0, limit - used)) {
29669
+ violations.push({ code: "RESOURCE_REMAINING_COHERENT", seq: e.seq, message: `remaining(${remaining}) != max(0, limit(${limit}) - used(${used}))` });
29670
+ }
29671
+ const overrun = e.data.overrun;
29672
+ if (overrun !== void 0) {
29673
+ if (typeof overrun !== "number" || overrun < 0 || overrun !== Math.max(0, used - limit)) {
29674
+ violations.push({ code: "RESOURCE_OVERRUN_COHERENT", seq: e.seq, message: `overrun(${String(overrun)}) != max(0, used(${used}) - limit(${limit}))` });
29675
+ }
29613
29676
  }
29614
29677
  for (const key of ["verificationReserve", "repairReserve"]) {
29615
29678
  const v = e.data[key];
@@ -29706,27 +29769,53 @@ function contractToCompactionFields(contract) {
29706
29769
  function deriveInitialContract(userSeq, text) {
29707
29770
  const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
29708
29771
  const stripped = lines.map((l) => l.replace(/^[-*]\s+/, ""));
29709
- const constraintLines = stripped.filter((l) => /^(do not|don't|never|no |non |without changing|keep)/i.test(l));
29710
- const criteriaLines = stripped.filter((l) => /^\[?\s*[x ]?\s*\]?\s*/.test(l) === false ? /^(acceptance|verify|test)[:\s]/i.test(l) : true).map((l) => l.replace(/^\[?\s*[x ]?\s*\]?\s*/, "").replace(/^(acceptance|verify|test)[:\s]+/i, ""));
29772
+ const acceptanceCriteria = [];
29773
+ const criterionLines = /* @__PURE__ */ new Set();
29774
+ stripped.forEach((line, i) => {
29775
+ const checkbox = CHECKBOX_CRITERION.exec(line);
29776
+ if (checkbox) {
29777
+ criterionLines.add(i);
29778
+ acceptanceCriteria.push({
29779
+ id: `ac-${acceptanceCriteria.length + 1}`,
29780
+ text: checkbox[2].trim(),
29781
+ source: "user",
29782
+ required: true
29783
+ });
29784
+ return;
29785
+ }
29786
+ const keyword = KEYWORD_CRITERION.exec(line);
29787
+ if (keyword) {
29788
+ criterionLines.add(i);
29789
+ const value = keyword[2].trim();
29790
+ const lead = keyword[1].toLowerCase();
29791
+ const hint = (lead === "verify" || lead === "test") && COMMAND_HINT.test(value) ? { kind: "command", value } : void 0;
29792
+ acceptanceCriteria.push({
29793
+ id: `ac-${acceptanceCriteria.length + 1}`,
29794
+ text: value,
29795
+ source: "user",
29796
+ required: true,
29797
+ ...hint ? { verificationHint: hint } : {}
29798
+ });
29799
+ }
29800
+ });
29801
+ const isConstraint = (i) => !criterionLines.has(i) && CONSTRAINT_LEAD.test(stripped[i] ?? "");
29802
+ const constraints = stripped.map((text2, i) => ({ text: text2, i })).filter(({ i }) => isConstraint(i)).map(({ text: text2 }, i) => ({
29803
+ id: `uc-${i + 1}`,
29804
+ text: text2,
29805
+ source: "user",
29806
+ required: true
29807
+ }));
29808
+ const goalIdx = lines.findIndex((_, i) => !criterionLines.has(i) && !isConstraint(i));
29809
+ const goal = goalIdx >= 0 ? lines[goalIdx] : lines[0] ?? text.slice(0, 200);
29711
29810
  return TaskContractSchema.parse({
29712
29811
  version: 1,
29713
- goal: lines[0] ?? text.slice(0, 200),
29714
- constraints: constraintLines.map((t, i) => ({
29715
- id: `uc-${i + 1}`,
29716
- text: t,
29717
- source: "user",
29718
- required: true
29719
- })),
29720
- acceptanceCriteria: criteriaLines.map((t, i) => ({
29721
- id: `ac-${i + 1}`,
29722
- text: t,
29723
- source: "user",
29724
- required: true
29725
- })),
29812
+ goal,
29813
+ constraints,
29814
+ acceptanceCriteria,
29726
29815
  source: { userSeq }
29727
29816
  });
29728
29817
  }
29729
- var TaskConstraintSchema, TaskCriterionSchema, TaskContractSchema, TaskContractConflictError;
29818
+ var TaskConstraintSchema, TaskCriterionSchema, TaskContractSchema, TaskContractConflictError, CHECKBOX_CRITERION, KEYWORD_CRITERION, COMMAND_HINT, CONSTRAINT_LEAD;
29730
29819
  var init_taskContract = __esm({
29731
29820
  "packages/core/dist/session/taskContract.js"() {
29732
29821
  "use strict";
@@ -29766,6 +29855,10 @@ var init_taskContract = __esm({
29766
29855
  this.name = "TaskContractConflictError";
29767
29856
  }
29768
29857
  };
29858
+ CHECKBOX_CRITERION = /^\[([ xX])\]\s*(.+)$/;
29859
+ KEYWORD_CRITERION = /^(acceptance|criterion|criteria|verify|test|success)\s*[:#]\s*(.+)$/i;
29860
+ COMMAND_HINT = /^(npm|pnpm|yarn|bun|npx|node|vitest|jest|tsc|eslint|prettier|git)\b/;
29861
+ CONSTRAINT_LEAD = /^(do not|don't|never|no\s|non\s|without changing|keep|must not|avoid)\b/i;
29769
29862
  }
29770
29863
  });
29771
29864
 
@@ -30233,9 +30326,13 @@ function classifyHarnessChanges(diff) {
30233
30326
  const hit = FIELD_CLASSES.find((m) => field === m.prefix || field.startsWith(m.prefix + "."));
30234
30327
  byField[field] = hit ? hit.cls : "cosmetic";
30235
30328
  }
30329
+ const changeSet = { structural: [], behavioral: [], cosmetic: [] };
30330
+ for (const field of diff.changed) {
30331
+ changeSet[byField[field]].push(field);
30332
+ }
30236
30333
  const order = { behavioral: 3, structural: 2, cosmetic: 1 };
30237
30334
  const overall = Object.values(byField).reduce((acc, cls) => order[cls] > order[acc] ? cls : acc, "cosmetic");
30238
- return { overall, byField };
30335
+ return { overall, byField, changeSet };
30239
30336
  }
30240
30337
  var HARNESS_MANIFEST_SCHEMA_VERSION, HarnessPromptsSchema, HarnessManifestV1Schema, FIELD_CLASSES;
30241
30338
  var init_harnessManifest = __esm({
@@ -30446,12 +30543,14 @@ var init_resourcePolicy = __esm({
30446
30543
 
30447
30544
  // packages/core/dist/runtime/resourceBudget.js
30448
30545
  function computeBudget(policy, usage, stage = "explore") {
30449
- const used = Math.max(0, Math.min(usage.toolCallsUsed, policy.maxToolCalls));
30546
+ const used = Math.max(0, usage.toolCallsUsed);
30547
+ const overrun = Math.max(0, used - policy.maxToolCalls);
30450
30548
  return {
30451
30549
  toolCalls: {
30452
30550
  limit: policy.maxToolCalls,
30453
30551
  used,
30454
- remaining: policy.maxToolCalls - used
30552
+ remaining: Math.max(0, policy.maxToolCalls - used),
30553
+ overrun
30455
30554
  },
30456
30555
  wallTime: {
30457
30556
  limitMs: policy.wallClockMs,
@@ -31465,6 +31564,48 @@ var init_resourceReserveGate = __esm({
31465
31564
  });
31466
31565
 
31467
31566
  // packages/core/dist/verification/index.js
31567
+ var verification_exports = {};
31568
+ __export(verification_exports, {
31569
+ BonConfigSchema: () => BonConfigSchema,
31570
+ CommandCheckSchema: () => CommandCheckSchema,
31571
+ CriterionSchema: () => CriterionSchema,
31572
+ CriterionSourceSchema: () => CriterionSourceSchema,
31573
+ DEFAULT_VERIFIER_CONFIG: () => DEFAULT_VERIFIER_CONFIG,
31574
+ DETERMINISTIC_EVIDENCE_TIERS: () => DETERMINISTIC_EVIDENCE_TIERS,
31575
+ DeterministicCheckSchema: () => DeterministicCheckSchema,
31576
+ EVENT_BACKED_EVIDENCE_TIERS: () => EVENT_BACKED_EVIDENCE_TIERS,
31577
+ EvidenceRefSchema: () => EvidenceRefSchema,
31578
+ EvidenceTierSchema: () => EvidenceTierSchema,
31579
+ FileAbsentCheckSchema: () => FileAbsentCheckSchema,
31580
+ FileContainsCheckSchema: () => FileContainsCheckSchema,
31581
+ FileExistsCheckSchema: () => FileExistsCheckSchema,
31582
+ ModelSelectionSchema: () => ModelSelectionSchema,
31583
+ NoneCheckSchema: () => NoneCheckSchema,
31584
+ STRICT_ALL_POLICY: () => STRICT_ALL_POLICY,
31585
+ STRICT_BUILD_POLICY: () => STRICT_BUILD_POLICY,
31586
+ VerificationEngine: () => VerificationEngine,
31587
+ VerificationResultSchema: () => VerificationResultSchema,
31588
+ VerificationSourceSchema: () => VerificationSourceSchema,
31589
+ VerificationStatusSchema: () => VerificationStatusSchema,
31590
+ VerifierConfigSchema: () => VerifierConfigSchema,
31591
+ VerifierService: () => VerifierService,
31592
+ ZELARI_CODING_PACK_ID: () => ZELARI_CODING_PACK_ID,
31593
+ analyzeScope: () => analyzeScope,
31594
+ codingCriteriaPack: () => codingCriteriaPack,
31595
+ computeFalseDoneRate: () => computeFalseDoneRate,
31596
+ costPerVerifiedSolve: () => costPerVerifiedSolve,
31597
+ evaluateCompletion: () => evaluateCompletion,
31598
+ evaluateResourceReserveGate: () => evaluateResourceReserveGate,
31599
+ isEventBackedEvidence: () => isEventBackedEvidence,
31600
+ isGeneratedPath: () => isGeneratedPath,
31601
+ lastVerificationRun: () => lastVerificationRun,
31602
+ parseNameOnlyDiff: () => parseNameOnlyDiff,
31603
+ parseVerificationRunPayload: () => parseVerificationRunPayload,
31604
+ snapshotToCompletionEvaluation: () => snapshotToCompletionEvaluation,
31605
+ strictBuildGate: () => strictBuildGate,
31606
+ verificationCostRatio: () => verificationCostRatio,
31607
+ verifiedSolveRate: () => verifiedSolveRate
31608
+ });
31468
31609
  var init_verification2 = __esm({
31469
31610
  "packages/core/dist/verification/index.js"() {
31470
31611
  "use strict";
@@ -31621,6 +31762,42 @@ var init_mission2 = __esm({
31621
31762
  }
31622
31763
  });
31623
31764
 
31765
+ // packages/core/dist/version.js
31766
+ var CORE_VERSION;
31767
+ var init_version = __esm({
31768
+ "packages/core/dist/version.js"() {
31769
+ "use strict";
31770
+ CORE_VERSION = "2.6.2";
31771
+ }
31772
+ });
31773
+
31774
+ // packages/core/dist/runtime/fingerprints.js
31775
+ import { createHash as createHash6 } from "node:crypto";
31776
+ function toolFingerprintHash(tools) {
31777
+ const canonical = [...tools].map((t) => ({
31778
+ name: t.name,
31779
+ ...t.description !== void 0 ? { description: t.description } : {},
31780
+ ...t.inputSchema !== void 0 ? { inputSchema: t.inputSchema } : {},
31781
+ ...t.outputContractVersion !== void 0 ? { outputContractVersion: t.outputContractVersion } : {},
31782
+ ...t.capabilityFlags !== void 0 ? { capabilityFlags: [...t.capabilityFlags].sort() } : {}
31783
+ })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
31784
+ return createHash6("sha256").update(stableStringify(canonical)).digest("hex");
31785
+ }
31786
+ function skillFingerprintHash(skills) {
31787
+ const canonical = [...skills].map((s) => ({
31788
+ id: s.id,
31789
+ ...s.version !== void 0 ? { version: s.version } : {},
31790
+ ...s.contentDigest !== void 0 ? { contentDigest: s.contentDigest } : {}
31791
+ })).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
31792
+ return createHash6("sha256").update(stableStringify(canonical)).digest("hex");
31793
+ }
31794
+ var init_fingerprints = __esm({
31795
+ "packages/core/dist/runtime/fingerprints.js"() {
31796
+ "use strict";
31797
+ init_requestSnapshot();
31798
+ }
31799
+ });
31800
+
31624
31801
  // packages/core/dist/index.js
31625
31802
  var dist_exports = {};
31626
31803
  __export(dist_exports, {
@@ -31642,6 +31819,7 @@ __export(dist_exports, {
31642
31819
  CODING_SKILL_CATALOG: () => CODING_SKILL_CATALOG,
31643
31820
  COLLABORATION_DIRECTIVE: () => COLLABORATION_DIRECTIVE,
31644
31821
  COMPOSITOR_ONLY_PROPS: () => COMPOSITOR_ONLY_PROPS,
31822
+ CORE_VERSION: () => CORE_VERSION,
31645
31823
  COUNCIL_V1: () => COUNCIL_V1,
31646
31824
  CommandCheckSchema: () => CommandCheckSchema,
31647
31825
  CriterionSchema: () => CriterionSchema,
@@ -31986,6 +32164,7 @@ __export(dist_exports, {
31986
32164
  shadowedSeqSet: () => shadowedSeqSet,
31987
32165
  shouldRetryMember: () => shouldRetryMember,
31988
32166
  sideEffectForTool: () => sideEffectForTool,
32167
+ skillFingerprintHash: () => skillFingerprintHash,
31989
32168
  slugify: () => slugify2,
31990
32169
  snapshotToCompletionEvaluation: () => snapshotToCompletionEvaluation,
31991
32170
  specificityFromAssumptions: () => specificityFromAssumptions,
@@ -31997,6 +32176,7 @@ __export(dist_exports, {
31997
32176
  taskMatchesNfrKeywords: () => taskMatchesNfrKeywords,
31998
32177
  tierAtLeast: () => tierAtLeast,
31999
32178
  tokenizeForSignature: () => tokenizeForSignature,
32179
+ toolFingerprintHash: () => toolFingerprintHash,
32000
32180
  toolManifestHash: () => toolManifestHash,
32001
32181
  toolMatches: () => toolMatches,
32002
32182
  topoLevels: () => topoLevels,
@@ -32035,6 +32215,8 @@ var init_dist = __esm({
32035
32215
  init_verification2();
32036
32216
  init_mission2();
32037
32217
  init_experimental();
32218
+ init_version();
32219
+ init_fingerprints();
32038
32220
  }
32039
32221
  });
32040
32222
 
@@ -32367,6 +32549,7 @@ function buildResourceSnapshot(budget, policy) {
32367
32549
  toolCallsLimit: budget.toolCalls.limit,
32368
32550
  toolCallsUsed: budget.toolCalls.used,
32369
32551
  toolCallsRemaining: budget.toolCalls.remaining,
32552
+ overrun: budget.toolCalls.overrun,
32370
32553
  ...budget.wallTime.remainingMs !== void 0 ? { wallMsRemaining: budget.wallTime.remainingMs } : {},
32371
32554
  verificationReserve: budget.reserve.verification,
32372
32555
  repairReserve: budget.reserve.repair,
@@ -32375,6 +32558,13 @@ function buildResourceSnapshot(budget, policy) {
32375
32558
  reserveProtected: isVerificationReserveProtected(budget)
32376
32559
  };
32377
32560
  }
32561
+ function withResourceSnapshotContext(snapshot, context) {
32562
+ return {
32563
+ ...snapshot,
32564
+ ...context.epoch !== void 0 ? { epoch: context.epoch } : {},
32565
+ ...context.sessionToolCallsUsed !== void 0 ? { sessionToolCallsUsed: context.sessionToolCallsUsed } : {}
32566
+ };
32567
+ }
32378
32568
  function shouldEmitSnapshot(previous, next) {
32379
32569
  if (!previous) return true;
32380
32570
  if (previous.stage !== next.stage) return true;
@@ -32394,7 +32584,29 @@ var init_resourceSnapshot = __esm({
32394
32584
  function resolveResourceEnforcement(env = process.env) {
32395
32585
  return env.ZELARI_RESOURCE_ENFORCEMENT === "protected" ? "protected" : "advisory";
32396
32586
  }
32397
- var DEFAULT_ESSENTIAL_TOOLS, ADVISORY_NOTICE, PROTECTED_DENIAL, BudgetRuntime;
32587
+ function bashCommand(args) {
32588
+ if (!args || typeof args !== "object") return "";
32589
+ const rec = args;
32590
+ for (const key of ["command", "cmd", "script"]) {
32591
+ if (typeof rec[key] === "string") return rec[key];
32592
+ }
32593
+ return "";
32594
+ }
32595
+ function isVerificationEssential(toolName, args, _stage = "implement") {
32596
+ if (toolName === "bash") {
32597
+ const cmd = bashCommand(args);
32598
+ if (!cmd) return false;
32599
+ return ESSENTIAL_BASH.some((re) => re.test(cmd));
32600
+ }
32601
+ if (toolName === "grep_content") {
32602
+ if (!args || typeof args !== "object") return true;
32603
+ const rec = args;
32604
+ const hasScope = typeof rec.path === "string" && rec.path !== "." && rec.path !== "./";
32605
+ return hasScope || typeof rec.pattern === "string";
32606
+ }
32607
+ return DEFAULT_ESSENTIAL_TOOLS.includes(toolName);
32608
+ }
32609
+ var DEFAULT_ESSENTIAL_TOOLS, ADVISORY_NOTICE, PROTECTED_DENIAL, HARD_LIMIT_DENIAL, ESSENTIAL_BASH, BudgetRuntime;
32398
32610
  var init_budgetRuntime = __esm({
32399
32611
  "src/cli/budget/budgetRuntime.ts"() {
32400
32612
  "use strict";
@@ -32402,7 +32614,6 @@ var init_budgetRuntime = __esm({
32402
32614
  init_resourceLedger();
32403
32615
  init_resourceSnapshot();
32404
32616
  DEFAULT_ESSENTIAL_TOOLS = [
32405
- "bash",
32406
32617
  "read_file",
32407
32618
  "edit_file",
32408
32619
  "write_file",
@@ -32413,27 +32624,88 @@ var init_budgetRuntime = __esm({
32413
32624
  ];
32414
32625
  ADVISORY_NOTICE = "Resource advisory: verification reserve reached. Prioritize test/typecheck/build/diff and targeted repair; avoid broad exploration or delegation.";
32415
32626
  PROTECTED_DENIAL = "Resource protected: remaining tool calls are reserved for verification and targeted repair. Run the required checks (test/typecheck/build), read the failure, apply a minimal fix, retest \u2014 or report BLOCKED with the evidence you have.";
32627
+ HARD_LIMIT_DENIAL = "Resource exhausted: this turn's execution budget (maxToolCalls) is spent. No further billable tool calls are allowed in this turn \u2014 summarize what was verified and report BLOCKED/resource-exhausted with the evidence already collected. A later user turn starts a fresh execution budget.";
32628
+ ESSENTIAL_BASH = [
32629
+ /\b(npm|pnpm|yarn|bun)\s+(run\s+)?(test|vitest|jest)\b/,
32630
+ /\b(npm|pnpm|yarn|bun)\s+run\s+[\w:-]*(typecheck|lint|build)\b/,
32631
+ /\bnpx\s+(vitest|tsc|typescript|eslint)\b/,
32632
+ /\b(npx\s+)?tsc\b/,
32633
+ /\bvitest\b/,
32634
+ /\bjest\b/,
32635
+ /\bnode\s+--run\b/,
32636
+ /\bgit\s+(diff|status|log|show)\b/
32637
+ ];
32416
32638
  BudgetRuntime = class {
32417
32639
  policy;
32418
32640
  enforcement;
32419
32641
  ledger;
32420
32642
  essential;
32643
+ initialStage;
32421
32644
  stage;
32645
+ epoch = 0;
32646
+ executionBaseline = { toolCallsUsed: 0, wallMs: 0, tokensUsed: 0 };
32422
32647
  lastEmitted;
32648
+ hardLimitAnnounced = false;
32423
32649
  constructor(profileId, opts = {}) {
32424
- this.policy = opts.policy ?? defaultResourcePolicy(profileId);
32650
+ const basePolicy = opts.policy ?? defaultResourcePolicy(profileId);
32651
+ const envCap = Number.parseInt(process.env.ZELARI_MAX_TOOL_CALLS ?? "", 10);
32652
+ this.policy = Number.isFinite(envCap) && envCap >= 1 ? { ...basePolicy, maxToolCalls: envCap } : basePolicy;
32425
32653
  this.enforcement = opts.enforcement ?? "advisory";
32426
32654
  this.essential = new Set(opts.essentialTools ?? DEFAULT_ESSENTIAL_TOOLS);
32427
- this.stage = opts.stage ?? "implement";
32655
+ this.initialStage = opts.stage ?? "implement";
32656
+ this.stage = this.initialStage;
32428
32657
  this.ledger = new ResourceLedger();
32429
32658
  }
32659
+ /**
32660
+ * Start a fresh user-turn execution epoch. The durable ledger remains
32661
+ * cumulative for telemetry; only the enforcement baseline moves forward.
32662
+ */
32663
+ beginTurn() {
32664
+ this.executionBaseline = this.ledger.usage();
32665
+ this.epoch += 1;
32666
+ this.stage = this.initialStage;
32667
+ this.hardLimitAnnounced = false;
32668
+ this.lastEmitted = void 0;
32669
+ const next = this.current();
32670
+ this.lastEmitted = next;
32671
+ return next;
32672
+ }
32673
+ /** Cumulative session telemetry (never reset by beginTurn). */
32674
+ sessionUsage() {
32675
+ return this.ledger.usage();
32676
+ }
32430
32677
  /**
32431
32678
  * Count one tool call; returns the snapshot to emit when §10.4 says so
32432
32679
  * (first sight, stage/pressure change, reserve crossing, any usage delta).
32433
32680
  */
32434
32681
  noteToolCall() {
32682
+ return this.consumeToolCall().snapshot;
32683
+ }
32684
+ /**
32685
+ * 2.6.1 (plan §9): count one tool call AND surface the hard-limit event
32686
+ * due on this call — `resource.limit_reached` once at the crossing,
32687
+ * `resource.overrun` for every call past the limit. The spine appends the
32688
+ * event right after its tool.call.
32689
+ */
32690
+ consumeToolCall() {
32435
32691
  this.ledger.record("tool-call");
32436
- return this.emitIfDue();
32692
+ const next = this.current();
32693
+ let hardEvent = null;
32694
+ const data = {
32695
+ epoch: next.epoch,
32696
+ used: next.toolCallsUsed,
32697
+ limit: next.toolCallsLimit,
32698
+ overrun: next.overrun,
32699
+ sessionToolCallsUsed: next.sessionToolCallsUsed
32700
+ };
32701
+ if (!this.hardLimitAnnounced && next.toolCallsRemaining <= 0) {
32702
+ this.hardLimitAnnounced = true;
32703
+ hardEvent = { kind: "resource.limit_reached", data };
32704
+ } else if (next.overrun > 0) {
32705
+ hardEvent = { kind: "resource.overrun", data };
32706
+ }
32707
+ const snapshot = shouldEmitSnapshot(this.lastEmitted, next) ? (this.lastEmitted = next, next) : null;
32708
+ return { snapshot, hardEvent };
32437
32709
  }
32438
32710
  /** §10.4 verification start: stage change (and a zero-cost ledger mark). */
32439
32711
  noteVerificationStart() {
@@ -32450,18 +32722,44 @@ var init_budgetRuntime = __esm({
32450
32722
  this.stage = stage;
32451
32723
  return this.emitIfDue();
32452
32724
  }
32725
+ /** 2.6.1 (plan §14): canonical budget for the reserve gate. */
32726
+ budgetSnapshot() {
32727
+ const cumulative = this.ledger.usage();
32728
+ return computeBudget(
32729
+ this.policy,
32730
+ {
32731
+ toolCallsUsed: Math.max(0, cumulative.toolCallsUsed - this.executionBaseline.toolCallsUsed),
32732
+ elapsedMs: Math.max(0, cumulative.wallMs - this.executionBaseline.wallMs),
32733
+ tokensUsed: Math.max(0, cumulative.tokensUsed - this.executionBaseline.tokensUsed)
32734
+ },
32735
+ this.stage
32736
+ );
32737
+ }
32453
32738
  /** Current projection without emitting. */
32454
32739
  current() {
32455
- return buildResourceSnapshot(this.ledger.budget(this.policy, this.stage), this.policy);
32740
+ const cumulative = this.ledger.usage();
32741
+ return withResourceSnapshotContext(buildResourceSnapshot(this.budgetSnapshot(), this.policy), {
32742
+ epoch: this.epoch,
32743
+ sessionToolCallsUsed: cumulative.toolCallsUsed
32744
+ });
32456
32745
  }
32457
- /** §11.3 gate — advisory mode never blocks; protected mode guards the zone. */
32458
- gateToolCall(toolName) {
32746
+ /**
32747
+ * §11.3 gate — argument-aware (2.6.1 §13). Hard limit denies first in BOTH
32748
+ * modes; advisory mode never blocks inside the protected zone; protected
32749
+ * mode guards the zone with isVerificationEssential(tool, args).
32750
+ */
32751
+ gateToolCall(toolNameOrInput) {
32752
+ const input = typeof toolNameOrInput === "string" ? { toolName: toolNameOrInput } : toolNameOrInput;
32459
32753
  const snapshot = this.current();
32754
+ if (snapshot.toolCallsRemaining <= 0) {
32755
+ return { allowed: false, advisory: false, reason: HARD_LIMIT_DENIAL, snapshot, hardLimit: true };
32756
+ }
32460
32757
  if (!snapshot.reserveProtected) return { allowed: true, advisory: false, snapshot };
32461
32758
  if (this.enforcement === "advisory") {
32462
32759
  return { allowed: true, advisory: true, reason: ADVISORY_NOTICE, snapshot };
32463
32760
  }
32464
- if (this.essential.has(toolName)) {
32761
+ const essential = input.toolName === "bash" || input.toolName === "grep_content" ? isVerificationEssential(input.toolName, input.args, input.stage ?? this.stage) : this.essential.has(input.toolName);
32762
+ if (essential) {
32465
32763
  return { allowed: true, advisory: true, reason: ADVISORY_NOTICE, snapshot };
32466
32764
  }
32467
32765
  return { allowed: false, advisory: false, reason: PROTECTED_DENIAL, snapshot };
@@ -32470,7 +32768,26 @@ var init_budgetRuntime = __esm({
32470
32768
  adoptLedgerFromEvents(events) {
32471
32769
  const rebuilt = rebuildLedgerFromEvents(events);
32472
32770
  this.ledger.resetTo(rebuilt.snapshot());
32771
+ let epochStart = -1;
32772
+ let fallbackUserStart = -1;
32773
+ let restoredEpoch = 0;
32774
+ for (let i = 0; i < events.length; i++) {
32775
+ const event = events[i];
32776
+ if (event.kind === "user.message") fallbackUserStart = i;
32777
+ if (event.kind === "resource.epoch_started") {
32778
+ epochStart = i;
32779
+ const value = event.data.epoch;
32780
+ if (typeof value === "number" && Number.isInteger(value) && value > restoredEpoch) {
32781
+ restoredEpoch = value;
32782
+ }
32783
+ }
32784
+ }
32785
+ const activeStart = epochStart >= 0 ? epochStart : fallbackUserStart;
32786
+ this.executionBaseline = activeStart >= 0 ? rebuildLedgerFromEvents(events.slice(0, activeStart + 1)).usage() : { toolCallsUsed: 0, wallMs: 0, tokensUsed: 0 };
32787
+ this.epoch = restoredEpoch;
32473
32788
  this.lastEmitted = void 0;
32789
+ const now = this.current();
32790
+ this.hardLimitAnnounced = now.toolCallsRemaining <= 0;
32474
32791
  }
32475
32792
  /** Latest emitted snapshot (what the model surface shows), if any. */
32476
32793
  latestEmitted() {
@@ -32488,8 +32805,293 @@ var init_budgetRuntime = __esm({
32488
32805
  }
32489
32806
  });
32490
32807
 
32491
- // src/cli/sessionSpine.ts
32808
+ // src/cli/budget/restoreRuntime.ts
32492
32809
  import path21 from "node:path";
32810
+ async function restoreBudgetRuntimeFromSession(budget, sessionId2, baseDir) {
32811
+ const eventsPath = path21.join(resolveSessionsDir({ baseDir }), sessionId2, "events.jsonl");
32812
+ const report = await readSessionLog(eventsPath).catch(() => null);
32813
+ if (!report || report.events.length === 0) return false;
32814
+ budget.adoptLedgerFromEvents(report.events);
32815
+ return true;
32816
+ }
32817
+ async function lastHarnessManifestHash(sessionId2, baseDir) {
32818
+ const eventsPath = path21.join(resolveSessionsDir({ baseDir }), sessionId2, "events.jsonl");
32819
+ const report = await readSessionLog(eventsPath).catch(() => null);
32820
+ if (!report) return null;
32821
+ for (let i = report.events.length - 1; i >= 0; i--) {
32822
+ const e = report.events[i];
32823
+ if (e.kind === "session.harness_manifest") {
32824
+ const h = e.data.manifestHash;
32825
+ return typeof h === "string" ? h : null;
32826
+ }
32827
+ }
32828
+ return null;
32829
+ }
32830
+ var init_restoreRuntime = __esm({
32831
+ "src/cli/budget/restoreRuntime.ts"() {
32832
+ "use strict";
32833
+ init_session();
32834
+ }
32835
+ });
32836
+
32837
+ // src/cli/utils/cmdline.ts
32838
+ function quoteCmdArg(arg) {
32839
+ if (arg === "") return '""';
32840
+ if (!/[\s"^&|<>()%!]/.test(arg)) return arg;
32841
+ return `"${arg.replace(/"/g, '""')}"`;
32842
+ }
32843
+ function buildCmdLine(command, args) {
32844
+ return [command, ...args].map(quoteCmdArg).join(" ");
32845
+ }
32846
+ var init_cmdline = __esm({
32847
+ "src/cli/utils/cmdline.ts"() {
32848
+ "use strict";
32849
+ }
32850
+ });
32851
+
32852
+ // src/cli/updater.ts
32853
+ var updater_exports = {};
32854
+ __export(updater_exports, {
32855
+ REGISTRY_URL: () => REGISTRY_URL,
32856
+ checkForUpdate: () => checkForUpdate,
32857
+ compareSemver: () => compareSemver,
32858
+ distTagForVersion: () => distTagForVersion,
32859
+ fetchLatestVersion: () => fetchLatestVersion,
32860
+ getCurrentVersion: () => getCurrentVersion,
32861
+ looksLikeBrokenShim: () => looksLikeBrokenShim,
32862
+ performUpdate: () => performUpdate,
32863
+ registryUrlForTag: () => registryUrlForTag,
32864
+ resolveBundledNpmCli: () => resolveBundledNpmCli
32865
+ });
32866
+ import { createRequire } from "node:module";
32867
+ import { spawn as spawn5 } from "node:child_process";
32868
+ import { existsSync as existsSync11 } from "node:fs";
32869
+ import path22 from "node:path";
32870
+ import { fileURLToPath } from "node:url";
32871
+ function resolveBundledNpmCli(execPath = process.execPath) {
32872
+ const dir = path22.dirname(execPath);
32873
+ const candidates = [
32874
+ // Windows: C:\...\node.exe → C:\...\node_modules\npm\bin\npm-cli.js
32875
+ path22.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
32876
+ // POSIX: <prefix>/bin/node → <prefix>/lib/node_modules/npm/bin/npm-cli.js
32877
+ path22.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
32878
+ ];
32879
+ for (const candidate of candidates) {
32880
+ try {
32881
+ if (existsSync11(candidate)) return candidate;
32882
+ } catch {
32883
+ }
32884
+ }
32885
+ return null;
32886
+ }
32887
+ function looksLikeBrokenShim(exitCode, output) {
32888
+ if (exitCode === 127) return true;
32889
+ const h = output.toLowerCase();
32890
+ return h.includes("shim target not found") || h.includes("is not recognized");
32891
+ }
32892
+ function getCurrentVersion() {
32893
+ try {
32894
+ const pkgPath = path22.resolve(__dirname2, "..", "..", "package.json");
32895
+ const pkg = require2(pkgPath);
32896
+ return pkg.version;
32897
+ } catch {
32898
+ return "0.0.0";
32899
+ }
32900
+ }
32901
+ function compareSemver(a, b) {
32902
+ const parse3 = (v) => {
32903
+ const m = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
32904
+ if (!m) return [0, 0, 0, null];
32905
+ return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] ?? null];
32906
+ };
32907
+ const [a1, a2, a3, aPre] = parse3(a);
32908
+ const [b1, b2, b3, bPre] = parse3(b);
32909
+ if (a1 !== b1) return a1 < b1 ? -1 : 1;
32910
+ if (a2 !== b2) return a2 < b2 ? -1 : 1;
32911
+ if (a3 !== b3) return a3 < b3 ? -1 : 1;
32912
+ if (aPre === bPre) return 0;
32913
+ if (aPre === null) return 1;
32914
+ if (bPre === null) return -1;
32915
+ return aPre < bPre ? -1 : 1;
32916
+ }
32917
+ function distTagForVersion(version2) {
32918
+ if (version2.includes("-alpha.")) return "alpha";
32919
+ if (version2.includes("-beta.")) return "beta";
32920
+ if (version2.includes("-next.")) return "next";
32921
+ return "latest";
32922
+ }
32923
+ function registryUrlForTag(tag = distTagForVersion(getCurrentVersion())) {
32924
+ return `https://registry.npmjs.org/zelari-code/${tag}`;
32925
+ }
32926
+ async function fetchLatestVersion(fetcher = fetch, registryUrl = REGISTRY_URL, timeoutMs2 = 5e3) {
32927
+ try {
32928
+ const controller = new AbortController();
32929
+ const timer = setTimeout(() => controller.abort(), timeoutMs2);
32930
+ const response = await fetcher(registryUrl, { signal: controller.signal });
32931
+ clearTimeout(timer);
32932
+ if (!response.ok) {
32933
+ return { error: `Registry responded ${response.status}` };
32934
+ }
32935
+ const data = await response.json();
32936
+ if (!data.version || typeof data.version !== "string") {
32937
+ return { error: "Registry response missing version field" };
32938
+ }
32939
+ return { version: data.version };
32940
+ } catch (err) {
32941
+ const message = err instanceof Error ? err.message : String(err);
32942
+ return { error: message };
32943
+ }
32944
+ }
32945
+ async function checkForUpdate(fetcher = fetch, registryUrl) {
32946
+ const currentVersion = getCurrentVersion();
32947
+ const url2 = registryUrl ?? registryUrlForTag();
32948
+ const latest = await fetchLatestVersion(fetcher, url2);
32949
+ if ("error" in latest) {
32950
+ return {
32951
+ currentVersion,
32952
+ latestVersion: currentVersion,
32953
+ updateAvailable: false,
32954
+ error: latest.error
32955
+ };
32956
+ }
32957
+ const cmp = compareSemver(currentVersion, latest.version);
32958
+ return {
32959
+ currentVersion,
32960
+ latestVersion: latest.version,
32961
+ updateAvailable: cmp < 0
32962
+ };
32963
+ }
32964
+ async function performUpdate(packageName = "zelari-code", executor = spawn5, resolveNpmCli = resolveBundledNpmCli, channel) {
32965
+ const tag = channel ?? distTagForVersion(getCurrentVersion());
32966
+ const args = ["install", "-g", `${packageName}@${tag}`];
32967
+ const primary = await runNpm(executor, args, "shim");
32968
+ if (primary.ok) return primary;
32969
+ const npmCli = resolveNpmCli();
32970
+ if (npmCli && looksLikeBrokenShim(primary.exitCode, primary.output)) {
32971
+ const fallback = await runNpm(executor, args, "bundled", npmCli);
32972
+ return {
32973
+ ...fallback,
32974
+ output: `[update] npm shim failed (${primary.error ?? "exit " + primary.exitCode}); retried via bundled npm (${npmCli}).
32975
+ ${fallback.output}`
32976
+ };
32977
+ }
32978
+ return primary;
32979
+ }
32980
+ function runNpm(executor, args, mode, npmCliPath) {
32981
+ return new Promise((resolve3) => {
32982
+ let stdout = "";
32983
+ let stderr = "";
32984
+ const stdio = ["ignore", "pipe", "pipe"];
32985
+ const child = mode === "bundled" && npmCliPath ? executor(process.execPath, [npmCliPath, ...args], { stdio }) : process.platform === "win32" ? executor(buildCmdLine("npm", args), { stdio, shell: true }) : executor("npm", args, { stdio });
32986
+ child.stdout?.on("data", (chunk) => {
32987
+ stdout += chunk.toString();
32988
+ });
32989
+ child.stderr?.on("data", (chunk) => {
32990
+ stderr += chunk.toString();
32991
+ });
32992
+ child.on("error", (err) => {
32993
+ resolve3({
32994
+ ok: false,
32995
+ output: stdout + stderr,
32996
+ error: err.message,
32997
+ exitCode: null
32998
+ });
32999
+ });
33000
+ child.on("close", (code) => {
33001
+ const ok = code === 0;
33002
+ resolve3({
33003
+ ok,
33004
+ output: stdout + stderr,
33005
+ error: ok ? void 0 : `npm exited with code ${code}`,
33006
+ exitCode: code
33007
+ });
33008
+ });
33009
+ });
33010
+ }
33011
+ var require2, __dirname2, REGISTRY_URL;
33012
+ var init_updater = __esm({
33013
+ "src/cli/updater.ts"() {
33014
+ "use strict";
33015
+ init_cmdline();
33016
+ require2 = createRequire(import.meta.url);
33017
+ __dirname2 = path22.dirname(fileURLToPath(import.meta.url));
33018
+ REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
33019
+ }
33020
+ });
33021
+
33022
+ // src/cli/harnessManifest.ts
33023
+ function buildHarnessManifest(parts) {
33024
+ const prompts = {};
33025
+ for (const [role, text] of Object.entries(parts.prompts ?? {})) {
33026
+ if (typeof text === "string" && text.length > 0) {
33027
+ prompts[role] = harnessInputHash(text);
33028
+ }
33029
+ }
33030
+ const manifest = HarnessManifestV1Schema.parse({
33031
+ schemaVersion: 1,
33032
+ profile: {
33033
+ id: parts.profile.id,
33034
+ phase: parts.phase,
33035
+ hash: profileHash(parts.profile)
33036
+ },
33037
+ prompts,
33038
+ capabilities: {
33039
+ // 2.6.1 (plan §7): full fingerprints when specs are available.
33040
+ toolManifestHash: parts.toolSpecs ? toolFingerprintHash(parts.toolSpecs) : toolManifestHash(parts.toolNames),
33041
+ skillManifestHash: parts.skillSpecs ? skillFingerprintHash(parts.skillSpecs) : harnessInputHash([...parts.skillIds ?? []].sort())
33042
+ },
33043
+ policies: {
33044
+ routingHash: harnessInputHash(parts.routing ?? { unset: true }),
33045
+ verificationHash: harnessInputHash(parts.verification ?? { engine: "deterministic" }),
33046
+ completionPolicyHash: harnessInputHash(parts.completionPolicy ?? { mode: "strict", required: "*" }),
33047
+ compactionHash: harnessInputHash(parts.compaction ?? { version: 1 }),
33048
+ resourcePolicyHash: harnessInputHash(parts.resourcePolicy ?? { unset: true })
33049
+ },
33050
+ runtime: {
33051
+ // 2.6.1 (plan §7): canonical export — no more require.resolve.
33052
+ coreVersion: parts.coreVersion ?? CORE_VERSION,
33053
+ cliVersion: parts.cliVersion ?? getCurrentVersion()
33054
+ }
33055
+ });
33056
+ return { manifest, manifestHash: hashHarnessManifest(manifest) };
33057
+ }
33058
+ var init_harnessManifest2 = __esm({
33059
+ "src/cli/harnessManifest.ts"() {
33060
+ "use strict";
33061
+ init_dist();
33062
+ init_dist();
33063
+ init_updater();
33064
+ }
33065
+ });
33066
+
33067
+ // src/cli/kraken/taskContract.ts
33068
+ function latestTaskContract(events) {
33069
+ let latest;
33070
+ for (const e of events) {
33071
+ if (e.kind !== "task.contract" && e.kind !== "task.contract_updated") continue;
33072
+ const raw = e.data.contract;
33073
+ if (raw && typeof raw === "object") {
33074
+ const candidate = raw;
33075
+ if (!latest || candidate.version > latest.version) latest = candidate;
33076
+ }
33077
+ }
33078
+ return latest;
33079
+ }
33080
+ function updateTaskContract(contract, update) {
33081
+ return applyTaskContractUpdate(contract, update);
33082
+ }
33083
+ function contractEventData(contract, updated) {
33084
+ return { contract, kind: updated ? "task.contract_updated" : "task.contract" };
33085
+ }
33086
+ var init_taskContract2 = __esm({
33087
+ "src/cli/kraken/taskContract.ts"() {
33088
+ "use strict";
33089
+ init_dist();
33090
+ }
33091
+ });
33092
+
33093
+ // src/cli/sessionSpine.ts
33094
+ import path23 from "node:path";
32493
33095
  function spineEnabled() {
32494
33096
  return process.env.ZELARI_SESSION_SPINE !== "0";
32495
33097
  }
@@ -32571,14 +33173,54 @@ async function wrapSessionWriter(inner, sessionId2, options = {}) {
32571
33173
  const spine = await SessionSpineMirror.adopt(sessionId2, options);
32572
33174
  if (spine.status === "active") {
32573
33175
  const profile = options.extraStarted?.profile;
32574
- spine.attachBudgetRuntime(
32575
- new BudgetRuntime(typeof profile === "string" ? profile : "kraken/v1", {
32576
- enforcement: resolveResourceEnforcement()
32577
- })
33176
+ const budget = new BudgetRuntime(typeof profile === "string" ? profile : "kraken/v1", {
33177
+ enforcement: resolveResourceEnforcement()
33178
+ });
33179
+ if (spine.resumedFromSeq !== void 0 && spine.resumedFromSeq > 0) {
33180
+ await restoreBudgetRuntimeFromSession(budget, sessionId2, options.baseDir);
33181
+ }
33182
+ spine.attachBudgetRuntime(budget);
33183
+ await noteHarnessLifecycle(
33184
+ spine,
33185
+ sessionId2,
33186
+ typeof profile === "string" ? profile : "kraken/v1",
33187
+ budget,
33188
+ options.baseDir,
33189
+ options.extraStarted?.phase
32578
33190
  );
32579
33191
  }
32580
33192
  return new SpineMirroringWriter(inner, spine.status === "active" ? spine : null);
32581
33193
  }
33194
+ function taskContractsEnabled() {
33195
+ return process.env.ZELARI_TASK_CONTRACT !== "0";
33196
+ }
33197
+ async function noteHarnessLifecycle(spine, sessionId2, profileId, budget, baseDir, phaseHint) {
33198
+ try {
33199
+ const profile = resolveProfile(profileId);
33200
+ const { manifest, manifestHash } = buildHarnessManifest({
33201
+ profile,
33202
+ phase: phaseHint === "plan" ? "plan" : "build",
33203
+ toolNames: profile.tools,
33204
+ // plan §7/§8: the REAL session policy — never the {unset:true} marker.
33205
+ resourcePolicy: budget.policy
33206
+ });
33207
+ if (spine.resumedFromSeq !== void 0 && spine.resumedFromSeq > 0) {
33208
+ const original = await lastHarnessManifestHash(sessionId2, baseDir);
33209
+ if (original === null) {
33210
+ spine.harnessManifest(manifest, manifestHash);
33211
+ } else if (original !== manifestHash) {
33212
+ await spine.appendEvent({
33213
+ kind: "session.harness_drift",
33214
+ actor: ACTOR_SYSTEM,
33215
+ data: { originalManifestHash: original, currentManifestHash: manifestHash }
33216
+ });
33217
+ }
33218
+ } else {
33219
+ spine.harnessManifest(manifest, manifestHash);
33220
+ }
33221
+ } catch {
33222
+ }
33223
+ }
32582
33224
  var MAX_STREAM_BUFFERS, SessionSpineMirror, SpineMirroringWriter;
32583
33225
  var init_sessionSpine = __esm({
32584
33226
  "src/cli/sessionSpine.ts"() {
@@ -32587,6 +33229,10 @@ var init_sessionSpine = __esm({
32587
33229
  init_session();
32588
33230
  init_verification2();
32589
33231
  init_budgetRuntime();
33232
+ init_restoreRuntime();
33233
+ init_runtime2();
33234
+ init_harnessManifest2();
33235
+ init_taskContract2();
32590
33236
  MAX_STREAM_BUFFERS = 32;
32591
33237
  SessionSpineMirror = class _SessionSpineMirror {
32592
33238
  constructor(sessionId2, options) {
@@ -32600,6 +33246,8 @@ var init_sessionSpine = __esm({
32600
33246
  warned = false;
32601
33247
  /** Host-owned budget runtime (attached via attachBudgetRuntime). */
32602
33248
  budgetRuntime = null;
33249
+ /** True after the host prepared an epoch but before its user.message lands. */
33250
+ resourceTurnPrepared = false;
32603
33251
  /** 2.6 Track A: set once a task.contract has been seeded (or the log had one). */
32604
33252
  contractSeeded = false;
32605
33253
  status = "disabled";
@@ -32615,8 +33263,8 @@ var init_sessionSpine = __esm({
32615
33263
  const mirror = new _SessionSpineMirror(sessionId2, options);
32616
33264
  if (!spineEnabled()) return mirror;
32617
33265
  try {
32618
- const sessionDir = path21.join(mirror.sessionsDir, sessionId2);
32619
- const report = await readSessionLog(path21.join(sessionDir, "events.jsonl"));
33266
+ const sessionDir = path23.join(mirror.sessionsDir, sessionId2);
33267
+ const report = await readSessionLog(path23.join(sessionDir, "events.jsonl"));
32620
33268
  const existed = report.events.length > 0 || report.issues.length > 0;
32621
33269
  if (report.events.some((e) => e.kind === "task.contract" || e.kind === "user.message")) {
32622
33270
  mirror.contractSeeded = true;
@@ -32660,8 +33308,16 @@ var init_sessionSpine = __esm({
32660
33308
  return mirror;
32661
33309
  }
32662
33310
  /** Log the user prompt — the P1 gap the 1.x log never closed. */
32663
- userMessage(text) {
32664
- const seqP = this.append({ kind: "user.message", actor: ACTOR_USER, data: { text } });
33311
+ userMessage(text, options = {}) {
33312
+ if (options.beginResourceTurn !== false) {
33313
+ void this.beginResourceTurn();
33314
+ this.resourceTurnPrepared = false;
33315
+ }
33316
+ const seqP = this.append({
33317
+ kind: "user.message",
33318
+ actor: ACTOR_USER,
33319
+ data: { text, ...options.imported ? { imported: options.imported } : {} }
33320
+ });
32665
33321
  void seqP;
32666
33322
  }
32667
33323
  /**
@@ -32697,24 +33353,77 @@ var init_sessionSpine = __esm({
32697
33353
  */
32698
33354
  attachBudgetRuntime(runtime) {
32699
33355
  this.budgetRuntime = runtime;
33356
+ this.resourceTurnPrepared = false;
33357
+ }
33358
+ /**
33359
+ * Prepare a fresh per-user-turn execution epoch before model context is
33360
+ * derived. The cumulative ResourceLedger is retained; enforcement and the
33361
+ * latest RESOURCE STATUS restart at 0 / policy.maxToolCalls.
33362
+ */
33363
+ beginResourceTurn() {
33364
+ if (!this.budgetRuntime || this.resourceTurnPrepared) {
33365
+ return this.chain.then(() => void 0);
33366
+ }
33367
+ const snapshot = this.budgetRuntime.beginTurn();
33368
+ this.resourceTurnPrepared = true;
33369
+ void this.append({
33370
+ kind: "resource.epoch_started",
33371
+ actor: ACTOR_SYSTEM,
33372
+ data: {
33373
+ epoch: snapshot.epoch,
33374
+ kind: "turn",
33375
+ sessionToolCallsUsed: snapshot.sessionToolCallsUsed
33376
+ }
33377
+ });
33378
+ return this.append({
33379
+ kind: "resource.snapshot",
33380
+ actor: ACTOR_SYSTEM,
33381
+ data: { ...snapshot }
33382
+ }).then(() => void 0);
32700
33383
  }
32701
33384
  /** Latest emitted resource snapshot (the model-visible one), or null. */
32702
33385
  latestResourceSnapshot() {
32703
33386
  return this.budgetRuntime?.latestEmitted() ?? null;
32704
33387
  }
33388
+ /**
33389
+ * 2.6.1 (plan §8): session ResourcePolicy cap for hosts that need to derive
33390
+ * per-turn limits — the policy stays the single authority. Null when no
33391
+ * runtime is attached (hosts keep their own default).
33392
+ */
33393
+ /** Full budget shape for evaluateResourceReserveGate (plan §14). */
33394
+ resourceBudgetSummary() {
33395
+ return this.budgetRuntime?.budgetSnapshot() ?? null;
33396
+ }
33397
+ resourceBudgetLimit() {
33398
+ const snap = this.budgetRuntime?.current();
33399
+ return snap ? {
33400
+ maxToolCalls: snap.toolCallsLimit,
33401
+ remaining: snap.toolCallsRemaining,
33402
+ verificationReserve: snap.verificationReserve
33403
+ } : null;
33404
+ }
32705
33405
  /**
32706
33406
  * §11.3 pre-dispatch gate for hosts that enforce the protected zone
32707
33407
  * (Phase 3): delegates to the attached runtime, never throws. Null when
32708
33408
  * no runtime is attached (hosts treat as "no budget info, allow").
33409
+ * 2.6.1 (plan §13): argument-aware — pass the tool args so `bash` is only
33410
+ * essential when it is a test/typecheck/build/git-diff command.
32709
33411
  */
32710
- gateResourceToolCall(toolName) {
32711
- const gate = this.budgetRuntime?.gateToolCall(toolName);
33412
+ gateResourceToolCall(toolName, args) {
33413
+ const gate = this.budgetRuntime?.gateToolCall({ toolName, args });
32712
33414
  if (!gate) return null;
32713
- return { allowed: gate.allowed, ...gate.reason ? { reason: gate.reason } : {} };
33415
+ return {
33416
+ allowed: gate.allowed,
33417
+ ...gate.reason ? { reason: gate.reason } : {},
33418
+ ...gate.hardLimit ? { hardLimit: gate.hardLimit } : {}
33419
+ };
32714
33420
  }
32715
- /** Count a landed tool.call; returns the snapshot due (§10.4), if any. */
33421
+ /**
33422
+ * Count a landed tool.call; returns the snapshot due (§10.4) plus the
33423
+ * hard-limit event due on this call (2.6.1 plan §9), if any.
33424
+ */
32716
33425
  onToolCallBudget() {
32717
- return this.budgetRuntime?.noteToolCall() ?? null;
33426
+ return this.budgetRuntime?.consumeToolCall() ?? { snapshot: null, hardEvent: null };
32718
33427
  }
32719
33428
  async flush() {
32720
33429
  await this.chain;
@@ -32726,7 +33435,7 @@ var init_sessionSpine = __esm({
32726
33435
  async derivedPriorTurns() {
32727
33436
  if (this.status !== "active" && this.status !== "closed") return null;
32728
33437
  const report = await readSessionLog(
32729
- path21.join(this.sessionsDir, this.sessionId, "events.jsonl")
33438
+ path23.join(this.sessionsDir, this.sessionId, "events.jsonl")
32730
33439
  ).catch(() => null);
32731
33440
  if (!report || report.events.length === 0) return null;
32732
33441
  return deriveMessages(report.events);
@@ -32736,7 +33445,7 @@ var init_sessionSpine = __esm({
32736
33445
  if (this.status !== "active" && this.status !== "closed") return null;
32737
33446
  await this.flush();
32738
33447
  const report = await readSessionLog(
32739
- path21.join(this.sessionsDir, this.sessionId, "events.jsonl")
33448
+ path23.join(this.sessionsDir, this.sessionId, "events.jsonl")
32740
33449
  ).catch(() => null);
32741
33450
  if (!report || report.events.length === 0) return null;
32742
33451
  return buildCompactionStateSnapshot(report.events, toSeq);
@@ -32749,7 +33458,7 @@ var init_sessionSpine = __esm({
32749
33458
  async lastVerificationRun() {
32750
33459
  if (this.status !== "active" && this.status !== "closed") return null;
32751
33460
  const report = await readSessionLog(
32752
- path21.join(this.sessionsDir, this.sessionId, "events.jsonl")
33461
+ path23.join(this.sessionsDir, this.sessionId, "events.jsonl")
32753
33462
  ).catch(() => null);
32754
33463
  if (!report) return null;
32755
33464
  return lastVerificationRun(report.events);
@@ -32820,10 +33529,18 @@ var init_sessionSpine = __esm({
32820
33529
  }
32821
33530
  append(input) {
32822
33531
  if (!this.writer || this.status === "closed") return Promise.resolve(null);
32823
- const dueSnapshot = input.kind === "tool.call" ? this.onToolCallBudget() : null;
32824
- const dueContract = input.kind === "user.message" && process.env.ZELARI_TASK_CONTRACT === "1" && !this.contractSeeded ? (this.contractSeeded = true, input.data?.text) : null;
33532
+ const budgetEffect = input.kind === "tool.call" ? this.onToolCallBudget() : null;
33533
+ const dueContract = input.kind === "user.message" && taskContractsEnabled() && !this.contractSeeded ? (this.contractSeeded = true, input.data?.text) : null;
33534
+ const steerText = input.kind === "user.message" && taskContractsEnabled() && this.contractSeeded && !dueContract ? input.data?.text ?? null : null;
32825
33535
  let seq = this.chain.then(() => this.writer.append(input)).then((envelope) => envelope.seq);
32826
- if (dueSnapshot) {
33536
+ if (budgetEffect?.hardEvent) {
33537
+ const hard = budgetEffect.hardEvent;
33538
+ seq = seq.then(
33539
+ (s) => this.writer.append({ kind: hard.kind, actor: ACTOR_SYSTEM, data: { ...hard.data } }).then(() => s)
33540
+ );
33541
+ }
33542
+ if (budgetEffect?.snapshot) {
33543
+ const dueSnapshot = budgetEffect.snapshot;
32827
33544
  seq = seq.then(
32828
33545
  (s) => this.writer.append({ kind: "resource.snapshot", actor: ACTOR_SYSTEM, data: { ...dueSnapshot } }).then(() => s)
32829
33546
  );
@@ -32844,6 +33561,30 @@ var init_sessionSpine = __esm({
32844
33561
  return s;
32845
33562
  });
32846
33563
  }
33564
+ if (steerText) {
33565
+ seq = seq.then(async (s) => {
33566
+ try {
33567
+ const eventsPath = path23.join(this.sessionsDir, this.sessionId, "events.jsonl");
33568
+ const report = await readSessionLog(eventsPath).catch(() => null);
33569
+ if (!report || typeof s !== "number") return s;
33570
+ const current = latestTaskContract(report.events);
33571
+ if (!current) return s;
33572
+ const updated = updateTaskContract(current, {
33573
+ addConstraints: [{ id: `steer-${s}`, text: steerText, source: "user", required: false }],
33574
+ nextUserSeq: s
33575
+ });
33576
+ if (updated.version !== current.version) {
33577
+ await this.writer.append({
33578
+ kind: "task.contract_updated",
33579
+ actor: ACTOR_SYSTEM,
33580
+ data: contractEventData(updated, true)
33581
+ });
33582
+ }
33583
+ } catch {
33584
+ }
33585
+ return s;
33586
+ });
33587
+ }
32847
33588
  this.chain = seq.catch((err) => {
32848
33589
  this.status = "degraded";
32849
33590
  this.writer = null;
@@ -33070,10 +33811,10 @@ var init_sessionSurface = __esm({
33070
33811
  });
33071
33812
 
33072
33813
  // src/cli/hooks/observationStore.ts
33073
- import { existsSync as existsSync11, statSync as statSync2 } from "node:fs";
33074
- import path22 from "node:path";
33814
+ import { existsSync as existsSync12, statSync as statSync2 } from "node:fs";
33815
+ import path24 from "node:path";
33075
33816
  function sessionFilePath(sessionId2, baseDir) {
33076
- return path22.join(baseDir ?? getSessionBaseDir(), `${sessionId2}.jsonl`);
33817
+ return path24.join(baseDir ?? getSessionBaseDir(), `${sessionId2}.jsonl`);
33077
33818
  }
33078
33819
  function isToolEnd(e) {
33079
33820
  return e.type === "tool_execution_end";
@@ -33083,7 +33824,7 @@ function isToolStart(e) {
33083
33824
  }
33084
33825
  async function loadObservationIndex(sessionId2, baseDir) {
33085
33826
  const filePath = sessionFilePath(sessionId2, baseDir);
33086
- const exists = existsSync11(filePath);
33827
+ const exists = existsSync12(filePath);
33087
33828
  let mtimeMs = 0;
33088
33829
  if (exists) {
33089
33830
  try {
@@ -33095,7 +33836,7 @@ async function loadObservationIndex(sessionId2, baseDir) {
33095
33836
  const hit = cache.get(sessionId2);
33096
33837
  if (hit && !exists) return hit;
33097
33838
  if (hit && hit.filePath === filePath && hit.mtimeMs === mtimeMs) return hit;
33098
- const events = existsSync11(filePath) ? await readSession(filePath) : [];
33839
+ const events = existsSync12(filePath) ? await readSession(filePath) : [];
33099
33840
  const names = /* @__PURE__ */ new Map();
33100
33841
  const bySeq = /* @__PURE__ */ new Map();
33101
33842
  const byToolCallId = /* @__PURE__ */ new Map();
@@ -33192,8 +33933,8 @@ var init_observationStore = __esm({
33192
33933
  });
33193
33934
 
33194
33935
  // src/cli/metrics.ts
33195
- import { promises as fs14, existsSync as existsSync12, statSync as statSync3, renameSync, appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
33196
- import path23 from "node:path";
33936
+ import { promises as fs14, existsSync as existsSync13, statSync as statSync3, renameSync, appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
33937
+ import path25 from "node:path";
33197
33938
  import os6 from "node:os";
33198
33939
  async function readMetrics(file2) {
33199
33940
  let raw = "";
@@ -33242,8 +33983,8 @@ var init_metrics2 = __esm({
33242
33983
  file;
33243
33984
  writeQueue = Promise.resolve();
33244
33985
  constructor(file2) {
33245
- this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path23.join(os6.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
33246
- mkdirSync6(path23.dirname(this.file), { recursive: true });
33986
+ this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path25.join(os6.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
33987
+ mkdirSync6(path25.dirname(this.file), { recursive: true });
33247
33988
  }
33248
33989
  /** Metrics file path — doctor/summary readers use this. */
33249
33990
  get filePath() {
@@ -33278,7 +34019,7 @@ var init_metrics2 = __esm({
33278
34019
  }
33279
34020
  /** If the file is over the rotation threshold, rotate it. */
33280
34021
  maybeRotate() {
33281
- if (!existsSync12(this.file)) return;
34022
+ if (!existsSync13(this.file)) return;
33282
34023
  try {
33283
34024
  const stat = statSync3(this.file);
33284
34025
  if (stat.size >= METRICS_ROTATE_BYTES) {
@@ -34459,8 +35200,8 @@ var init_resolveStream = __esm({
34459
35200
  });
34460
35201
 
34461
35202
  // packages/core/dist/core/tools/toolOutputSpill.js
34462
- import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
34463
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
35203
+ import { createHash as createHash7, randomBytes as randomBytes2 } from "node:crypto";
35204
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
34464
35205
  import { homedir as homedir3, tmpdir } from "node:os";
34465
35206
  import { join as join11 } from "node:path";
34466
35207
  function resolveToolOutputDir() {
@@ -34486,10 +35227,10 @@ function spillToolOutput(fullText, meta3) {
34486
35227
  return null;
34487
35228
  try {
34488
35229
  const dir = resolveToolOutputDir();
34489
- if (!existsSync13(dir)) {
35230
+ if (!existsSync14(dir)) {
34490
35231
  mkdirSync7(dir, { recursive: true });
34491
35232
  }
34492
- const hash3 = createHash6("sha256").update(fullText).digest("hex").slice(0, 12);
35233
+ const hash3 = createHash7("sha256").update(fullText).digest("hex").slice(0, 12);
34493
35234
  const stamp = Date.now().toString(36);
34494
35235
  const rnd = randomBytes2(3).toString("hex");
34495
35236
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
@@ -34753,14 +35494,14 @@ var init_registry2 = __esm({
34753
35494
  });
34754
35495
 
34755
35496
  // src/cli/safety/sandboxPath.ts
34756
- import path24 from "node:path";
35497
+ import path26 from "node:path";
34757
35498
  function resolveSandboxedPath(userPath, options = {}) {
34758
35499
  if (typeof userPath !== "string" || userPath.length === 0) {
34759
35500
  throw new SandboxViolationError("Empty path", userPath, "");
34760
35501
  }
34761
- const root = path24.resolve(options.root ?? process.cwd());
34762
- const resolved = path24.isAbsolute(userPath) ? path24.resolve(userPath) : path24.resolve(root, userPath);
34763
- const rootWithSep = root.endsWith(path24.sep) ? root : root + path24.sep;
35502
+ const root = path26.resolve(options.root ?? process.cwd());
35503
+ const resolved = path26.isAbsolute(userPath) ? path26.resolve(userPath) : path26.resolve(root, userPath);
35504
+ const rootWithSep = root.endsWith(path26.sep) ? root : root + path26.sep;
34764
35505
  if (resolved !== root && !resolved.startsWith(rootWithSep)) {
34765
35506
  throw new SandboxViolationError(
34766
35507
  `Path escapes sandbox root: ${userPath} \u2192 ${resolved} (root: ${root})`,
@@ -34848,12 +35589,12 @@ __export(auditLogger_exports, {
34848
35589
  AuditLogger: () => AuditLogger
34849
35590
  });
34850
35591
  import { promises as fs15 } from "node:fs";
34851
- import path25 from "node:path";
35592
+ import path27 from "node:path";
34852
35593
  import os7 from "node:os";
34853
35594
  function defaultAuditPath() {
34854
35595
  const override = process.env.ANATHEMA_AUDIT_LOG;
34855
35596
  if (override && override.trim().length > 0) return override;
34856
- return path25.join(os7.tmpdir(), "zelari-code", "audit.jsonl");
35597
+ return path27.join(os7.tmpdir(), "zelari-code", "audit.jsonl");
34857
35598
  }
34858
35599
  function redactArgs(args) {
34859
35600
  const redacted = {};
@@ -34896,7 +35637,7 @@ var init_auditLogger = __esm({
34896
35637
  async append(entry) {
34897
35638
  const line = JSON.stringify(entry) + "\n";
34898
35639
  this.writeQueue = this.writeQueue.then(async () => {
34899
- await fs15.mkdir(path25.dirname(this.logPath), { recursive: true });
35640
+ await fs15.mkdir(path27.dirname(this.logPath), { recursive: true });
34900
35641
  await fs15.appendFile(this.logPath, line, "utf-8");
34901
35642
  });
34902
35643
  return this.writeQueue;
@@ -34940,25 +35681,10 @@ var init_auditLogger = __esm({
34940
35681
  }
34941
35682
  });
34942
35683
 
34943
- // src/cli/utils/cmdline.ts
34944
- function quoteCmdArg(arg) {
34945
- if (arg === "") return '""';
34946
- if (!/[\s"^&|<>()%!]/.test(arg)) return arg;
34947
- return `"${arg.replace(/"/g, '""')}"`;
34948
- }
34949
- function buildCmdLine(command, args) {
34950
- return [command, ...args].map(quoteCmdArg).join(" ");
34951
- }
34952
- var init_cmdline = __esm({
34953
- "src/cli/utils/cmdline.ts"() {
34954
- "use strict";
34955
- }
34956
- });
34957
-
34958
35684
  // src/cli/diagnostics/engine.ts
34959
- import { spawn as spawn5 } from "node:child_process";
34960
- import { existsSync as existsSync14 } from "node:fs";
34961
- import path26 from "node:path";
35685
+ import { spawn as spawn6 } from "node:child_process";
35686
+ import { existsSync as existsSync15 } from "node:fs";
35687
+ import path28 from "node:path";
34962
35688
  function parseEslintJson(stdout, _file2) {
34963
35689
  const json2 = safeJson(stdout);
34964
35690
  if (!Array.isArray(json2)) return [];
@@ -35017,7 +35743,7 @@ function safeJson(s) {
35017
35743
  }
35018
35744
  }
35019
35745
  function providerForFile(file2, providers = DEFAULT_PROVIDERS) {
35020
- const ext = path26.extname(file2).toLowerCase();
35746
+ const ext = path28.extname(file2).toLowerCase();
35021
35747
  return providers.find((p3) => p3.extensions.includes(ext)) ?? null;
35022
35748
  }
35023
35749
  function resolveBin(bin, cwd) {
@@ -35025,10 +35751,10 @@ function resolveBin(bin, cwd) {
35025
35751
  let dir = cwd;
35026
35752
  for (let i = 0; i < 6; i += 1) {
35027
35753
  for (const suffix of suffixes) {
35028
- const candidate = path26.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
35029
- if (existsSync14(candidate)) return candidate;
35754
+ const candidate = path28.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
35755
+ if (existsSync15(candidate)) return candidate;
35030
35756
  }
35031
- const parent = path26.dirname(dir);
35757
+ const parent = path28.dirname(dir);
35032
35758
  if (parent === dir) break;
35033
35759
  dir = parent;
35034
35760
  }
@@ -35101,7 +35827,7 @@ var init_engine2 = __esm({
35101
35827
  };
35102
35828
  let child;
35103
35829
  try {
35104
- child = process.platform === "win32" ? spawn5(buildCmdLine(cmd, args), { cwd: opts.cwd, shell: true }) : spawn5(cmd, args, { cwd: opts.cwd });
35830
+ child = process.platform === "win32" ? spawn6(buildCmdLine(cmd, args), { cwd: opts.cwd, shell: true }) : spawn6(cmd, args, { cwd: opts.cwd });
35105
35831
  } catch {
35106
35832
  done({ code: null, stdout: "", stderr: "" });
35107
35833
  return;
@@ -35132,19 +35858,19 @@ var init_engine2 = __esm({
35132
35858
  });
35133
35859
 
35134
35860
  // src/cli/tools/krakenRadio.ts
35135
- import { appendFileSync as appendFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync14, readdirSync as readdirSync2 } from "node:fs";
35136
- import path27 from "node:path";
35861
+ import { appendFileSync as appendFileSync3, existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync14, readdirSync as readdirSync2 } from "node:fs";
35862
+ import path29 from "node:path";
35137
35863
  function radioDir(cwd) {
35138
- return path27.join(cwd, ".zelari", "radio");
35864
+ return path29.join(cwd, ".zelari", "radio");
35139
35865
  }
35140
35866
  function radioPath(cwd, sessionId2) {
35141
35867
  const safe = (sessionId2 || "default").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
35142
- return path27.join(radioDir(cwd), `${safe}.jsonl`);
35868
+ return path29.join(radioDir(cwd), `${safe}.jsonl`);
35143
35869
  }
35144
35870
  function appendKrakenRadio(cwd, sessionId2, event) {
35145
35871
  try {
35146
35872
  const dir = radioDir(cwd);
35147
- if (!existsSync15(dir)) mkdirSync8(dir, { recursive: true });
35873
+ if (!existsSync16(dir)) mkdirSync8(dir, { recursive: true });
35148
35874
  const row = {
35149
35875
  ts: event.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
35150
35876
  kind: event.kind,
@@ -35165,7 +35891,7 @@ function appendKrakenRadio(cwd, sessionId2, event) {
35165
35891
  function readKrakenRadio(cwd, sessionId2, limit = 50) {
35166
35892
  try {
35167
35893
  const file2 = radioPath(cwd, sessionId2);
35168
- if (!existsSync15(file2)) return [];
35894
+ if (!existsSync16(file2)) return [];
35169
35895
  const lines = readFileSync14(file2, "utf8").split(/\r?\n/).filter(Boolean);
35170
35896
  const slice = lines.slice(-Math.max(1, limit));
35171
35897
  const out = [];
@@ -35189,7 +35915,7 @@ function formatKrakenRadioStatus(cwd, sessionId2, limit = 12) {
35189
35915
  const flag = e.ok === false ? "\u2717" : e.ok === true ? "\u2713" : "\xB7";
35190
35916
  const ms = e.durationMs != null ? ` ${e.durationMs}ms` : "";
35191
35917
  const model = e.model ? ` [${e.model}]` : "";
35192
- const wt = e.worktree ? ` wt=${path27.basename(e.worktree)}` : "";
35918
+ const wt = e.worktree ? ` wt=${path29.basename(e.worktree)}` : "";
35193
35919
  const detail = e.detail ? ` \u2014 ${e.detail.slice(0, 120)}` : "";
35194
35920
  return `${flag} ${e.ts.slice(11, 19)} ${e.kind} ${e.agent} "${e.description}"${model}${wt}${ms}${detail}`;
35195
35921
  });
@@ -35203,8 +35929,8 @@ var init_krakenRadio = __esm({
35203
35929
 
35204
35930
  // src/cli/tools/krakenWorktree.ts
35205
35931
  import { execFile as execFile2 } from "node:child_process";
35206
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, rmSync } from "node:fs";
35207
- import path28 from "node:path";
35932
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9, rmSync } from "node:fs";
35933
+ import path30 from "node:path";
35208
35934
  import { promisify } from "node:util";
35209
35935
  import { randomBytes as randomBytes3 } from "node:crypto";
35210
35936
  function isKrakenWorktreeEnabled(env = process.env) {
@@ -35252,10 +35978,10 @@ async function createKrakenWorktree(cwd, label) {
35252
35978
  const id = `${Date.now().toString(36)}-${randomBytes3(3).toString("hex")}`;
35253
35979
  const slug = (label ?? "task").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "task";
35254
35980
  const branch = `kraken/${slug}-${id}`;
35255
- const wtRoot = path28.join(repoRoot, ".zelari", "worktrees");
35256
- const wtPath = path28.join(wtRoot, `kraken-${id}`);
35981
+ const wtRoot = path30.join(repoRoot, ".zelari", "worktrees");
35982
+ const wtPath = path30.join(wtRoot, `kraken-${id}`);
35257
35983
  try {
35258
- if (!existsSync16(wtRoot)) mkdirSync9(wtRoot, { recursive: true });
35984
+ if (!existsSync17(wtRoot)) mkdirSync9(wtRoot, { recursive: true });
35259
35985
  } catch {
35260
35986
  return null;
35261
35987
  }
@@ -35367,7 +36093,7 @@ async function cleanupKrakenWorktree(handle, env = process.env) {
35367
36093
  if (shouldKeepWorktree(env)) return;
35368
36094
  await git2(handle.repoRoot, ["worktree", "remove", "--force", handle.path]);
35369
36095
  try {
35370
- if (existsSync16(handle.path)) {
36096
+ if (existsSync17(handle.path)) {
35371
36097
  rmSync(handle.path, { recursive: true, force: true });
35372
36098
  }
35373
36099
  } catch {
@@ -36704,7 +37430,7 @@ var init_askUser = __esm({
36704
37430
  });
36705
37431
 
36706
37432
  // src/cli/skillsMd.ts
36707
- import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync15 } from "node:fs";
37433
+ import { existsSync as existsSync18, readdirSync as readdirSync3, readFileSync as readFileSync15 } from "node:fs";
36708
37434
  import { join as join12 } from "node:path";
36709
37435
  import { homedir as homedir4 } from "node:os";
36710
37436
  function skillMdSearchDirs(projectRoot = process.cwd()) {
@@ -36770,7 +37496,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36770
37496
  const summary = { loaded: [], skipped: [] };
36771
37497
  const seen = new Set(options.existingIds ?? []);
36772
37498
  for (const dir of skillMdSearchDirs(projectRoot)) {
36773
- if (!existsSync17(dir)) continue;
37499
+ if (!existsSync18(dir)) continue;
36774
37500
  let entries;
36775
37501
  try {
36776
37502
  entries = readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -36779,7 +37505,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36779
37505
  }
36780
37506
  for (const entry of entries) {
36781
37507
  const skillPath = join12(dir, entry, "SKILL.md");
36782
- if (!existsSync17(skillPath)) continue;
37508
+ if (!existsSync18(skillPath)) continue;
36783
37509
  try {
36784
37510
  const parsed = parseSkillMd(readFileSync15(skillPath, "utf8"), skillPath);
36785
37511
  if (!parsed) {
@@ -36954,14 +37680,14 @@ var init_todoTools = __esm({
36954
37680
  import {
36955
37681
  mkdirSync as mkdirSync10,
36956
37682
  writeFileSync as writeFileSync12,
36957
- existsSync as existsSync18,
37683
+ existsSync as existsSync19,
36958
37684
  accessSync,
36959
37685
  constants,
36960
37686
  realpathSync
36961
37687
  } from "node:fs";
36962
37688
  import { join as join13, basename } from "node:path";
36963
37689
  import { homedir as homedir5 } from "node:os";
36964
- import { createHash as createHash7 } from "node:crypto";
37690
+ import { createHash as createHash8 } from "node:crypto";
36965
37691
  function resolveWorkspaceRoot(projectRoot = process.cwd()) {
36966
37692
  const candidates = [
36967
37693
  join13(projectRoot, ".zelari"),
@@ -36977,11 +37703,11 @@ function resolveWorkspaceRoot(projectRoot = process.cwd()) {
36977
37703
  return candidates[0];
36978
37704
  }
36979
37705
  function hashProject(projectPath) {
36980
- return createHash7("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
37706
+ return createHash8("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
36981
37707
  }
36982
37708
  function isWritableDir(dir) {
36983
37709
  try {
36984
- if (!existsSync18(dir)) return false;
37710
+ if (!existsSync19(dir)) return false;
36985
37711
  accessSync(dir, constants.W_OK);
36986
37712
  return true;
36987
37713
  } catch {
@@ -36990,9 +37716,9 @@ function isWritableDir(dir) {
36990
37716
  }
36991
37717
  function ensureWorkspaceDir(workspaceDir) {
36992
37718
  mkdirSync10(workspaceDir, { recursive: true });
36993
- if (workspaceDir.endsWith("/.zelari") && existsSync18(join13(workspaceDir, "..", ".git"))) {
37719
+ if (workspaceDir.endsWith("/.zelari") && existsSync19(join13(workspaceDir, "..", ".git"))) {
36994
37720
  const gitignorePath = join13(workspaceDir, ".gitignore");
36995
- if (!existsSync18(gitignorePath)) {
37721
+ if (!existsSync19(gitignorePath)) {
36996
37722
  writeFileSync12(gitignorePath, "*\n!.gitignore\n");
36997
37723
  }
36998
37724
  }
@@ -37032,7 +37758,7 @@ __export(storage_exports, {
37032
37758
  import {
37033
37759
  readFileSync as readFileSync16,
37034
37760
  writeFileSync as writeFileSync13,
37035
- existsSync as existsSync19,
37761
+ existsSync as existsSync20,
37036
37762
  mkdirSync as mkdirSync11,
37037
37763
  readdirSync as readdirSync4,
37038
37764
  renameSync as renameSync2
@@ -37292,7 +38018,7 @@ var init_storage = __esm({
37292
38018
  Storage = class {
37293
38019
  /** Read a Markdown file with frontmatter. Throws if not found. */
37294
38020
  read(path65) {
37295
- if (!existsSync19(path65)) {
38021
+ if (!existsSync20(path65)) {
37296
38022
  throw new Error(`File not found: ${path65}`);
37297
38023
  }
37298
38024
  const md = readFileSync16(path65, "utf8");
@@ -37300,7 +38026,7 @@ var init_storage = __esm({
37300
38026
  }
37301
38027
  /** Read a Markdown file; returns null if not found. */
37302
38028
  readIfExists(path65) {
37303
- if (!existsSync19(path65)) return null;
38029
+ if (!existsSync20(path65)) return null;
37304
38030
  return this.read(path65);
37305
38031
  }
37306
38032
  /**
@@ -37316,7 +38042,7 @@ var init_storage = __esm({
37316
38042
  }
37317
38043
  /** List all .md files in a directory (non-recursive). */
37318
38044
  listMarkdown(dir) {
37319
- if (!existsSync19(dir)) return [];
38045
+ if (!existsSync20(dir)) return [];
37320
38046
  return readdirSync4(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join14(dir, f));
37321
38047
  }
37322
38048
  };
@@ -37349,7 +38075,7 @@ var init_storage = __esm({
37349
38075
  // src/cli/workspace/planStore.ts
37350
38076
  import {
37351
38077
  copyFileSync,
37352
- existsSync as existsSync20,
38078
+ existsSync as existsSync21,
37353
38079
  mkdirSync as mkdirSync12,
37354
38080
  readFileSync as readFileSync17,
37355
38081
  renameSync as renameSync3,
@@ -37400,7 +38126,7 @@ function writePlanTaskArtifact(rootDir, task) {
37400
38126
  }
37401
38127
  function loadHandle(rootDir) {
37402
38128
  const jsonPath = join15(rootDir, "plan.json");
37403
- if (!existsSync20(jsonPath)) {
38129
+ if (!existsSync21(jsonPath)) {
37404
38130
  return { rootDir, tasks: [], counter: 0, rootFields: {} };
37405
38131
  }
37406
38132
  let parsed;
@@ -37435,7 +38161,7 @@ function saveHandle(rootDir, handle) {
37435
38161
  }
37436
38162
  const jsonPath = join15(rootDir, "plan.json");
37437
38163
  mkdirSync12(rootDir, { recursive: true });
37438
- if (existsSync20(jsonPath)) {
38164
+ if (existsSync21(jsonPath)) {
37439
38165
  copyFileSync(jsonPath, `${jsonPath}.bak`);
37440
38166
  }
37441
38167
  const file2 = {
@@ -37717,8 +38443,8 @@ var init_planTaskTools = __esm({
37717
38443
 
37718
38444
  // src/cli/tools/inspectTypecheckSafety.ts
37719
38445
  import { promises as fs16 } from "node:fs";
37720
- import path29 from "node:path";
37721
- import { spawn as spawn6 } from "node:child_process";
38446
+ import path31 from "node:path";
38447
+ import { spawn as spawn7 } from "node:child_process";
37722
38448
  async function scanTsbuildinfo(root) {
37723
38449
  const found = [];
37724
38450
  const stack = [root];
@@ -37731,11 +38457,11 @@ async function scanTsbuildinfo(root) {
37731
38457
  continue;
37732
38458
  }
37733
38459
  for (const entry of entries) {
37734
- const p3 = path29.join(dir, entry.name);
38460
+ const p3 = path31.join(dir, entry.name);
37735
38461
  if (entry.isDirectory()) {
37736
38462
  if (!SCAN_SKIP.has(entry.name)) stack.push(p3);
37737
38463
  } else if (entry.name.endsWith(".tsbuildinfo")) {
37738
- found.push(path29.relative(root, p3).split(path29.sep).join("/"));
38464
+ found.push(path31.relative(root, p3).split(path31.sep).join("/"));
37739
38465
  }
37740
38466
  }
37741
38467
  }
@@ -37744,7 +38470,7 @@ async function scanTsbuildinfo(root) {
37744
38470
  }
37745
38471
  async function gitStatusPorcelain(root) {
37746
38472
  return new Promise((resolve3) => {
37747
- const child = spawn6("git", ["status", "--porcelain"], { cwd: root, shell: false });
38473
+ const child = spawn7("git", ["status", "--porcelain"], { cwd: root, shell: false });
37748
38474
  let out = "";
37749
38475
  child.stdout.on("data", (d) => out += d.toString());
37750
38476
  child.stderr.on("data", (d) => out += d.toString());
@@ -37771,7 +38497,7 @@ async function cleanupArtifacts(root, relPaths) {
37771
38497
  const failed = [];
37772
38498
  for (const rel2 of relPaths) {
37773
38499
  try {
37774
- await fs16.unlink(path29.join(root, rel2));
38500
+ await fs16.unlink(path31.join(root, rel2));
37775
38501
  cleaned.push(rel2);
37776
38502
  } catch {
37777
38503
  failed.push(rel2);
@@ -37794,17 +38520,17 @@ var init_inspectTypecheckSafety = __esm({
37794
38520
  });
37795
38521
 
37796
38522
  // src/cli/tools/inspectCommand.ts
37797
- import { spawn as spawn7 } from "node:child_process";
37798
- import { createHash as createHash8 } from "node:crypto";
37799
- import { existsSync as existsSync21, promises as fs17 } from "node:fs";
38523
+ import { spawn as spawn8 } from "node:child_process";
38524
+ import { createHash as createHash9 } from "node:crypto";
38525
+ import { existsSync as existsSync22, promises as fs17 } from "node:fs";
37800
38526
  import os8 from "node:os";
37801
- import path30 from "node:path";
38527
+ import path32 from "node:path";
37802
38528
  function resolveNodeModuleBin(start, rel2) {
37803
- let dir = path30.resolve(start);
38529
+ let dir = path32.resolve(start);
37804
38530
  for (; ; ) {
37805
- const candidate = path30.join(dir, "node_modules", rel2);
37806
- if (existsSync21(candidate)) return candidate;
37807
- const parent = path30.dirname(dir);
38531
+ const candidate = path32.join(dir, "node_modules", rel2);
38532
+ if (existsSync22(candidate)) return candidate;
38533
+ const parent = path32.dirname(dir);
37808
38534
  if (parent === dir) return void 0;
37809
38535
  dir = parent;
37810
38536
  }
@@ -37872,7 +38598,7 @@ function buildInspectCommand(op, ctx) {
37872
38598
  case "npm_ls":
37873
38599
  case "npm_outdated":
37874
38600
  case "npm_view": {
37875
- const npmCli = ctx.npmCliPath ?? resolveNodeModuleBin(ctx.root, path30.join("npm", "bin", "npm-cli.js")) ?? path30.join(ctx.root, "node_modules", "npm", "bin", "npm-cli.js");
38601
+ const npmCli = ctx.npmCliPath ?? resolveNodeModuleBin(ctx.root, path32.join("npm", "bin", "npm-cli.js")) ?? path32.join(ctx.root, "node_modules", "npm", "bin", "npm-cli.js");
37876
38602
  if (op.operation === "npm_view") {
37877
38603
  const err = rejectFlagLike("package", op.package);
37878
38604
  if (err) return { ok: false, reason: err };
@@ -37881,14 +38607,14 @@ function buildInspectCommand(op, ctx) {
37881
38607
  return { ok: true, command: process.execPath, argv: [npmCli, ...sub], inspectionClass: "env-info" };
37882
38608
  }
37883
38609
  case "typecheck": {
37884
- const project = path30.resolve(ctx.cwd, op.project ?? "tsconfig.json");
37885
- const hash3 = createHash8("sha256").update(project).digest("hex").slice(0, 16);
37886
- const tsBuildInfoFile = path30.join(os8.tmpdir(), "zelari-inspect", `${hash3}.tsbuildinfo`);
38610
+ const project = path32.resolve(ctx.cwd, op.project ?? "tsconfig.json");
38611
+ const hash3 = createHash9("sha256").update(project).digest("hex").slice(0, 16);
38612
+ const tsBuildInfoFile = path32.join(os8.tmpdir(), "zelari-inspect", `${hash3}.tsbuildinfo`);
37887
38613
  return {
37888
38614
  ok: true,
37889
38615
  command: process.execPath,
37890
38616
  argv: [
37891
- ctx.tscPath ?? resolveNodeModuleBin(ctx.root, path30.join("typescript", "bin", "tsc")) ?? resolveNodeModuleBin(ctx.cwd, path30.join("typescript", "bin", "tsc")) ?? path30.join(ctx.root, "node_modules", "typescript", "bin", "tsc"),
38617
+ ctx.tscPath ?? resolveNodeModuleBin(ctx.root, path32.join("typescript", "bin", "tsc")) ?? resolveNodeModuleBin(ctx.cwd, path32.join("typescript", "bin", "tsc")) ?? path32.join(ctx.root, "node_modules", "typescript", "bin", "tsc"),
37892
38618
  "--noEmit",
37893
38619
  // S3.5 primary mechanism: redirect, never disable — composite forces
37894
38620
  // incremental (TS#30661), so --incremental false would break on the
@@ -37910,7 +38636,7 @@ function runSpawn(command, argv, opts) {
37910
38636
  return new Promise((resolve3) => {
37911
38637
  let child;
37912
38638
  try {
37913
- child = spawn7(command, argv, { cwd: opts.cwd, shell: false });
38639
+ child = spawn8(command, argv, { cwd: opts.cwd, shell: false });
37914
38640
  } catch (err) {
37915
38641
  resolve3({ code: null, stdout: "", stderr: String(err), timedOut: false, spawnError: String(err) });
37916
38642
  return;
@@ -38648,9 +39374,9 @@ var init_client = __esm({
38648
39374
  });
38649
39375
 
38650
39376
  // src/cli/lsp/servers.ts
38651
- import path31 from "node:path";
39377
+ import path33 from "node:path";
38652
39378
  function languageIdForFile(file2) {
38653
- const ext = path31.extname(file2).toLowerCase();
39379
+ const ext = path33.extname(file2).toLowerCase();
38654
39380
  const map2 = {
38655
39381
  ".ts": "typescript",
38656
39382
  ".tsx": "typescriptreact",
@@ -38665,7 +39391,7 @@ function languageIdForFile(file2) {
38665
39391
  return map2[ext] ?? "plaintext";
38666
39392
  }
38667
39393
  function serverForFile(file2, servers = LSP_SERVERS) {
38668
- const ext = path31.extname(file2).toLowerCase();
39394
+ const ext = path33.extname(file2).toLowerCase();
38669
39395
  return servers.find((s) => s.extensions.includes(ext)) ?? null;
38670
39396
  }
38671
39397
  function resolveServerCommand(file2, cwd, servers = LSP_SERVERS) {
@@ -38714,7 +39440,7 @@ var init_servers = __esm({
38714
39440
  });
38715
39441
 
38716
39442
  // src/cli/lsp/manager.ts
38717
- import { spawn as spawn8 } from "node:child_process";
39443
+ import { spawn as spawn9 } from "node:child_process";
38718
39444
  import { readFileSync as readFileSync18 } from "node:fs";
38719
39445
  function processTransport(child) {
38720
39446
  return {
@@ -38850,7 +39576,7 @@ var init_manager = __esm({
38850
39576
  // languages already flagged as unavailable
38851
39577
  constructor(options = {}) {
38852
39578
  this.cwd = options.cwd ?? process.cwd();
38853
- this.spawnImpl = options.spawnImpl ?? spawn8;
39579
+ this.spawnImpl = options.spawnImpl ?? spawn9;
38854
39580
  this.timeoutMs = options.timeoutMs ?? 15e3;
38855
39581
  this.onWarn = options.onWarn ?? ((m) => console.error(m));
38856
39582
  }
@@ -39041,7 +39767,7 @@ var init_manager = __esm({
39041
39767
 
39042
39768
  // src/cli/ast/engine.ts
39043
39769
  import { readFile } from "node:fs/promises";
39044
- import path32 from "node:path";
39770
+ import path34 from "node:path";
39045
39771
  function loadTs() {
39046
39772
  if (!tsPromise) {
39047
39773
  tsPromise = import("typescript").then((m) => m.default ?? m).catch(() => null);
@@ -39052,8 +39778,8 @@ function errMessage(err) {
39052
39778
  return err instanceof Error ? err.message : String(err);
39053
39779
  }
39054
39780
  async function parseFileSymbolsDiag(file2, cwd) {
39055
- const resolvedPath = path32.isAbsolute(file2) ? file2 : path32.join(cwd ?? process.cwd(), file2);
39056
- const extension = path32.extname(resolvedPath).toLowerCase();
39781
+ const resolvedPath = path34.isAbsolute(file2) ? file2 : path34.join(cwd ?? process.cwd(), file2);
39782
+ const extension = path34.extname(resolvedPath).toLowerCase();
39057
39783
  if (!TS_EXTENSIONS.has(extension)) {
39058
39784
  return {
39059
39785
  status: "unsupported-extension",
@@ -39095,7 +39821,7 @@ async function parseFileSymbolsDiag(file2, cwd) {
39095
39821
  }
39096
39822
  let source;
39097
39823
  try {
39098
- source = ts.createSourceFile(path32.basename(resolvedPath), text, ts.ScriptTarget.Latest, true);
39824
+ source = ts.createSourceFile(path34.basename(resolvedPath), text, ts.ScriptTarget.Latest, true);
39099
39825
  } catch (err) {
39100
39826
  return {
39101
39827
  status: "parse-error",
@@ -39318,13 +40044,13 @@ var init_store2 = __esm({
39318
40044
  });
39319
40045
 
39320
40046
  // src/cli/semantic/index.ts
39321
- import { promises as fs18, existsSync as existsSync22, readFileSync as readFileSync19 } from "node:fs";
40047
+ import { promises as fs18, existsSync as existsSync23, readFileSync as readFileSync19 } from "node:fs";
39322
40048
  import { homedir as homedir6 } from "node:os";
39323
- import path33 from "node:path";
39324
- import { createHash as createHash9 } from "node:crypto";
40049
+ import path35 from "node:path";
40050
+ import { createHash as createHash10 } from "node:crypto";
39325
40051
  function getIndexPath(root) {
39326
- const hash3 = createHash9("sha1").update(path33.resolve(root)).digest("hex").slice(0, 16);
39327
- return process.env.ZELARI_SEMANTIC_FILE ?? path33.join(homedir6(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
40052
+ const hash3 = createHash10("sha1").update(path35.resolve(root)).digest("hex").slice(0, 16);
40053
+ return process.env.ZELARI_SEMANTIC_FILE ?? path35.join(homedir6(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
39328
40054
  }
39329
40055
  async function collectSourceFiles(root, maxFiles = 1500) {
39330
40056
  const out = [];
@@ -39342,11 +40068,11 @@ async function collectSourceFiles(root, maxFiles = 1500) {
39342
40068
  if (entry.isDirectory() && IGNORE_DIRS.has(entry.name)) continue;
39343
40069
  if (entry.isDirectory()) continue;
39344
40070
  }
39345
- const full = path33.join(dir, entry.name);
40071
+ const full = path35.join(dir, entry.name);
39346
40072
  if (entry.isDirectory()) {
39347
40073
  if (IGNORE_DIRS.has(entry.name)) continue;
39348
40074
  await walk2(full);
39349
- } else if (SOURCE_EXTENSIONS.has(path33.extname(entry.name).toLowerCase())) {
40075
+ } else if (SOURCE_EXTENSIONS.has(path35.extname(entry.name).toLowerCase())) {
39350
40076
  out.push(full);
39351
40077
  }
39352
40078
  }
@@ -39395,14 +40121,14 @@ async function buildIndex(files, embed, options) {
39395
40121
  }
39396
40122
  async function saveIndex(root, data) {
39397
40123
  const file2 = getIndexPath(root);
39398
- await fs18.mkdir(path33.dirname(file2), { recursive: true });
40124
+ await fs18.mkdir(path35.dirname(file2), { recursive: true });
39399
40125
  const tmp = `${file2}.tmp-${process.pid}`;
39400
40126
  await fs18.writeFile(tmp, JSON.stringify(data), "utf8");
39401
40127
  await fs18.rename(tmp, file2);
39402
40128
  }
39403
40129
  function loadIndex(root) {
39404
40130
  const file2 = getIndexPath(root);
39405
- if (!existsSync22(file2)) return null;
40131
+ if (!existsSync23(file2)) return null;
39406
40132
  try {
39407
40133
  const parsed = JSON.parse(readFileSync19(file2, "utf8"));
39408
40134
  if (parsed && Array.isArray(parsed.chunks)) return parsed;
@@ -39547,7 +40273,7 @@ var init_provider = __esm({
39547
40273
  });
39548
40274
 
39549
40275
  // src/cli/semantic/tools.ts
39550
- import path34 from "node:path";
40276
+ import path36 from "node:path";
39551
40277
  function createSemanticTool(deps) {
39552
40278
  const buildEmbedFn = deps.buildEmbedFn ?? buildProviderEmbedFn;
39553
40279
  return {
@@ -39574,7 +40300,7 @@ function createSemanticTool(deps) {
39574
40300
  return typedOk({
39575
40301
  count: res.hits.length,
39576
40302
  results: res.hits.map((h) => ({
39577
- location: `${path34.relative(deps.root, h.file) || h.file}:${h.startLine}-${h.endLine}`,
40303
+ location: `${path36.relative(deps.root, h.file) || h.file}:${h.startLine}-${h.endLine}`,
39578
40304
  score: Number(h.score.toFixed(3)),
39579
40305
  preview: h.text.length > 400 ? `${h.text.slice(0, 400)}\u2026` : h.text
39580
40306
  }))
@@ -39593,8 +40319,8 @@ var init_tools4 = __esm({
39593
40319
  });
39594
40320
 
39595
40321
  // src/cli/browser/driver.ts
39596
- import { createRequire } from "node:module";
39597
- import path35 from "node:path";
40322
+ import { createRequire as createRequire2 } from "node:module";
40323
+ import path37 from "node:path";
39598
40324
  import { pathToFileURL } from "node:url";
39599
40325
  function asPlaywright(mod) {
39600
40326
  if (!mod || typeof mod !== "object") return null;
@@ -39605,10 +40331,10 @@ function asPlaywright(mod) {
39605
40331
  return null;
39606
40332
  }
39607
40333
  async function loadPlaywright(cwd) {
39608
- const base = cwd && cwd.length > 0 ? path35.resolve(cwd) : void 0;
40334
+ const base = cwd && cwd.length > 0 ? path37.resolve(cwd) : void 0;
39609
40335
  if (base) {
39610
40336
  try {
39611
- const req = createRequire(path35.join(base, "package.json"));
40337
+ const req = createRequire2(path37.join(base, "package.json"));
39612
40338
  const resolved = req.resolve("playwright");
39613
40339
  const mod = await import(pathToFileURL(resolved).href);
39614
40340
  const pw = asPlaywright(mod);
@@ -39823,7 +40549,7 @@ var init_driver = __esm({
39823
40549
  });
39824
40550
 
39825
40551
  // src/cli/browser/tools.ts
39826
- import path36 from "node:path";
40552
+ import path38 from "node:path";
39827
40553
  import os9 from "node:os";
39828
40554
  function createBrowserTool(deps = {}) {
39829
40555
  return {
@@ -39842,7 +40568,7 @@ function createBrowserTool(deps = {}) {
39842
40568
  execute: async (args, ctx) => {
39843
40569
  const a = args;
39844
40570
  const dir = deps.screenshotDir ?? os9.tmpdir();
39845
- const screenshotPath = a.screenshot === false ? void 0 : path36.join(dir, `zelari-browser-${Date.now()}.png`);
40571
+ const screenshotPath = a.screenshot === false ? void 0 : path38.join(dir, `zelari-browser-${Date.now()}.png`);
39846
40572
  const result = await runBrowserCheck(
39847
40573
  {
39848
40574
  url: a.url,
@@ -39934,14 +40660,14 @@ __export(targets_exports, {
39934
40660
  });
39935
40661
  import {
39936
40662
  chmodSync,
39937
- existsSync as existsSync23,
40663
+ existsSync as existsSync24,
39938
40664
  mkdirSync as mkdirSync13,
39939
40665
  readFileSync as readFileSync20,
39940
40666
  writeFileSync as writeFileSync15
39941
40667
  } from "node:fs";
39942
40668
  import { dirname as dirname4, join as join16 } from "node:path";
39943
40669
  import { homedir as homedir7 } from "node:os";
39944
- import { spawn as spawn9 } from "node:child_process";
40670
+ import { spawn as spawn10 } from "node:child_process";
39945
40671
  function getSshTargetsPath() {
39946
40672
  return join16(homedir7(), ".zelari-code", "ssh-targets.json");
39947
40673
  }
@@ -39955,7 +40681,7 @@ function normalizeAuth(auth) {
39955
40681
  }
39956
40682
  function readSecrets() {
39957
40683
  const path65 = getSshSecretsPath();
39958
- if (!existsSync23(path65)) return {};
40684
+ if (!existsSync24(path65)) return {};
39959
40685
  try {
39960
40686
  return JSON.parse(readFileSync20(path65, "utf8"));
39961
40687
  } catch {
@@ -39998,7 +40724,7 @@ function deleteSshPassword(id) {
39998
40724
  }
39999
40725
  function readStore2() {
40000
40726
  const path65 = getSshTargetsPath();
40001
- if (!existsSync23(path65)) return [];
40727
+ if (!existsSync24(path65)) return [];
40002
40728
  try {
40003
40729
  const parsed = JSON.parse(readFileSync20(path65, "utf8"));
40004
40730
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
@@ -40171,7 +40897,7 @@ function runSsh(target, remoteCommand, timeoutMs2 = 6e4) {
40171
40897
  if (!env.DISPLAY) env.DISPLAY = "1";
40172
40898
  env.ZELARI_SSH_ASKPASS_PASS = pass;
40173
40899
  }
40174
- const child = spawn9("ssh", args, {
40900
+ const child = spawn10("ssh", args, {
40175
40901
  windowsHide: true,
40176
40902
  env,
40177
40903
  stdio: ["ignore", "pipe", "pipe"]
@@ -40212,7 +40938,7 @@ function readSshPublicKey(keyOrPubPath) {
40212
40938
  if (!raw) return { ok: false, error: "Empty path" };
40213
40939
  const candidates = raw.endsWith(".pub") ? [raw] : [`${raw}.pub`, raw];
40214
40940
  for (const p3 of candidates) {
40215
- if (!existsSync23(p3)) continue;
40941
+ if (!existsSync24(p3)) continue;
40216
40942
  try {
40217
40943
  const content = readFileSync20(p3, "utf8").trim();
40218
40944
  if (!content) continue;
@@ -40396,10 +41122,10 @@ var init_tools6 = __esm({
40396
41122
 
40397
41123
  // src/cli/workspace/worldModel.ts
40398
41124
  import { promises as fs19 } from "node:fs";
40399
- import path37 from "node:path";
40400
- import { spawn as spawn10 } from "node:child_process";
41125
+ import path39 from "node:path";
41126
+ import { spawn as spawn11 } from "node:child_process";
40401
41127
  function worldDir(cwd) {
40402
- return path37.join(cwd, WORLD_DIR_NAME);
41128
+ return path39.join(cwd, WORLD_DIR_NAME);
40403
41129
  }
40404
41130
  async function ensureWorldDir(cwd) {
40405
41131
  const dir = worldDir(cwd);
@@ -40409,10 +41135,10 @@ async function ensureWorldDir(cwd) {
40409
41135
  async function appendTimeline(cwd, entry) {
40410
41136
  const dir = await ensureWorldDir(cwd);
40411
41137
  const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }) + "\n";
40412
- await fs19.appendFile(path37.join(dir, TIMELINE_FILE), line, "utf8");
41138
+ await fs19.appendFile(path39.join(dir, TIMELINE_FILE), line, "utf8");
40413
41139
  }
40414
41140
  async function readChecks(cwd) {
40415
- const p3 = path37.join(worldDir(cwd), CHECKS_FILE);
41141
+ const p3 = path39.join(worldDir(cwd), CHECKS_FILE);
40416
41142
  try {
40417
41143
  const raw = await fs19.readFile(p3, "utf8");
40418
41144
  const parsed = JSON.parse(raw);
@@ -40424,7 +41150,7 @@ async function readChecks(cwd) {
40424
41150
  function runShell(command, cwd, timeoutMs2, signal) {
40425
41151
  return new Promise((resolve3) => {
40426
41152
  const isWin = process.platform === "win32";
40427
- const child = spawn10(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
41153
+ const child = spawn11(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
40428
41154
  cwd,
40429
41155
  env: process.env,
40430
41156
  windowsHide: true,
@@ -40487,8 +41213,8 @@ function runShell(command, cwd, timeoutMs2, signal) {
40487
41213
  });
40488
41214
  }
40489
41215
  async function runBacktest(cwd, signal) {
40490
- const checksPath = path37.join(worldDir(cwd), CHECKS_FILE);
40491
- const hypothesisPath = path37.join(worldDir(cwd), HYPOTHESIS_FILE);
41216
+ const checksPath = path39.join(worldDir(cwd), CHECKS_FILE);
41217
+ const hypothesisPath = path39.join(worldDir(cwd), HYPOTHESIS_FILE);
40492
41218
  const checks = await readChecks(cwd);
40493
41219
  if (checks.length === 0) {
40494
41220
  return {
@@ -40557,7 +41283,7 @@ var init_worldModel = __esm({
40557
41283
  "use strict";
40558
41284
  init_zod();
40559
41285
  init_toolTypes();
40560
- WORLD_DIR_NAME = path37.join(".zelari", "world");
41286
+ WORLD_DIR_NAME = path39.join(".zelari", "world");
40561
41287
  HYPOTHESIS_FILE = "hypothesis.md";
40562
41288
  CHECKS_FILE = "checks.json";
40563
41289
  TIMELINE_FILE = "timeline.jsonl";
@@ -40574,7 +41300,7 @@ var init_worldModel = __esm({
40574
41300
  execute: async (args, ctx) => {
40575
41301
  try {
40576
41302
  const dir = await ensureWorldDir(ctx.cwd);
40577
- const file2 = path37.join(dir, HYPOTHESIS_FILE);
41303
+ const file2 = path39.join(dir, HYPOTHESIS_FILE);
40578
41304
  if (args.append) {
40579
41305
  const block = `
40580
41306
 
@@ -40613,7 +41339,7 @@ ${args.content}
40613
41339
  execute: async (args, ctx) => {
40614
41340
  try {
40615
41341
  const dir = await ensureWorldDir(ctx.cwd);
40616
- const file2 = path37.join(dir, CHECKS_FILE);
41342
+ const file2 = path39.join(dir, CHECKS_FILE);
40617
41343
  const body = { checks: args.checks };
40618
41344
  await fs19.writeFile(file2, JSON.stringify(body, null, 2) + "\n", "utf8");
40619
41345
  await appendTimeline(ctx.cwd, { kind: "checks_set", count: args.checks.length });
@@ -40652,8 +41378,8 @@ ${args.content}
40652
41378
  stdoutPreview: "(dryRun)",
40653
41379
  mismatch: "dryRun"
40654
41380
  })),
40655
- hypothesisPath: path37.join(worldDir(ctx.cwd), HYPOTHESIS_FILE),
40656
- checksPath: path37.join(worldDir(ctx.cwd), CHECKS_FILE)
41381
+ hypothesisPath: path39.join(worldDir(ctx.cwd), HYPOTHESIS_FILE),
41382
+ checksPath: path39.join(worldDir(ctx.cwd), CHECKS_FILE)
40657
41383
  });
40658
41384
  }
40659
41385
  const result = await runBacktest(ctx.cwd, ctx.signal);
@@ -40677,7 +41403,7 @@ ${args.content}
40677
41403
  execute: async (args, ctx) => {
40678
41404
  try {
40679
41405
  const dir = await ensureWorldDir(ctx.cwd);
40680
- const file2 = path37.join(dir, TIMELINE_FILE);
41406
+ const file2 = path39.join(dir, TIMELINE_FILE);
40681
41407
  await appendTimeline(ctx.cwd, {
40682
41408
  kind: args.kind,
40683
41409
  summary: args.summary,
@@ -40785,13 +41511,13 @@ __export(folderTrust_exports, {
40785
41511
  untrustFolder: () => untrustFolder
40786
41512
  });
40787
41513
  import { homedir as homedir8 } from "node:os";
40788
- import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "node:fs";
40789
- import path38 from "node:path";
41514
+ import { existsSync as existsSync25, mkdirSync as mkdirSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "node:fs";
41515
+ import path40 from "node:path";
40790
41516
  function trustStorePath() {
40791
- return _overrideStorePath ?? path38.join(homedir8(), ".zelari-code", "trust.json");
41517
+ return _overrideStorePath ?? path40.join(homedir8(), ".zelari-code", "trust.json");
40792
41518
  }
40793
41519
  function normalize4(p3) {
40794
- const resolved = path38.resolve(p3);
41520
+ const resolved = path40.resolve(p3);
40795
41521
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
40796
41522
  }
40797
41523
  function readStore3() {
@@ -40807,7 +41533,7 @@ function readStore3() {
40807
41533
  function writeStore3(store6) {
40808
41534
  const p3 = trustStorePath();
40809
41535
  try {
40810
- mkdirSync14(path38.dirname(p3), { recursive: true });
41536
+ mkdirSync14(path40.dirname(p3), { recursive: true });
40811
41537
  writeFileSync16(p3, JSON.stringify(store6, null, 2), "utf8");
40812
41538
  } catch (err) {
40813
41539
  throw new Error(
@@ -40833,7 +41559,7 @@ function isFolderTrusted(folderPath) {
40833
41559
  }
40834
41560
  function trustFolder(folderPath) {
40835
41561
  const store6 = readStore3();
40836
- const normalized = path38.resolve(folderPath);
41562
+ const normalized = path40.resolve(folderPath);
40837
41563
  if (!store6.folders.some((f) => normalize4(f.path) === normalize4(normalized))) {
40838
41564
  store6.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
40839
41565
  writeStore3(store6);
@@ -40863,7 +41589,7 @@ function getTrustStorePath() {
40863
41589
  return trustStorePath();
40864
41590
  }
40865
41591
  function hasTrustStore() {
40866
- return existsSync24(trustStorePath());
41592
+ return existsSync25(trustStorePath());
40867
41593
  }
40868
41594
  function _setTrustStorePathForTests(p3) {
40869
41595
  _overrideStorePath = p3;
@@ -40947,9 +41673,9 @@ var init_lifecycleHooks = __esm({
40947
41673
  });
40948
41674
 
40949
41675
  // src/cli/toolResultCache.ts
40950
- import { createHash as createHash10 } from "node:crypto";
41676
+ import { createHash as createHash11 } from "node:crypto";
40951
41677
  import { promises as fs20 } from "node:fs";
40952
- import path39 from "node:path";
41678
+ import path41 from "node:path";
40953
41679
  function isToolCacheEnabled() {
40954
41680
  const raw = process.env.ZELARI_TOOL_CACHE;
40955
41681
  return raw !== "0" && raw !== "false" && raw !== "off";
@@ -40960,7 +41686,7 @@ function resolveToolCacheTtlMs() {
40960
41686
  return Number.isFinite(n) && n >= 0 ? n : TOOL_CACHE_DEFAULT_TTL_MS;
40961
41687
  }
40962
41688
  function hashKey(parts) {
40963
- return createHash10("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
41689
+ return createHash11("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
40964
41690
  }
40965
41691
  function resultBytes(result) {
40966
41692
  try {
@@ -41034,7 +41760,7 @@ async function statKey(toolName, input, ctx) {
41034
41760
  if (!input || typeof input !== "object") return null;
41035
41761
  const rawPath = input.path;
41036
41762
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
41037
- const abs = path39.isAbsolute(rawPath) ? rawPath : path39.join(ctx.cwd, rawPath);
41763
+ const abs = path41.isAbsolute(rawPath) ? rawPath : path41.join(ctx.cwd, rawPath);
41038
41764
  try {
41039
41765
  const st = await fs20.stat(abs);
41040
41766
  return hashKey({
@@ -41715,14 +42441,14 @@ var init_toolRegistry = __esm({
41715
42441
  });
41716
42442
 
41717
42443
  // src/cli/state/fileStateStore.ts
41718
- import { createHash as createHash12, randomUUID as randomUUID2 } from "node:crypto";
42444
+ import { createHash as createHash13, randomUUID as randomUUID2 } from "node:crypto";
41719
42445
  import { promises as fs21 } from "node:fs";
41720
- import * as path41 from "node:path";
42446
+ import * as path43 from "node:path";
41721
42447
  function shortId() {
41722
42448
  return randomUUID2().replace(/-/g, "").slice(0, 12);
41723
42449
  }
41724
42450
  async function writeJsonAtomic(filePath, data) {
41725
- await fs21.mkdir(path41.dirname(filePath), { recursive: true });
42451
+ await fs21.mkdir(path43.dirname(filePath), { recursive: true });
41726
42452
  const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
41727
42453
  await fs21.writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
41728
42454
  await fs21.rename(tmp, filePath);
@@ -41767,7 +42493,7 @@ async function getStateStore(projectRoot, env = process.env) {
41767
42493
  }
41768
42494
  }
41769
42495
  function hashStablePrompt(stable) {
41770
- return createHash12("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
42496
+ return createHash13("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
41771
42497
  }
41772
42498
  var DEFAULT_MATERIALIZE_CHARS, FileDurableStateStore, NoopDurableStateStore;
41773
42499
  var init_fileStateStore = __esm({
@@ -41783,11 +42509,11 @@ var init_fileStateStore = __esm({
41783
42509
  indexPath = "";
41784
42510
  async init(projectRoot) {
41785
42511
  this.root = projectRoot;
41786
- this.stateDir = path41.join(projectRoot, ".zelari", "state");
41787
- this.commitsDir = path41.join(this.stateDir, "commits");
41788
- this.artifactsDir = path41.join(this.stateDir, "artifacts");
41789
- this.headPath = path41.join(this.stateDir, "HEAD.json");
41790
- this.indexPath = path41.join(this.stateDir, "index.jsonl");
42512
+ this.stateDir = path43.join(projectRoot, ".zelari", "state");
42513
+ this.commitsDir = path43.join(this.stateDir, "commits");
42514
+ this.artifactsDir = path43.join(this.stateDir, "artifacts");
42515
+ this.headPath = path43.join(this.stateDir, "HEAD.json");
42516
+ this.indexPath = path43.join(this.stateDir, "index.jsonl");
41791
42517
  await fs21.mkdir(this.commitsDir, { recursive: true });
41792
42518
  await fs21.mkdir(this.artifactsDir, { recursive: true });
41793
42519
  }
@@ -41800,13 +42526,13 @@ var init_fileStateStore = __esm({
41800
42526
  const discoveries = input.discoveries ?? [];
41801
42527
  const parent = await this.head();
41802
42528
  const id = shortId();
41803
- const artifactRel = path41.join("artifacts", id);
41804
- const artifactAbs = path41.join(this.artifactsDir, id);
42529
+ const artifactRel = path43.join("artifacts", id);
42530
+ const artifactAbs = path43.join(this.artifactsDir, id);
41805
42531
  await fs21.mkdir(artifactAbs, { recursive: true });
41806
42532
  const summary = defaultSummary(input, discoveries);
41807
- await fs21.writeFile(path41.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
41808
- await writeJsonAtomic(path41.join(artifactAbs, "discoveries.json"), discoveries);
41809
- await writeJsonAtomic(path41.join(artifactAbs, "verification.json"), input.verification);
42533
+ await fs21.writeFile(path43.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
42534
+ await writeJsonAtomic(path43.join(artifactAbs, "discoveries.json"), discoveries);
42535
+ await writeJsonAtomic(path43.join(artifactAbs, "verification.json"), input.verification);
41810
42536
  const meta3 = {
41811
42537
  id,
41812
42538
  parentId: parent?.id ?? null,
@@ -41818,14 +42544,14 @@ var init_fileStateStore = __esm({
41818
42544
  workspaceCheckpointId: input.workspaceCheckpointId,
41819
42545
  verification: {
41820
42546
  ...input.verification,
41821
- reportPath: input.verification.reportPath ?? path41.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
42547
+ reportPath: input.verification.reportPath ?? path43.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
41822
42548
  },
41823
42549
  changedPaths: input.changedPaths ?? [],
41824
42550
  stablePromptHash: input.stablePromptHash,
41825
42551
  discoveryCount: discoveries.length,
41826
42552
  artifactDir: artifactRel.replace(/\\/g, "/")
41827
42553
  };
41828
- await writeJsonAtomic(path41.join(this.commitsDir, `${id}.json`), meta3);
42554
+ await writeJsonAtomic(path43.join(this.commitsDir, `${id}.json`), meta3);
41829
42555
  await writeJsonAtomic(this.headPath, { id, updatedAt: meta3.createdAt });
41830
42556
  await fs21.appendFile(this.indexPath, JSON.stringify({ id, createdAt: meta3.createdAt, label: meta3.label }) + "\n", "utf8");
41831
42557
  return stripStored(meta3);
@@ -41836,7 +42562,7 @@ var init_fileStateStore = __esm({
41836
42562
  return this.get(head.id);
41837
42563
  }
41838
42564
  async get(id) {
41839
- const stored = await readJsonFile(path41.join(this.commitsDir, `${id}.json`));
42565
+ const stored = await readJsonFile(path43.join(this.commitsDir, `${id}.json`));
41840
42566
  return stored ? stripStored(stored) : null;
41841
42567
  }
41842
42568
  async list(limit = 20) {
@@ -41875,9 +42601,9 @@ var init_fileStateStore = __esm({
41875
42601
  async loadDiscoveries(id) {
41876
42602
  const meta3 = id ? await this.get(id) : await this.head();
41877
42603
  if (!meta3) return [];
41878
- const stored = await readJsonFile(path41.join(this.commitsDir, `${meta3.id}.json`));
42604
+ const stored = await readJsonFile(path43.join(this.commitsDir, `${meta3.id}.json`));
41879
42605
  if (!stored?.artifactDir) return [];
41880
- const discPath = path41.join(this.stateDir, stored.artifactDir, "discoveries.json");
42606
+ const discPath = path43.join(this.stateDir, stored.artifactDir, "discoveries.json");
41881
42607
  return await readJsonFile(discPath) ?? [];
41882
42608
  }
41883
42609
  async materializeContext(id, maxChars = DEFAULT_MATERIALIZE_CHARS) {
@@ -42531,7 +43257,7 @@ __export(conversationContext_exports, {
42531
43257
  setHistory: () => setHistory,
42532
43258
  setLastClarification: () => setLastClarification
42533
43259
  });
42534
- import { existsSync as existsSync25 } from "node:fs";
43260
+ import { existsSync as existsSync26 } from "node:fs";
42535
43261
  import { join as join19 } from "node:path";
42536
43262
  function getHistory() {
42537
43263
  return history;
@@ -42541,7 +43267,7 @@ function setHistory(messages) {
42541
43267
  history = projected === messages ? [...messages] : projected;
42542
43268
  }
42543
43269
  function compactInPlace(cwd = process.cwd()) {
42544
- const durableStatePresent = existsSync25(join19(cwd, ".zelari", "state", "HEAD.json"));
43270
+ const durableStatePresent = existsSync26(join19(cwd, ".zelari", "state", "HEAD.json"));
42545
43271
  history = applySessionSurface(compactHistory(history, { durableStatePresent }));
42546
43272
  }
42547
43273
  function appendMessages(msgs) {
@@ -43124,7 +43850,6 @@ __export(headlessSpine_exports, {
43124
43850
  seedHeadlessModelHistory: () => seedHeadlessModelHistory,
43125
43851
  sessionStartedEvent: () => sessionStartedEvent
43126
43852
  });
43127
- import path42 from "node:path";
43128
43853
  function sessionStartedEvent(handle) {
43129
43854
  return {
43130
43855
  type: "session_started",
@@ -43158,12 +43883,10 @@ async function openHeadlessSpine(opts) {
43158
43883
  if (spine.status === "active") {
43159
43884
  const budget = new BudgetRuntime(profileId, { enforcement: resolveResourceEnforcement() });
43160
43885
  if (spine.resumedFromSeq !== void 0 && spine.resumedFromSeq > 0) {
43161
- const prior = await readSessionLog(
43162
- path42.join(spine.sessionsDir, opts.sessionId, "events.jsonl")
43163
- ).catch(() => null);
43164
- if (prior) budget.adoptLedgerFromEvents(prior.events);
43886
+ await restoreBudgetRuntimeFromSession(budget, opts.sessionId, opts.baseDir);
43165
43887
  }
43166
43888
  spine.attachBudgetRuntime(budget);
43889
+ await noteHarnessLifecycle(spine, opts.sessionId, profileId, budget, opts.baseDir);
43167
43890
  spine.note("headless.profile", { profile: profileId, mode: opts.mode ?? "kraken" });
43168
43891
  }
43169
43892
  return {
@@ -43175,11 +43898,20 @@ async function openHeadlessSpine(opts) {
43175
43898
  spine.mirrorBrainEvent(ev);
43176
43899
  }
43177
43900
  },
43901
+ beginResourceTurn() {
43902
+ return spine.beginResourceTurn();
43903
+ },
43178
43904
  userMessage(text) {
43179
43905
  spine.userMessage(text);
43180
43906
  },
43181
- gateResourceToolCall(toolName) {
43182
- return spine.gateResourceToolCall(toolName);
43907
+ gateResourceToolCall(toolName, args) {
43908
+ return spine.gateResourceToolCall(toolName, args);
43909
+ },
43910
+ resourceBudgetLimit() {
43911
+ return spine.resourceBudgetLimit();
43912
+ },
43913
+ resourceBudgetSummary() {
43914
+ return spine.resourceBudgetSummary();
43183
43915
  },
43184
43916
  verificationRun(payload) {
43185
43917
  spine.verificationRun(payload);
@@ -43253,7 +43985,7 @@ async function seedHeadlessModelHistory(handle, legacy) {
43253
43985
  }
43254
43986
  for (const m of legacySeed) {
43255
43987
  if (m.role === "user") {
43256
- mirror.userMessage(m.content);
43988
+ mirror.userMessage(m.content, { beginResourceTurn: false, imported: "legacy-history" });
43257
43989
  } else {
43258
43990
  mirror.assistantMessage(m.content, { imported: "legacy-history" });
43259
43991
  }
@@ -43297,10 +44029,11 @@ var init_headlessSpine = __esm({
43297
44029
  init_dist();
43298
44030
  init_session();
43299
44031
  init_mission2();
43300
- init_session();
43301
44032
  init_runtime2();
43302
44033
  init_sessionSpine();
43303
44034
  init_budgetRuntime();
44035
+ init_restoreRuntime();
44036
+ init_sessionSpine();
43304
44037
  init_headless();
43305
44038
  }
43306
44039
  });
@@ -43444,7 +44177,7 @@ var claudeProvider_exports = {};
43444
44177
  __export(claudeProvider_exports, {
43445
44178
  createLocalCliProvider: () => createLocalCliProvider
43446
44179
  });
43447
- import { spawn as spawn11 } from "node:child_process";
44180
+ import { spawn as spawn12 } from "node:child_process";
43448
44181
  function waitForExit(child, timeoutMs2 = 2e3) {
43449
44182
  return new Promise((resolve3) => {
43450
44183
  if (child.exitCode != null) return resolve3(child.exitCode);
@@ -43473,7 +44206,7 @@ function createLocalCliProvider(opts = {}) {
43473
44206
  );
43474
44207
  }
43475
44208
  }
43476
- const spawnFn = opts.spawnFn ?? spawn11;
44209
+ const spawnFn = opts.spawnFn ?? spawn12;
43477
44210
  let child;
43478
44211
  try {
43479
44212
  child = spawnFn(cli, args, {
@@ -43568,12 +44301,12 @@ var init_claudeProvider = __esm({
43568
44301
  });
43569
44302
 
43570
44303
  // src/cli/workspace/projectInstructions.ts
43571
- import { existsSync as existsSync26, readFileSync as readFileSync23 } from "node:fs";
44304
+ import { existsSync as existsSync27, readFileSync as readFileSync23 } from "node:fs";
43572
44305
  import { join as join20 } from "node:path";
43573
44306
  function loadProjectInstructions(projectRoot = process.cwd(), maxChars = MAX_CHARS) {
43574
44307
  for (const name of CANDIDATES) {
43575
44308
  const full = join20(projectRoot, name);
43576
- if (!existsSync26(full)) continue;
44309
+ if (!existsSync27(full)) continue;
43577
44310
  try {
43578
44311
  let raw = readFileSync23(full, "utf8");
43579
44312
  raw = raw.replace(/\r\n/g, "\n").trim();
@@ -43619,7 +44352,7 @@ __export(workspaceSummary_exports, {
43619
44352
  buildWorkspaceSummary: () => buildWorkspaceSummary,
43620
44353
  buildZelariReadHint: () => buildZelariReadHint
43621
44354
  });
43622
- import { existsSync as existsSync27, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
44355
+ import { existsSync as existsSync28, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
43623
44356
  import { join as join21, relative } from "node:path";
43624
44357
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
43625
44358
  const { maxEntries = 30, maxChars = 3500, maxDeps = 24, maxScripts = 16 } = options;
@@ -43654,7 +44387,7 @@ function formatTaskLine(t) {
43654
44387
  function buildPlanSummary(projectRoot = process.cwd(), options) {
43655
44388
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
43656
44389
  const planPath = join21(zelariRoot, "plan.json");
43657
- if (!existsSync27(planPath)) return null;
44390
+ if (!existsSync28(planPath)) return null;
43658
44391
  let plan;
43659
44392
  try {
43660
44393
  plan = JSON.parse(readFileSync24(planPath, "utf8"));
@@ -43797,7 +44530,7 @@ function pickNextTask(open) {
43797
44530
  }
43798
44531
  function buildZelariReadHint(projectRoot = process.cwd()) {
43799
44532
  const planPath = join21(resolveWorkspaceRoot(projectRoot), "plan.json");
43800
- if (!existsSync27(planPath)) return "";
44533
+ if (!existsSync28(planPath)) return "";
43801
44534
  return [
43802
44535
  "# Council workspace detected (.zelari/) \u2014 DRAFT vault",
43803
44536
  "`.zelari/plan.json` and `.zelari/docs/` hold **design hypotheses**, not verified product state.",
@@ -43813,7 +44546,7 @@ function safeProjectName(root) {
43813
44546
  }
43814
44547
  function readPackageJson(projectRoot) {
43815
44548
  const p3 = join21(projectRoot, "package.json");
43816
- if (!existsSync27(p3)) return null;
44549
+ if (!existsSync28(p3)) return null;
43817
44550
  try {
43818
44551
  return JSON.parse(readFileSync24(p3, "utf8"));
43819
44552
  } catch {
@@ -43917,12 +44650,12 @@ var init_workspaceSummary = __esm({
43917
44650
  });
43918
44651
 
43919
44652
  // src/cli/workspace/buildLessonsSummary.ts
43920
- import { existsSync as existsSync28 } from "node:fs";
44653
+ import { existsSync as existsSync29 } from "node:fs";
43921
44654
  import { join as join22 } from "node:path";
43922
44655
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
43923
44656
  if (process.env["ZELARI_LESSONS"] === "0") return null;
43924
44657
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
43925
- if (!existsSync28(join22(zelariRoot, "lessons.jsonl"))) return null;
44658
+ if (!existsSync29(join22(zelariRoot, "lessons.jsonl"))) return null;
43926
44659
  const lessons = recallLessons(zelariRoot, {
43927
44660
  maxLessons: 5,
43928
44661
  maxBytes: 2048,
@@ -43943,7 +44676,7 @@ var composeContext_exports = {};
43943
44676
  __export(composeContext_exports, {
43944
44677
  composeProjectContext: () => composeProjectContext
43945
44678
  });
43946
- import { existsSync as existsSync29, readdirSync as readdirSync7, readFileSync as readFileSync25 } from "node:fs";
44679
+ import { existsSync as existsSync30, readdirSync as readdirSync7, readFileSync as readFileSync25 } from "node:fs";
43947
44680
  import { join as join23 } from "node:path";
43948
44681
  function cap2(text, max, label) {
43949
44682
  if (!text || text.length <= max) return { text: text || "", truncated: false };
@@ -43956,13 +44689,13 @@ function cap2(text, max, label) {
43956
44689
  }
43957
44690
  function buildDesignIndex(projectRoot, maxChars) {
43958
44691
  const root = resolveWorkspaceRoot(projectRoot);
43959
- if (!existsSync29(root)) return "";
44692
+ if (!existsSync30(root)) return "";
43960
44693
  const lines = [
43961
44694
  "# Design vault index (.zelari/) \u2014 HYPOTHESES only",
43962
44695
  "Full design docs are NOT product source of truth. Open with list_files / read_file / searchDocuments if needed."
43963
44696
  ];
43964
44697
  const docsDir = join23(root, "docs");
43965
- if (existsSync29(docsDir)) {
44698
+ if (existsSync30(docsDir)) {
43966
44699
  try {
43967
44700
  const docs = readdirSync7(docsDir).filter((n) => n.endsWith(".md")).slice(0, 12);
43968
44701
  if (docs.length > 0) {
@@ -43976,12 +44709,12 @@ function buildDesignIndex(projectRoot, maxChars) {
43976
44709
  }
43977
44710
  }
43978
44711
  for (const name of ["risks.md", "plan.json", "nfr-spec.json"]) {
43979
- if (existsSync29(join23(root, name))) {
44712
+ if (existsSync30(join23(root, name))) {
43980
44713
  lines.push(`- .zelari/${name} present`);
43981
44714
  }
43982
44715
  }
43983
44716
  const decisionsDir = join23(root, "decisions");
43984
- if (existsSync29(decisionsDir)) {
44717
+ if (existsSync30(decisionsDir)) {
43985
44718
  try {
43986
44719
  const n = readdirSync7(decisionsDir).filter((f) => f.endsWith(".md")).length;
43987
44720
  if (n > 0) lines.push(`- .zelari/decisions/ (${n} ADR file(s) \u2014 treat proposed as non-binding)`);
@@ -44083,15 +44816,15 @@ function composeProjectContext(input) {
44083
44816
  function readDurableHeadSync(projectRoot) {
44084
44817
  try {
44085
44818
  const headPath = join23(projectRoot, ".zelari", "state", "HEAD.json");
44086
- if (!existsSync29(headPath)) return "";
44819
+ if (!existsSync30(headPath)) return "";
44087
44820
  const head = JSON.parse(readFileSync25(headPath, "utf8"));
44088
44821
  if (!head?.id) return "";
44089
44822
  const metaPath = join23(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
44090
- if (!existsSync29(metaPath)) return "";
44823
+ if (!existsSync30(metaPath)) return "";
44091
44824
  const meta3 = JSON.parse(readFileSync25(metaPath, "utf8"));
44092
44825
  const discPath = meta3.artifactDir ? join23(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join23(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
44093
44826
  let discoveries = [];
44094
- if (existsSync29(discPath)) {
44827
+ if (existsSync30(discPath)) {
44095
44828
  discoveries = JSON.parse(readFileSync25(discPath, "utf8"));
44096
44829
  }
44097
44830
  const reusable = discoveries.filter((d) => d.reusable !== false);
@@ -44125,11 +44858,11 @@ var planDetect_exports = {};
44125
44858
  __export(planDetect_exports, {
44126
44859
  hasWorkspacePlan: () => hasWorkspacePlan
44127
44860
  });
44128
- import { existsSync as existsSync30, readFileSync as readFileSync26 } from "node:fs";
44861
+ import { existsSync as existsSync31, readFileSync as readFileSync26 } from "node:fs";
44129
44862
  import { join as join24 } from "node:path";
44130
44863
  function hasWorkspacePlan(projectRoot = process.cwd()) {
44131
44864
  const planPath = join24(resolveWorkspaceRoot(projectRoot), "plan.json");
44132
- if (!existsSync30(planPath)) return false;
44865
+ if (!existsSync31(planPath)) return false;
44133
44866
  try {
44134
44867
  const parsed = JSON.parse(readFileSync26(planPath, "utf8"));
44135
44868
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
@@ -44190,7 +44923,7 @@ __export(stubs_exports, {
44190
44923
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
44191
44924
  });
44192
44925
  import {
44193
- existsSync as existsSync31,
44926
+ existsSync as existsSync32,
44194
44927
  readdirSync as readdirSync8,
44195
44928
  writeFileSync as writeFileSync17,
44196
44929
  readFileSync as readFileSync27,
@@ -44211,7 +44944,7 @@ function planJsonPath(ctx) {
44211
44944
  }
44212
44945
  function readPlan(ctx) {
44213
44946
  const jsonPath = planJsonPath(ctx);
44214
- if (existsSync31(jsonPath)) {
44947
+ if (existsSync32(jsonPath)) {
44215
44948
  try {
44216
44949
  const parsed = JSON.parse(
44217
44950
  readFileSync27(jsonPath, "utf8")
@@ -44321,7 +45054,7 @@ function renderPlanBody(summary) {
44321
45054
  }
44322
45055
  function nextAdrId(ctx) {
44323
45056
  const decisionsDir = join25(ctx.rootDir, "decisions");
44324
- if (!existsSync31(decisionsDir)) return "001";
45057
+ if (!existsSync32(decisionsDir)) return "001";
44325
45058
  const existing = readdirSync8(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
44326
45059
  const max = existing.length === 0 ? 0 : Math.max(...existing);
44327
45060
  return String(max + 1).padStart(3, "0");
@@ -44777,7 +45510,7 @@ function searchDocumentsStub(ctx) {
44777
45510
  ];
44778
45511
  const results = [];
44779
45512
  for (const file2 of files) {
44780
- if (!existsSync31(file2)) continue;
45513
+ if (!existsSync32(file2)) continue;
44781
45514
  const raw = readFileSync27(file2, "utf8");
44782
45515
  const content = raw.toLowerCase();
44783
45516
  let idx = -1;
@@ -44945,176 +45678,6 @@ var init_toolRegistry2 = __esm({
44945
45678
  }
44946
45679
  });
44947
45680
 
44948
- // src/cli/updater.ts
44949
- var updater_exports = {};
44950
- __export(updater_exports, {
44951
- REGISTRY_URL: () => REGISTRY_URL,
44952
- checkForUpdate: () => checkForUpdate,
44953
- compareSemver: () => compareSemver,
44954
- distTagForVersion: () => distTagForVersion,
44955
- fetchLatestVersion: () => fetchLatestVersion,
44956
- getCurrentVersion: () => getCurrentVersion,
44957
- looksLikeBrokenShim: () => looksLikeBrokenShim,
44958
- performUpdate: () => performUpdate,
44959
- registryUrlForTag: () => registryUrlForTag,
44960
- resolveBundledNpmCli: () => resolveBundledNpmCli
44961
- });
44962
- import { createRequire as createRequire2 } from "node:module";
44963
- import { spawn as spawn12 } from "node:child_process";
44964
- import { existsSync as existsSync32 } from "node:fs";
44965
- import path43 from "node:path";
44966
- import { fileURLToPath } from "node:url";
44967
- function resolveBundledNpmCli(execPath = process.execPath) {
44968
- const dir = path43.dirname(execPath);
44969
- const candidates = [
44970
- // Windows: C:\...\node.exe → C:\...\node_modules\npm\bin\npm-cli.js
44971
- path43.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
44972
- // POSIX: <prefix>/bin/node → <prefix>/lib/node_modules/npm/bin/npm-cli.js
44973
- path43.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
44974
- ];
44975
- for (const candidate of candidates) {
44976
- try {
44977
- if (existsSync32(candidate)) return candidate;
44978
- } catch {
44979
- }
44980
- }
44981
- return null;
44982
- }
44983
- function looksLikeBrokenShim(exitCode, output) {
44984
- if (exitCode === 127) return true;
44985
- const h = output.toLowerCase();
44986
- return h.includes("shim target not found") || h.includes("is not recognized");
44987
- }
44988
- function getCurrentVersion() {
44989
- try {
44990
- const pkgPath = path43.resolve(__dirname2, "..", "..", "package.json");
44991
- const pkg = require2(pkgPath);
44992
- return pkg.version;
44993
- } catch {
44994
- return "0.0.0";
44995
- }
44996
- }
44997
- function compareSemver(a, b) {
44998
- const parse3 = (v) => {
44999
- const m = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
45000
- if (!m) return [0, 0, 0, null];
45001
- return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] ?? null];
45002
- };
45003
- const [a1, a2, a3, aPre] = parse3(a);
45004
- const [b1, b2, b3, bPre] = parse3(b);
45005
- if (a1 !== b1) return a1 < b1 ? -1 : 1;
45006
- if (a2 !== b2) return a2 < b2 ? -1 : 1;
45007
- if (a3 !== b3) return a3 < b3 ? -1 : 1;
45008
- if (aPre === bPre) return 0;
45009
- if (aPre === null) return 1;
45010
- if (bPre === null) return -1;
45011
- return aPre < bPre ? -1 : 1;
45012
- }
45013
- function distTagForVersion(version2) {
45014
- if (version2.includes("-alpha.")) return "alpha";
45015
- if (version2.includes("-beta.")) return "beta";
45016
- if (version2.includes("-next.")) return "next";
45017
- return "latest";
45018
- }
45019
- function registryUrlForTag(tag = distTagForVersion(getCurrentVersion())) {
45020
- return `https://registry.npmjs.org/zelari-code/${tag}`;
45021
- }
45022
- async function fetchLatestVersion(fetcher = fetch, registryUrl = REGISTRY_URL, timeoutMs2 = 5e3) {
45023
- try {
45024
- const controller = new AbortController();
45025
- const timer = setTimeout(() => controller.abort(), timeoutMs2);
45026
- const response = await fetcher(registryUrl, { signal: controller.signal });
45027
- clearTimeout(timer);
45028
- if (!response.ok) {
45029
- return { error: `Registry responded ${response.status}` };
45030
- }
45031
- const data = await response.json();
45032
- if (!data.version || typeof data.version !== "string") {
45033
- return { error: "Registry response missing version field" };
45034
- }
45035
- return { version: data.version };
45036
- } catch (err) {
45037
- const message = err instanceof Error ? err.message : String(err);
45038
- return { error: message };
45039
- }
45040
- }
45041
- async function checkForUpdate(fetcher = fetch, registryUrl) {
45042
- const currentVersion = getCurrentVersion();
45043
- const url2 = registryUrl ?? registryUrlForTag();
45044
- const latest = await fetchLatestVersion(fetcher, url2);
45045
- if ("error" in latest) {
45046
- return {
45047
- currentVersion,
45048
- latestVersion: currentVersion,
45049
- updateAvailable: false,
45050
- error: latest.error
45051
- };
45052
- }
45053
- const cmp = compareSemver(currentVersion, latest.version);
45054
- return {
45055
- currentVersion,
45056
- latestVersion: latest.version,
45057
- updateAvailable: cmp < 0
45058
- };
45059
- }
45060
- async function performUpdate(packageName = "zelari-code", executor = spawn12, resolveNpmCli = resolveBundledNpmCli, channel) {
45061
- const tag = channel ?? distTagForVersion(getCurrentVersion());
45062
- const args = ["install", "-g", `${packageName}@${tag}`];
45063
- const primary = await runNpm(executor, args, "shim");
45064
- if (primary.ok) return primary;
45065
- const npmCli = resolveNpmCli();
45066
- if (npmCli && looksLikeBrokenShim(primary.exitCode, primary.output)) {
45067
- const fallback = await runNpm(executor, args, "bundled", npmCli);
45068
- return {
45069
- ...fallback,
45070
- output: `[update] npm shim failed (${primary.error ?? "exit " + primary.exitCode}); retried via bundled npm (${npmCli}).
45071
- ${fallback.output}`
45072
- };
45073
- }
45074
- return primary;
45075
- }
45076
- function runNpm(executor, args, mode, npmCliPath) {
45077
- return new Promise((resolve3) => {
45078
- let stdout = "";
45079
- let stderr = "";
45080
- const stdio = ["ignore", "pipe", "pipe"];
45081
- const child = mode === "bundled" && npmCliPath ? executor(process.execPath, [npmCliPath, ...args], { stdio }) : process.platform === "win32" ? executor(buildCmdLine("npm", args), { stdio, shell: true }) : executor("npm", args, { stdio });
45082
- child.stdout?.on("data", (chunk) => {
45083
- stdout += chunk.toString();
45084
- });
45085
- child.stderr?.on("data", (chunk) => {
45086
- stderr += chunk.toString();
45087
- });
45088
- child.on("error", (err) => {
45089
- resolve3({
45090
- ok: false,
45091
- output: stdout + stderr,
45092
- error: err.message,
45093
- exitCode: null
45094
- });
45095
- });
45096
- child.on("close", (code) => {
45097
- const ok = code === 0;
45098
- resolve3({
45099
- ok,
45100
- output: stdout + stderr,
45101
- error: ok ? void 0 : `npm exited with code ${code}`,
45102
- exitCode: code
45103
- });
45104
- });
45105
- });
45106
- }
45107
- var require2, __dirname2, REGISTRY_URL;
45108
- var init_updater = __esm({
45109
- "src/cli/updater.ts"() {
45110
- "use strict";
45111
- init_cmdline();
45112
- require2 = createRequire2(import.meta.url);
45113
- __dirname2 = path43.dirname(fileURLToPath(import.meta.url));
45114
- REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
45115
- }
45116
- });
45117
-
45118
45681
  // src/cli/mcp/mcpClient.ts
45119
45682
  import { spawn as spawn13 } from "node:child_process";
45120
45683
  var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
@@ -45784,7 +46347,7 @@ __export(agentsMd_exports, {
45784
46347
  updateAgentsMd: () => updateAgentsMd
45785
46348
  });
45786
46349
  import { existsSync as existsSync35, readFileSync as readFileSync30, writeFileSync as writeFileSync19 } from "node:fs";
45787
- import { createHash as createHash13 } from "node:crypto";
46350
+ import { createHash as createHash14 } from "node:crypto";
45788
46351
  import { join as join28 } from "node:path";
45789
46352
  import { readFile as readFile4 } from "node:fs/promises";
45790
46353
  async function readPackageJson2(projectRoot) {
@@ -45999,7 +46562,7 @@ async function updateAgentsMd(ctx, projectRoot) {
45999
46562
  return { changed: true, sections: changedSections };
46000
46563
  }
46001
46564
  function hash2(s) {
46002
- return createHash13("sha256").update(s).digest("hex").slice(0, 16);
46565
+ return createHash14("sha256").update(s).digest("hex").slice(0, 16);
46003
46566
  }
46004
46567
  var AUTO_SECTIONS, MARKER_OPEN, MARKER_CLOSE, GENERATORS;
46005
46568
  var init_agentsMd = __esm({
@@ -51691,8 +52254,8 @@ function shouldRunGauntletHostLoop(opts) {
51691
52254
  }
51692
52255
  function budgetAwareGauntletGate(input) {
51693
52256
  if (input.verdict === "PASS") return "proceed";
51694
- if (input.toolCallsRemaining <= input.verificationReserve) return "finalize-verify";
51695
52257
  if (input.toolCallsRemaining <= 0) return "hold";
52258
+ if (input.toolCallsRemaining <= input.verificationReserve) return "finalize-verify";
51696
52259
  return "proceed";
51697
52260
  }
51698
52261
  var DEFAULT_MAX_PIECES, DEFAULT_MAX_ROUNDS, DEFAULT_MAX_PARALLEL2, DEFAULT_WALL_MS, GAUNTLET_PARENT_BLOCKED_TOOLS;
@@ -52162,7 +52725,8 @@ var init_schedule = __esm({
52162
52725
 
52163
52726
  // src/cli/gauntlet/loop.ts
52164
52727
  async function runGauntletLoop(args) {
52165
- const { caps, deps } = args;
52728
+ const { caps } = args;
52729
+ const deps = { ...args.deps, budgetGate: args.budgetGate ?? args.deps.budgetGate };
52166
52730
  const pieces = args.pieces.slice(0, caps.maxPieces);
52167
52731
  const started = (deps.now ?? Date.now)();
52168
52732
  const results = [];
@@ -52229,6 +52793,21 @@ async function runGauntletLoop(args) {
52229
52793
  builderError: built.error
52230
52794
  });
52231
52795
  winner = parseBlindWinner(criticized.result) ?? winner;
52796
+ if (last.kind !== "PASS") {
52797
+ const b = deps.budgetGate?.() ?? null;
52798
+ if (b) {
52799
+ const decision = budgetAwareGauntletGate({
52800
+ verdict: last.kind,
52801
+ toolCallsRemaining: b.remaining,
52802
+ verificationReserve: b.verificationReserve
52803
+ });
52804
+ if (decision === "hold") {
52805
+ gap = last.gap;
52806
+ break;
52807
+ }
52808
+ if (decision === "finalize-verify") break;
52809
+ }
52810
+ }
52232
52811
  emitProgress({
52233
52812
  phase: last.kind === "PASS" ? "settled" : last.kind === "BLOCKED" ? "blocked" : "repairing",
52234
52813
  pieceId: piece.id,
@@ -52292,6 +52871,7 @@ var init_loop = __esm({
52292
52871
  init_blind();
52293
52872
  init_policy();
52294
52873
  init_verdict2();
52874
+ init_policy();
52295
52875
  init_prompts();
52296
52876
  init_schedule();
52297
52877
  init_events3();
@@ -52395,7 +52975,12 @@ async function runHeadlessGauntlet(opts, provider, model) {
52395
52975
  type: "log",
52396
52976
  message: `[gauntlet] ${decomposed.pieces.length} piece(s) from ${decomposed.source}${decomposed.error ? ` (${decomposed.error.slice(0, 80)})` : ""}`
52397
52977
  });
52978
+ const budgetGate = () => {
52979
+ const lim = spine.resourceBudgetLimit();
52980
+ return lim ? { remaining: lim.remaining, verificationReserve: lim.verificationReserve } : null;
52981
+ };
52398
52982
  const result = await runGauntletLoop({
52983
+ budgetGate,
52399
52984
  pieces: decomposed.pieces,
52400
52985
  caps,
52401
52986
  deps: {
@@ -53470,7 +54055,7 @@ import {
53470
54055
  } from "node:fs";
53471
54056
  import { join as join38 } from "node:path";
53472
54057
  import { homedir as homedir13 } from "node:os";
53473
- import { createHash as createHash14, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
54058
+ import { createHash as createHash15, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
53474
54059
  function getZelariHome() {
53475
54060
  return join38(homedir13(), ".zelari-code");
53476
54061
  }
@@ -53546,8 +54131,8 @@ function loadOrCreateToken(explicit) {
53546
54131
  }
53547
54132
  function tokenMatches(expected, provided) {
53548
54133
  if (!provided) return false;
53549
- const a = createHash14("sha256").update(expected).digest();
53550
- const b = createHash14("sha256").update(provided).digest();
54134
+ const a = createHash15("sha256").update(expected).digest();
54135
+ const b = createHash15("sha256").update(provided).digest();
53551
54136
  try {
53552
54137
  return timingSafeEqual(a, b);
53553
54138
  } catch {
@@ -58143,13 +58728,13 @@ init_completionGate();
58143
58728
  // src/cli/kraken/verificationBridge.ts
58144
58729
  init_candidateRegistry();
58145
58730
  init_completionGate();
58146
- import { createHash as createHash11 } from "node:crypto";
58731
+ import { createHash as createHash12 } from "node:crypto";
58147
58732
 
58148
58733
  // src/cli/kraken/nativeVerification.ts
58149
58734
  init_runtime2();
58150
58735
  init_verification2();
58151
58736
  import { readFile as readFile2 } from "node:fs/promises";
58152
- import path40 from "node:path";
58737
+ import path42 from "node:path";
58153
58738
  function nativePackEnabled(env = process.env) {
58154
58739
  const v = env.ZELARI_VERIFY_PACK?.toLowerCase();
58155
58740
  return v === "1" || v === "on" || v === "true";
@@ -58176,7 +58761,7 @@ function packTimeoutMs(env = process.env) {
58176
58761
  }
58177
58762
  async function readPackageScripts(cwd = process.cwd()) {
58178
58763
  try {
58179
- const raw = await readFile2(path40.join(cwd, "package.json"), "utf-8");
58764
+ const raw = await readFile2(path42.join(cwd, "package.json"), "utf-8");
58180
58765
  const parsed = JSON.parse(raw);
58181
58766
  if (parsed && typeof parsed === "object" && typeof parsed.scripts === "object") {
58182
58767
  return parsed.scripts;
@@ -58270,7 +58855,7 @@ function krakenResultsToContract(requiredChecks, results, now = Date.now()) {
58270
58855
  return { criteria, results: verifications };
58271
58856
  }
58272
58857
  function sha256Hex2(input) {
58273
- return createHash11("sha256").update(input).digest("hex");
58858
+ return createHash12("sha256").update(input).digest("hex");
58274
58859
  }
58275
58860
  function matchNoteToToolTrace(note, trace) {
58276
58861
  const n = normalize5(note);
@@ -58991,7 +59576,9 @@ async function buildModelContext(input) {
58991
59576
  };
58992
59577
  input.onCompactionMetric?.(compactionMetrics);
58993
59578
  }
58994
- if (input.resourceSnapshot) {
59579
+ if (input.resourceSnapshot && !history2.some(
59580
+ (m) => m.role === "system" && typeof m.content === "string" && m.content.startsWith("RESOURCE STATUS")
59581
+ )) {
58995
59582
  history2 = [...history2, resourceStatusMessage(input.resourceSnapshot)];
58996
59583
  }
58997
59584
  if (requestSurface) {
@@ -59195,6 +59782,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
59195
59782
  }
59196
59783
  const cwd = process.cwd();
59197
59784
  const requestSnapshot = getRequestSnapshotWithUsage(sessionId2);
59785
+ await writerRef.current?.spine?.beginResourceTurn();
59198
59786
  const modelContext = await buildModelContext({
59199
59787
  fallbackHistory: historyForModel,
59200
59788
  session: writerRef.current?.spine ?? null,
@@ -59393,10 +59981,12 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
59393
59981
  systemMessages = [{ role: "system", content: fallback }];
59394
59982
  }
59395
59983
  systemPrefixLen = systemMessages.length;
59396
- const maxToolCallsPerTurn = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
59984
+ const perTurnEnv = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
59397
59985
  default: 25,
59398
59986
  min: 1
59399
59987
  });
59988
+ const sessionCap = writerRef.current?.spine?.resourceBudgetLimit();
59989
+ const maxToolCallsPerTurn = sessionCap ? Math.max(1, Math.min(perTurnEnv, sessionCap.maxToolCalls)) : perTurnEnv;
59400
59990
  const maxToolLoopIterations = budget.maxToolLoopIterations;
59401
59991
  const maxToolLoopHardCap = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_HARD, {
59402
59992
  default: 0,
@@ -59426,7 +60016,9 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
59426
60016
  providerStream,
59427
60017
  // 2.6 Phase 3: host-owned pre-dispatch resource gate via the spine
59428
60018
  // mirror (doc section 11.3). Degrade-and-stop (null gate = allow).
59429
- toolCallGate: (name) => writerRef.current?.spine?.gateResourceToolCall(name) ?? { allowed: true },
60019
+ // 2.6.1 (plan §13): argument-aware bash is essential only when
60020
+ // the command is a test/typecheck/build/git-diff line.
60021
+ toolCallGate: (name, args) => writerRef.current?.spine?.gateResourceToolCall(name, args) ?? { allowed: true },
59430
60022
  cwd,
59431
60023
  maxToolCallsPerTurn,
59432
60024
  maxToolLoopIterations,
@@ -59874,6 +60466,7 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
59874
60466
  setBusy(true);
59875
60467
  const anchored = maybeAnchorShortAnswer(text);
59876
60468
  const effectiveText = anchored ?? text;
60469
+ await writerRef.current?.spine?.beginResourceTurn();
59877
60470
  const councilContext = await buildModelContext({
59878
60471
  fallbackHistory: getHistory(),
59879
60472
  session: writerRef.current?.spine ?? null,
@@ -60007,10 +60600,12 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
60007
60600
  let streamContent = "";
60008
60601
  let streamMemberId = null;
60009
60602
  const streamScrub = createStreamScrubber(16);
60010
- const councilMaxToolCalls = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
60603
+ const councilPerTurnEnv = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
60011
60604
  default: 15,
60012
60605
  min: 1
60013
60606
  });
60607
+ const councilSessionCap = writerRef.current?.spine?.resourceBudgetLimit();
60608
+ const councilMaxToolCalls = councilSessionCap ? Math.max(1, Math.min(councilPerTurnEnv, councilSessionCap.maxToolCalls)) : councilPerTurnEnv;
60014
60609
  const councilMaxToolLoop = envNumber(
60015
60610
  process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS,
60016
60611
  { default: 30, min: 1 }
@@ -65397,6 +65992,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
65397
65992
  }
65398
65993
  ];
65399
65994
  }
65995
+ await spine.beginResourceTurn();
65400
65996
  const modelContext = await buildModelContext({
65401
65997
  fallbackHistory: seededHistory.history,
65402
65998
  session: spine.spine,
@@ -65443,7 +66039,9 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
65443
66039
  // 2.6 Phase 3: host-owned pre-dispatch resource gate (doc section 11.3).
65444
66040
  // Advisory by default; ZELARI_RESOURCE_ENFORCEMENT=protected enables the
65445
66041
  // protected verification reserve. Degrade-and-stop (null gate = allow).
65446
- toolCallGate: (name) => spine.gateResourceToolCall(name) ?? { allowed: true },
66042
+ // 2.6.1 (plan §13): argument-aware bash is essential only when the
66043
+ // command is a test/typecheck/build/git-diff line.
66044
+ toolCallGate: (name, args) => spine.gateResourceToolCall(name, args) ?? { allowed: true },
65447
66045
  maxToolLoopIterations: maxToolLoop
65448
66046
  });
65449
66047
  let finalReason = "completed";
@@ -65755,6 +66353,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
65755
66353
  description: tool.function.description,
65756
66354
  parameters: tool.function.parameters
65757
66355
  }));
66356
+ await spine.beginResourceTurn();
65758
66357
  const councilContext = await buildModelContext({
65759
66358
  fallbackHistory: seededHistory.history,
65760
66359
  session: spine.spine,
@@ -65925,6 +66524,7 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
65925
66524
  description: tool.function.description,
65926
66525
  parameters: tool.function.parameters
65927
66526
  }));
66527
+ await spine.beginResourceTurn();
65928
66528
  const missionContext = await buildModelContext({
65929
66529
  fallbackHistory: seededHistory.history,
65930
66530
  session: spine.spine,
@@ -66094,6 +66694,28 @@ ${ragContext}` : slicePrompt;
66094
66694
  if (completionOk) {
66095
66695
  emit(`[zelari] slice completion ok`);
66096
66696
  }
66697
+ try {
66698
+ const budget = spine.resourceBudgetSummary();
66699
+ if (budget && !completionOk) {
66700
+ const { evaluateResourceReserveGate: evaluateResourceReserveGate2 } = await Promise.resolve().then(() => (init_verification2(), verification_exports));
66701
+ const gated = evaluateResourceReserveGate2({
66702
+ evaluation: {
66703
+ verdict: "REPAIR_REQUIRED",
66704
+ summary: "headless slice completion",
66705
+ satisfied: [],
66706
+ unsatisfied: [],
66707
+ evidenceComplete: false,
66708
+ eventBackedEvidenceComplete: false
66709
+ },
66710
+ budget
66711
+ });
66712
+ if (gated.verdict === "BLOCKED") {
66713
+ emit("[zelari] completion BLOCKED: resource budget exhausted (non-PASS + zero remaining)");
66714
+ }
66715
+ spine.note("completion.resource_gate", { decision: gated.verdict, remaining: budget.toolCalls.remaining });
66716
+ }
66717
+ } catch {
66718
+ }
66097
66719
  } catch {
66098
66720
  }
66099
66721
  if (synthesisText.trim()) {