feat(coding-agent): list a large skill catalog one line per skill
Fixes #4449
What
A large skill catalog is listed one line per skill instead of a five-line XML block:
<available_skills>
- smart-rebase [r0]: Rebase the current branch onto the right base branch with local conflict handling…
- gpt-image-gen [r1/bundle/SKILL-3R5ZD4J5.md]: MUST read before generating images…
</available_skills>
- Location is the root alias alone when the file is
<root>/<name>/SKILL.md(100 of 101 skills on a real catalog), else the path under the root, so every skill stays resolvable through the existing<skill_roots>table; one header line states the rule. - One-line descriptions are capped at 160 characters and keep the description's when-to-use sentence (
Use when/for/whenever ...,MUST USE ...,USE ONLY WHEN,This skill should be used when,Triggers: ...,Korean triggers: ...,Triggers - ...): a cut keeps that sentence whole (or its start, when it alone is longer than the line) and fills the remaining room with the leading what-it-does text, then the text after it, cut at word boundaries. Multi-line descriptions are flattened first, so a trigger on a later line still counts. A catalog whose 160-character lines overflow the 48 KiB cap tries 100-character lines before dropping descriptions. - Compact mode also triggers from 16 KiB of descriptions (was 64 KiB); the 100-skill count trigger is unchanged. A ~100-skill personal library no longer falls back to full multi-sentence descriptions when a few skills are hidden.
- The names-only fallback (over 48 KiB) uses the same line form without descriptions, and its header says
- name [location](no: when to use it). Small catalogs render exactly as before. - The
<available_skills>wrapper and the catalog marker sentence are kept, so the Claude-subscription prompt extractor still finds the block.
Measured: when-to-use clause survival vs tokens (review FIX_FIRST at 7da8afe7b9)
Real libraries on a dev machine: ~/.agents/skills (71 skills, 65 with a trigger clause) and the same plus the omo plugin skills (97 skills, 89 with a clause). Tokens are an o200k estimate (js-tiktoken) of the whole rendered skills section. "Whole" = the full trigger sentence appears in the line; "start" = its first 40 characters appear.
| set | version | clause whole | clause start | tokens | bytes |
|---|---|---|---|---|---|
| ~/.agents/skills (71) | main | 64/65 | 64/65 | 10,335 | 41,211 |
| ~/.agents/skills (71) | pr-7da8afe7b9 | 4/65 | 16/65 | 2,270 | 8,938 |
| ~/.agents/skills (71) | new | 39/65 | 64/65 | 3,252 | 13,111 |
| + omo plugin skills (97) | main | 19/89 | 52/89 | 7,573 | 28,445 |
| + omo plugin skills (97) | pr-7da8afe7b9 | 4/89 | 17/89 | 3,057 | 12,112 |
| + omo plugin skills (97) | new | 62/89 | 88/89 | 4,374 | 17,735 |
main renders the 71-skill set in full XML (below its thresholds) and the 97-skill set with a 160-character head cut (over the 48 KiB cap). The previous head of this PR (100-character head cut) kept the whole clause for 4 skills.
Candidate rules, whole-clause survival / tokens (71-skill set · 97-skill set):
| rule | 100 | 120 | 140 | 160 | 180 |
|---|---|---|---|---|---|
| head cut | 4/65 / 2,261 · 4/89 / 3,043 | 6/65 / 2,554 · 7/89 / 3,449 | 9/65 / 2,877 · 11/89 / 3,874 | 13/65 / 3,146 · 19/89 / 4,265 | 17/65 / 3,398 · 23/89 / 4,602 |
| trigger-first | 25/65 / 2,288 · 36/89 / 3,096 | 28/65 / 2,598 · 44/89 / 3,507 | 33/65 / 2,920 · 56/89 / 3,928 | 39/65 / 3,219 · 62/89 / 4,332 | 42/65 / 3,480 · 66/89 / 4,680 |
The bar was the 160-character baseline: a clause survives whenever it fits in 160 characters (39/65 and 62/89 clauses are that short). Trigger-first at 160 meets it exactly at the lowest cost of any rule that does; 140 falls to 33/65. A head cut never comes close at any cap.
Tests
- RED -> GREEN
test/suite/regressions/4449-compact-skill-catalog-lines.test.ts: onmainboth original cases fail (expected false to be truefor the 16 KiB trigger,expected [] to have a length of 98for the line form). The new table-driven casekeeps the when-to-use clause: $shapecovers 11 description shapes (Use when / Use for / Use this whenever / MUST USE first / USE ONLY WHEN / This skill should be used when / Triggers: / Korean triggers / Triggers - Korean list / trigger on a later line / clause longer than the line). Against the previous head 7da8afe7b9, 11 of 13 cases failed (RED); all 13 pass now (GREEN). It also resolves every listed line back to the skill's file through the roots table. The short-trigger case also asserts that the lead fill survives (the line starts withOperates the deployment toolchain); replacing the lead fill with""makes that case fail. - Updated
3460-context-file-size-cap.test.tsfor the line form and the new byte threshold. Its 400-skill case also covers the 100-character fallback tier. 3483-prompt-goldens.json: onlyprompts.compactchanged, and only between</skill_roots>and</available_skills>. The fixture's two-line descriptions are now flattened ("... verify behavior. Keep the full description."); every other key is byte-identical.docs/settings.mdnow states the real thresholds (16 KiB, 160 characters), the one-line format, and the trigger-sentence rule.- 8 related files, 164 tests green. Push gates (each its own process):
git diff --check origin/main...HEAD,bun run check, those tests, andnode scripts/check-pr-changelog.mjs.
Follow-up
- #4520: the generic verb branch of the trigger detection (
Run/Load/Call ... for/before/if) can pick imperative prose over the real when-to-use sentence (review LOW 1; 0 misfires on real libraries, synthetic repro in the issue). - #4518:
rewriteSkillsLocations(Anthropic subscription lane) has not rewritten any location since the roots-table aliases landed. It is pre-existing and out of scope here.