tickmarkr 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +10 -9
  2. package/dist/adapters/types.d.ts +20 -1
  3. package/dist/adapters/types.js +42 -2
  4. package/dist/cli/commands/approve.js +5 -4
  5. package/dist/cli/commands/beat.js +7 -4
  6. package/dist/cli/commands/doctor.d.ts +5 -1
  7. package/dist/cli/commands/doctor.js +67 -7
  8. package/dist/cli/commands/init.js +36 -21
  9. package/dist/cli/commands/plan.js +16 -2
  10. package/dist/cli/commands/report.js +37 -1
  11. package/dist/cli/commands/verify.d.ts +5 -0
  12. package/dist/cli/commands/verify.js +140 -25
  13. package/dist/compile/collateral.js +15 -9
  14. package/dist/compile/native.js +3 -3
  15. package/dist/config/config.js +1 -1
  16. package/dist/drivers/index.d.ts +6 -0
  17. package/dist/drivers/index.js +19 -4
  18. package/dist/drivers/orca.d.ts +22 -1
  19. package/dist/drivers/orca.js +147 -6
  20. package/dist/drivers/subprocess.d.ts +3 -3
  21. package/dist/drivers/subprocess.js +16 -9
  22. package/dist/drivers/types.d.ts +2 -0
  23. package/dist/gates/baseline.d.ts +2 -0
  24. package/dist/gates/baseline.js +21 -4
  25. package/dist/gates/llm.d.ts +6 -0
  26. package/dist/gates/llm.js +25 -9
  27. package/dist/gates/review.d.ts +3 -1
  28. package/dist/gates/review.js +40 -12
  29. package/dist/gates/run-gates.js +17 -10
  30. package/dist/gates/verdict-cause.d.ts +6 -2
  31. package/dist/gates/verdict-cause.js +8 -4
  32. package/dist/run/consult.js +5 -1
  33. package/dist/run/daemon.d.ts +12 -0
  34. package/dist/run/daemon.js +218 -25
  35. package/dist/run/git.d.ts +1 -0
  36. package/dist/run/git.js +4 -0
  37. package/dist/run/journal.d.ts +15 -2
  38. package/dist/run/journal.js +70 -12
  39. package/dist/run/supervision.d.ts +6 -0
  40. package/dist/run/supervision.js +29 -1
  41. package/dist/tui/ink/init-app.js +4 -4
  42. package/package.json +1 -1
  43. package/skills/tickmarkr-overseer/SKILL.md +77 -18
  44. package/skills/tickmarkr-overseer/scripts/seat-send.sh +88 -18
  45. package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +36 -2
  46. package/skills/tickmarkr-overseer/scripts/watch-contamination.sh +36 -15
  47. package/skills/tickmarkr-overseer/scripts/watch-context.sh +33 -7
  48. package/skills/tickmarkr-overseer/scripts/watch-pending-input.sh +32 -8
@@ -2,12 +2,13 @@ import { writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { channelKey, shq } from "../adapters/types.js";
4
4
  import { criticalPathHits, DEFAULT_DIFF_CAP, DEFAULT_REVIEW_CRITICAL_PATHS, declaredReviewPolicy, isReviewLeafPath, raiseReviewPolicy, REVIEW_VERSION_MIRRORS, TIER_RANK, } from "../config/config.js";
5
+ import { filesGlob } from "../graph/files-glob.js";
5
6
  import { renderAcceptanceItem } from "../graph/schema.js";
6
7
  import { getAdapter } from "../adapters/registry.js";
7
8
  import { shOk } from "../run/git.js";
8
9
  import { redactSecrets } from "../run/redact.js";
9
10
  import { marginalCostRank } from "../route/router.js";
10
- import { appendAnchoredReview, COMPLETION_FAKING_CHECKLIST, extractVerdictJson, generateVerdictNonce, runLlm, verdictNonceLine } from "./llm.js";
11
+ import { appendAnchoredReview, COMPLETION_FAKING_CHECKLIST, extractVerdictJson, generateVerdictNonce, runLlmDetailed, verdictNonceLine } from "./llm.js";
11
12
  import { classifyVerdictCause } from "./verdict-cause.js";
12
13
  import { captureDiffCapFor, measureArtifactDiff, reviewableLogicDiff, } from "./artifact-manifest.js";
13
14
  export { isProtectedEvidence, PROTECTED_EVIDENCE_PREFIXES, REGENERABLE_CAPTURE_PATHS, setAsideReceiptPath, setAsideRegenerableCaptures, } from "./artifact-manifest.js";
@@ -99,12 +100,14 @@ export async function mirrorsVersionOnly(worktree, baseRef, path) {
99
100
  const changed = diff.split("\n").filter((l) => /^[+-]/.test(l) && !/^(?:\+\+\+|---)/.test(l));
100
101
  return changed.length > 0 && changed.every((l) => VERSION_FIELD_LINE_RE.test(l));
101
102
  }
102
- export async function fetchTaskDiff(worktree, baseRef) {
103
+ export async function fetchTaskDiff(worktree, baseRef, files = []) {
103
104
  // --full-index: abbreviated index lines vary with object-store density, so two measurements of
104
105
  // the same diff could disagree by a few bytes between invocations (CI-only red, release 1.89.0).
105
- const [rawFull, rawForCap] = await Promise.all([
106
- shOk(`git diff --full-index '${baseRef}..HEAD'`, worktree),
107
- shOk(`git diff --full-index -U0 '${baseRef}..HEAD'`, worktree),
106
+ const matched = files.length ? (await changedPaths(worktree, baseRef)).filter(filesGlob([...files])) : [];
107
+ const pathspec = matched.length ? ` -- ${matched.map(shq).join(" ")}` : "";
108
+ const [rawFull, rawForCap] = files.length && matched.length === 0 ? ["", ""] : await Promise.all([
109
+ shOk(`git diff --full-index '${baseRef}..HEAD'${pathspec}`, worktree),
110
+ shOk(`git diff --full-index -U0 '${baseRef}..HEAD'${pathspec}`, worktree),
108
111
  ]);
109
112
  const fullMeasurement = measureArtifactDiff(rawFull);
110
113
  const capMeasurement = measureArtifactDiff(rawForCap);
@@ -162,6 +165,24 @@ export function diffCapParkReason(results) {
162
165
  export function modelId(model) {
163
166
  return model.slice(model.lastIndexOf("/") + 1);
164
167
  }
168
+ /** Provider identity comes from the served model, not a gateway adapter's stamped vendor. */
169
+ export function modelProvider(model, fallback = "unknown") {
170
+ const id = model.toLowerCase();
171
+ const prefix = id.includes("/") ? id.slice(0, id.indexOf("/")) : "";
172
+ if (prefix === "openai" || prefix === "openai-codex" || /^(?:gpt|o\d)/.test(id))
173
+ return "openai";
174
+ if (prefix === "anthropic" || /^(?:claude|opus|sonnet|haiku|fable)(?:-|$)/.test(id))
175
+ return "anthropic";
176
+ if (prefix === "google" || /^gemini(?:-|$)/.test(id))
177
+ return "google";
178
+ if (prefix === "xai" || /^grok(?:-|$)/.test(id))
179
+ return "xai";
180
+ if (["zai", "zhipu", "zai-coding-plan"].includes(prefix) || /^glm(?:-|$)/.test(id))
181
+ return "zhipu";
182
+ if (["kimi-code", "moonshot"].includes(prefix) || /^kimi(?:-|$)/.test(id))
183
+ return "moonshot";
184
+ return fallback;
185
+ }
165
186
  // v1.53 T2: same entry grammar as routing.map.prefer (router.ts preferIndex — router is out of this
166
187
  // module's dependency direction for a private fn, so the 3 lines live here too): `adapter` matches
167
188
  // every channel of that adapter, `adapter:model` exactly one; unmatched channels sort after all entries.
@@ -179,11 +200,14 @@ floor) {
179
200
  const authorChannel = channels.find((c) => c.adapter === author.adapter && c.model === author.model);
180
201
  if (!authorChannel)
181
202
  return null;
203
+ // Failover also guards true provider; the initial pick keeps the established stamped-vendor contract.
204
+ const authorProvider = modelProvider(author.model, authorChannel.vendor);
182
205
  return (channels
183
206
  // two independent axes: different vendor AND different base-model identity (ADDED TO the vendor
184
207
  // rule, never replacing it — a future edit can't silently drop either). The diversity filter runs
185
208
  // BEFORE preference ranking: prefer sorts survivors only, so no entry can resurrect an excluded channel.
186
209
  .filter((c) => c.vendor !== authorChannel.vendor
210
+ && (exclude.length === 0 || modelProvider(c.model, c.vendor) !== authorProvider)
187
211
  && modelId(c.model) !== modelId(author.model)
188
212
  && !exclude.includes(channelKey(c))
189
213
  && (floor === undefined || TIER_RANK[c.tier] >= TIER_RANK[floor]))
@@ -285,10 +309,10 @@ artifactDir) {
285
309
  ? `no cross-vendor reviewer available at or above task-declared ${reviewerFloor} floor (diversity rule)`
286
310
  : "no cross-vendor reviewer available (diversity rule)";
287
311
  return cfg.review.required
288
- ? { gate: "review", pass: false, details: `${reason}; set review.required:false to waive`, meta: { noEligibleReviewer: true, ...(reviewerFloor ? { reviewerFloor } : {}) } }
312
+ ? { gate: "review", pass: false, details: `unreadable — ${reason}; set review.required:false to waive`, meta: { noEligibleReviewer: true, unreadable: true, ...(reviewerFloor ? { reviewerFloor } : {}) } }
289
313
  : { gate: "review", pass: true, details: `WARNING: ${reason} — review waived by config`, meta: { noEligibleReviewer: true, ...(reviewerFloor ? { reviewerFloor } : {}) } };
290
314
  }
291
- const measuredDiff = await fetchTaskDiff(worktree, baseRef);
315
+ const measuredDiff = await fetchTaskDiff(worktree, baseRef, task.files);
292
316
  // Keep the reader payload identical to the text charged to the strict cap:
293
317
  // whole-file source deletions are represented by their citable operation fact.
294
318
  const diff = reviewableLogicDiff(measuredDiff.full);
@@ -327,7 +351,7 @@ Approve iff no material finding remains; an empty findings list is a clean appro
327
351
  The top-level comments array is optional. Use it only for actionable line-anchored feedback.
328
352
  `;
329
353
  let concludedOnInactivity = false;
330
- const raw = await runLlm(getAdapter(reviewer.adapter, adapters), reviewer.model, prompt, worktree, via ? {
354
+ const llm = await runLlmDetailed(getAdapter(reviewer.adapter, adapters), reviewer.model, prompt, worktree, via ? {
331
355
  driver: via.driver,
332
356
  keep: via.keep,
333
357
  onSlot: via.onSlot,
@@ -341,13 +365,15 @@ The top-level comments array is optional. Use it only for actionable line-anchor
341
365
  // (run-20260709-104447 P87-09). ponytail: literal 15min; make it cfg.review.timeoutMs if a
342
366
  // second knob-turner appears.
343
367
  900_000);
368
+ const raw = llm.output;
369
+ const provider = modelProvider(reviewer.model, reviewer.vendor);
344
370
  const v = extractVerdictJson(raw, nonce);
345
371
  const findings = v && Array.isArray(v.findings) ? v.findings : null;
346
372
  // findings decides the verdict on its own; the legacy path still needs approve + issues to parse.
347
373
  if (!v || (findings === null && (typeof v.approve !== "boolean" || !Array.isArray(v.issues)))) {
348
374
  // OBS-196: name the cause and persist the raw bytes — a ruled-on "unparseable" without its
349
375
  // evidence cannot be audited, and a cutoff must never be indistinguishable from a parse defect.
350
- const cause = classifyVerdictCause(raw, nonce, "approve");
376
+ const cause = classifyVerdictCause(raw, nonce, "approve", llm);
351
377
  const bytes = Buffer.byteLength(raw, "utf8");
352
378
  let saved;
353
379
  if (artifactDir) {
@@ -367,10 +393,12 @@ The top-level comments array is optional. Use it only for actionable line-anchor
367
393
  return {
368
394
  gate: "review",
369
395
  pass: false,
370
- details: `${failure} (reviewer ${reviewer.adapter}:${reviewer.model}; cause: ${cause}${saved ? `; raw saved: ${saved}` : ""}) — failing closed`,
396
+ details: `${failure} (reviewer ${reviewer.adapter}:${reviewer.model}; vendor: ${reviewer.vendor}; provider: ${provider}; cause: ${cause}${saved ? `; raw saved: ${saved}` : ""}) — failing closed`,
371
397
  meta: {
372
398
  ...policyMeta,
373
399
  reviewer: channelKey(reviewer),
400
+ vendor: reviewer.vendor,
401
+ provider,
374
402
  unparseable: true,
375
403
  cause,
376
404
  ...(cause === "empty-output" ? { bytes } : {}),
@@ -381,11 +409,11 @@ The top-level comments array is optional. Use it only for actionable line-anchor
381
409
  const decided = findings !== null
382
410
  ? classifyReviewFindings(findings)
383
411
  : classifyReviewIssues(v.approve, v.issues);
384
- const prose = `reviewer ${reviewer.adapter}:${reviewer.model} (${reviewer.vendor}): ${decided.headline}${decided.lines.length ? "\n" + decided.lines.join("\n") : ""}`;
412
+ const prose = `reviewer ${reviewer.adapter}:${reviewer.model} (vendor: ${reviewer.vendor}; provider: ${provider}): ${decided.headline}${decided.lines.length ? "\n" + decided.lines.join("\n") : ""}`;
385
413
  return {
386
414
  gate: "review",
387
415
  pass: decided.pass,
388
416
  details: appendAnchoredReview(prose, v),
389
- meta: { ...policyMeta, reviewer: channelKey(reviewer) },
417
+ meta: { ...policyMeta, reviewer: channelKey(reviewer), vendor: reviewer.vendor, provider },
390
418
  };
391
419
  }
@@ -11,7 +11,7 @@ import { evidenceGate } from "./evidence.js";
11
11
  import { captureLlmOutput } from "./llm.js";
12
12
  import { disallowedBy } from "../route/preference.js";
13
13
  import { marginalCostRank } from "../route/router.js";
14
- import { reviewGate } from "./review.js";
14
+ import { pickReviewer, reviewGate } from "./review.js";
15
15
  import { scopeGate } from "./scope.js";
16
16
  import { shGit } from "../run/git.js";
17
17
  import { withJudgeInvocationEvidence } from "../run/journal.js";
@@ -540,7 +540,7 @@ export async function runGates(task, ctx) {
540
540
  .sort((x, y) => TIER_RANK[y.tier] - TIER_RANK[x.tier] || marginalCostRank(x) - marginalCostRank(y))[0];
541
541
  // v1.87 T2: the failover seat obeys the same policy the primary judge just passed — a denied
542
542
  // channel is refused here too, never reached by falling through the exclusion arms below.
543
- const judgePool = (ctx.judgeChannels ?? ctx.channels).filter((c) => disallowedBy(c, ctx.cfg.routing, "judge") === null);
543
+ const judgePool = (ctx.judgeChannels ?? []).filter((c) => disallowedBy(c, ctx.cfg.routing, "judge") === null);
544
544
  const crossAdapter = pick(judgePool.filter((c) => c.adapter !== flakedAdapter));
545
545
  const sameAdapter = pick(judgePool.filter((c) => c.adapter === flakedAdapter && channelKey(c) !== flakedKey));
546
546
  // Prefer a different adapter; if the fleet only has one adapter, retry on a different channel of
@@ -576,11 +576,9 @@ export async function runGates(task, ctx) {
576
576
  return captured.value;
577
577
  };
578
578
  let rv = await dispatch((adapters) => reviewGate(task, ctx.worktree, ctx.baseRef, ctx.author, ctx.channels, adapters, ctx.cfg, ctx.via, ctx.excludeReviewers, ctx.artifactDir));
579
- // OBS-193: an unparseable review verdict retries the REVIEW exactly once on a different reviewer —
580
- // never the worker (GATE-09's judge-retry shape: straight-line single `if`, meta-only detection,
581
- // the flaked verdict never enters results). The exclusion rides reviewGate's own excludeReviewers
582
- // parameter, so pickReviewer's diversity rules still govern the retry seat; a fleet with no second
583
- // eligible seat keeps the ORIGINAL result so the recorded cause stays truthful (OBS-196).
579
+ // OBS-193/574: an unparseable review verdict retries the REVIEW exactly once, preferring a
580
+ // different adapter. Only a single-adapter eligible pool may fall back to another channel on the
581
+ // flaked adapter. The flaked verdict never enters results; an exhausted pool preserves its cause.
584
582
  if (rv.meta?.unparseable === true && typeof rv.meta.reviewer === "string") {
585
583
  const flaked = rv.meta.reviewer;
586
584
  const emptyOutput = rv.meta.cause === "empty-output";
@@ -594,15 +592,24 @@ export async function runGates(task, ctx) {
594
592
  const retryVia = ctx.via
595
593
  ? { ...ctx.via, nameFor: (role, adapter) => ctx.via.nameFor(role, adapter) + "-r1" }
596
594
  : undefined;
597
- const second = await dispatch((adapters) => reviewGate(task, ctx.worktree, ctx.baseRef, ctx.author, ctx.channels, adapters, ctx.cfg, retryVia, [...(ctx.excludeReviewers ?? []), flaked], ctx.artifactDir));
595
+ const priorExclusions = ctx.excludeReviewers ?? [];
596
+ const flakedAdapter = flaked.slice(0, flaked.indexOf(":"));
597
+ const adapterExclusions = ctx.channels.filter((c) => c.adapter === flakedAdapter).map(channelKey);
598
+ const crossAdapter = pickReviewer(ctx.author, ctx.channels, [...priorExclusions, ...adapterExclusions], ctx.cfg.review.prefer ?? [], task.routingHints?.floor);
599
+ const exclusion = crossAdapter ? "adapter" : "channel";
600
+ const retryExclusions = [...priorExclusions, ...(crossAdapter ? adapterExclusions : [flaked])];
601
+ const second = await dispatch((adapters) => reviewGate(task, ctx.worktree, ctx.baseRef, ctx.author, ctx.channels, adapters, ctx.cfg, retryVia, retryExclusions, ctx.artifactDir));
598
602
  if (second.meta?.noEligibleReviewer !== true) {
599
603
  const retried = typeof second.meta?.reviewer === "string" ? second.meta.reviewer : "none";
604
+ const route = exclusion === "adapter"
605
+ ? `different-adapter retry; excluded flaked adapter ${flakedAdapter}`
606
+ : `same-adapter fallback; excluded flaked channel ${flaked}`;
600
607
  rv = {
601
608
  ...second,
602
609
  // `details` is lifted onto the journal's gate-result row; meta.reviewRetry is not. Keep the
603
610
  // re-route visible in the result text a reader actually opens, including on a red retry.
604
- details: `review re-route: ${flaked} produced ${emptyOutput ? "EMPTY output" : "no parseable verdict"}; replaced by ${retried}\n${second.details}`,
605
- meta: { ...second.meta, reviewRetry: { flaked, retried } },
611
+ details: `review re-route (${route}): ${flaked} produced ${emptyOutput ? "EMPTY output" : "no parseable verdict"}; replaced by ${retried}\n${second.details}`,
612
+ meta: { ...second.meta, reviewRetry: { flaked, retried, exclusion } },
606
613
  };
607
614
  }
608
615
  else if (task.routingHints?.floor) {
@@ -1,4 +1,8 @@
1
1
  export type VerdictDiscriminator = "approve" | "pass" | "ok" | "action";
2
- export type VerdictUnparseableCause = "empty-output" | "no-verdict" | "malformed-verdict";
2
+ export type VerdictUnparseableCause = "empty-output" | "no-verdict" | "malformed-verdict" | "timeout" | "startup-failure";
3
+ export interface VerdictProcessFacts {
4
+ timedOut?: boolean;
5
+ exitCode?: number;
6
+ }
3
7
  export declare function hasVerdictParticipationWitness(raw: string, nonce: string, discriminator: VerdictDiscriminator): boolean;
4
- export declare function classifyVerdictCause(raw: string, nonce: string, discriminator: VerdictDiscriminator): VerdictUnparseableCause;
8
+ export declare function classifyVerdictCause(raw: string, nonce: string, discriminator: VerdictDiscriminator, process?: VerdictProcessFacts): VerdictUnparseableCause;
@@ -54,10 +54,14 @@ export function hasVerdictParticipationWitness(raw, nonce, discriminator) {
54
54
  const discriminatorPattern = new RegExp(String.raw `"${discriminator}"\s*:\s*${valuePattern}\s*[,}]`);
55
55
  return objectPrefixes(raw).some((prefix) => noncePattern.test(prefix) && discriminatorPattern.test(prefix));
56
56
  }
57
- export function classifyVerdictCause(raw, nonce, discriminator) {
57
+ export function classifyVerdictCause(raw, nonce, discriminator, process = {}) {
58
+ if (process.timedOut)
59
+ return "timeout";
58
60
  if (raw.trim().length === 0)
59
61
  return "empty-output";
60
- return hasVerdictParticipationWitness(raw, nonce, discriminator)
61
- ? "malformed-verdict"
62
- : "no-verdict";
62
+ if (hasVerdictParticipationWitness(raw, nonce, discriminator))
63
+ return "malformed-verdict";
64
+ if (process.exitCode !== undefined && process.exitCode !== 0)
65
+ return "startup-failure";
66
+ return "no-verdict";
63
67
  }
@@ -147,6 +147,10 @@ opts = {}) {
147
147
  out = r.stdout + r.stderr;
148
148
  }
149
149
  else {
150
+ // Preserve the adapter contract for CLIs that cannot seed their TUI: supported adapters use
151
+ // the interactive form, while a declared null keeps the existing visible print fallback.
152
+ const command = adapter.interactiveCommand(promptFile, seatModel)
153
+ ?? adapter.headlessCommand(promptFile, seatModel);
150
154
  // T8: role-first pane name for fleet visibility (consult · T2); consultSeq stays on the dossier artifact only
151
155
  const slot = await driver.slot(cwd, gatePaneName("consult", d.taskId), {
152
156
  label: `CONSULT ${d.taskId}`,
@@ -158,7 +162,7 @@ opts = {}) {
158
162
  writeFileSync(scriptPath, [
159
163
  "export BASH_SILENCE_DEPRECATION_WARNING=1",
160
164
  bannerShell(),
161
- adapter.headlessCommand(promptFile, seatModel),
165
+ command,
162
166
  gateExitTrailer(nonce),
163
167
  ].join("\n"));
164
168
  try {
@@ -102,6 +102,8 @@ export declare const gateSatisfied: (g: GateResult) => boolean;
102
102
  * A round is the gate-result span opened by each `gates` phase-start, per task.
103
103
  */
104
104
  export declare function decisiveReviewRounds(events: JournalEvent[]): JournalEvent[];
105
+ export declare const SUITE_POLL_MS = 250;
106
+ export declare const APPROVAL_POLL_MS = 250;
105
107
  export declare const EARLY_LAUNCH_LIVENESS_MS = 60000;
106
108
  /** Test seam — lowers the empty-pane liveness window without sleeping 60s per case. */
107
109
  export declare function setEarlyLaunchLivenessMsForTests(ms: number): void;
@@ -141,6 +143,16 @@ export declare function verifyIntegrationTipCached(intWt: string, commands: Reco
141
143
  lastMergedTask?: string;
142
144
  baseline?: Baseline;
143
145
  }): Promise<boolean>;
146
+ type SuitePidProbe = (pid: number) => number | undefined;
147
+ /** Count full-suite roots in one process-table snapshot. The probes are arguments so the ownership
148
+ * rules remain testable on hosts that forbid process inspection; production supplies cwd and the
149
+ * inherited TICKMARKR_SUITE_PARENT marker from the process itself. */
150
+ export declare function countLiveSuites(snapshot: string, repoRoot: string, daemonPid?: number, cwdForPid?: (pid: number) => string | undefined, suiteParentForPid?: SuitePidProbe): number;
151
+ export declare const setLiveSuiteCountForTests: (probe: (repoRoot: string) => Promise<number>) => void;
152
+ export declare const resetLiveSuiteCountForTests: () => void;
153
+ /** Live full-suite roots attributable to this repository or this daemon. Ancestors are excluded so
154
+ * a daemon invoked by vitest does not wait on its own test harness forever. */
155
+ export declare function liveSuiteCount(repoRoot: string): Promise<number>;
144
156
  /** Test seam — exercise the production observer's total read bound with a small real tree. */
145
157
  export declare function setObserveBudgetBytesForTests(bytes: number): void;
146
158
  export declare function resetObserveBudgetBytesForTests(): void;