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.
@@ -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 };