@jmtrin/opencode-kevin 0.4.0 → 0.5.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 (46) hide show
  1. package/README.md +87 -11
  2. package/dist/migrations/006_v05_glassbox.sql +118 -0
  3. package/dist/plugin/Archiver.d.ts +42 -0
  4. package/dist/plugin/Archiver.js +98 -0
  5. package/dist/plugin/Archiver.js.map +1 -0
  6. package/dist/plugin/ContextInjector.d.ts +72 -0
  7. package/dist/plugin/ContextInjector.js +177 -44
  8. package/dist/plugin/ContextInjector.js.map +1 -1
  9. package/dist/plugin/Feedback.d.ts +67 -0
  10. package/dist/plugin/Feedback.js +137 -0
  11. package/dist/plugin/Feedback.js.map +1 -0
  12. package/dist/plugin/InjectionLedger.d.ts +9 -2
  13. package/dist/plugin/InjectionLedger.js +63 -9
  14. package/dist/plugin/InjectionLedger.js.map +1 -1
  15. package/dist/plugin/MemoryService.d.ts +19 -0
  16. package/dist/plugin/MemoryService.js +119 -15
  17. package/dist/plugin/MemoryService.js.map +1 -1
  18. package/dist/plugin/Migrate.js +21 -0
  19. package/dist/plugin/Migrate.js.map +1 -1
  20. package/dist/plugin/QualityGate.d.ts +39 -0
  21. package/dist/plugin/QualityGate.js +38 -8
  22. package/dist/plugin/QualityGate.js.map +1 -1
  23. package/dist/plugin/Retrospective.js +12 -0
  24. package/dist/plugin/Retrospective.js.map +1 -1
  25. package/dist/plugin/confidence.d.ts +3 -1
  26. package/dist/plugin/confidence.js +14 -2
  27. package/dist/plugin/confidence.js.map +1 -1
  28. package/dist/plugin/index.d.ts +1 -1
  29. package/dist/plugin/index.js +152 -0
  30. package/dist/plugin/index.js.map +1 -1
  31. package/dist/plugin/kevin_audit.d.ts +49 -0
  32. package/dist/plugin/kevin_audit.js +141 -0
  33. package/dist/plugin/kevin_audit.js.map +1 -0
  34. package/dist/plugin/kevin_why.js +23 -2
  35. package/dist/plugin/kevin_why.js.map +1 -1
  36. package/dist/plugin/metrics.d.ts +21 -1
  37. package/dist/plugin/metrics.js +47 -2
  38. package/dist/plugin/metrics.js.map +1 -1
  39. package/dist/plugin/replay-types.d.ts +327 -0
  40. package/dist/plugin/replay-types.js +65 -0
  41. package/dist/plugin/replay-types.js.map +1 -0
  42. package/dist/plugin/replay.d.ts +36 -0
  43. package/dist/plugin/replay.js +193 -0
  44. package/dist/plugin/replay.js.map +1 -0
  45. package/migrations/006_v05_glassbox.sql +118 -0
  46. package/package.json +3 -2
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * v0.5.0 (K5-018 / plan §5.8) — replay transcript format.
4
+ *
5
+ * A hermetic, deterministic way to run a recorded session through the
6
+ * plugin's components and read the outcome distribution. `at` is an
7
+ * ISO-8601 string and is the ONLY source of time during replay:
8
+ * `Date.now()` must not be called anywhere in the replay path.
9
+ */
10
+ export const replayEventSchema = z.discriminatedUnion("kind", [
11
+ z.object({
12
+ kind: z.literal("session.created"),
13
+ at: z.string().datetime(),
14
+ sessionId: z.string().min(1),
15
+ }),
16
+ z.object({
17
+ kind: z.literal("chat.message"),
18
+ at: z.string().datetime(),
19
+ sessionId: z.string().min(1),
20
+ text: z.string().min(1),
21
+ }),
22
+ z.object({
23
+ kind: z.literal("tool.before"),
24
+ at: z.string().datetime(),
25
+ sessionId: z.string().min(1),
26
+ callId: z.string().min(1),
27
+ tool: z.string().min(1),
28
+ args: z.record(z.string(), z.unknown()),
29
+ }),
30
+ z.object({
31
+ kind: z.literal("tool.after"),
32
+ at: z.string().datetime(),
33
+ sessionId: z.string().min(1),
34
+ callId: z.string().min(1),
35
+ success: z.boolean(),
36
+ stdout: z.string().optional(),
37
+ stderr: z.string().optional(),
38
+ exitCode: z.number().int().optional(),
39
+ }),
40
+ z.object({
41
+ kind: z.literal("system.transform"),
42
+ at: z.string().datetime(),
43
+ sessionId: z.string().min(1),
44
+ }),
45
+ z.object({
46
+ kind: z.literal("compacting"),
47
+ at: z.string().datetime(),
48
+ sessionId: z.string().min(1),
49
+ }),
50
+ z.object({
51
+ kind: z.literal("session.idle"),
52
+ at: z.string().datetime(),
53
+ sessionId: z.string().min(1),
54
+ }),
55
+ ]);
56
+ export const replayTranscriptSchema = z.object({
57
+ version: z.literal(1),
58
+ name: z.string().min(1),
59
+ events: z.array(replayEventSchema).min(1),
60
+ });
61
+ /** Validates an unknown payload (e.g. a parsed JSON file) as a transcript. */
62
+ export function parseTranscript(json) {
63
+ return replayTranscriptSchema.parse(json);
64
+ }
65
+ //# sourceMappingURL=replay-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-types.js","sourceRoot":"","sources":["../../plugin/replay-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { ReplayTranscript } from "./replay-types.js";
2
+ /**
3
+ * v0.5.0 (K5-019 / plan §5.8, D5-12) — replay harness.
4
+ *
5
+ * Hermetic, deterministic driver: runs a recorded transcript through the
6
+ * plugin's components against an in-memory database with a frozen clock,
7
+ * then reports the outcome distribution. This is an artifact, not a
8
+ * release gate.
9
+ *
10
+ * The components are instantiated directly — NOT through `KevinPlugin`,
11
+ * which resolves a home-directory database path and registers tools.
12
+ * `Date.now()` must never be called in this path; every clock need is
13
+ * satisfied by the event's `at` timestamp (retrieval is frozen by the
14
+ * `deterministic_retrieval` setting; the Archiver clock is injected).
15
+ */
16
+ export interface ReplayResult {
17
+ readonly transcript: string;
18
+ readonly memoriesCreated: number;
19
+ readonly injections: {
20
+ total: number;
21
+ effective: number;
22
+ ineffective: number;
23
+ inconclusive: number;
24
+ unmeasured: number;
25
+ };
26
+ readonly precisionRate: number;
27
+ readonly coverageRate: number;
28
+ readonly tokensInjected: {
29
+ prePrompt: number;
30
+ compacting: number;
31
+ };
32
+ readonly blocked: Record<string, number>;
33
+ }
34
+ export declare function replay(transcript: ReplayTranscript, opts?: {
35
+ dbPath?: string;
36
+ }): Promise<ReplayResult>;
@@ -0,0 +1,193 @@
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { Archiver } from "./Archiver.js";
4
+ import { CausalChain } from "./CausalChain.js";
5
+ import { ContextInjector } from "./ContextInjector.js";
6
+ import { InjectionLedger } from "./InjectionLedger.js";
7
+ import { MemoryService } from "./MemoryService.js";
8
+ import { Migrate } from "./Migrate.js";
9
+ import { Reflector } from "./Reflector.js";
10
+ import { Store } from "./Store.js";
11
+ import { ToolCallObserver } from "./ToolCallObserver.js";
12
+ import { Metrics } from "./metrics.js";
13
+ const MIGRATIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "migrations");
14
+ export async function replay(transcript, opts) {
15
+ const store = new Store({ path: opts?.dbPath ?? ":memory:" });
16
+ await new Migrate(store, MIGRATIONS_DIR).run();
17
+ // Freeze retrieval BEFORE any query happens (K5-008): deterministic
18
+ // mode uses the DATE_NOW sentinel, ignores recency and never bumps.
19
+ store
20
+ .prepare("INSERT OR IGNORE INTO kevin_settings (key, value) VALUES ('deterministic_retrieval', '1')")
21
+ .run();
22
+ store
23
+ .prepare("UPDATE kevin_settings SET value = '1' WHERE key = 'deterministic_retrieval'")
24
+ .run();
25
+ const metrics = new Metrics(store);
26
+ const memoryService = new MemoryService(store, metrics);
27
+ const observer = new ToolCallObserver(store, metrics);
28
+ // v0.3.0 fix (mirrors index.ts) — the Reflector stamps
29
+ // tool_calls.error_fingerprint with the stderr-based fingerprint the
30
+ // error memory uses; without it the recurrence queries in settle and
31
+ // the feedback loop never match (tool_calls.fingerprint is keyed on
32
+ // `${tool}|${args}|${success}`, memories on a hash of stderr/stdout).
33
+ const linkErrorStmt = store.prepare("UPDATE tool_calls SET error_fingerprint = ? WHERE id = ?");
34
+ const reflector = new Reflector(memoryService, {
35
+ throttleMs: 0,
36
+ onLinkError: (callID, fp) => {
37
+ linkErrorStmt.run(fp, callID);
38
+ },
39
+ }, metrics);
40
+ const ledger = new InjectionLedger(store, metrics);
41
+ const injector = new ContextInjector(memoryService, metrics, ledger);
42
+ const causalChain = new CausalChain(store, memoryService, metrics);
43
+ // The Archiver reads the clock through this accessor, which is updated
44
+ // to the current event's `at` before each event is processed.
45
+ let currentAt = new Date(transcript.events[0]?.at ?? Date.now());
46
+ const archiver = new Archiver(store, memoryService, metrics, () => currentAt);
47
+ const lastUserQueryBySession = new Map();
48
+ const callCache = new Map();
49
+ function resolveQuery(sessionId) {
50
+ return lastUserQueryBySession.get(sessionId) ?? "";
51
+ }
52
+ for (const event of transcript.events) {
53
+ currentAt = new Date(event.at);
54
+ await handleEvent(event);
55
+ }
56
+ metrics.flush();
57
+ const outcomeRows = store
58
+ .prepare("SELECT outcome, COUNT(*) AS n FROM kevin_injections GROUP BY outcome")
59
+ .all();
60
+ const counts = {
61
+ unmeasured: 0,
62
+ effective: 0,
63
+ ineffective: 0,
64
+ inconclusive: 0,
65
+ };
66
+ for (const r of outcomeRows) {
67
+ counts[r.outcome] = r.n;
68
+ }
69
+ const total = outcomeRows.reduce((acc, r) => acc + r.n, 0);
70
+ const tokenValue = (key) => {
71
+ const row = store
72
+ .prepare("SELECT value FROM kevin_metrics WHERE key = ?")
73
+ .get(key);
74
+ return row?.value ?? 0;
75
+ };
76
+ return {
77
+ transcript: transcript.name,
78
+ memoriesCreated: totalMemories(store),
79
+ injections: {
80
+ total,
81
+ effective: counts.effective,
82
+ ineffective: counts.ineffective,
83
+ inconclusive: counts.inconclusive,
84
+ unmeasured: counts.unmeasured,
85
+ },
86
+ precisionRate: metrics.precisionRate(),
87
+ coverageRate: metrics.coverageRate(),
88
+ tokensInjected: {
89
+ prePrompt: tokenValue("tokens_injected_pre_prompt"),
90
+ compacting: tokenValue("tokens_injected_compacting"),
91
+ },
92
+ blocked: metrics.blockedSnapshot(),
93
+ };
94
+ async function handleEvent(event) {
95
+ switch (event.kind) {
96
+ case "session.created":
97
+ // Mirror of the live wiring (index.ts): a fresh session must
98
+ // not inherit the previous session's injection seen-set
99
+ // (plan §5.1 rule 3). Without this, a multi-session
100
+ // transcript would block every memory in session 2 with
101
+ // `seen_this_session` while the live plugin would admit it.
102
+ injector.onSessionCreated(event.sessionId);
103
+ return;
104
+ case "chat.message": {
105
+ const derived = injector.deriveQuery([
106
+ { role: "user", content: event.text },
107
+ ]);
108
+ if (derived.length > 0) {
109
+ lastUserQueryBySession.set(event.sessionId, derived);
110
+ }
111
+ return;
112
+ }
113
+ case "tool.before": {
114
+ callCache.set(event.callId, {
115
+ tool: event.tool,
116
+ args: event.args,
117
+ });
118
+ observer.onBefore({
119
+ tool: event.tool,
120
+ args: event.args,
121
+ sessionId: event.sessionId,
122
+ callID: event.callId,
123
+ projectId: null,
124
+ }, {});
125
+ return;
126
+ }
127
+ case "tool.after": {
128
+ const cached = callCache.get(event.callId);
129
+ const tool = cached?.tool ?? "unknown";
130
+ const args = cached?.args ?? {};
131
+ const stderr = event.stderr ?? "";
132
+ const stdout = event.stdout ?? "";
133
+ observer.onAfter({
134
+ tool,
135
+ args,
136
+ sessionId: event.sessionId,
137
+ callID: event.callId,
138
+ projectId: null,
139
+ }, { success: event.success, stdout, stderr, exitCode: event.exitCode });
140
+ if (!event.success) {
141
+ await reflector.invoke({
142
+ toolName: tool,
143
+ argsSummary: observer.summarizeArgs(args),
144
+ stderr,
145
+ stdout,
146
+ exitCode: event.exitCode,
147
+ errorType: observer.inferErrorType(stderr, stdout, event.exitCode),
148
+ sessionId: event.sessionId,
149
+ callID: event.callId,
150
+ projectId: null,
151
+ });
152
+ }
153
+ else {
154
+ causalChain.onSuccess(tool, args, null, event.sessionId);
155
+ }
156
+ return;
157
+ }
158
+ case "system.transform": {
159
+ const query = resolveQuery(event.sessionId);
160
+ if (!query)
161
+ return;
162
+ const output = { system: [] };
163
+ injector.onSystemTransform({
164
+ sessionID: event.sessionId,
165
+ messages: [{ role: "user", content: query }],
166
+ }, output);
167
+ return;
168
+ }
169
+ case "compacting": {
170
+ const query = resolveQuery(event.sessionId);
171
+ if (!query)
172
+ return;
173
+ const output = { context: [] };
174
+ injector.onCompacting({
175
+ sessionID: event.sessionId,
176
+ messages: [{ role: "user", content: query }],
177
+ }, output);
178
+ return;
179
+ }
180
+ case "session.idle": {
181
+ ledger.settle(event.sessionId);
182
+ archiver.run();
183
+ metrics.flush();
184
+ return;
185
+ }
186
+ }
187
+ }
188
+ }
189
+ function totalMemories(store) {
190
+ const row = store.prepare("SELECT COUNT(*) AS n FROM memories").get();
191
+ return row.n ?? 0;
192
+ }
193
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../../plugin/replay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ -- ============================================================================
2
+ -- 006_v05_glassbox.sql — v0.5.0 "Glass Box"
3
+ --
4
+ -- Honest measurement, human feedback, lifecycle completion.
5
+ --
6
+ -- Section 1: rebuild kevin_injections to admit a fourth outcome.
7
+ -- Section 2: human feedback storage.
8
+ -- Section 3: memory lifecycle columns.
9
+ -- Section 4: metric seeds.
10
+ -- Section 5: setting seeds.
11
+ -- Section 6: schema_version.
12
+ -- ============================================================================
13
+
14
+ -- ---------------------------------------------------------------------------
15
+ -- 1. kevin_injections: add 'inconclusive'.
16
+ --
17
+ -- SQLite cannot ALTER a CHECK constraint, so the table must be rebuilt.
18
+ -- Migration 004 set this precedent. kevin_injections has no FTS5 triggers,
19
+ -- so unlike 004 this is a straight four-step rebuild.
20
+ --
21
+ -- Existing rows with outcome='effective' are remapped to 'inconclusive'.
22
+ -- This is not data loss: v0.4's 'effective' meant "the error did not recur",
23
+ -- which is the exact definition of the new 'inconclusive' bucket. Rows that
24
+ -- genuinely earned the new 'effective' will be re-settled naturally, and the
25
+ -- post-apply hook re-derives the counters from the table.
26
+ -- ---------------------------------------------------------------------------
27
+ CREATE TABLE IF NOT EXISTS kevin_injections_new (
28
+ id TEXT PRIMARY KEY,
29
+ memory_id TEXT NOT NULL,
30
+ fingerprint TEXT NOT NULL,
31
+ session_id TEXT NOT NULL,
32
+ hook TEXT NOT NULL CHECK (hook IN ('pre_prompt','compacting')),
33
+ tokens INTEGER NOT NULL,
34
+ injected_at TEXT NOT NULL DEFAULT (datetime('now')),
35
+ outcome TEXT NOT NULL DEFAULT 'unmeasured'
36
+ CHECK (outcome IN ('unmeasured','effective','ineffective','inconclusive'))
37
+ );
38
+
39
+ INSERT INTO kevin_injections_new
40
+ (id, memory_id, fingerprint, session_id, hook, tokens, injected_at, outcome)
41
+ SELECT
42
+ id, memory_id, fingerprint, session_id, hook, tokens, injected_at,
43
+ CASE WHEN outcome = 'effective' THEN 'inconclusive' ELSE outcome END
44
+ FROM kevin_injections;
45
+
46
+ DROP TABLE kevin_injections;
47
+ ALTER TABLE kevin_injections_new RENAME TO kevin_injections;
48
+
49
+ CREATE INDEX IF NOT EXISTS idx_injections_fp ON kevin_injections(fingerprint);
50
+ CREATE INDEX IF NOT EXISTS idx_injections_session ON kevin_injections(session_id);
51
+ CREATE INDEX IF NOT EXISTS idx_injections_outcome ON kevin_injections(outcome);
52
+
53
+ -- ---------------------------------------------------------------------------
54
+ -- 2. Human feedback. Append-only audit trail; the hot path reads the
55
+ -- denormalized counters on `memories` (section 3), never this table.
56
+ -- ---------------------------------------------------------------------------
57
+ CREATE TABLE IF NOT EXISTS memory_feedback (
58
+ id TEXT PRIMARY KEY,
59
+ memory_id TEXT NOT NULL,
60
+ verdict TEXT NOT NULL CHECK (verdict IN ('useful','wrong','outdated','ignore')),
61
+ session_id TEXT,
62
+ note TEXT,
63
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
64
+ );
65
+
66
+ CREATE INDEX IF NOT EXISTS idx_feedback_memory ON memory_feedback(memory_id);
67
+ CREATE INDEX IF NOT EXISTS idx_feedback_created ON memory_feedback(created_at);
68
+
69
+ -- ---------------------------------------------------------------------------
70
+ -- 3. Memory lifecycle and feedback columns.
71
+ --
72
+ -- feedback_positive / feedback_negative are SEPARATE from evidence_count
73
+ -- and recurrence_count by design: human judgement is evidence about the
74
+ -- memory, causal counters are evidence about the world. Mixing them was
75
+ -- the confidence-poisoning defect closed in v0.4.0.
76
+ --
77
+ -- superseded_by has no REFERENCES clause on purpose. Store enables
78
+ -- PRAGMA foreign_keys=ON, and a hard FK would block deletion of a memory
79
+ -- that superseded another.
80
+ -- ---------------------------------------------------------------------------
81
+ ALTER TABLE memories ADD COLUMN feedback_positive INTEGER NOT NULL DEFAULT 0;
82
+ ALTER TABLE memories ADD COLUMN feedback_negative INTEGER NOT NULL DEFAULT 0;
83
+ ALTER TABLE memories ADD COLUMN ignored INTEGER NOT NULL DEFAULT 0;
84
+ ALTER TABLE memories ADD COLUMN superseded_by TEXT;
85
+ ALTER TABLE memories ADD COLUMN archived_at TEXT;
86
+
87
+ CREATE INDEX IF NOT EXISTS idx_memories_ignored ON memories(ignored);
88
+ CREATE INDEX IF NOT EXISTS idx_memories_archived ON memories(archived_at);
89
+
90
+ -- ---------------------------------------------------------------------------
91
+ -- 4. Metric seeds. Order matches the additions to METRIC_KEYS in metrics.ts.
92
+ -- ---------------------------------------------------------------------------
93
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
94
+ ('injections_inconclusive', 0),
95
+ ('injections_blocked_seen', 0),
96
+ ('injections_blocked_weak', 0),
97
+ ('injections_blocked_recurrence',0),
98
+ ('injections_blocked_stale', 0),
99
+ ('injections_blocked_ignored', 0),
100
+ ('feedback_positive_total', 0),
101
+ ('feedback_negative_total', 0),
102
+ ('memories_archived', 0);
103
+
104
+ -- ---------------------------------------------------------------------------
105
+ -- 5. Setting seeds. Values are TEXT, always. Read them with an explicit
106
+ -- string comparison or an explicit Number() parse — never `=== 1`.
107
+ -- (That exact mistake kept cross_project_enabled unreachable for the
108
+ -- whole of v0.3.0.)
109
+ -- ---------------------------------------------------------------------------
110
+ INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
111
+ ('deterministic_retrieval', '0'),
112
+ ('pre_prompt_budget_tokens', '900'),
113
+ ('archive_after_days', '30');
114
+
115
+ -- ---------------------------------------------------------------------------
116
+ -- 6. Version marker.
117
+ -- ---------------------------------------------------------------------------
118
+ INSERT OR IGNORE INTO schema_version (version) VALUES ('006');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmtrin/opencode-kevin",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Kevin — Observa y aprende: capa de aprendizaje para OpenCode",
5
5
  "type": "module",
6
6
  "main": "dist/plugin/index.js",
@@ -21,7 +21,8 @@
21
21
  "typecheck": "tsc --noEmit",
22
22
  "lint": "biome check .",
23
23
  "format": "biome format --write .",
24
- "verify": "node --import tsx scripts/verify-install.ts"
24
+ "verify": "node --import tsx scripts/verify-install.ts",
25
+ "replay": "node --import tsx scripts/replay.ts"
25
26
  },
26
27
  "dependencies": {
27
28
  "@opencode-ai/plugin": "^1.17.6",