content-filename-from-title
Public URLs age badly when filenames carry implementation detail
(walkthrough-part-1.html), numbering (part-3-thing.html), or
cluttering punctuation (url-%E2%86%94-purl-conversion.html). A
title-word filename (anatomy.html, parsing.html, conversion.html)
is short, speakable on a call, typeable, and doesn't need to change
when the surrounding ordering does.
Claude must apply the same reasoning every time, or the fleet of
filenames will drift stylistically across contributors and across
sessions. This skill captures the reasoning as a deterministic
procedure so the output is reproducible.
Where it fits in the repo
tour.json - the parts[].filename field is the URL segment
the page is published under at socketdev.github.io/socket-packageurl-js/<filename>.html.
docs/*.md - the file stem becomes the URL segment when docs are
stitched into the GH Pages flow (see docs/pages-design-system.md
for the surrounding design system).
- Any future blog or guide manifest added to this repo.
A build-time validator in scripts/tour.mts enforces the
shape ([a-z]+) and uniqueness; this skill decides the
choice (which word).
- ASCII lowercase letters only: matches
^[a-z]+$.
- No digits (
part2 - FORBIDDEN)
- No hyphens (
url-conversion - FORBIDDEN)
- No underscores, dots, slashes, or unicode
- Unique across all entries in the manifest. If the word you picked
is already taken, pick another that's still content-bearing.
- Single word. Compound phrases (
buildingandstringifying) are
FORBIDDEN - pick the stronger of the two nouns instead.
Soft constraints (style)
- Typeable. A user on a call should be able to say "go to the
anatomy page" and the listener can type it correctly without
spelling.
- Stable. The word should still make sense if the surrounding
ordering changes.
part-one is unstable (it renames when content
is reordered); anatomy is stable.
- Content-bearing, not generic.
page, doc, content, item
are FORBIDDEN. Pick a word that would still be meaningful if you
only saw it in a URL with no context.
Apply these rules in order. Stop at the first rule that produces a
clean single word.
Step 1 - Inventory the nouns in the title
Write out every noun and nominalized action (gerund, -ion, -ance).
Discard every filler word (articles, prepositions, conjunctions, "of",
"and", "&"). Discard every word that appears in 2+ other titles of the
same manifest - those are qualifiers, not distinguishers.
Why: a filename needs to distinguish this page from its
siblings. A word that isn't unique within the set can never be
load-bearing.
Step 2 - Among remaining candidates, pick the distinguishing noun
If one noun is unique to this title and the others are not, that noun
wins.
Example: "URL ↔ PURL Conversion" has three nouns (URL,
PURL, Conversion). URL and PURL appear in multiple titles;
Conversion is unique to this one. → conversion.
Step 3 - If several nouns are candidates, pick the superset
If the title lists multiple concepts that are facets of one bigger
concept, pick the bigger one.
Example: "Validation, Errors & Results" - errors and
Result<T,E> are outputs of validation. → validation.
Step 4 - If the title is "verb on a subject", pick the verb's nominal form
Gerund (-ing) if the activity itself is the topic; -ion / -ance
if the state or result is the topic.
Example: "Parsing & Normalization" - normalization is a
substep of parsing. The activity is the topic. → parsing.
Step 5 - If the title is a plain subject noun, use it directly
If the title is already a single content noun (Ecosystems), that's
the filename. Just lowercase it.
Step 6 - Check hard constraints, then pick an alternative if needed
Now validate the chosen word against the hard constraints:
- Does it match
^[a-z]+$? If not, reshape: URL ↔ PURL → consider
nominals like conversion, not urltopurl.
- Is it unique across the manifest? If not, go back to Step 2 and
pick the next-best candidate.
- Is it content-bearing? If it's generic (
items, details), go
back to Step 3 - you probably picked too abstract a word.
Step 7 - Sanity check
Read your picks as a list. Does it feel like a coherent table of
contents? If one word feels off-tempo (too long, too clinical, too
cute), adjust. Internal consistency matters - don't mix
gerunds + nouns + adjectives.
These are the filenames currently in tour.json at the time
this skill was written. Each shows the rule that produced the choice.
Counter-examples - choices the procedure rejects
Copy this into your working notes when adding/renaming a manifest
entry:
Filename choice: _______________
- [ ] Matches ^[a-z]+$ (lowercase ASCII letters only)
- [ ] Unique across every other entry in the manifest
- [ ] Content-bearing (not 'page', 'item', 'content', etc.)
- [ ] Stable under reordering (no 'part1', 'first', etc.)
- [ ] Typeable from hearing it spoken
- [ ] Feels consistent with neighbor filenames' style (all gerunds?
all plain nouns? all -ion forms? one style across the set)
If any checkbox fails, return to the decision procedure and pick
again.
- The filename is internal (e.g. a build artifact under
dist/,
an intermediate JSON in .cache/). Internal paths don't need to be
pretty - use whatever the code naturally emits.
- The filename is code-shaped, not content-shaped. TypeScript
source files follow the convention of the ecosystem (kebab-case,
matching export names). This skill is for content filenames only.
- The manifest exposes a hash or date-based identifier (e.g.
a release slug, a git-sha-addressable blob). Use the hash; it's
already optimal.
1---2name: content-filename-from-title3description: Create short URL-friendly filenames from prose titles. Use when naming docs, guides, or slugs.4---56# content-filename-from-title78<task>9Produce a single-word, lowercase, ASCII-only filename (no extension,10no hyphens, no digits) that best represents the content of a titled11page. The filename goes into a config manifest - `tour.json`12part entries, `docs/` frontmatter, or similar - where it becomes the13public URL segment for that page.14</task>1516<context>17## Why this skill exists1819Public URLs age badly when filenames carry implementation detail20(`walkthrough-part-1.html`), numbering (`part-3-thing.html`), or21cluttering punctuation (`url-%E2%86%94-purl-conversion.html`). A22title-word filename (`anatomy.html`, `parsing.html`, `conversion.html`)23is short, speakable on a call, typeable, and doesn't need to change24when the surrounding ordering does.2526Claude must apply the same reasoning every time, or the fleet of27filenames will drift stylistically across contributors and across28sessions. This skill captures the reasoning as a deterministic29procedure so the output is reproducible.3031## Where it fits in the repo3233- `tour.json` - the `parts[].filename` field is the URL segment34 the page is published under at `socketdev.github.io/socket-packageurl-js/<filename>.html`.35- `docs/*.md` - the file stem becomes the URL segment when docs are36 stitched into the GH Pages flow (see `docs/pages-design-system.md`37 for the surrounding design system).38- Any future blog or guide manifest added to this repo.3940A build-time validator in `scripts/tour.mts` enforces the41**shape** (`[a-z]+`) and **uniqueness**; this skill decides the42**choice** (which word).43</context>4445<constraints>46## Hard constraints (validator-enforced)4748- **ASCII lowercase letters only:** matches `^[a-z]+$`.49 - No digits (`part2` - FORBIDDEN)50 - No hyphens (`url-conversion` - FORBIDDEN)51 - No underscores, dots, slashes, or unicode52- **Unique across all entries in the manifest.** If the word you picked53 is already taken, pick another that's still content-bearing.54- **Single word.** Compound phrases (`buildingandstringifying`) are55 FORBIDDEN - pick the stronger of the two nouns instead.5657## Soft constraints (style)5859- **Typeable.** A user on a call should be able to say "go to the60 `anatomy` page" and the listener can type it correctly without61 spelling.62- **Stable.** The word should still make sense if the surrounding63 ordering changes. `part-one` is unstable (it renames when content64 is reordered); `anatomy` is stable.65- **Content-bearing, not generic.** `page`, `doc`, `content`, `item`66 are FORBIDDEN. Pick a word that would still be meaningful if you67 only saw it in a URL with no context.68 </constraints>6970<instructions>71## Decision procedure7273Apply these rules in order. Stop at the first rule that produces a74clean single word.7576### Step 1 - Inventory the nouns in the title7778Write out every noun and nominalized action (gerund, `-ion`, `-ance`).79Discard every filler word (articles, prepositions, conjunctions, "of",80"and", "&"). Discard every word that appears in 2+ other titles of the81same manifest - those are qualifiers, not distinguishers.8283> **Why:** a filename needs to distinguish this page from its84> siblings. A word that isn't unique within the set can never be85> load-bearing.8687### Step 2 - Among remaining candidates, pick the distinguishing noun8889If one noun is unique to this title and the others are not, that noun90wins.9192> **Example:** `"URL ↔ PURL Conversion"` has three nouns (`URL`,93> `PURL`, `Conversion`). `URL` and `PURL` appear in multiple titles;94> `Conversion` is unique to this one. → `conversion`.9596### Step 3 - If several nouns are candidates, pick the superset9798If the title lists multiple concepts that are facets of one bigger99concept, pick the bigger one.100101> **Example:** `"Validation, Errors & Results"` - errors and102> `Result<T,E>` are outputs of validation. → `validation`.103104### Step 4 - If the title is "verb on a subject", pick the verb's nominal form105106Gerund (`-ing`) if the activity itself is the topic; `-ion` / `-ance`107if the state or result is the topic.108109> **Example:** `"Parsing & Normalization"` - normalization is a110> substep of parsing. The activity is the topic. → `parsing`.111112### Step 5 - If the title is a plain subject noun, use it directly113114If the title is already a single content noun (`Ecosystems`), that's115the filename. Just lowercase it.116117### Step 6 - Check hard constraints, then pick an alternative if needed118119Now validate the chosen word against the hard constraints:1201211. Does it match `^[a-z]+$`? If not, reshape: `URL ↔ PURL` → consider122 nominals like `conversion`, not `urltopurl`.1232. Is it unique across the manifest? If not, go back to Step 2 and124 pick the next-best candidate.1253. Is it content-bearing? If it's generic (`items`, `details`), go126 back to Step 3 - you probably picked too abstract a word.127128### Step 7 - Sanity check129130Read your picks as a list. Does it feel like a coherent table of131contents? If one word feels off-tempo (too long, too clinical, too132cute), adjust. Internal consistency matters - don't mix133`gerunds` + `nouns` + `adjectives`.134</instructions>135136<examples>137## Worked examples - the 8 tour parts138139These are the filenames currently in `tour.json` at the time140this skill was written. Each shows the rule that produced the choice.141142<details>143<summary>All 8 tour filenames with the rule that produced each: anatomy, building, parsing, validation, conversion, ecosystems, comparison and security</summary>144145<example id="1">146<title>Anatomy of a PURL</title>147<filename>anatomy</filename>148<reasoning>149Nouns: `Anatomy`, `PURL`. `PURL` appears in 3 other titles (parts 2, 5),150so it's a qualifier, not a distinguisher. `Anatomy` is unique. → `anatomy`.151Rule applied: Step 2 (distinguishing noun).152</reasoning>153</example>154155<example id="2">156<title>Building & Stringifying PURLs</title>157<filename>building</filename>158<reasoning>159Nouns / gerunds: `Building`, `Stringifying`, `PURLs`. `PURLs` is a160qualifier. Stringifying is a substep of building (serialize is the161last step of building). → `building`.162Rule applied: Step 3 (superset) + Step 4 (gerund).163</reasoning>164</example>165166<example id="3">167<title>Parsing & Normalization</title>168<filename>parsing</filename>169<reasoning>170Nouns: `Parsing`, `Normalization`. Normalization is a substep of171parsing. The activity is the topic. → `parsing`.172Rule applied: Step 4 (gerund form).173</reasoning>174</example>175176<example id="4">177<title>Validation, Errors & Results</title>178<filename>validation</filename>179<reasoning>180Nouns: `Validation`, `Errors`, `Results`. Errors and Result<T,E> are181the outputs/facets of validation. → `validation`.182Rule applied: Step 3 (superset).183</reasoning>184</example>185186<example id="5">187<title>URL ↔ PURL Conversion</title>188<filename>conversion</filename>189<reasoning>190Nouns: `URL`, `PURL`, `Conversion`. `URL` and `PURL` are the domain191(appears in multiple titles). `Conversion` is unique.192→ `conversion`. Rule applied: Step 2 (distinguishing noun).193</reasoning>194</example>195196<example id="6">197<title>Ecosystems</title>198<filename>ecosystems</filename>199<reasoning>200Title is already a single content noun. Lowercase it.201Rule applied: Step 5 (plain subject noun).202</reasoning>203</example>204205<example id="7">206<title>Comparison, Matching & Existence</title>207<filename>comparison</filename>208<reasoning>209Nouns: `Comparison`, `Matching`, `Existence`. Matching is a flavor of210comparison (wildcard comparison). Existence is adjacent but weaker.211→ `comparison`. Rule applied: Step 3 (superset).212</reasoning>213</example>214215<example id="8">216<title>Security Primitives & VERS</title>217<filename>security</filename>218<reasoning>219Nouns: `Security`, `Primitives`, `VERS`. In this curriculum VERS is220scoped under security (injection + freeze + VERS-as-safety-boundary).221→ `security`. Rule applied: Step 3 (superset).222</reasoning>223</example>224225</details>226227## Counter-examples - choices the procedure rejects228229<details>230<summary>Four rejected filenames and the constraint each breaks: `purl` is not distinguishing, `buildingandstringifying` merges two words, `url-to-purl` carries a hyphen, and `page6` carries a digit</summary>231232<example id="bad-1">233<title>Anatomy of a PURL</title>234<rejected>purl</rejected>235<reasoning>236`PURL` appears in multiple titles → fails Step 1 (not237distinguishing). Also fails uniqueness against any other part that238might want `purl`.239</reasoning>240</example>241242<example id="bad-2">243<title>Building & Stringifying PURLs</title>244<rejected>buildingandstringifying</rejected>245<reasoning>246Compound phrase - violates the "single word" hard constraint. The247procedure always picks one over merging.248</reasoning>249</example>250251<example id="bad-3">252<title>URL ↔ PURL Conversion</title>253<rejected>url-to-purl</rejected>254<reasoning>255Contains a hyphen → fails the `[a-z]+` hard constraint. The validator256would reject this at build time; the skill catches it earlier at257Step 6.258</reasoning>259</example>260261<example id="bad-4">262<title>Ecosystems</title>263<rejected>page6</rejected>264<reasoning>265Numeric, generic, unstable to reordering, not content-bearing. Fails266hard constraints (digits) and soft constraints (stability,267content-bearing).268</reasoning>269</example>270</details>271272</examples>273274<checklist>275## Checklist before committing a filename276277Copy this into your working notes when adding/renaming a manifest278entry:279280```281Filename choice: _______________282283- [ ] Matches ^[a-z]+$ (lowercase ASCII letters only)284- [ ] Unique across every other entry in the manifest285- [ ] Content-bearing (not 'page', 'item', 'content', etc.)286- [ ] Stable under reordering (no 'part1', 'first', etc.)287- [ ] Typeable from hearing it spoken288- [ ] Feels consistent with neighbor filenames' style (all gerunds?289 all plain nouns? all -ion forms? one style across the set)290```291292If any checkbox fails, return to the decision procedure and pick293again.294</checklist>295296<when-not-to-use>297## When NOT to use this skill298299- The filename is **internal** (e.g. a build artifact under `dist/`,300 an intermediate JSON in `.cache/`). Internal paths don't need to be301 pretty - use whatever the code naturally emits.302- The filename is **code-shaped**, not content-shaped. TypeScript303 source files follow the convention of the ecosystem (kebab-case,304 matching export names). This skill is for _content_ filenames only.305- The manifest exposes a **hash** or **date-based identifier** (e.g.306 a release slug, a git-sha-addressable blob). Use the hash; it's307 already optimal.308 </when-not-to-use>309310<further-reading>311- [reference.md](./reference.md) - extended reference: edge cases,312 manifest types beyond `tour.json`, acronym / proper-noun handling,313 decision history for the 8 current filenames, rejected candidates,314 regex/validator quick-checks.315- `CLAUDE.md` § ERROR MESSAGES - the error-shape the filename316 validator uses when it rejects a bad filename.317- `docs/pages-design-system.md` - the surrounding design system for318 pages that use these filenames.319- `scripts/tour.mts` → `validatePartFilenames()` - the320 validator implementation that enforces the hard constraints.321- `tour.json` - the current live manifest applying this skill.322</further-reading>