@codexa/cli 8.5.0 → 8.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,249 +1,249 @@
1
- import { getDb } from "../db/connection";
2
- import { initSchema } from "../db/schema";
3
-
4
- function getNextDecisionId(specId: string): string {
5
- const db = getDb();
6
- const last = db
7
- .query("SELECT id FROM decisions WHERE spec_id = ? ORDER BY created_at DESC LIMIT 1")
8
- .get(specId) as any;
9
-
10
- if (!last) return "DEC-001";
11
-
12
- const num = parseInt(last.id.replace("DEC-", "")) + 1;
13
- return `DEC-${num.toString().padStart(3, "0")}`;
14
- }
15
-
16
- interface ConflictAnalysis {
17
- hasConflict: boolean;
18
- conflictingDecisions: Array<{
19
- id: string;
20
- title: string;
21
- decision: string;
22
- reason: string;
23
- }>;
24
- }
25
-
26
- // Extrair keywords relevantes de um texto
27
- function extractKeywords(text: string): string[] {
28
- const stopWords = new Set([
29
- "a", "o", "e", "de", "da", "do", "para", "com", "em", "que", "usar",
30
- "the", "and", "or", "to", "for", "with", "in", "on", "is", "are", "be",
31
- "will", "should", "must", "can", "use", "using", "como", "ser", "sera"
32
- ]);
33
-
34
- return text
35
- .toLowerCase()
36
- .replace(/[^\w\s-]/g, " ")
37
- .split(/\s+/)
38
- .filter((word) => word.length > 2 && !stopWords.has(word));
39
- }
40
-
41
- // Detectar conflitos semanticos entre decisoes
42
- function detectConflicts(
43
- newTitle: string,
44
- newDecision: string,
45
- existingDecisions: any[]
46
- ): ConflictAnalysis {
47
- const conflicts: ConflictAnalysis["conflictingDecisions"] = [];
48
-
49
- const newKeywords = new Set([
50
- ...extractKeywords(newTitle),
51
- ...extractKeywords(newDecision),
52
- ]);
53
-
54
- // Padroes de conflito conhecidos
55
- const conflictPatterns: Array<{ patterns: string[][]; reason: string }> = [
56
- {
57
- patterns: [["rest", "api"], ["graphql"]],
58
- reason: "Conflito de paradigma de API (REST vs GraphQL)",
59
- },
60
- {
61
- patterns: [["jwt", "token"], ["session", "cookie"]],
62
- reason: "Conflito de estrategia de autenticacao",
63
- },
64
- {
65
- patterns: [["zustand"], ["redux"]],
66
- reason: "Conflito de biblioteca de estado",
67
- },
68
- {
69
- patterns: [["prisma"], ["drizzle"]],
70
- reason: "Conflito de ORM",
71
- },
72
- {
73
- patterns: [["mysql"], ["postgres", "postgresql"]],
74
- reason: "Conflito de banco de dados",
75
- },
76
- {
77
- patterns: [["server", "component"], ["client", "component"]],
78
- reason: "Conflito de tipo de componente padrao",
79
- },
80
- {
81
- patterns: [["tailwind"], ["styled", "component"]],
82
- reason: "Conflito de abordagem de styling",
83
- },
84
- {
85
- patterns: [["vitest"], ["jest"]],
86
- reason: "Conflito de framework de testes",
87
- },
88
- ];
89
-
90
- for (const existing of existingDecisions) {
91
- const existingKeywords = new Set([
92
- ...extractKeywords(existing.title),
93
- ...extractKeywords(existing.decision),
94
- ]);
95
-
96
- // Verificar padroes de conflito conhecidos
97
- for (const { patterns, reason } of conflictPatterns) {
98
- const [patternA, patternB] = patterns;
99
-
100
- const newHasA = patternA.some((p) => newKeywords.has(p));
101
- const newHasB = patternB.some((p) => newKeywords.has(p));
102
- const existingHasA = patternA.some((p) => existingKeywords.has(p));
103
- const existingHasB = patternB.some((p) => existingKeywords.has(p));
104
-
105
- // Conflito: nova decisao menciona A, existente menciona B (ou vice-versa)
106
- if ((newHasA && existingHasB) || (newHasB && existingHasA)) {
107
- conflicts.push({
108
- id: existing.id,
109
- title: existing.title,
110
- decision: existing.decision,
111
- reason,
112
- });
113
- break; // Apenas um conflito por decisao existente
114
- }
115
- }
116
-
117
- // Verificar sobreposicao alta de keywords (mesmo topico, decisoes diferentes?)
118
- if (conflicts.every((c) => c.id !== existing.id)) {
119
- const intersection = [...newKeywords].filter((k) => existingKeywords.has(k));
120
- const similarity = intersection.length / Math.max(newKeywords.size, existingKeywords.size);
121
-
122
- if (similarity > 0.4 && intersection.length >= 3) {
123
- conflicts.push({
124
- id: existing.id,
125
- title: existing.title,
126
- decision: existing.decision,
127
- reason: `Alta sobreposicao de topico (${Math.round(similarity * 100)}% similar) - verificar se sao decisoes conflitantes`,
128
- });
129
- }
130
- }
131
- }
132
-
133
- return {
134
- hasConflict: conflicts.length > 0,
135
- conflictingDecisions: conflicts,
136
- };
137
- }
138
-
139
- export function decide(title: string, decision: string, options: { rationale?: string; force?: boolean }): void {
140
- initSchema();
141
-
142
- const db = getDb();
143
- const now = new Date().toISOString();
144
-
145
- const spec = db
146
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
147
- .get() as any;
148
-
149
- if (!spec) {
150
- console.error("\nNenhuma feature ativa.\n");
151
- process.exit(1);
152
- }
153
-
154
- // Verificar conflitos com decisoes existentes
155
- const existingDecisions = db
156
- .query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
157
- .all(spec.id) as any[];
158
-
159
- if (existingDecisions.length > 0 && !options.force) {
160
- const analysis = detectConflicts(title, decision, existingDecisions);
161
-
162
- if (analysis.hasConflict) {
163
- console.warn("\n⚠ POTENCIAL CONFLITO DETECTADO:\n");
164
- console.warn(`Nova decisao: "${title}"`);
165
- console.warn(` ${decision}\n`);
166
-
167
- for (const conflict of analysis.conflictingDecisions) {
168
- console.warn(`Pode conflitar com: ${conflict.id} - "${conflict.title}"`);
169
- console.warn(` ${conflict.decision}`);
170
- console.warn(` Motivo: ${conflict.reason}\n`);
171
- }
172
-
173
- console.warn(`${"─".repeat(50)}`);
174
- console.warn(`Para registrar mesmo assim, use: decide "${title}" "${decision}" --force`);
175
- console.warn(`Ou revise as decisoes existentes com: decisions\n`);
176
- process.exit(1);
177
- }
178
- }
179
-
180
- // Pegar task atual em execucao
181
- const currentTask = db
182
- .query("SELECT * FROM tasks WHERE spec_id = ? AND status = 'running' LIMIT 1")
183
- .get(spec.id) as any;
184
-
185
- const decisionId = getNextDecisionId(spec.id);
186
-
187
- db.run(
188
- `INSERT INTO decisions (id, spec_id, task_ref, title, decision, rationale, status, created_at)
189
- VALUES (?, ?, ?, ?, ?, ?, 'active', ?)`,
190
- [
191
- decisionId,
192
- spec.id,
193
- currentTask?.number || null,
194
- title,
195
- decision,
196
- options.rationale || null,
197
- now,
198
- ]
199
- );
200
-
201
- console.log(`\nDecisao registrada: ${decisionId}`);
202
- console.log(`Titulo: ${title}`);
203
- console.log(`Decisao: ${decision}`);
204
- if (options.rationale) console.log(`Racional: ${options.rationale}`);
205
- if (currentTask) console.log(`Task: #${currentTask.number}`);
206
- if (options.force) console.log(`[!] Registrada com --force (conflito ignorado)`);
207
- console.log();
208
- }
209
-
210
- export function listDecisions(json: boolean = false): void {
211
- initSchema();
212
-
213
- const db = getDb();
214
-
215
- const spec = db
216
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
217
- .get() as any;
218
-
219
- if (!spec) {
220
- console.error("\nNenhuma feature ativa.\n");
221
- process.exit(1);
222
- }
223
-
224
- const decisions = db
225
- .query("SELECT * FROM decisions WHERE spec_id = ? ORDER BY created_at")
226
- .all(spec.id) as any[];
227
-
228
- if (json) {
229
- console.log(JSON.stringify({ decisions }));
230
- return;
231
- }
232
-
233
- if (decisions.length === 0) {
234
- console.log("\nNenhuma decisao registrada.\n");
235
- return;
236
- }
237
-
238
- console.log(`\nDecisoes (${decisions.length}):`);
239
- console.log(`${"─".repeat(60)}`);
240
-
241
- for (const dec of decisions) {
242
- const taskRef = dec.task_ref ? ` [Task #${dec.task_ref}]` : "";
243
- const status = dec.status === "active" ? "" : ` (${dec.status})`;
244
- console.log(`${dec.id}: ${dec.title}${taskRef}${status}`);
245
- console.log(` ${dec.decision}`);
246
- if (dec.rationale) console.log(` Racional: ${dec.rationale}`);
247
- console.log();
248
- }
249
- }
1
+ import { getDb } from "../db/connection";
2
+ import { initSchema } from "../db/schema";
3
+
4
+ function getNextDecisionId(specId: string): string {
5
+ const db = getDb();
6
+ const last = db
7
+ .query("SELECT id FROM decisions WHERE spec_id = ? ORDER BY created_at DESC LIMIT 1")
8
+ .get(specId) as any;
9
+
10
+ if (!last) return "DEC-001";
11
+
12
+ const num = parseInt(last.id.replace("DEC-", "")) + 1;
13
+ return `DEC-${num.toString().padStart(3, "0")}`;
14
+ }
15
+
16
+ interface ConflictAnalysis {
17
+ hasConflict: boolean;
18
+ conflictingDecisions: Array<{
19
+ id: string;
20
+ title: string;
21
+ decision: string;
22
+ reason: string;
23
+ }>;
24
+ }
25
+
26
+ // Extrair keywords relevantes de um texto
27
+ function extractKeywords(text: string): string[] {
28
+ const stopWords = new Set([
29
+ "a", "o", "e", "de", "da", "do", "para", "com", "em", "que", "usar",
30
+ "the", "and", "or", "to", "for", "with", "in", "on", "is", "are", "be",
31
+ "will", "should", "must", "can", "use", "using", "como", "ser", "sera"
32
+ ]);
33
+
34
+ return text
35
+ .toLowerCase()
36
+ .replace(/[^\w\s-]/g, " ")
37
+ .split(/\s+/)
38
+ .filter((word) => word.length > 2 && !stopWords.has(word));
39
+ }
40
+
41
+ // Detectar conflitos semanticos entre decisoes
42
+ function detectConflicts(
43
+ newTitle: string,
44
+ newDecision: string,
45
+ existingDecisions: any[]
46
+ ): ConflictAnalysis {
47
+ const conflicts: ConflictAnalysis["conflictingDecisions"] = [];
48
+
49
+ const newKeywords = new Set([
50
+ ...extractKeywords(newTitle),
51
+ ...extractKeywords(newDecision),
52
+ ]);
53
+
54
+ // Padroes de conflito conhecidos
55
+ const conflictPatterns: Array<{ patterns: string[][]; reason: string }> = [
56
+ {
57
+ patterns: [["rest", "api"], ["graphql"]],
58
+ reason: "Conflito de paradigma de API (REST vs GraphQL)",
59
+ },
60
+ {
61
+ patterns: [["jwt", "token"], ["session", "cookie"]],
62
+ reason: "Conflito de estrategia de autenticacao",
63
+ },
64
+ {
65
+ patterns: [["zustand"], ["redux"]],
66
+ reason: "Conflito de biblioteca de estado",
67
+ },
68
+ {
69
+ patterns: [["prisma"], ["drizzle"]],
70
+ reason: "Conflito de ORM",
71
+ },
72
+ {
73
+ patterns: [["mysql"], ["postgres", "postgresql"]],
74
+ reason: "Conflito de banco de dados",
75
+ },
76
+ {
77
+ patterns: [["server", "component"], ["client", "component"]],
78
+ reason: "Conflito de tipo de componente padrao",
79
+ },
80
+ {
81
+ patterns: [["tailwind"], ["styled", "component"]],
82
+ reason: "Conflito de abordagem de styling",
83
+ },
84
+ {
85
+ patterns: [["vitest"], ["jest"]],
86
+ reason: "Conflito de framework de testes",
87
+ },
88
+ ];
89
+
90
+ for (const existing of existingDecisions) {
91
+ const existingKeywords = new Set([
92
+ ...extractKeywords(existing.title),
93
+ ...extractKeywords(existing.decision),
94
+ ]);
95
+
96
+ // Verificar padroes de conflito conhecidos
97
+ for (const { patterns, reason } of conflictPatterns) {
98
+ const [patternA, patternB] = patterns;
99
+
100
+ const newHasA = patternA.some((p) => newKeywords.has(p));
101
+ const newHasB = patternB.some((p) => newKeywords.has(p));
102
+ const existingHasA = patternA.some((p) => existingKeywords.has(p));
103
+ const existingHasB = patternB.some((p) => existingKeywords.has(p));
104
+
105
+ // Conflito: nova decisao menciona A, existente menciona B (ou vice-versa)
106
+ if ((newHasA && existingHasB) || (newHasB && existingHasA)) {
107
+ conflicts.push({
108
+ id: existing.id,
109
+ title: existing.title,
110
+ decision: existing.decision,
111
+ reason,
112
+ });
113
+ break; // Apenas um conflito por decisao existente
114
+ }
115
+ }
116
+
117
+ // Verificar sobreposicao alta de keywords (mesmo topico, decisoes diferentes?)
118
+ if (conflicts.every((c) => c.id !== existing.id)) {
119
+ const intersection = [...newKeywords].filter((k) => existingKeywords.has(k));
120
+ const similarity = intersection.length / Math.max(newKeywords.size, existingKeywords.size);
121
+
122
+ if (similarity > 0.4 && intersection.length >= 3) {
123
+ conflicts.push({
124
+ id: existing.id,
125
+ title: existing.title,
126
+ decision: existing.decision,
127
+ reason: `Alta sobreposicao de topico (${Math.round(similarity * 100)}% similar) - verificar se sao decisoes conflitantes`,
128
+ });
129
+ }
130
+ }
131
+ }
132
+
133
+ return {
134
+ hasConflict: conflicts.length > 0,
135
+ conflictingDecisions: conflicts,
136
+ };
137
+ }
138
+
139
+ export function decide(title: string, decision: string, options: { rationale?: string; force?: boolean }): void {
140
+ initSchema();
141
+
142
+ const db = getDb();
143
+ const now = new Date().toISOString();
144
+
145
+ const spec = db
146
+ .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
147
+ .get() as any;
148
+
149
+ if (!spec) {
150
+ console.error("\nNenhuma feature ativa.\n");
151
+ process.exit(1);
152
+ }
153
+
154
+ // Verificar conflitos com decisoes existentes
155
+ const existingDecisions = db
156
+ .query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
157
+ .all(spec.id) as any[];
158
+
159
+ if (existingDecisions.length > 0 && !options.force) {
160
+ const analysis = detectConflicts(title, decision, existingDecisions);
161
+
162
+ if (analysis.hasConflict) {
163
+ console.warn("\n⚠ POTENCIAL CONFLITO DETECTADO:\n");
164
+ console.warn(`Nova decisao: "${title}"`);
165
+ console.warn(` ${decision}\n`);
166
+
167
+ for (const conflict of analysis.conflictingDecisions) {
168
+ console.warn(`Pode conflitar com: ${conflict.id} - "${conflict.title}"`);
169
+ console.warn(` ${conflict.decision}`);
170
+ console.warn(` Motivo: ${conflict.reason}\n`);
171
+ }
172
+
173
+ console.warn(`${"─".repeat(50)}`);
174
+ console.warn(`Para registrar mesmo assim, use: decide "${title}" "${decision}" --force`);
175
+ console.warn(`Ou revise as decisoes existentes com: decisions\n`);
176
+ process.exit(1);
177
+ }
178
+ }
179
+
180
+ // Pegar task atual em execucao
181
+ const currentTask = db
182
+ .query("SELECT * FROM tasks WHERE spec_id = ? AND status = 'running' LIMIT 1")
183
+ .get(spec.id) as any;
184
+
185
+ const decisionId = getNextDecisionId(spec.id);
186
+
187
+ db.run(
188
+ `INSERT INTO decisions (id, spec_id, task_ref, title, decision, rationale, status, created_at)
189
+ VALUES (?, ?, ?, ?, ?, ?, 'active', ?)`,
190
+ [
191
+ decisionId,
192
+ spec.id,
193
+ currentTask?.number || null,
194
+ title,
195
+ decision,
196
+ options.rationale || null,
197
+ now,
198
+ ]
199
+ );
200
+
201
+ console.log(`\nDecisao registrada: ${decisionId}`);
202
+ console.log(`Titulo: ${title}`);
203
+ console.log(`Decisao: ${decision}`);
204
+ if (options.rationale) console.log(`Racional: ${options.rationale}`);
205
+ if (currentTask) console.log(`Task: #${currentTask.number}`);
206
+ if (options.force) console.log(`[!] Registrada com --force (conflito ignorado)`);
207
+ console.log();
208
+ }
209
+
210
+ export function listDecisions(json: boolean = false): void {
211
+ initSchema();
212
+
213
+ const db = getDb();
214
+
215
+ const spec = db
216
+ .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
217
+ .get() as any;
218
+
219
+ if (!spec) {
220
+ console.error("\nNenhuma feature ativa.\n");
221
+ process.exit(1);
222
+ }
223
+
224
+ const decisions = db
225
+ .query("SELECT * FROM decisions WHERE spec_id = ? ORDER BY created_at")
226
+ .all(spec.id) as any[];
227
+
228
+ if (json) {
229
+ console.log(JSON.stringify({ decisions }));
230
+ return;
231
+ }
232
+
233
+ if (decisions.length === 0) {
234
+ console.log("\nNenhuma decisao registrada.\n");
235
+ return;
236
+ }
237
+
238
+ console.log(`\nDecisoes (${decisions.length}):`);
239
+ console.log(`${"─".repeat(60)}`);
240
+
241
+ for (const dec of decisions) {
242
+ const taskRef = dec.task_ref ? ` [Task #${dec.task_ref}]` : "";
243
+ const status = dec.status === "active" ? "" : ` (${dec.status})`;
244
+ console.log(`${dec.id}: ${dec.title}${taskRef}${status}`);
245
+ console.log(` ${dec.decision}`);
246
+ if (dec.rationale) console.log(` Racional: ${dec.rationale}`);
247
+ console.log();
248
+ }
249
+ }
@@ -265,6 +265,9 @@ export function discoverConfirm(): void {
265
265
 
266
266
  const standardsCount = db.query("SELECT COUNT(*) as c FROM standards").get() as any;
267
267
 
268
+ // Auto-setup: deep-explore agent
269
+ ensureDeepExploreAgent();
270
+
268
271
  console.log("\nProjeto descoberto e configurado!");
269
272
  console.log(`Standards criados: ${standardsCount.c}`);
270
273
  console.log("\nArquivo gerado: .codexa/standards.md");
@@ -455,16 +458,6 @@ export async function discoverRefresh(options: { force?: boolean } = {}): Promis
455
458
  }
456
459
  }
457
460
 
458
- // Exportar detectStack para uso interno
459
- export { detectStack, detectStructure, detectFull };
460
-
461
- // Re-export detector utilities for advanced usage
462
- export {
463
- detectUniversal,
464
- formatDetectionResult,
465
- getDetailedTechnologies,
466
- } from "../detectors/loader";
467
-
468
461
  function generateStandardsMarkdown(): void {
469
462
  const db = getDb();
470
463
  const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
@@ -997,4 +990,83 @@ export function generatePatternsMarkdown(): void {
997
990
  export function discoverExportPatterns(): void {
998
991
  generatePatternsMarkdown();
999
992
  console.log("\n✓ Arquivo .codexa/patterns.md regenerado!\n");
993
+ }
994
+
995
+ // Auto-setup: garante que o agent deep-explore existe no projeto
996
+ export function ensureDeepExploreAgent(): void {
997
+ const agentPath = join(process.cwd(), ".claude", "agents", "deep-explore.md");
998
+
999
+ if (existsSync(agentPath)) return;
1000
+
1001
+ const agentsDir = join(process.cwd(), ".claude", "agents");
1002
+ if (!existsSync(agentsDir)) {
1003
+ mkdirSync(agentsDir, { recursive: true });
1004
+ }
1005
+
1006
+ const content = `---
1007
+ name: deep-explore
1008
+ description: Deep codebase exploration using grepai semantic search and call graph tracing. Use this agent for understanding code architecture, finding implementations by intent, analyzing function relationships, and exploring unfamiliar code areas.
1009
+ tools: Read, Grep, Glob, Bash
1010
+ color: cyan
1011
+ model: haiku
1012
+ ---
1013
+
1014
+ ## Instructions
1015
+
1016
+ You are a specialized code exploration agent with access to grepai semantic search and call graph tracing.
1017
+
1018
+ ### Primary Tools
1019
+
1020
+ #### 1. Semantic Search: \`grepai search\`
1021
+
1022
+ Use this to find code by intent and meaning:
1023
+
1024
+ \`\`\`bash
1025
+ # Use English queries for best results (--compact saves ~80% tokens)
1026
+ grepai search "authentication flow" --json --compact
1027
+ grepai search "error handling middleware" --json --compact
1028
+ grepai search "database connection management" --json --compact
1029
+ \`\`\`
1030
+
1031
+ #### 2. Call Graph Tracing: \`grepai trace\`
1032
+
1033
+ Use this to understand function relationships and code flow:
1034
+
1035
+ \`\`\`bash
1036
+ # Find all functions that call a symbol
1037
+ grepai trace callers "HandleRequest" --json
1038
+
1039
+ # Find all functions called by a symbol
1040
+ grepai trace callees "ProcessOrder" --json
1041
+
1042
+ # Build complete call graph
1043
+ grepai trace graph "ValidateToken" --depth 3 --json
1044
+ \`\`\`
1045
+
1046
+ Use \`grepai trace\` when you need to:
1047
+
1048
+ - Find all callers of a function
1049
+ - Understand the call hierarchy
1050
+ - Analyze the impact of changes to a function
1051
+ - Map dependencies between components
1052
+
1053
+ ### When to use standard tools
1054
+
1055
+ Only fall back to Grep/Glob when:
1056
+
1057
+ - You need exact text matching (variable names, imports)
1058
+ - grepai is not available or returns errors
1059
+ - You need file path patterns
1060
+
1061
+ ### Workflow
1062
+
1063
+ 1. Start with \`grepai search\` to find relevant code semantically
1064
+ 2. Use \`grepai trace\` to understand function relationships and call graphs
1065
+ 3. Use \`Read\` to examine promising files in detail
1066
+ 4. Use Grep only for exact string searches if needed
1067
+ 5. Synthesize findings into a clear summary
1068
+ `;
1069
+
1070
+ writeFileSync(agentPath, content);
1071
+ console.log("✓ Agent deep-explore instalado em .claude/agents/deep-explore.md");
1000
1072
  }