Tier 2: Semantic Deduplication

View as Markdown

Tier 2 finds redundant content — inside a single skill and across a skill collection — using embedding similarity, with chat-LLM verification for intra-skill candidates. It needs a configured embeddings provider; intra-skill analysis also needs a chat LLM. See Providers & Credentials to set both up, including the fully local recipe when skill content must not leave your machine.

Both checks read local files only. No external vector database or catalog service is required.

Which check do you need

dedup-scan is an alias for context-optimization-check with the same analysis options. The default report basenames identify which command name was invoked.

The two checks differ in what they call out to:

Intra-skill (context-optimization-check)Inter-skill (similarity-check)
ScopeWithin one skill directoryAcross a collection, or one candidate against a local catalog
NeedsEmbeddings provider and a chat LLMEmbeddings provider only — no chat LLM is ever called
ComparisonPairwise cosine similarity, then Union-Find clustering, then LLM verificationPairwise cosine similarity, or one target compared with catalog entries
Embedding storageEphemeral, in memory per runEphemeral for a direct scan, or a versioned local JSON catalog

Detect redundancy inside one skill

The repository’s exact SPDX-only HTML preamble is excluded from Markdown chunks, so required license metadata cannot create duplicate-content findings. Comments with any additional text or directive remain part of the comparison.

As a skill grows, its directory accumulates repeated content: sections copied between SKILL.md and reference documents, near-identical guidance under different headings, script docstrings that restate the main instructions. That repetition consumes context without adding information.

Not every similarity is redundant. A short overview in SKILL.md and a detailed explanation in references/ are intentional progressive disclosure. Tier 2 therefore runs two stages — embeddings find likely overlap, then a chat LLM classifies its purpose:

StageWhat runsWhat it produces
1. Embedding clusteringChunks are batch-embedded with the configured embeddings provider; pairs at or above the threshold are grouped with Union-FindCandidate clusters of overlapping chunks
2. LLM verificationThe actual text of each candidate cluster goes to the configured provider model for classificationA verdict, a confidence score, reasoning, and a suggested author action
Scan one skill for repeated content
$skillevaluator context-optimization-check ./my-skill

Useful options:

FlagDefaultEffect
--threshold0.8Minimum cosine score for a chunk pair to join a candidate cluster
--modelconfigured embedding modelEmbedding model override for this run
--llm-modelconfigured provider modelChat-LLM model override for the verification stage

The default intra-skill threshold (0.80) is higher than the inter-skill default because sections from the same skill naturally share more domain vocabulary.

What gets chunked

ExtensionChunking strategy
.md, .mdcHeading-based sections; sections over 3,000 characters are split at eligible paragraph boundaries when possible; chunks under 80 characters are dropped
.pyModule docstrings plus class and function signatures with their docstrings; implementation bodies are not included
.shFunction bodies and comment blocks of at least three consecutive comment lines
Other extensionsSkipped

Valid mapping frontmatter is removed from Markdown before chunking. Malformed, empty, or non-mapping frontmatter remains part of the analyzed text. Every retained chunk keeps its source file and line range, so findings point you to the exact overlapping sections.

Verdicts and what to do about them

VerdictMeaningYour action
DUPLICATEContent repeats the same information without a meaningful additionConsolidate into one location; the finding is actionable
INTENTIONAL_DETAILOne section summarizes content another section develops in detailKeep it — progressive disclosure is not reported as a finding
RELATED_BUT_DISTINCTSections cover the same topic for different purposes or from different anglesKeep it — not reported as a finding

Duplicate findings map to severities by confidence:

ConditionSeverity
DUPLICATE with confidence >= 0.70HIGH — blocking
DUPLICATE with confidence < 0.70MEDIUM — advisory
Short, same-file duplicate dominated by comment or configuration-style textCapped at LOW — advisory

If chat-LLM analysis fails for a candidate cluster, Tier 2 reports the incomplete analysis as a blocking error rather than treating the content as clean.

Compare skills across a collection

similarity-check recursively discovers skill manifests, embeds each skill’s name and description by default, and reports pairs at or above the selected threshold.

Scan a collection for overlapping skills
$skillevaluator similarity-check ./skills

Every match lands in one of five classifications:

ClassificationScoreSeverity and default behavior
EXACT_DUPLICATE>= 0.95CRITICAL — blocking
HIGH_SIMILARITY>= 0.90HIGH — blocking
SIMILAR>= 0.75MEDIUM — advisory
LOOSELY_RELATED>= 0.50LOW — advisory
DISTINCT< 0.50INFO — advisory; below the default reporting threshold

The threshold controls which score bands are returned; it does not change the classification boundaries. The default (0.75) reports SIMILAR and stronger matches.

FlagDefaultEffect
--threshold0.75Minimum reported cosine score
--full-bodyoffEmbed each discovered SKILL.md in full, including frontmatter, chunked at 512 tokens with 64-token overlap and average pooling — instead of only the name and description. Supporting files are not included
--modelconfigured embedding modelEmbedding model override for this run
--type skillautoExplicit skill discovery when automatic detection is not suitable

Use a local catalog

For repeated screening — say, every new skill submission against your team’s existing set — save the collection once and query candidates against it.

1

Build the catalog

Save a reusable local catalog
$skillevaluator similarity-check ./skills --save-catalog ./skill-catalog.json

--save-catalog builds a versioned JSON catalog from the supplied collection.

2

Screen a candidate

Compare one candidate against the catalog
$skillevaluator similarity-check ./candidate-skill --catalog ./skill-catalog.json

--catalog requires an existing catalog and a candidate directory containing a root SKILL.md. It compares only that candidate against the catalog entries — it does not compare catalog entries with one another, and it never silently rebuilds a missing catalog.

Catalogs contain finite embedding vectors, relative skill paths, display names, descriptions, content fingerprints, and provider, model, mode, and endpoint-fingerprint metadata for compatibility checks. They never contain API keys.

The provider, model, endpoint, vector length, and description-versus-full-body mode used for a query must be compatible with the catalog. A missing, malformed, incompatible, non-finite, or oversized catalog is rejected with an actionable error — query with matching settings, or rebuild the catalog with the settings you intend to use.

Catalogs stay local unless you explicitly share them. Because they contain embeddings and other data derived from skill content, review them before sharing like any other generated project artifact.

Inside validate

validate runs the intra-skill deduplication pass by default as part of a full evaluation. When the optional Tier 2 dependencies or an embeddings provider are unavailable, the pass skips gracefully with a warning — a keyless validate still completes. Use --no-tier2 (or --no-dedup) to disable the pass explicitly:

Skip the Tier 2 pass
$skillevaluator validate ./my-skill --no-dedup

All Tier 2 commands accept the standard report options (-r cli,json,html,markdown and -o). HIGH and CRITICAL findings, along with blocking analysis errors, produce a non-zero exit code; MEDIUM and LOW findings are advisory. Exit behavior is identical in every output mode, so these commands work as CI gates — see Gate Your CI.

Defaults and safety limits

SettingDefaultPurpose
Inter-skill reporting threshold0.75Reports SIMILAR and stronger matches
Intra-skill clustering threshold0.80Selects candidate chunk pairs for clustering
Full-body chunking512 tokens, 64-token overlapChunked average pooling for --full-body embeddings
Minimum content chunk80 charactersDrops fragments too small to compare usefully
Large Markdown section split point3,000 charactersSplits oversized sections at eligible paragraph boundaries
High-confidence duplicate boundary0.70Maps actionable duplicate verdicts to HIGH rather than MEDIUM

Tier 2 treats skill content and catalog files as untrusted input. Before any provider call, it rejects linked roots, linked or escaping files, non-regular files, and paths outside the verified scan root. Explicit limits bound discovered paths, file counts, per-file and total input bytes, content chunks, candidate clusters, catalog size and entry count, vector length, returned matches, and pairwise scalar work. Catalog loading also rejects duplicate JSON keys, unexpected fields, invalid identities, non-finite vectors, and incompatible provider metadata. If a collection exceeds a limit, split it into intentional batches rather than scanning an unbounded directory tree.

What leaves your machine

  • A default inter-skill scan sends skill names and descriptions to the configured embeddings provider. With --full-body, the scan sends each discovered SKILL.md in full, including frontmatter, in embedding chunks; supporting files are not sent.
  • Intra-skill analysis sends retained content chunks to the configured embeddings provider. Only candidate clusters found by the embedding stage are sent to the configured chat LLM for classification.
  • similarity-check never calls a chat LLM.
  • Catalog files are read and written locally; SkillEvaluator does not upload them to a catalog service.

Use the fully local provider recipe when skill content must not leave the machine, and review your provider’s data handling terms before sending confidential skill content to a hosted endpoint.

Edge cases

ScenarioBehavior
Skill contains only SKILL.mdChecks repeated sections within that file
Fewer than two chunks remainSucceeds with “Not enough content to compare”
Markdown has no headingsTreats the body as a preamble and attempts paragraph splitting when oversized; retains the original section if no eligible split is produced
Unsupported extensionSkips the file
Duplicate sections occur in one fileReports the relevant headings and line ranges
Skill carries large generated snapshots (evals, .evals, results, .results, versions, .versions)Excluded directories are pruned during traversal, so their contents never count toward the discovered-path limit — a well-used skill with large evals or results snapshots no longer trips it, as it could in earlier releases
A directory is a Windows junction or other reparse pointRejected during the walk, like symbolic links
Embedding or chat-LLM analysis is incompleteReports an error; does not claim a clean result
ScenarioBehavior
Direct collection has fewer than two skillsReturns an actionable input error
Catalog source collection is emptyRefuses to save an empty catalog
Catalog path is missingReturns an error and does not rebuild it silently
Candidate lacks a root SKILL.mdRejects the catalog query
Candidate matches multiple catalog entriesReports matches in descending similarity order
Provider, endpoint, model, mode, or vector length differsRejects the catalog; query with matching settings or rebuild it
Catalog is malformed, non-finite, or oversizedRejects it before comparison

Next steps