boundary-checker
Check FSD import boundary rules and detect violations
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Menu
Check FSD import boundary rules and detect violations
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Baseado na classificação ocupacional SOC
Check and install OpenAPI Sync MCP server dependency
Best practice templates for API layer scaffolding
Detect and analyze FSD layer structure in a project
Generate FSD-compliant slice boilerplate with pattern matching
Manage analysis cache for incremental FSD validation
Manage OpenAPI spec cache and implementation state for efficient diff-based sync
| name | boundary-checker |
| description | Check FSD import boundary rules and detect violations |
FSD Import 경계 규칙을 검사하고 위반 사항을 감지합니다.
For each file in the project:
Read file content
Extract import statements using regex:
import\s+.*?\s+from\s+['"]([^'"]+)['"]
SECURITY NOTE: Use non-greedy .*? instead of greedy .* to prevent ReDoS
(Regular Expression Denial of Service) attacks from maliciously crafted import statements.
Alternative safer patterns:
# More specific pattern (recommended for production)
import\s+(?:\{[^}]*\}|[^'"]+)\s+from\s+['"]([^'"]+)['"]
# Or use multiple specific patterns:
import\s+\{[^}]*\}\s+from\s+['"]([^'"]+)['"] # Named imports
import\s+\w+\s+from\s+['"]([^'"]+)['"] # Default imports
import\s+\*\s+as\s+\w+\s+from\s+['"]([^'"]+)['"] # Namespace imports
Categorize imports:
@entities/user../model/typesreact, lodashFor each import:
Layer Hierarchy (top to bottom):
app (6)
pages (5)
widgets (4)
features (3)
entities (2)
shared (1)
Rule: Lower number cannot import from higher number
For each import:
For sliced layers (pages, widgets, features, entities):
Rule: Slices in the same layer cannot import from each other
For each import within a sliced layer:
Exception: @x/ cross-reference pattern is allowed.
The @x/ pattern enables controlled cross-slice imports within the same layer. See "REFERENCE: @x/ Cross-Reference Pattern" section for details.
When an @x/ import is detected:
1. Check @x/ structure exists in source slice
2. Verify the re-export is properly set up
3. If structure invalid → Warning (not error)
Valid @x/ usage:
// In: entities/user/@x/order.ts
export { Order, type OrderStatus } from '@entities/order';
// In: entities/user/model/index.ts
import { Order } from '../@x/order'; // ✅ Valid
Invalid @x/ usage (direct import disguised):
// In: entities/user/model/index.ts
import { Order } from '@entities/order'; // ❌ E201: Cross-slice import
Rule: External imports must go through public API (index.ts)
For each import:
/index or slice root/model/types) → VIOLATIONinterface Violation {
code: string;
type: 'error' | 'warning';
message: string;
location: {
file: string;
line: number;
};
source: {
layer: string;
slice?: string;
};
target: {
layer: string;
slice?: string;
path: string;
};
suggestion: string;
}
function checkBoundaries(layerMap, files):
violations = []
for file in files:
imports = extractImports(file)
sourceLayer = getLayerFromPath(file.path)
sourceSlice = getSliceFromPath(file.path)
sourceSlicePath = getSlicePath(file.path) // Full path: e.g., "src/entities/user"
for imp in imports:
targetPath = resolveImport(imp, file.path)
targetLayer = getLayerFromPath(targetPath)
targetSlice = getSliceFromPath(targetPath)
// Check layer hierarchy
if layerRank(sourceLayer) < layerRank(targetLayer):
violations.push({
code: 'E203',
type: 'error',
message: 'Forbidden higher-layer import',
...
})
// Check cross-slice
if sourceLayer == targetLayer and sourceSlice != targetSlice:
if not isXReference(imp):
violations.push({
code: 'E201',
type: 'error',
message: 'Forbidden cross-slice import',
...
})
else:
// Validate @x/ structure (Step 5.5)
xRefFile = extractXRefFile(imp) // Extract file name from @x/ import
xRefResult = validateXReference(sourceSlicePath, xRefFile)
if not xRefResult.valid:
violations.push({
code: 'W105',
type: 'warning',
message: 'Invalid @x/ structure',
detail: xRefResult.warning,
...
})
// Check public API
if isInternalPath(targetPath):
violations.push({
code: 'E202',
type: 'error',
message: 'Public API sidestep',
...
})
return violations
| Code | Type | Description |
|---|---|---|
| E201 | Error | Cross-slice import (same layer) |
| E202 | Error | Public API sidestep |
| E203 | Error | Higher-layer import |
| E204 | Error | Missing public API |
| E205 | Error | Circular dependency |
| W101 | Warning | Inconsistent naming |
| W102 | Warning | Unused export |
| W103 | Warning | Missing segment |
| W105 | Warning | Invalid @x/ structure |
The @x/ (cross-reference) pattern is an advanced FSD pattern that enables controlled imports between slices within the same layer. This is an exception to the normal "no cross-slice imports" rule.
Use @x/ when:
entities/
├── user/
│ ├── @x/ # Cross-reference folder
│ │ └── order.ts # Re-exports from order entity
│ ├── model/
│ │ ├── index.ts
│ │ └── useUserOrders.ts # Can import from ../@x/order
│ ├── ui/
│ └── index.ts
├── order/
│ ├── model/
│ │ └── types.ts # Order, OrderStatus types
│ └── index.ts # Exports Order, OrderStatus
The @x/ files are re-exports only. They don't contain logic.
// entities/user/@x/order.ts
// Re-export only what user entity needs from order entity
export { Order } from '@entities/order';
export type { OrderStatus, OrderItem } from '@entities/order';
Within the same slice, use relative imports to @x/:
// entities/user/model/useUserOrders.ts
// ✅ CORRECT: Import via local @x/ re-export
import { Order, OrderStatus } from '../@x/order';
// ❌ WRONG: Direct cross-slice import
import { Order } from '@entities/order';
/**
* Check if an import uses the @x/ cross-reference pattern.
* @param importPath - The import path to check
* @returns true if this is an @x/ reference
*/
function isXReference(importPath: string): boolean {
// Check for @x/ in relative path
// Patterns: '/@x/', '../@x/', './@x/'
if (importPath.includes('/@x/') || importPath.includes('../@x/') || importPath.includes('./@x/')) {
return true;
}
return false;
}
/**
* Validate that @x/ structure is properly set up.
* @param sourceSlicePath - Path to the slice using @x/
* @param xRefFile - The @x/ file being imported
* @returns Validation result
*/
function validateXReference(
sourceSlicePath: string,
xRefFile: string
): { valid: boolean; warning?: string } {
// Check @x/ folder exists in source slice
const xRefPath = `${sourceSlicePath}/@x`;
if (!exists(xRefPath)) {
return {
valid: false,
warning: `Missing @x/ folder in ${sourceSlicePath}`
};
}
// Check the specific @x/ file exists
const xRefFilePath = `${xRefPath}/${xRefFile}.ts`;
if (!exists(xRefFilePath)) {
return {
valid: false,
warning: `Missing @x/ file: ${xRefFilePath}`
};
}
// Check file contains only re-exports (no logic)
const content = read(xRefFilePath);
if (!isReExportOnly(content)) {
return {
valid: false,
warning: `@x/ file should only contain re-exports: ${xRefFilePath}`
};
}
return { valid: true };
}
/**
* Check if file content is re-export only (no logic).
*/
function isReExportOnly(content: string): boolean {
// Should only have export statements, no function/class definitions
const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('//'));
for (const line of lines) {
// Valid: export { X } from 'Y'
// Valid: export type { X } from 'Y'
// Invalid: function, const, class, etc.
if (!line.match(/^export\s+(type\s+)?{.*}\s+from\s+/)) {
return false;
}
}
return true;
}
// entities/order/model/types.ts
export interface Order {
id: string;
userId: string;
items: OrderItem[];
status: OrderStatus;
}
export type OrderStatus = 'pending' | 'processing' | 'completed' | 'cancelled';
// entities/order/index.ts
export { Order, OrderStatus } from './model/types';
// entities/user/@x/order.ts
export { Order } from '@entities/order';
export type { OrderStatus } from '@entities/order';
// entities/user/model/useUserOrders.ts
import { Order, OrderStatus } from '../@x/order'; // ✅
export function useUserOrders(userId: string) {
const [orders, setOrders] = useState<Order[]>([]);
// ...
}
If file cannot be parsed:
If import path cannot be resolved: