@codexa/cli 8.5.0 → 8.6.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.
@@ -1,412 +1,402 @@
1
- /**
2
- * Subagent Protocol Parser & Validator
3
- *
4
- * Este modulo GARANTE que todos os retornos de subagents sigam o protocolo definido.
5
- * Sem validacao correta, task done NAO pode ser executado.
6
- */
7
-
8
- // Tipos do protocolo
9
- export interface Decision {
10
- title: string;
11
- decision: string;
12
- rationale?: string;
13
- }
14
-
15
- export interface Knowledge {
16
- category: "discovery" | "decision" | "blocker" | "pattern" | "constraint";
17
- content: string;
18
- severity: "info" | "warning" | "critical";
19
- }
20
-
21
- // v8.0: Raciocínio do subagent
22
- export interface Reasoning {
23
- approach: string; // Como abordou o problema
24
- challenges?: string[]; // Desafios encontrados
25
- alternatives?: string[]; // Alternativas consideradas
26
- recommendations?: string; // Recomendações para próximas tasks
27
- }
28
-
29
- export interface SubagentReturn {
30
- status: "completed" | "blocked" | "needs_decision";
31
- summary: string;
32
- files_created: string[];
33
- files_modified: string[];
34
- patterns_discovered?: string[];
35
- decisions_made?: Decision[];
36
- blockers?: string[];
37
- knowledge_to_broadcast?: Knowledge[];
38
- // v8.0: Campos de raciocínio (recomendados mas não obrigatórios por compatibilidade)
39
- reasoning?: Reasoning;
40
- // v8.5: Utilities criadas/exportadas nesta task (DRY enforcement)
41
- utilities_created?: Array<{
42
- name: string;
43
- file: string;
44
- type?: string;
45
- signature?: string;
46
- description?: string;
47
- }>;
48
- }
49
-
50
- export interface ParseResult {
51
- success: boolean;
52
- data?: SubagentReturn;
53
- errors: string[];
54
- rawInput: string;
55
- }
56
-
57
- const VALID_STATUSES = ["completed", "blocked", "needs_decision"];
58
- const VALID_KNOWLEDGE_CATEGORIES = ["discovery", "decision", "blocker", "pattern", "constraint"];
59
- const VALID_SEVERITIES = ["info", "warning", "critical"];
60
-
61
- /**
62
- * Extrai JSON de uma string que pode conter texto misto
63
- * Subagents podem retornar texto com JSON embutido
64
- */
65
- function extractJsonFromText(text: string): string | null {
66
- // Tenta encontrar JSON em bloco de codigo
67
- const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
68
- if (codeBlockMatch) {
69
- return codeBlockMatch[1].trim();
70
- }
71
-
72
- // Tenta encontrar objeto JSON direto (busca mais robusta)
73
- // Procura por { ... "status" ... } capturando todo o objeto
74
- let depth = 0;
75
- let start = -1;
76
- let inString = false;
77
- let escape = false;
78
-
79
- for (let i = 0; i < text.length; i++) {
80
- const char = text[i];
81
-
82
- if (escape) {
83
- escape = false;
84
- continue;
85
- }
86
-
87
- if (char === "\\") {
88
- escape = true;
89
- continue;
90
- }
91
-
92
- if (char === '"' && !escape) {
93
- inString = !inString;
94
- continue;
95
- }
96
-
97
- if (inString) continue;
98
-
99
- if (char === "{") {
100
- if (depth === 0) start = i;
101
- depth++;
102
- } else if (char === "}") {
103
- depth--;
104
- if (depth === 0 && start !== -1) {
105
- const candidate = text.slice(start, i + 1);
106
- if (candidate.includes('"status"')) {
107
- return candidate;
108
- }
109
- start = -1;
110
- }
111
- }
112
- }
113
-
114
- // Se o texto inteiro parece ser JSON
115
- const trimmed = text.trim();
116
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
117
- return trimmed;
118
- }
119
-
120
- return null;
121
- }
122
-
123
- /**
124
- * Valida um campo string
125
- */
126
- function validateString(value: unknown, field: string, minLen = 0, maxLen = Infinity): string | null {
127
- if (typeof value !== "string") {
128
- return `Campo '${field}' deve ser string`;
129
- }
130
- if (value.length < minLen) {
131
- return `Campo '${field}' deve ter pelo menos ${minLen} caracteres`;
132
- }
133
- if (value.length > maxLen) {
134
- return `Campo '${field}' deve ter no maximo ${maxLen} caracteres`;
135
- }
136
- return null;
137
- }
138
-
139
- /**
140
- * Valida um array de strings
141
- */
142
- function validateStringArray(value: unknown, field: string): string | null {
143
- if (!Array.isArray(value)) {
144
- return `Campo '${field}' deve ser um array`;
145
- }
146
- for (let i = 0; i < value.length; i++) {
147
- if (typeof value[i] !== "string") {
148
- return `Campo '${field}[${i}]' deve ser string`;
149
- }
150
- }
151
- return null;
152
- }
153
-
154
- /**
155
- * Valida uma decision
156
- */
157
- function validateDecision(value: unknown, index: number): string[] {
158
- const errors: string[] = [];
159
- if (typeof value !== "object" || value === null) {
160
- return [`decisions_made[${index}] deve ser um objeto`];
161
- }
162
- const dec = value as Record<string, unknown>;
163
-
164
- if (typeof dec.title !== "string" || dec.title.length === 0) {
165
- errors.push(`decisions_made[${index}].title obrigatorio`);
166
- }
167
- if (typeof dec.decision !== "string" || dec.decision.length === 0) {
168
- errors.push(`decisions_made[${index}].decision obrigatorio`);
169
- }
170
- return errors;
171
- }
172
-
173
- /**
174
- * Valida um knowledge
175
- */
176
- function validateKnowledge(value: unknown, index: number): string[] {
177
- const errors: string[] = [];
178
- if (typeof value !== "object" || value === null) {
179
- return [`knowledge_to_broadcast[${index}] deve ser um objeto`];
180
- }
181
- const k = value as Record<string, unknown>;
182
-
183
- if (!VALID_KNOWLEDGE_CATEGORIES.includes(k.category as string)) {
184
- errors.push(`knowledge_to_broadcast[${index}].category invalido. Use: ${VALID_KNOWLEDGE_CATEGORIES.join(", ")}`);
185
- }
186
- if (typeof k.content !== "string" || k.content.length === 0) {
187
- errors.push(`knowledge_to_broadcast[${index}].content obrigatorio`);
188
- }
189
- if (!VALID_SEVERITIES.includes(k.severity as string)) {
190
- errors.push(`knowledge_to_broadcast[${index}].severity invalido. Use: ${VALID_SEVERITIES.join(", ")}`);
191
- }
192
- return errors;
193
- }
194
-
195
- /**
196
- * Parse e valida o retorno de um subagent
197
- */
198
- export function parseSubagentReturn(input: string): ParseResult {
199
- const errors: string[] = [];
200
-
201
- // 1. Extrair JSON do input
202
- const jsonStr = extractJsonFromText(input);
203
- if (!jsonStr) {
204
- return {
205
- success: false,
206
- errors: [
207
- "Nenhum JSON encontrado no retorno do subagent.",
208
- "O subagent DEVE retornar um objeto JSON no formato especificado em PROTOCOL.md.",
209
- "Formatos aceitos: JSON puro, ou JSON em bloco ```json```",
210
- ],
211
- rawInput: input,
212
- };
213
- }
214
-
215
- // 2. Parse JSON
216
- let parsed: Record<string, unknown>;
217
- try {
218
- parsed = JSON.parse(jsonStr);
219
- } catch (e) {
220
- return {
221
- success: false,
222
- errors: [
223
- `JSON invalido: ${(e as Error).message}`,
224
- "Verifique se o JSON esta bem formatado.",
225
- ],
226
- rawInput: input,
227
- };
228
- }
229
-
230
- // 3. Validar campos obrigatorios
231
-
232
- // status
233
- if (!VALID_STATUSES.includes(parsed.status as string)) {
234
- errors.push(`Campo 'status' invalido. Use: ${VALID_STATUSES.join(", ")}`);
235
- }
236
-
237
- // summary
238
- const summaryError = validateString(parsed.summary, "summary", 10, 500);
239
- if (summaryError) errors.push(summaryError);
240
-
241
- // files_created
242
- const filesCreatedError = validateStringArray(parsed.files_created, "files_created");
243
- if (filesCreatedError) errors.push(filesCreatedError);
244
-
245
- // files_modified
246
- const filesModifiedError = validateStringArray(parsed.files_modified, "files_modified");
247
- if (filesModifiedError) errors.push(filesModifiedError);
248
-
249
- // 4. Validar campos opcionais se presentes
250
-
251
- // patterns_discovered
252
- if (parsed.patterns_discovered !== undefined) {
253
- const patternsError = validateStringArray(parsed.patterns_discovered, "patterns_discovered");
254
- if (patternsError) errors.push(patternsError);
255
- }
256
-
257
- // blockers
258
- if (parsed.blockers !== undefined) {
259
- const blockersError = validateStringArray(parsed.blockers, "blockers");
260
- if (blockersError) errors.push(blockersError);
261
- }
262
-
263
- // decisions_made
264
- if (parsed.decisions_made !== undefined) {
265
- if (!Array.isArray(parsed.decisions_made)) {
266
- errors.push("Campo 'decisions_made' deve ser um array");
267
- } else {
268
- for (let i = 0; i < parsed.decisions_made.length; i++) {
269
- errors.push(...validateDecision(parsed.decisions_made[i], i));
270
- }
271
- }
272
- }
273
-
274
- // knowledge_to_broadcast
275
- if (parsed.knowledge_to_broadcast !== undefined) {
276
- if (!Array.isArray(parsed.knowledge_to_broadcast)) {
277
- errors.push("Campo 'knowledge_to_broadcast' deve ser um array");
278
- } else {
279
- for (let i = 0; i < parsed.knowledge_to_broadcast.length; i++) {
280
- errors.push(...validateKnowledge(parsed.knowledge_to_broadcast[i], i));
281
- }
282
- }
283
- }
284
-
285
- // v8.0: reasoning (opcional, mas validar estrutura se presente)
286
- if (parsed.reasoning !== undefined) {
287
- if (typeof parsed.reasoning !== "object" || parsed.reasoning === null) {
288
- errors.push("Campo 'reasoning' deve ser um objeto");
289
- } else {
290
- const r = parsed.reasoning as Record<string, unknown>;
291
- if (r.approach !== undefined && typeof r.approach !== "string") {
292
- errors.push("Campo 'reasoning.approach' deve ser string");
293
- }
294
- if (r.challenges !== undefined) {
295
- const challengesError = validateStringArray(r.challenges, "reasoning.challenges");
296
- if (challengesError) errors.push(challengesError);
297
- }
298
- if (r.alternatives !== undefined) {
299
- const alternativesError = validateStringArray(r.alternatives, "reasoning.alternatives");
300
- if (alternativesError) errors.push(alternativesError);
301
- }
302
- if (r.recommendations !== undefined && typeof r.recommendations !== "string") {
303
- errors.push("Campo 'reasoning.recommendations' deve ser string");
304
- }
305
- }
306
- }
307
-
308
- // v8.5: utilities_created (opcional, validar estrutura se presente)
309
- if (parsed.utilities_created !== undefined) {
310
- if (!Array.isArray(parsed.utilities_created)) {
311
- errors.push("Campo 'utilities_created' deve ser um array");
312
- } else {
313
- for (let i = 0; i < parsed.utilities_created.length; i++) {
314
- const u = parsed.utilities_created[i] as any;
315
- if (!u || typeof u !== "object") {
316
- errors.push(`utilities_created[${i}] deve ser um objeto`);
317
- continue;
318
- }
319
- if (typeof u.name !== "string" || u.name.length === 0) {
320
- errors.push(`utilities_created[${i}].name obrigatorio`);
321
- }
322
- if (typeof u.file !== "string" || u.file.length === 0) {
323
- errors.push(`utilities_created[${i}].file obrigatorio`);
324
- }
325
- }
326
- }
327
- }
328
-
329
- // 5. Validacoes semanticas
330
-
331
- // Se blocked, deve ter blockers
332
- if (parsed.status === "blocked") {
333
- if (!parsed.blockers || !Array.isArray(parsed.blockers) || parsed.blockers.length === 0) {
334
- errors.push("Status 'blocked' requer campo 'blockers' nao vazio");
335
- }
336
- }
337
-
338
- // Se needs_decision, deve ter blockers descrevendo a decisao
339
- if (parsed.status === "needs_decision") {
340
- if (!parsed.blockers || !Array.isArray(parsed.blockers) || parsed.blockers.length === 0) {
341
- errors.push("Status 'needs_decision' requer campo 'blockers' descrevendo a decisao necessaria");
342
- }
343
- }
344
-
345
- if (errors.length > 0) {
346
- return {
347
- success: false,
348
- errors,
349
- rawInput: input,
350
- };
351
- }
352
-
353
- // Cast para tipo correto
354
- const data: SubagentReturn = {
355
- status: parsed.status as SubagentReturn["status"],
356
- summary: parsed.summary as string,
357
- files_created: parsed.files_created as string[],
358
- files_modified: parsed.files_modified as string[],
359
- patterns_discovered: parsed.patterns_discovered as string[] | undefined,
360
- decisions_made: parsed.decisions_made as Decision[] | undefined,
361
- blockers: parsed.blockers as string[] | undefined,
362
- knowledge_to_broadcast: parsed.knowledge_to_broadcast as Knowledge[] | undefined,
363
- reasoning: parsed.reasoning as Reasoning | undefined,
364
- utilities_created: parsed.utilities_created as SubagentReturn["utilities_created"],
365
- };
366
-
367
- return {
368
- success: true,
369
- data,
370
- errors: [],
371
- rawInput: input,
372
- };
373
- }
374
-
375
- /**
376
- * Formata erros de validacao para exibicao
377
- */
378
- export function formatValidationErrors(result: ParseResult): string {
379
- if (result.success) return "";
380
-
381
- let output = "\n[X] ERRO: Retorno do subagent INVALIDO\n";
382
- output += "─".repeat(50) + "\n\n";
383
-
384
- for (const error of result.errors) {
385
- output += ` - ${error}\n`;
386
- }
387
-
388
- output += "\n" + "─".repeat(50) + "\n";
389
- output += "O retorno deve seguir o formato:\n\n";
390
- output += `{
391
- "status": "completed | blocked | needs_decision",
392
- "summary": "Resumo do que foi feito (10-500 chars)",
393
- "files_created": ["path/to/file.ts"],
394
- "files_modified": ["path/to/other.ts"],
395
- "patterns_discovered": ["Pattern identificado"],
396
- "decisions_made": [{"title": "...", "decision": "..."}],
397
- "blockers": ["Bloqueio se status != completed"],
398
- "knowledge_to_broadcast": [{"category": "...", "content": "...", "severity": "..."}]
399
- }\n`;
400
-
401
- return output;
402
- }
403
-
404
- /**
405
- * Verifica se uma string parece conter um retorno de subagent
406
- */
407
- export function looksLikeSubagentReturn(text: string): boolean {
408
- return text.includes('"status"') &&
409
- (text.includes('"completed"') ||
410
- text.includes('"blocked"') ||
411
- text.includes('"needs_decision"'));
1
+ /**
2
+ * Subagent Protocol Parser & Validator
3
+ *
4
+ * Este modulo GARANTE que todos os retornos de subagents sigam o protocolo definido.
5
+ * Sem validacao correta, task done NAO pode ser executado.
6
+ */
7
+
8
+ // Tipos do protocolo
9
+ export interface Decision {
10
+ title: string;
11
+ decision: string;
12
+ rationale?: string;
13
+ }
14
+
15
+ export interface Knowledge {
16
+ category: "discovery" | "decision" | "blocker" | "pattern" | "constraint";
17
+ content: string;
18
+ severity: "info" | "warning" | "critical";
19
+ }
20
+
21
+ // v8.0: Raciocínio do subagent
22
+ export interface Reasoning {
23
+ approach: string; // Como abordou o problema
24
+ challenges?: string[]; // Desafios encontrados
25
+ alternatives?: string[]; // Alternativas consideradas
26
+ recommendations?: string; // Recomendações para próximas tasks
27
+ }
28
+
29
+ export interface SubagentReturn {
30
+ status: "completed" | "blocked" | "needs_decision";
31
+ summary: string;
32
+ files_created: string[];
33
+ files_modified: string[];
34
+ patterns_discovered?: string[];
35
+ decisions_made?: Decision[];
36
+ blockers?: string[];
37
+ knowledge_to_broadcast?: Knowledge[];
38
+ // v8.0: Campos de raciocínio (recomendados mas não obrigatórios por compatibilidade)
39
+ reasoning?: Reasoning;
40
+ // v8.5: Utilities criadas/exportadas nesta task (DRY enforcement)
41
+ utilities_created?: Array<{
42
+ name: string;
43
+ file: string;
44
+ type?: string;
45
+ signature?: string;
46
+ description?: string;
47
+ }>;
48
+ }
49
+
50
+ export interface ParseResult {
51
+ success: boolean;
52
+ data?: SubagentReturn;
53
+ errors: string[];
54
+ rawInput: string;
55
+ }
56
+
57
+ const VALID_STATUSES = ["completed", "blocked", "needs_decision"];
58
+ const VALID_KNOWLEDGE_CATEGORIES = ["discovery", "decision", "blocker", "pattern", "constraint"];
59
+ const VALID_SEVERITIES = ["info", "warning", "critical"];
60
+
61
+ /**
62
+ * Extrai JSON de uma string que pode conter texto misto
63
+ * Subagents podem retornar texto com JSON embutido
64
+ */
65
+ function extractJsonFromText(text: string): string | null {
66
+ // Tenta encontrar JSON em bloco de codigo
67
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
68
+ if (codeBlockMatch) {
69
+ return codeBlockMatch[1].trim();
70
+ }
71
+
72
+ // Tenta encontrar objeto JSON direto (busca mais robusta)
73
+ // Procura por { ... "status" ... } capturando todo o objeto
74
+ let depth = 0;
75
+ let start = -1;
76
+ let inString = false;
77
+ let escape = false;
78
+
79
+ for (let i = 0; i < text.length; i++) {
80
+ const char = text[i];
81
+
82
+ if (escape) {
83
+ escape = false;
84
+ continue;
85
+ }
86
+
87
+ if (char === "\\") {
88
+ escape = true;
89
+ continue;
90
+ }
91
+
92
+ if (char === '"' && !escape) {
93
+ inString = !inString;
94
+ continue;
95
+ }
96
+
97
+ if (inString) continue;
98
+
99
+ if (char === "{") {
100
+ if (depth === 0) start = i;
101
+ depth++;
102
+ } else if (char === "}") {
103
+ depth--;
104
+ if (depth === 0 && start !== -1) {
105
+ const candidate = text.slice(start, i + 1);
106
+ if (candidate.includes('"status"')) {
107
+ return candidate;
108
+ }
109
+ start = -1;
110
+ }
111
+ }
112
+ }
113
+
114
+ // Se o texto inteiro parece ser JSON
115
+ const trimmed = text.trim();
116
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
117
+ return trimmed;
118
+ }
119
+
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * Valida um campo string
125
+ */
126
+ function validateString(value: unknown, field: string, minLen = 0, maxLen = Infinity): string | null {
127
+ if (typeof value !== "string") {
128
+ return `Campo '${field}' deve ser string`;
129
+ }
130
+ if (value.length < minLen) {
131
+ return `Campo '${field}' deve ter pelo menos ${minLen} caracteres`;
132
+ }
133
+ if (value.length > maxLen) {
134
+ return `Campo '${field}' deve ter no maximo ${maxLen} caracteres`;
135
+ }
136
+ return null;
137
+ }
138
+
139
+ /**
140
+ * Valida um array de strings
141
+ */
142
+ function validateStringArray(value: unknown, field: string): string | null {
143
+ if (!Array.isArray(value)) {
144
+ return `Campo '${field}' deve ser um array`;
145
+ }
146
+ for (let i = 0; i < value.length; i++) {
147
+ if (typeof value[i] !== "string") {
148
+ return `Campo '${field}[${i}]' deve ser string`;
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+
154
+ /**
155
+ * Valida uma decision
156
+ */
157
+ function validateDecision(value: unknown, index: number): string[] {
158
+ const errors: string[] = [];
159
+ if (typeof value !== "object" || value === null) {
160
+ return [`decisions_made[${index}] deve ser um objeto`];
161
+ }
162
+ const dec = value as Record<string, unknown>;
163
+
164
+ if (typeof dec.title !== "string" || dec.title.length === 0) {
165
+ errors.push(`decisions_made[${index}].title obrigatorio`);
166
+ }
167
+ if (typeof dec.decision !== "string" || dec.decision.length === 0) {
168
+ errors.push(`decisions_made[${index}].decision obrigatorio`);
169
+ }
170
+ return errors;
171
+ }
172
+
173
+ /**
174
+ * Valida um knowledge
175
+ */
176
+ function validateKnowledge(value: unknown, index: number): string[] {
177
+ const errors: string[] = [];
178
+ if (typeof value !== "object" || value === null) {
179
+ return [`knowledge_to_broadcast[${index}] deve ser um objeto`];
180
+ }
181
+ const k = value as Record<string, unknown>;
182
+
183
+ if (!VALID_KNOWLEDGE_CATEGORIES.includes(k.category as string)) {
184
+ errors.push(`knowledge_to_broadcast[${index}].category invalido. Use: ${VALID_KNOWLEDGE_CATEGORIES.join(", ")}`);
185
+ }
186
+ if (typeof k.content !== "string" || k.content.length === 0) {
187
+ errors.push(`knowledge_to_broadcast[${index}].content obrigatorio`);
188
+ }
189
+ if (!VALID_SEVERITIES.includes(k.severity as string)) {
190
+ errors.push(`knowledge_to_broadcast[${index}].severity invalido. Use: ${VALID_SEVERITIES.join(", ")}`);
191
+ }
192
+ return errors;
193
+ }
194
+
195
+ /**
196
+ * Parse e valida o retorno de um subagent
197
+ */
198
+ export function parseSubagentReturn(input: string): ParseResult {
199
+ const errors: string[] = [];
200
+
201
+ // 1. Extrair JSON do input
202
+ const jsonStr = extractJsonFromText(input);
203
+ if (!jsonStr) {
204
+ return {
205
+ success: false,
206
+ errors: [
207
+ "Nenhum JSON encontrado no retorno do subagent.",
208
+ "O subagent DEVE retornar um objeto JSON no formato especificado em PROTOCOL.md.",
209
+ "Formatos aceitos: JSON puro, ou JSON em bloco ```json```",
210
+ ],
211
+ rawInput: input,
212
+ };
213
+ }
214
+
215
+ // 2. Parse JSON
216
+ let parsed: Record<string, unknown>;
217
+ try {
218
+ parsed = JSON.parse(jsonStr);
219
+ } catch (e) {
220
+ return {
221
+ success: false,
222
+ errors: [
223
+ `JSON invalido: ${(e as Error).message}`,
224
+ "Verifique se o JSON esta bem formatado.",
225
+ ],
226
+ rawInput: input,
227
+ };
228
+ }
229
+
230
+ // 3. Validar campos obrigatorios
231
+
232
+ // status
233
+ if (!VALID_STATUSES.includes(parsed.status as string)) {
234
+ errors.push(`Campo 'status' invalido. Use: ${VALID_STATUSES.join(", ")}`);
235
+ }
236
+
237
+ // summary
238
+ const summaryError = validateString(parsed.summary, "summary", 10, 500);
239
+ if (summaryError) errors.push(summaryError);
240
+
241
+ // files_created
242
+ const filesCreatedError = validateStringArray(parsed.files_created, "files_created");
243
+ if (filesCreatedError) errors.push(filesCreatedError);
244
+
245
+ // files_modified
246
+ const filesModifiedError = validateStringArray(parsed.files_modified, "files_modified");
247
+ if (filesModifiedError) errors.push(filesModifiedError);
248
+
249
+ // 4. Validar campos opcionais se presentes
250
+
251
+ // patterns_discovered
252
+ if (parsed.patterns_discovered !== undefined) {
253
+ const patternsError = validateStringArray(parsed.patterns_discovered, "patterns_discovered");
254
+ if (patternsError) errors.push(patternsError);
255
+ }
256
+
257
+ // blockers
258
+ if (parsed.blockers !== undefined) {
259
+ const blockersError = validateStringArray(parsed.blockers, "blockers");
260
+ if (blockersError) errors.push(blockersError);
261
+ }
262
+
263
+ // decisions_made
264
+ if (parsed.decisions_made !== undefined) {
265
+ if (!Array.isArray(parsed.decisions_made)) {
266
+ errors.push("Campo 'decisions_made' deve ser um array");
267
+ } else {
268
+ for (let i = 0; i < parsed.decisions_made.length; i++) {
269
+ errors.push(...validateDecision(parsed.decisions_made[i], i));
270
+ }
271
+ }
272
+ }
273
+
274
+ // knowledge_to_broadcast
275
+ if (parsed.knowledge_to_broadcast !== undefined) {
276
+ if (!Array.isArray(parsed.knowledge_to_broadcast)) {
277
+ errors.push("Campo 'knowledge_to_broadcast' deve ser um array");
278
+ } else {
279
+ for (let i = 0; i < parsed.knowledge_to_broadcast.length; i++) {
280
+ errors.push(...validateKnowledge(parsed.knowledge_to_broadcast[i], i));
281
+ }
282
+ }
283
+ }
284
+
285
+ // v8.0: reasoning (opcional, mas validar estrutura se presente)
286
+ if (parsed.reasoning !== undefined) {
287
+ if (typeof parsed.reasoning !== "object" || parsed.reasoning === null) {
288
+ errors.push("Campo 'reasoning' deve ser um objeto");
289
+ } else {
290
+ const r = parsed.reasoning as Record<string, unknown>;
291
+ if (r.approach !== undefined && typeof r.approach !== "string") {
292
+ errors.push("Campo 'reasoning.approach' deve ser string");
293
+ }
294
+ if (r.challenges !== undefined) {
295
+ const challengesError = validateStringArray(r.challenges, "reasoning.challenges");
296
+ if (challengesError) errors.push(challengesError);
297
+ }
298
+ if (r.alternatives !== undefined) {
299
+ const alternativesError = validateStringArray(r.alternatives, "reasoning.alternatives");
300
+ if (alternativesError) errors.push(alternativesError);
301
+ }
302
+ if (r.recommendations !== undefined && typeof r.recommendations !== "string") {
303
+ errors.push("Campo 'reasoning.recommendations' deve ser string");
304
+ }
305
+ }
306
+ }
307
+
308
+ // v8.5: utilities_created (opcional, validar estrutura se presente)
309
+ if (parsed.utilities_created !== undefined) {
310
+ if (!Array.isArray(parsed.utilities_created)) {
311
+ errors.push("Campo 'utilities_created' deve ser um array");
312
+ } else {
313
+ for (let i = 0; i < parsed.utilities_created.length; i++) {
314
+ const u = parsed.utilities_created[i] as any;
315
+ if (!u || typeof u !== "object") {
316
+ errors.push(`utilities_created[${i}] deve ser um objeto`);
317
+ continue;
318
+ }
319
+ if (typeof u.name !== "string" || u.name.length === 0) {
320
+ errors.push(`utilities_created[${i}].name obrigatorio`);
321
+ }
322
+ if (typeof u.file !== "string" || u.file.length === 0) {
323
+ errors.push(`utilities_created[${i}].file obrigatorio`);
324
+ }
325
+ }
326
+ }
327
+ }
328
+
329
+ // 5. Validacoes semanticas
330
+
331
+ // Se blocked, deve ter blockers
332
+ if (parsed.status === "blocked") {
333
+ if (!parsed.blockers || !Array.isArray(parsed.blockers) || parsed.blockers.length === 0) {
334
+ errors.push("Status 'blocked' requer campo 'blockers' nao vazio");
335
+ }
336
+ }
337
+
338
+ // Se needs_decision, deve ter blockers descrevendo a decisao
339
+ if (parsed.status === "needs_decision") {
340
+ if (!parsed.blockers || !Array.isArray(parsed.blockers) || parsed.blockers.length === 0) {
341
+ errors.push("Status 'needs_decision' requer campo 'blockers' descrevendo a decisao necessaria");
342
+ }
343
+ }
344
+
345
+ if (errors.length > 0) {
346
+ return {
347
+ success: false,
348
+ errors,
349
+ rawInput: input,
350
+ };
351
+ }
352
+
353
+ // Cast para tipo correto
354
+ const data: SubagentReturn = {
355
+ status: parsed.status as SubagentReturn["status"],
356
+ summary: parsed.summary as string,
357
+ files_created: parsed.files_created as string[],
358
+ files_modified: parsed.files_modified as string[],
359
+ patterns_discovered: parsed.patterns_discovered as string[] | undefined,
360
+ decisions_made: parsed.decisions_made as Decision[] | undefined,
361
+ blockers: parsed.blockers as string[] | undefined,
362
+ knowledge_to_broadcast: parsed.knowledge_to_broadcast as Knowledge[] | undefined,
363
+ reasoning: parsed.reasoning as Reasoning | undefined,
364
+ utilities_created: parsed.utilities_created as SubagentReturn["utilities_created"],
365
+ };
366
+
367
+ return {
368
+ success: true,
369
+ data,
370
+ errors: [],
371
+ rawInput: input,
372
+ };
373
+ }
374
+
375
+ /**
376
+ * Formata erros de validacao para exibicao
377
+ */
378
+ export function formatValidationErrors(result: ParseResult): string {
379
+ if (result.success) return "";
380
+
381
+ let output = "\n[X] ERRO: Retorno do subagent INVALIDO\n";
382
+ output += "─".repeat(50) + "\n\n";
383
+
384
+ for (const error of result.errors) {
385
+ output += ` - ${error}\n`;
386
+ }
387
+
388
+ output += "\n" + "─".repeat(50) + "\n";
389
+ output += "O retorno deve seguir o formato:\n\n";
390
+ output += `{
391
+ "status": "completed | blocked | needs_decision",
392
+ "summary": "Resumo do que foi feito (10-500 chars)",
393
+ "files_created": ["path/to/file.ts"],
394
+ "files_modified": ["path/to/other.ts"],
395
+ "patterns_discovered": ["Pattern identificado"],
396
+ "decisions_made": [{"title": "...", "decision": "..."}],
397
+ "blockers": ["Bloqueio se status != completed"],
398
+ "knowledge_to_broadcast": [{"category": "...", "content": "...", "severity": "..."}]
399
+ }\n`;
400
+
401
+ return output;
412
402
  }