@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/plan.ts
CHANGED
|
@@ -1,376 +1,376 @@
|
|
|
1
|
-
import { getDb } from "../db/connection";
|
|
2
|
-
import { initSchema, getArchitecturalAnalysisForSpec } from "../db/schema";
|
|
3
|
-
|
|
4
|
-
function generateSpecId(name: string): string {
|
|
5
|
-
const date = new Date().toISOString().split("T")[0];
|
|
6
|
-
const slug = name
|
|
7
|
-
.toLowerCase()
|
|
8
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
9
|
-
.substring(0, 30);
|
|
10
|
-
return `${date}-${slug}`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// v8.4: Suporte a --from-analysis para import automatico de baby steps
|
|
14
|
-
export function planStart(description: string, options: { fromAnalysis?: string; json?: boolean } = {}): void {
|
|
15
|
-
initSchema();
|
|
16
|
-
const db = getDb();
|
|
17
|
-
|
|
18
|
-
// Verificar se ja existe spec ativo (ignorar completed e cancelled)
|
|
19
|
-
const existing = db
|
|
20
|
-
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
21
|
-
.get() as any;
|
|
22
|
-
|
|
23
|
-
if (existing) {
|
|
24
|
-
if (options.json) {
|
|
25
|
-
console.log(JSON.stringify({
|
|
26
|
-
error: "FEATURE_ACTIVE",
|
|
27
|
-
name: existing.name,
|
|
28
|
-
id: existing.id,
|
|
29
|
-
phase: existing.phase,
|
|
30
|
-
}));
|
|
31
|
-
} else {
|
|
32
|
-
console.error(`\nJa existe uma feature ativa: ${existing.name} (${existing.id})`);
|
|
33
|
-
console.error(`Fase atual: ${existing.phase}`);
|
|
34
|
-
console.error(`\nPara continuar, use: status`);
|
|
35
|
-
console.error(`Para cancelar a atual, use: plan cancel\n`);
|
|
36
|
-
}
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// v8.4: Buscar analise arquitetural se --from-analysis fornecido
|
|
41
|
-
let analysis: any = null;
|
|
42
|
-
if (options.fromAnalysis) {
|
|
43
|
-
analysis = db.query(
|
|
44
|
-
"SELECT * FROM architectural_analyses WHERE id = ?"
|
|
45
|
-
).get(options.fromAnalysis) as any;
|
|
46
|
-
|
|
47
|
-
if (!analysis) {
|
|
48
|
-
if (options.json) {
|
|
49
|
-
console.log(JSON.stringify({ error: "ANALYSIS_NOT_FOUND", id: options.fromAnalysis }));
|
|
50
|
-
} else {
|
|
51
|
-
console.error(`\n[ERRO] Analise arquitetural '${options.fromAnalysis}' nao encontrada.\n`);
|
|
52
|
-
}
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (analysis.status !== "approved") {
|
|
57
|
-
if (options.json) {
|
|
58
|
-
console.log(JSON.stringify({ error: "ANALYSIS_NOT_APPROVED", id: analysis.id, status: analysis.status }));
|
|
59
|
-
} else {
|
|
60
|
-
console.error(`\n[ERRO] Analise '${analysis.id}' nao esta aprovada (status: ${analysis.status}).`);
|
|
61
|
-
console.error("Use 'architect approve' primeiro.\n");
|
|
62
|
-
}
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
} else {
|
|
66
|
-
// v8.4: Auto-deteccao por nome
|
|
67
|
-
const match = getArchitecturalAnalysisForSpec(description);
|
|
68
|
-
if (match) {
|
|
69
|
-
analysis = match;
|
|
70
|
-
if (!options.json) {
|
|
71
|
-
console.log(`\n[INFO] Analise arquitetural encontrada: ${match.id} (${match.name})`);
|
|
72
|
-
console.log("Importando baby steps automaticamente...\n");
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const specId = generateSpecId(description);
|
|
78
|
-
const now = new Date().toISOString();
|
|
79
|
-
|
|
80
|
-
// Criar spec (com analysis_id se disponivel)
|
|
81
|
-
if (analysis) {
|
|
82
|
-
db.run(
|
|
83
|
-
"INSERT INTO specs (id, name, phase, analysis_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
84
|
-
[specId, description, "planning", analysis.id, now, now]
|
|
85
|
-
);
|
|
86
|
-
} else {
|
|
87
|
-
db.run(
|
|
88
|
-
"INSERT INTO specs (id, name, phase, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
89
|
-
[specId, description, "planning", now, now]
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Criar contexto inicial
|
|
94
|
-
db.run(
|
|
95
|
-
"INSERT INTO context (spec_id, objective, updated_at) VALUES (?, ?, ?)",
|
|
96
|
-
[specId, description, now]
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// v8.4: Import automatico de baby steps da analise
|
|
100
|
-
let tasksCreated = 0;
|
|
101
|
-
if (analysis && analysis.baby_steps) {
|
|
102
|
-
try {
|
|
103
|
-
const babySteps = JSON.parse(analysis.baby_steps);
|
|
104
|
-
for (const step of babySteps) {
|
|
105
|
-
const files = step.files && step.files.length > 0 ? JSON.stringify(step.files) : null;
|
|
106
|
-
const dependsOn = step.dependsOn && step.dependsOn.length > 0 ? JSON.stringify(step.dependsOn) : null;
|
|
107
|
-
|
|
108
|
-
db.run(
|
|
109
|
-
`INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files)
|
|
110
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
111
|
-
[specId, step.number, step.name, step.agent || null, dependsOn, 1, files]
|
|
112
|
-
);
|
|
113
|
-
tasksCreated++;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Atualizar total de tasks no contexto
|
|
117
|
-
db.run("UPDATE context SET total_tasks = ?, updated_at = ? WHERE spec_id = ?", [
|
|
118
|
-
tasksCreated, now, specId
|
|
119
|
-
]);
|
|
120
|
-
|
|
121
|
-
// Marcar analise como implemented
|
|
122
|
-
db.run(
|
|
123
|
-
"UPDATE architectural_analyses SET status = 'implemented', updated_at = ? WHERE id = ?",
|
|
124
|
-
[now, analysis.id]
|
|
125
|
-
);
|
|
126
|
-
} catch {
|
|
127
|
-
if (!options.json) {
|
|
128
|
-
console.error("[AVISO] Falha ao parsear baby steps da analise. Adicione tasks manualmente.\n");
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (options.json) {
|
|
134
|
-
console.log(JSON.stringify({
|
|
135
|
-
success: true,
|
|
136
|
-
specId,
|
|
137
|
-
name: description,
|
|
138
|
-
phase: "planning",
|
|
139
|
-
analysisId: analysis?.id || null,
|
|
140
|
-
tasksImported: tasksCreated,
|
|
141
|
-
}));
|
|
142
|
-
} else {
|
|
143
|
-
console.log(`\nFeature iniciada: ${description}`);
|
|
144
|
-
console.log(`Spec ID: ${specId}`);
|
|
145
|
-
console.log(`Fase: planning`);
|
|
146
|
-
|
|
147
|
-
if (tasksCreated > 0) {
|
|
148
|
-
console.log(`Analise: ${analysis.id}`);
|
|
149
|
-
console.log(`Tasks importadas: ${tasksCreated}`);
|
|
150
|
-
console.log(`\nProximos passos:`);
|
|
151
|
-
console.log(`1. Visualize o plano: plan show`);
|
|
152
|
-
console.log(`2. Solicite aprovacao: check request\n`);
|
|
153
|
-
} else {
|
|
154
|
-
console.log(`\nProximos passos:`);
|
|
155
|
-
console.log(`1. Adicione tasks com: plan task-add --name "..." --agent "..."`);
|
|
156
|
-
console.log(`2. Visualize o plano com: plan show`);
|
|
157
|
-
console.log(`3. Solicite aprovacao com: check request\n`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function planShow(json: boolean = false): void {
|
|
163
|
-
initSchema();
|
|
164
|
-
const db = getDb();
|
|
165
|
-
|
|
166
|
-
const spec = db
|
|
167
|
-
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
168
|
-
.get() as any;
|
|
169
|
-
|
|
170
|
-
if (!spec) {
|
|
171
|
-
console.error("\nNenhuma feature ativa.");
|
|
172
|
-
console.error("Inicie com: /codexa:feature'\n");
|
|
173
|
-
process.exit(1);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const tasks = db
|
|
177
|
-
.query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
|
|
178
|
-
.all(spec.id) as any[];
|
|
179
|
-
|
|
180
|
-
const context = db
|
|
181
|
-
.query("SELECT * FROM context WHERE spec_id = ?")
|
|
182
|
-
.get(spec.id) as any;
|
|
183
|
-
|
|
184
|
-
if (json) {
|
|
185
|
-
console.log(JSON.stringify({ spec, context, tasks }, null, 2));
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
console.log(`\n${"=".repeat(60)}`);
|
|
190
|
-
console.log(`PLANO: ${spec.name}`);
|
|
191
|
-
console.log(`${"=".repeat(60)}`);
|
|
192
|
-
console.log(`Spec ID: ${spec.id}`);
|
|
193
|
-
console.log(`Fase: ${spec.phase}`);
|
|
194
|
-
console.log(`Aprovado: ${spec.approved_at ? "Sim" : "Nao"}`);
|
|
195
|
-
console.log(`\nObjetivo: ${context?.objective || "-"}`);
|
|
196
|
-
|
|
197
|
-
if (tasks.length === 0) {
|
|
198
|
-
console.log(`\nNenhuma task definida.`);
|
|
199
|
-
console.log(`Adicione com: plan task-add --name "..." --agent "..."\n`);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
console.log(`\nTasks (${tasks.length}):`);
|
|
204
|
-
console.log(`${"─".repeat(60)}`);
|
|
205
|
-
|
|
206
|
-
for (const task of tasks) {
|
|
207
|
-
const deps = task.depends_on ? JSON.parse(task.depends_on) : [];
|
|
208
|
-
const depsStr = deps.length > 0 ? ` [deps: ${deps.join(",")}]` : "";
|
|
209
|
-
const parallelStr = task.can_parallel ? " ||" : "";
|
|
210
|
-
const statusIcon =
|
|
211
|
-
task.status === "done" ? "[x]" :
|
|
212
|
-
task.status === "running" ? "[>]" :
|
|
213
|
-
task.status === "failed" ? "[!]" : "[ ]";
|
|
214
|
-
|
|
215
|
-
console.log(`${statusIcon} #${task.number}: ${task.name} (${task.agent || "geral"})${depsStr}${parallelStr}`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
console.log(`${"─".repeat(60)}`);
|
|
219
|
-
console.log(`Legenda: [ ] pendente [>] executando [x] concluido || paralelizavel\n`);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function planTaskAdd(options: {
|
|
223
|
-
name: string;
|
|
224
|
-
agent?: string;
|
|
225
|
-
depends?: string;
|
|
226
|
-
files?: string;
|
|
227
|
-
sequential?: boolean;
|
|
228
|
-
}): void {
|
|
229
|
-
initSchema();
|
|
230
|
-
const db = getDb();
|
|
231
|
-
|
|
232
|
-
const spec = db
|
|
233
|
-
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
234
|
-
.get() as any;
|
|
235
|
-
|
|
236
|
-
if (!spec) {
|
|
237
|
-
console.error("\nNenhuma feature ativa.");
|
|
238
|
-
console.error("Inicie com: /codexa:feature\n");
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (spec.phase !== "planning") {
|
|
243
|
-
console.error(`\nNao e possivel adicionar tasks na fase '${spec.phase}'.`);
|
|
244
|
-
console.error("Tasks so podem ser adicionadas na fase 'planning'.\n");
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Pegar proximo numero
|
|
249
|
-
const lastTask = db
|
|
250
|
-
.query("SELECT MAX(number) as max FROM tasks WHERE spec_id = ?")
|
|
251
|
-
.get(spec.id) as any;
|
|
252
|
-
const nextNumber = (lastTask?.max || 0) + 1;
|
|
253
|
-
|
|
254
|
-
// Validar dependencias
|
|
255
|
-
let dependsOn: number[] = [];
|
|
256
|
-
if (options.depends) {
|
|
257
|
-
dependsOn = options.depends.split(",").map((s) => parseInt(s.trim()));
|
|
258
|
-
for (const depId of dependsOn) {
|
|
259
|
-
const exists = db
|
|
260
|
-
.query("SELECT id FROM tasks WHERE spec_id = ? AND number = ?")
|
|
261
|
-
.get(spec.id, depId);
|
|
262
|
-
if (!exists) {
|
|
263
|
-
console.error(`\nDependencia invalida: task #${depId} nao existe.\n`);
|
|
264
|
-
process.exit(2);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// v8.1: Detectar dependencias circulares
|
|
269
|
-
// Construir grafo de dependencias e verificar se adicionar esta task cria ciclo
|
|
270
|
-
const allTasks = db
|
|
271
|
-
.query("SELECT number, depends_on FROM tasks WHERE spec_id = ?")
|
|
272
|
-
.all(spec.id) as any[];
|
|
273
|
-
|
|
274
|
-
// Grafo: taskNumber -> [dependencias]
|
|
275
|
-
const graph = new Map<number, number[]>();
|
|
276
|
-
for (const t of allTasks) {
|
|
277
|
-
graph.set(t.number, t.depends_on ? JSON.parse(t.depends_on) : []);
|
|
278
|
-
}
|
|
279
|
-
// Adicionar a nova task ao grafo temporariamente
|
|
280
|
-
graph.set(nextNumber, dependsOn);
|
|
281
|
-
|
|
282
|
-
// DFS para detectar ciclos
|
|
283
|
-
const visited = new Set<number>();
|
|
284
|
-
const inStack = new Set<number>();
|
|
285
|
-
|
|
286
|
-
function hasCycle(node: number): boolean {
|
|
287
|
-
if (inStack.has(node)) return true;
|
|
288
|
-
if (visited.has(node)) return false;
|
|
289
|
-
|
|
290
|
-
visited.add(node);
|
|
291
|
-
inStack.add(node);
|
|
292
|
-
|
|
293
|
-
const deps = graph.get(node) || [];
|
|
294
|
-
for (const dep of deps) {
|
|
295
|
-
if (hasCycle(dep)) return true;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
inStack.delete(node);
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (hasCycle(nextNumber)) {
|
|
303
|
-
console.error(`\nERRO: Dependencia circular detectada!`);
|
|
304
|
-
console.error(`Task #${nextNumber} -> [${dependsOn.join(", ")}] cria um ciclo.`);
|
|
305
|
-
console.error(`Corrija as dependencias para evitar deadlocks.\n`);
|
|
306
|
-
process.exit(2);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Parsear arquivos
|
|
311
|
-
let files: string[] = [];
|
|
312
|
-
if (options.files) {
|
|
313
|
-
files = options.files.split(",").map((s) => s.trim());
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
db.run(
|
|
317
|
-
`INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files)
|
|
318
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
319
|
-
[
|
|
320
|
-
spec.id,
|
|
321
|
-
nextNumber,
|
|
322
|
-
options.name,
|
|
323
|
-
options.agent || null,
|
|
324
|
-
dependsOn.length > 0 ? JSON.stringify(dependsOn) : null,
|
|
325
|
-
options.sequential ? 0 : 1,
|
|
326
|
-
files.length > 0 ? JSON.stringify(files) : null,
|
|
327
|
-
]
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
// Atualizar total de tasks no contexto
|
|
331
|
-
const count = db.query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?").get(spec.id) as any;
|
|
332
|
-
db.run("UPDATE context SET total_tasks = ?, updated_at = ? WHERE spec_id = ?", [
|
|
333
|
-
count.c,
|
|
334
|
-
new Date().toISOString(),
|
|
335
|
-
spec.id,
|
|
336
|
-
]);
|
|
337
|
-
|
|
338
|
-
console.log(`\nTask #${nextNumber} adicionada: ${options.name}`);
|
|
339
|
-
if (options.agent) console.log(` Agente: ${options.agent}`);
|
|
340
|
-
if (dependsOn.length > 0) console.log(` Depende de: ${dependsOn.join(", ")}`);
|
|
341
|
-
if (files.length > 0) console.log(` Arquivos: ${files.join(", ")}`);
|
|
342
|
-
console.log(` Paralelizavel: ${options.sequential ? "Nao" : "Sim"}\n`);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export function planCancel(): void {
|
|
346
|
-
initSchema();
|
|
347
|
-
const db = getDb();
|
|
348
|
-
|
|
349
|
-
const spec = db
|
|
350
|
-
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
351
|
-
.get() as any;
|
|
352
|
-
|
|
353
|
-
if (!spec) {
|
|
354
|
-
console.error("\nNenhuma feature ativa para cancelar.\n");
|
|
355
|
-
process.exit(1);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const now = new Date().toISOString();
|
|
359
|
-
|
|
360
|
-
// Marcar spec como cancelled
|
|
361
|
-
db.run(
|
|
362
|
-
"UPDATE specs SET phase = 'cancelled', updated_at = ? WHERE id = ?",
|
|
363
|
-
[now, spec.id]
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
// Marcar tasks pendentes/running como cancelled
|
|
367
|
-
const updatedTasks = db.run(
|
|
368
|
-
"UPDATE tasks SET status = 'cancelled' WHERE spec_id = ? AND status IN ('pending', 'running')",
|
|
369
|
-
[spec.id]
|
|
370
|
-
);
|
|
371
|
-
|
|
372
|
-
console.log(`\nFeature cancelada: ${spec.name}`);
|
|
373
|
-
console.log(`Spec ID: ${spec.id}`);
|
|
374
|
-
console.log(`Tasks canceladas: ${updatedTasks.changes}`);
|
|
375
|
-
console.log(`\nVoce pode iniciar uma nova feature com: plan start "..."\n`);
|
|
376
|
-
}
|
|
1
|
+
import { getDb } from "../db/connection";
|
|
2
|
+
import { initSchema, getArchitecturalAnalysisForSpec } from "../db/schema";
|
|
3
|
+
|
|
4
|
+
function generateSpecId(name: string): string {
|
|
5
|
+
const date = new Date().toISOString().split("T")[0];
|
|
6
|
+
const slug = name
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
9
|
+
.substring(0, 30);
|
|
10
|
+
return `${date}-${slug}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// v8.4: Suporte a --from-analysis para import automatico de baby steps
|
|
14
|
+
export function planStart(description: string, options: { fromAnalysis?: string; json?: boolean } = {}): void {
|
|
15
|
+
initSchema();
|
|
16
|
+
const db = getDb();
|
|
17
|
+
|
|
18
|
+
// Verificar se ja existe spec ativo (ignorar completed e cancelled)
|
|
19
|
+
const existing = db
|
|
20
|
+
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
21
|
+
.get() as any;
|
|
22
|
+
|
|
23
|
+
if (existing) {
|
|
24
|
+
if (options.json) {
|
|
25
|
+
console.log(JSON.stringify({
|
|
26
|
+
error: "FEATURE_ACTIVE",
|
|
27
|
+
name: existing.name,
|
|
28
|
+
id: existing.id,
|
|
29
|
+
phase: existing.phase,
|
|
30
|
+
}));
|
|
31
|
+
} else {
|
|
32
|
+
console.error(`\nJa existe uma feature ativa: ${existing.name} (${existing.id})`);
|
|
33
|
+
console.error(`Fase atual: ${existing.phase}`);
|
|
34
|
+
console.error(`\nPara continuar, use: status`);
|
|
35
|
+
console.error(`Para cancelar a atual, use: plan cancel\n`);
|
|
36
|
+
}
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// v8.4: Buscar analise arquitetural se --from-analysis fornecido
|
|
41
|
+
let analysis: any = null;
|
|
42
|
+
if (options.fromAnalysis) {
|
|
43
|
+
analysis = db.query(
|
|
44
|
+
"SELECT * FROM architectural_analyses WHERE id = ?"
|
|
45
|
+
).get(options.fromAnalysis) as any;
|
|
46
|
+
|
|
47
|
+
if (!analysis) {
|
|
48
|
+
if (options.json) {
|
|
49
|
+
console.log(JSON.stringify({ error: "ANALYSIS_NOT_FOUND", id: options.fromAnalysis }));
|
|
50
|
+
} else {
|
|
51
|
+
console.error(`\n[ERRO] Analise arquitetural '${options.fromAnalysis}' nao encontrada.\n`);
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (analysis.status !== "approved") {
|
|
57
|
+
if (options.json) {
|
|
58
|
+
console.log(JSON.stringify({ error: "ANALYSIS_NOT_APPROVED", id: analysis.id, status: analysis.status }));
|
|
59
|
+
} else {
|
|
60
|
+
console.error(`\n[ERRO] Analise '${analysis.id}' nao esta aprovada (status: ${analysis.status}).`);
|
|
61
|
+
console.error("Use 'architect approve' primeiro.\n");
|
|
62
|
+
}
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// v8.4: Auto-deteccao por nome
|
|
67
|
+
const match = getArchitecturalAnalysisForSpec(description);
|
|
68
|
+
if (match) {
|
|
69
|
+
analysis = match;
|
|
70
|
+
if (!options.json) {
|
|
71
|
+
console.log(`\n[INFO] Analise arquitetural encontrada: ${match.id} (${match.name})`);
|
|
72
|
+
console.log("Importando baby steps automaticamente...\n");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const specId = generateSpecId(description);
|
|
78
|
+
const now = new Date().toISOString();
|
|
79
|
+
|
|
80
|
+
// Criar spec (com analysis_id se disponivel)
|
|
81
|
+
if (analysis) {
|
|
82
|
+
db.run(
|
|
83
|
+
"INSERT INTO specs (id, name, phase, analysis_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
84
|
+
[specId, description, "planning", analysis.id, now, now]
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
db.run(
|
|
88
|
+
"INSERT INTO specs (id, name, phase, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
89
|
+
[specId, description, "planning", now, now]
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Criar contexto inicial
|
|
94
|
+
db.run(
|
|
95
|
+
"INSERT INTO context (spec_id, objective, updated_at) VALUES (?, ?, ?)",
|
|
96
|
+
[specId, description, now]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// v8.4: Import automatico de baby steps da analise
|
|
100
|
+
let tasksCreated = 0;
|
|
101
|
+
if (analysis && analysis.baby_steps) {
|
|
102
|
+
try {
|
|
103
|
+
const babySteps = JSON.parse(analysis.baby_steps);
|
|
104
|
+
for (const step of babySteps) {
|
|
105
|
+
const files = step.files && step.files.length > 0 ? JSON.stringify(step.files) : null;
|
|
106
|
+
const dependsOn = step.dependsOn && step.dependsOn.length > 0 ? JSON.stringify(step.dependsOn) : null;
|
|
107
|
+
|
|
108
|
+
db.run(
|
|
109
|
+
`INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files)
|
|
110
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
111
|
+
[specId, step.number, step.name, step.agent || null, dependsOn, 1, files]
|
|
112
|
+
);
|
|
113
|
+
tasksCreated++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Atualizar total de tasks no contexto
|
|
117
|
+
db.run("UPDATE context SET total_tasks = ?, updated_at = ? WHERE spec_id = ?", [
|
|
118
|
+
tasksCreated, now, specId
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
// Marcar analise como implemented
|
|
122
|
+
db.run(
|
|
123
|
+
"UPDATE architectural_analyses SET status = 'implemented', updated_at = ? WHERE id = ?",
|
|
124
|
+
[now, analysis.id]
|
|
125
|
+
);
|
|
126
|
+
} catch {
|
|
127
|
+
if (!options.json) {
|
|
128
|
+
console.error("[AVISO] Falha ao parsear baby steps da analise. Adicione tasks manualmente.\n");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (options.json) {
|
|
134
|
+
console.log(JSON.stringify({
|
|
135
|
+
success: true,
|
|
136
|
+
specId,
|
|
137
|
+
name: description,
|
|
138
|
+
phase: "planning",
|
|
139
|
+
analysisId: analysis?.id || null,
|
|
140
|
+
tasksImported: tasksCreated,
|
|
141
|
+
}));
|
|
142
|
+
} else {
|
|
143
|
+
console.log(`\nFeature iniciada: ${description}`);
|
|
144
|
+
console.log(`Spec ID: ${specId}`);
|
|
145
|
+
console.log(`Fase: planning`);
|
|
146
|
+
|
|
147
|
+
if (tasksCreated > 0) {
|
|
148
|
+
console.log(`Analise: ${analysis.id}`);
|
|
149
|
+
console.log(`Tasks importadas: ${tasksCreated}`);
|
|
150
|
+
console.log(`\nProximos passos:`);
|
|
151
|
+
console.log(`1. Visualize o plano: plan show`);
|
|
152
|
+
console.log(`2. Solicite aprovacao: check request\n`);
|
|
153
|
+
} else {
|
|
154
|
+
console.log(`\nProximos passos:`);
|
|
155
|
+
console.log(`1. Adicione tasks com: plan task-add --name "..." --agent "..."`);
|
|
156
|
+
console.log(`2. Visualize o plano com: plan show`);
|
|
157
|
+
console.log(`3. Solicite aprovacao com: check request\n`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function planShow(json: boolean = false): void {
|
|
163
|
+
initSchema();
|
|
164
|
+
const db = getDb();
|
|
165
|
+
|
|
166
|
+
const spec = db
|
|
167
|
+
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
168
|
+
.get() as any;
|
|
169
|
+
|
|
170
|
+
if (!spec) {
|
|
171
|
+
console.error("\nNenhuma feature ativa.");
|
|
172
|
+
console.error("Inicie com: /codexa:feature'\n");
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const tasks = db
|
|
177
|
+
.query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
|
|
178
|
+
.all(spec.id) as any[];
|
|
179
|
+
|
|
180
|
+
const context = db
|
|
181
|
+
.query("SELECT * FROM context WHERE spec_id = ?")
|
|
182
|
+
.get(spec.id) as any;
|
|
183
|
+
|
|
184
|
+
if (json) {
|
|
185
|
+
console.log(JSON.stringify({ spec, context, tasks }, null, 2));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(`\n${"=".repeat(60)}`);
|
|
190
|
+
console.log(`PLANO: ${spec.name}`);
|
|
191
|
+
console.log(`${"=".repeat(60)}`);
|
|
192
|
+
console.log(`Spec ID: ${spec.id}`);
|
|
193
|
+
console.log(`Fase: ${spec.phase}`);
|
|
194
|
+
console.log(`Aprovado: ${spec.approved_at ? "Sim" : "Nao"}`);
|
|
195
|
+
console.log(`\nObjetivo: ${context?.objective || "-"}`);
|
|
196
|
+
|
|
197
|
+
if (tasks.length === 0) {
|
|
198
|
+
console.log(`\nNenhuma task definida.`);
|
|
199
|
+
console.log(`Adicione com: plan task-add --name "..." --agent "..."\n`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(`\nTasks (${tasks.length}):`);
|
|
204
|
+
console.log(`${"─".repeat(60)}`);
|
|
205
|
+
|
|
206
|
+
for (const task of tasks) {
|
|
207
|
+
const deps = task.depends_on ? JSON.parse(task.depends_on) : [];
|
|
208
|
+
const depsStr = deps.length > 0 ? ` [deps: ${deps.join(",")}]` : "";
|
|
209
|
+
const parallelStr = task.can_parallel ? " ||" : "";
|
|
210
|
+
const statusIcon =
|
|
211
|
+
task.status === "done" ? "[x]" :
|
|
212
|
+
task.status === "running" ? "[>]" :
|
|
213
|
+
task.status === "failed" ? "[!]" : "[ ]";
|
|
214
|
+
|
|
215
|
+
console.log(`${statusIcon} #${task.number}: ${task.name} (${task.agent || "geral"})${depsStr}${parallelStr}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log(`${"─".repeat(60)}`);
|
|
219
|
+
console.log(`Legenda: [ ] pendente [>] executando [x] concluido || paralelizavel\n`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function planTaskAdd(options: {
|
|
223
|
+
name: string;
|
|
224
|
+
agent?: string;
|
|
225
|
+
depends?: string;
|
|
226
|
+
files?: string;
|
|
227
|
+
sequential?: boolean;
|
|
228
|
+
}): void {
|
|
229
|
+
initSchema();
|
|
230
|
+
const db = getDb();
|
|
231
|
+
|
|
232
|
+
const spec = db
|
|
233
|
+
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
234
|
+
.get() as any;
|
|
235
|
+
|
|
236
|
+
if (!spec) {
|
|
237
|
+
console.error("\nNenhuma feature ativa.");
|
|
238
|
+
console.error("Inicie com: /codexa:feature\n");
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (spec.phase !== "planning") {
|
|
243
|
+
console.error(`\nNao e possivel adicionar tasks na fase '${spec.phase}'.`);
|
|
244
|
+
console.error("Tasks so podem ser adicionadas na fase 'planning'.\n");
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Pegar proximo numero
|
|
249
|
+
const lastTask = db
|
|
250
|
+
.query("SELECT MAX(number) as max FROM tasks WHERE spec_id = ?")
|
|
251
|
+
.get(spec.id) as any;
|
|
252
|
+
const nextNumber = (lastTask?.max || 0) + 1;
|
|
253
|
+
|
|
254
|
+
// Validar dependencias
|
|
255
|
+
let dependsOn: number[] = [];
|
|
256
|
+
if (options.depends) {
|
|
257
|
+
dependsOn = options.depends.split(",").map((s) => parseInt(s.trim()));
|
|
258
|
+
for (const depId of dependsOn) {
|
|
259
|
+
const exists = db
|
|
260
|
+
.query("SELECT id FROM tasks WHERE spec_id = ? AND number = ?")
|
|
261
|
+
.get(spec.id, depId);
|
|
262
|
+
if (!exists) {
|
|
263
|
+
console.error(`\nDependencia invalida: task #${depId} nao existe.\n`);
|
|
264
|
+
process.exit(2);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// v8.1: Detectar dependencias circulares
|
|
269
|
+
// Construir grafo de dependencias e verificar se adicionar esta task cria ciclo
|
|
270
|
+
const allTasks = db
|
|
271
|
+
.query("SELECT number, depends_on FROM tasks WHERE spec_id = ?")
|
|
272
|
+
.all(spec.id) as any[];
|
|
273
|
+
|
|
274
|
+
// Grafo: taskNumber -> [dependencias]
|
|
275
|
+
const graph = new Map<number, number[]>();
|
|
276
|
+
for (const t of allTasks) {
|
|
277
|
+
graph.set(t.number, t.depends_on ? JSON.parse(t.depends_on) : []);
|
|
278
|
+
}
|
|
279
|
+
// Adicionar a nova task ao grafo temporariamente
|
|
280
|
+
graph.set(nextNumber, dependsOn);
|
|
281
|
+
|
|
282
|
+
// DFS para detectar ciclos
|
|
283
|
+
const visited = new Set<number>();
|
|
284
|
+
const inStack = new Set<number>();
|
|
285
|
+
|
|
286
|
+
function hasCycle(node: number): boolean {
|
|
287
|
+
if (inStack.has(node)) return true;
|
|
288
|
+
if (visited.has(node)) return false;
|
|
289
|
+
|
|
290
|
+
visited.add(node);
|
|
291
|
+
inStack.add(node);
|
|
292
|
+
|
|
293
|
+
const deps = graph.get(node) || [];
|
|
294
|
+
for (const dep of deps) {
|
|
295
|
+
if (hasCycle(dep)) return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
inStack.delete(node);
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (hasCycle(nextNumber)) {
|
|
303
|
+
console.error(`\nERRO: Dependencia circular detectada!`);
|
|
304
|
+
console.error(`Task #${nextNumber} -> [${dependsOn.join(", ")}] cria um ciclo.`);
|
|
305
|
+
console.error(`Corrija as dependencias para evitar deadlocks.\n`);
|
|
306
|
+
process.exit(2);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Parsear arquivos
|
|
311
|
+
let files: string[] = [];
|
|
312
|
+
if (options.files) {
|
|
313
|
+
files = options.files.split(",").map((s) => s.trim());
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
db.run(
|
|
317
|
+
`INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, files)
|
|
318
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
319
|
+
[
|
|
320
|
+
spec.id,
|
|
321
|
+
nextNumber,
|
|
322
|
+
options.name,
|
|
323
|
+
options.agent || null,
|
|
324
|
+
dependsOn.length > 0 ? JSON.stringify(dependsOn) : null,
|
|
325
|
+
options.sequential ? 0 : 1,
|
|
326
|
+
files.length > 0 ? JSON.stringify(files) : null,
|
|
327
|
+
]
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Atualizar total de tasks no contexto
|
|
331
|
+
const count = db.query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?").get(spec.id) as any;
|
|
332
|
+
db.run("UPDATE context SET total_tasks = ?, updated_at = ? WHERE spec_id = ?", [
|
|
333
|
+
count.c,
|
|
334
|
+
new Date().toISOString(),
|
|
335
|
+
spec.id,
|
|
336
|
+
]);
|
|
337
|
+
|
|
338
|
+
console.log(`\nTask #${nextNumber} adicionada: ${options.name}`);
|
|
339
|
+
if (options.agent) console.log(` Agente: ${options.agent}`);
|
|
340
|
+
if (dependsOn.length > 0) console.log(` Depende de: ${dependsOn.join(", ")}`);
|
|
341
|
+
if (files.length > 0) console.log(` Arquivos: ${files.join(", ")}`);
|
|
342
|
+
console.log(` Paralelizavel: ${options.sequential ? "Nao" : "Sim"}\n`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function planCancel(): void {
|
|
346
|
+
initSchema();
|
|
347
|
+
const db = getDb();
|
|
348
|
+
|
|
349
|
+
const spec = db
|
|
350
|
+
.query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
|
|
351
|
+
.get() as any;
|
|
352
|
+
|
|
353
|
+
if (!spec) {
|
|
354
|
+
console.error("\nNenhuma feature ativa para cancelar.\n");
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const now = new Date().toISOString();
|
|
359
|
+
|
|
360
|
+
// Marcar spec como cancelled
|
|
361
|
+
db.run(
|
|
362
|
+
"UPDATE specs SET phase = 'cancelled', updated_at = ? WHERE id = ?",
|
|
363
|
+
[now, spec.id]
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// Marcar tasks pendentes/running como cancelled
|
|
367
|
+
const updatedTasks = db.run(
|
|
368
|
+
"UPDATE tasks SET status = 'cancelled' WHERE spec_id = ? AND status IN ('pending', 'running')",
|
|
369
|
+
[spec.id]
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
console.log(`\nFeature cancelada: ${spec.name}`);
|
|
373
|
+
console.log(`Spec ID: ${spec.id}`);
|
|
374
|
+
console.log(`Tasks canceladas: ${updatedTasks.changes}`);
|
|
375
|
+
console.log(`\nVoce pode iniciar uma nova feature com: plan start "..."\n`);
|
|
376
|
+
}
|