@mainahq/core 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +31 -0
  2. package/package.json +37 -0
  3. package/src/ai/__tests__/ai.test.ts +207 -0
  4. package/src/ai/__tests__/design-approaches.test.ts +192 -0
  5. package/src/ai/__tests__/spec-questions.test.ts +191 -0
  6. package/src/ai/__tests__/tiers.test.ts +110 -0
  7. package/src/ai/commit-msg.ts +28 -0
  8. package/src/ai/design-approaches.ts +76 -0
  9. package/src/ai/index.ts +205 -0
  10. package/src/ai/pr-summary.ts +60 -0
  11. package/src/ai/spec-questions.ts +74 -0
  12. package/src/ai/tiers.ts +52 -0
  13. package/src/ai/try-generate.ts +89 -0
  14. package/src/ai/validate.ts +66 -0
  15. package/src/benchmark/__tests__/reporter.test.ts +525 -0
  16. package/src/benchmark/__tests__/runner.test.ts +113 -0
  17. package/src/benchmark/__tests__/story-loader.test.ts +152 -0
  18. package/src/benchmark/reporter.ts +332 -0
  19. package/src/benchmark/runner.ts +91 -0
  20. package/src/benchmark/story-loader.ts +88 -0
  21. package/src/benchmark/types.ts +95 -0
  22. package/src/cache/__tests__/keys.test.ts +97 -0
  23. package/src/cache/__tests__/manager.test.ts +312 -0
  24. package/src/cache/__tests__/ttl.test.ts +94 -0
  25. package/src/cache/keys.ts +44 -0
  26. package/src/cache/manager.ts +231 -0
  27. package/src/cache/ttl.ts +77 -0
  28. package/src/config/__tests__/config.test.ts +376 -0
  29. package/src/config/index.ts +198 -0
  30. package/src/context/__tests__/budget.test.ts +179 -0
  31. package/src/context/__tests__/engine.test.ts +163 -0
  32. package/src/context/__tests__/episodic.test.ts +291 -0
  33. package/src/context/__tests__/relevance.test.ts +323 -0
  34. package/src/context/__tests__/retrieval.test.ts +143 -0
  35. package/src/context/__tests__/selector.test.ts +174 -0
  36. package/src/context/__tests__/semantic.test.ts +252 -0
  37. package/src/context/__tests__/treesitter.test.ts +229 -0
  38. package/src/context/__tests__/working.test.ts +236 -0
  39. package/src/context/budget.ts +130 -0
  40. package/src/context/engine.ts +394 -0
  41. package/src/context/episodic.ts +251 -0
  42. package/src/context/relevance.ts +325 -0
  43. package/src/context/retrieval.ts +325 -0
  44. package/src/context/selector.ts +93 -0
  45. package/src/context/semantic.ts +331 -0
  46. package/src/context/treesitter.ts +216 -0
  47. package/src/context/working.ts +192 -0
  48. package/src/db/__tests__/db.test.ts +151 -0
  49. package/src/db/index.ts +211 -0
  50. package/src/db/schema.ts +84 -0
  51. package/src/design/__tests__/design.test.ts +310 -0
  52. package/src/design/__tests__/generate-hld-lld.test.ts +109 -0
  53. package/src/design/__tests__/review.test.ts +561 -0
  54. package/src/design/index.ts +297 -0
  55. package/src/design/review.ts +327 -0
  56. package/src/explain/__tests__/explain.test.ts +173 -0
  57. package/src/explain/index.ts +181 -0
  58. package/src/features/__tests__/analyzer.test.ts +358 -0
  59. package/src/features/__tests__/checklist.test.ts +454 -0
  60. package/src/features/__tests__/numbering.test.ts +319 -0
  61. package/src/features/__tests__/quality.test.ts +295 -0
  62. package/src/features/__tests__/traceability.test.ts +147 -0
  63. package/src/features/analyzer.ts +445 -0
  64. package/src/features/checklist.ts +366 -0
  65. package/src/features/index.ts +18 -0
  66. package/src/features/numbering.ts +404 -0
  67. package/src/features/quality.ts +349 -0
  68. package/src/features/test-stubs.ts +157 -0
  69. package/src/features/traceability.ts +260 -0
  70. package/src/feedback/__tests__/async-feedback.test.ts +52 -0
  71. package/src/feedback/__tests__/collector.test.ts +219 -0
  72. package/src/feedback/__tests__/compress.test.ts +150 -0
  73. package/src/feedback/__tests__/preferences.test.ts +169 -0
  74. package/src/feedback/collector.ts +135 -0
  75. package/src/feedback/compress.ts +92 -0
  76. package/src/feedback/preferences.ts +108 -0
  77. package/src/git/__tests__/git.test.ts +62 -0
  78. package/src/git/index.ts +110 -0
  79. package/src/hooks/__tests__/runner.test.ts +266 -0
  80. package/src/hooks/index.ts +8 -0
  81. package/src/hooks/runner.ts +130 -0
  82. package/src/index.ts +356 -0
  83. package/src/init/__tests__/init.test.ts +228 -0
  84. package/src/init/index.ts +364 -0
  85. package/src/language/__tests__/detect.test.ts +77 -0
  86. package/src/language/__tests__/profile.test.ts +51 -0
  87. package/src/language/detect.ts +70 -0
  88. package/src/language/profile.ts +110 -0
  89. package/src/prompts/__tests__/defaults.test.ts +52 -0
  90. package/src/prompts/__tests__/engine.test.ts +183 -0
  91. package/src/prompts/__tests__/evolution-resolve.test.ts +169 -0
  92. package/src/prompts/__tests__/evolution.test.ts +187 -0
  93. package/src/prompts/__tests__/loader.test.ts +105 -0
  94. package/src/prompts/candidates/review-v2.md +55 -0
  95. package/src/prompts/defaults/ai-review.md +49 -0
  96. package/src/prompts/defaults/commit.md +30 -0
  97. package/src/prompts/defaults/context.md +26 -0
  98. package/src/prompts/defaults/design-approaches.md +57 -0
  99. package/src/prompts/defaults/design-hld-lld.md +55 -0
  100. package/src/prompts/defaults/design.md +53 -0
  101. package/src/prompts/defaults/explain.md +31 -0
  102. package/src/prompts/defaults/fix.md +32 -0
  103. package/src/prompts/defaults/index.ts +38 -0
  104. package/src/prompts/defaults/review.md +41 -0
  105. package/src/prompts/defaults/spec-questions.md +59 -0
  106. package/src/prompts/defaults/tests.md +72 -0
  107. package/src/prompts/engine.ts +137 -0
  108. package/src/prompts/evolution.ts +409 -0
  109. package/src/prompts/loader.ts +71 -0
  110. package/src/review/__tests__/review.test.ts +288 -0
  111. package/src/review/comprehensive.ts +362 -0
  112. package/src/review/index.ts +417 -0
  113. package/src/stats/__tests__/tracker.test.ts +323 -0
  114. package/src/stats/index.ts +11 -0
  115. package/src/stats/tracker.ts +492 -0
  116. package/src/ticket/__tests__/ticket.test.ts +273 -0
  117. package/src/ticket/index.ts +185 -0
  118. package/src/utils.ts +87 -0
  119. package/src/verify/__tests__/ai-review.test.ts +242 -0
  120. package/src/verify/__tests__/coverage.test.ts +83 -0
  121. package/src/verify/__tests__/detect.test.ts +175 -0
  122. package/src/verify/__tests__/diff-filter.test.ts +338 -0
  123. package/src/verify/__tests__/fix.test.ts +478 -0
  124. package/src/verify/__tests__/linters/clippy.test.ts +45 -0
  125. package/src/verify/__tests__/linters/go-vet.test.ts +27 -0
  126. package/src/verify/__tests__/linters/ruff.test.ts +64 -0
  127. package/src/verify/__tests__/mutation.test.ts +141 -0
  128. package/src/verify/__tests__/pipeline.test.ts +553 -0
  129. package/src/verify/__tests__/proof.test.ts +97 -0
  130. package/src/verify/__tests__/secretlint.test.ts +190 -0
  131. package/src/verify/__tests__/semgrep.test.ts +217 -0
  132. package/src/verify/__tests__/slop.test.ts +366 -0
  133. package/src/verify/__tests__/sonar.test.ts +113 -0
  134. package/src/verify/__tests__/syntax-guard.test.ts +227 -0
  135. package/src/verify/__tests__/trivy.test.ts +191 -0
  136. package/src/verify/__tests__/visual.test.ts +139 -0
  137. package/src/verify/ai-review.ts +276 -0
  138. package/src/verify/coverage.ts +134 -0
  139. package/src/verify/detect.ts +171 -0
  140. package/src/verify/diff-filter.ts +183 -0
  141. package/src/verify/fix.ts +317 -0
  142. package/src/verify/linters/clippy.ts +52 -0
  143. package/src/verify/linters/go-vet.ts +32 -0
  144. package/src/verify/linters/ruff.ts +47 -0
  145. package/src/verify/mutation.ts +143 -0
  146. package/src/verify/pipeline.ts +328 -0
  147. package/src/verify/proof.ts +277 -0
  148. package/src/verify/secretlint.ts +168 -0
  149. package/src/verify/semgrep.ts +170 -0
  150. package/src/verify/slop.ts +493 -0
  151. package/src/verify/sonar.ts +146 -0
  152. package/src/verify/syntax-guard.ts +251 -0
  153. package/src/verify/trivy.ts +161 -0
  154. package/src/verify/visual.ts +460 -0
  155. package/src/workflow/__tests__/context.test.ts +110 -0
  156. package/src/workflow/context.ts +81 -0
@@ -0,0 +1,561 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdirSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import type { ReviewContext } from "../review";
6
+ import { buildReviewContext, reviewDesign } from "../review";
7
+
8
+ function makeTmpDir(): string {
9
+ const dir = join(
10
+ tmpdir(),
11
+ `maina-review-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
12
+ );
13
+ mkdirSync(dir, { recursive: true });
14
+ return dir;
15
+ }
16
+
17
+ // ── Sample ADRs ─────────────────────────────────────────────────────────────
18
+
19
+ const COMPLETE_ADR = `# 0001. Use Bun Runtime
20
+
21
+ Date: 2026-01-01
22
+
23
+ ## Status
24
+
25
+ Accepted
26
+
27
+ ## Context
28
+
29
+ We need a fast JavaScript runtime for our CLI tool.
30
+
31
+ ## Decision
32
+
33
+ We will use Bun as our JavaScript runtime.
34
+
35
+ ## Consequences
36
+
37
+ ### Positive
38
+
39
+ - Fast startup time
40
+ - Built-in test runner
41
+
42
+ ### Negative
43
+
44
+ - Smaller ecosystem than Node.js
45
+ `;
46
+
47
+ const MISSING_STATUS_ADR = `# 0002. Use Biome
48
+
49
+ Date: 2026-01-02
50
+
51
+ ## Context
52
+
53
+ We need a linter and formatter.
54
+
55
+ ## Decision
56
+
57
+ We will use Biome.
58
+
59
+ ## Consequences
60
+
61
+ ### Positive
62
+
63
+ - Fast
64
+ `;
65
+
66
+ const MISSING_DECISION_ADR = `# 0003. Choose Database
67
+
68
+ Date: 2026-01-03
69
+
70
+ ## Status
71
+
72
+ Proposed
73
+
74
+ ## Context
75
+
76
+ We need a database for caching.
77
+
78
+ ## Consequences
79
+
80
+ ### Positive
81
+
82
+ - Persistence
83
+ `;
84
+
85
+ const ADR_WITH_CLARIFICATION = `# 0004. API Design
86
+
87
+ Date: 2026-01-04
88
+
89
+ ## Status
90
+
91
+ Proposed
92
+
93
+ ## Context
94
+
95
+ We need to design the API surface.
96
+
97
+ [NEEDS CLARIFICATION] What protocols to support?
98
+
99
+ ## Decision
100
+
101
+ Use REST for now.
102
+
103
+ ## Consequences
104
+
105
+ ### Positive
106
+
107
+ - Simple to implement
108
+ `;
109
+
110
+ const CONSTITUTION = `# Constitution
111
+
112
+ ## Core Principles
113
+
114
+ - Always use TypeScript strict mode
115
+ - TDD always
116
+ - Never throw, use Result<T, E>
117
+ `;
118
+
119
+ // ── buildReviewContext ───────────────────────────────────────────────────────
120
+
121
+ describe("buildReviewContext", () => {
122
+ let tmpDir: string;
123
+ let adrDir: string;
124
+ let mainaDir: string;
125
+
126
+ beforeEach(() => {
127
+ tmpDir = makeTmpDir();
128
+ adrDir = join(tmpDir, "adr");
129
+ mainaDir = join(tmpDir, ".maina");
130
+ mkdirSync(adrDir, { recursive: true });
131
+ });
132
+
133
+ afterEach(() => {
134
+ rmSync(tmpDir, { recursive: true, force: true });
135
+ });
136
+
137
+ test("reads target ADR correctly", async () => {
138
+ const adrPath = join(adrDir, "0001-use-bun-runtime.md");
139
+ await Bun.write(adrPath, COMPLETE_ADR);
140
+
141
+ const result = await buildReviewContext(adrPath, adrDir, mainaDir);
142
+
143
+ expect(result.ok).toBe(true);
144
+ if (result.ok) {
145
+ expect(result.value.targetAdr.path).toBe(adrPath);
146
+ expect(result.value.targetAdr.content).toBe(COMPLETE_ADR);
147
+ expect(result.value.targetAdr.title).toBe("Use Bun Runtime");
148
+ }
149
+ });
150
+
151
+ test("includes existing ADRs (excluding target)", async () => {
152
+ const targetPath = join(adrDir, "0001-use-bun-runtime.md");
153
+ await Bun.write(targetPath, COMPLETE_ADR);
154
+ await Bun.write(join(adrDir, "0002-use-biome.md"), MISSING_STATUS_ADR);
155
+ await Bun.write(
156
+ join(adrDir, "0003-choose-database.md"),
157
+ MISSING_DECISION_ADR,
158
+ );
159
+
160
+ const result = await buildReviewContext(targetPath, adrDir, mainaDir);
161
+
162
+ expect(result.ok).toBe(true);
163
+ if (result.ok) {
164
+ // Should not include the target ADR in existing list
165
+ expect(result.value.existingAdrs.length).toBe(2);
166
+ const paths = result.value.existingAdrs.map((a) => a.path);
167
+ expect(paths).not.toContain(targetPath);
168
+ }
169
+ });
170
+
171
+ test("includes constitution when present", async () => {
172
+ const adrPath = join(adrDir, "0001-use-bun-runtime.md");
173
+ await Bun.write(adrPath, COMPLETE_ADR);
174
+ mkdirSync(mainaDir, { recursive: true });
175
+ await Bun.write(join(mainaDir, "constitution.md"), CONSTITUTION);
176
+
177
+ const result = await buildReviewContext(adrPath, adrDir, mainaDir);
178
+
179
+ expect(result.ok).toBe(true);
180
+ if (result.ok) {
181
+ expect(result.value.constitution).toBe(CONSTITUTION);
182
+ }
183
+ });
184
+
185
+ test("handles missing constitution gracefully", async () => {
186
+ const adrPath = join(adrDir, "0001-use-bun-runtime.md");
187
+ await Bun.write(adrPath, COMPLETE_ADR);
188
+
189
+ const result = await buildReviewContext(adrPath, adrDir, mainaDir);
190
+
191
+ expect(result.ok).toBe(true);
192
+ if (result.ok) {
193
+ expect(result.value.constitution).toBeNull();
194
+ }
195
+ });
196
+
197
+ test("returns error for missing target ADR", async () => {
198
+ const adrPath = join(adrDir, "0099-nonexistent.md");
199
+
200
+ const result = await buildReviewContext(adrPath, adrDir, mainaDir);
201
+
202
+ expect(result.ok).toBe(false);
203
+ if (!result.ok) {
204
+ expect(result.error).toContain("not found");
205
+ }
206
+ });
207
+ });
208
+
209
+ // ── reviewDesign ────────────────────────────────────────────────────────────
210
+
211
+ describe("reviewDesign", () => {
212
+ test("complete ADR has no errors", () => {
213
+ const context = {
214
+ targetAdr: {
215
+ path: "adr/0001-use-bun-runtime.md",
216
+ content: COMPLETE_ADR,
217
+ title: "Use Bun Runtime",
218
+ },
219
+ existingAdrs: [],
220
+ constitution: null,
221
+ };
222
+
223
+ const result = reviewDesign(context);
224
+
225
+ expect(result.ok).toBe(true);
226
+ if (result.ok) {
227
+ const errors = result.value.findings.filter(
228
+ (f) => f.severity === "error",
229
+ );
230
+ expect(errors.length).toBe(0);
231
+ expect(result.value.passed).toBe(true);
232
+ }
233
+ });
234
+
235
+ test("missing ## Status section produces error", () => {
236
+ const context = {
237
+ targetAdr: {
238
+ path: "adr/0002-use-biome.md",
239
+ content: MISSING_STATUS_ADR,
240
+ title: "Use Biome",
241
+ },
242
+ existingAdrs: [],
243
+ constitution: null,
244
+ };
245
+
246
+ const result = reviewDesign(context);
247
+
248
+ expect(result.ok).toBe(true);
249
+ if (result.ok) {
250
+ const errors = result.value.findings.filter(
251
+ (f) => f.severity === "error",
252
+ );
253
+ expect(errors.length).toBeGreaterThan(0);
254
+ const statusError = errors.find((f) => f.section === "Status");
255
+ expect(statusError).toBeDefined();
256
+ expect(result.value.passed).toBe(false);
257
+ }
258
+ });
259
+
260
+ test("missing ## Decision section produces error", () => {
261
+ const context = {
262
+ targetAdr: {
263
+ path: "adr/0003-choose-database.md",
264
+ content: MISSING_DECISION_ADR,
265
+ title: "Choose Database",
266
+ },
267
+ existingAdrs: [],
268
+ constitution: null,
269
+ };
270
+
271
+ const result = reviewDesign(context);
272
+
273
+ expect(result.ok).toBe(true);
274
+ if (result.ok) {
275
+ const errors = result.value.findings.filter(
276
+ (f) => f.severity === "error",
277
+ );
278
+ const decisionError = errors.find((f) => f.section === "Decision");
279
+ expect(decisionError).toBeDefined();
280
+ expect(result.value.passed).toBe(false);
281
+ }
282
+ });
283
+
284
+ test("many [NEEDS CLARIFICATION] markers produce error (empty ADR)", () => {
285
+ const emptyAdr = `# 0005. Empty ADR
286
+
287
+ ## Status
288
+ Proposed
289
+
290
+ ## Context
291
+ [NEEDS CLARIFICATION]
292
+
293
+ ## Decision
294
+ [NEEDS CLARIFICATION]
295
+
296
+ ## Consequences
297
+ [NEEDS CLARIFICATION]
298
+ [NEEDS CLARIFICATION]
299
+ [NEEDS CLARIFICATION]
300
+ [NEEDS CLARIFICATION]
301
+ `;
302
+ const context: ReviewContext = {
303
+ targetAdr: {
304
+ path: "adr/0005-empty.md",
305
+ content: emptyAdr,
306
+ title: "Empty ADR",
307
+ },
308
+ existingAdrs: [],
309
+ constitution: null,
310
+ };
311
+
312
+ const result = reviewDesign(context);
313
+ expect(result.ok).toBe(true);
314
+ if (result.ok) {
315
+ const errors = result.value.findings.filter(
316
+ (f) => f.severity === "error",
317
+ );
318
+ const clarificationError = errors.find((f) =>
319
+ f.message.includes("effectively empty"),
320
+ );
321
+ expect(clarificationError).toBeDefined();
322
+ expect(result.value.passed).toBe(false);
323
+ }
324
+ });
325
+
326
+ test("contains [NEEDS CLARIFICATION] produces warning", () => {
327
+ const context = {
328
+ targetAdr: {
329
+ path: "adr/0004-api-design.md",
330
+ content: ADR_WITH_CLARIFICATION,
331
+ title: "API Design",
332
+ },
333
+ existingAdrs: [],
334
+ constitution: null,
335
+ };
336
+
337
+ const result = reviewDesign(context);
338
+
339
+ expect(result.ok).toBe(true);
340
+ if (result.ok) {
341
+ const warnings = result.value.findings.filter(
342
+ (f) => f.severity === "warning",
343
+ );
344
+ expect(warnings.length).toBeGreaterThan(0);
345
+ const clarificationWarning = warnings.find((f) =>
346
+ f.message.includes("NEEDS CLARIFICATION"),
347
+ );
348
+ expect(clarificationWarning).toBeDefined();
349
+ }
350
+ });
351
+
352
+ test("all MADR sections present lists them in sectionsPresent", () => {
353
+ const context = {
354
+ targetAdr: {
355
+ path: "adr/0001-use-bun-runtime.md",
356
+ content: COMPLETE_ADR,
357
+ title: "Use Bun Runtime",
358
+ },
359
+ existingAdrs: [],
360
+ constitution: null,
361
+ };
362
+
363
+ const result = reviewDesign(context);
364
+
365
+ expect(result.ok).toBe(true);
366
+ if (result.ok) {
367
+ expect(result.value.sectionsPresent).toContain("Status");
368
+ expect(result.value.sectionsPresent).toContain("Context");
369
+ expect(result.value.sectionsPresent).toContain("Decision");
370
+ expect(result.value.sectionsPresent).toContain("Consequences");
371
+ expect(result.value.sectionsMissing.length).toBe(0);
372
+ }
373
+ });
374
+
375
+ test("passed is true when no errors", () => {
376
+ const context = {
377
+ targetAdr: {
378
+ path: "adr/0001-use-bun-runtime.md",
379
+ content: COMPLETE_ADR,
380
+ title: "Use Bun Runtime",
381
+ },
382
+ existingAdrs: [],
383
+ constitution: null,
384
+ };
385
+
386
+ const result = reviewDesign(context);
387
+
388
+ expect(result.ok).toBe(true);
389
+ if (result.ok) {
390
+ expect(result.value.passed).toBe(true);
391
+ }
392
+ });
393
+
394
+ test("sectionsMissing lists missing sections", () => {
395
+ const context = {
396
+ targetAdr: {
397
+ path: "adr/0002-use-biome.md",
398
+ content: MISSING_STATUS_ADR,
399
+ title: "Use Biome",
400
+ },
401
+ existingAdrs: [],
402
+ constitution: null,
403
+ };
404
+
405
+ const result = reviewDesign(context);
406
+
407
+ expect(result.ok).toBe(true);
408
+ if (result.ok) {
409
+ expect(result.value.sectionsMissing).toContain("Status");
410
+ expect(result.value.sectionsPresent).toContain("Context");
411
+ expect(result.value.sectionsPresent).toContain("Decision");
412
+ expect(result.value.sectionsPresent).toContain("Consequences");
413
+ }
414
+ });
415
+
416
+ test("adrPath is set in result", () => {
417
+ const context = {
418
+ targetAdr: {
419
+ path: "adr/0001-use-bun-runtime.md",
420
+ content: COMPLETE_ADR,
421
+ title: "Use Bun Runtime",
422
+ },
423
+ existingAdrs: [],
424
+ constitution: null,
425
+ };
426
+
427
+ const result = reviewDesign(context);
428
+
429
+ expect(result.ok).toBe(true);
430
+ if (result.ok) {
431
+ expect(result.value.adrPath).toBe("adr/0001-use-bun-runtime.md");
432
+ }
433
+ });
434
+ });
435
+
436
+ // ── reviewDesign HLD/LLD validation ─────────────────────────────────────────
437
+
438
+ describe("reviewDesign HLD/LLD validation", () => {
439
+ function makeContext(content: string): ReviewContext {
440
+ return {
441
+ targetAdr: { path: "/test/0001-test.md", content, title: "Test" },
442
+ existingAdrs: [],
443
+ constitution: null,
444
+ };
445
+ }
446
+
447
+ test("should warn when HLD sections are missing", () => {
448
+ const content = `# 0001. Test
449
+
450
+ ## Status
451
+ Proposed
452
+
453
+ ## Context
454
+ Some context.
455
+
456
+ ## Decision
457
+ Some decision.
458
+
459
+ ## Consequences
460
+ Some consequences.
461
+ `;
462
+
463
+ const result = reviewDesign(makeContext(content));
464
+ expect(result.ok).toBe(true);
465
+ if (!result.ok) return;
466
+
467
+ const hldWarning = result.value.findings.find((f) =>
468
+ f.message.includes("High-Level Design"),
469
+ );
470
+ expect(hldWarning).toBeDefined();
471
+ expect(hldWarning?.severity).toBe("warning");
472
+ });
473
+
474
+ test("should warn when LLD sections are missing", () => {
475
+ const content = `# 0001. Test
476
+
477
+ ## Status
478
+ Proposed
479
+
480
+ ## Context
481
+ Some context.
482
+
483
+ ## Decision
484
+ Some decision.
485
+
486
+ ## Consequences
487
+ Some consequences.
488
+
489
+ ## High-Level Design
490
+ ### System Overview
491
+ Overview here.
492
+ ### Component Boundaries
493
+ Components here.
494
+ ### Data Flow
495
+ Flow here.
496
+ ### External Dependencies
497
+ None.
498
+ `;
499
+
500
+ const result = reviewDesign(makeContext(content));
501
+ expect(result.ok).toBe(true);
502
+ if (!result.ok) return;
503
+
504
+ const lldWarning = result.value.findings.find((f) =>
505
+ f.message.includes("Low-Level Design"),
506
+ );
507
+ expect(lldWarning).toBeDefined();
508
+ });
509
+
510
+ test("should not warn when all HLD/LLD sections are present", () => {
511
+ const content = `# 0001. Test
512
+
513
+ ## Status
514
+ Proposed
515
+
516
+ ## Context
517
+ Some context.
518
+
519
+ ## Decision
520
+ Some decision.
521
+
522
+ ## Consequences
523
+ Some consequences.
524
+
525
+ ## High-Level Design
526
+ ### System Overview
527
+ Overview.
528
+ ### Component Boundaries
529
+ Components.
530
+ ### Data Flow
531
+ Flow.
532
+ ### External Dependencies
533
+ None.
534
+
535
+ ## Low-Level Design
536
+ ### Interfaces & Types
537
+ Types.
538
+ ### Function Signatures
539
+ Signatures.
540
+ ### DB Schema Changes
541
+ None.
542
+ ### Sequence of Operations
543
+ Steps.
544
+ ### Error Handling
545
+ Errors.
546
+ ### Edge Cases
547
+ Edges.
548
+ `;
549
+
550
+ const result = reviewDesign(makeContext(content));
551
+ expect(result.ok).toBe(true);
552
+ if (!result.ok) return;
553
+
554
+ const designWarnings = result.value.findings.filter(
555
+ (f) =>
556
+ f.message.includes("High-Level Design") ||
557
+ f.message.includes("Low-Level Design"),
558
+ );
559
+ expect(designWarnings).toHaveLength(0);
560
+ });
561
+ });