skill-usage
Skill0 starsChanged 5 months agoMITView on GitHub
Measure actual Claude Code skill usage from local transcript JSONL. Walks `~/.claude/projects/*/*.jsonl` (and `subagents/*.jsonl` sidechains), attributes messages by `attributionSkill`, sums tokens deduped by `requestId`, and emits a dated `SKILL-USAGE-<date>.json` with per-skill counts and token totals. Real receipts to replace editorial estimates in skill catalogs. Use for "how often have I used each skill", "measure skill usage", or a quarterly pass.
In this repository15 other files it gives its agents.
- ai-codegen-smell-auditSkill
- auditSkill
- dotnet-auditSkill
- llm-injection-auditSkill
- mikko-audit-suiteSkill
- mikko-helpSkill
- mikko-installSkill
- mikko-skillsSkill
- react-anti-patterns-auditSkill
- readme-drift-syncSkill
- security-auditSkill
- session-costSkill
- skill-calibrationSkill
- skills-freshnessSkill
- skills-qualitySkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 4 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 17
Before any public claim about token savings (interview demos, portfolio site, blog posts)
- Distinctive: line 18
After authoring a new skill, once it has been invoked enough times that the cadence claim is non-trivial
- Very specific: line 20
NOT for: real-time monitoring (the JSONL is flushed at session end, sometimes later), per-skill quality assessment (this counts invocations …
- Distinctive: line 53
Read the JSON. Sanity-check:
- Very specific: line 55
Every entry has name, invocations >= 0, tokens_per_use_avg >= 0, uses_per_year (extrapolated).
- Very specific: line 67
Commit the JSON (mikkonumminen.dev tracks SKILL-USAGE-*.json in .claude/agent-verdicts/ via gitignore exception — same pattern as …
- Very specific: line 68
Update docs/SKILLS.md catalog rows from the measured numbers
- Distinctive: line 71
Do NOT auto-commit. Do NOT mutate any other file.
- Very specific: line 131
Skill renamed mid-window: If a skill was renamed (e.g., /audit-old → /audit), both names appear as separate rows.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Skill usage measurement
Replace author-estimated "tokens per use × uses per year" guesses with measured values from real Claude Code sessions.
The Claude Code harness already tags every assistant message inside a skill run with "attributionSkill": "<skill-name>". This skill harvests that signal — no heuristic detection, no slash-command parsing, no fuzzy boundaries. The work is mostly file I/O, which the companion scan.mjs script handles; this SKILL.md is the procedure that drives the script and validates its output.
When to use
- "
/skill-usage", "measure my skill usage", "how many tokens has this skill actually used", "real receipts not estimates" - Quarterly drift-check on the portfolio token-savings figures
- Before any public claim about token savings (interview demos, portfolio site, blog posts)
- After authoring a new skill, once it has been invoked enough times that the cadence claim is non-trivial
NOT for: real-time monitoring (the JSONL is flushed at session end, sometimes later), per-skill quality assessment (this counts invocations and tokens, not outcomes), or detecting which skill should have been used in a given session (the attributionSkill field tells you what was used).
What this skill does
- Walk
~/.claude/projects/*/(every project Claude Code has been used in) and enumerate*.jsonlplus*/subagents/*.jsonlsidechain files. - For each line where
type == "assistant"ANDattributionSkillis set, record{ skill, promptId, requestId, sessionId, timestamp, usage }. - Deduplicate by
requestId(the harness emits two adjacent assistant lines per API call when a message contains both thinking and tool_use blocks — they sharerequestIdandusage). - Group records by
skill, then bysessionIdto count invocations — one(skill, session)pair is one invocation. (The harness only stampspromptIdon user / tool_result lines, not assistant lines, so the same skill run twice in one session is undercounted by design; see the note inscan.mjs.) - Compute per-skill: invocation count in window, average
tokens_per_use(including cache-creation input + output, excluding cache-read which is a hit, not a cost), total tokens in window, and an extrapolateduses_per_yearfrom the in-window rate. - Emit two files in
.claude/agent-verdicts/: the datedSKILL-USAGE-{YYYY-MM-DD}.json(history) and a byte-identicalSKILL-USAGE-LATEST.json(stable filename for consumers like/skill-registry's transcript-measurement overlay).
End-to-end with no user pauses. The companion script does the parsing; the main thread validates the output and reports the summary.
Procedure
1. Run the scanner (main thread)
node ~/.claude/skills/mikko-skill-usage/scan.mjs --window-days 90 --out .claude/agent-verdicts/SKILL-USAGE-$(date +%Y-%m-%d).jsonThe script writes two files: the dated SKILL-USAGE-{YYYY-MM-DD}.json (from --out) and a byte-identical SKILL-USAGE-LATEST.json sibling alongside it — a stable filename that consumers (e.g. /skill-registry's transcript-measurement overlay) read so they don't go stale. The dated file is the archive copy; LATEST always points at the newest run.
Args:
--window-days N(default 90) — only sessions within the last N days count toward invocations and tokens. The 90-day default balances "enough data to extrapolate" vs "stale enough that a year-old burst of usage doesn't dominate the average." Override to 365 for an annual snapshot or 30 for a recent-trend check.--out PATH— output file path. Default writes to the current repo's.claude/agent-verdicts/SKILL-USAGE-{YYYY-MM-DD}.json.--projects-dir PATH— override the default~/.claude/projects/location. Useful for testing against a captured sample.
The scanner prints a one-line summary on success: Wrote SKILL-USAGE-{date}.json — N skills measured across S sessions in {window} days.
2. Validate the output (main thread)
Read the JSON. Sanity-check:
- Every entry has
name,invocations >= 0,tokens_per_use_avg >= 0,uses_per_year(extrapolated). - The skill names present match real skills (cross-check against
.claude/skills/*/SKILL.mdin the consumer repos you care about). total_tokens_in_window≈invocations × tokens_per_use_avg(within rounding).
If a skill is present in ~/.claude/skills/ (or any consumer's .claude/skills/) but missing from the output, the most likely cause is zero invocations in the window. The scanner only reports skills it observed — skills with no recorded use don't get a row. That's intentional: a row with invocations: 0 would imply "we measured zero use" when in fact "we never saw a use" — different epistemic claim.
3. Done
Report the file path and a one-line summary: Wrote SKILL-USAGE-{date}.json — N skills (M invocations, ~T total tokens) across S sessions in {window} days.
The user decides whether to:
- Commit the JSON (mikkonumminen.dev tracks
SKILL-USAGE-*.jsonin.claude/agent-verdicts/via gitignore exception — same pattern asSKILL-REGISTRY-*) - Update
docs/SKILLS.mdcatalog rows from the measured numbers - Re-run
/skill-registryto ingest the measurements as the 5th receipt source
Do NOT auto-commit. Do NOT mutate any other file. The measurement is a snapshot; how it informs other artifacts is a separate decision.
Output schema
{
generated_at: string, // ISO 8601 UTC timestamp
window_days: number, // the --window-days arg used
projects_scanned: number, // count of directories under ~/.claude/projects/
sessions_scanned: number, // count of *.jsonl files (root + subagents) examined
attributed_assistant_messages: number, // total dedup'd assistant lines with attributionSkill set
skills: [{
name: string, // value of attributionSkill, verbatim
invocations: number, // count of distinct sessions for this skill (one (skill, session) = one invocation)
tokens_per_use_avg: number, // mean of per-invocation totals
uses_per_year: number, // (invocations / window_days) * 365, rounded
total_tokens_in_window: number,
annual_total: number, // tokens_per_use_avg * uses_per_year (matches /skill-registry's existing field name)
sample_session_ids: string[], // up to 5 sessionIds for spot-checking
last_invoked: string // ISO 8601 — most recent attributed message timestamp for this skill
}]
}Token accounting convention
Per-invocation tokens sum these usage fields across all messages in that invocation (one (skill, session) group):
input_tokens— fresh input to the APIoutput_tokens— model outputcache_creation_input_tokens— input that becomes cache (paid once when written)
NOT summed:
cache_read_input_tokens— input served from cache (already paid for upstream; ~10× cheaper per token but not free; tracking it would double-count across a single skill's multiple turns)
The convention matches how Anthropic's pricing actually bills cached vs fresh input — caches are a hit, not a cost. If you care about cache efficiency specifically, that's a separate report (the scanner could emit it as a sibling field; out of scope here).
Sub-agent accounting
Sub-agent JSONL files (under <session-id>/subagents/agent-<id>.jsonl) carry the same attributionSkill as the parent session. The scanner walks them as siblings of the root session file and dedupes by requestId so sub-agent tokens are counted once. This matters: a skill like /sync-readmes spawns 6 parallel Sonnet sub-agents, each consuming ~20K tokens — ignoring them would understate the skill's actual cost by 5-10×.
Token expectations
For a 90-day window across ~5 projects with ~100 total sessions:
- File I/O (script): ~5MB of JSONL read off disk; ~10-50ms per file; ~5-30s total wall-clock
- Main-context absorption: ~3K input (script summary line + JSON validation read) + ~1K output (report to user)
- Wall-clock for the model work: under 5s; the heavy lifting is in the script
The script is cheap because the harness already structured the data. Most existing token-measurement attempts re-derive what attributionSkill already gives you.
Cadence: quarterly per repo, ad-hoc before public claims. ~12 uses/year for an actively-maintained portfolio.
Failure modes
~/.claude/projects/not found. Either Claude Code has never run on this machine, or the user hasn't invoked the skill from a Claude Code session yet. The scanner exits 0 with a clear "no projects directory" message rather than failing the build.- JSONL file is partial / mid-write. The last line may be incomplete. The scanner skips malformed lines, counts the skip, and continues. A "skipped N malformed lines" warning surfaces in the script output.
- Same
sessionIdappears in multiple project directories. Sub-agent JSONL files inherit the parent session'ssessionIdbut may live in a different project directory (when the agent'scwddiffers from the parent's). The scanner dedupes bysessionId + requestId, so cross-directory duplicates collapse cleanly. attributionSkillfield absent. Some older transcript formats (pre-attribution) don't carry it. The scanner treats those messages as "no skill" and excludes them — they don't appear in the skills array. If most of your transcripts pre-date the attribution feature, the output will look sparse; rerun once enough new sessions accumulate.- Skill renamed mid-window. If a skill was renamed (e.g.,
/audit-old→/audit), both names appear as separate rows. Manual reconciliation: edit the JSON post-emit, or accept that the renamed skill needs a fresh window to look "real."
Limitations (measured, but not the whole story)
This skill produces an invocation count + token total, not a value claim. It doesn't measure:
- Outcome quality. A skill invoked 50 times producing low-quality output is still 50 invocations.
- User abandonment. A skill invoked and cancelled mid-run still counts as an invocation; tokens consumed up to the cancel point are summed honestly.
- Counterfactual savings. "How many tokens would you have used without this skill?" is unknowable; the scanner only sees what happened, not the parallel-universe alternative.
The output is an honest measurement of one specific quantity (token consumption attributed to a named skill across a time window). Combined with the editorial estimates in docs/SKILLS.md and the registry verdicts, it forms the receipt layer that makes portfolio claims falsifiable — but it's not the only claim worth making about a skill.
Companion script
See scan.mjs in this directory. ~200 lines of Node, no external npm deps. Reads JSONL line-by-line with a streaming parser to keep memory bounded; aggregates in-memory; writes one JSON file at the end.
Freshness check
Staleness checks run by /mikko-skills-freshness on any change to this skill — they assert the skill's load-bearing pieces still ship / stay documented. See that skill for the check vocabulary.
[[check]]
kind = "path_exists"
path = "scan.mjs"
[[check]]
kind = "command_exists"
command = "node"
[[check]]
kind = "file_contains"
path = "SKILL.md"
pattern = "--window-days"
[[check]]
kind = "no_broken_md_links"Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

