@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,133 @@
1
+ /**
2
+ * SMI-4577: HNSW search backend for `EmbeddingService.findSimilar`.
3
+ *
4
+ * Lazily loads `hnswlib-node` (declared as `optionalDependencies` on
5
+ * @skillsmith/core), builds or loads an on-disk index at
6
+ * `~/.skillsmith/cache/hnsw-{modelName}.{bin,meta.json,labels.json}`,
7
+ * and exposes incremental `addPoint`/`markDelete` semantics with a debounced
8
+ * atomic-rename persist.
9
+ *
10
+ * Failure modes:
11
+ * - `MODULE_NOT_FOUND` on import → permanently disable; brute-force fallback
12
+ * in `EmbeddingService.findSimilar` covers the case.
13
+ * - `readIndex` failure on a corrupt cache → delete + rebuild on next call
14
+ * (treat as transient).
15
+ * - Concurrent writers → atomic-rename (`writeIndex` to `.tmp`,
16
+ * `fs.renameSync` to final). Loser-of-race acceptable; readers re-read
17
+ * via the atomic pointer.
18
+ *
19
+ * @see ADR-009 (2026-05 amendment): brute-force fallback retained for
20
+ * environments where the optional dep failed to install.
21
+ */
22
+ import type { HierarchicalNSW } from './hnsw-store.types.js';
23
+ /**
24
+ * Persisted metadata describing the on-disk HNSW index. Used to invalidate the
25
+ * cached graph when the embedding count, model, or vector dimension drift.
26
+ */
27
+ export interface HnswMeta {
28
+ /** Schema version. Bump on incompatible meta changes. */
29
+ version: 1;
30
+ /** Model identifier (e.g. `Xenova/all-MiniLM-L6-v2`). */
31
+ modelName: string;
32
+ /** Vector dimensionality. */
33
+ dim: number;
34
+ /** Number of points the cache was built from. */
35
+ count: number;
36
+ /** ISO timestamp the cache was last persisted. */
37
+ builtAt: string;
38
+ }
39
+ /**
40
+ * Wrapper exposing the live HNSW index plus the bookkeeping needed by
41
+ * `EmbeddingService` to rewire incremental upserts/removes.
42
+ */
43
+ export interface HnswHandle {
44
+ /** The live HNSW index. */
45
+ index: HierarchicalNSW;
46
+ /** label → skillId mapping (HNSW returns numeric labels). */
47
+ labelToId: Map<number, string>;
48
+ /** skillId → label mapping (for incremental updates / deletes). */
49
+ idToLabel: Map<string, number>;
50
+ /** Next label to assign for new points. */
51
+ nextLabel: number;
52
+ /** Filesystem paths the index will read/write. Exposed for diagnostics/tests. */
53
+ paths: HnswCachePaths;
54
+ /** Schedule a debounced persist (5s). Safe to call repeatedly. */
55
+ schedulePersist: () => void;
56
+ /** Persist immediately (used at shutdown / for tests). */
57
+ persistNow: () => void;
58
+ }
59
+ export interface HnswCachePaths {
60
+ bin: string;
61
+ meta: string;
62
+ labels: string;
63
+ binTmp: string;
64
+ metaTmp: string;
65
+ labelsTmp: string;
66
+ }
67
+ /**
68
+ * Status reported back to `EmbeddingService` so it can distinguish
69
+ * "the optional dep is missing" (permanent) from "we hit a transient
70
+ * write error" (try again next time).
71
+ */
72
+ export type HnswStatus = {
73
+ kind: 'ok';
74
+ handle: HnswHandle;
75
+ } | {
76
+ kind: 'permanently-unavailable';
77
+ reason: string;
78
+ } | {
79
+ kind: 'temporarily-unavailable';
80
+ reason: string;
81
+ };
82
+ /**
83
+ * Build (or load from cache) an HNSW index for the supplied embedding map.
84
+ *
85
+ * `embeddings` is the canonical source of truth (from `EmbeddingService`'s
86
+ * SQLite cache). On a cold start with a populated cache file matching
87
+ * meta.json, we `readIndex` and skip the rebuild. Otherwise we initialise
88
+ * a fresh index and add every point — same I/O cost as a brute-force seed
89
+ * but the resulting graph survives subsequent `findSimilar` calls.
90
+ */
91
+ export declare function loadOrBuildHnsw(args: {
92
+ embeddings: Map<string, Float32Array>;
93
+ modelName: string;
94
+ dim: number;
95
+ /** Capacity hint. Defaults to ~2x current size, clamped to 1024 minimum. */
96
+ maxElements?: number;
97
+ /** Override hyperparams. Defaults match `DEFAULT_HNSW_CONFIG` in hnsw-store.types.ts. */
98
+ m?: number;
99
+ efConstruction?: number;
100
+ efSearch?: number;
101
+ }): Promise<HnswStatus>;
102
+ /**
103
+ * Top-K nearest-neighbour search via the supplied handle.
104
+ *
105
+ * @param handle - HNSW handle returned by `loadOrBuildHnsw`
106
+ * @param query - Query vector (must match `handle.dim`)
107
+ * @param topK - Maximum neighbours to return
108
+ * @returns Result rows in HNSW score order; `score` is `1 - cosineDistance`.
109
+ */
110
+ export declare function findSimilarHnsw(handle: HnswHandle, query: Float32Array, topK: number): Array<{
111
+ skillId: string;
112
+ score: number;
113
+ }>;
114
+ /**
115
+ * Add or replace a point. Used by `EmbeddingService.storeEmbedding` to keep
116
+ * the in-memory graph aligned with the SQLite cache. Marks the handle dirty;
117
+ * persist happens via the debounced 5s timer (or `persistNow`).
118
+ */
119
+ export declare function upsertPoint(handle: HnswHandle, skillId: string, vector: Float32Array): void;
120
+ /**
121
+ * Mark a point deleted. The point stays in the graph for traversal correctness
122
+ * but `findSimilarHnsw` filters it out via the labelToId lookup.
123
+ */
124
+ export declare function removePoint(handle: HnswHandle, skillId: string): boolean;
125
+ /**
126
+ * Test-only helper — clears the cached `hnswlib-node` constructor reference so
127
+ * tests can simulate "module reinstalled" scenarios. Not part of the public
128
+ * API; do not use in production code.
129
+ *
130
+ * @internal
131
+ */
132
+ export declare function __resetCachedHnswCtorForTests(): void;
133
+ //# sourceMappingURL=hnsw-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hnsw-search.d.ts","sourceRoot":"","sources":["../../../src/embeddings/hnsw-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAA8B,MAAM,uBAAuB,CAAA;AAExF;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,yDAAyD;IACzD,OAAO,EAAE,CAAC,CAAA;IACV,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,2BAA2B;IAC3B,KAAK,EAAE,eAAe,CAAA;IACtB,6DAA6D;IAC7D,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,mEAAmE;IACnE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,KAAK,EAAE,cAAc,CAAA;IACrB,kEAAkE;IAClE,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,0DAA0D;IAC1D,UAAU,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,yBAAyB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,yBAAyB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AA8FvD;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yFAAyF;IACzF,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GAAG,OAAO,CAAC,UAAU,CAAC,CA6GtB;AAgGD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,GACX,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAc3C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAgB3F;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQxE;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
@@ -0,0 +1,387 @@
1
+ /**
2
+ * SMI-4577: HNSW search backend for `EmbeddingService.findSimilar`.
3
+ *
4
+ * Lazily loads `hnswlib-node` (declared as `optionalDependencies` on
5
+ * @skillsmith/core), builds or loads an on-disk index at
6
+ * `~/.skillsmith/cache/hnsw-{modelName}.{bin,meta.json,labels.json}`,
7
+ * and exposes incremental `addPoint`/`markDelete` semantics with a debounced
8
+ * atomic-rename persist.
9
+ *
10
+ * Failure modes:
11
+ * - `MODULE_NOT_FOUND` on import → permanently disable; brute-force fallback
12
+ * in `EmbeddingService.findSimilar` covers the case.
13
+ * - `readIndex` failure on a corrupt cache → delete + rebuild on next call
14
+ * (treat as transient).
15
+ * - Concurrent writers → atomic-rename (`writeIndex` to `.tmp`,
16
+ * `fs.renameSync` to final). Loser-of-race acceptable; readers re-read
17
+ * via the atomic pointer.
18
+ *
19
+ * @see ADR-009 (2026-05 amendment): brute-force fallback retained for
20
+ * environments where the optional dep failed to install.
21
+ */
22
+ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'fs';
23
+ import { dirname, join } from 'path';
24
+ import { getCacheDir } from '../config/index.js';
25
+ /**
26
+ * One-shot module loader. Cached across calls; on `MODULE_NOT_FOUND` the
27
+ * caller receives a sentinel and is expected to permanently disable HNSW.
28
+ */
29
+ let cachedCtor = null;
30
+ /**
31
+ * Dynamically load `hnswlib-node`. Returns the constructor or `null` when the
32
+ * optional dependency is not installed (Vercel build, restricted hosts).
33
+ *
34
+ * Uses a literal dynamic `import()` (not the `Function('return import(...)')()`
35
+ * pattern that lives in `hnsw-store.helpers.ts`); vitest's vm-mode ESM rejects
36
+ * the latter with "A dynamic import callback was not specified." A native
37
+ * dynamic import is safe here because `hnswlib-node` is a CJS module that's
38
+ * not type-imported anywhere — only TS will error on resolution failure, but
39
+ * we catch that in the `catch` block below.
40
+ */
41
+ async function loadHnswCtor() {
42
+ if (cachedCtor === 'unavailable')
43
+ return null;
44
+ if (cachedCtor !== null)
45
+ return cachedCtor;
46
+ try {
47
+ // hnswlib-node is CJS; ESM dynamic import lifts its `module.exports` onto
48
+ // `.default`. Some bundlers / older Node versions also surface named
49
+ // exports at the top level — check both shapes so we work in every
50
+ // environment.
51
+ //
52
+ // Note: a previous codebase pattern used `Function('return import(...)')()`
53
+ // here. Vitest's vm-mode ESM rejects that with "A dynamic import callback
54
+ // was not specified" so we use a literal dynamic `import()`. The string
55
+ // literal is intentional to keep TypeScript from re-routing the specifier
56
+ // (`hnswlib-node` is in `optionalDependencies`, not a hard dep).
57
+ const mod = (await import('hnswlib-node'));
58
+ const ctor = mod.HierarchicalNSW ?? mod.default?.HierarchicalNSW;
59
+ if (!ctor) {
60
+ cachedCtor = 'unavailable';
61
+ return null;
62
+ }
63
+ cachedCtor = ctor;
64
+ return cachedCtor;
65
+ }
66
+ catch {
67
+ cachedCtor = 'unavailable';
68
+ return null;
69
+ }
70
+ }
71
+ function cachePaths(modelName) {
72
+ // Sanitize model name (slashes become double-underscore so we don't
73
+ // accidentally create nested directories under cache/).
74
+ const safeName = modelName.replace(/[/\\]/g, '__');
75
+ const dir = getCacheDir();
76
+ const base = join(dir, `hnsw-${safeName}`);
77
+ return {
78
+ bin: `${base}.bin`,
79
+ meta: `${base}.meta.json`,
80
+ labels: `${base}.labels.json`,
81
+ binTmp: `${base}.bin.tmp`,
82
+ metaTmp: `${base}.meta.json.tmp`,
83
+ labelsTmp: `${base}.labels.json.tmp`,
84
+ };
85
+ }
86
+ function readMeta(metaPath) {
87
+ if (!existsSync(metaPath))
88
+ return null;
89
+ try {
90
+ const parsed = JSON.parse(readFileSync(metaPath, 'utf-8'));
91
+ if (parsed.version !== 1)
92
+ return null;
93
+ return parsed;
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ function readLabels(labelsPath) {
100
+ if (!existsSync(labelsPath))
101
+ return null;
102
+ try {
103
+ const parsed = JSON.parse(readFileSync(labelsPath, 'utf-8'));
104
+ if (!Array.isArray(parsed))
105
+ return null;
106
+ return parsed;
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ function writeAtomic(tmp, final, contents) {
113
+ mkdirSync(dirname(tmp), { recursive: true });
114
+ writeFileSync(tmp, contents, typeof contents === 'string' ? { encoding: 'utf-8' } : undefined);
115
+ renameSync(tmp, final);
116
+ }
117
+ /**
118
+ * Build (or load from cache) an HNSW index for the supplied embedding map.
119
+ *
120
+ * `embeddings` is the canonical source of truth (from `EmbeddingService`'s
121
+ * SQLite cache). On a cold start with a populated cache file matching
122
+ * meta.json, we `readIndex` and skip the rebuild. Otherwise we initialise
123
+ * a fresh index and add every point — same I/O cost as a brute-force seed
124
+ * but the resulting graph survives subsequent `findSimilar` calls.
125
+ */
126
+ export async function loadOrBuildHnsw(args) {
127
+ const Ctor = await loadHnswCtor();
128
+ if (!Ctor) {
129
+ return {
130
+ kind: 'permanently-unavailable',
131
+ reason: 'hnswlib-node not installed (optionalDependencies)',
132
+ };
133
+ }
134
+ const paths = cachePaths(args.modelName);
135
+ const meta = readMeta(paths.meta);
136
+ const labels = readLabels(paths.labels);
137
+ const count = args.embeddings.size;
138
+ // R2 mitigation: defaults tuned to clear the recall@10 ≥ 0.95 gate.
139
+ // m=32/efConstruction=400/efSearch=200 is the `large` preset from
140
+ // hnsw-store.types.ts and matches what the original SMI-1519 design
141
+ // documented for 100k-scale skill registries. At 14k synthetic vectors
142
+ // we still see >100x speedup vs brute-force p99.
143
+ const m = args.m ?? 32;
144
+ const efConstruction = args.efConstruction ?? 400;
145
+ const efSearch = args.efSearch ?? 200;
146
+ const capacity = Math.max(args.maxElements ?? Math.max(count * 2, 1024), 1024);
147
+ const reusable = meta !== null &&
148
+ labels !== null &&
149
+ meta.modelName === args.modelName &&
150
+ meta.dim === args.dim &&
151
+ meta.count === count &&
152
+ existsSync(paths.bin);
153
+ let index;
154
+ let labelToId;
155
+ let idToLabel;
156
+ let nextLabel;
157
+ if (reusable) {
158
+ try {
159
+ index = new Ctor('cosine', args.dim);
160
+ // Use sync read; the async `readIndex` returns a Promise we'd have to
161
+ // await, defeating the synchronous boot path. Sync is fine here — the
162
+ // file is < a few MB at expected scale.
163
+ index.readIndexSync(paths.bin, true);
164
+ index.setEf(efSearch);
165
+ labelToId = new Map(labels);
166
+ idToLabel = new Map(labels.map(([label, id]) => [id, label]));
167
+ nextLabel = labels.reduce((max, [label]) => Math.max(max, label), -1) + 1;
168
+ }
169
+ catch (err) {
170
+ // Corrupt cache — wipe and recurse for a fresh build. The retry will
171
+ // hit `reusable === false` because we just removed the artefacts.
172
+ try {
173
+ if (existsSync(paths.bin))
174
+ unlinkSync(paths.bin);
175
+ if (existsSync(paths.meta))
176
+ unlinkSync(paths.meta);
177
+ if (existsSync(paths.labels))
178
+ unlinkSync(paths.labels);
179
+ }
180
+ catch {
181
+ /* best-effort cleanup */
182
+ }
183
+ try {
184
+ return await loadOrBuildHnsw(args);
185
+ }
186
+ catch (retryErr) {
187
+ return {
188
+ kind: 'temporarily-unavailable',
189
+ reason: `cache rebuild failed after corrupt-load: ${String(retryErr)} (initial: ${String(err)})`,
190
+ };
191
+ }
192
+ }
193
+ }
194
+ else {
195
+ index = new Ctor('cosine', args.dim);
196
+ index.initIndex(capacity, m, efConstruction);
197
+ index.setEf(efSearch);
198
+ labelToId = new Map();
199
+ idToLabel = new Map();
200
+ let nextLabelLocal = 0;
201
+ for (const [skillId, vec] of args.embeddings) {
202
+ // hnswlib-node@3 expects a plain Array<number> for addPoint, not a
203
+ // typed array — passing Float32Array surfaces as
204
+ // "Invalid the first argument type, must be an Array."
205
+ index.addPoint(Array.from(vec), nextLabelLocal);
206
+ labelToId.set(nextLabelLocal, skillId);
207
+ idToLabel.set(skillId, nextLabelLocal);
208
+ nextLabelLocal++;
209
+ }
210
+ nextLabel = nextLabelLocal;
211
+ }
212
+ const handle = createHandle({
213
+ index,
214
+ labelToId,
215
+ idToLabel,
216
+ nextLabel,
217
+ dim: args.dim,
218
+ modelName: args.modelName,
219
+ paths,
220
+ });
221
+ // Immediate persist after a fresh build. Reusable path skips this — the
222
+ // on-disk artefacts already match.
223
+ if (!reusable) {
224
+ try {
225
+ handle.persistNow();
226
+ }
227
+ catch (err) {
228
+ return {
229
+ kind: 'temporarily-unavailable',
230
+ reason: `persist after build failed: ${String(err)}`,
231
+ };
232
+ }
233
+ }
234
+ return { kind: 'ok', handle };
235
+ }
236
+ /**
237
+ * Wrap the raw HNSW index with the bookkeeping needed for incremental upserts
238
+ * + debounced persist. Caller owns the handle's lifecycle (no auto-cleanup).
239
+ */
240
+ function createHandle(args) {
241
+ let dirty = false;
242
+ let timer = null;
243
+ // Mutable closure state — we update via the handle methods below, but the
244
+ // returned object exposes the current values via getters.
245
+ const state = {
246
+ nextLabel: args.nextLabel,
247
+ };
248
+ const persistNow = () => {
249
+ if (timer) {
250
+ clearTimeout(timer);
251
+ timer = null;
252
+ }
253
+ if (!dirty && existsSync(args.paths.bin) && existsSync(args.paths.meta)) {
254
+ // Nothing to write and the cache is already consistent on disk.
255
+ return;
256
+ }
257
+ // `writeIndex` is async (returns a Promise) — we want sync semantics so
258
+ // the persist runs to completion inside the debounce callback / on close.
259
+ args.index.writeIndexSync(args.paths.binTmp);
260
+ renameSync(args.paths.binTmp, args.paths.bin);
261
+ const labelsArr = Array.from(args.labelToId.entries());
262
+ writeAtomic(args.paths.labelsTmp, args.paths.labels, JSON.stringify(labelsArr));
263
+ const meta = {
264
+ version: 1,
265
+ modelName: args.modelName,
266
+ dim: args.dim,
267
+ count: args.idToLabel.size,
268
+ builtAt: new Date().toISOString(),
269
+ };
270
+ writeAtomic(args.paths.metaTmp, args.paths.meta, JSON.stringify(meta, null, 2));
271
+ dirty = false;
272
+ };
273
+ const schedulePersist = () => {
274
+ dirty = true;
275
+ if (timer)
276
+ clearTimeout(timer);
277
+ timer = setTimeout(() => {
278
+ timer = null;
279
+ try {
280
+ persistNow();
281
+ }
282
+ catch (err) {
283
+ // Persist failures are non-fatal — the in-memory index stays valid;
284
+ // a future rebuild will recover from the cached embeddings map.
285
+ // Log the cache path so a user can spot a read-only cache dir.
286
+ console.warn(`[hnsw-search] debounced persist failed (path=${args.paths.bin}):`, err instanceof Error ? err.message : err);
287
+ }
288
+ }, 5000);
289
+ // Don't keep the event loop alive just for this timer.
290
+ if (timer && typeof timer.unref === 'function')
291
+ timer.unref();
292
+ };
293
+ return {
294
+ get index() {
295
+ return args.index;
296
+ },
297
+ get labelToId() {
298
+ return args.labelToId;
299
+ },
300
+ get idToLabel() {
301
+ return args.idToLabel;
302
+ },
303
+ get nextLabel() {
304
+ return state.nextLabel;
305
+ },
306
+ set nextLabel(v) {
307
+ state.nextLabel = v;
308
+ },
309
+ get paths() {
310
+ return args.paths;
311
+ },
312
+ schedulePersist,
313
+ persistNow,
314
+ };
315
+ }
316
+ /**
317
+ * Top-K nearest-neighbour search via the supplied handle.
318
+ *
319
+ * @param handle - HNSW handle returned by `loadOrBuildHnsw`
320
+ * @param query - Query vector (must match `handle.dim`)
321
+ * @param topK - Maximum neighbours to return
322
+ * @returns Result rows in HNSW score order; `score` is `1 - cosineDistance`.
323
+ */
324
+ export function findSimilarHnsw(handle, query, topK) {
325
+ const liveCount = handle.idToLabel.size;
326
+ if (liveCount === 0)
327
+ return [];
328
+ const k = Math.min(topK, liveCount);
329
+ // searchKnn also requires plain Array — Float32Array triggers
330
+ // "Invalid the first argument type, must be an Array."
331
+ const result = handle.index.searchKnn(Array.from(query), k);
332
+ const out = [];
333
+ for (let i = 0; i < result.neighbors.length; i++) {
334
+ const skillId = handle.labelToId.get(result.neighbors[i]);
335
+ if (!skillId)
336
+ continue; // marked-deleted points may still surface; skip
337
+ out.push({ skillId, score: 1 - result.distances[i] });
338
+ }
339
+ return out;
340
+ }
341
+ /**
342
+ * Add or replace a point. Used by `EmbeddingService.storeEmbedding` to keep
343
+ * the in-memory graph aligned with the SQLite cache. Marks the handle dirty;
344
+ * persist happens via the debounced 5s timer (or `persistNow`).
345
+ */
346
+ export function upsertPoint(handle, skillId, vector) {
347
+ const existing = handle.idToLabel.get(skillId);
348
+ if (existing !== undefined) {
349
+ // hnswlib supports `addPoint(..., replaceDeleted=true)` for true
350
+ // replacement; for non-deleted points we mark + reinsert under a new
351
+ // label to preserve correctness across efConstruction tuning.
352
+ handle.index.markDelete(existing);
353
+ handle.labelToId.delete(existing);
354
+ }
355
+ const label = handle.nextLabel;
356
+ // Plain Array required (see addPoint comment above).
357
+ handle.index.addPoint(Array.from(vector), label);
358
+ handle.idToLabel.set(skillId, label);
359
+ handle.labelToId.set(label, skillId);
360
+ handle.nextLabel = label + 1;
361
+ handle.schedulePersist();
362
+ }
363
+ /**
364
+ * Mark a point deleted. The point stays in the graph for traversal correctness
365
+ * but `findSimilarHnsw` filters it out via the labelToId lookup.
366
+ */
367
+ export function removePoint(handle, skillId) {
368
+ const label = handle.idToLabel.get(skillId);
369
+ if (label === undefined)
370
+ return false;
371
+ handle.index.markDelete(label);
372
+ handle.idToLabel.delete(skillId);
373
+ handle.labelToId.delete(label);
374
+ handle.schedulePersist();
375
+ return true;
376
+ }
377
+ /**
378
+ * Test-only helper — clears the cached `hnswlib-node` constructor reference so
379
+ * tests can simulate "module reinstalled" scenarios. Not part of the public
380
+ * API; do not use in production code.
381
+ *
382
+ * @internal
383
+ */
384
+ export function __resetCachedHnswCtorForTests() {
385
+ cachedCtor = null;
386
+ }
387
+ //# sourceMappingURL=hnsw-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hnsw-search.js","sourceRoot":"","sources":["../../../src/embeddings/hnsw-search.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,16 @@
1
1
  /**
2
2
  * SMI-1519: HNSW Embedding Store
3
+ * SMI-4577: Production HNSW path now lives in `embeddings/hnsw-search.ts` and is
4
+ * wired into `EmbeddingService.findSimilar`. This class retains its
5
+ * historical API for callers that want a self-contained store without
6
+ * the full `EmbeddingService` surface, but no longer references the
7
+ * V3 VectorDB (decommissioned after the claude-flow → ruflo rename).
3
8
  *
4
- * High-performance vector storage using HNSW index for fast ANN search.
5
- * Uses brute-force search (V3 VectorDB unavailable after claude-flow rename).
9
+ * High-performance vector storage with SQLite-backed metadata. `findSimilar`
10
+ * uses brute-force search; consumers wanting the HNSW backend should use
11
+ * `EmbeddingService` instead, which lazy-loads `hnswlib-node` from the
12
+ * shared search module.
6
13
  *
7
- * Enable via: SKILLSMITH_USE_HNSW=true
8
14
  * @see ADR-009: Embedding Service Fallback Strategy
9
15
  */
10
16
  import type { SimilarityResult } from './index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"hnsw-store.d.ts","sourceRoot":"","sources":["../../../src/embeddings/hnsw-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAMlD,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,0BAA0B,EAC1B,UAAU,EACV,yBAAyB,EACzB,cAAc,EACd,iBAAiB,EACjB,eAAe,GAChB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAGzE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAGvF,OAAO,KAAK,EACV,UAAU,EACV,yBAAyB,EACzB,cAAc,EACd,iBAAiB,EACjB,eAAe,EAEhB,MAAM,uBAAuB,CAAA;AAI9B;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACxD,OAAO,CAAC,EAAE,CAA4B;IACtC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,WAAW,CAA6B;IAEhD;;;;OAIG;gBACS,OAAO,GAAE,yBAA8B;IAqBnD;;;;;OAKG;WACU,MAAM,CAAC,OAAO,GAAE,yBAA8B,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAUnF,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAsB5E,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAalD,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC;IAiB7C,WAAW,CAAC,cAAc,EAAE,YAAY,EAAE,IAAI,GAAE,MAAW,GAAG,gBAAgB,EAAE;IA0B1E,gBAAgB,CACpB,cAAc,EAAE,YAAY,EAC5B,IAAI,GAAE,MAAW,GAChB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAgB9B,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,MAAM;IAgB1D,eAAe,IAAI,OAAO;IAI1B,KAAK,IAAI,IAAI;IAOb,QAAQ,IAAI,cAAc;IA4B1B,WAAW,CACT,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,YAAY,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,GAC5E,iBAAiB;IAqDpB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAmBzC,SAAS,IAAI,IAAI;IAKjB,SAAS,IAAI,IAAI;IAKX,YAAY,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBlE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOnC,OAAO,CAAC,YAAY;YAKN,iBAAiB;IAK/B,OAAO,CAAC,mBAAmB;YAYb,aAAa;IAM3B,OAAO,CAAC,oBAAoB;CAI7B"}
1
+ {"version":3,"file":"hnsw-store.d.ts","sourceRoot":"","sources":["../../../src/embeddings/hnsw-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAMlD,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,0BAA0B,EAC1B,UAAU,EACV,yBAAyB,EACzB,cAAc,EACd,iBAAiB,EACjB,eAAe,GAChB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAGzE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAGvF,OAAO,KAAK,EACV,UAAU,EACV,yBAAyB,EACzB,cAAc,EACd,iBAAiB,EACjB,eAAe,EAEhB,MAAM,uBAAuB,CAAA;AAI9B;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACxD,OAAO,CAAC,EAAE,CAA4B;IACtC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,WAAW,CAA6B;IAEhD;;;;OAIG;gBACS,OAAO,GAAE,yBAA8B;IAqBnD;;;;;OAKG;WACU,MAAM,CAAC,OAAO,GAAE,yBAA8B,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAUnF,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAsB5E,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAalD,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC;IAiB7C,WAAW,CAAC,cAAc,EAAE,YAAY,EAAE,IAAI,GAAE,MAAW,GAAG,gBAAgB,EAAE;IA0B1E,gBAAgB,CACpB,cAAc,EAAE,YAAY,EAC5B,IAAI,GAAE,MAAW,GAChB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAgB9B,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,MAAM;IAgB1D,eAAe,IAAI,OAAO;IAI1B,KAAK,IAAI,IAAI;IAOb,QAAQ,IAAI,cAAc;IA4B1B,WAAW,CACT,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,YAAY,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,GAC5E,iBAAiB;IAqDpB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAmBzC,SAAS,IAAI,IAAI;IAQjB,SAAS,IAAI,IAAI;IAOX,YAAY,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBlE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOnC,OAAO,CAAC,YAAY;YAKN,iBAAiB;IAK/B,OAAO,CAAC,mBAAmB;YAYb,aAAa;IAS3B,OAAO,CAAC,oBAAoB;CAI7B"}
@@ -1,10 +1,16 @@
1
1
  /**
2
2
  * SMI-1519: HNSW Embedding Store
3
+ * SMI-4577: Production HNSW path now lives in `embeddings/hnsw-search.ts` and is
4
+ * wired into `EmbeddingService.findSimilar`. This class retains its
5
+ * historical API for callers that want a self-contained store without
6
+ * the full `EmbeddingService` surface, but no longer references the
7
+ * V3 VectorDB (decommissioned after the claude-flow → ruflo rename).
3
8
  *
4
- * High-performance vector storage using HNSW index for fast ANN search.
5
- * Uses brute-force search (V3 VectorDB unavailable after claude-flow rename).
9
+ * High-performance vector storage with SQLite-backed metadata. `findSimilar`
10
+ * uses brute-force search; consumers wanting the HNSW backend should use
11
+ * `EmbeddingService` instead, which lazy-loads `hnswlib-node` from the
12
+ * shared search module.
6
13
  *
7
- * Enable via: SKILLSMITH_USE_HNSW=true
8
14
  * @see ADR-009: Embedding Service Fallback Strategy
9
15
  */
10
16
  import { createDatabaseSync, createDatabaseAsync } from '../db/createDatabase.js';
@@ -277,12 +283,13 @@ export class HNSWEmbeddingStore {
277
283
  saveIndex() {
278
284
  if (!this.indexPath)
279
285
  throw new Error('Cannot save index: indexPath not configured');
280
- console.log('[HNSWEmbeddingStore] Index persistence managed by V3 VectorDB backend');
286
+ // SMI-4577: persistence now handled by EmbeddingService HNSW backend; this store is a no-op shim.
287
+ console.log('[HNSWEmbeddingStore] saveIndex() is a no-op; use EmbeddingService for HNSW persistence');
281
288
  }
282
289
  loadIndex() {
283
290
  if (!this.indexPath)
284
291
  throw new Error('Cannot load index: indexPath not configured');
285
- console.log('[HNSWEmbeddingStore] Index persistence managed by V3 VectorDB backend');
292
+ console.log('[HNSWEmbeddingStore] loadIndex() is a no-op; use EmbeddingService for HNSW persistence');
286
293
  }
287
294
  async rebuildIndex(newConfig) {
288
295
  if (newConfig)
@@ -339,8 +346,11 @@ export class HNSWEmbeddingStore {
339
346
  `);
340
347
  }
341
348
  async initHNSWIndex() {
342
- // V3 VectorDB unavailable after claude-flow → ruflo rename (SMI-3600)
343
- // @claude-flow/cli restricts subpath imports; always use brute-force search
349
+ // SMI-4577: V3 VectorDB was decommissioned with the claude-flow → ruflo rename
350
+ // (SMI-3600). The replacement HNSW backend lives in `embeddings/hnsw-search.ts`
351
+ // and is exposed through `EmbeddingService.findSimilar`. This store retains
352
+ // its public API but always uses the brute-force code path; callers wanting
353
+ // HNSW should switch to `EmbeddingService` directly.
344
354
  this.vectorDB = null;
345
355
  }
346
356
  distanceToSimilarity(distance) {