@@ -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
@@ -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:]
}