@kb-labs/adapters 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 (276) hide show
  1. package/.cursorrules +32 -0
  2. package/.github/workflows/ci.yml +13 -0
  3. package/.github/workflows/deploy.yml +28 -0
  4. package/.github/workflows/docker-build.yml +25 -0
  5. package/.github/workflows/drift-check.yml +10 -0
  6. package/.github/workflows/profiles-validate.yml +16 -0
  7. package/.github/workflows/release.yml +8 -0
  8. package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
  9. package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
  10. package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
  11. package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
  12. package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
  13. package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
  14. package/.kb/devkit/agents/release-manager/context.globs +7 -0
  15. package/.kb/devkit/agents/release-manager/prompt.md +27 -0
  16. package/.kb/devkit/agents/release-manager/runbook.md +17 -0
  17. package/.kb/devkit/agents/test-generator/context.globs +7 -0
  18. package/.kb/devkit/agents/test-generator/prompt.md +27 -0
  19. package/.kb/devkit/agents/test-generator/runbook.md +18 -0
  20. package/CONTRIBUTING.md +90 -0
  21. package/IMPLEMENTATION_COMPLETE.md +416 -0
  22. package/LICENSE +186 -0
  23. package/README-TEMPLATE.md +179 -0
  24. package/README.md +306 -0
  25. package/docs/DOCUMENTATION.md +74 -0
  26. package/docs/adr/0000-template.md +49 -0
  27. package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
  28. package/docs/adr/0002-plugins-and-extensibility.md +46 -0
  29. package/docs/adr/0003-package-and-module-boundaries.md +37 -0
  30. package/docs/adr/0004-versioning-and-release-policy.md +38 -0
  31. package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
  32. package/docs/adr/0006-adopt-devkit-sync.md +47 -0
  33. package/docs/adr/0007-drift-kit-check.md +72 -0
  34. package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
  35. package/docs/naming-convention.md +272 -0
  36. package/eslint.config.js +27 -0
  37. package/kb-labs.config.json +5 -0
  38. package/package.json +84 -0
  39. package/package.json.bin +25 -0
  40. package/package.json.lib +30 -0
  41. package/packages/adapters-analytics-duckdb/package.json +54 -0
  42. package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
  43. package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
  44. package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
  45. package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
  46. package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
  47. package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
  48. package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
  49. package/packages/adapters-analytics-file/README.md +32 -0
  50. package/packages/adapters-analytics-file/eslint.config.js +27 -0
  51. package/packages/adapters-analytics-file/package.json +50 -0
  52. package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
  53. package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
  54. package/packages/adapters-analytics-file/src/index.test.ts +214 -0
  55. package/packages/adapters-analytics-file/src/index.ts +830 -0
  56. package/packages/adapters-analytics-file/src/manifest.ts +45 -0
  57. package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
  58. package/packages/adapters-analytics-file/tsconfig.json +9 -0
  59. package/packages/adapters-analytics-file/tsup.config.ts +9 -0
  60. package/packages/adapters-analytics-sqlite/package.json +55 -0
  61. package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
  62. package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
  63. package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
  64. package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
  65. package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
  66. package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
  67. package/packages/adapters-environment-docker/README.md +28 -0
  68. package/packages/adapters-environment-docker/eslint.config.js +5 -0
  69. package/packages/adapters-environment-docker/package.json +49 -0
  70. package/packages/adapters-environment-docker/src/index.test.ts +138 -0
  71. package/packages/adapters-environment-docker/src/index.ts +439 -0
  72. package/packages/adapters-environment-docker/src/manifest.ts +65 -0
  73. package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
  74. package/packages/adapters-environment-docker/tsconfig.json +16 -0
  75. package/packages/adapters-environment-docker/tsup.config.ts +9 -0
  76. package/packages/adapters-eventbus-cache/README.md +242 -0
  77. package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
  78. package/packages/adapters-eventbus-cache/package.json +46 -0
  79. package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
  80. package/packages/adapters-eventbus-cache/src/index.ts +215 -0
  81. package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
  82. package/packages/adapters-eventbus-cache/src/types.ts +58 -0
  83. package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
  84. package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
  85. package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
  86. package/packages/adapters-fs/README.md +171 -0
  87. package/packages/adapters-fs/allowed.txt +1 -0
  88. package/packages/adapters-fs/conflict.txt +1 -0
  89. package/packages/adapters-fs/dest.txt +1 -0
  90. package/packages/adapters-fs/eslint.config.js +27 -0
  91. package/packages/adapters-fs/exists.txt +1 -0
  92. package/packages/adapters-fs/not-allowed.txt +1 -0
  93. package/packages/adapters-fs/other.txt +1 -0
  94. package/packages/adapters-fs/package.json +55 -0
  95. package/packages/adapters-fs/public/file1.txt +1 -0
  96. package/packages/adapters-fs/public/file2.txt +1 -0
  97. package/packages/adapters-fs/secret.txt +1 -0
  98. package/packages/adapters-fs/secrets/key.txt +1 -0
  99. package/packages/adapters-fs/src/index.test.ts +243 -0
  100. package/packages/adapters-fs/src/index.ts +258 -0
  101. package/packages/adapters-fs/src/manifest.ts +35 -0
  102. package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
  103. package/packages/adapters-fs/src/secure-storage.ts +268 -0
  104. package/packages/adapters-fs/test.json +1 -0
  105. package/packages/adapters-fs/test.txt +1 -0
  106. package/packages/adapters-fs/test.xyz +1 -0
  107. package/packages/adapters-fs/test1.txt +1 -0
  108. package/packages/adapters-fs/test2.txt +1 -0
  109. package/packages/adapters-fs/tsconfig.build.json +15 -0
  110. package/packages/adapters-fs/tsconfig.json +9 -0
  111. package/packages/adapters-fs/tsup.config.ts +8 -0
  112. package/packages/adapters-fs/vitest.config.ts +19 -0
  113. package/packages/adapters-log-ringbuffer/README.md +228 -0
  114. package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
  115. package/packages/adapters-log-ringbuffer/package.json +47 -0
  116. package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
  117. package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
  118. package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
  119. package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
  120. package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
  121. package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
  122. package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
  123. package/packages/adapters-log-sqlite/README.md +396 -0
  124. package/packages/adapters-log-sqlite/eslint.config.js +27 -0
  125. package/packages/adapters-log-sqlite/package.json +49 -0
  126. package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
  127. package/packages/adapters-log-sqlite/src/index.ts +1068 -0
  128. package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
  129. package/packages/adapters-log-sqlite/src/schema.sql +46 -0
  130. package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
  131. package/packages/adapters-log-sqlite/tsconfig.json +9 -0
  132. package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
  133. package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
  134. package/packages/adapters-mongodb/README.md +147 -0
  135. package/packages/adapters-mongodb/eslint.config.js +27 -0
  136. package/packages/adapters-mongodb/package.json +53 -0
  137. package/packages/adapters-mongodb/src/index.ts +428 -0
  138. package/packages/adapters-mongodb/src/manifest.ts +45 -0
  139. package/packages/adapters-mongodb/src/secure-document.ts +231 -0
  140. package/packages/adapters-mongodb/tsconfig.build.json +15 -0
  141. package/packages/adapters-mongodb/tsconfig.json +9 -0
  142. package/packages/adapters-mongodb/tsup.config.ts +8 -0
  143. package/packages/adapters-openai/README.md +151 -0
  144. package/packages/adapters-openai/embeddings.ts +37 -0
  145. package/packages/adapters-openai/eslint.config.js +26 -0
  146. package/packages/adapters-openai/index.ts +22 -0
  147. package/packages/adapters-openai/package.json +57 -0
  148. package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
  149. package/packages/adapters-openai/src/embeddings.ts +104 -0
  150. package/packages/adapters-openai/src/index.ts +13 -0
  151. package/packages/adapters-openai/src/llm.ts +304 -0
  152. package/packages/adapters-openai/src/manifest.ts +47 -0
  153. package/packages/adapters-openai/tsconfig.build.json +15 -0
  154. package/packages/adapters-openai/tsconfig.json +9 -0
  155. package/packages/adapters-openai/tsup.config.ts +8 -0
  156. package/packages/adapters-pino/README.md +152 -0
  157. package/packages/adapters-pino/eslint.config.js +27 -0
  158. package/packages/adapters-pino/package.json +49 -0
  159. package/packages/adapters-pino/src/index.test.ts +44 -0
  160. package/packages/adapters-pino/src/index.ts +322 -0
  161. package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
  162. package/packages/adapters-pino/src/manifest.ts +49 -0
  163. package/packages/adapters-pino/tsconfig.build.json +15 -0
  164. package/packages/adapters-pino/tsconfig.json +9 -0
  165. package/packages/adapters-pino/tsup.config.ts +9 -0
  166. package/packages/adapters-pino-http/README.md +141 -0
  167. package/packages/adapters-pino-http/eslint.config.js +27 -0
  168. package/packages/adapters-pino-http/package.json +46 -0
  169. package/packages/adapters-pino-http/src/index.ts +229 -0
  170. package/packages/adapters-pino-http/tsconfig.build.json +15 -0
  171. package/packages/adapters-pino-http/tsconfig.json +9 -0
  172. package/packages/adapters-pino-http/tsup.config.ts +9 -0
  173. package/packages/adapters-qdrant/README.md +166 -0
  174. package/packages/adapters-qdrant/eslint.config.js +27 -0
  175. package/packages/adapters-qdrant/package.json +49 -0
  176. package/packages/adapters-qdrant/src/index.ts +490 -0
  177. package/packages/adapters-qdrant/src/manifest.ts +54 -0
  178. package/packages/adapters-qdrant/src/retry.ts +204 -0
  179. package/packages/adapters-qdrant/tsconfig.build.json +15 -0
  180. package/packages/adapters-qdrant/tsconfig.json +9 -0
  181. package/packages/adapters-qdrant/tsup.config.ts +9 -0
  182. package/packages/adapters-redis/README.md +159 -0
  183. package/packages/adapters-redis/eslint.config.js +27 -0
  184. package/packages/adapters-redis/package.json +49 -0
  185. package/packages/adapters-redis/src/index.ts +164 -0
  186. package/packages/adapters-redis/src/manifest.ts +49 -0
  187. package/packages/adapters-redis/tsconfig.build.json +15 -0
  188. package/packages/adapters-redis/tsconfig.json +9 -0
  189. package/packages/adapters-redis/tsup.config.ts +9 -0
  190. package/packages/adapters-snapshot-localfs/README.md +10 -0
  191. package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
  192. package/packages/adapters-snapshot-localfs/package.json +46 -0
  193. package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
  194. package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
  195. package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
  196. package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
  197. package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
  198. package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
  199. package/packages/adapters-sqlite/README.md +163 -0
  200. package/packages/adapters-sqlite/eslint.config.js +27 -0
  201. package/packages/adapters-sqlite/package.json +54 -0
  202. package/packages/adapters-sqlite/src/index.test.ts +245 -0
  203. package/packages/adapters-sqlite/src/index.ts +382 -0
  204. package/packages/adapters-sqlite/src/manifest.ts +47 -0
  205. package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
  206. package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
  207. package/packages/adapters-sqlite/tsconfig.build.json +15 -0
  208. package/packages/adapters-sqlite/tsconfig.json +9 -0
  209. package/packages/adapters-sqlite/tsup.config.ts +8 -0
  210. package/packages/adapters-sqlite/vitest.config.ts +19 -0
  211. package/packages/adapters-transport/README.md +170 -0
  212. package/packages/adapters-transport/eslint.config.js +27 -0
  213. package/packages/adapters-transport/package.json +49 -0
  214. package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
  215. package/packages/adapters-transport/src/index.ts +101 -0
  216. package/packages/adapters-transport/src/ipc-transport.ts +228 -0
  217. package/packages/adapters-transport/src/transport.ts +224 -0
  218. package/packages/adapters-transport/src/types.ts +92 -0
  219. package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
  220. package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
  221. package/packages/adapters-transport/tsconfig.build.json +15 -0
  222. package/packages/adapters-transport/tsconfig.json +9 -0
  223. package/packages/adapters-transport/tsup.config.ts +9 -0
  224. package/packages/adapters-vibeproxy/README.md +159 -0
  225. package/packages/adapters-vibeproxy/eslint.config.js +27 -0
  226. package/packages/adapters-vibeproxy/package.json +51 -0
  227. package/packages/adapters-vibeproxy/src/index.ts +13 -0
  228. package/packages/adapters-vibeproxy/src/llm.ts +437 -0
  229. package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
  230. package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
  231. package/packages/adapters-vibeproxy/tsconfig.json +9 -0
  232. package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
  233. package/packages/adapters-workspace-agent/package.json +46 -0
  234. package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
  235. package/packages/adapters-workspace-agent/src/index.ts +220 -0
  236. package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
  237. package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
  238. package/packages/adapters-workspace-agent/tsconfig.json +16 -0
  239. package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
  240. package/packages/adapters-workspace-localfs/README.md +9 -0
  241. package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
  242. package/packages/adapters-workspace-localfs/package.json +46 -0
  243. package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
  244. package/packages/adapters-workspace-localfs/src/index.ts +172 -0
  245. package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
  246. package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
  247. package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
  248. package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
  249. package/packages/adapters-workspace-worktree/README.md +9 -0
  250. package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
  251. package/packages/adapters-workspace-worktree/package.json +46 -0
  252. package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
  253. package/packages/adapters-workspace-worktree/src/index.ts +245 -0
  254. package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
  255. package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
  256. package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
  257. package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
  258. package/pnpm-workspace.yaml +2800 -0
  259. package/prettierrc.json +1 -0
  260. package/scripts/devkit-sync.mjs +37 -0
  261. package/scripts/hooks/post-push +9 -0
  262. package/scripts/hooks/pre-commit +9 -0
  263. package/scripts/hooks/pre-push +9 -0
  264. package/test-integration.ts +242 -0
  265. package/test.txt +1 -0
  266. package/tsconfig.base.json +6 -0
  267. package/tsconfig.build.json +15 -0
  268. package/tsconfig.json +9 -0
  269. package/tsconfig.paths.json +26 -0
  270. package/tsconfig.tools.json +17 -0
  271. package/tsup.config.bin.ts +34 -0
  272. package/tsup.config.cli.ts +41 -0
  273. package/tsup.config.dual.ts +46 -0
  274. package/tsup.config.ts +36 -0
  275. package/tsup.external.json +103 -0
  276. package/vitest.config.ts +2 -0
@@ -0,0 +1,460 @@
1
+ /**
2
+ * @module @kb-labs/adapters-analytics-sqlite
3
+ * SQLite analytics adapter for KB Labs platform.
4
+ *
5
+ * Uses better-sqlite3 with WAL mode for concurrent reads/writes from multiple processes.
6
+ * Implements IAnalytics with SQL-native time-series aggregation:
7
+ * - strftime() for groupBy (hour/day/week/month)
8
+ * - json_extract() for breakdownBy (dot-notation paths)
9
+ * - Dynamic SELECT for metrics filtering
10
+ * - Full EventsQuery support via SQL WHERE clauses
11
+ */
12
+
13
+ import Database from 'better-sqlite3';
14
+ import { join, isAbsolute, dirname } from 'node:path';
15
+ import { mkdirSync } from 'node:fs';
16
+ import type {
17
+ IAnalytics,
18
+ IDisposable,
19
+ AnalyticsContext,
20
+ AnalyticsEvent,
21
+ EventsQuery,
22
+ StatsQuery,
23
+ EventsResponse,
24
+ EventsStats,
25
+ BufferStatus,
26
+ DlqStatus,
27
+ DailyStats,
28
+ } from '@kb-labs/core-platform/adapters';
29
+
30
+
31
+ // ─── DDL ──────────────────────────────────────────────────────────────────────
32
+
33
+ const CREATE_EVENTS_TABLE = `
34
+ CREATE TABLE IF NOT EXISTS events (
35
+ id TEXT PRIMARY KEY,
36
+ schema TEXT NOT NULL DEFAULT 'kb.v1',
37
+ type TEXT NOT NULL,
38
+ ts TEXT NOT NULL,
39
+ ingest_ts TEXT,
40
+ run_id TEXT,
41
+ product TEXT,
42
+ version TEXT,
43
+ actor_type TEXT,
44
+ actor_id TEXT,
45
+ actor_name TEXT,
46
+ ctx TEXT,
47
+ payload TEXT
48
+ )
49
+ `;
50
+
51
+ const CREATE_INDEXES = [
52
+ `CREATE INDEX IF NOT EXISTS events_ts_idx ON events (ts)`,
53
+ `CREATE INDEX IF NOT EXISTS events_type_idx ON events (type)`,
54
+ `CREATE INDEX IF NOT EXISTS events_product_idx ON events (product)`,
55
+ `CREATE INDEX IF NOT EXISTS events_type_ts_idx ON events (type, ts)`,
56
+ ];
57
+
58
+ // ─── groupBy mappings ─────────────────────────────────────────────────────────
59
+
60
+ const GROUP_BY_FORMAT: Record<string, string> = {
61
+ hour: '%Y-%m-%dT%H:00',
62
+ day: '%Y-%m-%d',
63
+ week: '%Y-W%W',
64
+ month: '%Y-%m',
65
+ };
66
+
67
+ // ─── dot-path to SQLite json_extract ─────────────────────────────────────────
68
+
69
+ function dotPathToSQL(path: string): string {
70
+ // "payload.model" → json_extract(payload, '$.model')
71
+ // "payload.nested.key" → json_extract(payload, '$.nested.key')
72
+ const parts = path.split('.');
73
+ const column = parts[0] ?? 'payload';
74
+ const rest = parts.slice(1).join('.');
75
+ if (!rest) {return column;}
76
+ return `json_extract(${column}, '$.${rest}')`;
77
+ }
78
+
79
+ // ─── metrics helpers ──────────────────────────────────────────────────────────
80
+
81
+ const KNOWN_METRICS: Record<string, string> = {
82
+ totalTokens: `SUM(CAST(json_extract(payload, '$.totalTokens') AS REAL)) as totalTokens`,
83
+ promptTokens: `SUM(CAST(json_extract(payload, '$.promptTokens') AS REAL)) as promptTokens`,
84
+ completionTokens: `SUM(CAST(json_extract(payload, '$.completionTokens') AS REAL)) as completionTokens`,
85
+ estimatedCost: `SUM(CAST(json_extract(payload, '$.estimatedCost') AS REAL)) as estimatedCost`,
86
+ durationMs: `AVG(CAST(json_extract(payload, '$.durationMs') AS REAL)) as durationMs`,
87
+ textLength: `SUM(CAST(json_extract(payload, '$.textLength') AS REAL)) as textLength`,
88
+ vectorCount: `SUM(CAST(json_extract(payload, '$.vectorCount') AS REAL)) as vectorCount`,
89
+ resultsCount: `AVG(CAST(json_extract(payload, '$.resultsCount') AS REAL)) as resultsCount`,
90
+ };
91
+
92
+ function buildMetricsSelect(metrics: string[]): string[] {
93
+ return metrics.map((m) => KNOWN_METRICS[m] ?? `SUM(CAST(json_extract(payload, '$.${m}') AS REAL)) as ${m}`);
94
+ }
95
+
96
+ function getDefaultMetrics(typeFilter?: string | string[]): string[] {
97
+ const types = Array.isArray(typeFilter) ? typeFilter : typeFilter ? [typeFilter] : [];
98
+ if (types.some((t) => t.startsWith('llm.'))) {
99
+ return ['totalTokens', 'promptTokens', 'completionTokens', 'estimatedCost', 'durationMs'];
100
+ }
101
+ if (types.some((t) => t.startsWith('embeddings.'))) {
102
+ return ['textLength', 'estimatedCost', 'durationMs'];
103
+ }
104
+ if (types.some((t) => t.startsWith('vectorstore.'))) {
105
+ return ['resultsCount', 'durationMs', 'vectorCount'];
106
+ }
107
+ return ['durationMs'];
108
+ }
109
+
110
+ // ─── Options ──────────────────────────────────────────────────────────────────
111
+
112
+ export interface SQLiteAnalyticsOptions {
113
+ /** Path to the SQLite database file. Default: .kb/analytics/analytics.sqlite */
114
+ dbPath?: string;
115
+ /** Alias for dbPath — accepted for config compatibility with kb.config.json */
116
+ filename?: string;
117
+ /** Analytics context for event enrichment */
118
+ context?: AnalyticsContext;
119
+ /** Workspace context injected by core-runtime (provides cwd for relative path resolution) */
120
+ workspace?: { cwd: string };
121
+ /** Analytics context injected by core-runtime (for auto-enrichment) */
122
+ analytics?: AnalyticsContext;
123
+ }
124
+
125
+ // ─── Adapter ──────────────────────────────────────────────────────────────────
126
+
127
+ /**
128
+ * SQLite-based analytics adapter.
129
+ * Uses WAL mode — supports concurrent reads/writes from multiple processes.
130
+ */
131
+ export class SQLiteAnalytics implements IAnalytics, IDisposable {
132
+
133
+ private readonly dbPath: string;
134
+ private context: AnalyticsContext;
135
+ private db: Database.Database;
136
+
137
+ constructor(options: SQLiteAnalyticsOptions = {}) {
138
+ const cwd = options.workspace?.cwd ?? process.cwd();
139
+ const rawPath = options.dbPath ?? options.filename ?? '.kb/analytics/analytics.sqlite';
140
+ this.dbPath = isAbsolute(rawPath) ? rawPath : join(cwd, rawPath);
141
+
142
+ this.context = options.context ?? options.analytics ?? {
143
+ source: { product: 'unknown', version: '0.0.0' },
144
+ runId: `run-${Date.now()}`,
145
+ };
146
+
147
+ // Ensure directory exists
148
+ mkdirSync(dirname(this.dbPath), { recursive: true });
149
+
150
+ // Open database — better-sqlite3 is synchronous
151
+ this.db = new Database(this.dbPath);
152
+
153
+ // WAL mode: multiple readers + one writer, no exclusive lock
154
+ this.db.pragma('journal_mode = WAL');
155
+ this.db.pragma('busy_timeout = 5000');
156
+ this.db.pragma('synchronous = NORMAL');
157
+
158
+ // Setup schema
159
+ this.db.exec(CREATE_EVENTS_TABLE);
160
+ for (const idx of CREATE_INDEXES) {
161
+ this.db.exec(idx);
162
+ }
163
+
164
+ // Ensure clean shutdown — checkpoint WAL and close connection.
165
+ // Without this, process.exit() leaves WAL in inconsistent state
166
+ // causing "database disk image is malformed" for other processes.
167
+ this._onExit = () => this.close();
168
+ process.on('exit', this._onExit);
169
+ process.on('SIGINT', this._onExit);
170
+ process.on('SIGTERM', this._onExit);
171
+ }
172
+
173
+ private _onExit: () => void;
174
+ private _closed = false;
175
+
176
+ /** Checkpoint WAL and close the database connection cleanly. */
177
+ close(): void {
178
+ if (this._closed) {return;}
179
+ this._closed = true;
180
+ try {
181
+ this.db.pragma('wal_checkpoint(TRUNCATE)');
182
+ this.db.close();
183
+ } catch {
184
+ // Already closed or corrupted — nothing we can do
185
+ }
186
+ process.removeListener('exit', this._onExit);
187
+ process.removeListener('SIGINT', this._onExit);
188
+ process.removeListener('SIGTERM', this._onExit);
189
+ }
190
+
191
+ /**
192
+ * Implements IDisposable — delegates to close().
193
+ *
194
+ * close() already provides the complete, correct shutdown sequence:
195
+ * 1. Idempotency guard (this._closed)
196
+ * 2. WAL TRUNCATE checkpoint (flush frames → main DB file, empty WAL)
197
+ * 3. Connection close (this.db.close())
198
+ * 4. Process listener deregistration (exit, SIGINT, SIGTERM)
199
+ *
200
+ * PlatformContainer.shutdown() checks for close() first (container.ts line 830),
201
+ * so the container will call close() on this adapter. dispose() is provided so
202
+ * that isDisposable() returns true and this adapter is typed as IDisposable for
203
+ * observability logging in service-bootstrap's onBeforeShutdown hook.
204
+ */
205
+ dispose(): void {
206
+ this.close();
207
+ }
208
+
209
+
210
+ // ─── Write ────────────────────────────────────────────────────────────────
211
+
212
+ async track(event: string, properties?: Record<string, unknown>): Promise<void> {
213
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
214
+ const now = new Date().toISOString();
215
+
216
+ const stmt = this.db.prepare(`
217
+ INSERT OR IGNORE INTO events
218
+ (id, schema, type, ts, ingest_ts, run_id, product, version, actor_type, actor_id, actor_name, ctx, payload)
219
+ VALUES
220
+ (?, 'kb.v1', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
221
+ `);
222
+
223
+ stmt.run(
224
+ id,
225
+ event,
226
+ now,
227
+ now,
228
+ this.context.runId ?? null,
229
+ this.context.source.product,
230
+ this.context.source.version,
231
+ this.context.actor?.type ?? null,
232
+ this.context.actor?.id ?? null,
233
+ this.context.actor?.name ?? null,
234
+ this.context.ctx ? JSON.stringify(this.context.ctx) : null,
235
+ properties ? JSON.stringify(properties) : null,
236
+ );
237
+ }
238
+
239
+ async identify(userId: string, traits?: Record<string, unknown>): Promise<void> {
240
+ await this.track('identity', { userId, ...traits });
241
+ }
242
+
243
+ async flush(): Promise<void> {
244
+ // SQLite writes synchronously — nothing to flush
245
+ }
246
+
247
+ // ─── Source attribution ───────────────────────────────────────────────────
248
+
249
+ getSource(): { product: string; version: string } | undefined {
250
+ return this.context.source;
251
+ }
252
+
253
+ setSource(source: { product: string; version: string }): void {
254
+ this.context = { ...this.context, source };
255
+ }
256
+
257
+ // ─── Read: Events ─────────────────────────────────────────────────────────
258
+
259
+ async getEvents(query?: EventsQuery): Promise<EventsResponse> {
260
+ const { where, params } = buildWhereClause(query);
261
+ const limit = query?.limit ?? 100;
262
+ const offset = query?.offset ?? 0;
263
+ const whereClause = where ? ` WHERE ${where}` : '';
264
+
265
+ const countRow = this.db.prepare(`SELECT COUNT(*) as total FROM events${whereClause}`).get(...params) as { total: number };
266
+ const total = countRow.total;
267
+
268
+ const rows = this.db.prepare(
269
+ `SELECT * FROM events${whereClause} ORDER BY ts DESC LIMIT ? OFFSET ?`
270
+ ).all(...params, limit, offset) as Record<string, unknown>[];
271
+
272
+ const events: AnalyticsEvent[] = rows.map(rowToEvent);
273
+ return { events, total, hasMore: offset + events.length < total };
274
+ }
275
+
276
+ // ─── Read: Stats ──────────────────────────────────────────────────────────
277
+
278
+ async getStats(): Promise<EventsStats> {
279
+ const totalRow = this.db.prepare(
280
+ `SELECT COUNT(*) as total, MIN(ts) as minTs, MAX(ts) as maxTs FROM events`
281
+ ).get() as { total: number; minTs: string; maxTs: string };
282
+
283
+ const totalEvents = totalRow.total;
284
+ const from = totalRow.minTs ?? new Date().toISOString();
285
+ const to = totalRow.maxTs ?? new Date().toISOString();
286
+
287
+ const byType: Record<string, number> = {};
288
+ for (const r of this.db.prepare(`SELECT type, COUNT(*) as cnt FROM events GROUP BY type`).all() as { type: string; cnt: number }[]) {
289
+ byType[r.type] = r.cnt;
290
+ }
291
+
292
+ const bySource: Record<string, number> = {};
293
+ for (const r of this.db.prepare(
294
+ `SELECT COALESCE(product, 'unknown') as product, COUNT(*) as cnt FROM events GROUP BY product`
295
+ ).all() as { product: string; cnt: number }[]) {
296
+ bySource[r.product] = r.cnt;
297
+ }
298
+
299
+ const byActor: Record<string, number> = {};
300
+ for (const r of this.db.prepare(
301
+ `SELECT COALESCE(actor_id, 'unknown') as actor, COUNT(*) as cnt FROM events GROUP BY actor`
302
+ ).all() as { actor: string; cnt: number }[]) {
303
+ byActor[r.actor] = r.cnt;
304
+ }
305
+
306
+ return { totalEvents, byType, bySource, byActor, timeRange: { from, to } };
307
+ }
308
+
309
+ // ─── Read: Time-series ────────────────────────────────────────────────────
310
+
311
+ async getDailyStats(query?: StatsQuery): Promise<DailyStats[]> {
312
+ const groupBy = query?.groupBy ?? 'day';
313
+ const fmtStr = GROUP_BY_FORMAT[groupBy] ?? '%Y-%m-%d';
314
+
315
+ const breakdownBy = query?.breakdownBy;
316
+ const breakdownSQL = breakdownBy ? dotPathToSQL(breakdownBy) : null;
317
+
318
+ const metricsFilter = query?.metrics ?? getDefaultMetrics(query?.type);
319
+ const metricsSelect = buildMetricsSelect(metricsFilter);
320
+
321
+ const { where, params } = buildWhereClause(query);
322
+ const whereClause = where ? `WHERE ${where}` : '';
323
+
324
+ const selectParts = [
325
+ `strftime('${fmtStr}', ts) as date`,
326
+ `COUNT(*) as count`,
327
+ ...metricsSelect,
328
+ ...(breakdownSQL ? [`${breakdownSQL} as breakdown`] : []),
329
+ ];
330
+
331
+ const groupByParts = [`strftime('${fmtStr}', ts)`];
332
+ if (breakdownSQL) {groupByParts.push(breakdownSQL);}
333
+
334
+ const sql = `
335
+ SELECT ${selectParts.join(', ')}
336
+ FROM events
337
+ ${whereClause}
338
+ GROUP BY ${groupByParts.join(', ')}
339
+ ORDER BY strftime('${fmtStr}', ts) ASC${breakdownSQL ? ', breakdown ASC NULLS LAST' : ''}
340
+ `;
341
+
342
+ const rows = this.db.prepare(sql).all(...params) as Record<string, unknown>[];
343
+
344
+ return rows.map((r): DailyStats => {
345
+ const metrics: Record<string, number> = {};
346
+ for (const m of metricsFilter) {
347
+ const v = r[m];
348
+ if (v !== null && v !== undefined) {
349
+ metrics[m] = Number(v);
350
+ }
351
+ }
352
+
353
+ const stat: DailyStats = {
354
+ date: String(r['date']),
355
+ count: Number(r['count']),
356
+ };
357
+ if (Object.keys(metrics).length > 0) {stat.metrics = metrics;}
358
+ if (breakdownSQL && r['breakdown'] !== null && r['breakdown'] !== undefined) {
359
+ stat.breakdown = String(r['breakdown']);
360
+ }
361
+ return stat;
362
+ });
363
+ }
364
+
365
+ // ─── Read: Buffer / DLQ ───────────────────────────────────────────────────
366
+
367
+ async getBufferStatus(): Promise<BufferStatus | null> {
368
+ const row = this.db.prepare(
369
+ `SELECT COUNT(*) as segments, MIN(ts) as oldest, MAX(ts) as newest FROM events`
370
+ ).get() as { segments: number; oldest: string | null; newest: string | null };
371
+
372
+ return {
373
+ segments: row.segments,
374
+ totalSizeBytes: 0,
375
+ oldestEventTs: row.oldest,
376
+ newestEventTs: row.newest,
377
+ };
378
+ }
379
+
380
+ async getDlqStatus(): Promise<DlqStatus | null> {
381
+ return null;
382
+ }
383
+ }
384
+
385
+ // ─── SQL helpers ──────────────────────────────────────────────────────────────
386
+
387
+ function buildWhereClause(query?: EventsQuery): { where: string; params: unknown[] } {
388
+ if (!query) {return { where: '', params: [] };}
389
+
390
+ const clauses: string[] = [];
391
+ const params: unknown[] = [];
392
+
393
+ if (query.type) {
394
+ const types = Array.isArray(query.type) ? query.type : [query.type];
395
+ if (types.length === 1) {
396
+ clauses.push(`type = ?`);
397
+ params.push(types[0]);
398
+ } else {
399
+ clauses.push(`type IN (${types.map(() => '?').join(', ')})`);
400
+ params.push(...types);
401
+ }
402
+ }
403
+
404
+ if (query.source) {
405
+ clauses.push(`product = ?`);
406
+ params.push(query.source);
407
+ }
408
+
409
+ if (query.actor) {
410
+ clauses.push(`actor_id = ?`);
411
+ params.push(query.actor);
412
+ }
413
+
414
+ if (query.from) {
415
+ clauses.push(`ts >= ?`);
416
+ params.push(query.from);
417
+ }
418
+
419
+ if (query.to) {
420
+ clauses.push(`ts <= ?`);
421
+ params.push(query.to);
422
+ }
423
+
424
+ return { where: clauses.join(' AND '), params };
425
+ }
426
+
427
+ function rowToEvent(r: Record<string, unknown>): AnalyticsEvent {
428
+ return {
429
+ id: String(r['id']),
430
+ schema: 'kb.v1',
431
+ type: String(r['type']),
432
+ ts: String(r['ts']),
433
+ ingestTs: String(r['ingest_ts'] ?? r['ts']),
434
+ source: {
435
+ product: String(r['product'] ?? ''),
436
+ version: String(r['version'] ?? ''),
437
+ },
438
+ runId: String(r['run_id'] ?? ''),
439
+ actor: r['actor_type']
440
+ ? {
441
+ type: r['actor_type'] as 'user' | 'agent' | 'ci',
442
+ id: r['actor_id'] ? String(r['actor_id']) : undefined,
443
+ name: r['actor_name'] ? String(r['actor_name']) : undefined,
444
+ }
445
+ : undefined,
446
+ ctx: r['ctx'] ? (JSON.parse(String(r['ctx'])) as Record<string, string | number | boolean | null>) : undefined,
447
+ payload: r['payload'] ? JSON.parse(String(r['payload'])) : undefined,
448
+ };
449
+ }
450
+
451
+ export { manifest } from './manifest.js';
452
+
453
+ /**
454
+ * Factory function required by core-runtime adapter discovery.
455
+ */
456
+ export function createAdapter(options?: SQLiteAnalyticsOptions): IAnalytics {
457
+ return new SQLiteAnalytics(options);
458
+ }
459
+
460
+ export default createAdapter;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @module @kb-labs/adapters-analytics-sqlite/manifest
3
+ * Adapter manifest for SQLite analytics adapter.
4
+ */
5
+
6
+ import type { AdapterManifest } from '@kb-labs/core-platform';
7
+
8
+ export const manifest: AdapterManifest = {
9
+ manifestVersion: '1.0.0',
10
+ id: 'analytics-sqlite',
11
+ name: 'SQLite Analytics',
12
+ version: '0.1.0',
13
+ description: 'SQLite-based analytics adapter — concurrent writes, WAL mode, SQL analytics, no lock issues',
14
+ author: 'KB Labs Team',
15
+ license: 'KBPL-1.1',
16
+ type: 'core',
17
+ implements: 'IAnalytics',
18
+ contexts: ['workspace', 'analytics'],
19
+ capabilities: {
20
+ search: true,
21
+ custom: {
22
+ offline: true,
23
+ stats: true,
24
+ sql: true,
25
+ groupBy: true,
26
+ breakdownBy: true,
27
+ concurrent: true,
28
+ },
29
+ },
30
+ configSchema: {
31
+ dbPath: {
32
+ type: 'string',
33
+ default: '.kb/analytics/analytics.sqlite',
34
+ description: 'Path to the SQLite database file (relative to workspace root)',
35
+ },
36
+ filename: {
37
+ type: 'string',
38
+ description: 'Alias for dbPath — accepted for config compatibility',
39
+ },
40
+ },
41
+ };
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "baseUrl": ".",
6
+ "paths": {}
7
+ },
8
+ "include": [
9
+ "src/**/*"
10
+ ],
11
+ "exclude": [
12
+ "dist",
13
+ "node_modules"
14
+ ]
15
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "@kb-labs/devkit/tsconfig/node.json",
4
+ "compilerOptions": {
5
+ "rootDir": "src",
6
+ "outDir": "dist"
7
+ },
8
+ "include": ["src"]
9
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'tsup';
2
+ import nodePreset from '@kb-labs/devkit/tsup/node';
3
+
4
+ export default defineConfig({
5
+ ...nodePreset,
6
+ tsconfig: 'tsconfig.build.json',
7
+ entry: ['src/index.ts', 'src/manifest.ts'],
8
+ dts: true,
9
+ });
@@ -0,0 +1,28 @@
1
+ # @kb-labs/adapters-environment-docker
2
+
3
+ Docker-based implementation of `IEnvironmentProvider` for KB Labs full-cycle runs.
4
+
5
+ ## Features
6
+
7
+ - Create long-lived environments with `docker run -d`
8
+ - Query environment status via `docker inspect`
9
+ - Idempotent destroy via `docker rm -f`
10
+ - Lease renewal contract for orchestrator integration
11
+
12
+ ## Example config
13
+
14
+ ```json
15
+ {
16
+ "adapters": {
17
+ "environment": "@kb-labs/adapters-environment-docker"
18
+ },
19
+ "adapterOptions": {
20
+ "environment": {
21
+ "defaultImage": "node:20-alpine",
22
+ "defaultTtlMs": 3600000,
23
+ "mountWorkspace": true,
24
+ "workspaceMountPath": "/workspace"
25
+ }
26
+ }
27
+ }
28
+ ```
@@ -0,0 +1,5 @@
1
+ import nodePreset from '@kb-labs/devkit/eslint/node.js';
2
+
3
+ export default [
4
+ ...nodePreset,
5
+ ];
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@kb-labs/adapters-environment-docker",
3
+ "version": "0.5.0",
4
+ "description": "Docker adapter implementing IEnvironmentProvider",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "sideEffects": false,
19
+ "scripts": {
20
+ "clean": "rimraf dist",
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "type-check": "tsc --noEmit",
24
+ "test": "vitest run --passWithNoTests",
25
+ "test:watch": "vitest",
26
+ "lint": "eslint src --ext .ts",
27
+ "lint:fix": "eslint . --fix"
28
+ },
29
+ "peerDependencies": {
30
+ "@kb-labs/core-platform": "*"
31
+ },
32
+ "optionalDependencies": {
33
+ "@kb-labs/gateway-auth": "workspace:*"
34
+ },
35
+ "devDependencies": {
36
+ "@kb-labs/core-platform": "link:../../../../platform/kb-labs-core/packages/core-platform",
37
+ "@types/node": "^24.3.3",
38
+ "eslint": "^9",
39
+ "tsup": "^8.5.0",
40
+ "typescript": "^5.6.3",
41
+ "vitest": "^3.2.4",
42
+ "@kb-labs/devkit": "link:../../../kb-labs-devkit",
43
+ "rimraf": "^6.0.1"
44
+ },
45
+ "engines": {
46
+ "node": ">=20.0.0",
47
+ "pnpm": ">=9.0.0"
48
+ }
49
+ }