@jmtrin/opencode-kevin 1.2.0 → 1.4.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 (140) hide show
  1. package/README.md +84 -22
  2. package/dist/plugin/index.d.ts +5 -4
  3. package/dist/plugin/index.js +127 -113
  4. package/dist/plugin/native.d.ts +2 -2
  5. package/dist/plugin/native.js +2 -2
  6. package/package.json +56 -68
  7. package/dist/migrations/001_initial.sql +0 -92
  8. package/dist/migrations/002_indexes.sql +0 -14
  9. package/dist/migrations/003_v02_signal.sql +0 -58
  10. package/dist/migrations/004_v03_knowledge.sql +0 -138
  11. package/dist/migrations/005_v04_signal.sql +0 -57
  12. package/dist/migrations/006_v05_glassbox.sql +0 -118
  13. package/dist/migrations/007_v06_pull.sql +0 -145
  14. package/dist/migrations/008_v07_truth.sql +0 -124
  15. package/dist/migrations/009_v08_team.sql +0 -100
  16. package/dist/migrations/010_v09_native.sql +0 -78
  17. package/dist/migrations/011_v10_proven.sql +0 -69
  18. package/dist/migrations/012_v11_drift.sql +0 -24
  19. package/dist/plugin/Archiver.d.ts +0 -42
  20. package/dist/plugin/Archiver.js +0 -83
  21. package/dist/plugin/ArtifactWriter.d.ts +0 -93
  22. package/dist/plugin/ArtifactWriter.js +0 -291
  23. package/dist/plugin/CausalChain.d.ts +0 -22
  24. package/dist/plugin/CausalChain.js +0 -197
  25. package/dist/plugin/ChatBridge.d.ts +0 -41
  26. package/dist/plugin/ChatBridge.js +0 -103
  27. package/dist/plugin/ConflictDetector.d.ts +0 -35
  28. package/dist/plugin/ConflictDetector.js +0 -260
  29. package/dist/plugin/ContextInjector.d.ts +0 -222
  30. package/dist/plugin/ContextInjector.js +0 -533
  31. package/dist/plugin/ConventionMiner.d.ts +0 -35
  32. package/dist/plugin/ConventionMiner.js +0 -242
  33. package/dist/plugin/Curator.d.ts +0 -116
  34. package/dist/plugin/Curator.js +0 -324
  35. package/dist/plugin/DashboardHtml.d.ts +0 -5
  36. package/dist/plugin/DashboardHtml.js +0 -180
  37. package/dist/plugin/Feedback.d.ts +0 -67
  38. package/dist/plugin/Feedback.js +0 -120
  39. package/dist/plugin/HookLiveness.d.ts +0 -88
  40. package/dist/plugin/HookLiveness.js +0 -322
  41. package/dist/plugin/InjectionLedger.d.ts +0 -98
  42. package/dist/plugin/InjectionLedger.js +0 -295
  43. package/dist/plugin/LessonFixer.d.ts +0 -44
  44. package/dist/plugin/LessonFixer.js +0 -45
  45. package/dist/plugin/Materializer.d.ts +0 -83
  46. package/dist/plugin/Materializer.js +0 -181
  47. package/dist/plugin/MemoryService.d.ts +0 -360
  48. package/dist/plugin/MemoryService.js +0 -1242
  49. package/dist/plugin/Migrate.d.ts +0 -17
  50. package/dist/plugin/Migrate.js +0 -271
  51. package/dist/plugin/PatternMiner.d.ts +0 -49
  52. package/dist/plugin/PatternMiner.js +0 -132
  53. package/dist/plugin/QualityGate.d.ts +0 -124
  54. package/dist/plugin/QualityGate.js +0 -119
  55. package/dist/plugin/Reflector.d.ts +0 -91
  56. package/dist/plugin/Reflector.js +0 -307
  57. package/dist/plugin/RepoIdentity.d.ts +0 -124
  58. package/dist/plugin/RepoIdentity.js +0 -300
  59. package/dist/plugin/RepoTruth.d.ts +0 -80
  60. package/dist/plugin/RepoTruth.js +0 -599
  61. package/dist/plugin/Retrospective.d.ts +0 -17
  62. package/dist/plugin/Retrospective.js +0 -216
  63. package/dist/plugin/SharedLayer.d.ts +0 -159
  64. package/dist/plugin/SharedLayer.js +0 -462
  65. package/dist/plugin/Store.d.ts +0 -29
  66. package/dist/plugin/Store.js +0 -50
  67. package/dist/plugin/ToolCallObserver.d.ts +0 -32
  68. package/dist/plugin/ToolCallObserver.js +0 -201
  69. package/dist/plugin/TuiActions.d.ts +0 -43
  70. package/dist/plugin/TuiActions.js +0 -181
  71. package/dist/plugin/TuiSnapshots.d.ts +0 -24
  72. package/dist/plugin/TuiSnapshots.js +0 -158
  73. package/dist/plugin/columns.d.ts +0 -11
  74. package/dist/plugin/columns.js +0 -54
  75. package/dist/plugin/confidence.d.ts +0 -8
  76. package/dist/plugin/confidence.js +0 -34
  77. package/dist/plugin/contract.d.ts +0 -54
  78. package/dist/plugin/contract.js +0 -386
  79. package/dist/plugin/diff.d.ts +0 -8
  80. package/dist/plugin/diff.js +0 -182
  81. package/dist/plugin/escape.d.ts +0 -39
  82. package/dist/plugin/escape.js +0 -60
  83. package/dist/plugin/fingerprint.d.ts +0 -27
  84. package/dist/plugin/fingerprint.js +0 -73
  85. package/dist/plugin/inferability.d.ts +0 -32
  86. package/dist/plugin/inferability.js +0 -88
  87. package/dist/plugin/kevin_approve.d.ts +0 -34
  88. package/dist/plugin/kevin_approve.js +0 -52
  89. package/dist/plugin/kevin_audit.d.ts +0 -229
  90. package/dist/plugin/kevin_audit.js +0 -631
  91. package/dist/plugin/kevin_bench.d.ts +0 -10
  92. package/dist/plugin/kevin_bench.js +0 -82
  93. package/dist/plugin/kevin_conflicts.d.ts +0 -9
  94. package/dist/plugin/kevin_conflicts.js +0 -50
  95. package/dist/plugin/kevin_contract.d.ts +0 -8
  96. package/dist/plugin/kevin_contract.js +0 -59
  97. package/dist/plugin/kevin_doctor.d.ts +0 -62
  98. package/dist/plugin/kevin_doctor.js +0 -196
  99. package/dist/plugin/kevin_facts.d.ts +0 -42
  100. package/dist/plugin/kevin_facts.js +0 -36
  101. package/dist/plugin/kevin_forget.d.ts +0 -33
  102. package/dist/plugin/kevin_forget.js +0 -260
  103. package/dist/plugin/kevin_native.d.ts +0 -29
  104. package/dist/plugin/kevin_native.js +0 -79
  105. package/dist/plugin/kevin_propose.d.ts +0 -23
  106. package/dist/plugin/kevin_propose.js +0 -14
  107. package/dist/plugin/kevin_publish.d.ts +0 -38
  108. package/dist/plugin/kevin_publish.js +0 -18
  109. package/dist/plugin/kevin_why.d.ts +0 -23
  110. package/dist/plugin/kevin_why.js +0 -111
  111. package/dist/plugin/memory-format.d.ts +0 -20
  112. package/dist/plugin/memory-format.js +0 -59
  113. package/dist/plugin/metrics.d.ts +0 -97
  114. package/dist/plugin/metrics.js +0 -288
  115. package/dist/plugin/okf-export.d.ts +0 -3
  116. package/dist/plugin/okf-export.js +0 -134
  117. package/dist/plugin/okf-import.d.ts +0 -76
  118. package/dist/plugin/okf-import.js +0 -271
  119. package/dist/plugin/okf.d.ts +0 -107
  120. package/dist/plugin/okf.js +0 -303
  121. package/dist/plugin/perf.d.ts +0 -35
  122. package/dist/plugin/perf.js +0 -174
  123. package/dist/plugin/query-tokenizer.d.ts +0 -13
  124. package/dist/plugin/query-tokenizer.js +0 -133
  125. package/dist/plugin/redact.d.ts +0 -2
  126. package/dist/plugin/redact.js +0 -18
  127. package/dist/plugin/replay-types.d.ts +0 -69
  128. package/dist/plugin/replay-types.js +0 -156
  129. package/dist/plugin/replay.d.ts +0 -36
  130. package/dist/plugin/replay.js +0 -202
  131. package/dist/plugin/sqlite-adapter.d.ts +0 -12
  132. package/dist/plugin/sqlite-adapter.js +0 -81
  133. package/dist/plugin/time-ms.d.ts +0 -1
  134. package/dist/plugin/time-ms.js +0 -16
  135. package/dist/plugin/tui-types.d.ts +0 -59
  136. package/dist/plugin/tui-types.js +0 -4
  137. package/dist/plugin/tui.d.ts +0 -18
  138. package/dist/plugin/tui.js +0 -198
  139. package/dist/plugin/uuid.d.ts +0 -1
  140. package/dist/plugin/uuid.js +0 -50
@@ -1,631 +0,0 @@
1
- import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import { effectivePrePromptCap } from "./ContextInjector.js";
5
- import { hasRepoIdColumn } from "./MemoryService.js";
6
- import { contractDigest, describeContract } from "./contract.js";
7
- import { BUDGETS } from "./perf.js";
8
- const ALL_FALSE_CAPABILITIES = {
9
- skills: false,
10
- references: false,
11
- apiVersion: null,
12
- };
13
- function groupCounts(store, column) {
14
- const rows = store
15
- .prepare(`SELECT ${column} AS k, COUNT(*) AS n FROM memories GROUP BY ${column}`)
16
- .all();
17
- const out = {};
18
- for (const r of rows) {
19
- out[r.k ?? "unknown"] = r.n;
20
- }
21
- return out;
22
- }
23
- function scalar(store, sql, ...params) {
24
- const row = store.prepare(sql).get(...params);
25
- return row.n ?? 0;
26
- }
27
- export function buildAudit(store, metrics, capabilities = ALL_FALSE_CAPABILITIES, projectId, repoId, tuiRoot) {
28
- let partial = false;
29
- // Memories block: `total` + the three GROUP BYs are pre-006 safe; the
30
- // lifecycle counters need the 006 columns and fall back separately.
31
- let memories = {
32
- total: 0,
33
- by_status: {},
34
- by_origin: {},
35
- by_type: {},
36
- ignored: 0,
37
- archived: 0,
38
- with_feedback: 0,
39
- superseded_with_target: 0,
40
- };
41
- try {
42
- memories = {
43
- ...memories,
44
- total: scalar(store, "SELECT COUNT(*) AS n FROM memories"),
45
- by_status: groupCounts(store, "status"),
46
- by_origin: groupCounts(store, "origin"),
47
- by_type: groupCounts(store, "type"),
48
- };
49
- }
50
- catch {
51
- partial = true;
52
- }
53
- try {
54
- memories = {
55
- ...memories,
56
- ignored: scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE ignored = 1"),
57
- archived: scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE status = 'archived'"),
58
- with_feedback: scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE feedback_positive > 0 OR feedback_negative > 0"),
59
- superseded_with_target: scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE status = 'superseded' AND superseded_by IS NOT NULL"),
60
- };
61
- }
62
- catch {
63
- partial = true;
64
- }
65
- // Injections block: grouped rollup over the ledger (same query the
66
- // InjectionLedger.outcomeCounts() runs), rates from the metric cache.
67
- let injections = {
68
- total: 0,
69
- effective: 0,
70
- ineffective: 0,
71
- inconclusive: 0,
72
- unmeasured: 0,
73
- precision_rate: 0,
74
- coverage_rate: 0,
75
- };
76
- try {
77
- const rows = store
78
- .prepare("SELECT outcome, COUNT(*) AS n FROM kevin_injections GROUP BY outcome")
79
- .all();
80
- const counts = {
81
- unmeasured: 0,
82
- effective: 0,
83
- ineffective: 0,
84
- inconclusive: 0,
85
- };
86
- for (const r of rows) {
87
- counts[r.outcome] = r.n;
88
- }
89
- injections = {
90
- total: rows.reduce((acc, r) => acc + r.n, 0),
91
- effective: counts.effective,
92
- ineffective: counts.ineffective,
93
- inconclusive: counts.inconclusive,
94
- unmeasured: counts.unmeasured,
95
- precision_rate: metrics.precisionRate(),
96
- coverage_rate: metrics.coverageRate(),
97
- };
98
- }
99
- catch {
100
- partial = true;
101
- }
102
- // Feedback block: verdict rollup from the table, totals from the cache.
103
- let feedback = {
104
- positive: 0,
105
- negative: 0,
106
- by_verdict: {},
107
- };
108
- try {
109
- const rows = store
110
- .prepare("SELECT verdict, COUNT(*) AS n FROM memory_feedback GROUP BY verdict")
111
- .all();
112
- const byVerdict = {};
113
- for (const r of rows) {
114
- byVerdict[r.verdict] = r.n;
115
- }
116
- feedback = {
117
- positive: metrics.get("feedback_positive_total"),
118
- negative: metrics.get("feedback_negative_total"),
119
- by_verdict: byVerdict,
120
- };
121
- }
122
- catch {
123
- partial = true;
124
- }
125
- const blocked = metrics.blockedSnapshot();
126
- const tokens = {
127
- pre_prompt: metrics.get("tokens_injected_pre_prompt"),
128
- compacting: metrics.get("tokens_injected_compacting"),
129
- };
130
- let settings = {};
131
- try {
132
- const rows = store
133
- .prepare("SELECT key, value FROM kevin_settings")
134
- .all();
135
- settings = {};
136
- for (const r of rows) {
137
- settings[r.key] = r.value;
138
- }
139
- }
140
- catch {
141
- partial = true;
142
- }
143
- // v0.6.0 (K6-023 / plan §5.8) — channels + curation. Both blocks are
144
- // gated on migration 007's schema: on a pre-007 database they are
145
- // OMITTED (not zero-valued) and `partial` is set, so the release's
146
- // scoreboard is never presented as computed when it cannot be.
147
- let channels;
148
- let curation;
149
- let conflicts;
150
- let mix;
151
- let hasSchema007 = false;
152
- try {
153
- store.prepare("SELECT 1 FROM curation_proposals LIMIT 1").get();
154
- hasSchema007 = true;
155
- }
156
- catch {
157
- hasSchema007 = false;
158
- }
159
- if (hasSchema007) {
160
- try {
161
- const emission = (capable, key) => {
162
- if (!capable)
163
- return "unavailable";
164
- return settings[key] === "1" ? "on" : "off";
165
- };
166
- const registered = (key) => {
167
- const row = store
168
- .prepare("SELECT value FROM kevin_metrics WHERE key = ?")
169
- .get(key);
170
- return row?.value ?? 0;
171
- };
172
- channels = {
173
- push: {
174
- tokens_pre_prompt: metrics.get("tokens_injected_pre_prompt"),
175
- tokens_compacting: metrics.get("tokens_injected_compacting"),
176
- injections_total: metrics.get("injections_total"),
177
- precision_rate: metrics.precisionRate(),
178
- coverage_rate: metrics.coverageRate(),
179
- // The EFFECTIVE cap (K6-021 clamp semantics), not the raw
180
- // setting: the channel comparison must use the budget the
181
- // push channel actually charges against.
182
- budget_tokens: effectivePrePromptCap(settings.pre_prompt_budget_tokens),
183
- },
184
- pull: {
185
- proposals_created: metrics.get("proposals_created"),
186
- // "approved" is the human decision: approved + applied.
187
- proposals_approved: metrics.get("proposals_approved"),
188
- proposals_rejected: metrics.get("proposals_rejected"),
189
- artifact_writes_total: metrics.get("artifact_writes_total"),
190
- artifact_writes_noop: metrics.get("artifact_writes_noop"),
191
- references_registered: registered("references_registered"),
192
- skills_registered: registered("skills_registered"),
193
- skill_emission: emission(capabilities.skills, "skill_emission_enabled"),
194
- reference_emission: emission(capabilities.references, "reference_emission_enabled"),
195
- },
196
- };
197
- }
198
- catch {
199
- partial = true;
200
- channels = undefined;
201
- }
202
- try {
203
- const proposalRows = store
204
- .prepare("SELECT status, COUNT(*) AS n FROM curation_proposals GROUP BY status")
205
- .all();
206
- const byStatus = {};
207
- for (const r of proposalRows) {
208
- byStatus[r.status] = r.n;
209
- }
210
- curation = {
211
- // The Curator predicate is `inferable != 1` (plan §5.5): an
212
- // unclassified (NULL) memory must not be silently withheld.
213
- eligible: scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE inferable IS NOT 1"),
214
- curated: scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE curated = 1"),
215
- inferable: scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE inferable = 1"),
216
- non_inferable: scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE inferable = 0"),
217
- unknown: scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE inferable IS NULL"),
218
- proposals_by_status: byStatus,
219
- };
220
- }
221
- catch {
222
- partial = true;
223
- curation = undefined;
224
- }
225
- }
226
- else {
227
- partial = true;
228
- }
229
- // v0.8.0 (K8-018/023 / plan §5.2, §5.7) — the shared-layer block.
230
- // `write_refusals` lives OUTSIDE the frozen METRIC_KEYS ladder (K7-004)
231
- // and is read as a bare SQL scalar, following the `skills_registered`
232
- // precedent; a missing row means no refusals yet. The K8-023 team
233
- // rollup (shared_entries, okf_imports, layer precision) is gated on
234
- // migration 009's schema and is pure SQL — no JavaScript-side
235
- // aggregation (plan §5.7 acceptance).
236
- let team = { write_refusals: 0 };
237
- try {
238
- team = {
239
- write_refusals: scalar(store, `SELECT COALESCE(
240
- (SELECT value FROM kevin_metrics WHERE key = 'shared_write_refusals'),
241
- 0
242
- ) AS n`),
243
- };
244
- }
245
- catch {
246
- partial = true;
247
- team = undefined;
248
- }
249
- let hasSchema009 = false;
250
- try {
251
- store.prepare("SELECT 1 FROM shared_entries LIMIT 1").get();
252
- hasSchema009 = true;
253
- }
254
- catch {
255
- hasSchema009 = false;
256
- }
257
- if (hasSchema009 && team !== undefined) {
258
- try {
259
- const repoWhere = (column) => repoId ? `${column} = ?` : "1 = 1";
260
- const repoParams = repoId ? [repoId] : [];
261
- const lastImport = store
262
- .prepare(`SELECT imported_at, entries_rejected
263
- FROM okf_imports
264
- WHERE ${repoWhere("repo_id")}
265
- ORDER BY imported_at DESC, id DESC LIMIT 1`)
266
- .get(...repoParams);
267
- // v0.5.0 precision formula (effective / (effective +
268
- // ineffective)) per layer, same shape as `mix`.
269
- const layerPrecision = (layer) => {
270
- const row = store
271
- .prepare(`SELECT
272
- SUM(CASE WHEN i.outcome = 'effective' THEN 1 ELSE 0 END) AS effective,
273
- SUM(CASE WHEN i.outcome = 'ineffective' THEN 1 ELSE 0 END) AS ineffective
274
- FROM kevin_injections i JOIN memories m ON m.id = i.memory_id
275
- WHERE m.layer = ? ${repoId ? "AND (m.repo_id = ? OR m.repo_id IS NULL)" : ""}`)
276
- .get(layer, ...(repoId ? [repoId] : []));
277
- const effective = row.effective ?? 0;
278
- const measured = effective + (row.ineffective ?? 0);
279
- return measured > 0 ? effective / measured : 0;
280
- };
281
- // The v0.7.0 maturity floor, verbatim: below it the
282
- // precision numbers are omitted and `immature_db` reported
283
- // (plan §5.7 step 5).
284
- const memoryCount = scalar(store, "SELECT COUNT(*) AS n FROM memories");
285
- const settled = scalar(store, "SELECT COUNT(*) AS n FROM kevin_injections WHERE outcome IN ('effective','ineffective')");
286
- const mature = memoryCount >= 100 && settled >= 50;
287
- team = {
288
- ...team,
289
- shared_total: scalar(store, `SELECT COUNT(*) AS n FROM shared_entries
290
- WHERE ${repoWhere("repo_id")} AND op = 'assert'`, ...repoParams),
291
- tombstones: scalar(store, `SELECT COUNT(*) AS n FROM shared_entries
292
- WHERE ${repoWhere("repo_id")} AND op = 'tombstone'`, ...repoParams),
293
- // Counts distinct non-null author_hash values; with
294
- // author_identity_mode='none' imports write NULL, so
295
- // the count is naturally 0.
296
- distinct_authors: scalar(store, `SELECT COUNT(DISTINCT author_hash) AS n FROM shared_entries
297
- WHERE ${repoWhere("repo_id")} AND author_hash IS NOT NULL`, ...repoParams),
298
- last_import_at: lastImport?.imported_at ?? null,
299
- last_import_rejected: lastImport?.entries_rejected ?? 0,
300
- ...(mature
301
- ? {
302
- precision_shared: layerPrecision("shared"),
303
- precision_local: layerPrecision("local"),
304
- }
305
- : { reason: "immature_db" }),
306
- };
307
- }
308
- catch {
309
- partial = true;
310
- team = undefined;
311
- }
312
- }
313
- try {
314
- const rows = store
315
- .prepare("SELECT kind, status, COUNT(*) AS n FROM memory_conflicts GROUP BY kind, status")
316
- .all();
317
- const byKind = {};
318
- const byStatus = {};
319
- for (const row of rows) {
320
- byKind[row.kind] = (byKind[row.kind] ?? 0) + row.n;
321
- byStatus[row.status] = (byStatus[row.status] ?? 0) + row.n;
322
- }
323
- conflicts = { by_kind: byKind, by_status: byStatus };
324
- }
325
- catch {
326
- partial = true;
327
- }
328
- // v0.7.0 (K7-019 / plan §5.3, K7-006) — the `truth` block is
329
- // project-scoped. On a pre-008 database the repo_facts query throws, so
330
- // the block is omitted and `partial` set — a report that cannot answer
331
- // "how many facts does this project have?" must say so.
332
- // v0.8.0 (K8-007 / plan §5.7) — the penalized-memory rollup scopes on
333
- // repo_id once the 009 column exists and an identity is resolved
334
- // (NULL-repo_id rows are global); project_id stays as the fallback.
335
- let truth;
336
- if (projectId || repoId) {
337
- try {
338
- const truncatedRow = store
339
- .prepare("SELECT value FROM repo_facts WHERE project_id = ? AND key_path = '_truncated'")
340
- .get(projectId);
341
- const penalized = repoId && hasRepoIdColumn(store)
342
- ? scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE (repo_id = ? OR repo_id IS NULL) AND truth_penalty > 0", repoId)
343
- : scalar(store, "SELECT COUNT(*) AS n FROM memories WHERE project_id = ? AND truth_penalty > 0", projectId);
344
- truth = {
345
- facts_scanned: scalar(store, "SELECT COUNT(*) AS n FROM repo_facts WHERE project_id = ?", projectId),
346
- penalized_memories: penalized,
347
- truncated: truncatedRow
348
- ? { is_truncated: true, count: Number(truncatedRow.value) || 0 }
349
- : { is_truncated: false },
350
- };
351
- }
352
- catch {
353
- partial = true;
354
- truth = undefined;
355
- }
356
- }
357
- try {
358
- const typeRows = store
359
- .prepare(`SELECT m.type, COUNT(*) AS n
360
- FROM kevin_injections i JOIN memories m ON m.id = i.memory_id
361
- GROUP BY m.type`)
362
- .all();
363
- const injectedByType = {
364
- error: 0,
365
- rule: 0,
366
- decision: 0,
367
- pattern: 0,
368
- solution: 0,
369
- context: 0,
370
- };
371
- for (const row of typeRows)
372
- injectedByType[row.type] = row.n;
373
- const total = typeRows.reduce((sum, row) => sum + row.n, 0);
374
- const nonError = total - (injectedByType.error ?? 0);
375
- const precisionFor = (isError) => {
376
- const row = store
377
- .prepare(`SELECT
378
- SUM(CASE WHEN i.outcome = 'effective' THEN 1 ELSE 0 END) AS effective,
379
- SUM(CASE WHEN i.outcome = 'ineffective' THEN 1 ELSE 0 END) AS ineffective
380
- FROM kevin_injections i JOIN memories m ON m.id = i.memory_id
381
- WHERE ${isError ? "m.type = 'error'" : "m.type <> 'error'"}`)
382
- .get();
383
- const effective = row.effective ?? 0;
384
- const measured = effective + (row.ineffective ?? 0);
385
- return measured > 0 ? effective / measured : 0;
386
- };
387
- const precisionError = precisionFor(true);
388
- const precisionNonError = precisionFor(false);
389
- const memoryCount = scalar(store, "SELECT COUNT(*) AS n FROM memories");
390
- const settled = scalar(store, "SELECT COUNT(*) AS n FROM kevin_injections WHERE outcome IN ('effective','ineffective')");
391
- const mature = memoryCount >= 100 && settled >= 50;
392
- const meets = mature &&
393
- nonError / Math.max(total, 1) >= 0.5 &&
394
- precisionNonError > precisionError;
395
- mix = {
396
- injected_by_type: injectedByType,
397
- injected_total: total,
398
- non_error_injected: nonError,
399
- non_error_share: total > 0 ? nonError / total : 0,
400
- precision_error: precisionError,
401
- precision_non_error: precisionNonError,
402
- meets_exit_criterion: meets,
403
- ...(mature ? {} : { reason: "immature_db" }),
404
- };
405
- }
406
- catch {
407
- partial = true;
408
- }
409
- // v0.9.0 (K9-020 / plan §5.5) — the host block, mirroring the
410
- // channels/curation precedent: gated on migration 010's schema, pure
411
- // SQL, omitted (not zero-valued) on a pre-010 database with
412
- // `partial` set. The verdict is derived entirely in SQL: degraded
413
- // when any hook is dead, unknown when no hook has been observed at
414
- // all (never rounded to healthy), healthy otherwise.
415
- let host;
416
- let hasSchema010 = false;
417
- try {
418
- const row = store
419
- .prepare("SELECT 1 AS n FROM sqlite_master WHERE type='table' AND name='hook_liveness'")
420
- .get();
421
- hasSchema010 = !!row;
422
- }
423
- catch {
424
- hasSchema010 = false;
425
- }
426
- if (hasSchema010) {
427
- try {
428
- const hookRow = store
429
- .prepare(`SELECT
430
- COUNT(*) AS total,
431
- SUM(CASE WHEN dead_since IS NOT NULL THEN 1 ELSE 0 END) AS dead,
432
- SUM(CASE WHEN fire_count > 0 THEN 1 ELSE 0 END) AS live,
433
- SUM(fire_count) AS fires_total,
434
- SUM(error_count) AS errors_total
435
- FROM hook_liveness`)
436
- .get();
437
- const dead = hookRow.dead ?? 0;
438
- const live = hookRow.live ?? 0;
439
- const unknown = hookRow.total - dead - live;
440
- const versionRow = store
441
- .prepare(`SELECT plugin_version FROM host_probes
442
- ORDER BY probed_at DESC, id DESC LIMIT 1`)
443
- .get();
444
- const regRows = store
445
- .prepare(`SELECT surface, registered, verified, COUNT(*) AS n
446
- FROM native_registrations
447
- GROUP BY surface, registered, verified`)
448
- .all();
449
- const bySurface = {};
450
- let total = 0;
451
- let verified = 0;
452
- let failures = 0;
453
- for (const row of regRows) {
454
- total += row.n;
455
- if (row.verified === 1)
456
- verified += row.n;
457
- if (row.registered === 1 && row.verified === 0)
458
- failures += row.n;
459
- const acc = bySurface[row.surface] ?? { registered: 0, verified: 0 };
460
- acc.registered += row.registered === 1 ? row.n : 0;
461
- acc.verified += row.verified === 1 ? row.n : 0;
462
- bySurface[row.surface] = acc;
463
- }
464
- host = {
465
- plugin_version: versionRow?.plugin_version ?? null,
466
- hooks: {
467
- live,
468
- dead,
469
- unknown: unknown < 0 ? 0 : unknown,
470
- fires_total: hookRow.fires_total ?? 0,
471
- errors_total: hookRow.errors_total ?? 0,
472
- },
473
- native: { total, verified, failures, by_surface: bySurface },
474
- verdict: dead > 0
475
- ? "degraded"
476
- : hookRow.total === 0
477
- ? "unknown"
478
- : unknown > 0
479
- ? "unknown"
480
- : "healthy",
481
- };
482
- }
483
- catch {
484
- partial = true;
485
- host = undefined;
486
- }
487
- }
488
- // v0.9.0 (K9-020): pre-010 databases simply omit `host` without
489
- // marking the report partial — the host block is additive, unlike
490
- // channels/curation which are the v0.6.0 scoreboard. Existing
491
- // v0.8.0-era tests run on 007/008 stores and expect partial false.
492
- // v1.0.0 (K10-020 / plan §5.6) — perf block: the most recent aggregate
493
- // row per scope, read directly from perf_samples. A scope with no rows
494
- // reports count 0, zero timings and within_budget true — never NULL,
495
- // never NaN. Omitted on a pre-011 database (host precedent).
496
- let perf;
497
- let hasSchema011 = false;
498
- try {
499
- const row = store
500
- .prepare("SELECT 1 AS n FROM sqlite_master WHERE type='table' AND name='perf_samples'")
501
- .get();
502
- hasSchema011 = !!row;
503
- }
504
- catch {
505
- hasSchema011 = false;
506
- }
507
- if (hasSchema011) {
508
- try {
509
- const scopes = {};
510
- const latest = store.prepare(`SELECT sample_count AS count, p50_ms AS p50, p95_ms AS p95,
511
- max_ms AS max, budget_p95_ms AS budget_p95, within_budget
512
- FROM perf_samples WHERE scope = ? ORDER BY id DESC LIMIT 1`);
513
- for (const budget of BUDGETS) {
514
- const row = latest.get(budget.scope);
515
- scopes[budget.scope] = row
516
- ? {
517
- count: row.count,
518
- p50: row.p50,
519
- p95: row.p95,
520
- max: row.max,
521
- budget_p95: row.budget_p95,
522
- within_budget: row.within_budget === 1,
523
- }
524
- : {
525
- count: 0,
526
- p50: 0,
527
- p95: 0,
528
- max: 0,
529
- budget_p95: budget.p95Ms,
530
- within_budget: true,
531
- };
532
- }
533
- perf = { scopes };
534
- }
535
- catch {
536
- perf = undefined;
537
- }
538
- }
539
- // v1.0.0 (K10-020 / plan §5.6) — contract block: pure function of the
540
- // source, no database involvement.
541
- const liveContract = describeContract();
542
- const contract = {
543
- contract_version: liveContract.contractVersion,
544
- digest: contractDigest(liveContract),
545
- clause_count: liveContract.clauses.length,
546
- deprecated_count: liveContract.clauses.filter((c) => c.deprecated).length,
547
- };
548
- // v1.2.0 (K12-014/K12-019) — TUI block: best-effort fs introspection (absent → nulls)
549
- let tui;
550
- try {
551
- const enabled_setting = settings.tui_snapshots_enabled ?? "0";
552
- const root = tuiRoot ?? join(homedir(), ".opencode-kevin", "tui");
553
- let last_flush_age_s = null;
554
- try {
555
- const metaRaw = readFileSync(join(root, "meta.json"), "utf8");
556
- const meta = JSON.parse(metaRaw);
557
- if (meta.generatedAt) {
558
- const age = (Date.now() - Date.parse(meta.generatedAt)) / 1000;
559
- if (Number.isFinite(age) && age >= 0)
560
- last_flush_age_s = Math.floor(age);
561
- }
562
- }
563
- catch { }
564
- let mailbox_depth = null;
565
- try {
566
- if (existsSync(join(root, "actions.json"))) {
567
- const raw = readFileSync(join(root, "actions.json"), "utf8");
568
- const parsed = JSON.parse(raw);
569
- if (Array.isArray(parsed.actions))
570
- mailbox_depth = parsed.actions.length;
571
- else
572
- mailbox_depth = null;
573
- }
574
- else {
575
- mailbox_depth = null;
576
- }
577
- }
578
- catch {
579
- mailbox_depth = null;
580
- }
581
- let last_results = null;
582
- try {
583
- const raw = readFileSync(join(root, "results.json"), "utf8");
584
- last_results = JSON.parse(raw);
585
- }
586
- catch {
587
- last_results = null;
588
- }
589
- let dashboard_last_write_age_s = null;
590
- try {
591
- const st = statSync(join(root, "dashboard.html"));
592
- const age = (Date.now() - st.mtimeMs) / 1000;
593
- if (Number.isFinite(age) && age >= 0)
594
- dashboard_last_write_age_s = Math.floor(age);
595
- }
596
- catch {
597
- dashboard_last_write_age_s = null;
598
- }
599
- const bridge_interceptions = metrics.get("tui_actions_invoked") ?? 0;
600
- tui = {
601
- enabled_setting,
602
- last_flush_age_s,
603
- mailbox_depth,
604
- last_results,
605
- dashboard_last_write_age_s,
606
- bridge_interceptions,
607
- };
608
- }
609
- catch {
610
- tui = undefined;
611
- }
612
- return {
613
- memories,
614
- injections,
615
- blocked,
616
- feedback,
617
- tokens,
618
- settings,
619
- conflicts,
620
- truth,
621
- mix,
622
- channels,
623
- curation,
624
- team,
625
- host,
626
- perf,
627
- contract,
628
- tui,
629
- partial,
630
- };
631
- }
@@ -1,10 +0,0 @@
1
- import type { Store } from "./Store.js";
2
- export interface KevinBenchDeps {
3
- store: Store;
4
- /** Working directory used to locate bench/corpus (defaults process.cwd()). */
5
- cwd?: string;
6
- }
7
- export interface KevinBenchArgs {
8
- action: "status" | "last";
9
- }
10
- export declare function buildKevinBench(deps: KevinBenchDeps, args: KevinBenchArgs): Record<string, unknown>;