@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/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
- var PrivacyFilter = class {
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: /* @__PURE__ */ new Map([
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
- var ErrorDetector = class {
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 !== void 0 && toolOutput.exitCode !== 0) {
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("\n"),
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) : void 0;
454
- const sanitizedOutput = this.privacyFilter.sanitize(combinedOutput.substring(0, 5e3));
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") continue;
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) return 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))) return "build";
538
- if (ERROR_PATTERNS.package.some((p) => p.test(content))) return "dependency";
539
- if (ERROR_PATTERNS.permission.some((p) => p.test(content))) return "permission";
540
- if (ERROR_PATTERNS.network.some((p) => p.test(content))) return "network";
541
- if (ERROR_PATTERNS.test.some((p) => p.test(content))) return "test";
542
- if (/TypeError:|type error|Type '[^']+' is not assignable/i.test(content)) return "type_error";
543
- if (/SyntaxError:|syntax error|unexpected token/i.test(content)) return "syntax";
544
- if (/ReferenceError:|RangeError:|runtime error/i.test(content)) return "runtime";
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) return "runtime";
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 !== void 0 && EXIT_CODE_SEVERITY[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) return "error";
558
- if (maxWeight >= 0.7) return "error";
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("\n");
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/) || // JS stack
574
- line.match(/^\s+File\s/) || // Python stack
575
- line.match(/^\s+\d+:\s/) || // Rust stack
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 || void 0
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 = /* @__PURE__ */ new Set([...words1, ...words2]);
646
- if (union.size === 0) return 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
- var LocalStore = class _LocalStore {
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 (!_LocalStore.ALLOWED_STATS.includes(stat)) {
939
- throw new Error(`Invalid stat name: ${stat}. Allowed: ${_LocalStore.ALLOWED_STATS.join(", ")}`);
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 || void 0,
973
- language: row.language || void 0,
974
- framework: row.framework || void 0,
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 || void 0,
980
- resolutionCode: row.resolution_code || void 0,
845
+ resolution: row.resolution || undefined,
846
+ resolutionCode: row.resolution_code || undefined,
981
847
  createdAt: row.created_at,
982
- resolvedAt: row.resolved_at || void 0,
983
- uploadedAt: row.uploaded_at || void 0,
984
- cloudKnowledgeId: row.cloud_knowledge_id || void 0
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 = 3e4;
1006
- var EmbeddingService = class {
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("\n");
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("\n");
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; i < a.length; i++) {
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) return 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
- var CloudClient = class _CloudClient {
1119
- supabase;
1120
- embedding;
1121
- contributorId;
1122
- similarityThreshold;
1123
- constructor(supabase, embedding, contributorId, similarityThreshold) {
1124
- this.supabase = supabase;
1125
- this.embedding = embedding;
1126
- this.contributorId = contributorId;
1127
- this.similarityThreshold = similarityThreshold;
1128
- }
1129
- /**
1130
- * Create a CloudClient instance (async factory for Bun compatibility)
1131
- */
1132
- static async create(config) {
1133
- const createClientFn = await getCreateClient();
1134
- const supabase = createClientFn(config.supabaseUrl, config.supabaseAnonKey);
1135
- let embedding = null;
1136
- if (config.openaiApiKey) {
1137
- try {
1138
- embedding = new EmbeddingService(config.openaiApiKey);
1139
- } catch (err) {
1140
- console.warn("[FixHive] Failed to initialize embedding service:", err);
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
- * Search for similar errors in cloud knowledge base
1149
- */
1150
- async searchSimilar(request) {
1151
- const startTime = Date.now();
1152
- if (!this.embedding) {
1153
- return this.searchByText(request);
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 queryText = `${request.errorMessage}
1156
- ${request.errorStack || ""}`;
1157
- const embedding = await this.embedding.generate(queryText);
1158
- const { data, error } = await this.supabase.rpc("search_similar_errors", {
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
- console.error("Search error:", error);
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
- results: (data || []).map(this.mapToKnowledgeEntry),
1171
- queryTime: Date.now() - startTime,
1172
- cached: false
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 = this.supabase.from("knowledge_entries").select("*").ilike("error_message", `%${request.errorMessage.substring(0, 100)}%`).order("upvotes", { ascending: false }).limit(request.limit || 10);
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(this.mapToKnowledgeEntry),
1035
+ results: (data || []).map(mapToKnowledgeEntry),
1194
1036
  queryTime: Date.now() - startTime,
1195
1037
  cached: false
1196
1038
  };
1197
1039
  }
1198
- /**
1199
- * Upload a resolution to cloud knowledge base
1200
- */
1201
- async uploadResolution(request) {
1202
- const { errorRecord, resolution, resolutionCode, resolutionSteps } = request;
1203
- let embedding = null;
1204
- if (this.embedding) {
1205
- const embeddingText = `${errorRecord.errorMessage}
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
- embedding = await this.embedding.generate(embeddingText);
1208
- }
1209
- if (embedding) {
1210
- const duplicateCheck = await this.checkDuplicate(errorRecord.errorHash, embedding);
1211
- if (duplicateCheck.isDuplicate && duplicateCheck.similarityScore > 0.95) {
1212
- await this.supabase.rpc("increment_usage_count", {
1213
- entry_id: duplicateCheck.existingId
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: true,
1217
- isDuplicate: true,
1218
- existingId: duplicateCheck.existingId,
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: false,
1109
+ success: true,
1110
+ knowledgeId: data.id,
1239
1111
  isDuplicate: false,
1240
- message: `Upload failed: ${error.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
- const { data, error } = await this.supabase.rpc("check_duplicate_entry", {
1263
- new_hash: errorHash,
1264
- new_embedding: embedding,
1265
- similarity_threshold: 0.95
1266
- });
1267
- if (error || !data || data.length === 0) {
1268
- return { isDuplicate: false, similarityScore: 0 };
1269
- }
1270
- const result = data[0];
1271
- return {
1272
- isDuplicate: result.is_duplicate,
1273
- existingId: result.existing_id,
1274
- similarityScore: result.similarity_score
1275
- };
1276
- }
1277
- /**
1278
- * Vote on a knowledge entry (with duplicate vote prevention)
1279
- */
1280
- async vote(knowledgeId, helpful) {
1281
- const voteType = helpful ? "up" : "down";
1282
- const { data, error } = await this.supabase.rpc("safe_vote", {
1283
- p_entry_id: knowledgeId,
1284
- p_user_hash: this.contributorId,
1285
- p_vote_type: voteType
1286
- });
1287
- if (error) {
1288
- return { success: false, error: error.message };
1289
- }
1290
- const result = data;
1291
- if (result.success) {
1292
- await this.supabase.from("usage_logs").insert({
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: helpful ? "upvote" : "downvote",
1295
- user_hash: this.contributorId
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
- return result;
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("\n")}
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("\n");
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
- (e) => `| ${e.id.slice(0, 8)} | ${e.errorType} | ${e.status} | ${e.errorMessage.slice(0, 50)}... |`
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: 36e5,
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 CloudClient.create({
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)) return;
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 : void 0;
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("\n")}
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("\n")}
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 void 0;
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"]) return "nextjs";
1815
- if (deps["react"]) return "react";
1816
- if (deps["vue"]) return "vue";
1817
- if (deps["@angular/core"]) return "angular";
1818
- if (deps["express"]) return "express";
1819
- if (deps["fastify"]) return "fastify";
1820
- if (deps["hono"]) return "hono";
1821
- } catch {
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")) return "django";
1829
- if (content.includes("flask")) return "flask";
1830
- if (content.includes("fastapi")) return "fastapi";
1831
- } catch {
1832
- }
1833
- }
1834
- return void 0;
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
- CloudClient,
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
- shortHash
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
  };