agents/openai.yaml
interface: display_name: "Code Craft" short_description: "For writing, reviewing, or refactoring code" default_prompt: "Use $code-craft while writing or reviewing this code."
jssblck/agents · GitHub
Use when writing, reviewing, or refactoring code.
프로젝트 폴더에서 아래 명령어를 실행하고, 설치할 에이전트를 선택하세요.
npx skills add jssblck/agents --skill code-craft설치 명령을 직접 실행해야 적용됩니다. 지원 에이전트와 필요한 권한·라이선스는 제작자의 안내를 확인하세요.
agents/openai.yamlinterface: display_name: "Code Craft" short_description: "For writing, reviewing, or refactoring code" default_prompt: "Use $code-craft while writing or reviewing this code."
languages/go.md# Go dialect
How the universal core is spelled in Go, plus Go-specific idioms. Go's culture
prizes simplicity and explicitness; lean into that rather than importing patterns
from other languages.
## Tooling baseline
```sh
gofmt -l . # or goimports; formatting is not negotiable in Go
go vet ./...
staticcheck ./... # honnef.co/go/tools; the de facto strong linter
go test ./...
go build ./...
```
`golangci-lint` bundles vet, staticcheck, errcheck, and more; run it in CI. The
`errcheck` linter (no ignored error returns) is the most valuable one.
## Illegal states (core 1, 4)
- **Defined types over primitives** for domain values. Go has no generics-based
branding need; a defined type is already nominal:
```go
type UserID string
type Cents int64
```
`UserID` and a bare `string` are distinct types; the compiler stops the mix.
Keep the underlying type unexported-constructed where an invariant matters
(see below).
- **Enforce invariants with unexported fields + a constructor in the package.**
Go has no private constructors per se, but a struct with unexported fields can
only be built fully within its package, so a `NewEmail(raw string) (Email,
error)` becomes the only door from outside:
```go
type Email struct{ value string } // unexported field
func NewEmail(raw string) (Email, error) { /* validate */ return Email{raw}, nil }
func (e Email) String() string { return e.value }
```
- **States:** Go has no sum types. Model a closed set of states with either a
small `iota` enum plus a `String()` method, or a sealed interface (an
interface with an unexported method so only this package can implement it) with
one struct per variant. Use the sealed-interface form when each state carries
different data.
```go
type State interface{ isState() }
type Loading struct{}
type Ready struct{ Data []byte }
func (Loading) isState() {} ; func (Ready) isState() {}
```
- Use the zero value deliberately: design types so their zero value is a valid,
useful default (`bytes.Buffer`, `sync.Mutex`). If the zero value is invalid,
force construction through a `New*` function.
## Parse, don't validate (core 2)
- Decode external data into a struct and validate in the same step, returning the
parsed value:
```go
func ParseConfig(b []byte) (Config, error) {
var raw rawConfig
if err := json.Unmarshal(b, &raw); err != nil { return Config{}, err }
return raw.intoChecked() // returns Config or error
}
```
- Return the parsed `Config`, not a `validate(c) error` you call separately while
still passing the raw struct around.
- Accept interfaces, return structs (below) is the Go form of "accept the general
input."
## Errors (core 3)
- **Errors are values, returned explicitly.** Check every one. The
`if err != nil { return ..., err }` boilerplate is the language working as
intended; do not hide it.
- **Wrap with context using `%w`** so the chain is inspectable:
```go
if err != nil {
return fmt.Errorf("loading profile %q: %w", name, err)
}
```
`errors.Is` for sentinel comparison, `errors.As` to extract a typed error.
- **Sentinel and typed errors:** `var ErrNotFound = errors.New("not found")` for
conditions callers branch on; a custom error type implementing `error` when the
error carries data. Document which errors a function can return.
- **Never drop an error.** `_ = doThing()` must be a deliberate, commented
decision; `errcheck` flags the accidental ones. Empty error handling is a bug.
- **Fail closed** in gates: an error from the check returns the deny path, never
a default allow.
- Messages: lowercase, no trailing punctuation, no capitalization (they get
wrapped: `fmt.Errorf("reading %s: %w", ...)`).
- `panic` only for truly unrecoverable programmer errors and package-init
failures; never for ordinary control flow. `recover` only at well-defined
boundaries (a server handler that must not crash the process).
## Interfaces and abstraction (core 8)
- **Accept interfaces, return structs.** Functions take the narrow interface they
need; they return concrete types so callers keep full access.
- **Define interfaces at the consumer, keep them small.** The classic Go
interface is one or two methods (`io.Reader`, `io.Writer`). Do not define a big
interface next to its implementation "for testing"; define the small interface
where it is used.
- **Do not reach for `interface{}`/`any`** to be generic. Since 1.18, use
generics (`[T any]`) for genuinely type-parametric code, and concrete types
otherwise. `any` is a smell outside true dynamic boundaries (JSON, reflection).
- No premature interfaces: a struct with one implementation needs no interface
until a second implementation or a real test seam exists.
## Concurrency (Go-specific)
- **Share memory by communicating:** prefer channels for handoff, `sync.Mutex`
for protecting a small piece of shared state. Do not over-rotate to channels
where a mutex is simpler.
- **Pass `context.Context` as the first parameter** to anything that does IO,
blocks, or spawns work; honor cancellation and deadlines. Never store a
`Context` in a struct.
- **Every goroutine needs a known lifetime and exit.** A goroutine that nothing
waits on and nothing can stop is a leak. Use `sync.WaitGroup`,
`errgroup.Group` (parallel work with error propagation and cancellation), or a
done channel.
- Guard against data races; run `go test -race` in CI. `golang.org/x/sync` gives
`errgroup` and `semaphore` for bounded parallelism.
## Naming and style
`MixedCaps`/`mixedCaps`, never underscores. Exported = capitalized; keep the
exported surface small. Short names for short scopes (`i`, `r`, `buf`). No
`Get` prefix on getters (`user.Name()`, not `user.GetName()`). Interface names
often `-er` (`Reader`). Package names short, lowercase, no plurals, no
`util`/`common` grab-bags. Error variables `ErrFoo`. Avoid stutter
(`http.Server`, not `http.HTTPServer`).
## Project structure
Flat is good; resist deep nesting. Package by capability, not by layer. `cmd/`
for binaries, `internal/` for code you do not want importable outside the module.
Do not create `models/`, `controllers/`, `services/` layers by reflex; group by
domain. One package = one cohesive concern.
## Testing (core 6)
See the testing-craft skill:
[`testing-craft/languages/go.md`](../../testing-craft/languages/go.md).
## Anti-patterns to refuse
Ignored error returns; `panic` for control flow; naked returns in long
functions; `interface{}`/`any` where a concrete type or generic fits; large
interfaces defined next to their single implementation; storing `Context` in
structs; goroutines with no exit; getter `Get` prefixes; `util`/`common`
packages; deep layered package trees; mutating a shared map without a lock.
languages/python.md# Python dialect
How the universal core is spelled in Python, plus Python-specific idioms.
Python's dynamism makes the discipline opt-in: type hints, a strict type checker,
and boundary parsing are what buy you the safety other languages give by default.
## Tooling baseline
```sh
ruff check . # lint (replaces flake8/isort/pyupgrade and more)
ruff format . # format (black-compatible)
mypy --strict . # or: pyright / basedpyright
pytest
```
`ruff` is the locked default: one fast tool for both format and lint (it replaces
black, isort, flake8, and pyupgrade). `pyproject.toml` is the single config home.
Run a type checker in CI in strict mode: untyped Python silently rots. Pin Python
version and dependencies (uv, poetry, or pip-tools) when setting up a repository.
Follow existing tooling during ordinary edits. For requested repository setup,
see [project-bootstrap](../../project-bootstrap/SKILL.md).
```toml
[tool.mypy]
strict = true
warn_unreachable = true
```
## Type hints everywhere (foundation)
Type hints are not decoration; they are the type system you are choosing to turn
on. Annotate every function signature and every public attribute. Without them
mypy/pyright have nothing to check and the rest of this file does not apply.
- Modern syntax: `list[str]`, `dict[str, int]`, `X | None` (not `Optional[X]`),
`X | Y` unions. `from __future__ import annotations` or 3.10+.
- `typing.Final` for constants, `Literal["a", "b"]` for closed string sets,
`Self` for fluent returns.
## Illegal states (core 1, 4)
- **Dataclasses / frozen dataclasses for structured data,** not bare dicts or
tuples passed around:
```python
@dataclass(frozen=True, slots=True)
class User:
id: UserId
email: Email
```
`frozen=True` for immutability, `slots=True` for memory and typo-safety.
- **`NewType` for cheap nominal IDs,** a class with a validating constructor when
there is an invariant:
```python
UserId = NewType("UserId", int) # zero-cost label
@dataclass(frozen=True)
class Email:
value: str
def __post_init__(self) -> None:
if "@" not in self.value: raise ValueError("invalid email")
```
- **States: `enum.Enum` for closed sets;** a union of dataclasses plus
`match`/`case` for states that carry different data:
```python
match state:
case Loading(): ...
case Ready(data=d): ...
case Error(message=m): ...
```
Make the checker prove exhaustiveness: annotate the union and let mypy flag a
missing case (an `assert_never(state)` in a fallthrough forces it).
- Avoid passing `dict[str, Any]` as a pseudo-object through the codebase. That is
the Python form of stringly-typed data. Parse it into a dataclass/model at the
edge.
## Parse, don't validate (core 2)
- **Use pydantic (v2) or attrs+cattrs to parse external data into typed models at
the boundary:**
```python
class Config(BaseModel):
port: int = Field(gt=0)
host: str
config = Config.model_validate(raw_json) # raises on bad shape; config is typed
```
The model is the parser and the type in one. Do not write
`def is_valid(d: dict) -> bool` and keep handing the dict around.
- Boundaries that must be parsed: `json.loads` output, `os.environ` values (all
`str`), request bodies, config files, CSV rows, subprocess output. Each returns
untyped data; convert it once.
- Push the model down: inner functions take `Config`, not `dict`.
## Errors (core 3)
- **Exceptions are Python's value channel.** Raise specific exception types, not
bare `Exception`. Define a small exception hierarchy for your domain so callers
can catch precisely:
```python
class AppError(Exception): ...
class ConfigError(AppError): ...
```
- **Chain with `raise ... from err`** so the traceback shows the cause:
```python
raise ConfigError(f"loading profile {name!r}") from err
```
- **Catch narrowly.** `except SpecificError:`, never a bare `except:` or
`except Exception:` that hides bugs. Re-raise what you cannot handle.
- **Never swallow:** no `except SomeError: pass` without a logged, commented
reason. The silent pass is a data-corruption bug.
- **Fail closed** in gates: an exception or timeout in a check returns deny, not a
default allow.
- EAFP over LBYL where it reads cleanly (try the operation and catch, rather than
pre-checking), but not as an excuse to catch broadly.
- Use `contextlib` (`with`, `contextmanager`) for cleanup; do not hand-roll
try/finally where a context manager exists.
## Async (Python-specific)
- `asyncio` with `async`/`await`. `asyncio.gather(*aws)` for parallel,
`asyncio.TaskGroup` (3.11+) for structured concurrency with proper cancellation
and error aggregation (prefer it over bare `gather` on new code).
- Do not block the event loop: no synchronous IO or CPU-bound work in a coroutine;
use `asyncio.to_thread` / `run_in_executor`. Do not mix `time.sleep` into async
code (`await asyncio.sleep`).
- `asyncio.timeout()` for deadlines, cancellation via task cancellation.
## Naming and style (PEP 8)
`snake_case` functions/variables/modules, `PascalCase` classes,
`UPPER_SNAKE` constants. Booleans `is_`/`has_`/`can_`. Leading underscore for
non-public (`_internal`); module `__all__` to declare the public surface. No
single-char names except short loop indices. Avoid shadowing builtins (`list`,
`id`, `type`, `dict`).
## Project structure
A real package (`src/mypackage/` layout), not loose scripts. `__init__.py`
curates the public API. Group by feature/domain, not by technical layer. Keep IO
at the edges (a thin CLI/HTTP shell) and the core as pure, typed functions that
are trivial to test. `pyproject.toml` for all config.
## Testing (core 6)
See the testing-craft skill:
[`testing-craft/languages/python.md`](../../testing-craft/languages/python.md).
## Anti-patterns to refuse
Missing type hints; `Any` to silence the checker; `dict[str, Any]` passed around
as a pseudo-object; bare `except:`/`except Exception: pass`; `raise` without
`from` when chaining; mutable default arguments (`def f(x=[])`); `*` imports;
shadowing builtins; blocking the event loop; patching your own internals in
tests; `assert` for runtime validation in production paths (it is stripped under
`-O`); business logic at module import time.
languages/rust.md# Rust dialect
How the universal core is spelled in Rust, plus Rust-specific idioms. Distilled
from the Rust API Guidelines, the Performance Book, and production crates
(ripgrep, tokio, serde, axum).
## Tooling baseline
```sh
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
```
Lints worth setting at the crate/workspace root:
```rust
#![warn(clippy::all, clippy::pedantic)] // pedantic selectively; allow the noisy ones
#![deny(clippy::correctness)]
#![warn(missing_docs)] // for libraries
```
Deny `clippy::inline_always` and `clippy::unnecessary_wraps` (the latter catches
functions that claim fallibility they do not have). Configure lints in
`Cargo.toml` `[lints]` for a workspace.
## Illegal states (core 1, 4)
- **Newtypes with private fields + smart constructors.** Not
`pub struct UserId(pub String)` (a public field is a trapdoor). Use a private
field and a constructor that parses:
```rust
pub struct Email(String);
impl Email {
pub fn parse(raw: &str) -> Result<Self, EmailError> { /* check, then wrap */ }
pub fn as_str(&self) -> &str { &self.0 }
}
```
- **Enums for states.** Model mutually exclusive states as an `enum` with data on
the variant. Use `#[non_exhaustive]` on public enums/structs you may extend.
- **Typestate** for compile-time state machines (a builder that only exposes
`.build()` once required fields are set), `PhantomData` for type-level markers.
- **Do not derive your way around an invariant.** Be careful with `From`,
`Deref`/`DerefMut`, and `#[serde(...)]` on checked types; deserialization can
reconstruct an invalid value unless it goes through the parser (use
`#[serde(try_from = "Raw")]`).
- Wrap IDs and units: `OrderId(u64)`, `Cents(i64)`. `#[repr(transparent)]` for
FFI-safe newtypes.
## Parse, don't validate (core 2)
- Raw serde structs at the boundary, a checked domain type inside, a parser
between. Name the checked form: `CheckedConfig`, `VerifiedPlan`.
- Return the parsed value, not `Result<(), E>`. `clippy::unnecessary_wraps`
helps; treat `parse* -> Result<()>` and `validate* -> Result<()>` as smells.
- Accept the most general input: `&str` not `&String`, `&[T]` not `&Vec<T>`,
`impl AsRef<Path>` for paths, `impl Into<String>` where you will own a string.
## Errors (core 3)
- **Libraries:** typed errors with `thiserror`, one enum of failure modes,
`#[from]` for conversions, `#[source]` to chain. Document them with a
`# Errors` section.
- **Applications / top level:** `anyhow` (or `color_eyre`), `.context(...)` /
`.with_context(...)` as the error propagates, `?` for propagation.
- **Bugs only:** `.expect("invariant: ...")` for things that cannot happen;
never `.unwrap()` in production paths. `panic!`/`unreachable!` for genuine
invariant violations, with a message.
- **Fail closed:** a gate that errors or times out returns the block verdict, not
a default pass.
- Error messages: lowercase, no trailing period.
## Ownership and copies (core 5)
- Borrow over clone; clone only when you need owned data (storage, `'static` for
a spawned task) and make it explicit.
- `Cow<'a, T>` for conditional ownership (borrow the common case, own only when
you must mutate). `Arc<T>` for shared ownership across threads, `Rc<T>`
single-threaded.
- Interior mutability: `Mutex`/`RwLock` (multi-thread), `RefCell` (single).
`RwLock` when reads dominate.
- `with_capacity` when the size is known; reuse buffers with `clear()` in loops;
`write!` into a buffer instead of `format!` in hot paths. `SmallVec`/`ArrayVec`
for usually-small collections. Box large enum variants so the enum is not sized
to its biggest case.
- Prefer iterators over manual indexing (avoids bounds checks, clearer); keep
them lazy, `collect()` once at the end.
## Async (Rust-specific)
- Tokio for production. **Never hold a `Mutex`/`RwLock` guard across `.await`**
(clippy `await_holding_lock`): clone the needed data and drop the guard first,
or use `tokio::sync` primitives deliberately.
- `tokio::join!` for parallel awaits, `try_join!` when fallible, `select!` for
racing/timeouts, `JoinSet` for dynamic task groups. `spawn_blocking` for CPU
work or sync IO. `tokio::fs` not `std::fs` in async code.
- Channels: bounded `mpsc` for backpressure, `oneshot` for request/response,
`watch` for latest-value, `broadcast` for pub/sub. `CancellationToken` for
shutdown.
## Naming
`UpperCamelCase` types/traits/variants, `snake_case` fns/methods/modules,
`SCREAMING_SNAKE_CASE` consts. Conversions: `as_` (cheap borrow), `to_`
(expensive), `into_` (consumes). No `get_` prefix on simple getters. `is_`/`has_`
for booleans. Acronyms as words (`Uuid`, not `UUID`). Crates: no `-rs` suffix.
## Project structure
Keep `main.rs` thin, logic in `lib.rs`. Modules by feature, not by type. Flat
while small. `pub(crate)`/`pub(super)` for internal visibility, `pub use` to
curate the public surface. Workspaces for large multi-crate projects with shared
`[workspace.dependencies]`.
## Testing (core 6)
See the testing-craft skill:
[`testing-craft/languages/rust.md`](../../testing-craft/languages/rust.md).
## Docs
`///` on public items, `//!` for module docs. `# Examples` (runnable),
`# Errors`, `# Panics`, `# Safety` (for `unsafe`) sections. Intra-doc links
(`[Vec]`). Document every `unsafe` block with a `// SAFETY:` comment
(`clippy::undocumented_unsafe_blocks`).
## Anti-patterns to refuse
`.unwrap()`/`.expect()` on recoverable errors; cloning where a borrow works;
holding a lock across `.await`; `&String`/`&Vec<T>` in signatures; indexing where
an iterator reads cleaner; `panic!` on expected errors; empty `if let Err(_) =`;
`Box<dyn Trait>` where `impl Trait` works; stringly-typed data; `format!` in hot
paths; over-generic abstractions with one caller.
## Release profile (for performance-sensitive binaries)
```toml
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
strip = true
```
languages/typescript.md# TypeScript / JavaScript dialect
How the universal core is spelled in TypeScript, plus TS/JS-specific idioms. The
overriding rule: let the type system do work, and keep `any` out.
## Tooling
Follow the project's formatter, linter, and TypeScript configuration. Do not
replace tooling or install additional lint plugins during ordinary coding.
When repository setup is requested, Jess's defaults are `oxfmt`, `oxlint`,
and `tsc --noEmit`; see [project-bootstrap](../../project-bootstrap/SKILL.md).
Enable restrictions only where their scope fits the project.
Keep `any` and unchecked assertions out of application logic. Boundary parsers
may accept `unknown`, and adapters may return it until the caller parses it.
Use schema libraries already in the project, or focused type guards when a
dependency would add more complexity than the parser.
## Illegal states (core 1, 4)
- **Discriminated unions for state.** This is the single most valuable TS
pattern. Replace boolean/optional soup with a tagged union and `switch` on the
tag:
```ts
type State<T> =
| { kind: "loading" }
| { kind: "error"; message: string }
| { kind: "ready"; data: T };
```
The `ready` branch is the only place `data` exists, so you cannot read it while
loading. Use a `never`-returning `assertNever(x)` in the `default` case to get
exhaustiveness checking: adding a variant becomes a compile error everywhere
it is unhandled.
- **Branded (nominal) types** for newtypes, since TS is structural:
```ts
type UserId = string & { readonly __brand: "UserId" };
const UserId = (raw: string): UserId => {
if (raw.length === 0) throw new Error("empty UserId");
// SAFETY: the check above is the whole UserId invariant.
return raw as UserId;
};
```
Now a bare `string` will not pass where `UserId` is required. Brand IDs, units,
and validated values.
- `unknown`, never `any`. `any` disables the type checker locally and infectiously.
Parse `unknown` at the boundary into a named type; boundary function signatures
may accept it. Narrow with a schema or a type guard before use. If an assertion
is necessary, explain the invariant the checker cannot express.
- `readonly` and `as const` for immutability; `satisfies` to check a literal
against a type without widening it.
- Prefer unions of string literals over `enum` (enums have surprising runtime
and nominal behavior); reach for `enum` only when you need its specific
features.
## Parse, don't validate (core 2)
- **Parse external data at the boundary.** Use the project's schema library
or a focused type guard. With a schema, derive the static type from it:
```ts
const Config = z.object({ port: z.number().int().positive(), host: z.string() });
type Config = z.infer<typeof Config>;
const config = Config.parse(rawJson); // throws on bad shape; config is typed
```
Do not hand-write `isValidConfig(x): boolean` and keep passing the raw object.
Parse once, pass `Config` inward.
- `JSON.parse` returns `any`, network payloads need runtime checks, and
`process.env` values are `string | undefined`. Parse them before application use.
- `z.infer` so the static type and the runtime check cannot drift.
## Errors (core 3)
- **Throw for exceptional, return for expected.** Two viable styles; be
consistent within a module:
- Idiomatic TS: `throw` a typed `Error` subclass, `catch` at a known seam.
Always extend `Error` (never `throw "string"`), set `cause` to chain:
`throw new ConfigError("loading profile", { cause: err })`.
- Result style: return a `{ ok: true; value } | { ok: false; error }` union (or
neverthrow's `Result`) when you want the error in the signature and
exhaustive handling. Good for expected, branchy failure.
- **Never swallow:** no empty `catch {}`, no unhandled promise. A floating
promise drops its rejection; `await` it or `.catch` it explicitly. The
type-aware `no-floating-promises` and `no-misused-promises` rules gate this.
- **Fail closed** in gates: a guard that throws or times out denies.
- Async errors: `async`/`await` with `try/catch`, not raw `.then` chains. Use
`Promise.all` for parallel, `Promise.allSettled` when you need every result
regardless of individual failures.
## Naming and style
`camelCase` values/functions, `PascalCase` types/classes/components,
`UPPER_SNAKE` consts. Booleans `is`/`has`/`can`. No Hungarian, no `I` prefix on
interfaces. Files: match the project (kebab-case is common). Prefer named exports
over default exports (better refactor/autocomplete).
## Async (TS-specific)
- `async`/`await` throughout; never mix with bare callbacks.
- `Promise.all([...])` for independent parallel work, not sequential awaits in a
loop when the iterations are independent. `Promise.allSettled` to collect all
outcomes. `AbortController` / `AbortSignal` for cancellation and timeouts.
- Beware the sequential-await-in-a-loop performance trap; batch with
`Promise.all` when order-independent.
## Functional and immutability
Prefer `map`/`filter`/`reduce` and immutable updates over in-place mutation where
it reads clearly. `const` by default. Do not mutate function arguments. Keep
side effects at the edges so the core is testable.
## Project structure
Organize by feature/domain, not by technical layer (`user/` not
`controllers/ models/ views/` split across the app). Barrel files (`index.ts`)
sparingly: they help the public surface but can create import cycles and slow
tooling. Keep the public API of a module explicit.
## Testing (core 6)
See the testing-craft skill:
[`testing-craft/languages/typescript.md`](../../testing-craft/languages/typescript.md).
## React: effect discipline
`useEffect` synchronizes a component with a system React does not own. It is not
a data-flow tool. Effect chains (an effect sets state, which triggers another
effect) turn a component from a readable tree into a timeline that a reader,
human or agent, must simulate step by step. Default to zero effects; see
[You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect).
- Prefer these alternatives to effects:
- Derived state: compute it during render (`useMemo` if expensive).
- Resetting state when a prop changes: pass a `key` instead.
- Reacting to a user event: put the logic in the event handler.
- Data fetching: use the project's data-fetching layer (TanStack Query, SWR,
or the framework loader), which handles races, caching, and cancellation.
- **Allowed:** synchronizing with an external system: DOM APIs, subscriptions,
timers, third-party widgets, analytics. Clean up resources or subscriptions
when needed, and list accurate dependencies. For external stores, prefer
`useSyncExternalStore` over a hand-rolled subscribe effect.
- Extract a wrapper hook when it provides reuse or a clearer lifecycle boundary.
Do not create a wrapper directory solely to prohibit effect imports. Keep
dependencies accurate and do not suppress hook lint rules to hide stale state.
## Anti-patterns to refuse
`any` (use `unknown` + narrowing); non-null `!` to silence the checker instead of
handling the null; `as` casts that lie about runtime shape; `enum` by reflex;
floating promises; empty `catch`; `JSON.parse` result used untyped; boolean-flag
soup instead of a discriminated union; default exports everywhere; mocking your
own modules without a contract-level reason; `==` (use `===`). Follow the
project's enforced rules rather than installing new gates during a code change.
principles/architecture-docs.md# Architecture docs are a stable map > Keep a short, durable description of boundaries and invariants. Update the map > when code moves; keep churny detail in the code. From Matklad's `ARCHITECTURE.md` guidance. A good architecture doc is the thing a new contributor (human or agent) reads to know where code lives and why, without reading all of it. ## What goes in - **A plain-language overview.** One or two paragraphs: what problem this solves and the shape of the solution. Written for someone who has never seen the repo. - **Coarse boundaries.** The handful of major modules/layers and what each owns. Name the important files, types, and traits/interfaces, so the reader can jump. A codemap, not a tutorial. - **Invariants and cross-cutting concerns.** The rules that hold across the system: "all external input is parsed in `boundary/` before reaching `core/`", "the renderer never touches the network", "gates fail closed." - **Deliberate absences.** What is intentionally *not* there or *not* allowed: "no generic plugin scheduler, watch is hand-rolled", "Win32 calls stay out of the logic layer." Absences are as load-bearing as presences and far less discoverable from the code. ## What stays out - Implementation detail that churns: function-level behavior, exact signatures, step-by-step algorithms. Those live in code comments and module docs, where they sit next to the thing they describe and get updated with it. - Fragile links to specific line numbers or volatile paths. Name modules and types; avoid pinning to coordinates that rot. - History. The doc describes the current shape, not how it got there. ## Maintenance rules - **When code moves, update the map, do not append a migration note.** The doc describes now, not the journey. "X used to be in Y, now in Z" is noise; just say X is in Z. - **State invariants where the boundary is described**, especially when the invariant is enforced by construction (a type, a parse step, a config check). The reader should learn both the rule and where it is guaranteed. - **Split before it sprawls.** When one section grows too detailed, move it to a focused doc and leave a pointer-level summary in the main map. - **The map and the code must agree.** A review that changes module boundaries changes the map in the same change. A stale architecture doc is worse than none, because it is believed. ## Why it pays for agents specifically A coding agent re-reads the map every session. A tight, accurate codemap is the single highest-leverage doc for getting an agent to the right file fast and keeping it inside the intended boundaries. The `AGENTS.md` architecture section and the architecture doc serve the same role; keep them consistent.
principles/errors-as-values.md# Errors are values; gates fail closed
> Handle expected failure through the value channel, with context, and never
> swallow it. A check that cannot answer is a block, not a quiet pass.
## Expected vs unexpected failure
Draw the line clearly:
- **Expected/recoverable** (file missing, bad input, network hiccup, conflict):
flows through the value channel. Rust `Result`, Go `error` return, TS a
returned union or a thrown typed error caught at a known seam, Python a
specific exception. The caller can see it in the signature or contract and
decide what to do.
- **Bugs/invariants violated** (index out of bounds on data you just built, a
"this cannot happen" branch): may panic/throw/abort. These are programmer
errors, not conditions to recover from. Use the language's assert/expect/panic
with a message that says what invariant broke.
Do not blur them. Crashing on a missing config file is wrong; returning a
recoverable error from a corrupted-internal-state branch is also wrong.
## Add context as it propagates
A bare "file not found" three layers down is useless. Wrap the error with what
you were trying to do as it bubbles up, so the final message reads as a chain:
```
loading profile "alice": reading /etc/app/alice.toml: no such file
```
Every language has the idiom: Rust `.context(...)` / `?` with a context layer,
Go `fmt.Errorf("...: %w", err)`, TS error `cause`, Python `raise ... from err`.
Add a frame of context at each meaningful layer; do not re-wrap mechanically at
every function.
## Never swallow
The empty catch is a silent data-corruption machine:
```
# every one of these is a bug
try: do() except: pass
if let Err(_) = do() {} # ignored
_ = do() # Go: error dropped
do().catch(() => {}) # JS: rejection eaten
```
If you genuinely intend to ignore an error, that is a decision that must be
visible: log it at the right level, comment why it is safe to drop, or convert
it to a default through an explicit path. "Ignored silently" and "handled by
ignoring, on purpose, here is why" must not look the same in the code.
## Fail closed at gates
A gate is any check whose output controls whether something proceeds: an auth
check, a merge gate, a validation step, a feature flag guard, a security filter.
**If a gate cannot produce a valid verdict, the answer is no.** A gate that
errors, times out, or gets malformed data must block, never default to allow. An
advisor (something whose output is informational, not blocking) may fail open,
but say which one you are building. The dangerous bug is the gate that silently
becomes a pass when its backend is down.
```
verdict = run_check(change) # may error / time out
if verdict is None or verdict.errored:
return BLOCK # fail closed
return verdict.decision
```
## Typed errors at library boundaries
For a library or a module others depend on, give callers a typed error they can
match on (an enum/union of failure modes), not an opaque string or a catch-all.
Reserve opaque/aggregated error types (Rust `anyhow`, a bare `Exception`, a
plain `error`) for application top-levels where the caller just reports and
exits. The deeper and more reused the code, the more its errors should be
inspectable.
## Messages
Lowercase, no trailing period, no "Error:" prefix (the framework adds context).
Describe the condition, not the reaction: "connection refused", not "failed to
connect, exiting." Let the caller decide the reaction.
See the per-language files for the concrete error type, wrapping operator, and
the fail-closed pattern in each.
principles/illegal-states.md# Make illegal states unrepresentable > Encode invariants in types, not in names, comments, or conventions. This folds together three ideas that are really one: "make illegal states unrepresentable," "names are not type safety," and "newtypes over primitives." The common thread: a caller should not be able to construct or pass a wrong value and still type-check. ## The test Ask of every wrapper or type you introduce: **what illegal operation or invalid state does this prevent?** - If the honest answer is "it documents the role," you do not need a new type. Use a field name, a type alias, or a doc comment. - If the answer is "it enforces an invariant" (non-empty, in-range, validated, one-of-N states, parsed-once), encode that invariant by construction. A type that any caller can build from raw parts in any shape proves nothing. The invariant lives in the constructor, and the constructor must be the only door. ## Names are not type safety `type UserId = string` (or a public tuple struct that wraps a `String` with a public field) gives you a nicer name and zero safety: every string is still a valid `UserId`, and you can pass an `OrderId` where a `UserId` is wanted. Two ways to make it real: - **Just a label?** A transparent alias is fine. Be honest that it is documentation, not a guarantee. - **A guarantee?** Hide the inner value behind a private field and a smart constructor (a parser) that is the only way in. Now "I hold one of these" means "it passed the check." Avoid auto-deriving broad conversions (`From`, `Into`, blanket serde/JSON decode, `DerefMut`) on a checked type when the derive lets a caller route around the constructor. Every public constructor is a potential trapdoor; keep the trusted module small. ## States: enums/unions over flag soup When a value can be in one of several mutually exclusive states, model it as a sum type (Rust `enum`, TS discriminated union, Go a sealed interface or a small state enum, Python `Enum`/`match` over a union). Booleans multiply into impossible combinations: ``` # illegal combinations are representable, so they will happen is_loading: bool is_error: bool data: T | null # (loading && error)? (data while loading)? nothing stops it. # one state at a time, data attached to the state that has it state = Loading | Error(message) | Ready(data) ``` Carry the data on the variant that owns it. `Ready` holds the data; `Loading` and `Error` cannot accidentally expose a half-populated value. ## Newtypes over primitives Wrap distinct domain values in distinct types so the compiler enforces the distinction and parsing happens once: - IDs: `UserId`, `OrderId` instead of bare integers/strings that swap silently. - Validated values: `Email`, `Url`, `NonEmpty<T>`, `Percentage` (0..=100). - Units: `Cents` vs `Dollars`, `Millis` vs `Seconds`. Mixing units is a classic outage; types make the mix a compile error. ## When a transparent wrapper is still worth it Sometimes you wrap not for an invariant but for: secrecy/redaction (a `Secret` that does not print its contents), trait/interface coherence, or clarity across a long call chain. That is legitimate. Document that it discourages misuse but does not prove safety, so no one mistakes it for an enforced invariant. ## Do not over-split Do not mint a new type for every real-world noun. Split types when they **behave differently** or **rule out different states**, not merely because the domain uses different words. Two concepts that are interchangeable in code can share a type. See the per-language files for the concrete spelling (private fields and smart constructors, branded types, unexported struct fields, frozen dataclasses).
principles/parse-dont-validate.md# Parse, don't validate
> Turn weak external input into a proof-carrying type once, at the boundary.
From Alexis King's article. The core move: instead of checking that data is
valid and then continuing to pass the same weak type around, convert it into a
type that *can only hold valid data*, and pass that type inward.
## Validate vs parse
```
# validate: the knowledge gained is thrown away
def handle(raw: dict):
if not is_valid(raw): # we learned something...
raise BadInput
process(raw) # ...and immediately forgot it; process re-checks
# parse: the knowledge is captured in the type
def handle(raw: dict):
config = parse_config(raw) # -> Config, or raises/returns error
process(config) # process REQUIRES Config; cannot be called on junk
```
A validator returns a boolean or void and leaves you holding the same untrusted
value. A parser returns a new, stronger value (or an error) and makes the
untrusted value disappear. Downstream code that requires the strong type can no
longer be called with bad input, so it does not need to re-check.
## Workflow
1. **Find the boundary.** Where does weakly typed or untrusted data enter? File
load, deserialization, CLI/env parsing, network response, generated data,
user edits.
2. **Design the type you wish you had downstream.** What would let processing
code stop being defensive? Prefer enums, non-empty collections, validated
newtypes with private fields, maps/sets, bounded numbers, checked structs,
over `string`, `list`, `null`, and loose booleans.
3. **Write the parser:** `parse(raw) -> Strong | Error`. All shape and validity
failure happens here, once.
4. **Push the strong type down** into signatures. The check: if a caller can
skip the parser and still type-check, you are not done. Make the inner
functions demand the strong type.
5. **Keep failure at the boundary.** Processing code should not rediscover basic
shape errors after it has already acted.
6. **For checked-in assets / literals**, construct the strong form at startup or
compile time, not by sprinkling parse-and-unwrap through runtime paths.
## Smells
- A function named `validate*`/`check*`/`isValid*` that returns `bool` or
`void`/`unit` for a boundary shape check. It throws away what it learned.
Return the parsed value instead.
- A `Result<(), E>` / `Promise<void>` that exists only to signal "input was ok."
Fine for genuine effects with no value; suspicious when it is guarding data
that the caller then keeps using raw.
- Re-parsing or re-checking the same field at three different depths. The parse
belongs once, at the edge.
## Boundary types vs domain types
It is fine, often good, to have a loose "wire" type that mirrors the external
format exactly (the raw serde struct, the zod input, the JSON shape), and a
separate checked domain type. The parser is the function between them. Name the
checked type for what it proves (`CheckedConfig`, `VerifiedOrder`, `NonEmpty
Plan`), so its meaning is visible at every call site.
## If it is already typed, do not re-parse
If a value was constructed inside trusted code and no invalid state is
representable, do not parse it again "to be safe." Make the receiving function
require the precise type. Re-parsing typed data is noise and hides the real
boundary.
See the per-language files for the idiomatic parser shape (smart constructors,
zod/valibot schemas, decode functions returning errors, pydantic models).
principles/simplicity.md# Earn your abstractions; profile before optimizing
> Prefer the smallest correct thing. Add indirection only when real callers need
> it. Optimize only what you have measured.
Premature abstraction and premature optimization are the same error: paying a
cost now for a benefit that may never arrive, and obscuring the code in the
meantime.
## Earn abstractions
- **Rule of two (lean toward three).** Do not introduce a generic, a
trait/interface, a base class, a config option, or a new layer until there are
at least two real, present callers that need it. One caller does not justify
an abstraction; it justifies a concrete function. Duplication is cheaper to
fix later than the wrong abstraction.
- **Concrete first.** Write the specific version. When the second case appears,
factor out exactly what they share, no more. The shape of the right
abstraction is obvious once you have two examples and guesswork before.
- **Indirection has a cost.** Every interface, callback, generic parameter, and
layer is something the next reader must hold in their head and follow to find
the real behavior. Add it when it removes more complexity than it adds.
- **Avoid speculative generality.** "We might need to swap the database",
"someone might want another backend": until that someone exists, the
flexibility is dead weight that constrains the code that does exist.
- **Type erasure / dynamic dispatch on a hunch.** Reaching for `dyn Trait`,
`interface{}`/`any`, or a plugin registry where a concrete type would do trades
clarity and performance for flexibility you are not using.
When the abstraction does arrive, make it as small as the real callers require,
named for what it does, sealed where it should not be extended.
## Profile before optimizing
- **Measure first.** Intuition about hot paths is wrong more often than right.
Profile or benchmark to find where time/allocations actually go before
changing anything for speed.
- **Optimize the proven hot path, then measure again.** Confirm the change
helped and did not regress elsewhere. An optimization you did not verify is a
complexity increase you are guessing about.
- **Do not trade clarity for unmeasured speed.** Manual index loops over clear
iterators, hand-inlining, micro-tricks: only once a measurement says this spot
matters. Readable code that is fast enough beats clever code that is
marginally faster and wrong next quarter.
- **Algorithm and data layout beat micro-tweaks.** The O(n^2) that should be
O(n), the repeated work that should be cached, the chatty IO that should be
batched: these dominate. Find them before tuning constants.
## Smallest correct thing
The default for any change: the simplest implementation that is correct, clear,
and tested. Reach for more (a generic, an optimization, a layer) only when the
code in front of you, not an imagined future, demands it. This is not an excuse
to under-build the requested outcome; it is a rule against building things
nobody asked for and nobody measured.
SKILL.md---
name: code-craft
description: "Use when writing, reviewing, or refactoring code."
user-invocable: true
argument-hint: "[rust|typescript|go|python] [target]"
---
# Code Craft
A small set of durable, language-agnostic engineering principles, plus a router
to the dialect of whatever language you are actually editing. The principles are
the same everywhere; only the spelling changes.
## How to use this skill
1. **Apply the core below where it improves the requested change.** These are
defaults, not reasons to refactor unrelated code.
2. **Detect the language(s) in scope** from the files being touched (see the
detection guide), then **read `languages/<lang>.md` when needed** for its
idioms and tooling. Load only the language(s) you are working in.
3. **Read `principles/<name>.md` for depth** when a principle is the crux of the
change (a boundary redesign, an error-model decision). The core below is the
summary; the principle file is the workflow and the nuance.
4. Run the project's required checks and verification appropriate to the change.
A review does not require running the full suite. Report checks not performed.
## Universal core
Each principle links to a deeper file and maps into every language file.
### 1. Make illegal states unrepresentable
Encode invariants in the type system so the bad case cannot be constructed, not
in a name, comment, or convention that a caller can ignore. A wrapper that only
renames a value buys nothing; a type whose only constructor enforces the
invariant buys everything. Prefer enums/unions for mutually exclusive states
over flag soup, and structured data over a string that has to be re-parsed.
Depth: [`principles/illegal-states.md`](principles/illegal-states.md).
### 2. Parse, don't validate
At boundaries where external data enters (config, JSON/TOML, CLI/env, network,
user edits), parse it into a usable type and pass that value inward. Prefer
returning refined data over validating and continuing to pass the raw value.
Use the language's type system where it reduces misuse. Depth:
[`principles/parse-dont-validate.md`](principles/parse-dont-validate.md).
### 3. Errors are values; gates fail closed
Handle expected failure through the language's value channel (Result, error
return, typed exception), not by crashing on recoverable conditions. Add context
as the error propagates so the message is a chain, not a single line. Never
silently swallow an error. A gate or check that cannot produce a valid answer is
a block, never a quiet pass. Depth:
[`principles/errors-as-values.md`](principles/errors-as-values.md).
### 4. No stringly-typed data; newtypes over primitives
Use distinct types when mixing domain values would be a meaningful error, or
when a constructor preserves a useful invariant. Do not wrap every primitive
solely to replace its name. This is the everyday form of principle 1. Depth:
[`principles/illegal-states.md`](principles/illegal-states.md).
### 5. Mind ownership and copies, but clarity first
Avoid copying or allocating when borrowing or referencing is correct and clear,
especially in loops and hot paths. Accept the most general input type (a view,
not an owned container). This matters most in Rust and C-family code and least
in GC'd languages, but unnecessary deep copies and re-allocations are a smell
everywhere. Do not contort readable code for a copy you have not measured.
### 6. Testing lives in testing-craft
All test guidance (behavior over implementation, change-detector tests, test
doubles, layer choice, DAMP structure, determinism, property-based tests,
per-language test dialects) moved to the
[`testing-craft`](../testing-craft/SKILL.md) skill. Use it whenever you
write or review tests. The slot keeps its number so the other principles'
cross-references stay valid.
### 7. Architecture docs are a stable map
Keep a short, durable description of module boundaries, invariants, and
cross-cutting concerns. Name the important modules and the deliberate absences
("X stays out of layer Y"). When code moves, update the map rather than adding a
migration note. Keep churny detail in code comments, not the map. Depth:
[`principles/architecture-docs.md`](principles/architecture-docs.md).
### 8. Earn your abstractions; profile before optimizing
Prefer the smallest correct thing. Do not add generics, traits/interfaces,
layers, or indirection before there are two real callers that need them. Do not
optimize on a hunch: measure first, then optimize the proven hot path, then
measure again. Premature abstraction and premature optimization are the same
mistake (acting on a future that has not arrived). Depth:
[`principles/simplicity.md`](principles/simplicity.md).
### 9. Keep repository setup separate
Follow the existing toolchain. Ordinary coding, prototyping, and hardening do
not authorize new governance files, agent review gates, or release infrastructure.
When the user requests repository setup, use
[`project-bootstrap`](../project-bootstrap/SKILL.md) for Jess's defaults.
## Language router
Read the relevant language file when its details are needed: tooling,
concurrency, naming, project layout, and language-specific patterns.
| Language | File | Detect by |
|---|---|---|
| Rust | [`languages/rust.md`](languages/rust.md) | `*.rs`, `Cargo.toml` |
| TypeScript / JavaScript | [`languages/typescript.md`](languages/typescript.md) | `*.ts`, `*.tsx`, `*.js`, `tsconfig.json`, `package.json` |
| Go | [`languages/go.md`](languages/go.md) | `*.go`, `go.mod` |
| Python | [`languages/python.md`](languages/python.md) | `*.py`, `pyproject.toml`, `requirements.txt` |
For a language not listed, apply the universal core directly and follow the
project's existing conventions; the principles are designed to transfer.
## Precedence
Project instructions and existing code conventions win over this skill. If a
repo's `AGENTS.md`/`CLAUDE.md` or its established patterns conflict with a
principle here, follow the repo and say so. This skill is the default, not an
override.
## Provenance
Distilled from `leonardomso/rust-skills` (MIT), Matklad's Rust100k series, and
Alexis King's "Parse, don't validate" and "Names are not type safety", then
generalized beyond Rust.