tests.test_skills module¶
Conformance of bundled skills to the Agent Skills specification.
Every bundled SKILL.md is checked against the Agent Skills
spec, so a new or edited skill cannot
silently drift out of it.
Note
The spec allows six frontmatter fields, and argument-hint is the single
deliberate deviation (see CLAUDE_CODE_EXTENSIONS). Anything Claude Code
could express through another of its own extensions belongs in a spec field
instead: the recommended model rides in compatibility rather than a model:
key, which is why test_skill_compatibility_carries_the_model_hint()
exists. Pinning the extension set to exactly one entry is what stops that
budget creeping back, and it catches a misspelled field name
(user_invocable for user-invocable) that would otherwise parse as valid
YAML and be silently ignored. The same pin keeps disable-model-invocation:
out: every skill is model-invocable by design, and the permission layer, not
frontmatter, decides what it may do. Argue a new field here before adding it
to a skill.
- tests.test_skills.SKILLS_PAGE = PosixPath('/home/runner/work/repomatic/repomatic/docs/agent-skills.md')¶
The hand-maintained page whose table must mirror the skill registry.
- tests.test_skills.SKILL_TABLE_ROW_RE = re.compile('^\\|[^|]*\\|\\s*\\[`/([a-z0-9-]+)`\\]', re.MULTILINE)¶
The skill name out of a table row, read from the linked
/namecell.
- tests.test_skills.SPEC_FIELDS = frozenset({'allowed-tools', 'compatibility', 'description', 'license', 'metadata', 'name'})¶
The six frontmatter fields the Agent Skills spec defines.
- tests.test_skills.CLAUDE_CODE_EXTENSIONS = frozenset({'argument-hint'})¶
The only non-spec frontmatter field bundled skills are allowed to carry.
Kept because no spec field expresses an autocomplete hint, and it degrades to a no-op wherever it is not understood. Do not grow this set: reach for a spec field first, the way
compatibilitynow carries whatmodel:used to.
- tests.test_skills.MAX_COMPATIBILITY_LENGTH = 500¶
Spec ceiling on the
compatibilityfield.
- tests.test_skills.MAX_DESCRIPTION_LENGTH = 1024¶
Spec ceiling on the
descriptionfield.
- tests.test_skills.MAX_NAME_LENGTH = 64¶
Spec ceiling on the
namefield.
- tests.test_skills.SKILL_NAME_RE = re.compile('^[a-z0-9]+(-[a-z0-9]+)*$')¶
Spec charset for
name: lowercase alphanumerics and single inner hyphens.The grouping enforces the three separate prohibitions at once: no leading hyphen, no trailing hyphen, and no consecutive hyphens.
- tests.test_skills.frontmatter(entry)[source]¶
Parse the frontmatter of a bundled skill registry entry.
- tests.test_skills.exported_skill(tmp_path)[source]¶
A synthetic bundled skill, already exported once to its destination.
Stands in for a downstream repo right after
repomatic init skills: the destination holds a pristine copy of the bundle.- Returns:
The
(source, dest)folder pair.
- tests.test_skills.test_skill_is_a_folder_holding_a_skill_md(entry)[source]¶
The spec’s unit of distribution: a folder entered through
SKILL.md.Registering the folder rather than the file is what lets a skill grow
scripts/,references/orassets/without touching the registry.
- tests.test_skills.test_skill_name_matches_directory(entry)[source]¶
nameis required, spec-shaped, and equal to the parent directory.
- tests.test_skills.test_skill_description_within_spec_limit(entry)[source]¶
descriptionis required and fits the spec ceiling.
- tests.test_skills.test_skill_compatibility_carries_the_model_hint(entry)[source]¶
Every skill names its recommended model in
compatibility.model:is a Claude Code extension the spec does not define, so the hint rides in a spec field instead of a bespoke key or a line of body prose: structured, greppable, and portable to any client that reads the spec.
- tests.test_skills.test_skill_optional_spec_fields_are_well_formed(entry)[source]¶
compatibilityandmetadatahold what the spec says they hold.No bundled skill sets
metadatatoday, so that half guards the shape a future one would have to honour rather than an invariant already in play.
- tests.test_skills.test_skill_allowed_tools_are_space_separated(entry)[source]¶
allowed-toolsuses the spec’s space-separated string.Claude Code also accepts a comma-separated string and a YAML list, so a comma here still works locally while failing the spec.
- tests.test_skills.test_skill_frontmatter_fields_are_known(entry)[source]¶
No field beyond the spec’s six and the extensions we opted into.
- tests.test_skills.test_skill_resource_folders_are_copied_verbatim(tmp_path)[source]¶
scripts/,references/andassets/travel with a skill.No bundled skill ships resources yet, so this exercises the contract on a synthetic folder: whatever sits beside
SKILL.mdis copied as-is, with no per-file registration, and re-running changes nothing.
- tests.test_skills.test_drifted_skill_is_overwritten(exported_skill)[source]¶
A downstream copy that fell behind the bundle is reconciled.
Skills are copied verbatim, with no user-modified heuristic: content that differs from the bundle is rewritten, so a downstream repo picks up new frontmatter on its next
repomatic init. Adding a leave-what-is-there guard here would strand every downstream repo on whichever revision it first received, which is why this is pinned rather than left to the idempotency check above (identical content exercises neither branch).
- tests.test_skills.test_local_files_beside_a_skill_survive(exported_skill)[source]¶
Re-exporting creates and overwrites, but never prunes.
Wiping whatever is not bundled would be an easy way to reconcile a drifted folder, and would take a downstream repo’s own files with it. Retiring a bundled asset goes through a
REMOVED_ASSETStombstone instead.
- tests.test_skills.test_docs_table_lists_every_bundled_skill()[source]¶
docs/agent-skills.mdtabulates the registry, and nothing else.The page renders
list-skillslive right above the table, so a skill added to the registry shows up in the rendered output while the hand-maintained table below it keeps the old roster. Addingrepomatic-test-matrixleft exactly that gap, and only a reader comparing the two halves of one page would have caught it. Names only: the column padding belongs tomdformat.