slice-generator
Generate FSD-compliant slice boilerplate with pattern matching
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Generate FSD-compliant slice boilerplate with pattern matching
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
| name | slice-generator |
| description | Generate FSD-compliant slice boilerplate with pattern matching |
FSD 규격에 맞는 슬라이스 보일러플레이트를 생성합니다. 프로젝트 패턴을 학습하여 일관된 스타일로 생성합니다.
.. (path traversal attack)/ or \ (path separator injection). (hidden file creation)^[a-zA-Z][a-zA-Z0-9-_]*$--segments flag:
.. (path traversal attack)/ or \ (path separator injection). (hidden file creation)^[a-zA-Z][a-zA-Z0-9-_]*$function validateSegmentName(segment: string): { valid: boolean; error?: string } {
if (segment.includes('..')) {
return { valid: false, error: 'E305: Segment contains ".." (path traversal blocked)' };
}
if (segment.includes('/') || segment.includes('\\')) {
return { valid: false, error: 'E305: Segment contains path separators' };
}
if (segment.startsWith('.')) {
return { valid: false, error: 'E305: Hidden segment not allowed' };
}
if (!/^[a-zA-Z][a-zA-Z0-9-_]*$/.test(segment)) {
return { valid: false, error: 'E305: Invalid segment name format' };
}
return { valid: true };
}
// Validate all segments before creation
for (const segment of options.segments) {
const result = validateSegmentName(segment);
if (!result.valid) {
throw new Error(result.error);
}
}
Action: Read .fsd-architect.json and extract layer settings
1. Use Read tool to load .fsd-architect.json
2. If not exists:
→ Warn user: "Config not found. Run /fsdarch:init first"
→ Use DEFAULT_CONFIG as fallback
3. Extract from config:
- srcDir (default: 'src')
- layerAliases (default: {} - no aliases)
- patterns (for consistency)
Read command:
Read: .fsd-architect.json
Fallback config:
const DEFAULT_CONFIG: FsdConfig = {
srcDir: 'src',
layerAliases: {}, // No aliases = use layer names as-is
patterns: DEFAULT_PATTERNS
};
Config structure:
interface FsdConfig {
srcDir: string;
layerAliases?: {
app?: string; // e.g., 'core' for Next.js
pages?: string; // e.g., 'views' for Next.js
};
patterns?: PatternConfig;
}
Use layer-detector skill:
If no patterns found (new/empty project), use defaults:
const DEFAULT_PATTERNS = {
naming: 'kebab-case',
segments: ['ui', 'model', 'api', 'lib'],
indexFiles: true,
styleGuide: {
quotes: 'single',
semicolons: true,
componentStyle: 'function', // vs 'arrow'
typeStyle: 'interface', // vs 'type'
indentation: 2,
}
};
REQUIRED: If loadPatterns() returns empty, apply DEFAULT_PATTERNS instead of failing.
For the same layer, analyze existing slices:
Based on detected naming convention:
| Input | kebab-case | PascalCase | camelCase |
|---|---|---|---|
| user profile | user-profile | UserProfile | userProfile |
| UserProfile | user-profile | UserProfile | userProfile |
Action: Create slice directory using resolved path with layerAliases
1. Load config (from Step 1.5)
2. Verify parent layer directory exists using getLayerPath():
layerPath = getLayerPath(layer, config)
→ Use Glob to check: "{layerPath}/"
→ If not exists, create it or error
3. Compute slice path using resolveSlicePath():
slicePath = resolveSlicePath(layer, sliceName, config)
4. Create slice directory
CRITICAL: Apply layerAliases
// Step 2: Verify layer directory exists
const layerPath = getLayerPath(layer, config); // Uses utility function
// Glob: "{layerPath}/" to verify
// Step 3: Compute full slice path
const slicePath = resolveSlicePath(layer, sliceName, config);
Example paths:
| Layer | getLayerPath() | resolveSlicePath() |
|---|---|---|
| features | src/features | src/features/auth |
| app (aliased→core) | src/core | src/core/providers |
| pages (aliased→views) | src/views | src/views/home |
Glob command to verify parent (using getLayerPath):
Glob: "{getLayerPath(layer, config)}/"
# e.g., "src/core/" for Next.js app layer with alias
For each configured segment:
// index.ts
export { {Name}Component } from './{Name}Component';
// {Name}Component.tsx
import React from 'react';
interface {Name}ComponentProps {
className?: string;
children?: React.ReactNode;
}
export function {Name}Component({ className, children }: {Name}ComponentProps) {
return (
<div className={className}>
{children}
</div>
);
}
// index.ts
export * from './types';
export { use{Name} } from './use{Name}';
// types.ts
export interface {Name} {
id: string;
createdAt: string;
updatedAt: string;
}
// use{Name}.ts
import { useState, useCallback } from 'react';
import type { {Name} } from './types';
export function use{Name}(initialData?: {Name} | null) {
const [data, setData] = useState<{Name} | null>(initialData ?? null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const reset = useCallback(() => {
setData(null);
setError(null);
}, []);
return { data, setData, isLoading, setIsLoading, error, setError, reset };
}
// index.ts
export { {name}Api } from './{name}Api';
// {name}Api.ts
import type { {Name} } from '../model';
const BASE_URL = '/api/{name}';
export const {name}Api = {
getAll: async (): Promise<{Name}[]> => {
const response = await fetch(BASE_URL);
if (!response.ok) throw new Error('Failed to fetch {name} list');
return response.json();
},
getById: async (id: string): Promise<{Name}> => {
const response = await fetch(`${BASE_URL}/${id}`);
if (!response.ok) throw new Error('Failed to fetch {name}');
return response.json();
},
create: async (data: Omit<{Name}, 'id' | 'createdAt' | 'updatedAt'>): Promise<{Name}> => {
const response = await fetch(BASE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error('Failed to create {name}');
return response.json();
},
update: async (id: string, data: Partial<{Name}>): Promise<{Name}> => {
const response = await fetch(`${BASE_URL}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error('Failed to update {name}');
return response.json();
},
delete: async (id: string): Promise<void> => {
const response = await fetch(`${BASE_URL}/${id}`, { method: 'DELETE' });
if (!response.ok) throw new Error('Failed to delete {name}');
},
};
// index.ts
// Export utilities here
// index.ts
// Public API - export only what should be accessible from outside
// UI Components
export { {Name}Component } from './ui';
// Model (types, hooks, store)
export type { {Name} } from './model';
export { use{Name} } from './model';
// API (if needed externally)
// export { {name}Api } from './api';
interface GenerationResult {
slicePath: string;
files: {
path: string;
created: boolean;
}[];
patterns: {
naming: string;
segments: string[];
};
}
Resolves the actual filesystem path for a slice, applying layerAliases from config.
/**
* Resolve slice path with layer aliases applied.
*
* @param layer - FSD layer name (app, pages, widgets, features, entities)
* @param sliceName - Name of the slice to create
* @param config - Loaded .fsd-architect.json config
* @returns Full path to create the slice directory
*
* @example
* // Standard project (no aliases)
* resolveSlicePath('features', 'auth', { srcDir: 'src', layerAliases: {} })
* // → 'src/features/auth'
*
* // Next.js project (with aliases)
* resolveSlicePath('app', 'providers', {
* srcDir: 'src',
* layerAliases: { app: 'core', pages: 'views' }
* })
* // → 'src/core/providers'
*
* // Next.js hybrid (pages layer)
* resolveSlicePath('pages', 'home', {
* srcDir: 'src',
* layerAliases: { app: 'core', pages: 'views' }
* })
* // → 'src/views/home'
*/
function resolveSlicePath(
layer: string,
sliceName: string,
config: FsdConfig
): string {
// Apply layer alias if configured, otherwise use layer name as-is
const layerDir = config.layerAliases?.[layer] ?? layer;
// Build full path: srcDir/layerDir/sliceName
return `${config.srcDir}/${layerDir}/${sliceName}`;
}
Get the actual directory path for a layer (for checking existence).
/**
* Get the actual directory path for an FSD layer.
*
* @param layer - FSD layer name
* @param config - Loaded config
* @returns Directory path for the layer
*/
function getLayerPath(layer: string, config: FsdConfig): string {
const layerDir = config.layerAliases?.[layer] ?? layer;
return `${config.srcDir}/${layerDir}`;
}
function generateSlice(layer, sliceName, options):
// Step 1: Validate
if layer not in [pages, widgets, features, entities]:
throw E302('Invalid layer')
// Step 1.5: Load config
config = loadConfig('.fsd-architect.json')
if not config:
config = DEFAULT_CONFIG
warn("Using default config. Run /fsdarch:init for full setup.")
// Step 5: Verify layer directory exists (using getLayerPath)
layerPath = getLayerPath(layer, config)
if not exists(layerPath):
mkdir(layerPath) // Create layer directory if missing
// Step 5: Resolve slice path with aliases (CRITICAL FIX)
slicePath = resolveSlicePath(layer, sliceName, config)
if exists(slicePath):
throw E301('Slice exists')
// Load patterns
patterns = loadPatterns()
existingSlices = findExistingSlices(layer)
styleGuide = analyzeStyle(existingSlices)
// Transform name
transformedName = transformName(sliceName, patterns.naming)
// Generate
files = []
mkdir(slicePath)
// SECURITY: Validate all segment names before creation
for segment in options.segments or patterns.segments:
result = validateSegmentName(segment)
if not result.valid:
throw E305(result.error)
for segment in options.segments or patterns.segments:
segmentPath = slicePath + '/' + segment
mkdir(segmentPath)
templates = getTemplates(layer, segment)
for template in templates:
content = renderTemplate(template, {
name: transformedName,
Name: pascalCase(transformedName),
styleGuide: styleGuide
})
write(segmentPath + '/' + template.filename, content)
files.push(segmentPath + '/' + template.filename)
// Generate public API
publicApi = generatePublicApi(layer, transformedName, options.segments)
write(slicePath + '/index.ts', publicApi)
return {
slicePath: slicePath,
files: files,
patterns: patterns
}
If slice directory exists:
--force flag or different nameIf layer is not sliced:
If name contains invalid characters:
If slice name contains path traversal sequences:
[E304] Path Traversal Attempt Blocked
Slice name '../../etc/passwd' contains forbidden characters.
Security Policy:
- '..' sequences are blocked (directory traversal)
- '/' and '\' are blocked (path separators)
- Names starting with '.' are blocked (hidden files)
Valid slice names:
✓ user-profile
✓ auth
✓ shopping-cart
Invalid slice names:
✗ ../malicious
✗ foo/bar
✗ .hidden
If segment name (from --segments flag) contains path traversal sequences:
[E305] Segment Path Traversal Attempt Blocked
Segment name '../../etc' contains forbidden characters.
Security Policy (same as slice names):
- '..' sequences are blocked (directory traversal)
- '/' and '\' are blocked (path separators)
- Names starting with '.' are blocked (hidden files)
Valid segment names:
✓ ui
✓ model
✓ api
✓ lib
✓ config
Invalid segment names:
✗ ../../../etc
✗ ui/malicious
✗ .hidden-segment
Check and install OpenAPI Sync MCP server dependency
Best practice templates for API layer scaffolding
Detect and analyze FSD layer structure in a project
Check FSD import boundary rules and detect violations
Manage analysis cache for incremental FSD validation
Manage OpenAPI spec cache and implementation state for efficient diff-based sync