tryassay 0.28.2 → 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.
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/assess.js +4 -2
- package/dist/commands/assess.js.map +1 -1
- package/dist/commands/generate.js +1 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/harvest.d.ts +9 -0
- package/dist/commands/harvest.js +76 -0
- package/dist/commands/harvest.js.map +1 -0
- package/dist/lib/__tests__/learned-rules.test.d.ts +1 -0
- package/dist/lib/__tests__/learned-rules.test.js +260 -0
- package/dist/lib/__tests__/learned-rules.test.js.map +1 -0
- package/dist/lib/__tests__/pr-harvester-types.test.d.ts +1 -0
- package/dist/lib/__tests__/pr-harvester-types.test.js +43 -0
- package/dist/lib/__tests__/pr-harvester-types.test.js.map +1 -0
- package/dist/lib/__tests__/pr-harvester.test.d.ts +1 -0
- package/dist/lib/__tests__/pr-harvester.test.js +341 -0
- package/dist/lib/__tests__/pr-harvester.test.js.map +1 -0
- package/dist/lib/__tests__/rule-harvester.test.d.ts +1 -0
- package/dist/lib/__tests__/rule-harvester.test.js +526 -0
- package/dist/lib/__tests__/rule-harvester.test.js.map +1 -0
- package/dist/lib/claim-extractor.d.ts +1 -0
- package/dist/lib/claim-extractor.js +116 -15
- package/dist/lib/claim-extractor.js.map +1 -1
- package/dist/lib/code-verifier.d.ts +12 -1
- package/dist/lib/code-verifier.js +155 -12
- package/dist/lib/code-verifier.js.map +1 -1
- package/dist/lib/learned-rules/category-map.d.ts +28 -0
- package/dist/lib/learned-rules/category-map.js +110 -0
- package/dist/lib/learned-rules/category-map.js.map +1 -0
- package/dist/lib/learned-rules/index.d.ts +105 -0
- package/dist/lib/learned-rules/index.js +198 -0
- package/dist/lib/learned-rules/index.js.map +1 -0
- package/dist/lib/learned-rules/learned-catalog.d.ts +62 -0
- package/dist/lib/learned-rules/learned-catalog.js +161 -0
- package/dist/lib/learned-rules/learned-catalog.js.map +1 -0
- package/dist/lib/learned-rules/pattern-extractor.d.ts +25 -0
- package/dist/lib/learned-rules/pattern-extractor.js +351 -0
- package/dist/lib/learned-rules/pattern-extractor.js.map +1 -0
- package/dist/lib/learned-rules/rule-codifier.d.ts +41 -0
- package/dist/lib/learned-rules/rule-codifier.js +138 -0
- package/dist/lib/learned-rules/rule-codifier.js.map +1 -0
- package/dist/lib/learned-rules/starter-catalog.d.ts +16 -0
- package/dist/lib/learned-rules/starter-catalog.js +402 -0
- package/dist/lib/learned-rules/starter-catalog.js.map +1 -0
- package/dist/lib/learned-rules/types.d.ts +196 -0
- package/dist/lib/learned-rules/types.js +9 -0
- package/dist/lib/learned-rules/types.js.map +1 -0
- package/dist/lib/learned-rules/validation-harness.d.ts +26 -0
- package/dist/lib/learned-rules/validation-harness.js +260 -0
- package/dist/lib/learned-rules/validation-harness.js.map +1 -0
- package/dist/lib/llm-provider.d.ts +7 -0
- package/dist/lib/llm-provider.js +99 -6
- package/dist/lib/llm-provider.js.map +1 -1
- package/dist/lib/rule-harvester/diff-parser.d.ts +9 -0
- package/dist/lib/rule-harvester/diff-parser.js +77 -0
- package/dist/lib/rule-harvester/diff-parser.js.map +1 -0
- package/dist/lib/rule-harvester/file-selector.d.ts +10 -0
- package/dist/lib/rule-harvester/file-selector.js +59 -0
- package/dist/lib/rule-harvester/file-selector.js.map +1 -0
- package/dist/lib/rule-harvester/ground-truth.d.ts +19 -0
- package/dist/lib/rule-harvester/ground-truth.js +156 -0
- package/dist/lib/rule-harvester/ground-truth.js.map +1 -0
- package/dist/lib/rule-harvester/harvest.d.ts +26 -0
- package/dist/lib/rule-harvester/harvest.js +307 -0
- package/dist/lib/rule-harvester/harvest.js.map +1 -0
- package/dist/lib/rule-harvester/pr-discovery.d.ts +49 -0
- package/dist/lib/rule-harvester/pr-discovery.js +168 -0
- package/dist/lib/rule-harvester/pr-discovery.js.map +1 -0
- package/dist/lib/rule-harvester/pr-harvest.d.ts +53 -0
- package/dist/lib/rule-harvester/pr-harvest.js +326 -0
- package/dist/lib/rule-harvester/pr-harvest.js.map +1 -0
- package/dist/lib/rule-harvester/progress.d.ts +13 -0
- package/dist/lib/rule-harvester/progress.js +50 -0
- package/dist/lib/rule-harvester/progress.js.map +1 -0
- package/dist/lib/rule-harvester/reporter.d.ts +35 -0
- package/dist/lib/rule-harvester/reporter.js +46 -0
- package/dist/lib/rule-harvester/reporter.js.map +1 -0
- package/dist/lib/rule-harvester/rule-generalizer.d.ts +25 -0
- package/dist/lib/rule-harvester/rule-generalizer.js +135 -0
- package/dist/lib/rule-harvester/rule-generalizer.js.map +1 -0
- package/dist/lib/rule-harvester/scanner.d.ts +20 -0
- package/dist/lib/rule-harvester/scanner.js +37 -0
- package/dist/lib/rule-harvester/scanner.js.map +1 -0
- package/dist/runtime/types.d.ts +1 -1
- package/dist/sdk/forward-verify.d.ts +3 -1
- package/dist/sdk/forward-verify.js +68 -5
- package/dist/sdk/forward-verify.js.map +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +7 -5
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/types.d.ts +21 -0
- 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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"}
|
|
@@ -10,6 +10,13 @@ export declare class ClaudeCliProvider implements LLMProvider {
|
|
|
10
10
|
readonly type: LLMProviderType;
|
|
11
11
|
complete(opts: LLMCompletionOptions): Promise<LLMResult>;
|
|
12
12
|
}
|
|
13
|
+
export declare class OllamaProvider implements LLMProvider {
|
|
14
|
+
readonly type: LLMProviderType;
|
|
15
|
+
private readonly baseUrl;
|
|
16
|
+
private readonly model;
|
|
17
|
+
constructor(baseUrl?: string, model?: string);
|
|
18
|
+
complete(opts: LLMCompletionOptions): Promise<LLMResult>;
|
|
19
|
+
}
|
|
13
20
|
/**
|
|
14
21
|
* Get the configured LLM provider.
|
|
15
22
|
*
|
package/dist/lib/llm-provider.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ============================================================
|
|
2
|
-
// LLM Provider — Abstraction over Anthropic API
|
|
3
|
-
// Toggle via ASSAY_LLM_PROVIDER env var (api|cli|auto).
|
|
2
|
+
// LLM Provider — Abstraction over Anthropic API, Claude CLI, and Ollama
|
|
3
|
+
// Toggle via ASSAY_LLM_PROVIDER env var (api|cli|ollama|auto).
|
|
4
4
|
// ============================================================
|
|
5
5
|
import Anthropic from '@anthropic-ai/sdk';
|
|
6
6
|
import { safeExecWithStdin } from '../runtime/safe-executor.js';
|
|
@@ -131,6 +131,92 @@ export class ClaudeCliProvider {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
// ── Ollama Provider (OpenAI-compatible) ──────────────────────
|
|
135
|
+
export class OllamaProvider {
|
|
136
|
+
type = 'ollama';
|
|
137
|
+
baseUrl;
|
|
138
|
+
model;
|
|
139
|
+
constructor(baseUrl, model) {
|
|
140
|
+
this.baseUrl = baseUrl ?? process.env.ASSAY_OLLAMA_BASE_URL ?? 'http://localhost:11434';
|
|
141
|
+
this.model = model ?? process.env.ASSAY_OLLAMA_MODEL ?? 'qwen3.5:9b';
|
|
142
|
+
}
|
|
143
|
+
async complete(opts) {
|
|
144
|
+
const start = Date.now();
|
|
145
|
+
// Use native Ollama API (not OpenAI-compatible) for think:false support
|
|
146
|
+
const url = `${this.baseUrl}/api/chat`;
|
|
147
|
+
// Detect if the prompt expects JSON output (intent extraction, claim verification, etc.)
|
|
148
|
+
// and use Ollama's constrained decoding to force valid JSON.
|
|
149
|
+
const wantsJson = opts.systemPrompt.includes('Return ONLY a JSON')
|
|
150
|
+
|| opts.systemPrompt.includes('JSON object')
|
|
151
|
+
|| opts.systemPrompt.includes('Respond with JSON')
|
|
152
|
+
|| opts.userPrompt.includes('Return ONLY a JSON')
|
|
153
|
+
|| opts.userPrompt.includes('respond with a JSON');
|
|
154
|
+
const body = {
|
|
155
|
+
model: this.model,
|
|
156
|
+
messages: [
|
|
157
|
+
{ role: 'system', content: opts.systemPrompt },
|
|
158
|
+
{ role: 'user', content: opts.userPrompt },
|
|
159
|
+
],
|
|
160
|
+
stream: false,
|
|
161
|
+
think: false, // Disable thinking mode — Assay needs direct answers
|
|
162
|
+
options: {
|
|
163
|
+
num_predict: opts.maxTokens ?? 16_000,
|
|
164
|
+
...(opts.temperature !== undefined && { temperature: opts.temperature }),
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
// Local models can take 10+ minutes on large prompts.
|
|
168
|
+
// Node's undici has a default headers timeout (~300s) separate from AbortSignal.
|
|
169
|
+
// Use keepalive:false and a generous AbortSignal to avoid premature timeouts.
|
|
170
|
+
const timeoutMs = 30 * 60 * 1000; // 30min for local models
|
|
171
|
+
const controller = new AbortController();
|
|
172
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
173
|
+
let res;
|
|
174
|
+
try {
|
|
175
|
+
res = await fetch(url, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: {
|
|
178
|
+
'Content-Type': 'application/json',
|
|
179
|
+
'Connection': 'keep-alive',
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify(body),
|
|
182
|
+
signal: controller.signal,
|
|
183
|
+
// @ts-expect-error -- Node undici dispatcher options
|
|
184
|
+
dispatcher: new (await import('undici')).Agent({
|
|
185
|
+
headersTimeout: timeoutMs,
|
|
186
|
+
bodyTimeout: timeoutMs,
|
|
187
|
+
connectTimeout: 30_000,
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
}
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
const text = await res.text().catch(() => '');
|
|
196
|
+
throw new Error(`Ollama API error ${res.status}: ${text}`);
|
|
197
|
+
}
|
|
198
|
+
const data = await res.json();
|
|
199
|
+
// Native API returns message.content directly
|
|
200
|
+
// Fall back to thinking field if content is empty (model used thinking despite think:false)
|
|
201
|
+
let content = data.message?.content || data.message?.thinking || '';
|
|
202
|
+
// Tuned models may wrap JSON in markdown formatting (bold, code blocks).
|
|
203
|
+
// Strip markdown artifacts so downstream JSON parsers can handle the output.
|
|
204
|
+
content = content
|
|
205
|
+
.replace(/\*\*/g, '') // Remove bold markers
|
|
206
|
+
.replace(/^```(?:json)?\s*\n?/gm, '') // Remove opening code fences
|
|
207
|
+
.replace(/\n?```\s*$/gm, '') // Remove closing code fences
|
|
208
|
+
.trim();
|
|
209
|
+
const promptTokens = data.prompt_eval_count ?? null;
|
|
210
|
+
const completionTokens = data.eval_count ?? null;
|
|
211
|
+
return {
|
|
212
|
+
content,
|
|
213
|
+
inputTokens: promptTokens,
|
|
214
|
+
outputTokens: completionTokens,
|
|
215
|
+
provider: 'ollama',
|
|
216
|
+
durationMs: Date.now() - start,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
134
220
|
// ── Factory ──────────────────────────────────────────────────
|
|
135
221
|
let cachedProvider = null;
|
|
136
222
|
/**
|
|
@@ -143,7 +229,10 @@ export function getProvider() {
|
|
|
143
229
|
if (cachedProvider)
|
|
144
230
|
return cachedProvider;
|
|
145
231
|
const explicit = process.env.ASSAY_LLM_PROVIDER;
|
|
146
|
-
if (explicit === '
|
|
232
|
+
if (explicit === 'ollama') {
|
|
233
|
+
cachedProvider = new OllamaProvider();
|
|
234
|
+
}
|
|
235
|
+
else if (explicit === 'cli') {
|
|
147
236
|
if (isInsideClaudeCode()) {
|
|
148
237
|
throw new Error('ASSAY_LLM_PROVIDER=cli but running inside Claude Code (deadlock).\n' +
|
|
149
238
|
'Set ANTHROPIC_API_KEY and use ASSAY_LLM_PROVIDER=api, or run from a regular terminal.');
|
|
@@ -154,15 +243,19 @@ export function getProvider() {
|
|
|
154
243
|
cachedProvider = new AnthropicApiProvider();
|
|
155
244
|
}
|
|
156
245
|
else {
|
|
157
|
-
// Auto-detect:
|
|
246
|
+
// Auto-detect: Ollama if configured, API if key available, else CLI
|
|
158
247
|
// Never auto-select CLI when inside Claude Code (causes deadlock)
|
|
159
|
-
if (process.env.
|
|
248
|
+
if (process.env.ASSAY_OLLAMA_MODEL) {
|
|
249
|
+
cachedProvider = new OllamaProvider();
|
|
250
|
+
}
|
|
251
|
+
else if (process.env.ANTHROPIC_API_KEY) {
|
|
160
252
|
cachedProvider = new AnthropicApiProvider();
|
|
161
253
|
}
|
|
162
254
|
else if (isInsideClaudeCode()) {
|
|
163
255
|
throw new Error('No ANTHROPIC_API_KEY set and running inside Claude Code.\n' +
|
|
164
256
|
'The CLI provider deadlocks when spawned from Claude Code.\n' +
|
|
165
|
-
'Export ANTHROPIC_API_KEY or run Assay from a regular terminal
|
|
257
|
+
'Export ANTHROPIC_API_KEY or run Assay from a regular terminal.\n' +
|
|
258
|
+
'Or set ASSAY_LLM_PROVIDER=ollama to use a local model.');
|
|
166
259
|
}
|
|
167
260
|
else {
|
|
168
261
|
cachedProvider = new ClaudeCliProvider();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm-provider.js","sourceRoot":"","sources":["../../src/lib/llm-provider.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAC/D
|
|
1
|
+
{"version":3,"file":"llm-provider.js","sourceRoot":"","sources":["../../src/lib/llm-provider.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,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;
|