| name | crouton-config-registry |
| description | The catalog of every config-as-data surface in nuxt-crouton โ which file controls what, who actually reads it, its drift hazards, and how to change it safely. Use when asking "where is X configured", "who reads this file", "what happens if I edit harness.config.mjs / routing.json / digests.yml / labels.yml / a deploy.config.json / an e2e.manifest.json", when a setting change had no effect (silent no-op), when adding a new config surface, or when a gen-*.mjs --check fails. Trigger phrases: "where do I configure", "what reads this config", "config drift", "the setting didn't do anything", "stale generated doc", "add a new config-as-data surface". |
crouton-config-registry
One-line purpose: the master index of every configuration axis in this monorepo โ file โ what it controls โ who READS it โ drift hazard โ how to change it safely.
Background for a fresh session: this repo deliberately prefers config-as-data (versioned, PR-reviewed files read by deterministic scripts) over hosted settings UIs or workflow-embedded constants. That means the answer to "where is X configured" is always a file โ but some files have no live reader (silent no-ops, tracked in #1098), and several generated artifacts drift unless their --check mode is run. This skill is the map.
When to use / when NOT to use
Use this skill to locate a config surface, find out who consumes it, or check whether an edit will actually take effect.
Do NOT use it for the depth those surfaces' owners carry:
| Topic | Go to instead |
|---|
wrangler.jsonc anatomy, deploy.config.json semantics, secrets/tokens, workflow inventory | sibling crouton-ci-and-deploy-map |
pnpm-workspace.yaml catalog/overrides rationale, install traps | sibling crouton-build-and-env |
crouton.config.js / schemas/*.json field format, what generation produces | sibling crouton-generation-reference (workflow: the crouton skill) |
What croutonApps / croutonCollections / croutonLayoutBlocks mean architecturally | sibling crouton-architecture-contract; layout blocks: crouton-layout-reference |
| The ISR/routeRules breakage itself | sibling crouton-diagnostics-index (ยง "The ISR routeRules contradiction") |
| Which sign-off gate a change needs, packages-edit approval mechanics | sibling crouton-change-control |
| Digest content/cadence mechanics | the housekeeping, epic-digest, skills-digest skills |
Trust order when sources disagree: see sibling crouton-docs-trust-map ยง1.
Master index (all paths repo-relative)
A. Harness configuration (.claude/, .github/)
| File | Controls | Read by | Drift hazard |
|---|
harness.config.mjs (repo root) | The stage model (epic #952): stage โ paths โ gates โ deploy target โ editGuard โ see the file's own header | scripts/harness-stages.mjs (resolver + stageForPath/gateMode exports; contract test scripts/harness-stages.test.mjs). Referenced (not imported) by .claude/agents/task-worker.md and several skills | The packages-edit hook .claude/hooks/gate-package-edits.sh hardcodes packages/* on purpose (perf; #955). Rename/repoint the package stage here and the hook is out of sync until you edit it too โ both files document this |
.claude/routing.json | Model-routing registry (#864) โ DESCRIPTIVE, not prescriptive: it mirrors .claude/agents/*.md frontmatter; see the file's own header | scripts/gen-routing.mjs (โ writeups/architecture/routing-registry.md; --check fails on stale registry OR tier drifting from agent frontmatter). CI: routing-registry.yml. JSON not YAML so the check job needs zero deps (#1062) | Its _known_gaps states plainly: the top-level CI agent loop (claude.yml, decompose-on-issue.yml, fix-ci, daily sweeps) runs on the claude-code-action default model โ unpinned. Routes only cover the named agents |
.claude/settings.json | Wires the hooks (PreToolUse Edit|Write โ gate-package-edits.sh + gate-spec-signoff.mjs; PreToolUse mcp__github__add_issue_comment โ require-comment-provenance.mjs; SessionStart โ session-start.sh) + the mcpServers entries | The Claude Code harness itself | The mcpServers.crouton path was a silent no-op until fixed under #1098 โ verify with ls before trusting |
.claude/hooks/pre-commit-sync-reminder | Optional git pre-commit reminder (manual install; not wired by settings.json) | git, only if a human ran cp / core.hooksPath | Greps were dead (long-renamed package dirs) until fixed under #1098; still only live if a human installed it |
.github/digests.yml | Cadence + delivery per recurring report (housekeeping / epic-digest / skills-digest / eval-scoreboard), as data โ schema in the file's own header | .claude/skills/housekeeping/schedule.mjs, invoked by workflows housekeeping.yml, skills-digest.yml, eval-scoreboard.yml โ each cron fires daily as a cheap wake-up and schedule.mjs exits early on non-send days | Editing workflow cron: is the wrong move โ edit this file. (epic-digest.yml was the one exception โ hard-coded cron, no schedule.mjs โ until the #1139 retrofit; all four reports now gate through it) |
.github/labels.yml | The label taxonomy: pkg:* / app:* / poc:* / worker:* mirrors of the source dirs, plus type:* / status / meta labels | labels.yml workflow (crazy-max/ghaction-github-labeler, skip_delete: true โ never removes); coverage-checked by labelCoverage() in .claude/skills/housekeeping/gather.mjs (source dirs on disk vs declared labels; report-only) | New package/app/poc dir without a matching label = housekeeping-report finding, not a build failure. Some doubled app:*/poc:* entries exist for apps that moved to pocs/ (harmless) |
.claude/launch.json | Per-app dev-server launch configs (name, pnpm --filter <app> dev, port) | Editor/launcher tooling; no repo script reads it | Contains stale app names. Low stakes |
B. Per-app configuration (apps/<name>/, same shape in pocs/ and fixtures/)
| File | Controls | Read by | Drift hazard |
|---|
crouton.config.js | Generation input: features (which @fyit/crouton-* packages to enable), collections (name + fieldsFile + options like formComponent, hierarchy), targets (layer โ collections), dialect | crouton config (the CLI, packages/crouton-cli/lib/generate-collection.ts, loaded via c12); also probed by packages/crouton/src/module.ts and packages/crouton-core/nuxt.config.ts hooks | fieldsFile paths resolve relative to the config file, but output lands in cwd โ run the CLI from inside the app. Format/details: sibling crouton-generation-reference |
schemas/*.json (fieldsFiles) | The field definitions per collection โ the data model | The CLI via crouton.config.js. Changing one = a schema change โ Schema sign-off gate (#314) applies (see crouton-change-control / schema-review skill) | Editing a schema without regenerating leaves generated code stale; regenerating without sign-off skips a gate |
app/app.config.ts | The three runtime registries (defu-merged across layers): croutonCollections (app registers each generated *Config), croutonApps (addon packages self-register โ powers useCroutonApps().hasApp() detection), croutonLayoutBlocks (packages contribute layout blocks) | Nuxt at runtime; useCroutonApps, the layout engine, collection machinery | The generator upserts croutonCollections entries on generate โ hand edits near those imports can be clobbered/duplicated. Semantics: sibling crouton-architecture-contract |
deploy.config.json | Opt-in unit for CI deploys: stagingUrl, productionUrl, layerPackages, watchPaths, optional smoke.required | .github/workflows/deploy-apps.yml detect job | Owned by sibling crouton-ci-and-deploy-map |
wrangler.jsonc | Cloudflare Workers bindings (top-level = prod, env.staging = staging; id-less until first deploy auto-provisions, then scripts/sync-wrangler-ids.mjs writes ids back โ commit them) | wrangler / Nitro cloudflare_module preset / the app's cf:* scripts | Anatomy, nitro#3429 env-stripping, and the #138 migrate gotcha: sibling crouton-ci-and-deploy-map |
nuxt.config.ts routeRules | ISR/SWR/prerender caching per route | Nitro | โ ๏ธ Do NOT add wildcard /api/teams/*/... rules โ breaks ALL generated /api/teams/:id/* routes via a radix3 conflict (live warning comment in apps/velo/nuxt.config.ts); root CLAUDE.md's ISR example shows the broken pattern as an explicit โ (fixed under #1095). Full story: sibling crouton-diagnostics-index |
C. Per-package and per-fixture
| File | Controls | Read by | Drift hazard |
|---|
packages/*/crouton.manifest.ts | Package self-description via defineCroutonManifest (from @fyit/crouton-core/shared/manifest): id, bundled, fieldTypes (+ aliases โ canonical field-type source), autoGeneratedFields, reserved names, provides, detects | Two consumers: the meta module packages/crouton/src/module.ts (regex-scans packages/crouton-* + node_modules/@fyit/crouton-* for id/bundled) and the CLI's type mapping packages/crouton-cli/lib/utils/manifest-loader.ts (jiti-loads, caches) | Adding a field type = manifest change, not a CLI hardcode. The meta scan is regex, not import โ keep id: / bundled: literal. Field-type catalog: sibling crouton-generation-reference |
fixtures/<name>/e2e.manifest.json | What the fixture smokes: packages (drives PR-affected fixture selection), collections (key/heading/create/requiredField for the generic CRUD spec), i18n block | .github/workflows/e2e.yml detect job (jq over .packages); the generic specs e2e/collection.smoke.spec.ts, e2e/surface.smoke.spec.ts, e2e/helpers.ts | A new fixture without a manifest is invisible to the smart selection. Format reference: e2e/CLAUDE.md, fixtures/CLAUDE.md, e2e-smoke skill |
D. Workspace-wide toolchain
| File | Controls | Read by | Drift hazard |
|---|
pnpm-workspace.yaml | Workspace globs + the catalog: block (single source of shared dep versions, #142) | pnpm | Catalog/pins depth + the deliberately-uncataloged families: sibling crouton-build-and-env; change flow: dependency-sweep skill |
root package.json pnpm.overrides / onlyBuiltDependencies / ignoredBuiltDependencies | Workaround version pins (exact pins โ see the block itself) and the build-script allowlist | pnpm | Don't "upgrade" exact pins casually โ sibling crouton-build-and-env |
vitest.config.ts (root) | Test projects = packages/*/vitest.config.ts (each package with a vitest config is a project); excludes node_modules/dist/.nuxt | pnpm test (= vitest run) and CI ci.yml test job | A package without its own vitest.config.ts has no tests in the sweep โ silently untested. Coverage reality: sibling crouton-validation-reality |
eslint.config.mjs (root) | Lint for packages/* only (dirs.src: ['packages']) via @nuxt/eslint-config/flat + stylistic (no Prettier) + all eslint-plugin-vuejs-accessibility recommended rules at warn (epic #726, warn-first) | pnpm lint; apps carry their own eslint.config.mjs | Root lint does NOT cover apps/; a11y rules are warnings, not errors ("tighten to error once backlog cleared" โ still open) |
E. Generated artifacts and their --check modes
Each scripts/gen-*.mjs renders a config file into a committed artifact; --check fails when stale. Current exit statuses: run the one-liner below โ don't trust remembered ones.
| Generator | Source โ artifact | CI wiring |
|---|
scripts/gen-skills-doc.mjs | skill frontmatter + its in-file META/FLOWS maps โ writeups/architecture/skills-and-triggers.html | skills-doc.yml (PRs touching .claude/skills/**); also imported by skills-digest (#841). Asymmetry: a skill missing from META is only a console.warn (lands "Uncategorised"), but a FLOWS entry naming a deleted skill hard-fails |
scripts/gen-routing.mjs | .claude/routing.json โ writeups/architecture/routing-registry.md (+ frontmatter-drift check) | routing-registry.yml |
scripts/harness-layers.mjs | layer: frontmatter of every skill/agent โ inventory (--json); legacy no-frontmatter skills via its hardcoded OVERRIDES | skills-doc.yml runs --check (wired under #1098) |
scripts/gen-package-catalog.mjs | package CLAUDE.md files โ .claude/skills/task-decompose/package-catalog.md (#292 package-fit check) | none (root CLAUDE.md mandates running it on package add; no workflow) |
for s in gen-skills-doc gen-routing harness-layers gen-package-catalog; do node scripts/$s.mjs --check; echo "$s exit=$?"; done
Silent no-ops (config that looks live but isn't)
A silent no-op is a config surface with no live reader โ a stale path, a long-renamed package name, or a check documented as CI-enforced but wired into no workflow โ so editing it changes nothing. The live catalog is tracked in #1098; detection is always the same move: find the reader first (grep -rln '<filename>' scripts/ .claude/ .github/).
How to change a config surface safely (checklist)
- Find the reader first (tables above; when in doubt:
grep -rln '<filename>' scripts/ .claude/ .github/). A config file nobody reads is a doc, not a control โ editing it changes nothing.
- Check for a paired generated artifact (table E). If one exists: edit the source, run the generator, commit both.
- Check for a hardcoded twin. Two known:
gate-package-edits.sh hardcodes packages/* (twin of harness.config.mjs editGuard); the META/FLOWS maps in gen-skills-doc.mjs are the out-of-band twin of skill frontmatter (frontmatter carries no trigger/group).
- Route through the right gate โ schema files โ schema sign-off;
packages/* files (incl. crouton.manifest.ts) โ the packages-edit guard; see crouton-change-control.
- Verify the effect, not the edit โ e.g. after a
digests.yml change, node .claude/skills/housekeeping/schedule.mjs locally; after a stage change, node scripts/harness-stages.mjs <path>.
Adding a NEW config-as-data surface (the house pattern)
Modeled on digests.yml and routing.json (both files state the rationale in header comments):
- A versioned, PR-reviewed file with a self-documenting header comment (why it exists, field schema, who reads it) โ never a hosted UI, never constants buried in a workflow.
- A dependency-free Node reader (
scripts/*.mjs or a skill-local *.mjs) โ CI check jobs never install node_modules.
- If it renders an artifact: give the generator a
--check mode and actually wire it into a workflow (the harness-layers gap is the cautionary tale).
- Register the surface: root CLAUDE.md "Maintaining AI Documentation" table + this skill's index.
Provenance and maintenance
verified: 2026-07-02
Facts checked against the working tree: read in full โ harness.config.mjs, .claude/routing.json, .claude/settings.json, .github/digests.yml, vitest.config.ts, apps/velo/{crouton.config.js,deploy.config.json,wrangler.jsonc,app/app.config.ts}, fixtures/minimal/{crouton.config.js,e2e.manifest.json}, packages/crouton-core/crouton.manifest.ts (head), eslint.config.mjs (head), pnpm-workspace.yaml, root package.json (pnpm block); ran the four --check modes and the reader-greps. From discovery reports, cited not reproduced: the labels.yml doubled app:*/poc:* entries; routing A/B history (#824). Volatile facts (counts, exit codes, no-op states) โ re-verify here, not in the prose:
for s in gen-skills-doc gen-routing harness-layers gen-package-catalog; do node scripts/$s.mjs --check; echo "$s exit=$?"; done
grep -rln harness-layers .github/ || echo "still unwired"
ls packages/nuxt-crouton-mcp-server 2>&1
node scripts/harness-stages.mjs packages/crouton-core/x.ts
grep -c '^- name' .github/labels.yml
ls packages/*/crouton.manifest.ts | wc -l
ls fixtures/*/e2e.manifest.json | wc -l
Table E's exit codes change as work lands (e.g. new skills trip gen-skills-doc until registered in META + the HTML regenerated) โ trust the commands, not a remembered status.