diff --git a/.claude/commands/eureka/deploy-logs.md b/.claude/commands/eureka/deploy-logs.md new file mode 100644 index 0000000..9ed7e7c --- /dev/null +++ b/.claude/commands/eureka/deploy-logs.md @@ -0,0 +1,63 @@ +--- +description: View deployment logs from Eureka platform +allowed-tools: Read, Bash, Glob +--- + +# Eureka Deploy Logs + +**Input**: "$ARGUMENTS" + +--- + +## PURPOSE + +View the deployment logs from the Eureka platform to debug issues or monitor progress. + +--- + +## EXECUTION FLOW + +### ═══════════════════════════════════════════════════════════════ +### PHASE 1: Fetch Logs +### ═══════════════════════════════════════════════════════════════ + +#### 1.1: Run Logs Command + +```bash +# Default: last 100 lines +eureka deploy logs + +# Custom tail count +eureka deploy logs --tail 200 +``` + +#### 1.2: Parse Arguments + +If `$ARGUMENTS` contains a number, use it as tail count: +```bash +TAIL_COUNT="${ARGUMENTS:-100}" +eureka deploy logs --tail "$TAIL_COUNT" +``` + +--- + +## ARGUMENTS + +| Argument | Default | Description | +|----------|---------|-------------| +| `[tail]` | `100` | Number of log lines to show | +| `--id ` | Latest | Specific deployment ID | +| `--follow` | `false` | Follow logs in real-time | + +## EXAMPLES + +```bash +# View last 100 lines +/eureka:deploy-logs + +# View last 500 lines +/eureka:deploy-logs 500 + +# View specific deployment +/eureka:deploy-logs --id dep_abc123 +``` diff --git a/.claude/commands/eureka/deploy-status.md b/.claude/commands/eureka/deploy-status.md new file mode 100644 index 0000000..ab53cff --- /dev/null +++ b/.claude/commands/eureka/deploy-status.md @@ -0,0 +1,55 @@ +--- +description: Check deployment status on Eureka platform +allowed-tools: Read, Bash, Glob +--- + +# Eureka Deploy Status + +**Input**: "$ARGUMENTS" + +--- + +## PURPOSE + +Check the current deployment status of the application on the Eureka platform. + +--- + +## EXECUTION FLOW + +### ═══════════════════════════════════════════════════════════════ +### PHASE 1: Check Status +### ═══════════════════════════════════════════════════════════════ + +#### 1.1: Run Status Command + +```bash +eureka deploy status --verbose +``` + +#### 1.2: Display Results + +The command will show: +- Current deployment status (pending, building, deploying, deployed, failed) +- Version information +- Environment +- Timestamps +- Deployment URL (if deployed) + +--- + +## ARGUMENTS + +| Argument | Default | Description | +|----------|---------|-------------| +| `--verbose` | `false` | Show detailed logs | + +## EXAMPLES + +```bash +# Check current deployment status +/eureka:deploy-status + +# Check with verbose output +/eureka:deploy-status --verbose +``` diff --git a/.claude/commands/eureka/deploy.md b/.claude/commands/eureka/deploy.md new file mode 100644 index 0000000..e219771 --- /dev/null +++ b/.claude/commands/eureka/deploy.md @@ -0,0 +1,279 @@ +--- +description: Deploy application to Eureka platform (creates app if needed) +allowed-tools: Read, Write, Edit, Bash, Glob, Grep +--- + +# Eureka Deploy + +**Input**: "$ARGUMENTS" + +--- + +## PURPOSE + +Deploy the current project to the Eureka platform. If no `app_id` is configured, automatically creates a new directory app first, then triggers the deployment. + +--- + +## ⛔ CRITICAL RULES + +### MUST DO +1. **MUST** check for existing `app_id` in `.claude/eureka-factory.yaml` first +2. **MUST** create a new app via API if no `app_id` exists +3. **MUST** save the new `app_id` to config after creation +4. **MUST** display deployment status after triggering + +### CANNOT DO +1. **CANNOT** deploy without valid API key +2. **CANNOT** skip app creation if `app_id` is missing +3. **CANNOT** proceed if API calls fail + +--- + +## EXECUTION FLOW + +### ═══════════════════════════════════════════════════════════════ +### PHASE 1: Configuration Check +### ═══════════════════════════════════════════════════════════════ + +#### 1.1: Display Start Banner + +``` +╔══════════════════════════════════════════════════════════════╗ +║ 🚀 EUREKA DEPLOY ║ +╠══════════════════════════════════════════════════════════════╣ +║ Deploying to Eureka Platform... ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +#### 1.2: Check Configuration + +Read the configuration file: +```bash +# Check if config exists +cat .claude/eureka-factory.yaml 2>/dev/null || cat .claude/eureka-factory.yml 2>/dev/null || echo "NO_CONFIG" +``` + +**Extract from config:** +- `api_key` - Required for all operations +- `app_id` - If exists, skip app creation +- `api_endpoint` - Optional custom endpoint + +#### 1.3: Validate API Key + +If no `api_key` found: +``` +╔══════════════════════════════════════════════════════════════╗ +║ ❌ NO API KEY CONFIGURED ║ +╠══════════════════════════════════════════════════════════════╣ +║ Run `eureka setup` to configure your credentials. ║ +╚══════════════════════════════════════════════════════════════╝ +``` +**STOP EXECUTION** + +--- + +### ═══════════════════════════════════════════════════════════════ +### PHASE 2: App Creation (if needed) +### ═══════════════════════════════════════════════════════════════ + +#### 2.1: Check for app_id + +If `app_id` exists in config → **SKIP TO PHASE 3** + +If `app_id` is missing: + +``` +╔══════════════════════════════════════════════════════════════╗ +║ 📁 CREATING DIRECTORY APP ║ +╠══════════════════════════════════════════════════════════════╣ +║ No app_id found. Creating new app on Eureka... ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +#### 2.2: Determine App Name + +Use the project directory name as the default app name: +```bash +APP_NAME=$(basename $(pwd)) +echo "App name: $APP_NAME" +``` + +Or use argument if provided: `$ARGUMENTS` as app name + +#### 2.3: Create App via API + +```bash +# Create app using eureka CLI +eureka deploy trigger --name "$APP_NAME" --type other --yes +``` + +**If the command is not available, use direct API call:** + +```bash +API_KEY="" +API_ENDPOINT="" + +curl -X POST "${API_ENDPOINT}/v1/apps" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: ${API_KEY}" \ + -d "{\"name\": \"${APP_NAME}\", \"type\": \"other\"}" +``` + +#### 2.4: Save app_id to Config + +Extract `app_id` from API response and update config: + +```yaml +# .claude/eureka-factory.yaml +api_key: +project_id: +repo_id: +app_id: # Add this line +``` + +``` +╔══════════════════════════════════════════════════════════════╗ +║ ✅ APP CREATED ║ +╠══════════════════════════════════════════════════════════════╣ +║ App ID: ║ +║ Saved to: .claude/eureka-factory.yaml ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +--- + +### ═══════════════════════════════════════════════════════════════ +### PHASE 3: Trigger Deployment +### ═══════════════════════════════════════════════════════════════ + +#### 3.1: Trigger Deploy + +```bash +# Using eureka CLI +eureka deploy trigger --yes + +# Or direct API call +curl -X POST "${API_ENDPOINT}/v1/apps/${APP_ID}/deployments" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: ${API_KEY}" \ + -d '{"environment": "production"}' +``` + +#### 3.2: Display Deployment Status + +``` +╔══════════════════════════════════════════════════════════════╗ +║ ✅ DEPLOYMENT TRIGGERED ║ +╠══════════════════════════════════════════════════════════════╣ +║ Deployment ID: ║ +║ Status: PENDING ║ +║ Environment: production ║ +║ Version: ║ +╠══════════════════════════════════════════════════════════════╣ +║ Use `/eureka:deploy-status` to check progress ║ +║ Use `/eureka:deploy-logs` to view logs ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +--- + +## ARGUMENTS + +| Argument | Default | Description | +|----------|---------|-------------| +| `[app-name]` | Directory name | Name for new app (only used if creating) | +| `--env ` | `production` | Deployment environment | +| `--branch ` | Current branch | Git branch to deploy | +| `--force` | `false` | Force deploy even if already deploying | + +## EXAMPLES + +```bash +# Deploy current project (creates app if needed) +/eureka:deploy + +# Deploy with custom app name +/eureka:deploy my-awesome-app + +# Deploy specific branch to staging +/eureka:deploy --env staging --branch develop + +# Force redeploy +/eureka:deploy --force +``` + +--- + +## ERROR HANDLING + +### No Configuration +``` +❌ No configuration found. +Run `eureka setup` to configure credentials. +``` + +### App Creation Failed +``` +❌ Failed to create app: +Check your API key and try again. +``` + +### Deployment Failed +``` +❌ Deployment failed: +Use `/eureka:deploy-logs` to see details. +``` + +--- + +## FLOW DIAGRAM + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ /eureka:deploy │ +├─────────────────────────────────────────────────────────────────┤ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ Check Config │ │ +│ └────────┬────────┘ │ +│ │ │ +│ ┌─────────▼─────────┐ │ +│ │ Has API Key? │ │ +│ └─────────┬─────────┘ │ +│ │ │ +│ NO │ YES │ +│ ┌────────────────────┼────────────────────┐ │ +│ ▼ ▼ │ +│ ┌───────────┐ ┌─────────────────┐ │ +│ │ ERROR │ │ Has app_id? │ │ +│ │ No Key │ └────────┬────────┘ │ +│ └───────────┘ │ │ +│ NO │ YES │ +│ ┌───────────────────┼──────────┐ │ +│ ▼ ▼ │ +│ ┌─────────────────┐ ┌──────────────┐ +│ │ Create App │ │ │ +│ │ via API │ │ │ +│ └────────┬────────┘ │ │ +│ │ │ │ +│ ▼ │ │ +│ ┌─────────────────┐ │ │ +│ │ Save app_id │ │ │ +│ │ to Config │ │ │ +│ └────────┬────────┘ │ │ +│ │ │ │ +│ └───────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ Trigger Deploy │ │ +│ └────────┬────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ Show Status │ │ +│ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` diff --git a/.claude/commands/eureka/index.md b/.claude/commands/eureka/index.md new file mode 100644 index 0000000..be575e1 --- /dev/null +++ b/.claude/commands/eureka/index.md @@ -0,0 +1,595 @@ +--- +description: Generate comprehensive project documentation for engineers and non-engineers +allowed-tools: Read, Write, Edit, Bash, Task, TodoWrite, Glob, Grep +--- + +# Eureka Index - Project Documentation Generator + +**Input**: "$ARGUMENTS" + +--- + +## PURPOSE + +Generate comprehensive, dual-audience documentation by analyzing the current project structure using **parallel agent execution**. The output is designed to be understandable for **both engineers and non-engineers**. + +### Documentation Layers + +| Layer | Audience | Content | +|-------|----------|---------| +| Executive Summary | Everyone | Project purpose, value, capabilities | +| Architecture Overview | Everyone | Visual diagrams, technology stack | +| Getting Started | Semi-technical | Setup, basic usage, configuration | +| Feature Guide | Non-engineers | Plain-language feature descriptions | +| API Reference | Engineers | Endpoints, schemas, authentication | +| Component Catalog | Engineers | Props, interfaces, usage examples | +| Data Models | Both | ER diagrams + plain descriptions | +| Glossary | Non-engineers | Technical terms explained | + +--- + +## EXECUTION ARCHITECTURE + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ PARALLEL EXECUTION PIPELINE │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ PHASE 1: PARALLEL ANALYSIS (run_in_background: true) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ +│ │ Structure │ │ API │ │ Components │ │ Models │ │ +│ │ Analyzer │ │ Analyzer │ │ Analyzer │ │ Analyzer │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └─────┬──────┘ │ +│ │ │ │ │ │ +│ ▼ ▼ ▼ ▼ │ +│ PHASE 2: SYNCHRONIZATION │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Merge & Create Unified Analysis │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ PHASE 3: PARALLEL DOCUMENTATION (run_in_background: true) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ +│ │ Main Doc │ │ API Docs │ │ Components │ │ Quick │ │ +│ │ Generator │ │ Generator │ │ Generator │ │ Reference │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └─────┬──────┘ │ +│ │ │ │ │ │ +│ ▼ ▼ ▼ ▼ │ +│ PHASE 4: FINALIZATION │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ HTML Generation + Validation + Summary │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## ⛔ CRITICAL RULES + +### MUST DO +1. **MUST** launch analysis agents in parallel using `run_in_background: true` +2. **MUST** wait for all analysis agents before synchronization +3. **MUST** launch documentation agents in parallel after synchronization +4. **MUST** include both technical and non-technical descriptions +5. **MUST** validate generated documentation against actual code + +### CANNOT DO +1. **CANNOT** make up features that don't exist +2. **CANNOT** skip the parallel analysis phase +3. **CANNOT** generate docs without synchronizing analysis results + +--- + +## EXECUTION FLOW + +### ═══════════════════════════════════════════════════════════════ +### PHASE 1: Parallel Analysis +### ═══════════════════════════════════════════════════════════════ + +#### 1.1: Display Start Banner & Setup +``` +╔══════════════════════════════════════════════════════════════╗ +║ 📚 EUREKA INDEX - Parallel Documentation Generator ║ +╠══════════════════════════════════════════════════════════════╣ +║ Launching parallel analysis agents... ║ +║ Output: Dual-audience documentation (Engineer + Non-Engineer)║ +╚══════════════════════════════════════════════════════════════╝ +``` + +```bash +OUTPUT_DIR="${ARGUMENTS:-docs}" +mkdir -p "$OUTPUT_DIR" +echo "📁 Output directory: $OUTPUT_DIR" +``` + +#### 1.2: Launch Parallel Analysis Agents + +**CRITICAL: Launch ALL four agents in a SINGLE message with multiple Task tool calls:** + +``` +Launch these 4 Task agents IN PARALLEL (single message, multiple tool calls): + +┌─────────────────────────────────────────────────────────────────┐ +│ AGENT 1: Structure Analyzer │ +├─────────────────────────────────────────────────────────────────┤ +│ Task tool with: │ +│ subagent_type: "Explore" │ +│ run_in_background: true │ +│ prompt: | │ +│ # PROJECT STRUCTURE ANALYSIS │ +│ │ +│ Analyze the project structure and return findings. │ +│ │ +│ ## Tasks │ +│ 1. Identify project type (package.json, requirements.txt, │ +│ Cargo.toml, go.mod, pom.xml) │ +│ 2. Extract metadata (name, version, description) │ +│ 3. Map directory structure with purposes │ +│ 4. Identify tech stack (language, framework, database) │ +│ 5. List key dependencies with plain English purposes │ +│ │ +│ ## Output Format (YAML) │ +│ ```yaml │ +│ project: │ +│ name: "..." │ +│ version: "..." │ +│ description: "..." │ +│ type: "node|python|rust|go|java|other" │ +│ tech_stack: │ +│ language: "..." │ +│ framework: "..." │ +│ database: "..." │ +│ structure: │ +│ directories: │ +│ - path: "..." │ +│ purpose: "..." │ +│ file_count: N │ +│ dependencies: │ +│ - name: "..." │ +│ purpose: "plain English" │ +│ ``` │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ AGENT 2: API Analyzer │ +├─────────────────────────────────────────────────────────────────┤ +│ Task tool with: │ +│ subagent_type: "Explore" │ +│ run_in_background: true │ +│ prompt: | │ +│ # API ENDPOINTS ANALYSIS │ +│ │ +│ Find and analyze all API endpoints in the project. │ +│ │ +│ ## Search Patterns │ +│ - Next.js App Router: app/api/**/route.ts │ +│ - Next.js Pages: pages/api/**/*.ts │ +│ - Express: router.get/post/put/delete │ +│ - FastAPI: @app.get/post/put/delete │ +│ - GraphQL: Query/Mutation resolvers │ +│ │ +│ ## Output Format (YAML) │ +│ ```yaml │ +│ api_endpoints: │ +│ - method: "GET|POST|PUT|DELETE" │ +│ path: "/api/..." │ +│ handler_file: "path/to/file.ts" │ +│ description: "plain English" │ +│ request_body: "schema if POST/PUT" │ +│ response: "schema summary" │ +│ auth_required: true|false │ +│ ``` │ +│ │ +│ If no APIs found, return: api_endpoints: [] │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ AGENT 3: Components Analyzer │ +├─────────────────────────────────────────────────────────────────┤ +│ Task tool with: │ +│ subagent_type: "Explore" │ +│ run_in_background: true │ +│ prompt: | │ +│ # UI COMPONENTS ANALYSIS │ +│ │ +│ Find and analyze all UI components in the project. │ +│ │ +│ ## Search Patterns │ +│ - React: components/**/*.tsx, function Component() │ +│ - Vue: components/**/*.vue, + + + ``` + + ### Visual Design Standards + + **Hero Section**: + - Full viewport height or 80vh minimum + - Gradient or subtle pattern background + - Large, bold headline (48-72px) + - Clear visual hierarchy + - Floating elements or subtle animation + + **Feature Cards**: + - Icon + Title + Description + - Subtle hover effects (scale, shadow) + - Consistent spacing (24-32px gaps) + - 3-column on desktop, 1 on mobile + + **How It Works**: + - Visual step indicators (1, 2, 3) + - Connecting lines or arrows + - Icons or illustrations per step + - Horizontal on desktop, vertical on mobile + + **FAQ Accordion**: + - Click to expand/collapse + - Smooth animation (max-height transition) + - Plus/minus or chevron indicator + - Category grouping + + **Micro-interactions**: + - Button hover: scale(1.02) + shadow + - Card hover: translateY(-4px) + shadow + - Smooth scroll for anchor links + - Fade-in on scroll (intersection observer) + + ### CSS Requirements + + ```css + /* CSS Custom Properties from branding.json */ + :root { + --color-primary: ...; + --color-secondary: ...; + --font-heading: ...; + --radius: ...; + } + + /* Dark mode */ + @media (prefers-color-scheme: dark) { + :root { + --color-bg: var(--color-bg-dark); + ... + } + } + + /* Responsive breakpoints */ + /* Mobile: < 640px */ + /* Tablet: 640-1024px */ + /* Desktop: > 1024px */ + ``` + + ### JavaScript Requirements + - FAQ accordion functionality + - Smooth scroll for navigation + - Optional: Scroll-triggered animations + - Dark mode toggle (optional) + - Mobile menu toggle + + ### Performance + - Single HTML file (no external dependencies) + - Inline critical CSS + - Minimal JavaScript + - Optimized for Core Web Vitals + - < 100KB total size + + Write complete HTML to: $DOCS_DIR/landing.html +``` + +--- + +### ═══════════════════════════════════════════════════════════════ +### PHASE 4: Finalization +### ═══════════════════════════════════════════════════════════════ + +#### 4.1: Validate Generated Files + +Verify all files exist: +- `$DOCS_DIR/branding.json` +- `$DOCS_DIR/content.json` +- `$DOCS_DIR/faq.json` +- `$DOCS_DIR/seo.json` +- `$DOCS_DIR/landing.html` + +#### 4.2: Display Completion Banner + +``` +╔══════════════════════════════════════════════════════════════╗ +║ ✅ LANDING PAGE GENERATED SUCCESSFULLY ║ +╠══════════════════════════════════════════════════════════════╣ +║ Output Directory: $DOCS_DIR ║ +╠══════════════════════════════════════════════════════════════╣ +║ Files Created: ║ +║ 🎨 branding.json (Colors, typography, style guide) ║ +║ 📝 content.json (Hero, features, how-it-works) ║ +║ ❓ faq.json (Q&A content by category) ║ +║ 🔍 seo.json (Meta tags, Open Graph, Schema) ║ +║ 🌐 landing.html (Designer-quality landing page) ║ +╠══════════════════════════════════════════════════════════════╣ +║ Landing Page Features: ║ +║ ✅ Hero with compelling headline + CTAs ║ +║ ✅ Feature grid with icons ║ +║ ✅ How It Works visual flow ║ +║ ✅ Interactive FAQ accordion ║ +║ ✅ Responsive (mobile-first) ║ +║ ✅ Dark mode support ║ +║ ✅ SEO optimized ║ +║ ✅ Single file, no dependencies ║ +╠══════════════════════════════════════════════════════════════╣ +║ Next Steps: ║ +║ → Open landing.html in browser to preview ║ +║ → Customize colors in branding.json ║ +║ → Add real screenshots/images ║ +║ → Deploy to your hosting ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +--- + +## ARGUMENTS + +| Argument | Default | Description | +|----------|---------|-------------| +| `[docs_dir]` | `docs` | Directory containing documentation from /eureka:index | +| `--style` | `modern` | Design style: modern, minimal, bold, corporate | +| `--theme` | `auto` | Color theme: auto, light, dark | + +## EXAMPLES + +```bash +# Generate landing page from default docs directory +/eureka:landing + +# Generate from custom documentation directory +/eureka:landing my-docs + +# Generate with specific style +/eureka:landing --style minimal + +# Generate dark-only theme +/eureka:landing --theme dark +``` + +--- + +## DESIGN STYLE OPTIONS + +### Modern (Default) +- Gradient backgrounds +- Rounded corners (12-16px) +- Soft shadows +- Vibrant accent colors +- Floating elements + +### Minimal +- Clean white space +- Thin borders +- Muted colors +- Typography-focused +- Subtle interactions + +### Bold +- Strong colors +- Large typography +- High contrast +- Geometric shapes +- Impactful CTAs + +### Corporate +- Professional blues/grays +- Structured layout +- Trust indicators +- Clean lines +- Conservative animations + +--- + +## OUTPUT STRUCTURE + +``` +docs/ +├── analysis.yml (from /eureka:index) +├── PROJECT_DOCUMENTATION.md +├── API_REFERENCE.md +├── COMPONENTS.md +├── QUICK_REFERENCE.md +├── index.html (documentation HTML) +│ +├── branding.json (NEW - concept branding) +├── content.json (NEW - marketing content) +├── faq.json (NEW - Q&A content) +├── seo.json (NEW - SEO metadata) +└── landing.html (NEW - landing page) +``` + +--- + +## BRANDING SYSTEM + +The generated branding.json provides a complete design system: + +```json +{ + "brand": { + "name": "Project Name", + "tagline": "Your compelling tagline", + "value_proposition": "What makes it unique" + }, + "colors": { + "primary": "#6366f1", + "secondary": "#8b5cf6", + "accent": "#f59e0b", + "background": "#ffffff", + "background_dark": "#0f172a", + "text": "#1e293b", + "text_muted": "#64748b" + }, + "typography": { + "heading_font": "Inter, system-ui, sans-serif", + "body_font": "Inter, system-ui, sans-serif", + "mono_font": "JetBrains Mono, Consolas, monospace" + }, + "style": { + "border_radius": "12px", + "shadow_sm": "0 1px 2px rgba(0,0,0,0.05)", + "shadow_md": "0 4px 6px -1px rgba(0,0,0,0.1)", + "shadow_lg": "0 10px 15px -3px rgba(0,0,0,0.1)", + "gradient": "linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)" + } +} +``` + +This can be used to: +- Maintain consistent branding across all pages +- Generate CSS custom properties +- Create Figma/design tool exports +- Build component libraries diff --git a/.claude/commands/guardrail/analyze.md b/.claude/commands/guardrail/analyze.md new file mode 100644 index 0000000..4620c31 --- /dev/null +++ b/.claude/commands/guardrail/analyze.md @@ -0,0 +1,243 @@ +--- +description: Analyze codebase and generate project manifest from existing code +allowed-tools: Read, Write, Bash, Glob, Grep, Task, AskUserQuestion +--- + +# Analyze Codebase and Generate Manifest + +Analyze the current codebase and generate all files needed for the guardrail workflow system. + +## Arguments + +- `$ARGUMENTS` - Optional flags: + - `--force` - Overwrite existing manifest without asking + - `--dry-run` - Preview manifest without writing + - `--deep` - Use AI agent for deeper analysis + - `` - Custom project name + +## Generated Files + +This command creates: +1. `project_manifest.json` - Entity definitions and dependencies +2. `.workflow/index.yml` - Version tracking index +3. `.workflow/versions/` - Directory for version snapshots + +## Quick Execution (Default) + +### Step 1: Run the Python analyzer script +```bash +python3 skills/guardrail-orchestrator/scripts/analyze_codebase.py --path . $ARGUMENTS +``` + +If the script succeeds, continue to Step 2. + +### Step 2: Initialize Workflow Directory Structure [MANDATORY] +```bash +# Create workflow directory structure +mkdir -p .workflow/versions + +# Create index.yml if it doesn't exist +if [ ! -f .workflow/index.yml ]; then + cat > .workflow/index.yml << 'EOF' +versions: [] +latest_version: null +total_versions: 0 +EOF + echo "Created .workflow/index.yml" +fi +``` + +### Step 3: Display Summary +``` +╔══════════════════════════════════════════════════════════════╗ +║ ✅ GUARDRAIL INITIALIZED ║ +╠══════════════════════════════════════════════════════════════╣ +║ Files Created: ║ +║ ✓ project_manifest.json ║ +║ ✓ .workflow/index.yml ║ +║ ✓ .workflow/versions/ ║ +╠══════════════════════════════════════════════════════════════╣ +║ Ready to use: ║ +║ /workflow:spawn Start a new feature ║ +║ /guardrail:status Check project status ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +## Deep Analysis Mode (--deep flag) + +**Use the Task tool to spawn an Explore agent for comprehensive codebase analysis** + +Use Task tool with: + subagent_type: "Explore" + prompt: | + Analyze this codebase thoroughly and return structured information about: + + 1. **Pages** (Next.js App Router): + - Find all page.tsx files in app/ directory + - Extract route paths from file locations + - Identify components imported/used + - Identify API dependencies (fetch calls) + + 2. **Components**: + - Find all .tsx files in app/components/ + - Extract component names and exports + - Extract prop interfaces/types + - Identify child component dependencies + + 3. **API Routes**: + - Find all route.ts files in app/api/ + - Extract HTTP methods (GET, POST, PUT, DELETE, PATCH) + - Identify request/response types + - Extract path parameters + + 4. **Database/Types**: + - Find type definitions in app/lib/ + - Extract interfaces and type aliases + - Identify database schemas/tables + + 5. **Dependencies**: + - Which components are used by which pages + - Which APIs are called by which components + - Which database tables are used by which APIs + + Return the analysis as structured JSON sections. + +### Phase 2: Generate Manifest + +Based on the analysis, create `project_manifest.json` with this structure: + +```json +{ + "project": { + "name": "", + "version": "1.0.0", + "created_at": "", + "description": "" + }, + "state": { + "current_phase": "IMPLEMENTATION_PHASE", + "approval_status": { + "manifest_approved": true, + "approved_by": "analyzer", + "approved_at": "" + }, + "revision_history": [ + { + "action": "MANIFEST_GENERATED", + "timestamp": "", + "details": "Generated from existing codebase analysis" + } + ] + }, + "entities": { + "pages": [ + { + "id": "page_", + "path": "/", + "file_path": "app//page.tsx", + "status": "IMPLEMENTED", + "description": "", + "components": ["comp_", ...], + "data_dependencies": ["api_", ...] + } + ], + "components": [ + { + "id": "comp_", + "name": "", + "file_path": "app/components/.tsx", + "status": "IMPLEMENTED", + "description": "", + "props": { + "": { + "type": "", + "optional": true|false, + "description": "" + } + } + } + ], + "api_endpoints": [ + { + "id": "api__", + "path": "/api/", + "method": "GET|POST|PUT|DELETE|PATCH", + "file_path": "app/api//route.ts", + "status": "IMPLEMENTED", + "description": "", + "request": { + "params": {}, + "query": {}, + "body": {} + }, + "response": { + "type": "", + "description": "" + } + } + ], + "database_tables": [ + { + "id": "table_", + "name": "", + "file_path": "app/lib/db.ts", + "status": "IMPLEMENTED", + "description": "", + "columns": {} + } + ] + }, + "dependencies": { + "component_to_page": {}, + "api_to_component": {}, + "table_to_api": {} + }, + "types": {} +} +``` + +### Phase 3: Entity Naming Conventions + +Use these ID formats: +- **Pages**: `page_` (e.g., `page_home`, `page_tasks`, `page_task_detail`) +- **Components**: `comp_` (e.g., `comp_task_list`, `comp_filter_bar`) +- **APIs**: `api__` (e.g., `api_list_tasks`, `api_create_task`) +- **Tables**: `table_` (e.g., `table_tasks`, `table_users`) + +### Phase 4: Write Manifest + +1. Write the generated manifest to `project_manifest.json` +2. Validate JSON syntax +3. Display summary: + +``` +╔══════════════════════════════════════════════════════════════╗ +║ 📊 MANIFEST GENERATED ║ +╠══════════════════════════════════════════════════════════════╣ +║ Project: ║ +║ Generated: ║ +╠══════════════════════════════════════════════════════════════╣ +║ ENTITIES DISCOVERED ║ +║ 📄 Pages: X ║ +║ 🧩 Components: X ║ +║ 🔌 APIs: X ║ +║ 🗄️ Tables: X ║ +╠══════════════════════════════════════════════════════════════╣ +║ Status: All entities marked as IMPLEMENTED ║ +║ Phase: IMPLEMENTATION_PHASE ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +## Options + +If manifest already exists, ask user: +1. **Overwrite** - Replace existing manifest +2. **Merge** - Add new entities, keep existing +3. **Cancel** - Abort operation + +## Notes + +- All discovered entities are marked as `IMPLEMENTED` since they already exist +- Project starts in `IMPLEMENTATION_PHASE` since code exists +- Use this command to bring existing projects under guardrail management +- After generation, use `/guardrail:validate` to verify manifest accuracy diff --git a/.claude/commands/guardrail/approve.md b/.claude/commands/guardrail/approve.md new file mode 100644 index 0000000..1a04a9a --- /dev/null +++ b/.claude/commands/guardrail/approve.md @@ -0,0 +1,47 @@ +--- +description: Approve design and transition to IMPLEMENTATION_PHASE (Reviewer mode) +allowed-tools: Read, Write, Bash +--- + +# Approve Design (Reviewer Mode) + +✅ **REVIEWER MODE ACTIVATED** + +Approve the current design and enable implementation. + +## CRITICAL RULES + +You are acting as the **REVIEWER AGENT**. + +✅ **ALLOWED**: +- Read any file +- Update approval status in manifest +- Transition phases + +❌ **BLOCKED**: +- Write ANY code files +- You cannot implement anything + +## Steps + +1. **Verify Phase**: Must be in `DESIGN_REVIEW` + +2. **Run Full Validation**: +```bash +python3 "$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_manifest.py" --strict +``` + +3. **If Valid**, update manifest: + - Set `state.approval_status.manifest_approved = true` + - Set `state.approval_status.approved_by = "reviewer"` + - Set `state.approval_status.approved_at = ` + +4. **Transition to Implementation**: +```bash +python3 "$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/transition_phase.py" --to IMPLEMENTATION_PHASE +``` + +5. **Show Results**: + - List all entities now with status `APPROVED` + - Explain that code can now be written for these entities + - Suggest `/guardrail:implement` to start diff --git a/.claude/commands/guardrail/design.md b/.claude/commands/guardrail/design.md new file mode 100644 index 0000000..e5138aa --- /dev/null +++ b/.claude/commands/guardrail/design.md @@ -0,0 +1,81 @@ +--- +description: Design a new feature by adding entities to manifest (Architect mode) +allowed-tools: Read, Write, Bash +--- + +# Design Feature (Architect Mode) + +🏗️ **ARCHITECT MODE ACTIVATED** + +Design the feature: "$ARGUMENTS" + +## CRITICAL RULES + +You are now acting as the **ARCHITECT AGENT**. + +✅ **ALLOWED**: +- Read any file +- Write to `project_manifest.json` ONLY +- Run validation scripts + +❌ **BLOCKED**: +- Write ANY code files (.ts, .tsx, .css, .sql, .js, .jsx) +- You CANNOT write implementation code yet + +## Workflow + +1. **Verify Phase**: Check `project_manifest.json` - must be in `DESIGN_PHASE` + +2. **Analyze Requirements**: Break down "$ARGUMENTS" into: + - Pages needed (routes/screens) + - Components needed (UI elements) + - API endpoints needed (backend routes) + - Database tables needed (data storage) + +3. **Define Each Entity** in manifest with: + - Unique ID following naming convention + - Complete specification (props, schemas, columns) + - `status: "DEFINED"` + - File path where it will be implemented + +4. **Update Manifest**: Add all entities to `project_manifest.json` + +5. **Validate**: Run `python3 skills/guardrail-orchestrator/scripts/validate_manifest.py` + +6. **Summarize**: List what was added and suggest `/guardrail:review` + +## Entity Templates + +### Page +```json +{ + "id": "page_[name]", + "path": "/[route]", + "file_path": "src/pages/[name]/index.tsx", + "status": "DEFINED", + "components": [], + "data_dependencies": [] +} +``` + +### Component +```json +{ + "id": "comp_[name]", + "name": "[PascalCase]", + "file_path": "src/components/[Name]/index.tsx", + "status": "DEFINED", + "props": {} +} +``` + +### API Endpoint +```json +{ + "id": "api_[action]_[resource]", + "path": "/api/v1/[resource]", + "method": "GET|POST|PUT|DELETE", + "file_path": "src/api/[resource]/[action].ts", + "status": "DEFINED" +} +``` diff --git a/.claude/commands/guardrail/implement.md b/.claude/commands/guardrail/implement.md new file mode 100644 index 0000000..cb151e3 --- /dev/null +++ b/.claude/commands/guardrail/implement.md @@ -0,0 +1,74 @@ +--- +description: Implement an approved entity from the manifest +allowed-tools: Read, Write, Bash +--- + +# Implement Entity + +Implement the entity: "$ARGUMENTS" + +## CRITICAL RULES + +⚠️ **GUARDRAIL ENFORCEMENT ACTIVE** + +You can ONLY write to files that: +1. Are defined in `project_manifest.json` +2. Have status = `APPROVED` +3. Match the `file_path` in the manifest EXACTLY + +## Steps + +1. **Verify Phase**: Must be in `IMPLEMENTATION_PHASE` + +2. **Find Entity** in manifest: + - If "$ARGUMENTS" is `--all`: implement all APPROVED entities + - Otherwise: find the specific entity by ID + +3. **For Each Entity**: + + a. **Load Definition** from manifest + + b. **Verify Status** is `APPROVED` + + c. **Generate Code** matching the specification: + - Props must match manifest exactly + - Types must match manifest exactly + - File path must match `file_path` in manifest + + d. **Write File** to the exact path in manifest + + e. **Run Validations**: + ```bash + npm run lint --if-present + npm run type-check --if-present + ``` + +4. **Status Updates** (handled by post-hook): + - Entity status changes to `IMPLEMENTED` + - Timestamp recorded + +## Code Templates + +### Component (Frontend) +```tsx +import React from 'react'; + +interface [Name]Props { + // From manifest.props +} + +export const [Name]: React.FC<[Name]Props> = (props) => { + return ( + // Implementation + ); +}; +``` + +### API Endpoint (Backend) +```typescript +import { Request, Response } from 'express'; + +export async function handler(req: Request, res: Response) { + // From manifest.request/response schemas +} +``` diff --git a/.claude/commands/guardrail/init.md b/.claude/commands/guardrail/init.md new file mode 100644 index 0000000..94db4f3 --- /dev/null +++ b/.claude/commands/guardrail/init.md @@ -0,0 +1,67 @@ +--- +description: Initialize a new guardrailed project with manifest +allowed-tools: Bash, Write, Read +--- + +# Initialize Guardrailed Project + +Initialize a new project called "$ARGUMENTS" with guardrail enforcement and workflow system. + +## Generated Files + +This command creates: +1. `project_manifest.json` - Entity definitions and dependencies +2. `.workflow/index.yml` - Version tracking index +3. `.workflow/versions/` - Directory for version snapshots + +## Steps + +### Step 1: Run the initialization script +```bash +python3 skills/guardrail-orchestrator/scripts/init_project.py --name "$ARGUMENTS" --path . +``` + +### Step 2: Initialize Workflow Directory Structure [MANDATORY] +```bash +# Create workflow directory structure +mkdir -p .workflow/versions + +# Create index.yml if it doesn't exist +if [ ! -f .workflow/index.yml ]; then + cat > .workflow/index.yml << 'EOF' +versions: [] +latest_version: null +total_versions: 0 +EOF +fi +``` + +### Step 3: Verify and Display Summary +```bash +# Verify files exist +ls project_manifest.json .workflow/index.yml +``` + +Display: +``` +╔══════════════════════════════════════════════════════════════╗ +║ ✅ PROJECT INITIALIZED ║ +╠══════════════════════════════════════════════════════════════╣ +║ Project: $ARGUMENTS ║ +║ Phase: DESIGN_PHASE ║ +╠══════════════════════════════════════════════════════════════╣ +║ Files Created: ║ +║ ✓ project_manifest.json ║ +║ ✓ .workflow/index.yml ║ +║ ✓ .workflow/versions/ ║ +╠══════════════════════════════════════════════════════════════╣ +║ Next Steps: ║ +║ /guardrail:design Design features in manifest ║ +║ /workflow:spawn Start automated workflow ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +## Notes +- Project starts in **DESIGN_PHASE** (manifest edits only) +- Use `/guardrail:design` for manual design workflow +- Use `/workflow:spawn` for automated design + implementation diff --git a/.claude/commands/guardrail/review.md b/.claude/commands/guardrail/review.md new file mode 100644 index 0000000..908610d --- /dev/null +++ b/.claude/commands/guardrail/review.md @@ -0,0 +1,32 @@ +--- +description: Request design review and transition to DESIGN_REVIEW phase +allowed-tools: Read, Write, Bash +--- + +# Request Design Review + +Transition the project from DESIGN_PHASE to DESIGN_REVIEW. + +## Steps + +1. **Validate Manifest**: +```bash +python3 "$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_manifest.py" --strict +``` + +2. **If Valid**, transition phase: +```bash +python3 "$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/transition_phase.py" --to DESIGN_REVIEW +``` + +3. **Show Review Checklist**: + - [ ] All pages have at least one component + - [ ] All components have defined props with types + - [ ] All APIs have request/response schemas + - [ ] All database tables have primary keys + - [ ] No orphan components + - [ ] No circular dependencies + +4. **Explain Next Steps**: + - Use `/guardrail:approve` to approve and move to implementation + - Use `/guardrail:reject ` to send back for fixes diff --git a/.claude/commands/guardrail/status.md b/.claude/commands/guardrail/status.md new file mode 100644 index 0000000..53ba37e --- /dev/null +++ b/.claude/commands/guardrail/status.md @@ -0,0 +1,27 @@ +--- +description: Show current project phase, entity counts, and pending work +allowed-tools: Read, Bash +--- + +# Guardrail Status + +Display the current guardrail project status. + +## Steps + +1. Read `project_manifest.json` + +2. Display: + - **Current Phase**: `state.current_phase` + - **Approval Status**: `state.approval_status.manifest_approved` + - **Entity Counts**: + - Pages: count by status (DEFINED/APPROVED/IMPLEMENTED) + - Components: count by status + - API Endpoints: count by status + - Database Tables: count by status + - **Recent History**: last 5 items from `state.revision_history` + +3. Show available actions for current phase: + - DESIGN_PHASE: Can use `/guardrail:design`, then `/guardrail:review` + - DESIGN_REVIEW: Can use `/guardrail:approve` or `/guardrail:reject` + - IMPLEMENTATION_PHASE: Can use `/guardrail:implement` diff --git a/.claude/commands/guardrail/validate.md b/.claude/commands/guardrail/validate.md new file mode 100644 index 0000000..4467e50 --- /dev/null +++ b/.claude/commands/guardrail/validate.md @@ -0,0 +1,29 @@ +--- +description: Validate manifest integrity and completeness +allowed-tools: Bash, Read +--- + +# Validate Manifest + +Run validation checks on `project_manifest.json`. + +## Command + +```bash +python3 "$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_manifest.py" $ARGUMENTS +``` + +## Options + +- No arguments: Basic validation +- `--strict`: Treat warnings as errors + +## What It Checks + +1. **Structure**: Required top-level keys exist +2. **Pages**: Have paths, components, file_paths +3. **Components**: Have props with types, valid dependencies +4. **APIs**: Have methods, paths, request/response schemas +5. **Database**: Tables have primary keys, valid foreign keys +6. **Dependencies**: No orphans, no circular references +7. **Naming**: Follows conventions diff --git a/.claude/commands/guardrail/verify.md b/.claude/commands/guardrail/verify.md new file mode 100644 index 0000000..e6d03e1 --- /dev/null +++ b/.claude/commands/guardrail/verify.md @@ -0,0 +1,57 @@ +--- +description: Verify implementation matches manifest specifications +allowed-tools: Bash, Read +--- + +# Verify Implementation + +Run verification to ensure all implemented code matches the manifest specifications. + +## Command + +```bash +python3 "$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/verify_implementation.py" --project-root "$CLAUDE_PROJECT_DIR" $ARGUMENTS +``` + +## Options + +- No arguments: Basic verification +- `--verbose` or `-v`: Include warnings +- `--json`: Output as JSON for programmatic use + +## What It Checks + +For each entity in the manifest: + +### Components +- File exists at specified `file_path` +- Component name is exported +- Props interface matches manifest definition + +### Pages +- File exists at specified `file_path` +- Has `export default` (Next.js requirement) +- Uses specified component dependencies + +### API Endpoints +- File exists at specified `file_path` +- HTTP method handler exists (GET, POST, PUT, DELETE) +- Request parameters are handled + +### Database Tables +- File exists at specified `file_path` +- Column definitions present +- CRUD operations implemented + +## Example Output + +``` +✅ [component] comp_button + File: app/components/Button.tsx + +❌ [component] comp_missing + File: app/components/Missing.tsx + ❌ ERROR: File not found + +SUMMARY: 17/18 passed, 1 failed, 3 warnings +``` diff --git a/.claude/commands/workflow/approve.md b/.claude/commands/workflow/approve.md new file mode 100644 index 0000000..8bedd42 --- /dev/null +++ b/.claude/commands/workflow/approve.md @@ -0,0 +1,87 @@ +--- +description: Approve a workflow gate (design or implementation) +allowed-tools: Read, Write, Bash +--- + +# Approve Workflow Gate + +Approve gate: "$ARGUMENTS" + +## Valid Gates +- `design` - Approve the design phase (entities + tasks) +- `implementation` - Approve the implementation phase (all code) + +## Steps + +### 1. Validate Gate +- If "$ARGUMENTS" is not `design` or `implementation`: STOP and show usage + +### 2. Check Workflow State +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py exists +``` + +If no active workflow: +``` +❌ No active workflow found. + Start a new workflow with: /workflow:spawn "feature name" +``` + +### 3. Verify Current Phase + +**For design approval**: +- Current phase must be `AWAITING_DESIGN_APPROVAL` +- If not: Report error with current phase + +**For implementation approval**: +- Current phase must be `AWAITING_IMPL_APPROVAL` +- If not: Report error with current phase + +### 4. Execute Approval + +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py approve $ARGUMENTS +``` + +### 5. Transition to Next Phase + +**If design approved**: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition DESIGN_APPROVED +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPLEMENTING +``` + +**If implementation approved**: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPL_APPROVED +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition COMPLETING +``` + +### 6. Report + +**Design Approved**: +``` +╔══════════════════════════════════════════════════════════════╗ +║ ✅ DESIGN APPROVED ║ +╠══════════════════════════════════════════════════════════════╣ +║ The design has been approved. Implementation will begin. ║ +║ ║ +║ Next steps: ║ +║ /workflow:frontend --next Start frontend tasks ║ +║ /workflow:backend --next Start backend tasks ║ +║ /workflow:resume Auto-continue workflow ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +**Implementation Approved**: +``` +╔══════════════════════════════════════════════════════════════╗ +║ ✅ IMPLEMENTATION APPROVED ║ +╠══════════════════════════════════════════════════════════════╣ +║ All implementations have been approved. ║ +║ ║ +║ Next steps: ║ +║ /workflow:complete --all Mark all tasks as done ║ +║ /workflow:resume Auto-complete workflow ║ +╚══════════════════════════════════════════════════════════════╝ +``` diff --git a/.claude/commands/workflow/backend.md b/.claude/commands/workflow/backend.md new file mode 100644 index 0000000..59b253b --- /dev/null +++ b/.claude/commands/workflow/backend.md @@ -0,0 +1,85 @@ +--- +description: Implement backend tasks (Backend agent) +allowed-tools: Read, Write, Edit, Bash +--- + +# Backend Agent - Implementation Mode + +⚙️ **BACKEND AGENT ACTIVATED** + +Implement task: "$ARGUMENTS" + +## CRITICAL RULES + +You are now the **BACKEND AGENT**. + +✅ **ALLOWED**: +- Read any file +- Write new files (API routes, DB) +- Edit existing backend files +- Run Bash (build, lint, type-check, tests) + +✅ **ALLOWED FILES**: +- `app/api/**/*` +- `app/lib/**/*` +- `prisma/**/*` +- `db/**/*` + +## Workflow + +### Step 1: Load Task +First, get the version-specific tasks directory: +```bash +TASKS_DIR=$(python3 skills/guardrail-orchestrator/scripts/version_manager.py tasks-dir) +``` + +Read the task file: `$TASKS_DIR/$ARGUMENTS.yml` +- If "$ARGUMENTS" is `--next`: find first task with `agent: backend` and `status: pending` + +### Step 2: Update Workflow State +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task in_progress +``` + +### Step 3: Verify Prerequisites +- Check entity is `APPROVED` in `project_manifest.json` +- Check all `dependencies` tasks are `completed` +- If blocked: + ```bash + python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task blocked + ``` + Stop and report blocker. + +### Step 4: Implement +For each file in `file_paths`: +1. Read manifest entity specification +2. Generate code matching spec exactly: + - HTTP methods must match manifest + - Request params must match manifest + - Response types must match manifest +3. Follow existing project patterns + +### Step 5: Validate +Run validations: +```bash +npm run lint +npm run build +``` + +### Step 6: Update Task Status +Update the task file: +```yaml +status: review +completed_at: +``` + +Update workflow state: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task review +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py progress --tasks-impl +``` + +### Step 7: Report +- List implemented files +- Show validation results +- Suggest: `/workflow:review $ARGUMENTS` diff --git a/.claude/commands/workflow/complete.md b/.claude/commands/workflow/complete.md new file mode 100644 index 0000000..e9923d8 --- /dev/null +++ b/.claude/commands/workflow/complete.md @@ -0,0 +1,66 @@ +--- +description: Mark approved task as completed +allowed-tools: Read, Write, Bash +--- + +# Complete Task + +Mark task "$ARGUMENTS" as completed. + +## Prerequisites +- Task must have `status: approved` +- All acceptance criteria verified by reviewer + +## Steps + +### 1. Read Task +First, get the version-specific tasks directory: +```bash +TASKS_DIR=$(python3 skills/guardrail-orchestrator/scripts/version_manager.py tasks-dir) +``` + +Read `$TASKS_DIR/$ARGUMENTS.yml` + +### 2. Verify Status +- If `status` is NOT `approved`: STOP and report error +- If `status` is `approved`: proceed + +### 3. Update Task +Update the task file with: +```yaml +status: completed +completed_at: +``` + +### 4. Update Workflow State +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task completed +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py progress --tasks-completed +``` + +### 5. Update Manifest (if applicable) +For each entity in `entity_ids`: +- Update entity status to `IMPLEMENTED` in `project_manifest.json` + +### 6. Check Workflow Completion +Check if all tasks are now completed: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py status +``` + +If all tasks completed, transition to implementation approval: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition AWAITING_IMPL_APPROVAL +``` + +### 7. Report +``` +✅ Task completed: $ARGUMENTS + +Entities implemented: + - + - + +Next: /workflow:status to see remaining tasks + /workflow:complete --all to complete all approved tasks +``` diff --git a/.claude/commands/workflow/design.md b/.claude/commands/workflow/design.md new file mode 100644 index 0000000..fba6842 --- /dev/null +++ b/.claude/commands/workflow/design.md @@ -0,0 +1,476 @@ +--- +description: Design system architecture with ER diagram, API contracts, and UI structure +allowed-tools: Read, Write, Edit, Bash, Task, TodoWrite +--- + +# Workflow Design - System Architecture Phase + +**Input**: "$ARGUMENTS" + +--- + +## PURPOSE + +This command creates a comprehensive **design document** that serves as the source of truth for implementation. It defines: + +1. **Data Layer** - ER diagram with models, fields, relations +2. **API Layer** - REST endpoints with request/response contracts +3. **UI Layer** - Pages and components with data requirements +4. **Dependency Graph** - Layered execution order for parallel tasks + +--- + +## ⛔ CRITICAL RULES + +### MUST DO +1. **MUST** create `design_document.yml` with ALL layers defined +2. **MUST** run `validate_design.py` to generate dependency graph +3. **MUST** verify no circular dependencies before proceeding +4. **MUST** show layered execution plan to user + +### CANNOT DO +1. **CANNOT** create tasks without design document +2. **CANNOT** skip validation step +3. **CANNOT** proceed if validation fails + +--- + +## EXECUTION FLOW + +### ═══════════════════════════════════════════════════════════════ +### STEP 1: Initialize Design Session +### ═══════════════════════════════════════════════════════════════ + +#### 1.1: Get Current Version +```bash +# Get active version from workflow +VERSION_ID=$(cat .workflow/current.yml 2>/dev/null | grep "active_version:" | awk '{print $2}') +if [ -z "$VERSION_ID" ]; then + echo "ERROR: No active workflow. Run /workflow:spawn first" + exit 1 +fi +echo "VERSION_ID=$VERSION_ID" +``` + +#### 1.2: Create Design Directory +```bash +mkdir -p .workflow/versions/$VERSION_ID/design +mkdir -p .workflow/versions/$VERSION_ID/contexts +mkdir -p .workflow/versions/$VERSION_ID/tasks +``` + +#### 1.3: Display Design Start Banner +``` +╔══════════════════════════════════════════════════════════════╗ +║ 📐 SYSTEM DESIGN PHASE ║ +╠══════════════════════════════════════════════════════════════╣ +║ Feature: $ARGUMENTS ║ +║ Version: $VERSION_ID ║ +╠══════════════════════════════════════════════════════════════╣ +║ You will define: ║ +║ Layer 1: Data Models (ER Diagram) ║ +║ Layer 2: API Endpoints (REST Contracts) ║ +║ Layer 3: Pages & Components (UI Structure) ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +--- + +### ═══════════════════════════════════════════════════════════════ +### STEP 2: Analyze Requirements & Design System +### ═══════════════════════════════════════════════════════════════ + +**Use Task tool with system-architect agent:** + +``` +Use Task tool with: + subagent_type: "system-architect" + prompt: | + # SYSTEM ARCHITECT - Design Document Creation + + ## INPUT + Feature: "$ARGUMENTS" + Version: $VERSION_ID + Output: .workflow/versions/$VERSION_ID/design/design_document.yml + + ## YOUR MISSION + Create a comprehensive design document following the schema in: + skills/guardrail-orchestrator/schemas/design_document.yml + + ## ANALYSIS PROCESS + + ### Phase A: Understand Requirements + 1. Read the feature description carefully + 2. Identify the core user flows + 3. Determine what data needs to be stored + 4. Identify what APIs are needed + 5. Plan the UI structure + + ### Phase B: Design Data Layer (ER Diagram) + For each entity needed: + - Define fields with types and constraints + - Define relations to other entities + - Define validations + - Consider indexes for performance + + ### Phase C: Design API Layer + For each endpoint needed: + - Define HTTP method and path + - Define request body schema (for POST/PUT/PATCH) + - Define response schemas for all status codes + - Define authentication requirements + - Link to data models used + + ### Phase D: Design UI Layer + For each page needed: + - Define route path + - List data requirements (which APIs) + - List components used + - Define auth requirements + + For each component needed: + - Define props interface + - Define events emitted + - List child components + - List APIs called directly (if any) + + ## OUTPUT FORMAT + + Create `.workflow/versions/$VERSION_ID/design/design_document.yml`: + + ```yaml + # Design Document + workflow_version: "$VERSION_ID" + feature: "$ARGUMENTS" + created_at: + status: draft + revision: 1 + + # LAYER 1: DATA MODELS + data_models: + - id: model_ + name: + description: "" + table_name: + fields: + - name: id + type: uuid + constraints: [primary_key] + - name: + type: + constraints: [] + # If enum: + enum_values: [, ] + relations: + - type: + target: model_ + foreign_key: + on_delete: + timestamps: true + validations: + - field: + rule: "" + message: "" + + # LAYER 2: API ENDPOINTS + api_endpoints: + - id: api__ + method: + path: /api/ + summary: "" + description: "" + # For POST/PUT/PATCH: + request_body: + content_type: application/json + schema: + type: object + properties: + - name: + type: + required: + validations: [] + responses: + - status: 200 + description: "Success" + schema: + type: object + properties: + - name: + type: + - status: 400 + description: "Validation error" + schema: + type: object + properties: + - name: error + type: string + depends_on_models: [model_] + auth: + required: + roles: [, ] + + # LAYER 3: PAGES + pages: + - id: page_ + name: "" + path: / + layout: + data_needs: + - api_id: api_ + purpose: "" + on_load: + components: [component_, component_] + auth: + required: + roles: [] + redirect: /login + + # LAYER 3: COMPONENTS + components: + - id: component_ + name: + props: + - name: + type: + required: + description: "" + events: + - name: + payload: "" + description: "" + uses_apis: [] + uses_components: [component_] + variants: [, ] + ``` + + ## DESIGN PRINCIPLES + + 1. **Start with Data**: What data is needed? Design models first. + 2. **APIs Serve UI**: What operations does UI need? Design APIs next. + 3. **UI Consumes APIs**: Pages/Components use APIs. Design UI last. + 4. **Explicit Dependencies**: Every relation must be clearly defined. + 5. **Contracts First**: API request/response schemas are contracts. + + ## VERIFICATION + + After creating the design document, verify: + 1. Every API references existing models + 2. Every page references existing APIs and components + 3. Every component references existing child components + 4. No circular dependencies + + ## OUTPUT + + After creating the file, output: + ``` + === DESIGN DOCUMENT CREATED === + + Data Models: X + API Endpoints: X + Pages: X + Components: X + + File: .workflow/versions/$VERSION_ID/design/design_document.yml + ``` +``` + +--- + +### ═══════════════════════════════════════════════════════════════ +### STEP 3: Validate Design & Generate Dependency Graph +### ═══════════════════════════════════════════════════════════════ + +#### 3.1: Run Design Validation [MANDATORY] +```bash +python3 skills/guardrail-orchestrator/scripts/validate_design.py \ + .workflow/versions/$VERSION_ID/design/design_document.yml \ + --output-dir .workflow/versions/$VERSION_ID +``` + +**This generates:** +- `.workflow/versions/$VERSION_ID/dependency_graph.yml` - Layered execution order +- `.workflow/versions/$VERSION_ID/contexts/*.yml` - Per-entity context snapshots +- `.workflow/versions/$VERSION_ID/tasks/*.yml` - Tasks with full context + +#### 3.2: Check Validation Result +```bash +VALIDATION_EXIT=$? +if [ $VALIDATION_EXIT -ne 0 ]; then + echo "❌ Design validation failed. Fix errors and re-run." + exit 1 +fi +``` + +**BLOCK IF**: Validation fails → Display errors, do not proceed + +--- + +### ═══════════════════════════════════════════════════════════════ +### STEP 4: Display Layered Execution Plan +### ═══════════════════════════════════════════════════════════════ + +Read the generated dependency graph and display: + +``` +╔══════════════════════════════════════════════════════════════╗ +║ 📊 DEPENDENCY GRAPH - EXECUTION LAYERS ║ +╠══════════════════════════════════════════════════════════════╣ +║ ║ +║ Layer 1: DATA MODELS (Parallel) ║ +║ ───────────────────────────────────────────── ║ +║ 📦 model_user → backend ║ +║ 📦 model_post → backend ║ +║ ║ +║ Layer 2: API ENDPOINTS (Parallel, after Layer 1) ║ +║ ───────────────────────────────────────────── ║ +║ 🔌 api_create_user → backend (needs: model_user) ║ +║ 🔌 api_list_users → backend (needs: model_user) ║ +║ 🔌 api_create_post → backend (needs: model_user, model_post)║ +║ ║ +║ Layer 3: UI (Parallel, after Layer 2) ║ +║ ───────────────────────────────────────────── ║ +║ 🧩 component_user_card → frontend ║ +║ 📄 page_users → frontend (needs: api_list_users) ║ +║ ║ +╠══════════════════════════════════════════════════════════════╣ +║ EXECUTION SUMMARY ║ +║ Total entities: X ║ +║ Total layers: X ║ +║ Max parallelism: X (tasks can run simultaneously) ║ +║ Critical path: X layers deep ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +--- + +### ═══════════════════════════════════════════════════════════════ +### STEP 5: Display Design Summary for Approval +### ═══════════════════════════════════════════════════════════════ + +``` +╔══════════════════════════════════════════════════════════════╗ +║ 🛑 DESIGN APPROVAL REQUIRED ║ +╠══════════════════════════════════════════════════════════════╣ +║ Feature: $ARGUMENTS ║ +║ Version: $VERSION_ID ║ +╠══════════════════════════════════════════════════════════════╣ +║ DESIGN DOCUMENT ║ +║ 📦 Data Models: X ║ +║ 🔌 API Endpoints: X ║ +║ 📄 Pages: X ║ +║ 🧩 Components: X ║ +╠══════════════════════════════════════════════════════════════╣ +║ GENERATED ARTIFACTS ║ +║ ✅ Dependency graph calculated ║ +║ ✅ Context snapshots created (X files) ║ +║ ✅ Implementation tasks created (X tasks) ║ +╠══════════════════════════════════════════════════════════════╣ +║ EXECUTION PLAN ║ +║ Layer 1: X tasks (parallel) → backend ║ +║ Layer 2: X tasks (parallel) → backend ║ +║ Layer 3: X tasks (parallel) → frontend ║ +╠══════════════════════════════════════════════════════════════╣ +║ FILES CREATED ║ +║ .workflow/versions/$VERSION_ID/design/design_document.yml ║ +║ .workflow/versions/$VERSION_ID/dependency_graph.yml ║ +║ .workflow/versions/$VERSION_ID/contexts/*.yml ║ +║ .workflow/versions/$VERSION_ID/tasks/*.yml ║ +╠══════════════════════════════════════════════════════════════╣ +║ NEXT STEPS ║ +║ Review the design above, then: ║ +║ /workflow:approve - Proceed to implementation ║ +║ /workflow:reject - Request design changes ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +--- + +### ═══════════════════════════════════════════════════════════════ +### STEP 6: Transition Workflow State +### ═══════════════════════════════════════════════════════════════ + +```bash +# Update progress +TASK_COUNT=$(ls .workflow/versions/$VERSION_ID/tasks/*.yml 2>/dev/null | wc -l) +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py progress \ + --tasks-created $TASK_COUNT + +# Transition to awaiting approval +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition AWAITING_DESIGN_APPROVAL +``` + +--- + +## CONTEXT SNAPSHOT EXAMPLE + +Each task gets a context file like `.workflow/versions/$VERSION_ID/contexts/api_create_user.yml`: + +```yaml +task_id: task_create_api_create_user +entity_id: api_create_user +workflow_version: v001 + +target: + type: api + definition: + method: POST + path: /api/users + request_body: + properties: + - name: email + type: string + required: true + validations: [email] + - name: password + type: string + required: true + validations: [min:8] + responses: + - status: 201 + schema: { id, email, name, created_at } + - status: 400 + schema: { error, details } + - status: 409 + schema: { error } + +related: + models: + - id: model_user + definition: + name: User + fields: + - { name: id, type: uuid } + - { name: email, type: string } + - { name: password_hash, type: string } + +dependencies: + entity_ids: [model_user] + +files: + to_create: + - app/api/users/route.ts + reference: + - path: app/api/health/route.ts + purpose: "API route pattern" + +acceptance: + - criterion: "POST /api/users returns 201 on success" + verification: "curl -X POST /api/users with valid data" + - criterion: "Returns 409 if email exists" + verification: "Test with duplicate email" +``` + +--- + +## USAGE + +```bash +# After /workflow:spawn, run design: +/workflow:design + +# This will: +# 1. Create comprehensive design document +# 2. Validate and generate dependency graph +# 3. Create tasks with full context +# 4. Wait for approval before implementation +``` diff --git a/.claude/commands/workflow/diff.md b/.claude/commands/workflow/diff.md new file mode 100644 index 0000000..d24800d --- /dev/null +++ b/.claude/commands/workflow/diff.md @@ -0,0 +1,193 @@ +--- +description: Compare workflow versions and show manifest changes +allowed-tools: Read, Bash +--- + +# Workflow Version Diff + +Compare workflow versions to see what changed in the project manifest. + +## EXECUTION PROTOCOL + +### Step 1: Parse Arguments + +``` +IF "$ARGUMENTS" = "": + MODE = "current" (diff latest version with current) +ELSE IF "$ARGUMENTS" matches "v\d+ v\d+": + MODE = "versions" (diff two specific versions) +ELSE IF "$ARGUMENTS" matches "v\d+": + MODE = "single" (diff specific version with current) +ELSE IF "$ARGUMENTS" = "--changelog" or "--log": + MODE = "changelog" (show all version changelogs) +ELSE IF "$ARGUMENTS" contains "--json": + OUTPUT = "json" +``` + +### Step 2: Get Available Versions + +```bash +python3 skills/guardrail-orchestrator/scripts/manifest_diff.py versions +``` + +### Step 3: Execute Diff Based on Mode + +**MODE: current (default)** +```bash +# Get latest version +LATEST=$(ls -1 .workflow/versions/ 2>/dev/null | tail -1) + +# Diff with current manifest +python3 skills/guardrail-orchestrator/scripts/manifest_diff.py diff $LATEST current +``` + +**MODE: versions (e.g., "v001 v002")** +```bash +# Diff two specific versions +python3 skills/guardrail-orchestrator/scripts/manifest_diff.py diff v001 v002 +``` + +**MODE: single (e.g., "v001")** +```bash +# Diff specific version with current +python3 skills/guardrail-orchestrator/scripts/manifest_diff.py diff v001 +``` + +**MODE: changelog** +```bash +# Show all changelogs +python3 skills/guardrail-orchestrator/scripts/manifest_diff.py changelog +``` + +**JSON output** +```bash +# Add --json for programmatic use +python3 skills/guardrail-orchestrator/scripts/manifest_diff.py diff v001 --json +``` + +### Step 4: Display Results + +The script outputs: + +``` +╔══════════════════════════════════════════════════════════════════════╗ +║ MANIFEST DIFF: v001 → v002 ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ SUMMARY ║ +║ + Added: 3 ║ +║ ~ Modified: 2 ║ +║ - Removed: 1 ║ +║ = Unchanged: 5 ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ BY TYPE ║ +║ pages: +1 ║ +║ components: +2 ~1 ║ +║ api_endpoints: ~1 -1 ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ ➕ ADDED ║ +║ + 📄 Profile (app/profile/page.tsx) ║ +║ + 🧩 Button (app/components/Button.tsx) ║ +║ + 🧩 Modal (app/components/Modal.tsx) ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ 📝 MODIFIED ║ +║ ~ 🧩 Header (app/components/Header.tsx) ║ +║ dependencies: [] → ['Button'] ║ +║ ~ 🔌 users (app/api/users/route.ts) ║ +║ status: PENDING → IMPLEMENTED ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ ➖ REMOVED ║ +║ - 🔌 legacy (app/api/legacy/route.ts) ║ +╚══════════════════════════════════════════════════════════════════════╝ +``` + +--- + +## USAGE EXAMPLES + +```bash +# Compare latest version with current manifest +/workflow:diff + +# Compare two specific versions +/workflow:diff v001 v002 + +# Compare specific version with current +/workflow:diff v003 + +# Show all version changelogs +/workflow:diff --changelog + +# Output as JSON +/workflow:diff v001 --json +``` + +--- + +## WHAT IT SHOWS + +### Entity Changes +- **Added**: New pages, components, API endpoints, etc. +- **Modified**: Status changes, dependency updates, path changes +- **Removed**: Deleted entities from manifest + +### Entity Type Icons +- 📄 page +- 🧩 component +- 🔌 api_endpoint +- 📚 lib +- 🪝 hook +- 📝 type +- ⚙️ config + +### Change Details +- Entity name and file path +- Specific field changes with before/after values +- Summary statistics by type + +--- + +## CHANGELOG MODE + +Show version history with changes: + +```bash +/workflow:diff --changelog +``` + +Output: +``` +╔══════════════════════════════════════════════════════════════════════╗ +║ CHANGELOG: v001 ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ Feature: User authentication ║ +║ Status: completed ║ +║ Started: 2025-01-15 10:30:00 ║ +║ Completed: 2025-01-15 14:45:00 ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ CHANGES ║ +║ + Added page: Login ║ +║ + Added page: Register ║ +║ + Added component: AuthForm ║ +║ + Added api_endpoint: auth ║ +╚══════════════════════════════════════════════════════════════════════╝ +``` + +--- + +## INTEGRATION + +### Uses Version Snapshots + +The diff tool uses snapshots created by version_manager.py: +- `snapshot_before/manifest.json` - Manifest at version start +- `snapshot_after/manifest.json` - Manifest at version completion + +These are automatically created when: +- `/workflow:spawn` initializes a new version +- `/workflow:complete` marks a version as done + +### Related Commands + +- `/workflow:history` - List all workflow versions +- `/workflow:status` - Show current workflow state +- `/workflow:changelog ` - Alias for `--changelog` diff --git a/.claude/commands/workflow/frontend.md b/.claude/commands/workflow/frontend.md new file mode 100644 index 0000000..94582d8 --- /dev/null +++ b/.claude/commands/workflow/frontend.md @@ -0,0 +1,85 @@ +--- +description: Implement frontend tasks (Frontend agent) +allowed-tools: Read, Write, Edit, Bash +--- + +# Frontend Agent - Implementation Mode + +🎨 **FRONTEND AGENT ACTIVATED** + +Implement task: "$ARGUMENTS" + +## CRITICAL RULES + +You are now the **FRONTEND AGENT**. + +✅ **ALLOWED**: +- Read any file +- Write new files (components, pages) +- Edit existing UI files +- Run Bash (build, lint, type-check) + +✅ **ALLOWED FILES**: +- `app/components/**/*` +- `app/**/page.tsx` +- `app/**/layout.tsx` +- `app/globals.css` + +## Workflow + +### Step 1: Load Task +First, get the version-specific tasks directory: +```bash +TASKS_DIR=$(python3 skills/guardrail-orchestrator/scripts/version_manager.py tasks-dir) +``` + +Read the task file: `$TASKS_DIR/$ARGUMENTS.yml` +- If "$ARGUMENTS" is `--next`: find first task with `agent: frontend` and `status: pending` + +### Step 2: Update Workflow State +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task in_progress +``` + +### Step 3: Verify Prerequisites +- Check entity is `APPROVED` in `project_manifest.json` +- Check all `dependencies` tasks are `completed` +- If blocked: + ```bash + python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task blocked + ``` + Stop and report blocker. + +### Step 4: Implement +For each file in `file_paths`: +1. Read manifest entity specification +2. Generate code matching spec exactly: + - Props must match manifest + - Types must match manifest + - File path must match manifest +3. Follow existing project patterns + +### Step 5: Validate +Run validations: +```bash +npm run lint +npm run build +``` + +### Step 6: Update Task Status +Update the task file: +```yaml +status: review +completed_at: +``` + +Update workflow state: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task review +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py progress --tasks-impl +``` + +### Step 7: Report +- List implemented files +- Show validation results +- Suggest: `/workflow:review $ARGUMENTS` diff --git a/.claude/commands/workflow/history.md b/.claude/commands/workflow/history.md new file mode 100644 index 0000000..d434888 --- /dev/null +++ b/.claude/commands/workflow/history.md @@ -0,0 +1,94 @@ +--- +description: Show workflow version history +allowed-tools: Read, Bash +--- + +# Workflow History + +Display version history of all workflow sessions. + +## Usage +``` +/workflow:history # List all versions +/workflow:history v001 # Show details for specific version +/workflow:history --changelog # Show changelog for current version +``` + +## Steps + +### 1. List All Versions (default) +```bash +python3 skills/guardrail-orchestrator/scripts/version_manager.py history +``` + +### 2. Show Version Details +If "$ARGUMENTS" is a version (e.g., `v001`): +```bash +python3 skills/guardrail-orchestrator/scripts/version_manager.py changelog $ARGUMENTS +``` + +### 3. Display Format + +**Version List**: +``` +╔══════════════════════════════════════════════════════════════════════╗ +║ WORKFLOW VERSION HISTORY ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ ✅ v003: Dashboard with analytics ║ +║ Started: 2025-01-16T16:00:00 | Tasks: 12 | Ops: 45 ║ +║ ────────────────────────────────────────────────────────────────── ║ +║ ✅ v002: Task filters and search ║ +║ Started: 2025-01-16T14:00:00 | Tasks: 8 | Ops: 28 ║ +║ ────────────────────────────────────────────────────────────────── ║ +║ ✅ v001: User authentication ║ +║ Started: 2025-01-16T10:00:00 | Tasks: 5 | Ops: 18 ║ +╚══════════════════════════════════════════════════════════════════════╝ +``` + +**Version Changelog**: +``` +╔══════════════════════════════════════════════════════════════════════╗ +║ CHANGELOG: v001 ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ Feature: User authentication ║ +║ Status: completed ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ CREATED ║ +║ + [page] page_login ║ +║ app/login/page.tsx ║ +║ + [component] component_LoginForm ║ +║ app/components/LoginForm.tsx ║ +║ + [api] api_auth ║ +║ app/api/auth/route.ts ║ +║ UPDATED ║ +║ ~ [component] component_Header ║ +║ DELETED ║ +║ (none) ║ +╠══════════════════════════════════════════════════════════════════════╣ +║ SUMMARY ║ +║ Entities: +3 ~1 -0 ║ +║ Files: +4 ~2 -0 ║ +╚══════════════════════════════════════════════════════════════════════╝ +``` + +### 4. Show Task Sessions +If `$ARGUMENTS` includes `--tasks`: +List all task sessions for the version with their operations: + +``` +Task Sessions for v001: +───────────────────────────────────────────────── +🎨 task_create_LoginPage (frontend) + Status: completed | Duration: 5m 32s + Operations: + + CREATE file: app/login/page.tsx + ~ UPDATE manifest: project_manifest.json + Review: ✅ approved + +⚙️ task_create_AuthAPI (backend) + Status: completed | Duration: 8m 15s + Operations: + + CREATE file: app/api/auth/route.ts + + CREATE file: app/lib/auth.ts + Review: ✅ approved +``` diff --git a/.claude/commands/workflow/reject.md b/.claude/commands/workflow/reject.md new file mode 100644 index 0000000..9eec98c --- /dev/null +++ b/.claude/commands/workflow/reject.md @@ -0,0 +1,113 @@ +--- +description: Reject a workflow gate and request changes +allowed-tools: Read, Write, Bash +--- + +# Reject Workflow Gate + +Reject gate with reason: "$ARGUMENTS" + +## Usage +``` +/workflow:reject design "Need more API endpoints for authentication" +/workflow:reject implementation "Login form missing validation" +``` + +## Steps + +### 1. Parse Arguments +Extract: +- `gate`: First word (design | implementation) +- `reason`: Remaining text in quotes + +If invalid format: +``` +❌ Usage: /workflow:reject "reason" + + Examples: + /workflow:reject design "Need user profile page" + /workflow:reject implementation "Missing error handling" +``` + +### 2. Check Workflow State +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py exists +``` + +If no active workflow: +``` +❌ No active workflow found. +``` + +### 3. Verify Current Phase + +**For design rejection**: +- Current phase must be `AWAITING_DESIGN_APPROVAL` + +**For implementation rejection**: +- Current phase must be `AWAITING_IMPL_APPROVAL` + +### 4. Execute Rejection + +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py reject "" +``` + +### 5. Transition to Revision Phase + +**If design rejected**: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition DESIGN_REJECTED +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition DESIGNING +``` + +**If implementation rejected**: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPL_REJECTED +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPLEMENTING +``` + +### 6. Report + +**Design Rejected**: +``` +╔══════════════════════════════════════════════════════════════╗ +║ ❌ DESIGN REJECTED ║ +╠══════════════════════════════════════════════════════════════╣ +║ Reason: ║ +║ ║ +║ The workflow has returned to the DESIGNING phase. ║ +║ Revision count: X ║ +║ ║ +║ Next steps: ║ +║ /workflow:design --revise Revise the design ║ +║ /workflow:resume Auto-revise and continue ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +**Implementation Rejected**: +``` +╔══════════════════════════════════════════════════════════════╗ +║ ❌ IMPLEMENTATION REJECTED ║ +╠══════════════════════════════════════════════════════════════╣ +║ Reason: ║ +║ ║ +║ The workflow has returned to the IMPLEMENTING phase. ║ +║ Revision count: X ║ +║ ║ +║ Tasks requiring fixes will be marked as 'pending'. ║ +║ ║ +║ Next steps: ║ +║ /workflow:frontend --next Fix frontend tasks ║ +║ /workflow:backend --next Fix backend tasks ║ +║ /workflow:resume Auto-fix and continue ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +### 7. Update Related Tasks (Implementation Rejection) + +If implementation was rejected, identify tasks related to the rejection reason and mark them as pending: + +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task pending +``` diff --git a/.claude/commands/workflow/resume.md b/.claude/commands/workflow/resume.md new file mode 100644 index 0000000..3c51afb --- /dev/null +++ b/.claude/commands/workflow/resume.md @@ -0,0 +1,159 @@ +--- +description: Resume an interrupted workflow from saved state +allowed-tools: Read, Write, Edit, Bash, AskUserQuestion, TodoWrite +--- + +# Workflow Orchestrator - Resume + +Resume a previously interrupted or paused workflow. + +## EXECUTION PROTOCOL + +### Step 1: Load Workflow State + +Read `.workflow/current.yml`: +- If not found: Report "No workflow to resume" and exit +- If found: Load state and continue + +### Step 2: Display Resume Summary + +``` +╔══════════════════════════════════════════════════════════════╗ +║ 🔄 RESUMING WORKFLOW ║ +╠══════════════════════════════════════════════════════════════╣ +║ Workflow ID: ║ +║ Feature: ║ +║ Started: ║ +║ Last Updated: ║ +╠══════════════════════════════════════════════════════════════╣ +║ CURRENT STATE ║ +║ Phase: ║ +║ Resume Point: ║ +╠══════════════════════════════════════════════════════════════╣ +║ PROGRESS ║ +║ Entities Designed: ║ +║ Tasks Created: ║ +║ Tasks Implemented: ║ +║ Tasks Reviewed: ║ +║ Tasks Completed: ║ +╠══════════════════════════════════════════════════════════════╣ +║ LAST ERROR (if any): ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +### Step 3: Confirm Resume + +**Ask user**: +- Option 1: "Continue - Resume from current point" +- Option 2: "Restart Phase - Redo current phase from beginning" +- Option 3: "Abort - Cancel workflow entirely" + +### Step 4: Resume Based on Phase + +**INITIALIZING**: +→ Continue to DESIGNING phase + +**DESIGNING**: +→ Continue architect work +→ Resume creating entities/tasks + +**AWAITING_DESIGN_APPROVAL**: +→ Present design summary again +→ Ask for approval + +**DESIGN_APPROVED**: +→ Continue to IMPLEMENTING phase + +**DESIGN_REJECTED**: +→ Show rejection reason +→ Return to DESIGNING with feedback + +**IMPLEMENTING**: +→ Find incomplete tasks +→ Continue implementation from next pending task + +**REVIEWING**: +→ Find tasks awaiting review +→ Continue review process + +**SECURITY_REVIEW**: +→ Continue security scanning +→ Run: `python3 skills/guardrail-orchestrator/scripts/security_scan.py --project-dir . --severity HIGH` +→ Run: `python3 skills/guardrail-orchestrator/scripts/validate_api_contract.py --project-dir .` +→ If passed: Transition to AWAITING_IMPL_APPROVAL +→ If critical issues: Return to IMPLEMENTING with security feedback + +**AWAITING_IMPL_APPROVAL**: +→ Present implementation summary again +→ Ask for approval + +**IMPL_APPROVED**: +→ Continue to COMPLETING phase + +**IMPL_REJECTED**: +→ Show rejection reason +→ Return to IMPLEMENTING with feedback + +**COMPLETING**: +→ Continue marking tasks complete + +**PAUSED**: +→ Resume from `resume_point.phase` + +**FAILED**: +→ Show error details +→ Ask user how to proceed: + - Retry failed operation + - Skip and continue + - Abort workflow + +### Step 5: Continue Workflow + +Execute remaining phases following `/workflow:spawn` protocol. + +## TASK-LEVEL RESUME + +If resuming during IMPLEMENTING phase: + +1. **Identify incomplete tasks**: + ```yaml + # Resume from first task not in 'completed' or 'approved' + resume_task: tasks.pending[0] || tasks.in_progress[0] || tasks.review[0] + ``` + +2. **Skip completed work**: + - Don't recreate files that exist and are valid + - Don't re-run validations that passed + +3. **Continue from failure point**: + - If task failed mid-implementation, restart that task + - If validation failed, show error and retry + +## STATE RECOVERY + +If `.workflow/current.yml` is corrupted: + +1. **Check for backup**: `.workflow/current.yml.bak` +2. **Attempt recovery from manifest**: + - Read `project_manifest.json` for entity status + - Scan version-specific tasks directory for task status + - Reconstruct workflow state +3. **If unrecoverable**: + - Report error + - Suggest starting fresh with `/workflow:spawn` + +## ABORT WORKFLOW + +If user chooses to abort: + +1. **Confirm abort**: + "This will cancel the workflow. Files already created will remain. Continue?" + +2. **If confirmed**: + - Archive state to `.workflow/history/_aborted.yml` + - Clear `.workflow/current.yml` + - Report: "Workflow aborted. Created files remain in place." + +3. **Cleanup options**: + - Offer to rollback created files (if git available) + - Offer to keep partial implementation diff --git a/.claude/commands/workflow/review.md b/.claude/commands/workflow/review.md new file mode 100644 index 0000000..c021dc8 --- /dev/null +++ b/.claude/commands/workflow/review.md @@ -0,0 +1,526 @@ +--- +description: Review implementation (Reviewer agent) +allowed-tools: Read, Bash +--- + +# Reviewer Agent - Review Mode + +**Input**: "$ARGUMENTS" + +--- + +## CRITICAL ENFORCEMENT RULES + +**YOU ARE IN READ-ONLY MODE. VIOLATIONS WILL BE BLOCKED.** + +### MUST DO (Non-Negotiable) +1. **MUST** run all validation checks (build, typecheck, lint, test, API contract) +2. **MUST** verify every file in task's `file_paths` exists +3. **MUST** read and analyze each implemented file +4. **MUST** check against acceptance_criteria in task file +5. **MUST** output structured review report (format below) +6. **MUST** run workflow_manager.py to update task status + +### CANNOT DO (Strictly Forbidden) +1. **CANNOT** create files (Write tool blocked) +2. **CANNOT** modify files (Edit tool blocked) +3. **CANNOT** fix issues yourself - only report them +4. **CANNOT** approve tasks with missing files +5. **CANNOT** approve if ANY validation check fails +6. **CANNOT** skip any validation check + +### ALLOWED ACTIONS +- Read any file +- Run Bash commands (build, lint, test, typecheck, ls, cat, grep) +- Output review reports +- Update task status via workflow_manager.py + +--- + +## VALIDATION CHECKS MATRIX + +| Check | Command | Blocks Approval | When | +|-------|---------|-----------------|------| +| Build | `npm run build` | YES | Always | +| TypeScript | `npx tsc --noEmit` | YES | Always | +| Async/Await | `python3 verify_async.py` | YES | Always | +| Lint | `npm run lint` | YES (if --strict) | --strict mode | +| Unit Tests | `npm test -- --passWithNoTests` | YES (if --strict) | --strict mode | +| API Contract | `python3 validate_api_contract.py` | YES | Always | +| Security Scan | `python3 security_scan.py` | YES (critical) | Always | +| Files Exist | `ls -la` each file | YES | Always | + +**Note:** For comprehensive security audit, use `/workflow:security --full` + +--- + +## ARGUMENT PARSING + +``` +IF "$ARGUMENTS" contains "--auto": + MODE = AUTO (batch review all tasks) + STRICT = "$ARGUMENTS" contains "--strict" + FULL = "$ARGUMENTS" contains "--full" +ELSE IF "$ARGUMENTS" = "--next": + MODE = SINGLE (next pending task) +ELSE: + MODE = SINGLE (specific task: "$ARGUMENTS") +``` + +--- + +## MODE: AUTO REVIEW (--auto) + +### Step A1: Get Active Version [MANDATORY] +```bash +VERSION_ID=$(python3 skills/guardrail-orchestrator/scripts/version_manager.py current) +TASKS_DIR=".workflow/versions/$VERSION_ID/tasks" +``` + +### Step A2: Run Global Validations [MANDATORY] + +#### 2.1 Build Check +```bash +npm run build 2>&1 +BUILD_EXIT=$? +echo "BUILD_EXIT=$BUILD_EXIT" +``` + +#### 2.2 TypeScript Strict Check +```bash +npx tsc --noEmit 2>&1 +TS_EXIT=$? +echo "TS_EXIT=$TS_EXIT" +``` + +#### 2.3 Async/Await Check [MANDATORY] +```bash +python3 skills/guardrail-orchestrator/scripts/verify_async.py --path . 2>&1 +ASYNC_EXIT=$? +echo "ASYNC_EXIT=$ASYNC_EXIT" +``` + +This catches runtime errors at build time: +- `fetch()` without `await` +- `.json()` without `await` +- `Promise.all()` without `await` +- Floating promises (unawaited async calls) + +**Exit codes:** +- 0 = PASS (no high-severity issues) +- 1 = HIGH severity issues found (blocks approval) + +#### 2.4 Lint Check (if --strict or --full) +```bash +npm run lint 2>&1 +LINT_EXIT=$? +echo "LINT_EXIT=$LINT_EXIT" +``` + +#### 2.5 Unit Tests (if --strict or --full) +```bash +npm test -- --passWithNoTests 2>&1 +TEST_EXIT=$? +echo "TEST_EXIT=$TEST_EXIT" +``` + +#### 2.6 API Contract Validation [MANDATORY] +```bash +python3 skills/guardrail-orchestrator/scripts/validate_api_contract.py --project-dir . 2>&1 +API_EXIT=$? +echo "API_EXIT=$API_EXIT" +``` + +This validates: +- Frontend API calls have matching backend endpoints +- HTTP methods match (GET, POST, PUT, DELETE) +- Request bodies are sent where expected +- Response handling matches backend output + +#### 2.7 Security Scan [MANDATORY] +```bash +# Run comprehensive security scanner +python3 skills/guardrail-orchestrator/scripts/security_scan.py \ + --project-dir . \ + --severity HIGH +SECURITY_EXIT=$? +echo "SECURITY_EXIT=$SECURITY_EXIT" +``` + +**Security scan checks:** +- Hardcoded secrets (API keys, passwords, tokens) +- SQL injection vulnerabilities +- XSS risks (dangerouslySetInnerHTML, innerHTML) +- Command injection patterns +- Path traversal vulnerabilities +- NoSQL injection risks +- SSRF vulnerabilities +- Prototype pollution +- Insecure authentication patterns +- CORS misconfigurations +- Sensitive data exposure +- Debug code in production + +**Exit codes:** +- 0 = PASS (no critical/high issues) +- 1 = HIGH issues found (warning) +- 2 = CRITICAL issues found (blocks approval) + +**For full security audit, run:** `/workflow:security --full` + +### Step A3: Gather All Tasks [MANDATORY] +```bash +ls $TASKS_DIR/*.yml 2>/dev/null +``` +**MUST process ALL task files found** + +### Step A4: Review Each Task [MANDATORY] + +For EACH task file: + +```bash +# Extract file_paths from task +TASK_FILES=$(grep -A 20 "file_paths:" "$TASK_FILE" | grep -E "^\s+-" | sed 's/.*- //') +``` + +**Check each file exists**: +```bash +for file in $TASK_FILES; do + if [ -f "$file" ]; then + echo "EXISTS: $file" + else + echo "MISSING: $file" + MISSING_COUNT=$((MISSING_COUNT + 1)) + fi +done +``` + +**Determine task verdict**: +``` +IF all files exist + AND BUILD_EXIT = 0 + AND TS_EXIT = 0 + AND ASYNC_EXIT = 0 + AND API_EXIT = 0 + AND SECURITY_EXIT != 2 (no critical security issues) + AND (not --strict OR (LINT_EXIT = 0 AND TEST_EXIT = 0)): + -> TASK_VERDICT = APPROVED +ELSE: + -> TASK_VERDICT = REJECTED + -> Record reason (missing files / build failure / type error / async issue / API mismatch / security issue) +``` + +**Security exit codes:** +- 0 = PASS +- 1 = HIGH issues (warning, doesn't block unless --strict) +- 2 = CRITICAL issues (always blocks) + +### Step A5: Batch Update [MANDATORY] + +For APPROVED tasks: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task approved +``` + +For REJECTED tasks: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task pending +``` + +### Step A6: Auto Review Report [MANDATORY] + +**MUST output this exact format**: +``` ++======================================================================+ +| REVIEW COMPLETE | ++======================================================================+ +| Version: $VERSION_ID | +| Mode: AUTO [STRICT if --strict] [FULL if --full] | ++======================================================================+ +| VALIDATION RESULTS | ++----------------------------------------------------------------------+ +| Build: PASS (exit 0) / FAIL (exit $BUILD_EXIT) | +| TypeScript: PASS (exit 0) / FAIL (exit $TS_EXIT) | +| Async/Await: PASS / FAIL (X high, Y medium issues) | +| Lint: PASS / FAIL / SKIPPED | +| Tests: PASS / FAIL / SKIPPED | +| API Contract: PASS / FAIL (X errors, Y warnings) | +| Security: PASS / WARNING / CRITICAL | +| (C:X H:X M:X L:X issues) | ++======================================================================+ +| API CONTRACT DETAILS | ++----------------------------------------------------------------------+ +| Frontend calls: X matched, Y unmatched | +| Backend endpoints: X defined, Y unused | +| Method mismatches: X | +| Body mismatches: X | ++======================================================================+ +| TASK RESULTS | ++----------------------------------------------------------------------+ +| Total: X tasks | +| Approved: X tasks | +| Rejected: X tasks | +| Skipped: X tasks (already completed) | ++======================================================================+ +| APPROVED TASKS | +| - task_create_Button | +| - task_create_Form | ++----------------------------------------------------------------------+ +| REJECTED TASKS | +| - task_create_Modal | +| Reason: Missing file app/components/Modal.tsx | +| - task_update_API | +| Reason: API contract error - endpoint not found | ++======================================================================+ +| SECURITY WARNINGS | +| - src/lib/api.ts:15 - Possible hardcoded API key | +| - app/page.tsx:42 - dangerouslySetInnerHTML usage | ++======================================================================+ +``` + +### Step A7: Next Steps [MANDATORY] + +**IF all approved**: +``` +All tasks approved. +Next: Run `/workflow:approve implementation` to continue. +``` + +**IF any rejected**: +``` +Some tasks need fixes. + +API Contract Issues: + For frontend issues: Fix the API call URL or method + For backend issues: Create or fix the API endpoint + +For each rejected task, run: + /workflow:frontend (for frontend tasks) + /workflow:backend (for backend tasks) + +Then re-run: /workflow:review --auto +``` + +--- + +## MODE: SINGLE TASK REVIEW (--next or task_id) + +### Step S1: Get Task [MANDATORY] +```bash +VERSION_ID=$(python3 skills/guardrail-orchestrator/scripts/version_manager.py current) +TASKS_DIR=".workflow/versions/$VERSION_ID/tasks" +``` + +**IF --next**: +```bash +# Find first task with status: pending or status: implemented +TASK_FILE=$(grep -l "status: pending\|status: implemented" $TASKS_DIR/*.yml 2>/dev/null | head -1) +``` + +**IF specific task_id**: +```bash +TASK_FILE="$TASKS_DIR/$ARGUMENTS.yml" +``` + +**BLOCK IF**: Task file does not exist -> Error: "Task not found: $ARGUMENTS" + +### Step S2: Read Task Spec [MANDATORY] +```bash +cat "$TASK_FILE" +``` + +Extract: +- `id`: Task identifier +- `file_paths`: List of files to verify +- `acceptance_criteria`: List of requirements to check + +### Step S3: Run All Validations [MANDATORY] + +```bash +# Build +npm run build 2>&1 +BUILD_EXIT=$? + +# TypeScript +npx tsc --noEmit 2>&1 +TS_EXIT=$? + +# API Contract +python3 skills/guardrail-orchestrator/scripts/validate_api_contract.py --project-dir . 2>&1 +API_EXIT=$? +``` + +**MUST capture and report all exit codes** + +### Step S4: Verify Files Exist [MANDATORY] + +For each path in `file_paths`: +```bash +ls -la "$path" 2>/dev/null && echo "EXISTS" || echo "MISSING" +``` + +**Record**: +- FILES_EXIST = true/false +- MISSING_FILES = list of missing paths + +### Step S5: Read and Analyze Files [MANDATORY] + +For each EXISTING file: +1. Read file content +2. Check against acceptance_criteria: + - [ ] File exports correct components/functions + - [ ] Props/types match manifest spec + - [ ] Code follows project patterns + - [ ] No obvious bugs or issues +3. Check API contract compliance: + - [ ] Frontend calls use correct endpoints + - [ ] HTTP methods are appropriate + - [ ] Request bodies are properly structured + - [ ] Response handling is correct + +### Step S6: Determine Verdict [MANDATORY] + +``` +IF BUILD_EXIT = 0 + AND TS_EXIT = 0 + AND API_EXIT = 0 + AND FILES_EXIST = true + AND acceptance_criteria met: + -> VERDICT = APPROVED +ELSE: + -> VERDICT = REQUEST_CHANGES + -> Record all issues found +``` + +### Step S7: Update Task Status [MANDATORY] + +**IF APPROVED**: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task $TASK_ID approved +``` + +**IF REQUEST_CHANGES**: +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py task $TASK_ID pending +``` + +### Step S8: Output Review Report [MANDATORY] + +**MUST output this exact format**: +``` ++======================================================================+ +| TASK REVIEW | ++======================================================================+ +| Task: $TASK_ID | +| Version: $VERSION_ID | ++======================================================================+ +| VALIDATION CHECKS | ++----------------------------------------------------------------------+ +| Build: PASS / FAIL | +| TypeScript: PASS / FAIL | +| API Contract: PASS / FAIL | +| Files exist: PASS / FAIL (X/Y files) | +| Acceptance criteria: PASS / PARTIAL / FAIL | +| Code quality: PASS / ISSUES | ++======================================================================+ +| API CONTRACT STATUS | ++----------------------------------------------------------------------+ +| Endpoint calls: X found, Y matched | +| Method correctness: PASS / X mismatches | +| Request bodies: PASS / X issues | +| Response handling: PASS / ISSUES | ++======================================================================+ +| VERDICT: APPROVED / REQUEST_CHANGES | ++======================================================================+ +| [If REQUEST_CHANGES, list all issues:] | +| 1. Missing file: app/components/Button.tsx | +| 2. TypeScript error: Type 'string' not assignable to 'number' | +| 3. API contract: POST /api/users called but endpoint expects GET | +| 4. API contract: Frontend sends body but backend ignores it | +| 5. Acceptance criterion not met: "Must support dark mode" | ++======================================================================+ +``` + +### Step S9: Next Steps [MANDATORY] + +**IF APPROVED**: +``` +Task approved. +Next: Run `/workflow:complete $TASK_ID` to mark as done. + Or run `/workflow:review --next` to review next task. +``` + +**IF REQUEST_CHANGES**: +``` +Changes requested. + +Issues to fix: + [List specific issues with file locations] + +For API contract issues: + - If frontend issue: Fix the fetch/axios call in [file:line] + - If backend issue: Update the API route handler in [file] + +Fix issues and re-run: + /workflow:frontend $TASK_ID (for frontend tasks) + /workflow:backend $TASK_ID (for backend tasks) +Then: /workflow:review $TASK_ID +``` + +--- + +## USAGE EXAMPLES + +```bash +# Review specific task +/workflow:review task_create_Button + +# Review next pending task +/workflow:review --next + +# Auto-review all tasks (standard - build + types + API) +/workflow:review --auto + +# Auto-review all tasks (strict - includes lint + tests) +/workflow:review --auto --strict + +# Full review with all checks +/workflow:review --auto --full +``` + +--- + +## API CONTRACT VALIDATION DETAILS + +The API contract validator checks: + +### Frontend Analysis +- **fetch()** calls with `/api/` paths +- **axios** requests (get, post, put, delete) +- **useSWR** data fetching hooks +- **Custom API clients** (api.get, api.post, etc.) + +### Backend Analysis +- **Next.js App Router**: `app/api/*/route.ts` exports (GET, POST, PUT, DELETE) +- **Next.js Pages Router**: `pages/api/*.ts` with req.method checks +- **Express-style**: router.get/post/etc patterns + +### Validation Rules +1. **Endpoint Existence**: Every frontend call must have a matching backend route +2. **Method Match**: GET calls must hit GET endpoints, POST to POST, etc. +3. **Body Alignment**: POST/PUT calls should send bodies, GET should not +4. **Unused Endpoints**: Backend routes not called by frontend (warnings) + +--- + +## ENFORCEMENT CHECKLIST + +Before completing this command, verify: +- [ ] Build command executed and exit code captured +- [ ] TypeScript check executed and exit code captured +- [ ] API contract validation executed and exit code captured +- [ ] All file_paths verified with ls command +- [ ] Security scan completed +- [ ] Structured review report output (exact format above) +- [ ] Task status updated via workflow_manager.py +- [ ] Next steps clearly stated diff --git a/.claude/commands/workflow/security.md b/.claude/commands/workflow/security.md new file mode 100644 index 0000000..d76fd24 --- /dev/null +++ b/.claude/commands/workflow/security.md @@ -0,0 +1,342 @@ +--- +description: Run comprehensive security audit (Security Reviewer agent) +allowed-tools: Read, Bash, Grep, Task +--- + +# Security Reviewer Agent - Security Audit Mode + +**Input**: "$ARGUMENTS" + +--- + +## CRITICAL CONSTRAINTS + +**YOU ARE IN READ-ONLY MODE FOR ANALYSIS.** + +### MUST DO (Non-Negotiable) +1. **MUST** run automated security scanner +2. **MUST** analyze all CRITICAL and HIGH findings +3. **MUST** check dependency vulnerabilities +4. **MUST** review security configurations +5. **MUST** output structured security report +6. **MUST** provide remediation guidance + +### CANNOT DO (Strictly Forbidden) +1. **CANNOT** modify source files +2. **CANNOT** fix issues directly +3. **CANNOT** approve with CRITICAL issues +4. **CANNOT** skip any security category + +--- + +## ARGUMENT PARSING + +``` +IF "$ARGUMENTS" contains "--quick": + MODE = QUICK (scanner only) +ELSE IF "$ARGUMENTS" contains "--full": + MODE = FULL (scanner + deep analysis + deps + config) +ELSE: + MODE = STANDARD (scanner + deps) + +SEVERITY = extract from --severity [critical|high|medium|low] +OUTPUT = extract from --json (JSON output) or text +``` + +--- + +## EXECUTION FLOW + +### Step 1: Run Automated Security Scanner [MANDATORY] + +```bash +python3 skills/guardrail-orchestrator/scripts/security_scan.py \ + --project-dir . \ + --severity ${SEVERITY:-LOW} \ + ${OUTPUT:+--json} +``` + +**Capture output and exit code:** +```bash +SCAN_EXIT=$? +echo "SCAN_EXIT=$SCAN_EXIT" +``` + +**Exit codes:** +- 0 = PASS (no critical/high issues) +- 1 = HIGH issues found +- 2 = CRITICAL issues found + +### Step 2: Dependency Audit [MANDATORY unless --quick] + +```bash +echo "=== Dependency Audit ===" +npm audit --json 2>/dev/null || echo '{"vulnerabilities":{}}' +``` + +**Parse npm audit results:** +- Count critical, high, moderate, low vulnerabilities +- List affected packages and versions +- Note if fixes available (`npm audit fix`) + +### Step 3: Deep Analysis [FULL mode only] + +For each CRITICAL/HIGH finding from scanner: + +#### 3.1 Data Flow Tracing +Use Task agent with security-engineer subagent: +``` +Analyze data flow for vulnerability at [file:line]. +Trace user input from source to sink. +Identify all potential attack vectors. +Assess exploitability and impact. +``` + +#### 3.2 Attack Vector Analysis +For each vulnerability type: +- SQL Injection → Check if input reaches query without sanitization +- XSS → Check if input reaches DOM without encoding +- Command Injection → Check if input reaches shell without escaping +- Path Traversal → Check if input reaches file system without validation + +### Step 4: Configuration Review [FULL mode only] + +#### 4.1 CORS Configuration +```bash +grep -rn "cors\|Access-Control" app/ src/ pages/ --include="*.ts" --include="*.tsx" --include="*.js" +``` + +Check for: +- Wildcard origins (`*`) +- Credentials with permissive origins +- Missing CORS on sensitive endpoints + +#### 4.2 Security Headers +```bash +grep -rn "helmet\|Content-Security-Policy\|X-Frame-Options\|X-XSS-Protection" . --include="*.ts" --include="*.js" +``` + +Check for: +- Helmet middleware usage +- CSP configuration +- X-Frame-Options +- X-Content-Type-Options + +#### 4.3 Authentication Configuration +```bash +grep -rn "jwt\|session\|auth\|cookie" app/ src/ pages/ --include="*.ts" --include="*.tsx" +``` + +Check for: +- JWT algorithm (avoid 'none', prefer RS256) +- Session configuration +- Cookie flags (httpOnly, secure, sameSite) + +#### 4.4 Environment Variables +```bash +# Check .env files are gitignored +cat .gitignore 2>/dev/null | grep -E "\.env" + +# Check for env var usage +grep -rn "process\.env\." app/ src/ --include="*.ts" --include="*.tsx" | head -20 +``` + +### Step 5: Manual Review Checklist [FULL mode only] + +Read each file modified in current workflow and verify: + +**Input Validation** +- [ ] All user inputs validated +- [ ] Type checking enforced +- [ ] Length limits applied +- [ ] Format validation (email, URL, etc.) + +**Output Encoding** +- [ ] HTML encoding for DOM insertion +- [ ] URL encoding for URLs +- [ ] JSON encoding for API responses + +**Database Security** +- [ ] Parameterized queries used +- [ ] No string concatenation in queries +- [ ] Proper ORM usage + +**Authentication/Authorization** +- [ ] Auth checks on protected routes +- [ ] Role-based access control +- [ ] Session validation + +**Error Handling** +- [ ] Generic error messages to users +- [ ] No stack traces in production +- [ ] No sensitive data in logs + +### Step 6: Generate Security Report [MANDATORY] + +**MUST output this exact format:** + +``` ++======================================================================+ +| SECURITY AUDIT REPORT | ++======================================================================+ +| Mode: QUICK / STANDARD / FULL | +| Date: [current date] | +| Project: [project name from package.json] | ++======================================================================+ +| RISK ASSESSMENT | ++----------------------------------------------------------------------+ +| Overall Risk: CRITICAL / HIGH / MEDIUM / LOW / PASS | +| | +| Static Analysis: X issues (C:X H:X M:X L:X) | +| Dependencies: X vulnerabilities | +| Configuration: X concerns | ++======================================================================+ +| CRITICAL ISSUES (Immediate Action Required) | ++----------------------------------------------------------------------+ +| [1] [CATEGORY] Title | +| Location: file:line | +| CWE: CWE-XXX | +| OWASP: A0X:2021-Category | +| Evidence: [code snippet] | +| Impact: [description of potential attack] | +| Fix: [specific remediation steps] | +| | +| [2] ... | ++======================================================================+ +| HIGH ISSUES (Fix Before Production) | ++----------------------------------------------------------------------+ +| [3] ... | ++======================================================================+ +| MEDIUM ISSUES (Should Fix) | ++----------------------------------------------------------------------+ +| [4] ... | ++======================================================================+ +| DEPENDENCY VULNERABILITIES | ++----------------------------------------------------------------------+ +| Package Version Severity Fix Available | +| lodash 4.17.20 HIGH npm audit fix | +| axios 0.21.0 MEDIUM npm audit fix | ++======================================================================+ +| CONFIGURATION CONCERNS | ++----------------------------------------------------------------------+ +| - CORS: Wildcard origin detected in src/middleware.ts | +| - Session: Missing httpOnly flag on auth cookie | +| - Headers: No CSP header configured | ++======================================================================+ +| REMEDIATION PRIORITY | ++----------------------------------------------------------------------+ +| 1. [CRITICAL] Rotate exposed API key in src/lib/api.ts | +| 2. [CRITICAL] Fix SQL injection in app/api/users/route.ts | +| 3. [HIGH] Update lodash to 4.17.21 | +| 4. [HIGH] Add input validation to user registration | +| 5. [MEDIUM] Configure CSP headers | ++======================================================================+ +| VERDICT | ++----------------------------------------------------------------------+ +| FAIL - X critical issues must be fixed before deployment | +| or | +| PASS - No blocking security issues found | ++======================================================================+ +``` + +--- + +## VERDICT DETERMINATION + +### FAIL Conditions +- Any CRITICAL issue found +- 3+ HIGH issues found +- Critical npm vulnerabilities without fix +- Exposed secrets or credentials + +### PASS WITH WARNINGS +- Only MEDIUM/LOW issues +- All HIGH issues have accepted risk +- Dependencies have fixes available + +### PASS +- No CRITICAL/HIGH issues +- Dependencies up to date +- Configurations reviewed + +--- + +## POST-AUDIT ACTIONS + +### If FAIL: +``` +SECURITY AUDIT FAILED + +Blocking issues must be fixed: +1. [List critical issues] + +For each issue: + /workflow:frontend - if frontend issue + /workflow:backend - if backend issue + +Then re-run: /workflow:security +``` + +### If PASS: +``` +SECURITY AUDIT PASSED + +Proceed with: /workflow:review --auto +``` + +--- + +## USAGE EXAMPLES + +```bash +# Quick scan (automated scanner only) +/workflow:security --quick + +# Standard scan (scanner + dependencies) +/workflow:security + +# Full audit (all checks) +/workflow:security --full + +# Filter by severity +/workflow:security --severity high + +# JSON output for CI/CD +/workflow:security --json +``` + +--- + +## INTEGRATION WITH CI/CD + +### Pre-commit Hook +```bash +python3 skills/guardrail-orchestrator/scripts/security_scan.py \ + --project-dir . --severity HIGH --strict +``` + +### GitHub Actions +```yaml +- name: Security Scan + run: | + python3 skills/guardrail-orchestrator/scripts/security_scan.py \ + --project-dir . --json > security-report.json + + if [ $? -ne 0 ]; then + echo "Security issues found!" + cat security-report.json + exit 1 + fi +``` + +--- + +## ENFORCEMENT CHECKLIST + +Before completing this command, verify: +- [ ] Automated scanner executed +- [ ] All categories analyzed +- [ ] Dependencies audited (unless --quick) +- [ ] Structured report output +- [ ] Remediation guidance provided +- [ ] Clear verdict stated diff --git a/.claude/commands/workflow/spawn.md b/.claude/commands/workflow/spawn.md index 9ebb856..43d6053 100644 --- a/.claude/commands/workflow/spawn.md +++ b/.claude/commands/workflow/spawn.md @@ -21,8 +21,6 @@ allowed-tools: Read, Write, Edit, Bash, Task, AskUserQuestion, TodoWrite 5. **MUST** create task files in `.workflow/versions/vXXX/tasks/` (version-specific) 6. **MUST** wait for Task agents to fully complete before proceeding 7. **MUST** run `npm run build` and verify exit code = 0 before approval -8. **MUST** run `npx tsc --noEmit` (type check) and verify exit code = 0 -9. **MUST** run `npm run lint` and verify exit code = 0 ### 🚫 CANNOT DO (Strictly Forbidden) 1. **CANNOT** skip phases or combine phases @@ -41,8 +39,6 @@ These conditions **HALT** the workflow immediately: | `project_manifest.json` missing | INITIALIZE | Create manifest first | | No task files created | DESIGNING → IMPLEMENTING | Architect must create tasks | | Build fails | IMPLEMENTING → REVIEWING | Fix build errors | -| Type check fails | IMPLEMENTING → REVIEWING | Fix TypeScript errors | -| Lint fails | IMPLEMENTING → REVIEWING | Fix lint errors | | Files missing | REVIEWING | Implement missing files | | Version mismatch | Any | Run `/workflow:status` | @@ -94,21 +90,6 @@ FEATURE = "$ARGUMENTS" with "--auto" and "--full-auto" removed and trimmed **Entry Condition**: Command invoked **Exit Condition**: Version created, session started, phase = DESIGNING -#### Step 1.0: Gate Entry Check [MANDATORY - BLOCKING] -```bash -# Initialize gate state for new workflow -python3 skills/guardrail-orchestrator/scripts/phase_gate.py can-enter INITIALIZING -GATE_EXIT=$? -if [ $GATE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot start workflow" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Enter the phase (records entry timestamp) -python3 skills/guardrail-orchestrator/scripts/phase_gate.py enter INITIALIZING -``` - #### Step 1.1: Parse Arguments [MANDATORY] ``` Extract: AUTO_MODE, FEATURE @@ -154,25 +135,8 @@ python3 skills/guardrail-orchestrator/scripts/version_manager.py create "$FEATUR ╚══════════════════════════════════════════════════════════════╝ ``` -#### Step 1.5: Complete Phase & Transition [MANDATORY] +#### Step 1.5: Transition [MANDATORY] ```bash -# Save checkpoints -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint manifest_exists \ - --phase INITIALIZING --status passed -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint version_created \ - --phase INITIALIZING --status passed \ - --data "{\"version\": \"$VERSION_ID\"}" - -# Complete the phase -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete INITIALIZING -COMPLETE_EXIT=$? -if [ $COMPLETE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot complete INITIALIZING phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Transition to next phase python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition DESIGNING ``` **VERIFY**: Script exits with code 0 @@ -557,24 +521,9 @@ acceptance_criteria: ### PHASE 2: DESIGNING (Enhanced with Design Document) ### ═══════════════════════════════════════════════════════════════ -**Entry Condition**: Phase = DESIGNING (verified via gate check) +**Entry Condition**: Phase = DESIGNING (verified) **Exit Condition**: Design document validated, dependency graph generated, tasks with context created -#### Step 2.0: Gate Entry Check [MANDATORY - BLOCKING] -```bash -# MUST pass before proceeding - HALT if fails -python3 skills/guardrail-orchestrator/scripts/phase_gate.py can-enter DESIGNING -GATE_EXIT=$? -if [ $GATE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot enter DESIGNING phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Enter the phase (records entry timestamp) -python3 skills/guardrail-orchestrator/scripts/phase_gate.py enter DESIGNING -``` - #### Step 2.1: Verify Phase State [MANDATORY] ```bash python3 skills/guardrail-orchestrator/scripts/workflow_manager.py status @@ -719,11 +668,35 @@ python3 skills/guardrail-orchestrator/scripts/validate_design.py \ **BLOCK IF**: Validation fails (exit code != 0) → Display errors, re-run design +#### Step 2.4.5: Generate API Contract & Shared Types [MANDATORY] +**Generate the API contract that binds frontend and backend implementations**: + +```bash +python3 skills/guardrail-orchestrator/scripts/generate_api_contract.py \ + .workflow/versions/$VERSION_ID/design/design_document.yml \ + --output-dir .workflow/versions/$VERSION_ID +``` + +**This generates:** +- `contracts/api_contract.yml` - Strict API type definitions +- `app/types/api.ts` - Shared TypeScript interfaces + +**CRITICAL**: Both frontend and backend agents MUST import from `app/types/api.ts`. +This ensures type safety and contract compliance. + +**BLOCK IF**: Generation fails → Display errors, re-run design + #### Step 2.5: Verify Generated Artifacts [MANDATORY] ```bash # Check dependency graph exists ls .workflow/versions/$VERSION_ID/dependency_graph.yml +# Check API contract exists +ls .workflow/versions/$VERSION_ID/contracts/api_contract.yml + +# Check shared types file exists +ls app/types/api.ts + # Count generated tasks TASK_COUNT=$(ls .workflow/versions/$VERSION_ID/tasks/*.yml 2>/dev/null | wc -l) echo "Tasks generated: $TASK_COUNT" @@ -734,6 +707,7 @@ echo "Context files: $CONTEXT_COUNT" ``` **BLOCK IF**: TASK_COUNT = 0 → Error: "No tasks generated from design" +**BLOCK IF**: API contract missing → Error: "API contract not generated" #### Step 2.6: Display Layered Execution Plan [MANDATORY] Read dependency_graph.yml and display: @@ -766,27 +740,8 @@ Read dependency_graph.yml and display: ╚══════════════════════════════════════════════════════════════╝ ``` -#### Step 2.7: Complete Phase & Transition [MANDATORY] +#### Step 2.7: Transition [MANDATORY] ```bash -# Save checkpoints -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint design_document_created \ - --phase DESIGNING --status passed -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint design_validated \ - --phase DESIGNING --status passed -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint tasks_generated \ - --phase DESIGNING --status passed \ - --data "{\"task_count\": $TASK_COUNT, \"context_count\": $CONTEXT_COUNT}" - -# Complete the phase -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete DESIGNING -COMPLETE_EXIT=$? -if [ $COMPLETE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot complete DESIGNING phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Update progress and transition python3 skills/guardrail-orchestrator/scripts/workflow_manager.py progress \ --tasks-created $TASK_COUNT python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition AWAITING_DESIGN_APPROVAL @@ -831,34 +786,11 @@ python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition AWA ### PHASE 3: GATE 1 - Design Approval ### ═══════════════════════════════════════════════════════════════ -**Entry Condition**: Phase = AWAITING_DESIGN_APPROVAL (verified via gate check) +**Entry Condition**: Phase = AWAITING_DESIGN_APPROVAL **Exit Condition**: Design approved, phase = IMPLEMENTING -#### Step 3.0: Gate Entry Check [MANDATORY - BLOCKING] -```bash -# MUST pass before proceeding - HALT if fails -python3 skills/guardrail-orchestrator/scripts/phase_gate.py can-enter AWAITING_DESIGN_APPROVAL -GATE_EXIT=$? -if [ $GATE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot enter AWAITING_DESIGN_APPROVAL phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Enter the phase (records entry timestamp) -python3 skills/guardrail-orchestrator/scripts/phase_gate.py enter AWAITING_DESIGN_APPROVAL -``` - #### IF AUTO_MODE = true: ```bash -# Save approval checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint design_approved \ - --phase AWAITING_DESIGN_APPROVAL --status passed \ - --data "{\"approver\": \"auto\", \"mode\": \"auto\"}" - -# Complete the phase -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete AWAITING_DESIGN_APPROVAL - python3 skills/guardrail-orchestrator/scripts/workflow_manager.py approve design python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPLEMENTING ``` @@ -870,52 +802,28 @@ Use AskUserQuestion: Question: "Review the design. How do you want to proceed?" Options: 1. "Approve - Continue to implementation" - 2. "Reject - Revise design" + 2. "Reject - Revise design" 3. "Pause - Save and exit" ``` -**On Approve**: -```bash -# Save approval checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint design_approved \ - --phase AWAITING_DESIGN_APPROVAL --status passed \ - --data "{\"approver\": \"user\", \"mode\": \"manual\"}" - -# Complete the phase -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete AWAITING_DESIGN_APPROVAL - -python3 skills/guardrail-orchestrator/scripts/workflow_manager.py approve design -python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPLEMENTING -``` +**On Approve**: Run transition commands above **On Reject**: Return to Phase 2 **On Pause**: Output resume command and stop --- ### ═══════════════════════════════════════════════════════════════ -### PHASE 4: IMPLEMENTING (Layer-Based Parallel Execution) +### PHASE 4: IMPLEMENTING (Team-Based Parallel Execution) ### ═══════════════════════════════════════════════════════════════ -**Entry Condition**: Phase = IMPLEMENTING (verified via gate check) -**Exit Condition**: All layers implemented in order, build passes +**Entry Condition**: Phase = IMPLEMENTING (verified) +**Exit Condition**: Both teams complete, build passes, API contract validated -**KEY CHANGE**: Tasks are now executed LAYER BY LAYER with FULL CONTEXT. -Each subagent receives a context snapshot with all dependencies. +**TEAM-BASED PARALLELISM**: Two specialized agents run in parallel: +- **Backend Team**: Implements models + APIs (Layer 1 + Layer 2) +- **Frontend Team**: Implements components + pages (Layer 3) -#### Step 4.0: Gate Entry Check [MANDATORY - BLOCKING] -```bash -# MUST pass before proceeding - HALT if fails -python3 skills/guardrail-orchestrator/scripts/phase_gate.py can-enter IMPLEMENTING -GATE_EXIT=$? -if [ $GATE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot enter IMPLEMENTING phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Enter the phase (records entry timestamp) -python3 skills/guardrail-orchestrator/scripts/phase_gate.py enter IMPLEMENTING -``` +Both teams share the same API contract (`app/types/api.ts`) ensuring type safety. #### Step 4.1: Verify Phase State [MANDATORY] ```bash @@ -923,156 +831,216 @@ python3 skills/guardrail-orchestrator/scripts/workflow_manager.py status ``` **BLOCK IF**: Phase is not IMPLEMENTING -#### Step 4.2: Load Dependency Graph [MANDATORY] +#### Step 4.2: Load Contract and Tasks [MANDATORY] ```bash -# Read dependency graph to get layers -cat .workflow/versions/$VERSION_ID/dependency_graph.yml +# Read API contract +cat .workflow/versions/$VERSION_ID/contracts/api_contract.yml + +# Verify shared types exist +ls app/types/api.ts + +# Count backend and frontend tasks +BACKEND_TASKS=$(grep -l 'agent: backend' .workflow/versions/$VERSION_ID/tasks/*.yml 2>/dev/null | wc -l) +FRONTEND_TASKS=$(grep -l 'agent: frontend' .workflow/versions/$VERSION_ID/tasks/*.yml 2>/dev/null | wc -l) +echo "Backend tasks: $BACKEND_TASKS" +echo "Frontend tasks: $FRONTEND_TASKS" ``` -Extract layer information: -- Layer count -- Tasks per layer -- Dependencies per task +#### Step 4.3: Launch Both Teams IN PARALLEL [MANDATORY] +**CRITICAL**: Launch BOTH agents in a SINGLE message with TWO Task tool calls. +This enables true parallel execution. -#### Step 4.3: Execute Layers Sequentially [MANDATORY] - -**FOR EACH LAYER (1, 2, 3, ...):** - -##### Step 4.3.1: Get Layer Tasks -```bash -# Get all tasks for current layer -LAYER_TASKS=$(grep -l "layer: $LAYER_NUM" .workflow/versions/$VERSION_ID/tasks/*.yml) -``` - -##### Step 4.3.2: Spawn Parallel Agents for Layer [MANDATORY] -**Launch ALL tasks in current layer IN PARALLEL using multiple Task tool calls** - -For EACH task in layer, spawn agent with FULL CONTEXT: - -``` -Use Task tool with: - subagent_type: "backend-architect" OR "frontend-architect" # Based on task.agent - prompt: | - # IMPLEMENTATION AGENT - $TASK_ID - # VERSION $VERSION_ID | LAYER $LAYER_NUM - - ## YOUR SINGLE TASK - You are implementing ONE entity with FULL CONTEXT provided. - - ## CONTEXT (Read this first!) - Context file: .workflow/versions/$VERSION_ID/contexts/$ENTITY_ID.yml - - Read the context file. It contains: - - target: The entity you are implementing (full definition) - - related: All models/APIs/components you need to know about - - dependencies: What this entity depends on (already implemented) - - files: Files to create and reference files for patterns - - acceptance: Criteria that must be met - - ## TASK DETAILS - Task file: .workflow/versions/$VERSION_ID/tasks/$TASK_ID.yml - - ## IMPLEMENTATION PROCESS - - 1. **Read Context File** [MANDATORY] - ```bash - cat .workflow/versions/$VERSION_ID/contexts/$ENTITY_ID.yml - ``` - - 2. **Read Task File** [MANDATORY] - ```bash - cat .workflow/versions/$VERSION_ID/tasks/$TASK_ID.yml - ``` - - 3. **Read Reference Files** (from context.files.reference) - These show existing patterns to follow. - - 4. **Implement** - Create file(s) at exact paths from context.files.to_create - Follow patterns from reference files - Meet all acceptance criteria - - 5. **Verify** - ```bash - # Check file exists - ls - - # Check TypeScript - npx tsc --noEmit 2>&1 || true - ``` - - ## OUTPUT FORMAT - ``` - === TASK COMPLETE: $TASK_ID === - Entity: $ENTITY_ID - Layer: $LAYER_NUM - Files created: - - ✓ - TypeScript: PASS/FAIL - Acceptance criteria: - - [criterion 1]: PASS/FAIL - - [criterion 2]: PASS/FAIL - ``` -``` - -**IMPORTANT**: Launch ALL tasks in the same layer using PARALLEL Task tool calls in a single message. - -##### Step 4.3.3: Wait for Layer Completion [MANDATORY] -**MUST wait for ALL agents in current layer to complete before proceeding to next layer** - -##### Step 4.3.4: Verify Layer [MANDATORY] -```bash -# Check all files for this layer exist -for task in $LAYER_TASKS; do - # Extract file_paths and verify - grep "to_create:" -A 5 .workflow/versions/$VERSION_ID/contexts/*.yml | grep "app/" | while read path; do - ls "$path" 2>/dev/null || echo "MISSING: $path" - done -done -``` - -**BLOCK IF**: Any files missing in layer → Do not proceed to next layer - -##### Step 4.3.5: Display Layer Progress [MANDATORY] ``` ╔══════════════════════════════════════════════════════════════╗ -║ ✅ LAYER $LAYER_NUM COMPLETE ║ +║ 🚀 LAUNCHING PARALLEL TEAMS ║ ╠══════════════════════════════════════════════════════════════╣ -║ Tasks completed: X ║ -║ Files created: X ║ -║ Proceeding to Layer $NEXT_LAYER... ║ +║ ║ +║ ┌─────────────────┐ ┌─────────────────┐ ║ +║ │ BACKEND TEAM │ || │ FRONTEND TEAM │ ║ +║ │ │ || │ │ ║ +║ │ • Models │ || │ • Components │ ║ +║ │ • APIs │ || │ • Pages │ ║ +║ │ │ || │ │ ║ +║ │ Exports: │ || │ Imports: │ ║ +║ │ /api/* routes │ ──→→ ──→│ app/types/api │ ║ +║ └─────────────────┘ └─────────────────┘ ║ +║ ║ +║ SHARED CONTRACT: app/types/api.ts ║ +║ ║ ╚══════════════════════════════════════════════════════════════╝ ``` -**REPEAT for next layer until all layers complete** +##### Step 4.3.1: Spawn Backend Team Agent [IN PARALLEL] +``` +Use Task tool with: + subagent_type: "backend-architect" + prompt: | + # BACKEND TEAM - VERSION $VERSION_ID -#### Step 4.4: Post-Implementation Verification [MANDATORY] + ## YOUR MISSION + Implement ALL backend tasks (models + APIs) for this workflow. + You own the entire backend layer. + + ## CRITICAL: API CONTRACT COMPLIANCE + You MUST import types from the shared types file: + ```typescript + import type { User, CreateUserRequest, ... } from '@/types/api'; + ``` + + The API contract is at: .workflow/versions/$VERSION_ID/contracts/api_contract.yml + The shared types are at: app/types/api.ts + + **DO NOT** create your own types. Use the shared types. + + ## TASK FILES + Read all backend tasks: + ```bash + ls .workflow/versions/$VERSION_ID/tasks/task_*model*.yml + ls .workflow/versions/$VERSION_ID/tasks/task_*api*.yml + ``` + + ## IMPLEMENTATION ORDER + 1. **First**: Implement ALL models (Layer 1) + - Add to prisma/schema.prisma + - Run: npx prisma generate + + 2. **Then**: Implement ALL API endpoints (Layer 2) + - Create app/api/*/route.ts files + - Import shared types from @/types/api + - Implement request validation + - Return responses matching contract types + + ## FOR EACH TASK + 1. Read the task file and context file + 2. Read reference files for patterns + 3. Implement following the contract + 4. Verify with TypeScript + + ## VERIFICATION + After all tasks: + ```bash + npx prisma generate 2>&1 || true + npx tsc --noEmit 2>&1 || true + ``` + + ## OUTPUT FORMAT + ``` + === BACKEND TEAM COMPLETE === + Models implemented: + - model_xxx: app/api/... ✓ + + APIs implemented: + - api_xxx: app/api/.../route.ts ✓ + - api_yyy: app/api/.../route.ts ✓ + + Contract compliance: + - Types imported from @/types/api ✓ + - All endpoints match contract ✓ + + TypeScript: PASS/FAIL + Prisma: PASS/FAIL + ``` +``` + +##### Step 4.3.2: Spawn Frontend Team Agent [IN PARALLEL] +``` +Use Task tool with: + subagent_type: "frontend-architect" + prompt: | + # FRONTEND TEAM - VERSION $VERSION_ID + + ## YOUR MISSION + Implement ALL frontend tasks (components + pages) for this workflow. + You own the entire frontend layer. + + ## CRITICAL: API CONTRACT COMPLIANCE + You MUST import types from the shared types file: + ```typescript + import type { User, ApiError, ... } from '@/types/api'; + import { API_PATHS } from '@/types/api'; + ``` + + The API contract is at: .workflow/versions/$VERSION_ID/contracts/api_contract.yml + The shared types are at: app/types/api.ts + + **DO NOT** create your own API types. Use the shared types. + + ## TASK FILES + Read all frontend tasks: + ```bash + ls .workflow/versions/$VERSION_ID/tasks/task_*component*.yml + ls .workflow/versions/$VERSION_ID/tasks/task_*page*.yml + ``` + + ## IMPLEMENTATION + For EACH task: + 1. Read the task file and context file + 2. Read reference files for patterns + 3. Create component/page file + 4. Import types from @/types/api + 5. Use API_PATHS for fetch calls + 6. Type all props, state, and API responses + + ## API CALL PATTERN (USE THIS) + ```typescript + import type { User, CreateUserRequest } from '@/types/api'; + import { API_PATHS } from '@/types/api'; + + // Type-safe API call + const response = await fetch(API_PATHS.CREATE_USER, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data satisfies CreateUserRequest), + }); + const result: User = await response.json(); + ``` + + ## VERIFICATION + After all tasks: + ```bash + npx tsc --noEmit 2>&1 || true + ``` + + ## OUTPUT FORMAT + ``` + === FRONTEND TEAM COMPLETE === + Components implemented: + - component_xxx: app/components/Xxx.tsx ✓ + + Pages implemented: + - page_xxx: app/xxx/page.tsx ✓ + + Contract compliance: + - Types imported from @/types/api ✓ + - API calls use API_PATHS ✓ + + TypeScript: PASS/FAIL + ``` +``` + +**IMPORTANT**: You MUST send BOTH Task tool calls in a SINGLE message. +This enables parallel execution. Do NOT wait for one to complete. + +#### Step 4.4: Wait for Both Teams [MANDATORY] +**MUST wait for BOTH agents to complete before proceeding.** + +Display progress: +``` +╔══════════════════════════════════════════════════════════════╗ +║ ⏳ TEAMS EXECUTING... ║ +╠══════════════════════════════════════════════════════════════╣ +║ Backend Team: 🔄 In Progress ║ +║ Frontend Team: 🔄 In Progress ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +#### Step 4.5: Post-Implementation Verification [MANDATORY] ```bash # Verify build passes npm run build -BUILD_EXIT=$? -echo "Build exit code: $BUILD_EXIT" - -# Verify type check passes -npx tsc --noEmit -TYPE_EXIT=$? -echo "Type check exit code: $TYPE_EXIT" - -# Verify lint passes -npm run lint -LINT_EXIT=$? -echo "Lint exit code: $LINT_EXIT" - -# Check all passed -if [ $BUILD_EXIT -ne 0 ] || [ $TYPE_EXIT -ne 0 ] || [ $LINT_EXIT -ne 0 ]; then - echo "❌ VERIFICATION FAILED" - [ $BUILD_EXIT -ne 0 ] && echo " - Build failed" - [ $TYPE_EXIT -ne 0 ] && echo " - Type check failed" - [ $LINT_EXIT -ne 0 ] && echo " - Lint failed" - exit 1 -fi +echo "Exit code: $?" ``` -**BLOCK IF**: Any exit code != 0 → Error with output +**BLOCK IF**: Exit code != 0 → Error with build output ```bash # Verify all task files have corresponding implementation files @@ -1086,428 +1054,199 @@ done ``` **BLOCK IF**: Any file MISSING → List missing files, halt workflow -#### Step 4.5: Display Implementation Summary [MANDATORY] +#### Step 4.6: Validate API Contract Compliance [MANDATORY] +```bash +# Run contract validation +python3 skills/guardrail-orchestrator/scripts/validate_against_contract.py \ + .workflow/versions/$VERSION_ID/contracts/api_contract.yml \ + --project-dir . +CONTRACT_EXIT=$? +echo "CONTRACT_EXIT=$CONTRACT_EXIT" +``` + +**Validates:** +- All backend routes exist and match contract methods +- All frontend components import from shared types +- All API calls use correct paths and methods + +**BLOCK IF**: CONTRACT_EXIT != 0 → Display violations, halt workflow + +#### Step 4.7: Display Implementation Summary [MANDATORY] ``` ╔══════════════════════════════════════════════════════════════╗ -║ ✅ ALL LAYERS IMPLEMENTED ║ +║ ✅ PARALLEL IMPLEMENTATION COMPLETE ║ ╠══════════════════════════════════════════════════════════════╣ -║ Layer 1: X tasks (models) ✓ ║ -║ Layer 2: X tasks (APIs) ✓ ║ -║ Layer 3: X tasks (UI) ✓ ║ +║ Backend Team: ║ +║ Models: X implemented ║ +║ APIs: X implemented ║ +║ ║ +║ Frontend Team: ║ +║ Components: X implemented ║ +║ Pages: X implemented ║ ╠══════════════════════════════════════════════════════════════╣ -║ Total files created: X ║ -║ Build: PASS ║ +║ API Contract: VALID ✓ ║ +║ Shared Types: app/types/api.ts ✓ ║ +║ Build: PASS ✓ ║ ╚══════════════════════════════════════════════════════════════╝ ``` -#### Step 4.6: Complete Phase & Transition [MANDATORY] +#### Step 4.8: Transition [MANDATORY] ```bash -# Save checkpoints for each completed layer -for layer in $(seq 1 $TOTAL_LAYERS); do - python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint layer_${layer}_complete \ - --phase IMPLEMENTING --status passed -done - -# Save build checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint build_passes \ - --phase IMPLEMENTING --status passed \ - --data "{\"exit_code\": 0}" - -# Save type-check checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint type_check_passes \ - --phase IMPLEMENTING --status passed \ - --data "{\"exit_code\": 0}" - -# Save lint checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint lint_passes \ - --phase IMPLEMENTING --status passed \ - --data "{\"exit_code\": 0}" - -# Complete the phase -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete IMPLEMENTING -COMPLETE_EXIT=$? -if [ $COMPLETE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot complete IMPLEMENTING phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Transition to next phase python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition REVIEWING ``` --- ### ═══════════════════════════════════════════════════════════════ -### PHASE 5: REVIEWING (With Fix Loop Enforcement) +### PHASE 5: REVIEWING ### ═══════════════════════════════════════════════════════════════ -**Entry Condition**: Phase = REVIEWING (verified via gate check) -**Exit Condition**: All checks pass, review_passed checkpoint set -**Fix Loop**: If issues found → Return to IMPLEMENTING → Fix → Re-run review +**Entry Condition**: Phase = REVIEWING (verified) +**Exit Condition**: All checks pass, ready for approval -#### Step 5.0: Gate Entry Check [MANDATORY - BLOCKING] +#### Step 5.1: Verify Phase State [MANDATORY] ```bash -# MUST pass before proceeding - HALT if fails -python3 skills/guardrail-orchestrator/scripts/phase_gate.py can-enter REVIEWING -GATE_EXIT=$? -if [ $GATE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot enter REVIEWING phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Enter the phase (records entry timestamp) -python3 skills/guardrail-orchestrator/scripts/phase_gate.py enter REVIEWING +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py status ``` +**BLOCK IF**: Phase is not REVIEWING -#### Step 5.1: Run Build, Type-Check, and Lint Validation [MANDATORY] +#### IF AUTO_MODE = true: Automated Review + +##### Step 5.2a: Run Build Validation [MANDATORY] ```bash -# Run build npm run build 2>&1 BUILD_EXIT=$? - -# Run type check -npx tsc --noEmit 2>&1 -TYPE_EXIT=$? - -# Run lint -npm run lint 2>&1 -LINT_EXIT=$? - -# Save checkpoints -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint build_verified \ - --phase REVIEWING \ - --status $([ $BUILD_EXIT -eq 0 ] && echo "passed" || echo "failed") \ - --data "{\"exit_code\": $BUILD_EXIT}" - -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint type_check_verified \ - --phase REVIEWING \ - --status $([ $TYPE_EXIT -eq 0 ] && echo "passed" || echo "failed") \ - --data "{\"exit_code\": $TYPE_EXIT}" - -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint lint_verified \ - --phase REVIEWING \ - --status $([ $LINT_EXIT -eq 0 ] && echo "passed" || echo "failed") \ - --data "{\"exit_code\": $LINT_EXIT}" -``` -**BLOCK IF**: Any exit != 0 → Trigger fix loop - -#### Step 5.2: Spawn Code Review Agent [MANDATORY] -**Run comprehensive code review on all implemented files** - -``` -Use Task tool with: - subagent_type: "code-reviewer" - prompt: | - # CODE REVIEW AGENT - Quality Enhancement - # VERSION $VERSION_ID - - ## YOUR MISSION - Review ALL implemented files for this workflow version and identify issues - that need fixing before the workflow can proceed. - - ## REVIEW SCOPE - Task files: .workflow/versions/$VERSION_ID/tasks/*.yml - - For EACH task file: - 1. Read the task to find implementation file paths - 2. Review each implemented file - - ## REVIEW CRITERIA - - ### 1. Code Quality (CRITICAL) - - [ ] DRY violations - duplicated code that should be abstracted - - [ ] SOLID principle violations - - [ ] Dead code or unused imports - - [ ] Overly complex functions (cyclomatic complexity) - - [ ] Missing error handling - - ### 2. TypeScript Best Practices (CRITICAL) - - [ ] Any use of `any` type (should be properly typed) - - [ ] Missing type annotations on function parameters/returns - - [ ] Incorrect type assertions - - [ ] Unused type imports - - ### 3. Security Issues (CRITICAL - BLOCKING) - - [ ] Hardcoded secrets or API keys - - [ ] SQL injection vulnerabilities - - [ ] XSS vulnerabilities (unescaped user input) - - [ ] Insecure data handling - - [ ] Missing input validation - - ### 4. Performance Concerns (WARNING) - - [ ] N+1 query patterns - - [ ] Missing memoization in React components - - [ ] Unnecessary re-renders - - [ ] Large bundle imports that could be lazy-loaded - - [ ] Missing pagination for lists - - ### 5. Framework Best Practices (WARNING) - - [ ] React hooks rules violations - - [ ] Missing cleanup in useEffect - - [ ] Prop drilling that should use context - - [ ] Missing loading/error states - - [ ] Accessibility issues (missing aria labels, alt text) - - ### 6. Code Style & Maintainability (INFO) - - [ ] Inconsistent naming conventions - - [ ] Missing or outdated comments for complex logic - - [ ] Overly long files that should be split - - [ ] Magic numbers/strings that should be constants - - ## REVIEW PROCESS - - 1. **List all files to review** - ```bash - for task in .workflow/versions/$VERSION_ID/tasks/*.yml; do - grep -A 10 "to_create:" "$task" | grep -E "^\s+-" | sed 's/.*- //' - done - ``` - - 2. **For each file, run review** - - Read the file - - Check against ALL criteria above - - Note line numbers for issues - - 3. **Categorize findings by severity** - - CRITICAL: Must fix before proceeding (security, type errors) - - WARNING: Should fix, may cause problems - - INFO: Suggestions for improvement - - ## OUTPUT FORMAT - - ```yaml - # .workflow/versions/$VERSION_ID/review/code_review_report.yml - version: $VERSION_ID - reviewed_at: - reviewer: code-review-agent - - summary: - files_reviewed: X - critical_issues: X - warnings: X - info: X - verdict: PASS | NEEDS_FIX | BLOCKED - - files: - - path: "src/app/api/xxx/route.ts" - issues: - - severity: CRITICAL - line: 45 - category: security - message: "Hardcoded API key found" - suggestion: "Use environment variable" - auto_fixable: true - - severity: WARNING - line: 23 - category: performance - message: "Missing error boundary" - suggestion: "Wrap component in ErrorBoundary" - auto_fixable: false - - auto_fix_commands: - - file: "src/app/api/xxx/route.ts" - line: 45 - action: "Replace hardcoded key with process.env.API_KEY" - ``` - - ## FINAL OUTPUT - - After creating the report file, output a summary: - - ``` - === CODE REVIEW COMPLETE === - Files reviewed: X - Critical issues: X (must fix) - Warnings: X (should fix) - Info: X (suggestions) - - VERDICT: [PASS/NEEDS_FIX/BLOCKED] - - [If NEEDS_FIX or BLOCKED, list top 5 critical issues] - ``` ``` -**Capture review results**: -```bash -REVIEW_REPORT=".workflow/versions/$VERSION_ID/review/code_review_report.yml" -if [ -f "$REVIEW_REPORT" ]; then - CRITICAL_ISSUES=$(grep "severity: CRITICAL" "$REVIEW_REPORT" | wc -l) - WARNINGS=$(grep "severity: WARNING" "$REVIEW_REPORT" | wc -l) +##### Step 5.2b: Generate Implementation Visualization [MANDATORY] +**MUST show user what was built before review:** - python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint code_review_run \ - --phase REVIEWING --status passed \ - --data "{\"critical\": $CRITICAL_ISSUES, \"warnings\": $WARNINGS}" -fi -``` - -**Auto-Fix Critical Issues** (if auto_fixable): -``` -IF CRITICAL_ISSUES > 0 AND AUTO_MODE = true: - Use Task tool with: - subagent_type: "refactoring-expert" - prompt: | - # AUTO-FIX AGENT - Critical Issues - - ## MISSION - Apply automatic fixes for CRITICAL issues found in code review. - - ## SOURCE - Review report: .workflow/versions/$VERSION_ID/review/code_review_report.yml - - ## INSTRUCTIONS - 1. Read the review report - 2. For each issue with auto_fixable: true: - - Apply the suggested fix - - Verify the fix doesn't break anything - 3. Run: npm run build && npx tsc --noEmit && npm run lint - 4. If all pass, report success - 5. If any fail, report which issues couldn't be auto-fixed - - ## OUTPUT - List of: - - Successfully auto-fixed issues - - Issues requiring manual intervention -``` - -#### Step 5.3: Generate Implementation Visualization [MANDATORY] ```bash python3 skills/guardrail-orchestrator/scripts/visualize_implementation.py --manifest project_manifest.json ``` -#### Step 5.4: Verify All Task Files Exist [MANDATORY] +This displays: +- 📱 Page structure with routes +- 🧩 Component hierarchy tree +- 🔌 API endpoints with methods +- 📊 Implementation statistics (lines, hooks, types) + +##### Step 5.3a: Check All Files Exist [MANDATORY] ```bash -python3 skills/guardrail-orchestrator/scripts/verify_implementation.py --version $VERSION_ID -VERIFY_EXIT=$? -ISSUES_FOUND=$(python3 skills/guardrail-orchestrator/scripts/verify_implementation.py --version $VERSION_ID --json | jq '.missing_files | length') - -# Save checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint all_files_verified \ - --phase REVIEWING \ - --status $([ $VERIFY_EXIT -eq 0 ] && echo "passed" || echo "failed") \ - --data "{\"missing_count\": $ISSUES_FOUND}" -``` - -#### Step 5.5: Review Decision with Fix Loop [MANDATORY] - -##### Collect All Issues -```bash -REVIEW_ISSUES=() - -if [ $BUILD_EXIT -ne 0 ]; then - REVIEW_ISSUES+=("Build failed with exit code $BUILD_EXIT") -fi - -if [ $TYPE_EXIT -ne 0 ]; then - REVIEW_ISSUES+=("Type check failed with exit code $TYPE_EXIT") -fi - -if [ $LINT_EXIT -ne 0 ]; then - REVIEW_ISSUES+=("Lint failed with exit code $LINT_EXIT") -fi - -if [ $ISSUES_FOUND -gt 0 ]; then - REVIEW_ISSUES+=("$ISSUES_FOUND implementation files missing") -fi - -# Check code review results -REVIEW_REPORT=".workflow/versions/$VERSION_ID/review/code_review_report.yml" -if [ -f "$REVIEW_REPORT" ]; then - CODE_CRITICAL=$(grep "severity: CRITICAL" "$REVIEW_REPORT" | wc -l | tr -d ' ') - if [ "$CODE_CRITICAL" -gt 0 ]; then - REVIEW_ISSUES+=("Code review found $CODE_CRITICAL CRITICAL issues") - fi -fi -``` - -##### IF Issues Found → TRIGGER FIX LOOP [CRITICAL] -```bash -if [ ${#REVIEW_ISSUES[@]} -gt 0 ]; then - echo "❌ REVIEW FAILED - FIX LOOP TRIGGERED" - echo "" - echo "╔══════════════════════════════════════════════════════════════╗" - echo "║ 🔧 FIX LOOP: Returning to IMPLEMENTING ║" - echo "╠══════════════════════════════════════════════════════════════╣" - echo "║ Issues that MUST be fixed: ║" - for issue in "${REVIEW_ISSUES[@]}"; do - echo "║ • $issue" +MISSING_COUNT=0 +for task in .workflow/versions/$VERSION_ID/tasks/*.yml; do + grep "file_paths:" -A 20 "$task" | grep -E "^\s+-\s+" | sed 's/.*- //' | while read path; do + if [ ! -f "$path" ]; then + echo "MISSING: $path" + MISSING_COUNT=$((MISSING_COUNT + 1)) + fi done - echo "╠══════════════════════════════════════════════════════════════╣" - echo "║ 👉 NEXT STEPS: ║" - echo "║ 1. Fix the issues listed above ║" - echo "║ 2. Run: npm run build (verify it passes) ║" - echo "║ 3. Run: npx tsc --noEmit (verify type check passes) ║" - echo "║ 4. Run: npm run lint (verify lint passes) ║" - echo "║ 5. Fix any CRITICAL code review issues ║" - echo "║ 6. Run: /workflow:resume ║" - echo "║ ║" - echo "║ The workflow will automatically re-run REVIEWING after fix ║" - echo "╚══════════════════════════════════════════════════════════════╝" - - # Show code review details if issues exist - REVIEW_REPORT=".workflow/versions/$VERSION_ID/review/code_review_report.yml" - if [ -f "$REVIEW_REPORT" ]; then - echo "" - echo "📋 CODE REVIEW ISSUES:" - echo "────────────────────────────────────────────────────────────────" - grep -A 5 "severity: CRITICAL" "$REVIEW_REPORT" | head -30 - echo "" - echo "Full report: $REVIEW_REPORT" - echo "────────────────────────────────────────────────────────────────" - fi - - # Trigger fix loop - returns to IMPLEMENTING - python3 skills/guardrail-orchestrator/scripts/phase_gate.py fix-loop REVIEWING \ - --issues "${REVIEW_ISSUES[@]}" - - # Transition back to IMPLEMENTING - python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPLEMENTING - - # HALT - Must fix before continuing - exit 1 -fi +done +echo "Missing files: $MISSING_COUNT" ``` -##### IF No Issues → PASS Review -```bash -# All checks passed - save code_review_passed checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint code_review_passed \ - --phase REVIEWING \ - --status passed \ - --data "{\"critical_issues\": 0}" - -# Save review_passed checkpoint (umbrella checkpoint) -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint review_passed \ - --phase REVIEWING \ - --status passed \ - --data "{\"build\": \"passed\", \"type_check\": \"passed\", \"lint\": \"passed\", \"files\": \"verified\", \"code_review\": \"passed\"}" - -# Mark phase complete -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete REVIEWING +##### Step 5.4a: Auto-Decision [MANDATORY] +``` +IF BUILD_EXIT = 0 AND MISSING_COUNT = 0: + → Display: "✅ AUTO-REVIEW PASSED" + → Run: python3 .../workflow_manager.py transition SECURITY_REVIEW + → Proceed to Phase 5.5 (Security Review) +ELSE: + → Display: "❌ AUTO-REVIEW FAILED" + → List all failures (build errors, missing files) + → HALT workflow + → Output: "Fix issues and run /workflow:resume" ``` -#### Step 5.6: Display Review Report [MANDATORY] +##### Step 5.5a: Auto Review Report [MANDATORY] ``` ╔══════════════════════════════════════════════════════════════╗ -║ 🔍 REVIEW RESULTS ║ +║ 🔍 AUTO REVIEW RESULTS ║ ╠══════════════════════════════════════════════════════════════╣ -║ Build: ✅ PASS ║ -║ Type Check: ✅ PASS ║ -║ Lint: ✅ PASS ║ -║ Files: ✅ All exist ║ -║ Code Review: ✅ No CRITICAL issues ║ +║ Build: PASS / FAIL ║ +║ Files: X/Y exist ║ +║ Decision: AUTO-APPROVED / AUTO-REJECTED ║ ╠══════════════════════════════════════════════════════════════╣ ║ IMPLEMENTATION SUMMARY ║ ║ Pages: X implemented ║ ║ Components: X implemented ║ ║ API Endpoints: X implemented ║ +║ Total Lines: X ║ ╠══════════════════════════════════════════════════════════════╣ -║ ✅ Proceeding to SECURITY_REVIEW... ║ +║ 👆 See visualization above for details ║ +╠══════════════════════════════════════════════════════════════╣ +║ [If failed, list specific failures here] ║ ╚══════════════════════════════════════════════════════════════╝ ``` -#### Step 5.7: Transition to Security Review [MANDATORY] +--- + +#### IF AUTO_MODE = false: Agent Review + +##### Step 5.2b: Generate Implementation Visualization [MANDATORY] +**MUST show user what was built before spawning reviewer:** + +```bash +python3 skills/guardrail-orchestrator/scripts/visualize_implementation.py --manifest project_manifest.json +``` + +This shows the user: +- Pages with their routes and components +- Component hierarchy and relationships +- API endpoints with HTTP methods +- Code statistics and metrics + +##### Step 5.3b: Spawn Reviewer Agent [MANDATORY] +``` +Use Task tool with: + subagent_type: "quality-engineer" + prompt: | + # REVIEWER AGENT - VERSION $VERSION_ID + + ## STRICT REQUIREMENTS + Review ALL tasks. Report ALL issues. No partial reviews. + + ## REVIEW CHECKLIST (FOR EACH TASK) + + Task files: .workflow/versions/$VERSION_ID/tasks/*.yml + + For each task: + 1. [ ] Read task file + 2. [ ] Verify ALL file_paths exist + 3. [ ] Read each file, verify: + - [ ] Exports match manifest + - [ ] Types are correct + - [ ] No TypeScript errors + - [ ] Follows project patterns + 4. [ ] Check acceptance_criteria met + + ## VALIDATION (RUN THESE) + ```bash + npm run build + npm run lint 2>/dev/null || echo "No lint configured" + ``` + + ## OUTPUT FORMAT (REQUIRED) + ``` + === REVIEW REPORT === + + TASK: task_create_xxx + - Files: EXIST / MISSING (list) + - Build: PASS / FAIL + - Quality: PASS / ISSUES (list) + - Verdict: APPROVED / NEEDS_CHANGES + + [Repeat for each task] + + SUMMARY + - Total tasks: X + - Approved: X + - Need changes: X + - Overall: PASS / FAIL + ``` +``` + +##### Step 5.3b: Transition to Security Review [MANDATORY] ```bash python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition SECURITY_REVIEW ``` @@ -1515,203 +1254,125 @@ python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition SEC --- ### ═══════════════════════════════════════════════════════════════ -### PHASE 5.5: SECURITY REVIEW (With Fix Loop Enforcement) +### PHASE 5.5: SECURITY REVIEW ### ═══════════════════════════════════════════════════════════════ -**Entry Condition**: Phase = SECURITY_REVIEW (verified via gate check) -**Exit Condition**: Security scan passes, security_passed checkpoint set -**Fix Loop**: If CRITICAL/HIGH issues found → Return to IMPLEMENTING → Fix → Re-run security +**Entry Condition**: Phase = SECURITY_REVIEW (verified) +**Exit Condition**: Security scan passes (no CRITICAL issues), ready for approval -#### Step 5.5.0: Gate Entry Check [MANDATORY - BLOCKING] +#### Step 5.5.1: Verify Phase State [MANDATORY] ```bash -# MUST pass before proceeding - HALT if fails -python3 skills/guardrail-orchestrator/scripts/phase_gate.py can-enter SECURITY_REVIEW -GATE_EXIT=$? -if [ $GATE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot enter SECURITY_REVIEW phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Enter the phase (records entry timestamp) -python3 skills/guardrail-orchestrator/scripts/phase_gate.py enter SECURITY_REVIEW +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py status ``` +**BLOCK IF**: Phase is not SECURITY_REVIEW -#### Step 5.5.1: Run Security Scanner [MANDATORY] +#### Step 5.5.2: Run Security Scanner [MANDATORY] ```bash python3 skills/guardrail-orchestrator/scripts/security_scan.py \ --project-dir . \ --severity HIGH SECURITY_EXIT=$? - -# Capture security report for fix loop -SECURITY_REPORT=$(python3 skills/guardrail-orchestrator/scripts/security_scan.py \ - --project-dir . --severity HIGH --json 2>/dev/null || echo '{}') -CRITICAL_COUNT=$(echo "$SECURITY_REPORT" | jq '.by_severity.CRITICAL // 0') -HIGH_COUNT=$(echo "$SECURITY_REPORT" | jq '.by_severity.HIGH // 0') - -# Save checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint security_scan_run \ - --phase SECURITY_REVIEW \ - --status $([ $SECURITY_EXIT -le 1 ] && echo "passed" || echo "failed") \ - --data "{\"exit_code\": $SECURITY_EXIT, \"critical\": $CRITICAL_COUNT, \"high\": $HIGH_COUNT}" +echo "SECURITY_EXIT=$SECURITY_EXIT" ``` **Exit codes:** - 0 = PASS (no critical/high issues) -- 1 = HIGH issues found (triggers fix loop in strict mode) -- 2 = CRITICAL issues found (ALWAYS triggers fix loop) +- 1 = HIGH issues found (warning in normal mode, blocks in --strict) +- 2 = CRITICAL issues found (always blocks) -#### Step 5.5.2: API Contract Validation [MANDATORY] +#### Step 5.5.3: API Contract Validation [MANDATORY] ```bash +# Validate against generated contract (types and routes) +python3 skills/guardrail-orchestrator/scripts/validate_against_contract.py \ + .workflow/versions/$VERSION_ID/contracts/api_contract.yml \ + --project-dir . +CONTRACT_EXIT=$? +echo "CONTRACT_EXIT=$CONTRACT_EXIT" + +# Also run static API analysis python3 skills/guardrail-orchestrator/scripts/validate_api_contract.py \ --project-dir . API_EXIT=$? - -# Save checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint api_contract_validated \ - --phase SECURITY_REVIEW \ - --status $([ $API_EXIT -eq 0 ] && echo "passed" || echo "failed") \ - --data "{\"exit_code\": $API_EXIT}" +echo "API_EXIT=$API_EXIT" ``` -#### Step 5.5.3: Security Decision with Fix Loop [MANDATORY - CRITICAL] +**Contract Validation Checks:** +- Backend routes exist and export correct HTTP methods +- Frontend files import from shared types (@/types/api) +- Types not recreated locally (must use shared types) -##### Collect All Security Issues -```bash -SECURITY_ISSUES=() +**Static API Validation Checks:** +- Frontend API calls have matching backend endpoints +- HTTP methods match (GET, POST, PUT, DELETE) +- Request bodies are sent where expected -if [ $SECURITY_EXIT -eq 2 ]; then - SECURITY_ISSUES+=("CRITICAL: $CRITICAL_COUNT critical security vulnerabilities found") -fi +#### Step 5.5.4: Security Decision [MANDATORY] -if [ $SECURITY_EXIT -eq 1 ]; then - SECURITY_ISSUES+=("HIGH: $HIGH_COUNT high severity security issues found") -fi - -if [ $API_EXIT -ne 0 ]; then - SECURITY_ISSUES+=("API Contract: Frontend-backend API mismatch detected") -fi +##### IF AUTO_MODE = true: +``` +IF SECURITY_EXIT = 0 AND API_EXIT = 0 AND CONTRACT_EXIT = 0: + → Display: "✅ SECURITY & CONTRACT VALIDATION PASSED" + → Transition to AWAITING_IMPL_APPROVAL +ELSE IF SECURITY_EXIT = 2: + → Display: "❌ CRITICAL SECURITY ISSUES - BLOCKING" + → List all critical issues + → Transition to IMPLEMENTING (must fix) + → HALT workflow +ELSE IF CONTRACT_EXIT = 2: + → Display: "❌ CONTRACT VIOLATIONS - BLOCKING" + → List all contract violations + → Transition to IMPLEMENTING (must fix) + → HALT workflow +ELSE IF SECURITY_EXIT = 1 OR CONTRACT_EXIT = 1: + → Display: "⚠️ HIGH SEVERITY ISSUES - WARNING" + → List high severity issues and contract warnings + → Continue to approval (warning only in auto mode) ``` -##### IF CRITICAL Issues Found → TRIGGER FIX LOOP [MANDATORY] -```bash -if [ $SECURITY_EXIT -eq 2 ]; then - echo "❌ CRITICAL SECURITY ISSUES - FIX LOOP TRIGGERED" - echo "" - echo "╔══════════════════════════════════════════════════════════════╗" - echo "║ 🚨 SECURITY FIX REQUIRED - CRITICAL ISSUES ║" - echo "╠══════════════════════════════════════════════════════════════╣" - echo "║ CRITICAL issues MUST be fixed before workflow can continue ║" - echo "╠══════════════════════════════════════════════════════════════╣" - echo "║ Issues found: ║" - for issue in "${SECURITY_ISSUES[@]}"; do - echo "║ • $issue" - done - echo "╠══════════════════════════════════════════════════════════════╣" - echo "║ 👉 REQUIRED ACTIONS: ║" - echo "║ 1. Review security report above ║" - echo "║ 2. Fix ALL critical vulnerabilities ║" - echo "║ 3. Run: /workflow:resume ║" - echo "║ ║" - echo "║ Common fixes: ║" - echo "║ - Remove hardcoded secrets → use env vars ║" - echo "║ - Fix SQL injection → use parameterized queries ║" - echo "║ - Fix XSS → sanitize user input ║" - echo "╚══════════════════════════════════════════════════════════════╝" - - # Trigger fix loop - returns to IMPLEMENTING - python3 skills/guardrail-orchestrator/scripts/phase_gate.py fix-loop SECURITY_REVIEW \ - --issues "${SECURITY_ISSUES[@]}" - - # Transition back to IMPLEMENTING - python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPLEMENTING - - # HALT - Must fix before continuing - exit 1 -fi +##### IF AUTO_MODE = false: +Use AskUserQuestion: +``` +Question: "Security scan found issues. How do you want to proceed?" +Options: + 1. "Accept risks - Continue to approval" (if no CRITICAL) + 2. "Fix issues - Return to implementation" + 3. "Run full audit - /workflow:security --full" ``` -##### IF HIGH Issues Found (--auto mode) → WARNING but allow continue -```bash -if [ $SECURITY_EXIT -eq 1 ]; then - echo "⚠️ HIGH SEVERITY SECURITY ISSUES FOUND" - echo "" - echo "╔══════════════════════════════════════════════════════════════╗" - echo "║ ⚠️ SECURITY WARNING - HIGH SEVERITY ISSUES ║" - echo "╠══════════════════════════════════════════════════════════════╣" - echo "║ $HIGH_COUNT high severity issues detected ║" - echo "║ ║" - echo "║ In AUTO mode: Proceeding with warning ║" - echo "║ Recommendation: Fix these issues before production deploy ║" - echo "╚══════════════════════════════════════════════════════════════╝" - - # Log warning but continue in auto mode - # In manual mode, this would ask the user -fi -``` - -##### IF API Contract Failed → TRIGGER FIX LOOP [MANDATORY] -```bash -if [ $API_EXIT -ne 0 ]; then - echo "❌ API CONTRACT VALIDATION FAILED - FIX LOOP TRIGGERED" - echo "" - echo "╔══════════════════════════════════════════════════════════════╗" - echo "║ 🔌 API CONTRACT MISMATCH ║" - echo "╠══════════════════════════════════════════════════════════════╣" - echo "║ Frontend API calls don't match backend endpoints ║" - echo "╠══════════════════════════════════════════════════════════════╣" - echo "║ 👉 REQUIRED ACTIONS: ║" - echo "║ 1. Check that all frontend fetch/axios calls exist ║" - echo "║ 2. Verify HTTP methods match (GET/POST/PUT/DELETE) ║" - echo "║ 3. Ensure request bodies are correct ║" - echo "║ 4. Run: /workflow:resume ║" - echo "╚══════════════════════════════════════════════════════════════╝" - - # Trigger fix loop - python3 skills/guardrail-orchestrator/scripts/phase_gate.py fix-loop SECURITY_REVIEW \ - --issues "API contract validation failed" - - python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPLEMENTING - exit 1 -fi -``` - -##### IF All Passed → Complete Security Review -```bash -# All checks passed - save security_passed checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint security_passed \ - --phase SECURITY_REVIEW \ - --status passed \ - --data "{\"security\": \"passed\", \"api_contract\": \"passed\"}" - -# Mark phase complete -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete SECURITY_REVIEW -``` - -#### Step 5.5.4: Display Security Report [MANDATORY] +#### Step 5.5.5: Display Security Report [MANDATORY] ``` ╔══════════════════════════════════════════════════════════════╗ -║ 🔒 SECURITY REVIEW RESULTS ║ +║ 🔒 SECURITY & CONTRACT REVIEW RESULTS ║ ╠══════════════════════════════════════════════════════════════╣ -║ Security Scan: ✅ PASS / ⚠️ WARNING / ❌ CRITICAL ║ +║ Security Scan: PASS / WARNING / CRITICAL ║ ║ Critical: X issues ║ ║ High: X issues ║ ║ Medium: X issues ║ ║ Low: X issues ║ ╠══════════════════════════════════════════════════════════════╣ -║ API Contract: ✅ PASS / ❌ FAIL ║ -║ Matched calls: X ║ -║ Unmatched: X ║ -║ Method errors: X ║ +║ Contract Compliance: PASS / WARNING / FAIL ║ +║ Backend routes: X of X verified ║ +║ Type imports: X files checked ║ +║ Violations: X ║ ╠══════════════════════════════════════════════════════════════╣ -║ VERDICT: ✅ APPROVED / 🔧 NEEDS_FIXES ║ +║ API Static Analysis: PASS / FAIL ║ +║ Matched calls: X ║ +║ Unmatched: X ║ +║ Method errors: X ║ +╠══════════════════════════════════════════════════════════════╣ +║ [If issues found, list top 5 with file locations] ║ +╠══════════════════════════════════════════════════════════════╣ +║ VERDICT: APPROVED / NEEDS_FIXES ║ ╚══════════════════════════════════════════════════════════════╝ ``` -#### Step 5.5.5: Transition to Approval [MANDATORY] +#### Step 5.5.6: Transition [MANDATORY] ```bash +# If security passed python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition AWAITING_IMPL_APPROVAL + +# If security failed (CRITICAL issues) +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition IMPLEMENTING ``` --- @@ -1720,38 +1381,10 @@ python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition AWA ### PHASE 6: GATE 2 - Implementation Approval ### ═══════════════════════════════════════════════════════════════ -**Entry Condition**: Phase = AWAITING_IMPL_APPROVAL (verified via gate check) +**Entry Condition**: Phase = AWAITING_IMPL_APPROVAL **Exit Condition**: Implementation approved, phase = COMPLETING -#### Step 6.0: Gate Entry Check [MANDATORY - BLOCKING] -```bash -# MUST pass before proceeding - HALT if fails -python3 skills/guardrail-orchestrator/scripts/phase_gate.py can-enter AWAITING_IMPL_APPROVAL -GATE_EXIT=$? -if [ $GATE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot enter AWAITING_IMPL_APPROVAL phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Enter the phase (records entry timestamp) -python3 skills/guardrail-orchestrator/scripts/phase_gate.py enter AWAITING_IMPL_APPROVAL -``` - -#### IF AUTO_MODE = true: -```bash -# Auto-approve since review and security checks passed -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint implementation_approved \ - --phase AWAITING_IMPL_APPROVAL --status passed \ - --data "{\"approver\": \"auto\", \"mode\": \"auto\"}" - -# Complete the phase -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete AWAITING_IMPL_APPROVAL - -python3 skills/guardrail-orchestrator/scripts/workflow_manager.py approve implementation -python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition COMPLETING -``` -Output: "✅ Implementation auto-approved. Proceeding to completion." +**Note**: In AUTO_MODE, this gate is handled in Phase 5 #### IF AUTO_MODE = false: Use AskUserQuestion: @@ -1765,14 +1398,6 @@ Options: **On Approve**: ```bash -# Save approval checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint implementation_approved \ - --phase AWAITING_IMPL_APPROVAL --status passed \ - --data "{\"approver\": \"user\", \"mode\": \"manual\"}" - -# Complete the phase -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete AWAITING_IMPL_APPROVAL - python3 skills/guardrail-orchestrator/scripts/workflow_manager.py approve implementation python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition COMPLETING ``` @@ -1786,24 +1411,9 @@ python3 skills/guardrail-orchestrator/scripts/workflow_manager.py transition COM ### PHASE 7: COMPLETING ### ═══════════════════════════════════════════════════════════════ -**Entry Condition**: Phase = COMPLETING (verified via gate check) +**Entry Condition**: Phase = COMPLETING (verified) **Exit Condition**: Version marked complete, success report displayed -#### Step 7.0: Gate Entry Check [MANDATORY - BLOCKING] -```bash -# MUST pass before proceeding - HALT if fails -python3 skills/guardrail-orchestrator/scripts/phase_gate.py can-enter COMPLETING -GATE_EXIT=$? -if [ $GATE_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Cannot enter COMPLETING phase" - python3 skills/guardrail-orchestrator/scripts/phase_gate.py blockers - exit 1 -fi - -# Enter the phase (records entry timestamp) -python3 skills/guardrail-orchestrator/scripts/phase_gate.py enter COMPLETING -``` - #### Step 7.1: Verify Phase State [MANDATORY] ```bash python3 skills/guardrail-orchestrator/scripts/workflow_manager.py status @@ -1822,33 +1432,9 @@ done #### Step 7.3: Update Manifest Statuses [MANDATORY] Update all entities referenced in tasks from "PENDING" to "IMPLEMENTED" -#### Step 7.4: Complete Version & Phase [MANDATORY] +#### Step 7.4: Complete Version [MANDATORY] ```bash -# Save checkpoints -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint tasks_marked_complete \ - --phase COMPLETING --status passed -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint manifest_updated \ - --phase COMPLETING --status passed - -# Complete the version python3 skills/guardrail-orchestrator/scripts/version_manager.py complete -VERSION_EXIT=$? - -# Save version completion checkpoint -python3 skills/guardrail-orchestrator/scripts/phase_gate.py checkpoint version_finalized \ - --phase COMPLETING --status $([ $VERSION_EXIT -eq 0 ] && echo "passed" || echo "failed") - -if [ $VERSION_EXIT -ne 0 ]; then - echo "❌ BLOCKED: Version finalization failed" - exit 1 -fi - -# Complete the COMPLETING phase -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete COMPLETING - -# Transition to final COMPLETED state -python3 skills/guardrail-orchestrator/scripts/phase_gate.py enter COMPLETED -python3 skills/guardrail-orchestrator/scripts/phase_gate.py complete COMPLETED ``` **VERIFY**: Script exits with code 0 diff --git a/.claude/commands/workflow/status.md b/.claude/commands/workflow/status.md new file mode 100644 index 0000000..d3723fa --- /dev/null +++ b/.claude/commands/workflow/status.md @@ -0,0 +1,119 @@ +--- +description: Show workflow status and task summary +allowed-tools: Read, Bash +--- + +# Workflow Status + +Display current workflow status and task breakdown. + +## Steps + +### 1. Check Active Workflow +```bash +python3 skills/guardrail-orchestrator/scripts/workflow_manager.py status +``` + +If active workflow exists, display workflow state. +If no workflow, continue with manual task scan. + +### 2. Read Project Manifest +Check `project_manifest.json` for: +- Current phase +- Entity counts by status + +### 3. Scan Tasks +Get the version-specific tasks directory: +```bash +TASKS_DIR=$(python3 skills/guardrail-orchestrator/scripts/version_manager.py tasks-dir) +``` + +Read all `$TASKS_DIR/*.yml` files and count by: +- Status (pending, in_progress, review, approved, completed, blocked) +- Agent (frontend, backend, reviewer) +- Type (create, update, delete, review) + +### 4. Display Summary + +``` +╔══════════════════════════════════════════════════════════════╗ +║ WORKFLOW STATUS ║ +╠══════════════════════════════════════════════════════════════╣ +║ Active Workflow: | None ║ +║ Feature: ║ +║ Phase: ║ +╠══════════════════════════════════════════════════════════════╣ +║ APPROVAL GATES ║ +║ 🛑 Design: ║ +║ 🛑 Implementation: ║ +╠══════════════════════════════════════════════════════════════╣ +║ TASKS BY STATUS ║ +║ ⏳ Pending: X ║ +║ 🔄 In Progress: X ║ +║ 🔍 Review: X ║ +║ ✅ Approved: X ║ +║ ✓ Completed: X ║ +║ 🚫 Blocked: X ║ +╠══════════════════════════════════════════════════════════════╣ +║ TASKS BY AGENT ║ +║ 🎨 Frontend: X pending, X completed ║ +║ ⚙️ Backend: X pending, X completed ║ +║ 🔍 Reviewer: X pending ║ +╠══════════════════════════════════════════════════════════════╣ +║ NEXT ACTIONS ║ +║ /workflow:frontend --next (X tasks available) ║ +║ /workflow:backend --next (X tasks available) ║ +║ /workflow:review --next (X tasks to review) ║ +║ /workflow:resume (continue workflow) ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +### 5. Show Design Visualization +**If in DESIGNING or AWAITING_DESIGN_APPROVAL phase**, display visual design: + +```bash +python3 skills/guardrail-orchestrator/scripts/visualize_design.py --manifest project_manifest.json +``` + +This shows: +- 📱 Page flow diagram +- 📄 Page details with components +- 🧩 Component hierarchy +- 🔌 API endpoints +- 🔄 Data flow architecture + +### 5b. Show Implementation Visualization +**If in REVIEWING, SECURITY_REVIEW, or AWAITING_IMPL_APPROVAL phase**, display what was built: + +```bash +python3 skills/guardrail-orchestrator/scripts/visualize_implementation.py --manifest project_manifest.json +``` + +This shows: +- 📱 Page structure with routes +- 🧩 Component hierarchy and relationships +- 🔌 API endpoints with HTTP methods +- 📊 Implementation statistics (lines, hooks, types) +- 🌳 Component tree view + +### 6. List Pending Tasks +Show table of tasks ready to work on: + +| Task ID | Type | Agent | Priority | Dependencies | +|---------|------|-------|----------|--------------| + +### 7. Show Approval Instructions + +**If AWAITING_DESIGN_APPROVAL**: +``` +🛑 Design approval required. Review the entities and tasks, then: + - Approve: /workflow:approve design + - Reject: /workflow:reject design "reason" +``` + +**If AWAITING_IMPL_APPROVAL**: +``` +🛑 Implementation approval required. Review the code, then: + - Approve: /workflow:approve implementation + - Reject: /workflow:reject implementation "reason" +``` diff --git a/.claude/eureka-factory.yaml b/.claude/eureka-factory.yaml new file mode 100644 index 0000000..09ca138 --- /dev/null +++ b/.claude/eureka-factory.yaml @@ -0,0 +1,4 @@ +api_key: pk_user_f5a39cbc686d4f4e0f9e815c035b69a314fd938adecb84bd3800dc6934d8f550 +project_id: "" +repo_id: "" +app_id: cmjabwyo00028ms0t8ju6mtkw diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..8ab61e4 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,225 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_bash.py\" --command \"$TOOL_INPUT_COMMAND\"" + } + ] + }, + { + "matcher": "Task", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation task --input \"$TOOL_INPUT\"" + } + ] + }, + { + "matcher": "Write", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation write --file \"$TOOL_INPUT_FILE_PATH\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "Edit", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation edit --file \"$TOOL_INPUT_FILE_PATH\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "MultiEdit", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation edit --file \"$TOOL_INPUT_FILE_PATH\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "NotebookEdit", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation edit --file \"$TOOL_INPUT_NOTEBOOK_PATH\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_NOTEBOOK_PATH\"" + } + ] + }, + { + "matcher": "mcp__serena__create_text_file", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation write --input \"$TOOL_INPUT\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "mcp__serena__replace_content", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation edit --input \"$TOOL_INPUT\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "mcp__serena__replace_symbol_body", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation edit --input \"$TOOL_INPUT\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "mcp__morphllm-fast-apply__write_file", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation write --input \"$TOOL_INPUT\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "mcp__morphllm-fast-apply__tiny_edit_file", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation edit --input \"$TOOL_INPUT\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "mcp__filesystem__write_file", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation write --input \"$TOOL_INPUT\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_PATH\"" + } + ] + }, + { + "matcher": "mcp__filesystem__edit_file", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation edit --input \"$TOOL_INPUT\"" + }, + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_PATH\"" + } + ] + }, + { + "matcher": "mcp__filesystem__create_directory", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation write --input \"$TOOL_INPUT\"" + } + ] + }, + { + "matcher": "mcp__filesystem__move_file", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/validate_workflow.py\" --operation write --input \"$TOOL_INPUT\"" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Write", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/post_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "Edit", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/post_write.py\" --manifest \"$CLAUDE_PROJECT_DIR/project_manifest.json\" --file \"$TOOL_INPUT_FILE_PATH\"" + } + ] + }, + { + "matcher": "Task", + "hooks": [ + { + "type": "command", + "command": "echo '🔄 Agent task completed. Verify outputs before proceeding.'" + } + ] + } + ], + "Stop": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "python3 \"$CLAUDE_PROJECT_DIR/skills/guardrail-orchestrator/scripts/workflow_manager.py\" status 2>/dev/null || echo '🛡️ Session complete (no active workflow)'" + } + ] + } + ] + } +} diff --git a/.claude/skills/context-compaction/SKILL.md b/.claude/skills/context-compaction/SKILL.md new file mode 100644 index 0000000..9493e4a --- /dev/null +++ b/.claude/skills/context-compaction/SKILL.md @@ -0,0 +1,145 @@ +# Context Compaction Skill + +## Purpose +Manages context window by saving state before compaction and resuming after. +Ensures workflow continuity across context compressions. + +## Activation Triggers +- Manual: `/compact` command +- Manual: `/save-state` command +- Manual: `/resume` command +- Auto: When context usage exceeds 80% + +## Commands + +### /compact - Full Compaction Workflow +Saves state and prepares for context compaction: + +```bash +# 1. Save current workflow state +python3 skills/guardrail-orchestrator/scripts/context_compact.py save \ + --workflow-dir .workflow/versions/v001 \ + --checkpoint + +# 2. Display resume prompt for reference +python3 skills/guardrail-orchestrator/scripts/context_compact.py resume \ + --workflow-dir .workflow/versions/v001 +``` + +### /save-state - Quick State Save +Saves state without compaction: + +```bash +python3 skills/guardrail-orchestrator/scripts/context_compact.py save \ + --workflow-dir .workflow/versions/v001 +``` + +### /resume - Resume After Compaction +After context is compacted, inject resume context: + +```bash +python3 skills/guardrail-orchestrator/scripts/context_compact.py resume \ + --workflow-dir .workflow/versions/v001 +``` + +### /context-status - Check State +View current context state: + +```bash +python3 skills/guardrail-orchestrator/scripts/context_compact.py status \ + --workflow-dir .workflow/versions/v001 +``` + +## Auto-Detection Rules + +When context feels heavy (many tool calls, large files read), check: +1. Are we approaching context limit? +2. Is there unsaved progress? +3. Should we recommend compaction? + +### Warning Thresholds +- **70%**: Log warning, suggest saving state soon +- **80%**: Auto-save state, continue working +- **90%**: Strongly recommend compaction +- **95%**: Force state save immediately + +## State Files Generated + +After `/compact` or `/save-state`: + +``` +.workflow/versions/v001/ +├── context_state.json # Full serialized state +├── resume_prompt.md # Human-readable resume +└── modified_files.json # Recent file changes +``` + +## Resume Workflow + +After user runs `/compact` and context is cleared: + +1. **User starts new session** +2. **Run `/resume`** - Injects previous context +3. **Claude reads state** - Understands where to continue +4. **Continue work** - Pick up from next action + +## State Contents + +The context_state.json captures: + +```json +{ + "session_id": "compact_20250118_143022", + "workflow_position": { + "current_phase": "IMPLEMENTING", + "active_task_id": "task_create_api_login", + "layer": 2 + }, + "active_work": { + "entity_id": "api_auth_login", + "action": "implementing", + "file_path": "app/api/auth/login/route.ts", + "progress_notes": "Created route, need JWT generation" + }, + "next_actions": [ + {"action": "implement", "target": "JWT token generation", "priority": 1} + ], + "modified_files": [...], + "decisions": [...], + "blockers": [...] +} +``` + +## Integration with Workflow + +This skill integrates with: +- **workflow_state.yml** - Reads current phase and task status +- **tasks/*.yml** - Identifies pending and in-progress tasks +- **Git** - Tracks modified files, can create checkpoints + +## Best Practices + +1. **Save frequently** - Run `/save-state` after completing major steps +2. **Before risky operations** - Save state before large refactors +3. **End of session** - Always save state before ending work +4. **After compaction** - Always run `/resume` to restore context + +## Troubleshooting + +### No state found on resume +```bash +python3 skills/guardrail-orchestrator/scripts/context_compact.py status \ + --workflow-dir .workflow/versions/v001 +``` + +### Clear stale state +```bash +python3 skills/guardrail-orchestrator/scripts/context_compact.py clear \ + --workflow-dir .workflow/versions/v001 +``` + +### Manual state inspection +```bash +cat .workflow/versions/v001/context_state.json | jq . +cat .workflow/versions/v001/resume_prompt.md +``` diff --git a/.eureka-active-session b/.eureka-active-session new file mode 100644 index 0000000..a00735b --- /dev/null +++ b/.eureka-active-session @@ -0,0 +1,6 @@ +{ + "taskId": "workflow-v001-earn-app", + "title": "Implement Earn-to-Play App", + "startedAt": "2025-12-18T01:40:00Z", + "status": "in_progress" +} diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..be99453 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,16 @@ +{ + "mcpServers": { + "eureka-docs": { + "command": "npx", + "args": ["eureka-docs-server"], + "env": {} + }, + "eureka-imagen": { + "command": "npx", + "args": ["eureka-imagen-server"], + "env": { + "IMAGEROUTER_API_KEY": "${IMAGEROUTER_API_KEY}" + } + } + } +} diff --git a/.workflow/current.yml b/.workflow/current.yml new file mode 100644 index 0000000..3839720 --- /dev/null +++ b/.workflow/current.yml @@ -0,0 +1,4 @@ +{ + "active_version": "v001", + "session_id": "workflow_20251218_013734" +} \ No newline at end of file diff --git a/.workflow/index.yml b/.workflow/index.yml new file mode 100644 index 0000000..fe62a33 --- /dev/null +++ b/.workflow/index.yml @@ -0,0 +1,15 @@ +{ + "versions": [ + { + "version": "v001", + "feature": "a complete to earn app", + "status": "pending", + "started_at": "2025-12-18T01:37:34.999814", + "completed_at": null, + "tasks_count": 0, + "operations_count": 0 + } + ], + "latest_version": "v001", + "total_versions": 1 +} \ No newline at end of file diff --git a/.workflow/versions/v001/context_state.json b/.workflow/versions/v001/context_state.json new file mode 100644 index 0000000..ee568be --- /dev/null +++ b/.workflow/versions/v001/context_state.json @@ -0,0 +1,103 @@ +{ + "session_id": "compact_20251218_021138", + "captured_at": "2025-12-18T02:11:38.618766", + "context_usage": { + "tokens_used": 0, + "tokens_max": 0, + "percentage": 0.85, + "threshold_triggered": 0.8 + }, + "workflow_position": { + "workflow_id": "unknown", + "current_phase": "UNKNOWN", + "active_task_id": null, + "layer": 1 + }, + "active_work": { + "entity_id": "", + "entity_type": "", + "action": "pending", + "file_path": null, + "progress_notes": "" + }, + "next_actions": [], + "modified_files": [ + { + "path": "pp/page.tsx", + "action": "modified", + "summary": "" + }, + { + "path": ".claude/", + "action": "untracked", + "summary": "" + }, + { + "path": ".eureka-active-session", + "action": "untracked", + "summary": "" + }, + { + "path": ".mcp.json", + "action": "untracked", + "summary": "" + }, + { + "path": ".workflow/", + "action": "untracked", + "summary": "" + }, + { + "path": "CLAUDE.md", + "action": "untracked", + "summary": "" + }, + { + "path": "app/api/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/components/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/lib/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/login/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/register/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/tasks/", + "action": "untracked", + "summary": "" + }, + { + "path": "project_manifest.json", + "action": "untracked", + "summary": "" + }, + { + "path": "skills/", + "action": "untracked", + "summary": "" + }, + { + "path": "start-workflow.sh", + "action": "untracked", + "summary": "" + } + ], + "decisions": [], + "blockers": [] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_auth_login.yml b/.workflow/versions/v001/contexts/api_auth_login.yml new file mode 100644 index 0000000..a3b96fd --- /dev/null +++ b/.workflow/versions/v001/contexts/api_auth_login.yml @@ -0,0 +1,71 @@ +{ + "task_id": "task_create_api_auth_login", + "entity_id": "api_auth_login", + "generated_at": "2025-12-18T01:57:52.722550", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_auth_login", + "path": "/api/auth/login", + "method": "POST", + "description": "Login existing user", + "file_path": "app/api/auth/login/route.ts", + "status": "PENDING", + "request_body": { + "email": "string (required)", + "password": "string (required)" + }, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "user": { + "id": "string", + "email": "string", + "name": "string" + }, + "token": "string (JWT)" + } + }, + { + "status": 401, + "description": "Error", + "schema": { + "error": "Invalid credentials" + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/auth/login/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "POST /api/auth/login returns success response", + "verification": "curl -X POST /api/auth/login" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_auth_me.yml b/.workflow/versions/v001/contexts/api_auth_me.yml new file mode 100644 index 0000000..286df3b --- /dev/null +++ b/.workflow/versions/v001/contexts/api_auth_me.yml @@ -0,0 +1,66 @@ +{ + "task_id": "task_create_api_auth_me", + "entity_id": "api_auth_me", + "generated_at": "2025-12-18T01:57:52.722632", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_auth_me", + "path": "/api/auth/me", + "method": "GET", + "description": "Get current authenticated user", + "file_path": "app/api/auth/me/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "id": "string", + "email": "string", + "name": "string", + "created_at": "string (ISO date)" + } + }, + { + "status": 401, + "description": "Error", + "schema": { + "error": "Unauthorized" + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/auth/me/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "GET /api/auth/me returns success response", + "verification": "curl -X GET /api/auth/me" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_auth_register.yml b/.workflow/versions/v001/contexts/api_auth_register.yml new file mode 100644 index 0000000..2d07b57 --- /dev/null +++ b/.workflow/versions/v001/contexts/api_auth_register.yml @@ -0,0 +1,72 @@ +{ + "task_id": "task_create_api_auth_register", + "entity_id": "api_auth_register", + "generated_at": "2025-12-18T01:57:52.722465", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_auth_register", + "path": "/api/auth/register", + "method": "POST", + "description": "Create new user account", + "file_path": "app/api/auth/register/route.ts", + "status": "PENDING", + "request_body": { + "email": "string (required)", + "password": "string (required, min 8 chars)", + "name": "string (required)" + }, + "responses": [ + { + "status": 201, + "description": "Success", + "schema": { + "user": { + "id": "string", + "email": "string", + "name": "string" + }, + "token": "string (JWT)" + } + }, + { + "status": 400, + "description": "Error", + "schema": { + "error": "string (validation error or email exists)" + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/auth/register/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "POST /api/auth/register returns success response", + "verification": "curl -X POST /api/auth/register" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_leaderboard.yml b/.workflow/versions/v001/contexts/api_leaderboard.yml new file mode 100644 index 0000000..e508f11 --- /dev/null +++ b/.workflow/versions/v001/contexts/api_leaderboard.yml @@ -0,0 +1,67 @@ +{ + "task_id": "task_create_api_leaderboard", + "entity_id": "api_leaderboard", + "generated_at": "2025-12-18T01:57:52.723271", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_leaderboard", + "path": "/api/leaderboard", + "method": "GET", + "description": "Get top users by points", + "file_path": "app/api/leaderboard/route.ts", + "status": "PENDING", + "auth_required": true, + "query_params": { + "limit": "integer (optional, default 100)" + }, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "leaderboard": [ + { + "rank": "integer", + "user_id": "string", + "name": "string", + "total_points": "integer" + } + ], + "current_user_rank": "integer" + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/leaderboard/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "GET /api/leaderboard returns success response", + "verification": "curl -X GET /api/leaderboard" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_quizzes_get.yml b/.workflow/versions/v001/contexts/api_quizzes_get.yml new file mode 100644 index 0000000..82741eb --- /dev/null +++ b/.workflow/versions/v001/contexts/api_quizzes_get.yml @@ -0,0 +1,64 @@ +{ + "task_id": "task_create_api_quizzes_get", + "entity_id": "api_quizzes_get", + "generated_at": "2025-12-18T01:57:52.723112", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_quizzes_get", + "path": "/api/quizzes/:taskId", + "method": "GET", + "description": "Get quiz questions for a task", + "file_path": "app/api/quizzes/[taskId]/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "questions": [ + { + "id": "string", + "question": "string", + "options": [ + "string" + ] + } + ] + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/quizzes/taskId/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "GET /api/quizzes/:taskId returns success response", + "verification": "curl -X GET /api/quizzes/:taskId" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_quizzes_submit.yml b/.workflow/versions/v001/contexts/api_quizzes_submit.yml new file mode 100644 index 0000000..ea0fdd7 --- /dev/null +++ b/.workflow/versions/v001/contexts/api_quizzes_submit.yml @@ -0,0 +1,62 @@ +{ + "task_id": "task_create_api_quizzes_submit", + "entity_id": "api_quizzes_submit", + "generated_at": "2025-12-18T01:57:52.723196", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_quizzes_submit", + "path": "/api/quizzes/:taskId/submit", + "method": "POST", + "description": "Submit quiz answers", + "file_path": "app/api/quizzes/[taskId]/submit/route.ts", + "status": "PENDING", + "auth_required": true, + "request_body": { + "answers": "object (question_id -> answer mapping)" + }, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "score": "integer (0-100)", + "passed": "boolean", + "points_earned": "integer", + "new_balance": "integer" + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/quizzes/taskId/submit/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "POST /api/quizzes/:taskId/submit returns success response", + "verification": "curl -X POST /api/quizzes/:taskId/submit" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_referrals_claim.yml b/.workflow/versions/v001/contexts/api_referrals_claim.yml new file mode 100644 index 0000000..4323e6a --- /dev/null +++ b/.workflow/versions/v001/contexts/api_referrals_claim.yml @@ -0,0 +1,67 @@ +{ + "task_id": "task_create_api_referrals_claim", + "entity_id": "api_referrals_claim", + "generated_at": "2025-12-18T01:57:52.723424", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_referrals_claim", + "path": "/api/referrals/claim", + "method": "POST", + "description": "Claim referral bonus", + "file_path": "app/api/referrals/claim/route.ts", + "status": "PENDING", + "auth_required": true, + "request_body": { + "referral_code": "string (required)" + }, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "bonus_points": "integer", + "new_balance": "integer" + } + }, + { + "status": 400, + "description": "Error", + "schema": { + "error": "string (invalid code or already used)" + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/referrals/claim/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "POST /api/referrals/claim returns success response", + "verification": "curl -X POST /api/referrals/claim" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_referrals_create.yml b/.workflow/versions/v001/contexts/api_referrals_create.yml new file mode 100644 index 0000000..9eb4308 --- /dev/null +++ b/.workflow/versions/v001/contexts/api_referrals_create.yml @@ -0,0 +1,57 @@ +{ + "task_id": "task_create_api_referrals_create", + "entity_id": "api_referrals_create", + "generated_at": "2025-12-18T01:57:52.723352", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_referrals_create", + "path": "/api/referrals", + "method": "POST", + "description": "Create referral code", + "file_path": "app/api/referrals/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "referral_code": "string", + "referral_url": "string" + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/referrals/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "POST /api/referrals returns success response", + "verification": "curl -X POST /api/referrals" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_tasks_checkin.yml b/.workflow/versions/v001/contexts/api_tasks_checkin.yml new file mode 100644 index 0000000..d43688b --- /dev/null +++ b/.workflow/versions/v001/contexts/api_tasks_checkin.yml @@ -0,0 +1,65 @@ +{ + "task_id": "task_create_api_tasks_checkin", + "entity_id": "api_tasks_checkin", + "generated_at": "2025-12-18T01:57:52.722948", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_tasks_checkin", + "path": "/api/tasks/checkin", + "method": "POST", + "description": "Complete daily check-in", + "file_path": "app/api/tasks/checkin/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "points_earned": "integer", + "streak_days": "integer", + "new_balance": "integer" + } + }, + { + "status": 400, + "description": "Error", + "schema": { + "error": "Already checked in today" + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/tasks/checkin/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "POST /api/tasks/checkin returns success response", + "verification": "curl -X POST /api/tasks/checkin" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_tasks_complete.yml b/.workflow/versions/v001/contexts/api_tasks_complete.yml new file mode 100644 index 0000000..7c33506 --- /dev/null +++ b/.workflow/versions/v001/contexts/api_tasks_complete.yml @@ -0,0 +1,64 @@ +{ + "task_id": "task_create_api_tasks_complete", + "entity_id": "api_tasks_complete", + "generated_at": "2025-12-18T01:57:52.723026", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_tasks_complete", + "path": "/api/tasks/:id/complete", + "method": "POST", + "description": "Complete a specific task", + "file_path": "app/api/tasks/[id]/complete/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "points_earned": "integer", + "new_balance": "integer" + } + }, + { + "status": 400, + "description": "Error", + "schema": { + "error": "string (task not found or already completed)" + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/tasks/id/complete/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "POST /api/tasks/:id/complete returns success response", + "verification": "curl -X POST /api/tasks/:id/complete" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_tasks_list.yml b/.workflow/versions/v001/contexts/api_tasks_list.yml new file mode 100644 index 0000000..a5bc99e --- /dev/null +++ b/.workflow/versions/v001/contexts/api_tasks_list.yml @@ -0,0 +1,65 @@ +{ + "task_id": "task_create_api_tasks_list", + "entity_id": "api_tasks_list", + "generated_at": "2025-12-18T01:57:52.722869", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_tasks_list", + "path": "/api/tasks", + "method": "GET", + "description": "List all available tasks", + "file_path": "app/api/tasks/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "tasks": [ + { + "id": "string", + "type": "string", + "title": "string", + "description": "string", + "points_reward": "integer", + "is_completed_today": "boolean" + } + ] + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/tasks/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "GET /api/tasks returns success response", + "verification": "curl -X GET /api/tasks" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_users_badges.yml b/.workflow/versions/v001/contexts/api_users_badges.yml new file mode 100644 index 0000000..fdc34a5 --- /dev/null +++ b/.workflow/versions/v001/contexts/api_users_badges.yml @@ -0,0 +1,64 @@ +{ + "task_id": "task_create_api_users_badges", + "entity_id": "api_users_badges", + "generated_at": "2025-12-18T01:57:52.722788", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_users_badges", + "path": "/api/users/me/badges", + "method": "GET", + "description": "Get user earned badges", + "file_path": "app/api/users/me/badges/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "badges": [ + { + "id": "string", + "name": "string", + "description": "string", + "icon": "string", + "earned_at": "string (ISO date)" + } + ] + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/users/me/badges/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "GET /api/users/me/badges returns success response", + "verification": "curl -X GET /api/users/me/badges" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/api_users_points.yml b/.workflow/versions/v001/contexts/api_users_points.yml new file mode 100644 index 0000000..c7d2cd2 --- /dev/null +++ b/.workflow/versions/v001/contexts/api_users_points.yml @@ -0,0 +1,65 @@ +{ + "task_id": "task_create_api_users_points", + "entity_id": "api_users_points", + "generated_at": "2025-12-18T01:57:52.722710", + "workflow_version": "v001", + "target": { + "type": "api", + "definition": { + "id": "api_users_points", + "path": "/api/users/me/points", + "method": "GET", + "description": "Get user points balance and transaction history", + "file_path": "app/api/users/me/points/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "balance": "integer", + "transactions": [ + { + "id": "string", + "amount": "integer", + "type": "string", + "source": "string", + "created_at": "string (ISO date)" + } + ] + } + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/api/users/me/points/route.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "GET /api/users/me/points returns success response", + "verification": "curl -X GET /api/users/me/points" + }, + { + "criterion": "Request validation implemented", + "verification": "Test with invalid data" + }, + { + "criterion": "Error responses match contract", + "verification": "Test error scenarios" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_auth_form.yml b/.workflow/versions/v001/contexts/component_auth_form.yml new file mode 100644 index 0000000..fca0bc1 --- /dev/null +++ b/.workflow/versions/v001/contexts/component_auth_form.yml @@ -0,0 +1,58 @@ +{ + "task_id": "task_create_component_auth_form", + "entity_id": "component_auth_form", + "generated_at": "2025-12-18T01:57:52.724088", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_auth_form", + "name": "AuthForm", + "description": "Reusable login/register form component", + "file_path": "app/components/AuthForm.tsx", + "status": "PENDING", + "props": { + "mode": "'login' | 'register'", + "onSubmit": "(data: AuthData) => Promise", + "error": "string | null" + }, + "features": [ + "Email and password inputs", + "Name input (register mode only)", + "Client-side validation", + "Error display", + "Loading state", + "Submit button" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/AuthForm.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_badge_card.yml b/.workflow/versions/v001/contexts/component_badge_card.yml new file mode 100644 index 0000000..7450809 --- /dev/null +++ b/.workflow/versions/v001/contexts/component_badge_card.yml @@ -0,0 +1,57 @@ +{ + "task_id": "task_create_component_badge_card", + "entity_id": "component_badge_card", + "generated_at": "2025-12-18T01:57:52.724450", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_badge_card", + "name": "BadgeCard", + "description": "Display a badge or achievement", + "file_path": "app/components/BadgeCard.tsx", + "status": "PENDING", + "props": { + "badge": "Badge object", + "earned": "boolean", + "earnedAt": "Date | null" + }, + "features": [ + "Badge icon display", + "Badge name and description", + "Earned/locked state", + "Earned date (if earned)", + "Tooltip with requirement" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/BadgeCard.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_daily_checkin_button.yml b/.workflow/versions/v001/contexts/component_daily_checkin_button.yml new file mode 100644 index 0000000..47105c6 --- /dev/null +++ b/.workflow/versions/v001/contexts/component_daily_checkin_button.yml @@ -0,0 +1,57 @@ +{ + "task_id": "task_create_component_daily_checkin_button", + "entity_id": "component_daily_checkin_button", + "generated_at": "2025-12-18T01:57:52.724595", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_daily_checkin_button", + "name": "DailyCheckinButton", + "description": "Check-in button with streak display", + "file_path": "app/components/DailyCheckinButton.tsx", + "status": "PENDING", + "props": { + "onCheckIn": "() => Promise", + "isCheckedInToday": "boolean", + "streakDays": "number" + }, + "features": [ + "Large prominent button", + "Streak counter display", + "Disabled state if already checked in", + "Success animation", + "Points earned display" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/DailyCheckinButton.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_dark_theme_layout.yml b/.workflow/versions/v001/contexts/component_dark_theme_layout.yml new file mode 100644 index 0000000..cfff774 --- /dev/null +++ b/.workflow/versions/v001/contexts/component_dark_theme_layout.yml @@ -0,0 +1,54 @@ +{ + "task_id": "task_create_component_dark_theme_layout", + "entity_id": "component_dark_theme_layout", + "generated_at": "2025-12-18T01:57:52.724806", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_dark_theme_layout", + "name": "DarkThemeLayout", + "description": "Root layout with dark theme", + "file_path": "app/components/DarkThemeLayout.tsx", + "status": "PENDING", + "props": { + "children": "React.ReactNode" + }, + "features": [ + "Dark background colors", + "Global dark theme CSS variables", + "Consistent spacing and typography", + "Tailwind CSS dark mode utilities" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/DarkThemeLayout.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_leaderboard_table.yml b/.workflow/versions/v001/contexts/component_leaderboard_table.yml new file mode 100644 index 0000000..c39ec18 --- /dev/null +++ b/.workflow/versions/v001/contexts/component_leaderboard_table.yml @@ -0,0 +1,56 @@ +{ + "task_id": "task_create_component_leaderboard_table", + "entity_id": "component_leaderboard_table", + "generated_at": "2025-12-18T01:57:52.724524", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_leaderboard_table", + "name": "LeaderboardTable", + "description": "Rankings table", + "file_path": "app/components/LeaderboardTable.tsx", + "status": "PENDING", + "props": { + "entries": "LeaderboardEntry[]", + "currentUserId": "string" + }, + "features": [ + "Table with rank, name, points columns", + "Highlight current user row", + "Top 3 special styling", + "Responsive design", + "Dark theme table styling" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/LeaderboardTable.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_navbar.yml b/.workflow/versions/v001/contexts/component_navbar.yml new file mode 100644 index 0000000..b1f9744 --- /dev/null +++ b/.workflow/versions/v001/contexts/component_navbar.yml @@ -0,0 +1,56 @@ +{ + "task_id": "task_create_component_navbar", + "entity_id": "component_navbar", + "generated_at": "2025-12-18T01:57:52.724737", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_navbar", + "name": "Navbar", + "description": "Main navigation bar", + "file_path": "app/components/Navbar.tsx", + "status": "PENDING", + "props": { + "currentPath": "string" + }, + "features": [ + "Links to Dashboard, Tasks, Leaderboard, Profile, Referral", + "Active link highlighting", + "User menu with logout", + "Points balance display", + "Responsive mobile menu", + "Dark theme styling" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/Navbar.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_points_display.yml b/.workflow/versions/v001/contexts/component_points_display.yml new file mode 100644 index 0000000..155d519 --- /dev/null +++ b/.workflow/versions/v001/contexts/component_points_display.yml @@ -0,0 +1,55 @@ +{ + "task_id": "task_create_component_points_display", + "entity_id": "component_points_display", + "generated_at": "2025-12-18T01:57:52.724158", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_points_display", + "name": "PointsDisplay", + "description": "Display current points balance", + "file_path": "app/components/PointsDisplay.tsx", + "status": "PENDING", + "props": { + "balance": "number", + "showAnimation": "boolean (optional)" + }, + "features": [ + "Large points number display", + "Coin/points icon", + "Optional animation on change", + "Dark theme styling" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/PointsDisplay.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_quiz_question.yml b/.workflow/versions/v001/contexts/component_quiz_question.yml new file mode 100644 index 0000000..1369792 --- /dev/null +++ b/.workflow/versions/v001/contexts/component_quiz_question.yml @@ -0,0 +1,57 @@ +{ + "task_id": "task_create_component_quiz_question", + "entity_id": "component_quiz_question", + "generated_at": "2025-12-18T01:57:52.724372", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_quiz_question", + "name": "QuizQuestion", + "description": "Quiz question with multiple choice", + "file_path": "app/components/QuizQuestion.tsx", + "status": "PENDING", + "props": { + "question": "string", + "options": "string[]", + "onAnswer": "(answer: string) => void", + "selectedAnswer": "string | null" + }, + "features": [ + "Question text display", + "Radio button options", + "Highlight selected answer", + "Dark theme styling" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/QuizQuestion.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_task_card.yml b/.workflow/versions/v001/contexts/component_task_card.yml new file mode 100644 index 0000000..af4357e --- /dev/null +++ b/.workflow/versions/v001/contexts/component_task_card.yml @@ -0,0 +1,58 @@ +{ + "task_id": "task_create_component_task_card", + "entity_id": "component_task_card", + "generated_at": "2025-12-18T01:57:52.724228", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_task_card", + "name": "TaskCard", + "description": "Display a single task", + "file_path": "app/components/TaskCard.tsx", + "status": "PENDING", + "props": { + "task": "Task object", + "onComplete": "() => void", + "isCompleted": "boolean" + }, + "features": [ + "Task title and description", + "Points reward badge", + "Task type icon", + "Completion status", + "Complete button", + "Dark theme card styling" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/TaskCard.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_task_list.yml b/.workflow/versions/v001/contexts/component_task_list.yml new file mode 100644 index 0000000..2f0e621 --- /dev/null +++ b/.workflow/versions/v001/contexts/component_task_list.yml @@ -0,0 +1,55 @@ +{ + "task_id": "task_create_component_task_list", + "entity_id": "component_task_list", + "generated_at": "2025-12-18T01:57:52.724299", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_task_list", + "name": "TaskList", + "description": "List of task cards with filtering", + "file_path": "app/components/TaskList.tsx", + "status": "PENDING", + "props": { + "tasks": "Task[]", + "onTaskComplete": "(taskId: string) => void" + }, + "features": [ + "Grid layout of TaskCard components", + "Filter by task type", + "Empty state message", + "Loading state" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/TaskList.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/component_transaction_history.yml b/.workflow/versions/v001/contexts/component_transaction_history.yml new file mode 100644 index 0000000..1c4c79b --- /dev/null +++ b/.workflow/versions/v001/contexts/component_transaction_history.yml @@ -0,0 +1,55 @@ +{ + "task_id": "task_create_component_transaction_history", + "entity_id": "component_transaction_history", + "generated_at": "2025-12-18T01:57:52.724666", + "workflow_version": "v001", + "target": { + "type": "component", + "definition": { + "id": "component_transaction_history", + "name": "TransactionHistory", + "description": "List of points transactions", + "file_path": "app/components/TransactionHistory.tsx", + "status": "PENDING", + "props": { + "transactions": "Transaction[]" + }, + "features": [ + "List of transactions with date, amount, source", + "Color coding (green for earned, red for spent)", + "Pagination or infinite scroll", + "Empty state message", + "Dark theme styling" + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/components/TransactionHistory.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Component renders without errors", + "verification": "Import and render in test" + }, + { + "criterion": "Props are typed correctly", + "verification": "TypeScript compilation" + }, + { + "criterion": "Events fire correctly", + "verification": "Test event handlers" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/model_badge.yml b/.workflow/versions/v001/contexts/model_badge.yml new file mode 100644 index 0000000..7157979 --- /dev/null +++ b/.workflow/versions/v001/contexts/model_badge.yml @@ -0,0 +1,93 @@ +{ + "task_id": "task_create_model_badge", + "entity_id": "model_badge", + "generated_at": "2025-12-18T01:57:52.722103", + "workflow_version": "v001", + "target": { + "type": "model", + "definition": { + "id": "model_badge", + "name": "Badge", + "description": "Available badges and achievements", + "file_path": "app/lib/db/schema/badge.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique badge identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "name", + "type": "string", + "required": true, + "description": "Badge display name" + }, + { + "name": "description", + "type": "string", + "required": true, + "description": "Badge description" + }, + { + "name": "icon", + "type": "string", + "required": true, + "description": "Badge icon identifier or URL" + }, + { + "name": "requirement_type", + "type": "enum", + "required": true, + "description": "Type of requirement to earn badge", + "enum_values": [ + "points_total", + "tasks_completed", + "streak_days", + "referrals" + ] + }, + { + "name": "requirement_value", + "type": "integer", + "required": true, + "description": "Threshold value for requirement" + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "prisma/schema.prisma", + "app/models/badge.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Model defined in Prisma schema", + "verification": "Check prisma/schema.prisma" + }, + { + "criterion": "TypeScript types exported", + "verification": "Import type in test file" + }, + { + "criterion": "Relations properly configured", + "verification": "Check Prisma relations" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/model_points.yml b/.workflow/versions/v001/contexts/model_points.yml new file mode 100644 index 0000000..4abead3 --- /dev/null +++ b/.workflow/versions/v001/contexts/model_points.yml @@ -0,0 +1,100 @@ +{ + "task_id": "task_create_model_points", + "entity_id": "model_points", + "generated_at": "2025-12-18T01:57:52.721807", + "workflow_version": "v001", + "target": { + "type": "model", + "definition": { + "id": "model_points", + "name": "Points", + "description": "Points transaction history", + "file_path": "app/lib/db/schema/points.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique transaction identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "user_id", + "type": "string", + "required": true, + "description": "Foreign key to User" + }, + { + "name": "amount", + "type": "integer", + "required": true, + "description": "Points amount (positive for earned, negative for spent)" + }, + { + "name": "type", + "type": "enum", + "required": true, + "description": "Transaction type", + "enum_values": [ + "earned", + "spent" + ] + }, + { + "name": "source", + "type": "string", + "required": true, + "description": "Source of points (task_id, purchase_id, etc.)" + }, + { + "name": "created_at", + "type": "timestamp", + "required": true, + "description": "Transaction timestamp" + } + ], + "indexes": [ + { + "fields": [ + "user_id", + "created_at" + ], + "unique": false + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "prisma/schema.prisma", + "app/models/points.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Model defined in Prisma schema", + "verification": "Check prisma/schema.prisma" + }, + { + "criterion": "TypeScript types exported", + "verification": "Import type in test file" + }, + { + "criterion": "Relations properly configured", + "verification": "Check Prisma relations" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/model_quiz.yml b/.workflow/versions/v001/contexts/model_quiz.yml new file mode 100644 index 0000000..a57ee7e --- /dev/null +++ b/.workflow/versions/v001/contexts/model_quiz.yml @@ -0,0 +1,89 @@ +{ + "task_id": "task_create_model_quiz", + "entity_id": "model_quiz", + "generated_at": "2025-12-18T01:57:52.722279", + "workflow_version": "v001", + "target": { + "type": "model", + "definition": { + "id": "model_quiz", + "name": "Quiz", + "description": "Quiz questions for learning tasks", + "file_path": "app/lib/db/schema/quiz.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique quiz identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "task_id", + "type": "string", + "required": true, + "description": "Foreign key to Task" + }, + { + "name": "question", + "type": "string", + "required": true, + "description": "Quiz question text" + }, + { + "name": "options", + "type": "json", + "required": true, + "description": "Array of answer options" + }, + { + "name": "correct_answer", + "type": "string", + "required": true, + "description": "Correct answer identifier" + } + ], + "indexes": [ + { + "fields": [ + "task_id" + ], + "unique": false + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "prisma/schema.prisma", + "app/models/quiz.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Model defined in Prisma schema", + "verification": "Check prisma/schema.prisma" + }, + { + "criterion": "TypeScript types exported", + "verification": "Import type in test file" + }, + { + "criterion": "Relations properly configured", + "verification": "Check Prisma relations" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/model_referral.yml b/.workflow/versions/v001/contexts/model_referral.yml new file mode 100644 index 0000000..5c76fa1 --- /dev/null +++ b/.workflow/versions/v001/contexts/model_referral.yml @@ -0,0 +1,95 @@ +{ + "task_id": "task_create_model_referral", + "entity_id": "model_referral", + "generated_at": "2025-12-18T01:57:52.722366", + "workflow_version": "v001", + "target": { + "type": "model", + "definition": { + "id": "model_referral", + "name": "Referral", + "description": "Referral tracking and rewards", + "file_path": "app/lib/db/schema/referral.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique referral identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "referrer_id", + "type": "string", + "required": true, + "description": "Foreign key to User (who referred)" + }, + { + "name": "referred_id", + "type": "string", + "required": true, + "description": "Foreign key to User (who was referred)" + }, + { + "name": "bonus_points", + "type": "integer", + "required": true, + "description": "Points awarded to referrer" + }, + { + "name": "created_at", + "type": "timestamp", + "required": true, + "description": "Referral creation timestamp" + } + ], + "indexes": [ + { + "fields": [ + "referrer_id" + ], + "unique": false + }, + { + "fields": [ + "referred_id" + ], + "unique": true + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "prisma/schema.prisma", + "app/models/referral.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Model defined in Prisma schema", + "verification": "Check prisma/schema.prisma" + }, + { + "criterion": "TypeScript types exported", + "verification": "Import type in test file" + }, + { + "criterion": "Relations properly configured", + "verification": "Check Prisma relations" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/model_task.yml b/.workflow/versions/v001/contexts/model_task.yml new file mode 100644 index 0000000..94b8802 --- /dev/null +++ b/.workflow/versions/v001/contexts/model_task.yml @@ -0,0 +1,105 @@ +{ + "task_id": "task_create_model_task", + "entity_id": "model_task", + "generated_at": "2025-12-18T01:57:52.721911", + "workflow_version": "v001", + "target": { + "type": "model", + "definition": { + "id": "model_task", + "name": "Task", + "description": "Available tasks for earning points", + "file_path": "app/lib/db/schema/task.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique task identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "type", + "type": "enum", + "required": true, + "description": "Task category", + "enum_values": [ + "checkin", + "ad", + "survey", + "referral", + "quiz", + "video", + "reading" + ] + }, + { + "name": "title", + "type": "string", + "required": true, + "description": "Task display title" + }, + { + "name": "description", + "type": "string", + "required": true, + "description": "Task description" + }, + { + "name": "points_reward", + "type": "integer", + "required": true, + "description": "Points awarded upon completion" + }, + { + "name": "is_active", + "type": "boolean", + "required": true, + "description": "Whether task is currently available" + } + ], + "indexes": [ + { + "fields": [ + "type", + "is_active" + ], + "unique": false + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "prisma/schema.prisma", + "app/models/task.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Model defined in Prisma schema", + "verification": "Check prisma/schema.prisma" + }, + { + "criterion": "TypeScript types exported", + "verification": "Import type in test file" + }, + { + "criterion": "Relations properly configured", + "verification": "Check Prisma relations" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/model_user.yml b/.workflow/versions/v001/contexts/model_user.yml new file mode 100644 index 0000000..2c9d3ad --- /dev/null +++ b/.workflow/versions/v001/contexts/model_user.yml @@ -0,0 +1,95 @@ +{ + "task_id": "task_create_model_user", + "entity_id": "model_user", + "generated_at": "2025-12-18T01:57:52.721672", + "workflow_version": "v001", + "target": { + "type": "model", + "definition": { + "id": "model_user", + "name": "User", + "description": "User account and authentication", + "file_path": "app/lib/db/schema/user.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique user identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "email", + "type": "string", + "required": true, + "description": "User email address (unique)" + }, + { + "name": "password_hash", + "type": "string", + "required": true, + "description": "Hashed password using bcrypt" + }, + { + "name": "name", + "type": "string", + "required": true, + "description": "User display name" + }, + { + "name": "created_at", + "type": "timestamp", + "required": true, + "description": "Account creation timestamp" + }, + { + "name": "updated_at", + "type": "timestamp", + "required": true, + "description": "Last update timestamp" + } + ], + "indexes": [ + { + "fields": [ + "email" + ], + "unique": true + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "prisma/schema.prisma", + "app/models/user.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Model defined in Prisma schema", + "verification": "Check prisma/schema.prisma" + }, + { + "criterion": "TypeScript types exported", + "verification": "Import type in test file" + }, + { + "criterion": "Relations properly configured", + "verification": "Check Prisma relations" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/model_user_badge.yml b/.workflow/versions/v001/contexts/model_user_badge.yml new file mode 100644 index 0000000..607ee9e --- /dev/null +++ b/.workflow/versions/v001/contexts/model_user_badge.yml @@ -0,0 +1,84 @@ +{ + "task_id": "task_create_model_user_badge", + "entity_id": "model_user_badge", + "generated_at": "2025-12-18T01:57:52.722194", + "workflow_version": "v001", + "target": { + "type": "model", + "definition": { + "id": "model_user_badge", + "name": "UserBadge", + "description": "User earned badges", + "file_path": "app/lib/db/schema/user_badge.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique user badge identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "user_id", + "type": "string", + "required": true, + "description": "Foreign key to User" + }, + { + "name": "badge_id", + "type": "string", + "required": true, + "description": "Foreign key to Badge" + }, + { + "name": "earned_at", + "type": "timestamp", + "required": true, + "description": "Badge earned timestamp" + } + ], + "indexes": [ + { + "fields": [ + "user_id", + "badge_id" + ], + "unique": true + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "prisma/schema.prisma", + "app/models/userbadge.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Model defined in Prisma schema", + "verification": "Check prisma/schema.prisma" + }, + { + "criterion": "TypeScript types exported", + "verification": "Import type in test file" + }, + { + "criterion": "Relations properly configured", + "verification": "Check Prisma relations" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/model_user_task.yml b/.workflow/versions/v001/contexts/model_user_task.yml new file mode 100644 index 0000000..6e36a35 --- /dev/null +++ b/.workflow/versions/v001/contexts/model_user_task.yml @@ -0,0 +1,91 @@ +{ + "task_id": "task_create_model_user_task", + "entity_id": "model_user_task", + "generated_at": "2025-12-18T01:57:52.722010", + "workflow_version": "v001", + "target": { + "type": "model", + "definition": { + "id": "model_user_task", + "name": "UserTask", + "description": "User task completion records", + "file_path": "app/lib/db/schema/user_task.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique completion record identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "user_id", + "type": "string", + "required": true, + "description": "Foreign key to User" + }, + { + "name": "task_id", + "type": "string", + "required": true, + "description": "Foreign key to Task" + }, + { + "name": "completed_at", + "type": "timestamp", + "required": true, + "description": "Task completion timestamp" + }, + { + "name": "points_earned", + "type": "integer", + "required": true, + "description": "Points awarded for this completion" + } + ], + "indexes": [ + { + "fields": [ + "user_id", + "task_id", + "completed_at" + ], + "unique": false + } + ] + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "prisma/schema.prisma", + "app/models/usertask.ts" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Model defined in Prisma schema", + "verification": "Check prisma/schema.prisma" + }, + { + "criterion": "TypeScript types exported", + "verification": "Import type in test file" + }, + { + "criterion": "Relations properly configured", + "verification": "Check Prisma relations" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/page_dashboard.yml b/.workflow/versions/v001/contexts/page_dashboard.yml new file mode 100644 index 0000000..91e2e7b --- /dev/null +++ b/.workflow/versions/v001/contexts/page_dashboard.yml @@ -0,0 +1,61 @@ +{ + "task_id": "task_create_page_dashboard", + "entity_id": "page_dashboard", + "generated_at": "2025-12-18T01:57:52.723646", + "workflow_version": "v001", + "target": { + "type": "page", + "definition": { + "id": "page_dashboard", + "name": "Dashboard", + "description": "Main dashboard with overview", + "file_path": "app/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "PointsDisplay", + "TaskCard", + "DailyCheckinButton", + "BadgeCard", + "Navbar" + ], + "features": [ + "Points balance display", + "Daily check-in button with streak", + "Quick task overview (3-5 featured tasks)", + "Recent badges earned", + "Navigation to other sections" + ], + "path": "/" + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app//page.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Page renders at /", + "verification": "Navigate to /" + }, + { + "criterion": "Data fetching works", + "verification": "Check network tab" + }, + { + "criterion": "Components render correctly", + "verification": "Visual inspection" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/page_leaderboard.yml b/.workflow/versions/v001/contexts/page_leaderboard.yml new file mode 100644 index 0000000..98aa2e7 --- /dev/null +++ b/.workflow/versions/v001/contexts/page_leaderboard.yml @@ -0,0 +1,58 @@ +{ + "task_id": "task_create_page_leaderboard", + "entity_id": "page_leaderboard", + "generated_at": "2025-12-18T01:57:52.723944", + "workflow_version": "v001", + "target": { + "type": "page", + "definition": { + "id": "page_leaderboard", + "name": "Leaderboard Page", + "description": "Rankings of top users", + "file_path": "app/leaderboard/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "LeaderboardTable", + "Navbar" + ], + "features": [ + "Top 100 users by points", + "User rank display", + "Current user highlight", + "Points totals", + "Update in real-time" + ], + "path": "/leaderboard" + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/leaderboard/page.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Page renders at /leaderboard", + "verification": "Navigate to /leaderboard" + }, + { + "criterion": "Data fetching works", + "verification": "Check network tab" + }, + { + "criterion": "Components render correctly", + "verification": "Visual inspection" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/page_login.yml b/.workflow/versions/v001/contexts/page_login.yml new file mode 100644 index 0000000..c51140f --- /dev/null +++ b/.workflow/versions/v001/contexts/page_login.yml @@ -0,0 +1,56 @@ +{ + "task_id": "task_create_page_login", + "entity_id": "page_login", + "generated_at": "2025-12-18T01:57:52.723504", + "workflow_version": "v001", + "target": { + "type": "page", + "definition": { + "id": "page_login", + "name": "Login Page", + "description": "User login page", + "file_path": "app/login/page.tsx", + "status": "PENDING", + "components_used": [ + "AuthForm" + ], + "features": [ + "Email/password login form", + "Link to register page", + "Form validation", + "Error display", + "Redirect to dashboard on success" + ], + "path": "/login" + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/login/page.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Page renders at /login", + "verification": "Navigate to /login" + }, + { + "criterion": "Data fetching works", + "verification": "Check network tab" + }, + { + "criterion": "Components render correctly", + "verification": "Visual inspection" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/page_profile.yml b/.workflow/versions/v001/contexts/page_profile.yml new file mode 100644 index 0000000..bfde08b --- /dev/null +++ b/.workflow/versions/v001/contexts/page_profile.yml @@ -0,0 +1,60 @@ +{ + "task_id": "task_create_page_profile", + "entity_id": "page_profile", + "generated_at": "2025-12-18T01:57:52.723874", + "workflow_version": "v001", + "target": { + "type": "page", + "definition": { + "id": "page_profile", + "name": "Profile Page", + "description": "User profile with points history", + "file_path": "app/profile/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "PointsDisplay", + "TransactionHistory", + "BadgeCard", + "Navbar" + ], + "features": [ + "User info display", + "Total points balance", + "Points transaction history", + "Earned badges display", + "Account statistics" + ], + "path": "/profile" + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/profile/page.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Page renders at /profile", + "verification": "Navigate to /profile" + }, + { + "criterion": "Data fetching works", + "verification": "Check network tab" + }, + { + "criterion": "Components render correctly", + "verification": "Visual inspection" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/page_quiz.yml b/.workflow/versions/v001/contexts/page_quiz.yml new file mode 100644 index 0000000..a3a5f89 --- /dev/null +++ b/.workflow/versions/v001/contexts/page_quiz.yml @@ -0,0 +1,58 @@ +{ + "task_id": "task_create_page_quiz", + "entity_id": "page_quiz", + "generated_at": "2025-12-18T01:57:52.723794", + "workflow_version": "v001", + "target": { + "type": "page", + "definition": { + "id": "page_quiz", + "name": "Quiz Page", + "description": "Quiz interface for learning tasks", + "file_path": "app/quiz/[id]/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "QuizQuestion", + "Navbar" + ], + "features": [ + "Display quiz questions one by one", + "Multiple choice answers", + "Submit button", + "Score display after completion", + "Points earned display" + ], + "path": "/quiz/[id]" + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/quiz/[id]/page.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Page renders at /quiz/[id]", + "verification": "Navigate to /quiz/[id]" + }, + { + "criterion": "Data fetching works", + "verification": "Check network tab" + }, + { + "criterion": "Components render correctly", + "verification": "Visual inspection" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/page_referral.yml b/.workflow/versions/v001/contexts/page_referral.yml new file mode 100644 index 0000000..0858296 --- /dev/null +++ b/.workflow/versions/v001/contexts/page_referral.yml @@ -0,0 +1,57 @@ +{ + "task_id": "task_create_page_referral", + "entity_id": "page_referral", + "generated_at": "2025-12-18T01:57:52.724017", + "workflow_version": "v001", + "target": { + "type": "page", + "definition": { + "id": "page_referral", + "name": "Referral Page", + "description": "Referral code sharing", + "file_path": "app/referral/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "Navbar" + ], + "features": [ + "Generate referral code", + "Display shareable link", + "Copy to clipboard button", + "Show referral statistics", + "Bonus points display" + ], + "path": "/referral" + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/referral/page.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Page renders at /referral", + "verification": "Navigate to /referral" + }, + { + "criterion": "Data fetching works", + "verification": "Check network tab" + }, + { + "criterion": "Components render correctly", + "verification": "Visual inspection" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/page_register.yml b/.workflow/versions/v001/contexts/page_register.yml new file mode 100644 index 0000000..a6d7443 --- /dev/null +++ b/.workflow/versions/v001/contexts/page_register.yml @@ -0,0 +1,56 @@ +{ + "task_id": "task_create_page_register", + "entity_id": "page_register", + "generated_at": "2025-12-18T01:57:52.723574", + "workflow_version": "v001", + "target": { + "type": "page", + "definition": { + "id": "page_register", + "name": "Register Page", + "description": "User registration page", + "file_path": "app/register/page.tsx", + "status": "PENDING", + "components_used": [ + "AuthForm" + ], + "features": [ + "Registration form with email, password, name", + "Link to login page", + "Form validation (password min 8 chars)", + "Error display", + "Redirect to dashboard on success" + ], + "path": "/register" + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/register/page.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Page renders at /register", + "verification": "Navigate to /register" + }, + { + "criterion": "Data fetching works", + "verification": "Check network tab" + }, + { + "criterion": "Components render correctly", + "verification": "Visual inspection" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/contexts/page_tasks.yml b/.workflow/versions/v001/contexts/page_tasks.yml new file mode 100644 index 0000000..cc6138b --- /dev/null +++ b/.workflow/versions/v001/contexts/page_tasks.yml @@ -0,0 +1,59 @@ +{ + "task_id": "task_create_page_tasks", + "entity_id": "page_tasks", + "generated_at": "2025-12-18T01:57:52.723718", + "workflow_version": "v001", + "target": { + "type": "page", + "definition": { + "id": "page_tasks", + "name": "Tasks Page", + "description": "List of all available tasks", + "file_path": "app/tasks/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "TaskList", + "TaskCard", + "Navbar" + ], + "features": [ + "List all available tasks", + "Filter by task type", + "Show completion status", + "Click to complete or view task", + "Points reward display" + ], + "path": "/tasks" + } + }, + "related": { + "models": [], + "apis": [], + "components": [] + }, + "dependencies": { + "entity_ids": [], + "definitions": [] + }, + "files": { + "to_create": [ + "app/tasks/page.tsx" + ], + "reference": [] + }, + "acceptance": [ + { + "criterion": "Page renders at /tasks", + "verification": "Navigate to /tasks" + }, + { + "criterion": "Data fetching works", + "verification": "Check network tab" + }, + { + "criterion": "Components render correctly", + "verification": "Visual inspection" + } + ] +} \ No newline at end of file diff --git a/.workflow/versions/v001/dependency_graph.yml b/.workflow/versions/v001/dependency_graph.yml new file mode 100644 index 0000000..55b3631 --- /dev/null +++ b/.workflow/versions/v001/dependency_graph.yml @@ -0,0 +1,628 @@ +{ + "dependency_graph": { + "design_version": 1, + "workflow_version": "v001", + "generated_at": "2025-12-18T01:57:52.721168", + "generator": "validate_design.py", + "stats": { + "total_entities": 40, + "total_layers": 1, + "max_parallelism": 40, + "critical_path_length": 1 + } + }, + "layers": [ + { + "layer": 1, + "name": "Data Layer", + "description": "Database models - no external dependencies", + "items": [ + { + "id": "api_auth_login", + "type": "api", + "name": "api_auth_login", + "depends_on": [], + "task_id": "task_create_api_auth_login", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_auth_me", + "type": "api", + "name": "api_auth_me", + "depends_on": [], + "task_id": "task_create_api_auth_me", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_auth_register", + "type": "api", + "name": "api_auth_register", + "depends_on": [], + "task_id": "task_create_api_auth_register", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_leaderboard", + "type": "api", + "name": "api_leaderboard", + "depends_on": [], + "task_id": "task_create_api_leaderboard", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_quizzes_get", + "type": "api", + "name": "api_quizzes_get", + "depends_on": [], + "task_id": "task_create_api_quizzes_get", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_quizzes_submit", + "type": "api", + "name": "api_quizzes_submit", + "depends_on": [], + "task_id": "task_create_api_quizzes_submit", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_referrals_claim", + "type": "api", + "name": "api_referrals_claim", + "depends_on": [], + "task_id": "task_create_api_referrals_claim", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_referrals_create", + "type": "api", + "name": "api_referrals_create", + "depends_on": [], + "task_id": "task_create_api_referrals_create", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_tasks_checkin", + "type": "api", + "name": "api_tasks_checkin", + "depends_on": [], + "task_id": "task_create_api_tasks_checkin", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_tasks_complete", + "type": "api", + "name": "api_tasks_complete", + "depends_on": [], + "task_id": "task_create_api_tasks_complete", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_tasks_list", + "type": "api", + "name": "api_tasks_list", + "depends_on": [], + "task_id": "task_create_api_tasks_list", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_users_badges", + "type": "api", + "name": "api_users_badges", + "depends_on": [], + "task_id": "task_create_api_users_badges", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "api_users_points", + "type": "api", + "name": "api_users_points", + "depends_on": [], + "task_id": "task_create_api_users_points", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "component_auth_form", + "type": "component", + "name": "AuthForm", + "depends_on": [], + "task_id": "task_create_component_auth_form", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_badge_card", + "type": "component", + "name": "BadgeCard", + "depends_on": [], + "task_id": "task_create_component_badge_card", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_daily_checkin_button", + "type": "component", + "name": "DailyCheckinButton", + "depends_on": [], + "task_id": "task_create_component_daily_checkin_button", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_dark_theme_layout", + "type": "component", + "name": "DarkThemeLayout", + "depends_on": [], + "task_id": "task_create_component_dark_theme_layout", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_leaderboard_table", + "type": "component", + "name": "LeaderboardTable", + "depends_on": [], + "task_id": "task_create_component_leaderboard_table", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_navbar", + "type": "component", + "name": "Navbar", + "depends_on": [], + "task_id": "task_create_component_navbar", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_points_display", + "type": "component", + "name": "PointsDisplay", + "depends_on": [], + "task_id": "task_create_component_points_display", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_quiz_question", + "type": "component", + "name": "QuizQuestion", + "depends_on": [], + "task_id": "task_create_component_quiz_question", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_task_card", + "type": "component", + "name": "TaskCard", + "depends_on": [], + "task_id": "task_create_component_task_card", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_task_list", + "type": "component", + "name": "TaskList", + "depends_on": [], + "task_id": "task_create_component_task_list", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "component_transaction_history", + "type": "component", + "name": "TransactionHistory", + "depends_on": [], + "task_id": "task_create_component_transaction_history", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "model_badge", + "type": "model", + "name": "Badge", + "depends_on": [], + "task_id": "task_create_model_badge", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "model_points", + "type": "model", + "name": "Points", + "depends_on": [], + "task_id": "task_create_model_points", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "model_quiz", + "type": "model", + "name": "Quiz", + "depends_on": [], + "task_id": "task_create_model_quiz", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "model_referral", + "type": "model", + "name": "Referral", + "depends_on": [], + "task_id": "task_create_model_referral", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "model_task", + "type": "model", + "name": "Task", + "depends_on": [], + "task_id": "task_create_model_task", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "model_user", + "type": "model", + "name": "User", + "depends_on": [], + "task_id": "task_create_model_user", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "model_user_badge", + "type": "model", + "name": "UserBadge", + "depends_on": [], + "task_id": "task_create_model_user_badge", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "model_user_task", + "type": "model", + "name": "UserTask", + "depends_on": [], + "task_id": "task_create_model_user_task", + "agent": "backend", + "complexity": "medium" + }, + { + "id": "page_dashboard", + "type": "page", + "name": "Dashboard", + "depends_on": [], + "task_id": "task_create_page_dashboard", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "page_leaderboard", + "type": "page", + "name": "Leaderboard Page", + "depends_on": [], + "task_id": "task_create_page_leaderboard", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "page_login", + "type": "page", + "name": "Login Page", + "depends_on": [], + "task_id": "task_create_page_login", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "page_profile", + "type": "page", + "name": "Profile Page", + "depends_on": [], + "task_id": "task_create_page_profile", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "page_quiz", + "type": "page", + "name": "Quiz Page", + "depends_on": [], + "task_id": "task_create_page_quiz", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "page_referral", + "type": "page", + "name": "Referral Page", + "depends_on": [], + "task_id": "task_create_page_referral", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "page_register", + "type": "page", + "name": "Register Page", + "depends_on": [], + "task_id": "task_create_page_register", + "agent": "frontend", + "complexity": "medium" + }, + { + "id": "page_tasks", + "type": "page", + "name": "Tasks Page", + "depends_on": [], + "task_id": "task_create_page_tasks", + "agent": "frontend", + "complexity": "medium" + } + ], + "requires_layers": [], + "parallel_count": 40 + } + ], + "dependency_map": { + "model_user": { + "type": "model", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "model_points": { + "type": "model", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "model_task": { + "type": "model", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "model_user_task": { + "type": "model", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "model_badge": { + "type": "model", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "model_user_badge": { + "type": "model", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "model_quiz": { + "type": "model", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "model_referral": { + "type": "model", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_auth_register": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_auth_login": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_auth_me": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_users_points": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_users_badges": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_tasks_list": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_tasks_checkin": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_tasks_complete": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_quizzes_get": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_quizzes_submit": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_leaderboard": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_referrals_create": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "api_referrals_claim": { + "type": "api", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "page_login": { + "type": "page", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "page_register": { + "type": "page", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "page_dashboard": { + "type": "page", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "page_tasks": { + "type": "page", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "page_quiz": { + "type": "page", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "page_profile": { + "type": "page", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "page_leaderboard": { + "type": "page", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "page_referral": { + "type": "page", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_auth_form": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_points_display": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_task_card": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_task_list": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_quiz_question": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_badge_card": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_leaderboard_table": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_daily_checkin_button": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_transaction_history": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_navbar": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + }, + "component_dark_theme_layout": { + "type": "component", + "layer": 1, + "depends_on": [], + "depended_by": [] + } + }, + "task_map": [] +} \ No newline at end of file diff --git a/.workflow/versions/v001/design/design_document.json b/.workflow/versions/v001/design/design_document.json new file mode 100644 index 0000000..f54d494 --- /dev/null +++ b/.workflow/versions/v001/design/design_document.json @@ -0,0 +1,1196 @@ +{ + "workflow_version": "v001", + "feature": "a complete to earn app", + "created_at": "2025-12-18", + "status": "draft", + "revision": 1, + "data_models": [ + { + "id": "model_user", + "name": "User", + "description": "User account and authentication", + "file_path": "app/lib/db/schema/user.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique user identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "email", + "type": "string", + "required": true, + "description": "User email address (unique)" + }, + { + "name": "password_hash", + "type": "string", + "required": true, + "description": "Hashed password using bcrypt" + }, + { + "name": "name", + "type": "string", + "required": true, + "description": "User display name" + }, + { + "name": "created_at", + "type": "timestamp", + "required": true, + "description": "Account creation timestamp" + }, + { + "name": "updated_at", + "type": "timestamp", + "required": true, + "description": "Last update timestamp" + } + ], + "indexes": [ + { + "fields": [ + "email" + ], + "unique": true + } + ] + }, + { + "id": "model_points", + "name": "Points", + "description": "Points transaction history", + "file_path": "app/lib/db/schema/points.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique transaction identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "user_id", + "type": "string", + "required": true, + "description": "Foreign key to User" + }, + { + "name": "amount", + "type": "integer", + "required": true, + "description": "Points amount (positive for earned, negative for spent)" + }, + { + "name": "type", + "type": "enum", + "required": true, + "description": "Transaction type", + "enum_values": [ + "earned", + "spent" + ] + }, + { + "name": "source", + "type": "string", + "required": true, + "description": "Source of points (task_id, purchase_id, etc.)" + }, + { + "name": "created_at", + "type": "timestamp", + "required": true, + "description": "Transaction timestamp" + } + ], + "indexes": [ + { + "fields": [ + "user_id", + "created_at" + ], + "unique": false + } + ] + }, + { + "id": "model_task", + "name": "Task", + "description": "Available tasks for earning points", + "file_path": "app/lib/db/schema/task.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique task identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "type", + "type": "enum", + "required": true, + "description": "Task category", + "enum_values": [ + "checkin", + "ad", + "survey", + "referral", + "quiz", + "video", + "reading" + ] + }, + { + "name": "title", + "type": "string", + "required": true, + "description": "Task display title" + }, + { + "name": "description", + "type": "string", + "required": true, + "description": "Task description" + }, + { + "name": "points_reward", + "type": "integer", + "required": true, + "description": "Points awarded upon completion" + }, + { + "name": "is_active", + "type": "boolean", + "required": true, + "description": "Whether task is currently available" + } + ], + "indexes": [ + { + "fields": [ + "type", + "is_active" + ], + "unique": false + } + ] + }, + { + "id": "model_user_task", + "name": "UserTask", + "description": "User task completion records", + "file_path": "app/lib/db/schema/user_task.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique completion record identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "user_id", + "type": "string", + "required": true, + "description": "Foreign key to User" + }, + { + "name": "task_id", + "type": "string", + "required": true, + "description": "Foreign key to Task" + }, + { + "name": "completed_at", + "type": "timestamp", + "required": true, + "description": "Task completion timestamp" + }, + { + "name": "points_earned", + "type": "integer", + "required": true, + "description": "Points awarded for this completion" + } + ], + "indexes": [ + { + "fields": [ + "user_id", + "task_id", + "completed_at" + ], + "unique": false + } + ] + }, + { + "id": "model_badge", + "name": "Badge", + "description": "Available badges and achievements", + "file_path": "app/lib/db/schema/badge.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique badge identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "name", + "type": "string", + "required": true, + "description": "Badge display name" + }, + { + "name": "description", + "type": "string", + "required": true, + "description": "Badge description" + }, + { + "name": "icon", + "type": "string", + "required": true, + "description": "Badge icon identifier or URL" + }, + { + "name": "requirement_type", + "type": "enum", + "required": true, + "description": "Type of requirement to earn badge", + "enum_values": [ + "points_total", + "tasks_completed", + "streak_days", + "referrals" + ] + }, + { + "name": "requirement_value", + "type": "integer", + "required": true, + "description": "Threshold value for requirement" + } + ] + }, + { + "id": "model_user_badge", + "name": "UserBadge", + "description": "User earned badges", + "file_path": "app/lib/db/schema/user_badge.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique user badge identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "user_id", + "type": "string", + "required": true, + "description": "Foreign key to User" + }, + { + "name": "badge_id", + "type": "string", + "required": true, + "description": "Foreign key to Badge" + }, + { + "name": "earned_at", + "type": "timestamp", + "required": true, + "description": "Badge earned timestamp" + } + ], + "indexes": [ + { + "fields": [ + "user_id", + "badge_id" + ], + "unique": true + } + ] + }, + { + "id": "model_quiz", + "name": "Quiz", + "description": "Quiz questions for learning tasks", + "file_path": "app/lib/db/schema/quiz.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique quiz identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "task_id", + "type": "string", + "required": true, + "description": "Foreign key to Task" + }, + { + "name": "question", + "type": "string", + "required": true, + "description": "Quiz question text" + }, + { + "name": "options", + "type": "json", + "required": true, + "description": "Array of answer options" + }, + { + "name": "correct_answer", + "type": "string", + "required": true, + "description": "Correct answer identifier" + } + ], + "indexes": [ + { + "fields": [ + "task_id" + ], + "unique": false + } + ] + }, + { + "id": "model_referral", + "name": "Referral", + "description": "Referral tracking and rewards", + "file_path": "app/lib/db/schema/referral.ts", + "status": "PENDING", + "fields": [ + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique referral identifier (UUID)", + "constraints": [ + "primary_key" + ] + }, + { + "name": "referrer_id", + "type": "string", + "required": true, + "description": "Foreign key to User (who referred)" + }, + { + "name": "referred_id", + "type": "string", + "required": true, + "description": "Foreign key to User (who was referred)" + }, + { + "name": "bonus_points", + "type": "integer", + "required": true, + "description": "Points awarded to referrer" + }, + { + "name": "created_at", + "type": "timestamp", + "required": true, + "description": "Referral creation timestamp" + } + ], + "indexes": [ + { + "fields": [ + "referrer_id" + ], + "unique": false + }, + { + "fields": [ + "referred_id" + ], + "unique": true + } + ] + } + ], + "api_endpoints": [ + { + "id": "api_auth_register", + "path": "/api/auth/register", + "method": "POST", + "description": "Create new user account", + "file_path": "app/api/auth/register/route.ts", + "status": "PENDING", + "request_body": { + "email": "string (required)", + "password": "string (required, min 8 chars)", + "name": "string (required)" + }, + "responses": [ + { + "status": 201, + "description": "Success", + "schema": { + "user": { + "id": "string", + "email": "string", + "name": "string" + }, + "token": "string (JWT)" + } + }, + { + "status": 400, + "description": "Error", + "schema": { + "error": "string (validation error or email exists)" + } + } + ] + }, + { + "id": "api_auth_login", + "path": "/api/auth/login", + "method": "POST", + "description": "Login existing user", + "file_path": "app/api/auth/login/route.ts", + "status": "PENDING", + "request_body": { + "email": "string (required)", + "password": "string (required)" + }, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "user": { + "id": "string", + "email": "string", + "name": "string" + }, + "token": "string (JWT)" + } + }, + { + "status": 401, + "description": "Error", + "schema": { + "error": "Invalid credentials" + } + } + ] + }, + { + "id": "api_auth_me", + "path": "/api/auth/me", + "method": "GET", + "description": "Get current authenticated user", + "file_path": "app/api/auth/me/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "id": "string", + "email": "string", + "name": "string", + "created_at": "string (ISO date)" + } + }, + { + "status": 401, + "description": "Error", + "schema": { + "error": "Unauthorized" + } + } + ] + }, + { + "id": "api_users_points", + "path": "/api/users/me/points", + "method": "GET", + "description": "Get user points balance and transaction history", + "file_path": "app/api/users/me/points/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "balance": "integer", + "transactions": [ + { + "id": "string", + "amount": "integer", + "type": "string", + "source": "string", + "created_at": "string (ISO date)" + } + ] + } + } + ] + }, + { + "id": "api_users_badges", + "path": "/api/users/me/badges", + "method": "GET", + "description": "Get user earned badges", + "file_path": "app/api/users/me/badges/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "badges": [ + { + "id": "string", + "name": "string", + "description": "string", + "icon": "string", + "earned_at": "string (ISO date)" + } + ] + } + } + ] + }, + { + "id": "api_tasks_list", + "path": "/api/tasks", + "method": "GET", + "description": "List all available tasks", + "file_path": "app/api/tasks/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "tasks": [ + { + "id": "string", + "type": "string", + "title": "string", + "description": "string", + "points_reward": "integer", + "is_completed_today": "boolean" + } + ] + } + } + ] + }, + { + "id": "api_tasks_checkin", + "path": "/api/tasks/checkin", + "method": "POST", + "description": "Complete daily check-in", + "file_path": "app/api/tasks/checkin/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "points_earned": "integer", + "streak_days": "integer", + "new_balance": "integer" + } + }, + { + "status": 400, + "description": "Error", + "schema": { + "error": "Already checked in today" + } + } + ] + }, + { + "id": "api_tasks_complete", + "path": "/api/tasks/:id/complete", + "method": "POST", + "description": "Complete a specific task", + "file_path": "app/api/tasks/[id]/complete/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "points_earned": "integer", + "new_balance": "integer" + } + }, + { + "status": 400, + "description": "Error", + "schema": { + "error": "string (task not found or already completed)" + } + } + ] + }, + { + "id": "api_quizzes_get", + "path": "/api/quizzes/:taskId", + "method": "GET", + "description": "Get quiz questions for a task", + "file_path": "app/api/quizzes/[taskId]/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "questions": [ + { + "id": "string", + "question": "string", + "options": [ + "string" + ] + } + ] + } + } + ] + }, + { + "id": "api_quizzes_submit", + "path": "/api/quizzes/:taskId/submit", + "method": "POST", + "description": "Submit quiz answers", + "file_path": "app/api/quizzes/[taskId]/submit/route.ts", + "status": "PENDING", + "auth_required": true, + "request_body": { + "answers": "object (question_id -> answer mapping)" + }, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "score": "integer (0-100)", + "passed": "boolean", + "points_earned": "integer", + "new_balance": "integer" + } + } + ] + }, + { + "id": "api_leaderboard", + "path": "/api/leaderboard", + "method": "GET", + "description": "Get top users by points", + "file_path": "app/api/leaderboard/route.ts", + "status": "PENDING", + "auth_required": true, + "query_params": { + "limit": "integer (optional, default 100)" + }, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "leaderboard": [ + { + "rank": "integer", + "user_id": "string", + "name": "string", + "total_points": "integer" + } + ], + "current_user_rank": "integer" + } + } + ] + }, + { + "id": "api_referrals_create", + "path": "/api/referrals", + "method": "POST", + "description": "Create referral code", + "file_path": "app/api/referrals/route.ts", + "status": "PENDING", + "auth_required": true, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "referral_code": "string", + "referral_url": "string" + } + } + ] + }, + { + "id": "api_referrals_claim", + "path": "/api/referrals/claim", + "method": "POST", + "description": "Claim referral bonus", + "file_path": "app/api/referrals/claim/route.ts", + "status": "PENDING", + "auth_required": true, + "request_body": { + "referral_code": "string (required)" + }, + "responses": [ + { + "status": 200, + "description": "Success", + "schema": { + "bonus_points": "integer", + "new_balance": "integer" + } + }, + { + "status": 400, + "description": "Error", + "schema": { + "error": "string (invalid code or already used)" + } + } + ] + } + ], + "pages": [ + { + "id": "page_login", + "name": "Login Page", + "description": "User login page", + "file_path": "app/login/page.tsx", + "status": "PENDING", + "components_used": [ + "AuthForm" + ], + "features": [ + "Email/password login form", + "Link to register page", + "Form validation", + "Error display", + "Redirect to dashboard on success" + ], + "path": "/login" + }, + { + "id": "page_register", + "name": "Register Page", + "description": "User registration page", + "file_path": "app/register/page.tsx", + "status": "PENDING", + "components_used": [ + "AuthForm" + ], + "features": [ + "Registration form with email, password, name", + "Link to login page", + "Form validation (password min 8 chars)", + "Error display", + "Redirect to dashboard on success" + ], + "path": "/register" + }, + { + "id": "page_dashboard", + "name": "Dashboard", + "description": "Main dashboard with overview", + "file_path": "app/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "PointsDisplay", + "TaskCard", + "DailyCheckinButton", + "BadgeCard", + "Navbar" + ], + "features": [ + "Points balance display", + "Daily check-in button with streak", + "Quick task overview (3-5 featured tasks)", + "Recent badges earned", + "Navigation to other sections" + ], + "path": "/" + }, + { + "id": "page_tasks", + "name": "Tasks Page", + "description": "List of all available tasks", + "file_path": "app/tasks/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "TaskList", + "TaskCard", + "Navbar" + ], + "features": [ + "List all available tasks", + "Filter by task type", + "Show completion status", + "Click to complete or view task", + "Points reward display" + ], + "path": "/tasks" + }, + { + "id": "page_quiz", + "name": "Quiz Page", + "description": "Quiz interface for learning tasks", + "file_path": "app/quiz/[id]/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "QuizQuestion", + "Navbar" + ], + "features": [ + "Display quiz questions one by one", + "Multiple choice answers", + "Submit button", + "Score display after completion", + "Points earned display" + ], + "path": "/quiz/[id]" + }, + { + "id": "page_profile", + "name": "Profile Page", + "description": "User profile with points history", + "file_path": "app/profile/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "PointsDisplay", + "TransactionHistory", + "BadgeCard", + "Navbar" + ], + "features": [ + "User info display", + "Total points balance", + "Points transaction history", + "Earned badges display", + "Account statistics" + ], + "path": "/profile" + }, + { + "id": "page_leaderboard", + "name": "Leaderboard Page", + "description": "Rankings of top users", + "file_path": "app/leaderboard/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "LeaderboardTable", + "Navbar" + ], + "features": [ + "Top 100 users by points", + "User rank display", + "Current user highlight", + "Points totals", + "Update in real-time" + ], + "path": "/leaderboard" + }, + { + "id": "page_referral", + "name": "Referral Page", + "description": "Referral code sharing", + "file_path": "app/referral/page.tsx", + "status": "PENDING", + "auth_required": true, + "components_used": [ + "Navbar" + ], + "features": [ + "Generate referral code", + "Display shareable link", + "Copy to clipboard button", + "Show referral statistics", + "Bonus points display" + ], + "path": "/referral" + } + ], + "components": [ + { + "id": "component_auth_form", + "name": "AuthForm", + "description": "Reusable login/register form component", + "file_path": "app/components/AuthForm.tsx", + "status": "PENDING", + "props": { + "mode": "'login' | 'register'", + "onSubmit": "(data: AuthData) => Promise", + "error": "string | null" + }, + "features": [ + "Email and password inputs", + "Name input (register mode only)", + "Client-side validation", + "Error display", + "Loading state", + "Submit button" + ] + }, + { + "id": "component_points_display", + "name": "PointsDisplay", + "description": "Display current points balance", + "file_path": "app/components/PointsDisplay.tsx", + "status": "PENDING", + "props": { + "balance": "number", + "showAnimation": "boolean (optional)" + }, + "features": [ + "Large points number display", + "Coin/points icon", + "Optional animation on change", + "Dark theme styling" + ] + }, + { + "id": "component_task_card", + "name": "TaskCard", + "description": "Display a single task", + "file_path": "app/components/TaskCard.tsx", + "status": "PENDING", + "props": { + "task": "Task object", + "onComplete": "() => void", + "isCompleted": "boolean" + }, + "features": [ + "Task title and description", + "Points reward badge", + "Task type icon", + "Completion status", + "Complete button", + "Dark theme card styling" + ] + }, + { + "id": "component_task_list", + "name": "TaskList", + "description": "List of task cards with filtering", + "file_path": "app/components/TaskList.tsx", + "status": "PENDING", + "props": { + "tasks": "Task[]", + "onTaskComplete": "(taskId: string) => void" + }, + "features": [ + "Grid layout of TaskCard components", + "Filter by task type", + "Empty state message", + "Loading state" + ] + }, + { + "id": "component_quiz_question", + "name": "QuizQuestion", + "description": "Quiz question with multiple choice", + "file_path": "app/components/QuizQuestion.tsx", + "status": "PENDING", + "props": { + "question": "string", + "options": "string[]", + "onAnswer": "(answer: string) => void", + "selectedAnswer": "string | null" + }, + "features": [ + "Question text display", + "Radio button options", + "Highlight selected answer", + "Dark theme styling" + ] + }, + { + "id": "component_badge_card", + "name": "BadgeCard", + "description": "Display a badge or achievement", + "file_path": "app/components/BadgeCard.tsx", + "status": "PENDING", + "props": { + "badge": "Badge object", + "earned": "boolean", + "earnedAt": "Date | null" + }, + "features": [ + "Badge icon display", + "Badge name and description", + "Earned/locked state", + "Earned date (if earned)", + "Tooltip with requirement" + ] + }, + { + "id": "component_leaderboard_table", + "name": "LeaderboardTable", + "description": "Rankings table", + "file_path": "app/components/LeaderboardTable.tsx", + "status": "PENDING", + "props": { + "entries": "LeaderboardEntry[]", + "currentUserId": "string" + }, + "features": [ + "Table with rank, name, points columns", + "Highlight current user row", + "Top 3 special styling", + "Responsive design", + "Dark theme table styling" + ] + }, + { + "id": "component_daily_checkin_button", + "name": "DailyCheckinButton", + "description": "Check-in button with streak display", + "file_path": "app/components/DailyCheckinButton.tsx", + "status": "PENDING", + "props": { + "onCheckIn": "() => Promise", + "isCheckedInToday": "boolean", + "streakDays": "number" + }, + "features": [ + "Large prominent button", + "Streak counter display", + "Disabled state if already checked in", + "Success animation", + "Points earned display" + ] + }, + { + "id": "component_transaction_history", + "name": "TransactionHistory", + "description": "List of points transactions", + "file_path": "app/components/TransactionHistory.tsx", + "status": "PENDING", + "props": { + "transactions": "Transaction[]" + }, + "features": [ + "List of transactions with date, amount, source", + "Color coding (green for earned, red for spent)", + "Pagination or infinite scroll", + "Empty state message", + "Dark theme styling" + ] + }, + { + "id": "component_navbar", + "name": "Navbar", + "description": "Main navigation bar", + "file_path": "app/components/Navbar.tsx", + "status": "PENDING", + "props": { + "currentPath": "string" + }, + "features": [ + "Links to Dashboard, Tasks, Leaderboard, Profile, Referral", + "Active link highlighting", + "User menu with logout", + "Points balance display", + "Responsive mobile menu", + "Dark theme styling" + ] + }, + { + "id": "component_dark_theme_layout", + "name": "DarkThemeLayout", + "description": "Root layout with dark theme", + "file_path": "app/components/DarkThemeLayout.tsx", + "status": "PENDING", + "props": { + "children": "React.ReactNode" + }, + "features": [ + "Dark background colors", + "Global dark theme CSS variables", + "Consistent spacing and typography", + "Tailwind CSS dark mode utilities" + ] + } + ], + "technical_notes": { + "authentication": { + "method": "JWT tokens", + "storage": "HTTP-only cookies", + "library": "jsonwebtoken + bcrypt" + }, + "database": { + "type": "PostgreSQL (recommended) or SQLite for dev", + "orm": "Prisma", + "migrations": "Prisma migrate" + }, + "styling": { + "framework": "Tailwind CSS 4", + "theme": "Dark mode by default", + "responsive": "Mobile-first design" + }, + "state_management": { + "method": "React Context API or Zustand", + "server_state": "SWR or TanStack Query" + }, + "validation": { + "frontend": "Zod schemas", + "backend": "Zod schemas (shared)" + } + } +} \ No newline at end of file diff --git a/.workflow/versions/v001/gate_state.yml b/.workflow/versions/v001/gate_state.yml new file mode 100644 index 0000000..ce42599 --- /dev/null +++ b/.workflow/versions/v001/gate_state.yml @@ -0,0 +1,156 @@ +{ + "version": "v001", + "current_phase": "INITIALIZING", + "created_at": "2025-12-18T02:39:33.559779", + "updated_at": "2025-12-18T02:39:50.747171", + "phases": { + "INITIALIZING": { + "status": "completed", + "entered_at": "2025-12-18T02:39:33.559790", + "completed_at": "2025-12-18T02:39:50.747165", + "checkpoints": { + "manifest_exists": { + "status": "passed", + "at": "2025-12-18T02:39:33.775898", + "data": {} + }, + "version_created": { + "status": "passed", + "at": "2025-12-18T02:39:50.713177", + "data": {} + } + } + }, + "DESIGNING": { + "status": "not_started", + "entered_at": null, + "completed_at": null, + "checkpoints": { + "design_document_created": { + "status": "pending", + "at": null, + "data": {} + }, + "design_validated": { + "status": "pending", + "at": null, + "data": {} + }, + "tasks_generated": { + "status": "pending", + "at": null, + "data": {} + } + } + }, + "AWAITING_DESIGN_APPROVAL": { + "status": "not_started", + "entered_at": null, + "completed_at": null, + "checkpoints": { + "design_approved": { + "status": "pending", + "at": null, + "data": {} + } + } + }, + "IMPLEMENTING": { + "status": "not_started", + "entered_at": null, + "completed_at": null, + "checkpoints": { + "all_layers_complete": { + "status": "pending", + "at": null, + "data": {} + }, + "build_passes": { + "status": "pending", + "at": null, + "data": {} + } + } + }, + "REVIEWING": { + "status": "not_started", + "entered_at": null, + "completed_at": null, + "checkpoints": { + "review_script_run": { + "status": "pending", + "at": null, + "data": {} + }, + "all_files_verified": { + "status": "pending", + "at": null, + "data": {} + }, + "review_passed": { + "status": "pending", + "at": null, + "data": {} + } + } + }, + "SECURITY_REVIEW": { + "status": "not_started", + "entered_at": null, + "completed_at": null, + "checkpoints": { + "security_scan_run": { + "status": "pending", + "at": null, + "data": {} + }, + "api_contract_validated": { + "status": "pending", + "at": null, + "data": {} + }, + "security_passed": { + "status": "pending", + "at": null, + "data": {} + } + } + }, + "AWAITING_IMPL_APPROVAL": { + "status": "not_started", + "entered_at": null, + "completed_at": null, + "checkpoints": { + "implementation_approved": { + "status": "pending", + "at": null, + "data": {} + } + } + }, + "COMPLETING": { + "status": "not_started", + "entered_at": null, + "completed_at": null, + "checkpoints": { + "tasks_marked_complete": { + "status": "pending", + "at": null, + "data": {} + }, + "version_finalized": { + "status": "pending", + "at": null, + "data": {} + } + } + }, + "COMPLETED": { + "status": "not_started", + "entered_at": null, + "completed_at": null, + "checkpoints": {} + } + }, + "fix_loops": [] +} \ No newline at end of file diff --git a/.workflow/versions/v001/modified_files.json b/.workflow/versions/v001/modified_files.json new file mode 100644 index 0000000..0ce7e96 --- /dev/null +++ b/.workflow/versions/v001/modified_files.json @@ -0,0 +1,77 @@ +[ + { + "path": "pp/page.tsx", + "action": "modified", + "summary": "" + }, + { + "path": ".claude/", + "action": "untracked", + "summary": "" + }, + { + "path": ".eureka-active-session", + "action": "untracked", + "summary": "" + }, + { + "path": ".mcp.json", + "action": "untracked", + "summary": "" + }, + { + "path": ".workflow/", + "action": "untracked", + "summary": "" + }, + { + "path": "CLAUDE.md", + "action": "untracked", + "summary": "" + }, + { + "path": "app/api/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/components/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/lib/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/login/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/register/", + "action": "untracked", + "summary": "" + }, + { + "path": "app/tasks/", + "action": "untracked", + "summary": "" + }, + { + "path": "project_manifest.json", + "action": "untracked", + "summary": "" + }, + { + "path": "skills/", + "action": "untracked", + "summary": "" + }, + { + "path": "start-workflow.sh", + "action": "untracked", + "summary": "" + } +] \ No newline at end of file diff --git a/.workflow/versions/v001/requirements/summary.json b/.workflow/versions/v001/requirements/summary.json new file mode 100644 index 0000000..e2a2243 --- /dev/null +++ b/.workflow/versions/v001/requirements/summary.json @@ -0,0 +1,58 @@ +{ + "feature": "a complete to earn app", + "gathered_at": "2025-12-18", + "mode": "auto", + "requirements": { + "earning_mechanisms": { + "task_based": true, + "learning_based": true, + "game_based": false, + "activity_based": false, + "task_types": { + "daily_checkins": true, + "watch_ads": true, + "surveys": true, + "referrals": true + }, + "learning_types": { + "quizzes": true, + "video_lessons": true, + "reading_challenges": true, + "flashcards": false + } + }, + "rewards": { + "points_coins": true, + "badges_achievements": true, + "gift_cards": false, + "cryptocurrency": false + }, + "authentication": { + "email_password": true, + "social_login": false, + "anonymous": false + }, + "user_profile": { + "points_balance_history": true, + "leaderboard": true, + "level_xp_system": false, + "badge_showcase": false + }, + "ui_style": "modern_dark_theme" + }, + "acceptance_criteria": [ + "User can register with email and password", + "User can log in and view their dashboard", + "User can complete daily check-in to earn points", + "User can watch video ads to earn points", + "User can complete surveys to earn points", + "User can refer friends and earn bonus points", + "User can complete quiz challenges to earn points", + "User can watch educational videos to earn points", + "User can read articles and complete challenges to earn points", + "User can view their points balance and transaction history", + "User can earn badges for achievements", + "User can see their position on the leaderboard", + "App has modern dark theme UI" + ] +} diff --git a/.workflow/versions/v001/resume_prompt.md b/.workflow/versions/v001/resume_prompt.md new file mode 100644 index 0000000..791a277 --- /dev/null +++ b/.workflow/versions/v001/resume_prompt.md @@ -0,0 +1,41 @@ +## Context Recovery - Resuming Previous Session + +### Session Info +- **Original Session**: compact_20251218_021138 +- **Captured At**: 2025-12-18T02:11:38.618766 +- **Context Usage**: 85.0% + +### Workflow Position +- **Phase**: UNKNOWN +- **Active Task**: None +- **Layer**: 1 + +### What Was Being Worked On +- **Entity**: () +- **Action**: pending +- **File**: None +- **Progress**: + +### Next Actions (Priority Order) + +### Recent Changes +- `pp/page.tsx` - modified: +- `.claude/` - untracked: +- `.eureka-active-session` - untracked: +- `.mcp.json` - untracked: +- `.workflow/` - untracked: +- `CLAUDE.md` - untracked: +- `app/api/` - untracked: +- `app/components/` - untracked: +- `app/lib/` - untracked: +- `app/login/` - untracked: +- `app/register/` - untracked: +- `app/tasks/` - untracked: +- `project_manifest.json` - untracked: +- `skills/` - untracked: +- `start-workflow.sh` - untracked: + +--- +**Action Required**: Continue from the next action listed above. + +To load full context, read the following files: \ No newline at end of file diff --git a/.workflow/versions/v001/session.yml b/.workflow/versions/v001/session.yml new file mode 100644 index 0000000..e74c545 --- /dev/null +++ b/.workflow/versions/v001/session.yml @@ -0,0 +1,35 @@ +{ + "version": "v001", + "feature": "a complete to earn app", + "session_id": "workflow_20251218_013734", + "parent_version": null, + "status": "completed", + "started_at": "2025-12-18T01:37:34.999814", + "completed_at": "2025-12-18T02:16:34.986099", + "current_phase": "COMPLETED", + "approvals": { + "design": { + "status": "approved", + "approved_by": "auto", + "approved_at": "2025-12-18T01:55:00Z", + "rejection_reason": null + }, + "implementation": { + "status": "approved", + "approved_by": "auto", + "approved_at": "2025-12-18T02:16:34.986102", + "rejection_reason": null + } + }, + "task_sessions": [], + "summary": { + "total_tasks": 40, + "tasks_completed": 40, + "entities_created": 40, + "entities_updated": 0, + "entities_deleted": 0, + "files_created": 40, + "files_updated": 0, + "files_deleted": 0 + } +} \ No newline at end of file diff --git a/.workflow/versions/v001/snapshot_before/manifest.json b/.workflow/versions/v001/snapshot_before/manifest.json new file mode 100644 index 0000000..b53d657 --- /dev/null +++ b/.workflow/versions/v001/snapshot_before/manifest.json @@ -0,0 +1,23 @@ +{ + "project": { + "name": "todo-super", + "version": "0.1.0", + "description": "A complete earn-to-play app", + "tech_stack": { + "framework": "Next.js 16", + "language": "TypeScript", + "styling": "Tailwind CSS 4", + "runtime": "React 19" + } + }, + "state": { + "current_phase": "DESIGN_PHASE", + "last_updated": "2025-12-18" + }, + "entities": { + "components": [], + "pages": [], + "api_endpoints": [], + "data_models": [] + } +} diff --git a/.workflow/versions/v001/tasks/task_create_api_auth_login.yml b/.workflow/versions/v001/tasks/task_create_api_auth_login.yml new file mode 100644 index 0000000..946dd00 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_auth_login.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_auth_login", + "type": "create", + "title": "Create api_auth_login", + "agent": "backend", + "entity_id": "api_auth_login", + "entity_ids": [ + "api_auth_login" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_auth_login.yml" + }, + "created_at": "2025-12-18T01:57:52.724887" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_auth_me.yml b/.workflow/versions/v001/tasks/task_create_api_auth_me.yml new file mode 100644 index 0000000..9417404 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_auth_me.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_auth_me", + "type": "create", + "title": "Create api_auth_me", + "agent": "backend", + "entity_id": "api_auth_me", + "entity_ids": [ + "api_auth_me" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_auth_me.yml" + }, + "created_at": "2025-12-18T01:57:52.724946" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_auth_register.yml b/.workflow/versions/v001/tasks/task_create_api_auth_register.yml new file mode 100644 index 0000000..f25186c --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_auth_register.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_auth_register", + "type": "create", + "title": "Create api_auth_register", + "agent": "backend", + "entity_id": "api_auth_register", + "entity_ids": [ + "api_auth_register" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_auth_register.yml" + }, + "created_at": "2025-12-18T01:57:52.725003" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_leaderboard.yml b/.workflow/versions/v001/tasks/task_create_api_leaderboard.yml new file mode 100644 index 0000000..1bcae56 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_leaderboard.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_leaderboard", + "type": "create", + "title": "Create api_leaderboard", + "agent": "backend", + "entity_id": "api_leaderboard", + "entity_ids": [ + "api_leaderboard" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_leaderboard.yml" + }, + "created_at": "2025-12-18T01:57:52.725061" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_quizzes_get.yml b/.workflow/versions/v001/tasks/task_create_api_quizzes_get.yml new file mode 100644 index 0000000..d9c8019 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_quizzes_get.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_quizzes_get", + "type": "create", + "title": "Create api_quizzes_get", + "agent": "backend", + "entity_id": "api_quizzes_get", + "entity_ids": [ + "api_quizzes_get" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_quizzes_get.yml" + }, + "created_at": "2025-12-18T01:57:52.725120" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_quizzes_submit.yml b/.workflow/versions/v001/tasks/task_create_api_quizzes_submit.yml new file mode 100644 index 0000000..20701cc --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_quizzes_submit.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_quizzes_submit", + "type": "create", + "title": "Create api_quizzes_submit", + "agent": "backend", + "entity_id": "api_quizzes_submit", + "entity_ids": [ + "api_quizzes_submit" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_quizzes_submit.yml" + }, + "created_at": "2025-12-18T01:57:52.725176" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_referrals_claim.yml b/.workflow/versions/v001/tasks/task_create_api_referrals_claim.yml new file mode 100644 index 0000000..9d0a0a6 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_referrals_claim.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_referrals_claim", + "type": "create", + "title": "Create api_referrals_claim", + "agent": "backend", + "entity_id": "api_referrals_claim", + "entity_ids": [ + "api_referrals_claim" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_referrals_claim.yml" + }, + "created_at": "2025-12-18T01:57:52.725232" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_referrals_create.yml b/.workflow/versions/v001/tasks/task_create_api_referrals_create.yml new file mode 100644 index 0000000..89b49e1 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_referrals_create.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_referrals_create", + "type": "create", + "title": "Create api_referrals_create", + "agent": "backend", + "entity_id": "api_referrals_create", + "entity_ids": [ + "api_referrals_create" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_referrals_create.yml" + }, + "created_at": "2025-12-18T01:57:52.725285" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_tasks_checkin.yml b/.workflow/versions/v001/tasks/task_create_api_tasks_checkin.yml new file mode 100644 index 0000000..79d2eac --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_tasks_checkin.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_tasks_checkin", + "type": "create", + "title": "Create api_tasks_checkin", + "agent": "backend", + "entity_id": "api_tasks_checkin", + "entity_ids": [ + "api_tasks_checkin" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_tasks_checkin.yml" + }, + "created_at": "2025-12-18T01:57:52.725338" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_tasks_complete.yml b/.workflow/versions/v001/tasks/task_create_api_tasks_complete.yml new file mode 100644 index 0000000..f5f388d --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_tasks_complete.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_tasks_complete", + "type": "create", + "title": "Create api_tasks_complete", + "agent": "backend", + "entity_id": "api_tasks_complete", + "entity_ids": [ + "api_tasks_complete" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_tasks_complete.yml" + }, + "created_at": "2025-12-18T01:57:52.725390" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_tasks_list.yml b/.workflow/versions/v001/tasks/task_create_api_tasks_list.yml new file mode 100644 index 0000000..36dc401 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_tasks_list.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_tasks_list", + "type": "create", + "title": "Create api_tasks_list", + "agent": "backend", + "entity_id": "api_tasks_list", + "entity_ids": [ + "api_tasks_list" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_tasks_list.yml" + }, + "created_at": "2025-12-18T01:57:52.725451" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_users_badges.yml b/.workflow/versions/v001/tasks/task_create_api_users_badges.yml new file mode 100644 index 0000000..55b7f72 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_users_badges.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_users_badges", + "type": "create", + "title": "Create api_users_badges", + "agent": "backend", + "entity_id": "api_users_badges", + "entity_ids": [ + "api_users_badges" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_users_badges.yml" + }, + "created_at": "2025-12-18T01:57:52.725510" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_api_users_points.yml b/.workflow/versions/v001/tasks/task_create_api_users_points.yml new file mode 100644 index 0000000..37bc1eb --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_api_users_points.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_api_users_points", + "type": "create", + "title": "Create api_users_points", + "agent": "backend", + "entity_id": "api_users_points", + "entity_ids": [ + "api_users_points" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/api_users_points.yml" + }, + "created_at": "2025-12-18T01:57:52.725565" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_auth_form.yml b/.workflow/versions/v001/tasks/task_create_component_auth_form.yml new file mode 100644 index 0000000..f4792a8 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_auth_form.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_auth_form", + "type": "create", + "title": "Create AuthForm", + "agent": "frontend", + "entity_id": "component_auth_form", + "entity_ids": [ + "component_auth_form" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_auth_form.yml" + }, + "created_at": "2025-12-18T01:57:52.725622" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_badge_card.yml b/.workflow/versions/v001/tasks/task_create_component_badge_card.yml new file mode 100644 index 0000000..5b230fc --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_badge_card.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_badge_card", + "type": "create", + "title": "Create BadgeCard", + "agent": "frontend", + "entity_id": "component_badge_card", + "entity_ids": [ + "component_badge_card" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_badge_card.yml" + }, + "created_at": "2025-12-18T01:57:52.725677" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_daily_checkin_button.yml b/.workflow/versions/v001/tasks/task_create_component_daily_checkin_button.yml new file mode 100644 index 0000000..4400a14 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_daily_checkin_button.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_daily_checkin_button", + "type": "create", + "title": "Create DailyCheckinButton", + "agent": "frontend", + "entity_id": "component_daily_checkin_button", + "entity_ids": [ + "component_daily_checkin_button" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_daily_checkin_button.yml" + }, + "created_at": "2025-12-18T01:57:52.725731" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_dark_theme_layout.yml b/.workflow/versions/v001/tasks/task_create_component_dark_theme_layout.yml new file mode 100644 index 0000000..8ece217 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_dark_theme_layout.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_dark_theme_layout", + "type": "create", + "title": "Create DarkThemeLayout", + "agent": "frontend", + "entity_id": "component_dark_theme_layout", + "entity_ids": [ + "component_dark_theme_layout" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_dark_theme_layout.yml" + }, + "created_at": "2025-12-18T01:57:52.725786" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_leaderboard_table.yml b/.workflow/versions/v001/tasks/task_create_component_leaderboard_table.yml new file mode 100644 index 0000000..4ca5241 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_leaderboard_table.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_leaderboard_table", + "type": "create", + "title": "Create LeaderboardTable", + "agent": "frontend", + "entity_id": "component_leaderboard_table", + "entity_ids": [ + "component_leaderboard_table" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_leaderboard_table.yml" + }, + "created_at": "2025-12-18T01:57:52.725839" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_navbar.yml b/.workflow/versions/v001/tasks/task_create_component_navbar.yml new file mode 100644 index 0000000..907c79e --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_navbar.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_navbar", + "type": "create", + "title": "Create Navbar", + "agent": "frontend", + "entity_id": "component_navbar", + "entity_ids": [ + "component_navbar" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_navbar.yml" + }, + "created_at": "2025-12-18T01:57:52.725894" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_points_display.yml b/.workflow/versions/v001/tasks/task_create_component_points_display.yml new file mode 100644 index 0000000..5cdea17 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_points_display.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_points_display", + "type": "create", + "title": "Create PointsDisplay", + "agent": "frontend", + "entity_id": "component_points_display", + "entity_ids": [ + "component_points_display" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_points_display.yml" + }, + "created_at": "2025-12-18T01:57:52.725948" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_quiz_question.yml b/.workflow/versions/v001/tasks/task_create_component_quiz_question.yml new file mode 100644 index 0000000..9b2fdae --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_quiz_question.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_quiz_question", + "type": "create", + "title": "Create QuizQuestion", + "agent": "frontend", + "entity_id": "component_quiz_question", + "entity_ids": [ + "component_quiz_question" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_quiz_question.yml" + }, + "created_at": "2025-12-18T01:57:52.726006" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_task_card.yml b/.workflow/versions/v001/tasks/task_create_component_task_card.yml new file mode 100644 index 0000000..aa8dca0 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_task_card.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_task_card", + "type": "create", + "title": "Create TaskCard", + "agent": "frontend", + "entity_id": "component_task_card", + "entity_ids": [ + "component_task_card" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_task_card.yml" + }, + "created_at": "2025-12-18T01:57:52.726063" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_task_list.yml b/.workflow/versions/v001/tasks/task_create_component_task_list.yml new file mode 100644 index 0000000..0d13e7c --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_task_list.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_task_list", + "type": "create", + "title": "Create TaskList", + "agent": "frontend", + "entity_id": "component_task_list", + "entity_ids": [ + "component_task_list" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_task_list.yml" + }, + "created_at": "2025-12-18T01:57:52.726122" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_component_transaction_history.yml b/.workflow/versions/v001/tasks/task_create_component_transaction_history.yml new file mode 100644 index 0000000..ff5e6cf --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_component_transaction_history.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_component_transaction_history", + "type": "create", + "title": "Create TransactionHistory", + "agent": "frontend", + "entity_id": "component_transaction_history", + "entity_ids": [ + "component_transaction_history" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/component_transaction_history.yml" + }, + "created_at": "2025-12-18T01:57:52.726177" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_model_badge.yml b/.workflow/versions/v001/tasks/task_create_model_badge.yml new file mode 100644 index 0000000..963f20e --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_model_badge.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_model_badge", + "type": "create", + "title": "Create Badge", + "agent": "backend", + "entity_id": "model_badge", + "entity_ids": [ + "model_badge" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/model_badge.yml" + }, + "created_at": "2025-12-18T01:57:52.726234" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_model_points.yml b/.workflow/versions/v001/tasks/task_create_model_points.yml new file mode 100644 index 0000000..e46553a --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_model_points.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_model_points", + "type": "create", + "title": "Create Points", + "agent": "backend", + "entity_id": "model_points", + "entity_ids": [ + "model_points" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/model_points.yml" + }, + "created_at": "2025-12-18T01:57:52.726290" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_model_quiz.yml b/.workflow/versions/v001/tasks/task_create_model_quiz.yml new file mode 100644 index 0000000..33ac3c3 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_model_quiz.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_model_quiz", + "type": "create", + "title": "Create Quiz", + "agent": "backend", + "entity_id": "model_quiz", + "entity_ids": [ + "model_quiz" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/model_quiz.yml" + }, + "created_at": "2025-12-18T01:57:52.726345" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_model_referral.yml b/.workflow/versions/v001/tasks/task_create_model_referral.yml new file mode 100644 index 0000000..e817177 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_model_referral.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_model_referral", + "type": "create", + "title": "Create Referral", + "agent": "backend", + "entity_id": "model_referral", + "entity_ids": [ + "model_referral" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/model_referral.yml" + }, + "created_at": "2025-12-18T01:57:52.726397" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_model_task.yml b/.workflow/versions/v001/tasks/task_create_model_task.yml new file mode 100644 index 0000000..aebcd78 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_model_task.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_model_task", + "type": "create", + "title": "Create Task", + "agent": "backend", + "entity_id": "model_task", + "entity_ids": [ + "model_task" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/model_task.yml" + }, + "created_at": "2025-12-18T01:57:52.726452" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_model_user.yml b/.workflow/versions/v001/tasks/task_create_model_user.yml new file mode 100644 index 0000000..1540c64 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_model_user.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_model_user", + "type": "create", + "title": "Create User", + "agent": "backend", + "entity_id": "model_user", + "entity_ids": [ + "model_user" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/model_user.yml" + }, + "created_at": "2025-12-18T01:57:52.726509" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_model_user_badge.yml b/.workflow/versions/v001/tasks/task_create_model_user_badge.yml new file mode 100644 index 0000000..3e9395e --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_model_user_badge.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_model_user_badge", + "type": "create", + "title": "Create UserBadge", + "agent": "backend", + "entity_id": "model_user_badge", + "entity_ids": [ + "model_user_badge" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/model_user_badge.yml" + }, + "created_at": "2025-12-18T01:57:52.726565" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_model_user_task.yml b/.workflow/versions/v001/tasks/task_create_model_user_task.yml new file mode 100644 index 0000000..823f0ca --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_model_user_task.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_model_user_task", + "type": "create", + "title": "Create UserTask", + "agent": "backend", + "entity_id": "model_user_task", + "entity_ids": [ + "model_user_task" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/model_user_task.yml" + }, + "created_at": "2025-12-18T01:57:52.726620" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_page_dashboard.yml b/.workflow/versions/v001/tasks/task_create_page_dashboard.yml new file mode 100644 index 0000000..f0af8a2 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_page_dashboard.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_page_dashboard", + "type": "create", + "title": "Create Dashboard", + "agent": "frontend", + "entity_id": "page_dashboard", + "entity_ids": [ + "page_dashboard" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/page_dashboard.yml" + }, + "created_at": "2025-12-18T01:57:52.726674" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_page_leaderboard.yml b/.workflow/versions/v001/tasks/task_create_page_leaderboard.yml new file mode 100644 index 0000000..46518a3 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_page_leaderboard.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_page_leaderboard", + "type": "create", + "title": "Create Leaderboard Page", + "agent": "frontend", + "entity_id": "page_leaderboard", + "entity_ids": [ + "page_leaderboard" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/page_leaderboard.yml" + }, + "created_at": "2025-12-18T01:57:52.726730" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_page_login.yml b/.workflow/versions/v001/tasks/task_create_page_login.yml new file mode 100644 index 0000000..2f7d1bd --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_page_login.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_page_login", + "type": "create", + "title": "Create Login Page", + "agent": "frontend", + "entity_id": "page_login", + "entity_ids": [ + "page_login" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/page_login.yml" + }, + "created_at": "2025-12-18T01:57:52.726785" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_page_profile.yml b/.workflow/versions/v001/tasks/task_create_page_profile.yml new file mode 100644 index 0000000..a2434d4 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_page_profile.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_page_profile", + "type": "create", + "title": "Create Profile Page", + "agent": "frontend", + "entity_id": "page_profile", + "entity_ids": [ + "page_profile" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/page_profile.yml" + }, + "created_at": "2025-12-18T01:57:52.726836" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_page_quiz.yml b/.workflow/versions/v001/tasks/task_create_page_quiz.yml new file mode 100644 index 0000000..fa9e33a --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_page_quiz.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_page_quiz", + "type": "create", + "title": "Create Quiz Page", + "agent": "frontend", + "entity_id": "page_quiz", + "entity_ids": [ + "page_quiz" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/page_quiz.yml" + }, + "created_at": "2025-12-18T01:57:52.726888" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_page_referral.yml b/.workflow/versions/v001/tasks/task_create_page_referral.yml new file mode 100644 index 0000000..bae1cdd --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_page_referral.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_page_referral", + "type": "create", + "title": "Create Referral Page", + "agent": "frontend", + "entity_id": "page_referral", + "entity_ids": [ + "page_referral" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/page_referral.yml" + }, + "created_at": "2025-12-18T01:57:52.726940" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_page_register.yml b/.workflow/versions/v001/tasks/task_create_page_register.yml new file mode 100644 index 0000000..68a4b22 --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_page_register.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_page_register", + "type": "create", + "title": "Create Register Page", + "agent": "frontend", + "entity_id": "page_register", + "entity_ids": [ + "page_register" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/page_register.yml" + }, + "created_at": "2025-12-18T01:57:52.726996" +} \ No newline at end of file diff --git a/.workflow/versions/v001/tasks/task_create_page_tasks.yml b/.workflow/versions/v001/tasks/task_create_page_tasks.yml new file mode 100644 index 0000000..117c7bb --- /dev/null +++ b/.workflow/versions/v001/tasks/task_create_page_tasks.yml @@ -0,0 +1,21 @@ +{ + "id": "task_create_page_tasks", + "type": "create", + "title": "Create Tasks Page", + "agent": "frontend", + "entity_id": "page_tasks", + "entity_ids": [ + "page_tasks" + ], + "status": "pending", + "layer": 1, + "parallel_group": "layer_1", + "complexity": "medium", + "dependencies": [], + "context": { + "design_version": 1, + "workflow_version": "v001", + "context_snapshot_path": ".workflow/versions/v001/contexts/page_tasks.yml" + }, + "created_at": "2025-12-18T01:57:52.727050" +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4e6e5d9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,1182 @@ +# Project Instructions + +## Overview + +This project uses the **Guardrail Workflow System** - a hook-enforced development workflow that **prevents AI from bypassing the design-approve-implement cycle**. + +## Workflow Enforcement Architecture + +### How AI is Forced to Follow Workflows + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ HOOK ENFORCEMENT LAYER │ +├─────────────────────────────────────────────────────────────────┤ +│ PreToolUse Hooks intercept ALL file operations: │ +│ │ +│ Write/Edit/MultiEdit → validate_workflow.py → validate_write.py│ +│ Task (agents) → validate_workflow.py (agent restrictions)│ +│ MCP tools → validate_workflow.py (serena, morphllm) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ PHASE STATE MACHINE │ +├─────────────────────────────────────────────────────────────────┤ +│ INITIALIZING → DESIGNING → AWAITING_DESIGN_APPROVAL │ +│ │ │ │ │ +│ │ │ ┌────────┴────────┐ │ +│ │ │ ▼ ▼ │ +│ │ │ DESIGN_APPROVED DESIGN_REJECTED │ +│ │ │ │ │ │ +│ │ │ ▼ │ │ +│ │ │ IMPLEMENTING ←─────────┘ │ +│ │ │ │ │ +│ │ │ ▼ │ +│ │ │ REVIEWING │ +│ │ │ │ │ +│ │ │ ▼ │ +│ │ │ 🔒 SECURITY_REVIEW ←───────┐ │ +│ │ │ │ │ │ +│ │ │ ▼ │ │ +│ │ │ AWAITING_IMPL_APPROVAL │ │ +│ │ │ │ │ │ +│ │ │ ┌─────┴─────┐ │ │ +│ │ │ ▼ ▼ │ │ +│ │ │ IMPL_APPROVED IMPL_REJECTED─┘ │ +│ │ │ │ │ +│ │ │ ▼ │ +│ │ │ COMPLETING │ +│ │ │ │ │ +│ │ │ ▼ │ +│ │ │ COMPLETED │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Tool Restrictions by Phase + +| Phase | Write/Edit | Task (Agents) | Allowed Files | +|-------|------------|---------------|---------------| +| **NO_WORKFLOW** | ✅ Allowed | ✅ Allowed | Any | +| **DESIGNING** | ⛔ Blocked | system-architect only | manifest, .workflow/, tasks/*.yml | +| **AWAITING_DESIGN_APPROVAL** | ⛔ Blocked | ⛔ Blocked | None | +| **IMPLEMENTING** | ✅ Allowed | frontend/backend only | Files in task file_paths | +| **REVIEWING** | ⛔ Blocked | quality-engineer only | None (read-only) | +| **SECURITY_REVIEW** | ⛔ Blocked | security-engineer only | None (read-only) | +| **AWAITING_IMPL_APPROVAL** | ⛔ Blocked | ⛔ Blocked | None | +| **COMPLETED** | ⛔ Blocked | ⛔ Blocked | None | + +### Enforcement Scripts + +| Script | Purpose | Exit Codes | +|--------|---------|------------| +| `validate_workflow.py` | Phase-based blocking | 0=allow, 1=block | +| `validate_write.py` | Manifest path validation | 0=allow, 1=block | +| `workflow_manager.py` | State transitions | Manages .workflow/versions/ | +| `version_manager.py` | Version lifecycle | Creates/completes versions | + +### What Happens When AI Tries to Bypass + +``` +AI attempts: Write tool to create app/components/Button.tsx + +Hook intercepts → validate_workflow.py --operation write --file "app/components/Button.tsx" + +Script checks: + 1. Current phase = DESIGNING + 2. File is NOT in always_allowed list + 3. Returns exit code 1 + +Output to AI: + ⛔ WORKFLOW VIOLATION: Cannot write implementation files during DESIGNING + + Current Phase: DESIGNING + File: app/components/Button.tsx + + During DESIGNING phase, only these files can be modified: + - project_manifest.json + - .workflow/versions/vXXX/tasks/*.yml + + Complete design and get approval before implementing. +``` + +--- + +## Workflow Commands + +### Starting a Workflow + +```bash +# Automated workflow (auto-approves if validation passes) +/workflow:spawn --auto + +# Interactive workflow (stops at approval gates) +/workflow:spawn +``` + +### Checking Status + +```bash +/workflow:status # Current workflow state +/workflow:history # All workflow versions +``` + +### Approval Gates + +```bash +/workflow:approve # Approve current gate (design or implementation) +/workflow:reject # Reject with feedback +``` + +### Resuming + +```bash +/workflow:resume # Continue interrupted workflow +``` + +--- + +## Guardrail Commands (Manual Flow) + +For granular control without full orchestration: + +### Design Phase +```bash +/guardrail:init # Initialize manifest +/guardrail:design # Add entities to manifest +/guardrail:review # Request design review +/guardrail:approve # Approve design +``` + +### Implementation Phase +```bash +/guardrail:implement # Implement approved entity +/guardrail:verify # Verify implementation +/guardrail:validate # Validate manifest integrity +``` + +### Analysis +```bash +/guardrail:analyze # Analyze existing codebase +/guardrail:status # Show project status +``` + +--- + +## Project Structure + +``` +project/ +├── .claude/ +│ ├── commands/ +│ │ ├── eureka/ # Eureka utility commands +│ │ │ └── index.md # Documentation generator +│ │ ├── guardrail/ # Manual workflow commands +│ │ └── workflow/ # Orchestrated workflow commands +│ └── settings.json # Hook configurations (enforcement) +├── .workflow/ +│ ├── current.yml # Active version pointer +│ ├── index.yml # Version registry +│ └── versions/ +│ └── vXXX/ +│ ├── session.yml # Workflow state +│ └── tasks/ # Task definitions +│ └── task_*.yml +├── skills/ +│ ├── guardrail-orchestrator/ +│ │ ├── agents/ # Agent role definitions +│ │ ├── schemas/ # YAML schemas +│ │ └── scripts/ # Enforcement scripts +│ └── documentation-generator/ +│ ├── agents/ # doc-writer agent +│ ├── schemas/ # Documentation schemas +│ ├── scripts/ # Analysis & generation scripts +│ └── templates/ # HTML template +├── project_manifest.json # Entity definitions +└── CLAUDE.md # This file +``` + +--- + +## Development Guidelines + +### MANDATORY: Follow the Workflow + +1. **Never write implementation files directly** - hooks will block you +2. **Start with `/workflow:spawn`** or `/guardrail:init` +3. **Design first** - add entities to manifest +4. **Get approval** - wait for gate approval +5. **Then implement** - only after approval + +### Workflow State Files + +- `.workflow/current.yml` - Points to active version +- `.workflow/versions/vXXX/session.yml` - Phase state, approvals +- `.workflow/versions/vXXX/tasks/*.yml` - Task definitions + +### Manifest Structure + +```json +{ + "project": { "name": "...", "version": "..." }, + "state": { "current_phase": "DESIGN_PHASE" }, + "entities": { + "components": [ + { + "id": "component_button", + "name": "Button", + "status": "PENDING|APPROVED|IMPLEMENTED", + "file_path": "app/components/Button.tsx" + } + ], + "pages": [...], + "api_endpoints": [...] + } +} +``` + +--- + +## Quick Start + +### Option 1: Automated Workflow +```bash +# Single command - runs entire workflow +/workflow:spawn --auto add user authentication +``` + +### Option 2: Interactive Workflow +```bash +# Step-by-step with approval gates +/workflow:spawn add user profile page + +# Wait for design... +/workflow:approve # Approve design + +# Wait for implementation... +/workflow:approve # Approve implementation +``` + +### Option 3: Manual Guardrail +```bash +/guardrail:init my-feature +/guardrail:design +/guardrail:approve +/guardrail:implement +/guardrail:verify +``` + +--- + +## Troubleshooting + +### "WORKFLOW VIOLATION" Error + +You tried to write files outside the allowed phase. Check: +```bash +/workflow:status +``` + +Then follow the workflow: +1. If DESIGNING → finish design, get approval +2. If REVIEWING → wait for review to complete +3. If AWAITING_*_APPROVAL → approve or reject + +### "No active workflow" Error + +Start a workflow first: +```bash +/workflow:spawn +# or +/guardrail:init +``` + +### Resuming After Interruption + +```bash +/workflow:resume +``` + +--- + +## Hook Configuration Reference + +The enforcement is configured in `.claude/settings.json`: + +### Enforced Tools (ALL BLOCKED without workflow) + +| Tool | Validator | What's Checked | +|------|-----------|----------------| +| **Bash** | `validate_bash.py` | Blocks `>`, `>>`, `tee`, `sed -i`, `cp`, `mv`, `rm` | +| **Write** | `validate_workflow.py` + `validate_write.py` | Phase + manifest paths | +| **Edit** | `validate_workflow.py` + `validate_write.py` | Phase + manifest paths | +| **MultiEdit** | `validate_workflow.py` + `validate_write.py` | Phase + manifest paths | +| **NotebookEdit** | `validate_workflow.py` + `validate_write.py` | Phase + manifest paths | +| **Task** | `validate_workflow.py` | Agent type vs phase | +| **mcp__serena__*** | `validate_workflow.py` + `validate_write.py` | All Serena writes | +| **mcp__morphllm__*** | `validate_workflow.py` + `validate_write.py` | All Morphllm writes | +| **mcp__filesystem__*** | `validate_workflow.py` + `validate_write.py` | All filesystem writes | + +### Bash Commands - Blocked Patterns + +These bash patterns are **blocked** unless writing to allowed paths: + +```bash +# Redirections +echo "x" > file.txt # BLOCKED +cat > file.txt # BLOCKED +command >> file.txt # BLOCKED + +# File operations +tee file.txt # BLOCKED +cp source dest # BLOCKED +mv source dest # BLOCKED +rm file.txt # BLOCKED +touch file.txt # BLOCKED + +# In-place edits +sed -i 's/x/y/' file # BLOCKED +awk -i inplace # BLOCKED + +# Here documents +cat << EOF > file # BLOCKED +``` + +### Bash Commands - Always Allowed + +```bash +# Reading +cat file.txt # OK (no redirect) +head/tail/grep/find # OK +ls, pwd, cd # OK + +# Running +npm run build # OK +npm test # OK +node script.js # OK +python script.py # OK (no redirect) + +# Git (read operations) +git status/log/diff # OK +git branch/show # OK +``` + +### Enforcement Scripts + +| Script | Purpose | +|--------|---------| +| `validate_workflow.py` | Phase-based blocking, instructs `/workflow:spawn` | +| `validate_write.py` | Manifest path validation | +| `validate_bash.py` | Shell command pattern blocking | +| `validate_api_contract.py` | Frontend-backend API contract validation | +| `workflow_manager.py` | State machine transitions | +| `post_write.py` | Auto-update entity status after writes | +| `visualize_design.py` | ASCII art design visualization | + +--- + +## Review Command (Quality Gates) + +The `/workflow:review` command runs comprehensive quality checks before approving implementation. + +### Validation Checks + +| Check | Command | Blocks Approval | Mode | +|-------|---------|-----------------|------| +| Build | `npm run build` | YES | Always | +| TypeScript | `npx tsc --noEmit` | YES | Always | +| Lint | `npm run lint` | YES | --strict | +| Tests | `npm test` | YES | --strict | +| API Contract | `validate_api_contract.py` | YES | Always | +| Security | Pattern scan | WARNING | Always | + +### API Contract Validation + +The API contract validator ensures frontend and backend are aligned: + +**Frontend Detection**: +- `fetch('/api/...')` calls +- `axios.get/post/put/delete()` requests +- `useSWR()` data fetching +- Custom API clients + +**Backend Detection**: +- Next.js App Router: `app/api/*/route.ts` exports (GET, POST, etc.) +- Next.js Pages Router: `pages/api/*.ts` req.method checks +- Express-style: `router.get/post()` patterns + +**Validation Rules**: +1. **Endpoint Existence**: Frontend calls must have matching backend routes +2. **Method Match**: GET calls → GET endpoints, POST → POST, etc. +3. **Body Alignment**: POST/PUT should send bodies, GET should not +4. **Unused Detection**: Warns about backend routes not called by frontend + +### Security Scan + +Checks for common security issues: +- Hardcoded passwords, API keys, secrets +- SQL injection patterns (`query.*${`) +- XSS risks (`dangerouslySetInnerHTML`) +- Console statements in production code + +### Usage + +```bash +# Standard review (build + types + API contract + security) +/workflow:review --auto + +# Strict review (adds lint + tests) +/workflow:review --auto --strict + +# Full review (all checks) +/workflow:review --auto --full +``` + +--- + +## Security Audit Command + +For comprehensive security analysis, use the dedicated security command: + +```bash +# Quick scan (automated scanner only) +/workflow:security --quick + +# Standard scan (scanner + dependencies) +/workflow:security + +# Full audit (scanner + deps + config review + deep analysis) +/workflow:security --full +``` + +### Security Categories Checked + +| Category | CWE | OWASP | Severity | +|----------|-----|-------|----------| +| Hardcoded Secrets | CWE-798 | A07 | CRITICAL | +| SQL Injection | CWE-89 | A03 | CRITICAL | +| Command Injection | CWE-78 | A03 | CRITICAL | +| XSS | CWE-79 | A03 | HIGH | +| Path Traversal | CWE-22 | A01 | HIGH | +| NoSQL Injection | CWE-943 | A03 | HIGH | +| SSRF | CWE-918 | A10 | HIGH | +| Prototype Pollution | CWE-1321 | A03 | HIGH | +| Insecure Auth | CWE-287 | A07 | HIGH | +| CORS Misconfiguration | CWE-942 | A01 | MEDIUM | +| Sensitive Data Exposure | CWE-200 | A02 | MEDIUM | +| Insecure Dependencies | CWE-1104 | A06 | MEDIUM | + +### Exit Codes +- **0**: PASS - No critical/high issues +- **1**: WARNING - High issues found +- **2**: CRITICAL - Critical issues found (blocks deployment) + +--- + +## Documentation Generation + +Generate comprehensive project documentation for **both engineers and non-engineers** using the `/eureka:index` command. + +### Quick Start + +```bash +# Generate documentation in docs/ folder +/eureka:index + +# Generate in custom folder +/eureka:index my-docs +``` + +### Output Files + +| File | Audience | Description | +|------|----------|-------------| +| `index.html` | **Non-engineers** | Beautiful HTML - just open in browser! | +| `PROJECT_DOCUMENTATION.md` | Both | Main documentation | +| `QUICK_REFERENCE.md` | Both | One-page reference card | +| `API_REFERENCE.md` | Engineers | Detailed API documentation | +| `COMPONENTS.md` | Engineers | Component catalog | +| `analysis.yml` | Engineers | Raw project analysis data | + +### Documentation Structure + +``` +╔══════════════════════════════════════════════════════════════╗ +║ DUAL-AUDIENCE DESIGN ║ +╠══════════════════════════════════════════════════════════════╣ +║ ║ +║ FOR NON-ENGINEERS (HTML): FOR ENGINEERS (MD): ║ +║ ───────────────────────── ───────────────────── ║ +║ • Clean, professional view • Git-friendly format ║ +║ • Technical details hidden • Editable source ║ +║ • Click to expand if needed • Full technical specs ║ +║ • Works on mobile • Code examples ║ +║ • Print to PDF • Type definitions ║ +║ ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +### Documentation Sections + +| Section | Audience | Content | +|---------|----------|---------| +| Executive Summary | Everyone | What it does, who it's for | +| Architecture Overview | Everyone | Visual diagrams, tech stack | +| Getting Started | Semi-technical | Setup, installation | +| Feature Guide | Non-engineers | Plain-language descriptions | +| API Reference | Engineers | Endpoints, schemas (collapsible) | +| Component Catalog | Engineers | Props, events (collapsible) | +| Data Models | Both | ER diagrams + descriptions | +| Glossary | Non-engineers | Technical terms explained | + +### HTML Features + +The generated `index.html` includes: + +- **Responsive design** - Works on desktop, tablet, and mobile +- **Dark mode** - Automatically adapts to system preferences +- **Sidebar navigation** - Easy section jumping +- **Collapsible technical details** - Non-engineers see clean view +- **Print-friendly** - Can be saved as PDF +- **Single file** - No dependencies, just open in browser + +### For Non-Engineers + +Simply open `docs/index.html` in your web browser. You'll see: + +1. **Executive Summary** - What the project does in plain English +2. **Feature Guide** - What each feature does and how to use it +3. **Glossary** - Explanations of technical terms + +Technical details are hidden by default. Click "🔧 Technical Details" to expand if curious. + +### For Engineers + +Use the Markdown files for: + +- Version control (git-friendly) +- Easy editing and updates +- Integration with documentation systems +- Full technical specifications + +Or use the HTML and click "🔧 Technical Details" to see code examples, props, and schemas. + +### Example Output + +``` +docs/ +├── index.html # 🌐 Open this in browser! +├── PROJECT_DOCUMENTATION.md # 📄 Main docs (markdown) +├── QUICK_REFERENCE.md # 📄 Quick reference card +├── API_REFERENCE.md # 📄 API details +├── COMPONENTS.md # 📄 Component catalog +└── analysis.yml # 📊 Raw analysis data +``` + +--- + +## Template Development Guide + +This section explains how to create new **commands**, **skills**, and **agents** for the Eureka framework. + +### Templates Directory Structure + +``` +templates/ +├── .claude/ +│ ├── commands/ # Slash commands +│ │ ├── eureka/ # /eureka:* commands +│ │ │ └── index.md +│ │ ├── guardrail/ # /guardrail:* commands +│ │ │ ├── init.md +│ │ │ ├── design.md +│ │ │ └── ... +│ │ └── workflow/ # /workflow:* commands +│ │ ├── spawn.md +│ │ ├── design.md +│ │ └── ... +│ └── settings.json # Hooks configuration +│ +├── skills/ # Skill packages +│ ├── guardrail-orchestrator/ +│ │ ├── skill.yml # Skill manifest +│ │ ├── agents/ # Agent definitions +│ │ │ ├── orchestrator.yml +│ │ │ ├── architect.yml +│ │ │ └── ... +│ │ ├── schemas/ # YAML schemas +│ │ │ ├── design_document.yml +│ │ │ └── ... +│ │ └── scripts/ # Python scripts +│ │ ├── validate_workflow.py +│ │ └── ... +│ │ +│ └── documentation-generator/ +│ ├── skill.yml +│ ├── agents/ +│ │ └── doc-writer.yml +│ ├── schemas/ +│ │ ├── documentation_output.yml +│ │ └── project_analysis.yml +│ ├── scripts/ +│ │ ├── analyze_project.py +│ │ └── generate_html.py +│ └── templates/ +│ └── documentation.html +│ +├── CLAUDE.md # Project instructions +└── .mcp.json # MCP server config +``` + +--- + +### Creating a New Command + +Commands are markdown files that define executable workflows. + +#### File Location +``` +templates/.claude/commands//.md +``` + +#### Command File Format + +```markdown +--- +description: Short description shown in command list +allowed-tools: Read, Write, Edit, Bash, Task, TodoWrite +--- + +# Command Title + +**Input**: "$ARGUMENTS" + +--- + +## PURPOSE + +Explain what this command does and when to use it. + +--- + +## ⛔ CRITICAL RULES + +### MUST DO +1. **MUST** do this thing +2. **MUST** do another thing + +### CANNOT DO +1. **CANNOT** do this +2. **CANNOT** do that + +--- + +## EXECUTION FLOW + +### ═══════════════════════════════════════════════════════════════ +### PHASE 1: Phase Name +### ═══════════════════════════════════════════════════════════════ + +#### 1.1: Step Name +```bash +# Commands to execute +echo "Hello" +``` + +#### 1.2: Another Step + +**Use Task tool with agent:** +``` +Use Task tool with: + subagent_type: "agent-name" + prompt: | + Instructions for the agent... +``` + +--- + +### ═══════════════════════════════════════════════════════════════ +### PHASE 2: Next Phase +### ═══════════════════════════════════════════════════════════════ + +[Continue with more phases...] + +--- + +## USAGE + +```bash +/namespace:command +/namespace:command argument +/namespace:command --flag value +``` +``` + +#### Command Conventions + +| Element | Convention | +|---------|------------| +| Namespace | Lowercase, matches folder name (`eureka`, `workflow`) | +| Command name | Lowercase, matches file name without `.md` | +| Phases | Use `═══` banners with PHASE N headers | +| Steps | Use `####` with numbered steps (1.1, 1.2) | +| Banners | ASCII art boxes for important output | +| Arguments | Reference as `$ARGUMENTS` | + +--- + +### Creating a New Skill + +Skills are packages that bundle agents, schemas, scripts, and templates. + +#### Skill Directory Structure + +``` +skills// +├── skill.yml # REQUIRED: Skill manifest +├── agents/ # REQUIRED: Agent definitions +│ └── .yml +├── schemas/ # OPTIONAL: Data schemas +│ └── .yml +├── scripts/ # OPTIONAL: Python/Bash scripts +│ └── .py +└── templates/ # OPTIONAL: HTML/MD templates + └── .html +``` + +#### skill.yml Format + +```yaml +# Skill manifest +name: skill-name +version: "1.0.0" +description: | + Multi-line description of what this skill does. + +# Activation triggers +triggers: + commands: + - "/namespace:command" + keywords: + - "trigger phrase" + - "another trigger" + +# Components +agents: + - agent-name + +schemas: + - schema-name.yml + +scripts: + - script-name.py + +templates: + - template-name.html + +# Capabilities list +capabilities: + - Capability one + - Capability two + +# Output files +outputs: + primary: + - output-file.md + optional: + - optional-file.md + +# Tool dependencies +dependencies: + required: + - Read tool + - Write tool + optional: + - Bash tool +``` + +--- + +### Creating a New Agent + +Agents are YAML files that define specialized AI personas. + +#### File Location +``` +skills//agents/.yml +``` + +#### Agent File Format + +```yaml +# Agent Definition +name: agent-name +role: Agent Role Title +description: | + Multi-line description of what this agent specializes in + and when it should be used. + +# Tool permissions +allowed_tools: + - Read + - Write + - Edit + - Glob + - Grep + +blocked_tools: + - Task # Prevent sub-agent spawning + - Bash # Prevent shell access + +# File access restrictions +allowed_files: + - "docs/**/*" + - "*.md" + - "package.json" + +# What this agent does +responsibilities: + - First responsibility + - Second responsibility + - Third responsibility + +# What this agent produces +outputs: + - output-file.md + - another-output.yml + +# Restrictions +cannot_do: + - Cannot modify source code + - Cannot run tests + - Cannot deploy + +# Agent-specific configuration +custom_config: + key: value + nested: + setting: value +``` + +#### Agent Naming Conventions + +| Type | Naming Pattern | Examples | +|------|----------------|----------| +| Task agents | `-` | `backend-architect`, `frontend-engineer` | +| Review agents | `-reviewer` | `security-reviewer`, `code-reviewer` | +| Analysis agents | `-analyzer` | `codebase-analyzer`, `dependency-analyzer` | +| Writer agents | `-writer` | `doc-writer`, `test-writer` | + +--- + +### Creating a Schema + +Schemas define the structure of YAML/JSON data files. + +#### File Location +``` +skills//schemas/.yml +``` + +#### Schema File Format + +```yaml +# Schema metadata +version: "1.0" +description: What this schema defines + +# Top-level structure +: + type: object + required: true + fields: + field_name: + type: string|integer|boolean|array|object|enum + required: true|false + description: What this field represents + default: default_value + + enum_field: + type: enum + values: [value1, value2, value3] + + array_field: + type: array + items: + field: type + another: type + min_items: 1 + max_items: 10 + + nested_object: + type: object + fields: + nested_field: + type: string + +# Validation rules +validation_rules: + - name: rule_name + description: What this rule checks + +# Processing instructions +processing: + step_1: + description: First processing step + step_2: + description: Second processing step +``` + +--- + +### Creating a Script + +Scripts automate validation, generation, or transformation tasks. + +#### File Location +``` +skills//scripts/.py +``` + +#### Script Template + +```python +#!/usr/bin/env python3 +""" +Script Name +Brief description of what this script does. +""" + +import os +import sys +import json +from pathlib import Path +from typing import Dict, List, Any, Optional + +# Try optional imports +try: + import yaml +except ImportError: + yaml = None + + +def main_function(input_path: Path, output_path: Optional[Path] = None) -> Dict[str, Any]: + """ + Main processing function. + + Args: + input_path: Path to input file + output_path: Optional path for output + + Returns: + Processing result + """ + # Implementation here + pass + + +def main(): + """CLI entry point.""" + if len(sys.argv) < 2: + print("Usage: script_name.py [output]", file=sys.stderr) + sys.exit(1) + + input_path = Path(sys.argv[1]) + output_path = Path(sys.argv[2]) if len(sys.argv) > 2 else None + + if not input_path.exists(): + print(f"Error: Input not found: {input_path}", file=sys.stderr) + sys.exit(1) + + result = main_function(input_path, output_path) + + # Output result + if output_path: + output_path.write_text(json.dumps(result, indent=2)) + print(f"Output written to: {output_path}") + else: + print(json.dumps(result, indent=2)) + + +if __name__ == '__main__': + main() +``` + +#### Script Conventions + +| Convention | Description | +|------------|-------------| +| Exit codes | `0` = success, `1` = error, `2` = critical | +| Input | Accept file paths as arguments | +| Output | Write to stdout or specified file | +| Dependencies | Handle missing imports gracefully | +| Errors | Print to stderr with clear messages | + +--- + +### Creating a Template + +Templates are HTML/Markdown files with placeholders. + +#### File Location +``` +skills//templates/.html +``` + +#### Placeholder Conventions + +```html + +{{VARIABLE_NAME}} + + + +
default content
+ + + +{{#IF_CONDITION}} + Content shown if condition is true +{{/IF_CONDITION}} + + +{{#EACH_ITEMS}} +
{{ITEM_NAME}}
+{{/EACH_ITEMS}} +``` + +--- + +### Checklist: Creating a New Feature + +When creating a new feature, follow this checklist: + +``` +□ 1. PLAN THE FEATURE + □ Define the purpose and scope + □ Identify target users (engineers/non-engineers/both) + □ List required capabilities + +□ 2. CREATE THE SKILL + □ Create skill directory: skills// + □ Create skill.yml manifest + □ Define triggers and capabilities + +□ 3. CREATE THE AGENT(S) + □ Create agents/.yml + □ Define allowed/blocked tools + □ Define responsibilities and outputs + +□ 4. CREATE SCHEMAS (if needed) + □ Create schemas/.yml + □ Define data structure + □ Add validation rules + +□ 5. CREATE SCRIPTS (if needed) + □ Create scripts/.py + □ Handle CLI arguments + □ Add error handling + +□ 6. CREATE TEMPLATES (if needed) + □ Create templates/.html + □ Add CSS styling + □ Use placeholder conventions + +□ 7. CREATE THE COMMAND + □ Create .claude/commands//.md + □ Add YAML front-matter + □ Define execution phases + □ Add usage examples + +□ 8. UPDATE DOCUMENTATION + □ Update CLAUDE.md + □ Add to Project Structure section + □ Document new commands +``` + +--- + +### Example: Creating a "Report Generator" Feature + +Here's a complete example of creating a new feature: + +#### 1. Create Skill Directory +``` +skills/report-generator/ +├── skill.yml +├── agents/ +│ └── report-writer.yml +├── schemas/ +│ └── report_format.yml +├── scripts/ +│ └── generate_report.py +└── templates/ + └── report.html +``` + +#### 2. skill.yml +```yaml +name: report-generator +version: "1.0.0" +description: Generate project status reports + +triggers: + commands: + - "/eureka:report" + keywords: + - "generate report" + - "status report" + +agents: + - report-writer + +outputs: + primary: + - report.html + - report.md +``` + +#### 3. agents/report-writer.yml +```yaml +name: report-writer +role: Report Generation Specialist +description: Creates project status reports + +allowed_tools: + - Read + - Write + - Glob + - Grep + +responsibilities: + - Analyze project status + - Generate executive summaries + - Create visual charts + +outputs: + - report.html + - report.md +``` + +#### 4. .claude/commands/eureka/report.md +```markdown +--- +description: Generate project status report +allowed-tools: Read, Write, Task, Glob +--- + +# Eureka Report Generator + +**Input**: "$ARGUMENTS" + +## EXECUTION FLOW + +### PHASE 1: Analyze Project +[...] + +### PHASE 2: Generate Report +[...] +``` + +#### 5. Update CLAUDE.md +Add documentation for the new `/eureka:report` command. diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts new file mode 100644 index 0000000..8bfc249 --- /dev/null +++ b/app/api/auth/login/route.ts @@ -0,0 +1,87 @@ +/** + * POST /api/auth/login - Login user + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { findUserByEmail, getUserBalance, getUserCompletedTasks, getUserBadges } from '@/app/lib/db/store'; +import { verifyPassword, generateToken, sanitizeUser } from '@/app/lib/auth'; +import { LoginRequest, AuthResponse, ApiResponse } from '@/app/lib/types'; + +export async function POST(request: NextRequest) { + try { + const body: LoginRequest = await request.json(); + const { email, password } = body; + + // Validate input + if (!email || !password) { + return NextResponse.json( + { + success: false, + error: 'Email and password are required' + }, + { status: 400 } + ); + } + + // Find user by email + const user = findUserByEmail(email); + if (!user) { + return NextResponse.json( + { + success: false, + error: 'Invalid email or password' + }, + { status: 401 } + ); + } + + // Verify password + const isValidPassword = await verifyPassword(password, user.passwordHash); + if (!isValidPassword) { + return NextResponse.json( + { + success: false, + error: 'Invalid email or password' + }, + { status: 401 } + ); + } + + // Generate token + const token = generateToken(user.id); + + // Get user statistics + const pointsBalance = getUserBalance(user.id); + const tasksCompleted = getUserCompletedTasks(user.id).length; + const badgesCount = getUserBadges(user.id).length; + + // Return user profile and token + const response: AuthResponse = { + user: { + ...sanitizeUser(user), + pointsBalance, + tasksCompleted, + badgesCount + }, + token + }; + + return NextResponse.json>( + { + success: true, + data: response, + message: 'Login successful' + }, + { status: 200 } + ); + } catch (error) { + console.error('Login error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/auth/me/route.ts b/app/api/auth/me/route.ts new file mode 100644 index 0000000..de51f77 --- /dev/null +++ b/app/api/auth/me/route.ts @@ -0,0 +1,55 @@ +/** + * GET /api/auth/me - Get current authenticated user + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth, sanitizeUser } from '@/app/lib/auth'; +import { getUserBalance, getUserCompletedTasks, getUserBadges } from '@/app/lib/db/store'; +import { UserProfile, ApiResponse } from '@/app/lib/types'; + +export async function GET(request: NextRequest) { + try { + // Authenticate user + const user = requireAuth(request); + + // Get user statistics + const pointsBalance = getUserBalance(user.id); + const tasksCompleted = getUserCompletedTasks(user.id).length; + const badgesCount = getUserBadges(user.id).length; + + // Build user profile + const userProfile: UserProfile = { + ...sanitizeUser(user), + pointsBalance, + tasksCompleted, + badgesCount + }; + + return NextResponse.json>( + { + success: true, + data: userProfile + }, + { status: 200 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Get current user error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts new file mode 100644 index 0000000..5e76905 --- /dev/null +++ b/app/api/auth/register/route.ts @@ -0,0 +1,97 @@ +/** + * POST /api/auth/register - Register new user + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { createUser, findUserByEmail } from '@/app/lib/db/store'; +import { hashPassword, generateToken, isValidEmail, isValidPassword, sanitizeUser } from '@/app/lib/auth'; +import { RegisterRequest, AuthResponse, ApiResponse } from '@/app/lib/types'; + +export async function POST(request: NextRequest) { + try { + const body: RegisterRequest = await request.json(); + const { email, password, name } = body; + + // Validate input + if (!email || !password || !name) { + return NextResponse.json( + { + success: false, + error: 'Email, password, and name are required' + }, + { status: 400 } + ); + } + + // Validate email format + if (!isValidEmail(email)) { + return NextResponse.json( + { + success: false, + error: 'Invalid email format' + }, + { status: 400 } + ); + } + + // Validate password strength + const passwordValidation = isValidPassword(password); + if (!passwordValidation.valid) { + return NextResponse.json( + { + success: false, + error: passwordValidation.errors.join(', ') + }, + { status: 400 } + ); + } + + // Check if user already exists + const existingUser = findUserByEmail(email); + if (existingUser) { + return NextResponse.json( + { + success: false, + error: 'User with this email already exists' + }, + { status: 409 } + ); + } + + // Hash password and create user + const passwordHash = await hashPassword(password); + const user = createUser(email, passwordHash, name); + + // Generate token + const token = generateToken(user.id); + + // Return user profile and token + const response: AuthResponse = { + user: { + ...sanitizeUser(user), + pointsBalance: 0, + tasksCompleted: 0, + badgesCount: 0 + }, + token + }; + + return NextResponse.json>( + { + success: true, + data: response, + message: 'User registered successfully' + }, + { status: 201 } + ); + } catch (error) { + console.error('Registration error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/leaderboard/route.ts b/app/api/leaderboard/route.ts new file mode 100644 index 0000000..c192ee1 --- /dev/null +++ b/app/api/leaderboard/route.ts @@ -0,0 +1,71 @@ +/** + * GET /api/leaderboard - Get top users by points + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { getCurrentUser } from '@/app/lib/auth'; +import { getAllUsers, getUserBalance, getUserCompletedTasks, getUserBadges } from '@/app/lib/db/store'; +import { LeaderboardResponse, LeaderboardEntry, ApiResponse } from '@/app/lib/types'; + +export async function GET(request: NextRequest) { + try { + // Get current user (optional - for user rank) + const currentUser = getCurrentUser(request); + + // Get all users + const users = getAllUsers(); + + // Build leaderboard entries + const entries: LeaderboardEntry[] = users + .map(user => ({ + userId: user.id, + userName: user.name, + points: getUserBalance(user.id), + tasksCompleted: getUserCompletedTasks(user.id).length, + badgesEarned: getUserBadges(user.id).length, + rank: 0 // Will be set after sorting + })) + .sort((a, b) => { + // Sort by points descending, then by tasks completed + if (b.points !== a.points) { + return b.points - a.points; + } + return b.tasksCompleted - a.tasksCompleted; + }) + .map((entry, index) => ({ + ...entry, + rank: index + 1 + })); + + // Get top 100 entries + const topEntries = entries.slice(0, 100); + + // Find current user's rank + let userRank: LeaderboardEntry | undefined; + if (currentUser) { + userRank = entries.find(entry => entry.userId === currentUser.id); + } + + const response: LeaderboardResponse = { + entries: topEntries, + userRank + }; + + return NextResponse.json>( + { + success: true, + data: response + }, + { status: 200 } + ); + } catch (error) { + console.error('Get leaderboard error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/quizzes/[taskId]/route.ts b/app/api/quizzes/[taskId]/route.ts new file mode 100644 index 0000000..58c0d9d --- /dev/null +++ b/app/api/quizzes/[taskId]/route.ts @@ -0,0 +1,87 @@ +/** + * GET /api/quizzes/[taskId] - Get quiz questions for a task + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth } from '@/app/lib/auth'; +import { findTask, findQuizByTaskId } from '@/app/lib/db/store'; +import { TaskType, ApiResponse } from '@/app/lib/types'; + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ taskId: string }> } +) { + try { + // Authenticate user + requireAuth(request); + const { taskId } = await params; + + // Find the task + const task = findTask(taskId); + if (!task) { + return NextResponse.json( + { + success: false, + error: 'Task not found' + }, + { status: 404 } + ); + } + + // Verify it's a quiz task + if (task.type !== TaskType.QUIZ) { + return NextResponse.json( + { + success: false, + error: 'This is not a quiz task' + }, + { status: 400 } + ); + } + + // Find the quiz + const quiz = findQuizByTaskId(taskId); + if (!quiz) { + return NextResponse.json( + { + success: false, + error: 'Quiz not found for this task' + }, + { status: 404 } + ); + } + + // Return quiz without the correct answer + const { correctAnswer, ...quizData } = quiz; + + return NextResponse.json( + { + success: true, + data: { + ...quizData, + pointsReward: task.pointsReward + } + }, + { status: 200 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Get quiz error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/quizzes/[taskId]/submit/route.ts b/app/api/quizzes/[taskId]/submit/route.ts new file mode 100644 index 0000000..f5e798d --- /dev/null +++ b/app/api/quizzes/[taskId]/submit/route.ts @@ -0,0 +1,155 @@ +/** + * POST /api/quizzes/[taskId]/submit - Submit quiz answers + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth } from '@/app/lib/auth'; +import { findTask, findQuizByTaskId, hasUserCompletedTask, createUserTask } from '@/app/lib/db/store'; +import { addPoints } from '@/app/lib/points'; +import { checkAndAwardBadges } from '@/app/lib/badges'; +import { TaskType, ApiResponse } from '@/app/lib/types'; + +interface SubmitQuizRequest { + answer: number; +} + +interface QuizResult { + correct: boolean; + correctAnswer: number; + pointsEarned: number; + userTask?: { + id: string; + userId: string; + taskId: string; + completedAt: Date; + pointsEarned: number; + }; +} + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ taskId: string }> } +) { + try { + // Authenticate user + const user = requireAuth(request); + const { taskId } = await params; + + // Parse request body + const body: SubmitQuizRequest = await request.json(); + const { answer } = body; + + if (answer === undefined || answer === null) { + return NextResponse.json( + { + success: false, + error: 'Answer is required' + }, + { status: 400 } + ); + } + + // Find the task + const task = findTask(taskId); + if (!task) { + return NextResponse.json( + { + success: false, + error: 'Task not found' + }, + { status: 404 } + ); + } + + // Verify it's a quiz task + if (task.type !== TaskType.QUIZ) { + return NextResponse.json( + { + success: false, + error: 'This is not a quiz task' + }, + { status: 400 } + ); + } + + // Check if user has already completed this quiz + if (hasUserCompletedTask(user.id, taskId)) { + return NextResponse.json( + { + success: false, + error: 'You have already completed this quiz' + }, + { status: 409 } + ); + } + + // Find the quiz + const quiz = findQuizByTaskId(taskId); + if (!quiz) { + return NextResponse.json( + { + success: false, + error: 'Quiz not found for this task' + }, + { status: 404 } + ); + } + + // Check the answer + const isCorrect = answer === quiz.correctAnswer; + const pointsEarned = isCorrect ? task.pointsReward : 0; + + let userTask = undefined; + let newBadges = []; + + if (isCorrect) { + // Complete the task + userTask = createUserTask(user.id, taskId, pointsEarned); + + // Award points + addPoints(user.id, pointsEarned, `task:${taskId}`); + + // Check and award badges + newBadges = checkAndAwardBadges(user.id); + } + + const result: QuizResult = { + correct: isCorrect, + correctAnswer: quiz.correctAnswer, + pointsEarned, + userTask + }; + + return NextResponse.json>( + { + success: true, + data: result, + message: isCorrect + ? `Correct! You earned ${pointsEarned} points${ + newBadges.length > 0 ? ` and ${newBadges.length} new badge(s)!` : '' + }` + : `Incorrect. The correct answer was option ${quiz.correctAnswer}.` + }, + { status: isCorrect ? 201 : 200 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Submit quiz error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/referrals/claim/route.ts b/app/api/referrals/claim/route.ts new file mode 100644 index 0000000..9004036 --- /dev/null +++ b/app/api/referrals/claim/route.ts @@ -0,0 +1,137 @@ +/** + * POST /api/referrals/claim - Claim referral bonus + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth } from '@/app/lib/auth'; +import { createReferral, findUser, getUserReferrals } from '@/app/lib/db/store'; +import { awardReferralBonus } from '@/app/lib/points'; +import { checkAndAwardBadges } from '@/app/lib/badges'; +import { ApiResponse } from '@/app/lib/types'; + +interface ClaimReferralRequest { + referralCode: string; +} + +const REFERRAL_BONUS = 50; // Points awarded for referrals + +export async function POST(request: NextRequest) { + try { + // Authenticate user + const user = requireAuth(request); + + // Parse request body + const body: ClaimReferralRequest = await request.json(); + const { referralCode } = body; + + if (!referralCode) { + return NextResponse.json( + { + success: false, + error: 'Referral code is required' + }, + { status: 400 } + ); + } + + // Decode referral code to get referrer user ID + let referrerId: string; + try { + referrerId = Buffer.from(referralCode, 'base64').toString('utf-8'); + } catch (error) { + return NextResponse.json( + { + success: false, + error: 'Invalid referral code' + }, + { status: 400 } + ); + } + + // Check if referrer exists + const referrer = findUser(referrerId); + if (!referrer) { + return NextResponse.json( + { + success: false, + error: 'Referrer not found' + }, + { status: 404 } + ); + } + + // Prevent self-referral + if (referrerId === user.id) { + return NextResponse.json( + { + success: false, + error: 'You cannot refer yourself' + }, + { status: 400 } + ); + } + + // Check if user was already referred + const allReferrals = getUserReferrals(referrerId); + const alreadyReferred = allReferrals.some(ref => ref.referredId === user.id); + + if (alreadyReferred) { + return NextResponse.json( + { + success: false, + error: 'Referral bonus already claimed' + }, + { status: 409 } + ); + } + + // Create referral record + const referral = createReferral(referrerId, user.id, REFERRAL_BONUS); + + // Award points to referrer + awardReferralBonus(referrerId, referral.id, REFERRAL_BONUS); + + // Award smaller bonus to referred user + const referredBonus = Math.floor(REFERRAL_BONUS / 2); // 25 points + awardReferralBonus(user.id, referral.id, referredBonus); + + // Check and award badges for both users + const referrerBadges = checkAndAwardBadges(referrerId); + const referredBadges = checkAndAwardBadges(user.id); + + return NextResponse.json( + { + success: true, + data: { + referral, + referrerBonus: REFERRAL_BONUS, + referredBonus, + referrerName: referrer.name + }, + message: `Referral claimed! You earned ${referredBonus} points, and ${referrer.name} earned ${REFERRAL_BONUS} points${ + referredBadges.length > 0 ? `. You also earned ${referredBadges.length} new badge(s)!` : '' + }` + }, + { status: 201 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Claim referral error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/referrals/route.ts b/app/api/referrals/route.ts new file mode 100644 index 0000000..7e0a59e --- /dev/null +++ b/app/api/referrals/route.ts @@ -0,0 +1,110 @@ +/** + * POST /api/referrals - Create referral code + * GET /api/referrals - Get user's referrals + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth } from '@/app/lib/auth'; +import { getUserReferrals, findUser } from '@/app/lib/db/store'; +import { Referral, ApiResponse } from '@/app/lib/types'; + +interface ReferralCodeResponse { + referralCode: string; + referralLink: string; +} + +export async function GET(request: NextRequest) { + try { + // Authenticate user + const user = requireAuth(request); + + // Get user's referrals with referred user details + const referrals = getUserReferrals(user.id); + + const referralsWithDetails = referrals.map(ref => { + const referredUser = findUser(ref.referredId); + return { + ...ref, + referredUserName: referredUser?.name || 'Unknown' + }; + }); + + return NextResponse.json( + { + success: true, + data: { + referrals: referralsWithDetails, + totalReferrals: referrals.length, + totalBonusEarned: referrals.reduce((sum, ref) => sum + ref.bonusPoints, 0) + } + }, + { status: 200 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Get referrals error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} + +export async function POST(request: NextRequest) { + try { + // Authenticate user + const user = requireAuth(request); + + // Generate referral code (base64 encoded user ID) + const referralCode = Buffer.from(user.id).toString('base64'); + + // Build referral link + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'; + const referralLink = `${baseUrl}/register?ref=${referralCode}`; + + const response: ReferralCodeResponse = { + referralCode, + referralLink + }; + + return NextResponse.json>( + { + success: true, + data: response, + message: 'Referral code generated successfully' + }, + { status: 200 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Create referral code error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/tasks/[id]/complete/route.ts b/app/api/tasks/[id]/complete/route.ts new file mode 100644 index 0000000..8182d06 --- /dev/null +++ b/app/api/tasks/[id]/complete/route.ts @@ -0,0 +1,108 @@ +/** + * POST /api/tasks/[id]/complete - Complete a specific task + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth } from '@/app/lib/auth'; +import { findTask, hasUserCompletedTask, createUserTask } from '@/app/lib/db/store'; +import { addPoints } from '@/app/lib/points'; +import { checkAndAwardBadges } from '@/app/lib/badges'; +import { TaskType, UserTask, ApiResponse } from '@/app/lib/types'; + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + // Authenticate user + const user = requireAuth(request); + const { id: taskId } = await params; + + // Find the task + const task = findTask(taskId); + if (!task) { + return NextResponse.json( + { + success: false, + error: 'Task not found' + }, + { status: 404 } + ); + } + + // Check if task is active + if (!task.isActive) { + return NextResponse.json( + { + success: false, + error: 'Task is not active' + }, + { status: 400 } + ); + } + + // Check if user has already completed this task + // For certain task types, allow multiple completions + const allowMultiple = [TaskType.AD, TaskType.VIDEO, TaskType.READING].includes(task.type); + + if (!allowMultiple && hasUserCompletedTask(user.id, taskId)) { + return NextResponse.json( + { + success: false, + error: 'You have already completed this task' + }, + { status: 409 } + ); + } + + // Handle quiz tasks separately (they need quiz submission) + if (task.type === TaskType.QUIZ) { + return NextResponse.json( + { + success: false, + error: 'Quiz tasks must be completed via /api/quizzes/[taskId]/submit' + }, + { status: 400 } + ); + } + + // Complete the task + const userTask = createUserTask(user.id, taskId, task.pointsReward); + + // Award points + addPoints(user.id, task.pointsReward, `task:${taskId}`); + + // Check and award badges + const newBadges = checkAndAwardBadges(user.id); + + return NextResponse.json>( + { + success: true, + data: userTask, + message: `Task completed! You earned ${task.pointsReward} points${ + newBadges.length > 0 ? ` and ${newBadges.length} new badge(s)!` : '' + }` + }, + { status: 201 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Task completion error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/tasks/checkin/route.ts b/app/api/tasks/checkin/route.ts new file mode 100644 index 0000000..044d69e --- /dev/null +++ b/app/api/tasks/checkin/route.ts @@ -0,0 +1,88 @@ +/** + * POST /api/tasks/checkin - Daily check-in task completion + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth } from '@/app/lib/auth'; +import { getActiveTasks, getUserCompletedTasks, createUserTask } from '@/app/lib/db/store'; +import { addPoints } from '@/app/lib/points'; +import { checkAndAwardBadges } from '@/app/lib/badges'; +import { TaskType, UserTask, ApiResponse } from '@/app/lib/types'; + +export async function POST(request: NextRequest) { + try { + // Authenticate user + const user = requireAuth(request); + + // Find the daily check-in task + const tasks = getActiveTasks(); + const checkinTask = tasks.find(t => t.type === TaskType.CHECKIN); + + if (!checkinTask) { + return NextResponse.json( + { + success: false, + error: 'Daily check-in task not found' + }, + { status: 404 } + ); + } + + // Check if user already checked in today + const completedTasks = getUserCompletedTasks(user.id); + const today = new Date().toISOString().split('T')[0]; + const alreadyCheckedIn = completedTasks.some(ut => { + const completedDate = ut.completedAt.toISOString().split('T')[0]; + return ut.taskId === checkinTask.id && completedDate === today; + }); + + if (alreadyCheckedIn) { + return NextResponse.json( + { + success: false, + error: 'You have already checked in today' + }, + { status: 409 } + ); + } + + // Complete the check-in task + const userTask = createUserTask(user.id, checkinTask.id, checkinTask.pointsReward); + + // Award points + addPoints(user.id, checkinTask.pointsReward, `task:${checkinTask.id}`); + + // Check and award badges + const newBadges = checkAndAwardBadges(user.id); + + return NextResponse.json>( + { + success: true, + data: userTask, + message: `Check-in successful! You earned ${checkinTask.pointsReward} points${ + newBadges.length > 0 ? ` and ${newBadges.length} new badge(s)!` : '' + }` + }, + { status: 201 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Check-in error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/tasks/route.ts b/app/api/tasks/route.ts new file mode 100644 index 0000000..e2994c0 --- /dev/null +++ b/app/api/tasks/route.ts @@ -0,0 +1,53 @@ +/** + * GET /api/tasks - List all available tasks with user completion status + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth } from '@/app/lib/auth'; +import { getActiveTasks, getUserCompletedTasks } from '@/app/lib/db/store'; +import { TasksResponse, ApiResponse } from '@/app/lib/types'; + +export async function GET(request: NextRequest) { + try { + // Authenticate user + const user = requireAuth(request); + + // Get all active tasks + const available = getActiveTasks(); + + // Get user's completed tasks + const completed = getUserCompletedTasks(user.id); + + const response: TasksResponse = { + available, + completed + }; + + return NextResponse.json>( + { + success: true, + data: response + }, + { status: 200 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Get tasks error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/users/me/badges/route.ts b/app/api/users/me/badges/route.ts new file mode 100644 index 0000000..f6d1beb --- /dev/null +++ b/app/api/users/me/badges/route.ts @@ -0,0 +1,51 @@ +/** + * GET /api/users/me/badges - Get user's earned and available badges + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth } from '@/app/lib/auth'; +import { getUserBadgesWithDetails, getAvailableBadges } from '@/app/lib/badges'; +import { BadgesResponse, ApiResponse } from '@/app/lib/types'; + +export async function GET(request: NextRequest) { + try { + // Authenticate user + const user = requireAuth(request); + + // Get earned and available badges + const earned = getUserBadgesWithDetails(user.id); + const available = getAvailableBadges(user.id); + + const response: BadgesResponse = { + earned, + available + }; + + return NextResponse.json>( + { + success: true, + data: response + }, + { status: 200 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Get badges error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/users/me/points/route.ts b/app/api/users/me/points/route.ts new file mode 100644 index 0000000..b16f450 --- /dev/null +++ b/app/api/users/me/points/route.ts @@ -0,0 +1,51 @@ +/** + * GET /api/users/me/points - Get user's points balance and transaction history + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth } from '@/app/lib/auth'; +import { getBalance, getTransactions } from '@/app/lib/points'; +import { PointsResponse, ApiResponse } from '@/app/lib/types'; + +export async function GET(request: NextRequest) { + try { + // Authenticate user + const user = requireAuth(request); + + // Get balance and transactions + const balance = getBalance(user.id); + const transactions = getTransactions(user.id); + + const response: PointsResponse = { + balance, + transactions + }; + + return NextResponse.json>( + { + success: true, + data: response + }, + { status: 200 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Get points error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/api/users/me/route.ts b/app/api/users/me/route.ts new file mode 100644 index 0000000..467d26c --- /dev/null +++ b/app/api/users/me/route.ts @@ -0,0 +1,55 @@ +/** + * GET /api/users/me - Get current authenticated user + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAuth, sanitizeUser } from '@/app/lib/auth'; +import { getUserBalance, getUserCompletedTasks, getUserBadges } from '@/app/lib/db/store'; +import { UserProfile, ApiResponse } from '@/app/lib/types'; + +export async function GET(request: NextRequest) { + try { + // Authenticate user + const user = requireAuth(request); + + // Get user statistics + const pointsBalance = getUserBalance(user.id); + const tasksCompleted = getUserCompletedTasks(user.id).length; + const badgesCount = getUserBadges(user.id).length; + + // Build user profile + const userProfile: UserProfile = { + ...sanitizeUser(user), + pointsBalance, + tasksCompleted, + badgesCount + }; + + return NextResponse.json>( + { + success: true, + data: userProfile + }, + { status: 200 } + ); + } catch (error) { + if (error instanceof Error && error.message === 'Unauthorized') { + return NextResponse.json( + { + success: false, + error: 'Unauthorized' + }, + { status: 401 } + ); + } + + console.error('Get current user error:', error); + return NextResponse.json( + { + success: false, + error: 'Internal server error' + }, + { status: 500 } + ); + } +} diff --git a/app/components/AuthForm.tsx b/app/components/AuthForm.tsx new file mode 100644 index 0000000..24ef8f7 --- /dev/null +++ b/app/components/AuthForm.tsx @@ -0,0 +1,139 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function AuthForm() { + const [mode, setMode] = useState<"login" | "register">("login"); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [name, setName] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const router = useRouter(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + setLoading(true); + + try { + const endpoint = mode === "login" ? "/api/auth/login" : "/api/auth/register"; + const body = mode === "login" + ? { email, password } + : { email, password, name }; + + const response = await fetch(endpoint, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "Authentication failed"); + } + + // Store token for authenticated requests + if (data.data?.token) { + localStorage.setItem("token", data.data.token); + } + + router.push("/"); + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+

+ {mode === "login" ? "Welcome Back" : "Create Account"} +

+

+ {mode === "login" ? "Sign in to continue" : "Join and start earning"} +

+
+ +
+ {mode === "register" && ( +
+ + setName(e.target.value)} + required + className="w-full px-4 py-3 bg-gray-900 border border-gray-600 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:border-transparent transition" + placeholder="Enter your name" + /> +
+ )} + +
+ + setEmail(e.target.value)} + required + className="w-full px-4 py-3 bg-gray-900 border border-gray-600 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:border-transparent transition" + placeholder="you@example.com" + /> +
+ +
+ + setPassword(e.target.value)} + required + className="w-full px-4 py-3 bg-gray-900 border border-gray-600 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:border-transparent transition" + placeholder="••••••••" + /> +
+ + {error && ( +
+

{error}

+
+ )} + + +
+ +
+ +
+
+
+ ); +} diff --git a/app/components/BadgeCard.tsx b/app/components/BadgeCard.tsx new file mode 100644 index 0000000..6078820 --- /dev/null +++ b/app/components/BadgeCard.tsx @@ -0,0 +1,78 @@ +"use client"; + +interface BadgeCardProps { + badge: { + id: string; + name: string; + description: string; + icon: string; + earnedDate?: string; + progress?: number; + requirement?: number; + }; + earned: boolean; +} + +export default function BadgeCard({ badge, earned }: BadgeCardProps) { + const progressPercent = badge.progress && badge.requirement + ? (badge.progress / badge.requirement) * 100 + : 0; + + return ( +
+
+
+ {badge.icon} +
+ +

+ {badge.name} +

+ +

{badge.description}

+ + {earned && badge.earnedDate ? ( +
+ + + + + Earned {new Date(badge.earnedDate).toLocaleDateString()} + +
+ ) : badge.progress !== undefined && badge.requirement !== undefined ? ( +
+
+ Progress + {badge.progress} / {badge.requirement} +
+
+
+
+

+ {Math.round(progressPercent)}% complete +

+
+ ) : ( +
+ + + + Not earned yet +
+ )} +
+
+ ); +} diff --git a/app/components/DailyCheckinButton.tsx b/app/components/DailyCheckinButton.tsx new file mode 100644 index 0000000..ee66194 --- /dev/null +++ b/app/components/DailyCheckinButton.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useState, useEffect } from "react"; + +interface DailyCheckinButtonProps { + initialStreak?: number; + hasCheckedInToday?: boolean; + onCheckin: () => Promise<{ success: boolean; points: number; streak: number }>; +} + +export default function DailyCheckinButton({ + initialStreak = 0, + hasCheckedInToday = false, + onCheckin +}: DailyCheckinButtonProps) { + const [streak, setStreak] = useState(initialStreak); + const [checkedIn, setCheckedIn] = useState(hasCheckedInToday); + const [loading, setLoading] = useState(false); + const [showSuccess, setShowSuccess] = useState(false); + const [earnedPoints, setEarnedPoints] = useState(0); + + const handleCheckin = async () => { + if (checkedIn || loading) return; + + setLoading(true); + try { + const result = await onCheckin(); + if (result.success) { + setCheckedIn(true); + setStreak(result.streak); + setEarnedPoints(result.points); + setShowSuccess(true); + setTimeout(() => setShowSuccess(false), 3000); + } + } catch (error) { + console.error("Check-in failed:", error); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+
+ + + + {streak} Day Streak +
+

Daily Check-in

+

Come back every day to build your streak!

+
+ + + +

+ {checkedIn + ? "Come back tomorrow to continue your streak!" + : "Earn points and extend your streak"} +

+
+ + {showSuccess && ( +
+
+
+ + + + +{earnedPoints} Points! +
+
+
+ )} +
+ ); +} diff --git a/app/components/DarkThemeLayout.tsx b/app/components/DarkThemeLayout.tsx new file mode 100644 index 0000000..62013b2 --- /dev/null +++ b/app/components/DarkThemeLayout.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { ReactNode } from "react"; + +interface DarkThemeLayoutProps { + children: ReactNode; +} + +export default function DarkThemeLayout({ children }: DarkThemeLayoutProps) { + return ( +
+
+
+
+ {children} +
+
+
+ ); +} diff --git a/app/components/LeaderboardTable.tsx b/app/components/LeaderboardTable.tsx new file mode 100644 index 0000000..cf62d0a --- /dev/null +++ b/app/components/LeaderboardTable.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { useEffect, useState } from "react"; + +interface LeaderboardEntry { + id: string; + username: string; + points: number; + badgeCount: number; + rank: number; + isCurrentUser?: boolean; +} + +export default function LeaderboardTable() { + const [entries, setEntries] = useState([]); + const [loading, setLoading] = useState(true); + const [timeframe, setTimeframe] = useState<"daily" | "weekly" | "alltime">("alltime"); + + useEffect(() => { + fetchLeaderboard(); + }, [timeframe]); + + const fetchLeaderboard = async () => { + try { + const token = localStorage.getItem("token"); + const response = await fetch(`/api/leaderboard?timeframe=${timeframe}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + const data = await response.json(); + if (data.success && data.data) { + // API returns { entries: LeaderboardEntry[], userRank } + const leaderboardEntries = (data.data.entries || []).map((entry: { userId: string; userName: string; points: number; badgesEarned: number; rank: number }) => ({ + id: entry.userId, + username: entry.userName, + points: entry.points, + badgeCount: entry.badgesEarned, + rank: entry.rank, + })); + setEntries(leaderboardEntries); + } else if (Array.isArray(data)) { + setEntries(data); + } + } catch (error) { + console.error("Failed to fetch leaderboard:", error); + } finally { + setLoading(false); + } + }; + + const getRankIcon = (rank: number) => { + switch (rank) { + case 1: + return 🥇; + case 2: + return 🥈; + case 3: + return 🥉; + default: + return #{rank}; + } + }; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+ {(["daily", "weekly", "alltime"] as const).map(tf => ( + + ))} +
+ +
+
+ + + + + + + + + + + {entries.map(entry => ( + + + + + + + ))} + +
+ Rank + + Player + + Points + + Badges +
+
+ {getRankIcon(entry.rank)} +
+
+
+
+ {entry.username[0].toUpperCase()} +
+
+
+ {entry.username} + {entry.isCurrentUser && ( + + You + + )} +
+
+
+
+
+ + {entry.points.toLocaleString()} + + + + +
+
+
+ + + + {entry.badgeCount} +
+
+
+
+
+ ); +} diff --git a/app/components/Navbar.tsx b/app/components/Navbar.tsx new file mode 100644 index 0000000..4a536bc --- /dev/null +++ b/app/components/Navbar.tsx @@ -0,0 +1,144 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import PointsDisplay from "./PointsDisplay"; + +interface NavbarProps { + user?: { + username: string; + points: number; + avatar?: string; + }; +} + +export default function Navbar({ user }: NavbarProps) { + const [dropdownOpen, setDropdownOpen] = useState(false); + + const handleLogout = async () => { + await fetch("/api/auth/logout", { method: "POST" }); + window.location.href = "/"; + }; + + return ( + + ); +} diff --git a/app/components/PointsDisplay.tsx b/app/components/PointsDisplay.tsx new file mode 100644 index 0000000..925340b --- /dev/null +++ b/app/components/PointsDisplay.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { useEffect, useState } from "react"; + +interface PointsDisplayProps { + points: number; + size?: "small" | "medium" | "large"; +} + +export default function PointsDisplay({ points, size = "medium" }: PointsDisplayProps) { + const [displayPoints, setDisplayPoints] = useState(0); + + useEffect(() => { + const duration = 1000; + const steps = 30; + const increment = points / steps; + let current = 0; + let step = 0; + + const timer = setInterval(() => { + step++; + current += increment; + if (step >= steps) { + setDisplayPoints(points); + clearInterval(timer); + } else { + setDisplayPoints(Math.floor(current)); + } + }, duration / steps); + + return () => clearInterval(timer); + }, [points]); + + const sizeClasses = { + small: "text-lg", + medium: "text-3xl", + large: "text-5xl", + }; + + const iconSizes = { + small: "w-6 h-6", + medium: "w-10 h-10", + large: "w-16 h-16", + }; + + return ( +
+
+ + + + +
+
+

Your Points

+

+ {displayPoints.toLocaleString()} +

+
+
+ ); +} diff --git a/app/components/QuizQuestion.tsx b/app/components/QuizQuestion.tsx new file mode 100644 index 0000000..1d38ea4 --- /dev/null +++ b/app/components/QuizQuestion.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { useState } from "react"; + +interface QuizQuestionProps { + question: { + id: string; + text: string; + options: string[]; + correctAnswer?: number; + }; + onAnswer: (selectedIndex: number, isCorrect: boolean) => void; +} + +export default function QuizQuestion({ question, onAnswer }: QuizQuestionProps) { + const [selectedOption, setSelectedOption] = useState(null); + const [submitted, setSubmitted] = useState(false); + + const handleSubmit = () => { + if (selectedOption === null) return; + + const isCorrect = question.correctAnswer !== undefined + ? selectedOption === question.correctAnswer + : false; + + setSubmitted(true); + onAnswer(selectedOption, isCorrect); + }; + + const getOptionStyle = (index: number) => { + if (!submitted) { + return selectedOption === index + ? "bg-yellow-500/20 border-yellow-500" + : "bg-gray-800 border-gray-700 hover:border-gray-600"; + } + + if (question.correctAnswer !== undefined) { + if (index === question.correctAnswer) { + return "bg-green-500/20 border-green-500"; + } + if (index === selectedOption && selectedOption !== question.correctAnswer) { + return "bg-red-500/20 border-red-500"; + } + } + + return "bg-gray-800 border-gray-700 opacity-50"; + }; + + const getOptionIcon = (index: number) => { + if (!submitted) return null; + + if (question.correctAnswer !== undefined) { + if (index === question.correctAnswer) { + return ( + + + + ); + } + if (index === selectedOption && selectedOption !== question.correctAnswer) { + return ( + + + + ); + } + } + return null; + }; + + return ( +
+
+

{question.text}

+

Select one answer

+
+ +
+ {question.options.map((option, index) => ( + + ))} +
+ + {submitted ? ( +
+

+ {question.correctAnswer !== undefined && selectedOption === question.correctAnswer + ? "Correct! Well done!" + : "Incorrect. Try again next time!"} +

+
+ ) : ( + + )} +
+ ); +} diff --git a/app/components/TaskCard.tsx b/app/components/TaskCard.tsx new file mode 100644 index 0000000..290f73f --- /dev/null +++ b/app/components/TaskCard.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { useState } from "react"; + +interface TaskCardProps { + task: { + id: string; + name: string; + description: string; + points: number; + type: "daily" | "quiz" | "survey" | "referral" | "learning"; + completed?: boolean; + }; + onComplete: (taskId: string) => void; +} + +const taskIcons = { + daily: ( + + + + ), + quiz: ( + + + + ), + survey: ( + + + + + ), + referral: ( + + + + ), + learning: ( + + + + ), +}; + +const typeColors = { + daily: "from-blue-500 to-cyan-500", + quiz: "from-purple-500 to-pink-500", + survey: "from-green-500 to-teal-500", + referral: "from-orange-500 to-red-500", + learning: "from-indigo-500 to-purple-500", +}; + +export default function TaskCard({ task, onComplete }: TaskCardProps) { + const [loading, setLoading] = useState(false); + + const handleComplete = async () => { + setLoading(true); + await onComplete(task.id); + setLoading(false); + }; + + return ( +
+
+
+ {taskIcons[task.type]} +
+
+ + + + {task.points} +
+
+ +

{task.name}

+

{task.description}

+ + {task.completed ? ( +
+ + + + Completed +
+ ) : ( + + )} +
+ ); +} diff --git a/app/components/TaskList.tsx b/app/components/TaskList.tsx new file mode 100644 index 0000000..dcf666e --- /dev/null +++ b/app/components/TaskList.tsx @@ -0,0 +1,134 @@ +"use client"; + +import { useEffect, useState } from "react"; +import TaskCard from "./TaskCard"; + +interface Task { + id: string; + name: string; + description: string; + points: number; + type: "daily" | "quiz" | "survey" | "referral" | "learning"; + completed?: boolean; +} + +export default function TaskList() { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + const [filter, setFilter] = useState("all"); + + useEffect(() => { + fetchTasks(); + }, []); + + const fetchTasks = async () => { + try { + const token = localStorage.getItem("token"); + const response = await fetch("/api/tasks", { + headers: { Authorization: `Bearer ${token}` }, + }); + const data = await response.json(); + if (data.success && data.data) { + // API returns { available: Task[], completed: CompletedTask[] } + const availableTasks = data.data.available || []; + const completedIds = new Set((data.data.completed || []).map((c: { taskId: string }) => c.taskId)); + // Mark tasks as completed if in completed list + const tasksWithStatus = availableTasks.map((task: Task) => ({ + ...task, + completed: completedIds.has(task.id), + })); + setTasks(tasksWithStatus); + } else if (Array.isArray(data)) { + setTasks(data); + } + } catch (error) { + console.error("Failed to fetch tasks:", error); + } finally { + setLoading(false); + } + }; + + const handleComplete = async (taskId: string) => { + try { + const token = localStorage.getItem("token"); + const response = await fetch(`/api/tasks/${taskId}/complete`, { + method: "POST", + headers: { Authorization: `Bearer ${token}` }, + }); + if (response.ok) { + setTasks(tasks.map(task => + task.id === taskId ? { ...task, completed: true } : task + )); + } + } catch (error) { + console.error("Failed to complete task:", error); + } + }; + + const groupedTasks = tasks.reduce((acc, task) => { + if (!acc[task.type]) { + acc[task.type] = []; + } + acc[task.type].push(task); + return acc; + }, {} as Record); + + const filteredTasks = filter === "all" + ? tasks + : tasks.filter(task => task.type === filter); + + const categories = [ + { id: "all", label: "All Tasks", count: tasks.length }, + { id: "daily", label: "Daily", count: groupedTasks.daily?.length || 0 }, + { id: "quiz", label: "Quizzes", count: groupedTasks.quiz?.length || 0 }, + { id: "survey", label: "Surveys", count: groupedTasks.survey?.length || 0 }, + { id: "referral", label: "Referrals", count: groupedTasks.referral?.length || 0 }, + { id: "learning", label: "Learning", count: groupedTasks.learning?.length || 0 }, + ]; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+ {categories.map(category => ( + + ))} +
+ + {filteredTasks.length === 0 ? ( +
+
+ + + +
+

No tasks available in this category

+
+ ) : ( +
+ {filteredTasks.map(task => ( + + ))} +
+ )} +
+ ); +} diff --git a/app/components/TransactionHistory.tsx b/app/components/TransactionHistory.tsx new file mode 100644 index 0000000..37ca6e9 --- /dev/null +++ b/app/components/TransactionHistory.tsx @@ -0,0 +1,146 @@ +"use client"; + +import { useEffect, useState } from "react"; + +interface Transaction { + id: string; + type: "earned" | "spent" | "bonus" | "penalty"; + description: string; + amount: number; + timestamp: string; +} + +const transactionIcons = { + earned: ( + + + + ), + spent: ( + + + + ), + bonus: ( + + + + ), + penalty: ( + + + + ), +}; + +export default function TransactionHistory() { + const [transactions, setTransactions] = useState([]); + const [loading, setLoading] = useState(true); + const [filter, setFilter] = useState<"all" | Transaction["type"]>("all"); + + useEffect(() => { + fetchTransactions(); + }, []); + + const fetchTransactions = async () => { + try { + const token = localStorage.getItem("token"); + const response = await fetch("/api/users/me/points", { + headers: { Authorization: `Bearer ${token}` }, + }); + const data = await response.json(); + if (data.success && data.data) { + setTransactions(data.data.transactions || []); + } + } catch (error) { + console.error("Failed to fetch transactions:", error); + } finally { + setLoading(false); + } + }; + + const filteredTransactions = filter === "all" + ? transactions + : transactions.filter(t => t.type === filter); + + const formatDate = (timestamp: string) => { + const date = new Date(timestamp); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMs / 3600000); + const diffDays = Math.floor(diffMs / 86400000); + + if (diffMins < 1) return "Just now"; + if (diffMins < 60) return `${diffMins}m ago`; + if (diffHours < 24) return `${diffHours}h ago`; + if (diffDays < 7) return `${diffDays}d ago`; + return date.toLocaleDateString(); + }; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+ {(["all", "earned", "spent", "bonus", "penalty"] as const).map(type => ( + + ))} +
+ +
+ {filteredTransactions.length === 0 ? ( +
+
+ + + +
+

No transactions found

+
+ ) : ( + filteredTransactions.map(transaction => ( +
+
+
+
+ {transactionIcons[transaction.type]} +
+
+

{transaction.description}

+

{formatDate(transaction.timestamp)}

+
+
+
0 ? "text-green-400" : "text-red-400" + }`}> + {transaction.amount > 0 ? "+" : ""}{transaction.amount.toLocaleString()} +
+
+
+ )) + )} +
+
+ ); +} diff --git a/app/leaderboard/page.tsx b/app/leaderboard/page.tsx new file mode 100644 index 0000000..46a7d59 --- /dev/null +++ b/app/leaderboard/page.tsx @@ -0,0 +1,118 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import DarkThemeLayout from "../components/DarkThemeLayout"; +import Navbar from "../components/Navbar"; +import LeaderboardTable from "../components/LeaderboardTable"; + +interface User { + username: string; + points: number; +} + +export default function LeaderboardPage() { + const router = useRouter(); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + router.push("/login"); + return; + } + + fetchUserData(); + }, [router]); + + const fetchUserData = async () => { + try { + const token = localStorage.getItem("token"); + const response = await fetch("/api/users/me", { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!response.ok) { + throw new Error("Failed to fetch user data"); + } + const data = await response.json(); + if (data.success && data.data) { + setUser({ + username: data.data.name, + points: data.data.pointsBalance || 0, + }); + } + } catch (error) { + console.error("Error fetching user data:", error); + localStorage.removeItem("token"); + router.push("/login"); + } finally { + setLoading(false); + } + }; + + if (loading) { + return ( + +
+
+
+
+ ); + } + + if (!user) { + return null; + } + + return ( + <> + + +
+
+
+
+ + + +
+
+

Leaderboard

+

+ See where you rank among the top players +

+
+ +
+
+
+ + + + Complete tasks to climb the ranks +
+
+ + + + Build your streak for bonus points +
+
+
+ + + + +
+
+ + ); +} diff --git a/app/lib/README.md b/app/lib/README.md new file mode 100644 index 0000000..ff4cb31 --- /dev/null +++ b/app/lib/README.md @@ -0,0 +1,122 @@ +# Base Infrastructure + +This directory contains the core backend utilities for the task-based earning platform. + +## Structure + +``` +app/lib/ +├── types.ts # TypeScript type definitions +├── auth.ts # Authentication utilities +├── points.ts # Points management +├── badges.ts # Badge logic +└── db/ + └── store.ts # In-memory data store +``` + +## Types (types.ts) + +All TypeScript interfaces and enums: +- **User**: User account data +- **Transaction**: Points transactions (earned/spent) +- **Task**: Task definitions (checkin, ad, survey, quiz, etc.) +- **UserTask**: Completed tasks by users +- **Badge**: Achievement definitions +- **UserBadge**: Badges earned by users +- **Quiz**: Quiz questions and answers +- **Referral**: User referral tracking +- **API Response Types**: Structured responses for all endpoints + +## Database Store (db/store.ts) + +In-memory storage with CRUD operations: +- User management (create, find, update) +- Transaction tracking +- Task management +- Badge operations +- Quiz handling +- Referral tracking + +### Seed Data + +Pre-populated with: +- **6 Tasks**: Daily checkin, watch ad, survey, quiz, video, reading +- **6 Badges**: First steps, point collector, dedicated, week warrior, referral master, point millionaire +- **1 Quiz**: Sample quiz for quiz task + +## Authentication (auth.ts) + +User authentication and security: +- `hashPassword()`: Hash passwords (base64 for MVP) +- `verifyPassword()`: Verify password against hash +- `generateToken()`: Create JWT-like tokens +- `verifyToken()`: Validate and decode tokens +- `getCurrentUser()`: Extract user from request +- `requireAuth()`: Middleware for protected routes +- `isValidEmail()`: Email validation +- `isValidPassword()`: Password strength validation +- `sanitizeUser()`: Remove sensitive data from user object + +## Points Management (points.ts) + +Points and transactions: +- `addPoints()`: Award points to user +- `deductPoints()`: Spend points (with balance check) +- `getBalance()`: Get current balance +- `getTransactions()`: Get transaction history +- `getTransactionStats()`: Earnings/spending statistics +- `getPointsBreakdown()`: Points by source +- `awardTaskPoints()`: Task completion rewards +- `awardReferralBonus()`: Referral bonuses +- `awardStreakBonus()`: Daily streak bonuses + +## Badge System (badges.ts) + +Achievement tracking: +- `checkAndAwardBadges()`: Auto-award eligible badges +- `getUserBadgesWithDetails()`: Get earned badges +- `getAvailableBadges()`: Get unearned badges +- `getBadgeProgress()`: Progress towards specific badge +- `getAllBadgeProgress()`: Progress for all badges +- `getBadgeStats()`: Badge statistics + +### Badge Requirements + +- **POINTS_TOTAL**: Total points earned +- **TASKS_COMPLETED**: Number of tasks completed +- **STREAK_DAYS**: Consecutive daily activity +- **REFERRALS**: Number of successful referrals + +## Usage Example + +```typescript +import { createUser, findUserByEmail } from './db/store'; +import { hashPassword, generateToken } from './auth'; +import { addPoints, getBalance } from './points'; +import { checkAndAwardBadges } from './badges'; + +// Register user +const passwordHash = await hashPassword('SecurePass123'); +const user = createUser('user@example.com', passwordHash, 'John Doe'); + +// Generate auth token +const token = generateToken(user.id); + +// Award points for task completion +addPoints(user.id, 10, 'task:daily_checkin'); + +// Check balance +const balance = getBalance(user.id); + +// Check and award badges +const newBadges = checkAndAwardBadges(user.id); +``` + +## Next Steps + +This base infrastructure is ready for API endpoint integration: +1. Create `/api/auth/*` routes (register, login, logout) +2. Create `/api/tasks/*` routes (list, complete) +3. Create `/api/points/*` routes (balance, transactions) +4. Create `/api/badges/*` routes (earned, available, progress) +5. Create `/api/leaderboard` route diff --git a/app/lib/api.ts b/app/lib/api.ts new file mode 100644 index 0000000..0db8f21 --- /dev/null +++ b/app/lib/api.ts @@ -0,0 +1,45 @@ +/** + * API client utilities for making authenticated requests + */ + +export async function fetchWithAuth(url: string, options: RequestInit = {}) { + const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null; + + const headers = new Headers(options.headers); + if (token) { + headers.set('Authorization', `Bearer ${token}`); + } + if (!headers.has('Content-Type') && options.body) { + headers.set('Content-Type', 'application/json'); + } + + return fetch(url, { + ...options, + headers, + }); +} + +export async function apiGet(url: string): Promise { + const response = await fetchWithAuth(url); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Request failed'); + } + + return data; +} + +export async function apiPost(url: string, body?: unknown): Promise { + const response = await fetchWithAuth(url, { + method: 'POST', + body: body ? JSON.stringify(body) : undefined, + }); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Request failed'); + } + + return data; +} diff --git a/app/lib/auth.ts b/app/lib/auth.ts new file mode 100644 index 0000000..8f9e4fc --- /dev/null +++ b/app/lib/auth.ts @@ -0,0 +1,160 @@ +/** + * Authentication utilities for user management and token handling + */ + +import { User } from './types'; +import { findUser } from './db/store'; + +// Simple password hashing for MVP (use bcrypt in production) +export async function hashPassword(password: string): Promise { + // For MVP, use base64 encoding with a salt + // In production, use bcrypt or similar + const salt = 'app-salt-2024'; + const combined = `${salt}:${password}`; + + // Use browser-compatible encoding + if (typeof btoa !== 'undefined') { + return btoa(combined); + } + + // Node.js environment + return Buffer.from(combined).toString('base64'); +} + +export async function verifyPassword( + password: string, + hash: string +): Promise { + const expectedHash = await hashPassword(password); + return expectedHash === hash; +} + +// Simple JWT-like token generation +interface TokenPayload { + userId: string; + exp: number; +} + +export function generateToken(userId: string): string { + const payload: TokenPayload = { + userId, + exp: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days from now + }; + + const tokenData = JSON.stringify(payload); + + // Use browser-compatible encoding + if (typeof btoa !== 'undefined') { + return btoa(tokenData); + } + + // Node.js environment + return Buffer.from(tokenData).toString('base64'); +} + +export function verifyToken(token: string): { userId: string } | null { + try { + let decoded: string; + + // Use browser-compatible decoding + if (typeof atob !== 'undefined') { + decoded = atob(token); + } else { + // Node.js environment + decoded = Buffer.from(token, 'base64').toString('utf-8'); + } + + const payload: TokenPayload = JSON.parse(decoded); + + // Check expiration + if (payload.exp < Date.now()) { + return null; + } + + return { userId: payload.userId }; + } catch (error) { + console.error('Token verification failed:', error); + return null; + } +} + +export function getCurrentUser(request: Request): User | null { + try { + // Extract token from Authorization header + const authHeader = request.headers.get('Authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return null; + } + + const token = authHeader.substring(7); // Remove 'Bearer ' prefix + const payload = verifyToken(token); + + if (!payload) { + return null; + } + + const user = findUser(payload.userId); + return user || null; + } catch (error) { + console.error('getCurrentUser failed:', error); + return null; + } +} + +// Middleware helper for protected routes +export function requireAuth(request: Request): User { + const user = getCurrentUser(request); + + if (!user) { + throw new Error('Unauthorized'); + } + + return user; +} + +// Extract user ID from request without throwing +export function getUserId(request: Request): string | null { + const user = getCurrentUser(request); + return user?.id || null; +} + +// Validate email format +export function isValidEmail(email: string): boolean { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); +} + +// Validate password strength +export function isValidPassword(password: string): { + valid: boolean; + errors: string[]; +} { + const errors: string[] = []; + + if (password.length < 8) { + errors.push('Password must be at least 8 characters long'); + } + + if (!/[A-Z]/.test(password)) { + errors.push('Password must contain at least one uppercase letter'); + } + + if (!/[a-z]/.test(password)) { + errors.push('Password must contain at least one lowercase letter'); + } + + if (!/[0-9]/.test(password)) { + errors.push('Password must contain at least one number'); + } + + return { + valid: errors.length === 0, + errors + }; +} + +// Create a safe user object (without password hash) +export function sanitizeUser(user: User): Omit { + const { passwordHash, ...safeUser } = user; + return safeUser; +} diff --git a/app/lib/badges.ts b/app/lib/badges.ts new file mode 100644 index 0000000..993144a --- /dev/null +++ b/app/lib/badges.ts @@ -0,0 +1,229 @@ +/** + * Badge management and achievement utilities + */ + +import { + Badge, + UserBadge, + RequirementType +} from './types'; +import { + getAllBadges, + getUserBadges, + createUserBadge, + hasUserEarnedBadge, + getUserCompletedTasks, + findBadge +} from './db/store'; +import { getBalance } from './points'; + +/** + * Check user's progress towards badge requirements + */ +function checkRequirement( + userId: string, + requirementType: RequirementType, + requirementValue: number +): boolean { + switch (requirementType) { + case RequirementType.POINTS_TOTAL: { + const balance = getBalance(userId); + return balance >= requirementValue; + } + + case RequirementType.TASKS_COMPLETED: { + const completedTasks = getUserCompletedTasks(userId); + return completedTasks.length >= requirementValue; + } + + case RequirementType.STREAK_DAYS: { + // For MVP, we'll implement basic streak tracking + // In production, this would check consecutive daily check-ins + const completedTasks = getUserCompletedTasks(userId); + const uniqueDays = new Set( + completedTasks.map(task => + task.completedAt.toISOString().split('T')[0] + ) + ); + return uniqueDays.size >= requirementValue; + } + + case RequirementType.REFERRALS: { + // This would check referral count from referrals table + // For MVP, simplified implementation + return false; // Will be implemented with referral system + } + + default: + return false; + } +} + +/** + * Check and award all eligible badges to a user + */ +export function checkAndAwardBadges(userId: string): Badge[] { + const allBadges = getAllBadges(); + const newlyEarnedBadges: Badge[] = []; + + for (const badge of allBadges) { + // Skip if user already has this badge + if (hasUserEarnedBadge(userId, badge.id)) { + continue; + } + + // Check if user meets the requirement + const meetsRequirement = checkRequirement( + userId, + badge.requirementType, + badge.requirementValue + ); + + if (meetsRequirement) { + // Award the badge + createUserBadge(userId, badge.id); + newlyEarnedBadges.push(badge); + } + } + + return newlyEarnedBadges; +} + +/** + * Get all badges earned by a user with badge details + */ +export function getUserBadgesWithDetails(userId: string): (UserBadge & { badge: Badge })[] { + const userBadges = getUserBadges(userId); + + return userBadges.map(ub => { + const badge = findBadge(ub.badgeId); + if (!badge) { + throw new Error(`Badge not found: ${ub.badgeId}`); + } + return { ...ub, badge }; + }).sort((a, b) => b.earnedAt.getTime() - a.earnedAt.getTime()); +} + +/** + * Get available badges (not yet earned) + */ +export function getAvailableBadges(userId: string): Badge[] { + const allBadges = getAllBadges(); + const earnedBadgeIds = new Set( + getUserBadges(userId).map(ub => ub.badgeId) + ); + + return allBadges.filter(badge => !earnedBadgeIds.has(badge.id)); +} + +/** + * Get badge progress for a specific badge + */ +export function getBadgeProgress( + userId: string, + badgeId: string +): { + badge: Badge; + earned: boolean; + progress: number; + requirement: number; + percentage: number; +} { + const badge = findBadge(badgeId); + if (!badge) { + throw new Error(`Badge not found: ${badgeId}`); + } + + const earned = hasUserEarnedBadge(userId, badgeId); + + let progress = 0; + const requirement = badge.requirementValue; + + switch (badge.requirementType) { + case RequirementType.POINTS_TOTAL: + progress = getBalance(userId); + break; + + case RequirementType.TASKS_COMPLETED: + progress = getUserCompletedTasks(userId).length; + break; + + case RequirementType.STREAK_DAYS: { + const completedTasks = getUserCompletedTasks(userId); + const uniqueDays = new Set( + completedTasks.map(task => + task.completedAt.toISOString().split('T')[0] + ) + ); + progress = uniqueDays.size; + break; + } + + case RequirementType.REFERRALS: + progress = 0; // Will be implemented with referral system + break; + } + + const percentage = Math.min((progress / requirement) * 100, 100); + + return { + badge, + earned, + progress, + requirement, + percentage + }; +} + +/** + * Get all badge progress for a user + */ +export function getAllBadgeProgress(userId: string): ReturnType[] { + const allBadges = getAllBadges(); + return allBadges.map(badge => getBadgeProgress(userId, badge.id)); +} + +/** + * Get recently earned badges + */ +export function getRecentlyEarnedBadges( + userId: string, + limit: number = 5 +): (UserBadge & { badge: Badge })[] { + const earnedBadges = getUserBadgesWithDetails(userId); + return earnedBadges.slice(0, limit); +} + +/** + * Get badge statistics for a user + */ +export function getBadgeStats(userId: string): { + totalEarned: number; + totalAvailable: number; + completionPercentage: number; + recentlyEarned: (UserBadge & { badge: Badge })[]; +} { + const allBadges = getAllBadges(); + const earnedBadges = getUserBadges(userId); + const recentlyEarned = getRecentlyEarnedBadges(userId, 3); + + return { + totalEarned: earnedBadges.length, + totalAvailable: allBadges.length, + completionPercentage: (earnedBadges.length / allBadges.length) * 100, + recentlyEarned + }; +} + +/** + * Check if badge should be awarded after a specific action + */ +export function checkBadgesAfterAction( + userId: string, + action: 'task_complete' | 'points_earned' | 'referral' | 'streak' +): Badge[] { + // This is a convenience wrapper for checkAndAwardBadges + // In a real implementation, you might want to optimize by only checking + // relevant badges based on the action type + return checkAndAwardBadges(userId); +} diff --git a/app/lib/db/store.ts b/app/lib/db/store.ts new file mode 100644 index 0000000..3655e53 --- /dev/null +++ b/app/lib/db/store.ts @@ -0,0 +1,366 @@ +/** + * In-memory data store for MVP implementation + */ + +import { + User, + Transaction, + Task, + UserTask, + Badge, + UserBadge, + Quiz, + Referral, + TaskType, + RequirementType, + TransactionType +} from '../types'; + +// In-memory storage +const users: User[] = []; +const transactions: Transaction[] = []; +const tasks: Task[] = []; +const userTasks: UserTask[] = []; +const badges: Badge[] = []; +const userBadges: UserBadge[] = []; +const quizzes: Quiz[] = []; +const referrals: Referral[] = []; + +// ID generators +let userIdCounter = 1; +let transactionIdCounter = 1; +let taskIdCounter = 1; +let userTaskIdCounter = 1; +let badgeIdCounter = 1; +let userBadgeIdCounter = 1; +let quizIdCounter = 1; +let referralIdCounter = 1; + +// Helper: Generate IDs +const generateId = (prefix: string, counter: number): string => + `${prefix}_${String(counter).padStart(6, '0')}`; + +// User operations +export const findUser = (id: string): User | undefined => { + return users.find(u => u.id === id); +}; + +export const findUserByEmail = (email: string): User | undefined => { + return users.find(u => u.email.toLowerCase() === email.toLowerCase()); +}; + +export const createUser = (email: string, passwordHash: string, name: string): User => { + const user: User = { + id: generateId('user', userIdCounter++), + email, + passwordHash, + name, + createdAt: new Date(), + updatedAt: new Date() + }; + users.push(user); + return user; +}; + +export const updateUser = (id: string, updates: Partial): User | null => { + const user = findUser(id); + if (!user) return null; + + Object.assign(user, { ...updates, updatedAt: new Date() }); + return user; +}; + +export const getAllUsers = (): User[] => { + return [...users]; +}; + +// Transaction operations +export const createTransaction = ( + userId: string, + amount: number, + type: TransactionType, + source: string +): Transaction => { + const transaction: Transaction = { + id: generateId('txn', transactionIdCounter++), + userId, + amount, + type, + source, + createdAt: new Date() + }; + transactions.push(transaction); + return transaction; +}; + +export const getUserTransactions = (userId: string): Transaction[] => { + return transactions.filter(t => t.userId === userId); +}; + +export const getUserBalance = (userId: string): number => { + const userTxns = getUserTransactions(userId); + return userTxns.reduce((balance, txn) => { + return txn.type === TransactionType.EARNED + ? balance + txn.amount + : balance - txn.amount; + }, 0); +}; + +// Task operations +export const findTask = (id: string): Task | undefined => { + return tasks.find(t => t.id === id); +}; + +export const getActiveTasks = (): Task[] => { + return tasks.filter(t => t.isActive); +}; + +export const createTask = (task: Omit): Task => { + const newTask: Task = { + ...task, + id: generateId('task', taskIdCounter++), + createdAt: new Date(), + updatedAt: new Date() + }; + tasks.push(newTask); + return newTask; +}; + +// UserTask operations +export const createUserTask = ( + userId: string, + taskId: string, + pointsEarned: number +): UserTask => { + const userTask: UserTask = { + id: generateId('utask', userTaskIdCounter++), + userId, + taskId, + completedAt: new Date(), + pointsEarned + }; + userTasks.push(userTask); + return userTask; +}; + +export const getUserCompletedTasks = (userId: string): UserTask[] => { + return userTasks.filter(ut => ut.userId === userId); +}; + +export const hasUserCompletedTask = (userId: string, taskId: string): boolean => { + return userTasks.some(ut => ut.userId === userId && ut.taskId === taskId); +}; + +// Badge operations +export const findBadge = (id: string): Badge | undefined => { + return badges.find(b => b.id === id); +}; + +export const getAllBadges = (): Badge[] => { + return [...badges]; +}; + +export const createBadge = (badge: Omit): Badge => { + const newBadge: Badge = { + ...badge, + id: generateId('badge', badgeIdCounter++) + }; + badges.push(newBadge); + return newBadge; +}; + +// UserBadge operations +export const createUserBadge = (userId: string, badgeId: string): UserBadge => { + const userBadge: UserBadge = { + id: generateId('ubadge', userBadgeIdCounter++), + userId, + badgeId, + earnedAt: new Date() + }; + userBadges.push(userBadge); + return userBadge; +}; + +export const getUserBadges = (userId: string): UserBadge[] => { + return userBadges.filter(ub => ub.userId === userId); +}; + +export const hasUserEarnedBadge = (userId: string, badgeId: string): boolean => { + return userBadges.some(ub => ub.userId === userId && ub.badgeId === badgeId); +}; + +// Quiz operations +export const findQuiz = (id: string): Quiz | undefined => { + return quizzes.find(q => q.id === id); +}; + +export const findQuizByTaskId = (taskId: string): Quiz | undefined => { + return quizzes.find(q => q.taskId === taskId); +}; + +export const createQuiz = (quiz: Omit): Quiz => { + const newQuiz: Quiz = { + ...quiz, + id: generateId('quiz', quizIdCounter++) + }; + quizzes.push(newQuiz); + return newQuiz; +}; + +// Referral operations +export const createReferral = ( + referrerId: string, + referredId: string, + bonusPoints: number +): Referral => { + const referral: Referral = { + id: generateId('ref', referralIdCounter++), + referrerId, + referredId, + bonusPoints, + createdAt: new Date() + }; + referrals.push(referral); + return referral; +}; + +export const getUserReferrals = (userId: string): Referral[] => { + return referrals.filter(r => r.referrerId === userId); +}; + +// Seed data +export const seedDatabase = () => { + // Clear existing data + users.length = 0; + transactions.length = 0; + tasks.length = 0; + userTasks.length = 0; + badges.length = 0; + userBadges.length = 0; + quizzes.length = 0; + referrals.length = 0; + + // Reset counters + userIdCounter = 1; + transactionIdCounter = 1; + taskIdCounter = 1; + userTaskIdCounter = 1; + badgeIdCounter = 1; + userBadgeIdCounter = 1; + quizIdCounter = 1; + referralIdCounter = 1; + + // Seed tasks + const dailyCheckin = createTask({ + type: TaskType.CHECKIN, + title: 'Daily Check-in', + description: 'Check in daily to earn points', + pointsReward: 10, + isActive: true + }); + + const watchAd = createTask({ + type: TaskType.AD, + title: 'Watch Advertisement', + description: 'Watch a short video ad', + pointsReward: 5, + isActive: true + }); + + const takeSurvey = createTask({ + type: TaskType.SURVEY, + title: 'Complete Survey', + description: 'Share your opinion in a quick survey', + pointsReward: 20, + isActive: true + }); + + const quizTask = createTask({ + type: TaskType.QUIZ, + title: 'General Knowledge Quiz', + description: 'Test your knowledge and earn points', + pointsReward: 15, + isActive: true + }); + + const videoTask = createTask({ + type: TaskType.VIDEO, + title: 'Watch Educational Video', + description: 'Watch a 5-minute educational video', + pointsReward: 8, + isActive: true + }); + + const readingTask = createTask({ + type: TaskType.READING, + title: 'Read Article', + description: 'Read an interesting article', + pointsReward: 12, + isActive: true + }); + + // Seed quiz for quiz task + createQuiz({ + taskId: quizTask.id, + question: 'What is the capital of France?', + options: ['London', 'Berlin', 'Paris', 'Madrid'], + correctAnswer: 2 + }); + + // Seed badges + createBadge({ + name: 'First Steps', + description: 'Complete your first task', + icon: '🎯', + requirementType: RequirementType.TASKS_COMPLETED, + requirementValue: 1 + }); + + createBadge({ + name: 'Point Collector', + description: 'Earn 100 points', + icon: '💰', + requirementType: RequirementType.POINTS_TOTAL, + requirementValue: 100 + }); + + createBadge({ + name: 'Dedicated', + description: 'Complete 10 tasks', + icon: '⭐', + requirementType: RequirementType.TASKS_COMPLETED, + requirementValue: 10 + }); + + createBadge({ + name: 'Week Warrior', + description: 'Maintain a 7-day streak', + icon: '🔥', + requirementType: RequirementType.STREAK_DAYS, + requirementValue: 7 + }); + + createBadge({ + name: 'Referral Master', + description: 'Refer 5 friends', + icon: '🤝', + requirementType: RequirementType.REFERRALS, + requirementValue: 5 + }); + + createBadge({ + name: 'Point Millionaire', + description: 'Earn 1000 points', + icon: '💎', + requirementType: RequirementType.POINTS_TOTAL, + requirementValue: 1000 + }); + + console.log('Database seeded successfully'); + console.log(`- ${tasks.length} tasks`); + console.log(`- ${badges.length} badges`); + console.log(`- ${quizzes.length} quizzes`); +}; + +// Initialize seed data +seedDatabase(); diff --git a/app/lib/points.ts b/app/lib/points.ts new file mode 100644 index 0000000..1e9386c --- /dev/null +++ b/app/lib/points.ts @@ -0,0 +1,173 @@ +/** + * Points management utilities + */ + +import { + Transaction, + TransactionType +} from './types'; +import { + createTransaction, + getUserTransactions, + getUserBalance +} from './db/store'; + +/** + * Add points to a user's account + */ +export function addPoints( + userId: string, + amount: number, + source: string +): Transaction { + if (amount <= 0) { + throw new Error('Amount must be positive'); + } + + return createTransaction(userId, amount, TransactionType.EARNED, source); +} + +/** + * Deduct points from a user's account + */ +export function deductPoints( + userId: string, + amount: number, + source: string +): Transaction { + if (amount <= 0) { + throw new Error('Amount must be positive'); + } + + const currentBalance = getBalance(userId); + if (currentBalance < amount) { + throw new Error('Insufficient balance'); + } + + return createTransaction(userId, amount, TransactionType.SPENT, source); +} + +/** + * Get user's current points balance + */ +export function getBalance(userId: string): number { + return getUserBalance(userId); +} + +/** + * Get all transactions for a user + */ +export function getTransactions(userId: string): Transaction[] { + return getUserTransactions(userId).sort( + (a, b) => b.createdAt.getTime() - a.createdAt.getTime() + ); +} + +/** + * Get recent transactions (last N) + */ +export function getRecentTransactions( + userId: string, + limit: number = 10 +): Transaction[] { + const allTransactions = getTransactions(userId); + return allTransactions.slice(0, limit); +} + +/** + * Get transaction statistics for a user + */ +export function getTransactionStats(userId: string): { + totalEarned: number; + totalSpent: number; + balance: number; + transactionCount: number; +} { + const transactions = getUserTransactions(userId); + + const totalEarned = transactions + .filter(t => t.type === TransactionType.EARNED) + .reduce((sum, t) => sum + t.amount, 0); + + const totalSpent = transactions + .filter(t => t.type === TransactionType.SPENT) + .reduce((sum, t) => sum + t.amount, 0); + + return { + totalEarned, + totalSpent, + balance: totalEarned - totalSpent, + transactionCount: transactions.length + }; +} + +/** + * Get points earned from specific source + */ +export function getPointsBySource( + userId: string, + source: string +): number { + const transactions = getUserTransactions(userId); + + return transactions + .filter(t => t.type === TransactionType.EARNED && t.source === source) + .reduce((sum, t) => sum + t.amount, 0); +} + +/** + * Get breakdown of points by source + */ +export function getPointsBreakdown(userId: string): Record { + const transactions = getUserTransactions(userId); + + const breakdown: Record = {}; + + transactions + .filter(t => t.type === TransactionType.EARNED) + .forEach(t => { + breakdown[t.source] = (breakdown[t.source] || 0) + t.amount; + }); + + return breakdown; +} + +/** + * Check if user can afford a purchase + */ +export function canAfford(userId: string, amount: number): boolean { + return getBalance(userId) >= amount; +} + +/** + * Award task completion points + */ +export function awardTaskPoints( + userId: string, + taskId: string, + amount: number +): Transaction { + return addPoints(userId, amount, `task:${taskId}`); +} + +/** + * Award referral bonus points + */ +export function awardReferralBonus( + userId: string, + referralId: string, + amount: number +): Transaction { + return addPoints(userId, amount, `referral:${referralId}`); +} + +/** + * Award daily streak bonus + */ +export function awardStreakBonus( + userId: string, + streakDays: number +): Transaction { + const bonusAmount = Math.min(streakDays * 5, 50); // Max 50 points + return addPoints(userId, bonusAmount, `streak:${streakDays}days`); +} diff --git a/app/lib/types.ts b/app/lib/types.ts new file mode 100644 index 0000000..5518744 --- /dev/null +++ b/app/lib/types.ts @@ -0,0 +1,180 @@ +/** + * Core TypeScript types for the task-based earning platform + */ + +// Enums +export enum TaskType { + CHECKIN = 'checkin', + AD = 'ad', + SURVEY = 'survey', + REFERRAL = 'referral', + QUIZ = 'quiz', + VIDEO = 'video', + READING = 'reading' +} + +export enum RequirementType { + POINTS_TOTAL = 'points_total', + TASKS_COMPLETED = 'tasks_completed', + STREAK_DAYS = 'streak_days', + REFERRALS = 'referrals' +} + +export enum TransactionType { + EARNED = 'earned', + SPENT = 'spent' +} + +// User related types +export interface User { + id: string; + email: string; + passwordHash: string; + name: string; + createdAt: Date; + updatedAt: Date; +} + +export interface UserProfile extends Omit { + pointsBalance: number; + tasksCompleted: number; + badgesCount: number; +} + +// Points and Transactions +export interface Transaction { + id: string; + userId: string; + amount: number; + type: TransactionType; + source: string; + createdAt: Date; +} + +// Tasks +export interface Task { + id: string; + type: TaskType; + title: string; + description: string; + pointsReward: number; + isActive: boolean; + createdAt?: Date; + updatedAt?: Date; +} + +export interface UserTask { + id: string; + userId: string; + taskId: string; + completedAt: Date; + pointsEarned: number; +} + +// Badges +export interface Badge { + id: string; + name: string; + description: string; + icon: string; + requirementType: RequirementType; + requirementValue: number; +} + +export interface UserBadge { + id: string; + userId: string; + badgeId: string; + earnedAt: Date; +} + +// Quiz +export interface Quiz { + id: string; + taskId: string; + question: string; + options: string[]; + correctAnswer: number; +} + +export interface QuizAttempt { + id: string; + userId: string; + quizId: string; + selectedAnswer: number; + isCorrect: boolean; + pointsEarned: number; + completedAt: Date; +} + +// Referrals +export interface Referral { + id: string; + referrerId: string; + referredId: string; + bonusPoints: number; + createdAt: Date; +} + +// API Response Types +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; + message?: string; +} + +export interface AuthResponse { + user: UserProfile; + token: string; +} + +export interface PointsResponse { + balance: number; + transactions: Transaction[]; +} + +export interface TasksResponse { + available: Task[]; + completed: UserTask[]; +} + +export interface BadgesResponse { + earned: (UserBadge & { badge: Badge })[]; + available: Badge[]; +} + +export interface LeaderboardEntry { + userId: string; + userName: string; + points: number; + rank: number; + tasksCompleted: number; + badgesEarned: number; +} + +export interface LeaderboardResponse { + entries: LeaderboardEntry[]; + userRank?: LeaderboardEntry; +} + +// Request Types +export interface LoginRequest { + email: string; + password: string; +} + +export interface RegisterRequest { + email: string; + password: string; + name: string; +} + +export interface CompleteTaskRequest { + taskId: string; + quizAnswer?: number; +} + +export interface CreateReferralRequest { + referredEmail: string; +} diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..7dda671 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import AuthForm from "../components/AuthForm"; +import DarkThemeLayout from "../components/DarkThemeLayout"; + +export default function LoginPage() { + const router = useRouter(); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (token) { + router.push("/"); + } + }, [router]); + + return ( + +
+ +
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx index 295f8fd..029e7f5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,65 +1,202 @@ -import Image from "next/image"; +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import DarkThemeLayout from "./components/DarkThemeLayout"; +import Navbar from "./components/Navbar"; +import PointsDisplay from "./components/PointsDisplay"; +import DailyCheckinButton from "./components/DailyCheckinButton"; +import TaskList from "./components/TaskList"; +import BadgeCard from "./components/BadgeCard"; + +interface User { + id: string; + username: string; + email: string; + points: number; + rank: number; + badgeCount: number; + streak: number; + hasCheckedInToday: boolean; +} + +export default function DashboardPage() { + const router = useRouter(); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + router.push("/login"); + return; + } + + fetchUserData(); + }, [router]); + + const fetchUserData = async () => { + try { + const token = localStorage.getItem("token"); + const response = await fetch("/api/users/me", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (!response.ok) { + throw new Error("Failed to fetch user data"); + } + const data = await response.json(); + if (data.success && data.data) { + setUser({ + id: data.data.id, + username: data.data.name, + email: data.data.email, + points: data.data.pointsBalance || 0, + rank: 0, + badgeCount: data.data.badgesCount || 0, + streak: 0, + hasCheckedInToday: false, + }); + } else { + throw new Error("Invalid response"); + } + } catch (error) { + console.error("Error fetching user data:", error); + localStorage.removeItem("token"); + router.push("/login"); + } finally { + setLoading(false); + } + }; + + const handleCheckin = async () => { + try { + const response = await fetch("/api/checkin", { + method: "POST", + }); + const data = await response.json(); + + if (data.success) { + setUser(prev => prev ? { + ...prev, + points: prev.points + data.points, + streak: data.streak, + hasCheckedInToday: true + } : null); + + return data; + } + + throw new Error("Check-in failed"); + } catch (error) { + console.error("Check-in error:", error); + return { success: false, points: 0, streak: 0 }; + } + }; + + if (loading) { + return ( + +
+
+
+
+ ); + } + + if (!user) { + return null; + } -export default function Home() { return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

+ <> + + +
+
+

+ Welcome back, {user.username}! +

+

+ Ready to earn more points today? +

+
+ +
+
+
+ +
+ + + +
+
+

Available Tasks

+ + View All → + +
+ +
+
+ +
+
+

Quick Stats

+
+
+
+ + + + Global Rank +
+ #{user.rank} +
+ +
+
+ + + + Badges Earned +
+ {user.badgeCount} +
+ +
+
+ + + + Current Streak +
+ {user.streak} days +
+
+ + +
+
+
- -
-
+ + ); } diff --git a/app/profile/page.tsx b/app/profile/page.tsx new file mode 100644 index 0000000..d72f64c --- /dev/null +++ b/app/profile/page.tsx @@ -0,0 +1,189 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import DarkThemeLayout from "../components/DarkThemeLayout"; +import Navbar from "../components/Navbar"; +import PointsDisplay from "../components/PointsDisplay"; +import TransactionHistory from "../components/TransactionHistory"; +import BadgeCard from "../components/BadgeCard"; + +interface User { + id: string; + username: string; + email: string; + points: number; + createdAt: string; +} + +interface Badge { + id: string; + name: string; + description: string; + icon: string; + earnedDate?: string; + progress?: number; + requirement?: number; +} + +export default function ProfilePage() { + const router = useRouter(); + const [user, setUser] = useState(null); + const [badges, setBadges] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + router.push("/login"); + return; + } + + fetchData(); + }, [router]); + + const fetchData = async () => { + try { + const token = localStorage.getItem("token"); + const headers = { Authorization: `Bearer ${token}` }; + + const [userRes, badgesRes] = await Promise.all([ + fetch("/api/users/me", { headers }), + fetch("/api/users/me/badges", { headers }) + ]); + + if (!userRes.ok || !badgesRes.ok) { + throw new Error("Failed to fetch data"); + } + + const userData = await userRes.json(); + const badgesData = await badgesRes.json(); + + if (userData.success && userData.data) { + setUser({ + id: userData.data.id, + username: userData.data.name, + email: userData.data.email, + points: userData.data.pointsBalance || 0, + createdAt: userData.data.createdAt, + }); + } + if (badgesData.success && badgesData.data) { + setBadges(badgesData.data.badges || []); + } + } catch (error) { + console.error("Error fetching data:", error); + localStorage.removeItem("token"); + router.push("/login"); + } finally { + setLoading(false); + } + }; + + if (loading) { + return ( + +
+
+
+
+ ); + } + + if (!user) { + return null; + } + + const joinedDate = new Date(user.createdAt).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric" + }); + + const earnedBadges = badges.filter(b => b.earnedDate); + const inProgressBadges = badges.filter(b => !b.earnedDate); + + return ( + <> + + +
+
+

Profile

+

Manage your account and view your achievements

+
+ +
+
+
+
+
+ {user.username[0].toUpperCase()} +
+

{user.username}

+

{user.email}

+ +
+
+ Joined + {joinedDate} +
+
+ User ID + {user.id.slice(0, 8)}... +
+
+
+
+ + +
+ +
+
+

Transaction History

+ +
+ +
+
+

+ Badges Earned ({earnedBadges.length}) +

+
+ {earnedBadges.length === 0 ? ( +
+ + + +

No badges earned yet

+

Complete tasks to earn your first badge!

+
+ ) : ( +
+ {earnedBadges.map(badge => ( + + ))} +
+ )} +
+ + {inProgressBadges.length > 0 && ( +
+

+ Badges In Progress ({inProgressBadges.length}) +

+
+ {inProgressBadges.map(badge => ( + + ))} +
+
+ )} +
+
+
+
+ + ); +} diff --git a/app/quiz/[id]/page.tsx b/app/quiz/[id]/page.tsx new file mode 100644 index 0000000..0d9e5fd --- /dev/null +++ b/app/quiz/[id]/page.tsx @@ -0,0 +1,241 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter, useParams } from "next/navigation"; +import DarkThemeLayout from "../../components/DarkThemeLayout"; +import Navbar from "../../components/Navbar"; +import QuizQuestion from "../../components/QuizQuestion"; + +interface User { + username: string; + points: number; +} + +interface Quiz { + id: string; + title: string; + description: string; + questions: Array<{ + id: string; + text: string; + options: string[]; + correctAnswer: number; + }>; + pointsPerQuestion: number; +} + +export default function QuizPage() { + const router = useRouter(); + const params = useParams(); + const quizId = params.id as string; + + const [user, setUser] = useState(null); + const [quiz, setQuiz] = useState(null); + const [loading, setLoading] = useState(true); + const [currentQuestion, setCurrentQuestion] = useState(0); + const [answers, setAnswers] = useState>([]); + const [completed, setCompleted] = useState(false); + const [totalPoints, setTotalPoints] = useState(0); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + router.push("/login"); + return; + } + + fetchData(); + }, [router, quizId]); + + const fetchData = async () => { + try { + const token = localStorage.getItem("token"); + const headers = { Authorization: `Bearer ${token}` }; + + const [userRes, quizRes] = await Promise.all([ + fetch("/api/users/me", { headers }), + fetch(`/api/quizzes/${quizId}`, { headers }) + ]); + + if (!userRes.ok || !quizRes.ok) { + throw new Error("Failed to fetch data"); + } + + const userData = await userRes.json(); + const quizData = await quizRes.json(); + + if (userData.success && userData.data) { + setUser({ + username: userData.data.name, + points: userData.data.pointsBalance || 0, + }); + } + if (quizData.success && quizData.data) { + setQuiz(quizData.data); + } + } catch (error) { + console.error("Error fetching data:", error); + router.push("/tasks"); + } finally { + setLoading(false); + } + }; + + const handleAnswer = (selectedIndex: number, isCorrect: boolean) => { + setAnswers([...answers, { selected: selectedIndex, correct: isCorrect }]); + + if (isCorrect && quiz) { + setTotalPoints(totalPoints + quiz.pointsPerQuestion); + } + }; + + const handleNext = () => { + if (quiz && currentQuestion < quiz.questions.length - 1) { + setCurrentQuestion(currentQuestion + 1); + } else { + handleComplete(); + } + }; + + const handleComplete = async () => { + setCompleted(true); + + try { + await fetch(`/api/quizzes/${quizId}/complete`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + answers, + points: totalPoints + }) + }); + + if (user) { + setUser({ ...user, points: user.points + totalPoints }); + } + } catch (error) { + console.error("Error completing quiz:", error); + } + }; + + if (loading) { + return ( + +
+
+
+
+ ); + } + + if (!user || !quiz) { + return null; + } + + const correctAnswers = answers.filter(a => a.correct).length; + const totalQuestions = quiz.questions.length; + const progressPercent = ((currentQuestion + (completed ? 1 : 0)) / totalQuestions) * 100; + + if (completed) { + return ( + <> + + +
+
+
+ + + +
+

Quiz Complete!

+

Great job completing the quiz

+
+ +
+
+
+

Score

+

+ {correctAnswers}/{totalQuestions} +

+
+
+

Accuracy

+

+ {Math.round((correctAnswers / totalQuestions) * 100)}% +

+
+
+

Points Earned

+

+{totalPoints}

+
+
+ +
+ + +
+
+
+
+ + ); + } + + return ( + <> + + +
+
+

{quiz.title}

+

{quiz.description}

+
+ +
+
+ + Question {currentQuestion + 1} of {totalQuestions} + + + {quiz.pointsPerQuestion} points + +
+ +
+
+
+ + + + {answers[currentQuestion] && ( + + )} +
+
+
+ + ); +} diff --git a/app/referral/page.tsx b/app/referral/page.tsx new file mode 100644 index 0000000..ac06d3c --- /dev/null +++ b/app/referral/page.tsx @@ -0,0 +1,292 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import DarkThemeLayout from "../components/DarkThemeLayout"; +import Navbar from "../components/Navbar"; + +interface User { + id: string; + username: string; + points: number; + referralCode: string; +} + +interface Referral { + id: string; + username: string; + joinedDate: string; + pointsEarned: number; +} + +interface ReferralStats { + totalReferrals: number; + totalPointsEarned: number; + referrals: Referral[]; +} + +export default function ReferralPage() { + const router = useRouter(); + const [user, setUser] = useState(null); + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const [copied, setCopied] = useState(false); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + router.push("/login"); + return; + } + + fetchData(); + }, [router]); + + const fetchData = async () => { + try { + const token = localStorage.getItem("token"); + const headers = { Authorization: `Bearer ${token}` }; + + const [userRes, statsRes] = await Promise.all([ + fetch("/api/users/me", { headers }), + fetch("/api/referrals", { headers }) + ]); + + if (!userRes.ok || !statsRes.ok) { + throw new Error("Failed to fetch data"); + } + + const userData = await userRes.json(); + const statsData = await statsRes.json(); + + if (userData.success && userData.data) { + setUser({ + id: userData.data.id, + username: userData.data.name, + points: userData.data.pointsBalance || 0, + referralCode: userData.data.referralCode || userData.data.id.slice(0, 8), + }); + } + if (statsData.success && statsData.data) { + setStats(statsData.data); + } + } catch (error) { + console.error("Error fetching data:", error); + localStorage.removeItem("token"); + router.push("/login"); + } finally { + setLoading(false); + } + }; + + const copyReferralCode = () => { + if (user) { + const referralUrl = `${window.location.origin}/register?ref=${user.referralCode}`; + navigator.clipboard.writeText(referralUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + const shareToSocial = (platform: string) => { + if (!user) return; + + const referralUrl = `${window.location.origin}/register?ref=${user.referralCode}`; + const text = "Join me on EarnPlay and start earning points!"; + + const urls: Record = { + twitter: `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(referralUrl)}`, + facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(referralUrl)}`, + whatsapp: `https://wa.me/?text=${encodeURIComponent(text + " " + referralUrl)}`, + telegram: `https://t.me/share/url?url=${encodeURIComponent(referralUrl)}&text=${encodeURIComponent(text)}` + }; + + window.open(urls[platform], "_blank", "width=600,height=400"); + }; + + if (loading) { + return ( + +
+
+
+
+ ); + } + + if (!user || !stats) { + return null; + } + + const referralUrl = `${typeof window !== "undefined" ? window.location.origin : ""}/register?ref=${user.referralCode}`; + + return ( + <> + + +
+
+

Referral Program

+

+ Invite friends and earn rewards together +

+
+ +
+
+
+
+
+ + + +
+

Your Referral Code

+
+ +
+
+
+

Referral URL

+

{referralUrl}

+
+ +
+
+ +
+ + + + +
+
+ +
+

Your Referrals

+
+ {stats.referrals.length === 0 ? ( +
+ + + +

No referrals yet

+

Share your code to start earning!

+
+ ) : ( +
+ {stats.referrals.map(referral => ( +
+
+
+
+ {referral.username[0].toUpperCase()} +
+
+

{referral.username}

+

+ Joined {new Date(referral.joinedDate).toLocaleDateString()} +

+
+
+
+

+{referral.pointsEarned}

+

points earned

+
+
+
+ ))} +
+ )} +
+
+
+ +
+
+

Referral Stats

+
+
+

Total Referrals

+

{stats.totalReferrals}

+
+
+

Total Points Earned

+

+{stats.totalPointsEarned}

+
+
+
+ +
+

How it works

+
    +
  • + + + + Share your unique referral link with friends +
  • +
  • + + + + They sign up using your link +
  • +
  • + + + + You both earn bonus points! +
  • +
  • + + + + Unlimited referrals = unlimited rewards +
  • +
+
+
+
+
+
+ + ); +} diff --git a/app/register/page.tsx b/app/register/page.tsx new file mode 100644 index 0000000..af924f3 --- /dev/null +++ b/app/register/page.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import AuthForm from "../components/AuthForm"; +import DarkThemeLayout from "../components/DarkThemeLayout"; + +export default function RegisterPage() { + const router = useRouter(); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (token) { + router.push("/"); + } + }, [router]); + + return ( + +
+ +
+
+ ); +} diff --git a/app/tasks/page.tsx b/app/tasks/page.tsx new file mode 100644 index 0000000..abad93e --- /dev/null +++ b/app/tasks/page.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import DarkThemeLayout from "../components/DarkThemeLayout"; +import Navbar from "../components/Navbar"; +import TaskList from "../components/TaskList"; + +interface User { + username: string; + points: number; +} + +export default function TasksPage() { + const router = useRouter(); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + router.push("/login"); + return; + } + + fetchUserData(); + }, [router]); + + const fetchUserData = async () => { + try { + const token = localStorage.getItem("token"); + const response = await fetch("/api/users/me", { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!response.ok) { + throw new Error("Failed to fetch user data"); + } + const data = await response.json(); + if (data.success && data.data) { + setUser({ + username: data.data.name, + points: data.data.pointsBalance || 0, + }); + } + } catch (error) { + console.error("Error fetching user data:", error); + localStorage.removeItem("token"); + router.push("/login"); + } finally { + setLoading(false); + } + }; + + if (loading) { + return ( + +
+
+
+
+ ); + } + + if (!user) { + return null; + } + + return ( + <> + + +
+
+

Available Tasks

+

+ Complete tasks to earn points and climb the leaderboard +

+
+ + +
+
+ + ); +} diff --git a/project_manifest.json b/project_manifest.json new file mode 100644 index 0000000..eab7ea4 --- /dev/null +++ b/project_manifest.json @@ -0,0 +1,288 @@ +{ + "project": { + "name": "todo-super", + "version": "0.1.0", + "description": "A complete earn-to-play app", + "tech_stack": { + "framework": "Next.js 16", + "language": "TypeScript", + "styling": "Tailwind CSS 4", + "runtime": "React 19" + } + }, + "state": { + "current_phase": "IMPLEMENTED", + "last_updated": "2025-12-18" + }, + "entities": { + "data_models": [ + { + "id": "model_user", + "name": "User", + "status": "IMPLEMENTED", + "file_path": "app/lib/db/schema/user.ts" + }, + { + "id": "model_points", + "name": "Points", + "status": "IMPLEMENTED", + "file_path": "app/lib/db/schema/points.ts" + }, + { + "id": "model_task", + "name": "Task", + "status": "IMPLEMENTED", + "file_path": "app/lib/db/schema/task.ts" + }, + { + "id": "model_user_task", + "name": "UserTask", + "status": "IMPLEMENTED", + "file_path": "app/lib/db/schema/user_task.ts" + }, + { + "id": "model_badge", + "name": "Badge", + "status": "IMPLEMENTED", + "file_path": "app/lib/db/schema/badge.ts" + }, + { + "id": "model_user_badge", + "name": "UserBadge", + "status": "IMPLEMENTED", + "file_path": "app/lib/db/schema/user_badge.ts" + }, + { + "id": "model_quiz", + "name": "Quiz", + "status": "IMPLEMENTED", + "file_path": "app/lib/db/schema/quiz.ts" + }, + { + "id": "model_referral", + "name": "Referral", + "status": "IMPLEMENTED", + "file_path": "app/lib/db/schema/referral.ts" + } + ], + "api_endpoints": [ + { + "id": "api_auth_register", + "path": "/api/auth/register", + "method": "POST", + "status": "IMPLEMENTED", + "file_path": "app/api/auth/register/route.ts" + }, + { + "id": "api_auth_login", + "path": "/api/auth/login", + "method": "POST", + "status": "IMPLEMENTED", + "file_path": "app/api/auth/login/route.ts" + }, + { + "id": "api_auth_me", + "path": "/api/auth/me", + "method": "GET", + "status": "IMPLEMENTED", + "file_path": "app/api/auth/me/route.ts" + }, + { + "id": "api_users_points", + "path": "/api/users/me/points", + "method": "GET", + "status": "IMPLEMENTED", + "file_path": "app/api/users/me/points/route.ts" + }, + { + "id": "api_users_badges", + "path": "/api/users/me/badges", + "method": "GET", + "status": "IMPLEMENTED", + "file_path": "app/api/users/me/badges/route.ts" + }, + { + "id": "api_tasks_list", + "path": "/api/tasks", + "method": "GET", + "status": "IMPLEMENTED", + "file_path": "app/api/tasks/route.ts" + }, + { + "id": "api_tasks_checkin", + "path": "/api/tasks/checkin", + "method": "POST", + "status": "IMPLEMENTED", + "file_path": "app/api/tasks/checkin/route.ts" + }, + { + "id": "api_tasks_complete", + "path": "/api/tasks/:id/complete", + "method": "POST", + "status": "IMPLEMENTED", + "file_path": "app/api/tasks/[id]/complete/route.ts" + }, + { + "id": "api_quizzes_get", + "path": "/api/quizzes/:taskId", + "method": "GET", + "status": "IMPLEMENTED", + "file_path": "app/api/quizzes/[taskId]/route.ts" + }, + { + "id": "api_quizzes_submit", + "path": "/api/quizzes/:taskId/submit", + "method": "POST", + "status": "IMPLEMENTED", + "file_path": "app/api/quizzes/[taskId]/submit/route.ts" + }, + { + "id": "api_leaderboard", + "path": "/api/leaderboard", + "method": "GET", + "status": "IMPLEMENTED", + "file_path": "app/api/leaderboard/route.ts" + }, + { + "id": "api_referrals_create", + "path": "/api/referrals", + "method": "POST", + "status": "IMPLEMENTED", + "file_path": "app/api/referrals/route.ts" + }, + { + "id": "api_referrals_claim", + "path": "/api/referrals/claim", + "method": "POST", + "status": "IMPLEMENTED", + "file_path": "app/api/referrals/claim/route.ts" + } + ], + "pages": [ + { + "id": "page_login", + "name": "Login Page", + "route": "/login", + "status": "IMPLEMENTED", + "file_path": "app/login/page.tsx" + }, + { + "id": "page_register", + "name": "Register Page", + "route": "/register", + "status": "IMPLEMENTED", + "file_path": "app/register/page.tsx" + }, + { + "id": "page_dashboard", + "name": "Dashboard", + "route": "/", + "status": "IMPLEMENTED", + "file_path": "app/page.tsx" + }, + { + "id": "page_tasks", + "name": "Tasks Page", + "route": "/tasks", + "status": "IMPLEMENTED", + "file_path": "app/tasks/page.tsx" + }, + { + "id": "page_quiz", + "name": "Quiz Page", + "route": "/quiz/[id]", + "status": "IMPLEMENTED", + "file_path": "app/quiz/[id]/page.tsx" + }, + { + "id": "page_profile", + "name": "Profile Page", + "route": "/profile", + "status": "IMPLEMENTED", + "file_path": "app/profile/page.tsx" + }, + { + "id": "page_leaderboard", + "name": "Leaderboard Page", + "route": "/leaderboard", + "status": "IMPLEMENTED", + "file_path": "app/leaderboard/page.tsx" + }, + { + "id": "page_referral", + "name": "Referral Page", + "route": "/referral", + "status": "IMPLEMENTED", + "file_path": "app/referral/page.tsx" + } + ], + "components": [ + { + "id": "component_auth_form", + "name": "AuthForm", + "status": "IMPLEMENTED", + "file_path": "app/components/AuthForm.tsx" + }, + { + "id": "component_points_display", + "name": "PointsDisplay", + "status": "IMPLEMENTED", + "file_path": "app/components/PointsDisplay.tsx" + }, + { + "id": "component_task_card", + "name": "TaskCard", + "status": "IMPLEMENTED", + "file_path": "app/components/TaskCard.tsx" + }, + { + "id": "component_task_list", + "name": "TaskList", + "status": "IMPLEMENTED", + "file_path": "app/components/TaskList.tsx" + }, + { + "id": "component_quiz_question", + "name": "QuizQuestion", + "status": "IMPLEMENTED", + "file_path": "app/components/QuizQuestion.tsx" + }, + { + "id": "component_badge_card", + "name": "BadgeCard", + "status": "IMPLEMENTED", + "file_path": "app/components/BadgeCard.tsx" + }, + { + "id": "component_leaderboard_table", + "name": "LeaderboardTable", + "status": "IMPLEMENTED", + "file_path": "app/components/LeaderboardTable.tsx" + }, + { + "id": "component_daily_checkin_button", + "name": "DailyCheckinButton", + "status": "IMPLEMENTED", + "file_path": "app/components/DailyCheckinButton.tsx" + }, + { + "id": "component_transaction_history", + "name": "TransactionHistory", + "status": "IMPLEMENTED", + "file_path": "app/components/TransactionHistory.tsx" + }, + { + "id": "component_navbar", + "name": "Navbar", + "status": "IMPLEMENTED", + "file_path": "app/components/Navbar.tsx" + }, + { + "id": "component_dark_theme_layout", + "name": "DarkThemeLayout", + "status": "IMPLEMENTED", + "file_path": "app/components/DarkThemeLayout.tsx" + } + ] + } +} \ No newline at end of file diff --git a/skills/guardrail-orchestrator/schemas/api_contract.yml b/skills/guardrail-orchestrator/schemas/api_contract.yml new file mode 100644 index 0000000..d4c9b67 --- /dev/null +++ b/skills/guardrail-orchestrator/schemas/api_contract.yml @@ -0,0 +1,347 @@ +# API Contract Schema +# The binding agreement between frontend and backend implementations +# Generated during design phase, validated during review phase +# +# This contract ensures: +# 1. Backend implements exactly the endpoints frontend expects +# 2. Frontend calls endpoints with correct methods/bodies +# 3. Both use the same TypeScript types from shared file + +# ============================================================================ +# CONTRACT METADATA +# ============================================================================ +api_contract: + # Links to workflow + workflow_version: string # e.g., v001 + design_document_revision: integer + + # Timestamps + generated_at: timestamp + validated_at: timestamp | null + + # Contract status + status: draft | active | violated + +# ============================================================================ +# SHARED TYPES (Source of truth for both agents) +# ============================================================================ +# These types are generated into app/types/api.ts +# Both frontend and backend MUST import from this file +types: + description: "TypeScript interfaces shared between frontend and backend" + + type_schema: + # Identity + id: string # type_ (e.g., type_User, type_CreateUserRequest) + name: string # PascalCase type name (exported interface name) + + # Type definition + definition: + type: object | array | enum | union + + # For object types + properties: + - name: string # Property name (camelCase) + type: string # TypeScript type (string, number, boolean, other type name) + required: boolean + description: string + validation: string # Optional validation rule + + # For enum types + enum_values: [string] + + # For union types + union_members: [string] # Array of type names or literal types + + # For array types + array_item_type: string # Type of array items + + # Usage tracking + used_by: + requests: [string] # endpoint_ids that use this as request body + responses: [string] # endpoint_ids that use this as response + models: [string] # model_ids this type represents + + # Example + example_type: + id: type_User + name: User + definition: + type: object + properties: + - name: id + type: string + required: true + description: "Unique user identifier" + - name: email + type: string + required: true + description: "User email address" + validation: email + - name: name + type: string + required: true + description: "Display name" + - name: createdAt + type: Date + required: true + description: "Account creation timestamp" + used_by: + responses: [api_get_user, api_create_user, api_list_users] + models: [model_user] + +# ============================================================================ +# ENDPOINT CONTRACTS (Binding specifications) +# ============================================================================ +endpoints: + description: "API endpoint contracts with strict request/response typing" + + endpoint_schema: + # Identity + id: string # api__ from design_document + + # HTTP Specification + method: GET | POST | PUT | PATCH | DELETE + path: string # Exact path with params (e.g., /api/users/:id) + + # Path parameters (extracted from path) + path_params: + - name: string + type: string # TypeScript type + description: string + + # Query parameters (for GET requests) + query_params: + - name: string + type: string + required: boolean + default: any + description: string + + # Request body (for POST/PUT/PATCH) + request_body: + type_id: string # Reference to types section (e.g., type_CreateUserRequest) + content_type: application/json + + # Response specification + response: + # Success response + success: + status: integer # 200, 201, 204 + type_id: string # Reference to types section + is_array: boolean # If response is array of type + + # Error responses + errors: + - status: integer # 400, 401, 403, 404, 500 + type_id: string # Error response type (usually type_ApiError) + description: string + + # Authentication + auth: + required: boolean + roles: [string] # Required roles (empty = any authenticated) + + # Contract version for compatibility + version: string # Semantic version of this endpoint spec + + # Example + example_endpoint: + id: api_create_user + method: POST + path: /api/users + path_params: [] + query_params: [] + request_body: + type_id: type_CreateUserRequest + content_type: application/json + response: + success: + status: 201 + type_id: type_User + is_array: false + errors: + - status: 400 + type_id: type_ValidationError + description: "Invalid request body" + - status: 409 + type_id: type_ApiError + description: "Email already exists" + auth: + required: false + roles: [] + version: "1.0.0" + +# ============================================================================ +# FRONTEND USAGE CONTRACTS (What frontend expects to call) +# ============================================================================ +frontend_calls: + description: "Expected API calls from frontend components/pages" + + call_schema: + # Identity + id: string # call__ + + # Source + source: + entity_id: string # page_xxx or component_xxx + file_path: string # Expected file location + + # Target endpoint + endpoint_id: string # Reference to endpoints section + + # Call context + purpose: string # Why this call is made + trigger: string # What triggers this call (onLoad, onClick, onSubmit) + + # Data mapping + request_mapping: + # How component data maps to request + from_props: [string] # Props used in request + from_state: [string] # State used in request + from_form: [string] # Form fields used in request + + response_handling: + # How response is handled + success_action: string # What happens on success + error_action: string # What happens on error + + # Example + example_call: + id: call_signup_form_submit + source: + entity_id: component_signup_form + file_path: app/components/SignupForm.tsx + endpoint_id: api_create_user + purpose: "Submit registration form" + trigger: onSubmit + request_mapping: + from_form: [email, name, password] + response_handling: + success_action: "Redirect to dashboard" + error_action: "Display error message" + +# ============================================================================ +# BACKEND IMPLEMENTATION CONTRACTS (What backend must provide) +# ============================================================================ +backend_routes: + description: "Required backend route implementations" + + route_schema: + # Identity + id: string # route__ + + # Target endpoint + endpoint_id: string # Reference to endpoints section + + # Implementation location + file_path: string # Expected file (e.g., app/api/users/route.ts) + export_name: string # Exported function name (GET, POST, etc.) + + # Dependencies + uses_models: [string] # model_ids this route uses + uses_services: [string] # Service files this route depends on + + # Implementation requirements + must_validate: + - field: string + rule: string + must_authenticate: boolean + must_authorize: [string] # Role checks required + + # Example + example_route: + id: route_post_users + endpoint_id: api_create_user + file_path: app/api/users/route.ts + export_name: POST + uses_models: [model_user] + uses_services: [lib/auth.ts, lib/db.ts] + must_validate: + - field: email + rule: email + - field: password + rule: min:8 + must_authenticate: false + must_authorize: [] + +# ============================================================================ +# VALIDATION RULES +# ============================================================================ +validation_rules: + contracts: + - "Every frontend_call must reference existing endpoint_id" + - "Every backend_route must reference existing endpoint_id" + - "Request body type_id must exist in types section" + - "Response type_id must exist in types section" + - "Path params in endpoint must match :param patterns in path" + + types: + - "Every type must have unique id" + - "Type references (nested types) must exist" + - "Required properties cannot have default values" + + implementation: + - "Frontend must import types from shared types file" + - "Backend must import types from shared types file" + - "HTTP methods must match contract specification" + - "Response shapes must conform to type definitions" + +# ============================================================================ +# GENERATED FILES +# ============================================================================ +generated_files: + shared_types: + path: app/types/api.ts + description: "TypeScript interfaces for all API types" + template: | + // AUTO-GENERATED - DO NOT EDIT + // Source: .workflow/versions/vXXX/contracts/api_contract.yml + // Generated: {timestamp} + + // === Types === + {type_definitions} + + // === API Paths (for type-safe fetch calls) === + export const API_PATHS = { + {path_constants} + } as const; + + // === API Response Types === + {response_type_helpers} + + api_client: + path: app/lib/api-client.ts + description: "Type-safe API client (optional)" + template: | + // AUTO-GENERATED - DO NOT EDIT + // Type-safe API client generated from contract + + import type { * } from '@/types/api'; + + {api_client_methods} + +# ============================================================================ +# CONTRACT VIOLATION HANDLING +# ============================================================================ +violations: + severity_levels: + critical: + - "Endpoint exists in frontend but not backend" + - "Method mismatch (frontend calls POST, backend has GET)" + - "Required field missing in implementation" + high: + - "Response type mismatch" + - "Missing error handling for documented errors" + medium: + - "Extra undocumented endpoint in backend" + - "Type property order differs" + low: + - "Description mismatch" + - "Optional field handling differs" + + on_violation: + critical: "Block deployment, require immediate fix" + high: "Warn in review, require acknowledgment" + medium: "Report in review, fix recommended" + low: "Log for tracking" diff --git a/skills/guardrail-orchestrator/scripts/generate_api_contract.py b/skills/guardrail-orchestrator/scripts/generate_api_contract.py new file mode 100644 index 0000000..33ed7e4 --- /dev/null +++ b/skills/guardrail-orchestrator/scripts/generate_api_contract.py @@ -0,0 +1,615 @@ +#!/usr/bin/env python3 +""" +Generate API Contract and Shared Types from Design Document + +This script: +1. Reads the design_document.yml +2. Extracts all API endpoints and their types +3. Generates api_contract.yml with strict typing +4. Generates app/types/api.ts with shared TypeScript interfaces + +Both frontend and backend agents MUST use these generated files +to ensure contract compliance. +""" + +import os +import sys +import json +from pathlib import Path +from datetime import datetime +from typing import Dict, List, Any, Optional, Set + +try: + import yaml +except ImportError: + yaml = None + + +def load_yaml(path: Path) -> Dict: + """Load YAML file.""" + if yaml: + with open(path) as f: + return yaml.safe_load(f) + else: + # Fallback: simple YAML parser for basic cases + with open(path) as f: + content = f.read() + # Try JSON first (YAML is superset of JSON) + try: + return json.loads(content) + except: + print(f"Warning: yaml module not available, using basic parser", file=sys.stderr) + return {} + + +def save_yaml(data: Dict, path: Path) -> None: + """Save data as YAML.""" + if yaml: + with open(path, 'w') as f: + yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) + else: + # Fallback: JSON format + with open(path, 'w') as f: + json.dump(data, f, indent=2) + + +def ts_type_from_field(field: Dict) -> str: + """Convert design document field type to TypeScript type.""" + type_map = { + 'string': 'string', + 'text': 'string', + 'integer': 'number', + 'float': 'number', + 'decimal': 'number', + 'boolean': 'boolean', + 'datetime': 'Date', + 'date': 'Date', + 'uuid': 'string', + 'json': 'Record', + 'array': 'unknown[]', + } + + field_type = field.get('type', 'string') + + # Handle enum type + if field_type == 'enum': + enum_values = field.get('enum_values', []) + if enum_values: + return ' | '.join([f"'{v}'" for v in enum_values]) + return 'string' + + return type_map.get(field_type, 'unknown') + + +def generate_type_from_model(model: Dict) -> Dict: + """Generate TypeScript type definition from model.""" + type_id = f"type_{model['name']}" + properties = [] + + for field in model.get('fields', []): + # Skip internal fields like password_hash + if field['name'].endswith('_hash'): + continue + + constraints = field.get('constraints', []) + required = 'not_null' in constraints or 'primary_key' in constraints + + properties.append({ + 'name': to_camel_case(field['name']), + 'type': ts_type_from_field(field), + 'required': required, + 'description': field.get('description', ''), + }) + + return { + 'id': type_id, + 'name': model['name'], + 'definition': { + 'type': 'object', + 'properties': properties, + }, + 'used_by': { + 'models': [model['id']], + 'responses': [], + 'requests': [], + } + } + + +def generate_request_type(endpoint: Dict) -> Optional[Dict]: + """Generate request body type from endpoint definition.""" + request_body = endpoint.get('request_body', {}) + if not request_body: + return None + + schema = request_body.get('schema', {}) + if not schema: + return None + + # Generate type name from endpoint + parts = endpoint['id'].replace('api_', '').split('_') + type_name = ''.join([p.capitalize() for p in parts]) + 'Request' + type_id = f"type_{type_name}" + + properties = [] + for prop in schema.get('properties', []): + properties.append({ + 'name': to_camel_case(prop['name']), + 'type': ts_type_from_field(prop), + 'required': prop.get('required', False), + 'description': prop.get('description', ''), + 'validation': ','.join(prop.get('validations', [])) if prop.get('validations') else None, + }) + + return { + 'id': type_id, + 'name': type_name, + 'definition': { + 'type': 'object', + 'properties': properties, + }, + 'used_by': { + 'models': [], + 'responses': [], + 'requests': [endpoint['id']], + } + } + + +def generate_response_type(endpoint: Dict, models: Dict[str, Dict]) -> Optional[Dict]: + """Generate response type from endpoint definition - may reference model types.""" + responses = endpoint.get('responses', []) + success_response = None + + for resp in responses: + status = resp.get('status', 0) + if 200 <= status < 300: + success_response = resp + break + + if not success_response: + return None + + # Check if this response references a model + depends_on = endpoint.get('depends_on_models', []) + if depends_on: + # Response likely uses model type + primary_model = depends_on[0] + if primary_model in models: + return None # Will use model type directly + + # Generate custom response type + schema = success_response.get('schema', {}) + if not schema or schema.get('type') != 'object': + return None + + parts = endpoint['id'].replace('api_', '').split('_') + type_name = ''.join([p.capitalize() for p in parts]) + 'Response' + type_id = f"type_{type_name}" + + properties = [] + for prop in schema.get('properties', []): + properties.append({ + 'name': to_camel_case(prop['name']), + 'type': ts_type_from_field(prop), + 'required': True, + 'description': '', + }) + + return { + 'id': type_id, + 'name': type_name, + 'definition': { + 'type': 'object', + 'properties': properties, + }, + 'used_by': { + 'models': [], + 'responses': [endpoint['id']], + 'requests': [], + } + } + + +def to_camel_case(snake_str: str) -> str: + """Convert snake_case to camelCase.""" + components = snake_str.split('_') + return components[0] + ''.join(x.capitalize() for x in components[1:]) + + +def generate_endpoint_contract(endpoint: Dict, types: Dict[str, Dict], models: Dict[str, Dict]) -> Dict: + """Generate endpoint contract from design document endpoint.""" + # Determine request body type + request_body = None + if endpoint.get('request_body'): + # Generate request type name + parts = endpoint['id'].replace('api_', '').split('_') + type_name = ''.join([p.capitalize() for p in parts]) + 'Request' + request_body = { + 'type_id': f"type_{type_name}", + 'content_type': 'application/json', + } + + # Determine response type + response_type_id = None + is_array = False + + responses = endpoint.get('responses', []) + success_response = None + for resp in responses: + if 200 <= resp.get('status', 0) < 300: + success_response = resp + break + + if success_response: + # Check if referencing a model + depends_on = endpoint.get('depends_on_models', []) + if depends_on: + model_id = depends_on[0] + if model_id in models: + model_name = models[model_id].get('name', model_id.replace('model_', '').capitalize()) + response_type_id = f"type_{model_name}" + + # Check if response is array + schema = success_response.get('schema', {}) + if schema.get('type') == 'array': + is_array = True + + if not response_type_id: + parts = endpoint['id'].replace('api_', '').split('_') + type_name = ''.join([p.capitalize() for p in parts]) + 'Response' + response_type_id = f"type_{type_name}" + + # Extract path params + path_params = [] + for param in endpoint.get('path_params', []): + path_params.append({ + 'name': param['name'], + 'type': ts_type_from_field(param), + 'description': param.get('description', ''), + }) + + # Extract query params + query_params = [] + for param in endpoint.get('query_params', []): + query_params.append({ + 'name': param['name'], + 'type': ts_type_from_field(param), + 'required': param.get('required', False), + 'default': param.get('default'), + 'description': param.get('description', ''), + }) + + # Build error responses + error_responses = [] + for resp in responses: + status = resp.get('status', 0) + if status >= 400: + error_responses.append({ + 'status': status, + 'type_id': 'type_ApiError', + 'description': resp.get('description', ''), + }) + + return { + 'id': endpoint['id'], + 'method': endpoint['method'], + 'path': endpoint['path'], + 'path_params': path_params, + 'query_params': query_params, + 'request_body': request_body, + 'response': { + 'success': { + 'status': success_response.get('status', 200) if success_response else 200, + 'type_id': response_type_id, + 'is_array': is_array, + }, + 'errors': error_responses, + }, + 'auth': endpoint.get('auth', {'required': False, 'roles': []}), + 'version': '1.0.0', + } + + +def generate_frontend_calls(pages: List[Dict], components: List[Dict], endpoints: Dict[str, Dict]) -> List[Dict]: + """Generate frontend call contracts from pages and components.""" + calls = [] + + # From pages + for page in pages: + for data_need in page.get('data_needs', []): + api_id = data_need.get('api_id') + if api_id and api_id in endpoints: + calls.append({ + 'id': f"call_{page['id']}_{api_id}", + 'source': { + 'entity_id': page['id'], + 'file_path': f"app{page['path']}/page.tsx", + }, + 'endpoint_id': api_id, + 'purpose': data_need.get('purpose', 'Load data'), + 'trigger': 'onLoad' if data_need.get('on_load') else 'onDemand', + 'request_mapping': { + 'from_props': [], + 'from_state': [], + 'from_form': [], + }, + 'response_handling': { + 'success_action': 'Update state', + 'error_action': 'Show error', + }, + }) + + # From components + for component in components: + for api_id in component.get('uses_apis', []): + if api_id in endpoints: + endpoint = endpoints[api_id] + method = endpoint.get('method', 'GET') + trigger = 'onSubmit' if method in ['POST', 'PUT', 'PATCH'] else 'onDemand' + + calls.append({ + 'id': f"call_{component['id']}_{api_id}", + 'source': { + 'entity_id': component['id'], + 'file_path': f"app/components/{component['name']}.tsx", + }, + 'endpoint_id': api_id, + 'purpose': f"Call {api_id}", + 'trigger': trigger, + 'request_mapping': { + 'from_props': [], + 'from_state': [], + 'from_form': [], + }, + 'response_handling': { + 'success_action': 'Handle response', + 'error_action': 'Show error', + }, + }) + + return calls + + +def generate_backend_routes(endpoints: List[Dict]) -> List[Dict]: + """Generate backend route contracts from endpoints.""" + routes = [] + + for endpoint in endpoints: + # Determine file path from endpoint path + api_path = endpoint['path'].replace('/api/', '') + # Handle dynamic segments like /users/:id + parts = api_path.split('/') + file_parts = [] + for part in parts: + if part.startswith(':'): + file_parts.append(f"[{part[1:]}]") + else: + file_parts.append(part) + + file_path = f"app/api/{'/'.join(file_parts)}/route.ts" + + routes.append({ + 'id': f"route_{endpoint['method'].lower()}_{api_path.replace('/', '_')}", + 'endpoint_id': endpoint['id'], + 'file_path': file_path, + 'export_name': endpoint['method'], + 'uses_models': endpoint.get('depends_on_models', []), + 'uses_services': [], + 'must_validate': [], + 'must_authenticate': endpoint.get('auth', {}).get('required', False), + 'must_authorize': endpoint.get('auth', {}).get('roles', []), + }) + + return routes + + +def generate_typescript_types(types: List[Dict]) -> str: + """Generate TypeScript type definitions.""" + lines = [ + "// AUTO-GENERATED - DO NOT EDIT", + "// Source: .workflow/versions/vXXX/contracts/api_contract.yml", + f"// Generated: {datetime.now().isoformat()}", + "", + "// ============================================================================", + "// Shared API Types", + "// Both frontend and backend MUST import from this file", + "// ============================================================================", + "", + ] + + # Add standard error types + lines.extend([ + "// === Error Types ===", + "", + "export interface ApiError {", + " error: string;", + " message?: string;", + " code?: string;", + "}", + "", + "export interface ValidationError {", + " error: string;", + " details: string[];", + "}", + "", + ]) + + # Generate interfaces for each type + lines.append("// === Domain Types ===") + lines.append("") + + for type_def in types: + name = type_def['name'] + definition = type_def['definition'] + + if definition['type'] == 'object': + lines.append(f"export interface {name} {{") + for prop in definition.get('properties', []): + optional = '' if prop.get('required') else '?' + desc = prop.get('description', '') + if desc: + lines.append(f" /** {desc} */") + lines.append(f" {prop['name']}{optional}: {prop['type']};") + lines.append("}") + lines.append("") + + elif definition['type'] == 'enum': + values = definition.get('enum_values', []) + quoted_values = [f"'{v}'" for v in values] + lines.append(f"export type {name} = {' | '.join(quoted_values)};") + lines.append("") + + elif definition['type'] == 'union': + members = definition.get('union_members', []) + lines.append(f"export type {name} = {' | '.join(members)};") + lines.append("") + + return '\n'.join(lines) + + +def generate_api_paths(endpoints: List[Dict]) -> str: + """Generate API path constants for type-safe calls.""" + lines = [ + "", + "// === API Paths ===", + "", + "export const API_PATHS = {", + ] + + for endpoint in endpoints: + # Generate constant name: api_get_users -> GET_USERS + const_name = endpoint['id'].replace('api_', '').upper() + lines.append(f" {const_name}: '{endpoint['path']}' as const,") + + lines.append("} as const;") + lines.append("") + + return '\n'.join(lines) + + +def main(): + """Main entry point.""" + if len(sys.argv) < 2: + print("Usage: generate_api_contract.py [--output-dir ]", file=sys.stderr) + sys.exit(1) + + design_doc_path = Path(sys.argv[1]) + + # Parse output directory + output_dir = design_doc_path.parent.parent # .workflow/versions/vXXX/ + if '--output-dir' in sys.argv: + idx = sys.argv.index('--output-dir') + output_dir = Path(sys.argv[idx + 1]) + + if not design_doc_path.exists(): + print(f"Error: Design document not found: {design_doc_path}", file=sys.stderr) + sys.exit(1) + + # Load design document + design_doc = load_yaml(design_doc_path) + + if not design_doc: + print("Error: Failed to load design document", file=sys.stderr) + sys.exit(1) + + # Extract entities + models = {m['id']: m for m in design_doc.get('data_models', [])} + endpoints = design_doc.get('api_endpoints', []) + pages = design_doc.get('pages', []) + components = design_doc.get('components', []) + + workflow_version = design_doc.get('workflow_version', 'unknown') + revision = design_doc.get('revision', 1) + + # Generate types from models + types = [] + for model in models.values(): + type_def = generate_type_from_model(model) + types.append(type_def) + + # Generate request/response types from endpoints + endpoints_dict = {e['id']: e for e in endpoints} + for endpoint in endpoints: + req_type = generate_request_type(endpoint) + if req_type: + types.append(req_type) + + resp_type = generate_response_type(endpoint, models) + if resp_type: + types.append(resp_type) + + # Generate types dictionary for lookup + types_dict = {t['id']: t for t in types} + + # Generate endpoint contracts + endpoint_contracts = [] + for endpoint in endpoints: + contract = generate_endpoint_contract(endpoint, types_dict, models) + endpoint_contracts.append(contract) + + # Generate frontend calls + frontend_calls = generate_frontend_calls(pages, components, endpoints_dict) + + # Generate backend routes + backend_routes = generate_backend_routes(endpoints) + + # Build API contract + api_contract = { + 'api_contract': { + 'workflow_version': workflow_version, + 'design_document_revision': revision, + 'generated_at': datetime.now().isoformat(), + 'validated_at': None, + 'status': 'draft', + }, + 'types': types, + 'endpoints': endpoint_contracts, + 'frontend_calls': frontend_calls, + 'backend_routes': backend_routes, + } + + # Create output directories + contracts_dir = output_dir / 'contracts' + contracts_dir.mkdir(parents=True, exist_ok=True) + + # Save API contract + contract_path = contracts_dir / 'api_contract.yml' + save_yaml(api_contract, contract_path) + print(f"Generated: {contract_path}") + + # Generate TypeScript types + ts_types = generate_typescript_types(types) + ts_paths = generate_api_paths(endpoint_contracts) + + # Find project root (look for package.json) + project_root = output_dir + while project_root != project_root.parent: + if (project_root / 'package.json').exists(): + break + project_root = project_root.parent + + if not (project_root / 'package.json').exists(): + project_root = output_dir.parent.parent.parent # Assume .workflow is in project root + + # Create types directory and file + types_dir = project_root / 'app' / 'types' + types_dir.mkdir(parents=True, exist_ok=True) + + types_file = types_dir / 'api.ts' + types_file.write_text(ts_types + ts_paths) + print(f"Generated: {types_file}") + + # Summary + print("\n=== API CONTRACT GENERATED ===") + print(f"Types: {len(types)}") + print(f"Endpoints: {len(endpoint_contracts)}") + print(f"Frontend calls: {len(frontend_calls)}") + print(f"Backend routes: {len(backend_routes)}") + print(f"\nContract file: {contract_path}") + print(f"Types file: {types_file}") + print("\nBoth agents MUST import from app/types/api.ts") + + +if __name__ == '__main__': + main() diff --git a/skills/guardrail-orchestrator/scripts/validate_against_contract.py b/skills/guardrail-orchestrator/scripts/validate_against_contract.py new file mode 100644 index 0000000..f08329b --- /dev/null +++ b/skills/guardrail-orchestrator/scripts/validate_against_contract.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python3 +""" +Validate Implementation Against API Contract + +This script verifies that both backend and frontend implementations +comply with the generated API contract. + +Checks performed: +1. Backend routes exist and export correct HTTP methods +2. Frontend components import from shared types file +3. API calls use correct paths and methods +4. Types are properly imported (not recreated locally) + +Exit codes: + 0 = All validations pass + 1 = Warnings found (non-critical violations) + 2 = Critical violations (missing routes, type mismatches) +""" + +import os +import sys +import re +import json +from pathlib import Path +from typing import Dict, List, Any, Tuple, Optional + +try: + import yaml +except ImportError: + yaml = None + + +def load_yaml(path: Path) -> Dict: + """Load YAML file.""" + if yaml: + with open(path) as f: + return yaml.safe_load(f) + else: + with open(path) as f: + content = f.read() + try: + return json.loads(content) + except: + return {} + + +def find_project_root(start_dir: Path) -> Path: + """Find project root by looking for package.json.""" + current = start_dir.resolve() + while current != current.parent: + if (current / 'package.json').exists(): + return current + current = current.parent + return start_dir + + +class ContractValidator: + """Validates implementation against API contract.""" + + def __init__(self, contract_path: Path, project_dir: Path): + self.contract_path = contract_path + self.project_dir = project_dir + self.contract = load_yaml(contract_path) + self.violations: List[Dict[str, Any]] = [] + self.warnings: List[Dict[str, Any]] = [] + + def validate_all(self) -> Tuple[int, List[Dict], List[Dict]]: + """ + Run all validations. + + Returns: + Tuple of (exit_code, violations, warnings) + """ + # Validate backend + self.validate_backend_routes() + self.validate_backend_type_imports() + + # Validate frontend + self.validate_frontend_type_imports() + self.validate_frontend_api_calls() + + # Determine exit code + critical_count = len([v for v in self.violations if v.get('severity') == 'critical']) + warning_count = len(self.warnings) + + if critical_count > 0: + return 2, self.violations, self.warnings + elif len(self.violations) > 0: + return 1, self.violations, self.warnings + else: + return 0, self.violations, self.warnings + + def validate_backend_routes(self) -> None: + """Validate that all backend routes from contract exist.""" + backend_routes = self.contract.get('backend_routes', []) + + for route in backend_routes: + file_path = self.project_dir / route['file_path'] + endpoint_id = route.get('endpoint_id', 'unknown') + export_name = route.get('export_name', 'GET') + + if not file_path.exists(): + self.violations.append({ + 'type': 'missing_route', + 'severity': 'critical', + 'endpoint_id': endpoint_id, + 'expected_file': str(route['file_path']), + 'message': f"Backend route file missing: {route['file_path']}", + }) + continue + + # Check if file exports the correct HTTP method + content = file_path.read_text() + + # Check for Next.js App Router pattern: export async function GET/POST/etc. + export_pattern = rf'export\s+(async\s+)?function\s+{export_name}\s*\(' + if not re.search(export_pattern, content): + # Also check for const exports: export const GET = ... + const_pattern = rf'export\s+const\s+{export_name}\s*=' + if not re.search(const_pattern, content): + self.violations.append({ + 'type': 'missing_export', + 'severity': 'critical', + 'endpoint_id': endpoint_id, + 'file': str(route['file_path']), + 'expected_export': export_name, + 'message': f"Route {route['file_path']} missing {export_name} export", + }) + + def validate_backend_type_imports(self) -> None: + """Validate backend files import from shared types.""" + backend_routes = self.contract.get('backend_routes', []) + + for route in backend_routes: + file_path = self.project_dir / route['file_path'] + if not file_path.exists(): + continue # Already reported as missing + + content = file_path.read_text() + + # Check for import from @/types/api or ./types/api or ../types/api + import_patterns = [ + r"import\s+.*from\s+['\"]@/types/api['\"]", + r"import\s+.*from\s+['\"]\.+/types/api['\"]", + r"import\s+type\s+.*from\s+['\"]@/types/api['\"]", + ] + + has_import = any(re.search(p, content) for p in import_patterns) + + if not has_import: + self.warnings.append({ + 'type': 'missing_type_import', + 'severity': 'warning', + 'file': str(route['file_path']), + 'message': f"Backend route {route['file_path']} should import types from @/types/api", + }) + + # Check for local type declarations that might conflict + local_type_patterns = [ + r'(interface|type)\s+User\s*[={]', + r'(interface|type)\s+.*Request\s*[={]', + r'(interface|type)\s+.*Response\s*[={]', + ] + + for pattern in local_type_patterns: + match = re.search(pattern, content) + if match and 'import' not in content[:match.start()].split('\n')[-1]: + self.warnings.append({ + 'type': 'local_type_definition', + 'severity': 'warning', + 'file': str(route['file_path']), + 'message': f"Backend route defines local types. Should import from @/types/api instead.", + }) + break + + def validate_frontend_type_imports(self) -> None: + """Validate frontend files import from shared types.""" + frontend_calls = self.contract.get('frontend_calls', []) + + checked_files = set() + + for call in frontend_calls: + file_path_str = call.get('source', {}).get('file_path', '') + if not file_path_str or file_path_str in checked_files: + continue + + checked_files.add(file_path_str) + file_path = self.project_dir / file_path_str + + if not file_path.exists(): + # Check alternate paths (page vs component) + if '/components/' in file_path_str: + alt_path = file_path_str.replace('/components/', '/app/components/') + file_path = self.project_dir / alt_path + if not file_path.exists(): + self.violations.append({ + 'type': 'missing_frontend_file', + 'severity': 'high', + 'expected_file': file_path_str, + 'message': f"Frontend file missing: {file_path_str}", + }) + continue + else: + self.violations.append({ + 'type': 'missing_frontend_file', + 'severity': 'high', + 'expected_file': file_path_str, + 'message': f"Frontend file missing: {file_path_str}", + }) + continue + + content = file_path.read_text() + + # Check for import from @/types/api + import_patterns = [ + r"import\s+.*from\s+['\"]@/types/api['\"]", + r"import\s+.*from\s+['\"]\.+/types/api['\"]", + r"import\s+type\s+.*from\s+['\"]@/types/api['\"]", + ] + + has_import = any(re.search(p, content) for p in import_patterns) + + if not has_import: + self.warnings.append({ + 'type': 'missing_type_import', + 'severity': 'warning', + 'file': file_path_str, + 'message': f"Frontend file {file_path_str} should import types from @/types/api", + }) + + def validate_frontend_api_calls(self) -> None: + """Validate frontend API calls match contract.""" + frontend_calls = self.contract.get('frontend_calls', []) + endpoints = {e['id']: e for e in self.contract.get('endpoints', [])} + + for call in frontend_calls: + file_path_str = call.get('source', {}).get('file_path', '') + endpoint_id = call.get('endpoint_id', '') + + if not file_path_str or endpoint_id not in endpoints: + continue + + file_path = self.project_dir / file_path_str + + # Try alternate paths + if not file_path.exists(): + if '/components/' in file_path_str: + alt_path = file_path_str.replace('/components/', '/app/components/') + file_path = self.project_dir / alt_path + + if not file_path.exists(): + continue # Already reported + + content = file_path.read_text() + endpoint = endpoints[endpoint_id] + expected_method = endpoint.get('method', 'GET') + expected_path = endpoint.get('path', '') + + # Check for API call to this endpoint + # Look for fetch calls or axios calls + fetch_patterns = [ + rf"fetch\s*\(\s*['\"`][^'\"]*{re.escape(expected_path)}", + rf"fetch\s*\(\s*API_PATHS\.", + rf"axios\.{expected_method.lower()}\s*\(", + ] + + has_call = any(re.search(p, content, re.IGNORECASE) for p in fetch_patterns) + + # If component is supposed to call this API but doesn't, it might be a dynamic call + # or using a different pattern - this is a soft warning + # The important validation is that when they DO call, they use correct types + + def validate_types_file_exists(self) -> bool: + """Check if shared types file exists.""" + types_file = self.project_dir / 'app' / 'types' / 'api.ts' + if not types_file.exists(): + self.violations.append({ + 'type': 'missing_types_file', + 'severity': 'critical', + 'expected_file': 'app/types/api.ts', + 'message': "Shared types file missing: app/types/api.ts", + }) + return False + return True + + +def print_report(violations: List[Dict], warnings: List[Dict]) -> None: + """Print validation report.""" + print("\n" + "=" * 60) + print("API CONTRACT VALIDATION REPORT") + print("=" * 60) + + if not violations and not warnings: + print("\n✅ ALL VALIDATIONS PASSED") + print("\nBoth frontend and backend implementations comply with the API contract.") + return + + if violations: + print(f"\n❌ VIOLATIONS FOUND: {len(violations)}") + print("-" * 40) + + critical = [v for v in violations if v.get('severity') == 'critical'] + high = [v for v in violations if v.get('severity') == 'high'] + other = [v for v in violations if v.get('severity') not in ['critical', 'high']] + + if critical: + print("\n🔴 CRITICAL (Must fix):") + for v in critical: + print(f" • {v['message']}") + if 'expected_file' in v: + print(f" Expected: {v['expected_file']}") + + if high: + print("\n🟠 HIGH (Should fix):") + for v in high: + print(f" • {v['message']}") + + if other: + print("\n🟡 OTHER:") + for v in other: + print(f" • {v['message']}") + + if warnings: + print(f"\n⚠️ WARNINGS: {len(warnings)}") + print("-" * 40) + for w in warnings: + print(f" • {w['message']}") + + print("\n" + "=" * 60) + + if any(v.get('severity') == 'critical' for v in violations): + print("VERDICT: ❌ FAILED - Critical violations must be fixed") + elif violations: + print("VERDICT: ⚠️ WARNINGS - Review and fix if possible") + else: + print("VERDICT: ✅ PASSED with warnings") + + +def main(): + """Main entry point.""" + if len(sys.argv) < 2: + print("Usage: validate_against_contract.py [--project-dir ]", file=sys.stderr) + sys.exit(1) + + contract_path = Path(sys.argv[1]) + + # Parse project directory + project_dir = Path('.') + if '--project-dir' in sys.argv: + idx = sys.argv.index('--project-dir') + project_dir = Path(sys.argv[idx + 1]) + + project_dir = find_project_root(project_dir) + + if not contract_path.exists(): + print(f"Error: Contract file not found: {contract_path}", file=sys.stderr) + sys.exit(2) + + # Run validation + validator = ContractValidator(contract_path, project_dir) + + # First check types file exists + validator.validate_types_file_exists() + + # Run all validations + exit_code, violations, warnings = validator.validate_all() + + # Print report + print_report(violations, warnings) + + # Summary stats + print(f"\nValidation complete:") + print(f" Backend routes checked: {len(validator.contract.get('backend_routes', []))}") + print(f" Frontend calls checked: {len(validator.contract.get('frontend_calls', []))}") + print(f" Types defined: {len(validator.contract.get('types', []))}") + + sys.exit(exit_code) + + +if __name__ == '__main__': + main() diff --git a/skills/guardrail-orchestrator/scripts/validate_design.py b/skills/guardrail-orchestrator/scripts/validate_design.py index 9b846f2..867616c 100644 --- a/skills/guardrail-orchestrator/scripts/validate_design.py +++ b/skills/guardrail-orchestrator/scripts/validate_design.py @@ -31,7 +31,7 @@ except ImportError: # ============================================================================ def load_yaml(filepath: str) -> dict: - """Load YAML or JSON file.""" + """Load YAML file.""" if not os.path.exists(filepath): return {} with open(filepath, 'r') as f: @@ -39,25 +39,12 @@ def load_yaml(filepath: str) -> dict: if not content.strip(): return {} - # Handle JSON files directly (no PyYAML needed) - if filepath.endswith('.json'): - try: - return json.loads(content) or {} - except json.JSONDecodeError as e: - print(f"Error: Invalid JSON in {filepath}: {e}", file=sys.stderr) - return {} - - # Handle YAML files if HAS_YAML: return yaml.safe_load(content) or {} - # Basic fallback - try JSON parser for YAML (works for simple cases) - print(f"Warning: PyYAML not installed. Trying JSON parser for {filepath}", file=sys.stderr) - try: - return json.loads(content) or {} - except json.JSONDecodeError: - print(f"Error: Could not parse {filepath}. Install PyYAML: pip install pyyaml", file=sys.stderr) - return {} + # Basic fallback parser (limited) + print(f"Warning: Using basic YAML parser for {filepath}", file=sys.stderr) + return {} def save_yaml(filepath: str, data: dict): diff --git a/start-workflow.sh b/start-workflow.sh new file mode 100755 index 0000000..2181ff8 --- /dev/null +++ b/start-workflow.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# Auto-generated by Eureka Factory +# This script starts Claude Code with the workflow command + +cd "$(dirname "$0")" +claude '/workflow:spawn --auto a complete to earn app '