@jmtrin/opencode-kevin 1.0.0 → 1.1.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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -7
  3. package/dist/migrations/001_initial.sql +91 -91
  4. package/dist/migrations/002_indexes.sql +13 -13
  5. package/dist/migrations/003_v02_signal.sql +57 -57
  6. package/dist/migrations/004_v03_knowledge.sql +138 -138
  7. package/dist/migrations/005_v04_signal.sql +57 -57
  8. package/dist/migrations/006_v05_glassbox.sql +118 -118
  9. package/dist/migrations/007_v06_pull.sql +144 -144
  10. package/dist/migrations/012_v11_drift.sql +24 -0
  11. package/dist/plugin/Archiver.js +2 -17
  12. package/dist/plugin/CausalChain.js +32 -13
  13. package/dist/plugin/ConflictDetector.js +7 -29
  14. package/dist/plugin/Feedback.js +2 -19
  15. package/dist/plugin/HookLiveness.d.ts +1 -0
  16. package/dist/plugin/HookLiveness.js +11 -27
  17. package/dist/plugin/InjectionLedger.js +104 -57
  18. package/dist/plugin/Materializer.js +1 -88
  19. package/dist/plugin/MemoryService.d.ts +59 -1
  20. package/dist/plugin/MemoryService.js +13 -106
  21. package/dist/plugin/Migrate.js +5 -0
  22. package/dist/plugin/Retrospective.js +4 -0
  23. package/dist/plugin/ToolCallObserver.js +18 -5
  24. package/dist/plugin/columns.d.ts +11 -0
  25. package/dist/plugin/columns.js +54 -0
  26. package/dist/plugin/contract.d.ts +8 -0
  27. package/dist/plugin/contract.js +20 -5
  28. package/dist/plugin/index.d.ts +1 -1
  29. package/dist/plugin/index.js +24 -3
  30. package/dist/plugin/kevin_forget.d.ts +33 -0
  31. package/dist/plugin/kevin_forget.js +260 -0
  32. package/dist/plugin/kevin_why.js +1 -18
  33. package/dist/plugin/metrics.d.ts +1 -1
  34. package/dist/plugin/metrics.js +4 -0
  35. package/dist/plugin/query-tokenizer.js +56 -8
  36. package/dist/plugin/time-ms.d.ts +1 -0
  37. package/dist/plugin/time-ms.js +16 -0
  38. package/package.json +2 -1
@@ -0,0 +1,54 @@
1
+ const cache = new WeakMap();
2
+ export function hasColumn(store, table, column) {
3
+ const byStore = cache.get(store);
4
+ const key = `${table}.${column}`;
5
+ if (byStore?.get(key) === true)
6
+ return true;
7
+ try {
8
+ store.prepare(`SELECT ${column} FROM ${table} LIMIT 0`).get();
9
+ let m = byStore;
10
+ if (!m) {
11
+ m = new Map();
12
+ cache.set(store, m);
13
+ }
14
+ m.set(key, true);
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ // v1.1.0 (K11-011 / plan §5.5, D11-06) — named helpers delegating to the registry
22
+ export function hasIgnoredColumn(store) {
23
+ return hasColumn(store, "memories", "ignored");
24
+ }
25
+ export function hasCuratedColumn(store) {
26
+ return hasColumn(store, "memories", "curated");
27
+ }
28
+ export function hasTruthColumns(store) {
29
+ return hasColumn(store, "memories", "truth_penalty");
30
+ }
31
+ export function hasRepoIdColumn(store) {
32
+ return hasColumn(store, "memories", "repo_id");
33
+ }
34
+ export function hasLayerColumn(store) {
35
+ return hasColumn(store, "memories", "layer");
36
+ }
37
+ export function hasRecurrenceColumn(store) {
38
+ return hasColumn(store, "memories", "recurrence_count");
39
+ }
40
+ export function hasArchivedColumn(store) {
41
+ return hasColumn(store, "memories", "archived_at");
42
+ }
43
+ export function hasFeedbackColumns(store) {
44
+ return hasColumn(store, "memories", "feedback_positive");
45
+ }
46
+ export function hasFeedbackTable(store) {
47
+ try {
48
+ store.prepare("SELECT COUNT(*) FROM memory_feedback LIMIT 0").get();
49
+ return true;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
@@ -28,6 +28,14 @@ export declare const CONTRACT_TOOL_ADDITIONS: readonly {
28
28
  name: string;
29
29
  since: string;
30
30
  }[];
31
+ /**
32
+ * v1.1.0 (K11-007 / plan §5.1, D11-01) — metric keys added after the freeze,
33
+ * each carrying the `since` the deprecation policy requires (C-05).
34
+ */
35
+ export declare const CONTRACT_METRIC_ADDITIONS: readonly {
36
+ name: string;
37
+ since: string;
38
+ }[];
31
39
  /**
32
40
  * v1.0.0 (K10-027 / plan §5.7) — the C-09 boundary addition. Stored is
33
41
  * not trusted: anything reaching an artifact or a prompt is escaped at
@@ -41,8 +41,18 @@ export const CONTRACT_TOOL_NAMES = [
41
41
  * added_bare.
42
42
  */
43
43
  export const CONTRACT_TOOL_ADDITIONS = [
44
- { name: "kevin_contract", since: "1.0.0" },
45
44
  { name: "kevin_bench", since: "1.0.0" },
45
+ { name: "kevin_contract", since: "1.0.0" },
46
+ { name: "kevin_forget", since: "1.1.0" },
47
+ ];
48
+ /**
49
+ * v1.1.0 (K11-007 / plan §5.1, D11-01) — metric keys added after the freeze,
50
+ * each carrying the `since` the deprecation policy requires (C-05).
51
+ */
52
+ export const CONTRACT_METRIC_ADDITIONS = [
53
+ { name: "bench_regression_failures", since: "1.1.0" },
54
+ { name: "forget_requests_total", since: "1.1.0" },
55
+ { name: "forget_tombstones_published", since: "1.1.0" },
46
56
  ];
47
57
  /**
48
58
  * v1.0.0 (K10-027 / plan §5.7) — the C-09 boundary addition. Stored is
@@ -76,12 +86,17 @@ export function describeContract(_input) {
76
86
  // Derive clause values from live source wherever possible (plan §5.1).
77
87
  // C-03 members added after the freeze carry their `since` as objects;
78
88
  // the original frozen set stays plain strings.
79
- const additions = [...CONTRACT_TOOL_ADDITIONS].sort((a, b) => a.name.localeCompare(b.name));
89
+ const toolAdditions = [...CONTRACT_TOOL_ADDITIONS].sort((a, b) => a.name.localeCompare(b.name));
80
90
  const toolValue = {
81
- tools: [[...CONTRACT_TOOL_NAMES].sort(), additions].flat(),
91
+ tools: [[...CONTRACT_TOOL_NAMES].sort(), toolAdditions].flat(),
82
92
  };
83
93
  const settingValue = { keys: [...KEVIN_CONFIG_KEYS].sort() };
84
- const metricValue = { keys: Object.keys(METRIC_KEY_LABELS).sort() };
94
+ // v1.1.0 metric keys added after freeze carry `since` (C-05)
95
+ const metricAdditions = [...CONTRACT_METRIC_ADDITIONS].sort((a, b) => a.name.localeCompare(b.name));
96
+ const baseMetricKeys = Object.keys(METRIC_KEY_LABELS)
97
+ .filter((k) => !metricAdditions.some((a) => a.name === k))
98
+ .sort();
99
+ const metricValue = { keys: [...baseMetricKeys, ...metricAdditions].flat() };
85
100
  const clauses = [
86
101
  {
87
102
  id: "C-01",
@@ -149,7 +164,7 @@ export function describeContract(_input) {
149
164
  stability: "forward-only",
150
165
  since: "0.1.0",
151
166
  value: {
152
- schema_version: "011",
167
+ schema_version: "012",
153
168
  migrations_forward_only: true,
154
169
  },
155
170
  },
@@ -19,7 +19,7 @@ export interface KevinPluginOptions {
19
19
  export declare const KEVIN_CONFIG_KEYS: readonly ["quality_gate_enabled", "lesson_snippet_injection", "patternminer_enabled", "cross_project_enabled", "llm_reflection_enabled", "tool_calls_dedup_enabled", "deterministic_retrieval", "pre_prompt_budget_tokens", "archive_after_days", "curation_enabled", "agents_md_path", "skill_emission_enabled", "reference_emission_enabled", "injection_confidence_floor", "repo_truth_enabled", "convention_mining_enabled", "conflict_detection_enabled", "error_lesson_mode", "shared_layer_enabled", "okf_path", "share_requires_approval", "author_identity_mode", "shared_confidence_floor", "hook_liveness_enabled", "native_registration_enabled", "host_probe_history_enabled", "dead_hook_report_threshold", "perf_enabled", "perf_ring_capacity", "perf_flush_on_idle", "contract_report_enabled"];
20
20
  export declare const ERROR_LESSON_MODE_VALUES: readonly ["all", "triage_only"];
21
21
  /** Plugin release version — stamped into generated files (K8-021/027). */
22
- export declare const KEVIN_VERSION = "1.0.0";
22
+ export declare const KEVIN_VERSION = "1.1.0";
23
23
  export interface RekeyCounts {
24
24
  memories: number;
25
25
  shared_entries: number;
@@ -34,6 +34,7 @@ import { executeKevinConflicts } from "./kevin_conflicts.js";
34
34
  import { buildKevinContract } from "./kevin_contract.js";
35
35
  import { buildDoctor } from "./kevin_doctor.js";
36
36
  import { buildKevinFacts } from "./kevin_facts.js";
37
+ import { handleForget } from "./kevin_forget.js";
37
38
  import { handleNative } from "./kevin_native.js";
38
39
  import { kevinPropose } from "./kevin_propose.js";
39
40
  import { kevinPublish } from "./kevin_publish.js";
@@ -112,6 +113,7 @@ export const KEVIN_CONFIG_KEYS = [
112
113
  "perf_flush_on_idle",
113
114
  "contract_report_enabled",
114
115
  ];
116
+ // v1.1.0 (K11-007 / D11-08) — no new settings in 1.1.0; thresholds are constants (D11-03)
115
117
  // v0.7.0 (K7-003 / plan §5.6, D7-12) — the explicit VALUE domain for
116
118
  // `error_lesson_mode`. The setting is TEXT and must be compared with
117
119
  // `=== "triage_only"`, never by truthiness; the domain here is enforced by
@@ -120,7 +122,7 @@ export const KEVIN_CONFIG_KEYS = [
120
122
  // behaviour on the next reflection.
121
123
  export const ERROR_LESSON_MODE_VALUES = ["all", "triage_only"];
122
124
  /** Plugin release version — stamped into generated files (K8-021/027). */
123
- export const KEVIN_VERSION = "1.0.0";
125
+ export const KEVIN_VERSION = "1.1.0";
124
126
  function resolveMigrationsDir() {
125
127
  const here = dirname(fileURLToPath(import.meta.url));
126
128
  return join(here, "..", "migrations");
@@ -870,8 +872,9 @@ export const KevinPlugin = async (input, options) => {
870
872
  // monotone across releases; K6-024 extends this
871
873
  // block with the remaining v0.6 fields. v0.8.0
872
874
  // (K8-025) — 18 v0.7 + kevin_project +
873
- // kevin_native = 23.
874
- tool_count: 23,
875
+ // kevin_native = 23. v1.0.0 (K10-018) — +kevin_contract +kevin_bench = 25.
876
+ // v1.1.0 (K11-007) — +kevin_forget = 26.
877
+ tool_count: 26,
875
878
  v07,
876
879
  memories_reflector: memoriesReflector,
877
880
  memories_agent: memoriesAgent,
@@ -1461,6 +1464,24 @@ export const KevinPlugin = async (input, options) => {
1461
1464
  };
1462
1465
  },
1463
1466
  }),
1467
+ // v1.1.0 (K11-007 / plan §5.1, D11-02) — kevin_forget: closes the sharing lifecycle.
1468
+ kevin_forget: tool({
1469
+ description: "Olvida memorias y publica tombstones en la capa compartida (v1.1.0, K11-005/006 / plan §5.1): dry-run por defecto — sin confirm no muta nada y devuelve el plan (archived, tombstone planned); con confirm:true archiva localmente (status='archived') y, cuando la memoria proyecta a la capa compartida (layer='shared' o shared_entry_id), publica un tombstone via el unico write path (SharedLayer.applyExport, D8-08). Segunda invocacion identica reporta noop.",
1470
+ args: {
1471
+ ids: tool.schema.array(tool.schema.string()).min(1),
1472
+ confirm: tool.schema.boolean().optional(),
1473
+ },
1474
+ async execute(args) {
1475
+ const okfPath = join(projectRoot, memoryService.getSetting("okf_path", ".kevin/knowledge.okf"));
1476
+ const result = handleForget({ ids: args.ids, confirm: args.confirm }, { store, memoryService, sharedLayer, okfPath, metrics });
1477
+ return {
1478
+ title: result.dry_run
1479
+ ? "Plan de olvido (dry-run)"
1480
+ : "Olvido aplicado",
1481
+ output: JSON.stringify(result),
1482
+ };
1483
+ },
1484
+ }),
1464
1485
  kevin_approve: tool({
1465
1486
  description: "Aprueba o rechaza una propuesta de curacion (v0.6.0). reject: marca rejected, nada toca disco. approve: aplica el diff (unico call site de ArtifactWriter.apply, D6-01), marca applied y cura las memorias contribuyentes. Solo acepta propuestas pending.",
1466
1487
  args: {
@@ -0,0 +1,33 @@
1
+ import type { MemoryService } from "./MemoryService.js";
2
+ import type { SharedLayer } from "./SharedLayer.js";
3
+ import type { Store } from "./Store.js";
4
+ import type { Metrics } from "./metrics.js";
5
+ export interface ForgetInput {
6
+ ids: string[];
7
+ confirm?: boolean;
8
+ }
9
+ export interface Deps {
10
+ store: Store;
11
+ memoryService: MemoryService;
12
+ sharedLayer: SharedLayer;
13
+ okfPath: string;
14
+ metrics: Metrics;
15
+ }
16
+ export interface ForgetResult {
17
+ action: "forget";
18
+ ok: boolean;
19
+ dry_run: boolean;
20
+ per_id: Array<{
21
+ id: string;
22
+ archived: boolean;
23
+ reason?: string;
24
+ tombstone?: {
25
+ entry_id: string;
26
+ planned: boolean;
27
+ applied: boolean;
28
+ };
29
+ }>;
30
+ noop?: boolean;
31
+ reason?: string;
32
+ }
33
+ export declare function handleForget(input: ForgetInput, deps: Deps): ForgetResult;
@@ -0,0 +1,260 @@
1
+ import { computeEntryId } from "./okf.js";
2
+ export function handleForget(input, deps) {
3
+ // v1.1.0 — every invocation counts, including dry runs and refusals (K11-005)
4
+ try {
5
+ deps.metrics.incr("forget_requests_total", 1);
6
+ }
7
+ catch {
8
+ // best-effort
9
+ }
10
+ if (!input.ids || input.ids.length === 0) {
11
+ return {
12
+ action: "forget",
13
+ ok: false,
14
+ dry_run: input.confirm !== true,
15
+ per_id: [],
16
+ reason: "no_ids",
17
+ };
18
+ }
19
+ const isDry = input.confirm !== true;
20
+ const per_id = [];
21
+ let anyChange = false;
22
+ let anyTombstonePlanned = false;
23
+ // Helper to compute entry_id for a memory row
24
+ const getEntryId = (row) => {
25
+ if (row.shared_entry_id)
26
+ return row.shared_entry_id;
27
+ return computeEntryId(row.type, row.content, row.scope);
28
+ };
29
+ // For dry_run: just plan, mutate nothing
30
+ if (isDry) {
31
+ for (const id of input.ids) {
32
+ const row = deps.store
33
+ .prepare("SELECT id, type, content, scope, status, layer, shared_entry_id FROM memories WHERE id = ?")
34
+ .get(id);
35
+ if (!row) {
36
+ per_id.push({ id, archived: false, reason: "not_found" });
37
+ continue;
38
+ }
39
+ if (row.status === "archived") {
40
+ // Idempotence: already archived
41
+ const isShared = row.layer === "shared" || row.shared_entry_id !== null;
42
+ if (isShared) {
43
+ const entryId = getEntryId(row);
44
+ try {
45
+ const plan = deps.sharedLayer.planTombstone([entryId], deps.okfPath);
46
+ const planned = plan.write.outcome !== "refused" && plan.entriesAdded > 0;
47
+ // Already archived implies tombstone already applied, so planned should be false (noop)
48
+ per_id.push({
49
+ id,
50
+ archived: false,
51
+ reason: "already_archived",
52
+ tombstone: { entry_id: entryId, planned: false, applied: false },
53
+ });
54
+ void plan;
55
+ void planned;
56
+ }
57
+ catch {
58
+ per_id.push({
59
+ id,
60
+ archived: false,
61
+ reason: "already_archived",
62
+ tombstone: { entry_id: entryId, planned: false, applied: false },
63
+ });
64
+ }
65
+ }
66
+ else {
67
+ per_id.push({ id, archived: false, reason: "already_archived" });
68
+ }
69
+ continue;
70
+ }
71
+ // Would archive
72
+ anyChange = true;
73
+ const isShared = row.layer === "shared" || row.shared_entry_id !== null;
74
+ if (isShared) {
75
+ const entryId = getEntryId(row);
76
+ let planned = false;
77
+ try {
78
+ const plan = deps.sharedLayer.planTombstone([entryId], deps.okfPath);
79
+ // planTombstone returns ExportPlan with write outcome; if refused, planned false
80
+ planned = plan.write.outcome !== "refused";
81
+ anyTombstonePlanned = anyTombstonePlanned || planned;
82
+ per_id.push({
83
+ id,
84
+ archived: true,
85
+ tombstone: { entry_id: entryId, planned, applied: false },
86
+ });
87
+ }
88
+ catch {
89
+ per_id.push({
90
+ id,
91
+ archived: true,
92
+ tombstone: { entry_id: entryId, planned: false, applied: false },
93
+ });
94
+ }
95
+ }
96
+ else {
97
+ per_id.push({ id, archived: true });
98
+ }
99
+ }
100
+ const noop = !anyChange;
101
+ return {
102
+ action: "forget",
103
+ ok: true,
104
+ dry_run: true,
105
+ per_id,
106
+ ...(noop ? { noop: true } : {}),
107
+ };
108
+ }
109
+ // Apply mode: confirm === true
110
+ // We need to archive locally and publish tombstones through applyExport
111
+ let partial = false;
112
+ let appliedTombstones = 0;
113
+ for (const id of input.ids) {
114
+ const row = deps.store
115
+ .prepare("SELECT id, type, content, scope, status, layer, shared_entry_id FROM memories WHERE id = ?")
116
+ .get(id);
117
+ if (!row) {
118
+ per_id.push({ id, archived: false, reason: "not_found" });
119
+ continue;
120
+ }
121
+ if (row.status === "archived") {
122
+ const isShared = row.layer === "shared" || row.shared_entry_id !== null;
123
+ if (isShared) {
124
+ const entryId = getEntryId(row);
125
+ per_id.push({
126
+ id,
127
+ archived: false,
128
+ reason: "already_archived",
129
+ tombstone: { entry_id: entryId, planned: false, applied: false },
130
+ });
131
+ }
132
+ else {
133
+ per_id.push({ id, archived: false, reason: "already_archived" });
134
+ }
135
+ continue;
136
+ }
137
+ // Archive locally in a transaction
138
+ let archived = false;
139
+ try {
140
+ deps.store.transaction(() => {
141
+ deps.store
142
+ .prepare(`UPDATE memories SET status = 'archived', archived_at = datetime('now'), updated_at = datetime('now') WHERE id = ? AND status != 'archived'`)
143
+ .run(id);
144
+ const changes = deps.store.prepare("SELECT changes() AS c").get();
145
+ if (changes.c > 0)
146
+ archived = true;
147
+ });
148
+ }
149
+ catch (e) {
150
+ per_id.push({ id, archived: false, reason: "db_error" });
151
+ partial = true;
152
+ continue;
153
+ }
154
+ anyChange = anyChange || archived;
155
+ const isShared = row.layer === "shared" || row.shared_entry_id !== null;
156
+ if (!isShared) {
157
+ per_id.push({ id, archived });
158
+ continue;
159
+ }
160
+ const entryId = getEntryId(row);
161
+ // Plan tombstone
162
+ let plan;
163
+ try {
164
+ plan = deps.sharedLayer.planTombstone([entryId], deps.okfPath);
165
+ }
166
+ catch (e) {
167
+ // plan failure: rollback DB archive for this id (best-effort)
168
+ try {
169
+ deps.store
170
+ .prepare(`UPDATE memories SET status = 'active', archived_at = NULL WHERE id = ?`)
171
+ .run(id);
172
+ }
173
+ catch { }
174
+ per_id.push({
175
+ id,
176
+ archived: false,
177
+ reason: "plan_failed",
178
+ tombstone: { entry_id: entryId, planned: false, applied: false },
179
+ });
180
+ partial = true;
181
+ continue;
182
+ }
183
+ if (plan.write.outcome === "refused") {
184
+ // Refusal reasons are reused verbatim (repo_mismatch, unknown_entry)
185
+ const reason = plan.write.reason ?? "refused";
186
+ per_id.push({
187
+ id,
188
+ archived,
189
+ reason,
190
+ tombstone: { entry_id: entryId, planned: false, applied: false },
191
+ });
192
+ // DB archive already done; keep it (local archive is independent of shared refusal)
193
+ continue;
194
+ }
195
+ // Apply through single write funnel
196
+ try {
197
+ const applied = deps.sharedLayer.applyExport(plan);
198
+ const wasWritten = applied.applied === "written";
199
+ const wasNoop = applied.applied === "noop";
200
+ if (wasWritten)
201
+ appliedTombstones++;
202
+ // per_id tombstone: planned true if not refused, applied true only if written
203
+ per_id.push({
204
+ id,
205
+ archived,
206
+ tombstone: {
207
+ entry_id: entryId,
208
+ planned: true,
209
+ applied: wasWritten,
210
+ },
211
+ });
212
+ void wasNoop;
213
+ }
214
+ catch (e) {
215
+ // v1.1.0 — failure mid-way: transaction rollback restores DB; already-applied OKF write is reported honestly
216
+ // For this id, DB was already archived, but file write failed. We attempt to rollback DB for this id.
217
+ try {
218
+ deps.store
219
+ .prepare(`UPDATE memories SET status = 'active', archived_at = NULL WHERE id = ?`)
220
+ .run(id);
221
+ archived = false;
222
+ }
223
+ catch { }
224
+ per_id.push({
225
+ id,
226
+ archived: false,
227
+ reason: "partial",
228
+ tombstone: { entry_id: entryId, planned: true, applied: false },
229
+ });
230
+ partial = true;
231
+ // Continue to next id? According spec, failure mid-way reports ok:false reason partial
232
+ // We keep processing remaining ids? For now we continue but mark partial.
233
+ }
234
+ }
235
+ // Metrics: increment forget_tombstones_published by applied count (only when written, not noop)
236
+ if (appliedTombstones > 0) {
237
+ try {
238
+ deps.metrics.incr("forget_tombstones_published", appliedTombstones);
239
+ }
240
+ catch { }
241
+ }
242
+ const noop = !anyChange && per_id.every((p) => p.archived === false);
243
+ if (partial) {
244
+ return {
245
+ action: "forget",
246
+ ok: false,
247
+ dry_run: false,
248
+ per_id,
249
+ reason: "partial",
250
+ ...(noop ? { noop: true } : {}),
251
+ };
252
+ }
253
+ return {
254
+ action: "forget",
255
+ ok: true,
256
+ dry_run: false,
257
+ per_id,
258
+ ...(noop ? { noop: true } : {}),
259
+ };
260
+ }
@@ -1,24 +1,7 @@
1
1
  import { TS_CODE_RULES } from "./Reflector.js";
2
+ import { hasFeedbackColumns } from "./columns.js";
2
3
  import { computeConfidence } from "./confidence.js";
3
4
  import { toMatchClause, tokenizeQuery } from "./query-tokenizer.js";
4
- // v0.5.0 (K5-010 / plan §5.3) — pre-006 DBs lack the feedback columns;
5
- // kevin_why must not reference them (it degrades via the try/catch below,
6
- // which is for missing ROWS, not missing COLUMNS). Probe once per store.
7
- const feedbackColumnCache = new WeakMap();
8
- function hasFeedbackColumns(store) {
9
- const cached = feedbackColumnCache.get(store);
10
- if (cached !== undefined)
11
- return cached;
12
- try {
13
- store.prepare("SELECT feedback_positive FROM memories LIMIT 1").get();
14
- feedbackColumnCache.set(store, true);
15
- return true;
16
- }
17
- catch {
18
- feedbackColumnCache.set(store, false);
19
- return false;
20
- }
21
- }
22
5
  export function kevinWhy(store, query) {
23
6
  // v0.3.0 fix (bug #7) — the old code wrapped the WHOLE query in a
24
7
  // single quoted phrase, so it only matched the exact full-string
@@ -6,7 +6,7 @@ import type { Store } from "./Store.js";
6
6
  * underlying table is empty (e.g., before 003 is applied, on a fresh
7
7
  * :memory: test DB, or after a manual wipe).
8
8
  */
9
- export declare const METRIC_KEYS: readonly ["tokens_injected_pre_prompt", "tokens_injected_compacting", "reflections_throttled", "duplicate_suppressions", "tool_calls_deduped", "patterns_mined", "patterns_causal", "causal_links", "memories_superseded", "injections_total", "injections_effective", "injections_ineffective", "patterns_promoted_new", "injections_inconclusive", "injections_blocked_seen", "injections_blocked_weak", "injections_blocked_recurrence", "injections_blocked_stale", "injections_blocked_ignored", "feedback_positive_total", "feedback_negative_total", "memories_archived", "proposals_created", "proposals_approved", "proposals_rejected", "artifact_writes_total", "artifact_writes_noop", "injections_blocked_confidence", "repo_facts_scanned", "memories_contradicted", "conventions_mined", "conflicts_detected", "error_lessons_suppressed", "shared_entries_total", "shared_entries_imported", "shared_entries_exported", "okf_merge_folds", "rekey_events", "injections_from_shared"];
9
+ export declare const METRIC_KEYS: readonly ["tokens_injected_pre_prompt", "tokens_injected_compacting", "reflections_throttled", "duplicate_suppressions", "tool_calls_deduped", "patterns_mined", "patterns_causal", "causal_links", "memories_superseded", "injections_total", "injections_effective", "injections_ineffective", "patterns_promoted_new", "injections_inconclusive", "injections_blocked_seen", "injections_blocked_weak", "injections_blocked_recurrence", "injections_blocked_stale", "injections_blocked_ignored", "feedback_positive_total", "feedback_negative_total", "memories_archived", "proposals_created", "proposals_approved", "proposals_rejected", "artifact_writes_total", "artifact_writes_noop", "injections_blocked_confidence", "repo_facts_scanned", "memories_contradicted", "conventions_mined", "conflicts_detected", "error_lessons_suppressed", "shared_entries_total", "shared_entries_imported", "shared_entries_exported", "okf_merge_folds", "rekey_events", "injections_from_shared", "bench_regression_failures", "forget_requests_total", "forget_tombstones_published"];
10
10
  export type MetricKey = (typeof METRIC_KEYS)[number];
11
11
  /**
12
12
  * Cheap token estimate used when bumping the `tokens_injected_*` counters.
@@ -57,6 +57,10 @@ export const METRIC_KEYS = [
57
57
  "okf_merge_folds",
58
58
  "rekey_events",
59
59
  "injections_from_shared",
60
+ // v1.1.0 (K11-001 / plan §4, D11-01) — drift metrics; order matches 012 seed.
61
+ "bench_regression_failures",
62
+ "forget_requests_total",
63
+ "forget_tombstones_published",
60
64
  ];
61
65
  const DEFAULT_FLUSH_MS = 1000;
62
66
  function zeroCache() {
@@ -4,68 +4,116 @@
4
4
  * Injection recall ORs the quoted tokens (`"t1" OR "t2"`) while
5
5
  * `kevin_why` ANDs them (`"t1" AND "t2"`).
6
6
  */
7
+ // v1.1.0 (K11-012 / plan §5.5, D11-05) — single source for STOP_WORDS (union of three lists)
7
8
  export const STOP_WORDS = new Set([
8
9
  "a",
10
+ "about",
11
+ "after",
12
+ "again",
13
+ "all",
14
+ "also",
9
15
  "an",
10
16
  "and",
17
+ "any",
11
18
  "are",
19
+ "as",
12
20
  "at",
13
21
  "be",
14
22
  "been",
23
+ "before",
24
+ "being",
15
25
  "but",
16
26
  "by",
27
+ "can",
28
+ "como",
29
+ "con",
30
+ "could",
31
+ "de",
17
32
  "did",
18
33
  "do",
19
34
  "does",
20
35
  "el",
36
+ "en",
21
37
  "eso",
22
38
  "for",
39
+ "from",
40
+ "had",
41
+ "has",
42
+ "have",
43
+ "he",
44
+ "her",
45
+ "his",
23
46
  "how",
24
47
  "i",
25
48
  "if",
26
49
  "in",
50
+ "into",
27
51
  "is",
28
52
  "it",
53
+ "its",
29
54
  "la",
30
55
  "las",
31
56
  "los",
57
+ "may",
32
58
  "mi",
59
+ "might",
60
+ "more",
61
+ "most",
62
+ "must",
33
63
  "my",
64
+ "not",
34
65
  "o",
35
66
  "of",
36
67
  "on",
68
+ "one",
37
69
  "or",
70
+ "our",
38
71
  "para",
72
+ "per",
39
73
  "por",
40
74
  "que",
75
+ "shall",
41
76
  "she",
77
+ "sin",
78
+ "so",
42
79
  "su",
80
+ "than",
43
81
  "that",
44
82
  "the",
83
+ "their",
84
+ "them",
85
+ "then",
86
+ "there",
87
+ "these",
88
+ "they",
45
89
  "this",
90
+ "those",
91
+ "through",
46
92
  "to",
93
+ "too",
47
94
  "tu",
48
95
  "un",
49
96
  "una",
97
+ "under",
98
+ "up",
99
+ "us",
100
+ "via",
101
+ "was",
50
102
  "we",
51
103
  "were",
52
104
  "what",
53
105
  "when",
54
106
  "where",
55
107
  "which",
108
+ "while",
56
109
  "who",
57
110
  "why",
111
+ "will",
58
112
  "with",
113
+ "would",
59
114
  "y",
60
115
  "you",
61
- "como",
62
- "con",
63
- "de",
64
- "en",
65
- "he",
66
- "they",
67
- "was",
68
- "sin",
116
+ "your",
69
117
  ]);
70
118
  /** Lowercase, split on whitespace, drop stopwords. Returns raw tokens. */
71
119
  export function tokenizeQuery(query) {
@@ -0,0 +1 @@
1
+ export declare function toMs(legacyValue: string | null | undefined, msValue: number | null | undefined): number | null;