diff --git a/product-team/code-to-prd/SKILL.md b/product-team/code-to-prd/SKILL.md index 84ce2cb..3014a0a 100644 --- a/product-team/code-to-prd/SKILL.md +++ b/product-team/code-to-prd/SKILL.md @@ -1,4 +1,10 @@ --- +Name: code-to-prd +Tier: STANDARD +Category: product +Dependencies: none +Author: Alireza Rezvani +Version: 2.1.2 name: code-to-prd description: | Reverse-engineer any codebase into a complete Product Requirements Document (PRD). @@ -13,14 +19,17 @@ description: | or analyze backend routes. license: MIT metadata: - version: 2.1.2 - author: Alireza Rezvani - category: product - tier: STANDARD - dependencies: none updated: 2026-03-17 --- +## Name + +Code → PRD + +## Description + +Reverse-engineer any frontend, backend, or fullstack codebase into a complete Product Requirements Document (PRD). Analyzes routes, components, models, APIs, and user interactions to produce business-readable documentation detailed enough for engineers or AI agents to fully reconstruct every page and endpoint. + # Code → PRD: Reverse-Engineer Any Codebase into Product Requirements ## Features diff --git a/product-team/code-to-prd/expected_outputs/sample-enum-dictionary.md b/product-team/code-to-prd/expected_outputs/sample-enum-dictionary.md new file mode 100644 index 0000000..07908ca --- /dev/null +++ b/product-team/code-to-prd/expected_outputs/sample-enum-dictionary.md @@ -0,0 +1,25 @@ +# Enum Dictionary + +All enums, status codes, and constant mappings found in the codebase. + +## UserRole + +**Source:** `types/user.ts` +**Type:** TypeScript enum + +| Value | Label | Description | +|-------|-------|-------------| +| `admin` | Admin | Full system access, can manage all users | +| `manager` | Manager | Can view and edit users, cannot delete | +| `user` | User | Read-only access | + +## STATUS_MAP + +**Source:** `constants/status.ts` +**Type:** Constant map + +| Key | Display Value | Color | Description | +|-----|--------------|-------|-------------| +| `active` | Active | Green | Normal active account | +| `inactive` | Inactive | Gray | Account disabled by user | +| `suspended` | Suspended | Red | Account suspended by admin | diff --git a/product-team/code-to-prd/expected_outputs/sample-page-user-list.md b/product-team/code-to-prd/expected_outputs/sample-page-user-list.md new file mode 100644 index 0000000..569173e --- /dev/null +++ b/product-team/code-to-prd/expected_outputs/sample-page-user-list.md @@ -0,0 +1,83 @@ +# User List + +> **Route:** `/users` +> **Module:** User Management +> **Generated:** 2026-03-17 + +## Overview + +Displays all system users in a searchable, paginated table. Supports creating, editing, and deleting users. Only ADMIN and MANAGER roles can access this page. + +## Layout + +- **Top bar**: Search input + "Create User" button +- **Main area**: Data table with pagination +- **Modal**: Create/Edit user form (triggered by buttons) + +## Fields + +### Search Filters + +| Field | Type | Required | Options | Default | Notes | +|-------|------|----------|---------|---------|-------| +| Keyword | Text input | No | — | — | Searches name and email | +| Role | Select dropdown | No | Admin, Manager, User | All | Filters by role | +| Status | Select dropdown | No | Active, Inactive, Suspended | All | Filters by status | + +### Data Table + +| Column | Format | Sortable | Filterable | Notes | +|--------|--------|----------|-----------|-------| +| Name | Text | Yes | No | Full name | +| Email | Text (link) | Yes | No | Clickable → opens detail | +| Role | Badge | No | Yes | Color-coded by role | +| Status | Badge | No | Yes | Green=active, Red=suspended | +| Created | Date (YYYY-MM-DD) | Yes | No | — | +| Actions | Buttons | No | No | Edit, Delete | + +### Actions + +| Button | Visibility | Behavior | +|--------|-----------|----------| +| Create User | ADMIN, MANAGER | Opens create modal | +| Edit | ADMIN, MANAGER | Opens edit modal with prefilled data | +| Delete | ADMIN only | Confirmation dialog → soft delete | + +## Interactions + +### Page Load +- Fetches first page of users via `GET /api/users?page=1&size=20` +- Default sort: `created_at` descending + +### Search +- **Trigger:** User types in search field (300ms debounce) +- **Behavior:** Re-fetches users with `keyword` param, resets to page 1 +- **Special rules:** Minimum 2 characters to trigger search + +### Create User +- **Trigger:** Click "Create User" button +- **Modal content:** Name (required, max 50), Email (required, email format), Role (required, select), Status (default: Active) +- **Validation:** Name required + max length, Email required + format check +- **API:** `POST /api/users` with form data +- **On success:** Toast "User created", close modal, refresh list +- **On failure:** Show API error below form + +### Delete User +- **Trigger:** Click "Delete" button on row +- **Behavior:** Confirmation dialog "Are you sure you want to delete {name}?" +- **API:** `DELETE /api/users/:id` +- **On success:** Toast "User deleted", refresh list + +## API Dependencies + +| API | Method | Path | Trigger | Notes | +|-----|--------|------|---------|-------| +| List users | GET | /api/users | Load, search, paginate | Params: page, size, keyword, role, status | +| Create user | POST | /api/users | Submit create form | Body: name, email, role | +| Delete user | DELETE | /api/users/:id | Confirm delete | — | + +## Page Relationships + +- **From:** Dashboard (click "View Users" link) +- **To:** User Detail (click email or row) +- **Data coupling:** Creating/deleting a user triggers dashboard stats refresh diff --git a/product-team/code-to-prd/expected_outputs/sample-prd-readme.md b/product-team/code-to-prd/expected_outputs/sample-prd-readme.md new file mode 100644 index 0000000..1f5e315 --- /dev/null +++ b/product-team/code-to-prd/expected_outputs/sample-prd-readme.md @@ -0,0 +1,43 @@ +# My App — Product Requirements Document + +## System Overview + +My App is a user management platform for internal teams. It provides CRUD operations for users, a dashboard with key metrics, and system settings. Built with Next.js 14 (App Router) and Tailwind CSS. + +## Module Overview + +| Module | Pages | Core Functionality | +|--------|-------|--------------------| +| Dashboard | Dashboard | Key metrics, activity feed | +| User Management | User list, User detail | CRUD users, role assignment | +| Settings | Settings | System configuration | + +## Page Inventory + +| # | Page Name | Route | Module | Doc Link | +|---|-----------|-------|--------|----------| +| 1 | Home | / | — | [→](./pages/01-home.md) | +| 2 | Dashboard | /dashboard | Dashboard | [→](./pages/02-dashboard.md) | +| 3 | User List | /users | User Mgmt | [→](./pages/03-user-list.md) | +| 4 | User Detail | /users/:id | User Mgmt | [→](./pages/04-user-detail.md) | +| 5 | Settings | /settings | Settings | [→](./pages/05-settings.md) | + +## API Inventory + +| # | Method | Path | Status | Notes | +|---|--------|------|--------|-------| +| 1 | GET | /api/users | Integrated | Paginated list | +| 2 | POST | /api/users | Integrated | Create user | +| 3 | GET | /api/users/:id | Integrated | User detail | +| 4 | PUT | /api/users/:id | Integrated | Update user | +| 5 | GET | /api/dashboard/stats | Mock | Dashboard metrics | + +## Global Notes + +### Permission Model +Role-based access: ADMIN (full access), MANAGER (read + edit), USER (read-only). + +### Common Interaction Patterns +- All delete operations require confirmation modal +- Lists default to `created_at` descending, 20 items per page +- Form validation shows inline errors below each field diff --git a/product-team/code-to-prd/scripts/.gitignore b/product-team/code-to-prd/scripts/.gitignore new file mode 100644 index 0000000..d6ec751 --- /dev/null +++ b/product-team/code-to-prd/scripts/.gitignore @@ -0,0 +1 @@ +prd/ diff --git a/product-team/code-to-prd/scripts/prd_scaffolder.py b/product-team/code-to-prd/scripts/prd_scaffolder.py index cb06983..4724d91 100755 --- a/product-team/code-to-prd/scripts/prd_scaffolder.py +++ b/product-team/code-to-prd/scripts/prd_scaffolder.py @@ -311,30 +311,124 @@ def scaffold(analysis: Dict[str, Any], output_dir: Path, project_name: Optional[ print(f"\n Next: Review each page stub and fill in the TODO sections.") +def validate_analysis(analysis: Dict[str, Any]) -> List[str]: + """Validate analysis JSON has the required structure. Returns list of errors.""" + errors = [] + + if not isinstance(analysis, dict): + return ["Analysis must be a JSON object"] + + if "error" in analysis: + errors.append(f"Analysis contains error: {analysis['error']}") + + required_keys = ["project", "routes", "apis"] + for key in required_keys: + if key not in analysis: + errors.append(f"Missing required key: '{key}'") + + if "project" in analysis: + proj = analysis["project"] + if not isinstance(proj, dict): + errors.append("'project' must be an object") + elif "framework" not in proj: + errors.append("'project.framework' is missing") + + if "routes" in analysis: + routes = analysis["routes"] + if not isinstance(routes, dict): + errors.append("'routes' must be an object") + elif "pages" not in routes and "frontend_pages" not in routes and "backend_endpoints" not in routes: + errors.append("'routes' must contain 'pages', 'frontend_pages', or 'backend_endpoints'") + + if "apis" in analysis: + apis = analysis["apis"] + if not isinstance(apis, dict): + errors.append("'apis' must be an object") + elif "endpoints" not in apis: + errors.append("'apis.endpoints' is missing") + + return errors + + +def print_summary(output_dir: Path, analysis: Dict[str, Any]): + """Print a structured summary of what was generated.""" + routes = analysis.get("routes", {}).get("pages", []) + apis = analysis.get("apis", {}).get("endpoints", []) + enums = analysis.get("enums", {}).get("definitions", []) + models = analysis.get("models", {}).get("definitions", []) + summary = analysis.get("summary", {}) + stack = summary.get("stack_type", "unknown") + + print(f"\nPRD scaffold complete: {output_dir}/") + print(f" Stack type: {stack}") + print(f" Page stubs: {len(routes)}") + print(f" API endpoints: {len(apis)}") + print(f" Enums: {len(enums)}") + if models: + print(f" Models: {len(models)}") + print(f"\n Next: Review each page stub and fill in the TODO sections.") + + def main(): parser = argparse.ArgumentParser( - description="Scaffold PRD directory from frontend analysis" + description="Scaffold PRD directory from codebase analysis" ) - parser.add_argument("analysis", help="Path to analysis JSON from frontend_analyzer.py") + parser.add_argument("analysis", help="Path to analysis JSON from codebase_analyzer.py") parser.add_argument("-o", "--output-dir", default="prd", help="Output directory (default: prd/)") parser.add_argument("-n", "--project-name", help="Override project name") + parser.add_argument("--validate-only", action="store_true", + help="Validate analysis JSON without generating files") + parser.add_argument("--dry-run", action="store_true", + help="Show what would be created without writing files") args = parser.parse_args() analysis_path = Path(args.analysis) if not analysis_path.exists(): - print(f"Error: {analysis_path} not found") - return + print(f"Error: Analysis file not found: {analysis_path}") + raise SystemExit(2) - with open(analysis_path) as f: - analysis = json.load(f) + try: + with open(analysis_path) as f: + analysis = json.load(f) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in {analysis_path}: {e}") + raise SystemExit(2) - if "error" in analysis: - print(f"Error in analysis: {analysis['error']}") + # Validate + errors = validate_analysis(analysis) + if errors: + print(f"Validation errors in {analysis_path}:") + for err in errors: + print(f" - {err}") + raise SystemExit(1) + + if args.validate_only: + print(f"Analysis file is valid: {analysis_path}") + routes = analysis.get("routes", {}).get("pages", []) + print(f" {len(routes)} routes, " + f"{len(analysis.get('apis', {}).get('endpoints', []))} APIs, " + f"{len(analysis.get('enums', {}).get('definitions', []))} enums") return output_dir = Path(args.output_dir) + + if args.dry_run: + routes = analysis.get("routes", {}).get("pages", []) + print(f"Dry run — would create in {output_dir}/:\n") + print(f" {output_dir}/README.md") + for i, route in enumerate(routes, 1): + name = route_to_page_name(route.get("path", "/")) + slug = slugify(name) or f"page-{i}" + print(f" {output_dir}/pages/{i:02d}-{slug}.md") + print(f" {output_dir}/appendix/enum-dictionary.md") + print(f" {output_dir}/appendix/api-inventory.md") + print(f" {output_dir}/appendix/page-relationships.md") + print(f"\n Total: {len(routes) + 4} files") + return + print(f"Scaffolding PRD in {output_dir}/...\n") scaffold(analysis, output_dir, args.project_name) + print_summary(output_dir, analysis) if __name__ == "__main__":