@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.
- package/commands/architect.ts +760 -896
- package/commands/check.ts +131 -131
- package/commands/clear.ts +170 -174
- package/commands/decide.ts +249 -249
- package/commands/discover.ts +82 -10
- package/commands/knowledge.ts +361 -361
- package/commands/patterns.ts +621 -621
- package/commands/plan.ts +376 -376
- package/commands/product.ts +626 -628
- package/commands/research.ts +754 -754
- package/commands/review.ts +463 -463
- package/commands/standards.ts +200 -223
- package/commands/task.ts +2 -2
- package/commands/utils.ts +1021 -1021
- package/db/connection.ts +32 -32
- package/db/schema.ts +719 -788
- package/detectors/loader.ts +0 -12
- package/gates/standards-validator.ts +204 -204
- package/gates/validator.ts +441 -441
- package/package.json +43 -43
- package/protocol/process-return.ts +450 -450
- package/protocol/subagent-protocol.ts +401 -411
- package/workflow.ts +0 -18
|
@@ -1,451 +1,451 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Process Subagent Return
|
|
3
|
-
*
|
|
4
|
-
* Este modulo processa automaticamente o retorno de um subagent,
|
|
5
|
-
* extraindo e registrando:
|
|
6
|
-
* - Knowledge broadcast
|
|
7
|
-
* - Decisions
|
|
8
|
-
* - Artifacts (files created/modified)
|
|
9
|
-
* - Patterns descobertos
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { getDb } from "../db/connection";
|
|
13
|
-
import { SubagentReturn, Knowledge } from "./subagent-protocol";
|
|
14
|
-
import { addReasoning, addGraphRelation, getRelatedFiles, upsertUtility } from "../db/schema";
|
|
15
|
-
import { extractUtilitiesFromFile, inferScopeFromPath } from "../commands/patterns";
|
|
16
|
-
|
|
17
|
-
interface ProcessResult {
|
|
18
|
-
success: boolean;
|
|
19
|
-
knowledgeAdded: number;
|
|
20
|
-
decisionsAdded: number;
|
|
21
|
-
artifactsAdded: number;
|
|
22
|
-
patternsAdded: number;
|
|
23
|
-
reasoningAdded: number; // v8.0
|
|
24
|
-
relationsAdded: number; // v8.0
|
|
25
|
-
utilitiesRegistered: number; // v8.5
|
|
26
|
-
errors: string[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getNextDecisionId(specId: string): string {
|
|
30
|
-
const db = getDb();
|
|
31
|
-
const last = db
|
|
32
|
-
.query("SELECT id FROM decisions WHERE spec_id = ? ORDER BY created_at DESC LIMIT 1")
|
|
33
|
-
.get(specId) as any;
|
|
34
|
-
|
|
35
|
-
if (!last) return "DEC-001";
|
|
36
|
-
|
|
37
|
-
const num = parseInt(last.id.replace("DEC-", "")) + 1;
|
|
38
|
-
return `DEC-${num.toString().padStart(3, "0")}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Processa o retorno de um subagent e registra automaticamente
|
|
43
|
-
* todos os dados extraidos no banco
|
|
44
|
-
*/
|
|
45
|
-
export function processSubagentReturn(
|
|
46
|
-
specId: string,
|
|
47
|
-
taskId: number,
|
|
48
|
-
taskNumber: number,
|
|
49
|
-
data: SubagentReturn
|
|
50
|
-
): ProcessResult {
|
|
51
|
-
const db = getDb();
|
|
52
|
-
const now = new Date().toISOString();
|
|
53
|
-
const result: ProcessResult = {
|
|
54
|
-
success: true,
|
|
55
|
-
knowledgeAdded: 0,
|
|
56
|
-
decisionsAdded: 0,
|
|
57
|
-
artifactsAdded: 0,
|
|
58
|
-
patternsAdded: 0,
|
|
59
|
-
reasoningAdded: 0,
|
|
60
|
-
relationsAdded: 0,
|
|
61
|
-
utilitiesRegistered: 0,
|
|
62
|
-
errors: [],
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// 1. Registrar Knowledge Broadcast (v8.3: com deduplicacao)
|
|
66
|
-
if (data.knowledge_to_broadcast && data.knowledge_to_broadcast.length > 0) {
|
|
67
|
-
for (const k of data.knowledge_to_broadcast) {
|
|
68
|
-
try {
|
|
69
|
-
// v8.3: Deduplicacao - verificar se knowledge identico ja existe
|
|
70
|
-
const existing = db.query(
|
|
71
|
-
`SELECT id FROM knowledge WHERE spec_id = ? AND category = ? AND content = ? LIMIT 1`
|
|
72
|
-
).get(specId, k.category, k.content) as any;
|
|
73
|
-
|
|
74
|
-
if (existing) continue; // Skip duplicata
|
|
75
|
-
|
|
76
|
-
db.run(
|
|
77
|
-
`INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
|
|
78
|
-
VALUES (?, ?, ?, ?, ?, 'all', ?)`,
|
|
79
|
-
[specId, taskId, k.category, k.content, k.severity, now]
|
|
80
|
-
);
|
|
81
|
-
result.knowledgeAdded++;
|
|
82
|
-
} catch (e) {
|
|
83
|
-
result.errors.push(`Erro ao registrar knowledge: ${(e as Error).message}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 2. Registrar Decisions
|
|
89
|
-
if (data.decisions_made && data.decisions_made.length > 0) {
|
|
90
|
-
for (const dec of data.decisions_made) {
|
|
91
|
-
try {
|
|
92
|
-
const decisionId = getNextDecisionId(specId);
|
|
93
|
-
db.run(
|
|
94
|
-
`INSERT INTO decisions (id, spec_id, task_ref, title, decision, rationale, status, created_at)
|
|
95
|
-
VALUES (?, ?, ?, ?, ?, ?, 'active', ?)`,
|
|
96
|
-
[decisionId, specId, taskNumber, dec.title, dec.decision, dec.rationale || null, now]
|
|
97
|
-
);
|
|
98
|
-
result.decisionsAdded++;
|
|
99
|
-
} catch (e) {
|
|
100
|
-
result.errors.push(`Erro ao registrar decision: ${(e as Error).message}`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 2b. v8.3: Deteccao automatica de contradicoes entre decisoes
|
|
106
|
-
if (data.decisions_made && data.decisions_made.length > 0) {
|
|
107
|
-
try {
|
|
108
|
-
const existingDecisions = db.query(
|
|
109
|
-
"SELECT id, title, decision FROM decisions WHERE spec_id = ? AND status = 'active'"
|
|
110
|
-
).all(specId) as any[];
|
|
111
|
-
|
|
112
|
-
const allTaskFiles = [...data.files_created, ...data.files_modified];
|
|
113
|
-
|
|
114
|
-
for (const newDec of data.decisions_made) {
|
|
115
|
-
for (const existingDec of existingDecisions) {
|
|
116
|
-
// Skip se mesma decisao (recem adicionada)
|
|
117
|
-
if (existingDec.title === newDec.title) continue;
|
|
118
|
-
|
|
119
|
-
const newDecLower = `${newDec.title} ${newDec.decision}`.toLowerCase();
|
|
120
|
-
const existDecLower = `${existingDec.title} ${existingDec.decision}`.toLowerCase();
|
|
121
|
-
|
|
122
|
-
// Verificar overlap de arquivos
|
|
123
|
-
const existingDecFiles = getRelatedFiles(existingDec.id, "decision");
|
|
124
|
-
const fileOverlap = allTaskFiles.some(f => existingDecFiles.includes(f));
|
|
125
|
-
|
|
126
|
-
if (fileOverlap) {
|
|
127
|
-
// Heuristica de keywords opostos
|
|
128
|
-
const opposites = [
|
|
129
|
-
['usar', 'nao usar'], ['usar', 'evitar'],
|
|
130
|
-
['use', 'avoid'], ['add', 'remove'],
|
|
131
|
-
['incluir', 'excluir'], ['habilitar', 'desabilitar'],
|
|
132
|
-
['enable', 'disable'], ['create', 'delete'],
|
|
133
|
-
['client', 'server'], ['sync', 'async'],
|
|
134
|
-
];
|
|
135
|
-
|
|
136
|
-
let isContradiction = false;
|
|
137
|
-
for (const [word1, word2] of opposites) {
|
|
138
|
-
if ((newDecLower.includes(word1) && existDecLower.includes(word2)) ||
|
|
139
|
-
(newDecLower.includes(word2) && existDecLower.includes(word1))) {
|
|
140
|
-
isContradiction = true;
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (isContradiction) {
|
|
146
|
-
const newDecId = db.query(
|
|
147
|
-
"SELECT id FROM decisions WHERE spec_id = ? AND title = ? ORDER BY created_at DESC LIMIT 1"
|
|
148
|
-
).get(specId, newDec.title) as any;
|
|
149
|
-
|
|
150
|
-
if (newDecId) {
|
|
151
|
-
addGraphRelation(specId, {
|
|
152
|
-
sourceType: "decision",
|
|
153
|
-
sourceId: newDecId.id,
|
|
154
|
-
targetType: "decision",
|
|
155
|
-
targetId: existingDec.id,
|
|
156
|
-
relation: "contradicts",
|
|
157
|
-
strength: 0.7,
|
|
158
|
-
metadata: { reason: "Arquivos sobrepostos com semantica oposta" },
|
|
159
|
-
});
|
|
160
|
-
result.relationsAdded++;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
} catch (e) {
|
|
167
|
-
// Nao-critico: nao falhar processamento por deteccao de contradicoes
|
|
168
|
-
result.errors.push(`Aviso contradicoes: ${(e as Error).message}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 3. Registrar Artifacts (files_created e files_modified)
|
|
173
|
-
const allFiles = [
|
|
174
|
-
...data.files_created.map((f) => ({ path: f, action: "created" })),
|
|
175
|
-
...data.files_modified.map((f) => ({ path: f, action: "modified" })),
|
|
176
|
-
];
|
|
177
|
-
|
|
178
|
-
for (const file of allFiles) {
|
|
179
|
-
try {
|
|
180
|
-
db.run(
|
|
181
|
-
`INSERT OR REPLACE INTO artifacts (spec_id, task_ref, path, action, created_at)
|
|
182
|
-
VALUES (?, ?, ?, ?, ?)`,
|
|
183
|
-
[specId, taskNumber, file.path, file.action, now]
|
|
184
|
-
);
|
|
185
|
-
result.artifactsAdded++;
|
|
186
|
-
} catch (e) {
|
|
187
|
-
result.errors.push(`Erro ao registrar artifact: ${(e as Error).message}`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// 4. Registrar Patterns descobertos no contexto
|
|
192
|
-
if (data.patterns_discovered && data.patterns_discovered.length > 0) {
|
|
193
|
-
try {
|
|
194
|
-
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(specId) as any;
|
|
195
|
-
const currentPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
|
|
196
|
-
|
|
197
|
-
for (const pattern of data.patterns_discovered) {
|
|
198
|
-
currentPatterns.push({
|
|
199
|
-
pattern,
|
|
200
|
-
detected_at: now,
|
|
201
|
-
source_task: taskNumber,
|
|
202
|
-
});
|
|
203
|
-
result.patternsAdded++;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
db.run(
|
|
207
|
-
"UPDATE context SET patterns = ?, updated_at = ? WHERE spec_id = ?",
|
|
208
|
-
[JSON.stringify(currentPatterns), now, specId]
|
|
209
|
-
);
|
|
210
|
-
} catch (e) {
|
|
211
|
-
result.errors.push(`Erro ao registrar patterns: ${(e as Error).message}`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// 4b. v8.3: Broadcast patterns como knowledge (com deduplicacao)
|
|
216
|
-
if (data.patterns_discovered && data.patterns_discovered.length > 0) {
|
|
217
|
-
for (const pattern of data.patterns_discovered) {
|
|
218
|
-
try {
|
|
219
|
-
const content = `Pattern descoberto: ${pattern}`;
|
|
220
|
-
const existing = db.query(
|
|
221
|
-
`SELECT id FROM knowledge WHERE spec_id = ? AND category = 'pattern' AND content = ? LIMIT 1`
|
|
222
|
-
).get(specId, content) as any;
|
|
223
|
-
|
|
224
|
-
if (!existing) {
|
|
225
|
-
db.run(
|
|
226
|
-
`INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
|
|
227
|
-
VALUES (?, ?, 'pattern', ?, 'info', 'all', ?)`,
|
|
228
|
-
[specId, taskId, content, now]
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
} catch { /* ignore */ }
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// 5. Se status for blocked ou needs_decision, registrar como knowledge critico
|
|
236
|
-
if (data.status === "blocked" && data.blockers) {
|
|
237
|
-
for (const blocker of data.blockers) {
|
|
238
|
-
try {
|
|
239
|
-
db.run(
|
|
240
|
-
`INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
|
|
241
|
-
VALUES (?, ?, 'blocker', ?, 'critical', 'all', ?)`,
|
|
242
|
-
[specId, taskId, blocker, now]
|
|
243
|
-
);
|
|
244
|
-
result.knowledgeAdded++;
|
|
245
|
-
} catch (e) {
|
|
246
|
-
result.errors.push(`Erro ao registrar blocker: ${(e as Error).message}`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// 6. v8.0: Registrar Reasoning no reasoning_log
|
|
252
|
-
if (data.reasoning) {
|
|
253
|
-
try {
|
|
254
|
-
// Abordagem principal
|
|
255
|
-
if (data.reasoning.approach) {
|
|
256
|
-
addReasoning(specId, taskId, {
|
|
257
|
-
category: "decision",
|
|
258
|
-
thought: `Abordagem: ${data.reasoning.approach}`,
|
|
259
|
-
relatedFiles: [...data.files_created, ...data.files_modified],
|
|
260
|
-
importance: "normal",
|
|
261
|
-
});
|
|
262
|
-
result.reasoningAdded++;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Desafios encontrados
|
|
266
|
-
if (data.reasoning.challenges && data.reasoning.challenges.length > 0) {
|
|
267
|
-
for (const challenge of data.reasoning.challenges) {
|
|
268
|
-
addReasoning(specId, taskId, {
|
|
269
|
-
category: "challenge",
|
|
270
|
-
thought: challenge,
|
|
271
|
-
relatedFiles: [...data.files_created, ...data.files_modified],
|
|
272
|
-
importance: "high",
|
|
273
|
-
});
|
|
274
|
-
result.reasoningAdded++;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Alternativas consideradas
|
|
279
|
-
if (data.reasoning.alternatives && data.reasoning.alternatives.length > 0) {
|
|
280
|
-
for (const alt of data.reasoning.alternatives) {
|
|
281
|
-
addReasoning(specId, taskId, {
|
|
282
|
-
category: "decision",
|
|
283
|
-
thought: `Alternativa considerada: ${alt}`,
|
|
284
|
-
relatedFiles: [...data.files_created, ...data.files_modified],
|
|
285
|
-
importance: "normal",
|
|
286
|
-
});
|
|
287
|
-
result.reasoningAdded++;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Recomendações para próximas tasks
|
|
292
|
-
if (data.reasoning.recommendations) {
|
|
293
|
-
addReasoning(specId, taskId, {
|
|
294
|
-
category: "recommendation",
|
|
295
|
-
thought: data.reasoning.recommendations,
|
|
296
|
-
relatedFiles: [...data.files_created, ...data.files_modified],
|
|
297
|
-
importance: "high",
|
|
298
|
-
});
|
|
299
|
-
result.reasoningAdded++;
|
|
300
|
-
}
|
|
301
|
-
} catch (e) {
|
|
302
|
-
result.errors.push(`Erro ao registrar reasoning: ${(e as Error).message}`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// 7. v8.0: Criar relações no knowledge graph
|
|
307
|
-
try {
|
|
308
|
-
// Relação: task -> arquivos criados
|
|
309
|
-
for (const file of data.files_created) {
|
|
310
|
-
addGraphRelation(specId, {
|
|
311
|
-
sourceType: "task",
|
|
312
|
-
sourceId: String(taskId),
|
|
313
|
-
targetType: "file",
|
|
314
|
-
targetId: file,
|
|
315
|
-
relation: "creates",
|
|
316
|
-
strength: 1.0,
|
|
317
|
-
});
|
|
318
|
-
result.relationsAdded++;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Relação: task -> arquivos modificados
|
|
322
|
-
for (const file of data.files_modified) {
|
|
323
|
-
addGraphRelation(specId, {
|
|
324
|
-
sourceType: "task",
|
|
325
|
-
sourceId: String(taskId),
|
|
326
|
-
targetType: "file",
|
|
327
|
-
targetId: file,
|
|
328
|
-
relation: "modifies",
|
|
329
|
-
strength: 1.0,
|
|
330
|
-
});
|
|
331
|
-
result.relationsAdded++;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Relação: decision -> arquivos (se houver decisions)
|
|
335
|
-
if (data.decisions_made) {
|
|
336
|
-
for (const dec of data.decisions_made) {
|
|
337
|
-
const decisionId = getNextDecisionId(specId);
|
|
338
|
-
for (const file of [...data.files_created, ...data.files_modified]) {
|
|
339
|
-
addGraphRelation(specId, {
|
|
340
|
-
sourceType: "decision",
|
|
341
|
-
sourceId: decisionId,
|
|
342
|
-
targetType: "file",
|
|
343
|
-
targetId: file,
|
|
344
|
-
relation: "implements",
|
|
345
|
-
strength: 0.8,
|
|
346
|
-
});
|
|
347
|
-
result.relationsAdded++;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Relação: pattern -> arquivos (se patterns descobertos)
|
|
353
|
-
if (data.patterns_discovered) {
|
|
354
|
-
for (const pattern of data.patterns_discovered) {
|
|
355
|
-
for (const file of data.files_created) {
|
|
356
|
-
addGraphRelation(specId, {
|
|
357
|
-
sourceType: "pattern",
|
|
358
|
-
sourceId: pattern,
|
|
359
|
-
targetType: "file",
|
|
360
|
-
targetId: file,
|
|
361
|
-
relation: "extracted_from",
|
|
362
|
-
strength: 0.9,
|
|
363
|
-
});
|
|
364
|
-
result.relationsAdded++;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
} catch (e) {
|
|
369
|
-
result.errors.push(`Erro ao criar relações: ${(e as Error).message}`);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// 8. v8.5: Registrar utilities de arquivos criados/modificados (DRY map)
|
|
373
|
-
try {
|
|
374
|
-
// 8a. Usar utilities declaradas pelo subagent (metadata mais rica)
|
|
375
|
-
if (data.utilities_created && data.utilities_created.length > 0) {
|
|
376
|
-
for (const util of data.utilities_created) {
|
|
377
|
-
upsertUtility({
|
|
378
|
-
filePath: util.file,
|
|
379
|
-
utilityName: util.name,
|
|
380
|
-
utilityType: util.type || "function",
|
|
381
|
-
scope: inferScopeFromPath(util.file),
|
|
382
|
-
signature: util.signature,
|
|
383
|
-
description: util.description,
|
|
384
|
-
}, specId, taskNumber);
|
|
385
|
-
result.utilitiesRegistered++;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// 8b. Fallback: escanear exports de arquivos criados/modificados
|
|
390
|
-
for (const file of allFiles.map(f => f.path)) {
|
|
391
|
-
const utilities = extractUtilitiesFromFile(file);
|
|
392
|
-
const scope = inferScopeFromPath(file);
|
|
393
|
-
for (const util of utilities) {
|
|
394
|
-
upsertUtility({
|
|
395
|
-
filePath: file,
|
|
396
|
-
utilityName: util.name,
|
|
397
|
-
utilityType: util.type,
|
|
398
|
-
scope,
|
|
399
|
-
signature: util.signature,
|
|
400
|
-
}, specId, taskNumber);
|
|
401
|
-
result.utilitiesRegistered++;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
} catch (e) {
|
|
405
|
-
// Nao-critico: nao falhar processamento por extracao de utilities
|
|
406
|
-
result.errors.push(`Aviso utilities: ${(e as Error).message}`);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
result.success = result.errors.length === 0;
|
|
410
|
-
return result;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Formata o resultado do processamento para exibicao
|
|
415
|
-
*/
|
|
416
|
-
export function formatProcessResult(result: ProcessResult): string {
|
|
417
|
-
let output = "\n[i] Retorno do subagent processado automaticamente:\n";
|
|
418
|
-
|
|
419
|
-
if (result.knowledgeAdded > 0) {
|
|
420
|
-
output += ` + ${result.knowledgeAdded} knowledge(s) registrado(s) para broadcast\n`;
|
|
421
|
-
}
|
|
422
|
-
if (result.decisionsAdded > 0) {
|
|
423
|
-
output += ` + ${result.decisionsAdded} decisao(es) registrada(s)\n`;
|
|
424
|
-
}
|
|
425
|
-
if (result.artifactsAdded > 0) {
|
|
426
|
-
output += ` + ${result.artifactsAdded} artefato(s) registrado(s)\n`;
|
|
427
|
-
}
|
|
428
|
-
if (result.patternsAdded > 0) {
|
|
429
|
-
output += ` + ${result.patternsAdded} pattern(s) adicionado(s) ao contexto\n`;
|
|
430
|
-
}
|
|
431
|
-
// v8.0: Novas métricas
|
|
432
|
-
if (result.reasoningAdded > 0) {
|
|
433
|
-
output += ` + ${result.reasoningAdded} raciocinio(s) registrado(s) no historico\n`;
|
|
434
|
-
}
|
|
435
|
-
if (result.relationsAdded > 0) {
|
|
436
|
-
output += ` + ${result.relationsAdded} relacao(oes) criada(s) no grafo de conhecimento\n`;
|
|
437
|
-
}
|
|
438
|
-
// v8.5: Utilities registradas
|
|
439
|
-
if (result.utilitiesRegistered > 0) {
|
|
440
|
-
output += ` + ${result.utilitiesRegistered} utilidade(s) registrada(s) no mapa DRY\n`;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
if (result.errors.length > 0) {
|
|
444
|
-
output += "\n[!] Erros durante processamento:\n";
|
|
445
|
-
for (const error of result.errors) {
|
|
446
|
-
output += ` - ${error}\n`;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return output;
|
|
1
|
+
/**
|
|
2
|
+
* Process Subagent Return
|
|
3
|
+
*
|
|
4
|
+
* Este modulo processa automaticamente o retorno de um subagent,
|
|
5
|
+
* extraindo e registrando:
|
|
6
|
+
* - Knowledge broadcast
|
|
7
|
+
* - Decisions
|
|
8
|
+
* - Artifacts (files created/modified)
|
|
9
|
+
* - Patterns descobertos
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { getDb } from "../db/connection";
|
|
13
|
+
import { SubagentReturn, Knowledge } from "./subagent-protocol";
|
|
14
|
+
import { addReasoning, addGraphRelation, getRelatedFiles, upsertUtility } from "../db/schema";
|
|
15
|
+
import { extractUtilitiesFromFile, inferScopeFromPath } from "../commands/patterns";
|
|
16
|
+
|
|
17
|
+
interface ProcessResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
knowledgeAdded: number;
|
|
20
|
+
decisionsAdded: number;
|
|
21
|
+
artifactsAdded: number;
|
|
22
|
+
patternsAdded: number;
|
|
23
|
+
reasoningAdded: number; // v8.0
|
|
24
|
+
relationsAdded: number; // v8.0
|
|
25
|
+
utilitiesRegistered: number; // v8.5
|
|
26
|
+
errors: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getNextDecisionId(specId: string): string {
|
|
30
|
+
const db = getDb();
|
|
31
|
+
const last = db
|
|
32
|
+
.query("SELECT id FROM decisions WHERE spec_id = ? ORDER BY created_at DESC LIMIT 1")
|
|
33
|
+
.get(specId) as any;
|
|
34
|
+
|
|
35
|
+
if (!last) return "DEC-001";
|
|
36
|
+
|
|
37
|
+
const num = parseInt(last.id.replace("DEC-", "")) + 1;
|
|
38
|
+
return `DEC-${num.toString().padStart(3, "0")}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Processa o retorno de um subagent e registra automaticamente
|
|
43
|
+
* todos os dados extraidos no banco
|
|
44
|
+
*/
|
|
45
|
+
export function processSubagentReturn(
|
|
46
|
+
specId: string,
|
|
47
|
+
taskId: number,
|
|
48
|
+
taskNumber: number,
|
|
49
|
+
data: SubagentReturn
|
|
50
|
+
): ProcessResult {
|
|
51
|
+
const db = getDb();
|
|
52
|
+
const now = new Date().toISOString();
|
|
53
|
+
const result: ProcessResult = {
|
|
54
|
+
success: true,
|
|
55
|
+
knowledgeAdded: 0,
|
|
56
|
+
decisionsAdded: 0,
|
|
57
|
+
artifactsAdded: 0,
|
|
58
|
+
patternsAdded: 0,
|
|
59
|
+
reasoningAdded: 0,
|
|
60
|
+
relationsAdded: 0,
|
|
61
|
+
utilitiesRegistered: 0,
|
|
62
|
+
errors: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 1. Registrar Knowledge Broadcast (v8.3: com deduplicacao)
|
|
66
|
+
if (data.knowledge_to_broadcast && data.knowledge_to_broadcast.length > 0) {
|
|
67
|
+
for (const k of data.knowledge_to_broadcast) {
|
|
68
|
+
try {
|
|
69
|
+
// v8.3: Deduplicacao - verificar se knowledge identico ja existe
|
|
70
|
+
const existing = db.query(
|
|
71
|
+
`SELECT id FROM knowledge WHERE spec_id = ? AND category = ? AND content = ? LIMIT 1`
|
|
72
|
+
).get(specId, k.category, k.content) as any;
|
|
73
|
+
|
|
74
|
+
if (existing) continue; // Skip duplicata
|
|
75
|
+
|
|
76
|
+
db.run(
|
|
77
|
+
`INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
|
|
78
|
+
VALUES (?, ?, ?, ?, ?, 'all', ?)`,
|
|
79
|
+
[specId, taskId, k.category, k.content, k.severity, now]
|
|
80
|
+
);
|
|
81
|
+
result.knowledgeAdded++;
|
|
82
|
+
} catch (e) {
|
|
83
|
+
result.errors.push(`Erro ao registrar knowledge: ${(e as Error).message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 2. Registrar Decisions
|
|
89
|
+
if (data.decisions_made && data.decisions_made.length > 0) {
|
|
90
|
+
for (const dec of data.decisions_made) {
|
|
91
|
+
try {
|
|
92
|
+
const decisionId = getNextDecisionId(specId);
|
|
93
|
+
db.run(
|
|
94
|
+
`INSERT INTO decisions (id, spec_id, task_ref, title, decision, rationale, status, created_at)
|
|
95
|
+
VALUES (?, ?, ?, ?, ?, ?, 'active', ?)`,
|
|
96
|
+
[decisionId, specId, taskNumber, dec.title, dec.decision, dec.rationale || null, now]
|
|
97
|
+
);
|
|
98
|
+
result.decisionsAdded++;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
result.errors.push(`Erro ao registrar decision: ${(e as Error).message}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 2b. v8.3: Deteccao automatica de contradicoes entre decisoes
|
|
106
|
+
if (data.decisions_made && data.decisions_made.length > 0) {
|
|
107
|
+
try {
|
|
108
|
+
const existingDecisions = db.query(
|
|
109
|
+
"SELECT id, title, decision FROM decisions WHERE spec_id = ? AND status = 'active'"
|
|
110
|
+
).all(specId) as any[];
|
|
111
|
+
|
|
112
|
+
const allTaskFiles = [...data.files_created, ...data.files_modified];
|
|
113
|
+
|
|
114
|
+
for (const newDec of data.decisions_made) {
|
|
115
|
+
for (const existingDec of existingDecisions) {
|
|
116
|
+
// Skip se mesma decisao (recem adicionada)
|
|
117
|
+
if (existingDec.title === newDec.title) continue;
|
|
118
|
+
|
|
119
|
+
const newDecLower = `${newDec.title} ${newDec.decision}`.toLowerCase();
|
|
120
|
+
const existDecLower = `${existingDec.title} ${existingDec.decision}`.toLowerCase();
|
|
121
|
+
|
|
122
|
+
// Verificar overlap de arquivos
|
|
123
|
+
const existingDecFiles = getRelatedFiles(existingDec.id, "decision");
|
|
124
|
+
const fileOverlap = allTaskFiles.some(f => existingDecFiles.includes(f));
|
|
125
|
+
|
|
126
|
+
if (fileOverlap) {
|
|
127
|
+
// Heuristica de keywords opostos
|
|
128
|
+
const opposites = [
|
|
129
|
+
['usar', 'nao usar'], ['usar', 'evitar'],
|
|
130
|
+
['use', 'avoid'], ['add', 'remove'],
|
|
131
|
+
['incluir', 'excluir'], ['habilitar', 'desabilitar'],
|
|
132
|
+
['enable', 'disable'], ['create', 'delete'],
|
|
133
|
+
['client', 'server'], ['sync', 'async'],
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
let isContradiction = false;
|
|
137
|
+
for (const [word1, word2] of opposites) {
|
|
138
|
+
if ((newDecLower.includes(word1) && existDecLower.includes(word2)) ||
|
|
139
|
+
(newDecLower.includes(word2) && existDecLower.includes(word1))) {
|
|
140
|
+
isContradiction = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (isContradiction) {
|
|
146
|
+
const newDecId = db.query(
|
|
147
|
+
"SELECT id FROM decisions WHERE spec_id = ? AND title = ? ORDER BY created_at DESC LIMIT 1"
|
|
148
|
+
).get(specId, newDec.title) as any;
|
|
149
|
+
|
|
150
|
+
if (newDecId) {
|
|
151
|
+
addGraphRelation(specId, {
|
|
152
|
+
sourceType: "decision",
|
|
153
|
+
sourceId: newDecId.id,
|
|
154
|
+
targetType: "decision",
|
|
155
|
+
targetId: existingDec.id,
|
|
156
|
+
relation: "contradicts",
|
|
157
|
+
strength: 0.7,
|
|
158
|
+
metadata: { reason: "Arquivos sobrepostos com semantica oposta" },
|
|
159
|
+
});
|
|
160
|
+
result.relationsAdded++;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (e) {
|
|
167
|
+
// Nao-critico: nao falhar processamento por deteccao de contradicoes
|
|
168
|
+
result.errors.push(`Aviso contradicoes: ${(e as Error).message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 3. Registrar Artifacts (files_created e files_modified)
|
|
173
|
+
const allFiles = [
|
|
174
|
+
...data.files_created.map((f) => ({ path: f, action: "created" })),
|
|
175
|
+
...data.files_modified.map((f) => ({ path: f, action: "modified" })),
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
for (const file of allFiles) {
|
|
179
|
+
try {
|
|
180
|
+
db.run(
|
|
181
|
+
`INSERT OR REPLACE INTO artifacts (spec_id, task_ref, path, action, created_at)
|
|
182
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
183
|
+
[specId, taskNumber, file.path, file.action, now]
|
|
184
|
+
);
|
|
185
|
+
result.artifactsAdded++;
|
|
186
|
+
} catch (e) {
|
|
187
|
+
result.errors.push(`Erro ao registrar artifact: ${(e as Error).message}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 4. Registrar Patterns descobertos no contexto
|
|
192
|
+
if (data.patterns_discovered && data.patterns_discovered.length > 0) {
|
|
193
|
+
try {
|
|
194
|
+
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(specId) as any;
|
|
195
|
+
const currentPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
|
|
196
|
+
|
|
197
|
+
for (const pattern of data.patterns_discovered) {
|
|
198
|
+
currentPatterns.push({
|
|
199
|
+
pattern,
|
|
200
|
+
detected_at: now,
|
|
201
|
+
source_task: taskNumber,
|
|
202
|
+
});
|
|
203
|
+
result.patternsAdded++;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
db.run(
|
|
207
|
+
"UPDATE context SET patterns = ?, updated_at = ? WHERE spec_id = ?",
|
|
208
|
+
[JSON.stringify(currentPatterns), now, specId]
|
|
209
|
+
);
|
|
210
|
+
} catch (e) {
|
|
211
|
+
result.errors.push(`Erro ao registrar patterns: ${(e as Error).message}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 4b. v8.3: Broadcast patterns como knowledge (com deduplicacao)
|
|
216
|
+
if (data.patterns_discovered && data.patterns_discovered.length > 0) {
|
|
217
|
+
for (const pattern of data.patterns_discovered) {
|
|
218
|
+
try {
|
|
219
|
+
const content = `Pattern descoberto: ${pattern}`;
|
|
220
|
+
const existing = db.query(
|
|
221
|
+
`SELECT id FROM knowledge WHERE spec_id = ? AND category = 'pattern' AND content = ? LIMIT 1`
|
|
222
|
+
).get(specId, content) as any;
|
|
223
|
+
|
|
224
|
+
if (!existing) {
|
|
225
|
+
db.run(
|
|
226
|
+
`INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
|
|
227
|
+
VALUES (?, ?, 'pattern', ?, 'info', 'all', ?)`,
|
|
228
|
+
[specId, taskId, content, now]
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
} catch { /* ignore */ }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 5. Se status for blocked ou needs_decision, registrar como knowledge critico
|
|
236
|
+
if (data.status === "blocked" && data.blockers) {
|
|
237
|
+
for (const blocker of data.blockers) {
|
|
238
|
+
try {
|
|
239
|
+
db.run(
|
|
240
|
+
`INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
|
|
241
|
+
VALUES (?, ?, 'blocker', ?, 'critical', 'all', ?)`,
|
|
242
|
+
[specId, taskId, blocker, now]
|
|
243
|
+
);
|
|
244
|
+
result.knowledgeAdded++;
|
|
245
|
+
} catch (e) {
|
|
246
|
+
result.errors.push(`Erro ao registrar blocker: ${(e as Error).message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 6. v8.0: Registrar Reasoning no reasoning_log
|
|
252
|
+
if (data.reasoning) {
|
|
253
|
+
try {
|
|
254
|
+
// Abordagem principal
|
|
255
|
+
if (data.reasoning.approach) {
|
|
256
|
+
addReasoning(specId, taskId, {
|
|
257
|
+
category: "decision",
|
|
258
|
+
thought: `Abordagem: ${data.reasoning.approach}`,
|
|
259
|
+
relatedFiles: [...data.files_created, ...data.files_modified],
|
|
260
|
+
importance: "normal",
|
|
261
|
+
});
|
|
262
|
+
result.reasoningAdded++;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Desafios encontrados
|
|
266
|
+
if (data.reasoning.challenges && data.reasoning.challenges.length > 0) {
|
|
267
|
+
for (const challenge of data.reasoning.challenges) {
|
|
268
|
+
addReasoning(specId, taskId, {
|
|
269
|
+
category: "challenge",
|
|
270
|
+
thought: challenge,
|
|
271
|
+
relatedFiles: [...data.files_created, ...data.files_modified],
|
|
272
|
+
importance: "high",
|
|
273
|
+
});
|
|
274
|
+
result.reasoningAdded++;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Alternativas consideradas
|
|
279
|
+
if (data.reasoning.alternatives && data.reasoning.alternatives.length > 0) {
|
|
280
|
+
for (const alt of data.reasoning.alternatives) {
|
|
281
|
+
addReasoning(specId, taskId, {
|
|
282
|
+
category: "decision",
|
|
283
|
+
thought: `Alternativa considerada: ${alt}`,
|
|
284
|
+
relatedFiles: [...data.files_created, ...data.files_modified],
|
|
285
|
+
importance: "normal",
|
|
286
|
+
});
|
|
287
|
+
result.reasoningAdded++;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Recomendações para próximas tasks
|
|
292
|
+
if (data.reasoning.recommendations) {
|
|
293
|
+
addReasoning(specId, taskId, {
|
|
294
|
+
category: "recommendation",
|
|
295
|
+
thought: data.reasoning.recommendations,
|
|
296
|
+
relatedFiles: [...data.files_created, ...data.files_modified],
|
|
297
|
+
importance: "high",
|
|
298
|
+
});
|
|
299
|
+
result.reasoningAdded++;
|
|
300
|
+
}
|
|
301
|
+
} catch (e) {
|
|
302
|
+
result.errors.push(`Erro ao registrar reasoning: ${(e as Error).message}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 7. v8.0: Criar relações no knowledge graph
|
|
307
|
+
try {
|
|
308
|
+
// Relação: task -> arquivos criados
|
|
309
|
+
for (const file of data.files_created) {
|
|
310
|
+
addGraphRelation(specId, {
|
|
311
|
+
sourceType: "task",
|
|
312
|
+
sourceId: String(taskId),
|
|
313
|
+
targetType: "file",
|
|
314
|
+
targetId: file,
|
|
315
|
+
relation: "creates",
|
|
316
|
+
strength: 1.0,
|
|
317
|
+
});
|
|
318
|
+
result.relationsAdded++;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Relação: task -> arquivos modificados
|
|
322
|
+
for (const file of data.files_modified) {
|
|
323
|
+
addGraphRelation(specId, {
|
|
324
|
+
sourceType: "task",
|
|
325
|
+
sourceId: String(taskId),
|
|
326
|
+
targetType: "file",
|
|
327
|
+
targetId: file,
|
|
328
|
+
relation: "modifies",
|
|
329
|
+
strength: 1.0,
|
|
330
|
+
});
|
|
331
|
+
result.relationsAdded++;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Relação: decision -> arquivos (se houver decisions)
|
|
335
|
+
if (data.decisions_made) {
|
|
336
|
+
for (const dec of data.decisions_made) {
|
|
337
|
+
const decisionId = getNextDecisionId(specId);
|
|
338
|
+
for (const file of [...data.files_created, ...data.files_modified]) {
|
|
339
|
+
addGraphRelation(specId, {
|
|
340
|
+
sourceType: "decision",
|
|
341
|
+
sourceId: decisionId,
|
|
342
|
+
targetType: "file",
|
|
343
|
+
targetId: file,
|
|
344
|
+
relation: "implements",
|
|
345
|
+
strength: 0.8,
|
|
346
|
+
});
|
|
347
|
+
result.relationsAdded++;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Relação: pattern -> arquivos (se patterns descobertos)
|
|
353
|
+
if (data.patterns_discovered) {
|
|
354
|
+
for (const pattern of data.patterns_discovered) {
|
|
355
|
+
for (const file of data.files_created) {
|
|
356
|
+
addGraphRelation(specId, {
|
|
357
|
+
sourceType: "pattern",
|
|
358
|
+
sourceId: pattern,
|
|
359
|
+
targetType: "file",
|
|
360
|
+
targetId: file,
|
|
361
|
+
relation: "extracted_from",
|
|
362
|
+
strength: 0.9,
|
|
363
|
+
});
|
|
364
|
+
result.relationsAdded++;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} catch (e) {
|
|
369
|
+
result.errors.push(`Erro ao criar relações: ${(e as Error).message}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 8. v8.5: Registrar utilities de arquivos criados/modificados (DRY map)
|
|
373
|
+
try {
|
|
374
|
+
// 8a. Usar utilities declaradas pelo subagent (metadata mais rica)
|
|
375
|
+
if (data.utilities_created && data.utilities_created.length > 0) {
|
|
376
|
+
for (const util of data.utilities_created) {
|
|
377
|
+
upsertUtility({
|
|
378
|
+
filePath: util.file,
|
|
379
|
+
utilityName: util.name,
|
|
380
|
+
utilityType: util.type || "function",
|
|
381
|
+
scope: inferScopeFromPath(util.file),
|
|
382
|
+
signature: util.signature,
|
|
383
|
+
description: util.description,
|
|
384
|
+
}, specId, taskNumber);
|
|
385
|
+
result.utilitiesRegistered++;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 8b. Fallback: escanear exports de arquivos criados/modificados
|
|
390
|
+
for (const file of allFiles.map(f => f.path)) {
|
|
391
|
+
const utilities = extractUtilitiesFromFile(file);
|
|
392
|
+
const scope = inferScopeFromPath(file);
|
|
393
|
+
for (const util of utilities) {
|
|
394
|
+
upsertUtility({
|
|
395
|
+
filePath: file,
|
|
396
|
+
utilityName: util.name,
|
|
397
|
+
utilityType: util.type,
|
|
398
|
+
scope,
|
|
399
|
+
signature: util.signature,
|
|
400
|
+
}, specId, taskNumber);
|
|
401
|
+
result.utilitiesRegistered++;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch (e) {
|
|
405
|
+
// Nao-critico: nao falhar processamento por extracao de utilities
|
|
406
|
+
result.errors.push(`Aviso utilities: ${(e as Error).message}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
result.success = result.errors.length === 0;
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Formata o resultado do processamento para exibicao
|
|
415
|
+
*/
|
|
416
|
+
export function formatProcessResult(result: ProcessResult): string {
|
|
417
|
+
let output = "\n[i] Retorno do subagent processado automaticamente:\n";
|
|
418
|
+
|
|
419
|
+
if (result.knowledgeAdded > 0) {
|
|
420
|
+
output += ` + ${result.knowledgeAdded} knowledge(s) registrado(s) para broadcast\n`;
|
|
421
|
+
}
|
|
422
|
+
if (result.decisionsAdded > 0) {
|
|
423
|
+
output += ` + ${result.decisionsAdded} decisao(es) registrada(s)\n`;
|
|
424
|
+
}
|
|
425
|
+
if (result.artifactsAdded > 0) {
|
|
426
|
+
output += ` + ${result.artifactsAdded} artefato(s) registrado(s)\n`;
|
|
427
|
+
}
|
|
428
|
+
if (result.patternsAdded > 0) {
|
|
429
|
+
output += ` + ${result.patternsAdded} pattern(s) adicionado(s) ao contexto\n`;
|
|
430
|
+
}
|
|
431
|
+
// v8.0: Novas métricas
|
|
432
|
+
if (result.reasoningAdded > 0) {
|
|
433
|
+
output += ` + ${result.reasoningAdded} raciocinio(s) registrado(s) no historico\n`;
|
|
434
|
+
}
|
|
435
|
+
if (result.relationsAdded > 0) {
|
|
436
|
+
output += ` + ${result.relationsAdded} relacao(oes) criada(s) no grafo de conhecimento\n`;
|
|
437
|
+
}
|
|
438
|
+
// v8.5: Utilities registradas
|
|
439
|
+
if (result.utilitiesRegistered > 0) {
|
|
440
|
+
output += ` + ${result.utilitiesRegistered} utilidade(s) registrada(s) no mapa DRY\n`;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (result.errors.length > 0) {
|
|
444
|
+
output += "\n[!] Erros durante processamento:\n";
|
|
445
|
+
for (const error of result.errors) {
|
|
446
|
+
output += ` - ${error}\n`;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return output;
|
|
451
451
|
}
|