CCGM Doctor
Audit tool for Claude Code installs. Three subcommands: check-resolvable (hook refs, command descriptions, script refs), dry (lexical overlap between command descriptions), and resolver-eval (runs a routing suite of {intent, expected} assertions against keyword-overlap scoring). Ships a default routing suite covering common slash commands.
Tags
README
ccgm-doctor
Audit tool for Claude Code installs. Reports dark or broken entries so they can be fixed before a user hits them.
What It Does
Installs a ccgm-doctor CLI. Subcommands:
check-resolvable— reachability audit (hook refs, command descriptions, script refs)dry— DRY/overlap audit (pairs of commands whose triggers are lexically similar)resolver-eval— run a routing suite of{intent, expected}assertions
check-resolvable
Walks a Claude install and reports three classes of issue:
| Check | Severity | What it catches |
|---|---|---|
hook-refs |
error | settings.json hook command points at a file that does not exist |
command-descriptions |
warn | command .md has no frontmatter description: and no first-line heading (the model will not reliably discover it) |
script-refs |
error | a command's bash fenced block invokes a ccgm-* script that does not exist on PATH or in {claude_dir}/bin |
Each finding includes: check name, severity, path, and a one-line detail.
dry
Compares every pair of command trigger descriptions using Jaccard similarity over content tokens (stopwords and short tokens filtered out). Pairs above the threshold are flagged as likely ambiguous routing candidates.
| Check | Severity | What it catches |
|---|---|---|
dry-overlap |
warn | two commands whose trigger descriptions share > threshold tokens (default 0.5) |
Lexical overlap is a conservative signal — it catches copy-paste descriptions and near-duplicates, not semantic synonyms. For semantic routing analysis, use resolver-eval (below) — it is better suited to paraphrase-style overlap.
resolver-eval
Runs a suite of {intent, expected} assertions against the commands dir. For each intent, a keyword-overlap scorer ranks candidate commands by Jaccard similarity between intent tokens and (description + filename stem) tokens. Passes if the expected command appears in the top k candidates.
[PASS] stage all my changes and commit them
expected: commit
top: commit(0.50)
[FAIL] debug this failing test
expected: debug
top: user-test(0.12)
Suite format (JSON array):
[
{"intent": "stage all my changes and commit them", "expected": "commit"},
{"intent": "review this pull request", "expected": "review"}
]
The module ships evals/routing.json as a default suite covering ~18 common intents. Extend it by pointing at your own file with --suite.
Usage
# Reachability audit
ccgm-doctor check-resolvable
ccgm-doctor check-resolvable --claude-dir /path/to/.claude
ccgm-doctor check-resolvable --json
# DRY audit
ccgm-doctor dry
ccgm-doctor dry --threshold 0.3 # flag more overlapping pairs
ccgm-doctor dry --threshold 0.8 # only flag near-duplicates
ccgm-doctor dry --json
# Routing assertions
ccgm-doctor resolver-eval # uses default bundled suite
ccgm-doctor resolver-eval --suite my-evals.json # your own suite
ccgm-doctor resolver-eval --top-k 3 # pass if expected is in top 3
ccgm-doctor resolver-eval --json
Exit codes:
0— no issues1— issues found (see output)2— environment error (e.g.,--claude-dirdoes not exist)
Design Notes
The checks are pure functions of the filesystem. They run in milliseconds and require no model calls. This is intentional: the point of this tool is to catch deterministic reachability problems before they become silent drift. A model-backed check would be appropriate for ambiguous trigger-overlap problems (see #385 DRY audit and #386 resolver evals).
What's covered
hook-refs: parsessettings.json, walks everyhooks[event][].hooks[].command, extracts path-like tokens, expands$HOME/~, checks existence.command-descriptions: prefers YAML frontmatterdescription:(how Claude Code advertises commands). Falls back to first-line Markdown heading for CCGM-style commands. Flags absent or suspiciously short (< 10 chars).script-refs: only scans fenced bash/sh blocks, not prose, so directory names like~/code/ccgm-repos/ccgm-1do not false-flag. Checks PATH +{claude_dir}/binfor executability.
What's not covered (yet)
- Orphan detection — "script in
bin/that no command or hook references". CCGM does not track install provenance, so detecting orphans from source-module removal is non-trivial. Deferred. - Model-backed routing — the current
resolver-evalis a structural scorer. The model may choose differently, especially on paraphrases or short intents. A future enhancement can invokeclaude -por the API to ask the model directly and compare — the eval file format and pass contract stay the same.
Manual Installation
mkdir -p ~/.claude/bin
cp bin/ccgm-doctor ~/.claude/bin/ccgm-doctor
chmod +x ~/.claude/bin/ccgm-doctor
mkdir -p ~/.claude/lib
cp lib/doctor.py ~/.claude/lib/doctor.py
mkdir -p ~/.claude/evals
cp evals/routing.json ~/.claude/evals/routing.json
ccgm-doctor expects doctor.py to sit in ../lib/ relative to the executable. If you install it elsewhere, adjust sys.path accordingly or run via python3 -m doctor with the lib dir on your PYTHONPATH.
Files
| File | Description |
|---|---|
bin/ccgm-doctor |
Python CLI that dispatches subcommands |
lib/doctor.py |
Pure check functions — take paths, return findings |
evals/routing.json |
Default routing suite for resolver-eval |
tests/test_doctor.py |
47 unit tests covering all checks with tempdir fixtures |
Will install
| Path | Action | Target | Type |
|---|---|---|---|
bin/ccgm-doctor | → | bin/ccgm-doctor | script |
lib/doctor.py | → | lib/doctor.py | lib |
evals/routing.json | → | evals/routing.json | doc |
Dependencies
No dependencies.
Required by
No other module depends on this one.
Included in presets
Install this module
Agent prompt
Recommended for agent users -- hands the whole install off to your assistant.
Fetch https://cd23a9be.ccgm-site.pages.dev/modules/ccgm-doctor.md and install this module into my Claude Code setup.
Native plugin marketplace
One command via the native plugin marketplace -- additive, does not merge settings.json.
claude plugin install ccgm-doctor@ccgm
The marketplace path is additive, not a replacement: it installs commands, agents, and skills as native plugin components, but it does not perform the bash installer's deep settings.json merge, and it does not write the always-loaded global CLAUDE.md context. Rules are only injected via an opt-in SessionStart hook rather than being auto-loaded. Use the bash installer when those pieces matter to you.
Files
lib (1)
lib/doctor.py
"""
ccgm-doctor checks: pure functions that take paths and return findings.
A finding is a dict:
{"check": str, "severity": "warn"|"error", "path": str, "detail": str}
The CLI in bin/ccgm-doctor composes these into a report. Checks are pure so
they can be tested in isolation with tempdir fixtures (see tests/test_doctor.py).
"""
from __future__ import annotations
import json
import os
import re
import shlex
from pathlib import Path
from typing import Iterable
Finding = dict
# Paths like $HOME/.claude/hooks/foo.py, ~/.claude/hooks/foo.py, or absolute paths.
_PATH_TOKEN_RE = re.compile(r'(?:\$HOME|~|/)[^\s"\';]+')
# Fenced bash block: the only context where `ccgm-*` tokens are treated as
# script invocations. Scanning outside bash blocks picks up directory names
# like `ccgm-repos/` and false-flags them.
_BASH_BLOCK_RE = re.compile(r'```(?:bash|sh|shell)\n(.*?)```', re.DOTALL)
# Inside a bash block, a `ccgm-*` token preceded by a non-path character and
# followed by whitespace, end-of-line, or shell punctuation is a script
# invocation. The lookbehind excludes `/` and `-` so path segments like
# `code/ccgm-repos/ccgm-1` stay filtered.
_SCRIPT_REF_RE = re.compile(
r'(?:^|(?<=[\s`;|&()$]))(ccgm-[a-z0-9][a-z0-9-]*)(?=[\s;|&()<>]|$)',
re.MULTILINE,
)
def expand_path(raw: str, home: Path) -> Path:
"""
Expand $HOME, ${HOME}, and leading ~ to the given `home`. Relative paths
become absolute against `home`.
`home` here is the user's actual HOME dir (e.g. /Users/foo), NOT the
Claude install dir (~/.claude). Hook commands in settings.json
reference paths via $HOME and expect it to expand to the OS HOME.
"""
expanded = raw.replace("$HOME", str(home)).replace("${HOME}", str(home))
if expanded.startswith("~"):
expanded = str(home) + expanded[1:]
p = Path(expanded)
if not p.is_absolute():
p = home / p
return p
def _iter_hook_commands(settings: dict) -> Iterable[tuple[str, str]]:
"""Yield (event_name, command_string) for every hook entry in settings.json."""
hooks = settings.get("hooks") or {}
if not isinstance(hooks, dict):
return
for event, entries in hooks.items():
if not isinstance(entries, list):
continue
for entry in entries:
inner = entry.get("hooks") if isinstance(entry, dict) else None
if not isinstance(inner, list):
continue
for hook in inner:
if not isinstance(hook, dict):
continue
cmd = hook.get("command")
if isinstance(cmd, str) and cmd.strip():
yield event, cmd
def check_hook_refs(settings_path: Path, user_home: Path) -> list[Finding]:
"""
Verify every hook command references an existing file.
`user_home` is the user's OS home (for $HOME / ~ expansion). The claude
install dir usually lives AT `user_home/.claude` and its own settings.json
references paths back through $HOME.
"""
findings: list[Finding] = []
if not settings_path.exists():
return findings
try:
settings = json.loads(settings_path.read_text())
except json.JSONDecodeError as e:
findings.append({
"check": "hook-refs",
"severity": "error",
"path": str(settings_path),
"detail": f"settings.json is not valid JSON: {e}",
})
return findings
for event, cmd in _iter_hook_commands(settings):
# Extract path-like tokens. A hook command typically has one file
# path; we check every path-like token to be safe.
paths = _PATH_TOKEN_RE.findall(cmd)
if not paths:
continue
for raw in paths:
# Strip any trailing shell-like characters that slipped into the regex match.
raw = raw.rstrip(".,;)")
resolved = expand_path(raw, user_home)
if not resolved.exists():
findings.append({
"check": "hook-refs",
"severity": "error",
"path": str(resolved),
"detail": f"{event} hook references missing file: {cmd.strip()}",
})
return findings
_FRONTMATTER_DESC_RE = re.compile(r'^description:\s*(.+?)\s*$', re.MULTILINE)
def _extract_trigger_description(text: str) -> str | None:
"""
Return the text the model will use to decide whether to reach for this
command. Two valid sources, in priority order:
1. YAML frontmatter `description:` field (how Claude Code advertises
commands in the slash-command picker).
2. First-line Markdown heading (used by CCGM's hand-written commands
that do not have frontmatter).
Returns None when neither is present.
"""
stripped = text.lstrip("\n")
if stripped.startswith("---\n"):
# Find the closing --- on its own line.
end_idx = stripped.find("\n---", 4)
if end_idx != -1:
frontmatter = stripped[4:end_idx]
m = _FRONTMATTER_DESC_RE.search(frontmatter)
if m:
desc = m.group(1).strip().strip('"').strip("'")
return desc if desc else None
first_line = next((ln for ln in text.splitlines() if ln.strip()), "")
if first_line.startswith("#"):
heading = first_line.lstrip("#").strip()
return heading if heading else None
return None
def check_command_descriptions(commands_dir: Path) -> list[Finding]:
"""
Every command file should have a discoverable trigger description so the
model knows when to reach for it. Accepts either a YAML frontmatter
`description:` field or a first-line Markdown heading. Flag files with
neither, or whose description is suspiciously short.
"""
findings: list[Finding] = []
if not commands_dir.is_dir():
return findings
for md in sorted(commands_dir.glob("*.md")):
try:
text = md.read_text()
except OSError as e:
findings.append({
"check": "command-descriptions",
"severity": "error",
"path": str(md),
"detail": f"cannot read command file: {e}",
})
continue
desc = _extract_trigger_description(text)
if desc is None:
findings.append({
"check": "command-descriptions",
"severity": "warn",
"path": str(md),
"detail": "no frontmatter description or first-line heading; model may not discover its trigger",
})
continue
if len(desc) < 10:
findings.append({
"check": "command-descriptions",
"severity": "warn",
"path": str(md),
"detail": f"description is very short ('{desc}'); model may not discover its trigger",
})
return findings
def check_script_refs(commands_dir: Path, claude_dir: Path) -> list[Finding]:
"""
Command markdown that references a `ccgm-*` script should point at a
script that actually exists in `{claude_dir}/bin` or on PATH.
"""
findings: list[Finding] = []
if not commands_dir.is_dir():
return findings
bin_dir = claude_dir / "bin"
# PATH dirs (from env) plus the claude install's bin dir.
path_dirs = [Path(p) for p in os.environ.get("PATH", "").split(":") if p]
path_dirs.append(bin_dir)
def script_exists(name: str) -> bool:
for d in path_dirs:
candidate = d / name
if candidate.exists() and os.access(candidate, os.X_OK):
return True
return False
for md in sorted(commands_dir.glob("*.md")):
try:
text = md.read_text()
except OSError:
continue
# Only scan inside fenced bash blocks. Prose mentions and paths like
# `~/code/ccgm-repos/` should not trigger this check.
referenced: set[str] = set()
for block in _BASH_BLOCK_RE.findall(text):
referenced.update(_SCRIPT_REF_RE.findall(block))
for name in sorted(referenced):
if not script_exists(name):
findings.append({
"check": "script-refs",
"severity": "error",
"path": str(md),
"detail": f"references missing script: {name}",
})
return findings
def run_all_checks(claude_dir: Path, user_home: Path | None = None) -> list[Finding]:
"""
Run every check_resolvable check against a Claude install at `claude_dir`.
`user_home` is the OS home for $HOME / ~ expansion in hook refs. Defaults
to `claude_dir.parent` (since the install conventionally lives at
`~/.claude`, its parent IS the user home).
"""
if user_home is None:
user_home = claude_dir.parent
settings = claude_dir / "settings.json"
commands = claude_dir / "commands"
return (
check_hook_refs(settings, user_home)
+ check_command_descriptions(commands)
+ check_script_refs(commands, claude_dir)
)
# --- DRY / overlap audit ---
# Stopwords for command-trigger tokenization. Words too generic to signal
# command identity (the, a), common CLI/workflow verbs (run, use, add),
# and filler. Kept deliberately tight so meaningful nouns like "calendar",
# "commit", "review" stay in.
_STOPWORDS = frozenset({
"the", "and", "any", "all", "are", "but", "can", "did", "does", "done",
"during", "else", "for", "from", "get", "had", "has", "have", "here",
"how", "its", "just", "make", "must", "new", "not", "one", "only",
"should", "some", "that", "the", "then", "this", "those", "two", "use",
"using", "via", "was", "were", "what", "when", "where", "why", "will",
"with", "would", "you", "your", "set", "run", "task", "command",
"commands", "skill", "workflow", "claude", "code", "agent", "before",
"after", "because", "current", "previous", "next", "these",
})
_TOKEN_RE = re.compile(r'[a-z][a-z0-9]{2,}')
def _trigger_tokens(text: str) -> set[str]:
"""
Extract the identity-bearing tokens of a command's trigger description.
Lowercase, strip punctuation, drop stopwords and tokens shorter than 3.
"""
desc = _extract_trigger_description(text) or ""
return {tok for tok in _TOKEN_RE.findall(desc.lower()) if tok not in _STOPWORDS}
def _jaccard(a: set[str], b: set[str]) -> float:
if not a or not b:
return 0.0
intersection = len(a & b)
union = len(a | b)
return intersection / union if union else 0.0
def check_dry_overlap(commands_dir: Path, threshold: float = 0.5) -> list[Finding]:
"""
Flag pairs of commands whose trigger-description tokens overlap above
`threshold` (Jaccard similarity). Catches ambiguous routing: two skills
the model might plausibly pick for the same intent.
Commands whose trigger description is empty (already flagged by
check_command_descriptions) are skipped so this check stays orthogonal.
"""
findings: list[Finding] = []
if not commands_dir.is_dir():
return findings
commands: list[tuple[Path, set[str]]] = []
for md in sorted(commands_dir.glob("*.md")):
try:
text = md.read_text()
except OSError:
continue
tokens = _trigger_tokens(text)
if tokens:
commands.append((md, tokens))
# Pairwise comparison. O(n^2) but n is small (dozens, not thousands).
for i in range(len(commands)):
for j in range(i + 1, len(commands)):
path_a, tokens_a = commands[i]
path_b, tokens_b = commands[j]
sim = _jaccard(tokens_a, tokens_b)
if sim >= threshold:
shared = sorted(tokens_a & tokens_b)
findings.append({
"check": "dry-overlap",
"severity": "warn",
"path": f"{path_a.name} <-> {path_b.name}",
"detail": (
f"Jaccard={sim:.2f} over {len(shared)} shared tokens "
f"({', '.join(shared[:5])}"
f"{'...' if len(shared) > 5 else ''}). "
"Review for merge/deprecate, or sharpen descriptions."
),
})
return findings
# --- Resolver evals ---
def _tokenize_intent(text: str) -> set[str]:
"""Same tokenizer as _trigger_tokens but over arbitrary text, not a file."""
return {tok for tok in _TOKEN_RE.findall(text.lower()) if tok not in _STOPWORDS}
def _command_tokens(path: Path, text: str) -> set[str]:
"""
Tokens the scorer uses to rank a command against an intent: the trigger
description PLUS the filename stem (since users often hit a command
by name). Skill name tokens help disambiguate when descriptions are thin.
"""
tokens = _trigger_tokens(text)
stem_tokens = {tok for tok in _TOKEN_RE.findall(path.stem.lower()) if tok not in _STOPWORDS}
return tokens | stem_tokens
def score_intent_against_commands(
intent: str, commands_dir: Path
) -> list[tuple[str, float]]:
"""
Rank every command in `commands_dir` against `intent` by Jaccard similarity
of intent tokens against (description + filename stem) tokens.
Returns a list of (command_name, score) pairs sorted high-to-low. Ties at
score 0 are dropped (no evidence either way).
"""
if not commands_dir.is_dir():
return []
intent_tokens = _tokenize_intent(intent)
if not intent_tokens:
return []
scored: list[tuple[str, float]] = []
for md in sorted(commands_dir.glob("*.md")):
try:
text = md.read_text()
except OSError:
continue
cmd_tokens = _command_tokens(md, text)
if not cmd_tokens:
continue
sim = _jaccard(intent_tokens, cmd_tokens)
if sim > 0:
scored.append((md.stem, sim))
scored.sort(key=lambda pair: pair[1], reverse=True)
return scored
def run_resolver_evals(
suite: list[dict], commands_dir: Path, top_k: int = 1
) -> list[dict]:
"""
Run every {intent, expected} entry in `suite` against the commands dir.
Pass rule: `expected` is in the top `top_k` candidates returned by the
scorer. Ties at the k-th position are counted as part of the top (a pass
should not flip based on an arbitrary tiebreak in sorted()).
Returns one result dict per entry:
intent, expected, top_candidates (list of (name, score)), passed (bool)
"""
results: list[dict] = []
for entry in suite:
intent = entry["intent"]
expected = entry["expected"]
ranked = score_intent_against_commands(intent, commands_dir)
if ranked:
cutoff_idx = min(top_k - 1, len(ranked) - 1)
cutoff_score = ranked[cutoff_idx][1]
# All candidates tied at or above the cutoff score.
qualifying = [pair for pair in ranked if pair[1] >= cutoff_score]
passed = any(name == expected for name, _ in qualifying)
top_candidates = qualifying
else:
passed = False
top_candidates = []
results.append({
"intent": intent,
"expected": expected,
"top_candidates": top_candidates,
"passed": passed,
})
return results
script (1)
bin/ccgm-doctor
#!/usr/bin/env python3
"""
ccgm-doctor — audit a Claude Code install for reachability problems.
Subcommands:
check-resolvable Report dark/orphaned/broken entries in the install.
dry Report pairs of commands whose trigger descriptions
overlap (ambiguous routing candidates).
resolver-eval Run a routing eval suite: does each intent resolve
to the expected skill by keyword-overlap scoring?
Usage:
ccgm-doctor check-resolvable [--claude-dir PATH] [--json]
ccgm-doctor dry [--claude-dir PATH] [--threshold N] [--json]
ccgm-doctor resolver-eval [--claude-dir PATH] [--suite PATH] [--top-k N] [--json]
Exit codes:
0 no issues
1 issues found
2 environment error (e.g., --claude-dir does not exist)
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent / "lib"))
import doctor # noqa: E402
def _format_findings(findings: list[dict], scope_label: str) -> None:
if not findings:
print(f"OK: no issues found in {scope_label}")
return
by_severity: dict[str, list[dict]] = {}
for f in findings:
by_severity.setdefault(f["severity"], []).append(f)
for sev in ("error", "warn"):
items = by_severity.get(sev, [])
if not items:
continue
print(f"{sev.upper()} ({len(items)}):")
for f in items:
print(f" [{f['check']}] {f['path']}")
print(f" {f['detail']}")
print()
print(f"Total: {len(findings)} issue(s) across {scope_label}")
def _resolve_claude_dir(raw: str) -> Path | None:
path = Path(raw).expanduser().resolve()
if not path.is_dir():
print(f"error: --claude-dir does not exist: {path}", file=sys.stderr)
return None
return path
def _cmd_check_resolvable(args: argparse.Namespace) -> int:
home = _resolve_claude_dir(args.claude_dir)
if home is None:
return 2
findings = doctor.run_all_checks(home)
if args.json:
json.dump(findings, sys.stdout, indent=2)
sys.stdout.write("\n")
else:
_format_findings(findings, str(home))
return 1 if findings else 0
def _cmd_dry(args: argparse.Namespace) -> int:
home = _resolve_claude_dir(args.claude_dir)
if home is None:
return 2
commands_dir = home / "commands"
findings = doctor.check_dry_overlap(commands_dir, threshold=args.threshold)
if args.json:
json.dump(findings, sys.stdout, indent=2)
sys.stdout.write("\n")
else:
_format_findings(findings, f"{commands_dir} (threshold={args.threshold})")
return 1 if findings else 0
def _cmd_resolver_check(args: argparse.Namespace) -> int:
home = _resolve_claude_dir(args.claude_dir)
if home is None:
return 2
suite_path = Path(args.suite).expanduser().resolve()
if not suite_path.is_file():
print(f"error: --suite does not exist: {suite_path}", file=sys.stderr)
return 2
try:
suite = json.loads(suite_path.read_text())
except json.JSONDecodeError as e:
print(f"error: suite is not valid JSON: {e}", file=sys.stderr)
return 2
if not isinstance(suite, list) or not all(
isinstance(e, dict) and "intent" in e and "expected" in e for e in suite
):
print(
"error: suite must be a JSON array of {\"intent\": str, \"expected\": str} entries",
file=sys.stderr,
)
return 2
commands_dir = home / "commands"
results = doctor.run_resolver_evals(suite, commands_dir, top_k=args.top_k)
passed = sum(1 for r in results if r["passed"])
failed = len(results) - passed
if args.json:
json.dump(results, sys.stdout, indent=2)
sys.stdout.write("\n")
else:
for r in results:
mark = "PASS" if r["passed"] else "FAIL"
cands = ", ".join(f"{n}({s:.2f})" for n, s in r["top_candidates"][:3])
cands = cands or "(no matches)"
print(f" [{mark}] {r['intent']}")
print(f" expected: {r['expected']}")
print(f" top: {cands}")
print()
print(f"Results: {passed} passed, {failed} failed (top_k={args.top_k})")
return 1 if failed else 0
def main() -> int:
parser = argparse.ArgumentParser(
prog="ccgm-doctor",
description="Audit a Claude Code install for reachability problems.",
)
subs = parser.add_subparsers(dest="subcommand", required=True)
chk = subs.add_parser(
"check-resolvable",
help="Report dark/orphaned/broken entries in the install.",
)
chk.add_argument(
"--claude-dir",
default="~/.claude",
help="Path to the Claude Code install dir (default: ~/.claude)",
)
chk.add_argument(
"--json",
action="store_true",
help="Emit findings as a JSON array instead of a human-readable report.",
)
chk.set_defaults(func=_cmd_check_resolvable)
dry = subs.add_parser(
"dry",
help="Report command pairs whose trigger descriptions overlap.",
)
dry.add_argument(
"--claude-dir",
default="~/.claude",
help="Path to the Claude Code install dir (default: ~/.claude)",
)
dry.add_argument(
"--threshold",
type=float,
default=0.5,
help="Jaccard similarity threshold for flagging pairs (default: 0.5)",
)
dry.add_argument(
"--json",
action="store_true",
help="Emit findings as a JSON array instead of a human-readable report.",
)
dry.set_defaults(func=_cmd_dry)
rev = subs.add_parser(
"resolver-eval",
help="Run a routing suite: does each intent resolve to the expected skill?",
)
rev.add_argument(
"--claude-dir",
default="~/.claude",
help="Path to the Claude Code install dir (default: ~/.claude)",
)
rev.add_argument(
"--suite",
default=str(_HERE.parent / "evals" / "routing.json"),
help="Path to the JSON suite file (default: module's bundled routing.json)",
)
rev.add_argument(
"--top-k",
type=int,
default=1,
help="Pass if expected skill appears in the top-k scored candidates (default: 1)",
)
rev.add_argument(
"--json",
action="store_true",
help="Emit results as a JSON array instead of a human-readable report.",
)
rev.set_defaults(func=_cmd_resolver_check)
args = parser.parse_args()
return args.func(args)
if __name__ == "__main__":
sys.exit(main())
doc (1)
evals/routing.json
[
{"intent": "stage all my changes and commit them", "expected": "commit"},
{"intent": "commit push and merge this branch", "expected": "cpm"},
{"intent": "create a pull request for this work", "expected": "pr"},
{"intent": "review this pull request", "expected": "review"},
{"intent": "run a comprehensive security review", "expected": "security-review"},
{"intent": "debug this failing test", "expected": "debug"},
{"intent": "reflect on what I learned in this session", "expected": "reflect"},
{"intent": "consolidate and clean up my learnings", "expected": "consolidate"},
{"intent": "write a retrospective over the last week", "expected": "retro"},
{"intent": "summarize where I left off at session start", "expected": "startup"},
{"intent": "remember this capability and turn it into a permanent skill", "expected": "skillify"},
{"intent": "do deep research across the web on a topic", "expected": "deepresearch"},
{"intent": "audit this codebase for issues", "expected": "audit"},
{"intent": "update the project documentation to match the code", "expected": "docupdate"},
{"intent": "generate playwright tests for a feature", "expected": "e2e"},
{"intent": "check the health of my claude install", "expected": "ccgm-sync"},
{"intent": "brainstorm ideas for a new feature", "expected": "brainstorm"},
{"intent": "submit this chrome extension to the store", "expected": "cws-submit"}
]