feat: split entries/ into per-kind folders (gh-33) - #50
Merged
Conversation
gh-33) entries/<slug>/ moves to entries/<kind-folder>/<slug>/ (personas/themes/ fonts/shows) — this commit lands every tool, schema, guard, and doc change nested-only; the physical move of the 30 existing entries and the index.json regen land in the next commit on this same branch. - tools/catalog_lib.py: discover_entry_dirs() walks two levels, returning (kind_folder, entry_dir) pairs; new KIND_FOLDERS mapping is the single source of truth for the kind -> folder name mapping, alongside the existing KIND_SUFFIXES kind -> manifest-suffix mapping. - tools/validate.py: entries/ top-level check now enforces the four known kind folders (and nothing else); new kind-folder-mismatch check (a manifest's suffix must agree with the folder it lives under); new global slug-uniqueness check across kind folders; index.json slug-ownership now keys its owned-prefix off kind_folder too; symlink guard extended to an entry's parent (the kind folder) since nesting added a level. - tools/build_index.py, tools/lint.py: follow the shared two-level discovery; kind stays derived from the manifest filename, never the folder. - schemas/index.schema.json: the 7 path regexes gain a kind-folder segment, pinned per-kind where the branch knows its kind, four-way alternation where it's kind-agnostic (manifest/meta). - .github/diff_scope_guard.sh: slug parsing now expects entries/<kind-folder>/<slug>/..., with a clear error on an unknown kind folder; one-entry-per-PR now keys on <kind-folder>/<slug>. - .github/workflows/ci.yml: the diff-scope guard step gains a scope:catalog-wide label skip for maintainer-labeled catalog-wide PRs (like this one). - tools/run_selftest.sh + tools/testdata/: every scratch/fixture entries/ path gains its kind-folder segment; two new red cases (kind-folder mismatch, duplicate slug across kind folders). - README.md, CONTRIBUTING.md, .github/PULL_REQUEST_TEMPLATE.md: layout, copy-path, and rule wording updated to match. Nested-only after this commit — the tools will fail against the still-flat real entries/ tree until the next commit moves it.
…dex (gh-33) git mv entries/<slug> to entries/<kind-folder>/<slug> for all 30 entries (13 personas, 12 shows, 4 themes, 1 font), then regenerate index.json — only the path strings change; every sha256, generatedAt, and field value stays byte-identical to the pre-move index. Verified: tools/validate.py and tools/lint.py both pass clean against the real (now nested) entries/, tools/run_selftest.sh is fully green, and a fresh tools/build_index.py rebuild diffs empty against the committed index.json.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #33.⚠️ HOLD — do not merge until app PR GenWave-Org/genwave#472 is released and deployed: a station on an older app rejects the whole migrated index (fail-closed — empty shelf until upgraded; broadcast unaffected). The app PR accepts BOTH layouts, so merge order is strictly app → release → deploy → this.
🎯 What
entries/goes from one flat catch-all to per-kind folders — exactly the issue's ask:Commit 1 teaches the machinery; commit 2 is the move itself — 62 pure
git mvrenames (entry content byte-identical) + the regeneratedindex.json(path strings only; every sha256 andgeneratedAtunchanged).🔧 Machinery (commit 1)
catalog_lib.py:KIND_FOLDERSmapping (single source of truth), two-leveldiscover_entry_dirs(). Kind is still derived from the manifest suffix — the folder must now agree with it (newkind-folder-mismatchviolation).validate.py: top-level = only the four kind folders; slug ownership prefix isentries/<kind>/<slug>/; new global slug-uniqueness gate (two kind folders holding the same slug is now filesystem-legal, but index.json and the app both key on slug alone); symlink guard extended one level (a symlinked kind folder could otherwise smuggle reads past the leaf-only check).index.schema.json: all 7 path regexes gain the kind segment — pinned per kind where the branch knows it, four-way alternation for the kind-agnostic manifest/meta defs.diff_scope_guard.sh: slug is now segment 3; one-entry-per-PR keys on<kind>/<slug>; clear error on an unknown kind folder. The guard step in ci.yml is skipped when a maintainer applies the newscope:catalog-widelabel — which this PR carries (it touches all 30 entries by definition). If the diff-scope check shows red anyway, it raced the label at open — close/reopen refires the event with the label present.run_selftest.sh: all scratch trees nested; two new red cases (kind-folder mismatch, duplicate slug across kinds). 101 pass / 0 fail.entries/personas/example-dj/→ …).✅ Verified from the branch head
validate.pyPASS ·lint.pyPASS ·run_selftest.shSELFTEST PASS (101) · rebuild-and-diff of index.json empty. Cross-checked: the pinned sha256s in the app's test fixtures still match (paths moved, bytes didn't).🤝 Companion work: the app side already admits both layouts (GenWave-Org/genwave#472);
suggestedPersonarefs are slug-only and unaffected.