Make Obvious
Reduce the amount a maintainer must hold in working memory. Work on one
invariant-sized slice at a time. A cleanup pass is a bounded queue of such
slices, not permission to rewrite the repository.
This workflow requires the shared $patterns skill. Invoke it through skill
selection; do not assume it exists at a sibling filesystem path. If $patterns
is unavailable, report the missing prerequisite and stop.
Choose the lens
Match the lens to what the user authorized:
- Explain — model a dense behavior without editing it.
- Judge — review code shape and report only specific, actionable findings.
- Simplify — apply a named deletion, inline, colocation, rename, or accepted
extraction in the user-selected scope.
- Imports — change import paths only; read
the cleanup lenses.
- Casts — audit or remove TypeScript assertions; read
the cast lens before touching a cast.
- Pass — process an explicitly bounded cleanup scope as successive slices;
read the cleanup lenses.
Do not turn a request for judgment into an edit. When the user already named a
microscopic simplification and asked to make it, do not add a separate approval
pause unless it changes behavior, public surface, ownership, or architecture.
Ground the slice
- Read repository instructions, applicable patterns, the complete parent, and
current
git status --short plus the worktree diff. Search all callers,
tests, and architecture notes, then read the directly relevant ones
completely; do not turn a one-slice inquiry into an unbounded repository
survey.
- Preserve unrelated work. Re-read a file immediately before editing when the
worktree is changing concurrently.
- Honor a user-selected slice. Otherwise recommend the smallest cohesive slice
that owns a public promise, lifecycle, failure policy, observation rule, or
one concrete readability problem. Do not inventory every possible refactor
before teaching or fixing the selected slice.
- Invoke
$patterns and search its index for the exact task. For behavioral
orchestration, read Readable workflow boundaries. For a one-caller
wrapper around one simple call, read inline-one-call-simple-helpers.
When the slice contains concurrency, scopes, fibers, latches, streams,
subscriptions, or cancellation, also read Concurrency slice lens. For
imports, casts, or a named cleanup smell, read the matching pattern instead
of loading unrelated workflow guidance.
- For library primitives, inspect the installed version first and use vendored
or upstream source as explanatory reference. When versions differ, append
one brief version note to the local glossary.
Learn before editing
Use the full brief for Explain, for an unaccepted seam, or whenever the
observable contract is unclear. A directly requested microscopic
simplification may do this grounding internally and proceed. When presenting a
brief, keep it read-only, explain the slice in plain language before library
terminology, and include only:
- Public promise — one sentence describing what callers observe.
- Boundary — inputs, outputs, owned state, failures, and lifetime.
- Local glossary — each unfamiliar primitive, its local name, and the one
job it performs here.
- Ownership view — a small tree or table when resources have nested
lifetimes.
- Execution view — one ordinary sequence, followed by one race timeline at
a time only when a race defines the behavior.
- Invariant table — significant states or events and their observable
results.
- Complexity judgment — what is essential to the contract versus
accidental code shape, naming, duplicated state, or test ceremony.
- Proposed move — the smallest deletion, inline, colocation, rename, direct
import, or domain boundary that would make the slice obvious, plus the
existing tests that protect it; or an explicit no refactor warranted
conclusion.
- Vocabulary proof — when the move introduces a noun, identify the
distinct value, responsibility, or lifetime it names. If it is only another
view or duplicate of an existing domain object, keep the existing noun.
Use a proposed function name like an annotation: the parent should state what
happens while the function owns how it happens.
Do not infer a domain concept from an existing accessor name. A function named
readRouteMetadata(route) may be historical indirection around the Route
itself; its name does not prove that metadata is a separate concept worth
preserving or exposing at a new boundary.
Prefer named properties for ordinary data and genuine associations. Do not hide
such a value behind a symbol and then add a reader merely to recover it. A
symbol should buy identity, nominal typing, collision avoidance, or a required
framework protocol; opacity alone is not a benefit. When a typed reader only
returns value.property, inline the property access. Keep a reader only when it
owns real decoding, validation, policy, dynamic lookup, or another behavior
that callers should not reproduce.
When the brief proposes a behavior model or new seam that the user has not
accepted, stop and ask separately whether each is correct. Do not edit
production code, tests, or durable documentation until the user accepts that
action.
Refactor the confirmed slice
If the user accepts a no-refactor conclusion, do not edit. Report which
complexity is essential and stop.
- Recheck worktree status, source hashes, and test baselines before editing.
- Preserve public behavior, errors, cancellation, cleanup, ordering, object
identity, and failure causes unless the user explicitly changes the
contract.
- Reduce before extracting: delete proven stale structure; inline functions
with one caller and one simple call; colocate one-consumer details; import
from the defining module; and tighten names. Do not create a sibling file
for a one-call wrapper to satisfy one export per file. When a helper is
meaningful enough to retain, put it in its own same-named file and export
that function directly; do not leave named private helper functions in the
parent module. Extract only a block that owns a complete invariant, even
when it still has one caller. Stop as soon as the code is obvious.
- Extract only when the remaining complexity belongs to one complete
invariant. Move that invariant behind one domain-named boundary and keep
the caller as ordered orchestration; do not move arbitrary consecutive
lines.
- When extraction is warranted, prefer a deep module with a small honest
interface. Put each retained helper function in its own same-named file and
export that function directly, including helpers with only one caller. A
source file exports at most one function. Do not evade this rule with a bag
of function-valued properties. Its implementation may be as long as the
invariant requires; file or function length is diagnostic, not a pass/fail
rule.
- When one file implements one workflow, prefer one exported function whose
body presents that workflow in human reading order. In Effect code, export
the actual
Effect.fn; do not wrap a private ...Effect implementation only
to reshape its signature. Keep one-use checks and logical branches in that
body when they do not deserve a helper name. When a helper is meaningful
enough to name, move it to its own same-named file even when it has only one
caller; do not leave named private helper functions in the parent module. A
longer cohesive function is preferable to making the reader reconstruct one
workflow from several fragments. Use a short numbered phase overview and
matching inline checkpoints when stable steps make that reading order
clearer. This is the skill's production helper and workflow-module rule:
every retained helper function has its own file, and each source file
exports at most one function. Anonymous callbacks and class methods are not
separate helper functions. See the boundary rationale in the $patterns
Readable workflow boundaries pattern.
Treat an immediate kind branch as evidence that the boundary may hide
multiple workflows when the branches have materially different inputs,
failure channels, callers, or lifetimes. Inspect those workflows separately
and share only independently meaningful rules; see
its Split a boundary that hides multiple workflows test.
- In Effect code, use
Effect.fn when the boundary performs effects, can fail,
owns resources, or participates in interruption. Use a plain function for a
total synchronous transformation or a declarative Stream construction.
- Import directly from the defining module. Do not add feature barrels,
compatibility re-exports, generic utility bags, or configuration invented
only to make extraction possible.
- Do not create a large runtime-state parameter bag merely to split mutually
dependent lifecycle procedures. Treat that as a separate architecture
decision.
- Remove private data or branches only when they belong to the selected slice,
are proven unused, and remain covered by public behavior tests.
- Audit names after extraction. A name that depended on its old context is
still accidental complexity. Include the module, factory, filename, test,
documentation title, and each boundary member in that audit; when the owned
responsibility changes, stale outer names are as misleading as stale local
names. Name each boundary member so callers can tell
what it returns or changes without reading its implementation; distinguish
access shapes such as a one-time read and a Stream instead of giving both
the same content noun. When both content and mechanism are necessary to
remove ambiguity, name both—for example, pair
getHandle() with
handleStream, not handles or a bare stream. Apply the same vocabulary
across public and internal boundaries unless their roles genuinely differ.
Replace generic internal nouns such as snapshot,
value, data, current, or tail with the role they perform when that
role is more specific. Before renaming, identify what the variable itself
denotes. Do not overcorrect a content-only name with another content-only
name when the variable is actually a container. Name a meaningful container
as its semantic content plus the abstraction that determines how it is
used—for example, snapshotRef for a
SubscriptionRef<IActorStateSnapshot> and snapshot for one value read or
emitted from it. Do not rename the contained value after one downstream
interpretation: a Stream may interpret the snapshot, but that does not make
the stored value a streamStatus. Mirror the type's role when it reveals
available operations, mutation, subscription, ownership, or lifetime; do
not copy an incidental implementation type into every local name.
Make the tests teach the slice
- Build an old-test to invariant to new-test coverage map before moving tests.
- Group tests by observable behavior. Keep fixtures local to that behavior and
import them directly; do not create a universal machine or fake runtime.
- Keep concurrency deterministic with barriers such as
Deferred. Do not use
sleeps, polling, or timeouts as evidence of ordering.
- Put a short happens-before timeline above a test whose scheduling is not
obvious. Extract repeated scheduling only behind a domain-named scenario
driver, while keeping assertions in the test.
- Preserve cross-observer assertions when their shared identity or failure
cause is the invariant.
Leave focused knowledge
After the confirmed refactor passes, synchronize any existing concept or
architecture page made stale by the change. Create a new concept page only
with explicit user approval for that durable documentation scope. When a page
is updated or approved, include the public promise, ownership/lifetime view,
invariant table, primitive roles, failure and cancellation behavior, and
executable evidence. Prefer one small diagram or mapping table over another
end-to-end system tour.
Finish one slice
When code changed, run the repository task runner's focused tests, typecheck,
build, and diff checks. Report the before/after responsibilities and visible
orchestration, not line movement alone. Unless the user explicitly requested a
bounded pass or batch, name the next candidate slice and stop. In a bounded
pass, continue only through the named scope and stop before an unapproved
architecture decision or the first blocked cast removal.
1---2name: make-obvious3description: Make dense or needlessly indirect code understandable, then simplify one coherent slice at a time. Use for make-obvious; cleanup or slop review; prune, simplify, inline, or import cleanup; fix-casts or TypeScript assertion audits; and one-call wrappers or one-liner helpers that should be inlined. Do not use for generic tutorials or unbounded refactors without a concrete scope.4---56# Make Obvious78Reduce the amount a maintainer must hold in working memory. Work on one9invariant-sized slice at a time. A cleanup pass is a bounded queue of such10slices, not permission to rewrite the repository.1112This workflow requires the shared `$patterns` skill. Invoke it through skill13selection; do not assume it exists at a sibling filesystem path. If `$patterns`14is unavailable, report the missing prerequisite and stop.1516## Choose the lens1718Match the lens to what the user authorized:1920- **Explain** — model a dense behavior without editing it.21- **Judge** — review code shape and report only specific, actionable findings.22- **Simplify** — apply a named deletion, inline, colocation, rename, or accepted23 extraction in the user-selected scope.24- **Imports** — change import paths only; read25 [the cleanup lenses](references/cleanup.md).26- **Casts** — audit or remove TypeScript assertions; read27 [the cast lens](references/casts.md) before touching a cast.28- **Pass** — process an explicitly bounded cleanup scope as successive slices;29 read [the cleanup lenses](references/cleanup.md).3031Do not turn a request for judgment into an edit. When the user already named a32microscopic simplification and asked to make it, do not add a separate approval33pause unless it changes behavior, public surface, ownership, or architecture.3435## Ground the slice36371. Read repository instructions, applicable patterns, the complete parent, and38 current `git status --short` plus the worktree diff. Search all callers,39 tests, and architecture notes, then read the directly relevant ones40 completely; do not turn a one-slice inquiry into an unbounded repository41 survey.422. Preserve unrelated work. Re-read a file immediately before editing when the43 worktree is changing concurrently.443. Honor a user-selected slice. Otherwise recommend the smallest cohesive slice45 that owns a public promise, lifecycle, failure policy, observation rule, or46 one concrete readability problem. Do not inventory every possible refactor47 before teaching or fixing the selected slice.484. Invoke `$patterns` and search its index for the exact task. For behavioral49 orchestration, read **Readable workflow boundaries**. For a one-caller50 wrapper around one simple call, read **inline-one-call-simple-helpers**.51 When the slice contains concurrency, scopes, fibers, latches, streams,52 subscriptions, or cancellation, also read **Concurrency slice lens**. For53 imports, casts, or a named cleanup smell, read the matching pattern instead54 of loading unrelated workflow guidance.555. For library primitives, inspect the installed version first and use vendored56 or upstream source as explanatory reference. When versions differ, append57 one brief version note to the local glossary.5859## Learn before editing6061Use the full brief for **Explain**, for an unaccepted seam, or whenever the62observable contract is unclear. A directly requested microscopic63simplification may do this grounding internally and proceed. When presenting a64brief, keep it read-only, explain the slice in plain language before library65terminology, and include only:66671. **Public promise** — one sentence describing what callers observe.682. **Boundary** — inputs, outputs, owned state, failures, and lifetime.693. **Local glossary** — each unfamiliar primitive, its local name, and the one70 job it performs here.714. **Ownership view** — a small tree or table when resources have nested72 lifetimes.735. **Execution view** — one ordinary sequence, followed by one race timeline at74 a time only when a race defines the behavior.756. **Invariant table** — significant states or events and their observable76 results.777. **Complexity judgment** — what is essential to the contract versus78 accidental code shape, naming, duplicated state, or test ceremony.798. **Proposed move** — the smallest deletion, inline, colocation, rename, direct80 import, or domain boundary that would make the slice obvious, plus the81 existing tests that protect it; or an explicit **no refactor warranted**82 conclusion.839. **Vocabulary proof** — when the move introduces a noun, identify the84 distinct value, responsibility, or lifetime it names. If it is only another85 view or duplicate of an existing domain object, keep the existing noun.8687Use a proposed function name like an annotation: the parent should state what88happens while the function owns how it happens.8990Do not infer a domain concept from an existing accessor name. A function named91`readRouteMetadata(route)` may be historical indirection around the Route92itself; its name does not prove that `metadata` is a separate concept worth93preserving or exposing at a new boundary.9495Prefer named properties for ordinary data and genuine associations. Do not hide96such a value behind a symbol and then add a reader merely to recover it. A97symbol should buy identity, nominal typing, collision avoidance, or a required98framework protocol; opacity alone is not a benefit. When a typed reader only99returns `value.property`, inline the property access. Keep a reader only when it100owns real decoding, validation, policy, dynamic lookup, or another behavior101that callers should not reproduce.102103When the brief proposes a behavior model or new seam that the user has not104accepted, stop and ask separately whether each is correct. Do not edit105production code, tests, or durable documentation until the user accepts that106action.107108## Refactor the confirmed slice109110If the user accepts a no-refactor conclusion, do not edit. Report which111complexity is essential and stop.1121131. Recheck worktree status, source hashes, and test baselines before editing.1142. Preserve public behavior, errors, cancellation, cleanup, ordering, object115 identity, and failure causes unless the user explicitly changes the116 contract.1173. Reduce before extracting: delete proven stale structure; inline functions118 with one caller and one simple call; colocate one-consumer details; import119 from the defining module; and tighten names. Do not create a sibling file120 for a one-call wrapper to satisfy one export per file. When a helper is121 meaningful enough to retain, put it in its own same-named file and export122 that function directly; do not leave named private helper functions in the123 parent module. Extract only a block that owns a complete invariant, even124 when it still has one caller. Stop as soon as the code is obvious.1254. Extract only when the remaining complexity belongs to one complete126 invariant. Move that invariant behind one domain-named boundary and keep127 the caller as ordered orchestration; do not move arbitrary consecutive128 lines.1295. When extraction is warranted, prefer a deep module with a small honest130 interface. Put each retained helper function in its own same-named file and131 export that function directly, including helpers with only one caller. A132 source file exports at most one function. Do not evade this rule with a bag133 of function-valued properties. Its implementation may be as long as the134 invariant requires; file or function length is diagnostic, not a pass/fail135 rule.1366. When one file implements one workflow, prefer one exported function whose137 body presents that workflow in human reading order. In Effect code, export138 the actual `Effect.fn`; do not wrap a private `...Effect` implementation only139 to reshape its signature. Keep one-use checks and logical branches in that140 body when they do not deserve a helper name. When a helper is meaningful141 enough to name, move it to its own same-named file even when it has only one142 caller; do not leave named private helper functions in the parent module. A143 longer cohesive function is preferable to making the reader reconstruct one144 workflow from several fragments. Use a short numbered phase overview and145 matching inline checkpoints when stable steps make that reading order146 clearer. This is the skill's production helper and workflow-module rule:147 every retained helper function has its own file, and each source file148 exports at most one function. Anonymous callbacks and class methods are not149 separate helper functions. See the boundary rationale in the `$patterns`150 **Readable workflow boundaries** pattern.151 Treat an immediate kind branch as evidence that the boundary may hide152 multiple workflows when the branches have materially different inputs,153 failure channels, callers, or lifetimes. Inspect those workflows separately154 and share only independently meaningful rules; see155 its **Split a boundary that hides multiple workflows** test.1567. In Effect code, use `Effect.fn` when the boundary performs effects, can fail,157 owns resources, or participates in interruption. Use a plain function for a158 total synchronous transformation or a declarative Stream construction.1598. Import directly from the defining module. Do not add feature barrels,160 compatibility re-exports, generic utility bags, or configuration invented161 only to make extraction possible.1629. Do not create a large runtime-state parameter bag merely to split mutually163 dependent lifecycle procedures. Treat that as a separate architecture164 decision.16510. Remove private data or branches only when they belong to the selected slice,166 are proven unused, and remain covered by public behavior tests.16711. Audit names after extraction. A name that depended on its old context is168 still accidental complexity. Include the module, factory, filename, test,169 documentation title, and each boundary member in that audit; when the owned170 responsibility changes, stale outer names are as misleading as stale local171 names. Name each boundary member so callers can tell172 what it returns or changes without reading its implementation; distinguish173 access shapes such as a one-time read and a Stream instead of giving both174 the same content noun. When both content and mechanism are necessary to175 remove ambiguity, name both—for example, pair `getHandle()` with176 `handleStream`, not `handles` or a bare `stream`. Apply the same vocabulary177 across public and internal boundaries unless their roles genuinely differ.178 Replace generic internal nouns such as `snapshot`,179 `value`, `data`, `current`, or `tail` with the role they perform when that180 role is more specific. Before renaming, identify what the variable itself181 denotes. Do not overcorrect a content-only name with another content-only182 name when the variable is actually a container. Name a meaningful container183 as its semantic content plus the abstraction that determines how it is184 used—for example, `snapshotRef` for a185 `SubscriptionRef<IActorStateSnapshot>` and `snapshot` for one value read or186 emitted from it. Do not rename the contained value after one downstream187 interpretation: a Stream may interpret the snapshot, but that does not make188 the stored value a `streamStatus`. Mirror the type's role when it reveals189 available operations, mutation, subscription, ownership, or lifetime; do190 not copy an incidental implementation type into every local name.191192## Make the tests teach the slice1931941. Build an old-test to invariant to new-test coverage map before moving tests.1952. Group tests by observable behavior. Keep fixtures local to that behavior and196 import them directly; do not create a universal machine or fake runtime.1973. Keep concurrency deterministic with barriers such as `Deferred`. Do not use198 sleeps, polling, or timeouts as evidence of ordering.1994. Put a short happens-before timeline above a test whose scheduling is not200 obvious. Extract repeated scheduling only behind a domain-named scenario201 driver, while keeping assertions in the test.2025. Preserve cross-observer assertions when their shared identity or failure203 cause is the invariant.204205## Leave focused knowledge206207After the confirmed refactor passes, synchronize any existing concept or208architecture page made stale by the change. Create a new concept page only209with explicit user approval for that durable documentation scope. When a page210is updated or approved, include the public promise, ownership/lifetime view,211invariant table, primitive roles, failure and cancellation behavior, and212executable evidence. Prefer one small diagram or mapping table over another213end-to-end system tour.214215## Finish one slice216217When code changed, run the repository task runner's focused tests, typecheck,218build, and diff checks. Report the before/after responsibilities and visible219orchestration, not line movement alone. Unless the user explicitly requested a220bounded pass or batch, name the next candidate slice and stop. In a bounded221pass, continue only through the named scope and stop before an unapproved222architecture decision or the first blocked cast removal.