Skip to content

feat(check): lint bare pack-component names in game-root scenes (#490) - #494

Merged
apotema merged 1 commit into
mainfrom
feat/490-scene-name-lint
Jul 1, 2026
Merged

apotema merged 1 commit into
mainfrom
feat/490-scene-name-lint

Conversation

@apotema

@apotema apotema commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A game-root scene/prefab must reference a pack component by its namespaced registry key (citizens__Worker), not the bare local name (Worker). A bare name silently no-ops as an unknown component (RFC #596) — the entity loads with the component missing and no error. This is a nasty debugging trap (it cost real time in the pack-colony demo, where workers/workstations silently didn't exist until the keys were namespaced).

This adds a labelle check lint that catches it.

What it does

New rule scene-bare-pack-component walks the game root's scenes/ and prefabs/ and flags any bare PascalCase component reference that:

  • is not a name the game root itself owns (its components/*.zig), and
  • matches a known pack component's un-prefixed name,

emitting an actionable "Did you mean 'citizens__Worker'?" finding (listing every candidate when two packs export the same un-prefixed name).

Precision over recall (mirrors the existing check.zig rules):

  • a correct namespaced key (citizens__Worker) → silent
  • a game-owned component of the same bare name → silent (legit)
  • a genuinely-unknown name with no pack match → silent (today's behavior; the engine's warn-once path is the backstop)

Lint vs. rewrite

Implemented as a lint, not an auto-rewrite. Rewriting the game root's authored scenes is riskier — it mutates authored source and can't disambiguate when two packs export the same un-prefixed name. The pack-local rewrite (scan.rewritePackLocalRefs) only namespaces a pack's own refs at copy time; extending that to the game root is left as a follow-up. The lint lives in check — the RFC §6 enforcement net and the issue's sanctioned option 2.

Placement: check (not generate)

Put in labelle check only. It's the enforcement net the packs RFC §6 already positions for exactly these "the compile wall can't catch it" cases, is fully unit-tested and pure, and doesn't touch the (more invasive) generate pipeline. A generate-time warning could reuse the same pure scene_name_lint API later — noted as a follow-up.

Files

  • src/scene_name_lint.zig (new) — scope-aware JSONC component-ref collector + pure lint + directory walk, with unit tests.
  • src/check.zig — new scene_bare_pack_component Rule + slug.
  • src/check_cmd.zig — builds the pack (bare→namespaced) + game-owned name sets from the existing pack discovery, walks scenes/ + prefabs/; two e2e fixture tests.
  • src/root.zig — register the module for test discovery.

Testing

zig build + zig build test --summary all pass: 1120 passed, 4 skipped (46/46 steps).

Closes #490.

https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog

Summary by CodeRabbit

  • New Features
    • Added a new lint check for scene and prefab files that flags bare pack-component names and suggests the correct namespaced form.
    • Expanded lint scanning to cover additional game-root scene and prefab content.
  • Bug Fixes
    • Improved detection of component references in JSONC files, including nested structures and malformed/truncated content handling.
  • Tests
    • Added end-to-end and unit coverage for correct namespacing, game-owned components, and suggestion behavior.

A game-root scene/prefab must reference a pack component by its
namespaced registry key (`citizens__Worker`), not the bare local name
(`Worker`). A bare name silently no-ops as an unknown component (RFC
#596) — the entity loads with the component missing and no error, a
debugging trap that cost real time in the pack-colony demo.

Add a `labelle check` lint (`scene-bare-pack-component`) that walks the
game root's `scenes/` and `prefabs/` and flags any bare PascalCase
component reference that:
  - is not a name the game root itself owns (its `components/*.zig`), and
  - matches a known pack component's un-prefixed name,
emitting an actionable "did you mean 'citizens__Worker'?" finding
(listing every candidate when ambiguous). A correct namespaced key is
silent; a genuinely-unknown name with no pack match keeps today's
behavior (the engine's warn-once path is the backstop there).

Implemented as a LINT rather than an auto-rewrite: rewriting the game
root's authored scenes is riskier (mutates source, can't disambiguate
two packs exporting the same un-prefixed name) — tracked as a follow-up.
Lives in `check` (the RFC §6 enforcement net), the sanctioned home per
the issue's option 2.

- src/scene_name_lint.zig: scope-aware JSONC ref collector + pure lint +
  dir walk, with unit tests (flat-form, component-map, bundles, children,
  ambiguity, game-owned suppression).
- src/check.zig: new `scene_bare_pack_component` Rule + slug.
- src/check_cmd.zig: build the pack (bare→namespaced) + game-owned name
  sets from discovery, walk scenes/ + prefabs/; two e2e fixture tests.
- src/root.zig: register the module for test discovery.

Claude-Session: https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c391a24-1fe7-482e-945d-ba6c4beff820

📥 Commits

Reviewing files that changed from the base of the PR and between 6cf9f92 and e0b84e1.

📒 Files selected for processing (4)
  • src/check.zig
  • src/check_cmd.zig
  • src/root.zig
  • src/scene_name_lint.zig

📝 Walkthrough

Walkthrough

Adds a new scene_bare_pack_component lint rule that detects bare (unnamespaced) pack-component names referenced in game-root scenes/ and prefabs/ JSONC files, via a new scene_name_lint.zig module. Wires the scan into runLint and adds tests.

Changes

Scene bare pack-component lint

Layer / File(s) Summary
Rule definition
src/check.zig
Adds scene_bare_pack_component enum variant and its slug mapping.
JSONC scanning and lint core
src/scene_name_lint.zig
Implements PackComponent/CompRef types, a JSONC byte-walker (collectComponentRefs) that records component-reference keys while skipping comments, lintSource that flags bare names matching pack components but not game-owned components, scanScenesDir for recursive directory scanning, and accompanying unit tests.
CLI wiring and end-to-end tests
src/check_cmd.zig, src/root.zig
Adds scanSceneNames helper invoked from runLint, building pack registry keys and game-owned names then scanning scenes//prefabs/; registers the module for test discovery; updates existing fixture test and adds two new end-to-end tests for flagged and correctly-namespaced references.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CheckCmd as check_cmd.runLint
  participant SceneLint as scene_name_lint
  participant Findings as findings list

  CheckCmd->>SceneLint: build pack_components & game_owned names
  CheckCmd->>SceneLint: scanScenesDir(scenes/, pack_components, game_owned)
  SceneLint->>SceneLint: collectComponentRefs per .jsonc file
  SceneLint->>SceneLint: lintSource(refs vs pack_components/game_owned)
  SceneLint-->>Findings: append scene_bare_pack_component findings
  CheckCmd->>SceneLint: scanScenesDir(prefabs/, pack_components, game_owned)
  SceneLint-->>Findings: append additional findings
Loading

Possibly related PRs

Poem

A bare "Worker" hopped into a scene,
No prefix in sight — where has it been?
This rabbit dug deep through JSONC scope,
Found the missing citizens__ hope.
Now every findings list will show,
Just where your namespaced keys should go! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Linting game-root prefabs expands beyond #490, which only asks for game-root scenes. Restrict the lint to game-root scenes, or document and link a follow-up issue if prefabs are intentionally included.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: linting bare pack-component names in game-root scenes.
Linked Issues check ✅ Passed The PR implements the requested lint-based fix for bare pack-component names and suggests the namespaced key as required by #490.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/490-scene-name-lint

Comment @coderabbitai help to get the list of available commands.

@apotema

apotema commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0b84e1c3a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/scene_name_lint.zig
const refs = try collectComponentRefs(arena, src);
for (refs) |ref| {
// A name the game root itself owns is a legitimate bare reference.
if (containsStr(game_owned, ref.name)) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exempt built-in component names before suggesting pack keys

When a pack happens to define a component with the same bare name as an engine-provided component, legitimate root scene uses of that built-in are now reported as violations because only game_owned names are exempt here. For example, writeComponentRegistryBlock always registers .VideoComponent for scene/prefab JSONC, but if a pack also ships components/video_component.zig, { "VideoComponent": ... } in the game root will fail labelle check and suggest pack__VideoComponent even though the bare built-in key is valid. Add the engine/plugin-provided component names to the exempt set before matching pack candidates.

Useful? React with 👍 / 👎.

@apotema
apotema merged commit 55bcd4f into main Jul 1, 2026
4 checks passed
apotema added a commit that referenced this pull request Jul 1, 2026
…ins, pack-script order/staleness/context) (#500)

* fix(packs): address codex findings from #494/#496 — scene-lint builtins exemption, pack-script order/staleness/context

Four codex-review findings merged with the packs batch (#494, #496):

1. scene_name_lint.zig — exempt engine/gfx built-in component names (e.g.
   `VideoComponent`, always registered by `writeComponentRegistryBlock`) from
   the bare-pack-component lint. Previously a bare built-in was falsely flagged
   and mis-suggested to `pack__VideoComponent` when a pack shipped a same-named
   component. Adds `builtin_component_names` + exemption in `lintSource`.

2. root.zig — scan each light pack's `scripts/` at its `.plugins`
   declaration-order position (interleaved with plugins) so `ScriptScanner`'s
   `plugin_index` reflects `.plugins` order. The old two-phase scan (all
   plugins, then all packs) pushed a pack declared before a plugin behind it,
   breaking the per-state script ordering contract.

3. root.zig — when a pack ships no `scripts/` source, prune any stale dest a
   prior `generate` copied and register nothing. `copyAndScanAbs` no-ops on a
   missing source without touching the dest, so leftover copied scripts were
   otherwise scanned + compiled. Extracted `scanPackScriptsAt` (guards source
   existence) for both #2 and #3.

4. validate.zig / registries.zig — exclude pack/plugin `context` scripts
   (`plugin_name != null`) from the GameContext sentinel. A pack's
   `scripts/context.zig` no longer flips `hasContextEntry`, and `AllScripts`
   keeps importing it (instead of silently dropping it).

Tests: builtin-exemption lint test; pack-before-plugin ordering test; stale-dest
prune + present-script tests; pack-context emission test; hasContextEntry
game-vs-pack tests. Also wired `codegen/validate.zig` into root.zig's test
aggregator so its tests run under `zig build test`.

`zig build` + `zig build test` green (1134 tests pass).

Claude-Session: https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog

* fix(packs): propagate non-FileNotFound errors when probing pack scripts/ (codex P2 on #500)

`scanPackScriptsAt`'s source-`scripts/` existence probe used a catch-all that
treated ANY openDir failure — AccessDenied (permissions / broken mount), NotDir
(`scripts` is a file), etc. — the same as a missing directory: it pruned the
generated copy and silently dropped the pack's scripts, producing an incomplete
build with no error.

Mirror `copyAndScanAbs`'s source-root open (scanner.copyAndScanRecursive)
EXACTLY: tolerate ONLY `error.FileNotFound` (prune stale dest + skip) and
PROPAGATE every other error.

Test: `scanPackScriptsAt propagates a non-FileNotFound probe error and does NOT
prune (#500 codex)` — a pack whose `scripts` path is a file yields error.NotDir,
which must propagate while the pre-existing generated copy stays intact.

`zig build` + `zig build test` green (1135 tests pass).

Claude-Session: https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

packs: rewrite (or lint) bare pack-component names in game-root scenes

1 participant