elsabro 2.3.0 → 3.7.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/README.md +668 -20
- package/bin/install.js +0 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/package.json +3 -2
- package/references/SYSTEM_INDEX.md +379 -5
- package/references/agent-marketplace.md +2274 -0
- package/references/agent-protocol.md +1126 -0
- package/references/ai-code-suggestions.md +2413 -0
- package/references/checkpointing.md +595 -0
- package/references/collaboration-patterns.md +851 -0
- package/references/collaborative-sessions.md +1081 -0
- package/references/configuration-management.md +1810 -0
- package/references/cost-tracking.md +1095 -0
- package/references/enterprise-sso.md +2001 -0
- package/references/error-contracts-v2.md +968 -0
- package/references/event-driven.md +1031 -0
- package/references/flow-orchestration.md +940 -0
- package/references/flow-visualization.md +1557 -0
- package/references/ide-integrations.md +3513 -0
- package/references/interrupt-system.md +681 -0
- package/references/kubernetes-deployment.md +3099 -0
- package/references/memory-system.md +683 -0
- package/references/mobile-companion.md +3236 -0
- package/references/multi-llm-providers.md +2494 -0
- package/references/multi-project-memory.md +1182 -0
- package/references/observability.md +793 -0
- package/references/output-schemas.md +858 -0
- package/references/performance-profiler.md +955 -0
- package/references/plugin-system.md +1526 -0
- package/references/prompt-management.md +292 -0
- package/references/sandbox-execution.md +303 -0
- package/references/security-system.md +1253 -0
- package/references/streaming.md +696 -0
- package/references/testing-framework.md +1151 -0
- package/references/time-travel.md +802 -0
- package/references/tool-registry.md +886 -0
- package/references/voice-commands.md +3296 -0
- package/templates/agent-marketplace-config.json +220 -0
- package/templates/agent-protocol-config.json +136 -0
- package/templates/ai-suggestions-config.json +100 -0
- package/templates/checkpoint-state.json +61 -0
- package/templates/collaboration-config.json +157 -0
- package/templates/collaborative-sessions-config.json +153 -0
- package/templates/configuration-config.json +245 -0
- package/templates/cost-tracking-config.json +148 -0
- package/templates/enterprise-sso-config.json +438 -0
- package/templates/events-config.json +148 -0
- package/templates/flow-visualization-config.json +196 -0
- package/templates/ide-integrations-config.json +442 -0
- package/templates/kubernetes-config.json +764 -0
- package/templates/memory-state.json +84 -0
- package/templates/mobile-companion-config.json +600 -0
- package/templates/multi-llm-config.json +544 -0
- package/templates/multi-project-memory-config.json +145 -0
- package/templates/observability-config.json +109 -0
- package/templates/performance-profiler-config.json +125 -0
- package/templates/plugin-config.json +170 -0
- package/templates/prompt-management-config.json +86 -0
- package/templates/sandbox-config.json +185 -0
- package/templates/schemas-config.json +65 -0
- package/templates/security-config.json +120 -0
- package/templates/streaming-config.json +72 -0
- package/templates/testing-config.json +81 -0
- package/templates/timetravel-config.json +62 -0
- package/templates/tool-registry-config.json +109 -0
- package/templates/voice-commands-config.json +658 -0
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: output-schemas
|
|
3
|
+
description: Sistema de validación de outputs con schemas JSON/Zod
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ELSABRO Output Schemas System
|
|
8
|
+
|
|
9
|
+
## Vision General
|
|
10
|
+
|
|
11
|
+
El sistema de Output Schemas garantiza que los outputs de agentes cumplan con estructuras definidas, proporcionando type-safety y comunicación predecible entre componentes.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ OUTPUT VALIDATION FLOW │
|
|
16
|
+
├──────────────────────────────────────────────────────────────────────────┤
|
|
17
|
+
│ │
|
|
18
|
+
│ AGENT OUTPUT │
|
|
19
|
+
│ │ │
|
|
20
|
+
│ ▼ │
|
|
21
|
+
│ ┌──────────────┐ │
|
|
22
|
+
│ │ PARSE │ ──── Extract structured data from response │
|
|
23
|
+
│ └──────┬───────┘ │
|
|
24
|
+
│ │ │
|
|
25
|
+
│ ▼ │
|
|
26
|
+
│ ┌──────────────┐ INVALID ┌──────────────┐ │
|
|
27
|
+
│ │ VALIDATE │ ─────────────► │ RETRY │ │
|
|
28
|
+
│ │ (vs Schema) │ │ (with hints) │ │
|
|
29
|
+
│ └──────┬───────┘ └──────────────┘ │
|
|
30
|
+
│ │ VALID │
|
|
31
|
+
│ ▼ │
|
|
32
|
+
│ ┌──────────────┐ │
|
|
33
|
+
│ │ TRANSFORM │ ──── Apply defaults, coerce types │
|
|
34
|
+
│ └──────┬───────┘ │
|
|
35
|
+
│ │ │
|
|
36
|
+
│ ▼ │
|
|
37
|
+
│ TYPED OUTPUT │
|
|
38
|
+
│ │
|
|
39
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Definición de Schemas
|
|
45
|
+
|
|
46
|
+
### JSON Schema Format
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
51
|
+
"$id": "elsabro://schemas/analysis-result",
|
|
52
|
+
"title": "AnalysisResult",
|
|
53
|
+
"description": "Output schema for analysis agent",
|
|
54
|
+
"type": "object",
|
|
55
|
+
"required": ["summary", "files", "recommendations"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"summary": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "Brief summary of the analysis",
|
|
60
|
+
"minLength": 10,
|
|
61
|
+
"maxLength": 500
|
|
62
|
+
},
|
|
63
|
+
"files": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"description": "Files analyzed",
|
|
66
|
+
"items": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"required": ["path", "purpose"],
|
|
69
|
+
"properties": {
|
|
70
|
+
"path": { "type": "string" },
|
|
71
|
+
"purpose": { "type": "string" },
|
|
72
|
+
"complexity": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"enum": ["low", "medium", "high"]
|
|
75
|
+
},
|
|
76
|
+
"linesOfCode": { "type": "integer", "minimum": 0 }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"recommendations": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"description": "List of recommendations",
|
|
83
|
+
"items": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"required": ["action", "priority"],
|
|
86
|
+
"properties": {
|
|
87
|
+
"action": { "type": "string" },
|
|
88
|
+
"priority": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"enum": ["low", "medium", "high", "critical"]
|
|
91
|
+
},
|
|
92
|
+
"reason": { "type": "string" }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"techDebt": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"properties": {
|
|
99
|
+
"score": { "type": "number", "minimum": 0, "maximum": 10 },
|
|
100
|
+
"issues": { "type": "array", "items": { "type": "string" } }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### TypeScript/Zod Format
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { z } from 'zod';
|
|
111
|
+
|
|
112
|
+
// Schema para resultado de análisis
|
|
113
|
+
export const AnalysisResultSchema = z.object({
|
|
114
|
+
summary: z.string().min(10).max(500).describe('Brief summary of the analysis'),
|
|
115
|
+
|
|
116
|
+
files: z.array(z.object({
|
|
117
|
+
path: z.string(),
|
|
118
|
+
purpose: z.string(),
|
|
119
|
+
complexity: z.enum(['low', 'medium', 'high']).optional(),
|
|
120
|
+
linesOfCode: z.number().int().nonnegative().optional()
|
|
121
|
+
})).describe('Files analyzed'),
|
|
122
|
+
|
|
123
|
+
recommendations: z.array(z.object({
|
|
124
|
+
action: z.string(),
|
|
125
|
+
priority: z.enum(['low', 'medium', 'high', 'critical']),
|
|
126
|
+
reason: z.string().optional()
|
|
127
|
+
})).describe('List of recommendations'),
|
|
128
|
+
|
|
129
|
+
techDebt: z.object({
|
|
130
|
+
score: z.number().min(0).max(10),
|
|
131
|
+
issues: z.array(z.string())
|
|
132
|
+
}).optional()
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export type AnalysisResult = z.infer<typeof AnalysisResultSchema>;
|
|
136
|
+
|
|
137
|
+
// Schema para resultado de implementación
|
|
138
|
+
export const ImplementationResultSchema = z.object({
|
|
139
|
+
success: z.boolean(),
|
|
140
|
+
|
|
141
|
+
filesCreated: z.array(z.object({
|
|
142
|
+
path: z.string(),
|
|
143
|
+
purpose: z.string(),
|
|
144
|
+
linesAdded: z.number().int().nonnegative()
|
|
145
|
+
})).default([]),
|
|
146
|
+
|
|
147
|
+
filesModified: z.array(z.object({
|
|
148
|
+
path: z.string(),
|
|
149
|
+
changes: z.string(),
|
|
150
|
+
linesAdded: z.number().int().nonnegative(),
|
|
151
|
+
linesRemoved: z.number().int().nonnegative()
|
|
152
|
+
})).default([]),
|
|
153
|
+
|
|
154
|
+
testsCreated: z.array(z.object({
|
|
155
|
+
path: z.string(),
|
|
156
|
+
testCount: z.number().int().positive(),
|
|
157
|
+
coverage: z.number().min(0).max(100).optional()
|
|
158
|
+
})).default([]),
|
|
159
|
+
|
|
160
|
+
decisions: z.array(z.object({
|
|
161
|
+
question: z.string(),
|
|
162
|
+
decision: z.string(),
|
|
163
|
+
reasoning: z.string(),
|
|
164
|
+
alternatives: z.array(z.string()).optional()
|
|
165
|
+
})).default([]),
|
|
166
|
+
|
|
167
|
+
errors: z.array(z.object({
|
|
168
|
+
type: z.string(),
|
|
169
|
+
message: z.string(),
|
|
170
|
+
file: z.string().optional(),
|
|
171
|
+
line: z.number().optional()
|
|
172
|
+
})).default([])
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export type ImplementationResult = z.infer<typeof ImplementationResultSchema>;
|
|
176
|
+
|
|
177
|
+
// Schema para resultado de review
|
|
178
|
+
export const ReviewResultSchema = z.object({
|
|
179
|
+
approved: z.boolean(),
|
|
180
|
+
|
|
181
|
+
issues: z.array(z.object({
|
|
182
|
+
severity: z.enum(['critical', 'high', 'medium', 'low', 'info']),
|
|
183
|
+
type: z.enum(['bug', 'security', 'performance', 'style', 'maintainability']),
|
|
184
|
+
file: z.string(),
|
|
185
|
+
line: z.number().optional(),
|
|
186
|
+
message: z.string(),
|
|
187
|
+
suggestion: z.string().optional(),
|
|
188
|
+
autoFixable: z.boolean().default(false)
|
|
189
|
+
})).default([]),
|
|
190
|
+
|
|
191
|
+
summary: z.object({
|
|
192
|
+
critical: z.number().int().nonnegative(),
|
|
193
|
+
high: z.number().int().nonnegative(),
|
|
194
|
+
medium: z.number().int().nonnegative(),
|
|
195
|
+
low: z.number().int().nonnegative(),
|
|
196
|
+
info: z.number().int().nonnegative()
|
|
197
|
+
}),
|
|
198
|
+
|
|
199
|
+
recommendation: z.enum(['approve', 'request_changes', 'needs_discussion'])
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
export type ReviewResult = z.infer<typeof ReviewResultSchema>;
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Schemas Predefinidos
|
|
208
|
+
|
|
209
|
+
### 1. AnalysisResult
|
|
210
|
+
|
|
211
|
+
Para agentes de análisis (elsabro-analyst, Explore, Plan).
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
{
|
|
215
|
+
summary: string,
|
|
216
|
+
files: Array<{ path, purpose, complexity?, linesOfCode? }>,
|
|
217
|
+
recommendations: Array<{ action, priority, reason? }>,
|
|
218
|
+
techDebt?: { score, issues }
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 2. ImplementationResult
|
|
223
|
+
|
|
224
|
+
Para agentes de implementación (elsabro-executor, elsabro-quick-dev).
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
{
|
|
228
|
+
success: boolean,
|
|
229
|
+
filesCreated: Array<{ path, purpose, linesAdded }>,
|
|
230
|
+
filesModified: Array<{ path, changes, linesAdded, linesRemoved }>,
|
|
231
|
+
testsCreated: Array<{ path, testCount, coverage? }>,
|
|
232
|
+
decisions: Array<{ question, decision, reasoning, alternatives? }>,
|
|
233
|
+
errors: Array<{ type, message, file?, line? }>
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 3. ReviewResult
|
|
238
|
+
|
|
239
|
+
Para agentes de review (code-reviewer, silent-failure-hunter).
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
{
|
|
243
|
+
approved: boolean,
|
|
244
|
+
issues: Array<{ severity, type, file, line?, message, suggestion?, autoFixable }>,
|
|
245
|
+
summary: { critical, high, medium, low, info },
|
|
246
|
+
recommendation: 'approve' | 'request_changes' | 'needs_discussion'
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### 4. VerificationResult
|
|
251
|
+
|
|
252
|
+
Para agentes de verificación (elsabro-verifier, elsabro-qa).
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
{
|
|
256
|
+
passed: boolean,
|
|
257
|
+
tests: {
|
|
258
|
+
total: number,
|
|
259
|
+
passed: number,
|
|
260
|
+
failed: number,
|
|
261
|
+
skipped: number
|
|
262
|
+
},
|
|
263
|
+
typescript: {
|
|
264
|
+
passed: boolean,
|
|
265
|
+
errors: Array<{ file, line, message }>
|
|
266
|
+
},
|
|
267
|
+
lint: {
|
|
268
|
+
passed: boolean,
|
|
269
|
+
warnings: number,
|
|
270
|
+
errors: number
|
|
271
|
+
},
|
|
272
|
+
coverage?: {
|
|
273
|
+
statements: number,
|
|
274
|
+
branches: number,
|
|
275
|
+
functions: number,
|
|
276
|
+
lines: number
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 5. PlanResult
|
|
282
|
+
|
|
283
|
+
Para agentes de planificación (elsabro-planner).
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
{
|
|
287
|
+
phases: Array<{
|
|
288
|
+
id: string,
|
|
289
|
+
name: string,
|
|
290
|
+
description: string,
|
|
291
|
+
tasks: Array<{ id, description, agent?, dependencies? }>,
|
|
292
|
+
estimatedComplexity: 'low' | 'medium' | 'high'
|
|
293
|
+
}>,
|
|
294
|
+
dependencies: Array<{ from, to }>,
|
|
295
|
+
risks: Array<{ description, severity, mitigation }>,
|
|
296
|
+
prerequisites: Array<string>
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## API de Validación
|
|
303
|
+
|
|
304
|
+
### SchemaValidator
|
|
305
|
+
|
|
306
|
+
```javascript
|
|
307
|
+
/**
|
|
308
|
+
* SchemaValidator
|
|
309
|
+
* Valida outputs de agentes contra schemas definidos
|
|
310
|
+
*/
|
|
311
|
+
class SchemaValidator {
|
|
312
|
+
constructor() {
|
|
313
|
+
this.schemas = new Map();
|
|
314
|
+
this.validationCache = new Map();
|
|
315
|
+
|
|
316
|
+
// Registrar schemas predefinidos
|
|
317
|
+
this.registerPredefinedSchemas();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Registra un schema
|
|
322
|
+
*/
|
|
323
|
+
registerSchema(name, schema) {
|
|
324
|
+
this.schemas.set(name, {
|
|
325
|
+
name,
|
|
326
|
+
schema,
|
|
327
|
+
validator: this.compileValidator(schema)
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Compila un validator desde JSON Schema o Zod
|
|
333
|
+
*/
|
|
334
|
+
compileValidator(schema) {
|
|
335
|
+
// Si es Zod schema
|
|
336
|
+
if (schema._def) {
|
|
337
|
+
return (data) => {
|
|
338
|
+
const result = schema.safeParse(data);
|
|
339
|
+
return {
|
|
340
|
+
valid: result.success,
|
|
341
|
+
data: result.success ? result.data : null,
|
|
342
|
+
errors: result.success ? [] : result.error.errors.map(e => ({
|
|
343
|
+
path: e.path.join('.'),
|
|
344
|
+
message: e.message,
|
|
345
|
+
code: e.code
|
|
346
|
+
}))
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Si es JSON Schema
|
|
352
|
+
return (data) => this.validateJsonSchema(data, schema);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Valida JSON Schema manualmente
|
|
357
|
+
*/
|
|
358
|
+
validateJsonSchema(data, schema) {
|
|
359
|
+
const errors = [];
|
|
360
|
+
|
|
361
|
+
// Validar tipo
|
|
362
|
+
if (schema.type && typeof data !== schema.type) {
|
|
363
|
+
if (!(schema.type === 'array' && Array.isArray(data))) {
|
|
364
|
+
errors.push({
|
|
365
|
+
path: '',
|
|
366
|
+
message: `Expected ${schema.type}, got ${typeof data}`,
|
|
367
|
+
code: 'invalid_type'
|
|
368
|
+
});
|
|
369
|
+
return { valid: false, data: null, errors };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Validar required
|
|
374
|
+
if (schema.required && schema.type === 'object') {
|
|
375
|
+
for (const key of schema.required) {
|
|
376
|
+
if (!(key in data)) {
|
|
377
|
+
errors.push({
|
|
378
|
+
path: key,
|
|
379
|
+
message: `Missing required field: ${key}`,
|
|
380
|
+
code: 'missing_required'
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Validar propiedades
|
|
387
|
+
if (schema.properties && schema.type === 'object') {
|
|
388
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
389
|
+
if (key in data) {
|
|
390
|
+
const result = this.validateJsonSchema(data[key], propSchema);
|
|
391
|
+
if (!result.valid) {
|
|
392
|
+
errors.push(...result.errors.map(e => ({
|
|
393
|
+
...e,
|
|
394
|
+
path: e.path ? `${key}.${e.path}` : key
|
|
395
|
+
})));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Validar array items
|
|
402
|
+
if (schema.items && Array.isArray(data)) {
|
|
403
|
+
data.forEach((item, index) => {
|
|
404
|
+
const result = this.validateJsonSchema(item, schema.items);
|
|
405
|
+
if (!result.valid) {
|
|
406
|
+
errors.push(...result.errors.map(e => ({
|
|
407
|
+
...e,
|
|
408
|
+
path: `[${index}]${e.path ? '.' + e.path : ''}`
|
|
409
|
+
})));
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Validar enum
|
|
415
|
+
if (schema.enum && !schema.enum.includes(data)) {
|
|
416
|
+
errors.push({
|
|
417
|
+
path: '',
|
|
418
|
+
message: `Value must be one of: ${schema.enum.join(', ')}`,
|
|
419
|
+
code: 'invalid_enum'
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Validar string constraints
|
|
424
|
+
if (schema.type === 'string') {
|
|
425
|
+
if (schema.minLength && data.length < schema.minLength) {
|
|
426
|
+
errors.push({
|
|
427
|
+
path: '',
|
|
428
|
+
message: `String must be at least ${schema.minLength} characters`,
|
|
429
|
+
code: 'too_short'
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
if (schema.maxLength && data.length > schema.maxLength) {
|
|
433
|
+
errors.push({
|
|
434
|
+
path: '',
|
|
435
|
+
message: `String must be at most ${schema.maxLength} characters`,
|
|
436
|
+
code: 'too_long'
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Validar number constraints
|
|
442
|
+
if (schema.type === 'number' || schema.type === 'integer') {
|
|
443
|
+
if (schema.minimum !== undefined && data < schema.minimum) {
|
|
444
|
+
errors.push({
|
|
445
|
+
path: '',
|
|
446
|
+
message: `Number must be >= ${schema.minimum}`,
|
|
447
|
+
code: 'too_small'
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (schema.maximum !== undefined && data > schema.maximum) {
|
|
451
|
+
errors.push({
|
|
452
|
+
path: '',
|
|
453
|
+
message: `Number must be <= ${schema.maximum}`,
|
|
454
|
+
code: 'too_big'
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
valid: errors.length === 0,
|
|
461
|
+
data: errors.length === 0 ? data : null,
|
|
462
|
+
errors
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Valida output contra un schema
|
|
468
|
+
*/
|
|
469
|
+
validate(schemaName, data) {
|
|
470
|
+
const schemaEntry = this.schemas.get(schemaName);
|
|
471
|
+
|
|
472
|
+
if (!schemaEntry) {
|
|
473
|
+
return {
|
|
474
|
+
valid: false,
|
|
475
|
+
data: null,
|
|
476
|
+
errors: [{ path: '', message: `Schema not found: ${schemaName}`, code: 'schema_not_found' }]
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return schemaEntry.validator(data);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Valida y transforma (aplica defaults, coerción)
|
|
485
|
+
*/
|
|
486
|
+
validateAndTransform(schemaName, data, options = {}) {
|
|
487
|
+
const result = this.validate(schemaName, data);
|
|
488
|
+
|
|
489
|
+
if (!result.valid && options.coerce) {
|
|
490
|
+
// Intentar coerción de tipos
|
|
491
|
+
const coerced = this.coerceTypes(data, this.schemas.get(schemaName).schema);
|
|
492
|
+
const retryResult = this.validate(schemaName, coerced);
|
|
493
|
+
|
|
494
|
+
if (retryResult.valid) {
|
|
495
|
+
return { ...retryResult, coerced: true };
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (result.valid && options.applyDefaults) {
|
|
500
|
+
result.data = this.applyDefaults(result.data, this.schemas.get(schemaName).schema);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Intenta coercer tipos
|
|
508
|
+
*/
|
|
509
|
+
coerceTypes(data, schema) {
|
|
510
|
+
if (schema.type === 'string' && typeof data !== 'string') {
|
|
511
|
+
return String(data);
|
|
512
|
+
}
|
|
513
|
+
if (schema.type === 'number' && typeof data === 'string') {
|
|
514
|
+
const num = parseFloat(data);
|
|
515
|
+
if (!isNaN(num)) return num;
|
|
516
|
+
}
|
|
517
|
+
if (schema.type === 'integer' && typeof data === 'string') {
|
|
518
|
+
const num = parseInt(data, 10);
|
|
519
|
+
if (!isNaN(num)) return num;
|
|
520
|
+
}
|
|
521
|
+
if (schema.type === 'boolean') {
|
|
522
|
+
if (data === 'true') return true;
|
|
523
|
+
if (data === 'false') return false;
|
|
524
|
+
}
|
|
525
|
+
if (schema.type === 'array' && !Array.isArray(data)) {
|
|
526
|
+
return [data];
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (schema.type === 'object' && schema.properties) {
|
|
530
|
+
const result = { ...data };
|
|
531
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
532
|
+
if (key in result) {
|
|
533
|
+
result[key] = this.coerceTypes(result[key], propSchema);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return result;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return data;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Aplica valores por defecto
|
|
544
|
+
*/
|
|
545
|
+
applyDefaults(data, schema) {
|
|
546
|
+
if (schema.type !== 'object' || !schema.properties) {
|
|
547
|
+
return data;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const result = { ...data };
|
|
551
|
+
|
|
552
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
553
|
+
if (!(key in result) && propSchema.default !== undefined) {
|
|
554
|
+
result[key] = propSchema.default;
|
|
555
|
+
} else if (key in result && propSchema.type === 'object') {
|
|
556
|
+
result[key] = this.applyDefaults(result[key], propSchema);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return result;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Genera hints para corregir errores
|
|
565
|
+
*/
|
|
566
|
+
generateHints(errors, schema) {
|
|
567
|
+
return errors.map(error => {
|
|
568
|
+
let hint = `Fix ${error.path || 'root'}: ${error.message}`;
|
|
569
|
+
|
|
570
|
+
if (error.code === 'missing_required') {
|
|
571
|
+
hint += `. Add the missing field.`;
|
|
572
|
+
} else if (error.code === 'invalid_type') {
|
|
573
|
+
hint += `. Check the data type.`;
|
|
574
|
+
} else if (error.code === 'invalid_enum') {
|
|
575
|
+
hint += `. Use one of the allowed values.`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return { ...error, hint };
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Registra schemas predefinidos
|
|
584
|
+
*/
|
|
585
|
+
registerPredefinedSchemas() {
|
|
586
|
+
// AnalysisResult
|
|
587
|
+
this.registerSchema('analysis', {
|
|
588
|
+
type: 'object',
|
|
589
|
+
required: ['summary', 'files', 'recommendations'],
|
|
590
|
+
properties: {
|
|
591
|
+
summary: { type: 'string', minLength: 10, maxLength: 500 },
|
|
592
|
+
files: {
|
|
593
|
+
type: 'array',
|
|
594
|
+
items: {
|
|
595
|
+
type: 'object',
|
|
596
|
+
required: ['path', 'purpose'],
|
|
597
|
+
properties: {
|
|
598
|
+
path: { type: 'string' },
|
|
599
|
+
purpose: { type: 'string' },
|
|
600
|
+
complexity: { type: 'string', enum: ['low', 'medium', 'high'] },
|
|
601
|
+
linesOfCode: { type: 'integer', minimum: 0 }
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
recommendations: {
|
|
606
|
+
type: 'array',
|
|
607
|
+
items: {
|
|
608
|
+
type: 'object',
|
|
609
|
+
required: ['action', 'priority'],
|
|
610
|
+
properties: {
|
|
611
|
+
action: { type: 'string' },
|
|
612
|
+
priority: { type: 'string', enum: ['low', 'medium', 'high', 'critical'] },
|
|
613
|
+
reason: { type: 'string' }
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
techDebt: {
|
|
618
|
+
type: 'object',
|
|
619
|
+
properties: {
|
|
620
|
+
score: { type: 'number', minimum: 0, maximum: 10 },
|
|
621
|
+
issues: { type: 'array', items: { type: 'string' } }
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// ImplementationResult
|
|
628
|
+
this.registerSchema('implementation', {
|
|
629
|
+
type: 'object',
|
|
630
|
+
required: ['success'],
|
|
631
|
+
properties: {
|
|
632
|
+
success: { type: 'boolean' },
|
|
633
|
+
filesCreated: { type: 'array', default: [] },
|
|
634
|
+
filesModified: { type: 'array', default: [] },
|
|
635
|
+
testsCreated: { type: 'array', default: [] },
|
|
636
|
+
decisions: { type: 'array', default: [] },
|
|
637
|
+
errors: { type: 'array', default: [] }
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// ReviewResult
|
|
642
|
+
this.registerSchema('review', {
|
|
643
|
+
type: 'object',
|
|
644
|
+
required: ['approved', 'issues', 'summary', 'recommendation'],
|
|
645
|
+
properties: {
|
|
646
|
+
approved: { type: 'boolean' },
|
|
647
|
+
issues: { type: 'array' },
|
|
648
|
+
summary: {
|
|
649
|
+
type: 'object',
|
|
650
|
+
required: ['critical', 'high', 'medium', 'low', 'info'],
|
|
651
|
+
properties: {
|
|
652
|
+
critical: { type: 'integer', minimum: 0 },
|
|
653
|
+
high: { type: 'integer', minimum: 0 },
|
|
654
|
+
medium: { type: 'integer', minimum: 0 },
|
|
655
|
+
low: { type: 'integer', minimum: 0 },
|
|
656
|
+
info: { type: 'integer', minimum: 0 }
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
recommendation: { type: 'string', enum: ['approve', 'request_changes', 'needs_discussion'] }
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// VerificationResult
|
|
664
|
+
this.registerSchema('verification', {
|
|
665
|
+
type: 'object',
|
|
666
|
+
required: ['passed', 'tests', 'typescript', 'lint'],
|
|
667
|
+
properties: {
|
|
668
|
+
passed: { type: 'boolean' },
|
|
669
|
+
tests: {
|
|
670
|
+
type: 'object',
|
|
671
|
+
required: ['total', 'passed', 'failed', 'skipped'],
|
|
672
|
+
properties: {
|
|
673
|
+
total: { type: 'integer', minimum: 0 },
|
|
674
|
+
passed: { type: 'integer', minimum: 0 },
|
|
675
|
+
failed: { type: 'integer', minimum: 0 },
|
|
676
|
+
skipped: { type: 'integer', minimum: 0 }
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
typescript: {
|
|
680
|
+
type: 'object',
|
|
681
|
+
required: ['passed'],
|
|
682
|
+
properties: {
|
|
683
|
+
passed: { type: 'boolean' },
|
|
684
|
+
errors: { type: 'array' }
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
lint: {
|
|
688
|
+
type: 'object',
|
|
689
|
+
required: ['passed'],
|
|
690
|
+
properties: {
|
|
691
|
+
passed: { type: 'boolean' },
|
|
692
|
+
warnings: { type: 'integer', minimum: 0 },
|
|
693
|
+
errors: { type: 'integer', minimum: 0 }
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
coverage: {
|
|
697
|
+
type: 'object',
|
|
698
|
+
properties: {
|
|
699
|
+
statements: { type: 'number', minimum: 0, maximum: 100 },
|
|
700
|
+
branches: { type: 'number', minimum: 0, maximum: 100 },
|
|
701
|
+
functions: { type: 'number', minimum: 0, maximum: 100 },
|
|
702
|
+
lines: { type: 'number', minimum: 0, maximum: 100 }
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// PlanResult
|
|
709
|
+
this.registerSchema('plan', {
|
|
710
|
+
type: 'object',
|
|
711
|
+
required: ['phases'],
|
|
712
|
+
properties: {
|
|
713
|
+
phases: {
|
|
714
|
+
type: 'array',
|
|
715
|
+
items: {
|
|
716
|
+
type: 'object',
|
|
717
|
+
required: ['id', 'name', 'tasks'],
|
|
718
|
+
properties: {
|
|
719
|
+
id: { type: 'string' },
|
|
720
|
+
name: { type: 'string' },
|
|
721
|
+
description: { type: 'string' },
|
|
722
|
+
tasks: { type: 'array' },
|
|
723
|
+
estimatedComplexity: { type: 'string', enum: ['low', 'medium', 'high'] }
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
dependencies: { type: 'array' },
|
|
728
|
+
risks: { type: 'array' },
|
|
729
|
+
prerequisites: { type: 'array', items: { type: 'string' } }
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Singleton instance
|
|
736
|
+
let validatorInstance = null;
|
|
737
|
+
|
|
738
|
+
function getValidator() {
|
|
739
|
+
if (!validatorInstance) {
|
|
740
|
+
validatorInstance = new SchemaValidator();
|
|
741
|
+
}
|
|
742
|
+
return validatorInstance;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
module.exports = { SchemaValidator, getValidator };
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## Integración con Agentes
|
|
751
|
+
|
|
752
|
+
### Validación de Output en Agent Execution
|
|
753
|
+
|
|
754
|
+
```javascript
|
|
755
|
+
async function executeAgentWithSchema(agent, inputs, expectedSchema) {
|
|
756
|
+
const validator = getValidator();
|
|
757
|
+
const telemetry = getTelemetry();
|
|
758
|
+
|
|
759
|
+
// Ejecutar agente
|
|
760
|
+
const rawOutput = await executeAgent(agent, inputs);
|
|
761
|
+
|
|
762
|
+
// Parsear output estructurado
|
|
763
|
+
const parsed = parseAgentOutput(rawOutput);
|
|
764
|
+
|
|
765
|
+
// Validar contra schema
|
|
766
|
+
const validation = validator.validateAndTransform(expectedSchema, parsed, {
|
|
767
|
+
coerce: true,
|
|
768
|
+
applyDefaults: true
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
if (!validation.valid) {
|
|
772
|
+
telemetry.logger.warn(`Agent output validation failed: ${agent.id}`, {
|
|
773
|
+
errors: validation.errors,
|
|
774
|
+
schema: expectedSchema
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
// Retry con hints
|
|
778
|
+
if (agent.config.retryOnSchemaError) {
|
|
779
|
+
const hints = validator.generateHints(validation.errors, expectedSchema);
|
|
780
|
+
const retryOutput = await executeAgent(agent, {
|
|
781
|
+
...inputs,
|
|
782
|
+
_schemaHints: hints,
|
|
783
|
+
_expectedFormat: `Please return output matching this structure: ${JSON.stringify(expectedSchema)}`
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
const retryValidation = validator.validate(expectedSchema, parseAgentOutput(retryOutput));
|
|
787
|
+
if (retryValidation.valid) {
|
|
788
|
+
return retryValidation.data;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
throw new SchemaValidationError(validation.errors, expectedSchema);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return validation.data;
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### Declaración de Schema en Flow
|
|
800
|
+
|
|
801
|
+
```json
|
|
802
|
+
{
|
|
803
|
+
"id": "analyze",
|
|
804
|
+
"type": "agent",
|
|
805
|
+
"agent": "elsabro-analyst",
|
|
806
|
+
"outputSchema": "analysis",
|
|
807
|
+
"config": {
|
|
808
|
+
"retryOnSchemaError": true,
|
|
809
|
+
"maxSchemaRetries": 2
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
---
|
|
815
|
+
|
|
816
|
+
## Comandos de Usuario
|
|
817
|
+
|
|
818
|
+
### /elsabro:schema
|
|
819
|
+
|
|
820
|
+
```bash
|
|
821
|
+
/elsabro:schema list # Listar schemas disponibles
|
|
822
|
+
/elsabro:schema show <name> # Mostrar schema específico
|
|
823
|
+
/elsabro:schema validate <file> # Validar archivo contra schema
|
|
824
|
+
/elsabro:schema create # Crear nuevo schema (interactivo)
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
---
|
|
828
|
+
|
|
829
|
+
## Configuración
|
|
830
|
+
|
|
831
|
+
### .planning/schemas-config.json
|
|
832
|
+
|
|
833
|
+
```json
|
|
834
|
+
{
|
|
835
|
+
"schemas": {
|
|
836
|
+
"validation": {
|
|
837
|
+
"strict": true,
|
|
838
|
+
"coerceTypes": true,
|
|
839
|
+
"applyDefaults": true,
|
|
840
|
+
"retryOnError": true,
|
|
841
|
+
"maxRetries": 2
|
|
842
|
+
},
|
|
843
|
+
"customSchemas": {
|
|
844
|
+
"directory": ".planning/schemas/",
|
|
845
|
+
"autoLoad": true
|
|
846
|
+
},
|
|
847
|
+
"agentDefaults": {
|
|
848
|
+
"elsabro-analyst": "analysis",
|
|
849
|
+
"elsabro-executor": "implementation",
|
|
850
|
+
"elsabro-verifier": "verification",
|
|
851
|
+
"elsabro-planner": "plan",
|
|
852
|
+
"code-reviewer": "review",
|
|
853
|
+
"pr-review-toolkit:code-reviewer": "review",
|
|
854
|
+
"pr-review-toolkit:silent-failure-hunter": "review"
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
```
|