/** * Reference lint — atomization safety net (Step 1). * * The per-kind file filters in {@link ./builtin-providers.ts} decide which * files belong to ONE asset purely structurally (manifest dir + a fixed set of * conventional companion files/subdirs). They never read the manifest *body*, * so a manifest that textually points at a helper file living in a * non-conventional location (e.g. a skill referencing `modes/write.md` or * `scripts/run.ts`) silently drops those bytes from the atomized asset. * * This module scans the *text* of the already-included files for path-like * references, resolves each against the asset root, and classifies it. The * `uncovered-in-dir` class is the actionable one: a real file or directory that * lives inside the asset's own directory, is referenced by the asset, yet was * NOT captured by the structural filter. Discovery surfaces those as warnings. * * Pure classification — no auto-inclusion here. Closure (Step 3) and the * full-dir fallback (Step 4) build on the same scan. */ import type { RequiresEdge } from "./requires-graph.js"; /** * How a textual path reference relates to the asset's captured byte set. * * - `covered` — resolves to a file/dir already in the asset's filter. * - `uncovered-in-dir` — resolves to an existing file/dir INSIDE the asset * root that the structural filter dropped (actionable). * - `external` — resolves outside the asset root (cross-asset / repo * file); not this asset's bytes to bundle. * - `unresolved` — points at nothing on disk (prose / false positive). */ export type ReferenceClass = "covered" | "uncovered-in-dir" | "external" | "unresolved"; /** A single path-like reference found in an asset's text, with its verdict. */ export interface AssetReference { /** File within the asset that contains the reference (relative to assetRoot, posix). */ fromFile: string; /** Raw reference token exactly as written in the source. */ raw: string; /** Path relative to assetRoot when it stays inside; the normalized join otherwise. */ resolved: string; /** `true` when `resolved` names a directory on disk (vs a file). */ isDir: boolean; classification: ReferenceClass; } /** A discovery warning emitted for an `uncovered-in-dir` reference. */ export interface ReferenceWarning { /** Relative path (from source root) of the manifest whose asset has the gap. */ path: string; warning: string; /** * Optional machine-readable tag for advisory grouping. Consumers (e.g. the * sync layer) key off this instead of string-matching `warning`. Currently * only `"missing_sha256"` is emitted — one per asset with no pinned hash — * so the CLI can collapse many into a single summary line. */ code?: string; } /** * Extract path-like candidate tokens from a chunk of markdown/text. * * Three sources, union-ed and de-duped: * 1. Markdown link / image targets — `[txt](path)` and `![alt](path)`. * 2. Inline code spans — `` `modes/write.md` `` (tables, prose). * 3. Bare path tokens — `scripts/run.ts` mentioned in running text. */ export declare function extractPathCandidates(text: string): string[]; /** * Extract standalone single-segment slash-command names (without the leading * slash) from markdown/text — both prose (`Run a /grilling session`) and inline * code (`` `/grilling` ``). De-duped, first-seen order. Multi-segment paths * (`/api/users`), extensions (`/foo.md`), non-standalone slashes (`path/x`), and * URLs never match — those are path references handled by {@link scanAssetReferences}. */ export declare function extractSlashCommands(text: string): string[]; /** * Scan an asset's scannable text files for standalone slash-command names, * unioned and de-duped. Shared by both discovery paths (local FS + virtual tree) * so the name-based body-link pass stays in strict parity. */ export declare function scanSlashCommandNames(afs: AssetFs, includedFiles: string[]): string[]; /** * Map each SKILL name to the ref(s) sharing it, for slash-command resolution. * Slash commands invoke skills, so only skills are candidate targets — a * same-named non-skill must not make a legit `/`→skill edge ambiguous. * Shared by both discovery paths so the matching rule lives in one place. */ export declare function buildSkillNameToRefs(assets: ReadonlyArray<{ kind: string; name: string; ref: string; }>): Map; /** Resolve a slash-command name to a target ref, or undefined if ambiguous/self. */ export declare function resolveSlashCommand(nameToRefs: Map, name: string, selfRef: string): string | undefined; /** Append a deduped soft `body-link` edge keyed by `from`/`to`. */ export declare function pushBodyLinkEdge(edges: RequiresEdge[], seen: Set, from: string, to: string): void; /** * Minimal filesystem view of a single asset, rooted at the asset directory. * Both discovery paths implement it so the scan/closure logic is shared * verbatim: {@link createNodeAssetFs} over `node:fs` (local) and the virtual * tree's blob index (store). All paths are posix, relative to the asset root. */ export interface AssetFs { /** Text content of a file, or `null` if absent/unreadable. */ readText(rel: string): string | null; /** Classify a path: a file, a directory, or absent. */ statKind(rel: string): "file" | "dir" | "absent"; /** Files under `rel` (recursive), posix-relative to the root. A file `rel` yields `[rel]`. */ listFilesUnder(rel: string): string[]; } /** An {@link AssetFs} backed by the local filesystem under `assetRoot`. */ export declare function createNodeAssetFs(assetRoot: string): AssetFs; /** * Scan an asset's text files for path references and classify each one. * * @param assetRootOrFs Absolute asset-root path, or an {@link AssetFs}. * @param includedFiles Filter file list, relative to the asset root (posix). * @returns One {@link AssetReference} per distinct (fromFile, resolved) pair. */ export declare function scanAssetReferences(assetRootOrFs: string | AssetFs, includedFiles: string[]): AssetReference[]; /** * Result of a reference-closure expansion: the final file set plus the paths * that were pulled in beyond the structural seed (for observability). */ export interface ClosureResult { /** Final file list (relative to the asset root, sorted). */ files: string[]; /** Files added by closure that the structural filter had dropped. */ added: string[]; } /** * Expand a structural file set to its reference closure: repeatedly scan the * asset's text files, and for every `uncovered-in-dir` reference pull the * target file (or whole directory) into the set, then re-scan newly added text * files — to a fixpoint. Pure in-asset expansion; never escapes the root. * * @param assetRootOrFs Absolute asset-root path, or an {@link AssetFs}. * @param seedFiles Structural filter file list, relative to the root. */ export declare function closeOverReferences(assetRootOrFs: string | AssetFs, seedFiles: string[]): ClosureResult; /** * Lint an asset: return one warning per distinct uncovered in-dir reference * target. The structural filter dropped these bytes even though the asset's * own text points at them. * * @param manifestRelPath Manifest path from source root (for the warning). * @param assetRootOrFs Absolute asset-root path, or an {@link AssetFs}. * @param includedFiles Filter file list, relative to the asset root. */ export declare function lintUncoveredReferences(manifestRelPath: string, assetRootOrFs: string | AssetFs, includedFiles: string[]): ReferenceWarning[]; //# sourceMappingURL=reference-lint.d.ts.map