Add catalog-reuse skill - #124
Conversation
New sibling skill mirroring boxel-ui-component-discovery, one level up: before authoring a new CardDef or FieldDef, search the catalog for an existing card/field Spec and reuse it. Reference it as-is by default, remix only when it must be modified, build new only when nothing matches (and record the gap). Registered in index.md and skills/glossary.md so the pull model can discover it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Workspace Sync Dry-Run CompleteDry-run completed successfully - no changes were made Dry-Run Results |
Broaden catalog-card-field-reuse into a single catalog-reuse skill covering everything the catalog publishes as reusable: card, field, component, and command Specs (searchable), plus apps via Listing install/remix and file/theme instances via linksTo. Keep boxel-ui-component-discovery as the specialized front-end for UI primitives in a template and cross-link the two. The broad Spec query filters to any:[card,field,component,command] — the specTypes the catalog actually publishes; app is not Spec-searchable. Update index.md and skills/glossary.md to match.
…neutral transport - Step 2 now issues one narrowed Spec query per enumerated need (specType + matches/contains) instead of dumping all four specTypes (~188 specs with readMes) into context; a category-wide sweep is required before declaring a gap. - Frontmatter description trimmed to two sentences. - Both this skill and boxel-ui-component-discovery now state the filter JSON once and name both transports (assistant-room card-search tool, boxel search from a CLI session) instead of assuming a shell. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
habdelra
left a comment
There was a problem hiding this comment.
Reviewed the four files against the references they cite and against README.md's authoring invariants. The skill's structure is good — enumerate → narrowed per-need query → sweep before declaring a gap → reference/remix/build rubric → self-audit — and the reference-as-default framing with "a runtime dependency on the catalog is desirable, not a smell" is a genuinely useful thing to have written down.
Six findings, inline. The ones I'd want resolved before merge:
specType: 'app'does exist —spec-usage.mdhas a section for it. "App 0 in prod" is an inventory count; the skill states it as a schema fact, and that suppresses the category sweep the skill otherwise mandates.- Cardinal Rule 1 is misattributed — it's about
contains(CardDef), not about fields never beinglinksTo. - The bare-string
matchesform conflicts withquery-systems.md:85, which listsmatchesamong the field-keyed predicates. One of the two files is wrong;matchesis the skill's preferred key so it's worth pinning down. - No
Skill/catalog-reuse.json— the in-app assistant loadsSkill/cards, so the assistant-room surface the description leads with doesn't actually get this skill. AninstructionsSourcepointer card avoids duplicating the body.
Plus two smaller ones: catalog placement / Pre-flight promotion in index.md, and bare boxel vs npx boxel.
One nit not worth its own thread: boxel-ui-component-discovery still says "One broad query returns the full inventory (~50 specs)", but this PR's own verification puts component specs at 62. Cheap to correct while the file is open.
Generated by Claude Code
| - **Whole apps** → install or remix a catalog **Listing** (see | ||
| `catalog-listing`), not a `Spec` search. Apps are distributed as | ||
| Listings; a Spec query for `specType: app` finds nothing. |
There was a problem hiding this comment.
app is a real specType — this states an inventory count as a schema fact.
skills/boxel/references/spec-usage.md has a dedicated section for it:
### App Specs (`specType: 'app'`)
(Apps extend AppCard and are typically linked to like regular cards...)
The PR description's evidence is "app 0 — verified against prod", i.e. the catalog currently publishes zero app Specs. That is not the same as "whole apps are not Specs" (line 50) or "a Spec query for specType: app finds nothing" (line 110) — the second sentence is true today and false the moment someone publishes one, and the first contradicts the reference this skill links to two sections earlier.
Since the skill's whole contract is "one narrowed query per need, and a category-wide sweep before you may declare a gap", a reader who takes line 50 at face value will never sweep specType: app and will hand-build an app the catalog has started shipping.
Suggest keeping the routing advice (Listings are the right reuse mechanic for apps) but grounding it in what's actually true:
Whole apps → install or remix a catalog Listing (see
catalog-listing).specType: 'app'exists in the Spec model, but the catalog publishes apps as Listings and currently has no app Specs — go to Listings first, not a Spec search.
Same edit needed for the parenthetical on line 50.
Generated by Claude Code
There was a problem hiding this comment.
[Claude Code 🤖] Reworked this whole area. app is back in the query any and in the reference table (it's a CardDef extending AppCard, referenced like a card) — so the sweep covers it and won't tell readers it "isn't a Spec." The whole-app-family case now routes to AppListing, framed as a packaging/inventory fact (catalog currently ships apps as Listings, 0 app Specs) rather than a schema claim. Also restructured the skill around the two reuse modes — reference a Spec's target vs remix a Listing — and confirmed in code that listing-remix takes a Listing (ListingInstallInput.listing = linksTo(CardDef)), not a Spec, so the skill now says remix runs on the Listing that bundles the definition.
| | `specType` | How you reuse it | | ||
| |---|---| | ||
| | `card` | `adoptsFrom` the catalog card to reuse its schema, or `linksTo` / `linksToMany` an existing catalog instance | | ||
| | `field` | import the FieldDef from the catalog module and use `contains` / `containsMany` (fields are never `linksTo` — Cardinal Rule 1) | |
There was a problem hiding this comment.
Misattributed citation — Cardinal Rule 1 says the opposite thing.
From skills/boxel/SKILL.md:
| 1 |
contains(CardDef)orcontainsMany(CardDef)— uselinksTo/linksToManyinstead. CardDefs have identity, FieldDefs don't. |
Rule 1 is about CardDefs (never contains one), not about fields. A reader who follows the pointer to check "fields are never linksTo" finds a rule that doesn't mention it, which undermines trust in the rest of the cross-references — and this skill leans heavily on cross-references.
Either drop the citation:
| `field` | import the FieldDef from the catalog module and use `contains` / `containsMany` (a FieldDef is contained, never linked) |
or cite it accurately as the converse of Rule 1.
Generated by Claude Code
There was a problem hiding this comment.
[Claude Code 🤖] Good catch — Rule 1 is "never contains(CardDef)," not anything about fields. Dropped the citation; the field row now reads "(a FieldDef is contained, never linked)."
|
|
||
| ```json | ||
| { | ||
| "filter": { | ||
| "on": { "module": "@cardstack/base/spec", "name": "Spec" }, | ||
| "every": [ | ||
| { "eq": { "specType": "field" } }, | ||
| { "matches": "address" } |
There was a problem hiding this comment.
The bare-string matches form conflicts with this repo's own query reference.
skills/boxel/references/query-systems.md:85:
eq,in,contains,range,matches: predicates over fields; each must includeon…
That groups matches with the field-keyed predicates, so a reader cross-checking against the reference will write { "matches": { "readMe": "address" } } and get a different (or rejected) query than the one this skill prescribes. The PR description says the bare-string form matches the wire contract — I can't verify MatchesFilter from this repo, so I'm not claiming the example is wrong, only that exactly one of these two files is.
Whichever is correct, they should agree. If the bare-string form is right, query-systems.md:85 needs matches moved out of the field-predicate list with a note that it's realm-wide full text; the "Verified-working composition patterns" block just below it would be a natural home for a fifth entry.
Worth pinning down before merge, since step 2 makes matches the preferred key and the whole "one narrowed query per need" flow rests on it working.
Generated by Claude Code
There was a problem hiding this comment.
[Claude Code 🤖] Pinned it against packages/runtime-common/query.ts:167 — interface MatchesFilter { matches: string }, a bare string; every real usage in the tree is bare-string, and I confirmed both the skill's shape and the pattern shape against the live catalog (every:[{eq:{specType:'field'}},{matches:'date'}] → 4 hits; every:[{type},{on,matches}] → 12). So { "matches": "address" } is correct and query-systems.md is the file out of step — it groups matches with the field-keyed predicates and has no example. Split matches onto its own line (bare-string full-text, still needs on/type scope) plus a fifth verified-working pattern. The "must include on" half was right, so it's a split, not a rewrite.
| --- | ||
| name: catalog-reuse | ||
| description: MANDATORY before building a card, field, command, app, or asset (image, font, icon, theme) — search the catalog and reuse what's there: reference as-is by default, remix only to modify, build new only when nothing matches. For UI primitives in a `.gts` template, use boxel-ui-component-discovery instead. | ||
| boxel: | ||
| kind: skill | ||
| --- |
There was a problem hiding this comment.
No Skill/catalog-reuse.json — so the in-app assistant never loads this skill, which is the surface the PR description names first.
The description frames the transport rewording as serving "assistant rooms (skills realm tracking main), where the AI has a card-search tool but no shell". But per README.md, the in-app assistant loads Skill/<name>.json SkillPlusMarkdown cards, not skills/**/SKILL.md:
Author guidance into both
skills/andSkill/. … nothing syncs them. Update only one and the two harnesses drift.
Without a Skill/ card, the assistant-room half of the "surface-neutral" design ships inert — the CLI/factory side gets the skill, assistant rooms don't. That's the one consumer whose transport this PR went out of its way to accommodate.
The body doesn't need duplicating: Skill/bxl-authoring.json and Skill/source-code-editing.json point instructionsSource at the skills/ file, so a ~30-line card is enough:
"relationships": {
"instructionsSource": {
"links": { "self": "../skills/catalog-reuse/SKILL.md" },
"data": { "type": "file", "id": "../skills/catalog-reuse/SKILL.md" }
}
}Two caveats if you take this:
- README notes the chooser-facing metadata (
cardInfo.name/cardInfo.summary) is hand-authored and does drift — keep it in step with the frontmatterdescriptionand theindex.mdline. boxel-ui-component-discoveryhas noSkill/card either, so if that's a deliberate scoping call rather than an oversight, say so in the description and this comment is moot — but then the assistant-room framing should come out of the description too.
Generated by Claude Code
There was a problem hiding this comment.
[Claude Code 🤖] I'd hold off on the Skill/ card. Md-only is the established pattern here, not an oversight: boxel itself has no Skill/ card, nor do boxel-ui-component-discovery (the sibling this mirrors) and 10 others — 12 md-only skills total. If md-only shipped inert in the assistant, the core boxel skill would be too. And the mixed skill-file chooser that lets the assistant attach .md skills directly is live — the README's "once the assistant consumes skill markdown files directly" line is stale (that shipped in July). So this reaches assistant rooms the same way every md-only skill does. You're right the PR description over-leaned on the assistant framing, though — corrected it to "reachable like every md-only skill (chooser + index.md), not a special transport." If you'd still want a belt-and-suspenders instructionsSource card I'll add one, but it diverges from the sibling we're deliberately mirroring.
|
|
||
| - **[`boxel-ui-guidelines/`](skills/boxel-ui-guidelines/SKILL.md)** — Template UI rules: theme tokens, `@fields` vs `@model`, container queries, layout safety. | ||
| - **[`boxel-ui-component-discovery/`](skills/boxel-ui-component-discovery/SKILL.md)** — Mandatory catalog search for a boxel-ui component Spec before hand-rolling any UI primitive in a `.gts` template. | ||
| - **[`catalog-reuse/`](skills/catalog-reuse/SKILL.md)** — Mandatory catalog search for an existing definition or asset (card / field / component / command Spec, app Listing, or file/theme instance) before building a new one; reference as-is by default, remix only to modify, build new only when nothing matches. The general form of `boxel-ui-component-discovery`. |
There was a problem hiding this comment.
Two placement issues, both of which blunt a skill whose frontmatter says MANDATORY.
1. It's filed under "UI & content" but it isn't a UI skill. Its scope is cards, fields, commands, apps, and assets — only one of its four specType rows is component, and that row delegates straight to boxel-ui-component-discovery. It reads as adjacency to its sibling rather than to its subject. "Foundation" is the better home: boxel, the cardinal-rules checklist, and source-code-editing are all cross-cutting disciplines like this one, and someone scanning "UI & content" for a general reuse rule won't look there.
2. MANDATORY skills that actually bind are surfaced in Pre-flight, not just the catalog. The Pre-flight section (line 25) is this index's enforcement mechanism, and it currently promotes two:
Card work takes one more read too. Before writing or editing any card definition (
.gts) or instance JSON, readboxel-workspace-cardinal-rules/SKILL.md…Reports go in Rich Markdown, not a new card. …
A skill that opens with "Before you build anything the catalog might already have … you must first search" and whose named failure mode is "agent reuses one thing, hand-builds the rest" is aimed at the same moment as the cardinal-rules read — right before authoring a card or field — and depends on the agent having loaded it before it starts building. Catalog-only listing means it triggers on description match, which is exactly the coin-flip the Pre-flight section exists to remove. A third bullet there, in the same shape as the other two, would close it.
(boxel-ui-component-discovery is catalog-only despite also saying MANDATORY — so this is arguably a pre-existing gap, but this PR is the one generalizing the rule to all authoring, which makes it worth more here than it was there.)
Generated by Claude Code
There was a problem hiding this comment.
[Claude Code 🤖] Both taken. Moved it from "UI & content" to "Foundation" — its subject is cross-cutting authoring, only 1 of 5 specType rows is UI and that one delegates to the sibling. And added a third Pre-flight bullet in the same shape as the cardinal-rules and rich-markdown ones, so a MANDATORY skill binds before authoring instead of riding on description-match. Agreed that generalizing the rule to all authoring makes the Pre-flight promotion matter more here than it did for the component-only sibling.
| Run the filter through whatever search transport your session has — | ||
| the card-search tool in an assistant room, or | ||
| `boxel search --realm <catalog-realm-url> --query '<filter-json>' --json` |
There was a problem hiding this comment.
Nit: bare boxel against the repo's own npx boxel convention.
index.md conventions:
Prefer
npx boxelover bareboxel(stale shims)
Counts across skills/: npx boxel search × 20, bare boxel search × 2 — and both bare ones are the lines this PR writes (here and the parallel line in boxel-ui-component-discovery). Same for boxel realm ls on line 45 of this file, which is the only occurrence in the tree.
Since the stated failure is a stale global shim silently exposing a smaller command surface — skills/boxel-patterns/references/integration-surfaces.md:316 documents exactly that, an old install whose surface is just realm create/pull/push/sync + profile + run-command — the reader most likely to hit it is a factory session following a new skill verbatim. npx on both lines.
Generated by Claude Code
There was a problem hiding this comment.
[Claude Code 🤖] Fixed — npx boxel on the search and realm ls lines here, plus the pre-existing bare boxel search / boxel realm ls in boxel-ui-component-discovery while I'm in that file. Matches the repo's npx boxel convention.
There was a problem hiding this comment.
Can we get a couple of examples of how this works, and some before/after benchmarks, for example:
- What prompts can we use today that result in an item from the catalog being used? For example a demo would be good
- How does this affect the token usage in the AI assistant? Does the AI assistant usually fetch this skill right at the beginning, or in the middle when it starts building cards?
- When this skill is used, how many times does the search get called during generating code? Is it happening on every turn? Can the result of the search be cached between turns?
- Frame a Spec as a searchable pointer, not the card/field/component: search Specs, reference the definition a Spec names (its `ref`), never the Spec itself. - Two reuse modes: reference a Spec's target (live dependency, default) vs remix a Listing (copy-in bundle). The listing-remix command takes a Listing, not a Spec. - Fold `app` back into the query and reference table (an app is a CardDef extending AppCard); route whole-app families to AppListing. - Fix the field-row citation (a FieldDef is contained, never linked). - index.md: move catalog-reuse to Foundation + add a Pre-flight bullet. - query-systems.md: `matches` is a bare-string full-text predicate, not field-keyed — split it out and add a verified composition pattern. - Use `npx boxel` per repo convention (here and in the component skill). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a flagged "coming in a separate engine change; not live yet" note to the query procedure: once the search engine adds the opt-in `_matchRelevance` sort (ts_rank_cd score on `entry.meta._matchRelevance`), per-need `matches` queries sort by it and read the score to rank/threshold hits, replacing the contains/sweep fallbacks. Guard against premature use — an unknown sort key is rejected — until it ships. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…path
Present the relevance-sorted query as the standard per-need search:
`sort: [{ by: "_matchRelevance", direction: "desc" }]` in the example,
read `entry.meta._matchRelevance` to rank/threshold hits, contains/sweep
as fallbacks. A note records that `_matchRelevance` is delivered by a
separate in-flight engine change. Also corrects the `matches` wording to
"full-text over the Spec's rendered content."
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
[Claude Code 🤖] This skill's per-need catalog query now sorts by Until that engine change ships, sorting by |
What
Adds
catalog-reuse— the umbrella "search before you build" skill — and keepsboxel-ui-component-discoveryas its specialized front-end for UI primitives.Core model
A
Specis the catalog's searchable pointer, not the definition itself. You search Specs (specTypecard/field/component/command/app), read each hit'sreadMe, then reference the definition a Spec names — via itsref— never the Spec.Two reuse modes:
adoptsFrom/ import /linksTothe definition the Spec'srefnames; keep the live catalog dependency. The target realm need not be self-contained.Listing(the installable bundle: specs + examples + skills). Thelisting-remixcommand takes a Listing, not a Spec (ListingInstallInput.listing = linksTo(CardDef)), so remixing a discovered Spec means finding the Listing that bundles it.Per-type reference: card/app →
adoptsFrom/linksTo; field → import +contains/containsMany; component → template markup; command → import + invoke. Whole apps →AppListing; files/themes → instance search +linksTo/cardInfo.theme.Files
skills/catalog-reuse/SKILL.md— the skill.index.md— filed under Foundation + a Pre-flight bullet (aMANDATORYskill that binds before authoring, not on description-match).skills/glossary.md— one-liner.skills/boxel-ui-component-discovery/SKILL.md— "Related" cross-link up tocatalog-reuse(kept as the specialized UI front-end) +npx boxelfixes.skills/boxel/references/query-systems.md— correctsmatchesto a bare-string full-text predicate (it had no example and grouped it with the field-keyed predicates); adds a live-verified composition pattern.Reachability
Reaches both surfaces the same way every md-only skill does (including
boxeland the siblingboxel-ui-component-discovery): the in-app assistant via the skills realm (trackingmain) — attachable as a skill file through the mixed skill chooser, discoverable viaindex.md— and the software factory via the boxel-cli plugin copy, once a release is tagged and pinned (deferred to the factory ticket).Verified against the live catalog
Prod specType distribution: field 70, component 62, card 45, command 11, app 0 (apps ship as Listings). The
matchesquery shape confirmed live (every:[{eq:{specType:'field'}},{matches:'date'}]→ 4 hits;every:[{type},{on,matches}]→ 12).Draft until the factory-side firewall lift + wiring is reviewed alongside it.