This repository is a library of reusable guidance for AI coding agents. It contains three kinds of material: instructions documents that describe how output in a given domain should be produced, Claude Code skills that describe a repeatable procedure an agent should follow, including when to follow it and how to verify the result, and agent templates that describe who runs the work, with which model and which tools.
instructions/ Domain-specific writing and coding standards (plain Markdown, no frontmatter)
skills/ Claude Code skills, one directory per skill: skills/<category>/<name>/SKILL.md
agent-templates/ Copy-and-adapt Claude Code subagent definitions, one flat file per agent
evals/ Measurements of whether each skill changes agent output, and what it triggers on
scripts/ Checks that enforce this repository's own authoring rules
.claude-plugin/ Marketplace manifest, so other projects can install the skills as plugins
Each file in instructions/ sets the objective, required style, and a quality checklist for one
kind of output. These documents are not auto-discovered by any tool. Use them by referencing the
relevant file from a project's CLAUDE.md or AGENTS.md, for example:
When writing Python, follow instructions/python_coding_instructions.md.Or by pointing an agent at the file directly at the start of a task.
Current instructions documents:
| File | Covers |
|---|---|
python_coding_instructions.md |
Passing ruff and ty cleanly, plus the judgment calls those tools cannot make. |
bash_coding_instructions.md |
Passing shellcheck, bash -n, and the repository's formatter cleanly, plus the layout, naming, and judgment rules those tools cannot enforce. |
written_language_instructions.md |
Formal, concise, precise written style for any prose output. |
overview_document_instructions.md |
Structure and content for a repository-level overview document. |
github_governance_instructions.md |
The security and compliance baseline for GitHub repository and organization configuration, and the change and evidence rules that go with it. |
agent_configuration_instructions.md |
Choosing between a project instructions file, a skill, a hook, and a subagent, writing each, and changing one without losing the behavior it was written for. |
terraform_coding_instructions.md |
Passing terraform fmt, terraform validate, and tflint cleanly, plus the state, secret, version-pinning, and layout judgment those tools cannot enforce. |
Each skill is a SKILL.md file with a YAML frontmatter block (name, description) followed by
a fixed set of sections, listed under Adding new material. The
description field is what a Claude Code agent uses to decide whether a skill applies to the
current task, so it should state concretely what the skill is for and when it should trigger.
A skill may keep longer material in a references/ directory next to its SKILL.md, linked by
relative path from the skill body. SKILL.md then stays a triage layer: the steps, the verify
loop, and an index pointing at the detail to read for the change at hand. An agent loads a
reference file only when it applies, instead of carrying every category of detail in context.
Once a skill is available to Claude Code, it is invoked automatically when its description
matches the current task, or explicitly by name. See
Using this library from another project for how to
make it available without copying it.
Current skills:
| Skill | Path | Covers |
|---|---|---|
ansible-verification-loop |
skills/ansible/ansible-verification-loop/SKILL.md |
Reviewing or modifying Ansible roles and collections, verified through the repository's own lint/test loop, and keeping the local state a test run leaves behind out of both the repository and the built collection artifact. |
bash-secure-scripting |
skills/bash/bash-secure-scripting/SKILL.md |
The shellcheck/bash -n baseline from bash_coding_instructions.md, extended with the stability and security properties a linter cannot verify: strict-mode semantics, cleanup on every exit path, untrusted input and injection, PATH and environment control, temporary files, and credentials, run through a bounded verify-fix loop. |
bash-testing |
skills/bash/bash-testing/SKILL.md |
Adding or updating coverage for a shell change: discovering and matching the repository's existing framework (bats-core, shunit2, or plain scripts), making a script testable, covering exit codes and failure paths, and running the suite through a bounded verify-fix loop. |
github-actions-security |
skills/github/github-actions-security/SKILL.md |
Authoring and reviewing GitHub Actions workflows and actions: least-privilege GITHUB_TOKEN permissions, dependencies pinned by commit SHA to the latest published release, injection-safe handling of untrusted event data, safe triggers and runners, workflows that run an AI coding agent, and structures that scale across repositories, run through a bounded verify-fix loop with actionlint and zizmor. |
github-organization-governance |
skills/github/github-organization-governance/SKILL.md |
Configuring and reviewing the settings that span repositories: member privileges and base permissions, authentication and provisioning, app and token policy, the actions and runner policy, organization rulesets targeted by custom properties, and audit evidence, run through a bounded verify-fix loop that reads the applied state back and measures coverage across the fleet. |
github-repository-security |
skills/github/github-repository-security/SKILL.md |
Configuring and reviewing one repository: rulesets and review requirements, secret and code scanning, dependency alerts, access and deploy keys, tag and release protection, and the agent-facing content a repository ships, run through a bounded verify-fix loop that reads the applied state back rather than trusting the API response. |
python-secure-coding |
skills/python/python-secure-coding/SKILL.md |
The ruff/ty baseline from python_coding_instructions.md, extended with Python-specific security best practices aligned to the OWASP Top 10:2025 (input handling, deserialization, secrets, subprocess/SQL/crypto usage, SSRF, dependency hygiene), run through a bounded verify-fix loop. |
python-testing |
skills/python/python-testing/SKILL.md |
Adding or updating pytest coverage for a Python change: discovering and matching the repository's existing test layout, deciding when a test is required, and running the suite through a bounded verify-fix loop. |
terraform-secure-iac |
skills/terraform/terraform-secure-iac/SKILL.md |
The fmt/validate/tflint baseline from terraform_coding_instructions.md, extended with the properties no linter verifies: state and secret exposure, least-privilege execution identity, provider and module supply chain, sensitive markings, and policy-as-code, run through a bounded verify-fix loop with a configuration scanner. |
terraform-testing |
skills/terraform/terraform-testing/SKILL.md |
Adding or updating coverage for a Terraform change: discovering and matching the repository's existing approach (native terraform test, Terratest, or plan-and-policy assertions), deciding when a test is required, covering the failure path, and running the suite through a bounded verify-fix loop. |
An agent template is a Claude Code subagent definition: a Markdown file with YAML frontmatter that
gives the agent its own context window, system prompt, model:, and tools: allowlist. Claude
Code loads subagents from a project's .claude/agents/ directory or from ~/.claude/agents/,
never from this library's directory.
These files are templates, not installable agents. An agent definition encodes per-repository
policy: what a model costs there, which tools are trusted there, which commands its verify loop
runs there. Copy one into the consuming project's .claude/agents/ and edit it. Do not symlink
it. Divergence between the copy and this library is the intended outcome, which is the opposite of
the rule for instructions/ and skills/.
Three frontmatter fields are left for whoever copies a template to decide:
model:. Every template shipsmodel: inherit, so a fresh copy pins no model of its own and runs on whatever the main conversation uses. Pin a stronger model for review-heavy agents, or a cheaper one for agents that apply a fixed checklist.tools:. Every template ships the smallest allowlist its work needs. Widen or narrow it against what the project trusts the agent to do. Frontmatter comments in each template state what to consider changing and why.memory:. No template ships it, so a fresh copy keeps nothing between runs. Setting it touser,project, orlocalgives the agent a memory directory whoseMEMORY.mdis read into its system prompt at startup and written to as it works. Read the caveat before setting it: memory addsRead,Write, andEditto the agent whatevertools:holds, so a review-only copy that droppedEditgets it back and a copy ofprose-editorgainsWrite. Underprojectscope the directory is committed, which puts model-authored text into the system prompt of every later run for everyone working in that repository.scripts/check_skills.pyfails a template that ships the field, so the decision is made in the copy rather than inherited from here.
Each template is a thin wrapper. Its system prompt names the instructions document or skill that holds the substance and points at it by path, rather than restating it. What the agent file adds is routing and policy: which model, which tools, which scope, and what to report back.
Current templates:
| Template | Wraps | Notes |
|---|---|---|
ansible-reviewer.md |
skills/ansible/ansible-verification-loop |
Needs Bash for ansible-lint and the target repository's test entry point. Consider pinning a strong model. |
python-security-reviewer.md |
skills/python/python-secure-coding |
Needs Bash for ruff and ty. Consider pinning a strong model. |
prose-editor.md |
instructions/written_language_instructions.md |
Read and Edit only, no Bash. Candidate for a cheaper model. Needs the submodule, since it references an instructions document rather than a skill. |
workflow-security-reviewer.md |
skills/github/github-actions-security |
Needs Bash for actionlint, zizmor, and the gh call that resolves an action SHA. Consider pinning a strong model. |
bash-security-reviewer.md |
skills/bash/bash-secure-scripting |
Needs Bash for shellcheck, bash -n, and for running the script under review on a failure path, which is the widest grant of the six. Consider pinning a strong model. |
terraform-security-reviewer.md |
skills/terraform/terraform-secure-iac |
Needs Bash for terraform fmt, terraform validate, tflint, and the repository's configuration scanner. Does not run terraform apply. Consider pinning a strong model. |
The directory is named agent-templates/ rather than agents/ deliberately. Claude Code
auto-discovers an agents/ directory at a plugin's root, and every plugin here is sourced
from the repository root, so templates placed in agents/ would install into every consuming
project as live subagents, adding their descriptions to every session. That inverts the
copy-and-adapt rule, so the name that triggers discovery is avoided. Neither omitting the agents
field from a marketplace entry nor setting it to an empty list suppresses the discovery.
scripts/check_skills.py fails if an agents/ directory reappears at the repository root.
Agent templates are the exception to everything in this section: copy them, as described in
agent-templates/. For instructions/ and skills/, a consuming project
should not copy the files or write its own version of them. Use one of the mechanisms below, each
of which keeps a single upstream copy that can be updated in place.
This repository is its own plugin marketplace. The skills are grouped into five plugins so a project installs only what it needs:
| Plugin | Skills |
|---|---|
python-standards |
python-secure-coding, python-testing |
bash-standards |
bash-secure-scripting, bash-testing |
ansible-standards |
ansible-verification-loop |
github-standards |
github-actions-security, github-repository-security, github-organization-governance |
terraform-standards |
terraform-secure-iac, terraform-testing |
From inside Claude Code, in the consuming project. Pin to a release tag:
/plugin marketplace add konstruktoid/agent-instructions-skills@v0.1.0
/plugin install python-standards@konstruktoid
/reload-pluginsWithout the @<tag> suffix the marketplace tracks the default branch, and every commit
pushed here reaches the project at its next update, reviewed by nobody on the consuming side.
Use the unpinned form only when following this repository's main is the intent:
/plugin marketplace add konstruktoid/agent-instructions-skillsThe same operations exist as claude plugin marketplace add and claude plugin install outside a
session. Plugin skills are namespaced by plugin name, so python-secure-coding is invoked as
/python-standards:python-secure-coding, and Claude still triggers it automatically when the
task matches its description.
Installing the plugin brings the whole library along, instructions/ included, so the skills
resolve their own references to instructions/python_coding_instructions.md without the project
doing anything.
To move to a newer release, re-add the marketplace at the new tag, then update:
/plugin marketplace add konstruktoid/agent-instructions-skills@v0.2.0
/plugin marketplace update konstruktoid
/plugin update python-standardsEvery plugin entry declares the same version, which matches the tag it ships from, so an
installed plugin names a fixed point rather than whatever the default branch held that day.
On an unpinned marketplace, update still fetches the current default branch.
Commit .claude/settings.json in the consuming repository so members are prompted to install the
plugins when they trust the project folder:
{
"extraKnownMarketplaces": {
"konstruktoid": {
"source": {
"source": "github",
"repo": "konstruktoid/agent-instructions-skills"
}
}
},
"enabledPlugins": {
"python-standards@konstruktoid": true
}
}Add "ref": "v0.1.0" next to repo to pin the team to a release tag rather than tracking the
default branch. The field accepts a branch or a tag, not a commit SHA, and the two are not
equivalent: a tag in this repository is protected against deletion and force update, so it names
the same tree tomorrow, while a branch is a moving reference that the next push changes. Pin to a
tag unless following a branch is the intent.
The files in instructions/ are plain Markdown that no tool auto-discovers. The skills read them
on their own once the plugin is installed, but a project that wants an instructions document
applied outside a skill, for example a prose style that should hold for all output, needs a path
it can reference. A submodule gives it one, and works for agents and tools that have no plugin
mechanism at all:
git submodule add https://github.com/konstruktoid/agent-instructions-skills .agent-standardsThen reference the file by path from the project's CLAUDE.md or AGENTS.md:
When writing Python, follow .agent-standards/instructions/python_coding_instructions.md.
When writing shell, follow .agent-standards/instructions/bash_coding_instructions.md.
When writing prose, follow .agent-standards/instructions/written_language_instructions.md.A submodule pins an exact commit, which is recorded in the consuming repository and updated
deliberately with git submodule update --remote. Projects using Claude Code without plugins can
also expose the skills from the same submodule:
mkdir -p .claude/skills
ln -s ../../.agent-standards/skills/python/python-secure-coding .claude/skills/python-secure-codingDo not reference ${CLAUDE_PLUGIN_ROOT} from a project's own CLAUDE.md. That variable is
substituted in plugin content, such as a skill body, and does not resolve in project files.
Agent templates are copied by design, whichever of the mechanisms above the project already uses:
mkdir -p .claude/agents
cp .agent-standards/agent-templates/prose-editor.md .claude/agents/prose-editor.mdThen edit the copy: set model: and tools:, remove the frontmatter comments once the choices
are made, and resolve the reference the system prompt points at. A template that wraps a skill
offers one row per install mechanism, plugin or submodule, and expects the row that does not apply
to be deleted.
A copied template is project content, not plugin content, so ${CLAUDE_PLUGIN_ROOT} does not
substitute in it. Under a plugin install a template reaches a skill by invoking it under its
namespaced name, such as ansible-standards:ansible-verification-loop, and the skills:
frontmatter field can preload that skill at startup instead. A template that references an
instructions document directly has no such name to use, so it needs the submodule.
Claude Code reloads .claude/agents/ within a few seconds of a file changing. Creating the
directory for the first time during a session is the exception and needs a restart.
For instructions/ and skills/, copy a file only when the consuming environment can use neither
a plugin nor a submodule, such as an air-gapped checkout. Record the upstream commit the copy came
from, so the drift is visible later. A copy stops receiving fixes the moment it is made, which is
the outcome the mechanisms above exist to avoid.
-
Add a new instructions document to
instructions/when the goal is a style or quality standard for a category of output. Follow the existing files' structure: Objective, required/avoid style rules, and a Quality Checklist. -
Add a new skill to
skills/<category>/<name>/SKILL.mdwhen the goal is a repeatable procedure with a verification step. Write thedescriptionfield so it states precisely which tasks should trigger the skill. Use the section order the existing skills share, so an agent finds the same things in the same place in every skill:Purpose, When to use this, When NOT to use this, Steps, [any skill-specific sections], Verify, Verification checklist, References -
List every new skill in
.claude-plugin/marketplace.json, under the plugin for its category. A skill that is not listed there is invisible to any project that installs this library as a plugin.scripts/check_skills.pyfails when a skill is unlisted, listed twice, or points at a path with noSKILL.md. -
Write
descriptionin third person: lead with what the skill does, close with when to use it (see Anthropic's skill authoring best practices). Avoid first/second person ("I can help you...", "Use this to..."), keep it under 1,024 characters, and keep the SKILL.md body under 500 lines, moving longer material into files it links to directly. -
Give every
references/*.mdfile over 100 lines a## Contentssection listing its own headings, placed after the opening paragraph and before the first section. The same best practices document asks for one, because an agent previewing a long file with a partial read otherwise sees only its first screen and cannot tell what else the file covers.scripts/check_skills.pyfails when a long reference file has noContentssection, when a section already precedes it, and when the entries do not match the headings that follow them, since a list that drifts from the document is worse than none. -
For any skill with a verify-then-fix cycle, bound the retries explicitly and define what one attempt is: one full fix-and-rerun cycle. This repo baselines the bound at 3 attempts, lets an agent continue while each cycle produces strictly fewer findings, and requires it to stop early when the loop oscillates without progress. On stopping, the skill must require reporting the failing check and its output to the user, instead of looping silently or declaring success unverified. Every skill here uses the same wording for this loop; copy it rather than paraphrasing, so the bound means the same thing everywhere.
scripts/check_skills.pycompares the block against the canonical wording and fails on any rewording, so this is enforced rather than left to whoever copied it last. A testing skill may write "failures" for "findings" and "failing test" for "failing check", since it counts failing tests; the checker folds those two spellings together and holds every other word exactly. -
When a skill extends or depends on an instructions document, cross-reference it by path in both directions, as done between
python_coding_instructions.mdandpython-secure-coding. Do not copy the shared material into both files. The instructions document is the single source of truth; the skill carries a short summary and a pointer to it. -
Add a new agent template to
agent-templates/<name>.mdwhen the goal is to give a kind of work its own context window, model, and tool allowlist. One flat file per agent: nothing auto-discovers these from the library, so a directory per agent buys nothing. Keep the file a thin wrapper, naming the instructions document or skill that holds the substance and pointing at it by path rather than restating it. What belongs in the agent file is routing and policy: scope, and what the agent reports back to the main conversation. -
Ship agent templates with neutral defaults:
model: inherit, the smallesttools:allowlist the work needs, and nomemory:field, so copying one pins no model on the consumer, grants no broad tool access, and carries nothing between runs. Memory stays out of the defaults because enabling it grantsRead,Write, andEditbeside the allowlist rather than within it, which is a widening no reader of thetools:line would see. State in frontmatter comments what to consider changing and why, for example pinning a stronger model for a review-heavy agent, or addingBashonly because the verify loop needs it. -
A template aimed at a cheaper model needs its verification spelled out rather than assumed. Keep what it must follow short and checklist-like, and reuse this repository's bounded verify-fix wording, adapted to whatever one attempt means for that agent.
-
Never place agent templates in a directory named
agents/at the repository root. Claude Code auto-discovers that name at a plugin root, which would install every template into every consuming project as a live subagent.
evals/ measures what the skills actually do. The authoring rules in
Checks confirm a skill is well formed; they cannot confirm it changes an agent's
output, or that its description routes the right tasks to it. Two measurements cover that:
- Task evals. A suite holds 4 to 7 multi-step task prompts in
tasks.json, each with a fixture repository and a set of objective checks inassertions.jsonderived from that skill's own Verify and Verification checklist sections. Every task runs twice against an identical fixture copy, once with the skill available and once without. The only difference between the two runs is a single-skill plugin passed with--plugin-dir, so a delta is attributable to the skill. Six of the ten skills have a suite.github-repository-securityandgithub-organization-governancehave none, because a task for either acts on a live GitHub organization rather than on a fixture directory, andterraform-secure-iacandterraform-testinghave none yet;scripts/check_evals.pyreports each of them as unmeasured. - Trigger evals.
trigger-eval.jsonholds 10 routing probes per skill, five in scope and five adjacent but out of scope, which measure thedescriptionfield rather than the body.
python3 evals/run_eval.py tasks --skill <name> --model sonnet --parallel 5
python3 evals/run_eval.py triggers --skill <name> --model sonnet --parallel 5
python3 evals/run_eval.py report --skill <name>Results land in evals/<skill>/results/<date>.md, rendered by report rather than written by
hand, with transcripts, workspaces, and per-run grades kept under results/raw/<date>/. A
delta of zero is reported as a delta of zero: where a skill produces no measurable
improvement, the results file says so. See evals/README.md for how the two
conditions are isolated, what an assertion may and may not be, and the limitations that apply
to every number in there.
Six of the ten skills define both evals and have results committed. The table records the
latest stamp for each of those, what it measured, and the limitation that keeps that number
from standing as a general claim about the skill. github-repository-security and
github-organization-governance have neither eval yet: both act on live GitHub settings, so
a task eval needs a fixture that stands in for an organization, and until that exists there is
no measurement of what either skill changes. terraform-secure-iac and terraform-testing
have neither eval yet either; a suite for each is a follow-up.
scripts/check_evals.py holds the six suites to the structure described here, and separates
what an edit can fix from what only a re-run can. Every suite passes the structural checks, and
every committed results file regenerates byte-identically from the artifacts under
results/raw/, so no number in the table was written by hand. What the checker reports instead
is staleness: three defined tasks, avl-06-autofix-cosmetics, gas-06-blocked-egress and
gas-07-agent-workflow, have
never been graded in any stamp, four of the six stamps predate a change to the skill they
measured, and the newest stamp, ansible-verification-loop's 2026-08-20-repeat, was measured
against a modified working tree, so the source it graded is in no commit and the run cannot be
reproduced from the repository until it is repeated from a clean checkout. One skill's
description has changed since the stamp that measured its routing: github-actions-security
gained a clause covering workflows that run an AI coding agent, so its 9/10 is a measurement of
the description as it stood on 2026-07-27 and its probes test nothing in the added clause. Every
other row's routing column still describes the description as it stands.
| Skill | Latest stamp | Task delta | Cost | Routing | Limitation |
|---|---|---|---|---|---|
ansible-verification-loop |
2026-08-20-repeat | +6 over 1 task | 1.2x | 10/10 (2026-07-25) | Three runs per condition on avl-07-artifact-hygiene alone, 15/16 in all three with-skill runs against 9 to 10 in the baseline, so the ranges do not overlap. Two of its assertions were corrected after the single-run 2026-08-20 stamp but before these six runs, which makes this stamp a measurement of checks fixed in advance rather than after the fact; both stamps and that reasoning are in evals/ansible-verification-loop/README.md. The stamp was measured against an uncommitted tree. Earlier stamps: 2026-07-28-isolation measured +1 over avl-03 at 1.8x, and 2026-07-25 measured +6 over 5 tasks at 2.2x with avl-05 classified truncated rather than graded. avl-06-autofix-cosmetics has never been graded in any stamp. |
bash-secure-scripting |
2026-08-14 | +9 over 4 tasks | 3.5x | 9/10 | One run per condition, so variance is uncontrolled. bss-t09 is out of scope and routed in. |
bash-testing |
2026-08-14 | +1 over 4 tasks | 2.1x | 7/10 | Two fixtures pass fully in both conditions and cannot discriminate. bt-t01 and bt-t04 are in scope and never routed; bt-t07 is out of scope and routed in 2 of 3 repetitions. |
github-actions-security |
2026-07-28 | +29 over 4 comparable tasks | 2.4x | 9/10 (2026-07-27) | Three runs per condition. gas-05-dependabot-pinning aborted in all three with-skill runs and has no comparable measurement, and gas-02 is marked no reliable difference. gas-06-blocked-egress has never been graded in any stamp. gas-07-agent-workflow was added with the clause about workflows that run an AI coding agent and has never been graded either, and gas-t01 was replaced by a probe for that clause, so the routing score above measures a probe set the suite no longer holds. gas-t06 is out of scope and routed in on all 3 repetitions. |
python-secure-coding |
2026-07-28, marked for regeneration | +4 over 5 tasks | 1.7x | 10/10 (2026-07-25) | Only psc-02 has a delta not marked no reliable difference, and on psc-03, psc-04 and psc-05 the with-skill condition failed the same security assertions as the baseline. The fixtures were anchored for ty on 2026-08-17, which this stamp predates; see evals/python-secure-coding/README.md. |
python-testing |
2026-07-28 | +1 over 5 tasks | 1.4x | 9/10 (2026-07-25) | Four of five deltas are zero or marked no reliable difference, at $2.07 per net assertion gained. |
Two limits cut across the whole table. A routing score carried from an earlier stamp than the
task result was measured against an earlier revision of that skill's description, so it does
not transfer forward on its own. And a task delta is a measurement of the skill revision that
ran, not of the file as it stands now: editing a skill, its tasks.json or its
assertions.json invalidates the stamp above it until the eval is run again. That second limit
is not hypothetical here. Every row except ansible-verification-loop and bash-testing
carries a stamp older than the skill directory it measured, and github-actions-security is
older than its own tasks.json and assertions.json, both of which gained a task on
2026-08-17. That cannot be repaired by regrade, since it re-runs assertions only where the
finished workspace survives, and a workspace is gitignored. The ansible-verification-loop row
is the one case where the workspaces were still on disk, which is why its correction could be
applied by regrading rather than by paying for the runs again.
Eval fixtures are deliberately flawed inputs, so pyproject.toml excludes
evals/*/fixtures, evals/*/results, and evals/probe-sandbox from ruff and ty. Each
fixture carries its own tool configuration, which is what the eval measures against.
Markdown is the exception: .markdownlint-cli2.yaml ignores only evals/*/results/raw/**,
the verbatim transcripts and workspaces of a graded run, so a fixture's own README.md is
still held to this repository's Markdown rules.
.github/workflows/lint.yml enforces the rules above on every push and pull request, in five
jobs: the authoring rules, this repository's own Python, its eval suites, its own workflows, and
its Markdown. Every check runs locally:
uv run --frozen python scripts/check_skills.py # authoring rules for every SKILL.md
uv run --frozen python scripts/check_capabilities.py # capabilities a change adds undeclared
python3 scripts/check_evals.py # structure and coverage of every suite
python3 scripts/check_citations.py # every `path:line` citation in the prose
python3 scripts/check_citations.py --renumber # rewrite the ones an edit moved
uv run --frozen ruff check . # the repository's own Python
uv run --frozen ruff format --check .
uv run --frozen ty check .
npx --yes markdownlint-cli2@0.23.2 "**/*.md" # add --fix to correct spacing in place
docker run --rm -v "$PWD:/repo" -w /repo \
rhysd/actionlint@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667 -color
uvx zizmor@1.29.0 --persona=pedantic --no-progress .github/The last two are this repository's own workflows held to the skill it publishes about them,
at the versions skills/github/github-actions-security/SKILL.md pins in its Verify section.
zizmor is scoped to .github/ for the same reason ruff excludes evals/*/fixtures: a
fixture workflow plants the finding its eval measures.
scripts/check_skills.py verifies, for each skills/*/*/SKILL.md, that the frontmatter parses as
YAML, name matches the parent directory, description is non-empty, under 1,024 characters, and
not written in first or second person, that the body is under 500 lines, and that the body carries
the bounded verify loop in the shared wording described above. It applies the same frontmatter rules
to each agent-templates/*.md, with name matching the file name, and adds the neutral defaults a
template must ship with: model is inherit, tools is a non-empty allowlist, and memory is
absent, since a template that set it would grant Read, Write, and Edit past the allowlist
beside it. It then checks .claude-plugin/marketplace.json: it must parse, every listed path must
hold a SKILL.md, and every skill in the repository must be listed by exactly one plugin. It fails
if an agents/ directory has appeared at the repository root, which would ship the agent templates
as installable subagents. It verifies the cross-references this library maintains by hand: a
SKILL.md may not name an instructions/*.md that does not exist, an instructions/*.md may not
name a skills/*/*/SKILL.md that does not exist, and a skill that names an instructions document
must be named back by it, which is the bidirectional rule stated above. It requires every
references/*.md over 100 lines to carry a ## Contents section ahead of every other section, and
compares its entries against the headings that follow, so a list cannot drift into pointing at a
section that has been renamed or removed. Last, it holds the prose this repository writes about
itself, meaning README.md, instructions/*.md, skills/**/*.md, agent-templates/*.md, and the
hand-written evals/*/README.md, to the em dash, arrow, inflated-vocabulary, and grammatical-person
rules in instructions/written_language_instructions.md. Fenced blocks, inline code spans, and
table rows are exempt, because the rule allows those and a document has to be able to quote what it
bans. The person check additionally exempts Markdown link text and double-quoted spans, which is
where a cited title and a quoted example live. The word list carries only the entries with no
technical meaning in this subject matter: harness and elevate stay legal, since "test harness"
and "privilege elevation" are the domain's own terms. It needs only pyyaml, so
python3 scripts/check_skills.py also works outside uv.
scripts/check_evals.py verifies, for each evals/<skill>/ suite, that tasks.json,
assertions.json, and trigger-eval.json parse and name their own skill, that the suite defines
4 to 7 tasks whose fixtures exist at fixtures/<task-id> with none unreferenced, that the
assertions cover exactly the defined tasks with unique ids, a known kind, and a source, that
every workspace_command parses under bash -n and every regex compiles, that the probes number
10 in a 5 and 5 split, and that every raw stamp holding graded runs has a rendered
results/<stamp>.md beside it. A measurement that ran and was never reported is otherwise
invisible, which is how a five-task, three-run github-actions-security stamp sat unread in
results/raw/2026-07-28/.
Four further findings are reported separately and do not fail the run, because the fix for each is a
paid re-run rather than an edit: a skill with no suite at all, a task no stamp has ever graded, a
stamp older than the skill or the specification it measured, and a stamp that graded a modified
working tree. --strict fails on those as well. The first is reported rather than failed for a
reason worth stating: the structural rules require a rendered results file, so the suite that would
answer the finding cannot be authored complete until a run has been paid for. Freshness compares the
commit a stamp recorded in source-revision.json against the commit that last changed the skill or
the specification, so a change made later on the day of the run is still seen; a stamp with no
recorded revision falls back to comparing dates, and a stamp that graded a modified tree measured
source held in no commit, which nothing can reproduce. The staleness check reads git log, so it
reports nothing useful outside a checkout. Like check_skills.py it needs no third-party package,
and the evals job runs it with fetch-depth: 0 so the freshness comparison has history to read.
scripts/check_citations.py reads the path/to/file:123 citations this repository argues from,
in README.md, SECURITY.md, docs/*.md, instructions/*.md, skills/**/*.md,
agent-templates/*.md and the hand-written eval READMEs, and fails when one does not hold. A
citation is evidence only while it resolves, and an edit anywhere above a cited line moves it
without touching the document that cites it. It checks that the line number is inside the file,
that the cited line is not blank, which is what a shifted number looks like most of the time,
that a range such as `path:12`-`:15` ends after it starts, and, where the citing sentence
quotes the source, that the quoted passage is near the line named. A
cited path resolves as a repository path or as a unique path suffix among tracked files, since the
documents abbreviate; eval fixtures are left out of that index, because a fixture ships its own
lint.yml and would make every citation of the real one ambiguous. A suffix matching more than
one tracked file, such as a bare SKILL.md, resolves to none and fails, since a citation nothing
can resolve is where drift hides.
A bare continuation such as `:21` is held to the same checks. It inherits the file named by
the section heading, by the table row it sits in, or by the sentence before it, and inside a row
the row's own subject wins, because naming another file in one cell does not change what the row
is about. Where a document breaks that rule the continuation resolves to the wrong file and is
reported, which is the correction: the fix is to name the file. This matters more than the count
suggests, since continuations outnumber the citations that name a path. It needs no third-party
package.
Repairing a moved citation is mechanical, so --renumber does it: the script diffs each cited
file against HEAD, maps the old line numbers to the new ones, and rewrites the citations that
moved, continuations included, printing each one. It refuses to run when a citing document
itself has uncommitted changes,
because the mapping runs from HEAD and a number already corrected by hand reads the same as one
that never moved, so rewriting it would shift it twice. Commit the prose first, or finish the
remaining citations by hand. Where a citation quotes its source, a failure names the line the
quoted passage is actually on, which is the correction rather than a report of the drift.
Every SKILL.md declares a capabilities block: the tools it uses, the commands it runs, the
paths it touches, and the hosts it reaches. check_skills.py checks that block's shape and fails
without it. check_capabilities.py compares a branch against origin/main and prints what the
change adds that the block does not declare: a hostname, a path outside the repository, or a
command name inside a shell fence. It reports and does not fail, on purpose. The declaration and
the body have the same author, so a contributor who adds a command and declares it passes; the
block exists to put a capability change in a four-line frontmatter diff rather than a
four-hundred-line body diff. It cannot see a capability written in prose, which is most of them.
claude plugin validate . checks the marketplace manifest against Claude Code's own schema, and
claude plugin validate --strict <plugin-dir> checks the skills themselves, which is what catches
frontmatter that fails to parse. Both need the Claude Code CLI, so they are local steps rather
than CI ones.
Markdown rules apply to every .md file and are configured in .markdownlint-cli2.yaml: prose
wraps at 100 columns, headings and lists are surrounded by blank lines, and code fences declare a
language. The ruff and ty steps hold this repository's own Python to
instructions/python_coding_instructions.md, with the tool versions pinned in uv.lock and every
lint ignore justified in pyproject.toml.
SECURITY.md carries the private reporting channel and the response time, the supported versions, the procedure for withdrawing a bad release, and the data-access statement: what this content reads, what it runs, and what it sends, named by path and endpoint. A project installing these skills is the reader that statement is written for.
Consumers install from a tag, so a release is a tag rather than a branch state. The rule this
repository publishes at skills/github/github-repository-security/references/agent-content.md:125
applies to itself: release from a tag, and make the tag protected and immutable.
A release is cut in this order:
- Set the same
versionon every plugin entry in.claude-plugin/marketplace.json. It isMAJOR.MINOR.PATCHwithout thev, andscripts/check_skills.pyfails the build when an entry is missing one, when one is malformed, or when the entries disagree. One repository at one tag is one version. - Merge that change through a pull request, like any other.
- Tag the merge commit
v<version>and push the tag. - State in the release notes what changed in what a skill can do: a new command, a new file it reads, a new host it reaches, a changed pin. Prose about improvements is not that.
.github/rulesets/release-tags.json holds the tag protection, kept in the repository so the
policy is reviewable and reproducible rather than living only in a settings page. It targets
refs/tags/v*, blocks deletion and non-fast-forward updates, and lists no bypass actors, so a
published tag names the same tree permanently and a mistake in one is corrected by cutting the
next version rather than by moving the old one. Apply and read it back with:
gh api --method POST repos/konstruktoid/agent-instructions-skills/rulesets \
--input .github/rulesets/release-tags.json
gh api repos/konstruktoid/agent-instructions-skills/rulesets --jq '.[] | {id, name, target}'A tag is a fixed point, not a safe one: a tag cut from a bad commit is a bad tag. What tagging changes is that a consumer moves between versions deliberately instead of receiving every commit at the moment it merges.