@codexa/cli 9.0.2 → 9.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,14 @@
1
1
  import { getDb } from "../db/connection";
2
2
  import { initSchema } from "../db/schema";
3
3
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
4
+ import { CodexaError } from "../errors";
4
5
  import { join } from "path";
5
6
 
6
7
  // ═══════════════════════════════════════════════════════════════
7
8
  // TYPES
8
9
  // ═══════════════════════════════════════════════════════════════
9
10
 
10
- interface BabyStep {
11
+ export interface BabyStep {
11
12
  number: number;
12
13
  name: string;
13
14
  what: string;
@@ -18,7 +19,7 @@ interface BabyStep {
18
19
  dependsOn?: number[];
19
20
  }
20
21
 
21
- interface Risk {
22
+ export interface Risk {
22
23
  id: string;
23
24
  description: string;
24
25
  probability: "low" | "medium" | "high";
@@ -35,7 +36,7 @@ interface Alternative {
35
36
  whyDiscarded?: string;
36
37
  }
37
38
 
38
- interface ArchitecturalDecision {
39
+ export interface ArchitecturalDecision {
39
40
  decision: string;
40
41
  rationale: string;
41
42
  }
@@ -103,23 +104,80 @@ function formatDate(date: Date): string {
103
104
  }
104
105
 
105
106
  // ═══════════════════════════════════════════════════════════════
106
- // MARKDOWN PARSER (v8.4)
107
+ // MARKDOWN PARSER (v8.4 + v9.0 tolerant matching)
107
108
  // ═══════════════════════════════════════════════════════════════
108
109
 
109
- function extractSection(content: string, header: string): string {
110
- // Split por headers ## e encontrar a secao correta
110
+ const HEADER_ALIASES: Record<string, string[]> = {
111
+ "Contexto e Entendimento": [
112
+ "contexto e entendimento", "context and understanding", "contexto",
113
+ "entendimento do problema", "understanding", "background",
114
+ ],
115
+ "Stack e Arquitetura Atual": [
116
+ "stack e arquitetura atual", "stack e arquitetura", "current architecture",
117
+ "arquitetura atual", "stack atual", "tech stack",
118
+ ],
119
+ "Solucao Proposta": [
120
+ "solucao proposta", "proposed solution", "abordagem proposta",
121
+ "proposta", "solution", "approach",
122
+ ],
123
+ "Diagramas": [
124
+ "diagramas", "diagrams", "diagramas mermaid", "architecture diagrams",
125
+ ],
126
+ "Baby Steps": [
127
+ "baby steps", "passos", "steps", "plano de implementacao",
128
+ "implementation plan", "implementation steps", "etapas",
129
+ ],
130
+ "Riscos e Mitigacoes": [
131
+ "riscos e mitigacoes", "risks and mitigations", "riscos",
132
+ "risks", "analise de riscos", "risk analysis",
133
+ ],
134
+ "Alternativas Descartadas": [
135
+ "alternativas descartadas", "alternativas", "alternatives",
136
+ "discarded alternatives", "alternativas consideradas",
137
+ ],
138
+ "Decisoes Arquiteturais": [
139
+ "decisoes arquiteturais", "architectural decisions",
140
+ "decisoes", "decisions",
141
+ ],
142
+ };
143
+
144
+ export function extractSection(content: string, header: string): string {
111
145
  const sections = content.split(/^## /m);
146
+
147
+ // Tier 1: Exact startsWith (fast path)
112
148
  for (const section of sections) {
113
149
  if (section.startsWith(header)) {
114
- // Remover o header e retornar o conteudo
115
150
  const lines = section.split("\n");
116
151
  return lines.slice(1).join("\n").trim();
117
152
  }
118
153
  }
154
+
155
+ // Tier 2: Case-insensitive startsWith
156
+ const headerLower = header.toLowerCase();
157
+ for (const section of sections) {
158
+ if (section.toLowerCase().startsWith(headerLower)) {
159
+ const lines = section.split("\n");
160
+ return lines.slice(1).join("\n").trim();
161
+ }
162
+ }
163
+
164
+ // Tier 3: Alias-based includes matching (case-insensitive)
165
+ const aliases = HEADER_ALIASES[header] || [];
166
+ for (const section of sections) {
167
+ const sectionHeader = section.split("\n")[0]?.toLowerCase().trim() || "";
168
+ for (const alias of aliases) {
169
+ if (sectionHeader.includes(alias)) {
170
+ const lines = section.split("\n");
171
+ return lines.slice(1).join("\n").trim();
172
+ }
173
+ }
174
+ }
175
+
176
+ console.warn(`[architect] Secao "${header}" nao encontrada no .md`);
119
177
  return "";
120
178
  }
121
179
 
122
- function parseBabySteps(section: string): BabyStep[] {
180
+ export function parseBabySteps(section: string): BabyStep[] {
123
181
  if (!section) return [];
124
182
  const steps: BabyStep[] = [];
125
183
  // Match "### N. Name" or "### Step N: Name"
@@ -152,7 +210,7 @@ function parseBabySteps(section: string): BabyStep[] {
152
210
  return steps;
153
211
  }
154
212
 
155
- function parseRisks(section: string): Risk[] {
213
+ export function parseRisks(section: string): Risk[] {
156
214
  if (!section) return [];
157
215
  const risks: Risk[] = [];
158
216
  const riskBlocks = section.split(/^###\s+/m).filter(Boolean);
@@ -173,7 +231,7 @@ function parseRisks(section: string): Risk[] {
173
231
  return risks;
174
232
  }
175
233
 
176
- function parseDiagrams(section: string): { name: string; type: string; content: string }[] {
234
+ export function parseDiagrams(section: string): { name: string; type: string; content: string }[] {
177
235
  if (!section) return [];
178
236
  const diagrams: { name: string; type: string; content: string }[] = [];
179
237
  const diagramRegex = /###\s+(.+)\n[\s\S]*?```mermaid\n([\s\S]*?)```/g;
@@ -192,7 +250,7 @@ function parseDiagrams(section: string): { name: string; type: string; content:
192
250
  return diagrams;
193
251
  }
194
252
 
195
- function parseDecisionsTable(section: string): ArchitecturalDecision[] {
253
+ export function parseDecisionsTable(section: string): ArchitecturalDecision[] {
196
254
  if (!section) return [];
197
255
  const decisions: ArchitecturalDecision[] = [];
198
256
  const lines = section.split("\n");
@@ -307,7 +365,7 @@ export function architectStart(description: string, options: { json?: boolean }
307
365
  console.log(` Criada: ${pending.created_at}`);
308
366
  console.log("\nUse 'architect show' para ver detalhes ou 'architect cancel' para cancelar.\n");
309
367
  }
310
- process.exit(1);
368
+ throw new CodexaError("Ja existe uma analise pendente. Use 'architect show' ou 'architect cancel'.");
311
369
  }
312
370
 
313
371
  // Criar nova analise
@@ -509,7 +567,7 @@ export function architectSave(options: { file?: string; json?: boolean }): void
509
567
  } else {
510
568
  console.log("\n[ERRO] Nenhuma analise pendente encontrada.\n");
511
569
  }
512
- process.exit(1);
570
+ throw new CodexaError("Nenhuma analise pendente encontrada.");
513
571
  }
514
572
 
515
573
  // v8.4: Resolver caminho do arquivo .md
@@ -530,7 +588,7 @@ export function architectSave(options: { file?: string; json?: boolean }): void
530
588
  console.log(`\n[ERRO] Arquivo nao encontrado: ${filePath}`);
531
589
  console.log("O agente architect deve escrever o .md antes de chamar 'architect save'.\n");
532
590
  }
533
- process.exit(1);
591
+ throw new CodexaError(`Arquivo nao encontrado: ${filePath}`);
534
592
  }
535
593
 
536
594
  const content = readFileSync(filePath, "utf-8");
@@ -640,7 +698,7 @@ export function architectApprove(options: { id?: string; json?: boolean }): void
640
698
  } else {
641
699
  console.log("\n[ERRO] Analise nao encontrada.\n");
642
700
  }
643
- process.exit(1);
701
+ throw new CodexaError("Analise nao encontrada.");
644
702
  }
645
703
 
646
704
  if (analysis.status !== "pending") {
@@ -649,7 +707,7 @@ export function architectApprove(options: { id?: string; json?: boolean }): void
649
707
  } else {
650
708
  console.log(`\n[ERRO] Analise ja esta com status '${analysis.status}'.\n`);
651
709
  }
652
- process.exit(1);
710
+ throw new CodexaError(`Analise ja esta com status '${analysis.status}'.`);
653
711
  }
654
712
 
655
713
  db.run(
@@ -693,7 +751,7 @@ export function architectExport(options: { id?: string; json?: boolean }): void
693
751
  console.log("\n[ERRO] Nenhuma analise aprovada encontrada.");
694
752
  console.log("Use 'architect approve' primeiro.\n");
695
753
  }
696
- process.exit(1);
754
+ throw new CodexaError("Nenhuma analise aprovada encontrada. Use 'architect approve' primeiro.");
697
755
  }
698
756
 
699
757
  const babySteps: BabyStep[] = analysis.baby_steps ? JSON.parse(analysis.baby_steps) : [];
package/commands/check.ts CHANGED
@@ -1,15 +1,14 @@
1
1
  import { getDb } from "../db/connection";
2
2
  import { initSchema } from "../db/schema";
3
3
  import { enforceGate } from "../gates/validator";
4
+ import { resolveSpec } from "./spec-resolver";
4
5
 
5
- export function checkRequest(): void {
6
+ export function checkRequest(specId?: string): void {
6
7
  initSchema();
7
8
  enforceGate("check-request");
8
9
 
9
10
  const db = getDb();
10
- const spec = db
11
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
12
- .get() as any;
11
+ const spec = resolveSpec(specId, ["planning"]);
13
12
 
14
13
  const tasks = db
15
14
  .query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
@@ -68,16 +67,14 @@ export function checkRequest(): void {
68
67
  console.log(`Para ajustar: plan task-add (adiciona mais tasks)\n`);
69
68
  }
70
69
 
71
- export function checkApprove(): void {
70
+ export function checkApprove(specId?: string): void {
72
71
  initSchema();
73
72
  enforceGate("check-approve");
74
73
 
75
74
  const db = getDb();
76
75
  const now = new Date().toISOString();
77
76
 
78
- const spec = db
79
- .query("SELECT * FROM specs WHERE phase = 'checking' ORDER BY created_at DESC LIMIT 1")
80
- .get() as any;
77
+ const spec = resolveSpec(specId, ["checking"]);
81
78
 
82
79
  // Atualizar para aprovado
83
80
  db.run(
@@ -100,20 +97,13 @@ export function checkApprove(): void {
100
97
  console.log(`3. Complete uma task com: task done <id> --checkpoint "..."\n`);
101
98
  }
102
99
 
103
- export function checkReject(reason: string): void {
100
+ export function checkReject(reason: string, specId?: string): void {
104
101
  initSchema();
105
102
 
106
103
  const db = getDb();
107
104
  const now = new Date().toISOString();
108
105
 
109
- const spec = db
110
- .query("SELECT * FROM specs WHERE phase = 'checking' ORDER BY created_at DESC LIMIT 1")
111
- .get() as any;
112
-
113
- if (!spec) {
114
- console.error("\nNenhum plano aguardando aprovacao.\n");
115
- process.exit(1);
116
- }
106
+ const spec = resolveSpec(specId, ["checking"]);
117
107
 
118
108
  // Voltar para planning
119
109
  db.run("UPDATE specs SET phase = 'planning', updated_at = ? WHERE id = ?", [now, spec.id]);
package/commands/clear.ts CHANGED
@@ -5,6 +5,7 @@ import { join, dirname } from "path";
5
5
 
6
6
  interface ClearOptions {
7
7
  force?: boolean;
8
+ specId?: string;
8
9
  }
9
10
 
10
11
  /**
@@ -64,6 +65,45 @@ export function clearTasks(options: ClearOptions = {}): void {
64
65
  return;
65
66
  }
66
67
 
68
+ // Per-spec clear or clear all
69
+ if (options.specId) {
70
+ console.log("\n" + "═".repeat(60));
71
+ console.log(`LIMPANDO SPEC: ${options.specId}`);
72
+ console.log("═".repeat(60) + "\n");
73
+
74
+ const tablesWithSpecId = [
75
+ "reasoning_log",
76
+ "knowledge_graph",
77
+ "knowledge",
78
+ "gate_bypasses",
79
+ "snapshots",
80
+ "review",
81
+ "artifacts",
82
+ "decisions",
83
+ "tasks",
84
+ "context",
85
+ "specs",
86
+ ];
87
+
88
+ for (const table of tablesWithSpecId) {
89
+ try {
90
+ const result = db.run(`DELETE FROM ${table} WHERE spec_id = ?`, [options.specId]);
91
+ if (result.changes > 0) {
92
+ console.log(` Limpo: ${table} (${result.changes} registros)`);
93
+ }
94
+ } catch (err: any) {
95
+ if (!err.message.includes("no such table") && !err.message.includes("no such column")) {
96
+ console.error(` Erro ao limpar ${table}: ${err.message}`);
97
+ }
98
+ }
99
+ }
100
+
101
+ console.log("\n" + "═".repeat(60));
102
+ console.log(`SPEC ${options.specId} LIMPO`);
103
+ console.log("═".repeat(60) + "\n");
104
+ return;
105
+ }
106
+
67
107
  console.log("\n" + "═".repeat(60));
68
108
  console.log("LIMPANDO TASKS...");
69
109
  console.log("═".repeat(60) + "\n");
@@ -71,7 +111,6 @@ export function clearTasks(options: ClearOptions = {}): void {
71
111
  // Ordem de deleção respeita foreign keys
72
112
  // v8.0: Incluindo novas tabelas
73
113
  const tablesToClear = [
74
- "session_summaries",
75
114
  "reasoning_log",
76
115
  "knowledge_graph",
77
116
  "knowledge",
@@ -1,17 +1,7 @@
1
1
  import { getDb } from "../db/connection";
2
- import { initSchema } from "../db/schema";
3
-
4
- function getNextDecisionId(specId: string): string {
5
- const db = getDb();
6
- const last = db
7
- .query("SELECT id FROM decisions WHERE spec_id = ? ORDER BY created_at DESC LIMIT 1")
8
- .get(specId) as any;
9
-
10
- if (!last) return "DEC-001";
11
-
12
- const num = parseInt(last.id.replace("DEC-", "")) + 1;
13
- return `DEC-${num.toString().padStart(3, "0")}`;
14
- }
2
+ import { initSchema, getNextDecisionId } from "../db/schema";
3
+ import { resolveSpec } from "./spec-resolver";
4
+ import { CodexaError } from "../errors";
15
5
 
16
6
  interface ConflictAnalysis {
17
7
  hasConflict: boolean;
@@ -136,20 +126,13 @@ function detectConflicts(
136
126
  };
137
127
  }
138
128
 
139
- export function decide(title: string, decision: string, options: { rationale?: string; force?: boolean }): void {
129
+ export function decide(title: string, decision: string, options: { rationale?: string; force?: boolean; specId?: string }): void {
140
130
  initSchema();
141
131
 
142
132
  const db = getDb();
143
133
  const now = new Date().toISOString();
144
134
 
145
- const spec = db
146
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
147
- .get() as any;
148
-
149
- if (!spec) {
150
- console.error("\nNenhuma feature ativa.\n");
151
- process.exit(1);
152
- }
135
+ const spec = resolveSpec(options.specId);
153
136
 
154
137
  // Verificar conflitos com decisoes existentes
155
138
  const existingDecisions = db
@@ -171,9 +154,9 @@ export function decide(title: string, decision: string, options: { rationale?: s
171
154
  }
172
155
 
173
156
  console.warn(`${"─".repeat(50)}`);
174
- console.warn(`Para registrar mesmo assim, use: decide "${title}" "${decision}" --force`);
175
- console.warn(`Ou revise as decisoes existentes com: decisions\n`);
176
- process.exit(1);
157
+ throw new CodexaError(
158
+ `Potencial conflito detectado.\nPara registrar mesmo assim, use: decide "${title}" "${decision}" --force\nOu revise as decisoes existentes com: decisions`
159
+ );
177
160
  }
178
161
  }
179
162
 
@@ -182,21 +165,33 @@ export function decide(title: string, decision: string, options: { rationale?: s
182
165
  .query("SELECT * FROM tasks WHERE spec_id = ? AND status = 'running' LIMIT 1")
183
166
  .get(spec.id) as any;
184
167
 
185
- const decisionId = getNextDecisionId(spec.id);
186
-
187
- db.run(
188
- `INSERT INTO decisions (id, spec_id, task_ref, title, decision, rationale, status, created_at)
189
- VALUES (?, ?, ?, ?, ?, ?, 'active', ?)`,
190
- [
191
- decisionId,
192
- spec.id,
193
- currentTask?.number || null,
194
- title,
195
- decision,
196
- options.rationale || null,
197
- now,
198
- ]
199
- );
168
+ let decisionId = "";
169
+ let retries = 3;
170
+ while (retries > 0) {
171
+ decisionId = getNextDecisionId(spec.id);
172
+ try {
173
+ db.run(
174
+ `INSERT INTO decisions (id, spec_id, task_ref, title, decision, rationale, status, created_at)
175
+ VALUES (?, ?, ?, ?, ?, ?, 'active', ?)`,
176
+ [
177
+ decisionId,
178
+ spec.id,
179
+ currentTask?.number || null,
180
+ title,
181
+ decision,
182
+ options.rationale || null,
183
+ now,
184
+ ]
185
+ );
186
+ break;
187
+ } catch (e: any) {
188
+ if (e.message?.includes("UNIQUE constraint") && retries > 1) {
189
+ retries--;
190
+ continue;
191
+ }
192
+ throw e;
193
+ }
194
+ }
200
195
 
201
196
  console.log(`\nDecisao registrada: ${decisionId}`);
202
197
  console.log(`Titulo: ${title}`);
@@ -207,19 +202,12 @@ export function decide(title: string, decision: string, options: { rationale?: s
207
202
  console.log();
208
203
  }
209
204
 
210
- export function listDecisions(json: boolean = false): void {
205
+ export function listDecisions(json: boolean = false, specId?: string): void {
211
206
  initSchema();
212
207
 
213
208
  const db = getDb();
214
209
 
215
- const spec = db
216
- .query("SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled') ORDER BY created_at DESC LIMIT 1")
217
- .get() as any;
218
-
219
- if (!spec) {
220
- console.error("\nNenhuma feature ativa.\n");
221
- process.exit(1);
222
- }
210
+ const spec = resolveSpec(specId);
223
211
 
224
212
  const decisions = db
225
213
  .query("SELECT * FROM decisions WHERE spec_id = ? ORDER BY created_at")