/** * The frame of a path — WHICH DIRECTORY it is relative to — named by its TYPE * rather than by a comment (#281). * * 🔴 WHY THIS EXISTS. Every path the CLI handled was a bare `string`, so a path * relative to a nested bundle, one relative to the repository and an absolute one * were the same type, and nothing stopped a caller from printing the wrong one. * `overBundles` handed each check ONE `root: string` that had to be the discovery * base, the base of the config's globs AND the frame of every printed path at * once. Under `bundles: "all"` that root was the nested bundle, so `include` * resolved from the wrong directory and `::warning file=skills/x/SKILL.md` named a * file that did not exist from where the command ran — while a root-bundle skill * of the same name made the annotation land on the WRONG real file, which looks * like success. * * Three frames, two of them branded: * - {@link RepoPath} — relative to {@link Frame.root}, the directory whose * `.vigilesrc.json` governs the run. Every printed or annotated path. * - {@link BundlePath} — relative to one {@link Bundle}'s own directory. What a * per-bundle scan reports. * - an absolute path — a plain `string`, the only thing the filesystem takes. * * The brand is `Opaque` from `ts-essentials` (a `unique symbol` key, so no object * literal can forge it), imported as a TYPE ONLY: nothing of it survives the * build. It must not reach a public `.d.ts` either — this module is internal and * the API report (`api-surface/*.api.md`) is the gate that would show a leak. * * 🔴 THE ONE CONVERSION POINT. A `RepoPath` is minted here and nowhere else: * {@link Frame.repo} from an absolute path, {@link Bundle.path} from a * `BundlePath`. A cast to `RepoPath` outside this file is refused by ESLint * (`local/frame-mint` in `eslint.config.mjs`), so "where did this path get its * frame" always has the answer "in frame.ts". * * WHAT IS UNREPRESENTABLE AND WHAT IS ONLY DETECTABLE. The CLI's output boundary * (`ghAnnotate`, the per-bundle check context) takes `RepoPath`, so handing it a * bundle-relative or absolute string is a tsc error. Upstream of that, scan-core * keeps reporting plain bundle-relative strings, because its browser twin * (`scan-files.ts`) shares those shapes and has no filesystem to have a frame * over. {@link Bundle.scanned} is the seam where such a string gets its frame; * a relative string that is secretly repo-relative still passes it, and that * residue is caught by tests, not by the compiler. * * NODE-FREE: path ops come from `../posix-path.js`, and the caller supplies the * working directory, so this module has no process state of its own. */ import type { Opaque } from "ts-essentials"; /** A `/`-separated path relative to {@link Frame.root}. `.` is the root itself. */ export type RepoPath = Opaque; /** A `/`-separated path relative to one {@link Bundle}'s own directory. */ export type BundlePath = Opaque; /** One scored directory: the lint target itself, or a nested plugin bundle. */ export interface Bundle { /** Absolute directory of the bundle. */ readonly abs: string; /** Where the bundle sits, from the frame root (`.` when it IS the root). */ readonly at: RepoPath; /** A path this bundle's scan reported, re-expressed from the frame root. */ path(p: BundlePath): RepoPath; /** * The seam for scan-core's plain strings, which are bundle-relative by * contract (see the header). An ABSOLUTE string is accepted too and converted * directly — `hook-block-ineffective` reports its script that way. */ scanned(p: string): RepoPath; } /** The single owner of "relative to which directory", built once per command. */ export interface Frame { /** Absolute directory every {@link RepoPath} is relative to. */ readonly root: string; /** The one conversion from an absolute path. Throws on a relative input. */ repo(abs: string): RepoPath; /** Describe the bundle at an absolute directory, in this frame. */ bundle(abs: string): Bundle; } /** * The frame for a command run from `cwd` against `target` (absolute). * * The root is `cwd` — the project root the CLI resolved once with * {@link findProjectRoot} and runs from — UNLESS the target lies outside it. A foreign * target (`vigiles lint ../other`) is somebody else's repository, and its paths * mean something from ITS root; relative to `cwd` they would all start with * `../`, and a foreign lint must never let the caller's own files satisfy the * target's `sharedDirs` references. That exception was `sharedDirsRootFor` in * the CLI, applied to `sharedDirs` alone; it is owned here now, so `include`, * `exclude`, `sharedDirs` and every printed path agree on one root. */ export declare function frameFor(cwd: string, target: string): Frame; /** The frame rooted at an absolute directory. */ export declare function frameAt(root: string): Frame; /** * The target of a markdown link written INTO the file `from` that points at * `to`; both are repo-relative. A link resolves from the directory of the file * it sits in, so a compiled `plugin/CLAUDE.md` must link `../docs/x.md`, not * `docs/x.md` (#371). The ONE place a repo path becomes a link: every renderer * that writes a path as a link calls this. */ export declare function linkFrom(from: string, to: string): string; /** * The repo path of a spec's secondary target `target`, which the spec names * relative to its primary output `primary`'s directory: `docs/CLAUDE.md` with * `../AGENTS.md` is `AGENTS.md`. */ export declare function targetBeside(primary: string, target: string): string; /** * The project root for a command started in `start` (absolute): the ONE answer * to "relative to which directory", resolved once by the CLI, so running * `vigiles lint` from `src/` gives the same findings as from the root (#371). * * From `start` upward: * 1. the nearest directory holding `.vigilesrc.json` — a package in a * monorepo that has its own config is its own project; * 2. else the git work-tree top (the nearest directory holding `.git`, a * directory or a worktree's file); * 3. else `start` itself. * * Inside a git checkout the config search stops at the work-tree top, so a * config ABOVE the checkout (a home directory) is never read. Outside any * checkout it climbs to the filesystem root. `exists` is the filesystem port; * the caller passes `existsSync`. */ export declare function findProjectRoot(start: string, exists: (abs: string) => boolean): string; /** * A path argument typed in `from`, re-anchored for a command that runs at * `root` (both absolute) — so `vigiles lint CLAUDE.md` typed in `pkg/` reads * `pkg/CLAUDE.md`. Typed AT the root it is returned exactly as typed, so a * message quotes what the user wrote. Absolute (a Windows drive-letter or UNC * path included), or leaving the root, it is absolute: `../../etc/passwd` * names the same file and reads as a different one. */ export declare function rebaseArg(typed: string, from: string, root: string): string; //# sourceMappingURL=frame.d.ts.map