zelari-code 2.6.0 → 2.6.1

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",
@@ -28373,6 +28376,8 @@ var init_types8 = __esm({
28373
28376
  "resource.snapshot",
28374
28377
  "resource.limit_reached",
28375
28378
  "resource.reserve_entered",
28379
+ // 2.6.1 (closure plan §9): hard-limit overrun telemetry — state-only.
28380
+ "resource.overrun",
28376
28381
  "note"
28377
28382
  ];
28378
28383
  SessionEventEnvelopeSchema = external_exports.object({
@@ -28703,11 +28708,15 @@ function asNumber(value) {
28703
28708
  function formatResourceSnapshot(data) {
28704
28709
  const used = asNumber(data.toolCallsUsed) ?? 0;
28705
28710
  const remaining = asNumber(data.toolCallsRemaining) ?? 0;
28711
+ const limit = asNumber(data.toolCallsLimit) ?? used + remaining;
28706
28712
  const lines = [
28707
28713
  "RESOURCE STATUS",
28708
- `Tool calls: ${used} / ${used + remaining}`,
28714
+ `Tool calls: ${used} / ${limit}`,
28709
28715
  `Remaining: ${remaining}`
28710
28716
  ];
28717
+ const overrun = asNumber(data.overrun);
28718
+ if (overrun !== void 0 && overrun > 0)
28719
+ lines.push(`Overrun: ${overrun}`);
28711
28720
  const wall = asNumber(data.wallMsRemaining);
28712
28721
  if (wall !== void 0)
28713
28722
  lines.push(`Wall clock remaining: ${Math.max(0, Math.round(wall / 1e3))}s`);
@@ -29608,8 +29617,14 @@ function validateResourceAndContractEvents(events) {
29608
29617
  }
29609
29618
  lastToolCallsUsed = used;
29610
29619
  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})` });
29620
+ if (remaining !== Math.max(0, limit - used)) {
29621
+ violations.push({ code: "RESOURCE_REMAINING_COHERENT", seq: e.seq, message: `remaining(${remaining}) != max(0, limit(${limit}) - used(${used}))` });
29622
+ }
29623
+ const overrun = e.data.overrun;
29624
+ if (overrun !== void 0) {
29625
+ if (typeof overrun !== "number" || overrun < 0 || overrun !== Math.max(0, used - limit)) {
29626
+ violations.push({ code: "RESOURCE_OVERRUN_COHERENT", seq: e.seq, message: `overrun(${String(overrun)}) != max(0, used(${used}) - limit(${limit}))` });
29627
+ }
29613
29628
  }
29614
29629
  for (const key of ["verificationReserve", "repairReserve"]) {
29615
29630
  const v = e.data[key];
@@ -29706,27 +29721,53 @@ function contractToCompactionFields(contract) {
29706
29721
  function deriveInitialContract(userSeq, text) {
29707
29722
  const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
29708
29723
  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, ""));
29724
+ const acceptanceCriteria = [];
29725
+ const criterionLines = /* @__PURE__ */ new Set();
29726
+ stripped.forEach((line, i) => {
29727
+ const checkbox = CHECKBOX_CRITERION.exec(line);
29728
+ if (checkbox) {
29729
+ criterionLines.add(i);
29730
+ acceptanceCriteria.push({
29731
+ id: `ac-${acceptanceCriteria.length + 1}`,
29732
+ text: checkbox[2].trim(),
29733
+ source: "user",
29734
+ required: true
29735
+ });
29736
+ return;
29737
+ }
29738
+ const keyword = KEYWORD_CRITERION.exec(line);
29739
+ if (keyword) {
29740
+ criterionLines.add(i);
29741
+ const value = keyword[2].trim();
29742
+ const lead = keyword[1].toLowerCase();
29743
+ const hint = (lead === "verify" || lead === "test") && COMMAND_HINT.test(value) ? { kind: "command", value } : void 0;
29744
+ acceptanceCriteria.push({
29745
+ id: `ac-${acceptanceCriteria.length + 1}`,
29746
+ text: value,
29747
+ source: "user",
29748
+ required: true,
29749
+ ...hint ? { verificationHint: hint } : {}
29750
+ });
29751
+ }
29752
+ });
29753
+ const isConstraint = (i) => !criterionLines.has(i) && CONSTRAINT_LEAD.test(stripped[i] ?? "");
29754
+ const constraints = stripped.map((text2, i) => ({ text: text2, i })).filter(({ i }) => isConstraint(i)).map(({ text: text2 }, i) => ({
29755
+ id: `uc-${i + 1}`,
29756
+ text: text2,
29757
+ source: "user",
29758
+ required: true
29759
+ }));
29760
+ const goalIdx = lines.findIndex((_, i) => !criterionLines.has(i) && !isConstraint(i));
29761
+ const goal = goalIdx >= 0 ? lines[goalIdx] : lines[0] ?? text.slice(0, 200);
29711
29762
  return TaskContractSchema.parse({
29712
29763
  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
- })),
29764
+ goal,
29765
+ constraints,
29766
+ acceptanceCriteria,
29726
29767
  source: { userSeq }
29727
29768
  });
29728
29769
  }
29729
- var TaskConstraintSchema, TaskCriterionSchema, TaskContractSchema, TaskContractConflictError;
29770
+ var TaskConstraintSchema, TaskCriterionSchema, TaskContractSchema, TaskContractConflictError, CHECKBOX_CRITERION, KEYWORD_CRITERION, COMMAND_HINT, CONSTRAINT_LEAD;
29730
29771
  var init_taskContract = __esm({
29731
29772
  "packages/core/dist/session/taskContract.js"() {
29732
29773
  "use strict";
@@ -29766,6 +29807,10 @@ var init_taskContract = __esm({
29766
29807
  this.name = "TaskContractConflictError";
29767
29808
  }
29768
29809
  };
29810
+ CHECKBOX_CRITERION = /^\[([ xX])\]\s*(.+)$/;
29811
+ KEYWORD_CRITERION = /^(acceptance|criterion|criteria|verify|test|success)\s*[:#]\s*(.+)$/i;
29812
+ COMMAND_HINT = /^(npm|pnpm|yarn|bun|npx|node|vitest|jest|tsc|eslint|prettier|git)\b/;
29813
+ CONSTRAINT_LEAD = /^(do not|don't|never|no\s|non\s|without changing|keep|must not|avoid)\b/i;
29769
29814
  }
29770
29815
  });
29771
29816
 
@@ -30233,9 +30278,13 @@ function classifyHarnessChanges(diff) {
30233
30278
  const hit = FIELD_CLASSES.find((m) => field === m.prefix || field.startsWith(m.prefix + "."));
30234
30279
  byField[field] = hit ? hit.cls : "cosmetic";
30235
30280
  }
30281
+ const changeSet = { structural: [], behavioral: [], cosmetic: [] };
30282
+ for (const field of diff.changed) {
30283
+ changeSet[byField[field]].push(field);
30284
+ }
30236
30285
  const order = { behavioral: 3, structural: 2, cosmetic: 1 };
30237
30286
  const overall = Object.values(byField).reduce((acc, cls) => order[cls] > order[acc] ? cls : acc, "cosmetic");
30238
- return { overall, byField };
30287
+ return { overall, byField, changeSet };
30239
30288
  }
30240
30289
  var HARNESS_MANIFEST_SCHEMA_VERSION, HarnessPromptsSchema, HarnessManifestV1Schema, FIELD_CLASSES;
30241
30290
  var init_harnessManifest = __esm({
@@ -30446,12 +30495,14 @@ var init_resourcePolicy = __esm({
30446
30495
 
30447
30496
  // packages/core/dist/runtime/resourceBudget.js
30448
30497
  function computeBudget(policy, usage, stage = "explore") {
30449
- const used = Math.max(0, Math.min(usage.toolCallsUsed, policy.maxToolCalls));
30498
+ const used = Math.max(0, usage.toolCallsUsed);
30499
+ const overrun = Math.max(0, used - policy.maxToolCalls);
30450
30500
  return {
30451
30501
  toolCalls: {
30452
30502
  limit: policy.maxToolCalls,
30453
30503
  used,
30454
- remaining: policy.maxToolCalls - used
30504
+ remaining: Math.max(0, policy.maxToolCalls - used),
30505
+ overrun
30455
30506
  },
30456
30507
  wallTime: {
30457
30508
  limitMs: policy.wallClockMs,
@@ -31465,6 +31516,48 @@ var init_resourceReserveGate = __esm({
31465
31516
  });
31466
31517
 
31467
31518
  // packages/core/dist/verification/index.js
31519
+ var verification_exports = {};
31520
+ __export(verification_exports, {
31521
+ BonConfigSchema: () => BonConfigSchema,
31522
+ CommandCheckSchema: () => CommandCheckSchema,
31523
+ CriterionSchema: () => CriterionSchema,
31524
+ CriterionSourceSchema: () => CriterionSourceSchema,
31525
+ DEFAULT_VERIFIER_CONFIG: () => DEFAULT_VERIFIER_CONFIG,
31526
+ DETERMINISTIC_EVIDENCE_TIERS: () => DETERMINISTIC_EVIDENCE_TIERS,
31527
+ DeterministicCheckSchema: () => DeterministicCheckSchema,
31528
+ EVENT_BACKED_EVIDENCE_TIERS: () => EVENT_BACKED_EVIDENCE_TIERS,
31529
+ EvidenceRefSchema: () => EvidenceRefSchema,
31530
+ EvidenceTierSchema: () => EvidenceTierSchema,
31531
+ FileAbsentCheckSchema: () => FileAbsentCheckSchema,
31532
+ FileContainsCheckSchema: () => FileContainsCheckSchema,
31533
+ FileExistsCheckSchema: () => FileExistsCheckSchema,
31534
+ ModelSelectionSchema: () => ModelSelectionSchema,
31535
+ NoneCheckSchema: () => NoneCheckSchema,
31536
+ STRICT_ALL_POLICY: () => STRICT_ALL_POLICY,
31537
+ STRICT_BUILD_POLICY: () => STRICT_BUILD_POLICY,
31538
+ VerificationEngine: () => VerificationEngine,
31539
+ VerificationResultSchema: () => VerificationResultSchema,
31540
+ VerificationSourceSchema: () => VerificationSourceSchema,
31541
+ VerificationStatusSchema: () => VerificationStatusSchema,
31542
+ VerifierConfigSchema: () => VerifierConfigSchema,
31543
+ VerifierService: () => VerifierService,
31544
+ ZELARI_CODING_PACK_ID: () => ZELARI_CODING_PACK_ID,
31545
+ analyzeScope: () => analyzeScope,
31546
+ codingCriteriaPack: () => codingCriteriaPack,
31547
+ computeFalseDoneRate: () => computeFalseDoneRate,
31548
+ costPerVerifiedSolve: () => costPerVerifiedSolve,
31549
+ evaluateCompletion: () => evaluateCompletion,
31550
+ evaluateResourceReserveGate: () => evaluateResourceReserveGate,
31551
+ isEventBackedEvidence: () => isEventBackedEvidence,
31552
+ isGeneratedPath: () => isGeneratedPath,
31553
+ lastVerificationRun: () => lastVerificationRun,
31554
+ parseNameOnlyDiff: () => parseNameOnlyDiff,
31555
+ parseVerificationRunPayload: () => parseVerificationRunPayload,
31556
+ snapshotToCompletionEvaluation: () => snapshotToCompletionEvaluation,
31557
+ strictBuildGate: () => strictBuildGate,
31558
+ verificationCostRatio: () => verificationCostRatio,
31559
+ verifiedSolveRate: () => verifiedSolveRate
31560
+ });
31468
31561
  var init_verification2 = __esm({
31469
31562
  "packages/core/dist/verification/index.js"() {
31470
31563
  "use strict";
@@ -31621,6 +31714,42 @@ var init_mission2 = __esm({
31621
31714
  }
31622
31715
  });
31623
31716
 
31717
+ // packages/core/dist/version.js
31718
+ var CORE_VERSION;
31719
+ var init_version = __esm({
31720
+ "packages/core/dist/version.js"() {
31721
+ "use strict";
31722
+ CORE_VERSION = "2.6.1";
31723
+ }
31724
+ });
31725
+
31726
+ // packages/core/dist/runtime/fingerprints.js
31727
+ import { createHash as createHash6 } from "node:crypto";
31728
+ function toolFingerprintHash(tools) {
31729
+ const canonical = [...tools].map((t) => ({
31730
+ name: t.name,
31731
+ ...t.description !== void 0 ? { description: t.description } : {},
31732
+ ...t.inputSchema !== void 0 ? { inputSchema: t.inputSchema } : {},
31733
+ ...t.outputContractVersion !== void 0 ? { outputContractVersion: t.outputContractVersion } : {},
31734
+ ...t.capabilityFlags !== void 0 ? { capabilityFlags: [...t.capabilityFlags].sort() } : {}
31735
+ })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
31736
+ return createHash6("sha256").update(stableStringify(canonical)).digest("hex");
31737
+ }
31738
+ function skillFingerprintHash(skills) {
31739
+ const canonical = [...skills].map((s) => ({
31740
+ id: s.id,
31741
+ ...s.version !== void 0 ? { version: s.version } : {},
31742
+ ...s.contentDigest !== void 0 ? { contentDigest: s.contentDigest } : {}
31743
+ })).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
31744
+ return createHash6("sha256").update(stableStringify(canonical)).digest("hex");
31745
+ }
31746
+ var init_fingerprints = __esm({
31747
+ "packages/core/dist/runtime/fingerprints.js"() {
31748
+ "use strict";
31749
+ init_requestSnapshot();
31750
+ }
31751
+ });
31752
+
31624
31753
  // packages/core/dist/index.js
31625
31754
  var dist_exports = {};
31626
31755
  __export(dist_exports, {
@@ -31642,6 +31771,7 @@ __export(dist_exports, {
31642
31771
  CODING_SKILL_CATALOG: () => CODING_SKILL_CATALOG,
31643
31772
  COLLABORATION_DIRECTIVE: () => COLLABORATION_DIRECTIVE,
31644
31773
  COMPOSITOR_ONLY_PROPS: () => COMPOSITOR_ONLY_PROPS,
31774
+ CORE_VERSION: () => CORE_VERSION,
31645
31775
  COUNCIL_V1: () => COUNCIL_V1,
31646
31776
  CommandCheckSchema: () => CommandCheckSchema,
31647
31777
  CriterionSchema: () => CriterionSchema,
@@ -31986,6 +32116,7 @@ __export(dist_exports, {
31986
32116
  shadowedSeqSet: () => shadowedSeqSet,
31987
32117
  shouldRetryMember: () => shouldRetryMember,
31988
32118
  sideEffectForTool: () => sideEffectForTool,
32119
+ skillFingerprintHash: () => skillFingerprintHash,
31989
32120
  slugify: () => slugify2,
31990
32121
  snapshotToCompletionEvaluation: () => snapshotToCompletionEvaluation,
31991
32122
  specificityFromAssumptions: () => specificityFromAssumptions,
@@ -31997,6 +32128,7 @@ __export(dist_exports, {
31997
32128
  taskMatchesNfrKeywords: () => taskMatchesNfrKeywords,
31998
32129
  tierAtLeast: () => tierAtLeast,
31999
32130
  tokenizeForSignature: () => tokenizeForSignature,
32131
+ toolFingerprintHash: () => toolFingerprintHash,
32000
32132
  toolManifestHash: () => toolManifestHash,
32001
32133
  toolMatches: () => toolMatches,
32002
32134
  topoLevels: () => topoLevels,
@@ -32035,6 +32167,8 @@ var init_dist = __esm({
32035
32167
  init_verification2();
32036
32168
  init_mission2();
32037
32169
  init_experimental();
32170
+ init_version();
32171
+ init_fingerprints();
32038
32172
  }
32039
32173
  });
32040
32174
 
@@ -32367,6 +32501,7 @@ function buildResourceSnapshot(budget, policy) {
32367
32501
  toolCallsLimit: budget.toolCalls.limit,
32368
32502
  toolCallsUsed: budget.toolCalls.used,
32369
32503
  toolCallsRemaining: budget.toolCalls.remaining,
32504
+ overrun: budget.toolCalls.overrun,
32370
32505
  ...budget.wallTime.remainingMs !== void 0 ? { wallMsRemaining: budget.wallTime.remainingMs } : {},
32371
32506
  verificationReserve: budget.reserve.verification,
32372
32507
  repairReserve: budget.reserve.repair,
@@ -32394,7 +32529,29 @@ var init_resourceSnapshot = __esm({
32394
32529
  function resolveResourceEnforcement(env = process.env) {
32395
32530
  return env.ZELARI_RESOURCE_ENFORCEMENT === "protected" ? "protected" : "advisory";
32396
32531
  }
32397
- var DEFAULT_ESSENTIAL_TOOLS, ADVISORY_NOTICE, PROTECTED_DENIAL, BudgetRuntime;
32532
+ function bashCommand(args) {
32533
+ if (!args || typeof args !== "object") return "";
32534
+ const rec = args;
32535
+ for (const key of ["command", "cmd", "script"]) {
32536
+ if (typeof rec[key] === "string") return rec[key];
32537
+ }
32538
+ return "";
32539
+ }
32540
+ function isVerificationEssential(toolName, args, _stage = "implement") {
32541
+ if (toolName === "bash") {
32542
+ const cmd = bashCommand(args);
32543
+ if (!cmd) return false;
32544
+ return ESSENTIAL_BASH.some((re) => re.test(cmd));
32545
+ }
32546
+ if (toolName === "grep_content") {
32547
+ if (!args || typeof args !== "object") return true;
32548
+ const rec = args;
32549
+ const hasScope = typeof rec.path === "string" && rec.path !== "." && rec.path !== "./";
32550
+ return hasScope || typeof rec.pattern === "string";
32551
+ }
32552
+ return DEFAULT_ESSENTIAL_TOOLS.includes(toolName);
32553
+ }
32554
+ var DEFAULT_ESSENTIAL_TOOLS, ADVISORY_NOTICE, PROTECTED_DENIAL, HARD_LIMIT_DENIAL, ESSENTIAL_BASH, BudgetRuntime;
32398
32555
  var init_budgetRuntime = __esm({
32399
32556
  "src/cli/budget/budgetRuntime.ts"() {
32400
32557
  "use strict";
@@ -32402,7 +32559,6 @@ var init_budgetRuntime = __esm({
32402
32559
  init_resourceLedger();
32403
32560
  init_resourceSnapshot();
32404
32561
  DEFAULT_ESSENTIAL_TOOLS = [
32405
- "bash",
32406
32562
  "read_file",
32407
32563
  "edit_file",
32408
32564
  "write_file",
@@ -32413,6 +32569,17 @@ var init_budgetRuntime = __esm({
32413
32569
  ];
32414
32570
  ADVISORY_NOTICE = "Resource advisory: verification reserve reached. Prioritize test/typecheck/build/diff and targeted repair; avoid broad exploration or delegation.";
32415
32571
  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.";
32572
+ HARD_LIMIT_DENIAL = "Resource exhausted: the session tool budget (maxToolCalls) is spent. No further billable tool calls are allowed \u2014 summarize what was verified and report BLOCKED/resource-exhausted with the evidence already collected.";
32573
+ ESSENTIAL_BASH = [
32574
+ /\b(npm|pnpm|yarn|bun)\s+(run\s+)?(test|vitest|jest)\b/,
32575
+ /\b(npm|pnpm|yarn|bun)\s+run\s+[\w:-]*(typecheck|lint|build)\b/,
32576
+ /\bnpx\s+(vitest|tsc|typescript|eslint)\b/,
32577
+ /\b(npx\s+)?tsc\b/,
32578
+ /\bvitest\b/,
32579
+ /\bjest\b/,
32580
+ /\bnode\s+--run\b/,
32581
+ /\bgit\s+(diff|status|log|show)\b/
32582
+ ];
32416
32583
  BudgetRuntime = class {
32417
32584
  policy;
32418
32585
  enforcement;
@@ -32420,8 +32587,11 @@ var init_budgetRuntime = __esm({
32420
32587
  essential;
32421
32588
  stage;
32422
32589
  lastEmitted;
32590
+ hardLimitAnnounced = false;
32423
32591
  constructor(profileId, opts = {}) {
32424
- this.policy = opts.policy ?? defaultResourcePolicy(profileId);
32592
+ const basePolicy = opts.policy ?? defaultResourcePolicy(profileId);
32593
+ const envCap = Number.parseInt(process.env.ZELARI_MAX_TOOL_CALLS ?? "", 10);
32594
+ this.policy = Number.isFinite(envCap) && envCap >= 1 ? { ...basePolicy, maxToolCalls: envCap } : basePolicy;
32425
32595
  this.enforcement = opts.enforcement ?? "advisory";
32426
32596
  this.essential = new Set(opts.essentialTools ?? DEFAULT_ESSENTIAL_TOOLS);
32427
32597
  this.stage = opts.stage ?? "implement";
@@ -32432,8 +32602,27 @@ var init_budgetRuntime = __esm({
32432
32602
  * (first sight, stage/pressure change, reserve crossing, any usage delta).
32433
32603
  */
32434
32604
  noteToolCall() {
32605
+ return this.consumeToolCall().snapshot;
32606
+ }
32607
+ /**
32608
+ * 2.6.1 (plan §9): count one tool call AND surface the hard-limit event
32609
+ * due on this call — `resource.limit_reached` once at the crossing,
32610
+ * `resource.overrun` for every call past the limit. The spine appends the
32611
+ * event right after its tool.call.
32612
+ */
32613
+ consumeToolCall() {
32435
32614
  this.ledger.record("tool-call");
32436
- return this.emitIfDue();
32615
+ const next = this.current();
32616
+ let hardEvent = null;
32617
+ const data = { used: next.toolCallsUsed, limit: next.toolCallsLimit, overrun: next.overrun };
32618
+ if (!this.hardLimitAnnounced && next.toolCallsRemaining <= 0) {
32619
+ this.hardLimitAnnounced = true;
32620
+ hardEvent = { kind: "resource.limit_reached", data };
32621
+ } else if (next.overrun > 0) {
32622
+ hardEvent = { kind: "resource.overrun", data };
32623
+ }
32624
+ const snapshot = shouldEmitSnapshot(this.lastEmitted, next) ? (this.lastEmitted = next, next) : null;
32625
+ return { snapshot, hardEvent };
32437
32626
  }
32438
32627
  /** §10.4 verification start: stage change (and a zero-cost ledger mark). */
32439
32628
  noteVerificationStart() {
@@ -32450,18 +32639,31 @@ var init_budgetRuntime = __esm({
32450
32639
  this.stage = stage;
32451
32640
  return this.emitIfDue();
32452
32641
  }
32642
+ /** 2.6.1 (plan §14): canonical budget for the reserve gate. */
32643
+ budgetSnapshot() {
32644
+ return this.ledger.budget(this.policy, this.stage);
32645
+ }
32453
32646
  /** Current projection without emitting. */
32454
32647
  current() {
32455
32648
  return buildResourceSnapshot(this.ledger.budget(this.policy, this.stage), this.policy);
32456
32649
  }
32457
- /** §11.3 gate — advisory mode never blocks; protected mode guards the zone. */
32458
- gateToolCall(toolName) {
32650
+ /**
32651
+ * §11.3 gate — argument-aware (2.6.1 §13). Hard limit denies first in BOTH
32652
+ * modes; advisory mode never blocks inside the protected zone; protected
32653
+ * mode guards the zone with isVerificationEssential(tool, args).
32654
+ */
32655
+ gateToolCall(toolNameOrInput) {
32656
+ const input = typeof toolNameOrInput === "string" ? { toolName: toolNameOrInput } : toolNameOrInput;
32459
32657
  const snapshot = this.current();
32658
+ if (snapshot.toolCallsRemaining <= 0) {
32659
+ return { allowed: false, advisory: false, reason: HARD_LIMIT_DENIAL, snapshot, hardLimit: true };
32660
+ }
32460
32661
  if (!snapshot.reserveProtected) return { allowed: true, advisory: false, snapshot };
32461
32662
  if (this.enforcement === "advisory") {
32462
32663
  return { allowed: true, advisory: true, reason: ADVISORY_NOTICE, snapshot };
32463
32664
  }
32464
- if (this.essential.has(toolName)) {
32665
+ const essential = input.toolName === "bash" || input.toolName === "grep_content" ? isVerificationEssential(input.toolName, input.args, input.stage ?? this.stage) : this.essential.has(input.toolName);
32666
+ if (essential) {
32465
32667
  return { allowed: true, advisory: true, reason: ADVISORY_NOTICE, snapshot };
32466
32668
  }
32467
32669
  return { allowed: false, advisory: false, reason: PROTECTED_DENIAL, snapshot };
@@ -32471,6 +32673,8 @@ var init_budgetRuntime = __esm({
32471
32673
  const rebuilt = rebuildLedgerFromEvents(events);
32472
32674
  this.ledger.resetTo(rebuilt.snapshot());
32473
32675
  this.lastEmitted = void 0;
32676
+ const now = this.current();
32677
+ this.hardLimitAnnounced = now.toolCallsRemaining <= 0;
32474
32678
  }
32475
32679
  /** Latest emitted snapshot (what the model surface shows), if any. */
32476
32680
  latestEmitted() {
@@ -32488,8 +32692,293 @@ var init_budgetRuntime = __esm({
32488
32692
  }
32489
32693
  });
32490
32694
 
32491
- // src/cli/sessionSpine.ts
32695
+ // src/cli/budget/restoreRuntime.ts
32492
32696
  import path21 from "node:path";
32697
+ async function restoreBudgetRuntimeFromSession(budget, sessionId2, baseDir) {
32698
+ const eventsPath = path21.join(resolveSessionsDir({ baseDir }), sessionId2, "events.jsonl");
32699
+ const report = await readSessionLog(eventsPath).catch(() => null);
32700
+ if (!report || report.events.length === 0) return false;
32701
+ budget.adoptLedgerFromEvents(report.events);
32702
+ return true;
32703
+ }
32704
+ async function lastHarnessManifestHash(sessionId2, baseDir) {
32705
+ const eventsPath = path21.join(resolveSessionsDir({ baseDir }), sessionId2, "events.jsonl");
32706
+ const report = await readSessionLog(eventsPath).catch(() => null);
32707
+ if (!report) return null;
32708
+ for (let i = report.events.length - 1; i >= 0; i--) {
32709
+ const e = report.events[i];
32710
+ if (e.kind === "session.harness_manifest") {
32711
+ const h = e.data.manifestHash;
32712
+ return typeof h === "string" ? h : null;
32713
+ }
32714
+ }
32715
+ return null;
32716
+ }
32717
+ var init_restoreRuntime = __esm({
32718
+ "src/cli/budget/restoreRuntime.ts"() {
32719
+ "use strict";
32720
+ init_session();
32721
+ }
32722
+ });
32723
+
32724
+ // src/cli/utils/cmdline.ts
32725
+ function quoteCmdArg(arg) {
32726
+ if (arg === "") return '""';
32727
+ if (!/[\s"^&|<>()%!]/.test(arg)) return arg;
32728
+ return `"${arg.replace(/"/g, '""')}"`;
32729
+ }
32730
+ function buildCmdLine(command, args) {
32731
+ return [command, ...args].map(quoteCmdArg).join(" ");
32732
+ }
32733
+ var init_cmdline = __esm({
32734
+ "src/cli/utils/cmdline.ts"() {
32735
+ "use strict";
32736
+ }
32737
+ });
32738
+
32739
+ // src/cli/updater.ts
32740
+ var updater_exports = {};
32741
+ __export(updater_exports, {
32742
+ REGISTRY_URL: () => REGISTRY_URL,
32743
+ checkForUpdate: () => checkForUpdate,
32744
+ compareSemver: () => compareSemver,
32745
+ distTagForVersion: () => distTagForVersion,
32746
+ fetchLatestVersion: () => fetchLatestVersion,
32747
+ getCurrentVersion: () => getCurrentVersion,
32748
+ looksLikeBrokenShim: () => looksLikeBrokenShim,
32749
+ performUpdate: () => performUpdate,
32750
+ registryUrlForTag: () => registryUrlForTag,
32751
+ resolveBundledNpmCli: () => resolveBundledNpmCli
32752
+ });
32753
+ import { createRequire } from "node:module";
32754
+ import { spawn as spawn5 } from "node:child_process";
32755
+ import { existsSync as existsSync11 } from "node:fs";
32756
+ import path22 from "node:path";
32757
+ import { fileURLToPath } from "node:url";
32758
+ function resolveBundledNpmCli(execPath = process.execPath) {
32759
+ const dir = path22.dirname(execPath);
32760
+ const candidates = [
32761
+ // Windows: C:\...\node.exe → C:\...\node_modules\npm\bin\npm-cli.js
32762
+ path22.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
32763
+ // POSIX: <prefix>/bin/node → <prefix>/lib/node_modules/npm/bin/npm-cli.js
32764
+ path22.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
32765
+ ];
32766
+ for (const candidate of candidates) {
32767
+ try {
32768
+ if (existsSync11(candidate)) return candidate;
32769
+ } catch {
32770
+ }
32771
+ }
32772
+ return null;
32773
+ }
32774
+ function looksLikeBrokenShim(exitCode, output) {
32775
+ if (exitCode === 127) return true;
32776
+ const h = output.toLowerCase();
32777
+ return h.includes("shim target not found") || h.includes("is not recognized");
32778
+ }
32779
+ function getCurrentVersion() {
32780
+ try {
32781
+ const pkgPath = path22.resolve(__dirname2, "..", "..", "package.json");
32782
+ const pkg = require2(pkgPath);
32783
+ return pkg.version;
32784
+ } catch {
32785
+ return "0.0.0";
32786
+ }
32787
+ }
32788
+ function compareSemver(a, b) {
32789
+ const parse3 = (v) => {
32790
+ const m = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
32791
+ if (!m) return [0, 0, 0, null];
32792
+ return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] ?? null];
32793
+ };
32794
+ const [a1, a2, a3, aPre] = parse3(a);
32795
+ const [b1, b2, b3, bPre] = parse3(b);
32796
+ if (a1 !== b1) return a1 < b1 ? -1 : 1;
32797
+ if (a2 !== b2) return a2 < b2 ? -1 : 1;
32798
+ if (a3 !== b3) return a3 < b3 ? -1 : 1;
32799
+ if (aPre === bPre) return 0;
32800
+ if (aPre === null) return 1;
32801
+ if (bPre === null) return -1;
32802
+ return aPre < bPre ? -1 : 1;
32803
+ }
32804
+ function distTagForVersion(version2) {
32805
+ if (version2.includes("-alpha.")) return "alpha";
32806
+ if (version2.includes("-beta.")) return "beta";
32807
+ if (version2.includes("-next.")) return "next";
32808
+ return "latest";
32809
+ }
32810
+ function registryUrlForTag(tag = distTagForVersion(getCurrentVersion())) {
32811
+ return `https://registry.npmjs.org/zelari-code/${tag}`;
32812
+ }
32813
+ async function fetchLatestVersion(fetcher = fetch, registryUrl = REGISTRY_URL, timeoutMs2 = 5e3) {
32814
+ try {
32815
+ const controller = new AbortController();
32816
+ const timer = setTimeout(() => controller.abort(), timeoutMs2);
32817
+ const response = await fetcher(registryUrl, { signal: controller.signal });
32818
+ clearTimeout(timer);
32819
+ if (!response.ok) {
32820
+ return { error: `Registry responded ${response.status}` };
32821
+ }
32822
+ const data = await response.json();
32823
+ if (!data.version || typeof data.version !== "string") {
32824
+ return { error: "Registry response missing version field" };
32825
+ }
32826
+ return { version: data.version };
32827
+ } catch (err) {
32828
+ const message = err instanceof Error ? err.message : String(err);
32829
+ return { error: message };
32830
+ }
32831
+ }
32832
+ async function checkForUpdate(fetcher = fetch, registryUrl) {
32833
+ const currentVersion = getCurrentVersion();
32834
+ const url2 = registryUrl ?? registryUrlForTag();
32835
+ const latest = await fetchLatestVersion(fetcher, url2);
32836
+ if ("error" in latest) {
32837
+ return {
32838
+ currentVersion,
32839
+ latestVersion: currentVersion,
32840
+ updateAvailable: false,
32841
+ error: latest.error
32842
+ };
32843
+ }
32844
+ const cmp = compareSemver(currentVersion, latest.version);
32845
+ return {
32846
+ currentVersion,
32847
+ latestVersion: latest.version,
32848
+ updateAvailable: cmp < 0
32849
+ };
32850
+ }
32851
+ async function performUpdate(packageName = "zelari-code", executor = spawn5, resolveNpmCli = resolveBundledNpmCli, channel) {
32852
+ const tag = channel ?? distTagForVersion(getCurrentVersion());
32853
+ const args = ["install", "-g", `${packageName}@${tag}`];
32854
+ const primary = await runNpm(executor, args, "shim");
32855
+ if (primary.ok) return primary;
32856
+ const npmCli = resolveNpmCli();
32857
+ if (npmCli && looksLikeBrokenShim(primary.exitCode, primary.output)) {
32858
+ const fallback = await runNpm(executor, args, "bundled", npmCli);
32859
+ return {
32860
+ ...fallback,
32861
+ output: `[update] npm shim failed (${primary.error ?? "exit " + primary.exitCode}); retried via bundled npm (${npmCli}).
32862
+ ${fallback.output}`
32863
+ };
32864
+ }
32865
+ return primary;
32866
+ }
32867
+ function runNpm(executor, args, mode, npmCliPath) {
32868
+ return new Promise((resolve3) => {
32869
+ let stdout = "";
32870
+ let stderr = "";
32871
+ const stdio = ["ignore", "pipe", "pipe"];
32872
+ 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 });
32873
+ child.stdout?.on("data", (chunk) => {
32874
+ stdout += chunk.toString();
32875
+ });
32876
+ child.stderr?.on("data", (chunk) => {
32877
+ stderr += chunk.toString();
32878
+ });
32879
+ child.on("error", (err) => {
32880
+ resolve3({
32881
+ ok: false,
32882
+ output: stdout + stderr,
32883
+ error: err.message,
32884
+ exitCode: null
32885
+ });
32886
+ });
32887
+ child.on("close", (code) => {
32888
+ const ok = code === 0;
32889
+ resolve3({
32890
+ ok,
32891
+ output: stdout + stderr,
32892
+ error: ok ? void 0 : `npm exited with code ${code}`,
32893
+ exitCode: code
32894
+ });
32895
+ });
32896
+ });
32897
+ }
32898
+ var require2, __dirname2, REGISTRY_URL;
32899
+ var init_updater = __esm({
32900
+ "src/cli/updater.ts"() {
32901
+ "use strict";
32902
+ init_cmdline();
32903
+ require2 = createRequire(import.meta.url);
32904
+ __dirname2 = path22.dirname(fileURLToPath(import.meta.url));
32905
+ REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
32906
+ }
32907
+ });
32908
+
32909
+ // src/cli/harnessManifest.ts
32910
+ function buildHarnessManifest(parts) {
32911
+ const prompts = {};
32912
+ for (const [role, text] of Object.entries(parts.prompts ?? {})) {
32913
+ if (typeof text === "string" && text.length > 0) {
32914
+ prompts[role] = harnessInputHash(text);
32915
+ }
32916
+ }
32917
+ const manifest = HarnessManifestV1Schema.parse({
32918
+ schemaVersion: 1,
32919
+ profile: {
32920
+ id: parts.profile.id,
32921
+ phase: parts.phase,
32922
+ hash: profileHash(parts.profile)
32923
+ },
32924
+ prompts,
32925
+ capabilities: {
32926
+ // 2.6.1 (plan §7): full fingerprints when specs are available.
32927
+ toolManifestHash: parts.toolSpecs ? toolFingerprintHash(parts.toolSpecs) : toolManifestHash(parts.toolNames),
32928
+ skillManifestHash: parts.skillSpecs ? skillFingerprintHash(parts.skillSpecs) : harnessInputHash([...parts.skillIds ?? []].sort())
32929
+ },
32930
+ policies: {
32931
+ routingHash: harnessInputHash(parts.routing ?? { unset: true }),
32932
+ verificationHash: harnessInputHash(parts.verification ?? { engine: "deterministic" }),
32933
+ completionPolicyHash: harnessInputHash(parts.completionPolicy ?? { mode: "strict", required: "*" }),
32934
+ compactionHash: harnessInputHash(parts.compaction ?? { version: 1 }),
32935
+ resourcePolicyHash: harnessInputHash(parts.resourcePolicy ?? { unset: true })
32936
+ },
32937
+ runtime: {
32938
+ // 2.6.1 (plan §7): canonical export — no more require.resolve.
32939
+ coreVersion: parts.coreVersion ?? CORE_VERSION,
32940
+ cliVersion: parts.cliVersion ?? getCurrentVersion()
32941
+ }
32942
+ });
32943
+ return { manifest, manifestHash: hashHarnessManifest(manifest) };
32944
+ }
32945
+ var init_harnessManifest2 = __esm({
32946
+ "src/cli/harnessManifest.ts"() {
32947
+ "use strict";
32948
+ init_dist();
32949
+ init_dist();
32950
+ init_updater();
32951
+ }
32952
+ });
32953
+
32954
+ // src/cli/kraken/taskContract.ts
32955
+ function latestTaskContract(events) {
32956
+ let latest;
32957
+ for (const e of events) {
32958
+ if (e.kind !== "task.contract" && e.kind !== "task.contract_updated") continue;
32959
+ const raw = e.data.contract;
32960
+ if (raw && typeof raw === "object") {
32961
+ const candidate = raw;
32962
+ if (!latest || candidate.version > latest.version) latest = candidate;
32963
+ }
32964
+ }
32965
+ return latest;
32966
+ }
32967
+ function updateTaskContract(contract, update) {
32968
+ return applyTaskContractUpdate(contract, update);
32969
+ }
32970
+ function contractEventData(contract, updated) {
32971
+ return { contract, kind: updated ? "task.contract_updated" : "task.contract" };
32972
+ }
32973
+ var init_taskContract2 = __esm({
32974
+ "src/cli/kraken/taskContract.ts"() {
32975
+ "use strict";
32976
+ init_dist();
32977
+ }
32978
+ });
32979
+
32980
+ // src/cli/sessionSpine.ts
32981
+ import path23 from "node:path";
32493
32982
  function spineEnabled() {
32494
32983
  return process.env.ZELARI_SESSION_SPINE !== "0";
32495
32984
  }
@@ -32571,14 +33060,54 @@ async function wrapSessionWriter(inner, sessionId2, options = {}) {
32571
33060
  const spine = await SessionSpineMirror.adopt(sessionId2, options);
32572
33061
  if (spine.status === "active") {
32573
33062
  const profile = options.extraStarted?.profile;
32574
- spine.attachBudgetRuntime(
32575
- new BudgetRuntime(typeof profile === "string" ? profile : "kraken/v1", {
32576
- enforcement: resolveResourceEnforcement()
32577
- })
33063
+ const budget = new BudgetRuntime(typeof profile === "string" ? profile : "kraken/v1", {
33064
+ enforcement: resolveResourceEnforcement()
33065
+ });
33066
+ if (spine.resumedFromSeq !== void 0 && spine.resumedFromSeq > 0) {
33067
+ await restoreBudgetRuntimeFromSession(budget, sessionId2, options.baseDir);
33068
+ }
33069
+ spine.attachBudgetRuntime(budget);
33070
+ await noteHarnessLifecycle(
33071
+ spine,
33072
+ sessionId2,
33073
+ typeof profile === "string" ? profile : "kraken/v1",
33074
+ budget,
33075
+ options.baseDir,
33076
+ options.extraStarted?.phase
32578
33077
  );
32579
33078
  }
32580
33079
  return new SpineMirroringWriter(inner, spine.status === "active" ? spine : null);
32581
33080
  }
33081
+ function taskContractsEnabled() {
33082
+ return process.env.ZELARI_TASK_CONTRACT !== "0";
33083
+ }
33084
+ async function noteHarnessLifecycle(spine, sessionId2, profileId, budget, baseDir, phaseHint) {
33085
+ try {
33086
+ const profile = resolveProfile(profileId);
33087
+ const { manifest, manifestHash } = buildHarnessManifest({
33088
+ profile,
33089
+ phase: phaseHint === "plan" ? "plan" : "build",
33090
+ toolNames: profile.tools,
33091
+ // plan §7/§8: the REAL session policy — never the {unset:true} marker.
33092
+ resourcePolicy: budget.policy
33093
+ });
33094
+ if (spine.resumedFromSeq !== void 0 && spine.resumedFromSeq > 0) {
33095
+ const original = await lastHarnessManifestHash(sessionId2, baseDir);
33096
+ if (original === null) {
33097
+ spine.harnessManifest(manifest, manifestHash);
33098
+ } else if (original !== manifestHash) {
33099
+ await spine.appendEvent({
33100
+ kind: "session.harness_drift",
33101
+ actor: ACTOR_SYSTEM,
33102
+ data: { originalManifestHash: original, currentManifestHash: manifestHash }
33103
+ });
33104
+ }
33105
+ } else {
33106
+ spine.harnessManifest(manifest, manifestHash);
33107
+ }
33108
+ } catch {
33109
+ }
33110
+ }
32582
33111
  var MAX_STREAM_BUFFERS, SessionSpineMirror, SpineMirroringWriter;
32583
33112
  var init_sessionSpine = __esm({
32584
33113
  "src/cli/sessionSpine.ts"() {
@@ -32587,6 +33116,10 @@ var init_sessionSpine = __esm({
32587
33116
  init_session();
32588
33117
  init_verification2();
32589
33118
  init_budgetRuntime();
33119
+ init_restoreRuntime();
33120
+ init_runtime2();
33121
+ init_harnessManifest2();
33122
+ init_taskContract2();
32590
33123
  MAX_STREAM_BUFFERS = 32;
32591
33124
  SessionSpineMirror = class _SessionSpineMirror {
32592
33125
  constructor(sessionId2, options) {
@@ -32615,8 +33148,8 @@ var init_sessionSpine = __esm({
32615
33148
  const mirror = new _SessionSpineMirror(sessionId2, options);
32616
33149
  if (!spineEnabled()) return mirror;
32617
33150
  try {
32618
- const sessionDir = path21.join(mirror.sessionsDir, sessionId2);
32619
- const report = await readSessionLog(path21.join(sessionDir, "events.jsonl"));
33151
+ const sessionDir = path23.join(mirror.sessionsDir, sessionId2);
33152
+ const report = await readSessionLog(path23.join(sessionDir, "events.jsonl"));
32620
33153
  const existed = report.events.length > 0 || report.issues.length > 0;
32621
33154
  if (report.events.some((e) => e.kind === "task.contract" || e.kind === "user.message")) {
32622
33155
  mirror.contractSeeded = true;
@@ -32702,19 +33235,45 @@ var init_sessionSpine = __esm({
32702
33235
  latestResourceSnapshot() {
32703
33236
  return this.budgetRuntime?.latestEmitted() ?? null;
32704
33237
  }
33238
+ /**
33239
+ * 2.6.1 (plan §8): session ResourcePolicy cap for hosts that need to derive
33240
+ * per-turn limits — the policy stays the single authority. Null when no
33241
+ * runtime is attached (hosts keep their own default).
33242
+ */
33243
+ /** Full budget shape for evaluateResourceReserveGate (plan §14). */
33244
+ resourceBudgetSummary() {
33245
+ return this.budgetRuntime?.budgetSnapshot() ?? null;
33246
+ }
33247
+ resourceBudgetLimit() {
33248
+ const snap = this.budgetRuntime?.current();
33249
+ return snap ? {
33250
+ maxToolCalls: snap.toolCallsLimit,
33251
+ remaining: snap.toolCallsRemaining,
33252
+ verificationReserve: snap.verificationReserve
33253
+ } : null;
33254
+ }
32705
33255
  /**
32706
33256
  * §11.3 pre-dispatch gate for hosts that enforce the protected zone
32707
33257
  * (Phase 3): delegates to the attached runtime, never throws. Null when
32708
33258
  * no runtime is attached (hosts treat as "no budget info, allow").
33259
+ * 2.6.1 (plan §13): argument-aware — pass the tool args so `bash` is only
33260
+ * essential when it is a test/typecheck/build/git-diff command.
32709
33261
  */
32710
- gateResourceToolCall(toolName) {
32711
- const gate = this.budgetRuntime?.gateToolCall(toolName);
33262
+ gateResourceToolCall(toolName, args) {
33263
+ const gate = this.budgetRuntime?.gateToolCall({ toolName, args });
32712
33264
  if (!gate) return null;
32713
- return { allowed: gate.allowed, ...gate.reason ? { reason: gate.reason } : {} };
33265
+ return {
33266
+ allowed: gate.allowed,
33267
+ ...gate.reason ? { reason: gate.reason } : {},
33268
+ ...gate.hardLimit ? { hardLimit: gate.hardLimit } : {}
33269
+ };
32714
33270
  }
32715
- /** Count a landed tool.call; returns the snapshot due (§10.4), if any. */
33271
+ /**
33272
+ * Count a landed tool.call; returns the snapshot due (§10.4) plus the
33273
+ * hard-limit event due on this call (2.6.1 plan §9), if any.
33274
+ */
32716
33275
  onToolCallBudget() {
32717
- return this.budgetRuntime?.noteToolCall() ?? null;
33276
+ return this.budgetRuntime?.consumeToolCall() ?? { snapshot: null, hardEvent: null };
32718
33277
  }
32719
33278
  async flush() {
32720
33279
  await this.chain;
@@ -32726,7 +33285,7 @@ var init_sessionSpine = __esm({
32726
33285
  async derivedPriorTurns() {
32727
33286
  if (this.status !== "active" && this.status !== "closed") return null;
32728
33287
  const report = await readSessionLog(
32729
- path21.join(this.sessionsDir, this.sessionId, "events.jsonl")
33288
+ path23.join(this.sessionsDir, this.sessionId, "events.jsonl")
32730
33289
  ).catch(() => null);
32731
33290
  if (!report || report.events.length === 0) return null;
32732
33291
  return deriveMessages(report.events);
@@ -32736,7 +33295,7 @@ var init_sessionSpine = __esm({
32736
33295
  if (this.status !== "active" && this.status !== "closed") return null;
32737
33296
  await this.flush();
32738
33297
  const report = await readSessionLog(
32739
- path21.join(this.sessionsDir, this.sessionId, "events.jsonl")
33298
+ path23.join(this.sessionsDir, this.sessionId, "events.jsonl")
32740
33299
  ).catch(() => null);
32741
33300
  if (!report || report.events.length === 0) return null;
32742
33301
  return buildCompactionStateSnapshot(report.events, toSeq);
@@ -32749,7 +33308,7 @@ var init_sessionSpine = __esm({
32749
33308
  async lastVerificationRun() {
32750
33309
  if (this.status !== "active" && this.status !== "closed") return null;
32751
33310
  const report = await readSessionLog(
32752
- path21.join(this.sessionsDir, this.sessionId, "events.jsonl")
33311
+ path23.join(this.sessionsDir, this.sessionId, "events.jsonl")
32753
33312
  ).catch(() => null);
32754
33313
  if (!report) return null;
32755
33314
  return lastVerificationRun(report.events);
@@ -32820,10 +33379,18 @@ var init_sessionSpine = __esm({
32820
33379
  }
32821
33380
  append(input) {
32822
33381
  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;
33382
+ const budgetEffect = input.kind === "tool.call" ? this.onToolCallBudget() : null;
33383
+ const dueContract = input.kind === "user.message" && taskContractsEnabled() && !this.contractSeeded ? (this.contractSeeded = true, input.data?.text) : null;
33384
+ const steerText = input.kind === "user.message" && taskContractsEnabled() && this.contractSeeded && !dueContract ? input.data?.text ?? null : null;
32825
33385
  let seq = this.chain.then(() => this.writer.append(input)).then((envelope) => envelope.seq);
32826
- if (dueSnapshot) {
33386
+ if (budgetEffect?.hardEvent) {
33387
+ const hard = budgetEffect.hardEvent;
33388
+ seq = seq.then(
33389
+ (s) => this.writer.append({ kind: hard.kind, actor: ACTOR_SYSTEM, data: { ...hard.data } }).then(() => s)
33390
+ );
33391
+ }
33392
+ if (budgetEffect?.snapshot) {
33393
+ const dueSnapshot = budgetEffect.snapshot;
32827
33394
  seq = seq.then(
32828
33395
  (s) => this.writer.append({ kind: "resource.snapshot", actor: ACTOR_SYSTEM, data: { ...dueSnapshot } }).then(() => s)
32829
33396
  );
@@ -32844,6 +33411,30 @@ var init_sessionSpine = __esm({
32844
33411
  return s;
32845
33412
  });
32846
33413
  }
33414
+ if (steerText) {
33415
+ seq = seq.then(async (s) => {
33416
+ try {
33417
+ const eventsPath = path23.join(this.sessionsDir, this.sessionId, "events.jsonl");
33418
+ const report = await readSessionLog(eventsPath).catch(() => null);
33419
+ if (!report || typeof s !== "number") return s;
33420
+ const current = latestTaskContract(report.events);
33421
+ if (!current) return s;
33422
+ const updated = updateTaskContract(current, {
33423
+ addConstraints: [{ id: `steer-${s}`, text: steerText, source: "user", required: false }],
33424
+ nextUserSeq: s
33425
+ });
33426
+ if (updated.version !== current.version) {
33427
+ await this.writer.append({
33428
+ kind: "task.contract_updated",
33429
+ actor: ACTOR_SYSTEM,
33430
+ data: contractEventData(updated, true)
33431
+ });
33432
+ }
33433
+ } catch {
33434
+ }
33435
+ return s;
33436
+ });
33437
+ }
32847
33438
  this.chain = seq.catch((err) => {
32848
33439
  this.status = "degraded";
32849
33440
  this.writer = null;
@@ -33070,10 +33661,10 @@ var init_sessionSurface = __esm({
33070
33661
  });
33071
33662
 
33072
33663
  // src/cli/hooks/observationStore.ts
33073
- import { existsSync as existsSync11, statSync as statSync2 } from "node:fs";
33074
- import path22 from "node:path";
33664
+ import { existsSync as existsSync12, statSync as statSync2 } from "node:fs";
33665
+ import path24 from "node:path";
33075
33666
  function sessionFilePath(sessionId2, baseDir) {
33076
- return path22.join(baseDir ?? getSessionBaseDir(), `${sessionId2}.jsonl`);
33667
+ return path24.join(baseDir ?? getSessionBaseDir(), `${sessionId2}.jsonl`);
33077
33668
  }
33078
33669
  function isToolEnd(e) {
33079
33670
  return e.type === "tool_execution_end";
@@ -33083,7 +33674,7 @@ function isToolStart(e) {
33083
33674
  }
33084
33675
  async function loadObservationIndex(sessionId2, baseDir) {
33085
33676
  const filePath = sessionFilePath(sessionId2, baseDir);
33086
- const exists = existsSync11(filePath);
33677
+ const exists = existsSync12(filePath);
33087
33678
  let mtimeMs = 0;
33088
33679
  if (exists) {
33089
33680
  try {
@@ -33095,7 +33686,7 @@ async function loadObservationIndex(sessionId2, baseDir) {
33095
33686
  const hit = cache.get(sessionId2);
33096
33687
  if (hit && !exists) return hit;
33097
33688
  if (hit && hit.filePath === filePath && hit.mtimeMs === mtimeMs) return hit;
33098
- const events = existsSync11(filePath) ? await readSession(filePath) : [];
33689
+ const events = existsSync12(filePath) ? await readSession(filePath) : [];
33099
33690
  const names = /* @__PURE__ */ new Map();
33100
33691
  const bySeq = /* @__PURE__ */ new Map();
33101
33692
  const byToolCallId = /* @__PURE__ */ new Map();
@@ -33192,8 +33783,8 @@ var init_observationStore = __esm({
33192
33783
  });
33193
33784
 
33194
33785
  // 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";
33786
+ import { promises as fs14, existsSync as existsSync13, statSync as statSync3, renameSync, appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
33787
+ import path25 from "node:path";
33197
33788
  import os6 from "node:os";
33198
33789
  async function readMetrics(file2) {
33199
33790
  let raw = "";
@@ -33242,8 +33833,8 @@ var init_metrics2 = __esm({
33242
33833
  file;
33243
33834
  writeQueue = Promise.resolve();
33244
33835
  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 });
33836
+ this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path25.join(os6.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
33837
+ mkdirSync6(path25.dirname(this.file), { recursive: true });
33247
33838
  }
33248
33839
  /** Metrics file path — doctor/summary readers use this. */
33249
33840
  get filePath() {
@@ -33278,7 +33869,7 @@ var init_metrics2 = __esm({
33278
33869
  }
33279
33870
  /** If the file is over the rotation threshold, rotate it. */
33280
33871
  maybeRotate() {
33281
- if (!existsSync12(this.file)) return;
33872
+ if (!existsSync13(this.file)) return;
33282
33873
  try {
33283
33874
  const stat = statSync3(this.file);
33284
33875
  if (stat.size >= METRICS_ROTATE_BYTES) {
@@ -34459,8 +35050,8 @@ var init_resolveStream = __esm({
34459
35050
  });
34460
35051
 
34461
35052
  // 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";
35053
+ import { createHash as createHash7, randomBytes as randomBytes2 } from "node:crypto";
35054
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
34464
35055
  import { homedir as homedir3, tmpdir } from "node:os";
34465
35056
  import { join as join11 } from "node:path";
34466
35057
  function resolveToolOutputDir() {
@@ -34486,10 +35077,10 @@ function spillToolOutput(fullText, meta3) {
34486
35077
  return null;
34487
35078
  try {
34488
35079
  const dir = resolveToolOutputDir();
34489
- if (!existsSync13(dir)) {
35080
+ if (!existsSync14(dir)) {
34490
35081
  mkdirSync7(dir, { recursive: true });
34491
35082
  }
34492
- const hash3 = createHash6("sha256").update(fullText).digest("hex").slice(0, 12);
35083
+ const hash3 = createHash7("sha256").update(fullText).digest("hex").slice(0, 12);
34493
35084
  const stamp = Date.now().toString(36);
34494
35085
  const rnd = randomBytes2(3).toString("hex");
34495
35086
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
@@ -34753,14 +35344,14 @@ var init_registry2 = __esm({
34753
35344
  });
34754
35345
 
34755
35346
  // src/cli/safety/sandboxPath.ts
34756
- import path24 from "node:path";
35347
+ import path26 from "node:path";
34757
35348
  function resolveSandboxedPath(userPath, options = {}) {
34758
35349
  if (typeof userPath !== "string" || userPath.length === 0) {
34759
35350
  throw new SandboxViolationError("Empty path", userPath, "");
34760
35351
  }
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;
35352
+ const root = path26.resolve(options.root ?? process.cwd());
35353
+ const resolved = path26.isAbsolute(userPath) ? path26.resolve(userPath) : path26.resolve(root, userPath);
35354
+ const rootWithSep = root.endsWith(path26.sep) ? root : root + path26.sep;
34764
35355
  if (resolved !== root && !resolved.startsWith(rootWithSep)) {
34765
35356
  throw new SandboxViolationError(
34766
35357
  `Path escapes sandbox root: ${userPath} \u2192 ${resolved} (root: ${root})`,
@@ -34848,12 +35439,12 @@ __export(auditLogger_exports, {
34848
35439
  AuditLogger: () => AuditLogger
34849
35440
  });
34850
35441
  import { promises as fs15 } from "node:fs";
34851
- import path25 from "node:path";
35442
+ import path27 from "node:path";
34852
35443
  import os7 from "node:os";
34853
35444
  function defaultAuditPath() {
34854
35445
  const override = process.env.ANATHEMA_AUDIT_LOG;
34855
35446
  if (override && override.trim().length > 0) return override;
34856
- return path25.join(os7.tmpdir(), "zelari-code", "audit.jsonl");
35447
+ return path27.join(os7.tmpdir(), "zelari-code", "audit.jsonl");
34857
35448
  }
34858
35449
  function redactArgs(args) {
34859
35450
  const redacted = {};
@@ -34896,7 +35487,7 @@ var init_auditLogger = __esm({
34896
35487
  async append(entry) {
34897
35488
  const line = JSON.stringify(entry) + "\n";
34898
35489
  this.writeQueue = this.writeQueue.then(async () => {
34899
- await fs15.mkdir(path25.dirname(this.logPath), { recursive: true });
35490
+ await fs15.mkdir(path27.dirname(this.logPath), { recursive: true });
34900
35491
  await fs15.appendFile(this.logPath, line, "utf-8");
34901
35492
  });
34902
35493
  return this.writeQueue;
@@ -34940,25 +35531,10 @@ var init_auditLogger = __esm({
34940
35531
  }
34941
35532
  });
34942
35533
 
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
35534
  // 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";
35535
+ import { spawn as spawn6 } from "node:child_process";
35536
+ import { existsSync as existsSync15 } from "node:fs";
35537
+ import path28 from "node:path";
34962
35538
  function parseEslintJson(stdout, _file2) {
34963
35539
  const json2 = safeJson(stdout);
34964
35540
  if (!Array.isArray(json2)) return [];
@@ -35017,7 +35593,7 @@ function safeJson(s) {
35017
35593
  }
35018
35594
  }
35019
35595
  function providerForFile(file2, providers = DEFAULT_PROVIDERS) {
35020
- const ext = path26.extname(file2).toLowerCase();
35596
+ const ext = path28.extname(file2).toLowerCase();
35021
35597
  return providers.find((p3) => p3.extensions.includes(ext)) ?? null;
35022
35598
  }
35023
35599
  function resolveBin(bin, cwd) {
@@ -35025,10 +35601,10 @@ function resolveBin(bin, cwd) {
35025
35601
  let dir = cwd;
35026
35602
  for (let i = 0; i < 6; i += 1) {
35027
35603
  for (const suffix of suffixes) {
35028
- const candidate = path26.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
35029
- if (existsSync14(candidate)) return candidate;
35604
+ const candidate = path28.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
35605
+ if (existsSync15(candidate)) return candidate;
35030
35606
  }
35031
- const parent = path26.dirname(dir);
35607
+ const parent = path28.dirname(dir);
35032
35608
  if (parent === dir) break;
35033
35609
  dir = parent;
35034
35610
  }
@@ -35101,7 +35677,7 @@ var init_engine2 = __esm({
35101
35677
  };
35102
35678
  let child;
35103
35679
  try {
35104
- child = process.platform === "win32" ? spawn5(buildCmdLine(cmd, args), { cwd: opts.cwd, shell: true }) : spawn5(cmd, args, { cwd: opts.cwd });
35680
+ child = process.platform === "win32" ? spawn6(buildCmdLine(cmd, args), { cwd: opts.cwd, shell: true }) : spawn6(cmd, args, { cwd: opts.cwd });
35105
35681
  } catch {
35106
35682
  done({ code: null, stdout: "", stderr: "" });
35107
35683
  return;
@@ -35132,19 +35708,19 @@ var init_engine2 = __esm({
35132
35708
  });
35133
35709
 
35134
35710
  // 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";
35711
+ import { appendFileSync as appendFileSync3, existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync14, readdirSync as readdirSync2 } from "node:fs";
35712
+ import path29 from "node:path";
35137
35713
  function radioDir(cwd) {
35138
- return path27.join(cwd, ".zelari", "radio");
35714
+ return path29.join(cwd, ".zelari", "radio");
35139
35715
  }
35140
35716
  function radioPath(cwd, sessionId2) {
35141
35717
  const safe = (sessionId2 || "default").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
35142
- return path27.join(radioDir(cwd), `${safe}.jsonl`);
35718
+ return path29.join(radioDir(cwd), `${safe}.jsonl`);
35143
35719
  }
35144
35720
  function appendKrakenRadio(cwd, sessionId2, event) {
35145
35721
  try {
35146
35722
  const dir = radioDir(cwd);
35147
- if (!existsSync15(dir)) mkdirSync8(dir, { recursive: true });
35723
+ if (!existsSync16(dir)) mkdirSync8(dir, { recursive: true });
35148
35724
  const row = {
35149
35725
  ts: event.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
35150
35726
  kind: event.kind,
@@ -35165,7 +35741,7 @@ function appendKrakenRadio(cwd, sessionId2, event) {
35165
35741
  function readKrakenRadio(cwd, sessionId2, limit = 50) {
35166
35742
  try {
35167
35743
  const file2 = radioPath(cwd, sessionId2);
35168
- if (!existsSync15(file2)) return [];
35744
+ if (!existsSync16(file2)) return [];
35169
35745
  const lines = readFileSync14(file2, "utf8").split(/\r?\n/).filter(Boolean);
35170
35746
  const slice = lines.slice(-Math.max(1, limit));
35171
35747
  const out = [];
@@ -35189,7 +35765,7 @@ function formatKrakenRadioStatus(cwd, sessionId2, limit = 12) {
35189
35765
  const flag = e.ok === false ? "\u2717" : e.ok === true ? "\u2713" : "\xB7";
35190
35766
  const ms = e.durationMs != null ? ` ${e.durationMs}ms` : "";
35191
35767
  const model = e.model ? ` [${e.model}]` : "";
35192
- const wt = e.worktree ? ` wt=${path27.basename(e.worktree)}` : "";
35768
+ const wt = e.worktree ? ` wt=${path29.basename(e.worktree)}` : "";
35193
35769
  const detail = e.detail ? ` \u2014 ${e.detail.slice(0, 120)}` : "";
35194
35770
  return `${flag} ${e.ts.slice(11, 19)} ${e.kind} ${e.agent} "${e.description}"${model}${wt}${ms}${detail}`;
35195
35771
  });
@@ -35203,8 +35779,8 @@ var init_krakenRadio = __esm({
35203
35779
 
35204
35780
  // src/cli/tools/krakenWorktree.ts
35205
35781
  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";
35782
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9, rmSync } from "node:fs";
35783
+ import path30 from "node:path";
35208
35784
  import { promisify } from "node:util";
35209
35785
  import { randomBytes as randomBytes3 } from "node:crypto";
35210
35786
  function isKrakenWorktreeEnabled(env = process.env) {
@@ -35252,10 +35828,10 @@ async function createKrakenWorktree(cwd, label) {
35252
35828
  const id = `${Date.now().toString(36)}-${randomBytes3(3).toString("hex")}`;
35253
35829
  const slug = (label ?? "task").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "task";
35254
35830
  const branch = `kraken/${slug}-${id}`;
35255
- const wtRoot = path28.join(repoRoot, ".zelari", "worktrees");
35256
- const wtPath = path28.join(wtRoot, `kraken-${id}`);
35831
+ const wtRoot = path30.join(repoRoot, ".zelari", "worktrees");
35832
+ const wtPath = path30.join(wtRoot, `kraken-${id}`);
35257
35833
  try {
35258
- if (!existsSync16(wtRoot)) mkdirSync9(wtRoot, { recursive: true });
35834
+ if (!existsSync17(wtRoot)) mkdirSync9(wtRoot, { recursive: true });
35259
35835
  } catch {
35260
35836
  return null;
35261
35837
  }
@@ -35367,7 +35943,7 @@ async function cleanupKrakenWorktree(handle, env = process.env) {
35367
35943
  if (shouldKeepWorktree(env)) return;
35368
35944
  await git2(handle.repoRoot, ["worktree", "remove", "--force", handle.path]);
35369
35945
  try {
35370
- if (existsSync16(handle.path)) {
35946
+ if (existsSync17(handle.path)) {
35371
35947
  rmSync(handle.path, { recursive: true, force: true });
35372
35948
  }
35373
35949
  } catch {
@@ -36704,7 +37280,7 @@ var init_askUser = __esm({
36704
37280
  });
36705
37281
 
36706
37282
  // src/cli/skillsMd.ts
36707
- import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync15 } from "node:fs";
37283
+ import { existsSync as existsSync18, readdirSync as readdirSync3, readFileSync as readFileSync15 } from "node:fs";
36708
37284
  import { join as join12 } from "node:path";
36709
37285
  import { homedir as homedir4 } from "node:os";
36710
37286
  function skillMdSearchDirs(projectRoot = process.cwd()) {
@@ -36770,7 +37346,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36770
37346
  const summary = { loaded: [], skipped: [] };
36771
37347
  const seen = new Set(options.existingIds ?? []);
36772
37348
  for (const dir of skillMdSearchDirs(projectRoot)) {
36773
- if (!existsSync17(dir)) continue;
37349
+ if (!existsSync18(dir)) continue;
36774
37350
  let entries;
36775
37351
  try {
36776
37352
  entries = readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -36779,7 +37355,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36779
37355
  }
36780
37356
  for (const entry of entries) {
36781
37357
  const skillPath = join12(dir, entry, "SKILL.md");
36782
- if (!existsSync17(skillPath)) continue;
37358
+ if (!existsSync18(skillPath)) continue;
36783
37359
  try {
36784
37360
  const parsed = parseSkillMd(readFileSync15(skillPath, "utf8"), skillPath);
36785
37361
  if (!parsed) {
@@ -36954,14 +37530,14 @@ var init_todoTools = __esm({
36954
37530
  import {
36955
37531
  mkdirSync as mkdirSync10,
36956
37532
  writeFileSync as writeFileSync12,
36957
- existsSync as existsSync18,
37533
+ existsSync as existsSync19,
36958
37534
  accessSync,
36959
37535
  constants,
36960
37536
  realpathSync
36961
37537
  } from "node:fs";
36962
37538
  import { join as join13, basename } from "node:path";
36963
37539
  import { homedir as homedir5 } from "node:os";
36964
- import { createHash as createHash7 } from "node:crypto";
37540
+ import { createHash as createHash8 } from "node:crypto";
36965
37541
  function resolveWorkspaceRoot(projectRoot = process.cwd()) {
36966
37542
  const candidates = [
36967
37543
  join13(projectRoot, ".zelari"),
@@ -36977,11 +37553,11 @@ function resolveWorkspaceRoot(projectRoot = process.cwd()) {
36977
37553
  return candidates[0];
36978
37554
  }
36979
37555
  function hashProject(projectPath) {
36980
- return createHash7("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
37556
+ return createHash8("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
36981
37557
  }
36982
37558
  function isWritableDir(dir) {
36983
37559
  try {
36984
- if (!existsSync18(dir)) return false;
37560
+ if (!existsSync19(dir)) return false;
36985
37561
  accessSync(dir, constants.W_OK);
36986
37562
  return true;
36987
37563
  } catch {
@@ -36990,9 +37566,9 @@ function isWritableDir(dir) {
36990
37566
  }
36991
37567
  function ensureWorkspaceDir(workspaceDir) {
36992
37568
  mkdirSync10(workspaceDir, { recursive: true });
36993
- if (workspaceDir.endsWith("/.zelari") && existsSync18(join13(workspaceDir, "..", ".git"))) {
37569
+ if (workspaceDir.endsWith("/.zelari") && existsSync19(join13(workspaceDir, "..", ".git"))) {
36994
37570
  const gitignorePath = join13(workspaceDir, ".gitignore");
36995
- if (!existsSync18(gitignorePath)) {
37571
+ if (!existsSync19(gitignorePath)) {
36996
37572
  writeFileSync12(gitignorePath, "*\n!.gitignore\n");
36997
37573
  }
36998
37574
  }
@@ -37032,7 +37608,7 @@ __export(storage_exports, {
37032
37608
  import {
37033
37609
  readFileSync as readFileSync16,
37034
37610
  writeFileSync as writeFileSync13,
37035
- existsSync as existsSync19,
37611
+ existsSync as existsSync20,
37036
37612
  mkdirSync as mkdirSync11,
37037
37613
  readdirSync as readdirSync4,
37038
37614
  renameSync as renameSync2
@@ -37292,7 +37868,7 @@ var init_storage = __esm({
37292
37868
  Storage = class {
37293
37869
  /** Read a Markdown file with frontmatter. Throws if not found. */
37294
37870
  read(path65) {
37295
- if (!existsSync19(path65)) {
37871
+ if (!existsSync20(path65)) {
37296
37872
  throw new Error(`File not found: ${path65}`);
37297
37873
  }
37298
37874
  const md = readFileSync16(path65, "utf8");
@@ -37300,7 +37876,7 @@ var init_storage = __esm({
37300
37876
  }
37301
37877
  /** Read a Markdown file; returns null if not found. */
37302
37878
  readIfExists(path65) {
37303
- if (!existsSync19(path65)) return null;
37879
+ if (!existsSync20(path65)) return null;
37304
37880
  return this.read(path65);
37305
37881
  }
37306
37882
  /**
@@ -37316,7 +37892,7 @@ var init_storage = __esm({
37316
37892
  }
37317
37893
  /** List all .md files in a directory (non-recursive). */
37318
37894
  listMarkdown(dir) {
37319
- if (!existsSync19(dir)) return [];
37895
+ if (!existsSync20(dir)) return [];
37320
37896
  return readdirSync4(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join14(dir, f));
37321
37897
  }
37322
37898
  };
@@ -37349,7 +37925,7 @@ var init_storage = __esm({
37349
37925
  // src/cli/workspace/planStore.ts
37350
37926
  import {
37351
37927
  copyFileSync,
37352
- existsSync as existsSync20,
37928
+ existsSync as existsSync21,
37353
37929
  mkdirSync as mkdirSync12,
37354
37930
  readFileSync as readFileSync17,
37355
37931
  renameSync as renameSync3,
@@ -37400,7 +37976,7 @@ function writePlanTaskArtifact(rootDir, task) {
37400
37976
  }
37401
37977
  function loadHandle(rootDir) {
37402
37978
  const jsonPath = join15(rootDir, "plan.json");
37403
- if (!existsSync20(jsonPath)) {
37979
+ if (!existsSync21(jsonPath)) {
37404
37980
  return { rootDir, tasks: [], counter: 0, rootFields: {} };
37405
37981
  }
37406
37982
  let parsed;
@@ -37435,7 +38011,7 @@ function saveHandle(rootDir, handle) {
37435
38011
  }
37436
38012
  const jsonPath = join15(rootDir, "plan.json");
37437
38013
  mkdirSync12(rootDir, { recursive: true });
37438
- if (existsSync20(jsonPath)) {
38014
+ if (existsSync21(jsonPath)) {
37439
38015
  copyFileSync(jsonPath, `${jsonPath}.bak`);
37440
38016
  }
37441
38017
  const file2 = {
@@ -37717,8 +38293,8 @@ var init_planTaskTools = __esm({
37717
38293
 
37718
38294
  // src/cli/tools/inspectTypecheckSafety.ts
37719
38295
  import { promises as fs16 } from "node:fs";
37720
- import path29 from "node:path";
37721
- import { spawn as spawn6 } from "node:child_process";
38296
+ import path31 from "node:path";
38297
+ import { spawn as spawn7 } from "node:child_process";
37722
38298
  async function scanTsbuildinfo(root) {
37723
38299
  const found = [];
37724
38300
  const stack = [root];
@@ -37731,11 +38307,11 @@ async function scanTsbuildinfo(root) {
37731
38307
  continue;
37732
38308
  }
37733
38309
  for (const entry of entries) {
37734
- const p3 = path29.join(dir, entry.name);
38310
+ const p3 = path31.join(dir, entry.name);
37735
38311
  if (entry.isDirectory()) {
37736
38312
  if (!SCAN_SKIP.has(entry.name)) stack.push(p3);
37737
38313
  } else if (entry.name.endsWith(".tsbuildinfo")) {
37738
- found.push(path29.relative(root, p3).split(path29.sep).join("/"));
38314
+ found.push(path31.relative(root, p3).split(path31.sep).join("/"));
37739
38315
  }
37740
38316
  }
37741
38317
  }
@@ -37744,7 +38320,7 @@ async function scanTsbuildinfo(root) {
37744
38320
  }
37745
38321
  async function gitStatusPorcelain(root) {
37746
38322
  return new Promise((resolve3) => {
37747
- const child = spawn6("git", ["status", "--porcelain"], { cwd: root, shell: false });
38323
+ const child = spawn7("git", ["status", "--porcelain"], { cwd: root, shell: false });
37748
38324
  let out = "";
37749
38325
  child.stdout.on("data", (d) => out += d.toString());
37750
38326
  child.stderr.on("data", (d) => out += d.toString());
@@ -37771,7 +38347,7 @@ async function cleanupArtifacts(root, relPaths) {
37771
38347
  const failed = [];
37772
38348
  for (const rel2 of relPaths) {
37773
38349
  try {
37774
- await fs16.unlink(path29.join(root, rel2));
38350
+ await fs16.unlink(path31.join(root, rel2));
37775
38351
  cleaned.push(rel2);
37776
38352
  } catch {
37777
38353
  failed.push(rel2);
@@ -37794,17 +38370,17 @@ var init_inspectTypecheckSafety = __esm({
37794
38370
  });
37795
38371
 
37796
38372
  // 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";
38373
+ import { spawn as spawn8 } from "node:child_process";
38374
+ import { createHash as createHash9 } from "node:crypto";
38375
+ import { existsSync as existsSync22, promises as fs17 } from "node:fs";
37800
38376
  import os8 from "node:os";
37801
- import path30 from "node:path";
38377
+ import path32 from "node:path";
37802
38378
  function resolveNodeModuleBin(start, rel2) {
37803
- let dir = path30.resolve(start);
38379
+ let dir = path32.resolve(start);
37804
38380
  for (; ; ) {
37805
- const candidate = path30.join(dir, "node_modules", rel2);
37806
- if (existsSync21(candidate)) return candidate;
37807
- const parent = path30.dirname(dir);
38381
+ const candidate = path32.join(dir, "node_modules", rel2);
38382
+ if (existsSync22(candidate)) return candidate;
38383
+ const parent = path32.dirname(dir);
37808
38384
  if (parent === dir) return void 0;
37809
38385
  dir = parent;
37810
38386
  }
@@ -37872,7 +38448,7 @@ function buildInspectCommand(op, ctx) {
37872
38448
  case "npm_ls":
37873
38449
  case "npm_outdated":
37874
38450
  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");
38451
+ 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
38452
  if (op.operation === "npm_view") {
37877
38453
  const err = rejectFlagLike("package", op.package);
37878
38454
  if (err) return { ok: false, reason: err };
@@ -37881,14 +38457,14 @@ function buildInspectCommand(op, ctx) {
37881
38457
  return { ok: true, command: process.execPath, argv: [npmCli, ...sub], inspectionClass: "env-info" };
37882
38458
  }
37883
38459
  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`);
38460
+ const project = path32.resolve(ctx.cwd, op.project ?? "tsconfig.json");
38461
+ const hash3 = createHash9("sha256").update(project).digest("hex").slice(0, 16);
38462
+ const tsBuildInfoFile = path32.join(os8.tmpdir(), "zelari-inspect", `${hash3}.tsbuildinfo`);
37887
38463
  return {
37888
38464
  ok: true,
37889
38465
  command: process.execPath,
37890
38466
  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"),
38467
+ 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
38468
  "--noEmit",
37893
38469
  // S3.5 primary mechanism: redirect, never disable — composite forces
37894
38470
  // incremental (TS#30661), so --incremental false would break on the
@@ -37910,7 +38486,7 @@ function runSpawn(command, argv, opts) {
37910
38486
  return new Promise((resolve3) => {
37911
38487
  let child;
37912
38488
  try {
37913
- child = spawn7(command, argv, { cwd: opts.cwd, shell: false });
38489
+ child = spawn8(command, argv, { cwd: opts.cwd, shell: false });
37914
38490
  } catch (err) {
37915
38491
  resolve3({ code: null, stdout: "", stderr: String(err), timedOut: false, spawnError: String(err) });
37916
38492
  return;
@@ -38648,9 +39224,9 @@ var init_client = __esm({
38648
39224
  });
38649
39225
 
38650
39226
  // src/cli/lsp/servers.ts
38651
- import path31 from "node:path";
39227
+ import path33 from "node:path";
38652
39228
  function languageIdForFile(file2) {
38653
- const ext = path31.extname(file2).toLowerCase();
39229
+ const ext = path33.extname(file2).toLowerCase();
38654
39230
  const map2 = {
38655
39231
  ".ts": "typescript",
38656
39232
  ".tsx": "typescriptreact",
@@ -38665,7 +39241,7 @@ function languageIdForFile(file2) {
38665
39241
  return map2[ext] ?? "plaintext";
38666
39242
  }
38667
39243
  function serverForFile(file2, servers = LSP_SERVERS) {
38668
- const ext = path31.extname(file2).toLowerCase();
39244
+ const ext = path33.extname(file2).toLowerCase();
38669
39245
  return servers.find((s) => s.extensions.includes(ext)) ?? null;
38670
39246
  }
38671
39247
  function resolveServerCommand(file2, cwd, servers = LSP_SERVERS) {
@@ -38714,7 +39290,7 @@ var init_servers = __esm({
38714
39290
  });
38715
39291
 
38716
39292
  // src/cli/lsp/manager.ts
38717
- import { spawn as spawn8 } from "node:child_process";
39293
+ import { spawn as spawn9 } from "node:child_process";
38718
39294
  import { readFileSync as readFileSync18 } from "node:fs";
38719
39295
  function processTransport(child) {
38720
39296
  return {
@@ -38850,7 +39426,7 @@ var init_manager = __esm({
38850
39426
  // languages already flagged as unavailable
38851
39427
  constructor(options = {}) {
38852
39428
  this.cwd = options.cwd ?? process.cwd();
38853
- this.spawnImpl = options.spawnImpl ?? spawn8;
39429
+ this.spawnImpl = options.spawnImpl ?? spawn9;
38854
39430
  this.timeoutMs = options.timeoutMs ?? 15e3;
38855
39431
  this.onWarn = options.onWarn ?? ((m) => console.error(m));
38856
39432
  }
@@ -39041,7 +39617,7 @@ var init_manager = __esm({
39041
39617
 
39042
39618
  // src/cli/ast/engine.ts
39043
39619
  import { readFile } from "node:fs/promises";
39044
- import path32 from "node:path";
39620
+ import path34 from "node:path";
39045
39621
  function loadTs() {
39046
39622
  if (!tsPromise) {
39047
39623
  tsPromise = import("typescript").then((m) => m.default ?? m).catch(() => null);
@@ -39052,8 +39628,8 @@ function errMessage(err) {
39052
39628
  return err instanceof Error ? err.message : String(err);
39053
39629
  }
39054
39630
  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();
39631
+ const resolvedPath = path34.isAbsolute(file2) ? file2 : path34.join(cwd ?? process.cwd(), file2);
39632
+ const extension = path34.extname(resolvedPath).toLowerCase();
39057
39633
  if (!TS_EXTENSIONS.has(extension)) {
39058
39634
  return {
39059
39635
  status: "unsupported-extension",
@@ -39095,7 +39671,7 @@ async function parseFileSymbolsDiag(file2, cwd) {
39095
39671
  }
39096
39672
  let source;
39097
39673
  try {
39098
- source = ts.createSourceFile(path32.basename(resolvedPath), text, ts.ScriptTarget.Latest, true);
39674
+ source = ts.createSourceFile(path34.basename(resolvedPath), text, ts.ScriptTarget.Latest, true);
39099
39675
  } catch (err) {
39100
39676
  return {
39101
39677
  status: "parse-error",
@@ -39318,13 +39894,13 @@ var init_store2 = __esm({
39318
39894
  });
39319
39895
 
39320
39896
  // src/cli/semantic/index.ts
39321
- import { promises as fs18, existsSync as existsSync22, readFileSync as readFileSync19 } from "node:fs";
39897
+ import { promises as fs18, existsSync as existsSync23, readFileSync as readFileSync19 } from "node:fs";
39322
39898
  import { homedir as homedir6 } from "node:os";
39323
- import path33 from "node:path";
39324
- import { createHash as createHash9 } from "node:crypto";
39899
+ import path35 from "node:path";
39900
+ import { createHash as createHash10 } from "node:crypto";
39325
39901
  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`);
39902
+ const hash3 = createHash10("sha1").update(path35.resolve(root)).digest("hex").slice(0, 16);
39903
+ return process.env.ZELARI_SEMANTIC_FILE ?? path35.join(homedir6(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
39328
39904
  }
39329
39905
  async function collectSourceFiles(root, maxFiles = 1500) {
39330
39906
  const out = [];
@@ -39342,11 +39918,11 @@ async function collectSourceFiles(root, maxFiles = 1500) {
39342
39918
  if (entry.isDirectory() && IGNORE_DIRS.has(entry.name)) continue;
39343
39919
  if (entry.isDirectory()) continue;
39344
39920
  }
39345
- const full = path33.join(dir, entry.name);
39921
+ const full = path35.join(dir, entry.name);
39346
39922
  if (entry.isDirectory()) {
39347
39923
  if (IGNORE_DIRS.has(entry.name)) continue;
39348
39924
  await walk2(full);
39349
- } else if (SOURCE_EXTENSIONS.has(path33.extname(entry.name).toLowerCase())) {
39925
+ } else if (SOURCE_EXTENSIONS.has(path35.extname(entry.name).toLowerCase())) {
39350
39926
  out.push(full);
39351
39927
  }
39352
39928
  }
@@ -39395,14 +39971,14 @@ async function buildIndex(files, embed, options) {
39395
39971
  }
39396
39972
  async function saveIndex(root, data) {
39397
39973
  const file2 = getIndexPath(root);
39398
- await fs18.mkdir(path33.dirname(file2), { recursive: true });
39974
+ await fs18.mkdir(path35.dirname(file2), { recursive: true });
39399
39975
  const tmp = `${file2}.tmp-${process.pid}`;
39400
39976
  await fs18.writeFile(tmp, JSON.stringify(data), "utf8");
39401
39977
  await fs18.rename(tmp, file2);
39402
39978
  }
39403
39979
  function loadIndex(root) {
39404
39980
  const file2 = getIndexPath(root);
39405
- if (!existsSync22(file2)) return null;
39981
+ if (!existsSync23(file2)) return null;
39406
39982
  try {
39407
39983
  const parsed = JSON.parse(readFileSync19(file2, "utf8"));
39408
39984
  if (parsed && Array.isArray(parsed.chunks)) return parsed;
@@ -39547,7 +40123,7 @@ var init_provider = __esm({
39547
40123
  });
39548
40124
 
39549
40125
  // src/cli/semantic/tools.ts
39550
- import path34 from "node:path";
40126
+ import path36 from "node:path";
39551
40127
  function createSemanticTool(deps) {
39552
40128
  const buildEmbedFn = deps.buildEmbedFn ?? buildProviderEmbedFn;
39553
40129
  return {
@@ -39574,7 +40150,7 @@ function createSemanticTool(deps) {
39574
40150
  return typedOk({
39575
40151
  count: res.hits.length,
39576
40152
  results: res.hits.map((h) => ({
39577
- location: `${path34.relative(deps.root, h.file) || h.file}:${h.startLine}-${h.endLine}`,
40153
+ location: `${path36.relative(deps.root, h.file) || h.file}:${h.startLine}-${h.endLine}`,
39578
40154
  score: Number(h.score.toFixed(3)),
39579
40155
  preview: h.text.length > 400 ? `${h.text.slice(0, 400)}\u2026` : h.text
39580
40156
  }))
@@ -39593,8 +40169,8 @@ var init_tools4 = __esm({
39593
40169
  });
39594
40170
 
39595
40171
  // src/cli/browser/driver.ts
39596
- import { createRequire } from "node:module";
39597
- import path35 from "node:path";
40172
+ import { createRequire as createRequire2 } from "node:module";
40173
+ import path37 from "node:path";
39598
40174
  import { pathToFileURL } from "node:url";
39599
40175
  function asPlaywright(mod) {
39600
40176
  if (!mod || typeof mod !== "object") return null;
@@ -39605,10 +40181,10 @@ function asPlaywright(mod) {
39605
40181
  return null;
39606
40182
  }
39607
40183
  async function loadPlaywright(cwd) {
39608
- const base = cwd && cwd.length > 0 ? path35.resolve(cwd) : void 0;
40184
+ const base = cwd && cwd.length > 0 ? path37.resolve(cwd) : void 0;
39609
40185
  if (base) {
39610
40186
  try {
39611
- const req = createRequire(path35.join(base, "package.json"));
40187
+ const req = createRequire2(path37.join(base, "package.json"));
39612
40188
  const resolved = req.resolve("playwright");
39613
40189
  const mod = await import(pathToFileURL(resolved).href);
39614
40190
  const pw = asPlaywright(mod);
@@ -39823,7 +40399,7 @@ var init_driver = __esm({
39823
40399
  });
39824
40400
 
39825
40401
  // src/cli/browser/tools.ts
39826
- import path36 from "node:path";
40402
+ import path38 from "node:path";
39827
40403
  import os9 from "node:os";
39828
40404
  function createBrowserTool(deps = {}) {
39829
40405
  return {
@@ -39842,7 +40418,7 @@ function createBrowserTool(deps = {}) {
39842
40418
  execute: async (args, ctx) => {
39843
40419
  const a = args;
39844
40420
  const dir = deps.screenshotDir ?? os9.tmpdir();
39845
- const screenshotPath = a.screenshot === false ? void 0 : path36.join(dir, `zelari-browser-${Date.now()}.png`);
40421
+ const screenshotPath = a.screenshot === false ? void 0 : path38.join(dir, `zelari-browser-${Date.now()}.png`);
39846
40422
  const result = await runBrowserCheck(
39847
40423
  {
39848
40424
  url: a.url,
@@ -39934,14 +40510,14 @@ __export(targets_exports, {
39934
40510
  });
39935
40511
  import {
39936
40512
  chmodSync,
39937
- existsSync as existsSync23,
40513
+ existsSync as existsSync24,
39938
40514
  mkdirSync as mkdirSync13,
39939
40515
  readFileSync as readFileSync20,
39940
40516
  writeFileSync as writeFileSync15
39941
40517
  } from "node:fs";
39942
40518
  import { dirname as dirname4, join as join16 } from "node:path";
39943
40519
  import { homedir as homedir7 } from "node:os";
39944
- import { spawn as spawn9 } from "node:child_process";
40520
+ import { spawn as spawn10 } from "node:child_process";
39945
40521
  function getSshTargetsPath() {
39946
40522
  return join16(homedir7(), ".zelari-code", "ssh-targets.json");
39947
40523
  }
@@ -39955,7 +40531,7 @@ function normalizeAuth(auth) {
39955
40531
  }
39956
40532
  function readSecrets() {
39957
40533
  const path65 = getSshSecretsPath();
39958
- if (!existsSync23(path65)) return {};
40534
+ if (!existsSync24(path65)) return {};
39959
40535
  try {
39960
40536
  return JSON.parse(readFileSync20(path65, "utf8"));
39961
40537
  } catch {
@@ -39998,7 +40574,7 @@ function deleteSshPassword(id) {
39998
40574
  }
39999
40575
  function readStore2() {
40000
40576
  const path65 = getSshTargetsPath();
40001
- if (!existsSync23(path65)) return [];
40577
+ if (!existsSync24(path65)) return [];
40002
40578
  try {
40003
40579
  const parsed = JSON.parse(readFileSync20(path65, "utf8"));
40004
40580
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
@@ -40171,7 +40747,7 @@ function runSsh(target, remoteCommand, timeoutMs2 = 6e4) {
40171
40747
  if (!env.DISPLAY) env.DISPLAY = "1";
40172
40748
  env.ZELARI_SSH_ASKPASS_PASS = pass;
40173
40749
  }
40174
- const child = spawn9("ssh", args, {
40750
+ const child = spawn10("ssh", args, {
40175
40751
  windowsHide: true,
40176
40752
  env,
40177
40753
  stdio: ["ignore", "pipe", "pipe"]
@@ -40212,7 +40788,7 @@ function readSshPublicKey(keyOrPubPath) {
40212
40788
  if (!raw) return { ok: false, error: "Empty path" };
40213
40789
  const candidates = raw.endsWith(".pub") ? [raw] : [`${raw}.pub`, raw];
40214
40790
  for (const p3 of candidates) {
40215
- if (!existsSync23(p3)) continue;
40791
+ if (!existsSync24(p3)) continue;
40216
40792
  try {
40217
40793
  const content = readFileSync20(p3, "utf8").trim();
40218
40794
  if (!content) continue;
@@ -40396,10 +40972,10 @@ var init_tools6 = __esm({
40396
40972
 
40397
40973
  // src/cli/workspace/worldModel.ts
40398
40974
  import { promises as fs19 } from "node:fs";
40399
- import path37 from "node:path";
40400
- import { spawn as spawn10 } from "node:child_process";
40975
+ import path39 from "node:path";
40976
+ import { spawn as spawn11 } from "node:child_process";
40401
40977
  function worldDir(cwd) {
40402
- return path37.join(cwd, WORLD_DIR_NAME);
40978
+ return path39.join(cwd, WORLD_DIR_NAME);
40403
40979
  }
40404
40980
  async function ensureWorldDir(cwd) {
40405
40981
  const dir = worldDir(cwd);
@@ -40409,10 +40985,10 @@ async function ensureWorldDir(cwd) {
40409
40985
  async function appendTimeline(cwd, entry) {
40410
40986
  const dir = await ensureWorldDir(cwd);
40411
40987
  const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }) + "\n";
40412
- await fs19.appendFile(path37.join(dir, TIMELINE_FILE), line, "utf8");
40988
+ await fs19.appendFile(path39.join(dir, TIMELINE_FILE), line, "utf8");
40413
40989
  }
40414
40990
  async function readChecks(cwd) {
40415
- const p3 = path37.join(worldDir(cwd), CHECKS_FILE);
40991
+ const p3 = path39.join(worldDir(cwd), CHECKS_FILE);
40416
40992
  try {
40417
40993
  const raw = await fs19.readFile(p3, "utf8");
40418
40994
  const parsed = JSON.parse(raw);
@@ -40424,7 +41000,7 @@ async function readChecks(cwd) {
40424
41000
  function runShell(command, cwd, timeoutMs2, signal) {
40425
41001
  return new Promise((resolve3) => {
40426
41002
  const isWin = process.platform === "win32";
40427
- const child = spawn10(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
41003
+ const child = spawn11(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
40428
41004
  cwd,
40429
41005
  env: process.env,
40430
41006
  windowsHide: true,
@@ -40487,8 +41063,8 @@ function runShell(command, cwd, timeoutMs2, signal) {
40487
41063
  });
40488
41064
  }
40489
41065
  async function runBacktest(cwd, signal) {
40490
- const checksPath = path37.join(worldDir(cwd), CHECKS_FILE);
40491
- const hypothesisPath = path37.join(worldDir(cwd), HYPOTHESIS_FILE);
41066
+ const checksPath = path39.join(worldDir(cwd), CHECKS_FILE);
41067
+ const hypothesisPath = path39.join(worldDir(cwd), HYPOTHESIS_FILE);
40492
41068
  const checks = await readChecks(cwd);
40493
41069
  if (checks.length === 0) {
40494
41070
  return {
@@ -40557,7 +41133,7 @@ var init_worldModel = __esm({
40557
41133
  "use strict";
40558
41134
  init_zod();
40559
41135
  init_toolTypes();
40560
- WORLD_DIR_NAME = path37.join(".zelari", "world");
41136
+ WORLD_DIR_NAME = path39.join(".zelari", "world");
40561
41137
  HYPOTHESIS_FILE = "hypothesis.md";
40562
41138
  CHECKS_FILE = "checks.json";
40563
41139
  TIMELINE_FILE = "timeline.jsonl";
@@ -40574,7 +41150,7 @@ var init_worldModel = __esm({
40574
41150
  execute: async (args, ctx) => {
40575
41151
  try {
40576
41152
  const dir = await ensureWorldDir(ctx.cwd);
40577
- const file2 = path37.join(dir, HYPOTHESIS_FILE);
41153
+ const file2 = path39.join(dir, HYPOTHESIS_FILE);
40578
41154
  if (args.append) {
40579
41155
  const block = `
40580
41156
 
@@ -40613,7 +41189,7 @@ ${args.content}
40613
41189
  execute: async (args, ctx) => {
40614
41190
  try {
40615
41191
  const dir = await ensureWorldDir(ctx.cwd);
40616
- const file2 = path37.join(dir, CHECKS_FILE);
41192
+ const file2 = path39.join(dir, CHECKS_FILE);
40617
41193
  const body = { checks: args.checks };
40618
41194
  await fs19.writeFile(file2, JSON.stringify(body, null, 2) + "\n", "utf8");
40619
41195
  await appendTimeline(ctx.cwd, { kind: "checks_set", count: args.checks.length });
@@ -40652,8 +41228,8 @@ ${args.content}
40652
41228
  stdoutPreview: "(dryRun)",
40653
41229
  mismatch: "dryRun"
40654
41230
  })),
40655
- hypothesisPath: path37.join(worldDir(ctx.cwd), HYPOTHESIS_FILE),
40656
- checksPath: path37.join(worldDir(ctx.cwd), CHECKS_FILE)
41231
+ hypothesisPath: path39.join(worldDir(ctx.cwd), HYPOTHESIS_FILE),
41232
+ checksPath: path39.join(worldDir(ctx.cwd), CHECKS_FILE)
40657
41233
  });
40658
41234
  }
40659
41235
  const result = await runBacktest(ctx.cwd, ctx.signal);
@@ -40677,7 +41253,7 @@ ${args.content}
40677
41253
  execute: async (args, ctx) => {
40678
41254
  try {
40679
41255
  const dir = await ensureWorldDir(ctx.cwd);
40680
- const file2 = path37.join(dir, TIMELINE_FILE);
41256
+ const file2 = path39.join(dir, TIMELINE_FILE);
40681
41257
  await appendTimeline(ctx.cwd, {
40682
41258
  kind: args.kind,
40683
41259
  summary: args.summary,
@@ -40785,13 +41361,13 @@ __export(folderTrust_exports, {
40785
41361
  untrustFolder: () => untrustFolder
40786
41362
  });
40787
41363
  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";
41364
+ import { existsSync as existsSync25, mkdirSync as mkdirSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "node:fs";
41365
+ import path40 from "node:path";
40790
41366
  function trustStorePath() {
40791
- return _overrideStorePath ?? path38.join(homedir8(), ".zelari-code", "trust.json");
41367
+ return _overrideStorePath ?? path40.join(homedir8(), ".zelari-code", "trust.json");
40792
41368
  }
40793
41369
  function normalize4(p3) {
40794
- const resolved = path38.resolve(p3);
41370
+ const resolved = path40.resolve(p3);
40795
41371
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
40796
41372
  }
40797
41373
  function readStore3() {
@@ -40807,7 +41383,7 @@ function readStore3() {
40807
41383
  function writeStore3(store6) {
40808
41384
  const p3 = trustStorePath();
40809
41385
  try {
40810
- mkdirSync14(path38.dirname(p3), { recursive: true });
41386
+ mkdirSync14(path40.dirname(p3), { recursive: true });
40811
41387
  writeFileSync16(p3, JSON.stringify(store6, null, 2), "utf8");
40812
41388
  } catch (err) {
40813
41389
  throw new Error(
@@ -40833,7 +41409,7 @@ function isFolderTrusted(folderPath) {
40833
41409
  }
40834
41410
  function trustFolder(folderPath) {
40835
41411
  const store6 = readStore3();
40836
- const normalized = path38.resolve(folderPath);
41412
+ const normalized = path40.resolve(folderPath);
40837
41413
  if (!store6.folders.some((f) => normalize4(f.path) === normalize4(normalized))) {
40838
41414
  store6.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
40839
41415
  writeStore3(store6);
@@ -40863,7 +41439,7 @@ function getTrustStorePath() {
40863
41439
  return trustStorePath();
40864
41440
  }
40865
41441
  function hasTrustStore() {
40866
- return existsSync24(trustStorePath());
41442
+ return existsSync25(trustStorePath());
40867
41443
  }
40868
41444
  function _setTrustStorePathForTests(p3) {
40869
41445
  _overrideStorePath = p3;
@@ -40947,9 +41523,9 @@ var init_lifecycleHooks = __esm({
40947
41523
  });
40948
41524
 
40949
41525
  // src/cli/toolResultCache.ts
40950
- import { createHash as createHash10 } from "node:crypto";
41526
+ import { createHash as createHash11 } from "node:crypto";
40951
41527
  import { promises as fs20 } from "node:fs";
40952
- import path39 from "node:path";
41528
+ import path41 from "node:path";
40953
41529
  function isToolCacheEnabled() {
40954
41530
  const raw = process.env.ZELARI_TOOL_CACHE;
40955
41531
  return raw !== "0" && raw !== "false" && raw !== "off";
@@ -40960,7 +41536,7 @@ function resolveToolCacheTtlMs() {
40960
41536
  return Number.isFinite(n) && n >= 0 ? n : TOOL_CACHE_DEFAULT_TTL_MS;
40961
41537
  }
40962
41538
  function hashKey(parts) {
40963
- return createHash10("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
41539
+ return createHash11("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
40964
41540
  }
40965
41541
  function resultBytes(result) {
40966
41542
  try {
@@ -41034,7 +41610,7 @@ async function statKey(toolName, input, ctx) {
41034
41610
  if (!input || typeof input !== "object") return null;
41035
41611
  const rawPath = input.path;
41036
41612
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
41037
- const abs = path39.isAbsolute(rawPath) ? rawPath : path39.join(ctx.cwd, rawPath);
41613
+ const abs = path41.isAbsolute(rawPath) ? rawPath : path41.join(ctx.cwd, rawPath);
41038
41614
  try {
41039
41615
  const st = await fs20.stat(abs);
41040
41616
  return hashKey({
@@ -41715,14 +42291,14 @@ var init_toolRegistry = __esm({
41715
42291
  });
41716
42292
 
41717
42293
  // src/cli/state/fileStateStore.ts
41718
- import { createHash as createHash12, randomUUID as randomUUID2 } from "node:crypto";
42294
+ import { createHash as createHash13, randomUUID as randomUUID2 } from "node:crypto";
41719
42295
  import { promises as fs21 } from "node:fs";
41720
- import * as path41 from "node:path";
42296
+ import * as path43 from "node:path";
41721
42297
  function shortId() {
41722
42298
  return randomUUID2().replace(/-/g, "").slice(0, 12);
41723
42299
  }
41724
42300
  async function writeJsonAtomic(filePath, data) {
41725
- await fs21.mkdir(path41.dirname(filePath), { recursive: true });
42301
+ await fs21.mkdir(path43.dirname(filePath), { recursive: true });
41726
42302
  const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
41727
42303
  await fs21.writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
41728
42304
  await fs21.rename(tmp, filePath);
@@ -41767,7 +42343,7 @@ async function getStateStore(projectRoot, env = process.env) {
41767
42343
  }
41768
42344
  }
41769
42345
  function hashStablePrompt(stable) {
41770
- return createHash12("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
42346
+ return createHash13("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
41771
42347
  }
41772
42348
  var DEFAULT_MATERIALIZE_CHARS, FileDurableStateStore, NoopDurableStateStore;
41773
42349
  var init_fileStateStore = __esm({
@@ -41783,11 +42359,11 @@ var init_fileStateStore = __esm({
41783
42359
  indexPath = "";
41784
42360
  async init(projectRoot) {
41785
42361
  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");
42362
+ this.stateDir = path43.join(projectRoot, ".zelari", "state");
42363
+ this.commitsDir = path43.join(this.stateDir, "commits");
42364
+ this.artifactsDir = path43.join(this.stateDir, "artifacts");
42365
+ this.headPath = path43.join(this.stateDir, "HEAD.json");
42366
+ this.indexPath = path43.join(this.stateDir, "index.jsonl");
41791
42367
  await fs21.mkdir(this.commitsDir, { recursive: true });
41792
42368
  await fs21.mkdir(this.artifactsDir, { recursive: true });
41793
42369
  }
@@ -41800,13 +42376,13 @@ var init_fileStateStore = __esm({
41800
42376
  const discoveries = input.discoveries ?? [];
41801
42377
  const parent = await this.head();
41802
42378
  const id = shortId();
41803
- const artifactRel = path41.join("artifacts", id);
41804
- const artifactAbs = path41.join(this.artifactsDir, id);
42379
+ const artifactRel = path43.join("artifacts", id);
42380
+ const artifactAbs = path43.join(this.artifactsDir, id);
41805
42381
  await fs21.mkdir(artifactAbs, { recursive: true });
41806
42382
  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);
42383
+ await fs21.writeFile(path43.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
42384
+ await writeJsonAtomic(path43.join(artifactAbs, "discoveries.json"), discoveries);
42385
+ await writeJsonAtomic(path43.join(artifactAbs, "verification.json"), input.verification);
41810
42386
  const meta3 = {
41811
42387
  id,
41812
42388
  parentId: parent?.id ?? null,
@@ -41818,14 +42394,14 @@ var init_fileStateStore = __esm({
41818
42394
  workspaceCheckpointId: input.workspaceCheckpointId,
41819
42395
  verification: {
41820
42396
  ...input.verification,
41821
- reportPath: input.verification.reportPath ?? path41.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
42397
+ reportPath: input.verification.reportPath ?? path43.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
41822
42398
  },
41823
42399
  changedPaths: input.changedPaths ?? [],
41824
42400
  stablePromptHash: input.stablePromptHash,
41825
42401
  discoveryCount: discoveries.length,
41826
42402
  artifactDir: artifactRel.replace(/\\/g, "/")
41827
42403
  };
41828
- await writeJsonAtomic(path41.join(this.commitsDir, `${id}.json`), meta3);
42404
+ await writeJsonAtomic(path43.join(this.commitsDir, `${id}.json`), meta3);
41829
42405
  await writeJsonAtomic(this.headPath, { id, updatedAt: meta3.createdAt });
41830
42406
  await fs21.appendFile(this.indexPath, JSON.stringify({ id, createdAt: meta3.createdAt, label: meta3.label }) + "\n", "utf8");
41831
42407
  return stripStored(meta3);
@@ -41836,7 +42412,7 @@ var init_fileStateStore = __esm({
41836
42412
  return this.get(head.id);
41837
42413
  }
41838
42414
  async get(id) {
41839
- const stored = await readJsonFile(path41.join(this.commitsDir, `${id}.json`));
42415
+ const stored = await readJsonFile(path43.join(this.commitsDir, `${id}.json`));
41840
42416
  return stored ? stripStored(stored) : null;
41841
42417
  }
41842
42418
  async list(limit = 20) {
@@ -41875,9 +42451,9 @@ var init_fileStateStore = __esm({
41875
42451
  async loadDiscoveries(id) {
41876
42452
  const meta3 = id ? await this.get(id) : await this.head();
41877
42453
  if (!meta3) return [];
41878
- const stored = await readJsonFile(path41.join(this.commitsDir, `${meta3.id}.json`));
42454
+ const stored = await readJsonFile(path43.join(this.commitsDir, `${meta3.id}.json`));
41879
42455
  if (!stored?.artifactDir) return [];
41880
- const discPath = path41.join(this.stateDir, stored.artifactDir, "discoveries.json");
42456
+ const discPath = path43.join(this.stateDir, stored.artifactDir, "discoveries.json");
41881
42457
  return await readJsonFile(discPath) ?? [];
41882
42458
  }
41883
42459
  async materializeContext(id, maxChars = DEFAULT_MATERIALIZE_CHARS) {
@@ -42531,7 +43107,7 @@ __export(conversationContext_exports, {
42531
43107
  setHistory: () => setHistory,
42532
43108
  setLastClarification: () => setLastClarification
42533
43109
  });
42534
- import { existsSync as existsSync25 } from "node:fs";
43110
+ import { existsSync as existsSync26 } from "node:fs";
42535
43111
  import { join as join19 } from "node:path";
42536
43112
  function getHistory() {
42537
43113
  return history;
@@ -42541,7 +43117,7 @@ function setHistory(messages) {
42541
43117
  history = projected === messages ? [...messages] : projected;
42542
43118
  }
42543
43119
  function compactInPlace(cwd = process.cwd()) {
42544
- const durableStatePresent = existsSync25(join19(cwd, ".zelari", "state", "HEAD.json"));
43120
+ const durableStatePresent = existsSync26(join19(cwd, ".zelari", "state", "HEAD.json"));
42545
43121
  history = applySessionSurface(compactHistory(history, { durableStatePresent }));
42546
43122
  }
42547
43123
  function appendMessages(msgs) {
@@ -43124,7 +43700,6 @@ __export(headlessSpine_exports, {
43124
43700
  seedHeadlessModelHistory: () => seedHeadlessModelHistory,
43125
43701
  sessionStartedEvent: () => sessionStartedEvent
43126
43702
  });
43127
- import path42 from "node:path";
43128
43703
  function sessionStartedEvent(handle) {
43129
43704
  return {
43130
43705
  type: "session_started",
@@ -43158,12 +43733,10 @@ async function openHeadlessSpine(opts) {
43158
43733
  if (spine.status === "active") {
43159
43734
  const budget = new BudgetRuntime(profileId, { enforcement: resolveResourceEnforcement() });
43160
43735
  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);
43736
+ await restoreBudgetRuntimeFromSession(budget, opts.sessionId, opts.baseDir);
43165
43737
  }
43166
43738
  spine.attachBudgetRuntime(budget);
43739
+ await noteHarnessLifecycle(spine, opts.sessionId, profileId, budget, opts.baseDir);
43167
43740
  spine.note("headless.profile", { profile: profileId, mode: opts.mode ?? "kraken" });
43168
43741
  }
43169
43742
  return {
@@ -43178,8 +43751,14 @@ async function openHeadlessSpine(opts) {
43178
43751
  userMessage(text) {
43179
43752
  spine.userMessage(text);
43180
43753
  },
43181
- gateResourceToolCall(toolName) {
43182
- return spine.gateResourceToolCall(toolName);
43754
+ gateResourceToolCall(toolName, args) {
43755
+ return spine.gateResourceToolCall(toolName, args);
43756
+ },
43757
+ resourceBudgetLimit() {
43758
+ return spine.resourceBudgetLimit();
43759
+ },
43760
+ resourceBudgetSummary() {
43761
+ return spine.resourceBudgetSummary();
43183
43762
  },
43184
43763
  verificationRun(payload) {
43185
43764
  spine.verificationRun(payload);
@@ -43297,10 +43876,11 @@ var init_headlessSpine = __esm({
43297
43876
  init_dist();
43298
43877
  init_session();
43299
43878
  init_mission2();
43300
- init_session();
43301
43879
  init_runtime2();
43302
43880
  init_sessionSpine();
43303
43881
  init_budgetRuntime();
43882
+ init_restoreRuntime();
43883
+ init_sessionSpine();
43304
43884
  init_headless();
43305
43885
  }
43306
43886
  });
@@ -43444,7 +44024,7 @@ var claudeProvider_exports = {};
43444
44024
  __export(claudeProvider_exports, {
43445
44025
  createLocalCliProvider: () => createLocalCliProvider
43446
44026
  });
43447
- import { spawn as spawn11 } from "node:child_process";
44027
+ import { spawn as spawn12 } from "node:child_process";
43448
44028
  function waitForExit(child, timeoutMs2 = 2e3) {
43449
44029
  return new Promise((resolve3) => {
43450
44030
  if (child.exitCode != null) return resolve3(child.exitCode);
@@ -43473,7 +44053,7 @@ function createLocalCliProvider(opts = {}) {
43473
44053
  );
43474
44054
  }
43475
44055
  }
43476
- const spawnFn = opts.spawnFn ?? spawn11;
44056
+ const spawnFn = opts.spawnFn ?? spawn12;
43477
44057
  let child;
43478
44058
  try {
43479
44059
  child = spawnFn(cli, args, {
@@ -43568,12 +44148,12 @@ var init_claudeProvider = __esm({
43568
44148
  });
43569
44149
 
43570
44150
  // src/cli/workspace/projectInstructions.ts
43571
- import { existsSync as existsSync26, readFileSync as readFileSync23 } from "node:fs";
44151
+ import { existsSync as existsSync27, readFileSync as readFileSync23 } from "node:fs";
43572
44152
  import { join as join20 } from "node:path";
43573
44153
  function loadProjectInstructions(projectRoot = process.cwd(), maxChars = MAX_CHARS) {
43574
44154
  for (const name of CANDIDATES) {
43575
44155
  const full = join20(projectRoot, name);
43576
- if (!existsSync26(full)) continue;
44156
+ if (!existsSync27(full)) continue;
43577
44157
  try {
43578
44158
  let raw = readFileSync23(full, "utf8");
43579
44159
  raw = raw.replace(/\r\n/g, "\n").trim();
@@ -43619,7 +44199,7 @@ __export(workspaceSummary_exports, {
43619
44199
  buildWorkspaceSummary: () => buildWorkspaceSummary,
43620
44200
  buildZelariReadHint: () => buildZelariReadHint
43621
44201
  });
43622
- import { existsSync as existsSync27, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
44202
+ import { existsSync as existsSync28, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
43623
44203
  import { join as join21, relative } from "node:path";
43624
44204
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
43625
44205
  const { maxEntries = 30, maxChars = 3500, maxDeps = 24, maxScripts = 16 } = options;
@@ -43654,7 +44234,7 @@ function formatTaskLine(t) {
43654
44234
  function buildPlanSummary(projectRoot = process.cwd(), options) {
43655
44235
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
43656
44236
  const planPath = join21(zelariRoot, "plan.json");
43657
- if (!existsSync27(planPath)) return null;
44237
+ if (!existsSync28(planPath)) return null;
43658
44238
  let plan;
43659
44239
  try {
43660
44240
  plan = JSON.parse(readFileSync24(planPath, "utf8"));
@@ -43797,7 +44377,7 @@ function pickNextTask(open) {
43797
44377
  }
43798
44378
  function buildZelariReadHint(projectRoot = process.cwd()) {
43799
44379
  const planPath = join21(resolveWorkspaceRoot(projectRoot), "plan.json");
43800
- if (!existsSync27(planPath)) return "";
44380
+ if (!existsSync28(planPath)) return "";
43801
44381
  return [
43802
44382
  "# Council workspace detected (.zelari/) \u2014 DRAFT vault",
43803
44383
  "`.zelari/plan.json` and `.zelari/docs/` hold **design hypotheses**, not verified product state.",
@@ -43813,7 +44393,7 @@ function safeProjectName(root) {
43813
44393
  }
43814
44394
  function readPackageJson(projectRoot) {
43815
44395
  const p3 = join21(projectRoot, "package.json");
43816
- if (!existsSync27(p3)) return null;
44396
+ if (!existsSync28(p3)) return null;
43817
44397
  try {
43818
44398
  return JSON.parse(readFileSync24(p3, "utf8"));
43819
44399
  } catch {
@@ -43917,12 +44497,12 @@ var init_workspaceSummary = __esm({
43917
44497
  });
43918
44498
 
43919
44499
  // src/cli/workspace/buildLessonsSummary.ts
43920
- import { existsSync as existsSync28 } from "node:fs";
44500
+ import { existsSync as existsSync29 } from "node:fs";
43921
44501
  import { join as join22 } from "node:path";
43922
44502
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
43923
44503
  if (process.env["ZELARI_LESSONS"] === "0") return null;
43924
44504
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
43925
- if (!existsSync28(join22(zelariRoot, "lessons.jsonl"))) return null;
44505
+ if (!existsSync29(join22(zelariRoot, "lessons.jsonl"))) return null;
43926
44506
  const lessons = recallLessons(zelariRoot, {
43927
44507
  maxLessons: 5,
43928
44508
  maxBytes: 2048,
@@ -43943,7 +44523,7 @@ var composeContext_exports = {};
43943
44523
  __export(composeContext_exports, {
43944
44524
  composeProjectContext: () => composeProjectContext
43945
44525
  });
43946
- import { existsSync as existsSync29, readdirSync as readdirSync7, readFileSync as readFileSync25 } from "node:fs";
44526
+ import { existsSync as existsSync30, readdirSync as readdirSync7, readFileSync as readFileSync25 } from "node:fs";
43947
44527
  import { join as join23 } from "node:path";
43948
44528
  function cap2(text, max, label) {
43949
44529
  if (!text || text.length <= max) return { text: text || "", truncated: false };
@@ -43956,13 +44536,13 @@ function cap2(text, max, label) {
43956
44536
  }
43957
44537
  function buildDesignIndex(projectRoot, maxChars) {
43958
44538
  const root = resolveWorkspaceRoot(projectRoot);
43959
- if (!existsSync29(root)) return "";
44539
+ if (!existsSync30(root)) return "";
43960
44540
  const lines = [
43961
44541
  "# Design vault index (.zelari/) \u2014 HYPOTHESES only",
43962
44542
  "Full design docs are NOT product source of truth. Open with list_files / read_file / searchDocuments if needed."
43963
44543
  ];
43964
44544
  const docsDir = join23(root, "docs");
43965
- if (existsSync29(docsDir)) {
44545
+ if (existsSync30(docsDir)) {
43966
44546
  try {
43967
44547
  const docs = readdirSync7(docsDir).filter((n) => n.endsWith(".md")).slice(0, 12);
43968
44548
  if (docs.length > 0) {
@@ -43976,12 +44556,12 @@ function buildDesignIndex(projectRoot, maxChars) {
43976
44556
  }
43977
44557
  }
43978
44558
  for (const name of ["risks.md", "plan.json", "nfr-spec.json"]) {
43979
- if (existsSync29(join23(root, name))) {
44559
+ if (existsSync30(join23(root, name))) {
43980
44560
  lines.push(`- .zelari/${name} present`);
43981
44561
  }
43982
44562
  }
43983
44563
  const decisionsDir = join23(root, "decisions");
43984
- if (existsSync29(decisionsDir)) {
44564
+ if (existsSync30(decisionsDir)) {
43985
44565
  try {
43986
44566
  const n = readdirSync7(decisionsDir).filter((f) => f.endsWith(".md")).length;
43987
44567
  if (n > 0) lines.push(`- .zelari/decisions/ (${n} ADR file(s) \u2014 treat proposed as non-binding)`);
@@ -44083,15 +44663,15 @@ function composeProjectContext(input) {
44083
44663
  function readDurableHeadSync(projectRoot) {
44084
44664
  try {
44085
44665
  const headPath = join23(projectRoot, ".zelari", "state", "HEAD.json");
44086
- if (!existsSync29(headPath)) return "";
44666
+ if (!existsSync30(headPath)) return "";
44087
44667
  const head = JSON.parse(readFileSync25(headPath, "utf8"));
44088
44668
  if (!head?.id) return "";
44089
44669
  const metaPath = join23(projectRoot, ".zelari", "state", "commits", `${head.id}.json`);
44090
- if (!existsSync29(metaPath)) return "";
44670
+ if (!existsSync30(metaPath)) return "";
44091
44671
  const meta3 = JSON.parse(readFileSync25(metaPath, "utf8"));
44092
44672
  const discPath = meta3.artifactDir ? join23(projectRoot, ".zelari", "state", meta3.artifactDir, "discoveries.json") : join23(projectRoot, ".zelari", "state", "artifacts", head.id, "discoveries.json");
44093
44673
  let discoveries = [];
44094
- if (existsSync29(discPath)) {
44674
+ if (existsSync30(discPath)) {
44095
44675
  discoveries = JSON.parse(readFileSync25(discPath, "utf8"));
44096
44676
  }
44097
44677
  const reusable = discoveries.filter((d) => d.reusable !== false);
@@ -44125,11 +44705,11 @@ var planDetect_exports = {};
44125
44705
  __export(planDetect_exports, {
44126
44706
  hasWorkspacePlan: () => hasWorkspacePlan
44127
44707
  });
44128
- import { existsSync as existsSync30, readFileSync as readFileSync26 } from "node:fs";
44708
+ import { existsSync as existsSync31, readFileSync as readFileSync26 } from "node:fs";
44129
44709
  import { join as join24 } from "node:path";
44130
44710
  function hasWorkspacePlan(projectRoot = process.cwd()) {
44131
44711
  const planPath = join24(resolveWorkspaceRoot(projectRoot), "plan.json");
44132
- if (!existsSync30(planPath)) return false;
44712
+ if (!existsSync31(planPath)) return false;
44133
44713
  try {
44134
44714
  const parsed = JSON.parse(readFileSync26(planPath, "utf8"));
44135
44715
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
@@ -44190,7 +44770,7 @@ __export(stubs_exports, {
44190
44770
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
44191
44771
  });
44192
44772
  import {
44193
- existsSync as existsSync31,
44773
+ existsSync as existsSync32,
44194
44774
  readdirSync as readdirSync8,
44195
44775
  writeFileSync as writeFileSync17,
44196
44776
  readFileSync as readFileSync27,
@@ -44211,7 +44791,7 @@ function planJsonPath(ctx) {
44211
44791
  }
44212
44792
  function readPlan(ctx) {
44213
44793
  const jsonPath = planJsonPath(ctx);
44214
- if (existsSync31(jsonPath)) {
44794
+ if (existsSync32(jsonPath)) {
44215
44795
  try {
44216
44796
  const parsed = JSON.parse(
44217
44797
  readFileSync27(jsonPath, "utf8")
@@ -44321,7 +44901,7 @@ function renderPlanBody(summary) {
44321
44901
  }
44322
44902
  function nextAdrId(ctx) {
44323
44903
  const decisionsDir = join25(ctx.rootDir, "decisions");
44324
- if (!existsSync31(decisionsDir)) return "001";
44904
+ if (!existsSync32(decisionsDir)) return "001";
44325
44905
  const existing = readdirSync8(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
44326
44906
  const max = existing.length === 0 ? 0 : Math.max(...existing);
44327
44907
  return String(max + 1).padStart(3, "0");
@@ -44777,7 +45357,7 @@ function searchDocumentsStub(ctx) {
44777
45357
  ];
44778
45358
  const results = [];
44779
45359
  for (const file2 of files) {
44780
- if (!existsSync31(file2)) continue;
45360
+ if (!existsSync32(file2)) continue;
44781
45361
  const raw = readFileSync27(file2, "utf8");
44782
45362
  const content = raw.toLowerCase();
44783
45363
  let idx = -1;
@@ -44945,176 +45525,6 @@ var init_toolRegistry2 = __esm({
44945
45525
  }
44946
45526
  });
44947
45527
 
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
45528
  // src/cli/mcp/mcpClient.ts
45119
45529
  import { spawn as spawn13 } from "node:child_process";
45120
45530
  var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
@@ -45784,7 +46194,7 @@ __export(agentsMd_exports, {
45784
46194
  updateAgentsMd: () => updateAgentsMd
45785
46195
  });
45786
46196
  import { existsSync as existsSync35, readFileSync as readFileSync30, writeFileSync as writeFileSync19 } from "node:fs";
45787
- import { createHash as createHash13 } from "node:crypto";
46197
+ import { createHash as createHash14 } from "node:crypto";
45788
46198
  import { join as join28 } from "node:path";
45789
46199
  import { readFile as readFile4 } from "node:fs/promises";
45790
46200
  async function readPackageJson2(projectRoot) {
@@ -45999,7 +46409,7 @@ async function updateAgentsMd(ctx, projectRoot) {
45999
46409
  return { changed: true, sections: changedSections };
46000
46410
  }
46001
46411
  function hash2(s) {
46002
- return createHash13("sha256").update(s).digest("hex").slice(0, 16);
46412
+ return createHash14("sha256").update(s).digest("hex").slice(0, 16);
46003
46413
  }
46004
46414
  var AUTO_SECTIONS, MARKER_OPEN, MARKER_CLOSE, GENERATORS;
46005
46415
  var init_agentsMd = __esm({
@@ -51691,8 +52101,8 @@ function shouldRunGauntletHostLoop(opts) {
51691
52101
  }
51692
52102
  function budgetAwareGauntletGate(input) {
51693
52103
  if (input.verdict === "PASS") return "proceed";
51694
- if (input.toolCallsRemaining <= input.verificationReserve) return "finalize-verify";
51695
52104
  if (input.toolCallsRemaining <= 0) return "hold";
52105
+ if (input.toolCallsRemaining <= input.verificationReserve) return "finalize-verify";
51696
52106
  return "proceed";
51697
52107
  }
51698
52108
  var DEFAULT_MAX_PIECES, DEFAULT_MAX_ROUNDS, DEFAULT_MAX_PARALLEL2, DEFAULT_WALL_MS, GAUNTLET_PARENT_BLOCKED_TOOLS;
@@ -52162,7 +52572,8 @@ var init_schedule = __esm({
52162
52572
 
52163
52573
  // src/cli/gauntlet/loop.ts
52164
52574
  async function runGauntletLoop(args) {
52165
- const { caps, deps } = args;
52575
+ const { caps } = args;
52576
+ const deps = { ...args.deps, budgetGate: args.budgetGate ?? args.deps.budgetGate };
52166
52577
  const pieces = args.pieces.slice(0, caps.maxPieces);
52167
52578
  const started = (deps.now ?? Date.now)();
52168
52579
  const results = [];
@@ -52229,6 +52640,21 @@ async function runGauntletLoop(args) {
52229
52640
  builderError: built.error
52230
52641
  });
52231
52642
  winner = parseBlindWinner(criticized.result) ?? winner;
52643
+ if (last.kind !== "PASS") {
52644
+ const b = deps.budgetGate?.() ?? null;
52645
+ if (b) {
52646
+ const decision = budgetAwareGauntletGate({
52647
+ verdict: last.kind,
52648
+ toolCallsRemaining: b.remaining,
52649
+ verificationReserve: b.verificationReserve
52650
+ });
52651
+ if (decision === "hold") {
52652
+ gap = last.gap;
52653
+ break;
52654
+ }
52655
+ if (decision === "finalize-verify") break;
52656
+ }
52657
+ }
52232
52658
  emitProgress({
52233
52659
  phase: last.kind === "PASS" ? "settled" : last.kind === "BLOCKED" ? "blocked" : "repairing",
52234
52660
  pieceId: piece.id,
@@ -52292,6 +52718,7 @@ var init_loop = __esm({
52292
52718
  init_blind();
52293
52719
  init_policy();
52294
52720
  init_verdict2();
52721
+ init_policy();
52295
52722
  init_prompts();
52296
52723
  init_schedule();
52297
52724
  init_events3();
@@ -52395,7 +52822,12 @@ async function runHeadlessGauntlet(opts, provider, model) {
52395
52822
  type: "log",
52396
52823
  message: `[gauntlet] ${decomposed.pieces.length} piece(s) from ${decomposed.source}${decomposed.error ? ` (${decomposed.error.slice(0, 80)})` : ""}`
52397
52824
  });
52825
+ const budgetGate = () => {
52826
+ const lim = spine.resourceBudgetLimit();
52827
+ return lim ? { remaining: lim.remaining, verificationReserve: lim.verificationReserve } : null;
52828
+ };
52398
52829
  const result = await runGauntletLoop({
52830
+ budgetGate,
52399
52831
  pieces: decomposed.pieces,
52400
52832
  caps,
52401
52833
  deps: {
@@ -53470,7 +53902,7 @@ import {
53470
53902
  } from "node:fs";
53471
53903
  import { join as join38 } from "node:path";
53472
53904
  import { homedir as homedir13 } from "node:os";
53473
- import { createHash as createHash14, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
53905
+ import { createHash as createHash15, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
53474
53906
  function getZelariHome() {
53475
53907
  return join38(homedir13(), ".zelari-code");
53476
53908
  }
@@ -53546,8 +53978,8 @@ function loadOrCreateToken(explicit) {
53546
53978
  }
53547
53979
  function tokenMatches(expected, provided) {
53548
53980
  if (!provided) return false;
53549
- const a = createHash14("sha256").update(expected).digest();
53550
- const b = createHash14("sha256").update(provided).digest();
53981
+ const a = createHash15("sha256").update(expected).digest();
53982
+ const b = createHash15("sha256").update(provided).digest();
53551
53983
  try {
53552
53984
  return timingSafeEqual(a, b);
53553
53985
  } catch {
@@ -58143,13 +58575,13 @@ init_completionGate();
58143
58575
  // src/cli/kraken/verificationBridge.ts
58144
58576
  init_candidateRegistry();
58145
58577
  init_completionGate();
58146
- import { createHash as createHash11 } from "node:crypto";
58578
+ import { createHash as createHash12 } from "node:crypto";
58147
58579
 
58148
58580
  // src/cli/kraken/nativeVerification.ts
58149
58581
  init_runtime2();
58150
58582
  init_verification2();
58151
58583
  import { readFile as readFile2 } from "node:fs/promises";
58152
- import path40 from "node:path";
58584
+ import path42 from "node:path";
58153
58585
  function nativePackEnabled(env = process.env) {
58154
58586
  const v = env.ZELARI_VERIFY_PACK?.toLowerCase();
58155
58587
  return v === "1" || v === "on" || v === "true";
@@ -58176,7 +58608,7 @@ function packTimeoutMs(env = process.env) {
58176
58608
  }
58177
58609
  async function readPackageScripts(cwd = process.cwd()) {
58178
58610
  try {
58179
- const raw = await readFile2(path40.join(cwd, "package.json"), "utf-8");
58611
+ const raw = await readFile2(path42.join(cwd, "package.json"), "utf-8");
58180
58612
  const parsed = JSON.parse(raw);
58181
58613
  if (parsed && typeof parsed === "object" && typeof parsed.scripts === "object") {
58182
58614
  return parsed.scripts;
@@ -58270,7 +58702,7 @@ function krakenResultsToContract(requiredChecks, results, now = Date.now()) {
58270
58702
  return { criteria, results: verifications };
58271
58703
  }
58272
58704
  function sha256Hex2(input) {
58273
- return createHash11("sha256").update(input).digest("hex");
58705
+ return createHash12("sha256").update(input).digest("hex");
58274
58706
  }
58275
58707
  function matchNoteToToolTrace(note, trace) {
58276
58708
  const n = normalize5(note);
@@ -58991,7 +59423,9 @@ async function buildModelContext(input) {
58991
59423
  };
58992
59424
  input.onCompactionMetric?.(compactionMetrics);
58993
59425
  }
58994
- if (input.resourceSnapshot) {
59426
+ if (input.resourceSnapshot && !history2.some(
59427
+ (m) => m.role === "system" && typeof m.content === "string" && m.content.startsWith("RESOURCE STATUS")
59428
+ )) {
58995
59429
  history2 = [...history2, resourceStatusMessage(input.resourceSnapshot)];
58996
59430
  }
58997
59431
  if (requestSurface) {
@@ -59393,10 +59827,12 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
59393
59827
  systemMessages = [{ role: "system", content: fallback }];
59394
59828
  }
59395
59829
  systemPrefixLen = systemMessages.length;
59396
- const maxToolCallsPerTurn = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
59830
+ const perTurnEnv = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
59397
59831
  default: 25,
59398
59832
  min: 1
59399
59833
  });
59834
+ const sessionCap = writerRef.current?.spine?.resourceBudgetLimit();
59835
+ const maxToolCallsPerTurn = sessionCap ? Math.max(1, Math.min(perTurnEnv, sessionCap.maxToolCalls)) : perTurnEnv;
59400
59836
  const maxToolLoopIterations = budget.maxToolLoopIterations;
59401
59837
  const maxToolLoopHardCap = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_HARD, {
59402
59838
  default: 0,
@@ -59426,7 +59862,9 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
59426
59862
  providerStream,
59427
59863
  // 2.6 Phase 3: host-owned pre-dispatch resource gate via the spine
59428
59864
  // mirror (doc section 11.3). Degrade-and-stop (null gate = allow).
59429
- toolCallGate: (name) => writerRef.current?.spine?.gateResourceToolCall(name) ?? { allowed: true },
59865
+ // 2.6.1 (plan §13): argument-aware bash is essential only when
59866
+ // the command is a test/typecheck/build/git-diff line.
59867
+ toolCallGate: (name, args) => writerRef.current?.spine?.gateResourceToolCall(name, args) ?? { allowed: true },
59430
59868
  cwd,
59431
59869
  maxToolCallsPerTurn,
59432
59870
  maxToolLoopIterations,
@@ -60007,10 +60445,12 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
60007
60445
  let streamContent = "";
60008
60446
  let streamMemberId = null;
60009
60447
  const streamScrub = createStreamScrubber(16);
60010
- const councilMaxToolCalls = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
60448
+ const councilPerTurnEnv = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
60011
60449
  default: 15,
60012
60450
  min: 1
60013
60451
  });
60452
+ const councilSessionCap = writerRef.current?.spine?.resourceBudgetLimit();
60453
+ const councilMaxToolCalls = councilSessionCap ? Math.max(1, Math.min(councilPerTurnEnv, councilSessionCap.maxToolCalls)) : councilPerTurnEnv;
60014
60454
  const councilMaxToolLoop = envNumber(
60015
60455
  process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS,
60016
60456
  { default: 30, min: 1 }
@@ -65443,7 +65883,9 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
65443
65883
  // 2.6 Phase 3: host-owned pre-dispatch resource gate (doc section 11.3).
65444
65884
  // Advisory by default; ZELARI_RESOURCE_ENFORCEMENT=protected enables the
65445
65885
  // protected verification reserve. Degrade-and-stop (null gate = allow).
65446
- toolCallGate: (name) => spine.gateResourceToolCall(name) ?? { allowed: true },
65886
+ // 2.6.1 (plan §13): argument-aware bash is essential only when the
65887
+ // command is a test/typecheck/build/git-diff line.
65888
+ toolCallGate: (name, args) => spine.gateResourceToolCall(name, args) ?? { allowed: true },
65447
65889
  maxToolLoopIterations: maxToolLoop
65448
65890
  });
65449
65891
  let finalReason = "completed";
@@ -66094,6 +66536,28 @@ ${ragContext}` : slicePrompt;
66094
66536
  if (completionOk) {
66095
66537
  emit(`[zelari] slice completion ok`);
66096
66538
  }
66539
+ try {
66540
+ const budget = spine.resourceBudgetSummary();
66541
+ if (budget && !completionOk) {
66542
+ const { evaluateResourceReserveGate: evaluateResourceReserveGate2 } = await Promise.resolve().then(() => (init_verification2(), verification_exports));
66543
+ const gated = evaluateResourceReserveGate2({
66544
+ evaluation: {
66545
+ verdict: "REPAIR_REQUIRED",
66546
+ summary: "headless slice completion",
66547
+ satisfied: [],
66548
+ unsatisfied: [],
66549
+ evidenceComplete: false,
66550
+ eventBackedEvidenceComplete: false
66551
+ },
66552
+ budget
66553
+ });
66554
+ if (gated.verdict === "BLOCKED") {
66555
+ emit("[zelari] completion BLOCKED: resource budget exhausted (non-PASS + zero remaining)");
66556
+ }
66557
+ spine.note("completion.resource_gate", { decision: gated.verdict, remaining: budget.toolCalls.remaining });
66558
+ }
66559
+ } catch {
66560
+ }
66097
66561
  } catch {
66098
66562
  }
66099
66563
  if (synthesisText.trim()) {