@the-magic-tower/fixhive-opencode-plugin 0.1.22 → 0.1.24

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,10 +1,14 @@
1
1
  // src/plugin/index.ts
2
2
  import { tool as tool2 } from "@opencode-ai/plugin";
3
- import { existsSync as existsSync2, readFileSync } from "node:fs";
4
- import { join } from "node:path";
3
+ import { existsSync as existsSync2, readFileSync } from "fs";
4
+ import { join } from "path";
5
5
 
6
6
  // src/core/privacy-filter.ts
7
7
  var DEFAULT_FILTER_RULES = [
8
+ // =====================================
9
+ // SECRETS (Critical - Always Filter)
10
+ // =====================================
11
+ // AWS Keys
8
12
  {
9
13
  name: "aws_access_key",
10
14
  category: "secret",
@@ -12,6 +16,7 @@ var DEFAULT_FILTER_RULES = [
12
16
  replacement: "[AWS_KEY_REDACTED]",
13
17
  priority: 100
14
18
  },
19
+ // OpenAI API Keys
15
20
  {
16
21
  name: "openai_key",
17
22
  category: "secret",
@@ -19,6 +24,7 @@ var DEFAULT_FILTER_RULES = [
19
24
  replacement: "[OPENAI_KEY_REDACTED]",
20
25
  priority: 100
21
26
  },
27
+ // GitHub Tokens
22
28
  {
23
29
  name: "github_token",
24
30
  category: "secret",
@@ -26,6 +32,7 @@ var DEFAULT_FILTER_RULES = [
26
32
  replacement: "[GITHUB_TOKEN_REDACTED]",
27
33
  priority: 100
28
34
  },
35
+ // Google API Keys
29
36
  {
30
37
  name: "google_api_key",
31
38
  category: "secret",
@@ -33,6 +40,7 @@ var DEFAULT_FILTER_RULES = [
33
40
  replacement: "[GOOGLE_API_KEY_REDACTED]",
34
41
  priority: 100
35
42
  },
43
+ // Stripe Keys
36
44
  {
37
45
  name: "stripe_key",
38
46
  category: "secret",
@@ -40,6 +48,7 @@ var DEFAULT_FILTER_RULES = [
40
48
  replacement: "[STRIPE_KEY_REDACTED]",
41
49
  priority: 100
42
50
  },
51
+ // JWT Tokens (limited length to prevent ReDoS)
43
52
  {
44
53
  name: "jwt_token",
45
54
  category: "secret",
@@ -47,6 +56,7 @@ var DEFAULT_FILTER_RULES = [
47
56
  replacement: "[JWT_REDACTED]",
48
57
  priority: 100
49
58
  },
59
+ // Bearer Tokens
50
60
  {
51
61
  name: "bearer_token",
52
62
  category: "secret",
@@ -54,6 +64,7 @@ var DEFAULT_FILTER_RULES = [
54
64
  replacement: "$1 [TOKEN_REDACTED]",
55
65
  priority: 100
56
66
  },
67
+ // Private Keys
57
68
  {
58
69
  name: "private_key",
59
70
  category: "secret",
@@ -61,6 +72,7 @@ var DEFAULT_FILTER_RULES = [
61
72
  replacement: "[PRIVATE_KEY_REDACTED]",
62
73
  priority: 100
63
74
  },
75
+ // Generic API Keys (context-based, limited length to prevent ReDoS)
64
76
  {
65
77
  name: "generic_api_key",
66
78
  category: "secret",
@@ -68,6 +80,7 @@ var DEFAULT_FILTER_RULES = [
68
80
  replacement: "$1=[KEY_REDACTED]",
69
81
  priority: 95
70
82
  },
83
+ // Secret/Password assignments (limited length to prevent ReDoS)
71
84
  {
72
85
  name: "secret_assignment",
73
86
  category: "secret",
@@ -75,6 +88,10 @@ var DEFAULT_FILTER_RULES = [
75
88
  replacement: "$1=[REDACTED]",
76
89
  priority: 90
77
90
  },
91
+ // =====================================
92
+ // IDENTITY (High Risk)
93
+ // =====================================
94
+ // Email Addresses
78
95
  {
79
96
  name: "email",
80
97
  category: "identity",
@@ -82,6 +99,10 @@ var DEFAULT_FILTER_RULES = [
82
99
  replacement: "[EMAIL_REDACTED]",
83
100
  priority: 80
84
101
  },
102
+ // =====================================
103
+ // INFRASTRUCTURE (Medium Risk)
104
+ // =====================================
105
+ // Database Connection Strings
85
106
  {
86
107
  name: "db_connection",
87
108
  category: "infrastructure",
@@ -89,6 +110,7 @@ var DEFAULT_FILTER_RULES = [
89
110
  replacement: "$1://[CONNECTION_REDACTED]",
90
111
  priority: 85
91
112
  },
113
+ // Internal URLs
92
114
  {
93
115
  name: "internal_url",
94
116
  category: "infrastructure",
@@ -96,6 +118,7 @@ var DEFAULT_FILTER_RULES = [
96
118
  replacement: "[INTERNAL_URL_REDACTED]",
97
119
  priority: 75
98
120
  },
121
+ // IP Addresses (except localhost and common dev IPs)
99
122
  {
100
123
  name: "ipv4",
101
124
  category: "infrastructure",
@@ -108,6 +131,10 @@ var DEFAULT_FILTER_RULES = [
108
131
  },
109
132
  priority: 70
110
133
  },
134
+ // =====================================
135
+ // FILE PATHS (Context-dependent)
136
+ // =====================================
137
+ // Home Directory Paths (macOS)
111
138
  {
112
139
  name: "macos_home_path",
113
140
  category: "path",
@@ -115,6 +142,7 @@ var DEFAULT_FILTER_RULES = [
115
142
  replacement: "~",
116
143
  priority: 60
117
144
  },
145
+ // Home Directory Paths (Linux)
118
146
  {
119
147
  name: "linux_home_path",
120
148
  category: "path",
@@ -122,6 +150,7 @@ var DEFAULT_FILTER_RULES = [
122
150
  replacement: "~",
123
151
  priority: 60
124
152
  },
153
+ // Home Directory Paths (Windows)
125
154
  {
126
155
  name: "windows_home_path",
127
156
  category: "path",
@@ -129,6 +158,10 @@ var DEFAULT_FILTER_RULES = [
129
158
  replacement: "~",
130
159
  priority: 60
131
160
  },
161
+ // =====================================
162
+ // ENVIRONMENT (Medium Risk)
163
+ // =====================================
164
+ // Sensitive Environment Variables
132
165
  {
133
166
  name: "env_var_value",
134
167
  category: "environment",
@@ -157,7 +190,9 @@ var DEFAULT_FILTER_RULES = [
157
190
  }
158
191
  ];
159
192
  function createPrivacyFilter(customRules) {
160
- const rules = [...DEFAULT_FILTER_RULES, ...customRules || []].sort((a, b) => b.priority - a.priority);
193
+ const rules = [...DEFAULT_FILTER_RULES, ...customRules || []].sort(
194
+ (a, b) => b.priority - a.priority
195
+ );
161
196
  function generalizePaths(content, context) {
162
197
  let result = content;
163
198
  if (context.projectRoot) {
@@ -173,6 +208,9 @@ function createPrivacyFilter(customRules) {
173
208
  return result;
174
209
  }
175
210
  return {
211
+ /**
212
+ * Sanitize content by applying all filter rules
213
+ */
176
214
  sanitize(content, context) {
177
215
  let result = content;
178
216
  const appliedFilters = [];
@@ -202,10 +240,16 @@ function createPrivacyFilter(customRules) {
202
240
  appliedFilters: [...new Set(appliedFilters)]
203
241
  };
204
242
  },
243
+ /**
244
+ * Add a custom filter rule
245
+ */
205
246
  addRule(rule) {
206
247
  rules.push(rule);
207
248
  rules.sort((a, b) => b.priority - a.priority);
208
249
  },
250
+ /**
251
+ * Remove a filter rule by name
252
+ */
209
253
  removeRule(name) {
210
254
  const index = rules.findIndex((r) => r.name === name);
211
255
  if (index !== -1) {
@@ -214,9 +258,16 @@ function createPrivacyFilter(customRules) {
214
258
  }
215
259
  return false;
216
260
  },
261
+ /**
262
+ * Get all current rules
263
+ */
217
264
  getRules() {
218
265
  return rules;
219
266
  },
267
+ /**
268
+ * Check if content contains sensitive data
269
+ * Note: Always reset regex lastIndex BEFORE testing to prevent state pollution
270
+ */
220
271
  containsSensitiveData(content) {
221
272
  for (const rule of rules) {
222
273
  if (rule.category === "secret") {
@@ -240,7 +291,7 @@ function createFilterContext(projectDirectory) {
240
291
  return {
241
292
  projectRoot: projectDirectory,
242
293
  homeDir,
243
- commonPaths: new Map([
294
+ commonPaths: /* @__PURE__ */ new Map([
244
295
  ["/usr/local/lib/", "<LIB>/"],
245
296
  ["/usr/lib/", "<LIB>/"],
246
297
  ["/var/log/", "<LOG>/"],
@@ -253,6 +304,7 @@ var defaultPrivacyFilter = createPrivacyFilter();
253
304
 
254
305
  // src/core/error-detector.ts
255
306
  var ERROR_PATTERNS = {
307
+ // Universal error indicators
256
308
  universal: [
257
309
  /\b(error|failed|failure|fatal|exception|panic)\b/i,
258
310
  /\b(cannot|could not|unable to|couldn't)\b/i,
@@ -263,26 +315,36 @@ var ERROR_PATTERNS = {
263
315
  /\b(segmentation fault|segfault|core dumped)\b/i,
264
316
  /\b(out of memory|oom|memory allocation failed)\b/i
265
317
  ],
318
+ // Error prefixes
266
319
  prefixed: [
267
320
  /^Error:/m,
268
321
  /^ERROR\s/m,
269
322
  /^E\s+\d+:/m,
323
+ // Rust errors
270
324
  /^\[ERROR\]/m,
271
325
  /^fatal:/m,
272
326
  /^FATAL:/m,
273
327
  /^panic:/m,
274
328
  /^Traceback \(most recent call last\):/m,
329
+ // Python
275
330
  /^Exception in thread/m,
331
+ // Java
276
332
  /^Uncaught \w+Error:/m
333
+ // JavaScript
277
334
  ],
335
+ // Build/compilation errors
278
336
  build: [
279
337
  /^.+:\d+:\d+:\s*error:/m,
338
+ // GCC/Clang format
280
339
  /error\[E\d+\]:/m,
340
+ // Rust compiler
281
341
  /error TS\d+:/m,
342
+ // TypeScript
282
343
  /SyntaxError:/m,
283
344
  /ParseError:/m,
284
345
  /CompileError:/m
285
346
  ],
347
+ // Package manager errors
286
348
  package: [
287
349
  /npm ERR!/m,
288
350
  /npm error/m,
@@ -295,7 +357,9 @@ var ERROR_PATTERNS = {
295
357
  /Cannot find module/m,
296
358
  /Module not found/m
297
359
  ],
360
+ // Permission errors
298
361
  permission: [/EACCES:/m, /EPERM:/m, /Permission denied/m, /Access denied/m, /Insufficient permissions/m],
362
+ // Network errors
299
363
  network: [
300
364
  /ECONNREFUSED/m,
301
365
  /ETIMEDOUT/m,
@@ -304,6 +368,7 @@ var ERROR_PATTERNS = {
304
368
  /Connection refused/m,
305
369
  /Network is unreachable/m
306
370
  ],
371
+ // Test failures
307
372
  test: [/FAIL /m, /AssertionError/m, /Expected .+ but got/m, /Test failed/i, /\d+ failing/m]
308
373
  };
309
374
  var STACK_TRACE_PATTERNS = {
@@ -318,14 +383,23 @@ var STACK_TRACE_PATTERNS = {
318
383
  };
319
384
  var EXIT_CODE_SEVERITY = {
320
385
  1: "error",
386
+ // General errors
321
387
  2: "error",
388
+ // Misuse of shell builtins
322
389
  126: "error",
390
+ // Command cannot execute
323
391
  127: "error",
392
+ // Command not found
324
393
  128: "critical",
394
+ // Invalid exit argument
325
395
  130: "warning",
396
+ // Script terminated by Ctrl+C
326
397
  137: "critical",
398
+ // SIGKILL (OOM, etc.)
327
399
  139: "critical",
400
+ // Segmentation fault
328
401
  143: "warning"
402
+ // SIGTERM
329
403
  };
330
404
  function createErrorDetector(privacyFilter) {
331
405
  const filter = privacyFilter || createPrivacyFilter();
@@ -343,8 +417,7 @@ function createErrorDetector(privacyFilter) {
343
417
  test: 0.7
344
418
  };
345
419
  for (const [category, patterns] of Object.entries(ERROR_PATTERNS)) {
346
- if (category === "universal")
347
- continue;
420
+ if (category === "universal") continue;
348
421
  for (const pattern of patterns) {
349
422
  const match = content.match(pattern);
350
423
  if (match) {
@@ -379,44 +452,32 @@ function createErrorDetector(privacyFilter) {
379
452
  };
380
453
  }
381
454
  function calculateConfidence(signals) {
382
- if (signals.length === 0)
383
- return 0;
455
+ if (signals.length === 0) return 0;
384
456
  const totalWeight = signals.reduce((sum, s) => sum + s.weight, 0);
385
457
  const avgWeight = totalWeight / signals.length;
386
458
  const multiplier = Math.min(1.2, 1 + signals.length * 0.05);
387
459
  return Math.min(1, avgWeight * multiplier);
388
460
  }
389
461
  function classifyErrorType(signals, content) {
390
- if (ERROR_PATTERNS.build.some((p) => p.test(content)))
391
- return "build";
392
- if (ERROR_PATTERNS.package.some((p) => p.test(content)))
393
- return "dependency";
394
- if (ERROR_PATTERNS.permission.some((p) => p.test(content)))
395
- return "permission";
396
- if (ERROR_PATTERNS.network.some((p) => p.test(content)))
397
- return "network";
398
- if (ERROR_PATTERNS.test.some((p) => p.test(content)))
399
- return "test";
400
- if (/TypeError:|type error|Type '[^']+' is not assignable/i.test(content))
401
- return "type_error";
402
- if (/SyntaxError:|syntax error|unexpected token/i.test(content))
403
- return "syntax";
404
- if (/ReferenceError:|RangeError:|runtime error/i.test(content))
405
- return "runtime";
462
+ if (ERROR_PATTERNS.build.some((p) => p.test(content))) return "build";
463
+ if (ERROR_PATTERNS.package.some((p) => p.test(content))) return "dependency";
464
+ if (ERROR_PATTERNS.permission.some((p) => p.test(content))) return "permission";
465
+ if (ERROR_PATTERNS.network.some((p) => p.test(content))) return "network";
466
+ if (ERROR_PATTERNS.test.some((p) => p.test(content))) return "test";
467
+ if (/TypeError:|type error|Type '[^']+' is not assignable/i.test(content)) return "type_error";
468
+ if (/SyntaxError:|syntax error|unexpected token/i.test(content)) return "syntax";
469
+ if (/ReferenceError:|RangeError:|runtime error/i.test(content)) return "runtime";
406
470
  const hasStackTrace = signals.some((s) => s.type === "stack_trace");
407
- if (hasStackTrace)
408
- return "runtime";
471
+ if (hasStackTrace) return "runtime";
409
472
  return "unknown";
410
473
  }
411
474
  function determineSeverity(signals, exitCode) {
412
- if (exitCode !== undefined && EXIT_CODE_SEVERITY[exitCode]) {
475
+ if (exitCode !== void 0 && EXIT_CODE_SEVERITY[exitCode]) {
413
476
  return EXIT_CODE_SEVERITY[exitCode];
414
477
  }
415
478
  const maxWeight = Math.max(...signals.map((s) => s.weight));
416
- if (maxWeight >= 0.9)
417
- return "error";
418
- if (maxWeight >= 0.7)
419
- return "error";
479
+ if (maxWeight >= 0.9) return "error";
480
+ if (maxWeight >= 0.7) return "error";
420
481
  return "warning";
421
482
  }
422
483
  function isErrorLine(line) {
@@ -424,8 +485,7 @@ function createErrorDetector(privacyFilter) {
424
485
  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);
425
486
  }
426
487
  function extractErrorDetails(output) {
427
- const lines = output.split(`
428
- `);
488
+ const lines = output.split("\n");
429
489
  let message = "";
430
490
  let stack = "";
431
491
  let inStack = false;
@@ -433,9 +493,11 @@ function createErrorDetector(privacyFilter) {
433
493
  if (isErrorLine(line) && !message) {
434
494
  message = line.trim();
435
495
  inStack = true;
436
- } else if (inStack && (line.match(/^\s+at\s/) || line.match(/^\s+File\s/) || line.match(/^\s+\d+:\s/) || line.match(/^\s+from\s/))) {
437
- stack += line + `
438
- `;
496
+ } else if (inStack && (line.match(/^\s+at\s/) || // JS stack
497
+ line.match(/^\s+File\s/) || // Python stack
498
+ line.match(/^\s+\d+:\s/) || // Rust stack
499
+ line.match(/^\s+from\s/))) {
500
+ stack += line + "\n";
439
501
  }
440
502
  }
441
503
  if (!message) {
@@ -448,15 +510,18 @@ function createErrorDetector(privacyFilter) {
448
510
  }
449
511
  return {
450
512
  message: message || output.substring(0, 500),
451
- stack: stack || undefined
513
+ stack: stack || void 0
452
514
  };
453
515
  }
454
516
  return {
517
+ /**
518
+ * Detect if output contains an error
519
+ */
455
520
  detect(toolOutput) {
456
521
  const signals = [];
457
522
  const combinedOutput = `${toolOutput.output || ""}
458
523
  ${toolOutput.stderr || ""}`;
459
- if (toolOutput.exitCode !== undefined && toolOutput.exitCode !== 0) {
524
+ if (toolOutput.exitCode !== void 0 && toolOutput.exitCode !== 0) {
460
525
  const severity2 = EXIT_CODE_SEVERITY[toolOutput.exitCode] || "error";
461
526
  signals.push({
462
527
  type: "exit_code",
@@ -482,8 +547,7 @@ ${toolOutput.stderr || ""}`;
482
547
  signals.push({
483
548
  type: "stack_trace",
484
549
  weight: 0.95,
485
- value: stackTrace.frames.slice(0, 5).join(`
486
- `),
550
+ value: stackTrace.frames.slice(0, 5).join("\n"),
487
551
  description: `${stackTrace.language} stack trace detected`
488
552
  });
489
553
  }
@@ -493,8 +557,8 @@ ${toolOutput.stderr || ""}`;
493
557
  const severity = determineSeverity(signals, toolOutput.exitCode);
494
558
  const { message, stack } = extractErrorDetails(combinedOutput);
495
559
  const sanitizedMessage = filter.sanitize(message);
496
- const sanitizedStack = stack ? filter.sanitize(stack) : undefined;
497
- const sanitizedOutput = filter.sanitize(combinedOutput.substring(0, 5000));
560
+ const sanitizedStack = stack ? filter.sanitize(stack) : void 0;
561
+ const sanitizedOutput = filter.sanitize(combinedOutput.substring(0, 5e3));
498
562
  return {
499
563
  detected,
500
564
  confidence,
@@ -555,9 +619,8 @@ function calculateStringSimilarity(str1, str2) {
555
619
  const words1 = new Set(str1.toLowerCase().split(/\s+/));
556
620
  const words2 = new Set(str2.toLowerCase().split(/\s+/));
557
621
  const intersection = new Set([...words1].filter((x) => words2.has(x)));
558
- const union = new Set([...words1, ...words2]);
559
- if (union.size === 0)
560
- return 0;
622
+ const union = /* @__PURE__ */ new Set([...words1, ...words2]);
623
+ if (union.size === 0) return 0;
561
624
  return intersection.size / union.size;
562
625
  }
563
626
 
@@ -570,7 +633,9 @@ function runMigrations(db) {
570
633
  applied_at TEXT DEFAULT CURRENT_TIMESTAMP
571
634
  )
572
635
  `);
573
- const appliedMigrations = new Set(db.prepare("SELECT name FROM migrations").all().map((r) => r.name));
636
+ const appliedMigrations = new Set(
637
+ db.prepare("SELECT name FROM migrations").all().map((r) => r.name)
638
+ );
574
639
  for (const migration of MIGRATIONS) {
575
640
  if (!appliedMigrations.has(migration.name)) {
576
641
  db.exec(migration.sql);
@@ -664,19 +729,19 @@ function rowToRecord(row) {
664
729
  errorHash: row.error_hash,
665
730
  errorType: row.error_type,
666
731
  errorMessage: row.error_message,
667
- errorStack: row.error_stack || undefined,
668
- language: row.language || undefined,
669
- framework: row.framework || undefined,
732
+ errorStack: row.error_stack || void 0,
733
+ language: row.language || void 0,
734
+ framework: row.framework || void 0,
670
735
  toolName: row.tool_name,
671
736
  toolInput: JSON.parse(row.tool_input || "{}"),
672
737
  sessionId: row.session_id,
673
738
  status: row.status,
674
- resolution: row.resolution || undefined,
675
- resolutionCode: row.resolution_code || undefined,
739
+ resolution: row.resolution || void 0,
740
+ resolutionCode: row.resolution_code || void 0,
676
741
  createdAt: row.created_at,
677
- resolvedAt: row.resolved_at || undefined,
678
- uploadedAt: row.uploaded_at || undefined,
679
- cloudKnowledgeId: row.cloud_knowledge_id || undefined
742
+ resolvedAt: row.resolved_at || void 0,
743
+ uploadedAt: row.uploaded_at || void 0,
744
+ cloudKnowledgeId: row.cloud_knowledge_id || void 0
680
745
  };
681
746
  }
682
747
  function createLocalStore(projectDirectory) {
@@ -697,6 +762,10 @@ function createLocalStore(projectDirectory) {
697
762
  stmt.run();
698
763
  }
699
764
  return {
765
+ // ============ Error Records ============
766
+ /**
767
+ * Create a new error record
768
+ */
700
769
  createErrorRecord(data) {
701
770
  const id = uuidv4();
702
771
  const errorHash = generateErrorFingerprint(data.errorMessage, data.errorStack);
@@ -724,11 +793,17 @@ function createLocalStore(projectDirectory) {
724
793
  incrementStat("total_errors");
725
794
  return this.getErrorById(id);
726
795
  },
796
+ /**
797
+ * Get error record by ID
798
+ */
727
799
  getErrorById(id) {
728
800
  const stmt = db.prepare("SELECT * FROM error_records WHERE id = ?");
729
801
  const row = stmt.get(id);
730
802
  return row ? rowToRecord(row) : null;
731
803
  },
804
+ /**
805
+ * Get errors by session
806
+ */
732
807
  getSessionErrors(sessionId, options) {
733
808
  let query = "SELECT * FROM error_records WHERE session_id = ?";
734
809
  const params = [sessionId];
@@ -744,13 +819,24 @@ function createLocalStore(projectDirectory) {
744
819
  const stmt = db.prepare(query);
745
820
  return stmt.all(...params).map((row) => rowToRecord(row));
746
821
  },
822
+ /**
823
+ * Get unresolved errors for a session
824
+ */
747
825
  getUnresolvedErrors(sessionId) {
748
826
  return this.getSessionErrors(sessionId, { status: "unresolved" });
749
827
  },
828
+ /**
829
+ * Get recent errors across all sessions
830
+ */
750
831
  getRecentErrors(limit = 10) {
751
- const stmt = db.prepare("SELECT * FROM error_records ORDER BY created_at DESC LIMIT ?");
832
+ const stmt = db.prepare(
833
+ "SELECT * FROM error_records ORDER BY created_at DESC LIMIT ?"
834
+ );
752
835
  return stmt.all(limit).map((row) => rowToRecord(row));
753
836
  },
837
+ /**
838
+ * Mark error as resolved
839
+ */
754
840
  markResolved(id, data) {
755
841
  const stmt = db.prepare(`
756
842
  UPDATE error_records
@@ -767,6 +853,9 @@ function createLocalStore(projectDirectory) {
767
853
  }
768
854
  return null;
769
855
  },
856
+ /**
857
+ * Mark error as uploaded to cloud
858
+ */
770
859
  markUploaded(id, cloudKnowledgeId) {
771
860
  const stmt = db.prepare(`
772
861
  UPDATE error_records
@@ -780,10 +869,19 @@ function createLocalStore(projectDirectory) {
780
869
  incrementStat("uploaded_errors");
781
870
  }
782
871
  },
872
+ /**
873
+ * Find similar errors by hash
874
+ */
783
875
  findSimilarErrors(errorHash) {
784
- const stmt = db.prepare("SELECT * FROM error_records WHERE error_hash = ? ORDER BY created_at DESC");
876
+ const stmt = db.prepare(
877
+ "SELECT * FROM error_records WHERE error_hash = ? ORDER BY created_at DESC"
878
+ );
785
879
  return stmt.all(errorHash).map((row) => rowToRecord(row));
786
880
  },
881
+ // ============ Query Cache ============
882
+ /**
883
+ * Get cached query results
884
+ */
787
885
  getCachedResults(errorHash) {
788
886
  const stmt = db.prepare(`
789
887
  SELECT results FROM query_cache
@@ -796,7 +894,10 @@ function createLocalStore(projectDirectory) {
796
894
  }
797
895
  return null;
798
896
  },
799
- cacheResults(errorHash, results, expirationMs = 3600000) {
897
+ /**
898
+ * Cache query results
899
+ */
900
+ cacheResults(errorHash, results, expirationMs = 36e5) {
800
901
  const id = uuidv4();
801
902
  const expiresAt = new Date(Date.now() + expirationMs).toISOString();
802
903
  const stmt = db.prepare(`
@@ -806,13 +907,22 @@ function createLocalStore(projectDirectory) {
806
907
  stmt.run(id, errorHash, JSON.stringify(results), expiresAt);
807
908
  incrementStat("queries_made");
808
909
  },
910
+ /**
911
+ * Clear expired cache entries
912
+ */
809
913
  clearExpiredCache() {
810
914
  const stmt = db.prepare("DELETE FROM query_cache WHERE expires_at <= datetime('now')");
811
915
  const result = stmt.run();
812
916
  return result.changes;
813
917
  },
918
+ // ============ Statistics ============
919
+ /**
920
+ * Get usage statistics
921
+ */
814
922
  getStats() {
815
- const stmt = db.prepare("SELECT total_errors, resolved_errors, uploaded_errors FROM usage_stats WHERE id = 1");
923
+ const stmt = db.prepare(
924
+ "SELECT total_errors, resolved_errors, uploaded_errors FROM usage_stats WHERE id = 1"
925
+ );
816
926
  const row = stmt.get();
817
927
  return {
818
928
  totalErrors: row.total_errors,
@@ -820,20 +930,34 @@ function createLocalStore(projectDirectory) {
820
930
  uploadedErrors: row.uploaded_errors
821
931
  };
822
932
  },
933
+ // ============ Preferences ============
934
+ /**
935
+ * Get preference value
936
+ */
823
937
  getPreference(key) {
824
938
  const stmt = db.prepare("SELECT value FROM user_preferences WHERE key = ?");
825
939
  const row = stmt.get(key);
826
940
  return row?.value || null;
827
941
  },
942
+ /**
943
+ * Set preference value
944
+ */
828
945
  setPreference(key, value) {
829
946
  const stmt = db.prepare(`
830
947
  INSERT OR REPLACE INTO user_preferences (key, value) VALUES (?, ?)
831
948
  `);
832
949
  stmt.run(key, value);
833
950
  },
951
+ // ============ Utilities ============
952
+ /**
953
+ * Close database connection
954
+ */
834
955
  close() {
835
956
  db.close();
836
957
  },
958
+ /**
959
+ * Get database for advanced queries
960
+ */
837
961
  getDatabase() {
838
962
  return db;
839
963
  }
@@ -843,14 +967,14 @@ var LocalStore = {
843
967
  create: createLocalStore
844
968
  };
845
969
 
846
- // src/cloud/client.ts
847
- import * as supabaseJs from "@supabase/supabase-js";
848
-
849
970
  // src/cloud/embedding.ts
850
- import * as openaiModule from "openai";
851
971
  var DEFAULT_MODEL = "text-embedding-3-small";
852
972
  var DEFAULT_DIMENSIONS = 1536;
853
- var MAX_INPUT_LENGTH = 30000;
973
+ var MAX_INPUT_LENGTH = 3e4;
974
+ async function getOpenAIClass() {
975
+ const mod = await import("openai");
976
+ return mod.OpenAI || mod.default;
977
+ }
854
978
  function cosineSimilarity(a, b) {
855
979
  if (a.length !== b.length) {
856
980
  throw new Error("Embeddings must have same dimensions");
@@ -858,19 +982,18 @@ function cosineSimilarity(a, b) {
858
982
  let dotProduct = 0;
859
983
  let normA = 0;
860
984
  let normB = 0;
861
- for (let i = 0;i < a.length; i++) {
985
+ for (let i = 0; i < a.length; i++) {
862
986
  dotProduct += a[i] * b[i];
863
987
  normA += a[i] * a[i];
864
988
  normB += b[i] * b[i];
865
989
  }
866
990
  const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
867
- if (magnitude === 0)
868
- return 0;
991
+ if (magnitude === 0) return 0;
869
992
  return dotProduct / magnitude;
870
993
  }
871
- function createEmbeddingService(config) {
872
- const OpenAI2 = openaiModule.OpenAI || openaiModule.default;
873
- const client = new OpenAI2({ apiKey: config.apiKey });
994
+ async function createEmbeddingService(config) {
995
+ const OpenAI = await getOpenAIClass();
996
+ const client = new OpenAI({ apiKey: config.apiKey });
874
997
  const model = config.model || DEFAULT_MODEL;
875
998
  const dimensions = config.dimensions || DEFAULT_DIMENSIONS;
876
999
  function truncateText(text) {
@@ -878,14 +1001,16 @@ function createEmbeddingService(config) {
878
1001
  return text;
879
1002
  }
880
1003
  const truncated = text.substring(0, MAX_INPUT_LENGTH);
881
- const lastNewline = truncated.lastIndexOf(`
882
- `);
1004
+ const lastNewline = truncated.lastIndexOf("\n");
883
1005
  if (lastNewline > MAX_INPUT_LENGTH * 0.8) {
884
1006
  return truncated.substring(0, lastNewline);
885
1007
  }
886
1008
  return truncated;
887
1009
  }
888
1010
  return {
1011
+ /**
1012
+ * Generate embedding for a single text
1013
+ */
889
1014
  async generate(text) {
890
1015
  const truncated = truncateText(text);
891
1016
  const response = await client.embeddings.create({
@@ -895,6 +1020,9 @@ function createEmbeddingService(config) {
895
1020
  });
896
1021
  return response.data[0].embedding;
897
1022
  },
1023
+ /**
1024
+ * Generate embeddings for multiple texts
1025
+ */
898
1026
  async generateBatch(texts) {
899
1027
  const truncated = texts.map((t) => truncateText(t));
900
1028
  const response = await client.embeddings.create({
@@ -904,6 +1032,10 @@ function createEmbeddingService(config) {
904
1032
  });
905
1033
  return response.data.map((d) => d.embedding);
906
1034
  },
1035
+ /**
1036
+ * Generate embedding for error context
1037
+ * Combines error message, stack trace, and context
1038
+ */
907
1039
  async generateErrorEmbedding(errorMessage, errorStack, context) {
908
1040
  const parts = [];
909
1041
  if (context?.language) {
@@ -917,13 +1049,24 @@ function createEmbeddingService(config) {
917
1049
  parts.push(`Stack Trace:
918
1050
  ${errorStack}`);
919
1051
  }
920
- const text = parts.join(`
921
- `);
922
- return this.generate(text);
1052
+ const text = parts.join("\n");
1053
+ const truncated = truncateText(text);
1054
+ const response = await client.embeddings.create({
1055
+ model,
1056
+ input: truncated,
1057
+ dimensions
1058
+ });
1059
+ return response.data[0].embedding;
923
1060
  },
1061
+ /**
1062
+ * Get embedding dimensions
1063
+ */
924
1064
  getDimensions() {
925
1065
  return dimensions;
926
1066
  },
1067
+ /**
1068
+ * Get model name
1069
+ */
927
1070
  getModel() {
928
1071
  return model;
929
1072
  }
@@ -935,19 +1078,23 @@ var EmbeddingService = {
935
1078
  };
936
1079
 
937
1080
  // src/cloud/client.ts
1081
+ async function getSupabaseCreateClient() {
1082
+ const mod = await import("@supabase/supabase-js");
1083
+ return mod.createClient || mod.default?.createClient;
1084
+ }
938
1085
  function mapToKnowledgeEntry(row) {
939
1086
  return {
940
1087
  id: row.id,
941
1088
  errorHash: row.error_hash,
942
1089
  errorType: row.error_type,
943
1090
  errorMessage: row.error_message,
944
- errorStack: row.error_stack || undefined,
1091
+ errorStack: row.error_stack || void 0,
945
1092
  language: row.language,
946
- framework: row.framework || undefined,
947
- dependencies: row.dependencies || undefined,
1093
+ framework: row.framework || void 0,
1094
+ dependencies: row.dependencies || void 0,
948
1095
  resolutionDescription: row.resolution_description,
949
- resolutionCode: row.resolution_code || undefined,
950
- resolutionSteps: row.resolution_steps || undefined,
1096
+ resolutionCode: row.resolution_code || void 0,
1097
+ resolutionSteps: row.resolution_steps || void 0,
951
1098
  contributorId: row.contributor_id,
952
1099
  upvotes: row.upvotes || 0,
953
1100
  downvotes: row.downvotes || 0,
@@ -955,15 +1102,16 @@ function mapToKnowledgeEntry(row) {
955
1102
  createdAt: row.created_at,
956
1103
  updatedAt: row.updated_at,
957
1104
  isVerified: row.is_verified || false,
958
- similarity: row.similarity || undefined
1105
+ similarity: row.similarity || void 0
959
1106
  };
960
1107
  }
961
1108
  async function createCloudClient(config) {
962
- const supabase = supabaseJs.createClient(config.supabaseUrl, config.supabaseAnonKey);
1109
+ const createClient = await getSupabaseCreateClient();
1110
+ const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
963
1111
  let embedding = null;
964
1112
  if (config.openaiApiKey) {
965
1113
  try {
966
- embedding = createEmbeddingService({ apiKey: config.openaiApiKey });
1114
+ embedding = await createEmbeddingService({ apiKey: config.openaiApiKey });
967
1115
  } catch (err) {
968
1116
  console.warn("[FixHive] Failed to initialize embedding service:", err);
969
1117
  }
@@ -1167,6 +1315,9 @@ var CloudClient = {
1167
1315
  import { tool } from "@opencode-ai/plugin";
1168
1316
  function createTools(localStore, cloudClient, privacyFilter, context) {
1169
1317
  return {
1318
+ /**
1319
+ * Search cloud knowledge base for error solutions
1320
+ */
1170
1321
  fixhive_search: tool({
1171
1322
  description: "Search FixHive knowledge base for error solutions. Use when encountering errors to find community solutions.",
1172
1323
  args: {
@@ -1195,6 +1346,9 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
1195
1346
  return formatSearchResults(results.results, false);
1196
1347
  }
1197
1348
  }),
1349
+ /**
1350
+ * Mark error as resolved and optionally upload solution
1351
+ */
1198
1352
  fixhive_resolve: tool({
1199
1353
  description: "Mark an error as resolved and optionally share the solution with the community.",
1200
1354
  args: {
@@ -1230,6 +1384,9 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
1230
1384
  return "Error marked as resolved locally.";
1231
1385
  }
1232
1386
  }),
1387
+ /**
1388
+ * List errors in current session
1389
+ */
1233
1390
  fixhive_list: tool({
1234
1391
  description: "List errors detected in the current session.",
1235
1392
  args: {
@@ -1248,6 +1405,9 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
1248
1405
  return formatErrorList(errors);
1249
1406
  }
1250
1407
  }),
1408
+ /**
1409
+ * Vote on a solution
1410
+ */
1251
1411
  fixhive_vote: tool({
1252
1412
  description: "Upvote or downvote a FixHive solution based on whether it helped.",
1253
1413
  args: {
@@ -1265,6 +1425,9 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
1265
1425
  return args.helpful ? "Thanks for the feedback! Solution upvoted." : "Thanks for the feedback! Solution downvoted.";
1266
1426
  }
1267
1427
  }),
1428
+ /**
1429
+ * Report inappropriate content
1430
+ */
1268
1431
  fixhive_report: tool({
1269
1432
  description: "Report a FixHive solution for inappropriate content, spam, or incorrect information.",
1270
1433
  args: {
@@ -1279,6 +1442,9 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
1279
1442
  return "Report submitted. Thank you for helping keep FixHive clean!";
1280
1443
  }
1281
1444
  }),
1445
+ /**
1446
+ * Get usage statistics
1447
+ */
1282
1448
  fixhive_stats: tool({
1283
1449
  description: "Get FixHive usage statistics.",
1284
1450
  args: {},
@@ -1300,6 +1466,9 @@ function createTools(localStore, cloudClient, privacyFilter, context) {
1300
1466
  `;
1301
1467
  }
1302
1468
  }),
1469
+ /**
1470
+ * Report that a solution was helpful
1471
+ */
1303
1472
  fixhive_helpful: tool({
1304
1473
  description: "Report that a FixHive solution was helpful and resolved your issue.",
1305
1474
  args: {
@@ -1334,8 +1503,7 @@ ${r.resolutionCode}
1334
1503
  if (r.resolutionSteps?.length) {
1335
1504
  entry += `
1336
1505
  **Steps:**
1337
- ${r.resolutionSteps.map((s, j) => `${j + 1}. ${s}`).join(`
1338
- `)}
1506
+ ${r.resolutionSteps.map((s, j) => `${j + 1}. ${s}`).join("\n")}
1339
1507
  `;
1340
1508
  }
1341
1509
  entry += `
@@ -1343,8 +1511,7 @@ ${r.resolutionSteps.map((s, j) => `${j + 1}. ${s}`).join(`
1343
1511
 
1344
1512
  ---`;
1345
1513
  return entry;
1346
- }).join(`
1347
- `);
1514
+ }).join("\n");
1348
1515
  return `${header}
1349
1516
  ${entries}
1350
1517
 
@@ -1355,8 +1522,9 @@ function formatErrorList(errors) {
1355
1522
  const table = `
1356
1523
  | ID | Type | Status | Message |
1357
1524
  |----|------|--------|---------|
1358
- ${errors.map((e) => `| ${e.id.slice(0, 8)} | ${e.errorType} | ${e.status} | ${e.errorMessage.slice(0, 50)}... |`).join(`
1359
- `)}
1525
+ ${errors.map(
1526
+ (e) => `| ${e.id.slice(0, 8)} | ${e.errorType} | ${e.status} | ${e.errorMessage.slice(0, 50)}... |`
1527
+ ).join("\n")}
1360
1528
  `;
1361
1529
  return `${header}
1362
1530
  ${table}
@@ -1366,7 +1534,8 @@ Use \`fixhive_resolve <id>\` to mark as resolved and share solutions.`;
1366
1534
 
1367
1535
  // src/plugin/index.ts
1368
1536
  var DEFAULT_CONFIG = {
1369
- cacheExpirationMs: 3600000,
1537
+ cacheExpirationMs: 36e5,
1538
+ // 1 hour
1370
1539
  embeddingModel: "text-embedding-3-small",
1371
1540
  embeddingDimensions: 1536,
1372
1541
  similarityThreshold: 0.7,
@@ -1408,9 +1577,9 @@ var FixHivePlugin = async (ctx) => {
1408
1577
  console.log("[FixHive] Ready - use fixhive_stats to verify");
1409
1578
  const errorProducingTools = ["bash", "edit", "write", "read", "terminal"];
1410
1579
  return {
1580
+ // ============ Tool Execution Hook ============
1411
1581
  "tool.execute.after": async (input, output) => {
1412
- if (!errorProducingTools.includes(input.tool))
1413
- return;
1582
+ if (!errorProducingTools.includes(input.tool)) return;
1414
1583
  const detection = errorDetector.detect({
1415
1584
  tool: input.tool,
1416
1585
  output: output.output,
@@ -1420,7 +1589,7 @@ var FixHivePlugin = async (ctx) => {
1420
1589
  });
1421
1590
  if (detection.detected && detection.confidence >= 0.5) {
1422
1591
  const sanitizedErrorMessage = privacyFilter.sanitize(detection.errorMessage, filterContext).sanitized;
1423
- const sanitizedErrorStack = detection.errorStack ? privacyFilter.sanitize(detection.errorStack, filterContext).sanitized : undefined;
1592
+ const sanitizedErrorStack = detection.errorStack ? privacyFilter.sanitize(detection.errorStack, filterContext).sanitized : void 0;
1424
1593
  localStore.createErrorRecord({
1425
1594
  errorType: detection.errorType,
1426
1595
  errorMessage: sanitizedErrorMessage,
@@ -1429,6 +1598,7 @@ var FixHivePlugin = async (ctx) => {
1429
1598
  framework: pluginContext.framework,
1430
1599
  toolName: input.tool,
1431
1600
  toolInput: {},
1601
+ // Tool input is intentionally omitted to avoid storing sensitive data
1432
1602
  sessionId: pluginContext.sessionId || input.sessionID
1433
1603
  });
1434
1604
  if (cloudClient) {
@@ -1441,7 +1611,10 @@ var FixHivePlugin = async (ctx) => {
1441
1611
  limit: 3
1442
1612
  });
1443
1613
  if (solutions.results.length > 0) {
1444
- localStore.cacheResults(generateErrorFingerprint(sanitizedErrorMessage, sanitizedErrorStack), solutions.results);
1614
+ localStore.cacheResults(
1615
+ generateErrorFingerprint(sanitizedErrorMessage, sanitizedErrorStack),
1616
+ solutions.results
1617
+ );
1445
1618
  output.title = `${output.title} [FixHive: ${solutions.results.length} solution(s) found]`;
1446
1619
  }
1447
1620
  } catch (e) {
@@ -1451,22 +1624,24 @@ var FixHivePlugin = async (ctx) => {
1451
1624
  }
1452
1625
  }
1453
1626
  },
1627
+ // ============ Session Compaction Hook ============
1454
1628
  "experimental.session.compacting": async (_input, output) => {
1455
1629
  const unresolvedErrors = localStore.getUnresolvedErrors(pluginContext.sessionId);
1456
1630
  if (unresolvedErrors.length > 0) {
1457
1631
  output.context.push(`
1458
1632
  ## FixHive: Unresolved Errors in Session
1459
1633
 
1460
- ${unresolvedErrors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 100)}...`).join(`
1461
- `)}
1634
+ ${unresolvedErrors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 100)}...`).join("\n")}
1462
1635
 
1463
1636
  Use \`fixhive_mark_resolved\` when errors are fixed to contribute solutions.
1464
1637
  `);
1465
1638
  }
1466
1639
  },
1640
+ // ============ Chat Message Hook ============
1467
1641
  "chat.message": async (input, _output) => {
1468
1642
  pluginContext.sessionId = input.sessionID;
1469
1643
  },
1644
+ // ============ Custom Tools ============
1470
1645
  tool: cloudClient ? createTools(localStore, cloudClient, privacyFilter, pluginContext) : createOfflineTools(localStore, privacyFilter, pluginContext)
1471
1646
  };
1472
1647
  };
@@ -1489,8 +1664,7 @@ function createOfflineTools(localStore, _privacyFilter, context) {
1489
1664
  }
1490
1665
  return `## Session Errors (${errors.length})
1491
1666
 
1492
- ${errors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 80)}...`).join(`
1493
- `)}
1667
+ ${errors.map((e) => `- [${e.id.slice(0, 8)}] ${e.errorType}: ${e.errorMessage.slice(0, 80)}...`).join("\n")}
1494
1668
 
1495
1669
  *Cloud features disabled. Set FIXHIVE_SUPABASE_URL and FIXHIVE_SUPABASE_KEY to enable.*`;
1496
1670
  }
@@ -1549,7 +1723,7 @@ function detectLanguage(directory) {
1549
1723
  return lang;
1550
1724
  }
1551
1725
  }
1552
- return;
1726
+ return void 0;
1553
1727
  }
1554
1728
  function detectFramework(directory) {
1555
1729
  const pkgPath = join(directory, "package.json");
@@ -1557,61 +1731,54 @@ function detectFramework(directory) {
1557
1731
  try {
1558
1732
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1559
1733
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1560
- if (deps["next"])
1561
- return "nextjs";
1562
- if (deps["react"])
1563
- return "react";
1564
- if (deps["vue"])
1565
- return "vue";
1566
- if (deps["@angular/core"])
1567
- return "angular";
1568
- if (deps["express"])
1569
- return "express";
1570
- if (deps["fastify"])
1571
- return "fastify";
1572
- if (deps["hono"])
1573
- return "hono";
1574
- } catch {}
1734
+ if (deps["next"]) return "nextjs";
1735
+ if (deps["react"]) return "react";
1736
+ if (deps["vue"]) return "vue";
1737
+ if (deps["@angular/core"]) return "angular";
1738
+ if (deps["express"]) return "express";
1739
+ if (deps["fastify"]) return "fastify";
1740
+ if (deps["hono"]) return "hono";
1741
+ } catch {
1742
+ }
1575
1743
  }
1576
1744
  const reqPath = join(directory, "requirements.txt");
1577
1745
  if (existsSync2(reqPath)) {
1578
1746
  try {
1579
1747
  const content = readFileSync(reqPath, "utf-8");
1580
- if (content.includes("django"))
1581
- return "django";
1582
- if (content.includes("flask"))
1583
- return "flask";
1584
- if (content.includes("fastapi"))
1585
- return "fastapi";
1586
- } catch {}
1748
+ if (content.includes("django")) return "django";
1749
+ if (content.includes("flask")) return "flask";
1750
+ if (content.includes("fastapi")) return "fastapi";
1751
+ } catch {
1752
+ }
1587
1753
  }
1588
- return;
1754
+ return void 0;
1589
1755
  }
1590
1756
  var plugin_default = FixHivePlugin;
1591
1757
  export {
1592
- shortHash,
1593
- sha256,
1594
- runMigrations,
1595
- normalizeErrorContent,
1596
- generateSessionHash,
1597
- generateErrorFingerprint,
1598
- generateContributorId,
1599
- fingerprintsMatch,
1600
- defaultPrivacyFilter,
1601
- defaultErrorDetector,
1602
- plugin_default as default,
1603
- createPrivacyFilter,
1604
- createLocalStore,
1605
- createFilterContext,
1606
- createErrorDetector,
1607
- createEmbeddingService,
1608
- createCloudClient,
1609
- cosineSimilarity,
1610
- calculateStringSimilarity,
1611
- PrivacyFilter,
1612
- LocalStore,
1613
- FixHivePlugin,
1614
- ErrorDetector,
1758
+ CloudClient,
1615
1759
  EmbeddingService,
1616
- CloudClient
1760
+ ErrorDetector,
1761
+ FixHivePlugin,
1762
+ LocalStore,
1763
+ PrivacyFilter,
1764
+ calculateStringSimilarity,
1765
+ cosineSimilarity,
1766
+ createCloudClient,
1767
+ createEmbeddingService,
1768
+ createErrorDetector,
1769
+ createFilterContext,
1770
+ createLocalStore,
1771
+ createPrivacyFilter,
1772
+ plugin_default as default,
1773
+ defaultErrorDetector,
1774
+ defaultPrivacyFilter,
1775
+ fingerprintsMatch,
1776
+ generateContributorId,
1777
+ generateErrorFingerprint,
1778
+ generateSessionHash,
1779
+ normalizeErrorContent,
1780
+ runMigrations,
1781
+ sha256,
1782
+ shortHash
1617
1783
  };
1784
+ //# sourceMappingURL=index.js.map