AI Skill Report Card

Writing Discoverable Code

A91·Jul 29, 2026·Source: Web
Markdown
--- name: writing-discoverable-code description: Refactors and writes code so coding agents can find it via grep/ripgrep-based search — specific multi-word names, precise types instead of any, comments on definitions, and one spelling per concept. Use when naming functions/variables/files, reviewing code for AI-agent readability, refactoring generic or ambiguous identifiers, splitting large monolithic files, or setting up naming conventions in AGENTS.md/CLAUDE.md. --- Coding agents navigate repositories almost entirely via text search (grep/ripgrep), not compilers or dependency graphs. A name is a search term. Generic names (`create`, `data`, `Config`, `utils.ts`) return hundreds of irrelevant hits and burn the agent's context ruling them out. Specific names (`createStripeClient`, `NotificationDeliveryConfig`) resolve in one hop.
15 / 15

Before/after for a single function:

TypeScript
// Before — greps against hundreds of unrelated hits export function create(apiKey: string) { ... } function process(data) { ... } // After — greps to just its own definition + call sites export function createStripeClient(apiKey: string): StripeClient { ... } function enrichUserProfile(user: User): EnrichedUserProfile { ... }

Test any existing name before renaming:

Bash
rg -w "functionName" --type <lang> -c | wc -l # 1-3 files = good (unique). 10+ files = too generic, needs a domain word.
Recommendation
Add a bad-outcome example (e.g., a rename that grep-tests fine but breaks reverse lookup) to round out the good/bad contrast
15 / 15

Progress checklist for refactoring a file, module, or whole codebase:

  • Step 1: Inventory exported symbols (functions, classes, types, constants) and file/directory names
  • Step 2: Grep-test each name's uniqueness (rg -w "<name>" -c | wc -l)
  • Step 3: Rename generic names to 2-3 word, domain-qualified names
  • Step 4: Replace any / untyped params with precise types; introduce branded/newtypes for easily-confused primitives (IDs, units)
  • Step 5: Split god-files/god-functions into modules named after the concept they contain
  • Step 6: Move explanatory comments onto definitions (not call sites)
  • Step 7: Consolidate synonyms — pick one spelling per concept across the codebase
  • Step 8: Mark legacy/unused code paths @deprecated or remove them
  • Step 9: Record conventions in AGENTS.md / CLAUDE.md
  • Step 10: Re-run grep tests to confirm each renamed symbol now resolves to ≤3 files

Step details

Step 1-2: Find the generic names.

Bash
# List exported symbols, then check each for ambiguity rg -w "create|get|process|handle|data|result|config|utils" --type ts -c

Anything matching 10+ files across unrelated concerns is a candidate.

Step 3: Naming rule of thumb.

WordsUniquenessVerdict
1 (get)~61% uniqueToo generic
2 (getUser)~88% uniqueBorderline
3 (getUserProfile)~96% uniqueGood
4+ (getUserProfileById)~98% uniqueGreat, don't overdo it

Target 2-3 words per exported name, with at least one domain word (e.g. Stripe, Notification, User) — not generic verbs/nouns alone. In qualified-call languages (Go, Java with packages), the package/namespace counts as one word.

Step 4: Types as search anchors.

  • Replace any/untyped params with real interfaces/types — the type name itself becomes a grep target.
  • Use branded/newtypes for primitives that shouldn't mix: UserId, OrgId, ProjectId instead of three string params. This turns argument-order bugs into compiler errors that name the exact type to search for.
  • Precise return types (EnrichedUser not any/object) let agents skip reading the implementation entirely.

Step 5: File/module splitting. Large files with many unrelated concerns (e.g. one file with 80 nested route handlers) should be split into files named after the concept, not the mechanism:

Before: routes.py (all handlers, one giant function)
After:  smtp_settings.py, message_rendering.py, folder_fallbacks.py, delivery_history.py

Directory/file names are search terms exactly like symbol names — an agent will rg --files | rg -i 'session-broker' before reading anything.

Step 6: Comments belong on definitions. Agents reach code by searching a name, and that search resolves to the definition site. Put the one sentence explaining why directly above the definition — not at the call site, not in a separate doc that won't surface in the same search.

Step 7: One spelling per concept. Don't alternate organizations/customers, or create/build/make for the same operation across files. Consistent vocabulary means one search finds everything.

Step 8: Deprecate, don't silently keep.

TypeScript
/** @deprecated Use createStripeClient instead. Removed in v3. */ export function create(apiKey: string) { ... }

Unmarked legacy code will be discovered and reused by agents as if it were current.

Step 9: Write it down. Add a section to AGENTS.md/CLAUDE.md covering: naming conventions, where things live, deprecated paths, and any behavior that's deliberately absent (grep can't find the absence of code — e.g. "we do NOT sanitize inbound HTML here, see X for why").

Recommendation
Consider trimming the 'Step details' section slightly since some content overlaps with Best Practices/Pitfalls
18 / 20

Example 1: Ambiguous client function Input:

TypeScript
export function create(apiKey: string) { ... }

Output:

TypeScript
/** Creates a client for the Stripe Payments API. */ export function createStripeClient(apiKey: string): StripeClient { ... }

rg 'create' → 1,585 hits across 459 files. rg 'createStripeClient' → 43 hits across 19 files, all relevant.

Example 2: Untyped ID params Input:

TypeScript
function transferOwnership(userId: string, orgId: string, projectId: string) { ... } transferOwnership(orgId, projectId, userId); // silent bug

Output:

TypeScript
function transferOwnership(userId: UserId, orgId: OrgId, projectId: ProjectId) { ... } // swapped args now fail to compile: // Argument of type 'OrgId' is not assignable to parameter of type 'UserId'.

Example 3: God-file split Input: email_routes.py — 4,943 lines, 81 route handlers nested in one function. Output: email/smtp_settings.py, email/message_rendering.py, email/folder_fallbacks.py, email/delivery_history.py — each named after the concept it implements. Observed effect: agents that previously burned 290k+ tokens per question dropped to ~190k, with catastrophic no-answer sessions nearly eliminated.

Recommendation
Include an example of AGENTS.md/CLAUDE.md documentation output for Step 9 to make that step as concrete as the others
  • Grep-test names before and after renaming (rg -w "<name>" -c | wc -l); aim for ≤3 files.
  • Prefer 2-3 word names with a domain term over single generic verbs.
  • Use branded/newtypes for IDs and easily-confused primitives so the compiler catches misuse.
  • Never leave params/returns as any — it removes both the type-as-search-target and the compiler's ability to help.
  • Name test files after the source they cover (stripe.test.tsstripe.ts).
  • Put the explanatory comment on the definition, not the call site.
  • Consolidate on one term per concept across the whole codebase.
  • Explicitly document intentional absences of behavior — agents can't grep for what isn't there.
  • Record conventions in AGENTS.md/CLAUDE.md; it's the highest-leverage single artifact for agent navigation.
  • Renaming without grep-testing. A "better" name that still matches 50 files didn't solve anything — verify uniqueness after renaming.
  • Over-specifying names. 5+ word names (e.g. notificationRetryBackoffCalculationHelper) add noise without improving uniqueness meaningfully; 2-3 words is usually the sweet spot.
  • Relying on imports/modules for reverse lookup. Imports point forward (call site → definition) but grep has no reverse index; barrels (export * from './x') and package specifiers actively hide the real definition location. Distinctive names are the only reliable reverse lookup.
  • Assuming any is harmless because "it's just a type." It removes both the compiler's error-catching and the agent's ability to skip reading the implementation.
  • Splitting files without renaming them meaningfully. A refactor that produces helpers.py or utils2.py recreates the same generic-name problem in a new location.
  • Leaving deprecated code unmarked. Agents will find and use it as if it were current, especially if it has a shorter/simpler name than the replacement.
  • Forgetting synonyms. orgId vs organizationId, customer vs organization — pick one and use it everywhere.
0
Grade AAI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
15/15
Workflow
15/15
Examples
18/20
Completeness
15/20
Format
15/15
Conciseness
13/15