agentic-kdd 3.5.6 → 3.5.8
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.
- package/CLAUDE.md +80 -55
- package/README.md +120 -35
- package/akdd-analyze.cjs +319 -0
- package/bin/akdd.js +8 -0
- package/lock-manager.cjs +545 -0
- package/mem-curator.cjs +290 -513
- package/package.json +1 -1
- package/src/onboard.js +312 -0
- package/src/update.js +154 -33
- package/tdd-gate.cjs +4 -0
- package/templates/.agentic/DESIGN_SYSTEM.fastapi.md +109 -0
- package/templates/.agentic/DESIGN_SYSTEM.nextjs.md +91 -0
- package/templates/.agentic/grafo/akdd-analyze.cjs +319 -0
- package/templates/.agentic/grafo/grafo.cjs +696 -2
- package/templates/.agentic/grafo/lock-manager.cjs +545 -0
- package/templates/.agentic/grafo/mem-curator.cjs +361 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# DESIGN_SYSTEM.md — API Contract Reference
|
|
2
|
+
<!-- Agentic KDD — API Design System -->
|
|
3
|
+
<!-- El agente Back lee este archivo antes de cualquier tarea de API -->
|
|
4
|
+
|
|
5
|
+
## Stack de Backend
|
|
6
|
+
- Framework: (completar: FastAPI / Express / Fastify / NestJS)
|
|
7
|
+
- ORM: (completar: SQLAlchemy / Prisma / TypeORM)
|
|
8
|
+
- Auth: JWT (access 15min + refresh 7d)
|
|
9
|
+
- DB: (completar: PostgreSQL / MySQL / SQLite)
|
|
10
|
+
|
|
11
|
+
## Convenciones de API
|
|
12
|
+
|
|
13
|
+
### Rutas
|
|
14
|
+
```
|
|
15
|
+
GET /recursos/ → listar (con filtros y paginación)
|
|
16
|
+
POST /recursos/ → crear
|
|
17
|
+
GET /recursos/{id} → obtener por ID
|
|
18
|
+
PUT /recursos/{id} → actualizar completo
|
|
19
|
+
PATCH /recursos/{id} → actualizar parcial
|
|
20
|
+
DELETE /recursos/{id} → eliminar (soft delete preferido)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Respuestas de éxito
|
|
24
|
+
```json
|
|
25
|
+
// Lista
|
|
26
|
+
{
|
|
27
|
+
"data": [...],
|
|
28
|
+
"total": 100,
|
|
29
|
+
"page": 1,
|
|
30
|
+
"per_page": 20
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Item único
|
|
34
|
+
{
|
|
35
|
+
"id": "uuid",
|
|
36
|
+
"...campos": "..."
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Creación exitosa → 201
|
|
40
|
+
// Actualización exitosa → 200
|
|
41
|
+
// Sin contenido → 204
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Respuestas de error
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"detail": "Mensaje de error legible",
|
|
48
|
+
"code": "ERROR_CODE_SNAKE_CASE"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Códigos de error estándar
|
|
53
|
+
- 400 Bad Request — validación fallida
|
|
54
|
+
- 401 Unauthorized — sin token o token inválido
|
|
55
|
+
- 403 Forbidden — token válido pero sin permiso
|
|
56
|
+
- 404 Not Found — recurso no existe
|
|
57
|
+
- 409 Conflict — duplicado o violación de constraint
|
|
58
|
+
- 422 Unprocessable Entity — pydantic validation error
|
|
59
|
+
- 500 Internal Server Error — error inesperado
|
|
60
|
+
|
|
61
|
+
## Reglas de negocio críticas
|
|
62
|
+
<!-- COMPLETAR con las reglas de tu dominio -->
|
|
63
|
+
<!-- Ejemplo: -->
|
|
64
|
+
```
|
|
65
|
+
OVERTIME_THRESHOLD = 8 # horas por día antes de overtime
|
|
66
|
+
OVERTIME_MULTIPLIER = 1.5 # multiplicador de tarifa
|
|
67
|
+
INVOICE_DUE_DAYS = 30 # días de plazo en facturas
|
|
68
|
+
INVOICE_PREFIX = "AGY" # prefijo de números de factura
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Multi-tenancy
|
|
72
|
+
- Toda tabla de datos tiene `agency_id` (o equivalente)
|
|
73
|
+
- Toda query DEBE filtrar por `agency_id` del token JWT
|
|
74
|
+
- Nunca exponer datos de un tenant a otro
|
|
75
|
+
- El campo `agency_id` en requests del body se IGNORA — siempre del token
|
|
76
|
+
|
|
77
|
+
## Paginación
|
|
78
|
+
```
|
|
79
|
+
?page=1&per_page=20 → paginación estándar
|
|
80
|
+
?cursor=xxx → cursor-based (preferido para listas grandes)
|
|
81
|
+
?search=texto → búsqueda fulltext
|
|
82
|
+
?sort=campo&order=asc|desc → ordenamiento
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Autenticación
|
|
86
|
+
```
|
|
87
|
+
Authorization: Bearer <access_token>
|
|
88
|
+
X-Refresh-Token: <refresh_token> (solo en /auth/refresh)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Convenciones de naming
|
|
92
|
+
- Rutas: snake_case y plural → /agency_users/
|
|
93
|
+
- Campos en JSON: snake_case
|
|
94
|
+
- IDs: UUID v4
|
|
95
|
+
- Fechas: ISO 8601 → "2026-06-27T18:00:00Z"
|
|
96
|
+
- Booleanos: is_active, has_*, can_*
|
|
97
|
+
|
|
98
|
+
## Campos auditables estándar
|
|
99
|
+
Toda tabla de datos debe tener:
|
|
100
|
+
```
|
|
101
|
+
id UUID PRIMARY KEY
|
|
102
|
+
created_at TIMESTAMP WITH TIME ZONE
|
|
103
|
+
updated_at TIMESTAMP WITH TIME ZONE
|
|
104
|
+
is_active BOOLEAN DEFAULT true
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
> Actualiza este archivo cuando cambies la API o las reglas de negocio.
|
|
109
|
+
> El agente Back lo lee antes de cada tarea.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# DESIGN_SYSTEM.md
|
|
2
|
+
<!-- Agentic KDD — Design System Reference -->
|
|
3
|
+
<!-- El agente Front lee este archivo antes de cualquier tarea de UI -->
|
|
4
|
+
|
|
5
|
+
## Stack de UI
|
|
6
|
+
- Framework: Next.js 14 (App Router)
|
|
7
|
+
- Estilos: Tailwind CSS
|
|
8
|
+
- Componentes: (completar: shadcn/ui, Radix, custom)
|
|
9
|
+
- Iconos: (completar: lucide-react, heroicons, etc.)
|
|
10
|
+
- Fuentes: (completar)
|
|
11
|
+
|
|
12
|
+
## Paleta de colores
|
|
13
|
+
<!-- Rellenar con los colores reales del proyecto -->
|
|
14
|
+
```
|
|
15
|
+
Primary: #000000 (completar)
|
|
16
|
+
Secondary: #000000 (completar)
|
|
17
|
+
Background: #FFFFFF (completar)
|
|
18
|
+
Surface: #F5F5F5 (completar)
|
|
19
|
+
Border: #E5E5E5 (completar)
|
|
20
|
+
Text: #111111 (completar)
|
|
21
|
+
Muted: #6B7280 (completar)
|
|
22
|
+
Danger: #EF4444
|
|
23
|
+
Success: #22C55E
|
|
24
|
+
Warning: #F59E0B
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Tipografía
|
|
28
|
+
```
|
|
29
|
+
Font base: (completar)
|
|
30
|
+
Scale:
|
|
31
|
+
xs: 12px
|
|
32
|
+
sm: 14px
|
|
33
|
+
base: 16px
|
|
34
|
+
lg: 18px
|
|
35
|
+
xl: 20px
|
|
36
|
+
2xl: 24px
|
|
37
|
+
3xl: 30px
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Espaciado
|
|
41
|
+
Usa la escala de Tailwind. Unidad base: 4px (= 1 unidad Tailwind).
|
|
42
|
+
|
|
43
|
+
## Componentes base
|
|
44
|
+
<!-- Documenta los componentes compartidos del proyecto -->
|
|
45
|
+
|
|
46
|
+
### Button
|
|
47
|
+
```tsx
|
|
48
|
+
// Variantes disponibles: default, outline, ghost, destructive
|
|
49
|
+
<Button variant="default" size="md">Label</Button>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Card
|
|
53
|
+
```tsx
|
|
54
|
+
<Card>
|
|
55
|
+
<CardHeader><CardTitle>Título</CardTitle></CardHeader>
|
|
56
|
+
<CardContent>Contenido</CardContent>
|
|
57
|
+
</Card>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Form inputs
|
|
61
|
+
```tsx
|
|
62
|
+
<Input type="text" placeholder="..." />
|
|
63
|
+
<Textarea placeholder="..." />
|
|
64
|
+
<Select>...</Select>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Convenciones de layout
|
|
68
|
+
- Container max-width: (completar)
|
|
69
|
+
- Sidebar: (completar) px
|
|
70
|
+
- Padding de página: p-6 (desktop), p-4 (mobile)
|
|
71
|
+
- Gap estándar en grids: gap-4 o gap-6
|
|
72
|
+
|
|
73
|
+
## Convenciones de naming
|
|
74
|
+
- Componentes: PascalCase
|
|
75
|
+
- Páginas (App Router): page.tsx en carpeta del segmento
|
|
76
|
+
- Layouts: layout.tsx
|
|
77
|
+
- Clases Tailwind: orden → layout → spacing → typography → colors → states
|
|
78
|
+
|
|
79
|
+
## Estados de loading / error
|
|
80
|
+
- Skeleton: usar componente Skeleton durante carga
|
|
81
|
+
- Error: mostrar mensaje + botón retry
|
|
82
|
+
- Empty state: ilustración + texto descriptivo + CTA
|
|
83
|
+
|
|
84
|
+
## Accesibilidad mínima
|
|
85
|
+
- Todos los inputs tienen label visible o aria-label
|
|
86
|
+
- Botones de icono tienen aria-label
|
|
87
|
+
- Contraste mínimo: 4.5:1 para texto normal
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
> Actualiza este archivo cuando cambies el design system.
|
|
91
|
+
> El agente Front lo lee antes de cada tarea de UI.
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Agentic KDD — Analyzer v1.0
|
|
4
|
+
* Verificación de consistencia cross-artefacto.
|
|
5
|
+
* Compara specs, código y tests para detectar inconsistencias antes de que
|
|
6
|
+
* el agente las encuentre en producción.
|
|
7
|
+
*
|
|
8
|
+
* Uso:
|
|
9
|
+
* node .agentic/grafo/akdd-analyze.cjs run → análisis completo
|
|
10
|
+
* node .agentic/grafo/akdd-analyze.cjs contracts → verifica contratos vs tests
|
|
11
|
+
* node .agentic/grafo/akdd-analyze.cjs memory → verifica memoria vs código
|
|
12
|
+
* node .agentic/grafo/akdd-analyze.cjs spec → verifica spec vs código
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const ROOT = process.cwd();
|
|
19
|
+
const AGENTIC_DIR = path.join(ROOT, '.agentic');
|
|
20
|
+
const MEMORIA_DIR = path.join(AGENTIC_DIR, 'memoria');
|
|
21
|
+
const CONFIG_FILE = path.join(AGENTIC_DIR, 'config.md');
|
|
22
|
+
const SPECS_DIR = path.join(AGENTIC_DIR, 'specs');
|
|
23
|
+
|
|
24
|
+
// ── Findings ──────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const findings = [];
|
|
27
|
+
|
|
28
|
+
function addFinding(severity, category, message, suggestion) {
|
|
29
|
+
findings.push({ severity, category, message, suggestion });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Check 1: Contratos vs test files ─────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
function checkContractsVsTests() {
|
|
35
|
+
// Open the SQLite DB if available
|
|
36
|
+
const dbPath = path.join(AGENTIC_DIR, 'memoria.db');
|
|
37
|
+
if (!require('fs').existsSync(dbPath)) {
|
|
38
|
+
addFinding('INFO', 'contracts', 'memoria.db no encontrada — sin contratos que verificar', 'Corre akdd init y algunos ciclos aa:');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let db;
|
|
43
|
+
try {
|
|
44
|
+
const projNodeModules = path.join(ROOT, 'node_modules');
|
|
45
|
+
if (!module.paths.includes(projNodeModules)) module.paths.unshift(projNodeModules);
|
|
46
|
+
db = new (require('better-sqlite3'))(dbPath);
|
|
47
|
+
} catch(e) {
|
|
48
|
+
addFinding('WARN', 'contracts', 'No se pudo abrir memoria.db: ' + e.message, 'Verifica que better-sqlite3 está instalado');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const contracts = db.prepare("SELECT * FROM verified_contracts WHERE status != 'invalidated'").all();
|
|
54
|
+
|
|
55
|
+
if (contracts.length === 0) {
|
|
56
|
+
addFinding('INFO', 'contracts', 'Sin contratos registrados aún', 'Corre ciclos aa: para acumular contratos');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check each contract's test file still exists
|
|
61
|
+
for (const c of contracts) {
|
|
62
|
+
if (c.test_file && c.test_file !== 'npm test' && c.test_file !== 'pytest') {
|
|
63
|
+
const testPath = path.join(ROOT, c.test_file);
|
|
64
|
+
if (!fs.existsSync(testPath)) {
|
|
65
|
+
addFinding('HIGH', 'contracts',
|
|
66
|
+
`Contrato [${c.id}] referencia "${c.test_file}" que ya no existe`,
|
|
67
|
+
`Corre akdd contracts verify para actualizar el contrato`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Warn on stale protected contracts
|
|
72
|
+
if (c.status === 'protected' && c.last_verified) {
|
|
73
|
+
const days = Math.floor((Date.now() - new Date(c.last_verified).getTime()) / 86400000);
|
|
74
|
+
if (days > 30) {
|
|
75
|
+
addFinding('WARN', 'contracts',
|
|
76
|
+
`Contrato PROTECTED [${c.module}] no verificado en ${days} días`,
|
|
77
|
+
`Corre akdd contracts gate para re-verificar`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const protected_ = contracts.filter(c => c.status === 'protected').length;
|
|
83
|
+
const verified = contracts.filter(c => c.status === 'verified').length;
|
|
84
|
+
addFinding('INFO', 'contracts',
|
|
85
|
+
`${contracts.length} contratos: ${protected_} PROTECTED, ${verified} VERIFIED`,
|
|
86
|
+
null);
|
|
87
|
+
|
|
88
|
+
} catch(e) {
|
|
89
|
+
addFinding('WARN', 'contracts', 'Error leyendo contratos: ' + e.message, null);
|
|
90
|
+
} finally {
|
|
91
|
+
db.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Check 2: Memoria vs código actual ────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
function checkMemoryVsCode() {
|
|
98
|
+
const erroresFile = path.join(MEMORIA_DIR, 'errores.md');
|
|
99
|
+
const patronesFile= path.join(MEMORIA_DIR, 'patrones.md');
|
|
100
|
+
|
|
101
|
+
if (!fs.existsSync(erroresFile) && !fs.existsSync(patronesFile)) {
|
|
102
|
+
addFinding('INFO', 'memory', 'Archivos de memoria no encontrados', 'Corre akdd init');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check patrones.md for HIGH-confidence rules and verify they're not violated in code
|
|
107
|
+
if (fs.existsSync(patronesFile)) {
|
|
108
|
+
const patrones = fs.readFileSync(patronesFile, 'utf8');
|
|
109
|
+
const highPatterns = patrones.match(/###[^\n]+\n[\s\S]*?\*\*confianza\*\*:\s*ALTA[\s\S]*?(?=###|$)/gi) || [];
|
|
110
|
+
|
|
111
|
+
// Basic structural checks on source files
|
|
112
|
+
const srcFiles = findSourceFiles(ROOT);
|
|
113
|
+
|
|
114
|
+
for (const pattern of highPatterns) {
|
|
115
|
+
const titleMatch = pattern.match(/###\s+(.+)/);
|
|
116
|
+
const ruleMatch = pattern.match(/\*\*regla\*\*:\s*(.+)/i);
|
|
117
|
+
if (!titleMatch || !ruleMatch) continue;
|
|
118
|
+
|
|
119
|
+
const rule = ruleMatch[1].toLowerCase();
|
|
120
|
+
|
|
121
|
+
// Check: if rule mentions "tenant" or "agency_id", verify files filter by it
|
|
122
|
+
if ((rule.includes('tenant') || rule.includes('agency_id')) && srcFiles.length > 0) {
|
|
123
|
+
const routeFiles = srcFiles.filter(f => f.includes('route') || f.includes('router') || f.includes('api'));
|
|
124
|
+
const violations = routeFiles.filter(f => {
|
|
125
|
+
const content = safeRead(f);
|
|
126
|
+
return content && content.includes('findMany') && !content.includes('agency_id') &&
|
|
127
|
+
!content.includes('tenant') && !content.includes('TESTING');
|
|
128
|
+
});
|
|
129
|
+
if (violations.length > 0) {
|
|
130
|
+
addFinding('HIGH', 'memory',
|
|
131
|
+
`Patrón HIGH "${titleMatch[1]}" puede estar violado en: ${violations.slice(0,3).map(f => path.relative(ROOT,f)).join(', ')}`,
|
|
132
|
+
`Revisar manualmente — patrón: ${rule}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
addFinding('INFO', 'memory', `${highPatterns.length} patrones HIGH verificados contra código`, null);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check errores.md size
|
|
141
|
+
if (fs.existsSync(erroresFile)) {
|
|
142
|
+
const errLines = fs.readFileSync(erroresFile, 'utf8').split('\n').filter(l => l.startsWith('### ')).length;
|
|
143
|
+
if (errLines > 50) {
|
|
144
|
+
addFinding('WARN', 'memory',
|
|
145
|
+
`errores.md tiene ${errLines} entradas — puede degradar el contexto del agente`,
|
|
146
|
+
`Corre: node .agentic/grafo/mem-curator.cjs run`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Check 3: Config vs stack real ────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
function checkConfigVsStack() {
|
|
154
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
155
|
+
addFinding('HIGH', 'config', 'config.md no encontrado', 'Corre akdd init');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const config = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
160
|
+
|
|
161
|
+
// Check test command is configured
|
|
162
|
+
const testMatch = config.match(/^\s*test:\s*(.+)$/m);
|
|
163
|
+
if (!testMatch || testMatch[1].trim() === '—' || testMatch[1].trim() === '') {
|
|
164
|
+
addFinding('HIGH', 'config',
|
|
165
|
+
'Comando de tests no configurado en config.md',
|
|
166
|
+
'Agrega: test: npm test (o el comando de tu stack)');
|
|
167
|
+
} else {
|
|
168
|
+
addFinding('INFO', 'config', `Comando de tests: ${testMatch[1].trim()}`, null);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check Python project has test command pointing to pytest
|
|
172
|
+
const hasPython = fs.existsSync(path.join(ROOT, 'backend', 'requirements.txt')) ||
|
|
173
|
+
fs.existsSync(path.join(ROOT, 'requirements.txt'));
|
|
174
|
+
if (hasPython && testMatch && !testMatch[1].includes('pytest')) {
|
|
175
|
+
addFinding('WARN', 'config',
|
|
176
|
+
'Proyecto Python detectado pero test: no usa pytest',
|
|
177
|
+
`Cambia a: test: cd backend && py -3.13 -m pytest -x -v`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check DESIGN_SYSTEM
|
|
181
|
+
const hasDesignSystem = fs.existsSync(path.join(ROOT, 'DESIGN_SYSTEM.md')) ||
|
|
182
|
+
fs.existsSync(path.join(ROOT, '.agentic', 'DESIGN_SYSTEM.md'));
|
|
183
|
+
if (!hasDesignSystem) {
|
|
184
|
+
addFinding('INFO', 'config', 'DESIGN_SYSTEM.md no encontrado',
|
|
185
|
+
'Opcional: crea DESIGN_SYSTEM.md para que el agente Front tenga referencia de tokens');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── Check 4: Specs vs código ──────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
function checkSpecsVsCode() {
|
|
192
|
+
if (!fs.existsSync(SPECS_DIR)) {
|
|
193
|
+
addFinding('INFO', 'specs', 'No hay specs registradas aún', 'Se crean automáticamente durante ciclos aa:');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const specFiles = fs.readdirSync(SPECS_DIR).filter(f => f.endsWith('.md'));
|
|
198
|
+
if (specFiles.length === 0) {
|
|
199
|
+
addFinding('INFO', 'specs', 'Directorio specs vacío', null);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
addFinding('INFO', 'specs', `${specFiles.length} specs encontradas`, null);
|
|
204
|
+
|
|
205
|
+
// Check specs for unresolved TODOs
|
|
206
|
+
for (const specFile of specFiles) {
|
|
207
|
+
const content = safeRead(path.join(SPECS_DIR, specFile)) || '';
|
|
208
|
+
const todos = (content.match(/\bTODO\b|\bPENDING\b|\bFIXME\b/gi) || []).length;
|
|
209
|
+
if (todos > 0) {
|
|
210
|
+
addFinding('WARN', 'specs',
|
|
211
|
+
`${specFile} tiene ${todos} TODOs/PENDINGs sin resolver`,
|
|
212
|
+
`Revisar y actualizar o eliminar entradas obsoletas`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
function findSourceFiles(root, extensions = ['.ts', '.tsx', '.js', '.py']) {
|
|
220
|
+
const results = [];
|
|
221
|
+
const skip = new Set(['node_modules', '.agentic', '.git', '__pycache__', '.next', 'dist', 'build']);
|
|
222
|
+
|
|
223
|
+
function walk(dir) {
|
|
224
|
+
try {
|
|
225
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
226
|
+
if (skip.has(entry.name)) continue;
|
|
227
|
+
const full = path.join(dir, entry.name);
|
|
228
|
+
if (entry.isDirectory()) walk(full);
|
|
229
|
+
else if (extensions.some(e => entry.name.endsWith(e))) results.push(full);
|
|
230
|
+
}
|
|
231
|
+
} catch {}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
walk(root);
|
|
235
|
+
return results;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function safeRead(filePath) {
|
|
239
|
+
try { return fs.readFileSync(filePath, 'utf8'); } catch { return null; }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── Report printer ────────────────────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
function printReport() {
|
|
245
|
+
console.log('\n══════════════════════════════════════════════════');
|
|
246
|
+
console.log(' 🔍 Agentic KDD — Analyzer');
|
|
247
|
+
console.log('══════════════════════════════════════════════════');
|
|
248
|
+
|
|
249
|
+
const bySeverity = { HIGH: [], WARN: [], INFO: [] };
|
|
250
|
+
for (const f of findings) {
|
|
251
|
+
(bySeverity[f.severity] || bySeverity.INFO).push(f);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (bySeverity.HIGH.length > 0) {
|
|
255
|
+
console.log('\n 🔴 HIGH — requieren atención:');
|
|
256
|
+
for (const f of bySeverity.HIGH) {
|
|
257
|
+
console.log(`\n [${f.category.toUpperCase()}] ${f.message}`);
|
|
258
|
+
if (f.suggestion) console.log(` → ${f.suggestion}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (bySeverity.WARN.length > 0) {
|
|
263
|
+
console.log('\n 🟠 WARN — revisar:');
|
|
264
|
+
for (const f of bySeverity.WARN) {
|
|
265
|
+
console.log(`\n [${f.category.toUpperCase()}] ${f.message}`);
|
|
266
|
+
if (f.suggestion) console.log(` → ${f.suggestion}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (bySeverity.INFO.length > 0) {
|
|
271
|
+
console.log('\n ✅ INFO:');
|
|
272
|
+
for (const f of bySeverity.INFO) {
|
|
273
|
+
console.log(` [${f.category.toUpperCase()}] ${f.message}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const total = findings.length;
|
|
278
|
+
const issues = bySeverity.HIGH.length + bySeverity.WARN.length;
|
|
279
|
+
console.log(`\n Total: ${total} checks | Problemas: ${issues}`);
|
|
280
|
+
|
|
281
|
+
if (issues === 0) {
|
|
282
|
+
console.log(' ✅ Todo consistente — el proyecto está en buen estado.');
|
|
283
|
+
} else if (bySeverity.HIGH.length > 0) {
|
|
284
|
+
console.log(' ⛔ Hay inconsistencias críticas — resolver antes del próximo ciclo aa:');
|
|
285
|
+
process.exit(1);
|
|
286
|
+
} else {
|
|
287
|
+
console.log(' ⚠️ Hay advertencias — revisar cuando sea posible.');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log('══════════════════════════════════════════════════\n');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── CLI ───────────────────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
if (require.main === module) {
|
|
296
|
+
const cmd = process.argv[2] || 'run';
|
|
297
|
+
|
|
298
|
+
if (cmd === 'run') {
|
|
299
|
+
checkContractsVsTests();
|
|
300
|
+
checkMemoryVsCode();
|
|
301
|
+
checkConfigVsStack();
|
|
302
|
+
checkSpecsVsCode();
|
|
303
|
+
} else if (cmd === 'contracts') {
|
|
304
|
+
checkContractsVsTests();
|
|
305
|
+
} else if (cmd === 'memory') {
|
|
306
|
+
checkMemoryVsCode();
|
|
307
|
+
} else if (cmd === 'spec') {
|
|
308
|
+
checkSpecsVsCode();
|
|
309
|
+
} else if (cmd === 'config') {
|
|
310
|
+
checkConfigVsStack();
|
|
311
|
+
} else {
|
|
312
|
+
console.log('Uso: node akdd-analyze.cjs [run|contracts|memory|spec|config]');
|
|
313
|
+
process.exit(0);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
printReport();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
module.exports = { checkContractsVsTests, checkMemoryVsCode, checkConfigVsStack, checkSpecsVsCode };
|