@kybernesis/brain-testkit 0.1.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.
@@ -0,0 +1,8 @@
1
+ import { type TenantContext } from '@kybernesis/brain-contracts';
2
+ export interface TestTenantHandle {
3
+ tenant: TenantContext;
4
+ root: string;
5
+ cleanup: () => void;
6
+ }
7
+ export declare function createTestTenant(slug?: string): TestTenantHandle;
8
+ //# sourceMappingURL=tenant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/helpers/tenant.ts"],"names":[],"mappings":"AAGA,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE3E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAYD,wBAAgB,gBAAgB,CAAC,IAAI,SAAS,GAAG,gBAAgB,CAsBhE"}
@@ -0,0 +1,37 @@
1
+ import { tmpdir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import { mkdirSync, rmSync } from 'node:fs';
4
+ import { pathsFor } from '@kybernesis/brain-contracts';
5
+ /**
6
+ * Creates an isolated TenantContext rooted in tmpdir. Each call gets its own
7
+ * directory (process-unique slug + timestamp + counter) so parallel tests
8
+ * never share SQLite files.
9
+ *
10
+ * Usage:
11
+ * const { tenant, cleanup } = createTestTenant();
12
+ * afterAll(cleanup);
13
+ */
14
+ let counter = 0;
15
+ export function createTestTenant(slug = 'test') {
16
+ counter += 1;
17
+ const uniq = `${slug}-${process.pid}-${Date.now()}-${counter}`;
18
+ const root = join(tmpdir(), `cortex-test-${uniq}`);
19
+ mkdirSync(join(root, 'brain'), { recursive: true });
20
+ const tenant = {
21
+ slug: uniq,
22
+ paths: pathsFor(uniq, root),
23
+ };
24
+ return {
25
+ tenant,
26
+ root,
27
+ cleanup: () => {
28
+ try {
29
+ rmSync(root, { recursive: true, force: true });
30
+ }
31
+ catch {
32
+ /* ignore — best-effort */
33
+ }
34
+ },
35
+ };
36
+ }
37
+ //# sourceMappingURL=tenant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.js","sourceRoot":"","sources":["../../src/helpers/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAsB,MAAM,6BAA6B,CAAC;AAQ3E;;;;;;;;GAQG;AACH,IAAI,OAAO,GAAG,CAAC,CAAC;AAChB,MAAM,UAAU,gBAAgB,CAAC,IAAI,GAAG,MAAM;IAC5C,OAAO,IAAI,CAAC,CAAC;IACb,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,OAAO,EAAE,CAAC;IAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,IAAI,EAAE,CAAC,CAAC;IACnD,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAkB;QAC5B,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;KAC5B,CAAC;IAEF,OAAO;QACL,MAAM;QACN,IAAI;QACJ,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,CAAC;gBACH,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;YAAC,MAAM,CAAC;gBACP,0BAA0B;YAC5B,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './parity/index.js';
2
+ export * from './helpers/tenant.js';
3
+ export * from './llm-stub.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './parity/index.js';
2
+ export * from './helpers/tenant.js';
3
+ export * from './llm-stub.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC"}
@@ -0,0 +1,11 @@
1
+ interface LLMProvider {
2
+ call(prompt: string, opts?: unknown): Promise<string | null>;
3
+ }
4
+ /**
5
+ * Test double for LLMProvider. `responses` is a map from prompt substring to
6
+ * the string the stub should return. '*' is a special wildcard fallback — it
7
+ * only fires when no other key matches (regardless of insertion order).
8
+ */
9
+ export declare function makeLLMStub(responses?: Record<string, string>): LLMProvider;
10
+ export {};
11
+ //# sourceMappingURL=llm-stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-stub.d.ts","sourceRoot":"","sources":["../src/llm-stub.ts"],"names":[],"mappings":"AAEA,UAAU,WAAW;IACnB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,WAAW,CAS/E"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Test double for LLMProvider. `responses` is a map from prompt substring to
3
+ * the string the stub should return. '*' is a special wildcard fallback — it
4
+ * only fires when no other key matches (regardless of insertion order).
5
+ */
6
+ export function makeLLMStub(responses = {}) {
7
+ return {
8
+ async call(prompt) {
9
+ for (const [key, value] of Object.entries(responses)) {
10
+ if (key !== '*' && prompt.includes(key))
11
+ return value;
12
+ }
13
+ return responses['*'] ?? null;
14
+ },
15
+ };
16
+ }
17
+ //# sourceMappingURL=llm-stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-stub.js","sourceRoot":"","sources":["../src/llm-stub.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,YAAoC,EAAE;IAChE,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,MAAc;YACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;gBACrD,IAAI,GAAG,KAAK,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;oBAAE,OAAO,KAAK,CAAC;YACxD,CAAC;YACD,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;QAChC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,34 @@
1
+ export interface ParityQuery {
2
+ id: string;
3
+ input: unknown;
4
+ }
5
+ export interface ParityHarnessInput<TResult, TId = string> {
6
+ queries: ParityQuery[];
7
+ baseline: (input: unknown) => Promise<TResult>;
8
+ candidate: (input: unknown) => Promise<TResult>;
9
+ extractIds: (result: TResult) => TId[];
10
+ topN?: number;
11
+ threshold?: number;
12
+ }
13
+ export interface ParityPerQueryReport<TId = string> {
14
+ queryId: string;
15
+ overlap: number;
16
+ baselineIds: TId[];
17
+ candidateIds: TId[];
18
+ missingFromCandidate: TId[];
19
+ extraInCandidate: TId[];
20
+ error?: {
21
+ side: 'baseline' | 'candidate';
22
+ message: string;
23
+ };
24
+ }
25
+ export interface ParityReport<TId = string> {
26
+ passes: boolean;
27
+ threshold: number;
28
+ topN: number;
29
+ meanOverlap: number;
30
+ totalQueries: number;
31
+ perQuery: ParityPerQueryReport<TId>[];
32
+ }
33
+ export declare function runParityHarness<TResult, TId = string>(input: ParityHarnessInput<TResult, TId>): Promise<ParityReport<TId>>;
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parity/index.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM;IACvD,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,GAAG,EAAE,CAAC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB,CAAC,GAAG,GAAG,MAAM;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,GAAG,EAAE,CAAC;IACnB,YAAY,EAAE,GAAG,EAAE,CAAC;IACpB,oBAAoB,EAAE,GAAG,EAAE,CAAC;IAC5B,gBAAgB,EAAE,GAAG,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AAED,MAAM,WAAW,YAAY,CAAC,GAAG,GAAG,MAAM;IACxC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;CACvC;AAKD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAC1D,KAAK,EAAE,kBAAkB,CAAC,OAAO,EAAE,GAAG,CAAC,GACtC,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAsE5B"}
@@ -0,0 +1,79 @@
1
+ // Parity harness — generic top-N overlap test for swapping a working
2
+ // parallel implementation to a library implementation.
3
+ //
4
+ // Port from cortex-appydave/packages/cortex-testkit/src/parity/index.ts
5
+ // (verbatim, as required by the port-faithful rule).
6
+ //
7
+ // Usage:
8
+ // const report = await runParityHarness({
9
+ // queries: [{ id: 'q1', input: { query: 'kybernesis' } }],
10
+ // baseline: (input) => legacyHybridSearch(input as Query),
11
+ // candidate: (input) => library.hybridSearch(input as Query),
12
+ // extractIds: (r) => r.data.map((row) => row.id),
13
+ // });
14
+ // expect(report.passes).toBe(true);
15
+ const DEFAULT_TOP_N = 10;
16
+ const DEFAULT_THRESHOLD = 0.8;
17
+ export async function runParityHarness(input) {
18
+ const topN = input.topN ?? DEFAULT_TOP_N;
19
+ const threshold = input.threshold ?? DEFAULT_THRESHOLD;
20
+ const perQuery = [];
21
+ for (const q of input.queries) {
22
+ let baselineResult;
23
+ let candidateResult;
24
+ let error;
25
+ try {
26
+ baselineResult = await input.baseline(q.input);
27
+ }
28
+ catch (err) {
29
+ error = { side: 'baseline', message: err.message };
30
+ }
31
+ if (!error) {
32
+ try {
33
+ candidateResult = await input.candidate(q.input);
34
+ }
35
+ catch (err) {
36
+ error = { side: 'candidate', message: err.message };
37
+ }
38
+ }
39
+ if (error || baselineResult === undefined || candidateResult === undefined) {
40
+ perQuery.push({
41
+ queryId: q.id,
42
+ overlap: 0,
43
+ baselineIds: [],
44
+ candidateIds: [],
45
+ missingFromCandidate: [],
46
+ extraInCandidate: [],
47
+ error,
48
+ });
49
+ continue;
50
+ }
51
+ const baselineIds = input.extractIds(baselineResult).slice(0, topN);
52
+ const candidateIds = input.extractIds(candidateResult).slice(0, topN);
53
+ const candidateSet = new Set(candidateIds);
54
+ const baselineSet = new Set(baselineIds);
55
+ const overlap = baselineIds.length === 0
56
+ ? 0
57
+ : baselineIds.filter((id) => candidateSet.has(id)).length / baselineIds.length;
58
+ perQuery.push({
59
+ queryId: q.id,
60
+ overlap,
61
+ baselineIds,
62
+ candidateIds,
63
+ missingFromCandidate: baselineIds.filter((id) => !candidateSet.has(id)),
64
+ extraInCandidate: candidateIds.filter((id) => !baselineSet.has(id)),
65
+ });
66
+ }
67
+ const meanOverlap = perQuery.length === 0
68
+ ? 0
69
+ : perQuery.reduce((sum, q) => sum + q.overlap, 0) / perQuery.length;
70
+ return {
71
+ passes: perQuery.length > 0 && meanOverlap >= threshold,
72
+ threshold,
73
+ topN,
74
+ meanOverlap,
75
+ totalQueries: perQuery.length,
76
+ perQuery,
77
+ };
78
+ }
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parity/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@kybernesis/brain-testkit",
3
+ "version": "0.1.0",
4
+ "description": "In-memory fakes and parity harness for testing brain-* consumers",
5
+ "license": "MIT",
6
+ "author": "David Cruwys (AppyDave)",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/KybernesisAI/cortex.git",
10
+ "directory": "packages/brain-testkit"
11
+ },
12
+ "homepage": "https://github.com/KybernesisAI/cortex/tree/main/packages/brain-testkit#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/KybernesisAI/cortex/issues"
15
+ },
16
+ "type": "module",
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./parity": {
25
+ "types": "./dist/parity/index.d.ts",
26
+ "default": "./dist/parity/index.js"
27
+ },
28
+ "./helpers": {
29
+ "types": "./dist/helpers/tenant.d.ts",
30
+ "default": "./dist/helpers/tenant.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md"
36
+ ],
37
+ "dependencies": {
38
+ "@kybernesis/brain-contracts": "0.1.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "scripts": {
44
+ "build": "tsc -b",
45
+ "clean": "tsc -b --clean",
46
+ "typecheck": "tsc -b"
47
+ }
48
+ }