FOSUtilities API Catalog — Update
This skill keeps .claude/skills/shared/api-catalog/ truthful and complete. The
catalog is the discovery surface for every project that imports FOSUtilities
(via the fosutilities-api-catalog skill); an entry that lies about the API is
worse than a missing one, which is why the audit fails CI on stale entries but
only warns on gaps.
Run this on macOS — that builds the full API surface. On Linux the
Apple-only modules (FOSReporting) produce no symbol graph, and the audit skips
the stale checks for those files, so a Linux run can pass while an Apple-only
entry is stale. (This platform note lives here, in the maintainer skill — never
in the catalog files themselves, which serve the customer audience only.)
Audit-driven workflow
Run the audit from the package root:
swift scripts/api-catalog-audit.swift
It builds the package (swift package dump-symbol-graph — may take a
while), then reports three sections: Catalog gaps (warning), Stale
entries (ERROR — exit code 1), and the DocC worklist (warning).
Gotcha: the gaps section and the DocC-worklist section share the same
Module: symbol [source file] line format. Any grep over the output must be
scoped to one section, e.g.:
swift scripts/api-catalog-audit.swift | awk '/== Catalog gaps/,/== Stale/'
Note that piping through awk masks the audit's exit code — always check
exit status on an unpiped run.
Fix stale entries FIRST — they fail CI. A stale entry means the API
changed out from under the catalog: a symbol named in an entry's ### title
no longer exists in that file's modules. Read the source to find what
happened, then either update the entry's title symbols (rename), rewrite the
entry (redesign), or remove it (deletion).
Cross-module caveat: each catalog file's stale universe is only its own
modules' symbol graphs (see moduleToCatalog in the audit script). A title
symbol that actually lives in another file's module reports stale even
though it exists. Remediation is moving the entry to the owning module's
catalog file — or, rarely, the ignore list.
Write a curated entry for each gap, per the entry format rules below.
Always read the actual source first — never write an entry from the symbol
name alone. If the symbol is not customer-reachable (deprecated,
compatibility shim, macro plumbing), add its base identifier to
scripts/api-catalog-ignore.txt with a # comment stating the reason —
never leave a silent gap. The comment goes on its own line above the
identifier: the script only strips full-line comments, so an inline
name # reason would fail to match the symbol.
Update the reach-for index when a new entry serves a reach that the
discovery skill (.claude/skills/fosutilities-api-catalog/SKILL.md) doesn't
index yet. Index lines are keyed by what the implementor is reaching for
(Reaching for URLSession…), never by our library layout, and point to a
real ## header: `FILE.md § Section`.
Report the DocC-worklist count. No DocC action happens in this workflow
— the count is the measurable backlog for the separate DocC effort. Just
surface it in your report so the trend is visible.
Re-run the audit and repeat until: 0 stale entries, 0 non-ignored gaps,
exit code 0 (checked on an unpiped run — see step 1). Catalog-only edits
don't invalidate the symbol graphs, so re-runs can skip the package rebuild
by passing --symbolgraph-dir <dir> with the symbol-graph directory the
first run produced (under .build/).
Bump the plugin version in .claude-plugin/plugin.json. Consumers only
receive catalog and skill updates on a version bump — an unbumped update
ships to no one. Catalog and skill content changes take a minor bump
(consumers should receive them; this repo's practice is minor bumps for
skill-doc updates); reserve patch bumps for typo-level fixes.
Non-audit path: index and structure maintenance
Some catalog work has no audit signal:
- Adding or adjusting reach-for index lines for already-catalogued
capabilities (a reach was missing, or its wording didn't match how
implementors think): edit the discovery skill's index directly, then do
step 7 (version bump).
- Renaming a
## section or splitting a catalog file: sweep the reach-for
indexes — the discovery skill and any CLAUDE.md index blocks — for dangling
`FILE.md § Section` pointers and fix them. Pointers are not
audited; a rename silently breaks them.
Entry format rules (binding)
These rules are the single source of truth for catalog entry format. They exist
because the audit parses entries mechanically and because the catalog ships on
a public plugin surface.
## categories mirror the library's source folders (e.g. ## Coding
for Sources/FOSFoundation/Coding/). Each category opens with 1–2
orientation sentences that cover everything in the section — including
naming internal files as internal when relevant, so a reader doesn't go
hunting for API that isn't public. Exception: multi-module catalog files
(like FOSTesting.md, covering FOSTesting/FOSTestingUI/FOSTestingVapor)
use one ## per module in library-hierarchy order, with categories nested
beneath as needed — do not restructure them to per-folder ## headers, or
the discovery skill's § pointers break.
### titles are task-framed with the entry's own symbols in backticks in
the title line. The title line is the audit's only freshness anchor —
prose and example backticks are ignored by design.
- Label-free symbol forms only:
count(), never count(of:). The
parser tokenizes on non-identifier characters and would flag of as a
stale symbol.
- Never backtick stdlib/Foundation/SwiftUI/Vapor types in titles —
they aren't in our symbol graphs and would report stale. Mention them in
prose or examples, which the audit ignores.
- Only symbols owned by THIS file's modules go in titles (stale checks
are scoped per file). A cross-module symbol goes in prose with a pointer
to its own catalog file.
- Apple-only symbols need the marker. A title whose symbols are
SwiftUI/UIKit-gated (
#if canImport(SwiftUI), #if os(iOS), …) inside a
module that still builds on Linux must end with <!-- apple-only -->
(invisible when rendered). Without it, Linux CI reports the symbols stale
— they're absent from Linux symbol graphs. On macOS the marker is inert,
so full stale enforcement still happens on every local/update-skill run.
Verify the marker path with FOS_AUDIT_ASSUME_NON_APPLE=1 if needed.
- Mixed title grammar is fine: imperative ("Compare versions — …") or
noun-phrase ("Codable JSON round-trip — …").
Mandatory "Reach for this when:" line describing the reader's mid-task
moment — the situation they're in, not a restatement of the API name.
Optional "Don't:" line naming the concrete reinvention the entry
replaces (Don't hand-roll JSONDecoder configuration…).
Examples are short and real. 2–4 lines for call-style APIs; up to ~8
lines for inherently structural declarations (type conformances, macro
expansions). Never padded to look thorough. Every example must be
verified against real source — an example calling a nonexistent API is
the catalog's worst possible failure.
Contracts, never representations. The catalog is a public,
plugin-shipped surface: no encoded shapes, token formats, byte layouts, or
internal implementation details. State what the API guarantees, not how it
does it.
One entry may cover a small family of symbols reached for as one
capability (e.g. an encode/decode pair) — but every family symbol goes in
the title backticks, or the audit reports the missing ones as gaps.
House style: honest limitation notes (say what an API does not do);
cross-references between related entries; generator-skill pointers limited
to one prose line; customer audience only — no maintainer or tooling
commentary in catalog files (that belongs here).
Canonical example
From FOSFoundation.md § Coding — title symbols as freshness anchor, family in
one entry, "Reach for" + "Don't" lines, minimal verified example:
### Codable JSON round-trip — `fromJSON()` / `toJSON()` / `toJSONData()`
Reach for this when: converting any Codable to/from JSON strings or Data.
Don't hand-roll `JSONDecoder` configuration — these apply the library's standard
coding strategy (dates, keys) consistently with the server.
```swift
let user: User = try jsonString.fromJSON()
let json = try user.toJSON()
let data = try user.toJSONData()
```
Why these rules protect the architecture
Entries state contracts, never representations — the catalog ships publicly
with the plugin, and publishing an encoded shape or internal layout there would
make it a de-facto schema consumers parse and depend on, freezing internals
forever (see CLAUDE.md, "Encapsulation Is the Precondition SOLID Assumes").
Titles are the audit's freshness anchor precisely so the catalog cannot
silently lie: any entry whose title symbols drift from the real API fails CI.
Prose and example backticks are ignored by design — they may freely mention
foreign types and context without polluting the freshness check.
1---2name: fosutilities-api-catalog-update3description: Update the FOSUtilities API catalog after API changes. Runs the symbol-graph audit, fixes stale entries, writes curated entries for gaps, maintains the reach-for index, and bumps the plugin version. Use in the FOSUtilities repo when CI's catalog audit warns/fails, after adding or renaming public API, or when a reach-for index line is missing.4---56# FOSUtilities API Catalog — Update78This skill keeps `.claude/skills/shared/api-catalog/` truthful and complete. The9catalog is the discovery surface for every project that imports FOSUtilities10(via the `fosutilities-api-catalog` skill); an entry that lies about the API is11worse than a missing one, which is why the audit fails CI on stale entries but12only warns on gaps.1314Run this on **macOS** — that builds the full API surface. On Linux the15Apple-only modules (FOSReporting) produce no symbol graph, and the audit skips16the stale checks for those files, so a Linux run can pass while an Apple-only17entry is stale. (This platform note lives here, in the maintainer skill — never18in the catalog files themselves, which serve the customer audience only.)1920## Audit-driven workflow21221. **Run the audit** from the package root:2324 ```bash25 swift scripts/api-catalog-audit.swift26 ```2728 It builds the package (`swift package dump-symbol-graph` — may take a29 while), then reports three sections: **Catalog gaps** (warning), **Stale30 entries** (ERROR — exit code 1), and the **DocC worklist** (warning).3132 Gotcha: the gaps section and the DocC-worklist section share the same33 `Module: symbol [source file]` line format. Any grep over the output must be34 scoped to one section, e.g.:3536 ```bash37 swift scripts/api-catalog-audit.swift | awk '/== Catalog gaps/,/== Stale/'38 ```3940 Note that piping through `awk` masks the audit's exit code — always check41 exit status on an unpiped run.42432. **Fix stale entries FIRST** — they fail CI. A stale entry means the API44 changed out from under the catalog: a symbol named in an entry's `###` title45 no longer exists in that file's modules. Read the source to find what46 happened, then either update the entry's title symbols (rename), rewrite the47 entry (redesign), or remove it (deletion).4849 Cross-module caveat: each catalog file's stale universe is **only its own50 modules' symbol graphs** (see `moduleToCatalog` in the audit script). A title51 symbol that actually lives in another file's module reports stale even52 though it exists. Remediation is moving the entry to the owning module's53 catalog file — or, rarely, the ignore list.54553. **Write a curated entry for each gap**, per the entry format rules below.56 Always read the actual source first — never write an entry from the symbol57 name alone. If the symbol is **not customer-reachable** (deprecated,58 compatibility shim, macro plumbing), add its base identifier to59 `scripts/api-catalog-ignore.txt` with a `#` comment stating the reason —60 never leave a silent gap. The comment goes on its own line **above** the61 identifier: the script only strips full-line comments, so an inline62 `name # reason` would fail to match the symbol.63644. **Update the reach-for index** when a new entry serves a reach that the65 discovery skill (`.claude/skills/fosutilities-api-catalog/SKILL.md`) doesn't66 index yet. Index lines are keyed by **what the implementor is reaching for**67 (`Reaching for URLSession…`), never by our library layout, and point to a68 real `## ` header: `` `FILE.md § Section` ``.69705. **Report the DocC-worklist count.** No DocC action happens in this workflow71 — the count is the measurable backlog for the separate DocC effort. Just72 surface it in your report so the trend is visible.73746. **Re-run the audit** and repeat until: 0 stale entries, 0 non-ignored gaps,75 exit code 0 (checked on an unpiped run — see step 1). Catalog-only edits76 don't invalidate the symbol graphs, so re-runs can skip the package rebuild77 by passing `--symbolgraph-dir <dir>` with the symbol-graph directory the78 first run produced (under `.build/`).79807. **Bump the plugin version** in `.claude-plugin/plugin.json`. Consumers only81 receive catalog and skill updates on a version bump — an unbumped update82 ships to no one. Catalog and skill content changes take a **minor** bump83 (consumers should receive them; this repo's practice is minor bumps for84 skill-doc updates); reserve patch bumps for typo-level fixes.8586## Non-audit path: index and structure maintenance8788Some catalog work has no audit signal:8990- **Adding or adjusting reach-for index lines** for already-catalogued91 capabilities (a reach was missing, or its wording didn't match how92 implementors think): edit the discovery skill's index directly, then do93 step 7 (version bump).94- **Renaming a `##` section or splitting a catalog file:** sweep the reach-for95 indexes — the discovery skill and any CLAUDE.md index blocks — for dangling96 `` `FILE.md § Section` `` pointers and fix them. Pointers are **not**97 audited; a rename silently breaks them.9899## Entry format rules (binding)100101These rules are the single source of truth for catalog entry format. They exist102because the audit parses entries mechanically and because the catalog ships on103a public plugin surface.1041051. **`##` categories mirror the library's source folders** (e.g. `## Coding`106 for `Sources/FOSFoundation/Coding/`). Each category opens with 1–2107 orientation sentences that cover **everything** in the section — including108 naming internal files as internal when relevant, so a reader doesn't go109 hunting for API that isn't public. Exception: multi-module catalog files110 (like `FOSTesting.md`, covering FOSTesting/FOSTestingUI/FOSTestingVapor)111 use one `##` per module in library-hierarchy order, with categories nested112 beneath as needed — do not restructure them to per-folder `##` headers, or113 the discovery skill's `§` pointers break.1141152. **`###` titles are task-framed with the entry's own symbols in backticks in116 the title line.** The title line is the audit's **only** freshness anchor —117 prose and example backticks are ignored by design.118 - **Label-free symbol forms only**: `count()`, never `count(of:)`. The119 parser tokenizes on non-identifier characters and would flag `of` as a120 stale symbol.121 - **Never backtick stdlib/Foundation/SwiftUI/Vapor types in titles** —122 they aren't in our symbol graphs and would report stale. Mention them in123 prose or examples, which the audit ignores.124 - **Only symbols owned by THIS file's modules go in titles** (stale checks125 are scoped per file). A cross-module symbol goes in prose with a pointer126 to its own catalog file.127 - **Apple-only symbols need the marker.** A title whose symbols are128 SwiftUI/UIKit-gated (`#if canImport(SwiftUI)`, `#if os(iOS)`, …) inside a129 module that still builds on Linux must end with `<!-- apple-only -->`130 (invisible when rendered). Without it, Linux CI reports the symbols stale131 — they're absent from Linux symbol graphs. On macOS the marker is inert,132 so full stale enforcement still happens on every local/update-skill run.133 Verify the marker path with `FOS_AUDIT_ASSUME_NON_APPLE=1` if needed.134 - Mixed title grammar is fine: imperative ("Compare versions — …") or135 noun-phrase ("Codable JSON round-trip — …").1361373. **Mandatory "Reach for this when:" line** describing the reader's mid-task138 moment — the situation they're in, not a restatement of the API name.139 Optional **"Don't:"** line naming the concrete reinvention the entry140 replaces (`Don't hand-roll JSONDecoder configuration…`).1411424. **Examples are short and real.** 2–4 lines for call-style APIs; up to ~8143 lines for inherently structural declarations (type conformances, macro144 expansions). Never padded to look thorough. **Every example must be145 verified against real source** — an example calling a nonexistent API is146 the catalog's worst possible failure.1471485. **Contracts, never representations.** The catalog is a public,149 plugin-shipped surface: no encoded shapes, token formats, byte layouts, or150 internal implementation details. State what the API guarantees, not how it151 does it.1521536. **One entry may cover a small family** of symbols reached for as one154 capability (e.g. an encode/decode pair) — but **every family symbol goes in155 the title backticks**, or the audit reports the missing ones as gaps.1561577. **House style:** honest limitation notes (say what an API does *not* do);158 cross-references between related entries; generator-skill pointers limited159 to one prose line; customer audience only — no maintainer or tooling160 commentary in catalog files (that belongs here).161162### Canonical example163164From `FOSFoundation.md § Coding` — title symbols as freshness anchor, family in165one entry, "Reach for" + "Don't" lines, minimal verified example:166167````markdown168### Codable JSON round-trip — `fromJSON()` / `toJSON()` / `toJSONData()`169Reach for this when: converting any Codable to/from JSON strings or Data.170Don't hand-roll `JSONDecoder` configuration — these apply the library's standard171coding strategy (dates, keys) consistently with the server.172173```swift174let user: User = try jsonString.fromJSON()175let json = try user.toJSON()176let data = try user.toJSONData()177```178````179180## Why these rules protect the architecture181182Entries state **contracts, never representations** — the catalog ships publicly183with the plugin, and publishing an encoded shape or internal layout there would184make it a de-facto schema consumers parse and depend on, freezing internals185forever (see CLAUDE.md, "Encapsulation Is the Precondition SOLID Assumes").186Titles are the audit's freshness anchor precisely so the catalog **cannot187silently lie**: any entry whose title symbols drift from the real API fails CI.188Prose and example backticks are ignored by design — they may freely mention189foreign types and context without polluting the freshness check.