@the-magic-tower/fixhive-opencode-plugin 0.1.15 → 0.1.19

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,22 +1,4 @@
1
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
-
20
2
  // src/plugin/index.ts
21
3
  import { tool as tool2 } from "@opencode-ai/plugin";
22
4
  import { existsSync as existsSync2, readFileSync } from "fs";
@@ -175,42 +157,9 @@ var DEFAULT_FILTER_RULES = [
175
157
  priority: 65
176
158
  }
177
159
  ];
178
-
179
- class PrivacyFilter {
180
- rules;
181
- constructor(customRules) {
182
- this.rules = [...DEFAULT_FILTER_RULES, ...customRules || []].sort((a, b) => b.priority - a.priority);
183
- }
184
- sanitize(content, context) {
185
- let result = content;
186
- const appliedFilters = [];
187
- let totalRedacted = 0;
188
- for (const rule of this.rules) {
189
- const before = result;
190
- if (typeof rule.replacement === "function") {
191
- result = result.replace(rule.pattern, rule.replacement);
192
- } else {
193
- result = result.replace(rule.pattern, rule.replacement);
194
- }
195
- if (result !== before) {
196
- const matches = before.match(rule.pattern);
197
- if (matches) {
198
- totalRedacted += matches.length;
199
- appliedFilters.push(rule.name);
200
- }
201
- }
202
- }
203
- if (context) {
204
- result = this.generalizePaths(result, context);
205
- }
206
- return {
207
- original: content,
208
- sanitized: result,
209
- redactedCount: totalRedacted,
210
- appliedFilters: [...new Set(appliedFilters)]
211
- };
212
- }
213
- generalizePaths(content, context) {
160
+ function createPrivacyFilter(customRules) {
161
+ const rules = [...DEFAULT_FILTER_RULES, ...customRules || []].sort((a, b) => b.priority - a.priority);
162
+ function generalizePaths(content, context) {
214
163
  let result = content;
215
164
  if (context.projectRoot) {
216
165
  const escapedRoot = context.projectRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -224,35 +173,69 @@ class PrivacyFilter {
224
173
  result = result.replace(/site-packages\/[\w.-]+\//g, "site-packages/<PKG>/");
225
174
  return result;
226
175
  }
227
- addRule(rule) {
228
- this.rules.push(rule);
229
- this.rules.sort((a, b) => b.priority - a.priority);
230
- }
231
- removeRule(name) {
232
- const index = this.rules.findIndex((r) => r.name === name);
233
- if (index !== -1) {
234
- this.rules.splice(index, 1);
235
- return true;
236
- }
237
- return false;
238
- }
239
- getRules() {
240
- return this.rules;
241
- }
242
- containsSensitiveData(content) {
243
- for (const rule of this.rules) {
244
- if (rule.category === "secret") {
245
- rule.pattern.lastIndex = 0;
246
- const hasSensitiveData = rule.pattern.test(content);
247
- rule.pattern.lastIndex = 0;
248
- if (hasSensitiveData) {
249
- return true;
176
+ return {
177
+ sanitize(content, context) {
178
+ let result = content;
179
+ const appliedFilters = [];
180
+ let totalRedacted = 0;
181
+ for (const rule of rules) {
182
+ const before = result;
183
+ if (typeof rule.replacement === "function") {
184
+ result = result.replace(rule.pattern, rule.replacement);
185
+ } else {
186
+ result = result.replace(rule.pattern, rule.replacement);
187
+ }
188
+ if (result !== before) {
189
+ const matches = before.match(rule.pattern);
190
+ if (matches) {
191
+ totalRedacted += matches.length;
192
+ appliedFilters.push(rule.name);
193
+ }
194
+ }
195
+ }
196
+ if (context) {
197
+ result = generalizePaths(result, context);
198
+ }
199
+ return {
200
+ original: content,
201
+ sanitized: result,
202
+ redactedCount: totalRedacted,
203
+ appliedFilters: [...new Set(appliedFilters)]
204
+ };
205
+ },
206
+ addRule(rule) {
207
+ rules.push(rule);
208
+ rules.sort((a, b) => b.priority - a.priority);
209
+ },
210
+ removeRule(name) {
211
+ const index = rules.findIndex((r) => r.name === name);
212
+ if (index !== -1) {
213
+ rules.splice(index, 1);
214
+ return true;
215
+ }
216
+ return false;
217
+ },
218
+ getRules() {
219
+ return rules;
220
+ },
221
+ containsSensitiveData(content) {
222
+ for (const rule of rules) {
223
+ if (rule.category === "secret") {
224
+ rule.pattern.lastIndex = 0;
225
+ const hasSensitiveData = rule.pattern.test(content);
226
+ rule.pattern.lastIndex = 0;
227
+ if (hasSensitiveData) {
228
+ return true;
229
+ }
250
230
  }
251
231
  }
232
+ return false;
252
233
  }
253
- return false;
254
- }
234
+ };
255
235
  }
236
+ var PrivacyFilter = {
237
+ create: createPrivacyFilter
238
+ };
256
239
  function createFilterContext(projectDirectory) {
257
240
  const homeDir = process.env.HOME || process.env.USERPROFILE || "~";
258
241
  return {
@@ -267,7 +250,7 @@ function createFilterContext(projectDirectory) {
267
250
  ])
268
251
  };
269
252
  }
270
- var defaultPrivacyFilter = new PrivacyFilter;
253
+ var defaultPrivacyFilter = createPrivacyFilter();
271
254
 
272
255
  // src/core/error-detector.ts
273
256
  var ERROR_PATTERNS = {
@@ -345,70 +328,12 @@ var EXIT_CODE_SEVERITY = {
345
328
  139: "critical",
346
329
  143: "warning"
347
330
  };
348
-
349
- class ErrorDetector {
350
- privacyFilter;
351
- constructor(privacyFilter) {
352
- this.privacyFilter = privacyFilter || new PrivacyFilter;
353
- }
354
- detect(toolOutput) {
355
- const signals = [];
356
- const combinedOutput = `${toolOutput.output || ""}
357
- ${toolOutput.stderr || ""}`;
358
- if (toolOutput.exitCode !== undefined && toolOutput.exitCode !== 0) {
359
- const severity2 = EXIT_CODE_SEVERITY[toolOutput.exitCode] || "error";
360
- signals.push({
361
- type: "exit_code",
362
- weight: 0.9,
363
- value: toolOutput.exitCode,
364
- description: `Non-zero exit code: ${toolOutput.exitCode} (${severity2})`
365
- });
366
- }
367
- if (toolOutput.stderr && toolOutput.stderr.trim().length > 0) {
368
- const hasErrorKeywords = this.containsErrorKeywords(toolOutput.stderr);
369
- const stderrWeight = hasErrorKeywords ? 0.85 : 0.4;
370
- signals.push({
371
- type: "stderr",
372
- weight: stderrWeight,
373
- value: toolOutput.stderr.substring(0, 500),
374
- description: hasErrorKeywords ? "Stderr with error keywords" : "Stderr output present"
375
- });
376
- }
377
- const patternMatches = this.detectErrorPatterns(combinedOutput);
378
- signals.push(...patternMatches);
379
- const stackTrace = this.detectStackTrace(combinedOutput);
380
- if (stackTrace.hasStackTrace) {
381
- signals.push({
382
- type: "stack_trace",
383
- weight: 0.95,
384
- value: stackTrace.frames.slice(0, 5).join(`
385
- `),
386
- description: `${stackTrace.language} stack trace detected`
387
- });
388
- }
389
- const confidence = this.calculateConfidence(signals);
390
- const detected = confidence >= 0.5;
391
- const errorType = this.classifyErrorType(signals, combinedOutput);
392
- const severity = this.determineSeverity(signals, toolOutput.exitCode);
393
- const { message, stack } = this.extractErrorDetails(combinedOutput);
394
- const sanitizedMessage = this.privacyFilter.sanitize(message);
395
- const sanitizedStack = stack ? this.privacyFilter.sanitize(stack) : undefined;
396
- const sanitizedOutput = this.privacyFilter.sanitize(combinedOutput.substring(0, 5000));
397
- return {
398
- detected,
399
- confidence,
400
- errorType,
401
- severity,
402
- signals,
403
- errorMessage: sanitizedMessage.sanitized,
404
- errorStack: sanitizedStack?.sanitized,
405
- rawOutput: sanitizedOutput.sanitized
406
- };
407
- }
408
- containsErrorKeywords(content) {
331
+ function createErrorDetector(privacyFilter) {
332
+ const filter = privacyFilter || createPrivacyFilter();
333
+ function containsErrorKeywords(content) {
409
334
  return ERROR_PATTERNS.universal.some((p) => p.test(content));
410
335
  }
411
- detectErrorPatterns(content) {
336
+ function detectErrorPatterns(content) {
412
337
  const signals = [];
413
338
  const weights = {
414
339
  prefixed: 0.85,
@@ -436,7 +361,7 @@ ${toolOutput.stderr || ""}`;
436
361
  }
437
362
  return signals;
438
363
  }
439
- detectStackTrace(content) {
364
+ function detectStackTrace(content) {
440
365
  for (const [language, pattern] of Object.entries(STACK_TRACE_PATTERNS)) {
441
366
  const globalPattern = new RegExp(pattern.source, "gm");
442
367
  const matches = content.match(globalPattern);
@@ -454,7 +379,7 @@ ${toolOutput.stderr || ""}`;
454
379
  frames: []
455
380
  };
456
381
  }
457
- calculateConfidence(signals) {
382
+ function calculateConfidence(signals) {
458
383
  if (signals.length === 0)
459
384
  return 0;
460
385
  const totalWeight = signals.reduce((sum, s) => sum + s.weight, 0);
@@ -462,7 +387,7 @@ ${toolOutput.stderr || ""}`;
462
387
  const multiplier = Math.min(1.2, 1 + signals.length * 0.05);
463
388
  return Math.min(1, avgWeight * multiplier);
464
389
  }
465
- classifyErrorType(signals, content) {
390
+ function classifyErrorType(signals, content) {
466
391
  if (ERROR_PATTERNS.build.some((p) => p.test(content)))
467
392
  return "build";
468
393
  if (ERROR_PATTERNS.package.some((p) => p.test(content)))
@@ -484,7 +409,7 @@ ${toolOutput.stderr || ""}`;
484
409
  return "runtime";
485
410
  return "unknown";
486
411
  }
487
- determineSeverity(signals, exitCode) {
412
+ function determineSeverity(signals, exitCode) {
488
413
  if (exitCode !== undefined && EXIT_CODE_SEVERITY[exitCode]) {
489
414
  return EXIT_CODE_SEVERITY[exitCode];
490
415
  }
@@ -495,14 +420,18 @@ ${toolOutput.stderr || ""}`;
495
420
  return "error";
496
421
  return "warning";
497
422
  }
498
- extractErrorDetails(output) {
423
+ function isErrorLine(line) {
424
+ const trimmed = line.trim();
425
+ 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);
426
+ }
427
+ function extractErrorDetails(output) {
499
428
  const lines = output.split(`
500
429
  `);
501
430
  let message = "";
502
431
  let stack = "";
503
432
  let inStack = false;
504
433
  for (const line of lines) {
505
- if (this.isErrorLine(line) && !message) {
434
+ if (isErrorLine(line) && !message) {
506
435
  message = line.trim();
507
436
  inStack = true;
508
437
  } else if (inStack && (line.match(/^\s+at\s/) || line.match(/^\s+File\s/) || line.match(/^\s+\d+:\s/) || line.match(/^\s+from\s/))) {
@@ -523,12 +452,67 @@ ${toolOutput.stderr || ""}`;
523
452
  stack: stack || undefined
524
453
  };
525
454
  }
526
- isErrorLine(line) {
527
- const trimmed = line.trim();
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);
529
- }
455
+ return {
456
+ detect(toolOutput) {
457
+ const signals = [];
458
+ const combinedOutput = `${toolOutput.output || ""}
459
+ ${toolOutput.stderr || ""}`;
460
+ if (toolOutput.exitCode !== undefined && toolOutput.exitCode !== 0) {
461
+ const severity2 = EXIT_CODE_SEVERITY[toolOutput.exitCode] || "error";
462
+ signals.push({
463
+ type: "exit_code",
464
+ weight: 0.9,
465
+ value: toolOutput.exitCode,
466
+ description: `Non-zero exit code: ${toolOutput.exitCode} (${severity2})`
467
+ });
468
+ }
469
+ if (toolOutput.stderr && toolOutput.stderr.trim().length > 0) {
470
+ const hasErrorKeywords = containsErrorKeywords(toolOutput.stderr);
471
+ const stderrWeight = hasErrorKeywords ? 0.85 : 0.4;
472
+ signals.push({
473
+ type: "stderr",
474
+ weight: stderrWeight,
475
+ value: toolOutput.stderr.substring(0, 500),
476
+ description: hasErrorKeywords ? "Stderr with error keywords" : "Stderr output present"
477
+ });
478
+ }
479
+ const patternMatches = detectErrorPatterns(combinedOutput);
480
+ signals.push(...patternMatches);
481
+ const stackTrace = detectStackTrace(combinedOutput);
482
+ if (stackTrace.hasStackTrace) {
483
+ signals.push({
484
+ type: "stack_trace",
485
+ weight: 0.95,
486
+ value: stackTrace.frames.slice(0, 5).join(`
487
+ `),
488
+ description: `${stackTrace.language} stack trace detected`
489
+ });
490
+ }
491
+ const confidence = calculateConfidence(signals);
492
+ const detected = confidence >= 0.5;
493
+ const errorType = classifyErrorType(signals, combinedOutput);
494
+ const severity = determineSeverity(signals, toolOutput.exitCode);
495
+ const { message, stack } = extractErrorDetails(combinedOutput);
496
+ const sanitizedMessage = filter.sanitize(message);
497
+ const sanitizedStack = stack ? filter.sanitize(stack) : undefined;
498
+ const sanitizedOutput = filter.sanitize(combinedOutput.substring(0, 5000));
499
+ return {
500
+ detected,
501
+ confidence,
502
+ errorType,
503
+ severity,
504
+ signals,
505
+ errorMessage: sanitizedMessage.sanitized,
506
+ errorStack: sanitizedStack?.sanitized,
507
+ rawOutput: sanitizedOutput.sanitized
508
+ };
509
+ }
510
+ };
530
511
  }
531
- var defaultErrorDetector = new ErrorDetector;
512
+ var ErrorDetector = {
513
+ create: createErrorDetector
514
+ };
515
+ var defaultErrorDetector = createErrorDetector();
532
516
 
533
517
  // src/storage/local-store.ts
534
518
  import Database from "better-sqlite3";
@@ -669,246 +653,227 @@ var MIGRATIONS = [
669
653
  ];
670
654
 
671
655
  // src/storage/local-store.ts
672
- class LocalStore {
673
- db;
674
- constructor(projectDirectory) {
675
- const dbPath = `${projectDirectory}/.fixhive/fixhive.db`;
676
- const dir = dirname(dbPath);
677
- if (!existsSync(dir)) {
678
- mkdirSync(dir, { recursive: true });
679
- }
680
- this.db = new Database(dbPath);
681
- this.db.pragma("journal_mode = WAL");
682
- this.db.pragma("foreign_keys = ON");
683
- runMigrations(this.db);
684
- }
685
- createErrorRecord(data) {
686
- const id = uuidv4();
687
- const errorHash = generateErrorFingerprint(data.errorMessage, data.errorStack);
688
- const stmt = this.db.prepare(`
689
- INSERT INTO error_records (
690
- id, error_hash, error_type, error_message, error_stack,
691
- language, framework, tool_name, tool_input, session_id, status
692
- ) VALUES (
693
- @id, @errorHash, @errorType, @errorMessage, @errorStack,
694
- @language, @framework, @toolName, @toolInput, @sessionId, 'unresolved'
695
- )
696
- `);
697
- stmt.run({
698
- id,
699
- errorHash,
700
- errorType: data.errorType,
701
- errorMessage: data.errorMessage,
702
- errorStack: data.errorStack || null,
703
- language: data.language || null,
704
- framework: data.framework || null,
705
- toolName: data.toolName,
706
- toolInput: JSON.stringify(data.toolInput),
707
- sessionId: data.sessionId
708
- });
709
- this.incrementStat("total_errors");
710
- return this.getErrorById(id);
711
- }
712
- getErrorById(id) {
713
- const stmt = this.db.prepare("SELECT * FROM error_records WHERE id = ?");
714
- const row = stmt.get(id);
715
- return row ? this.rowToRecord(row) : null;
656
+ var ALLOWED_STATS = [
657
+ "total_errors",
658
+ "resolved_errors",
659
+ "uploaded_errors",
660
+ "queries_made"
661
+ ];
662
+ function rowToRecord(row) {
663
+ return {
664
+ id: row.id,
665
+ errorHash: row.error_hash,
666
+ errorType: row.error_type,
667
+ errorMessage: row.error_message,
668
+ errorStack: row.error_stack || undefined,
669
+ language: row.language || undefined,
670
+ framework: row.framework || undefined,
671
+ toolName: row.tool_name,
672
+ toolInput: JSON.parse(row.tool_input || "{}"),
673
+ sessionId: row.session_id,
674
+ status: row.status,
675
+ resolution: row.resolution || undefined,
676
+ resolutionCode: row.resolution_code || undefined,
677
+ createdAt: row.created_at,
678
+ resolvedAt: row.resolved_at || undefined,
679
+ uploadedAt: row.uploaded_at || undefined,
680
+ cloudKnowledgeId: row.cloud_knowledge_id || undefined
681
+ };
682
+ }
683
+ function createLocalStore(projectDirectory) {
684
+ const dbPath = `${projectDirectory}/.fixhive/fixhive.db`;
685
+ const dir = dirname(dbPath);
686
+ if (!existsSync(dir)) {
687
+ mkdirSync(dir, { recursive: true });
716
688
  }
717
- getSessionErrors(sessionId, options) {
718
- let query = "SELECT * FROM error_records WHERE session_id = ?";
719
- const params = [sessionId];
720
- if (options?.status) {
721
- query += " AND status = ?";
722
- params.push(options.status);
723
- }
724
- query += " ORDER BY created_at DESC";
725
- if (options?.limit) {
726
- query += " LIMIT ?";
727
- params.push(options.limit);
689
+ const db = new Database(dbPath);
690
+ db.pragma("journal_mode = WAL");
691
+ db.pragma("foreign_keys = ON");
692
+ runMigrations(db);
693
+ function incrementStat(stat) {
694
+ if (!ALLOWED_STATS.includes(stat)) {
695
+ throw new Error(`Invalid stat name: ${stat}. Allowed: ${ALLOWED_STATS.join(", ")}`);
728
696
  }
729
- const stmt = this.db.prepare(query);
730
- return stmt.all(...params).map((row) => this.rowToRecord(row));
731
- }
732
- getUnresolvedErrors(sessionId) {
733
- return this.getSessionErrors(sessionId, { status: "unresolved" });
734
- }
735
- getRecentErrors(limit = 10) {
736
- const stmt = this.db.prepare("SELECT * FROM error_records ORDER BY created_at DESC LIMIT ?");
737
- return stmt.all(limit).map((row) => this.rowToRecord(row));
697
+ const stmt = db.prepare(`UPDATE usage_stats SET ${stat} = ${stat} + 1 WHERE id = 1`);
698
+ stmt.run();
738
699
  }
739
- markResolved(id, data) {
740
- const stmt = this.db.prepare(`
741
- UPDATE error_records
742
- SET status = 'resolved',
743
- resolution = ?,
744
- resolution_code = ?,
745
- resolved_at = datetime('now')
746
- WHERE id = ?
747
- `);
748
- const result = stmt.run(data.resolution, data.resolutionCode || null, id);
749
- if (result.changes > 0) {
750
- this.incrementStat("resolved_errors");
700
+ return {
701
+ createErrorRecord(data) {
702
+ const id = uuidv4();
703
+ const errorHash = generateErrorFingerprint(data.errorMessage, data.errorStack);
704
+ const stmt = db.prepare(`
705
+ INSERT INTO error_records (
706
+ id, error_hash, error_type, error_message, error_stack,
707
+ language, framework, tool_name, tool_input, session_id, status
708
+ ) VALUES (
709
+ @id, @errorHash, @errorType, @errorMessage, @errorStack,
710
+ @language, @framework, @toolName, @toolInput, @sessionId, 'unresolved'
711
+ )
712
+ `);
713
+ stmt.run({
714
+ id,
715
+ errorHash,
716
+ errorType: data.errorType,
717
+ errorMessage: data.errorMessage,
718
+ errorStack: data.errorStack || null,
719
+ language: data.language || null,
720
+ framework: data.framework || null,
721
+ toolName: data.toolName,
722
+ toolInput: JSON.stringify(data.toolInput),
723
+ sessionId: data.sessionId
724
+ });
725
+ incrementStat("total_errors");
751
726
  return this.getErrorById(id);
727
+ },
728
+ getErrorById(id) {
729
+ const stmt = db.prepare("SELECT * FROM error_records WHERE id = ?");
730
+ const row = stmt.get(id);
731
+ return row ? rowToRecord(row) : null;
732
+ },
733
+ getSessionErrors(sessionId, options) {
734
+ let query = "SELECT * FROM error_records WHERE session_id = ?";
735
+ const params = [sessionId];
736
+ if (options?.status) {
737
+ query += " AND status = ?";
738
+ params.push(options.status);
739
+ }
740
+ query += " ORDER BY created_at DESC";
741
+ if (options?.limit) {
742
+ query += " LIMIT ?";
743
+ params.push(options.limit);
744
+ }
745
+ const stmt = db.prepare(query);
746
+ return stmt.all(...params).map((row) => rowToRecord(row));
747
+ },
748
+ getUnresolvedErrors(sessionId) {
749
+ return this.getSessionErrors(sessionId, { status: "unresolved" });
750
+ },
751
+ getRecentErrors(limit = 10) {
752
+ const stmt = db.prepare("SELECT * FROM error_records ORDER BY created_at DESC LIMIT ?");
753
+ return stmt.all(limit).map((row) => rowToRecord(row));
754
+ },
755
+ markResolved(id, data) {
756
+ const stmt = db.prepare(`
757
+ UPDATE error_records
758
+ SET status = 'resolved',
759
+ resolution = ?,
760
+ resolution_code = ?,
761
+ resolved_at = datetime('now')
762
+ WHERE id = ?
763
+ `);
764
+ const result = stmt.run(data.resolution, data.resolutionCode || null, id);
765
+ if (result.changes > 0) {
766
+ incrementStat("resolved_errors");
767
+ return this.getErrorById(id);
768
+ }
769
+ return null;
770
+ },
771
+ markUploaded(id, cloudKnowledgeId) {
772
+ const stmt = db.prepare(`
773
+ UPDATE error_records
774
+ SET status = 'uploaded',
775
+ cloud_knowledge_id = ?,
776
+ uploaded_at = datetime('now')
777
+ WHERE id = ?
778
+ `);
779
+ const result = stmt.run(cloudKnowledgeId, id);
780
+ if (result.changes > 0) {
781
+ incrementStat("uploaded_errors");
782
+ }
783
+ },
784
+ findSimilarErrors(errorHash) {
785
+ const stmt = db.prepare("SELECT * FROM error_records WHERE error_hash = ? ORDER BY created_at DESC");
786
+ return stmt.all(errorHash).map((row) => rowToRecord(row));
787
+ },
788
+ getCachedResults(errorHash) {
789
+ const stmt = db.prepare(`
790
+ SELECT results FROM query_cache
791
+ WHERE error_hash = ? AND expires_at > datetime('now')
792
+ ORDER BY created_at DESC LIMIT 1
793
+ `);
794
+ const row = stmt.get(errorHash);
795
+ if (row) {
796
+ return JSON.parse(row.results);
797
+ }
798
+ return null;
799
+ },
800
+ cacheResults(errorHash, results, expirationMs = 3600000) {
801
+ const id = uuidv4();
802
+ const expiresAt = new Date(Date.now() + expirationMs).toISOString();
803
+ const stmt = db.prepare(`
804
+ INSERT INTO query_cache (id, error_hash, results, expires_at)
805
+ VALUES (?, ?, ?, ?)
806
+ `);
807
+ stmt.run(id, errorHash, JSON.stringify(results), expiresAt);
808
+ incrementStat("queries_made");
809
+ },
810
+ clearExpiredCache() {
811
+ const stmt = db.prepare("DELETE FROM query_cache WHERE expires_at <= datetime('now')");
812
+ const result = stmt.run();
813
+ return result.changes;
814
+ },
815
+ getStats() {
816
+ const stmt = db.prepare("SELECT total_errors, resolved_errors, uploaded_errors FROM usage_stats WHERE id = 1");
817
+ const row = stmt.get();
818
+ return {
819
+ totalErrors: row.total_errors,
820
+ resolvedErrors: row.resolved_errors,
821
+ uploadedErrors: row.uploaded_errors
822
+ };
823
+ },
824
+ getPreference(key) {
825
+ const stmt = db.prepare("SELECT value FROM user_preferences WHERE key = ?");
826
+ const row = stmt.get(key);
827
+ return row?.value || null;
828
+ },
829
+ setPreference(key, value) {
830
+ const stmt = db.prepare(`
831
+ INSERT OR REPLACE INTO user_preferences (key, value) VALUES (?, ?)
832
+ `);
833
+ stmt.run(key, value);
834
+ },
835
+ close() {
836
+ db.close();
837
+ },
838
+ getDatabase() {
839
+ return db;
752
840
  }
753
- return null;
754
- }
755
- markUploaded(id, cloudKnowledgeId) {
756
- const stmt = this.db.prepare(`
757
- UPDATE error_records
758
- SET status = 'uploaded',
759
- cloud_knowledge_id = ?,
760
- uploaded_at = datetime('now')
761
- WHERE id = ?
762
- `);
763
- const result = stmt.run(cloudKnowledgeId, id);
764
- if (result.changes > 0) {
765
- this.incrementStat("uploaded_errors");
766
- }
767
- }
768
- findSimilarErrors(errorHash) {
769
- const stmt = this.db.prepare("SELECT * FROM error_records WHERE error_hash = ? ORDER BY created_at DESC");
770
- return stmt.all(errorHash).map((row) => this.rowToRecord(row));
771
- }
772
- getCachedResults(errorHash) {
773
- const stmt = this.db.prepare(`
774
- SELECT results FROM query_cache
775
- WHERE error_hash = ? AND expires_at > datetime('now')
776
- ORDER BY created_at DESC LIMIT 1
777
- `);
778
- const row = stmt.get(errorHash);
779
- if (row) {
780
- return JSON.parse(row.results);
781
- }
782
- return null;
783
- }
784
- cacheResults(errorHash, results, expirationMs = 3600000) {
785
- const id = uuidv4();
786
- const expiresAt = new Date(Date.now() + expirationMs).toISOString();
787
- const stmt = this.db.prepare(`
788
- INSERT INTO query_cache (id, error_hash, results, expires_at)
789
- VALUES (?, ?, ?, ?)
790
- `);
791
- stmt.run(id, errorHash, JSON.stringify(results), expiresAt);
792
- this.incrementStat("queries_made");
793
- }
794
- clearExpiredCache() {
795
- const stmt = this.db.prepare("DELETE FROM query_cache WHERE expires_at <= datetime('now')");
796
- const result = stmt.run();
797
- return result.changes;
798
- }
799
- getStats() {
800
- const stmt = this.db.prepare("SELECT total_errors, resolved_errors, uploaded_errors FROM usage_stats WHERE id = 1");
801
- const row = stmt.get();
802
- return {
803
- totalErrors: row.total_errors,
804
- resolvedErrors: row.resolved_errors,
805
- uploadedErrors: row.uploaded_errors
806
- };
807
- }
808
- static ALLOWED_STATS = [
809
- "total_errors",
810
- "resolved_errors",
811
- "uploaded_errors",
812
- "queries_made"
813
- ];
814
- incrementStat(stat) {
815
- if (!LocalStore.ALLOWED_STATS.includes(stat)) {
816
- throw new Error(`Invalid stat name: ${stat}. Allowed: ${LocalStore.ALLOWED_STATS.join(", ")}`);
817
- }
818
- const stmt = this.db.prepare(`UPDATE usage_stats SET ${stat} = ${stat} + 1 WHERE id = 1`);
819
- stmt.run();
820
- }
821
- getPreference(key) {
822
- const stmt = this.db.prepare("SELECT value FROM user_preferences WHERE key = ?");
823
- const row = stmt.get(key);
824
- return row?.value || null;
825
- }
826
- setPreference(key, value) {
827
- const stmt = this.db.prepare(`
828
- INSERT OR REPLACE INTO user_preferences (key, value) VALUES (?, ?)
829
- `);
830
- stmt.run(key, value);
831
- }
832
- rowToRecord(row) {
833
- return {
834
- id: row.id,
835
- errorHash: row.error_hash,
836
- errorType: row.error_type,
837
- errorMessage: row.error_message,
838
- errorStack: row.error_stack || undefined,
839
- language: row.language || undefined,
840
- framework: row.framework || undefined,
841
- toolName: row.tool_name,
842
- toolInput: JSON.parse(row.tool_input || "{}"),
843
- sessionId: row.session_id,
844
- status: row.status,
845
- resolution: row.resolution || undefined,
846
- resolutionCode: row.resolution_code || undefined,
847
- createdAt: row.created_at,
848
- resolvedAt: row.resolved_at || undefined,
849
- uploadedAt: row.uploaded_at || undefined,
850
- cloudKnowledgeId: row.cloud_knowledge_id || undefined
851
- };
852
- }
853
- close() {
854
- this.db.close();
855
- }
856
- getDatabase() {
857
- return this.db;
858
- }
841
+ };
859
842
  }
843
+ var LocalStore = {
844
+ create: createLocalStore
845
+ };
846
+
847
+ // src/cloud/client.ts
848
+ import { createClient } from "@supabase/supabase-js";
860
849
 
861
850
  // src/cloud/embedding.ts
862
851
  import OpenAI from "openai";
863
852
  var DEFAULT_MODEL = "text-embedding-3-small";
864
853
  var DEFAULT_DIMENSIONS = 1536;
865
854
  var MAX_INPUT_LENGTH = 30000;
866
-
867
- class EmbeddingService {
868
- client;
869
- model;
870
- dimensions;
871
- constructor(apiKey, model, dimensions) {
872
- this.client = new OpenAI({ apiKey });
873
- this.model = model || DEFAULT_MODEL;
874
- this.dimensions = dimensions || DEFAULT_DIMENSIONS;
875
- }
876
- async generate(text) {
877
- const truncated = this.truncateText(text);
878
- const response = await this.client.embeddings.create({
879
- model: this.model,
880
- input: truncated,
881
- dimensions: this.dimensions
882
- });
883
- return response.data[0].embedding;
884
- }
885
- async generateBatch(texts) {
886
- const truncated = texts.map((t) => this.truncateText(t));
887
- const response = await this.client.embeddings.create({
888
- model: this.model,
889
- input: truncated,
890
- dimensions: this.dimensions
891
- });
892
- return response.data.map((d) => d.embedding);
855
+ function cosineSimilarity(a, b) {
856
+ if (a.length !== b.length) {
857
+ throw new Error("Embeddings must have same dimensions");
893
858
  }
894
- async generateErrorEmbedding(errorMessage, errorStack, context) {
895
- const parts = [];
896
- if (context?.language) {
897
- parts.push(`Language: ${context.language}`);
898
- }
899
- if (context?.framework) {
900
- parts.push(`Framework: ${context.framework}`);
901
- }
902
- parts.push(`Error: ${errorMessage}`);
903
- if (errorStack) {
904
- parts.push(`Stack Trace:
905
- ${errorStack}`);
906
- }
907
- const text = parts.join(`
908
- `);
909
- return this.generate(text);
859
+ let dotProduct = 0;
860
+ let normA = 0;
861
+ let normB = 0;
862
+ for (let i = 0;i < a.length; i++) {
863
+ dotProduct += a[i] * b[i];
864
+ normA += a[i] * a[i];
865
+ normB += b[i] * b[i];
910
866
  }
911
- truncateText(text) {
867
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
868
+ if (magnitude === 0)
869
+ return 0;
870
+ return dotProduct / magnitude;
871
+ }
872
+ function createEmbeddingService(config) {
873
+ const client = new OpenAI({ apiKey: config.apiKey });
874
+ const model = config.model || DEFAULT_MODEL;
875
+ const dimensions = config.dimensions || DEFAULT_DIMENSIONS;
876
+ function truncateText(text) {
912
877
  if (text.length <= MAX_INPUT_LENGTH) {
913
878
  return text;
914
879
  }
@@ -920,43 +885,56 @@ ${errorStack}`);
920
885
  }
921
886
  return truncated;
922
887
  }
923
- static cosineSimilarity(a, b) {
924
- if (a.length !== b.length) {
925
- throw new Error("Embeddings must have same dimensions");
926
- }
927
- let dotProduct = 0;
928
- let normA = 0;
929
- let normB = 0;
930
- for (let i = 0;i < a.length; i++) {
931
- dotProduct += a[i] * b[i];
932
- normA += a[i] * a[i];
933
- normB += b[i] * b[i];
888
+ return {
889
+ async generate(text) {
890
+ const truncated = truncateText(text);
891
+ const response = await client.embeddings.create({
892
+ model,
893
+ input: truncated,
894
+ dimensions
895
+ });
896
+ return response.data[0].embedding;
897
+ },
898
+ async generateBatch(texts) {
899
+ const truncated = texts.map((t) => truncateText(t));
900
+ const response = await client.embeddings.create({
901
+ model,
902
+ input: truncated,
903
+ dimensions
904
+ });
905
+ return response.data.map((d) => d.embedding);
906
+ },
907
+ async generateErrorEmbedding(errorMessage, errorStack, context) {
908
+ const parts = [];
909
+ if (context?.language) {
910
+ parts.push(`Language: ${context.language}`);
911
+ }
912
+ if (context?.framework) {
913
+ parts.push(`Framework: ${context.framework}`);
914
+ }
915
+ parts.push(`Error: ${errorMessage}`);
916
+ if (errorStack) {
917
+ parts.push(`Stack Trace:
918
+ ${errorStack}`);
919
+ }
920
+ const text = parts.join(`
921
+ `);
922
+ return this.generate(text);
923
+ },
924
+ getDimensions() {
925
+ return dimensions;
926
+ },
927
+ getModel() {
928
+ return model;
934
929
  }
935
- const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
936
- if (magnitude === 0)
937
- return 0;
938
- return dotProduct / magnitude;
939
- }
940
- getDimensions() {
941
- return this.dimensions;
942
- }
943
- getModel() {
944
- return this.model;
945
- }
946
- }
947
- function createEmbeddingService(config) {
948
- return new EmbeddingService(config.apiKey, config.model, config.dimensions);
930
+ };
949
931
  }
932
+ var EmbeddingService = {
933
+ create: createEmbeddingService,
934
+ cosineSimilarity
935
+ };
950
936
 
951
937
  // src/cloud/client.ts
952
- var createClient;
953
- async function getCreateClient() {
954
- if (!createClient) {
955
- const supabase = await import("@supabase/supabase-js");
956
- createClient = supabase.createClient;
957
- }
958
- return createClient;
959
- }
960
938
  function mapToKnowledgeEntry(row) {
961
939
  return {
962
940
  id: row.id,
@@ -981,12 +959,11 @@ function mapToKnowledgeEntry(row) {
981
959
  };
982
960
  }
983
961
  async function createCloudClient(config) {
984
- const createClientFn = await getCreateClient();
985
- const supabase = createClientFn(config.supabaseUrl, config.supabaseAnonKey);
962
+ const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
986
963
  let embedding = null;
987
964
  if (config.openaiApiKey) {
988
965
  try {
989
- embedding = new EmbeddingService(config.openaiApiKey);
966
+ embedding = createEmbeddingService({ apiKey: config.openaiApiKey });
990
967
  } catch (err) {
991
968
  console.warn("[FixHive] Failed to initialize embedding service:", err);
992
969
  }
@@ -1400,10 +1377,10 @@ var FixHivePlugin = async (ctx) => {
1400
1377
  console.log("[FixHive] Plugin loaded");
1401
1378
  console.log(`[FixHive] Project: ${ctx.directory}`);
1402
1379
  console.log(`[FixHive] Cloud: ${config.supabaseUrl ? "enabled" : "disabled"}`);
1403
- const privacyFilter = new PrivacyFilter;
1380
+ const privacyFilter = createPrivacyFilter();
1404
1381
  const filterContext = createFilterContext(ctx.directory);
1405
- const errorDetector = new ErrorDetector(privacyFilter);
1406
- const localStore = new LocalStore(ctx.directory);
1382
+ const errorDetector = createErrorDetector(privacyFilter);
1383
+ const localStore = createLocalStore(ctx.directory);
1407
1384
  let cloudClient = null;
1408
1385
  if (config.supabaseUrl && config.supabaseAnonKey) {
1409
1386
  try {
@@ -1623,9 +1600,13 @@ export {
1623
1600
  defaultPrivacyFilter,
1624
1601
  defaultErrorDetector,
1625
1602
  plugin_default as default,
1603
+ createPrivacyFilter,
1604
+ createLocalStore,
1626
1605
  createFilterContext,
1606
+ createErrorDetector,
1627
1607
  createEmbeddingService,
1628
1608
  createCloudClient,
1609
+ cosineSimilarity,
1629
1610
  calculateStringSimilarity,
1630
1611
  PrivacyFilter,
1631
1612
  LocalStore,