@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.
- package/commands/architect.test.ts +531 -0
- package/commands/architect.ts +68 -11
- package/commands/clear.ts +0 -1
- package/commands/decide.ts +28 -28
- package/commands/discover.ts +128 -3
- package/commands/knowledge.ts +2 -27
- package/commands/patterns.test.ts +169 -0
- package/commands/plan.test.ts +73 -0
- package/commands/plan.ts +4 -2
- package/commands/sync.ts +90 -0
- package/commands/task.ts +43 -159
- package/commands/utils.ts +251 -249
- package/db/schema.test.ts +333 -0
- package/db/schema.ts +160 -130
- package/gates/validator.test.ts +617 -0
- package/gates/validator.ts +42 -10
- package/package.json +3 -1
- package/protocol/process-return.ts +25 -93
- package/protocol/subagent-protocol.test.ts +936 -0
- package/protocol/subagent-protocol.ts +19 -1
- package/workflow.ts +85 -27
|
@@ -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
|
+
});
|
package/commands/architect.ts
CHANGED
|
@@ -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
|
-
|
|
110
|
-
|
|
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