Generate README with overhauled skill stats

Amolith created

Change summary

README.md      | 1058 ++++++++++++++++++++++-----------------------------
skill-stats.go |  615 ++++++++++++-----------------
2 files changed, 704 insertions(+), 969 deletions(-)

Detailed changes

README.md πŸ”—

@@ -6,157 +6,18 @@ SPDX-License-Identifier: CC0-1.0
 
 # agent-skills
 
-A collection of [Agent Skills]; structured markdown that extends LLM
-agent capabilities through a standardised format.
+A collection of [Agent Skills]: structured markdown that extends LLM agent
+capabilities through a standardised format. See [Β§ Available
+skills](#available-skills) for the full list.
 
 [Agent Skills]: https://agentskills.io/
 
-## Available Skills
-
-Refer to [Β§ Token stats](#token-stats) for a detailed breakdown of each skill's
-token count, plus overall metadata usage. I've used and tested them most with
-[Crush], [Amp], and [Shelley], and to a lesser degree, [Octofriend] and [Pi].
+I've used and tested them most with [Crush], [Pi], [Amp], and [Shelley].
 
 [Crush]: https://github.com/charmbracelet/crush
+[Pi]: https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent
 [Amp]: https://ampcode.com
 [Shelley]: https://github.com/boldsoftware/shelley
-[Octofriend]: https://github.com/synthetic-lab/octofriend
-[Pi]: https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent
-
-- [addressing-code-review-comments](skills/addressing-code-review-comments/SKILL.md):
-  Finds `CR:` comments in code, gathers feedback, and carries out requested
-  changes.
-- [amoliths-opinions](skills/amoliths-opinions/SKILL.md): Applies Amolith's
-  opinions across project setup, tooling, licensing, dependencies, mise tasks,
-  CLIs, TUIs, MCP servers, web apps, packaging/releases, testing, and
-  Go-specific ecosystem defaults.
-- [ast-grep](skills/ast-grep/SKILL.md): Writes [ast-grep] rules for structural
-  code search and modification.
-- [auditing-repositories](skills/auditing-repositories/SKILL.md): Audits open
-  source repositories for security, privacy, and unexpected behavior.
-- [authoring-skills](skills/authoring-skills/SKILL.md): Creates and reviews
-  Agent Skills following best practices. Covers skill structure, frontmatter,
-  and progressive disclosure patterns.
-- [backing-up-with-keld](skills/backing-up-with-keld/SKILL.md): Writes and
-  manages keld configuration for restic backups. Covers TOML preset structure,
-  split preset composition (`home@cloud`), config file discovery, environment
-  variables, and systemd timer setup.
-- [collaborating-through-pr-pico-sh](skills/collaborating-through-pr-pico-sh/SKILL.md):
-  Collaborates on git patches via [pr.pico.sh], a minimal patchbin service.
-  Covers both contributing and reviewing patch requests using `git format-patch`
-  and `git am`.
-- [computing-golden-ratio-typography](skills/computing-golden-ratio-typography/SKILL.md):
-  Computes Golden Ratio Typography line heights, spacing units, type scales, and
-  readable measures from a font file or explicit font metrics.
-- [cooking](skills/cooking/SKILL.md): Guides home cooking as a technically
-  grounded collaborator. Helps plan meals, use odds and ends, troubleshoot
-  techniques, handle substitutions, and reason through cuisine-specific flavor
-  logic without flattening regional traditions.
-- [creating-tasks-through-lunatask](skills/creating-tasks-through-lunatask/SKILL.md):
-  Creates tasks and handoffs in [Lunatask] via [lune]. Tasks are just tasks.
-  Handoffs capture work to resume later across sessions without filling context
-  windows.
-- [fallback-code-review](skills/fallback-code-review/SKILL.md): Provides a
-  fallback-only external review flow via CLI tools like Amp, CodeRabbit, or
-  Kodus.
-- [formatting-commits](skills/formatting-commits/SKILL.md): Detects a project's
-  commit style from recent history and formats messages accordingly. Supports
-  Conventional Commits and kernel-style imperative commits.
-- [frontend-accessibility](skills/frontend-accessibility/SKILL.md): Strives to
-  generate accessible HTML, React, and frontend code following WCAG 2.2 AA.
-  Prioritizes semantic HTML over ARIA, keyboard navigation, and screen reader
-  compatibility.
-- [handling-customer-data](skills/handling-customer-data/SKILL.md): Queries
-  customer data responsiblyβ€”the agent answers questions about the data without
-  ever seeing it directly.
-- [humanize](skills/humanize/SKILL.md): Removes AI-generated patterns from
-  text like promotional fluff, weasel words, and mechanical sentence structures.
-  Based on Wikipedia's AI Cleanup research. Originally from [blader/humanizer].
-- [ideating-with-bdd](skills/ideating-with-bdd/SKILL.md): Guides collaborative
-  discovery of behaviour through structured conversation, iterating with the user
-  to refine ideas into user stories and Gherkin scenarios. Works for any
-  language or framework.
-- [in-session-review](skills/in-session-review/SKILL.md): Runs an extremely
-  strict maintainability review for abstraction quality, giant files, and
-  spaghetti-condition growth.
-- [initialising-and-updating-agents-md](skills/initialising-and-updating-agents-md/SKILL.md):
-  Analyses a codebase and creates or updates `AGENTS.md` to help future agents
-  work effectively. Discovers commands, conventions, patterns, and gotchas from
-  the project's source and config files.
-- [invoking-subagents](skills/invoking-subagents/SKILL.md): Spawns subagents
-  with restricted tool access for parallel tasks across repositories. Requires
-  [the Pi coding agent][Pi]. Useful for summarizing git history or processing
-  large diffs without filling the main context window.
-- [licensing-with-reuse](skills/licensing-with-reuse/SKILL.md): Manages
-  REUSE-compliant licensing with the `reuse` CLI. Covers `reuse annotate`,
-  `.license` sidecars for prompt files, custom `LicenseRef-...` identifiers,
-  `LICENSES/`, project conventions, and `reuse lint`.
-- [maintaining-aur-packages](skills/maintaining-aur-packages/SKILL.md): Creates
-  and updates AUR packages following Arch packaging standards. Covers PKGBUILDs
-  for source, `-bin`, and `-git` package types, checksums with `updpkgsums`,
-  linting with `namcap`, and `.SRCINFO` generation.
-- [managing-personal-knowledge](skills/managing-personal-knowledge/SKILL.md):
-  Works inside a personal knowledge base as an exacting steward, shaping atomic,
-  concept-oriented notes, a dense wikilink graph, and disciplined tags instead
-  of dumping transcripts. Tool-agnostic across Markdown vaults like Obsidian,
-  Logseq, Roam, and SilverBullet.
-- [monitoring-with-munin](skills/monitoring-with-munin/SKILL.md): Deploys and
-  manages Munin monitoring across servers. Sets up munin-node on hosts, writes
-  plugins, configures masters, and handles alerts.
-- [notifying-through-ntfy](skills/notifying-through-ntfy/SKILL.md): Sends push
-  notifications via [ntfy.sh] when requested, such as at the end of its turn.
-- [rebasing-with-git](skills/rebasing-with-git/SKILL.md): Manages git rebase
-  workflows from simple rebases to the drop-and-repick integration branch
-  pattern. Assumes `upstream` remote for forks and `rerere` enabled. Supports
-  worktrees including `wt`-managed repos.
-- [researching-with-rumilo](skills/researching-with-rumilo/SKILL.md): Dispatches
-  AI research subagents via [rumilo] for web search and repository exploration.
-  Research topics, look up library usage, or explore external codebases without
-  filling the main context window.
-- [scripting-with-go](skills/scripting-with-go/SKILL.md): Creates executable Go
-  scripts using a shell trick (not a true shebang). For automation and tooling
-  outside of Go projects.
-- [testing-with-gocuke-and-gherkin](skills/testing-with-gocuke-and-gherkin/SKILL.md):
-  Drives BDD, red/green TDD, and property-based testing in Go projects using
-  [gocuke] and Gherkin feature files.
-- [toki-pona-dictionary](skills/toki-pona-dictionary/SKILL.md): Searches the
-  [nimi.li] toki pona dictionary by English meaning. Caches word data locally
-  after a one-time fetch, then runs offline searches across definitions and
-  community usage tags.
-- [updating-llm-client-model-lists](skills/updating-llm-client-model-lists/SKILL.md):
-  Synchronizes model configurations across Zed, Crush, Octofriend, and Pi from
-  Plexus' /v1/models endpoint.
-- [using-exe-dev](skills/using-exe-dev/SKILL.md): Guides working with [exe.dev]
-  VMs, which provide Linux VMs with persistent disks, instant HTTPS, and
-  built-in auth via SSH.
-- [using-jujutsu](skills/using-jujutsu/SKILL.md): Guides [jujutsu] (jj)
-  workflows, covering commit selection, change creation, diff reading, and
-  common pitfalls that trip up agents.
-- [working-with-zmx](skills/working-with-zmx/SKILL.md): Manages persistent
-  terminal sessions for background processes, dev servers, and long-running tasks.
-  Creates sessions, runs commands detached, checks output, and writes files into
-  sessions over SSH.
-- [writing-git-tags](skills/writing-git-tags/SKILL.md): Generates git tag
-  annotations from commit history following Semantic Versioning and Conventional
-  Commits via [git-format].
-- [writing-rust](skills/writing-rust/SKILL.md): Writes idiomatic Rust with
-  strict quality gates: `cargo fmt`, `clippy -- -D warnings`, and required
-  tests. Enforces `thiserror` for libs, `anyhow` for apps, warns against common
-  anti-patterns.
-
-[blader/humanizer]: https://github.com/blader/humanizer
-[ast-grep]: https://github.com/ast-grep/ast-grep
-[git-format]: https://git.secluded.site/git-format
-[gocuke]: https://github.com/tenntenn/gocuke
-[lune]: https://git.secluded.site/lune
-[Lunatask]: https://lunatask.app/
-[nimi.li]: https://nimi.li
-[ntfy.sh]: https://ntfy.sh
-[rumilo]: https://git.secluded.site/rumilo
-[synu]: https://git.secluded.site/synu
-[wt]: https://git.secluded.site/wt
-[exe.dev]: https://exe.dev
-[jujutsu]: https://jj-vcs.github.io/jj/latest/
 
 ## Installation
 
@@ -173,9 +34,9 @@ cd agent-skills
 
 ### With Make
 
-Skills install to `~/.agents/skills/` by default. Override by
-setting `SKILL_DIR`. Tab completion works so you don't have to remember
-skill names or copy/paste.
+Skills install to `~/.agents/skills/` by default. Override by setting
+`SKILL_DIR`. Tab completion works so you don't have to remember skill names or
+copy/paste.
 
 ```sh
 # List available skills
@@ -209,12 +70,12 @@ rm ~/.agents/skills/formatting-commits
 
 ### Agent skill directories
 
-| Agent                                   | Dir                   |
-| --------------------------------------- | --------------------- |
-| [Crush], [Octofriend], [Shelley], [Amp] | `~/.agents/skills/`   |
-| [Pi]                                    | `~/.pi/agent/skills/` |
-| Anthropic's Claude Code                 | `~/.claude/skills/`   |
-| OpenAI's Codex                          | `~/.codex/skills/`    |
+| Agent                     | Dir                   |
+| ------------------------- | --------------------- |
+| [Crush], [Shelley], [Amp] | `~/.agents/skills/`   |
+| [Pi]                      | `~/.pi/agent/skills/` |
+| Anthropic's Claude Code   | `~/.claude/skills/`   |
+| OpenAI's Codex            | `~/.codex/skills/`    |
 
 ## Updating
 
@@ -225,6 +86,441 @@ git pull
 
 Symlinked skills update automatically.
 
+## Available skills
+
+Each skill below lists its install command, source path, and token counts. The
+counts are computed by [./skill-stats.go](./skill-stats.go) against
+[Synthetic.new]'s [/messages/count_tokens] endpoint.
+
+[Synthetic.new]: https://synthetic.new
+[/messages/count_tokens]: https://dev.synthetic.new/docs/anthropic/messages/count-tokens
+
+### addressing-code-review-comments
+
+Finds `CR:` comments in code, gathers feedback, and carries out requested
+changes.
+
+- Install with: `make addressing-code-review-comments`
+- Source: [skills/addressing-code-review-comments/](skills/addressing-code-review-comments/)
+- Name/desc: 28 tok
+- SKILL.md: 133 tok
+- Full skill: 161 tok (all `.md` files in the folder)
+
+### amoliths-opinions
+
+Applies Amolith's opinions across project setup, tooling, licensing,
+dependencies, mise tasks, CLIs, TUIs, MCP servers, web apps, packaging/releases,
+testing, and Go-specific ecosystem defaults.
+
+- Install with: `make amoliths-opinions`
+- Source: [skills/amoliths-opinions/](skills/amoliths-opinions/)
+- Name/desc: 50 tok
+- SKILL.md: 271 tok
+- Full skill: 5210 tok (all `.md` files in the folder)
+
+### ast-grep
+
+Writes [ast-grep] rules for structural code search and modification.
+
+[ast-grep]: https://github.com/ast-grep/ast-grep
+
+- Install with: `make ast-grep`
+- Source: [skills/ast-grep/](skills/ast-grep/)
+- Name/desc: 43 tok
+- SKILL.md: 1272 tok
+- Full skill: 2457 tok (all `.md` files in the folder)
+
+### auditing-repositories
+
+Audits open source repositories for security, privacy, and unexpected behavior.
+
+- Install with: `make auditing-repositories`
+- Source: [skills/auditing-repositories/](skills/auditing-repositories/)
+- Name/desc: 32 tok
+- SKILL.md: 480 tok
+- Full skill: 707 tok (all `.md` files in the folder)
+
+### authoring-skills
+
+Creates and reviews Agent Skills following best practices. Covers skill
+structure, frontmatter, and progressive disclosure patterns.
+
+- Install with: `make authoring-skills`
+- Source: [skills/authoring-skills/](skills/authoring-skills/)
+- Name/desc: 24 tok
+- SKILL.md: 401 tok
+- Full skill: 1787 tok (all `.md` files in the folder)
+
+### backing-up-with-keld
+
+Writes and manages keld configuration for restic backups. Covers TOML preset
+structure, split preset composition (`home@cloud`), config file discovery,
+environment variables, and systemd timer setup.
+
+- Install with: `make backing-up-with-keld`
+- Source: [skills/backing-up-with-keld/](skills/backing-up-with-keld/)
+- Name/desc: 27 tok
+- SKILL.md: 796 tok
+- Full skill: 1478 tok (all `.md` files in the folder)
+
+### collaborating-through-pr-pico-sh
+
+Collaborates on git patches via [pr.pico.sh], a minimal patchbin service. Covers
+both contributing and reviewing patch requests using `git format-patch` and
+`git am`.
+
+[pr.pico.sh]: https://pr.pico.sh
+
+- Install with: `make collaborating-through-pr-pico-sh`
+- Source: [skills/collaborating-through-pr-pico-sh/](skills/collaborating-through-pr-pico-sh/)
+- Name/desc: 32 tok
+- SKILL.md: 239 tok
+- Full skill: 775 tok (all `.md` files in the folder)
+
+### computing-golden-ratio-typography
+
+Computes Golden Ratio Typography line heights, spacing units, type scales, and
+readable measures from a font file or explicit font metrics.
+
+- Install with: `make computing-golden-ratio-typography`
+- Source: [skills/computing-golden-ratio-typography/](skills/computing-golden-ratio-typography/)
+- Name/desc: 38 tok
+- SKILL.md: 560 tok
+- Full skill: 598 tok (all `.md` files in the folder)
+
+### cooking
+
+Guides home cooking as a technically grounded collaborator. Helps plan meals,
+use odds and ends, troubleshoot techniques, handle substitutions, and reason
+through cuisine-specific flavor logic without flattening regional traditions.
+
+- Install with: `make cooking`
+- Source: [skills/cooking/](skills/cooking/)
+- Name/desc: 86 tok
+- SKILL.md: 1292 tok
+- Full skill: 12751 tok (all `.md` files in the folder)
+
+### creating-tasks-through-lunatask
+
+Creates tasks and handoffs in [Lunatask] via [lune]. Tasks are just tasks.
+Handoffs capture work to resume later across sessions without filling context
+windows.
+
+[lune]: https://git.secluded.site/lune
+[Lunatask]: https://lunatask.app/
+
+- Install with: `make creating-tasks-through-lunatask`
+- Source: [skills/creating-tasks-through-lunatask/](skills/creating-tasks-through-lunatask/)
+- Name/desc: 29 tok
+- SKILL.md: 343 tok
+- Full skill: 980 tok (all `.md` files in the folder)
+
+### fallback-code-review
+
+Provides a fallback-only external review flow via CLI tools like Amp,
+CodeRabbit, or Kodus.
+
+- Install with: `make fallback-code-review`
+- Source: [skills/fallback-code-review/](skills/fallback-code-review/)
+- Name/desc: 44 tok
+- SKILL.md: 228 tok
+- Full skill: 272 tok (all `.md` files in the folder)
+
+### formatting-commits
+
+Detects a project's commit style from recent history and formats messages
+accordingly. Supports Conventional Commits and kernel-style imperative commits.
+
+- Install with: `make formatting-commits`
+- Source: [skills/formatting-commits/](skills/formatting-commits/)
+- Name/desc: 34 tok
+- SKILL.md: 142 tok
+- Full skill: 596 tok (all `.md` files in the folder)
+
+### frontend-accessibility
+
+Strives to generate accessible HTML, React, and frontend code following WCAG
+2.2 AA. Prioritizes semantic HTML over ARIA, keyboard navigation, and screen
+reader compatibility.
+
+- Install with: `make frontend-accessibility`
+- Source: [skills/frontend-accessibility/](skills/frontend-accessibility/)
+- Name/desc: 32 tok
+- SKILL.md: 498 tok
+- Full skill: 2173 tok (all `.md` files in the folder)
+
+### handling-customer-data
+
+Queries customer data responsiblyβ€”the agent answers questions about the data
+without ever seeing it directly.
+
+- Install with: `make handling-customer-data`
+- Source: [skills/handling-customer-data/](skills/handling-customer-data/)
+- Name/desc: 29 tok
+- SKILL.md: 331 tok
+- Full skill: 360 tok (all `.md` files in the folder)
+
+### humanize
+
+Removes AI-generated patterns from text like promotional fluff, weasel words,
+and mechanical sentence structures. Based on Wikipedia's AI Cleanup research.
+Originally from [blader/humanizer].
+
+[blader/humanizer]: https://github.com/blader/humanizer
+
+- Install with: `make humanize`
+- Source: [skills/humanize/](skills/humanize/)
+- Name/desc: 55 tok
+- SKILL.md: 860 tok
+- Full skill: 2289 tok (all `.md` files in the folder)
+
+### ideating-with-bdd
+
+Guides collaborative discovery of behaviour through structured conversation,
+iterating with the user to refine ideas into user stories and Gherkin
+scenarios. Works for any language or framework.
+
+- Install with: `make ideating-with-bdd`
+- Source: [skills/ideating-with-bdd/](skills/ideating-with-bdd/)
+- Name/desc: 69 tok
+- SKILL.md: 730 tok
+- Full skill: 1253 tok (all `.md` files in the folder)
+
+### in-session-review
+
+Runs an extremely strict maintainability review for abstraction quality, giant
+files, and spaghetti-condition growth.
+
+- Install with: `make in-session-review`
+- Source: [skills/in-session-review/](skills/in-session-review/)
+- Name/desc: 32 tok
+- SKILL.md: 1015 tok
+- Full skill: 1047 tok (all `.md` files in the folder)
+
+### initialising-and-updating-agents-md
+
+Analyses a codebase and creates or updates `AGENTS.md` to help future agents
+work effectively. Discovers commands, conventions, patterns, and gotchas from
+the project's source and config files.
+
+- Install with: `make initialising-and-updating-agents-md`
+- Source: [skills/initialising-and-updating-agents-md/](skills/initialising-and-updating-agents-md/)
+- Name/desc: 38 tok
+- SKILL.md: 395 tok
+- Full skill: 433 tok (all `.md` files in the folder)
+
+### invoking-subagents
+
+Spawns subagents with restricted tool access for parallel tasks across
+repositories. Requires [the Pi coding agent][Pi]. Useful for summarizing git
+history or processing large diffs without filling the main context window.
+
+- Install with: `make invoking-subagents`
+- Source: [skills/invoking-subagents/](skills/invoking-subagents/)
+- Name/desc: 27 tok
+- SKILL.md: 210 tok
+- Full skill: 237 tok (all `.md` files in the folder)
+
+### licensing-with-reuse
+
+Manages REUSE-compliant licensing with the `reuse` CLI. Covers `reuse annotate`,
+`.license` sidecars for prompt files, custom `LicenseRef-...` identifiers,
+`LICENSES/`, project conventions, and `reuse lint`.
+
+- Install with: `make licensing-with-reuse`
+- Source: [skills/licensing-with-reuse/](skills/licensing-with-reuse/)
+- Name/desc: 33 tok
+- SKILL.md: 560 tok
+- Full skill: 593 tok (all `.md` files in the folder)
+
+### maintaining-aur-packages
+
+Creates and updates AUR packages following Arch packaging standards. Covers
+PKGBUILDs for source, `-bin`, and `-git` package types, checksums with
+`updpkgsums`, linting with `namcap`, and `.SRCINFO` generation.
+
+- Install with: `make maintaining-aur-packages`
+- Source: [skills/maintaining-aur-packages/](skills/maintaining-aur-packages/)
+- Name/desc: 35 tok
+- SKILL.md: 689 tok
+- Full skill: 1274 tok (all `.md` files in the folder)
+
+### managing-personal-knowledge
+
+Works inside a personal knowledge base as an exacting steward, shaping atomic,
+concept-oriented notes, a dense wikilink graph, and disciplined tags instead of
+dumping transcripts. Tool-agnostic across Markdown vaults like Obsidian,
+Logseq, Roam, and SilverBullet.
+
+- Install with: `make managing-personal-knowledge`
+- Source: [skills/managing-personal-knowledge/](skills/managing-personal-knowledge/)
+- Name/desc: 68 tok
+- SKILL.md: 859 tok
+- Full skill: 2172 tok (all `.md` files in the folder)
+
+### monitoring-with-munin
+
+Deploys and manages Munin monitoring across servers. Sets up munin-node on
+hosts, writes plugins, configures masters, and handles alerts.
+
+- Install with: `make monitoring-with-munin`
+- Source: [skills/monitoring-with-munin/](skills/monitoring-with-munin/)
+- Name/desc: 38 tok
+- SKILL.md: 907 tok
+- Full skill: 1470 tok (all `.md` files in the folder)
+
+### notifying-through-ntfy
+
+Sends push notifications via [ntfy.sh] when requested, such as at the end of its
+turn.
+
+[ntfy.sh]: https://ntfy.sh
+
+- Install with: `make notifying-through-ntfy`
+- Source: [skills/notifying-through-ntfy/](skills/notifying-through-ntfy/)
+- Name/desc: 26 tok
+- SKILL.md: 478 tok
+- Full skill: 1541 tok (all `.md` files in the folder)
+
+### rebasing-with-git
+
+Manages git rebase workflows from simple rebases to the drop-and-repick
+integration branch pattern. Assumes `upstream` remote for forks and `rerere`
+enabled. Supports worktrees including `wt`-managed repos.
+
+- Install with: `make rebasing-with-git`
+- Source: [skills/rebasing-with-git/](skills/rebasing-with-git/)
+- Name/desc: 41 tok
+- SKILL.md: 1057 tok
+- Full skill: 1098 tok (all `.md` files in the folder)
+
+### researching-with-rumilo
+
+Dispatches AI research subagents via [rumilo] for web search and repository
+exploration. Research topics, look up library usage, or explore external
+codebases without filling the main context window.
+
+[rumilo]: https://git.secluded.site/rumilo
+
+- Install with: `make researching-with-rumilo`
+- Source: [skills/researching-with-rumilo/](skills/researching-with-rumilo/)
+- Name/desc: 37 tok
+- SKILL.md: 557 tok
+- Full skill: 594 tok (all `.md` files in the folder)
+
+### scripting-with-go
+
+Creates executable Go scripts using a shell trick (not a true shebang). For
+automation and tooling outside of Go projects. For scripting in a non-Go
+project, copy the header comment and adjust as needed.
+
+- Install with: `make scripting-with-go`
+- Source: [skills/scripting-with-go/](skills/scripting-with-go/)
+- Name/desc: 29 tok
+- SKILL.md: 333 tok
+- Full skill: 362 tok (all `.md` files in the folder)
+
+### testing-with-gocuke-and-gherkin
+
+Drives BDD, red/green TDD, and property-based testing in Go projects using
+[gocuke] and Gherkin feature files.
+
+[gocuke]: https://github.com/tenntenn/gocuke
+
+- Install with: `make testing-with-gocuke-and-gherkin`
+- Source: [skills/testing-with-gocuke-and-gherkin/](skills/testing-with-gocuke-and-gherkin/)
+- Name/desc: 47 tok
+- SKILL.md: 299 tok
+- Full skill: 1123 tok (all `.md` files in the folder)
+
+### toki-pona-dictionary
+
+Searches the [nimi.li] toki pona dictionary by English meaning. Caches word
+data locally after a one-time fetch, then runs offline searches across
+definitions and community usage tags.
+
+[nimi.li]: https://nimi.li
+
+- Install with: `make toki-pona-dictionary`
+- Source: [skills/toki-pona-dictionary/](skills/toki-pona-dictionary/)
+- Name/desc: 48 tok
+- SKILL.md: 114 tok
+- Full skill: 162 tok (all `.md` files in the folder)
+
+### updating-llm-client-model-lists
+
+Synchronizes model configurations across Zed, Crush, and Pi from Plexus'
+/v1/models endpoint.
+
+- Install with: `make updating-llm-client-model-lists`
+- Source: [skills/updating-llm-client-model-lists/](skills/updating-llm-client-model-lists/)
+- Name/desc: 32 tok
+- SKILL.md: 1009 tok
+- Full skill: 1041 tok (all `.md` files in the folder)
+
+### using-exe-dev
+
+Guides working with [exe.dev] VMs, which provide Linux VMs with persistent
+disks, instant HTTPS, and built-in auth via SSH.
+
+[exe.dev]: https://exe.dev
+
+- Install with: `make using-exe-dev`
+- Source: [skills/using-exe-dev/](skills/using-exe-dev/)
+- Name/desc: 24 tok
+- SKILL.md: 176 tok
+- Full skill: 200 tok (all `.md` files in the folder)
+
+### using-jujutsu
+
+Guides [jujutsu] (jj) workflows, covering commit selection, change creation,
+diff reading, and common pitfalls that trip up agents.
+
+[jujutsu]: https://jj-vcs.github.io/jj/latest/
+
+- Install with: `make using-jujutsu`
+- Source: [skills/using-jujutsu/](skills/using-jujutsu/)
+- Name/desc: 35 tok
+- SKILL.md: 337 tok
+- Full skill: 372 tok (all `.md` files in the folder)
+
+### working-with-zmx
+
+Manages persistent terminal sessions for background processes, dev servers, and
+long-running tasks. Creates sessions, runs commands detached, checks output, and
+writes files into sessions over SSH.
+
+- Install with: `make working-with-zmx`
+- Source: [skills/working-with-zmx/](skills/working-with-zmx/)
+- Name/desc: 35 tok
+- SKILL.md: 241 tok
+- Full skill: 276 tok (all `.md` files in the folder)
+
+### writing-git-tags
+
+Generates git tag annotations from commit history following Semantic Versioning
+and Conventional Commits via [git-format].
+
+[git-format]: https://git.secluded.site/git-format
+
+- Install with: `make writing-git-tags`
+- Source: [skills/writing-git-tags/](skills/writing-git-tags/)
+- Name/desc: 26 tok
+- SKILL.md: 386 tok
+- Full skill: 425 tok (all `.md` files in the folder)
+
+### writing-rust
+
+Writes idiomatic Rust with strict quality gates: `cargo fmt`, `clippy -- -D
+warnings`, and required tests. Enforces `thiserror` for libs, `anyhow` for apps,
+warns against common anti-patterns.
+
+- Install with: `make writing-rust`
+- Source: [skills/writing-rust/](skills/writing-rust/)
+- Name/desc: 29 tok
+- SKILL.md: 607 tok
+- Full skill: 1425 tok (all `.md` files in the folder)
+
 ## Contributions
 
 Patch requests are in [amolith/llm-projects] on [pr.pico.sh]. You don't need a
@@ -254,462 +550,6 @@ complete example workflow.
 [amolith/llm-projects]: https://pr.pico.sh/r/amolith/llm-projects
 [pr.pico.sh]: https://pr.pico.sh
 
-## Token stats
-
-Real token stats as reported by [Synthetic.new]'s [/messages/count_tokens]
-endpoint with [./skill-stats.go](./skill-stats.go).
-
-[Synthetic.new]: https://synthetic.new
-[/messages/count_tokens]: https://dev.synthetic.new/docs/anthropic/messages/count-tokens
-
-```
-=== addressing-code-review-comments ===
-
-Token breakdown:
-  Name:            9 tokens
-  Description:    40 tokens
-  Body:          290 tokens (38 lines)
-  ───────────────────────────────────────────────
-  Total:         339 tokens
-
-=== amoliths-opinions ===
-
-Token breakdown:
-  Name:           10 tokens
-  Description:    89 tokens
-  Body:          590 tokens (51 lines)
-  References:
-    cli.md                                     385 tokens
-    golang/baseline.md                         860 tokens
-    golang/cli.md                              777 tokens
-    golang/index.md                            599 tokens
-    golang/licencing.md                        206 tokens
-    golang/mcp.md                              524 tokens
-    golang/mise.md                            1331 tokens
-    golang/packaging.md                        947 tokens
-    golang/preferred-libraries.md              621 tokens
-    golang/testing.md                          486 tokens
-    golang/tui.md                              381 tokens
-    golang/wails.md                            621 tokens
-    golang/web.md                              405 tokens
-    licencing.md                               493 tokens
-    mcp.md                                     326 tokens
-    mise.md                                    350 tokens
-    packaging.md                               462 tokens
-    testing.md                                 345 tokens
-    tui.md                                     222 tokens
-    web.md                                     400 tokens
-  ───────────────────────────────────────────────
-  Total:       11430 tokens
-
-=== ast-grep ===
-
-Token breakdown:
-  Name:            7 tokens
-  Description:    76 tokens
-  Body:         2765 tokens (469 lines)
-  References:
-    rule_reference.md                         2515 tokens
-  ───────────────────────────────────────────────
-  Total:        5363 tokens
-
-=== auditing-repositories ===
-
-Token breakdown:
-  Name:            8 tokens
-  Description:    51 tokens
-  Body:         1046 tokens (156 lines)
-  References:
-    llm-security.md                            427 tokens
-  ───────────────────────────────────────────────
-  Total:        1532 tokens
-
-=== authoring-skills ===
-
-Token breakdown:
-  Name:            8 tokens
-  Description:    32 tokens
-  Body:          861 tokens (107 lines)
-  References:
-    checklist.md                               301 tokens
-    patterns.md                                880 tokens
-    process.md                                 311 tokens
-    specification.md                          1441 tokens
-  ───────────────────────────────────────────────
-  Total:        3834 tokens
-
-=== backing-up-with-keld ===
-
-Token breakdown:
-  Name:            9 tokens
-  Description:    39 tokens
-  Body:         1742 tokens (235 lines)
-  References:
-    config-examples.md                        1415 tokens
-    installation.md                             19 tokens
-  ───────────────────────────────────────────────
-  Total:        3224 tokens
-
-=== collaborating-through-pr-pico-sh ===
-
-Token breakdown:
-  Name:           12 tokens
-  Description:    47 tokens
-  Body:          521 tokens (43 lines)
-  References:
-    contributing.md                            162 tokens
-    jujutsu.md                                 516 tokens
-    reviewing.md                               406 tokens
-  ───────────────────────────────────────────────
-  Total:        1664 tokens
-
-=== computing-golden-ratio-typography ===
-
-Token breakdown:
-  Name:           12 tokens
-  Description:    59 tokens
-  Body:         1208 tokens (108 lines)
-  ───────────────────────────────────────────────
-  Total:        1279 tokens
-
-=== cooking ===
-
-Token breakdown:
-  Name:            6 tokens
-  Description:   166 tokens
-  Body:         2805 tokens (117 lines)
-  References:
-    cuisines/_index.md                        1274 tokens
-    cuisines/caribbean/dish-boundaries.md      232 tokens
-    cuisines/caribbean/overview.md             730 tokens
-    cuisines/caribbean/pantry-techniques.md    399 tokens
-    cuisines/caribbean/regions.md              450 tokens
-    cuisines/chinese-regional/overview.md     1140 tokens
-    cuisines/chinese-regional/regions.md       500 tokens
-    cuisines/chinese-regional/substitutions.md   225 tokens
-    cuisines/chinese-regional/techniques-pantry.md   278 tokens
-    cuisines/german.md                         642 tokens
-    cuisines/hawaiian.md                       931 tokens
-    cuisines/indian/overview.md                873 tokens
-    cuisines/indian/pantry-substitutions.md    267 tokens
-    cuisines/indian/regions-communities.md     524 tokens
-    cuisines/indian/techniques.md              243 tokens
-    cuisines/irish-british.md                  641 tokens
-    cuisines/japanese-home.md                  673 tokens
-    cuisines/korean.md                         761 tokens
-    cuisines/levantine.md                      659 tokens
-    cuisines/mexican-regional/overview.md      761 tokens
-    cuisines/mexican-regional/pantry-substitutions.md   288 tokens
-    cuisines/mexican-regional/regions.md       402 tokens
-    cuisines/mexican-regional/techniques.md    237 tokens
-    cuisines/north-african-middle-eastern/egypt.md   146 tokens
-    cuisines/north-african-middle-eastern/gulf.md   149 tokens
-    cuisines/north-african-middle-eastern/maghreb.md   218 tokens
-    cuisines/north-african-middle-eastern/overview.md   820 tokens
-    cuisines/north-african-middle-eastern/pantry-techniques.md   266 tokens
-    cuisines/north-african-middle-eastern/persian.md   159 tokens
-    cuisines/sichuan/flavor-profiles.md        301 tokens
-    cuisines/sichuan/overview.md               774 tokens
-    cuisines/sichuan/pantry.md                 225 tokens
-    cuisines/sichuan/pivots-history.md         151 tokens
-    cuisines/sichuan/techniques.md             195 tokens
-    example-conversations.md                  1873 tokens
-    foundations.md                            1335 tokens
-    introductions.md                           532 tokens
-    searching-sources.md                       693 tokens
-    substitutions-and-pivots.md               1086 tokens
-    techniques.md                             2584 tokens
-  ───────────────────────────────────────────────
-  Total:       27614 tokens
-
-=== creating-tasks-through-lunatask ===
-
-Token breakdown:
-  Name:           12 tokens
-  Description:    41 tokens
-  Body:          741 tokens (88 lines)
-  References:
-    cli.md                                     218 tokens
-    handoff.md                                1035 tokens
-    installing-lune.md                          65 tokens
-  ───────────────────────────────────────────────
-  Total:        2112 tokens
-
-=== fallback-code-review ===
-
-Token breakdown:
-  Name:            7 tokens
-  Description:    79 tokens
-  Body:          489 tokens (56 lines)
-  ───────────────────────────────────────────────
-  Total:         575 tokens
-
-=== formatting-commits ===
-
-Token breakdown:
-  Name:            8 tokens
-  Description:    57 tokens
-  Body:          310 tokens (34 lines)
-  References:
-    conventional-commits.md                    362 tokens
-    installing-git-format.md                    22 tokens
-    kernel-style.md                            526 tokens
-  ───────────────────────────────────────────────
-  Total:        1285 tokens
-
-=== frontend-accessibility ===
-
-Token breakdown:
-  Name:            7 tokens
-  Description:    52 tokens
-  Body:         1091 tokens (172 lines)
-  References:
-    antipatterns.md                           1341 tokens
-    patterns.md                               2279 tokens
-  ───────────────────────────────────────────────
-  Total:        4770 tokens
-
-=== handling-customer-data ===
-
-Token breakdown:
-  Name:            9 tokens
-  Description:    46 tokens
-  Body:          715 tokens (115 lines)
-  ───────────────────────────────────────────────
-  Total:         770 tokens
-
-=== humanize ===
-
-Token breakdown:
-  Name:            6 tokens
-  Description:   100 tokens
-  Body:         1886 tokens (152 lines)
-  References:
-    DETAILED_PATTERNS.md                      1903 tokens
-    REPLACEMENTS.md                           1125 tokens
-  ───────────────────────────────────────────────
-  Total:        5020 tokens
-
-=== ideating-with-bdd ===
-
-Token breakdown:
-  Name:            9 tokens
-  Description:   133 tokens
-  Body:         1582 tokens (177 lines)
-  References:
-    gherkin-reference.md                       984 tokens
-  ───────────────────────────────────────────────
-  Total:        2708 tokens
-
-=== in-session-review ===
-
-Token breakdown:
-  Name:            7 tokens
-  Description:    48 tokens
-  Body:         2248 tokens (182 lines)
-  ───────────────────────────────────────────────
-  Total:        2303 tokens
-
-=== initialising-and-updating-agents-md ===
-
-Token breakdown:
-  Name:           12 tokens
-  Description:    59 tokens
-  Body:          850 tokens (63 lines)
-  ───────────────────────────────────────────────
-  Total:         921 tokens
-
-=== invoking-subagents ===
-
-Token breakdown:
-  Name:            8 tokens
-  Description:    42 tokens
-  Body:          450 tokens (20 lines)
-  ───────────────────────────────────────────────
-  Total:         500 tokens
-
-=== licensing-with-reuse ===
-
-Token breakdown:
-  Name:            9 tokens
-  Description:    51 tokens
-  Body:         1234 tokens (135 lines)
-  ───────────────────────────────────────────────
-  Total:        1294 tokens
-
-=== maintaining-aur-packages ===
-
-Token breakdown:
-  Name:            9 tokens
-  Description:    58 tokens
-  Body:         1481 tokens (105 lines)
-  References:
-    build-patterns.md                         1120 tokens
-  ───────────────────────────────────────────────
-  Total:        2668 tokens
-
-=== managing-personal-knowledge ===
-
-Token breakdown:
-  Name:           10 tokens
-  Description:   129 tokens
-  Body:         1867 tokens (64 lines)
-  References:
-    philosophy.md                             1426 tokens
-    scenarios.md                              1294 tokens
-  ───────────────────────────────────────────────
-  Total:        4726 tokens
-
-=== monitoring-with-munin ===
-
-Token breakdown:
-  Name:           10 tokens
-  Description:    62 tokens
-  Body:         1981 tokens (238 lines)
-  References:
-    writing-plugins.md                        1151 tokens
-  ───────────────────────────────────────────────
-  Total:        3204 tokens
-
-=== notifying-through-ntfy ===
-
-Token breakdown:
-  Name:           10 tokens
-  Description:    37 tokens
-  Body:         1052 tokens (89 lines)
-  References:
-    ht.md                                      383 tokens
-    httpie.md                                  385 tokens
-    nodejs.md                                  541 tokens
-    python.md                                  517 tokens
-    wget.md                                    430 tokens
-  ───────────────────────────────────────────────
-  Total:        3355 tokens
-
-=== rebasing-with-git ===
-
-Token breakdown:
-  Name:            8 tokens
-  Description:    71 tokens
-  Body:         2317 tokens (239 lines)
-  ───────────────────────────────────────────────
-  Total:        2396 tokens
-
-=== researching-with-rumilo ===
-
-Token breakdown:
-  Name:           10 tokens
-  Description:    60 tokens
-  Body:         1217 tokens (112 lines)
-  ───────────────────────────────────────────────
-  Total:        1287 tokens
-
-=== scripting-with-go ===
-
-Token breakdown:
-  Name:            8 tokens
-  Description:    46 tokens
-  Body:          727 tokens (141 lines)
-  ───────────────────────────────────────────────
-  Total:         781 tokens
-
-=== testing-with-gocuke-and-gherkin ===
-
-Token breakdown:
-  Name:           14 tokens
-  Description:    77 tokens
-  Body:          633 tokens (67 lines)
-  References:
-    bdd-practices.md                           261 tokens
-    gocuke-api.md                             1427 tokens
-  ───────────────────────────────────────────────
-  Total:        2412 tokens
-
-=== toki-pona-dictionary ===
-
-Token breakdown:
-  Name:           11 tokens
-  Description:    86 tokens
-  Body:          250 tokens (24 lines)
-  ───────────────────────────────────────────────
-  Total:         347 tokens
-
-=== updating-llm-client-model-lists ===
-
-Token breakdown:
-  Name:           13 tokens
-  Description:    47 tokens
-  Body:         2210 tokens (264 lines)
-  ───────────────────────────────────────────────
-  Total:        2270 tokens
-
-=== using-exe-dev ===
-
-Token breakdown:
-  Name:            8 tokens
-  Description:    32 tokens
-  Body:          380 tokens (45 lines)
-  ───────────────────────────────────────────────
-  Total:         420 tokens
-
-=== using-jujutsu ===
-
-Token breakdown:
-  Name:            9 tokens
-  Description:    55 tokens
-  Body:          735 tokens (92 lines)
-  ───────────────────────────────────────────────
-  Total:         799 tokens
-
-=== working-with-zmx ===
-
-Token breakdown:
-  Name:            8 tokens
-  Description:    58 tokens
-  Body:          522 tokens (69 lines)
-  ───────────────────────────────────────────────
-  Total:         588 tokens
-
-=== writing-git-tags ===
-
-Token breakdown:
-  Name:            7 tokens
-  Description:    38 tokens
-  Body:          846 tokens (109 lines)
-  References:
-    installing-git-format.md                    22 tokens
-  ───────────────────────────────────────────────
-  Total:         913 tokens
-
-=== writing-rust ===
-
-Token breakdown:
-  Name:            7 tokens
-  Description:    45 tokens
-  Body:         1324 tokens (139 lines)
-  References:
-    idioms.md                                 1708 tokens
-  ───────────────────────────────────────────────
-  Total:        3084 tokens
-
-============================================================
-SUMMARY
-============================================================
-
-Skills: 35
-Metadata: 2525 tokens
-Combined bodies: 40966 tokens
-Overall: 107787 tokens
-Validation errors: 0
-
-Largest skills (by total tokens):
-  1. cooking                                  27614 tokens
-  2. amoliths-opinions                        11430 tokens
-  3. ast-grep                                  5363 tokens
-  4. humanize                                  5020 tokens
-  5. frontend-accessibility                    4770 tokens
-```
-
 ---
 
 Some other tools if these interested you

skill-stats.go πŸ”—

@@ -9,7 +9,6 @@ import (
 	"io"
 	"net/http"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"regexp"
 	"sort"
@@ -19,9 +18,13 @@ import (
 )
 
 const (
-	syntheticAPI = "https://api.synthetic.new/anthropic/v1/messages/count_tokens"
-	model        = "hf:moonshotai/Kimi-K2.6"
-	workerCount  = 5 // Number of parallel API workers
+	syntheticAPI  = "https://api.synthetic.new/anthropic/v1/messages/count_tokens"
+	model         = "syn:large:text"
+	workerCount   = 5 // Number of parallel API workers
+	readmePath    = "README.md"
+	skillsHeading = "## Available skills"
+	todoParagraph = "TODO: describe me"
+	maxBodyLines  = 500
 )
 
 var httpClient = &http.Client{
@@ -33,11 +36,13 @@ type Frontmatter struct {
 	Description string
 }
 
+// TokenCount breaks down a skill's token usage. Extra is the sum of every
+// .md file in the skill folder other than SKILL.md itself.
 type TokenCount struct {
 	Name        int
 	Description int
 	Body        int
-	References  map[string]int
+	Extra       int
 	Total       int
 }
 
@@ -60,19 +65,7 @@ type TokenResult struct {
 	Err   error
 }
 
-type SkillComparison struct {
-	PrevTotal     int
-	PrevMetadata  int
-	PrevBody      int
-	Delta         int
-	MetadataDelta int
-	BodyDelta     int
-	Percent       float64
-	IsNew         bool
-}
-
 func main() {
-	compare := flag.Bool("compare", false, "Compare with HEAD commit")
 	workers := flag.Int("workers", workerCount, "Number of parallel API workers")
 	flag.Parse()
 
@@ -82,7 +75,6 @@ func main() {
 		os.Exit(1)
 	}
 
-	// Start worker pool
 	counter := newTokenCounter(apiKey, *workers)
 	defer counter.Close()
 
@@ -92,33 +84,20 @@ func main() {
 		os.Exit(1)
 	}
 
-	// Build comparison map if requested
-	var comparisons map[string]SkillComparison
-	if *compare {
-		comparisons = buildComparisons(skills, counter)
-	}
-
-	// Sort skills by name for consistent output
 	sort.Slice(skills, func(i, j int) bool {
 		return skills[i].Dir < skills[j].Dir
 	})
 
-	// Print reports
-	for _, skill := range skills {
-		var comp *SkillComparison
-		if comparisons != nil {
-			if c, ok := comparisons[skill.Dir]; ok {
-				comp = &c
-			}
-		}
-		printSkillReport(skill, comp)
+	stats, err := updateReadme(readmePath, skills)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error updating README: %v\n", err)
+		os.Exit(1)
 	}
 
-	// Print summary
-	printSummary(skills, comparisons)
+	fmt.Fprintf(os.Stderr, "README updated: %d skills maintained, %d errored, %d present in README but missing on disk\n",
+		stats.Maintained, stats.Errored, stats.Missing)
 }
 
-// TokenCounter manages a pool of workers for parallel token counting
 type TokenCounter struct {
 	apiKey  string
 	jobs    chan TokenJob
@@ -132,13 +111,14 @@ func newTokenCounter(apiKey string, workers int) *TokenCounter {
 		jobs:    make(chan TokenJob, 100),
 		results: make(chan TokenResult, 100),
 	}
-
-	// Start workers
+	tc.wg.Add(workers)
 	for i := 0; i < workers; i++ {
-		tc.wg.Add(1)
 		go tc.worker()
 	}
-
+	go func() {
+		tc.wg.Wait()
+		close(tc.results)
+	}()
 	return tc
 }
 
@@ -155,13 +135,20 @@ func (tc *TokenCounter) Count(id, text string) {
 }
 
 func (tc *TokenCounter) GetResult() TokenResult {
-	return <-tc.results
+	result, ok := <-tc.results
+	if !ok {
+		return TokenResult{Err: fmt.Errorf("results channel closed")}
+	}
+	return result
 }
 
 func (tc *TokenCounter) TryGetResult() (TokenResult, bool) {
 	select {
-	case r := <-tc.results:
-		return r, true
+	case result, ok := <-tc.results:
+		if !ok {
+			return TokenResult{}, false
+		}
+		return result, true
 	default:
 		return TokenResult{}, false
 	}
@@ -169,8 +156,6 @@ func (tc *TokenCounter) TryGetResult() (TokenResult, bool) {
 
 func (tc *TokenCounter) Close() {
 	close(tc.jobs)
-	tc.wg.Wait()
-	close(tc.results)
 }
 
 func analyzeSkills(counter *TokenCounter) ([]SkillInfo, error) {
@@ -199,12 +184,7 @@ func analyzeSkills(counter *TokenCounter) ([]SkillInfo, error) {
 }
 
 func analyzeSkill(path string, counter *TokenCounter) (SkillInfo, error) {
-	skill := SkillInfo{
-		Dir: filepath.Base(path),
-		Tokens: TokenCount{
-			References: make(map[string]int),
-		},
-	}
+	skill := SkillInfo{Dir: filepath.Base(path)}
 
 	// Read SKILL.md
 	skillMdPath := filepath.Join(path, "SKILL.md")
@@ -233,40 +213,46 @@ func analyzeSkill(path string, counter *TokenCounter) (SkillInfo, error) {
 
 	fmt.Fprintf(os.Stderr, "Analyzing %s...\n", skill.Dir)
 
-	// Collect all jobs first (no channel use yet)
+	// Collect token-counting jobs. SKILL.md is counted as its frontmatter
+	// name+description fields plus the body; every other .md file in the
+	// folder is counted wholesale.
 	var jobs []TokenJob
 	jobs = append(jobs, TokenJob{ID: fmt.Sprintf("%s:name", skill.Dir), Text: fm.Name})
 	jobs = append(jobs, TokenJob{ID: fmt.Sprintf("%s:description", skill.Dir), Text: fm.Description})
 	jobs = append(jobs, TokenJob{ID: fmt.Sprintf("%s:body", skill.Dir), Text: body})
 
-	// Collect reference file jobs (recursively walk subdirectories)
-	refsPath := filepath.Join(path, "references")
-	filepath.WalkDir(refsPath, func(refPath string, d os.DirEntry, err error) error {
-		if err != nil {
-			if os.IsNotExist(err) {
-				return nil
-			}
-			skill.Errors = append(skill.Errors, fmt.Sprintf("Cannot access references path %s: %v", refPath, err))
+	err = filepath.WalkDir(path, func(p string, d os.DirEntry, walkErr error) error {
+		if walkErr != nil {
+			skill.Errors = append(skill.Errors, fmt.Sprintf("Cannot walk %s: %v", p, walkErr))
 			return nil
 		}
 		if d.IsDir() {
 			return nil
 		}
-		refContent, err := os.ReadFile(refPath)
+		if !strings.HasSuffix(p, ".md") {
+			return nil
+		}
+		// SKILL.md is already covered by the name/description/body jobs.
+		if p == skillMdPath {
+			return nil
+		}
+		fileContent, err := os.ReadFile(p)
 		if err != nil {
-			skill.Errors = append(skill.Errors, fmt.Sprintf("Cannot read reference %s: %v", refPath, err))
+			skill.Errors = append(skill.Errors, fmt.Sprintf("Cannot read %s: %v", p, err))
 			return nil
 		}
-		// Use path relative to references/ dir as the key
-		relPath, _ := filepath.Rel(refsPath, refPath)
+		relPath, _ := filepath.Rel(path, p)
 		jobs = append(jobs, TokenJob{
-			ID:   fmt.Sprintf("%s:ref:%s", skill.Dir, relPath),
-			Text: string(refContent),
+			ID:   fmt.Sprintf("%s:file:%s", skill.Dir, relPath),
+			Text: string(fileContent),
 		})
 		return nil
 	})
+	if err != nil {
+		skill.Errors = append(skill.Errors, fmt.Sprintf("Walk error: %v", err))
+	}
 
-	// Interleave enqueue and drain to prevent deadlock
+	// Interleave enqueue and drain to avoid backpressure on the buffered channels.
 	processResult := func(result TokenResult) {
 		if result.Err != nil {
 			skill.Errors = append(skill.Errors, fmt.Sprintf("Token count failed for %s: %v", result.ID, result.Err))
@@ -283,10 +269,8 @@ func analyzeSkill(path string, counter *TokenCounter) (SkillInfo, error) {
 			skill.Tokens.Description = result.Count
 		case "body":
 			skill.Tokens.Body = result.Count
-		case "ref":
-			if len(parts) == 3 {
-				skill.Tokens.References[parts[2]] = result.Count
-			}
+		case "file":
+			skill.Tokens.Extra += result.Count
 		}
 	}
 
@@ -294,7 +278,6 @@ func analyzeSkill(path string, counter *TokenCounter) (SkillInfo, error) {
 	for _, job := range jobs {
 		counter.Count(job.ID, job.Text)
 		outstanding++
-		// Drain any available results to prevent backpressure
 		for {
 			if result, ok := counter.TryGetResult(); ok {
 				processResult(result)
@@ -304,19 +287,13 @@ func analyzeSkill(path string, counter *TokenCounter) (SkillInfo, error) {
 			}
 		}
 	}
-
-	// Drain remaining results
 	for outstanding > 0 {
 		result := counter.GetResult()
 		processResult(result)
 		outstanding--
 	}
 
-	// Calculate total
-	skill.Tokens.Total = skill.Tokens.Name + skill.Tokens.Description + skill.Tokens.Body
-	for _, count := range skill.Tokens.References {
-		skill.Tokens.Total += count
-	}
+	skill.Tokens.Total = skill.Tokens.Name + skill.Tokens.Description + skill.Tokens.Body + skill.Tokens.Extra
 
 	return skill, nil
 }
@@ -403,14 +380,14 @@ func validateSkill(skill SkillInfo) []string {
 	}
 
 	// Check body line count
-	if skill.BodyLines > 500 {
-		errors = append(errors, fmt.Sprintf("body has %d lines (recommended: < 500)", skill.BodyLines))
+	if skill.BodyLines > maxBodyLines {
+		errors = append(errors, fmt.Sprintf("body has %d lines (recommended: < %d)", skill.BodyLines, maxBodyLines))
 	}
 
 	return errors
 }
 
-func countTokensAPI(apiKey string, text string) (int, error) {
+func countTokensAPI(apiKey, text string) (int, error) {
 	reqBody := map[string]interface{}{
 		"model": model,
 		"messages": []map[string]string{
@@ -430,7 +407,6 @@ func countTokensAPI(apiKey string, text string) (int, error) {
 	if err != nil {
 		return 0, fmt.Errorf("create request: %w", err)
 	}
-
 	req.Header.Set("Authorization", "Bearer "+apiKey)
 	req.Header.Set("Content-Type", "application/json")
 	req.Header.Set("anthropic-version", "2023-06-01")
@@ -446,10 +422,7 @@ func countTokensAPI(apiKey string, text string) (int, error) {
 		return 0, fmt.Errorf("API status %d: %s", resp.StatusCode, string(body))
 	}
 
-	var result struct {
-		InputTokens int `json:"input_tokens"`
-	}
-
+	var result InputTokens
 	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
 		return 0, fmt.Errorf("decode response: %w", err)
 	}
@@ -457,322 +430,244 @@ func countTokensAPI(apiKey string, text string) (int, error) {
 	return result.InputTokens, nil
 }
 
-type PrevTokens struct {
-	Total    int
-	Metadata int
-	Body     int
+type InputTokens struct {
+	InputTokens int `json:"input_tokens"`
 }
 
-func buildComparisons(currentSkills []SkillInfo, counter *TokenCounter) map[string]SkillComparison {
-	comparisons := make(map[string]SkillComparison)
-
-	for _, skill := range currentSkills {
-		prev, err := getSkillTokensFromGit(skill.Dir, counter)
-		if err != nil {
-			// Skill is new
-			if skill.Tokens.Total > 0 {
-				comparisons[skill.Dir] = SkillComparison{
-					PrevTotal:     0,
-					PrevMetadata:  0,
-					PrevBody:      0,
-					Delta:         skill.Tokens.Total,
-					MetadataDelta: skill.Tokens.Name + skill.Tokens.Description,
-					BodyDelta:     skill.Tokens.Body,
-					Percent:       100.0,
-					IsNew:         true,
-				}
-			}
-			continue
-		}
-
-		delta := skill.Tokens.Total - prev.Total
-		metadataDelta := (skill.Tokens.Name + skill.Tokens.Description) - prev.Metadata
-		bodyDelta := skill.Tokens.Body - prev.Body
-		var percent float64
-		if prev.Total > 0 {
-			percent = (float64(delta) / float64(prev.Total)) * 100
-		}
-
-		if delta != 0 || metadataDelta != 0 || bodyDelta != 0 {
-			comparisons[skill.Dir] = SkillComparison{
-				PrevTotal:     prev.Total,
-				PrevMetadata:  prev.Metadata,
-				PrevBody:      prev.Body,
-				Delta:         delta,
-				MetadataDelta: metadataDelta,
-				BodyDelta:     bodyDelta,
-				Percent:       percent,
-				IsNew:         false,
-			}
-		}
-	}
+// updateStats summarises what updateReadme did.
+type updateStats struct {
+	Maintained int // skills on disk whose section was (re)written
+	Errored    int // skills with one or more errors
+	Missing    int // sections in README with no matching skill on disk
+}
 
-	return comparisons
+// parsedSection is one ### skill-name block from the existing README. The
+// script rewrites the heading and bullets of every maintained section but
+// preserves the paragraph verbatim. For sections whose skill has disappeared
+// from disk (orphans), raw is emitted unchanged.
+type parsedSection struct {
+	name      string
+	paragraph []string // prose between heading and first bullet (preserved)
+	raw       []string // full original section text (used for orphan passthrough)
 }
 
-func getSkillTokensFromGit(skillDir string, counter *TokenCounter) (PrevTokens, error) {
-	// Get file from HEAD
-	skillPath := fmt.Sprintf("skills/%s/SKILL.md", skillDir)
-	cmd := exec.Command("git", "show", fmt.Sprintf("HEAD:%s", skillPath))
-	output, err := cmd.Output()
-	if err != nil {
-		return PrevTokens{}, err
-	}
+// updateReadme rewrites the ## Available Skills region of the README: it
+// regenerates each section's heading and bullet list, preserving the
+// author-written paragraph; inserts a TODO scaffold for skills present on disk
+// but not in the README; and leaves sections whose skill is missing from disk
+// untouched (with a stderr warning). Everything outside the region is passed
+// through byte-for-byte.
+func updateReadme(path string, skills []SkillInfo) (updateStats, error) {
+	var stats updateStats
 
-	// Parse frontmatter and body
-	fm, body, err := parseFrontmatter(string(output))
+	data, err := os.ReadFile(path)
 	if err != nil {
-		return PrevTokens{}, err
+		return stats, fmt.Errorf("cannot read %s: %w", path, err)
 	}
+	lines := strings.Split(string(data), "\n")
 
-	// Collect all jobs first (no channel use yet)
-	var jobs []TokenJob
-	jobs = append(jobs, TokenJob{ID: fmt.Sprintf("prev:%s:name", skillDir), Text: fm.Name})
-	jobs = append(jobs, TokenJob{ID: fmt.Sprintf("prev:%s:description", skillDir), Text: fm.Description})
-	jobs = append(jobs, TokenJob{ID: fmt.Sprintf("prev:%s:body", skillDir), Text: body})
-
-	// Get reference files from HEAD
-	refsPath := fmt.Sprintf("skills/%s/references", skillDir)
-	cmd = exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD", refsPath)
-	output, err = cmd.Output()
-	if err != nil {
-		// Log non-fatal git errors (e.g., refs directory doesn't exist in HEAD)
-		if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 0 {
-			fmt.Fprintf(os.Stderr, "Warning: cannot list git refs for %s (may not exist in HEAD)\n", refsPath)
+	headingIdx := -1
+	for i, l := range lines {
+		if strings.TrimSpace(l) == skillsHeading {
+			headingIdx = i
+			break
 		}
-	} else {
-		refPaths := strings.Split(strings.TrimSpace(string(output)), "\n")
-		for _, refPath := range refPaths {
-			if refPath == "" {
-				continue
-			}
-			cmd = exec.Command("git", "show", fmt.Sprintf("HEAD:%s", refPath))
-			refContent, err := cmd.Output()
-			if err != nil {
-				fmt.Fprintf(os.Stderr, "Warning: cannot read %s from HEAD: %v\n", refPath, err)
-				continue
-			}
-			jobs = append(jobs, TokenJob{ID: fmt.Sprintf("prev:%s:ref", skillDir), Text: string(refContent)})
+	}
+	if headingIdx == -1 {
+		return stats, fmt.Errorf("could not find %q in %s", skillsHeading, path)
+	}
+
+	regionEnd := len(lines)
+	for i := headingIdx + 1; i < len(lines); i++ {
+		if strings.HasPrefix(strings.TrimSpace(lines[i]), "## ") {
+			regionEnd = i
+			break
 		}
 	}
 
-	// Interleave enqueue and drain to prevent deadlock
-	prev := PrevTokens{}
-	processResult := func(result TokenResult) {
-		if result.Err != nil {
-			fmt.Fprintf(os.Stderr, "Warning: token count failed for %s: %v\n", result.ID, result.Err)
-			return
+	// Leading = lines between the heading and the first ### section.
+	firstSectionIdx := regionEnd
+	for i := headingIdx + 1; i < regionEnd; i++ {
+		if strings.HasPrefix(strings.TrimSpace(lines[i]), "### ") {
+			firstSectionIdx = i
+			break
 		}
-		parts := strings.SplitN(result.ID, ":", 3)
-		if len(parts) < 3 {
-			prev.Total += result.Count
-			return
+	}
+	leading := lines[headingIdx+1 : firstSectionIdx]
+
+	// Parse sections and whatever follows them (trailing).
+	var existing []parsedSection
+	i := firstSectionIdx
+	for i < regionEnd {
+		if !strings.HasPrefix(strings.TrimSpace(lines[i]), "### ") {
+			break // non-section content ends the section run; rest is trailing
 		}
-		switch parts[2] {
-		case "name":
-			prev.Metadata += result.Count
-		case "description":
-			prev.Metadata += result.Count
-		case "body":
-			prev.Body += result.Count
-		case "ref":
-			// References are counted in total but not metadata or body
+		name := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(lines[i]), "### "))
+
+		bodyStart := i + 1
+		bodyEnd := regionEnd
+		for j := bodyStart; j < regionEnd; j++ {
+			if strings.HasPrefix(strings.TrimSpace(lines[j]), "### ") {
+				bodyEnd = j
+				break
+			}
 		}
-		prev.Total += result.Count
-	}
+		raw := lines[i:bodyEnd]
+		body := lines[bodyStart:bodyEnd]
 
-	outstanding := 0
-	for _, job := range jobs {
-		counter.Count(job.ID, job.Text)
-		outstanding++
-		// Drain any available results to prevent backpressure
-		for {
-			if result, ok := counter.TryGetResult(); ok {
-				processResult(result)
-				outstanding--
-			} else {
+		bulletStart := -1
+		for k, bl := range body {
+			if strings.HasPrefix(strings.TrimSpace(bl), "- ") {
+				bulletStart = k
 				break
 			}
 		}
-	}
+		var paragraph []string
+		if bulletStart == -1 {
+			paragraph = body
+		} else {
+			paragraph = body[:bulletStart]
+		}
+		paragraph = trimLeadingBlanks(trimTrailingBlanks(paragraph))
 
-	// Drain remaining results
-	for outstanding > 0 {
-		result := counter.GetResult()
-		processResult(result)
-		outstanding--
+		existing = append(existing, parsedSection{name: name, paragraph: paragraph, raw: raw})
+		i = bodyEnd
 	}
+	trailing := lines[i:regionEnd]
 
-	return prev, nil
-}
-
-func printSkillReport(skill SkillInfo, comp *SkillComparison) {
-	fmt.Printf("\n=== %s ===\n", skill.Dir)
-
-	if len(skill.Errors) > 0 {
-		fmt.Println("\nValidation errors:")
-		for _, err := range skill.Errors {
-			fmt.Printf("  βœ— %s\n", err)
-		}
+	existingByPos := existing
+	existingByName := make(map[string]parsedSection, len(existing))
+	for _, e := range existing {
+		existingByName[e.name] = e
 	}
 
-	fmt.Println("\nToken breakdown:")
-	fmt.Printf("  Name:        %5d tokens\n", skill.Tokens.Name)
-	fmt.Printf("  Description: %5d tokens\n", skill.Tokens.Description)
-	fmt.Printf("  Body:        %5d tokens (%d lines)\n", skill.Tokens.Body, skill.BodyLines)
-
-	if len(skill.Tokens.References) > 0 {
-		fmt.Println("  References:")
-		// Sort reference names for consistent output
-		refNames := make([]string, 0, len(skill.Tokens.References))
-		for name := range skill.Tokens.References {
-			refNames = append(refNames, name)
+	desired := make(map[string]bool, len(skills))
+	for _, s := range skills {
+		desired[s.Dir] = true
+		if len(s.Errors) > 0 {
+			stats.Errored++
 		}
-		sort.Strings(refNames)
-
-		for _, name := range refNames {
-			count := skill.Tokens.References[name]
-			fmt.Printf("    %-40s %5d tokens\n", name, count)
+	}
+	stats.Maintained = len(skills)
+	for _, e := range existingByPos {
+		if !desired[e.name] {
+			stats.Missing++
 		}
 	}
 
-	fmt.Println("  ───────────────────────────────────────────────")
+	// Build the rebuilt region.
+	var regionLines []string
+	lead := trimTrailingBlanks(leading)
+	regionLines = append(regionLines, lead...)
 
-	// Print total with comparison if available
-	if comp != nil {
-		sign := "+"
-		if comp.Delta < 0 {
-			sign = ""
-		}
-		indicator := ""
-		if comp.IsNew {
-			indicator = " [NEW]"
-		} else if comp.Percent > 20 {
-			indicator = " ⚠️"
-		} else if comp.Percent < -20 {
-			indicator = " βœ“"
+	for _, s := range skills {
+		paragraph, ok := existingByName[s.Dir]
+		if ok && len(paragraph.paragraph) > 0 {
+			regionLines = appendBlankSep(regionLines)
+			regionLines = append(regionLines, renderSection(s, paragraph.paragraph)...)
+		} else {
+			// New skill: scaffold with a TODO placeholder.
+			if !ok {
+				fmt.Fprintf(os.Stderr, "Warning: inserting new section for %s (no description yet)\n", s.Dir)
+			}
+			regionLines = appendBlankSep(regionLines)
+			regionLines = append(regionLines, renderSection(s, []string{todoParagraph})...)
 		}
-		fmt.Printf("  Total:       %5d tokens (%s%d, %s%.1f%% from HEAD)%s\n",
-			skill.Tokens.Total, sign, comp.Delta, sign, comp.Percent, indicator)
-	} else {
-		fmt.Printf("  Total:       %5d tokens\n", skill.Tokens.Total)
 	}
 
-	// Warn if approaching budget
-	if skill.Tokens.Body > 5000 {
-		fmt.Println("  ⚠️  Body exceeds recommended 5000 token budget!")
-	} else if skill.Tokens.Body > 4000 {
-		fmt.Println("  ⚠️  Body approaching 5000 token budget")
+	// Orphans: sections in the README whose skill is gone from disk. Emit
+	// their raw text unchanged and warn.
+	for _, e := range existingByPos {
+		if desired[e.name] {
+			continue
+		}
+		fmt.Fprintf(os.Stderr, "Warning: section %q is in README but missing from disk; leaving untouched\n", e.name)
+		regionLines = appendBlankSep(regionLines)
+		regionLines = append(regionLines, e.raw...)
 	}
-}
 
-func printSummary(skills []SkillInfo, comparisons map[string]SkillComparison) {
-	fmt.Println("\n" + strings.Repeat("=", 60))
-	fmt.Println("SUMMARY")
-	fmt.Println(strings.Repeat("=", 60))
-
-	totalTokens := 0
-	totalMetadataTokens := 0
-	totalBodyTokens := 0
-	totalErrors := 0
-	totalDelta := 0
-	metadataDelta := 0
-	bodyDelta := 0
-
-	for _, skill := range skills {
-		totalTokens += skill.Tokens.Total
-		totalMetadataTokens += skill.Tokens.Name + skill.Tokens.Description
-		totalBodyTokens += skill.Tokens.Body
-		totalErrors += len(skill.Errors)
-		if comp, ok := comparisons[skill.Dir]; ok {
-			totalDelta += comp.Delta
-			metadataDelta += comp.MetadataDelta
-			bodyDelta += comp.BodyDelta
-		}
+	trail := trimLeadingBlanks(trailing)
+	if len(trail) > 0 {
+		regionLines = appendBlankSep(regionLines)
+		regionLines = append(regionLines, trail...)
 	}
 
-	fmt.Printf("\nSkills: %d\n", len(skills))
-	if comparisons != nil && metadataDelta != 0 {
-		fmt.Printf("Metadata: %d tokens (%+d)\n", totalMetadataTokens, metadataDelta)
-	} else {
-		fmt.Printf("Metadata: %d tokens\n", totalMetadataTokens)
+	// Ensure a blank line separates the region from the following ## heading
+	// (if any), matching markdown convention.
+	if len(regionLines) > 0 && regionLines[len(regionLines)-1] != "" && regionEnd < len(lines) {
+		regionLines = append(regionLines, "")
 	}
-	if comparisons != nil && bodyDelta != 0 {
-		fmt.Printf("Combined bodies: %d tokens (%+d)\n", totalBodyTokens, bodyDelta)
-	} else {
-		fmt.Printf("Combined bodies: %d tokens\n", totalBodyTokens)
+
+	var newLines []string
+	newLines = append(newLines, lines[:headingIdx]...)
+	newLines = append(newLines, lines[headingIdx]) // "## Available Skills"
+	newLines = append(newLines, regionLines...)
+	newLines = append(newLines, lines[regionEnd:]...)
+
+	tmp := path + ".tmp"
+	if err := os.WriteFile(tmp, []byte(strings.Join(newLines, "\n")), 0o644); err != nil {
+		return stats, fmt.Errorf("write temp: %w", err)
 	}
-	if comparisons != nil && totalDelta != 0 {
-		fmt.Printf("Overall: %d tokens (%+d from HEAD)\n", totalTokens, totalDelta)
-	} else {
-		fmt.Printf("Overall: %d tokens\n", totalTokens)
+	if err := os.Rename(tmp, path); err != nil {
+		return stats, fmt.Errorf("rename: %w", err)
 	}
-	fmt.Printf("Validation errors: %d\n", totalErrors)
+	return stats, nil
+}
 
-	// Find largest skills
-	sort.Slice(skills, func(i, j int) bool {
-		return skills[i].Tokens.Total > skills[j].Tokens.Total
-	})
+// renderSection produces the deterministic heading + (preserved) paragraph +
+// bullets + optional warning block. When the skill has errors, all three
+// token bullets show 0 and a blockquote warning is appended.
+func renderSection(s SkillInfo, paragraph []string) []string {
+	var out []string
+	out = append(out, "### "+s.Dir, "")
+	out = append(out, paragraph...)
+	out = append(out, "")
+
+	nameDesc, bodyTk, totalTk := 0, 0, 0
+	if len(s.Errors) == 0 {
+		nameDesc = s.Tokens.Name + s.Tokens.Description
+		bodyTk = s.Tokens.Body
+		totalTk = s.Tokens.Total
+	}
+	out = append(out,
+		fmt.Sprintf("- Install with: `make %s`", s.Dir),
+		fmt.Sprintf("- Source: [skills/%s/](skills/%s/)", s.Dir, s.Dir),
+		fmt.Sprintf("- Name/desc: %d tok", nameDesc),
+		fmt.Sprintf("- SKILL.md: %d tok", bodyTk),
+		fmt.Sprintf("- Full skill: %d tok (all `.md` files in the folder)", totalTk),
+	)
+
+	if len(s.Errors) > 0 {
+		out = append(out, "", "> ⚠️ **Errors:**")
+		for _, e := range s.Errors {
+			out = append(out, "> - "+e)
+		}
+	}
+	return out
+}
 
-	fmt.Println("\nLargest skills (by total tokens):")
-	for i := 0; i < 5 && i < len(skills); i++ {
-		skill := skills[i]
-		if comp, ok := comparisons[skill.Dir]; ok {
-			sign := "+"
-			if comp.Delta < 0 {
-				sign = ""
-			}
-			fmt.Printf("  %d. %-40s %5d tokens (%s%d)\n",
-				i+1, skill.Dir, skill.Tokens.Total, sign, comp.Delta)
-		} else {
-			fmt.Printf("  %d. %-40s %5d tokens\n", i+1, skill.Dir, skill.Tokens.Total)
-		}
+func appendBlankSep(lines []string) []string {
+	if len(lines) == 0 {
+		return lines
 	}
+	if lines[len(lines)-1] != "" {
+		return append(lines, "")
+	}
+	return lines
+}
 
-	// Show biggest changes if comparing
-	if comparisons != nil && len(comparisons) > 0 {
-		type changeEntry struct {
-			name string
-			comp SkillComparison
-		}
-		var changes []changeEntry
-		for name, comp := range comparisons {
-			changes = append(changes, changeEntry{name, comp})
-		}
-
-		sort.Slice(changes, func(i, j int) bool {
-			absI := changes[i].comp.Delta
-			if absI < 0 {
-				absI = -absI
-			}
-			absJ := changes[j].comp.Delta
-			if absJ < 0 {
-				absJ = -absJ
-			}
-			return absI > absJ
-		})
+func trimTrailingBlanks(lines []string) []string {
+	end := len(lines)
+	for end > 0 && strings.TrimSpace(lines[end-1]) == "" {
+		end--
+	}
+	return lines[:end]
+}
 
-		fmt.Println("\nBiggest changes:")
-		displayed := 0
-		for _, change := range changes {
-			if displayed >= 5 {
-				break
-			}
-			sign := "+"
-			if change.comp.Delta < 0 {
-				sign = ""
-			}
-			indicator := ""
-			if change.comp.IsNew {
-				indicator = " [NEW]"
-			} else if change.comp.Percent > 20 {
-				indicator = " ⚠️"
-			} else if change.comp.Percent < -20 {
-				indicator = " βœ“"
-			}
-			fmt.Printf("  %-40s %s%-5d tokens (%s%.1f%%)%s\n",
-				change.name, sign, change.comp.Delta, sign, change.comp.Percent, indicator)
-			displayed++
-		}
+func trimLeadingBlanks(lines []string) []string {
+	start := 0
+	for start < len(lines) && strings.TrimSpace(lines[start]) == "" {
+		start++
 	}
+	return lines[start:]
 }