@jmtrin/opencode-kevin 0.9.0 → 1.0.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 (131) hide show
  1. package/README.md +558 -823
  2. package/dist/migrations/011_v10_proven.sql +69 -0
  3. package/dist/plugin/Archiver.js +0 -1
  4. package/dist/plugin/ArtifactWriter.d.ts +25 -10
  5. package/dist/plugin/ArtifactWriter.js +43 -25
  6. package/dist/plugin/CausalChain.js +0 -1
  7. package/dist/plugin/ConflictDetector.js +0 -1
  8. package/dist/plugin/ContextInjector.js +0 -1
  9. package/dist/plugin/ConventionMiner.js +0 -1
  10. package/dist/plugin/Curator.js +0 -1
  11. package/dist/plugin/Feedback.js +0 -1
  12. package/dist/plugin/HookLiveness.d.ts +6 -0
  13. package/dist/plugin/HookLiveness.js +15 -1
  14. package/dist/plugin/InjectionLedger.js +0 -1
  15. package/dist/plugin/LessonFixer.js +0 -1
  16. package/dist/plugin/Materializer.js +0 -1
  17. package/dist/plugin/MemoryService.js +0 -1
  18. package/dist/plugin/Migrate.d.ts +1 -1
  19. package/dist/plugin/Migrate.js +25 -3
  20. package/dist/plugin/PatternMiner.js +0 -1
  21. package/dist/plugin/QualityGate.js +0 -1
  22. package/dist/plugin/Reflector.js +0 -1
  23. package/dist/plugin/RepoIdentity.js +0 -1
  24. package/dist/plugin/RepoTruth.js +0 -1
  25. package/dist/plugin/Retrospective.js +7 -1
  26. package/dist/plugin/SharedLayer.js +0 -1
  27. package/dist/plugin/Store.js +0 -1
  28. package/dist/plugin/ToolCallObserver.js +0 -1
  29. package/dist/plugin/capabilities.js +0 -1
  30. package/dist/plugin/confidence.js +0 -1
  31. package/dist/plugin/contract.d.ts +46 -0
  32. package/dist/plugin/contract.js +368 -0
  33. package/dist/plugin/diff.js +0 -1
  34. package/dist/plugin/escape.d.ts +39 -0
  35. package/dist/plugin/escape.js +60 -0
  36. package/dist/plugin/fingerprint.js +0 -1
  37. package/dist/plugin/host.js +0 -1
  38. package/dist/plugin/index.d.ts +2 -2
  39. package/dist/plugin/index.js +425 -227
  40. package/dist/plugin/inferability.js +0 -1
  41. package/dist/plugin/kevin_approve.js +0 -1
  42. package/dist/plugin/kevin_audit.d.ts +31 -0
  43. package/dist/plugin/kevin_audit.js +60 -1
  44. package/dist/plugin/kevin_bench.d.ts +10 -0
  45. package/dist/plugin/kevin_bench.js +82 -0
  46. package/dist/plugin/kevin_conflicts.js +0 -1
  47. package/dist/plugin/kevin_contract.d.ts +8 -0
  48. package/dist/plugin/kevin_contract.js +59 -0
  49. package/dist/plugin/kevin_doctor.d.ts +5 -0
  50. package/dist/plugin/kevin_doctor.js +30 -2
  51. package/dist/plugin/kevin_facts.js +0 -1
  52. package/dist/plugin/kevin_native.js +0 -1
  53. package/dist/plugin/kevin_propose.js +0 -1
  54. package/dist/plugin/kevin_publish.js +0 -1
  55. package/dist/plugin/kevin_why.js +0 -1
  56. package/dist/plugin/memory-format.js +0 -1
  57. package/dist/plugin/metrics.js +0 -1
  58. package/dist/plugin/native.js +0 -1
  59. package/dist/plugin/okf-export.js +0 -1
  60. package/dist/plugin/okf-import.js +0 -1
  61. package/dist/plugin/okf.js +0 -1
  62. package/dist/plugin/perf.d.ts +35 -0
  63. package/dist/plugin/perf.js +174 -0
  64. package/dist/plugin/query-tokenizer.js +0 -1
  65. package/dist/plugin/redact.js +0 -1
  66. package/dist/plugin/replay-types.js +0 -1
  67. package/dist/plugin/replay.js +0 -1
  68. package/dist/plugin/sqlite-adapter.js +0 -1
  69. package/dist/plugin/uuid.js +0 -1
  70. package/package.json +21 -6
  71. package/dist/plugin/Archiver.js.map +0 -1
  72. package/dist/plugin/ArtifactWriter.js.map +0 -1
  73. package/dist/plugin/CausalChain.js.map +0 -1
  74. package/dist/plugin/ConflictDetector.js.map +0 -1
  75. package/dist/plugin/ContextInjector.js.map +0 -1
  76. package/dist/plugin/ConventionMiner.js.map +0 -1
  77. package/dist/plugin/Curator.js.map +0 -1
  78. package/dist/plugin/Feedback.js.map +0 -1
  79. package/dist/plugin/HookLiveness.js.map +0 -1
  80. package/dist/plugin/InjectionLedger.js.map +0 -1
  81. package/dist/plugin/LessonFixer.js.map +0 -1
  82. package/dist/plugin/Materializer.js.map +0 -1
  83. package/dist/plugin/MemoryService.js.map +0 -1
  84. package/dist/plugin/Migrate.js.map +0 -1
  85. package/dist/plugin/PatternMiner.js.map +0 -1
  86. package/dist/plugin/QualityGate.js.map +0 -1
  87. package/dist/plugin/Reflector.js.map +0 -1
  88. package/dist/plugin/RepoIdentity.js.map +0 -1
  89. package/dist/plugin/RepoTruth.js.map +0 -1
  90. package/dist/plugin/Retrospective.js.map +0 -1
  91. package/dist/plugin/SharedLayer.js.map +0 -1
  92. package/dist/plugin/Store.js.map +0 -1
  93. package/dist/plugin/ToolCallObserver.js.map +0 -1
  94. package/dist/plugin/capabilities.js.map +0 -1
  95. package/dist/plugin/confidence.js.map +0 -1
  96. package/dist/plugin/diff.js.map +0 -1
  97. package/dist/plugin/fingerprint.js.map +0 -1
  98. package/dist/plugin/host.js.map +0 -1
  99. package/dist/plugin/index.js.map +0 -1
  100. package/dist/plugin/inferability.js.map +0 -1
  101. package/dist/plugin/kevin_approve.js.map +0 -1
  102. package/dist/plugin/kevin_audit.js.map +0 -1
  103. package/dist/plugin/kevin_conflicts.js.map +0 -1
  104. package/dist/plugin/kevin_doctor.js.map +0 -1
  105. package/dist/plugin/kevin_facts.js.map +0 -1
  106. package/dist/plugin/kevin_native.js.map +0 -1
  107. package/dist/plugin/kevin_propose.js.map +0 -1
  108. package/dist/plugin/kevin_publish.js.map +0 -1
  109. package/dist/plugin/kevin_why.js.map +0 -1
  110. package/dist/plugin/memory-format.js.map +0 -1
  111. package/dist/plugin/metrics.js.map +0 -1
  112. package/dist/plugin/native.js.map +0 -1
  113. package/dist/plugin/okf-export.js.map +0 -1
  114. package/dist/plugin/okf-import.js.map +0 -1
  115. package/dist/plugin/okf.js.map +0 -1
  116. package/dist/plugin/query-tokenizer.js.map +0 -1
  117. package/dist/plugin/redact.js.map +0 -1
  118. package/dist/plugin/replay-types.js.map +0 -1
  119. package/dist/plugin/replay.js.map +0 -1
  120. package/dist/plugin/sqlite-adapter.js.map +0 -1
  121. package/dist/plugin/uuid.js.map +0 -1
  122. package/migrations/001_initial.sql +0 -92
  123. package/migrations/002_indexes.sql +0 -14
  124. package/migrations/003_v02_signal.sql +0 -58
  125. package/migrations/004_v03_knowledge.sql +0 -138
  126. package/migrations/005_v04_signal.sql +0 -57
  127. package/migrations/006_v05_glassbox.sql +0 -118
  128. package/migrations/007_v06_pull.sql +0 -145
  129. package/migrations/008_v07_truth.sql +0 -124
  130. package/migrations/009_v08_team.sql +0 -100
  131. package/migrations/010_v09_native.sql +0 -78
@@ -86,4 +86,3 @@ export function classify(memory) {
86
86
  // Rule 5.
87
87
  return "unknown";
88
88
  }
89
- //# sourceMappingURL=inferability.js.map
@@ -50,4 +50,3 @@ export function kevinApprove(store, memoryService, curator, writer, metrics, arg
50
50
  function sqliteNow() {
51
51
  return new Date().toISOString().slice(0, 19).replace("T", " ");
52
52
  }
53
- //# sourceMappingURL=kevin_approve.js.map
@@ -122,6 +122,37 @@ export interface AuditReport {
122
122
  };
123
123
  verdict: "healthy" | "degraded" | "unknown";
124
124
  };
125
+ /**
126
+ * v1.0.0 (K10-020 / plan §5.6) — the perf block: per scope, the most
127
+ * recent `perf_samples` aggregate row (count/p50/p95/max) plus the
128
+ * stored budget and verdict. The stored aggregates are used DIRECTLY —
129
+ * re-aggregating aggregates would produce a number with no meaning
130
+ * (plan anti-pattern 42), and using the most recent row per scope is
131
+ * exactly what `bench:check` does, so `within_budget` agrees with it
132
+ * on the same data. Omitted (not zero-valued) on a pre-011 database,
133
+ * following the host precedent.
134
+ */
135
+ perf?: {
136
+ scopes: Record<string, {
137
+ count: number;
138
+ p50: number;
139
+ p95: number;
140
+ max: number;
141
+ budget_p95: number;
142
+ within_budget: boolean;
143
+ }>;
144
+ };
145
+ /**
146
+ * v1.0.0 (K10-020 / plan §5.6) — the contract block: the live contract's
147
+ * identity, computed from source rather than the database, so it is
148
+ * present even on a pre-011 database.
149
+ */
150
+ contract?: {
151
+ contract_version: number;
152
+ digest: string;
153
+ clause_count: number;
154
+ deprecated_count: number;
155
+ };
125
156
  partial: boolean;
126
157
  }
127
158
  /**
@@ -1,5 +1,7 @@
1
1
  import { effectivePrePromptCap } from "./ContextInjector.js";
2
2
  import { hasRepoIdColumn } from "./MemoryService.js";
3
+ import { contractDigest, describeContract } from "./contract.js";
4
+ import { BUDGETS } from "./perf.js";
3
5
  const ALL_FALSE_CAPABILITIES = {
4
6
  skills: false,
5
7
  references: false,
@@ -484,6 +486,62 @@ export function buildAudit(store, metrics, capabilities = ALL_FALSE_CAPABILITIES
484
486
  // marking the report partial — the host block is additive, unlike
485
487
  // channels/curation which are the v0.6.0 scoreboard. Existing
486
488
  // v0.8.0-era tests run on 007/008 stores and expect partial false.
489
+ // v1.0.0 (K10-020 / plan §5.6) — perf block: the most recent aggregate
490
+ // row per scope, read directly from perf_samples. A scope with no rows
491
+ // reports count 0, zero timings and within_budget true — never NULL,
492
+ // never NaN. Omitted on a pre-011 database (host precedent).
493
+ let perf;
494
+ let hasSchema011 = false;
495
+ try {
496
+ const row = store
497
+ .prepare("SELECT 1 AS n FROM sqlite_master WHERE type='table' AND name='perf_samples'")
498
+ .get();
499
+ hasSchema011 = !!row;
500
+ }
501
+ catch {
502
+ hasSchema011 = false;
503
+ }
504
+ if (hasSchema011) {
505
+ try {
506
+ const scopes = {};
507
+ const latest = store.prepare(`SELECT sample_count AS count, p50_ms AS p50, p95_ms AS p95,
508
+ max_ms AS max, budget_p95_ms AS budget_p95, within_budget
509
+ FROM perf_samples WHERE scope = ? ORDER BY id DESC LIMIT 1`);
510
+ for (const budget of BUDGETS) {
511
+ const row = latest.get(budget.scope);
512
+ scopes[budget.scope] = row
513
+ ? {
514
+ count: row.count,
515
+ p50: row.p50,
516
+ p95: row.p95,
517
+ max: row.max,
518
+ budget_p95: row.budget_p95,
519
+ within_budget: row.within_budget === 1,
520
+ }
521
+ : {
522
+ count: 0,
523
+ p50: 0,
524
+ p95: 0,
525
+ max: 0,
526
+ budget_p95: budget.p95Ms,
527
+ within_budget: true,
528
+ };
529
+ }
530
+ perf = { scopes };
531
+ }
532
+ catch {
533
+ perf = undefined;
534
+ }
535
+ }
536
+ // v1.0.0 (K10-020 / plan §5.6) — contract block: pure function of the
537
+ // source, no database involvement.
538
+ const liveContract = describeContract();
539
+ const contract = {
540
+ contract_version: liveContract.contractVersion,
541
+ digest: contractDigest(liveContract),
542
+ clause_count: liveContract.clauses.length,
543
+ deprecated_count: liveContract.clauses.filter((c) => c.deprecated).length,
544
+ };
487
545
  return {
488
546
  memories,
489
547
  injections,
@@ -498,7 +556,8 @@ export function buildAudit(store, metrics, capabilities = ALL_FALSE_CAPABILITIES
498
556
  curation,
499
557
  team,
500
558
  host,
559
+ perf,
560
+ contract,
501
561
  partial,
502
562
  };
503
563
  }
504
- //# sourceMappingURL=kevin_audit.js.map
@@ -0,0 +1,10 @@
1
+ import type { Store } from "./Store.js";
2
+ export interface KevinBenchDeps {
3
+ store: Store;
4
+ /** Working directory used to locate bench/corpus (defaults process.cwd()). */
5
+ cwd?: string;
6
+ }
7
+ export interface KevinBenchArgs {
8
+ action: "status" | "last";
9
+ }
10
+ export declare function buildKevinBench(deps: KevinBenchDeps, args: KevinBenchArgs): Record<string, unknown>;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * K10-019 — kevin_bench: reports what `npm run bench` recorded.
3
+ *
4
+ * It NEVER runs the benchmark — running it from inside a live session
5
+ * would measure the session and pollute the user's database. The module
6
+ * imports nothing from scripts/bench.ts; the corpus digest of the on-disk
7
+ * corpus is recomputed here with the same fnv1a64 discipline so a result
8
+ * computed from a different corpus is visibly a different measurement.
9
+ */
10
+ import { readFileSync, statSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { fnv1a64 } from "./fingerprint.js";
13
+ function onDiskCorpusDigest(cwd) {
14
+ try {
15
+ const dir = join(cwd, "bench", "corpus");
16
+ statSync(dir);
17
+ const mem = readFileSync(join(dir, "memories.jsonl"), "utf8");
18
+ const q = readFileSync(join(dir, "queries.jsonl"), "utf8");
19
+ return {
20
+ digest: fnv1a64(`${mem}\n${q}`),
21
+ queries: q.split("\n").filter((l) => l.trim().length > 0).length,
22
+ };
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ function noRuns() {
29
+ return {
30
+ has_runs: false,
31
+ hint: "npm run bench",
32
+ };
33
+ }
34
+ export function buildKevinBench(deps, args) {
35
+ if (args.action === "status") {
36
+ const count = deps.store
37
+ .prepare("SELECT COUNT(*) AS c FROM bench_runs")
38
+ .get();
39
+ if (!count || count.c === 0)
40
+ return noRuns();
41
+ const latest = deps.store
42
+ .prepare("SELECT corpus_digest, contract_digest, package_version, runtime, ran_at FROM bench_runs ORDER BY id DESC LIMIT 1")
43
+ .get();
44
+ if (!latest)
45
+ return noRuns();
46
+ const disk = onDiskCorpusDigest(deps.cwd ?? process.cwd());
47
+ return {
48
+ has_runs: true,
49
+ total_runs: count.c,
50
+ corpus_digest: latest.corpus_digest,
51
+ contract_digest: latest.contract_digest,
52
+ package_version: latest.package_version,
53
+ runtime: latest.runtime,
54
+ last_ran_at: latest.ran_at,
55
+ corpus_on_disk: disk === null ? null : disk.digest,
56
+ matches_disk_corpus: disk !== null && disk.digest === latest.corpus_digest,
57
+ };
58
+ }
59
+ const latest = deps.store
60
+ .prepare("SELECT corpus_digest, contract_digest, package_version, runtime, ran_at FROM bench_runs ORDER BY id DESC LIMIT 1")
61
+ .get();
62
+ if (!latest)
63
+ return noRuns();
64
+ const rows = deps.store
65
+ .prepare("SELECT arm, k, precision_at_k, recall_at_k, mrr FROM bench_runs WHERE corpus_digest = ? AND ran_at = ?")
66
+ .all(latest.corpus_digest, latest.ran_at);
67
+ return {
68
+ has_runs: true,
69
+ corpus_digest: latest.corpus_digest,
70
+ contract_digest: latest.contract_digest,
71
+ package_version: latest.package_version,
72
+ runtime: latest.runtime,
73
+ ran_at: latest.ran_at,
74
+ arms: rows.map((r) => ({
75
+ arm: r.arm,
76
+ k: r.k,
77
+ precision_at_k: r.precision_at_k,
78
+ recall_at_k: r.recall_at_k,
79
+ mrr: r.mrr,
80
+ })),
81
+ };
82
+ }
@@ -48,4 +48,3 @@ export function executeKevinConflicts(deps, action, id, keep, requestedStatus) {
48
48
  conflicts: rows,
49
49
  };
50
50
  }
51
- //# sourceMappingURL=kevin_conflicts.js.map
@@ -0,0 +1,8 @@
1
+ export interface KevinContractDeps {
2
+ packageVersion: string;
3
+ }
4
+ export interface KevinContractArgs {
5
+ clause?: string;
6
+ format?: "summary" | "full";
7
+ }
8
+ export declare function buildKevinContract(deps: KevinContractDeps, args: KevinContractArgs): Record<string, unknown>;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * K10-018 — kevin_contract: the frozen surface, inspectable at runtime.
3
+ *
4
+ * Read-only. Returns the live contract with its digest and per-clause
5
+ * stability/since/deprecation; with `clause`, one clause's full value.
6
+ * No filesystem paths, no project ids, no LLM, no network.
7
+ */
8
+ import { contractDigest, describeContract, } from "./contract.js";
9
+ export function buildKevinContract(deps, args) {
10
+ const contract = describeContract();
11
+ const digest = contractDigest(contract);
12
+ const format = args.format ?? "summary";
13
+ if (args.clause !== undefined) {
14
+ const clause = contract.clauses.find((c) => c.id === args.clause);
15
+ if (!clause) {
16
+ return {
17
+ error: "unknown_clause",
18
+ clause: args.clause,
19
+ known_clauses: contract.clauses.map((c) => c.id),
20
+ };
21
+ }
22
+ return {
23
+ contract_version: contract.contractVersion,
24
+ digest,
25
+ package_version: deps.packageVersion,
26
+ clause: {
27
+ id: clause.id,
28
+ title: clause.title,
29
+ stability: clause.stability,
30
+ since: clause.since,
31
+ ...(clause.deprecated ? { deprecated: clause.deprecated } : {}),
32
+ ...(clause.replacement ? { replacement: clause.replacement } : {}),
33
+ value: clause.value,
34
+ },
35
+ };
36
+ }
37
+ if (format === "full") {
38
+ return {
39
+ contract_version: contract.contractVersion,
40
+ digest,
41
+ package_version: deps.packageVersion,
42
+ clauses: contract.clauses,
43
+ deprecated: contract.clauses.filter((c) => c.deprecated).map((c) => c.id),
44
+ };
45
+ }
46
+ return {
47
+ contract_version: contract.contractVersion,
48
+ digest,
49
+ package_version: deps.packageVersion,
50
+ clauses: contract.clauses.map((c) => ({
51
+ id: c.id,
52
+ title: c.title,
53
+ stability: c.stability,
54
+ since: c.since,
55
+ ...(c.deprecated ? { deprecated: c.deprecated } : {}),
56
+ })),
57
+ deprecated: contract.clauses.filter((c) => c.deprecated).map((c) => c.id),
58
+ };
59
+ }
@@ -36,6 +36,11 @@ export interface DoctorReport {
36
36
  readonly reference: boolean;
37
37
  };
38
38
  };
39
+ /** v1.0.0 (K10-021) — scopes whose most recent sample exceeded their
40
+ * p95 budget. Omitted entirely on pre-011 databases (no table). */
41
+ readonly perf?: {
42
+ readonly scopes_over_budget: readonly string[];
43
+ };
39
44
  readonly verdict: "healthy" | "degraded" | "unknown";
40
45
  readonly reason: string;
41
46
  readonly partial: boolean;
@@ -106,6 +106,22 @@ export function countZodCopies(cwd) {
106
106
  return { copies: null, note: "zod resolution walk failed" };
107
107
  }
108
108
  }
109
+ /** v1.0.0 (K10-021) — scopes whose most recent perf sample breached the
110
+ * p95 budget. The within_budget flag is materialized by Perf.flush()
111
+ * against the frozen BUDGETS; this block reads that persisted verdict
112
+ * and never re-aggregates. A missing table (pre-011 DB) yields no
113
+ * block rather than an empty one, matching the hooks precedent. */
114
+ function perfOverBudget(store) {
115
+ try {
116
+ const rows = store
117
+ .prepare("SELECT DISTINCT scope FROM perf_samples WHERE within_budget = 0 ORDER BY scope")
118
+ .all();
119
+ return rows.map((r) => r.scope);
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
109
125
  function lastRegistration(store, surface) {
110
126
  try {
111
127
  const row = store
@@ -124,7 +140,7 @@ export function buildDoctor(store, host, settings, options = {}) {
124
140
  // construction — kevin_doctor never re-probes (K9-004: probe once,
125
141
  // freeze, reuse).
126
142
  const hooks = hooksBlock(store);
127
- const verdict = reduceVerdict(hooks.hooks.map((h) => ({
143
+ let verdict = reduceVerdict(hooks.hooks.map((h) => ({
128
144
  hook: h.hook,
129
145
  experimental: h.experimental,
130
146
  state: h.state,
@@ -134,6 +150,18 @@ export function buildDoctor(store, host, settings, options = {}) {
134
150
  expectedCount: h.expected_count,
135
151
  deadSince: h.since ?? null,
136
152
  })));
153
+ // v1.0.0 (K10-021) — a slow plugin is not healthy even when every
154
+ // hook is live. A budget breach degrades the verdict and names the
155
+ // scope; a dead hook keeps its reason; `unknown` is never rounded up.
156
+ const breaches = perfOverBudget(store);
157
+ if (breaches !== null &&
158
+ breaches.length > 0 &&
159
+ verdict.verdict !== "degraded") {
160
+ verdict = {
161
+ verdict: "degraded",
162
+ reason: `perf budget exceeded: ${breaches.join(", ")}`,
163
+ };
164
+ }
137
165
  const zod = countZodCopies(options.zodRoot ?? process.cwd());
138
166
  const enabled = settings.getSetting("native_registration_enabled", "0") === "1";
139
167
  return {
@@ -162,7 +190,7 @@ export function buildDoctor(store, host, settings, options = {}) {
162
190
  },
163
191
  verdict: verdict.verdict,
164
192
  reason: verdict.reason,
193
+ ...(breaches !== null ? { perf: { scopes_over_budget: breaches } } : {}),
165
194
  partial: hooks.partial,
166
195
  };
167
196
  }
168
- //# sourceMappingURL=kevin_doctor.js.map
@@ -34,4 +34,3 @@ export function buildKevinFacts(deps, refresh) {
34
34
  penalized,
35
35
  };
36
36
  }
37
- //# sourceMappingURL=kevin_facts.js.map
@@ -77,4 +77,3 @@ export function handleNative(action, deps) {
77
77
  };
78
78
  return report;
79
79
  }
80
- //# sourceMappingURL=kevin_native.js.map
@@ -12,4 +12,3 @@ export function kevinPropose(curator, writer, kind) {
12
12
  })),
13
13
  };
14
14
  }
15
- //# sourceMappingURL=kevin_propose.js.map
@@ -16,4 +16,3 @@ export function kevinPublish(deps) {
16
16
  },
17
17
  };
18
18
  }
19
- //# sourceMappingURL=kevin_publish.js.map
@@ -126,4 +126,3 @@ export function kevinWhy(store, query) {
126
126
  related_rules: relatedRules,
127
127
  };
128
128
  }
129
- //# sourceMappingURL=kevin_why.js.map
@@ -57,4 +57,3 @@ export function formatMemorySnippets(memories, tag) {
57
57
  return "";
58
58
  return wrapBlock(memories.map(formatSnippet).join("\n"), tag);
59
59
  }
60
- //# sourceMappingURL=memory-format.js.map
@@ -278,4 +278,3 @@ export class Metrics {
278
278
  }
279
279
  }
280
280
  }
281
- //# sourceMappingURL=metrics.js.map
@@ -188,4 +188,3 @@ function bumpNativeCounter(store, key) {
188
188
  updated_at = datetime('now')`)
189
189
  .run(key);
190
190
  }
191
- //# sourceMappingURL=native.js.map
@@ -132,4 +132,3 @@ export function exportMarkdown(store, projectId = null) {
132
132
  }
133
133
  return lines.join("\n");
134
134
  }
135
- //# sourceMappingURL=okf-export.js.map
@@ -269,4 +269,3 @@ export function importOkf(bundle, memoryService) {
269
269
  }
270
270
  return { imported, superseded };
271
271
  }
272
- //# sourceMappingURL=okf-import.js.map
@@ -301,4 +301,3 @@ export function parse(text) {
301
301
  export function computeEntryId(type, statement, scope) {
302
302
  return fnv1a64(`okf:v2\u0000${type}\u0000${statement}\u0000${scope ?? ""}`);
303
303
  }
304
- //# sourceMappingURL=okf.js.map
@@ -0,0 +1,35 @@
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
+ }
@@ -0,0 +1,174 @@
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
+ }
@@ -83,4 +83,3 @@ export function quoteToken(token) {
83
83
  export function toMatchClause(tokens, separator) {
84
84
  return tokens.map(quoteToken).join(separator);
85
85
  }
86
- //# sourceMappingURL=query-tokenizer.js.map
@@ -16,4 +16,3 @@ export function stripPrivate(text) {
16
16
  return `<private: redacted ${n} chars>`;
17
17
  });
18
18
  }
19
- //# sourceMappingURL=redact.js.map
@@ -154,4 +154,3 @@ function toSchema(parse) {
154
154
  }
155
155
  export const replayEventSchema = toSchema(parseEvent);
156
156
  export const replayTranscriptSchema = toSchema(parseTranscript);
157
- //# sourceMappingURL=replay-types.js.map
@@ -200,4 +200,3 @@ function totalMemories(store) {
200
200
  const row = store.prepare("SELECT COUNT(*) AS n FROM memories").get();
201
201
  return row.n ?? 0;
202
202
  }
203
- //# sourceMappingURL=replay.js.map