@the-magic-tower/fixhive-opencode-plugin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -0
- package/dist/index.js +1782 -0
- package/package.json +68 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1782 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/core/privacy-filter.ts
|
|
9
|
+
var DEFAULT_FILTER_RULES = [
|
|
10
|
+
// =====================================
|
|
11
|
+
// SECRETS (Critical - Always Filter)
|
|
12
|
+
// =====================================
|
|
13
|
+
// AWS Keys
|
|
14
|
+
{
|
|
15
|
+
name: "aws_access_key",
|
|
16
|
+
category: "secret",
|
|
17
|
+
pattern: /\b(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\b/g,
|
|
18
|
+
replacement: "[AWS_KEY_REDACTED]",
|
|
19
|
+
priority: 100
|
|
20
|
+
},
|
|
21
|
+
// OpenAI API Keys
|
|
22
|
+
{
|
|
23
|
+
name: "openai_key",
|
|
24
|
+
category: "secret",
|
|
25
|
+
pattern: /\b(sk-[a-zA-Z0-9]{20,})\b/g,
|
|
26
|
+
replacement: "[OPENAI_KEY_REDACTED]",
|
|
27
|
+
priority: 100
|
|
28
|
+
},
|
|
29
|
+
// GitHub Tokens
|
|
30
|
+
{
|
|
31
|
+
name: "github_token",
|
|
32
|
+
category: "secret",
|
|
33
|
+
pattern: /\b(gh[pousr]_[A-Za-z0-9_]{36,}|github_pat_[A-Za-z0-9_]{22,})\b/g,
|
|
34
|
+
replacement: "[GITHUB_TOKEN_REDACTED]",
|
|
35
|
+
priority: 100
|
|
36
|
+
},
|
|
37
|
+
// Google API Keys
|
|
38
|
+
{
|
|
39
|
+
name: "google_api_key",
|
|
40
|
+
category: "secret",
|
|
41
|
+
pattern: /\bAIza[0-9A-Za-z\-_]{35}\b/g,
|
|
42
|
+
replacement: "[GOOGLE_API_KEY_REDACTED]",
|
|
43
|
+
priority: 100
|
|
44
|
+
},
|
|
45
|
+
// Stripe Keys
|
|
46
|
+
{
|
|
47
|
+
name: "stripe_key",
|
|
48
|
+
category: "secret",
|
|
49
|
+
pattern: /\b(sk|pk|rk)_(live|test)_[0-9a-zA-Z]{24,}\b/g,
|
|
50
|
+
replacement: "[STRIPE_KEY_REDACTED]",
|
|
51
|
+
priority: 100
|
|
52
|
+
},
|
|
53
|
+
// JWT Tokens (limited length to prevent ReDoS)
|
|
54
|
+
{
|
|
55
|
+
name: "jwt_token",
|
|
56
|
+
category: "secret",
|
|
57
|
+
pattern: /\beyJ[A-Za-z0-9_-]{10,500}\.eyJ[A-Za-z0-9_-]{10,1000}\.[A-Za-z0-9_-]{10,500}/g,
|
|
58
|
+
replacement: "[JWT_REDACTED]",
|
|
59
|
+
priority: 100
|
|
60
|
+
},
|
|
61
|
+
// Bearer Tokens
|
|
62
|
+
{
|
|
63
|
+
name: "bearer_token",
|
|
64
|
+
category: "secret",
|
|
65
|
+
pattern: /\b(Bearer|Authorization:?\s*Bearer)\s+[\w\-._~+\/]+=*/gi,
|
|
66
|
+
replacement: "$1 [TOKEN_REDACTED]",
|
|
67
|
+
priority: 100
|
|
68
|
+
},
|
|
69
|
+
// Private Keys
|
|
70
|
+
{
|
|
71
|
+
name: "private_key",
|
|
72
|
+
category: "secret",
|
|
73
|
+
pattern: /-----BEGIN\s+(RSA|DSA|EC|OPENSSH|PGP)?\s*PRIVATE KEY-----[\s\S]*?-----END\s+(RSA|DSA|EC|OPENSSH|PGP)?\s*PRIVATE KEY-----/g,
|
|
74
|
+
replacement: "[PRIVATE_KEY_REDACTED]",
|
|
75
|
+
priority: 100
|
|
76
|
+
},
|
|
77
|
+
// Generic API Keys (context-based, limited length to prevent ReDoS)
|
|
78
|
+
{
|
|
79
|
+
name: "generic_api_key",
|
|
80
|
+
category: "secret",
|
|
81
|
+
pattern: /\b(api[_-]?key|apikey|api[_-]?secret)\s{0,5}[=:]\s{0,5}["']?[\w-]{20,200}["']?/gi,
|
|
82
|
+
replacement: "$1=[KEY_REDACTED]",
|
|
83
|
+
priority: 95
|
|
84
|
+
},
|
|
85
|
+
// Secret/Password assignments (limited length to prevent ReDoS)
|
|
86
|
+
{
|
|
87
|
+
name: "secret_assignment",
|
|
88
|
+
category: "secret",
|
|
89
|
+
pattern: /\b(password|passwd|pwd|secret|token|credential)\s{0,5}[=:]\s{0,5}["']?[^\s"']{8,200}["']?/gi,
|
|
90
|
+
replacement: "$1=[REDACTED]",
|
|
91
|
+
priority: 90
|
|
92
|
+
},
|
|
93
|
+
// =====================================
|
|
94
|
+
// IDENTITY (High Risk)
|
|
95
|
+
// =====================================
|
|
96
|
+
// Email Addresses
|
|
97
|
+
{
|
|
98
|
+
name: "email",
|
|
99
|
+
category: "identity",
|
|
100
|
+
pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
101
|
+
replacement: "[EMAIL_REDACTED]",
|
|
102
|
+
priority: 80
|
|
103
|
+
},
|
|
104
|
+
// =====================================
|
|
105
|
+
// INFRASTRUCTURE (Medium Risk)
|
|
106
|
+
// =====================================
|
|
107
|
+
// Database Connection Strings
|
|
108
|
+
{
|
|
109
|
+
name: "db_connection",
|
|
110
|
+
category: "infrastructure",
|
|
111
|
+
pattern: /\b(mongodb|postgres|postgresql|mysql|redis|amqp):\/\/[^\s]+/gi,
|
|
112
|
+
replacement: "$1://[CONNECTION_REDACTED]",
|
|
113
|
+
priority: 85
|
|
114
|
+
},
|
|
115
|
+
// Internal URLs
|
|
116
|
+
{
|
|
117
|
+
name: "internal_url",
|
|
118
|
+
category: "infrastructure",
|
|
119
|
+
pattern: /\bhttps?:\/\/(?:[\w-]+\.)*(?:internal|corp|local|intranet|private)[\w.-]*(?::\d+)?(?:\/[^\s]*)?/gi,
|
|
120
|
+
replacement: "[INTERNAL_URL_REDACTED]",
|
|
121
|
+
priority: 75
|
|
122
|
+
},
|
|
123
|
+
// IP Addresses (except localhost and common dev IPs)
|
|
124
|
+
{
|
|
125
|
+
name: "ipv4",
|
|
126
|
+
category: "infrastructure",
|
|
127
|
+
pattern: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g,
|
|
128
|
+
replacement: (match) => {
|
|
129
|
+
if (match === "127.0.0.1" || match === "0.0.0.0" || match.startsWith("192.168.")) {
|
|
130
|
+
return match;
|
|
131
|
+
}
|
|
132
|
+
return "[IP_REDACTED]";
|
|
133
|
+
},
|
|
134
|
+
priority: 70
|
|
135
|
+
},
|
|
136
|
+
// =====================================
|
|
137
|
+
// FILE PATHS (Context-dependent)
|
|
138
|
+
// =====================================
|
|
139
|
+
// Home Directory Paths (macOS)
|
|
140
|
+
{
|
|
141
|
+
name: "macos_home_path",
|
|
142
|
+
category: "path",
|
|
143
|
+
pattern: /\/Users\/[\w.-]+/g,
|
|
144
|
+
replacement: "~",
|
|
145
|
+
priority: 60
|
|
146
|
+
},
|
|
147
|
+
// Home Directory Paths (Linux)
|
|
148
|
+
{
|
|
149
|
+
name: "linux_home_path",
|
|
150
|
+
category: "path",
|
|
151
|
+
pattern: /\/home\/[\w.-]+/g,
|
|
152
|
+
replacement: "~",
|
|
153
|
+
priority: 60
|
|
154
|
+
},
|
|
155
|
+
// Home Directory Paths (Windows)
|
|
156
|
+
{
|
|
157
|
+
name: "windows_home_path",
|
|
158
|
+
category: "path",
|
|
159
|
+
pattern: /C:\\Users\\[\w.-]+/gi,
|
|
160
|
+
replacement: "~",
|
|
161
|
+
priority: 60
|
|
162
|
+
},
|
|
163
|
+
// =====================================
|
|
164
|
+
// ENVIRONMENT (Medium Risk)
|
|
165
|
+
// =====================================
|
|
166
|
+
// Sensitive Environment Variables
|
|
167
|
+
{
|
|
168
|
+
name: "env_var_value",
|
|
169
|
+
category: "environment",
|
|
170
|
+
pattern: /\b([A-Z_][A-Z0-9_]*)\s*=\s*["']?([^\s"']+)["']?/g,
|
|
171
|
+
replacement: (match, name, _value) => {
|
|
172
|
+
const sensitivePatterns = [
|
|
173
|
+
"API_KEY",
|
|
174
|
+
"SECRET",
|
|
175
|
+
"PASSWORD",
|
|
176
|
+
"TOKEN",
|
|
177
|
+
"CREDENTIAL",
|
|
178
|
+
"PRIVATE",
|
|
179
|
+
"AUTH",
|
|
180
|
+
"DATABASE_URL",
|
|
181
|
+
"REDIS_URL",
|
|
182
|
+
"OPENAI",
|
|
183
|
+
"STRIPE",
|
|
184
|
+
"AWS"
|
|
185
|
+
];
|
|
186
|
+
if (sensitivePatterns.some((s) => name.toUpperCase().includes(s))) {
|
|
187
|
+
return `${name}=[REDACTED]`;
|
|
188
|
+
}
|
|
189
|
+
return match;
|
|
190
|
+
},
|
|
191
|
+
priority: 65
|
|
192
|
+
}
|
|
193
|
+
];
|
|
194
|
+
var PrivacyFilter = class {
|
|
195
|
+
rules;
|
|
196
|
+
constructor(customRules) {
|
|
197
|
+
this.rules = [...DEFAULT_FILTER_RULES, ...customRules || []].sort(
|
|
198
|
+
(a, b) => b.priority - a.priority
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Sanitize content by applying all filter rules
|
|
203
|
+
*/
|
|
204
|
+
sanitize(content, context) {
|
|
205
|
+
let result = content;
|
|
206
|
+
const appliedFilters = [];
|
|
207
|
+
let totalRedacted = 0;
|
|
208
|
+
for (const rule of this.rules) {
|
|
209
|
+
const before = result;
|
|
210
|
+
if (typeof rule.replacement === "function") {
|
|
211
|
+
result = result.replace(rule.pattern, rule.replacement);
|
|
212
|
+
} else {
|
|
213
|
+
result = result.replace(rule.pattern, rule.replacement);
|
|
214
|
+
}
|
|
215
|
+
if (result !== before) {
|
|
216
|
+
const matches = before.match(rule.pattern);
|
|
217
|
+
if (matches) {
|
|
218
|
+
totalRedacted += matches.length;
|
|
219
|
+
appliedFilters.push(rule.name);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (context) {
|
|
224
|
+
result = this.generalizePaths(result, context);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
original: content,
|
|
228
|
+
sanitized: result,
|
|
229
|
+
redactedCount: totalRedacted,
|
|
230
|
+
appliedFilters: [...new Set(appliedFilters)]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Generalize file paths while keeping meaningful structure
|
|
235
|
+
*/
|
|
236
|
+
generalizePaths(content, context) {
|
|
237
|
+
let result = content;
|
|
238
|
+
if (context.projectRoot) {
|
|
239
|
+
const escapedRoot = context.projectRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
240
|
+
result = result.replace(new RegExp(escapedRoot, "g"), "<PROJECT>");
|
|
241
|
+
}
|
|
242
|
+
for (const [fullPath, alias] of context.commonPaths) {
|
|
243
|
+
const escapedPath = fullPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
244
|
+
result = result.replace(new RegExp(escapedPath, "g"), alias);
|
|
245
|
+
}
|
|
246
|
+
result = result.replace(/node_modules\/(?:@[\w.-]+\/)?[\w.-]+\//g, "node_modules/<PKG>/");
|
|
247
|
+
result = result.replace(/site-packages\/[\w.-]+\//g, "site-packages/<PKG>/");
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Add a custom filter rule
|
|
252
|
+
*/
|
|
253
|
+
addRule(rule) {
|
|
254
|
+
this.rules.push(rule);
|
|
255
|
+
this.rules.sort((a, b) => b.priority - a.priority);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Remove a filter rule by name
|
|
259
|
+
*/
|
|
260
|
+
removeRule(name) {
|
|
261
|
+
const index = this.rules.findIndex((r) => r.name === name);
|
|
262
|
+
if (index !== -1) {
|
|
263
|
+
this.rules.splice(index, 1);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get all current rules
|
|
270
|
+
*/
|
|
271
|
+
getRules() {
|
|
272
|
+
return this.rules;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Check if content contains sensitive data
|
|
276
|
+
* Note: Always reset regex lastIndex BEFORE testing to prevent state pollution
|
|
277
|
+
*/
|
|
278
|
+
containsSensitiveData(content) {
|
|
279
|
+
for (const rule of this.rules) {
|
|
280
|
+
if (rule.category === "secret") {
|
|
281
|
+
rule.pattern.lastIndex = 0;
|
|
282
|
+
const hasSensitiveData = rule.pattern.test(content);
|
|
283
|
+
rule.pattern.lastIndex = 0;
|
|
284
|
+
if (hasSensitiveData) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
function createFilterContext(projectDirectory) {
|
|
293
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "~";
|
|
294
|
+
return {
|
|
295
|
+
projectRoot: projectDirectory,
|
|
296
|
+
homeDir,
|
|
297
|
+
commonPaths: /* @__PURE__ */ new Map([
|
|
298
|
+
["/usr/local/lib/", "<LIB>/"],
|
|
299
|
+
["/usr/lib/", "<LIB>/"],
|
|
300
|
+
["/var/log/", "<LOG>/"],
|
|
301
|
+
["/tmp/", "<TMP>/"],
|
|
302
|
+
["/etc/", "<ETC>/"]
|
|
303
|
+
])
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
var defaultPrivacyFilter = new PrivacyFilter();
|
|
307
|
+
|
|
308
|
+
// src/core/error-detector.ts
|
|
309
|
+
var ERROR_PATTERNS = {
|
|
310
|
+
// Universal error indicators
|
|
311
|
+
universal: [
|
|
312
|
+
/\b(error|failed|failure|fatal|exception|panic)\b/i,
|
|
313
|
+
/\b(cannot|could not|unable to|couldn't)\b/i,
|
|
314
|
+
/\b(denied|rejected|refused|forbidden)\b/i,
|
|
315
|
+
/\b(not found|missing|undefined|null pointer)\b/i,
|
|
316
|
+
/\b(invalid|illegal|malformed|corrupt)\b/i,
|
|
317
|
+
/\b(timeout|timed out|connection refused)\b/i,
|
|
318
|
+
/\b(segmentation fault|segfault|core dumped)\b/i,
|
|
319
|
+
/\b(out of memory|oom|memory allocation failed)\b/i
|
|
320
|
+
],
|
|
321
|
+
// Error prefixes
|
|
322
|
+
prefixed: [
|
|
323
|
+
/^Error:/m,
|
|
324
|
+
/^ERROR\s/m,
|
|
325
|
+
/^E\s+\d+:/m,
|
|
326
|
+
// Rust errors
|
|
327
|
+
/^\[ERROR\]/m,
|
|
328
|
+
/^fatal:/m,
|
|
329
|
+
/^FATAL:/m,
|
|
330
|
+
/^panic:/m,
|
|
331
|
+
/^Traceback \(most recent call last\):/m,
|
|
332
|
+
// Python
|
|
333
|
+
/^Exception in thread/m,
|
|
334
|
+
// Java
|
|
335
|
+
/^Uncaught \w+Error:/m
|
|
336
|
+
// JavaScript
|
|
337
|
+
],
|
|
338
|
+
// Build/compilation errors
|
|
339
|
+
build: [
|
|
340
|
+
/^.+:\d+:\d+:\s*error:/m,
|
|
341
|
+
// GCC/Clang format
|
|
342
|
+
/error\[E\d+\]:/m,
|
|
343
|
+
// Rust compiler
|
|
344
|
+
/error TS\d+:/m,
|
|
345
|
+
// TypeScript
|
|
346
|
+
/SyntaxError:/m,
|
|
347
|
+
/ParseError:/m,
|
|
348
|
+
/CompileError:/m
|
|
349
|
+
],
|
|
350
|
+
// Package manager errors
|
|
351
|
+
package: [
|
|
352
|
+
/npm ERR!/m,
|
|
353
|
+
/npm error/m,
|
|
354
|
+
/yarn error/m,
|
|
355
|
+
/pnpm error/m,
|
|
356
|
+
/pip error/m,
|
|
357
|
+
/cargo error/m,
|
|
358
|
+
/ModuleNotFoundError:/m,
|
|
359
|
+
/ImportError:/m,
|
|
360
|
+
/Cannot find module/m,
|
|
361
|
+
/Module not found/m
|
|
362
|
+
],
|
|
363
|
+
// Permission errors
|
|
364
|
+
permission: [/EACCES:/m, /EPERM:/m, /Permission denied/m, /Access denied/m, /Insufficient permissions/m],
|
|
365
|
+
// Network errors
|
|
366
|
+
network: [
|
|
367
|
+
/ECONNREFUSED/m,
|
|
368
|
+
/ETIMEDOUT/m,
|
|
369
|
+
/ENOTFOUND/m,
|
|
370
|
+
/getaddrinfo ENOTFOUND/m,
|
|
371
|
+
/Connection refused/m,
|
|
372
|
+
/Network is unreachable/m
|
|
373
|
+
],
|
|
374
|
+
// Test failures
|
|
375
|
+
test: [/FAIL /m, /AssertionError/m, /Expected .+ but got/m, /Test failed/i, /\d+ failing/m]
|
|
376
|
+
};
|
|
377
|
+
var STACK_TRACE_PATTERNS = {
|
|
378
|
+
javascript: /^\s+at\s+(?:(?:async\s+)?[\w.<>]+\s+\()?(?:[\w/\\.:-]+:\d+:\d+|native|<anonymous>)\)?$/m,
|
|
379
|
+
python: /^\s+File\s+"[^"]+",\s+line\s+\d+/m,
|
|
380
|
+
java: /^\s+at\s+[\w.$]+\([\w.]+:\d+\)$/m,
|
|
381
|
+
golang: /^goroutine\s+\d+\s+\[.+\]:/m,
|
|
382
|
+
rust: /^\s+\d+:\s+0x[\da-f]+\s+-\s+.+$/m,
|
|
383
|
+
ruby: /^\s+from\s+[\w/\\.]+:\d+:in\s+`.+'$/m,
|
|
384
|
+
php: /^#\d+\s+[\w/\\.]+\(\d+\):\s+[\w\\]+/m,
|
|
385
|
+
csharp: /^\s+at\s+[\w.<>]+\s+in\s+[\w/\\.]+:line\s+\d+$/m
|
|
386
|
+
};
|
|
387
|
+
var EXIT_CODE_SEVERITY = {
|
|
388
|
+
1: "error",
|
|
389
|
+
// General errors
|
|
390
|
+
2: "error",
|
|
391
|
+
// Misuse of shell builtins
|
|
392
|
+
126: "error",
|
|
393
|
+
// Command cannot execute
|
|
394
|
+
127: "error",
|
|
395
|
+
// Command not found
|
|
396
|
+
128: "critical",
|
|
397
|
+
// Invalid exit argument
|
|
398
|
+
130: "warning",
|
|
399
|
+
// Script terminated by Ctrl+C
|
|
400
|
+
137: "critical",
|
|
401
|
+
// SIGKILL (OOM, etc.)
|
|
402
|
+
139: "critical",
|
|
403
|
+
// Segmentation fault
|
|
404
|
+
143: "warning"
|
|
405
|
+
// SIGTERM
|
|
406
|
+
};
|
|
407
|
+
var ErrorDetector = class {
|
|
408
|
+
privacyFilter;
|
|
409
|
+
constructor(privacyFilter) {
|
|
410
|
+
this.privacyFilter = privacyFilter || new PrivacyFilter();
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Detect if output contains an error
|
|
414
|
+
*/
|
|
415
|
+
detect(toolOutput) {
|
|
416
|
+
const signals = [];
|
|
417
|
+
const combinedOutput = `${toolOutput.output || ""}
|
|
418
|
+
${toolOutput.stderr || ""}`;
|
|
419
|
+
if (toolOutput.exitCode !== void 0 && toolOutput.exitCode !== 0) {
|
|
420
|
+
const severity2 = EXIT_CODE_SEVERITY[toolOutput.exitCode] || "error";
|
|
421
|
+
signals.push({
|
|
422
|
+
type: "exit_code",
|
|
423
|
+
weight: 0.9,
|
|
424
|
+
value: toolOutput.exitCode,
|
|
425
|
+
description: `Non-zero exit code: ${toolOutput.exitCode} (${severity2})`
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
if (toolOutput.stderr && toolOutput.stderr.trim().length > 0) {
|
|
429
|
+
const hasErrorKeywords = this.containsErrorKeywords(toolOutput.stderr);
|
|
430
|
+
const stderrWeight = hasErrorKeywords ? 0.85 : 0.4;
|
|
431
|
+
signals.push({
|
|
432
|
+
type: "stderr",
|
|
433
|
+
weight: stderrWeight,
|
|
434
|
+
value: toolOutput.stderr.substring(0, 500),
|
|
435
|
+
description: hasErrorKeywords ? "Stderr with error keywords" : "Stderr output present"
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
const patternMatches = this.detectErrorPatterns(combinedOutput);
|
|
439
|
+
signals.push(...patternMatches);
|
|
440
|
+
const stackTrace = this.detectStackTrace(combinedOutput);
|
|
441
|
+
if (stackTrace.hasStackTrace) {
|
|
442
|
+
signals.push({
|
|
443
|
+
type: "stack_trace",
|
|
444
|
+
weight: 0.95,
|
|
445
|
+
value: stackTrace.frames.slice(0, 5).join("\n"),
|
|
446
|
+
description: `${stackTrace.language} stack trace detected`
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
const confidence = this.calculateConfidence(signals);
|
|
450
|
+
const detected = confidence >= 0.5;
|
|
451
|
+
const errorType = this.classifyErrorType(signals, combinedOutput);
|
|
452
|
+
const severity = this.determineSeverity(signals, toolOutput.exitCode);
|
|
453
|
+
const { message, stack } = this.extractErrorDetails(combinedOutput);
|
|
454
|
+
const sanitizedMessage = this.privacyFilter.sanitize(message);
|
|
455
|
+
const sanitizedStack = stack ? this.privacyFilter.sanitize(stack) : void 0;
|
|
456
|
+
const sanitizedOutput = this.privacyFilter.sanitize(combinedOutput.substring(0, 5e3));
|
|
457
|
+
return {
|
|
458
|
+
detected,
|
|
459
|
+
confidence,
|
|
460
|
+
errorType,
|
|
461
|
+
severity,
|
|
462
|
+
signals,
|
|
463
|
+
errorMessage: sanitizedMessage.sanitized,
|
|
464
|
+
errorStack: sanitizedStack?.sanitized,
|
|
465
|
+
rawOutput: sanitizedOutput.sanitized
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Check if content contains error keywords
|
|
470
|
+
*/
|
|
471
|
+
containsErrorKeywords(content) {
|
|
472
|
+
return ERROR_PATTERNS.universal.some((p) => p.test(content));
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Detect error patterns in content
|
|
476
|
+
*/
|
|
477
|
+
detectErrorPatterns(content) {
|
|
478
|
+
const signals = [];
|
|
479
|
+
const weights = {
|
|
480
|
+
prefixed: 0.85,
|
|
481
|
+
build: 0.8,
|
|
482
|
+
package: 0.75,
|
|
483
|
+
permission: 0.8,
|
|
484
|
+
network: 0.75,
|
|
485
|
+
test: 0.7
|
|
486
|
+
};
|
|
487
|
+
for (const [category, patterns] of Object.entries(ERROR_PATTERNS)) {
|
|
488
|
+
if (category === "universal") continue;
|
|
489
|
+
for (const pattern of patterns) {
|
|
490
|
+
const match = content.match(pattern);
|
|
491
|
+
if (match) {
|
|
492
|
+
signals.push({
|
|
493
|
+
type: "pattern",
|
|
494
|
+
weight: weights[category] || 0.7,
|
|
495
|
+
value: match[0].substring(0, 200),
|
|
496
|
+
description: `Matched ${category} pattern: ${pattern.source.substring(0, 50)}`
|
|
497
|
+
});
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return signals;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Detect stack traces in content
|
|
506
|
+
*/
|
|
507
|
+
detectStackTrace(content) {
|
|
508
|
+
for (const [language, pattern] of Object.entries(STACK_TRACE_PATTERNS)) {
|
|
509
|
+
const globalPattern = new RegExp(pattern.source, "gm");
|
|
510
|
+
const matches = content.match(globalPattern);
|
|
511
|
+
if (matches && matches.length >= 2) {
|
|
512
|
+
return {
|
|
513
|
+
hasStackTrace: true,
|
|
514
|
+
language,
|
|
515
|
+
frames: matches
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
hasStackTrace: false,
|
|
521
|
+
language: null,
|
|
522
|
+
frames: []
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Calculate confidence score from signals
|
|
527
|
+
*/
|
|
528
|
+
calculateConfidence(signals) {
|
|
529
|
+
if (signals.length === 0) return 0;
|
|
530
|
+
const totalWeight = signals.reduce((sum, s) => sum + s.weight, 0);
|
|
531
|
+
const avgWeight = totalWeight / signals.length;
|
|
532
|
+
const multiplier = Math.min(1.2, 1 + signals.length * 0.05);
|
|
533
|
+
return Math.min(1, avgWeight * multiplier);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Classify error type based on signals and content
|
|
537
|
+
*/
|
|
538
|
+
classifyErrorType(signals, content) {
|
|
539
|
+
if (ERROR_PATTERNS.build.some((p) => p.test(content))) return "build";
|
|
540
|
+
if (ERROR_PATTERNS.package.some((p) => p.test(content))) return "dependency";
|
|
541
|
+
if (ERROR_PATTERNS.permission.some((p) => p.test(content))) return "permission";
|
|
542
|
+
if (ERROR_PATTERNS.network.some((p) => p.test(content))) return "network";
|
|
543
|
+
if (ERROR_PATTERNS.test.some((p) => p.test(content))) return "test";
|
|
544
|
+
if (/TypeError:|type error|Type '[^']+' is not assignable/i.test(content)) return "type_error";
|
|
545
|
+
if (/SyntaxError:|syntax error|unexpected token/i.test(content)) return "syntax";
|
|
546
|
+
if (/ReferenceError:|RangeError:|runtime error/i.test(content)) return "runtime";
|
|
547
|
+
const hasStackTrace = signals.some((s) => s.type === "stack_trace");
|
|
548
|
+
if (hasStackTrace) return "runtime";
|
|
549
|
+
return "unknown";
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Determine severity from signals and exit code
|
|
553
|
+
*/
|
|
554
|
+
determineSeverity(signals, exitCode) {
|
|
555
|
+
if (exitCode !== void 0 && EXIT_CODE_SEVERITY[exitCode]) {
|
|
556
|
+
return EXIT_CODE_SEVERITY[exitCode];
|
|
557
|
+
}
|
|
558
|
+
const maxWeight = Math.max(...signals.map((s) => s.weight));
|
|
559
|
+
if (maxWeight >= 0.9) return "error";
|
|
560
|
+
if (maxWeight >= 0.7) return "error";
|
|
561
|
+
return "warning";
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Extract error message and stack from output
|
|
565
|
+
*/
|
|
566
|
+
extractErrorDetails(output) {
|
|
567
|
+
const lines = output.split("\n");
|
|
568
|
+
let message = "";
|
|
569
|
+
let stack = "";
|
|
570
|
+
let inStack = false;
|
|
571
|
+
for (const line of lines) {
|
|
572
|
+
if (this.isErrorLine(line) && !message) {
|
|
573
|
+
message = line.trim();
|
|
574
|
+
inStack = true;
|
|
575
|
+
} else if (inStack && (line.match(/^\s+at\s/) || // JS stack
|
|
576
|
+
line.match(/^\s+File\s/) || // Python stack
|
|
577
|
+
line.match(/^\s+\d+:\s/) || // Rust stack
|
|
578
|
+
line.match(/^\s+from\s/))) {
|
|
579
|
+
stack += line + "\n";
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (!message) {
|
|
583
|
+
for (const line of lines) {
|
|
584
|
+
if (line.trim()) {
|
|
585
|
+
message = line.trim();
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
message: message || output.substring(0, 500),
|
|
592
|
+
stack: stack || void 0
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Check if a line looks like an error message
|
|
597
|
+
*/
|
|
598
|
+
isErrorLine(line) {
|
|
599
|
+
const trimmed = line.trim();
|
|
600
|
+
return /^(Error|TypeError|ReferenceError|SyntaxError|RangeError):/i.test(trimmed) || /^(error|FAIL|fatal|panic)\b/i.test(trimmed) || /^error\[E\d+\]:/.test(trimmed) || /^error TS\d+:/.test(trimmed);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
var defaultErrorDetector = new ErrorDetector();
|
|
604
|
+
|
|
605
|
+
// src/storage/local-store.ts
|
|
606
|
+
import Database from "better-sqlite3";
|
|
607
|
+
import { v4 as uuidv4 } from "uuid";
|
|
608
|
+
import { mkdirSync, existsSync } from "fs";
|
|
609
|
+
import { dirname } from "path";
|
|
610
|
+
|
|
611
|
+
// src/core/hash.ts
|
|
612
|
+
import { createHash } from "crypto";
|
|
613
|
+
function sha256(content) {
|
|
614
|
+
return createHash("sha256").update(content).digest("hex");
|
|
615
|
+
}
|
|
616
|
+
function shortHash(content) {
|
|
617
|
+
return sha256(content).substring(0, 16);
|
|
618
|
+
}
|
|
619
|
+
function generateErrorFingerprint(errorMessage, errorStack) {
|
|
620
|
+
const combined = `${errorMessage}
|
|
621
|
+
${errorStack || ""}`;
|
|
622
|
+
const normalized = normalizeErrorContent(combined);
|
|
623
|
+
return shortHash(normalized);
|
|
624
|
+
}
|
|
625
|
+
function normalizeErrorContent(content) {
|
|
626
|
+
return content.replace(/['"`][^'"`\n]{0,100}['"`]/g, "<STR>").replace(/(?:\/[\w.-]+)+/g, "<PATH>").replace(/(?:[A-Z]:\\[\w.-]+)+/gi, "<PATH>").replace(/:\d+:\d+/g, ":<LINE>:<COL>").replace(/line\s+\d+/gi, "line <LINE>").replace(/0x[a-f0-9]+/gi, "<ADDR>").replace(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi, "<UUID>").replace(/\b[a-f0-9]{8,}\b/gi, "<HASH>").replace(/\b\d{5,}\b/g, "<NUM>").replace(/\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}/g, "<TIMESTAMP>").replace(/\s+/g, " ").trim();
|
|
627
|
+
}
|
|
628
|
+
function generateContributorId() {
|
|
629
|
+
const machineInfo = [
|
|
630
|
+
process.env.USER || process.env.USERNAME || "",
|
|
631
|
+
process.env.HOME || process.env.USERPROFILE || "",
|
|
632
|
+
process.platform,
|
|
633
|
+
process.arch
|
|
634
|
+
].join(":");
|
|
635
|
+
return `anon_${shortHash(machineInfo)}`;
|
|
636
|
+
}
|
|
637
|
+
function generateSessionHash(sessionId) {
|
|
638
|
+
return shortHash(`session:${sessionId}:${Date.now()}`);
|
|
639
|
+
}
|
|
640
|
+
function fingerprintsMatch(fp1, fp2) {
|
|
641
|
+
return fp1 === fp2;
|
|
642
|
+
}
|
|
643
|
+
function calculateStringSimilarity(str1, str2) {
|
|
644
|
+
const words1 = new Set(str1.toLowerCase().split(/\s+/));
|
|
645
|
+
const words2 = new Set(str2.toLowerCase().split(/\s+/));
|
|
646
|
+
const intersection = new Set([...words1].filter((x) => words2.has(x)));
|
|
647
|
+
const union = /* @__PURE__ */ new Set([...words1, ...words2]);
|
|
648
|
+
if (union.size === 0) return 0;
|
|
649
|
+
return intersection.size / union.size;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/storage/migrations.ts
|
|
653
|
+
function runMigrations(db) {
|
|
654
|
+
db.exec(`
|
|
655
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
656
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
657
|
+
name TEXT NOT NULL UNIQUE,
|
|
658
|
+
applied_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
659
|
+
)
|
|
660
|
+
`);
|
|
661
|
+
const appliedMigrations = new Set(
|
|
662
|
+
db.prepare("SELECT name FROM migrations").all().map((r) => r.name)
|
|
663
|
+
);
|
|
664
|
+
for (const migration of MIGRATIONS) {
|
|
665
|
+
if (!appliedMigrations.has(migration.name)) {
|
|
666
|
+
db.exec(migration.sql);
|
|
667
|
+
db.prepare("INSERT INTO migrations (name) VALUES (?)").run(migration.name);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
var MIGRATIONS = [
|
|
672
|
+
{
|
|
673
|
+
name: "001_initial_schema",
|
|
674
|
+
sql: `
|
|
675
|
+
-- Error records table
|
|
676
|
+
CREATE TABLE IF NOT EXISTS error_records (
|
|
677
|
+
id TEXT PRIMARY KEY,
|
|
678
|
+
error_hash TEXT NOT NULL,
|
|
679
|
+
error_type TEXT NOT NULL,
|
|
680
|
+
error_message TEXT NOT NULL,
|
|
681
|
+
error_stack TEXT,
|
|
682
|
+
language TEXT,
|
|
683
|
+
framework TEXT,
|
|
684
|
+
tool_name TEXT NOT NULL,
|
|
685
|
+
tool_input TEXT NOT NULL DEFAULT '{}',
|
|
686
|
+
session_id TEXT NOT NULL,
|
|
687
|
+
status TEXT NOT NULL DEFAULT 'unresolved',
|
|
688
|
+
resolution TEXT,
|
|
689
|
+
resolution_code TEXT,
|
|
690
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
691
|
+
resolved_at TEXT,
|
|
692
|
+
uploaded_at TEXT,
|
|
693
|
+
cloud_knowledge_id TEXT
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
-- Indexes for error_records
|
|
697
|
+
CREATE INDEX IF NOT EXISTS idx_error_records_hash ON error_records(error_hash);
|
|
698
|
+
CREATE INDEX IF NOT EXISTS idx_error_records_status ON error_records(status);
|
|
699
|
+
CREATE INDEX IF NOT EXISTS idx_error_records_session ON error_records(session_id);
|
|
700
|
+
CREATE INDEX IF NOT EXISTS idx_error_records_created ON error_records(created_at);
|
|
701
|
+
|
|
702
|
+
-- Query cache table
|
|
703
|
+
CREATE TABLE IF NOT EXISTS query_cache (
|
|
704
|
+
id TEXT PRIMARY KEY,
|
|
705
|
+
error_hash TEXT NOT NULL,
|
|
706
|
+
results TEXT NOT NULL DEFAULT '[]',
|
|
707
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
708
|
+
expires_at TEXT NOT NULL
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
-- Indexes for query_cache
|
|
712
|
+
CREATE INDEX IF NOT EXISTS idx_query_cache_hash ON query_cache(error_hash);
|
|
713
|
+
CREATE INDEX IF NOT EXISTS idx_query_cache_expires ON query_cache(expires_at);
|
|
714
|
+
|
|
715
|
+
-- User preferences table
|
|
716
|
+
CREATE TABLE IF NOT EXISTS user_preferences (
|
|
717
|
+
key TEXT PRIMARY KEY,
|
|
718
|
+
value TEXT NOT NULL
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
-- Usage statistics table
|
|
722
|
+
CREATE TABLE IF NOT EXISTS usage_stats (
|
|
723
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
724
|
+
total_errors INTEGER NOT NULL DEFAULT 0,
|
|
725
|
+
resolved_errors INTEGER NOT NULL DEFAULT 0,
|
|
726
|
+
uploaded_errors INTEGER NOT NULL DEFAULT 0,
|
|
727
|
+
queries_made INTEGER NOT NULL DEFAULT 0,
|
|
728
|
+
last_sync TEXT
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
-- Initialize usage stats
|
|
732
|
+
INSERT OR IGNORE INTO usage_stats (id) VALUES (1);
|
|
733
|
+
`
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
name: "002_add_confidence_score",
|
|
737
|
+
sql: `
|
|
738
|
+
ALTER TABLE error_records ADD COLUMN confidence REAL DEFAULT 0;
|
|
739
|
+
ALTER TABLE error_records ADD COLUMN severity TEXT DEFAULT 'error';
|
|
740
|
+
`
|
|
741
|
+
}
|
|
742
|
+
];
|
|
743
|
+
|
|
744
|
+
// src/storage/local-store.ts
|
|
745
|
+
var LocalStore = class _LocalStore {
|
|
746
|
+
db;
|
|
747
|
+
constructor(projectDirectory) {
|
|
748
|
+
const dbPath = `${projectDirectory}/.fixhive/fixhive.db`;
|
|
749
|
+
const dir = dirname(dbPath);
|
|
750
|
+
if (!existsSync(dir)) {
|
|
751
|
+
mkdirSync(dir, { recursive: true });
|
|
752
|
+
}
|
|
753
|
+
this.db = new Database(dbPath);
|
|
754
|
+
this.db.pragma("journal_mode = WAL");
|
|
755
|
+
this.db.pragma("foreign_keys = ON");
|
|
756
|
+
runMigrations(this.db);
|
|
757
|
+
}
|
|
758
|
+
// ============ Error Records ============
|
|
759
|
+
/**
|
|
760
|
+
* Create a new error record
|
|
761
|
+
*/
|
|
762
|
+
createErrorRecord(data) {
|
|
763
|
+
const id = uuidv4();
|
|
764
|
+
const errorHash = generateErrorFingerprint(data.errorMessage, data.errorStack);
|
|
765
|
+
const stmt = this.db.prepare(`
|
|
766
|
+
INSERT INTO error_records (
|
|
767
|
+
id, error_hash, error_type, error_message, error_stack,
|
|
768
|
+
language, framework, tool_name, tool_input, session_id, status
|
|
769
|
+
) VALUES (
|
|
770
|
+
@id, @errorHash, @errorType, @errorMessage, @errorStack,
|
|
771
|
+
@language, @framework, @toolName, @toolInput, @sessionId, 'unresolved'
|
|
772
|
+
)
|
|
773
|
+
`);
|
|
774
|
+
stmt.run({
|
|
775
|
+
id,
|
|
776
|
+
errorHash,
|
|
777
|
+
errorType: data.errorType,
|
|
778
|
+
errorMessage: data.errorMessage,
|
|
779
|
+
errorStack: data.errorStack || null,
|
|
780
|
+
language: data.language || null,
|
|
781
|
+
framework: data.framework || null,
|
|
782
|
+
toolName: data.toolName,
|
|
783
|
+
toolInput: JSON.stringify(data.toolInput),
|
|
784
|
+
sessionId: data.sessionId
|
|
785
|
+
});
|
|
786
|
+
this.incrementStat("total_errors");
|
|
787
|
+
return this.getErrorById(id);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Get error record by ID
|
|
791
|
+
*/
|
|
792
|
+
getErrorById(id) {
|
|
793
|
+
const stmt = this.db.prepare("SELECT * FROM error_records WHERE id = ?");
|
|
794
|
+
const row = stmt.get(id);
|
|
795
|
+
return row ? this.rowToRecord(row) : null;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Get errors by session
|
|
799
|
+
*/
|
|
800
|
+
getSessionErrors(sessionId, options) {
|
|
801
|
+
let query = "SELECT * FROM error_records WHERE session_id = ?";
|
|
802
|
+
const params = [sessionId];
|
|
803
|
+
if (options?.status) {
|
|
804
|
+
query += " AND status = ?";
|
|
805
|
+
params.push(options.status);
|
|
806
|
+
}
|
|
807
|
+
query += " ORDER BY created_at DESC";
|
|
808
|
+
if (options?.limit) {
|
|
809
|
+
query += " LIMIT ?";
|
|
810
|
+
params.push(options.limit);
|
|
811
|
+
}
|
|
812
|
+
const stmt = this.db.prepare(query);
|
|
813
|
+
return stmt.all(...params).map((row) => this.rowToRecord(row));
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Get unresolved errors for a session
|
|
817
|
+
*/
|
|
818
|
+
getUnresolvedErrors(sessionId) {
|
|
819
|
+
return this.getSessionErrors(sessionId, { status: "unresolved" });
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Get recent errors across all sessions
|
|
823
|
+
*/
|
|
824
|
+
getRecentErrors(limit = 10) {
|
|
825
|
+
const stmt = this.db.prepare(
|
|
826
|
+
"SELECT * FROM error_records ORDER BY created_at DESC LIMIT ?"
|
|
827
|
+
);
|
|
828
|
+
return stmt.all(limit).map((row) => this.rowToRecord(row));
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Mark error as resolved
|
|
832
|
+
*/
|
|
833
|
+
markResolved(id, data) {
|
|
834
|
+
const stmt = this.db.prepare(`
|
|
835
|
+
UPDATE error_records
|
|
836
|
+
SET status = 'resolved',
|
|
837
|
+
resolution = ?,
|
|
838
|
+
resolution_code = ?,
|
|
839
|
+
resolved_at = datetime('now')
|
|
840
|
+
WHERE id = ?
|
|
841
|
+
`);
|
|
842
|
+
const result = stmt.run(data.resolution, data.resolutionCode || null, id);
|
|
843
|
+
if (result.changes > 0) {
|
|
844
|
+
this.incrementStat("resolved_errors");
|
|
845
|
+
return this.getErrorById(id);
|
|
846
|
+
}
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Mark error as uploaded to cloud
|
|
851
|
+
*/
|
|
852
|
+
markUploaded(id, cloudKnowledgeId) {
|
|
853
|
+
const stmt = this.db.prepare(`
|
|
854
|
+
UPDATE error_records
|
|
855
|
+
SET status = 'uploaded',
|
|
856
|
+
cloud_knowledge_id = ?,
|
|
857
|
+
uploaded_at = datetime('now')
|
|
858
|
+
WHERE id = ?
|
|
859
|
+
`);
|
|
860
|
+
const result = stmt.run(cloudKnowledgeId, id);
|
|
861
|
+
if (result.changes > 0) {
|
|
862
|
+
this.incrementStat("uploaded_errors");
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Find similar errors by hash
|
|
867
|
+
*/
|
|
868
|
+
findSimilarErrors(errorHash) {
|
|
869
|
+
const stmt = this.db.prepare(
|
|
870
|
+
"SELECT * FROM error_records WHERE error_hash = ? ORDER BY created_at DESC"
|
|
871
|
+
);
|
|
872
|
+
return stmt.all(errorHash).map((row) => this.rowToRecord(row));
|
|
873
|
+
}
|
|
874
|
+
// ============ Query Cache ============
|
|
875
|
+
/**
|
|
876
|
+
* Get cached query results
|
|
877
|
+
*/
|
|
878
|
+
getCachedResults(errorHash) {
|
|
879
|
+
const stmt = this.db.prepare(`
|
|
880
|
+
SELECT results FROM query_cache
|
|
881
|
+
WHERE error_hash = ? AND expires_at > datetime('now')
|
|
882
|
+
ORDER BY created_at DESC LIMIT 1
|
|
883
|
+
`);
|
|
884
|
+
const row = stmt.get(errorHash);
|
|
885
|
+
if (row) {
|
|
886
|
+
return JSON.parse(row.results);
|
|
887
|
+
}
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Cache query results
|
|
892
|
+
*/
|
|
893
|
+
cacheResults(errorHash, results, expirationMs = 36e5) {
|
|
894
|
+
const id = uuidv4();
|
|
895
|
+
const expiresAt = new Date(Date.now() + expirationMs).toISOString();
|
|
896
|
+
const stmt = this.db.prepare(`
|
|
897
|
+
INSERT INTO query_cache (id, error_hash, results, expires_at)
|
|
898
|
+
VALUES (?, ?, ?, ?)
|
|
899
|
+
`);
|
|
900
|
+
stmt.run(id, errorHash, JSON.stringify(results), expiresAt);
|
|
901
|
+
this.incrementStat("queries_made");
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Clear expired cache entries
|
|
905
|
+
*/
|
|
906
|
+
clearExpiredCache() {
|
|
907
|
+
const stmt = this.db.prepare("DELETE FROM query_cache WHERE expires_at <= datetime('now')");
|
|
908
|
+
const result = stmt.run();
|
|
909
|
+
return result.changes;
|
|
910
|
+
}
|
|
911
|
+
// ============ Statistics ============
|
|
912
|
+
/**
|
|
913
|
+
* Get usage statistics
|
|
914
|
+
*/
|
|
915
|
+
getStats() {
|
|
916
|
+
const stmt = this.db.prepare(
|
|
917
|
+
"SELECT total_errors, resolved_errors, uploaded_errors FROM usage_stats WHERE id = 1"
|
|
918
|
+
);
|
|
919
|
+
const row = stmt.get();
|
|
920
|
+
return {
|
|
921
|
+
totalErrors: row.total_errors,
|
|
922
|
+
resolvedErrors: row.resolved_errors,
|
|
923
|
+
uploadedErrors: row.uploaded_errors
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Allowed stat column names for incrementStat (whitelist to prevent SQL injection)
|
|
928
|
+
*/
|
|
929
|
+
static ALLOWED_STATS = [
|
|
930
|
+
"total_errors",
|
|
931
|
+
"resolved_errors",
|
|
932
|
+
"uploaded_errors",
|
|
933
|
+
"queries_made"
|
|
934
|
+
];
|
|
935
|
+
/**
|
|
936
|
+
* Increment a stat counter
|
|
937
|
+
* @throws Error if stat name is not in the allowed whitelist
|
|
938
|
+
*/
|
|
939
|
+
incrementStat(stat) {
|
|
940
|
+
if (!_LocalStore.ALLOWED_STATS.includes(stat)) {
|
|
941
|
+
throw new Error(`Invalid stat name: ${stat}. Allowed: ${_LocalStore.ALLOWED_STATS.join(", ")}`);
|
|
942
|
+
}
|
|
943
|
+
const stmt = this.db.prepare(`UPDATE usage_stats SET ${stat} = ${stat} + 1 WHERE id = 1`);
|
|
944
|
+
stmt.run();
|
|
945
|
+
}
|
|
946
|
+
// ============ Preferences ============
|
|
947
|
+
/**
|
|
948
|
+
* Get preference value
|
|
949
|
+
*/
|
|
950
|
+
getPreference(key) {
|
|
951
|
+
const stmt = this.db.prepare("SELECT value FROM user_preferences WHERE key = ?");
|
|
952
|
+
const row = stmt.get(key);
|
|
953
|
+
return row?.value || null;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Set preference value
|
|
957
|
+
*/
|
|
958
|
+
setPreference(key, value) {
|
|
959
|
+
const stmt = this.db.prepare(`
|
|
960
|
+
INSERT OR REPLACE INTO user_preferences (key, value) VALUES (?, ?)
|
|
961
|
+
`);
|
|
962
|
+
stmt.run(key, value);
|
|
963
|
+
}
|
|
964
|
+
// ============ Utilities ============
|
|
965
|
+
/**
|
|
966
|
+
* Convert database row to LocalErrorRecord
|
|
967
|
+
*/
|
|
968
|
+
rowToRecord(row) {
|
|
969
|
+
return {
|
|
970
|
+
id: row.id,
|
|
971
|
+
errorHash: row.error_hash,
|
|
972
|
+
errorType: row.error_type,
|
|
973
|
+
errorMessage: row.error_message,
|
|
974
|
+
errorStack: row.error_stack || void 0,
|
|
975
|
+
language: row.language || void 0,
|
|
976
|
+
framework: row.framework || void 0,
|
|
977
|
+
toolName: row.tool_name,
|
|
978
|
+
toolInput: JSON.parse(row.tool_input || "{}"),
|
|
979
|
+
sessionId: row.session_id,
|
|
980
|
+
status: row.status,
|
|
981
|
+
resolution: row.resolution || void 0,
|
|
982
|
+
resolutionCode: row.resolution_code || void 0,
|
|
983
|
+
createdAt: row.created_at,
|
|
984
|
+
resolvedAt: row.resolved_at || void 0,
|
|
985
|
+
uploadedAt: row.uploaded_at || void 0,
|
|
986
|
+
cloudKnowledgeId: row.cloud_knowledge_id || void 0
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Close database connection
|
|
991
|
+
*/
|
|
992
|
+
close() {
|
|
993
|
+
this.db.close();
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Get database for advanced queries
|
|
997
|
+
*/
|
|
998
|
+
getDatabase() {
|
|
999
|
+
return this.db;
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
// src/cloud/client.ts
|
|
1004
|
+
import { createClient } from "@supabase/supabase-js";
|
|
1005
|
+
|
|
1006
|
+
// src/cloud/embedding.ts
|
|
1007
|
+
import OpenAI from "openai";
|
|
1008
|
+
var DEFAULT_MODEL = "text-embedding-3-small";
|
|
1009
|
+
var DEFAULT_DIMENSIONS = 1536;
|
|
1010
|
+
var MAX_INPUT_LENGTH = 3e4;
|
|
1011
|
+
var EmbeddingService = class {
|
|
1012
|
+
client;
|
|
1013
|
+
model;
|
|
1014
|
+
dimensions;
|
|
1015
|
+
constructor(apiKey, model, dimensions) {
|
|
1016
|
+
this.client = new OpenAI({ apiKey });
|
|
1017
|
+
this.model = model || DEFAULT_MODEL;
|
|
1018
|
+
this.dimensions = dimensions || DEFAULT_DIMENSIONS;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Generate embedding for a single text
|
|
1022
|
+
*/
|
|
1023
|
+
async generate(text) {
|
|
1024
|
+
const truncated = this.truncateText(text);
|
|
1025
|
+
const response = await this.client.embeddings.create({
|
|
1026
|
+
model: this.model,
|
|
1027
|
+
input: truncated,
|
|
1028
|
+
dimensions: this.dimensions
|
|
1029
|
+
});
|
|
1030
|
+
return response.data[0].embedding;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Generate embeddings for multiple texts
|
|
1034
|
+
*/
|
|
1035
|
+
async generateBatch(texts) {
|
|
1036
|
+
const truncated = texts.map((t) => this.truncateText(t));
|
|
1037
|
+
const response = await this.client.embeddings.create({
|
|
1038
|
+
model: this.model,
|
|
1039
|
+
input: truncated,
|
|
1040
|
+
dimensions: this.dimensions
|
|
1041
|
+
});
|
|
1042
|
+
return response.data.map((d) => d.embedding);
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Generate embedding for error context
|
|
1046
|
+
* Combines error message, stack trace, and context
|
|
1047
|
+
*/
|
|
1048
|
+
async generateErrorEmbedding(errorMessage, errorStack, context) {
|
|
1049
|
+
const parts = [];
|
|
1050
|
+
if (context?.language) {
|
|
1051
|
+
parts.push(`Language: ${context.language}`);
|
|
1052
|
+
}
|
|
1053
|
+
if (context?.framework) {
|
|
1054
|
+
parts.push(`Framework: ${context.framework}`);
|
|
1055
|
+
}
|
|
1056
|
+
parts.push(`Error: ${errorMessage}`);
|
|
1057
|
+
if (errorStack) {
|
|
1058
|
+
parts.push(`Stack Trace:
|
|
1059
|
+
${errorStack}`);
|
|
1060
|
+
}
|
|
1061
|
+
const text = parts.join("\n");
|
|
1062
|
+
return this.generate(text);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Truncate text to fit within model limits
|
|
1066
|
+
*/
|
|
1067
|
+
truncateText(text) {
|
|
1068
|
+
if (text.length <= MAX_INPUT_LENGTH) {
|
|
1069
|
+
return text;
|
|
1070
|
+
}
|
|
1071
|
+
const truncated = text.substring(0, MAX_INPUT_LENGTH);
|
|
1072
|
+
const lastNewline = truncated.lastIndexOf("\n");
|
|
1073
|
+
if (lastNewline > MAX_INPUT_LENGTH * 0.8) {
|
|
1074
|
+
return truncated.substring(0, lastNewline);
|
|
1075
|
+
}
|
|
1076
|
+
return truncated;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Calculate cosine similarity between two embeddings
|
|
1080
|
+
*/
|
|
1081
|
+
static cosineSimilarity(a, b) {
|
|
1082
|
+
if (a.length !== b.length) {
|
|
1083
|
+
throw new Error("Embeddings must have same dimensions");
|
|
1084
|
+
}
|
|
1085
|
+
let dotProduct = 0;
|
|
1086
|
+
let normA = 0;
|
|
1087
|
+
let normB = 0;
|
|
1088
|
+
for (let i = 0; i < a.length; i++) {
|
|
1089
|
+
dotProduct += a[i] * b[i];
|
|
1090
|
+
normA += a[i] * a[i];
|
|
1091
|
+
normB += b[i] * b[i];
|
|
1092
|
+
}
|
|
1093
|
+
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1094
|
+
if (magnitude === 0) return 0;
|
|
1095
|
+
return dotProduct / magnitude;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Get embedding dimensions
|
|
1099
|
+
*/
|
|
1100
|
+
getDimensions() {
|
|
1101
|
+
return this.dimensions;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Get model name
|
|
1105
|
+
*/
|
|
1106
|
+
getModel() {
|
|
1107
|
+
return this.model;
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
function createEmbeddingService(config) {
|
|
1111
|
+
return new EmbeddingService(config.apiKey, config.model, config.dimensions);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/cloud/client.ts
|
|
1115
|
+
var CloudClient = class {
|
|
1116
|
+
supabase;
|
|
1117
|
+
embedding;
|
|
1118
|
+
contributorId;
|
|
1119
|
+
similarityThreshold;
|
|
1120
|
+
constructor(config) {
|
|
1121
|
+
this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
|
|
1122
|
+
this.embedding = config.openaiApiKey ? new EmbeddingService(config.openaiApiKey) : null;
|
|
1123
|
+
this.contributorId = config.contributorId || generateContributorId();
|
|
1124
|
+
this.similarityThreshold = config.similarityThreshold || 0.7;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Search for similar errors in cloud knowledge base
|
|
1128
|
+
*/
|
|
1129
|
+
async searchSimilar(request) {
|
|
1130
|
+
const startTime = Date.now();
|
|
1131
|
+
if (!this.embedding) {
|
|
1132
|
+
return this.searchByText(request);
|
|
1133
|
+
}
|
|
1134
|
+
const queryText = `${request.errorMessage}
|
|
1135
|
+
${request.errorStack || ""}`;
|
|
1136
|
+
const embedding = await this.embedding.generate(queryText);
|
|
1137
|
+
const { data, error } = await this.supabase.rpc("search_similar_errors", {
|
|
1138
|
+
query_embedding: embedding,
|
|
1139
|
+
match_threshold: request.threshold || this.similarityThreshold,
|
|
1140
|
+
match_count: request.limit || 10,
|
|
1141
|
+
filter_language: request.language || null,
|
|
1142
|
+
filter_framework: request.framework || null
|
|
1143
|
+
});
|
|
1144
|
+
if (error) {
|
|
1145
|
+
console.error("Search error:", error);
|
|
1146
|
+
return { results: [], queryTime: Date.now() - startTime, cached: false };
|
|
1147
|
+
}
|
|
1148
|
+
return {
|
|
1149
|
+
results: (data || []).map(this.mapToKnowledgeEntry),
|
|
1150
|
+
queryTime: Date.now() - startTime,
|
|
1151
|
+
cached: false
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Fallback text-based search
|
|
1156
|
+
*/
|
|
1157
|
+
async searchByText(request) {
|
|
1158
|
+
const startTime = Date.now();
|
|
1159
|
+
let query = this.supabase.from("knowledge_entries").select("*").ilike("error_message", `%${request.errorMessage.substring(0, 100)}%`).order("upvotes", { ascending: false }).limit(request.limit || 10);
|
|
1160
|
+
if (request.language) {
|
|
1161
|
+
query = query.eq("language", request.language);
|
|
1162
|
+
}
|
|
1163
|
+
if (request.framework) {
|
|
1164
|
+
query = query.eq("framework", request.framework);
|
|
1165
|
+
}
|
|
1166
|
+
const { data, error } = await query;
|
|
1167
|
+
if (error) {
|
|
1168
|
+
console.error("Text search error:", error);
|
|
1169
|
+
return { results: [], queryTime: Date.now() - startTime, cached: false };
|
|
1170
|
+
}
|
|
1171
|
+
return {
|
|
1172
|
+
results: (data || []).map(this.mapToKnowledgeEntry),
|
|
1173
|
+
queryTime: Date.now() - startTime,
|
|
1174
|
+
cached: false
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Upload a resolution to cloud knowledge base
|
|
1179
|
+
*/
|
|
1180
|
+
async uploadResolution(request) {
|
|
1181
|
+
const { errorRecord, resolution, resolutionCode, resolutionSteps } = request;
|
|
1182
|
+
let embedding = null;
|
|
1183
|
+
if (this.embedding) {
|
|
1184
|
+
const embeddingText = `${errorRecord.errorMessage}
|
|
1185
|
+
${errorRecord.errorStack || ""}`;
|
|
1186
|
+
embedding = await this.embedding.generate(embeddingText);
|
|
1187
|
+
}
|
|
1188
|
+
if (embedding) {
|
|
1189
|
+
const duplicateCheck = await this.checkDuplicate(errorRecord.errorHash, embedding);
|
|
1190
|
+
if (duplicateCheck.isDuplicate && duplicateCheck.similarityScore > 0.95) {
|
|
1191
|
+
await this.supabase.rpc("increment_usage_count", {
|
|
1192
|
+
entry_id: duplicateCheck.existingId
|
|
1193
|
+
});
|
|
1194
|
+
return {
|
|
1195
|
+
success: true,
|
|
1196
|
+
isDuplicate: true,
|
|
1197
|
+
existingId: duplicateCheck.existingId,
|
|
1198
|
+
message: "Similar solution already exists. Usage count incremented."
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
const { data, error } = await this.supabase.from("knowledge_entries").insert({
|
|
1203
|
+
error_hash: errorRecord.errorHash,
|
|
1204
|
+
error_type: errorRecord.errorType,
|
|
1205
|
+
error_message: errorRecord.errorMessage,
|
|
1206
|
+
error_stack: errorRecord.errorStack,
|
|
1207
|
+
language: errorRecord.language || "other",
|
|
1208
|
+
framework: errorRecord.framework,
|
|
1209
|
+
embedding,
|
|
1210
|
+
resolution_description: resolution,
|
|
1211
|
+
resolution_code: resolutionCode,
|
|
1212
|
+
resolution_steps: resolutionSteps,
|
|
1213
|
+
contributor_id: this.contributorId
|
|
1214
|
+
}).select("id").single();
|
|
1215
|
+
if (error) {
|
|
1216
|
+
return {
|
|
1217
|
+
success: false,
|
|
1218
|
+
isDuplicate: false,
|
|
1219
|
+
message: `Upload failed: ${error.message}`
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
return {
|
|
1223
|
+
success: true,
|
|
1224
|
+
knowledgeId: data.id,
|
|
1225
|
+
isDuplicate: false,
|
|
1226
|
+
message: "Solution uploaded successfully!"
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Check for duplicate entries
|
|
1231
|
+
*/
|
|
1232
|
+
async checkDuplicate(errorHash, embedding) {
|
|
1233
|
+
const { data: hashMatch } = await this.supabase.from("knowledge_entries").select("id").eq("error_hash", errorHash).limit(1).single();
|
|
1234
|
+
if (hashMatch) {
|
|
1235
|
+
return {
|
|
1236
|
+
isDuplicate: true,
|
|
1237
|
+
existingId: hashMatch.id,
|
|
1238
|
+
similarityScore: 1
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
const { data, error } = await this.supabase.rpc("check_duplicate_entry", {
|
|
1242
|
+
new_hash: errorHash,
|
|
1243
|
+
new_embedding: embedding,
|
|
1244
|
+
similarity_threshold: 0.95
|
|
1245
|
+
});
|
|
1246
|
+
if (error || !data || data.length === 0) {
|
|
1247
|
+
return { isDuplicate: false, similarityScore: 0 };
|
|
1248
|
+
}
|
|
1249
|
+
const result = data[0];
|
|
1250
|
+
return {
|
|
1251
|
+
isDuplicate: result.is_duplicate,
|
|
1252
|
+
existingId: result.existing_id,
|
|
1253
|
+
similarityScore: result.similarity_score
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Vote on a knowledge entry
|
|
1258
|
+
*/
|
|
1259
|
+
async vote(knowledgeId, helpful) {
|
|
1260
|
+
const column = helpful ? "upvotes" : "downvotes";
|
|
1261
|
+
await this.supabase.rpc("increment_vote", {
|
|
1262
|
+
entry_id: knowledgeId,
|
|
1263
|
+
vote_type: column
|
|
1264
|
+
});
|
|
1265
|
+
await this.supabase.from("usage_logs").insert({
|
|
1266
|
+
knowledge_id: knowledgeId,
|
|
1267
|
+
action: helpful ? "upvote" : "downvote",
|
|
1268
|
+
user_hash: this.contributorId
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Report helpful usage
|
|
1273
|
+
*/
|
|
1274
|
+
async reportHelpful(knowledgeId) {
|
|
1275
|
+
await this.supabase.rpc("increment_usage_count", {
|
|
1276
|
+
entry_id: knowledgeId
|
|
1277
|
+
});
|
|
1278
|
+
await this.supabase.from("usage_logs").insert({
|
|
1279
|
+
knowledge_id: knowledgeId,
|
|
1280
|
+
action: "apply",
|
|
1281
|
+
user_hash: this.contributorId
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Get contributor statistics
|
|
1286
|
+
*/
|
|
1287
|
+
async getContributorStats() {
|
|
1288
|
+
const { data } = await this.supabase.from("knowledge_entries").select("upvotes, usage_count").eq("contributor_id", this.contributorId);
|
|
1289
|
+
if (!data || data.length === 0) {
|
|
1290
|
+
return { contributionCount: 0, helpedCount: 0, totalUpvotes: 0 };
|
|
1291
|
+
}
|
|
1292
|
+
return {
|
|
1293
|
+
contributionCount: data.length,
|
|
1294
|
+
helpedCount: data.reduce((sum, e) => sum + (e.usage_count || 0), 0),
|
|
1295
|
+
totalUpvotes: data.reduce((sum, e) => sum + (e.upvotes || 0), 0)
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Get entry by ID
|
|
1300
|
+
*/
|
|
1301
|
+
async getEntry(id) {
|
|
1302
|
+
const { data, error } = await this.supabase.from("knowledge_entries").select("*").eq("id", id).single();
|
|
1303
|
+
if (error || !data) {
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
return this.mapToKnowledgeEntry(data);
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Map database row to CloudKnowledgeEntry
|
|
1310
|
+
*/
|
|
1311
|
+
mapToKnowledgeEntry(row) {
|
|
1312
|
+
return {
|
|
1313
|
+
id: row.id,
|
|
1314
|
+
errorHash: row.error_hash,
|
|
1315
|
+
errorType: row.error_type,
|
|
1316
|
+
errorMessage: row.error_message,
|
|
1317
|
+
errorStack: row.error_stack || void 0,
|
|
1318
|
+
language: row.language,
|
|
1319
|
+
framework: row.framework || void 0,
|
|
1320
|
+
dependencies: row.dependencies || void 0,
|
|
1321
|
+
resolutionDescription: row.resolution_description,
|
|
1322
|
+
resolutionCode: row.resolution_code || void 0,
|
|
1323
|
+
resolutionSteps: row.resolution_steps || void 0,
|
|
1324
|
+
contributorId: row.contributor_id,
|
|
1325
|
+
upvotes: row.upvotes || 0,
|
|
1326
|
+
downvotes: row.downvotes || 0,
|
|
1327
|
+
usageCount: row.usage_count || 0,
|
|
1328
|
+
createdAt: row.created_at,
|
|
1329
|
+
updatedAt: row.updated_at,
|
|
1330
|
+
isVerified: row.is_verified || false,
|
|
1331
|
+
similarity: row.similarity || void 0
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Get contributor ID
|
|
1336
|
+
*/
|
|
1337
|
+
getContributorId() {
|
|
1338
|
+
return this.contributorId;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Check if embedding service is available
|
|
1342
|
+
*/
|
|
1343
|
+
hasEmbeddingService() {
|
|
1344
|
+
return this.embedding !== null;
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
function createCloudClient(config) {
|
|
1348
|
+
return new CloudClient(config);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// src/plugin/tools.ts
|
|
1352
|
+
import { tool } from "@opencode-ai/plugin";
|
|
1353
|
+
function createTools(localStore, cloudClient, privacyFilter, context) {
|
|
1354
|
+
return {
|
|
1355
|
+
/**
|
|
1356
|
+
* Search cloud knowledge base for error solutions
|
|
1357
|
+
*/
|
|
1358
|
+
fixhive_search: tool({
|
|
1359
|
+
description: "Search FixHive knowledge base for error solutions. Use when encountering errors to find community solutions.",
|
|
1360
|
+
args: {
|
|
1361
|
+
errorMessage: tool.schema.string().describe("The error message to search for"),
|
|
1362
|
+
language: tool.schema.string().optional().describe("Programming language (typescript, python, etc.)"),
|
|
1363
|
+
framework: tool.schema.string().optional().describe("Framework (react, nextjs, express, etc.)"),
|
|
1364
|
+
limit: tool.schema.number().optional().describe("Maximum results (default: 5)")
|
|
1365
|
+
},
|
|
1366
|
+
async execute(args, ctx) {
|
|
1367
|
+
context.sessionId = ctx.sessionID;
|
|
1368
|
+
const sanitized = privacyFilter.sanitize(args.errorMessage);
|
|
1369
|
+
const cachedResults = localStore.getCachedResults(sanitized.sanitized);
|
|
1370
|
+
if (cachedResults && cachedResults.length > 0) {
|
|
1371
|
+
return formatSearchResults(cachedResults, true);
|
|
1372
|
+
}
|
|
1373
|
+
const results = await cloudClient.searchSimilar({
|
|
1374
|
+
errorMessage: sanitized.sanitized,
|
|
1375
|
+
language: args.language,
|
|
1376
|
+
framework: args.framework,
|
|
1377
|
+
limit: args.limit || 5
|
|
1378
|
+
});
|
|
1379
|
+
if (results.results.length === 0) {
|
|
1380
|
+
return "No matching solutions found in FixHive knowledge base.";
|
|
1381
|
+
}
|
|
1382
|
+
localStore.cacheResults(sanitized.sanitized, results.results);
|
|
1383
|
+
return formatSearchResults(results.results, false);
|
|
1384
|
+
}
|
|
1385
|
+
}),
|
|
1386
|
+
/**
|
|
1387
|
+
* Mark error as resolved and optionally upload solution
|
|
1388
|
+
*/
|
|
1389
|
+
fixhive_resolve: tool({
|
|
1390
|
+
description: "Mark an error as resolved and optionally share the solution with the community.",
|
|
1391
|
+
args: {
|
|
1392
|
+
errorId: tool.schema.string().describe("Error ID from fixhive_list"),
|
|
1393
|
+
resolution: tool.schema.string().describe("Description of how the error was resolved"),
|
|
1394
|
+
resolutionCode: tool.schema.string().optional().describe("Code fix or diff that resolved the error"),
|
|
1395
|
+
upload: tool.schema.boolean().optional().describe("Upload solution to community (default: true)")
|
|
1396
|
+
},
|
|
1397
|
+
async execute(args, ctx) {
|
|
1398
|
+
context.sessionId = ctx.sessionID;
|
|
1399
|
+
const record = localStore.markResolved(args.errorId, {
|
|
1400
|
+
resolution: args.resolution,
|
|
1401
|
+
resolutionCode: args.resolutionCode
|
|
1402
|
+
});
|
|
1403
|
+
if (!record) {
|
|
1404
|
+
return `Error with ID ${args.errorId} not found.`;
|
|
1405
|
+
}
|
|
1406
|
+
if (args.upload !== false) {
|
|
1407
|
+
const uploadResult = await cloudClient.uploadResolution({
|
|
1408
|
+
errorRecord: record,
|
|
1409
|
+
resolution: args.resolution,
|
|
1410
|
+
resolutionCode: args.resolutionCode
|
|
1411
|
+
});
|
|
1412
|
+
if (uploadResult.isDuplicate) {
|
|
1413
|
+
return `Error resolved locally. Similar solution already exists in FixHive (ID: ${uploadResult.existingId}).`;
|
|
1414
|
+
}
|
|
1415
|
+
if (uploadResult.success && uploadResult.knowledgeId) {
|
|
1416
|
+
localStore.markUploaded(args.errorId, uploadResult.knowledgeId);
|
|
1417
|
+
return `Error resolved and shared with FixHive community! Knowledge ID: ${uploadResult.knowledgeId}`;
|
|
1418
|
+
}
|
|
1419
|
+
return `Error resolved locally. Upload failed: ${uploadResult.message}`;
|
|
1420
|
+
}
|
|
1421
|
+
return "Error marked as resolved locally.";
|
|
1422
|
+
}
|
|
1423
|
+
}),
|
|
1424
|
+
/**
|
|
1425
|
+
* List errors in current session
|
|
1426
|
+
*/
|
|
1427
|
+
fixhive_list: tool({
|
|
1428
|
+
description: "List errors detected in the current session.",
|
|
1429
|
+
args: {
|
|
1430
|
+
status: tool.schema.enum(["unresolved", "resolved", "uploaded"]).optional().describe("Filter by status"),
|
|
1431
|
+
limit: tool.schema.number().optional().describe("Maximum results (default: 10)")
|
|
1432
|
+
},
|
|
1433
|
+
async execute(args, ctx) {
|
|
1434
|
+
context.sessionId = ctx.sessionID;
|
|
1435
|
+
const errors = localStore.getSessionErrors(ctx.sessionID, {
|
|
1436
|
+
status: args.status,
|
|
1437
|
+
limit: args.limit || 10
|
|
1438
|
+
});
|
|
1439
|
+
if (errors.length === 0) {
|
|
1440
|
+
return "No errors recorded in this session.";
|
|
1441
|
+
}
|
|
1442
|
+
return formatErrorList(errors);
|
|
1443
|
+
}
|
|
1444
|
+
}),
|
|
1445
|
+
/**
|
|
1446
|
+
* Vote on a solution
|
|
1447
|
+
*/
|
|
1448
|
+
fixhive_vote: tool({
|
|
1449
|
+
description: "Upvote or downvote a FixHive solution based on whether it helped.",
|
|
1450
|
+
args: {
|
|
1451
|
+
knowledgeId: tool.schema.string().describe("Knowledge entry ID"),
|
|
1452
|
+
helpful: tool.schema.boolean().describe("True for upvote, false for downvote")
|
|
1453
|
+
},
|
|
1454
|
+
async execute(args) {
|
|
1455
|
+
await cloudClient.vote(args.knowledgeId, args.helpful);
|
|
1456
|
+
return args.helpful ? "Thanks for the feedback! Solution upvoted." : "Thanks for the feedback! Solution downvoted.";
|
|
1457
|
+
}
|
|
1458
|
+
}),
|
|
1459
|
+
/**
|
|
1460
|
+
* Get usage statistics
|
|
1461
|
+
*/
|
|
1462
|
+
fixhive_stats: tool({
|
|
1463
|
+
description: "Get FixHive usage statistics.",
|
|
1464
|
+
args: {},
|
|
1465
|
+
async execute() {
|
|
1466
|
+
const localStats = localStore.getStats();
|
|
1467
|
+
const cloudStats = await cloudClient.getContributorStats();
|
|
1468
|
+
return `
|
|
1469
|
+
## FixHive Statistics
|
|
1470
|
+
|
|
1471
|
+
### Local
|
|
1472
|
+
- Errors recorded: ${localStats.totalErrors}
|
|
1473
|
+
- Resolved: ${localStats.resolvedErrors}
|
|
1474
|
+
- Uploaded: ${localStats.uploadedErrors}
|
|
1475
|
+
|
|
1476
|
+
### Community Contributions
|
|
1477
|
+
- Solutions shared: ${cloudStats.contributionCount}
|
|
1478
|
+
- Times your solutions helped: ${cloudStats.helpedCount}
|
|
1479
|
+
- Total upvotes received: ${cloudStats.totalUpvotes}
|
|
1480
|
+
`;
|
|
1481
|
+
}
|
|
1482
|
+
}),
|
|
1483
|
+
/**
|
|
1484
|
+
* Report that a solution was helpful
|
|
1485
|
+
*/
|
|
1486
|
+
fixhive_helpful: tool({
|
|
1487
|
+
description: "Report that a FixHive solution was helpful and resolved your issue.",
|
|
1488
|
+
args: {
|
|
1489
|
+
knowledgeId: tool.schema.string().describe("Knowledge entry ID that helped")
|
|
1490
|
+
},
|
|
1491
|
+
async execute(args) {
|
|
1492
|
+
await cloudClient.reportHelpful(args.knowledgeId);
|
|
1493
|
+
return "Thanks! Your feedback helps improve solution rankings.";
|
|
1494
|
+
}
|
|
1495
|
+
})
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
function formatSearchResults(results, cached) {
|
|
1499
|
+
const header = cached ? `## FixHive Solutions Found (${results.length}) [Cached]` : `## FixHive Solutions Found (${results.length})`;
|
|
1500
|
+
const entries = results.map((r, i) => {
|
|
1501
|
+
const similarity = r.similarity ? ` (${Math.round(r.similarity * 100)}% match)` : "";
|
|
1502
|
+
let entry = `
|
|
1503
|
+
### ${i + 1}. ${r.errorMessage.slice(0, 80)}...${similarity}
|
|
1504
|
+
**Language:** ${r.language} | **Framework:** ${r.framework || "N/A"} | **Upvotes:** ${r.upvotes}
|
|
1505
|
+
|
|
1506
|
+
**Resolution:**
|
|
1507
|
+
${r.resolutionDescription}
|
|
1508
|
+
`;
|
|
1509
|
+
if (r.resolutionCode) {
|
|
1510
|
+
entry += `
|
|
1511
|
+
**Code Fix:**
|
|
1512
|
+
\`\`\`
|
|
1513
|
+
${r.resolutionCode}
|
|
1514
|
+
\`\`\`
|
|
1515
|
+
`;
|
|
1516
|
+
}
|
|
1517
|
+
if (r.resolutionSteps?.length) {
|
|
1518
|
+
entry += `
|
|
1519
|
+
**Steps:**
|
|
1520
|
+
${r.resolutionSteps.map((s, j) => `${j + 1}. ${s}`).join("\n")}
|
|
1521
|
+
`;
|
|
1522
|
+
}
|
|
1523
|
+
entry += `
|
|
1524
|
+
*ID: ${r.id}*
|
|
1525
|
+
|
|
1526
|
+
---`;
|
|
1527
|
+
return entry;
|
|
1528
|
+
}).join("\n");
|
|
1529
|
+
return `${header}
|
|
1530
|
+
${entries}
|
|
1531
|
+
|
|
1532
|
+
Use \`fixhive_vote\` to rate solutions or \`fixhive_helpful\` if a solution resolved your issue.`;
|
|
1533
|
+
}
|
|
1534
|
+
function formatErrorList(errors) {
|
|
1535
|
+
const header = `## Session Errors (${errors.length})`;
|
|
1536
|
+
const table = `
|
|
1537
|
+
| ID | Type | Status | Message |
|
|
1538
|
+
|----|------|--------|---------|
|
|
1539
|
+
${errors.map(
|
|
1540
|
+
(e) => `| ${e.id.slice(0, 8)} | ${e.errorType} | ${e.status} | ${e.errorMessage.slice(0, 50)}... |`
|
|
1541
|
+
).join("\n")}
|
|
1542
|
+
`;
|
|
1543
|
+
return `${header}
|
|
1544
|
+
${table}
|
|
1545
|
+
|
|
1546
|
+
Use \`fixhive_resolve <id>\` to mark as resolved and share solutions.`;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// src/plugin/index.ts
|
|
1550
|
+
var DEFAULT_CONFIG = {
|
|
1551
|
+
cacheExpirationMs: 36e5,
|
|
1552
|
+
// 1 hour
|
|
1553
|
+
embeddingModel: "text-embedding-3-small",
|
|
1554
|
+
embeddingDimensions: 1536,
|
|
1555
|
+
similarityThreshold: 0.7,
|
|
1556
|
+
maxSearchResults: 10
|
|
1557
|
+
};
|
|
1558
|
+
var FixHivePlugin = async (ctx) => {
|
|
1559
|
+
const config = loadConfig();
|
|
1560
|
+
const privacyFilter = new PrivacyFilter();
|
|
1561
|
+
const filterContext = createFilterContext(ctx.directory);
|
|
1562
|
+
const errorDetector = new ErrorDetector(privacyFilter);
|
|
1563
|
+
const localStore = new LocalStore(ctx.directory);
|
|
1564
|
+
let cloudClient = null;
|
|
1565
|
+
if (config.supabaseUrl && config.supabaseAnonKey) {
|
|
1566
|
+
cloudClient = new CloudClient({
|
|
1567
|
+
supabaseUrl: config.supabaseUrl,
|
|
1568
|
+
supabaseAnonKey: config.supabaseAnonKey,
|
|
1569
|
+
openaiApiKey: config.openaiApiKey,
|
|
1570
|
+
contributorId: config.contributorId,
|
|
1571
|
+
similarityThreshold: config.similarityThreshold
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
const pluginContext = {
|
|
1575
|
+
sessionId: "",
|
|
1576
|
+
projectDirectory: ctx.directory,
|
|
1577
|
+
language: detectLanguage(ctx.directory),
|
|
1578
|
+
framework: detectFramework(ctx.directory)
|
|
1579
|
+
};
|
|
1580
|
+
const errorProducingTools = ["bash", "edit", "write", "read", "terminal"];
|
|
1581
|
+
return {
|
|
1582
|
+
// ============ Tool Execution Hook ============
|
|
1583
|
+
"tool.execute.after": async (input, output) => {
|
|
1584
|
+
if (!errorProducingTools.includes(input.tool)) return;
|
|
1585
|
+
const detection = errorDetector.detect({
|
|
1586
|
+
tool: input.tool,
|
|
1587
|
+
output: output.output,
|
|
1588
|
+
exitCode: output.metadata?.exitCode,
|
|
1589
|
+
stderr: output.metadata?.stderr,
|
|
1590
|
+
metadata: output.metadata
|
|
1591
|
+
});
|
|
1592
|
+
if (detection.detected && detection.confidence >= 0.5) {
|
|
1593
|
+
const sanitizedErrorMessage = privacyFilter.sanitize(detection.errorMessage, filterContext).sanitized;
|
|
1594
|
+
const sanitizedErrorStack = detection.errorStack ? privacyFilter.sanitize(detection.errorStack, filterContext).sanitized : void 0;
|
|
1595
|
+
localStore.createErrorRecord({
|
|
1596
|
+
errorType: detection.errorType,
|
|
1597
|
+
errorMessage: sanitizedErrorMessage,
|
|
1598
|
+
errorStack: sanitizedErrorStack,
|
|
1599
|
+
language: pluginContext.language,
|
|
1600
|
+
framework: pluginContext.framework,
|
|
1601
|
+
toolName: input.tool,
|
|
1602
|
+
toolInput: {},
|
|
1603
|
+
// Tool input is intentionally omitted to avoid storing sensitive data
|
|
1604
|
+
sessionId: pluginContext.sessionId || input.sessionID
|
|
1605
|
+
});
|
|
1606
|
+
if (cloudClient) {
|
|
1607
|
+
try {
|
|
1608
|
+
const solutions = await cloudClient.searchSimilar({
|
|
1609
|
+
errorMessage: sanitizedErrorMessage,
|
|
1610
|
+
errorStack: sanitizedErrorStack,
|
|
1611
|
+
language: pluginContext.language,
|
|
1612
|
+
framework: pluginContext.framework,
|
|
1613
|
+
limit: 3
|
|
1614
|
+
});
|
|
1615
|
+
if (solutions.results.length > 0) {
|
|
1616
|
+
localStore.cacheResults(
|
|
1617
|
+
generateErrorFingerprint(sanitizedErrorMessage, sanitizedErrorStack),
|
|
1618
|
+
solutions.results
|
|
1619
|
+
);
|
|
1620
|
+
output.title = `${output.title} [FixHive: ${solutions.results.length} solution(s) found]`;
|
|
1621
|
+
}
|
|
1622
|
+
} catch (e) {
|
|
1623
|
+
const errorMessage = e instanceof Error ? e.message : "Unknown error";
|
|
1624
|
+
console.error(`[FixHive] Cloud query failed: ${errorMessage}`);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
},
|
|
1629
|
+
// ============ Session Compaction Hook ============
|
|
1630
|
+
"experimental.session.compacting": async (_input, output) => {
|
|
1631
|
+
const unresolvedErrors = localStore.getUnresolvedErrors(pluginContext.sessionId);
|
|
1632
|
+
if (unresolvedErrors.length > 0) {
|
|
1633
|
+
output.context.push(`
|
|
1634
|
+
## FixHive: Unresolved Errors in Session
|
|
1635
|
+
|
|
1636
|
+
${unresolvedErrors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 100)}...`).join("\n")}
|
|
1637
|
+
|
|
1638
|
+
Use \`fixhive_mark_resolved\` when errors are fixed to contribute solutions.
|
|
1639
|
+
`);
|
|
1640
|
+
}
|
|
1641
|
+
},
|
|
1642
|
+
// ============ Chat Message Hook ============
|
|
1643
|
+
"chat.message": async (input, _output) => {
|
|
1644
|
+
pluginContext.sessionId = input.sessionID;
|
|
1645
|
+
},
|
|
1646
|
+
// ============ Custom Tools ============
|
|
1647
|
+
tool: cloudClient ? createTools(localStore, cloudClient, privacyFilter, pluginContext) : createOfflineTools(localStore, privacyFilter, pluginContext)
|
|
1648
|
+
};
|
|
1649
|
+
};
|
|
1650
|
+
function createOfflineTools(localStore, _privacyFilter, context) {
|
|
1651
|
+
const { tool: tool2 } = __require("@opencode-ai/plugin");
|
|
1652
|
+
return {
|
|
1653
|
+
fixhive_list: tool2({
|
|
1654
|
+
description: "List errors detected in the current session.",
|
|
1655
|
+
args: {
|
|
1656
|
+
status: tool2.schema.enum(["unresolved", "resolved", "uploaded"]).optional().describe("Filter by status"),
|
|
1657
|
+
limit: tool2.schema.number().optional().describe("Maximum results (default: 10)")
|
|
1658
|
+
},
|
|
1659
|
+
async execute(args, ctx) {
|
|
1660
|
+
context.sessionId = ctx.sessionID;
|
|
1661
|
+
const errors = localStore.getSessionErrors(ctx.sessionID, {
|
|
1662
|
+
status: args.status,
|
|
1663
|
+
limit: args.limit || 10
|
|
1664
|
+
});
|
|
1665
|
+
if (errors.length === 0) {
|
|
1666
|
+
return "No errors recorded in this session.";
|
|
1667
|
+
}
|
|
1668
|
+
return `## Session Errors (${errors.length})
|
|
1669
|
+
|
|
1670
|
+
${errors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 80)}...`).join("\n")}
|
|
1671
|
+
|
|
1672
|
+
*Cloud features disabled. Set FIXHIVE_SUPABASE_URL and FIXHIVE_SUPABASE_KEY to enable.*`;
|
|
1673
|
+
}
|
|
1674
|
+
}),
|
|
1675
|
+
fixhive_stats: tool2({
|
|
1676
|
+
description: "Get FixHive usage statistics.",
|
|
1677
|
+
args: {},
|
|
1678
|
+
async execute() {
|
|
1679
|
+
const stats = localStore.getStats();
|
|
1680
|
+
return `
|
|
1681
|
+
## FixHive Statistics (Offline Mode)
|
|
1682
|
+
|
|
1683
|
+
### Local
|
|
1684
|
+
- Errors recorded: ${stats.totalErrors}
|
|
1685
|
+
- Resolved: ${stats.resolvedErrors}
|
|
1686
|
+
- Uploaded: ${stats.uploadedErrors}
|
|
1687
|
+
|
|
1688
|
+
*Cloud features disabled. Set FIXHIVE_SUPABASE_URL and FIXHIVE_SUPABASE_KEY to enable community sharing.*
|
|
1689
|
+
`;
|
|
1690
|
+
}
|
|
1691
|
+
})
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
function loadConfig() {
|
|
1695
|
+
return {
|
|
1696
|
+
supabaseUrl: process.env.FIXHIVE_SUPABASE_URL || "",
|
|
1697
|
+
supabaseAnonKey: process.env.FIXHIVE_SUPABASE_KEY || "",
|
|
1698
|
+
openaiApiKey: process.env.OPENAI_API_KEY || process.env.FIXHIVE_OPENAI_KEY || "",
|
|
1699
|
+
contributorId: process.env.FIXHIVE_CONTRIBUTOR_ID || "",
|
|
1700
|
+
cacheExpirationMs: DEFAULT_CONFIG.cacheExpirationMs,
|
|
1701
|
+
embeddingModel: DEFAULT_CONFIG.embeddingModel,
|
|
1702
|
+
embeddingDimensions: DEFAULT_CONFIG.embeddingDimensions,
|
|
1703
|
+
similarityThreshold: DEFAULT_CONFIG.similarityThreshold,
|
|
1704
|
+
maxSearchResults: DEFAULT_CONFIG.maxSearchResults
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
function detectLanguage(directory) {
|
|
1708
|
+
const fs = __require("fs");
|
|
1709
|
+
const path = __require("path");
|
|
1710
|
+
const indicators = [
|
|
1711
|
+
["package.json", "typescript"],
|
|
1712
|
+
["tsconfig.json", "typescript"],
|
|
1713
|
+
["pyproject.toml", "python"],
|
|
1714
|
+
["requirements.txt", "python"],
|
|
1715
|
+
["Cargo.toml", "rust"],
|
|
1716
|
+
["go.mod", "go"],
|
|
1717
|
+
["pom.xml", "java"],
|
|
1718
|
+
["build.gradle", "java"],
|
|
1719
|
+
["Gemfile", "ruby"],
|
|
1720
|
+
["composer.json", "php"]
|
|
1721
|
+
];
|
|
1722
|
+
for (const [file, lang] of indicators) {
|
|
1723
|
+
if (fs.existsSync(path.join(directory, file))) {
|
|
1724
|
+
return lang;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return void 0;
|
|
1728
|
+
}
|
|
1729
|
+
function detectFramework(directory) {
|
|
1730
|
+
const fs = __require("fs");
|
|
1731
|
+
const path = __require("path");
|
|
1732
|
+
const pkgPath = path.join(directory, "package.json");
|
|
1733
|
+
if (fs.existsSync(pkgPath)) {
|
|
1734
|
+
try {
|
|
1735
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
1736
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1737
|
+
if (deps["next"]) return "nextjs";
|
|
1738
|
+
if (deps["react"]) return "react";
|
|
1739
|
+
if (deps["vue"]) return "vue";
|
|
1740
|
+
if (deps["@angular/core"]) return "angular";
|
|
1741
|
+
if (deps["express"]) return "express";
|
|
1742
|
+
if (deps["fastify"]) return "fastify";
|
|
1743
|
+
if (deps["hono"]) return "hono";
|
|
1744
|
+
} catch {
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
const reqPath = path.join(directory, "requirements.txt");
|
|
1748
|
+
if (fs.existsSync(reqPath)) {
|
|
1749
|
+
try {
|
|
1750
|
+
const content = fs.readFileSync(reqPath, "utf-8");
|
|
1751
|
+
if (content.includes("django")) return "django";
|
|
1752
|
+
if (content.includes("flask")) return "flask";
|
|
1753
|
+
if (content.includes("fastapi")) return "fastapi";
|
|
1754
|
+
} catch {
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
return void 0;
|
|
1758
|
+
}
|
|
1759
|
+
var plugin_default = FixHivePlugin;
|
|
1760
|
+
export {
|
|
1761
|
+
CloudClient,
|
|
1762
|
+
EmbeddingService,
|
|
1763
|
+
ErrorDetector,
|
|
1764
|
+
FixHivePlugin,
|
|
1765
|
+
LocalStore,
|
|
1766
|
+
PrivacyFilter,
|
|
1767
|
+
calculateStringSimilarity,
|
|
1768
|
+
createCloudClient,
|
|
1769
|
+
createEmbeddingService,
|
|
1770
|
+
createFilterContext,
|
|
1771
|
+
plugin_default as default,
|
|
1772
|
+
defaultErrorDetector,
|
|
1773
|
+
defaultPrivacyFilter,
|
|
1774
|
+
fingerprintsMatch,
|
|
1775
|
+
generateContributorId,
|
|
1776
|
+
generateErrorFingerprint,
|
|
1777
|
+
generateSessionHash,
|
|
1778
|
+
normalizeErrorContent,
|
|
1779
|
+
runMigrations,
|
|
1780
|
+
sha256,
|
|
1781
|
+
shortHash
|
|
1782
|
+
};
|