From 9d41ace076013f9092acea4ab4019382dcfaa989 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 1 Apr 2026 14:46:19 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[DOCS]=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=97=90=EC=9D=B4=EC=A0=84=ED=8A=B8=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로젝트 개요, 기술 스택, 코드 컨벤션, 테스트 전략, Speckits 워크플로우, Git 워크플로우 등 AI 에이전트가 참조할 프로젝트 문서를 추가한다. Co-Authored-By: Claude Sonnet 4.6 --- AGENTS.md | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..6a89c20f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,94 @@ +# Debate Timer FE + +## Project Overview + +토론 타이머 웹 애플리케이션 프론트엔드. 토론 테이블 구성, 타이머 실행, 투표 기능을 제공한다. + +## Tech Stack + +- **Framework**: React 18 + Vite +- **Language**: TypeScript (strict mode) +- **Routing**: React Router v7 (`createBrowserRouter`) +- **Server State**: TanStack React Query 5 +- **HTTP**: Axios (custom `request` primitive in `src/apis/primitives.ts`) +- **Styling**: Tailwind CSS 3 + PostCSS +- **i18n**: i18next + react-i18next +- **Animation**: Framer Motion +- **Testing**: Vitest + @testing-library/react + userEvent + MSW +- **Storybook**: Available on port 6006 + +## Project Structure + +``` +src/ +├── page/{PageName}/ # Page components (local components/ + hooks/) +├── components/{Component}/ # Reusable UI components +├── hooks/ +│ ├── query/ # TanStack Query hooks (useGet*) +│ ├── mutations/ # TanStack Mutation hooks (usePost*, usePatch*, useDelete*) +│ └── use{Hook}.ts # Utility hooks +├── apis/ +│ ├── apis/{domain}.ts # API functions (Axios) +│ ├── requests/{domain}.ts # Request types +│ ├── responses/{domain}.ts # Response types +│ ├── primitives.ts # Generic request helper +│ ├── axiosInstance.ts # Axios instance with interceptors +│ └── endpoints.ts # API URL constants +├── util/ # Utility functions +├── constants/ # Constants and static data +├── type/ # Shared TypeScript types +├── repositories/ # Repository pattern (API/Session) +├── mocks/handlers/ # MSW handlers +├── layout/ # Layout components (DefaultLayout) +└── routes/routes.tsx # Route definitions +``` + +## Code Conventions + +- **Components**: function declaration (`export default function X() {}`), NOT arrow function const +- **Variables**: `const` default, `let` only for reassignment, NEVER `var` +- **Naming**: Components PascalCase, hooks `use` prefix camelCase, utils camelCase, constants UPPER_SNAKE_CASE +- **Boolean**: `is`/`has`/`should` prefix +- **Event handlers**: `handle` prefix +- **i18n**: All user-facing text via `useTranslation()` + +## Testing (TDD) + +- **Approach**: Red-Green-Refactor +- **Runner**: Vitest (globals: true, jsdom) +- **Setup**: `setup.ts` (MSW server, ResizeObserver mock, i18n) +- **Convention**: `{module}.test.ts(x)` co-located, Korean test descriptions +- **Mocking**: MSW for API, minimize other mocks +- **Order**: `util/` → `apis/` → `hooks/` → `components/` → `page/` + +## Key Commands + +```bash +npm run dev # Development server +npm run dev-mock # Dev with MSW API mocking +npm run build # Production build +npm run test # Run Vitest +npm run lint # ESLint + Stylelint + TSC +npm run storybook # Storybook on port 6006 +``` + +## Speckits Workflow + +This project uses the speckits specification workflow. Scripts are in `.specify/scripts/bash/` and templates in `.specify/templates/`. + +Workflow order: +1. `/speckits/specify` — Create feature specification +2. `/speckits/clarify` — Clarify ambiguities in spec +3. `/speckits/plan` — Generate TDD-driven implementation plan +4. `/speckits/tasks` — Break plan into ordered tasks +5. `/speckits/analyze` — Cross-artifact consistency check +6. `/speckits/implement` — Execute tasks +7. `/speckits/checklist` — Generate quality checklist + +Command definitions are in `.claude/commands/speckits/`. Constitution is at `.specify/memory/constitution.md`. + +## Git Workflow + +- Main branch: `develop` +- Feature branches: `feat/#{issue}-{slug}` +- PR target: `develop` From 9932fcbace18cc836eb6d9c9f5d8a2c5fc16ab2c Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 1 Apr 2026 14:46:28 +0900 Subject: [PATCH 2/9] =?UTF-8?q?[DOCS]=20Speckits=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기능 명세, 계획, 태스크 생성, 구현을 위한 Speckits 워크플로우 핵심 파일을 추가한다. 템플릿, 실행 스크립트, 헌법(constitution) 파일을 포함한다. Co-Authored-By: Claude Sonnet 4.6 --- .specify/memory/constitution.md | 104 +++ .specify/scripts/bash/check-prerequisites.sh | 166 ++++ .specify/scripts/bash/common.sh | 185 ++++ .specify/scripts/bash/create-new-feature.sh | 351 ++++++++ .specify/scripts/bash/setup-plan.sh | 61 ++ .specify/scripts/bash/update-agent-context.sh | 799 ++++++++++++++++++ .specify/templates/agent-file-template.md | 28 + .specify/templates/checklist-template.md | 40 + .specify/templates/plan-template.md | 105 +++ .specify/templates/spec-template.md | 115 +++ .specify/templates/tasks-template.md | 250 ++++++ 11 files changed, 2204 insertions(+) create mode 100644 .specify/memory/constitution.md create mode 100755 .specify/scripts/bash/check-prerequisites.sh create mode 100755 .specify/scripts/bash/common.sh create mode 100755 .specify/scripts/bash/create-new-feature.sh create mode 100755 .specify/scripts/bash/setup-plan.sh create mode 100755 .specify/scripts/bash/update-agent-context.sh create mode 100644 .specify/templates/agent-file-template.md create mode 100644 .specify/templates/checklist-template.md create mode 100644 .specify/templates/plan-template.md create mode 100644 .specify/templates/spec-template.md create mode 100644 .specify/templates/tasks-template.md diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md new file mode 100644 index 00000000..9405b27f --- /dev/null +++ b/.specify/memory/constitution.md @@ -0,0 +1,104 @@ +# Debate Timer FE Constitution + +## Core Principles + +### I. Layered Folder Structure + +모든 코드는 기능별 폴더 구조를 따른다. 각 레이어는 명확한 책임을 가진다. + +- **page/**: 페이지 단위 컴포넌트. 각 페이지는 로컬 `components/`, `hooks/` 하위 디렉토리를 가진다. +- **components/**: 페이지 간 재사용 가능한 UI 컴포넌트. 폴더 단위로 관리. +- **hooks/**: 공유 커스텀 훅. + - `query/` — TanStack Query 래핑 (useGet*) + - `mutations/` — TanStack Mutation 래핑 (usePost*, usePatch*, useDelete*) + - 루트 — 유틸리티 훅 (useModal, useMobile 등) +- **apis/**: API 통신 레이어. + - `apis/` — Axios 기반 API 함수 + - `requests/` — 요청 타입 정의 + - `responses/` — 응답 타입 정의 + - `primitives.ts` — 공통 `request` 헬퍼 + - `axiosInstance.ts` — Axios 인스턴스 (인터셉터, 인증) + - `endpoints.ts` — API URL 상수 +- **util/**: 순수 유틸리티 함수 +- **constants/**: 상수 및 정적 데이터 +- **type/**: 공유 TypeScript 타입 정의 +- **repositories/**: Repository 패턴 구현 (API/Session 추상화) +- **mocks/handlers/**: MSW 핸들러 (API mocking) +- **layout/**: 레이아웃 컴포넌트 (DefaultLayout + Compound Component 패턴) + +### II. Consistent Code Style + +일관된 코드 작성 규칙을 프로젝트 전체에 적용한다. + +- **컴포넌트**: function declaration 사용 (`export default function Component() {}`), arrow function const 금지. +- **변수 선언**: `const` 기본, 재할당 필요 시에만 `let`, `var` 절대 금지. +- **네이밍**: 변수/함수 camelCase, 컴포넌트 PascalCase, 상수 UPPER_SNAKE_CASE. +- **Boolean**: `is`/`has`/`should` 접두사 (e.g., `isLoading`, `hasError`). +- **이벤트 핸들러**: `handle` 접두사 (e.g., `handleSubmit`). +- **커스텀 훅**: `use` 접두사 (e.g., `useTimerPageState`). +- **파일명**: 컴포넌트 PascalCase, 훅 camelCase(`use` prefix), 유틸 camelCase. + +### III. TDD (Test-Driven Development) + +모든 기능 구현은 TDD 방식을 따른다. + +- **Red-Green-Refactor 사이클**: 실패하는 테스트 → 최소 구현 → 리팩토링. +- **테스트 먼저**: 구현 코드보다 테스트를 먼저 작성한다. +- **테스트 파일 co-location**: `{module}.test.ts(x)` 형식으로 소스 파일과 같은 디렉토리에 배치. +- **테스트 설명은 한국어**: `describe`, `test` 설명을 한국어로 작성. +- **mock 최소화**: 순수 함수와 실제 코드를 우선 테스트. API만 MSW로 mocking. +- **구현 순서**: `util/` → `apis/` → `hooks/` → `components/` → `page/` +- **Vitest globals**: import 없이 `describe`, `test`, `expect` 사용 가능. + +### IV. i18n First + +모든 사용자 대면 텍스트는 i18next를 통해 관리한다. + +- `useTranslation()` 훅으로 번역 키 사용. +- 하드코딩된 한국어 텍스트 금지. +- 기본 언어: ko (한국어). + +## Technology Stack + +| Category | Technology | +| --------------- | ------------------------------------------------- | +| Framework | React 18 + Vite | +| Language | TypeScript (strict mode) | +| Routing | React Router v7 (createBrowserRouter) | +| Server State | TanStack React Query 5 | +| HTTP Client | Axios (custom request primitive) | +| Styling | Tailwind CSS 3 + PostCSS | +| i18n | i18next + react-i18next | +| Animation | Framer Motion | +| Icons | React Icons | +| Testing | Vitest + @testing-library/react + userEvent + MSW | +| Storybook | Storybook (port 6006) | +| Linting | ESLint + Stylelint + Prettier | +| Analytics | Google Analytics (ReactGA) | +| Package Manager | npm | + +## Development Workflow + +### Feature 생성 절차 + +1. `src/page/` 아래에 PascalCase 이름으로 페이지 디렉토리 생성 +2. 페이지 내 `components/`, `hooks/` 하위 디렉토리 구성 (필요 시) +3. 필요 시 `src/apis/apis/`에 API 함수, `src/apis/requests/` + `responses/`에 타입 정의 추가 +4. TanStack Query 훅을 `src/hooks/query/` 또는 `src/hooks/mutations/`에 추가 +5. 재사용 컴포넌트는 `src/components/`에 배치 +6. 라우트를 `src/routes/routes.tsx`에 등록 + +### Quality Gates + +- **ESLint + Stylelint**: 코드 품질 및 스타일 검사 +- **TypeScript**: `tsc --noEmit`으로 타입 체크 +- **Vitest**: TDD 방식의 단위/통합 테스트 +- **Storybook**: 컴포넌트 문서화 및 시각적 검증 + +## Governance + +- 이 Constitution은 프로젝트의 모든 코드 작성 및 리뷰의 기준이 된다. +- 아키텍처 결정이 불명확할 경우, 코드 작성 전 팀원과 논의한다. +- Constitution 변경 시 팀 합의 및 문서 업데이트가 필요하다. + +**Version**: 2.0.0 | **Ratified**: 2026-02-13 | **Last Amended**: 2026-03-31 diff --git a/.specify/scripts/bash/check-prerequisites.sh b/.specify/scripts/bash/check-prerequisites.sh new file mode 100755 index 00000000..3b7c793e --- /dev/null +++ b/.specify/scripts/bash/check-prerequisites.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +# Consolidated prerequisite checking script +# +# This script provides unified prerequisite checking for Spec-Driven Development workflow. +# It replaces the functionality previously spread across multiple scripts. +# +# Usage: ./check-prerequisites.sh [OPTIONS] +# +# OPTIONS: +# --json Output in JSON format +# --require-tasks Require tasks.md to exist (for implementation phase) +# --include-tasks Include tasks.md in AVAILABLE_DOCS list +# --paths-only Only output path variables (no validation) +# --help, -h Show help message +# +# OUTPUTS: +# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]} +# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md +# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc. + +set -e + +# Parse command line arguments +JSON_MODE=false +REQUIRE_TASKS=false +INCLUDE_TASKS=false +PATHS_ONLY=false + +for arg in "$@"; do + case "$arg" in + --json) + JSON_MODE=true + ;; + --require-tasks) + REQUIRE_TASKS=true + ;; + --include-tasks) + INCLUDE_TASKS=true + ;; + --paths-only) + PATHS_ONLY=true + ;; + --help|-h) + cat << 'EOF' +Usage: check-prerequisites.sh [OPTIONS] + +Consolidated prerequisite checking for Spec-Driven Development workflow. + +OPTIONS: + --json Output in JSON format + --require-tasks Require tasks.md to exist (for implementation phase) + --include-tasks Include tasks.md in AVAILABLE_DOCS list + --paths-only Only output path variables (no prerequisite validation) + --help, -h Show this help message + +EXAMPLES: + # Check task prerequisites (plan.md required) + ./check-prerequisites.sh --json + + # Check implementation prerequisites (plan.md + tasks.md required) + ./check-prerequisites.sh --json --require-tasks --include-tasks + + # Get feature paths only (no validation) + ./check-prerequisites.sh --paths-only + +EOF + exit 0 + ;; + *) + echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2 + exit 1 + ;; + esac +done + +# Source common functions +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# Get feature paths and validate branch +eval $(get_feature_paths) +check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 + +# If paths-only mode, output paths and exit (support JSON + paths-only combined) +if $PATHS_ONLY; then + if $JSON_MODE; then + # Minimal JSON paths payload (no validation performed) + printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \ + "$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS" + else + echo "REPO_ROOT: $REPO_ROOT" + echo "BRANCH: $CURRENT_BRANCH" + echo "FEATURE_DIR: $FEATURE_DIR" + echo "FEATURE_SPEC: $FEATURE_SPEC" + echo "IMPL_PLAN: $IMPL_PLAN" + echo "TASKS: $TASKS" + fi + exit 0 +fi + +# Validate required directories and files +if [[ ! -d "$FEATURE_DIR" ]]; then + echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2 + echo "Run /speckits/specify first to create the feature structure." >&2 + exit 1 +fi + +if [[ ! -f "$IMPL_PLAN" ]]; then + echo "ERROR: plan.md not found in $FEATURE_DIR" >&2 + echo "Run /speckits/plan first to create the implementation plan." >&2 + exit 1 +fi + +# Check for tasks.md if required +if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then + echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2 + echo "Run /speckits/tasks first to create the task list." >&2 + exit 1 +fi + +# Build list of available documents +docs=() + +# Always check these optional docs +[[ -f "$RESEARCH" ]] && docs+=("research.md") +[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md") + +# Check contracts directory (only if it exists and has files) +if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then + docs+=("contracts/") +fi + +[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md") + +# Include tasks.md if requested and it exists +if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then + docs+=("tasks.md") +fi + +# Output results +if $JSON_MODE; then + # Build JSON array of documents + if [[ ${#docs[@]} -eq 0 ]]; then + json_docs="[]" + else + json_docs=$(printf '"%s",' "${docs[@]}") + json_docs="[${json_docs%,}]" + fi + + printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs" +else + # Text output + echo "FEATURE_DIR:$FEATURE_DIR" + echo "AVAILABLE_DOCS:" + + # Show status of each potential document + check_file "$RESEARCH" "research.md" + check_file "$DATA_MODEL" "data-model.md" + check_dir "$CONTRACTS_DIR" "contracts/" + check_file "$QUICKSTART" "quickstart.md" + + if $INCLUDE_TASKS; then + check_file "$TASKS" "tasks.md" + fi +fi diff --git a/.specify/scripts/bash/common.sh b/.specify/scripts/bash/common.sh new file mode 100755 index 00000000..612ebd86 --- /dev/null +++ b/.specify/scripts/bash/common.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env bash +# Common functions and variables for all scripts + +# Get repository root, with fallback for non-git repositories +get_repo_root() { + if git rev-parse --show-toplevel >/dev/null 2>&1; then + git rev-parse --show-toplevel + else + # Fall back to script location for non-git repos + local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + (cd "$script_dir/../../.." && pwd) + fi +} + +# Get current branch, with fallback for non-git repositories +get_current_branch() { + # First check if SPECIFY_FEATURE environment variable is set + if [[ -n "${SPECIFY_FEATURE:-}" ]]; then + echo "$SPECIFY_FEATURE" + return + fi + + # Then check git if available + if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then + git rev-parse --abbrev-ref HEAD + return + fi + + # For non-git repos, try to find the latest feature directory + local repo_root=$(get_repo_root) + local specs_dir="$repo_root/specs/feat" + + if [[ -d "$specs_dir" ]]; then + local latest_feature="" + local highest=0 + + for dir in "$specs_dir"/*; do + if [[ -d "$dir" ]]; then + local dirname=$(basename "$dir") + if [[ "$dirname" =~ ^([0-9]+)- ]]; then + local number=${BASH_REMATCH[1]} + number=$((10#$number)) + if [[ "$number" -gt "$highest" ]]; then + highest=$number + latest_feature=$dirname + fi + fi + fi + done + + if [[ -n "$latest_feature" ]]; then + echo "$latest_feature" + return + fi + fi + + echo "main" # Final fallback +} + +# Check if we have git available +has_git() { + git rev-parse --show-toplevel >/dev/null 2>&1 +} + +# Extract issue number from branch name +# Supports: feat/#96-slug, feat/#096-slug, 096-slug +extract_issue_number() { + local branch="$1" + + # Pattern: feat/#NNN-slug or type/#NNN-slug + if [[ "$branch" =~ ^[a-z]+/#([0-9]+)- ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + + # Pattern: NNN-slug (legacy) + if [[ "$branch" =~ ^([0-9]{3})- ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + + echo "" +} + +check_feature_branch() { + local branch="$1" + local has_git_repo="$2" + + # For non-git repos, we can't enforce branch naming but still provide output + if [[ "$has_git_repo" != "true" ]]; then + echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2 + return 0 + fi + + # Accept: feat/#96-slug, fix/#88-jm, type/#NNN-slug + if [[ "$branch" =~ ^[a-z]+/#[0-9]+- ]]; then + return 0 + fi + + # Accept: 001-feature-name (legacy pattern) + if [[ "$branch" =~ ^[0-9]{3}- ]]; then + return 0 + fi + + echo "ERROR: Not on a feature branch. Current branch: $branch" >&2 + echo "Feature branches should be named like: feat/#96-feature-name or 001-feature-name" >&2 + return 1 +} + +get_feature_dir() { echo "$1/specs/feat/$2"; } + +# Find feature directory by issue number or numeric prefix +# Supports: feat/#96-social-login → specs/feat/096-* +# 096-social-login → specs/feat/096-* +find_feature_dir_by_prefix() { + local repo_root="$1" + local branch_name="$2" + local specs_dir="$repo_root/specs/feat" + + # Extract issue number from branch name + local issue_num=$(extract_issue_number "$branch_name") + + if [[ -z "$issue_num" ]]; then + # If no issue number found, fall back to exact match under specs/feat/ + echo "$specs_dir/$branch_name" + return + fi + + # Zero-pad to 3 digits for matching + local padded=$(printf "%03d" "$((10#$issue_num))") + + # Search for directories in specs/feat/ that start with this prefix + local matches=() + if [[ -d "$specs_dir" ]]; then + for dir in "$specs_dir"/"$padded"-*; do + if [[ -d "$dir" ]]; then + matches+=("$(basename "$dir")") + fi + done + fi + + # Handle results + if [[ ${#matches[@]} -eq 0 ]]; then + # No match found - return the padded path (will fail later with clear error) + echo "$specs_dir/$padded-$branch_name" + elif [[ ${#matches[@]} -eq 1 ]]; then + # Exactly one match - perfect! + echo "$specs_dir/${matches[0]}" + else + # Multiple matches - this shouldn't happen with proper naming convention + echo "ERROR: Multiple spec directories found with prefix '$padded': ${matches[*]}" >&2 + echo "Please ensure only one spec directory exists per issue number." >&2 + echo "$specs_dir/${matches[0]}" # Return first match + fi +} + +get_feature_paths() { + local repo_root=$(get_repo_root) + local current_branch=$(get_current_branch) + local has_git_repo="false" + + if has_git; then + has_git_repo="true" + fi + + # Use prefix-based lookup to support branch → specs/feat/ mapping + local feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch") + + cat </dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; } diff --git a/.specify/scripts/bash/create-new-feature.sh b/.specify/scripts/bash/create-new-feature.sh new file mode 100755 index 00000000..7764a1e1 --- /dev/null +++ b/.specify/scripts/bash/create-new-feature.sh @@ -0,0 +1,351 @@ +#!/usr/bin/env bash + +set -e + +JSON_MODE=false +SHORT_NAME="" +BRANCH_NUMBER="" +NO_BRANCH=false +TYPE_PREFIX="" +ISSUE_NUMBER="" +ARGS=() +i=1 +while [ $i -le $# ]; do + arg="${!i}" + case "$arg" in + --json) + JSON_MODE=true + ;; + --no-branch) + NO_BRANCH=true + ;; + --type-prefix) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --type-prefix requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + if [[ "$next_arg" == --* ]]; then + echo 'Error: --type-prefix requires a value' >&2 + exit 1 + fi + TYPE_PREFIX="$next_arg" + ;; + --issue-number) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --issue-number requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + if [[ "$next_arg" == --* ]]; then + echo 'Error: --issue-number requires a value' >&2 + exit 1 + fi + ISSUE_NUMBER="$next_arg" + ;; + --short-name) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + # Check if the next argument is another option (starts with --) + if [[ "$next_arg" == --* ]]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + SHORT_NAME="$next_arg" + ;; + --number) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + if [[ "$next_arg" == --* ]]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + BRANCH_NUMBER="$next_arg" + ;; + --help|-h) + echo "Usage: $0 [--json] [--short-name ] [--number N] [--no-branch] [--type-prefix ] [--issue-number ] " + echo "" + echo "Options:" + echo " --json Output in JSON format" + echo " --short-name Provide a custom short name (2-4 words) for the branch" + echo " --number N Specify branch number manually (overrides auto-detection)" + echo " --no-branch Skip git branch creation (use when branch already exists)" + echo " --type-prefix Add type prefix to specs directory (e.g., 'feat' → specs/feat/)" + echo " --issue-number GitHub issue number (included in JSON output)" + echo " --help, -h Show this help message" + echo "" + echo "Examples:" + echo " $0 'Add user authentication system' --short-name 'user-auth'" + echo " $0 --json --no-branch --type-prefix feat --number 96 --short-name 'social-login' --issue-number 96 'Add social login'" + exit 0 + ;; + *) + ARGS+=("$arg") + ;; + esac + i=$((i + 1)) +done + +FEATURE_DESCRIPTION="${ARGS[*]}" +if [ -z "$FEATURE_DESCRIPTION" ]; then + echo "Usage: $0 [--json] [--short-name ] [--number N] [--no-branch] [--type-prefix ] [--issue-number ] " >&2 + exit 1 +fi + +# Function to find the repository root by searching for existing project markers +find_repo_root() { + local dir="$1" + while [ "$dir" != "/" ]; do + if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then + echo "$dir" + return 0 + fi + dir="$(dirname "$dir")" + done + return 1 +} + +# Function to get highest number from specs directory +get_highest_from_specs() { + local specs_dir="$1" + local highest=0 + + if [ -d "$specs_dir" ]; then + for dir in "$specs_dir"/*; do + [ -d "$dir" ] || continue + dirname=$(basename "$dir") + number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0") + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + done + fi + + echo "$highest" +} + +# Function to get highest number from git branches +get_highest_from_branches() { + local highest=0 + + # Get all branches (local and remote) + branches=$(git branch -a 2>/dev/null || echo "") + + if [ -n "$branches" ]; then + while IFS= read -r branch; do + # Clean branch name: remove leading markers and remote prefixes + clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||') + + # Extract feature number if branch matches pattern ###-* + if echo "$clean_branch" | grep -q '^[0-9]\{3\}-'; then + number=$(echo "$clean_branch" | grep -o '^[0-9]\{3\}' || echo "0") + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + fi + done <<< "$branches" + fi + + echo "$highest" +} + +# Function to check existing branches (local and remote) and return next available number +check_existing_branches() { + local specs_dir="$1" + + # Fetch all remotes to get latest branch info (suppress errors if no remotes) + git fetch --all --prune 2>/dev/null || true + + # Get highest number from ALL branches (not just matching short name) + local highest_branch=$(get_highest_from_branches) + + # Get highest number from ALL specs (not just matching short name) + local highest_spec=$(get_highest_from_specs "$specs_dir") + + # Take the maximum of both + local max_num=$highest_branch + if [ "$highest_spec" -gt "$max_num" ]; then + max_num=$highest_spec + fi + + # Return next number + echo $((max_num + 1)) +} + +# Function to clean and format a branch name +clean_branch_name() { + local name="$1" + echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' +} + +# Resolve repository root. Prefer git information when available, but fall back +# to searching for repository markers so the workflow still functions in repositories that +# were initialised with --no-git. +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if git rev-parse --show-toplevel >/dev/null 2>&1; then + REPO_ROOT=$(git rev-parse --show-toplevel) + HAS_GIT=true +else + REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")" + if [ -z "$REPO_ROOT" ]; then + echo "Error: Could not determine repository root. Please run this script from within the repository." >&2 + exit 1 + fi + HAS_GIT=false +fi + +cd "$REPO_ROOT" + +# Build SPECS_DIR with optional type prefix +if [ -n "$TYPE_PREFIX" ]; then + SPECS_DIR="$REPO_ROOT/specs/$TYPE_PREFIX" +else + SPECS_DIR="$REPO_ROOT/specs" +fi +mkdir -p "$SPECS_DIR" + +# Function to generate branch name with stop word filtering and length filtering +generate_branch_name() { + local description="$1" + + # Common stop words to filter out + local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$" + + # Convert to lowercase and split into words + local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') + + # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original) + local meaningful_words=() + for word in $clean_name; do + # Skip empty words + [ -z "$word" ] && continue + + # Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms) + if ! echo "$word" | grep -qiE "$stop_words"; then + if [ ${#word} -ge 3 ]; then + meaningful_words+=("$word") + elif echo "$description" | grep -q "\b${word^^}\b"; then + # Keep short words if they appear as uppercase in original (likely acronyms) + meaningful_words+=("$word") + fi + fi + done + + # If we have meaningful words, use first 3-4 of them + if [ ${#meaningful_words[@]} -gt 0 ]; then + local max_words=3 + if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi + + local result="" + local count=0 + for word in "${meaningful_words[@]}"; do + if [ $count -ge $max_words ]; then break; fi + if [ -n "$result" ]; then result="$result-"; fi + result="$result$word" + count=$((count + 1)) + done + echo "$result" + else + # Fallback to original logic if no meaningful words found + local cleaned=$(clean_branch_name "$description") + echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' + fi +} + +# Generate branch name +if [ -n "$SHORT_NAME" ]; then + # Use provided short name, just clean it up + BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME") +else + # Generate from description with smart filtering + BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION") +fi + +# Determine branch number +if [ -z "$BRANCH_NUMBER" ]; then + if [ "$HAS_GIT" = true ]; then + # Check existing branches on remotes + BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") + else + # Fall back to local directory check + HIGHEST=$(get_highest_from_specs "$SPECS_DIR") + BRANCH_NUMBER=$((HIGHEST + 1)) + fi +fi + +# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal) +FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))") +BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" + +# GitHub enforces a 244-byte limit on branch names +# Validate and truncate if necessary +MAX_BRANCH_LENGTH=244 +if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then + # Calculate how much we need to trim from suffix + # Account for: feature number (3) + hyphen (1) = 4 chars + MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4)) + + # Truncate suffix at word boundary if possible + TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH) + # Remove trailing hyphen if truncation created one + TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//') + + ORIGINAL_BRANCH_NAME="$BRANCH_NAME" + BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}" + + >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" + >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)" + >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)" +fi + +# Create branch unless --no-branch flag is set +if [ "$NO_BRANCH" = false ]; then + if [ "$HAS_GIT" = true ]; then + git checkout -b "$BRANCH_NAME" + else + >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" + fi +else + >&2 echo "[specify] Skipping branch creation (--no-branch flag set)" +fi + +FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME" +mkdir -p "$FEATURE_DIR" + +TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md" +SPEC_FILE="$FEATURE_DIR/spec.md" +if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi + +# Set the SPECIFY_FEATURE environment variable for the current session +export SPECIFY_FEATURE="$BRANCH_NAME" + +if $JSON_MODE; then + # Build JSON with optional ISSUE_NUMBER field + if [ -n "$ISSUE_NUMBER" ]; then + printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","FEATURE_DIR":"%s","ISSUE_NUMBER":"%s"}\n' \ + "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM" "$FEATURE_DIR" "$ISSUE_NUMBER" + else + printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","FEATURE_DIR":"%s"}\n' \ + "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM" "$FEATURE_DIR" + fi +else + echo "BRANCH_NAME: $BRANCH_NAME" + echo "SPEC_FILE: $SPEC_FILE" + echo "FEATURE_NUM: $FEATURE_NUM" + echo "FEATURE_DIR: $FEATURE_DIR" + [ -n "$ISSUE_NUMBER" ] && echo "ISSUE_NUMBER: $ISSUE_NUMBER" + echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME" +fi diff --git a/.specify/scripts/bash/setup-plan.sh b/.specify/scripts/bash/setup-plan.sh new file mode 100755 index 00000000..d01c6d6c --- /dev/null +++ b/.specify/scripts/bash/setup-plan.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +set -e + +# Parse command line arguments +JSON_MODE=false +ARGS=() + +for arg in "$@"; do + case "$arg" in + --json) + JSON_MODE=true + ;; + --help|-h) + echo "Usage: $0 [--json]" + echo " --json Output results in JSON format" + echo " --help Show this help message" + exit 0 + ;; + *) + ARGS+=("$arg") + ;; + esac +done + +# Get script directory and load common functions +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# Get all paths and variables from common functions +eval $(get_feature_paths) + +# Check if we're on a proper feature branch (only for git repos) +check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 + +# Ensure the feature directory exists +mkdir -p "$FEATURE_DIR" + +# Copy plan template if it exists +TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md" +if [[ -f "$TEMPLATE" ]]; then + cp "$TEMPLATE" "$IMPL_PLAN" + echo "Copied plan template to $IMPL_PLAN" +else + echo "Warning: Plan template not found at $TEMPLATE" + # Create a basic plan file if template doesn't exist + touch "$IMPL_PLAN" +fi + +# Output results +if $JSON_MODE; then + printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \ + "$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT" +else + echo "FEATURE_SPEC: $FEATURE_SPEC" + echo "IMPL_PLAN: $IMPL_PLAN" + echo "SPECS_DIR: $FEATURE_DIR" + echo "BRANCH: $CURRENT_BRANCH" + echo "HAS_GIT: $HAS_GIT" +fi + diff --git a/.specify/scripts/bash/update-agent-context.sh b/.specify/scripts/bash/update-agent-context.sh new file mode 100755 index 00000000..6d3e0b37 --- /dev/null +++ b/.specify/scripts/bash/update-agent-context.sh @@ -0,0 +1,799 @@ +#!/usr/bin/env bash + +# Update agent context files with information from plan.md +# +# This script maintains AI agent context files by parsing feature specifications +# and updating agent-specific configuration files with project information. +# +# MAIN FUNCTIONS: +# 1. Environment Validation +# - Verifies git repository structure and branch information +# - Checks for required plan.md files and templates +# - Validates file permissions and accessibility +# +# 2. Plan Data Extraction +# - Parses plan.md files to extract project metadata +# - Identifies language/version, frameworks, databases, and project types +# - Handles missing or incomplete specification data gracefully +# +# 3. Agent File Management +# - Creates new agent context files from templates when needed +# - Updates existing agent files with new project information +# - Preserves manual additions and custom configurations +# - Supports multiple AI agent formats and directory structures +# +# 4. Content Generation +# - Generates language-specific build/test commands +# - Creates appropriate project directory structures +# - Updates technology stacks and recent changes sections +# - Maintains consistent formatting and timestamps +# +# 5. Multi-Agent Support +# - Handles agent-specific file paths and naming conventions +# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, or Amazon Q Developer CLI +# - Can update single agents or all existing agent files +# - Creates default Claude file if no agent files exist +# +# Usage: ./update-agent-context.sh [agent_type] +# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|shai|q|bob|qoder +# Leave empty to update all existing agent files + +set -e + +# Enable strict error handling +set -u +set -o pipefail + +#============================================================================== +# Configuration and Global Variables +#============================================================================== + +# Get script directory and load common functions +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# Get all paths and variables from common functions +eval $(get_feature_paths) + +NEW_PLAN="$IMPL_PLAN" # Alias for compatibility with existing code +AGENT_TYPE="${1:-}" + +# Agent-specific file paths +CLAUDE_FILE="$REPO_ROOT/CLAUDE.md" +GEMINI_FILE="$REPO_ROOT/GEMINI.md" +COPILOT_FILE="$REPO_ROOT/.github/agents/copilot-instructions.md" +CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc" +QWEN_FILE="$REPO_ROOT/QWEN.md" +AGENTS_FILE="$REPO_ROOT/AGENTS.md" +WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md" +KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md" +AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md" +ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md" +CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md" +QODER_FILE="$REPO_ROOT/QODER.md" +AMP_FILE="$REPO_ROOT/AGENTS.md" +SHAI_FILE="$REPO_ROOT/SHAI.md" +Q_FILE="$REPO_ROOT/AGENTS.md" +BOB_FILE="$REPO_ROOT/AGENTS.md" + +# Template file +TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md" + +# Global variables for parsed plan data +NEW_LANG="" +NEW_FRAMEWORK="" +NEW_DB="" +NEW_PROJECT_TYPE="" + +#============================================================================== +# Utility Functions +#============================================================================== + +log_info() { + echo "INFO: $1" +} + +log_success() { + echo "✓ $1" +} + +log_error() { + echo "ERROR: $1" >&2 +} + +log_warning() { + echo "WARNING: $1" >&2 +} + +# Cleanup function for temporary files +cleanup() { + local exit_code=$? + rm -f /tmp/agent_update_*_$$ + rm -f /tmp/manual_additions_$$ + exit $exit_code +} + +# Set up cleanup trap +trap cleanup EXIT INT TERM + +#============================================================================== +# Validation Functions +#============================================================================== + +validate_environment() { + # Check if we have a current branch/feature (git or non-git) + if [[ -z "$CURRENT_BRANCH" ]]; then + log_error "Unable to determine current feature" + if [[ "$HAS_GIT" == "true" ]]; then + log_info "Make sure you're on a feature branch" + else + log_info "Set SPECIFY_FEATURE environment variable or create a feature first" + fi + exit 1 + fi + + # Check if plan.md exists + if [[ ! -f "$NEW_PLAN" ]]; then + log_error "No plan.md found at $NEW_PLAN" + log_info "Make sure you're working on a feature with a corresponding spec directory" + if [[ "$HAS_GIT" != "true" ]]; then + log_info "Use: export SPECIFY_FEATURE=your-feature-name or create a new feature first" + fi + exit 1 + fi + + # Check if template exists (needed for new files) + if [[ ! -f "$TEMPLATE_FILE" ]]; then + log_warning "Template file not found at $TEMPLATE_FILE" + log_warning "Creating new agent files will fail" + fi +} + +#============================================================================== +# Plan Parsing Functions +#============================================================================== + +extract_plan_field() { + local field_pattern="$1" + local plan_file="$2" + + grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \ + head -1 | \ + sed "s|^\*\*${field_pattern}\*\*: ||" | \ + sed 's/^[ \t]*//;s/[ \t]*$//' | \ + grep -v "NEEDS CLARIFICATION" | \ + grep -v "^N/A$" || echo "" +} + +parse_plan_data() { + local plan_file="$1" + + if [[ ! -f "$plan_file" ]]; then + log_error "Plan file not found: $plan_file" + return 1 + fi + + if [[ ! -r "$plan_file" ]]; then + log_error "Plan file is not readable: $plan_file" + return 1 + fi + + log_info "Parsing plan data from $plan_file" + + NEW_LANG=$(extract_plan_field "Language/Version" "$plan_file") + NEW_FRAMEWORK=$(extract_plan_field "Primary Dependencies" "$plan_file") + NEW_DB=$(extract_plan_field "Storage" "$plan_file") + NEW_PROJECT_TYPE=$(extract_plan_field "Project Type" "$plan_file") + + # Log what we found + if [[ -n "$NEW_LANG" ]]; then + log_info "Found language: $NEW_LANG" + else + log_warning "No language information found in plan" + fi + + if [[ -n "$NEW_FRAMEWORK" ]]; then + log_info "Found framework: $NEW_FRAMEWORK" + fi + + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then + log_info "Found database: $NEW_DB" + fi + + if [[ -n "$NEW_PROJECT_TYPE" ]]; then + log_info "Found project type: $NEW_PROJECT_TYPE" + fi +} + +format_technology_stack() { + local lang="$1" + local framework="$2" + local parts=() + + # Add non-empty parts + [[ -n "$lang" && "$lang" != "NEEDS CLARIFICATION" ]] && parts+=("$lang") + [[ -n "$framework" && "$framework" != "NEEDS CLARIFICATION" && "$framework" != "N/A" ]] && parts+=("$framework") + + # Join with proper formatting + if [[ ${#parts[@]} -eq 0 ]]; then + echo "" + elif [[ ${#parts[@]} -eq 1 ]]; then + echo "${parts[0]}" + else + # Join multiple parts with " + " + local result="${parts[0]}" + for ((i=1; i<${#parts[@]}; i++)); do + result="$result + ${parts[i]}" + done + echo "$result" + fi +} + +#============================================================================== +# Template and Content Generation Functions +#============================================================================== + +get_project_structure() { + local project_type="$1" + + if [[ "$project_type" == *"web"* ]]; then + echo "backend/\\nfrontend/\\ntests/" + else + echo "src/\\ntests/" + fi +} + +get_commands_for_language() { + local lang="$1" + + case "$lang" in + *"Python"*) + echo "cd src && pytest && ruff check ." + ;; + *"Rust"*) + echo "cargo test && cargo clippy" + ;; + *"JavaScript"*|*"TypeScript"*) + echo "npm test \\&\\& npm run lint" + ;; + *) + echo "# Add commands for $lang" + ;; + esac +} + +get_language_conventions() { + local lang="$1" + echo "$lang: Follow standard conventions" +} + +create_new_agent_file() { + local target_file="$1" + local temp_file="$2" + local project_name="$3" + local current_date="$4" + + if [[ ! -f "$TEMPLATE_FILE" ]]; then + log_error "Template not found at $TEMPLATE_FILE" + return 1 + fi + + if [[ ! -r "$TEMPLATE_FILE" ]]; then + log_error "Template file is not readable: $TEMPLATE_FILE" + return 1 + fi + + log_info "Creating new agent context file from template..." + + if ! cp "$TEMPLATE_FILE" "$temp_file"; then + log_error "Failed to copy template file" + return 1 + fi + + # Replace template placeholders + local project_structure + project_structure=$(get_project_structure "$NEW_PROJECT_TYPE") + + local commands + commands=$(get_commands_for_language "$NEW_LANG") + + local language_conventions + language_conventions=$(get_language_conventions "$NEW_LANG") + + # Perform substitutions with error checking using safer approach + # Escape special characters for sed by using a different delimiter or escaping + local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\[\.*^$()+{}|]/\\&/g') + local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\[\.*^$()+{}|]/\\&/g') + local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\[\.*^$()+{}|]/\\&/g') + + # Build technology stack and recent change strings conditionally + local tech_stack + if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then + tech_stack="- $escaped_lang + $escaped_framework ($escaped_branch)" + elif [[ -n "$escaped_lang" ]]; then + tech_stack="- $escaped_lang ($escaped_branch)" + elif [[ -n "$escaped_framework" ]]; then + tech_stack="- $escaped_framework ($escaped_branch)" + else + tech_stack="- ($escaped_branch)" + fi + + local recent_change + if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then + recent_change="- $escaped_branch: Added $escaped_lang + $escaped_framework" + elif [[ -n "$escaped_lang" ]]; then + recent_change="- $escaped_branch: Added $escaped_lang" + elif [[ -n "$escaped_framework" ]]; then + recent_change="- $escaped_branch: Added $escaped_framework" + else + recent_change="- $escaped_branch: Added" + fi + + local substitutions=( + "s|\[PROJECT NAME\]|$project_name|" + "s|\[DATE\]|$current_date|" + "s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|" + "s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g" + "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|" + "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|" + "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|" + ) + + for substitution in "${substitutions[@]}"; do + if ! sed -i.bak -e "$substitution" "$temp_file"; then + log_error "Failed to perform substitution: $substitution" + rm -f "$temp_file" "$temp_file.bak" + return 1 + fi + done + + # Convert \n sequences to actual newlines + newline=$(printf '\n') + sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file" + + # Clean up backup files + rm -f "$temp_file.bak" "$temp_file.bak2" + + return 0 +} + + + + +update_existing_agent_file() { + local target_file="$1" + local current_date="$2" + + log_info "Updating existing agent context file..." + + # Use a single temporary file for atomic update + local temp_file + temp_file=$(mktemp) || { + log_error "Failed to create temporary file" + return 1 + } + + # Process the file in one pass + local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK") + local new_tech_entries=() + local new_change_entry="" + + # Prepare new technology entries + if [[ -n "$tech_stack" ]] && ! grep -q "$tech_stack" "$target_file"; then + new_tech_entries+=("- $tech_stack ($CURRENT_BRANCH)") + fi + + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]] && ! grep -q "$NEW_DB" "$target_file"; then + new_tech_entries+=("- $NEW_DB ($CURRENT_BRANCH)") + fi + + # Prepare new change entry + if [[ -n "$tech_stack" ]]; then + new_change_entry="- $CURRENT_BRANCH: Added $tech_stack" + elif [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]]; then + new_change_entry="- $CURRENT_BRANCH: Added $NEW_DB" + fi + + # Check if sections exist in the file + local has_active_technologies=0 + local has_recent_changes=0 + + if grep -q "^## Active Technologies" "$target_file" 2>/dev/null; then + has_active_technologies=1 + fi + + if grep -q "^## Recent Changes" "$target_file" 2>/dev/null; then + has_recent_changes=1 + fi + + # Process file line by line + local in_tech_section=false + local in_changes_section=false + local tech_entries_added=false + local changes_entries_added=false + local existing_changes_count=0 + local file_ended=false + + while IFS= read -r line || [[ -n "$line" ]]; do + # Handle Active Technologies section + if [[ "$line" == "## Active Technologies" ]]; then + echo "$line" >> "$temp_file" + in_tech_section=true + continue + elif [[ $in_tech_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then + # Add new tech entries before closing the section + if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" + tech_entries_added=true + fi + echo "$line" >> "$temp_file" + in_tech_section=false + continue + elif [[ $in_tech_section == true ]] && [[ -z "$line" ]]; then + # Add new tech entries before empty line in tech section + if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" + tech_entries_added=true + fi + echo "$line" >> "$temp_file" + continue + fi + + # Handle Recent Changes section + if [[ "$line" == "## Recent Changes" ]]; then + echo "$line" >> "$temp_file" + # Add new change entry right after the heading + if [[ -n "$new_change_entry" ]]; then + echo "$new_change_entry" >> "$temp_file" + fi + in_changes_section=true + changes_entries_added=true + continue + elif [[ $in_changes_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then + echo "$line" >> "$temp_file" + in_changes_section=false + continue + elif [[ $in_changes_section == true ]] && [[ "$line" == "- "* ]]; then + # Keep only first 2 existing changes + if [[ $existing_changes_count -lt 2 ]]; then + echo "$line" >> "$temp_file" + ((existing_changes_count++)) + fi + continue + fi + + # Update timestamp + if [[ "$line" =~ \*\*Last\ updated\*\*:.*[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]]; then + echo "$line" | sed "s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/$current_date/" >> "$temp_file" + else + echo "$line" >> "$temp_file" + fi + done < "$target_file" + + # Post-loop check: if we're still in the Active Technologies section and haven't added new entries + if [[ $in_tech_section == true ]] && [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" + tech_entries_added=true + fi + + # If sections don't exist, add them at the end of the file + if [[ $has_active_technologies -eq 0 ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then + echo "" >> "$temp_file" + echo "## Active Technologies" >> "$temp_file" + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" + tech_entries_added=true + fi + + if [[ $has_recent_changes -eq 0 ]] && [[ -n "$new_change_entry" ]]; then + echo "" >> "$temp_file" + echo "## Recent Changes" >> "$temp_file" + echo "$new_change_entry" >> "$temp_file" + changes_entries_added=true + fi + + # Move temp file to target atomically + if ! mv "$temp_file" "$target_file"; then + log_error "Failed to update target file" + rm -f "$temp_file" + return 1 + fi + + return 0 +} +#============================================================================== +# Main Agent File Update Function +#============================================================================== + +update_agent_file() { + local target_file="$1" + local agent_name="$2" + + if [[ -z "$target_file" ]] || [[ -z "$agent_name" ]]; then + log_error "update_agent_file requires target_file and agent_name parameters" + return 1 + fi + + log_info "Updating $agent_name context file: $target_file" + + local project_name + project_name=$(basename "$REPO_ROOT") + local current_date + current_date=$(date +%Y-%m-%d) + + # Create directory if it doesn't exist + local target_dir + target_dir=$(dirname "$target_file") + if [[ ! -d "$target_dir" ]]; then + if ! mkdir -p "$target_dir"; then + log_error "Failed to create directory: $target_dir" + return 1 + fi + fi + + if [[ ! -f "$target_file" ]]; then + # Create new file from template + local temp_file + temp_file=$(mktemp) || { + log_error "Failed to create temporary file" + return 1 + } + + if create_new_agent_file "$target_file" "$temp_file" "$project_name" "$current_date"; then + if mv "$temp_file" "$target_file"; then + log_success "Created new $agent_name context file" + else + log_error "Failed to move temporary file to $target_file" + rm -f "$temp_file" + return 1 + fi + else + log_error "Failed to create new agent file" + rm -f "$temp_file" + return 1 + fi + else + # Update existing file + if [[ ! -r "$target_file" ]]; then + log_error "Cannot read existing file: $target_file" + return 1 + fi + + if [[ ! -w "$target_file" ]]; then + log_error "Cannot write to existing file: $target_file" + return 1 + fi + + if update_existing_agent_file "$target_file" "$current_date"; then + log_success "Updated existing $agent_name context file" + else + log_error "Failed to update existing agent file" + return 1 + fi + fi + + return 0 +} + +#============================================================================== +# Agent Selection and Processing +#============================================================================== + +update_specific_agent() { + local agent_type="$1" + + case "$agent_type" in + claude) + update_agent_file "$CLAUDE_FILE" "Claude Code" + ;; + gemini) + update_agent_file "$GEMINI_FILE" "Gemini CLI" + ;; + copilot) + update_agent_file "$COPILOT_FILE" "GitHub Copilot" + ;; + cursor-agent) + update_agent_file "$CURSOR_FILE" "Cursor IDE" + ;; + qwen) + update_agent_file "$QWEN_FILE" "Qwen Code" + ;; + opencode) + update_agent_file "$AGENTS_FILE" "opencode" + ;; + codex) + update_agent_file "$AGENTS_FILE" "Codex CLI" + ;; + windsurf) + update_agent_file "$WINDSURF_FILE" "Windsurf" + ;; + kilocode) + update_agent_file "$KILOCODE_FILE" "Kilo Code" + ;; + auggie) + update_agent_file "$AUGGIE_FILE" "Auggie CLI" + ;; + roo) + update_agent_file "$ROO_FILE" "Roo Code" + ;; + codebuddy) + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" + ;; + qoder) + update_agent_file "$QODER_FILE" "Qoder CLI" + ;; + amp) + update_agent_file "$AMP_FILE" "Amp" + ;; + shai) + update_agent_file "$SHAI_FILE" "SHAI" + ;; + q) + update_agent_file "$Q_FILE" "Amazon Q Developer CLI" + ;; + bob) + update_agent_file "$BOB_FILE" "IBM Bob" + ;; + *) + log_error "Unknown agent type '$agent_type'" + log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|shai|q|bob|qoder" + exit 1 + ;; + esac +} + +update_all_existing_agents() { + local found_agent=false + + # Check each possible agent file and update if it exists + if [[ -f "$CLAUDE_FILE" ]]; then + update_agent_file "$CLAUDE_FILE" "Claude Code" + found_agent=true + fi + + if [[ -f "$GEMINI_FILE" ]]; then + update_agent_file "$GEMINI_FILE" "Gemini CLI" + found_agent=true + fi + + if [[ -f "$COPILOT_FILE" ]]; then + update_agent_file "$COPILOT_FILE" "GitHub Copilot" + found_agent=true + fi + + if [[ -f "$CURSOR_FILE" ]]; then + update_agent_file "$CURSOR_FILE" "Cursor IDE" + found_agent=true + fi + + if [[ -f "$QWEN_FILE" ]]; then + update_agent_file "$QWEN_FILE" "Qwen Code" + found_agent=true + fi + + if [[ -f "$AGENTS_FILE" ]]; then + update_agent_file "$AGENTS_FILE" "Codex/opencode" + found_agent=true + fi + + if [[ -f "$WINDSURF_FILE" ]]; then + update_agent_file "$WINDSURF_FILE" "Windsurf" + found_agent=true + fi + + if [[ -f "$KILOCODE_FILE" ]]; then + update_agent_file "$KILOCODE_FILE" "Kilo Code" + found_agent=true + fi + + if [[ -f "$AUGGIE_FILE" ]]; then + update_agent_file "$AUGGIE_FILE" "Auggie CLI" + found_agent=true + fi + + if [[ -f "$ROO_FILE" ]]; then + update_agent_file "$ROO_FILE" "Roo Code" + found_agent=true + fi + + if [[ -f "$CODEBUDDY_FILE" ]]; then + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" + found_agent=true + fi + + if [[ -f "$SHAI_FILE" ]]; then + update_agent_file "$SHAI_FILE" "SHAI" + found_agent=true + fi + + if [[ -f "$QODER_FILE" ]]; then + update_agent_file "$QODER_FILE" "Qoder CLI" + found_agent=true + fi + + if [[ -f "$Q_FILE" ]]; then + update_agent_file "$Q_FILE" "Amazon Q Developer CLI" + found_agent=true + fi + + if [[ -f "$BOB_FILE" ]]; then + update_agent_file "$BOB_FILE" "IBM Bob" + found_agent=true + fi + + # If no agent files exist, create a default Claude file + if [[ "$found_agent" == false ]]; then + log_info "No existing agent files found, creating default Claude file..." + update_agent_file "$CLAUDE_FILE" "Claude Code" + fi +} +print_summary() { + echo + log_info "Summary of changes:" + + if [[ -n "$NEW_LANG" ]]; then + echo " - Added language: $NEW_LANG" + fi + + if [[ -n "$NEW_FRAMEWORK" ]]; then + echo " - Added framework: $NEW_FRAMEWORK" + fi + + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then + echo " - Added database: $NEW_DB" + fi + + echo + + log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|shai|q|bob|qoder]" +} + +#============================================================================== +# Main Execution +#============================================================================== + +main() { + # Validate environment before proceeding + validate_environment + + log_info "=== Updating agent context files for feature $CURRENT_BRANCH ===" + + # Parse the plan file to extract project information + if ! parse_plan_data "$NEW_PLAN"; then + log_error "Failed to parse plan data" + exit 1 + fi + + # Process based on agent type argument + local success=true + + if [[ -z "$AGENT_TYPE" ]]; then + # No specific agent provided - update all existing agent files + log_info "No agent specified, updating all existing agent files..." + if ! update_all_existing_agents; then + success=false + fi + else + # Specific agent provided - update only that agent + log_info "Updating specific agent: $AGENT_TYPE" + if ! update_specific_agent "$AGENT_TYPE"; then + success=false + fi + fi + + # Print summary + print_summary + + if [[ "$success" == true ]]; then + log_success "Agent context update completed successfully" + exit 0 + else + log_error "Agent context update completed with errors" + exit 1 + fi +} + +# Execute main function if script is run directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi + diff --git a/.specify/templates/agent-file-template.md b/.specify/templates/agent-file-template.md new file mode 100644 index 00000000..4cc7fd66 --- /dev/null +++ b/.specify/templates/agent-file-template.md @@ -0,0 +1,28 @@ +# [PROJECT NAME] Development Guidelines + +Auto-generated from all feature plans. Last updated: [DATE] + +## Active Technologies + +[EXTRACTED FROM ALL PLAN.MD FILES] + +## Project Structure + +```text +[ACTUAL STRUCTURE FROM PLANS] +``` + +## Commands + +[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES] + +## Code Style + +[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE] + +## Recent Changes + +[LAST 3 FEATURES AND WHAT THEY ADDED] + + + diff --git a/.specify/templates/checklist-template.md b/.specify/templates/checklist-template.md new file mode 100644 index 00000000..0caeacf8 --- /dev/null +++ b/.specify/templates/checklist-template.md @@ -0,0 +1,40 @@ +# [CHECKLIST TYPE] Checklist: [FEATURE NAME] + +**Purpose**: [Brief description of what this checklist covers] +**Created**: [DATE] +**Feature**: [Link to spec.md or relevant documentation] + +**Note**: This checklist is generated by the `/speckit.checklist` command based on feature context and requirements. + + + +## [Category 1] + +- [ ] CHK001 First checklist item with clear action +- [ ] CHK002 Second checklist item +- [ ] CHK003 Third checklist item + +## [Category 2] + +- [ ] CHK004 Another category item +- [ ] CHK005 Item with specific criteria +- [ ] CHK006 Final item in this category + +## Notes + +- Check items off as completed: `[x]` +- Add comments or findings inline +- Link to relevant resources or documentation +- Items are numbered sequentially for easy reference diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md new file mode 100644 index 00000000..146d7163 --- /dev/null +++ b/.specify/templates/plan-template.md @@ -0,0 +1,105 @@ +# Implementation Plan: [FEATURE] + +**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link] +**Input**: Feature specification from `/specs/[###-feature-name]/spec.md` + +**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow. + +## Summary + +[Extract from feature spec: primary requirement + technical approach from research] + +## Technical Context + + + +**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION] +**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION] +**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A] +**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION] +**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION] +**Project Type**: [single/web/mobile - determines source structure] +**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION] +**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION] +**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION] + +## Constitution Check + +_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._ + +[Gates determined based on constitution file] + +## Project Structure + +### Documentation (this feature) + +```text +specs/[###-feature]/ +├── plan.md # This file (/speckit.plan command output) +├── research.md # Phase 0 output (/speckit.plan command) +├── data-model.md # Phase 1 output (/speckit.plan command) +├── quickstart.md # Phase 1 output (/speckit.plan command) +├── contracts/ # Phase 1 output (/speckit.plan command) +└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan) +``` + +### Source Code (repository root) + + + +```text +# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT) +src/ +├── models/ +├── services/ +├── cli/ +└── lib/ + +tests/ +├── contract/ +├── integration/ +└── unit/ + +# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected) +backend/ +├── src/ +│ ├── models/ +│ ├── services/ +│ └── api/ +└── tests/ + +frontend/ +├── src/ +│ ├── components/ +│ ├── pages/ +│ └── services/ +└── tests/ + +# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected) +api/ +└── [same as backend above] + +ios/ or android/ +└── [platform-specific structure: feature modules, UI flows, platform tests] +``` + +**Structure Decision**: [Document the selected structure and reference the real +directories captured above] + +## Complexity Tracking + +> **Fill ONLY if Constitution Check has violations that must be justified** + +| Violation | Why Needed | Simpler Alternative Rejected Because | +| -------------------------- | ------------------ | ------------------------------------ | +| [e.g., 4th project] | [current need] | [why 3 projects insufficient] | +| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] | diff --git a/.specify/templates/spec-template.md b/.specify/templates/spec-template.md new file mode 100644 index 00000000..dcfc69c8 --- /dev/null +++ b/.specify/templates/spec-template.md @@ -0,0 +1,115 @@ +# Feature Specification: [FEATURE NAME] + +**Feature Branch**: `[###-feature-name]` +**Created**: [DATE] +**Status**: Draft +**Input**: User description: "$ARGUMENTS" + +## User Scenarios & Testing _(mandatory)_ + + + +### User Story 1 - [Brief Title] (Priority: P1) + +[Describe this user journey in plain language] + +**Why this priority**: [Explain the value and why it has this priority level] + +**Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"] + +**Acceptance Scenarios**: + +1. **Given** [initial state], **When** [action], **Then** [expected outcome] +2. **Given** [initial state], **When** [action], **Then** [expected outcome] + +--- + +### User Story 2 - [Brief Title] (Priority: P2) + +[Describe this user journey in plain language] + +**Why this priority**: [Explain the value and why it has this priority level] + +**Independent Test**: [Describe how this can be tested independently] + +**Acceptance Scenarios**: + +1. **Given** [initial state], **When** [action], **Then** [expected outcome] + +--- + +### User Story 3 - [Brief Title] (Priority: P3) + +[Describe this user journey in plain language] + +**Why this priority**: [Explain the value and why it has this priority level] + +**Independent Test**: [Describe how this can be tested independently] + +**Acceptance Scenarios**: + +1. **Given** [initial state], **When** [action], **Then** [expected outcome] + +--- + +[Add more user stories as needed, each with an assigned priority] + +### Edge Cases + + + +- What happens when [boundary condition]? +- How does system handle [error scenario]? + +## Requirements _(mandatory)_ + + + +### Functional Requirements + +- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"] +- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"] +- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"] +- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"] +- **FR-005**: System MUST [behavior, e.g., "log all security events"] + +_Example of marking unclear requirements:_ + +- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?] +- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified] + +### Key Entities _(include if feature involves data)_ + +- **[Entity 1]**: [What it represents, key attributes without implementation] +- **[Entity 2]**: [What it represents, relationships to other entities] + +## Success Criteria _(mandatory)_ + + + +### Measurable Outcomes + +- **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"] +- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"] +- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"] +- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"] diff --git a/.specify/templates/tasks-template.md b/.specify/templates/tasks-template.md new file mode 100644 index 00000000..4ed0721d --- /dev/null +++ b/.specify/templates/tasks-template.md @@ -0,0 +1,250 @@ +--- +description: 'Task list template for feature implementation' +--- + +# Tasks: [FEATURE NAME] + +**Input**: Design documents from `/specs/[###-feature-name]/` +**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ + +**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification. + +**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) +- Include exact file paths in descriptions + +## Path Conventions + +- **Single project**: `src/`, `tests/` at repository root +- **Web app**: `backend/src/`, `frontend/src/` +- **Mobile**: `api/src/`, `ios/src/` or `android/src/` +- Paths shown below assume single project - adjust based on plan.md structure + + + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Project initialization and basic structure + +- [ ] T001 Create project structure per implementation plan +- [ ] T002 Initialize [language] project with [framework] dependencies +- [ ] T003 [P] Configure linting and formatting tools + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented + +**⚠️ CRITICAL**: No user story work can begin until this phase is complete + +Examples of foundational tasks (adjust based on your project): + +- [ ] T004 Setup database schema and migrations framework +- [ ] T005 [P] Implement authentication/authorization framework +- [ ] T006 [P] Setup API routing and middleware structure +- [ ] T007 Create base models/entities that all stories depend on +- [ ] T008 Configure error handling and logging infrastructure +- [ ] T009 Setup environment configuration management + +**Checkpoint**: Foundation ready - user story implementation can now begin in parallel + +--- + +## Phase 3: User Story 1 - [Title] (Priority: P1) 🎯 MVP + +**Goal**: [Brief description of what this story delivers] + +**Independent Test**: [How to verify this story works on its own] + +### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️ + +> **NOTE: Write these tests FIRST, ensure they FAIL before implementation** + +- [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test\_[name].py +- [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test\_[name].py + +### Implementation for User Story 1 + +- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py +- [ ] T013 [P] [US1] Create [Entity2] model in src/models/[entity2].py +- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013) +- [ ] T015 [US1] Implement [endpoint/feature] in src/[location]/[file].py +- [ ] T016 [US1] Add validation and error handling +- [ ] T017 [US1] Add logging for user story 1 operations + +**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently + +--- + +## Phase 4: User Story 2 - [Title] (Priority: P2) + +**Goal**: [Brief description of what this story delivers] + +**Independent Test**: [How to verify this story works on its own] + +### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️ + +- [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test\_[name].py +- [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test\_[name].py + +### Implementation for User Story 2 + +- [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py +- [ ] T021 [US2] Implement [Service] in src/services/[service].py +- [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py +- [ ] T023 [US2] Integrate with User Story 1 components (if needed) + +**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently + +--- + +## Phase 5: User Story 3 - [Title] (Priority: P3) + +**Goal**: [Brief description of what this story delivers] + +**Independent Test**: [How to verify this story works on its own] + +### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️ + +- [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test\_[name].py +- [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test\_[name].py + +### Implementation for User Story 3 + +- [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py +- [ ] T027 [US3] Implement [Service] in src/services/[service].py +- [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py + +**Checkpoint**: All user stories should now be independently functional + +--- + +[Add more user story phases as needed, following the same pattern] + +--- + +## Phase N: Polish & Cross-Cutting Concerns + +**Purpose**: Improvements that affect multiple user stories + +- [ ] TXXX [P] Documentation updates in docs/ +- [ ] TXXX Code cleanup and refactoring +- [ ] TXXX Performance optimization across all stories +- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/ +- [ ] TXXX Security hardening +- [ ] TXXX Run quickstart.md validation + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies - can start immediately +- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories +- **User Stories (Phase 3+)**: All depend on Foundational phase completion + - User stories can then proceed in parallel (if staffed) + - Or sequentially in priority order (P1 → P2 → P3) +- **Polish (Final Phase)**: Depends on all desired user stories being complete + +### User Story Dependencies + +- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories +- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable +- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable + +### Within Each User Story + +- Tests (if included) MUST be written and FAIL before implementation +- Models before services +- Services before endpoints +- Core implementation before integration +- Story complete before moving to next priority + +### Parallel Opportunities + +- All Setup tasks marked [P] can run in parallel +- All Foundational tasks marked [P] can run in parallel (within Phase 2) +- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows) +- All tests for a user story marked [P] can run in parallel +- Models within a story marked [P] can run in parallel +- Different user stories can be worked on in parallel by different team members + +--- + +## Parallel Example: User Story 1 + +```bash +# Launch all tests for User Story 1 together (if tests requested): +Task: "Contract test for [endpoint] in tests/contract/test_[name].py" +Task: "Integration test for [user journey] in tests/integration/test_[name].py" + +# Launch all models for User Story 1 together: +Task: "Create [Entity1] model in src/models/[entity1].py" +Task: "Create [Entity2] model in src/models/[entity2].py" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1: Setup +2. Complete Phase 2: Foundational (CRITICAL - blocks all stories) +3. Complete Phase 3: User Story 1 +4. **STOP and VALIDATE**: Test User Story 1 independently +5. Deploy/demo if ready + +### Incremental Delivery + +1. Complete Setup + Foundational → Foundation ready +2. Add User Story 1 → Test independently → Deploy/Demo (MVP!) +3. Add User Story 2 → Test independently → Deploy/Demo +4. Add User Story 3 → Test independently → Deploy/Demo +5. Each story adds value without breaking previous stories + +### Parallel Team Strategy + +With multiple developers: + +1. Team completes Setup + Foundational together +2. Once Foundational is done: + - Developer A: User Story 1 + - Developer B: User Story 2 + - Developer C: User Story 3 +3. Stories complete and integrate independently + +--- + +## Notes + +- [P] tasks = different files, no dependencies +- [Story] label maps task to specific user story for traceability +- Each user story should be independently completable and testable +- Verify tests fail before implementing +- Commit after each task or logical group +- Stop at any checkpoint to validate story independently +- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence From 35f73f9ce46d36a9a8183d1b2329998f94e77e5d Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 1 Apr 2026 14:46:41 +0900 Subject: [PATCH 3/9] =?UTF-8?q?[DOCS]=20=EC=97=90=EC=9D=B4=EC=A0=84?= =?UTF-8?q?=ED=8A=B8=20=EC=8A=A4=ED=82=AC=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit frontend-fundamentals, remotion-best-practices, vercel-composition-patterns, vercel-react-best-practices 스킬을 추가한다. skills-lock.json으로 버전을 고정한다. Co-Authored-By: Claude Sonnet 4.6 --- .agents/skills/frontend-fundamentals/SKILL.md | 80 + .../frontend-fundamentals/references/a11y.md | 221 + .../references/bundling.md | 192 + .../references/code-quality.md | 333 ++ .../references/debugging.md | 226 + .../skills/remotion-best-practices/SKILL.md | 61 + .../remotion-best-practices/rules/3d.md | 86 + .../rules/animations.md | 27 + .../remotion-best-practices/rules/assets.md | 78 + .../rules/assets/charts-bar-chart.tsx | 173 + .../assets/text-animations-typewriter.tsx | 100 + .../assets/text-animations-word-highlight.tsx | 103 + .../rules/audio-visualization.md | 198 + .../remotion-best-practices/rules/audio.md | 169 + .../rules/calculate-metadata.md | 134 + .../rules/can-decode.md | 81 + .../remotion-best-practices/rules/charts.md | 120 + .../rules/compositions.md | 154 + .../rules/display-captions.md | 184 + .../rules/extract-frames.md | 229 ++ .../remotion-best-practices/rules/ffmpeg.md | 38 + .../remotion-best-practices/rules/fonts.md | 152 + .../rules/get-audio-duration.md | 58 + .../rules/get-video-dimensions.md | 68 + .../rules/get-video-duration.md | 60 + .../remotion-best-practices/rules/gifs.md | 141 + .../remotion-best-practices/rules/images.md | 134 + .../rules/import-srt-captions.md | 69 + .../rules/light-leaks.md | 73 + .../remotion-best-practices/rules/lottie.md | 70 + .../remotion-best-practices/rules/maps.md | 412 ++ .../rules/measuring-dom-nodes.md | 34 + .../rules/measuring-text.md | 140 + .../rules/parameters.md | 109 + .../rules/sequencing.md | 118 + .../remotion-best-practices/rules/sfx.md | 30 + .../rules/subtitles.md | 36 + .../remotion-best-practices/rules/tailwind.md | 11 + .../rules/text-animations.md | 20 + .../remotion-best-practices/rules/timing.md | 179 + .../rules/transcribe-captions.md | 70 + .../rules/transitions.md | 197 + .../rules/transparent-videos.md | 106 + .../remotion-best-practices/rules/trimming.md | 51 + .../remotion-best-practices/rules/videos.md | 171 + .../rules/voiceover.md | 99 + .../vercel-composition-patterns/AGENTS.md | 946 +++++ .../vercel-composition-patterns/README.md | 60 + .../vercel-composition-patterns/SKILL.md | 89 + .../rules/_sections.md | 29 + .../rules/_template.md | 24 + .../rules/architecture-avoid-boolean-props.md | 100 + .../rules/architecture-compound-components.md | 112 + .../patterns-children-over-render-props.md | 87 + .../rules/patterns-explicit-variants.md | 100 + .../rules/react19-no-forwardref.md | 42 + .../rules/state-context-interface.md | 191 + .../rules/state-decouple-implementation.md | 113 + .../rules/state-lift-state.md | 125 + .../vercel-react-best-practices/AGENTS.md | 3648 +++++++++++++++++ .../vercel-react-best-practices/README.md | 123 + .../vercel-react-best-practices/SKILL.md | 146 + .../rules/_sections.md | 46 + .../rules/_template.md | 28 + .../rules/advanced-event-handler-refs.md | 55 + .../rules/advanced-init-once.md | 42 + .../rules/advanced-use-latest.md | 39 + .../rules/async-api-routes.md | 38 + .../async-cheap-condition-before-await.md | 37 + .../rules/async-defer-await.md | 82 + .../rules/async-dependencies.md | 51 + .../rules/async-parallel.md | 28 + .../rules/async-suspense-boundaries.md | 99 + .../rules/bundle-barrel-imports.md | 60 + .../rules/bundle-conditional.md | 31 + .../rules/bundle-defer-third-party.md | 49 + .../rules/bundle-dynamic-imports.md | 35 + .../rules/bundle-preload.md | 50 + .../rules/client-event-listeners.md | 74 + .../rules/client-localstorage-schema.md | 71 + .../rules/client-passive-event-listeners.md | 48 + .../rules/client-swr-dedup.md | 56 + .../rules/js-batch-dom-css.md | 107 + .../rules/js-cache-function-results.md | 80 + .../rules/js-cache-property-access.md | 28 + .../rules/js-cache-storage.md | 70 + .../rules/js-combine-iterations.md | 32 + .../rules/js-early-exit.md | 50 + .../rules/js-flatmap-filter.md | 60 + .../rules/js-hoist-regexp.md | 45 + .../rules/js-index-maps.md | 37 + .../rules/js-length-check-first.md | 49 + .../rules/js-min-max-loop.md | 82 + .../rules/js-request-idle-callback.md | 105 + .../rules/js-set-map-lookups.md | 24 + .../rules/js-tosorted-immutable.md | 57 + .../rules/rendering-activity.md | 26 + .../rules/rendering-animate-svg-wrapper.md | 47 + .../rules/rendering-conditional-render.md | 40 + .../rules/rendering-content-visibility.md | 38 + .../rules/rendering-hoist-jsx.md | 46 + .../rules/rendering-hydration-no-flicker.md | 82 + .../rendering-hydration-suppress-warning.md | 30 + .../rules/rendering-resource-hints.md | 85 + .../rules/rendering-script-defer-async.md | 68 + .../rules/rendering-svg-precision.md | 28 + .../rules/rendering-usetransition-loading.md | 75 + .../rules/rerender-defer-reads.md | 39 + .../rules/rerender-dependencies.md | 45 + .../rules/rerender-derived-state-no-effect.md | 40 + .../rules/rerender-derived-state.md | 29 + .../rules/rerender-functional-setstate.md | 74 + .../rules/rerender-lazy-state-init.md | 58 + .../rules/rerender-memo-with-default-value.md | 38 + .../rules/rerender-memo.md | 44 + .../rules/rerender-move-effect-to-event.md | 45 + .../rules/rerender-no-inline-components.md | 82 + .../rerender-simple-expression-in-memo.md | 35 + .../rules/rerender-split-combined-hooks.md | 64 + .../rules/rerender-transitions.md | 40 + .../rules/rerender-use-deferred-value.md | 59 + .../rerender-use-ref-transient-values.md | 73 + .../rules/server-after-nonblocking.md | 73 + .../rules/server-auth-actions.md | 96 + .../rules/server-cache-lru.md | 41 + .../rules/server-cache-react.md | 76 + .../rules/server-dedup-props.md | 65 + .../rules/server-hoist-static-io.md | 149 + .../rules/server-parallel-fetching.md | 83 + .../rules/server-parallel-nested-fetching.md | 34 + .../rules/server-serialization.md | 38 + skills-lock.json | 25 + 132 files changed, 15385 insertions(+) create mode 100644 .agents/skills/frontend-fundamentals/SKILL.md create mode 100644 .agents/skills/frontend-fundamentals/references/a11y.md create mode 100644 .agents/skills/frontend-fundamentals/references/bundling.md create mode 100644 .agents/skills/frontend-fundamentals/references/code-quality.md create mode 100644 .agents/skills/frontend-fundamentals/references/debugging.md create mode 100644 .agents/skills/remotion-best-practices/SKILL.md create mode 100644 .agents/skills/remotion-best-practices/rules/3d.md create mode 100644 .agents/skills/remotion-best-practices/rules/animations.md create mode 100644 .agents/skills/remotion-best-practices/rules/assets.md create mode 100644 .agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx create mode 100644 .agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx create mode 100644 .agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx create mode 100644 .agents/skills/remotion-best-practices/rules/audio-visualization.md create mode 100644 .agents/skills/remotion-best-practices/rules/audio.md create mode 100644 .agents/skills/remotion-best-practices/rules/calculate-metadata.md create mode 100644 .agents/skills/remotion-best-practices/rules/can-decode.md create mode 100644 .agents/skills/remotion-best-practices/rules/charts.md create mode 100644 .agents/skills/remotion-best-practices/rules/compositions.md create mode 100644 .agents/skills/remotion-best-practices/rules/display-captions.md create mode 100644 .agents/skills/remotion-best-practices/rules/extract-frames.md create mode 100644 .agents/skills/remotion-best-practices/rules/ffmpeg.md create mode 100644 .agents/skills/remotion-best-practices/rules/fonts.md create mode 100644 .agents/skills/remotion-best-practices/rules/get-audio-duration.md create mode 100644 .agents/skills/remotion-best-practices/rules/get-video-dimensions.md create mode 100644 .agents/skills/remotion-best-practices/rules/get-video-duration.md create mode 100644 .agents/skills/remotion-best-practices/rules/gifs.md create mode 100644 .agents/skills/remotion-best-practices/rules/images.md create mode 100644 .agents/skills/remotion-best-practices/rules/import-srt-captions.md create mode 100644 .agents/skills/remotion-best-practices/rules/light-leaks.md create mode 100644 .agents/skills/remotion-best-practices/rules/lottie.md create mode 100644 .agents/skills/remotion-best-practices/rules/maps.md create mode 100644 .agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md create mode 100644 .agents/skills/remotion-best-practices/rules/measuring-text.md create mode 100644 .agents/skills/remotion-best-practices/rules/parameters.md create mode 100644 .agents/skills/remotion-best-practices/rules/sequencing.md create mode 100644 .agents/skills/remotion-best-practices/rules/sfx.md create mode 100644 .agents/skills/remotion-best-practices/rules/subtitles.md create mode 100644 .agents/skills/remotion-best-practices/rules/tailwind.md create mode 100644 .agents/skills/remotion-best-practices/rules/text-animations.md create mode 100644 .agents/skills/remotion-best-practices/rules/timing.md create mode 100644 .agents/skills/remotion-best-practices/rules/transcribe-captions.md create mode 100644 .agents/skills/remotion-best-practices/rules/transitions.md create mode 100644 .agents/skills/remotion-best-practices/rules/transparent-videos.md create mode 100644 .agents/skills/remotion-best-practices/rules/trimming.md create mode 100644 .agents/skills/remotion-best-practices/rules/videos.md create mode 100644 .agents/skills/remotion-best-practices/rules/voiceover.md create mode 100644 .agents/skills/vercel-composition-patterns/AGENTS.md create mode 100644 .agents/skills/vercel-composition-patterns/README.md create mode 100644 .agents/skills/vercel-composition-patterns/SKILL.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/_sections.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/_template.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/state-context-interface.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/state-lift-state.md create mode 100644 .agents/skills/vercel-react-best-practices/AGENTS.md create mode 100644 .agents/skills/vercel-react-best-practices/README.md create mode 100644 .agents/skills/vercel-react-best-practices/SKILL.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/_sections.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/_template.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/advanced-init-once.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-api-routes.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-cheap-condition-before-await.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-defer-await.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-dependencies.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-parallel.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-conditional.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-preload.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/client-event-listeners.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-cache-storage.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-early-exit.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-flatmap-filter.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-index-maps.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-length-check-first.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-request-idle-callback.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-activity.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-resource-hints.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-memo.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-split-combined-hooks.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-transitions.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-use-deferred-value.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-auth-actions.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-cache-lru.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-cache-react.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-dedup-props.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-parallel-nested-fetching.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-serialization.md create mode 100644 skills-lock.json diff --git a/.agents/skills/frontend-fundamentals/SKILL.md b/.agents/skills/frontend-fundamentals/SKILL.md new file mode 100644 index 00000000..864caa74 --- /dev/null +++ b/.agents/skills/frontend-fundamentals/SKILL.md @@ -0,0 +1,80 @@ +--- +name: frontend-fundamentals +description: | + 토스(Toss) Frontend Fundamentals 기반의 프론트엔드 개발 종합 가이드. 코드 품질(가독성, 예측 가능성, 응집도, 결합도), 번들링(Webpack, 트리셰이킹, 코드 스플리팅), 접근성(A11y, 스크린 리더, ARIA), 디버깅(진단, 재현, 수정, 재발 방지) 4개 도메인을 포괄한다. + + 반드시 이 스킬을 사용해야 하는 상황: + - 프론트엔드 코드 리뷰, 리팩토링, 코드 품질 개선 요청 시 + - React/TypeScript 컴포넌트의 가독성, 응집도, 결합도 관련 질문 시 + - Webpack, Rollup, Vite 등 번들링 설정, 트리셰이킹, 코드 스플리팅 질문 시 + - 웹 접근성(a11y), ARIA, 스크린 리더, 키보드 내비게이션 관련 질문 시 + - 프론트엔드 디버깅 전략, 에러 메시지 분석, 버그 재현/수정/방지 질문 시 + - "좋은 코드란?", "변경하기 쉬운 코드", "클린 코드" 등의 추상적 질문 시 + - 면접 준비에서 프론트엔드 기초 개념을 정리하고 싶을 때 + - 컴포넌트 분리 전략, Props Drilling, 매직 넘버, 삼항 연산자 등 구체적 패턴 질문 시 + - ESM vs CJS, sideEffects, 번들 분석, HMR, 소스맵 등 빌드 관련 질문 시 + - 버튼 안에 버튼, 테이블 행 클릭, 폼 접근성 등 UI 접근성 안티패턴 질문 시 +--- + +# Frontend Fundamentals 스킬 + +토스(Toss)의 **Frontend Fundamentals** 프로젝트에서 다루는 프론트엔드 핵심 지식을 종합한 스킬이다. 4개 도메인으로 구성되어 있으며, 각 도메인의 상세 내용은 `references/` 디렉토리의 개별 파일에서 확인한다. + +## 도메인 구조 + +### 1. 코드 품질 (Code Quality) +좋은 프론트엔드 코드 = **변경하기 쉬운 코드**. 4가지 기준으로 판단한다: +- **가독성**: 맥락 줄이기, 이름 붙이기, 위에서 아래로 읽히게 하기 +- **예측 가능성**: 이름 겹침 방지, 반환 타입 통일, 숨은 로직 드러내기 +- **응집도**: 함께 수정되는 코드를 가까이 두기, 매직 넘버 제거 +- **결합도**: 책임 분리, 중복 허용, Props Drilling 제거 + +→ 상세: `references/code-quality.md`를 읽어라 + +### 2. 번들링 (Bundling) +여러 파일을 하나로 묶어 웹 성능을 최적화하는 과정: +- **기본 개념**: 번들링이란, 번들러란 +- **Webpack 튜토리얼**: 첫 번들 → 모듈 → TS → React → 스타일 → 에셋 → 플러그인 → 개발 서버 +- **심화**: 진입점, 경로 탐색, 로더, 플러그인, 출력 +- **최적화**: 코드 스플리팅, 트리 셰이킹, 번들 분석 +- **개발 환경**: 개발 서버, HMR, 소스맵 + +→ 상세: `references/bundling.md`를 읽어라 + +### 3. 접근성 (A11y) +모든 사용자가 웹을 편리하게 이용하도록 돕는 기본 원칙: +- **핵심 3요소**: 역할(Role), 레이블(Label), 상태(State) +- **4가지 원칙**: 올바른 구조, 정확한 의미 전달, 예측 가능한 동작, 시각 정보 보완 +- **UI별 접근성**: 탭, 아코디언, 모달, 라디오, 체크박스, 스위치 +- **실전 가이드**: 버튼 중첩 금지, 테이블 행 클릭, 레이블 필수, 폼 감싸기 +- **심화**: ESLint 접근성 규칙, 디자인 시스템 결합 + +→ 상세: `references/a11y.md`를 읽어라 + +### 4. 디버깅 (Debug) +버그를 찾고 해결하는 체계적 4단계 프로세스: +- **진단하기**: 에러 메시지 분석, 작업 지도 그리기 +- **재현하기**: 간단하게 재현, 디버거/콘솔 활용, 경계값 테스트, 자동화, 경로 추적 +- **수정하기**: 근본 원인 수정, 순수 함수 분리, 데드코드 제거 +- **재발 방지**: 에러 로그, 버그 리포트, 팀 공유 및 공통 유틸 반영 + +→ 상세: `references/debugging.md`를 읽어라 + +## 사용 방법 + +1. 사용자의 질문이 어떤 도메인에 해당하는지 파악한다 +2. 해당 도메인의 reference 파일을 읽는다 +3. reference 파일의 원칙과 예제를 기반으로 구체적이고 실용적인 답변을 제공한다 +4. 코드 예시가 필요하면 Before/After 패턴으로 보여준다 +5. 여러 도메인이 겹치는 질문이면 관련 reference를 모두 참고한다 + +## 핵심 철학 + +- 4가지 기준(가독성, 예측 가능성, 응집도, 결합도)은 서로 상충할 수 있다 +- 상황에 따라 어떤 가치를 우선할지 깊이 고민해야 한다 +- 응집도를 높이면 가독성이 떨어질 수 있고, 중복을 허용하면 결합도는 낮아지지만 응집도가 떨어질 수 있다 +- "정답"보다는 "트레이드오프를 이해하고 맥락에 맞게 결정하는 것"이 중요하다 + +## 출처 + +모든 내용은 [Frontend Fundamentals](https://frontend-fundamentals.com) 프로젝트 기반이다. diff --git a/.agents/skills/frontend-fundamentals/references/a11y.md b/.agents/skills/frontend-fundamentals/references/a11y.md new file mode 100644 index 00000000..1d82f7b2 --- /dev/null +++ b/.agents/skills/frontend-fundamentals/references/a11y.md @@ -0,0 +1,221 @@ +# 접근성 (A11y) 상세 가이드 + +출처: https://frontend-fundamentals.com/a11y/ + +## 접근성이란 + +접근성(Accessibility, A11y)은 모든 사용자가 더 쉽고 편리하게 웹을 사용할 수 있도록 돕는 기본 원칙이다. 프론트엔드 개발자는 HTML 구조를 만들고 인터랙션을 정의하므로 **접근성의 출발점이자 핵심 역할**을 맡고 있다. + +--- + +## 스크린 리더의 3요소 + +스크린 리더는 화면 속 요소와 정보를 음성으로 전달하는 보조 기술이다. 3가지 핵심 정보를 읽는다: + +1. **역할(Role)**: 요소가 어떤 종류인지 (예: 버튼, 입력창, 스위치) +2. **레이블(Label)**: 컴포넌트의 이름, 어떤 기능인지 설명 +3. **상태(State)**: 현재 상태 (예: 활성화됨, 꺼짐, 선택됨) + +### 읽는 순서 예시 +```html +
+``` +스크린 리더가 읽는 순서: +1. "마케팅 알림" (레이블) +2. "체크상자" (역할) +3. "선택됨" (상태) +4. "설정을 끄거나 켜려면 이중탭 하십시오" (자동 설명) + +### 탭 목록 예시 +```tsx +
+ + +
+``` +스크린 리더: "메뉴, 탭그룹 → 첫번째 항목, 탭, 선택됨 → 두번째 항목, 탭" + +--- + +## 접근성 개발 입문: 역할, 레이블, 상태 + +### 역할 (Role) +HTML 시맨틱 태그를 사용하면 역할이 자동으로 부여된다: +- ` +``` + +방법 2: `aria-label` +```html + +``` + +방법 3: `aria-labelledby` +```html +

알림 설정

+
...
+``` + +### 상태 (State) +동적으로 변하는 요소의 현재 상태를 전달한다: +- `aria-checked`: 체크박스/라디오/스위치의 선택 여부 +- `aria-selected`: 탭/옵션의 선택 여부 +- `aria-expanded`: 아코디언/드롭다운의 펼침 여부 +- `aria-disabled`: 비활성화 여부 +- `aria-hidden`: 스크린 리더에서 숨김 여부 + +--- + +## 4가지 핵심 원칙 + +### 원칙 1: 올바른 구조 만들기 + +#### 버튼 안에 버튼 넣지 않기 +HTML 명세상 ` + +``` + +**해결**: 카드 전체를 `
`로 만들고 내부 버튼만 ` +``` + +**Good**: +```html + +``` + +#### 같은 이름의 요소에는 설명 추가하기 +여러 "더보기" 버튼이 있으면 어떤 "더보기"인지 구별할 수 없다. `aria-describedby` 또는 더 구체적인 `aria-label`을 사용한다. + +### 원칙 3: 예측 가능한 동작 만들기 + +#### 버튼의 역할과 동작이 일치하게 만들기 +`
`으로 버튼을 흉내 내면 안 된다. 키보드 이벤트(`Enter`, `Space`)가 작동하지 않고, 스크린 리더가 버튼으로 인식하지 못한다. + +**Bad**: +```html +
클릭
+``` + +**Good**: +```html + +``` + +#### 입력 요소는 `
`으로 감싸기 +`` 태그로 감싸면 Enter 키로 제출, 자동완성, 스크린 리더의 폼 모드 등이 자동으로 작동한다. + +### 원칙 4: 시각 정보 보완하기 + +#### 이미지와 아이콘에 대체 텍스트 제공하기 +- 정보를 전달하는 이미지: `alt` 텍스트 필수 +- 장식적 이미지: `alt=""` (빈 문자열) 또는 `aria-hidden="true"` +- 아이콘 버튼: `aria-label` 사용 + +```html + +2024년 매출 추이 그래프 + + + + + + +``` + +--- + +## UI 요소별 접근성 기초 + +### 탭 (Tab) +```tsx +
+ + +
+
일반 설정 내용
+``` +키보드: 좌우 화살표로 탭 이동, Enter/Space로 선택 + +### 아코디언 (Accordion) +```html +

+ +

+
내용
+``` + +### 모달 (Modal) +- `role="dialog"` 또는 `role="alertdialog"` +- `aria-modal="true"` +- 포커스 트랩: 모달 내부에서만 Tab 이동 +- ESC로 닫기 +- 열릴 때 첫 번째 포커스 가능 요소로 포커스 이동 +- 닫힐 때 트리거 요소로 포커스 복원 + +### 라디오 (Radio) +```html +
+
카드
+
계좌이체
+
+``` +키보드: 화살표로 이동, 선택과 포커스가 동시에 이동 + +### 체크박스 (Checkbox) +- `aria-checked`: `"true"`, `"false"`, `"mixed"` (부분 선택) +- Space로 토글 + +### 스위치 (Switch) +```html + +``` + +--- + +## 심화: ESLint로 접근성 개선하기 + +### 주요 규칙 +`eslint-plugin-jsx-a11y` 플러그인의 주요 규칙: +- `alt-text`: ``에 `alt` 속성 필수 +- `anchor-is-valid`: `
`에 유효한 `href` 필수 +- `click-events-have-key-events`: `onClick`에 키보드 이벤트 동반 +- `no-static-element-interactions`: 비인터랙티브 요소에 이벤트 핸들러 금지 +- `label-has-associated-control`: `