From 0741ad2e3e593249d6ac13605900640de34dcb21 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 15:12:50 +0000 Subject: [PATCH 1/3] =?UTF-8?q?write-discoverable-code:=20stop=20flattenin?= =?UTF-8?q?g=20repos=20=E2=80=94=20folders=20organize,=20names=20disambigu?= =?UTF-8?q?ate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skill's 'put the context in the symbol, not the folder' rule plus 'one searchable concept per file' was being read as 'folders are useless', producing source roots with dozens of flat files (e.g. modem-dev/cli needed a follow-up PR to organize 67 flat files in src/). - Clarify the module-path bullet: it's about what a name must carry, not an argument against folders. - Add a section on directory structure: group files into domain-named subfolders, split by feature/layer not syntax kind, folders group but still don't disambiguate. - Point the file-splitting bullet at the subsystem folder instead of the source root, and add a checklist item for folder hygiene. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_014C4qvBvsHQScHF5idhxk6g --- write-discoverable-code/SKILL.md | 36 ++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/write-discoverable-code/SKILL.md b/write-discoverable-code/SKILL.md index a5255e8..aa021f5 100644 --- a/write-discoverable-code/SKILL.md +++ b/write-discoverable-code/SKILL.md @@ -34,7 +34,9 @@ rules make code resolvable in one search instead of five. disambiguates `users/diff.ts` from `orders/diff.ts` sits at the top of the file; the search hit is at line 300. Put the context in the symbol (`formatDurationMs`), not the folder. Exception: rigid, absolute conventions where the path carries the meaning - (e.g. every contract file exporting `Input`/`Output`). + (e.g. every contract file exporting `Input`/`Output`). This is a rule about what the + name must carry, not an argument against folders: precisely because names stay + self-contained, folders are free to do their real job — grouping (see section 2). - **One concept, one spelling.** Pick `organizationId` or `orgId` and use it everywhere; every synonym splits every future search in half. Reuse existing vocabulary in the codebase you are editing rather than introducing near-synonyms. @@ -47,7 +49,30 @@ rules make code resolvable in one search instead of five. `billing-plan-config.ts`, not `config.ts`. (`index.ts` is acceptable only as a thin re-export entry point.) -## 2. Types are the documentation agents can't skip +## 2. Names disambiguate; folders organize + +None of the rules above are a reason to flatten the repo. A directory listing is the +first search an agent runs — `ls src/` should read as a map of the system, not a wall +of filenames. Self-contained names and a real folder structure are complements, not +alternatives. + +- **Group files into domain subfolders.** A flat directory of 40 well-named files fails + the same way a 2,000-line file does: every listing returns everything, and nothing + shows which files form a subsystem. Apply the one-concept-one-home rule one level up: + each subsystem gets a folder named after it, and a new file goes into the folder whose + question it answers, not the source root. Roughly a dozen entries per folder is the + point to start asking whether a subsystem is hiding in there. +- **Folder names follow the same rules as filenames**: domain words, never bare roles. + `billing/`, `auth/`, `ingest/` — not `utils/`, `helpers/`, `common/`, or `misc/`. +- **Split by feature or layer, not by syntax kind.** Folders like `commands/`, `http/`, + `output/` keep each concept's files together and let imports flow one direction; + folders like `interfaces/`, `classes/`, `constants/` scatter every concept across the + tree so no single listing answers any question. +- **Folders group; they still don't disambiguate.** Moving `diff.ts` into `users/` does + not fix its name — the exported symbol is still `diffUserObjects`, per section 1. + Structure and naming solve different problems; do both. + +## 3. Types are the documentation agents can't skip - **Brand your primitive IDs.** `z.string().brand<'UserId'>()` (TS) or newtypes (Rust). A `transferOwnership(userId: string, orgId: string)` signature makes argument @@ -61,7 +86,7 @@ rules make code resolvable in one search instead of five. uses to self-correct. `OrgScopedDb` explains itself; `Ctx2` does not. Avoid `any`: every `any` is a spot where the compiler goes silent and the agent is back to guessing. -## 3. Say it where the search lands +## 4. Say it where the search lands - **One-line doc comment on every export**, stating the sharpest constraint the code itself can't show (units, timezone, "source time, not insert time", ownership). @@ -88,7 +113,8 @@ rules make code resolvable in one search instead of five. well-named modules; if a reader lands in it from a search, every line should point them one hop from the real implementation. Burying the implementation of several concepts in one large file makes every search for any of them land on the same wall of code. - Split until each question-sized concept has one named home, then stop: a helper + Split until each question-sized concept has one named home — filed in its subsystem's + folder (section 2), not dropped at the source root — then stop: a helper meaningful only inside one concept belongs inline, and a file per tiny function fragments one answer across several reads. The test runs both ways: a module that answers many unrelated questions is holding more than one concept. @@ -105,3 +131,5 @@ rules make code resolvable in one search instead of five. 4. Do all log/error strings exist verbatim in the source? 5. Did anything change behavior without changing its name? 6. When code moved, is it gone from where it came from? +7. Is every new file inside the folder for its subsystem — and does `ls` on that folder + (and on the source root) still read as a map, not a wall of filenames? From e0940ca88d9c22db1aa82174a7c905a52253db07 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 15:16:10 +0000 Subject: [PATCH 2/3] Generalize the folder guidance to a single principle Drop the prescriptive rules (specific layouts, entry-count thresholds, banned folder names) in favor of the general point: group related files into folders so a directory listing reads as a map, with whatever organizing principle fits the codebase. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_014C4qvBvsHQScHF5idhxk6g --- write-discoverable-code/SKILL.md | 34 ++++++++++---------------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/write-discoverable-code/SKILL.md b/write-discoverable-code/SKILL.md index aa021f5..29bd8b6 100644 --- a/write-discoverable-code/SKILL.md +++ b/write-discoverable-code/SKILL.md @@ -51,26 +51,13 @@ rules make code resolvable in one search instead of five. ## 2. Names disambiguate; folders organize -None of the rules above are a reason to flatten the repo. A directory listing is the -first search an agent runs — `ls src/` should read as a map of the system, not a wall -of filenames. Self-contained names and a real folder structure are complements, not -alternatives. - -- **Group files into domain subfolders.** A flat directory of 40 well-named files fails - the same way a 2,000-line file does: every listing returns everything, and nothing - shows which files form a subsystem. Apply the one-concept-one-home rule one level up: - each subsystem gets a folder named after it, and a new file goes into the folder whose - question it answers, not the source root. Roughly a dozen entries per folder is the - point to start asking whether a subsystem is hiding in there. -- **Folder names follow the same rules as filenames**: domain words, never bare roles. - `billing/`, `auth/`, `ingest/` — not `utils/`, `helpers/`, `common/`, or `misc/`. -- **Split by feature or layer, not by syntax kind.** Folders like `commands/`, `http/`, - `output/` keep each concept's files together and let imports flow one direction; - folders like `interfaces/`, `classes/`, `constants/` scatter every concept across the - tree so no single listing answers any question. -- **Folders group; they still don't disambiguate.** Moving `diff.ts` into `users/` does - not fix its name — the exported symbol is still `diffUserObjects`, per section 1. - Structure and naming solve different problems; do both. +Nothing above is a reason to flatten the repo. Self-contained names free folders from +disambiguation duty — they don't make them useless. A directory listing is the first +look an agent gets at an unfamiliar codebase, and it should read as a map of the +system: a flat directory of dozens of files, however well named each one is, answers +no question at all. Group related files into folders following whatever organizing +principle fits the codebase, and keep the context in the symbol regardless — moving a +file into a folder doesn't fix a generic name. ## 3. Types are the documentation agents can't skip @@ -113,8 +100,7 @@ alternatives. well-named modules; if a reader lands in it from a search, every line should point them one hop from the real implementation. Burying the implementation of several concepts in one large file makes every search for any of them land on the same wall of code. - Split until each question-sized concept has one named home — filed in its subsystem's - folder (section 2), not dropped at the source root — then stop: a helper + Split until each question-sized concept has one named home, then stop: a helper meaningful only inside one concept belongs inline, and a file per tiny function fragments one answer across several reads. The test runs both ways: a module that answers many unrelated questions is holding more than one concept. @@ -131,5 +117,5 @@ alternatives. 4. Do all log/error strings exist verbatim in the source? 5. Did anything change behavior without changing its name? 6. When code moved, is it gone from where it came from? -7. Is every new file inside the folder for its subsystem — and does `ls` on that folder - (and on the source root) still read as a map, not a wall of filenames? +7. Are new files grouped into folders with related code, rather than piling up in a + flat directory? From 6b3c398905d8c6612de4166a1ef1617f1d79d240 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 15:19:07 +0000 Subject: [PATCH 3/3] Reframe folder guidance as positive rules, not a correction Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_014C4qvBvsHQScHF5idhxk6g --- write-discoverable-code/SKILL.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/write-discoverable-code/SKILL.md b/write-discoverable-code/SKILL.md index 29bd8b6..966c720 100644 --- a/write-discoverable-code/SKILL.md +++ b/write-discoverable-code/SKILL.md @@ -34,9 +34,8 @@ rules make code resolvable in one search instead of five. disambiguates `users/diff.ts` from `orders/diff.ts` sits at the top of the file; the search hit is at line 300. Put the context in the symbol (`formatDurationMs`), not the folder. Exception: rigid, absolute conventions where the path carries the meaning - (e.g. every contract file exporting `Input`/`Output`). This is a rule about what the - name must carry, not an argument against folders: precisely because names stay - self-contained, folders are free to do their real job — grouping (see section 2). + (e.g. every contract file exporting `Input`/`Output`). Folders still group related + files (section 2); they just don't carry the name's context. - **One concept, one spelling.** Pick `organizationId` or `orgId` and use it everywhere; every synonym splits every future search in half. Reuse existing vocabulary in the codebase you are editing rather than introducing near-synonyms. @@ -49,15 +48,14 @@ rules make code resolvable in one search instead of five. `billing-plan-config.ts`, not `config.ts`. (`index.ts` is acceptable only as a thin re-export entry point.) -## 2. Names disambiguate; folders organize +## 2. Folders are the map -Nothing above is a reason to flatten the repo. Self-contained names free folders from -disambiguation duty — they don't make them useless. A directory listing is the first -look an agent gets at an unfamiliar codebase, and it should read as a map of the -system: a flat directory of dozens of files, however well named each one is, answers -no question at all. Group related files into folders following whatever organizing -principle fits the codebase, and keep the context in the symbol regardless — moving a -file into a folder doesn't fix a generic name. +A directory listing is the first search in an unfamiliar codebase, and it should read +as a map of the system. Group related files into folders — by feature, domain, or +layer, whichever organizing principle the codebase already follows — so each listing +shows a handful of named areas rather than every file at once. Folders carry the +grouping; context still lives in the symbol (section 1), so a name should make sense +even when quoted without its path. ## 3. Types are the documentation agents can't skip @@ -117,5 +115,5 @@ file into a folder doesn't fix a generic name. 4. Do all log/error strings exist verbatim in the source? 5. Did anything change behavior without changing its name? 6. When code moved, is it gone from where it came from? -7. Are new files grouped into folders with related code, rather than piling up in a - flat directory? +7. Does each new file sit in a folder alongside related code, and does the listing + still read as a map of the system?