@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.
- package/README.md +31 -0
- package/package.json +37 -0
- package/src/ai/__tests__/ai.test.ts +207 -0
- package/src/ai/__tests__/design-approaches.test.ts +192 -0
- package/src/ai/__tests__/spec-questions.test.ts +191 -0
- package/src/ai/__tests__/tiers.test.ts +110 -0
- package/src/ai/commit-msg.ts +28 -0
- package/src/ai/design-approaches.ts +76 -0
- package/src/ai/index.ts +205 -0
- package/src/ai/pr-summary.ts +60 -0
- package/src/ai/spec-questions.ts +74 -0
- package/src/ai/tiers.ts +52 -0
- package/src/ai/try-generate.ts +89 -0
- package/src/ai/validate.ts +66 -0
- package/src/benchmark/__tests__/reporter.test.ts +525 -0
- package/src/benchmark/__tests__/runner.test.ts +113 -0
- package/src/benchmark/__tests__/story-loader.test.ts +152 -0
- package/src/benchmark/reporter.ts +332 -0
- package/src/benchmark/runner.ts +91 -0
- package/src/benchmark/story-loader.ts +88 -0
- package/src/benchmark/types.ts +95 -0
- package/src/cache/__tests__/keys.test.ts +97 -0
- package/src/cache/__tests__/manager.test.ts +312 -0
- package/src/cache/__tests__/ttl.test.ts +94 -0
- package/src/cache/keys.ts +44 -0
- package/src/cache/manager.ts +231 -0
- package/src/cache/ttl.ts +77 -0
- package/src/config/__tests__/config.test.ts +376 -0
- package/src/config/index.ts +198 -0
- package/src/context/__tests__/budget.test.ts +179 -0
- package/src/context/__tests__/engine.test.ts +163 -0
- package/src/context/__tests__/episodic.test.ts +291 -0
- package/src/context/__tests__/relevance.test.ts +323 -0
- package/src/context/__tests__/retrieval.test.ts +143 -0
- package/src/context/__tests__/selector.test.ts +174 -0
- package/src/context/__tests__/semantic.test.ts +252 -0
- package/src/context/__tests__/treesitter.test.ts +229 -0
- package/src/context/__tests__/working.test.ts +236 -0
- package/src/context/budget.ts +130 -0
- package/src/context/engine.ts +394 -0
- package/src/context/episodic.ts +251 -0
- package/src/context/relevance.ts +325 -0
- package/src/context/retrieval.ts +325 -0
- package/src/context/selector.ts +93 -0
- package/src/context/semantic.ts +331 -0
- package/src/context/treesitter.ts +216 -0
- package/src/context/working.ts +192 -0
- package/src/db/__tests__/db.test.ts +151 -0
- package/src/db/index.ts +211 -0
- package/src/db/schema.ts +84 -0
- package/src/design/__tests__/design.test.ts +310 -0
- package/src/design/__tests__/generate-hld-lld.test.ts +109 -0
- package/src/design/__tests__/review.test.ts +561 -0
- package/src/design/index.ts +297 -0
- package/src/design/review.ts +327 -0
- package/src/explain/__tests__/explain.test.ts +173 -0
- package/src/explain/index.ts +181 -0
- package/src/features/__tests__/analyzer.test.ts +358 -0
- package/src/features/__tests__/checklist.test.ts +454 -0
- package/src/features/__tests__/numbering.test.ts +319 -0
- package/src/features/__tests__/quality.test.ts +295 -0
- package/src/features/__tests__/traceability.test.ts +147 -0
- package/src/features/analyzer.ts +445 -0
- package/src/features/checklist.ts +366 -0
- package/src/features/index.ts +18 -0
- package/src/features/numbering.ts +404 -0
- package/src/features/quality.ts +349 -0
- package/src/features/test-stubs.ts +157 -0
- package/src/features/traceability.ts +260 -0
- package/src/feedback/__tests__/async-feedback.test.ts +52 -0
- package/src/feedback/__tests__/collector.test.ts +219 -0
- package/src/feedback/__tests__/compress.test.ts +150 -0
- package/src/feedback/__tests__/preferences.test.ts +169 -0
- package/src/feedback/collector.ts +135 -0
- package/src/feedback/compress.ts +92 -0
- package/src/feedback/preferences.ts +108 -0
- package/src/git/__tests__/git.test.ts +62 -0
- package/src/git/index.ts +110 -0
- package/src/hooks/__tests__/runner.test.ts +266 -0
- package/src/hooks/index.ts +8 -0
- package/src/hooks/runner.ts +130 -0
- package/src/index.ts +356 -0
- package/src/init/__tests__/init.test.ts +228 -0
- package/src/init/index.ts +364 -0
- package/src/language/__tests__/detect.test.ts +77 -0
- package/src/language/__tests__/profile.test.ts +51 -0
- package/src/language/detect.ts +70 -0
- package/src/language/profile.ts +110 -0
- package/src/prompts/__tests__/defaults.test.ts +52 -0
- package/src/prompts/__tests__/engine.test.ts +183 -0
- package/src/prompts/__tests__/evolution-resolve.test.ts +169 -0
- package/src/prompts/__tests__/evolution.test.ts +187 -0
- package/src/prompts/__tests__/loader.test.ts +105 -0
- package/src/prompts/candidates/review-v2.md +55 -0
- package/src/prompts/defaults/ai-review.md +49 -0
- package/src/prompts/defaults/commit.md +30 -0
- package/src/prompts/defaults/context.md +26 -0
- package/src/prompts/defaults/design-approaches.md +57 -0
- package/src/prompts/defaults/design-hld-lld.md +55 -0
- package/src/prompts/defaults/design.md +53 -0
- package/src/prompts/defaults/explain.md +31 -0
- package/src/prompts/defaults/fix.md +32 -0
- package/src/prompts/defaults/index.ts +38 -0
- package/src/prompts/defaults/review.md +41 -0
- package/src/prompts/defaults/spec-questions.md +59 -0
- package/src/prompts/defaults/tests.md +72 -0
- package/src/prompts/engine.ts +137 -0
- package/src/prompts/evolution.ts +409 -0
- package/src/prompts/loader.ts +71 -0
- package/src/review/__tests__/review.test.ts +288 -0
- package/src/review/comprehensive.ts +362 -0
- package/src/review/index.ts +417 -0
- package/src/stats/__tests__/tracker.test.ts +323 -0
- package/src/stats/index.ts +11 -0
- package/src/stats/tracker.ts +492 -0
- package/src/ticket/__tests__/ticket.test.ts +273 -0
- package/src/ticket/index.ts +185 -0
- package/src/utils.ts +87 -0
- package/src/verify/__tests__/ai-review.test.ts +242 -0
- package/src/verify/__tests__/coverage.test.ts +83 -0
- package/src/verify/__tests__/detect.test.ts +175 -0
- package/src/verify/__tests__/diff-filter.test.ts +338 -0
- package/src/verify/__tests__/fix.test.ts +478 -0
- package/src/verify/__tests__/linters/clippy.test.ts +45 -0
- package/src/verify/__tests__/linters/go-vet.test.ts +27 -0
- package/src/verify/__tests__/linters/ruff.test.ts +64 -0
- package/src/verify/__tests__/mutation.test.ts +141 -0
- package/src/verify/__tests__/pipeline.test.ts +553 -0
- package/src/verify/__tests__/proof.test.ts +97 -0
- package/src/verify/__tests__/secretlint.test.ts +190 -0
- package/src/verify/__tests__/semgrep.test.ts +217 -0
- package/src/verify/__tests__/slop.test.ts +366 -0
- package/src/verify/__tests__/sonar.test.ts +113 -0
- package/src/verify/__tests__/syntax-guard.test.ts +227 -0
- package/src/verify/__tests__/trivy.test.ts +191 -0
- package/src/verify/__tests__/visual.test.ts +139 -0
- package/src/verify/ai-review.ts +276 -0
- package/src/verify/coverage.ts +134 -0
- package/src/verify/detect.ts +171 -0
- package/src/verify/diff-filter.ts +183 -0
- package/src/verify/fix.ts +317 -0
- package/src/verify/linters/clippy.ts +52 -0
- package/src/verify/linters/go-vet.ts +32 -0
- package/src/verify/linters/ruff.ts +47 -0
- package/src/verify/mutation.ts +143 -0
- package/src/verify/pipeline.ts +328 -0
- package/src/verify/proof.ts +277 -0
- package/src/verify/secretlint.ts +168 -0
- package/src/verify/semgrep.ts +170 -0
- package/src/verify/slop.ts +493 -0
- package/src/verify/sonar.ts +146 -0
- package/src/verify/syntax-guard.ts +251 -0
- package/src/verify/trivy.ts +161 -0
- package/src/verify/visual.ts +460 -0
- package/src/workflow/__tests__/context.test.ts +110 -0
- 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
|
+
});
|