Skip to main content

author-skill

Author a new skill for this repo end to end — take a topic, check for existing coverage, drill the official docs behind it, write a filled handoff brief to docs/handoffs/, then draft the SKILL.md and run the post-draft checks. Use when asked to write, author, create, or scaffold a new skill, or when a drift-audit new-skill candidate has been accepted. Encodes this repo's own conventions rather than generic skill advice — verb naming for behavioral skills and fabric-/pbir-/pbid- prefixes for platform ones, the description as the entire trigger mechanism, long detail split into references/, lint-frontmatter.py, and which tree a new skill belongs in and its deploy step. Drills before it writes and never encodes an unverified claim. Ends at a linted draft plus a fresh-session test plan; writes no test fixtures and does not commit — fixtures and validation are test-skill's, which reads the brief back off disk. To fold a session learning into guidance that already exists, use learn instead.

Jump to install

Source facts

Repository
wardawgmalvicious/agent-config
Last source activity
September 16, 2026 at 00:07
Detected SKILL.md language
English
Stars
1
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
author-skill
description
Author a new skill for this repo end to end — take a topic, check for existing coverage, drill the official docs behind it, write a filled handoff brief to docs/handoffs/, then draft the SKILL.md and run the post-draft checks. Use when asked to write, author, create, or scaffold a new skill, or when a drift-audit new-skill candidate has been accepted. Encodes this repo's own conventions rather than generic skill advice — verb naming for behavioral skills and fabric-/pbir-/pbid- prefixes for platform ones, the description as the entire trigger mechanism, long detail split into references/, lint-frontmatter.py, and which tree a new skill belongs in and its deploy step. Drills before it writes and never encodes an unverified claim. Ends at a linted draft plus a fresh-session test plan; writes no test fixtures and does not commit — fixtures and validation are test-skill's, which reads the brief back off disk. To fold a session learning into guidance that already exists, use learn instead.
argument-hint
[topic]
allowed-tools
Read Write Edit Glob Grep Bash WebFetch
model
fable
effort
max
disable-model-invocation
false
context
inline
# Author a skill Take a **topic** and end at a linted `SKILL.md` draft plus a runnable fresh-session test plan. The input is a subject, not a specification — deciding the name, the boundaries, and what the skill deliberately will not cover is the work, not a precondition for it. This skill is house-style. It encodes how *this* repo builds skills; the generic mechanics of the Agent Skills format are somebody else's job (see step 1). Paths below are relative to the repo root. This skill is project scope and fires only in sessions here, but a skill it authors in `skills/` deploys through `~/.claude/skills` — a real directory holding **one junction per skill** in that tree. **An edit is immediate; a new skill needs the linker once.** The asymmetry follows from those per-skill junctions. Editing an already-junctioned skill is live the moment it hits disk, for every session on this machine, committed or not. But a directory that did not exist at the last `scripts/link-claude.ps1` run has **no junction**, so a newly authored skill is invisible everywhere — absent from the listing, and `/<name>` answers `Unknown command`. Measured 2026-09-02: `land` was missing from a listing of 8 junctions immediately after its `SKILL.md` was written, and appeared only once the linker ran. On this machine the form is `./scripts/link-claude.ps1 -SkillGroups workflow,social,meta`, **never bare** — see root `CLAUDE.md`. A **platform** skill is the exception that proves the rule: `fabric` and `powerbi` are pruned from user scope here on purpose, so a new one stays unlinked by design and enters no session's payload. Nothing is broken when that happens, and no linker run changes it. **A project-scope skill needs the linker not at all**, which is the third case and the easiest to get wrong in the other direction. Nothing junctions `.claude/skills/` — Claude Code reads it in place — so a new skill there is live on save for sessions in this repo, with no deploy step to forget. Do not run the linker "to pick it up"; it selects out of `skills/` and will not see it. The trade is that it is live only here, which is the whole reason it is there. Once linked, a half-drafted skill is a live half-drafted skill; finish the frontmatter before walking away. ## 1. Check this is the right skill Two routes out, both cheap to check first. **Guidance that already exists → `/learn`.** The dividing line is whether the destination exists. `/learn` folds a session learning into a skill, rule, or `CLAUDE.md` that should already have covered it. This skill creates guidance that has no home yet. A learning that arrives as "the `fabric-cicd` skill was wrong about X" is `/learn` work even if the fix is large. **Generic skill-authoring help → `skill-creator` or `plugin-dev:skill-development`.** Both are loaded and both are good. They are not wrong, only generic: they know the Agent Skills format, progressive disclosure, and description tuning in the abstract. They do not know this repo's naming rules, its lint command, its `references/` split, its junction deployment, or the discipline of writing a handoff brief first. That gap is the entire reason this skill exists alongside them. Say so plainly when the overlap comes up, so the duplication reads as deliberate rather than as something nobody noticed. If the user wants a *subagent* rather than a skill, this is the wrong skill: the artifact lives in `claude/agents/` and uses `templates/subagent-handoff.md`. Stop and say so. ## 2. Check for existing coverage Before naming anything, find out whether the topic is already covered. ``` grep -li "<topic term>" skills/*/*/SKILL.md .claude/skills/*/SKILL.md grep -n -i "<topic term>" skills/fabric/fabric-gotchas/SKILL.md ``` Check `fabric-gotchas` explicitly. `/learn` treats it as the default home for cross-product troubleshooting content, so a topic can be half-covered there without any dedicated skill existing. **An incidental grep hit is not coverage.** The worked failure is a Mirroring search matching "Spark History Server mirror" in `fabric-spark-monitoring` — same substring, unrelated subject. Read the surrounding heading before counting a hit. Conversely, a topic can be genuinely covered under vocabulary you did not grep for; skim the `skills/README.md` section list for the relevant domain as a second pass. **A grep finds a word; the script finds a competitor.** The second outcome below — two descriptions that both half-match one request — is invisible to a grep, because neither description need contain the other's vocabulary. Run it against the nearest existing skill the topic would sit beside: ``` uv run --with pyyaml scripts/skill-overlap.py overlap --skill <nearest-skill> ``` It ranks that skill's pairs by shared distinctive tokens, so what already clusters around your topic shows up before you have written a line. There is no way to score a description that does not exist yet, which is why this runs against the neighbour rather than the candidate. Read its output into the three outcomes; it changes what you find, not what you do about it. **Before authoring a *platform* skill, check upstream first.** Microsoft ships a catalog at `microsoft/skills-for-fabric`, and vendoring one of its skills is sometimes the better answer than authoring a parallel one. `drift-audit`'s `skills-for-fabric` registry entry carries the counterpart table as a starting map. Decide vendor-versus-author deliberately; the failure this prevents is a local skill and an upstream one competing for the same request, with no record of why both exist. Three outcomes: - **Covered correctly** — stop. Say where, and that nothing is needed. - **Covered partially, in a skill that owns the domain** — stop and propose *extending* that skill instead. A second skill splitting one domain makes both harder to trigger, because the model is choosing between two descriptions that both half-match. - **Not covered** — continue. The stop cases are real stops. Do not proceed to drilling because the topic is interesting. ## 3. Decide the name and namespace Put the proposal to the user before drilling. Naming is cheap to change now and expensive later — the name is in the directory, the slash command, and every cross-reference. - **Behavioral, cross-domain skills take the verb you invoke** — `commit`, `learn`, `code-review`, `drift-audit`. Read the name as the user typing it. - **Platform skills take a namespace prefix** — `fabric-`, `pbir-`, or `pbid-`. - **`powerbi-*` is reserved.** Those are vendored from `microsoft/skills-for-fabric` and keep upstream naming so re-sync diffs stay clean. Never take that prefix for a local skill. - **Pick the TREE before the group**, and pick it by asking what the skill acts on — not from the namespace, which cannot answer this. **Does it act on this repo, or on the user's own work?** - Acts on `agent-config` itself — its groups, its linter, its handoff queue, its audit ledger: `.claude/skills/<name>/SKILL.md`, project scope. No group directory. It deploys nowhere and no script reaches it, which is the point: outside this working tree it has nothing to act on, so shipping it to user scope would put it in the startup listing of every client-repo session for no possible benefit. - Acts on the user's work — code, Fabric items, reports, any repo: `skills/<group>/<name>/SKILL.md`, deployable payload. Getting this wrong is silent and costs listing budget everywhere. The seven skills at project scope today (`author-skill`, `test-skill`, `learn`, `drift-*`, `land`) were all at user scope until 2026-09-09 for exactly this reason. - **Within `skills/`, the namespace picks the group directory**, and the group directory is load-bearing. `skills/fabric/` for `fabric-*`, `skills/powerbi/` for `pbir-`, `pbid-`, `pbip-` and the vendored `powerbi-*`, `skills/workflow/` for the behavioral ones — which now means the repo-general verbs only, `code-review` and `commit` — and `skills/social/` for personal-profile writing (`linkedin-highlights`), split out so a workplace Copilot deployment of `workflow` never carries it. A new group needs adding to the machine's `-SkillGroups` default everywhere that default is written, or the next documented linker run prunes it. - **Depth is pinned per tree and a misplacement fails twice silently.** The pre-commit hook matches `^(skills/[^/]+|\.claude/skills)/[^/]+/SKILL\.md$` — two directories under `skills/`, one under `.claude/skills/`. A skill placed flat at `skills/<name>/SKILL.md`, or nested a level deeper in either tree, is invisible to the linter *and* to Claude Code, which discovers skills exactly one level under a skills root. - **Name the job, not the target**, where they differ. `drift-audit` is named that way because it audits rules, `CLAUDE.md`, and the MCP templates too — `skill-audit` would have named a quarter of its scope and collided with a plausible future skill. - The linter enforces the mechanics: lowercase letters, digits and hyphens only, ≤ 64 chars, and no `anthropic` or `claude` anywhere in the name. **Check the name against the loaded plugin skills, not just this repo.** A local skill competing with `skill-creator` or a `plugin-dev:*` skill for the same trigger surface is a real collision even though the directories never touch. Also settle the scope question at this point: **is this one skill or two?** A topic that splits cleanly into a reference half and a workflow half is often two skills (`pbir-cli` and `pbir-report-workflow` are the in-repo example). Decide with the user now; discovering it during drafting means rewriting the brief. ## 4. Drill the sources Do not write from training data. The point of this step is that every claim in the finished skill traces to something read during this run. - **Microsoft Learn** — `microsoft_docs_search` to find the pages, then `microsoft_docs_fetch` for the ones that matter. Search returns 500-token excerpts, which are enough to locate a page and never enough to encode a constraint from. - **Exact repo bytes, changelogs, release notes** — `github-mcp`. - **Anything else** — `WebFetch`. MCP tools are not in this skill's `allowed-tools`, which only pre-approves and never restricts (Claude Code skills docs, verified 2026-09-11), so leaving them out costs at most a permission prompt. Prefer them when present; fall back without ceremony when not. **Record what was drilled and what was not.** The undrilled set is what bounds the draft, and it is the part that gets lost if it is not written down at the time. A brief that says "the REST surface was not drilled; nothing in this skill describes it" is what stops the next reader assuming the omission was an oversight. The template's **Sources drilled** section is where both halves go — not Notes, and not a sentence buried in Scope. **Evidence from a client's estate is cited by kind, never by name.** Observed platform behaviour often comes from client work, and the behaviour is generic where the names are not. Write "a client sandbox workspace" and "a directory of upload files", not the workspace, item, table, column or repo names you saw — and never write a list of names "to genericize at draft time", which is the leak itself. This repo is public, a pushed name cannot be fixed forward, and the identity guard matches only names already on the denylist. On 2026-09-10 a brief reached public `main` carrying a client's workspace and table names in exactly such a list. **Delegating the drill is fine; delegating the verification is not.** A large surface splits cleanly across subagents by source family, and each one must return a verbatim quote per claim rather than a summary. Then re-read the quotes behind anything load-bearing — a permission, a limit, a path, a gate — yourself, before it reaches the draft. Observed 2026-09-11: a subagent marked a claim CONFIRMED against a quote that did not support it, and the firsthand re-reads also caught a wrong endpoint path that two shipped skills had been carrying. Stop drilling when new pages stop changing the outline, not when the source list is exhausted. ## 5. Write the handoff brief The target is `docs/handoffs/execute/<name>.md`, built from `docs/handoffs/templates/skill-handoff.md`. **Check whether that path is already occupied before writing a byte.** A `/drift-update` escalation leaves its scoping input at exactly this name, so the file that authorizes the work and the file this step produces collide by default. Such a file is normally untracked, which makes an overwrite unrecoverable — git has nothing to restore. If something is there, read it in full and treat it as source rather than as an obstacle. Carry across everything that outlives it — the authorization, the audit evidence, the undrilled set, and anything citing a gitignored path that will not survive on its own — then confirm with the user that the file may be replaced. **Never overwrite a brief path unread.** **Every heading in the template survives into the filled brief.** Sections that do not apply get `N/A — <brief reason>` under the heading, never deletion. **Strip the per-section `> Guidance:` notes.** They are instructions for filling the template, not content, and a brief that keeps them reads as half-finished. Exactly two blocks are reproduced verbatim: the guidance note directly under the title, and Claude Code's post-draft checklist. `docs/handoffs/examples/author-skill.example.md` is the reference — two guidance blocks in the finished brief, not one per heading. The other two examples predate this convention and strip both; follow the `author-skill` one. Fill `Last verified` with today's date. The brief is not ceremony and not a handoff to another surface. It is the record of what was decided and what was deliberately left out, and the excluded set is the half that cannot be reconstructed from the finished skill. Write it before drafting even though the same session does both — the ordering is what makes the scope decisions explicit instead of emergent. If an existing brief in `docs/handoffs/examples/` matches the new skill's shape, lift it as design source and record that lineage in `Changes from source proposal` using the wording that `examples/README.md` specifies, rather than re-narrating the design. Show the brief to the user before drafting from it. ## 6. Draft the SKILL.md Write the `SKILL.md` from the brief, at the path the tree decision in step 3 picked — `skills/<group>/<name>/SKILL.md` for payload, `.claude/skills/<name>/SKILL.md` for a skill that maintains this repo. **The `description` is the entire model-invoked trigger mechanism.** Write it to fire on the queries the skill should answer — the user's vocabulary, error strings, tool and command names — not to summarize the body. A description that reads as an accurate abstract and never triggers has failed at its only job. Where the skill neighbours another, spend a clause on the disambiguation. **A disambiguation pointer must be reachable from where it is read.** A `paths:`-scoped skill is in the listing only once one of its *own* globs has matched, so "use `<other-skill>`" is dead whenever the two share no trigger file. The failure is worse than a no-op: the model calls the Skill tool, gets `Unknown skill`, and reports that skill as **not installed** — a false diagnosis layered on a dead pointer, in a session where it was deployed and merely not activated. So point at a co-active neighbour by **name**, and point off the shared surface by **file** — opening the file is what activates the skill there, which makes the file the only pointer that works cold. Measured 2026-09-04: `pbir-filters` said "for filter-pane styling use `pbir-themes`", and their globs are disjoint. **Long detail goes to the skill's own `references/`, not the body.** Root `CLAUDE.md` is explicit about this. Command flag tables, per-item-type matrices, and long worked examples belong in a reference file the body points at. The linter caps the body at 500 lines, but that is a backstop, not a target. **If you are *relocating* content out of an existing body rather than writing new, verify nothing was lost before committing.** Moving prose between files is where silent content loss happens — it happened twice during the 2026-08-31 body-slimming pass, once from a substring heading match that dropped a whole section. Match headings **exactly**, with an assertion, never by substring, and diff the result against `HEAD`: ```python n = lambda s: re.sub(r'\s+', ' ', s).strip() u = lambda s: set(re.findall(r'https?://[^\s\)\|]+', s)) print("URLs lost:", u(orig) - u(new_skill_plus_all_references)) missing = [l for l in orig.split('\n') if len(n(l)) > 45 and n(l) not in n(new)] ``` Every surviving entry in `missing` must be explainable as an intentional rewrite. An empty `missing` and an empty URL set is the pass condition. Match the house voice: numbered steps, bold lead-ins for the rule being stated, an explicit constraints section at the end, and reasons attached to rules that would otherwise look arbitrary. **Nothing goes in that drilling did not establish.** A plausible claim with no source behind it is the failure mode this whole procedure exists to prevent. If something is believed but unverified, mark it as such inline with the date and version, the way `/learn` does, so a later `/drift-audit` can confirm or remove it. ## 7. Post-draft checks Run all four. Each catches something the others do not. ``` uv run --with pyyaml scripts/lint-frontmatter.py <the SKILL.md you wrote> ``` **Re-count the description.** The linter gates `description` at 1,024 and `when_to_use` at 512 — the two halves of the 1,536 listing truncation point — but it reports overflow only after the fact and never warns on a near miss: ``` uv run --with pyyaml python -c "import sys,yaml; print(len(yaml.safe_load(open(sys.argv[1],encoding='utf-8').read().split('---')[1])['description']))" <the SKILL.md you wrote> ``` 1,024 is the Agent Skills spec cap, and `description` is one of the six fields the claude.ai upload path accepts — so that is the field that has to stay portable. Re-count after *any* wording change, not once at the end. If the skill sets `when_to_use`, count it too against its own 512: it is a Claude Code extension the spec does not carry, so spending the remainder there costs no portability the skill still had. **`cat` the whole file after any edit.** YAML frontmatter is a single malformed line away from the skill silently not loading, and the Edit tool on Windows is where that line comes from. ``` pre-commit run --all-files ``` ## 8. Register it in skills/README.md Add the entry to the section the namespace implies — Behavioral, Microsoft Fabric platform, or Power BI Desktop / Reports. Update the count in the section heading where one is present. Match the house style there: what the skill covers, and — where the name is not self-evident — **why it is named that**. The `drift-audit` and `drift-handoff` entries are the model for the second half. An entry that only restates the description earns nothing the description does not already do. This is the one place this skill edits a file it did not create. Keep the diff to the single added entry and the count. ## 9. Report, sweep, hand off Report: 1. **What was drilled and what was not.** The undrilled set, verbatim from the brief. 2. **The scope decisions** — what the skill deliberately does not cover, and why. 3. **Check results** — lint, description count, `pre-commit`. 4. **That the skill is not behaviourally tested.** Say it plainly — but for the right reason. It is *not* that the file cannot reload: skills hot-reload in-session, fixed upstream in 2.1.216 and verified here 2026-08-31 and again 2026-09-02 on an in-place `description` edit. It is that the session which wrote the skill is the worst place to test it — accumulated context can mask a co-load failure, and you already know the answers you are checking for. A cold run against a `--safe-mode` baseline is the test. Name the specific queries it should fire on, so the fresh-session test is runnable rather than aspirational — **those queries are what `/test-skill` reads back out of the brief**, so a vague one here becomes a vague test there. Then **sweep `docs/handoffs/execute/`** for briefs whose work has landed. A queued brief is deleted once its change lands, or promoted into `examples/` in the one case where something cites it. That convention has already failed once unattended, which is why the sweep is a step here instead of a habit. - The brief written at step 5 **stays queued**. Nothing is validated until the fresh-session test has run. - For any other brief whose skill or subagent now exists, **propose** deletion or promotion and name which. Propose rather than act: you can see that an artifact exists, but not whether the user considers the work landed. Deletion itself is safe — briefs are committed when written, so the deleting commit and its content stay recoverable. Hand off to **`/test-skill`, then `/commit`**. Do not commit here, and do not test here either. `/test-skill` reads the brief from disk, so it can run now or in a fresh session next week — the two do not have to be back to back, and a cold run is the better one. ## 10. Constraints - **No unverified claims.** If step 4 did not establish it, it does not go in the skill. Marked-unverified is the only exception, and it carries a date. - **The stop cases in steps 1 and 2 are stops.** Route to `/learn`, propose an extension, or hand off to a generic authoring skill — do not author anyway. - **Name before drilling.** Naming and the one-skill-or-two call go to the user first; both are cheap now and expensive after a draft exists. - **Brief before draft**, even in one session.
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub