@codexa/cli 8.6.15 → 9.0.1

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/task.ts CHANGED
@@ -3,7 +3,7 @@ import { initSchema, getPatternsForFiles, getPatternsByScope, addSessionSummary,
3
3
  import { enforceGate } from "../gates/validator";
4
4
  import { parseSubagentReturn, formatValidationErrors } from "../protocol/subagent-protocol";
5
5
  import { processSubagentReturn, formatProcessResult } from "../protocol/process-return";
6
- import { getContextForSubagent } from "./utils";
6
+ import { getContextForSubagent, getMinimalContextForSubagent } from "./utils";
7
7
  import { getUnreadKnowledgeForTask } from "./knowledge";
8
8
 
9
9
  export function taskNext(json: boolean = false): void {
@@ -101,7 +101,7 @@ export function taskNext(json: boolean = false): void {
101
101
  console.log(`Use: task start <id> ou task start <id1>,<id2>,...\n`);
102
102
  }
103
103
 
104
- export function taskStart(ids: string, json: boolean = false): void {
104
+ export function taskStart(ids: string, json: boolean = false, fullContext: boolean = false): void {
105
105
  initSchema();
106
106
  enforceGate("task-start");
107
107
 
@@ -144,10 +144,33 @@ export function taskStart(ids: string, json: boolean = false): void {
144
144
  spec.id,
145
145
  ]);
146
146
 
147
+ // v9.0: Mostrar resumo de knowledge relevante antes de iniciar
148
+ const allKnowledge = db.query(
149
+ `SELECT * FROM knowledge
150
+ WHERE spec_id = ? AND severity IN ('critical', 'warning')
151
+ ORDER BY severity DESC, created_at DESC
152
+ LIMIT 10`
153
+ ).all(spec.id) as any[];
154
+
155
+ if (allKnowledge.length > 0 && !json) {
156
+ console.log(`\n[i] Knowledge ativo (${allKnowledge.length}):`);
157
+ for (const k of allKnowledge.slice(0, 5)) {
158
+ const icon = k.severity === 'critical' ? '[X]' : '[!]';
159
+ console.log(` ${icon} ${k.content}`);
160
+ }
161
+ if (allKnowledge.length > 5) {
162
+ console.log(` ... +${allKnowledge.length - 5} mais`);
163
+ }
164
+ console.log();
165
+ }
166
+
147
167
  if (json) {
148
168
  // NOVO: Incluir contexto COMPLETO para cada task
149
169
  const contexts = startedTasks.map((task) => {
150
- const contextText = getContextForSubagent(task.id);
170
+ // v9.0: Usar contexto minimo por padrao, expandido via --full-context
171
+ const contextText = fullContext
172
+ ? getContextForSubagent(task.id)
173
+ : getMinimalContextForSubagent(task.id);
151
174
  const unreadKnowledge = getUnreadKnowledgeForTask(spec.id, task.id);
152
175
 
153
176
  // NOVO v7.4: Buscar implementation patterns relevantes
@@ -196,8 +219,9 @@ export function taskStart(ids: string, json: boolean = false): void {
196
219
  files: taskFiles,
197
220
  // AVISO PARA O ORQUESTRADOR - NAO EXECUTE, DELEGUE
198
221
  _orchestratorWarning: "NAO execute esta task diretamente. Use Task tool com subagent_type='general-purpose' para delegar. O campo 'subagentContext' abaixo e o prompt para o SUBAGENT.",
199
- // Contexto completo para o subagent (NAO para o orquestrador)
222
+ // Contexto para o subagent (NAO para o orquestrador)
200
223
  context: contextText,
224
+ contextMode: fullContext ? "full" : "minimal",
201
225
  // Knowledge nao lido (broadcast de outras tasks)
202
226
  unreadKnowledge: unreadKnowledge.map((k: any) => ({
203
227
  id: k.id,
@@ -237,6 +261,13 @@ CHECKLIST OBRIGATORIO (verifique ANTES de retornar):
237
261
  - [ ] Os arquivos que vou listar em files_created EXISTEM no disco?
238
262
 
239
263
  Se nao marcou todos os items, PARE e corrija AGORA.
264
+
265
+ CONTEXTO ON-DEMAND: Se precisar de mais contexto alem do fornecido, execute:
266
+ codexa context detail standards # Regras do projeto
267
+ codexa context detail decisions # Decisoes tomadas
268
+ codexa context detail patterns # Patterns de codigo
269
+ codexa context detail knowledge # Discoveries de outras tasks
270
+ NAO execute todos - apenas o que for NECESSARIO para sua task.
240
271
  `.trim(),
241
272
  // Instrucoes de retorno para o SUBAGENT
242
273
  subagentReturnProtocol: `
@@ -246,19 +277,20 @@ FORMATO DE RETORNO (apos criar os arquivos):
246
277
  "summary": "Resumo do que foi feito (10-500 chars)",
247
278
  "files_created": ["path/arquivo.ts"],
248
279
  "files_modified": ["path/outro.ts"],
249
- "patterns_discovered": ["Pattern identificado"],
250
- "decisions_made": [{"title": "...", "decision": "..."}],
251
- "blockers": ["Se status != completed"],
252
- "knowledge_to_broadcast": [{"category": "discovery|pattern|constraint", "content": "...", "severity": "info|warning|critical"}],
253
280
  "reasoning": {
254
- "approach": "Como voce abordou o problema (RECOMENDADO)",
281
+ "approach": "OBRIGATORIO (min 20 chars): Como voce abordou o problema e POR QUE tomou essas decisoes",
255
282
  "challenges": ["Desafios encontrados"],
256
283
  "recommendations": "Sugestoes para proximas tasks"
257
- }
284
+ },
285
+ "patterns_discovered": ["Pattern identificado"],
286
+ "decisions_made": [{"title": "...", "decision": "..."}],
287
+ "blockers": ["Se status != completed"],
288
+ "knowledge_to_broadcast": [{"category": "discovery|pattern|constraint", "content": "...", "severity": "info|warning|critical"}]
258
289
  }
259
290
 
260
- IMPORTANTE: O campo "reasoning" ajuda a preservar contexto entre sessoes.
261
- Inclua pelo menos "approach" para explicar suas escolhas.
291
+ ATENCAO: O campo "reasoning.approach" e OBRIGATORIO para status "completed".
292
+ Se retornar sem ele, Gate 4.4 BLOQUEIA a finalizacao da task.
293
+ Descreva COMO abordou o problema, nao apenas O QUE fez.
262
294
 
263
295
  Se NAO conseguir criar arquivos (sem permissao, sem ferramentas), retorne:
264
296
  {
@@ -401,17 +433,36 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
401
433
  : [];
402
434
  }
403
435
 
404
- // Validar gates incluindo files-exist e standards-follow
436
+ // Validar gates incluindo files-exist, standards-follow e reasoning (v9.0)
405
437
  enforceGate("task-done", {
406
438
  taskId,
407
439
  checkpoint,
408
440
  files: expectedFiles,
409
441
  force: options.force,
410
442
  forceReason: options.forceReason,
443
+ subagentData: subagentData,
411
444
  });
412
445
 
413
446
  // Nota: Validacao de standards agora e feita pelo Gate 4.2 (standards-follow) em enforceGate
414
447
 
448
+ // v9.0: Atualizar patterns incrementalmente com arquivos da task
449
+ try {
450
+ if (subagentData && subagentData.status === "completed") {
451
+ const allFiles = [
452
+ ...(subagentData.files_created || []),
453
+ ...(subagentData.files_modified || []),
454
+ ];
455
+ const codeFiles = allFiles.filter((f: string) => /\.(ts|tsx|js|jsx)$/.test(f));
456
+
457
+ if (codeFiles.length > 0) {
458
+ const { updatePatternsIncremental } = require("./patterns");
459
+ updatePatternsIncremental(codeFiles, task.number);
460
+ }
461
+ }
462
+ } catch {
463
+ // Nao-critico: nao falhar task done por atualizacao de patterns
464
+ }
465
+
415
466
  // Marcar como done
416
467
  db.run(
417
468
  "UPDATE tasks SET status = 'done', checkpoint = ?, completed_at = ? WHERE id = ?",
package/commands/utils.ts CHANGED
@@ -345,6 +345,102 @@ export function contextExport(options: { task?: string; json?: boolean; }): void
345
345
  console.log(JSON.stringify(exportData, null, 2));
346
346
  }
347
347
 
348
+ // v9.0: Contexto minimo para subagent (max ~2KB)
349
+ const MAX_MINIMAL_CONTEXT = 2048;
350
+
351
+ export function getMinimalContextForSubagent(taskId: number): string {
352
+ initSchema();
353
+ const db = getDb();
354
+
355
+ const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
356
+ if (!task) return "ERRO: Task nao encontrada";
357
+
358
+ const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
359
+ const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(task.spec_id) as any;
360
+
361
+ const taskFiles = task.files ? JSON.parse(task.files) : [];
362
+ const domain = task.agent?.split("-")[0] || "all";
363
+
364
+ // 1. Standards REQUIRED apenas (nao recommended)
365
+ const requiredStandards = db.query(
366
+ `SELECT rule FROM standards
367
+ WHERE enforcement = 'required' AND (scope = 'all' OR scope = ?)
368
+ LIMIT 10`
369
+ ).all(domain) as any[];
370
+
371
+ // 2. Blockers CRITICAL apenas
372
+ const criticalBlockers = db.query(
373
+ `SELECT content FROM knowledge
374
+ WHERE spec_id = ? AND severity = 'critical'
375
+ ORDER BY created_at DESC LIMIT 5`
376
+ ).all(task.spec_id) as any[];
377
+
378
+ // 3. Contradicoes (se existir)
379
+ let contradictions: any[] = [];
380
+ try {
381
+ contradictions = findContradictions(task.spec_id);
382
+ } catch { /* ignore */ }
383
+
384
+ // 4. Decisoes da task anterior (dependency direta)
385
+ const dependsOn = task.depends_on ? JSON.parse(task.depends_on) : [];
386
+ let depDecisions: any[] = [];
387
+ if (dependsOn.length > 0) {
388
+ const placeholders = dependsOn.map(() => '?').join(',');
389
+ const depTasks = db.query(
390
+ `SELECT id FROM tasks WHERE spec_id = ? AND number IN (${placeholders})`
391
+ ).all(task.spec_id, ...dependsOn) as any[];
392
+
393
+ for (const dt of depTasks) {
394
+ const reasoning = db.query(
395
+ `SELECT thought FROM reasoning_log
396
+ WHERE spec_id = ? AND task_id = ? AND category = 'recommendation'
397
+ ORDER BY created_at DESC LIMIT 2`
398
+ ).all(task.spec_id, dt.id) as any[];
399
+ depDecisions.push(...reasoning);
400
+ }
401
+ }
402
+
403
+ // Montar output minimo
404
+ let output = `## CONTEXTO MINIMO (Task #${task.number})
405
+
406
+ **Feature:** ${spec.name}
407
+ **Objetivo:** ${context?.objective || "N/A"}
408
+ **Arquivos:** ${taskFiles.join(", ") || "Nenhum especificado"}
409
+ `;
410
+
411
+ if (requiredStandards.length > 0) {
412
+ output += `\n**Standards (OBRIGATORIOS):**\n${requiredStandards.map((s: any) => `- ${s.rule}`).join("\n")}\n`;
413
+ }
414
+
415
+ if (criticalBlockers.length > 0) {
416
+ output += `\n**BLOCKERS CRITICOS:**\n${criticalBlockers.map((b: any) => `[X] ${b.content}`).join("\n")}\n`;
417
+ }
418
+
419
+ if (contradictions.length > 0) {
420
+ output += `\n**CONTRADICOES:**\n${contradictions.map((c: any) => `[!] "${c.decision1}" <-> "${c.decision2}"`).join("\n")}\n`;
421
+ }
422
+
423
+ if (depDecisions.length > 0) {
424
+ output += `\n**Recomendacoes de tasks anteriores:**\n${depDecisions.map((d: any) => `- ${d.thought}`).join("\n")}\n`;
425
+ }
426
+
427
+ output += `
428
+ **Contexto expandido**: Se precisar de mais contexto, execute:
429
+ codexa context detail standards # Todos os standards
430
+ codexa context detail decisions # Todas as decisoes
431
+ codexa context detail patterns # Patterns do projeto
432
+ codexa context detail knowledge # Knowledge completo
433
+ codexa context detail architecture # Analise arquitetural
434
+ `;
435
+
436
+ // Truncar se exceder limite
437
+ if (output.length > MAX_MINIMAL_CONTEXT) {
438
+ output = output.substring(0, MAX_MINIMAL_CONTEXT - 100) + "\n\n[CONTEXTO TRUNCADO - use: codexa context detail <secao>]\n";
439
+ }
440
+
441
+ return output;
442
+ }
443
+
348
444
  // Funcao helper para formatar contexto para subagent (uso interno)
349
445
  // v8.1: Contexto COMPLETO mas ESTRUTURADO - todas as fontes relevantes incluidas
350
446
  export function getContextForSubagent(taskId: number): string {
@@ -873,6 +969,154 @@ function filterRelevantStandards(standards: any[], taskFiles: string[]): any[] {
873
969
  });
874
970
  }
875
971
 
972
+ export function contextDetail(section: string, json: boolean = false): void {
973
+ initSchema();
974
+ const db = getDb();
975
+
976
+ const spec = db
977
+ .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
978
+ .get() as any;
979
+
980
+ if (!spec) {
981
+ console.error("\nNenhuma feature ativa.\n");
982
+ process.exit(1);
983
+ }
984
+
985
+ // Encontrar task running (para contexto relevante)
986
+ const runningTask = db.query(
987
+ "SELECT * FROM tasks WHERE spec_id = ? AND status = 'running' LIMIT 1"
988
+ ).get(spec.id) as any;
989
+
990
+ const domain = runningTask?.agent?.split("-")[0] || "all";
991
+
992
+ let output = "";
993
+
994
+ switch (section) {
995
+ case "standards": {
996
+ const standards = db.query(
997
+ "SELECT * FROM standards WHERE scope = 'all' OR scope = ? ORDER BY enforcement DESC, category"
998
+ ).all(domain) as any[];
999
+
1000
+ if (json) { console.log(JSON.stringify({ standards })); return; }
1001
+
1002
+ output = `Standards (${standards.length}):\n`;
1003
+ for (const s of standards) {
1004
+ output += `\n[${s.enforcement.toUpperCase()}] ${s.category}: ${s.rule}`;
1005
+ if (s.examples) {
1006
+ try {
1007
+ const examples = JSON.parse(s.examples);
1008
+ if (examples.length > 0) output += `\n Exemplos: ${examples.slice(0, 2).join(", ")}`;
1009
+ } catch { /* ignore */ }
1010
+ }
1011
+ if (s.anti_examples) {
1012
+ try {
1013
+ const anti = JSON.parse(s.anti_examples);
1014
+ if (anti.length > 0) output += `\n Anti-exemplos: ${anti.slice(0, 2).join(", ")}`;
1015
+ } catch { /* ignore */ }
1016
+ }
1017
+ }
1018
+ break;
1019
+ }
1020
+
1021
+ case "decisions": {
1022
+ const decisions = db.query(
1023
+ "SELECT * FROM decisions WHERE spec_id = ? AND status = 'active' ORDER BY created_at DESC"
1024
+ ).all(spec.id) as any[];
1025
+
1026
+ if (json) { console.log(JSON.stringify({ decisions })); return; }
1027
+
1028
+ output = `Decisoes ativas (${decisions.length}):\n`;
1029
+ for (const d of decisions) {
1030
+ output += `\n[${d.id}] ${d.title}`;
1031
+ output += `\n Decisao: ${d.decision}`;
1032
+ if (d.rationale) output += `\n Racional: ${d.rationale}`;
1033
+ output += `\n Task: #${d.task_ref} | ${d.created_at}`;
1034
+ }
1035
+ break;
1036
+ }
1037
+
1038
+ case "patterns": {
1039
+ const patterns = db.query(
1040
+ "SELECT * FROM implementation_patterns ORDER BY category, name"
1041
+ ).all() as any[];
1042
+
1043
+ if (json) { console.log(JSON.stringify({ patterns })); return; }
1044
+
1045
+ output = `Patterns (${patterns.length}):\n`;
1046
+ for (const p of patterns) {
1047
+ output += `\n[${p.category}] ${p.name} (${p.scope})`;
1048
+ output += `\n Applies to: ${p.applies_to}`;
1049
+ output += `\n Confidence: ${(p.confidence * 100).toFixed(0)}%`;
1050
+ if (p.template) {
1051
+ const preview = p.template.length > 300 ? p.template.substring(0, 300) + "..." : p.template;
1052
+ output += `\n Template:\n${preview}`;
1053
+ }
1054
+ const anti = p.anti_patterns ? JSON.parse(p.anti_patterns) : [];
1055
+ if (anti.length > 0) {
1056
+ output += `\n Anti-patterns: ${anti.join("; ")}`;
1057
+ }
1058
+ }
1059
+ break;
1060
+ }
1061
+
1062
+ case "knowledge": {
1063
+ const knowledge = db.query(
1064
+ "SELECT k.*, t.number as task_number FROM knowledge k LEFT JOIN tasks t ON k.task_origin = t.id WHERE k.spec_id = ? ORDER BY k.severity DESC, k.created_at DESC LIMIT 50"
1065
+ ).all(spec.id) as any[];
1066
+
1067
+ if (json) { console.log(JSON.stringify({ knowledge })); return; }
1068
+
1069
+ output = `Knowledge (${knowledge.length}):\n`;
1070
+ for (const k of knowledge) {
1071
+ const icon = k.severity === 'critical' ? '[X]' : k.severity === 'warning' ? '[!]' : '[i]';
1072
+ output += `\n${icon} #${k.id} (${k.category}) Task #${k.task_number || '?'}`;
1073
+ output += `\n ${k.content}`;
1074
+ }
1075
+ break;
1076
+ }
1077
+
1078
+ case "architecture": {
1079
+ const analysis = getArchitecturalAnalysisForSpec(spec.name, spec.id);
1080
+ if (!analysis) {
1081
+ output = "Nenhuma analise arquitetural encontrada.";
1082
+ break;
1083
+ }
1084
+
1085
+ if (json) { console.log(JSON.stringify({ analysis })); return; }
1086
+
1087
+ output = `Analise Arquitetural: ${analysis.id}\n`;
1088
+ output += `Status: ${analysis.status}\n`;
1089
+ if (analysis.approach) output += `\nAbordagem:\n${analysis.approach}\n`;
1090
+ if (analysis.risks) {
1091
+ try {
1092
+ const risks = JSON.parse(analysis.risks);
1093
+ output += `\nRiscos (${risks.length}):\n`;
1094
+ for (const r of risks) {
1095
+ output += ` - ${r.description} (${r.probability}/${r.impact}) -> ${r.mitigation}\n`;
1096
+ }
1097
+ } catch { /* ignore */ }
1098
+ }
1099
+ if (analysis.decisions) {
1100
+ try {
1101
+ const decisions = JSON.parse(analysis.decisions);
1102
+ output += `\nDecisoes (${decisions.length}):\n`;
1103
+ for (const d of decisions) {
1104
+ output += ` - ${d.decision}: ${d.rationale || ''}\n`;
1105
+ }
1106
+ } catch { /* ignore */ }
1107
+ }
1108
+ break;
1109
+ }
1110
+
1111
+ default:
1112
+ console.error(`\nSecao invalida: ${section}`);
1113
+ console.error("Secoes validas: standards, decisions, patterns, knowledge, architecture\n");
1114
+ process.exit(1);
1115
+ }
1116
+
1117
+ console.log(output || "\nNenhum dado encontrado.\n");
1118
+ }
1119
+
876
1120
  export function recover(options: {
877
1121
  list?: boolean;
878
1122
  snapshot?: string;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Gate 4.5: Typecheck Validator
3
+ *
4
+ * Executa verificacao de tipos nos arquivos criados/modificados pelo subagent.
5
+ * Requer tsconfig.json no projeto.
6
+ */
7
+
8
+ import { existsSync } from "fs";
9
+ import { spawnSync } from "child_process";
10
+ import { resolve, dirname } from "path";
11
+
12
+ export interface TypecheckResult {
13
+ passed: boolean;
14
+ errors: TypecheckError[];
15
+ skipped: boolean;
16
+ reason?: string;
17
+ }
18
+
19
+ export interface TypecheckError {
20
+ file: string;
21
+ line: number;
22
+ column: number;
23
+ message: string;
24
+ code: string;
25
+ }
26
+
27
+ /**
28
+ * Encontra o tsconfig.json mais proximo subindo diretorios
29
+ */
30
+ function findTsConfig(startDir: string): string | null {
31
+ let dir = startDir;
32
+ const root = resolve("/");
33
+
34
+ while (dir !== root) {
35
+ const candidate = resolve(dir, "tsconfig.json");
36
+ if (existsSync(candidate)) return candidate;
37
+ dir = dirname(dir);
38
+ }
39
+ return null;
40
+ }
41
+
42
+ /**
43
+ * Detecta o runtime do projeto para escolher o comando tsc
44
+ */
45
+ function detectTscCommand(): string[] {
46
+ const bunCheck = spawnSync("bun", ["--version"], { encoding: "utf-8", timeout: 3000 });
47
+ if (!bunCheck.error) return ["bunx", "tsc"];
48
+
49
+ const npxCheck = spawnSync("npx", ["--version"], { encoding: "utf-8", timeout: 3000 });
50
+ if (!npxCheck.error) return ["npx", "tsc"];
51
+
52
+ const tscCheck = spawnSync("tsc", ["--version"], { encoding: "utf-8", timeout: 3000 });
53
+ if (!tscCheck.error) return ["tsc"];
54
+
55
+ return [];
56
+ }
57
+
58
+ /**
59
+ * Parseia output do tsc em erros estruturados
60
+ */
61
+ function parseTscOutput(output: string, relevantFiles: string[]): TypecheckError[] {
62
+ const errors: TypecheckError[] = [];
63
+ const lines = output.split(/\r?\n/);
64
+
65
+ // Formato: file(line,col): error TSxxxx: message
66
+ const errorRegex = /^(.+)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.+)$/;
67
+
68
+ for (const line of lines) {
69
+ const match = line.match(errorRegex);
70
+ if (!match) continue;
71
+
72
+ const [, file, lineNum, col, code, message] = match;
73
+ const normalizedFile = file.replace(/\\/g, "/");
74
+
75
+ // Filtrar: apenas erros nos arquivos relevantes
76
+ const isRelevant = relevantFiles.some(rf => {
77
+ const normalizedRf = rf.replace(/\\/g, "/");
78
+ return (
79
+ normalizedFile.endsWith(normalizedRf) ||
80
+ normalizedFile.includes(normalizedRf) ||
81
+ normalizedRf.endsWith(normalizedFile) ||
82
+ normalizedRf.includes(normalizedFile)
83
+ );
84
+ });
85
+
86
+ if (isRelevant) {
87
+ errors.push({
88
+ file: normalizedFile,
89
+ line: parseInt(lineNum),
90
+ column: parseInt(col),
91
+ message,
92
+ code,
93
+ });
94
+ }
95
+ }
96
+
97
+ return errors;
98
+ }
99
+
100
+ /**
101
+ * Executa typecheck nos arquivos especificados
102
+ */
103
+ export function runTypecheck(files: string[]): TypecheckResult {
104
+ // Filtrar apenas arquivos TS/TSX
105
+ const tsFiles = files.filter(f => /\.(ts|tsx)$/.test(f) && existsSync(f));
106
+
107
+ if (tsFiles.length === 0) {
108
+ return { passed: true, errors: [], skipped: true, reason: "Nenhum arquivo TypeScript" };
109
+ }
110
+
111
+ // Encontrar tsconfig.json (primeiro no cwd, depois no diretorio dos arquivos)
112
+ let tsconfig = findTsConfig(process.cwd());
113
+ if (!tsconfig) {
114
+ const firstFileDir = dirname(resolve(tsFiles[0]));
115
+ tsconfig = findTsConfig(firstFileDir);
116
+ }
117
+ if (!tsconfig) {
118
+ return { passed: true, errors: [], skipped: true, reason: "tsconfig.json nao encontrado" };
119
+ }
120
+
121
+ // Detectar comando tsc
122
+ const tscCmd = detectTscCommand();
123
+ if (tscCmd.length === 0) {
124
+ return { passed: true, errors: [], skipped: true, reason: "tsc nao encontrado (instale typescript)" };
125
+ }
126
+
127
+ // Executar tsc --noEmit
128
+ const result = spawnSync(
129
+ tscCmd[0],
130
+ [...tscCmd.slice(1), "--noEmit", "--project", tsconfig],
131
+ {
132
+ encoding: "utf-8",
133
+ timeout: 60000,
134
+ maxBuffer: 5 * 1024 * 1024,
135
+ cwd: dirname(tsconfig),
136
+ }
137
+ );
138
+
139
+ if (result.error) {
140
+ return { passed: true, errors: [], skipped: true, reason: `Erro ao executar tsc: ${result.error.message}` };
141
+ }
142
+
143
+ // Parsear erros, filtrando apenas os dos arquivos relevantes
144
+ const output = (result.stdout || "") + (result.stderr || "");
145
+ const errors = parseTscOutput(output, tsFiles);
146
+
147
+ return {
148
+ passed: errors.length === 0,
149
+ errors,
150
+ skipped: false,
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Formata resultado para exibicao no CLI
156
+ */
157
+ export function printTypecheckResult(result: TypecheckResult): void {
158
+ if (result.skipped) {
159
+ console.log(`\n[i] Typecheck pulado: ${result.reason}`);
160
+ return;
161
+ }
162
+
163
+ if (result.passed) {
164
+ console.log(`\n[ok] Typecheck: sem erros nos arquivos da task`);
165
+ return;
166
+ }
167
+
168
+ console.error(`\n[X] TYPECHECK: ${result.errors.length} erro(s) encontrado(s)\n`);
169
+ for (const err of result.errors) {
170
+ console.error(` ${err.file}:${err.line}:${err.column}`);
171
+ console.error(` ${err.code}: ${err.message}`);
172
+ console.error();
173
+ }
174
+ }
@@ -2,6 +2,7 @@ import { getDb } from "../db/connection";
2
2
  import { existsSync, readFileSync, statSync } from "fs";
3
3
  import { extname } from "path";
4
4
  import { validateAgainstStandards, printValidationResult } from "./standards-validator";
5
+ import { runTypecheck, printTypecheckResult } from "./typecheck-validator";
5
6
  import { extractUtilitiesFromFile } from "../commands/patterns";
6
7
  import { findDuplicateUtilities } from "../db/schema";
7
8
 
@@ -48,6 +49,11 @@ const GATES: Record<string, GateCheck[]> = {
48
49
  message: "Dependencias pendentes",
49
50
  resolution: "Complete as tasks dependentes primeiro",
50
51
  },
52
+ {
53
+ check: "no-critical-blockers",
54
+ message: "Knowledge critico nao resolvido",
55
+ resolution: "Verifique os blockers com: knowledge list --severity critical. Reconheca com: knowledge resolve <id>",
56
+ },
51
57
  ],
52
58
  "task-done": [
53
59
  {
@@ -75,6 +81,16 @@ const GATES: Record<string, GateCheck[]> = {
75
81
  message: "Duplicacao de utilities detectada (DRY)",
76
82
  resolution: "Importe do arquivo existente ou use --force --force-reason para bypass",
77
83
  },
84
+ {
85
+ check: "reasoning-provided",
86
+ message: "Reasoning obrigatorio ausente no retorno do subagent",
87
+ resolution: "O subagent deve incluir 'reasoning.approach' (min 20 chars) no retorno JSON",
88
+ },
89
+ {
90
+ check: "typecheck-pass",
91
+ message: "Erros de tipo encontrados nos arquivos da task",
92
+ resolution: "Corrija os erros TypeScript ou use --force --force-reason para bypass",
93
+ },
78
94
  ],
79
95
  "review-start": [
80
96
  {
@@ -275,6 +291,78 @@ function executeCheck(check: string, context: any): { passed: boolean; details?:
275
291
  return { passed: true };
276
292
  }
277
293
 
294
+ case "typecheck-pass": {
295
+ if (context.force) {
296
+ logGateBypass(context.taskId, "typecheck-pass", context.forceReason);
297
+ return { passed: true };
298
+ }
299
+
300
+ if (!context.files || context.files.length === 0) return { passed: true };
301
+
302
+ const typecheckResult = runTypecheck(context.files);
303
+
304
+ printTypecheckResult(typecheckResult);
305
+
306
+ if (typecheckResult.skipped) return { passed: true };
307
+
308
+ return {
309
+ passed: typecheckResult.passed,
310
+ details: typecheckResult.errors.map(e => `${e.file}:${e.line} ${e.code}: ${e.message}`).join("\n"),
311
+ };
312
+ }
313
+
314
+ case "no-critical-blockers": {
315
+ const spec = getActiveSpec();
316
+ if (!spec) return { passed: true };
317
+
318
+ const allKnowledge = db
319
+ .query(
320
+ `SELECT k.*, t.number as task_number, t.name as task_name
321
+ FROM knowledge k
322
+ LEFT JOIN tasks t ON k.task_origin = t.id
323
+ WHERE k.spec_id = ? AND k.severity = 'critical'
324
+ ORDER BY k.created_at DESC`
325
+ )
326
+ .all(spec.id) as any[];
327
+
328
+ const unresolved = allKnowledge.filter((k: any) => {
329
+ if (!k.acknowledged_by) return true;
330
+ try {
331
+ const acked = JSON.parse(k.acknowledged_by) as number[];
332
+ return acked.length === 0;
333
+ } catch {
334
+ return true;
335
+ }
336
+ });
337
+
338
+ if (unresolved.length === 0) return { passed: true };
339
+
340
+ const details = unresolved.map((k: any) => {
341
+ const origin = k.task_name ? `Task #${k.task_number} (${k.task_name})` : `Task ID ${k.task_origin}`;
342
+ return `[CRITICAL] ${k.content} (origem: ${origin})`;
343
+ }).join("\n");
344
+
345
+ return {
346
+ passed: false,
347
+ details: `${unresolved.length} blocker(s) critico(s) nao resolvido(s):\n${details}`,
348
+ };
349
+ }
350
+
351
+ case "reasoning-provided": {
352
+ if (!context.subagentData) return { passed: true };
353
+
354
+ const data = context.subagentData;
355
+ if (data.status !== "completed") return { passed: true };
356
+
357
+ if (!data.reasoning?.approach || data.reasoning.approach.length < 20) {
358
+ return {
359
+ passed: false,
360
+ details: "O subagent retornou status 'completed' sem reasoning.approach (min 20 chars)",
361
+ };
362
+ }
363
+ return { passed: true };
364
+ }
365
+
278
366
  default:
279
367
  return { passed: true };
280
368
  }