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:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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",
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user