tryassay 0.29.0 → 0.30.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 (79) hide show
  1. package/dist/cli.js +10 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/generate.js +1 -0
  4. package/dist/commands/generate.js.map +1 -1
  5. package/dist/commands/harvest.d.ts +9 -0
  6. package/dist/commands/harvest.js +76 -0
  7. package/dist/commands/harvest.js.map +1 -0
  8. package/dist/lib/__tests__/learned-rules.test.d.ts +1 -0
  9. package/dist/lib/__tests__/learned-rules.test.js +260 -0
  10. package/dist/lib/__tests__/learned-rules.test.js.map +1 -0
  11. package/dist/lib/__tests__/pr-harvester-types.test.d.ts +1 -0
  12. package/dist/lib/__tests__/pr-harvester-types.test.js +43 -0
  13. package/dist/lib/__tests__/pr-harvester-types.test.js.map +1 -0
  14. package/dist/lib/__tests__/pr-harvester.test.d.ts +1 -0
  15. package/dist/lib/__tests__/pr-harvester.test.js +341 -0
  16. package/dist/lib/__tests__/pr-harvester.test.js.map +1 -0
  17. package/dist/lib/__tests__/rule-harvester.test.d.ts +1 -0
  18. package/dist/lib/__tests__/rule-harvester.test.js +526 -0
  19. package/dist/lib/__tests__/rule-harvester.test.js.map +1 -0
  20. package/dist/lib/learned-rules/category-map.d.ts +28 -0
  21. package/dist/lib/learned-rules/category-map.js +110 -0
  22. package/dist/lib/learned-rules/category-map.js.map +1 -0
  23. package/dist/lib/learned-rules/index.d.ts +105 -0
  24. package/dist/lib/learned-rules/index.js +198 -0
  25. package/dist/lib/learned-rules/index.js.map +1 -0
  26. package/dist/lib/learned-rules/learned-catalog.d.ts +62 -0
  27. package/dist/lib/learned-rules/learned-catalog.js +161 -0
  28. package/dist/lib/learned-rules/learned-catalog.js.map +1 -0
  29. package/dist/lib/learned-rules/pattern-extractor.d.ts +25 -0
  30. package/dist/lib/learned-rules/pattern-extractor.js +351 -0
  31. package/dist/lib/learned-rules/pattern-extractor.js.map +1 -0
  32. package/dist/lib/learned-rules/rule-codifier.d.ts +41 -0
  33. package/dist/lib/learned-rules/rule-codifier.js +138 -0
  34. package/dist/lib/learned-rules/rule-codifier.js.map +1 -0
  35. package/dist/lib/learned-rules/starter-catalog.d.ts +16 -0
  36. package/dist/lib/learned-rules/starter-catalog.js +402 -0
  37. package/dist/lib/learned-rules/starter-catalog.js.map +1 -0
  38. package/dist/lib/learned-rules/types.d.ts +196 -0
  39. package/dist/lib/learned-rules/types.js +9 -0
  40. package/dist/lib/learned-rules/types.js.map +1 -0
  41. package/dist/lib/learned-rules/validation-harness.d.ts +26 -0
  42. package/dist/lib/learned-rules/validation-harness.js +260 -0
  43. package/dist/lib/learned-rules/validation-harness.js.map +1 -0
  44. package/dist/lib/rule-harvester/diff-parser.d.ts +9 -0
  45. package/dist/lib/rule-harvester/diff-parser.js +77 -0
  46. package/dist/lib/rule-harvester/diff-parser.js.map +1 -0
  47. package/dist/lib/rule-harvester/file-selector.d.ts +10 -0
  48. package/dist/lib/rule-harvester/file-selector.js +59 -0
  49. package/dist/lib/rule-harvester/file-selector.js.map +1 -0
  50. package/dist/lib/rule-harvester/ground-truth.d.ts +19 -0
  51. package/dist/lib/rule-harvester/ground-truth.js +156 -0
  52. package/dist/lib/rule-harvester/ground-truth.js.map +1 -0
  53. package/dist/lib/rule-harvester/harvest.d.ts +26 -0
  54. package/dist/lib/rule-harvester/harvest.js +307 -0
  55. package/dist/lib/rule-harvester/harvest.js.map +1 -0
  56. package/dist/lib/rule-harvester/pr-discovery.d.ts +49 -0
  57. package/dist/lib/rule-harvester/pr-discovery.js +168 -0
  58. package/dist/lib/rule-harvester/pr-discovery.js.map +1 -0
  59. package/dist/lib/rule-harvester/pr-harvest.d.ts +53 -0
  60. package/dist/lib/rule-harvester/pr-harvest.js +326 -0
  61. package/dist/lib/rule-harvester/pr-harvest.js.map +1 -0
  62. package/dist/lib/rule-harvester/progress.d.ts +13 -0
  63. package/dist/lib/rule-harvester/progress.js +50 -0
  64. package/dist/lib/rule-harvester/progress.js.map +1 -0
  65. package/dist/lib/rule-harvester/reporter.d.ts +35 -0
  66. package/dist/lib/rule-harvester/reporter.js +46 -0
  67. package/dist/lib/rule-harvester/reporter.js.map +1 -0
  68. package/dist/lib/rule-harvester/rule-generalizer.d.ts +25 -0
  69. package/dist/lib/rule-harvester/rule-generalizer.js +135 -0
  70. package/dist/lib/rule-harvester/rule-generalizer.js.map +1 -0
  71. package/dist/lib/rule-harvester/scanner.d.ts +20 -0
  72. package/dist/lib/rule-harvester/scanner.js +37 -0
  73. package/dist/lib/rule-harvester/scanner.js.map +1 -0
  74. package/dist/sdk/forward-verify.d.ts +3 -1
  75. package/dist/sdk/forward-verify.js +68 -5
  76. package/dist/sdk/forward-verify.js.map +1 -1
  77. package/dist/sdk/index.d.ts +1 -1
  78. package/dist/sdk/types.d.ts +21 -0
  79. package/package.json +1 -1
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Validation Harness — tests learned rules before promotion to the formal catalog.
3
+ *
4
+ * Every auto-generated rule must pass validation:
5
+ * 1. Fires correctly on the ORIGINAL code that triggered it
6
+ * 2. Fires correctly on synthetic "known-bad" variations
7
+ * 3. Does NOT fire on synthetic "known-good" variations
8
+ *
9
+ * Only rules with precision >= 0.8 and recall >= 0.5 are promoted.
10
+ */
11
+ import { executeRule } from './rule-codifier.js';
12
+ // ── Thresholds ───────────────────────────────────────────────
13
+ const MIN_PRECISION = 0.8; // At most 20% false positive rate
14
+ const MIN_RECALL = 0.5; // Catch at least half the real bugs
15
+ const MIN_TEST_CASES = 3; // At least 3 test cases required
16
+ // ── Synthetic Test Case Generation ───────────────────────────
17
+ /**
18
+ * Generate synthetic test cases from the original source finding.
19
+ * Creates variations of the original code that should and shouldn't
20
+ * trigger the rule.
21
+ */
22
+ function generateTestCases(rule) {
23
+ const cases = [];
24
+ const pattern = rule.pattern;
25
+ // Test 1: Original code should trigger the rule
26
+ for (const finding of rule.sourceFindings) {
27
+ cases.push({
28
+ description: `Original finding: ${finding.claimDescription}`,
29
+ code: finding.codeSnippet,
30
+ shouldMatch: pattern.matchBehavior === 'presence_is_bad',
31
+ didMatch: false, // Will be filled during validation
32
+ });
33
+ }
34
+ // Test 2: Generate "fixed" version that should NOT trigger
35
+ for (const finding of rule.sourceFindings) {
36
+ const fixedCode = generateFixedCode(finding.codeSnippet, pattern, finding.language);
37
+ if (fixedCode) {
38
+ cases.push({
39
+ description: `Fixed version of: ${finding.claimDescription}`,
40
+ code: fixedCode,
41
+ shouldMatch: false,
42
+ didMatch: false,
43
+ });
44
+ }
45
+ }
46
+ // Test 3: Generate additional "bad" variations
47
+ for (const finding of rule.sourceFindings) {
48
+ const variation = generateBadVariation(finding.codeSnippet, pattern, finding.language);
49
+ if (variation) {
50
+ cases.push({
51
+ description: `Bad variation of: ${finding.claimDescription}`,
52
+ code: variation,
53
+ shouldMatch: pattern.matchBehavior === 'presence_is_bad',
54
+ didMatch: false,
55
+ });
56
+ }
57
+ }
58
+ // Test 4: Known-good code that should never trigger
59
+ const safeCode = generateSafeCode(pattern, rule.sourceFindings[0]?.language ?? 'typescript');
60
+ if (safeCode) {
61
+ cases.push({
62
+ description: 'Known-good code (should not trigger)',
63
+ code: safeCode,
64
+ shouldMatch: false,
65
+ didMatch: false,
66
+ });
67
+ }
68
+ return cases;
69
+ }
70
+ /**
71
+ * Generate a "fixed" version of buggy code.
72
+ * Applies the obvious fix based on the pattern category.
73
+ */
74
+ function generateFixedCode(code, pattern, language) {
75
+ const lang = language.toLowerCase();
76
+ const category = pattern.claimCategory;
77
+ if (category === 'error-handling') {
78
+ // Wrap in try/catch
79
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
80
+ return `try {\n${code}\n} catch (error) {\n console.error('Operation failed:', error);\n throw error;\n}`;
81
+ }
82
+ if (['py', 'python'].includes(lang)) {
83
+ const indented = code.split('\n').map(l => ' ' + l).join('\n');
84
+ return `try:\n${indented}\nexcept Exception as e:\n raise`;
85
+ }
86
+ }
87
+ if (category === 'security' && pattern.regexPattern?.includes('SELECT|INSERT|UPDATE|DELETE')) {
88
+ // Replace string interpolation with parameterized query
89
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
90
+ return code
91
+ .replace(/`([^`]*)\$\{(\w+)\}([^`]*)`/g, "'$1?' + ',$3'")
92
+ .replace(/(['"])([^'"]*)\1\s*\+\s*(\w+)/g, "'$2?'")
93
+ + '\n// Parameters passed separately: [userId]';
94
+ }
95
+ }
96
+ if (category === 'edge-case' || category === 'correctness') {
97
+ // Add null check before the function body
98
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
99
+ const funcMatch = code.match(/(function\s+\w+\s*\([^)]*\)\s*\{)/);
100
+ if (funcMatch) {
101
+ return code.replace(funcMatch[1], funcMatch[1] + '\n if (arguments[0] === null || arguments[0] === undefined) throw new Error("Invalid input");');
102
+ }
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+ /**
108
+ * Generate a "bad" variation of buggy code.
109
+ * Creates a similar but different instance of the same bug class.
110
+ */
111
+ function generateBadVariation(code, pattern, language) {
112
+ const lang = language.toLowerCase();
113
+ if (pattern.claimCategory === 'error-handling') {
114
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
115
+ // Generate another async call without error handling
116
+ return `async function processData(input: unknown) {\n const result = await fetch('/api/data');\n return result.json();\n}`;
117
+ }
118
+ }
119
+ if (pattern.claimCategory === 'security') {
120
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
121
+ return 'const query = `SELECT * FROM users WHERE id = ${userId}`;';
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+ /**
127
+ * Generate known-good code that should never trigger the rule.
128
+ */
129
+ function generateSafeCode(pattern, language) {
130
+ const lang = language.toLowerCase();
131
+ if (pattern.claimCategory === 'error-handling') {
132
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
133
+ return `async function safeOp() {\n try {\n const result = await fetch('/api/data');\n return await result.json();\n } catch (error) {\n console.error('Failed:', error);\n throw error;\n }\n}`;
134
+ }
135
+ }
136
+ if (pattern.claimCategory === 'security') {
137
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
138
+ return "const result = await db.query('SELECT * FROM users WHERE id = ?', [userId]);";
139
+ }
140
+ }
141
+ if (pattern.claimCategory === 'edge-case' || pattern.claimCategory === 'correctness') {
142
+ if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
143
+ return `function safeProcess(input: string | null) {\n if (input === null || input === undefined) {\n throw new Error('Input required');\n }\n return input.trim();\n}`;
144
+ }
145
+ }
146
+ return null;
147
+ }
148
+ // ── Validation Execution ─────────────────────────────────────
149
+ /**
150
+ * Validate a learned rule against synthetic test cases.
151
+ *
152
+ * @param rule - The candidate rule to validate.
153
+ * @returns The validation result with precision/recall metrics.
154
+ */
155
+ export function validateRule(rule) {
156
+ const testCases = generateTestCases(rule);
157
+ // PR-sourced rules: if we have fewer synthetic cases but the regex fires on
158
+ // the original buggy code, accept it. The merged PR is the human confirmation.
159
+ if (testCases.length < MIN_TEST_CASES && rule.source === 'pr') {
160
+ return validatePRRule(rule, testCases);
161
+ }
162
+ if (testCases.length < MIN_TEST_CASES) {
163
+ return {
164
+ passed: false,
165
+ truePositives: 0,
166
+ falsePositives: 0,
167
+ trueNegatives: 0,
168
+ falseNegatives: 0,
169
+ precision: 0,
170
+ recall: 0,
171
+ validatedAt: new Date().toISOString(),
172
+ testCases,
173
+ };
174
+ }
175
+ // Run each test case
176
+ const language = rule.sourceFindings[0]?.language ?? 'typescript';
177
+ const executedCases = [];
178
+ let tp = 0, fp = 0, tn = 0, fn = 0;
179
+ for (const testCase of testCases) {
180
+ const { fires } = executeRule(rule, testCase.code, language);
181
+ const executed = {
182
+ ...testCase,
183
+ didMatch: fires,
184
+ };
185
+ executedCases.push(executed);
186
+ if (testCase.shouldMatch && fires)
187
+ tp++;
188
+ else if (testCase.shouldMatch && !fires)
189
+ fn++;
190
+ else if (!testCase.shouldMatch && fires)
191
+ fp++;
192
+ else
193
+ tn++;
194
+ }
195
+ const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
196
+ const recall = tp + fn > 0 ? tp / (tp + fn) : 0;
197
+ const passed = precision >= MIN_PRECISION && recall >= MIN_RECALL;
198
+ return {
199
+ passed,
200
+ truePositives: tp,
201
+ falsePositives: fp,
202
+ trueNegatives: tn,
203
+ falseNegatives: fn,
204
+ precision,
205
+ recall,
206
+ validatedAt: new Date().toISOString(),
207
+ testCases: executedCases,
208
+ };
209
+ }
210
+ /**
211
+ * Lightweight validation for PR-sourced rules.
212
+ * The merged PR is the human confirmation — we just need to verify
213
+ * the regex actually fires on the buggy code from the diff.
214
+ */
215
+ function validatePRRule(rule, testCases) {
216
+ const language = rule.sourceFindings[0]?.language ?? 'typescript';
217
+ let firesOnOriginal = false;
218
+ // Test: does the regex fire on any source finding's code snippet?
219
+ for (const finding of rule.sourceFindings) {
220
+ if (!finding.codeSnippet || finding.codeSnippet.startsWith('PR #'))
221
+ continue;
222
+ const { fires } = executeRule(rule, finding.codeSnippet, language);
223
+ if (fires) {
224
+ firesOnOriginal = true;
225
+ break;
226
+ }
227
+ }
228
+ // Also check: does it compile as valid regex?
229
+ let regexValid = false;
230
+ try {
231
+ if (rule.pattern.regexPattern) {
232
+ new RegExp(rule.pattern.regexPattern, 'gi');
233
+ regexValid = true;
234
+ }
235
+ }
236
+ catch { /* invalid */ }
237
+ const passed = firesOnOriginal && regexValid;
238
+ return {
239
+ passed,
240
+ truePositives: firesOnOriginal ? 1 : 0,
241
+ falsePositives: 0,
242
+ trueNegatives: 0,
243
+ falseNegatives: firesOnOriginal ? 0 : 1,
244
+ precision: firesOnOriginal ? 1.0 : 0,
245
+ recall: firesOnOriginal ? 1.0 : 0,
246
+ validatedAt: new Date().toISOString(),
247
+ testCases,
248
+ };
249
+ }
250
+ /**
251
+ * Get the validation thresholds.
252
+ */
253
+ export function getValidationThresholds() {
254
+ return {
255
+ minPrecision: MIN_PRECISION,
256
+ minRecall: MIN_RECALL,
257
+ minTestCases: MIN_TEST_CASES,
258
+ };
259
+ }
260
+ //# sourceMappingURL=validation-harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-harness.js","sourceRoot":"","sources":["../../../src/lib/learned-rules/validation-harness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { DiffHunk } from '../learned-rules/types.js';
2
+ /**
3
+ * Parse a GitHub PR file `patch` string (unified diff format) into typed hunks.
4
+ */
5
+ export declare function parsePatch(filePath: string, patch: string): DiffHunk[];
6
+ /**
7
+ * Filter out noise hunks: import-only, comment-only, whitespace-only, <2 meaningful lines.
8
+ */
9
+ export declare function filterHunks(hunks: DiffHunk[]): DiffHunk[];
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Parse a GitHub PR file `patch` string (unified diff format) into typed hunks.
3
+ */
4
+ export function parsePatch(filePath, patch) {
5
+ if (!patch)
6
+ return [];
7
+ const hunks = [];
8
+ const lines = patch.split('\n');
9
+ let currentHunk = null;
10
+ for (const line of lines) {
11
+ const headerMatch = line.match(/^@@ -(\d+)/);
12
+ if (headerMatch) {
13
+ if (currentHunk) {
14
+ hunks.push(makeHunk(filePath, currentHunk));
15
+ }
16
+ currentHunk = {
17
+ removed: [],
18
+ added: [],
19
+ context: [],
20
+ startLine: parseInt(headerMatch[1], 10),
21
+ };
22
+ continue;
23
+ }
24
+ if (!currentHunk)
25
+ continue;
26
+ if (line.startsWith('-')) {
27
+ currentHunk.removed.push(line.slice(1));
28
+ }
29
+ else if (line.startsWith('+')) {
30
+ currentHunk.added.push(line.slice(1));
31
+ }
32
+ else if (line.startsWith(' ')) {
33
+ currentHunk.context.push(line);
34
+ }
35
+ }
36
+ if (currentHunk) {
37
+ hunks.push(makeHunk(filePath, currentHunk));
38
+ }
39
+ return hunks;
40
+ }
41
+ function makeHunk(file, raw) {
42
+ return {
43
+ file,
44
+ removedLines: raw.removed,
45
+ addedLines: raw.added,
46
+ context: raw.context,
47
+ startLine: raw.startLine,
48
+ };
49
+ }
50
+ /**
51
+ * Filter out noise hunks: import-only, comment-only, whitespace-only, <2 meaningful lines.
52
+ */
53
+ export function filterHunks(hunks) {
54
+ return hunks.filter((hunk) => {
55
+ const removed = hunk.removedLines;
56
+ const added = hunk.addedLines;
57
+ if (Math.max(removed.length, added.length) < 2)
58
+ return false;
59
+ const allImports = [...removed, ...added].every((l) => l.trim().startsWith('import ') || (l.trim().startsWith('export ') && l.includes('from')));
60
+ if (allImports)
61
+ return false;
62
+ const allComments = [...removed, ...added].every((l) => {
63
+ const trimmed = l.trim();
64
+ return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*') || trimmed === '';
65
+ });
66
+ if (allComments)
67
+ return false;
68
+ const removedNormalized = removed.map((l) => l.replace(/\s+/g, ' ').trim());
69
+ const addedNormalized = added.map((l) => l.replace(/\s+/g, ' ').trim());
70
+ if (removedNormalized.length === addedNormalized.length &&
71
+ removedNormalized.every((l, i) => l === addedNormalized[i])) {
72
+ return false;
73
+ }
74
+ return true;
75
+ });
76
+ }
77
+ //# sourceMappingURL=diff-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-parser.js","sourceRoot":"","sources":["../../../src/lib/rule-harvester/diff-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export type FileCandidate = {
2
+ path: string;
3
+ lineCount: number;
4
+ };
5
+ export type SelectedFile = {
6
+ path: string;
7
+ reason: string;
8
+ priority: number;
9
+ };
10
+ export declare function selectFiles(candidates: FileCandidate[]): SelectedFile[];
@@ -0,0 +1,59 @@
1
+ const MAX_FILES = 20;
2
+ const MAX_LINE_COUNT = 800;
3
+ const SKIP_PATTERNS = [
4
+ /\.test\./,
5
+ /\.spec\./,
6
+ /__tests__\//,
7
+ /\.d\.ts$/,
8
+ /\.config\.(js|ts)$/,
9
+ /tsconfig/,
10
+ /package\.json$/,
11
+ /\/generated\//,
12
+ /node_modules/,
13
+ /tailwind\.config/,
14
+ /next\.config/,
15
+ /vite\.config/,
16
+ /jest\.config/,
17
+ /vitest\.config/,
18
+ /eslint\.config/,
19
+ /prettier\.config/,
20
+ /\.eslintrc/,
21
+ /\.prettierrc/,
22
+ ];
23
+ function shouldSkip(path) {
24
+ return SKIP_PATTERNS.some((pattern) => pattern.test(path));
25
+ }
26
+ function assignPriority(path) {
27
+ if (/\/api\/|\/routes\/|\/middleware\//.test(path)) {
28
+ return { priority: 1, reason: 'API/route/middleware file' };
29
+ }
30
+ if (/\/auth/.test(path)) {
31
+ return { priority: 2, reason: 'Auth-related file' };
32
+ }
33
+ if (/\/db\/|\/database\/|\/models\/|\/entities\//.test(path)) {
34
+ return { priority: 3, reason: 'Database/model file' };
35
+ }
36
+ if (/\/services\/|\/handlers\/|\/controllers\//.test(path)) {
37
+ return { priority: 4, reason: 'Service/handler/controller file' };
38
+ }
39
+ if (/\/lib\/|\/utils\/|\/helpers\//.test(path)) {
40
+ return { priority: 5, reason: 'Library/utility/helper file' };
41
+ }
42
+ if (/\.ts$/.test(path)) {
43
+ return { priority: 6, reason: 'TypeScript file' };
44
+ }
45
+ if (/\.tsx$/.test(path)) {
46
+ return { priority: 7, reason: 'TypeScript React file' };
47
+ }
48
+ return { priority: 99, reason: 'Other file' };
49
+ }
50
+ export function selectFiles(candidates) {
51
+ const filtered = candidates.filter((c) => !shouldSkip(c.path) && c.lineCount <= MAX_LINE_COUNT);
52
+ const scored = filtered.map((c) => {
53
+ const { priority, reason } = assignPriority(c.path);
54
+ return { path: c.path, reason, priority };
55
+ });
56
+ scored.sort((a, b) => a.priority - b.priority);
57
+ return scored.slice(0, MAX_FILES);
58
+ }
59
+ //# sourceMappingURL=file-selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-selector.js","sourceRoot":"","sources":["../../../src/lib/rule-harvester/file-selector.ts"],"names":[],"mappings":"AAWA,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,MAAM,aAAa,GAAa;IAC9B,UAAU;IACV,UAAU;IACV,aAAa;IACb,UAAU;IACV,oBAAoB;IACpB,UAAU;IACV,gBAAgB;IAChB,eAAe;IACf,cAAc;IACd,kBAAkB;IAClB,cAAc;IACd,cAAc;IACd,cAAc;IACd,gBAAgB;IAChB,gBAAgB;IAChB,kBAAkB;IAClB,YAAY;IACZ,cAAc;CACf,CAAC;AAEF,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,mCAAmC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;IAC9D,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IACtD,CAAC;IACD,IAAI,6CAA6C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7D,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;IACxD,CAAC;IACD,IAAI,2CAA2C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,iCAAiC,EAAE,CAAC;IACpE,CAAC;IACD,IAAI,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/C,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,6BAA6B,EAAE,CAAC;IAChE,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IACpD,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;IAC1D,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAA2B;IACrD,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,cAAc,CAC5D,CAAC;IAEF,MAAM,MAAM,GAAmB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAChD,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpD,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAE/C,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,19 @@
1
+ export interface ClaimForConfirmation {
2
+ claimId: string;
3
+ category: string;
4
+ description: string;
5
+ verdict: string;
6
+ }
7
+ export interface GroundTruthResult {
8
+ claimId: string;
9
+ confirmed: boolean;
10
+ method: 'compiler' | 'test' | 'grep';
11
+ evidence: string;
12
+ }
13
+ export declare function runTscCheck(repoDir: string): {
14
+ errors: string;
15
+ exitCode: number;
16
+ } | null;
17
+ export declare function tscReportsErrorInFile(tscOutput: string, filePath: string): boolean;
18
+ export declare function confirmByGrep(code: string, claim: ClaimForConfirmation): GroundTruthResult;
19
+ export declare function parseConfirmation(code: string, claim: ClaimForConfirmation, tscOutput: string | null, filePath: string): GroundTruthResult;
@@ -0,0 +1,156 @@
1
+ import { execSync } from 'node:child_process';
2
+ export function runTscCheck(repoDir) {
3
+ try {
4
+ execSync('npx tsc --noEmit 2>&1', {
5
+ cwd: repoDir,
6
+ timeout: 120_000,
7
+ encoding: 'utf-8',
8
+ });
9
+ return { errors: '', exitCode: 0 };
10
+ }
11
+ catch (err) {
12
+ const e = err;
13
+ if (e.status === undefined) {
14
+ return null;
15
+ }
16
+ const combined = ((e.stdout ?? '') + (e.stderr ?? '')).trim();
17
+ return { errors: combined, exitCode: e.status };
18
+ }
19
+ }
20
+ export function tscReportsErrorInFile(tscOutput, filePath) {
21
+ return tscOutput.includes(filePath);
22
+ }
23
+ export function confirmByGrep(code, claim) {
24
+ const cat = claim.category.toLowerCase();
25
+ const desc = claim.description.toLowerCase();
26
+ // error-handling: await without surrounding try/catch or .catch()
27
+ if (cat.includes('error-handling') ||
28
+ desc.includes('error handling') ||
29
+ desc.includes('try/catch')) {
30
+ const hasAwait = /\bawait\b/.test(code);
31
+ const hasTryCatch = /\btry\s*\{/.test(code);
32
+ const hasCatchChain = /\.catch\s*\(/.test(code);
33
+ const confirmed = hasAwait && !hasTryCatch && !hasCatchChain;
34
+ return {
35
+ claimId: claim.claimId,
36
+ confirmed,
37
+ method: 'grep',
38
+ evidence: confirmed
39
+ ? 'Found await without try/catch or .catch()'
40
+ : 'await is wrapped in try/catch or .catch()',
41
+ };
42
+ }
43
+ // security + SQL injection: template literals with SQL keywords and ${
44
+ if ((cat.includes('security') &&
45
+ (desc.includes('sql') ||
46
+ desc.includes('injection') ||
47
+ desc.includes('query'))) ||
48
+ cat.includes('injection')) {
49
+ const sqlTemplatePattern = /`[^`]*(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)[^`]*\$\{/i;
50
+ const confirmed = sqlTemplatePattern.test(code);
51
+ return {
52
+ claimId: claim.claimId,
53
+ confirmed,
54
+ method: 'grep',
55
+ evidence: confirmed
56
+ ? 'Found SQL template literal with ${} interpolation'
57
+ : 'No SQL injection pattern found',
58
+ };
59
+ }
60
+ // hardcoded secrets: key|secret|token|password|api_key = "long_string"
61
+ if (cat.includes('hardcoded') ||
62
+ cat.includes('secret') ||
63
+ desc.includes('hardcoded') ||
64
+ desc.includes('secret') ||
65
+ desc.includes('api key') ||
66
+ desc.includes('api_key')) {
67
+ const secretPattern = /\b(?:key|secret|token|password|api_key)\s*=\s*["'][^"']{8,}["']/i;
68
+ const confirmed = secretPattern.test(code);
69
+ return {
70
+ claimId: claim.claimId,
71
+ confirmed,
72
+ method: 'grep',
73
+ evidence: confirmed
74
+ ? 'Found hardcoded secret assignment'
75
+ : 'No hardcoded secret pattern found',
76
+ };
77
+ }
78
+ // edge-case / null/undefined: nested property access without optional chaining or null checks
79
+ if (cat.includes('edge-case') ||
80
+ cat.includes('null') ||
81
+ cat.includes('undefined') ||
82
+ desc.includes('null') ||
83
+ desc.includes('undefined') ||
84
+ desc.includes('optional')) {
85
+ // Match a.b.c patterns where there's no ?. in the chain
86
+ const unsafeAccessPattern = /\b\w+\.\w+\.\w+/;
87
+ const safeAccessPattern = /\b\w+\??\.\w+\??\.\w+/;
88
+ const hasUnsafe = unsafeAccessPattern.test(code);
89
+ const hasSafe = /\?\.\w+/.test(code);
90
+ const confirmed = hasUnsafe && !hasSafe;
91
+ return {
92
+ claimId: claim.claimId,
93
+ confirmed,
94
+ method: 'grep',
95
+ evidence: confirmed
96
+ ? 'Found nested property access without optional chaining'
97
+ : 'Optional chaining or null checks present',
98
+ };
99
+ }
100
+ // input validation: req.body/params/query without validation
101
+ if (cat.includes('input validation') ||
102
+ cat.includes('validation') ||
103
+ desc.includes('input validation') ||
104
+ desc.includes('req.body') ||
105
+ desc.includes('req.params') ||
106
+ desc.includes('req.query')) {
107
+ const hasDirectAccess = /req\.(body|params|query)/.test(code);
108
+ const hasValidation = /validate|schema|zod|yup|joi|\.parse\(|\.safeParse\(/.test(code);
109
+ const confirmed = hasDirectAccess && !hasValidation;
110
+ return {
111
+ claimId: claim.claimId,
112
+ confirmed,
113
+ method: 'grep',
114
+ evidence: confirmed
115
+ ? 'Found req.body/params/query without validation'
116
+ : 'Validation present',
117
+ };
118
+ }
119
+ // unhandled promise: floating promise without await
120
+ if (cat.includes('unhandled promise') ||
121
+ cat.includes('promise') ||
122
+ desc.includes('unhandled promise') ||
123
+ desc.includes('floating promise')) {
124
+ // Look for function calls that return promises but are not awaited
125
+ const floatingPattern = /^\s+(?!.*await\s)\w+\s*\(.*\)\s*;/m;
126
+ const confirmed = floatingPattern.test(code);
127
+ return {
128
+ claimId: claim.claimId,
129
+ confirmed,
130
+ method: 'grep',
131
+ evidence: confirmed
132
+ ? 'Found potential floating promise (unawaited call)'
133
+ : 'No floating promise pattern found',
134
+ };
135
+ }
136
+ return {
137
+ claimId: claim.claimId,
138
+ confirmed: false,
139
+ method: 'grep',
140
+ evidence: `No grep strategy for category "${claim.category}"`,
141
+ };
142
+ }
143
+ export function parseConfirmation(code, claim, tscOutput, filePath) {
144
+ if (tscOutput !== null &&
145
+ claim.category.toLowerCase().includes('type-safety') &&
146
+ tscReportsErrorInFile(tscOutput, filePath)) {
147
+ return {
148
+ claimId: claim.claimId,
149
+ confirmed: true,
150
+ method: 'compiler',
151
+ evidence: `tsc reported error in ${filePath}`,
152
+ };
153
+ }
154
+ return confirmByGrep(code, claim);
155
+ }
156
+ //# sourceMappingURL=ground-truth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ground-truth.js","sourceRoot":"","sources":["../../../src/lib/rule-harvester/ground-truth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface HarvestConfig {
2
+ repoList: Array<{
3
+ owner: string;
4
+ name: string;
5
+ category: string;
6
+ }>;
7
+ /** Assay project root — where rules go */
8
+ catalogPath: string;
9
+ /** Where progress.json lives */
10
+ progressDir: string;
11
+ /** Where run reports go */
12
+ runsDir: string;
13
+ /** Temp dir for clones (e.g. /private/tmp/assay-harvester) */
14
+ workDir: string;
15
+ /** Model name for reporting */
16
+ model: string;
17
+ /** Max repos to scan */
18
+ limit?: number;
19
+ /** Only scan these repos (owner/name format) */
20
+ repoFilter?: string[];
21
+ /** Skip completed repos */
22
+ resume?: boolean;
23
+ /** Logging callback */
24
+ onLog?: (msg: string) => void;
25
+ }
26
+ export declare function harvestRepos(config: HarvestConfig): Promise<void>;