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 +3 -2
- package/src/cli/sonar-chat.ts +1 -1
- package/src/cli.ts +4 -4
- package/src/config/defaults.ts +3 -6
- package/src/core/EdgeWeaver.ts +1 -1
- package/src/core/GraphEngine.ts +4 -4
- package/src/core/MarkdownMasker.ts +1 -1
- package/src/daemon/index.ts +6 -6
- package/src/daemon/sonar-agent.ts +1 -1
- package/src/daemon/sonar-inference.ts +1 -1
- package/src/daemon/sonar-logic.ts +50 -25
- package/src/daemon/sonar-strategies.ts +0 -1
- package/src/mcp/index.ts +3 -3
- package/src/pipeline/AmalfaIngestor.ts +1 -1
- package/src/pipeline/PreFlightAnalyzer.ts +26 -11
- package/src/pipeline/SemanticHarvester.ts +2 -2
- package/src/resonance/DatabaseFactory.ts +4 -1
- package/src/resonance/services/embedder.ts +1 -1
- package/src/resonance/services/simpleTokenizer.ts +6 -6
- package/src/resonance/services/vector-daemon.ts +9 -8
- package/src/utils/DaemonManager.ts +3 -3
- package/src/utils/Notifications.ts +2 -2
- package/src/utils/ServiceLifecycle.ts +3 -4
- package/src/utils/StatsTracker.ts +2 -2
- package/src/utils/TagInjector.ts +2 -2
- package/src/utils/ollama-discovery.ts +2 -2
- package/src/utils/sonar-client.ts +5 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amalfa",
|
|
3
|
-
"version": "1.0.
|
|
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",
|
package/src/cli/sonar-chat.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
},
|
package/src/config/defaults.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Default settings that can be overridden via amalfa.config.{ts,js,json}
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
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 (
|
|
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
|
package/src/core/EdgeWeaver.ts
CHANGED
|
@@ -134,7 +134,7 @@ export class EdgeWeaver {
|
|
|
134
134
|
|
|
135
135
|
for (const match of matches) {
|
|
136
136
|
if (!match[2]) continue;
|
|
137
|
-
|
|
137
|
+
const linkPath = match[2].trim();
|
|
138
138
|
|
|
139
139
|
// Skip external links (http/https)
|
|
140
140
|
if (linkPath.startsWith("http://") || linkPath.startsWith("https://")) {
|
package/src/core/GraphEngine.ts
CHANGED
|
@@ -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
|
|
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
|
|
package/src/daemon/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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";
|
|
@@ -197,14 +197,22 @@ export async function handleSearchAnalysis(
|
|
|
197
197
|
{
|
|
198
198
|
role: "system",
|
|
199
199
|
content:
|
|
200
|
-
'Analyze
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
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
|
+
}
|
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
|
|
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 {
|
|
14
|
-
|
|
15
|
-
|
|
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 {
|
|
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:
|
|
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:
|
|
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 (
|
|
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(
|
|
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(
|
|
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
|
/**
|
|
@@ -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
|
|
63
|
-
result.protocols
|
|
62
|
+
if (!result.protocols?.includes(realTerm))
|
|
63
|
+
result.protocols?.push(realTerm);
|
|
64
64
|
} else if (tag === "Concept") {
|
|
65
|
-
if (!result.concepts
|
|
66
|
-
result.concepts
|
|
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
|
|
73
|
-
result.concepts
|
|
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 {
|
|
8
|
-
import {
|
|
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 {
|
|
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 (
|
|
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
|
|
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,7 +1,6 @@
|
|
|
1
|
-
import { existsSync } from "fs";
|
|
2
|
-
import { unlink } from "fs/promises";
|
|
3
|
-
import {
|
|
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"
|
package/src/utils/TagInjector.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync
|
|
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()
|
|
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
|
|
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 (
|
|
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<
|
|
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
|
|
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(
|
|
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?:
|
|
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<
|
|
305
|
+
async getGaps(): Promise<unknown[]> {
|
|
308
306
|
return [];
|
|
309
307
|
},
|
|
310
308
|
};
|