/** How a bundled-resource reference was found in the body. */ export type SkillResourceKind = "link" | "path"; /** * Why a bundled-resource reference is reported. A ref that resolves under the * skill dir (or through a declared shared dir) is not reported at all; one that * does not is reported for exactly one of these reasons: * * - `missing` — the file exists nowhere the detector can see. The agent reads * the instruction and gets nothing, wherever the skill runs. * - `repo-root-only` — not beside the SKILL.md, but present at `repoRoot` (#382). * The agent usually runs from the repository root, so a body that says * "run `scripts/check.sh`" works while the skill runs inside its own repo and * breaks once the skill is installed anywhere else (a plugin install, a copied * skill). Not a miss, and not fine either — a separate fact. * * Two values rather than a boolean because each one drives a different * consequence downstream: only `missing` is scored or gated, and * `repo-root-only` is printed as a note. A consumer that reads only one of them * says which one in its type (`SkillResourceFinding<"missing">`). */ export type SkillResourceResolution = "missing" | "repo-root-only"; /** The reference itself, whatever its resolution. */ interface SkillResourceRef { /** The reference text exactly as written in the body (e.g. `scripts/run.sh`). */ readonly ref: string; /** * The path normalized relative to the skill dir — and, for `repo-root-only`, * equally the path relative to the repo root, since both resolve the same * relative string. */ readonly resolved: string; /** Whether the ref came from a markdown link `[..](..)` or an inline/path mention. */ readonly kind: SkillResourceKind; /** * 1-based line of the reference in the SKILL.md FILE — not in the body string * the detector was handed. The caller strips frontmatter before calling, so a * body-relative number is short by exactly that block and points the author at * the wrong line (#206); `lineOffset` is what closes the gap. */ readonly line: number; } /** * One file reference in a SKILL.md body that is not beside the SKILL.md. * `resolution` says why it was reported. For `.claude/skills/deploy/SKILL.md`: * * ```ts * // line 8 says "run `scripts/deploy.sh`"; only /scripts/deploy.sh exists * { ref: "scripts/deploy.sh", resolved: "scripts/deploy.sh", kind: "path", * line: 8, resolution: "repo-root-only" } * // line 9 says "see `scripts/smoke.sh`"; that file exists nowhere * { ref: "scripts/smoke.sh", resolved: "scripts/smoke.sh", kind: "path", * line: 9, resolution: "missing" } * ``` * * `SkillResourceFinding` on its own is either case: check `resolution` to tell * them apart. `SkillResourceFinding<"missing">` is only the first kind, so a * list typed with it (the scored one) cannot be handed a note. */ export type SkillResourceFinding = R extends SkillResourceResolution ? SkillResourceRef & { readonly resolution: R; } : never; export interface SkillResourceOptions { /** REQUIRED, injected existence check (disk: node:fs existsSync). */ readonly existsSync: (p: string) => boolean; /** * The repository root — the directory the agent usually runs from. A ref that * misses under the skill dir but exists here is reported as `repo-root-only` * instead of `missing`; a ref under a declared `sharedDirs` entry (below) * resolves here outright. Omitted → the detector sees only the skill dir and * every miss is `missing`. */ readonly repoRoot?: string; /** * OPT-IN shared-resource dirs — top-level dir names a repo shares across skills * (`.vigilesrc.json` `sharedDirs`, e.g. `["scripts", "references"]`). Many skill * libraries keep ONE top-level `scripts/` tree instead of a copy beside every * SKILL.md, so a ref like `scripts/promptfoo/x.py` lives at the repo root. When * a ref's FIRST path segment is a declared shared dir, it may ALSO resolve * against `repoRoot`. Scoped to declared dirs on PURPOSE: a ref OUTSIDE a * shared dir is never RESOLVED by a same-named repo-root file — at most it is * reported as `repo-root-only`, visibly, never dropped (feedback P1-4). */ readonly sharedDirs?: readonly string[]; /** * How many lines the caller stripped off the FRONT of the file before handing * over `skillBody` — added to every finding's `line` so the coordinate names a * line of the real SKILL.md. * * 🔴 Not cosmetic, and not the caller's to patch afterwards. `markdownRefs` * counts from one over whatever string it gets, so a detector fed a * frontmatter-stripped body emits coordinates short by exactly that block — * measured on a 5-line frontmatter: the ref on file line 11 printed as 6 * (#206). An author who opens the printed line finds something else and either * calls the finding false or edits the wrong line. Taking the offset as INPUT * keeps the number right where it is produced, instead of relying on every * consumer to remember; same shape as `doc-refs.ts`'s `firstLine + line`. * Defaults to 0 — a caller passing a whole file needs nothing. */ readonly lineOffset?: number; } /** * Reads a SKILL.md body, finds every file it mentions (`scripts/x.sh`, * `[guide](references/guide.md)`), and returns the ones that are NOT in the * skill's own folder. Files that are there are not returned at all. * * ```ts * // skillDir = .claude/skills/deploy, repoRoot = the repo * skillResourceIssues("Run `scripts/deploy.sh`.\nSee `scripts/smoke.sh`.", skillDir, { * existsSync, repoRoot, * }); * // → [{ ref: "scripts/deploy.sh", resolution: "repo-root-only", … }, * // { ref: "scripts/smoke.sh", resolution: "missing", … }] * ``` * * Despite the name, the result holds both kinds: `missing` (a real break) and * `repo-root-only` (works here, breaks when installed elsewhere — a note). The * split into two lists happens once, in `collectSurfaceFindings`. It only reads * the file system through the injected `existsSync`, so it runs on disk and in * the browser, and `lint` and `audit` both call it. */ export declare function skillResourceIssues(skillBody: string, skillDir: string, opts: SkillResourceOptions): readonly SkillResourceFinding[]; /** * What a `repo-root-only` finding means and what to do about it — ONE sentence * shared by every surface that prints the note (`audit`'s text and HTML report, * `lint`'s line and its CI annotation), so they cannot drift apart. * * The cure depends on the path's shape: `sharedDirs` declares a top-level * DIRECTORY, so a ref under one (`scripts/check.sh`) can be declared shared, * while a bare file at the root (`guide.md`) has no directory to declare and the * only cure is to ship it beside the SKILL.md. */ export declare function repoRootOnlyAdvice(f: SkillResourceFinding<"repo-root-only">): string; export {}; //# sourceMappingURL=skill-resources.d.ts.map