@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,492 @@
|
|
|
1
|
+
import type { Result } from "../db/index.ts";
|
|
2
|
+
import { getContextDb, getStatsDb } from "../db/index.ts";
|
|
3
|
+
|
|
4
|
+
export interface SnapshotInput {
|
|
5
|
+
branch: string;
|
|
6
|
+
commitHash: string;
|
|
7
|
+
verifyDurationMs: number;
|
|
8
|
+
totalDurationMs: number;
|
|
9
|
+
contextTokens: number;
|
|
10
|
+
contextBudget: number;
|
|
11
|
+
cacheHits: number;
|
|
12
|
+
cacheMisses: number;
|
|
13
|
+
findingsTotal: number;
|
|
14
|
+
findingsErrors: number;
|
|
15
|
+
findingsWarnings: number;
|
|
16
|
+
toolsRun: number;
|
|
17
|
+
syntaxPassed: boolean;
|
|
18
|
+
pipelinePassed: boolean;
|
|
19
|
+
skipped?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CommitSnapshot extends SnapshotInput {
|
|
23
|
+
id: string;
|
|
24
|
+
timestamp: string;
|
|
25
|
+
contextUtilization: number;
|
|
26
|
+
skipped: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface StatsReport {
|
|
30
|
+
totalCommits: number;
|
|
31
|
+
latest: CommitSnapshot | null;
|
|
32
|
+
averages: {
|
|
33
|
+
verifyDurationMs: number;
|
|
34
|
+
contextTokens: number;
|
|
35
|
+
cacheHitRate: number;
|
|
36
|
+
findingsPerCommit: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type TrendDirection = "up" | "down" | "stable";
|
|
41
|
+
|
|
42
|
+
export interface TrendsReport {
|
|
43
|
+
verifyDuration: TrendDirection;
|
|
44
|
+
contextTokens: TrendDirection;
|
|
45
|
+
cacheHitRate: TrendDirection;
|
|
46
|
+
findingsPerCommit: TrendDirection;
|
|
47
|
+
window: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ComparisonReport {
|
|
51
|
+
totalCommits: number;
|
|
52
|
+
/** Total findings caught by maina that would have shipped without it */
|
|
53
|
+
findingsCaught: number;
|
|
54
|
+
/** Total verification time invested */
|
|
55
|
+
totalVerifyTimeMs: number;
|
|
56
|
+
/** Average verify time per commit */
|
|
57
|
+
avgVerifyTimeMs: number;
|
|
58
|
+
/** Context tokens assembled (shows context engine doing useful work) */
|
|
59
|
+
totalContextTokens: number;
|
|
60
|
+
/** Episodic entries (shows memory growing) */
|
|
61
|
+
episodicEntries: number;
|
|
62
|
+
/** Semantic entities indexed */
|
|
63
|
+
semanticEntities: number;
|
|
64
|
+
/** Dependency edges mapped */
|
|
65
|
+
dependencyEdges: number;
|
|
66
|
+
/** Cache hits (tokens saved) */
|
|
67
|
+
cacheHits: number;
|
|
68
|
+
/** What raw git gives you: none of the above */
|
|
69
|
+
withoutMaina: {
|
|
70
|
+
findingsCaught: 0;
|
|
71
|
+
contextTokens: 0;
|
|
72
|
+
episodicMemory: 0;
|
|
73
|
+
verificationTools: 0;
|
|
74
|
+
cacheHits: 0;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface RawSnapshotRow {
|
|
79
|
+
id: string;
|
|
80
|
+
timestamp: string;
|
|
81
|
+
branch: string;
|
|
82
|
+
commit_hash: string;
|
|
83
|
+
verify_duration_ms: number;
|
|
84
|
+
total_duration_ms: number;
|
|
85
|
+
context_tokens: number;
|
|
86
|
+
context_budget: number;
|
|
87
|
+
context_utilization: number;
|
|
88
|
+
cache_hits: number;
|
|
89
|
+
cache_misses: number;
|
|
90
|
+
findings_total: number;
|
|
91
|
+
findings_errors: number;
|
|
92
|
+
findings_warnings: number;
|
|
93
|
+
tools_run: number;
|
|
94
|
+
syntax_passed: number;
|
|
95
|
+
pipeline_passed: number;
|
|
96
|
+
skipped: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function rowToSnapshot(row: RawSnapshotRow): CommitSnapshot {
|
|
100
|
+
return {
|
|
101
|
+
id: row.id,
|
|
102
|
+
timestamp: row.timestamp,
|
|
103
|
+
branch: row.branch,
|
|
104
|
+
commitHash: row.commit_hash,
|
|
105
|
+
verifyDurationMs: row.verify_duration_ms,
|
|
106
|
+
totalDurationMs: row.total_duration_ms,
|
|
107
|
+
contextTokens: row.context_tokens,
|
|
108
|
+
contextBudget: row.context_budget,
|
|
109
|
+
contextUtilization: row.context_utilization,
|
|
110
|
+
cacheHits: row.cache_hits,
|
|
111
|
+
cacheMisses: row.cache_misses,
|
|
112
|
+
findingsTotal: row.findings_total,
|
|
113
|
+
findingsErrors: row.findings_errors,
|
|
114
|
+
findingsWarnings: row.findings_warnings,
|
|
115
|
+
toolsRun: row.tools_run,
|
|
116
|
+
syntaxPassed: row.syntax_passed === 1,
|
|
117
|
+
pipelinePassed: row.pipeline_passed === 1,
|
|
118
|
+
skipped: row.skipped === 1,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Records a commit snapshot to the stats database.
|
|
124
|
+
* Generates a UUID id and ISO timestamp automatically.
|
|
125
|
+
* Computes contextUtilization from contextTokens / contextBudget.
|
|
126
|
+
*/
|
|
127
|
+
export function recordSnapshot(
|
|
128
|
+
mainaDir: string,
|
|
129
|
+
snapshot: SnapshotInput,
|
|
130
|
+
): Result<void> {
|
|
131
|
+
try {
|
|
132
|
+
const dbResult = getStatsDb(mainaDir);
|
|
133
|
+
if (!dbResult.ok) return dbResult;
|
|
134
|
+
|
|
135
|
+
const { db } = dbResult.value;
|
|
136
|
+
const id = crypto.randomUUID();
|
|
137
|
+
const timestamp = new Date().toISOString();
|
|
138
|
+
const contextUtilization =
|
|
139
|
+
snapshot.contextBudget > 0
|
|
140
|
+
? snapshot.contextTokens / snapshot.contextBudget
|
|
141
|
+
: 0;
|
|
142
|
+
|
|
143
|
+
db.prepare(
|
|
144
|
+
`INSERT INTO commit_snapshots (
|
|
145
|
+
id, timestamp, branch, commit_hash,
|
|
146
|
+
verify_duration_ms, total_duration_ms,
|
|
147
|
+
context_tokens, context_budget, context_utilization,
|
|
148
|
+
cache_hits, cache_misses,
|
|
149
|
+
findings_total, findings_errors, findings_warnings,
|
|
150
|
+
tools_run, syntax_passed, pipeline_passed, skipped
|
|
151
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
152
|
+
).run(
|
|
153
|
+
id,
|
|
154
|
+
timestamp,
|
|
155
|
+
snapshot.branch,
|
|
156
|
+
snapshot.commitHash,
|
|
157
|
+
snapshot.verifyDurationMs,
|
|
158
|
+
snapshot.totalDurationMs,
|
|
159
|
+
snapshot.contextTokens,
|
|
160
|
+
snapshot.contextBudget,
|
|
161
|
+
contextUtilization,
|
|
162
|
+
snapshot.cacheHits,
|
|
163
|
+
snapshot.cacheMisses,
|
|
164
|
+
snapshot.findingsTotal,
|
|
165
|
+
snapshot.findingsErrors,
|
|
166
|
+
snapshot.findingsWarnings,
|
|
167
|
+
snapshot.toolsRun,
|
|
168
|
+
snapshot.syntaxPassed ? 1 : 0,
|
|
169
|
+
snapshot.pipelinePassed ? 1 : 0,
|
|
170
|
+
snapshot.skipped ? 1 : 0,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return { ok: true, value: undefined };
|
|
174
|
+
} catch (e) {
|
|
175
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Returns the most recent commit snapshot, or null if none exist.
|
|
181
|
+
*/
|
|
182
|
+
export function getLatest(mainaDir: string): Result<CommitSnapshot | null> {
|
|
183
|
+
try {
|
|
184
|
+
const dbResult = getStatsDb(mainaDir);
|
|
185
|
+
if (!dbResult.ok) return dbResult;
|
|
186
|
+
|
|
187
|
+
const { db } = dbResult.value;
|
|
188
|
+
const row = db
|
|
189
|
+
.prepare("SELECT * FROM commit_snapshots ORDER BY rowid DESC LIMIT 1")
|
|
190
|
+
.get() as RawSnapshotRow | null;
|
|
191
|
+
|
|
192
|
+
if (!row) return { ok: true, value: null };
|
|
193
|
+
return { ok: true, value: rowToSnapshot(row) };
|
|
194
|
+
} catch (e) {
|
|
195
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Computes aggregate statistics over the last N snapshots.
|
|
201
|
+
* Returns total commit count, latest snapshot, and averages.
|
|
202
|
+
*/
|
|
203
|
+
export function getStats(
|
|
204
|
+
mainaDir: string,
|
|
205
|
+
options?: { last?: number },
|
|
206
|
+
): Result<StatsReport> {
|
|
207
|
+
try {
|
|
208
|
+
const dbResult = getStatsDb(mainaDir);
|
|
209
|
+
if (!dbResult.ok) return dbResult;
|
|
210
|
+
|
|
211
|
+
const { db } = dbResult.value;
|
|
212
|
+
const limit = options?.last ?? 10;
|
|
213
|
+
|
|
214
|
+
// Total count of all snapshots
|
|
215
|
+
const countRow = db
|
|
216
|
+
.prepare("SELECT COUNT(*) as cnt FROM commit_snapshots")
|
|
217
|
+
.get() as { cnt: number };
|
|
218
|
+
const totalCommits = countRow.cnt;
|
|
219
|
+
|
|
220
|
+
// Latest snapshot
|
|
221
|
+
const latestRow = db
|
|
222
|
+
.prepare("SELECT * FROM commit_snapshots ORDER BY rowid DESC LIMIT 1")
|
|
223
|
+
.get() as RawSnapshotRow | null;
|
|
224
|
+
const latest = latestRow ? rowToSnapshot(latestRow) : null;
|
|
225
|
+
|
|
226
|
+
// Last N snapshots for averages
|
|
227
|
+
const rows = db
|
|
228
|
+
.prepare("SELECT * FROM commit_snapshots ORDER BY rowid DESC LIMIT ?")
|
|
229
|
+
.all(limit) as RawSnapshotRow[];
|
|
230
|
+
|
|
231
|
+
let avgVerifyDurationMs = 0;
|
|
232
|
+
let avgContextTokens = 0;
|
|
233
|
+
let avgCacheHitRate = 0;
|
|
234
|
+
let avgFindingsPerCommit = 0;
|
|
235
|
+
|
|
236
|
+
if (rows.length > 0) {
|
|
237
|
+
let totalVerify = 0;
|
|
238
|
+
let totalTokens = 0;
|
|
239
|
+
let totalCacheHits = 0;
|
|
240
|
+
let totalCacheTotal = 0;
|
|
241
|
+
let totalFindings = 0;
|
|
242
|
+
|
|
243
|
+
for (const row of rows) {
|
|
244
|
+
totalVerify += row.verify_duration_ms;
|
|
245
|
+
totalTokens += row.context_tokens;
|
|
246
|
+
totalCacheHits += row.cache_hits;
|
|
247
|
+
totalCacheTotal += row.cache_hits + row.cache_misses;
|
|
248
|
+
totalFindings += row.findings_total;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
avgVerifyDurationMs = totalVerify / rows.length;
|
|
252
|
+
avgContextTokens = totalTokens / rows.length;
|
|
253
|
+
avgCacheHitRate =
|
|
254
|
+
totalCacheTotal > 0 ? totalCacheHits / totalCacheTotal : 0;
|
|
255
|
+
avgFindingsPerCommit = totalFindings / rows.length;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
ok: true,
|
|
260
|
+
value: {
|
|
261
|
+
totalCommits,
|
|
262
|
+
latest,
|
|
263
|
+
averages: {
|
|
264
|
+
verifyDurationMs: avgVerifyDurationMs,
|
|
265
|
+
contextTokens: avgContextTokens,
|
|
266
|
+
cacheHitRate: avgCacheHitRate,
|
|
267
|
+
findingsPerCommit: avgFindingsPerCommit,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
} catch (e) {
|
|
272
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function computeTrend(recent: number, previous: number): TrendDirection {
|
|
277
|
+
if (previous === 0 && recent === 0) return "stable";
|
|
278
|
+
if (previous === 0) return "up";
|
|
279
|
+
|
|
280
|
+
const change = (recent - previous) / Math.abs(previous);
|
|
281
|
+
if (Math.abs(change) < 0.05) return "stable";
|
|
282
|
+
return change > 0 ? "up" : "down";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Compares averages of recent N snapshots vs previous N snapshots.
|
|
287
|
+
* Returns trend direction for each metric with a 5% threshold.
|
|
288
|
+
*/
|
|
289
|
+
export function getTrends(
|
|
290
|
+
mainaDir: string,
|
|
291
|
+
options?: { window?: number },
|
|
292
|
+
): Result<TrendsReport> {
|
|
293
|
+
try {
|
|
294
|
+
const dbResult = getStatsDb(mainaDir);
|
|
295
|
+
if (!dbResult.ok) return dbResult;
|
|
296
|
+
|
|
297
|
+
const { db } = dbResult.value;
|
|
298
|
+
const window = options?.window ?? 5;
|
|
299
|
+
|
|
300
|
+
const allStable: TrendsReport = {
|
|
301
|
+
verifyDuration: "stable",
|
|
302
|
+
contextTokens: "stable",
|
|
303
|
+
cacheHitRate: "stable",
|
|
304
|
+
findingsPerCommit: "stable",
|
|
305
|
+
window,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Need at least 2*window snapshots
|
|
309
|
+
const countRow = db
|
|
310
|
+
.prepare("SELECT COUNT(*) as cnt FROM commit_snapshots")
|
|
311
|
+
.get() as { cnt: number };
|
|
312
|
+
|
|
313
|
+
if (countRow.cnt < 2 * window) {
|
|
314
|
+
return { ok: true, value: allStable };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Fetch 2*window most recent snapshots
|
|
318
|
+
const rows = db
|
|
319
|
+
.prepare("SELECT * FROM commit_snapshots ORDER BY rowid DESC LIMIT ?")
|
|
320
|
+
.all(2 * window) as RawSnapshotRow[];
|
|
321
|
+
|
|
322
|
+
// Recent = first `window` rows, Previous = next `window` rows
|
|
323
|
+
const recentRows = rows.slice(0, window);
|
|
324
|
+
const previousRows = rows.slice(window, 2 * window);
|
|
325
|
+
|
|
326
|
+
function avgMetrics(subset: RawSnapshotRow[]) {
|
|
327
|
+
let totalVerify = 0;
|
|
328
|
+
let totalTokens = 0;
|
|
329
|
+
let totalCacheHits = 0;
|
|
330
|
+
let totalCacheTotal = 0;
|
|
331
|
+
let totalFindings = 0;
|
|
332
|
+
|
|
333
|
+
for (const row of subset) {
|
|
334
|
+
totalVerify += row.verify_duration_ms;
|
|
335
|
+
totalTokens += row.context_tokens;
|
|
336
|
+
totalCacheHits += row.cache_hits;
|
|
337
|
+
totalCacheTotal += row.cache_hits + row.cache_misses;
|
|
338
|
+
totalFindings += row.findings_total;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const n = subset.length;
|
|
342
|
+
return {
|
|
343
|
+
verifyDuration: n > 0 ? totalVerify / n : 0,
|
|
344
|
+
contextTokens: n > 0 ? totalTokens / n : 0,
|
|
345
|
+
cacheHitRate:
|
|
346
|
+
totalCacheTotal > 0 ? totalCacheHits / totalCacheTotal : 0,
|
|
347
|
+
findingsPerCommit: n > 0 ? totalFindings / n : 0,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const recent = avgMetrics(recentRows);
|
|
352
|
+
const previous = avgMetrics(previousRows);
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
ok: true,
|
|
356
|
+
value: {
|
|
357
|
+
verifyDuration: computeTrend(
|
|
358
|
+
recent.verifyDuration,
|
|
359
|
+
previous.verifyDuration,
|
|
360
|
+
),
|
|
361
|
+
contextTokens: computeTrend(
|
|
362
|
+
recent.contextTokens,
|
|
363
|
+
previous.contextTokens,
|
|
364
|
+
),
|
|
365
|
+
cacheHitRate: computeTrend(recent.cacheHitRate, previous.cacheHitRate),
|
|
366
|
+
findingsPerCommit: computeTrend(
|
|
367
|
+
recent.findingsPerCommit,
|
|
368
|
+
previous.findingsPerCommit,
|
|
369
|
+
),
|
|
370
|
+
window,
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
} catch (e) {
|
|
374
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Generate a comparison report: what maina provides vs raw git commit.
|
|
380
|
+
* Queries stats DB + context DB for comprehensive metrics.
|
|
381
|
+
*/
|
|
382
|
+
export function getComparison(mainaDir: string): Result<ComparisonReport> {
|
|
383
|
+
try {
|
|
384
|
+
const dbResult = getStatsDb(mainaDir);
|
|
385
|
+
if (!dbResult.ok) {
|
|
386
|
+
return { ok: false, error: dbResult.error };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const db = dbResult.value.db;
|
|
390
|
+
|
|
391
|
+
const aggRow = db
|
|
392
|
+
.prepare(
|
|
393
|
+
`SELECT
|
|
394
|
+
COUNT(*) as total_commits,
|
|
395
|
+
SUM(findings_total) as total_findings,
|
|
396
|
+
SUM(verify_duration_ms) as total_verify_ms,
|
|
397
|
+
AVG(verify_duration_ms) as avg_verify_ms,
|
|
398
|
+
SUM(context_tokens) as total_context_tokens,
|
|
399
|
+
SUM(cache_hits) as total_cache_hits
|
|
400
|
+
FROM commit_snapshots`,
|
|
401
|
+
)
|
|
402
|
+
.get() as {
|
|
403
|
+
total_commits: number;
|
|
404
|
+
total_findings: number;
|
|
405
|
+
total_verify_ms: number;
|
|
406
|
+
avg_verify_ms: number;
|
|
407
|
+
total_context_tokens: number;
|
|
408
|
+
total_cache_hits: number;
|
|
409
|
+
} | null;
|
|
410
|
+
|
|
411
|
+
let episodicEntries = 0;
|
|
412
|
+
let semanticEntities = 0;
|
|
413
|
+
let dependencyEdges = 0;
|
|
414
|
+
try {
|
|
415
|
+
const ctxDb = getContextDb(mainaDir);
|
|
416
|
+
if (ctxDb.ok) {
|
|
417
|
+
const epCount = ctxDb.value.db
|
|
418
|
+
.prepare("SELECT COUNT(*) as c FROM episodic_entries")
|
|
419
|
+
.get() as { c: number } | null;
|
|
420
|
+
episodicEntries = epCount?.c ?? 0;
|
|
421
|
+
|
|
422
|
+
const seCount = ctxDb.value.db
|
|
423
|
+
.prepare("SELECT COUNT(*) as c FROM semantic_entities")
|
|
424
|
+
.get() as { c: number } | null;
|
|
425
|
+
semanticEntities = seCount?.c ?? 0;
|
|
426
|
+
|
|
427
|
+
const deCount = ctxDb.value.db
|
|
428
|
+
.prepare("SELECT COUNT(*) as c FROM dependency_edges")
|
|
429
|
+
.get() as { c: number } | null;
|
|
430
|
+
dependencyEdges = deCount?.c ?? 0;
|
|
431
|
+
}
|
|
432
|
+
} catch {
|
|
433
|
+
// Context DB not available
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
ok: true,
|
|
438
|
+
value: {
|
|
439
|
+
totalCommits: aggRow?.total_commits ?? 0,
|
|
440
|
+
findingsCaught: aggRow?.total_findings ?? 0,
|
|
441
|
+
totalVerifyTimeMs: aggRow?.total_verify_ms ?? 0,
|
|
442
|
+
avgVerifyTimeMs: Math.round(aggRow?.avg_verify_ms ?? 0),
|
|
443
|
+
totalContextTokens: aggRow?.total_context_tokens ?? 0,
|
|
444
|
+
episodicEntries,
|
|
445
|
+
semanticEntities,
|
|
446
|
+
dependencyEdges,
|
|
447
|
+
cacheHits: aggRow?.total_cache_hits ?? 0,
|
|
448
|
+
withoutMaina: {
|
|
449
|
+
findingsCaught: 0,
|
|
450
|
+
contextTokens: 0,
|
|
451
|
+
episodicMemory: 0,
|
|
452
|
+
verificationTools: 0,
|
|
453
|
+
cacheHits: 0,
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
} catch (e) {
|
|
458
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Compute the skip rate from commit_snapshots.
|
|
464
|
+
* Returns total commits, skipped count, and rate (0-1).
|
|
465
|
+
*/
|
|
466
|
+
export function getSkipRate(
|
|
467
|
+
mainaDir: string,
|
|
468
|
+
): Result<{ total: number; skipped: number; rate: number }> {
|
|
469
|
+
try {
|
|
470
|
+
const dbResult = getStatsDb(mainaDir);
|
|
471
|
+
if (!dbResult.ok) return dbResult;
|
|
472
|
+
|
|
473
|
+
const { db } = dbResult.value;
|
|
474
|
+
|
|
475
|
+
const row = db
|
|
476
|
+
.prepare(
|
|
477
|
+
`SELECT
|
|
478
|
+
COUNT(*) as total,
|
|
479
|
+
SUM(CASE WHEN skipped = 1 THEN 1 ELSE 0 END) as skipped
|
|
480
|
+
FROM commit_snapshots`,
|
|
481
|
+
)
|
|
482
|
+
.get() as { total: number; skipped: number } | null;
|
|
483
|
+
|
|
484
|
+
const total = row?.total ?? 0;
|
|
485
|
+
const skipped = row?.skipped ?? 0;
|
|
486
|
+
const rate = total > 0 ? skipped / total : 0;
|
|
487
|
+
|
|
488
|
+
return { ok: true, value: { total, skipped, rate } };
|
|
489
|
+
} catch (e) {
|
|
490
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
491
|
+
}
|
|
492
|
+
}
|