monomind 2.0.3 → 2.1.0
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 +9 -5
- package/packages/@monomind/cli/.claude/helpers/control-start.cjs +13 -20
- package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +109 -6
- package/packages/@monomind/cli/.claude/helpers/graphify-freshen.cjs +19 -17
- package/packages/@monomind/cli/.claude/helpers/handlers/agent-start-handler.cjs +60 -28
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +75 -18
- package/packages/@monomind/cli/.claude/helpers/handlers/edit-handler.cjs +27 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +16 -7
- package/packages/@monomind/cli/.claude/helpers/handlers/loops-status-handler.cjs +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +4 -4
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +40 -22
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +0 -12
- package/packages/@monomind/cli/.claude/helpers/handlers/task-handler.cjs +53 -22
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +54 -4
- package/packages/@monomind/cli/.claude/helpers/intelligence.cjs +94 -15
- package/packages/@monomind/cli/.claude/helpers/session.cjs +6 -1
- package/packages/@monomind/cli/.claude/helpers/statusline.cjs +10 -10
- package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +3 -3
- package/packages/@monomind/cli/.claude/helpers/utils/agent-registrations.cjs +41 -0
- package/packages/@monomind/cli/.claude/helpers/utils/fs-helpers.cjs +183 -0
- package/packages/@monomind/cli/.claude/helpers/utils/micro-agents.cjs +10 -6
- package/packages/@monomind/cli/.claude/helpers/utils/telemetry.cjs +35 -19
- package/packages/@monomind/cli/bin/cli.js +10 -1
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +9 -15
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +20 -4
- package/packages/@monomind/cli/dist/src/commands/completions.js +66 -261
- package/packages/@monomind/cli/dist/src/commands/doctor.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +7 -0
- package/packages/@monomind/cli/dist/src/commands/index.js +3 -0
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -6
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +12 -6
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +6 -13
- package/packages/@monomind/cli/dist/src/commands/monograph.js +3 -3
- package/packages/@monomind/cli/dist/src/commands/org.d.ts +3 -1
- package/packages/@monomind/cli/dist/src/commands/org.js +306 -129
- package/packages/@monomind/cli/dist/src/commands/start.js +142 -37
- package/packages/@monomind/cli/dist/src/commands/tokens.js +21 -3
- package/packages/@monomind/cli/dist/src/config-adapter.js +37 -17
- package/packages/@monomind/cli/dist/src/index.js +64 -28
- package/packages/@monomind/cli/dist/src/init/claudemd-generator.js +44 -11
- package/packages/@monomind/cli/dist/src/init/executor.js +63 -49
- package/packages/@monomind/cli/dist/src/mcp-client.d.ts +8 -3
- package/packages/@monomind/cli/dist/src/mcp-client.js +37 -3
- package/packages/@monomind/cli/dist/src/mcp-server.js +8 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +10 -5
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +30 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +51 -34
- package/packages/@monomind/cli/dist/src/mcp-tools/knowledge-tools.js +9 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +56 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +65 -43
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +6 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +4 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/types.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/types.js +36 -1
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.d.ts +13 -1
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.js +66 -14
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +99 -3
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +20 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +24 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.js +68 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +30 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +96 -7
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +10 -5
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +105 -8
- package/packages/@monomind/cli/dist/src/orgrt/live.html +56 -0
- package/packages/@monomind/cli/dist/src/orgrt/provider.js +1 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.d.ts +13 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.js +48 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.js +72 -0
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +108 -0
- package/packages/@monomind/cli/dist/src/output.js +12 -5
- package/packages/@monomind/cli/dist/src/parser.d.ts +32 -0
- package/packages/@monomind/cli/dist/src/parser.js +130 -5
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +13 -1
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +31 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.d.ts +21 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +60 -7
- package/packages/@monomind/cli/dist/src/services/crash-reporter.js +2 -1
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +40 -40
- package/packages/@monomind/cli/dist/src/ui/orgs.html +110 -11
- package/packages/@monomind/cli/dist/src/ui/server.mjs +304 -133
- package/packages/@monomind/cli/dist/src/utils/input-guards.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/utils/input-guards.js +39 -5
- package/packages/@monomind/cli/package.json +6 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Knowledge Tools — Second Brain document ingest and search
|
|
3
3
|
*/
|
|
4
|
+
import { validateInput } from '../utils/input-guards.js';
|
|
4
5
|
const knowledgeIngest = {
|
|
5
6
|
name: 'knowledge_ingest',
|
|
6
7
|
description: 'Ingest documents into the Second Brain knowledge base. Accepts a file or directory path. Extracts text, chunks, embeds, and stores in LanceDB for semantic search.',
|
|
@@ -15,10 +16,17 @@ const knowledgeIngest = {
|
|
|
15
16
|
required: ['path'],
|
|
16
17
|
},
|
|
17
18
|
handler: async (input) => {
|
|
19
|
+
const pathCheck = validateInput(input.path, { type: 'path' });
|
|
20
|
+
if (!pathCheck.valid) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: pathCheck.error }) }],
|
|
23
|
+
isError: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
18
26
|
const { ingestDocument, ingestDirectory } = await import('../knowledge/document-pipeline.js');
|
|
19
27
|
const fs = await import('node:fs');
|
|
20
28
|
const pathMod = await import('node:path');
|
|
21
|
-
const target = pathMod.resolve(
|
|
29
|
+
const target = pathMod.resolve(pathCheck.sanitized);
|
|
22
30
|
const scope = String(input.scope || 'shared');
|
|
23
31
|
try {
|
|
24
32
|
const stat = fs.statSync(target);
|
|
@@ -6,26 +6,67 @@
|
|
|
6
6
|
* moved to the real package.
|
|
7
7
|
*/
|
|
8
8
|
import { join, resolve, relative } from 'path';
|
|
9
|
-
import { existsSync,
|
|
10
|
-
import {
|
|
9
|
+
import { existsSync, statSync } from 'fs';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import { openDb, closeDb, countNodes, parseGroupConfig } from '@monoes/monograph';
|
|
12
|
+
import { getProjectCwd } from './types.js';
|
|
13
|
+
import { realOrResolved } from '../utils/input-guards.js';
|
|
14
|
+
// ── Group config reader ───────────────────────────────────────────────────────
|
|
11
15
|
const MAX_GROUP_CONFIG_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
16
|
+
/**
|
|
17
|
+
* Read repos from a group.yaml config (the real group config format used by
|
|
18
|
+
* monograph_group_query / monograph_group_sync / monograph_group_list — see
|
|
19
|
+
* @monoes/monograph's groups/group-config.ts).
|
|
20
|
+
*
|
|
21
|
+
* Throws on an out-of-bounds or oversized path instead of silently returning
|
|
22
|
+
* an empty list, so callers can distinguish "no groups configured" from
|
|
23
|
+
* "your repoPath was rejected".
|
|
24
|
+
*/
|
|
12
25
|
function readGroupConfig(configPath) {
|
|
13
26
|
if (!existsSync(configPath))
|
|
14
27
|
return [];
|
|
28
|
+
// Guard: only allow paths within the project cwd to prevent traversal to
|
|
29
|
+
// /etc/passwd etc. Uses getProjectCwd() (honors MONOMIND_CWD) rather than
|
|
30
|
+
// process.cwd(), which is trivially bypassed when the MCP server runs with
|
|
31
|
+
// cwd "/".
|
|
32
|
+
// realOrResolved resolves symlinks (walking up to the longest existing
|
|
33
|
+
// ancestor) so a link that's lexically inside projectCwd but physically
|
|
34
|
+
// points outside it can't bypass this containment check — same guard
|
|
35
|
+
// input-guards.ts's validatePath uses, applied here against getProjectCwd()
|
|
36
|
+
// instead of process.cwd() since that's the whole reason this check isn't
|
|
37
|
+
// just a direct call to validateInput({type:'path'}).
|
|
38
|
+
const projectCwd = realOrResolved(getProjectCwd());
|
|
39
|
+
const resolved = realOrResolved(resolve(configPath));
|
|
40
|
+
const rel = relative(projectCwd, resolved);
|
|
41
|
+
if (rel.startsWith('..') || resolve(rel) === resolve('/')) {
|
|
42
|
+
throw new Error(`Rejected group config path outside project directory: ${resolved} (project cwd: ${projectCwd})`);
|
|
43
|
+
}
|
|
44
|
+
// OOM guard: skip files larger than 1 MB
|
|
45
|
+
if (statSync(configPath).size > MAX_GROUP_CONFIG_BYTES) {
|
|
46
|
+
throw new Error(`Group config file too large (> ${MAX_GROUP_CONFIG_BYTES} bytes): ${configPath}`);
|
|
47
|
+
}
|
|
48
|
+
return parseGroupConfig(configPath).repos;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Compute whether a repo's monograph index is stale relative to HEAD, the
|
|
52
|
+
* same way monograph_staleness does: compare the last indexed commit hash
|
|
53
|
+
* against `git rev-list --count <lastCommit>..HEAD`. Mirrors the threshold
|
|
54
|
+
* used by monograph-tools.ts (STALENESS_THRESHOLD = 10).
|
|
55
|
+
*/
|
|
56
|
+
function isRepoStale(db, repoPath) {
|
|
15
57
|
try {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return JSON.parse(raw);
|
|
58
|
+
const meta = (db.prepare("SELECT value FROM index_meta WHERE key = 'last_commit_hash'").get()
|
|
59
|
+
?? db.prepare("SELECT value FROM index_meta WHERE key = 'lastCommit'").get());
|
|
60
|
+
const lastCommit = meta?.value;
|
|
61
|
+
if (!lastCommit || !/^[0-9a-f]{7,40}$/i.test(lastCommit))
|
|
62
|
+
return false;
|
|
63
|
+
const out = execSync(`git rev-list --count ${lastCommit}..HEAD`, {
|
|
64
|
+
cwd: repoPath, encoding: 'utf-8',
|
|
65
|
+
}).trim();
|
|
66
|
+
return parseInt(out, 10) > 10;
|
|
26
67
|
}
|
|
27
68
|
catch {
|
|
28
|
-
return
|
|
69
|
+
return false;
|
|
29
70
|
}
|
|
30
71
|
}
|
|
31
72
|
// ── getGroupContracts ─────────────────────────────────────────────────────────
|
|
@@ -74,11 +115,12 @@ export async function getGroupStatus(configPath) {
|
|
|
74
115
|
const nc = countNodes(db);
|
|
75
116
|
const contracts = db.prepare("SELECT COUNT(*) as n FROM nodes WHERE label = 'Route'").get();
|
|
76
117
|
const meta = db.prepare("SELECT value FROM index_meta WHERE key IN ('ua_last_commit','lastCommit') LIMIT 1").get();
|
|
118
|
+
const stale = isRepoStale(db, repoPath);
|
|
77
119
|
closeDb(db);
|
|
78
120
|
groups.push({
|
|
79
121
|
name,
|
|
80
122
|
indexed: nc > 0,
|
|
81
|
-
stale
|
|
123
|
+
stale,
|
|
82
124
|
contractCount: contracts?.n ?? 0,
|
|
83
125
|
...(meta?.value ? { lastSync: meta.value } : {}),
|
|
84
126
|
});
|
|
@@ -19,23 +19,36 @@ function _isValidDb(p) {
|
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the monograph DB path for a given repo root (defaults to project cwd).
|
|
24
|
+
* Falls back to searching up to the git root when the DB isn't directly under
|
|
25
|
+
* `<cwd>/.monomind` — e.g. when called from a subdirectory of the repo. Only
|
|
26
|
+
* the no-arg (project cwd) form is cached; explicit `repoPath` overrides are
|
|
27
|
+
* cheap one-off lookups (staleness checks with a user-supplied path) so caching
|
|
28
|
+
* them isn't worth the invalidation complexity.
|
|
29
|
+
*/
|
|
30
|
+
function getDbPath(repoPathOverride) {
|
|
31
|
+
const cwd = repoPathOverride ?? getProjectCwd();
|
|
32
|
+
const useCache = repoPathOverride === undefined;
|
|
24
33
|
// Invalidate cache when project root changes (e.g. MONOMIND_CWD set after initialize)
|
|
25
|
-
if (_cachedDbPath && _cachedCwd === cwd)
|
|
34
|
+
if (useCache && _cachedDbPath && _cachedCwd === cwd)
|
|
26
35
|
return _cachedDbPath;
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
if (useCache) {
|
|
37
|
+
_cachedCwd = cwd;
|
|
38
|
+
_cachedDbPath = undefined;
|
|
39
|
+
}
|
|
29
40
|
const direct = join(cwd, '.monomind', 'monograph.db');
|
|
30
41
|
if (_isValidDb(direct)) {
|
|
31
|
-
|
|
42
|
+
if (useCache)
|
|
43
|
+
_cachedDbPath = direct;
|
|
32
44
|
return direct;
|
|
33
45
|
}
|
|
34
46
|
try {
|
|
35
47
|
const root = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf8' }).trim();
|
|
36
48
|
const candidate = join(root, '.monomind', 'monograph.db');
|
|
37
49
|
if (_isValidDb(candidate)) {
|
|
38
|
-
|
|
50
|
+
if (useCache)
|
|
51
|
+
_cachedDbPath = candidate;
|
|
39
52
|
return candidate;
|
|
40
53
|
}
|
|
41
54
|
}
|
|
@@ -46,13 +59,6 @@ function getDbPath() {
|
|
|
46
59
|
function text(t) {
|
|
47
60
|
return { content: [{ type: 'text', text: t }] };
|
|
48
61
|
}
|
|
49
|
-
function ensureDbExists() {
|
|
50
|
-
const p = getDbPath();
|
|
51
|
-
if (!_isValidDb(p)) {
|
|
52
|
-
throw new Error(`Monograph index not built yet. Run monograph_build first (path: ${p}).`);
|
|
53
|
-
}
|
|
54
|
-
return p;
|
|
55
|
-
}
|
|
56
62
|
// ── Active watcher registry ──────────────────────────────────────────────────
|
|
57
63
|
const _activeWatchers = new Map();
|
|
58
64
|
function applyPprRerank(db, seedNodes, damping, maxResults) {
|
|
@@ -577,9 +583,13 @@ function taskRelevance(task, nodeText) {
|
|
|
577
583
|
return taskTerms.filter(t => txt.includes(t)).length / taskTerms.length;
|
|
578
584
|
}
|
|
579
585
|
// ── monograph_visualize ───────────────────────────────────────────────────────
|
|
586
|
+
// Columns needed to render/export the graph — deliberately excludes `embedding`,
|
|
587
|
+
// which can be several KB per node (384D vectors) and would otherwise bloat
|
|
588
|
+
// tool-result payloads and written files by many MB.
|
|
589
|
+
const NODE_RENDER_COLUMNS = 'id, label, name, norm_label, file_path, start_line, end_line, community_id, is_exported, language, properties';
|
|
580
590
|
const monographVisualizeTool = {
|
|
581
591
|
name: 'monograph_visualize',
|
|
582
|
-
description: 'Render the knowledge graph as HTML (default), SVG, or JSON.',
|
|
592
|
+
description: 'Render the knowledge graph as HTML (default), SVG, or JSON. Writes output to a file under .monomind/visualize/ and returns the file path (output can be multi-MB at the max node count, too large to return inline).',
|
|
583
593
|
inputSchema: {
|
|
584
594
|
type: 'object',
|
|
585
595
|
properties: {
|
|
@@ -589,6 +599,7 @@ const monographVisualizeTool = {
|
|
|
589
599
|
},
|
|
590
600
|
handler: async (input) => {
|
|
591
601
|
const { openDb, closeDb, toJson, toHtml, toSvg } = await import('@monoes/monograph');
|
|
602
|
+
const { writeFileSync, mkdirSync } = await import('fs');
|
|
592
603
|
const db = openDb(getDbPath());
|
|
593
604
|
try {
|
|
594
605
|
// Cap maxNodes: passed to SQL LIMIT clause for both nodes (n) and edges
|
|
@@ -598,7 +609,7 @@ const monographVisualizeTool = {
|
|
|
598
609
|
const limit = Number.isFinite(rawMaxNodes) && rawMaxNodes > 0
|
|
599
610
|
? Math.min(Math.floor(rawMaxNodes), MAX_EXPORT_NODES)
|
|
600
611
|
: 500;
|
|
601
|
-
const nodes = db.prepare(
|
|
612
|
+
const nodes = db.prepare(`SELECT ${NODE_RENDER_COLUMNS} FROM nodes LIMIT ?`).all(limit);
|
|
602
613
|
// Only include edges where both endpoints are in the visible node set
|
|
603
614
|
const edges = db.prepare(`
|
|
604
615
|
SELECT e.* FROM edges e
|
|
@@ -607,11 +618,15 @@ const monographVisualizeTool = {
|
|
|
607
618
|
LIMIT ?
|
|
608
619
|
`).all(limit, limit, limit * 3);
|
|
609
620
|
const fmt = input.format ?? 'html';
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
621
|
+
const rendered = fmt === 'json' ? toJson(nodes, edges)
|
|
622
|
+
: fmt === 'svg' ? toSvg(nodes, edges)
|
|
623
|
+
: toHtml(nodes, edges);
|
|
624
|
+
const ext = fmt === 'json' ? 'json' : fmt === 'svg' ? 'svg' : 'html';
|
|
625
|
+
const outDir = resolve(join(getProjectCwd(), '.monomind', 'visualize'));
|
|
626
|
+
mkdirSync(outDir, { recursive: true });
|
|
627
|
+
const outPath = join(outDir, `graph-${Date.now()}.${ext}`);
|
|
628
|
+
writeFileSync(outPath, rendered);
|
|
629
|
+
return text(`Visualization written to ${outPath} (${nodes.length} nodes, ${edges.length} edges)${ext === 'html' ? '\nNote: HTML uses the vis-network CDN script and requires network access to render.' : ''}`);
|
|
615
630
|
}
|
|
616
631
|
finally {
|
|
617
632
|
closeDb(db);
|
|
@@ -700,7 +715,8 @@ const monographReportTool = {
|
|
|
700
715
|
return `${i + 1}. **${n.name}** (${n.label}) — degree ${n.degree}${loc ? ` \`${loc}\`` : ''}`;
|
|
701
716
|
}),
|
|
702
717
|
].join('\n');
|
|
703
|
-
const
|
|
718
|
+
const rawOutPath = input.path ?? join(getProjectCwd(), '.monomind', 'GRAPH_REPORT.md');
|
|
719
|
+
const outPath = resolve(getProjectCwd(), rawOutPath);
|
|
704
720
|
const allowedRoot = resolve(getProjectCwd());
|
|
705
721
|
if (outPath !== allowedRoot && !outPath.startsWith(allowedRoot + sep)) {
|
|
706
722
|
return text(`Error: path must be within the project directory (${allowedRoot})`);
|
|
@@ -725,7 +741,10 @@ let _buildInProgress = false;
|
|
|
725
741
|
async function computeCommitsBehind(repoPath) {
|
|
726
742
|
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
727
743
|
const { execSync } = await import('child_process');
|
|
728
|
-
const
|
|
744
|
+
const dbPath = getDbPath(repoPath);
|
|
745
|
+
if (!_isValidDb(dbPath))
|
|
746
|
+
return null;
|
|
747
|
+
const db = openDb(dbPath, { fileMustExist: true });
|
|
729
748
|
try {
|
|
730
749
|
const meta = db.prepare("SELECT value FROM index_meta WHERE key = 'last_commit_hash'").get() ?? db.prepare("SELECT value FROM index_meta WHERE key = 'lastCommit'").get();
|
|
731
750
|
const lastCommit = meta?.value ?? null;
|
|
@@ -929,11 +948,13 @@ const monographExportTool = {
|
|
|
929
948
|
const { writeFileSync, mkdirSync } = await import('fs');
|
|
930
949
|
const db = openDb(getDbPath());
|
|
931
950
|
try {
|
|
932
|
-
|
|
951
|
+
// Exclude `embedding` — a several-KB-per-node vector column that would
|
|
952
|
+
// otherwise bloat exported files by many MB for no rendering benefit.
|
|
953
|
+
const nodes = db.prepare(`SELECT ${NODE_RENDER_COLUMNS} FROM nodes`).all();
|
|
933
954
|
const edges = db.prepare('SELECT * FROM edges').all();
|
|
934
955
|
const fmt = input.format;
|
|
935
956
|
const requestedOut = input.outputPath ?? join(getProjectCwd(), '.monomind', 'export');
|
|
936
|
-
const outDir = resolve(requestedOut);
|
|
957
|
+
const outDir = resolve(getProjectCwd(), requestedOut);
|
|
937
958
|
const allowedRoot = resolve(getProjectCwd());
|
|
938
959
|
if (outDir !== allowedRoot && !outDir.startsWith(allowedRoot + sep)) {
|
|
939
960
|
return text(`Error: outputPath must be within the project directory (${allowedRoot})`);
|
|
@@ -1072,8 +1093,10 @@ const monographImpactTool = {
|
|
|
1072
1093
|
const impactPath = typeof rawImpactPath === 'string' && rawImpactPath.length > MAX_IMPACT_PATH_LEN
|
|
1073
1094
|
? rawImpactPath.slice(0, MAX_IMPACT_PATH_LEN) : rawImpactPath;
|
|
1074
1095
|
const rawDepth = input.depth;
|
|
1075
|
-
const depth =
|
|
1076
|
-
?
|
|
1096
|
+
const depth = rawDepth === undefined
|
|
1097
|
+
? undefined
|
|
1098
|
+
: (typeof rawDepth === 'number' && Number.isFinite(rawDepth) && rawDepth > 0
|
|
1099
|
+
? Math.min(Math.floor(rawDepth), 6) : 3);
|
|
1077
1100
|
const result = getMonographImpact(db, {
|
|
1078
1101
|
name: impactName,
|
|
1079
1102
|
filePath: impactPath,
|
|
@@ -1737,10 +1760,10 @@ const monographInstallSkillsTool = {
|
|
|
1737
1760
|
return text(`Invalid platform "${platform}". Must be one of: ${validPlatforms.join(', ')}`);
|
|
1738
1761
|
}
|
|
1739
1762
|
// Load community data from graph
|
|
1740
|
-
const dbPath =
|
|
1763
|
+
const dbPath = getDbPath(repoPath);
|
|
1741
1764
|
let db;
|
|
1742
1765
|
try {
|
|
1743
|
-
db = openDb(dbPath);
|
|
1766
|
+
db = openDb(dbPath, { fileMustExist: true });
|
|
1744
1767
|
}
|
|
1745
1768
|
catch {
|
|
1746
1769
|
return text('Graph not built yet. Run monograph_build first.');
|
|
@@ -1846,21 +1869,19 @@ const monographListReposTool = {
|
|
|
1846
1869
|
// ── monograph_group_contracts ─────────────────────────────────────────────────
|
|
1847
1870
|
const monographGroupContractsTool = {
|
|
1848
1871
|
name: 'monograph_group_contracts',
|
|
1849
|
-
description: 'List public API contracts (exported symbols, interfaces, and types) for all groups defined in .
|
|
1872
|
+
description: 'List public API contracts (exported symbols, interfaces, and types) for all groups defined in group.yaml.',
|
|
1850
1873
|
inputSchema: {
|
|
1851
1874
|
type: 'object',
|
|
1852
1875
|
properties: {
|
|
1853
|
-
|
|
1876
|
+
configPath: { type: 'string', description: 'Path to group.yaml (defaults to group.yaml in project cwd)' },
|
|
1854
1877
|
},
|
|
1855
1878
|
},
|
|
1856
1879
|
handler: async (input) => {
|
|
1857
1880
|
const { getGroupContracts } = await import('./monograph-compat.js');
|
|
1858
|
-
const
|
|
1859
|
-
const repoPath = input.repoPath ?? getProjectCwd();
|
|
1860
|
-
const configPath = join(repoPath, '.monograph', 'groups.json');
|
|
1881
|
+
const configPath = input.configPath ?? join(getProjectCwd(), 'group.yaml');
|
|
1861
1882
|
const contracts = await getGroupContracts(configPath);
|
|
1862
1883
|
if (contracts.length === 0)
|
|
1863
|
-
return text(
|
|
1884
|
+
return text(`No contracts found. Ensure groups are defined in ${configPath}.`);
|
|
1864
1885
|
const lines = contracts.map(c => `[${c.groupName}] ${c.symbol} — ${c.filePath}:${c.line}`);
|
|
1865
1886
|
return text(lines.join('\n'));
|
|
1866
1887
|
},
|
|
@@ -1872,14 +1893,12 @@ const monographGroupStatusTool = {
|
|
|
1872
1893
|
inputSchema: {
|
|
1873
1894
|
type: 'object',
|
|
1874
1895
|
properties: {
|
|
1875
|
-
|
|
1896
|
+
configPath: { type: 'string', description: 'Path to group.yaml (defaults to group.yaml in project cwd)' },
|
|
1876
1897
|
},
|
|
1877
1898
|
},
|
|
1878
1899
|
handler: async (input) => {
|
|
1879
1900
|
const { getGroupStatus } = await import('./monograph-compat.js');
|
|
1880
|
-
const
|
|
1881
|
-
const repoPath = input.repoPath ?? getProjectCwd();
|
|
1882
|
-
const configPath = join(repoPath, '.monograph', 'groups.json');
|
|
1901
|
+
const configPath = input.configPath ?? join(getProjectCwd(), 'group.yaml');
|
|
1883
1902
|
const status = await getGroupStatus(configPath);
|
|
1884
1903
|
const lines = [`Groups: ${status.totalGroups} (${status.indexedGroups} indexed, ${status.stalledGroups} stalled)`];
|
|
1885
1904
|
for (const g of status.groups) {
|
|
@@ -1954,10 +1973,10 @@ const monographDeadCodeTool = {
|
|
|
1954
1973
|
const repoPath = input.path ?? getProjectCwd();
|
|
1955
1974
|
const cats = input.categories ?? ['dead-functions', 'orphan-files', 'stale-dist'];
|
|
1956
1975
|
const result = {};
|
|
1957
|
-
const dbPath =
|
|
1976
|
+
const dbPath = getDbPath(repoPath);
|
|
1958
1977
|
let db = null;
|
|
1959
1978
|
try {
|
|
1960
|
-
db = openDb(dbPath);
|
|
1979
|
+
db = openDb(dbPath, { fileMustExist: true });
|
|
1961
1980
|
}
|
|
1962
1981
|
catch {
|
|
1963
1982
|
return text(JSON.stringify({ error: 'No monograph index found. Run monograph_build first.' }));
|
|
@@ -1997,9 +2016,12 @@ const monographDeadCodeTool = {
|
|
|
1997
2016
|
FROM nodes n
|
|
1998
2017
|
WHERE n.label = 'File'
|
|
1999
2018
|
AND (SELECT COUNT(*) FROM edges e WHERE e.target_id = n.id AND e.relation = 'IMPORTS') = 0
|
|
2000
|
-
AND n.file_path NOT LIKE '
|
|
2019
|
+
AND n.file_path NOT LIKE '%/test/%'
|
|
2020
|
+
AND n.file_path NOT LIKE '%/tests/%'
|
|
2021
|
+
AND n.file_path NOT LIKE '%.test.%'
|
|
2001
2022
|
AND n.file_path NOT LIKE '%__tests__%'
|
|
2002
|
-
AND n.file_path NOT LIKE '
|
|
2023
|
+
AND n.file_path NOT LIKE '%/spec/%'
|
|
2024
|
+
AND n.file_path NOT LIKE '%.spec.%'
|
|
2003
2025
|
AND n.file_path NOT LIKE '%/index.%'
|
|
2004
2026
|
AND n.file_path NOT LIKE 'bin/%'
|
|
2005
2027
|
AND n.file_path NOT LIKE 'scripts/%'
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, statSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { randomBytes } from 'node:crypto';
|
|
10
|
-
import {
|
|
10
|
+
import { getMonomindDataRoot, migrateLegacyStoreFile } from './types.js';
|
|
11
11
|
import { agentTools } from './agent-tools.js';
|
|
12
|
-
// Swarm state persistence
|
|
13
|
-
|
|
12
|
+
// Swarm state persistence — relative to the git-safe data root (see
|
|
13
|
+
// getMonomindDataRoot()), matching agent/task/hive-mind stores.
|
|
14
|
+
const SWARM_DIR = 'swarm';
|
|
14
15
|
const SWARM_STATE_FILE = 'swarm-state.json';
|
|
15
16
|
function getSwarmDir() {
|
|
16
|
-
return join(
|
|
17
|
+
return join(getMonomindDataRoot(), SWARM_DIR);
|
|
17
18
|
}
|
|
18
19
|
function getSwarmStatePath() {
|
|
19
20
|
return join(getSwarmDir(), SWARM_STATE_FILE);
|
|
@@ -28,6 +29,7 @@ const MAX_SWARM_STORE_BYTES = 10 * 1024 * 1024;
|
|
|
28
29
|
function loadSwarmStore() {
|
|
29
30
|
try {
|
|
30
31
|
const path = getSwarmStatePath();
|
|
32
|
+
migrateLegacyStoreFile(path, join(SWARM_DIR, SWARM_STATE_FILE));
|
|
31
33
|
if (existsSync(path)) {
|
|
32
34
|
if (statSync(path).size > MAX_SWARM_STORE_BYTES)
|
|
33
35
|
return { swarms: {}, version: '3.0.0' };
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - process.cpuUsage() for real CPU stats
|
|
9
9
|
* - os module for system information
|
|
10
10
|
*/
|
|
11
|
-
import { getProjectCwd } from './types.js';
|
|
11
|
+
import { getProjectCwd, getMonomindDataRoot } from './types.js';
|
|
12
12
|
import { existsSync, readFileSync, statSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
13
13
|
import { join, dirname } from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
@@ -184,7 +184,7 @@ export const systemTools = [
|
|
|
184
184
|
// Fallback: JSON store files (backward compatibility)
|
|
185
185
|
if (_metricsSource === 'none') {
|
|
186
186
|
try {
|
|
187
|
-
const agentStorePath = join(
|
|
187
|
+
const agentStorePath = join(getMonomindDataRoot(), 'agents', 'store.json');
|
|
188
188
|
if (existsSync(agentStorePath) && statSync(agentStorePath).size <= MAX_SYSTEM_STORE_BYTES) {
|
|
189
189
|
const agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
|
|
190
190
|
const agents = Object.values(agentStore.agents || {});
|
|
@@ -197,7 +197,7 @@ export const systemTools = [
|
|
|
197
197
|
}
|
|
198
198
|
catch { /* agent store not available */ }
|
|
199
199
|
try {
|
|
200
|
-
const taskStorePath = join(
|
|
200
|
+
const taskStorePath = join(getMonomindDataRoot(), 'tasks', 'store.json');
|
|
201
201
|
if (existsSync(taskStorePath) && statSync(taskStorePath).size <= MAX_SYSTEM_STORE_BYTES) {
|
|
202
202
|
const taskStore = JSON.parse(readFileSync(taskStorePath, 'utf-8'));
|
|
203
203
|
const tasks = Object.values(taskStore.tasks || {});
|
|
@@ -534,7 +534,7 @@ export const systemTools = [
|
|
|
534
534
|
},
|
|
535
535
|
handler: async () => {
|
|
536
536
|
// Read from the task store file
|
|
537
|
-
const storePath = join(
|
|
537
|
+
const storePath = join(getMonomindDataRoot(), 'tasks', 'store.json');
|
|
538
538
|
let tasks = [];
|
|
539
539
|
try {
|
|
540
540
|
if (existsSync(storePath) && statSync(storePath).size <= MAX_SYSTEM_STORE_BYTES) {
|
|
@@ -24,6 +24,26 @@ export interface MCPToolResult {
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function getProjectCwd(): string;
|
|
26
26
|
export declare function getMonomindDataRoot(cwd?: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* One-time migration for the agent/task/hive/swarm stores that historically lived
|
|
29
|
+
* under `<projectCwd>/.monomind/<subpath>` (via getProjectCwd()) before being
|
|
30
|
+
* consolidated onto the canonical getMonomindDataRoot() location (typically
|
|
31
|
+
* `<repo>/.git/monomind/<subpath>`). Several MCP tool files (agent-tools.ts,
|
|
32
|
+
* hive-mind-tools.ts, swarm-tools.ts, system-tools.ts) used to read/write the
|
|
33
|
+
* legacy path directly, causing the same logical store to physically split from
|
|
34
|
+
* task-tools.ts/session-tools.ts, which always used getMonomindDataRoot().
|
|
35
|
+
*
|
|
36
|
+
* If the canonical file is missing but the legacy file exists, copy (never move,
|
|
37
|
+
* for safety) the legacy file into place so pre-existing data isn't silently
|
|
38
|
+
* orphaned. Best-effort and idempotent — never throws, and it's a no-op once the
|
|
39
|
+
* canonical file exists or when the two paths already coincide (e.g. no .git).
|
|
40
|
+
*
|
|
41
|
+
* @param canonicalPath Absolute path under getMonomindDataRoot() the tool now reads from.
|
|
42
|
+
* @param legacySubpath Path relative to `.monomind/` that the tool used to read from
|
|
43
|
+
* (e.g. `join('agents', 'store.json')`).
|
|
44
|
+
* @param cwd Optional project cwd override (defaults to getProjectCwd()).
|
|
45
|
+
*/
|
|
46
|
+
export declare function migrateLegacyStoreFile(canonicalPath: string, legacySubpath: string, cwd?: string): void;
|
|
27
47
|
export interface MCPTool {
|
|
28
48
|
name: string;
|
|
29
49
|
description: string;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Local type definitions to avoid external imports outside package boundary.
|
|
5
5
|
*/
|
|
6
|
-
import { statSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { statSync, readFileSync, existsSync, mkdirSync, copyFileSync } from 'node:fs';
|
|
7
7
|
import { join, dirname, resolve } from 'node:path';
|
|
8
8
|
/**
|
|
9
9
|
* Returns the effective project working directory.
|
|
@@ -60,4 +60,39 @@ export function getMonomindDataRoot(cwd) {
|
|
|
60
60
|
_dataRootCache.set(workDir, result);
|
|
61
61
|
return result;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* One-time migration for the agent/task/hive/swarm stores that historically lived
|
|
65
|
+
* under `<projectCwd>/.monomind/<subpath>` (via getProjectCwd()) before being
|
|
66
|
+
* consolidated onto the canonical getMonomindDataRoot() location (typically
|
|
67
|
+
* `<repo>/.git/monomind/<subpath>`). Several MCP tool files (agent-tools.ts,
|
|
68
|
+
* hive-mind-tools.ts, swarm-tools.ts, system-tools.ts) used to read/write the
|
|
69
|
+
* legacy path directly, causing the same logical store to physically split from
|
|
70
|
+
* task-tools.ts/session-tools.ts, which always used getMonomindDataRoot().
|
|
71
|
+
*
|
|
72
|
+
* If the canonical file is missing but the legacy file exists, copy (never move,
|
|
73
|
+
* for safety) the legacy file into place so pre-existing data isn't silently
|
|
74
|
+
* orphaned. Best-effort and idempotent — never throws, and it's a no-op once the
|
|
75
|
+
* canonical file exists or when the two paths already coincide (e.g. no .git).
|
|
76
|
+
*
|
|
77
|
+
* @param canonicalPath Absolute path under getMonomindDataRoot() the tool now reads from.
|
|
78
|
+
* @param legacySubpath Path relative to `.monomind/` that the tool used to read from
|
|
79
|
+
* (e.g. `join('agents', 'store.json')`).
|
|
80
|
+
* @param cwd Optional project cwd override (defaults to getProjectCwd()).
|
|
81
|
+
*/
|
|
82
|
+
export function migrateLegacyStoreFile(canonicalPath, legacySubpath, cwd) {
|
|
83
|
+
try {
|
|
84
|
+
if (existsSync(canonicalPath))
|
|
85
|
+
return;
|
|
86
|
+
const legacyPath = join(cwd || getProjectCwd(), '.monomind', legacySubpath);
|
|
87
|
+
if (legacyPath === canonicalPath)
|
|
88
|
+
return;
|
|
89
|
+
if (!existsSync(legacyPath))
|
|
90
|
+
return;
|
|
91
|
+
mkdirSync(dirname(canonicalPath), { recursive: true });
|
|
92
|
+
copyFileSync(legacyPath, canonicalPath);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Best-effort; leave both stores as-is on any failure.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
63
98
|
//# sourceMappingURL=types.js.map
|
|
@@ -10,8 +10,20 @@ interface HNSWEntry {
|
|
|
10
10
|
namespace: string;
|
|
11
11
|
content: string;
|
|
12
12
|
}
|
|
13
|
+
/** Minimal shape of the real @monoes/memory HNSWIndex class relied on here. */
|
|
14
|
+
interface RealHNSWIndex {
|
|
15
|
+
addPoint(id: string, vector: Float32Array): Promise<void>;
|
|
16
|
+
search(query: Float32Array, k: number, ef?: number): Promise<Array<{
|
|
17
|
+
id: string;
|
|
18
|
+
distance: number;
|
|
19
|
+
}>>;
|
|
20
|
+
rebuild(entries: Array<{
|
|
21
|
+
id: string;
|
|
22
|
+
vector: Float32Array;
|
|
23
|
+
}>): Promise<void>;
|
|
24
|
+
}
|
|
13
25
|
interface HNSWIndex {
|
|
14
|
-
|
|
26
|
+
instance: RealHNSWIndex;
|
|
15
27
|
entries: Map<string, HNSWEntry>;
|
|
16
28
|
dimensions: number;
|
|
17
29
|
initialized: boolean;
|
|
@@ -24,14 +24,59 @@ async function getBridge() {
|
|
|
24
24
|
}
|
|
25
25
|
let hnswIndex = null;
|
|
26
26
|
let hnswInitializing = false;
|
|
27
|
+
/** Lazily import the pure-JS HNSWIndex class from the optional @monoes/memory package. */
|
|
28
|
+
async function loadHNSWIndexClass() {
|
|
29
|
+
try {
|
|
30
|
+
const mod = await import('@monoes/memory');
|
|
31
|
+
return mod?.HNSWIndex ?? null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Read entries with parseable embeddings of the expected dimension from the sql.js DB. */
|
|
38
|
+
async function loadEntriesFromDb(dbPath, dimensions) {
|
|
39
|
+
if (!fs.existsSync(dbPath))
|
|
40
|
+
return [];
|
|
41
|
+
const initSqlJs = (await import('sql.js')).default;
|
|
42
|
+
const SQL = await initSqlJs();
|
|
43
|
+
const fileBuffer = fs.readFileSync(dbPath);
|
|
44
|
+
const db = new SQL.Database(fileBuffer);
|
|
45
|
+
try {
|
|
46
|
+
const { safeParseEmbedding } = await import('./memory-bridge.js');
|
|
47
|
+
const stmt = db.prepare(`SELECT id, key, namespace, content, embedding FROM memory_entries WHERE status = 'active' AND embedding IS NOT NULL LIMIT 50000`);
|
|
48
|
+
const rows = [];
|
|
49
|
+
while (stmt.step())
|
|
50
|
+
rows.push(stmt.get());
|
|
51
|
+
stmt.free();
|
|
52
|
+
const results = [];
|
|
53
|
+
for (const row of rows) {
|
|
54
|
+
const [id, key, namespace, content, embeddingJson] = row;
|
|
55
|
+
const parsed = safeParseEmbedding(embeddingJson);
|
|
56
|
+
if (!parsed || parsed.length !== dimensions)
|
|
57
|
+
continue;
|
|
58
|
+
results.push({
|
|
59
|
+
id,
|
|
60
|
+
vector: new Float32Array(parsed),
|
|
61
|
+
entry: { id, key: key || id, namespace: namespace || 'default', content: content || '' },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
db.close();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
27
70
|
/**
|
|
28
71
|
* Get or create the HNSW index singleton
|
|
29
72
|
* Lazily initializes from SQLite data on first use
|
|
30
73
|
*/
|
|
31
74
|
export async function getHNSWIndex(options) {
|
|
32
75
|
const dimensions = options?.dimensions ?? 384;
|
|
33
|
-
// Return existing index if already initialized
|
|
34
|
-
|
|
76
|
+
// Return existing index if already initialized for the same dimensionality.
|
|
77
|
+
// A cached index built for a different embedding size must be rebuilt —
|
|
78
|
+
// otherwise addPoint()/search() silently fail (or corrupt) on the mismatch.
|
|
79
|
+
if (hnswIndex?.initialized && hnswIndex.dimensions === dimensions && !options?.forceRebuild) {
|
|
35
80
|
return hnswIndex;
|
|
36
81
|
}
|
|
37
82
|
// Prevent concurrent initialization
|
|
@@ -50,13 +95,23 @@ export async function getHNSWIndex(options) {
|
|
|
50
95
|
}
|
|
51
96
|
hnswInitializing = true;
|
|
52
97
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
98
|
+
const HNSWIndexClass = await loadHNSWIndexClass();
|
|
99
|
+
if (!HNSWIndexClass) {
|
|
100
|
+
// @monoes/memory isn't installed — no pure-JS fallback available;
|
|
101
|
+
// callers fall through to brute-force search.
|
|
102
|
+
hnswInitializing = false;
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const dbPath = options?.dbPath ?? path.join(process.cwd(), '.swarm', 'memory.db');
|
|
106
|
+
const loaded = await loadEntriesFromDb(dbPath, dimensions);
|
|
107
|
+
const instance = new HNSWIndexClass({ dimensions });
|
|
108
|
+
await instance.rebuild(loaded.map((l) => ({ id: l.id, vector: l.vector })));
|
|
109
|
+
const entries = new Map();
|
|
110
|
+
for (const l of loaded)
|
|
111
|
+
entries.set(l.id, l.entry);
|
|
112
|
+
hnswIndex = { instance, entries, dimensions, initialized: true };
|
|
58
113
|
hnswInitializing = false;
|
|
59
|
-
return
|
|
114
|
+
return hnswIndex;
|
|
60
115
|
}
|
|
61
116
|
catch {
|
|
62
117
|
hnswInitializing = false;
|
|
@@ -90,10 +145,7 @@ export async function addToHNSWIndex(id, embedding, entry) {
|
|
|
90
145
|
return false;
|
|
91
146
|
try {
|
|
92
147
|
const vector = new Float32Array(embedding);
|
|
93
|
-
await index.
|
|
94
|
-
id,
|
|
95
|
-
vector
|
|
96
|
-
});
|
|
148
|
+
await index.instance.addPoint(id, vector);
|
|
97
149
|
index.entries.set(id, entry);
|
|
98
150
|
// Save metadata for persistence (debounced would be better for high-volume)
|
|
99
151
|
saveHNSWMetadata();
|
|
@@ -115,7 +167,7 @@ export async function searchHNSWIndex(queryEmbedding, options) {
|
|
|
115
167
|
const vector = new Float32Array(queryEmbedding);
|
|
116
168
|
const k = options?.k ?? 10;
|
|
117
169
|
// HNSW search returns results with cosine distance (lower = more similar)
|
|
118
|
-
const results = await index.
|
|
170
|
+
const results = await index.instance.search(vector, k * 2); // Get extra for filtering
|
|
119
171
|
const filtered = [];
|
|
120
172
|
for (const result of results) {
|
|
121
173
|
const entry = index.entries.get(result.id);
|
|
@@ -127,7 +179,7 @@ export async function searchHNSWIndex(queryEmbedding, options) {
|
|
|
127
179
|
}
|
|
128
180
|
// Convert cosine distance to similarity score (1 - distance)
|
|
129
181
|
// Cosine distance convention: 0 = identical, 2 = opposite
|
|
130
|
-
const score = 1 - (result.
|
|
182
|
+
const score = 1 - (result.distance / 2);
|
|
131
183
|
filtered.push({
|
|
132
184
|
id: entry.id.substring(0, 12),
|
|
133
185
|
key: entry.key || entry.id.substring(0, 15),
|