tools/skill-validator.md
An LLM-readable validation prompt for skills following the Agent Skills specification.
Before running inference-based validation, run the deterministic validator:
uv run --python 3.11 tools/validate_skills.py --json path/to/skill-dir
This checks 10 rules deterministically: SKILL-01, SKILL-02, SKILL-03, SKILL-04, SKILL-05, SKILL-06, SKILL-07, PATH-02, SEQ-02, TPL-01.
Review its JSON output. Skip any rule that produced zero findings — it is already verified. A rule that produced findings still gets reviewed (SKILL-06's what-and-when check benefits from judgment). The 10 rules that need judgment are PATH-01, PATH-03, PATH-04, PATH-05, STEP-04, STEP-05, SEQ-01, REF-01, REF-02, REF-03.
If no findings are generated (from either pass), the skill passes validation.
SKILL.md and all supporting files.prompt: in src/core-skills/module.yaml or src/bmm-skills/module.yaml. The installer writes these to {project-root}/_bmad/config.toml (team scope) and config.user.toml (user scope); _bmad/custom/ may override either. Examples: project_name, output_folder, communication_language, planning_artifacts, project_knowledge.customize.toml, in its [workflow] table (most skills) or [agent] table (agent skills), layered with _bmad/custom/<skill-name>.toml and .user.toml.spec_file, date, status).SKILL.md invokes render_skill.py, which renders the skill's Markdown files (entry point workflow.md; SKILL.md excluded) into an immutable snapshot before execution. Only rendered skills may use compile-time tokens. Every other skill interpolates customization values itself at runtime.Three layouts coexist. None is preferred, and the validator does not enforce a choice between them:
SKILL.md, with optional supporting files (references/, templates/, checklist.md). The common case.step-NN-name.md beside SKILL.md at the skill root.steps/ subdirectory — steps/step-NN-name.md.Path resolution differs between the last two; see PATH-01.
| Form | Resolved by | Valid where |
|---|---|---|
{name} | the agent, at runtime | anywhere |
{project-root}, {skill-root} | the agent, at runtime — the project working directory and the skill's own directory | anywhere |
{workflow.key} | render_skill.py at render time, or the agent from resolve_customization.py JSON output | any skill with a [workflow] table in customize.toml |
{agent.key} | the agent, from resolve_customization.py JSON output | agent skills |
{{.key}} | render_skill.py, at render time | rendered skills only |
{{config.key}} | render_skill.py, at render time | rendered skills only |
{{name}} (no leading dot) | nothing — survives verbatim into the generated artifact | templates and the artifacts they seed |
[[bmad-snapshot:file.md]] | render_skill.py, at render time | rendered skills only |
The distinction between {{name}} and {{.name}} matters: the first is an artifact placeholder the consumer of the generated document fills in later; the second is a substitution baked in at render time. See REF-01 and TPL-01.
SKILL.md (exact case).SKILL.md as the skill entrypoint.name in FrontmatterSKILL.mdname field.--- delimited frontmatter block and check for name:.name: <skill-name> to the frontmatter.description in FrontmatterSKILL.mddescription field.--- delimited frontmatter block and check for description:.description: '<what it does and when to use it>' to the frontmatter.name FormatSKILL.mdname value must be bmad or start with bmad-, using only lowercase letters, numbers, and single hyphens between segments.^(?:bmad|bmad-[a-z0-9]+(?:-[a-z0-9]+)*)$.bmad-my-skill).name Must Match Directory NameSKILL.mdname value in SKILL.md frontmatter must exactly match the skill directory name. The directory name is the canonical identifier used by installers, manifests, and skill references throughout the project.name: frontmatter value against the basename of the skill directory (i.e., the immediate parent directory of SKILL.md).name: value to match the directory name, or rename the directory to match — prefer changing name: unless other references depend on the current value.description QualitySKILL.mddescription must state both what the skill does AND when to use it. Max 1024 characters.SKILL.md--- frontmatter delimiter and check it is non-empty after trimming whitespace.---../ prefix for siblings or children, ../ for parent traversal. Bare relative filenames in markdown links (e.g., [text](sibling.md)) are also acceptable../, ../, or bare filename). Always resolve the path from the originating file's directory — a reference to ./steps/step-02-review.md from a file already inside steps/ would resolve to steps/steps/step-02-review.md, which is wrong../steps/step-01-gather-context.md (from a skill-root file into a steps/ subdirectory)./step-02-plan.md (sibling, in the flat step layout or from inside steps/)./template.md (from SKILL.md to a sibling)../spec-template.md (from steps/step-01.md to a skill-root file)workflow.md (bare relative filename for sibling)./steps/step-02-review.md (from a file already inside steps/ — resolves to steps/steps/){project-root}/.claude/skills/my-skill/template.md/Users/someone/.claude/skills/my-skill/steps/step-01.md~/.claude/skills/my-skill/file.mdinstalled_path Variableinstalled_path variable is a leftover from pre-skill workflows. It must not be defined in any frontmatter, and {installed_path} must not appear anywhere in any file.installed_path:{installed_path} anywhere in contentinstalled_path (e.g., `installed_path` = `.`)installed_path definitions. Replace every {installed_path}/path with ./path (relative from the file that contains the reference). If the reference is in a step file and points to a skill-root file, use ../path instead.{project-root} or Config Values{project-root}/... or a config-derived path (e.g., {planning_artifacts}/..., {implementation_artifacts}/..., {project_knowledge}/...).{project-root} or a known config key. Flag absolute paths, home-relative paths (~/), or bare paths that resolve outside the skill.{project-root}/... or the appropriate config value.`template` = `./template.md` under a ### Paths section)../, ../, or is a bare filename of a file that exists in the skill. Exclude variables whose values are prefixed with a config key like {planning_artifacts}, {implementation_artifacts}, or {project-root} — these are external references and are legitimate.{variable_name} usage with the direct relative path.SKILL.md). Patterns to flag:
{project-root}/_bmad/.../other-skill/anything.md{project-root}/_bmad/.../other-skill/steps/...{project-root}/_bmad/.../other-skill/templates/...Invoke the `skill-name` skill (see REF-03).{project_knowledge}/..., or a file:-prefixed entry in customize.toml — rather than reaching across a skill boundary.[C] Continue, [A] Approve, [S] Split) must explicitly HALT and wait for user response before proceeding.## NEXT sections, navigation/dispatch sections that list valid resumption targets, and conditional routing branches..md files whose name contains template (case-insensitive)render_skill.py would replace a {{.var}} with a value from the rendering machine's config, and every artifact produced from the template would carry it.\{\{\.\w+\}\} match anywhere in a file whose basename matches /template/i.{{.var}} reference. Use single-curly {var} if the value should be resolved at runtime by the consumer of the generated artifact, or plain double-curly {{var}} if it is a placeholder the consumer fills in.{name} — a frontmatter variable in the same file, a config key, a runtime variable set during execution, or the path anchors {project-root} and {skill-root}.{workflow.key} — must name a key in the [workflow] table of the skill's own customize.toml.{agent.key} — must name a key in the [agent] table of the skill's own customize.toml.{{.key}}, {{config.key}}, [[bmad-snapshot:file.md]] — only in a rendered skill (one whose SKILL.md invokes render_skill.py). In any other skill nothing will substitute them and they reach the agent verbatim. A [[bmad-snapshot:file.md]] target must name a Markdown file in the skill other than SKILL.md, which the renderer excludes from its source set.prompt: keys in module.yaml; resolve {workflow.*} and {agent.*} against the skill's customize.toml. Before flagging a compile-time token, grep the skill's SKILL.md for render_skill.py — if it is a rendered skill, the token is legitimate. Flag any token that cannot be traced to a source.{{name}} with no leading dot — an artifact placeholder that survives rendering into the generated document, to be filled in by whoever consumes it (e.g. {{story_key}} in a story template). Do not flag these. Dotted {{.key}} and {{config.key}} are not covered by this exception; they are compile-time substitutions governed by the rule above and by TPL-01.customize.toml table or frontmatter, or replace the reference with a literal value. If a config key was misspelled, correct the spelling.{planning_artifacts}/*.md is plausible, {planning_artifacts}/../../etc/passwd is not).Invoke the `skill-name` skill. Phrases like "Read fully and follow", "Execute", "Run", "Load", "Open", or "Follow" are invalid — they imply file-level operations on a document, not skill invocation.bmad-foo). Check the surrounding instruction text (same sentence or directive) for file-oriented verbs: "read", "follow", "load", "execute", "run", "open". Flag any that do not use "invoke" (or a close synonym like "activate" or "launch").Invoke the `skill-name` skill. Remove any "read fully and follow" or similar file-oriented phrasing. Do NOT add a skill: prefix in prose — use natural language.skill:skill-name is the correct form inside customize.toml values (for example a persistent_facts entry, or a directive such as skill:bmad-review lenses=<code>), where the string is data consumed by a resolver rather than an instruction to the agent. Do not flag it there.When reporting findings, use this format:
# Skill Validation Report: {skill-name}
**Directory:** {path}
**Date:** {date}
**Files scanned:** {count}
## Summary
| Severity | Count |
| -------- | ----- |
| CRITICAL | N |
| HIGH | N |
| MEDIUM | N |
| LOW | N |
## Findings
### {RULE-ID} — {Rule Title}
- **Severity:** {severity}
- **File:** `{relative-path-within-skill}`
- **Line:** {line number or range, if identifiable}
- **Detail:** {what was found}
- **Fix:** {specific fix for this instance}
---
(repeat for each finding, grouped by rule ID)
## Passed Rules
(list rule IDs that produced no findings)
If zero findings: report "All 20 rules passed. No findings." and list all passed rule IDs.