@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,35 +0,0 @@
1
- import type { Store } from "./Store.js";
2
- export type PerfScope = "tool.execute.before" | "tool.execute.after" | "chat.message" | "chat.system.transform" | "session.compacting" | "event" | "session.idle" | "dispose";
3
- export interface Budget {
4
- readonly scope: PerfScope;
5
- readonly p95Ms: number;
6
- readonly maxMs: number;
7
- }
8
- export declare const BUDGETS: readonly Budget[];
9
- export interface PerfStat {
10
- readonly scope: PerfScope;
11
- readonly count: number;
12
- readonly p50: number;
13
- readonly p95: number;
14
- readonly max: number;
15
- readonly budget: Budget;
16
- readonly withinBudget: boolean;
17
- }
18
- export declare class Perf {
19
- private readonly enabled;
20
- private readonly capacity;
21
- private readonly rings;
22
- private readonly cursors;
23
- private readonly counts;
24
- constructor(opts: {
25
- enabled: boolean;
26
- capacity?: number | string;
27
- });
28
- static fromSettings(settings: Record<string, string | null | undefined>): Perf;
29
- measure<T>(scope: PerfScope, fn: () => T): T;
30
- measureAsync<T>(scope: PerfScope, fn: () => Promise<T>): Promise<T>;
31
- private record;
32
- stats(): readonly PerfStat[];
33
- flush(store: Store): void;
34
- reset(): void;
35
- }
@@ -1,174 +0,0 @@
1
- export const BUDGETS = [
2
- { scope: "tool.execute.before", p95Ms: 2, maxMs: 10 },
3
- { scope: "tool.execute.after", p95Ms: 5, maxMs: 25 },
4
- { scope: "chat.message", p95Ms: 2, maxMs: 10 },
5
- { scope: "chat.system.transform", p95Ms: 15, maxMs: 50 },
6
- { scope: "session.compacting", p95Ms: 15, maxMs: 50 },
7
- { scope: "event", p95Ms: 5, maxMs: 25 },
8
- { scope: "session.idle", p95Ms: 150, maxMs: 600 },
9
- { scope: "dispose", p95Ms: 50, maxMs: 250 },
10
- ];
11
- function clampCapacity(raw) {
12
- if (raw === null || raw === undefined || raw === "")
13
- return 512;
14
- const n = Number.parseInt(raw, 10);
15
- if (Number.isNaN(n))
16
- return 512;
17
- return Math.min(8192, Math.max(64, n));
18
- }
19
- export class Perf {
20
- enabled;
21
- capacity;
22
- rings;
23
- cursors;
24
- counts;
25
- constructor(opts) {
26
- this.enabled = opts.enabled;
27
- if (typeof opts.capacity === "string") {
28
- this.capacity = clampCapacity(opts.capacity);
29
- }
30
- else if (typeof opts.capacity === "number") {
31
- this.capacity = Math.min(8192, Math.max(64, Math.floor(opts.capacity)));
32
- }
33
- else {
34
- this.capacity = 512;
35
- }
36
- this.rings = new Map();
37
- this.cursors = new Map();
38
- this.counts = new Map();
39
- const scopes = BUDGETS.map((b) => b.scope);
40
- for (const s of scopes) {
41
- this.rings.set(s, new Float64Array(this.capacity));
42
- this.cursors.set(s, 0);
43
- this.counts.set(s, 0);
44
- }
45
- }
46
- static fromSettings(settings) {
47
- const enabled = settings.perf_enabled === "1";
48
- const cap = clampCapacity(settings.perf_ring_capacity);
49
- return new Perf({ enabled, capacity: cap });
50
- }
51
- measure(scope, fn) {
52
- if (!this.enabled)
53
- return fn();
54
- const start = performance.now();
55
- try {
56
- return fn();
57
- }
58
- finally {
59
- const dur = performance.now() - start;
60
- this.record(scope, dur);
61
- }
62
- }
63
- async measureAsync(scope, fn) {
64
- if (!this.enabled)
65
- return fn();
66
- const start = performance.now();
67
- try {
68
- return await fn();
69
- }
70
- finally {
71
- const dur = performance.now() - start;
72
- this.record(scope, dur);
73
- }
74
- }
75
- record(scope, ms) {
76
- const ring = this.rings.get(scope);
77
- if (!ring)
78
- return;
79
- const cur = this.cursors.get(scope) ?? 0;
80
- ring[cur % this.capacity] = ms;
81
- this.cursors.set(scope, (cur + 1) % this.capacity);
82
- this.counts.set(scope, (this.counts.get(scope) ?? 0) + 1);
83
- }
84
- stats() {
85
- const out = [];
86
- for (const b of BUDGETS) {
87
- const ring = this.rings.get(b.scope);
88
- if (!ring)
89
- continue;
90
- const count = this.counts.get(b.scope) ?? 0;
91
- if (count === 0) {
92
- out.push({
93
- scope: b.scope,
94
- count: 0,
95
- p50: 0,
96
- p95: 0,
97
- max: 0,
98
- budget: b,
99
- withinBudget: true,
100
- });
101
- continue;
102
- }
103
- const n = Math.min(count, this.capacity);
104
- // Copy live samples: need to reconstruct in insertion order but for percentiles order doesn't matter.
105
- // Take last n entries from ring (wrap-aware).
106
- const samples = [];
107
- const cur = this.cursors.get(b.scope) ?? 0;
108
- // If count < capacity, samples are ring[0..count-1]; else ring[0..capacity-1] all valid but order is cur..cur+cap
109
- if (count < this.capacity) {
110
- for (let i = 0; i < count; i++)
111
- samples.push(ring[i]);
112
- }
113
- else {
114
- for (let i = 0; i < this.capacity; i++)
115
- samples.push(ring[i]);
116
- }
117
- const sorted = [...samples].sort((a, b) => a - b);
118
- // Percentile method: nearest-rank — p is the value at index ceil(p/100 * n) - 1
119
- // of the ascending-sorted copy. Stated here so an implementer comparing against
120
- // another percentile definition can see which one was chosen.
121
- const p50 = sorted[Math.max(0, Math.ceil(sorted.length * 0.5) - 1)];
122
- const p95 = sorted[Math.max(0, Math.ceil(sorted.length * 0.95) - 1)];
123
- const max = sorted[sorted.length - 1];
124
- out.push({
125
- scope: b.scope,
126
- count,
127
- p50,
128
- p95,
129
- max,
130
- budget: b,
131
- withinBudget: p95 <= b.p95Ms,
132
- });
133
- }
134
- return out;
135
- }
136
- flush(store) {
137
- if (!this.enabled)
138
- return;
139
- const stats = this.stats();
140
- let recorded = 0;
141
- let breaches = 0;
142
- for (const s of stats) {
143
- if (s.count === 0)
144
- continue;
145
- store
146
- .prepare("INSERT INTO perf_samples (scope, sample_count, p50_ms, p95_ms, max_ms, budget_p95_ms, within_budget) VALUES (?, ?, ?, ?, ?, ?, ?)")
147
- .run(s.scope, s.count, s.p50, s.p95, s.max, s.budget.p95Ms, s.withinBudget ? 1 : 0);
148
- recorded++;
149
- if (!s.withinBudget)
150
- breaches++;
151
- }
152
- if (recorded > 0) {
153
- store
154
- .prepare(`UPDATE kevin_metrics SET value = value + ? WHERE key = 'perf_samples_recorded'`)
155
- .run(recorded);
156
- }
157
- if (breaches > 0) {
158
- store
159
- .prepare(`UPDATE kevin_metrics SET value = value + ? WHERE key = 'perf_budget_breaches'`)
160
- .run(breaches);
161
- }
162
- this.reset();
163
- }
164
- reset() {
165
- for (const b of BUDGETS) {
166
- // Zero the ring too: cursors and counts alone would let the next
167
- // period's stats() read stale pre-reset samples whenever
168
- // count < capacity (review fix, v1.0.0).
169
- this.rings.get(b.scope)?.fill(0);
170
- this.cursors.set(b.scope, 0);
171
- this.counts.set(b.scope, 0);
172
- }
173
- }
174
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * K4-013 — shared FTS5 query tokenizer.
3
- *
4
- * Injection recall ORs the quoted tokens (`"t1" OR "t2"`) while
5
- * `kevin_why` ANDs them (`"t1" AND "t2"`).
6
- */
7
- export declare const STOP_WORDS: Set<string>;
8
- /** Lowercase, split on whitespace, drop stopwords. Returns raw tokens. */
9
- export declare function tokenizeQuery(query: string): string[];
10
- /** Quote a token for an FTS5 MATCH clause, escaping embedded quotes. */
11
- export declare function quoteToken(token: string): string;
12
- /** Join quoted tokens into a MATCH clause with the given separator. */
13
- export declare function toMatchClause(tokens: string[], separator: " OR " | " AND "): string;
@@ -1,133 +0,0 @@
1
- /**
2
- * K4-013 — shared FTS5 query tokenizer.
3
- *
4
- * Injection recall ORs the quoted tokens (`"t1" OR "t2"`) while
5
- * `kevin_why` ANDs them (`"t1" AND "t2"`).
6
- */
7
- // v1.1.0 (K11-012 / plan §5.5, D11-05) — single source for STOP_WORDS (union of three lists)
8
- export const STOP_WORDS = new Set([
9
- "a",
10
- "about",
11
- "after",
12
- "again",
13
- "all",
14
- "also",
15
- "an",
16
- "and",
17
- "any",
18
- "are",
19
- "as",
20
- "at",
21
- "be",
22
- "been",
23
- "before",
24
- "being",
25
- "but",
26
- "by",
27
- "can",
28
- "como",
29
- "con",
30
- "could",
31
- "de",
32
- "did",
33
- "do",
34
- "does",
35
- "el",
36
- "en",
37
- "eso",
38
- "for",
39
- "from",
40
- "had",
41
- "has",
42
- "have",
43
- "he",
44
- "her",
45
- "his",
46
- "how",
47
- "i",
48
- "if",
49
- "in",
50
- "into",
51
- "is",
52
- "it",
53
- "its",
54
- "la",
55
- "las",
56
- "los",
57
- "may",
58
- "mi",
59
- "might",
60
- "more",
61
- "most",
62
- "must",
63
- "my",
64
- "not",
65
- "o",
66
- "of",
67
- "on",
68
- "one",
69
- "or",
70
- "our",
71
- "para",
72
- "per",
73
- "por",
74
- "que",
75
- "shall",
76
- "she",
77
- "sin",
78
- "so",
79
- "su",
80
- "than",
81
- "that",
82
- "the",
83
- "their",
84
- "them",
85
- "then",
86
- "there",
87
- "these",
88
- "they",
89
- "this",
90
- "those",
91
- "through",
92
- "to",
93
- "too",
94
- "tu",
95
- "un",
96
- "una",
97
- "under",
98
- "up",
99
- "us",
100
- "via",
101
- "was",
102
- "we",
103
- "were",
104
- "what",
105
- "when",
106
- "where",
107
- "which",
108
- "while",
109
- "who",
110
- "why",
111
- "will",
112
- "with",
113
- "would",
114
- "y",
115
- "you",
116
- "your",
117
- ]);
118
- /** Lowercase, split on whitespace, drop stopwords. Returns raw tokens. */
119
- export function tokenizeQuery(query) {
120
- return query
121
- .trim()
122
- .toLowerCase()
123
- .split(/\s+/)
124
- .filter((t) => t.length > 0 && !STOP_WORDS.has(t));
125
- }
126
- /** Quote a token for an FTS5 MATCH clause, escaping embedded quotes. */
127
- export function quoteToken(token) {
128
- return `"${token.replace(/"/g, '""')}"`;
129
- }
130
- /** Join quoted tokens into a MATCH clause with the given separator. */
131
- export function toMatchClause(tokens, separator) {
132
- return tokens.map(quoteToken).join(separator);
133
- }
@@ -1,2 +0,0 @@
1
- export declare function redactPaths(text: string): string;
2
- export declare function stripPrivate(text: string): string;
@@ -1,18 +0,0 @@
1
- const PATH_PATTERNS = [
2
- /[a-z]:\\[^\s"'<>|*?:]+/gi,
3
- /\/(?:home|users|var|tmp|opt|etc|root|usr|app|work|workspace|code|repo|project|src|build|dist|packages|services|api|web|client|server|lib|node_modules)(?:\/[^\s"'<>|*?:]+)*/gi,
4
- ];
5
- export function redactPaths(text) {
6
- let out = text;
7
- for (const pat of PATH_PATTERNS) {
8
- out = out.replace(pat, "<path>");
9
- }
10
- return out;
11
- }
12
- const PRIVATE_BLOCK_RE = /<private\b[^>]*>([\s\S]*?)<\/private>/gi;
13
- export function stripPrivate(text) {
14
- return text.replace(PRIVATE_BLOCK_RE, (_match, inner) => {
15
- const n = inner.length;
16
- return `<private: redacted ${n} chars>`;
17
- });
18
- }
@@ -1,69 +0,0 @@
1
- /**
2
- * v0.5.0 (K5-018 / plan §5.8) — replay transcript format.
3
- *
4
- * A hermetic, deterministic way to run a recorded session through the
5
- * plugin's components and read the outcome distribution. `at` is an
6
- * ISO-8601 string and is the ONLY source of time during replay:
7
- * `Date.now()` must not be called anywhere in the replay path.
8
- *
9
- * v0.7.0 — hand-rolled validation: the zod dependency was removed
10
- * (docs/Kevin_v0.9.0_Plan.md D9-05). This module has no zod import.
11
- */
12
- export type ReplayEvent = {
13
- kind: "session.created";
14
- at: string;
15
- sessionId: string;
16
- } | {
17
- kind: "chat.message";
18
- at: string;
19
- sessionId: string;
20
- text: string;
21
- } | {
22
- kind: "tool.before";
23
- at: string;
24
- sessionId: string;
25
- callId: string;
26
- tool: string;
27
- args: Record<string, unknown>;
28
- } | {
29
- kind: "tool.after";
30
- at: string;
31
- sessionId: string;
32
- callId: string;
33
- success: boolean;
34
- stdout?: string;
35
- stderr?: string;
36
- exitCode?: number;
37
- } | {
38
- kind: "system.transform";
39
- at: string;
40
- sessionId: string;
41
- } | {
42
- kind: "compacting";
43
- at: string;
44
- sessionId: string;
45
- } | {
46
- kind: "session.idle";
47
- at: string;
48
- sessionId: string;
49
- };
50
- export interface ReplayTranscript {
51
- readonly version: 1;
52
- readonly name: string;
53
- readonly events: readonly ReplayEvent[];
54
- }
55
- /** Validates an unknown payload (e.g. a parsed JSON file) as a transcript. */
56
- export declare function parseTranscript(json: unknown): ReplayTranscript;
57
- interface SchemaLike<T> {
58
- parse(value: unknown): T;
59
- safeParse(value: unknown): {
60
- success: true;
61
- data: T;
62
- } | {
63
- success: false;
64
- error: Error;
65
- };
66
- }
67
- export declare const replayEventSchema: SchemaLike<ReplayEvent>;
68
- export declare const replayTranscriptSchema: SchemaLike<ReplayTranscript>;
69
- export {};
@@ -1,156 +0,0 @@
1
- /**
2
- * v0.5.0 (K5-018 / plan §5.8) — replay transcript format.
3
- *
4
- * A hermetic, deterministic way to run a recorded session through the
5
- * plugin's components and read the outcome distribution. `at` is an
6
- * ISO-8601 string and is the ONLY source of time during replay:
7
- * `Date.now()` must not be called anywhere in the replay path.
8
- *
9
- * v0.7.0 — hand-rolled validation: the zod dependency was removed
10
- * (docs/Kevin_v0.9.0_Plan.md D9-05). This module has no zod import.
11
- */
12
- const EVENT_KINDS = [
13
- "session.created",
14
- "chat.message",
15
- "tool.before",
16
- "tool.after",
17
- "system.transform",
18
- "compacting",
19
- "session.idle",
20
- ];
21
- const ISO_DATETIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
22
- function isObject(value) {
23
- return typeof value === "object" && value !== null && !Array.isArray(value);
24
- }
25
- function isIsoDatetime(value) {
26
- return (typeof value === "string" &&
27
- ISO_DATETIME_RE.test(value) &&
28
- !Number.isNaN(Date.parse(value)));
29
- }
30
- function isNonEmptyString(value) {
31
- return typeof value === "string" && value.length > 0;
32
- }
33
- function isInteger(value) {
34
- return typeof value === "number" && Number.isInteger(value);
35
- }
36
- function parseEvent(value) {
37
- if (!isObject(value)) {
38
- throw new Error("replay event must be an object");
39
- }
40
- const kind = value.kind;
41
- if (typeof kind !== "string" ||
42
- !EVENT_KINDS.includes(kind)) {
43
- throw new Error(`unknown replay event kind: ${String(kind)}`);
44
- }
45
- const at = value.at;
46
- if (!isIsoDatetime(at)) {
47
- throw new Error("replay event `at` must be an ISO-8601 datetime string");
48
- }
49
- const sessionId = value.sessionId;
50
- if (!isNonEmptyString(sessionId)) {
51
- throw new Error("replay event `sessionId` must be a non-empty string");
52
- }
53
- switch (kind) {
54
- case "session.created":
55
- case "system.transform":
56
- case "compacting":
57
- case "session.idle":
58
- return { kind, at, sessionId };
59
- case "chat.message": {
60
- const text = value.text;
61
- if (!isNonEmptyString(text)) {
62
- throw new Error("chat.message `text` must be a non-empty string");
63
- }
64
- return { kind, at, sessionId, text };
65
- }
66
- case "tool.before": {
67
- const callId = value.callId;
68
- if (!isNonEmptyString(callId)) {
69
- throw new Error("tool.before `callId` must be a non-empty string");
70
- }
71
- const tool = value.tool;
72
- if (!isNonEmptyString(tool)) {
73
- throw new Error("tool.before `tool` must be a non-empty string");
74
- }
75
- if (!isObject(value.args)) {
76
- throw new Error("tool.before `args` must be an object");
77
- }
78
- return { kind, at, sessionId, callId, tool, args: value.args };
79
- }
80
- case "tool.after": {
81
- const callId = value.callId;
82
- if (!isNonEmptyString(callId)) {
83
- throw new Error("tool.after `callId` must be a non-empty string");
84
- }
85
- if (typeof value.success !== "boolean") {
86
- throw new Error("tool.after `success` must be a boolean");
87
- }
88
- const event = {
89
- kind,
90
- at,
91
- sessionId,
92
- callId,
93
- success: value.success,
94
- };
95
- if (value.stdout !== undefined) {
96
- if (!isNonEmptyString(value.stdout)) {
97
- throw new Error("tool.after `stdout` must be a non-empty string");
98
- }
99
- event.stdout = value.stdout;
100
- }
101
- if (value.stderr !== undefined) {
102
- if (!isNonEmptyString(value.stderr)) {
103
- throw new Error("tool.after `stderr` must be a non-empty string");
104
- }
105
- event.stderr = value.stderr;
106
- }
107
- if (value.exitCode !== undefined) {
108
- if (!isInteger(value.exitCode)) {
109
- throw new Error("tool.after `exitCode` must be an integer");
110
- }
111
- event.exitCode = value.exitCode;
112
- }
113
- return event;
114
- }
115
- }
116
- throw new Error(`unhandled replay event kind: ${String(kind)}`);
117
- }
118
- /** Validates an unknown payload (e.g. a parsed JSON file) as a transcript. */
119
- export function parseTranscript(json) {
120
- if (!isObject(json)) {
121
- throw new Error("replay transcript must be an object");
122
- }
123
- if (json.version !== 1) {
124
- throw new Error("replay transcript `version` must be 1");
125
- }
126
- const name = json.name;
127
- if (!isNonEmptyString(name)) {
128
- throw new Error("replay transcript `name` must be a non-empty string");
129
- }
130
- if (!Array.isArray(json.events) || json.events.length === 0) {
131
- throw new Error("replay transcript `events` must be a non-empty array");
132
- }
133
- return {
134
- version: 1,
135
- name,
136
- events: json.events.map(parseEvent),
137
- };
138
- }
139
- function toSchema(parse) {
140
- return {
141
- parse,
142
- safeParse(value) {
143
- try {
144
- return { success: true, data: parse(value) };
145
- }
146
- catch (error) {
147
- return {
148
- success: false,
149
- error: error instanceof Error ? error : new Error(String(error)),
150
- };
151
- }
152
- },
153
- };
154
- }
155
- export const replayEventSchema = toSchema(parseEvent);
156
- export const replayTranscriptSchema = toSchema(parseTranscript);
@@ -1,36 +0,0 @@
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>;