Search your own Claude Code history to find out whether you have asked for something before — and see what was actually done each time.
One dependency. No network calls. Everything stays on your machine.
You ask for the same thing over and over without noticing, because you phrase it differently every time. "reset the test database", "wipe the db again", "do the usual cleanup before the demo" — three ways of saying one thing, spread over two months.
skillmine finds those, and more importantly shows what the assistant actually
ran in response, plus how you reacted. That is the material a reusable skill
is made of. A skill written from your prompts alone just restates the request.
Requires Go. The plugin ships source, not binaries.
Claude Code
claude plugin marketplace add MattK97/skillmine
claude plugin install skillmine@skillmineStart a new session and type /skillmine. No local clone needed — Claude Code
fetches the repo and keeps it updated.
On first use the skill builds the binary into $(go env GOPATH)/bin from the
plugin's own checkout, so nothing extra is downloaded.
Verify
claude plugin listUpdate
claude plugin marketplace update skillmine
claude plugin install skillmine@skillmineUninstall
claude plugin uninstall skillmine
claude plugin marketplace remove skillmineOr keep it and turn it off: claude plugin disable skillmine.
Command line only
go install github.com/MattK97/skillmine@latestgo install writes to $(go env GOPATH)/bin, which is not on PATH by default.
Either add it, or install somewhere that already is:
GOBIN=~/.local/bin go install github.com/MattK97/skillmine@latestCursor, Zed, Copilot, and other agent-skills harnesses
skills/skillmine/SKILL.md is a plain Agent Skill, so any harness that reads the
format can use it. It needs the binary on PATH, so install that first (see
above), then drop the skill folder where your agent looks:
git clone https://github.com/MattK97/skillmine
cp -R skillmine/skills/skillmine ~/.cursor/skills/ # or ~/.copilot/skills, ~/.config/zed/skills, ...Or with the skills CLI:
npx skills add MattK97/skillmineThe skill's build step looks for the plugin checkout and will not find one here,
so make sure skillmine resolves on PATH before invoking it.
/skillmine missing from autocomplete. Start a new session; the plugin index
is read once at startup.
An update does not show up. Claude Code does not re-check the remote on its
own. Run claude plugin marketplace update skillmine, then reinstall and restart.
skillmine -query "reset the database"Query: "reset the database"
1. 0.460 2026-04-01 demo-app do the usual database reset before I record the demo video
2. 0.418 2026-03-02 demo-app reset the test database, keep only my user account and phone number
3. 0.219 2026-04-20 demo-app clean the database, products and the message history need to go
4 prompts on 3 separate days, over a 50-day window (2026-03-02 .. 2026-04-20), 1 project
recurring. Read the matched prompts before acting: are they the same request
phrased differently, or unrelated work that shares a topic? Only the first makes a skill.
Add -evidence to see what happened next:
[1] 2026-04-01 ASKED: database cleanup: drop products, vehicles and conversations…
-> Bash {"command": "psql $DATABASE_URL -c 'select count(*) from products'"}
-> Bash {"command": "psql $DATABASE_URL -c 'begin; delete from products; …
USER REPLIED: no wait, you also need to purge the storage bucket
-> Bash {"command": "psql $DATABASE_URL -c 'delete from storage_objects …
USER REPLIED: perfect, that is it
(no tool calls)
That USER REPLIED line is the point. The correction says what the first attempt
missed, so the skill you write from it starts where the last run finished.
For agents, -format json emits the same data with a precomputed verdict.
| flag | default | meaning |
|---|---|---|
-query |
— | what to look for (required) |
-dir |
~/.claude/projects |
where transcripts live |
-top |
15 | how many hits to show |
-min-score |
0.10 | similarity threshold, 0..1 |
-format |
text |
text or json |
-evidence |
off | include tool calls and your replies |
-exclude-session |
$CLAUDE_CODE_SESSION_ID |
session to leave out |
-exclude-session defaults to the conversation the command is running inside.
Without it, a search started mid-conversation matches its own prompts from a few
minutes ago, and whatever you are discussing right now looks like a long-standing
habit. Pass an empty value to search everything.
Note that excluding documents changes IDF, so hit counts between two runs with different corpora are not directly comparable — a term's weight depends on how many documents contain it.
Truncating the display with -top never changes the summary — it is always
computed over every hit above the threshold.
It reports that a query matched N prompts on D separate days. Days, not sessions: a Claude Code session id survives resumption, so on a real corpus three of four sessions spanned multiple days, making session counts useless as a proxy for separate occasions.
It deliberately stops short of saying "make a skill out of this". Whether the
matches are one request phrased differently or unrelated work sharing a subject
is a semantic judgement, and two attempts to measure it from the text failed —
mean pairwise similarity within the hit set ranked a pure topic search
(flutter, 0.160) above a genuine repeated request (reset database, keep the user, 0.089). So the tool reports the spread and hands the judgement over.
One-word queries are flagged (broad_query) for the same reason: supabase
returns fifteen matches across nine days that share nothing but a product name.
skills/skillmine/SKILL.md teaches an agent to run the tool on its own: derive a
query from the current conversation, check whether the topic repeats, pull the
evidence, and draft a skill from it. It works with anything that can run a shell
command — Claude Code, and other agent CLIs alike. No MCP server needed.
TF-IDF with cosine similarity over the user prompts in
~/.claude/projects/**/*.jsonl.
The method was chosen by measurement. On a set of seven real prompts asking for the same thing in different words:
| method | result |
|---|---|
| Jaccard over word trigrams | 0 of 21 pairs linked |
| Jaccard over character 4-grams | 2 of 21 |
| Jaccard over 5-character stems | 3 of 21 |
| TF-IDF + cosine | 6 of 7 targets retrieved, no false positives |
Jaccard fails because it weighs filler words like "okay" and "actually" the same as "cleanup" and "database". Conversational prompts are mostly filler, so the signal drowns. IDF suppresses terms that appear everywhere.
Text is lowercased, stripped of diacritics, split on non-alphanumerics and truncated to 6-character stems. Each of those choices was measured against the golden test rather than assumed, and two of them are less obvious than they look.
Diacritic folding is the single most important step. On a Polish corpus,
removing it drops retrieval from 6 of 7 targets to 2 of 7. Folding uses Unicode
normalisation (golang.org/x/text) to decompose accented letters and drop the
combining marks — the only dependency in the project, and the reason it is worth
having, since it covers every Latin script rather than a hand-written subset.
Normalisation alone is not enough, though: eight letters do not decompose, because they are distinct letters rather than a base plus an accent. A small replacer catches them:
ł ß æ ø đ ı œ þ
Polish ł is the one that bites. Without that table, łatwo and latwo never
match, and a pure NFD implementation looks correct until someone tests it.
Truncation stemming replaces a real morphological stemmer. It is crude, but it is language-agnostic, which matters when prompts arrive in whatever language the user thinks in — Snowball has no algorithm for several of those, Polish included. Six characters is measured: it retrieved one more paraphrase than no stemming, while five degraded ranking and eight lost recall on short queries.
There is no stop-word list. An earlier version had one. Measured across two corpora it changed no result, because IDF already drives common words to near zero weight, so it was deleted rather than kept as decoration.
testdata/sample.jsonl holds seven paraphrases of one request among 32
distractors, several of which deliberately share database vocabulary.
internal/search/tfidf_test.go asserts two properties: enough of the seven are
retrieved, and the top of the ranking contains no distractors.
The test asserts behaviour, not implementation. Replace the whole similarity
method and it will still tell you whether the replacement is better. It also
pins the threshold, so -min-score is measured rather than guessed.
- Matching is lexical. Two phrasings that share no words will not be linked, even when they mean the same thing. Mixing vocabularies in the query helps.
- No discovery mode. You need a topic, or an agent that derives one from the conversation. Clustering the whole corpus is not implemented.
- File order is treated as chronological. Evidence collection assumes
.jsonlfiles are appended sequentially, which may not hold for branched sessions.
main.go flags, Reporter interface, wiring
internal/transcript/ .jsonl reading, polymorphic content decoding
internal/search/ TF-IDF, cosine similarity, the golden test
internal/evidence/ what ran after a prompt, and how the user replied
internal/report/ text and JSON output
skills/skillmine/SKILL.md instructions for an agent
testdata/sample.jsonl synthetic fixture, no real conversation data
go test ./... -race
go vet ./...The fixture is synthetic on purpose. Transcripts contain private conversations, absolute paths and project identifiers, so none are committed here.
MIT