@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
package/commands/utils.ts
CHANGED
|
@@ -1,1021 +1,1021 @@
|
|
|
1
|
-
import { getDb } from "../db/connection";
|
|
2
|
-
import { initSchema, getPatternsForFiles, getLastSessionSummary, getSessionSummaries, getRelatedDecisions, findContradictions, getArchitecturalAnalysisForSpec, getUtilitiesForContext } from "../db/schema";
|
|
3
|
-
import { getKnowledgeForTask } from "./knowledge";
|
|
4
|
-
import { getLibContextsForAgent } from "./research";
|
|
5
|
-
|
|
6
|
-
// v8.3: Limite maximo de contexto para subagents (16KB)
|
|
7
|
-
const MAX_CONTEXT_SIZE = 16384;
|
|
8
|
-
|
|
9
|
-
export function contextUpdate(options: {
|
|
10
|
-
approach?: string;
|
|
11
|
-
pattern?: string;
|
|
12
|
-
constraint?: string;
|
|
13
|
-
}): void {
|
|
14
|
-
initSchema();
|
|
15
|
-
const db = getDb();
|
|
16
|
-
|
|
17
|
-
const spec = db
|
|
18
|
-
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
19
|
-
.get() as any;
|
|
20
|
-
|
|
21
|
-
if (!spec) {
|
|
22
|
-
console.error("\nNenhuma feature ativa.\n");
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Verificar se está na fase de implementação
|
|
27
|
-
if (spec.phase !== "implementing") {
|
|
28
|
-
console.error(`\nContexto só pode ser atualizado na fase 'implementing'.`);
|
|
29
|
-
console.error(`Fase atual: ${spec.phase}\n`);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
|
|
34
|
-
const now = new Date().toISOString();
|
|
35
|
-
|
|
36
|
-
let updated = false;
|
|
37
|
-
|
|
38
|
-
if (options.approach) {
|
|
39
|
-
// Append ao approach existente, não substitui
|
|
40
|
-
const currentApproach = context?.approach || "";
|
|
41
|
-
const newApproach = currentApproach
|
|
42
|
-
? `${currentApproach}\n- ${options.approach}`
|
|
43
|
-
: options.approach;
|
|
44
|
-
|
|
45
|
-
db.run("UPDATE context SET approach = ?, updated_at = ? WHERE spec_id = ?", [
|
|
46
|
-
newApproach,
|
|
47
|
-
now,
|
|
48
|
-
spec.id,
|
|
49
|
-
]);
|
|
50
|
-
console.log(`\n✓ Approach atualizado`);
|
|
51
|
-
updated = true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (options.pattern) {
|
|
55
|
-
// Adiciona ao array de patterns
|
|
56
|
-
const currentPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
|
|
57
|
-
currentPatterns.push({
|
|
58
|
-
pattern: options.pattern,
|
|
59
|
-
detected_at: now,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
db.run("UPDATE context SET patterns = ?, updated_at = ? WHERE spec_id = ?", [
|
|
63
|
-
JSON.stringify(currentPatterns),
|
|
64
|
-
now,
|
|
65
|
-
spec.id,
|
|
66
|
-
]);
|
|
67
|
-
console.log(`✓ Pattern adicionado (total: ${currentPatterns.length})`);
|
|
68
|
-
updated = true;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (options.constraint) {
|
|
72
|
-
// Adiciona ao array de constraints
|
|
73
|
-
const currentConstraints = context?.constraints ? JSON.parse(context.constraints) : [];
|
|
74
|
-
currentConstraints.push({
|
|
75
|
-
constraint: options.constraint,
|
|
76
|
-
added_at: now,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
db.run("UPDATE context SET constraints = ?, updated_at = ? WHERE spec_id = ?", [
|
|
80
|
-
JSON.stringify(currentConstraints),
|
|
81
|
-
now,
|
|
82
|
-
spec.id,
|
|
83
|
-
]);
|
|
84
|
-
console.log(`✓ Constraint adicionado (total: ${currentConstraints.length})`);
|
|
85
|
-
updated = true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!updated) {
|
|
89
|
-
console.log("\nNenhuma opcao fornecida.");
|
|
90
|
-
console.log("Use:");
|
|
91
|
-
console.log(" --approach 'Nova abordagem descoberta'");
|
|
92
|
-
console.log(" --pattern 'Padrao identificado no codigo'");
|
|
93
|
-
console.log(" --constraint 'Nova limitacao encontrada'\n");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
console.log(`\nContexto atualizado para: ${spec.name}\n`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function status(json: boolean = false): void {
|
|
101
|
-
initSchema();
|
|
102
|
-
|
|
103
|
-
const db = getDb();
|
|
104
|
-
|
|
105
|
-
const spec = db
|
|
106
|
-
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
107
|
-
.get() as any;
|
|
108
|
-
|
|
109
|
-
if (!spec) {
|
|
110
|
-
if (json) {
|
|
111
|
-
console.log(JSON.stringify({ active: false, message: "Nenhuma feature ativa" }));
|
|
112
|
-
} else {
|
|
113
|
-
console.log("\nNenhuma feature ativa.");
|
|
114
|
-
console.log("Inicie com: /codexa:feature\n");
|
|
115
|
-
}
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
|
|
120
|
-
const tasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
|
|
121
|
-
const decisions = db.query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'").all(spec.id) as any[];
|
|
122
|
-
|
|
123
|
-
const tasksDone = tasks.filter((t) => t.status === "done").length;
|
|
124
|
-
const tasksRunning = tasks.filter((t) => t.status === "running").length;
|
|
125
|
-
const tasksPending = tasks.filter((t) => t.status === "pending").length;
|
|
126
|
-
|
|
127
|
-
const progress = tasks.length > 0 ? Math.round((tasksDone / tasks.length) * 100) : 0;
|
|
128
|
-
|
|
129
|
-
if (json) {
|
|
130
|
-
console.log(
|
|
131
|
-
JSON.stringify({
|
|
132
|
-
active: true,
|
|
133
|
-
spec,
|
|
134
|
-
context,
|
|
135
|
-
progress: {
|
|
136
|
-
done: tasksDone,
|
|
137
|
-
running: tasksRunning,
|
|
138
|
-
pending: tasksPending,
|
|
139
|
-
total: tasks.length,
|
|
140
|
-
percent: progress,
|
|
141
|
-
},
|
|
142
|
-
decisions: decisions.length,
|
|
143
|
-
})
|
|
144
|
-
);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
console.log(`\n${"=".repeat(60)}`);
|
|
149
|
-
console.log(`STATUS: ${spec.name}`);
|
|
150
|
-
console.log(`${"=".repeat(60)}`);
|
|
151
|
-
console.log(`\nSpec ID: ${spec.id}`);
|
|
152
|
-
console.log(`Fase: ${spec.phase}`);
|
|
153
|
-
console.log(`Aprovado: ${spec.approved_at ? "Sim" : "Nao"}`);
|
|
154
|
-
|
|
155
|
-
console.log(`\nProgresso:`);
|
|
156
|
-
const bar = "█".repeat(Math.floor(progress / 5)) + "░".repeat(20 - Math.floor(progress / 5));
|
|
157
|
-
console.log(` [${bar}] ${progress}%`);
|
|
158
|
-
console.log(` Concluidas: ${tasksDone}/${tasks.length}`);
|
|
159
|
-
console.log(` Em execucao: ${tasksRunning}`);
|
|
160
|
-
console.log(` Pendentes: ${tasksPending}`);
|
|
161
|
-
|
|
162
|
-
if (context?.last_checkpoint) {
|
|
163
|
-
console.log(`\nUltimo checkpoint:`);
|
|
164
|
-
console.log(` ${context.last_checkpoint}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (decisions.length > 0) {
|
|
168
|
-
console.log(`\nDecisoes ativas: ${decisions.length}`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// v8.1: Mostrar ultimo session summary para continuidade
|
|
172
|
-
const lastSession = getLastSessionSummary(spec.id);
|
|
173
|
-
if (lastSession) {
|
|
174
|
-
console.log(`\nUltima sessao:`);
|
|
175
|
-
console.log(` ${lastSession.summary}`);
|
|
176
|
-
if (lastSession.next_steps) {
|
|
177
|
-
try {
|
|
178
|
-
const nextSteps = JSON.parse(lastSession.next_steps);
|
|
179
|
-
if (nextSteps.length > 0) {
|
|
180
|
-
console.log(` Proximos passos sugeridos:`);
|
|
181
|
-
for (const step of nextSteps.slice(0, 3)) {
|
|
182
|
-
console.log(` - ${step}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
} catch { /* ignore */ }
|
|
186
|
-
}
|
|
187
|
-
if (lastSession.blockers) {
|
|
188
|
-
try {
|
|
189
|
-
const blockers = JSON.parse(lastSession.blockers);
|
|
190
|
-
if (blockers.length > 0) {
|
|
191
|
-
console.log(` Blockers pendentes:`);
|
|
192
|
-
for (const b of blockers) {
|
|
193
|
-
console.log(` [!] ${b}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
} catch { /* ignore */ }
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
console.log(`\n${"─".repeat(60)}`);
|
|
201
|
-
|
|
202
|
-
// Sugerir proximo passo
|
|
203
|
-
switch (spec.phase) {
|
|
204
|
-
case "planning":
|
|
205
|
-
console.log(`Proximo: plan task-add ou check request`);
|
|
206
|
-
break;
|
|
207
|
-
case "checking":
|
|
208
|
-
console.log(`Proximo: check approve ou check reject`);
|
|
209
|
-
break;
|
|
210
|
-
case "implementing":
|
|
211
|
-
console.log(`Proximo: task next`);
|
|
212
|
-
break;
|
|
213
|
-
case "reviewing":
|
|
214
|
-
console.log(`Proximo: review approve`);
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
console.log();
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function contextExport(options: { task?: string; json?: boolean; }): void {
|
|
221
|
-
initSchema();
|
|
222
|
-
|
|
223
|
-
const db = getDb();
|
|
224
|
-
|
|
225
|
-
const spec = db
|
|
226
|
-
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
227
|
-
.get() as any;
|
|
228
|
-
|
|
229
|
-
if (!spec) {
|
|
230
|
-
console.error("\nNenhuma feature ativa.\n");
|
|
231
|
-
process.exit(1);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
|
|
235
|
-
const decisions = db
|
|
236
|
-
.query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
|
|
237
|
-
.all(spec.id) as any[];
|
|
238
|
-
const artifacts = db.query("SELECT * FROM artifacts WHERE spec_id = ?").all(spec.id) as any[];
|
|
239
|
-
const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
|
|
240
|
-
|
|
241
|
-
let taskContext = null;
|
|
242
|
-
let standards: any[] = [];
|
|
243
|
-
let knowledge: any[] = [];
|
|
244
|
-
let libContexts: string[] = [];
|
|
245
|
-
|
|
246
|
-
if (options.task) {
|
|
247
|
-
const taskId = parseInt(options.task);
|
|
248
|
-
const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
|
|
249
|
-
if (task) {
|
|
250
|
-
taskContext = {
|
|
251
|
-
id: task.id,
|
|
252
|
-
number: task.number,
|
|
253
|
-
name: task.name,
|
|
254
|
-
agent: task.agent,
|
|
255
|
-
files: task.files ? JSON.parse(task.files) : [],
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
// Obter standards para o agente da task
|
|
259
|
-
const domain = task.agent?.split("-")[0] || "all";
|
|
260
|
-
standards = db
|
|
261
|
-
.query(
|
|
262
|
-
`SELECT * FROM standards
|
|
263
|
-
WHERE scope = 'all' OR scope = ?
|
|
264
|
-
ORDER BY enforcement DESC, category`
|
|
265
|
-
)
|
|
266
|
-
.all(domain) as any[];
|
|
267
|
-
|
|
268
|
-
standards = standards.map((std) => ({
|
|
269
|
-
category: std.category,
|
|
270
|
-
rule: std.rule,
|
|
271
|
-
examples: std.examples ? JSON.parse(std.examples) : [],
|
|
272
|
-
anti_examples: std.anti_examples ? JSON.parse(std.anti_examples) : [],
|
|
273
|
-
enforcement: std.enforcement,
|
|
274
|
-
}));
|
|
275
|
-
|
|
276
|
-
// Obter knowledge broadcast (automatico)
|
|
277
|
-
knowledge = getKnowledgeForTask(spec.id, taskId).map((k) => ({
|
|
278
|
-
id: k.id,
|
|
279
|
-
category: k.category,
|
|
280
|
-
content: k.content,
|
|
281
|
-
severity: k.severity,
|
|
282
|
-
origin_task: k.task_origin,
|
|
283
|
-
created_at: k.created_at,
|
|
284
|
-
}));
|
|
285
|
-
|
|
286
|
-
// Obter lib-contexts para o agente da task
|
|
287
|
-
if (task.agent) {
|
|
288
|
-
libContexts = getLibContextsForAgent(task.agent);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Obter contexto de produto
|
|
294
|
-
const productContext = db.query("SELECT * FROM product_context WHERE id = 'default'").get() as any;
|
|
295
|
-
const productGoals = db.query("SELECT * FROM product_goals WHERE product_id = 'default'").all() as any[];
|
|
296
|
-
const productFeatures = db.query("SELECT * FROM product_features WHERE product_id = 'default'").all() as any[];
|
|
297
|
-
|
|
298
|
-
const exportData = {
|
|
299
|
-
spec: {
|
|
300
|
-
id: spec.id,
|
|
301
|
-
name: spec.name,
|
|
302
|
-
phase: spec.phase,
|
|
303
|
-
},
|
|
304
|
-
project: project ? {
|
|
305
|
-
stack: JSON.parse(project.stack),
|
|
306
|
-
} : null,
|
|
307
|
-
productContext: productContext ? {
|
|
308
|
-
name: productContext.name,
|
|
309
|
-
problem: productContext.problem,
|
|
310
|
-
solution: productContext.solution,
|
|
311
|
-
targetUsers: productContext.target_users,
|
|
312
|
-
valueProposition: productContext.value_proposition,
|
|
313
|
-
outOfScope: productContext.out_of_scope ? JSON.parse(productContext.out_of_scope) : [],
|
|
314
|
-
constraints: productContext.constraints ? JSON.parse(productContext.constraints) : [],
|
|
315
|
-
} : null,
|
|
316
|
-
productGoals: productGoals.map((g) => ({
|
|
317
|
-
category: g.category,
|
|
318
|
-
goal: g.goal,
|
|
319
|
-
priority: g.priority,
|
|
320
|
-
})),
|
|
321
|
-
productFeatures: productFeatures.map((f) => ({
|
|
322
|
-
name: f.name,
|
|
323
|
-
description: f.description,
|
|
324
|
-
priority: f.priority,
|
|
325
|
-
})),
|
|
326
|
-
objective: context?.objective,
|
|
327
|
-
approach: context?.approach,
|
|
328
|
-
patterns: context?.patterns ? JSON.parse(context.patterns) : [],
|
|
329
|
-
constraints: context?.constraints ? JSON.parse(context.constraints) : [],
|
|
330
|
-
decisions: decisions.map((d) => ({
|
|
331
|
-
id: d.id,
|
|
332
|
-
title: d.title,
|
|
333
|
-
decision: d.decision,
|
|
334
|
-
})),
|
|
335
|
-
artifacts: artifacts.map((a) => a.path),
|
|
336
|
-
task: taskContext,
|
|
337
|
-
standards: standards,
|
|
338
|
-
knowledge: knowledge,
|
|
339
|
-
libContexts: libContexts,
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
console.log(JSON.stringify(exportData, null, 2));
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Funcao helper para formatar contexto para subagent (uso interno)
|
|
346
|
-
// v8.1: Contexto COMPLETO mas ESTRUTURADO - todas as fontes relevantes incluidas
|
|
347
|
-
export function getContextForSubagent(taskId: number): string {
|
|
348
|
-
initSchema();
|
|
349
|
-
|
|
350
|
-
const db = getDb();
|
|
351
|
-
|
|
352
|
-
const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
|
|
353
|
-
if (!task) {
|
|
354
|
-
return "ERRO: Task nao encontrada";
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
|
|
358
|
-
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(task.spec_id) as any;
|
|
359
|
-
const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
|
|
360
|
-
|
|
361
|
-
// v8.4: Analise arquitetural (link explicito via analysis_id ou nome)
|
|
362
|
-
const archAnalysis = getArchitecturalAnalysisForSpec(spec.name, task.spec_id);
|
|
363
|
-
|
|
364
|
-
// Arquivos da task para filtrar contexto relevante
|
|
365
|
-
const taskFiles = task.files ? JSON.parse(task.files) : [];
|
|
366
|
-
const domain = task.agent?.split("-")[0] || "all";
|
|
367
|
-
|
|
368
|
-
// Decisoes relevantes (max 8, priorizando as que mencionam arquivos da task)
|
|
369
|
-
const allDecisions = db
|
|
370
|
-
.query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active' ORDER BY created_at DESC")
|
|
371
|
-
.all(task.spec_id) as any[];
|
|
372
|
-
const decisions = filterRelevantDecisions(allDecisions, taskFiles, 8);
|
|
373
|
-
|
|
374
|
-
// Standards required + recommended que se aplicam aos arquivos
|
|
375
|
-
const standards = db
|
|
376
|
-
.query(
|
|
377
|
-
`SELECT * FROM standards
|
|
378
|
-
WHERE (scope = 'all' OR scope = ?)
|
|
379
|
-
ORDER BY enforcement DESC, category`
|
|
380
|
-
)
|
|
381
|
-
.all(domain) as any[];
|
|
382
|
-
const relevantStandards = filterRelevantStandards(standards, taskFiles);
|
|
383
|
-
|
|
384
|
-
// v8.3: Knowledge com TTL, caps e indicadores de truncamento
|
|
385
|
-
const allKnowledge = getKnowledgeForTask(task.spec_id, taskId);
|
|
386
|
-
const now48h = new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString();
|
|
387
|
-
const recentKnowledge = allKnowledge.filter((k: any) => k.created_at > now48h);
|
|
388
|
-
const staleKnowledge = allKnowledge.filter((k: any) => k.created_at <= now48h);
|
|
389
|
-
// Conhecimento recente primeiro; stale so se critical
|
|
390
|
-
const effectiveKnowledge = [
|
|
391
|
-
...recentKnowledge,
|
|
392
|
-
...staleKnowledge.filter((k: any) => k.severity === 'critical'),
|
|
393
|
-
];
|
|
394
|
-
const allCriticalKnowledge = effectiveKnowledge.filter((k: any) => k.severity === 'critical' || k.severity === 'warning');
|
|
395
|
-
const criticalKnowledge = allCriticalKnowledge.slice(0, 20);
|
|
396
|
-
const truncatedCritical = allCriticalKnowledge.length - criticalKnowledge.length;
|
|
397
|
-
const allInfoKnowledge = effectiveKnowledge.filter((k: any) => k.severity === 'info');
|
|
398
|
-
const infoKnowledge = allInfoKnowledge.slice(0, 10);
|
|
399
|
-
const truncatedInfo = allInfoKnowledge.length - infoKnowledge.length;
|
|
400
|
-
|
|
401
|
-
// v8.1: Contexto de produto (resumido - problema, usuarios, restricoes)
|
|
402
|
-
const productContext = db.query("SELECT * FROM product_context WHERE id = 'default'").get() as any;
|
|
403
|
-
|
|
404
|
-
// v8.1: Patterns de implementacao matchados aos arquivos da task
|
|
405
|
-
const patterns = getPatternsForFiles(taskFiles);
|
|
406
|
-
|
|
407
|
-
// v8.2: Reasoning de tasks dependentes + todas tasks completas recentes
|
|
408
|
-
const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
|
|
409
|
-
const depReasoning: any[] = [];
|
|
410
|
-
const seenTaskIds = new Set<number>();
|
|
411
|
-
|
|
412
|
-
// 1. Reasoning de dependencias diretas (prioridade maxima)
|
|
413
|
-
if (dependsOn.length > 0) {
|
|
414
|
-
const placeholders = dependsOn.map(() => '?').join(',');
|
|
415
|
-
const depTasks = db.query(
|
|
416
|
-
`SELECT id, number FROM tasks WHERE spec_id = ? AND number IN (${placeholders})`
|
|
417
|
-
).all(task.spec_id, ...dependsOn) as any[];
|
|
418
|
-
|
|
419
|
-
for (const depTask of depTasks) {
|
|
420
|
-
seenTaskIds.add(depTask.id);
|
|
421
|
-
const reasoning = db.query(
|
|
422
|
-
`SELECT category, thought FROM reasoning_log
|
|
423
|
-
WHERE spec_id = ? AND task_id = ?
|
|
424
|
-
AND category IN ('recommendation', 'decision', 'challenge')
|
|
425
|
-
ORDER BY
|
|
426
|
-
CASE importance WHEN 'critical' THEN 1 WHEN 'high' THEN 2 ELSE 3 END,
|
|
427
|
-
created_at DESC
|
|
428
|
-
LIMIT 3`
|
|
429
|
-
).all(task.spec_id, depTask.id) as any[];
|
|
430
|
-
for (const r of reasoning) {
|
|
431
|
-
depReasoning.push({ ...r, fromTask: depTask.number });
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// 2. Reasoning de TODAS tasks completas (recommendations e challenges de alta importancia)
|
|
437
|
-
const completedTasks = db.query(
|
|
438
|
-
`SELECT t.id, t.number FROM tasks t
|
|
439
|
-
WHERE t.spec_id = ? AND t.status = 'done' AND t.id != ?
|
|
440
|
-
ORDER BY t.completed_at DESC LIMIT 10`
|
|
441
|
-
).all(task.spec_id, taskId) as any[];
|
|
442
|
-
|
|
443
|
-
for (const ct of completedTasks) {
|
|
444
|
-
if (seenTaskIds.has(ct.id)) continue;
|
|
445
|
-
const reasoning = db.query(
|
|
446
|
-
`SELECT category, thought FROM reasoning_log
|
|
447
|
-
WHERE spec_id = ? AND task_id = ?
|
|
448
|
-
AND category IN ('recommendation', 'challenge')
|
|
449
|
-
AND importance IN ('critical', 'high')
|
|
450
|
-
ORDER BY
|
|
451
|
-
CASE importance WHEN 'critical' THEN 1 ELSE 2 END,
|
|
452
|
-
created_at DESC
|
|
453
|
-
LIMIT 2`
|
|
454
|
-
).all(task.spec_id, ct.id) as any[];
|
|
455
|
-
for (const r of reasoning) {
|
|
456
|
-
depReasoning.push({ ...r, fromTask: ct.number });
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// v8.1: Lib contexts do projeto (resumido - nomes e versoes)
|
|
461
|
-
const libContexts = db.query(
|
|
462
|
-
"SELECT lib_name, version FROM lib_contexts ORDER BY lib_name LIMIT 10"
|
|
463
|
-
).all() as any[];
|
|
464
|
-
|
|
465
|
-
// v8.3: Sessoes compostas para continuidade cross-session (todas, nao so ultima)
|
|
466
|
-
const sessions = getSessionSummaries(task.spec_id, 5);
|
|
467
|
-
|
|
468
|
-
// v8.2: Knowledge graph - decisoes que afetam arquivos da task
|
|
469
|
-
const graphDecisions: any[] = [];
|
|
470
|
-
for (const file of taskFiles) {
|
|
471
|
-
try {
|
|
472
|
-
const related = getRelatedDecisions(file, "file");
|
|
473
|
-
for (const d of related) {
|
|
474
|
-
if (!graphDecisions.find((gd: any) => gd.id === d.id)) {
|
|
475
|
-
graphDecisions.push(d);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
} catch { /* ignore if graph empty */ }
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// v8.2: Deteccao de contradicoes
|
|
482
|
-
let contradictions: any[] = [];
|
|
483
|
-
try {
|
|
484
|
-
contradictions = findContradictions(task.spec_id);
|
|
485
|
-
} catch { /* ignore if no contradictions */ }
|
|
486
|
-
|
|
487
|
-
// v8.2: Patterns descobertos por subagents anteriores
|
|
488
|
-
const discoveredPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
|
|
489
|
-
|
|
490
|
-
// ===== MONTAR OUTPUT ESTRUTURADO =====
|
|
491
|
-
|
|
492
|
-
let output = `## CONTEXTO (Task #${task.number})
|
|
493
|
-
|
|
494
|
-
**Feature:** ${spec.name}
|
|
495
|
-
**Objetivo:** ${context?.objective || "N/A"}
|
|
496
|
-
**Arquivos:** ${taskFiles.join(", ") || "Nenhum especificado"}
|
|
497
|
-
`;
|
|
498
|
-
|
|
499
|
-
// Contexto de produto
|
|
500
|
-
if (productContext) {
|
|
501
|
-
output += `
|
|
502
|
-
### PRODUTO
|
|
503
|
-
- **Problema:** ${productContext.problem || "N/A"}
|
|
504
|
-
- **Usuarios:** ${productContext.target_users || "N/A"}`;
|
|
505
|
-
if (productContext.constraints) {
|
|
506
|
-
try {
|
|
507
|
-
const constraints = JSON.parse(productContext.constraints);
|
|
508
|
-
if (constraints.length > 0) {
|
|
509
|
-
output += `\n- **Restricoes:** ${constraints.slice(0, 3).join("; ")}${constraints.length > 3 ? ` [+${constraints.length - 3} mais]` : ''}`;
|
|
510
|
-
}
|
|
511
|
-
} catch { /* ignore parse errors */ }
|
|
512
|
-
}
|
|
513
|
-
output += "\n";
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// v8.3: Secao ARQUITETURA (Fase 3 - Architect Pipeline)
|
|
517
|
-
if (archAnalysis) {
|
|
518
|
-
output += `
|
|
519
|
-
### ARQUITETURA (${archAnalysis.id})`;
|
|
520
|
-
if (archAnalysis.approach) {
|
|
521
|
-
const approachPreview = archAnalysis.approach.length > 500
|
|
522
|
-
? archAnalysis.approach.substring(0, 500) + "..."
|
|
523
|
-
: archAnalysis.approach;
|
|
524
|
-
output += `\n**Abordagem:** ${approachPreview}`;
|
|
525
|
-
}
|
|
526
|
-
if (archAnalysis.risks) {
|
|
527
|
-
try {
|
|
528
|
-
const risks = JSON.parse(archAnalysis.risks);
|
|
529
|
-
if (risks.length > 0) {
|
|
530
|
-
const highRisks = risks.filter((r: any) => r.impact === 'high' || r.probability === 'high');
|
|
531
|
-
const risksToShow = highRisks.length > 0 ? highRisks.slice(0, 3) : risks.slice(0, 3);
|
|
532
|
-
output += `\n**Riscos:**`;
|
|
533
|
-
for (const r of risksToShow) {
|
|
534
|
-
output += `\n - ${r.description} (${r.probability}/${r.impact}) -> ${r.mitigation}`;
|
|
535
|
-
}
|
|
536
|
-
if (risks.length > risksToShow.length) {
|
|
537
|
-
output += `\n [+${risks.length - risksToShow.length} mais riscos]`;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
} catch { /* ignore */ }
|
|
541
|
-
}
|
|
542
|
-
if (archAnalysis.decisions) {
|
|
543
|
-
try {
|
|
544
|
-
const archDecisions = JSON.parse(archAnalysis.decisions);
|
|
545
|
-
if (archDecisions.length > 0) {
|
|
546
|
-
output += `\n**Decisoes arquiteturais:**`;
|
|
547
|
-
for (const d of archDecisions.slice(0, 5)) {
|
|
548
|
-
output += `\n - ${d.decision}: ${d.rationale || ''}`;
|
|
549
|
-
}
|
|
550
|
-
if (archDecisions.length > 5) {
|
|
551
|
-
output += `\n [+${archDecisions.length - 5} mais]`;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
} catch { /* ignore */ }
|
|
555
|
-
}
|
|
556
|
-
output += "\n";
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Standards
|
|
560
|
-
const requiredStds = relevantStandards.filter((s: any) => s.enforcement === 'required');
|
|
561
|
-
const recommendedStds = relevantStandards.filter((s: any) => s.enforcement === 'recommended');
|
|
562
|
-
|
|
563
|
-
output += `
|
|
564
|
-
### STANDARDS (${relevantStandards.length})`;
|
|
565
|
-
if (requiredStds.length > 0) {
|
|
566
|
-
output += `\n**Obrigatorios:**\n${requiredStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
|
|
567
|
-
}
|
|
568
|
-
if (recommendedStds.length > 0) {
|
|
569
|
-
output += `\n**Recomendados:**\n${recommendedStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
|
|
570
|
-
}
|
|
571
|
-
if (relevantStandards.length === 0) {
|
|
572
|
-
output += "\nNenhum standard aplicavel";
|
|
573
|
-
}
|
|
574
|
-
output += "\n";
|
|
575
|
-
|
|
576
|
-
// v8.3: Decisoes com indicador de truncamento
|
|
577
|
-
const truncatedDecisions = allDecisions.length - decisions.length;
|
|
578
|
-
output += `
|
|
579
|
-
### DECISOES (${decisions.length}${truncatedDecisions > 0 ? ` [+${truncatedDecisions} mais - use: decisions list]` : ''})
|
|
580
|
-
${decisions.length > 0 ? decisions.map((d) => `- **${d.title}**: ${d.decision}`).join("\n") : "Nenhuma"}
|
|
581
|
-
`;
|
|
582
|
-
|
|
583
|
-
// Reasoning de tasks anteriores
|
|
584
|
-
if (depReasoning.length > 0) {
|
|
585
|
-
output += `
|
|
586
|
-
### CONTEXTO DE TASKS ANTERIORES
|
|
587
|
-
${depReasoning.map((r: any) => `- [Task #${r.fromTask}/${r.category}] ${r.thought}`).join("\n")}
|
|
588
|
-
`;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// v8.3: Alertas com cap de 20 e indicador de truncamento
|
|
592
|
-
if (criticalKnowledge.length > 0) {
|
|
593
|
-
const severityIcon: Record<string, string> = { warning: "!!", critical: "XX" };
|
|
594
|
-
output += `
|
|
595
|
-
### ALERTAS (${criticalKnowledge.length}${truncatedCritical > 0 ? ` [+${truncatedCritical} mais - use: knowledge list --severity warning]` : ''})
|
|
596
|
-
${criticalKnowledge.map((k: any) => `[${severityIcon[k.severity] || "!"}] ${k.content}`).join("\n")}
|
|
597
|
-
`;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// v8.3: Discoveries com indicador de truncamento
|
|
601
|
-
if (infoKnowledge.length > 0) {
|
|
602
|
-
output += `
|
|
603
|
-
### DISCOVERIES DE TASKS ANTERIORES (${infoKnowledge.length}${truncatedInfo > 0 ? ` [+${truncatedInfo} mais - use: knowledge list]` : ''})
|
|
604
|
-
${infoKnowledge.map((k: any) => `- ${k.content}`).join("\n")}
|
|
605
|
-
`;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// Patterns de implementacao matchados
|
|
609
|
-
if (patterns.length > 0) {
|
|
610
|
-
output += `
|
|
611
|
-
### PATTERNS DO PROJETO (${patterns.length})
|
|
612
|
-
${patterns.map((p: any) => {
|
|
613
|
-
const tmpl = p.template || "";
|
|
614
|
-
const preview = tmpl.length > 200 ? tmpl.substring(0, 200) + `... [truncado de ${tmpl.length} chars]` : tmpl;
|
|
615
|
-
return `- **${p.name}** (${p.category}): ${preview || p.description || "Sem template"}`;
|
|
616
|
-
}).join("\n")}
|
|
617
|
-
`;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// v8.5: Utility Map (DRY prevention - Camada 1)
|
|
621
|
-
try {
|
|
622
|
-
const taskDirs = [...new Set(taskFiles.map((f: string) => {
|
|
623
|
-
const parts = f.replace(/\\/g, "/").split("/");
|
|
624
|
-
return parts.slice(0, -1).join("/");
|
|
625
|
-
}).filter(Boolean))];
|
|
626
|
-
|
|
627
|
-
const agentScope = task.agent?.split("-")[0] || undefined;
|
|
628
|
-
let relevantUtilities = getUtilitiesForContext(taskDirs, undefined, 15);
|
|
629
|
-
|
|
630
|
-
// Se poucos resultados por diretorio, ampliar para scope do agent
|
|
631
|
-
if (relevantUtilities.length < 5 && agentScope) {
|
|
632
|
-
const scopeUtils = getUtilitiesForContext([], agentScope, 15);
|
|
633
|
-
const existingKeys = new Set(relevantUtilities.map((u: any) => `${u.file_path}:${u.utility_name}`));
|
|
634
|
-
for (const u of scopeUtils) {
|
|
635
|
-
if (!existingKeys.has(`${u.file_path}:${u.utility_name}`)) {
|
|
636
|
-
relevantUtilities.push(u);
|
|
637
|
-
}
|
|
638
|
-
if (relevantUtilities.length >= 15) break;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (relevantUtilities.length > 0) {
|
|
643
|
-
const totalCount = (db.query("SELECT COUNT(*) as c FROM project_utilities").get() as any)?.c || 0;
|
|
644
|
-
const truncated = totalCount - relevantUtilities.length;
|
|
645
|
-
output += `
|
|
646
|
-
### UTILITIES EXISTENTES (${relevantUtilities.length}${truncated > 0 ? ` [+${truncated} mais]` : ''})
|
|
647
|
-
**REGRA DRY**: Reutilize ao inves de recriar. Importe do arquivo existente.
|
|
648
|
-
${relevantUtilities.map((u: any) => {
|
|
649
|
-
const sig = u.signature ? ` ${u.signature}` : '';
|
|
650
|
-
return `- **${u.utility_name}** [${u.utility_type}]${sig} <- \`${u.file_path}\``;
|
|
651
|
-
}).join("\n")}
|
|
652
|
-
`;
|
|
653
|
-
}
|
|
654
|
-
} catch { /* tabela pode nao existir ainda */ }
|
|
655
|
-
|
|
656
|
-
// v8.3: Sessoes compostas (todas, nao so ultima)
|
|
657
|
-
if (sessions.length > 0) {
|
|
658
|
-
output += `
|
|
659
|
-
### HISTORICO DE SESSOES (${sessions.length})`;
|
|
660
|
-
// Sessao mais recente: detalhes completos
|
|
661
|
-
const latest = sessions[0];
|
|
662
|
-
output += `\n**Ultima:** ${latest.summary}`;
|
|
663
|
-
if (latest.next_steps) {
|
|
664
|
-
try {
|
|
665
|
-
const steps = JSON.parse(latest.next_steps);
|
|
666
|
-
if (steps.length > 0) {
|
|
667
|
-
output += `\n**Proximos passos:** ${steps.slice(0, 3).join("; ")}${steps.length > 3 ? ` [+${steps.length - 3} mais]` : ''}`;
|
|
668
|
-
}
|
|
669
|
-
} catch { /* ignore */ }
|
|
670
|
-
}
|
|
671
|
-
if (latest.blockers) {
|
|
672
|
-
try {
|
|
673
|
-
const blockers = JSON.parse(latest.blockers);
|
|
674
|
-
if (blockers.length > 0) {
|
|
675
|
-
output += `\n**Blockers:** ${blockers.join("; ")}`;
|
|
676
|
-
}
|
|
677
|
-
} catch { /* ignore */ }
|
|
678
|
-
}
|
|
679
|
-
// Sessoes anteriores: resumo de 1 linha
|
|
680
|
-
if (sessions.length > 1) {
|
|
681
|
-
output += `\n**Sessoes anteriores:**`;
|
|
682
|
-
for (const s of sessions.slice(1)) {
|
|
683
|
-
output += `\n - ${s.summary.substring(0, 100)}${s.summary.length > 100 ? '...' : ''} (${s.tasks_completed || 0} tasks)`;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
output += "\n";
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// Decisoes do knowledge graph que afetam arquivos desta task
|
|
690
|
-
if (graphDecisions.length > 0) {
|
|
691
|
-
output += `
|
|
692
|
-
### DECISOES QUE AFETAM SEUS ARQUIVOS (${graphDecisions.length})
|
|
693
|
-
${graphDecisions.map((d: any) => `- **${d.title}**: ${d.decision}`).join("\n")}
|
|
694
|
-
`;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// Contradicoes detectadas
|
|
698
|
-
if (contradictions.length > 0) {
|
|
699
|
-
output += `
|
|
700
|
-
### [!!] CONTRADICOES DETECTADAS (${contradictions.length})
|
|
701
|
-
${contradictions.map((c: any) => `- "${c.decision1}" <-> "${c.decision2}"`).join("\n")}
|
|
702
|
-
Verifique estas contradicoes antes de prosseguir.
|
|
703
|
-
`;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// v8.3: Patterns descobertos com indicador de truncamento
|
|
707
|
-
if (discoveredPatterns.length > 0) {
|
|
708
|
-
const patternsToShow = discoveredPatterns.slice(-10);
|
|
709
|
-
output += `
|
|
710
|
-
### PATTERNS DESCOBERTOS (${discoveredPatterns.length})
|
|
711
|
-
${discoveredPatterns.length > 10 ? `[mostrando ultimos 10 de ${discoveredPatterns.length}]\n` : ''}${patternsToShow.map((p: any) => `- ${p.pattern}${p.source_task ? ` (Task #${p.source_task})` : ""}`).join("\n")}
|
|
712
|
-
`;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// Stack
|
|
716
|
-
if (project) {
|
|
717
|
-
const stack = JSON.parse(project.stack);
|
|
718
|
-
const allStackEntries = Object.entries(stack);
|
|
719
|
-
const mainStack = allStackEntries.slice(0, 6);
|
|
720
|
-
output += `
|
|
721
|
-
### STACK
|
|
722
|
-
${mainStack.map(([k, v]) => `${k}: ${v}`).join(" | ")}${allStackEntries.length > 6 ? ` [+${allStackEntries.length - 6} mais]` : ''}
|
|
723
|
-
`;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// Bibliotecas do projeto
|
|
727
|
-
if (libContexts.length > 0) {
|
|
728
|
-
output += `
|
|
729
|
-
### BIBLIOTECAS
|
|
730
|
-
${libContexts.map((l: any) => `- ${l.lib_name}${l.version ? ` v${l.version}` : ""}`).join("\n")}
|
|
731
|
-
`;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// Protocolo de retorno
|
|
735
|
-
output += `
|
|
736
|
-
### RETORNO OBRIGATORIO
|
|
737
|
-
\`\`\`json
|
|
738
|
-
{"status": "completed|blocked", "summary": "...", "files_created": [], "files_modified": [], "reasoning": {"approach": "como abordou", "challenges": [], "recommendations": "para proximas tasks"}}
|
|
739
|
-
\`\`\`
|
|
740
|
-
`;
|
|
741
|
-
|
|
742
|
-
// v8.3: Overall size cap com truncamento inteligente por secao
|
|
743
|
-
if (output.length > MAX_CONTEXT_SIZE) {
|
|
744
|
-
const sections = output.split('\n### ');
|
|
745
|
-
let trimmed = sections[0]; // Sempre manter header
|
|
746
|
-
|
|
747
|
-
for (let i = 1; i < sections.length; i++) {
|
|
748
|
-
const candidate = trimmed + '\n### ' + sections[i];
|
|
749
|
-
if (candidate.length > MAX_CONTEXT_SIZE - 200) {
|
|
750
|
-
break;
|
|
751
|
-
}
|
|
752
|
-
trimmed = candidate;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
const omittedSections = sections.length - trimmed.split('\n### ').length;
|
|
756
|
-
if (omittedSections > 0) {
|
|
757
|
-
trimmed += `\n\n[CONTEXTO TRUNCADO: ${omittedSections} secao(oes) omitida(s) por limite de ${MAX_CONTEXT_SIZE} chars. Use: context-export para contexto completo]`;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
output = trimmed;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
return output;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// v8.3: Filtrar decisoes relevantes com scoring melhorado
|
|
767
|
-
function filterRelevantDecisions(decisions: any[], taskFiles: string[], maxCount: number): any[] {
|
|
768
|
-
if (decisions.length === 0) return [];
|
|
769
|
-
if (taskFiles.length === 0) return decisions.slice(0, maxCount);
|
|
770
|
-
|
|
771
|
-
// Extrair keywords semanticas dos arquivos da task
|
|
772
|
-
const fileExtensions = new Set(taskFiles.map(f => f.split('.').pop()?.toLowerCase()).filter(Boolean));
|
|
773
|
-
const fileDirs = new Set(taskFiles.flatMap(f => f.split('/').slice(0, -1)).filter(Boolean));
|
|
774
|
-
|
|
775
|
-
// Keywords de dominio para scoring contextual
|
|
776
|
-
const domainKeywords: Record<string, string[]> = {
|
|
777
|
-
frontend: ['component', 'page', 'layout', 'css', 'style', 'ui', 'react', 'next', 'hook'],
|
|
778
|
-
backend: ['api', 'route', 'handler', 'middleware', 'server', 'endpoint', 'controller'],
|
|
779
|
-
database: ['schema', 'migration', 'query', 'table', 'index', 'sql', 'model'],
|
|
780
|
-
testing: ['test', 'spec', 'mock', 'fixture', 'assert', 'jest', 'vitest'],
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
const scored = decisions.map((d) => {
|
|
784
|
-
let score = 0;
|
|
785
|
-
const combined = `${d.decision || ''} ${d.title || ''} ${d.rationale || ''}`.toLowerCase();
|
|
786
|
-
|
|
787
|
-
// Arquivo exato e diretorio (+10/+5)
|
|
788
|
-
for (const file of taskFiles) {
|
|
789
|
-
const fileName = file.split("/").pop() || file;
|
|
790
|
-
const dirName = file.split("/").slice(-2, -1)[0] || "";
|
|
791
|
-
|
|
792
|
-
if (combined.includes(fileName.toLowerCase())) score += 10;
|
|
793
|
-
if (dirName && combined.includes(dirName.toLowerCase())) score += 5;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// v8.3: Extensao dos arquivos (+3)
|
|
797
|
-
for (const ext of fileExtensions) {
|
|
798
|
-
if (combined.includes(`.${ext}`)) { score += 3; break; }
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// v8.3: Diretorio mencionado (+4)
|
|
802
|
-
for (const dir of fileDirs) {
|
|
803
|
-
if (combined.includes(dir.toLowerCase())) { score += 4; break; }
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// v8.3: Keywords de dominio (+2)
|
|
807
|
-
const taskCombined = taskFiles.join(' ').toLowerCase();
|
|
808
|
-
for (const [, keywords] of Object.entries(domainKeywords)) {
|
|
809
|
-
for (const kw of keywords) {
|
|
810
|
-
if (combined.includes(kw) && taskCombined.includes(kw)) {
|
|
811
|
-
score += 2;
|
|
812
|
-
break;
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// Recencia (+3/+1)
|
|
818
|
-
const age = Date.now() - new Date(d.created_at).getTime();
|
|
819
|
-
const hoursOld = age / (1000 * 60 * 60);
|
|
820
|
-
if (hoursOld < 1) score += 3;
|
|
821
|
-
else if (hoursOld < 24) score += 1;
|
|
822
|
-
|
|
823
|
-
return { ...d, _relevanceScore: score };
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
return scored
|
|
827
|
-
.sort((a, b) => b._relevanceScore - a._relevanceScore)
|
|
828
|
-
.slice(0, maxCount)
|
|
829
|
-
.map(({ _relevanceScore, ...d }) => d);
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
// v8.0: Filtrar standards relevantes para os arquivos da task
|
|
833
|
-
function filterRelevantStandards(standards: any[], taskFiles: string[]): any[] {
|
|
834
|
-
if (standards.length === 0) return [];
|
|
835
|
-
if (taskFiles.length === 0) return standards;
|
|
836
|
-
|
|
837
|
-
// Extrair extensoes e diretorios dos arquivos da task
|
|
838
|
-
const extensions = new Set(taskFiles.map((f) => {
|
|
839
|
-
const ext = f.split(".").pop();
|
|
840
|
-
return ext ? `.${ext}` : "";
|
|
841
|
-
}).filter(Boolean));
|
|
842
|
-
|
|
843
|
-
const directories = new Set(taskFiles.map((f) => {
|
|
844
|
-
const parts = f.split("/");
|
|
845
|
-
return parts.length > 1 ? parts.slice(0, -1).join("/") : "";
|
|
846
|
-
}).filter(Boolean));
|
|
847
|
-
|
|
848
|
-
// Filtrar standards que se aplicam
|
|
849
|
-
return standards.filter((s) => {
|
|
850
|
-
// Standards de naming sempre aplicam
|
|
851
|
-
if (s.category === "naming") return true;
|
|
852
|
-
|
|
853
|
-
// Standards de code aplicam se mencionam extensao dos arquivos
|
|
854
|
-
if (s.category === "code") {
|
|
855
|
-
for (const ext of extensions) {
|
|
856
|
-
if (s.rule?.includes(ext) || s.scope === "all") return true;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Standards de structure aplicam se mencionam diretorio
|
|
861
|
-
if (s.category === "structure") {
|
|
862
|
-
for (const dir of directories) {
|
|
863
|
-
if (s.rule?.includes(dir)) return true;
|
|
864
|
-
}
|
|
865
|
-
return s.scope === "all";
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// Outros standards: incluir se scope combina
|
|
869
|
-
return true;
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
export function recover(options: {
|
|
874
|
-
list?: boolean;
|
|
875
|
-
snapshot?: string;
|
|
876
|
-
restore?: boolean;
|
|
877
|
-
auto?: boolean;
|
|
878
|
-
}): void {
|
|
879
|
-
initSchema();
|
|
880
|
-
|
|
881
|
-
const db = getDb();
|
|
882
|
-
|
|
883
|
-
// Listar snapshots
|
|
884
|
-
if (options.list) {
|
|
885
|
-
const snapshots = db
|
|
886
|
-
.query("SELECT id, spec_id, trigger, created_at FROM snapshots ORDER BY created_at DESC LIMIT 20")
|
|
887
|
-
.all() as any[];
|
|
888
|
-
|
|
889
|
-
if (snapshots.length === 0) {
|
|
890
|
-
console.log("\nNenhum snapshot disponivel.\n");
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
console.log(`\nSnapshots disponiveis:`);
|
|
895
|
-
console.log(`${"─".repeat(60)}`);
|
|
896
|
-
for (const snap of snapshots) {
|
|
897
|
-
console.log(` #${snap.id}: ${snap.spec_id} (${snap.trigger}) - ${snap.created_at}`);
|
|
898
|
-
}
|
|
899
|
-
console.log(`\nPara restaurar: recover --restore --snapshot <id>`);
|
|
900
|
-
console.log(`Ou automatico: recover --auto\n`);
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
// Restaurar snapshot (--restore ou --auto)
|
|
905
|
-
if (options.restore || options.auto) {
|
|
906
|
-
let snapshot: any;
|
|
907
|
-
|
|
908
|
-
if (options.auto) {
|
|
909
|
-
snapshot = db.query("SELECT * FROM snapshots ORDER BY created_at DESC LIMIT 1").get();
|
|
910
|
-
} else if (options.snapshot) {
|
|
911
|
-
const snapshotId = parseInt(options.snapshot);
|
|
912
|
-
snapshot = db.query("SELECT * FROM snapshots WHERE id = ?").get(snapshotId);
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
if (!snapshot) {
|
|
916
|
-
console.error("\nNenhum snapshot para restaurar.\n");
|
|
917
|
-
process.exit(1);
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
const data = JSON.parse(snapshot.data);
|
|
921
|
-
const now = new Date().toISOString();
|
|
922
|
-
|
|
923
|
-
console.log(`\nRestaurando snapshot #${snapshot.id}...`);
|
|
924
|
-
|
|
925
|
-
// 1. Restaurar spec
|
|
926
|
-
if (data.spec) {
|
|
927
|
-
db.run(
|
|
928
|
-
`UPDATE specs SET phase = ?, approved_at = ?, updated_at = ? WHERE id = ?`,
|
|
929
|
-
[data.spec.phase, data.spec.approved_at, now, data.spec.id]
|
|
930
|
-
);
|
|
931
|
-
console.log(` ✓ Spec restaurado (fase: ${data.spec.phase})`);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// 2. Restaurar context
|
|
935
|
-
if (data.context) {
|
|
936
|
-
db.run(
|
|
937
|
-
`UPDATE context SET
|
|
938
|
-
objective = ?, approach = ?, constraints = ?, patterns = ?,
|
|
939
|
-
current_task = ?, last_checkpoint = ?, updated_at = ?
|
|
940
|
-
WHERE spec_id = ?`,
|
|
941
|
-
[
|
|
942
|
-
data.context.objective,
|
|
943
|
-
data.context.approach,
|
|
944
|
-
data.context.constraints,
|
|
945
|
-
data.context.patterns,
|
|
946
|
-
data.context.current_task,
|
|
947
|
-
data.checkpoint || data.context.last_checkpoint,
|
|
948
|
-
now,
|
|
949
|
-
snapshot.spec_id,
|
|
950
|
-
]
|
|
951
|
-
);
|
|
952
|
-
console.log(` ✓ Context restaurado`);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// 3. Restaurar status das tasks
|
|
956
|
-
if (data.tasks && Array.isArray(data.tasks)) {
|
|
957
|
-
for (const task of data.tasks) {
|
|
958
|
-
db.run(
|
|
959
|
-
`UPDATE tasks SET status = ?, checkpoint = ?, completed_at = ? WHERE id = ?`,
|
|
960
|
-
[task.status, task.checkpoint, task.completed_at, task.id]
|
|
961
|
-
);
|
|
962
|
-
}
|
|
963
|
-
console.log(` ✓ ${data.tasks.length} tasks restauradas`);
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
// 4. Log do que foi restaurado
|
|
967
|
-
console.log(`\n${"─".repeat(50)}`);
|
|
968
|
-
console.log(`Snapshot #${snapshot.id} restaurado com sucesso!`);
|
|
969
|
-
console.log(` Spec: ${snapshot.spec_id}`);
|
|
970
|
-
console.log(` Trigger: ${snapshot.trigger}`);
|
|
971
|
-
console.log(` Data original: ${snapshot.created_at}`);
|
|
972
|
-
if (data.taskCompleted) {
|
|
973
|
-
console.log(` Task completada: #${data.taskCompleted}`);
|
|
974
|
-
}
|
|
975
|
-
if (data.checkpoint) {
|
|
976
|
-
console.log(` Checkpoint: ${data.checkpoint}`);
|
|
977
|
-
}
|
|
978
|
-
console.log(`\nUse 'status' para ver o estado atual.\n`);
|
|
979
|
-
return;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// Mostrar detalhes de snapshot específico
|
|
983
|
-
if (options.snapshot) {
|
|
984
|
-
const snapshotId = parseInt(options.snapshot);
|
|
985
|
-
const snapshot = db.query("SELECT * FROM snapshots WHERE id = ?").get(snapshotId) as any;
|
|
986
|
-
|
|
987
|
-
if (!snapshot) {
|
|
988
|
-
console.error(`\nSnapshot #${snapshotId} nao encontrado.\n`);
|
|
989
|
-
process.exit(1);
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
const data = JSON.parse(snapshot.data);
|
|
993
|
-
|
|
994
|
-
console.log(`\nSnapshot #${snapshotId}:`);
|
|
995
|
-
console.log(` Spec: ${snapshot.spec_id}`);
|
|
996
|
-
console.log(` Trigger: ${snapshot.trigger}`);
|
|
997
|
-
console.log(` Data: ${snapshot.created_at}`);
|
|
998
|
-
console.log(`\nDados:`);
|
|
999
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1000
|
-
console.log(`\nPara restaurar: recover --restore --snapshot ${snapshotId}\n`);
|
|
1001
|
-
return;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// Mostrar ultimo snapshot
|
|
1005
|
-
const last = db
|
|
1006
|
-
.query("SELECT * FROM snapshots ORDER BY created_at DESC LIMIT 1")
|
|
1007
|
-
.get() as any;
|
|
1008
|
-
|
|
1009
|
-
if (!last) {
|
|
1010
|
-
console.log("\nNenhum snapshot disponivel.\n");
|
|
1011
|
-
return;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
console.log(`\nUltimo snapshot:`);
|
|
1015
|
-
console.log(` #${last.id}: ${last.spec_id} (${last.trigger}) - ${last.created_at}`);
|
|
1016
|
-
console.log(`\nComandos:`);
|
|
1017
|
-
console.log(` --list Lista todos os snapshots`);
|
|
1018
|
-
console.log(` --snapshot <id> Mostra detalhes de um snapshot`);
|
|
1019
|
-
console.log(` --restore --snapshot <id> Restaura um snapshot especifico`);
|
|
1020
|
-
console.log(` --auto Restaura o ultimo snapshot automaticamente\n`);
|
|
1021
|
-
}
|
|
1
|
+
import { getDb } from "../db/connection";
|
|
2
|
+
import { initSchema, getPatternsForFiles, getLastSessionSummary, getSessionSummaries, getRelatedDecisions, findContradictions, getArchitecturalAnalysisForSpec, getUtilitiesForContext } from "../db/schema";
|
|
3
|
+
import { getKnowledgeForTask } from "./knowledge";
|
|
4
|
+
import { getLibContextsForAgent } from "./research";
|
|
5
|
+
|
|
6
|
+
// v8.3: Limite maximo de contexto para subagents (16KB)
|
|
7
|
+
const MAX_CONTEXT_SIZE = 16384;
|
|
8
|
+
|
|
9
|
+
export function contextUpdate(options: {
|
|
10
|
+
approach?: string;
|
|
11
|
+
pattern?: string;
|
|
12
|
+
constraint?: string;
|
|
13
|
+
}): void {
|
|
14
|
+
initSchema();
|
|
15
|
+
const db = getDb();
|
|
16
|
+
|
|
17
|
+
const spec = db
|
|
18
|
+
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
19
|
+
.get() as any;
|
|
20
|
+
|
|
21
|
+
if (!spec) {
|
|
22
|
+
console.error("\nNenhuma feature ativa.\n");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Verificar se está na fase de implementação
|
|
27
|
+
if (spec.phase !== "implementing") {
|
|
28
|
+
console.error(`\nContexto só pode ser atualizado na fase 'implementing'.`);
|
|
29
|
+
console.error(`Fase atual: ${spec.phase}\n`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
|
|
34
|
+
const now = new Date().toISOString();
|
|
35
|
+
|
|
36
|
+
let updated = false;
|
|
37
|
+
|
|
38
|
+
if (options.approach) {
|
|
39
|
+
// Append ao approach existente, não substitui
|
|
40
|
+
const currentApproach = context?.approach || "";
|
|
41
|
+
const newApproach = currentApproach
|
|
42
|
+
? `${currentApproach}\n- ${options.approach}`
|
|
43
|
+
: options.approach;
|
|
44
|
+
|
|
45
|
+
db.run("UPDATE context SET approach = ?, updated_at = ? WHERE spec_id = ?", [
|
|
46
|
+
newApproach,
|
|
47
|
+
now,
|
|
48
|
+
spec.id,
|
|
49
|
+
]);
|
|
50
|
+
console.log(`\n✓ Approach atualizado`);
|
|
51
|
+
updated = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (options.pattern) {
|
|
55
|
+
// Adiciona ao array de patterns
|
|
56
|
+
const currentPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
|
|
57
|
+
currentPatterns.push({
|
|
58
|
+
pattern: options.pattern,
|
|
59
|
+
detected_at: now,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
db.run("UPDATE context SET patterns = ?, updated_at = ? WHERE spec_id = ?", [
|
|
63
|
+
JSON.stringify(currentPatterns),
|
|
64
|
+
now,
|
|
65
|
+
spec.id,
|
|
66
|
+
]);
|
|
67
|
+
console.log(`✓ Pattern adicionado (total: ${currentPatterns.length})`);
|
|
68
|
+
updated = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (options.constraint) {
|
|
72
|
+
// Adiciona ao array de constraints
|
|
73
|
+
const currentConstraints = context?.constraints ? JSON.parse(context.constraints) : [];
|
|
74
|
+
currentConstraints.push({
|
|
75
|
+
constraint: options.constraint,
|
|
76
|
+
added_at: now,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
db.run("UPDATE context SET constraints = ?, updated_at = ? WHERE spec_id = ?", [
|
|
80
|
+
JSON.stringify(currentConstraints),
|
|
81
|
+
now,
|
|
82
|
+
spec.id,
|
|
83
|
+
]);
|
|
84
|
+
console.log(`✓ Constraint adicionado (total: ${currentConstraints.length})`);
|
|
85
|
+
updated = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!updated) {
|
|
89
|
+
console.log("\nNenhuma opcao fornecida.");
|
|
90
|
+
console.log("Use:");
|
|
91
|
+
console.log(" --approach 'Nova abordagem descoberta'");
|
|
92
|
+
console.log(" --pattern 'Padrao identificado no codigo'");
|
|
93
|
+
console.log(" --constraint 'Nova limitacao encontrada'\n");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(`\nContexto atualizado para: ${spec.name}\n`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function status(json: boolean = false): void {
|
|
101
|
+
initSchema();
|
|
102
|
+
|
|
103
|
+
const db = getDb();
|
|
104
|
+
|
|
105
|
+
const spec = db
|
|
106
|
+
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
107
|
+
.get() as any;
|
|
108
|
+
|
|
109
|
+
if (!spec) {
|
|
110
|
+
if (json) {
|
|
111
|
+
console.log(JSON.stringify({ active: false, message: "Nenhuma feature ativa" }));
|
|
112
|
+
} else {
|
|
113
|
+
console.log("\nNenhuma feature ativa.");
|
|
114
|
+
console.log("Inicie com: /codexa:feature\n");
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
|
|
120
|
+
const tasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
|
|
121
|
+
const decisions = db.query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'").all(spec.id) as any[];
|
|
122
|
+
|
|
123
|
+
const tasksDone = tasks.filter((t) => t.status === "done").length;
|
|
124
|
+
const tasksRunning = tasks.filter((t) => t.status === "running").length;
|
|
125
|
+
const tasksPending = tasks.filter((t) => t.status === "pending").length;
|
|
126
|
+
|
|
127
|
+
const progress = tasks.length > 0 ? Math.round((tasksDone / tasks.length) * 100) : 0;
|
|
128
|
+
|
|
129
|
+
if (json) {
|
|
130
|
+
console.log(
|
|
131
|
+
JSON.stringify({
|
|
132
|
+
active: true,
|
|
133
|
+
spec,
|
|
134
|
+
context,
|
|
135
|
+
progress: {
|
|
136
|
+
done: tasksDone,
|
|
137
|
+
running: tasksRunning,
|
|
138
|
+
pending: tasksPending,
|
|
139
|
+
total: tasks.length,
|
|
140
|
+
percent: progress,
|
|
141
|
+
},
|
|
142
|
+
decisions: decisions.length,
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(`\n${"=".repeat(60)}`);
|
|
149
|
+
console.log(`STATUS: ${spec.name}`);
|
|
150
|
+
console.log(`${"=".repeat(60)}`);
|
|
151
|
+
console.log(`\nSpec ID: ${spec.id}`);
|
|
152
|
+
console.log(`Fase: ${spec.phase}`);
|
|
153
|
+
console.log(`Aprovado: ${spec.approved_at ? "Sim" : "Nao"}`);
|
|
154
|
+
|
|
155
|
+
console.log(`\nProgresso:`);
|
|
156
|
+
const bar = "█".repeat(Math.floor(progress / 5)) + "░".repeat(20 - Math.floor(progress / 5));
|
|
157
|
+
console.log(` [${bar}] ${progress}%`);
|
|
158
|
+
console.log(` Concluidas: ${tasksDone}/${tasks.length}`);
|
|
159
|
+
console.log(` Em execucao: ${tasksRunning}`);
|
|
160
|
+
console.log(` Pendentes: ${tasksPending}`);
|
|
161
|
+
|
|
162
|
+
if (context?.last_checkpoint) {
|
|
163
|
+
console.log(`\nUltimo checkpoint:`);
|
|
164
|
+
console.log(` ${context.last_checkpoint}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (decisions.length > 0) {
|
|
168
|
+
console.log(`\nDecisoes ativas: ${decisions.length}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// v8.1: Mostrar ultimo session summary para continuidade
|
|
172
|
+
const lastSession = getLastSessionSummary(spec.id);
|
|
173
|
+
if (lastSession) {
|
|
174
|
+
console.log(`\nUltima sessao:`);
|
|
175
|
+
console.log(` ${lastSession.summary}`);
|
|
176
|
+
if (lastSession.next_steps) {
|
|
177
|
+
try {
|
|
178
|
+
const nextSteps = JSON.parse(lastSession.next_steps);
|
|
179
|
+
if (nextSteps.length > 0) {
|
|
180
|
+
console.log(` Proximos passos sugeridos:`);
|
|
181
|
+
for (const step of nextSteps.slice(0, 3)) {
|
|
182
|
+
console.log(` - ${step}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch { /* ignore */ }
|
|
186
|
+
}
|
|
187
|
+
if (lastSession.blockers) {
|
|
188
|
+
try {
|
|
189
|
+
const blockers = JSON.parse(lastSession.blockers);
|
|
190
|
+
if (blockers.length > 0) {
|
|
191
|
+
console.log(` Blockers pendentes:`);
|
|
192
|
+
for (const b of blockers) {
|
|
193
|
+
console.log(` [!] ${b}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch { /* ignore */ }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log(`\n${"─".repeat(60)}`);
|
|
201
|
+
|
|
202
|
+
// Sugerir proximo passo
|
|
203
|
+
switch (spec.phase) {
|
|
204
|
+
case "planning":
|
|
205
|
+
console.log(`Proximo: plan task-add ou check request`);
|
|
206
|
+
break;
|
|
207
|
+
case "checking":
|
|
208
|
+
console.log(`Proximo: check approve ou check reject`);
|
|
209
|
+
break;
|
|
210
|
+
case "implementing":
|
|
211
|
+
console.log(`Proximo: task next`);
|
|
212
|
+
break;
|
|
213
|
+
case "reviewing":
|
|
214
|
+
console.log(`Proximo: review approve`);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
console.log();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function contextExport(options: { task?: string; json?: boolean; }): void {
|
|
221
|
+
initSchema();
|
|
222
|
+
|
|
223
|
+
const db = getDb();
|
|
224
|
+
|
|
225
|
+
const spec = db
|
|
226
|
+
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
227
|
+
.get() as any;
|
|
228
|
+
|
|
229
|
+
if (!spec) {
|
|
230
|
+
console.error("\nNenhuma feature ativa.\n");
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
|
|
235
|
+
const decisions = db
|
|
236
|
+
.query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
|
|
237
|
+
.all(spec.id) as any[];
|
|
238
|
+
const artifacts = db.query("SELECT * FROM artifacts WHERE spec_id = ?").all(spec.id) as any[];
|
|
239
|
+
const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
|
|
240
|
+
|
|
241
|
+
let taskContext = null;
|
|
242
|
+
let standards: any[] = [];
|
|
243
|
+
let knowledge: any[] = [];
|
|
244
|
+
let libContexts: string[] = [];
|
|
245
|
+
|
|
246
|
+
if (options.task) {
|
|
247
|
+
const taskId = parseInt(options.task);
|
|
248
|
+
const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
|
|
249
|
+
if (task) {
|
|
250
|
+
taskContext = {
|
|
251
|
+
id: task.id,
|
|
252
|
+
number: task.number,
|
|
253
|
+
name: task.name,
|
|
254
|
+
agent: task.agent,
|
|
255
|
+
files: task.files ? JSON.parse(task.files) : [],
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Obter standards para o agente da task
|
|
259
|
+
const domain = task.agent?.split("-")[0] || "all";
|
|
260
|
+
standards = db
|
|
261
|
+
.query(
|
|
262
|
+
`SELECT * FROM standards
|
|
263
|
+
WHERE scope = 'all' OR scope = ?
|
|
264
|
+
ORDER BY enforcement DESC, category`
|
|
265
|
+
)
|
|
266
|
+
.all(domain) as any[];
|
|
267
|
+
|
|
268
|
+
standards = standards.map((std) => ({
|
|
269
|
+
category: std.category,
|
|
270
|
+
rule: std.rule,
|
|
271
|
+
examples: std.examples ? JSON.parse(std.examples) : [],
|
|
272
|
+
anti_examples: std.anti_examples ? JSON.parse(std.anti_examples) : [],
|
|
273
|
+
enforcement: std.enforcement,
|
|
274
|
+
}));
|
|
275
|
+
|
|
276
|
+
// Obter knowledge broadcast (automatico)
|
|
277
|
+
knowledge = getKnowledgeForTask(spec.id, taskId).map((k) => ({
|
|
278
|
+
id: k.id,
|
|
279
|
+
category: k.category,
|
|
280
|
+
content: k.content,
|
|
281
|
+
severity: k.severity,
|
|
282
|
+
origin_task: k.task_origin,
|
|
283
|
+
created_at: k.created_at,
|
|
284
|
+
}));
|
|
285
|
+
|
|
286
|
+
// Obter lib-contexts para o agente da task
|
|
287
|
+
if (task.agent) {
|
|
288
|
+
libContexts = getLibContextsForAgent(task.agent);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Obter contexto de produto
|
|
294
|
+
const productContext = db.query("SELECT * FROM product_context WHERE id = 'default'").get() as any;
|
|
295
|
+
const productGoals = db.query("SELECT * FROM product_goals WHERE product_id = 'default'").all() as any[];
|
|
296
|
+
const productFeatures = db.query("SELECT * FROM product_features WHERE product_id = 'default'").all() as any[];
|
|
297
|
+
|
|
298
|
+
const exportData = {
|
|
299
|
+
spec: {
|
|
300
|
+
id: spec.id,
|
|
301
|
+
name: spec.name,
|
|
302
|
+
phase: spec.phase,
|
|
303
|
+
},
|
|
304
|
+
project: project ? {
|
|
305
|
+
stack: JSON.parse(project.stack),
|
|
306
|
+
} : null,
|
|
307
|
+
productContext: productContext ? {
|
|
308
|
+
name: productContext.name,
|
|
309
|
+
problem: productContext.problem,
|
|
310
|
+
solution: productContext.solution,
|
|
311
|
+
targetUsers: productContext.target_users,
|
|
312
|
+
valueProposition: productContext.value_proposition,
|
|
313
|
+
outOfScope: productContext.out_of_scope ? JSON.parse(productContext.out_of_scope) : [],
|
|
314
|
+
constraints: productContext.constraints ? JSON.parse(productContext.constraints) : [],
|
|
315
|
+
} : null,
|
|
316
|
+
productGoals: productGoals.map((g) => ({
|
|
317
|
+
category: g.category,
|
|
318
|
+
goal: g.goal,
|
|
319
|
+
priority: g.priority,
|
|
320
|
+
})),
|
|
321
|
+
productFeatures: productFeatures.map((f) => ({
|
|
322
|
+
name: f.name,
|
|
323
|
+
description: f.description,
|
|
324
|
+
priority: f.priority,
|
|
325
|
+
})),
|
|
326
|
+
objective: context?.objective,
|
|
327
|
+
approach: context?.approach,
|
|
328
|
+
patterns: context?.patterns ? JSON.parse(context.patterns) : [],
|
|
329
|
+
constraints: context?.constraints ? JSON.parse(context.constraints) : [],
|
|
330
|
+
decisions: decisions.map((d) => ({
|
|
331
|
+
id: d.id,
|
|
332
|
+
title: d.title,
|
|
333
|
+
decision: d.decision,
|
|
334
|
+
})),
|
|
335
|
+
artifacts: artifacts.map((a) => a.path),
|
|
336
|
+
task: taskContext,
|
|
337
|
+
standards: standards,
|
|
338
|
+
knowledge: knowledge,
|
|
339
|
+
libContexts: libContexts,
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
console.log(JSON.stringify(exportData, null, 2));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Funcao helper para formatar contexto para subagent (uso interno)
|
|
346
|
+
// v8.1: Contexto COMPLETO mas ESTRUTURADO - todas as fontes relevantes incluidas
|
|
347
|
+
export function getContextForSubagent(taskId: number): string {
|
|
348
|
+
initSchema();
|
|
349
|
+
|
|
350
|
+
const db = getDb();
|
|
351
|
+
|
|
352
|
+
const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
|
|
353
|
+
if (!task) {
|
|
354
|
+
return "ERRO: Task nao encontrada";
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
|
|
358
|
+
const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(task.spec_id) as any;
|
|
359
|
+
const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
|
|
360
|
+
|
|
361
|
+
// v8.4: Analise arquitetural (link explicito via analysis_id ou nome)
|
|
362
|
+
const archAnalysis = getArchitecturalAnalysisForSpec(spec.name, task.spec_id);
|
|
363
|
+
|
|
364
|
+
// Arquivos da task para filtrar contexto relevante
|
|
365
|
+
const taskFiles = task.files ? JSON.parse(task.files) : [];
|
|
366
|
+
const domain = task.agent?.split("-")[0] || "all";
|
|
367
|
+
|
|
368
|
+
// Decisoes relevantes (max 8, priorizando as que mencionam arquivos da task)
|
|
369
|
+
const allDecisions = db
|
|
370
|
+
.query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active' ORDER BY created_at DESC")
|
|
371
|
+
.all(task.spec_id) as any[];
|
|
372
|
+
const decisions = filterRelevantDecisions(allDecisions, taskFiles, 8);
|
|
373
|
+
|
|
374
|
+
// Standards required + recommended que se aplicam aos arquivos
|
|
375
|
+
const standards = db
|
|
376
|
+
.query(
|
|
377
|
+
`SELECT * FROM standards
|
|
378
|
+
WHERE (scope = 'all' OR scope = ?)
|
|
379
|
+
ORDER BY enforcement DESC, category`
|
|
380
|
+
)
|
|
381
|
+
.all(domain) as any[];
|
|
382
|
+
const relevantStandards = filterRelevantStandards(standards, taskFiles);
|
|
383
|
+
|
|
384
|
+
// v8.3: Knowledge com TTL, caps e indicadores de truncamento
|
|
385
|
+
const allKnowledge = getKnowledgeForTask(task.spec_id, taskId);
|
|
386
|
+
const now48h = new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString();
|
|
387
|
+
const recentKnowledge = allKnowledge.filter((k: any) => k.created_at > now48h);
|
|
388
|
+
const staleKnowledge = allKnowledge.filter((k: any) => k.created_at <= now48h);
|
|
389
|
+
// Conhecimento recente primeiro; stale so se critical
|
|
390
|
+
const effectiveKnowledge = [
|
|
391
|
+
...recentKnowledge,
|
|
392
|
+
...staleKnowledge.filter((k: any) => k.severity === 'critical'),
|
|
393
|
+
];
|
|
394
|
+
const allCriticalKnowledge = effectiveKnowledge.filter((k: any) => k.severity === 'critical' || k.severity === 'warning');
|
|
395
|
+
const criticalKnowledge = allCriticalKnowledge.slice(0, 20);
|
|
396
|
+
const truncatedCritical = allCriticalKnowledge.length - criticalKnowledge.length;
|
|
397
|
+
const allInfoKnowledge = effectiveKnowledge.filter((k: any) => k.severity === 'info');
|
|
398
|
+
const infoKnowledge = allInfoKnowledge.slice(0, 10);
|
|
399
|
+
const truncatedInfo = allInfoKnowledge.length - infoKnowledge.length;
|
|
400
|
+
|
|
401
|
+
// v8.1: Contexto de produto (resumido - problema, usuarios, restricoes)
|
|
402
|
+
const productContext = db.query("SELECT * FROM product_context WHERE id = 'default'").get() as any;
|
|
403
|
+
|
|
404
|
+
// v8.1: Patterns de implementacao matchados aos arquivos da task
|
|
405
|
+
const patterns = getPatternsForFiles(taskFiles);
|
|
406
|
+
|
|
407
|
+
// v8.2: Reasoning de tasks dependentes + todas tasks completas recentes
|
|
408
|
+
const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
|
|
409
|
+
const depReasoning: any[] = [];
|
|
410
|
+
const seenTaskIds = new Set<number>();
|
|
411
|
+
|
|
412
|
+
// 1. Reasoning de dependencias diretas (prioridade maxima)
|
|
413
|
+
if (dependsOn.length > 0) {
|
|
414
|
+
const placeholders = dependsOn.map(() => '?').join(',');
|
|
415
|
+
const depTasks = db.query(
|
|
416
|
+
`SELECT id, number FROM tasks WHERE spec_id = ? AND number IN (${placeholders})`
|
|
417
|
+
).all(task.spec_id, ...dependsOn) as any[];
|
|
418
|
+
|
|
419
|
+
for (const depTask of depTasks) {
|
|
420
|
+
seenTaskIds.add(depTask.id);
|
|
421
|
+
const reasoning = db.query(
|
|
422
|
+
`SELECT category, thought FROM reasoning_log
|
|
423
|
+
WHERE spec_id = ? AND task_id = ?
|
|
424
|
+
AND category IN ('recommendation', 'decision', 'challenge')
|
|
425
|
+
ORDER BY
|
|
426
|
+
CASE importance WHEN 'critical' THEN 1 WHEN 'high' THEN 2 ELSE 3 END,
|
|
427
|
+
created_at DESC
|
|
428
|
+
LIMIT 3`
|
|
429
|
+
).all(task.spec_id, depTask.id) as any[];
|
|
430
|
+
for (const r of reasoning) {
|
|
431
|
+
depReasoning.push({ ...r, fromTask: depTask.number });
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 2. Reasoning de TODAS tasks completas (recommendations e challenges de alta importancia)
|
|
437
|
+
const completedTasks = db.query(
|
|
438
|
+
`SELECT t.id, t.number FROM tasks t
|
|
439
|
+
WHERE t.spec_id = ? AND t.status = 'done' AND t.id != ?
|
|
440
|
+
ORDER BY t.completed_at DESC LIMIT 10`
|
|
441
|
+
).all(task.spec_id, taskId) as any[];
|
|
442
|
+
|
|
443
|
+
for (const ct of completedTasks) {
|
|
444
|
+
if (seenTaskIds.has(ct.id)) continue;
|
|
445
|
+
const reasoning = db.query(
|
|
446
|
+
`SELECT category, thought FROM reasoning_log
|
|
447
|
+
WHERE spec_id = ? AND task_id = ?
|
|
448
|
+
AND category IN ('recommendation', 'challenge')
|
|
449
|
+
AND importance IN ('critical', 'high')
|
|
450
|
+
ORDER BY
|
|
451
|
+
CASE importance WHEN 'critical' THEN 1 ELSE 2 END,
|
|
452
|
+
created_at DESC
|
|
453
|
+
LIMIT 2`
|
|
454
|
+
).all(task.spec_id, ct.id) as any[];
|
|
455
|
+
for (const r of reasoning) {
|
|
456
|
+
depReasoning.push({ ...r, fromTask: ct.number });
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// v8.1: Lib contexts do projeto (resumido - nomes e versoes)
|
|
461
|
+
const libContexts = db.query(
|
|
462
|
+
"SELECT lib_name, version FROM lib_contexts ORDER BY lib_name LIMIT 10"
|
|
463
|
+
).all() as any[];
|
|
464
|
+
|
|
465
|
+
// v8.3: Sessoes compostas para continuidade cross-session (todas, nao so ultima)
|
|
466
|
+
const sessions = getSessionSummaries(task.spec_id, 5);
|
|
467
|
+
|
|
468
|
+
// v8.2: Knowledge graph - decisoes que afetam arquivos da task
|
|
469
|
+
const graphDecisions: any[] = [];
|
|
470
|
+
for (const file of taskFiles) {
|
|
471
|
+
try {
|
|
472
|
+
const related = getRelatedDecisions(file, "file");
|
|
473
|
+
for (const d of related) {
|
|
474
|
+
if (!graphDecisions.find((gd: any) => gd.id === d.id)) {
|
|
475
|
+
graphDecisions.push(d);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} catch { /* ignore if graph empty */ }
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// v8.2: Deteccao de contradicoes
|
|
482
|
+
let contradictions: any[] = [];
|
|
483
|
+
try {
|
|
484
|
+
contradictions = findContradictions(task.spec_id);
|
|
485
|
+
} catch { /* ignore if no contradictions */ }
|
|
486
|
+
|
|
487
|
+
// v8.2: Patterns descobertos por subagents anteriores
|
|
488
|
+
const discoveredPatterns = context?.patterns ? JSON.parse(context.patterns) : [];
|
|
489
|
+
|
|
490
|
+
// ===== MONTAR OUTPUT ESTRUTURADO =====
|
|
491
|
+
|
|
492
|
+
let output = `## CONTEXTO (Task #${task.number})
|
|
493
|
+
|
|
494
|
+
**Feature:** ${spec.name}
|
|
495
|
+
**Objetivo:** ${context?.objective || "N/A"}
|
|
496
|
+
**Arquivos:** ${taskFiles.join(", ") || "Nenhum especificado"}
|
|
497
|
+
`;
|
|
498
|
+
|
|
499
|
+
// Contexto de produto
|
|
500
|
+
if (productContext) {
|
|
501
|
+
output += `
|
|
502
|
+
### PRODUTO
|
|
503
|
+
- **Problema:** ${productContext.problem || "N/A"}
|
|
504
|
+
- **Usuarios:** ${productContext.target_users || "N/A"}`;
|
|
505
|
+
if (productContext.constraints) {
|
|
506
|
+
try {
|
|
507
|
+
const constraints = JSON.parse(productContext.constraints);
|
|
508
|
+
if (constraints.length > 0) {
|
|
509
|
+
output += `\n- **Restricoes:** ${constraints.slice(0, 3).join("; ")}${constraints.length > 3 ? ` [+${constraints.length - 3} mais]` : ''}`;
|
|
510
|
+
}
|
|
511
|
+
} catch { /* ignore parse errors */ }
|
|
512
|
+
}
|
|
513
|
+
output += "\n";
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// v8.3: Secao ARQUITETURA (Fase 3 - Architect Pipeline)
|
|
517
|
+
if (archAnalysis) {
|
|
518
|
+
output += `
|
|
519
|
+
### ARQUITETURA (${archAnalysis.id})`;
|
|
520
|
+
if (archAnalysis.approach) {
|
|
521
|
+
const approachPreview = archAnalysis.approach.length > 500
|
|
522
|
+
? archAnalysis.approach.substring(0, 500) + "..."
|
|
523
|
+
: archAnalysis.approach;
|
|
524
|
+
output += `\n**Abordagem:** ${approachPreview}`;
|
|
525
|
+
}
|
|
526
|
+
if (archAnalysis.risks) {
|
|
527
|
+
try {
|
|
528
|
+
const risks = JSON.parse(archAnalysis.risks);
|
|
529
|
+
if (risks.length > 0) {
|
|
530
|
+
const highRisks = risks.filter((r: any) => r.impact === 'high' || r.probability === 'high');
|
|
531
|
+
const risksToShow = highRisks.length > 0 ? highRisks.slice(0, 3) : risks.slice(0, 3);
|
|
532
|
+
output += `\n**Riscos:**`;
|
|
533
|
+
for (const r of risksToShow) {
|
|
534
|
+
output += `\n - ${r.description} (${r.probability}/${r.impact}) -> ${r.mitigation}`;
|
|
535
|
+
}
|
|
536
|
+
if (risks.length > risksToShow.length) {
|
|
537
|
+
output += `\n [+${risks.length - risksToShow.length} mais riscos]`;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} catch { /* ignore */ }
|
|
541
|
+
}
|
|
542
|
+
if (archAnalysis.decisions) {
|
|
543
|
+
try {
|
|
544
|
+
const archDecisions = JSON.parse(archAnalysis.decisions);
|
|
545
|
+
if (archDecisions.length > 0) {
|
|
546
|
+
output += `\n**Decisoes arquiteturais:**`;
|
|
547
|
+
for (const d of archDecisions.slice(0, 5)) {
|
|
548
|
+
output += `\n - ${d.decision}: ${d.rationale || ''}`;
|
|
549
|
+
}
|
|
550
|
+
if (archDecisions.length > 5) {
|
|
551
|
+
output += `\n [+${archDecisions.length - 5} mais]`;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
} catch { /* ignore */ }
|
|
555
|
+
}
|
|
556
|
+
output += "\n";
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Standards
|
|
560
|
+
const requiredStds = relevantStandards.filter((s: any) => s.enforcement === 'required');
|
|
561
|
+
const recommendedStds = relevantStandards.filter((s: any) => s.enforcement === 'recommended');
|
|
562
|
+
|
|
563
|
+
output += `
|
|
564
|
+
### STANDARDS (${relevantStandards.length})`;
|
|
565
|
+
if (requiredStds.length > 0) {
|
|
566
|
+
output += `\n**Obrigatorios:**\n${requiredStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
|
|
567
|
+
}
|
|
568
|
+
if (recommendedStds.length > 0) {
|
|
569
|
+
output += `\n**Recomendados:**\n${recommendedStds.map((s: any) => `- ${s.rule}`).join("\n")}`;
|
|
570
|
+
}
|
|
571
|
+
if (relevantStandards.length === 0) {
|
|
572
|
+
output += "\nNenhum standard aplicavel";
|
|
573
|
+
}
|
|
574
|
+
output += "\n";
|
|
575
|
+
|
|
576
|
+
// v8.3: Decisoes com indicador de truncamento
|
|
577
|
+
const truncatedDecisions = allDecisions.length - decisions.length;
|
|
578
|
+
output += `
|
|
579
|
+
### DECISOES (${decisions.length}${truncatedDecisions > 0 ? ` [+${truncatedDecisions} mais - use: decisions list]` : ''})
|
|
580
|
+
${decisions.length > 0 ? decisions.map((d) => `- **${d.title}**: ${d.decision}`).join("\n") : "Nenhuma"}
|
|
581
|
+
`;
|
|
582
|
+
|
|
583
|
+
// Reasoning de tasks anteriores
|
|
584
|
+
if (depReasoning.length > 0) {
|
|
585
|
+
output += `
|
|
586
|
+
### CONTEXTO DE TASKS ANTERIORES
|
|
587
|
+
${depReasoning.map((r: any) => `- [Task #${r.fromTask}/${r.category}] ${r.thought}`).join("\n")}
|
|
588
|
+
`;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// v8.3: Alertas com cap de 20 e indicador de truncamento
|
|
592
|
+
if (criticalKnowledge.length > 0) {
|
|
593
|
+
const severityIcon: Record<string, string> = { warning: "!!", critical: "XX" };
|
|
594
|
+
output += `
|
|
595
|
+
### ALERTAS (${criticalKnowledge.length}${truncatedCritical > 0 ? ` [+${truncatedCritical} mais - use: knowledge list --severity warning]` : ''})
|
|
596
|
+
${criticalKnowledge.map((k: any) => `[${severityIcon[k.severity] || "!"}] ${k.content}`).join("\n")}
|
|
597
|
+
`;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// v8.3: Discoveries com indicador de truncamento
|
|
601
|
+
if (infoKnowledge.length > 0) {
|
|
602
|
+
output += `
|
|
603
|
+
### DISCOVERIES DE TASKS ANTERIORES (${infoKnowledge.length}${truncatedInfo > 0 ? ` [+${truncatedInfo} mais - use: knowledge list]` : ''})
|
|
604
|
+
${infoKnowledge.map((k: any) => `- ${k.content}`).join("\n")}
|
|
605
|
+
`;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Patterns de implementacao matchados
|
|
609
|
+
if (patterns.length > 0) {
|
|
610
|
+
output += `
|
|
611
|
+
### PATTERNS DO PROJETO (${patterns.length})
|
|
612
|
+
${patterns.map((p: any) => {
|
|
613
|
+
const tmpl = p.template || "";
|
|
614
|
+
const preview = tmpl.length > 200 ? tmpl.substring(0, 200) + `... [truncado de ${tmpl.length} chars]` : tmpl;
|
|
615
|
+
return `- **${p.name}** (${p.category}): ${preview || p.description || "Sem template"}`;
|
|
616
|
+
}).join("\n")}
|
|
617
|
+
`;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// v8.5: Utility Map (DRY prevention - Camada 1)
|
|
621
|
+
try {
|
|
622
|
+
const taskDirs = [...new Set(taskFiles.map((f: string) => {
|
|
623
|
+
const parts = f.replace(/\\/g, "/").split("/");
|
|
624
|
+
return parts.slice(0, -1).join("/");
|
|
625
|
+
}).filter(Boolean))];
|
|
626
|
+
|
|
627
|
+
const agentScope = task.agent?.split("-")[0] || undefined;
|
|
628
|
+
let relevantUtilities = getUtilitiesForContext(taskDirs, undefined, 15);
|
|
629
|
+
|
|
630
|
+
// Se poucos resultados por diretorio, ampliar para scope do agent
|
|
631
|
+
if (relevantUtilities.length < 5 && agentScope) {
|
|
632
|
+
const scopeUtils = getUtilitiesForContext([], agentScope, 15);
|
|
633
|
+
const existingKeys = new Set(relevantUtilities.map((u: any) => `${u.file_path}:${u.utility_name}`));
|
|
634
|
+
for (const u of scopeUtils) {
|
|
635
|
+
if (!existingKeys.has(`${u.file_path}:${u.utility_name}`)) {
|
|
636
|
+
relevantUtilities.push(u);
|
|
637
|
+
}
|
|
638
|
+
if (relevantUtilities.length >= 15) break;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (relevantUtilities.length > 0) {
|
|
643
|
+
const totalCount = (db.query("SELECT COUNT(*) as c FROM project_utilities").get() as any)?.c || 0;
|
|
644
|
+
const truncated = totalCount - relevantUtilities.length;
|
|
645
|
+
output += `
|
|
646
|
+
### UTILITIES EXISTENTES (${relevantUtilities.length}${truncated > 0 ? ` [+${truncated} mais]` : ''})
|
|
647
|
+
**REGRA DRY**: Reutilize ao inves de recriar. Importe do arquivo existente.
|
|
648
|
+
${relevantUtilities.map((u: any) => {
|
|
649
|
+
const sig = u.signature ? ` ${u.signature}` : '';
|
|
650
|
+
return `- **${u.utility_name}** [${u.utility_type}]${sig} <- \`${u.file_path}\``;
|
|
651
|
+
}).join("\n")}
|
|
652
|
+
`;
|
|
653
|
+
}
|
|
654
|
+
} catch { /* tabela pode nao existir ainda */ }
|
|
655
|
+
|
|
656
|
+
// v8.3: Sessoes compostas (todas, nao so ultima)
|
|
657
|
+
if (sessions.length > 0) {
|
|
658
|
+
output += `
|
|
659
|
+
### HISTORICO DE SESSOES (${sessions.length})`;
|
|
660
|
+
// Sessao mais recente: detalhes completos
|
|
661
|
+
const latest = sessions[0];
|
|
662
|
+
output += `\n**Ultima:** ${latest.summary}`;
|
|
663
|
+
if (latest.next_steps) {
|
|
664
|
+
try {
|
|
665
|
+
const steps = JSON.parse(latest.next_steps);
|
|
666
|
+
if (steps.length > 0) {
|
|
667
|
+
output += `\n**Proximos passos:** ${steps.slice(0, 3).join("; ")}${steps.length > 3 ? ` [+${steps.length - 3} mais]` : ''}`;
|
|
668
|
+
}
|
|
669
|
+
} catch { /* ignore */ }
|
|
670
|
+
}
|
|
671
|
+
if (latest.blockers) {
|
|
672
|
+
try {
|
|
673
|
+
const blockers = JSON.parse(latest.blockers);
|
|
674
|
+
if (blockers.length > 0) {
|
|
675
|
+
output += `\n**Blockers:** ${blockers.join("; ")}`;
|
|
676
|
+
}
|
|
677
|
+
} catch { /* ignore */ }
|
|
678
|
+
}
|
|
679
|
+
// Sessoes anteriores: resumo de 1 linha
|
|
680
|
+
if (sessions.length > 1) {
|
|
681
|
+
output += `\n**Sessoes anteriores:**`;
|
|
682
|
+
for (const s of sessions.slice(1)) {
|
|
683
|
+
output += `\n - ${s.summary.substring(0, 100)}${s.summary.length > 100 ? '...' : ''} (${s.tasks_completed || 0} tasks)`;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
output += "\n";
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Decisoes do knowledge graph que afetam arquivos desta task
|
|
690
|
+
if (graphDecisions.length > 0) {
|
|
691
|
+
output += `
|
|
692
|
+
### DECISOES QUE AFETAM SEUS ARQUIVOS (${graphDecisions.length})
|
|
693
|
+
${graphDecisions.map((d: any) => `- **${d.title}**: ${d.decision}`).join("\n")}
|
|
694
|
+
`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Contradicoes detectadas
|
|
698
|
+
if (contradictions.length > 0) {
|
|
699
|
+
output += `
|
|
700
|
+
### [!!] CONTRADICOES DETECTADAS (${contradictions.length})
|
|
701
|
+
${contradictions.map((c: any) => `- "${c.decision1}" <-> "${c.decision2}"`).join("\n")}
|
|
702
|
+
Verifique estas contradicoes antes de prosseguir.
|
|
703
|
+
`;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// v8.3: Patterns descobertos com indicador de truncamento
|
|
707
|
+
if (discoveredPatterns.length > 0) {
|
|
708
|
+
const patternsToShow = discoveredPatterns.slice(-10);
|
|
709
|
+
output += `
|
|
710
|
+
### PATTERNS DESCOBERTOS (${discoveredPatterns.length})
|
|
711
|
+
${discoveredPatterns.length > 10 ? `[mostrando ultimos 10 de ${discoveredPatterns.length}]\n` : ''}${patternsToShow.map((p: any) => `- ${p.pattern}${p.source_task ? ` (Task #${p.source_task})` : ""}`).join("\n")}
|
|
712
|
+
`;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Stack
|
|
716
|
+
if (project) {
|
|
717
|
+
const stack = JSON.parse(project.stack);
|
|
718
|
+
const allStackEntries = Object.entries(stack);
|
|
719
|
+
const mainStack = allStackEntries.slice(0, 6);
|
|
720
|
+
output += `
|
|
721
|
+
### STACK
|
|
722
|
+
${mainStack.map(([k, v]) => `${k}: ${v}`).join(" | ")}${allStackEntries.length > 6 ? ` [+${allStackEntries.length - 6} mais]` : ''}
|
|
723
|
+
`;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Bibliotecas do projeto
|
|
727
|
+
if (libContexts.length > 0) {
|
|
728
|
+
output += `
|
|
729
|
+
### BIBLIOTECAS
|
|
730
|
+
${libContexts.map((l: any) => `- ${l.lib_name}${l.version ? ` v${l.version}` : ""}`).join("\n")}
|
|
731
|
+
`;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Protocolo de retorno
|
|
735
|
+
output += `
|
|
736
|
+
### RETORNO OBRIGATORIO
|
|
737
|
+
\`\`\`json
|
|
738
|
+
{"status": "completed|blocked", "summary": "...", "files_created": [], "files_modified": [], "reasoning": {"approach": "como abordou", "challenges": [], "recommendations": "para proximas tasks"}}
|
|
739
|
+
\`\`\`
|
|
740
|
+
`;
|
|
741
|
+
|
|
742
|
+
// v8.3: Overall size cap com truncamento inteligente por secao
|
|
743
|
+
if (output.length > MAX_CONTEXT_SIZE) {
|
|
744
|
+
const sections = output.split('\n### ');
|
|
745
|
+
let trimmed = sections[0]; // Sempre manter header
|
|
746
|
+
|
|
747
|
+
for (let i = 1; i < sections.length; i++) {
|
|
748
|
+
const candidate = trimmed + '\n### ' + sections[i];
|
|
749
|
+
if (candidate.length > MAX_CONTEXT_SIZE - 200) {
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
trimmed = candidate;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const omittedSections = sections.length - trimmed.split('\n### ').length;
|
|
756
|
+
if (omittedSections > 0) {
|
|
757
|
+
trimmed += `\n\n[CONTEXTO TRUNCADO: ${omittedSections} secao(oes) omitida(s) por limite de ${MAX_CONTEXT_SIZE} chars. Use: context-export para contexto completo]`;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
output = trimmed;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return output;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// v8.3: Filtrar decisoes relevantes com scoring melhorado
|
|
767
|
+
function filterRelevantDecisions(decisions: any[], taskFiles: string[], maxCount: number): any[] {
|
|
768
|
+
if (decisions.length === 0) return [];
|
|
769
|
+
if (taskFiles.length === 0) return decisions.slice(0, maxCount);
|
|
770
|
+
|
|
771
|
+
// Extrair keywords semanticas dos arquivos da task
|
|
772
|
+
const fileExtensions = new Set(taskFiles.map(f => f.split('.').pop()?.toLowerCase()).filter(Boolean));
|
|
773
|
+
const fileDirs = new Set(taskFiles.flatMap(f => f.split('/').slice(0, -1)).filter(Boolean));
|
|
774
|
+
|
|
775
|
+
// Keywords de dominio para scoring contextual
|
|
776
|
+
const domainKeywords: Record<string, string[]> = {
|
|
777
|
+
frontend: ['component', 'page', 'layout', 'css', 'style', 'ui', 'react', 'next', 'hook'],
|
|
778
|
+
backend: ['api', 'route', 'handler', 'middleware', 'server', 'endpoint', 'controller'],
|
|
779
|
+
database: ['schema', 'migration', 'query', 'table', 'index', 'sql', 'model'],
|
|
780
|
+
testing: ['test', 'spec', 'mock', 'fixture', 'assert', 'jest', 'vitest'],
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
const scored = decisions.map((d) => {
|
|
784
|
+
let score = 0;
|
|
785
|
+
const combined = `${d.decision || ''} ${d.title || ''} ${d.rationale || ''}`.toLowerCase();
|
|
786
|
+
|
|
787
|
+
// Arquivo exato e diretorio (+10/+5)
|
|
788
|
+
for (const file of taskFiles) {
|
|
789
|
+
const fileName = file.split("/").pop() || file;
|
|
790
|
+
const dirName = file.split("/").slice(-2, -1)[0] || "";
|
|
791
|
+
|
|
792
|
+
if (combined.includes(fileName.toLowerCase())) score += 10;
|
|
793
|
+
if (dirName && combined.includes(dirName.toLowerCase())) score += 5;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// v8.3: Extensao dos arquivos (+3)
|
|
797
|
+
for (const ext of fileExtensions) {
|
|
798
|
+
if (combined.includes(`.${ext}`)) { score += 3; break; }
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// v8.3: Diretorio mencionado (+4)
|
|
802
|
+
for (const dir of fileDirs) {
|
|
803
|
+
if (combined.includes(dir.toLowerCase())) { score += 4; break; }
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// v8.3: Keywords de dominio (+2)
|
|
807
|
+
const taskCombined = taskFiles.join(' ').toLowerCase();
|
|
808
|
+
for (const [, keywords] of Object.entries(domainKeywords)) {
|
|
809
|
+
for (const kw of keywords) {
|
|
810
|
+
if (combined.includes(kw) && taskCombined.includes(kw)) {
|
|
811
|
+
score += 2;
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Recencia (+3/+1)
|
|
818
|
+
const age = Date.now() - new Date(d.created_at).getTime();
|
|
819
|
+
const hoursOld = age / (1000 * 60 * 60);
|
|
820
|
+
if (hoursOld < 1) score += 3;
|
|
821
|
+
else if (hoursOld < 24) score += 1;
|
|
822
|
+
|
|
823
|
+
return { ...d, _relevanceScore: score };
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
return scored
|
|
827
|
+
.sort((a, b) => b._relevanceScore - a._relevanceScore)
|
|
828
|
+
.slice(0, maxCount)
|
|
829
|
+
.map(({ _relevanceScore, ...d }) => d);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// v8.0: Filtrar standards relevantes para os arquivos da task
|
|
833
|
+
function filterRelevantStandards(standards: any[], taskFiles: string[]): any[] {
|
|
834
|
+
if (standards.length === 0) return [];
|
|
835
|
+
if (taskFiles.length === 0) return standards;
|
|
836
|
+
|
|
837
|
+
// Extrair extensoes e diretorios dos arquivos da task
|
|
838
|
+
const extensions = new Set(taskFiles.map((f) => {
|
|
839
|
+
const ext = f.split(".").pop();
|
|
840
|
+
return ext ? `.${ext}` : "";
|
|
841
|
+
}).filter(Boolean));
|
|
842
|
+
|
|
843
|
+
const directories = new Set(taskFiles.map((f) => {
|
|
844
|
+
const parts = f.split("/");
|
|
845
|
+
return parts.length > 1 ? parts.slice(0, -1).join("/") : "";
|
|
846
|
+
}).filter(Boolean));
|
|
847
|
+
|
|
848
|
+
// Filtrar standards que se aplicam
|
|
849
|
+
return standards.filter((s) => {
|
|
850
|
+
// Standards de naming sempre aplicam
|
|
851
|
+
if (s.category === "naming") return true;
|
|
852
|
+
|
|
853
|
+
// Standards de code aplicam se mencionam extensao dos arquivos
|
|
854
|
+
if (s.category === "code") {
|
|
855
|
+
for (const ext of extensions) {
|
|
856
|
+
if (s.rule?.includes(ext) || s.scope === "all") return true;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Standards de structure aplicam se mencionam diretorio
|
|
861
|
+
if (s.category === "structure") {
|
|
862
|
+
for (const dir of directories) {
|
|
863
|
+
if (s.rule?.includes(dir)) return true;
|
|
864
|
+
}
|
|
865
|
+
return s.scope === "all";
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Outros standards: incluir se scope combina
|
|
869
|
+
return true;
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
export function recover(options: {
|
|
874
|
+
list?: boolean;
|
|
875
|
+
snapshot?: string;
|
|
876
|
+
restore?: boolean;
|
|
877
|
+
auto?: boolean;
|
|
878
|
+
}): void {
|
|
879
|
+
initSchema();
|
|
880
|
+
|
|
881
|
+
const db = getDb();
|
|
882
|
+
|
|
883
|
+
// Listar snapshots
|
|
884
|
+
if (options.list) {
|
|
885
|
+
const snapshots = db
|
|
886
|
+
.query("SELECT id, spec_id, trigger, created_at FROM snapshots ORDER BY created_at DESC LIMIT 20")
|
|
887
|
+
.all() as any[];
|
|
888
|
+
|
|
889
|
+
if (snapshots.length === 0) {
|
|
890
|
+
console.log("\nNenhum snapshot disponivel.\n");
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
console.log(`\nSnapshots disponiveis:`);
|
|
895
|
+
console.log(`${"─".repeat(60)}`);
|
|
896
|
+
for (const snap of snapshots) {
|
|
897
|
+
console.log(` #${snap.id}: ${snap.spec_id} (${snap.trigger}) - ${snap.created_at}`);
|
|
898
|
+
}
|
|
899
|
+
console.log(`\nPara restaurar: recover --restore --snapshot <id>`);
|
|
900
|
+
console.log(`Ou automatico: recover --auto\n`);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Restaurar snapshot (--restore ou --auto)
|
|
905
|
+
if (options.restore || options.auto) {
|
|
906
|
+
let snapshot: any;
|
|
907
|
+
|
|
908
|
+
if (options.auto) {
|
|
909
|
+
snapshot = db.query("SELECT * FROM snapshots ORDER BY created_at DESC LIMIT 1").get();
|
|
910
|
+
} else if (options.snapshot) {
|
|
911
|
+
const snapshotId = parseInt(options.snapshot);
|
|
912
|
+
snapshot = db.query("SELECT * FROM snapshots WHERE id = ?").get(snapshotId);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (!snapshot) {
|
|
916
|
+
console.error("\nNenhum snapshot para restaurar.\n");
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const data = JSON.parse(snapshot.data);
|
|
921
|
+
const now = new Date().toISOString();
|
|
922
|
+
|
|
923
|
+
console.log(`\nRestaurando snapshot #${snapshot.id}...`);
|
|
924
|
+
|
|
925
|
+
// 1. Restaurar spec
|
|
926
|
+
if (data.spec) {
|
|
927
|
+
db.run(
|
|
928
|
+
`UPDATE specs SET phase = ?, approved_at = ?, updated_at = ? WHERE id = ?`,
|
|
929
|
+
[data.spec.phase, data.spec.approved_at, now, data.spec.id]
|
|
930
|
+
);
|
|
931
|
+
console.log(` ✓ Spec restaurado (fase: ${data.spec.phase})`);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// 2. Restaurar context
|
|
935
|
+
if (data.context) {
|
|
936
|
+
db.run(
|
|
937
|
+
`UPDATE context SET
|
|
938
|
+
objective = ?, approach = ?, constraints = ?, patterns = ?,
|
|
939
|
+
current_task = ?, last_checkpoint = ?, updated_at = ?
|
|
940
|
+
WHERE spec_id = ?`,
|
|
941
|
+
[
|
|
942
|
+
data.context.objective,
|
|
943
|
+
data.context.approach,
|
|
944
|
+
data.context.constraints,
|
|
945
|
+
data.context.patterns,
|
|
946
|
+
data.context.current_task,
|
|
947
|
+
data.checkpoint || data.context.last_checkpoint,
|
|
948
|
+
now,
|
|
949
|
+
snapshot.spec_id,
|
|
950
|
+
]
|
|
951
|
+
);
|
|
952
|
+
console.log(` ✓ Context restaurado`);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// 3. Restaurar status das tasks
|
|
956
|
+
if (data.tasks && Array.isArray(data.tasks)) {
|
|
957
|
+
for (const task of data.tasks) {
|
|
958
|
+
db.run(
|
|
959
|
+
`UPDATE tasks SET status = ?, checkpoint = ?, completed_at = ? WHERE id = ?`,
|
|
960
|
+
[task.status, task.checkpoint, task.completed_at, task.id]
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
console.log(` ✓ ${data.tasks.length} tasks restauradas`);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// 4. Log do que foi restaurado
|
|
967
|
+
console.log(`\n${"─".repeat(50)}`);
|
|
968
|
+
console.log(`Snapshot #${snapshot.id} restaurado com sucesso!`);
|
|
969
|
+
console.log(` Spec: ${snapshot.spec_id}`);
|
|
970
|
+
console.log(` Trigger: ${snapshot.trigger}`);
|
|
971
|
+
console.log(` Data original: ${snapshot.created_at}`);
|
|
972
|
+
if (data.taskCompleted) {
|
|
973
|
+
console.log(` Task completada: #${data.taskCompleted}`);
|
|
974
|
+
}
|
|
975
|
+
if (data.checkpoint) {
|
|
976
|
+
console.log(` Checkpoint: ${data.checkpoint}`);
|
|
977
|
+
}
|
|
978
|
+
console.log(`\nUse 'status' para ver o estado atual.\n`);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Mostrar detalhes de snapshot específico
|
|
983
|
+
if (options.snapshot) {
|
|
984
|
+
const snapshotId = parseInt(options.snapshot);
|
|
985
|
+
const snapshot = db.query("SELECT * FROM snapshots WHERE id = ?").get(snapshotId) as any;
|
|
986
|
+
|
|
987
|
+
if (!snapshot) {
|
|
988
|
+
console.error(`\nSnapshot #${snapshotId} nao encontrado.\n`);
|
|
989
|
+
process.exit(1);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const data = JSON.parse(snapshot.data);
|
|
993
|
+
|
|
994
|
+
console.log(`\nSnapshot #${snapshotId}:`);
|
|
995
|
+
console.log(` Spec: ${snapshot.spec_id}`);
|
|
996
|
+
console.log(` Trigger: ${snapshot.trigger}`);
|
|
997
|
+
console.log(` Data: ${snapshot.created_at}`);
|
|
998
|
+
console.log(`\nDados:`);
|
|
999
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1000
|
+
console.log(`\nPara restaurar: recover --restore --snapshot ${snapshotId}\n`);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Mostrar ultimo snapshot
|
|
1005
|
+
const last = db
|
|
1006
|
+
.query("SELECT * FROM snapshots ORDER BY created_at DESC LIMIT 1")
|
|
1007
|
+
.get() as any;
|
|
1008
|
+
|
|
1009
|
+
if (!last) {
|
|
1010
|
+
console.log("\nNenhum snapshot disponivel.\n");
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
console.log(`\nUltimo snapshot:`);
|
|
1015
|
+
console.log(` #${last.id}: ${last.spec_id} (${last.trigger}) - ${last.created_at}`);
|
|
1016
|
+
console.log(`\nComandos:`);
|
|
1017
|
+
console.log(` --list Lista todos os snapshots`);
|
|
1018
|
+
console.log(` --snapshot <id> Mostra detalhes de um snapshot`);
|
|
1019
|
+
console.log(` --restore --snapshot <id> Restaura um snapshot especifico`);
|
|
1020
|
+
console.log(` --auto Restaura o ultimo snapshot automaticamente\n`);
|
|
1021
|
+
}
|