/** The package whose skills are linked. */ export declare const VIGILES_PACKAGE = "vigiles"; /** What occupies `/` today, as the IO edge observed it. */ export type SkillEntry = { readonly kind: "missing"; } | { readonly kind: "link"; /** The link's stored target, exactly as `readlink` returns it. */ readonly target: string; /** Where it resolves, or null when it dangles. */ readonly resolvesTo: string | null; } | { readonly kind: "directory"; } | { readonly kind: "file"; }; /** What `init` should do about one skill. */ export type SkillLinkDecision = { readonly name: string; /** `replace`: an earlier `init`'s link that now points at the wrong place. */ readonly action: "create" | "replace"; readonly target: string; } | { readonly name: string; readonly action: "keep"; } | { readonly name: string; readonly action: "refuse"; readonly reason: string; }; /** The link `init` wants for one skill. */ export interface WantedLink { /** The relative target to write (and to recognise on a re-run). */ readonly target: string; /** The real path of the shipped skill, or null while it is not installed. */ readonly real: string | null; } /** * Decide one entry. A link with OUR target is ours even while it dangles — that * is exactly how a committed link looks in a clone that has not run * `npm install` yet, and calling it foreign would make re-running `init` noisy * in precisely the place it is meant to help. */ export declare function decideSkillLink(name: string, entry: SkillEntry, want: WantedLink): SkillLinkDecision; /** Where the package is, as the project spells it. */ export type PackageSite = /** Installed: `/node_modules/vigiles`, inside the repository. */ { readonly kind: "installed"; readonly dir: string; } /** Not installed yet: where npm puts the devDependency `init` declares. */ | { readonly kind: "expected"; readonly dir: string; } /** * Not installed, and the project is a member of a workspace: npm and yarn * hoist its dependencies to the workspace ROOT's `node_modules`, pnpm keeps * them in the member's own, and a hoist can be refused by a version * conflict. Which one happens is decided by the install, so no link is made * before it — a guessed link can dangle for good. */ | { readonly kind: "unsure"; readonly reason: string; }; /** What the walk may ask of the filesystem. */ export interface PackageProbe { /** Is `dir` an installed vigiles package (its `package.json` names it)? */ readonly isPackage: (dir: string) => boolean; /** Is `dir` the repository root (it holds `.git`)? The walk stops there. */ readonly isRepoRoot: (dir: string) => boolean; /** Is `dir` a workspace root (`workspaces` in package.json, or pnpm-workspace.yaml)? */ readonly isWorkspaceRoot: (dir: string) => boolean; } /** * Node's own lookup — the first `node_modules/vigiles` up the tree — bounded by * the repository root, because a committed link that leaves the repository is a * link only this machine can follow. */ export declare function locatePackage(project: string, probe: PackageProbe): PackageSite; /** * Is the real path `path` the project's own real path or somewhere under it? * Both must be physical (symlink-free), or a link could pass for a child. */ export declare function isWithinProject(project: string, path: string): boolean; /** The facts that decide whether linking makes sense at all here. */ export interface LinkFacts { /** The project IS vigiles — its skills are the plugin's own `skills/`. */ readonly isVigilesItself: boolean; /** `package.json` declares vigiles (so `npm install` will put it in node_modules). */ readonly dependsOnVigiles: boolean; /** The package already resolves from the project. */ readonly installed: boolean; } /** * Why NOT to link, or null to go ahead. A link into a `node_modules/vigiles` * that no install will ever create is a dangling link committed to the repo — a * worse state than no link, because it looks like the job was done. */ export declare function linkPrecondition(facts: LinkFacts): string | null; /** Everything {@link planSkillLinks} needs, observed by the IO edge. */ export interface SkillLinkInput { /** The skills the package ships. */ readonly names: readonly string[]; readonly site: Exclude; /** * The skills home as it PHYSICALLY exists (`.claude` may itself be a link). * A relative link target resolves against the directory that holds the link. */ readonly physicalHome: string; readonly entries: (name: string) => SkillEntry; /** The real path of the installed skill, or null. */ readonly realOf: (name: string) => string | null; } /** The decisions, and whether the links will dangle until `npm install`. */ export interface SkillLinkPlan { readonly pending: boolean; readonly decisions: readonly SkillLinkDecision[]; } /** * A link target as it is committed: `/` separators on every OS. `relative()` on * Windows yields `..\\..\\node_modules\\…`, and a clone on Linux or macOS * reads those backslashes as ordinary filename characters, so the link dangles. */ export declare function portableTarget(target: string, separator?: string): string; export declare function planSkillLinks(input: SkillLinkInput): SkillLinkPlan; /** The three filesystem calls that write a link; the IO edge passes the real ones. */ export interface LinkIo { readonly symlink: (target: string, path: string) => void; readonly rename: (from: string, to: string) => void; readonly unlink: (path: string) => void; } /** Where a replacement link is built before it takes the old one's place. */ export declare function relinkTempPath(entry: string): string; /** * Write the link `decision` asks for. A `replace` never leaves a gap: the new * link is built under a temporary name beside the old one and renamed over it, * which is atomic. Removing the old link first would lose it for good when the * new one cannot be created (a Windows account without the symlink privilege, * a full disk) — a skill that worked yesterday gone, and only a warning to show * for it. Any failure leaves the old link in place and throws. */ export declare function writeSkillLink(io: LinkIo, entry: string, target: string, action: "create" | "replace"): void; /** Why a `symlink` call failed, with the fix when there is a known one. */ export declare function linkFailureReason(code: string, message: string): string; /** One skill's result after the IO ran. */ export type SkillLinkResult = { readonly name: string; readonly status: "created" | "relinked" | "present"; } | { readonly name: string; readonly status: "skipped"; readonly reason: string; }; /** What `init` did about the skills for one harness. */ export type SkillLinkOutcome = { readonly kind: "linked"; /** Repo-relative skills home, e.g. `.claude/skills`. */ readonly home: string; /** * The same home with every symlink resolved, repo-relative with `/`. * Git refuses a pathspec that runs through a symlink, so the commit hint * names this one. */ readonly physicalHome: string; readonly pending: boolean; readonly results: readonly SkillLinkResult[]; } | { readonly kind: "not-linked"; readonly reason: string; }; /** At least one shipped skill now reaches the agent through a link. */ export declare function skillLinksUsable(outcome: SkillLinkOutcome): boolean; /** * The lines `init` prints. Skipped skills are named one by one: the user loses * that skill, and the reason is theirs to act on. */ export declare function formatSkillLinks(outcome: SkillLinkOutcome): readonly string[]; //# sourceMappingURL=skill-links.d.ts.map