@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.
- package/README.md +84 -22
- package/dist/plugin/index.d.ts +5 -4
- package/dist/plugin/index.js +127 -113
- package/dist/plugin/native.d.ts +2 -2
- package/dist/plugin/native.js +2 -2
- package/package.json +56 -68
- package/dist/migrations/001_initial.sql +0 -92
- package/dist/migrations/002_indexes.sql +0 -14
- package/dist/migrations/003_v02_signal.sql +0 -58
- package/dist/migrations/004_v03_knowledge.sql +0 -138
- package/dist/migrations/005_v04_signal.sql +0 -57
- package/dist/migrations/006_v05_glassbox.sql +0 -118
- package/dist/migrations/007_v06_pull.sql +0 -145
- package/dist/migrations/008_v07_truth.sql +0 -124
- package/dist/migrations/009_v08_team.sql +0 -100
- package/dist/migrations/010_v09_native.sql +0 -78
- package/dist/migrations/011_v10_proven.sql +0 -69
- package/dist/migrations/012_v11_drift.sql +0 -24
- package/dist/plugin/Archiver.d.ts +0 -42
- package/dist/plugin/Archiver.js +0 -83
- package/dist/plugin/ArtifactWriter.d.ts +0 -93
- package/dist/plugin/ArtifactWriter.js +0 -291
- package/dist/plugin/CausalChain.d.ts +0 -22
- package/dist/plugin/CausalChain.js +0 -197
- package/dist/plugin/ChatBridge.d.ts +0 -41
- package/dist/plugin/ChatBridge.js +0 -103
- package/dist/plugin/ConflictDetector.d.ts +0 -35
- package/dist/plugin/ConflictDetector.js +0 -260
- package/dist/plugin/ContextInjector.d.ts +0 -222
- package/dist/plugin/ContextInjector.js +0 -533
- package/dist/plugin/ConventionMiner.d.ts +0 -35
- package/dist/plugin/ConventionMiner.js +0 -242
- package/dist/plugin/Curator.d.ts +0 -116
- package/dist/plugin/Curator.js +0 -324
- package/dist/plugin/DashboardHtml.d.ts +0 -5
- package/dist/plugin/DashboardHtml.js +0 -180
- package/dist/plugin/Feedback.d.ts +0 -67
- package/dist/plugin/Feedback.js +0 -120
- package/dist/plugin/HookLiveness.d.ts +0 -88
- package/dist/plugin/HookLiveness.js +0 -322
- package/dist/plugin/InjectionLedger.d.ts +0 -98
- package/dist/plugin/InjectionLedger.js +0 -295
- package/dist/plugin/LessonFixer.d.ts +0 -44
- package/dist/plugin/LessonFixer.js +0 -45
- package/dist/plugin/Materializer.d.ts +0 -83
- package/dist/plugin/Materializer.js +0 -181
- package/dist/plugin/MemoryService.d.ts +0 -360
- package/dist/plugin/MemoryService.js +0 -1242
- package/dist/plugin/Migrate.d.ts +0 -17
- package/dist/plugin/Migrate.js +0 -271
- package/dist/plugin/PatternMiner.d.ts +0 -49
- package/dist/plugin/PatternMiner.js +0 -132
- package/dist/plugin/QualityGate.d.ts +0 -124
- package/dist/plugin/QualityGate.js +0 -119
- package/dist/plugin/Reflector.d.ts +0 -91
- package/dist/plugin/Reflector.js +0 -307
- package/dist/plugin/RepoIdentity.d.ts +0 -124
- package/dist/plugin/RepoIdentity.js +0 -300
- package/dist/plugin/RepoTruth.d.ts +0 -80
- package/dist/plugin/RepoTruth.js +0 -599
- package/dist/plugin/Retrospective.d.ts +0 -17
- package/dist/plugin/Retrospective.js +0 -216
- package/dist/plugin/SharedLayer.d.ts +0 -159
- package/dist/plugin/SharedLayer.js +0 -462
- package/dist/plugin/Store.d.ts +0 -29
- package/dist/plugin/Store.js +0 -50
- package/dist/plugin/ToolCallObserver.d.ts +0 -32
- package/dist/plugin/ToolCallObserver.js +0 -201
- package/dist/plugin/TuiActions.d.ts +0 -43
- package/dist/plugin/TuiActions.js +0 -181
- package/dist/plugin/TuiSnapshots.d.ts +0 -24
- package/dist/plugin/TuiSnapshots.js +0 -158
- package/dist/plugin/columns.d.ts +0 -11
- package/dist/plugin/columns.js +0 -54
- package/dist/plugin/confidence.d.ts +0 -8
- package/dist/plugin/confidence.js +0 -34
- package/dist/plugin/contract.d.ts +0 -54
- package/dist/plugin/contract.js +0 -386
- package/dist/plugin/diff.d.ts +0 -8
- package/dist/plugin/diff.js +0 -182
- package/dist/plugin/escape.d.ts +0 -39
- package/dist/plugin/escape.js +0 -60
- package/dist/plugin/fingerprint.d.ts +0 -27
- package/dist/plugin/fingerprint.js +0 -73
- package/dist/plugin/inferability.d.ts +0 -32
- package/dist/plugin/inferability.js +0 -88
- package/dist/plugin/kevin_approve.d.ts +0 -34
- package/dist/plugin/kevin_approve.js +0 -52
- package/dist/plugin/kevin_audit.d.ts +0 -229
- package/dist/plugin/kevin_audit.js +0 -631
- package/dist/plugin/kevin_bench.d.ts +0 -10
- package/dist/plugin/kevin_bench.js +0 -82
- package/dist/plugin/kevin_conflicts.d.ts +0 -9
- package/dist/plugin/kevin_conflicts.js +0 -50
- package/dist/plugin/kevin_contract.d.ts +0 -8
- package/dist/plugin/kevin_contract.js +0 -59
- package/dist/plugin/kevin_doctor.d.ts +0 -62
- package/dist/plugin/kevin_doctor.js +0 -196
- package/dist/plugin/kevin_facts.d.ts +0 -42
- package/dist/plugin/kevin_facts.js +0 -36
- package/dist/plugin/kevin_forget.d.ts +0 -33
- package/dist/plugin/kevin_forget.js +0 -260
- package/dist/plugin/kevin_native.d.ts +0 -29
- package/dist/plugin/kevin_native.js +0 -79
- package/dist/plugin/kevin_propose.d.ts +0 -23
- package/dist/plugin/kevin_propose.js +0 -14
- package/dist/plugin/kevin_publish.d.ts +0 -38
- package/dist/plugin/kevin_publish.js +0 -18
- package/dist/plugin/kevin_why.d.ts +0 -23
- package/dist/plugin/kevin_why.js +0 -111
- package/dist/plugin/memory-format.d.ts +0 -20
- package/dist/plugin/memory-format.js +0 -59
- package/dist/plugin/metrics.d.ts +0 -97
- package/dist/plugin/metrics.js +0 -288
- package/dist/plugin/okf-export.d.ts +0 -3
- package/dist/plugin/okf-export.js +0 -134
- package/dist/plugin/okf-import.d.ts +0 -76
- package/dist/plugin/okf-import.js +0 -271
- package/dist/plugin/okf.d.ts +0 -107
- package/dist/plugin/okf.js +0 -303
- package/dist/plugin/perf.d.ts +0 -35
- package/dist/plugin/perf.js +0 -174
- package/dist/plugin/query-tokenizer.d.ts +0 -13
- package/dist/plugin/query-tokenizer.js +0 -133
- package/dist/plugin/redact.d.ts +0 -2
- package/dist/plugin/redact.js +0 -18
- package/dist/plugin/replay-types.d.ts +0 -69
- package/dist/plugin/replay-types.js +0 -156
- package/dist/plugin/replay.d.ts +0 -36
- package/dist/plugin/replay.js +0 -202
- package/dist/plugin/sqlite-adapter.d.ts +0 -12
- package/dist/plugin/sqlite-adapter.js +0 -81
- package/dist/plugin/time-ms.d.ts +0 -1
- package/dist/plugin/time-ms.js +0 -16
- package/dist/plugin/tui-types.d.ts +0 -59
- package/dist/plugin/tui-types.js +0 -4
- package/dist/plugin/tui.d.ts +0 -18
- package/dist/plugin/tui.js +0 -198
- package/dist/plugin/uuid.d.ts +0 -1
- package/dist/plugin/uuid.js +0 -50
package/dist/plugin/metrics.d.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import type { Store } from "./Store.js";
|
|
2
|
-
/**
|
|
3
|
-
* Seeded metric keys defined by migration 003_v02_signal.sql.
|
|
4
|
-
* The order here MUST match the migration's INSERT OR IGNORE block, since
|
|
5
|
-
* snapshot() relies on those keys being present in the cache even when the
|
|
6
|
-
* underlying table is empty (e.g., before 003 is applied, on a fresh
|
|
7
|
-
* :memory: test DB, or after a manual wipe).
|
|
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", "bench_regression_failures", "forget_requests_total", "forget_tombstones_published", "tui_snapshots_flushed", "tui_actions_invoked"];
|
|
10
|
-
export type MetricKey = (typeof METRIC_KEYS)[number];
|
|
11
|
-
/**
|
|
12
|
-
* Cheap token estimate used when bumping the `tokens_injected_*` counters.
|
|
13
|
-
* Per plan §B6.2: heuristic = block.length / 4, floored to 1 so empty strings
|
|
14
|
-
* don't contribute zero tokens (avoids losing signal on whitespace-only
|
|
15
|
-
* blocks).
|
|
16
|
-
*/
|
|
17
|
-
export declare function estimateTokens(text: string): number;
|
|
18
|
-
/**
|
|
19
|
-
* In-memory mirror of the `kevin_metrics` table with debounced writes.
|
|
20
|
-
*
|
|
21
|
-
* The cache is seeded from `kevin_metrics` on construction (or zeros if the
|
|
22
|
-
* table is missing — graceful degradation for unit tests and pre-003 DBs).
|
|
23
|
-
* `incr()` updates the cache and schedules a debounced `flush()` (1 s by
|
|
24
|
-
* default). `flush()` writes every dirty key in a single transaction and
|
|
25
|
-
* clears the timer, so the call site can also force a flush on `session.idle`
|
|
26
|
-
* and on plugin dispose.
|
|
27
|
-
*/
|
|
28
|
-
export declare class Metrics {
|
|
29
|
-
private readonly store;
|
|
30
|
-
private readonly cache;
|
|
31
|
-
private readonly dirty;
|
|
32
|
-
private flushTimer;
|
|
33
|
-
private readonly flushMs;
|
|
34
|
-
private closed;
|
|
35
|
-
constructor(store: Store, flushMs?: number);
|
|
36
|
-
private loadFromDb;
|
|
37
|
-
incr(key: MetricKey, by?: number): void;
|
|
38
|
-
/**
|
|
39
|
-
* v0.6.0 (K6-018/019 / plan §5.8) — the pull-channel registration
|
|
40
|
-
* counters. These live OUTSIDE `METRIC_KEYS`, which is frozen at 33
|
|
41
|
-
* (K7-004 acceptance, the verified cumulative ladder), but persist to
|
|
42
|
-
* the same `kevin_metrics` table so `kevin_audit`'s channels block can
|
|
43
|
-
* read them by SQL (K6-023). They are written immediately (no debounce):
|
|
44
|
-
* they change at most twice per process, on session start.
|
|
45
|
-
*/
|
|
46
|
-
incrRegistered(key: "skills_registered" | "references_registered", by?: number): void;
|
|
47
|
-
/**
|
|
48
|
-
* Returns a snapshot of the cache. The returned object always contains all
|
|
49
|
-
* METRIC_KEYS, even if the DB has no rows yet. Does NOT flush.
|
|
50
|
-
*/
|
|
51
|
-
snapshot(): Record<MetricKey, number>;
|
|
52
|
-
/**
|
|
53
|
-
* Returns the cached value for a single key. Does NOT flush.
|
|
54
|
-
*/
|
|
55
|
-
get(key: MetricKey): number;
|
|
56
|
-
/**
|
|
57
|
-
* v0.4.0 (K4-008): injection precision = effective / total settled
|
|
58
|
-
* injections. 0 when the ledger has no entries yet (no division by zero).
|
|
59
|
-
* Computed from the cached counters — does NOT flush.
|
|
60
|
-
* v0.5.0 (K5-004 / plan §5.1, D5-02) — the denominator is now
|
|
61
|
-
* `effective + ineffective` only. `inconclusive` (the new majority
|
|
62
|
-
* bucket) must not inflate precision: absence of recurrence is not
|
|
63
|
-
* evidence of effect. An idle session therefore no longer drives
|
|
64
|
-
* precision toward 1.0 — expect the reported rate to fall sharply on
|
|
65
|
-
* real databases; that is the intended result.
|
|
66
|
-
*/
|
|
67
|
-
precisionRate(): number;
|
|
68
|
-
/**
|
|
69
|
-
* v0.5.0 (K5-004 / plan §5.1, D5-02) — the share of injections that
|
|
70
|
-
* were actually measured (effective + ineffective) of all injections.
|
|
71
|
-
* Reported alongside `precisionRate` so a low measurable fraction is
|
|
72
|
-
* visible rather than hidden behind a large `total`. 0 when the ledger
|
|
73
|
-
* is empty. Computed from the cached counters — does NOT flush.
|
|
74
|
-
*/
|
|
75
|
-
coverageRate(): number;
|
|
76
|
-
/**
|
|
77
|
-
* v0.5.0 (K5-004 / plan §5.2, D5-02) — the five `injections_blocked_*`
|
|
78
|
-
* counters keyed by their short names. Consumed by `kevin_audit`.
|
|
79
|
-
* v0.6.0 (K6-004 / plan §8.3) — sixth member keyed `confidence`
|
|
80
|
-
* (the low_confidence gate branch, K6-022).
|
|
81
|
-
* Computed from the cached counters — does NOT flush.
|
|
82
|
-
*/
|
|
83
|
-
blockedSnapshot(): Record<string, number>;
|
|
84
|
-
/** True iff a debounced flush is scheduled. Useful for tests. */
|
|
85
|
-
isFlushScheduled(): boolean;
|
|
86
|
-
/**
|
|
87
|
-
* Writes every dirty key to `kevin_metrics` in one transaction. Clears
|
|
88
|
-
* the debounce timer. Safe to call repeatedly; no-op when nothing is
|
|
89
|
-
* dirty or when the object is closed. Missing rows are inserted, present
|
|
90
|
-
* rows are updated. The table is created lazily on first flush so this
|
|
91
|
-
* works against pre-003 DBs too.
|
|
92
|
-
*/
|
|
93
|
-
flush(): void;
|
|
94
|
-
close(): void;
|
|
95
|
-
private scheduleFlush;
|
|
96
|
-
private clearTimer;
|
|
97
|
-
}
|
package/dist/plugin/metrics.js
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Seeded metric keys defined by migration 003_v02_signal.sql.
|
|
3
|
-
* The order here MUST match the migration's INSERT OR IGNORE block, since
|
|
4
|
-
* snapshot() relies on those keys being present in the cache even when the
|
|
5
|
-
* underlying table is empty (e.g., before 003 is applied, on a fresh
|
|
6
|
-
* :memory: test DB, or after a manual wipe).
|
|
7
|
-
*/
|
|
8
|
-
export const METRIC_KEYS = [
|
|
9
|
-
"tokens_injected_pre_prompt",
|
|
10
|
-
"tokens_injected_compacting",
|
|
11
|
-
"reflections_throttled",
|
|
12
|
-
"duplicate_suppressions",
|
|
13
|
-
"tool_calls_deduped",
|
|
14
|
-
"patterns_mined",
|
|
15
|
-
"patterns_causal",
|
|
16
|
-
"causal_links",
|
|
17
|
-
"memories_superseded",
|
|
18
|
-
"injections_total",
|
|
19
|
-
"injections_effective",
|
|
20
|
-
"injections_ineffective",
|
|
21
|
-
"patterns_promoted_new",
|
|
22
|
-
// v0.5.0 (K5-004 / plan §8.3) — order matches migration 006's seed block.
|
|
23
|
-
"injections_inconclusive",
|
|
24
|
-
"injections_blocked_seen",
|
|
25
|
-
"injections_blocked_weak",
|
|
26
|
-
"injections_blocked_recurrence",
|
|
27
|
-
"injections_blocked_stale",
|
|
28
|
-
"injections_blocked_ignored",
|
|
29
|
-
"feedback_positive_total",
|
|
30
|
-
"feedback_negative_total",
|
|
31
|
-
"memories_archived",
|
|
32
|
-
// v0.6.0 (K6-004 / plan §8.3) — order matches migration 007's seed block.
|
|
33
|
-
// injections_blocked_confidence is the sixth member of the blocked
|
|
34
|
-
// family and MUST be counted like the other five (Principle 16).
|
|
35
|
-
"proposals_created",
|
|
36
|
-
"proposals_approved",
|
|
37
|
-
"proposals_rejected",
|
|
38
|
-
"artifact_writes_total",
|
|
39
|
-
"artifact_writes_noop",
|
|
40
|
-
"injections_blocked_confidence",
|
|
41
|
-
// v0.7.0 (K7-004 / plan §8.3) — order matches migration 008's seed block.
|
|
42
|
-
// These are the Project Truth counters. No per-type precision helper is
|
|
43
|
-
// exported here: the per-type split of plan §5.6 lives in `kevin_audit`
|
|
44
|
-
// as pure SQL, not as a `Metrics` method, so there is never a second
|
|
45
|
-
// definition of precision to drift from the first (D7-14).
|
|
46
|
-
"repo_facts_scanned",
|
|
47
|
-
"memories_contradicted",
|
|
48
|
-
"conventions_mined",
|
|
49
|
-
"conflicts_detected",
|
|
50
|
-
"error_lessons_suppressed",
|
|
51
|
-
// v0.8.0 (K8-003 / plan §8.3) — order matches migration 009's seed block.
|
|
52
|
-
// These are the Team counters. shared_entries_total is re-derived from
|
|
53
|
-
// the table by the 009 post-apply hook, never trusted from increments.
|
|
54
|
-
"shared_entries_total",
|
|
55
|
-
"shared_entries_imported",
|
|
56
|
-
"shared_entries_exported",
|
|
57
|
-
"okf_merge_folds",
|
|
58
|
-
"rekey_events",
|
|
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",
|
|
64
|
-
// v1.2.0 (K12-001 / plan §4) — surface metrics; no migration this
|
|
65
|
-
// release — rows are created on first incr via upsert (K12-001).
|
|
66
|
-
"tui_snapshots_flushed",
|
|
67
|
-
"tui_actions_invoked",
|
|
68
|
-
];
|
|
69
|
-
const DEFAULT_FLUSH_MS = 1000;
|
|
70
|
-
function zeroCache() {
|
|
71
|
-
const m = new Map();
|
|
72
|
-
for (const k of METRIC_KEYS)
|
|
73
|
-
m.set(k, 0);
|
|
74
|
-
return m;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Cheap token estimate used when bumping the `tokens_injected_*` counters.
|
|
78
|
-
* Per plan §B6.2: heuristic = block.length / 4, floored to 1 so empty strings
|
|
79
|
-
* don't contribute zero tokens (avoids losing signal on whitespace-only
|
|
80
|
-
* blocks).
|
|
81
|
-
*/
|
|
82
|
-
export function estimateTokens(text) {
|
|
83
|
-
return Math.max(1, Math.round(text.length / 4));
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* In-memory mirror of the `kevin_metrics` table with debounced writes.
|
|
87
|
-
*
|
|
88
|
-
* The cache is seeded from `kevin_metrics` on construction (or zeros if the
|
|
89
|
-
* table is missing — graceful degradation for unit tests and pre-003 DBs).
|
|
90
|
-
* `incr()` updates the cache and schedules a debounced `flush()` (1 s by
|
|
91
|
-
* default). `flush()` writes every dirty key in a single transaction and
|
|
92
|
-
* clears the timer, so the call site can also force a flush on `session.idle`
|
|
93
|
-
* and on plugin dispose.
|
|
94
|
-
*/
|
|
95
|
-
export class Metrics {
|
|
96
|
-
store;
|
|
97
|
-
cache;
|
|
98
|
-
dirty = new Set();
|
|
99
|
-
flushTimer = null;
|
|
100
|
-
flushMs;
|
|
101
|
-
closed = false;
|
|
102
|
-
constructor(store, flushMs = DEFAULT_FLUSH_MS) {
|
|
103
|
-
this.store = store;
|
|
104
|
-
this.flushMs = flushMs;
|
|
105
|
-
this.cache = zeroCache();
|
|
106
|
-
this.loadFromDb();
|
|
107
|
-
}
|
|
108
|
-
loadFromDb() {
|
|
109
|
-
// Graceful: kevin_metrics only exists after migration 003. If a caller
|
|
110
|
-
// instantiates Metrics against a fresh / pre-003 DB, leave the zeros
|
|
111
|
-
// seeded in memory; the eventual flush() will create the rows.
|
|
112
|
-
let rows = [];
|
|
113
|
-
try {
|
|
114
|
-
rows = this.store
|
|
115
|
-
.prepare("SELECT key, value FROM kevin_metrics")
|
|
116
|
-
.all();
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
rows = [];
|
|
120
|
-
}
|
|
121
|
-
for (const row of rows) {
|
|
122
|
-
if (this.cache.has(row.key)) {
|
|
123
|
-
this.cache.set(row.key, row.value);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
incr(key, by = 1) {
|
|
128
|
-
if (this.closed)
|
|
129
|
-
return;
|
|
130
|
-
const current = this.cache.get(key) ?? 0;
|
|
131
|
-
this.cache.set(key, current + by);
|
|
132
|
-
this.dirty.add(key);
|
|
133
|
-
this.scheduleFlush();
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* v0.6.0 (K6-018/019 / plan §5.8) — the pull-channel registration
|
|
137
|
-
* counters. These live OUTSIDE `METRIC_KEYS`, which is frozen at 33
|
|
138
|
-
* (K7-004 acceptance, the verified cumulative ladder), but persist to
|
|
139
|
-
* the same `kevin_metrics` table so `kevin_audit`'s channels block can
|
|
140
|
-
* read them by SQL (K6-023). They are written immediately (no debounce):
|
|
141
|
-
* they change at most twice per process, on session start.
|
|
142
|
-
*/
|
|
143
|
-
incrRegistered(key, by = 1) {
|
|
144
|
-
if (this.closed)
|
|
145
|
-
return;
|
|
146
|
-
const store = this.store;
|
|
147
|
-
store.transaction(() => {
|
|
148
|
-
store.exec(`CREATE TABLE IF NOT EXISTS kevin_metrics (
|
|
149
|
-
key TEXT PRIMARY KEY,
|
|
150
|
-
value INTEGER NOT NULL DEFAULT 0,
|
|
151
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
152
|
-
)`);
|
|
153
|
-
store
|
|
154
|
-
.prepare(`INSERT INTO kevin_metrics (key, value, updated_at)
|
|
155
|
-
VALUES (?, ?, datetime('now'))
|
|
156
|
-
ON CONFLICT(key) DO UPDATE SET
|
|
157
|
-
value = value + excluded.value,
|
|
158
|
-
updated_at = datetime('now')`)
|
|
159
|
-
.run(key, by);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Returns a snapshot of the cache. The returned object always contains all
|
|
164
|
-
* METRIC_KEYS, even if the DB has no rows yet. Does NOT flush.
|
|
165
|
-
*/
|
|
166
|
-
snapshot() {
|
|
167
|
-
const out = {};
|
|
168
|
-
for (const k of METRIC_KEYS)
|
|
169
|
-
out[k] = this.cache.get(k) ?? 0;
|
|
170
|
-
return out;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Returns the cached value for a single key. Does NOT flush.
|
|
174
|
-
*/
|
|
175
|
-
get(key) {
|
|
176
|
-
return this.cache.get(key) ?? 0;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* v0.4.0 (K4-008): injection precision = effective / total settled
|
|
180
|
-
* injections. 0 when the ledger has no entries yet (no division by zero).
|
|
181
|
-
* Computed from the cached counters — does NOT flush.
|
|
182
|
-
* v0.5.0 (K5-004 / plan §5.1, D5-02) — the denominator is now
|
|
183
|
-
* `effective + ineffective` only. `inconclusive` (the new majority
|
|
184
|
-
* bucket) must not inflate precision: absence of recurrence is not
|
|
185
|
-
* evidence of effect. An idle session therefore no longer drives
|
|
186
|
-
* precision toward 1.0 — expect the reported rate to fall sharply on
|
|
187
|
-
* real databases; that is the intended result.
|
|
188
|
-
*/
|
|
189
|
-
precisionRate() {
|
|
190
|
-
const effective = this.cache.get("injections_effective") ?? 0;
|
|
191
|
-
const measured = effective + (this.cache.get("injections_ineffective") ?? 0);
|
|
192
|
-
if (measured <= 0)
|
|
193
|
-
return 0;
|
|
194
|
-
return Math.min(1, effective / measured);
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* v0.5.0 (K5-004 / plan §5.1, D5-02) — the share of injections that
|
|
198
|
-
* were actually measured (effective + ineffective) of all injections.
|
|
199
|
-
* Reported alongside `precisionRate` so a low measurable fraction is
|
|
200
|
-
* visible rather than hidden behind a large `total`. 0 when the ledger
|
|
201
|
-
* is empty. Computed from the cached counters — does NOT flush.
|
|
202
|
-
*/
|
|
203
|
-
coverageRate() {
|
|
204
|
-
const total = this.cache.get("injections_total") ?? 0;
|
|
205
|
-
if (total <= 0)
|
|
206
|
-
return 0;
|
|
207
|
-
const measured = (this.cache.get("injections_effective") ?? 0) +
|
|
208
|
-
(this.cache.get("injections_ineffective") ?? 0);
|
|
209
|
-
return Math.min(1, measured / total);
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* v0.5.0 (K5-004 / plan §5.2, D5-02) — the five `injections_blocked_*`
|
|
213
|
-
* counters keyed by their short names. Consumed by `kevin_audit`.
|
|
214
|
-
* v0.6.0 (K6-004 / plan §8.3) — sixth member keyed `confidence`
|
|
215
|
-
* (the low_confidence gate branch, K6-022).
|
|
216
|
-
* Computed from the cached counters — does NOT flush.
|
|
217
|
-
*/
|
|
218
|
-
blockedSnapshot() {
|
|
219
|
-
return {
|
|
220
|
-
seen: this.cache.get("injections_blocked_seen") ?? 0,
|
|
221
|
-
weak: this.cache.get("injections_blocked_weak") ?? 0,
|
|
222
|
-
recurrence: this.cache.get("injections_blocked_recurrence") ?? 0,
|
|
223
|
-
stale: this.cache.get("injections_blocked_stale") ?? 0,
|
|
224
|
-
ignored: this.cache.get("injections_blocked_ignored") ?? 0,
|
|
225
|
-
confidence: this.cache.get("injections_blocked_confidence") ?? 0,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
/** True iff a debounced flush is scheduled. Useful for tests. */
|
|
229
|
-
isFlushScheduled() {
|
|
230
|
-
return this.flushTimer !== null;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Writes every dirty key to `kevin_metrics` in one transaction. Clears
|
|
234
|
-
* the debounce timer. Safe to call repeatedly; no-op when nothing is
|
|
235
|
-
* dirty or when the object is closed. Missing rows are inserted, present
|
|
236
|
-
* rows are updated. The table is created lazily on first flush so this
|
|
237
|
-
* works against pre-003 DBs too.
|
|
238
|
-
*/
|
|
239
|
-
flush() {
|
|
240
|
-
if (this.closed || this.dirty.size === 0) {
|
|
241
|
-
this.clearTimer();
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
this.clearTimer();
|
|
245
|
-
const dirtyKeys = Array.from(this.dirty);
|
|
246
|
-
this.dirty.clear();
|
|
247
|
-
const store = this.store;
|
|
248
|
-
store.transaction(() => {
|
|
249
|
-
store.exec(`CREATE TABLE IF NOT EXISTS kevin_metrics (
|
|
250
|
-
key TEXT PRIMARY KEY,
|
|
251
|
-
value INTEGER NOT NULL DEFAULT 0,
|
|
252
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
253
|
-
)`);
|
|
254
|
-
const upsert = store.prepare(`INSERT INTO kevin_metrics (key, value, updated_at)
|
|
255
|
-
VALUES (?, ?, datetime('now'))
|
|
256
|
-
ON CONFLICT(key) DO UPDATE SET
|
|
257
|
-
value = excluded.value,
|
|
258
|
-
updated_at = datetime('now')`);
|
|
259
|
-
for (const k of dirtyKeys) {
|
|
260
|
-
upsert.run(k, this.cache.get(k) ?? 0);
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
close() {
|
|
265
|
-
if (this.closed)
|
|
266
|
-
return;
|
|
267
|
-
this.flush();
|
|
268
|
-
this.closed = true;
|
|
269
|
-
this.clearTimer();
|
|
270
|
-
}
|
|
271
|
-
scheduleFlush() {
|
|
272
|
-
if (this.flushTimer !== null)
|
|
273
|
-
return;
|
|
274
|
-
this.flushTimer = setTimeout(() => {
|
|
275
|
-
this.flushTimer = null;
|
|
276
|
-
this.flush();
|
|
277
|
-
}, this.flushMs);
|
|
278
|
-
// unref so the timer never keeps a Node process alive on its own.
|
|
279
|
-
const t = this.flushTimer;
|
|
280
|
-
t.unref?.();
|
|
281
|
-
}
|
|
282
|
-
clearTimer() {
|
|
283
|
-
if (this.flushTimer !== null) {
|
|
284
|
-
clearTimeout(this.flushTimer);
|
|
285
|
-
this.flushTimer = null;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { computeConfidence } from "./confidence.js";
|
|
2
|
-
const EXPORT_TYPES = new Set(["decision", "rule", "pattern"]);
|
|
3
|
-
function formatTimestamp(ts) {
|
|
4
|
-
try {
|
|
5
|
-
// SQLite `datetime('now')` is UTC without a zone suffix; parse as
|
|
6
|
-
// UTC so a round-trip never shifts the value by the local offset.
|
|
7
|
-
const iso = ts.includes("T") ? ts : `${ts.replace(" ", "T")}Z`;
|
|
8
|
-
return new Date(iso).toISOString().replace("T", " ").slice(0, 19);
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return ts;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* BUG-008 — select export rows including `recurrence_count` (the v0.4.0
|
|
16
|
-
* demotion signal, column from migration 005). DBs that predate 005 have
|
|
17
|
-
* no such column; the SELECT is retried without it and recurrences
|
|
18
|
-
* degrade to 0 (legacy confidence formula applies — see below).
|
|
19
|
-
*
|
|
20
|
-
* v0.8.0 (K8-027) — the v1 export is scoped to one project: the global
|
|
21
|
-
* database may hold several projects' memories, and once an export lands
|
|
22
|
-
* in a committed file a cross-project leak becomes permanent history.
|
|
23
|
-
* A `project_id` of null keeps the legacy unscoped behaviour for direct
|
|
24
|
-
* callers that predate the fix; the kevin_export tool always threads it.
|
|
25
|
-
*/
|
|
26
|
-
function selectExportRows(store, projectId) {
|
|
27
|
-
try {
|
|
28
|
-
return {
|
|
29
|
-
hasRecurrence: true,
|
|
30
|
-
rows: store
|
|
31
|
-
.prepare(`SELECT id, type, content, scope, relevance_score, fingerprint,
|
|
32
|
-
evidence_count, recurrence_count, last_verified_at, status,
|
|
33
|
-
source_tool, source_session, created_at, updated_at
|
|
34
|
-
FROM memories
|
|
35
|
-
WHERE status = 'active'
|
|
36
|
-
AND project_id = ?
|
|
37
|
-
ORDER BY type, created_at DESC`)
|
|
38
|
-
.all(projectId),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
const legacy = store
|
|
43
|
-
.prepare(`SELECT id, type, content, scope, relevance_score, fingerprint,
|
|
44
|
-
evidence_count, last_verified_at, status,
|
|
45
|
-
source_tool, source_session, created_at, updated_at
|
|
46
|
-
FROM memories
|
|
47
|
-
WHERE status = 'active'
|
|
48
|
-
AND project_id = ?
|
|
49
|
-
ORDER BY type, created_at DESC`)
|
|
50
|
-
.all(projectId);
|
|
51
|
-
return {
|
|
52
|
-
hasRecurrence: false,
|
|
53
|
-
rows: legacy.map((r) => ({ ...r, recurrence_count: 0 })),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* BUG-008 — the exported confidence must match the two-sided v0.4.0
|
|
59
|
-
* formula (K4-010), demoting lessons with recurrences. Pre-005 DBs (no
|
|
60
|
-
* `recurrence_count` column) keep the legacy one-sided formula.
|
|
61
|
-
*/
|
|
62
|
-
function exportConfidence(row, hasRecurrence) {
|
|
63
|
-
const confidence = hasRecurrence
|
|
64
|
-
? computeConfidence(row.evidence_count, row.recurrence_count)
|
|
65
|
-
: Math.min(1, 0.5 + 0.1 * row.evidence_count);
|
|
66
|
-
return confidence.toFixed(2);
|
|
67
|
-
}
|
|
68
|
-
export function exportOkf(store, projectId = null) {
|
|
69
|
-
const { rows: allRows, hasRecurrence } = selectExportRows(store, projectId);
|
|
70
|
-
const filtered = allRows.filter((r) => EXPORT_TYPES.has(r.type));
|
|
71
|
-
if (filtered.length === 0)
|
|
72
|
-
return "<!-- No exportable memories found. -->\n";
|
|
73
|
-
const blocks = [];
|
|
74
|
-
for (const m of filtered) {
|
|
75
|
-
const fm = [];
|
|
76
|
-
fm.push("---");
|
|
77
|
-
fm.push(`id: ${m.id}`);
|
|
78
|
-
fm.push(`type: ${m.type}`);
|
|
79
|
-
fm.push(`confidence: ${exportConfidence(m, hasRecurrence)}`);
|
|
80
|
-
fm.push(`evidence_count: ${m.evidence_count}`);
|
|
81
|
-
if (m.recurrence_count > 0) {
|
|
82
|
-
fm.push(`recurrence_count: ${m.recurrence_count}`);
|
|
83
|
-
}
|
|
84
|
-
if (m.last_verified_at) {
|
|
85
|
-
fm.push(`last_verified_at: ${formatTimestamp(m.last_verified_at)}`);
|
|
86
|
-
}
|
|
87
|
-
if (m.fingerprint) {
|
|
88
|
-
fm.push(`fingerprint: ${m.fingerprint}`);
|
|
89
|
-
}
|
|
90
|
-
fm.push(`created: ${formatTimestamp(m.created_at)}`);
|
|
91
|
-
fm.push(`scope: ${m.scope}`);
|
|
92
|
-
fm.push("---");
|
|
93
|
-
fm.push("");
|
|
94
|
-
fm.push(m.content);
|
|
95
|
-
blocks.push(fm.join("\n"));
|
|
96
|
-
}
|
|
97
|
-
return `${blocks.join("\n\n")}\n`;
|
|
98
|
-
}
|
|
99
|
-
export function exportMarkdown(store, projectId = null) {
|
|
100
|
-
const { rows: allRows, hasRecurrence } = selectExportRows(store, projectId);
|
|
101
|
-
const filtered = allRows.filter((r) => EXPORT_TYPES.has(r.type));
|
|
102
|
-
if (filtered.length === 0)
|
|
103
|
-
return "# Kevin Knowledge Export\n\n_No exportable memories found._\n";
|
|
104
|
-
const lines = [];
|
|
105
|
-
lines.push("# Kevin Knowledge Export");
|
|
106
|
-
lines.push("");
|
|
107
|
-
lines.push(`Exported: ${new Date().toISOString().replace("T", " ").slice(0, 19)}`);
|
|
108
|
-
lines.push(`Total entries: ${filtered.length}`);
|
|
109
|
-
lines.push("");
|
|
110
|
-
for (const m of filtered) {
|
|
111
|
-
lines.push(`## ${m.type}: \`${m.fingerprint ?? m.id.slice(0, 8)}\``);
|
|
112
|
-
lines.push("");
|
|
113
|
-
lines.push(`- **ID:** \`${m.id}\``);
|
|
114
|
-
lines.push(`- **Confidence:** ${exportConfidence(m, hasRecurrence)}`);
|
|
115
|
-
lines.push(`- **Evidence count:** ${m.evidence_count}`);
|
|
116
|
-
if (m.recurrence_count > 0) {
|
|
117
|
-
lines.push(`- **Recurrence count:** ${m.recurrence_count}`);
|
|
118
|
-
}
|
|
119
|
-
if (m.last_verified_at) {
|
|
120
|
-
lines.push(`- **Last verified:** ${formatTimestamp(m.last_verified_at)}`);
|
|
121
|
-
}
|
|
122
|
-
if (m.fingerprint) {
|
|
123
|
-
lines.push(`- **Fingerprint:** \`${m.fingerprint}\``);
|
|
124
|
-
}
|
|
125
|
-
lines.push(`- **Scope:** ${m.scope}`);
|
|
126
|
-
lines.push(`- **Created:** ${formatTimestamp(m.created_at)}`);
|
|
127
|
-
lines.push("");
|
|
128
|
-
lines.push(m.content);
|
|
129
|
-
lines.push("");
|
|
130
|
-
lines.push("---");
|
|
131
|
-
lines.push("");
|
|
132
|
-
}
|
|
133
|
-
return lines.join("\n");
|
|
134
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
-
/**
|
|
3
|
-
* v0.3.0 fix — A single parsed bundle entry. `evidence_count` and
|
|
4
|
-
* `last_verified_at` are preserved across round-trips (export → import)
|
|
5
|
-
* so causal confidence is not lost when knowledge is shared between
|
|
6
|
-
* projects. v0.4.0 (BUG-008) — `recurrence_count` too, so the two-sided
|
|
7
|
-
* v0.4.0 confidence demotion survives the round-trip.
|
|
8
|
-
*/
|
|
9
|
-
export interface ParsedEntry {
|
|
10
|
-
id: string;
|
|
11
|
-
type: string;
|
|
12
|
-
content: string;
|
|
13
|
-
fingerprint: string | null;
|
|
14
|
-
evidence_count: number;
|
|
15
|
-
/** v0.4.0 (BUG-008) — recurrence demotion, 0 when absent. */
|
|
16
|
-
recurrence_count: number;
|
|
17
|
-
last_verified_at: string | null;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* v0.3.0 fix — Clean state-machine parser for the frontmatter bundle
|
|
21
|
-
* format produced by `okf-export.ts::exportOkf`. Each entry is:
|
|
22
|
-
*
|
|
23
|
-
* ---
|
|
24
|
-
* id: <uuid>
|
|
25
|
-
* type: <decision|rule|pattern>
|
|
26
|
-
* confidence: 0.70
|
|
27
|
-
* evidence_count: 2
|
|
28
|
-
* last_verified_at: 2026-07-25 12:34:56
|
|
29
|
-
* fingerprint: <hex>
|
|
30
|
-
* created: 2026-07-25 12:00:00
|
|
31
|
-
* scope: project
|
|
32
|
-
* ---
|
|
33
|
-
*
|
|
34
|
-
* <content body, may span multiple lines, may include `---` lines
|
|
35
|
-
* within — the body terminator is the NEXT top-level `---` followed
|
|
36
|
-
* by an `id:` line, or EOF>
|
|
37
|
-
*
|
|
38
|
-
* The previous implementation only ever flagged `inFm = true` once and
|
|
39
|
-
* never reset `contentStarted` between entries, so 2..N entries were
|
|
40
|
-
* silently dropped (bug #1). This rewrite handles arbitrary numbers of
|
|
41
|
-
* consecutive frontmatter sections.
|
|
42
|
-
*/
|
|
43
|
-
export declare function parseMarkdownBundle(text: string): ParsedEntry[];
|
|
44
|
-
/**
|
|
45
|
-
* v0.3.0 fix — Fallback parser for the markdown-style `##` heading
|
|
46
|
-
* format produced by `okf-export.ts::exportMarkdown`. The previous
|
|
47
|
-
* version extracted `fingerprint: null` for every entry (regex was
|
|
48
|
-
* pinned to 16 hex chars while actual fingerprints are variable
|
|
49
|
-
* length) and contaminated `content` with the heading line and
|
|
50
|
-
* metadata bullets. This version cleanly separates metadata bullets
|
|
51
|
-
* (looking for `**ID:**`, `**Fingerprint:**`, `**Evidence count:**`,
|
|
52
|
-
* `**Last verified:**`, `**Scope:**` prefixes) from the content
|
|
53
|
-
* body, which starts after the first blank line following the bullet
|
|
54
|
-
* block and runs until the trailing `---` separator (or EOF).
|
|
55
|
-
*/
|
|
56
|
-
export declare function parseMarkdownHeadings(text: string): ParsedEntry[];
|
|
57
|
-
export interface ImportResult {
|
|
58
|
-
imported: number;
|
|
59
|
-
superseded: number;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* v0.3.0 fix — Ingest a bundle (frontmatter OR markdown) into the
|
|
63
|
-
* local SQLite store as `context` memories with `origin='imported'`.
|
|
64
|
-
*
|
|
65
|
-
* Fixes over the v0.3.0 baseline:
|
|
66
|
-
* * Multi-entry bundles now produce N imports instead of N=1 — the
|
|
67
|
-
* parser bug is closed.
|
|
68
|
-
* * `ParsedEntry.evidence_count` and `last_verified_at` are
|
|
69
|
-
* threaded through so causal confidence survives a round-trip.
|
|
70
|
-
* * `ImportResult.superseded` is populated using
|
|
71
|
-
* `countSupersedeCandidates`, which mirrors the supersede logic
|
|
72
|
-
* in `MemoryService.save()`. Previously it was hard-coded to 0.
|
|
73
|
-
* * Generated ids use `uuidv7()` (the project's id generator)
|
|
74
|
-
* instead of `crypto.randomUUID()` for consistency.
|
|
75
|
-
*/
|
|
76
|
-
export declare function importOkf(bundle: string, memoryService: MemoryService): ImportResult;
|