PABCD Initiative Documentation Hub pabcd_initiative codexclaw cli-jaw

dev-architecture

Source: skills/dev-architecture/SKILL.md

Module boundaries, circular deps, coupling taxonomy, and boundary defenses.

왜 별도 모듈인가
AI 에이전트는 기능을 빠르게 구현하지만 모듈 경계를 의식하지 않는다. 결과적으로 순환 의존성, 암묵적 커플링, barrel 남용이 쌓여 코드베이스가 리팩토링 불가능한 진흙 덩어리가 된다. 이 스킬은 모듈 분할, 의존성 방향, 레이어 경계를 기계적 규칙으로 강제하여 구조적 부패를 차단한다.
LLM 단독 사용 시 발생하는 문제

LLM은 '가장 가까운 import'를 선호한다. 기능을 구현할 때 이미 아는 파일에서 직접 import하므로, 레이어 경계를 넘는 의존성(UI에서 DB 클라이언트를 직접 import)이 자연스럽게 생긴다. 이건 LLM이 코드를 텍스트 토큰으로 처리하기 때문에 발생하는 구조적 문제다 — LLM은 '이 import가 아키텍처 경계를 넘는다'는 공간적 인식이 없다. madge, pydeps 같은 정적 분석 도구를 CI에 넣어서 기계적으로 잡아야 하는 이유가 여기에 있다.

이 스킬이 해결하는 실제 문제

에이전트가 만드는 순환 의존성

에이전트가 기능을 추가할 때 가장 가까운 파일에서 import하는 습관이 있다. A가 B를, B가 A를 import하는 순환이 생겨도 에러 없이 동작하다가, 번들 크기 폭발이나 런타임 undefined로 나중에 터진다. ARCH-CYCLE-01은 madge/pydeps로 순환을 CI에서 차단한다.

Barrel 파일의 트리셰이킹 파괴

index.ts에서 모든 것을 re-export하는 barrel 패턴은 편리하지만, 웹팩/롤업의 트리셰이킹을 무력화한다. 하나의 유틸을 import하면 barrel 전체가 번들에 포함된다. barrel-discipline.md는 barrel이 허용되는 조건과 금지 조건을 명시한다.

400 LOC 파일의 책임 과잉

에이전트는 기존 파일에 계속 코드를 추가하는 경향이 있다. 파일이 400 LOC을 넘으면 여러 책임이 뒤섞여 있을 가능성이 높다. 이 스킬은 기계적 분할 기준(LOC, 의존자 수, 무관 기능 공존)을 제공한다.

Triggers: circular import, module split, layer violation, dependency direction, barrel file, re-export, architecture refactor

Key Concepts

  • Structural Decision Gate
  • Pre-Change Structural Map
  • Layered Architecture Boundaries
  • Circular Dependency Detection
  • Coupling Taxonomy (8 types)
  • Barrel Discipline

Reference Documents

DocumentDescription
Barrel/Re-export Disciplinebarrel/index 파일의 허용 조건과 금지 조건, 트리셰이킹 영향
Circular Dependency Detection & Resolution순환 의존성 탐지 명령(madge/pydeps/go vet), 수정 전략, 실제 사례
Implicit Coupling Taxonomy8가지 커플링 유형, 심각도 매트릭스, 리팩토링 패턴

Sections Overview

SectionSummary
Module Boundaries모듈 경계 변경 시 구조 결정을 명시하고 구조 맵을 만든다.
Layered ArchitecturePresentation/Application/Domain/Infrastructure 4계층 import 규칙.
When to Split400 LOC 초과, 6+ 직접 의존자, 무관한 기능 공존 시 분할 기준.
Dependency Direction의존성 역전 원칙과 포트/어댑터 패턴 적용 가이드.

Academic References

PaperYearRelevance
Untangling Patterns of Two-Class Dependency Cycles
arXiv:2306.10599
202338개 OSS 프로젝트에서 순환 의존성을 해소하는 5가지 반복 패턴을 식별. circular-dependencies.md의 수정 전략 근거.
PeerChecker: Detecting Peer Dependency Resolving Loops in npm
arXiv:2505.12676
2025npm 생태계의 의존성 해결 루프 탐지 도구. 패키지 매니저 수준의 순환 문제 연구.
Comparing Static and Dynamic Weighted Software Coupling Metrics
arXiv:1909.12521
2019소스 코드 커플링과 런타임 커플링의 비교 — coupling-taxonomy.md의 8가지 유형 분류 근거.

Official Guides

Full Specification

Show full SKILL.md content

Dev-Architecture — Module Boundaries & Structural Integrity

> C0/C1 work (small local patches): See dev §0.0 Work Classifier + §0.1 Patch Fast-Path before reading references.

> Always read dev/SKILL.md first for project-wide conventions before applying architecture rules.

Enforces architectural rules that prevent structural decay: circular dependencies, implicit coupling, barrel abuse, and misplaced validation. These rules are mechanical — an AI coding agent can follow them without subjective judgment.

Severity mapping (dev §0.2): Severity: CRITICAL/HIGH ⇒ STRICT; MEDIUM ⇒ DEFAULT.

Modular References

FileWhen to ReadWhat It Covers
references/circular-dependencies.mdDetecting or fixing import cyclesDetection commands (madge/pydeps/go vet), fix strategies, real examples
references/coupling-taxonomy.mdReviewing code for hidden coupling8 coupling types, severity matrix, refactoring patterns, banned review responses
references/barrel-discipline.mdCreating/modifying index/barrel filesWhen barrels OK vs banned, tree-shaking impact, safe barrel template

External/current architecture evidence

Architecture rules in this skill are local and mechanical. When an architectural

decision depends on current framework guidance, cloud/provider reference

architecture, package deprecation, platform limits, or public source evidence,

read the active search skill and follow its query-rewrite, source-fetch, and

evidence-status rules. Use browser verification only after candidate URLs exist.

---

1. Module Boundaries

Structural Decision Gate

Severity: HIGH

Rule (ARCH-DECISION-01): When changing module boundaries, seams, layering, shared packages, or public exports above C0/C1 local-patch scope, name the structural decision before editing.

Required decision record, inline in the plan or in the repo's ADR/source-of-truth file:

  • Context: what pressure forced the boundary change.
  • Rejected alternative: at least one plausible option not chosen, with why.
  • Chosen move: split, extract, merge, invert dependency, introduce adapter, or change public export.
  • Consequences: new dependency direction, public contract impact, migration cost, and follow-up verification.

Route durable, surprising, hard-to-reverse, or cross-team choices to the repo's ADR/current-architecture source of truth instead of leaving them only in chat. dev-scaffolding owns where those durable docs live; this skill owns the boundary decision content.

Pre-Change Structural Map

Severity: HIGH

Rule (ARCH-MAP-01): Before recommending or applying a split/extract/merge for boundary work above C0/C1, produce a compact structural map from code evidence.

Map fields:

  • Core modules involved.
  • Direct dependents and dependencies.
  • Current and intended dependency direction.
  • Public boundaries touched (index.*, package exports, route/API contracts, CLI entry points).

Layered Architecture Boundaries

LayerMay ImportMUST NOT ImportExample
Presentation (UI/CLI/Controller)Application, DomainInfrastructure directlyReact component importing DB client
Application (Use Cases/Services)Domain, PortsPresentation, Infra adaptersService importing React component
Domain (Entities/Value Objects)Nothing (self-contained)Any other layerEntity importing Express
Infrastructure (Adapters/DB/HTTP)Domain (implements ports)Presentation, ApplicationDB adapter importing controller

When to Split a Module

Canonical file-size rule: >400 LOC -> split (DEFAULT). Deviations require a stated reason.

SignalAction
File exceeds 400 LOCSplit by responsibility (DEFAULT)
Module has 6+ direct dependentsExtract shared interface
Two unrelated features share a fileSeparate into own modules
Circular import detectedExtract shared types/interfaces to a third module
Module name contains "and" or "utils"Split by actual concern
3+ apps/services import the same feature folderPromote to a monorepo package with its own manifest + public boundary
Package needs its own release cadence, CI matrix, or versionSplit at package level, not folder level

Banned Patterns

BannedWhyFix utils.ts / helpers.ts growing unboundedBecomes a coupling magnetSplit by domain: date-utils.ts, string-format.ts
Cross-layer direct importBreaks dependency directionUse ports/adapters or event bus
Shared mutable state between modulesHidden temporal couplingPass explicitly or use event system
God module (20+ exports)Everything depends on itExtract cohesive sub-modules

Module SSOT (Single Source of Truth)

Every concept, constant, type, or configuration value MUST have exactly one canonical owner module.

ConceptCanonical OwnerConsumers Do
Shared types / interfacestypes/ or contracts/ moduleImport, never redefine
Constants / magic valuesDomain-specific constants moduleImport the constant
Config / envCentral config moduleImport resolved values
Validation schemasBoundary module (API entry)Import schema, don't recreate
API contractsAPI layerImport types from API module
BannedWhyFix
Duplicating a type/constant in a consumerTwo sources of truth → driftImport from canonical owner
"Local copy for convenience"Convenience becomes divergenceImport the original
Re-deriving a value that has a canonical sourceSilent inconsistencyImport the derived value or computation

Deep Modules and Seams

Use this vocabulary when deciding whether an abstraction earns its keep:

TermMeaning
ModuleA cohesive unit with a named responsibility and public interface
InterfaceThe small surface consumers depend on
ImplementationThe hidden work behind that surface
DepthLarge useful behavior hidden behind a small interface
SeamA boundary where alternative implementations are real or likely
AdapterCode translating one external shape into the module's interface
LeverageHow much change the abstraction absorbs for its callers
LocalityHow close related behavior stays to its owning concept

Frontend depth means small props/events hiding complex rendering, state management,

data transformation, or integration behavior. One adapter usually means hypothetical

indirection; two adapters, or a near-term second adapter, is evidence of a real seam.

Do not expose internals only for tests; test through the public interface or add a

boundary-owned diagnostic hook with production value.

---

2. Circular Dependency Detection & Prevention

Severity: CRITICAL

Rule: No circular dependency may exist between modules. Every detected cycle MUST be resolved before merge.

Required Agent Workflow

PhaseRequired ActionPass Condition
1. DetectRun ecosystem-specific detection commandCommand exits clean (no cycles reported)
2. ClassifyIdentify cycle type: direct A<->B or transitive A->B->C->AType documented
3. AnalyzeDetermine root cause: shared type? callback? event?Root interface identified
4. FixApply appropriate fix strategy (see references/)Detection command passes
5. VerifyRe-run detection + confirm no regressionsZero cycles in report

Detection commands are ecosystem-specific. See references/circular-dependencies.md

for command templates, examples, and verification details.

Banned Patterns

Banned PatternWhy BannedRequired Fix
A imports B, B imports A (direct cycle)Compile failures, bundler issues, test fragilityExtract shared interface to C
Type-only cycle (import type both ways)Still signals wrong boundaryMove shared types to types/ module
Barrel re-export creating hidden cycleIndex file masks real dependency graphRemove barrel, use direct imports
Lazy import to "break" cycle (require() inside function)Hides the problem, breaks tree-shakingFix the architecture, not the symptom
"It works in runtime" as justificationFragile, bundler-dependent, blocks refactoringMust pass static analysis
Circular via test file importing source that imports test helperTest infra leaking into production graphIsolate test helpers in __test_utils__/

Fix Guidance

SituationPreferred Fix Two modules share typesExtract types.ts or contracts/ module both import Module A calls back into BDependency inversion: A defines interface, B implements
Event producer and consumer import each otherEvent bus / mediator pattern
Circular at package level (monorepo)Introduce shared or contracts package
UI component imports its containerLift shared state to context or prop drilling
Service layer cycleExtract orchestrator service or use events

---

3. Implicit Coupling Taxonomy

Severity: CRITICAL

Rule: Every coupling instance in a code review MUST be classified by type. Coupling severity determines whether the code can merge.

Coupling Types (ordered by severity, worst first)

#TypeDefinitionSeverityFix Pattern
1ContentModule reaches into another's internalsCRITICALExpose via public API/method
2CommonMultiple modules share global mutable stateCRITICALDependency injection, immutable config
3ControlModule passes flag to control another's logicHIGHPolymorphism, strategy pattern
4StampModule passes large struct when only one field neededHIGHPass only needed fields
5ExternalMultiple modules depend on same external formatHIGHSingle parser module, shared schema
6TemporalModules must execute in specific orderMEDIUMMake ordering explicit (state machine, builder)
7SequentialOutput of A is input of BLOWDocument the contract, validate at boundary
8FunctionalModules share a well-defined interfaceLOWThis is GOOD coupling — the target state

See references/coupling-taxonomy.md for examples, detection signals,

refactoring patterns, and banned review responses.

Review Decision Matrix

SeverityMerge?Action Required
CRITICAL (Content, Common)BLOCKMust refactor before merge
HIGH (Control, Stamp, External)BLOCK unless justifiedRequire tech-debt ticket if merged
MEDIUM (Temporal)Allowed with documentationAdd ordering comments or state assertions
LOW (Sequential, Functional)ALLOWEDNo action needed

---

4. Boundary-Only Defensive Programming

Severity: CRITICAL

Rule: Validation and defensive checks belong ONLY at system boundaries. Internal module boundaries MUST trust their callers.

Ownership split: placement (validation happens at the boundary, nowhere else) is owned

by this section; what the validation schema enforces (content/policy) is owned by

dev-security §1.

Validation Location Matrix

LocationValidate?RationaleExample
HTTP/API controller inputYESUntrusted external dataZod schema, JSON schema
CLI argument parsingYESUntrusted user inputyargs/commander validation
File system readsYESExternal data, may be corruptParse + validate structure
Database query resultsYES at ORM-untyped/raw-query boundaries (shape only); NO when a typed schema/ORM guarantees the shapeUntyped results may drift; typed guarantees are trusted (see Banned Patterns)Check raw-query nulls/shape; trust typed ORM results
Message queue consumerYESCross-process boundaryValidate message schema
Internal function paramsNOCaller is trusted code you controlType system handles this
Private method argsNOSame module, same authorRedundant — types suffice
Service-to-service in same processNOIn-process calls share type systemInterface contracts handle this

Banned Patterns

Banned PatternWhy BannedFix
if (!param) throw at start of every internal functionRedundant with type system, clutters codeRemove — let TypeScript/types enforce
Runtime type checks in typed language internalsDuplicates compiler work, adds noiseTrust the type system
assert(x !== null) in module-internal codeIf x can be null, fix the type; if it can't, the assert is noiseFix type signature or remove assert
Validation in domain entity constructor for in-process callersEntities should be created from validated dataValidate at boundary, trust domain layer
Try-catch around every internal callHides bugs, makes debugging harderLet errors propagate, catch at boundary
Null checks after DB query that schema guarantees NOT NULLDistrusts your own schemaTrust schema, validate at migration time

Allowed Defensive Checks (Exceptions)

SituationWhy AllowedPattern
Security-critical path (auth, crypto)Defense in depth required by policyDouble-check even internal calls
Data from deserialization (JSON.parse)Runtime data, types lostValidate with schema (Zod/io-ts)
Plugin/extension boundaryThird-party code, untrustedValidate at plugin interface
Across deployment boundary (microservice call)Network = system boundaryFull validation required
Feature flags / A-B test pathsRuntime variation, not type-safeGuard with runtime check

Fix Guidance

SmellDiagnosisFix
10+ if (!x) throw in one fileOver-defensive internal codeRemove guards, fix types
Every function starts with parameter validationBoundary confusionMove all validation to entry point
try { } catch { return null } everywhereError suppressionLet errors bubble, handle at boundary
typeof x === 'string' in TypeScriptDistrusting compilerRemove, or fix the type to be accurate
Same validation in controller AND serviceDuplicated boundaryValidate once at controller, service trusts

---

5. Barrel/Re-export Discipline

Severity: HIGH

Rule: Barrel files (index.ts/index.js/__init__.py) are ONLY allowed at public boundaries — package APIs and feature public boundary exports. Internal convenience barrels are banned.

Barrel Policy Matrix

ContextBarrel Allowed?Rationale
Library/package public API (packages/ui/index.ts)YESSingle entry point for consumers
Framework plugin entry (plugin/index.ts)YESPlugin contract requires it
Feature public boundary export (features/auth/index.ts as the feature's single external entry)YESPublic Boundary Export (dev-scaffolding §1); external consumers import the boundary
Feature internal convenience barrel (re-exporting siblings for imports inside the feature)NOHides internal structure, breaks tree-shaking
Utility folder (utils/index.ts)NOCreates coupling magnet
Component folder re-exporting siblingsNODirect imports are clearer
Monorepo package boundary (@org/shared/index.ts)YESCross-package contract

See references/barrel-discipline.md for import examples, tree-shaking

details, ESLint enforcement, and the safe barrel template.

---

6. Review Integration

Architecture Review Checklist (for code-reviewer)

When reviewing any PR that adds/modifies module structure, verify:

  • [ ] No new circular dependencies — run madge --circular or equivalent
  • [ ] Layer violations — no upward imports (infra->domain OK, domain->infra BLOCKED)
  • [ ] Coupling classified — any new cross-module dependency has coupling type identified
  • [ ] No CRITICAL/HIGH coupling without justification — Content/Common/Control coupling blocked
  • [ ] Barrel files — no new internal barrels; existing public barrels use named exports only
  • [ ] Validation placement — new validation is at system boundary, not internal functions
  • [ ] Module size — new/modified modules under 400 LOC
  • [ ] No "utils" growth — shared code placed in domain-specific module, not catch-all utils
  • [ ] Dependency direction — dependencies point inward toward Domain: outer layers depend on inner layers (Presentation/Application/Infrastructure -> Domain), and inner layers never import outward
  • [ ] No lazy-import hacks — no require() inside function body to hide circular deps

Automated Enforcement (CI Recommendations)

CheckToolCI Command
Layer/dependency rules (preferred CI gate)dependency-cruisernpx depcruise --validate .dependency-cruiser.cjs src/
Import boundarieseslint-plugin-boundariesESLint with boundaries config
Circular deps (quick visualization)madgenpx madge --circular --extensions ts,tsx src/ && echo "OK"
Barrel abuseBiome noBarrelFile or ESLint no-restricted-importspattern for internal index files
Dead files/exports/depsknipnpx knip
Monorepo package consistencysherifnpx sherif
Module sizecustom script`find src -name '*.ts' -exec wc -l {} + \awk '$1 > 400'`

Tool roles verified 2026-07-02 (Sources: references/circular-dependencies.md).

---

Cross-Skill References

  • Observability: Trace emission at module boundaries is a production/long-lived-runtime concern (DEFAULT there, not universal). See dev-backend/references/core/observability.md for the canonical OTel setup.
  • Security: Validate at every trust/process/external boundary (HTTP entry, IPC, file/CLI input, third-party responses). Intra-trust-domain module calls follow §4 boundary-only defense — do not re-validate already-trusted data. See dev-security/SKILL.md for input validation and auth patterns.

---

Quick Decision Trees

"Should I create a new module?"

Does the code serve a distinct responsibility? 
  NO  -> Keep in existing module
  YES -> Is it used by 3+ other modules?
    NO  -> Co-locate with primary consumer
    YES -> Create dedicated module with clear interface

"Is this coupling acceptable?"

What type? (see taxonomy above)
  Content/Common -> BLOCK, refactor now
  Control/Stamp/External -> BLOCK unless tech-debt ticket created
  Temporal -> ALLOW with documentation
  Sequential/Functional -> ALLOW

"Where does this validation go?"

Is the data source external (HTTP, file, queue, DB, user input)?
  YES -> Validate here (boundary)
  NO  -> Is this a security-critical path?
    YES -> Validate (defense in depth)
    NO  -> Trust the type system, no validation needed