slice-generator
Generate FSD-compliant slice boilerplate with pattern matching
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Generate FSD-compliant slice boilerplate with pattern matching
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
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
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
| 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