@the-magic-tower/fixhive-opencode-plugin 0.1.13 → 0.1.15
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/cloud/client.d.ts +44 -0
- package/dist/cloud/client.d.ts.map +1 -0
- package/dist/cloud/embedding.d.ts +55 -0
- package/dist/cloud/embedding.d.ts.map +1 -0
- package/dist/core/error-detector.d.ts +52 -0
- package/dist/core/error-detector.d.ts.map +1 -0
- package/dist/core/hash.d.ts +41 -0
- package/dist/core/hash.d.ts.map +1 -0
- package/dist/core/privacy-filter.d.ts +44 -0
- package/dist/core/privacy-filter.d.ts.map +1 -0
- package/dist/index.d.ts +34 -617
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +353 -576
- package/dist/plugin/index.d.ts +11 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/tools.d.ts +15 -0
- package/dist/plugin/tools.d.ts.map +1 -0
- package/dist/storage/local-store.d.ts +98 -0
- package/dist/storage/local-store.d.ts.map +1 -0
- package/dist/storage/migrations.d.ts +10 -0
- package/dist/storage/migrations.d.ts.map +1 -0
- package/dist/types/index.d.ts +218 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = import.meta.require;
|
|
19
|
+
|
|
1
20
|
// src/plugin/index.ts
|
|
2
21
|
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
3
22
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
@@ -5,10 +24,6 @@ import { join } from "path";
|
|
|
5
24
|
|
|
6
25
|
// src/core/privacy-filter.ts
|
|
7
26
|
var DEFAULT_FILTER_RULES = [
|
|
8
|
-
// =====================================
|
|
9
|
-
// SECRETS (Critical - Always Filter)
|
|
10
|
-
// =====================================
|
|
11
|
-
// AWS Keys
|
|
12
27
|
{
|
|
13
28
|
name: "aws_access_key",
|
|
14
29
|
category: "secret",
|
|
@@ -16,7 +31,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
16
31
|
replacement: "[AWS_KEY_REDACTED]",
|
|
17
32
|
priority: 100
|
|
18
33
|
},
|
|
19
|
-
// OpenAI API Keys
|
|
20
34
|
{
|
|
21
35
|
name: "openai_key",
|
|
22
36
|
category: "secret",
|
|
@@ -24,7 +38,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
24
38
|
replacement: "[OPENAI_KEY_REDACTED]",
|
|
25
39
|
priority: 100
|
|
26
40
|
},
|
|
27
|
-
// GitHub Tokens
|
|
28
41
|
{
|
|
29
42
|
name: "github_token",
|
|
30
43
|
category: "secret",
|
|
@@ -32,7 +45,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
32
45
|
replacement: "[GITHUB_TOKEN_REDACTED]",
|
|
33
46
|
priority: 100
|
|
34
47
|
},
|
|
35
|
-
// Google API Keys
|
|
36
48
|
{
|
|
37
49
|
name: "google_api_key",
|
|
38
50
|
category: "secret",
|
|
@@ -40,7 +52,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
40
52
|
replacement: "[GOOGLE_API_KEY_REDACTED]",
|
|
41
53
|
priority: 100
|
|
42
54
|
},
|
|
43
|
-
// Stripe Keys
|
|
44
55
|
{
|
|
45
56
|
name: "stripe_key",
|
|
46
57
|
category: "secret",
|
|
@@ -48,7 +59,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
48
59
|
replacement: "[STRIPE_KEY_REDACTED]",
|
|
49
60
|
priority: 100
|
|
50
61
|
},
|
|
51
|
-
// JWT Tokens (limited length to prevent ReDoS)
|
|
52
62
|
{
|
|
53
63
|
name: "jwt_token",
|
|
54
64
|
category: "secret",
|
|
@@ -56,7 +66,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
56
66
|
replacement: "[JWT_REDACTED]",
|
|
57
67
|
priority: 100
|
|
58
68
|
},
|
|
59
|
-
// Bearer Tokens
|
|
60
69
|
{
|
|
61
70
|
name: "bearer_token",
|
|
62
71
|
category: "secret",
|
|
@@ -64,7 +73,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
64
73
|
replacement: "$1 [TOKEN_REDACTED]",
|
|
65
74
|
priority: 100
|
|
66
75
|
},
|
|
67
|
-
// Private Keys
|
|
68
76
|
{
|
|
69
77
|
name: "private_key",
|
|
70
78
|
category: "secret",
|
|
@@ -72,7 +80,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
72
80
|
replacement: "[PRIVATE_KEY_REDACTED]",
|
|
73
81
|
priority: 100
|
|
74
82
|
},
|
|
75
|
-
// Generic API Keys (context-based, limited length to prevent ReDoS)
|
|
76
83
|
{
|
|
77
84
|
name: "generic_api_key",
|
|
78
85
|
category: "secret",
|
|
@@ -80,7 +87,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
80
87
|
replacement: "$1=[KEY_REDACTED]",
|
|
81
88
|
priority: 95
|
|
82
89
|
},
|
|
83
|
-
// Secret/Password assignments (limited length to prevent ReDoS)
|
|
84
90
|
{
|
|
85
91
|
name: "secret_assignment",
|
|
86
92
|
category: "secret",
|
|
@@ -88,10 +94,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
88
94
|
replacement: "$1=[REDACTED]",
|
|
89
95
|
priority: 90
|
|
90
96
|
},
|
|
91
|
-
// =====================================
|
|
92
|
-
// IDENTITY (High Risk)
|
|
93
|
-
// =====================================
|
|
94
|
-
// Email Addresses
|
|
95
97
|
{
|
|
96
98
|
name: "email",
|
|
97
99
|
category: "identity",
|
|
@@ -99,10 +101,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
99
101
|
replacement: "[EMAIL_REDACTED]",
|
|
100
102
|
priority: 80
|
|
101
103
|
},
|
|
102
|
-
// =====================================
|
|
103
|
-
// INFRASTRUCTURE (Medium Risk)
|
|
104
|
-
// =====================================
|
|
105
|
-
// Database Connection Strings
|
|
106
104
|
{
|
|
107
105
|
name: "db_connection",
|
|
108
106
|
category: "infrastructure",
|
|
@@ -110,7 +108,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
110
108
|
replacement: "$1://[CONNECTION_REDACTED]",
|
|
111
109
|
priority: 85
|
|
112
110
|
},
|
|
113
|
-
// Internal URLs
|
|
114
111
|
{
|
|
115
112
|
name: "internal_url",
|
|
116
113
|
category: "infrastructure",
|
|
@@ -118,7 +115,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
118
115
|
replacement: "[INTERNAL_URL_REDACTED]",
|
|
119
116
|
priority: 75
|
|
120
117
|
},
|
|
121
|
-
// IP Addresses (except localhost and common dev IPs)
|
|
122
118
|
{
|
|
123
119
|
name: "ipv4",
|
|
124
120
|
category: "infrastructure",
|
|
@@ -131,10 +127,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
131
127
|
},
|
|
132
128
|
priority: 70
|
|
133
129
|
},
|
|
134
|
-
// =====================================
|
|
135
|
-
// FILE PATHS (Context-dependent)
|
|
136
|
-
// =====================================
|
|
137
|
-
// Home Directory Paths (macOS)
|
|
138
130
|
{
|
|
139
131
|
name: "macos_home_path",
|
|
140
132
|
category: "path",
|
|
@@ -142,7 +134,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
142
134
|
replacement: "~",
|
|
143
135
|
priority: 60
|
|
144
136
|
},
|
|
145
|
-
// Home Directory Paths (Linux)
|
|
146
137
|
{
|
|
147
138
|
name: "linux_home_path",
|
|
148
139
|
category: "path",
|
|
@@ -150,7 +141,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
150
141
|
replacement: "~",
|
|
151
142
|
priority: 60
|
|
152
143
|
},
|
|
153
|
-
// Home Directory Paths (Windows)
|
|
154
144
|
{
|
|
155
145
|
name: "windows_home_path",
|
|
156
146
|
category: "path",
|
|
@@ -158,10 +148,6 @@ var DEFAULT_FILTER_RULES = [
|
|
|
158
148
|
replacement: "~",
|
|
159
149
|
priority: 60
|
|
160
150
|
},
|
|
161
|
-
// =====================================
|
|
162
|
-
// ENVIRONMENT (Medium Risk)
|
|
163
|
-
// =====================================
|
|
164
|
-
// Sensitive Environment Variables
|
|
165
151
|
{
|
|
166
152
|
name: "env_var_value",
|
|
167
153
|
category: "environment",
|
|
@@ -189,16 +175,12 @@ var DEFAULT_FILTER_RULES = [
|
|
|
189
175
|
priority: 65
|
|
190
176
|
}
|
|
191
177
|
];
|
|
192
|
-
|
|
178
|
+
|
|
179
|
+
class PrivacyFilter {
|
|
193
180
|
rules;
|
|
194
181
|
constructor(customRules) {
|
|
195
|
-
this.rules = [...DEFAULT_FILTER_RULES, ...customRules || []].sort(
|
|
196
|
-
(a, b) => b.priority - a.priority
|
|
197
|
-
);
|
|
182
|
+
this.rules = [...DEFAULT_FILTER_RULES, ...customRules || []].sort((a, b) => b.priority - a.priority);
|
|
198
183
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Sanitize content by applying all filter rules
|
|
201
|
-
*/
|
|
202
184
|
sanitize(content, context) {
|
|
203
185
|
let result = content;
|
|
204
186
|
const appliedFilters = [];
|
|
@@ -228,9 +210,6 @@ var PrivacyFilter = class {
|
|
|
228
210
|
appliedFilters: [...new Set(appliedFilters)]
|
|
229
211
|
};
|
|
230
212
|
}
|
|
231
|
-
/**
|
|
232
|
-
* Generalize file paths while keeping meaningful structure
|
|
233
|
-
*/
|
|
234
213
|
generalizePaths(content, context) {
|
|
235
214
|
let result = content;
|
|
236
215
|
if (context.projectRoot) {
|
|
@@ -245,16 +224,10 @@ var PrivacyFilter = class {
|
|
|
245
224
|
result = result.replace(/site-packages\/[\w.-]+\//g, "site-packages/<PKG>/");
|
|
246
225
|
return result;
|
|
247
226
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Add a custom filter rule
|
|
250
|
-
*/
|
|
251
227
|
addRule(rule) {
|
|
252
228
|
this.rules.push(rule);
|
|
253
229
|
this.rules.sort((a, b) => b.priority - a.priority);
|
|
254
230
|
}
|
|
255
|
-
/**
|
|
256
|
-
* Remove a filter rule by name
|
|
257
|
-
*/
|
|
258
231
|
removeRule(name) {
|
|
259
232
|
const index = this.rules.findIndex((r) => r.name === name);
|
|
260
233
|
if (index !== -1) {
|
|
@@ -263,16 +236,9 @@ var PrivacyFilter = class {
|
|
|
263
236
|
}
|
|
264
237
|
return false;
|
|
265
238
|
}
|
|
266
|
-
/**
|
|
267
|
-
* Get all current rules
|
|
268
|
-
*/
|
|
269
239
|
getRules() {
|
|
270
240
|
return this.rules;
|
|
271
241
|
}
|
|
272
|
-
/**
|
|
273
|
-
* Check if content contains sensitive data
|
|
274
|
-
* Note: Always reset regex lastIndex BEFORE testing to prevent state pollution
|
|
275
|
-
*/
|
|
276
242
|
containsSensitiveData(content) {
|
|
277
243
|
for (const rule of this.rules) {
|
|
278
244
|
if (rule.category === "secret") {
|
|
@@ -286,13 +252,13 @@ var PrivacyFilter = class {
|
|
|
286
252
|
}
|
|
287
253
|
return false;
|
|
288
254
|
}
|
|
289
|
-
}
|
|
255
|
+
}
|
|
290
256
|
function createFilterContext(projectDirectory) {
|
|
291
257
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "~";
|
|
292
258
|
return {
|
|
293
259
|
projectRoot: projectDirectory,
|
|
294
260
|
homeDir,
|
|
295
|
-
commonPaths:
|
|
261
|
+
commonPaths: new Map([
|
|
296
262
|
["/usr/local/lib/", "<LIB>/"],
|
|
297
263
|
["/usr/lib/", "<LIB>/"],
|
|
298
264
|
["/var/log/", "<LOG>/"],
|
|
@@ -301,11 +267,10 @@ function createFilterContext(projectDirectory) {
|
|
|
301
267
|
])
|
|
302
268
|
};
|
|
303
269
|
}
|
|
304
|
-
var defaultPrivacyFilter = new PrivacyFilter
|
|
270
|
+
var defaultPrivacyFilter = new PrivacyFilter;
|
|
305
271
|
|
|
306
272
|
// src/core/error-detector.ts
|
|
307
273
|
var ERROR_PATTERNS = {
|
|
308
|
-
// Universal error indicators
|
|
309
274
|
universal: [
|
|
310
275
|
/\b(error|failed|failure|fatal|exception|panic)\b/i,
|
|
311
276
|
/\b(cannot|could not|unable to|couldn't)\b/i,
|
|
@@ -316,36 +281,26 @@ var ERROR_PATTERNS = {
|
|
|
316
281
|
/\b(segmentation fault|segfault|core dumped)\b/i,
|
|
317
282
|
/\b(out of memory|oom|memory allocation failed)\b/i
|
|
318
283
|
],
|
|
319
|
-
// Error prefixes
|
|
320
284
|
prefixed: [
|
|
321
285
|
/^Error:/m,
|
|
322
286
|
/^ERROR\s/m,
|
|
323
287
|
/^E\s+\d+:/m,
|
|
324
|
-
// Rust errors
|
|
325
288
|
/^\[ERROR\]/m,
|
|
326
289
|
/^fatal:/m,
|
|
327
290
|
/^FATAL:/m,
|
|
328
291
|
/^panic:/m,
|
|
329
292
|
/^Traceback \(most recent call last\):/m,
|
|
330
|
-
// Python
|
|
331
293
|
/^Exception in thread/m,
|
|
332
|
-
// Java
|
|
333
294
|
/^Uncaught \w+Error:/m
|
|
334
|
-
// JavaScript
|
|
335
295
|
],
|
|
336
|
-
// Build/compilation errors
|
|
337
296
|
build: [
|
|
338
297
|
/^.+:\d+:\d+:\s*error:/m,
|
|
339
|
-
// GCC/Clang format
|
|
340
298
|
/error\[E\d+\]:/m,
|
|
341
|
-
// Rust compiler
|
|
342
299
|
/error TS\d+:/m,
|
|
343
|
-
// TypeScript
|
|
344
300
|
/SyntaxError:/m,
|
|
345
301
|
/ParseError:/m,
|
|
346
302
|
/CompileError:/m
|
|
347
303
|
],
|
|
348
|
-
// Package manager errors
|
|
349
304
|
package: [
|
|
350
305
|
/npm ERR!/m,
|
|
351
306
|
/npm error/m,
|
|
@@ -358,9 +313,7 @@ var ERROR_PATTERNS = {
|
|
|
358
313
|
/Cannot find module/m,
|
|
359
314
|
/Module not found/m
|
|
360
315
|
],
|
|
361
|
-
// Permission errors
|
|
362
316
|
permission: [/EACCES:/m, /EPERM:/m, /Permission denied/m, /Access denied/m, /Insufficient permissions/m],
|
|
363
|
-
// Network errors
|
|
364
317
|
network: [
|
|
365
318
|
/ECONNREFUSED/m,
|
|
366
319
|
/ETIMEDOUT/m,
|
|
@@ -369,7 +322,6 @@ var ERROR_PATTERNS = {
|
|
|
369
322
|
/Connection refused/m,
|
|
370
323
|
/Network is unreachable/m
|
|
371
324
|
],
|
|
372
|
-
// Test failures
|
|
373
325
|
test: [/FAIL /m, /AssertionError/m, /Expected .+ but got/m, /Test failed/i, /\d+ failing/m]
|
|
374
326
|
};
|
|
375
327
|
var STACK_TRACE_PATTERNS = {
|
|
@@ -384,37 +336,26 @@ var STACK_TRACE_PATTERNS = {
|
|
|
384
336
|
};
|
|
385
337
|
var EXIT_CODE_SEVERITY = {
|
|
386
338
|
1: "error",
|
|
387
|
-
// General errors
|
|
388
339
|
2: "error",
|
|
389
|
-
// Misuse of shell builtins
|
|
390
340
|
126: "error",
|
|
391
|
-
// Command cannot execute
|
|
392
341
|
127: "error",
|
|
393
|
-
// Command not found
|
|
394
342
|
128: "critical",
|
|
395
|
-
// Invalid exit argument
|
|
396
343
|
130: "warning",
|
|
397
|
-
// Script terminated by Ctrl+C
|
|
398
344
|
137: "critical",
|
|
399
|
-
// SIGKILL (OOM, etc.)
|
|
400
345
|
139: "critical",
|
|
401
|
-
// Segmentation fault
|
|
402
346
|
143: "warning"
|
|
403
|
-
// SIGTERM
|
|
404
347
|
};
|
|
405
|
-
|
|
348
|
+
|
|
349
|
+
class ErrorDetector {
|
|
406
350
|
privacyFilter;
|
|
407
351
|
constructor(privacyFilter) {
|
|
408
|
-
this.privacyFilter = privacyFilter || new PrivacyFilter
|
|
352
|
+
this.privacyFilter = privacyFilter || new PrivacyFilter;
|
|
409
353
|
}
|
|
410
|
-
/**
|
|
411
|
-
* Detect if output contains an error
|
|
412
|
-
*/
|
|
413
354
|
detect(toolOutput) {
|
|
414
355
|
const signals = [];
|
|
415
356
|
const combinedOutput = `${toolOutput.output || ""}
|
|
416
357
|
${toolOutput.stderr || ""}`;
|
|
417
|
-
if (toolOutput.exitCode !==
|
|
358
|
+
if (toolOutput.exitCode !== undefined && toolOutput.exitCode !== 0) {
|
|
418
359
|
const severity2 = EXIT_CODE_SEVERITY[toolOutput.exitCode] || "error";
|
|
419
360
|
signals.push({
|
|
420
361
|
type: "exit_code",
|
|
@@ -440,7 +381,8 @@ ${toolOutput.stderr || ""}`;
|
|
|
440
381
|
signals.push({
|
|
441
382
|
type: "stack_trace",
|
|
442
383
|
weight: 0.95,
|
|
443
|
-
value: stackTrace.frames.slice(0, 5).join(
|
|
384
|
+
value: stackTrace.frames.slice(0, 5).join(`
|
|
385
|
+
`),
|
|
444
386
|
description: `${stackTrace.language} stack trace detected`
|
|
445
387
|
});
|
|
446
388
|
}
|
|
@@ -450,8 +392,8 @@ ${toolOutput.stderr || ""}`;
|
|
|
450
392
|
const severity = this.determineSeverity(signals, toolOutput.exitCode);
|
|
451
393
|
const { message, stack } = this.extractErrorDetails(combinedOutput);
|
|
452
394
|
const sanitizedMessage = this.privacyFilter.sanitize(message);
|
|
453
|
-
const sanitizedStack = stack ? this.privacyFilter.sanitize(stack) :
|
|
454
|
-
const sanitizedOutput = this.privacyFilter.sanitize(combinedOutput.substring(0,
|
|
395
|
+
const sanitizedStack = stack ? this.privacyFilter.sanitize(stack) : undefined;
|
|
396
|
+
const sanitizedOutput = this.privacyFilter.sanitize(combinedOutput.substring(0, 5000));
|
|
455
397
|
return {
|
|
456
398
|
detected,
|
|
457
399
|
confidence,
|
|
@@ -463,15 +405,9 @@ ${toolOutput.stderr || ""}`;
|
|
|
463
405
|
rawOutput: sanitizedOutput.sanitized
|
|
464
406
|
};
|
|
465
407
|
}
|
|
466
|
-
/**
|
|
467
|
-
* Check if content contains error keywords
|
|
468
|
-
*/
|
|
469
408
|
containsErrorKeywords(content) {
|
|
470
409
|
return ERROR_PATTERNS.universal.some((p) => p.test(content));
|
|
471
410
|
}
|
|
472
|
-
/**
|
|
473
|
-
* Detect error patterns in content
|
|
474
|
-
*/
|
|
475
411
|
detectErrorPatterns(content) {
|
|
476
412
|
const signals = [];
|
|
477
413
|
const weights = {
|
|
@@ -483,7 +419,8 @@ ${toolOutput.stderr || ""}`;
|
|
|
483
419
|
test: 0.7
|
|
484
420
|
};
|
|
485
421
|
for (const [category, patterns] of Object.entries(ERROR_PATTERNS)) {
|
|
486
|
-
if (category === "universal")
|
|
422
|
+
if (category === "universal")
|
|
423
|
+
continue;
|
|
487
424
|
for (const pattern of patterns) {
|
|
488
425
|
const match = content.match(pattern);
|
|
489
426
|
if (match) {
|
|
@@ -499,9 +436,6 @@ ${toolOutput.stderr || ""}`;
|
|
|
499
436
|
}
|
|
500
437
|
return signals;
|
|
501
438
|
}
|
|
502
|
-
/**
|
|
503
|
-
* Detect stack traces in content
|
|
504
|
-
*/
|
|
505
439
|
detectStackTrace(content) {
|
|
506
440
|
for (const [language, pattern] of Object.entries(STACK_TRACE_PATTERNS)) {
|
|
507
441
|
const globalPattern = new RegExp(pattern.source, "gm");
|
|
@@ -520,49 +454,50 @@ ${toolOutput.stderr || ""}`;
|
|
|
520
454
|
frames: []
|
|
521
455
|
};
|
|
522
456
|
}
|
|
523
|
-
/**
|
|
524
|
-
* Calculate confidence score from signals
|
|
525
|
-
*/
|
|
526
457
|
calculateConfidence(signals) {
|
|
527
|
-
if (signals.length === 0)
|
|
458
|
+
if (signals.length === 0)
|
|
459
|
+
return 0;
|
|
528
460
|
const totalWeight = signals.reduce((sum, s) => sum + s.weight, 0);
|
|
529
461
|
const avgWeight = totalWeight / signals.length;
|
|
530
462
|
const multiplier = Math.min(1.2, 1 + signals.length * 0.05);
|
|
531
463
|
return Math.min(1, avgWeight * multiplier);
|
|
532
464
|
}
|
|
533
|
-
/**
|
|
534
|
-
* Classify error type based on signals and content
|
|
535
|
-
*/
|
|
536
465
|
classifyErrorType(signals, content) {
|
|
537
|
-
if (ERROR_PATTERNS.build.some((p) => p.test(content)))
|
|
538
|
-
|
|
539
|
-
if (ERROR_PATTERNS.
|
|
540
|
-
|
|
541
|
-
if (ERROR_PATTERNS.
|
|
542
|
-
|
|
543
|
-
if (
|
|
544
|
-
|
|
466
|
+
if (ERROR_PATTERNS.build.some((p) => p.test(content)))
|
|
467
|
+
return "build";
|
|
468
|
+
if (ERROR_PATTERNS.package.some((p) => p.test(content)))
|
|
469
|
+
return "dependency";
|
|
470
|
+
if (ERROR_PATTERNS.permission.some((p) => p.test(content)))
|
|
471
|
+
return "permission";
|
|
472
|
+
if (ERROR_PATTERNS.network.some((p) => p.test(content)))
|
|
473
|
+
return "network";
|
|
474
|
+
if (ERROR_PATTERNS.test.some((p) => p.test(content)))
|
|
475
|
+
return "test";
|
|
476
|
+
if (/TypeError:|type error|Type '[^']+' is not assignable/i.test(content))
|
|
477
|
+
return "type_error";
|
|
478
|
+
if (/SyntaxError:|syntax error|unexpected token/i.test(content))
|
|
479
|
+
return "syntax";
|
|
480
|
+
if (/ReferenceError:|RangeError:|runtime error/i.test(content))
|
|
481
|
+
return "runtime";
|
|
545
482
|
const hasStackTrace = signals.some((s) => s.type === "stack_trace");
|
|
546
|
-
if (hasStackTrace)
|
|
483
|
+
if (hasStackTrace)
|
|
484
|
+
return "runtime";
|
|
547
485
|
return "unknown";
|
|
548
486
|
}
|
|
549
|
-
/**
|
|
550
|
-
* Determine severity from signals and exit code
|
|
551
|
-
*/
|
|
552
487
|
determineSeverity(signals, exitCode) {
|
|
553
|
-
if (exitCode !==
|
|
488
|
+
if (exitCode !== undefined && EXIT_CODE_SEVERITY[exitCode]) {
|
|
554
489
|
return EXIT_CODE_SEVERITY[exitCode];
|
|
555
490
|
}
|
|
556
491
|
const maxWeight = Math.max(...signals.map((s) => s.weight));
|
|
557
|
-
if (maxWeight >= 0.9)
|
|
558
|
-
|
|
492
|
+
if (maxWeight >= 0.9)
|
|
493
|
+
return "error";
|
|
494
|
+
if (maxWeight >= 0.7)
|
|
495
|
+
return "error";
|
|
559
496
|
return "warning";
|
|
560
497
|
}
|
|
561
|
-
/**
|
|
562
|
-
* Extract error message and stack from output
|
|
563
|
-
*/
|
|
564
498
|
extractErrorDetails(output) {
|
|
565
|
-
const lines = output.split(
|
|
499
|
+
const lines = output.split(`
|
|
500
|
+
`);
|
|
566
501
|
let message = "";
|
|
567
502
|
let stack = "";
|
|
568
503
|
let inStack = false;
|
|
@@ -570,11 +505,9 @@ ${toolOutput.stderr || ""}`;
|
|
|
570
505
|
if (this.isErrorLine(line) && !message) {
|
|
571
506
|
message = line.trim();
|
|
572
507
|
inStack = true;
|
|
573
|
-
} else if (inStack && (line.match(/^\s+at\s/) ||
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
line.match(/^\s+from\s/))) {
|
|
577
|
-
stack += line + "\n";
|
|
508
|
+
} else if (inStack && (line.match(/^\s+at\s/) || line.match(/^\s+File\s/) || line.match(/^\s+\d+:\s/) || line.match(/^\s+from\s/))) {
|
|
509
|
+
stack += line + `
|
|
510
|
+
`;
|
|
578
511
|
}
|
|
579
512
|
}
|
|
580
513
|
if (!message) {
|
|
@@ -587,18 +520,15 @@ ${toolOutput.stderr || ""}`;
|
|
|
587
520
|
}
|
|
588
521
|
return {
|
|
589
522
|
message: message || output.substring(0, 500),
|
|
590
|
-
stack: stack ||
|
|
523
|
+
stack: stack || undefined
|
|
591
524
|
};
|
|
592
525
|
}
|
|
593
|
-
/**
|
|
594
|
-
* Check if a line looks like an error message
|
|
595
|
-
*/
|
|
596
526
|
isErrorLine(line) {
|
|
597
527
|
const trimmed = line.trim();
|
|
598
528
|
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);
|
|
599
529
|
}
|
|
600
|
-
}
|
|
601
|
-
var defaultErrorDetector = new ErrorDetector
|
|
530
|
+
}
|
|
531
|
+
var defaultErrorDetector = new ErrorDetector;
|
|
602
532
|
|
|
603
533
|
// src/storage/local-store.ts
|
|
604
534
|
import Database from "better-sqlite3";
|
|
@@ -642,8 +572,9 @@ function calculateStringSimilarity(str1, str2) {
|
|
|
642
572
|
const words1 = new Set(str1.toLowerCase().split(/\s+/));
|
|
643
573
|
const words2 = new Set(str2.toLowerCase().split(/\s+/));
|
|
644
574
|
const intersection = new Set([...words1].filter((x) => words2.has(x)));
|
|
645
|
-
const union =
|
|
646
|
-
if (union.size === 0)
|
|
575
|
+
const union = new Set([...words1, ...words2]);
|
|
576
|
+
if (union.size === 0)
|
|
577
|
+
return 0;
|
|
647
578
|
return intersection.size / union.size;
|
|
648
579
|
}
|
|
649
580
|
|
|
@@ -656,9 +587,7 @@ function runMigrations(db) {
|
|
|
656
587
|
applied_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
657
588
|
)
|
|
658
589
|
`);
|
|
659
|
-
const appliedMigrations = new Set(
|
|
660
|
-
db.prepare("SELECT name FROM migrations").all().map((r) => r.name)
|
|
661
|
-
);
|
|
590
|
+
const appliedMigrations = new Set(db.prepare("SELECT name FROM migrations").all().map((r) => r.name));
|
|
662
591
|
for (const migration of MIGRATIONS) {
|
|
663
592
|
if (!appliedMigrations.has(migration.name)) {
|
|
664
593
|
db.exec(migration.sql);
|
|
@@ -740,7 +669,7 @@ var MIGRATIONS = [
|
|
|
740
669
|
];
|
|
741
670
|
|
|
742
671
|
// src/storage/local-store.ts
|
|
743
|
-
|
|
672
|
+
class LocalStore {
|
|
744
673
|
db;
|
|
745
674
|
constructor(projectDirectory) {
|
|
746
675
|
const dbPath = `${projectDirectory}/.fixhive/fixhive.db`;
|
|
@@ -753,10 +682,6 @@ var LocalStore = class _LocalStore {
|
|
|
753
682
|
this.db.pragma("foreign_keys = ON");
|
|
754
683
|
runMigrations(this.db);
|
|
755
684
|
}
|
|
756
|
-
// ============ Error Records ============
|
|
757
|
-
/**
|
|
758
|
-
* Create a new error record
|
|
759
|
-
*/
|
|
760
685
|
createErrorRecord(data) {
|
|
761
686
|
const id = uuidv4();
|
|
762
687
|
const errorHash = generateErrorFingerprint(data.errorMessage, data.errorStack);
|
|
@@ -784,17 +709,11 @@ var LocalStore = class _LocalStore {
|
|
|
784
709
|
this.incrementStat("total_errors");
|
|
785
710
|
return this.getErrorById(id);
|
|
786
711
|
}
|
|
787
|
-
/**
|
|
788
|
-
* Get error record by ID
|
|
789
|
-
*/
|
|
790
712
|
getErrorById(id) {
|
|
791
713
|
const stmt = this.db.prepare("SELECT * FROM error_records WHERE id = ?");
|
|
792
714
|
const row = stmt.get(id);
|
|
793
715
|
return row ? this.rowToRecord(row) : null;
|
|
794
716
|
}
|
|
795
|
-
/**
|
|
796
|
-
* Get errors by session
|
|
797
|
-
*/
|
|
798
717
|
getSessionErrors(sessionId, options) {
|
|
799
718
|
let query = "SELECT * FROM error_records WHERE session_id = ?";
|
|
800
719
|
const params = [sessionId];
|
|
@@ -810,24 +729,13 @@ var LocalStore = class _LocalStore {
|
|
|
810
729
|
const stmt = this.db.prepare(query);
|
|
811
730
|
return stmt.all(...params).map((row) => this.rowToRecord(row));
|
|
812
731
|
}
|
|
813
|
-
/**
|
|
814
|
-
* Get unresolved errors for a session
|
|
815
|
-
*/
|
|
816
732
|
getUnresolvedErrors(sessionId) {
|
|
817
733
|
return this.getSessionErrors(sessionId, { status: "unresolved" });
|
|
818
734
|
}
|
|
819
|
-
/**
|
|
820
|
-
* Get recent errors across all sessions
|
|
821
|
-
*/
|
|
822
735
|
getRecentErrors(limit = 10) {
|
|
823
|
-
const stmt = this.db.prepare(
|
|
824
|
-
"SELECT * FROM error_records ORDER BY created_at DESC LIMIT ?"
|
|
825
|
-
);
|
|
736
|
+
const stmt = this.db.prepare("SELECT * FROM error_records ORDER BY created_at DESC LIMIT ?");
|
|
826
737
|
return stmt.all(limit).map((row) => this.rowToRecord(row));
|
|
827
738
|
}
|
|
828
|
-
/**
|
|
829
|
-
* Mark error as resolved
|
|
830
|
-
*/
|
|
831
739
|
markResolved(id, data) {
|
|
832
740
|
const stmt = this.db.prepare(`
|
|
833
741
|
UPDATE error_records
|
|
@@ -844,9 +752,6 @@ var LocalStore = class _LocalStore {
|
|
|
844
752
|
}
|
|
845
753
|
return null;
|
|
846
754
|
}
|
|
847
|
-
/**
|
|
848
|
-
* Mark error as uploaded to cloud
|
|
849
|
-
*/
|
|
850
755
|
markUploaded(id, cloudKnowledgeId) {
|
|
851
756
|
const stmt = this.db.prepare(`
|
|
852
757
|
UPDATE error_records
|
|
@@ -860,19 +765,10 @@ var LocalStore = class _LocalStore {
|
|
|
860
765
|
this.incrementStat("uploaded_errors");
|
|
861
766
|
}
|
|
862
767
|
}
|
|
863
|
-
/**
|
|
864
|
-
* Find similar errors by hash
|
|
865
|
-
*/
|
|
866
768
|
findSimilarErrors(errorHash) {
|
|
867
|
-
const stmt = this.db.prepare(
|
|
868
|
-
"SELECT * FROM error_records WHERE error_hash = ? ORDER BY created_at DESC"
|
|
869
|
-
);
|
|
769
|
+
const stmt = this.db.prepare("SELECT * FROM error_records WHERE error_hash = ? ORDER BY created_at DESC");
|
|
870
770
|
return stmt.all(errorHash).map((row) => this.rowToRecord(row));
|
|
871
771
|
}
|
|
872
|
-
// ============ Query Cache ============
|
|
873
|
-
/**
|
|
874
|
-
* Get cached query results
|
|
875
|
-
*/
|
|
876
772
|
getCachedResults(errorHash) {
|
|
877
773
|
const stmt = this.db.prepare(`
|
|
878
774
|
SELECT results FROM query_cache
|
|
@@ -885,10 +781,7 @@ var LocalStore = class _LocalStore {
|
|
|
885
781
|
}
|
|
886
782
|
return null;
|
|
887
783
|
}
|
|
888
|
-
|
|
889
|
-
* Cache query results
|
|
890
|
-
*/
|
|
891
|
-
cacheResults(errorHash, results, expirationMs = 36e5) {
|
|
784
|
+
cacheResults(errorHash, results, expirationMs = 3600000) {
|
|
892
785
|
const id = uuidv4();
|
|
893
786
|
const expiresAt = new Date(Date.now() + expirationMs).toISOString();
|
|
894
787
|
const stmt = this.db.prepare(`
|
|
@@ -898,22 +791,13 @@ var LocalStore = class _LocalStore {
|
|
|
898
791
|
stmt.run(id, errorHash, JSON.stringify(results), expiresAt);
|
|
899
792
|
this.incrementStat("queries_made");
|
|
900
793
|
}
|
|
901
|
-
/**
|
|
902
|
-
* Clear expired cache entries
|
|
903
|
-
*/
|
|
904
794
|
clearExpiredCache() {
|
|
905
795
|
const stmt = this.db.prepare("DELETE FROM query_cache WHERE expires_at <= datetime('now')");
|
|
906
796
|
const result = stmt.run();
|
|
907
797
|
return result.changes;
|
|
908
798
|
}
|
|
909
|
-
// ============ Statistics ============
|
|
910
|
-
/**
|
|
911
|
-
* Get usage statistics
|
|
912
|
-
*/
|
|
913
799
|
getStats() {
|
|
914
|
-
const stmt = this.db.prepare(
|
|
915
|
-
"SELECT total_errors, resolved_errors, uploaded_errors FROM usage_stats WHERE id = 1"
|
|
916
|
-
);
|
|
800
|
+
const stmt = this.db.prepare("SELECT total_errors, resolved_errors, uploaded_errors FROM usage_stats WHERE id = 1");
|
|
917
801
|
const row = stmt.get();
|
|
918
802
|
return {
|
|
919
803
|
totalErrors: row.total_errors,
|
|
@@ -921,89 +805,66 @@ var LocalStore = class _LocalStore {
|
|
|
921
805
|
uploadedErrors: row.uploaded_errors
|
|
922
806
|
};
|
|
923
807
|
}
|
|
924
|
-
/**
|
|
925
|
-
* Allowed stat column names for incrementStat (whitelist to prevent SQL injection)
|
|
926
|
-
*/
|
|
927
808
|
static ALLOWED_STATS = [
|
|
928
809
|
"total_errors",
|
|
929
810
|
"resolved_errors",
|
|
930
811
|
"uploaded_errors",
|
|
931
812
|
"queries_made"
|
|
932
813
|
];
|
|
933
|
-
/**
|
|
934
|
-
* Increment a stat counter
|
|
935
|
-
* @throws Error if stat name is not in the allowed whitelist
|
|
936
|
-
*/
|
|
937
814
|
incrementStat(stat) {
|
|
938
|
-
if (!
|
|
939
|
-
throw new Error(`Invalid stat name: ${stat}. Allowed: ${
|
|
815
|
+
if (!LocalStore.ALLOWED_STATS.includes(stat)) {
|
|
816
|
+
throw new Error(`Invalid stat name: ${stat}. Allowed: ${LocalStore.ALLOWED_STATS.join(", ")}`);
|
|
940
817
|
}
|
|
941
818
|
const stmt = this.db.prepare(`UPDATE usage_stats SET ${stat} = ${stat} + 1 WHERE id = 1`);
|
|
942
819
|
stmt.run();
|
|
943
820
|
}
|
|
944
|
-
// ============ Preferences ============
|
|
945
|
-
/**
|
|
946
|
-
* Get preference value
|
|
947
|
-
*/
|
|
948
821
|
getPreference(key) {
|
|
949
822
|
const stmt = this.db.prepare("SELECT value FROM user_preferences WHERE key = ?");
|
|
950
823
|
const row = stmt.get(key);
|
|
951
824
|
return row?.value || null;
|
|
952
825
|
}
|
|
953
|
-
/**
|
|
954
|
-
* Set preference value
|
|
955
|
-
*/
|
|
956
826
|
setPreference(key, value) {
|
|
957
827
|
const stmt = this.db.prepare(`
|
|
958
828
|
INSERT OR REPLACE INTO user_preferences (key, value) VALUES (?, ?)
|
|
959
829
|
`);
|
|
960
830
|
stmt.run(key, value);
|
|
961
831
|
}
|
|
962
|
-
// ============ Utilities ============
|
|
963
|
-
/**
|
|
964
|
-
* Convert database row to LocalErrorRecord
|
|
965
|
-
*/
|
|
966
832
|
rowToRecord(row) {
|
|
967
833
|
return {
|
|
968
834
|
id: row.id,
|
|
969
835
|
errorHash: row.error_hash,
|
|
970
836
|
errorType: row.error_type,
|
|
971
837
|
errorMessage: row.error_message,
|
|
972
|
-
errorStack: row.error_stack ||
|
|
973
|
-
language: row.language ||
|
|
974
|
-
framework: row.framework ||
|
|
838
|
+
errorStack: row.error_stack || undefined,
|
|
839
|
+
language: row.language || undefined,
|
|
840
|
+
framework: row.framework || undefined,
|
|
975
841
|
toolName: row.tool_name,
|
|
976
842
|
toolInput: JSON.parse(row.tool_input || "{}"),
|
|
977
843
|
sessionId: row.session_id,
|
|
978
844
|
status: row.status,
|
|
979
|
-
resolution: row.resolution ||
|
|
980
|
-
resolutionCode: row.resolution_code ||
|
|
845
|
+
resolution: row.resolution || undefined,
|
|
846
|
+
resolutionCode: row.resolution_code || undefined,
|
|
981
847
|
createdAt: row.created_at,
|
|
982
|
-
resolvedAt: row.resolved_at ||
|
|
983
|
-
uploadedAt: row.uploaded_at ||
|
|
984
|
-
cloudKnowledgeId: row.cloud_knowledge_id ||
|
|
848
|
+
resolvedAt: row.resolved_at || undefined,
|
|
849
|
+
uploadedAt: row.uploaded_at || undefined,
|
|
850
|
+
cloudKnowledgeId: row.cloud_knowledge_id || undefined
|
|
985
851
|
};
|
|
986
852
|
}
|
|
987
|
-
/**
|
|
988
|
-
* Close database connection
|
|
989
|
-
*/
|
|
990
853
|
close() {
|
|
991
854
|
this.db.close();
|
|
992
855
|
}
|
|
993
|
-
/**
|
|
994
|
-
* Get database for advanced queries
|
|
995
|
-
*/
|
|
996
856
|
getDatabase() {
|
|
997
857
|
return this.db;
|
|
998
858
|
}
|
|
999
|
-
}
|
|
859
|
+
}
|
|
1000
860
|
|
|
1001
861
|
// src/cloud/embedding.ts
|
|
1002
862
|
import OpenAI from "openai";
|
|
1003
863
|
var DEFAULT_MODEL = "text-embedding-3-small";
|
|
1004
864
|
var DEFAULT_DIMENSIONS = 1536;
|
|
1005
|
-
var MAX_INPUT_LENGTH =
|
|
1006
|
-
|
|
865
|
+
var MAX_INPUT_LENGTH = 30000;
|
|
866
|
+
|
|
867
|
+
class EmbeddingService {
|
|
1007
868
|
client;
|
|
1008
869
|
model;
|
|
1009
870
|
dimensions;
|
|
@@ -1012,9 +873,6 @@ var EmbeddingService = class {
|
|
|
1012
873
|
this.model = model || DEFAULT_MODEL;
|
|
1013
874
|
this.dimensions = dimensions || DEFAULT_DIMENSIONS;
|
|
1014
875
|
}
|
|
1015
|
-
/**
|
|
1016
|
-
* Generate embedding for a single text
|
|
1017
|
-
*/
|
|
1018
876
|
async generate(text) {
|
|
1019
877
|
const truncated = this.truncateText(text);
|
|
1020
878
|
const response = await this.client.embeddings.create({
|
|
@@ -1024,9 +882,6 @@ var EmbeddingService = class {
|
|
|
1024
882
|
});
|
|
1025
883
|
return response.data[0].embedding;
|
|
1026
884
|
}
|
|
1027
|
-
/**
|
|
1028
|
-
* Generate embeddings for multiple texts
|
|
1029
|
-
*/
|
|
1030
885
|
async generateBatch(texts) {
|
|
1031
886
|
const truncated = texts.map((t) => this.truncateText(t));
|
|
1032
887
|
const response = await this.client.embeddings.create({
|
|
@@ -1036,10 +891,6 @@ var EmbeddingService = class {
|
|
|
1036
891
|
});
|
|
1037
892
|
return response.data.map((d) => d.embedding);
|
|
1038
893
|
}
|
|
1039
|
-
/**
|
|
1040
|
-
* Generate embedding for error context
|
|
1041
|
-
* Combines error message, stack trace, and context
|
|
1042
|
-
*/
|
|
1043
894
|
async generateErrorEmbedding(errorMessage, errorStack, context) {
|
|
1044
895
|
const parts = [];
|
|
1045
896
|
if (context?.language) {
|
|
@@ -1053,26 +904,22 @@ var EmbeddingService = class {
|
|
|
1053
904
|
parts.push(`Stack Trace:
|
|
1054
905
|
${errorStack}`);
|
|
1055
906
|
}
|
|
1056
|
-
const text = parts.join(
|
|
907
|
+
const text = parts.join(`
|
|
908
|
+
`);
|
|
1057
909
|
return this.generate(text);
|
|
1058
910
|
}
|
|
1059
|
-
/**
|
|
1060
|
-
* Truncate text to fit within model limits
|
|
1061
|
-
*/
|
|
1062
911
|
truncateText(text) {
|
|
1063
912
|
if (text.length <= MAX_INPUT_LENGTH) {
|
|
1064
913
|
return text;
|
|
1065
914
|
}
|
|
1066
915
|
const truncated = text.substring(0, MAX_INPUT_LENGTH);
|
|
1067
|
-
const lastNewline = truncated.lastIndexOf(
|
|
916
|
+
const lastNewline = truncated.lastIndexOf(`
|
|
917
|
+
`);
|
|
1068
918
|
if (lastNewline > MAX_INPUT_LENGTH * 0.8) {
|
|
1069
919
|
return truncated.substring(0, lastNewline);
|
|
1070
920
|
}
|
|
1071
921
|
return truncated;
|
|
1072
922
|
}
|
|
1073
|
-
/**
|
|
1074
|
-
* Calculate cosine similarity between two embeddings
|
|
1075
|
-
*/
|
|
1076
923
|
static cosineSimilarity(a, b) {
|
|
1077
924
|
if (a.length !== b.length) {
|
|
1078
925
|
throw new Error("Embeddings must have same dimensions");
|
|
@@ -1080,28 +927,23 @@ ${errorStack}`);
|
|
|
1080
927
|
let dotProduct = 0;
|
|
1081
928
|
let normA = 0;
|
|
1082
929
|
let normB = 0;
|
|
1083
|
-
for (let i = 0;
|
|
930
|
+
for (let i = 0;i < a.length; i++) {
|
|
1084
931
|
dotProduct += a[i] * b[i];
|
|
1085
932
|
normA += a[i] * a[i];
|
|
1086
933
|
normB += b[i] * b[i];
|
|
1087
934
|
}
|
|
1088
935
|
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1089
|
-
if (magnitude === 0)
|
|
936
|
+
if (magnitude === 0)
|
|
937
|
+
return 0;
|
|
1090
938
|
return dotProduct / magnitude;
|
|
1091
939
|
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Get embedding dimensions
|
|
1094
|
-
*/
|
|
1095
940
|
getDimensions() {
|
|
1096
941
|
return this.dimensions;
|
|
1097
942
|
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Get model name
|
|
1100
|
-
*/
|
|
1101
943
|
getModel() {
|
|
1102
944
|
return this.model;
|
|
1103
945
|
}
|
|
1104
|
-
}
|
|
946
|
+
}
|
|
1105
947
|
function createEmbeddingService(config) {
|
|
1106
948
|
return new EmbeddingService(config.apiKey, config.model, config.dimensions);
|
|
1107
949
|
}
|
|
@@ -1115,69 +957,69 @@ async function getCreateClient() {
|
|
|
1115
957
|
}
|
|
1116
958
|
return createClient;
|
|
1117
959
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
960
|
+
function mapToKnowledgeEntry(row) {
|
|
961
|
+
return {
|
|
962
|
+
id: row.id,
|
|
963
|
+
errorHash: row.error_hash,
|
|
964
|
+
errorType: row.error_type,
|
|
965
|
+
errorMessage: row.error_message,
|
|
966
|
+
errorStack: row.error_stack || undefined,
|
|
967
|
+
language: row.language,
|
|
968
|
+
framework: row.framework || undefined,
|
|
969
|
+
dependencies: row.dependencies || undefined,
|
|
970
|
+
resolutionDescription: row.resolution_description,
|
|
971
|
+
resolutionCode: row.resolution_code || undefined,
|
|
972
|
+
resolutionSteps: row.resolution_steps || undefined,
|
|
973
|
+
contributorId: row.contributor_id,
|
|
974
|
+
upvotes: row.upvotes || 0,
|
|
975
|
+
downvotes: row.downvotes || 0,
|
|
976
|
+
usageCount: row.usage_count || 0,
|
|
977
|
+
createdAt: row.created_at,
|
|
978
|
+
updatedAt: row.updated_at,
|
|
979
|
+
isVerified: row.is_verified || false,
|
|
980
|
+
similarity: row.similarity || undefined
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
async function createCloudClient(config) {
|
|
984
|
+
const createClientFn = await getCreateClient();
|
|
985
|
+
const supabase = createClientFn(config.supabaseUrl, config.supabaseAnonKey);
|
|
986
|
+
let embedding = null;
|
|
987
|
+
if (config.openaiApiKey) {
|
|
988
|
+
try {
|
|
989
|
+
embedding = new EmbeddingService(config.openaiApiKey);
|
|
990
|
+
} catch (err) {
|
|
991
|
+
console.warn("[FixHive] Failed to initialize embedding service:", err);
|
|
1142
992
|
}
|
|
1143
|
-
const contributorId = config.contributorId || generateContributorId();
|
|
1144
|
-
const similarityThreshold = config.similarityThreshold || 0.7;
|
|
1145
|
-
return new _CloudClient(supabase, embedding, contributorId, similarityThreshold);
|
|
1146
993
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
994
|
+
const contributorId = config.contributorId || generateContributorId();
|
|
995
|
+
const similarityThreshold = config.similarityThreshold || 0.7;
|
|
996
|
+
async function checkDuplicateInternal(errorHash, embeddingData) {
|
|
997
|
+
const { data: hashMatch } = await supabase.from("knowledge_entries").select("id").eq("error_hash", errorHash).limit(1).single();
|
|
998
|
+
if (hashMatch) {
|
|
999
|
+
return {
|
|
1000
|
+
isDuplicate: true,
|
|
1001
|
+
existingId: hashMatch.id,
|
|
1002
|
+
similarityScore: 1
|
|
1003
|
+
};
|
|
1154
1004
|
}
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
query_embedding: embedding,
|
|
1160
|
-
match_threshold: request.threshold || this.similarityThreshold,
|
|
1161
|
-
match_count: request.limit || 10,
|
|
1162
|
-
filter_language: request.language || null,
|
|
1163
|
-
filter_framework: request.framework || null
|
|
1005
|
+
const { data, error } = await supabase.rpc("check_duplicate_entry", {
|
|
1006
|
+
new_hash: errorHash,
|
|
1007
|
+
new_embedding: embeddingData,
|
|
1008
|
+
similarity_threshold: 0.95
|
|
1164
1009
|
});
|
|
1165
|
-
if (error) {
|
|
1166
|
-
|
|
1167
|
-
return { results: [], queryTime: Date.now() - startTime, cached: false };
|
|
1010
|
+
if (error || !data || data.length === 0) {
|
|
1011
|
+
return { isDuplicate: false, similarityScore: 0 };
|
|
1168
1012
|
}
|
|
1013
|
+
const result = data[0];
|
|
1169
1014
|
return {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1015
|
+
isDuplicate: result.is_duplicate,
|
|
1016
|
+
existingId: result.existing_id,
|
|
1017
|
+
similarityScore: result.similarity_score
|
|
1173
1018
|
};
|
|
1174
1019
|
}
|
|
1175
|
-
|
|
1176
|
-
* Fallback text-based search
|
|
1177
|
-
*/
|
|
1178
|
-
async searchByText(request) {
|
|
1020
|
+
async function searchByText(request) {
|
|
1179
1021
|
const startTime = Date.now();
|
|
1180
|
-
let query =
|
|
1022
|
+
let query = supabase.from("knowledge_entries").select("*").ilike("error_message", `%${request.errorMessage.substring(0, 100)}%`).order("upvotes", { ascending: false }).limit(request.limit || 10);
|
|
1181
1023
|
if (request.language) {
|
|
1182
1024
|
query = query.eq("language", request.language);
|
|
1183
1025
|
}
|
|
@@ -1190,214 +1032,164 @@ ${request.errorStack || ""}`;
|
|
|
1190
1032
|
return { results: [], queryTime: Date.now() - startTime, cached: false };
|
|
1191
1033
|
}
|
|
1192
1034
|
return {
|
|
1193
|
-
results: (data || []).map(
|
|
1035
|
+
results: (data || []).map(mapToKnowledgeEntry),
|
|
1194
1036
|
queryTime: Date.now() - startTime,
|
|
1195
1037
|
cached: false
|
|
1196
1038
|
};
|
|
1197
1039
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1040
|
+
return {
|
|
1041
|
+
async searchSimilar(request) {
|
|
1042
|
+
const startTime = Date.now();
|
|
1043
|
+
if (!embedding) {
|
|
1044
|
+
return searchByText(request);
|
|
1045
|
+
}
|
|
1046
|
+
const queryText = `${request.errorMessage}
|
|
1047
|
+
${request.errorStack || ""}`;
|
|
1048
|
+
const queryEmbedding = await embedding.generate(queryText);
|
|
1049
|
+
const { data, error } = await supabase.rpc("search_similar_errors", {
|
|
1050
|
+
query_embedding: queryEmbedding,
|
|
1051
|
+
match_threshold: request.threshold || similarityThreshold,
|
|
1052
|
+
match_count: request.limit || 10,
|
|
1053
|
+
filter_language: request.language || null,
|
|
1054
|
+
filter_framework: request.framework || null
|
|
1055
|
+
});
|
|
1056
|
+
if (error) {
|
|
1057
|
+
console.error("Search error:", error);
|
|
1058
|
+
return { results: [], queryTime: Date.now() - startTime, cached: false };
|
|
1059
|
+
}
|
|
1060
|
+
return {
|
|
1061
|
+
results: (data || []).map(mapToKnowledgeEntry),
|
|
1062
|
+
queryTime: Date.now() - startTime,
|
|
1063
|
+
cached: false
|
|
1064
|
+
};
|
|
1065
|
+
},
|
|
1066
|
+
async uploadResolution(request) {
|
|
1067
|
+
const { errorRecord, resolution, resolutionCode, resolutionSteps } = request;
|
|
1068
|
+
let embeddingData = null;
|
|
1069
|
+
if (embedding) {
|
|
1070
|
+
const embeddingText = `${errorRecord.errorMessage}
|
|
1206
1071
|
${errorRecord.errorStack || ""}`;
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1072
|
+
embeddingData = await embedding.generate(embeddingText);
|
|
1073
|
+
}
|
|
1074
|
+
if (embeddingData) {
|
|
1075
|
+
const duplicateCheck = await checkDuplicateInternal(errorRecord.errorHash, embeddingData);
|
|
1076
|
+
if (duplicateCheck.isDuplicate && duplicateCheck.similarityScore > 0.95) {
|
|
1077
|
+
await supabase.rpc("increment_usage_count", {
|
|
1078
|
+
entry_id: duplicateCheck.existingId
|
|
1079
|
+
});
|
|
1080
|
+
return {
|
|
1081
|
+
success: true,
|
|
1082
|
+
isDuplicate: true,
|
|
1083
|
+
existingId: duplicateCheck.existingId,
|
|
1084
|
+
message: "Similar solution already exists. Usage count incremented."
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const { data, error } = await supabase.from("knowledge_entries").insert({
|
|
1089
|
+
error_hash: errorRecord.errorHash,
|
|
1090
|
+
error_type: errorRecord.errorType,
|
|
1091
|
+
error_message: errorRecord.errorMessage,
|
|
1092
|
+
error_stack: errorRecord.errorStack,
|
|
1093
|
+
language: errorRecord.language || "other",
|
|
1094
|
+
framework: errorRecord.framework,
|
|
1095
|
+
embedding: embeddingData,
|
|
1096
|
+
resolution_description: resolution,
|
|
1097
|
+
resolution_code: resolutionCode,
|
|
1098
|
+
resolution_steps: resolutionSteps,
|
|
1099
|
+
contributor_id: contributorId
|
|
1100
|
+
}).select("id").single();
|
|
1101
|
+
if (error) {
|
|
1215
1102
|
return {
|
|
1216
|
-
success:
|
|
1217
|
-
isDuplicate:
|
|
1218
|
-
|
|
1219
|
-
message: "Similar solution already exists. Usage count incremented."
|
|
1103
|
+
success: false,
|
|
1104
|
+
isDuplicate: false,
|
|
1105
|
+
message: `Upload failed: ${error.message}`
|
|
1220
1106
|
};
|
|
1221
1107
|
}
|
|
1222
|
-
}
|
|
1223
|
-
const { data, error } = await this.supabase.from("knowledge_entries").insert({
|
|
1224
|
-
error_hash: errorRecord.errorHash,
|
|
1225
|
-
error_type: errorRecord.errorType,
|
|
1226
|
-
error_message: errorRecord.errorMessage,
|
|
1227
|
-
error_stack: errorRecord.errorStack,
|
|
1228
|
-
language: errorRecord.language || "other",
|
|
1229
|
-
framework: errorRecord.framework,
|
|
1230
|
-
embedding,
|
|
1231
|
-
resolution_description: resolution,
|
|
1232
|
-
resolution_code: resolutionCode,
|
|
1233
|
-
resolution_steps: resolutionSteps,
|
|
1234
|
-
contributor_id: this.contributorId
|
|
1235
|
-
}).select("id").single();
|
|
1236
|
-
if (error) {
|
|
1237
1108
|
return {
|
|
1238
|
-
success:
|
|
1109
|
+
success: true,
|
|
1110
|
+
knowledgeId: data.id,
|
|
1239
1111
|
isDuplicate: false,
|
|
1240
|
-
message:
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
return {
|
|
1244
|
-
success: true,
|
|
1245
|
-
knowledgeId: data.id,
|
|
1246
|
-
isDuplicate: false,
|
|
1247
|
-
message: "Solution uploaded successfully!"
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
/**
|
|
1251
|
-
* Check for duplicate entries
|
|
1252
|
-
*/
|
|
1253
|
-
async checkDuplicate(errorHash, embedding) {
|
|
1254
|
-
const { data: hashMatch } = await this.supabase.from("knowledge_entries").select("id").eq("error_hash", errorHash).limit(1).single();
|
|
1255
|
-
if (hashMatch) {
|
|
1256
|
-
return {
|
|
1257
|
-
isDuplicate: true,
|
|
1258
|
-
existingId: hashMatch.id,
|
|
1259
|
-
similarityScore: 1
|
|
1112
|
+
message: "Solution uploaded successfully!"
|
|
1260
1113
|
};
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1114
|
+
},
|
|
1115
|
+
async checkDuplicate(errorHash, embeddingData) {
|
|
1116
|
+
return checkDuplicateInternal(errorHash, embeddingData);
|
|
1117
|
+
},
|
|
1118
|
+
async vote(knowledgeId, helpful) {
|
|
1119
|
+
const voteType = helpful ? "up" : "down";
|
|
1120
|
+
const { data, error } = await supabase.rpc("safe_vote", {
|
|
1121
|
+
p_entry_id: knowledgeId,
|
|
1122
|
+
p_user_hash: contributorId,
|
|
1123
|
+
p_vote_type: voteType
|
|
1124
|
+
});
|
|
1125
|
+
if (error) {
|
|
1126
|
+
return { success: false, error: error.message };
|
|
1127
|
+
}
|
|
1128
|
+
const result = data;
|
|
1129
|
+
if (result.success) {
|
|
1130
|
+
await supabase.from("usage_logs").insert({
|
|
1131
|
+
knowledge_id: knowledgeId,
|
|
1132
|
+
action: helpful ? "upvote" : "downvote",
|
|
1133
|
+
user_hash: contributorId
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
return result;
|
|
1137
|
+
},
|
|
1138
|
+
async reportEntry(knowledgeId, reason) {
|
|
1139
|
+
const { data, error } = await supabase.rpc("report_entry", {
|
|
1140
|
+
p_entry_id: knowledgeId,
|
|
1141
|
+
p_user_hash: contributorId,
|
|
1142
|
+
p_reason: reason || null
|
|
1143
|
+
});
|
|
1144
|
+
if (error) {
|
|
1145
|
+
return { success: false };
|
|
1146
|
+
}
|
|
1147
|
+
return data;
|
|
1148
|
+
},
|
|
1149
|
+
async reportHelpful(knowledgeId) {
|
|
1150
|
+
await supabase.rpc("increment_usage_count", {
|
|
1151
|
+
entry_id: knowledgeId
|
|
1152
|
+
});
|
|
1153
|
+
await supabase.from("usage_logs").insert({
|
|
1293
1154
|
knowledge_id: knowledgeId,
|
|
1294
|
-
action:
|
|
1295
|
-
user_hash:
|
|
1155
|
+
action: "apply",
|
|
1156
|
+
user_hash: contributorId
|
|
1296
1157
|
});
|
|
1158
|
+
},
|
|
1159
|
+
async getContributorStats() {
|
|
1160
|
+
const { data } = await supabase.from("knowledge_entries").select("upvotes, usage_count").eq("contributor_id", contributorId);
|
|
1161
|
+
if (!data || data.length === 0) {
|
|
1162
|
+
return { contributionCount: 0, helpedCount: 0, totalUpvotes: 0 };
|
|
1163
|
+
}
|
|
1164
|
+
return {
|
|
1165
|
+
contributionCount: data.length,
|
|
1166
|
+
helpedCount: data.reduce((sum, e) => sum + (e.usage_count || 0), 0),
|
|
1167
|
+
totalUpvotes: data.reduce((sum, e) => sum + (e.upvotes || 0), 0)
|
|
1168
|
+
};
|
|
1169
|
+
},
|
|
1170
|
+
async getEntry(id) {
|
|
1171
|
+
const { data, error } = await supabase.from("knowledge_entries").select("*").eq("id", id).single();
|
|
1172
|
+
if (error || !data) {
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
return mapToKnowledgeEntry(data);
|
|
1176
|
+
},
|
|
1177
|
+
getContributorId() {
|
|
1178
|
+
return contributorId;
|
|
1179
|
+
},
|
|
1180
|
+
hasEmbeddingService() {
|
|
1181
|
+
return embedding !== null;
|
|
1297
1182
|
}
|
|
1298
|
-
|
|
1299
|
-
}
|
|
1300
|
-
/**
|
|
1301
|
-
* Report an entry for review
|
|
1302
|
-
*/
|
|
1303
|
-
async reportEntry(knowledgeId, reason) {
|
|
1304
|
-
const { data, error } = await this.supabase.rpc("report_entry", {
|
|
1305
|
-
p_entry_id: knowledgeId,
|
|
1306
|
-
p_user_hash: this.contributorId,
|
|
1307
|
-
p_reason: reason || null
|
|
1308
|
-
});
|
|
1309
|
-
if (error) {
|
|
1310
|
-
return { success: false };
|
|
1311
|
-
}
|
|
1312
|
-
return data;
|
|
1313
|
-
}
|
|
1314
|
-
/**
|
|
1315
|
-
* Report helpful usage
|
|
1316
|
-
*/
|
|
1317
|
-
async reportHelpful(knowledgeId) {
|
|
1318
|
-
await this.supabase.rpc("increment_usage_count", {
|
|
1319
|
-
entry_id: knowledgeId
|
|
1320
|
-
});
|
|
1321
|
-
await this.supabase.from("usage_logs").insert({
|
|
1322
|
-
knowledge_id: knowledgeId,
|
|
1323
|
-
action: "apply",
|
|
1324
|
-
user_hash: this.contributorId
|
|
1325
|
-
});
|
|
1326
|
-
}
|
|
1327
|
-
/**
|
|
1328
|
-
* Get contributor statistics
|
|
1329
|
-
*/
|
|
1330
|
-
async getContributorStats() {
|
|
1331
|
-
const { data } = await this.supabase.from("knowledge_entries").select("upvotes, usage_count").eq("contributor_id", this.contributorId);
|
|
1332
|
-
if (!data || data.length === 0) {
|
|
1333
|
-
return { contributionCount: 0, helpedCount: 0, totalUpvotes: 0 };
|
|
1334
|
-
}
|
|
1335
|
-
return {
|
|
1336
|
-
contributionCount: data.length,
|
|
1337
|
-
helpedCount: data.reduce((sum, e) => sum + (e.usage_count || 0), 0),
|
|
1338
|
-
totalUpvotes: data.reduce((sum, e) => sum + (e.upvotes || 0), 0)
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1341
|
-
/**
|
|
1342
|
-
* Get entry by ID
|
|
1343
|
-
*/
|
|
1344
|
-
async getEntry(id) {
|
|
1345
|
-
const { data, error } = await this.supabase.from("knowledge_entries").select("*").eq("id", id).single();
|
|
1346
|
-
if (error || !data) {
|
|
1347
|
-
return null;
|
|
1348
|
-
}
|
|
1349
|
-
return this.mapToKnowledgeEntry(data);
|
|
1350
|
-
}
|
|
1351
|
-
/**
|
|
1352
|
-
* Map database row to CloudKnowledgeEntry
|
|
1353
|
-
*/
|
|
1354
|
-
mapToKnowledgeEntry(row) {
|
|
1355
|
-
return {
|
|
1356
|
-
id: row.id,
|
|
1357
|
-
errorHash: row.error_hash,
|
|
1358
|
-
errorType: row.error_type,
|
|
1359
|
-
errorMessage: row.error_message,
|
|
1360
|
-
errorStack: row.error_stack || void 0,
|
|
1361
|
-
language: row.language,
|
|
1362
|
-
framework: row.framework || void 0,
|
|
1363
|
-
dependencies: row.dependencies || void 0,
|
|
1364
|
-
resolutionDescription: row.resolution_description,
|
|
1365
|
-
resolutionCode: row.resolution_code || void 0,
|
|
1366
|
-
resolutionSteps: row.resolution_steps || void 0,
|
|
1367
|
-
contributorId: row.contributor_id,
|
|
1368
|
-
upvotes: row.upvotes || 0,
|
|
1369
|
-
downvotes: row.downvotes || 0,
|
|
1370
|
-
usageCount: row.usage_count || 0,
|
|
1371
|
-
createdAt: row.created_at,
|
|
1372
|
-
updatedAt: row.updated_at,
|
|
1373
|
-
isVerified: row.is_verified || false,
|
|
1374
|
-
similarity: row.similarity || void 0
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
/**
|
|
1378
|
-
* Get contributor ID
|
|
1379
|
-
*/
|
|
1380
|
-
getContributorId() {
|
|
1381
|
-
return this.contributorId;
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Check if embedding service is available
|
|
1385
|
-
*/
|
|
1386
|
-
hasEmbeddingService() {
|
|
1387
|
-
return this.embedding !== null;
|
|
1388
|
-
}
|
|
1389
|
-
};
|
|
1390
|
-
async function createCloudClient(config) {
|
|
1391
|
-
return CloudClient.create(config);
|
|
1183
|
+
};
|
|
1392
1184
|
}
|
|
1185
|
+
var CloudClient = {
|
|
1186
|
+
create: createCloudClient
|
|
1187
|
+
};
|
|
1393
1188
|
|
|
1394
1189
|
// src/plugin/tools.ts
|
|
1395
1190
|
import { tool } from "@opencode-ai/plugin";
|
|
1396
1191
|
function createTools(localStore, cloudClient, privacyFilter, context) {
|
|
1397
1192
|
return {
|
|
1398
|
-
/**
|
|
1399
|
-
* Search cloud knowledge base for error solutions
|
|
1400
|
-
*/
|
|
1401
1193
|
fixhive_search: tool({
|
|
1402
1194
|
description: "Search FixHive knowledge base for error solutions. Use when encountering errors to find community solutions.",
|
|
1403
1195
|
args: {
|
|
@@ -1426,9 +1218,6 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
|
|
|
1426
1218
|
return formatSearchResults(results.results, false);
|
|
1427
1219
|
}
|
|
1428
1220
|
}),
|
|
1429
|
-
/**
|
|
1430
|
-
* Mark error as resolved and optionally upload solution
|
|
1431
|
-
*/
|
|
1432
1221
|
fixhive_resolve: tool({
|
|
1433
1222
|
description: "Mark an error as resolved and optionally share the solution with the community.",
|
|
1434
1223
|
args: {
|
|
@@ -1464,9 +1253,6 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
|
|
|
1464
1253
|
return "Error marked as resolved locally.";
|
|
1465
1254
|
}
|
|
1466
1255
|
}),
|
|
1467
|
-
/**
|
|
1468
|
-
* List errors in current session
|
|
1469
|
-
*/
|
|
1470
1256
|
fixhive_list: tool({
|
|
1471
1257
|
description: "List errors detected in the current session.",
|
|
1472
1258
|
args: {
|
|
@@ -1485,9 +1271,6 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
|
|
|
1485
1271
|
return formatErrorList(errors);
|
|
1486
1272
|
}
|
|
1487
1273
|
}),
|
|
1488
|
-
/**
|
|
1489
|
-
* Vote on a solution
|
|
1490
|
-
*/
|
|
1491
1274
|
fixhive_vote: tool({
|
|
1492
1275
|
description: "Upvote or downvote a FixHive solution based on whether it helped.",
|
|
1493
1276
|
args: {
|
|
@@ -1505,9 +1288,6 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
|
|
|
1505
1288
|
return args.helpful ? "Thanks for the feedback! Solution upvoted." : "Thanks for the feedback! Solution downvoted.";
|
|
1506
1289
|
}
|
|
1507
1290
|
}),
|
|
1508
|
-
/**
|
|
1509
|
-
* Report inappropriate content
|
|
1510
|
-
*/
|
|
1511
1291
|
fixhive_report: tool({
|
|
1512
1292
|
description: "Report a FixHive solution for inappropriate content, spam, or incorrect information.",
|
|
1513
1293
|
args: {
|
|
@@ -1522,9 +1302,6 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
|
|
|
1522
1302
|
return "Report submitted. Thank you for helping keep FixHive clean!";
|
|
1523
1303
|
}
|
|
1524
1304
|
}),
|
|
1525
|
-
/**
|
|
1526
|
-
* Get usage statistics
|
|
1527
|
-
*/
|
|
1528
1305
|
fixhive_stats: tool({
|
|
1529
1306
|
description: "Get FixHive usage statistics.",
|
|
1530
1307
|
args: {},
|
|
@@ -1546,9 +1323,6 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
|
|
|
1546
1323
|
`;
|
|
1547
1324
|
}
|
|
1548
1325
|
}),
|
|
1549
|
-
/**
|
|
1550
|
-
* Report that a solution was helpful
|
|
1551
|
-
*/
|
|
1552
1326
|
fixhive_helpful: tool({
|
|
1553
1327
|
description: "Report that a FixHive solution was helpful and resolved your issue.",
|
|
1554
1328
|
args: {
|
|
@@ -1583,7 +1357,8 @@ ${r.resolutionCode}
|
|
|
1583
1357
|
if (r.resolutionSteps?.length) {
|
|
1584
1358
|
entry += `
|
|
1585
1359
|
**Steps:**
|
|
1586
|
-
${r.resolutionSteps.map((s, j) => `${j + 1}. ${s}`).join(
|
|
1360
|
+
${r.resolutionSteps.map((s, j) => `${j + 1}. ${s}`).join(`
|
|
1361
|
+
`)}
|
|
1587
1362
|
`;
|
|
1588
1363
|
}
|
|
1589
1364
|
entry += `
|
|
@@ -1591,7 +1366,8 @@ ${r.resolutionSteps.map((s, j) => `${j + 1}. ${s}`).join("\n")}
|
|
|
1591
1366
|
|
|
1592
1367
|
---`;
|
|
1593
1368
|
return entry;
|
|
1594
|
-
}).join(
|
|
1369
|
+
}).join(`
|
|
1370
|
+
`);
|
|
1595
1371
|
return `${header}
|
|
1596
1372
|
${entries}
|
|
1597
1373
|
|
|
@@ -1602,9 +1378,8 @@ function formatErrorList(errors) {
|
|
|
1602
1378
|
const table = `
|
|
1603
1379
|
| ID | Type | Status | Message |
|
|
1604
1380
|
|----|------|--------|---------|
|
|
1605
|
-
${errors.map(
|
|
1606
|
-
|
|
1607
|
-
).join("\n")}
|
|
1381
|
+
${errors.map((e) => `| ${e.id.slice(0, 8)} | ${e.errorType} | ${e.status} | ${e.errorMessage.slice(0, 50)}... |`).join(`
|
|
1382
|
+
`)}
|
|
1608
1383
|
`;
|
|
1609
1384
|
return `${header}
|
|
1610
1385
|
${table}
|
|
@@ -1614,8 +1389,7 @@ Use \`fixhive_resolve <id>\` to mark as resolved and share solutions.`;
|
|
|
1614
1389
|
|
|
1615
1390
|
// src/plugin/index.ts
|
|
1616
1391
|
var DEFAULT_CONFIG = {
|
|
1617
|
-
cacheExpirationMs:
|
|
1618
|
-
// 1 hour
|
|
1392
|
+
cacheExpirationMs: 3600000,
|
|
1619
1393
|
embeddingModel: "text-embedding-3-small",
|
|
1620
1394
|
embeddingDimensions: 1536,
|
|
1621
1395
|
similarityThreshold: 0.7,
|
|
@@ -1626,14 +1400,14 @@ var FixHivePlugin = async (ctx) => {
|
|
|
1626
1400
|
console.log("[FixHive] Plugin loaded");
|
|
1627
1401
|
console.log(`[FixHive] Project: ${ctx.directory}`);
|
|
1628
1402
|
console.log(`[FixHive] Cloud: ${config.supabaseUrl ? "enabled" : "disabled"}`);
|
|
1629
|
-
const privacyFilter = new PrivacyFilter
|
|
1403
|
+
const privacyFilter = new PrivacyFilter;
|
|
1630
1404
|
const filterContext = createFilterContext(ctx.directory);
|
|
1631
1405
|
const errorDetector = new ErrorDetector(privacyFilter);
|
|
1632
1406
|
const localStore = new LocalStore(ctx.directory);
|
|
1633
1407
|
let cloudClient = null;
|
|
1634
1408
|
if (config.supabaseUrl && config.supabaseAnonKey) {
|
|
1635
1409
|
try {
|
|
1636
|
-
cloudClient = await
|
|
1410
|
+
cloudClient = await createCloudClient({
|
|
1637
1411
|
supabaseUrl: config.supabaseUrl,
|
|
1638
1412
|
supabaseAnonKey: config.supabaseAnonKey,
|
|
1639
1413
|
openaiApiKey: config.openaiApiKey,
|
|
@@ -1657,9 +1431,9 @@ var FixHivePlugin = async (ctx) => {
|
|
|
1657
1431
|
console.log("[FixHive] Ready - use fixhive_stats to verify");
|
|
1658
1432
|
const errorProducingTools = ["bash", "edit", "write", "read", "terminal"];
|
|
1659
1433
|
return {
|
|
1660
|
-
// ============ Tool Execution Hook ============
|
|
1661
1434
|
"tool.execute.after": async (input, output) => {
|
|
1662
|
-
if (!errorProducingTools.includes(input.tool))
|
|
1435
|
+
if (!errorProducingTools.includes(input.tool))
|
|
1436
|
+
return;
|
|
1663
1437
|
const detection = errorDetector.detect({
|
|
1664
1438
|
tool: input.tool,
|
|
1665
1439
|
output: output.output,
|
|
@@ -1669,7 +1443,7 @@ var FixHivePlugin = async (ctx) => {
|
|
|
1669
1443
|
});
|
|
1670
1444
|
if (detection.detected && detection.confidence >= 0.5) {
|
|
1671
1445
|
const sanitizedErrorMessage = privacyFilter.sanitize(detection.errorMessage, filterContext).sanitized;
|
|
1672
|
-
const sanitizedErrorStack = detection.errorStack ? privacyFilter.sanitize(detection.errorStack, filterContext).sanitized :
|
|
1446
|
+
const sanitizedErrorStack = detection.errorStack ? privacyFilter.sanitize(detection.errorStack, filterContext).sanitized : undefined;
|
|
1673
1447
|
localStore.createErrorRecord({
|
|
1674
1448
|
errorType: detection.errorType,
|
|
1675
1449
|
errorMessage: sanitizedErrorMessage,
|
|
@@ -1678,7 +1452,6 @@ var FixHivePlugin = async (ctx) => {
|
|
|
1678
1452
|
framework: pluginContext.framework,
|
|
1679
1453
|
toolName: input.tool,
|
|
1680
1454
|
toolInput: {},
|
|
1681
|
-
// Tool input is intentionally omitted to avoid storing sensitive data
|
|
1682
1455
|
sessionId: pluginContext.sessionId || input.sessionID
|
|
1683
1456
|
});
|
|
1684
1457
|
if (cloudClient) {
|
|
@@ -1691,10 +1464,7 @@ var FixHivePlugin = async (ctx) => {
|
|
|
1691
1464
|
limit: 3
|
|
1692
1465
|
});
|
|
1693
1466
|
if (solutions.results.length > 0) {
|
|
1694
|
-
localStore.cacheResults(
|
|
1695
|
-
generateErrorFingerprint(sanitizedErrorMessage, sanitizedErrorStack),
|
|
1696
|
-
solutions.results
|
|
1697
|
-
);
|
|
1467
|
+
localStore.cacheResults(generateErrorFingerprint(sanitizedErrorMessage, sanitizedErrorStack), solutions.results);
|
|
1698
1468
|
output.title = `${output.title} [FixHive: ${solutions.results.length} solution(s) found]`;
|
|
1699
1469
|
}
|
|
1700
1470
|
} catch (e) {
|
|
@@ -1704,24 +1474,22 @@ var FixHivePlugin = async (ctx) => {
|
|
|
1704
1474
|
}
|
|
1705
1475
|
}
|
|
1706
1476
|
},
|
|
1707
|
-
// ============ Session Compaction Hook ============
|
|
1708
1477
|
"experimental.session.compacting": async (_input, output) => {
|
|
1709
1478
|
const unresolvedErrors = localStore.getUnresolvedErrors(pluginContext.sessionId);
|
|
1710
1479
|
if (unresolvedErrors.length > 0) {
|
|
1711
1480
|
output.context.push(`
|
|
1712
1481
|
## FixHive: Unresolved Errors in Session
|
|
1713
1482
|
|
|
1714
|
-
${unresolvedErrors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 100)}...`).join(
|
|
1483
|
+
${unresolvedErrors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 100)}...`).join(`
|
|
1484
|
+
`)}
|
|
1715
1485
|
|
|
1716
1486
|
Use \`fixhive_mark_resolved\` when errors are fixed to contribute solutions.
|
|
1717
1487
|
`);
|
|
1718
1488
|
}
|
|
1719
1489
|
},
|
|
1720
|
-
// ============ Chat Message Hook ============
|
|
1721
1490
|
"chat.message": async (input, _output) => {
|
|
1722
1491
|
pluginContext.sessionId = input.sessionID;
|
|
1723
1492
|
},
|
|
1724
|
-
// ============ Custom Tools ============
|
|
1725
1493
|
tool: cloudClient ? createTools(localStore, cloudClient, privacyFilter, pluginContext) : createOfflineTools(localStore, privacyFilter, pluginContext)
|
|
1726
1494
|
};
|
|
1727
1495
|
};
|
|
@@ -1744,7 +1512,8 @@ function createOfflineTools(localStore, _privacyFilter, context) {
|
|
|
1744
1512
|
}
|
|
1745
1513
|
return `## Session Errors (${errors.length})
|
|
1746
1514
|
|
|
1747
|
-
${errors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 80)}...`).join(
|
|
1515
|
+
${errors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 80)}...`).join(`
|
|
1516
|
+
`)}
|
|
1748
1517
|
|
|
1749
1518
|
*Cloud features disabled. Set FIXHIVE_SUPABASE_URL and FIXHIVE_SUPABASE_KEY to enable.*`;
|
|
1750
1519
|
}
|
|
@@ -1803,7 +1572,7 @@ function detectLanguage(directory) {
|
|
|
1803
1572
|
return lang;
|
|
1804
1573
|
}
|
|
1805
1574
|
}
|
|
1806
|
-
return
|
|
1575
|
+
return;
|
|
1807
1576
|
}
|
|
1808
1577
|
function detectFramework(directory) {
|
|
1809
1578
|
const pkgPath = join(directory, "package.json");
|
|
@@ -1811,49 +1580,57 @@ function detectFramework(directory) {
|
|
|
1811
1580
|
try {
|
|
1812
1581
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1813
1582
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1814
|
-
if (deps["next"])
|
|
1815
|
-
|
|
1816
|
-
if (deps["
|
|
1817
|
-
|
|
1818
|
-
if (deps["
|
|
1819
|
-
|
|
1820
|
-
if (deps["
|
|
1821
|
-
|
|
1822
|
-
|
|
1583
|
+
if (deps["next"])
|
|
1584
|
+
return "nextjs";
|
|
1585
|
+
if (deps["react"])
|
|
1586
|
+
return "react";
|
|
1587
|
+
if (deps["vue"])
|
|
1588
|
+
return "vue";
|
|
1589
|
+
if (deps["@angular/core"])
|
|
1590
|
+
return "angular";
|
|
1591
|
+
if (deps["express"])
|
|
1592
|
+
return "express";
|
|
1593
|
+
if (deps["fastify"])
|
|
1594
|
+
return "fastify";
|
|
1595
|
+
if (deps["hono"])
|
|
1596
|
+
return "hono";
|
|
1597
|
+
} catch {}
|
|
1823
1598
|
}
|
|
1824
1599
|
const reqPath = join(directory, "requirements.txt");
|
|
1825
1600
|
if (existsSync2(reqPath)) {
|
|
1826
1601
|
try {
|
|
1827
1602
|
const content = readFileSync(reqPath, "utf-8");
|
|
1828
|
-
if (content.includes("django"))
|
|
1829
|
-
|
|
1830
|
-
if (content.includes("
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1603
|
+
if (content.includes("django"))
|
|
1604
|
+
return "django";
|
|
1605
|
+
if (content.includes("flask"))
|
|
1606
|
+
return "flask";
|
|
1607
|
+
if (content.includes("fastapi"))
|
|
1608
|
+
return "fastapi";
|
|
1609
|
+
} catch {}
|
|
1610
|
+
}
|
|
1611
|
+
return;
|
|
1835
1612
|
}
|
|
1836
1613
|
var plugin_default = FixHivePlugin;
|
|
1837
1614
|
export {
|
|
1838
|
-
|
|
1839
|
-
EmbeddingService,
|
|
1840
|
-
ErrorDetector,
|
|
1841
|
-
FixHivePlugin,
|
|
1842
|
-
LocalStore,
|
|
1843
|
-
PrivacyFilter,
|
|
1844
|
-
calculateStringSimilarity,
|
|
1845
|
-
createCloudClient,
|
|
1846
|
-
createEmbeddingService,
|
|
1847
|
-
createFilterContext,
|
|
1848
|
-
plugin_default as default,
|
|
1849
|
-
defaultErrorDetector,
|
|
1850
|
-
defaultPrivacyFilter,
|
|
1851
|
-
fingerprintsMatch,
|
|
1852
|
-
generateContributorId,
|
|
1853
|
-
generateErrorFingerprint,
|
|
1854
|
-
generateSessionHash,
|
|
1855
|
-
normalizeErrorContent,
|
|
1856
|
-
runMigrations,
|
|
1615
|
+
shortHash,
|
|
1857
1616
|
sha256,
|
|
1858
|
-
|
|
1617
|
+
runMigrations,
|
|
1618
|
+
normalizeErrorContent,
|
|
1619
|
+
generateSessionHash,
|
|
1620
|
+
generateErrorFingerprint,
|
|
1621
|
+
generateContributorId,
|
|
1622
|
+
fingerprintsMatch,
|
|
1623
|
+
defaultPrivacyFilter,
|
|
1624
|
+
defaultErrorDetector,
|
|
1625
|
+
plugin_default as default,
|
|
1626
|
+
createFilterContext,
|
|
1627
|
+
createEmbeddingService,
|
|
1628
|
+
createCloudClient,
|
|
1629
|
+
calculateStringSimilarity,
|
|
1630
|
+
PrivacyFilter,
|
|
1631
|
+
LocalStore,
|
|
1632
|
+
FixHivePlugin,
|
|
1633
|
+
ErrorDetector,
|
|
1634
|
+
EmbeddingService,
|
|
1635
|
+
CloudClient
|
|
1859
1636
|
};
|