Skip to content
Back to skills

Dotnet Consistency

ASecurity

Use when auditing this plugin's own docs, skills, agents, rules, hooks, CLAUDE.md and README.md — three axes at once. (1) Do they match the actual MCP tool implementation under src/DotnetToolkit.McpServer? Run after adding, removing, renaming, or changing the signature/return shape/[Description] of a tool, or after adding or editing a hook. (2) Do we still follow official Claude Code and Claude API guidance — "are our tool descriptions findable", "does tool search actually find every tool", "...

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 5, 2026
ai-agentsrustgoshellbashawstestingcode-reviewgitapisecurity

Works with

  • claude code
  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 22, 2026

npx -y skills add Attemainio/dotnet-toolkit --skill dotnet-consistency --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dotnet Consistency?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dotnet Consistency
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/attemainio-dotnet-consistency/badge)](https://www.skillsdirectory.com/skills/attemainio-dotnet-consistency)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dotnet-consistency
description: Use when auditing this plugin's own docs, skills, agents, rules, hooks, CLAUDE.md and README.md — three axes at once. (1) Do they match the actual MCP tool implementation under src/DotnetToolkit.McpServer? Run after adding, removing, renaming, or changing the signature/return shape/[Description] of a tool, or after adding or editing a hook. (2) Do we still follow official Claude Code and Claude API guidance — "are our tool descriptions findable", "does tool search actually find every tool", "is CLAUDE.md too long", "is anything always-loaded that shouldn't be", "check we follow Claude's guidelines", "refresh the Claude docs links". (3) What has drifted since a baseline — "what has drifted since <commit>", "what did we change and forget to document". Also owns the install-procedure audit — "audit the init skill", "does what we ship actually reach a consuming repo", "would uninstalling leave anything behind". Reports or fixes exact drift, file by file.
---

# Auditing plugin self-consistency

This plugin teaches Claude to use its own MCP tools through a scattered set of hand-maintained files —
skills, agent definitions, rules, hooks, `docs/*.md`, `CLAUDE.md`, `README.md`. None is generated, so
any of them can silently drift the moment something changes and the doc edit is skipped. This is the
audit pass that catches that, run as its own task rather than trusted to unrelated work.

## Three sources of truth, and which one wins

**The code is not the only ground truth**, and treating it that way is how a defect gets locked in:
the doc gets rewritten to match code that was itself wrong.

- **Code is ground truth for *facts*.** Tool names, signatures, arguments, defaults, return shapes,
  error codes, what a hook actually dispatches. A doc that disagrees here always loses.
- **Official Claude documentation is ground truth for *design*.** How a tool gets found, what is
  always loaded and what is not, what a skill / agent / rule may contain, the harness's own limits.
  **Code that disagrees here loses** — and the result is a `CODE FINDING`, not a doc edit.
- **Internal documents are ground truth for nothing.** They are claims, checked against both.

## The three reference files

All in `${CLAUDE_PLUGIN_ROOT}/skills/dotnet-consistency/`, read on demand — a run that only
needs drift detection should not pay for the rest.

| File | Owns | Read by |
| --- | --- | --- |
| `claude-docs.md` | the official Claude doc URLs, their must-remember shortlists, and the staleness gate | Step 1 |
| `harness-compliance.md` | the by-section checklist §A–§G with every threshold and its citation | Step 1 |
| `drift.md` | the baseline, the evidence order, and the drift-sensitivity ranking | Step 7 |

## Step 0 — enumerate the actual tool surface

Get the current, complete list from the source — don't trust any doc's tool list, including this
skill's own examples; that is exactly the kind of claim being audited. **Not `grep`**: the
`guard-cs-bash-read` hook flags a shell read of a solution `.cs` file as advisory (allowed, not
blocked, since 2026-08-21) rather than denying it, but `get_symbol` is still the right call regardless
of what the hook does — this is a symbol query, not a text search.

```
get_symbol(symbols: ["ContextTools", "FlowTools", "GraphTools", "HistoryTools",
                     "RenameTools", "MetricsTools", "ServerTools", "GuardTools"], include: "members")
```

Each `[McpServerToolType]` class's public methods **are** its tools, one per method, with every
parameter and default. Confirm the class list itself first with
`search_index(query: "Tools", pathPrefix: "src/DotnetToolkit.McpServer/Tools")`, so a **new** tool
*group* is caught rather than assumed away — a new `Tools/*.cs` file is itself a finding (Step 7).
That search is also what keeps the list above honest: it is an example, and an example of the thing
being audited goes stale exactly like any other doc claim.

Not every file under `Tools/` is a tool group. `ToolTelemetry.cs`, `ResponseGuard.cs` and
`VocabularyHint.cs` carry no `[McpServerToolType]` and are shared internal helpers; they belong in
`docs/design/architecture.md`'s `Tools/` table, not in any tool list. **Check the attribute, not the
folder.** A search hit's `modifiers` column separates them at a glance without a second call: a tool
group renders `ps` *and* an `A1` in `shape` (the `[McpServerToolType]` attribute), a helper renders
`is` with no `A`.

## The audit, step by step

**1. Harness compliance.** Read `harness-compliance.md` and run §A–§G. **First**, because it is the
only axis that can produce code findings, and because a guidance change invalidates a
previously-clean result on every other axis.

Before running it, apply the staleness gate: if `claude-docs.md`'s `last-verified` is older than its
`refresh-window`, or the user asked to refresh the links, run that file's refresh procedure and update
the shortlists before checking anything against them.

**2. Implementation.** For each tool from Step 0, read its method body (not just the `[Description]`)
— `get_symbol` for its `declarationSites`, then a ranged `Read` of that span — and confirm what it
actually accepts, returns, and errors on. Docs
get audited against this, not against their own prior text.

**3. `[Description]` attributes vs. documented behavior and return shape.** Compare each tool's
`[Description]` (on the method and on each parameter) against:
   - `docs/tools/<tool>.md` — its "When to reach for it" guidance, arguments, defaults, the example
     call/response, and its **Next steps** footer. If you can, call the tool with the documented
     example arguments and confirm the response still matches what's printed; an example that no
     longer matches the current return shape is worse than no example.
   - Any wording that has drifted from what the code now does — e.g. a default named in the attribute
     that the method body no longer honors.
   - Whether it works as a **search target** — that criterion, its thresholds and the live probe are
     `harness-compliance.md` §B and §C, already run in Step 1. Don't re-derive them here.

**4. Skills vs. tool set.** For `skills/dotnet-read/SKILL.md`, `skills/dotnet-write/SKILL.md`,
`skills/dotnet-explore/SKILL.md` and `skills/dotnet-review/SKILL.md`, confirm every tool they describe
still exists (Step 0) with the arguments they claim, and that every tool relevant to that skill's
subject actually appears in it — a tool added to `Tools/*.cs` that fits an existing skill's scope but
isn't mentioned there is a finding, not just a missing doc row.

**Every one of the 17 tools must be named by some skill**, because
`.claude/rules/dotnet-index.md` names none. A tool named by no skill is unreachable: nothing
always-loaded points at it, and free-text `ToolSearch` does not reliably find it (§C). Split by side —
retrieval and the server/meta tools in `dotnet-read`, `rename_symbol` in `dotnet-write` — with
`workspace_status` and `reload_workspace` legitimately in both, since each skill's step 0 depends on
them, and `set_hook_guards` in `dotnet-performance` alone, its only sanctioned caller.

**`set_hook_guards` is the one deliberate exception, owned by `dotnet-performance` alone.** It is not
missing from `dotnet-read`; it is kept out. The tool suspends the guards that make the rest of the
plugin load-bearing, so naming it in the skill every read task loads would advertise the off-switch to
every session — including the ones the guards exist for. Reachability is satisfied because
`dotnet-performance` names it (for its strict variant only — the default procedure calls it nowhere,
because Claude Code's auto-mode classifier denies the call as self-modification and the meter's
`wholeFileCsReads` count answers the same question without it), and that skill is the only sanctioned
reason to call it. **If a future audit finds it added to `dotnet-read` or to the always-loaded rule,
or back in `dotnet-performance`'s default steps, that is the finding** — not its absence there.

**5. Every instruction/guideline file that tells a caller to use the MCP tools.** Check each still
lists every tool from Step 0, with nothing stale (a tool it describes that no longer exists) and
nothing missing (a tool that exists but appears nowhere in it):

| File | What it must carry |
| --- | --- |
| `docs/tools/<tool>.md` (one per tool, plus `server.md`) | when to reach for it, arguments, one real example call/response, and a **Next steps** footer naming what to call with what it just returned. Every tool from Step 0 has a file, and no file names a tool that no longer exists — **the filename is the reachability contract** now that `dotnet-index.md` derives it (`<tool>.md`, the five server/meta tools sharing `server.md`) rather than tabulating it, so a manual named anything else is unreachable no matter how good it is. Each tool's `[Description]` should also name its own file. These carry the per-tool mechanics that must **not** move into the always-loaded router |
| `CLAUDE.md`'s "Non-negotiable workflow" and "Where to read what" | that they still route to `.claude/rules/dotnet-index.md` and the maintainer-facing files rather than carrying a copy of any table. A per-tool table, architecture rundown, skill catalog, or size policy reappearing here is drift — each was moved out deliberately (`harness-compliance.md` §A) |
| `docs/design/architecture.md`'s `Tools/` table | every `Tools/*.cs` file and the tool names it groups — a new file (Step 0) needs a new row. Also its Id-namespace table, subsystem list, and "Changing the tool surface" consequences (positional test callers, `Contracts/Contract.cs` bump) |
| `.claude/rules/dotnet-index.md` | **the only always-loaded rule** — here *and* copied verbatim into consuming repos by init, so drift ships and is paid by every session and every subagent. **What it may and may not contain is `harness-compliance.md` §E**, and §D owns its size. It is a **pure skill router**: check that it names **no MCP tool at all** (one appearing is the finding), that its four rows still match the skills that exist, and that the "agents are launched by skills" mandate survives. A tool table, a standards table, a `limitedBy` section or a `pluginRoot` join reappearing here is drift — each was moved out deliberately |
| `standards/index.md` | the **shared** standards routing table. It lists exactly the files present in `standards/`; its stated `dotnet-code-review` core matches `agents/dotnet-code-review.md`'s six exactly; every "When" cell states an **observable property of the code** rather than a topic; and the `pluginRoot` join matches what `workspace_status` actually returns, naming **exactly one** location per file — a per-repo override tier reappearing is the drift to flag |
| `skills/dotnet-read/SKILL.md` | (a) it names every retrieval tool from Step 0, each with a **Manual:** pointer whose filename is derived (`<tool>.md`, the five server/meta tools sharing `server.md`); (b) its per-tool "Answers to" lists stay questions, never argument grammar — that is the manual's job; (c) it owns the **cheap-route table** and the `limitedBy`/TOON response conventions, which must not drift back into the always-loaded rule; (d) its step 0 mandates `workspace_status` before any read, and the `${CLAUDE_PLUGIN_ROOT}`-is-not-expanded warning is intact |
| `skills/dotnet-write/SKILL.md` | (a) its pre-edit standards step **defers to `standards/index.md` rather than re-listing the files**; (b) it resolves standards as `<pluginRoot>/standards/<name>.md` via `workspace_status`, with no override tier; (c) **it owns the write-*decisions*** — when to dry-run, when to amend versus rebuild, the definition of done, raising `.editorconfig` severity with the user rather than unasked — which were moved out of the always-loaded layer and must not drift back; (d) its step 0 mandates `workspace_status` before an edit, and its "How the index follows your edits" section matches what `refresh-cs-files` actually does (silent on success, a note only for a new/deleted file, a queued refresh, or an unreachable server); (e) it states `Edit`/`Write` as the write path and `rename_symbol` as the one tool-made edit, with `dotnet build` as the gate — a validating or compiling tool reappearing in its text is the drift to flag. Its two known duplication risks (standards list, write-time checklist) are `harness-compliance.md` §A |
| `skills/dotnet-explore/SKILL.md` | it is the **only** sanctioned launcher of the `dotnet-explore` agent. It must check workspace readiness before spawning, must state that the agent reads no `.cs` text (it reports locations; the caller reads), and must not restate the agent's own router — `agents/dotnet-explore.md` is self-contained and authoritative |
| `skills/dotnet-review/SKILL.md` | it does **not** claim to inject a `Standards root:` into the spawn — the agent resolves its own `pluginRoot` from its own `workspace_status` call (§below), so the skill's job is only to state each instance's scope/mode/focus, never a path. It must also check workspace readiness **before** spawning: launching parallel instances against a degraded workspace is a parallel waste, and it points at `standards/index.md` rather than restating the table |
| every file in `standards/` | every MCP tool named in them (e.g. `get_references` in `testing.md`'s calibration, `get_symbol` in `xml-documentation.md`'s) still exists with the described behavior; cross-file pointers still resolve; **none offers a per-repo override path** — that tier was removed, and a reintroduced sentence would promise a mechanism nothing implements. The no-frontmatter invariant is `harness-compliance.md` §G |
| `skills/dotnet-init/SKILL.md` | that it still **copies `.claude/rules/dotnet-index.md` verbatim** rather than embedding its own tool table or standards list. Also the "what is deliberately *not* written" table and the uninstall list: every asset the plugin ships is in exactly one of write / not-written / uninstall, and the write and uninstall lists name the same files |
| `docs/install/audit.md` | the install-procedure audit this skill runs in Step 5b (maintainer-facing; the consumer never reads it). Its four-mechanism inventory must cover every top-level directory the plugin actually ships (`skills/`, `agents/`, `hooks/`, `docs/`, `docs/tools/`, `.claude/rules/`, `scripts/`, `.mcp.json`), and its reachability scenarios must resolve to files that exist |
| `docs/install/{install,verify,uninstall}.md` | the three paths `dotnet-init` routes to. They name the files init actually writes (the settings allowlist, `install.json`, and `.claude/dotnet-toolkit/.gitignore`) and no file it doesn't, and the skill's router table points at all three. The three must agree on the **count**: a file added to install.md's table and not to verify.md's presence check is installed but never verified |
| `.claude/dotnet-toolkit/.gitignore` | copied verbatim into consuming repos by init, so it is a mechanism-2 file like the rule. Check it still covers every directory the skills write reports into — a new report directory added to a skill without a line here is committed by the next `git add -A` in every consuming repo — and that it still leaves `install.json`, `config.json` and `cache/` alone |
| `skills/dotnet-selfeval/SKILL.md` + `analyses.md` | the efficiency probe matrix. Its Step 2 families still cover every tool from Step 0; the tools it lists as recording **no** telemetry (`ping`, `workspace_status`, `set_output_format`, `reload_workspace`, `get_retrieval_metrics`, `set_hook_guards`) are still exactly the ones with no `ToolTelemetry.Record` call, and it still tells the run **not to probe `set_hook_guards` by calling it** — a suspension mid-evaluation would let the rest of the run bypass the very tools being measured; its `taskId`/`taskIds`/`groupBy:"task"` recipe still matches `MetricsTools`/`MetricsReader`. A tool newly instrumented but still listed as unmeasurable understates what the evaluation can see |
| `agents/dotnet-code-review.md` | the agent's **complete, self-contained** instructions. `tools:` frontmatter matches Step 0's read-side subset (nothing stale, still excluding `get_project_graph`/`detect_circular_dependencies`, withheld as out of a scope slice's reach); every tool it names in Process, evidence bars and Boundaries is granted there *and* behaves as described; its standards core and per-aspect fold-ins resolve to real `standards/` filenames, reached through the `pluginRoot` it resolves itself from its own `workspace_status` call and **never** by constructing a `${CLAUDE_PLUGIN_ROOT}` path itself; the no-root fallback still reports standards-derived aspects as not-assessed rather than reviewing from memory; it still requires the `Standards:` line; and it has **not** re-acquired a `skills:` grant or a directive to read `docs/design/agents.md` — both removed to hold the per-instance token baseline down |
| `agents/dotnet-explore.md` | the navigator's **complete, self-contained** instructions. Its router must name no write tool as callable, and three properties are load-bearing (rationale in `docs/design/agents.md`, not here): **no writer and no `memory:` key** in `tools:`, `Read` whitelisted to `docs/tools/<tool>.md` only, and no `.cs` reads at all |
| `docs/design/agents.md` | covers **both** agents; **human-facing only — neither agent is told to read it.** It must not contradict the agent file (authoritative); its tool-grant and token-budget sections match the agents' actual frontmatter and loading rule; every tool it names still exists |
| `docs/design/hooks.md` | maintainer-facing design notes; nothing routes to it. Describes exactly the hooks `hooks/hooks.json` registers and the behavior they implement — matchers, allow/deny cases, fallback chain |
| `README.md`'s Features table | every tool from Step 0 appears in some row; no row names a tool that no longer exists |

**5b. Consumer reachability — does the plugin work out of the box?** Steps 2–5 check that the files
describing the tools are *accurate*. This checks they are *reachable from a consuming repo*, where
this repo's `CLAUDE.md` does not exist. Every operational instruction must live in something that
ships: the one rule init copies (`.claude/rules/dotnet-index.md`), a skill, an agent file, or a
`docs/`/`standards/` file reachable by `${CLAUDE_PLUGIN_ROOT}` path **from a skill** — rules and agent
files must not depend on that variable expanding, which is why `agents/dotnet-code-review.md` resolves
its own `pluginRoot` from its own `workspace_status` call instead of expecting a path handed to it.

**The install procedure is half of this step.** `dotnet-init` is prose, hand-maintained, and
describes a file set that grows every time this plugin gains a tool, doc, skill, or standard. When it
falls behind, the failure is silent in both directions: an asset ships but never reaches the consumer,
or the uninstall instructions leave files behind that keep steering a repo that no longer has the
plugin. **Read `docs/install/audit.md` and run it.** Two of its findings are hard errors worth naming
here because both have shipped: a `${CLAUDE_PLUGIN_ROOT}` path in a *copied rule*, and any claim that
mechanism-1 assets need a repo-local install or cleanup step.

Then two sweeps, both cheap:

- **This repo's `CLAUDE.md`, paragraph by paragraph.** For each instruction, decide: is this *about
  maintaining the plugin* (correct — it belongs only here), or *about using the tools*? Anything in
  the second category must also exist in a shipped file, with `CLAUDE.md` carrying a pointer rather
  than a second copy. The write-path discipline is the canonical example — the "`Edit`/`Write` are
  the write path, the hook re-indexes, `dotnet build` is the gate" argument lives in the rule and
  `dotnet-write`, never in `CLAUDE.md`, because a consumer never sees `CLAUDE.md`.
- **The maintainer's memory directory** (`~/.claude/projects/<repo-slug>/memory/`, indexed by its
  `MEMORY.md`). Read the index, then each entry, and sort every one into: **(a)** operational for
  consumers → must be embedded in a shipped file; **(b)** operational for this repo only → belongs in
  `CLAUDE.md` or `.claude/rules/`; **(c)** personal or environment-specific → belongs in neither, and
  is correctly only in memory. Memory is user-local and does not travel with the plugin, so anything
  in (a) or (b) that exists *only* there is a finding — silently absent for every other user, and for
  this repo after a memory reset.

Report each finding as: the memory or `CLAUDE.md` paragraph, its category, and the shipped file that
should carry it. Do not copy a category-(c) fact into a shipped file to close a finding —
categorising it correctly *is* closing it.

**6. Hooks and launch path.** All seven hooks are `hook <name>` subcommands of the published server
binary, in `src/DotnetToolkit.McpServer/Hooks/` — `HookCli.cs` dispatches, the
`GuardCsRead`/`GuardCsBashRead`/`CsFileRefresh`/`CsWriteChecklistHint`/`ToolCallMeter` files carry
the behaviour, and four more have no `hooks.json` entry of their own: `CsFileMembership.cs`/
`BashCommandScanner.cs` (shared guard logic, including the ranged-read recogniser), `GuardSuspension.cs`
(the off-switch `HookCli` reads before dispatching the two read guards, written by `set_hook_guards`)
and `ControlClient.cs` (how `refresh-cs-files` and `meter-tool-call` both reach the running server,
with the command words in `Control/ControlCommands.cs`).
Read those with `get_symbol` (not `grep` — a tree grep here would only earn an advisory note from
`guard-cs-bash-read`, not a denial, but `get_symbol` is still the right call), plus `hooks/hooks.json`
and `.mcp.json`:
   - Does each guard's advisory text still name the correct tool(s) and procedure (`get_symbol` plus
     a ranged `Read` for the read guards, `reload_workspace(scope: "all")` for the refresh hook's
     unreachable-server note)? It is read at the exact moment a caller is nudged — a stale one
     teaches the wrong fix at the worst moment. And do both read guards still pass a **ranged** read
     silently in every mode (`Read` with `offset`/`limit`; `sed -n 'a,bp'`, `head -n`, `tail -n +N`)?
     A guard that notes a ranged read is arguing against the route the skills recommend.
   - **`hint-cs-write-checklist` owns the write-time checklist**, matching `Edit`/`Write`/
     `NotebookEdit`. Its text must still match the standards it summarizes (`security.md`,
     `testing.md`), and it must stay once-per-session and fail-quiet — a version that fires on every
     edit, or that denies, is a finding.
   - **`refresh-cs-files` is the `PostToolUse` hook that makes the index follow edits.** It must stay
     silent on success, send every written `.cs` path (from `Edit`/`Write`/`NotebookEdit`/`apply_patch`
     input and from `Bash` write shapes) to the server's `refresh` command, ask for a rescan on a git
     verb, and answer with a note only when a file was added/deleted (reload started), a refresh is
     queued, or the server was unreachable.
   - **`meter-tool-call` is the one hook matching every tool (`"*"`), and the one exempt from
     `GuardSuspension` on purpose.** It observes rather than withholds, and a guard suspension is
     asked for precisely in order to measure the unguarded route — a version that honours
     suspension silently produces a benchmark with half its data missing, which is the finding. It
     is also the hook that records `wholeFileCsRead` on every call, the count `dotnet-performance`
     reads per probe instead of switching guard state; the predicate is `WholeFileCsRead.Matches`,
     shared with the two read guards so the count and the advisory agree on what a whole-file read
     is — a version where they diverge is the finding. It
     must also stay fail-open-and-silent and keep reporting over the control channel rather than
     opening the store itself: a hook process has its own ambient session id, so a row it wrote
     directly would be invisible to every read.
   - Does `hooks/hooks.json` name subcommands `HookCli` actually dispatches, with matchers
     (`Edit`/`Write`/`NotebookEdit`/`apply_patch`/`Read`/`Bash`) matching `docs/design/hooks.md` and
     `docs/design/architecture.md`'s "Packaging" section?
     The shell-free and output-cap invariants are `harness-compliance.md` §G. `scripts/` holds
     developer conveniences only; anything new there or in `Hooks/` unmentioned by
     `docs/design/hooks.md` or "Packaging" is a Step 7 finding.
   - Matchers key on tool name, not on which agent calls, so they fire for subagents too. For
     `dotnet-code-review`: (a) its `tools:` list never grants `Edit`/`Write`/`NotebookEdit` —
     `memory: project` makes the harness grant them anyway, which is why Boundaries carries the
     weight; (b) its Process step 2 still locates with `search_index`/`get_symbol` and reads with a
     **ranged** `Read` over `declarationSites`, treating a whole-file `Read` as the scope-mode
     exception rather than the default — its own text (checked in Step 5's table) must say that a
     ranged read draws no note and a whole-file one only advises. For `dotnet-explore`, whose own
     file bans `Read` on `.cs` outright with no hook backstop behind that ban, a `Read` of a `.cs`
     file appearing in its transcript is itself the drift to report — the file's own instruction,
     not any hook, is what should have stopped it.

**7. Internal drift.** Read `drift.md` and run it. This is the step that starts from *what changed*
rather than from a file list: `get_semantic_diff` against a baseline, then `git log` for the recorded
intent and for everything the semantic diff cannot see, checked against the drift-sensitivity
ranking. It is how a shipped-but-undocumented tool gets found — nothing in a
per-file check ever will, because the files that should mention it are internally consistent.

**8. The skills' own instructions.** Once Steps 1–7 have surfaced concrete drift, the fix usually
touches a skill body, not just a table row — e.g. a new tool needs a section in `dotnet-read` or
`dotnet-write` (whichever side it belongs to), a row in that skill's cheap-route table if it
displaces an existing route, *and* its own `docs/tools/<tool>.md`. **It needs nothing in
`.claude/rules/dotnet-index.md`**, which names no tools: a new row there is only warranted by a new
*skill*. Update the skill body, not only its tool list, so a caller reading the skill gets the same
guidance a caller reading the code would.

**9. `CLAUDE.md` and `README.md` last.** The two files a fresh session or a new user reads first, so
they should reflect the *already-corrected* state of everything above rather than being patched
independently of it. Update `CLAUDE.md` per Step 5's table, then `README.md`'s Features table and prose.

## Output format

Report drift the same way `dotnet-code-review` reports findings — concrete and file-anchored, not a
narrative:

- **File:line** of the stale claim.
- **What it claims** vs. **what is actually true**, naming which source of truth decided it — the code
  (Steps 0/2) or the official guidance (`claude-docs.md`, via Step 1).
- **Exact fix** — the replacement text or the specific file to add a row to. Point at the file; don't
  describe the fix in the abstract.

A finding driven by guidance rather than by code, where the **code** is what must change, uses a
distinct form so it is never mistaken for something this skill already fixed:

> **CODE FINDING** — `src/…/Tools/X.cs:NN` · guideline: `harness-compliance.md §B` ·
> current: *<measurement or text>* · required: *<threshold>* · proposed replacement: *<exact text>* ·
> apply via: `dotnet-write` → `Edit`, then `dotnet build`.

Group findings by file, in Step 5's table order, then hooks, then `CLAUDE.md`/`README.md` last, with
`CODE FINDING`s collected together at the end. State the baseline Step 7 used. If a section is in
sync, say so in one line — don't manufacture findings to justify the run.

## Fixing vs. reporting

- **Doc findings: fix them yourself**, in Step 1→9 order. Every such fix is to a non-`.cs` file, so
  no hook fires and no build is needed.
- **`CODE FINDING`s: report only, never apply.** This skill does not edit `.cs`, even when the fix is
  a one-line attribute change with no behavior impact — an audit that can change the tool contract
  can no longer be run freely. Hand it off with the exact replacement text; the user or a follow-up
  `dotnet-write` task applies it.
- **Never silently skip a step** because "nothing looks wrong there" — state it was checked and came
  back clean. A skipped step and a clean step read identically in a report, and only one of them is
  true.

Files in this skill

  • SKILL.md26.7 KB
  • claude-docs.md11.4 KB
  • drift.md6.1 KB
  • harness-compliance.md20.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.