@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,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
+ });