Mine Transcripts
A word or phrase a person keeps reaching for is a procedure they keep
re-explaining. This skill finds that repetition in your own transcripts and
turns it into a ranked list of candidates with evidence — not a skill.
Every other skill in a typical collection encodes a known practice; this one
finds what has not been named yet, so that noticing does not depend on
someone happening to notice.
Reach for this when
- You are deliberately reviewing your own transcripts for skill
opportunities — a periodic pass, done on purpose.
Do not reach for this when
- Mid-task, because you or the user just said "we keep doing this." That
observation is worth a note for the next review, not a mining run right
now. A skill that fired on that sentence would interrupt constantly and
would still only produce a candidate list, not the fix the user wants in
the moment.
- You already know what the next skill should be. Mining finds candidates
when the pattern isn't obvious yet; it adds nothing once it is.
Extraction is a tool's job now, not the model's
Filtering JSONL by shape — which turns are harness noise, which are
site-specific recurring prompts, which look typed versus pasted, which fall
in a date range or match a grep — is deterministic. It has one correct
answer per input, and re-deriving that answer by reading transcripts by eye
every run is a model doing a fixed job forever
(jonhill90/skills#207).
That job now lives in mine_prompts.py, part of the
agent-supervisor project
— not bundled in this collection, because it is harness machinery, not
skill content. Confirm it's on the machine before relying on it (find </path/to/agent-supervisor> -name mine_prompts.py or ask where that repo is
checked out). If it isn't available, say so and stop — walking through its
extraction logic by hand is exactly the fixed-job-forever this rewrite
removes; don't reintroduce it because the tool is momentarily missing.
The tool takes:
--root — transcript root (defaults to ~/.claude/projects; override for
another harness).
--since YYYY-MM-DD — restrict to a date range.
--grep <pattern> — restrict to turns matching a regex.
--typed-only — heuristic: keep only turns that look typed (short, few
newlines, no code fence) and drop turns that look pasted (skill files,
briefs, API docs). It is a heuristic, not a guarantee — of one measured
corpus, roughly 46% of extracted turns were pasted, not typed, so a plain
--grep without --typed-only will hit pasted material too.
--stats — counts per day, plus a typed/pasted split. Use this first, see
Verify the instrument.
--json — machine-readable rows (at, text, typed, source), for
reading programmatically instead of scrolling terminal output.
What it will never do: reason about what a turn means, decide whether a
repeated term is a re-made decision, or tell you a request recurred because
it was never delivered. That's this skill's job, below — it starts only
after the tool's output exists.
What this reads, and what it must never emit
Settle this before running anything — it governs every step below.
- Read-only. Both the extractor and this skill only read transcript
files; neither writes, moves, or deletes them. If the transcript root is
under
$HOME (it usually is), say so out loud before scanning: "reading
~/..., read-only."
- No network calls, ever. Nothing about a transcript's content leaves
the local machine. This includes not pasting raw extracted turns into an
issue, message, or any other outward-facing tool without redacting first.
mine_prompts.py does not redact — it prints operator turns verbatim, by
design, on the assumption its output stays local. Treat every row it
returns as unredacted until you've handled it.
- What you may quote outward. A short (roughly one sentence) trimmed
snippet as evidence for a candidate, hand-checked for anything sensitive
before it leaves this skill's working notes — never a full turn, a full
file, or a full command line.
- What must never leave this machine. Credentials, tokens, or keys;
anything that reads as employer-owned or project-specific material;
third-party names beyond what the evidence strictly needs; private
repository content.
- When in doubt, drop the quote. A candidate is still evidenced by its
term, count, and file-spread alone. A preference can be described
without being quoted — characterise it, don't paste it, whenever the
description carries the same evidence.
- The output stays local until you decide otherwise. Write the ranked
list to a scratch file. Do not open an issue, send a message, or commit
the list anywhere without a separate, deliberate decision to do so — and
redact again at that boundary, because the bar for something staying on
your own disk is lower than the bar for something leaving it.
Verify the instrument before trusting a result
Run mine_prompts.py --stats with no filters before anything else. It must
return a nonzero count for turns you already know exist. If it doesn't,
the tool's harness-noise filter or your --root is wrong — fix that before
believing any downstream zero or thin result.
This was a real failure, not a hypothetical: the extractor's predecessor
returned 80 "candidates" that were all JSON envelope noise (tokens,
cache, sessionId) because raw transcript bytes are JSON-per-line and
most byte volume is API metadata, not anything a human typed
(jonhill90/skills#199).
The instrument was wrong, not the corpus. mine_prompts.py itself refuses
to print an empty, confident-looking result — an unfiltered, no-match run
exits nonzero with a message on stderr telling you to verify the instrument
first — but that only catches the total-absence case. A --grep or
--since filter that quietly returns nothing looks identical to "he never
said that" and to "the filter is wrong"; treat both as open until you've
checked the unfiltered stats.
The corpus itself is also incomplete, separately from any tool bug: one
measured phrase — said and quoted verbatim elsewhere (a personal vault) —
had no matching original turn across 1,150 transcript files (could not
measure — no primary source found to confirm this count). Transcripts
are one record of what was said, not the only one. Before reporting "never
said" or "no evidence of," check whatever other record exists (notes,
vault, prior write-ups) — an absence in mine_prompts.py's output is
evidence about the corpus's coverage, not about what the operator did or
didn't say.
Split deterministic from judgement
Counting, filtering, and typed/pasted classification are deterministic —
mine_prompts.py's job, and its output is inspectable text or JSON, not a
claim you have to trust. Deciding "this term names a procedure" is
judgement, and it happens next, by reading that output, not by re-scanning
transcripts yourself.
1. Run the tool
python3 /path/to/agent-supervisor/scripts/supervisor/mine_prompts.py --stats
python3 /path/to/agent-supervisor/scripts/supervisor/mine_prompts.py --typed-only --json > /tmp/mined.json
python3 /path/to/agent-supervisor/scripts/supervisor/mine_prompts.py --since 2026-08-01 --grep '<term>'
Start broad (--stats, no filters) as the positive control above, then
narrow with --typed-only, --since, and --grep once you trust the
instrument. --typed-only cuts pasted briefs and skill files out of a
plain word search; keep both typed and pasted runs in view when a term
might genuinely appear in either.
2. Cluster and count by eye
Unlike the old bundled script, mine_prompts.py does not compute a
consistency score — it hands you rows, not a ranked table. Group the rows
that share a term or phrase yourself (grep, or read --json output) and
for each candidate note: how many times it appears, across how many
distinct sessions (source), and whether the surrounding text reads the
same way each time.
3. Judge the candidates
For each term worth a look:
- Read the actual rows, not just the count. Do they show the same
kind of explanation each occurrence, or unrelated sentences that happen
to share a word?
- Ask the tool-vs-decision question.
gh, pytest, git recur
because they're invoked, not because a decision gets re-made — not a
candidate. A term can also name a decision-shaped use of a tool
("always confirm before force-push") that no denylist catches; judge the
use, not just the noun.
- Ask the decision-vs-boilerplate question — this is what a tool
cannot do. A phrase repeated across many dispatch briefs or templates
can look exactly like a re-made decision by count and file-spread alone.
Tell them apart by reading what surrounds the phrase each time: template
boilerplate is copied verbatim, same wording, same slot, from a fixed
source (a brief template, a prompt skeleton); a re-made decision is
re-derived — worded differently each time, appearing in reasoning or
correction, not in a fixed template slot. This distinction was a real
finding in a prior run and the reason judgement stays a model's job
here.
- Check whether a request recurred because it was never delivered,
not because it's a recurring practice. A term repeated across sessions
can mean "the same fix keeps getting asked for" rather than "the same
procedure keeps getting used" — read whether the surrounding turns show
the request being satisfied, or being asked again.
- Check whether a decision was settled and later reopened, and
whether a correction repeats. Both are visible only in the surrounding
text, not in the count.
- Check it isn't already a skill. If an existing skill already covers
the term, that's evidence the skill's
description is doing its job,
not a new candidate.
- Drop what you can't evidence. A term with a few occurrences and no
readable pattern in the rows is a lead, not a candidate — leave it out
rather than padding the list.
4. Report the ranked list
One entry per surviving candidate:
- the term or phrase
- count and distinct-source spread
- one sentence on why it looks like a re-made decision rather than a
repeated tool call or repeated boilerplate
- one or two hand-checked, trimmed snippets as evidence (see
What this reads, and what it must never emit)
This list is the deliverable. It names candidates for the next skill; it is
not one. Turning a candidate into a skill is a separate, deliberate step —
in this collection, that means opening an issue the way
jonhill90/skills#136
records distill having been proposed, not filing one automatically from
this run.
Worked example
references/eval-case.md walks a small,
synthetic set of extracted rows through the judgement steps above —
including a term that's boilerplate, one that's a repeated tool call, and
one that's a genuine re-made decision — with the expected classification
for each. Use it to sanity-check that this skill's judgement guidance
actually discriminates before trusting it on a real transcript set.
Notes
- This skill is public opt-in, not part of any default roster — rostering a
skill and adding it here are separate decisions.
mine_prompts.py lives outside this collection because it's harness
machinery (transcript root, site-specific exclude list) not skill
content. If a project needs an extractor bundled instead of external,
that's a different skill for a different repository, not a reason to
re-embed extraction logic here.
1---2name: mine-transcripts3description: Mine your own agent transcripts for vocabulary you keep reaching for, to find candidates for the next skill — not to write one. Invokes the external mine_prompts.py extractor, then applies judgement the tool can't — separating a re-made decision from a repeated tool call or repeated boilerplate. User-invoked only, for a deliberate periodic review; never fire this mid-task just because something feels repetitive.4---56# Mine Transcripts78A word or phrase a person keeps reaching for is a procedure they keep9re-explaining. This skill finds that repetition in your own transcripts and10turns it into a ranked list of **candidates with evidence** — not a skill.11Every other skill in a typical collection encodes a known practice; this one12finds what has not been named yet, so that noticing does not depend on13someone happening to notice.1415## Reach for this when1617- You are deliberately reviewing your own transcripts for skill18 opportunities — a periodic pass, done on purpose.1920## Do not reach for this when2122- Mid-task, because you or the user just said "we keep doing this." That23 observation is worth a note for the next review, not a mining run right24 now. A skill that fired on that sentence would interrupt constantly and25 would still only produce a candidate list, not the fix the user wants in26 the moment.27- You already know what the next skill should be. Mining finds candidates28 when the pattern isn't obvious yet; it adds nothing once it is.2930## Extraction is a tool's job now, not the model's3132Filtering JSONL by shape — which turns are harness noise, which are33site-specific recurring prompts, which look typed versus pasted, which fall34in a date range or match a grep — is deterministic. It has one correct35answer per input, and re-deriving that answer by reading transcripts by eye36every run is a model doing a fixed job forever37([jonhill90/skills#207](https://github.com/jonhill90/skills/issues/207)).3839That job now lives in `mine_prompts.py`, part of the40[`agent-supervisor`](https://github.com/jonhill90/agent-supervisor) project41— **not bundled in this collection**, because it is harness machinery, not42skill content. Confirm it's on the machine before relying on it (`find43</path/to/agent-supervisor> -name mine_prompts.py` or ask where that repo is44checked out). If it isn't available, say so and stop — walking through its45extraction logic by hand is exactly the fixed-job-forever this rewrite46removes; don't reintroduce it because the tool is momentarily missing.4748The tool takes:4950- `--root` — transcript root (defaults to `~/.claude/projects`; override for51 another harness).52- `--since YYYY-MM-DD` — restrict to a date range.53- `--grep <pattern>` — restrict to turns matching a regex.54- `--typed-only` — heuristic: keep only turns that look typed (short, few55 newlines, no code fence) and drop turns that look pasted (skill files,56 briefs, API docs). It is a heuristic, not a guarantee — of one measured57 corpus, roughly 46% of extracted turns were pasted, not typed, so a plain58 `--grep` without `--typed-only` will hit pasted material too.59- `--stats` — counts per day, plus a typed/pasted split. Use this first, see60 [Verify the instrument](#verify-the-instrument-before-trusting-a-result).61- `--json` — machine-readable rows (`at`, `text`, `typed`, `source`), for62 reading programmatically instead of scrolling terminal output.6364What it will never do: reason about what a turn *means*, decide whether a65repeated term is a re-made decision, or tell you a request recurred because66it was never delivered. That's this skill's job, below — it starts only67after the tool's output exists.6869## What this reads, and what it must never emit7071Settle this before running anything — it governs every step below.7273- **Read-only.** Both the extractor and this skill only read transcript74 files; neither writes, moves, or deletes them. If the transcript root is75 under `$HOME` (it usually is), say so out loud before scanning: *"reading76 `~/...`, read-only."*77- **No network calls, ever.** Nothing about a transcript's content leaves78 the local machine. This includes not pasting raw extracted turns into an79 issue, message, or any other outward-facing tool without redacting first.80 `mine_prompts.py` does not redact — it prints operator turns verbatim, by81 design, on the assumption its output stays local. Treat every row it82 returns as unredacted until you've handled it.83- **What you may quote outward.** A short (roughly one sentence) trimmed84 snippet as evidence for a candidate, hand-checked for anything sensitive85 before it leaves this skill's working notes — never a full turn, a full86 file, or a full command line.87- **What must never leave this machine.** Credentials, tokens, or keys;88 anything that reads as employer-owned or project-specific material;89 third-party names beyond what the evidence strictly needs; private90 repository content.91- **When in doubt, drop the quote.** A candidate is still evidenced by its92 term, count, and file-spread alone. A preference can be *described*93 without being *quoted* — characterise it, don't paste it, whenever the94 description carries the same evidence.95- **The output stays local until you decide otherwise.** Write the ranked96 list to a scratch file. Do not open an issue, send a message, or commit97 the list anywhere without a separate, deliberate decision to do so — and98 redact again at that boundary, because the bar for something staying on99 your own disk is lower than the bar for something leaving it.100101## Verify the instrument before trusting a result102103Run `mine_prompts.py --stats` with no filters before anything else. It must104return a nonzero count for turns you already know exist. If it doesn't,105the tool's harness-noise filter or your `--root` is wrong — fix that before106believing any downstream zero or thin result.107108This was a real failure, not a hypothetical: the extractor's predecessor109returned 80 "candidates" that were all JSON envelope noise (`tokens`,110`cache`, `sessionId`) because raw transcript bytes are JSON-per-line and111most byte volume is API metadata, not anything a human typed112([jonhill90/skills#199](https://github.com/jonhill90/skills/issues/199)).113The instrument was wrong, not the corpus. `mine_prompts.py` itself refuses114to print an empty, confident-looking result — an unfiltered, no-match run115exits nonzero with a message on stderr telling you to verify the instrument116first — but that only catches the *total-absence* case. A `--grep` or117`--since` filter that quietly returns nothing looks identical to "he never118said that" and to "the filter is wrong"; treat both as open until you've119checked the unfiltered stats.120121**The corpus itself is also incomplete**, separately from any tool bug: one122measured phrase — said and quoted verbatim elsewhere (a personal vault) —123had **no matching original turn** across 1,150 transcript files (could not124measure — no primary source found to confirm this count). Transcripts125are one record of what was said, not the only one. Before reporting "never126said" or "no evidence of," check whatever other record exists (notes,127vault, prior write-ups) — an absence in `mine_prompts.py`'s output is128evidence about the corpus's coverage, not about what the operator did or129didn't say.130131## Split deterministic from judgement132133Counting, filtering, and typed/pasted classification are deterministic —134`mine_prompts.py`'s job, and its output is inspectable text or JSON, not a135claim you have to trust. Deciding "this term names a procedure" is136judgement, and it happens next, by reading that output, not by re-scanning137transcripts yourself.138139### 1. Run the tool140141```bash142python3 /path/to/agent-supervisor/scripts/supervisor/mine_prompts.py --stats143python3 /path/to/agent-supervisor/scripts/supervisor/mine_prompts.py --typed-only --json > /tmp/mined.json144python3 /path/to/agent-supervisor/scripts/supervisor/mine_prompts.py --since 2026-08-01 --grep '<term>'145```146147Start broad (`--stats`, no filters) as the positive control above, then148narrow with `--typed-only`, `--since`, and `--grep` once you trust the149instrument. `--typed-only` cuts pasted briefs and skill files out of a150plain word search; keep both typed and pasted runs in view when a term151might genuinely appear in either.152153### 2. Cluster and count by eye154155Unlike the old bundled script, `mine_prompts.py` does not compute a156consistency score — it hands you rows, not a ranked table. Group the rows157that share a term or phrase yourself (grep, or read `--json` output) and158for each candidate note: how many times it appears, across how many159distinct sessions (`source`), and whether the surrounding text reads the160same way each time.161162### 3. Judge the candidates163164For each term worth a look:1651661. **Read the actual rows**, not just the count. Do they show the same167 kind of explanation each occurrence, or unrelated sentences that happen168 to share a word?1692. **Ask the tool-vs-decision question.** `gh`, `pytest`, `git` recur170 because they're invoked, not because a decision gets re-made — not a171 candidate. A term can also name a decision-shaped *use* of a tool172 ("always confirm before force-push") that no denylist catches; judge the173 use, not just the noun.1743. **Ask the decision-vs-boilerplate question — this is what a tool175 cannot do.** A phrase repeated across many dispatch briefs or templates176 can look exactly like a re-made decision by count and file-spread alone.177 Tell them apart by reading what surrounds the phrase each time: template178 boilerplate is copied verbatim, same wording, same slot, from a fixed179 source (a brief template, a prompt skeleton); a re-made decision is180 re-derived — worded differently each time, appearing in reasoning or181 correction, not in a fixed template slot. This distinction was a real182 finding in a prior run and the reason judgement stays a model's job183 here.1844. **Check whether a request recurred because it was never delivered**,185 not because it's a recurring practice. A term repeated across sessions186 can mean "the same fix keeps getting asked for" rather than "the same187 procedure keeps getting used" — read whether the surrounding turns show188 the request being satisfied, or being asked again.1895. **Check whether a decision was settled and later reopened**, and190 whether a correction repeats. Both are visible only in the surrounding191 text, not in the count.1926. **Check it isn't already a skill.** If an existing skill already covers193 the term, that's evidence the skill's `description` is doing its job,194 not a new candidate.1957. **Drop what you can't evidence.** A term with a few occurrences and no196 readable pattern in the rows is a lead, not a candidate — leave it out197 rather than padding the list.198199### 4. Report the ranked list200201One entry per surviving candidate:202203- the term or phrase204- count and distinct-source spread205- one sentence on *why* it looks like a re-made decision rather than a206 repeated tool call or repeated boilerplate207- one or two hand-checked, trimmed snippets as evidence (see208 [What this reads, and what it must never emit](#what-this-reads-and-what-it-must-never-emit))209210This list is the deliverable. It names candidates for the next skill; it is211not one. Turning a candidate into a skill is a separate, deliberate step —212in this collection, that means opening an issue the way213[jonhill90/skills#136](https://github.com/jonhill90/skills/issues/136)214records `distill` having been proposed, not filing one automatically from215this run.216217## Worked example218219[`references/eval-case.md`](references/eval-case.md) walks a small,220synthetic set of extracted rows through the judgement steps above —221including a term that's boilerplate, one that's a repeated tool call, and222one that's a genuine re-made decision — with the expected classification223for each. Use it to sanity-check that this skill's judgement guidance224actually discriminates before trusting it on a real transcript set.225226## Notes227228- This skill is public opt-in, not part of any default roster — rostering a229 skill and adding it here are separate decisions.230- `mine_prompts.py` lives outside this collection because it's harness231 machinery (transcript root, site-specific exclude list) not skill232 content. If a project needs an extractor bundled instead of external,233 that's a different skill for a different repository, not a reason to234 re-embed extraction logic here.