@codexa/cli 8.6.0 → 8.6.9
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 -760
- package/commands/check.ts +131 -131
- package/commands/clear.ts +170 -170
- package/commands/decide.ts +249 -249
- package/commands/discover.ts +1071 -1071
- package/commands/knowledge.ts +361 -361
- package/commands/patterns.ts +621 -621
- package/commands/plan.ts +376 -376
- package/commands/product.ts +626 -626
- package/commands/research.ts +754 -754
- package/commands/review.ts +463 -463
- package/commands/standards.ts +200 -200
- package/commands/task.ts +623 -623
- package/commands/utils.ts +1021 -1021
- package/db/connection.ts +32 -32
- package/db/schema.ts +719 -719
- package/detectors/README.md +109 -109
- package/detectors/dotnet.ts +357 -357
- package/detectors/flutter.ts +350 -350
- package/detectors/go.ts +324 -324
- package/detectors/index.ts +387 -387
- package/detectors/jvm.ts +433 -433
- package/detectors/loader.ts +128 -128
- package/detectors/node.ts +493 -493
- package/detectors/python.ts +423 -423
- package/detectors/rust.ts +348 -348
- package/gates/standards-validator.ts +204 -204
- package/gates/validator.ts +441 -441
- package/package.json +44 -43
- package/protocol/process-return.ts +450 -450
- package/protocol/subagent-protocol.ts +401 -401
- package/workflow.ts +783 -782
package/commands/architect.ts
CHANGED
|
@@ -1,760 +1,760 @@
|
|
|
1
|
-
import { getDb } from "../db/connection";
|
|
2
|
-
import { initSchema } from "../db/schema";
|
|
3
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
|
|
6
|
-
// ═══════════════════════════════════════════════════════════════
|
|
7
|
-
// TYPES
|
|
8
|
-
// ═══════════════════════════════════════════════════════════════
|
|
9
|
-
|
|
10
|
-
interface BabyStep {
|
|
11
|
-
number: number;
|
|
12
|
-
name: string;
|
|
13
|
-
what: string;
|
|
14
|
-
why: string;
|
|
15
|
-
result: string;
|
|
16
|
-
files: string[];
|
|
17
|
-
agent: string;
|
|
18
|
-
dependsOn?: number[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface Risk {
|
|
22
|
-
id: string;
|
|
23
|
-
description: string;
|
|
24
|
-
probability: "low" | "medium" | "high";
|
|
25
|
-
impact: "low" | "medium" | "high";
|
|
26
|
-
mitigation: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface Alternative {
|
|
30
|
-
name: string;
|
|
31
|
-
description: string;
|
|
32
|
-
pros: string[];
|
|
33
|
-
cons: string[];
|
|
34
|
-
complexity: "low" | "medium" | "high";
|
|
35
|
-
whyDiscarded?: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface ArchitecturalDecision {
|
|
39
|
-
decision: string;
|
|
40
|
-
rationale: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface ArchitecturalAnalysis {
|
|
44
|
-
id: string;
|
|
45
|
-
name: string;
|
|
46
|
-
description: string;
|
|
47
|
-
status: "pending" | "approved" | "implemented" | "rejected";
|
|
48
|
-
|
|
49
|
-
// Contexto
|
|
50
|
-
context: string;
|
|
51
|
-
currentArchitecture: string;
|
|
52
|
-
|
|
53
|
-
// Solucao
|
|
54
|
-
approach: string;
|
|
55
|
-
chosenAlternative: string;
|
|
56
|
-
|
|
57
|
-
// Diagramas (Mermaid)
|
|
58
|
-
diagrams: {
|
|
59
|
-
name: string;
|
|
60
|
-
type: string;
|
|
61
|
-
content: string;
|
|
62
|
-
}[];
|
|
63
|
-
|
|
64
|
-
// Baby Steps
|
|
65
|
-
babySteps: BabyStep[];
|
|
66
|
-
|
|
67
|
-
// Riscos
|
|
68
|
-
risks: Risk[];
|
|
69
|
-
|
|
70
|
-
// Alternativas
|
|
71
|
-
alternatives: Alternative[];
|
|
72
|
-
|
|
73
|
-
// Decisoes
|
|
74
|
-
decisions: ArchitecturalDecision[];
|
|
75
|
-
|
|
76
|
-
// Metadados
|
|
77
|
-
filePath?: string;
|
|
78
|
-
createdAt: string;
|
|
79
|
-
updatedAt?: string;
|
|
80
|
-
approvedAt?: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ═══════════════════════════════════════════════════════════════
|
|
84
|
-
// HELPERS
|
|
85
|
-
// ═══════════════════════════════════════════════════════════════
|
|
86
|
-
|
|
87
|
-
function generateId(): string {
|
|
88
|
-
const timestamp = Date.now().toString(36);
|
|
89
|
-
const random = Math.random().toString(36).substring(2, 6);
|
|
90
|
-
return `ARCH-${timestamp}-${random}`.toUpperCase();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function ensureAnalysisDir(): string {
|
|
94
|
-
const dir = ".codexa/analysis";
|
|
95
|
-
if (!existsSync(dir)) {
|
|
96
|
-
mkdirSync(dir, { recursive: true });
|
|
97
|
-
}
|
|
98
|
-
return dir;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function formatDate(date: Date): string {
|
|
102
|
-
return date.toISOString().split("T")[0];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ═══════════════════════════════════════════════════════════════
|
|
106
|
-
// MARKDOWN PARSER (v8.4)
|
|
107
|
-
// ═══════════════════════════════════════════════════════════════
|
|
108
|
-
|
|
109
|
-
function extractSection(content: string, header: string): string {
|
|
110
|
-
// Split por headers ## e encontrar a secao correta
|
|
111
|
-
const sections = content.split(/^## /m);
|
|
112
|
-
for (const section of sections) {
|
|
113
|
-
if (section.startsWith(header)) {
|
|
114
|
-
// Remover o header e retornar o conteudo
|
|
115
|
-
const lines = section.split("\n");
|
|
116
|
-
return lines.slice(1).join("\n").trim();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return "";
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function parseBabySteps(section: string): BabyStep[] {
|
|
123
|
-
if (!section) return [];
|
|
124
|
-
const steps: BabyStep[] = [];
|
|
125
|
-
// Match "### N. Name" or "### Step N: Name"
|
|
126
|
-
const stepBlocks = section.split(/^###\s+/m).filter(Boolean);
|
|
127
|
-
|
|
128
|
-
for (const block of stepBlocks) {
|
|
129
|
-
const headerMatch = block.match(/^(?:Step\s+)?(\d+)[.:]\s*(.+)/);
|
|
130
|
-
if (!headerMatch) continue;
|
|
131
|
-
|
|
132
|
-
const number = parseInt(headerMatch[1]);
|
|
133
|
-
const name = headerMatch[2].trim();
|
|
134
|
-
const what = block.match(/\*\*O que\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
135
|
-
const why = block.match(/\*\*Por que\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
136
|
-
const result = block.match(/\*\*Resultado\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
137
|
-
const filesStr = block.match(/\*\*Arquivos?\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
138
|
-
const agent = block.match(/\*\*Agente\*\*:\s*(.+)/)?.[1]?.trim() || "general";
|
|
139
|
-
const depsStr = block.match(/\*\*Depende de\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
140
|
-
|
|
141
|
-
const files = filesStr
|
|
142
|
-
? filesStr.split(/[,;]/).map(f => f.trim().replace(/`/g, "")).filter(Boolean)
|
|
143
|
-
: [];
|
|
144
|
-
|
|
145
|
-
const dependsOn = depsStr
|
|
146
|
-
? (depsStr.match(/\d+/g) || []).map(Number)
|
|
147
|
-
: [];
|
|
148
|
-
|
|
149
|
-
steps.push({ number, name, what, why, result, files, agent, dependsOn: dependsOn.length > 0 ? dependsOn : undefined });
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return steps;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function parseRisks(section: string): Risk[] {
|
|
156
|
-
if (!section) return [];
|
|
157
|
-
const risks: Risk[] = [];
|
|
158
|
-
const riskBlocks = section.split(/^###\s+/m).filter(Boolean);
|
|
159
|
-
|
|
160
|
-
for (const block of riskBlocks) {
|
|
161
|
-
const headerMatch = block.match(/^R?(\d+)[.:]\s*(.+)/);
|
|
162
|
-
if (!headerMatch) continue;
|
|
163
|
-
|
|
164
|
-
const id = `R${headerMatch[1]}`;
|
|
165
|
-
const description = headerMatch[2].trim();
|
|
166
|
-
const probability = (block.match(/\*\*Probabilidade\*\*:\s*(\w+)/)?.[1]?.trim() || "medium") as Risk["probability"];
|
|
167
|
-
const impact = (block.match(/\*\*Impacto\*\*:\s*(\w+)/)?.[1]?.trim() || "medium") as Risk["impact"];
|
|
168
|
-
const mitigation = block.match(/\*\*Mitiga[cç][aã]o\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
169
|
-
|
|
170
|
-
risks.push({ id, description, probability, impact, mitigation });
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return risks;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function parseDiagrams(section: string): { name: string; type: string; content: string }[] {
|
|
177
|
-
if (!section) return [];
|
|
178
|
-
const diagrams: { name: string; type: string; content: string }[] = [];
|
|
179
|
-
const diagramRegex = /###\s+(.+)\n[\s\S]*?```mermaid\n([\s\S]*?)```/g;
|
|
180
|
-
let match;
|
|
181
|
-
|
|
182
|
-
while ((match = diagramRegex.exec(section)) !== null) {
|
|
183
|
-
const content = match[2].trim();
|
|
184
|
-
const typeMatch = content.match(/^(\w+)/);
|
|
185
|
-
diagrams.push({
|
|
186
|
-
name: match[1].trim(),
|
|
187
|
-
type: typeMatch ? typeMatch[1] : "flowchart",
|
|
188
|
-
content,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return diagrams;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function parseDecisionsTable(section: string): ArchitecturalDecision[] {
|
|
196
|
-
if (!section) return [];
|
|
197
|
-
const decisions: ArchitecturalDecision[] = [];
|
|
198
|
-
const lines = section.split("\n");
|
|
199
|
-
|
|
200
|
-
for (const line of lines) {
|
|
201
|
-
// Match table rows: | Decision | Rationale |
|
|
202
|
-
const rowMatch = line.match(/^\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|$/);
|
|
203
|
-
if (!rowMatch) continue;
|
|
204
|
-
const decision = rowMatch[1].trim();
|
|
205
|
-
const rationale = rowMatch[2].trim();
|
|
206
|
-
// Skip header and separator rows
|
|
207
|
-
if (decision === "Decisao" || decision.startsWith("---")) continue;
|
|
208
|
-
if (decision && rationale) {
|
|
209
|
-
decisions.push({ decision, rationale });
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return decisions;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function parseAlternatives(section: string): Alternative[] {
|
|
217
|
-
if (!section) return [];
|
|
218
|
-
const alternatives: Alternative[] = [];
|
|
219
|
-
const altBlocks = section.split(/^###\s+/m).filter(Boolean);
|
|
220
|
-
|
|
221
|
-
for (const block of altBlocks) {
|
|
222
|
-
const name = block.split("\n")[0]?.trim();
|
|
223
|
-
if (!name) continue;
|
|
224
|
-
|
|
225
|
-
const description = block.match(/\*\*Descri[cç][aã]o\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
226
|
-
const whyDiscarded = block.match(/\*\*Por que descartada\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
227
|
-
|
|
228
|
-
alternatives.push({
|
|
229
|
-
name,
|
|
230
|
-
description,
|
|
231
|
-
pros: [],
|
|
232
|
-
cons: [],
|
|
233
|
-
complexity: "medium",
|
|
234
|
-
whyDiscarded: whyDiscarded || undefined,
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return alternatives;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* v8.4: Parseia um arquivo .md de analise arquitetural e extrai dados estruturados.
|
|
243
|
-
* O .md deve seguir o template padrao do codexa:architect.
|
|
244
|
-
*/
|
|
245
|
-
export function parseAnalysisMd(content: string): {
|
|
246
|
-
context: string;
|
|
247
|
-
currentArchitecture: string;
|
|
248
|
-
approach: string;
|
|
249
|
-
diagrams: { name: string; type: string; content: string }[];
|
|
250
|
-
babySteps: BabyStep[];
|
|
251
|
-
risks: Risk[];
|
|
252
|
-
alternatives: Alternative[];
|
|
253
|
-
decisions: ArchitecturalDecision[];
|
|
254
|
-
} {
|
|
255
|
-
const context = extractSection(content, "Contexto e Entendimento");
|
|
256
|
-
const currentArchitecture = extractSection(content, "Stack e Arquitetura Atual");
|
|
257
|
-
const approach = extractSection(content, "Solucao Proposta");
|
|
258
|
-
|
|
259
|
-
const diagramsSection = extractSection(content, "Diagramas");
|
|
260
|
-
const diagrams = parseDiagrams(diagramsSection);
|
|
261
|
-
|
|
262
|
-
const babyStepsSection = extractSection(content, "Baby Steps");
|
|
263
|
-
const babySteps = parseBabySteps(babyStepsSection);
|
|
264
|
-
|
|
265
|
-
const risksSection = extractSection(content, "Riscos e Mitigacoes");
|
|
266
|
-
const risks = parseRisks(risksSection);
|
|
267
|
-
|
|
268
|
-
const alternativesSection = extractSection(content, "Alternativas Descartadas");
|
|
269
|
-
const alternatives = parseAlternatives(alternativesSection);
|
|
270
|
-
|
|
271
|
-
const decisionsSection = extractSection(content, "Decisoes Arquiteturais");
|
|
272
|
-
const decisions = parseDecisionsTable(decisionsSection);
|
|
273
|
-
|
|
274
|
-
return { context, currentArchitecture, approach, diagrams, babySteps, risks, alternatives, decisions };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ═══════════════════════════════════════════════════════════════
|
|
278
|
-
// COMMANDS
|
|
279
|
-
// ═══════════════════════════════════════════════════════════════
|
|
280
|
-
|
|
281
|
-
export function architectStart(description: string, options: { json?: boolean } = {}): void {
|
|
282
|
-
initSchema();
|
|
283
|
-
const db = getDb();
|
|
284
|
-
const now = new Date().toISOString();
|
|
285
|
-
const id = generateId();
|
|
286
|
-
|
|
287
|
-
// Verificar se ja existe analise pendente
|
|
288
|
-
const pending = db.query(
|
|
289
|
-
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
290
|
-
).get() as any;
|
|
291
|
-
|
|
292
|
-
if (pending) {
|
|
293
|
-
if (options.json) {
|
|
294
|
-
console.log(JSON.stringify({
|
|
295
|
-
error: "ANALYSIS_ACTIVE",
|
|
296
|
-
message: "Ja existe uma analise pendente",
|
|
297
|
-
analysis: {
|
|
298
|
-
id: pending.id,
|
|
299
|
-
name: pending.name,
|
|
300
|
-
createdAt: pending.created_at,
|
|
301
|
-
},
|
|
302
|
-
}));
|
|
303
|
-
} else {
|
|
304
|
-
console.log("\n[ERRO] Ja existe uma analise pendente.\n");
|
|
305
|
-
console.log(` ID: ${pending.id}`);
|
|
306
|
-
console.log(` Nome: ${pending.name}`);
|
|
307
|
-
console.log(` Criada: ${pending.created_at}`);
|
|
308
|
-
console.log("\nUse 'architect show' para ver detalhes ou 'architect cancel' para cancelar.\n");
|
|
309
|
-
}
|
|
310
|
-
process.exit(1);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Criar nova analise
|
|
314
|
-
db.run(
|
|
315
|
-
`INSERT INTO architectural_analyses (id, name, description, status, created_at)
|
|
316
|
-
VALUES (?, ?, ?, 'pending', ?)`,
|
|
317
|
-
[id, description, description, now]
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
if (options.json) {
|
|
321
|
-
console.log(JSON.stringify({
|
|
322
|
-
success: true,
|
|
323
|
-
analysis: {
|
|
324
|
-
id,
|
|
325
|
-
name: description,
|
|
326
|
-
status: "pending",
|
|
327
|
-
createdAt: now,
|
|
328
|
-
},
|
|
329
|
-
nextSteps: [
|
|
330
|
-
"Explorar codebase com deep-explore",
|
|
331
|
-
"Analisar arquitetura atual",
|
|
332
|
-
"Propor solucoes com trade-offs",
|
|
333
|
-
"Escrever .md em .codexa/analysis/",
|
|
334
|
-
"Registrar com 'architect save --file <path>'",
|
|
335
|
-
"Aprovar com 'architect approve'",
|
|
336
|
-
],
|
|
337
|
-
}));
|
|
338
|
-
} else {
|
|
339
|
-
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
340
|
-
console.log("║ ANALISE ARQUITETURAL INICIADA ║");
|
|
341
|
-
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
342
|
-
console.log(` ID: ${id}`);
|
|
343
|
-
console.log(` Descricao: ${description}`);
|
|
344
|
-
console.log(` Status: pending`);
|
|
345
|
-
console.log("\n Proximos passos:");
|
|
346
|
-
console.log(" 1. Explorar codebase com deep-explore");
|
|
347
|
-
console.log(" 2. Analisar arquitetura atual");
|
|
348
|
-
console.log(" 3. Propor solucoes com trade-offs");
|
|
349
|
-
console.log(" 4. Escrever .md em .codexa/analysis/");
|
|
350
|
-
console.log(" 5. Registrar: architect save --file <path>");
|
|
351
|
-
console.log(" 6. Aprovar: architect approve\n");
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
export function architectShow(options: { id?: string; json?: boolean } = {}): void {
|
|
356
|
-
initSchema();
|
|
357
|
-
const db = getDb();
|
|
358
|
-
|
|
359
|
-
let analysis: any;
|
|
360
|
-
|
|
361
|
-
if (options.id) {
|
|
362
|
-
analysis = db.query("SELECT * FROM architectural_analyses WHERE id = ?").get(options.id);
|
|
363
|
-
} else {
|
|
364
|
-
// Pegar a mais recente pendente ou a ultima
|
|
365
|
-
analysis = db.query(
|
|
366
|
-
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
367
|
-
).get();
|
|
368
|
-
|
|
369
|
-
if (!analysis) {
|
|
370
|
-
analysis = db.query(
|
|
371
|
-
"SELECT * FROM architectural_analyses ORDER BY created_at DESC LIMIT 1"
|
|
372
|
-
).get();
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (!analysis) {
|
|
377
|
-
if (options.json) {
|
|
378
|
-
console.log(JSON.stringify({ error: "NO_ANALYSIS", message: "Nenhuma analise encontrada" }));
|
|
379
|
-
} else {
|
|
380
|
-
console.log("\n[INFO] Nenhuma analise arquitetural encontrada.");
|
|
381
|
-
console.log("Use 'architect start <descricao>' para iniciar.\n");
|
|
382
|
-
}
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Parse JSON fields
|
|
387
|
-
const parsed: ArchitecturalAnalysis = {
|
|
388
|
-
id: analysis.id,
|
|
389
|
-
name: analysis.name,
|
|
390
|
-
description: analysis.description,
|
|
391
|
-
status: analysis.status,
|
|
392
|
-
context: analysis.context || "",
|
|
393
|
-
currentArchitecture: analysis.current_architecture || "",
|
|
394
|
-
approach: analysis.approach || "",
|
|
395
|
-
chosenAlternative: analysis.chosen_alternative || "",
|
|
396
|
-
diagrams: analysis.diagrams ? JSON.parse(analysis.diagrams) : [],
|
|
397
|
-
babySteps: analysis.baby_steps ? JSON.parse(analysis.baby_steps) : [],
|
|
398
|
-
risks: analysis.risks ? JSON.parse(analysis.risks) : [],
|
|
399
|
-
alternatives: analysis.alternatives ? JSON.parse(analysis.alternatives) : [],
|
|
400
|
-
decisions: analysis.decisions ? JSON.parse(analysis.decisions) : [],
|
|
401
|
-
filePath: analysis.file_path,
|
|
402
|
-
createdAt: analysis.created_at,
|
|
403
|
-
updatedAt: analysis.updated_at,
|
|
404
|
-
approvedAt: analysis.approved_at,
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
if (options.json) {
|
|
408
|
-
console.log(JSON.stringify(parsed, null, 2));
|
|
409
|
-
} else {
|
|
410
|
-
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
411
|
-
console.log("║ ANALISE ARQUITETURAL ║");
|
|
412
|
-
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
413
|
-
|
|
414
|
-
console.log(` ID: ${parsed.id}`);
|
|
415
|
-
console.log(` Nome: ${parsed.name}`);
|
|
416
|
-
console.log(` Status: ${parsed.status}`);
|
|
417
|
-
console.log(` Criada: ${parsed.createdAt}`);
|
|
418
|
-
|
|
419
|
-
if (parsed.filePath) {
|
|
420
|
-
console.log(` Arquivo: ${parsed.filePath}`);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (parsed.approach) {
|
|
424
|
-
console.log(`\n Abordagem: ${parsed.approach}`);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (parsed.babySteps.length > 0) {
|
|
428
|
-
console.log(`\n Baby Steps: ${parsed.babySteps.length}`);
|
|
429
|
-
parsed.babySteps.forEach((step, i) => {
|
|
430
|
-
console.log(` ${i + 1}. ${step.name} [${step.agent}]`);
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (parsed.risks.length > 0) {
|
|
435
|
-
console.log(`\n Riscos: ${parsed.risks.length}`);
|
|
436
|
-
parsed.risks.forEach((risk) => {
|
|
437
|
-
console.log(` - ${risk.description} (${risk.probability}/${risk.impact})`);
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (parsed.diagrams.length > 0) {
|
|
442
|
-
console.log(`\n Diagramas: ${parsed.diagrams.length}`);
|
|
443
|
-
parsed.diagrams.forEach((d) => {
|
|
444
|
-
console.log(` - ${d.name} (${d.type})`);
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
console.log("");
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
export function architectList(options: { json?: boolean; status?: string } = {}): void {
|
|
453
|
-
initSchema();
|
|
454
|
-
const db = getDb();
|
|
455
|
-
|
|
456
|
-
let query = "SELECT id, name, status, created_at, file_path FROM architectural_analyses";
|
|
457
|
-
const params: any[] = [];
|
|
458
|
-
|
|
459
|
-
if (options.status) {
|
|
460
|
-
query += " WHERE status = ?";
|
|
461
|
-
params.push(options.status);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
query += " ORDER BY created_at DESC";
|
|
465
|
-
|
|
466
|
-
const analyses = db.query(query).all(...params) as any[];
|
|
467
|
-
|
|
468
|
-
if (options.json) {
|
|
469
|
-
console.log(JSON.stringify(analyses, null, 2));
|
|
470
|
-
} else {
|
|
471
|
-
if (analyses.length === 0) {
|
|
472
|
-
console.log("\n[INFO] Nenhuma analise arquitetural encontrada.\n");
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
477
|
-
console.log("║ ANALISES ARQUITETURAIS ║");
|
|
478
|
-
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
479
|
-
|
|
480
|
-
analyses.forEach((a) => {
|
|
481
|
-
const statusIcon =
|
|
482
|
-
a.status === "approved" ? "✓" :
|
|
483
|
-
a.status === "implemented" ? "★" :
|
|
484
|
-
a.status === "rejected" ? "✗" : "○";
|
|
485
|
-
|
|
486
|
-
console.log(` ${statusIcon} [${a.id}] ${a.name}`);
|
|
487
|
-
console.log(` Status: ${a.status} | Criada: ${a.created_at}`);
|
|
488
|
-
if (a.file_path) {
|
|
489
|
-
console.log(` Arquivo: ${a.file_path}`);
|
|
490
|
-
}
|
|
491
|
-
console.log("");
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
export function architectSave(options: { file?: string; json?: boolean }): void {
|
|
497
|
-
initSchema();
|
|
498
|
-
const db = getDb();
|
|
499
|
-
const now = new Date().toISOString();
|
|
500
|
-
|
|
501
|
-
// Pegar analise pendente
|
|
502
|
-
const analysis = db.query(
|
|
503
|
-
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
504
|
-
).get() as any;
|
|
505
|
-
|
|
506
|
-
if (!analysis) {
|
|
507
|
-
if (options.json) {
|
|
508
|
-
console.log(JSON.stringify({ error: "NO_PENDING", message: "Nenhuma analise pendente" }));
|
|
509
|
-
} else {
|
|
510
|
-
console.log("\n[ERRO] Nenhuma analise pendente encontrada.\n");
|
|
511
|
-
}
|
|
512
|
-
process.exit(1);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// v8.4: Resolver caminho do arquivo .md
|
|
516
|
-
const dir = ensureAnalysisDir();
|
|
517
|
-
let filePath: string;
|
|
518
|
-
if (options.file) {
|
|
519
|
-
// Se path fornecido, usar como-is (aceita absoluto ou relativo)
|
|
520
|
-
filePath = options.file;
|
|
521
|
-
} else {
|
|
522
|
-
filePath = join(dir, `${analysis.id}-${analysis.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").substring(0, 30)}.md`);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// v8.4: Ler .md do disco e parsear para popular o DB
|
|
526
|
-
if (!existsSync(filePath)) {
|
|
527
|
-
if (options.json) {
|
|
528
|
-
console.log(JSON.stringify({ error: "FILE_NOT_FOUND", message: `Arquivo nao encontrado: ${filePath}` }));
|
|
529
|
-
} else {
|
|
530
|
-
console.log(`\n[ERRO] Arquivo nao encontrado: ${filePath}`);
|
|
531
|
-
console.log("O agente architect deve escrever o .md antes de chamar 'architect save'.\n");
|
|
532
|
-
}
|
|
533
|
-
process.exit(1);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
const content = readFileSync(filePath, "utf-8");
|
|
537
|
-
const parsed = parseAnalysisMd(content);
|
|
538
|
-
|
|
539
|
-
// Popular DB com dados extraidos do .md
|
|
540
|
-
const updates: string[] = [];
|
|
541
|
-
const values: any[] = [];
|
|
542
|
-
|
|
543
|
-
if (parsed.context) {
|
|
544
|
-
updates.push("context = ?");
|
|
545
|
-
values.push(parsed.context);
|
|
546
|
-
}
|
|
547
|
-
if (parsed.currentArchitecture) {
|
|
548
|
-
updates.push("current_architecture = ?");
|
|
549
|
-
values.push(parsed.currentArchitecture);
|
|
550
|
-
}
|
|
551
|
-
if (parsed.approach) {
|
|
552
|
-
updates.push("approach = ?");
|
|
553
|
-
values.push(parsed.approach);
|
|
554
|
-
}
|
|
555
|
-
if (parsed.diagrams.length > 0) {
|
|
556
|
-
updates.push("diagrams = ?");
|
|
557
|
-
values.push(JSON.stringify(parsed.diagrams));
|
|
558
|
-
}
|
|
559
|
-
if (parsed.babySteps.length > 0) {
|
|
560
|
-
updates.push("baby_steps = ?");
|
|
561
|
-
values.push(JSON.stringify(parsed.babySteps));
|
|
562
|
-
}
|
|
563
|
-
if (parsed.risks.length > 0) {
|
|
564
|
-
updates.push("risks = ?");
|
|
565
|
-
values.push(JSON.stringify(parsed.risks));
|
|
566
|
-
}
|
|
567
|
-
if (parsed.alternatives.length > 0) {
|
|
568
|
-
updates.push("alternatives = ?");
|
|
569
|
-
values.push(JSON.stringify(parsed.alternatives));
|
|
570
|
-
}
|
|
571
|
-
if (parsed.decisions.length > 0) {
|
|
572
|
-
updates.push("decisions = ?");
|
|
573
|
-
values.push(JSON.stringify(parsed.decisions));
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
updates.push("file_path = ?");
|
|
577
|
-
values.push(filePath);
|
|
578
|
-
updates.push("updated_at = ?");
|
|
579
|
-
values.push(now);
|
|
580
|
-
values.push(analysis.id);
|
|
581
|
-
|
|
582
|
-
db.run(
|
|
583
|
-
`UPDATE architectural_analyses SET ${updates.join(", ")} WHERE id = ?`,
|
|
584
|
-
values
|
|
585
|
-
);
|
|
586
|
-
|
|
587
|
-
if (options.json) {
|
|
588
|
-
console.log(JSON.stringify({
|
|
589
|
-
success: true,
|
|
590
|
-
id: analysis.id,
|
|
591
|
-
filePath,
|
|
592
|
-
parsed: {
|
|
593
|
-
babySteps: parsed.babySteps.length,
|
|
594
|
-
risks: parsed.risks.length,
|
|
595
|
-
diagrams: parsed.diagrams.length,
|
|
596
|
-
decisions: parsed.decisions.length,
|
|
597
|
-
hasApproach: !!parsed.approach,
|
|
598
|
-
hasContext: !!parsed.context,
|
|
599
|
-
},
|
|
600
|
-
}));
|
|
601
|
-
} else {
|
|
602
|
-
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
603
|
-
console.log("║ ANALISE SALVA (parseada do .md) ║");
|
|
604
|
-
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
605
|
-
console.log(` Arquivo: ${filePath}`);
|
|
606
|
-
console.log(` Baby Steps: ${parsed.babySteps.length}`);
|
|
607
|
-
console.log(` Riscos: ${parsed.risks.length}`);
|
|
608
|
-
console.log(` Diagramas: ${parsed.diagrams.length}`);
|
|
609
|
-
console.log(` Decisoes: ${parsed.decisions.length}`);
|
|
610
|
-
if (!parsed.approach) {
|
|
611
|
-
console.log("\n [AVISO] Secao 'Solucao Proposta' nao encontrada no .md");
|
|
612
|
-
}
|
|
613
|
-
if (parsed.babySteps.length === 0) {
|
|
614
|
-
console.log(" [AVISO] Nenhum baby step encontrado. Verifique formato '### N. Nome'");
|
|
615
|
-
}
|
|
616
|
-
console.log("\n Proximo passo:");
|
|
617
|
-
console.log(` - Aprovar: architect approve`);
|
|
618
|
-
console.log(` - Iniciar feature: plan start "nome" --from-analysis ${analysis.id}\n`);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
export function architectApprove(options: { id?: string; json?: boolean }): void {
|
|
623
|
-
initSchema();
|
|
624
|
-
const db = getDb();
|
|
625
|
-
const now = new Date().toISOString();
|
|
626
|
-
|
|
627
|
-
let analysis: any;
|
|
628
|
-
|
|
629
|
-
if (options.id) {
|
|
630
|
-
analysis = db.query("SELECT * FROM architectural_analyses WHERE id = ?").get(options.id);
|
|
631
|
-
} else {
|
|
632
|
-
analysis = db.query(
|
|
633
|
-
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
634
|
-
).get();
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
if (!analysis) {
|
|
638
|
-
if (options.json) {
|
|
639
|
-
console.log(JSON.stringify({ error: "NOT_FOUND", message: "Analise nao encontrada" }));
|
|
640
|
-
} else {
|
|
641
|
-
console.log("\n[ERRO] Analise nao encontrada.\n");
|
|
642
|
-
}
|
|
643
|
-
process.exit(1);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
if (analysis.status !== "pending") {
|
|
647
|
-
if (options.json) {
|
|
648
|
-
console.log(JSON.stringify({ error: "INVALID_STATUS", message: `Analise ja esta ${analysis.status}` }));
|
|
649
|
-
} else {
|
|
650
|
-
console.log(`\n[ERRO] Analise ja esta com status '${analysis.status}'.\n`);
|
|
651
|
-
}
|
|
652
|
-
process.exit(1);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
db.run(
|
|
656
|
-
"UPDATE architectural_analyses SET status = 'approved', approved_at = ?, updated_at = ? WHERE id = ?",
|
|
657
|
-
[now, now, analysis.id]
|
|
658
|
-
);
|
|
659
|
-
|
|
660
|
-
if (options.json) {
|
|
661
|
-
console.log(JSON.stringify({
|
|
662
|
-
success: true,
|
|
663
|
-
id: analysis.id,
|
|
664
|
-
status: "approved",
|
|
665
|
-
nextStep: `plan start "${analysis.name}" --from-analysis ${analysis.id}`,
|
|
666
|
-
}));
|
|
667
|
-
} else {
|
|
668
|
-
console.log(`\n[OK] Analise ${analysis.id} aprovada.`);
|
|
669
|
-
console.log(`\nInicie a feature com import automatico dos baby steps:`);
|
|
670
|
-
console.log(` plan start "${analysis.name}" --from-analysis ${analysis.id}\n`);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// v8.4: Simplificado - agora o feature importa baby steps automaticamente via --from-analysis
|
|
675
|
-
export function architectExport(options: { id?: string; json?: boolean }): void {
|
|
676
|
-
initSchema();
|
|
677
|
-
const db = getDb();
|
|
678
|
-
|
|
679
|
-
let analysis: any;
|
|
680
|
-
|
|
681
|
-
if (options.id) {
|
|
682
|
-
analysis = db.query("SELECT * FROM architectural_analyses WHERE id = ?").get(options.id);
|
|
683
|
-
} else {
|
|
684
|
-
analysis = db.query(
|
|
685
|
-
"SELECT * FROM architectural_analyses WHERE status = 'approved' ORDER BY approved_at DESC LIMIT 1"
|
|
686
|
-
).get();
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
if (!analysis) {
|
|
690
|
-
if (options.json) {
|
|
691
|
-
console.log(JSON.stringify({ error: "NOT_FOUND", message: "Nenhuma analise aprovada encontrada" }));
|
|
692
|
-
} else {
|
|
693
|
-
console.log("\n[ERRO] Nenhuma analise aprovada encontrada.");
|
|
694
|
-
console.log("Use 'architect approve' primeiro.\n");
|
|
695
|
-
}
|
|
696
|
-
process.exit(1);
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const babySteps: BabyStep[] = analysis.baby_steps ? JSON.parse(analysis.baby_steps) : [];
|
|
700
|
-
const planCmd = `codexa plan start "${analysis.name}" --from-analysis ${analysis.id}`;
|
|
701
|
-
|
|
702
|
-
if (options.json) {
|
|
703
|
-
console.log(JSON.stringify({
|
|
704
|
-
analysisId: analysis.id,
|
|
705
|
-
analysisName: analysis.name,
|
|
706
|
-
babyStepsCount: babySteps.length,
|
|
707
|
-
filePath: analysis.file_path || null,
|
|
708
|
-
planStartCommand: planCmd,
|
|
709
|
-
}));
|
|
710
|
-
} else {
|
|
711
|
-
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
712
|
-
console.log("║ EXPORTAR PARA FEATURE ║");
|
|
713
|
-
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
714
|
-
|
|
715
|
-
console.log(` Analise: ${analysis.name} (${analysis.id})`);
|
|
716
|
-
console.log(` Baby Steps: ${babySteps.length}`);
|
|
717
|
-
if (analysis.file_path) {
|
|
718
|
-
console.log(` Arquivo: ${analysis.file_path}`);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
console.log(`\n Comando para iniciar feature com import automatico:`);
|
|
722
|
-
console.log(` ${planCmd}\n`);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
export function architectCancel(options: { id?: string; json?: boolean }): void {
|
|
727
|
-
initSchema();
|
|
728
|
-
const db = getDb();
|
|
729
|
-
const now = new Date().toISOString();
|
|
730
|
-
|
|
731
|
-
let analysis: any;
|
|
732
|
-
|
|
733
|
-
if (options.id) {
|
|
734
|
-
analysis = db.query("SELECT * FROM architectural_analyses WHERE id = ?").get(options.id);
|
|
735
|
-
} else {
|
|
736
|
-
analysis = db.query(
|
|
737
|
-
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
738
|
-
).get();
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
if (!analysis) {
|
|
742
|
-
if (options.json) {
|
|
743
|
-
console.log(JSON.stringify({ error: "NOT_FOUND", message: "Nenhuma analise pendente encontrada" }));
|
|
744
|
-
} else {
|
|
745
|
-
console.log("\n[INFO] Nenhuma analise pendente para cancelar.\n");
|
|
746
|
-
}
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
db.run(
|
|
751
|
-
"UPDATE architectural_analyses SET status = 'rejected', updated_at = ? WHERE id = ?",
|
|
752
|
-
[now, analysis.id]
|
|
753
|
-
);
|
|
754
|
-
|
|
755
|
-
if (options.json) {
|
|
756
|
-
console.log(JSON.stringify({ success: true, id: analysis.id, status: "rejected" }));
|
|
757
|
-
} else {
|
|
758
|
-
console.log(`\n[OK] Analise ${analysis.id} cancelada.\n`);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
1
|
+
import { getDb } from "../db/connection";
|
|
2
|
+
import { initSchema } from "../db/schema";
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
|
|
6
|
+
// ═══════════════════════════════════════════════════════════════
|
|
7
|
+
// TYPES
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════
|
|
9
|
+
|
|
10
|
+
interface BabyStep {
|
|
11
|
+
number: number;
|
|
12
|
+
name: string;
|
|
13
|
+
what: string;
|
|
14
|
+
why: string;
|
|
15
|
+
result: string;
|
|
16
|
+
files: string[];
|
|
17
|
+
agent: string;
|
|
18
|
+
dependsOn?: number[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Risk {
|
|
22
|
+
id: string;
|
|
23
|
+
description: string;
|
|
24
|
+
probability: "low" | "medium" | "high";
|
|
25
|
+
impact: "low" | "medium" | "high";
|
|
26
|
+
mitigation: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Alternative {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
pros: string[];
|
|
33
|
+
cons: string[];
|
|
34
|
+
complexity: "low" | "medium" | "high";
|
|
35
|
+
whyDiscarded?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ArchitecturalDecision {
|
|
39
|
+
decision: string;
|
|
40
|
+
rationale: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ArchitecturalAnalysis {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
description: string;
|
|
47
|
+
status: "pending" | "approved" | "implemented" | "rejected";
|
|
48
|
+
|
|
49
|
+
// Contexto
|
|
50
|
+
context: string;
|
|
51
|
+
currentArchitecture: string;
|
|
52
|
+
|
|
53
|
+
// Solucao
|
|
54
|
+
approach: string;
|
|
55
|
+
chosenAlternative: string;
|
|
56
|
+
|
|
57
|
+
// Diagramas (Mermaid)
|
|
58
|
+
diagrams: {
|
|
59
|
+
name: string;
|
|
60
|
+
type: string;
|
|
61
|
+
content: string;
|
|
62
|
+
}[];
|
|
63
|
+
|
|
64
|
+
// Baby Steps
|
|
65
|
+
babySteps: BabyStep[];
|
|
66
|
+
|
|
67
|
+
// Riscos
|
|
68
|
+
risks: Risk[];
|
|
69
|
+
|
|
70
|
+
// Alternativas
|
|
71
|
+
alternatives: Alternative[];
|
|
72
|
+
|
|
73
|
+
// Decisoes
|
|
74
|
+
decisions: ArchitecturalDecision[];
|
|
75
|
+
|
|
76
|
+
// Metadados
|
|
77
|
+
filePath?: string;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
updatedAt?: string;
|
|
80
|
+
approvedAt?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ═══════════════════════════════════════════════════════════════
|
|
84
|
+
// HELPERS
|
|
85
|
+
// ═══════════════════════════════════════════════════════════════
|
|
86
|
+
|
|
87
|
+
function generateId(): string {
|
|
88
|
+
const timestamp = Date.now().toString(36);
|
|
89
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
90
|
+
return `ARCH-${timestamp}-${random}`.toUpperCase();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function ensureAnalysisDir(): string {
|
|
94
|
+
const dir = ".codexa/analysis";
|
|
95
|
+
if (!existsSync(dir)) {
|
|
96
|
+
mkdirSync(dir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
return dir;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function formatDate(date: Date): string {
|
|
102
|
+
return date.toISOString().split("T")[0];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ═══════════════════════════════════════════════════════════════
|
|
106
|
+
// MARKDOWN PARSER (v8.4)
|
|
107
|
+
// ═══════════════════════════════════════════════════════════════
|
|
108
|
+
|
|
109
|
+
function extractSection(content: string, header: string): string {
|
|
110
|
+
// Split por headers ## e encontrar a secao correta
|
|
111
|
+
const sections = content.split(/^## /m);
|
|
112
|
+
for (const section of sections) {
|
|
113
|
+
if (section.startsWith(header)) {
|
|
114
|
+
// Remover o header e retornar o conteudo
|
|
115
|
+
const lines = section.split("\n");
|
|
116
|
+
return lines.slice(1).join("\n").trim();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseBabySteps(section: string): BabyStep[] {
|
|
123
|
+
if (!section) return [];
|
|
124
|
+
const steps: BabyStep[] = [];
|
|
125
|
+
// Match "### N. Name" or "### Step N: Name"
|
|
126
|
+
const stepBlocks = section.split(/^###\s+/m).filter(Boolean);
|
|
127
|
+
|
|
128
|
+
for (const block of stepBlocks) {
|
|
129
|
+
const headerMatch = block.match(/^(?:Step\s+)?(\d+)[.:]\s*(.+)/);
|
|
130
|
+
if (!headerMatch) continue;
|
|
131
|
+
|
|
132
|
+
const number = parseInt(headerMatch[1]);
|
|
133
|
+
const name = headerMatch[2].trim();
|
|
134
|
+
const what = block.match(/\*\*O que\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
135
|
+
const why = block.match(/\*\*Por que\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
136
|
+
const result = block.match(/\*\*Resultado\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
137
|
+
const filesStr = block.match(/\*\*Arquivos?\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
138
|
+
const agent = block.match(/\*\*Agente\*\*:\s*(.+)/)?.[1]?.trim() || "general";
|
|
139
|
+
const depsStr = block.match(/\*\*Depende de\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
140
|
+
|
|
141
|
+
const files = filesStr
|
|
142
|
+
? filesStr.split(/[,;]/).map(f => f.trim().replace(/`/g, "")).filter(Boolean)
|
|
143
|
+
: [];
|
|
144
|
+
|
|
145
|
+
const dependsOn = depsStr
|
|
146
|
+
? (depsStr.match(/\d+/g) || []).map(Number)
|
|
147
|
+
: [];
|
|
148
|
+
|
|
149
|
+
steps.push({ number, name, what, why, result, files, agent, dependsOn: dependsOn.length > 0 ? dependsOn : undefined });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return steps;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseRisks(section: string): Risk[] {
|
|
156
|
+
if (!section) return [];
|
|
157
|
+
const risks: Risk[] = [];
|
|
158
|
+
const riskBlocks = section.split(/^###\s+/m).filter(Boolean);
|
|
159
|
+
|
|
160
|
+
for (const block of riskBlocks) {
|
|
161
|
+
const headerMatch = block.match(/^R?(\d+)[.:]\s*(.+)/);
|
|
162
|
+
if (!headerMatch) continue;
|
|
163
|
+
|
|
164
|
+
const id = `R${headerMatch[1]}`;
|
|
165
|
+
const description = headerMatch[2].trim();
|
|
166
|
+
const probability = (block.match(/\*\*Probabilidade\*\*:\s*(\w+)/)?.[1]?.trim() || "medium") as Risk["probability"];
|
|
167
|
+
const impact = (block.match(/\*\*Impacto\*\*:\s*(\w+)/)?.[1]?.trim() || "medium") as Risk["impact"];
|
|
168
|
+
const mitigation = block.match(/\*\*Mitiga[cç][aã]o\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
169
|
+
|
|
170
|
+
risks.push({ id, description, probability, impact, mitigation });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return risks;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseDiagrams(section: string): { name: string; type: string; content: string }[] {
|
|
177
|
+
if (!section) return [];
|
|
178
|
+
const diagrams: { name: string; type: string; content: string }[] = [];
|
|
179
|
+
const diagramRegex = /###\s+(.+)\n[\s\S]*?```mermaid\n([\s\S]*?)```/g;
|
|
180
|
+
let match;
|
|
181
|
+
|
|
182
|
+
while ((match = diagramRegex.exec(section)) !== null) {
|
|
183
|
+
const content = match[2].trim();
|
|
184
|
+
const typeMatch = content.match(/^(\w+)/);
|
|
185
|
+
diagrams.push({
|
|
186
|
+
name: match[1].trim(),
|
|
187
|
+
type: typeMatch ? typeMatch[1] : "flowchart",
|
|
188
|
+
content,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return diagrams;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function parseDecisionsTable(section: string): ArchitecturalDecision[] {
|
|
196
|
+
if (!section) return [];
|
|
197
|
+
const decisions: ArchitecturalDecision[] = [];
|
|
198
|
+
const lines = section.split("\n");
|
|
199
|
+
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
// Match table rows: | Decision | Rationale |
|
|
202
|
+
const rowMatch = line.match(/^\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|$/);
|
|
203
|
+
if (!rowMatch) continue;
|
|
204
|
+
const decision = rowMatch[1].trim();
|
|
205
|
+
const rationale = rowMatch[2].trim();
|
|
206
|
+
// Skip header and separator rows
|
|
207
|
+
if (decision === "Decisao" || decision.startsWith("---")) continue;
|
|
208
|
+
if (decision && rationale) {
|
|
209
|
+
decisions.push({ decision, rationale });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return decisions;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parseAlternatives(section: string): Alternative[] {
|
|
217
|
+
if (!section) return [];
|
|
218
|
+
const alternatives: Alternative[] = [];
|
|
219
|
+
const altBlocks = section.split(/^###\s+/m).filter(Boolean);
|
|
220
|
+
|
|
221
|
+
for (const block of altBlocks) {
|
|
222
|
+
const name = block.split("\n")[0]?.trim();
|
|
223
|
+
if (!name) continue;
|
|
224
|
+
|
|
225
|
+
const description = block.match(/\*\*Descri[cç][aã]o\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
226
|
+
const whyDiscarded = block.match(/\*\*Por que descartada\*\*:\s*(.+)/)?.[1]?.trim() || "";
|
|
227
|
+
|
|
228
|
+
alternatives.push({
|
|
229
|
+
name,
|
|
230
|
+
description,
|
|
231
|
+
pros: [],
|
|
232
|
+
cons: [],
|
|
233
|
+
complexity: "medium",
|
|
234
|
+
whyDiscarded: whyDiscarded || undefined,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return alternatives;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* v8.4: Parseia um arquivo .md de analise arquitetural e extrai dados estruturados.
|
|
243
|
+
* O .md deve seguir o template padrao do codexa:architect.
|
|
244
|
+
*/
|
|
245
|
+
export function parseAnalysisMd(content: string): {
|
|
246
|
+
context: string;
|
|
247
|
+
currentArchitecture: string;
|
|
248
|
+
approach: string;
|
|
249
|
+
diagrams: { name: string; type: string; content: string }[];
|
|
250
|
+
babySteps: BabyStep[];
|
|
251
|
+
risks: Risk[];
|
|
252
|
+
alternatives: Alternative[];
|
|
253
|
+
decisions: ArchitecturalDecision[];
|
|
254
|
+
} {
|
|
255
|
+
const context = extractSection(content, "Contexto e Entendimento");
|
|
256
|
+
const currentArchitecture = extractSection(content, "Stack e Arquitetura Atual");
|
|
257
|
+
const approach = extractSection(content, "Solucao Proposta");
|
|
258
|
+
|
|
259
|
+
const diagramsSection = extractSection(content, "Diagramas");
|
|
260
|
+
const diagrams = parseDiagrams(diagramsSection);
|
|
261
|
+
|
|
262
|
+
const babyStepsSection = extractSection(content, "Baby Steps");
|
|
263
|
+
const babySteps = parseBabySteps(babyStepsSection);
|
|
264
|
+
|
|
265
|
+
const risksSection = extractSection(content, "Riscos e Mitigacoes");
|
|
266
|
+
const risks = parseRisks(risksSection);
|
|
267
|
+
|
|
268
|
+
const alternativesSection = extractSection(content, "Alternativas Descartadas");
|
|
269
|
+
const alternatives = parseAlternatives(alternativesSection);
|
|
270
|
+
|
|
271
|
+
const decisionsSection = extractSection(content, "Decisoes Arquiteturais");
|
|
272
|
+
const decisions = parseDecisionsTable(decisionsSection);
|
|
273
|
+
|
|
274
|
+
return { context, currentArchitecture, approach, diagrams, babySteps, risks, alternatives, decisions };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ═══════════════════════════════════════════════════════════════
|
|
278
|
+
// COMMANDS
|
|
279
|
+
// ═══════════════════════════════════════════════════════════════
|
|
280
|
+
|
|
281
|
+
export function architectStart(description: string, options: { json?: boolean } = {}): void {
|
|
282
|
+
initSchema();
|
|
283
|
+
const db = getDb();
|
|
284
|
+
const now = new Date().toISOString();
|
|
285
|
+
const id = generateId();
|
|
286
|
+
|
|
287
|
+
// Verificar se ja existe analise pendente
|
|
288
|
+
const pending = db.query(
|
|
289
|
+
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
290
|
+
).get() as any;
|
|
291
|
+
|
|
292
|
+
if (pending) {
|
|
293
|
+
if (options.json) {
|
|
294
|
+
console.log(JSON.stringify({
|
|
295
|
+
error: "ANALYSIS_ACTIVE",
|
|
296
|
+
message: "Ja existe uma analise pendente",
|
|
297
|
+
analysis: {
|
|
298
|
+
id: pending.id,
|
|
299
|
+
name: pending.name,
|
|
300
|
+
createdAt: pending.created_at,
|
|
301
|
+
},
|
|
302
|
+
}));
|
|
303
|
+
} else {
|
|
304
|
+
console.log("\n[ERRO] Ja existe uma analise pendente.\n");
|
|
305
|
+
console.log(` ID: ${pending.id}`);
|
|
306
|
+
console.log(` Nome: ${pending.name}`);
|
|
307
|
+
console.log(` Criada: ${pending.created_at}`);
|
|
308
|
+
console.log("\nUse 'architect show' para ver detalhes ou 'architect cancel' para cancelar.\n");
|
|
309
|
+
}
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Criar nova analise
|
|
314
|
+
db.run(
|
|
315
|
+
`INSERT INTO architectural_analyses (id, name, description, status, created_at)
|
|
316
|
+
VALUES (?, ?, ?, 'pending', ?)`,
|
|
317
|
+
[id, description, description, now]
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
if (options.json) {
|
|
321
|
+
console.log(JSON.stringify({
|
|
322
|
+
success: true,
|
|
323
|
+
analysis: {
|
|
324
|
+
id,
|
|
325
|
+
name: description,
|
|
326
|
+
status: "pending",
|
|
327
|
+
createdAt: now,
|
|
328
|
+
},
|
|
329
|
+
nextSteps: [
|
|
330
|
+
"Explorar codebase com deep-explore",
|
|
331
|
+
"Analisar arquitetura atual",
|
|
332
|
+
"Propor solucoes com trade-offs",
|
|
333
|
+
"Escrever .md em .codexa/analysis/",
|
|
334
|
+
"Registrar com 'architect save --file <path>'",
|
|
335
|
+
"Aprovar com 'architect approve'",
|
|
336
|
+
],
|
|
337
|
+
}));
|
|
338
|
+
} else {
|
|
339
|
+
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
340
|
+
console.log("║ ANALISE ARQUITETURAL INICIADA ║");
|
|
341
|
+
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
342
|
+
console.log(` ID: ${id}`);
|
|
343
|
+
console.log(` Descricao: ${description}`);
|
|
344
|
+
console.log(` Status: pending`);
|
|
345
|
+
console.log("\n Proximos passos:");
|
|
346
|
+
console.log(" 1. Explorar codebase com deep-explore");
|
|
347
|
+
console.log(" 2. Analisar arquitetura atual");
|
|
348
|
+
console.log(" 3. Propor solucoes com trade-offs");
|
|
349
|
+
console.log(" 4. Escrever .md em .codexa/analysis/");
|
|
350
|
+
console.log(" 5. Registrar: architect save --file <path>");
|
|
351
|
+
console.log(" 6. Aprovar: architect approve\n");
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function architectShow(options: { id?: string; json?: boolean } = {}): void {
|
|
356
|
+
initSchema();
|
|
357
|
+
const db = getDb();
|
|
358
|
+
|
|
359
|
+
let analysis: any;
|
|
360
|
+
|
|
361
|
+
if (options.id) {
|
|
362
|
+
analysis = db.query("SELECT * FROM architectural_analyses WHERE id = ?").get(options.id);
|
|
363
|
+
} else {
|
|
364
|
+
// Pegar a mais recente pendente ou a ultima
|
|
365
|
+
analysis = db.query(
|
|
366
|
+
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
367
|
+
).get();
|
|
368
|
+
|
|
369
|
+
if (!analysis) {
|
|
370
|
+
analysis = db.query(
|
|
371
|
+
"SELECT * FROM architectural_analyses ORDER BY created_at DESC LIMIT 1"
|
|
372
|
+
).get();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!analysis) {
|
|
377
|
+
if (options.json) {
|
|
378
|
+
console.log(JSON.stringify({ error: "NO_ANALYSIS", message: "Nenhuma analise encontrada" }));
|
|
379
|
+
} else {
|
|
380
|
+
console.log("\n[INFO] Nenhuma analise arquitetural encontrada.");
|
|
381
|
+
console.log("Use 'architect start <descricao>' para iniciar.\n");
|
|
382
|
+
}
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Parse JSON fields
|
|
387
|
+
const parsed: ArchitecturalAnalysis = {
|
|
388
|
+
id: analysis.id,
|
|
389
|
+
name: analysis.name,
|
|
390
|
+
description: analysis.description,
|
|
391
|
+
status: analysis.status,
|
|
392
|
+
context: analysis.context || "",
|
|
393
|
+
currentArchitecture: analysis.current_architecture || "",
|
|
394
|
+
approach: analysis.approach || "",
|
|
395
|
+
chosenAlternative: analysis.chosen_alternative || "",
|
|
396
|
+
diagrams: analysis.diagrams ? JSON.parse(analysis.diagrams) : [],
|
|
397
|
+
babySteps: analysis.baby_steps ? JSON.parse(analysis.baby_steps) : [],
|
|
398
|
+
risks: analysis.risks ? JSON.parse(analysis.risks) : [],
|
|
399
|
+
alternatives: analysis.alternatives ? JSON.parse(analysis.alternatives) : [],
|
|
400
|
+
decisions: analysis.decisions ? JSON.parse(analysis.decisions) : [],
|
|
401
|
+
filePath: analysis.file_path,
|
|
402
|
+
createdAt: analysis.created_at,
|
|
403
|
+
updatedAt: analysis.updated_at,
|
|
404
|
+
approvedAt: analysis.approved_at,
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
if (options.json) {
|
|
408
|
+
console.log(JSON.stringify(parsed, null, 2));
|
|
409
|
+
} else {
|
|
410
|
+
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
411
|
+
console.log("║ ANALISE ARQUITETURAL ║");
|
|
412
|
+
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
413
|
+
|
|
414
|
+
console.log(` ID: ${parsed.id}`);
|
|
415
|
+
console.log(` Nome: ${parsed.name}`);
|
|
416
|
+
console.log(` Status: ${parsed.status}`);
|
|
417
|
+
console.log(` Criada: ${parsed.createdAt}`);
|
|
418
|
+
|
|
419
|
+
if (parsed.filePath) {
|
|
420
|
+
console.log(` Arquivo: ${parsed.filePath}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (parsed.approach) {
|
|
424
|
+
console.log(`\n Abordagem: ${parsed.approach}`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (parsed.babySteps.length > 0) {
|
|
428
|
+
console.log(`\n Baby Steps: ${parsed.babySteps.length}`);
|
|
429
|
+
parsed.babySteps.forEach((step, i) => {
|
|
430
|
+
console.log(` ${i + 1}. ${step.name} [${step.agent}]`);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (parsed.risks.length > 0) {
|
|
435
|
+
console.log(`\n Riscos: ${parsed.risks.length}`);
|
|
436
|
+
parsed.risks.forEach((risk) => {
|
|
437
|
+
console.log(` - ${risk.description} (${risk.probability}/${risk.impact})`);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (parsed.diagrams.length > 0) {
|
|
442
|
+
console.log(`\n Diagramas: ${parsed.diagrams.length}`);
|
|
443
|
+
parsed.diagrams.forEach((d) => {
|
|
444
|
+
console.log(` - ${d.name} (${d.type})`);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
console.log("");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export function architectList(options: { json?: boolean; status?: string } = {}): void {
|
|
453
|
+
initSchema();
|
|
454
|
+
const db = getDb();
|
|
455
|
+
|
|
456
|
+
let query = "SELECT id, name, status, created_at, file_path FROM architectural_analyses";
|
|
457
|
+
const params: any[] = [];
|
|
458
|
+
|
|
459
|
+
if (options.status) {
|
|
460
|
+
query += " WHERE status = ?";
|
|
461
|
+
params.push(options.status);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
query += " ORDER BY created_at DESC";
|
|
465
|
+
|
|
466
|
+
const analyses = db.query(query).all(...params) as any[];
|
|
467
|
+
|
|
468
|
+
if (options.json) {
|
|
469
|
+
console.log(JSON.stringify(analyses, null, 2));
|
|
470
|
+
} else {
|
|
471
|
+
if (analyses.length === 0) {
|
|
472
|
+
console.log("\n[INFO] Nenhuma analise arquitetural encontrada.\n");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
477
|
+
console.log("║ ANALISES ARQUITETURAIS ║");
|
|
478
|
+
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
479
|
+
|
|
480
|
+
analyses.forEach((a) => {
|
|
481
|
+
const statusIcon =
|
|
482
|
+
a.status === "approved" ? "✓" :
|
|
483
|
+
a.status === "implemented" ? "★" :
|
|
484
|
+
a.status === "rejected" ? "✗" : "○";
|
|
485
|
+
|
|
486
|
+
console.log(` ${statusIcon} [${a.id}] ${a.name}`);
|
|
487
|
+
console.log(` Status: ${a.status} | Criada: ${a.created_at}`);
|
|
488
|
+
if (a.file_path) {
|
|
489
|
+
console.log(` Arquivo: ${a.file_path}`);
|
|
490
|
+
}
|
|
491
|
+
console.log("");
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export function architectSave(options: { file?: string; json?: boolean }): void {
|
|
497
|
+
initSchema();
|
|
498
|
+
const db = getDb();
|
|
499
|
+
const now = new Date().toISOString();
|
|
500
|
+
|
|
501
|
+
// Pegar analise pendente
|
|
502
|
+
const analysis = db.query(
|
|
503
|
+
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
504
|
+
).get() as any;
|
|
505
|
+
|
|
506
|
+
if (!analysis) {
|
|
507
|
+
if (options.json) {
|
|
508
|
+
console.log(JSON.stringify({ error: "NO_PENDING", message: "Nenhuma analise pendente" }));
|
|
509
|
+
} else {
|
|
510
|
+
console.log("\n[ERRO] Nenhuma analise pendente encontrada.\n");
|
|
511
|
+
}
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// v8.4: Resolver caminho do arquivo .md
|
|
516
|
+
const dir = ensureAnalysisDir();
|
|
517
|
+
let filePath: string;
|
|
518
|
+
if (options.file) {
|
|
519
|
+
// Se path fornecido, usar como-is (aceita absoluto ou relativo)
|
|
520
|
+
filePath = options.file;
|
|
521
|
+
} else {
|
|
522
|
+
filePath = join(dir, `${analysis.id}-${analysis.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").substring(0, 30)}.md`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// v8.4: Ler .md do disco e parsear para popular o DB
|
|
526
|
+
if (!existsSync(filePath)) {
|
|
527
|
+
if (options.json) {
|
|
528
|
+
console.log(JSON.stringify({ error: "FILE_NOT_FOUND", message: `Arquivo nao encontrado: ${filePath}` }));
|
|
529
|
+
} else {
|
|
530
|
+
console.log(`\n[ERRO] Arquivo nao encontrado: ${filePath}`);
|
|
531
|
+
console.log("O agente architect deve escrever o .md antes de chamar 'architect save'.\n");
|
|
532
|
+
}
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const content = readFileSync(filePath, "utf-8");
|
|
537
|
+
const parsed = parseAnalysisMd(content);
|
|
538
|
+
|
|
539
|
+
// Popular DB com dados extraidos do .md
|
|
540
|
+
const updates: string[] = [];
|
|
541
|
+
const values: any[] = [];
|
|
542
|
+
|
|
543
|
+
if (parsed.context) {
|
|
544
|
+
updates.push("context = ?");
|
|
545
|
+
values.push(parsed.context);
|
|
546
|
+
}
|
|
547
|
+
if (parsed.currentArchitecture) {
|
|
548
|
+
updates.push("current_architecture = ?");
|
|
549
|
+
values.push(parsed.currentArchitecture);
|
|
550
|
+
}
|
|
551
|
+
if (parsed.approach) {
|
|
552
|
+
updates.push("approach = ?");
|
|
553
|
+
values.push(parsed.approach);
|
|
554
|
+
}
|
|
555
|
+
if (parsed.diagrams.length > 0) {
|
|
556
|
+
updates.push("diagrams = ?");
|
|
557
|
+
values.push(JSON.stringify(parsed.diagrams));
|
|
558
|
+
}
|
|
559
|
+
if (parsed.babySteps.length > 0) {
|
|
560
|
+
updates.push("baby_steps = ?");
|
|
561
|
+
values.push(JSON.stringify(parsed.babySteps));
|
|
562
|
+
}
|
|
563
|
+
if (parsed.risks.length > 0) {
|
|
564
|
+
updates.push("risks = ?");
|
|
565
|
+
values.push(JSON.stringify(parsed.risks));
|
|
566
|
+
}
|
|
567
|
+
if (parsed.alternatives.length > 0) {
|
|
568
|
+
updates.push("alternatives = ?");
|
|
569
|
+
values.push(JSON.stringify(parsed.alternatives));
|
|
570
|
+
}
|
|
571
|
+
if (parsed.decisions.length > 0) {
|
|
572
|
+
updates.push("decisions = ?");
|
|
573
|
+
values.push(JSON.stringify(parsed.decisions));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
updates.push("file_path = ?");
|
|
577
|
+
values.push(filePath);
|
|
578
|
+
updates.push("updated_at = ?");
|
|
579
|
+
values.push(now);
|
|
580
|
+
values.push(analysis.id);
|
|
581
|
+
|
|
582
|
+
db.run(
|
|
583
|
+
`UPDATE architectural_analyses SET ${updates.join(", ")} WHERE id = ?`,
|
|
584
|
+
values
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
if (options.json) {
|
|
588
|
+
console.log(JSON.stringify({
|
|
589
|
+
success: true,
|
|
590
|
+
id: analysis.id,
|
|
591
|
+
filePath,
|
|
592
|
+
parsed: {
|
|
593
|
+
babySteps: parsed.babySteps.length,
|
|
594
|
+
risks: parsed.risks.length,
|
|
595
|
+
diagrams: parsed.diagrams.length,
|
|
596
|
+
decisions: parsed.decisions.length,
|
|
597
|
+
hasApproach: !!parsed.approach,
|
|
598
|
+
hasContext: !!parsed.context,
|
|
599
|
+
},
|
|
600
|
+
}));
|
|
601
|
+
} else {
|
|
602
|
+
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
603
|
+
console.log("║ ANALISE SALVA (parseada do .md) ║");
|
|
604
|
+
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
605
|
+
console.log(` Arquivo: ${filePath}`);
|
|
606
|
+
console.log(` Baby Steps: ${parsed.babySteps.length}`);
|
|
607
|
+
console.log(` Riscos: ${parsed.risks.length}`);
|
|
608
|
+
console.log(` Diagramas: ${parsed.diagrams.length}`);
|
|
609
|
+
console.log(` Decisoes: ${parsed.decisions.length}`);
|
|
610
|
+
if (!parsed.approach) {
|
|
611
|
+
console.log("\n [AVISO] Secao 'Solucao Proposta' nao encontrada no .md");
|
|
612
|
+
}
|
|
613
|
+
if (parsed.babySteps.length === 0) {
|
|
614
|
+
console.log(" [AVISO] Nenhum baby step encontrado. Verifique formato '### N. Nome'");
|
|
615
|
+
}
|
|
616
|
+
console.log("\n Proximo passo:");
|
|
617
|
+
console.log(` - Aprovar: architect approve`);
|
|
618
|
+
console.log(` - Iniciar feature: plan start "nome" --from-analysis ${analysis.id}\n`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export function architectApprove(options: { id?: string; json?: boolean }): void {
|
|
623
|
+
initSchema();
|
|
624
|
+
const db = getDb();
|
|
625
|
+
const now = new Date().toISOString();
|
|
626
|
+
|
|
627
|
+
let analysis: any;
|
|
628
|
+
|
|
629
|
+
if (options.id) {
|
|
630
|
+
analysis = db.query("SELECT * FROM architectural_analyses WHERE id = ?").get(options.id);
|
|
631
|
+
} else {
|
|
632
|
+
analysis = db.query(
|
|
633
|
+
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
634
|
+
).get();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (!analysis) {
|
|
638
|
+
if (options.json) {
|
|
639
|
+
console.log(JSON.stringify({ error: "NOT_FOUND", message: "Analise nao encontrada" }));
|
|
640
|
+
} else {
|
|
641
|
+
console.log("\n[ERRO] Analise nao encontrada.\n");
|
|
642
|
+
}
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (analysis.status !== "pending") {
|
|
647
|
+
if (options.json) {
|
|
648
|
+
console.log(JSON.stringify({ error: "INVALID_STATUS", message: `Analise ja esta ${analysis.status}` }));
|
|
649
|
+
} else {
|
|
650
|
+
console.log(`\n[ERRO] Analise ja esta com status '${analysis.status}'.\n`);
|
|
651
|
+
}
|
|
652
|
+
process.exit(1);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
db.run(
|
|
656
|
+
"UPDATE architectural_analyses SET status = 'approved', approved_at = ?, updated_at = ? WHERE id = ?",
|
|
657
|
+
[now, now, analysis.id]
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
if (options.json) {
|
|
661
|
+
console.log(JSON.stringify({
|
|
662
|
+
success: true,
|
|
663
|
+
id: analysis.id,
|
|
664
|
+
status: "approved",
|
|
665
|
+
nextStep: `plan start "${analysis.name}" --from-analysis ${analysis.id}`,
|
|
666
|
+
}));
|
|
667
|
+
} else {
|
|
668
|
+
console.log(`\n[OK] Analise ${analysis.id} aprovada.`);
|
|
669
|
+
console.log(`\nInicie a feature com import automatico dos baby steps:`);
|
|
670
|
+
console.log(` plan start "${analysis.name}" --from-analysis ${analysis.id}\n`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// v8.4: Simplificado - agora o feature importa baby steps automaticamente via --from-analysis
|
|
675
|
+
export function architectExport(options: { id?: string; json?: boolean }): void {
|
|
676
|
+
initSchema();
|
|
677
|
+
const db = getDb();
|
|
678
|
+
|
|
679
|
+
let analysis: any;
|
|
680
|
+
|
|
681
|
+
if (options.id) {
|
|
682
|
+
analysis = db.query("SELECT * FROM architectural_analyses WHERE id = ?").get(options.id);
|
|
683
|
+
} else {
|
|
684
|
+
analysis = db.query(
|
|
685
|
+
"SELECT * FROM architectural_analyses WHERE status = 'approved' ORDER BY approved_at DESC LIMIT 1"
|
|
686
|
+
).get();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (!analysis) {
|
|
690
|
+
if (options.json) {
|
|
691
|
+
console.log(JSON.stringify({ error: "NOT_FOUND", message: "Nenhuma analise aprovada encontrada" }));
|
|
692
|
+
} else {
|
|
693
|
+
console.log("\n[ERRO] Nenhuma analise aprovada encontrada.");
|
|
694
|
+
console.log("Use 'architect approve' primeiro.\n");
|
|
695
|
+
}
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const babySteps: BabyStep[] = analysis.baby_steps ? JSON.parse(analysis.baby_steps) : [];
|
|
700
|
+
const planCmd = `codexa plan start "${analysis.name}" --from-analysis ${analysis.id}`;
|
|
701
|
+
|
|
702
|
+
if (options.json) {
|
|
703
|
+
console.log(JSON.stringify({
|
|
704
|
+
analysisId: analysis.id,
|
|
705
|
+
analysisName: analysis.name,
|
|
706
|
+
babyStepsCount: babySteps.length,
|
|
707
|
+
filePath: analysis.file_path || null,
|
|
708
|
+
planStartCommand: planCmd,
|
|
709
|
+
}));
|
|
710
|
+
} else {
|
|
711
|
+
console.log("\n╔════════════════════════════════════════════════════════════╗");
|
|
712
|
+
console.log("║ EXPORTAR PARA FEATURE ║");
|
|
713
|
+
console.log("╚════════════════════════════════════════════════════════════╝\n");
|
|
714
|
+
|
|
715
|
+
console.log(` Analise: ${analysis.name} (${analysis.id})`);
|
|
716
|
+
console.log(` Baby Steps: ${babySteps.length}`);
|
|
717
|
+
if (analysis.file_path) {
|
|
718
|
+
console.log(` Arquivo: ${analysis.file_path}`);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
console.log(`\n Comando para iniciar feature com import automatico:`);
|
|
722
|
+
console.log(` ${planCmd}\n`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
export function architectCancel(options: { id?: string; json?: boolean }): void {
|
|
727
|
+
initSchema();
|
|
728
|
+
const db = getDb();
|
|
729
|
+
const now = new Date().toISOString();
|
|
730
|
+
|
|
731
|
+
let analysis: any;
|
|
732
|
+
|
|
733
|
+
if (options.id) {
|
|
734
|
+
analysis = db.query("SELECT * FROM architectural_analyses WHERE id = ?").get(options.id);
|
|
735
|
+
} else {
|
|
736
|
+
analysis = db.query(
|
|
737
|
+
"SELECT * FROM architectural_analyses WHERE status = 'pending' ORDER BY created_at DESC LIMIT 1"
|
|
738
|
+
).get();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (!analysis) {
|
|
742
|
+
if (options.json) {
|
|
743
|
+
console.log(JSON.stringify({ error: "NOT_FOUND", message: "Nenhuma analise pendente encontrada" }));
|
|
744
|
+
} else {
|
|
745
|
+
console.log("\n[INFO] Nenhuma analise pendente para cancelar.\n");
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
db.run(
|
|
751
|
+
"UPDATE architectural_analyses SET status = 'rejected', updated_at = ? WHERE id = ?",
|
|
752
|
+
[now, analysis.id]
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
if (options.json) {
|
|
756
|
+
console.log(JSON.stringify({ success: true, id: analysis.id, status: "rejected" }));
|
|
757
|
+
} else {
|
|
758
|
+
console.log(`\n[OK] Analise ${analysis.id} cancelada.\n`);
|
|
759
|
+
}
|
|
760
|
+
}
|