From 4b7a084ee3692f0e17c33d7e7e26f759cc764812 Mon Sep 17 00:00:00 2001 From: Reza Rezvani Date: Tue, 17 Mar 2026 12:28:30 +0100 Subject: [PATCH] =?UTF-8?q?feat(code-to-prd):=20expand=20to=20fullstack=20?= =?UTF-8?q?=E2=80=94=20add=20NestJS,=20Django,=20Express,=20FastAPI=20supp?= =?UTF-8?q?ort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename frontend_analyzer.py → codebase_analyzer.py — now detects backend frameworks via package.json (NestJS, Express, Fastify) and project files (manage.py, requirements.txt for Django, FastAPI, Flask) - Add backend route extraction: NestJS @Controller/@Get decorators, Django urls.py path() patterns - Add model/entity extraction: Django models.Model fields, NestJS @Entity and DTO classes - Add stack_type detection (frontend / backend / fullstack) to analysis output - SKILL.md: add Supported Stacks table, backend directory guide, backend endpoint inventory template, backend page type strategies, backend pitfalls - references/framework-patterns.md: add NestJS, Express, Django, DRF, FastAPI pattern tables + database model patterns + backend validation patterns - references/prd-quality-checklist.md: add backend-specific checks (endpoints, DTOs, models, admin, middleware, migrations) - Update all descriptions and keywords across plugin.json, settings.json, marketplace.json, and /code-to-prd command Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude-plugin/marketplace.json | 10 +- commands/code-to-prd.md | 6 +- .../code-to-prd/.claude-plugin/plugin.json | 2 +- product-team/code-to-prd/SKILL.md | 85 ++++- .../references/framework-patterns.md | 109 +++++- .../references/prd-quality-checklist.md | 11 + ...ntend_analyzer.py => codebase_analyzer.py} | 319 +++++++++++++++--- product-team/code-to-prd/settings.json | 4 +- 8 files changed, 483 insertions(+), 63 deletions(-) rename product-team/code-to-prd/scripts/{frontend_analyzer.py => codebase_analyzer.py} (56%) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 15edbcc..389793b 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -396,7 +396,7 @@ { "name": "code-to-prd", "source": "./product-team/code-to-prd", - "description": "Reverse-engineer any frontend codebase into a complete PRD. Analyzes routes, components, state, APIs, and interactions. Framework-agnostic: React, Vue, Angular, Svelte, Next.js. 2 Python scripts (frontend_analyzer, prd_scaffolder), 2 reference guides, /code-to-prd slash command.", + "description": "Reverse-engineer any codebase into a complete PRD. Frontend (React, Vue, Angular, Next.js), backend (NestJS, Django, Express, FastAPI), and fullstack. 2 Python scripts (codebase_analyzer, prd_scaffolder), 2 reference guides, /code-to-prd slash command.", "version": "2.1.2", "author": { "name": "Alireza Rezvani" @@ -406,11 +406,17 @@ "product-requirements", "reverse-engineering", "frontend", + "backend", + "fullstack", "documentation", "code-analysis", "react", "vue", - "angular" + "angular", + "nestjs", + "django", + "fastapi", + "express" ], "category": "product" } diff --git a/commands/code-to-prd.md b/commands/code-to-prd.md index ccb2ae4..1bace0d 100644 --- a/commands/code-to-prd.md +++ b/commands/code-to-prd.md @@ -17,7 +17,7 @@ Reverse-engineer a frontend codebase into a complete Product Requirements Docume ## What It Does -1. **Scan** — Run `frontend_analyzer.py` to detect framework, routes, APIs, enums, and project structure +1. **Scan** — Run `codebase_analyzer.py` to detect framework, routes, APIs, enums, and project structure 2. **Scaffold** — Run `prd_scaffolder.py` to create `prd/` directory with README.md, per-page stubs, and appendix files 3. **Analyze** — Walk through each page following the Phase 2 workflow: fields, interactions, API dependencies, page relationships 4. **Generate** — Produce the final PRD with all pages, enum dictionary, API inventory, and page relationship map @@ -29,7 +29,7 @@ Reverse-engineer a frontend codebase into a complete Product Requirements Docume Determine the project path (default: current directory). Run the frontend analyzer: ```bash -python3 {skill_path}/scripts/frontend_analyzer.py {project_path} -o .code-to-prd-analysis.json +python3 {skill_path}/scripts/codebase_analyzer.py {project_path} -o .code-to-prd-analysis.json ``` Display a summary of findings: framework, page count, API count, enum count. @@ -73,6 +73,6 @@ A `prd/` directory containing: ## Skill Reference - `product-team/code-to-prd/SKILL.md` -- `product-team/code-to-prd/scripts/frontend_analyzer.py` +- `product-team/code-to-prd/scripts/codebase_analyzer.py` - `product-team/code-to-prd/scripts/prd_scaffolder.py` - `product-team/code-to-prd/references/prd-quality-checklist.md` diff --git a/product-team/code-to-prd/.claude-plugin/plugin.json b/product-team/code-to-prd/.claude-plugin/plugin.json index 86d11f7..9c9461a 100644 --- a/product-team/code-to-prd/.claude-plugin/plugin.json +++ b/product-team/code-to-prd/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "code-to-prd", - "description": "Reverse-engineer any frontend codebase into a complete Product Requirements Document (PRD). Analyzes routes, components, state, APIs, and interactions to generate business-readable documentation that engineers or AI agents can use to fully reconstruct every page. Framework-agnostic: React, Vue, Angular, Svelte, Next.js, Nuxt, and more.", + "description": "Reverse-engineer any codebase into a complete Product Requirements Document (PRD). Analyzes routes, components, models, APIs, and interactions for frontend (React, Vue, Angular, Next.js), backend (NestJS, Django, Express, FastAPI), and fullstack applications.", "version": "2.1.2", "author": { "name": "Alireza Rezvani", diff --git a/product-team/code-to-prd/SKILL.md b/product-team/code-to-prd/SKILL.md index 42c73b8..1872f33 100644 --- a/product-team/code-to-prd/SKILL.md +++ b/product-team/code-to-prd/SKILL.md @@ -1,17 +1,19 @@ --- name: code-to-prd description: | - Reverse-engineer any frontend codebase into a complete Product Requirements Document (PRD). + Reverse-engineer any codebase into a complete Product Requirements Document (PRD). Analyzes routes, components, state management, API integrations, and user interactions to produce business-readable documentation detailed enough for engineers or AI agents to fully reconstruct - every page. Framework-agnostic: works with React, Vue, Angular, Svelte, Next.js, Nuxt, and more. + every page and endpoint. Works with frontend frameworks (React, Vue, Angular, Svelte, Next.js, Nuxt), + backend frameworks (NestJS, Django, Express, FastAPI), and fullstack applications. Trigger when users mention: generate PRD, reverse-engineer requirements, code to documentation, extract product specs from code, document page logic, analyze page fields and interactions, - create a functional inventory, or write requirements from an existing codebase. + create a functional inventory, write requirements from an existing codebase, document API endpoints, + or analyze backend routes. --- -# Code → PRD: Reverse-Engineer Frontend into Product Requirements +# Code → PRD: Reverse-Engineer Any Codebase into Product Requirements ## Role @@ -24,6 +26,16 @@ You are a senior product analyst and technical architect. Your job is to read a Your document must describe functionality in non-technical language while omitting zero business details. +### Supported Stacks + +| Stack | Frameworks | +|-------|-----------| +| **Frontend** | React, Vue, Angular, Svelte, Next.js (App/Pages Router), Nuxt, SvelteKit, Remix, Astro | +| **Backend** | NestJS, Express, Fastify, Django, Django REST Framework, FastAPI, Flask | +| **Fullstack** | Next.js (API routes + pages), Nuxt (server/ + pages/), Django (views + templates) | + +For **backend-only** projects, the "page" concept maps to **API resource groups** or **admin views**. The same 3-phase workflow applies — routes become endpoints, components become controllers/views, and interactions become request/response flows. + --- ## Workflow @@ -37,16 +49,34 @@ Build global context before diving into pages. Scan the root directory and understand organization: ``` -Key directories: +Frontend directories: - Pages/routes (pages/, views/, routes/, app/, src/pages/) - Components (components/, modules/) - Route config (router.ts, routes.ts, App.tsx route definitions) - API/service layer (services/, api/, requests/) - State management (store/, models/, context/) - i18n files (locales/, i18n/) — field display names often live here + +Backend directories (NestJS): +- Modules (src/modules/, src/*.module.ts) +- Controllers (*.controller.ts) — route handlers +- Services (*.service.ts) — business logic +- DTOs (dto/, *.dto.ts) — request/response shapes +- Entities (entities/, *.entity.ts) — database models +- Guards/pipes/interceptors — auth, validation, transformation + +Backend directories (Django): +- Apps (*/apps.py, */views.py, */models.py, */urls.py) +- URL config (urls.py, */urls.py) +- Views (views.py, viewsets.py) — route handlers +- Models (models.py) — database schema +- Serializers (serializers.py) — request/response shapes +- Forms (forms.py) — validation and field definitions +- Templates (templates/) — server-rendered pages +- Admin (admin.py) — admin panel configuration ``` -**Identify framework** from `package.json` (React / Vue / Angular / Svelte / etc.). Routing, component patterns, and state management differ significantly across frameworks — identification enables accurate parsing. +**Identify framework** from `package.json` (Node.js frameworks) or project files (`manage.py` for Django, `requirements.txt`/`pyproject.toml` for Python). Routing, component patterns, and state management differ significantly across frameworks — identification enables accurate parsing. #### 2. Build Route & Page Inventory @@ -61,6 +91,19 @@ Extract all pages from route config into a complete **page inventory**: For file-system routing (Next.js, Nuxt), infer from directory structure. +**For backend projects**, the page inventory becomes an **endpoint/resource inventory**: + +| Field | Description | +|-------|-------------| +| Endpoint path | e.g. `/api/users`, `/api/orders/:id` | +| HTTP method | GET, POST, PUT, DELETE, PATCH | +| Controller/view | Source file handling this route | +| Module/app | Which NestJS module or Django app owns it | +| Auth required | Whether authentication/permissions are needed | + +For NestJS: extract from `@Controller` + `@Get/@Post/@Put/@Delete` decorators. +For Django: extract from `urls.py` → `urlpatterns` and `viewsets.py` → router registrations. + #### 3. Map Global Context Before analyzing individual pages, capture: @@ -69,8 +112,11 @@ Before analyzing individual pages, capture: - **Shared components** — layout, nav, auth guards, error boundaries - **Enums & constants** — status codes, type mappings, role definitions - **API base config** — base URL, interceptors, auth headers, error handling +- **Database models** (backend) — entity relationships, field types, constraints +- **Middleware** (backend) — auth middleware, rate limiting, logging, CORS +- **DTOs/Serializers** (backend) — request validation shapes, response formats -These will be referenced throughout page analysis. +These will be referenced throughout page/endpoint analysis. --- @@ -309,6 +355,8 @@ Each page's Markdown should be **standalone** — reading just that file gives c ## Page Type Strategies +### Frontend Pages + | Page Type | Focus Areas | |-----------|------------| | **List / Table** | Search conditions, columns, row actions, pagination, bulk ops | @@ -317,6 +365,17 @@ Each page's Markdown should be **standalone** — reading just that file gives c | **Modal / Drawer** | Describe as part of triggering page — not a separate file. But fully document content | | **Dashboard** | Data cards, charts, metrics meaning, filter dimensions, refresh frequency | +### Backend Endpoints (NestJS / Django / Express) + +| Endpoint Type | Focus Areas | +|---------------|------------| +| **CRUD resource** | All fields (from DTO/serializer), validation rules, permissions, pagination, filtering, sorting | +| **Auth endpoints** | Login/register flow, token format, refresh logic, password reset, OAuth providers | +| **File upload** | Accepted types, size limits, storage destination, processing pipeline | +| **Webhook / event** | Trigger conditions, payload shape, retry policy, idempotency | +| **Background job** | Trigger, schedule, input/output, failure handling, monitoring | +| **Admin views** (Django) | Registered models, list_display, search_fields, filters, inline models, custom actions | + --- ## Execution Pacing @@ -337,6 +396,10 @@ Each page's Markdown should be **standalone** — reading just that file gives c | Ignoring dynamic route params | `/order/:id` = page requires an order ID to load | | Forgetting permission controls | Document which roles see which buttons/pages | | Assuming all APIs are real | Check for mock data patterns before documenting endpoints | +| Skipping Django admin customization | `admin.py` often contains critical business rules (list filters, custom actions, inlines) | +| Missing NestJS guards/pipes | `@UseGuards`, `@UsePipes` contain auth and validation logic that affects behavior | +| Ignoring database constraints | Model field constraints (unique, max_length, choices) are validation rules for the PRD | +| Overlooking middleware | Auth middleware, rate limiters, and CORS config define system-wide behavior | --- @@ -346,16 +409,16 @@ Each page's Markdown should be **standalone** — reading just that file gives c | Script | Purpose | Usage | |--------|---------|-------| -| `scripts/frontend_analyzer.py` | Scan codebase → extract routes, APIs, enums, structure | `python3 frontend_analyzer.py /path/to/project` | +| `scripts/codebase_analyzer.py` | Scan codebase → extract routes, APIs, models, enums, structure | `python3 codebase_analyzer.py /path/to/project` | | `scripts/prd_scaffolder.py` | Generate PRD directory skeleton from analysis JSON | `python3 prd_scaffolder.py analysis.json` | **Recommended workflow:** ```bash -# 1. Analyze the project (JSON output) -python3 scripts/frontend_analyzer.py /path/to/project -o analysis.json +# 1. Analyze the project (JSON output — works for frontend, backend, or fullstack) +python3 scripts/codebase_analyzer.py /path/to/project -o analysis.json # 2. Review the analysis (markdown summary) -python3 scripts/frontend_analyzer.py /path/to/project -f markdown +python3 scripts/codebase_analyzer.py /path/to/project -f markdown # 3. Scaffold the PRD directory with stubs python3 scripts/prd_scaffolder.py analysis.json -o prd/ -n "My App" diff --git a/product-team/code-to-prd/references/framework-patterns.md b/product-team/code-to-prd/references/framework-patterns.md index e3916cc..549a67f 100644 --- a/product-team/code-to-prd/references/framework-patterns.md +++ b/product-team/code-to-prd/references/framework-patterns.md @@ -1,6 +1,6 @@ # Framework-Specific Patterns -Quick reference for identifying routes, components, state, and APIs across frontend frameworks. +Quick reference for identifying routes, components, state, and APIs across frontend and backend frameworks. ## React (CRA / Vite) @@ -76,6 +76,71 @@ Quick reference for identifying routes, components, state, and APIs across front | API routes | `+server.ts` | | State | Svelte stores (`writable`, `readable`, `derived`) | +## NestJS + +| Aspect | Where to Look | +|--------|--------------| +| Routes | `@Controller('prefix')` + `@Get()/@Post()/@Put()/@Delete()` decorators | +| Modules | `*.module.ts` — `@Module({ controllers, providers, imports })` | +| Services | `*.service.ts` — injected via constructor, contains business logic | +| DTOs | `*.dto.ts` — `class-validator` decorators define validation rules | +| Entities | `*.entity.ts` — TypeORM `@Entity()` / Prisma schemas | +| Auth | `@UseGuards(AuthGuard)`, `@Roles('admin')`, Passport strategies | +| Middleware | `*.middleware.ts`, registered in module `configure()` | +| Pipes | `ValidationPipe`, `ParseIntPipe` — input transformation | +| Config | `ConfigModule`, `.env` files, `config/` directory | + +## Express / Fastify + +| Aspect | Where to Look | +|--------|--------------| +| Routes | `router.get('/path', handler)`, `app.post('/path', ...)` | +| Middleware | `app.use(...)`, `router.use(...)` | +| Controllers | Route handler files in `routes/`, `controllers/` | +| Models | Mongoose schemas (`*.model.ts`), Sequelize models, Prisma | +| Auth | `passport`, `jsonwebtoken`, middleware auth checks | +| Validation | `express-validator`, `joi`, `zod`, custom middleware | + +## Django + +| Aspect | Where to Look | +|--------|--------------| +| Routes | `urls.py` — `urlpatterns = [path('...', view)]` | +| Views | `views.py` — function-based or class-based views (`APIView`, `ViewSet`) | +| Models | `models.py` — `class MyModel(models.Model)` with field definitions | +| Forms | `forms.py` — `ModelForm`, `Form` with validation | +| Serializers | `serializers.py` (DRF) — `ModelSerializer`, field-level validation | +| Admin | `admin.py` — `@admin.register`, `list_display`, `search_fields`, `list_filter` | +| Templates | `templates/` — Jinja2/Django template HTML files | +| Middleware | `MIDDLEWARE` in `settings.py` | +| Auth | `django.contrib.auth`, `rest_framework.permissions`, `@login_required` | +| Signals | `signals.py` — `post_save`, `pre_delete` hooks (hidden business logic) | +| Management commands | `management/commands/` — CLI operations | +| Celery tasks | `tasks.py` — async/background operations | + +## Django REST Framework (DRF) + +| Aspect | Where to Look | +|--------|--------------| +| Endpoints | `router.register('prefix', ViewSet)` in `urls.py` | +| ViewSets | `viewsets.py` — `ModelViewSet` (full CRUD), `ReadOnlyModelViewSet` | +| Serializers | `serializers.py` — field types, validators, nested relations | +| Permissions | `permission_classes = [IsAuthenticated, IsAdminUser]` | +| Filtering | `django-filter`, `search_fields`, `ordering_fields` | +| Pagination | `DEFAULT_PAGINATION_CLASS` in settings, per-view override | +| Throttling | `DEFAULT_THROTTLE_CLASSES`, per-view `throttle_classes` | + +## FastAPI + +| Aspect | Where to Look | +|--------|--------------| +| Routes | `@app.get('/path')`, `@router.post('/path')` decorators | +| Models | Pydantic `BaseModel` classes — request/response schemas | +| Dependencies | `Depends(...)` — auth, DB sessions, shared logic | +| DB | SQLAlchemy models, Tortoise ORM, or raw SQL | +| Auth | `OAuth2PasswordBearer`, JWT middleware, `Depends(get_current_user)` | +| Background | `BackgroundTasks`, Celery integration | + ## Common Patterns Across Frameworks ### Mock Detection @@ -118,4 +183,46 @@ rules="required|email|max:100" # Angular Reactive Forms Validators.required, Validators.minLength(3), Validators.pattern(...) + +# NestJS (class-validator) +@IsString() @IsNotEmpty() @MaxLength(50) name: string; +@IsEmail() email: string; +@IsEnum(UserRole) role: UserRole; + +# Django Forms +name = forms.CharField(max_length=50, required=True) +email = forms.EmailField() + +# DRF Serializers +name = serializers.CharField(max_length=50) +email = serializers.EmailField(required=True) + +# FastAPI (Pydantic) +name: str = Field(max_length=50) +email: EmailStr +``` + +### Database Model Patterns +``` +# Django +class Order(models.Model): + status = models.CharField(max_length=20, choices=STATUS_CHOICES) + user = models.ForeignKey(User, on_delete=models.CASCADE) + total = models.DecimalField(max_digits=10, decimal_places=2) + +# TypeORM (NestJS) +@Entity() +export class Order { + @Column({ type: 'enum', enum: OrderStatus }) + status: OrderStatus; + @ManyToOne(() => User) + user: User; +} + +# Prisma +model Order { + status OrderStatus + user User @relation(fields: [userId], references: [id]) + total Decimal +} ``` diff --git a/product-team/code-to-prd/references/prd-quality-checklist.md b/product-team/code-to-prd/references/prd-quality-checklist.md index c956b91..6c2009f 100644 --- a/product-team/code-to-prd/references/prd-quality-checklist.md +++ b/product-team/code-to-prd/references/prd-quality-checklist.md @@ -42,6 +42,17 @@ Use this checklist to validate generated PRDs before delivery. - [ ] `prd/appendix/page-relationships.md` exists - [ ] Cross-references use relative links +## Backend-Specific Checks + +- [ ] All controller/view endpoints documented with method, path, auth +- [ ] DTO/serializer fields listed with type, required, validation +- [ ] Database model relationships mapped (FK, M2M, O2O) +- [ ] Django admin customizations documented (list_display, actions, inlines) +- [ ] Background tasks/Celery jobs documented with trigger and schedule +- [ ] Middleware pipeline documented (auth, logging, rate limiting) +- [ ] Environment-dependent behavior noted (dev vs prod differences) +- [ ] Database migrations reviewed for field constraints and defaults + ## Common Issues to Watch | Issue | How to Detect | Fix | diff --git a/product-team/code-to-prd/scripts/frontend_analyzer.py b/product-team/code-to-prd/scripts/codebase_analyzer.py similarity index 56% rename from product-team/code-to-prd/scripts/frontend_analyzer.py rename to product-team/code-to-prd/scripts/codebase_analyzer.py index 9d9247f..3cd6dea 100755 --- a/product-team/code-to-prd/scripts/frontend_analyzer.py +++ b/product-team/code-to-prd/scripts/codebase_analyzer.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 -"""Analyze a frontend codebase and extract page inventory, routes, APIs, and project structure. +"""Analyze any codebase (frontend, backend, or fullstack) and extract routes, APIs, models, and structure. +Supports: React, Vue, Angular, Svelte, Next.js, Nuxt, NestJS, Express, Django, FastAPI, Flask. Stdlib only — no third-party dependencies. Outputs JSON for downstream PRD generation. Usage: - python3 frontend_analyzer.py /path/to/project - python3 frontend_analyzer.py /path/to/project --output prd-analysis.json - python3 frontend_analyzer.py /path/to/project --format markdown + python3 codebase_analyzer.py /path/to/project + python3 codebase_analyzer.py /path/to/project --output prd-analysis.json + python3 codebase_analyzer.py /path/to/project --format markdown """ from __future__ import annotations @@ -23,6 +24,8 @@ IGNORED_DIRS = { ".git", "node_modules", ".next", "dist", "build", "coverage", "venv", ".venv", "__pycache__", ".nuxt", ".output", ".cache", ".turbo", ".vercel", "out", "storybook-static", + ".tox", ".mypy_cache", ".pytest_cache", "htmlcov", "staticfiles", + "media", "migrations", "egg-info", } FRAMEWORK_SIGNALS = { @@ -36,6 +39,16 @@ FRAMEWORK_SIGNALS = { "solid": ["solid-js"], "astro": ["astro"], "remix": ["@remix-run/react"], + "nestjs": ["@nestjs/core"], + "express": ["express"], + "fastify": ["fastify"], +} + +# Python backend frameworks detected via project files (no package.json) +PYTHON_FRAMEWORK_FILES = { + "django": ["manage.py", "settings.py"], + "fastapi": ["main.py"], # confirmed via imports + "flask": ["app.py"], # confirmed via imports } ROUTE_FILE_PATTERNS = [ @@ -65,6 +78,19 @@ I18N_DIR_PATTERNS = [ "src/locales", "src/i18n", "src/lang", ] +# Backend-specific directory patterns +CONTROLLER_DIR_PATTERNS = [ + "controllers", "src/controllers", "src/modules", +] + +MODEL_DIR_PATTERNS = [ + "models", "entities", "src/entities", "src/models", +] + +DTO_DIR_PATTERNS = [ + "dto", "dtos", "src/dto", "serializers", +] + MOCK_SIGNALS = [ r"setTimeout\s*\(.*\breturn\b", r"Promise\.resolve\s*\(", @@ -108,32 +134,82 @@ API_PATH_PATTERNS = [ ] COMPONENT_EXTENSIONS = {".tsx", ".jsx", ".vue", ".svelte", ".astro"} -CODE_EXTENSIONS = {".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro"} +CODE_EXTENSIONS = {".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro", ".py"} + +# NestJS decorator patterns +NEST_ROUTE_PATTERNS = [ + r"@(?:Get|Post|Put|Delete|Patch|Head|Options|All)\s*\(\s*['\"]([^'\"]*)['\"]", + r"@Controller\s*\(\s*['\"]([^'\"]*)['\"]", +] + +# Django URL patterns +DJANGO_ROUTE_PATTERNS = [ + r"path\s*\(\s*['\"]([^'\"]+)['\"]", + r"url\s*\(\s*r?['\"]([^'\"]+)['\"]", + r"register\s*\(\s*r?['\"]([^'\"]+)['\"]", +] + +# Django/Python model patterns +PYTHON_MODEL_PATTERNS = [ + r"class\s+(\w+)\s*\(.*?models\.Model\)", + r"class\s+(\w+)\s*\(.*?BaseModel\)", # Pydantic +] + +# NestJS entity/DTO patterns +NEST_MODEL_PATTERNS = [ + r"@Entity\s*\(.*?\)\s*(?:export\s+)?class\s+(\w+)", + r"class\s+(\w+(?:Dto|DTO|Entity|Schema))\b", +] def detect_framework(project_root: Path) -> Dict[str, Any]: - """Detect frontend framework from package.json.""" - pkg_path = project_root / "package.json" - if not pkg_path.exists(): - return {"framework": "unknown", "dependencies": {}} - - try: - with open(pkg_path) as f: - pkg = json.load(f) - except (json.JSONDecodeError, IOError): - return {"framework": "unknown", "dependencies": {}} - - all_deps = {} - for key in ("dependencies", "devDependencies", "peerDependencies"): - all_deps.update(pkg.get(key, {})) - + """Detect framework from package.json (Node.js) or project files (Python).""" detected = [] - for framework, signals in FRAMEWORK_SIGNALS.items(): - if any(s in all_deps for s in signals): - detected.append(framework) + all_deps = {} + pkg_name = "" + pkg_version = "" - # Prefer specific over generic (next > react, nuxt > vue) - priority = ["sveltekit", "next", "nuxt", "remix", "astro", "angular", "svelte", "vue", "react", "solid"] + # Node.js detection via package.json + pkg_path = project_root / "package.json" + if pkg_path.exists(): + try: + with open(pkg_path) as f: + pkg = json.load(f) + pkg_name = pkg.get("name", "") + pkg_version = pkg.get("version", "") + for key in ("dependencies", "devDependencies", "peerDependencies"): + all_deps.update(pkg.get(key, {})) + for framework, signals in FRAMEWORK_SIGNALS.items(): + if any(s in all_deps for s in signals): + detected.append(framework) + except (json.JSONDecodeError, IOError): + pass + + # Python backend detection via project files and imports + if (project_root / "manage.py").exists(): + detected.append("django") + if (project_root / "requirements.txt").exists() or (project_root / "pyproject.toml").exists(): + for req_file in ["requirements.txt", "pyproject.toml", "setup.py", "Pipfile"]: + req_path = project_root / req_file + if req_path.exists(): + try: + content = req_path.read_text(errors="replace").lower() + if "django" in content and "django" not in detected: + detected.append("django") + if "fastapi" in content: + detected.append("fastapi") + if "flask" in content and "flask" not in detected: + detected.append("flask") + except IOError: + pass + + # Prefer specific over generic + priority = [ + "sveltekit", "next", "nuxt", "remix", "astro", # fullstack JS + "nestjs", "express", "fastify", # backend JS + "django", "fastapi", "flask", # backend Python + "angular", "svelte", "vue", "react", "solid", # frontend JS + ] framework = "unknown" for fw in priority: if fw in detected: @@ -142,15 +218,17 @@ def detect_framework(project_root: Path) -> Dict[str, Any]: return { "framework": framework, - "name": pkg.get("name", ""), - "version": pkg.get("version", ""), + "name": pkg_name or project_root.name, + "version": pkg_version, "detected_frameworks": detected, "dependency_count": len(all_deps), "key_deps": {k: v for k, v in all_deps.items() if any(s in k for s in ["router", "redux", "vuex", "pinia", "zustand", "mobx", "recoil", "jotai", "tanstack", "swr", "axios", "tailwind", "material", "ant", - "chakra", "shadcn", "i18n", "intl"])}, + "chakra", "shadcn", "i18n", "intl", + "typeorm", "prisma", "sequelize", "mongoose", + "passport", "jwt", "class-validator"])}, } @@ -288,6 +366,91 @@ def extract_enums(filepath: Path) -> List[Dict[str, Any]]: return enums +def extract_backend_routes(filepath: Path, framework: str) -> List[Dict[str, str]]: + """Extract route definitions from NestJS controllers or Django url configs.""" + routes = [] + try: + content = filepath.read_text(errors="replace") + except IOError: + return routes + + patterns = [] + if framework in ("nestjs", "express", "fastify"): + patterns = NEST_ROUTE_PATTERNS + elif framework == "django": + patterns = DJANGO_ROUTE_PATTERNS + + # For NestJS, also grab the controller prefix + controller_prefix = "" + if framework == "nestjs": + m = re.search(r"@Controller\s*\(\s*['\"]([^'\"]*)['\"]", content) + if m: + controller_prefix = "/" + m.group(1).strip("/") + + for pattern in patterns: + for match in re.finditer(pattern, content): + path = match.group(1) + if not path or path.startswith("http") or len(path) > 200: + continue + # For NestJS method decorators, prepend controller prefix + if framework == "nestjs" and not path.startswith("/"): + full_path = f"{controller_prefix}/{path}".replace("//", "/") + else: + full_path = path if path.startswith("/") else f"/{path}" + + # Detect HTTP method from decorator name + method = "UNKNOWN" + ctx = content[max(0, match.start() - 30):match.start()] + for m_name in ["Get", "Post", "Put", "Delete", "Patch"]: + if f"@{m_name}" in ctx or f"@{m_name.lower()}" in ctx: + method = m_name.upper() + break + + routes.append({ + "path": full_path, + "method": method, + "source": str(filepath), + "line": content[:match.start()].count("\n") + 1, + "type": "backend", + }) + return routes + + +def extract_models(filepath: Path, framework: str) -> List[Dict[str, Any]]: + """Extract model/entity definitions from backend code.""" + models = [] + try: + content = filepath.read_text(errors="replace") + except IOError: + return models + + patterns = PYTHON_MODEL_PATTERNS if framework in ("django", "fastapi", "flask") else NEST_MODEL_PATTERNS + for pattern in patterns: + for match in re.finditer(pattern, content): + name = match.group(1) + # Try to extract fields + fields = [] + # For Django models: field_name = models.FieldType(...) + if framework == "django": + block_start = match.end() + block = content[block_start:block_start + 2000] + for fm in re.finditer( + r"(\w+)\s*=\s*models\.(\w+)\s*\(([^)]*)\)", block + ): + fields.append({ + "name": fm.group(1), + "type": fm.group(2), + "args": fm.group(3).strip()[:100], + }) + models.append({ + "name": name, + "source": str(filepath), + "framework": framework, + "fields": fields, + }) + return models + + def count_components(files: List[Path]) -> Dict[str, int]: """Count components by type.""" counts: Dict[str, int] = defaultdict(int) @@ -318,24 +481,35 @@ def analyze_project(project_root: Path) -> Dict[str, Any]: state_dirs = find_dirs(root, STATE_DIR_PATTERNS) i18n_dirs = find_dirs(root, I18N_DIR_PATTERNS) - # 4. Routes + # 4. Routes (frontend + backend) routes = [] - # Config-based routes + fw = framework_info["framework"] + + # Frontend: config-based routes for f in all_files: if any(p in f.name.lower() for p in ["router", "routes", "routing"]): routes.extend(extract_routes_from_file(f)) - # File-system routes (Next.js, Nuxt, SvelteKit) - if framework_info["framework"] in ("next", "nuxt", "sveltekit", "remix", "astro"): + # Frontend: file-system routes (Next.js, Nuxt, SvelteKit) + if fw in ("next", "nuxt", "sveltekit", "remix", "astro"): for d in route_dirs: routes.extend(extract_routes_from_filesystem(d, root)) - # Deduplicate routes by path + # Backend: NestJS controllers, Django urls + if fw in ("nestjs", "express", "fastify", "django"): + for f in all_files: + if fw == "django" and "urls.py" in f.name: + routes.extend(extract_backend_routes(f, fw)) + elif fw in ("nestjs", "express", "fastify") and ".controller." in f.name: + routes.extend(extract_backend_routes(f, fw)) + + # Deduplicate routes by path (+ method for backend) seen_paths: Set[str] = set() unique_routes = [] for r in routes: - if r["path"] not in seen_paths: - seen_paths.add(r["path"]) + key = r["path"] if r.get("type") != "backend" else f"{r.get('method', '')}:{r['path']}" + if key not in seen_paths: + seen_paths.add(key) unique_routes.append(r) routes = sorted(unique_routes, key=lambda r: r["path"]) @@ -359,9 +533,34 @@ def analyze_project(project_root: Path) -> Dict[str, Any]: for f in all_files: enums.extend(extract_enums(f)) - # 7. Summary + # 7. Models/entities (backend) + models = [] + if fw in ("django", "fastapi", "flask", "nestjs"): + for f in all_files: + if fw == "django" and "models.py" in f.name: + models.extend(extract_models(f, fw)) + elif fw == "nestjs" and (".entity." in f.name or ".dto." in f.name): + models.extend(extract_models(f, fw)) + + # Deduplicate models by name + seen_models: Set[str] = set() + unique_models = [] + for m in models: + if m["name"] not in seen_models: + seen_models.add(m["name"]) + unique_models.append(m) + models = sorted(unique_models, key=lambda m: m["name"]) + + # Backend-specific directories + controller_dirs = find_dirs(root, CONTROLLER_DIR_PATTERNS) + model_dirs = find_dirs(root, MODEL_DIR_PATTERNS) + dto_dirs = find_dirs(root, DTO_DIR_PATTERNS) + + # 8. Summary mock_count = sum(1 for a in apis if a.get("mock_detected")) real_count = sum(1 for a in apis if a.get("integrated")) + backend_routes = [r for r in routes if r.get("type") == "backend"] + frontend_routes = [r for r in routes if r.get("type") != "backend"] analysis = { "project": { @@ -370,6 +569,8 @@ def analyze_project(project_root: Path) -> Dict[str, Any]: "framework": framework_info["framework"], "detected_frameworks": framework_info.get("detected_frameworks", []), "key_dependencies": framework_info.get("key_deps", {}), + "stack_type": "backend" if fw in ("django", "fastapi", "flask", "nestjs", "express", "fastify") and not frontend_routes else + "fullstack" if backend_routes and frontend_routes else "frontend", }, "structure": { "total_files": len(all_files), @@ -378,10 +579,15 @@ def analyze_project(project_root: Path) -> Dict[str, Any]: "api_dirs": [str(d) for d in api_dirs], "state_dirs": [str(d) for d in state_dirs], "i18n_dirs": [str(d) for d in i18n_dirs], + "controller_dirs": [str(d) for d in controller_dirs], + "model_dirs": [str(d) for d in model_dirs], + "dto_dirs": [str(d) for d in dto_dirs], }, "routes": { "count": len(routes), - "pages": routes, + "frontend_pages": frontend_routes, + "backend_endpoints": backend_routes, + "pages": routes, # backward compat }, "apis": { "total": len(apis), @@ -393,14 +599,22 @@ def analyze_project(project_root: Path) -> Dict[str, Any]: "count": len(enums), "definitions": enums, }, + "models": { + "count": len(models), + "definitions": models, + }, "summary": { - "pages": len(routes), + "pages": len(frontend_routes), + "backend_endpoints": len(backend_routes), "api_endpoints": len(apis), "api_integrated": real_count, "api_mock": mock_count, "enums": len(enums), + "models": len(models), "has_i18n": len(i18n_dirs) > 0, "has_state_management": len(state_dirs) > 0, + "stack_type": "backend" if fw in ("django", "fastapi", "flask", "nestjs", "express", "fastify") and not frontend_routes else + "fullstack" if backend_routes and frontend_routes else "frontend", }, } @@ -412,15 +626,22 @@ def format_markdown(analysis: Dict[str, Any]) -> str: lines = [] proj = analysis["project"] summary = analysis["summary"] + stack = summary.get("stack_type", "frontend") - lines.append(f"# Frontend Analysis: {proj['name'] or 'Project'}") + lines.append(f"# Codebase Analysis: {proj['name'] or 'Project'}") lines.append("") lines.append(f"**Framework:** {proj['framework']}") + lines.append(f"**Stack type:** {stack}") lines.append(f"**Total files:** {analysis['structure']['total_files']}") - lines.append(f"**Pages:** {summary['pages']}") - lines.append(f"**API endpoints:** {summary['api_endpoints']} " + if summary.get("pages"): + lines.append(f"**Frontend pages:** {summary['pages']}") + if summary.get("backend_endpoints"): + lines.append(f"**Backend endpoints:** {summary['backend_endpoints']}") + lines.append(f"**API calls detected:** {summary['api_endpoints']} " f"({summary['api_integrated']} integrated, {summary['api_mock']} mock)") lines.append(f"**Enums:** {summary['enums']}") + if summary.get("models"): + lines.append(f"**Models/entities:** {summary['models']}") lines.append(f"**i18n:** {'Yes' if summary['has_i18n'] else 'No'}") lines.append(f"**State management:** {'Yes' if summary['has_state_management'] else 'No'}") lines.append("") @@ -459,6 +680,18 @@ def format_markdown(analysis: Dict[str, Any]) -> str: lines.append(f"| {k} | {v} |") lines.append("") + if analysis.get("models", {}).get("definitions"): + lines.append("## Models / Entities") + lines.append("") + for m in analysis["models"]["definitions"]: + lines.append(f"### {m['name']} ({m.get('framework', '')})") + if m.get("fields"): + lines.append("| Field | Type | Args |") + lines.append("|-------|------|------|") + for fld in m["fields"]: + lines.append(f"| {fld['name']} | {fld['type']} | {fld.get('args', '')} |") + lines.append("") + if proj.get("key_dependencies"): lines.append("## Key Dependencies") lines.append("") @@ -471,9 +704,9 @@ def format_markdown(analysis: Dict[str, Any]) -> str: def main(): parser = argparse.ArgumentParser( - description="Analyze frontend codebase for PRD generation" + description="Analyze any codebase (frontend, backend, fullstack) for PRD generation" ) - parser.add_argument("project", help="Path to frontend project root") + parser.add_argument("project", help="Path to project root") parser.add_argument("-o", "--output", help="Output file (default: stdout)") parser.add_argument( "-f", "--format", diff --git a/product-team/code-to-prd/settings.json b/product-team/code-to-prd/settings.json index e0c1f3d..489535e 100644 --- a/product-team/code-to-prd/settings.json +++ b/product-team/code-to-prd/settings.json @@ -2,12 +2,12 @@ "name": "code-to-prd", "displayName": "Code → PRD", "version": "2.1.2", - "description": "Reverse-engineer any frontend codebase into a complete PRD. Analyzes routes, components, state, APIs, and interactions. Framework-agnostic.", + "description": "Reverse-engineer any codebase into a complete PRD. Analyzes routes, components, models, APIs, and interactions. Frontend (React, Vue, Next.js), backend (NestJS, Django, FastAPI), and fullstack.", "author": "Alireza Rezvani", "license": "MIT", "platforms": ["claude-code", "openclaw", "codex"], "category": "product", - "tags": ["prd", "product-requirements", "reverse-engineering", "frontend", "documentation", "code-analysis", "react", "vue", "angular", "next-js"], + "tags": ["prd", "product-requirements", "reverse-engineering", "frontend", "backend", "fullstack", "documentation", "code-analysis", "react", "vue", "angular", "next-js", "nestjs", "django", "fastapi", "express"], "repository": "https://github.com/alirezarezvani/claude-skills", "commands": { "code-to-prd": "/code-to-prd"