@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.
Files changed (3) hide show
  1. package/README.md +134 -0
  2. package/dist/index.js +1782 -0
  3. 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
+ };