project-mcp-server 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +335 -0
  3. package/dist/config.d.ts +15 -0
  4. package/dist/config.js +38 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +28 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/scanner/actions.d.ts +9 -0
  10. package/dist/scanner/actions.js +77 -0
  11. package/dist/scanner/actions.js.map +1 -0
  12. package/dist/scanner/cache.d.ts +4 -0
  13. package/dist/scanner/cache.js +34 -0
  14. package/dist/scanner/cache.js.map +1 -0
  15. package/dist/scanner/components.d.ts +17 -0
  16. package/dist/scanner/components.js +74 -0
  17. package/dist/scanner/components.js.map +1 -0
  18. package/dist/scanner/prisma.d.ts +17 -0
  19. package/dist/scanner/prisma.js +75 -0
  20. package/dist/scanner/prisma.js.map +1 -0
  21. package/dist/scanner/routes.d.ts +11 -0
  22. package/dist/scanner/routes.js +98 -0
  23. package/dist/scanner/routes.js.map +1 -0
  24. package/dist/setup.d.ts +10 -0
  25. package/dist/setup.js +289 -0
  26. package/dist/setup.js.map +1 -0
  27. package/dist/tools/env/index.d.ts +2 -0
  28. package/dist/tools/env/index.js +152 -0
  29. package/dist/tools/env/index.js.map +1 -0
  30. package/dist/tools/generate/index.d.ts +2 -0
  31. package/dist/tools/generate/index.js +320 -0
  32. package/dist/tools/generate/index.js.map +1 -0
  33. package/dist/tools/project/index.d.ts +2 -0
  34. package/dist/tools/project/index.js +193 -0
  35. package/dist/tools/project/index.js.map +1 -0
  36. package/package.json +35 -0
  37. package/skills/commit.md +109 -0
  38. package/skills/infra.md +218 -0
  39. package/skills/nextauth.md +256 -0
  40. package/skills/nextjs.md +262 -0
  41. package/skills/prisma.md +281 -0
  42. package/skills/project-intelligence.SKILL.md +141 -0
  43. package/skills/security.md +353 -0
  44. package/skills/shadcn.md +299 -0
  45. package/skills/testing.md +188 -0
  46. package/skills/zod.md +253 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fernando Secchi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,335 @@
1
+ # project-mcp-server
2
+
3
+ [![CI](https://github.com/fernandosecchi/project-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/fernandosecchi/project-mcp-server/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ [![Node.js](https://img.shields.io/badge/Node.js-20%2B-green.svg)](https://nodejs.org/)
6
+ [![MCP](https://img.shields.io/badge/MCP-compatible-purple.svg)](https://modelcontextprotocol.io)
7
+
8
+ MCP server con inteligencia de proyecto para Next.js + Prisma. Dos pasos y listo:
9
+
10
+ ```bash
11
+ # 1. Instalar (una sola vez)
12
+ git clone https://github.com/fernandosecchi/project-mcp-server.git ~/.project-mcp
13
+ cd ~/.project-mcp && npm install && npm run build
14
+
15
+ # 2. Configurar (desde tu proyecto)
16
+ cd /ruta/a/tu-proyecto
17
+ node ~/.project-mcp/dist/setup.js
18
+ ```
19
+
20
+ El setup detecta tus agentes (Claude Code, Cursor, Open Code, Gemini CLI, Codex, VS Code), configura el MCP y copia las skills. **Prerequisito**: Node.js 20+.
21
+
22
+ Para actualizar: `cd ~/.project-mcp && git pull && npm install && npm run build`
23
+
24
+ ---
25
+
26
+ ## Qué es
27
+
28
+ Tres capas de inteligencia para tu agente de IA:
29
+
30
+ - **Inteligencia del proyecto** — escanea rutas, Server Actions, modelos Prisma y componentes en tiempo real
31
+ - **Control del entorno** — verifica Docker, migraciones y ejecuta type-check/lint/test/build
32
+ - **Generación con convenciones propias** — genera código que lee tu schema de Prisma real
33
+
34
+ Sin bases de datos, sin servicios externos. Solo Node.js.
35
+
36
+ Se integra con [Gentleman AI Ecosystem](https://github.com/Gentleman-Programming/gentle-ai) (Engram + SDD + Skills) para memoria persistente y workflow de desarrollo.
37
+
38
+ ---
39
+
40
+ ## Configuración manual
41
+
42
+ Si preferís configurar a mano en vez de usar el setup:
43
+
44
+ ### Variables de entorno
45
+
46
+ | Variable | Default | Descripción |
47
+ |---|---|---|
48
+ | `MCP_PROJECT_ROOT` | `process.cwd()` | Raíz del proyecto a analizar |
49
+
50
+ ### Claude Code
51
+
52
+ Editá `~/.claude/config.json`:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "project": {
58
+ "command": "node",
59
+ "args": ["~/.project-mcp/dist/index.js"],
60
+ "env": {
61
+ "MCP_PROJECT_ROOT": "/ruta/absoluta/tu-proyecto"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ Verificar: abrí Claude Code en tu proyecto → `/mcp` → debe aparecer `project` con 10 tools.
69
+
70
+ ### Open Code
71
+
72
+ Editá `~/.config/opencode/config.json`:
73
+
74
+ ```json
75
+ {
76
+ "mcp": {
77
+ "project": {
78
+ "type": "local",
79
+ "command": "node",
80
+ "args": ["~/.project-mcp/dist/index.js"],
81
+ "env": {
82
+ "MCP_PROJECT_ROOT": "/ruta/absoluta/tu-proyecto"
83
+ }
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### Gemini CLI
90
+
91
+ Editá `~/.gemini/config.json`:
92
+
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "project": {
97
+ "command": "node",
98
+ "args": ["~/.project-mcp/dist/index.js"],
99
+ "env": {
100
+ "MCP_PROJECT_ROOT": "/ruta/absoluta/tu-proyecto"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### Codex CLI
108
+
109
+ Editá `~/.codex/config.json`:
110
+
111
+ ```json
112
+ {
113
+ "mcpServers": {
114
+ "project": {
115
+ "command": "node",
116
+ "args": ["~/.project-mcp/dist/index.js"],
117
+ "env": {
118
+ "MCP_PROJECT_ROOT": "/ruta/absoluta/tu-proyecto"
119
+ }
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Cursor
126
+
127
+ Settings → MCP → Add server:
128
+
129
+ ```json
130
+ {
131
+ "project": {
132
+ "command": "node",
133
+ "args": ["~/.project-mcp/dist/index.js"],
134
+ "env": {
135
+ "MCP_PROJECT_ROOT": "/ruta/absoluta/tu-proyecto"
136
+ }
137
+ }
138
+ }
139
+ ```
140
+
141
+ ### VS Code (GitHub Copilot)
142
+
143
+ `.vscode/mcp.json` en el workspace:
144
+
145
+ ```json
146
+ {
147
+ "servers": {
148
+ "project": {
149
+ "type": "stdio",
150
+ "command": "node",
151
+ "args": ["~/.project-mcp/dist/index.js"],
152
+ "env": {
153
+ "MCP_PROJECT_ROOT": "${workspaceFolder}"
154
+ }
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Tools disponibles (10)
163
+
164
+ ### Inteligencia del proyecto
165
+
166
+ | Tool | Descripción |
167
+ |---|---|
168
+ | `project_scan` | Mapa completo: rutas, actions, modelos, componentes |
169
+ | `project_routes` | Lista rutas del App Router con rendering, params y métodos |
170
+ | `project_actions` | Lista Server Actions con schema Zod y flags de auth |
171
+ | `project_models` | Lista modelos de Prisma con campos por schema |
172
+
173
+ ### Control del entorno
174
+
175
+ | Tool | Descripción |
176
+ |---|---|
177
+ | `env_status` | Estado de Docker, .env.local y herramientas de desarrollo |
178
+ | `env_run_check` | Ejecuta typecheck, lint, test o build |
179
+ | `env_prisma_status` | Migraciones pendientes y estado de la DB |
180
+
181
+ ### Generación
182
+
183
+ | Tool | Descripción |
184
+ |---|---|
185
+ | `generate_action` | Server Action con Zod, auth, revalidación — basado en tu schema Prisma real |
186
+ | `generate_page` | Página RSC con auth, metadata y estructura correcta |
187
+ | `generate_component` | Componente con cn(), tipado explícito y directiva correcta |
188
+
189
+ ---
190
+
191
+ ## Integración con Gentleman AI Ecosystem
192
+
193
+ Este MCP está diseñado para funcionar junto con [Engram](https://github.com/Gentleman-Programming/engram) (memoria persistente) y el [orquestador SDD](https://github.com/Gentleman-Programming/gentle-ai) (workflow de desarrollo).
194
+
195
+ ### Cómo funciona
196
+
197
+ Engram se encarga de la **memoria** (decisiones, bugs, patrones, contexto entre sesiones). Este MCP se encarga de la **inteligencia del proyecto** (escaneo real del código, generación basada en el schema).
198
+
199
+ El skill `project-intelligence.SKILL.md` (incluido en `skills/`) le indica al orquestador SDD cuándo usar cada tool:
200
+
201
+ | Fase SDD | Tools del MCP |
202
+ |----------|---------------|
203
+ | `sdd-explore` | `project_scan` — mapa completo del proyecto |
204
+ | `sdd-spec` / `sdd-design` | `project_models`, `project_routes`, `project_actions` |
205
+ | `sdd-apply` | `generate_action`, `generate_page`, `generate_component` |
206
+ | `sdd-verify` | `env_run_check`, `env_prisma_status` |
207
+ | `sdd-archive` | `env_status` — confirmar entorno sano |
208
+
209
+ ### Setup con Gentleman
210
+
211
+ 1. Instalar [gentle-ai](https://github.com/Gentleman-Programming/gentle-ai) (`brew install gentleman-programming/tap/gentle-ai`)
212
+ 2. Instalar este MCP server (ver arriba)
213
+ 3. Correr `node ~/.project-mcp/dist/setup.js` — copia las skills automáticamente
214
+
215
+ ---
216
+
217
+ ## Workflow sugerido
218
+
219
+ ### Sin Gentleman (standalone)
220
+
221
+ ```
222
+ 1. env_status()
223
+ → verificar que el entorno está sano antes de codear
224
+
225
+ 2. project_scan()
226
+ → mapa actual de rutas, actions y modelos
227
+
228
+ 3. project_actions(feature: "users")
229
+ → ver patterns existentes antes de crear uno nuevo
230
+
231
+ 4. generate_action(entity: "user", operation: "create")
232
+ → código base generado con TU schema Prisma real
233
+ ```
234
+
235
+ ### Con Gentleman (SDD)
236
+
237
+ ```
238
+ 1. Engram: mem_context() → contexto de sesiones anteriores
239
+ 2. MCP: project_scan() → mapa del proyecto actual
240
+ 3. SDD: el orquestador delega fases a sub-agentes
241
+ → cada sub-agente usa las tools del MCP según el skill project-intelligence
242
+ 4. Engram: mem_save() → guardar decisiones y aprendizajes
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Skills incluidas (10)
248
+
249
+ El directorio `skills/` incluye skills en formato Gentleman (YAML frontmatter):
250
+
251
+ | Skill | Stack |
252
+ |---|---|
253
+ | `nextjs.md` | Next.js 16 + React 19 |
254
+ | `prisma.md` | Prisma 7 multi-schema |
255
+ | `nextauth.md` | NextAuth.js v5 |
256
+ | `zod.md` | Zod 4 validación |
257
+ | `shadcn.md` | shadcn/ui + Tailwind CSS 4 |
258
+ | `testing.md` | Vitest + Testing Library |
259
+ | `infra.md` | Docker, env vars, DB local |
260
+ | `security.md` | OWASP para Next.js + Prisma |
261
+ | `commit.md` | Conventional Commits y PRs |
262
+ | `project-intelligence.SKILL.md` | Integración SDD |
263
+
264
+ El setup las copia automáticamente al directorio de skills de tu agente.
265
+
266
+ ---
267
+
268
+ ## Estructura del proyecto
269
+
270
+ ```
271
+ project-mcp-server/
272
+ ├── src/
273
+ │ ├── index.ts ← entry point (MCP server)
274
+ │ ├── setup.ts ← setup interactivo
275
+ │ ├── config.ts ← configuración desde env vars
276
+ │ ├── scanner/
277
+ │ │ ├── cache.ts ← TTL cache para escaneos
278
+ │ │ ├── routes.ts ← App Router scanner
279
+ │ │ ├── actions.ts ← Server Actions scanner
280
+ │ │ ├── prisma.ts ← multi-schema Prisma parser
281
+ │ │ └── components.ts ← shadcn + custom components
282
+ │ └── tools/
283
+ │ ├── project/index.ts ← 4 tools de inteligencia
284
+ │ ├── env/index.ts ← 3 tools de entorno
285
+ │ └── generate/index.ts ← 3 tools de generación
286
+ ├── skills/ ← archivos .md de skills (formato Gentleman)
287
+ ├── dist/ ← compilado (no commitear)
288
+ ├── package.json
289
+ └── tsconfig.json
290
+ ```
291
+
292
+ ---
293
+
294
+ ## Actualizar
295
+
296
+ ```bash
297
+ cd ~/.project-mcp && git pull && npm install && npm run build
298
+ ```
299
+
300
+ No hay que reiniciar el LLM — el servidor se actualiza al próximo restart del proceso MCP.
301
+
302
+ ---
303
+
304
+ ## Testing local
305
+
306
+ ```bash
307
+ # Inspeccionar con MCP Inspector (UI visual)
308
+ npm run inspect
309
+
310
+ # Test manual de una tool
311
+ echo '{
312
+ "jsonrpc": "2.0",
313
+ "id": 1,
314
+ "method": "tools/call",
315
+ "params": {
316
+ "name": "env_status",
317
+ "arguments": {}
318
+ }
319
+ }' | MCP_PROJECT_ROOT=/tu/proyecto node dist/index.js 2>/dev/null
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Créditos
325
+
326
+ - [Gentleman Programming](https://github.com/Gentleman-Programming) (Alan Buscaglia) — Engram, Gentle AI, SDD, formato de skills y la filosofía de ecosistema que inspira este proyecto
327
+ - [Anthropic](https://github.com/modelcontextprotocol) — Model Context Protocol y SDK
328
+ - [Everything Claude Code](https://github.com/affaan-m/everything-claude-code) (Affaan M) — Inspiración para el skill de seguridad
329
+ - [Vercel](https://github.com/vercel/next.js), [Prisma](https://github.com/prisma/prisma), [shadcn](https://github.com/shadcn-ui/ui), [Zod](https://github.com/colinhacks/zod), [NextAuth.js](https://github.com/nextauthjs/next-auth), [Vitest](https://github.com/vitest-dev/vitest), [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss)
330
+
331
+ Ver [ACKNOWLEDGMENTS.md](ACKNOWLEDGMENTS.md) para el detalle completo.
332
+
333
+ ## Licencia
334
+
335
+ MIT — ver [LICENSE](LICENSE)
@@ -0,0 +1,15 @@
1
+ export declare const config: {
2
+ readonly projectRoot: string;
3
+ readonly scanner: {
4
+ readonly appDir: "src/app";
5
+ readonly actionsDir: "server/actions";
6
+ readonly schemasDir: "src/types/schemas";
7
+ readonly componentsUiDir: "src/components/ui";
8
+ readonly prismaDir: "prisma/schema";
9
+ readonly cacheTtlMs: 30000;
10
+ };
11
+ readonly skills: {
12
+ readonly dir: string;
13
+ };
14
+ };
15
+ export type Config = typeof config;
package/dist/config.js ADDED
@@ -0,0 +1,38 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ function requireEnv(name, fallback) {
4
+ const val = process.env[name] ?? fallback;
5
+ if (!val)
6
+ throw new Error(`Variable de entorno requerida: ${name}`);
7
+ return val;
8
+ }
9
+ function resolveProjectRoot() {
10
+ const fromEnv = process.env["MCP_PROJECT_ROOT"];
11
+ if (fromEnv && existsSync(fromEnv))
12
+ return resolve(fromEnv);
13
+ // Detectar desde cwd buscando indicadores del proyecto
14
+ const cwd = process.cwd();
15
+ const markers = ["CLAUDE.md", "next.config.ts", "next.config.js", "package.json"];
16
+ for (const marker of markers) {
17
+ if (existsSync(resolve(cwd, marker)))
18
+ return cwd;
19
+ }
20
+ return cwd;
21
+ }
22
+ export const config = {
23
+ projectRoot: resolveProjectRoot(),
24
+ scanner: {
25
+ // Rutas relativas al projectRoot
26
+ appDir: "src/app",
27
+ actionsDir: "server/actions",
28
+ schemasDir: "src/types/schemas",
29
+ componentsUiDir: "src/components/ui",
30
+ prismaDir: "prisma/schema",
31
+ cacheTtlMs: 30_000 // 30 segundos
32
+ },
33
+ skills: {
34
+ // Directorio de skills relativo a este archivo compilado
35
+ dir: new URL("../skills", import.meta.url).pathname
36
+ }
37
+ };
38
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,SAAS,UAAU,CAAC,IAAY,EAAE,QAAiB;IACjD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAA;IACzC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAA;IACnE,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,kBAAkB;IACzB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IAC/C,IAAI,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAA;IAE3D,uDAAuD;IACvD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IACzB,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,CAAC,CAAA;IACjF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,GAAG,CAAA;IAClD,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,WAAW,EAAE,kBAAkB,EAAE;IAEjC,OAAO,EAAE;QACP,iCAAiC;QACjC,MAAM,EAAE,SAAS;QACjB,UAAU,EAAE,gBAAgB;QAC5B,UAAU,EAAE,mBAAmB;QAC/B,eAAe,EAAE,mBAAmB;QACpC,SAAS,EAAE,eAAe;QAC1B,UAAU,EAAE,MAAM,CAAC,cAAc;KAClC;IAED,MAAM,EAAE;QACN,yDAAyD;QACzD,GAAG,EAAE,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ;KACpD;CACO,CAAA"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerProjectTools } from "./tools/project/index.js";
5
+ import { registerEnvTools } from "./tools/env/index.js";
6
+ import { registerGenerateTools } from "./tools/generate/index.js";
7
+ import { config } from "./config.js";
8
+ const server = new McpServer({
9
+ name: "project-mcp-server",
10
+ version: "2.0.0"
11
+ });
12
+ registerProjectTools(server);
13
+ registerEnvTools(server);
14
+ registerGenerateTools(server);
15
+ async function main() {
16
+ console.error(`✓ project-mcp-server v2.0.0 iniciado`);
17
+ console.error(` Proyecto: ${config.projectRoot}`);
18
+ console.error(` Tools: project_scan, project_routes, project_actions, project_models,`);
19
+ console.error(` env_status, env_run_check, env_prisma_status,`);
20
+ console.error(` generate_action, generate_page, generate_component`);
21
+ const transport = new StdioServerTransport();
22
+ await server.connect(transport);
23
+ }
24
+ main().catch((err) => {
25
+ console.error("Error fatal:", err);
26
+ process.exit(1);
27
+ });
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,oBAAoB;IAC1B,OAAO,EAAE,OAAO;CACjB,CAAC,CAAA;AAEF,oBAAoB,CAAC,MAAM,CAAC,CAAA;AAC5B,gBAAgB,CAAC,MAAM,CAAC,CAAA;AACxB,qBAAqB,CAAC,MAAM,CAAC,CAAA;AAE7B,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAA;IACrD,OAAO,CAAC,KAAK,CAAC,eAAe,MAAM,CAAC,WAAW,EAAE,CAAC,CAAA;IAClD,OAAO,CAAC,KAAK,CAAC,yEAAyE,CAAC,CAAA;IACxF,OAAO,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAA;IACvE,OAAO,CAAC,KAAK,CAAC,6DAA6D,CAAC,CAAA;IAE5E,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAA;IAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
@@ -0,0 +1,9 @@
1
+ export interface ServerAction {
2
+ name: string;
3
+ file: string;
4
+ zodSchema?: string;
5
+ params: string[];
6
+ hasAuth: boolean;
7
+ hasRevalidate: boolean;
8
+ }
9
+ export declare function scanActions(): Promise<ServerAction[]>;
@@ -0,0 +1,77 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join, relative } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { config } from "../config.js";
5
+ import { cached } from "./cache.js";
6
+ function extractFunctions(content) {
7
+ const results = [];
8
+ // Detectar exports de async functions
9
+ const fnRegex = /export\s+async\s+function\s+(\w+)\s*\(([^)]*)\)/g;
10
+ let match;
11
+ while ((match = fnRegex.exec(content)) !== null) {
12
+ const name = match[1] ?? "";
13
+ const rawParams = match[2] ?? "";
14
+ const params = rawParams
15
+ .split(",")
16
+ .map(p => p.trim().split(":")[0]?.trim() ?? "")
17
+ .filter(p => p.length > 0 && p !== "_");
18
+ results.push({ name, params });
19
+ }
20
+ return results;
21
+ }
22
+ function detectZodSchema(content, fnName) {
23
+ // Buscar patrones como: const parsed = XxxSchema.safeParse(...) o XxxSchema.parse(...)
24
+ const patterns = [
25
+ /const\s+\w+\s*=\s*(\w+Schema)\.(?:safe)?[Pp]arse/g,
26
+ /(\w+Schema)\.(?:safe)?[Pp]arse\(/g,
27
+ new RegExp(`${fnName}Schema`, "g")
28
+ ];
29
+ for (const pattern of patterns) {
30
+ const m = pattern.exec(content);
31
+ if (m)
32
+ return m[1];
33
+ }
34
+ return undefined;
35
+ }
36
+ async function scanFile(filePath) {
37
+ const content = await readFile(filePath, "utf-8").catch(() => "");
38
+ if (!content)
39
+ return [];
40
+ // Verificar que tiene "use server"
41
+ const first300 = content.slice(0, 300);
42
+ if (!first300.includes('"use server"') && !first300.includes("'use server'"))
43
+ return [];
44
+ const relativePath = relative(config.projectRoot, filePath);
45
+ const functions = extractFunctions(content);
46
+ return functions.map(({ name, params }) => ({
47
+ name,
48
+ file: relativePath,
49
+ zodSchema: detectZodSchema(content, name),
50
+ params,
51
+ hasAuth: content.includes("await auth()") || content.includes("const session = await auth"),
52
+ hasRevalidate: content.includes("revalidateTag(") || content.includes("revalidatePath(")
53
+ }));
54
+ }
55
+ async function walkDir(dir) {
56
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
57
+ const files = [];
58
+ for (const entry of entries) {
59
+ const full = join(dir, entry.name);
60
+ if (entry.isDirectory())
61
+ files.push(...await walkDir(full));
62
+ else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx"))
63
+ files.push(full);
64
+ }
65
+ return files;
66
+ }
67
+ export async function scanActions() {
68
+ return cached("actions", async () => {
69
+ const actionsDir = join(config.projectRoot, config.scanner.actionsDir);
70
+ if (!existsSync(actionsDir))
71
+ return [];
72
+ const files = await walkDir(actionsDir);
73
+ const results = await Promise.all(files.map(scanFile));
74
+ return results.flat().sort((a, b) => a.name.localeCompare(b.name));
75
+ });
76
+ }
77
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/scanner/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function getCached<T>(key: string): T | null;
2
+ export declare function setCached<T>(key: string, data: T, ttlMs?: number): void;
3
+ export declare function invalidate(prefix?: string): void;
4
+ export declare function cached<T>(key: string, fn: () => Promise<T>, ttlMs?: number): Promise<T>;
@@ -0,0 +1,34 @@
1
+ import { config } from "../config.js";
2
+ const store = new Map();
3
+ export function getCached(key) {
4
+ const entry = store.get(key);
5
+ if (!entry)
6
+ return null;
7
+ if (Date.now() > entry.expiresAt) {
8
+ store.delete(key);
9
+ return null;
10
+ }
11
+ return entry.data;
12
+ }
13
+ export function setCached(key, data, ttlMs = config.scanner.cacheTtlMs) {
14
+ store.set(key, { data, expiresAt: Date.now() + ttlMs });
15
+ }
16
+ export function invalidate(prefix) {
17
+ if (!prefix) {
18
+ store.clear();
19
+ return;
20
+ }
21
+ for (const key of store.keys()) {
22
+ if (key.startsWith(prefix))
23
+ store.delete(key);
24
+ }
25
+ }
26
+ export async function cached(key, fn, ttlMs = config.scanner.cacheTtlMs) {
27
+ const hit = getCached(key);
28
+ if (hit !== null)
29
+ return hit;
30
+ const data = await fn();
31
+ setCached(key, data, ttlMs);
32
+ return data;
33
+ }
34
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/scanner/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAOrC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAA;AAEpD,MAAM,UAAU,SAAS,CAAI,GAAW;IACtC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAA8B,CAAA;IACzD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IACvB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACjB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAA;AACnB,CAAC;AAED,MAAM,UAAU,SAAS,CAAI,GAAW,EAAE,IAAO,EAAE,QAAgB,MAAM,CAAC,OAAO,CAAC,UAAoB;IACpG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAA;AACzD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAe;IACxC,IAAI,CAAC,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QAAC,OAAM;IAAC,CAAC;IACtC,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/B,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC/C,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,GAAW,EACX,EAAoB,EACpB,QAAgB,MAAM,CAAC,OAAO,CAAC,UAAU;IAEzC,MAAM,GAAG,GAAG,SAAS,CAAI,GAAG,CAAC,CAAA;IAC7B,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,GAAG,CAAA;IAC5B,MAAM,IAAI,GAAG,MAAM,EAAE,EAAE,CAAA;IACvB,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;IAC3B,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,17 @@
1
+ export interface InstalledComponent {
2
+ name: string;
3
+ file: string;
4
+ exports: string[];
5
+ usesRadix: boolean;
6
+ usesCva: boolean;
7
+ }
8
+ export interface CustomComponent {
9
+ name: string;
10
+ file: string;
11
+ feature: string;
12
+ isClient: boolean;
13
+ }
14
+ export declare function scanComponents(): Promise<{
15
+ ui: InstalledComponent[];
16
+ custom: CustomComponent[];
17
+ }>;