Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/AxGord/claude-workflow --skill architecture명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | architecture |
| description | Simplicity-first architecture decisions |
Claude's #1 architecture mistake: adding complexity before pain exists.
Three questions (if you can't answer all three concretely, suggest the simpler option):
DON'T: Create interfaces "for testability" or "future flexibility" DO: Create interfaces when you have 2+ real implementations OR a package boundary GOTCHA: Single implementation behind interface is ceremony, not architecture EXCEPTION: Public library APIs only
Models often default to "interfaces for testing are fine" — this skill corrects that.
Simple approach: [simplest solution]
This works until: [specific pain point]
If you hit that pain, then consider: [next step up]
This adds: [specific cost]
But solves: [specific problem]
DON'T: Put serialize()/deserialize() methods directly in runtime/state classes
DO: Keep serialization in a separate class or in the persistence layer
Why: Runtime state manages current values, interpolation, transitions. Serialization is I/O concern — different reason to change, different dependencies (XML, JSON, etc.). Mixing them inflates the class and hides the boundary.
GOTCHA at review: Inline serialization "works" and passes tests — the violation is structural, not functional. Actively check for it: if a class has both state logic AND serialize/deserialize, flag it.
Before proposing ANY storage or data mechanism, trace the COMPLETE path:
DON'T: Propose new storage mechanisms (counters, file scanning, new DB columns) before checking if the project already has infrastructure for this type of data. DO: Find how analogous fields in the same system are stored and propagated. Follow the same path.
Common mistake: understanding WHERE data is stored but not HOW it flows through intermediate layers (serialization, copy-on-read, event dispatch). Missing one copy step = silent data loss.
When in doubt: suggest the boring, proven, simple solution.
When moving a conditional guard from inside a function to its call site (wrapping the call in if), immediately remove the now-redundant parameter/branch from the callee.
DON'T: Make the call conditional but leave the parameter that's now always the same value
DO: Remove the parameter entirely — the caller's if IS the guard now
Why: A parameter that's always true (or always false) is dead code. It misleads readers into thinking there's a second code path. Finish the refactor in one step.
When moving filtering logic from a caller into a shared state manager (allocator, registry, cache), verify the manager's API distinguishes all semantic categories the original code handled.
DON'T: Replace an explicit filter with a generic state query that collapses distinct categories DO: List every category the original filter distinguished, confirm the API preserves each — or extend the API
Example (abstracted): an allocator tracked "taken by owner X"; the original caller excluded only MANUALLY-assigned resources from its forbidden set (auto-assigned ones were reassignable). Replacing that filter with a generic isAvailable() collapsed manual and auto into one check and blocked selectable resources.
Rule: A generic isX() that returns the same answer for semantically different states is a lossy abstraction. Before using it, check: does the original code care why the state is set, not just that it's set?
When you remove a function/call because one of its responsibilities is rejected or obsolete, list every side effect it has before deleting it. A mechanism named for one concern often quietly carries a second, still-needed concern — and the name masks it, so wholesale deletion silently disables the second.
DON'T: See "this does the rejected X" → delete the whole call/function. DO: Trace every effect (what it sets, reveals, schedules, gates). Keep the still-needed effects; remove only the rejected one — often by splitting the dual-purpose function.
Tell: the deleted thing was a per-frame/lifecycle hook whose name describes only one job; the regression appears in an adjacent concern (visibility, enablement, layout), not the one you were targeting — and surfaces late (doc/visual pass), not in the correctness check.
Example (abstracted): a per-frame hook named for one job did TWO things through one output channel — it both revealed a layer (fade-in) and hid it near a moving object (the rejected job). Deleting the whole call removed the rejected behavior AND the reveal — the layer never appeared again. Fix: split the dual-purpose function, keep the reveal, drop only the rejected half.
When refactoring an inline operation into a two-phase approach (set intent + execute), match the original operation's lifecycle scope.
DON'T: Wrap the new orchestration in container-level stop/start if the original operated at element level DO: Let existing element-level lifecycle (setters, listeners) handle transitions — they already work
Example (abstracted): an inline single-element swap was refactored to intent+reallocate and wrapped in container-level stop/start — whose start-gate imposed a minimum-count constraint the original inline swap never hit. The element-level setter already handled stop/start of the swapped pair.
Rule: Before adding lifecycle management to a refactored operation, check: did the original operation need it? If the original worked within existing infrastructure (setters, listeners), the refactored version should too.
When changing a method's return type, parameter list, or visibility, grep ALL callsites BEFORE finalizing the change.
A signature change compiles fine in the modified file but silently breaks callers that ignore the new return type. Example: clearData(): void → clearData(): ITask — callers that fired-and-forgot still compile; the returned ITask is constructed and discarded; the async work never starts. No compile error, no exception — the cleanup pipeline is a no-op.
Symptom: behavior silently regresses in code paths nobody runs during initial smoke test. Caught later by code reviewers, integration tests, or production bugs.
DON'T: Change a signature, run the obvious test, declare done.
DO: Change a signature, grep <methodName>\b across the whole project, audit each callsite. Trust "find references" only if the IDE has full project indexing.
Adding a member to an interface/abstract type is also a signature change. Every implementor must gain the member — including hand-written test doubles / fakes / in-memory stubs, not just production classes.
Tooling gotcha: test runners that strip types via esbuild/swc (vitest, ts-jest isolatedModules, bun test) do NOT typecheck. A test double missing a newly-added interface method compiles fine and fails at RUNTIME as TypeError: x.method is not a function — often in unrelated tests that happen to reach the missing call. tsc --noEmit catches it; the test run alone does not.
DO: after adding an interface member, grep for implements <Interface> and for the type used as a field/param type, and update every implementor (prod + test) in the same change; run tsc (not just the test suite) before declaring done.
When you add a new canonical input/output mechanism that does the same thing as existing legacy paths (a typed API replacing an untyped one, a structured event replacing a string topic, a new control replacing a shortcut), the default urge is to keep the legacy paths "for compatibility" and build a coexistence shim. Don't.
DON'T: Silently keep legacy paths AND add a coexistence shim (gates, target-checks, debouncers) so the new and old don't fight. DO: Surface the question explicitly: "should we remove the old paths, or keep them?" — and prefer single-source by default.
Symptom of violation: you find yourself adding gating logic (coordinate checks, if (ev.target === ...) filters, inflight flags) just to make two input paths coexist when one is meant to subsume the other.
Rule: A new canonical mechanism is an opportunity to remove the old one, not to add a referee between them. If the user didn't explicitly ask to keep both, ask before keeping both.
When something grows unbounded (state files, DB rows, temp dirs, handles, cache entries), the artifact has a creation event but no deletion event tied to its owner's lifecycle.
DON'T: Add a periodic retention/TTL/prune sweep ("delete things older than N days"). It leaves the leak in place, adds a tunable nobody sets correctly, and needs a guard against its own edge cases (NaN window deleting everything). DO: Tie the artifact's lifetime to its owner. Delete it at the terminal transition (session ends → its file is removed; process dies → its records go). The owner that creates it owns destroying it.
Tell: if your fix introduces a RETENTION_DAYS/maxAge/cleanup-cron, ask "why does this outlive its owner at all?" A retention sweep is a legitimate policy for audit data deliberately kept; it is the wrong tool for a missing teardown.
Before designing the fix: check sibling/related projects for an already-shipped fix of the same bug class (same author/org repos especially) and mirror its approach instead of inventing a parallel one.
When you change a parameter X, the instinct is to update code that reads X. But state that was computed/staged earlier from X's old value (a precomputed position, a cached offset, a buffer size, a timeout deadline) doesn't "read" anything — it silently encodes the stale quantity. Those frozen values contradict the new X, often by the full ratio of old/new.
DON'T: Change X, fix the obvious live consumers, ship. The stale-derived state then fires with pre-change values intact.
DO: When changing X, enumerate not just "who reads X now" but "what was sized/positioned/scheduled FROM X (possibly long before X changed)." Re-derive those from the new X, or trigger their recompute at the moment X takes effect.
Tell: a downstream effect is off by a suspiciously clean multiple; bug magnitude ≈ oldValue / newValue.
GOTCHA: The stale computation may happen at a different time (init, precompute pass) than the code you just changed (tick, callback). Grep for every site that captures a derived value from X into a local variable, struct field, or closure — those are the freeze points.
Do not tell the user a change makes things WORSE without deriving the actual result. A negative framing extrapolated from a stale note, a prior decision, or an intuitive "it'll scatter / fragment / break" mental model is not evidence.
DON'T: Warn "this will be markedly worse" / "destroys the existing structure" from memory or intuition, then ask the user whether to proceed. DO: Run the concrete analysis that confirms or refutes the claim FIRST — then present the measured result, and only flag a downside that survives the measurement.
Tell: You're about to present a tradeoff as a downside, and your reason is a remembered decision ("we left this alone before") or a shape you pictured ("members will scatter"), not something you computed on the actual artifact.
Example (abstracted): asked to apply an automated reorder to a large file, the agent warned it would be "markedly worse", citing a stale note — the user pushed back; a one-command permutation check proved the change was content-neutral and mild. The unmeasured negative cost a round-trip and trust.
Principle: "I think this is worse" is a hypothesis, not a finding. The cost of measuring is usually one command; the cost of a wrong negative is a wasted round-trip and a user who now distrusts your tradeoff calls.
A quantitative claim ("switching source X→Y makes the artifact 3× smaller") given to the user becomes a decision input — if it's wrong in either direction, the user picks the wrong option. Two failure modes:
objdump -p | grep NEEDED, recursively) against what the bundle itself ships before quoting its size.zip without -y storing a large lib once per symlink) reported ~2× the honest size. Measure through the EXACT pipeline that ships, not a hand-rolled equivalent.Tell: you're comparing options by size and one number came from a test that "just worked" on a machine with the dependency's ecosystem already installed, or from a different archiver/flags than production uses.
DON'T: Present partial-closure or wrong-pipeline numbers as the decision matrix. DO: Verify both sides' numbers with the full dependency closure and the production packaging command before the user chooses.
When the requirement names a concrete observable event — a specific transition, a bounce, a distinct step in a sequence — that beat is not a flourish. It is the spec. Reproducing the surrounding behavior while omitting the named beat is a wrong implementation, not a simpler one.
Tell: You're choosing an implementation because it's easier or "reads cleaner," and the specific event the user named is no longer distinctly observable in the result. Or: the user says the new thing "looks the same as the old/plain one."
DON'T: Trade away a named beat for implementation simplicity or "plausibility" concerns without surfacing the trade-off. DO: