tryassay 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/cli.js +21 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/catalog-push.d.ts +7 -0
  4. package/dist/commands/catalog-push.js +47 -0
  5. package/dist/commands/catalog-push.js.map +1 -0
  6. package/dist/commands/generate.js +1 -0
  7. package/dist/commands/generate.js.map +1 -1
  8. package/dist/commands/harvest.d.ts +9 -0
  9. package/dist/commands/harvest.js +76 -0
  10. package/dist/commands/harvest.js.map +1 -0
  11. package/dist/lib/__tests__/learned-rules.test.d.ts +1 -0
  12. package/dist/lib/__tests__/learned-rules.test.js +260 -0
  13. package/dist/lib/__tests__/learned-rules.test.js.map +1 -0
  14. package/dist/lib/__tests__/pr-harvester-types.test.d.ts +1 -0
  15. package/dist/lib/__tests__/pr-harvester-types.test.js +43 -0
  16. package/dist/lib/__tests__/pr-harvester-types.test.js.map +1 -0
  17. package/dist/lib/__tests__/pr-harvester.test.d.ts +1 -0
  18. package/dist/lib/__tests__/pr-harvester.test.js +341 -0
  19. package/dist/lib/__tests__/pr-harvester.test.js.map +1 -0
  20. package/dist/lib/__tests__/rule-harvester.test.d.ts +1 -0
  21. package/dist/lib/__tests__/rule-harvester.test.js +526 -0
  22. package/dist/lib/__tests__/rule-harvester.test.js.map +1 -0
  23. package/dist/lib/learned-rules/category-map.d.ts +28 -0
  24. package/dist/lib/learned-rules/category-map.js +110 -0
  25. package/dist/lib/learned-rules/category-map.js.map +1 -0
  26. package/dist/lib/learned-rules/index.d.ts +107 -0
  27. package/dist/lib/learned-rules/index.js +198 -0
  28. package/dist/lib/learned-rules/index.js.map +1 -0
  29. package/dist/lib/learned-rules/learned-catalog.d.ts +62 -0
  30. package/dist/lib/learned-rules/learned-catalog.js +161 -0
  31. package/dist/lib/learned-rules/learned-catalog.js.map +1 -0
  32. package/dist/lib/learned-rules/pattern-extractor.d.ts +25 -0
  33. package/dist/lib/learned-rules/pattern-extractor.js +351 -0
  34. package/dist/lib/learned-rules/pattern-extractor.js.map +1 -0
  35. package/dist/lib/learned-rules/rule-codifier.d.ts +41 -0
  36. package/dist/lib/learned-rules/rule-codifier.js +138 -0
  37. package/dist/lib/learned-rules/rule-codifier.js.map +1 -0
  38. package/dist/lib/learned-rules/starter-catalog.d.ts +16 -0
  39. package/dist/lib/learned-rules/starter-catalog.js +402 -0
  40. package/dist/lib/learned-rules/starter-catalog.js.map +1 -0
  41. package/dist/lib/learned-rules/types.d.ts +196 -0
  42. package/dist/lib/learned-rules/types.js +9 -0
  43. package/dist/lib/learned-rules/types.js.map +1 -0
  44. package/dist/lib/learned-rules/validation-harness.d.ts +26 -0
  45. package/dist/lib/learned-rules/validation-harness.js +260 -0
  46. package/dist/lib/learned-rules/validation-harness.js.map +1 -0
  47. package/dist/lib/rule-harvester/diff-parser.d.ts +9 -0
  48. package/dist/lib/rule-harvester/diff-parser.js +77 -0
  49. package/dist/lib/rule-harvester/diff-parser.js.map +1 -0
  50. package/dist/lib/rule-harvester/file-selector.d.ts +10 -0
  51. package/dist/lib/rule-harvester/file-selector.js +59 -0
  52. package/dist/lib/rule-harvester/file-selector.js.map +1 -0
  53. package/dist/lib/rule-harvester/ground-truth.d.ts +19 -0
  54. package/dist/lib/rule-harvester/ground-truth.js +156 -0
  55. package/dist/lib/rule-harvester/ground-truth.js.map +1 -0
  56. package/dist/lib/rule-harvester/harvest.d.ts +26 -0
  57. package/dist/lib/rule-harvester/harvest.js +307 -0
  58. package/dist/lib/rule-harvester/harvest.js.map +1 -0
  59. package/dist/lib/rule-harvester/pr-discovery.d.ts +49 -0
  60. package/dist/lib/rule-harvester/pr-discovery.js +168 -0
  61. package/dist/lib/rule-harvester/pr-discovery.js.map +1 -0
  62. package/dist/lib/rule-harvester/pr-harvest.d.ts +53 -0
  63. package/dist/lib/rule-harvester/pr-harvest.js +326 -0
  64. package/dist/lib/rule-harvester/pr-harvest.js.map +1 -0
  65. package/dist/lib/rule-harvester/progress.d.ts +13 -0
  66. package/dist/lib/rule-harvester/progress.js +50 -0
  67. package/dist/lib/rule-harvester/progress.js.map +1 -0
  68. package/dist/lib/rule-harvester/reporter.d.ts +35 -0
  69. package/dist/lib/rule-harvester/reporter.js +46 -0
  70. package/dist/lib/rule-harvester/reporter.js.map +1 -0
  71. package/dist/lib/rule-harvester/rule-generalizer.d.ts +25 -0
  72. package/dist/lib/rule-harvester/rule-generalizer.js +135 -0
  73. package/dist/lib/rule-harvester/rule-generalizer.js.map +1 -0
  74. package/dist/lib/rule-harvester/scanner.d.ts +20 -0
  75. package/dist/lib/rule-harvester/scanner.js +37 -0
  76. package/dist/lib/rule-harvester/scanner.js.map +1 -0
  77. package/dist/sdk/api-client.d.ts +65 -0
  78. package/dist/sdk/api-client.js +41 -0
  79. package/dist/sdk/api-client.js.map +1 -0
  80. package/dist/sdk/forward-verify.d.ts +3 -1
  81. package/dist/sdk/forward-verify.js +138 -5
  82. package/dist/sdk/forward-verify.js.map +1 -1
  83. package/dist/sdk/index.d.ts +1 -1
  84. package/dist/sdk/types.d.ts +21 -0
  85. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Pattern Extractor — extracts generalizable detection patterns from confirmed LLM findings.
3
+ *
4
+ * This is the core of Phase 1: Self-Expanding Formal Verification.
5
+ * When the LLM finds a real bug and it's confirmed, this module
6
+ * analyzes the code and finding to extract a regex pattern that
7
+ * would catch the same class of bug deterministically.
8
+ *
9
+ * The extracted pattern becomes a formal rule — no LLM needed for
10
+ * future instances of the same bug class.
11
+ */
12
+ import type { PatternExtractionInput, PatternExtractionResult } from './types.js';
13
+ /** Reset counter (for testing). */
14
+ export declare function resetPatternCounter(start?: number): void;
15
+ /**
16
+ * Extract a generalizable detection pattern from a confirmed LLM finding.
17
+ *
18
+ * @param input - The confirmed finding with code context.
19
+ * @returns The extraction result with pattern or failure reason.
20
+ */
21
+ export declare function extractPattern(input: PatternExtractionInput): PatternExtractionResult;
22
+ /**
23
+ * Get the list of claim categories that have extraction strategies.
24
+ */
25
+ export declare function getSupportedCategories(): string[];
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Pattern Extractor — extracts generalizable detection patterns from confirmed LLM findings.
3
+ *
4
+ * This is the core of Phase 1: Self-Expanding Formal Verification.
5
+ * When the LLM finds a real bug and it's confirmed, this module
6
+ * analyzes the code and finding to extract a regex pattern that
7
+ * would catch the same class of bug deterministically.
8
+ *
9
+ * The extracted pattern becomes a formal rule — no LLM needed for
10
+ * future instances of the same bug class.
11
+ */
12
+ // ── Language Helpers ──────────────────────────────────────────
13
+ function langGroup(language) {
14
+ const lang = language.toLowerCase();
15
+ if (['typescript', 'ts', 'tsx'].includes(lang))
16
+ return ['ts', 'tsx', 'typescript'];
17
+ if (['javascript', 'js', 'jsx'].includes(lang))
18
+ return ['js', 'jsx', 'javascript'];
19
+ if (['python', 'py'].includes(lang))
20
+ return ['py', 'python'];
21
+ if (['go', 'golang'].includes(lang))
22
+ return ['go', 'golang'];
23
+ return [lang];
24
+ }
25
+ function fileGlobForLang(language) {
26
+ const lang = language.toLowerCase();
27
+ if (['typescript', 'ts', 'tsx', 'javascript', 'js', 'jsx'].includes(lang)) {
28
+ return '**/*.{ts,tsx,js,jsx}';
29
+ }
30
+ if (['python', 'py'].includes(lang))
31
+ return '**/*.py';
32
+ if (['go', 'golang'].includes(lang))
33
+ return '**/*.go';
34
+ if (['rust', 'rs'].includes(lang))
35
+ return '**/*.rs';
36
+ if (['java'].includes(lang))
37
+ return '**/*.java';
38
+ return '**/*';
39
+ }
40
+ // ── ID Generation ────────────────────────────────────────────
41
+ let patternCounter = 0;
42
+ function nextPatternId() {
43
+ patternCounter++;
44
+ return `lp_${String(patternCounter).padStart(4, '0')}`;
45
+ }
46
+ /** Reset counter (for testing). */
47
+ export function resetPatternCounter(start = 0) {
48
+ patternCounter = start;
49
+ }
50
+ // ── Extraction Strategies ────────────────────────────────────
51
+ /**
52
+ * Strategy: Missing error handling.
53
+ * If the LLM found that error handling is missing, extract a pattern
54
+ * that checks for try/catch, .catch(), or error callbacks near
55
+ * async operations.
56
+ */
57
+ const missingErrorHandling = {
58
+ categories: ['error-handling'],
59
+ extract(input) {
60
+ const { claim, code, language } = input;
61
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
62
+ // Only handle "missing error handling" findings
63
+ if (!/(?:missing|no|lacks?|without|absent)\s+(?:error|exception)\s+(?:handling|catching|recovery)/i.test(text) &&
64
+ !/(?:does\s+not|doesn't)\s+(?:handle|catch)\s+(?:errors?|exceptions?|failures?)/i.test(text)) {
65
+ return null;
66
+ }
67
+ const lang = language.toLowerCase();
68
+ // Extract the specific operation that needs error handling
69
+ const asyncOpMatch = code.match(/(?:await\s+(\w+(?:\.\w+)*)\s*\()|(?:(\w+(?:\.\w+)*)\s*\(\s*\)\.then)/);
70
+ let pattern;
71
+ let description;
72
+ if (asyncOpMatch) {
73
+ const op = asyncOpMatch[1] || asyncOpMatch[2];
74
+ // Pattern: this specific async call without surrounding try/catch
75
+ pattern = `(?:await\\s+${escapeRegex(op)}\\s*\\()`;
76
+ description = `Async call to '${op}' without error handling`;
77
+ }
78
+ else if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
79
+ // Generic: any fetch/API call without error handling nearby
80
+ pattern = '\\bfetch\\s*\\([^)]*\\)(?![\\s\\S]{0,200}(?:\\.catch|try\\s*\\{|catch\\s*\\())';
81
+ description = 'Fetch call without error handling within 200 characters';
82
+ }
83
+ else if (['py', 'python'].includes(lang)) {
84
+ pattern = '(?:requests\\.(?:get|post|put|delete|patch)\\s*\\()(?![\\s\\S]{0,200}(?:except|try\\s*:))';
85
+ description = 'HTTP request without error handling within 200 characters';
86
+ }
87
+ else {
88
+ return null; // Can't generalize for this language
89
+ }
90
+ return {
91
+ id: nextPatternId(),
92
+ description,
93
+ kind: 'regex',
94
+ languages: langGroup(language),
95
+ regexPattern: pattern,
96
+ fileGlob: fileGlobForLang(language),
97
+ matchBehavior: 'presence_is_bad',
98
+ claimCategory: 'error-handling',
99
+ severity: claim.severity === 'low' ? 'medium' : claim.severity,
100
+ evidenceTemplate: `Missing error handling: {match} in {file}`,
101
+ };
102
+ },
103
+ };
104
+ /**
105
+ * Strategy: SQL injection via string concatenation.
106
+ * Extracts patterns for detecting SQL built from string concatenation.
107
+ */
108
+ const sqlInjection = {
109
+ categories: ['security'],
110
+ extract(input) {
111
+ const { claim, language } = input;
112
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
113
+ if (!/sql\s+injection|string\s+concatenat.*sql|unsanitized.*sql|sql.*interpolat/i.test(text)) {
114
+ return null;
115
+ }
116
+ const lang = language.toLowerCase();
117
+ let pattern;
118
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
119
+ // Template literals with SQL keywords and interpolation
120
+ pattern = '`[^`]*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\\s[^`]*\\$\\{(?!\\d)';
121
+ }
122
+ else if (['py', 'python'].includes(lang)) {
123
+ // f-strings or .format() with SQL
124
+ pattern = '(?:f[\'"][^\'"]*(?:SELECT|INSERT|UPDATE|DELETE)|\\.format\\s*\\([^)]*\\).*(?:SELECT|INSERT|UPDATE|DELETE))';
125
+ }
126
+ else {
127
+ // Generic string concat with SQL
128
+ pattern = '(?:SELECT|INSERT|UPDATE|DELETE|DROP)\\s[^"\']*\\+\\s*\\w+';
129
+ }
130
+ return {
131
+ id: nextPatternId(),
132
+ description: 'SQL query built with string interpolation/concatenation',
133
+ kind: 'regex',
134
+ languages: langGroup(language),
135
+ regexPattern: pattern,
136
+ fileGlob: fileGlobForLang(language),
137
+ matchBehavior: 'presence_is_bad',
138
+ claimCategory: 'security',
139
+ severity: 'critical',
140
+ evidenceTemplate: `SQL injection risk: {match} in {file}`,
141
+ };
142
+ },
143
+ };
144
+ /**
145
+ * Strategy: Missing null/undefined check.
146
+ * When LLM finds that a function doesn't validate its inputs for null.
147
+ */
148
+ const missingNullCheck = {
149
+ categories: ['edge-case', 'correctness'],
150
+ extract(input) {
151
+ const { claim, code, language } = input;
152
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
153
+ if (!/(?:null|undefined|nil|none)\s+(?:check|guard|validation|handling)/i.test(text) &&
154
+ !/(?:does\s+not|doesn't)\s+(?:check|validate|guard|handle)\s+(?:null|undefined|nil|none)/i.test(text)) {
155
+ return null;
156
+ }
157
+ // Try to extract the specific function/variable name
158
+ const funcMatch = code.match(/(?:function|const|let|var)\s+(\w+)\s*(?:=\s*(?:async\s+)?)?(?:\([^)]*\)|=>|\{)/);
159
+ if (!funcMatch)
160
+ return null;
161
+ const funcName = funcMatch[1];
162
+ const lang = language.toLowerCase();
163
+ let pattern;
164
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
165
+ // Function that takes params but doesn't check for null/undefined
166
+ pattern = `(?:function\\s+${escapeRegex(funcName)}|(?:const|let|var)\\s+${escapeRegex(funcName)}\\s*=)\\s*(?:async\\s+)?\\([^)]+\\)[^{]*\\{(?![\\s\\S]{0,300}(?:!==?\\s*(?:null|undefined)|===?\\s*(?:null|undefined)|\\?\\.))`;
167
+ }
168
+ else {
169
+ return null;
170
+ }
171
+ return {
172
+ id: nextPatternId(),
173
+ description: `Function '${funcName}' does not validate inputs for null/undefined`,
174
+ kind: 'regex',
175
+ languages: langGroup(language),
176
+ regexPattern: pattern,
177
+ fileGlob: fileGlobForLang(language),
178
+ matchBehavior: 'presence_is_bad',
179
+ claimCategory: claim.category,
180
+ severity: claim.severity === 'low' ? 'medium' : claim.severity,
181
+ evidenceTemplate: `Missing null check in function '{match}' in {file}`,
182
+ };
183
+ },
184
+ };
185
+ /**
186
+ * Strategy: Hardcoded secrets/credentials.
187
+ * When LLM finds API keys, tokens, or passwords hardcoded in source.
188
+ */
189
+ const hardcodedSecrets = {
190
+ categories: ['security'],
191
+ extract(input) {
192
+ const { claim } = input;
193
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
194
+ if (!/(?:hardcod|embed|literal)\w*\s+(?:secret|key|token|password|credential|api.?key)/i.test(text) &&
195
+ !/(?:secret|key|token|password|credential|api.?key)\s+(?:hardcod|embed|literal)/i.test(text)) {
196
+ return null;
197
+ }
198
+ return {
199
+ id: nextPatternId(),
200
+ description: 'Hardcoded secret, API key, token, or password in source code',
201
+ kind: 'regex',
202
+ languages: [], // All languages
203
+ regexPattern: "(?:api[_-]?key|secret|password|token|auth)\\s*[:=]\\s*['\"`][A-Za-z0-9+/=_-]{20,}['\"`]",
204
+ fileGlob: '**/*.{ts,tsx,js,jsx,py,go,java,rs}',
205
+ matchBehavior: 'presence_is_bad',
206
+ claimCategory: 'security',
207
+ severity: 'critical',
208
+ evidenceTemplate: `Hardcoded secret found: {match} in {file}`,
209
+ };
210
+ },
211
+ };
212
+ /**
213
+ * Strategy: Missing input validation.
214
+ * When LLM finds that user input isn't validated before use.
215
+ */
216
+ const missingInputValidation = {
217
+ categories: ['security', 'correctness'],
218
+ extract(input) {
219
+ const { claim, code, language } = input;
220
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
221
+ if (!/(?:missing|no|lacks?|without)\s+(?:input|user|request)\s+validation/i.test(text) &&
222
+ !/(?:does\s+not|doesn't)\s+(?:validate|sanitize|check)\s+(?:input|user|request)/i.test(text)) {
223
+ return null;
224
+ }
225
+ const lang = language.toLowerCase();
226
+ let pattern;
227
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
228
+ // Express/Node: req.body used without validation
229
+ if (code.includes('req.body') || code.includes('req.params') || code.includes('req.query')) {
230
+ pattern = '(?:req\\.(?:body|params|query))(?![\\s\\S]{0,100}(?:validate|parse|schema|zod|joi|yup))';
231
+ }
232
+ else {
233
+ return null;
234
+ }
235
+ }
236
+ else if (['py', 'python'].includes(lang)) {
237
+ // Flask/Django: request data used without validation
238
+ pattern = '(?:request\\.(?:json|form|args|data))(?![\\s\\S]{0,100}(?:validate|schema|marshmallow|pydantic))';
239
+ }
240
+ else {
241
+ return null;
242
+ }
243
+ return {
244
+ id: nextPatternId(),
245
+ description: 'User input used without validation or sanitization',
246
+ kind: 'regex',
247
+ languages: langGroup(language),
248
+ regexPattern: pattern,
249
+ fileGlob: fileGlobForLang(language),
250
+ matchBehavior: 'presence_is_bad',
251
+ claimCategory: 'security',
252
+ severity: 'high',
253
+ evidenceTemplate: `Unvalidated input: {match} in {file}`,
254
+ };
255
+ },
256
+ };
257
+ /**
258
+ * Strategy: Unhandled promise rejection.
259
+ * When LLM finds async operations without await or .catch().
260
+ */
261
+ const unhandledPromise = {
262
+ categories: ['error-handling', 'correctness'],
263
+ extract(input) {
264
+ const { claim, language } = input;
265
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
266
+ if (!/(?:unhandled|floating|detached)\s+promise/i.test(text) &&
267
+ !/promise\s+(?:not\s+)?(?:awaited|handled|caught)/i.test(text)) {
268
+ return null;
269
+ }
270
+ const lang = language.toLowerCase();
271
+ if (!['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
272
+ return null;
273
+ }
274
+ return {
275
+ id: nextPatternId(),
276
+ description: 'Promise-returning call without await or .catch()',
277
+ kind: 'regex',
278
+ languages: langGroup(language),
279
+ // Match function calls that likely return promises but aren't awaited
280
+ // Look for common async patterns without await
281
+ regexPattern: '(?<!await\\s)(?<!return\\s)\\b(?:fetch|axios\\.\\w+|\\w+\\.save|\\w+\\.delete|\\w+\\.update)\\s*\\([^)]*\\)(?!\\s*\\.(?:then|catch))',
282
+ fileGlob: fileGlobForLang(language),
283
+ matchBehavior: 'presence_is_bad',
284
+ claimCategory: 'error-handling',
285
+ severity: 'high',
286
+ evidenceTemplate: `Unhandled promise: {match} in {file}`,
287
+ };
288
+ },
289
+ };
290
+ // ── Registry ─────────────────────────────────────────────────
291
+ const STRATEGIES = [
292
+ missingErrorHandling,
293
+ sqlInjection,
294
+ missingNullCheck,
295
+ hardcodedSecrets,
296
+ missingInputValidation,
297
+ unhandledPromise,
298
+ ];
299
+ // ── Helpers ──────────────────────────────────────────────────
300
+ function escapeRegex(str) {
301
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
302
+ }
303
+ // ── Public API ───────────────────────────────────────────────
304
+ /**
305
+ * Extract a generalizable detection pattern from a confirmed LLM finding.
306
+ *
307
+ * @param input - The confirmed finding with code context.
308
+ * @returns The extraction result with pattern or failure reason.
309
+ */
310
+ export function extractPattern(input) {
311
+ // Only process confirmed failures
312
+ if (input.verification.verdict !== 'FAIL') {
313
+ return {
314
+ success: false,
315
+ failureReason: `Verdict is '${input.verification.verdict}', not 'FAIL'. Only confirmed failures can generate rules.`,
316
+ };
317
+ }
318
+ // Try each strategy in order
319
+ for (const strategy of STRATEGIES) {
320
+ if (!strategy.categories.includes(input.claim.category))
321
+ continue;
322
+ const pattern = strategy.extract(input);
323
+ if (pattern) {
324
+ // Validate the regex compiles
325
+ try {
326
+ new RegExp(pattern.regexPattern);
327
+ }
328
+ catch {
329
+ continue; // Skip patterns that don't compile
330
+ }
331
+ return { success: true, pattern };
332
+ }
333
+ }
334
+ return {
335
+ success: false,
336
+ failureReason: `No extraction strategy matched claim category '${input.claim.category}' with description: ${input.claim.description}`,
337
+ };
338
+ }
339
+ /**
340
+ * Get the list of claim categories that have extraction strategies.
341
+ */
342
+ export function getSupportedCategories() {
343
+ const categories = new Set();
344
+ for (const strategy of STRATEGIES) {
345
+ for (const cat of strategy.categories) {
346
+ categories.add(cat);
347
+ }
348
+ }
349
+ return [...categories];
350
+ }
351
+ //# sourceMappingURL=pattern-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-extractor.js","sourceRoot":"","sources":["../../../src/lib/learned-rules/pattern-extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Rule Codifier — converts extracted patterns into executable formal checks.
3
+ *
4
+ * Takes an ExtractedPattern and produces a LearnedRule that can run
5
+ * alongside hand-crafted rules in the formal verifier.
6
+ *
7
+ * Current implementation: regex-based rules only.
8
+ * Future: AST patterns (tree-sitter), type constraints (ts-morph).
9
+ */
10
+ import type { ExtractedPattern, LearnedRule, PatternExtractionInput } from './types.js';
11
+ /** Reset counter (for testing). */
12
+ export declare function resetRuleCounter(start?: number): void;
13
+ /**
14
+ * Create a new learned rule from an extracted pattern and its source finding.
15
+ *
16
+ * The rule starts in 'candidate' status and must pass validation
17
+ * before being promoted to 'validated' and then 'promoted'.
18
+ */
19
+ export declare function codifyRule(pattern: ExtractedPattern, input: PatternExtractionInput): LearnedRule;
20
+ /**
21
+ * Execute a learned rule against a code string.
22
+ *
23
+ * Returns true if the rule fires (detects a potential issue).
24
+ */
25
+ export declare function executeRule(rule: LearnedRule, code: string, language: string): {
26
+ fires: boolean;
27
+ matches: string[];
28
+ };
29
+ /**
30
+ * Merge a new source finding into an existing rule.
31
+ * This happens when the same pattern is extracted from a different finding.
32
+ */
33
+ export declare function mergeSourceFinding(rule: LearnedRule, input: PatternExtractionInput): LearnedRule;
34
+ /**
35
+ * Update rule status through the lifecycle.
36
+ */
37
+ export declare function updateRuleStatus(rule: LearnedRule, status: LearnedRule['status']): LearnedRule;
38
+ /**
39
+ * Record that a rule fired and was confirmed/rejected by a human or auto-confirmation.
40
+ */
41
+ export declare function recordRuleFire(rule: LearnedRule, wasCorrect: boolean): LearnedRule;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Rule Codifier — converts extracted patterns into executable formal checks.
3
+ *
4
+ * Takes an ExtractedPattern and produces a LearnedRule that can run
5
+ * alongside hand-crafted rules in the formal verifier.
6
+ *
7
+ * Current implementation: regex-based rules only.
8
+ * Future: AST patterns (tree-sitter), type constraints (ts-morph).
9
+ */
10
+ // ── Rule ID Generation ───────────────────────────────────────
11
+ let ruleCounter = 0;
12
+ function nextRuleId() {
13
+ ruleCounter++;
14
+ return `lr_${String(ruleCounter).padStart(4, '0')}`;
15
+ }
16
+ /** Reset counter (for testing). */
17
+ export function resetRuleCounter(start = 0) {
18
+ ruleCounter = start;
19
+ }
20
+ // ── Rule Creation ────────────────────────────────────────────
21
+ /**
22
+ * Create a new learned rule from an extracted pattern and its source finding.
23
+ *
24
+ * The rule starts in 'candidate' status and must pass validation
25
+ * before being promoted to 'validated' and then 'promoted'.
26
+ */
27
+ export function codifyRule(pattern, input) {
28
+ const now = new Date().toISOString();
29
+ const sourceFinding = {
30
+ claimId: input.claim.id,
31
+ claimDescription: input.claim.description,
32
+ codeSnippet: truncateCode(input.code, 500),
33
+ filePath: input.filePath,
34
+ language: input.language,
35
+ timestamp: now,
36
+ };
37
+ return {
38
+ id: nextRuleId(),
39
+ pattern,
40
+ status: 'candidate',
41
+ createdAt: now,
42
+ updatedAt: now,
43
+ fireCount: 0,
44
+ truePositiveCount: 0,
45
+ falsePositiveCount: 0,
46
+ sourceFindings: [sourceFinding],
47
+ };
48
+ }
49
+ /**
50
+ * Execute a learned rule against a code string.
51
+ *
52
+ * Returns true if the rule fires (detects a potential issue).
53
+ */
54
+ export function executeRule(rule, code, language) {
55
+ const pattern = rule.pattern;
56
+ // Check language applicability
57
+ if (pattern.languages.length > 0) {
58
+ const langLower = language.toLowerCase();
59
+ if (!pattern.languages.some(l => l.toLowerCase() === langLower)) {
60
+ return { fires: false, matches: [] };
61
+ }
62
+ }
63
+ // Currently only regex patterns are supported
64
+ if (pattern.kind !== 'regex' || !pattern.regexPattern) {
65
+ return { fires: false, matches: [] };
66
+ }
67
+ try {
68
+ const regex = new RegExp(pattern.regexPattern, 'gi');
69
+ const matches = [];
70
+ let match;
71
+ while ((match = regex.exec(code)) !== null) {
72
+ matches.push(match[0]);
73
+ // Prevent infinite loops on zero-length matches
74
+ if (match[0].length === 0)
75
+ regex.lastIndex++;
76
+ }
77
+ if (pattern.matchBehavior === 'presence_is_bad') {
78
+ return { fires: matches.length > 0, matches };
79
+ }
80
+ else {
81
+ // absence_is_bad: fires when pattern is NOT found
82
+ return { fires: matches.length === 0, matches: [] };
83
+ }
84
+ }
85
+ catch {
86
+ // Invalid regex — rule should be rejected
87
+ return { fires: false, matches: [] };
88
+ }
89
+ }
90
+ /**
91
+ * Merge a new source finding into an existing rule.
92
+ * This happens when the same pattern is extracted from a different finding.
93
+ */
94
+ export function mergeSourceFinding(rule, input) {
95
+ const now = new Date().toISOString();
96
+ const newFinding = {
97
+ claimId: input.claim.id,
98
+ claimDescription: input.claim.description,
99
+ codeSnippet: truncateCode(input.code, 500),
100
+ filePath: input.filePath,
101
+ language: input.language,
102
+ timestamp: now,
103
+ };
104
+ return {
105
+ ...rule,
106
+ updatedAt: now,
107
+ sourceFindings: [...rule.sourceFindings, newFinding],
108
+ };
109
+ }
110
+ /**
111
+ * Update rule status through the lifecycle.
112
+ */
113
+ export function updateRuleStatus(rule, status) {
114
+ return {
115
+ ...rule,
116
+ status,
117
+ updatedAt: new Date().toISOString(),
118
+ };
119
+ }
120
+ /**
121
+ * Record that a rule fired and was confirmed/rejected by a human or auto-confirmation.
122
+ */
123
+ export function recordRuleFire(rule, wasCorrect) {
124
+ return {
125
+ ...rule,
126
+ fireCount: rule.fireCount + 1,
127
+ truePositiveCount: rule.truePositiveCount + (wasCorrect ? 1 : 0),
128
+ falsePositiveCount: rule.falsePositiveCount + (wasCorrect ? 0 : 1),
129
+ updatedAt: new Date().toISOString(),
130
+ };
131
+ }
132
+ // ── Helpers ──────────────────────────────────────────────────
133
+ function truncateCode(code, maxLength) {
134
+ if (code.length <= maxLength)
135
+ return code;
136
+ return code.slice(0, maxLength) + '\n// ... truncated';
137
+ }
138
+ //# sourceMappingURL=rule-codifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-codifier.js","sourceRoot":"","sources":["../../../src/lib/learned-rules/rule-codifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Starter Catalog — ~15 high-signal learned rules that ship with the npm package.
3
+ *
4
+ * These rules were curated from 214 rules harvested from real PR diffs in
5
+ * popular open-source TypeScript projects. Selection criteria:
6
+ * - Valid, compilable regex
7
+ * - High or critical severity preferred
8
+ * - General-purpose (not tied to a specific project)
9
+ * - Good coverage across categories (security, error handling, etc.)
10
+ * - Useful fixDescription
11
+ *
12
+ * All starter rules have `source: "bundled"` to distinguish them from
13
+ * user-harvested rules. Local rules with the same ID take precedence.
14
+ */
15
+ import type { LearnedRule } from './types.js';
16
+ export declare const STARTER_RULES: readonly LearnedRule[];