Guard
불변식(invariant) 계층 — Mandu 정적 분석기가 강제하는 모든 규칙, 각 규칙의 의미, 위반 해결 방법.
On this page
Guard
Guard는 에이전트가 당신의 레포에 커밋하면서도 불태워먹지 않을 수 있는 이유 입니다. 빌드 타임(그리고 dev에서는 매 저장마다)에 실행되는 정적 분석기로, import를 훑고 아키텍처 계약을 위반하는 것은 거부합니다.
이 페이지는 불변식 카탈로그입니다. Guard가 소리를 지른다면, 해결책은 여기 있습니다.
Guard가 실제로 하는 일
감시 대상 파일(.ts, .tsx, .js, .jsx, .mjs, .cjs)마다 Guard는 모든
import, import(), require() 문을 추출하고(출처: guard/analyzer.ts:64-132),
각각을 레이어로 해석한 뒤 활성화된 프리셋의 레이어 그래프와 대조합니다.
여섯 가지 위반 패밀리가 발생할 수 있습니다 (출처: guard/types.ts:191-197):
| 타입 | 의미 |
|---|---|
layer-violation |
import가 금지된 레이어 경계를 넘음 |
circular-dependency |
A → B → A 감지 |
cross-slice |
같은 레이어, 다른 슬라이스를 직접 import |
deep-nesting |
슬라이스의 public API를 우회해 깊숙이 접근 |
file-type |
스캔 대상 레이어 안에 .js 또는 .jsx 파일 존재 |
invalid-shared-segment |
src/shared/ 하위 경로가 허용 목록에 없음 |
그 위에 FS-routes, contract/slot 규칙이 추가로 겹칩니다 (아래 참고).
레이어 모델
각 레이어는 { name, pattern, canImport, description } 튜플입니다 (출처:
guard/types.ts:116-128). Mandu 프리셋은 client/widgets, client/features,
server/infra, shared/contracts, shared/utils/client, shared/utils/server
같은 레이어를 정의합니다. 파일은 각 레이어의 glob 패턴과 경로가 매칭되어
레이어에 할당됩니다 (출처: guard/analyzer.ts:152-166).
import는 다음 조건에서만 허용됩니다:
importedLayer === importingLayer
OR
importedLayer ∈ importingLayer.canImport
이 검사 전에 두 가지 예외가 먼저 적용됩니다:
shared/env는 서버 전용. 레이어가client/로 시작하는 파일에서shared/env를 import하면 심각도error로 거부됩니다 (출처:guard/validator.ts:277-287).- 같은 레이어, 다른 슬라이스 — 레이어의 public API로 해석되는 import는
허용; 직접 깊이 들어가는 import는
cross-slice발동.
불변식 카탈로그
layer-violation
발동 조건: importing 레이어의 canImport 리스트에 imported 레이어가 없음.
// src/client/widgets/chart/Chart.tsx
import { db } from "@/server/infra/db"; // ✗ client/widgets는 server/infra import 불가
해결: 서버 사이드 작업을 API 라우트 뒤로 옮기거나, src/shared/contracts
아래에 공유 contract/schema를 노출하세요.
circular-dependency
발동 조건: A.ts가 B.ts를 import하고 B.ts가 A.ts를 import
(직접 사이클 감지, guard/validator.ts:624-685).
해결: 공유되는 부분을 제3의 모듈로 추출하거나, 의존성 역전으로 사이클을 끊으세요.
cross-slice
발동 조건: 같은 레이어, 다른 슬라이스, 직접 import. 예: 슬라이스 chart/의
위젯이 슬라이스 table/의 내부를 import.
해결: 슬라이스의 public API(대개 index.ts)를 통해 import하거나, 공유
조각을 두 슬라이스 모두 import 가능한 레이어로 옮기세요.
deep-nesting
발동 조건: import가 슬라이스의 public API를 건너뛰어 깊숙이 접근
(예: @/client/widgets/chart/internal/helpers).
해결: 필요한 심볼을 슬라이스의 index.ts에서 export하세요.
file-type
발동 조건: 파일 확장자가 .js 또는 .jsx (출처: guard/validator.ts:233-237).
Guard FAIL src/client/utils/legacy.js:1
Rule: TypeScript Only
Fix: .ts 또는 .tsx로 리네임하고 타입을 추가하세요.
해결: 리네임하고 타이핑. 예외 없음.
invalid-shared-segment
발동 조건: src/shared/ 아래 파일이 허용 목록에 없는 세그먼트에 있음
(출처: guard/validator.ts:155-188).
허용 구조:
| 경로 | 용도 |
|---|---|
src/shared/contracts/ 또는 src/shared/contracts.ts |
서버/클라이언트 공유 API contract |
src/shared/schema/ 또는 src/shared/schema.ts |
Zod 스키마, DB 스키마 |
src/shared/types/ 또는 src/shared/types.ts |
순수 TypeScript 타입 |
src/shared/utils/client/ |
클라이언트에서 안전한 유틸 |
src/shared/utils/server/ |
Node/Bun API가 필요한 유틸 |
src/shared/env/ 또는 src/shared/env.ts |
환경 설정 (서버 전용 판독기) |
그 외 — src/shared/components/, src/shared/lib/, src/shared/helpers/ — 는 거부됩니다.
해결: 올바른 버킷을 고르세요. 맞는 게 없다면 shared에 있을 파일이 아닙니다.
FS-routes 규칙
app/** 하위 파일에만 적용되는 추가 불변식 (출처: guard/validator.ts:392-498):
| 파일 | import 가능 레이어 (fsRoutes 설정이 있을 때) |
|---|---|
app/**/page.tsx |
fsRoutes.pageCanImport 레이어만 |
app/**/layout.tsx |
fsRoutes.layoutCanImport 레이어만 |
app/**/route.ts |
fsRoutes.routeCanImport 레이어만 |
그리고 설정과 무관하게 항상 적용되는 강제 규칙:
noPageToPage:page.tsx는 다른page.tsx를 import할 수 없음 — 상대 경로, 별칭,app/경유 모두 불가 (출처:guard/validator.ts:548-583).
심각도와 설정
각 위반 타입에는 설정 가능한 심각도가 있습니다 (출처: guard/types.ts:34-47):
// mandu.config.ts
export default {
guard: {
severity: {
layerViolation: "error", // 기본값
circularDependency: "warn", // 기본값
deepNesting: "info", // 기본값
crossSliceDependency: "warn", // 기본값
fileType: "error", // 기본값
invalidSharedSegment: "error", // 기본값
},
},
};
"error"는 빌드 실패. "warn"은 리포터에 노출되지만 실패하지 않음.
"info"는 로그만 남김.
에러 코드 → 해결 매핑
| 규칙 ID | 패밀리 | 표준 해결 |
|---|---|---|
layer-violation |
imports | 타겟 레이어를 소스 레이어의 canImport에 추가하거나 허용된 레이어 경유 |
circular-dependency |
imports | 공유 코드를 제3의 모듈로 추출 |
cross-slice |
imports | 슬라이스의 public API를 통해 import |
deep-nesting |
imports | 심볼을 index.ts에서 export |
file-type |
file | .js/.jsx를 .ts/.tsx로 리네임 |
invalid-shared-segment |
file | 허용된 src/shared/* 버킷으로 이동 |
FORBIDDEN_IMPORT_IN_GENERATED |
codegen | 생성된 파일에서 금지된 fs/child_process 등 import 제거 |
SLOT_MISSING_DEFAULT_EXPORT |
slots | export default Mandu.filling(...) 추가 |
SLOT_MISSING_FILLING_PATTERN |
slots | 핸들러를 Mandu.filling()으로 래핑 |
SLOT_ZOD_DIRECT_IMPORT |
slots | 스키마를 src/shared/schema에서 import (zod 직접 X) |
ISLAND_FIRST_INTEGRITY |
island | page 옆에 .island.tsx 생성; page.tsx에 island() import 금지 |
CLIENT_MODULE_NOT_FOUND |
island | 라우트의 clientModule 경로가 없음 — spec 재생성 또는 파일 추가 |
CONTRACT_NOT_FOUND |
contract | spec이 가리키는 contract 파일 생성 |
CONTRACT_METHOD_NOT_IMPLEMENTED |
contract | 누락된 메서드를 slot에 구현 |
안티패턴
이건 하지 마세요.
- 빌드를 통과시키려고 Guard 비활성화. 위반이 곧 신호입니다. import를 고치거나 파일을 옮기세요 — 심각도를 낮추지 마세요.
- 허용 목록에 없는 새
src/shared/utils/버킷 만들기. 허용 목록은 의도적으로 짧습니다. 다른 카테고리가 필요하다면 아키텍처 대화를 먼저 하세요.- island 관련 에러를 덮으려고
"use client"추가. island 계약은 파일명 AND 지시어를 봅니다. 어느 한쪽이 다른 쪽을 대신하지 못합니다.
🤖 에이전트 프롬프트
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/guard.ko to my project.
Summary of the page:
Guard는 빌드 타임 정적 분석기. 여섯 가지 불변식 패밀리를 강제한다: layer-violation, circular-dependency, cross-slice, deep-nesting, file-type, invalid-shared-segment. 또한 FS-routes 규칙(page/layout/route import whitelist)과 contract 규칙(slot/contract export)도 실행한다. 심각도(severity)는 설정 가능하며 'error'는 빌드를 실패시킨다. 각 위반은 filePath, line, column, ruleName, ruleDescription, suggestions를 포함한다.
Required invariants — must hold after your changes:
- 스캔 대상 레이어 내의 .js 또는 .jsx 파일은 file-type 위반이다
- src/shared는 {contracts, schema, types, utils/client, utils/server, env}로 제한된다. 다른 세그먼트는 invalid-shared-segment
- 클라이언트 레이어 파일은 shared/env를 import할 수 없다 (서버 전용)
- page.tsx는 다른 page.tsx를 import할 수 없다 (noPageToPage 규칙)
- 레이어 import는 importing 레이어의 canImport 리스트에 있어야 한다
- 같은 레이어 내 다른 슬라이스 간 직접 import는 public API 경유가 아닌 한 거부된다
Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.
For Agents
Guard는 빌드 타임 정적 분석기. 여섯 가지 불변식 패밀리를 강제한다: layer-violation, circular-dependency, cross-slice, deep-nesting, file-type, invalid-shared-segment. 또한 FS-routes 규칙(page/layout/route import whitelist)과 contract 규칙(slot/contract export)도 실행한다. 심각도(severity)는 설정 가능하며 'error'는 빌드를 실패시킨다. 각 위반은 filePath, line, column, ruleName, ruleDescription, suggestions를 포함한다.
- 스캔 대상 레이어 내의 .js 또는 .jsx 파일은 file-type 위반이다
- src/shared는 {contracts, schema, types, utils/client, utils/server, env}로 제한된다. 다른 세그먼트는 invalid-shared-segment
- 클라이언트 레이어 파일은 shared/env를 import할 수 없다 (서버 전용)
- page.tsx는 다른 page.tsx를 import할 수 없다 (noPageToPage 규칙)
- 레이어 import는 importing 레이어의 canImport 리스트에 있어야 한다
- 같은 레이어 내 다른 슬라이스 간 직접 import는 public API 경유가 아닌 한 거부된다