Project Conventions
Vendor skills often need project-specific context: a Jira project key, a branch naming pattern, a test runner name. Without a way to inject it, every consumer shadows the skill just to change one line, and the maintenance debt explodes.
Project Conventions is a JSONSchema-based slot fill-in that closes that gap:
- A vendor declares the slots it needs in
resources/boost/conventions-schema.json. - A consumer fills them in
boost.phpvia->withConventions([...]). boost syncvalidates the values and makes them available to skills and guidelines.
boost.php is the single source of truth. The relevant frozen formats (the boost:conv token, the schema-version handshake, the render annotation) are pinned in PUBLIC_API.md.
Declaring values
// boost.php
return BoostConfig::configure()
->withAgents([Agent::CLAUDE_CODE])
->withConventions([
'jira' => ['project_key' => 'HPB'],
'github' => ['default_base_branch' => 'develop'],
]);Diagnostics route through SyncResult::diagnostics (a lenient channel that never fails sync) and render after the primary boost sync / boost where output.
Two ways to consume a slot
1. Inline tokens (preferred)
A skill or guideline references a slot with a render-time token, and boost sync resolves the value INTO the emitted file, so the value lives next to the instruction, loaded on demand with the skill:
<!--boost:conv path="jira.project_key" mode="inline"-->Resolution order is declared → schema default → inline fallback, keyed by path existence (a declared false / [] counts as declared, not missing). mode is one of inline (scalars + comma-joined scalar lists), bullets, yaml, or json. A type×mode mismatch, an unknown slot, or an unset slot with no default and no fallback is a render-class error that fails boost sync --check.
Paired visible-default form (1.2.1+)
A bare <!--boost:conv …--> token resolves only under boost-core. An engine with no resolver, notably laravel/boost, which installs a package's SKILL.md and preserves HTML comments verbatim, leaves it inert, so an inline token reads as a word gap and its fallback stays hidden inside the comment.
The paired form closes that gap by wrapping a visible default between an open and an end marker:
Run <!--boost:conv path="testing.runner" mode="inline"-->Pest<!--boost:conv:end--> to verify.- boost-core replaces the whole span (open comment →
<!--boost:conv:end-->) with the resolved value; the visible default doubles as the inline fallback (an explicitfallback=still wins). - A resolver-less engine leaves both comments inert, so the visible default reads as ordinary prose:
Run Pest to verify., with no gap where the markers were.
The ```boost:conv fence takes the same <!--boost:conv:end--> marker and buffers its body as one block, so a multi-line span resolves whole. Paired spans resolve before bare tokens, so an open comment is never consumed as a stray unpaired token; inline-code and plain-fence examples stay literal; an orphan end marker is inert and keeps the Project Conventions block. Prefer this form for any token that may ship to a consumer using laravel/boost.
2. The ## Project Conventions block (legacy)
boost sync also renders declared values into a markerless ## Project Conventions section in CLAUDE.md (always, even if Claude Code isn't in withAgents(...)). This block is kept whenever any live skill or guidance still needs it (a legacy $.slot reference, an unresolved token, or prose pointing at "the Project Conventions section") and drops only on positive proof of full migration to tokens. A project with no tokens renders the block unchanged.
Inspect the effective resolved set any time with boost where --conventions (add --json for a machine-readable shape).
Observability
Three surfaces catch problems:
| Surface | What it reports |
|---|---|
boost sync | Inline file:line warnings for leaked tokens; INFO per block keep-reason (under -v) |
boost doctor | Lists every token leak with its cause; a "Project Conventions block" section naming why the block is kept |
boost validate --strict | Turns each leaked token into an error → hard-fails CI |
Leaked tokens. A token that doesn't resolve leaves a raw <!--boost:conv …--> comment in the emitted file, so the agent reads the literal token instead of the value. The usual cause is a token-bearing vendor skill synced by a consumer still on boost-core < 0.15 (the old engine copies the token verbatim). Detection is prose-scoped: a token inside a plain code fence or inline-code span is an intentional literal and is never flagged.
Keep-reasons. When the block is kept, boost-core names the single artifact still holding it open (a skill or guidance file) and its cause: a legacy $.<root> ref, an unresolved token, a prose pointer, or (no migration yet) for a pure-conventions project that hasn't adopted tokens. Run boost where --conventions against a tree you expect to be fully migrated to find the one ref still pinning it.
Legacy $.<root> refs. A pre-token $.slot reference (e.g. $.testing.runner) is detected but never resolved, so it emits literally and dangles for every non-Claude agent (the block is CLAUDE.md-only). boost validate surfaces each distinct ref as a warning (so it does not fail --strict, since a ref may be mid-migration), pointing at the first file it appears in.
Canonical CI recipe: run boost sync (or composer install), then boost validate --strict over the post-sync emitted set.
Migrating vendor skills to tokens
Package authors moving a skill off $.slot references onto tokens should follow the conventions-token-migration skill (shipped in resources/boost/skills/). It's the canonical recipe for the mode×type placement rules, the footguns (inline-code-wrapped tokens stay literal; an errored token ships raw), dropping the obsolete slot-table scaffolding, and the verify-before-ship workflow.
Floor rule: a token-bearing skill requires the consumer engine at ^0.15 (a pre-0.15 engine emits the raw token, value lost), or ^0.16 if it uses open-vocab map sub-keys like mcp.jira. Bump the package's consumer floor before shipping token skills.
Migrating from 0.8.x marker conventions
A project still carrying a boost-core:conventions:* marker block migrates the YAML into boost.php with vendor/bin/boost convert-conventions. Run it before upgrading to 0.12.0, while the markers still exist. After a 0.12.0 sync has stripped the markers, copy any preserved conventions YAML into ->withConventions([...]) by hand. boost.php is canonical thereafter.
For schema authors, see the spec in internal/specs/conventions-schema.md (gitignored, branch-local).