@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,338 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import {
3
+ type DiffFilterResult,
4
+ type Finding,
5
+ filterByDiffWithMap,
6
+ parseChangedLines,
7
+ } from "../diff-filter";
8
+
9
+ // ─── parseChangedLines ────────────────────────────────────────────────────
10
+
11
+ describe("parseChangedLines", () => {
12
+ it("should return empty map for empty diff", () => {
13
+ const result = parseChangedLines("");
14
+ expect(result.size).toBe(0);
15
+ });
16
+
17
+ it("should parse single file with added lines", () => {
18
+ const diff = `diff --git a/src/foo.ts b/src/foo.ts
19
+ index 1234567..abcdefg 100644
20
+ --- a/src/foo.ts
21
+ +++ b/src/foo.ts
22
+ @@ -10,6 +10,8 @@
23
+ unchanged line
24
+ +added line 1
25
+ +added line 2
26
+ unchanged line
27
+ unchanged line
28
+ unchanged line`;
29
+
30
+ const result = parseChangedLines(diff);
31
+ expect(result.has("src/foo.ts")).toBe(true);
32
+ const lines = result.get("src/foo.ts") ?? new Set<number>();
33
+ expect(lines.has(11)).toBe(true);
34
+ expect(lines.has(12)).toBe(true);
35
+ // Unchanged lines should NOT be in the set
36
+ expect(lines.has(10)).toBe(false);
37
+ expect(lines.has(13)).toBe(false);
38
+ });
39
+
40
+ it("should parse multiple hunks in same file", () => {
41
+ const diff = `diff --git a/src/bar.ts b/src/bar.ts
42
+ --- a/src/bar.ts
43
+ +++ b/src/bar.ts
44
+ @@ -5,3 +5,4 @@
45
+ unchanged
46
+ +new line at 6
47
+ unchanged
48
+ @@ -20,3 +21,4 @@
49
+ unchanged
50
+ +new line at 22
51
+ unchanged`;
52
+
53
+ const result = parseChangedLines(diff);
54
+ expect(result.has("src/bar.ts")).toBe(true);
55
+ const lines = result.get("src/bar.ts") ?? new Set<number>();
56
+ expect(lines.has(6)).toBe(true);
57
+ expect(lines.has(22)).toBe(true);
58
+ expect(lines.has(5)).toBe(false);
59
+ expect(lines.has(21)).toBe(false);
60
+ });
61
+
62
+ it("should parse multiple files", () => {
63
+ const diff = `diff --git a/src/a.ts b/src/a.ts
64
+ --- a/src/a.ts
65
+ +++ b/src/a.ts
66
+ @@ -1,3 +1,4 @@
67
+ line 1
68
+ +added at line 2
69
+ line 2
70
+ line 3
71
+ diff --git a/src/b.ts b/src/b.ts
72
+ --- a/src/b.ts
73
+ +++ b/src/b.ts
74
+ @@ -10,3 +10,4 @@
75
+ existing
76
+ +added at line 11
77
+ existing`;
78
+
79
+ const result = parseChangedLines(diff);
80
+ expect(result.size).toBe(2);
81
+ expect(result.get("src/a.ts")?.has(2)).toBe(true);
82
+ expect(result.get("src/b.ts")?.has(11)).toBe(true);
83
+ });
84
+
85
+ it("should handle modified lines (shown as deletion + addition)", () => {
86
+ const diff = `diff --git a/src/mod.ts b/src/mod.ts
87
+ --- a/src/mod.ts
88
+ +++ b/src/mod.ts
89
+ @@ -5,4 +5,4 @@
90
+ unchanged
91
+ -old line
92
+ +new line
93
+ unchanged`;
94
+
95
+ const result = parseChangedLines(diff);
96
+ const lines = result.get("src/mod.ts") ?? new Set<number>();
97
+ // The + line is line 6 in the new file
98
+ expect(lines.has(6)).toBe(true);
99
+ // The - line should not produce an entry (it's removed)
100
+ expect(lines.size).toBe(1);
101
+ });
102
+
103
+ it("should handle new files (all lines are additions)", () => {
104
+ const diff = `diff --git a/src/new-file.ts b/src/new-file.ts
105
+ new file mode 100644
106
+ index 0000000..abcdefg
107
+ --- /dev/null
108
+ +++ b/src/new-file.ts
109
+ @@ -0,0 +1,3 @@
110
+ +line 1
111
+ +line 2
112
+ +line 3`;
113
+
114
+ const result = parseChangedLines(diff);
115
+ expect(result.has("src/new-file.ts")).toBe(true);
116
+ const lines = result.get("src/new-file.ts") ?? new Set<number>();
117
+ expect(lines.has(1)).toBe(true);
118
+ expect(lines.has(2)).toBe(true);
119
+ expect(lines.has(3)).toBe(true);
120
+ });
121
+
122
+ it("should skip lines starting with --- and +++", () => {
123
+ // These are file headers, not content lines
124
+ const diff = `diff --git a/src/foo.ts b/src/foo.ts
125
+ --- a/src/foo.ts
126
+ +++ b/src/foo.ts
127
+ @@ -1,3 +1,4 @@
128
+ line 1
129
+ +added
130
+ line 2
131
+ line 3`;
132
+
133
+ const result = parseChangedLines(diff);
134
+ const lines = result.get("src/foo.ts") ?? new Set<number>();
135
+ // Should only contain line 2 (the added line), not metadata
136
+ expect(lines.has(2)).toBe(true);
137
+ expect(lines.size).toBe(1);
138
+ });
139
+ });
140
+
141
+ // ─── filterByDiff ─────────────────────────────────────────────────────────
142
+
143
+ describe("filterByDiff", () => {
144
+ const sampleFindings: Finding[] = [
145
+ {
146
+ tool: "biome",
147
+ file: "src/foo.ts",
148
+ line: 11,
149
+ message: "Use const instead of let",
150
+ severity: "warning",
151
+ ruleId: "lint/style/useConst",
152
+ },
153
+ {
154
+ tool: "biome",
155
+ file: "src/foo.ts",
156
+ line: 50,
157
+ message: "Unused variable",
158
+ severity: "warning",
159
+ ruleId: "lint/correctness/noUnusedVariables",
160
+ },
161
+ {
162
+ tool: "semgrep",
163
+ file: "src/bar.ts",
164
+ line: 5,
165
+ message: "Potential injection",
166
+ severity: "error",
167
+ },
168
+ ];
169
+
170
+ it("should show findings on changed lines and hide findings on unchanged lines", () => {
171
+ const diff = `diff --git a/src/foo.ts b/src/foo.ts
172
+ --- a/src/foo.ts
173
+ +++ b/src/foo.ts
174
+ @@ -10,3 +10,4 @@
175
+ unchanged
176
+ +added line
177
+ unchanged`;
178
+
179
+ const changedLines = parseChangedLines(diff);
180
+ // src/foo.ts line 11 is changed, line 50 is not
181
+ // src/bar.ts has no changes in this diff
182
+
183
+ const result = filterByDiffWithMap(sampleFindings, changedLines);
184
+
185
+ expect(result.shown.length).toBe(1);
186
+ expect(result.shown[0]?.file).toBe("src/foo.ts");
187
+ expect(result.shown[0]?.line).toBe(11);
188
+ expect(result.hidden).toBe(2);
189
+ });
190
+ });
191
+
192
+ // ─── VerifyPipeline TDD contract ──────────────────────────────────────────
193
+
194
+ describe("VerifyPipeline", () => {
195
+ it("should apply diff-only filtering by default", () => {
196
+ const diff = `diff --git a/src/app.ts b/src/app.ts
197
+ --- a/src/app.ts
198
+ +++ b/src/app.ts
199
+ @@ -1,3 +1,5 @@
200
+ existing line
201
+ +new import
202
+ +new function call
203
+ existing line`;
204
+
205
+ const changedLines = parseChangedLines(diff);
206
+
207
+ const findings: Finding[] = [
208
+ {
209
+ tool: "biome",
210
+ file: "src/app.ts",
211
+ line: 2,
212
+ message: "New issue on changed line",
213
+ severity: "warning",
214
+ },
215
+ {
216
+ tool: "biome",
217
+ file: "src/app.ts",
218
+ line: 3,
219
+ message: "Another new issue on changed line",
220
+ severity: "error",
221
+ },
222
+ {
223
+ tool: "biome",
224
+ file: "src/app.ts",
225
+ line: 1,
226
+ message: "Pre-existing issue",
227
+ severity: "warning",
228
+ },
229
+ ];
230
+
231
+ const result = filterByDiffWithMap(findings, changedLines);
232
+
233
+ // Changed lines 2 and 3 should be shown
234
+ expect(result.shown.length).toBe(2);
235
+ expect(result.shown.every((f) => [2, 3].includes(f.line))).toBe(true);
236
+ // Unchanged line 1 should be hidden
237
+ expect(result.hidden).toBe(1);
238
+ });
239
+
240
+ it("should report pre-existing count as hidden", () => {
241
+ const diff = `diff --git a/src/legacy.ts b/src/legacy.ts
242
+ --- a/src/legacy.ts
243
+ +++ b/src/legacy.ts
244
+ @@ -100,3 +100,4 @@
245
+ old code
246
+ +new code
247
+ old code`;
248
+
249
+ const changedLines = parseChangedLines(diff);
250
+
251
+ const findings: Finding[] = [
252
+ {
253
+ tool: "semgrep",
254
+ file: "src/legacy.ts",
255
+ line: 5,
256
+ message: "Old issue 1",
257
+ severity: "warning",
258
+ },
259
+ {
260
+ tool: "semgrep",
261
+ file: "src/legacy.ts",
262
+ line: 20,
263
+ message: "Old issue 2",
264
+ severity: "error",
265
+ },
266
+ {
267
+ tool: "semgrep",
268
+ file: "src/legacy.ts",
269
+ line: 80,
270
+ message: "Old issue 3",
271
+ severity: "warning",
272
+ },
273
+ {
274
+ tool: "semgrep",
275
+ file: "src/legacy.ts",
276
+ line: 101,
277
+ message: "New issue on changed line",
278
+ severity: "error",
279
+ },
280
+ ];
281
+
282
+ const result = filterByDiffWithMap(findings, changedLines);
283
+
284
+ // 3 pre-existing issues should be hidden
285
+ expect(result.hidden).toBe(3);
286
+ // 1 new issue on changed line should be shown
287
+ expect(result.shown.length).toBe(1);
288
+ expect(result.shown[0]?.line).toBe(101);
289
+ });
290
+ });
291
+
292
+ // ─── Finding type ─────────────────────────────────────────────────────────
293
+
294
+ describe("Finding type", () => {
295
+ it("should have all required fields", () => {
296
+ const finding: Finding = {
297
+ tool: "biome",
298
+ file: "src/test.ts",
299
+ line: 10,
300
+ message: "Test message",
301
+ severity: "error",
302
+ };
303
+
304
+ expect(finding.tool).toBe("biome");
305
+ expect(finding.file).toBe("src/test.ts");
306
+ expect(finding.line).toBe(10);
307
+ expect(finding.message).toBe("Test message");
308
+ expect(finding.severity).toBe("error");
309
+ });
310
+
311
+ it("should support optional fields", () => {
312
+ const finding: Finding = {
313
+ tool: "biome",
314
+ file: "src/test.ts",
315
+ line: 10,
316
+ column: 5,
317
+ message: "Test message",
318
+ severity: "warning",
319
+ ruleId: "lint/style/useConst",
320
+ };
321
+
322
+ expect(finding.column).toBe(5);
323
+ expect(finding.ruleId).toBe("lint/style/useConst");
324
+ });
325
+ });
326
+
327
+ // ─── DiffFilterResult type ────────────────────────────────────────────────
328
+
329
+ describe("DiffFilterResult type", () => {
330
+ it("should have shown array and hidden count", () => {
331
+ const result: DiffFilterResult = {
332
+ shown: [],
333
+ hidden: 0,
334
+ };
335
+ expect(Array.isArray(result.shown)).toBe(true);
336
+ expect(typeof result.hidden).toBe("number");
337
+ });
338
+ });