@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,323 @@
1
+ import { afterAll, beforeAll, 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 {
6
+ type CommitSnapshot,
7
+ getLatest,
8
+ getSkipRate,
9
+ getStats,
10
+ getTrends,
11
+ recordSnapshot,
12
+ type SnapshotInput,
13
+ } from "../tracker.ts";
14
+
15
+ const TEST_DIR = join(tmpdir(), `maina-stats-test-${Date.now()}`);
16
+
17
+ beforeAll(() => {
18
+ mkdirSync(TEST_DIR, { recursive: true });
19
+ });
20
+
21
+ afterAll(() => {
22
+ rmSync(TEST_DIR, { recursive: true, force: true });
23
+ });
24
+
25
+ function makeDir(sub: string): string {
26
+ const d = join(TEST_DIR, sub);
27
+ mkdirSync(d, { recursive: true });
28
+ return d;
29
+ }
30
+
31
+ function makeSnapshot(overrides: Partial<SnapshotInput> = {}): SnapshotInput {
32
+ return {
33
+ branch: "main",
34
+ commitHash: "abc123",
35
+ verifyDurationMs: 1500,
36
+ totalDurationMs: 3000,
37
+ contextTokens: 2000,
38
+ contextBudget: 4000,
39
+ cacheHits: 5,
40
+ cacheMisses: 3,
41
+ findingsTotal: 2,
42
+ findingsErrors: 1,
43
+ findingsWarnings: 1,
44
+ toolsRun: 4,
45
+ syntaxPassed: true,
46
+ pipelinePassed: true,
47
+ ...overrides,
48
+ };
49
+ }
50
+
51
+ describe("recordSnapshot", () => {
52
+ test("inserts a row, returns ok", () => {
53
+ const dir = makeDir("record-ok");
54
+ const result = recordSnapshot(dir, makeSnapshot());
55
+ expect(result.ok).toBe(true);
56
+ });
57
+
58
+ test("generates id and timestamp automatically", () => {
59
+ const dir = makeDir("record-auto");
60
+ const result = recordSnapshot(dir, makeSnapshot());
61
+ expect(result.ok).toBe(true);
62
+
63
+ const latest = getLatest(dir);
64
+ expect(latest.ok).toBe(true);
65
+ if (!latest.ok) return;
66
+ expect(latest.value).not.toBeNull();
67
+ const snap = latest.value as CommitSnapshot;
68
+ expect(snap.id).toBeDefined();
69
+ expect(snap.id.length).toBeGreaterThan(0);
70
+ expect(snap.timestamp).toBeDefined();
71
+ // UUID format: 8-4-4-4-12
72
+ expect(snap.id).toMatch(
73
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
74
+ );
75
+ // ISO 8601 timestamp
76
+ expect(Number.isNaN(Date.parse(snap.timestamp))).toBe(false);
77
+ });
78
+
79
+ test("computes contextUtilization from tokens/budget", () => {
80
+ const dir = makeDir("record-util");
81
+ recordSnapshot(
82
+ dir,
83
+ makeSnapshot({ contextTokens: 3000, contextBudget: 4000 }),
84
+ );
85
+
86
+ const latest = getLatest(dir);
87
+ expect(latest.ok).toBe(true);
88
+ if (!latest.ok) return;
89
+ const snap = latest.value as CommitSnapshot;
90
+ expect(snap.contextUtilization).toBeCloseTo(0.75, 4);
91
+ });
92
+ });
93
+
94
+ describe("getLatest", () => {
95
+ test("returns null when no snapshots exist", () => {
96
+ const dir = makeDir("latest-empty");
97
+ const result = getLatest(dir);
98
+ expect(result.ok).toBe(true);
99
+ if (!result.ok) return;
100
+ expect(result.value).toBeNull();
101
+ });
102
+
103
+ test("returns the most recent snapshot", () => {
104
+ const dir = makeDir("latest-recent");
105
+ recordSnapshot(dir, makeSnapshot({ commitHash: "first" }));
106
+ recordSnapshot(dir, makeSnapshot({ commitHash: "second" }));
107
+ recordSnapshot(dir, makeSnapshot({ commitHash: "third" }));
108
+
109
+ const result = getLatest(dir);
110
+ expect(result.ok).toBe(true);
111
+ if (!result.ok) return;
112
+ expect(result.value).not.toBeNull();
113
+ const snap = result.value as CommitSnapshot;
114
+ expect(snap.commitHash).toBe("third");
115
+ });
116
+ });
117
+
118
+ describe("getStats", () => {
119
+ test("returns correct averages over last N snapshots", () => {
120
+ const dir = makeDir("stats-avg");
121
+ // Insert 3 snapshots with known values
122
+ recordSnapshot(
123
+ dir,
124
+ makeSnapshot({
125
+ verifyDurationMs: 1000,
126
+ contextTokens: 2000,
127
+ cacheHits: 4,
128
+ cacheMisses: 6,
129
+ findingsTotal: 3,
130
+ }),
131
+ );
132
+ recordSnapshot(
133
+ dir,
134
+ makeSnapshot({
135
+ verifyDurationMs: 2000,
136
+ contextTokens: 3000,
137
+ cacheHits: 6,
138
+ cacheMisses: 4,
139
+ findingsTotal: 5,
140
+ }),
141
+ );
142
+ recordSnapshot(
143
+ dir,
144
+ makeSnapshot({
145
+ verifyDurationMs: 3000,
146
+ contextTokens: 4000,
147
+ cacheHits: 10,
148
+ cacheMisses: 0,
149
+ findingsTotal: 7,
150
+ }),
151
+ );
152
+
153
+ const result = getStats(dir);
154
+ expect(result.ok).toBe(true);
155
+ if (!result.ok) return;
156
+
157
+ const report = result.value;
158
+ expect(report.averages.verifyDurationMs).toBeCloseTo(2000, 0);
159
+ expect(report.averages.contextTokens).toBeCloseTo(3000, 0);
160
+ // cache hit rate: (4+6+10) / (4+6+6+4+10+0) = 20/30 ≈ 0.6667
161
+ expect(report.averages.cacheHitRate).toBeCloseTo(20 / 30, 2);
162
+ expect(report.averages.findingsPerCommit).toBeCloseTo(5, 0);
163
+ });
164
+
165
+ test("with --last flag limits to N", () => {
166
+ const dir = makeDir("stats-last");
167
+ // Insert 5 snapshots
168
+ for (let i = 1; i <= 5; i++) {
169
+ recordSnapshot(
170
+ dir,
171
+ makeSnapshot({
172
+ verifyDurationMs: i * 1000,
173
+ contextTokens: i * 1000,
174
+ findingsTotal: i,
175
+ }),
176
+ );
177
+ }
178
+
179
+ const result = getStats(dir, { last: 2 });
180
+ expect(result.ok).toBe(true);
181
+ if (!result.ok) return;
182
+
183
+ const report = result.value;
184
+ // Last 2: verifyDurationMs 4000, 5000 => avg 4500
185
+ expect(report.averages.verifyDurationMs).toBeCloseTo(4500, 0);
186
+ expect(report.totalCommits).toBe(5);
187
+ });
188
+
189
+ test("returns totalCommits count", () => {
190
+ const dir = makeDir("stats-total");
191
+ recordSnapshot(dir, makeSnapshot());
192
+ recordSnapshot(dir, makeSnapshot());
193
+ recordSnapshot(dir, makeSnapshot());
194
+
195
+ const result = getStats(dir);
196
+ expect(result.ok).toBe(true);
197
+ if (!result.ok) return;
198
+ expect(result.value.totalCommits).toBe(3);
199
+ });
200
+ });
201
+
202
+ describe("getTrends", () => {
203
+ test("returns 'stable' when not enough data (< 2*window)", () => {
204
+ const dir = makeDir("trends-stable-nodata");
205
+ // Insert only 3 snapshots with default window of 5 (needs 10)
206
+ for (let i = 0; i < 3; i++) {
207
+ recordSnapshot(dir, makeSnapshot());
208
+ }
209
+
210
+ const result = getTrends(dir);
211
+ expect(result.ok).toBe(true);
212
+ if (!result.ok) return;
213
+ expect(result.value.verifyDuration).toBe("stable");
214
+ expect(result.value.contextTokens).toBe("stable");
215
+ expect(result.value.cacheHitRate).toBe("stable");
216
+ expect(result.value.findingsPerCommit).toBe("stable");
217
+ });
218
+
219
+ test("returns 'down' for verify time when improving", () => {
220
+ const dir = makeDir("trends-verify-down");
221
+ const window = 3;
222
+ // Older window: high verify times
223
+ for (let i = 0; i < window; i++) {
224
+ recordSnapshot(dir, makeSnapshot({ verifyDurationMs: 5000 }));
225
+ }
226
+ // Recent window: low verify times (improving)
227
+ for (let i = 0; i < window; i++) {
228
+ recordSnapshot(dir, makeSnapshot({ verifyDurationMs: 1000 }));
229
+ }
230
+
231
+ const result = getTrends(dir, { window });
232
+ expect(result.ok).toBe(true);
233
+ if (!result.ok) return;
234
+ expect(result.value.verifyDuration).toBe("down");
235
+ expect(result.value.window).toBe(window);
236
+ });
237
+
238
+ test("returns 'up' for cache hit rate when improving", () => {
239
+ const dir = makeDir("trends-cache-up");
240
+ const window = 3;
241
+ // Older window: low cache hit rate
242
+ for (let i = 0; i < window; i++) {
243
+ recordSnapshot(dir, makeSnapshot({ cacheHits: 1, cacheMisses: 9 }));
244
+ }
245
+ // Recent window: high cache hit rate (improving)
246
+ for (let i = 0; i < window; i++) {
247
+ recordSnapshot(dir, makeSnapshot({ cacheHits: 9, cacheMisses: 1 }));
248
+ }
249
+
250
+ const result = getTrends(dir, { window });
251
+ expect(result.ok).toBe(true);
252
+ if (!result.ok) return;
253
+ expect(result.value.cacheHitRate).toBe("up");
254
+ });
255
+
256
+ test("5% threshold — small changes are 'stable'", () => {
257
+ const dir = makeDir("trends-threshold");
258
+ const window = 3;
259
+ // Older window
260
+ for (let i = 0; i < window; i++) {
261
+ recordSnapshot(dir, makeSnapshot({ verifyDurationMs: 1000 }));
262
+ }
263
+ // Recent window: only 2% change (below 5% threshold)
264
+ for (let i = 0; i < window; i++) {
265
+ recordSnapshot(dir, makeSnapshot({ verifyDurationMs: 980 }));
266
+ }
267
+
268
+ const result = getTrends(dir, { window });
269
+ expect(result.ok).toBe(true);
270
+ if (!result.ok) return;
271
+ expect(result.value.verifyDuration).toBe("stable");
272
+ });
273
+ });
274
+
275
+ describe("skip tracking", () => {
276
+ test("recordSnapshot with skipped=true stores correctly", () => {
277
+ const dir = makeDir("skip-true");
278
+ recordSnapshot(dir, makeSnapshot({ skipped: true }));
279
+
280
+ const latest = getLatest(dir);
281
+ expect(latest.ok).toBe(true);
282
+ if (!latest.ok) return;
283
+ const snap = latest.value as CommitSnapshot;
284
+ expect(snap.skipped).toBe(true);
285
+ });
286
+
287
+ test("recordSnapshot without skipped defaults to false", () => {
288
+ const dir = makeDir("skip-default");
289
+ recordSnapshot(dir, makeSnapshot());
290
+
291
+ const latest = getLatest(dir);
292
+ expect(latest.ok).toBe(true);
293
+ if (!latest.ok) return;
294
+ const snap = latest.value as CommitSnapshot;
295
+ expect(snap.skipped).toBe(false);
296
+ });
297
+
298
+ test("getSkipRate with 5 commits, 2 skipped returns rate 0.4", () => {
299
+ const dir = makeDir("skip-rate-mixed");
300
+ recordSnapshot(dir, makeSnapshot({ skipped: true }));
301
+ recordSnapshot(dir, makeSnapshot({ skipped: false }));
302
+ recordSnapshot(dir, makeSnapshot({ skipped: true }));
303
+ recordSnapshot(dir, makeSnapshot({ skipped: false }));
304
+ recordSnapshot(dir, makeSnapshot({ skipped: false }));
305
+
306
+ const result = getSkipRate(dir);
307
+ expect(result.ok).toBe(true);
308
+ if (!result.ok) return;
309
+ expect(result.value.total).toBe(5);
310
+ expect(result.value.skipped).toBe(2);
311
+ expect(result.value.rate).toBeCloseTo(0.4, 4);
312
+ });
313
+
314
+ test("getSkipRate with 0 commits returns rate 0", () => {
315
+ const dir = makeDir("skip-rate-empty");
316
+ const result = getSkipRate(dir);
317
+ expect(result.ok).toBe(true);
318
+ if (!result.ok) return;
319
+ expect(result.value.total).toBe(0);
320
+ expect(result.value.skipped).toBe(0);
321
+ expect(result.value.rate).toBe(0);
322
+ });
323
+ });
@@ -0,0 +1,11 @@
1
+ export {
2
+ type CommitSnapshot,
3
+ getLatest,
4
+ getStats,
5
+ getTrends,
6
+ recordSnapshot,
7
+ type SnapshotInput,
8
+ type StatsReport,
9
+ type TrendDirection,
10
+ type TrendsReport,
11
+ } from "./tracker.ts";