@rigour-labs/core 2.22.0 → 3.0.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 (117) hide show
  1. package/README.md +58 -0
  2. package/dist/context.test.js +2 -3
  3. package/dist/environment.test.js +2 -1
  4. package/dist/gates/agent-team.d.ts +2 -1
  5. package/dist/gates/agent-team.js +1 -0
  6. package/dist/gates/base.d.ts +3 -1
  7. package/dist/gates/base.js +3 -0
  8. package/dist/gates/checkpoint.d.ts +2 -1
  9. package/dist/gates/checkpoint.js +3 -2
  10. package/dist/gates/context-window-artifacts.d.ts +2 -1
  11. package/dist/gates/context-window-artifacts.js +6 -3
  12. package/dist/gates/context.d.ts +2 -1
  13. package/dist/gates/context.js +1 -0
  14. package/dist/gates/coverage.js +3 -1
  15. package/dist/gates/dependency.js +5 -5
  16. package/dist/gates/duplication-drift.d.ts +2 -1
  17. package/dist/gates/duplication-drift.js +4 -1
  18. package/dist/gates/environment.js +4 -4
  19. package/dist/gates/hallucinated-imports.d.ts +21 -2
  20. package/dist/gates/hallucinated-imports.js +116 -2
  21. package/dist/gates/inconsistent-error-handling.d.ts +2 -1
  22. package/dist/gates/inconsistent-error-handling.js +21 -7
  23. package/dist/gates/promise-safety.d.ts +68 -0
  24. package/dist/gates/promise-safety.js +509 -0
  25. package/dist/gates/retry-loop-breaker.d.ts +2 -1
  26. package/dist/gates/retry-loop-breaker.js +2 -1
  27. package/dist/gates/runner.js +34 -1
  28. package/dist/gates/safety.d.ts +2 -1
  29. package/dist/gates/safety.js +2 -1
  30. package/dist/gates/security-patterns-owasp.test.d.ts +1 -0
  31. package/dist/gates/security-patterns-owasp.test.js +171 -0
  32. package/dist/gates/security-patterns.d.ts +6 -1
  33. package/dist/gates/security-patterns.js +101 -0
  34. package/dist/gates/structure.js +1 -1
  35. package/dist/hooks/checker.d.ts +23 -0
  36. package/dist/hooks/checker.js +222 -0
  37. package/dist/hooks/checker.test.d.ts +1 -0
  38. package/dist/hooks/checker.test.js +132 -0
  39. package/dist/hooks/index.d.ts +9 -0
  40. package/dist/hooks/index.js +8 -0
  41. package/dist/hooks/standalone-checker.d.ts +15 -0
  42. package/dist/hooks/standalone-checker.js +106 -0
  43. package/dist/hooks/templates.d.ts +22 -0
  44. package/dist/hooks/templates.js +232 -0
  45. package/dist/hooks/types.d.ts +34 -0
  46. package/dist/hooks/types.js +21 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +2 -0
  49. package/dist/services/fix-packet-service.d.ts +0 -1
  50. package/dist/services/fix-packet-service.js +9 -14
  51. package/dist/services/score-history.d.ts +54 -0
  52. package/dist/services/score-history.js +122 -0
  53. package/dist/templates/index.js +176 -0
  54. package/dist/types/fix-packet.d.ts +5 -5
  55. package/dist/types/fix-packet.js +1 -1
  56. package/dist/types/index.d.ts +207 -0
  57. package/dist/types/index.js +32 -0
  58. package/package.json +21 -1
  59. package/src/context.test.ts +0 -256
  60. package/src/discovery.test.ts +0 -88
  61. package/src/discovery.ts +0 -112
  62. package/src/environment.test.ts +0 -115
  63. package/src/gates/agent-team.test.ts +0 -134
  64. package/src/gates/agent-team.ts +0 -210
  65. package/src/gates/ast-handlers/base.ts +0 -13
  66. package/src/gates/ast-handlers/python.ts +0 -145
  67. package/src/gates/ast-handlers/python_parser.py +0 -181
  68. package/src/gates/ast-handlers/typescript.ts +0 -264
  69. package/src/gates/ast-handlers/universal.ts +0 -184
  70. package/src/gates/ast.ts +0 -54
  71. package/src/gates/base.ts +0 -28
  72. package/src/gates/checkpoint.test.ts +0 -135
  73. package/src/gates/checkpoint.ts +0 -311
  74. package/src/gates/content.ts +0 -51
  75. package/src/gates/context-window-artifacts.ts +0 -277
  76. package/src/gates/context.ts +0 -270
  77. package/src/gates/coverage.ts +0 -74
  78. package/src/gates/dependency.ts +0 -108
  79. package/src/gates/duplication-drift.ts +0 -231
  80. package/src/gates/environment.ts +0 -94
  81. package/src/gates/file.ts +0 -46
  82. package/src/gates/hallucinated-imports.ts +0 -361
  83. package/src/gates/inconsistent-error-handling.ts +0 -254
  84. package/src/gates/retry-loop-breaker.ts +0 -151
  85. package/src/gates/runner.ts +0 -188
  86. package/src/gates/safety.ts +0 -56
  87. package/src/gates/security-patterns.test.ts +0 -162
  88. package/src/gates/security-patterns.ts +0 -306
  89. package/src/gates/structure.ts +0 -36
  90. package/src/index.ts +0 -13
  91. package/src/pattern-index/embeddings.ts +0 -84
  92. package/src/pattern-index/index.ts +0 -59
  93. package/src/pattern-index/indexer.test.ts +0 -276
  94. package/src/pattern-index/indexer.ts +0 -1023
  95. package/src/pattern-index/matcher.test.ts +0 -293
  96. package/src/pattern-index/matcher.ts +0 -493
  97. package/src/pattern-index/overrides.ts +0 -235
  98. package/src/pattern-index/security.ts +0 -151
  99. package/src/pattern-index/staleness.test.ts +0 -313
  100. package/src/pattern-index/staleness.ts +0 -568
  101. package/src/pattern-index/types.ts +0 -339
  102. package/src/safety.test.ts +0 -53
  103. package/src/services/adaptive-thresholds.test.ts +0 -189
  104. package/src/services/adaptive-thresholds.ts +0 -275
  105. package/src/services/context-engine.ts +0 -104
  106. package/src/services/fix-packet-service.ts +0 -42
  107. package/src/services/state-service.ts +0 -138
  108. package/src/smoke.test.ts +0 -18
  109. package/src/templates/index.ts +0 -338
  110. package/src/types/fix-packet.ts +0 -32
  111. package/src/types/index.ts +0 -200
  112. package/src/utils/logger.ts +0 -43
  113. package/src/utils/scanner.test.ts +0 -37
  114. package/src/utils/scanner.ts +0 -43
  115. package/tsconfig.json +0 -10
  116. package/vitest.config.ts +0 -7
  117. package/vitest.setup.ts +0 -30
@@ -1,493 +0,0 @@
1
- /**
2
- * Pattern Matcher
3
- *
4
- * Matches queries against the pattern index using multiple strategies:
5
- * 1. Exact name match
6
- * 2. Fuzzy name match (Levenshtein)
7
- * 3. Signature match
8
- * 4. Keyword/semantic match
9
- */
10
-
11
- import type {
12
- PatternEntry,
13
- PatternIndex,
14
- PatternMatch,
15
- PatternMatchResult,
16
- PatternOverride
17
- } from './types.js';
18
- import { generateEmbedding, cosineSimilarity } from './embeddings.js';
19
-
20
- /**
21
- * Configuration for pattern matching.
22
- */
23
- export interface MatcherConfig {
24
- /** Minimum confidence threshold to include a match (0-100) */
25
- minConfidence: number;
26
-
27
- /** Maximum number of matches to return */
28
- maxMatches: number;
29
-
30
- /** Whether to use fuzzy matching */
31
- useFuzzy: boolean;
32
-
33
- /** Whether to use signature matching */
34
- useSignature: boolean;
35
-
36
- /** Whether to use keyword/semantic matching */
37
- useKeywords: boolean;
38
-
39
- /** Action when matches are found */
40
- defaultAction: 'BLOCK' | 'WARN' | 'ALLOW';
41
-
42
- /** Whether to use semantic embeddings for matching */
43
- useEmbeddings: boolean;
44
- }
45
-
46
- /** Default matcher configuration */
47
- const DEFAULT_MATCHER_CONFIG: MatcherConfig = {
48
- minConfidence: 60,
49
- maxMatches: 5,
50
- useFuzzy: true,
51
- useSignature: true,
52
- useKeywords: true,
53
- defaultAction: 'WARN',
54
- useEmbeddings: true
55
- };
56
-
57
- /**
58
- * Pattern Matcher class.
59
- * Finds similar patterns in the index.
60
- */
61
- export class PatternMatcher {
62
- private index: PatternIndex;
63
- private config: MatcherConfig;
64
- private overrides: PatternOverride[];
65
- private nameMap: Map<string, PatternEntry[]>;
66
-
67
- constructor(
68
- index: PatternIndex,
69
- config: Partial<MatcherConfig> = {},
70
- overrides: PatternOverride[] = []
71
- ) {
72
- this.index = index;
73
- this.config = { ...DEFAULT_MATCHER_CONFIG, ...config };
74
- this.overrides = overrides;
75
-
76
- // Build name map for O(1) lookups
77
- this.nameMap = new Map();
78
- for (const pattern of index.patterns) {
79
- const normalized = pattern.name.toLowerCase();
80
- const existing = this.nameMap.get(normalized) || [];
81
- existing.push(pattern);
82
- this.nameMap.set(normalized, existing);
83
- }
84
- }
85
-
86
- /**
87
- * Find patterns similar to a query.
88
- */
89
- async match(query: {
90
- name?: string;
91
- signature?: string;
92
- keywords?: string[];
93
- type?: string;
94
- intent?: string; // High-level description for semantic match
95
- }): Promise<PatternMatchResult> {
96
- const matches: PatternMatch[] = [];
97
-
98
- // Pre-calculate query embedding if semantic search is enabled
99
- let queryEmbedding: number[] | null = null;
100
- if (this.config.useEmbeddings && (query.intent || query.name)) {
101
- queryEmbedding = await generateEmbedding(`${query.name || ''} ${query.intent || ''}`);
102
- }
103
-
104
- // Check for override first
105
- if (query.name && this.hasOverride(query.name)) {
106
- return {
107
- query: query.name || '',
108
- matches: [],
109
- suggestion: 'Human override granted for this pattern.',
110
- canOverride: false,
111
- status: 'OVERRIDE_ALLOWED',
112
- action: 'ALLOW'
113
- };
114
- }
115
-
116
- // Strategy 1: Fast Exact name match (O(1))
117
- if (query.name) {
118
- const exactPatterns = this.nameMap.get(query.name.toLowerCase()) || [];
119
- for (const pattern of exactPatterns) {
120
- const match = this.exactNameMatch(query.name, pattern);
121
- if (match) {
122
- matches.push(match);
123
- }
124
- }
125
-
126
- // If we found exact matches, we might still want to find others,
127
- // but we can skip checking these specific patterns again in the main loop.
128
- }
129
-
130
- for (const pattern of this.index.patterns) {
131
- // Skip if we already matched this pattern via exact match
132
- if (query.name && pattern.name.toLowerCase() === query.name.toLowerCase()) {
133
- continue;
134
- }
135
-
136
- let currentBest: PatternMatch | null = null;
137
- let maxConfidence = -1;
138
-
139
- // Strategy 2: Fuzzy name match
140
- if (query.name && this.config.useFuzzy) {
141
- const match = this.fuzzyNameMatch(query.name, pattern);
142
- if (match && match.confidence > maxConfidence) {
143
- currentBest = match;
144
- maxConfidence = match.confidence;
145
- }
146
- }
147
-
148
- // Strategy 3: Signature match
149
- if (query.signature && this.config.useSignature) {
150
- const match = this.signatureMatch(query.signature, pattern);
151
- if (match && match.confidence > maxConfidence) {
152
- currentBest = match;
153
- maxConfidence = match.confidence;
154
- }
155
- }
156
-
157
- // Strategy 4: Keyword match
158
- if (query.keywords && query.keywords.length > 0 && this.config.useKeywords) {
159
- const match = this.keywordMatch(query.keywords, pattern);
160
- if (match && match.confidence > maxConfidence) {
161
- currentBest = match;
162
- maxConfidence = match.confidence;
163
- }
164
- }
165
-
166
- // Strategy 5: Semantic embedding match
167
- if (queryEmbedding && pattern.embedding && this.config.useEmbeddings) {
168
- const similarity = cosineSimilarity(queryEmbedding, pattern.embedding);
169
- const confidence = Math.round(similarity * 100);
170
-
171
- if (confidence > maxConfidence) {
172
- currentBest = {
173
- pattern,
174
- matchType: 'semantic',
175
- confidence,
176
- reason: `Semantic match (${confidence}%): similar intent to "${pattern.name}"`
177
- };
178
- maxConfidence = confidence;
179
- }
180
- }
181
-
182
- if (currentBest && maxConfidence >= this.config.minConfidence) {
183
- matches.push(currentBest);
184
- }
185
- }
186
-
187
- // Sort by confidence and limit
188
- matches.sort((a, b) => b.confidence - a.confidence);
189
- const topMatches = matches.slice(0, this.config.maxMatches);
190
-
191
- // Determine status and action
192
- const hasExact = topMatches.some(m => m.matchType === 'exact');
193
- const hasHighConfidence = topMatches.some(m => m.confidence >= 90);
194
-
195
- let status: PatternMatchResult['status'] = 'NO_MATCH';
196
- let action: PatternMatchResult['action'] = 'ALLOW';
197
- let suggestion = '';
198
-
199
- if (topMatches.length > 0) {
200
- status = 'FOUND_SIMILAR';
201
- action = hasExact || hasHighConfidence ? 'BLOCK' : this.config.defaultAction;
202
-
203
- const best = topMatches[0];
204
- suggestion = this.generateSuggestion(best);
205
- }
206
-
207
- return {
208
- query: query.name || query.signature || query.keywords?.join(' ') || '',
209
- matches: topMatches,
210
- suggestion,
211
- canOverride: true,
212
- status,
213
- action
214
- };
215
- }
216
-
217
- /**
218
- * Check for exact name match.
219
- */
220
- private exactNameMatch(queryName: string, pattern: PatternEntry): PatternMatch | null {
221
- const normalizedQuery = queryName.toLowerCase();
222
- const normalizedPattern = pattern.name.toLowerCase();
223
-
224
- if (normalizedQuery === normalizedPattern) {
225
- return {
226
- pattern,
227
- matchType: 'exact',
228
- confidence: 100,
229
- reason: `Exact match: "${pattern.name}" already exists in ${pattern.file}`
230
- };
231
- }
232
-
233
- return null;
234
- }
235
-
236
- /**
237
- * Check for fuzzy name match using Levenshtein distance.
238
- */
239
- private fuzzyNameMatch(queryName: string, pattern: PatternEntry): PatternMatch | null {
240
- const distance = this.levenshteinDistance(
241
- queryName.toLowerCase(),
242
- pattern.name.toLowerCase()
243
- );
244
-
245
- const maxLength = Math.max(queryName.length, pattern.name.length);
246
- const similarity = 1 - (distance / maxLength);
247
- const confidence = Math.round(similarity * 100);
248
-
249
- // Also check word overlap
250
- const queryWords = this.extractWords(queryName);
251
- const patternWords = this.extractWords(pattern.name);
252
- const wordOverlap = this.calculateWordOverlap(queryWords, patternWords);
253
-
254
- // Combine word overlap with character similarity
255
- const combinedConfidence = Math.round((confidence + wordOverlap * 100) / 2);
256
-
257
- if (combinedConfidence >= 60) {
258
- return {
259
- pattern,
260
- matchType: 'fuzzy',
261
- confidence: combinedConfidence,
262
- reason: `Similar name: "${pattern.name}" in ${pattern.file} (${combinedConfidence}% similar)`
263
- };
264
- }
265
-
266
- return null;
267
- }
268
-
269
- /**
270
- * Check for signature match.
271
- */
272
- private signatureMatch(querySignature: string, pattern: PatternEntry): PatternMatch | null {
273
- if (!pattern.signature) return null;
274
-
275
- // Normalize signatures for comparison
276
- const normalizedQuery = this.normalizeSignature(querySignature);
277
- const normalizedPattern = this.normalizeSignature(pattern.signature);
278
-
279
- if (normalizedQuery === normalizedPattern) {
280
- return {
281
- pattern,
282
- matchType: 'signature',
283
- confidence: 85,
284
- reason: `Matching signature: ${pattern.name}${pattern.signature} in ${pattern.file}`
285
- };
286
- }
287
-
288
- // Check parameter count and types
289
- const queryParams = this.extractParameters(querySignature);
290
- const patternParams = this.extractParameters(pattern.signature);
291
-
292
- if (queryParams.length === patternParams.length && queryParams.length > 0) {
293
- const typeMatch = this.compareParameterTypes(queryParams, patternParams);
294
- if (typeMatch >= 0.7) {
295
- const confidence = Math.round(60 + typeMatch * 20);
296
- return {
297
- pattern,
298
- matchType: 'signature',
299
- confidence,
300
- reason: `Similar signature (${confidence}% match): ${pattern.name} in ${pattern.file}`
301
- };
302
- }
303
- }
304
-
305
- return null;
306
- }
307
-
308
- /**
309
- * Check for keyword/semantic match.
310
- */
311
- private keywordMatch(queryKeywords: string[], pattern: PatternEntry): PatternMatch | null {
312
- if (!pattern.keywords || pattern.keywords.length === 0) return null;
313
-
314
- const normalizedQuery = queryKeywords.map(k => k.toLowerCase());
315
- const normalizedPattern = pattern.keywords.map(k => k.toLowerCase());
316
-
317
- const matches = normalizedQuery.filter(k => normalizedPattern.includes(k));
318
- const overlap = matches.length / Math.max(normalizedQuery.length, normalizedPattern.length);
319
-
320
- if (overlap >= 0.5) {
321
- const confidence = Math.round(50 + overlap * 40);
322
- return {
323
- pattern,
324
- matchType: 'semantic',
325
- confidence,
326
- reason: `Semantic match (keywords: ${matches.join(', ')}): ${pattern.name} in ${pattern.file}`
327
- };
328
- }
329
-
330
- return null;
331
- }
332
-
333
- /**
334
- * Check if an override exists for a pattern.
335
- */
336
- private hasOverride(name: string): boolean {
337
- const now = new Date();
338
-
339
- return this.overrides.some(override => {
340
- // Check expiration
341
- if (override.expiresAt && new Date(override.expiresAt) < now) {
342
- return false;
343
- }
344
-
345
- // Check pattern match (supports globs)
346
- if (override.pattern.includes('*')) {
347
- const regex = new RegExp(
348
- '^' + override.pattern.replace(/\*/g, '.*') + '$'
349
- );
350
- return regex.test(name);
351
- }
352
-
353
- return override.pattern === name;
354
- });
355
- }
356
-
357
- /**
358
- * Generate a suggestion message.
359
- */
360
- private generateSuggestion(match: PatternMatch): string {
361
- const { pattern } = match;
362
-
363
- if (pattern.exported) {
364
- return `Import "${pattern.name}" from "${pattern.file}" instead of creating a new one.`;
365
- }
366
-
367
- return `A similar pattern "${pattern.name}" exists in "${pattern.file}". Consider reusing it or extracting it to a shared location.`;
368
- }
369
-
370
- /**
371
- * Calculate Levenshtein distance between two strings.
372
- */
373
- private levenshteinDistance(a: string, b: string): number {
374
- const matrix: number[][] = [];
375
-
376
- for (let i = 0; i <= b.length; i++) {
377
- matrix[i] = [i];
378
- }
379
-
380
- for (let j = 0; j <= a.length; j++) {
381
- matrix[0][j] = j;
382
- }
383
-
384
- for (let i = 1; i <= b.length; i++) {
385
- for (let j = 1; j <= a.length; j++) {
386
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
387
- matrix[i][j] = matrix[i - 1][j - 1];
388
- } else {
389
- matrix[i][j] = Math.min(
390
- matrix[i - 1][j - 1] + 1, // substitution
391
- matrix[i][j - 1] + 1, // insertion
392
- matrix[i - 1][j] + 1 // deletion
393
- );
394
- }
395
- }
396
- }
397
-
398
- return matrix[b.length][a.length];
399
- }
400
-
401
- /**
402
- * Extract words from a camelCase/PascalCase name.
403
- */
404
- private extractWords(name: string): string[] {
405
- return name
406
- .replace(/([a-z])([A-Z])/g, '$1 $2')
407
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
408
- .toLowerCase()
409
- .split(/[\s_-]+/)
410
- .filter(w => w.length > 1);
411
- }
412
-
413
- /**
414
- * Calculate word overlap ratio.
415
- */
416
- private calculateWordOverlap(words1: string[], words2: string[]): number {
417
- const set1 = new Set(words1);
418
- const set2 = new Set(words2);
419
-
420
- let matches = 0;
421
- for (const word of set1) {
422
- if (set2.has(word)) {
423
- matches++;
424
- }
425
- }
426
-
427
- return matches / Math.max(set1.size, set2.size);
428
- }
429
-
430
- /**
431
- * Normalize a signature for comparison.
432
- */
433
- private normalizeSignature(sig: string): string {
434
- return sig
435
- .replace(/\s+/g, '') // Remove whitespace
436
- .replace(/:\s*\w+/g, '') // Remove type annotations
437
- .replace(/\?/g, '') // Remove optional markers
438
- .toLowerCase();
439
- }
440
-
441
- /**
442
- * Extract parameters from a signature.
443
- */
444
- private extractParameters(sig: string): string[] {
445
- const match = sig.match(/\(([^)]*)\)/);
446
- if (!match) return [];
447
-
448
- return match[1]
449
- .split(',')
450
- .map(p => p.trim())
451
- .filter(p => p.length > 0);
452
- }
453
-
454
- /**
455
- * Compare parameter types between two signatures.
456
- */
457
- private compareParameterTypes(params1: string[], params2: string[]): number {
458
- let matches = 0;
459
-
460
- for (let i = 0; i < params1.length; i++) {
461
- const type1 = this.extractType(params1[i]);
462
- const type2 = this.extractType(params2[i]);
463
-
464
- if (type1 === type2) {
465
- matches++;
466
- } else if (type1 && type2 && (type1.includes(type2) || type2.includes(type1))) {
467
- matches += 0.5;
468
- }
469
- }
470
-
471
- return matches / params1.length;
472
- }
473
-
474
- /**
475
- * Extract type from a parameter declaration.
476
- */
477
- private extractType(param: string): string | null {
478
- const match = param.match(/:\s*(\w+)/);
479
- return match ? match[1].toLowerCase() : null;
480
- }
481
- }
482
-
483
- /**
484
- * Quick helper to check for pattern duplicates.
485
- */
486
- export async function checkPatternDuplicate(
487
- index: PatternIndex,
488
- name: string,
489
- options: Partial<MatcherConfig> = {}
490
- ): Promise<PatternMatchResult> {
491
- const matcher = new PatternMatcher(index, options);
492
- return matcher.match({ name });
493
- }