Skip to content
Back to skills

Audit

ASecurity

Audit markdown prose for AI-writing tells (slop): em dashes (zero-tolerance by default), emoji formatting, AI vocabulary, negative parallelisms, chatbot phrases, filler, stacked hedging, citation artifacts, model-era phrases ('that's the unlock', 'the part most people skip'), and the rest of the catalog (distilled from Wikipedia's Signs of AI writing plus an evolving model-era inventory), plus a judgment rubric for superficial analysis, vague attribution, promotional tone, metaphor jargon ('l...

  • 13 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 2, 2026
ai-agentsgoshellbashgitapi

Works with

  • claude code
  • cursor
  • cli
  • api

Security analysis

A100/100

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

Scanned October 4, 2026

npx -y skills add melodic-software/claude-code-plugins --skill audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Audit?

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

Security grade badge for Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/melodic-software-audit/badge)](https://www.skillsdirectory.com/skills/melodic-software-audit)

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
---
description: "Audit markdown prose for AI-writing tells (slop): em dashes (zero-tolerance by default), emoji formatting, AI vocabulary, negative parallelisms, chatbot phrases, filler, stacked hedging, citation artifacts, model-era phrases ('that's the unlock', 'the part most people skip'), and the rest of the catalog (distilled from Wikipedia's Signs of AI writing plus an evolving model-era inventory), plus a judgment rubric for superficial analysis, vague attribution, promotional tone, metaphor jargon ('load-bearing', 'seam'), and mechanism-free claims. Use when: 'check for AI slop', 'de-slop this doc', 'unslop this', 'find AI tells', 'does this read AI-written', 'remove em dashes', or before publishing agent-written prose. Read-only by default; 'fix' as an explicit argument applies rewrites behind a semantic-diff guard and may be chained ('detect and rewrite'). Empty target audits the repo's tracked markdown, high-impact and high-velocity files first."
argument-hint: "[audit|fix] [target|user-scope [memory]]"
user-invocable: true
disable-model-invocation: false
allowed-tools: ["Bash(${CLAUDE_SKILL_DIR}/scripts/detect.sh:*)", "Bash(\"${CLAUDE_SKILL_DIR}/scripts/detect.sh\":*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/emit-findings.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/emit-fix-record.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/rubric-fanout.sh:*)", "Bash(\"${CLAUDE_SKILL_DIR}/scripts/rubric-fanout.sh\":*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/cross-check.sh:*)", "Bash(\"${CLAUDE_SKILL_DIR}/scripts/cross-check.sh\":*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/user-scope.sh:*)", "Bash(\"${CLAUDE_SKILL_DIR}/scripts/user-scope.sh\":*)", "Bash(sha256sum:*)", "Bash(shasum:*)", "Bash(mkdir:*)", "Bash(git:*)", "Bash(grep:*)", "Bash(head:*)", "Bash(wc:*)"]
shell: bash
metadata:
  workflow-stage: anytime
  summary: Detect and remove AI-writing tells from markdown prose
---

## Repository context. Gather first

Collect these with **individual** Bash calls, one command per call, never combined into a single
invocation:

- Current branch, `git branch --show-current`

Treat a failure (not a repository, git unavailable) as an unknown value and carry on. Keep these as
separate body Bash calls rather than pre-compute lines: the harness runs a skill's whole pre-compute
block as one shell invocation, and a worktree-isolated session refuses a compound command that
contains git.

## Pre-computed context

Effective config: !`"${CLAUDE_SKILL_DIR}/scripts/detect.sh" --show-config >/dev/null 2>&1 && { "${CLAUDE_SKILL_DIR}/scripts/detect.sh" --show-config 2>/dev/null | head -40; :; } || echo "detector unavailable"`

The bound above is generous on purpose: `--show-config` prints `disabled_rules` and every
`rule_allowed_paths` entry after the fixed lines, and those are the values that decide which
rules ran at all.

## Purpose

Detect and remove AI-writing tells in checked-in markdown prose. Two detection layers over one
rule inventory ([`reference/catalog.md`](reference/catalog.md), distilled from Wikipedia's
"Signs of AI writing", revision-pinned, plus the catalog's "Cursor unslop additions" section
and its repo-owned, evidence-graded "Model-era additions" section of current-generation model
vocabulary):

1. **Deterministic**: `${CLAUDE_SKILL_DIR}/scripts/detect.sh` runs the catalog's `v1: script`
   rules. Its findings carry argued severity tiers (the detector-findings convention's crosswalk)
   and persist as a conforming findings file, which is how a consumer sees them and how they
   reach a surface that can rewrite them. Audit step 6 names which surface, and when.
2. **Judgment rubric**: the catalog's `v1: rubric` tells, applied by reading the prose. Rubric
   findings reach the human report only, never the findings file.

Both layers sit behind the catalog's policy-level **quotation exemption**: no rule scans fenced
code or inline code spans, wording rules also skip blockquotes and double-quoted spans, and
typography rules (em dashes, emoji, paste residue, citation tokens, tracking params) still scan
blockquotes and double-quoted spans. A document that quotes a wording tell to document it, and a
changelog that backticks the phrase a fix removed, stay marker-free by construction.

## Action router

| Argument | Action |
|---|---|
| *(empty)* or `audit [target]` | Read-only audit (default). Empty target = repo-wide |
| `fix [target]` | Explicit fix pass over the target's findings (guarded; below). "Detect and rewrite" or `audit fix` chains audit then fix in one invocation |
| `audit user-scope [memory]` | Read-only audit of the user-level Claude Code markdown, per [User-scope target](#user-scope-target) |
| `fix user-scope [memory]` | Fix pass over that same file list, under the same rules |

## Audit flow

1. **Scope.** A path argument narrows to that file or directory. Empty target = the repo's
   tracked markdown minus config `excluded_paths`. Order repo-wide work by impact class first
   (instruction surfaces: `CLAUDE.md`, `AGENTS.md`, `.claude/rules/**`, `**/SKILL.md`,
   `README.md`), then by change frequency (`git log --since=90.days --name-only` counts over
   tracked `.md`). `rubric-fanout.sh plan` applies this order to the rubric batches; the
   detector keeps its own path order, and ordering never changes inclusion. Inside a
   repository, a directory target expands to its tracked markdown only, so pass untracked
   in-repo files as file paths. Check the target first: a target outside any repository
   follows [Non-repository targets](#non-repository-targets), and the literal target
   `user-scope` follows [User-scope target](#user-scope-target).
2. **Run the detector.** Build the target list once: `detect.sh --list-targets <targets>`,
   with its stdout redirected to a list file in the session scratchpad (with no targets, it
   lists the repository's tracked markdown). Each line is `<key><TAB><path>`, the key spelled as the
   detector's `file=` field, after directory expansion and `excluded_paths`. That file feeds
   the chunked runs, `detect.sh --paths-file <list> --offset N --limit M` per chunk (one
   process per chunk, no per-file shell loop; roughly 200 files per chunk keeps each call under
   a minute), and `rubric-fanout.sh plan` in step 3. A `--paths-file` with no paths scans
   nothing.
3. **Apply the rubric** to every file in scope, in the same priority order. The rubric pass is
   independent of the detector: a file with zero script findings still gets its rubric read,
   and a fix pass that only revisits detector hits has not covered the rubric. The rubric tells
   and their boundaries are the catalog entries marked `v1: rubric`; cite the entry when
   reporting. Counter-signs (the catalog's "Signs of human writing") temper a verdict, never
   generate findings. Coverage is uniform: never trade the rubric away for budget on a
   repo-wide run. Make it affordable by fanning out instead, per
   [`context/rubric-fanout.md`](context/rubric-fanout.md), with `rubric-fanout.sh`: `plan`
   orders the step 2 list, packs it into batches of about 50,000 words and writes the
   corpus cue counts every batch receives, `extract` writes
   the rubric text to one file, one fresh-context subagent per batch gets that file's path and
   its batch list and writes its result file into the rubric working directory before it
   reports (resolved before this step, with no remote fetch; see
   [`context/rubric-fanout.md`](context/rubric-fanout.md) "Rubric working directory"),
   `status` names the batches that still need a run,
   and `merge` joins the results once every batch is complete and flags cross-batch
   verdict splits. A batch whose result file is
   bound to its current list and the listed files' current contents is skipped on a re-run,
   so a rate limit or a crash costs one batch, not the pass, and a leftover result from an
   earlier scope or from before an edit is never accepted.
4. **Report.** Group findings by file in priority order: for script findings quote the rule id,
   line, and fired condition; for rubric findings quote the offending text and name the catalog
   entry. State the declined counts (marker/config/code-fence exemptions) and any disabled rules
   from the detector's `Summary` rows. State what was scanned and what the rubric did not cover.
5. **Persist the findings file** per [`context/persist-findings.md`](context/persist-findings.md)
   whenever the audit examined tracked files: fetch the producer contract first and refuse to
   write when unreachable, or when the findings home cannot be resolved without asking. The
   outcome is then report-only, and the report says `report-only` and why; it never skips
   silently. When the home resolves, the file is mandatory even with zero findings, as the
   coverage record. Script findings only. A run with any non-repository target writes no
   findings file. The rubric files never depend on this step: they live in the rubric working
   directory.
6. **Recommend**, never auto-run: the `fix` action for the findings, or `/ai-slop:setup` when the
   run tripped over deliberate house style (heavy declined counts or a flooded rule).
   `review:fanout fix` routes the whole file: it hands every row but `rule-utm-params` to this
   skill's own `fix` action, which the crosswalk declares as their remediation owner. `rule-utm-params` is the one row the relay is *capable* of applying
   meaning-preservingly. Do not promise that it will. It takes its ordinary cleanup class and
   reaches the relay's cleanup route, which prefers `/simplify`, a code-simplification skill that
   reads no findings file, and applies rows itself only when `/simplify` is absent. Neither the
   relay's own applier nor `/simplify` loads this skill's rewrite guide. Recommend the relay when
   the operator is already running a fix pass; recommend this skill's `fix` directly
   when they are not, since it is the shorter path to the same rewrites. Name the condition that
   changes the answer: the relay can only hand the rows over when `/ai-slop:audit` is available
   in that session, and surfaces them otherwise.

### User-scope target

The target `user-scope` audits the user-level markdown files Claude Code loads, listed by
`user-scope.sh`. Run `${CLAUDE_SKILL_DIR}/scripts/user-scope.sh` (add `--memory` when the argument carries
`memory`) with its stdout redirected to a list file in the session scratchpad, and feed that
list to step 2 as `detect.sh --list-targets --paths-file <list>`. Run the rest of the audit, or
the fix flow, under [Non-repository targets](#non-repository-targets).

- The surfaces are the user-level markdown files the
  [`.claude` directory docs](https://code.claude.com/docs/en/claude-directory) name:
  `CLAUDE.md`, `rules/`, skills that hold `SKILL.md` with their supporting files, `commands/`,
  `agents/`, and top-level `output-styles/*.md`; `memory` adds `projects/*/memory/` and
  `agent-memory/*/`. The root is `CLAUDE_CONFIG_DIR` when set and non-empty, else
  `~/.claude`. Verified 2026-09-25 against that page: it lists these user-level files and
  states that with `CLAUDE_CONFIG_DIR` set, every `~/.claude` path on the page lives under that
  directory instead. Recheck when the page adds, moves, or drops a user-level markdown surface,
  or changes what `CLAUDE_CONFIG_DIR` relocates.
- `@path` imports are not followed. Pass an imported file as an explicit path target.
- `memory` lists the default auto memory location only. Any settings scope can move it with
  `autoMemoryDirectory` ([memory docs](https://code.claude.com/docs/en/memory)); the script
  warns when the user `settings.json` sets it, and a relocated directory is passed as a path
  target.
- A symlink is followed only to a readable regular `.md` file inside the config root, and
  that file is listed once. A symlinked directory is not walked, and a link pointing outside
  the root is skipped; the script names each on stderr. Pass a skipped link's target as an
  explicit path.
- `plugins/` is not in scope; plugin content is audited in its own repository.
- The detector's user config layer is `$HOME/.claude/ai-slop.json` even when
  `CLAUDE_CONFIG_DIR` is set, and its project layers come from the session's project directory.
- A path target such as `~/.claude` covers every `*.md` beneath it.

### Non-repository targets

A target is outside a repository when `git -C <dir> rev-parse --is-inside-work-tree` fails or
does not print `true`, where `<dir>` is the target directory or a file target's parent. Check
the target, not the cwd. An empty target checks `CLAUDE_PROJECT_DIR`, else the cwd; when that is
not a repository, stop and ask for an explicit path instead of guessing a scope. If any target
is outside a repository, the whole run follows these rules:

- Pass targets to the detector as absolute paths, and use the `file=` path it reports as the
  path everywhere else in the run. It may be relative (to `CLAUDE_PROJECT_DIR`, else the git
  toplevel or cwd) when the target sits under that directory.
- A directory target expands to every `*.md` beneath it, untracked and vendored files included.
  The detector usually prints a "could not confirm a work tree" line on stderr; a target inside
  a `.git` directory is walked with no message. Both are expected.
- Order files by modification time, newest first, ties broken by path. Ordering never changes
  inclusion. `rubric-fanout.sh plan` picks this order for a target outside a repository; apply
  it to the report too.
- Config layers come from the session's project directory and the user-global file, not from
  the target. `--show-config` names the layers in effect. A path glob such as `excluded_paths`
  applies only when it matches the path as the detector sees it.
- No findings file: none is written, and the fix flow's closing re-emit is skipped.
- A `fix` run has no git history to undo an edit, so back up each file before editing it:
  - The backup path mirrors the file's full absolute path under the fix flow's run directory,
    with a drive letter as a directory. Never name a backup by basename alone: a target holds
    many files named `SKILL.md`.
  - If the backup path already exists, do not edit the file. Stop and report it.
  - If the backup cannot be written (the copy fails, or the mirrored path is not a valid path,
    as a `\\?\` or UNC source produces), do not edit the file. Stop and report it.
  - Next to each backup, write `<backup>.source` holding the file's absolute path.
  - Verification takes the backup as the "before" file. Before restoring any hunk, read
    `<backup>.source` and confirm it equals the file being restored; on a mismatch restore
    nothing and report it. Restore only the hunks verification flags; an edit that passes
    stays. Name the backup path in the per-file report.

  Example, run directory `R`, home directory `H` on drive `C:`:
  `H/.claude/skills/a/SKILL.md` backs up to `R/C/H/.claude/skills/a/SKILL.md`, and `H/.claude/skills/b/SKILL.md` to
  `R/C/H/.claude/skills/b/SKILL.md`, with `H` spelled out in full. The two never collide, and each `.source` file
  names its own original, so a restore meant for `a` can never write `b`'s content.
- The rubric working directory is under the session scratchpad, else the system temp
  directory, per [`context/rubric-fanout.md`](context/rubric-fanout.md) "Rubric working
  directory".
- `rule-style-shift` is not evaluable, because there is no history to compare against. Report
  it under what the rubric did not cover.

## Fix flow (explicit invocation only)

Never runs on bare invocation. Requires the user's explicit `fix` (or a chained
"detect and rewrite" request).

Every fix run first creates one new run directory, `<scratchpad>/ai-slop-fix-<TS>/` (`TS` as
in [`context/persist-findings.md`](context/persist-findings.md)). A path "mirrored" under it
is the file's full absolute path with a drive letter as a directory, the same rule the
non-repository backups use.

**Concurrent-edit guard.** The flow assumes nothing else writes the files it fixes, and checks
that before each of its own writes. When the flow first reads a file, before it composes any
rewrite, record the file's digest: create the state file's parent directory, then run
`sha256sum <absolute path>` with its stdout redirected to `<run>/digests/<mirrored
path>.sha256`. Before every write the flow makes to that file, a rewrite or a restore, run
`sha256sum -c --status <that state file>`; after each of its own writes, record the digest
again the same way. On a mismatch, write nothing more to the file, stop work on it, and report
it. If the state file cannot be written, do not edit the file; stop and report it. On macOS use
`shasum -a 256` and `shasum -a 256 -c`. A write that lands between a check and
the re-record after the flow's own write is not detected.

Per file, worst-first:

1. **Apply** the file's findings per [`reference/rewrite-guide.md`](reference/rewrite-guide.md)
   (read it first; it owns the replacement forms, the plain-speech target, the legitimate-hit
   taxonomy, the risky-class disambiguation rules, and the voice guidance): rewrite each
   flagged line (em dashes to commas, periods, or restructured sentences, never parentheses
   or en dashes, which swap one tell for another; deflate stock phrases; collapse
   parallelisms; delete filler and chat residue; strip `utm_*` params; delete or source
   residue artifacts) and the rubric rewrites for tells the audit reported. Preserve meaning
   over style: when a rewrite would change what a sentence asserts, skip it and record why.
   **Triads collapse toward one**: for a rule-of-three rubric finding, prefer the single
   strongest item and cut the rest. Keep all three only when each item is needed (a complete
   set the reader needs, not rhetorical rhythm; enumerating three actual things is not a
   tell), and never collapse when the survivors would not entail the deleted items. Fewer
   parallel items is also less to maintain. Then run the guide's **voice pass** (its "Adding
   voice" section) on the file's authored-register prose: README narrative, changelog
   rationale, design tradeoffs; never operative instructions or reference tables. Close each
   file with the guide's self-audit pass ("what still makes this read machine-written?")
   before handing it to verification.
2. **Verify** with a fresh-context semantic-diff subagent: hand it the before/after pair,
   blind to the rewrite rationale; it flags SEMANTIC LOSS (a qualifier, threshold, or claim
   dropped), AMBIGUITY (a reading the original excluded), and QUOTE CORRUPTION (any changed
   byte inside quoted text), with the guide's risky classes called out for adversarial
   attention: a negative-parallelism restatement must preserve which reading the original
   meant, and a collapsed triad must still entail its deleted items. Revert every flagged
   hunk before moving on.
3. **Close** the file: findings fixed, explicitly suppressed (in-file marker with a reason), or
   reverted-with-reason. Report per file as you go on long runs.

After the last file: build the fixed set's list with `detect.sh --list-targets`, run the
detector over it with `--paths-file`, redirecting both outputs to files in the run directory.
Then, per [`context/persist-findings.md`](context/persist-findings.md) "Re-running", retire the
findings file this run consumed with a `fix-pass-record` (`scripts/emit-fix-record.sh`), and
re-emit a fresh findings file for what remains, in that order: `review:fanout fix` skips a
file only when a record names it by name and digest, so the fresh file is then the only
candidate left. Skip both for a non-repository target, which never wrote a file. Then run `cross-check.sh --targets <list>
--detector <detector output>`: it finds em-dash lines with its own parse, so an em dash the
detector's parse missed still shows up. A `Disagree:` row names the lines only one side
counted (`detector_only=`, `cross_check_only=`), which is where the two parses differ; a row
with both lists `-` differs by count alone, as when the detector reports one line twice. It
follows the detector's fence rules but not its full parse, so a row is not a proven miss:
reread those lines. Report every `Disagree:` row, then totals:
fixed, suppressed, reverted, remaining.

## Configuration

`.claude/ai-slop.json` per the config-cascade convention; keys, layers, and the in-file marker
forms are documented in the plugin README and managed by `/ai-slop:setup`. The detector's
`--show-config` names the layer supplying each effective value. When a whole document
legitimately needs em dashes, the remedy is `em_dash_allowed_paths` or the file marker, never a
threshold. The em-dash rule is zero-tolerance by design; the catalog's `rule-em-dash` entry
carries the reason.

## What this skill does NOT do

- **Does not fix on bare invocation.** `audit` and `scan` verbs are read-only in this
  marketplace; mutation rides only the explicit `fix` argument.
- **Does not put rubric findings in the findings file.** No crosswalk row, no relay: judgment
  verdicts reach the human report only (V1 boundary, revisit with field history).
- **Does not scan code comments** (`code-tidying:audit-comment-residue` owns them), commit
  messages, PR bodies, or text outside markdown files; structural markdown (heading
  hierarchy, multiple H1, title case) belongs to the markdown linter lane. Reshaping those
  commit messages, PR bodies and other text so they lead with the point and carry fewer words
  is `/writing:be-concise`,
  which owns that doctrine when the `writing` plugin is installed; without it, say the text
  sits outside this skill's regime rather than auditing it anyway.
- **Does not weaken rules to pass its own corpus**: a deliberate house style is config in the
  consuming repo, never a shipped-default change.

Files in this skill

  • SKILL.md10.5 KB
  • context/persist-findings.md4.8 KB
  • evals/evals.json11 KB
  • evals/fixtures/em-dash-substitution.md245 B
  • evals/fixtures/fix-guarded-rewrite.md327 B
  • evals/fixtures/knowledge-cutoff-prose.md433 B
  • evals/fixtures/report-only.md294 B
  • evals/fixtures/rubric-boundary.md346 B
  • evals/fixtures/triads.md395 B
  • reference/catalog.md54.9 KB
  • reference/rewrite-guide.md11 KB
  • scripts/detect.sh32.5 KB
  • scripts/detect.test.sh55.8 KB
  • scripts/emit-findings.sh12.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.