"Deduplicate repeated markdown content — rule files, skill bodies, ADRs, docs — into a single named source of truth and migrate every call site to cite it by exact heading. Use when the same prose, literal, or concept appears (or is reworded) across 3+ files: 'DRY this prose', 'extract a shared rule', 'single source of truth for X', a value-bump diff touching 3+ files — refuses extraction below the Rule of Three."
Resources
4Install
npx skillscat add melodic-software/claude-code-plugins/plugins-docs-hygiene-skills-extract-ssot Install via the SkillsCat registry.
Extract SSOT
Why this skill exists
Codifies the markdown-SSOT-extraction pattern as a repeatable workflow. Each invocation targets ONE cluster of repeated markdown content and resolves it to a single named source of truth — consolidating into an existing SSOT home when one already owns the concept, otherwise creating a new artifact (e.g. a rule file or a new skill) — plus migration of all call sites to cite by exact heading.
The principle is the coding Rule of Three / DRY, applied to markdown text. If the same unit of prose appears 3+ times, changes together, and has an identity that can be named, collapse to one definition and reference by name. Below 3 instances, inline is the disciplined call — premature abstraction is the dominant failure mode.
Typical markdown extraction shapes the workflow handles:
- A vocabulary of CLI/API verbs that many prompts, skills, and rules invoke inline — collapsed into one rule file with stable headings the call sites cite
- A constraint or guardrail repeated across multiple skills (e.g. "always run X before Y") — promoted to a single always-loaded rule
- A workflow primitive appearing across several skills — extracted to a shared primitive doc that skills cite by name
Extraction is dangerous: ~19% failure rate even on curated skills (SkillsBench, n=84 tasks), ~50% on practitioner-authored skills (40-skill failure analysis). This skill encodes the guardrails (Rule of Three, categorical-shape test, ≤500-line bound, one-level-deep mandate, Metz unwind procedure) so each invocation is reversible and reviewable.
Code / config escape-hatch. Repeated string literals, magic constants, helper functions in source code, or repeated CI / settings / MCP stanzas in config files are also extractable in principle (Rule of Three applies). This skill flags such clusters during identify but does NOT ship a citation contract for them — the caller uses the language-idiomatic form (import / using / source, YAML anchor, JSON $ref, build-tool include) and language-aware refactoring tools (IDE rename, Roslyn / ts-morph). Markdown is the only file class with no language-level rename safety net — that is where the contract here adds value.
Evidence discipline
Every extraction decision must be grounded in direct evidence captured this session — grep output or file reads you performed yourself. This skill calls such evidence Tier 0. Recall ("I remember seeing this repeated"), a subagent's survey summary, or any other synthesized claim is NOT Tier 0 — promote it via your own grep before it drives a plan or an edit. A subagent roster is a lead list, never proof.
Scope: markdown SSOT
In-scope: repeated content across the consuming repository's tracked markdown — instruction files (CLAUDE.md, AGENTS.md, README.md), rule files (.claude/rules/), skill bodies (.claude/skills/), agent definitions (.claude/agents/), automation/routine prompts, ADRs, and docs. Markdown is the file class with no compiler or IDE refactor to catch a broken reference — the citation contract here is what closes that gap.
| Citation target | Form |
|---|---|
| Rule-file H3 heading | per `<file>.md` "<exact heading>" |
| New skill | /<skill-name> invocation (skill internals NOT cited externally — see /docs-hygiene:audit-encapsulation) |
Out-of-scope, but flagged during identify:
- Code clusters (
.cs,.ts,.py,.sh,.ps1, …) — repeated literal, magic constant, regex, helper function. Use language-idiomatic extraction (constants file, shared module, IDE rename refactor / Roslyn / ts-morph). Compiler + lint catch missed call sites — citation rot is structurally prevented. - Config clusters (
.yml,.json,.toml,.editorconfig) — repeated CI step, MCP entry, settings stanza. Use the tooling's native include / anchor /$refmechanism. Schema validation catches mismatches. - Mixed clusters spanning markdown + code + config — pick the canonical owner (usually code/schema where runtime authority lives), then each file class cites in its native form. This skill handles the markdown half; the code/config half follows that file class's own conventions.
The 6-test extraction gate (Rule of Three, namable, stable, self-contained, bounded, one level deep) generalizes to all file classes, and context/decision-framework.md annotates each test with code/config equivalents. The HOW (citation contract, rename sweep, encapsulation rule) is markdown-specific.
Boundary with single-file refactoring: a rename, inline, or extract confined to one file or one recent diff is ordinary editing, not this skill's job. /docs-hygiene:extract-ssot handles cross-file markdown deduplication where the repeated unit needs a stable name and cross-file citations. When work touches both, normalize the call sites first, then lift the named unit.
When to use vs not use
Use when: 3+ instances of the same unit exist across files; the instances change together (correlated edits); the unit has a stable identity that can be named; the unit is self-contained (extracts cleanly without dragging unrelated context). Normal-work entry point — when a cleanup pass, audit, value-bump diff, or review surfaces ANY of the three duplication smells — (a) same literal repeated across 3+ tracked files, (b) value-bump diff touches 3+ unrelated files, (c) same concept reworded across 3+ files or contradicting nuance between files — route detection here. The skill accepts both literal and semantic clusters; the 6-test gate's "namable + categorical-shape + stable identity" tests admit semantic clusters provided the unit can be named and instances change together.
Classify each file's role before flagging it as a duplicate:
- DESCRIBE — the file IS the SSOT and owns the value or concept; keep the body.
- USE — the file consumes the concept as a load-bearing reference (rules, agent prompts, skill bodies invoking the rule); it MUST cite the SSOT by exact heading or path + key rather than restate body content.
- EXPOSE — the file surfaces the concept to humans for onboarding clarity (README install commands, error messages, public-facing docs); it MAY restate when onboarding clarity outweighs maintenance cost AND adjacent prose cross-references the SSOT.
Contract identifiers the SSOT defines (tier names, label slugs, action verbs, command names) stay inline in consumers — naming them is not duplication; those tokens ARE the contract surface. Content (definitions, criteria, mapping tables, thresholds, exception clauses) must cite, never recap.
Don't use for: single-file refactoring inside one diff; recent-diff simplification of a single skill or feature; filing a tracking issue (route to the repo's issue tracker); writing a new skill from scratch without an underlying repetition trigger (use a skill-authoring workflow such as the skill-creator plugin); cross-language type sharing where the answer is codegen, not text dedup.
Full decision matrix: context/decision-framework.md (6+5 checklist with worked examples).
Action router
| Argument | Action | Purpose |
|---|---|---|
| (empty) | Smart default | Auto-detect: working notes from a prior run hold an active candidate roster → resume the current phase; the invocation or conversation already names a scope → identify; otherwise → confirm scope with the user first (see "Bare invocation — confirm scope first") |
identify [<cluster-name>] |
Find candidates (default = exhaustive subagent survey) | Dispatches a read-only exploration subagent over 30+ duplication heuristics (full body in actions/identify.md); ranks by ROI; emits batch-sequencing matrix + recommended /docs-hygiene:extract-ssot batch invocation. Refuses premature (<3 instances). Single-cluster mode (identify <name>) skips the subagent for a targeted Tier 0 grep |
verify <cluster-name> |
Refuse-fast pre-extraction gate | 6-gate cheap check (Tier 0 grep, citation state, primary-source URL gate, bifurcation check, off-by-one heuristic, LOW-ROI threshold). Output: PROCEED | REFUSE-{reason} | WARN. OPTIONAL — does not gate plan/execute. See actions/verify.md |
plan <cluster-name> |
Architect | Pre-step (Tier 0 grep): does an existing rule/doc already own the concept? If yes → consolidate-into-existing branch (extend the home + de-recap consumers, no new artifact). Else choose creation output type (rule vs skill); draft or extend SSOT body; sketch migration plan |
execute <cluster-name> |
Migrate | Write or extend the SSOT (skip writing when an existing home already documents the concept); rewrite call sites to cite + de-recap inline reproductions; sweep references via /docs-hygiene:rename-references if a heading/identifier changed; verify |
batch <cluster-list> |
Multi-candidate orchestration | Auto-verify filter, file-overlap matrix, sequential-by-default dispatch, lesson injection between subagents. See actions/batch.md |
unwind <ssot-name> |
Reverse | Re-introduce duplication per Sandi Metz wrong-abstraction recovery |
One action per response; actions don't chain implicitly.
Bare invocation — confirm scope first
Shared clean-tree / no-scope shape: `../../context/clean-tree-fallback.md`.
A bare /docs-hygiene:extract-ssot with no working notes to resume, no
argument, and no scope implied by the conversation does not
auto-dispatch the exhaustive survey. Exhaustive identify sweeps every
tracked markdown file and, at whole-repo scale, feeds a multi-agent
verify/execute batch — a spend the user opts into, never a default.
Ask one question with prescribed defaults, recommended option first:
- Whole-repo exhaustive survey (recommended for maintenance
sweeps) — runs undercontext/orchestrated-mode.mddefaults; in the
same ask, confirm depth (roster only / verified roster / full
pipeline with wave commits). - Path- or glob-scoped exhaustive survey — the user names one or
more directories / globs (e.g.plugins/docs-hygiene/,docs/**/*.md);
routes to exhaustiveidentifyrestricted to tracked markdown under
that pathspec (still a survey, not a single-cluster grep). - Targeted cluster — the user names a semantic cluster; routes to
identify <cluster-name>(Tier 0 grep on that cluster only). - Not now.
Non-interactive sessions (no user to ask): proceed only when the
invoking automation supplied an explicit scope or action argument;
otherwise report the available options and stop rather than assuming
whole-repo intent.
Decision framework
Before recommending extraction, run the 6-test gate (all must pass) + 5-test inline gate (any one keeps inline). Full checklist with evidence and worked examples in context/decision-framework.md.
Headline gate: Rule of Three (Don Roberts / Fowler) — refuse extraction at <3 instances. Premature abstraction is the dominant failure mode.
Output type
Markdown branch (primary):
| Shape | Target | Trigger |
|---|---|---|
| Concept already has an SSOT home | Consolidate into the existing file (extend it only where a consumer carries nuance the home lacks) + de-recap the inline reproductions; create no new artifact | An existing rule/skill/doc already owns the concept and consumers recap it inline instead of citing it. Positive output-type form of verify Gate 2 + anti-pattern Shape C; identify flags it as edit-existing-rule / trim-to-citation |
| Vocabulary, IF-THEN rules, hard constraints, ≤500 lines | Rule file wherever the consuming repository's own conventions place shared rules — default .claude/rules/<topic>.md (always-loaded) or a path-scoped rule file |
Categorical markdown content; consumers cite by H3 heading |
| Workflow, multi-action, has its own actions/anti-patterns | New skill at .claude/skills/<name>/SKILL.md, authored via the consumer's skill-authoring workflow (e.g. the skill-creator plugin) |
Process content; consumers invoke /<name> |
| New action on existing skill | Action row added to the skill's action router | The workflow maps cleanly onto an existing skill's concern — same domain, same triggers, same output surface — rather than warranting a new top-level skill |
Skill-vs-rule heuristic: if the SSOT body is mostly nouns (named units the caller cites), it's a rule file. If the SSOT body is mostly verbs (steps the caller invokes), it's a skill.
Non-markdown escape (out of scope for this skill's HOW; flag during identify, refer the caller to language-idiomatic tooling): code constants / shared modules / helper libraries; config includes / YAML anchors / JSON $ref. See context/decision-framework.md "Output type: rule file vs skill" for the fuller table including code/config rows and worked examples; this section is the canonical markdown summary.
Citation form
For markdown call sites, cite by exact H3 heading text + 1-line inline summary. Template:
<scope phrase> per `<rule-file>.md` "<exact heading text>".
<scope phrase> per `<rule-file>.md` "<exact heading text>" — <optional ≤80-char summary>.For code call sites, use the language's native import syntax. For config call sites, use the tooling's native include / anchor / $ref mechanism.
One level deep — never chain A.md → B.md → C.md. A heading rename triggers a /docs-hygiene:rename-references sweep across all 10 syntactic forms.
Full contract incl. line-wrap edge case: context/citation-form.md.
Encapsulation rule
Encapsulation enforcement (detection grep, public/private surface matrix, remediation paths) lives in its own skill — /docs-hygiene:audit-encapsulation. Different concern from duplication: violations are single-instance matters (Rule of Three does not gate them).
/docs-hygiene:extract-ssot execute invokes /docs-hygiene:audit-encapsulation detect during the refactor pass to catch any encapsulation violations introduced or exposed by the migration. See /docs-hygiene:audit-encapsulation for the public surface matrix, filter taxonomy, and remediation paths.
Anti-patterns guarded
13-pattern taxonomy with mitigations: citation rot, over-indirection, leaky abstraction, loss of locality, reference resolution failure, wrong abstraction, premature extraction, self-generated SSOT, always-loaded SSOT propagation lag, encapsulation violation, source-of-truth bifurcation, primary-source citation gate, Shape C dedup-by-deletion (positive). Each pattern + symptom + mitigation procedure: context/anti-patterns.md.
Patterns #11/#12/#13 derive from the empirical lessons in context/lessons.md and are surfaced as REFUSE triggers in the verify action.
The unwind action implements Metz's 3-step recovery for the wrong-abstraction case (re-introduce → keep used subset → delete unneeded → re-isolate).
Phases per invocation
identify-cluster → architect-plan → execute-migration → sweep-references → verifyFor multi-session work, persist the candidate roster, plan, and per-phase status to working notes in the consuming repository (wherever its conventions put task notes) so a fresh session can resume from durable state instead of re-deriving it. End each phase with a short status entry: what's done, what's next.
Per-phase checklist: context/execution-checklist.md.
Sanity checks
| When | Check | Evidence |
|---|---|---|
| Pre-extraction | 3+ instances confirmed via grep (Tier 0) | Grep output captured in the plan/working notes |
| Pre-extraction | Cluster has stable identity that can be named; instances change together | Decision-framework checklist marked in the plan |
| Pre-extraction | File-class scope identified (markdown / code / config / mixed) | Listed in the plan; citation form chosen per class |
| Per-callsite | Citation/import in the form native to the call site's file class | Diff review |
| Post-extraction | All 10 /docs-hygiene:rename-references patterns swept (markdown call sites) |
Skill output |
| Post-extraction | SSOT reads sensibly in isolation (leaky-abstraction self-test) | Manual read |
| Post-extraction | Lint clean across affected file classes; cross-references and imports resolve | Linter/build output |
What this skill does NOT do
- Decide for the user whether to migrate a specific cluster (advisory; user picks)
- Modify files outside the repository it runs in
- Skip per-phase user diff review
- Single-file micro-refactoring (rename, inline, extract confined to one file) — ordinary editing
- Workitem filing — route to the repo's issue tracker
- Author a brand-new skill from scratch without a repetition trigger — use a skill-authoring workflow (e.g. the skill-creator plugin)
- Cross-language type sharing where the answer is codegen, not text dedup
- Replace a workspace-wide verification pass — the
verifyaction here is a per-cluster refuse-fast pre-extraction gate, not a build+test+lint run; run the consuming repository's own verification afterexecute
Cross-references
context/decision-framework.md— 6+5 gate, Pre-extraction Tier 0 checklist, output-type table, worked examplescontext/citation-form.md— full citation contract for markdown call sitescontext/anti-patterns.md— 13-pattern taxonomy with mitigationscontext/execution-checklist.md— per-phase checks for theexecuteactioncontext/lessons.md— append-only empirical lessons from batch executions; consumed by theverifyaction andcontext/decision-framework.mdcontext/orchestrated-mode.md— whole-repo batch defaults: worker tiering, static concurrency cap, rate-limit-guard consumption, wave-committed cadenceactions/identify.md,actions/verify.md,actions/batch.md— action bodies (private surface)/docs-hygiene:rename-references— load-bearing 10-pattern sweep after any heading change (owns the syntactic-form set)/docs-hygiene:audit-encapsulation— encapsulation detection + remediation (separate concern)
Recheck triggers
| Condition | Action |
|---|---|
| External tool/CLI/API documented inside an SSOT rule ships a major version bump | Re-verify the Tier 0 flag/verb set in the affected rule file; cited entries may have moved or renamed |
/docs-hygiene:rename-references adds a new syntactic form to its 10-pattern sweep |
Update the sweep step in context/execution-checklist.md |
| Anthropic ships a first-class native rule/skill linker (heading-rename auto-sweep) | Demote the /docs-hygiene:rename-references step to advisory; reduce sweep scope |
| Practitioner-authored skill failure rate drops below 20% (SkillsBench refresh) | Reduce gate strictness; consider relaxing Rule of Three to Two for low-risk vocabulary |