/** * Adaptive heap sizing (change: make-analyze-scale-to-any-repo). * * A large repository's call graph can outgrow Node's default old-space heap, and the only recourse * used to be knowing to pass `--max-old-space-size` by hand. This module removes that: at CLI * entry, before any heavy work, it sizes the heap to the memory ACTUALLY available to the process * (the container/cgroup limit when there is one, else host RAM) by re-executing itself ONCE with * the right flag. "It works if you set a Node flag" becomes "it just works." * * The whole module is designed to be safe and quiet: * - at most once — a marker env var prevents any re-exec loop, whatever the outcome; * - never when the user already chose a heap (`--max-old-space-size`, `NODE_OPTIONS`) or opted * out (`OPENLORE_NO_AUTO_HEAP`); * - transparent to the stdio MCP server — stdio is inherited untouched and the one-line * disclosure goes to STDERR, never stdout (which carries JSON-RPC); * - fail-open — any error in sizing continues at the default heap rather than breaking the CLI. * * The decision ({@link planHeapReexec}) and the cgroup parsers are pure so they are unit-tested * directly; the side-effecting {@link maybeReexecForHeap} is the thin orchestrator the bootstrap * calls. */ /** * The commands whose heap is worth sizing — the FINITE, graph-building runs: `analyze` and the * commands that run it in-process. Everything else (the `orient`/`search` query hot paths an agent * drives constantly, `--version`, `doctor`, …) reads bounded data and runs fine at the default heap, * so it is NOT re-executed — an extra process spawn on those would be pure latency, exactly the * attention this feature removes. * * The long-lived daemons `mcp` and `serve` are deliberately EXCLUDED. Re-execution here is a * blocking `spawnSync` supervisor, which cannot forward a signal to the child while it blocks; a * directed `SIGTERM` to the supervisor (how a programmatic MCP host stops a stdio server) would * orphan the real server holding the whole graph. A finite batch command's supervisor blocks only * for the run and its child dies with the foreground process group, so the hazard is theirs alone. * A daemon on a huge repo can still be given a heap the ordinary way (`--max-old-space-size` / * `OPENLORE_HEAP_MB`), which this feature honors. * * An allowlist, not a denylist, on purpose: a command mistakenly omitted merely runs at the default * heap (today's behavior — no regression), whereas a hot path mistakenly INCLUDED would pay a second * process spawn on every call. The safe failure is "no improvement," never "slower." */ export declare const HEAP_SIZED_COMMANDS: ReadonlySet; /** * Top-level options that take a VALUE (see `src/cli/index.ts`). In the space-separated form * (`--config prod.json`) the value is a non-dash token that must NOT be mistaken for the * subcommand — otherwise `openlore --config prod.json analyze` would read `prod.json` as the * command and silently skip heap sizing for a config-guarded analyze. The `=`-joined form * (`--config=prod.json`) already starts with `-`, so only the space form needs this. */ export declare const GLOBAL_VALUE_FLAGS: ReadonlySet; /** * The subcommand from a raw argv (`[node, script, ...rest]`): the first token that is neither a * flag nor the value of a global value-taking option. `undefined` for a bare `openlore` or a * global-flag-only invocation (`openlore --version`). */ export declare function commandFromArgv(argv: readonly string[]): string | undefined; /** Opt-out: any non-empty value restores the prior behavior (Node's default or the user's flag). */ export declare const NO_AUTO_HEAP_ENV = "OPENLORE_NO_AUTO_HEAP"; /** Explicit target old-space size in MB — skips detection and the fraction math. */ export declare const HEAP_MB_ENV = "OPENLORE_HEAP_MB"; /** Override the fraction of the memory budget used for the heap (default {@link DEFAULT_HEAP_FRACTION}). */ export declare const HEAP_FRACTION_ENV = "OPENLORE_HEAP_FRACTION"; /** Internal marker set on the re-executed child so it never re-execs again (at-most-once). */ export declare const HEAP_REEXEC_MARKER_ENV = "OPENLORE_HEAP_REEXEC"; /** * Fraction of the available-memory budget used for the old-space heap. Generous enough that a large * repository fits, conservative enough to leave headroom for young-gen, code, native (tree-sitter, * SQLite) and external buffers so a hard container limit is not blown (which the kernel answers with * SIGKILL, past the reach of the degradation ladder). Overridable with {@link HEAP_FRACTION_ENV}. */ export declare const DEFAULT_HEAP_FRACTION = 0.75; /** Never target a heap smaller than this — below it re-exec is pointless. */ export declare const MIN_TARGET_MB = 512; /** * Only re-exec when the target beats the current limit by at least this margin. Stops a pointless * re-exec for a trivial gain (Node already auto-sizes the default heap from physical RAM on recent * versions, so the gain is often small on a roomy host — exactly where re-exec is not worth it). */ export declare const MIN_GAIN_MB = 512; /** * Parse a cgroup v2 `memory.max` value. It is either a byte count or the literal `max` (no limit). * Returns the byte limit, or `undefined` for `max`, an unlimited sentinel, or an unparseable value. */ export declare function parseCgroupV2Max(content: string | null | undefined): number | undefined; /** * Parse a cgroup v1 `memory.limit_in_bytes` value — a byte count, or a near-`INT64_MAX` sentinel * when there is no limit. Returns the byte limit, or `undefined` when unlimited/unparseable. */ export declare function parseCgroupV1Limit(content: string | null | undefined): number | undefined; /** * The `memory.max` file paths to consult for the current cgroup v2 process, from the mount root down * to the process's own leaf, derived from `/proc/self/cgroup` (a single `0::` line under v2). * Pure so the path logic is unit-tested without a Linux filesystem. Always includes the root; adds * each ancestor when the process is in a non-root cgroup. `unified` is the cgroup v2 mount root. */ export declare function cgroupV2MemoryMaxPaths(procCgroupContent: string | null | undefined, unified?: string): string[]; /** * The cgroup/container memory limit in bytes, or `undefined` when there is none (non-Linux, no * cgroup, or an "unlimited" limit). Reads cgroup v2 (walking the hierarchy for the tightest cap) * first, then v1. */ export declare function readCgroupMemoryLimitBytes(): number | undefined; /** * The memory budget this process should size to: the cgroup/container limit when present and * smaller than host RAM, otherwise host RAM. Never larger than host RAM, so a stale or huge cgroup * value can never inflate the budget past the physical machine. */ export declare function detectMemoryBudgetBytes(): number; /** * True when the user already chose the heap — via a `--max-old-space-size` in the Node exec args or * in `NODE_OPTIONS`. Their choice is respected: adaptive sizing then does nothing. */ export declare function userHasSetHeap(execArgv: readonly string[], nodeOptions: string | undefined): boolean; export interface HeapPlanInputs { /** Available-memory budget in bytes (cgroup limit or host RAM). */ budgetBytes: number; /** The heap old-space limit in bytes this process currently has. */ currentHeapLimitBytes: number; /** The user already chose a heap (`--max-old-space-size` / `NODE_OPTIONS`). */ userSetHeap: boolean; /** The opt-out is set. */ optOut: boolean; /** This process is itself a re-exec (the marker is set) — so it must NOT re-exec again. */ alreadyReexeced: boolean; /** Explicit target old-space MB (from {@link HEAP_MB_ENV}), or undefined. */ explicitTargetMb: number | undefined; /** Fraction of the budget to use (from {@link HEAP_FRACTION_ENV}), or undefined for the default. */ fraction: number | undefined; } export interface HeapPlan { action: 'reexec' | 'skip'; /** The old-space MB to re-exec with. Present only when `action === 'reexec'`. */ targetMb?: number; /** One-line human explanation, for the disclosure line or a `--verbose` skip trace. */ reason: string; } /** * Decide whether to re-exec with a larger heap, and to what size. Pure and total: every path * returns a plan with a reason, and the ordering guarantees at-most-once (the marker is checked * FIRST, so a re-executed process can never re-exec again regardless of any other input). */ export declare function planHeapReexec(inputs: HeapPlanInputs): HeapPlan; /** Gather the live inputs for the decision from the current process/environment. */ export declare function gatherHeapPlanInputs(): HeapPlanInputs; /** * Size the heap by re-executing once when the plan calls for it, then exit with the child's status. * When no re-exec is warranted (the common case) this returns immediately and the CLI runs normally. * Fail-open: any surprise leaves the process running at its current heap rather than aborting. */ export declare function maybeReexecForHeap(): void; //# sourceMappingURL=heap-sizing.d.ts.map