@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,310 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, test } from "bun:test";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { getNextAdrNumber, listAdrs, scaffoldAdr } from "../index";
|
|
6
|
+
|
|
7
|
+
function makeTmpDir(): string {
|
|
8
|
+
const dir = join(
|
|
9
|
+
tmpdir(),
|
|
10
|
+
`maina-design-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
11
|
+
);
|
|
12
|
+
mkdirSync(dir, { recursive: true });
|
|
13
|
+
return dir;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("getNextAdrNumber", () => {
|
|
17
|
+
let tmpDir: string;
|
|
18
|
+
let adrDir: string;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
tmpDir = makeTmpDir();
|
|
22
|
+
adrDir = join(tmpDir, "adr");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("empty dir returns '0001'", async () => {
|
|
30
|
+
mkdirSync(adrDir, { recursive: true });
|
|
31
|
+
const result = await getNextAdrNumber(adrDir);
|
|
32
|
+
expect(result.ok).toBe(true);
|
|
33
|
+
if (result.ok) {
|
|
34
|
+
expect(result.value).toBe("0001");
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("existing 0001, 0002 returns '0003'", async () => {
|
|
39
|
+
mkdirSync(adrDir, { recursive: true });
|
|
40
|
+
await Bun.write(
|
|
41
|
+
join(adrDir, "0001-use-bun-runtime.md"),
|
|
42
|
+
"# 0001. Use Bun Runtime\n\nDate: 2026-01-01\n\n## Status\n\nAccepted\n",
|
|
43
|
+
);
|
|
44
|
+
await Bun.write(
|
|
45
|
+
join(adrDir, "0002-use-biome.md"),
|
|
46
|
+
"# 0002. Use Biome\n\nDate: 2026-01-02\n\n## Status\n\nProposed\n",
|
|
47
|
+
);
|
|
48
|
+
const result = await getNextAdrNumber(adrDir);
|
|
49
|
+
expect(result.ok).toBe(true);
|
|
50
|
+
if (result.ok) {
|
|
51
|
+
expect(result.value).toBe("0003");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("creates adr/ if missing", async () => {
|
|
56
|
+
expect(existsSync(adrDir)).toBe(false);
|
|
57
|
+
const result = await getNextAdrNumber(adrDir);
|
|
58
|
+
expect(result.ok).toBe(true);
|
|
59
|
+
if (result.ok) {
|
|
60
|
+
expect(result.value).toBe("0001");
|
|
61
|
+
}
|
|
62
|
+
expect(existsSync(adrDir)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("non-sequential (0001, 0003) returns '0004'", async () => {
|
|
66
|
+
mkdirSync(adrDir, { recursive: true });
|
|
67
|
+
await Bun.write(
|
|
68
|
+
join(adrDir, "0001-first.md"),
|
|
69
|
+
"# 0001. First\n\nDate: 2026-01-01\n\n## Status\n\nAccepted\n",
|
|
70
|
+
);
|
|
71
|
+
await Bun.write(
|
|
72
|
+
join(adrDir, "0003-third.md"),
|
|
73
|
+
"# 0003. Third\n\nDate: 2026-01-03\n\n## Status\n\nProposed\n",
|
|
74
|
+
);
|
|
75
|
+
const result = await getNextAdrNumber(adrDir);
|
|
76
|
+
expect(result.ok).toBe(true);
|
|
77
|
+
if (result.ok) {
|
|
78
|
+
expect(result.value).toBe("0004");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("ignores files without numeric prefix", async () => {
|
|
83
|
+
mkdirSync(adrDir, { recursive: true });
|
|
84
|
+
await Bun.write(join(adrDir, "README.md"), "# ADR Directory");
|
|
85
|
+
await Bun.write(
|
|
86
|
+
join(adrDir, "0001-first.md"),
|
|
87
|
+
"# 0001. First\n\nDate: 2026-01-01\n\n## Status\n\nAccepted\n",
|
|
88
|
+
);
|
|
89
|
+
const result = await getNextAdrNumber(adrDir);
|
|
90
|
+
expect(result.ok).toBe(true);
|
|
91
|
+
if (result.ok) {
|
|
92
|
+
expect(result.value).toBe("0002");
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("scaffoldAdr", () => {
|
|
98
|
+
let tmpDir: string;
|
|
99
|
+
let adrDir: string;
|
|
100
|
+
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
tmpDir = makeTmpDir();
|
|
103
|
+
adrDir = join(tmpDir, "adr");
|
|
104
|
+
mkdirSync(adrDir, { recursive: true });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
afterEach(() => {
|
|
108
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("creates file with MADR template", async () => {
|
|
112
|
+
const result = await scaffoldAdr(adrDir, "0001", "Use Bun Runtime");
|
|
113
|
+
expect(result.ok).toBe(true);
|
|
114
|
+
if (result.ok) {
|
|
115
|
+
expect(existsSync(result.value)).toBe(true);
|
|
116
|
+
expect(result.value).toContain("0001-use-bun-runtime.md");
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("file contains title, date, status sections", async () => {
|
|
121
|
+
const result = await scaffoldAdr(adrDir, "0001", "Use Bun Runtime");
|
|
122
|
+
expect(result.ok).toBe(true);
|
|
123
|
+
if (result.ok) {
|
|
124
|
+
const content = readFileSync(result.value, "utf-8");
|
|
125
|
+
expect(content).toContain("# 0001. Use Bun Runtime");
|
|
126
|
+
expect(content).toContain("Date:");
|
|
127
|
+
expect(content).toContain("## Status");
|
|
128
|
+
expect(content).toContain("Proposed");
|
|
129
|
+
expect(content).toContain("## Context");
|
|
130
|
+
expect(content).toContain("## Decision");
|
|
131
|
+
expect(content).toContain("## Consequences");
|
|
132
|
+
expect(content).toContain("### Positive");
|
|
133
|
+
expect(content).toContain("### Negative");
|
|
134
|
+
expect(content).toContain("### Neutral");
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("file contains [NEEDS CLARIFICATION] markers", async () => {
|
|
139
|
+
const result = await scaffoldAdr(adrDir, "0001", "Use Bun Runtime");
|
|
140
|
+
expect(result.ok).toBe(true);
|
|
141
|
+
if (result.ok) {
|
|
142
|
+
const content = readFileSync(result.value, "utf-8");
|
|
143
|
+
expect(content).toContain("[NEEDS CLARIFICATION]");
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("returns the file path", async () => {
|
|
148
|
+
const result = await scaffoldAdr(
|
|
149
|
+
adrDir,
|
|
150
|
+
"0002",
|
|
151
|
+
"Choose Biome Over ESLint",
|
|
152
|
+
);
|
|
153
|
+
expect(result.ok).toBe(true);
|
|
154
|
+
if (result.ok) {
|
|
155
|
+
expect(result.value).toBe(
|
|
156
|
+
join(adrDir, "0002-choose-biome-over-eslint.md"),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("converts title to kebab-case for filename", async () => {
|
|
162
|
+
const result = await scaffoldAdr(adrDir, "0001", "My Cool Decision");
|
|
163
|
+
expect(result.ok).toBe(true);
|
|
164
|
+
if (result.ok) {
|
|
165
|
+
expect(result.value).toContain("0001-my-cool-decision.md");
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("date is today's date in YYYY-MM-DD format", async () => {
|
|
170
|
+
const result = await scaffoldAdr(adrDir, "0001", "Test Date");
|
|
171
|
+
expect(result.ok).toBe(true);
|
|
172
|
+
if (result.ok) {
|
|
173
|
+
const content = readFileSync(result.value, "utf-8");
|
|
174
|
+
const today = new Date().toISOString().split("T")[0];
|
|
175
|
+
expect(content).toContain(`Date: ${today}`);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("scaffoldAdr with HLD/LLD", () => {
|
|
181
|
+
const testDir = join(import.meta.dir, "__fixtures__/adr-hld");
|
|
182
|
+
|
|
183
|
+
it("should include HLD and LLD sections in scaffold", async () => {
|
|
184
|
+
if (existsSync(testDir)) rmSync(testDir, { recursive: true });
|
|
185
|
+
mkdirSync(testDir, { recursive: true });
|
|
186
|
+
|
|
187
|
+
const result = await scaffoldAdr(testDir, "0001", "Test Decision");
|
|
188
|
+
|
|
189
|
+
expect(result.ok).toBe(true);
|
|
190
|
+
if (!result.ok) return;
|
|
191
|
+
|
|
192
|
+
const content = readFileSync(result.value, "utf-8");
|
|
193
|
+
|
|
194
|
+
// HLD sections
|
|
195
|
+
expect(content).toContain("## High-Level Design");
|
|
196
|
+
expect(content).toContain("### System Overview");
|
|
197
|
+
expect(content).toContain("### Component Boundaries");
|
|
198
|
+
expect(content).toContain("### Data Flow");
|
|
199
|
+
expect(content).toContain("### External Dependencies");
|
|
200
|
+
|
|
201
|
+
// LLD sections
|
|
202
|
+
expect(content).toContain("## Low-Level Design");
|
|
203
|
+
expect(content).toContain("### Interfaces & Types");
|
|
204
|
+
expect(content).toContain("### Function Signatures");
|
|
205
|
+
expect(content).toContain("### DB Schema Changes");
|
|
206
|
+
expect(content).toContain("### Sequence of Operations");
|
|
207
|
+
expect(content).toContain("### Error Handling");
|
|
208
|
+
expect(content).toContain("### Edge Cases");
|
|
209
|
+
|
|
210
|
+
// Cleanup
|
|
211
|
+
rmSync(testDir, { recursive: true });
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("listAdrs", () => {
|
|
216
|
+
let tmpDir: string;
|
|
217
|
+
let adrDir: string;
|
|
218
|
+
|
|
219
|
+
beforeEach(() => {
|
|
220
|
+
tmpDir = makeTmpDir();
|
|
221
|
+
adrDir = join(tmpDir, "adr");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
afterEach(() => {
|
|
225
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("empty dir returns empty array", async () => {
|
|
229
|
+
mkdirSync(adrDir, { recursive: true });
|
|
230
|
+
const result = await listAdrs(adrDir);
|
|
231
|
+
expect(result.ok).toBe(true);
|
|
232
|
+
if (result.ok) {
|
|
233
|
+
expect(result.value).toEqual([]);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("returns summaries with number, title, status", async () => {
|
|
238
|
+
mkdirSync(adrDir, { recursive: true });
|
|
239
|
+
await Bun.write(
|
|
240
|
+
join(adrDir, "0001-use-bun-runtime.md"),
|
|
241
|
+
"# 0001. Use Bun Runtime\n\nDate: 2026-01-01\n\n## Status\n\nAccepted\n",
|
|
242
|
+
);
|
|
243
|
+
await Bun.write(
|
|
244
|
+
join(adrDir, "0002-use-biome.md"),
|
|
245
|
+
"# 0002. Use Biome\n\nDate: 2026-01-02\n\n## Status\n\nProposed\n",
|
|
246
|
+
);
|
|
247
|
+
const result = await listAdrs(adrDir);
|
|
248
|
+
expect(result.ok).toBe(true);
|
|
249
|
+
if (result.ok) {
|
|
250
|
+
expect(result.value.length).toBe(2);
|
|
251
|
+
|
|
252
|
+
const first = result.value.find((a) => a.number === "0001");
|
|
253
|
+
expect(first).toBeDefined();
|
|
254
|
+
expect(first?.title).toBe("Use Bun Runtime");
|
|
255
|
+
expect(first?.status).toBe("Accepted");
|
|
256
|
+
expect(first?.path).toContain("0001-use-bun-runtime.md");
|
|
257
|
+
|
|
258
|
+
const second = result.value.find((a) => a.number === "0002");
|
|
259
|
+
expect(second).toBeDefined();
|
|
260
|
+
expect(second?.title).toBe("Use Biome");
|
|
261
|
+
expect(second?.status).toBe("Proposed");
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("ignores non-ADR files", async () => {
|
|
266
|
+
mkdirSync(adrDir, { recursive: true });
|
|
267
|
+
await Bun.write(join(adrDir, "README.md"), "# ADR Directory");
|
|
268
|
+
await Bun.write(
|
|
269
|
+
join(adrDir, "0001-first.md"),
|
|
270
|
+
"# 0001. First\n\nDate: 2026-01-01\n\n## Status\n\nAccepted\n",
|
|
271
|
+
);
|
|
272
|
+
const result = await listAdrs(adrDir);
|
|
273
|
+
expect(result.ok).toBe(true);
|
|
274
|
+
if (result.ok) {
|
|
275
|
+
expect(result.value.length).toBe(1);
|
|
276
|
+
expect(result.value[0]?.number).toBe("0001");
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("returns error if adr/ does not exist", async () => {
|
|
281
|
+
const result = await listAdrs(join(tmpDir, "nonexistent"));
|
|
282
|
+
expect(result.ok).toBe(false);
|
|
283
|
+
if (!result.ok) {
|
|
284
|
+
expect(result.error).toBeDefined();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("results are sorted by number", async () => {
|
|
289
|
+
mkdirSync(adrDir, { recursive: true });
|
|
290
|
+
await Bun.write(
|
|
291
|
+
join(adrDir, "0003-third.md"),
|
|
292
|
+
"# 0003. Third\n\nDate: 2026-01-03\n\n## Status\n\nProposed\n",
|
|
293
|
+
);
|
|
294
|
+
await Bun.write(
|
|
295
|
+
join(adrDir, "0001-first.md"),
|
|
296
|
+
"# 0001. First\n\nDate: 2026-01-01\n\n## Status\n\nAccepted\n",
|
|
297
|
+
);
|
|
298
|
+
await Bun.write(
|
|
299
|
+
join(adrDir, "0002-second.md"),
|
|
300
|
+
"# 0002. Second\n\nDate: 2026-01-02\n\n## Status\n\nDeprecated\n",
|
|
301
|
+
);
|
|
302
|
+
const result = await listAdrs(adrDir);
|
|
303
|
+
expect(result.ok).toBe(true);
|
|
304
|
+
if (result.ok) {
|
|
305
|
+
expect(result.value[0]?.number).toBe("0001");
|
|
306
|
+
expect(result.value[1]?.number).toBe("0002");
|
|
307
|
+
expect(result.value[2]?.number).toBe("0003");
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { afterAll, beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
|
+
import type { TryAIResult } from "../../ai/try-generate";
|
|
3
|
+
|
|
4
|
+
let mockAIResult: TryAIResult = {
|
|
5
|
+
text: null,
|
|
6
|
+
fromAI: false,
|
|
7
|
+
hostDelegation: false,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
mock.module("../../ai/try-generate", () => ({
|
|
11
|
+
tryAIGenerate: async () => mockAIResult,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
afterAll(() => mock.restore());
|
|
15
|
+
|
|
16
|
+
// Import AFTER mocking
|
|
17
|
+
const { generateHldLld } = await import("../index");
|
|
18
|
+
|
|
19
|
+
describe("generateHldLld", () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockAIResult = { text: null, fromAI: false, hostDelegation: false };
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should return AI-generated HLD/LLD content when AI is available", async () => {
|
|
25
|
+
const hldLldContent = `## High-Level Design
|
|
26
|
+
|
|
27
|
+
### System Overview
|
|
28
|
+
This adds AI review to the verify pipeline.
|
|
29
|
+
|
|
30
|
+
### Component Boundaries
|
|
31
|
+
- ai-review.ts: AI review logic
|
|
32
|
+
|
|
33
|
+
### Data Flow
|
|
34
|
+
Diff -> AI -> Findings
|
|
35
|
+
|
|
36
|
+
### External Dependencies
|
|
37
|
+
None
|
|
38
|
+
|
|
39
|
+
## Low-Level Design
|
|
40
|
+
|
|
41
|
+
### Interfaces & Types
|
|
42
|
+
AIReviewResult interface
|
|
43
|
+
|
|
44
|
+
### Function Signatures
|
|
45
|
+
runAIReview(options): Promise<AIReviewResult>
|
|
46
|
+
|
|
47
|
+
### DB Schema Changes
|
|
48
|
+
None
|
|
49
|
+
|
|
50
|
+
### Sequence of Operations
|
|
51
|
+
1. Get diff 2. Call AI
|
|
52
|
+
|
|
53
|
+
### Error Handling
|
|
54
|
+
Graceful skip on failure
|
|
55
|
+
|
|
56
|
+
### Edge Cases
|
|
57
|
+
Empty diff returns no findings`;
|
|
58
|
+
|
|
59
|
+
mockAIResult = { text: hldLldContent, fromAI: true, hostDelegation: false };
|
|
60
|
+
|
|
61
|
+
const result = await generateHldLld("Test spec content", ".maina");
|
|
62
|
+
|
|
63
|
+
expect(result.ok).toBe(true);
|
|
64
|
+
if (!result.ok) return;
|
|
65
|
+
expect(result.value).toContain("## High-Level Design");
|
|
66
|
+
expect(result.value).toContain("## Low-Level Design");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should return error when AI is unavailable", async () => {
|
|
70
|
+
mockAIResult = { text: null, fromAI: false, hostDelegation: false };
|
|
71
|
+
|
|
72
|
+
const result = await generateHldLld("Test spec", ".maina");
|
|
73
|
+
|
|
74
|
+
expect(result.ok).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should use delegation text as content when host delegation active", async () => {
|
|
78
|
+
mockAIResult = {
|
|
79
|
+
text: "Generate HLD and LLD sections for this spec:\n\nTest spec content",
|
|
80
|
+
fromAI: false,
|
|
81
|
+
hostDelegation: true,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const result = await generateHldLld("Test spec content", ".maina");
|
|
85
|
+
|
|
86
|
+
expect(result.ok).toBe(true);
|
|
87
|
+
if (!result.ok) return;
|
|
88
|
+
expect(result.value).toContain("Test spec content");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should return delegation prompt when AI unavailable but host delegation active", async () => {
|
|
92
|
+
mockAIResult = {
|
|
93
|
+
text: null,
|
|
94
|
+
fromAI: false,
|
|
95
|
+
hostDelegation: true,
|
|
96
|
+
delegation: {
|
|
97
|
+
task: "design-hld-lld",
|
|
98
|
+
systemPrompt: "system",
|
|
99
|
+
userPrompt: "Generate HLD for this spec:\n\nTest spec",
|
|
100
|
+
promptHash: "hash123",
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const result = await generateHldLld("Test spec", ".maina");
|
|
105
|
+
expect(result.ok).toBe(true);
|
|
106
|
+
if (!result.ok) return;
|
|
107
|
+
expect(result.value).toContain("Test spec");
|
|
108
|
+
});
|
|
109
|
+
});
|