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.