@codexa/cli 9.0.2 → 9.0.3

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.
@@ -0,0 +1,531 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import {
3
+ extractSection,
4
+ parseBabySteps,
5
+ parseRisks,
6
+ parseDiagrams,
7
+ parseDecisionsTable,
8
+ type BabyStep,
9
+ type Risk,
10
+ type ArchitecturalDecision,
11
+ } from "./architect";
12
+
13
+ describe("extractSection", () => {
14
+ it("should extract section with exact match (Tier 1)", () => {
15
+ const content = `## Solucao Proposta
16
+
17
+ This is the proposed solution.
18
+
19
+ ## Other Section
20
+ Other content.`;
21
+
22
+ const result = extractSection(content, "Solucao Proposta");
23
+ expect(result).toBe("This is the proposed solution.");
24
+ });
25
+
26
+ it("should extract section with case-insensitive match (Tier 2)", () => {
27
+ const content = `## solucao proposta
28
+
29
+ This is the content.
30
+
31
+ ## Next Section`;
32
+
33
+ const result = extractSection(content, "Solucao Proposta");
34
+ expect(result).toBe("This is the content.");
35
+ });
36
+
37
+ it("should extract section using alias (Tier 3)", () => {
38
+ const content = `## Proposed Solution
39
+
40
+ This is the solution content.
41
+
42
+ ## Risks`;
43
+
44
+ const result = extractSection(content, "Solucao Proposta");
45
+ expect(result).toBe("This is the solution content.");
46
+ });
47
+
48
+ it("should return empty string when header not found", () => {
49
+ const content = `## Some Header
50
+
51
+ Content here.`;
52
+
53
+ const result = extractSection(content, "Nonexistent Section");
54
+ expect(result).toBe("");
55
+ });
56
+
57
+ it("should extract correct section when multiple sections exist", () => {
58
+ const content = `## Contexto e Entendimento
59
+
60
+ Context content.
61
+
62
+ ## Solucao Proposta
63
+
64
+ Solution content.
65
+
66
+ ## Baby Steps
67
+
68
+ Steps content.`;
69
+
70
+ const result = extractSection(content, "Solucao Proposta");
71
+ expect(result).toBe("Solution content.");
72
+ });
73
+
74
+ it("should extract section with multiple lines", () => {
75
+ const content = `## Riscos e Mitigacoes
76
+
77
+ Line 1
78
+ Line 2
79
+ Line 3
80
+
81
+ ## Next Section`;
82
+
83
+ const result = extractSection(content, "Riscos e Mitigacoes");
84
+ expect(result).toBe("Line 1\nLine 2\nLine 3");
85
+ });
86
+
87
+ it("should handle alias 'risks' for Riscos e Mitigacoes", () => {
88
+ const content = `## Risks
89
+
90
+ Risk content here.
91
+
92
+ ## Other`;
93
+
94
+ const result = extractSection(content, "Riscos e Mitigacoes");
95
+ expect(result).toBe("Risk content here.");
96
+ });
97
+
98
+ it("should handle alias 'steps' for Baby Steps", () => {
99
+ const content = `## Steps
100
+
101
+ Step content.
102
+
103
+ ## End`;
104
+
105
+ const result = extractSection(content, "Baby Steps");
106
+ expect(result).toBe("Step content.");
107
+ });
108
+ });
109
+
110
+ describe("parseBabySteps", () => {
111
+ it("should parse single basic step", () => {
112
+ const section = `### 1. Create schema
113
+
114
+ **O que**: Build database schema
115
+ **Por que**: Foundation for data layer`;
116
+
117
+ const result = parseBabySteps(section);
118
+ expect(result).toHaveLength(1);
119
+ expect(result[0]).toMatchObject({
120
+ number: 1,
121
+ name: "Create schema",
122
+ what: "Build database schema",
123
+ why: "Foundation for data layer",
124
+ });
125
+ });
126
+
127
+ it("should parse step with 'Step N:' format", () => {
128
+ const section = `### Step 2: Setup API routes
129
+
130
+ **O que**: Create REST endpoints
131
+ **Por que**: Enable frontend communication`;
132
+
133
+ const result = parseBabySteps(section);
134
+ expect(result).toHaveLength(1);
135
+ expect(result[0]).toMatchObject({
136
+ number: 2,
137
+ name: "Setup API routes",
138
+ what: "Create REST endpoints",
139
+ why: "Enable frontend communication",
140
+ });
141
+ });
142
+
143
+ it("should parse step with files", () => {
144
+ const section = `### 3. Implement handlers
145
+
146
+ **O que**: Write business logic
147
+ **Arquivos**: \`src/handlers/user.ts\`, \`src/handlers/auth.ts\``;
148
+
149
+ const result = parseBabySteps(section);
150
+ expect(result).toHaveLength(1);
151
+ expect(result[0].files).toEqual(["src/handlers/user.ts", "src/handlers/auth.ts"]);
152
+ });
153
+
154
+ it("should parse step with files using semicolon separator", () => {
155
+ const section = `### 1. Create files
156
+
157
+ **Arquivos**: \`app.ts\`; \`config.ts\`; \`index.ts\``;
158
+
159
+ const result = parseBabySteps(section);
160
+ expect(result[0].files).toEqual(["app.ts", "config.ts", "index.ts"]);
161
+ });
162
+
163
+ it("should parse step with dependencies", () => {
164
+ const section = `### 4. Build UI
165
+
166
+ **O que**: Create components
167
+ **Depende de**: 1, 2, 3`;
168
+
169
+ const result = parseBabySteps(section);
170
+ expect(result[0].dependsOn).toEqual([1, 2, 3]);
171
+ });
172
+
173
+ it("should parse step with agent", () => {
174
+ const section = `### 5. Deploy
175
+
176
+ **O que**: Setup infrastructure
177
+ **Agente**: database-postgres`;
178
+
179
+ const result = parseBabySteps(section);
180
+ expect(result[0].agent).toBe("database-postgres");
181
+ });
182
+
183
+ it("should return empty array for empty section", () => {
184
+ const result = parseBabySteps("");
185
+ expect(result).toEqual([]);
186
+ });
187
+
188
+ it("should parse multiple steps", () => {
189
+ const section = `### 1. First step
190
+
191
+ **O que**: Do first thing
192
+ **Por que**: Needed first
193
+
194
+ ### 2. Second step
195
+
196
+ **O que**: Do second thing
197
+ **Por que**: Needed second
198
+ **Depende de**: 1`;
199
+
200
+ const result = parseBabySteps(section);
201
+ expect(result).toHaveLength(2);
202
+ expect(result[0].number).toBe(1);
203
+ expect(result[0].name).toBe("First step");
204
+ expect(result[1].number).toBe(2);
205
+ expect(result[1].name).toBe("Second step");
206
+ expect(result[1].dependsOn).toEqual([1]);
207
+ });
208
+
209
+ it("should parse step with all fields", () => {
210
+ const section = `### Step 6: Complete integration
211
+
212
+ **O que**: Integrate all systems
213
+ **Por que**: Final assembly needed
214
+ **Resultado**: Fully integrated system
215
+ **Arquivos**: \`src/main.ts\`, \`src/index.ts\`
216
+ **Agente**: frontend-next
217
+ **Depende de**: 4, 5`;
218
+
219
+ const result = parseBabySteps(section);
220
+ expect(result[0]).toMatchObject({
221
+ number: 6,
222
+ name: "Complete integration",
223
+ what: "Integrate all systems",
224
+ why: "Final assembly needed",
225
+ result: "Fully integrated system",
226
+ files: ["src/main.ts", "src/index.ts"],
227
+ agent: "frontend-next",
228
+ dependsOn: [4, 5],
229
+ });
230
+ });
231
+
232
+ it("should handle files without backticks", () => {
233
+ const section = `### 1. Create files
234
+
235
+ **Arquivos**: file1.ts, file2.ts`;
236
+
237
+ const result = parseBabySteps(section);
238
+ expect(result[0].files).toEqual(["file1.ts", "file2.ts"]);
239
+ });
240
+ });
241
+
242
+ describe("parseRisks", () => {
243
+ it("should parse standard risk with R prefix", () => {
244
+ const section = `### R1. Data loss
245
+
246
+ **Probabilidade**: high
247
+ **Impacto**: high
248
+ **Mitigacao**: Implement automated backups`;
249
+
250
+ const result = parseRisks(section);
251
+ expect(result).toHaveLength(1);
252
+ expect(result[0]).toMatchObject({
253
+ id: "R1",
254
+ description: "Data loss",
255
+ probability: "high",
256
+ impact: "high",
257
+ mitigation: "Implement automated backups",
258
+ });
259
+ });
260
+
261
+ it("should parse risk without R prefix", () => {
262
+ const section = `### 1. Performance degradation
263
+
264
+ **Probabilidade**: medium
265
+ **Impacto**: high
266
+ **Mitigacao**: Add caching layer`;
267
+
268
+ const result = parseRisks(section);
269
+ expect(result[0]).toMatchObject({
270
+ id: "R1",
271
+ description: "Performance degradation",
272
+ probability: "medium",
273
+ impact: "high",
274
+ mitigation: "Add caching layer",
275
+ });
276
+ });
277
+
278
+ it("should return empty array for empty section", () => {
279
+ const result = parseRisks("");
280
+ expect(result).toEqual([]);
281
+ });
282
+
283
+ it("should default to medium for missing probability", () => {
284
+ const section = `### R2. Security breach
285
+
286
+ **Impacto**: high
287
+ **Mitigacao**: Use encryption`;
288
+
289
+ const result = parseRisks(section);
290
+ expect(result[0].probability).toBe("medium");
291
+ });
292
+
293
+ it("should default to medium for missing impact", () => {
294
+ const section = `### R3. API rate limiting
295
+
296
+ **Probabilidade**: low
297
+ **Mitigacao**: Implement retry logic`;
298
+
299
+ const result = parseRisks(section);
300
+ expect(result[0].impact).toBe("medium");
301
+ });
302
+
303
+ it("should handle Mitigação with cedilla", () => {
304
+ const section = `### R1. Test risk
305
+
306
+ **Probabilidade**: low
307
+ **Impacto**: low
308
+ **Mitigação**: Do something`;
309
+
310
+ const result = parseRisks(section);
311
+ expect(result[0].mitigation).toBe("Do something");
312
+ });
313
+
314
+ it("should parse multiple risks", () => {
315
+ const section = `### R1. First risk
316
+
317
+ **Probabilidade**: high
318
+ **Impacto**: medium
319
+ **Mitigacao**: Mitigation 1
320
+
321
+ ### R2. Second risk
322
+
323
+ **Probabilidade**: low
324
+ **Impacto**: high
325
+ **Mitigacao**: Mitigation 2`;
326
+
327
+ const result = parseRisks(section);
328
+ expect(result).toHaveLength(2);
329
+ expect(result[0].id).toBe("R1");
330
+ expect(result[0].description).toBe("First risk");
331
+ expect(result[1].id).toBe("R2");
332
+ expect(result[1].description).toBe("Second risk");
333
+ });
334
+
335
+ it("should handle probability and impact variations", () => {
336
+ const section = `### R1. Test
337
+
338
+ **Probabilidade**: high
339
+ **Impacto**: low
340
+ **Mitigacao**: Fix`;
341
+
342
+ const result = parseRisks(section);
343
+ expect(result[0].probability).toBe("high");
344
+ expect(result[0].impact).toBe("low");
345
+ });
346
+ });
347
+
348
+ describe("parseDiagrams", () => {
349
+ it("should parse mermaid flowchart diagram", () => {
350
+ const section = `### System Architecture
351
+
352
+ Some description here.
353
+
354
+ \`\`\`mermaid
355
+ flowchart TD
356
+ A[Client] --> B[Server]
357
+ B --> C[Database]
358
+ \`\`\``;
359
+
360
+ const result = parseDiagrams(section);
361
+ expect(result).toHaveLength(1);
362
+ expect(result[0]).toMatchObject({
363
+ name: "System Architecture",
364
+ type: "flowchart",
365
+ content: "flowchart TD\n A[Client] --> B[Server]\n B --> C[Database]",
366
+ });
367
+ });
368
+
369
+ it("should parse mermaid sequence diagram", () => {
370
+ const section = `### User Flow
371
+
372
+ \`\`\`mermaid
373
+ sequenceDiagram
374
+ User->>API: Request
375
+ API->>DB: Query
376
+ DB-->>API: Result
377
+ API-->>User: Response
378
+ \`\`\``;
379
+
380
+ const result = parseDiagrams(section);
381
+ expect(result[0]).toMatchObject({
382
+ name: "User Flow",
383
+ type: "sequenceDiagram",
384
+ });
385
+ });
386
+
387
+ it("should return empty array when no diagrams present", () => {
388
+ const section = `### Some Section
389
+
390
+ No diagrams here.`;
391
+
392
+ const result = parseDiagrams(section);
393
+ expect(result).toEqual([]);
394
+ });
395
+
396
+ it("should parse multiple diagrams", () => {
397
+ const section = `### First Diagram
398
+
399
+ \`\`\`mermaid
400
+ flowchart LR
401
+ A --> B
402
+ \`\`\`
403
+
404
+ ### Second Diagram
405
+
406
+ \`\`\`mermaid
407
+ graph TD
408
+ C --> D
409
+ \`\`\``;
410
+
411
+ const result = parseDiagrams(section);
412
+ expect(result).toHaveLength(2);
413
+ expect(result[0].name).toBe("First Diagram");
414
+ expect(result[0].type).toBe("flowchart");
415
+ expect(result[1].name).toBe("Second Diagram");
416
+ expect(result[1].type).toBe("graph");
417
+ });
418
+
419
+ it("should handle diagram with complex content", () => {
420
+ const section = `### Component Diagram
421
+
422
+ \`\`\`mermaid
423
+ classDiagram
424
+ class User {
425
+ +String name
426
+ +String email
427
+ +login()
428
+ }
429
+ class Admin {
430
+ +deleteUser()
431
+ }
432
+ User <|-- Admin
433
+ \`\`\``;
434
+
435
+ const result = parseDiagrams(section);
436
+ expect(result[0].type).toBe("classDiagram");
437
+ expect(result[0].content).toContain("class User");
438
+ expect(result[0].content).toContain("User <|-- Admin");
439
+ });
440
+ });
441
+
442
+ describe("parseDecisionsTable", () => {
443
+ it("should parse standard decisions table", () => {
444
+ const section = `| Decisao | Justificativa |
445
+ | --- | --- |
446
+ | Use Drizzle ORM | Better TypeScript support and DX |
447
+ | Deploy on Vercel | Seamless Next.js integration |`;
448
+
449
+ const result = parseDecisionsTable(section);
450
+ expect(result).toHaveLength(2);
451
+ expect(result[0]).toMatchObject({
452
+ decision: "Use Drizzle ORM",
453
+ rationale: "Better TypeScript support and DX",
454
+ });
455
+ expect(result[1]).toMatchObject({
456
+ decision: "Deploy on Vercel",
457
+ rationale: "Seamless Next.js integration",
458
+ });
459
+ });
460
+
461
+ it("should skip header row with 'Decisao'", () => {
462
+ const section = `| Decisao | Justificativa |
463
+ | --- | --- |
464
+ | Use PostgreSQL | Reliable and scalable |`;
465
+
466
+ const result = parseDecisionsTable(section);
467
+ expect(result).toHaveLength(1);
468
+ expect(result[0].decision).not.toBe("Decisao");
469
+ });
470
+
471
+ it("should return empty array for empty section", () => {
472
+ const result = parseDecisionsTable("");
473
+ expect(result).toEqual([]);
474
+ });
475
+
476
+ it("should handle table with only header", () => {
477
+ const section = `| Decisao | Justificativa |
478
+ | --- | --- |`;
479
+
480
+ const result = parseDecisionsTable(section);
481
+ expect(result).toEqual([]);
482
+ });
483
+
484
+ it("should parse single decision", () => {
485
+ const section = `| Decisao | Justificativa |
486
+ | --- | --- |
487
+ | Use TypeScript | Type safety and better IDE support |`;
488
+
489
+ const result = parseDecisionsTable(section);
490
+ expect(result).toHaveLength(1);
491
+ expect(result[0]).toMatchObject({
492
+ decision: "Use TypeScript",
493
+ rationale: "Type safety and better IDE support",
494
+ });
495
+ });
496
+
497
+ it("should handle decisions with pipes in content", () => {
498
+ const section = `| Decisao | Justificativa |
499
+ | --- | --- |
500
+ | Use A or B | Choose A if X, otherwise B |`;
501
+
502
+ const result = parseDecisionsTable(section);
503
+ expect(result).toHaveLength(1);
504
+ // Note: This will capture "Use A or B " due to split behavior
505
+ expect(result[0].decision).toContain("Use A or B");
506
+ });
507
+
508
+ it("should trim whitespace from cells", () => {
509
+ const section = `| Decisao | Justificativa |
510
+ | --- | --- |
511
+ | Use React | Component-based architecture |`;
512
+
513
+ const result = parseDecisionsTable(section);
514
+ expect(result[0].decision).toBe("Use React");
515
+ expect(result[0].rationale).toBe("Component-based architecture");
516
+ });
517
+
518
+ it("should handle alternative header names (not auto-skipped)", () => {
519
+ // Note: The code only skips rows where decision === "Decisao", not "Decision"
520
+ // So "Decision" header row is treated as a data row
521
+ const section = `| Decision | Rationale |
522
+ | --- | --- |
523
+ | Use REST API | Well-understood and widely supported |`;
524
+
525
+ const result = parseDecisionsTable(section);
526
+ // "Decision" row is included (not filtered), separator is skipped
527
+ expect(result).toHaveLength(2);
528
+ expect(result[0].decision).toBe("Decision");
529
+ expect(result[1].decision).toBe("Use REST API");
530
+ });
531
+ });
@@ -7,7 +7,7 @@ import { join } from "path";
7
7
  // TYPES
8
8
  // ═══════════════════════════════════════════════════════════════
9
9
 
10
- interface BabyStep {
10
+ export interface BabyStep {
11
11
  number: number;
12
12
  name: string;
13
13
  what: string;
@@ -18,7 +18,7 @@ interface BabyStep {
18
18
  dependsOn?: number[];
19
19
  }
20
20
 
21
- interface Risk {
21
+ export interface Risk {
22
22
  id: string;
23
23
  description: string;
24
24
  probability: "low" | "medium" | "high";
@@ -35,7 +35,7 @@ interface Alternative {
35
35
  whyDiscarded?: string;
36
36
  }
37
37
 
38
- interface ArchitecturalDecision {
38
+ export interface ArchitecturalDecision {
39
39
  decision: string;
40
40
  rationale: string;
41
41
  }
@@ -103,23 +103,80 @@ function formatDate(date: Date): string {
103
103
  }
104
104
 
105
105
  // ═══════════════════════════════════════════════════════════════
106
- // MARKDOWN PARSER (v8.4)
106
+ // MARKDOWN PARSER (v8.4 + v9.0 tolerant matching)
107
107
  // ═══════════════════════════════════════════════════════════════
108
108
 
109
- function extractSection(content: string, header: string): string {
110
- // Split por headers ## e encontrar a secao correta
109
+ const HEADER_ALIASES: Record<string, string[]> = {
110
+ "Contexto e Entendimento": [
111
+ "contexto e entendimento", "context and understanding", "contexto",
112
+ "entendimento do problema", "understanding", "background",
113
+ ],
114
+ "Stack e Arquitetura Atual": [
115
+ "stack e arquitetura atual", "stack e arquitetura", "current architecture",
116
+ "arquitetura atual", "stack atual", "tech stack",
117
+ ],
118
+ "Solucao Proposta": [
119
+ "solucao proposta", "proposed solution", "abordagem proposta",
120
+ "proposta", "solution", "approach",
121
+ ],
122
+ "Diagramas": [
123
+ "diagramas", "diagrams", "diagramas mermaid", "architecture diagrams",
124
+ ],
125
+ "Baby Steps": [
126
+ "baby steps", "passos", "steps", "plano de implementacao",
127
+ "implementation plan", "implementation steps", "etapas",
128
+ ],
129
+ "Riscos e Mitigacoes": [
130
+ "riscos e mitigacoes", "risks and mitigations", "riscos",
131
+ "risks", "analise de riscos", "risk analysis",
132
+ ],
133
+ "Alternativas Descartadas": [
134
+ "alternativas descartadas", "alternativas", "alternatives",
135
+ "discarded alternatives", "alternativas consideradas",
136
+ ],
137
+ "Decisoes Arquiteturais": [
138
+ "decisoes arquiteturais", "architectural decisions",
139
+ "decisoes", "decisions",
140
+ ],
141
+ };
142
+
143
+ export function extractSection(content: string, header: string): string {
111
144
  const sections = content.split(/^## /m);
145
+
146
+ // Tier 1: Exact startsWith (fast path)
112
147
  for (const section of sections) {
113
148
  if (section.startsWith(header)) {
114
- // Remover o header e retornar o conteudo
115
149
  const lines = section.split("\n");
116
150
  return lines.slice(1).join("\n").trim();
117
151
  }
118
152
  }
153
+
154
+ // Tier 2: Case-insensitive startsWith
155
+ const headerLower = header.toLowerCase();
156
+ for (const section of sections) {
157
+ if (section.toLowerCase().startsWith(headerLower)) {
158
+ const lines = section.split("\n");
159
+ return lines.slice(1).join("\n").trim();
160
+ }
161
+ }
162
+
163
+ // Tier 3: Alias-based includes matching (case-insensitive)
164
+ const aliases = HEADER_ALIASES[header] || [];
165
+ for (const section of sections) {
166
+ const sectionHeader = section.split("\n")[0]?.toLowerCase().trim() || "";
167
+ for (const alias of aliases) {
168
+ if (sectionHeader.includes(alias)) {
169
+ const lines = section.split("\n");
170
+ return lines.slice(1).join("\n").trim();
171
+ }
172
+ }
173
+ }
174
+
175
+ console.warn(`[architect] Secao "${header}" nao encontrada no .md`);
119
176
  return "";
120
177
  }
121
178
 
122
- function parseBabySteps(section: string): BabyStep[] {
179
+ export function parseBabySteps(section: string): BabyStep[] {
123
180
  if (!section) return [];
124
181
  const steps: BabyStep[] = [];
125
182
  // Match "### N. Name" or "### Step N: Name"
@@ -152,7 +209,7 @@ function parseBabySteps(section: string): BabyStep[] {
152
209
  return steps;
153
210
  }
154
211
 
155
- function parseRisks(section: string): Risk[] {
212
+ export function parseRisks(section: string): Risk[] {
156
213
  if (!section) return [];
157
214
  const risks: Risk[] = [];
158
215
  const riskBlocks = section.split(/^###\s+/m).filter(Boolean);
@@ -173,7 +230,7 @@ function parseRisks(section: string): Risk[] {
173
230
  return risks;
174
231
  }
175
232
 
176
- function parseDiagrams(section: string): { name: string; type: string; content: string }[] {
233
+ export function parseDiagrams(section: string): { name: string; type: string; content: string }[] {
177
234
  if (!section) return [];
178
235
  const diagrams: { name: string; type: string; content: string }[] = [];
179
236
  const diagramRegex = /###\s+(.+)\n[\s\S]*?```mermaid\n([\s\S]*?)```/g;
@@ -192,7 +249,7 @@ function parseDiagrams(section: string): { name: string; type: string; content:
192
249
  return diagrams;
193
250
  }
194
251
 
195
- function parseDecisionsTable(section: string): ArchitecturalDecision[] {
252
+ export function parseDecisionsTable(section: string): ArchitecturalDecision[] {
196
253
  if (!section) return [];
197
254
  const decisions: ArchitecturalDecision[] = [];
198
255
  const lines = section.split("\n");
package/commands/clear.ts CHANGED
@@ -71,7 +71,6 @@ export function clearTasks(options: ClearOptions = {}): void {
71
71
  // Ordem de deleção respeita foreign keys
72
72
  // v8.0: Incluindo novas tabelas
73
73
  const tablesToClear = [
74
- "session_summaries",
75
74
  "reasoning_log",
76
75
  "knowledge_graph",
77
76
  "knowledge",