From dc6c5d01dde515e5f414c06a3dc00433e2ec1a20 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 17 Mar 2026 11:33:34 -0700 Subject: [PATCH] feat(skills): Add idiomatic-code skill Add a source-backed skill for reviewing and rewriting public APIs, names, and contract docs toward simple, explicit interfaces. Ground the guidance in positive and contrast examples across TypeScript and Python, including oRPC, requests, Remix, better-auth, ts-rest, and Next.js. Include transformed examples, trigger sets, and the required skill registration updates. Co-Authored-By: OpenAI Codex --- .claude/settings.json | 1 + README.md | 1 + .../skills/claude-settings-audit/SKILL.md | 1 + .../skills/idiomatic-code/SKILL.md | 123 ++++++++ .../skills/idiomatic-code/SOURCES.md | 131 ++++++++ .../idiomatic-code/references/api-surface.md | 88 ++++++ .../references/common-use-cases.md | 22 ++ .../references/contrast-exemplars.md | 200 ++++++++++++ .../idiomatic-code/references/principles.md | 115 +++++++ .../references/python-exemplars.md | 78 +++++ .../references/transformed-examples.md | 287 ++++++++++++++++++ .../idiomatic-code/references/trigger-sets.md | 35 +++ .../references/troubleshooting-workarounds.md | 60 ++++ .../references/typescript-exemplars.md | 142 +++++++++ 14 files changed, 1284 insertions(+) create mode 100644 plugins/sentry-skills/skills/idiomatic-code/SKILL.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/SOURCES.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/references/api-surface.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/references/common-use-cases.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/references/contrast-exemplars.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/references/principles.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/references/python-exemplars.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/references/transformed-examples.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/references/trigger-sets.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/references/troubleshooting-workarounds.md create mode 100644 plugins/sentry-skills/skills/idiomatic-code/references/typescript-exemplars.md diff --git a/.claude/settings.json b/.claude/settings.json index 014b51c..c4c74d2 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -57,6 +57,7 @@ "Skill(sentry-skills:find-bugs)", "Skill(sentry-skills:gh-review-requests)", "Skill(sentry-skills:gha-security-review)", + "Skill(sentry-skills:idiomatic-code)", "Skill(sentry-skills:iterate-pr)", "Skill(sentry-skills:pr-writer)", "Skill(sentry-skills:security-review)", diff --git a/README.md b/README.md index 93c335b..42180f3 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Works with Claude Code, Cursor, Cline, GitHub Copilot, and other compatible agen | [find-bugs](plugins/sentry-skills/skills/find-bugs/SKILL.md) | Find bugs, security vulnerabilities, and code quality issues in local branch changes. | | [gh-review-requests](plugins/sentry-skills/skills/gh-review-requests/SKILL.md) | Fetch unread GitHub notifications for open PRs where review is requested from a specified team or opened by a team member. | | [gha-security-review](plugins/sentry-skills/skills/gha-security-review/SKILL.md) | GitHub Actions security review for workflow exploitation vulnerabilities. | +| [idiomatic-code](plugins/sentry-skills/skills/idiomatic-code/SKILL.md) | Designs and rewrites public code interfaces, names, and contract docs to be idiomatic, explicit, and easy to consume. | | [iterate-pr](plugins/sentry-skills/skills/iterate-pr/SKILL.md) | Iterate on a PR until CI passes. | | [pr-writer](plugins/sentry-skills/skills/pr-writer/SKILL.md) | Canonical workflow to create and update pull requests following Sentry conventions. | | [security-review](plugins/sentry-skills/skills/security-review/SKILL.md) | Security code review for vulnerabilities. | diff --git a/plugins/sentry-skills/skills/claude-settings-audit/SKILL.md b/plugins/sentry-skills/skills/claude-settings-audit/SKILL.md index 9bc84b0..2863bcb 100644 --- a/plugins/sentry-skills/skills/claude-settings-audit/SKILL.md +++ b/plugins/sentry-skills/skills/claude-settings-audit/SKILL.md @@ -157,6 +157,7 @@ If this is a Sentry project (or sentry-skills plugin is installed), include: "Skill(sentry-skills:find-bugs)", "Skill(sentry-skills:gh-review-requests)", "Skill(sentry-skills:gha-security-review)", + "Skill(sentry-skills:idiomatic-code)", "Skill(sentry-skills:iterate-pr)", "Skill(sentry-skills:pr-writer)", "Skill(sentry-skills:security-review)", diff --git a/plugins/sentry-skills/skills/idiomatic-code/SKILL.md b/plugins/sentry-skills/skills/idiomatic-code/SKILL.md new file mode 100644 index 0000000..5dffa0b --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/SKILL.md @@ -0,0 +1,123 @@ +--- +name: idiomatic-code +description: Designs and rewrites public code interfaces, names, and contract docs to be idiomatic, explicit, and easy to consume. Use when asked to "make this API/interface clearer", "improve naming", "rewrite docstrings/comments", "reduce magic or implicit behavior", "too much DI/factory machinery", "server vs client is confusing", or turn clever public abstractions into plain functions or objects with well-designed interfaces. +--- + +# Idiomatic Code + +Produce code that is absurdly easy to understand and consume. +Optimize first for public interface clarity, explicit behavior, and names that match the caller's mental model. + +## Use This Skill For + +- Public API and interface design +- Naming functions, types, modules, procedures, and options +- Rewriting abstractions that feel clever, generic, or hard to consume +- Making TypeScript and Python code more explicit and easier to reason about +- Rewriting docstrings, JSDoc, and comments so they explain contract and behavior + +Do not use this skill for generic cleanup, formatting, lint-only refactors, or broad code review. +Use `code-simplifier` for general simplification work that is not primarily about interface design. + +## Load Only What You Need + +| Situation | Read | +| --- | --- | +| TypeScript interfaces, RPC surfaces, or library APIs | `references/principles.md`, `references/api-surface.md`, `references/typescript-exemplars.md`, `references/transformed-examples.md` | +| Python helpers, clients, or library APIs | `references/principles.md`, `references/api-surface.md`, `references/python-exemplars.md`, `references/transformed-examples.md` | +| "How should this read for callers?" or "what should the public contract be?" | `references/api-surface.md`, plus the language-specific exemplar file | +| "Give me a concrete rewrite example" or "show me how to rewrite this" | `references/common-use-cases.md`, `references/transformed-examples.md` | +| "Why does this still feel confusing?" or "what smell am I looking at?" | `references/troubleshooting-workarounds.md`, `references/principles.md` | +| "This has too many factories/providers", "too much DI", or "too much machinery" | `references/contrast-exemplars.md`, `references/troubleshooting-workarounds.md` | +| "This reads like a state machine / DSL / framework", or "this is explicit but still noisy" | `references/contrast-exemplars.md`, `references/principles.md` | +| "This changes behavior depending on file context", "server vs client makes this hard to reason about", or "this framework relies on too much implicit behavior" | `references/contrast-exemplars.md`, `references/troubleshooting-workarounds.md` | +| "Should this skill trigger here?" or "is this really `idiomatic-code` and not `code-simplifier`?" | `references/trigger-sets.md`, `references/principles.md` | +| Naming, comments, JSDoc, or docstrings | `references/principles.md`, `references/transformed-examples.md` | +| Design review of an existing abstraction | `references/principles.md` plus the language-specific exemplar file | + +Read `SOURCES.md` only when you need provenance or exemplar rationale. + +## Core Standard + +The consumer experience is the design surface. + +When multiple designs work, choose the one that makes the call site, return value, and failure behavior easiest to predict without reading implementation details. + +## Working Method + +1. Start from the public surface, not the implementation. + List the nouns, verbs, inputs, outputs, errors, defaults, and comments a caller sees first. +2. Reduce concept count. + Remove wrappers, builders, option flags, or indirection that do not buy clear user value. +3. Rename for concrete meaning. + Prefer domain words over framework words and generic verbs like `run`, `handle`, `execute`, or `process`. +4. Make behavior explicit. + Show required inputs, optional inputs, side effects, nullability, and expected failure modes in the API shape itself. +5. Prefer plain composition. + Use plain functions, objects, and data structures before introducing classes, fluent builders, registries, or meta-programming. +6. Rewrite comments and docstrings last. + Explain contract, invariants, side effects, and failure behavior. Do not narrate line-by-line implementation. + +## Design Rules + +| Prefer | Avoid | +| --- | --- | +| One obvious way to do the common thing | Multiple entry points for the same job | +| Plain objects and named functions | Clever builders, hidden registration, magic defaults | +| Domain names at the call site | Generic names like `manager`, `service`, `utils`, `data`, `thing` | +| Options that add data, not behavior switches | Boolean flags that change semantics | +| Explicit success and failure shapes | Hidden throws, `null`, or `undefined` with no contract | +| Short contract comments | Comments that restate the code | + +## Language Guidance + +### TypeScript + +- Bias toward oRPC-style surfaces: plain object routers, obvious names, and one predictable handler shape. +- Prefer APIs that read cleanly at the call site over APIs that maximize inference tricks. +- Use `Result`-like return values or small tagged unions when expected failures are part of normal behavior. +- Keep schema and transport details close to the procedure or function they shape. +- When the design starts drifting toward provider graphs, container tokens, state-machine ceremony, or theory-heavy vocabulary, load `references/contrast-exemplars.md` and simplify back toward the caller's mental model. +- When a framework changes semantics through directives, file placement, or routing conventions, make those boundaries explicit in local module interfaces instead of letting hidden mode switches leak everywhere. +- Prefer naming conventions that reveal structure over directives that silently change semantics. + +### Python + +- Prefer requests and HTTPX style APIs: obvious verbs, obvious arguments, small objects, and clear return values. +- Use keyword-only arguments to make optional behavior explicit. +- Raise named exceptions for exceptional failures; document them briefly in the docstring. +- Keep docstrings short and contract-focused. + +## Comments And Docstrings + +Good comments and docstrings answer one of these questions: + +- What does the caller get back? +- What side effects happen? +- What invariants or preconditions matter? +- Which failures are expected? +- Why is this design intentionally narrower than alternatives? + +Avoid comments and docstrings that only translate syntax into English. + +## Output Expectations + +When rewriting code: + +1. Produce the clearer interface first. +2. Preserve behavior unless the user asked for a semantic change. +3. If you changed the public surface, include a short rationale with at most 3 bullets focused on naming, interface shape, and failure behavior. +4. When comments or docstrings are part of the task, include the rewritten versions in the final code. + +When reviewing code: + +1. Focus findings on interface clarity, naming, concept count, and contract documentation. +2. Do not spend the review on formatting or stylistic nits. +3. Call out when the current abstraction is more complex than its use cases require. + +## Boundaries + +- Preserve local project conventions when they are already strong, even if they differ from the exemplars. +- Do not introduce an RPC framework, result type, or schema library just because a good example uses it. +- Do not replace a familiar local pattern with a "cleaner" abstraction if the migration cost outweighs the clarity gain. +- If the existing code intentionally hides complexity behind a stable public interface, simplify the public contract first and leave internals alone unless the user asked for deeper refactoring. diff --git a/plugins/sentry-skills/skills/idiomatic-code/SOURCES.md b/plugins/sentry-skills/skills/idiomatic-code/SOURCES.md new file mode 100644 index 0000000..6105ce4 --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/SOURCES.md @@ -0,0 +1,131 @@ +# Sources + +This file tracks source material synthesized into `idiomatic-code`, plus the decisions taken from that material. + +## Selected example profile + +- `plugins/sentry-skills/skills/skill-writer/references/examples/documentation-skill.md` + - Used as the closest synthesis pattern for a source-backed, reference-heavy skill with intent-based loading and transformed examples. + +## Current source inventory + +| Source | Type | Trust tier | Retrieved | Confidence | Contribution | Usage constraints | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `AGENTS.md` | repo convention | canonical | 2026-03-17 | high | Registration rules, alias policy, path conventions | repository-local policy | Required local source of truth | +| `README.md` | repo convention | canonical | 2026-03-17 | high | Public skill inventory format and authoring conventions | repository-local policy | Determines README registration shape | +| `plugins/sentry-skills/skills/code-simplifier/SKILL.md` | local adjacent skill | canonical | 2026-03-17 | high | Boundary definition versus generic simplification | repository-local policy | Used to keep trigger boundaries clean | +| `plugins/sentry-skills/skills/security-review/SKILL.md` | local pattern example | canonical | 2026-03-17 | high | Domain-expert skill structure with selective reference loading | repository-local policy | Used as the main structural pattern | +| `plugins/sentry-skills/skills/skill-writer/SKILL.md` | local canonical | canonical | 2026-03-17 | high | Required workflow for creating this skill | repository-local policy | Primary local authoring workflow | +| `plugins/sentry-skills/skills/skill-writer/references/*.md` | local canonical | canonical | 2026-03-17 | high | Synthesis, authoring, description optimization, and validation requirements | repository-local policy | Includes required provenance and artifact expectations | +| `https://orpc.unnoq.com/` | external official docs | canonical | 2026-03-17 | medium | Primary TypeScript exemplar for plain public shapes and clean RPC design | docs may evolve | Anchor exemplar for TypeScript guidance | +| `https://orpc.unnoq.com/docs/migrations/from-trpc` | external official docs | canonical | 2026-03-17 | medium | Confirms object-router and `handler`-oriented design differences | docs may evolve | Supports the skill's oRPC-first bias | +| `https://trpc.io/docs/server/procedures` | external official docs | canonical | 2026-03-17 | medium | Secondary RPC exemplar and comparison point | docs may evolve | Used for contrast and limited positive evidence | +| `https://hono.dev/docs/api/` | external official docs | canonical | 2026-03-17 | medium | Small-core routing and Web-standard-aligned API design | docs may evolve | Positive TypeScript exemplar | +| `https://hono.dev/docs/guides/rpc` | external official docs | canonical | 2026-03-17 | medium | Shows how an RPC surface can remain small on top of Hono | docs may evolve | Supports plain-surface guidance | +| `https://valibot.dev/guides/internal-architecture/` | external official docs | canonical | 2026-03-17 | medium | Pure factory function and plain-object design rationale | docs may evolve | Positive TypeScript exemplar | +| `https://github.com/gvergnaud/ts-pattern` | external upstream README | secondary | 2026-03-17 | medium | Tiny semantic core and naming clarity for fluent APIs | GitHub README, not a formal spec | Positive but narrower TypeScript exemplar | +| `https://github.com/supermacro/neverthrow` | external upstream README | secondary | 2026-03-17 | medium | Explicit result-based failure semantics | GitHub README, not a formal spec | Positive TypeScript exemplar for visible failures | +| `https://ts-rest.com/quickstart` | external official docs | canonical | 2026-03-17 | medium | Contrast example for richer contract-first APIs | docs may evolve | Used to define what is beyond the default simplicity bar | +| `https://docs.nestjs.com/fundamentals/custom-providers` | external official docs | canonical | 2026-03-17 | high | Contrast example for provider, token, and factory-heavy DI surfaces | docs may evolve | Used for machinery-heavy failure mode | +| `https://inversify.io/docs/api/container/` | external official docs | canonical | 2026-03-17 | high | Contrast example for container-managed binding and resolution vocabulary | docs may evolve | Used for machinery-heavy failure mode | +| `https://inversify.io/docs/ecosystem/binding-decorators/` | external official docs | canonical | 2026-03-17 | medium | Confirms decorator-driven registration as a contrast smell when overused | docs may evolve | Supports Inversify contrast guidance | +| `https://stately.ai/docs/setup` | external official docs | canonical | 2026-03-17 | high | Contrast example for concept-heavy state-machine setup vocabulary | docs may evolve | Used for concept-heavy failure mode | +| `https://stately.ai/docs/xstate` | external official docs | canonical | 2026-03-17 | high | Confirms state machines, actors, and orchestration as the public model | docs may evolve | Supports XState contrast guidance | +| `https://gcanti.github.io/fp-ts/` | external official docs | canonical | 2026-03-17 | high | Contrast example for abstraction-first FP vocabulary | docs may evolve | Used for abstraction-first failure mode | +| `https://gcanti.github.io/fp-ts/modules/` | external official docs | canonical | 2026-03-17 | high | Shows breadth of module and type-class surface area | docs may evolve | Supports fp-ts contrast guidance | +| `https://effect.website/` | external official docs | canonical | 2026-03-17 | high | Contrast example for style-shift, learning curve, and extensive API surface | docs may evolve | Used to classify Effect precisely rather than as blanket complexity | +| `https://nextjs.org/docs/app/getting-started/server-and-client-components` | external official docs | canonical | 2026-03-17 | high | Contrast example for server/client split behavior and component-mode boundaries | docs may evolve | Used for Next.js hidden-boundary guidance | +| `https://nextjs.org/docs/app/api-reference/directives/use-client` | external official docs | canonical | 2026-03-17 | high | Confirms file-level client boundary behavior and serialization constraints | docs may evolve | Supports directive-based contrast guidance | +| `https://nextjs.org/docs/app/api-reference/directives/use-server` | external official docs | canonical | 2026-03-17 | high | Confirms file-level and inline server function directives | docs may evolve | Supports server action boundary guidance | +| `https://nextjs.org/docs/app/getting-started/updating-data` | external official docs | canonical | 2026-03-17 | high | Confirms Server Actions as framework-mediated mutation interfaces | docs may evolve | Supports Next.js mutation contrast guidance | +| `https://nextjs.org/docs/app/api-reference/file-conventions` | external official docs | canonical | 2026-03-17 | high | Confirms breadth of file-system conventions affecting behavior | docs may evolve | Supports convention-driven contrast guidance | +| `https://nextjs.org/docs/app/api-reference/file-conventions/intercepting-routes` | external official docs | canonical | 2026-03-17 | medium | Contrast example for routing behavior added by special folder conventions | docs may evolve | Supports advanced route-convention guidance | +| `https://nextjs.org/docs/app/api-reference/file-conventions/parallel-routes` | external official docs | canonical | 2026-03-17 | medium | Shows slot-based route composition and special folder semantics | docs may evolve | Supports routing complexity guidance | +| `https://nextjs.org/docs/app/api-reference/file-conventions/route-groups` | external official docs | canonical | 2026-03-17 | medium | Shows route grouping as a positive convention that can become harder to reason about when overused | docs may evolve | Supports nuanced Next.js classification | +| `https://remix.run/docs/en/main/file-conventions/routes` | external official docs | canonical | 2026-03-17 | high | Positive reference for route file naming that is easier to reason about than directive-driven mode switches | docs may evolve | Supports Remix-vs-Next.js naming distinction | +| `https://www.better-auth.com` | external official docs | canonical | 2026-03-17 | medium | Positive reference for explicit auth configuration and a visible client/server split | docs may evolve | Supports better-auth as a secondary TypeScript exemplar | +| `https://www.better-auth.com/docs/concepts/plugins` | external official docs | canonical | 2026-03-17 | medium | Supports plugin additions as visible configuration rather than hidden behavior | docs may evolve | Supports better-auth exemplar guidance | +| `https://requests.readthedocs.io/en/latest/user/quickstart/` | external official docs | canonical | 2026-03-17 | high | Primary Python exemplar for obvious verbs and short examples | docs may evolve | Positive Python anchor | +| `https://www.python-httpx.org/quickstart/` | external official docs | canonical | 2026-03-17 | high | Modern client design with requests-like usability and explicit options | docs may evolve | Positive Python exemplar | +| `https://pluggy.readthedocs.io/en/stable/` | external official docs | canonical | 2026-03-17 | medium | Small extension vocabulary with clear roles | docs may evolve | Positive Python exemplar for extensibility interfaces | +| `https://click.palletsprojects.com/en/stable/` | external official docs | canonical | 2026-03-17 | medium | Clear command/function mapping and readable option names | docs may evolve | Positive Python exemplar | +| `https://docs.python.org/3/library/pathlib.html` | external standard library docs | canonical | 2026-03-17 | medium | Coherent object model and direct naming | Python version may vary | Secondary Python exemplar | + +## Decisions + +1. `idiomatic-code` is a design and interface skill, not a general simplification skill. +2. TypeScript guidance is biased toward plain object and function APIs rather than fluent magic. +3. oRPC is the primary TypeScript exemplar because its naming and interface shape are closer to the target simplicity bar than ts-rest. +4. ts-rest remains useful as a contrast example for "plain data, but more surface area than needed by default." +5. Python guidance is anchored on requests and HTTPX style APIs with small surfaces and short contract docs. +6. The skill remains documentation-only in v1; no scripts are necessary. +7. Transformed examples are required because abstract advice is too easy to apply shallowly. +8. Named contrast examples are grouped by failure mode, not by a blanket notion of quality. +9. Effect is a style-shift contrast, not simply a "too complex" library. +10. Machinery-heavy and concept-heavy smells are useful warning labels when reviewing local abstractions. +11. Convention-driven design can be good; Next.js is a contrast because some conventions become hidden mode boundaries that alter runtime behavior. +12. The negative lesson from Next.js is not "avoid conventions" but "avoid semantics that depend mainly on directives, file placement, or framework context." +13. Naming conventions can be a positive design tool when they reveal structure directly; Remix route file naming is the cleaner positive reference in this area. +14. The specific problem with Next.js is not its naming conventions but its directive-driven and context-driven semantics. +15. A full pass should pair each major contrast smell with either a positive adjacent exemplar or a transformed rewrite pattern. +16. better-auth is a positive secondary TypeScript exemplar for explicit capability configuration and a visible server/client split, but it is not a primary anchor on the level of oRPC. + +## Coverage matrix + +| Dimension | Coverage status | Notes | +| --- | --- | --- | +| API surface and behavior contracts | complete | Covered in `references/api-surface.md`, `SKILL.md`, and transformed examples | +| Config/runtime options | complete | Covered in `references/api-surface.md`, `references/principles.md`, and common use cases about option shapes | +| Common use cases | complete | Covered in `references/common-use-cases.md` and `references/transformed-examples.md` | +| Known issues/workarounds | complete | Covered in `references/troubleshooting-workarounds.md` | +| Version/migration variance | complete | Covered by the source set and decision to treat oRPC as the primary bar while using tRPC and ts-rest as comparison and migration context | +| Public surface shape | complete | Covered in `SKILL.md`, `references/principles.md`, and both language exemplar files | +| Naming clarity | complete | Covered in `references/principles.md` and transformed examples | +| Composition model | complete | Covered in the TypeScript and Python exemplar files | +| Docs and comments quality | complete | Covered in `SKILL.md`, `references/principles.md`, and the Python example | +| Failure semantics | complete | Covered in `references/principles.md` and the robust transformed example | +| Negative examples and corrections | complete | Covered in `references/transformed-examples.md` and `references/contrast-exemplars.md` | + +## Open gaps + +1. Add one more standard-library-style TypeScript exemplar if a future revision needs stronger coverage outside RPC and schema tooling. +2. Add repo-specific transformed examples from real Sentry code if future iterations need tighter local calibration. +3. Add one transformed example that explicitly removes DI/provider machinery from a small module if future iterations need a stronger rewrite pattern. + +## Stopping rationale + +Additional retrieval was low-yield after oRPC, Hono, Valibot, requests, HTTPX, pluggy, and Click all converged on the same guidance: + +- keep the public surface small +- use names the caller can predict +- prefer plain functions and objects +- keep failure behavior visible +- teach through direct examples instead of framework philosophy + +At that point, more source collection was mostly producing restatements or more complicated variants of the same ideas. + +For the contrast pass, NestJS, InversifyJS, XState, fp-ts, Effect, and ts-rest converged on a second useful conclusion: + +- some APIs are difficult because of machinery +- some because of concept load +- some because they assume a different programming style +- some because the contract is explicit but too rich for the common path + +That made failure-mode categorization more useful than collecting more named examples. + +For the Next.js pass, the official docs confirmed a third useful conclusion: + +- convention-driven design is not the problem by itself +- naming conventions can be helpful when they directly reveal structure +- the problem appears when conventions become hidden mode switches +- directives, file placement, and special route folders can make behavior harder to predict than the module interface suggests + +That made "convention-driven with hidden mode boundaries" a useful contrast category. + +## Changelog + +- 2026-03-17: Created `idiomatic-code` with source-backed TypeScript and Python exemplars, transformed examples, and repo registration notes. +- 2026-03-17: Added named contrast examples grouped by failure mode, including machinery-heavy, concept-heavy, abstraction-first, style-shift, and contract-rich categories. +- 2026-03-17: Added Next.js as a nuanced contrast example for convention-driven design with hidden mode boundaries. +- 2026-03-17: Ran a full consistency pass, added Remix as a positive naming reference in TypeScript guidance, and added a transformed example for pulling directive-driven framework boundaries to the edge. +- 2026-03-17: Tightened the trigger description, added explicit should-trigger/should-not-trigger query sets, removed unnecessary client-side directive usage from the framework-boundary example, and added better-auth as a secondary TypeScript exemplar. diff --git a/plugins/sentry-skills/skills/idiomatic-code/references/api-surface.md b/plugins/sentry-skills/skills/idiomatic-code/references/api-surface.md new file mode 100644 index 0000000..edf8a61 --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/references/api-surface.md @@ -0,0 +1,88 @@ +# API Surface + +Use this reference when the user is shaping a public interface, not just refactoring internal implementation. + +## Surface Checklist + +Before rewriting anything, identify: + +- the exported nouns +- the exported verbs +- the input shape +- the output shape +- default behavior +- expected failures +- side effects +- the one most common call site + +If any of these are hard to describe in one short sentence, the public surface is probably too complicated. + +## Design Targets + +### Functions + +Prefer named functions when the job can be described in one verb: + +- `listMembers` +- `createInvoice` +- `retryDelivery` + +Use one obvious input shape. +Avoid mixing identity, behavior flags, and transport details in one loose options bag. + +### Procedure Collections + +Prefer a small object of named procedures when the consumer needs a family of related operations: + +- `memberRouter.list` +- `memberRouter.invite` +- `memberRouter.remove` + +This is usually clearer than one `execute` function with an `action` field. + +### Types And Options + +Keep input types close to the function or procedure they describe. +Prefer option names that explain behavior directly: + +- `notify` +- `includeTeams` +- `timeoutMs` +- `retry` + +Avoid names that require implementation knowledge: + +- `config` +- `payload` +- `contextId` +- `mode` + +### Errors + +Public APIs must make failure behavior visible. + +Use: + +- named exceptions in Python +- tagged unions or `Result` types in TypeScript for expected failures +- short docstrings or JSDoc that name expected failures + +## Decision Rules + +Choose the narrowest public shape that satisfies the common case: + +1. one named function +2. small object of named functions or procedures +3. stateful object only when state is a real part of the contract + +Move to the next option only when the simpler one stops being clear. + +## Review Questions + +Ask these before finalizing: + +1. Can a caller guess the right entry point from the name alone? +2. Is there one obvious common path? +3. Are optional knobs clearly named? +4. Is failure behavior visible? +5. Would the interface still make sense in a code sample with no prose around it? diff --git a/plugins/sentry-skills/skills/idiomatic-code/references/common-use-cases.md b/plugins/sentry-skills/skills/idiomatic-code/references/common-use-cases.md new file mode 100644 index 0000000..fe7e158 --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/references/common-use-cases.md @@ -0,0 +1,22 @@ +# Common Use Cases + +Use these patterns when the user wants a concrete rewrite path, not just general principles. + +1. Rewrite a generic `execute` or `handle` API into a small object of named functions or procedures. +2. Split one function with `mode` or boolean behavior flags into separate functions with domain names. +3. Turn a builder-heavy TypeScript API into an oRPC-like plain object surface with one predictable handler shape. +4. Replace hidden or ad hoc error handling with an explicit return contract or named exceptions. +5. Rewrite a Python helper so required inputs are positional, optional behavior is keyword-only, and the docstring names expected failures. +6. Move transport, schema, or validation details next to the function or procedure they shape instead of hiding them behind framework layers. +7. Rename exported symbols so the call site uses domain words instead of framework or implementation words. +8. Replace comments that narrate implementation with short contract comments that explain return value, side effects, and invariants. +9. Pull server/client or action-specific framework behavior to the edge and expose a plain local module interface underneath. + +## Default Rewrite Order + +1. Name the actual job. +2. Reduce the number of entry points. +3. Make the input shape obvious. +4. Make failure behavior obvious. +5. Keep framework boundaries and mode switches at the edge. +6. Rewrite comments and docstrings last. diff --git a/plugins/sentry-skills/skills/idiomatic-code/references/contrast-exemplars.md b/plugins/sentry-skills/skills/idiomatic-code/references/contrast-exemplars.md new file mode 100644 index 0000000..d16ee75 --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/references/contrast-exemplars.md @@ -0,0 +1,200 @@ +# Contrast Exemplars + +Use this reference when the user is reacting to an interface that is technically capable but harder to consume than it needs to be. + +These are contrast examples, not blanket negative judgments. +Each project below is useful in the right context. The point is to recognize when local code has drifted toward the same failure mode without needing the same power. + +## Machinery-Heavy: Container And Provider Systems + +### NestJS Custom Providers + +What it is good at: + +- flexible dependency injection +- swapping implementations for testing and environment-specific behavior +- centralizing wiring in large apps + +Why it exceeds this skill's default simplicity bar: + +- the consumer vocabulary quickly expands from classes to tokens, `useClass`, `useValue`, `useFactory`, `useExisting`, and `@Inject` +- provider wiring can become more visible than the business operation itself + +Local smell to watch for: + +- exported code reads like container registration instead of domain behavior +- factories and tokens proliferate before the business interface is even clear + +Preferred correction: + +- keep DI and provider machinery internal +- expose a plain function, router, or small object with domain names + +### InversifyJS + +What it is good at: + +- container-managed composition in larger modular systems +- binding scopes, container modules, and runtime resolution + +Why it exceeds this skill's default simplicity bar: + +- the public mental model often shifts from "what does this module do?" to bindings, identifiers, scopes, modules, decorators, and container behavior +- annotation-driven or module-driven registration can make the real interface harder to see + +Local smell to watch for: + +- many service identifiers, decorators, or container modules before a caller can perform one useful action +- factory and binding vocabulary dominates the exported API + +Preferred correction: + +- collapse wiring behind a direct module API +- make the consumer call site independent from container terminology + +## Concept-Heavy: State And Orchestration DSLs + +### XState + +What it is good at: + +- explicit orchestration of genuinely complex workflows +- predictable state transition modeling + +Why it exceeds this skill's default simplicity bar: + +- actions, guards, actors, delays, snapshots, setup, and machine configuration can all appear before the reader sees the business operation +- the concept load is high even when the underlying task is small + +Local smell to watch for: + +- simple UI or workflow code starts reading like a machine definition language +- the reader has to understand orchestration vocabulary before the business action is obvious + +Preferred correction: + +- use plain functions and narrow state objects for small workflows +- reserve a full state-machine surface for problems that are truly state-machine-shaped + +## Abstraction-First: Theory And Algebra Vocabulary + +### fp-ts + +What it is good at: + +- strongly structured functional composition +- reusable abstractions for teams already fluent in typed FP + +Why it exceeds this skill's default simplicity bar: + +- the docs explicitly assume FP knowledge and center abstractions from Haskell, PureScript, and Scala +- type classes, higher-kinded types, and algebraic vocabulary can dominate the reader experience + +Local smell to watch for: + +- exported code requires the caller to parse `pipe`, type-class vocabulary, or layered functional abstractions before understanding the domain action +- helper APIs optimize for abstraction reuse more than call-site readability + +Preferred correction: + +- keep abstraction-heavy helpers internal +- expose domain functions whose names and inputs tell the story directly + +## Style-Shift: A Different Programming Model + +### Effect + +What it is good at: + +- coherent error handling, concurrency, retries, dependency management, and composition +- scaling a consistent programming model across large TypeScript systems + +Why it is a style-shift contrast: + +- the official docs explicitly call out a learning curve, a different programming style, and an extensive API surface +- this is not merely "too complex"; it is a different default way of structuring programs + +Local smell to watch for: + +- a small module starts adopting framework-wide concepts and runtime vocabulary before the user problem demands them +- the caller has to learn the programming model before the interface becomes obvious + +Preferred correction: + +- keep the public contract simple even if internals use richer Effect-style composition +- do not force the entire programming model onto small, local interfaces + +## Contract-Rich: Explicit But Still Too Busy + +### ts-rest + +What it is good at: + +- explicit contract-first API definitions +- keeping request and response details visible + +Why it is a lighter contrast: + +- it remains relatively plain, but its surface can accumulate params, headers, metadata, and response maps faster than the common case needs +- the interface can become clear but still noisy + +Local smell to watch for: + +- a contract is fully explicit, yet the common path is still hard to scan +- transport detail overwhelms the one thing the caller is trying to do + +Preferred correction: + +- keep the common path tiny +- move advanced contract detail off the main line unless it materially changes how callers use the API + +## Convention-Driven With Hidden Mode Boundaries + +### Next.js + +What it is good at: + +- file-based routing and colocated route structure can be clear at the simple end +- some file and route naming conventions make app organization obvious +- layouts, pages, and route-level boundaries can be productive when the hierarchy stays shallow +- at its best, naming conventions give you useful structure without much ceremony + +Why it exceeds this skill's default simplicity bar: + +- behavior changes across Server Components, Client Components, and Server Actions +- directives like `'use client'` and `'use server'` create mode boundaries that are easy to miss at the call site +- special routing conventions such as route groups, parallel routes, and intercepting routes add behavior through folder names and placement +- the reader often needs framework context to predict where code runs, what can be imported, and which data can cross the boundary +- the directives feel like magic because semantics depend on top-of-file markers instead of the module interface itself + +Local smell to watch for: + +- a component or module changes behavior depending on file directive or file location +- the interface only makes sense once the reader knows whether it runs on the server, the client, or through a framework action boundary +- route behavior depends on special folders or naming conventions that are no longer obvious from the page or module API +- a simple mutation suddenly depends on form actions, serialization rules, or framework-specific execution context + +Preferred correction: + +- keep framework-specific mode switches at the edge and expose plain functions or modules underneath +- make server/client and action boundaries explicit in local names and module structure +- use convention-driven routing while it is locally obvious, but treat advanced conventions as a cost that must pay for itself +- avoid APIs whose semantics depend primarily on where the file lives +- keep the naming convention if it helps, but remove directive-driven magic from the local API shape + +Positive adjacent reference: + +- Remix route file naming is a cleaner example of convention-driven structure because the route shape is carried more by the filename itself than by hidden client/server directives +- use Remix as the positive bar for naming-based organization, and Next.js as the warning sign for conventions that start changing execution semantics + +## Decision Rule + +When you see one of these failure modes locally, treat the project as a contrast example, not a default bar. +Extract the smallest design lesson: + +- machinery-heavy: hide the wiring +- concept-heavy: lower the concept count +- abstraction-first: expose domain language first +- style-shift: keep the public contract simple even if internals are richer +- contract-rich: trim the common path until it reads in one straight line +- convention-driven with hidden mode boundaries: keep conventions helpful, but do not let file context or directives become the main source of behavior diff --git a/plugins/sentry-skills/skills/idiomatic-code/references/principles.md b/plugins/sentry-skills/skills/idiomatic-code/references/principles.md new file mode 100644 index 0000000..024548e --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/references/principles.md @@ -0,0 +1,115 @@ +# Principles + +Use this rubric to design or rewrite code so the interface is obvious before the implementation is inspected. + +## Public Surface First + +Start with the code a consumer reads first: + +- exported functions +- procedure names +- public object keys +- constructor arguments +- option names +- return values +- docstrings and comments + +If the call site is confusing, the design is confusing. + +## Naming + +Names should tell the consumer what the code means, not how it is implemented. + +| Prefer | Instead of | +| --- | --- | +| `createInvoice` | `executeInvoiceFlow` | +| `listMembers` | `handleMembers` | +| `retry` | `shouldTryAgain` | +| `timeoutMs` | `configValue` | +| `organizationId` | `contextId` | + +Rules: + +- Use domain nouns and verbs. +- Make similar operations read like a family. +- Avoid "manager", "service", "helper", "util", "processor", and "data" unless they name a real domain concept. +- Split overloaded functions before inventing generic names. + +## Interface Shape + +Choose the smallest interface that makes the common case obvious. + +Prefer: + +- one clear entry point per job +- objects when a named shape helps readability +- positional arguments only when there are one or two obvious inputs +- keyword-only or named options for optional behavior +- separate functions or procedures when behavior truly changes + +Avoid: + +- one function with many booleans +- option bags that mix identity, behavior flags, and transport details +- classes with one public method +- builders that exist only to hide required parameters + +## Explicit Behavior + +The interface should answer these questions without digging through the body: + +- What inputs are required? +- What is optional? +- What shape comes back? +- What can fail? +- Which failures are part of normal behavior? +- What side effects happen? + +If the answer depends on hidden conventions, make it explicit in code or docs. + +## Failure Semantics + +Make expected failures visible. + +Prefer: + +- named exceptions in Python +- tagged unions or `Result` types in TypeScript when failures are normal outcomes +- narrow error categories over generic strings +- short comments or docstrings that name expected failures + +Avoid: + +- `return null` with no contract +- one catch-all `Error` +- silently swallowing failures +- making callers infer whether a function throws + +## Comments And Docstrings + +Good contract comments are short and specific. + +Use comments and docstrings to describe: + +- return contract +- side effects +- invariants +- expected failures +- why the API is intentionally constrained + +Do not use comments and docstrings to: + +- paraphrase the next line +- narrate obvious control flow +- explain internal mechanics the caller does not need + +## Pressure Test + +Before finalizing a rewrite, answer these questions: + +1. Could a new caller predict how to use this from the names alone? +2. Is there one obvious path through the public API? +3. Are behavior-changing flags gone or at least clearly named? +4. Does the interface show how failure works? +5. Do comments and docstrings explain contract instead of implementation? +6. Would the code still feel clear if the caller never read the implementation body? diff --git a/plugins/sentry-skills/skills/idiomatic-code/references/python-exemplars.md b/plugins/sentry-skills/skills/idiomatic-code/references/python-exemplars.md new file mode 100644 index 0000000..85a413d --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/references/python-exemplars.md @@ -0,0 +1,78 @@ +# Python Exemplars + +Use these examples to calibrate what strong interface clarity, small public surfaces, and concise contract documentation look like. + +## requests + +What makes this a good example: + +- obvious verbs like `get`, `post`, and `request` +- very small amount of surface area for the common case +- examples that teach by showing straightforward calls first + +What to avoid: + +- hiding basic behavior behind wrappers that are less obvious than the original call +- adding helper layers that rename standard HTTP concepts without a good reason + +## HTTPX + +What makes this a good example: + +- requests-like usability with explicit modern details such as clients, timeouts, and async variants +- parallel sync and async APIs that stay recognizable +- names that tell the caller exactly what they are configuring + +What to avoid: + +- exposing transport knobs before callers need them +- letting configuration objects replace simple function arguments when the common case is small + +## pluggy + +What makes this a good example: + +- extremely small extension vocabulary +- clear separation between hook definition and hook implementation +- names like `hookspec`, `hookimpl`, and `PluginManager` that explain the role directly + +What to avoid: + +- plugin systems with hidden registration rules +- too many lifecycle concepts before the caller has even used one hook + +## Click + +What makes this a good example: + +- command functions that read like normal Python +- decorators and docstrings that map directly to CLI help +- clear option and argument names + +What to avoid: + +- burying the command contract in decorator noise +- using short option names when the long name is the one users actually understand + +## pathlib + +What makes this a good example: + +- one coherent object model +- names that map closely to real filesystem concepts +- methods that usually mean exactly what they say + +What to avoid: + +- wrapping a simple domain in many helper classes +- generic methods that combine unrelated file operations + +## Python Heuristics + +If you are rewriting a Python API, prefer this order of choices: + +1. named function with a short docstring +2. small module of related functions +3. object with real state and several meaningful methods + +Reach for classes, decorators, or registries only when the public contract stays clearer because of them. diff --git a/plugins/sentry-skills/skills/idiomatic-code/references/transformed-examples.md b/plugins/sentry-skills/skills/idiomatic-code/references/transformed-examples.md new file mode 100644 index 0000000..0ab4495 --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/references/transformed-examples.md @@ -0,0 +1,287 @@ +# Transformed Examples + +These are directly reusable examples of the style this skill should produce. + +## Happy Path: Plain, oRPC-Like Router Surface + +### Before + +```ts +type MemberAction = "list" | "invite"; + +type ExecuteMemberRequest = { + action: MemberAction; + organizationId: string; + payload?: unknown; +}; + +export function createMemberService(context: AppContext) { + return { + async execute(request: ExecuteMemberRequest) { + if (request.action === "list") { + return listMembers(context, request.organizationId); + } + + if (request.action === "invite") { + return inviteMember(context, request.organizationId, request.payload as InviteMemberInput); + } + + throw new Error(`Unsupported member action: ${request.action}`); + }, + }; +} +``` + +### After + +```ts +export const memberRouter = { + list: protectedProcedure + .input(ListMembersInput) + .handler(({ context, input }) => { + return listMembers(context, input.organizationId); + }), + + invite: protectedProcedure + .input(InviteMemberInput) + .handler(({ context, input }) => { + return inviteMember(context, input.organizationId, input.email); + }), +}; +``` + +Why this is better: + +- The public surface names the available operations directly. +- Consumers no longer pass a behavior-switching `action`. +- The router reads like a small object of obvious capabilities. + +## Robust Variant: Explicit Failure Contract + +### Before + +```ts +export async function fetchProject(api: ApiClient, projectId: string) { + const response = await api.get(`/projects/${projectId}`); + + if (!response.ok) { + throw new Error("Unable to fetch project"); + } + + return response.json(); +} +``` + +### After + +```ts +export type GetProjectResult = + | { ok: true; project: Project } + | { ok: false; error: "not_found" | "forbidden" | "network_error" }; + +/** + * Return one visible project. + * + * Expected failures are returned as tagged values. + * Unexpected failures may still throw. + */ +export async function getProject( + api: ApiClient, + input: { projectId: string }, +): Promise { + const response = await api.get(`/projects/${input.projectId}`); + + if (response.status === 404) { + return { ok: false, error: "not_found" }; + } + + if (response.status === 403) { + return { ok: false, error: "forbidden" }; + } + + if (!response.ok) { + return { ok: false, error: "network_error" }; + } + + return { + ok: true, + project: await response.json(), + }; +} +``` + +Why this is better: + +- The function name describes the job directly. +- The input shape is explicit and stable. +- Expected failures are visible to the caller and documented in one short comment. + +## Anti-Pattern And Correction: Split Generic Workflows By Meaning + +### Before + +```ts +type SaveThingInput = { + entityId?: string; + payload: Record; + shouldValidate?: boolean; + sendNotifications?: boolean; + mode?: "create" | "update"; +}; + +export async function saveThing(input: SaveThingInput): Promise { + if (input.shouldValidate) { + await validate(input.payload); + } + + if (input.mode === "update") { + return updateEntity(input.entityId!, input.payload, input.sendNotifications === true); + } + + return createEntity(input.payload, input.sendNotifications === true); +} +``` + +### After + +```ts +export type CreateEntityInput = { + payload: Record; + notify: boolean; +}; + +export async function createEntityRecord(input: CreateEntityInput): Promise { + await validate(input.payload); + return createEntity(input.payload, input.notify); +} + +export type UpdateEntityInput = { + entityId: string; + payload: Record; + notify: boolean; +}; + +export async function updateEntityRecord(input: UpdateEntityInput): Promise { + await validate(input.payload); + return updateEntity(input.entityId, input.payload, input.notify); +} +``` + +Why this is better: + +- Create and update are different jobs, so they get different names. +- Validation is no longer hidden behind a flag. +- Callers do not need to infer required fields from `mode`. + +## Python Example: Contract-Focused Docstring + +### Before + +```python +def fetch_user(client, user_id, include_teams=False): + """Fetch a user.""" + response = client.get(f"/users/{user_id}", include_teams=include_teams) + response.raise_for_status() + return response.json() +``` + +### After + +```python +def fetch_user(client: APIClient, user_id: str, *, include_teams: bool = False) -> User: + """Return one user by ID. + + Raises: + UserNotFound: If the user does not exist. + APIError: If the request fails for any other reason. + """ + response = client.get(f"/users/{user_id}", include_teams=include_teams) + response.raise_for_status() + return response.json() +``` + +Why this is better: + +- The signature makes optional behavior explicit. +- The first line says exactly what the caller gets. +- The docstring documents expected failures instead of repeating the body. + +## Framework Boundary Example: Keep Naming, Remove Directive Magic + +### Before + +```tsx +'use client'; + +import { updateProjectAction } from './actions'; + +export function ProjectSettingsForm({ projectId }: { projectId: string }) { + async function save(formData: FormData) { + await updateProjectAction(formData); + } + + return ( +
+ + + +
+ ); +} +``` + +```ts +'use server'; + +export async function updateProjectAction(formData: FormData) { + const projectId = String(formData.get('projectId')); + const name = String(formData.get('name')); + return updateProjectInDatabase(projectId, name); +} +``` + +### After + +```ts +export type UpdateProjectInput = { + projectId: string; + name: string; +}; + +export async function updateProject(input: UpdateProjectInput): Promise { + return updateProjectInDatabase(input.projectId, input.name); +} +``` + +```ts +'use server'; + +import { updateProject } from './project-service'; + +export async function submitProjectSettings(formData: FormData): Promise { + await updateProject({ + projectId: String(formData.get('projectId')), + name: String(formData.get('name')), + }); +} +``` + +```tsx +import { submitProjectSettings } from './actions'; + +export function ProjectSettingsForm({ projectId }: { projectId: string }) { + return ( +
+ + + +
+ ); +} +``` + +Why this is better: + +- The domain operation is a plain function with an explicit input type. +- The framework-specific action wrapper stays at the edge instead of defining the core interface. +- The file naming and structure can stay conventional without making the business behavior depend on extra client-side directive magic. diff --git a/plugins/sentry-skills/skills/idiomatic-code/references/trigger-sets.md b/plugins/sentry-skills/skills/idiomatic-code/references/trigger-sets.md new file mode 100644 index 0000000..7c2f32d --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/references/trigger-sets.md @@ -0,0 +1,35 @@ +# Trigger Sets + +Use this reference to keep `idiomatic-code` discoverable without letting it eat generic cleanup prompts that should go to `code-simplifier`. + +## Should Trigger + +- "Make this API easier to consume." +- "Improve the naming on this interface." +- "Rewrite these docstrings so the contract is obvious." +- "This abstraction has too much magic." +- "There is too much DI / too many factories here." +- "Server vs client is making this hard to reason about." +- "This framework behavior feels too implicit." +- "Turn this clever public abstraction into plain functions." +- "This server action interface is confusing." +- "Help me make this library surface more explicit." + +## Should Not Trigger + +- "Clean up this code." +- "Reduce duplication in this module." +- "Refactor this for readability." +- "Fix the failing tests." +- "Find bugs in this PR." +- "Security review this diff." +- "Make this faster." +- "Format this file." +- "Fix the type errors." +- "Write tests for this." + +## Description Edits + +- Tightened the description around public interfaces, naming, and contract docs. +- Added trigger phrases for magic, implicit behavior, DI/factory machinery, and server/client confusion. +- Removed broad wording that drifted toward generic cleanup territory already covered by `code-simplifier`. diff --git a/plugins/sentry-skills/skills/idiomatic-code/references/troubleshooting-workarounds.md b/plugins/sentry-skills/skills/idiomatic-code/references/troubleshooting-workarounds.md new file mode 100644 index 0000000..682e491 --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/references/troubleshooting-workarounds.md @@ -0,0 +1,60 @@ +# Troubleshooting And Workarounds + +Use this reference when the code still feels confusing after a first simplification pass. + +1. Issue: The API uses one generic verb like `execute`, `process`, or `handle`. + Fix: Split by domain action and rename each entry point for the caller's intent. + +2. Issue: One function uses `mode` or boolean flags to switch behavior. + Fix: Create separate functions or procedures for each meaningfully different job. + +3. Issue: Required and optional inputs are mixed into one vague options bag. + Fix: Make required inputs obvious and group only true optional behavior in named options. + +4. Issue: The caller cannot tell whether the function throws, returns `null`, or returns an error object. + Fix: Pick one visible failure contract and document it in code. + +5. Issue: A class exists only to wrap one public method. + Fix: Replace it with a named function or a small object of related functions unless state is part of the contract. + +6. Issue: The code sample needs a long explanation before the API makes sense. + Fix: Rename the surface and shrink the concept count until the sample is readable without prose. + +7. Issue: Comments or docstrings only translate the next line into English. + Fix: Rewrite them to explain return value, side effects, invariants, and expected failures. + +8. Issue: Framework vocabulary has displaced domain vocabulary. + Fix: Keep framework constructs local, but make exported names describe the business action. + +9. Issue: Schema, routing, and business logic are all tangled in one long chain. + Fix: Keep the public route or function surface small and move non-contract detail down a layer. + +10. Issue: The API is technically explicit but still feels noisy. + Fix: Remove knobs that do not matter to the common case and keep advanced configuration off the main path. + +11. Issue: Factories, providers, or tokens dominate the design before the business API is obvious. + Fix: Treat this as a machinery-heavy smell. Hide the wiring and expose a plain domain interface first. + +12. Issue: The code reads like a container configuration system rather than a module with one clear job. + Fix: Collapse DI vocabulary behind direct functions or a small object of named operations. + +13. Issue: A small feature now needs guards, actors, machine setup, or other orchestration concepts just to explain itself. + Fix: Treat this as a concept-heavy smell. Use a full state-machine surface only when the workflow is genuinely state-machine-shaped. + +14. Issue: The interface requires theory-heavy vocabulary before the reader can tell what it does. + Fix: Treat this as an abstraction-first smell. Keep algebraic or effectful helpers internal and expose domain language first. + +15. Issue: A small module now assumes a whole programming style or runtime model. + Fix: Treat this as a style-shift smell. Keep the public contract simple even if the internals use richer composition. + +16. Issue: Behavior changes depending on file directives, file placement, or whether the code runs on the server or client. + Fix: Treat this as a hidden-boundary smell. Move the mode switch to the edge and expose a plain local contract underneath. + +17. Issue: A simple mutation now depends on framework actions, serialization rules, or form wiring that is not obvious at the call site. + Fix: Define the plain server-side operation first, then wrap it with the framework-specific action interface. + +18. Issue: Routing behavior now depends on special folders, slots, route groups, or intercepting conventions that are hard to predict without framework expertise. + Fix: Treat this as a convention-driven smell. Keep the simple naming conventions, but collapse advanced routing behavior unless it clearly earns its complexity. + +19. Issue: A naming convention is helping structure, but top-of-file directives or framework mode switches are carrying too much semantic weight. + Fix: Keep the naming convention and move the semantic boundary into explicit module or function interfaces instead of directive magic. diff --git a/plugins/sentry-skills/skills/idiomatic-code/references/typescript-exemplars.md b/plugins/sentry-skills/skills/idiomatic-code/references/typescript-exemplars.md new file mode 100644 index 0000000..8b99e9a --- /dev/null +++ b/plugins/sentry-skills/skills/idiomatic-code/references/typescript-exemplars.md @@ -0,0 +1,142 @@ +# TypeScript Exemplars + +Use these examples to calibrate what good and bad interface design look like. +Do not treat them as templates to copy verbatim. + +## Primary Anchor: oRPC + +Why it matters: + +- The public surface stays close to plain objects and named procedures. +- Routers read like data, not framework ceremony. +- Procedure handlers have one obvious place where behavior lives. +- Naming stays close to the consumer view of the system. + +What makes oRPC a strong positive example: + +- plain object router shapes +- one predictable handler shape +- names that read cleanly at the call site +- transport and schema details attached close to the procedure they describe + +Why it is still not a template: + +- framework adoption when a plain module API is enough +- inferred complexity that obscures domain names + +## Positive Secondary Examples + +### tRPC + +What makes this a good example: + +- reusable base procedures for shared auth or context +- simple router and procedure families with consistent naming + +What to avoid: + +- stacking builders until a simple procedure becomes hard to scan +- allowing the framework vocabulary to dominate domain vocabulary + +### Hono + +What makes this a good example: + +- tiny core API with obvious verbs +- route definitions that read in straight lines +- heavy reuse of Web Platform concepts instead of custom wrappers + +What to avoid: + +- accumulating too much app wiring in one chain +- mixing routing, validation, and business logic into one unreadable block + +### Remix + +What makes this a good example: + +- route file naming that carries structure directly in the filename +- conventions that are easy to scan because they describe organization more than runtime mode +- colocated route behavior that still reads like normal module boundaries + +What to avoid: + +- treating framework file conventions as the local default where plain module names would be clearer +- turning naming conventions into hidden execution semantics + +### better-auth + +What makes this a good example: + +- explicit configuration of enabled auth capabilities +- a clear split between server-side auth configuration and client-side auth usage +- plugin additions that are visible in config instead of hidden behind framework magic + +What to avoid: + +- treating the whole auth framework surface as the local default when a module only needs a few named operations +- letting plugin or auth-library vocabulary replace the application's own domain terms + +### Valibot + +What makes this a good example: + +- plain object literals and pure factory functions +- modular building blocks with explicit composition +- small API pieces that stay predictable when combined + +What to avoid: + +- exposing internal composition mechanics to consumers +- adding schema helpers that save keystrokes but obscure intent + +### ts-pattern + +What makes this a good example: + +- tiny semantic core +- names that explain the flow by themselves: `match`, `with`, `otherwise`, `exhaustive` + +What to avoid: + +- introducing pattern matching where simple `if` or `switch` is clearer +- chaining so much logic that the branch structure disappears + +### neverthrow + +What makes this a good example: + +- explicit success and failure values +- narrow, composable result vocabulary + +What to avoid: + +- wrapping every trivial function in `Result` +- replacing straightforward exceptions when failure is truly exceptional + +## Contrast Example: ts-rest + +What is useful: + +- contract-first design with plain data shapes +- explicit request and response structure + +Why it is a contrast, not the default bar for this skill: + +- the contract surface is richer and more configuration-heavy than the target simplicity bar +- names and concepts can become less obvious once headers, params, metadata, and response maps all accumulate + +Use ts-rest as evidence that plain-data contracts can scale. +Do not treat its complexity level as the default interface target when a smaller surface would do. + +## TypeScript Heuristics + +If you are rewriting a TypeScript API, prefer this order of choices: + +1. plain named function +2. small object of named functions or procedures +3. a narrow builder only when it materially improves correctness + +For application structure, prefer naming conventions that reveal shape directly over directives or framework context that silently change semantics. + +Reach for classes, fluent chains, or meta-programming only when they pay for themselves in clarity at the call site.