import type { Context } from '@wrongstack/core/agent'; /** * sha-256 hex of a UTF-8 string. Used by the file tools to record a content * hash alongside the mtime in `ctx.recordRead` — the hash is the authoritative * staleness arbiter for `edit` (mtime has a 2 s tolerance window on Windows). */ export declare function sha256hex(content: string): string; /** Detected package manager for a project directory. */ export type PackageManager = 'pnpm' | 'yarn' | 'npm'; /** * Detect the project's package manager. * * Per directory, precedence is: `package.json#packageManager` (authoritative * when declared) → `pnpm-lock.yaml` → `yarn.lock` → `bun.lockb`/`bun.lock` * (bun is treated as npm-compatible and reported as `npm`) → * `package-lock.json`/`npm-shrinkwrap.json`. When `stopAt` (usually the * project root) is provided and `cwd` is nested inside it, parent directories * are walked up to and including `stopAt` — monorepo packages rarely carry * their own lockfile. Missing or unreadable directories fall back to `npm` * rather than throwing, so a `safeResolve`-checked cwd that happens to be * empty never aborts the tool. */ export declare function detectPackageManager(cwd: string, stopAt?: string): Promise; export declare function resolvePath(input: string, ctx: Context): string; export declare function ensureInsideRoot(absPath: string, ctx: Context): string; export declare function safeResolve(input: string, ctx: Context): string; /** * Defense against in-root→out-of-root symlink escape (CWE-59). `safeResolve` * only does a syntactic `../` check, so a symlink that lives *inside* the * project root but points outside still passes it. This resolves the path * through `fs.realpath` and re-verifies containment against the realpath of * the project root (comparing like-for-like, since the root itself may be a * symlink — macOS `/var`→`/private/var`, Windows 8.3 short names). For a path * that does not exist yet (e.g. a `write` to a new file) the nearest existing * ancestor directory is checked instead. Throws if the real target escapes. * * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied * to single-file `read`/`edit`/`write` it throws (rather than skips) because * the caller named exactly one file. */ export declare function assertRealInsideRoot(absPath: string, ctx: Context): Promise; /** * Containment check that RETURNS the canonical path it validated (WS-048). * * The check resolves symlinks and then confirms the resolved target is inside * an allowed root. Handing the caller back the *unresolved* path throws that * work away: the caller opens a path whose components are still symlinks, so * whatever the check proved about the target is no longer what the caller * touches. Swapping an intermediate component between the check and the open * redirects the operation, and nothing downstream re-validates. * * Returning the resolved path removes that gap for every component that * existed at check time — those are now literal directories, not links that can * be re-pointed. It is not a total TOCTOU cure (only fd-relative operations * would be), but the window it closes is the one this function's own contract * claims to have closed. * * For a path that does not exist yet — a `write` to a new file — the deepest * existing ancestor is resolved and the not-yet-created tail is re-joined onto * it, so the canonical form is still returned rather than the raw input. */ export declare function resolveRealInsideRoot(absPath: string, ctx: Context): Promise; /** * `safeResolve` + symlink realpath containment check, returning the CANONICAL * path (WS-048). * * This used to return the unvalidated `abs` while validating `real`, so the * header's promise of a "containment check" did not extend to the value the * caller then opened. */ export declare function safeResolveReal(input: string, ctx: Context): Promise; /** * Project-root-relative variant of {@link safeResolveReal} for the codebase * tools whose documented input contract is "relative to projectRoot or * absolute" (codebase-skeleton, security-ast-scan, codebase-invariant-check, * codebase-ast-replace). The sibling file tools (edit/grep/glob/…) resolve * relative input against the session cwd — that is THEIR contract; these four * always resolved against the project root and their schemas say so. Routing * them through plain safeResolveReal silently changed that: with a nested * workingDir (worktrees, set_working_dir) a relative input resolved to the * wrong file. Containment is still enforced exactly as in safeResolveReal * (realpath + allowOutsideProjectRoot honored). */ export declare function safeResolveProjectPath(input: string, ctx: Context): Promise; /** * Truncate a diff (or similar text payload) to `maxBytes`, cutting at a line * boundary and appending an explicit marker. Used by the mutating file tools * (`write`, `edit`, `replace`) so their returned diffs stay inside the tool's * declared `maxOutputBytes` budget instead of relying on downstream clipping. */ export declare function truncateDiffPayload(diff: string, maxBytes: number): { text: string; truncated: boolean; }; /** * Truncate to `max` UTF-8 bytes keeping BOTH ends, appending a marker that * reports the true omitted byte count. Like `truncateHeadTail` and * `truncateDiffPayload`, marker room is reserved from the budget and both * slices are cut byte-accurately (not by UTF-16 code unit), so the result * never exceeds `max` — the contract `fetchTool`'s `maxOutputBytes` relies on. */ export declare function truncateMiddle(s: string, max: number): string; export declare function isBinaryBuffer(buf: Buffer): boolean; /** Unified byte cap for all command tool output fed to the model. */ export declare const COMMAND_OUTPUT_MAX_BYTES = 32768; /** * Collapse carriage-return overwrites the way a terminal would: `\r\n` becomes * `\n`, and a bare `\r` (progress redraw) keeps only the text after the LAST * `\r` on its physical line. Without this, a single progress bar that redraws * 200 times explodes into 200 lines. */ export declare function collapseCarriageReturns(text: string): string; /** * Collapse a run of `minRun`+ identical consecutive lines into the line once * plus a marker. Consecutive-only — it never reorders or dedups non-adjacent * lines, so diffs/source stay intact. */ export declare function collapseConsecutiveDuplicates(text: string, minRun?: number): string; /** * Truncate to `maxBytes` keeping BOTH ends — the head (what ran / early context) * and the tail (errors and summaries usually land last), biased ~45/55 toward * the tail. The result never exceeds `maxBytes`. */ export declare function truncateHeadTail(s: string, maxBytes: number): string; /** * Full token-saving pipeline for command tool output: strip ANSI → collapse * carriage-return progress → trim trailing whitespace → collapse identical * consecutive lines → squeeze blank-line runs → head+tail truncate to the cap. */ export declare function normalizeCommandOutput(raw: string, opts?: { maxBytes?: number | undefined; }): string; /** * Build a project-root-relative path shortener for search tool output. * * `grep` and `glob` emit one path per match, and the absolute project prefix * (`D:\long\path\to\project\`) is repeated on every single line — pure token * waste in the model's context, since the agent already works from the project * root. Stripping it makes match lines read like the paths the user and the * other tools use (`packages/tools/src/grep.ts:42:…`). * * Only paths that actually live under `root` are shortened. The search tools * resolve their base through `safeResolveReal`, which also admits * `~/.wrongstack`; turning those into a `../../..` chain would be strictly * worse than the absolute path, so they are returned untouched. Windows path * comparison is case-insensitive (a drive letter can arrive as `d:\` from one * source and `D:\` from another) but the returned suffix always preserves the * original casing. */ export declare function makeRootRelativizer(root: string): (absPath: string) => string; //# sourceMappingURL=_util.d.ts.map