feat(code-to-prd): expand to fullstack — add NestJS, Django, Express, FastAPI support

- 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) <noreply@anthropic.com>
This commit is contained in:
Reza Rezvani
2026-03-17 12:28:30 +01:00
parent 70ada54c95
commit 4b7a084ee3
8 changed files with 483 additions and 63 deletions

View File

@@ -396,7 +396,7 @@
{ {
"name": "code-to-prd", "name": "code-to-prd",
"source": "./product-team/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", "version": "2.1.2",
"author": { "author": {
"name": "Alireza Rezvani" "name": "Alireza Rezvani"
@@ -406,11 +406,17 @@
"product-requirements", "product-requirements",
"reverse-engineering", "reverse-engineering",
"frontend", "frontend",
"backend",
"fullstack",
"documentation", "documentation",
"code-analysis", "code-analysis",
"react", "react",
"vue", "vue",
"angular" "angular",
"nestjs",
"django",
"fastapi",
"express"
], ],
"category": "product" "category": "product"
} }

View File

@@ -17,7 +17,7 @@ Reverse-engineer a frontend codebase into a complete Product Requirements Docume
## What It Does ## 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 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 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 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: Determine the project path (default: current directory). Run the frontend analyzer:
```bash ```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. Display a summary of findings: framework, page count, API count, enum count.
@@ -73,6 +73,6 @@ A `prd/` directory containing:
## Skill Reference ## Skill Reference
- `product-team/code-to-prd/SKILL.md` - `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/scripts/prd_scaffolder.py`
- `product-team/code-to-prd/references/prd-quality-checklist.md` - `product-team/code-to-prd/references/prd-quality-checklist.md`

View File

@@ -1,6 +1,6 @@
{ {
"name": "code-to-prd", "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", "version": "2.1.2",
"author": { "author": {
"name": "Alireza Rezvani", "name": "Alireza Rezvani",

View File

@@ -1,17 +1,19 @@
--- ---
name: code-to-prd name: code-to-prd
description: | 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 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 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, 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, 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 ## 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. 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 ## Workflow
@@ -37,16 +49,34 @@ Build global context before diving into pages.
Scan the root directory and understand organization: Scan the root directory and understand organization:
``` ```
Key directories: Frontend directories:
- Pages/routes (pages/, views/, routes/, app/, src/pages/) - Pages/routes (pages/, views/, routes/, app/, src/pages/)
- Components (components/, modules/) - Components (components/, modules/)
- Route config (router.ts, routes.ts, App.tsx route definitions) - Route config (router.ts, routes.ts, App.tsx route definitions)
- API/service layer (services/, api/, requests/) - API/service layer (services/, api/, requests/)
- State management (store/, models/, context/) - State management (store/, models/, context/)
- i18n files (locales/, i18n/) — field display names often live here - 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 #### 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 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 #### 3. Map Global Context
Before analyzing individual pages, capture: Before analyzing individual pages, capture:
@@ -69,8 +112,11 @@ Before analyzing individual pages, capture:
- **Shared components** — layout, nav, auth guards, error boundaries - **Shared components** — layout, nav, auth guards, error boundaries
- **Enums & constants** — status codes, type mappings, role definitions - **Enums & constants** — status codes, type mappings, role definitions
- **API base config** — base URL, interceptors, auth headers, error handling - **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 ## Page Type Strategies
### Frontend Pages
| Page Type | Focus Areas | | Page Type | Focus Areas |
|-----------|------------| |-----------|------------|
| **List / Table** | Search conditions, columns, row actions, pagination, bulk ops | | **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 | | **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 | | **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 ## 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 | | Ignoring dynamic route params | `/order/:id` = page requires an order ID to load |
| Forgetting permission controls | Document which roles see which buttons/pages | | Forgetting permission controls | Document which roles see which buttons/pages |
| Assuming all APIs are real | Check for mock data patterns before documenting endpoints | | 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 | | 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` | | `scripts/prd_scaffolder.py` | Generate PRD directory skeleton from analysis JSON | `python3 prd_scaffolder.py analysis.json` |
**Recommended workflow:** **Recommended workflow:**
```bash ```bash
# 1. Analyze the project (JSON output) # 1. Analyze the project (JSON output — works for frontend, backend, or fullstack)
python3 scripts/frontend_analyzer.py /path/to/project -o analysis.json python3 scripts/codebase_analyzer.py /path/to/project -o analysis.json
# 2. Review the analysis (markdown summary) # 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 # 3. Scaffold the PRD directory with stubs
python3 scripts/prd_scaffolder.py analysis.json -o prd/ -n "My App" python3 scripts/prd_scaffolder.py analysis.json -o prd/ -n "My App"

View File

@@ -1,6 +1,6 @@
# Framework-Specific Patterns # 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) ## React (CRA / Vite)
@@ -76,6 +76,71 @@ Quick reference for identifying routes, components, state, and APIs across front
| API routes | `+server.ts` | | API routes | `+server.ts` |
| State | Svelte stores (`writable`, `readable`, `derived`) | | 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 ## Common Patterns Across Frameworks
### Mock Detection ### Mock Detection
@@ -118,4 +183,46 @@ rules="required|email|max:100"
# Angular Reactive Forms # Angular Reactive Forms
Validators.required, Validators.minLength(3), Validators.pattern(...) 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
}
``` ```

View File

@@ -42,6 +42,17 @@ Use this checklist to validate generated PRDs before delivery.
- [ ] `prd/appendix/page-relationships.md` exists - [ ] `prd/appendix/page-relationships.md` exists
- [ ] Cross-references use relative links - [ ] 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 ## Common Issues to Watch
| Issue | How to Detect | Fix | | Issue | How to Detect | Fix |

View File

@@ -1,12 +1,13 @@
#!/usr/bin/env python3 #!/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. Stdlib only no third-party dependencies. Outputs JSON for downstream PRD generation.
Usage: Usage:
python3 frontend_analyzer.py /path/to/project python3 codebase_analyzer.py /path/to/project
python3 frontend_analyzer.py /path/to/project --output prd-analysis.json python3 codebase_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 --format markdown
""" """
from __future__ import annotations from __future__ import annotations
@@ -23,6 +24,8 @@ IGNORED_DIRS = {
".git", "node_modules", ".next", "dist", "build", "coverage", ".git", "node_modules", ".next", "dist", "build", "coverage",
"venv", ".venv", "__pycache__", ".nuxt", ".output", ".cache", "venv", ".venv", "__pycache__", ".nuxt", ".output", ".cache",
".turbo", ".vercel", "out", "storybook-static", ".turbo", ".vercel", "out", "storybook-static",
".tox", ".mypy_cache", ".pytest_cache", "htmlcov", "staticfiles",
"media", "migrations", "egg-info",
} }
FRAMEWORK_SIGNALS = { FRAMEWORK_SIGNALS = {
@@ -36,6 +39,16 @@ FRAMEWORK_SIGNALS = {
"solid": ["solid-js"], "solid": ["solid-js"],
"astro": ["astro"], "astro": ["astro"],
"remix": ["@remix-run/react"], "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 = [ ROUTE_FILE_PATTERNS = [
@@ -65,6 +78,19 @@ I18N_DIR_PATTERNS = [
"src/locales", "src/i18n", "src/lang", "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 = [ MOCK_SIGNALS = [
r"setTimeout\s*\(.*\breturn\b", r"setTimeout\s*\(.*\breturn\b",
r"Promise\.resolve\s*\(", r"Promise\.resolve\s*\(",
@@ -108,32 +134,82 @@ API_PATH_PATTERNS = [
] ]
COMPONENT_EXTENSIONS = {".tsx", ".jsx", ".vue", ".svelte", ".astro"} 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]: def detect_framework(project_root: Path) -> Dict[str, Any]:
"""Detect frontend framework from package.json.""" """Detect framework from package.json (Node.js) or project files (Python)."""
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, {}))
detected = [] detected = []
for framework, signals in FRAMEWORK_SIGNALS.items(): all_deps = {}
if any(s in all_deps for s in signals): pkg_name = ""
detected.append(framework) pkg_version = ""
# Prefer specific over generic (next > react, nuxt > vue) # Node.js detection via package.json
priority = ["sveltekit", "next", "nuxt", "remix", "astro", "angular", "svelte", "vue", "react", "solid"] 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" framework = "unknown"
for fw in priority: for fw in priority:
if fw in detected: if fw in detected:
@@ -142,15 +218,17 @@ def detect_framework(project_root: Path) -> Dict[str, Any]:
return { return {
"framework": framework, "framework": framework,
"name": pkg.get("name", ""), "name": pkg_name or project_root.name,
"version": pkg.get("version", ""), "version": pkg_version,
"detected_frameworks": detected, "detected_frameworks": detected,
"dependency_count": len(all_deps), "dependency_count": len(all_deps),
"key_deps": {k: v for k, v in all_deps.items() "key_deps": {k: v for k, v in all_deps.items()
if any(s in k for s in ["router", "redux", "vuex", "pinia", "zustand", if any(s in k for s in ["router", "redux", "vuex", "pinia", "zustand",
"mobx", "recoil", "jotai", "tanstack", "swr", "mobx", "recoil", "jotai", "tanstack", "swr",
"axios", "tailwind", "material", "ant", "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 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]: def count_components(files: List[Path]) -> Dict[str, int]:
"""Count components by type.""" """Count components by type."""
counts: Dict[str, int] = defaultdict(int) 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) state_dirs = find_dirs(root, STATE_DIR_PATTERNS)
i18n_dirs = find_dirs(root, I18N_DIR_PATTERNS) i18n_dirs = find_dirs(root, I18N_DIR_PATTERNS)
# 4. Routes # 4. Routes (frontend + backend)
routes = [] routes = []
# Config-based routes fw = framework_info["framework"]
# Frontend: config-based routes
for f in all_files: for f in all_files:
if any(p in f.name.lower() for p in ["router", "routes", "routing"]): if any(p in f.name.lower() for p in ["router", "routes", "routing"]):
routes.extend(extract_routes_from_file(f)) routes.extend(extract_routes_from_file(f))
# File-system routes (Next.js, Nuxt, SvelteKit) # Frontend: file-system routes (Next.js, Nuxt, SvelteKit)
if framework_info["framework"] in ("next", "nuxt", "sveltekit", "remix", "astro"): if fw in ("next", "nuxt", "sveltekit", "remix", "astro"):
for d in route_dirs: for d in route_dirs:
routes.extend(extract_routes_from_filesystem(d, root)) 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() seen_paths: Set[str] = set()
unique_routes = [] unique_routes = []
for r in routes: for r in routes:
if r["path"] not in seen_paths: key = r["path"] if r.get("type") != "backend" else f"{r.get('method', '')}:{r['path']}"
seen_paths.add(r["path"]) if key not in seen_paths:
seen_paths.add(key)
unique_routes.append(r) unique_routes.append(r)
routes = sorted(unique_routes, key=lambda r: r["path"]) 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: for f in all_files:
enums.extend(extract_enums(f)) 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")) 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")) 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 = { analysis = {
"project": { "project": {
@@ -370,6 +569,8 @@ def analyze_project(project_root: Path) -> Dict[str, Any]:
"framework": framework_info["framework"], "framework": framework_info["framework"],
"detected_frameworks": framework_info.get("detected_frameworks", []), "detected_frameworks": framework_info.get("detected_frameworks", []),
"key_dependencies": framework_info.get("key_deps", {}), "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": { "structure": {
"total_files": len(all_files), "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], "api_dirs": [str(d) for d in api_dirs],
"state_dirs": [str(d) for d in state_dirs], "state_dirs": [str(d) for d in state_dirs],
"i18n_dirs": [str(d) for d in i18n_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": { "routes": {
"count": len(routes), "count": len(routes),
"pages": routes, "frontend_pages": frontend_routes,
"backend_endpoints": backend_routes,
"pages": routes, # backward compat
}, },
"apis": { "apis": {
"total": len(apis), "total": len(apis),
@@ -393,14 +599,22 @@ def analyze_project(project_root: Path) -> Dict[str, Any]:
"count": len(enums), "count": len(enums),
"definitions": enums, "definitions": enums,
}, },
"models": {
"count": len(models),
"definitions": models,
},
"summary": { "summary": {
"pages": len(routes), "pages": len(frontend_routes),
"backend_endpoints": len(backend_routes),
"api_endpoints": len(apis), "api_endpoints": len(apis),
"api_integrated": real_count, "api_integrated": real_count,
"api_mock": mock_count, "api_mock": mock_count,
"enums": len(enums), "enums": len(enums),
"models": len(models),
"has_i18n": len(i18n_dirs) > 0, "has_i18n": len(i18n_dirs) > 0,
"has_state_management": len(state_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 = [] lines = []
proj = analysis["project"] proj = analysis["project"]
summary = analysis["summary"] 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("")
lines.append(f"**Framework:** {proj['framework']}") lines.append(f"**Framework:** {proj['framework']}")
lines.append(f"**Stack type:** {stack}")
lines.append(f"**Total files:** {analysis['structure']['total_files']}") lines.append(f"**Total files:** {analysis['structure']['total_files']}")
lines.append(f"**Pages:** {summary['pages']}") if summary.get("pages"):
lines.append(f"**API endpoints:** {summary['api_endpoints']} " 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)") f"({summary['api_integrated']} integrated, {summary['api_mock']} mock)")
lines.append(f"**Enums:** {summary['enums']}") 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"**i18n:** {'Yes' if summary['has_i18n'] else 'No'}")
lines.append(f"**State management:** {'Yes' if summary['has_state_management'] else 'No'}") lines.append(f"**State management:** {'Yes' if summary['has_state_management'] else 'No'}")
lines.append("") lines.append("")
@@ -459,6 +680,18 @@ def format_markdown(analysis: Dict[str, Any]) -> str:
lines.append(f"| {k} | {v} |") lines.append(f"| {k} | {v} |")
lines.append("") 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"): if proj.get("key_dependencies"):
lines.append("## Key Dependencies") lines.append("## Key Dependencies")
lines.append("") lines.append("")
@@ -471,9 +704,9 @@ def format_markdown(analysis: Dict[str, Any]) -> str:
def main(): def main():
parser = argparse.ArgumentParser( 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("-o", "--output", help="Output file (default: stdout)")
parser.add_argument( parser.add_argument(
"-f", "--format", "-f", "--format",

View File

@@ -2,12 +2,12 @@
"name": "code-to-prd", "name": "code-to-prd",
"displayName": "Code → PRD", "displayName": "Code → PRD",
"version": "2.1.2", "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", "author": "Alireza Rezvani",
"license": "MIT", "license": "MIT",
"platforms": ["claude-code", "openclaw", "codex"], "platforms": ["claude-code", "openclaw", "codex"],
"category": "product", "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", "repository": "https://github.com/alirezarezvani/claude-skills",
"commands": { "commands": {
"code-to-prd": "/code-to-prd" "code-to-prd": "/code-to-prd"