/** * Skill catalog lockfile. * * The lockfile (`qodo.lock.yaml`) pins each catalog entry to a content * hash, giving consumers reproducible boots when the catalog includes * third-party skills. Without it the SDK behaves dynamically — discovery * is authoritative. * * Spec: `knowledge/doc/skills-discovery.md` §5–6. * * ## Boot semantics * * 1. Manager looks for `qodo.lock.yaml` at the SDK construction CWD or * alongside `agent.toml`. Absence is silent. * 2. For each entry, the manager re-hashes the on-disk skill directory * and compares against the lockfile's `sha256`. * 3. Match → use as-is. Drift → emit `sdk.skill.lock_drift` and throw. * Unreachable → emit `sdk.skill.lock_unreachable` and throw. * 4. Skills not in the lockfile are still discovered + included. * * ## File format * * The SDK ships no YAML serializer for skill bodies and runs zero * runtime deps. The lockfile is therefore stored as a YAML subset the * skill-frontmatter parser already handles plus a hand-rolled writer. * * schemaVersion: 1 * skills: * - fqn: anthropic-skills/code-review@1.2.0 * source: * type: package * packageName: "@anthropic-skills/core" * sha256: "abc123...def" * - fqn: local/security@0.0.0 * source: * type: project * sha256: "def456...abc" * * ## Hashing contract * * `computeSkillHash` produces a stable digest by: * * 1. Listing every file under the skill directory (recursive), sorted. * 2. For each file: hash `relPath\0sha256(content)\0`, with CRLF folded * to LF in text (NUL-free, valid UTF-8) files. * 3. Hash the concatenation; return its hex digest. * * Two skill directories with byte-identical files at identical paths * produce identical hashes; so do CRLF and LF checkouts of the same text * files. Permissions, timestamps, inodes are out * of scope — drifting timestamps alone would force needless re-locks. */ import type { ParsedSkill, SkillSource } from './types.js'; /** Current lockfile schema version. */ export declare const LOCKFILE_SCHEMA_VERSION = 1; /** Default lockfile basename. Manager looks beside `agent.toml` + at CWD. */ export declare const LOCKFILE_BASENAME = "qodo.lock.yaml"; /** One entry in the lockfile. */ export interface LockfileEntry { /** Fully-qualified `/@` identifier. */ readonly fqn: string; /** Discriminated source identity (matches the manager's `SkillSource`). */ readonly source: SkillSource; /** Hex-encoded SHA-256 from {@link computeSkillHash}. */ readonly sha256: string; } export interface Lockfile { readonly schemaVersion: number; readonly skills: readonly LockfileEntry[]; } /** Result of `validateLockfileEntry` for one record. */ export type LockfileValidation = { readonly outcome: 'ok'; readonly actualSha256: string; } | { readonly outcome: 'drift'; readonly actualSha256: string; readonly expectedSha256: string; } | { readonly outcome: 'unreachable'; readonly reason: string; }; export declare class LockfileParseError extends Error { constructor(message: string); } export declare class LockfileValidationError extends Error { readonly fqn: string; readonly kind: 'drift' | 'unreachable'; constructor(fqn: string, kind: 'drift' | 'unreachable', message: string); } /** * Find the lockfile at the manager's CWD or one of the supplied search * paths. Returns the first match's path; `null` when none exist. */ export declare function findLockfile(searchPaths: readonly string[]): Promise; /** * Read + parse a lockfile from `lockfilePath`. Throws * {@link LockfileParseError} on schema violations. */ export declare function loadLockfile(lockfilePath: string): Promise; /** * Parse a lockfile from a raw YAML string. Public for tests and the * forthcoming CLI's lint/validate commands. */ export declare function parseLockfile(text: string): Lockfile; /** * Serialize a lockfile structure to its on-disk YAML form. Output is * deterministic — entries sort by FQN; the writer does not depend on * input order. */ export declare function writeLockfile(lockfile: Lockfile): string; /** * Compute a deterministic SHA-256 hash of a skill's directory contents. * * Walks the directory recursively, sorts the file list, and digests * `\0\0` for each file. Text content is hashed * with CRLF folded to LF (see {@link normalizeLineEndings}), so the digest * matches across a Windows and a POSIX checkout of the same skill. * * Symlinks are followed if they resolve inside the skill directory; * symlinks pointing outside are skipped (and recorded in the * `skipped` list so the caller can surface the audit if needed). */ export declare function computeSkillHash(skillDir: string): Promise<{ readonly sha256: string; readonly fileCount: number; readonly skipped: readonly string[]; }>; /** * Validate one lockfile entry against the current on-disk skill. Returns * a structured outcome the manager translates to telemetry + boot * behavior. */ export declare function validateLockfileEntry(entry: LockfileEntry, skill: ParsedSkill | undefined): Promise; /** * Build a lockfile from an already-discovered catalog. The CLI's * `qodo skills lock` op calls this after a fresh `discover()`. */ export declare function buildLockfile(skills: readonly ParsedSkill[]): Promise; //# sourceMappingURL=lockfile.d.ts.map