@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/decide.ts
CHANGED
|
@@ -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
|
+
}
|
package/commands/discover.ts
CHANGED
|
@@ -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
|
}
|