tryassay 0.23.0 → 0.27.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/dist/cli.js +31 -6
- package/dist/cli.js.map +1 -1
- package/dist/commands/assess.d.ts +1 -0
- package/dist/commands/assess.js +143 -8
- package/dist/commands/assess.js.map +1 -1
- package/dist/commands/config-cmd.d.ts +5 -0
- package/dist/commands/config-cmd.js +35 -0
- package/dist/commands/config-cmd.js.map +1 -0
- package/dist/commands/suppress.d.ts +7 -0
- package/dist/commands/suppress.js +66 -1
- package/dist/commands/suppress.js.map +1 -1
- package/dist/lib/__tests__/gap-matcher.test.d.ts +1 -0
- package/dist/lib/__tests__/gap-matcher.test.js +291 -0
- package/dist/lib/__tests__/gap-matcher.test.js.map +1 -0
- package/dist/lib/__tests__/gap-suppression.test.d.ts +1 -0
- package/dist/lib/__tests__/gap-suppression.test.js +168 -0
- package/dist/lib/__tests__/gap-suppression.test.js.map +1 -0
- package/dist/lib/gap-matcher.d.ts +63 -0
- package/dist/lib/gap-matcher.js +268 -0
- package/dist/lib/gap-matcher.js.map +1 -0
- package/dist/lib/intent-extractor.d.ts +5 -0
- package/dist/lib/intent-extractor.js +18 -3
- package/dist/lib/intent-extractor.js.map +1 -1
- package/dist/lib/intent-types.d.ts +11 -0
- package/dist/lib/overrides.d.ts +18 -0
- package/dist/lib/overrides.js +49 -0
- package/dist/lib/overrides.js.map +1 -1
- package/dist/lib/publisher.d.ts +19 -1
- package/dist/lib/publisher.js +62 -8
- package/dist/lib/publisher.js.map +1 -1
- package/dist/lib/telemetry.d.ts +39 -0
- package/dist/lib/telemetry.js +69 -0
- package/dist/lib/telemetry.js.map +1 -0
- package/dist/lib/user-config.d.ts +17 -0
- package/dist/lib/user-config.js +34 -0
- package/dist/lib/user-config.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { extractAnchors, simpleStem, extractTokens, tokensOverlap, matchGapToSuppression, } from '../gap-matcher.js';
|
|
3
|
+
// ── extractAnchors ───────────────────────────────────────────────────
|
|
4
|
+
describe('extractAnchors', () => {
|
|
5
|
+
it('extracts file paths', () => {
|
|
6
|
+
const anchors = extractAnchors('Missing src/routes/auth.ts implementation');
|
|
7
|
+
expect(anchors).toContain('src/routes/auth.ts');
|
|
8
|
+
});
|
|
9
|
+
it('extracts nested file paths', () => {
|
|
10
|
+
const anchors = extractAnchors('The file src/lib/gmail/handler.ts is referenced');
|
|
11
|
+
expect(anchors).toContain('src/lib/gmail/handler.ts');
|
|
12
|
+
});
|
|
13
|
+
it('extracts API route patterns', () => {
|
|
14
|
+
const anchors = extractAnchors('POST /api/users endpoint not found');
|
|
15
|
+
expect(anchors).toContain('/api/users');
|
|
16
|
+
});
|
|
17
|
+
it('extracts route-like paths without HTTP method', () => {
|
|
18
|
+
const anchors = extractAnchors('The /api/v2/auth/login route');
|
|
19
|
+
expect(anchors).toContain('/api/v2/auth/login');
|
|
20
|
+
});
|
|
21
|
+
it('extracts camelCase identifiers >= 6 chars', () => {
|
|
22
|
+
const anchors = extractAnchors('The handleAuth function is missing');
|
|
23
|
+
expect(anchors).toContain('handleauth');
|
|
24
|
+
});
|
|
25
|
+
it('extracts snake_case identifiers >= 6 chars', () => {
|
|
26
|
+
const anchors = extractAnchors('Missing user_profile_handler implementation');
|
|
27
|
+
expect(anchors).toContain('user_profile_handler');
|
|
28
|
+
});
|
|
29
|
+
it('ignores short identifiers', () => {
|
|
30
|
+
const anchors = extractAnchors('The foo_bar variable');
|
|
31
|
+
// foo_bar is 7 chars, should be included
|
|
32
|
+
expect(anchors).toContain('foo_bar');
|
|
33
|
+
});
|
|
34
|
+
it('returns empty array for empty input', () => {
|
|
35
|
+
expect(extractAnchors('')).toEqual([]);
|
|
36
|
+
});
|
|
37
|
+
it('returns empty array for text without anchors', () => {
|
|
38
|
+
const anchors = extractAnchors('This is a plain sentence');
|
|
39
|
+
expect(anchors).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
// ── simpleStem ───────────────────────────────────────────────────────
|
|
43
|
+
describe('simpleStem', () => {
|
|
44
|
+
it('strips -ing suffix', () => {
|
|
45
|
+
expect(simpleStem('handling')).toBe('handl');
|
|
46
|
+
});
|
|
47
|
+
it('strips -tion suffix', () => {
|
|
48
|
+
// 'authentication' hits -ation first (longer match) → 'authentic'
|
|
49
|
+
expect(simpleStem('authentication')).toBe('authentic');
|
|
50
|
+
// 'production' hits -tion → 'produc'
|
|
51
|
+
expect(simpleStem('production')).toBe('produc');
|
|
52
|
+
});
|
|
53
|
+
it('strips -ation suffix', () => {
|
|
54
|
+
expect(simpleStem('validation')).toBe('valid');
|
|
55
|
+
});
|
|
56
|
+
it('strips -ed suffix', () => {
|
|
57
|
+
expect(simpleStem('configured')).toBe('configur');
|
|
58
|
+
});
|
|
59
|
+
it('strips trailing -s', () => {
|
|
60
|
+
expect(simpleStem('handlers')).toBe('handler');
|
|
61
|
+
});
|
|
62
|
+
it('does not strip -ss words', () => {
|
|
63
|
+
expect(simpleStem('process')).toBe('process');
|
|
64
|
+
});
|
|
65
|
+
it('protects short words (4 chars or less)', () => {
|
|
66
|
+
expect(simpleStem('auth')).toBe('auth');
|
|
67
|
+
expect(simpleStem('file')).toBe('file');
|
|
68
|
+
expect(simpleStem('code')).toBe('code');
|
|
69
|
+
});
|
|
70
|
+
it('lowercases input', () => {
|
|
71
|
+
expect(simpleStem('GMAIL')).toBe('gmail');
|
|
72
|
+
});
|
|
73
|
+
it('strips -ment suffix', () => {
|
|
74
|
+
expect(simpleStem('management')).toBe('manage');
|
|
75
|
+
});
|
|
76
|
+
it('strips -ness suffix', () => {
|
|
77
|
+
expect(simpleStem('completeness')).toBe('complete');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
// ── extractTokens ────────────────────────────────────────────────────
|
|
81
|
+
describe('extractTokens', () => {
|
|
82
|
+
it('removes general stop words', () => {
|
|
83
|
+
const tokens = extractTokens('the handler is not working for the user', []);
|
|
84
|
+
expect(tokens).not.toContain('the');
|
|
85
|
+
expect(tokens).not.toContain('is');
|
|
86
|
+
expect(tokens).not.toContain('not');
|
|
87
|
+
expect(tokens).not.toContain('for');
|
|
88
|
+
});
|
|
89
|
+
it('removes domain stop stems', () => {
|
|
90
|
+
const tokens = extractTokens('missing file implementation documented', []);
|
|
91
|
+
// "miss", "file", "implement", "document" are all domain stop stems
|
|
92
|
+
expect(tokens).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
it('excludes anchor strings from tokens', () => {
|
|
95
|
+
const anchors = ['src/routes/auth.ts'];
|
|
96
|
+
const tokens = extractTokens('The src/routes/auth.ts file has gmail logic', anchors);
|
|
97
|
+
// "auth" and "ts" should be excluded since they're part of the anchor path
|
|
98
|
+
// "gmail" should remain
|
|
99
|
+
expect(tokens).toContain('gmail');
|
|
100
|
+
});
|
|
101
|
+
it('returns stemmed tokens', () => {
|
|
102
|
+
const tokens = extractTokens('authentication handling validation', []);
|
|
103
|
+
expect(tokens).toContain('authentic');
|
|
104
|
+
expect(tokens).toContain('handl');
|
|
105
|
+
expect(tokens).toContain('valid');
|
|
106
|
+
});
|
|
107
|
+
it('returns empty for empty input', () => {
|
|
108
|
+
expect(extractTokens('', [])).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
it('deduplicates tokens', () => {
|
|
111
|
+
const tokens = extractTokens('gmail gmail gmail calendar', []);
|
|
112
|
+
const gmailCount = tokens.filter(t => t === 'gmail').length;
|
|
113
|
+
expect(gmailCount).toBe(1);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
// ── tokensOverlap ────────────────────────────────────────────────────
|
|
117
|
+
describe('tokensOverlap', () => {
|
|
118
|
+
it('finds exact matches', () => {
|
|
119
|
+
const overlap = tokensOverlap(['gmail', 'calendar'], ['gmail', 'webhook']);
|
|
120
|
+
expect(overlap).toContain('gmail');
|
|
121
|
+
expect(overlap).not.toContain('calendar');
|
|
122
|
+
expect(overlap).not.toContain('webhook');
|
|
123
|
+
});
|
|
124
|
+
it('finds prefix matches (4+ chars)', () => {
|
|
125
|
+
const overlap = tokensOverlap(['auth'], ['authent']);
|
|
126
|
+
expect(overlap).toContain('auth');
|
|
127
|
+
});
|
|
128
|
+
it('prefix matching works in both directions', () => {
|
|
129
|
+
const overlap = tokensOverlap(['authent'], ['auth']);
|
|
130
|
+
expect(overlap).toContain('auth');
|
|
131
|
+
});
|
|
132
|
+
it('rejects prefix matches shorter than 4 chars', () => {
|
|
133
|
+
const overlap = tokensOverlap(['foo'], ['foobar']);
|
|
134
|
+
expect(overlap).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
it('returns empty for no overlap', () => {
|
|
137
|
+
const overlap = tokensOverlap(['gmail', 'calendar'], ['webhook', 'cron']);
|
|
138
|
+
expect(overlap).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
it('handles empty inputs', () => {
|
|
141
|
+
expect(tokensOverlap([], ['gmail'])).toEqual([]);
|
|
142
|
+
expect(tokensOverlap(['gmail'], [])).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
// ── matchGapToSuppression ────────────────────────────────────────────
|
|
146
|
+
describe('matchGapToSuppression', () => {
|
|
147
|
+
it('returns null on type mismatch', () => {
|
|
148
|
+
const gap = { type: 'missing-flow', description: 'No auth flow', gapId: 'abc123' };
|
|
149
|
+
const supp = {
|
|
150
|
+
gapId: 'abc123',
|
|
151
|
+
type: 'dead-code',
|
|
152
|
+
description: 'No auth flow',
|
|
153
|
+
reason: 'test',
|
|
154
|
+
suppressedAt: '',
|
|
155
|
+
};
|
|
156
|
+
expect(matchGapToSuppression(gap, supp)).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
it('returns exact confidence on ID match', () => {
|
|
159
|
+
const gap = { type: 'missing-flow', description: 'anything', gapId: 'abc12345' };
|
|
160
|
+
const supp = {
|
|
161
|
+
gapId: 'abc12345',
|
|
162
|
+
type: 'missing-flow',
|
|
163
|
+
description: 'completely different text',
|
|
164
|
+
reason: 'test',
|
|
165
|
+
suppressedAt: '',
|
|
166
|
+
};
|
|
167
|
+
const result = matchGapToSuppression(gap, supp);
|
|
168
|
+
expect(result).not.toBeNull();
|
|
169
|
+
expect(result.confidence).toBe('exact');
|
|
170
|
+
expect(result.score).toBe(1.0);
|
|
171
|
+
});
|
|
172
|
+
it('matches Gmail gap rephrasing (real claudeclaw data)', () => {
|
|
173
|
+
// Run 1 suppressed gap
|
|
174
|
+
const supp = {
|
|
175
|
+
gapId: '870b77bf',
|
|
176
|
+
type: 'incomplete-flow',
|
|
177
|
+
description: 'Gmail and Calendar scripts referenced in .env.example but no corresponding implementation files found in source directory',
|
|
178
|
+
evidence: 'GMAIL_CREDENTIALS_PATH and GOOGLE_CALENDAR_ID referenced in .env.example but no gmail.ts or calendar.ts found in src/',
|
|
179
|
+
reason: 'User-installed skill dependencies, not core application code',
|
|
180
|
+
suppressedAt: '2026-03-07T14:20:00Z',
|
|
181
|
+
};
|
|
182
|
+
// Run 2 rephrased gap (different description, different gapId)
|
|
183
|
+
const gap = {
|
|
184
|
+
type: 'incomplete-flow',
|
|
185
|
+
gapId: '09fddfd4',
|
|
186
|
+
description: 'Gmail/Calendar integration scripts missing despite environment configuration referencing them',
|
|
187
|
+
evidence: '.env.example defines GMAIL_CREDENTIALS_PATH and GOOGLE_CALENDAR_ID but src/ contains no gmail or calendar implementation modules',
|
|
188
|
+
};
|
|
189
|
+
const result = matchGapToSuppression(gap, supp);
|
|
190
|
+
expect(result).not.toBeNull();
|
|
191
|
+
expect(result.confidence).toBe('medium');
|
|
192
|
+
expect(result.score).toBeGreaterThanOrEqual(0.55);
|
|
193
|
+
});
|
|
194
|
+
it('does not match different gaps about the same file', () => {
|
|
195
|
+
const supp = {
|
|
196
|
+
gapId: 'aaa11111',
|
|
197
|
+
type: 'incomplete-flow',
|
|
198
|
+
description: 'Error handling in src/routes/auth.ts is incomplete — no try-catch around token verification',
|
|
199
|
+
evidence: 'src/routes/auth.ts:45-60 has unprotected JWT verification call',
|
|
200
|
+
reason: 'Known, tracked separately',
|
|
201
|
+
suppressedAt: '',
|
|
202
|
+
};
|
|
203
|
+
const gap = {
|
|
204
|
+
type: 'incomplete-flow',
|
|
205
|
+
gapId: 'bbb22222',
|
|
206
|
+
description: 'Input validation missing in src/routes/auth.ts — no schema validation for login request body',
|
|
207
|
+
evidence: 'src/routes/auth.ts:20-30 accepts req.body without zod/joi validation',
|
|
208
|
+
};
|
|
209
|
+
// These share the file path anchor but describe different issues.
|
|
210
|
+
// The token content should be different enough to prevent matching.
|
|
211
|
+
const result = matchGapToSuppression(gap, supp);
|
|
212
|
+
// They might match on high anchor overlap alone if only 1 anchor each,
|
|
213
|
+
// but should not reach the threshold since tokens differ significantly
|
|
214
|
+
if (result !== null) {
|
|
215
|
+
// If it matches, the score should be below threshold or have too few shared items
|
|
216
|
+
// This test is defensive — we want no match ideally
|
|
217
|
+
expect(result.score).toBeLessThan(0.55);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
it('does not match completely different gaps', () => {
|
|
221
|
+
const supp = {
|
|
222
|
+
gapId: 'xxx11111',
|
|
223
|
+
type: 'missing-flow',
|
|
224
|
+
description: 'No password reset flow exists for user accounts',
|
|
225
|
+
evidence: 'No reset endpoint or email sending logic found in auth module',
|
|
226
|
+
reason: 'Planned for v2',
|
|
227
|
+
suppressedAt: '',
|
|
228
|
+
};
|
|
229
|
+
const gap = {
|
|
230
|
+
type: 'missing-flow',
|
|
231
|
+
gapId: 'yyy22222',
|
|
232
|
+
description: 'WebSocket disconnection handling is absent from the chat module',
|
|
233
|
+
evidence: 'src/chat/websocket.ts has no onclose or reconnection logic',
|
|
234
|
+
};
|
|
235
|
+
expect(matchGapToSuppression(gap, supp)).toBeNull();
|
|
236
|
+
});
|
|
237
|
+
it('matches short descriptions with shared concept', () => {
|
|
238
|
+
const supp = {
|
|
239
|
+
gapId: 'short1',
|
|
240
|
+
type: 'incomplete-flow',
|
|
241
|
+
description: 'Gradium TTS provider lacks dedicated implementation file',
|
|
242
|
+
evidence: 'GRADIUM_API_KEY referenced in .env.example but no gradium.ts module',
|
|
243
|
+
reason: 'Consolidated in voice.ts',
|
|
244
|
+
suppressedAt: '',
|
|
245
|
+
};
|
|
246
|
+
const gap = {
|
|
247
|
+
type: 'incomplete-flow',
|
|
248
|
+
gapId: 'short2',
|
|
249
|
+
description: 'No dedicated Gradium TTS implementation module exists',
|
|
250
|
+
evidence: 'GRADIUM_API_KEY and GRADIUM_VOICE_ID in .env.example without corresponding gradium provider file',
|
|
251
|
+
};
|
|
252
|
+
const result = matchGapToSuppression(gap, supp);
|
|
253
|
+
expect(result).not.toBeNull();
|
|
254
|
+
expect(result.score).toBeGreaterThanOrEqual(0.55);
|
|
255
|
+
});
|
|
256
|
+
it('uses pre-computed matchAnchors and matchTokens', () => {
|
|
257
|
+
const supp = {
|
|
258
|
+
gapId: 'pre1',
|
|
259
|
+
type: 'incomplete-flow',
|
|
260
|
+
description: 'Gmail integration scripts missing',
|
|
261
|
+
evidence: 'src/gmail.ts not found',
|
|
262
|
+
reason: 'test',
|
|
263
|
+
suppressedAt: '',
|
|
264
|
+
matchAnchors: ['src/gmail.ts'],
|
|
265
|
+
matchTokens: ['gmail', 'integr', 'script'],
|
|
266
|
+
};
|
|
267
|
+
const gap = {
|
|
268
|
+
type: 'incomplete-flow',
|
|
269
|
+
gapId: 'pre2',
|
|
270
|
+
description: 'Gmail integration modules absent from source',
|
|
271
|
+
evidence: 'Expected src/gmail.ts but not present',
|
|
272
|
+
};
|
|
273
|
+
const result = matchGapToSuppression(gap, supp);
|
|
274
|
+
expect(result).not.toBeNull();
|
|
275
|
+
});
|
|
276
|
+
it('returns null when gap has no gapId and no text overlap', () => {
|
|
277
|
+
const supp = {
|
|
278
|
+
gapId: 'id1',
|
|
279
|
+
type: 'dead-code',
|
|
280
|
+
description: 'Unused helper function in utils',
|
|
281
|
+
reason: 'test',
|
|
282
|
+
suppressedAt: '',
|
|
283
|
+
};
|
|
284
|
+
const gap = {
|
|
285
|
+
type: 'dead-code',
|
|
286
|
+
description: 'Orphaned database migration script',
|
|
287
|
+
};
|
|
288
|
+
expect(matchGapToSuppression(gap, supp)).toBeNull();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
//# sourceMappingURL=gap-matcher.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gap-matcher.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/gap-matcher.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,cAAc,EACd,UAAU,EACV,aAAa,EACb,aAAa,EACb,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAG3B,wEAAwE;AAExE,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,OAAO,GAAG,cAAc,CAAC,2CAA2C,CAAC,CAAC;QAC5E,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,OAAO,GAAG,cAAc,CAAC,iDAAiD,CAAC,CAAC;QAClF,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,OAAO,GAAG,cAAc,CAAC,oCAAoC,CAAC,CAAC;QACrE,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,OAAO,GAAG,cAAc,CAAC,8BAA8B,CAAC,CAAC;QAC/D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,OAAO,GAAG,cAAc,CAAC,oCAAoC,CAAC,CAAC;QACrE,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,OAAO,GAAG,cAAc,CAAC,6CAA6C,CAAC,CAAC;QAC9E,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,OAAO,GAAG,cAAc,CAAC,sBAAsB,CAAC,CAAC;QACvD,yCAAyC;QACzC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,OAAO,GAAG,cAAc,CAAC,0BAA0B,CAAC,CAAC;QAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AAExE,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,kEAAkE;QAClE,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACvD,qCAAqC;QACrC,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC1B,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AAExE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,MAAM,GAAG,aAAa,CAAC,yCAAyC,EAAE,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,MAAM,GAAG,aAAa,CAAC,wCAAwC,EAAE,EAAE,CAAC,CAAC;QAC3E,oEAAoE;QACpE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,OAAO,GAAG,CAAC,oBAAoB,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,aAAa,CAAC,6CAA6C,EAAE,OAAO,CAAC,CAAC;QACrF,2EAA2E;QAC3E,wBAAwB;QACxB,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,MAAM,GAAG,aAAa,CAAC,oCAAoC,EAAE,EAAE,CAAC,CAAC;QACvE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,MAAM,GAAG,aAAa,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC;QAC/D,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;QAC5D,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AAExE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QAC3E,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1E,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AAExE,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,cAAuB,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QAC5F,MAAM,IAAI,GAAyB;YACjC,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,WAAW;YACjB,WAAW,EAAE,cAAc;YAC3B,MAAM,EAAE,MAAM;YACd,YAAY,EAAE,EAAE;SACjB,CAAC;QACF,MAAM,CAAC,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,cAAuB,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;QAC1F,MAAM,IAAI,GAAyB;YACjC,KAAK,EAAE,UAAU;YACjB,IAAI,EAAE,cAAc;YACpB,WAAW,EAAE,2BAA2B;YACxC,MAAM,EAAE,MAAM;YACd,YAAY,EAAE,EAAE;SACjB,CAAC;QACF,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,uBAAuB;QACvB,MAAM,IAAI,GAAyB;YACjC,KAAK,EAAE,UAAU;YACjB,IAAI,EAAE,iBAAiB;YACvB,WAAW,EAAE,2HAA2H;YACxI,QAAQ,EAAE,uHAAuH;YACjI,MAAM,EAAE,8DAA8D;YACtE,YAAY,EAAE,sBAAsB;SACrC,CAAC;QAEF,+DAA+D;QAC/D,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,iBAA0B;YAChC,KAAK,EAAE,UAAU;YACjB,WAAW,EAAE,+FAA+F;YAC5G,QAAQ,EAAE,kIAAkI;SAC7I,CAAC;QAEF,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,IAAI,GAAyB;YACjC,KAAK,EAAE,UAAU;YACjB,IAAI,EAAE,iBAAiB;YACvB,WAAW,EAAE,6FAA6F;YAC1G,QAAQ,EAAE,gEAAgE;YAC1E,MAAM,EAAE,2BAA2B;YACnC,YAAY,EAAE,EAAE;SACjB,CAAC;QAEF,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,iBAA0B;YAChC,KAAK,EAAE,UAAU;YACjB,WAAW,EAAE,8FAA8F;YAC3G,QAAQ,EAAE,sEAAsE;SACjF,CAAC;QAEF,kEAAkE;QAClE,oEAAoE;QACpE,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAChD,uEAAuE;QACvE,uEAAuE;QACvE,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,kFAAkF;YAClF,oDAAoD;YACpD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,IAAI,GAAyB;YACjC,KAAK,EAAE,UAAU;YACjB,IAAI,EAAE,cAAc;YACpB,WAAW,EAAE,iDAAiD;YAC9D,QAAQ,EAAE,+DAA+D;YACzE,MAAM,EAAE,gBAAgB;YACxB,YAAY,EAAE,EAAE;SACjB,CAAC;QAEF,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,cAAuB;YAC7B,KAAK,EAAE,UAAU;YACjB,WAAW,EAAE,iEAAiE;YAC9E,QAAQ,EAAE,4DAA4D;SACvE,CAAC;QAEF,MAAM,CAAC,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,IAAI,GAAyB;YACjC,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,iBAAiB;YACvB,WAAW,EAAE,0DAA0D;YACvE,QAAQ,EAAE,qEAAqE;YAC/E,MAAM,EAAE,0BAA0B;YAClC,YAAY,EAAE,EAAE;SACjB,CAAC;QAEF,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,iBAA0B;YAChC,KAAK,EAAE,QAAQ;YACf,WAAW,EAAE,uDAAuD;YACpE,QAAQ,EAAE,kGAAkG;SAC7G,CAAC;QAEF,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,IAAI,GAAyB;YACjC,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,iBAAiB;YACvB,WAAW,EAAE,mCAAmC;YAChD,QAAQ,EAAE,wBAAwB;YAClC,MAAM,EAAE,MAAM;YACd,YAAY,EAAE,EAAE;YAChB,YAAY,EAAE,CAAC,cAAc,CAAC;YAC9B,WAAW,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC;SAC3C,CAAC;QAEF,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,iBAA0B;YAChC,KAAK,EAAE,MAAM;YACb,WAAW,EAAE,8CAA8C;YAC3D,QAAQ,EAAE,uCAAuC;SAClD,CAAC;QAEF,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,IAAI,GAAyB;YACjC,KAAK,EAAE,KAAK;YACZ,IAAI,EAAE,WAAW;YACjB,WAAW,EAAE,iCAAiC;YAC9C,MAAM,EAAE,MAAM;YACd,YAAY,EAAE,EAAE;SACjB,CAAC;QAEF,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,WAAoB;YAC1B,WAAW,EAAE,oCAAoC;SAClD,CAAC;QAEF,MAAM,CAAC,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { generateGapId } from '../intent-extractor.js';
|
|
6
|
+
import { addGapSuppression, buildSuppressedGapIdSet, loadOverrides, isGapSuppressed } from '../overrides.js';
|
|
7
|
+
describe('generateGapId', () => {
|
|
8
|
+
it('produces deterministic 8-char hex', () => {
|
|
9
|
+
const id1 = generateGapId('missing-flow', 'No password reset flow');
|
|
10
|
+
const id2 = generateGapId('missing-flow', 'No password reset flow');
|
|
11
|
+
expect(id1).toBe(id2);
|
|
12
|
+
expect(id1).toMatch(/^[0-9a-f]{8}$/);
|
|
13
|
+
});
|
|
14
|
+
it('produces different IDs for different inputs', () => {
|
|
15
|
+
const id1 = generateGapId('missing-flow', 'No password reset');
|
|
16
|
+
const id2 = generateGapId('dead-code', 'Unused helper function');
|
|
17
|
+
expect(id1).not.toBe(id2);
|
|
18
|
+
});
|
|
19
|
+
it('produces different IDs when type differs but description is same', () => {
|
|
20
|
+
const id1 = generateGapId('missing-flow', 'same description');
|
|
21
|
+
const id2 = generateGapId('dead-code', 'same description');
|
|
22
|
+
expect(id1).not.toBe(id2);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('gap suppression storage', () => {
|
|
26
|
+
let tempDir;
|
|
27
|
+
beforeEach(async () => {
|
|
28
|
+
tempDir = await mkdtemp(join(tmpdir(), 'assay-gap-test-'));
|
|
29
|
+
await mkdir(join(tempDir, '.assay'), { recursive: true });
|
|
30
|
+
});
|
|
31
|
+
it('adds a gap suppression to overrides', async () => {
|
|
32
|
+
await addGapSuppression(tempDir, {
|
|
33
|
+
gapId: 'abc12345',
|
|
34
|
+
type: 'missing-flow',
|
|
35
|
+
description: 'No password reset flow',
|
|
36
|
+
reason: 'Handled by external auth service',
|
|
37
|
+
});
|
|
38
|
+
const overrides = await loadOverrides(tempDir);
|
|
39
|
+
expect(overrides.gapSuppressions).toHaveLength(1);
|
|
40
|
+
expect(overrides.gapSuppressions[0].gapId).toBe('abc12345');
|
|
41
|
+
expect(overrides.gapSuppressions[0].reason).toBe('Handled by external auth service');
|
|
42
|
+
});
|
|
43
|
+
it('does not add duplicate gap suppressions', async () => {
|
|
44
|
+
await addGapSuppression(tempDir, {
|
|
45
|
+
gapId: 'abc12345',
|
|
46
|
+
type: 'missing-flow',
|
|
47
|
+
description: 'test',
|
|
48
|
+
reason: 'reason 1',
|
|
49
|
+
});
|
|
50
|
+
await addGapSuppression(tempDir, {
|
|
51
|
+
gapId: 'abc12345',
|
|
52
|
+
type: 'missing-flow',
|
|
53
|
+
description: 'test',
|
|
54
|
+
reason: 'reason 2',
|
|
55
|
+
});
|
|
56
|
+
const overrides = await loadOverrides(tempDir);
|
|
57
|
+
expect(overrides.gapSuppressions).toHaveLength(1);
|
|
58
|
+
});
|
|
59
|
+
it('preserves existing claim suppressions when adding gap suppression', async () => {
|
|
60
|
+
// Write existing overrides with claim suppressions
|
|
61
|
+
await writeFile(join(tempDir, '.assay', 'overrides.json'), JSON.stringify({
|
|
62
|
+
suppressions: [{
|
|
63
|
+
claimId: 'claim001',
|
|
64
|
+
file: 'a.ts',
|
|
65
|
+
claim: 'test claim',
|
|
66
|
+
reason: 'fp',
|
|
67
|
+
suppressedAt: '2026-01-01T00:00:00Z',
|
|
68
|
+
suppressedBy: 'manual',
|
|
69
|
+
}],
|
|
70
|
+
}));
|
|
71
|
+
await addGapSuppression(tempDir, {
|
|
72
|
+
gapId: 'gap001',
|
|
73
|
+
type: 'dead-code',
|
|
74
|
+
description: 'Unused helper',
|
|
75
|
+
reason: 'Intentional',
|
|
76
|
+
});
|
|
77
|
+
const overrides = await loadOverrides(tempDir);
|
|
78
|
+
expect(overrides.suppressions).toHaveLength(1);
|
|
79
|
+
expect(overrides.gapSuppressions).toHaveLength(1);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('buildSuppressedGapIdSet', () => {
|
|
83
|
+
it('returns empty set for empty overrides', () => {
|
|
84
|
+
const set = buildSuppressedGapIdSet({ suppressions: [] });
|
|
85
|
+
expect(set.size).toBe(0);
|
|
86
|
+
});
|
|
87
|
+
it('returns empty set when gapSuppressions is undefined (backward compat)', () => {
|
|
88
|
+
const set = buildSuppressedGapIdSet({ suppressions: [] });
|
|
89
|
+
expect(set.size).toBe(0);
|
|
90
|
+
});
|
|
91
|
+
it('returns set of suppressed gap IDs', () => {
|
|
92
|
+
const set = buildSuppressedGapIdSet({
|
|
93
|
+
suppressions: [],
|
|
94
|
+
gapSuppressions: [
|
|
95
|
+
{ gapId: 'aaa', type: 'missing-flow', description: 'x', reason: 'y', suppressedAt: '' },
|
|
96
|
+
{ gapId: 'bbb', type: 'dead-code', description: 'x', reason: 'y', suppressedAt: '' },
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
expect(set.size).toBe(2);
|
|
100
|
+
expect(set.has('aaa')).toBe(true);
|
|
101
|
+
expect(set.has('bbb')).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('isGapSuppressed', () => {
|
|
105
|
+
it('returns exact match for matching gap ID', () => {
|
|
106
|
+
const result = isGapSuppressed({ type: 'missing-flow', description: 'test', gapId: 'abc123' }, {
|
|
107
|
+
suppressions: [],
|
|
108
|
+
gapSuppressions: [
|
|
109
|
+
{ gapId: 'abc123', type: 'missing-flow', description: 'test', reason: 'r', suppressedAt: '' },
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
expect(result).not.toBeNull();
|
|
113
|
+
expect(result.confidence).toBe('exact');
|
|
114
|
+
});
|
|
115
|
+
it('returns null when no suppressions exist', () => {
|
|
116
|
+
const result = isGapSuppressed({ type: 'missing-flow', description: 'test', gapId: 'abc123' }, { suppressions: [] });
|
|
117
|
+
expect(result).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
it('backward compat: works with old suppressions missing matchAnchors/matchTokens', () => {
|
|
120
|
+
// Old-format suppression without matchAnchors, matchTokens, or evidence
|
|
121
|
+
const result = isGapSuppressed({
|
|
122
|
+
type: 'incomplete-flow',
|
|
123
|
+
gapId: 'new-id',
|
|
124
|
+
description: 'Gmail and Calendar scripts referenced in .env.example but no implementation files found',
|
|
125
|
+
evidence: 'GMAIL_CREDENTIALS_PATH and GOOGLE_CALENDAR_ID in .env.example',
|
|
126
|
+
}, {
|
|
127
|
+
suppressions: [],
|
|
128
|
+
gapSuppressions: [
|
|
129
|
+
{
|
|
130
|
+
gapId: 'old-id',
|
|
131
|
+
type: 'incomplete-flow',
|
|
132
|
+
description: 'Gmail/Calendar integration scripts missing despite environment configuration',
|
|
133
|
+
reason: 'User-installed skills',
|
|
134
|
+
suppressedAt: '',
|
|
135
|
+
// NO evidence, matchAnchors, or matchTokens — old format
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
// Should still attempt pattern matching using lazy computation
|
|
140
|
+
// Whether it matches depends on token overlap — the key test is that it doesn't crash
|
|
141
|
+
expect(result === null || result.confidence === 'medium').toBe(true);
|
|
142
|
+
});
|
|
143
|
+
it('pattern-matches rephrased gap when exact ID fails', () => {
|
|
144
|
+
const result = isGapSuppressed({
|
|
145
|
+
type: 'incomplete-flow',
|
|
146
|
+
gapId: 'different-id',
|
|
147
|
+
description: 'Gradium TTS provider lacks dedicated implementation module',
|
|
148
|
+
evidence: 'GRADIUM_API_KEY and GRADIUM_VOICE_ID in .env.example without gradium.ts',
|
|
149
|
+
}, {
|
|
150
|
+
suppressions: [],
|
|
151
|
+
gapSuppressions: [
|
|
152
|
+
{
|
|
153
|
+
gapId: 'original-id',
|
|
154
|
+
type: 'incomplete-flow',
|
|
155
|
+
description: 'No dedicated Gradium TTS implementation file exists',
|
|
156
|
+
evidence: 'GRADIUM_API_KEY referenced in .env.example but no gradium.ts module found',
|
|
157
|
+
reason: 'Consolidated in voice.ts',
|
|
158
|
+
suppressedAt: '',
|
|
159
|
+
matchAnchors: ['gradium.ts'],
|
|
160
|
+
matchTokens: ['gradium', 'tts', 'provid', 'dedic', 'lack'],
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
expect(result).not.toBeNull();
|
|
165
|
+
expect(result.confidence).toBe('medium');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
//# sourceMappingURL=gap-suppression.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gap-suppression.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/gap-suppression.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern-based gap matching for persistent suppression across LLM rephrases.
|
|
3
|
+
*
|
|
4
|
+
* Gap IDs are SHA256(type + "::" + description), so when the LLM rephrases
|
|
5
|
+
* a gap description between runs, the ID changes. This module provides a
|
|
6
|
+
* deterministic fallback matcher that uses structural anchors (file paths,
|
|
7
|
+
* routes, code identifiers) and normalized token overlap to match
|
|
8
|
+
* conceptually identical gaps even when their descriptions differ.
|
|
9
|
+
*
|
|
10
|
+
* Pure functions only — no I/O, no LLM calls.
|
|
11
|
+
*/
|
|
12
|
+
import type { IntentGapSuppression, IntentGapType } from './intent-types.js';
|
|
13
|
+
export type MatchConfidence = 'exact' | 'high' | 'medium';
|
|
14
|
+
export interface GapMatchResult {
|
|
15
|
+
confidence: MatchConfidence;
|
|
16
|
+
score: number;
|
|
17
|
+
suppressionGapId: string;
|
|
18
|
+
matchedAnchors: string[];
|
|
19
|
+
matchedTokens: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface GapLike {
|
|
22
|
+
gapId?: string;
|
|
23
|
+
type: IntentGapType | string;
|
|
24
|
+
description: string;
|
|
25
|
+
evidence?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const MATCH_THRESHOLD = 0.55;
|
|
28
|
+
export declare const MIN_SHARED_EVIDENCE = 2;
|
|
29
|
+
/**
|
|
30
|
+
* Extract structural anchors from text: file paths, route patterns, code identifiers.
|
|
31
|
+
* Anchors are concrete code artifacts that survive rephrasing.
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractAnchors(text: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Very simple English stemmer. Strips common suffixes.
|
|
36
|
+
* No external deps. Good enough for overlap comparison.
|
|
37
|
+
*/
|
|
38
|
+
export declare function simpleStem(word: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Extract normalized, meaningful tokens from text.
|
|
41
|
+
* Removes general stop words, domain stop stems, and anchor strings
|
|
42
|
+
* (to prevent double-counting when both anchors and tokens are scored).
|
|
43
|
+
*/
|
|
44
|
+
export declare function extractTokens(text: string, anchors: string[]): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Find overlapping tokens between two sets.
|
|
47
|
+
* Uses prefix matching: tokens sharing a 4+ char prefix count as matching.
|
|
48
|
+
* e.g., "auth" matches "authent", "valid" matches "validat"
|
|
49
|
+
*/
|
|
50
|
+
export declare function tokensOverlap(setA: string[], setB: string[]): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Try to match a gap against a stored suppression using pattern matching.
|
|
53
|
+
* Returns null if no match, or a GapMatchResult with confidence and score.
|
|
54
|
+
*
|
|
55
|
+
* Algorithm:
|
|
56
|
+
* 1. Type must match exactly
|
|
57
|
+
* 2. Extract anchors from both sides
|
|
58
|
+
* 3. High-confidence structural exit: >= 2 anchors on both sides, Jaccard >= 0.7 or >= 3 shared
|
|
59
|
+
* 4. Extract content tokens (minus anchors)
|
|
60
|
+
* 5. Compute token overlap with prefix matching
|
|
61
|
+
* 6. Score = tokenScore + (anchorScore * 0.3) >= 0.55 and total shared >= 2
|
|
62
|
+
*/
|
|
63
|
+
export declare function matchGapToSuppression(gap: GapLike, suppression: IntentGapSuppression): GapMatchResult | null;
|