Skip to content

Commit 2eb3c5c

Browse files
committed
skills(content-filename-from-title): add reference.md
Progressive-disclosure reference per Anthropic's skill authoring best practices — loaded only when SKILL.md's decision procedure is not enough. Covers edge cases (tied candidates, non-English titles, acronyms, proper nouns), manifest types beyond tour.json, decision history explaining why each of the 8 current filenames was picked over alternatives, a rejected-candidates catalog, and regex/validator quick-checks. ToC at the top so the file is useful via partial read. SKILL.md's Further Reading section now links it one level deep, matching the progressive-disclosure pattern.
1 parent 2dc057b commit 2eb3c5c

2 files changed

Lines changed: 380 additions & 0 deletions

File tree

.claude/skills/content-filename-from-title/SKILL.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ again.
298298
</when-not-to-use>
299299

300300
<further-reading>
301+
- [reference.md](./reference.md) — extended reference: edge cases,
302+
manifest types beyond `tour.json`, acronym / proper-noun handling,
303+
decision history for the 8 current filenames, rejected candidates,
304+
regex/validator quick-checks.
301305
- `CLAUDE.md` § ERROR MESSAGES — the error-shape the filename
302306
validator uses when it rejects a bad filename.
303307
- `docs/pages-design-system.md` — the surrounding design system for
Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
# content-filename-from-title Reference
2+
3+
Extended reference material for the `content-filename-from-title`
4+
skill. SKILL.md carries the decision procedure + the 8 worked
5+
examples from the live tour manifest; this file carries the edge
6+
cases, the alternative-manifest-type guidance, and the decision
7+
history that explains why the procedure looks the way it does.
8+
9+
## Table of Contents
10+
11+
1. [When the procedure in SKILL.md does not produce a clean word](#edge-cases)
12+
2. [Manifest types beyond tour.json](#manifest-types)
13+
3. [Handling acronyms, proper nouns, and non-English titles](#special-tokens)
14+
4. [Decision history for the 8 current filenames](#decision-history)
15+
5. [Rejected candidates and why](#rejected-candidates)
16+
6. [Regex + validator reference](#regex--validator)
17+
7. [Cross-references](#cross-references)
18+
19+
---
20+
21+
<a id="edge-cases"></a>
22+
## 1. When the procedure in SKILL.md does not produce a clean word
23+
24+
The decision procedure covers the common cases. When it does not, apply these tie-breakers in order.
25+
26+
### Two words feel equally strong
27+
28+
If Steps 2–4 produce two candidates that both read as "the topic" —
29+
pick the one that is:
30+
31+
1. **Shorter.** Fewer keystrokes, fewer chances to mistype.
32+
2. **More common English.** A word the reader has seen before in other contexts.
33+
3. **Closer to the verb form your neighbors use.** If the manifest's other entries are all gerunds, prefer the gerund. If all `-ion` forms, prefer `-ion`. Internal consistency is worth more than individually-optimal word choice.
34+
35+
If still tied — flip a coin or ask the user; the downstream cost of a wrong pick is low because the filename validator only checks shape, not "optimality."
36+
37+
### The title is in a second language
38+
39+
Translate to English first, then apply the procedure. The filename
40+
is a URL segment consumed globally; English is the lowest-common-
41+
denominator, not a cultural preference. If the original-language
42+
word happens to be a better fit *and* passes `^[a-z]+$`, that is
43+
acceptable — but default to English.
44+
45+
### The title is a person's name, a product name, or a brand
46+
47+
Use the most recognizable single-word form of the name:
48+
49+
- "Linus Torvalds" → `linus` (if the name is the topic) or
50+
`torvalds` (if the surname is more distinguishing in the set).
51+
- "Meander" → `meander`.
52+
- "Package URL" → `purl` **only if** `purl` is not already taken
53+
elsewhere in the manifest. Otherwise pick the action, e.g.
54+
`parsing` or `conversion`.
55+
56+
Don't transliterate, strip accents, or otherwise mangle the name.
57+
Either the plain ASCII form works (`rust`, `kubernetes`) or you
58+
need to pick a different word.
59+
60+
### Every candidate noun is generic
61+
62+
If the title is something like "Overview" or "Introduction" and
63+
you're staring at `overview`, `introduction`, `intro`, `summary`
64+
these are all generic. Escape by picking a word that describes
65+
**the thing you are introducing**, not the act of introduction:
66+
67+
- "Introduction to PURLs" → `purl` (the subject, not the gerund).
68+
- "Overview of the Build Pipeline" → `pipeline`.
69+
70+
If the title truly has no distinguishing noun, the content probably
71+
needs a better title first. Push back on the title author before
72+
inventing a filename.
73+
74+
### The title contains a URL-unsafe character
75+
76+
Unicode, emoji, arrows, operators — all of these have to be
77+
translated to the nearest ASCII equivalent during Step 6:
78+
79+
- `URL ↔ PURL Conversion` → the arrow is visual; ignore it for
80+
filename purposes. The nouns are `URL`, `PURL`, `Conversion`.
81+
- `C++ Primer``++` is not part of a noun; the noun is `C` but
82+
`c` is too short to be a good filename. Use the surrounding
83+
context: `cpp`? `primer`? This is an acronym case (below).
84+
85+
### The title has only one noun and it's already taken
86+
87+
Go to the verb:
88+
89+
- "Injection" is already used. Title: "Injection". → `detecting`.
90+
- Go to the object: "Injection of What?" → `characters`,
91+
`sequences`.
92+
- Last resort: add a one-word modifier: `injection-safety` **is
93+
forbidden** (hyphen), but `hardening` works (we actually picked
94+
this one for a safety doc). Pick a synonym that hits the same
95+
concept.
96+
97+
---
98+
99+
<a id="manifest-types"></a>
100+
## 2. Manifest types beyond tour.json
101+
102+
The skill's primary home is `tour.json` entries, but the rules
103+
generalize. Quick guide per manifest type:
104+
105+
### tour.json parts
106+
107+
What you've seen — `anatomy`, `building`, `parsing`, etc. Internal
108+
consistency matters because there are 8+ of them in a visible list.
109+
Stick with one style family (all gerunds / all `-ion` / all plain
110+
nouns) where possible.
111+
112+
### tour.json docs (topics)
113+
114+
Same rules as parts. Current entries: `architecture`, `builders`,
115+
`converters`, `hardening`, `release`, `tour`, `vers`,
116+
`contributing`. These lean toward plain nouns (no gerunds) because
117+
they are *topics*, not actions. Stay consistent with that style if
118+
you add one.
119+
120+
### docs/*.md without a manifest
121+
122+
If a doc lives under `docs/` and isn't registered in `tour.json`,
123+
its filename still follows the skill's rules. Examples:
124+
`docs/api.md`, `docs/types.md`, `docs/pages-design-system.md`.
125+
Multi-word here (`pages-design-system`) is acceptable **only when**
126+
it names a compound domain that no single word covers. `api` and
127+
`types` are single words; `pages-design-system` is tolerated
128+
because "design system" is a term of art.
129+
130+
### Blog slugs, if one ever exists
131+
132+
Blog posts usually get 2–4 word slugs — different grammar from the
133+
tour. The skill's rules don't apply. Use this skill only for
134+
**content identifiers in a bounded manifest**, not for prose-heavy
135+
URLs.
136+
137+
### CLI subcommand names
138+
139+
Subcommands (`pnpm tour:build`, `pnpm tour:serve`) follow the same
140+
rules — short, lowercase, content-bearing. `:` is the separator
141+
within pnpm; each segment on its own side of the colon should pass
142+
`^[a-z]+$`.
143+
144+
### API route segments
145+
146+
Same rules again. A REST endpoint at `/anatomy/:id` is more
147+
readable than `/walkthrough-part/:id`.
148+
149+
---
150+
151+
<a id="special-tokens"></a>
152+
## 3. Handling acronyms, proper nouns, and non-English titles
153+
154+
### Acronyms
155+
156+
All-lowercase the acronym and use as a single word:
157+
158+
- `URL``url`
159+
- `JSON``json`
160+
- `SBOM``sbom`
161+
- `CSS``css`
162+
- `OIDC``oidc`
163+
164+
Do **not** expand (`json` not `javascriptobjectnotation`). Do not
165+
CamelCase (`json` not `JSON`, which violates `^[a-z]+$`). Do not
166+
hyphenate (`sbom` not `s-bom`).
167+
168+
If the acronym clashes with another entry, fall back to a noun
169+
that describes what the acronym *is about*`json` collides? Use
170+
`config` or `manifest` depending on context.
171+
172+
### Proper nouns / product names
173+
174+
Use the most recognizable lowercase form:
175+
176+
- `GitHub``github`
177+
- `TypeScript``typescript` (or `ts` if space-constrained, but
178+
`typescript` is preferred)
179+
- `Socket.dev``socket`
180+
- `Val Town``valtown` (concatenated — space is not legal)
181+
182+
### Non-English source text
183+
184+
Translate to English. If the English form is awkward, pick the
185+
underlying concept noun:
186+
187+
- "こんにちは" → the title is a greeting; use `greeting` or
188+
`hello`, not transliterate to `konnichiwa`.
189+
- "packageurl-js 入門" → `intro` or the target ("PURL") — `purl` or
190+
`getting-started`-style (but this skill is for single words, so
191+
`intro` or `purl`).
192+
193+
---
194+
195+
<a id="decision-history"></a>
196+
## 4. Decision history for the 8 current filenames
197+
198+
Why each of the 8 tour-part filenames was chosen over alternatives.
199+
200+
### Part 1: `anatomy` (title: "Anatomy of a PURL")
201+
202+
Candidates: `anatomy`, `purl`, `structure`, `shape`.
203+
204+
- `purl` — rejected, shared with Parts 2 and 5.
205+
- `structure` — considered; `anatomy` is more distinctive and
206+
carries the right "take a thing apart to see how it works"
207+
connotation.
208+
- `shape` — too visual.
209+
- Winner: `anatomy`. Clinical, short, distinctive.
210+
211+
### Part 2: `building` (title: "Building & Stringifying PURLs")
212+
213+
Candidates: `building`, `stringifying`, `construction`, `build`.
214+
215+
- `build` — same root, shorter, but less gerund-consistent with
216+
Part 3 (`parsing`).
217+
- `stringifying` — rejected. Stringifying is a specific substep
218+
(the final serialization). Building is the superset.
219+
- `construction` — considered; `building` is shorter + reads as an
220+
activity rather than an abstract state.
221+
- Winner: `building`. Gerund form matches Part 3's `parsing`.
222+
223+
### Part 3: `parsing` (title: "Parsing & Normalization")
224+
225+
Candidates: `parsing`, `normalization`, `parse`.
226+
227+
- `normalization` — rejected, it's a substep of parsing.
228+
- `parse` — shorter but breaks gerund consistency.
229+
- Winner: `parsing`.
230+
231+
### Part 4: `validation` (title: "Validation, Errors & Results")
232+
233+
Candidates: `validation`, `errors`, `results`, `validate`.
234+
235+
- `errors` — rejected, output of validation.
236+
- `results` — rejected, ambiguous (result pattern or outcomes?).
237+
- `validate` — gerund would be `validating`; `validation` is the
238+
`-ion` form, used here because the doc treats validation as a
239+
topic (the shape of errors/results), not an activity. Mixing
240+
`-ion` forms with gerunds is acceptable when the semantic shift
241+
is real.
242+
- Winner: `validation`.
243+
244+
### Part 5: `conversion` (title: "URL ↔ PURL Conversion")
245+
246+
Candidates: `conversion`, `url`, `purl`, `converter`.
247+
248+
- `url`, `purl` — rejected, both appear in multiple titles.
249+
- `converter` — the tool name, not the activity. Awkward as a URL
250+
segment (reads like a product name).
251+
- Winner: `conversion`.
252+
253+
### Part 6: `ecosystems` (title: "Ecosystems")
254+
255+
Candidates: `ecosystems`, `types`, `handlers`.
256+
257+
- `types` — rejected, too generic (collides with `docs/types.md`
258+
type reference).
259+
- `handlers` — technical, not reader-facing.
260+
- Winner: `ecosystems`. Plain noun, already in the title.
261+
262+
### Part 7: `comparison` (title: "Comparison, Matching & Existence")
263+
264+
Candidates: `comparison`, `matching`, `existence`, `compare`.
265+
266+
- `matching` — considered strongly; matching is adjacent to
267+
comparison with wildcards. Rejected as narrower than
268+
`comparison`.
269+
- `existence` — rejected, it's a separate concern (registry
270+
checks) awkwardly bundled into this part.
271+
- `compare` — shorter but less gerund-stable.
272+
- Winner: `comparison`.
273+
274+
### Part 8: `security` (title: "Security Primitives & VERS")
275+
276+
Candidates: `security`, `primitives`, `vers`, `safety`,
277+
`hardening`.
278+
279+
- `primitives` — rejected, too abstract.
280+
- `vers` — rejected. VERS is covered in `docs/vers.md`; having a
281+
tour part also use `vers` would be redundant and collision-prone.
282+
- `safety` / `hardening` — considered for the later-added
283+
`docs/hardening.md` doc. In the context of Part 8 (which is
284+
broader than injection detection), `security` reads as the
285+
superset.
286+
- Winner: `security`.
287+
288+
---
289+
290+
<a id="rejected-candidates"></a>
291+
## 5. Rejected candidates and why
292+
293+
A catalog of candidate filenames we considered and rejected. Use
294+
this to save time when picking a filename that someone has already
295+
thought about.
296+
297+
| Rejected | Because |
298+
|---|---|
299+
| `page` | Generic. Not content-bearing. Every page is "a page." |
300+
| `doc` | Generic. Every doc is "a doc." |
301+
| `item` | Generic. |
302+
| `content` | Generic. |
303+
| `index` | Reserved — conflicts with `index.html`. |
304+
| `part` | Too generic; collides with the structure word itself. |
305+
| `part1`, `part2`, … | Contains digit. Fails `^[a-z]+$`. Also unstable. |
306+
| `first`, `second` | Ordinal. Unstable; renames on reorder. |
307+
| `url-to-purl` | Contains hyphen. |
308+
| `buildingandstringifying` | Compound phrase. Forbidden. |
309+
| `howto` | Too generic; "how to do what?" |
310+
| `intro` | Okay as last resort but generic; prefer the subject. |
311+
| `purl` | In tour.json, collides with multiple parts. |
312+
| `errors` | In tour.json, redundant with `validation`. |
313+
| `primitives` | Abstract; always rejected in favor of the concrete noun. |
314+
315+
---
316+
317+
<a id="regex--validator"></a>
318+
## 6. Regex + validator reference
319+
320+
The build-time validator in `scripts/tour.mts` enforces three
321+
constraints. Use these when hand-validating a filename before
322+
running the build.
323+
324+
### Shape: `^[a-z]+$`
325+
326+
ASCII lowercase letters only. **No** digits, hyphens, underscores,
327+
dots, slashes, or unicode.
328+
329+
```regex
330+
^[a-z]+$
331+
```
332+
333+
Quick-check in a terminal:
334+
335+
```bash
336+
echo -n "yourfilename" | grep -qE '^[a-z]+$' && echo OK || echo FAIL
337+
```
338+
339+
### Uniqueness
340+
341+
Each manifest entry's `filename` must be unique **across all
342+
entries in the same manifest**. For `tour.json`, that means
343+
uniqueness across both `parts` and `docs` combined.
344+
345+
Quick-check:
346+
347+
```bash
348+
node -e "
349+
const c = JSON.parse(require('fs').readFileSync('tour.json','utf8'));
350+
const all = [...c.parts.map(p => p.filename), ...c.docs.map(d => d.filename)];
351+
const dupes = all.filter((f, i) => all.indexOf(f) !== i);
352+
if (dupes.length) console.log('DUPES:', dupes);
353+
else console.log('OK — ' + all.length + ' unique filenames');
354+
"
355+
```
356+
357+
### Presence
358+
359+
Every part has a `filename` set. Validator errors if missing.
360+
361+
---
362+
363+
<a id="cross-references"></a>
364+
## 7. Cross-references
365+
366+
- **SKILL.md** (this skill's main file) — the decision procedure
367+
and 8 worked examples.
368+
- `CLAUDE.md` § ERROR MESSAGES — the shape of errors the validator
369+
emits when you break a rule.
370+
- `docs/pages-design-system.md` — the surrounding design system for
371+
pages that use these filenames.
372+
- `docs/tour.md` — the tour pipeline, including how filenames
373+
become public URLs.
374+
- `scripts/tour.mts``validatePartFilenames()` +
375+
`validateDocFilenames()` — the two validator functions.
376+
- `tour.json` — the live manifest applying this skill.

0 commit comments

Comments
 (0)