cntx-ui 3.1.2 ā 3.1.4
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/README.md +9 -0
- package/dist/bin/cntx-ui.js +7 -2
- package/dist/lib/api-router.js +119 -0
- package/dist/lib/artifact-manager.js +147 -0
- package/dist/lib/bundle-manager.js +4 -0
- package/dist/lib/database-manager.js +48 -0
- package/dist/lib/mcp-server.js +42 -0
- package/dist/lib/semantic-splitter.js +9 -1
- package/dist/lib/simple-vector-store.js +15 -4
- package/dist/server.js +65 -29
- package/package.json +2 -1
- package/web/dist/assets/index-CZ-DWr8c.css +1 -0
- package/web/dist/assets/index-DAunWqbX.js +344 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-8QXMnXVq.js +0 -1968
- package/web/dist/assets/index-DJi03HLz.css +0 -1
package/README.md
CHANGED
|
@@ -40,6 +40,10 @@ Agents interact through MCP tools or the HTTP API:
|
|
|
40
40
|
| `agent/query` | Semantic search ā "where is auth handled?" |
|
|
41
41
|
| `agent/investigate` | Find integration points for a new feature |
|
|
42
42
|
| `agent/organize` | Audit and optimize bundle structure |
|
|
43
|
+
| `artifacts/list` | List normalized project artifacts (OpenAPI + Navigation) |
|
|
44
|
+
| `artifacts/get_openapi` | Return OpenAPI artifact summary and payload |
|
|
45
|
+
| `artifacts/get_navigation` | Return Navigation artifact summary and payload |
|
|
46
|
+
| `artifacts/summarize` | Compact cross-artifact summary for agents |
|
|
43
47
|
| `list_bundles` | List all bundles with metadata |
|
|
44
48
|
| `get_bundle` | Get full bundle content as XML |
|
|
45
49
|
| `get_semantic_chunks` | Get all analyzed code chunks |
|
|
@@ -47,6 +51,11 @@ Agents interact through MCP tools or the HTTP API:
|
|
|
47
51
|
|
|
48
52
|
Full tool reference with parameters is generated in `.cntx/AGENT.md` and `.cntx/TOOLS.md`.
|
|
49
53
|
|
|
54
|
+
Artifact HTTP endpoints:
|
|
55
|
+
- `GET /api/artifacts`
|
|
56
|
+
- `GET /api/artifacts/openapi`
|
|
57
|
+
- `GET /api/artifacts/navigation`
|
|
58
|
+
|
|
50
59
|
## How it works
|
|
51
60
|
|
|
52
61
|
1. **tree-sitter** parses source files into AST, extracts functions/types/interfaces
|
package/dist/bin/cntx-ui.js
CHANGED
|
@@ -14,7 +14,12 @@ const command = args[0] || 'help';
|
|
|
14
14
|
const isVerbose = args.includes('--verbose');
|
|
15
15
|
// Graceful shutdown
|
|
16
16
|
process.on('SIGINT', () => {
|
|
17
|
-
|
|
17
|
+
if (command === 'mcp') {
|
|
18
|
+
process.stderr.write('\nš Shutting down cntx-ui...\n');
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.log('\nš Shutting down cntx-ui...');
|
|
22
|
+
}
|
|
18
23
|
process.exit(0);
|
|
19
24
|
});
|
|
20
25
|
async function main() {
|
|
@@ -32,7 +37,7 @@ async function main() {
|
|
|
32
37
|
await initConfig();
|
|
33
38
|
break;
|
|
34
39
|
case 'mcp':
|
|
35
|
-
await startServer({ withMcp: true, skipFileWatcher: true,
|
|
40
|
+
await startServer({ withMcp: true, skipFileWatcher: true, isMcp: true });
|
|
36
41
|
break;
|
|
37
42
|
case 'bundle':
|
|
38
43
|
const bundleName = args[1] || 'master';
|
package/dist/lib/api-router.js
CHANGED
|
@@ -81,6 +81,9 @@ export default class APIRouter {
|
|
|
81
81
|
if (pathname === '/api/vector-db/search' && method === 'POST') {
|
|
82
82
|
return await this.handlePostVectorDbSearch(req, res);
|
|
83
83
|
}
|
|
84
|
+
if (pathname === '/api/vector-db/projection' && method === 'GET') {
|
|
85
|
+
return await this.handleGetVectorDbProjection(req, res);
|
|
86
|
+
}
|
|
84
87
|
if (pathname === '/api/vector-db/network' && method === 'GET') {
|
|
85
88
|
return await this.handleGetVectorDbNetwork(req, res);
|
|
86
89
|
}
|
|
@@ -106,6 +109,16 @@ export default class APIRouter {
|
|
|
106
109
|
if (pathname === '/api/mcp-status' && method === 'GET') {
|
|
107
110
|
return await this.handleGetMcpStatus(req, res);
|
|
108
111
|
}
|
|
112
|
+
// === Artifact Endpoints ===
|
|
113
|
+
if (pathname === '/api/artifacts' && method === 'GET') {
|
|
114
|
+
return await this.handleGetArtifacts(req, res);
|
|
115
|
+
}
|
|
116
|
+
if (pathname === '/api/artifacts/openapi' && method === 'GET') {
|
|
117
|
+
return await this.handleGetArtifact(req, res, 'openapi');
|
|
118
|
+
}
|
|
119
|
+
if (pathname === '/api/artifacts/navigation' && method === 'GET') {
|
|
120
|
+
return await this.handleGetArtifact(req, res, 'navigation');
|
|
121
|
+
}
|
|
109
122
|
// === Rule Management ===
|
|
110
123
|
if (pathname === '/api/cursor-rules' && method === 'GET') {
|
|
111
124
|
return await this.handleGetCursorRules(req, res);
|
|
@@ -301,6 +314,103 @@ export default class APIRouter {
|
|
|
301
314
|
const results = await this.vectorStore.search(query, { limit });
|
|
302
315
|
this.sendResponse(res, 200, results);
|
|
303
316
|
}
|
|
317
|
+
async handleGetVectorDbProjection(req, res) {
|
|
318
|
+
const dbManager = this.configManager.dbManager;
|
|
319
|
+
const embeddingCountRow = dbManager.db.prepare('SELECT COUNT(*) as count FROM vector_embeddings').get();
|
|
320
|
+
const currentEmbeddingCount = embeddingCountRow.count;
|
|
321
|
+
if (currentEmbeddingCount === 0) {
|
|
322
|
+
return this.sendResponse(res, 200, {
|
|
323
|
+
points: [],
|
|
324
|
+
meta: { totalPoints: 0, embeddingCount: 0, computedAt: null, cached: false }
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
// Check cache freshness
|
|
328
|
+
const cachedCount = dbManager.getProjectionEmbeddingCount();
|
|
329
|
+
if (cachedCount === currentEmbeddingCount) {
|
|
330
|
+
const cached = dbManager.getProjections();
|
|
331
|
+
if (cached) {
|
|
332
|
+
// Join with chunk metadata
|
|
333
|
+
const chunkMap = new Map();
|
|
334
|
+
const chunks = dbManager.db.prepare('SELECT * FROM semantic_chunks').all();
|
|
335
|
+
for (const c of chunks) {
|
|
336
|
+
const mapped = dbManager.mapChunkRow(c);
|
|
337
|
+
chunkMap.set(mapped.id, mapped);
|
|
338
|
+
}
|
|
339
|
+
const points = cached.map(p => {
|
|
340
|
+
const chunk = chunkMap.get(p.chunkId);
|
|
341
|
+
return {
|
|
342
|
+
id: p.chunkId,
|
|
343
|
+
x: p.x,
|
|
344
|
+
y: p.y,
|
|
345
|
+
name: chunk?.name || 'unknown',
|
|
346
|
+
filePath: chunk?.filePath || '',
|
|
347
|
+
purpose: chunk?.purpose || 'unknown',
|
|
348
|
+
semanticType: chunk?.subtype || chunk?.type || 'unknown',
|
|
349
|
+
complexity: chunk?.complexity?.score || 0,
|
|
350
|
+
directory: chunk?.filePath ? chunk.filePath.split('/').slice(0, -1).join('/') || '.' : '.'
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
return this.sendResponse(res, 200, {
|
|
354
|
+
points,
|
|
355
|
+
meta: { totalPoints: points.length, embeddingCount: currentEmbeddingCount, computedAt: new Date().toISOString(), cached: true }
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Compute fresh UMAP projection
|
|
360
|
+
const rows = dbManager.db.prepare(`
|
|
361
|
+
SELECT ve.chunk_id, ve.embedding, sc.name, sc.file_path, sc.type, sc.subtype, sc.complexity_score, sc.purpose
|
|
362
|
+
FROM vector_embeddings ve
|
|
363
|
+
JOIN semantic_chunks sc ON ve.chunk_id = sc.id
|
|
364
|
+
`).all();
|
|
365
|
+
if (rows.length < 2) {
|
|
366
|
+
return this.sendResponse(res, 200, {
|
|
367
|
+
points: rows.map((r) => ({
|
|
368
|
+
id: r.chunk_id, x: 0, y: 0,
|
|
369
|
+
name: r.name, filePath: r.file_path, purpose: r.purpose || 'unknown',
|
|
370
|
+
semanticType: r.subtype || r.type || 'unknown', complexity: r.complexity_score || 0,
|
|
371
|
+
directory: r.file_path ? r.file_path.split('/').slice(0, -1).join('/') || '.' : '.'
|
|
372
|
+
})),
|
|
373
|
+
meta: { totalPoints: rows.length, embeddingCount: currentEmbeddingCount, computedAt: new Date().toISOString(), cached: false }
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
// Extract embeddings as number[][]
|
|
377
|
+
const embeddings = rows.map((r) => {
|
|
378
|
+
const buf = r.embedding;
|
|
379
|
+
const floats = new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
380
|
+
return Array.from(floats);
|
|
381
|
+
});
|
|
382
|
+
// Run UMAP
|
|
383
|
+
const { UMAP } = await import('umap-js');
|
|
384
|
+
const umap = new UMAP({
|
|
385
|
+
nNeighbors: Math.min(15, rows.length - 1),
|
|
386
|
+
minDist: 0.1,
|
|
387
|
+
nComponents: 2
|
|
388
|
+
});
|
|
389
|
+
const projected = umap.fit(embeddings);
|
|
390
|
+
// Save to cache
|
|
391
|
+
const projections = rows.map((r, i) => ({
|
|
392
|
+
chunkId: r.chunk_id,
|
|
393
|
+
x: projected[i][0],
|
|
394
|
+
y: projected[i][1]
|
|
395
|
+
}));
|
|
396
|
+
dbManager.saveProjections(projections, currentEmbeddingCount);
|
|
397
|
+
// Build response
|
|
398
|
+
const points = rows.map((r, i) => ({
|
|
399
|
+
id: r.chunk_id,
|
|
400
|
+
x: projected[i][0],
|
|
401
|
+
y: projected[i][1],
|
|
402
|
+
name: r.name,
|
|
403
|
+
filePath: r.file_path,
|
|
404
|
+
purpose: r.purpose || 'unknown',
|
|
405
|
+
semanticType: r.subtype || r.type || 'unknown',
|
|
406
|
+
complexity: r.complexity_score || 0,
|
|
407
|
+
directory: r.file_path ? r.file_path.split('/').slice(0, -1).join('/') || '.' : '.'
|
|
408
|
+
}));
|
|
409
|
+
this.sendResponse(res, 200, {
|
|
410
|
+
points,
|
|
411
|
+
meta: { totalPoints: points.length, embeddingCount: currentEmbeddingCount, computedAt: new Date().toISOString(), cached: false }
|
|
412
|
+
});
|
|
413
|
+
}
|
|
304
414
|
async handleGetVectorDbNetwork(req, res) {
|
|
305
415
|
const chunks = this.configManager.dbManager.db.prepare('SELECT * FROM semantic_chunks').all();
|
|
306
416
|
const embeddings = this.configManager.dbManager.db.prepare('SELECT * FROM vector_embeddings').all();
|
|
@@ -350,6 +460,15 @@ export default class APIRouter {
|
|
|
350
460
|
message: isRunning ? 'MCP server is running' : 'MCP server integration available'
|
|
351
461
|
});
|
|
352
462
|
}
|
|
463
|
+
async handleGetArtifacts(req, res) {
|
|
464
|
+
const artifacts = this.cntxServer.artifactManager.refresh();
|
|
465
|
+
this.sendResponse(res, 200, { artifacts });
|
|
466
|
+
}
|
|
467
|
+
async handleGetArtifact(req, res, type) {
|
|
468
|
+
this.cntxServer.artifactManager.refresh();
|
|
469
|
+
const payload = this.cntxServer.artifactManager.getPayload(type);
|
|
470
|
+
this.sendResponse(res, 200, payload);
|
|
471
|
+
}
|
|
353
472
|
async handleGetCursorRules(req, res) {
|
|
354
473
|
const filePath = join(this.configManager.CWD, '.cursorrules');
|
|
355
474
|
if (existsSync(filePath)) {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
export default class ArtifactManager {
|
|
4
|
+
cwd;
|
|
5
|
+
records;
|
|
6
|
+
constructor(cwd) {
|
|
7
|
+
this.cwd = cwd;
|
|
8
|
+
this.records = new Map();
|
|
9
|
+
}
|
|
10
|
+
refresh() {
|
|
11
|
+
const openapi = this.resolveOpenApiArtifact();
|
|
12
|
+
const navigation = this.resolveNavigationArtifact();
|
|
13
|
+
this.records.set('openapi', openapi);
|
|
14
|
+
this.records.set('navigation', navigation);
|
|
15
|
+
return this.list();
|
|
16
|
+
}
|
|
17
|
+
list() {
|
|
18
|
+
if (this.records.size === 0) {
|
|
19
|
+
this.refresh();
|
|
20
|
+
}
|
|
21
|
+
return Array.from(this.records.values());
|
|
22
|
+
}
|
|
23
|
+
get(type) {
|
|
24
|
+
if (!this.records.has(type)) {
|
|
25
|
+
this.refresh();
|
|
26
|
+
}
|
|
27
|
+
return this.records.get(type);
|
|
28
|
+
}
|
|
29
|
+
getPayload(type) {
|
|
30
|
+
const record = this.get(type);
|
|
31
|
+
if (!record) {
|
|
32
|
+
return {
|
|
33
|
+
type,
|
|
34
|
+
filePath: '',
|
|
35
|
+
format: 'json',
|
|
36
|
+
exists: false,
|
|
37
|
+
summary: {}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (!record.exists) {
|
|
41
|
+
return { ...record };
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const absolutePath = join(this.cwd, record.filePath);
|
|
45
|
+
const raw = readFileSync(absolutePath, 'utf8');
|
|
46
|
+
if (record.format === 'json') {
|
|
47
|
+
return { ...record, parsed: JSON.parse(raw), raw };
|
|
48
|
+
}
|
|
49
|
+
return { ...record, raw };
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { ...record };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
resolveOpenApiArtifact() {
|
|
56
|
+
const candidates = [
|
|
57
|
+
{ path: 'openapi.json', format: 'json' },
|
|
58
|
+
{ path: 'openapi.yaml', format: 'yaml' },
|
|
59
|
+
{ path: 'openapi.yml', format: 'yaml' }
|
|
60
|
+
];
|
|
61
|
+
for (const candidate of candidates) {
|
|
62
|
+
const absolutePath = join(this.cwd, candidate.path);
|
|
63
|
+
if (!existsSync(absolutePath))
|
|
64
|
+
continue;
|
|
65
|
+
const summary = this.summarizeOpenApi(absolutePath, candidate.format);
|
|
66
|
+
return {
|
|
67
|
+
type: 'openapi',
|
|
68
|
+
filePath: candidate.path,
|
|
69
|
+
format: candidate.format,
|
|
70
|
+
exists: true,
|
|
71
|
+
summary
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
type: 'openapi',
|
|
76
|
+
filePath: 'openapi.json',
|
|
77
|
+
format: 'json',
|
|
78
|
+
exists: false,
|
|
79
|
+
summary: {}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
resolveNavigationArtifact() {
|
|
83
|
+
const candidates = [
|
|
84
|
+
{ path: 'navigation.manifest.json', format: 'json' },
|
|
85
|
+
{ path: 'navigation.json', format: 'json' }
|
|
86
|
+
];
|
|
87
|
+
for (const candidate of candidates) {
|
|
88
|
+
const absolutePath = join(this.cwd, candidate.path);
|
|
89
|
+
if (!existsSync(absolutePath))
|
|
90
|
+
continue;
|
|
91
|
+
const summary = this.summarizeNavigation(absolutePath);
|
|
92
|
+
return {
|
|
93
|
+
type: 'navigation',
|
|
94
|
+
filePath: candidate.path,
|
|
95
|
+
format: candidate.format,
|
|
96
|
+
exists: true,
|
|
97
|
+
summary
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
type: 'navigation',
|
|
102
|
+
filePath: 'navigation.manifest.json',
|
|
103
|
+
format: 'json',
|
|
104
|
+
exists: false,
|
|
105
|
+
summary: {}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
summarizeOpenApi(absolutePath, format) {
|
|
109
|
+
try {
|
|
110
|
+
const raw = readFileSync(absolutePath, 'utf8');
|
|
111
|
+
if (format === 'json') {
|
|
112
|
+
const parsed = JSON.parse(raw);
|
|
113
|
+
const endpointCount = parsed.paths ? Object.keys(parsed.paths).length : 0;
|
|
114
|
+
return {
|
|
115
|
+
title: parsed.info?.title,
|
|
116
|
+
version: parsed.info?.version,
|
|
117
|
+
endpointCount
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const titleMatch = raw.match(/^\s*title:\s*["']?(.+?)["']?\s*$/m);
|
|
121
|
+
const versionMatch = raw.match(/^\s*version:\s*["']?(.+?)["']?\s*$/m);
|
|
122
|
+
return {
|
|
123
|
+
title: titleMatch?.[1],
|
|
124
|
+
version: versionMatch?.[1]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return {};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
summarizeNavigation(absolutePath) {
|
|
132
|
+
try {
|
|
133
|
+
const parsed = JSON.parse(readFileSync(absolutePath, 'utf8'));
|
|
134
|
+
return {
|
|
135
|
+
title: parsed.project?.name || parsed.name,
|
|
136
|
+
version: parsed.version ? String(parsed.version) : undefined,
|
|
137
|
+
routeCount: Array.isArray(parsed.routes) ? parsed.routes.length : 0,
|
|
138
|
+
stateCount: Array.isArray(parsed.states) ? parsed.states.length : 0,
|
|
139
|
+
flowCount: Array.isArray(parsed.flows) ? parsed.flows.length : 0,
|
|
140
|
+
viewportCount: Array.isArray(parsed.viewports) ? parsed.viewports.length : 0
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -170,6 +170,10 @@ export default class BundleManager {
|
|
|
170
170
|
try {
|
|
171
171
|
const fullPath = this.fileSystemManager.fullPath(relativePath);
|
|
172
172
|
const content = readFileSync(fullPath, 'utf8');
|
|
173
|
+
// Skip files over 100KB in bundle XML to prevent string length crashes
|
|
174
|
+
if (content.length > 100_000) {
|
|
175
|
+
return ` <file path="${this.escapeXml(relativePath)}" skipped="too-large" size="${content.length}" />\n`;
|
|
176
|
+
}
|
|
173
177
|
const chunks = this.db.getChunksByFile(relativePath);
|
|
174
178
|
let xml = ` <file path="${this.escapeXml(relativePath)}">\n`;
|
|
175
179
|
if (chunks.length > 0) {
|
|
@@ -79,6 +79,16 @@ export default class DatabaseManager {
|
|
|
79
79
|
FOREIGN KEY(session_id) REFERENCES agent_sessions(id) ON DELETE CASCADE
|
|
80
80
|
);
|
|
81
81
|
|
|
82
|
+
-- UMAP Projection Cache
|
|
83
|
+
CREATE TABLE IF NOT EXISTS umap_projections (
|
|
84
|
+
chunk_id TEXT PRIMARY KEY,
|
|
85
|
+
x REAL NOT NULL,
|
|
86
|
+
y REAL NOT NULL,
|
|
87
|
+
computed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
88
|
+
embedding_count INTEGER NOT NULL,
|
|
89
|
+
FOREIGN KEY(chunk_id) REFERENCES semantic_chunks(id) ON DELETE CASCADE
|
|
90
|
+
);
|
|
91
|
+
|
|
82
92
|
CREATE INDEX IF NOT EXISTS idx_bundles_changed ON bundles(changed);
|
|
83
93
|
CREATE INDEX IF NOT EXISTS idx_chunks_file ON semantic_chunks(file_path);
|
|
84
94
|
CREATE INDEX IF NOT EXISTS idx_chunks_purpose ON semantic_chunks(purpose);
|
|
@@ -265,6 +275,44 @@ export default class DatabaseManager {
|
|
|
265
275
|
return [];
|
|
266
276
|
}
|
|
267
277
|
}
|
|
278
|
+
// UMAP Projection Cache
|
|
279
|
+
saveProjections(projections, embeddingCount) {
|
|
280
|
+
const transaction = this.db.transaction(() => {
|
|
281
|
+
this.db.prepare('DELETE FROM umap_projections').run();
|
|
282
|
+
const stmt = this.db.prepare('INSERT INTO umap_projections (chunk_id, x, y, embedding_count) VALUES (?, ?, ?, ?)');
|
|
283
|
+
for (const p of projections) {
|
|
284
|
+
stmt.run(p.chunkId, p.x, p.y, embeddingCount);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
try {
|
|
288
|
+
transaction();
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
console.error('Failed to save projections:', error.message);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
getProjections() {
|
|
297
|
+
try {
|
|
298
|
+
const rows = this.db.prepare('SELECT chunk_id, x, y, embedding_count FROM umap_projections').all();
|
|
299
|
+
if (rows.length === 0)
|
|
300
|
+
return null;
|
|
301
|
+
return rows.map(r => ({ chunkId: r.chunk_id, x: r.x, y: r.y, embeddingCount: r.embedding_count }));
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
getProjectionEmbeddingCount() {
|
|
308
|
+
try {
|
|
309
|
+
const row = this.db.prepare('SELECT embedding_count FROM umap_projections LIMIT 1').get();
|
|
310
|
+
return row?.embedding_count ?? 0;
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
268
316
|
// Close database connection
|
|
269
317
|
close() {
|
|
270
318
|
if (this.db) {
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -92,6 +92,28 @@ export class MCPServer {
|
|
|
92
92
|
}
|
|
93
93
|
// Legacy tools mapping
|
|
94
94
|
switch (name) {
|
|
95
|
+
case 'artifacts/list': {
|
|
96
|
+
const artifacts = this.cntxServer.artifactManager.refresh();
|
|
97
|
+
return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify({ artifacts }, null, 2) }] });
|
|
98
|
+
}
|
|
99
|
+
case 'artifacts/get_openapi': {
|
|
100
|
+
this.cntxServer.artifactManager.refresh();
|
|
101
|
+
const payload = this.cntxServer.artifactManager.getPayload('openapi');
|
|
102
|
+
return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] });
|
|
103
|
+
}
|
|
104
|
+
case 'artifacts/get_navigation': {
|
|
105
|
+
this.cntxServer.artifactManager.refresh();
|
|
106
|
+
const payload = this.cntxServer.artifactManager.getPayload('navigation');
|
|
107
|
+
return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] });
|
|
108
|
+
}
|
|
109
|
+
case 'artifacts/summarize': {
|
|
110
|
+
const artifacts = this.cntxServer.artifactManager.refresh();
|
|
111
|
+
const summary = {
|
|
112
|
+
openapi: artifacts.find((a) => a.type === 'openapi')?.summary ?? {},
|
|
113
|
+
navigation: artifacts.find((a) => a.type === 'navigation')?.summary ?? {}
|
|
114
|
+
};
|
|
115
|
+
return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] });
|
|
116
|
+
}
|
|
95
117
|
case 'list_bundles':
|
|
96
118
|
const bundles = this.cntxServer.bundleManager.getAllBundleInfo();
|
|
97
119
|
return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(bundles, null, 2) }] });
|
|
@@ -144,6 +166,26 @@ export class MCPServer {
|
|
|
144
166
|
description: 'Investigation Mode: Suggest integration points.',
|
|
145
167
|
inputSchema: { type: 'object', properties: { feature: { type: 'string' } }, required: ['feature'] }
|
|
146
168
|
},
|
|
169
|
+
{
|
|
170
|
+
name: 'artifacts/list',
|
|
171
|
+
description: 'List normalized project artifacts (OpenAPI and Navigation manifests).',
|
|
172
|
+
inputSchema: { type: 'object', properties: {} }
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'artifacts/get_openapi',
|
|
176
|
+
description: 'Get OpenAPI artifact payload (summary + parsed content when JSON).',
|
|
177
|
+
inputSchema: { type: 'object', properties: {} }
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'artifacts/get_navigation',
|
|
181
|
+
description: 'Get Navigation artifact payload (summary + parsed manifest).',
|
|
182
|
+
inputSchema: { type: 'object', properties: {} }
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'artifacts/summarize',
|
|
186
|
+
description: 'Get compact summaries for OpenAPI and Navigation artifacts.',
|
|
187
|
+
inputSchema: { type: 'object', properties: {} }
|
|
188
|
+
},
|
|
147
189
|
{
|
|
148
190
|
name: 'list_bundles',
|
|
149
191
|
description: 'List all project bundles.',
|
|
@@ -106,6 +106,11 @@ export default class SemanticSplitter {
|
|
|
106
106
|
if (!existsSync(fullPath))
|
|
107
107
|
return [];
|
|
108
108
|
const content = readFileSync(fullPath, 'utf8');
|
|
109
|
+
// Skip files larger than 200KB ā tree-sitter and embeddings can't handle them well
|
|
110
|
+
if (content.length > 200_000) {
|
|
111
|
+
console.warn(`Skipping ${relativePath}: file too large (${Math.round(content.length / 1024)}KB)`);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
109
114
|
const parser = this.getParser(relativePath);
|
|
110
115
|
const tree = parser.parse(content);
|
|
111
116
|
const root = tree.rootNode;
|
|
@@ -394,7 +399,10 @@ export default class SemanticSplitter {
|
|
|
394
399
|
if (!node)
|
|
395
400
|
continue;
|
|
396
401
|
if (node.type === 'table' || node.type === 'table_array_element' || node.type === 'pair') {
|
|
397
|
-
|
|
402
|
+
// Legacy parser uses namedChild instead of childForFieldName
|
|
403
|
+
const keyNode = node.childForFieldName?.('name')
|
|
404
|
+
|| node.childForFieldName?.('key')
|
|
405
|
+
|| node.namedChild(0);
|
|
398
406
|
const name = keyNode ? content.slice(keyNode.startIndex, keyNode.endIndex) : node.type;
|
|
399
407
|
const structure = this.mapStructureNode(name, node, content, filePath);
|
|
400
408
|
if (structure.code.length >= this.options.minStructureSize) {
|
|
@@ -9,19 +9,29 @@ export default class SimpleVectorStore {
|
|
|
9
9
|
modelName;
|
|
10
10
|
pipe;
|
|
11
11
|
initialized;
|
|
12
|
+
isMcp;
|
|
12
13
|
constructor(databaseManager, options = {}) {
|
|
13
14
|
this.db = databaseManager;
|
|
14
15
|
this.modelName = options.modelName || 'Xenova/all-MiniLM-L6-v2';
|
|
15
16
|
this.pipe = null;
|
|
16
17
|
this.initialized = false;
|
|
18
|
+
this.isMcp = options.isMcp || false;
|
|
19
|
+
}
|
|
20
|
+
log(message) {
|
|
21
|
+
if (this.isMcp) {
|
|
22
|
+
process.stderr.write(message + '\n');
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.log(message);
|
|
26
|
+
}
|
|
17
27
|
}
|
|
18
28
|
async init() {
|
|
19
29
|
if (this.initialized)
|
|
20
30
|
return;
|
|
21
|
-
|
|
31
|
+
this.log(`š¤ Initializing local RAG engine (${this.modelName})...`);
|
|
22
32
|
this.pipe = await pipeline('feature-extraction', this.modelName);
|
|
23
33
|
this.initialized = true;
|
|
24
|
-
|
|
34
|
+
this.log('ā
Local RAG engine ready');
|
|
25
35
|
}
|
|
26
36
|
async generateEmbedding(text) {
|
|
27
37
|
await this.init();
|
|
@@ -37,8 +47,9 @@ export default class SimpleVectorStore {
|
|
|
37
47
|
const existing = this.db.getEmbedding(chunkId);
|
|
38
48
|
if (existing)
|
|
39
49
|
return existing;
|
|
40
|
-
// Generate new embedding
|
|
41
|
-
const
|
|
50
|
+
// Generate new embedding ā truncate to 8KB to stay within model limits
|
|
51
|
+
const rawText = `${chunk.name} ${chunk.purpose} ${chunk.code}`;
|
|
52
|
+
const textToEmbed = rawText.length > 8192 ? rawText.slice(0, 8192) : rawText;
|
|
42
53
|
const embedding = await this.generateEmbedding(textToEmbed);
|
|
43
54
|
// Save to SQLite
|
|
44
55
|
this.db.saveEmbedding(chunkId, embedding, this.modelName);
|