@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.
- package/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/CONTRIBUTING.md +90 -0
- package/IMPLEMENTATION_COMPLETE.md +416 -0
- package/LICENSE +186 -0
- package/README-TEMPLATE.md +179 -0
- package/README.md +306 -0
- package/docs/DOCUMENTATION.md +74 -0
- package/docs/adr/0000-template.md +49 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
- package/docs/adr/0002-plugins-and-extensibility.md +46 -0
- package/docs/adr/0003-package-and-module-boundaries.md +37 -0
- package/docs/adr/0004-versioning-and-release-policy.md +38 -0
- package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
- package/docs/adr/0006-adopt-devkit-sync.md +47 -0
- package/docs/adr/0007-drift-kit-check.md +72 -0
- package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
- package/docs/naming-convention.md +272 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +84 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/adapters-analytics-duckdb/package.json +54 -0
- package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
- package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
- package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
- package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
- package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
- package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
- package/packages/adapters-analytics-file/README.md +32 -0
- package/packages/adapters-analytics-file/eslint.config.js +27 -0
- package/packages/adapters-analytics-file/package.json +50 -0
- package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
- package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
- package/packages/adapters-analytics-file/src/index.test.ts +214 -0
- package/packages/adapters-analytics-file/src/index.ts +830 -0
- package/packages/adapters-analytics-file/src/manifest.ts +45 -0
- package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-file/tsconfig.json +9 -0
- package/packages/adapters-analytics-file/tsup.config.ts +9 -0
- package/packages/adapters-analytics-sqlite/package.json +55 -0
- package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
- package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
- package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
- package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
- package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-environment-docker/README.md +28 -0
- package/packages/adapters-environment-docker/eslint.config.js +5 -0
- package/packages/adapters-environment-docker/package.json +49 -0
- package/packages/adapters-environment-docker/src/index.test.ts +138 -0
- package/packages/adapters-environment-docker/src/index.ts +439 -0
- package/packages/adapters-environment-docker/src/manifest.ts +65 -0
- package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
- package/packages/adapters-environment-docker/tsconfig.json +16 -0
- package/packages/adapters-environment-docker/tsup.config.ts +9 -0
- package/packages/adapters-eventbus-cache/README.md +242 -0
- package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
- package/packages/adapters-eventbus-cache/package.json +46 -0
- package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
- package/packages/adapters-eventbus-cache/src/index.ts +215 -0
- package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
- package/packages/adapters-eventbus-cache/src/types.ts +58 -0
- package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
- package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
- package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
- package/packages/adapters-fs/README.md +171 -0
- package/packages/adapters-fs/allowed.txt +1 -0
- package/packages/adapters-fs/conflict.txt +1 -0
- package/packages/adapters-fs/dest.txt +1 -0
- package/packages/adapters-fs/eslint.config.js +27 -0
- package/packages/adapters-fs/exists.txt +1 -0
- package/packages/adapters-fs/not-allowed.txt +1 -0
- package/packages/adapters-fs/other.txt +1 -0
- package/packages/adapters-fs/package.json +55 -0
- package/packages/adapters-fs/public/file1.txt +1 -0
- package/packages/adapters-fs/public/file2.txt +1 -0
- package/packages/adapters-fs/secret.txt +1 -0
- package/packages/adapters-fs/secrets/key.txt +1 -0
- package/packages/adapters-fs/src/index.test.ts +243 -0
- package/packages/adapters-fs/src/index.ts +258 -0
- package/packages/adapters-fs/src/manifest.ts +35 -0
- package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
- package/packages/adapters-fs/src/secure-storage.ts +268 -0
- package/packages/adapters-fs/test.json +1 -0
- package/packages/adapters-fs/test.txt +1 -0
- package/packages/adapters-fs/test.xyz +1 -0
- package/packages/adapters-fs/test1.txt +1 -0
- package/packages/adapters-fs/test2.txt +1 -0
- package/packages/adapters-fs/tsconfig.build.json +15 -0
- package/packages/adapters-fs/tsconfig.json +9 -0
- package/packages/adapters-fs/tsup.config.ts +8 -0
- package/packages/adapters-fs/vitest.config.ts +19 -0
- package/packages/adapters-log-ringbuffer/README.md +228 -0
- package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
- package/packages/adapters-log-ringbuffer/package.json +47 -0
- package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
- package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
- package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
- package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
- package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
- package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
- package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
- package/packages/adapters-log-sqlite/README.md +396 -0
- package/packages/adapters-log-sqlite/eslint.config.js +27 -0
- package/packages/adapters-log-sqlite/package.json +49 -0
- package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
- package/packages/adapters-log-sqlite/src/index.ts +1068 -0
- package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
- package/packages/adapters-log-sqlite/src/schema.sql +46 -0
- package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-log-sqlite/tsconfig.json +9 -0
- package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
- package/packages/adapters-mongodb/README.md +147 -0
- package/packages/adapters-mongodb/eslint.config.js +27 -0
- package/packages/adapters-mongodb/package.json +53 -0
- package/packages/adapters-mongodb/src/index.ts +428 -0
- package/packages/adapters-mongodb/src/manifest.ts +45 -0
- package/packages/adapters-mongodb/src/secure-document.ts +231 -0
- package/packages/adapters-mongodb/tsconfig.build.json +15 -0
- package/packages/adapters-mongodb/tsconfig.json +9 -0
- package/packages/adapters-mongodb/tsup.config.ts +8 -0
- package/packages/adapters-openai/README.md +151 -0
- package/packages/adapters-openai/embeddings.ts +37 -0
- package/packages/adapters-openai/eslint.config.js +26 -0
- package/packages/adapters-openai/index.ts +22 -0
- package/packages/adapters-openai/package.json +57 -0
- package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
- package/packages/adapters-openai/src/embeddings.ts +104 -0
- package/packages/adapters-openai/src/index.ts +13 -0
- package/packages/adapters-openai/src/llm.ts +304 -0
- package/packages/adapters-openai/src/manifest.ts +47 -0
- package/packages/adapters-openai/tsconfig.build.json +15 -0
- package/packages/adapters-openai/tsconfig.json +9 -0
- package/packages/adapters-openai/tsup.config.ts +8 -0
- package/packages/adapters-pino/README.md +152 -0
- package/packages/adapters-pino/eslint.config.js +27 -0
- package/packages/adapters-pino/package.json +49 -0
- package/packages/adapters-pino/src/index.test.ts +44 -0
- package/packages/adapters-pino/src/index.ts +322 -0
- package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
- package/packages/adapters-pino/src/manifest.ts +49 -0
- package/packages/adapters-pino/tsconfig.build.json +15 -0
- package/packages/adapters-pino/tsconfig.json +9 -0
- package/packages/adapters-pino/tsup.config.ts +9 -0
- package/packages/adapters-pino-http/README.md +141 -0
- package/packages/adapters-pino-http/eslint.config.js +27 -0
- package/packages/adapters-pino-http/package.json +46 -0
- package/packages/adapters-pino-http/src/index.ts +229 -0
- package/packages/adapters-pino-http/tsconfig.build.json +15 -0
- package/packages/adapters-pino-http/tsconfig.json +9 -0
- package/packages/adapters-pino-http/tsup.config.ts +9 -0
- package/packages/adapters-qdrant/README.md +166 -0
- package/packages/adapters-qdrant/eslint.config.js +27 -0
- package/packages/adapters-qdrant/package.json +49 -0
- package/packages/adapters-qdrant/src/index.ts +490 -0
- package/packages/adapters-qdrant/src/manifest.ts +54 -0
- package/packages/adapters-qdrant/src/retry.ts +204 -0
- package/packages/adapters-qdrant/tsconfig.build.json +15 -0
- package/packages/adapters-qdrant/tsconfig.json +9 -0
- package/packages/adapters-qdrant/tsup.config.ts +9 -0
- package/packages/adapters-redis/README.md +159 -0
- package/packages/adapters-redis/eslint.config.js +27 -0
- package/packages/adapters-redis/package.json +49 -0
- package/packages/adapters-redis/src/index.ts +164 -0
- package/packages/adapters-redis/src/manifest.ts +49 -0
- package/packages/adapters-redis/tsconfig.build.json +15 -0
- package/packages/adapters-redis/tsconfig.json +9 -0
- package/packages/adapters-redis/tsup.config.ts +9 -0
- package/packages/adapters-snapshot-localfs/README.md +10 -0
- package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
- package/packages/adapters-snapshot-localfs/package.json +46 -0
- package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
- package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
- package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
- package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
- package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
- package/packages/adapters-sqlite/README.md +163 -0
- package/packages/adapters-sqlite/eslint.config.js +27 -0
- package/packages/adapters-sqlite/package.json +54 -0
- package/packages/adapters-sqlite/src/index.test.ts +245 -0
- package/packages/adapters-sqlite/src/index.ts +382 -0
- package/packages/adapters-sqlite/src/manifest.ts +47 -0
- package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
- package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
- package/packages/adapters-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-sqlite/tsconfig.json +9 -0
- package/packages/adapters-sqlite/tsup.config.ts +8 -0
- package/packages/adapters-sqlite/vitest.config.ts +19 -0
- package/packages/adapters-transport/README.md +170 -0
- package/packages/adapters-transport/eslint.config.js +27 -0
- package/packages/adapters-transport/package.json +49 -0
- package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
- package/packages/adapters-transport/src/index.ts +101 -0
- package/packages/adapters-transport/src/ipc-transport.ts +228 -0
- package/packages/adapters-transport/src/transport.ts +224 -0
- package/packages/adapters-transport/src/types.ts +92 -0
- package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
- package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
- package/packages/adapters-transport/tsconfig.build.json +15 -0
- package/packages/adapters-transport/tsconfig.json +9 -0
- package/packages/adapters-transport/tsup.config.ts +9 -0
- package/packages/adapters-vibeproxy/README.md +159 -0
- package/packages/adapters-vibeproxy/eslint.config.js +27 -0
- package/packages/adapters-vibeproxy/package.json +51 -0
- package/packages/adapters-vibeproxy/src/index.ts +13 -0
- package/packages/adapters-vibeproxy/src/llm.ts +437 -0
- package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
- package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
- package/packages/adapters-vibeproxy/tsconfig.json +9 -0
- package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
- package/packages/adapters-workspace-agent/package.json +46 -0
- package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
- package/packages/adapters-workspace-agent/src/index.ts +220 -0
- package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
- package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-agent/tsconfig.json +16 -0
- package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
- package/packages/adapters-workspace-localfs/README.md +9 -0
- package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
- package/packages/adapters-workspace-localfs/package.json +46 -0
- package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
- package/packages/adapters-workspace-localfs/src/index.ts +172 -0
- package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
- package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
- package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
- package/packages/adapters-workspace-worktree/README.md +9 -0
- package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
- package/packages/adapters-workspace-worktree/package.json +46 -0
- package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
- package/packages/adapters-workspace-worktree/src/index.ts +245 -0
- package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
- package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
- package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +2800 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/test-integration.ts +242 -0
- package/test.txt +1 -0
- package/tsconfig.base.json +6 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +26 -0
- package/tsconfig.tools.json +17 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +103 -0
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/adapters-analytics-file",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "File-based analytics adapter for KB Labs platform (writes JSONL to .kb/analytics/buffer)",
|
|
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
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"scripts": {
|
|
21
|
+
"clean": "rimraf dist",
|
|
22
|
+
"build": "tsup --config tsup.config.ts",
|
|
23
|
+
"dev": "tsup --config tsup.config.ts --watch",
|
|
24
|
+
"lint": "eslint src --ext .ts,.tsx,.js,.jsx",
|
|
25
|
+
"lint:fix": "eslint . --fix",
|
|
26
|
+
"type-check": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run --passWithNoTests",
|
|
28
|
+
"test:watch": "vitest --passWithNoTests"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@kb-labs/core-platform": "link:../../../../platform/kb-labs-core/packages/core-platform",
|
|
32
|
+
"fs-extra": "^11.0.0",
|
|
33
|
+
"date-fns": "^3.6.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@kb-labs/devkit": "link:../../../kb-labs-devkit",
|
|
37
|
+
"@types/node": "^24.3.3",
|
|
38
|
+
"@types/fs-extra": "^11.0.0",
|
|
39
|
+
"eslint": "^9",
|
|
40
|
+
"rimraf": "^6.0.1",
|
|
41
|
+
"tsup": "^8.5.0",
|
|
42
|
+
"typescript": "^5.6.3",
|
|
43
|
+
"vitest": "^3.2.4"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20.0.0",
|
|
47
|
+
"pnpm": ">=9.0.0"
|
|
48
|
+
},
|
|
49
|
+
"packageManager": "pnpm@9.11.0"
|
|
50
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { createAdapter } from "../index";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import type {
|
|
7
|
+
IAnalytics,
|
|
8
|
+
} from "@kb-labs/core-platform/adapters";
|
|
9
|
+
|
|
10
|
+
const TEST_BASE_DIR = join(__dirname, `test-analytics-${randomUUID()}`);
|
|
11
|
+
|
|
12
|
+
describe("FileAnalytics - getDailyStats", () => {
|
|
13
|
+
let analytics: IAnalytics;
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
analytics = createAdapter({ baseDir: TEST_BASE_DIR });
|
|
17
|
+
await fs.ensureDir(TEST_BASE_DIR);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
await fs.remove(TEST_BASE_DIR);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("LLM events", () => {
|
|
25
|
+
it("should aggregate LLM events by day with correct metrics", async () => {
|
|
26
|
+
// Track events across 3 days
|
|
27
|
+
await analytics.track("llm.completion.completed", {
|
|
28
|
+
model: "gpt-4",
|
|
29
|
+
totalTokens: 1000,
|
|
30
|
+
cost: 0.05,
|
|
31
|
+
durationMs: 1500,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await analytics.track("llm.completion.completed", {
|
|
35
|
+
model: "gpt-4",
|
|
36
|
+
totalTokens: 1500,
|
|
37
|
+
cost: 0.07,
|
|
38
|
+
durationMs: 2000,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Get stats
|
|
42
|
+
const stats = await analytics.getDailyStats?.({
|
|
43
|
+
type: "llm.completion.completed",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(stats).toHaveLength(1);
|
|
47
|
+
expect(stats![0]!.count).toBe(2);
|
|
48
|
+
expect(stats![0]!.metrics?.totalTokens).toBe(2500);
|
|
49
|
+
expect(stats![0]!.metrics?.totalCost).toBeCloseTo(0.12, 2);
|
|
50
|
+
expect(stats![0]!.metrics?.avgDurationMs).toBe(1750);
|
|
51
|
+
expect(stats![0]!.date).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should filter by date range", async () => {
|
|
55
|
+
// Use local date (adapter writes files with local date, not UTC)
|
|
56
|
+
const d = new Date();
|
|
57
|
+
const today = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
58
|
+
|
|
59
|
+
// Track an event
|
|
60
|
+
await analytics.track("llm.completion.completed", {
|
|
61
|
+
model: "gpt-4",
|
|
62
|
+
totalTokens: 1000,
|
|
63
|
+
cost: 0.05,
|
|
64
|
+
durationMs: 1500,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const yesterday = new Date(Date.now() - 86400000)
|
|
68
|
+
.toISOString()
|
|
69
|
+
.split("T")[0];
|
|
70
|
+
const tomorrow = new Date(Date.now() + 86400000)
|
|
71
|
+
.toISOString()
|
|
72
|
+
.split("T")[0];
|
|
73
|
+
|
|
74
|
+
// Query for future dates (should be empty)
|
|
75
|
+
const futureStats = await analytics.getDailyStats?.({
|
|
76
|
+
type: "llm.completion.completed",
|
|
77
|
+
from: `${tomorrow}T00:00:00Z`,
|
|
78
|
+
to: `${tomorrow}T23:59:59Z`,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(futureStats).toHaveLength(0);
|
|
82
|
+
|
|
83
|
+
// Query for today (should have data)
|
|
84
|
+
const todayStats = await analytics.getDailyStats?.({
|
|
85
|
+
type: "llm.completion.completed",
|
|
86
|
+
from: `${yesterday}T00:00:00Z`,
|
|
87
|
+
to: `${tomorrow}T23:59:59Z`,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(todayStats).toHaveLength(1);
|
|
91
|
+
expect(todayStats![0]!.date).toBe(today);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should return empty array when no events match", async () => {
|
|
95
|
+
const stats = await analytics.getDailyStats?.({
|
|
96
|
+
type: "llm.completion.completed",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(stats).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("Embeddings events", () => {
|
|
104
|
+
it("should aggregate embeddings events with correct metrics", async () => {
|
|
105
|
+
await analytics.track("embeddings.generate.completed", {
|
|
106
|
+
model: "text-embedding-ada-002",
|
|
107
|
+
tokens: 500,
|
|
108
|
+
cost: 0.001,
|
|
109
|
+
durationMs: 200,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await analytics.track("embeddings.generate.completed", {
|
|
113
|
+
model: "text-embedding-ada-002",
|
|
114
|
+
tokens: 700,
|
|
115
|
+
cost: 0.0015,
|
|
116
|
+
durationMs: 250,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const stats = await analytics.getDailyStats?.({
|
|
120
|
+
type: "embeddings.generate.completed",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(stats).toHaveLength(1);
|
|
124
|
+
expect(stats![0]!).toMatchObject({
|
|
125
|
+
count: 2,
|
|
126
|
+
metrics: {
|
|
127
|
+
totalTokens: 1200,
|
|
128
|
+
totalCost: 0.0025,
|
|
129
|
+
avgDurationMs: 225,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("VectorStore events", () => {
|
|
136
|
+
it("should count operations correctly", async () => {
|
|
137
|
+
await analytics.track("vectorstore.search.completed", {
|
|
138
|
+
durationMs: 50,
|
|
139
|
+
resultsCount: 5,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await analytics.track("vectorstore.search.completed", {
|
|
143
|
+
durationMs: 60,
|
|
144
|
+
resultsCount: 3,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await analytics.track("vectorstore.upsert.completed", {
|
|
148
|
+
durationMs: 100,
|
|
149
|
+
vectorCount: 10,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await analytics.track("vectorstore.delete.completed", {
|
|
153
|
+
durationMs: 30,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const stats = await analytics.getDailyStats?.({
|
|
157
|
+
type: [
|
|
158
|
+
"vectorstore.search.completed",
|
|
159
|
+
"vectorstore.upsert.completed",
|
|
160
|
+
"vectorstore.delete.completed",
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(stats).toHaveLength(1);
|
|
165
|
+
expect(stats![0]!.count).toBe(4);
|
|
166
|
+
expect(stats![0]!.metrics).toMatchObject({
|
|
167
|
+
totalSearches: 2,
|
|
168
|
+
totalUpserts: 1,
|
|
169
|
+
totalDeletes: 1,
|
|
170
|
+
avgDurationMs: 60, // (50 + 60 + 100 + 30) / 4
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("Cache events", () => {
|
|
176
|
+
it("should calculate hit rate correctly", async () => {
|
|
177
|
+
// 3 hits, 2 misses, 1 set
|
|
178
|
+
await analytics.track("cache.hit", { durationMs: 1 });
|
|
179
|
+
await analytics.track("cache.hit", { durationMs: 1 });
|
|
180
|
+
await analytics.track("cache.hit", { durationMs: 2 });
|
|
181
|
+
await analytics.track("cache.miss", { durationMs: 1 });
|
|
182
|
+
await analytics.track("cache.miss", { durationMs: 1 });
|
|
183
|
+
await analytics.track("cache.set", { durationMs: 5 });
|
|
184
|
+
|
|
185
|
+
const stats = await analytics.getDailyStats?.({
|
|
186
|
+
type: ["cache.hit", "cache.miss", "cache.set"],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(stats).toHaveLength(1);
|
|
190
|
+
expect(stats![0]!.count).toBe(6);
|
|
191
|
+
expect(stats![0]!.metrics).toMatchObject({
|
|
192
|
+
totalHits: 3,
|
|
193
|
+
totalMisses: 2,
|
|
194
|
+
totalSets: 1,
|
|
195
|
+
hitRate: 60, // 3/(3+2) * 100 = 60%
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should handle zero gets gracefully", async () => {
|
|
200
|
+
await analytics.track("cache.set", { durationMs: 5 });
|
|
201
|
+
|
|
202
|
+
const stats = await analytics.getDailyStats?.({
|
|
203
|
+
type: ["cache.hit", "cache.miss", "cache.set"],
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(stats).toHaveLength(1);
|
|
207
|
+
expect(stats![0]!.metrics?.hitRate).toBe(0);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("Storage events", () => {
|
|
212
|
+
it("should aggregate bytes read and written", async () => {
|
|
213
|
+
await analytics.track("storage.read.completed", {
|
|
214
|
+
durationMs: 10,
|
|
215
|
+
bytesRead: 1024,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
await analytics.track("storage.read.completed", {
|
|
219
|
+
durationMs: 15,
|
|
220
|
+
bytesRead: 2048,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
await analytics.track("storage.write.completed", {
|
|
224
|
+
durationMs: 20,
|
|
225
|
+
bytesWritten: 512,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await analytics.track("storage.delete.completed", {
|
|
229
|
+
durationMs: 5,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const stats = await analytics.getDailyStats?.({
|
|
233
|
+
type: [
|
|
234
|
+
"storage.read.completed",
|
|
235
|
+
"storage.write.completed",
|
|
236
|
+
"storage.delete.completed",
|
|
237
|
+
],
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(stats).toHaveLength(1);
|
|
241
|
+
expect(stats![0]!.count).toBe(4);
|
|
242
|
+
expect(stats![0]!.metrics).toMatchObject({
|
|
243
|
+
totalBytesRead: 3072,
|
|
244
|
+
totalBytesWritten: 512,
|
|
245
|
+
avgDurationMs: 12.5, // (10 + 15 + 20 + 5) / 4
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("Sorting", () => {
|
|
251
|
+
it("should sort results by date ascending", async () => {
|
|
252
|
+
// Note: We can't easily test multi-day sorting in a unit test
|
|
253
|
+
// since events are timestamped with the current time.
|
|
254
|
+
// This would require mocking Date or using a more sophisticated approach.
|
|
255
|
+
// For now, we just verify that single-day results are returned correctly.
|
|
256
|
+
|
|
257
|
+
await analytics.track("llm.completion.completed", {
|
|
258
|
+
totalTokens: 100,
|
|
259
|
+
cost: 0.01,
|
|
260
|
+
durationMs: 100,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const stats = await analytics.getDailyStats?.({
|
|
264
|
+
type: "llm.completion.completed",
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(stats).toHaveLength(1);
|
|
268
|
+
expect(stats![0]!.date).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("Empty metrics", () => {
|
|
273
|
+
it("should not include metrics object for unknown event types", async () => {
|
|
274
|
+
await analytics.track("custom.event", {
|
|
275
|
+
someData: "value",
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const stats = await analytics.getDailyStats?.({
|
|
279
|
+
type: "custom.event",
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(stats).toHaveLength(1);
|
|
283
|
+
expect(stats![0]!.count).toBe(1);
|
|
284
|
+
expect(stats![0]!.metrics).toBeUndefined();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/adapters-analytics-file/__tests__/scoped-analytics
|
|
3
|
+
* Tests for analytics source attribution and scoping
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { mkdtemp, rm, readFile } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { createAdapter } from "../index.js";
|
|
11
|
+
import type {
|
|
12
|
+
IAnalytics,
|
|
13
|
+
AnalyticsContext,
|
|
14
|
+
} from "@kb-labs/core-platform/adapters";
|
|
15
|
+
|
|
16
|
+
/** Local date stamp for event file names. Adapter uses local date, not UTC. */
|
|
17
|
+
function todayStamp(): string {
|
|
18
|
+
const d = new Date();
|
|
19
|
+
const y = d.getFullYear();
|
|
20
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
21
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
22
|
+
return `${y}${m}${day}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function eventFile(dir: string, stamp: string): string {
|
|
26
|
+
return join(dir, `events-${stamp}.jsonl`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("Analytics Source Attribution", () => {
|
|
30
|
+
let testDir: string;
|
|
31
|
+
let analytics: IAnalytics;
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
// Create temporary directory for test events
|
|
35
|
+
testDir = await mkdtemp(join(tmpdir(), "analytics-test-"));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(async () => {
|
|
39
|
+
// Clean up test directory
|
|
40
|
+
await rm(testDir, { recursive: true, force: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("Default behavior (current implementation)", () => {
|
|
44
|
+
it("should use source from AnalyticsContext", async () => {
|
|
45
|
+
// Create analytics with explicit context (simulating root package.json)
|
|
46
|
+
const context: AnalyticsContext = {
|
|
47
|
+
source: {
|
|
48
|
+
product: "@kb-labs/ai-review",
|
|
49
|
+
version: "1.0.0",
|
|
50
|
+
},
|
|
51
|
+
runId: "test-run-123",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
analytics = createAdapter({ baseDir: testDir, analytics: context });
|
|
55
|
+
const stamp = todayStamp();
|
|
56
|
+
await analytics.track("test.event", { foo: "bar" });
|
|
57
|
+
|
|
58
|
+
const files = await readFile(eventFile(testDir, stamp), "utf-8");
|
|
59
|
+
const event = JSON.parse(files.trim());
|
|
60
|
+
|
|
61
|
+
expect(event.source).toEqual({
|
|
62
|
+
product: "@kb-labs/ai-review",
|
|
63
|
+
version: "1.0.0",
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should use default source if no context provided", async () => {
|
|
68
|
+
analytics = createAdapter({ baseDir: testDir });
|
|
69
|
+
const stamp = todayStamp();
|
|
70
|
+
await analytics.track("test.event", { foo: "bar" });
|
|
71
|
+
|
|
72
|
+
const files = await readFile(eventFile(testDir, stamp), "utf-8");
|
|
73
|
+
const event = JSON.parse(files.trim());
|
|
74
|
+
|
|
75
|
+
expect(event.source).toEqual({
|
|
76
|
+
product: "unknown",
|
|
77
|
+
version: "0.0.0",
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("Multiple plugins tracking events", () => {
|
|
83
|
+
it("should track events from different plugins with same analytics instance", async () => {
|
|
84
|
+
// This test demonstrates the CURRENT PROBLEM:
|
|
85
|
+
// All events show the same source, even if they come from different plugins
|
|
86
|
+
|
|
87
|
+
const rootContext: AnalyticsContext = {
|
|
88
|
+
source: {
|
|
89
|
+
product: "@kb-labs/ai-review", // Root package.json
|
|
90
|
+
version: "1.0.0",
|
|
91
|
+
},
|
|
92
|
+
runId: "test-run-123",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
analytics = createAdapter({ baseDir: testDir, analytics: rootContext });
|
|
96
|
+
const stamp = todayStamp();
|
|
97
|
+
|
|
98
|
+
await analytics.track("mind.rag-index.started", { scope: "default" });
|
|
99
|
+
await analytics.track("workflow.run.started", { workflowId: "test-workflow" });
|
|
100
|
+
await analytics.track("commit.generated", { commits: 2 });
|
|
101
|
+
|
|
102
|
+
const files = await readFile(eventFile(testDir, stamp), "utf-8");
|
|
103
|
+
const events = files
|
|
104
|
+
.trim()
|
|
105
|
+
.split("\n")
|
|
106
|
+
.map((line) => JSON.parse(line));
|
|
107
|
+
|
|
108
|
+
expect(events).toHaveLength(3);
|
|
109
|
+
|
|
110
|
+
// CURRENT BEHAVIOR: All events show same source (ROOT)
|
|
111
|
+
// This is the problem we're trying to fix!
|
|
112
|
+
expect(events[0].source.product).toBe("@kb-labs/ai-review");
|
|
113
|
+
expect(events[1].source.product).toBe("@kb-labs/ai-review");
|
|
114
|
+
expect(events[2].source.product).toBe("@kb-labs/ai-review");
|
|
115
|
+
|
|
116
|
+
// DESIRED BEHAVIOR (after fix):
|
|
117
|
+
// expect(events[0].source.product).toBe('@kb-labs/mind');
|
|
118
|
+
// expect(events[1].source.product).toBe('@kb-labs/workflow');
|
|
119
|
+
// expect(events[2].source.product).toBe('@kb-labs/commit');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("Event schema validation", () => {
|
|
124
|
+
it("should create events with kb.v1 schema", async () => {
|
|
125
|
+
const context: AnalyticsContext = {
|
|
126
|
+
source: { product: "test-product", version: "1.0.0" },
|
|
127
|
+
runId: "test-run-123",
|
|
128
|
+
actor: { type: "user", id: "test@example.com", name: "Test User" },
|
|
129
|
+
ctx: { workspace: "/test/workspace", branch: "main" },
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
analytics = createAdapter({ baseDir: testDir, analytics: context });
|
|
133
|
+
const stamp = todayStamp();
|
|
134
|
+
await analytics.track("test.event", { data: "test" });
|
|
135
|
+
|
|
136
|
+
const files = await readFile(eventFile(testDir, stamp), "utf-8");
|
|
137
|
+
const event = JSON.parse(files.trim());
|
|
138
|
+
|
|
139
|
+
// Validate kb.v1 schema
|
|
140
|
+
expect(event).toMatchObject({
|
|
141
|
+
schema: "kb.v1",
|
|
142
|
+
type: "test.event",
|
|
143
|
+
source: { product: "test-product", version: "1.0.0" },
|
|
144
|
+
runId: "test-run-123",
|
|
145
|
+
actor: { type: "user", id: "test@example.com", name: "Test User" },
|
|
146
|
+
ctx: { workspace: "/test/workspace", branch: "main" },
|
|
147
|
+
payload: { data: "test" },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Verify auto-generated fields
|
|
151
|
+
expect(event.id).toBeDefined();
|
|
152
|
+
expect(event.ts).toBeDefined();
|
|
153
|
+
expect(event.ingestTs).toBeDefined();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("getEvents filtering by source", () => {
|
|
158
|
+
it("should filter events by source.product", async () => {
|
|
159
|
+
const context: AnalyticsContext = {
|
|
160
|
+
source: { product: "@kb-labs/mind", version: "0.1.0" },
|
|
161
|
+
runId: "test-run-123",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
analytics = createAdapter({ baseDir: testDir, analytics: context });
|
|
165
|
+
|
|
166
|
+
await analytics.track("mind.event1", {});
|
|
167
|
+
await analytics.track("mind.event2", {});
|
|
168
|
+
await analytics.track("mind.event3", {});
|
|
169
|
+
|
|
170
|
+
// Filter by source
|
|
171
|
+
const result = await analytics.getEvents!({ source: "@kb-labs/mind" });
|
|
172
|
+
|
|
173
|
+
expect(result.total).toBe(3);
|
|
174
|
+
expect(result.events).toHaveLength(3);
|
|
175
|
+
expect(
|
|
176
|
+
result.events.every((e) => e.source.product === "@kb-labs/mind"),
|
|
177
|
+
).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should return empty when filtering for non-existent source", async () => {
|
|
181
|
+
const context: AnalyticsContext = {
|
|
182
|
+
source: { product: "@kb-labs/mind", version: "0.1.0" },
|
|
183
|
+
runId: "test-run-123",
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
analytics = createAdapter({ baseDir: testDir, analytics: context });
|
|
187
|
+
await analytics.track("mind.event", {});
|
|
188
|
+
|
|
189
|
+
// Filter for different source
|
|
190
|
+
const result = await analytics.getEvents!({
|
|
191
|
+
source: "@kb-labs/workflow",
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(result.total).toBe(0);
|
|
195
|
+
expect(result.events).toHaveLength(0);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("getStats aggregation by source", () => {
|
|
200
|
+
it("should aggregate events by source", async () => {
|
|
201
|
+
// Create multiple analytics instances with different sources
|
|
202
|
+
const mindAnalytics = createAdapter(
|
|
203
|
+
{ baseDir: testDir },
|
|
204
|
+
{
|
|
205
|
+
source: { product: "@kb-labs/mind", version: "0.1.0" },
|
|
206
|
+
runId: "run-1",
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const workflowAnalytics = createAdapter(
|
|
211
|
+
{ baseDir: testDir },
|
|
212
|
+
{
|
|
213
|
+
source: { product: "@kb-labs/workflow", version: "2.0.0" },
|
|
214
|
+
runId: "run-2",
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// Track events from different sources
|
|
219
|
+
await mindAnalytics.track("mind.event1", {});
|
|
220
|
+
await mindAnalytics.track("mind.event2", {});
|
|
221
|
+
await workflowAnalytics.track("workflow.event1", {});
|
|
222
|
+
|
|
223
|
+
// Get stats (reads all events from directory)
|
|
224
|
+
const stats = await mindAnalytics.getStats!();
|
|
225
|
+
|
|
226
|
+
expect(stats.totalEvents).toBe(3);
|
|
227
|
+
expect(stats.bySource).toEqual({
|
|
228
|
+
"@kb-labs/mind": 2,
|
|
229
|
+
"@kb-labs/workflow": 1,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|