Standards for authoring SDD plugin agents — frontmatter, self-containment, skill references, and no-user-interaction rules.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
MVVM architecture standards for React/TypeScript frontends with TanStack ecosystem, TailwindCSS, and Shadcn UI.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
MVVM architecture for React/TypeScript frontends with strict separation between View, ViewModel, and Model layers.
View (React Components) → ViewModel (Hooks) → Model (Business Logic)
↓ ↓ ↓
TailwindCSS TanStack Query Services/API
Shadcn UI useReducer+Context
| Layer | Knows About | Example |
|---|---|---|
| View | UI rendering only | "Display user name in a card with blue border" |
| ViewModel | State and handlers | "Fetch user, track loading, provide edit handler" |
| Model | Business rules | "Format display name, check edit permissions" |
Key Principle: Views never contain business logic. ViewModels connect Views to Models. Models are framework-agnostic.
src/
├── components/ # Shared presentational components
│ ├── user_card/
│ │ ├── index.ts # Barrel exports only
│ │ ├── user_card.tsx
│ │ └── user_card.test.tsx
│ └── ui/ # Shadcn UI primitives (see shadcn.md)
│ ├── index.ts
│ ├── button.tsx
│ ├── dialog.tsx
│ └── ...
├── hooks/ # Shared hooks (auth, user data, etc.)
│ ├── index.ts
│ ├── use_auth.ts
│ ├── use_user_data.ts
│ └── ...
├── lib/ # Pure utilities and helpers
│ ├── index.ts
│ ├── utils.ts # cn() — clsx + tailwind-merge
│ └── ...
├── pages/ # Page components (View + ViewModel + Model)
│ ├── home_page/
│ │ ├── index.ts
│ │ ├── home_page.tsx
│ │ ├── use_home_view_model.ts
│ │ ├── home_model.ts
│ │ └── home_page.test.tsx
│ └── user_profile/
│ ├── index.ts
│ ├── user_profile.tsx
│ ├── use_user_profile_view_model.ts
│ ├── user_profile_model.ts
│ └── user_profile.test.tsx
├── routes/ # TanStack Router route definitions
│ ├── index.ts
│ └── routes.tsx # createAppRouter() factory
├── services/ # API clients and external services (flat)
│ ├── index.ts
│ ├── users.ts
│ ├── auth.ts
│ └── ...
├── types/ # App-local type definitions
│ ├── index.ts
│ └── ...
└── index.ts # Entry point (only file with side-effects)
Only the following src/ subdirectories are permitted:
components/ — shared presentational componentscomponents/ui/ — Shadcn UI primitiveshooks/ — shared hooks (replaces viewmodels/)lib/ — pure utilities and helperspages/ — page components (View + ViewModel + Model)routes/ — TanStack Router route definitionsservices/ — API clients and external services (flat, no subdirectories)types/ — app-local type definitionsNo new top-level src/ directories. If something doesn't fit, it belongs in one of the above.
All index.ts files must be pure barrels — imports and re-exports only. No logic, no side effects.
// src/hooks/index.ts — GOOD: pure barrel
export { useAuth } from './use_auth';
export { useUserData } from './use_user_data';
.ts (never .tsx) for index filesEvery subdirectory has an index.ts barrel. All imports from outside a directory go through its barrel.
// GOOD: barrel import
import { useAuth } from '@/hooks';
import { cn } from '@/lib';
import { fetchUser } from '@/services';
// BAD: deep import
import { useAuth } from '@/hooks/use_auth';
import { cn } from '@/lib/cn';
import { fetchUser } from '@/services/users';
Inside a module, nothing should ever import from its own index.ts. All imports within a module must use relative paths. The barrel is the module's public API for external consumers only. For nested modules, the same barrel rules apply.
// In components/layout/layout.tsx:
// GOOD: relative path to sibling sub-module barrel
import { Sidebar } from '../sidebar';
import { Button } from '../ui';
// BAD: importing from own module's barrel — circular dependency
import { Sidebar } from '@/components';
import { Sidebar } from '@/components/sidebar';
Only src/index.ts (the app entry point) may have module-level side-effects (CSS import, ReactDOM.render, window assignment). All other files must be side-effect free.
// src/index.ts — OK: entry point, side-effects allowed
import './index.css';
import { createRoot } from 'react-dom/client';
import { App } from '@/components';
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
// src/lib/utils.ts — GOOD: no side-effects, exports only
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
QueryClient and router instances must be lazily created via useState hooks inside provider components. No direct instantiation at module scope.
// GOOD: lazy creation inside component
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
export const AppQueryProvider = ({ children }: { readonly children: React.ReactNode }) => {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: { queries: { staleTime: 5 * 60 * 1000 } },
}));
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
};
// BAD: module-scope instantiation (side-effect)
const queryClient = new QueryClient();
Routes use a createAppRouter() factory exported from routes/routes.tsx. Type registration uses ReturnType<typeof createAppRouter>.
// src/routes/routes.tsx
import { createRootRoute, createRoute, createRouter } from '@tanstack/react-router';
import { HomePage } from '@/pages';
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -- AppRouter derives via ReturnType<>; explicit annotation would be circular
export const createAppRouter = () => {
const rootRoute = createRootRoute();
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: HomePage,
});
const routeTree = rootRoute.addChildren([indexRoute]);
return createRouter({ routeTree });
};
export type AppRouter = ReturnType<typeof createAppRouter>;
// Exception: declare module augmentation requires `interface` (not `type`)
// because TypeScript declaration merging only works with interfaces.
declare module '@tanstack/react-router' {
interface Register {
router: AppRouter;
}
}
cn() for Class MergingUse cn() from @/lib (clsx + tailwind-merge) instead of raw clsx. This correctly handles Tailwind class conflicts.
import { cn } from '@/lib';
export const Card = ({ className, children }: CardProps) => (
<div className={cn('rounded-lg border p-4', className)}>
{children}
</div>
);
React components that render UI. No business logic.
// src/pages/user_profile/user_profile.tsx
import { useUserProfileViewModel } from './use_user_profile_view_model';
type UserProfileProps = {
readonly userId: string;
};
export const UserProfile = ({ userId }: UserProfileProps) => {
const { user, displayName, isLoading, error, canEdit, handleEdit } = useUserProfileViewModel(userId);
if (isLoading) return <div className="flex items-center justify-center">Loading...</div>;
if (error) return <div className="text-red-600">Error: {error.message}</div>;
if (!user) return <div className="text-gray-500">User not found</div>;
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">{displayName}</h1>
<p className="text-gray-600">{user.email}</p>
{canEdit && (
<button
onClick={handleEdit}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Edit Profile
</button>
)}
</div>
);
};
View Rules:
For detailed guidance, read these on-demand:
Always consume shared API types from workspace packages via barrel imports:
import type { User, CreateUserRequest, ApiError } from '@my-org/api-types';
Never hand-write API types — they are generated from the contract component's openapi.yaml at components/contracts/{name}/openapi.yaml.
CRITICAL: Use lowercase_with_underscores for ALL filenames
| Pattern | Example | Status |
|---|---|---|
lowercase_with_underscores | user_profile.tsx | CORRECT |
| PascalCase | UserProfile.tsx | WRONG |
| camelCase | userProfile.tsx | WRONG |
| kebab-case | user-profile.tsx | WRONG |
Examples:
src/pages/user_profile/user_profile.tsxsrc/pages/user_profile/use_user_profile_view_model.tssrc/pages/user_profile/user_profile_model.tssrc/components/button/button.tsxsrc/hooks/use_auth.tssrc/services/users.tsNote: Component names in code remain PascalCase (e.g., export const UserProfile = ...).
Shared components go in src/components/:
// src/components/user_card/user_card.tsx
import type { User } from '@my-org/api-types';
import { cn } from '@/lib';
type UserCardProps = {
readonly user: User;
readonly onEdit: (id: string) => void;
readonly className?: string;
};
export const UserCard = ({ user, onEdit, className }: UserCardProps) => {
return (
<div className={cn('p-4 border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow', className)}>
<h2 className="text-xl font-semibold mb-2">{user.name}</h2>
<p className="text-gray-600 mb-4">{user.email}</p>
<button
onClick={() => onEdit(user.id)}
className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Edit
</button>
</div>
);
};
Presentational Component Rules:
readonlyBefore committing frontend code, verify:
readonly propertiescn() used for class merging (not raw clsx)lowercase_with_underscoressrc/index.ts)index.ts files are pure barrelssrc/readonly modifieruseReducer + Context for global client state (no Zustand)This skill defines no input parameters or structured output.
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Standards for authoring SDD plugin agents — frontmatter, self-containment, skill references, and no-user-interaction rules.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Scaffolds Node.js/TypeScript backend components with CMDO architecture, driven by component settings.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
CMDO architecture standards for Node.js/TypeScript backends with strict layer separation.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Intent-to-command mappings for fullstack-typescript tech pack features.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Create change specification and implementation plan with dynamic phase generation. Supports feature, bugfix, refactor, and epic types.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Orchestrates the full change lifecycle — routes actions to phase-specific sub-files.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.