amalfa 1.0.31 → 1.0.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalfa",
3
- "version": "1.0.31",
3
+ "version": "1.0.34",
4
4
  "description": "Local-first knowledge graph engine for AI agents. Transforms markdown into searchable memory with MCP protocol.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/pjsvis/amalfa#readme",
@@ -46,7 +46,8 @@
46
46
  "@biomejs/biome": "2.3.8",
47
47
  "@types/bun": "1.3.4",
48
48
  "only-allow": "^1.2.2",
49
- "pino-pretty": "^13.1.3"
49
+ "pino-pretty": "^13.1.3",
50
+ "typescript": "^5.9.3"
50
51
  },
51
52
  "scripts": {
52
53
  "precommit": "bun run scripts/maintenance/pre-commit.ts",
@@ -1,5 +1,5 @@
1
1
  // import { Command } from "commander";
2
- import { createInterface } from "readline";
2
+ import { createInterface } from "node:readline";
3
3
  import { DaemonManager } from "../utils/DaemonManager";
4
4
 
5
5
  export async function chatLoop() {
package/src/cli.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
- import { existsSync, statSync } from "fs";
3
- import { join } from "path";
4
- import { spawn } from "child_process";
2
+ import { spawn } from "node:child_process";
3
+ import { existsSync, statSync } from "node:fs";
4
+ import { join } from "node:path";
5
5
  import pkg from "../package.json" with { type: "json" };
6
6
 
7
7
  const VERSION = pkg.version;
@@ -551,7 +551,7 @@ async function cmdSetupMcp() {
551
551
  mcpServers: {
552
552
  amalfa: {
553
553
  command: "bun",
554
- args: ["run", mcpScript],
554
+ args: ["run", "--cwd", cwd, mcpScript],
555
555
  env: {
556
556
  PATH: minimalPath,
557
557
  },
@@ -3,8 +3,8 @@
3
3
  * Default settings that can be overridden via amalfa.config.{ts,js,json}
4
4
  */
5
5
 
6
- import { mkdirSync, existsSync } from "fs";
7
- import { join } from "path";
6
+ import { existsSync, mkdirSync } from "node:fs";
7
+ import { join } from "node:path";
8
8
 
9
9
  /** AMALFA directory structure */
10
10
  export const AMALFA_DIRS = {
@@ -289,10 +289,7 @@ export async function loadConfig(): Promise<AmalfaConfig> {
289
289
 
290
290
  return merged;
291
291
  }
292
- } catch (e) {
293
- // Silently continue to next config file
294
- continue;
295
- }
292
+ } catch (_e) {}
296
293
  }
297
294
 
298
295
  // Return defaults if no config found
@@ -134,7 +134,7 @@ export class EdgeWeaver {
134
134
 
135
135
  for (const match of matches) {
136
136
  if (!match[2]) continue;
137
- let linkPath = match[2].trim();
137
+ const linkPath = match[2].trim();
138
138
 
139
139
  // Skip external links (http/https)
140
140
  if (linkPath.startsWith("http://") || linkPath.startsWith("https://")) {
@@ -1,11 +1,11 @@
1
1
  import type { Database } from "bun:sqlite";
2
+ import { getLogger } from "@src/utils/Logger";
2
3
  import { MultiDirectedGraph } from "graphology";
3
- import { bidirectional as shortestPath } from "graphology-shortest-path/unweighted";
4
4
  import communitiesLouvain from "graphology-communities-louvain";
5
- import pagerank from "graphology-metrics/centrality/pagerank";
6
- import betweenness from "graphology-metrics/centrality/betweenness";
7
5
  import { connectedComponents } from "graphology-components";
8
- import { getLogger } from "@src/utils/Logger";
6
+ import betweenness from "graphology-metrics/centrality/betweenness";
7
+ import pagerank from "graphology-metrics/centrality/pagerank";
8
+ import { bidirectional as shortestPath } from "graphology-shortest-path/unweighted";
9
9
 
10
10
  const log = getLogger("GraphEngine");
11
11
 
@@ -1,4 +1,4 @@
1
- import { randomUUID } from "crypto";
1
+ import { randomUUID } from "node:crypto";
2
2
 
3
3
  export class MarkdownMasker {
4
4
  private stash: Map<string, string> = new Map();
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env bun
2
+
2
3
  /**
3
4
  * AMALFA Daemon
4
5
  * File watcher for incremental database updates
5
6
  */
6
7
 
7
- import { existsSync } from "fs";
8
- import { watch } from "fs";
9
- import { join } from "path";
8
+ import { existsSync, watch } from "node:fs";
9
+ import { join } from "node:path";
10
10
  import {
11
- loadConfig,
12
11
  AMALFA_DIRS,
13
12
  type AmalfaConfig,
13
+ loadConfig,
14
14
  } from "@src/config/defaults";
15
15
  import { AmalfaIngestor } from "@src/pipeline/AmalfaIngestor";
16
16
  import { ResonanceDB } from "@src/resonance/db";
17
17
  import { getLogger } from "@src/utils/Logger";
18
- import { ServiceLifecycle } from "@src/utils/ServiceLifecycle";
19
18
  import { sendNotification } from "@src/utils/Notifications";
19
+ import { ServiceLifecycle } from "@src/utils/ServiceLifecycle";
20
20
 
21
21
  const args = process.argv.slice(2);
22
22
  const command = args[0] || "serve";
@@ -101,7 +101,7 @@ function startWatcher(sourceDir: string, debounceMs: number) {
101
101
  try {
102
102
  watch(watchPath, { recursive: true }, (event, filename) => {
103
103
  // Only process markdown files
104
- if (filename && filename.endsWith(".md")) {
104
+ if (filename?.endsWith(".md")) {
105
105
  const fullPath = join(watchPath, filename);
106
106
 
107
107
  log.debug(
@@ -21,6 +21,7 @@ import {
21
21
  discoverOllamaCapabilities,
22
22
  } from "@src/utils/ollama-discovery";
23
23
  import { ServiceLifecycle } from "@src/utils/ServiceLifecycle";
24
+ import { inferenceState } from "./sonar-inference";
24
25
  import {
25
26
  handleBatchEnhancement,
26
27
  handleChat,
@@ -44,7 +45,6 @@ import type {
44
45
  SearchRerankRequest,
45
46
  SonarTask,
46
47
  } from "./sonar-types";
47
- import { inferenceState } from "./sonar-inference";
48
48
 
49
49
  const args = process.argv.slice(2);
50
50
  const command = args[0] || "serve";
@@ -1,4 +1,4 @@
1
- import { AMALFA_DIRS, loadConfig } from "@src/config/defaults";
1
+ import { loadConfig } from "@src/config/defaults";
2
2
  import { getLogger } from "@src/utils/Logger";
3
3
  import type { Message, RequestOptions } from "./sonar-types";
4
4
 
@@ -197,14 +197,22 @@ export async function handleSearchAnalysis(
197
197
  {
198
198
  role: "system",
199
199
  content:
200
- 'Analyze search queries and extract intent, entities and suggested filters. Return JSON: { "intent": "", "entities": [], "filters": {} }',
200
+ 'Analyze the user query. Extract the search intent, key entities, and any implicit filters. You MUST return valid JSON. Example: { "intent": "informational", "entities": ["vector"], "filters": {} }. Do not include any text outside the JSON object.',
201
201
  },
202
202
  { role: "user", content: query },
203
203
  ],
204
204
  { temperature: 0.1, format: "json" },
205
205
  );
206
206
 
207
- return JSON.parse(response.message.content);
207
+ const parsed = safeJsonParse(response.message.content);
208
+ if (!parsed) {
209
+ log.warn(
210
+ { content: response.message.content },
211
+ "Failed to parse JSON response, using fallback",
212
+ );
213
+ return { intent: "search", entities: [query], filters: {} };
214
+ }
215
+ return parsed;
208
216
  } catch (error) {
209
217
  log.error({ error, query }, "Query analysis failed");
210
218
  throw error;
@@ -250,7 +258,10 @@ export async function handleResultReranking(
250
258
 
251
259
  const content = response.message.content;
252
260
  try {
253
- const rankings = JSON.parse(content);
261
+ const rankings = safeJsonParse(content);
262
+ if (!rankings || !Array.isArray(rankings))
263
+ throw new Error("Invalid JSON");
264
+
254
265
  return results.map((result, idx) => {
255
266
  const ranking = rankings.find(
256
267
  (r: { index: number }) => r.index === idx + 1,
@@ -283,19 +294,25 @@ export async function handleContextExtraction(
283
294
  {
284
295
  role: "system",
285
296
  content:
286
- "Extract the most relevant 200-300 character snippet from the document for the given query.",
297
+ "You are a helpful assistant. Extract the exact text snippet from the document that answers the query. Return ONLY the snippet text. If the answer is not found, return the most relevant paragraph.",
287
298
  },
288
299
  {
289
300
  role: "user",
290
- content: `Query: ${query}\nDocument [${result.id}]:\n${result.content.slice(0, 4000)}`,
301
+ content: `Query: ${query}\n\nDocument Text:\n${result.content.slice(0, 4000)}`,
291
302
  },
292
303
  ],
293
- { temperature: 0 },
304
+ { temperature: 0.1 },
294
305
  );
295
306
 
307
+ const snippet = response.message.content.trim();
308
+
309
+ // Fallback if model refuses to extract or returns empty
310
+ const finalSnippet =
311
+ snippet.length > 5 ? snippet : result.content.slice(0, 300).trim();
312
+
296
313
  return {
297
314
  id: result.id,
298
- snippet: response.message.content.trim(),
315
+ snippet: finalSnippet,
299
316
  };
300
317
  } catch (error) {
301
318
  log.error({ error, docId: result.id }, "Context extraction failed");
@@ -520,17 +537,11 @@ Return JSON: { "action": "SEARCH"|"READ"|"EXPLORE"|"FINISH", "query": "...", "no
520
537
  nodeId?: string;
521
538
  reasoning: string;
522
539
  answer?: string;
523
- };
524
- try {
525
- decision = JSON.parse(content);
526
- } catch {
527
- // Try to extract JSON from markdown blocks
528
- const match = content.match(/\{[\s\S]*\}/);
529
- if (match) {
530
- decision = JSON.parse(match[0]);
531
- } else {
532
- throw new Error("Could not parse JSON from response");
533
- }
540
+ } | null = null;
541
+
542
+ decision = safeJsonParse(content);
543
+ if (!decision) {
544
+ throw new Error("Could not parse JSON from response");
534
545
  }
535
546
  output += `> **Reasoning:** ${decision.reasoning}\n\n`;
536
547
 
@@ -635,13 +646,7 @@ Return JSON: { "answered": true|false, "missing_info": "...", "final_answer": ".
635
646
  missing_info: string;
636
647
  final_answer: string;
637
648
  };
638
- let audit: AuditResult | null = null;
639
- try {
640
- audit = JSON.parse(resultSnippet);
641
- } catch {
642
- const match = resultSnippet.match(/\{[\s\S]*\}/);
643
- audit = match ? JSON.parse(match[0]) : null;
644
- }
649
+ const audit = safeJsonParse(resultSnippet) as AuditResult | null;
645
650
 
646
651
  if (audit) {
647
652
  if (!audit.answered) {
@@ -660,3 +665,23 @@ Return JSON: { "answered": true|false, "missing_info": "...", "final_answer": ".
660
665
 
661
666
  return output;
662
667
  }
668
+
669
+ /**
670
+ * Helper to safely parse JSON from LLM responses, handling markdown blocks
671
+ */
672
+ function safeJsonParse(content: string): any {
673
+ try {
674
+ return JSON.parse(content);
675
+ } catch {
676
+ // Try to extract JSON from markdown blocks
677
+ const match = content.match(/\{[\s\S]*\}/);
678
+ if (match) {
679
+ try {
680
+ return JSON.parse(match[0]);
681
+ } catch {
682
+ return null;
683
+ }
684
+ }
685
+ return null;
686
+ }
687
+ }
@@ -1,7 +1,6 @@
1
1
  import { loadConfig } from "@src/config/defaults";
2
2
  import { getLogger } from "@src/utils/Logger";
3
3
  import { callOllama, inferenceState } from "./sonar-inference";
4
- import type { SonarTask } from "./sonar-types";
5
4
 
6
5
  const log = getLogger("SonarStrategies");
7
6
 
package/src/mcp/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { appendFileSync } from "fs";
2
- import { join } from "path";
1
+ import { appendFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
3
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import {
@@ -200,7 +200,7 @@ async function runServer() {
200
200
  let queryAnalysis: Awaited<
201
201
  ReturnType<typeof sonarClient.analyzeQuery>
202
202
  > | null = null;
203
- let queryIntent: string | undefined = undefined;
203
+ let queryIntent: string | undefined;
204
204
 
205
205
  if (sonarAvailable) {
206
206
  log.info({ query }, "🔍 Analyzing query with Sonar");
@@ -4,7 +4,7 @@
4
4
  * No Persona/CDA complexity - just pure markdown → knowledge graph
5
5
  */
6
6
 
7
- import { join } from "path";
7
+ import { join } from "node:path";
8
8
  import type { AmalfaConfig } from "@src/config/defaults";
9
9
  import { EdgeWeaver } from "@src/core/EdgeWeaver";
10
10
  import type { Node, ResonanceDB } from "@src/resonance/db";
@@ -1,20 +1,27 @@
1
1
  /**
2
2
  * PreFlightAnalyzer
3
- *
3
+ *
4
4
  * Validates source directories before ingestion to detect issues:
5
5
  * - Large files that need splitting
6
6
  * - Symlinks and circular references
7
7
  * - Empty or invalid files
8
8
  * - Estimated resource usage
9
- *
9
+ *
10
10
  * Generates .amalfa/logs/pre-flight.log with recommendations.
11
11
  */
12
12
 
13
- import { existsSync, lstatSync, readdirSync, realpathSync, statSync, writeFileSync } from "fs";
14
- import { join, relative } from "path";
15
- import { getLogger } from "@src/utils/Logger";
13
+ import {
14
+ existsSync,
15
+ lstatSync,
16
+ readdirSync,
17
+ realpathSync,
18
+ statSync,
19
+ writeFileSync,
20
+ } from "node:fs";
21
+ import { join } from "node:path";
16
22
  import type { AmalfaConfig } from "@src/config/defaults";
17
- import { AMALFA_DIRS, initAmalfaDirs } from "@src/config/defaults";
23
+ import { initAmalfaDirs } from "@src/config/defaults";
24
+ import { getLogger } from "@src/utils/Logger";
18
25
 
19
26
  const log = getLogger("PreFlightAnalyzer");
20
27
 
@@ -27,7 +34,13 @@ const WARN_TOTAL_SIZE_MB = 100;
27
34
 
28
35
  export interface FileIssue {
29
36
  path: string;
30
- issue: "too_large" | "too_small" | "symlink" | "circular_ref" | "empty" | "non_markdown";
37
+ issue:
38
+ | "too_large"
39
+ | "too_small"
40
+ | "symlink"
41
+ | "circular_ref"
42
+ | "empty"
43
+ | "non_markdown";
31
44
  severity: "error" | "warning" | "info";
32
45
  details: string;
33
46
  recommendation?: string;
@@ -49,7 +62,6 @@ export class PreFlightAnalyzer {
49
62
  private config: AmalfaConfig;
50
63
  private visitedPaths = new Set<string>();
51
64
  private issues: FileIssue[] = [];
52
- private logPath = join(AMALFA_DIRS.logs, "pre-flight.log");
53
65
 
54
66
  constructor(config: AmalfaConfig) {
55
67
  this.config = config;
@@ -181,13 +193,14 @@ export class PreFlightAnalyzer {
181
193
  issue: "circular_ref",
182
194
  severity: "error",
183
195
  details: "Circular symlink reference detected",
184
- recommendation: "Remove circular symlink to prevent infinite loops",
196
+ recommendation:
197
+ "Remove circular symlink to prevent infinite loops",
185
198
  });
186
199
  return { valid: false, size: 0 };
187
200
  }
188
201
 
189
202
  this.visitedPaths.add(realPath);
190
- } catch (err) {
203
+ } catch (_err) {
191
204
  this.issues.push({
192
205
  path: filePath,
193
206
  issue: "circular_ref",
@@ -416,7 +429,9 @@ export class PreFlightAnalyzer {
416
429
  lines.push("");
417
430
  lines.push("Why this limit exists:");
418
431
  lines.push("- Prevents excessive memory usage during embedding generation");
419
- lines.push("- Ensures good search quality (large files = poor granularity)");
432
+ lines.push(
433
+ "- Ensures good search quality (large files = poor granularity)",
434
+ );
420
435
  lines.push("- Maintains reasonable graph structure");
421
436
  lines.push("");
422
437
  lines.push("How to handle large files:");
@@ -10,8 +10,8 @@
10
10
  * await harvester.loadIntoResonance(graph);
11
11
  */
12
12
 
13
- import { existsSync } from "fs";
14
- import { join } from "path";
13
+ import { existsSync } from "node:fs";
14
+ import { join } from "node:path";
15
15
  import { getLogger } from "@src/utils/Logger";
16
16
  import { $ } from "bun";
17
17
 
@@ -15,7 +15,10 @@ export const DatabaseFactory = {
15
15
  * Connects specifically to the main Resonance Graph database.
16
16
  * @deprecated Use connect() with explicit path from config instead.
17
17
  */
18
- connectToResonance(dbPath: string = ".amalfa/resonance.db", options: { readonly?: boolean } = {}): Database {
18
+ connectToResonance(
19
+ dbPath: string = ".amalfa/resonance.db",
20
+ options: { readonly?: boolean } = {},
21
+ ): Database {
19
22
  return DatabaseFactory.connect(dbPath, options);
20
23
  },
21
24
  /**
@@ -1,4 +1,4 @@
1
- import { join } from "path";
1
+ import { join } from "node:path";
2
2
  import { toFafcas } from "@src/resonance/db";
3
3
  import { EmbeddingModel, FlagEmbedding } from "fastembed";
4
4
 
@@ -59,18 +59,18 @@ export class SimpleTokenizerService {
59
59
  const realTerm = match ? match[0] : term;
60
60
 
61
61
  if (tag === "Protocol") {
62
- if (!result.protocols!.includes(realTerm))
63
- result.protocols!.push(realTerm);
62
+ if (!result.protocols?.includes(realTerm))
63
+ result.protocols?.push(realTerm);
64
64
  } else if (tag === "Concept") {
65
- if (!result.concepts!.includes(realTerm))
66
- result.concepts!.push(realTerm);
65
+ if (!result.concepts?.includes(realTerm))
66
+ result.concepts?.push(realTerm);
67
67
  } else if (tag === "Organization") {
68
68
  if (!result.organizations.includes(realTerm))
69
69
  result.organizations.push(realTerm);
70
70
  } else {
71
71
  // Default to concepts
72
- if (!result.concepts!.includes(realTerm))
73
- result.concepts!.push(realTerm);
72
+ if (!result.concepts?.includes(realTerm))
73
+ result.concepts?.push(realTerm);
74
74
  }
75
75
  }
76
76
  }
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env bun
2
+
2
3
  /**
3
4
  * Vector Daemon - HTTP server for fast embedding generation
4
5
  * Keeps FastEmbed model loaded in memory for <100ms embedding lookups
5
6
  */
6
7
 
7
- import { serve } from "bun";
8
- import { join } from "path";
9
- import { EmbeddingModel, FlagEmbedding } from "fastembed";
8
+ import { join } from "node:path";
9
+ import { AMALFA_DIRS } from "@src/config/defaults";
10
10
  import { toFafcas } from "@src/resonance/db";
11
11
  import { getLogger } from "@src/utils/Logger";
12
12
  import { ServiceLifecycle } from "@src/utils/ServiceLifecycle";
13
- import { AMALFA_DIRS } from "@src/config/defaults";
13
+ import { serve } from "bun";
14
+ import { EmbeddingModel, FlagEmbedding } from "fastembed";
14
15
 
15
16
  const log = getLogger("VectorDaemon");
16
17
  const PORT = Number(process.env.VECTOR_PORT || 3010);
@@ -33,16 +34,16 @@ const currentModel = EmbeddingModel.BGESmallENV15;
33
34
  async function initEmbedder() {
34
35
  if (!embedder) {
35
36
  log.info({ model: currentModel }, "🔄 Initializing embedding model...");
36
-
37
+
37
38
  // Ensure cache directory exists
38
39
  const cacheDir = ".amalfa/cache";
39
40
  const { mkdir } = await import("node:fs/promises");
40
41
  try {
41
42
  await mkdir(cacheDir, { recursive: true });
42
- } catch (e) {
43
+ } catch (_e) {
43
44
  // Directory might already exist, that's fine
44
45
  }
45
-
46
+
46
47
  embedder = await FlagEmbedding.init({
47
48
  model: currentModel,
48
49
  cacheDir,
@@ -60,7 +61,7 @@ async function runServer() {
60
61
  await initEmbedder();
61
62
 
62
63
  // Start HTTP server
63
- const server = serve({
64
+ const _server = serve({
64
65
  port: PORT,
65
66
  async fetch(req) {
66
67
  const url = new URL(req.url);
@@ -1,7 +1,7 @@
1
- import { existsSync } from "fs";
2
- import { join } from "path";
3
- import { ServiceLifecycle } from "./ServiceLifecycle";
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
4
3
  import { AMALFA_DIRS } from "@src/config/defaults";
4
+ import { ServiceLifecycle } from "./ServiceLifecycle";
5
5
 
6
6
  export interface DaemonStatus {
7
7
  running: boolean;
@@ -1,5 +1,5 @@
1
- import { spawn } from "child_process";
2
- import { platform } from "os";
1
+ import { spawn } from "node:child_process";
2
+ import { platform } from "node:os";
3
3
 
4
4
  /**
5
5
  * Send a native desktop notification
@@ -1,7 +1,6 @@
1
- import { existsSync } from "fs";
2
- import { unlink } from "fs/promises";
3
- import { join } from "path";
4
- import { AMALFA_DIRS, initAmalfaDirs } from "@src/config/defaults";
1
+ import { existsSync } from "node:fs";
2
+ import { unlink } from "node:fs/promises";
3
+ import { initAmalfaDirs } from "@src/config/defaults";
5
4
 
6
5
  export interface ServiceConfig {
7
6
  name: string; // e.g. "Daemon"
@@ -1,5 +1,5 @@
1
- import { existsSync, mkdirSync } from "fs";
2
- import { join } from "path";
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
3
  import { AMALFA_DIRS } from "@src/config/defaults";
4
4
 
5
5
  export interface DatabaseSnapshot {
@@ -1,4 +1,4 @@
1
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { getLogger } from "@src/utils/Logger";
3
3
 
4
4
  const log = getLogger("TagInjector");
@@ -79,7 +79,7 @@ export class TagInjector {
79
79
 
80
80
  if (content.includes(link)) return true;
81
81
 
82
- content = content.trimEnd() + `\n\nSee also: ${link}\n`;
82
+ content = `${content.trimEnd()}\n\nSee also: ${link}\n`;
83
83
  writeFileSync(filePath, content, "utf-8");
84
84
  return true;
85
85
  } catch (error) {
@@ -128,7 +128,7 @@ export async function discoverOllamaCapabilities(): Promise<OllamaCapabilities>
128
128
  }
129
129
 
130
130
  // Get model details for the selected model
131
- const phi3 = models.find((m) => m.name === "phi3:latest");
131
+ const _phi3 = models.find((m) => m.name === "phi3:latest");
132
132
 
133
133
  let modelInfo: unknown = null;
134
134
  try {
@@ -136,7 +136,7 @@ export async function discoverOllamaCapabilities(): Promise<OllamaCapabilities>
136
136
  if (showResult.exitCode === 0) {
137
137
  modelInfo = parseModelInfo(showResult.stdout?.toString() ?? "");
138
138
  }
139
- } catch (e) {
139
+ } catch (_e) {
140
140
  log.warn(`⚠️ Could not get details for ${searchModel}`);
141
141
  }
142
142
 
@@ -35,7 +35,7 @@ export interface SonarClient {
35
35
  result: { id: string; content: string },
36
36
  query: string,
37
37
  ): Promise<{ snippet: string; context: string; confidence: number } | null>;
38
- getGaps(limit?: number): Promise<any[]>;
38
+ getGaps(limit?: number): Promise<unknown[]>;
39
39
  }
40
40
 
41
41
  /**
@@ -56,7 +56,6 @@ export async function createSonarClient(): Promise<SonarClient> {
56
56
 
57
57
  // Check if Sonar is enabled
58
58
  // Checking both for backward compatibility or migration
59
- // @ts-ignore
60
59
  const isEnabled = config.sonar?.enabled ?? config.phi3?.enabled;
61
60
 
62
61
  if (!isEnabled) {
@@ -64,9 +63,8 @@ export async function createSonarClient(): Promise<SonarClient> {
64
63
  return createDisabledClient();
65
64
  }
66
65
 
67
- // @ts-ignore
68
66
  const hostArgs = config.sonar || config.phi3 || {};
69
- const host = hostArgs.host || "localhost:11434";
67
+ const _host = hostArgs.host || "localhost:11434";
70
68
  const port = hostArgs.port || 3012;
71
69
  const baseUrl = `http://localhost:${port}`;
72
70
  const timeout = hostArgs.tasks?.search?.timeout || 5000;
@@ -257,12 +255,12 @@ export async function createSonarClient(): Promise<SonarClient> {
257
255
  }
258
256
  },
259
257
 
260
- async getGaps(limit?: number): Promise<any[]> {
258
+ async getGaps(_limit?: number): Promise<unknown[]> {
261
259
  if (!(await isAvailable())) return [];
262
260
  try {
263
261
  const response = await fetch(`${baseUrl}/graph/explore`);
264
262
  if (!response.ok) return [];
265
- const data = (await response.json()) as { gaps?: any[] };
263
+ const data = (await response.json()) as { gaps?: unknown[] };
266
264
  return data.gaps || [];
267
265
  } catch (error) {
268
266
  log.error({ error }, "Failed to fetch gaps");
@@ -304,7 +302,7 @@ function createDisabledClient(): SonarClient {
304
302
  } | null> {
305
303
  return null;
306
304
  },
307
- async getGaps(): Promise<any[]> {
305
+ async getGaps(): Promise<unknown[]> {
308
306
  return [];
309
307
  },
310
308
  };