Scan a directory or workspace for SKILL.md files across all agents and repos, capture supporting files (references, scripts, linked docs), dedupe vendored copies, enrich each Tessl tile with registry signals, and emit a canonical JSON inventory validated by JSON Schema. Then run four analytical phases in parallel against the inventory — staleness + git provenance (history, broken refs, contributors), quality (Tessl `skill review`), duplicates (similarity + LLM judgement), registry-search (per-standalone-skill registry suggestions, HTTP only) — and render a self-contained interactive HTML report with a top-of-report health overview, top-issues panel, recently-changed list, and per-tessl.json manifests view.
84
90%
Does it follow best practices?
Impact
97%
1.44xAverage score across 2 eval scenarios
Advisory
Suggest reviewing before use
For every standalone skill in a discovery.json, query the Tessl registry's
/experimental/search endpoint twice (once for the skill index, once for the
tile index) and record the single higher-aggregate-score hit as best_match.
Output conforms to
registry-search.schema.json
(currently schema_version: "1.2").
Fully programmatic — no LLM, no agent judgement. The script validates its
input (discovery.json) against
discovery.schema.json and
its output against registry-search.schema.json at the IO boundary.
Only skills with source_type: "standalone" are searched. Anything inside a
tile or an agent-harness directory (tessl_tile_skill, claude_skill,
cursor_skill, agents_skill, claude_plugin_skill) is skipped — the user has
already chosen its source by either authoring it as a tile or installing it
via tessl.json, so a registry suggestion would be noise. Standalone skills are
the ones that don't yet belong to anything, which is exactly when a registry
alternative is useful.
The skipped count is recorded in metadata.skills_skipped_non_standalone.
--discovery <path> (required) — discovery.json produced by discover-skills (schema 1.4).--output <path> — defaults to <dirname(discovery)>/registry-search.json.--registry-base-url <url> — defaults to https://api.tessl.io (or $TESSL_API_BASE_URL).--concurrency <n> — parallel HTTP workers, default 8.The script never sends an Authorization header. /experimental/search accepts
anonymous calls and returns public registry data, which is everything the
report needs to surface registry suggestions; skipping auth keeps the script
free of credential state and stale-token failure modes.
python3 <skill-dir>/scripts/registry_search.py \
--discovery "$DISCOVERY_PATH" \
--output "$OUTPUT_PATH"Stdlib + Python ≥ 3.9. jsonschema is a soft dep used for IO contract
validation. Runtime is dominated by HTTP latency: at concurrency 8, a typical
estate of 30 non-published skills completes in ~5–10s.
For each candidate, the query string is "<skill name> <skill description>"
trimmed to 480 chars. Two parallel GET requests:
GET {base}/experimental/search?q=<query>&searchMode=hybrid&filter[type][eq]=skill&page[size]=1
GET {base}/experimental/search?q=<query>&searchMode=hybrid&filter[type][eq]=tile&page[size]=1The two top-1 hits are compared on scores.aggregate and only the higher one
is recorded as best_match (with a kind: "skill" | "tile" discriminator).
Both per-query errors are still surfaced in search_errors[] for diagnostics,
but the schema records exactly one suggestion per source skill.
{
"schema_version": "1.2",
"metadata": {
"scan_id": "scan-...",
"tool_version": "skill-insights@0.11.0",
"registry_base_url": "https://api.tessl.io",
"search_mode": "hybrid",
"skills_searched": 3,
"skills_skipped_non_standalone": 79
},
"matches": [
{
"source_skill_id": "<repo>::<skill-name>",
"source_skill_name": "skill-name",
"source_tile_name": "workspace/tile-name",
"source_query": "skill-name <description>",
"best_match": { "kind": "tile", "id": "...", "name": "...", "full_name": "ws/name", "latest_version": { "version": "0.4.1", ... }, "scores": { "aggregate": 0.94, "evalAvg": 0.96, ... } },
"search_errors": []
}
],
"stats": {
"total_skills_searched": 12,
"skills_with_match": 9,
"skills_with_no_match": 3,
"match_kinds": { "skill": 4, "tile": 5 },
"search_errors": 0
},
"warnings": []
}best_match is null when both queries fail or return zero results;
otherwise it is the higher-aggregate-score hit, tagged with kind so consumers
can read kind-specific fields (description/path for skills,
full_name/latest_version for tiles) without re-querying. Per-request HTTP
errors are captured in search_errors[] (target: "skill" or "tile")
without aborting the phase.
jq -e '.schema_version == "1.2"' "$OUTPUT_PATH" > /dev/null
jq '.stats' "$OUTPUT_PATH"matches.length should equal metadata.skills_searched.
Registry search complete.
Skills searched: <N> (source_type=standalone)
Skipped (non-standalone): <N>
With match: <N> (skill=<N>, tile=<N>)
No match: <N>
Search errors: <N>
Output: <path>Needs only discovery.json and network access to api.tessl.io:
python3 <skill-dir>/scripts/registry_search.py \
--discovery /path/to/.skill-insights/discovery.json \
--output /tmp/registry-search.jsonNo other phases need to have run.