Skip to content

feat: split entries/ into per-kind folders (gh-33) - #50

Merged
genwave-radio merged 2 commits into
mainfrom
feat/gh-33-kind-folders
Aug 12, 2026
Merged

feat: split entries/ into per-kind folders (gh-33)#50
genwave-radio merged 2 commits into
mainfrom
feat/gh-33-kind-folders

Conversation

@genwave-radio

Copy link
Copy Markdown
Contributor

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:

entries/
  personas/  (13)   themes/  (4)
  shows/     (12)   fonts/   (1)

Commit 1 teaches the machinery; commit 2 is the move itself — 62 pure git mv renames (entry content byte-identical) + the regenerated index.json (path strings only; every sha256 and generatedAt unchanged).

🔧 Machinery (commit 1)

  • 🗂️ catalog_lib.py: KIND_FOLDERS mapping (single source of truth), two-level discover_entry_dirs(). Kind is still derived from the manifest suffix — the folder must now agree with it (new kind-folder-mismatch violation).
  • validate.py: top-level = only the four kind folders; slug ownership prefix is entries/<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 new scope:catalog-wide label — 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.
  • 📚 README / CONTRIBUTING / PR template: layout diagram + per-kind copy-path instructions (entries/personas/example-dj/ → …).

✅ Verified from the branch head

validate.py PASS · lint.py PASS · run_selftest.sh SELFTEST 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); suggestedPersona refs are slug-only and unaffected.

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.
@genwave-radio genwave-radio added the scope:catalog-wide Maintainer-applied: repo-wide maintenance PR, diff-scope one-entry rule does not apply label Aug 12, 2026
@genwave-radio
genwave-radio merged commit c8092e5 into main Aug 12, 2026
3 checks passed
@genwave-radio
genwave-radio deleted the feat/gh-33-kind-folders branch August 12, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:catalog-wide Maintainer-applied: repo-wide maintenance PR, diff-scope one-entry rule does not apply

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Split entries by folder

1 participant