write-discoverable-code
Rules for writing code that coding agents (and humans) can find and understand through plain-text search.
Skill by modem-dev
Chalk Labs
Need help deploying write-discoverable-code?
Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/modem-dev-write-discoverable-code
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: documents, backend, writing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/write-discoverable-code
What's inside
- Skill name
write-discoverable-code- Description
- Rules for writing code that coding agents (and humans) can find and understand through plain-text search. Apply whenever writing or renaming code: functions, types, constants, files, error messages, doc comments. Grounded in measurement: agents navigate by plain-text search, not by AST or language server, so every identifier is a search query and every search miss costs wasted reads.
- License
- MIT
- Instructions
- 96 lines in SKILL.md · view on GitHub
Files (1)
- SKILL.md
SKILL.md (first lines)
# Write discoverable code
Coding agents discover code by searching for strings and reading small windows around the
hits. They have no hover text, no jump-to-definition, and no memory between sessions. These
rules make code resolvable in one search instead of five.
## 1. Names are search queries
- **Exported symbols get 2–4 word names, at least one of them a domain word.**
`diffUserObjects`, not `diff`. `queueEventForDispatch`, not `queue`.
Measured on a ~700k-line monorepo: 1-word exported names are globally unique 61% of
the time; 3-word names 96%; 4+ words 98%. Three words is the knee of the curve.
Use the shortest name that greps uniquely; put the rest in the doc comment.
- **Give generic verbs their object.** `sanitizeEmailHtml`, not `sanitize`;
`validateSmtpConfig`, not `validateConfig`. Qualify only as far as uniqueness
requires, then stop.
- **One definition site per symbol.** Never copy a function between files; move it and
delete the original in the same change. Shared helpers get one concept-named home
and are imported everywhere else.
- **Do not rely on the module path to disambiguate a generic name.** The import that
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`).
- **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.
- **When behavior or audience changes, rename in the same commit.** A stale name is
misinformation with a 100% open rate — that includes visibility markers: a `_private`
helper that other modules now import needs a public name.
- **Filenames are names too — never use bare-role filenames.** `config.ts`, `types.ts`,
`utils.ts`, `helpers.ts`, `handlers.ts` say nothing in a search result and collide with
every other module's config/types/utils in the repo. Prefix the domain:
`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
- **Brand your primitive IDs.** `z.string().brand<'UserId'>()` (TS) or newtypes (Rust).
A `transferOwnership(userId: string, orgId: string)` signature makes argument
transposition invisible; branded types make it a compile error that names the concepts.
- **Use capability-token parameter types** for privileged operations (e.g. requiring an
`OrgScopedDb` instead of a raw connection). A comment is a request; a required type is
physics.
- **Model state with discriminated unions**, not clusters of nullable fields with implicit
rules.
- **Name types like they'll be quoted back** — they will be, in compiler errors the agent
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
- **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).
The definition is where a name search lands; that line is your whole message.
- **Write the plain-words phrase in the doc comment.** Searches arrive as natural language
("rate limit", "retry delay"), and camelCase identifiers don't match phrase greps —
`RateLimiter` is invisible to a search for "rate limit". The doc comment above each
export should contain, in ordinary spaced-out words, the phrase someone would search
for: a `SessionExpiryChecker` should say /\*_ Checks whether the user session has
expired. _/ so that a grep for "session expired" or "session has expired" lands here.
- **A module should make sense with its imports unread.** Each imported name plus its
doc line should say enough that the reader never has to open the source module. If
they do, the import's name is failing, not the reader.
- **Keep strings whole.** Never build event names, flags, or error codes with template
interpolation (`` `github.${entity}.${action}` `` makes `github.pr.merged` unsearchable).
Write the full literal even when a loop feels DRYer.
- **Error messages start with a unique literal prefix**, so a message seen in a log greps
straight back to the throw site. ``throw new Error(`Webhook signature mismatch for ${id}`)``,
never ``throw new Error(`${prefix}: mismatch`)``.
- **One searchable concept per file, and keep orchestrators thin.** The code that answers
"where is X done?" should live in a module named after X — the thing a reader would
ask about, not the mechanism inside — not inline in a coordinator,
pipeline, or service class. An orchestrator should read as a sequence of calls into
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
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.
- **Colocate tests** (`foo.test.ts` next to `foo.ts`) so one search finds behavior and its
specification together.
- **Mark dead ends.** `@deprecated` on the old path, with a pointer to the new one.
## Quick checklist before committing
1. Would one search for each new exported name be enough to find its implementation?
2. Would swapping two arguments of the new function fail the build?
3. Is the one thing a caller must know but the signature can't say (units, timezone,
ownership, ordering) written right at the definition?
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?From modem-dev/skills, MIT.
Signals
- Stars
- 59
- Forks
- 1
- Open issues
- 0
- Last commit (this folder)
- Jul 20, 2026 (3 months ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/modem-dev/skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
Same skill elsewhere
Copies of this SKILL.md published by others; search shows only one of them.
More from modem-dev
Done for you
Rather not set it up yourself?
Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.
See how we build AI agents →Read the case studies
Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.
- launches shipped
- 50+
- launches shipped
- media placements
- 500+
- media placements
- founders on every account
- 2
- founders on every account
Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block
Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.
Get write-discoverable-code running in your business
Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.
Cite this page
write-discoverable-code skill. Agent Mart. https://agentmart.dev/skills/modem-dev-write-discoverable-code (last updated 2026-10-24)