Name lib/ + tests/ as the canonical package subfolders

The depth-based rules already treat any subfolder as private, so the
config needs no change. Document lib/ (implementation) and tests/ as the
conventional two-folder shape, with entry points at the root — a fixed
convention that never needs the config extended for a new folder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Pocock
2026-07-10 10:16:03 +01:00
co-authored by Claude Opus 4.8
parent 3ea30afc68
commit 3687fb4f38
2 changed files with 8 additions and 6 deletions
@@ -17,11 +17,11 @@ src/packages/
<name>/ <name>/
index.ts ← an entry point (public). Import this from outside. index.ts ← an entry point (public). Import this from outside.
client.ts ← another entry point. Packages may expose SEVERAL. client.ts ← another entry point. Packages may expose SEVERAL.
<subfolder>/ ← internals: hidden from outside, free to import each other. lib/ ← implementation: hidden from outside, free to import each other.
tests/ ← co-located tests + fixtures (a subfolder, so private). tests/ ← co-located tests + fixtures (a subfolder, so private).
``` ```
The public surface is the package's **root files** — not one designated `index.ts`. Implementation lives in subfolders. The public surface is the package's **root files** — not one designated `index.ts`. By convention implementation lives in `lib/` and tests in `tests/`, giving every package the same two-folder shape. The rule itself is general, though: *anything* in *any* subfolder is private, so you never extend the config to add a folder.
Four rules, all `error`: Four rules, all `error`:
@@ -88,7 +88,7 @@ This is the completion criterion for the whole skill — a config that doesn't f
### 7. Document the convention ### 7. Document the convention
Write a `README.md` **in the packages folder** (`<packages-root>/README.md`) — next to the packages it governs — covering: the `src/packages/<name>/` layout, "import only through a package's entry points (its root files)", that implementation lives in subfolders, where tests live, and how to run `lint:boundaries`. **Discourage barrel files** explicitly — expose several small entry points instead of re-exporting a whole subtree through one index. Keep it to the copy-me snippet plus the four rules in one paragraph each. Write a `README.md` **in the packages folder** (`<packages-root>/README.md`) — next to the packages it governs — covering: the `src/packages/<name>/` layout (entry points at the root, `lib/` for implementation, `tests/` for tests), "import only through a package's entry points (its root files)", and how to run `lint:boundaries`. **Discourage barrel files** explicitly — expose several small entry points instead of re-exporting a whole subtree through one index. Keep it to the copy-me snippet plus the four rules in one paragraph each.
Then add a **context pointer** to it from the repo's agent-instructions file — `CLAUDE.md` if present, else `AGENTS.md` (create `AGENTS.md` if neither exists). One line is enough, e.g. `Packages are deep modules — see [src/packages/README.md](./src/packages/README.md) before adding or importing one.` This is what makes an agent discover the boundary rule instead of tripping over it. Then add a **context pointer** to it from the repo's agent-instructions file — `CLAUDE.md` if present, else `AGENTS.md` (create `AGENTS.md` if neither exists). One line is enough, e.g. `Packages are deep modules — see [src/packages/README.md](./src/packages/README.md) before adding or importing one.` This is what makes an agent discover the boundary rule instead of tripping over it.
@@ -97,6 +97,6 @@ Then add a **context pointer** to it from the repo's agent-instructions file —
## Notes ## Notes
- The config's `$1` back-references (dependency-cruiser's group matching) are what let a package reach its own internals while outsiders can't — don't flatten them into separate per-package rules. - The config's `$1` back-references (dependency-cruiser's group matching) are what let a package reach its own internals while outsiders can't — don't flatten them into separate per-package rules.
- Public vs private is decided by **depth**: a package's root files are entry points; anything in a subfolder is private. Adding an entry point is just adding a root file — no barrel, no config change. - Public vs private is decided by **depth**: a package's root files are entry points; anything in a subfolder is private. The conventional subfolders are `lib/` (implementation) and `tests/`, but the rule doesn't hardcode them — any subfolder is private, so a new folder never needs a config change. Adding an entry point is just adding a root file — no barrel.
- Packages are **flat**: one tier of immediate children under the root. A package's internals may nest as deep as you like; a package may not contain another package. - Packages are **flat**: one tier of immediate children under the root. A package's internals may nest as deep as you like; a package may not contain another package.
- Use `.cjs` (not `.js`) so the config's `module.exports` works even in `"type": "module"` repos. - Use `.cjs` (not `.js`) so the config's `module.exports` works even in `"type": "module"` repos.
@@ -4,8 +4,10 @@
// Each package under the packages root is a DEEP MODULE: a lot of behaviour // Each package under the packages root is a DEEP MODULE: a lot of behaviour
// behind a small interface. A package's PUBLIC SURFACE is its ENTRY POINTS — // behind a small interface. A package's PUBLIC SURFACE is its ENTRY POINTS —
// the files at the package root. Implementation lives in SUBFOLDERS and is // the files at the package root. Implementation lives in SUBFOLDERS and is
// private. A package may expose several small entry points (index.ts, // private — by convention `lib/` for implementation and `tests/` for tests,
// client.ts, server.ts, …) — prefer that over one giant barrel index. // though any subfolder is private. A package may expose several small entry
// points (index.ts, client.ts, server.ts, …) — prefer that over one giant
// barrel index.
// //
// The only thing you should ever need to edit here is PACKAGES_ROOT. // The only thing you should ever need to edit here is PACKAGES_ROOT.