arckode-framework 1.1.2 → 1.2.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.
- package/cli/analyze.ts +28 -2
- package/cli/index.ts +16 -12
- package/cli/stubs/claude-md-stub.ts +20 -7
- package/cli/stubs/module-stub.ts +1 -3
- package/package.json +3 -2
package/cli/analyze.ts
CHANGED
|
@@ -42,6 +42,9 @@ export interface Violation {
|
|
|
42
42
|
| 'IDOR_RISK' // findById sin assertOwnership posterior
|
|
43
43
|
// ── Portabilidad ──
|
|
44
44
|
| 'SERVICE_DEPENDS_ON_ORM' // service inyecta ORM en lugar de RepositoryAdapter
|
|
45
|
+
// ── Estructura de archivos (Regla #22) ──
|
|
46
|
+
| 'MODEL_IN_TYPES_FILE' // ModelDefinition en types.ts en vez de model.ts
|
|
47
|
+
| 'MISSING_MODEL_TS' // módulo sin model.ts
|
|
45
48
|
// ── Conectores ──
|
|
46
49
|
| 'DUPLICATE_CONNECTOR' // mismo nombre lógico con distinto casing (foo-bar.ts y fooBar.ts)
|
|
47
50
|
| 'UNREGISTERED_CONNECTOR' // archivo existe pero no está en composition-root.ts
|
|
@@ -427,7 +430,6 @@ export async function analyzeProject(basePath: string): Promise<AnalysisResult>
|
|
|
427
430
|
// 9. SERVICE DEPENDS ON ORM — service inyecta ORM en lugar de RepositoryAdapter (Regla #18)
|
|
428
431
|
try {
|
|
429
432
|
const serviceContent = await readFile(servicePath, 'utf-8')
|
|
430
|
-
// Detectar: private orm: ORM o private readonly orm: ORM en el constructor
|
|
431
433
|
if (/private\s+(readonly\s+)?\w+\s*:\s*ORM\b/.test(serviceContent)) {
|
|
432
434
|
violations.push({
|
|
433
435
|
type: 'SERVICE_DEPENDS_ON_ORM',
|
|
@@ -436,6 +438,28 @@ export async function analyzeProject(basePath: string): Promise<AnalysisResult>
|
|
|
436
438
|
})
|
|
437
439
|
}
|
|
438
440
|
} catch { /* no service */ }
|
|
441
|
+
|
|
442
|
+
// 10. MODEL IN TYPES — ModelDefinition en types.ts en vez de model.ts (Regla #22)
|
|
443
|
+
const typesFilePath = join(modPath, 'types.ts')
|
|
444
|
+
try {
|
|
445
|
+
const typesContent = await readFile(typesFilePath, 'utf-8')
|
|
446
|
+
if (/\bModelDefinition\b/.test(typesContent)) {
|
|
447
|
+
violations.push({
|
|
448
|
+
type: 'MODEL_IN_TYPES_FILE',
|
|
449
|
+
module: module.name,
|
|
450
|
+
message: `"${module.name}/types.ts" contiene ModelDefinition o schema de DB. Moverlo a model.ts — son conceptos distintos. (CLAUDE #22)`,
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
} catch { /* no types.ts */ }
|
|
454
|
+
|
|
455
|
+
// 11. MISSING MODEL.TS — módulo sin model.ts (Regla #22)
|
|
456
|
+
if (!await fileExists(join(modPath, 'model.ts'))) {
|
|
457
|
+
violations.push({
|
|
458
|
+
type: 'MISSING_MODEL_TS',
|
|
459
|
+
module: module.name,
|
|
460
|
+
message: `"${module.name}" no tiene model.ts. El schema de DB debe vivir separado de types.ts. (CLAUDE #22)`,
|
|
461
|
+
})
|
|
462
|
+
}
|
|
439
463
|
}
|
|
440
464
|
|
|
441
465
|
return {
|
|
@@ -525,7 +549,7 @@ export function printAnalysis(result: AnalysisResult): void {
|
|
|
525
549
|
}
|
|
526
550
|
|
|
527
551
|
for (const v of result.violations) {
|
|
528
|
-
if (['MISSING_INDEX', 'MISSING_SERVICE', 'MISSING_TYPES', 'MISSING_SOCKETS', 'MISSING_VALIDATORS', 'MISSING_TESTS', 'MISSING_CONNECTORS', 'EMPTY_MODULE_DESCRIPTION'].includes(v.type)) {
|
|
552
|
+
if (['MISSING_INDEX', 'MISSING_SERVICE', 'MISSING_TYPES', 'MISSING_SOCKETS', 'MISSING_VALIDATORS', 'MISSING_TESTS', 'MISSING_CONNECTORS', 'EMPTY_MODULE_DESCRIPTION', 'MODEL_IN_TYPES_FILE', 'MISSING_MODEL_TS'].includes(v.type)) {
|
|
529
553
|
byCategory['Estructura']!.push(v)
|
|
530
554
|
} else if (['DIRECT_MODULE_IMPORT', 'SERVICE_IMPORTS_OTHER_MODULE', 'CONNECTOR_BUSINESS_LOGIC'].includes(v.type)) {
|
|
531
555
|
byCategory['Acoplamiento']!.push(v)
|
|
@@ -616,6 +640,8 @@ export function buildManifest(result: AnalysisResult): Record<string, unknown> {
|
|
|
616
640
|
'REGLA 6: Todo POST/PUT/PATCH requiere validateSchema().',
|
|
617
641
|
'REGLA 7: Controller NO llama al ORM. Llama al service.',
|
|
618
642
|
'REGLA 18: Service recibe RepositoryAdapter<T>, no ORM directamente.',
|
|
643
|
+
'REGLA 22: model.ts separado de types.ts. Schema DB ≠ contrato TS.',
|
|
644
|
+
'REGLA 23: Un DEFAULT por caso. Escalar SOLO cuando la condición lo justifica. Nivel 1: OrmRepository / service.ts / Conector. Nivel 2+ solo si aplica.',
|
|
619
645
|
],
|
|
620
646
|
}
|
|
621
647
|
}
|
package/cli/index.ts
CHANGED
|
@@ -168,12 +168,12 @@ async function main() {
|
|
|
168
168
|
version: '1.0.0',
|
|
169
169
|
type: 'module',
|
|
170
170
|
scripts: {
|
|
171
|
-
dev:
|
|
172
|
-
start:
|
|
173
|
-
test:
|
|
174
|
-
'test:watch':
|
|
175
|
-
analyze:
|
|
176
|
-
'analyze:
|
|
171
|
+
dev: `bun --watch src/composition-root.ts`,
|
|
172
|
+
start: `bun run src/composition-root.ts`,
|
|
173
|
+
test: `bun test`,
|
|
174
|
+
'test:watch': `bun test --watch`,
|
|
175
|
+
analyze: `bun run node_modules/arckode-framework/cli/index.ts analyze`,
|
|
176
|
+
'analyze:ci': `bun run node_modules/arckode-framework/cli/index.ts analyze --ci`,
|
|
177
177
|
},
|
|
178
178
|
dependencies: {
|
|
179
179
|
'arckode-framework': 'latest',
|
|
@@ -260,14 +260,18 @@ data/
|
|
|
260
260
|
const basePath = process.cwd()
|
|
261
261
|
const result = await analyzeProject(basePath)
|
|
262
262
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
263
|
+
// arckode.json siempre se regenera — es el snapshot que la IA lee primero
|
|
264
|
+
const manifest = buildManifest(result)
|
|
265
|
+
const dest = join(basePath, 'arckode.json')
|
|
266
|
+
await writeFile(dest, JSON.stringify(manifest, null, 2))
|
|
267
|
+
|
|
268
|
+
if (args.includes('--ci')) {
|
|
269
|
+
// Modo CI: sin output de consola, exit 1 si hay violaciones
|
|
270
|
+
if (!result.valid) process.exit(1)
|
|
269
271
|
} else {
|
|
270
272
|
printAnalysis(result)
|
|
273
|
+
const status = result.valid ? '✅ sin violaciones' : `❌ ${result.violations.length} violación(es)`
|
|
274
|
+
console.log(`→ arckode.json actualizado — ${status}\n`)
|
|
271
275
|
}
|
|
272
276
|
break
|
|
273
277
|
}
|
|
@@ -62,7 +62,8 @@ src/modules/mi-modulo/
|
|
|
62
62
|
| 19 | Transactor para atomicidad multi-tabla — \`transactor.run(async (repos) => ...)\` | manual |
|
|
63
63
|
| 20 | Conectores limpios: kebab-case, con prefijo \`connect\`, registrados en composition-root | \`arckode analyze\` |
|
|
64
64
|
| 21 | Si el service repite filtros 3+ veces o necesita JOIN/IN/LIKE → crear \`repository.ts\` | manual |
|
|
65
|
-
| 22 | \`model.ts\` separado de \`types.ts\` (schema DB ≠ contrato TS) |
|
|
65
|
+
| 22 | \`model.ts\` separado de \`types.ts\` (schema DB ≠ contrato TS) | \`arckode analyze\` |
|
|
66
|
+
| 23 | Un DEFAULT por caso. Escalar SOLO cuando la condición lo justifica | \`arckode analyze\` |
|
|
66
67
|
|
|
67
68
|
---
|
|
68
69
|
|
|
@@ -152,11 +153,22 @@ seeds/ ← datos de prueba
|
|
|
152
153
|
migrations/ ← migraciones explícitas de DB
|
|
153
154
|
\`\`\`
|
|
154
155
|
|
|
156
|
+
## Escalation ladder — un DEFAULT por caso
|
|
157
|
+
|
|
158
|
+
\`\`\`
|
|
159
|
+
Capa de datos: OrmRepository<T> → repository.ts custom → Transactor
|
|
160
|
+
Lógica de negocio: service.ts → usecases/{caso}.ts (nunca para CRUD)
|
|
161
|
+
Entre módulos: Conector → EventBus (2+ módulos reaccionan)
|
|
162
|
+
DB Adapter: SqliteAdapter → MySQL / Postgres (concurrencia alta)
|
|
163
|
+
\`\`\`
|
|
164
|
+
|
|
165
|
+
Escalar SOLO cuando la condición lo justifica. Si no hay condición → nivel 1.
|
|
166
|
+
|
|
155
167
|
## Comandos útiles
|
|
156
168
|
\`\`\`bash
|
|
157
169
|
bun run dev # desarrollo con hot reload
|
|
158
|
-
bun run analyze # verificar arquitectura
|
|
159
|
-
bun run analyze:
|
|
170
|
+
bun run analyze # verificar arquitectura + actualiza arckode.json
|
|
171
|
+
bun run analyze:ci # modo CI: exit 1 si hay violaciones
|
|
160
172
|
bun arckode make:module Nombre # generar módulo completo
|
|
161
173
|
bun arckode make:connector nombre-x mod1 mod2 # conectar dos módulos (kebab-case)
|
|
162
174
|
bun test # correr todos los tests
|
|
@@ -166,9 +178,10 @@ bun test # correr todos los tests
|
|
|
166
178
|
|
|
167
179
|
## Notas para la IA
|
|
168
180
|
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
181
|
+
- CRUD simple: usá \`OrmRepository<T>\` directo. NO crees \`repository.ts\` ni \`usecases/\`.
|
|
182
|
+
- 3+ tablas en un módulo: definí todos los modelos en \`model.ts\`, registralos en \`registerXxxModels(orm)\`.
|
|
183
|
+
- Atomicidad multi-tabla: inyectá \`Transactor\` al service, no el ORM.
|
|
184
|
+
- Antes de escribir código: revisá \`arckode.json\` (violations) y \`composition-root.ts\` (estado actual).
|
|
185
|
+
- Si el analyzer muestra \`LEGACY_ACTIONS_FOLDER\`, \`DUPLICATE_CONNECTOR\`, \`UNREGISTERED_CONNECTOR\`, \`MODEL_IN_TYPES_FILE\` o \`MISSING_MODEL_TS\`: arreglarlos ANTES de agregar código nuevo.
|
|
173
186
|
`
|
|
174
187
|
}
|
package/cli/stubs/module-stub.ts
CHANGED
|
@@ -487,9 +487,7 @@ ${sampleData.replace(`'${p.name} de ejemplo'`, `'${p.name} de ejemplo 2'`).repla
|
|
|
487
487
|
},
|
|
488
488
|
]
|
|
489
489
|
|
|
490
|
-
|
|
491
|
-
await orm.create('${p.name}', item)
|
|
492
|
-
}
|
|
490
|
+
await Promise.all(items.map(item => orm.create('${p.name}', item)))
|
|
493
491
|
|
|
494
492
|
console.log(' ✓ ${p.name} seeded: ' + items.length + ' items')
|
|
495
493
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arckode-framework",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "AI-first TypeScript/Bun framework. Modular, SOLID, zero magic. The AI reads the composition root and knows everything.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./kernel/framework.ts",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"mysql2": ">=3.0.0",
|
|
62
62
|
"pg": "^8.0.0",
|
|
63
|
-
"redis": "
|
|
63
|
+
"redis": "",
|
|
64
64
|
"nodemailer": ">=6.0.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"@types/pg": "^8.11.0",
|
|
84
84
|
"bun-types": "^1.3.14",
|
|
85
85
|
"pg": "^8.0.0",
|
|
86
|
+
"redis": ">=4.0.0",
|
|
86
87
|
"typescript": "^5.6.0"
|
|
87
88
|
},
|
|
88
89
|
"keywords": [
|