Skill: athola/nm-scribe-slop-detector (version 1.0.2)

--- name: slop-detector description: Detect and flag AI-generated content markers in documentation and prose version: 1.9.5 triggers: - ai-detection - slop - writing - cleanup - documentation - quality metadata: {"openclaw": {"homepage": "https://github.com/athola/claude-night-market/tree/master/plugins/scribe", "emoji": "\u270d\ufe0f", "requires": {"config": ["night-market.scribe:shared"]}}} source: claude-night-market source_plugin: scribe --- > **Night Market Skill** — ported from [claude-night-market/scribe](https://github.com/athola/claude-night-market/tree/master/plugins/scribe). For the full experience with agents, hooks, and commands, install the Claude Code plugin. # AI Slop Detection AI slop is identified by patterns of usage rather than individual words. While a single "delve" might be acceptable, its proximity to markers like "tapestry" or "embark" signals generated text. We analyze the density of these markers per 100 words, their clustering, and whether the overall tone fits the document type. ## Execution Workflow Start by identifying target files and classifying them as technical docs, narrative prose, or code comments. This allows for context-aware scoring during analysis. ### Language Detection - Auto-detect language from text content using function word frequency - Override with explicit `--lang` parameter (en, de, fr, es) - Load language-specific patterns from `data/languages/{lang}.yaml` - Fall back to English if detection confidence is low - See `modules/language-support.md` for details on cultural calibration ### Vocabulary and Phrase Detection Load: `@modules/vocabulary-patterns.md` We categorize markers into three tiers based on confidence. Tier 1 words appear dramatically more often in AI text and include "delve," "multifaceted," and "leverage." Tier 2 covers context-dependent transitions like "moreover" or "subsequently," while Tier 3 identifies vapid phrases such as "In today's fast-paced world" or "cannot be overstated." | Word | Context | Human Alternative | |------|---------|-------------------| | delve | "delve into" | explore, examine, look at | | tapestry | "rich tapestry" | mix, combination, variety | | realm | "in the realm of" | in, within, regarding | | embark | "embark on a journey" | start, begin | | beacon | "a beacon of" | example, model | | spearheaded | formal attribution | led, started | | multifaceted | describing complexity | complex, varied | | comprehensive | describing scope | thorough, complete | | pivotal | importance marker | key, important | | nuanced | sophistication signal | subtle, detailed | | meticulous/meticulously | care marker | careful, detailed | | intricate | complexity marker | detailed, complex | | showcasing | display verb | showing, displaying | | leveraging | business jargon | using | | streamline | optimization verb | simplify, improve | ### Tier 2: Medium-Confidence Markers (Score: 2 each) Common but context-dependent: | Category | Words | |----------|-------| | Transition overuse | moreover, furthermore, indeed, notably, subsequently | | Intensity clustering | significantly, substantially, fundamentally, profoundly | | Hedging stacks | potentially, typically, often, might, perhaps | | Action inflation | revolutionize, transform, unlock, unleash, elevate | | Empty emphasis | crucial, vital, essential, paramount | ### Tier 3: Phrase Patterns (Score: 2-4 each) | Phrase | Score | Issue | |--------|-------|-------| | "In today's fast-paced world" | 4 | Vapid opener | | "It's worth noting that" | 3 | Filler | | "At its core" | 2 | Positional crutch | | "Cannot be overstated" | 3 | Empty emphasis | | "A testament to" | 3 | Attribution cliche | | "Navigate the complexities" | 4 | Business speak | | "Unlock the potential" | 4 | Marketing speak | | "Treasure trove of" | 3 | Overused metaphor | | "Game changer" | 3 | Buzzword | | "Look no further" | 4 | Sales pitch | | "Nestled in the heart of" | 4 | Travel writing cliche | | "Embark on a journey" | 4 | Melodrama | | "Ever-evolving landscape" | 4 | Tech cliche | | "Hustle and bustle" | 3 | Filler | ## Step 3: Structural Pattern Detection Load: `@modules/structural-patterns.md` ### Em Dash Overuse Count em dashes (—) per 1000 words: - **0-2**: Normal human range - **3-5**: Elevated, review usage - **6+**: Strong AI signal ```bash # Count em dashes in file grep -o '—' file.md | wc -l ``` ### Tricolon Detection AI loves groups of three with alliteration: - "fast, efficient, and reliable" - "clear, concise, and compelling" - "robust, reliable, and resilient" Pattern: `adjective, adjective, and adjective` with similar sounds. ### List-to-Prose Ratio Count bullet points vs paragraph sentences: - **>60% bullets**: AI tendency - **Emoji-led bullets**: Strong AI signal in technical docs ### Sentence Length Uniformity Measure standard deviation of sentence lengths: - **Low variance** (SD < 5 words): AI monotony - **High variance** (SD > 10 words): Human variation ### Paragraph Symmetry AI produces "blocky" text with uniform paragraph lengths. Check if paragraphs cluster around the same word count. ## Step 4: Sycophantic Pattern Detection Especially relevant for conversational or instructional content: | Phrase | Issue | |--------|-------| | "I'd be happy to" | Servile opener | | "Great question!" | Empty validation | | "Absolutely!" | Over-agreement | | "That's a wonderful point" | Flattery | | "I'm glad you asked" | Filler | | "You're absolutely right" | Sycophancy | These phrases add no information and signal generated content. ## Step 5: Calculate Slop Density Score ``` slop_score = (tier1_count * 3 + tier2_count * 2 + phrase_count * avg_phrase_score) / word_count * 100 ``` | Score | Rating | Action | |-------|--------|--------| | 0-1.0 | Clean | No action needed | | 1.0-2.5 | Light | Spot remediation | | 2.5-5.0 | Moderate | Section rewrite recommended | | 5.0+ | Heavy | Full document review | ## Step 6: Generate Report Output format: ```markdown ## Slop Detection Report: [filename] **Overall Score**: X.X / 10 (Rating) **Word Count**: N words **Markers Found**: N total ### High-Confidence Markers - Line 23: "delve into" -> consider: "explore" - Line 45: "rich tapestry" -> consider: "variety" ### Structural Issues - Em dash density: 8/1000 words (HIGH) - Bullet ratio: 72% (ELEVATED) - Sentence length SD: 3.2 words (LOW VARIANCE) ### Phrase Patterns - Line 12: "In today's fast-paced world" (vapid opener) - Line 89: "cannot be overstated" (empty emphasis) ### Recommendations 1. Replace [specific word] with [alternative] 2. Convert bullet list at line 34-56 to prose 3. Vary sentence structure in paragraphs 3-5 ``` ## Module Reference - See `modules/fiction-patterns.md` for narrative-specific slop markers - See `modules/remediation-strategies.md` for fix recommendations ## Integration with Remediation After detection, invoke `Skill(scribe:doc-generator)` with `--remediate` flag to apply fixes, or manually edit using the report as a guide. ## Exit Criteria - All target files scanned - Density scores calculated - Report generated with actionable recommendations - High-severity items flagged for immediate attention

path: modules/ci-integration.md
content: ---
module: ci-integration
category: automation
dependencies: [Bash]
estimated_tokens: 500
---

# CI Integration

Use the `--ci` flag to produce machine-readable output and exit with a non-zero code when
slop density exceeds a threshold. Intended for use in GitHub Actions and pre-commit hooks.

## Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--ci` | off | Emit JSON output instead of the markdown report |
| `--threshold <float>` | `3.0` | Score above which the run fails (exit code 1) |

## JSON Output Schema

When `--ci` is set, write a single JSON object to stdout:

```json
{
  "files": [
    {
      "path": "docs/guide.md",
      "score": 2.4,
      "rating": "Light",
      "markers": 7
    }
  ],
  "summary": {
    "total_files": 1,
    "avg_score": 2.4,
    "max_score": 2.4,
    "pass": true
  }
}
```

### Field Definitions

| Field | Type | Description |
|-------|------|-------------|
| `files[].path` | str | Path to the scanned file (relative to repo root) |
| `files[].score` | float | Slop density score (0–10+) |
| `files[].rating` | str | One of: Clean, Light, Moderate, Heavy |
| `files[].markers` | int | Total marker count in the file |
| `summary.total_files` | int | Number of files scanned |
| `summary.avg_score` | float | Mean score across all files |
| `summary.max_score` | float | Highest score across all files |
| `summary.pass` | bool | True when max_score <= threshold |

## Exit Codes

| Code | Meaning |
|------|---------|
| 0 | All files pass (max_score <= threshold) |
| 1 | One or more files exceed the threshold |
| 2 | Execution error (file not found, parse failure, etc.) |

## Instructions for Claude

When `--ci` appears in the invocation:

1. Run the full detection workflow as normal.
2. Collect per-file results: path, score, rating, marker count.
3. Compute summary fields: total_files, avg_score (round to 2 decimal places), max_score.
4. Set `pass` to `true` when `max_score <= threshold`, `false` otherwise.
5. Write the JSON object to stdout. Do not write the markdown report.
6. Report exit code 1 if `pass` is false, 0 if true, 2 on any error.

Do not mix prose with the JSON output. The JSON must be the only content on stdout so
it can be parsed by downstream tools.

## GitHub Actions Example

```yaml
- name: Slop check
  run: |
    result=$(claude -p "Skill(scribe:slop-detector) --ci --threshold 3.0 docs/")
    echo "$result" | jq .
    pass=$(echo "$result" | jq -r '.summary.pass')
    if [ "$pass" != "true" ]; then
      echo "Slop threshold exceeded" >&2
      exit 1
    fi
```

## Pre-commit Hook Example

```yaml
# .pre-commit-config.yaml
- repo: local
  hooks:
    - id: slop-check
      name: Slop density check
      language: system
      entry: bash -c 'claude -p "Skill(scribe:slop-detector) --ci --threshold 3.0" "$@"'
      types: [markdown]
      pass_filenames: true
```


path: modules/config-file.md
content: ---
module: config-file
category: configuration
dependencies: [Read]
estimated_tokens: 600
---

# Config File Support

Load a `.slop-config.yaml` file to adjust detection behavior for the current project.

## Discovery

Walk up the directory tree from the target file toward the repo root. Stop at the first `.slop-config.yaml` found. If none exists, use built-in defaults.

```
target file: /project/docs/guide.md
check:       /project/docs/.slop-config.yaml
check:       /project/.slop-config.yaml      <- found, use this
check:       /.slop-config.yaml              (would stop here at repo root)
```

To find the repo root, check for a `.git` directory while walking up.

## YAML Schema

```yaml
# .slop-config.yaml

# Extra words treated as tier-1 markers (score: 3 each)
custom_words:
  tier1:
    - synergize
    - ideate
  tier2:
    - impactful
    - learnings

# Words to skip during detection (exact match, case-insensitive)
allowlist:
  - robust      # used correctly in our engineering specs
  - leverage    # used correctly in our physics docs

# Score thresholds (warn < error required)
thresholds:
  warn: 2.0     # flag for review
  error: 5.0    # fail CI check

# Glob patterns for files to skip entirely
exclude_patterns:
  - "vendor/**"
  - "**/*.generated.md"
  - "CHANGELOG.md"

# Inherit from a base config, then apply overrides above
extends: "../../.slop-config.yaml"
```

### Field Reference

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `custom_words.tier1` | list[str] | `[]` | Additional tier-1 words (score 3 each) |
| `custom_words.tier2` | list[str] | `[]` | Additional tier-2 words (score 2 each) |
| `allowlist` | list[str] | `[]` | Words to ignore during detection |
| `thresholds.warn` | float | `2.0` | Score at which to warn |
| `thresholds.error` | float | `5.0` | Score at which to fail |
| `exclude_patterns` | list[str] | `[]` | Glob patterns for files to skip |
| `extends` | str | none | Path to a base config to inherit from |

## Loading Procedure

1. Walk directories from target file up to repo root, collecting any `.slop-config.yaml` files found.
2. If `extends` is set in a config, load that base config first.
3. Merge: base config values are the defaults; the child config overrides them.
4. For list fields (`custom_words.tier1`, `allowlist`, etc.), merge lists rather than replace.
5. Validate that `thresholds.warn < thresholds.error`. If not, warn and use built-in defaults.

## Merging Custom Words with Built-in Patterns

After loading the config:

- Append `custom_words.tier1` to the built-in TIER1 word list before scanning.
- Append `custom_words.tier2` to the built-in TIER2 word list before scanning.
- After each match, check if the matched word appears in the `allowlist`. If so, discard the match.

The allowlist check is case-insensitive and applied per-match, not per-word-list.

## Exclude Pattern Matching

Before scanning a file, check its path against each pattern in `exclude_patterns` using `fnmatch`. If any pattern matches, skip the file and report it as excluded.

```python
import fnmatch

def is_excluded(file_path: str, patterns: list) -> bool:
    for pattern in patterns:
        if fnmatch.fnmatch(file_path, pattern):
            return True
    return False
```

## Reporting

When a config file is active, include it in the report header:

```
Config: /project/.slop-config.yaml
Allowlist: robust, leverage (2 words)
Custom tier-1: synergize, ideate (2 words)
Thresholds: warn=2.0, error=5.0
```


path: modules/fiction-patterns.md
content: ---
module: fiction-patterns
category: detection
dependencies: [Grep, Read]
estimated_tokens: 700
---

# Fiction-Specific AI Pattern Detection

Creative writing has distinct AI tells beyond technical documentation markers.

## Physical/Emotional Cliche Beats

AI defaults to formulaic body language and emotional descriptions.

### Breath Cliches (Score: 3 each)
```
"breath he didn't know he was holding"
"let out a breath"
"released a breath"
"exhaled a breath he'd been holding"
"breath caught in [his/her] throat"
```

### Body Protest Metaphors (Score: 2 each)
```
"[body part] protested"
"his shoulder protests"
"muscles screamed in protest"
"[body part] screamed"
```

### Emotion Washing (Score: 3 each)
```
"relief washed over"
"[emotion] washed over"
"a sense of [emotion] washed"
"dread pooled in [his/her] stomach"
"heart clenched"
```

### Vague Depth Markers (Score: 3 each)
```
"something in [his/her] expression"
"something in [his/her] eyes"
"something shifted"
"something precious"
"something soft in"
"something [adjective] in [his/her] voice"
```

### Decision/Reaction Avoidance (Score: 2 each)
```
"doesn't know what to do with that"
"didn't know what to do with"
"couldn't process"
"brain short-circuited"
```

## Narrative Structure Cliches

### Simile Abuse (Score: 2-4 each)
```
"like it's the most natural thing in the world" (4)
"like a vow" (3)
"like a promise" (3)
"like coming home" (3)
"like a blade wrapped in silk" (4)
"stone in still water" (4)
```

### Rhetorical Emphasis (Score: 3 each)
```
"He x—really x—" pattern
"He looked at her—really looked"
"He listened—really listened"
"Not x but y" / "Not x, just y"
"didn't [verb] but [verb]"
```

### Sentence Fragment Overuse

AI uses stylistic fragments excessively for "punch":
```
"Tosses it somewhere behind him."
"Gone."
"Just like that."
"Nothing more."
```

One or two per scene is stylistic; five or more signals generation.

## Word-Level Fiction Tells

### Overused Descriptors
```
cataloguing, measured, clocked, flickering,
perhaps, maybe, just, that, something,
kind of, sort of
```

### Action Inflation
```
screaming (metaphorical: "hip screaming")
protesting (body parts)
dancing (non-dance contexts: "fingers dancing")
singing (objects: "the blade sang")
```

### Adverb Clustering

AI repeats the same adverbs within short spans:
- "softly" appearing 3+ times in a scene
- "quietly" used with multiple actions
- "slowly" describing everything

Check adverb variety: count unique adverbs vs total adverb uses.

## Dialogue Patterns

### Sycophantic Character Speech
```
"That's a great idea"
"You're absolutely right"
"I never thought of it that way"
```

### Overly Clean Attribution
AI avoids "said" excessively:
```
he murmured, she whispered, he breathed,
she exhaled, he muttered, she remarked
```

Natural dialogue uses "said" frequently without variation.

## Chapter/Scene Structure

### Upbeat Endings

AI ends scenes with false resolution:
- Characters reaching understanding
- Hopeful forward-looking statements
- Emotional catharsis without buildup

### Character Name Patterns

AI defaults to certain names with suspicious frequency:
- Sarah Chen (extremely common in AI fiction)
- Emma, Liam, Maya, Marcus
- Asian names with Western first names

## Detection Regex

```python
FICTION_PATTERNS = [
    r"breath \w+ didn't know",
    r"let out a breath",
    r"\w+ protested?(?:\s|$)",
    r"(?:relief|fear|dread|panic) washed over",
    r"something (?:in|about) (?:his|her)",
    r"like (?:it's|it was) the most natural",
    r"—really \w+—",
    r"(?:didn't|doesn't) know what to do with",
]
```

## Scoring for Fiction

```python
def fiction_slop_score(text):
    patterns_found = []
    for pattern, score in FICTION_PATTERNS:
        matches = re.findall(pattern, text, re.IGNORECASE)
        patterns_found.extend([(m, score) for m in matches])

    # Weight by scene length (per 500 words)
    word_count = len(text.split())
    raw_score = sum(score for _, score in patterns_found)
    normalized = (raw_score / word_count) * 500

    return {
        'score': min(10, normalized),
        'patterns': patterns_found,
        'density': len(patterns_found) / (word_count / 500)
    }
```

## Remediation Notes

Fiction slop requires rewriting, not just word replacement. The underlying issue is reliance on familiar emotional beats rather than character-specific reactions.

Recommend:
1. What would THIS character actually do/feel?
2. What sensory details are specific to this scene?
3. What's the subtext beneath the surface emotion?


path: modules/i18n-patterns.md
content: ---
module: i18n-patterns
category: detection
dependencies: [Read, Grep]
estimated_tokens: 700
---

# Internationalisation Pattern Detection

Slop markers exist in every language. This module extends detection to German, French,
and Spanish. The same density scoring applies: tier-1 words score 3 each, tier-3
phrases score 4 each, normalised per 100 words.

## Language Selection

### Step 1 - Check config

Look for a `languages` key in `.slop-config.yaml`:

```yaml
languages:
  - en
  - de
```

If `languages` is set, scan only those pattern sets. If absent, fall back to
heuristic detection.

### Step 2 - Heuristic (no config)

Sample the first 200 words. Count function-word hits:

| Language | Function words |
|----------|----------------|
| German   | der, die, das, und, ist, nicht, mit, von |
| French   | le, la, les, et, est, pas, avec, une, dans |
| Spanish  | el, la, los, las, es, con, una, por, que |

Use the highest-scoring language. If the top score is below 5, treat the document
as English only and skip this module.

---

## German (de)

```python
DE_TIER1_PATTERNS = [
    r'\bumfassend\w*\b',    # umfassend, umfassende, umfassender ...
    r'\bnutzen\b',
    r'\bvielf[äa]ltig\w*\b',
    r'\btiefgreifend\w*\b',
    r'\bbahnbrechend\w*\b',
    r'\bganzheitlich\w*\b',
    r'\bmaßgeblich\w*\b',
    r'\bwegweisend\w*\b',
]

DE_PHRASE_PATTERNS = [
    r'in der heutigen schnelllebigen welt',  # vapid opener
    r'es sei darauf hingewiesen',            # filler
]
```

---

## French (fr)

`tirer parti de` is a multi-word phrase treated as a single tier-1 unit.

```python
FR_TIER1_PATTERNS = [
    r'\btirer parti de\b',
    r'\bexhaustif(?:ve)?\b|\bexhaustive\b',  # exhaustif / exhaustive
    r'\bpolyvalent\w*\b',
    r'\bincontournable[s]?\b',
    r'\bnovateur\b|\bnovatrice\b',
    r'\bprimordial\w*\b',
]

FR_PHRASE_PATTERNS = [
    r"dans le monde d'aujourd'hui",  # vapid opener
    r'il convient de noter que',     # filler
]
```

---

## Spanish (es)

```python
ES_TIER1_PATTERNS = [
    r'\baprovechar?\b',          # aprovechar
    r'\bintegral[e-z]?\b',       # integral
    r'\bpolifac[eé]tico[s]?\b',  # polifacético
    r'\binnovador[a-z]?\b',      # innovador
    r'\bfundamental[e-z]?\b',    # fundamental
    r'\bimprescindible[s]?\b',   # imprescindible
]

ES_PHRASE_PATTERNS = [
    r'en el mundo acelerado de hoy',  # vapid opener
    r'cabe destacar que',             # filler
]
```

---

## Scoring and reporting

Add i18n matches to the overall document score:

```
slop_score += (tier1_count * 3 + phrase_count * 4) / word_count * 100
```

Report language-specific hits in a subsection:

```
### Non-English Markers (de)
- Line 4: "bahnbrechend" -> consider: "neu" or be specific
- Line 12: "In der heutigen schnelllebigen Welt" (vapid opener)
```

If two or more languages score above 5 in the heuristic, apply all matching
pattern sets and label each match with its language code.


path: modules/language-support.md
content: ---
module: language-support
category: detection
dependencies: [Read]
estimated_tokens: 400
---

# Multi-Language Slop Detection

## Supported Languages

| Code | Language | Tier Coverage | Calibration |
|------|----------|---------------|-------------|
| en | English | Full (Tier 1-4, phrases, fiction, sycophantic) | 1.0 (baseline) |
| de | German | Core (Tier 1-2, key phrases) | 0.85 |
| fr | French | Core (Tier 1-2, key phrases) | 0.80 |
| es | Spanish | Core (Tier 1-2, key phrases) | 0.85 |
| pt | Portuguese | Core (Tier 1-2, key phrases) | 0.85 |
| it | Italian | Core (Tier 1-2, key phrases) | 0.80 |

## Language Detection

Auto-detection uses function word frequency analysis. Common words like articles,
prepositions, and auxiliary verbs serve as language markers.

Detection is conservative: defaults to English unless another language has
significantly more markers in the text.

### Override

Specify language explicitly when auto-detection is unreliable:
- Mixed-language documents
- Short texts (< 100 words)
- Code-heavy documents

## Pattern Loading

Patterns are stored in `data/languages/{code}.yaml` with consistent structure:

```yaml
language: xx
name: Language Name
tier1:
  power_words: [...]
  sophistication_signals: [...]
  metaphor_abuse: [...]
tier2:
  transition_overuse: [...]
  hedging: [...]
  business_jargon: [...]
phrases:
  vapid_openers:
    score: 4
    patterns: [...]
  filler:
    score: 2
    patterns: [...]
```

## Cultural Considerations

Slop perception varies by culture:
- **German**: Formal register is more accepted; fewer words flagged as pretentious
- **French**: Literary flourishes are culturally valued; calibrate sensitivity
- **Spanish**: Formal transitions are standard in academic writing
- **Portuguese**: Academic norms closely follow Spanish; formal phrasing less penalised
- **Italian**: Literary style is culturally valued, similar to French calibration

Structural metrics (em dashes, bullet ratios) are language-agnostic.

## Scoring Calibration

Each language has a calibration factor (see `LANGUAGE_CALIBRATION` in `pattern_loader.py`).
Multiply raw slop scores by this factor before reporting. English is the baseline (1.0).
Languages with a factor below 1.0 are less penalised for formal or literary register.


path: modules/metrics.md
content: ---
module: metrics
category: tracking
dependencies: [Read, Bash]
estimated_tokens: 500
---

# Historical Metrics Tracking

Store scan results over time to detect regressions and measure cleanup progress.

## Storage Location

Write scan records to `.slop-history/` at the repo root. Add this directory to `.gitignore`:

```
.slop-history/
```

## File Naming

One JSON file per scan:

```
.slop-history/scan-YYYY-MM-DD-HHMMSS.json
```

Example: `.slop-history/scan-2026-03-01-143022.json`

Generate the timestamp with:

```python
from datetime import datetime
datetime.now().strftime("%Y-%m-%d-%H%M%S")
```

## JSON Schema

```json
{
  "timestamp": "2026-03-01T14:30:22",
  "files_scanned": 12,
  "scores": [
    {"path": "docs/guide.md", "score": 1.4, "word_count": 320}
  ],
  "summary": {
    "avg_score": 1.4,
    "max_score": 3.1,
    "total_markers": 18
  }
}
```

All fields are required. Use `datetime.isoformat()` for the timestamp string.

## Saving Results (`--track`)

When the `--track` flag is present, write a record after every scan:

1. Build the `scores` list from the per-file results.
2. Compute `summary.avg_score` as the mean of all scores (0.0 if no files).
3. Compute `summary.max_score` as the maximum score (0.0 if no files).
4. Count `summary.total_markers` as the sum of all raw marker hits across files.
5. Write the JSON file to `.slop-history/` using the timestamp name.
6. Report the path written: `Saved: .slop-history/scan-YYYY-MM-DD-HHMMSS.json`

## Loading History (`--history`)

When the `--history` flag is present, read all files in `.slop-history/` sorted by
filename (chronological order) and print a trend table:

```
Date                 Files  Avg Score  Delta
2026-02-15-090000       10       1.20      —
2026-02-22-143000       12       1.45  +0.25
2026-03-01-143022       12       1.90  +0.45
```

Column widths: date 20, files 6, avg score 10, delta 8.
Use `—` for the delta on the first row. Prefix positive deltas with `+`.

## Regression Warning

After printing the trend table, check the two most recent records. If
`avg_score` increased by more than 0.5:

```
WARNING: avg score increased by 0.62 since last scan (1.28 -> 1.90)
```

This warning also fires when `--track` saves a new result and the previous
record exists. Compare the new avg against the most recent existing record
before writing.


path: modules/progress-indicators.md
content: ---
module: progress-indicators
category: ux
dependencies: [Bash]
estimated_tokens: 500
---

# Progress Indicators

Instructions for displaying progress when scanning multiple files.

## When to Show Progress

Skip progress output entirely for single files or pairs of files. Show progress
when scanning 3 or more files. This avoids noise for the common single-file case.

| File count | Default behavior |
|------------|-----------------|
| 1-2 | No progress output |
| 3+ | Show file count progress |

## Output Modes

Three modes control what gets printed during a scan.

### Default Mode

Show a counter line for each file as it is processed:

```
[1/49] Scanning plugins/scribe/README.md...
[2/49] Scanning plugins/scribe/SKILL.md...
...
[49/49] Scanning plugins/abstract/README.md...

Scanned 49 files in 3.2s
```

Format for each progress line:

```
[{current}/{total}] Scanning {filepath}...
```

Format for the completion summary:

```
Scanned {total} files in {elapsed:.1f}s
```

### Quiet Mode (`--quiet`)

Suppress all progress output. Print only the final report. Use this in CI
pipelines and scripts where progress lines would pollute logs.

No per-file lines. No summary line. Only the report.

### Verbose Mode (`--verbose`)

Print the per-file slop score immediately after each file is processed:

```
[1/49] Scanning plugins/scribe/README.md... score=2.1 (Light)
[2/49] Scanning plugins/scribe/SKILL.md... score=0.4 (Clean)
...

Scanned 49 files in 3.2s
```

Score label mapping:

| Score range | Label |
|-------------|-------|
| 0 - 1.0 | Clean |
| 1.0 - 2.5 | Light |
| 2.5 - 5.0 | Moderate |
| 5.0+ | Heavy |

## Implementation Notes

- Write progress lines to stdout so they can be redirected or captured.
- Compute elapsed time from scan start to the final file completion.
- Round elapsed time to one decimal place.
- Use 1-based indexing in the counter (`[1/49]`, not `[0/49]`).
- If a file cannot be read, print a warning on that line and continue:

```
[7/49] Scanning path/to/file.md... WARNING: could not read file
```

Do not abort the scan on a single unreadable file.


path: modules/remediation-strategies.md
content: ---
module: remediation-strategies
category: writing-quality
dependencies: [Edit, Write]
estimated_tokens: 600
---

# AI Slop Remediation Strategies

Guidance for fixing detected slop patterns while preserving meaning.

## Core Principles

1. **Preserve meaning**: Never change what's said, only how it's said
2. **Match context**: Technical docs need different fixes than narratives
3. **Maintain voice**: If the document has established tone, preserve it
4. **Incremental changes**: Edit section by section, not wholesale rewrites
5. **Ask before gutting**: Major restructuring requires user approval

## Vocabulary Remediation

### Direct Substitutions

| AI Word | Context | Replacements |
|---------|---------|-------------|
| delve | exploration | explore, examine, look at, dig into |
| leverage | usage | use, apply, employ |
| utilize | usage | use |
| embark | starting | begin, start, launch |
| comprehensive | scope | thorough, complete, full |
| robust | quality | solid, strong, reliable |
| seamless | integration | smooth, easy, simple |
| pivotal | importance | key, important, critical |
| multifaceted | complexity | complex, varied, diverse |
| nuanced | detail | subtle, detailed, fine-grained |
| streamline | improvement | simplify, improve, speed up |
| optimize | improvement | improve, tune, adjust |
| facilitate | enablement | enable, help, allow |
| utilize | use | use |

### Phrase Substitutions

| AI Phrase | Replacement Options |
|-----------|---------------------|
| "In today's fast-paced world" | [Delete entirely] or start with the actual point |
| "It's worth noting that" | [Delete] - just state the thing |
| "At its core" | "Fundamentally" or [delete] |
| "Cannot be overstated" | "is important" or "matters because [reason]" |
| "Navigate the complexities" | "handle", "work through", "deal with" |
| "Unlock the potential" | "enable", "allow", "make possible" |
| "A testament to" | "shows", "demonstrates", "proves" |
| "Treasure trove" | "collection", "set", "many" |

## Structural Remediation

### Reducing Em Dashes

Replace with:
- Commas for brief asides
- Parentheses for tangential info
- Periods for complete thoughts
- Colons for introductions

Before: "The system—which was designed for scale—handles millions of requests."
After: "The system handles millions of requests. It was designed for scale."

### Converting Bullets to Prose

Before:
```
Key benefits:
- Fast processing
- Easy integration
- Low maintenance
```

After:
"The system processes quickly, integrates without friction, and requires little upkeep."

### Varying Sentence Length

If all sentences are 15-20 words, intersperse:
- Short punchy statements (5-8 words)
- Longer explanatory sentences (25-30 words)
- Questions or fragments for emphasis

### Adding Contractions

Replace formal constructions:
- "do not" -> "don't"
- "cannot" -> "can't"
- "it is" -> "it's"
- "we will" -> "we'll"

Exception: Legal, academic, or formal documents may require formality.

## Tone Remediation

### Removing Sycophancy

| Remove | Replace With |
|--------|--------------|
| "Great question!" | [Delete, just answer] |
| "I'd be happy to" | [Delete, just do it] |
| "Absolutely!" | [Delete or use sparingly] |
| "That's a wonderful point" | [Delete] |

### Adding Authorial Voice

Insert:
- First-person perspective where appropriate
- Specific examples from real experience
- Acknowledgment of limitations or unknowns
- Trade-off discussions with reasoning

Before: "This approach optimizes performance."
After: "We chose this approach because it cut latency by 40% in our tests, though it uses more memory."

### Grounding Abstract Claims

Before: "This provides comprehensive coverage."
After: "This covers all 47 API endpoints documented in v2.3."

## Section-by-Section Workflow

For documents over 200 lines:

1. **Scan entire document** for slop density
2. **Prioritize sections** by severity
3. **Present each section** to user with proposed changes
4. **Wait for approval** before proceeding
5. **Track changes** in a summary

```markdown
## Section 3: API Overview (Lines 45-89)

**Slop Score**: 4.2 (Moderate)

**Proposed Changes**:
1. Line 47: "delve into" -> "examine"
2. Line 52: Remove "In today's fast-paced world"
3. Lines 60-75: Convert bullet list to two paragraphs

Proceed with these changes? [Y/n/edit]
```

## Docstring Remediation

Special rules for code comments:

1. **Imperative mood**: "Validate" not "Validates"
2. **No surrounding code changes**: Only modify the comment text
3. **Preserve parameter documentation**: Keep Args/Returns format
4. **Brief is better**: Remove filler, keep essential info

Before:
```python
def process(data):
    """
    This function processes the data in a comprehensive manner,
    leveraging advanced algorithms to optimize the output.
    """
```

After:
```python
def process(data):
    """Process input data and return optimized result."""
```

## When NOT to Remediate

- **Quoted material**: Don't change quotes from other sources
- **Historical documents**: Preserve original language
- **Intentional style**: Some "AI-like" formality may be intentional
- **User preference**: If user wants formal tone, respect it


path: modules/structural-patterns.md
content: ---
module: structural-patterns
category: detection
dependencies: [Bash, Grep]
estimated_tokens: 600
---

# Structural Pattern Detection

AI-generated text exhibits distinctive structural patterns beyond vocabulary.

## Em Dash Analysis

AI uses em dashes (—) excessively as a rhetorical device.

```bash
# Count em dashes per file
em_count=$(grep -o '—' "$file" | wc -l)
word_count=$(wc -w < "$file")
density=$((em_count * 1000 / word_count))
```

| Density (per 1000 words) | Signal |
|--------------------------|--------|
| 0-2 | Normal |
| 3-5 | Elevated |
| 6-10 | High AI signal |
| 10+ | Very high AI signal |

## Tricolon Detection

AI produces alliterative groups of three with suspicious frequency.

Pattern examples:
- "clear, concise, and compelling"
- "fast, flexible, and free"
- "robust, reliable, and resilient"

Detection approach:
```python
# Look for: adjective, adjective, and adjective
tricolon_pattern = r'\b(\w+), (\w+),? and (\w+)\b'
# Flag if words share first letter or similar endings
```

## Sentence Length Uniformity

Human writing varies naturally. AI tends toward medium-length sentences.

```python
def sentence_uniformity(sentences):
    lengths = [len(s.split()) for s in sentences]
    mean = sum(lengths) / len(lengths)
    variance = sum((l - mean) ** 2 for l in lengths) / len(lengths)
    std_dev = variance ** 0.5
    return std_dev

# std_dev < 5: Suspicious uniformity
# std_dev 5-15: Normal variation
# std_dev > 15: High variation (human)
```

## Paragraph Symmetry

AI produces "blocky" text with uniform paragraph lengths.

```bash
# Check paragraph length distribution
awk '/^$/{if(p)print p; p=0; next}{p+=NF}END{print p}' file.md | sort -n | uniq -c
```

If most paragraphs cluster around the same length (e.g., 40-60 words), flag as AI signal.

## Bullet-to-Prose Ratio

AI defaults to bullet points, especially with emojis.

```bash
# Count bullet lines vs total lines
bullet_lines=$(grep -c '^\s*[-*]' file.md)
total_lines=$(wc -l < file.md)
ratio=$((bullet_lines * 100 / total_lines))
```

| Ratio | Signal |
|-------|--------|
| 0-30% | Normal |
| 30-50% | Elevated |
| 50-70% | High (check context) |
| 70%+ | Very high AI signal |

**Emoji bullets** (e.g., lines starting with emoji) in technical documentation are a strong AI tell.

## Five-Paragraph Essay Structure

AI defaults to: intro + three body sections + conclusion recap.

Check for:
1. Opening paragraph that restates the question
2. Three distinct middle sections
3. Closing paragraph that summarizes without adding new information

## Perfect Grammar Signals

| Pattern | Human Range | AI Signal |
|---------|-------------|-----------|
| Contractions | Common | Rare/absent |
| Oxford commas | Variable | Always present |
| Typos | Occasional | None |
| Sentence fragments | Present | Rare |
| Starting with "And" or "But" | Common | Rare |

## Register Uniformity

Human writing shifts between abstract and concrete, formal and casual. AI maintains consistent register throughout.

Check for:
- Absence of colloquialisms
- No slang or informal expressions
- Uniform formality level across all sections

## Participial Phrase Tail-Loading

AI appends present participial (-ing) phrases to sentence ends at 2-5x the human rate (Wagner 2025).

Pattern: `[Main clause], [present participle] [detail].`

Examples:
- "The team developed a framework, **enabling** researchers to analyze data."
- "The policy was implemented, **marking** a shift in approach."
- "She published findings, **contributing** to the body of research."

```python
# Detect sentences ending with ", [word]-ing ..."
participial_tail = r',\s+\w+ing\s+[\w\s]+\.$'
# 3+ matches in a paragraph is a strong signal
```

| Count per 500 words | Signal |
|---------------------|--------|
| 0-1 | Normal |
| 2-3 | Elevated |
| 4+ | Strong AI signal |

## "From X to Y" Range Construction

AI uses this template to express scope at much higher rates than human writers.

Examples:
- "From bustling cities to serene landscapes"
- "From beginners to experts"
- "From ancient traditions to modern innovations"

```python
from_to_pattern = r'\bfrom\s+[\w\s]+\s+to\s+[\w\s]+'
```

## Correlative Conjunction Overuse

AI over-relies on correlative pairs in close proximity:

| Pattern | Example |
|---------|---------|
| "whether...or" | "Whether you're a beginner or an expert" |
| "not only...but also" | "Not only does it improve X, but also Y" |
| "not just...but" | "Not just a tool, but a transformation" |

2+ correlative pairs in the same paragraph is a signal.

## ASCII Arrow Prose Connector

AI uses `->` and `→` as prose shorthand instead of writing
"to", "into", or "produces". Arrows are fine in code, type
signatures, and diagrams but mark AI-generated prose.

Examples:
- "spec -> plan -> tasks" (slop)
- "spec to plan to tasks" (human)
- "returns `int -> str`" (fine, code context)

```bash
# Detect arrows in prose (exclude code blocks)
awk '/^```/{c=!c}!c' file.md | grep -oP '\s->\s|→' | wc -l
```

## Plus-Sign Conjunction

AI uses `+` as a conjunction ("X + Y") in prose instead of
"and" or "with". Fine in code, math, and labels.

Examples:
- "hooks + skills" (slop)
- "hooks and skills" (human)
- "1 + 1 = 2" (fine, math)

```bash
# Detect prose plus signs (word + word pattern)
awk '/^```/{c=!c}!c' file.md | grep -oP '\w\s\+\s\w' | wc -l
```

## Colon Addiction

AI uses colons to introduce explanations at 3-5x the human rate.

Pattern: "Topic: explanation" as a sentence structure.

```bash
# Count colons used as sentence-internal punctuation
grep -oP '(?<=[a-z]): (?=[A-Z])' file.md | wc -l
```

Combined with em dash overuse, this creates a "punctuation for professionalism" signature.

## Semicolon Avoidance

AI rarely uses semicolons. The ratio of em dashes to semicolons is skewed compared to human writing, where semicolons appear in roughly 1 in 50 sentences for experienced writers.

```bash
em_dashes=$(grep -o '—' file.md | wc -l)
semicolons=$(grep -o ';' file.md | wc -l)
# Human ratio: roughly equal. AI ratio: 10:1 or worse.
```

## Sentence Length Clustering (Refined)

Beyond uniformity (tracked above), the specific AI cluster is **15-25 words per sentence**. Human writing ranges from 3-word fragments to 40+ word complex sentences. AI avoids both extremes.

```python
def length_clustering(sentences):
    lengths = [len(s.split()) for s in sentences]
    in_range = sum(1 for l in lengths if 15 <= l <= 25)
    return in_range / len(lengths)

# > 0.7 (70% of sentences in 15-25 range): strong AI signal
```

## Topic-Evidence-Summary Paragraph Template

AI paragraphs follow a rigid structure:
1. Topic sentence (states the point)
2. Supporting detail (1-3 sentences)
3. Summary/transition (restates or bridges)

Human writers vary this: some paragraphs are all evidence, some start with a question, some end abruptly.

Detection: check if the first and last sentences of each paragraph express the same idea using different words.

## Conclusion Mirroring

AI introductions and conclusions are near-paraphrases of each other. Check cosine similarity between first and last paragraphs.

Human writing ends with specifics, callbacks to earlier points, questions, or simply stops.

## Structural Score Calculation

```python
def structural_score(metrics):
    score = 0
    if metrics['em_dash_density'] > 5:
        score += 2
    if metrics['sentence_std_dev'] < 5:
        score += 2
    if metrics['bullet_ratio'] > 0.5:
        score += 2
    if metrics['paragraph_uniformity'] > 0.8:
        score += 2
    if metrics['zero_contractions']:
        score += 1
    if metrics['emoji_bullets']:
        score += 3
    # New patterns (2025-2026 research)
    if metrics.get('participial_tail_count', 0) > 3:
        score += 2
    if metrics.get('sentence_length_cluster_ratio', 0) > 0.7:
        score += 2
    if metrics.get('semicolon_count', 1) == 0 and metrics.get('em_dash_density', 0) > 3:
        score += 1  # em dashes without semicolons
    if metrics.get('correlative_pairs', 0) > 2:
        score += 1
    if metrics.get('arrow_connectors', 0) > 0:
        score += 1
    if metrics.get('plus_conjunctions', 0) > 1:
        score += 1
    return min(10, score)
```


path: modules/vocabulary-patterns.md
content: ---
module: vocabulary-patterns
category: detection
dependencies: [Grep, Read]
estimated_tokens: 800
---

# Vocabulary Pattern Detection

Comprehensive word and phrase lists for AI slop detection, organized by confidence level and category.

## Tier 1: Highest Confidence Words

These words appear dramatically more often in AI-generated text. Research shows some (like "delve") appeared 10-25x more frequently after 2023.

### Power Words (Overinflated Verbs)
```
delve, embark, unleash, unlock, revolutionize, spearhead,
foster, harness, elevate, transcend, forge, ignite,
propel, catalyze, galvanize, amplify
```

### Sophistication Signals (False Depth)
```
multifaceted, nuanced, intricate, meticulous, profound,
comprehensive, holistic, robust, pivotal, paramount,
indispensable, quintessential
```

### Metaphor Abuse
```
tapestry, beacon, realm, landscape, symphony, mosaic,
crucible, labyrinth, odyssey, cornerstone, bedrock,
linchpin, nexus
```

### Display Verbs
```
showcasing, exemplifying, demonstrating, illuminating,
underscoring, highlighting, epitomizing
```

## Tier 2: Medium Confidence Words

Context-dependent markers that become problematic in clusters.

### Transition Overuse
```
moreover, furthermore, indeed, notably, subsequently,
consequently, additionally, likewise, nonetheless,
henceforth, thereby, whereby
```

### Hedging Stacks
```
potentially, typically, generally, arguably, presumably,
ostensibly, conceivably, seemingly, perhaps, might
```

### Intensity Words
```
significantly, substantially, fundamentally, profoundly,
dramatically, tremendously, remarkably, exceedingly,
immensely, vastly
```

### Business Jargon
```
leverage, synergy, optimize, streamline, scalability,
actionable, deliverables, stakeholders, bandwidth,
paradigm, disruptive, ecosystem
```

### Tech Buzzwords
```
cutting-edge, state-of-the-art, next-gen, AI-powered,
game-changing, innovative, transformative, seamless,
user-friendly, best-in-class
```

## Tier 3: Phrase Patterns

### Vapid Openers (Score: 4)
```
"In today's fast-paced world"
"In an ever-evolving landscape"
"In the dynamic world of"
"In this digital age"
"As technology continues to evolve"
"In the realm of"
```

### Empty Emphasis (Score: 3)
```
"cannot be overstated"
"goes without saying"
"needless to say"
"it bears mentioning"
"of paramount importance"
"absolutely essential"
```

### Filler Phrases (Score: 2)
```
"it's worth noting that"
"it's important to understand"
"at its core"
"from a broader perspective"
"through this lens"
"when it comes to"
"at the end of the day"
```

### Attribution Cliches (Score: 3)
```
"a testament to"
"serves as a reminder"
"stands as proof"
"speaks volumes about"
"a shining example of"
```

### Marketing/Sales Speak (Score: 4)
```
"look no further"
"unlock the potential"
"unleash the power"
"treasure trove of"
"game changer"
"take it to the next level"
"best kept secret"
```

### Travel/Place Cliches (Score: 4)
```
"nestled in the heart of"
"bustling streets"
"hidden gem"
"off the beaten path"
"steeped in history"
"a feast for the senses"
```

### Journey Metaphors (Score: 3)
```
"embark on a journey"
"navigate the complexities"
"pave the way"
"chart a course"
"at a crossroads"
```

## Tier 4: Research-Validated Markers (2025-2026)

From peer-reviewed studies on AI text detection (PMC12219543, arXiv 2503.01659v1).

### High-Frequency Shift Words

These common words had the largest frequency increase in post-ChatGPT text. Individually unremarkable; their co-occurrence is the signal.

**10-word co-occurrence test** (5+ in a single paragraph = strong signal):
```
across, additionally, comprehensive, crucial, enhancing,
exhibited, insights, notably, particularly, within
```

### New Verbs (Score: 3 each)
```
underscore(s), bolster, foster, harness, illuminate,
elucidate, grapple, reimagine, intertwine, exemplify,
evoke, emulate, transcend, unravel
```

Note: "underscores the importance" is a near-diagnostic construction (13.8x human baseline frequency).

### New Adjectives (Score: 2-3 each)
```
seamless, invaluable, dynamic, whimsical, vibrant,
timeless, sustainable (outside environmental context)
```

### New Adverbs (Score: 2 each)
```
aptly, tirelessly, seamlessly, vividly
```

### New Nouns (Score: 2 each)
```
interplay, facet, symphony (metaphorical), endeavor,
synergy, insights (as padding: "provides valuable insights")
```

### Hedging Constructions (Score: 2-3 each)
```
"It's important to note that"
"It's important to remember that"
"Generally speaking"
"To some extent"
"From a broader perspective"
"One might argue that"
"It could be said that"
"There is growing evidence that"
```

### Conclusion Starters (Score: 2 each)
```
"Overall, "
"In conclusion, "
"In summary, "
"Ultimately, "
"To sum up, "
```

These overwhelmingly start AI conclusions. Human writers end with specifics, callbacks, or questions.

### Sycophantic Positivity (Score: 3 each)
```
"areas for improvement" (avoids saying "problems")
"invaluable resource"
"exceptional"
"remarkable"
```

AI text is measured at 50% more sycophantic than human text (Georgetown AI Sycophancy Research, 2025).

## Detection Regex Patterns

For automated scanning:

```python
TIER1_PATTERNS = [
    r'\bdelve\b', r'\bembark\b', r'\btapestry\b', r'\brealm\b',
    r'\bbeacon\b', r'\bmultifaceted\b', r'\bpivotal\b', r'\bnuanced\b',
    r'\bmeticulous(?:ly)?\b', r'\bintricate\b', r'\bshowcasing\b',
    r'\bleveraging\b', r'\bstreamline\b', r'\bunleash\b',
]

TIER1_NEW_PATTERNS = [
    r'\bunderscore[sd]?\b', r'\bbolster\b', r'\bfoster\b',
    r'\billumicat[es]\b', r'\belucidat[es]\b', r'\bgrapple\b',
    r'\breimaginc[es]\b', r'\bintertwine[ds]?\b', r'\bexemplif(?:y|ies)\b',
    r'\bseamless(?:ly)?\b', r'\binvaluable\b', r'\bvibrant\b',
    r'\binterplay\b', r'\bfacet[s]?\b', r'\bendeavor[s]?\b',
    r'\baptly\b', r'\btirelessly\b', r'\bvividly\b',
]

PHRASE_PATTERNS = [
    r"in today's fast-paced",
    r'cannot be overstated',
    r"it's worth noting",
    r'at its core',
    r'a testament to',
    r'unlock the (?:full )?potential',
    r'embark on (?:a |the )?journey',
    r'nestled in the heart',
    r'treasure trove',
    r'game[- ]changer',
    r"it's important to (?:note|remember|understand) that",
    r'generally speaking',
    r'from a broader perspective',
    r'one might argue',
    r'it could be said',
    r'there is growing evidence',
    r'areas for improvement',
    r'invaluable resource',
    r'underscores the importance',
]

# Conclusion starters (check first word of last paragraph)
CONCLUSION_STARTERS = [
    r'^Overall,',
    r'^In conclusion,',
    r'^In summary,',
    r'^Ultimately,',
    r'^To sum up,',
]

# Co-occurrence test: 5+ of these in one paragraph = strong signal
HIGH_FREQ_SHIFT_WORDS = [
    'across', 'additionally', 'comprehensive', 'crucial', 'enhancing',
    'exhibited', 'insights', 'notably', 'particularly', 'within',
]
```

## False-Positive Exclusions

Always define what to detect AND what to ignore.
Flagging legitimate usage erodes trust in the detector.

### Word-Level Exclusions

| Word | Skip When | Flag When |
|------|-----------|-----------|
| delve | Technical deep-dive, data analysis | Generic "delve into the topic" |
| leverage | Physics, mechanics, finance (actual leverage) | Business jargon substitute for "use" |
| robust | Engineering specs, statistical methods | Marketing claims, feature descriptions |
| seamless | Measured UX testing results with data | Feature descriptions without evidence |
| journey | Actual travel, user journey maps with data | Metaphor for any process or experience |
| comprehensive | Describing verified 100% coverage | Filler adjective for any collection |
| nuanced | Academic analysis with cited evidence | Flattery or vague praise |
| foster | Childcare, horticulture, proper nouns | Generic "foster collaboration/innovation" |
| landscape | Geography, actual visual design | "the AI landscape", "competitive landscape" |
| ecosystem | Biology, verified platform with APIs | Vague reference to any group of things |
| insights | Data analysis with specific findings | "provides valuable insights" (padding) |
| dynamic | Physics, runtime behavior (code) | Filler adjective for any changing thing |

### Phrase-Level Exclusions

| Phrase | Skip When | Flag When |
|--------|-----------|-----------|
| "at its core" | Describing actual CPU/kernel internals | Filler for "basically" |
| "it's worth noting" | Preceding a genuine caveat with evidence | Preceding obvious information |
| "generally speaking" | Qualifying a statistical claim | Hedging an opinion |
| "areas for improvement" | Formal performance review documents | Avoiding saying "problems" or "bugs" |

### Structural Exclusions

Do not flag these as slop even if they match patterns:

- **Quoted text**: Content inside blockquotes or citation marks
- **Code examples**: Technical terms inside backticks or code blocks
- **Proper nouns**: Product names, titles, organizations
- **Direct citations**: Attributed quotes from named sources
- **Glossary definitions**: Defining a term that happens to be a slop word
- **Changelogs**: Version history entries using standardized language
- **Generated output**: Content explicitly marked as AI-generated for comparison

## Scoring Formula

```python
def calculate_vocabulary_score(text, word_count):
    tier1_matches = count_tier1_matches(text)
    tier2_matches = count_tier2_matches(text)
    phrase_matches = count_phrase_matches(text)

    raw_score = (tier1_matches * 3) + (tier2_matches * 2) + (phrase_matches * 3)
    normalized = (raw_score / word_count) * 100

    return min(10.0, normalized)  # Cap at 10
```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)