@skillsmith/core 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/embeddings/probe.d.ts +62 -0
- package/dist/src/embeddings/probe.d.ts.map +1 -0
- package/dist/src/embeddings/probe.js +89 -0
- package/dist/src/embeddings/probe.js.map +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/telemetry/index.d.ts +2 -1
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +4 -1
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/posthog.d.ts +19 -1
- package/dist/src/telemetry/posthog.d.ts.map +1 -1
- package/dist/src/telemetry/posthog.js +14 -0
- package/dist/src/telemetry/posthog.js.map +1 -1
- package/dist/src/telemetry/wrap.bench.d.ts +13 -0
- package/dist/src/telemetry/wrap.bench.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.bench.js +41 -0
- package/dist/src/telemetry/wrap.bench.js.map +1 -0
- package/dist/src/telemetry/wrap.d.ts +75 -0
- package/dist/src/telemetry/wrap.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.js +124 -0
- package/dist/src/telemetry/wrap.js.map +1 -0
- package/dist/src/telemetry/wrap.test.d.ts +14 -0
- package/dist/src/telemetry/wrap.test.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.test.js +283 -0
- package/dist/src/telemetry/wrap.test.js.map +1 -0
- package/dist/tests/embeddings/probe.test.d.ts +13 -0
- package/dist/tests/embeddings/probe.test.d.ts.map +1 -0
- package/dist/tests/embeddings/probe.test.js +154 -0
- package/dist/tests/embeddings/probe.test.js.map +1 -0
- package/package.json +11 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5016: Unit tests for withTelemetry HOF + isTelemetered registry.
|
|
3
|
+
*
|
|
4
|
+
* Covers the shared-state matrix invariants from plan line 720:
|
|
5
|
+
* - function-decl wrap
|
|
6
|
+
* - arrow-const export wrap (H3 critical case)
|
|
7
|
+
* - double-register idempotency (Set dedupes by reference)
|
|
8
|
+
* - isTelemetered true/false
|
|
9
|
+
* - emit-on-throw (success:false, then re-throw)
|
|
10
|
+
* - telemetry emit failure does NOT affect wrapped fn
|
|
11
|
+
* - per-request framework (H4 — not memoised)
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
14
|
+
import { withTelemetry, isTelemetered, setEmissionGate } from './wrap.js';
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Mock trackSkillInvoke so tests run without a live PostHog instance
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
vi.mock('./posthog.js', () => ({
|
|
19
|
+
trackSkillInvoke: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
import { trackSkillInvoke } from './posthog.js';
|
|
22
|
+
const mockTrack = vi.mocked(trackSkillInvoke);
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
mockTrack.mockReset();
|
|
25
|
+
// SMI-5019 wire-in: default is now suppress-when-no-gate. Install a permissive
|
|
26
|
+
// gate so the legacy SMI-5016 tests below (which predate the consent gate)
|
|
27
|
+
// continue to assert against emit behaviour. The new emission-gate describe
|
|
28
|
+
// block at the bottom overrides this per-case.
|
|
29
|
+
setEmissionGate(() => true);
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
// Reset to default-suppress so a leaked gate from one test does not flow
|
|
33
|
+
// into the next file's tests or pollute the per-process module state.
|
|
34
|
+
setEmissionGate(undefined);
|
|
35
|
+
});
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Helpers
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
const BASE_OPTS = {
|
|
40
|
+
source: 'mcp-tool',
|
|
41
|
+
extractSkillId: () => 'test/skill',
|
|
42
|
+
};
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// 1. Function-declaration wrap
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
describe('function-declaration wrap', () => {
|
|
47
|
+
it('wraps a named function declaration and registers it', async () => {
|
|
48
|
+
function myHandler(x) {
|
|
49
|
+
return x * 2;
|
|
50
|
+
}
|
|
51
|
+
const wrapped = withTelemetry(myHandler, BASE_OPTS);
|
|
52
|
+
expect(isTelemetered(wrapped)).toBe(true);
|
|
53
|
+
expect(isTelemetered(myHandler)).toBe(false);
|
|
54
|
+
const result = await wrapped(3);
|
|
55
|
+
expect(result).toBe(6);
|
|
56
|
+
expect(mockTrack).toHaveBeenCalledOnce();
|
|
57
|
+
expect(mockTrack).toHaveBeenCalledWith(expect.objectContaining({ skillId: 'test/skill', success: true }));
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// 2. Arrow-const export wrap (H3 critical case)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
describe('arrow-const export wrap', () => {
|
|
64
|
+
it('wraps an arrow-const and registers the returned function (not the original)', async () => {
|
|
65
|
+
// This is the exact pattern that plan review change H3 was about:
|
|
66
|
+
// arrow-const exports cannot be mutated, so the registry MUST track the
|
|
67
|
+
// returned wrappedFn, not mutate the original.
|
|
68
|
+
const originalFn = (v) => v.toUpperCase();
|
|
69
|
+
const wrappedExport = withTelemetry(originalFn, BASE_OPTS);
|
|
70
|
+
// The returned function is telemetered; the original is not.
|
|
71
|
+
expect(isTelemetered(wrappedExport)).toBe(true);
|
|
72
|
+
expect(isTelemetered(originalFn)).toBe(false);
|
|
73
|
+
const result = await wrappedExport('hello');
|
|
74
|
+
expect(result).toBe('HELLO');
|
|
75
|
+
expect(mockTrack).toHaveBeenCalledOnce();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// 3. Double-register idempotency
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
describe('double-register', () => {
|
|
82
|
+
it('re-wrapping the same RETURNED function adds no new entries (Set dedupes by ref)', () => {
|
|
83
|
+
const original = () => 'value';
|
|
84
|
+
const w1 = withTelemetry(original, BASE_OPTS);
|
|
85
|
+
// Capture Set size after first wrap
|
|
86
|
+
expect(isTelemetered(w1)).toBe(true);
|
|
87
|
+
// Now attempt to re-wrap the *returned* function.
|
|
88
|
+
// The Set already contains w1, so adding it again is a no-op by Set semantics.
|
|
89
|
+
const w2 = withTelemetry(w1, BASE_OPTS);
|
|
90
|
+
// w2 is a NEW wrapper around w1 — both are in the Set, but w1 wasn't
|
|
91
|
+
// added a second time. isTelemetered(w1) is still true, and w2 is also true.
|
|
92
|
+
expect(isTelemetered(w1)).toBe(true);
|
|
93
|
+
expect(isTelemetered(w2)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
it('wrapping the same original fn twice produces two distinct wrapped fns, both registered', () => {
|
|
96
|
+
const original = () => 'x';
|
|
97
|
+
const wa = withTelemetry(original, BASE_OPTS);
|
|
98
|
+
const wb = withTelemetry(original, BASE_OPTS);
|
|
99
|
+
// Different wrapper references
|
|
100
|
+
expect(wa).not.toBe(wb);
|
|
101
|
+
expect(isTelemetered(wa)).toBe(true);
|
|
102
|
+
expect(isTelemetered(wb)).toBe(true);
|
|
103
|
+
// Original still not in registry
|
|
104
|
+
expect(isTelemetered(original)).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// 4. isTelemetered true/false
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
describe('isTelemetered', () => {
|
|
111
|
+
it('returns false for an arbitrary unwrapped function', () => {
|
|
112
|
+
const plain = () => { };
|
|
113
|
+
expect(isTelemetered(plain)).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
it('returns true only for the wrapped return value', () => {
|
|
116
|
+
const original = () => 42;
|
|
117
|
+
const result = withTelemetry(original, BASE_OPTS);
|
|
118
|
+
expect(isTelemetered(result)).toBe(true);
|
|
119
|
+
expect(isTelemetered(original)).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// 5. Emit happens even on thrown errors
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
describe('emit-on-throw', () => {
|
|
126
|
+
it('emits with success:false and re-throws when handler throws', async () => {
|
|
127
|
+
const boom = () => {
|
|
128
|
+
throw new Error('handler exploded');
|
|
129
|
+
};
|
|
130
|
+
const wrappedBoom = withTelemetry(boom, BASE_OPTS);
|
|
131
|
+
await expect(wrappedBoom()).rejects.toThrow('handler exploded');
|
|
132
|
+
expect(mockTrack).toHaveBeenCalledOnce();
|
|
133
|
+
expect(mockTrack).toHaveBeenCalledWith(expect.objectContaining({ success: false, skillId: 'test/skill' }));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// 6. Telemetry emission failure does NOT affect wrapped fn
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
describe('emit-failure survival', () => {
|
|
140
|
+
it('returns the handler result even when trackSkillInvoke throws', async () => {
|
|
141
|
+
mockTrack.mockImplementation(() => {
|
|
142
|
+
throw new Error('PostHog is down');
|
|
143
|
+
});
|
|
144
|
+
const stable = () => 'still works';
|
|
145
|
+
const wrappedStable = withTelemetry(stable, BASE_OPTS);
|
|
146
|
+
const result = await wrappedStable();
|
|
147
|
+
expect(result).toBe('still works');
|
|
148
|
+
// trackSkillInvoke was called (and threw), but the wrapper swallowed it.
|
|
149
|
+
expect(mockTrack).toHaveBeenCalledOnce();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// 7. Per-request framework value (H4 — not memoised)
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
describe('per-request framework', () => {
|
|
156
|
+
it('reflects opts.extractFramework return value at each call independently', async () => {
|
|
157
|
+
let frameValue = 'cursor';
|
|
158
|
+
const handler = () => 'ok';
|
|
159
|
+
const wrappedWithFramework = withTelemetry(handler, {
|
|
160
|
+
...BASE_OPTS,
|
|
161
|
+
extractFramework: () => frameValue,
|
|
162
|
+
});
|
|
163
|
+
await wrappedWithFramework();
|
|
164
|
+
expect(mockTrack).toHaveBeenLastCalledWith(expect.objectContaining({ framework: 'cursor' }));
|
|
165
|
+
// Mutate the frame value — simulates a different client on the same server
|
|
166
|
+
frameValue = 'copilot';
|
|
167
|
+
mockTrack.mockReset();
|
|
168
|
+
await wrappedWithFramework();
|
|
169
|
+
expect(mockTrack).toHaveBeenLastCalledWith(expect.objectContaining({ framework: 'copilot' }));
|
|
170
|
+
});
|
|
171
|
+
it('defaults framework to "unknown" when extractFramework is not provided', async () => {
|
|
172
|
+
const handler = () => 'ok';
|
|
173
|
+
const wrappedNoFrame = withTelemetry(handler, BASE_OPTS // no extractFramework
|
|
174
|
+
);
|
|
175
|
+
await wrappedNoFrame();
|
|
176
|
+
expect(mockTrack).toHaveBeenCalledWith(expect.objectContaining({ framework: 'unknown' }));
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// 8. Overhead gate (Risk #7 — p99 < 1ms per wrapped call)
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
describe('overhead gate (risk #7)', () => {
|
|
183
|
+
it('p99 per-call overhead is < 1ms over 10 000 iterations', async () => {
|
|
184
|
+
const ITERATIONS = 10_000;
|
|
185
|
+
const noopHandler = async () => 42;
|
|
186
|
+
const wrappedNoop = withTelemetry(noopHandler, {
|
|
187
|
+
source: 'mcp-tool',
|
|
188
|
+
extractSkillId: () => 'bench/skill',
|
|
189
|
+
});
|
|
190
|
+
// JIT warm-up — stabilise before measuring
|
|
191
|
+
for (let i = 0; i < 100; i++) {
|
|
192
|
+
await wrappedNoop();
|
|
193
|
+
}
|
|
194
|
+
mockTrack.mockReset();
|
|
195
|
+
const elapsed = [];
|
|
196
|
+
for (let i = 0; i < ITERATIONS; i++) {
|
|
197
|
+
const t0 = performance.now();
|
|
198
|
+
await wrappedNoop();
|
|
199
|
+
elapsed.push(performance.now() - t0);
|
|
200
|
+
}
|
|
201
|
+
elapsed.sort((a, b) => a - b);
|
|
202
|
+
const p99 = elapsed[Math.ceil(ITERATIONS * 0.99) - 1];
|
|
203
|
+
const mean = elapsed.reduce((s, v) => s + v, 0) / ITERATIONS;
|
|
204
|
+
// Risk #7 gate: p99 must be below 1ms
|
|
205
|
+
expect(p99).toBeLessThan(1);
|
|
206
|
+
// Sanity gate: mean overhead should be well below 0.5ms
|
|
207
|
+
expect(mean).toBeLessThan(0.5);
|
|
208
|
+
expect(mockTrack).toHaveBeenCalledTimes(ITERATIONS);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// 9. Emission gate (SMI-5019 wire-in)
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
//
|
|
215
|
+
// These tests verify the privacy-safe default-suppress contract:
|
|
216
|
+
//
|
|
217
|
+
// - No gate installed → withTelemetry must NOT call trackSkillInvoke
|
|
218
|
+
// - Gate returns true → emit
|
|
219
|
+
// - Gate returns false → suppress
|
|
220
|
+
// - Gate is evaluated per-call (not memoised), so a toggling predicate
|
|
221
|
+
// produces different outcomes on consecutive calls
|
|
222
|
+
//
|
|
223
|
+
// The outer `beforeEach` installs a permissive gate for the legacy tests;
|
|
224
|
+
// these cases override that gate explicitly per-case.
|
|
225
|
+
describe('emission gate (SMI-5019 wire-in)', () => {
|
|
226
|
+
beforeEach(() => {
|
|
227
|
+
// Override the outer permissive gate — start each case with no gate
|
|
228
|
+
// installed, so the default-suppress behaviour is the natural baseline.
|
|
229
|
+
setEmissionGate(undefined);
|
|
230
|
+
});
|
|
231
|
+
it('default-suppress: no gate installed → no emit', async () => {
|
|
232
|
+
const handler = () => 'ok';
|
|
233
|
+
const wrappedFn = withTelemetry(handler, BASE_OPTS);
|
|
234
|
+
const result = await wrappedFn();
|
|
235
|
+
expect(result).toBe('ok');
|
|
236
|
+
expect(mockTrack).not.toHaveBeenCalled();
|
|
237
|
+
});
|
|
238
|
+
it('gate returns true → emit', async () => {
|
|
239
|
+
setEmissionGate(() => true);
|
|
240
|
+
const handler = () => 'ok';
|
|
241
|
+
const wrappedFn = withTelemetry(handler, BASE_OPTS);
|
|
242
|
+
await wrappedFn();
|
|
243
|
+
expect(mockTrack).toHaveBeenCalledOnce();
|
|
244
|
+
expect(mockTrack).toHaveBeenCalledWith(expect.objectContaining({ skillId: 'test/skill', success: true }));
|
|
245
|
+
});
|
|
246
|
+
it('gate returns false → no emit', async () => {
|
|
247
|
+
setEmissionGate(() => false);
|
|
248
|
+
const handler = () => 'ok';
|
|
249
|
+
const wrappedFn = withTelemetry(handler, BASE_OPTS);
|
|
250
|
+
await wrappedFn();
|
|
251
|
+
expect(mockTrack).not.toHaveBeenCalled();
|
|
252
|
+
});
|
|
253
|
+
it('gate is evaluated per-call (toggling predicate produces emit then no-emit)', async () => {
|
|
254
|
+
// Toggle true → false across the two invocations. Verifies the gate is
|
|
255
|
+
// NOT memoised at install time — each call queries it fresh, matching
|
|
256
|
+
// the per-call extractFramework contract (H4).
|
|
257
|
+
let nextReturn = true;
|
|
258
|
+
setEmissionGate(() => {
|
|
259
|
+
const value = nextReturn;
|
|
260
|
+
nextReturn = !nextReturn;
|
|
261
|
+
return value;
|
|
262
|
+
});
|
|
263
|
+
const handler = () => 'ok';
|
|
264
|
+
const wrappedFn = withTelemetry(handler, BASE_OPTS);
|
|
265
|
+
await wrappedFn();
|
|
266
|
+
expect(mockTrack).toHaveBeenCalledOnce();
|
|
267
|
+
mockTrack.mockReset();
|
|
268
|
+
await wrappedFn();
|
|
269
|
+
expect(mockTrack).not.toHaveBeenCalled();
|
|
270
|
+
});
|
|
271
|
+
it('gate is also consulted on throw — no emit when suppressed even if handler fails', async () => {
|
|
272
|
+
// Reinforces the privacy-safe invariant: a throwing handler must not
|
|
273
|
+
// smuggle telemetry past a suppressing gate via the finally block.
|
|
274
|
+
setEmissionGate(() => false);
|
|
275
|
+
const boom = () => {
|
|
276
|
+
throw new Error('handler exploded');
|
|
277
|
+
};
|
|
278
|
+
const wrappedBoom = withTelemetry(boom, BASE_OPTS);
|
|
279
|
+
await expect(wrappedBoom()).rejects.toThrow('handler exploded');
|
|
280
|
+
expect(mockTrack).not.toHaveBeenCalled();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
//# sourceMappingURL=wrap.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap.test.js","sourceRoot":"","sources":["../../../src/telemetry/wrap.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5039: Unit tests for the canonical `probeEmbeddingCapability` helper in
|
|
3
|
+
* `@skillsmith/core/embeddings/probe`. Mirrors the shape of the legacy
|
|
4
|
+
* `packages/mcp-server/tests/startup-probe.test.ts` unit tier (SMI-5009) but
|
|
5
|
+
* exercises the real exported function — not an inline copy — so the four
|
|
6
|
+
* consumers (mcp-server, doc-retrieval-mcp server/cli, cli) all share the
|
|
7
|
+
* same audited behavior.
|
|
8
|
+
*
|
|
9
|
+
* The mcp-server integration tier (spawn-based) is preserved separately to
|
|
10
|
+
* pin the MCP stdio invariant — see SMI-5009 for the rationale.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=probe.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.test.d.ts","sourceRoot":"","sources":["../../../tests/embeddings/probe.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5039: Unit tests for the canonical `probeEmbeddingCapability` helper in
|
|
3
|
+
* `@skillsmith/core/embeddings/probe`. Mirrors the shape of the legacy
|
|
4
|
+
* `packages/mcp-server/tests/startup-probe.test.ts` unit tier (SMI-5009) but
|
|
5
|
+
* exercises the real exported function — not an inline copy — so the four
|
|
6
|
+
* consumers (mcp-server, doc-retrieval-mcp server/cli, cli) all share the
|
|
7
|
+
* same audited behavior.
|
|
8
|
+
*
|
|
9
|
+
* The mcp-server integration tier (spawn-based) is preserved separately to
|
|
10
|
+
* pin the MCP stdio invariant — see SMI-5009 for the rationale.
|
|
11
|
+
*/
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
|
+
import { EmbeddingService } from '../../src/embeddings/index.js';
|
|
14
|
+
import { probeEmbeddingCapability } from '../../src/embeddings/probe.js';
|
|
15
|
+
describe('SMI-5039 probeEmbeddingCapability — success path', () => {
|
|
16
|
+
let checkSpy;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
19
|
+
// Spy on getTransformersLoadError to prevent any accidental real-module
|
|
20
|
+
// access during the test even though the success path never reads it.
|
|
21
|
+
vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
26
|
+
});
|
|
27
|
+
it('is silent when real embeddings are available', async () => {
|
|
28
|
+
checkSpy.mockResolvedValue(true);
|
|
29
|
+
const logs = [];
|
|
30
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
31
|
+
expect(logs).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('SMI-5039 probeEmbeddingCapability — mock fallback path', () => {
|
|
35
|
+
let checkSpy;
|
|
36
|
+
let getErrSpy;
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
39
|
+
getErrSpy = vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
40
|
+
});
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
vi.restoreAllMocks();
|
|
43
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
44
|
+
});
|
|
45
|
+
it('logs structured mock-fallback warning with reason when checkAvailability returns false', async () => {
|
|
46
|
+
checkSpy.mockResolvedValue(false);
|
|
47
|
+
getErrSpy.mockReturnValue(new Error('ENOENT: cannot find module @huggingface/transformers'));
|
|
48
|
+
const logs = [];
|
|
49
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
50
|
+
expect(logs).toHaveLength(1);
|
|
51
|
+
expect(logs[0]).toMatch(/\[skillsmith\] embeddings: mock \(transformers unavailable: ENOENT: cannot find module @huggingface\/transformers/);
|
|
52
|
+
// Remediation hint MUST be present.
|
|
53
|
+
expect(logs[0]).toContain('install @huggingface/transformers');
|
|
54
|
+
expect(logs[0]).toContain('SKILLSMITH_USE_MOCK_EMBEDDINGS=true');
|
|
55
|
+
});
|
|
56
|
+
it('falls back to "module-load-failed" when no load error is recorded', async () => {
|
|
57
|
+
checkSpy.mockResolvedValue(false);
|
|
58
|
+
getErrSpy.mockReturnValue(null);
|
|
59
|
+
const logs = [];
|
|
60
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
61
|
+
expect(logs).toHaveLength(1);
|
|
62
|
+
expect(logs[0]).toContain('transformers unavailable: module-load-failed');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('SMI-5039 probeEmbeddingCapability — timeout path', () => {
|
|
66
|
+
let checkSpy;
|
|
67
|
+
let getErrSpy;
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
70
|
+
getErrSpy = vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
71
|
+
});
|
|
72
|
+
afterEach(() => {
|
|
73
|
+
vi.restoreAllMocks();
|
|
74
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
75
|
+
});
|
|
76
|
+
it('emits probe-timeout line and returns within the bound when checkAvailability hangs', async () => {
|
|
77
|
+
// Hangs forever — only the timeout sentinel should resolve.
|
|
78
|
+
checkSpy.mockImplementation(() => new Promise(() => undefined));
|
|
79
|
+
getErrSpy.mockReturnValue(null);
|
|
80
|
+
const logs = [];
|
|
81
|
+
const start = Date.now();
|
|
82
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m), timeoutMs: 100 });
|
|
83
|
+
const elapsed = Date.now() - start;
|
|
84
|
+
expect(logs).toHaveLength(1);
|
|
85
|
+
expect(logs[0]).toMatch(/embeddings: mock \(transformers unavailable: probe-timeout/);
|
|
86
|
+
// Must complete within a small multiple of the timeout — proves the
|
|
87
|
+
// hard-bound holds even when checkAvailability never resolves.
|
|
88
|
+
expect(elapsed).toBeLessThan(2000);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('SMI-5039 probeEmbeddingCapability — error path', () => {
|
|
92
|
+
let checkSpy;
|
|
93
|
+
let getErrSpy;
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
96
|
+
getErrSpy = vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
97
|
+
});
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
vi.restoreAllMocks();
|
|
100
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
101
|
+
});
|
|
102
|
+
it('catches a thrown error and logs probe-failed without rethrowing', async () => {
|
|
103
|
+
checkSpy.mockRejectedValue(new Error('boom'));
|
|
104
|
+
getErrSpy.mockReturnValue(null);
|
|
105
|
+
const logs = [];
|
|
106
|
+
await expect(probeEmbeddingCapability({ logger: (m) => logs.push(m) })).resolves.toBeUndefined();
|
|
107
|
+
expect(logs).toHaveLength(1);
|
|
108
|
+
expect(logs[0]).toMatch(/embeddings: probe-failed \(boom/);
|
|
109
|
+
expect(logs[0]).toContain('install @huggingface/transformers');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe('SMI-5039 probeEmbeddingCapability — quiet mode', () => {
|
|
113
|
+
let checkSpy;
|
|
114
|
+
let getErrSpy;
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
117
|
+
getErrSpy = vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
118
|
+
});
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
vi.restoreAllMocks();
|
|
121
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
122
|
+
});
|
|
123
|
+
it('suppresses the warning when opts.quiet=true', async () => {
|
|
124
|
+
checkSpy.mockResolvedValue(false);
|
|
125
|
+
getErrSpy.mockReturnValue(new Error('boom'));
|
|
126
|
+
const logs = [];
|
|
127
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m), quiet: true });
|
|
128
|
+
expect(logs).toEqual([]);
|
|
129
|
+
});
|
|
130
|
+
it('suppresses the warning when SKILLSMITH_QUIET=true', async () => {
|
|
131
|
+
checkSpy.mockResolvedValue(false);
|
|
132
|
+
getErrSpy.mockReturnValue(new Error('boom'));
|
|
133
|
+
process.env['SKILLSMITH_QUIET'] = 'true';
|
|
134
|
+
const logs = [];
|
|
135
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
136
|
+
expect(logs).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
it('honors SKILLSMITH_QUIET=1 (numeric truthy)', async () => {
|
|
139
|
+
checkSpy.mockResolvedValue(false);
|
|
140
|
+
getErrSpy.mockReturnValue(new Error('boom'));
|
|
141
|
+
process.env['SKILLSMITH_QUIET'] = '1';
|
|
142
|
+
const logs = [];
|
|
143
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
144
|
+
expect(logs).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
it('does NOT suppress when SKILLSMITH_QUIET is unset and quiet=false', async () => {
|
|
147
|
+
checkSpy.mockResolvedValue(false);
|
|
148
|
+
getErrSpy.mockReturnValue(new Error('boom'));
|
|
149
|
+
const logs = [];
|
|
150
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m), quiet: false });
|
|
151
|
+
expect(logs).toHaveLength(1);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=probe.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.test.js","sourceRoot":"","sources":["../../../tests/embeddings/probe.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillsmith/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Core types and utilities for Skillsmith",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
"import": "./dist/src/embeddings/index.js",
|
|
22
22
|
"default": "./dist/src/embeddings/index.js"
|
|
23
23
|
},
|
|
24
|
+
"./embeddings/probe": {
|
|
25
|
+
"types": "./dist/src/embeddings/probe.d.ts",
|
|
26
|
+
"import": "./dist/src/embeddings/probe.js",
|
|
27
|
+
"default": "./dist/src/embeddings/probe.js"
|
|
28
|
+
},
|
|
24
29
|
"./testing": {
|
|
25
30
|
"types": "./dist/src/testing/index.d.ts",
|
|
26
31
|
"import": "./dist/src/testing/index.js",
|
|
@@ -60,6 +65,11 @@
|
|
|
60
65
|
"types": "./dist/src/skills/index-local.d.ts",
|
|
61
66
|
"import": "./dist/src/skills/index-local.js",
|
|
62
67
|
"default": "./dist/src/skills/index-local.js"
|
|
68
|
+
},
|
|
69
|
+
"./telemetry": {
|
|
70
|
+
"types": "./dist/src/telemetry/index.d.ts",
|
|
71
|
+
"import": "./dist/src/telemetry/index.js",
|
|
72
|
+
"default": "./dist/src/telemetry/index.js"
|
|
63
73
|
}
|
|
64
74
|
},
|
|
65
75
|
"scripts": {
|