/** * Validation rules for Claude Code skills * * Thresholds and their provenance — do not read this list as "Anthropic says so". * Only the first is Anthropic's; the rest are VAT's, and two of them Anthropic * actively contradicts. The full audit is in the comment above * VALIDATION_THRESHOLDS below; read it before tuning any number here. * * - SKILL.md recommended: ≤500 lines (Anthropic's, verbatim) * - Total skill size: ≤2000 lines (VAT's; Anthropic counter-signals it) * - File count: ≤6 files (VAT's; Anthropic counter-signals it) * - Reference depth: ≤2 levels (VAT's; Anthropic's rule is ONE level) * * References: * - https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices * - docs/external/anthropic-skill-authoring-best-practices.md (dated cache of the above) * - https://github.com/anthropics/skills (official examples) */ import type { ValidationIssue } from '@vibe-agent-toolkit/agent-schema'; /** * Validation rule category * - required: Non-overridable rules (fundamental correctness) * - best_practice: Overridable rules (Anthropic recommendations) */ export type RuleCategory = 'required' | 'best_practice'; /** * Validation rule code */ export type ValidationRuleCode = 'BROKEN_INTERNAL_LINK' | 'CIRCULAR_REFERENCE' | 'OUTSIDE_PROJECT_BOUNDARY' | 'WINDOWS_BACKSLASH_IN_PATH' | 'LINK_TARGETS_DIRECTORY' | 'SKILL_LENGTH_EXCEEDS_RECOMMENDED' | 'SKILL_TOTAL_SIZE_LARGE' | 'SKILL_TOO_MANY_FILES' | 'REFERENCE_TOO_DEEP' | 'LINKS_TO_NAVIGATION_FILES' | 'DESCRIPTION_TOO_VAGUE' | 'NO_PROGRESSIVE_DISCLOSURE' | 'PACKAGED_UNREFERENCED_FILE'; /** * Validation rule definition */ export interface ValidationRule { code: ValidationRuleCode; category: RuleCategory; message: (context: Record) => string; fix: string; example?: string; link?: string; } /** * Validation rule definitions */ export declare const VALIDATION_RULES: Record; /** * Validation thresholds — mostly VAT's, not Anthropic's (see the audit below) * * @vendor-claim reviewed=2026-07-30 verify=Re-fetch https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices and diff it against docs/external/anthropic-skill-authoring-best-practices.md * * The original header ("based on Anthropic guidance") overstated the provenance. * Of the six numbers here, the repo's own cached copy of Anthropic's guidance * supports only RECOMMENDED_SKILL_LINES (500), and it explicitly *disclaims* * MAX_DESCRIPTION_CHARS_CLAUDE_CODE (250) — Anthropic documents a 1024-character * maximum, while 250 is VAT's own reading of where the Claude Code `/skills` * listing truncates. MAX_TOTAL_LINES, MAX_FILE_COUNT, MAX_REFERENCE_DEPTH and * MIN_DESCRIPTION_LENGTH are VAT-originated. * * Re-verified against the live page on 2026-07-30. That pass sharpened two of * those verdicts from "unsupported" to "contradicted", which is a stronger claim: * * - MAX_REFERENCE_DEPTH (2) is CONTRADICTED, not merely VAT-originated. Anthropic: * "Keep references one level deep from SKILL.md", and their "Bad example: Too * deep" is literally `SKILL.md → advanced.md → details.md` — the exact chain * REFERENCE_TOO_DEEP's own `example` string above blesses as "2 hops, OK". VAT * is deliberately one hop laxer than the vendor here; that is a product * decision, not an oversight, but nothing in this file may imply Anthropic * endorses 2. * - MAX_TOTAL_LINES (2000) and MAX_FILE_COUNT (6) are COUNTER-SIGNALLED, not just * silent. Anthropic's runtime-environment guidance says "Bundle comprehensive * resources: Include complete API docs, extensive examples, large datasets; no * context penalty until accessed", and "No context penalty for large files". * Their own pdf/ example ships 7 files, one past MAX_FILE_COUNT. VAT flags large * bundles as a maintainability/reviewability signal, which the vendor does not. * - MIN_DESCRIPTION_LENGTH (50) stays SILENT-not-contradicted: Anthropic rejects * vague descriptions ("Helps with documents") on specificity, never on length. * * The `reviewed=` date above is now a real review date, not the cache's Fetched * date. Do not resolve any of these divergences by editing a number here — the * values decide what fires on adopter trees and are the repo owner's call. */ export declare const VALIDATION_THRESHOLDS: { /** Recommended maximum lines for SKILL.md */ readonly RECOMMENDED_SKILL_LINES: 500; /** Maximum total lines for entire skill (all files) */ readonly MAX_TOTAL_LINES: 2000; /** Maximum number of files in skill */ readonly MAX_FILE_COUNT: 6; /** Maximum reference depth (levels of nested links) */ readonly MAX_REFERENCE_DEPTH: 2; /** Minimum description length (characters) */ readonly MIN_DESCRIPTION_LENGTH: 50; /** * Claude Code /skills listing truncates descriptions at this character count (since v2.1.86). * Descriptions longer than this lose their tail — critical trigger keywords may be cut. */ readonly MAX_DESCRIPTION_CHARS_CLAUDE_CODE: 250; }; /** * Navigation file patterns to detect. * * ONE canonical spelling per name. Every consumer matches these * **case-insensitively** — via {@link isNavigationBasename} / * {@link isNeverPackagedBasename} for basename lookups, or via * {@link toAnyDepthGlobs} for glob lanes. * * This list used to enumerate spellings (`README.md` *and* `readme.md`, * `index.md` *and* `INDEX.md`, …) and that approach provably cannot win: it * never contained `Readme.md`, the single most common real spelling, nor * `ReadMe.md` or `README.MD`. Do not re-add a second spelling of any name — * matching handles case, and a duplicate implies enumeration still matters. */ export declare const NAVIGATION_FILE_PATTERNS: readonly ["README.md", "index.md", "toc.md", "overview.md"]; /** * Repo-internal agent-instruction files. Deliberately a SEPARATE list from * {@link NAVIGATION_FILE_PATTERNS}: a README is real content at the wrong * granularity, whereas these files are guidance *about the repository they live * in* and are meaningless — or actively misleading — once copied into a * distributed bundle. * * Two distinct harms, both observed: * * 1. **Silent mis-resolution.** Two packages' `CLAUDE.md` files bundled under * `resourceNaming: basename` collide on one destination; one wins, and every * link to either source then resolves to the winner's content. * 2. **Unintended instruction loading.** Claude Code loads `CLAUDE.md` files * found in subdirectories *under the working directory* on demand, when it * reads a file in that directory. A skill installed project-locally (e.g. * `.claude/skills//`) is under the working directory, so a bundled * `CLAUDE.md` is read as instructions the moment the agent opens a reference * beside it. (Skills installed outside the cwd — `~/.claude/skills`, plugin * directories — are not affected by this second harm. `AGENTS.md` and * `GEMINI.md` are not loaded as memory by Claude Code — it reads `CLAUDE.md`, * and picks up an `AGENTS.md` only where a `CLAUDE.md` imports it explicitly * (`@AGENTS.md`) or `/init` incorporates it — so they are listed here for the * portability and collision reasons, and treated identically to `CLAUDE.md`.) * * @vendor-claim reviewed=2026-08-02 verify=Re-read https://code.claude.com/docs/en/memory — "How CLAUDE.md files load" for harm 2, and the "AGENTS.md" section for the AGENTS.md/GEMINI.md sentence */ export declare const AGENT_INSTRUCTION_FILE_PATTERNS: readonly ["CLAUDE.md", "CLAUDE.local.md", "AGENTS.md", "GEMINI.md"]; /** * Basenames a GLOB `files:` entry never packages into a **skill bundle**. * * Two tiers, deliberately composed here rather than merged at the source: * * - {@link AGENT_INSTRUCTION_FILE_PATTERNS} — never packaged on ANY surface. * - {@link NAVIGATION_FILE_PATTERNS} — never packaged into a *skill bundle*, and * **only** there. A plugin-root `README.md` is the plugin's front page (measured * 2026-08-02: 50 of 86 installed plugins ship one; 57 of 94 when first measured), * so the plugin tree-copy must import the agent-instruction list alone. Merging * the two lists would strip the front page off three in five real plugins. The * population moves as plugins are installed and removed — the ratio is the load- * bearing part, so re-measure before citing the count as current. * * Only globs consult this list. Naming `source: README.md` explicitly is an * unambiguous instruction to ship that file; a glob is a net, not a declaration, * and does not get to launder an exemption for a file it never named. */ export declare const NEVER_PACKAGE_IN_SKILL_BUNDLE: readonly ["CLAUDE.md", "CLAUDE.local.md", "AGENTS.md", "GEMINI.md", "README.md", "index.md", "toc.md", "overview.md"]; /** * Is this basename a navigation file (README/index/toc/overview, any case)? * * Skill-bundle scope only — see {@link NEVER_PACKAGE_IN_SKILL_BUNDLE}. */ export declare const isNavigationBasename: (name: string) => boolean; /** * Is this basename a repo-internal agent-instruction file (CLAUDE.md/AGENTS.md/…, any case)? * * Case-insensitivity is not cosmetic here: on APFS and NTFS, `Claude.md` and * `claude.md` satisfy Claude Code's project-local instruction lookup exactly as * `CLAUDE.md` does, so a case-sensitive check leaves the harm fully reachable. */ export declare const isAgentInstructionBasename: (name: string) => boolean; /** * Is this basename one a GLOB `files:` entry never packages into a skill bundle? * * The basename-lane counterpart of {@link toAnyDepthGlobs}; both read * {@link NEVER_PACKAGE_IN_SKILL_BUNDLE}. Any lane doing a `Set.has(basename)` * against that constant must call this instead — a raw Set is case-sensitive and * is exactly the gap this exists to close. */ export declare const isNeverPackagedBasename: (name: string) => boolean; /** * Expand basenames into globs matching them at the tree root AND at any depth, * **regardless of case**. * * `**\/name` alone is not portable across matchers for a root-level hit, so both * depths are emitted. Shared by the presence detector's include globs and the * plugin tree-copy's exclude globs so the two can never drift apart — and folded * to case-insensitive so neither can be evaded by spelling the file `Claude.md`. */ export declare function toAnyDepthGlobs(patterns: readonly string[]): string[]; /** * Create a validation issue from a rule */ export declare function createIssue(rule: ValidationRule, context?: Record, location?: string): ValidationIssue; //# sourceMappingURL=validation-rules.d.ts.map