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