Skip to content

Repository files navigation

Agent Instructions and Skills

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.

Repository structure

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

instructions/

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.

skills/

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.

agent-templates/

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 ships model: 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 to user, project, or local gives the agent a memory directory whose MEMORY.md is read into its system prompt at startup and written to as it works. Read the caveat before setting it: memory adds Read, Write, and Edit to the agent whatever tools: holds, so a review-only copy that dropped Edit gets it back and a copy of prose-editor gains Write. Under project scope 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.py fails 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.

Using this library from another project

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.

Skills, as a Claude Code plugin

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-plugins

Without 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-skills

The 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-standards

Every 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.

For a whole team, through project settings

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.

Instructions documents, and non-plugin setups

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-standards

Then 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-coding

Do 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, by copying

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.md

Then 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.

Copying, as a last resort

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.

Adding new material

  • 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.md when the goal is a repeatable procedure with a verification step. Write the description field 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.py fails when a skill is unlisted, listed twice, or points at a path with no SKILL.md.

  • Write description in 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/*.md file over 100 lines a ## Contents section 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.py fails when a long reference file has no Contents section, 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.py compares 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.md and python-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>.md when 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 smallest tools: allowlist the work needs, and no memory: 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 grants Read, Write, and Edit beside the allowlist rather than within it, which is a widening no reader of the tools: 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 adding Bash only 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

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 in assertions.json derived 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-security and github-organization-governance have none, because a task for either acts on a live GitHub organization rather than on a fixture directory, and terraform-secure-iac and terraform-testing have none yet; scripts/check_evals.py reports each of them as unmeasured.
  • Trigger evals. trigger-eval.json holds 10 routing probes per skill, five in scope and five adjacent but out of scope, which measure the description field 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.

Checks

.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

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.

Releases

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:

  1. Set the same version on every plugin entry in .claude-plugin/marketplace.json. It is MAJOR.MINOR.PATCH without the v, and scripts/check_skills.py fails 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.
  2. Merge that change through a pull request, like any other.
  3. Tag the merge commit v<version> and push the tag.
  4. 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.

About

A library of reusable instructions and Claude Code skills for AI coding agents.

Topics

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages