thumbgate 1.34.3 → 1.37.1

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 (200) hide show
  1. package/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
  2. package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
  3. package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
  4. package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
  5. package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
  6. package/.claude-plugin/plugin.json +1 -1
  7. package/.well-known/llms.txt +1 -0
  8. package/.well-known/mcp/server-card.json +1 -1
  9. package/CONTRIBUTING.md +95 -0
  10. package/README.md +195 -632
  11. package/THIRD_PARTY_NOTICES.md +89 -0
  12. package/adapters/claude/.mcp.json +2 -2
  13. package/adapters/forge/forge.yaml +3 -3
  14. package/adapters/future-agi/.mcp.json +8 -0
  15. package/adapters/future-agi/FUTURE_AGI.md +23 -0
  16. package/adapters/future-agi/config.toml +3 -0
  17. package/adapters/future-agi/future-agi-bridge.js +9 -0
  18. package/adapters/future-agi/opencode.json +8 -0
  19. package/adapters/herdr/herdr-plugin.toml +18 -0
  20. package/adapters/mcp/server-stdio.js +238 -25
  21. package/adapters/opencode/opencode.json +1 -1
  22. package/adapters/workos/WORKOS.md +52 -0
  23. package/bin/cli.js +373 -5
  24. package/bin/futureagi-bridge +9 -0
  25. package/config/gate-templates.json +653 -4
  26. package/config/gates/actor-critic-audit.json +34 -0
  27. package/config/gates/default.json +21 -2
  28. package/config/gates/five-walls-governance.json +34 -0
  29. package/config/gates/future-agi-guardrails.json +34 -0
  30. package/config/gates/radware-threat-defense-2026.json +61 -0
  31. package/config/gates/simatree-data-governance.json +33 -0
  32. package/config/mcp-allowlists.json +4 -0
  33. package/config/merge-quality-checks.json +10 -1
  34. package/config/model-candidates.json +382 -24
  35. package/config/model-tiers.json +18 -0
  36. package/config/post-deploy-marketing-pages.json +10 -0
  37. package/config/progressive/01-wire-only.json +11 -0
  38. package/config/progressive/02-dashboard-empty-ok.json +10 -0
  39. package/config/progressive/03-one-lesson.json +10 -0
  40. package/config/progressive/04-warn-fires.json +11 -0
  41. package/config/progressive/05-strict-optional.json +11 -0
  42. package/config/progressive/README.md +15 -0
  43. package/config/schemas/broker-execution-receipt.schema.json +139 -0
  44. package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
  45. package/conformance/provider-attestation/vectors.json +320 -0
  46. package/docs/specs/provider-execution-attestation-v1.md +69 -0
  47. package/openapi/openapi.yaml +15 -0
  48. package/package.json +401 -147
  49. package/public/about.html +2 -2
  50. package/public/ai-malpractice-prevention.html +7 -7
  51. package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
  52. package/public/blog/a-receipt-is-not-world-state.html +388 -0
  53. package/public/blog/git-at-agent-scale.html +374 -0
  54. package/public/blog/no-llm-in-the-gate.html +133 -0
  55. package/public/blog.html +80 -0
  56. package/public/case-studies.html +16 -1
  57. package/public/compare.html +28 -0
  58. package/public/diagnostic.html +216 -7
  59. package/public/docs/connectors.html +39 -0
  60. package/public/federal.html +2 -2
  61. package/public/founders.html +639 -0
  62. package/public/index.html +87 -9
  63. package/public/install.html +8 -8
  64. package/public/learn.html +39 -0
  65. package/public/numbers.html +2 -2
  66. package/public/peter.html +310 -0
  67. package/public/platform-partners.html +119 -0
  68. package/public/pricing.html +24 -3
  69. package/public/privacy.html +117 -0
  70. package/public/pro.html +17 -0
  71. package/public/support.html +62 -0
  72. package/public/terms.html +130 -0
  73. package/public/third-party-notices.html +95 -0
  74. package/public/yt.html +351 -0
  75. package/scripts/action-receipts.js +133 -3
  76. package/scripts/adaptive-governance-arena.js +349 -0
  77. package/scripts/admin-override.js +205 -0
  78. package/scripts/agent-action-inventory.js +869 -0
  79. package/scripts/agent-audit-trace.js +42 -2
  80. package/scripts/agent-egress-policy.js +1117 -0
  81. package/scripts/agent-memory-lifecycle.js +141 -2
  82. package/scripts/agent-operations-planner.js +441 -1
  83. package/scripts/agent-readiness.js +68 -0
  84. package/scripts/agent-security-central.js +647 -0
  85. package/scripts/allowlist-bridge-honesty.js +417 -0
  86. package/scripts/async-job-runner.js +102 -11
  87. package/scripts/audit-trail.js +212 -0
  88. package/scripts/auto-promote-gates.js +178 -27
  89. package/scripts/billing.js +1 -1
  90. package/scripts/broker-execution-receipts.js +719 -0
  91. package/scripts/budget-aware-gates-proof.js +423 -0
  92. package/scripts/claude-feedback-sync.js +29 -3
  93. package/scripts/claw-harness-production.js +237 -0
  94. package/scripts/cli-progress.js +111 -0
  95. package/scripts/cli-schema.js +163 -1
  96. package/scripts/codex-runbook-flywheel.js +318 -0
  97. package/scripts/context-footprint.js +186 -0
  98. package/scripts/contextfs.js +143 -61
  99. package/scripts/dashboard-limits.js +27 -0
  100. package/scripts/dashboard.js +279 -9
  101. package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
  102. package/scripts/docker-sandbox-planner.js +18 -0
  103. package/scripts/double-blind-eval-protocol.js +252 -0
  104. package/scripts/edotenv-rl-gateway.js +259 -0
  105. package/scripts/ensure-production-search-corpus.js +162 -0
  106. package/scripts/eval-holdout.js +311 -0
  107. package/scripts/feedback-aggregate.js +21 -2
  108. package/scripts/feedback-loop.js +87 -5
  109. package/scripts/feedback-quality.js +9 -0
  110. package/scripts/file-ledger-lock.js +4 -1
  111. package/scripts/financial-control-plane.js +41 -1
  112. package/scripts/find-dormant-requires.js +118 -0
  113. package/scripts/fs-utils.js +84 -8
  114. package/scripts/gate-stats.js +2 -2
  115. package/scripts/gates-engine.js +859 -58
  116. package/scripts/generate-case-study-outreach.js +24 -15
  117. package/scripts/git-at-scale.js +628 -0
  118. package/scripts/governance-conflict-audit.js +1650 -0
  119. package/scripts/governance-difficulty-curriculum.js +328 -0
  120. package/scripts/graphrag-retrieval.js +275 -0
  121. package/scripts/gurobi-optimizer.js +324 -0
  122. package/scripts/gurobi_optimizer.py +485 -0
  123. package/scripts/harness-selector.js +82 -1
  124. package/scripts/hidden-entry-points.js +284 -0
  125. package/scripts/human-escalation.js +199 -1
  126. package/scripts/hybrid-feedback-context.js +152 -19
  127. package/scripts/intent-governed-execution.js +602 -0
  128. package/scripts/intervention-policy.js +123 -20
  129. package/scripts/jit-harness-compose.js +628 -0
  130. package/scripts/jsonl-watcher.js +10 -0
  131. package/scripts/lesson-embedding-index.js +95 -12
  132. package/scripts/lesson-retrieval.js +105 -19
  133. package/scripts/local-model-profile.js +19 -2
  134. package/scripts/mailer/resend-mailer.js +1 -1
  135. package/scripts/matryoshka-embedding.js +235 -0
  136. package/scripts/mcp-oauth.js +42 -4
  137. package/scripts/mcp-session-handles.js +1016 -0
  138. package/scripts/mcp-wiring-doctor.js +314 -0
  139. package/scripts/memory-firewall.js +115 -2
  140. package/scripts/memory-scope-readiness.js +299 -0
  141. package/scripts/memory-vs-rag-route.js +161 -0
  142. package/scripts/model-tier-router.js +148 -21
  143. package/scripts/nvidia-specdecode-al-doctor.js +536 -0
  144. package/scripts/openui-catalog-compose-honesty.js +593 -0
  145. package/scripts/operational-integrity.js +19 -1
  146. package/scripts/override-audit.js +213 -0
  147. package/scripts/package-manager-honesty-doctor.js +458 -0
  148. package/scripts/pr-manager.js +63 -1
  149. package/scripts/prove-herdr-adapter.js +52 -0
  150. package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
  151. package/scripts/prove-workos.js +73 -0
  152. package/scripts/provider-attestation-conformance.js +192 -0
  153. package/scripts/provider-receipt-contract.js +136 -0
  154. package/scripts/qwen38-max-cost-optimizer.js +401 -0
  155. package/scripts/radware-threat-defense.js +280 -0
  156. package/scripts/rag-embedding-identity.js +221 -0
  157. package/scripts/rag-precision-guardrails.js +112 -2
  158. package/scripts/remote-feedback-capture.js +159 -0
  159. package/scripts/research-agent-harness.js +256 -0
  160. package/scripts/rsi-safety-hillclimb.js +200 -0
  161. package/scripts/rule-sprawl.js +188 -0
  162. package/scripts/schedule-manager.js +147 -0
  163. package/scripts/self-heal.js +8 -0
  164. package/scripts/session-lease.js +415 -0
  165. package/scripts/simatree-data-governance.js +347 -0
  166. package/scripts/slo-alert-engine.js +172 -7
  167. package/scripts/solver-parity.js +539 -0
  168. package/scripts/stealth-memory-injection-gate.js +333 -0
  169. package/scripts/switchyard-router.js +366 -0
  170. package/scripts/telemetry-analytics.js +84 -27
  171. package/scripts/temporal-decay-weighting.js +138 -0
  172. package/scripts/test-all.js +165 -0
  173. package/scripts/token-savings.js +42 -0
  174. package/scripts/tool-kpi-tracker.js +108 -5
  175. package/scripts/tool-registry.js +193 -5
  176. package/scripts/universal-claim-evaluator.js +14 -2
  177. package/scripts/vector-store.js +279 -9
  178. package/scripts/workflow-notebook.js +391 -0
  179. package/scripts/workflow-sentinel.js +111 -12
  180. package/scripts/workos-production-guard.js +260 -0
  181. package/scripts/workspace-search-route.js +515 -0
  182. package/server.json +2 -2
  183. package/src/agent-identity-boundary.js +76 -0
  184. package/src/agent-retrieval-cache.js +155 -0
  185. package/src/alert-noise-ledger.js +502 -0
  186. package/src/api/server.js +802 -185
  187. package/src/git-fast-cache.js +220 -0
  188. package/src/git-wal-sync.js +156 -0
  189. package/src/hash-anchored-edit.js +82 -0
  190. package/src/hermes-platform-protocol.js +475 -0
  191. package/src/hermes-sync-plane.js +241 -0
  192. package/src/index.js +30 -1
  193. package/src/iso42001-compliance-guard.js +97 -0
  194. package/src/latency-budget.js +244 -0
  195. package/src/mcp-writeguard.js +316 -0
  196. package/src/miminions-adapter.js +106 -0
  197. package/src/pipeline-compass.js +104 -0
  198. package/src/ppl-alert-pipeline.js +284 -0
  199. package/src/rendezvous-router.js +90 -0
  200. package/src/security-questionnaire.js +195 -0
@@ -31,6 +31,60 @@ let _geminiEmbedderForTests = null;
31
31
  const TABLE_NAME = 'thumbgate_memories';
32
32
  const FEATURE_HASH_DIMENSIONS = 384;
33
33
 
34
+
35
+ /**
36
+ * Matryoshka / MRL-style dimensionality reduction: keep leading dims and
37
+ * re-L2-normalize so cosine similarity stays valid after truncation.
38
+ * Safe no-op when target is missing, non-positive, or >= full vector length.
39
+ */
40
+ function l2NormalizeVector(vector) {
41
+ if (!Array.isArray(vector) || vector.length === 0) return vector;
42
+ const numbers = vector.map(Number);
43
+ const norm = Math.sqrt(numbers.reduce((sum, value) => sum + (value * value), 0));
44
+ if (!Number.isFinite(norm) || norm === 0) {
45
+ const zero = numbers.slice();
46
+ zero[0] = 1;
47
+ return zero;
48
+ }
49
+ return numbers.map((value) => value / norm);
50
+ }
51
+
52
+ function resolveOutputDimensionality(options = {}, env = process.env) {
53
+ const provider = String(options.provider || options.embeddingProvider || '').toLowerCase();
54
+ const geminiOnly = provider === 'gemini' || options.geminiManaged === true;
55
+ const candidates = [
56
+ options.outputDimensionality,
57
+ options.dimensions,
58
+ options.matryoshkaDim,
59
+ env.THUMBGATE_MATRYOSHKA_DIM,
60
+ env.THUMBGATE_EMBED_DIM,
61
+ ];
62
+ // Gemini-only dim must not reshape Ollama / transformers / feature-hash vectors.
63
+ if (geminiOnly) {
64
+ candidates.push(env.THUMBGATE_GEMINI_EMBED_DIM);
65
+ }
66
+ for (const candidate of candidates) {
67
+ if (candidate === undefined || candidate === null || candidate === '') continue;
68
+ const parsed = Number(candidate);
69
+ if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
70
+ }
71
+ return null;
72
+ }
73
+
74
+ function applyMatryoshkaDimensionality(vector, targetDim) {
75
+ if (!Array.isArray(vector) || vector.length === 0) return vector;
76
+ const dim = Number(targetDim);
77
+ if (!Number.isFinite(dim) || dim <= 0 || dim >= vector.length) {
78
+ return vector.map(Number);
79
+ }
80
+ return l2NormalizeVector(vector.slice(0, Math.floor(dim)));
81
+ }
82
+
83
+ function finalizeEmbedding(vector, options = {}) {
84
+ return applyMatryoshkaDimensionality(vector, resolveOutputDimensionality(options));
85
+ }
86
+
87
+
34
88
  async function getLanceDB() {
35
89
  if (!_lancedb) {
36
90
  _lancedb = _lancedbLoader ? await _lancedbLoader() : await import('@lancedb/lancedb');
@@ -105,8 +159,125 @@ function getOllamaEmbeddingConfig() {
105
159
  * Synchronous capability check used by hot-path routing. This reports only
106
160
  * semantic providers, never the deterministic feature-hash degradation.
107
161
  */
162
+ function getOpenAICompatibleEmbeddingConfig(env = process.env) {
163
+ const providerHint = String(env.THUMBGATE_EMBED_PROVIDER || '').trim().toLowerCase();
164
+ const baseUrlRaw = String(
165
+ env.THUMBGATE_OPENAI_EMBED_BASE_URL
166
+ || env.DASHSCOPE_BASE_URL
167
+ || '',
168
+ ).trim();
169
+ const isDashscopeHint = ['dashscope', 'qwen', 'model-studio', 'openai-compatible'].includes(providerHint)
170
+ || /dashscope/i.test(baseUrlRaw);
171
+ const apiKey = String(
172
+ env.THUMBGATE_OPENAI_EMBED_API_KEY
173
+ || env.DASHSCOPE_API_KEY
174
+ || env.QWEN_API_KEY
175
+ || '',
176
+ ).trim();
177
+ let baseUrl = baseUrlRaw
178
+ || (isDashscopeHint ? 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1' : '');
179
+ if (baseUrl && !/^https?:\/\//i.test(baseUrl)) baseUrl = `https://${baseUrl}`;
180
+ while (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1);
181
+
182
+ const model = String(
183
+ env.THUMBGATE_OPENAI_EMBED_MODEL
184
+ || env.THUMBGATE_QWEN_EMBED_MODEL
185
+ || (isDashscopeHint ? 'text-embedding-v4' : ''),
186
+ ).trim();
187
+ const dimRaw = Number(env.THUMBGATE_OPENAI_EMBED_DIM || env.THUMBGATE_QWEN_EMBED_DIM || 0);
188
+ const dimensions = Number.isFinite(dimRaw) && dimRaw > 0 ? Math.floor(dimRaw) : null;
189
+
190
+ return {
191
+ enabled: Boolean(apiKey && baseUrl && model && (isDashscopeHint || providerHint === 'openai-compatible' || baseUrlRaw)),
192
+ provider: /dashscope/i.test(baseUrl) || isDashscopeHint ? 'dashscope' : 'openai-compatible',
193
+ apiKey,
194
+ baseUrl,
195
+ model,
196
+ dimensions,
197
+ endpoint: baseUrl ? `${baseUrl}/embeddings` : null,
198
+ timeoutMs: Math.max(
199
+ 250,
200
+ Math.min(30_000, Number(env.THUMBGATE_OPENAI_EMBED_TIMEOUT_MS) || 15_000),
201
+ ),
202
+ };
203
+ }
204
+
205
+ let _openaiEmbedderForTests = null;
206
+
207
+ /**
208
+ * OpenAI-compatible embeddings (DashScope text-embedding-v4 / Model Studio).
209
+ * Supports Matryoshka dims when the provider accepts `dimensions`.
210
+ */
211
+ async function embedWithOpenAICompatible(text, options = {}) {
212
+ const config = options.config || getOpenAICompatibleEmbeddingConfig(options.env || process.env);
213
+ if (!config.enabled && !_openaiEmbedderForTests) {
214
+ throw new Error('OpenAI-compatible embeddings requested but base URL, model, or API key is missing');
215
+ }
216
+
217
+ if (_openaiEmbedderForTests) {
218
+ return _openaiEmbedderForTests(text, config, options);
219
+ }
220
+
221
+ if (typeof fetch !== 'function') {
222
+ throw new TypeError('OpenAI-compatible embeddings require global fetch (Node 18.18+).');
223
+ }
224
+
225
+ const body = {
226
+ model: config.model,
227
+ input: String(text || ''),
228
+ };
229
+ const dim = resolveOutputDimensionality({
230
+ ...options,
231
+ provider: config.provider,
232
+ dimensions: options.dimensions || config.dimensions,
233
+ }) || config.dimensions;
234
+ if (dim) body.dimensions = dim;
235
+
236
+ const controller = new AbortController();
237
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
238
+ let response;
239
+ try {
240
+ response = await fetch(config.endpoint, {
241
+ method: 'POST',
242
+ headers: {
243
+ 'Content-Type': 'application/json',
244
+ Authorization: `Bearer ${config.apiKey}`,
245
+ },
246
+ body: JSON.stringify(body),
247
+ signal: controller.signal,
248
+ });
249
+ } catch (error) {
250
+ throw new Error(`OpenAI-compatible embedding service unavailable: ${error.message}`);
251
+ } finally {
252
+ clearTimeout(timer);
253
+ }
254
+
255
+ if (!response.ok) {
256
+ const errBody = await response.text().catch(() => '');
257
+ throw new Error(
258
+ `OpenAI-compatible embedding request failed: ${response.status} ${response.statusText}`
259
+ + `${errBody ? ` — ${errBody.slice(0, 240)}` : ''}`,
260
+ );
261
+ }
262
+
263
+ const payload = await response.json();
264
+ const values = payload
265
+ && Array.isArray(payload.data)
266
+ && payload.data[0]
267
+ && Array.isArray(payload.data[0].embedding)
268
+ ? payload.data[0].embedding
269
+ : null;
270
+
271
+ if (!Array.isArray(values) || values.length === 0) {
272
+ throw new Error('OpenAI-compatible embedding response did not include vector values');
273
+ }
274
+
275
+ return values.map(Number);
276
+ }
277
+
108
278
  function hasSemanticEmbeddingProvider() {
109
279
  if (process.env.THUMBGATE_VECTOR_STUB_EMBED === 'true') return true;
280
+ if (getOpenAICompatibleEmbeddingConfig().enabled) return true;
110
281
  if (getOllamaEmbeddingConfig().enabled) return true;
111
282
  if (hasLocalTransformerProvider()) return true;
112
283
  try {
@@ -221,7 +392,7 @@ async function embedWithGemini(text, options = {}) {
221
392
  content: {
222
393
  parts: [{ text: preparedText }],
223
394
  },
224
- outputDimensionality: config.outputDimensionality,
395
+ outputDimensionality: resolveOutputDimensionality(options) || config.outputDimensionality,
225
396
  };
226
397
  const taskType = resolveGeminiTaskType({
227
398
  kind: options.kind,
@@ -315,7 +486,7 @@ async function embedWithOllama(text, options = {}) {
315
486
  model: config.model,
316
487
  input: inputText,
317
488
  truncate: true,
318
- dimensions: options.outputDimensionality || undefined,
489
+ dimensions: resolveOutputDimensionality(options) || undefined,
319
490
  }),
320
491
  signal: AbortSignal.timeout(config.timeoutMs),
321
492
  });
@@ -372,7 +543,7 @@ async function embed(text, options = {}) {
372
543
  // Deterministic 384-dim unit vector: first element = 1.0, rest = 0.0
373
544
  const stub = Array(384).fill(0);
374
545
  stub[0] = 1.0;
375
- return stub;
546
+ return finalizeEmbedding(stub, options);
376
547
  }
377
548
  const geminiConfig = resolveGeminiEmbeddingConfig();
378
549
  if (geminiConfig.provider === 'coreai') {
@@ -390,11 +561,41 @@ async function embed(text, options = {}) {
390
561
  },
391
562
  fallbackUsed: false,
392
563
  };
393
- return vector;
564
+ return finalizeEmbedding(vector, options);
394
565
  } catch (coreaiError) {
395
566
  console.warn(`Core AI embedding failed, falling back to local: ${coreaiError.message}`);
396
567
  }
397
568
  }
569
+ const openaiEmbedConfig = getOpenAICompatibleEmbeddingConfig();
570
+ if (openaiEmbedConfig.enabled) {
571
+ try {
572
+ const vector = await embedWithOpenAICompatible(text, {
573
+ ...options,
574
+ config: openaiEmbedConfig,
575
+ });
576
+ _lastEmbeddingProfile = {
577
+ generatedAt: new Date().toISOString(),
578
+ source: openaiEmbedConfig.provider === 'dashscope' ? 'dashscope' : 'openai-compatible',
579
+ activeProfile: {
580
+ id: openaiEmbedConfig.provider,
581
+ model: openaiEmbedConfig.model,
582
+ outputDimensionality: vector.length,
583
+ task: options.task || 'code retrieval',
584
+ rationale: 'OpenAI-compatible embedding (DashScope text-embedding-v4 / Model Studio).',
585
+ qualityTier: 'production',
586
+ },
587
+ fallbackUsed: false,
588
+ };
589
+ return finalizeEmbedding(vector, {
590
+ ...options,
591
+ provider: openaiEmbedConfig.provider,
592
+ dimensions: openaiEmbedConfig.dimensions,
593
+ });
594
+ } catch (openaiEmbedError) {
595
+ const code = openaiEmbedError && (openaiEmbedError.code || openaiEmbedError.name || 'Error');
596
+ console.warn(`OpenAI-compatible embedding fallback: ${code}`);
597
+ }
598
+ }
398
599
  const ollamaConfig = getOllamaEmbeddingConfig();
399
600
  if (ollamaConfig.enabled) {
400
601
  try {
@@ -412,14 +613,14 @@ async function embed(text, options = {}) {
412
613
  },
413
614
  fallbackUsed: false,
414
615
  };
415
- return vector;
616
+ return finalizeEmbedding(vector, options);
416
617
  } catch (ollamaError) {
417
618
  console.warn(`Ollama embedding failed, falling back: ${ollamaError.message}`);
418
619
  }
419
620
  }
420
621
  if (geminiConfig.enabled) {
421
622
  const vector = await tryGeminiManagedEmbedding(text, options, geminiConfig);
422
- if (vector) return vector;
623
+ if (vector) return finalizeEmbedding(vector, { ...options, provider: 'gemini', geminiManaged: true });
423
624
  }
424
625
  if (hasLocalTransformerProvider()) {
425
626
  try {
@@ -428,7 +629,7 @@ async function embed(text, options = {}) {
428
629
  pooling: 'mean',
429
630
  normalize: true,
430
631
  });
431
- return Array.from(output.data); // Float32Array -> plain number[] for LanceDB Arrow serialization
632
+ return finalizeEmbedding(Array.from(output.data), options); // Float32Array -> plain number[] for LanceDB Arrow serialization
432
633
  } catch (transformerError) {
433
634
  console.warn(`Transformers.js embedding fallback: ${transformerError.message}`);
434
635
  }
@@ -439,7 +640,7 @@ async function embed(text, options = {}) {
439
640
  // so that THUMBGATE_GEMINI_EMBED_FALLBACK_LOCAL=false makes Gemini mandatory.
440
641
  if (geminiConfig.apiKey && !geminiConfig.enabled) {
441
642
  const vector = await tryGeminiManagedEmbedding(text, options, geminiConfig);
442
- if (vector) return vector;
643
+ if (vector) return finalizeEmbedding(vector, { ...options, provider: 'gemini', geminiManaged: true });
443
644
  }
444
645
 
445
646
  const vector = embedWithFeatureHash(text);
@@ -459,7 +660,60 @@ async function embed(text, options = {}) {
459
660
  fallbackUsed: true,
460
661
  fallbackReason: 'no_managed_or_transformer_embedder',
461
662
  };
462
- return vector;
663
+ return finalizeEmbedding(vector, options);
664
+ }
665
+
666
+
667
+ async function ensureTableCompatibleWithVector(db, vector) {
668
+ const dim = Array.isArray(vector) ? vector.length : 0;
669
+ if (!dim) return;
670
+ const tableNames = await db.tableNames();
671
+ if (!tableNames.includes(TABLE_NAME)) return;
672
+ try {
673
+ const table = await db.openTable(TABLE_NAME);
674
+ // Probe one row for stored vector length when available.
675
+ let storedDim = null;
676
+ if (typeof table.countRows === 'function') {
677
+ const count = await table.countRows();
678
+ if (count === 0) return;
679
+ }
680
+ if (typeof table.query === 'function') {
681
+ try {
682
+ const rows = await table.query().limit(1).toArray();
683
+ if (rows && rows[0] && Array.isArray(rows[0].vector)) {
684
+ storedDim = rows[0].vector.length;
685
+ }
686
+ } catch {
687
+ /* fall through to schema path */
688
+ }
689
+ }
690
+ if (storedDim == null && table.schema) {
691
+ try {
692
+ const schema = typeof table.schema === 'function' ? await table.schema() : table.schema;
693
+ const vectorField = schema && (schema.fields || schema).find
694
+ ? (schema.fields || schema).find((f) => f.name === 'vector' || f.name === 'embedding')
695
+ : null;
696
+ if (vectorField && vectorField.type && Number(vectorField.type.listSize || vectorField.type.length)) {
697
+ storedDim = Number(vectorField.type.listSize || vectorField.type.length);
698
+ }
699
+ } catch {
700
+ /* ignore */
701
+ }
702
+ }
703
+ if (storedDim != null && storedDim !== dim) {
704
+ console.warn(
705
+ `[vector-store] Embedding dimension changed (${storedDim} -> ${dim}); dropping ${TABLE_NAME} for full reindex.`,
706
+ );
707
+ if (typeof db.dropTable === 'function') {
708
+ await db.dropTable(TABLE_NAME);
709
+ } else if (typeof table.delete === 'function') {
710
+ // Best-effort wipe when drop is unavailable.
711
+ await table.delete('true');
712
+ }
713
+ }
714
+ } catch (err) {
715
+ console.warn(`[vector-store] dimension compatibility check failed: ${err && err.message ? err.message : err}`);
716
+ }
463
717
  }
464
718
 
465
719
  async function upsertFeedback(feedbackEvent) {
@@ -505,6 +759,7 @@ async function upsertFeedback(feedbackEvent) {
505
759
  retries: 2,
506
760
  logger: (msg) => console.warn(msg),
507
761
  }, async () => {
762
+ await ensureTableCompatibleWithVector(db, vector);
508
763
  const tableNames = await db.tableNames();
509
764
  if (tableNames.includes(TABLE_NAME)) {
510
765
  const table = await db.openTable(TABLE_NAME);
@@ -529,6 +784,9 @@ async function searchSimilar(queryText, limit = 5) {
529
784
  kind: 'query',
530
785
  task: 'code retrieval',
531
786
  });
787
+ await ensureTableCompatibleWithVector(db, vector);
788
+ const tableNamesAfter = await db.tableNames();
789
+ if (!tableNamesAfter.includes(TABLE_NAME)) return [];
532
790
  const table = await db.openTable(TABLE_NAME);
533
791
  const results = await table.search(vector).limit(limit).toArray();
534
792
  return results;
@@ -561,6 +819,11 @@ function setGeminiEmbedderForTests(loader) {
561
819
  _lastEmbeddingProfile = null;
562
820
  }
563
821
 
822
+ function setOpenAICompatibleEmbedderForTests(loader) {
823
+ _openaiEmbedderForTests = loader;
824
+ _lastEmbeddingProfile = null;
825
+ }
826
+
564
827
  module.exports = {
565
828
  upsertFeedback,
566
829
  searchSimilar,
@@ -572,9 +835,16 @@ module.exports = {
572
835
  setPipelineLoaderForTests,
573
836
  setLanceLoaderForTests,
574
837
  setGeminiEmbedderForTests,
838
+ setOpenAICompatibleEmbedderForTests,
575
839
  truncateForEmbedding,
576
840
  embedWithFeatureHash,
577
841
  embedWithOllama,
842
+ embedWithOpenAICompatible,
578
843
  getOllamaEmbeddingConfig,
844
+ getOpenAICompatibleEmbeddingConfig,
579
845
  hasSemanticEmbeddingProvider,
846
+ l2NormalizeVector,
847
+ resolveOutputDimensionality,
848
+ applyMatryoshkaDimensionality,
849
+ finalizeEmbedding,
580
850
  };