@skillsmith/core 0.5.8 → 0.6.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 (172) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/audit/exclusions.d.ts +67 -0
  4. package/dist/src/audit/exclusions.d.ts.map +1 -0
  5. package/dist/src/audit/exclusions.js +133 -0
  6. package/dist/src/audit/exclusions.js.map +1 -0
  7. package/dist/src/audit/exclusions.types.d.ts +45 -0
  8. package/dist/src/audit/exclusions.types.d.ts.map +1 -0
  9. package/dist/src/audit/exclusions.types.js +11 -0
  10. package/dist/src/audit/exclusions.types.js.map +1 -0
  11. package/dist/src/audit/index.d.ts +15 -0
  12. package/dist/src/audit/index.d.ts.map +1 -0
  13. package/dist/src/audit/index.js +14 -0
  14. package/dist/src/audit/index.js.map +1 -0
  15. package/dist/src/benchmarks/embeddingBenchmark.d.ts.map +1 -1
  16. package/dist/src/benchmarks/embeddingBenchmark.js +5 -2
  17. package/dist/src/benchmarks/embeddingBenchmark.js.map +1 -1
  18. package/dist/src/config/audit-mode.d.ts +71 -0
  19. package/dist/src/config/audit-mode.d.ts.map +1 -0
  20. package/dist/src/config/audit-mode.js +69 -0
  21. package/dist/src/config/audit-mode.js.map +1 -0
  22. package/dist/src/config/index.d.ts +13 -0
  23. package/dist/src/config/index.d.ts.map +1 -1
  24. package/dist/src/config/index.js +24 -0
  25. package/dist/src/config/index.js.map +1 -1
  26. package/dist/src/db/migration-runner.d.ts +9 -2
  27. package/dist/src/db/migration-runner.d.ts.map +1 -1
  28. package/dist/src/db/migration-runner.js +30 -3
  29. package/dist/src/db/migration-runner.js.map +1 -1
  30. package/dist/src/db/migration.d.ts.map +1 -1
  31. package/dist/src/db/migration.js +9 -1
  32. package/dist/src/db/migration.js.map +1 -1
  33. package/dist/src/db/migrations/v16-skill-source.d.ts +41 -0
  34. package/dist/src/db/migrations/v16-skill-source.d.ts.map +1 -0
  35. package/dist/src/db/migrations/v16-skill-source.js +87 -0
  36. package/dist/src/db/migrations/v16-skill-source.js.map +1 -0
  37. package/dist/src/db/schema-sql.d.ts +1 -1
  38. package/dist/src/db/schema-sql.d.ts.map +1 -1
  39. package/dist/src/db/schema-sql.js +2 -1
  40. package/dist/src/db/schema-sql.js.map +1 -1
  41. package/dist/src/db/schema.d.ts +9 -3
  42. package/dist/src/db/schema.d.ts.map +1 -1
  43. package/dist/src/db/schema.js +12 -2
  44. package/dist/src/db/schema.js.map +1 -1
  45. package/dist/src/embeddings/embedding-utils.d.ts +23 -0
  46. package/dist/src/embeddings/embedding-utils.d.ts.map +1 -1
  47. package/dist/src/embeddings/embedding-utils.js +39 -0
  48. package/dist/src/embeddings/embedding-utils.js.map +1 -1
  49. package/dist/src/embeddings/hnsw-search.d.ts +133 -0
  50. package/dist/src/embeddings/hnsw-search.d.ts.map +1 -0
  51. package/dist/src/embeddings/hnsw-search.js +387 -0
  52. package/dist/src/embeddings/hnsw-search.js.map +1 -0
  53. package/dist/src/embeddings/hnsw-store.d.ts +9 -3
  54. package/dist/src/embeddings/hnsw-store.d.ts.map +1 -1
  55. package/dist/src/embeddings/hnsw-store.js +17 -7
  56. package/dist/src/embeddings/hnsw-store.js.map +1 -1
  57. package/dist/src/embeddings/hnsw-store.types.d.ts +17 -4
  58. package/dist/src/embeddings/hnsw-store.types.d.ts.map +1 -1
  59. package/dist/src/embeddings/hnsw-store.types.js.map +1 -1
  60. package/dist/src/embeddings/index.d.ts +50 -4
  61. package/dist/src/embeddings/index.d.ts.map +1 -1
  62. package/dist/src/embeddings/index.js +166 -24
  63. package/dist/src/embeddings/index.js.map +1 -1
  64. package/dist/src/index.d.ts +4 -1
  65. package/dist/src/index.d.ts.map +1 -1
  66. package/dist/src/index.js +5 -1
  67. package/dist/src/index.js.map +1 -1
  68. package/dist/src/install/fan-out.d.ts +81 -0
  69. package/dist/src/install/fan-out.d.ts.map +1 -0
  70. package/dist/src/install/fan-out.js +236 -0
  71. package/dist/src/install/fan-out.js.map +1 -0
  72. package/dist/src/install/index.d.ts +16 -0
  73. package/dist/src/install/index.d.ts.map +1 -0
  74. package/dist/src/install/index.js +14 -0
  75. package/dist/src/install/index.js.map +1 -0
  76. package/dist/src/install/paths.d.ts +16 -0
  77. package/dist/src/install/paths.d.ts.map +1 -0
  78. package/dist/src/install/paths.js +56 -0
  79. package/dist/src/install/paths.js.map +1 -0
  80. package/dist/src/repositories/AdvisoryRepository.test.js +2 -2
  81. package/dist/src/repositories/AdvisoryRepository.test.js.map +1 -1
  82. package/dist/src/repositories/SkillRepository.d.ts.map +1 -1
  83. package/dist/src/repositories/SkillRepository.js +12 -4
  84. package/dist/src/repositories/SkillRepository.js.map +1 -1
  85. package/dist/src/search/hybrid.js +2 -2
  86. package/dist/src/search/hybrid.js.map +1 -1
  87. package/dist/src/security/pathValidation.d.ts +6 -1
  88. package/dist/src/security/pathValidation.d.ts.map +1 -1
  89. package/dist/src/security/pathValidation.js +6 -1
  90. package/dist/src/security/pathValidation.js.map +1 -1
  91. package/dist/src/services/skill-installation.service.d.ts.map +1 -1
  92. package/dist/src/services/skill-installation.service.js +4 -2
  93. package/dist/src/services/skill-installation.service.js.map +1 -1
  94. package/dist/src/skills/index-local.d.ts +107 -0
  95. package/dist/src/skills/index-local.d.ts.map +1 -0
  96. package/dist/src/skills/index-local.js +208 -0
  97. package/dist/src/skills/index-local.js.map +1 -0
  98. package/dist/src/sync/SyncEngine.d.ts.map +1 -1
  99. package/dist/src/sync/SyncEngine.js +20 -3
  100. package/dist/src/sync/SyncEngine.js.map +1 -1
  101. package/dist/src/types/skill.d.ts +19 -4
  102. package/dist/src/types/skill.d.ts.map +1 -1
  103. package/dist/src/types/skill.js.map +1 -1
  104. package/dist/tests/EmbeddingService.test.js +1 -1
  105. package/dist/tests/EmbeddingService.test.js.map +1 -1
  106. package/dist/tests/SkillVersionRepository.test.js +2 -2
  107. package/dist/tests/SkillVersionRepository.test.js.map +1 -1
  108. package/dist/tests/db/migration.test.js +13 -5
  109. package/dist/tests/db/migration.test.js.map +1 -1
  110. package/dist/tests/embeddings/hnsw-bench-gate.test.d.ts +15 -0
  111. package/dist/tests/embeddings/hnsw-bench-gate.test.d.ts.map +1 -0
  112. package/dist/tests/embeddings/hnsw-bench-gate.test.js +117 -0
  113. package/dist/tests/embeddings/hnsw-bench-gate.test.js.map +1 -0
  114. package/dist/tests/embeddings/hnsw-integration.test.d.ts +14 -0
  115. package/dist/tests/embeddings/hnsw-integration.test.d.ts.map +1 -0
  116. package/dist/tests/embeddings/hnsw-integration.test.js +160 -0
  117. package/dist/tests/embeddings/hnsw-integration.test.js.map +1 -0
  118. package/dist/tests/embeddings/hnsw-vs-brute-force.bench.d.ts +15 -0
  119. package/dist/tests/embeddings/hnsw-vs-brute-force.bench.d.ts.map +1 -0
  120. package/dist/tests/embeddings/hnsw-vs-brute-force.bench.js +64 -0
  121. package/dist/tests/embeddings/hnsw-vs-brute-force.bench.js.map +1 -0
  122. package/dist/tests/embeddings/seed-bench.d.ts +15 -0
  123. package/dist/tests/embeddings/seed-bench.d.ts.map +1 -0
  124. package/dist/tests/embeddings/seed-bench.js +61 -0
  125. package/dist/tests/embeddings/seed-bench.js.map +1 -0
  126. package/dist/tests/helpers/database.d.ts +11 -2
  127. package/dist/tests/helpers/database.d.ts.map +1 -1
  128. package/dist/tests/helpers/database.js +23 -7
  129. package/dist/tests/helpers/database.js.map +1 -1
  130. package/dist/tests/install/fan-out.test.d.ts +2 -0
  131. package/dist/tests/install/fan-out.test.d.ts.map +1 -0
  132. package/dist/tests/install/fan-out.test.js +238 -0
  133. package/dist/tests/install/fan-out.test.js.map +1 -0
  134. package/dist/tests/install/paths.test.d.ts +2 -0
  135. package/dist/tests/install/paths.test.d.ts.map +1 -0
  136. package/dist/tests/install/paths.test.js +79 -0
  137. package/dist/tests/install/paths.test.js.map +1 -0
  138. package/dist/tests/repositories/CoInstallRepository.test.js +2 -2
  139. package/dist/tests/repositories/CoInstallRepository.test.js.map +1 -1
  140. package/dist/tests/repositories/SkillDependencyRepository.test.js +2 -2
  141. package/dist/tests/repositories/SkillDependencyRepository.test.js.map +1 -1
  142. package/dist/tests/schema.test.js +12 -0
  143. package/dist/tests/schema.test.js.map +1 -1
  144. package/dist/tests/skill-scanner/allowlist.test.js +27 -4
  145. package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
  146. package/dist/tests/unit/audit/exclusions.test.d.ts +7 -0
  147. package/dist/tests/unit/audit/exclusions.test.d.ts.map +1 -0
  148. package/dist/tests/unit/audit/exclusions.test.js +157 -0
  149. package/dist/tests/unit/audit/exclusions.test.js.map +1 -0
  150. package/dist/tests/unit/config/audit-mode.test.d.ts +11 -0
  151. package/dist/tests/unit/config/audit-mode.test.d.ts.map +1 -0
  152. package/dist/tests/unit/config/audit-mode.test.js +86 -0
  153. package/dist/tests/unit/config/audit-mode.test.js.map +1 -0
  154. package/dist/tests/unit/migrations/migration-v16.test.d.ts +11 -0
  155. package/dist/tests/unit/migrations/migration-v16.test.d.ts.map +1 -0
  156. package/dist/tests/unit/migrations/migration-v16.test.js +134 -0
  157. package/dist/tests/unit/migrations/migration-v16.test.js.map +1 -0
  158. package/dist/tests/unit/migrations/v10-dependencies.test.js +2 -2
  159. package/dist/tests/unit/migrations/v10-dependencies.test.js.map +1 -1
  160. package/dist/tests/unit/services/skill-installation-extended.test.js +1 -1
  161. package/dist/tests/unit/services/skill-installation-extended.test.js.map +1 -1
  162. package/dist/tests/unit/services/skill-installation.service.test.js +1 -1
  163. package/dist/tests/unit/services/skill-installation.service.test.js.map +1 -1
  164. package/dist/tests/unit/skills/index-local.test.d.ts +11 -0
  165. package/dist/tests/unit/skills/index-local.test.d.ts.map +1 -0
  166. package/dist/tests/unit/skills/index-local.test.js +88 -0
  167. package/dist/tests/unit/skills/index-local.test.js.map +1 -0
  168. package/dist/tests/unit/sync-engine.source-aware.test.d.ts +11 -0
  169. package/dist/tests/unit/sync-engine.source-aware.test.d.ts.map +1 -0
  170. package/dist/tests/unit/sync-engine.source-aware.test.js +147 -0
  171. package/dist/tests/unit/sync-engine.source-aware.test.js.map +1 -0
  172. package/package.json +26 -3
@@ -0,0 +1,160 @@
1
+ /**
2
+ * SMI-4577: HNSW + EmbeddingService integration test.
3
+ *
4
+ * Asserts:
5
+ * - `findSimilar` returns the same top-1 as the brute-force fallback
6
+ * - recall@10 ≥ 0.95 across 50 query iterations
7
+ * - the on-disk cache (`~/.skillsmith/cache/hnsw-{model}.bin`) is created
8
+ * after first call
9
+ * - deleting the cache forces a rebuild on the next call (no crash)
10
+ *
11
+ * Uses a temp `HOME` to keep the cache out of the user's real `~/.skillsmith/`.
12
+ */
13
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
14
+ import { existsSync, mkdirSync, rmSync, unlinkSync } from 'node:fs';
15
+ import { createRequire } from 'node:module';
16
+ import { join } from 'node:path';
17
+ import { tmpdir } from 'node:os';
18
+ import { EmbeddingService } from '../../src/embeddings/index.js';
19
+ const MODEL_NAME_SAFE = 'Xenova__all-MiniLM-L6-v2';
20
+ // SMI-4691: hnswlib-node is an optionalDependency. When the native binding is
21
+ // unavailable (e.g. macOS host without the postinstall step), EmbeddingService
22
+ // silently falls through to brute-force and never persists a cache file. The
23
+ // cache-write assertion is only meaningful when HNSW is actually loaded.
24
+ const HNSW_AVAILABLE = (() => {
25
+ try {
26
+ createRequire(import.meta.url)('hnswlib-node');
27
+ return true;
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ })();
33
+ describe('HNSW + EmbeddingService integration (SMI-4577)', () => {
34
+ let tmpHome;
35
+ let originalHome;
36
+ let originalCacheOverride;
37
+ let service;
38
+ let dbPath;
39
+ beforeEach(async () => {
40
+ tmpHome = join(tmpdir(), `skillsmith-hnsw-int-${Date.now()}-${Math.random().toString(36).slice(2)}`);
41
+ mkdirSync(tmpHome, { recursive: true });
42
+ originalHome = process.env.HOME;
43
+ process.env.HOME = tmpHome;
44
+ // SMI-4691: HOME stub is ignored by os.homedir() on macOS (getpwuid path).
45
+ // Pin getCacheDir() to the temp tree explicitly via the override env.
46
+ originalCacheOverride = process.env.SKILLSMITH_CACHE_DIR_OVERRIDE;
47
+ process.env.SKILLSMITH_CACHE_DIR_OVERRIDE = join(tmpHome, '.skillsmith', 'cache');
48
+ process.env.SKILLSMITH_USE_MOCK_EMBEDDINGS = 'true';
49
+ delete process.env.SKILLSMITH_USE_HNSW;
50
+ dbPath = join(tmpHome, 'skills.db');
51
+ service = await EmbeddingService.create({ dbPath, useFallback: true });
52
+ // Seed 100 deterministic embeddings.
53
+ const skills = Array.from({ length: 100 }, (_, i) => ({
54
+ id: `skill-${i}`,
55
+ text: `Skill ${i} category ${i % 7} description ${i % 13}`,
56
+ }));
57
+ const batch = await service.embedBatch(skills);
58
+ for (const { skillId, embedding, text } of batch) {
59
+ service.storeEmbedding(skillId, embedding, text);
60
+ }
61
+ });
62
+ afterEach(() => {
63
+ service?.close();
64
+ if (originalHome !== undefined) {
65
+ process.env.HOME = originalHome;
66
+ }
67
+ else {
68
+ delete process.env.HOME;
69
+ }
70
+ if (originalCacheOverride !== undefined) {
71
+ process.env.SKILLSMITH_CACHE_DIR_OVERRIDE = originalCacheOverride;
72
+ }
73
+ else {
74
+ delete process.env.SKILLSMITH_CACHE_DIR_OVERRIDE;
75
+ }
76
+ if (existsSync(tmpHome)) {
77
+ try {
78
+ rmSync(tmpHome, { recursive: true, force: true });
79
+ }
80
+ catch {
81
+ /* best-effort cleanup */
82
+ }
83
+ }
84
+ });
85
+ it('findSimilar produces deterministic top-1 matching brute-force', async () => {
86
+ const all = service.getAllEmbeddings();
87
+ const someVector = Array.from(all.values())[0];
88
+ const hnsw = await service.findSimilar(someVector, 10);
89
+ const brute = service.findSimilarBruteForce(someVector, 10);
90
+ expect(hnsw.length).toBe(brute.length);
91
+ expect(hnsw[0]?.skillId).toBe(brute[0]?.skillId);
92
+ });
93
+ it('recall@10 ≥ 0.95 across 50 query iterations (score-tolerant)', async () => {
94
+ // Mock embeddings produce many tied cosine scores in the top-10 band
95
+ // (variants of the same template share most of their text). Treat
96
+ // HNSW hits within `1e-6` of any brute-force top-10 score as
97
+ // semantically equivalent to capture the intent of the gate.
98
+ const all = service.getAllEmbeddings();
99
+ const vectors = Array.from(all.values());
100
+ let sum = 0;
101
+ let count = 0;
102
+ const SCORE_TIE_EPSILON = 1e-6;
103
+ for (let i = 0; i < 50; i++) {
104
+ const q = vectors[(i * 31) % vectors.length];
105
+ const brute = service.findSimilarBruteForce(q, 10);
106
+ const hnsw = await service.findSimilar(q, 10);
107
+ const bruteIds = new Set(brute.map((r) => r.skillId));
108
+ const bruteScores = brute.map((r) => r.score);
109
+ let hits = 0;
110
+ for (const r of hnsw) {
111
+ if (bruteIds.has(r.skillId)) {
112
+ hits++;
113
+ }
114
+ else if (bruteScores.some((s) => Math.abs(s - r.score) < SCORE_TIE_EPSILON)) {
115
+ hits++;
116
+ }
117
+ }
118
+ sum += hits / 10;
119
+ count++;
120
+ }
121
+ expect(sum / count).toBeGreaterThanOrEqual(0.95);
122
+ });
123
+ it.skipIf(!HNSW_AVAILABLE)('writes ~/.skillsmith/cache/hnsw-{model}.bin after first call', async () => {
124
+ const all = service.getAllEmbeddings();
125
+ const someVector = Array.from(all.values())[0];
126
+ await service.findSimilar(someVector, 10);
127
+ // Force the debounced persist.
128
+ service.close();
129
+ const cacheBin = join(tmpHome, '.skillsmith', 'cache', `hnsw-${MODEL_NAME_SAFE}.bin`);
130
+ expect(existsSync(cacheBin)).toBe(true);
131
+ });
132
+ it('rebuilds index when cache is deleted (no crash)', async () => {
133
+ const all = service.getAllEmbeddings();
134
+ const someVector = Array.from(all.values())[0];
135
+ await service.findSimilar(someVector, 10);
136
+ service.close();
137
+ const cacheBin = join(tmpHome, '.skillsmith', 'cache', `hnsw-${MODEL_NAME_SAFE}.bin`);
138
+ if (existsSync(cacheBin)) {
139
+ unlinkSync(cacheBin);
140
+ }
141
+ // New service should rebuild from SQLite without erroring.
142
+ const service2 = await EmbeddingService.create({ dbPath, useFallback: true });
143
+ const result = await service2.findSimilar(someVector, 10);
144
+ expect(result.length).toBeGreaterThan(0);
145
+ service2.close();
146
+ });
147
+ it('honours SKILLSMITH_USE_HNSW=false (brute-force only path)', async () => {
148
+ process.env.SKILLSMITH_USE_HNSW = 'false';
149
+ try {
150
+ const all = service.getAllEmbeddings();
151
+ const someVector = Array.from(all.values())[0];
152
+ const result = await service.findSimilar(someVector, 5);
153
+ expect(result.length).toBe(5);
154
+ }
155
+ finally {
156
+ delete process.env.SKILLSMITH_USE_HNSW;
157
+ }
158
+ });
159
+ });
160
+ //# sourceMappingURL=hnsw-integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hnsw-integration.test.js","sourceRoot":"","sources":["../../../tests/embeddings/hnsw-integration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SMI-4577: HNSW vs. brute-force microbench.
3
+ *
4
+ * Reports `bench()` timings for both backends. **Does NOT fail CI on
5
+ * regression** — vitest's bench mode is reporting-only. The hard CI gate
6
+ * lives in `hnsw-bench-gate.test.ts` which mirrors the same workload inside
7
+ * a `test()` and asserts the 5x p99 + recall@10 + rss-delta thresholds.
8
+ *
9
+ * Memory tracked via `process.memoryUsage().rss` (NOT `heapUsed` — hnswlib's
10
+ * graph lives in C++ memory and would undercount).
11
+ *
12
+ * Run: `docker exec skillsmith-dev-1 npm run bench:hnsw --workspace=@skillsmith/core`
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=hnsw-vs-brute-force.bench.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hnsw-vs-brute-force.bench.d.ts","sourceRoot":"","sources":["../../../tests/embeddings/hnsw-vs-brute-force.bench.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * SMI-4577: HNSW vs. brute-force microbench.
3
+ *
4
+ * Reports `bench()` timings for both backends. **Does NOT fail CI on
5
+ * regression** — vitest's bench mode is reporting-only. The hard CI gate
6
+ * lives in `hnsw-bench-gate.test.ts` which mirrors the same workload inside
7
+ * a `test()` and asserts the 5x p99 + recall@10 + rss-delta thresholds.
8
+ *
9
+ * Memory tracked via `process.memoryUsage().rss` (NOT `heapUsed` — hnswlib's
10
+ * graph lives in C++ memory and would undercount).
11
+ *
12
+ * Run: `docker exec skillsmith-dev-1 npm run bench:hnsw --workspace=@skillsmith/core`
13
+ */
14
+ import { describe, bench, beforeAll, afterAll } from 'vitest';
15
+ import { existsSync } from 'node:fs';
16
+ import { dirname, resolve } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { spawnSync } from 'node:child_process';
19
+ import { EmbeddingService } from '../../src/embeddings/index.js';
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ const FIXTURE_PATH = resolve(__dirname, 'fixtures', '14k-bench.db');
22
+ // Forced to mock embeddings throughout — the bench exercises the search
23
+ // backend, not the model.
24
+ process.env.SKILLSMITH_USE_MOCK_EMBEDDINGS = 'true';
25
+ let service;
26
+ const queryVectors = [];
27
+ async function ensureFixture() {
28
+ if (existsSync(FIXTURE_PATH))
29
+ return;
30
+ const seedScript = resolve(__dirname, 'seed-bench.ts');
31
+ const result = spawnSync('npx', ['tsx', seedScript], {
32
+ stdio: 'inherit',
33
+ cwd: resolve(__dirname, '..', '..'),
34
+ });
35
+ if (result.status !== 0) {
36
+ throw new Error(`seed-bench failed with exit code ${result.status}`);
37
+ }
38
+ }
39
+ beforeAll(async () => {
40
+ await ensureFixture();
41
+ service = await EmbeddingService.create({ dbPath: FIXTURE_PATH, useFallback: true });
42
+ // 50 random queries — enough samples for stable p99 without dominating
43
+ // bench warmup. Pulled from the same vector space so neighbourhoods exist.
44
+ const allEmbeddings = service.getAllEmbeddings();
45
+ const embeddings = Array.from(allEmbeddings.values());
46
+ for (let i = 0; i < 50; i++) {
47
+ queryVectors.push(embeddings[(i * 271) % embeddings.length]);
48
+ }
49
+ // Warm up the HNSW index so the bench measures steady-state search, not
50
+ // first-call build cost.
51
+ await service.findSimilar(queryVectors[0], 10);
52
+ });
53
+ afterAll(() => {
54
+ service?.close();
55
+ });
56
+ describe('findSimilar @ 14k vectors', () => {
57
+ bench('brute-force findSimilar topK=10', () => {
58
+ service.findSimilarBruteForce(queryVectors[Math.floor(Math.random() * queryVectors.length)], 10);
59
+ }, { iterations: 100, warmupIterations: 10 });
60
+ bench('hnsw findSimilar topK=10', async () => {
61
+ await service.findSimilar(queryVectors[Math.floor(Math.random() * queryVectors.length)], 10);
62
+ }, { iterations: 100, warmupIterations: 10 });
63
+ });
64
+ //# sourceMappingURL=hnsw-vs-brute-force.bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hnsw-vs-brute-force.bench.js","sourceRoot":"","sources":["../../../tests/embeddings/hnsw-vs-brute-force.bench.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SMI-4577: Seed a synthetic 14k-skill embedding fixture for the HNSW bench.
3
+ *
4
+ * Generates deterministic mock embeddings via `EmbeddingService` (with
5
+ * `SKILLSMITH_USE_MOCK_EMBEDDINGS=true` for speed) and writes them to a
6
+ * SQLite cache that the bench reads back without network/model load.
7
+ *
8
+ * Run via: `npm run bench:hnsw:seed --workspace=@skillsmith/core`
9
+ *
10
+ * The fixture is gitignored (see top-level `.gitignore`) so each environment
11
+ * regenerates it on first bench run. CI invokes this as a `pretest` hook so
12
+ * the bench can boot from a clean checkout.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=seed-bench.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-bench.d.ts","sourceRoot":"","sources":["../../../tests/embeddings/seed-bench.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * SMI-4577: Seed a synthetic 14k-skill embedding fixture for the HNSW bench.
3
+ *
4
+ * Generates deterministic mock embeddings via `EmbeddingService` (with
5
+ * `SKILLSMITH_USE_MOCK_EMBEDDINGS=true` for speed) and writes them to a
6
+ * SQLite cache that the bench reads back without network/model load.
7
+ *
8
+ * Run via: `npm run bench:hnsw:seed --workspace=@skillsmith/core`
9
+ *
10
+ * The fixture is gitignored (see top-level `.gitignore`) so each environment
11
+ * regenerates it on first bench run. CI invokes this as a `pretest` hook so
12
+ * the bench can boot from a clean checkout.
13
+ */
14
+ import { existsSync, mkdirSync, rmSync } from 'node:fs';
15
+ import { dirname, resolve } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { EmbeddingService } from '../../src/embeddings/index.js';
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const FIXTURE_PATH = resolve(__dirname, 'fixtures', '14k-bench.db');
20
+ const TARGET_COUNT = 14_000;
21
+ async function main() {
22
+ // Force mock-embedding mode so the seed runs in <1s instead of pulling
23
+ // the all-MiniLM-L6-v2 model. The bench reads vectors back as raw
24
+ // Float32Array — semantic correctness is checked separately in the
25
+ // integration test.
26
+ process.env.SKILLSMITH_USE_MOCK_EMBEDDINGS = 'true';
27
+ mkdirSync(dirname(FIXTURE_PATH), { recursive: true });
28
+ if (existsSync(FIXTURE_PATH)) {
29
+ rmSync(FIXTURE_PATH);
30
+ }
31
+ const service = await EmbeddingService.create({ dbPath: FIXTURE_PATH, useFallback: true });
32
+ // Generate 14k synthetic skills. We oversample 100 base templates with
33
+ // numeric suffixes so vectors form natural clusters (a few "neighbours"
34
+ // per query) rather than uniform random — gives the bench a realistic
35
+ // recall workload.
36
+ const templates = Array.from({ length: 100 }, (_, i) => ({
37
+ id: `template-${i}`,
38
+ text: `Skill template ${i}: testing automation framework category ${i % 12} ${i % 7}`,
39
+ }));
40
+ const skills = [];
41
+ for (let i = 0; i < TARGET_COUNT; i++) {
42
+ const t = templates[i % templates.length];
43
+ skills.push({
44
+ id: `${t.id}-variant-${Math.floor(i / templates.length)}`,
45
+ text: `${t.text} variant ${i}`,
46
+ });
47
+ }
48
+ const start = Date.now();
49
+ const batch = await service.embedBatch(skills.map(({ id, text }) => ({ id, text })));
50
+ for (const { skillId, embedding, text } of batch) {
51
+ service.storeEmbedding(skillId, embedding, text);
52
+ }
53
+ const elapsedMs = Date.now() - start;
54
+ service.close();
55
+ console.log(`[seed-bench] wrote ${batch.length} embeddings to ${FIXTURE_PATH} in ${elapsedMs}ms`);
56
+ }
57
+ main().catch((err) => {
58
+ console.error('[seed-bench] failed:', err);
59
+ process.exit(1);
60
+ });
61
+ //# sourceMappingURL=seed-bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-bench.js","sourceRoot":"","sources":["../../../tests/embeddings/seed-bench.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Test database helpers — prevents "no such table" for migrated tables
3
3
  * @see SMI-2749
4
+ * @see SMI-4756 — async variant added to support WASM fallback in post-merge-verify
4
5
  *
5
6
  * Use createTestDatabase() instead of createDatabase(':memory:') whenever tests
6
7
  * need tables that only exist in migration files (e.g. skill_versions).
@@ -15,6 +16,11 @@
15
16
  * createTestDatabase() iterates MIGRATIONS directly (no version gate) so every
16
17
  * migration's SQL runs unconditionally. New migrations added to MIGRATIONS are
17
18
  * automatically included — no change required here.
19
+ *
20
+ * SMI-4756: createTestDatabase is now async. It uses createDatabaseAsync() so the
21
+ * sql.js WASM driver is used automatically when better-sqlite3 native bindings are
22
+ * unavailable (e.g. in post-merge-verify CI where the native module is not rebuilt
23
+ * for the container platform). Callers must await the result and use async beforeEach.
18
24
  */
19
25
  import type { Database } from '../../src/db/database-interface.js';
20
26
  export { closeDatabase } from '../../src/db/schema.js';
@@ -26,7 +32,10 @@ export type { Database } from '../../src/db/database-interface.js';
26
32
  * sync_config, sync_history). Forward-compatible: new migrations added to
27
33
  * MIGRATIONS are included automatically.
28
34
  *
29
- * @returns Database with full schema + all migrations applied
35
+ * SMI-4756: Returns a Promise so the sql.js WASM driver is used automatically
36
+ * when better-sqlite3 native bindings are unavailable. Callers must await.
37
+ *
38
+ * @returns Promise resolving to a Database with full schema + all migrations applied
30
39
  */
31
- export declare function createTestDatabase(): Database;
40
+ export declare function createTestDatabase(): Promise<Database>;
32
41
  //# sourceMappingURL=database.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../tests/helpers/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAA;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACtD,YAAY,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAA;AAElE;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,QAAQ,CAsB7C"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../tests/helpers/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAA;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACtD,YAAY,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAA;AAElE;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,QAAQ,CAAC,CA4B5D"}
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Test database helpers — prevents "no such table" for migrated tables
3
3
  * @see SMI-2749
4
+ * @see SMI-4756 — async variant added to support WASM fallback in post-merge-verify
4
5
  *
5
6
  * Use createTestDatabase() instead of createDatabase(':memory:') whenever tests
6
7
  * need tables that only exist in migration files (e.g. skill_versions).
@@ -15,8 +16,13 @@
15
16
  * createTestDatabase() iterates MIGRATIONS directly (no version gate) so every
16
17
  * migration's SQL runs unconditionally. New migrations added to MIGRATIONS are
17
18
  * automatically included — no change required here.
19
+ *
20
+ * SMI-4756: createTestDatabase is now async. It uses createDatabaseAsync() so the
21
+ * sql.js WASM driver is used automatically when better-sqlite3 native bindings are
22
+ * unavailable (e.g. in post-merge-verify CI where the native module is not rebuilt
23
+ * for the container platform). Callers must await the result and use async beforeEach.
18
24
  */
19
- import { createDatabase, MIGRATIONS } from '../../src/db/schema.js';
25
+ import { createDatabaseAsync, initializeSchema, MIGRATIONS } from '../../src/db/schema.js';
20
26
  // Re-exported for test convenience — tests only need to import from this module
21
27
  export { closeDatabase } from '../../src/db/schema.js';
22
28
  /**
@@ -26,12 +32,16 @@ export { closeDatabase } from '../../src/db/schema.js';
26
32
  * sync_config, sync_history). Forward-compatible: new migrations added to
27
33
  * MIGRATIONS are included automatically.
28
34
  *
29
- * @returns Database with full schema + all migrations applied
35
+ * SMI-4756: Returns a Promise so the sql.js WASM driver is used automatically
36
+ * when better-sqlite3 native bindings are unavailable. Callers must await.
37
+ *
38
+ * @returns Promise resolving to a Database with full schema + all migrations applied
30
39
  */
31
- export function createTestDatabase() {
32
- // createDatabase() calls initializeSchema() — runs SCHEMA_SQL (handling FTS5 triggers
33
- // and multi-statement SQL correctly) and stamps SCHEMA_VERSION in schema_version.
34
- const db = createDatabase();
40
+ export async function createTestDatabase() {
41
+ // createDatabaseAsync() + initializeSchema() — async path uses WASM fallback
42
+ // when better-sqlite3 native module is unavailable (cross-platform / CI).
43
+ const db = await createDatabaseAsync();
44
+ initializeSchema(db);
35
45
  // Run all migrations unconditionally (no version gate) so tables that only exist in
36
46
  // migration SQL (not SCHEMA_SQL) are created. db.exec() handles multi-statement SQL
37
47
  // natively — no semicolon split needed.
@@ -40,7 +50,13 @@ export function createTestDatabase() {
40
50
  // already included in the canonical SCHEMA_SQL. Those errors are expected and safe.
41
51
  for (const migration of MIGRATIONS) {
42
52
  try {
43
- db.exec(migration.sql);
53
+ // SMI-4665: migrations may carry an `apply` function instead of `sql`.
54
+ if (migration.apply) {
55
+ migration.apply(db);
56
+ }
57
+ else if (migration.sql !== undefined) {
58
+ db.exec(migration.sql);
59
+ }
44
60
  }
45
61
  catch (error) {
46
62
  const msg = error instanceof Error ? error.message : String(error);
@@ -1 +1 @@
1
- {"version":3,"file":"database.js","sourceRoot":"","sources":["../../../tests/helpers/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAGnE,gFAAgF;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAGtD;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB;IAChC,sFAAsF;IACtF,kFAAkF;IAClF,MAAM,EAAE,GAAG,cAAc,EAAE,CAAA;IAE3B,oFAAoF;IACpF,oFAAoF;IACpF,wCAAwC;IACxC,EAAE;IACF,iFAAiF;IACjF,oFAAoF;IACpF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAClE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,kBAAkB,CAAC;gBAAE,MAAM,KAAK,CAAA;QACpD,CAAC;QACD,EAAE,CAAC,OAAO,CAAC,4DAA4D,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACjG,CAAC;IAED,OAAO,EAAE,CAAA;AACX,CAAC"}
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../../tests/helpers/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAG1F,gFAAgF;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAGtD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,6EAA6E;IAC7E,0EAA0E;IAC1E,MAAM,EAAE,GAAG,MAAM,mBAAmB,EAAE,CAAA;IACtC,gBAAgB,CAAC,EAAE,CAAC,CAAA;IAEpB,oFAAoF;IACpF,oFAAoF;IACpF,wCAAwC;IACxC,EAAE;IACF,iFAAiF;IACjF,oFAAoF;IACpF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,uEAAuE;YACvE,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;gBACpB,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACrB,CAAC;iBAAM,IAAI,SAAS,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;gBACvC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;YACxB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAClE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,kBAAkB,CAAC;gBAAE,MAAM,KAAK,CAAA;QACpD,CAAC;QACD,EAAE,CAAC,OAAO,CAAC,4DAA4D,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACjG,CAAC;IAED,OAAO,EAAE,CAAA;AACX,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fan-out.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fan-out.test.d.ts","sourceRoot":"","sources":["../../../tests/install/fan-out.test.ts"],"names":[],"mappings":""}