@jmtrin/opencode-kevin 1.2.0 → 1.3.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 (141) hide show
  1. package/dist/plugin/index.d.ts +4 -3
  2. package/dist/plugin/index.js +122 -113
  3. package/dist/plugin/native.d.ts +2 -2
  4. package/dist/plugin/native.js +2 -2
  5. package/package.json +56 -68
  6. package/LICENSE +0 -21
  7. package/README.md +0 -605
  8. package/dist/migrations/001_initial.sql +0 -92
  9. package/dist/migrations/002_indexes.sql +0 -14
  10. package/dist/migrations/003_v02_signal.sql +0 -58
  11. package/dist/migrations/004_v03_knowledge.sql +0 -138
  12. package/dist/migrations/005_v04_signal.sql +0 -57
  13. package/dist/migrations/006_v05_glassbox.sql +0 -118
  14. package/dist/migrations/007_v06_pull.sql +0 -145
  15. package/dist/migrations/008_v07_truth.sql +0 -124
  16. package/dist/migrations/009_v08_team.sql +0 -100
  17. package/dist/migrations/010_v09_native.sql +0 -78
  18. package/dist/migrations/011_v10_proven.sql +0 -69
  19. package/dist/migrations/012_v11_drift.sql +0 -24
  20. package/dist/plugin/Archiver.d.ts +0 -42
  21. package/dist/plugin/Archiver.js +0 -83
  22. package/dist/plugin/ArtifactWriter.d.ts +0 -93
  23. package/dist/plugin/ArtifactWriter.js +0 -291
  24. package/dist/plugin/CausalChain.d.ts +0 -22
  25. package/dist/plugin/CausalChain.js +0 -197
  26. package/dist/plugin/ChatBridge.d.ts +0 -41
  27. package/dist/plugin/ChatBridge.js +0 -103
  28. package/dist/plugin/ConflictDetector.d.ts +0 -35
  29. package/dist/plugin/ConflictDetector.js +0 -260
  30. package/dist/plugin/ContextInjector.d.ts +0 -222
  31. package/dist/plugin/ContextInjector.js +0 -533
  32. package/dist/plugin/ConventionMiner.d.ts +0 -35
  33. package/dist/plugin/ConventionMiner.js +0 -242
  34. package/dist/plugin/Curator.d.ts +0 -116
  35. package/dist/plugin/Curator.js +0 -324
  36. package/dist/plugin/DashboardHtml.d.ts +0 -5
  37. package/dist/plugin/DashboardHtml.js +0 -180
  38. package/dist/plugin/Feedback.d.ts +0 -67
  39. package/dist/plugin/Feedback.js +0 -120
  40. package/dist/plugin/HookLiveness.d.ts +0 -88
  41. package/dist/plugin/HookLiveness.js +0 -322
  42. package/dist/plugin/InjectionLedger.d.ts +0 -98
  43. package/dist/plugin/InjectionLedger.js +0 -295
  44. package/dist/plugin/LessonFixer.d.ts +0 -44
  45. package/dist/plugin/LessonFixer.js +0 -45
  46. package/dist/plugin/Materializer.d.ts +0 -83
  47. package/dist/plugin/Materializer.js +0 -181
  48. package/dist/plugin/MemoryService.d.ts +0 -360
  49. package/dist/plugin/MemoryService.js +0 -1242
  50. package/dist/plugin/Migrate.d.ts +0 -17
  51. package/dist/plugin/Migrate.js +0 -271
  52. package/dist/plugin/PatternMiner.d.ts +0 -49
  53. package/dist/plugin/PatternMiner.js +0 -132
  54. package/dist/plugin/QualityGate.d.ts +0 -124
  55. package/dist/plugin/QualityGate.js +0 -119
  56. package/dist/plugin/Reflector.d.ts +0 -91
  57. package/dist/plugin/Reflector.js +0 -307
  58. package/dist/plugin/RepoIdentity.d.ts +0 -124
  59. package/dist/plugin/RepoIdentity.js +0 -300
  60. package/dist/plugin/RepoTruth.d.ts +0 -80
  61. package/dist/plugin/RepoTruth.js +0 -599
  62. package/dist/plugin/Retrospective.d.ts +0 -17
  63. package/dist/plugin/Retrospective.js +0 -216
  64. package/dist/plugin/SharedLayer.d.ts +0 -159
  65. package/dist/plugin/SharedLayer.js +0 -462
  66. package/dist/plugin/Store.d.ts +0 -29
  67. package/dist/plugin/Store.js +0 -50
  68. package/dist/plugin/ToolCallObserver.d.ts +0 -32
  69. package/dist/plugin/ToolCallObserver.js +0 -201
  70. package/dist/plugin/TuiActions.d.ts +0 -43
  71. package/dist/plugin/TuiActions.js +0 -181
  72. package/dist/plugin/TuiSnapshots.d.ts +0 -24
  73. package/dist/plugin/TuiSnapshots.js +0 -158
  74. package/dist/plugin/columns.d.ts +0 -11
  75. package/dist/plugin/columns.js +0 -54
  76. package/dist/plugin/confidence.d.ts +0 -8
  77. package/dist/plugin/confidence.js +0 -34
  78. package/dist/plugin/contract.d.ts +0 -54
  79. package/dist/plugin/contract.js +0 -386
  80. package/dist/plugin/diff.d.ts +0 -8
  81. package/dist/plugin/diff.js +0 -182
  82. package/dist/plugin/escape.d.ts +0 -39
  83. package/dist/plugin/escape.js +0 -60
  84. package/dist/plugin/fingerprint.d.ts +0 -27
  85. package/dist/plugin/fingerprint.js +0 -73
  86. package/dist/plugin/inferability.d.ts +0 -32
  87. package/dist/plugin/inferability.js +0 -88
  88. package/dist/plugin/kevin_approve.d.ts +0 -34
  89. package/dist/plugin/kevin_approve.js +0 -52
  90. package/dist/plugin/kevin_audit.d.ts +0 -229
  91. package/dist/plugin/kevin_audit.js +0 -631
  92. package/dist/plugin/kevin_bench.d.ts +0 -10
  93. package/dist/plugin/kevin_bench.js +0 -82
  94. package/dist/plugin/kevin_conflicts.d.ts +0 -9
  95. package/dist/plugin/kevin_conflicts.js +0 -50
  96. package/dist/plugin/kevin_contract.d.ts +0 -8
  97. package/dist/plugin/kevin_contract.js +0 -59
  98. package/dist/plugin/kevin_doctor.d.ts +0 -62
  99. package/dist/plugin/kevin_doctor.js +0 -196
  100. package/dist/plugin/kevin_facts.d.ts +0 -42
  101. package/dist/plugin/kevin_facts.js +0 -36
  102. package/dist/plugin/kevin_forget.d.ts +0 -33
  103. package/dist/plugin/kevin_forget.js +0 -260
  104. package/dist/plugin/kevin_native.d.ts +0 -29
  105. package/dist/plugin/kevin_native.js +0 -79
  106. package/dist/plugin/kevin_propose.d.ts +0 -23
  107. package/dist/plugin/kevin_propose.js +0 -14
  108. package/dist/plugin/kevin_publish.d.ts +0 -38
  109. package/dist/plugin/kevin_publish.js +0 -18
  110. package/dist/plugin/kevin_why.d.ts +0 -23
  111. package/dist/plugin/kevin_why.js +0 -111
  112. package/dist/plugin/memory-format.d.ts +0 -20
  113. package/dist/plugin/memory-format.js +0 -59
  114. package/dist/plugin/metrics.d.ts +0 -97
  115. package/dist/plugin/metrics.js +0 -288
  116. package/dist/plugin/okf-export.d.ts +0 -3
  117. package/dist/plugin/okf-export.js +0 -134
  118. package/dist/plugin/okf-import.d.ts +0 -76
  119. package/dist/plugin/okf-import.js +0 -271
  120. package/dist/plugin/okf.d.ts +0 -107
  121. package/dist/plugin/okf.js +0 -303
  122. package/dist/plugin/perf.d.ts +0 -35
  123. package/dist/plugin/perf.js +0 -174
  124. package/dist/plugin/query-tokenizer.d.ts +0 -13
  125. package/dist/plugin/query-tokenizer.js +0 -133
  126. package/dist/plugin/redact.d.ts +0 -2
  127. package/dist/plugin/redact.js +0 -18
  128. package/dist/plugin/replay-types.d.ts +0 -69
  129. package/dist/plugin/replay-types.js +0 -156
  130. package/dist/plugin/replay.d.ts +0 -36
  131. package/dist/plugin/replay.js +0 -202
  132. package/dist/plugin/sqlite-adapter.d.ts +0 -12
  133. package/dist/plugin/sqlite-adapter.js +0 -81
  134. package/dist/plugin/time-ms.d.ts +0 -1
  135. package/dist/plugin/time-ms.js +0 -16
  136. package/dist/plugin/tui-types.d.ts +0 -59
  137. package/dist/plugin/tui-types.js +0 -4
  138. package/dist/plugin/tui.d.ts +0 -18
  139. package/dist/plugin/tui.js +0 -198
  140. package/dist/plugin/uuid.d.ts +0 -1
  141. 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>;