cntx-ui 3.1.0 β 3.1.2
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.
|
@@ -39,7 +39,9 @@ export class AgentRuntime {
|
|
|
39
39
|
let toolsReference = '';
|
|
40
40
|
if (this.cntxServer.mcpServer) {
|
|
41
41
|
const tools = this.cntxServer.mcpServer.getToolDefinitions();
|
|
42
|
-
toolsReference = tools
|
|
42
|
+
toolsReference = tools
|
|
43
|
+
.filter(t => !t.name?.includes('activities'))
|
|
44
|
+
.map(t => {
|
|
43
45
|
let params = [];
|
|
44
46
|
if (t.inputSchema?.properties) {
|
|
45
47
|
params = Object.entries(t.inputSchema.properties).map(([name, prop]) => {
|
|
@@ -50,6 +52,12 @@ export class AgentRuntime {
|
|
|
50
52
|
return `### \`${t.name}\`\n${t.description}\n${params.length > 0 ? '**Parameters:**\n- ' + params.join('\n- ') : '*No parameters required*'}\n`;
|
|
51
53
|
}).join('\n');
|
|
52
54
|
}
|
|
55
|
+
// Find TOOLS.md template
|
|
56
|
+
let toolsMdPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../templates/TOOLS.md');
|
|
57
|
+
if (!fs.existsSync(toolsMdPath)) {
|
|
58
|
+
// Fallback for dist/lib/ context
|
|
59
|
+
toolsMdPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../templates/TOOLS.md');
|
|
60
|
+
}
|
|
53
61
|
const manifest = `# π€ Agent Handshake: ${overview.projectPath.split('/').pop()}
|
|
54
62
|
|
|
55
63
|
## Project Overview
|
|
@@ -70,7 +78,7 @@ ${toolsReference || '*(MCP Server not yet initialized, tools will appear here)*'
|
|
|
70
78
|
## π Complete Tool & API Reference
|
|
71
79
|
Refer to the dynamic reference below for full parameter schemas and HTTP fallback endpoints.
|
|
72
80
|
|
|
73
|
-
${fs.
|
|
81
|
+
${fs.existsSync(toolsMdPath) ? fs.readFileSync(toolsMdPath, 'utf8') : '*(Tools documentation missing)*'}
|
|
74
82
|
|
|
75
83
|
## Working Memory
|
|
76
84
|
This agent is **stateful**. All interactions in this directory are logged to a persistent SQLite database (\`.cntx/bundles.db\`), allowing for context retention across sessions.
|
|
@@ -9,6 +9,13 @@ import Parser from 'tree-sitter';
|
|
|
9
9
|
import JavaScript from 'tree-sitter-javascript';
|
|
10
10
|
import TypeScript from 'tree-sitter-typescript';
|
|
11
11
|
import Rust from 'tree-sitter-rust';
|
|
12
|
+
import Json from 'tree-sitter-json';
|
|
13
|
+
import Css from 'tree-sitter-css';
|
|
14
|
+
import Html from 'tree-sitter-html';
|
|
15
|
+
import Sql from 'tree-sitter-sql';
|
|
16
|
+
import Markdown from 'tree-sitter-markdown';
|
|
17
|
+
import Toml from 'tree-sitter-toml';
|
|
18
|
+
import LegacyParser from 'tree-sitter-legacy';
|
|
12
19
|
import HeuristicsManager from './heuristics-manager.js';
|
|
13
20
|
export default class SemanticSplitter {
|
|
14
21
|
options;
|
|
@@ -20,6 +27,7 @@ export default class SemanticSplitter {
|
|
|
20
27
|
maxChunkSize: 3000, // Max chars per chunk
|
|
21
28
|
includeContext: true, // Include imports/types needed
|
|
22
29
|
minFunctionSize: 40, // Skip tiny functions
|
|
30
|
+
minStructureSize: 20, // Skip tiny structures
|
|
23
31
|
...options
|
|
24
32
|
};
|
|
25
33
|
// Initialize tree-sitter parsers
|
|
@@ -27,17 +35,37 @@ export default class SemanticSplitter {
|
|
|
27
35
|
javascript: new Parser(),
|
|
28
36
|
typescript: new Parser(),
|
|
29
37
|
tsx: new Parser(),
|
|
30
|
-
rust: new Parser()
|
|
38
|
+
rust: new Parser(),
|
|
39
|
+
json: new Parser(),
|
|
40
|
+
css: new Parser(),
|
|
41
|
+
html: new Parser(),
|
|
42
|
+
sql: new LegacyParser(),
|
|
43
|
+
markdown: new LegacyParser(),
|
|
44
|
+
toml: new LegacyParser()
|
|
31
45
|
};
|
|
32
46
|
this.parsers.javascript.setLanguage(JavaScript);
|
|
33
47
|
this.parsers.typescript.setLanguage(TypeScript.typescript);
|
|
34
48
|
this.parsers.tsx.setLanguage(TypeScript.tsx);
|
|
35
49
|
this.parsers.rust.setLanguage(Rust);
|
|
50
|
+
this.parsers.json.setLanguage(Json);
|
|
51
|
+
this.parsers.css.setLanguage(Css);
|
|
52
|
+
this.parsers.html.setLanguage(Html);
|
|
53
|
+
this.parsers.sql.setLanguage(Sql);
|
|
54
|
+
this.parsers.markdown.setLanguage(Markdown);
|
|
55
|
+
this.parsers.toml.setLanguage(Toml);
|
|
36
56
|
this.heuristicsManager = new HeuristicsManager();
|
|
37
57
|
}
|
|
38
58
|
getParser(filePath) {
|
|
39
59
|
const ext = extname(filePath);
|
|
40
60
|
switch (ext) {
|
|
61
|
+
case '.json': return this.parsers.json;
|
|
62
|
+
case '.css': return this.parsers.css;
|
|
63
|
+
case '.scss': return this.parsers.css;
|
|
64
|
+
case '.html': return this.parsers.html;
|
|
65
|
+
case '.sql': return this.parsers.sql;
|
|
66
|
+
case '.md': return this.parsers.markdown;
|
|
67
|
+
case '.toml': return this.parsers.toml;
|
|
68
|
+
case '.jsx': return this.parsers.javascript;
|
|
41
69
|
case '.ts': return this.parsers.typescript;
|
|
42
70
|
case '.tsx': return this.parsers.tsx;
|
|
43
71
|
case '.rs': return this.parsers.rust;
|
|
@@ -81,13 +109,35 @@ export default class SemanticSplitter {
|
|
|
81
109
|
const parser = this.getParser(relativePath);
|
|
82
110
|
const tree = parser.parse(content);
|
|
83
111
|
const root = tree.rootNode;
|
|
112
|
+
const ext = extname(relativePath).toLowerCase();
|
|
84
113
|
const elements = {
|
|
85
114
|
functions: [],
|
|
86
115
|
types: [],
|
|
87
|
-
imports:
|
|
116
|
+
imports: []
|
|
88
117
|
};
|
|
89
|
-
|
|
90
|
-
|
|
118
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.rs'].includes(ext)) {
|
|
119
|
+
elements.imports = this.extractImports(root, content, relativePath);
|
|
120
|
+
// Traverse AST for functions and types
|
|
121
|
+
this.traverse(root, content, relativePath, elements);
|
|
122
|
+
}
|
|
123
|
+
else if (ext === '.json') {
|
|
124
|
+
this.extractJsonStructures(root, content, relativePath, elements);
|
|
125
|
+
}
|
|
126
|
+
else if (ext === '.css' || ext === '.scss') {
|
|
127
|
+
this.extractCssStructures(root, content, relativePath, elements);
|
|
128
|
+
}
|
|
129
|
+
else if (ext === '.html') {
|
|
130
|
+
this.extractHtmlStructures(root, content, relativePath, elements);
|
|
131
|
+
}
|
|
132
|
+
else if (ext === '.sql') {
|
|
133
|
+
this.extractSqlStructures(root, content, relativePath, elements);
|
|
134
|
+
}
|
|
135
|
+
else if (ext === '.md') {
|
|
136
|
+
this.extractMarkdownStructures(root, content, relativePath, elements);
|
|
137
|
+
}
|
|
138
|
+
else if (ext === '.toml') {
|
|
139
|
+
this.extractTomlStructures(root, content, relativePath, elements);
|
|
140
|
+
}
|
|
91
141
|
// Create chunks from elements
|
|
92
142
|
return this.createChunks(elements, content, relativePath);
|
|
93
143
|
}
|
|
@@ -176,9 +226,183 @@ export default class SemanticSplitter {
|
|
|
176
226
|
startLine: node.startPosition.row + 1,
|
|
177
227
|
code,
|
|
178
228
|
isExported: this.isExported(node),
|
|
179
|
-
isAsync: code.includes('async')
|
|
229
|
+
isAsync: code.includes('async'),
|
|
230
|
+
category: 'function'
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
mapStructureNode(name, node, content, filePath) {
|
|
234
|
+
return {
|
|
235
|
+
name,
|
|
236
|
+
type: node.type,
|
|
237
|
+
filePath,
|
|
238
|
+
startLine: node.startPosition.row + 1,
|
|
239
|
+
code: content.slice(node.startIndex, node.endIndex),
|
|
240
|
+
isExported: false,
|
|
241
|
+
isAsync: false,
|
|
242
|
+
category: 'structure'
|
|
180
243
|
};
|
|
181
244
|
}
|
|
245
|
+
extractJsonStructures(root, content, filePath, elements) {
|
|
246
|
+
const rootNode = root.namedChild(0);
|
|
247
|
+
if (!rootNode)
|
|
248
|
+
return;
|
|
249
|
+
if (rootNode.type === 'object') {
|
|
250
|
+
for (let i = 0; i < rootNode.namedChildCount; i++) {
|
|
251
|
+
const child = rootNode.namedChild(i);
|
|
252
|
+
if (child && child.type === 'pair') {
|
|
253
|
+
const keyNode = child.childForFieldName('key') || child.namedChild(0);
|
|
254
|
+
const name = keyNode ? content.slice(keyNode.startIndex, keyNode.endIndex).replace(/['"]/g, '') : 'pair';
|
|
255
|
+
const structure = this.mapStructureNode(name, child, content, filePath);
|
|
256
|
+
if (structure.code.length >= this.options.minStructureSize) {
|
|
257
|
+
elements.functions.push(structure);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else if (rootNode.type === 'array') {
|
|
263
|
+
for (let i = 0; i < rootNode.namedChildCount; i++) {
|
|
264
|
+
const child = rootNode.namedChild(i);
|
|
265
|
+
if (child) {
|
|
266
|
+
const structure = this.mapStructureNode(`item_${i + 1}`, child, content, filePath);
|
|
267
|
+
if (structure.code.length >= this.options.minStructureSize) {
|
|
268
|
+
elements.functions.push(structure);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
extractCssStructures(root, content, filePath, elements) {
|
|
275
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
276
|
+
const node = root.namedChild(i);
|
|
277
|
+
if (!node)
|
|
278
|
+
continue;
|
|
279
|
+
if (node.type === 'rule_set' || node.type === 'at_rule') {
|
|
280
|
+
const name = this.getCssRuleName(node, content);
|
|
281
|
+
const structure = this.mapStructureNode(name, node, content, filePath);
|
|
282
|
+
if (structure.code.length >= this.options.minStructureSize) {
|
|
283
|
+
elements.functions.push(structure);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
getCssRuleName(node, content) {
|
|
289
|
+
const selectorsNode = node.childForFieldName('selectors') || node.namedChild(0);
|
|
290
|
+
if (selectorsNode) {
|
|
291
|
+
return content.slice(selectorsNode.startIndex, selectorsNode.endIndex).trim();
|
|
292
|
+
}
|
|
293
|
+
const code = content.slice(node.startIndex, node.endIndex);
|
|
294
|
+
return code.split('{')[0].trim() || 'rule';
|
|
295
|
+
}
|
|
296
|
+
extractHtmlStructures(root, content, filePath, elements) {
|
|
297
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
298
|
+
const node = root.namedChild(i);
|
|
299
|
+
if (!node)
|
|
300
|
+
continue;
|
|
301
|
+
if (node.type === 'element' || node.type === 'script_element' || node.type === 'style_element') {
|
|
302
|
+
const name = this.getHtmlElementName(node, content);
|
|
303
|
+
const structure = this.mapStructureNode(name, node, content, filePath);
|
|
304
|
+
if (structure.code.length >= this.options.minStructureSize) {
|
|
305
|
+
elements.functions.push(structure);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
getHtmlElementName(node, content) {
|
|
311
|
+
const startTag = node.childForFieldName('start_tag') || node.namedChild(0);
|
|
312
|
+
const tagNameNode = startTag?.childForFieldName('tag_name') || startTag?.namedChild(0);
|
|
313
|
+
if (tagNameNode) {
|
|
314
|
+
return content.slice(tagNameNode.startIndex, tagNameNode.endIndex);
|
|
315
|
+
}
|
|
316
|
+
return node.type;
|
|
317
|
+
}
|
|
318
|
+
extractSqlStructures(root, content, filePath, elements) {
|
|
319
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
320
|
+
const node = root.namedChild(i);
|
|
321
|
+
if (!node)
|
|
322
|
+
continue;
|
|
323
|
+
const name = this.getSqlStatementName(node, content);
|
|
324
|
+
const structure = this.mapStructureNode(name, node, content, filePath);
|
|
325
|
+
if (structure.code.length >= this.options.minStructureSize) {
|
|
326
|
+
elements.functions.push(structure);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
getSqlStatementName(node, content) {
|
|
331
|
+
const code = content.slice(node.startIndex, node.endIndex).trim();
|
|
332
|
+
if (!code)
|
|
333
|
+
return node.type;
|
|
334
|
+
const firstLine = code.split('\n')[0];
|
|
335
|
+
const match = firstLine.match(/^\s*([A-Za-z_]+)/);
|
|
336
|
+
if (match)
|
|
337
|
+
return match[1].toUpperCase();
|
|
338
|
+
return node.type;
|
|
339
|
+
}
|
|
340
|
+
extractMarkdownStructures(root, content, filePath, elements) {
|
|
341
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
342
|
+
const node = root.namedChild(i);
|
|
343
|
+
if (!node)
|
|
344
|
+
continue;
|
|
345
|
+
if (this.isMarkdownStructureNode(node.type)) {
|
|
346
|
+
const name = this.getMarkdownNodeName(node, content);
|
|
347
|
+
const structure = this.mapStructureNode(name, node, content, filePath);
|
|
348
|
+
if (structure.code.length >= this.options.minStructureSize) {
|
|
349
|
+
elements.functions.push(structure);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
isMarkdownStructureNode(type) {
|
|
355
|
+
return [
|
|
356
|
+
'atx_heading',
|
|
357
|
+
'setext_heading',
|
|
358
|
+
'fenced_code_block',
|
|
359
|
+
'indented_code_block',
|
|
360
|
+
'tight_list',
|
|
361
|
+
'loose_list',
|
|
362
|
+
'list',
|
|
363
|
+
'block_quote',
|
|
364
|
+
'thematic_break'
|
|
365
|
+
].includes(type);
|
|
366
|
+
}
|
|
367
|
+
getMarkdownNodeName(node, content) {
|
|
368
|
+
const text = content.slice(node.startIndex, node.endIndex).trim();
|
|
369
|
+
if (node.type === 'atx_heading') {
|
|
370
|
+
const withoutHashes = text.replace(/^#{1,6}\s*/, '').replace(/\s*#+\s*$/, '').trim();
|
|
371
|
+
return withoutHashes || 'heading';
|
|
372
|
+
}
|
|
373
|
+
if (node.type === 'setext_heading') {
|
|
374
|
+
const firstLine = text.split('\n')[0]?.trim();
|
|
375
|
+
return firstLine || 'heading';
|
|
376
|
+
}
|
|
377
|
+
if (node.type === 'fenced_code_block' || node.type === 'indented_code_block') {
|
|
378
|
+
return 'code_block';
|
|
379
|
+
}
|
|
380
|
+
if (node.type.includes('list')) {
|
|
381
|
+
return 'list';
|
|
382
|
+
}
|
|
383
|
+
if (node.type === 'block_quote') {
|
|
384
|
+
return 'blockquote';
|
|
385
|
+
}
|
|
386
|
+
if (node.type === 'thematic_break') {
|
|
387
|
+
return 'break';
|
|
388
|
+
}
|
|
389
|
+
return node.type;
|
|
390
|
+
}
|
|
391
|
+
extractTomlStructures(root, content, filePath, elements) {
|
|
392
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
393
|
+
const node = root.namedChild(i);
|
|
394
|
+
if (!node)
|
|
395
|
+
continue;
|
|
396
|
+
if (node.type === 'table' || node.type === 'table_array_element' || node.type === 'pair') {
|
|
397
|
+
const keyNode = node.childForFieldName('name') || node.childForFieldName('key') || node.namedChild(0);
|
|
398
|
+
const name = keyNode ? content.slice(keyNode.startIndex, keyNode.endIndex) : node.type;
|
|
399
|
+
const structure = this.mapStructureNode(name, node, content, filePath);
|
|
400
|
+
if (structure.code.length >= this.options.minStructureSize) {
|
|
401
|
+
elements.functions.push(structure);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
182
406
|
mapTypeNode(node, content, filePath) {
|
|
183
407
|
const nameNode = node.childForFieldName('name');
|
|
184
408
|
if (!nameNode)
|
|
@@ -251,7 +475,7 @@ export default class SemanticSplitter {
|
|
|
251
475
|
id: `${filePath}:${func.name}:${func.startLine}`,
|
|
252
476
|
name: func.name,
|
|
253
477
|
filePath,
|
|
254
|
-
type: 'function',
|
|
478
|
+
type: func.category === 'structure' ? 'structure' : 'function',
|
|
255
479
|
subtype: func.type,
|
|
256
480
|
code: chunkCode,
|
|
257
481
|
startLine: func.startLine,
|
package/dist/server.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Lean orchestration layer using modular architecture
|
|
4
4
|
*/
|
|
5
5
|
import { createServer } from 'http';
|
|
6
|
-
import { join, dirname, extname } from 'path';
|
|
6
|
+
import { join, dirname, extname, basename } from 'path';
|
|
7
7
|
import { fileURLToPath, parse } from 'url';
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, cpSync } from 'fs';
|
|
9
9
|
import { homedir } from 'os';
|
|
@@ -19,6 +19,21 @@ import SimpleVectorStore from './lib/simple-vector-store.js';
|
|
|
19
19
|
import { MCPServer } from './lib/mcp-server.js';
|
|
20
20
|
import AgentRuntime from './lib/agent-runtime.js';
|
|
21
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
function getProjectName(cwd) {
|
|
23
|
+
const packageJsonPath = join(cwd, 'package.json');
|
|
24
|
+
if (existsSync(packageJsonPath)) {
|
|
25
|
+
try {
|
|
26
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
27
|
+
if (typeof packageJson?.name === 'string' && packageJson.name.trim()) {
|
|
28
|
+
return packageJson.name.trim();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Fall through to directory name
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return basename(cwd);
|
|
36
|
+
}
|
|
22
37
|
export class CntxServer {
|
|
23
38
|
CWD;
|
|
24
39
|
CNTX_DIR;
|
|
@@ -121,7 +136,7 @@ export class CntxServer {
|
|
|
121
136
|
catch (e) { }
|
|
122
137
|
// Fresh analysis
|
|
123
138
|
const files = this.fileSystemManager.getAllFiles().map(f => this.fileSystemManager.relativePath(f))
|
|
124
|
-
.filter(f => ['.js', '.jsx', '.ts', '.tsx', '.rs'].includes(extname(f).toLowerCase()));
|
|
139
|
+
.filter(f => ['.js', '.jsx', '.ts', '.tsx', '.rs', '.json', '.css', '.scss', '.html', '.sql', '.md', '.toml'].includes(extname(f).toLowerCase()));
|
|
125
140
|
let bundleConfig = null;
|
|
126
141
|
if (existsSync(this.configManager.CONFIG_FILE)) {
|
|
127
142
|
bundleConfig = JSON.parse(readFileSync(this.configManager.CONFIG_FILE, 'utf8'));
|
|
@@ -294,7 +309,12 @@ export async function initConfig(cwd = process.cwd()) {
|
|
|
294
309
|
console.log('π Created .cntxignore with smart defaults');
|
|
295
310
|
}
|
|
296
311
|
console.log('βοΈ Basic configuration initialized');
|
|
297
|
-
|
|
312
|
+
let templateDir = join(__dirname, 'templates');
|
|
313
|
+
if (!existsSync(templateDir)) {
|
|
314
|
+
// Fallback for dist/ context
|
|
315
|
+
templateDir = join(__dirname, '..', 'templates');
|
|
316
|
+
}
|
|
317
|
+
const projectName = getProjectName(cwd);
|
|
298
318
|
// Copy agent configuration files
|
|
299
319
|
const agentFiles = [
|
|
300
320
|
'agent-config.yaml',
|
|
@@ -304,7 +324,14 @@ export async function initConfig(cwd = process.cwd()) {
|
|
|
304
324
|
const sourcePath = join(templateDir, file);
|
|
305
325
|
const destPath = join(server.CNTX_DIR, file);
|
|
306
326
|
if (existsSync(sourcePath) && !existsSync(destPath)) {
|
|
307
|
-
|
|
327
|
+
if (file === 'agent-config.yaml') {
|
|
328
|
+
const template = readFileSync(sourcePath, 'utf8');
|
|
329
|
+
const updated = template.replace(/^project:\s*["'].*?["']\s*$/m, `project: "${projectName}"`);
|
|
330
|
+
writeFileSync(destPath, updated, 'utf8');
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
copyFileSync(sourcePath, destPath);
|
|
334
|
+
}
|
|
308
335
|
console.log(`π Created ${file}`);
|
|
309
336
|
}
|
|
310
337
|
}
|
|
@@ -315,39 +342,6 @@ export async function initConfig(cwd = process.cwd()) {
|
|
|
315
342
|
cpSync(agentRulesSource, agentRulesDest, { recursive: true });
|
|
316
343
|
console.log('π Created agent-rules directory with templates');
|
|
317
344
|
}
|
|
318
|
-
// Copy activities framework
|
|
319
|
-
const activitiesDir = join(server.CNTX_DIR, 'activities');
|
|
320
|
-
if (!existsSync(activitiesDir)) {
|
|
321
|
-
mkdirSync(activitiesDir, { recursive: true });
|
|
322
|
-
}
|
|
323
|
-
// Copy activities README
|
|
324
|
-
const activitiesReadmeSource = join(templateDir, 'activities', 'README.md');
|
|
325
|
-
const activitiesReadmeDest = join(activitiesDir, 'README.md');
|
|
326
|
-
if (existsSync(activitiesReadmeSource) && !existsSync(activitiesReadmeDest)) {
|
|
327
|
-
copyFileSync(activitiesReadmeSource, activitiesReadmeDest);
|
|
328
|
-
console.log('π Created activities/README.md');
|
|
329
|
-
}
|
|
330
|
-
// Copy activities lib directory (MDC templates)
|
|
331
|
-
const activitiesLibSource = join(templateDir, 'activities', 'lib');
|
|
332
|
-
const activitiesLibDest = join(activitiesDir, 'lib');
|
|
333
|
-
if (existsSync(activitiesLibSource) && !existsSync(activitiesLibDest)) {
|
|
334
|
-
cpSync(activitiesLibSource, activitiesLibDest, { recursive: true });
|
|
335
|
-
console.log('π Created activities/lib with MDC templates');
|
|
336
|
-
}
|
|
337
|
-
// Copy activities.json from templates
|
|
338
|
-
const activitiesJsonPath = join(activitiesDir, 'activities.json');
|
|
339
|
-
const templateActivitiesJsonPath = join(templateDir, 'activities', 'activities.json');
|
|
340
|
-
if (!existsSync(activitiesJsonPath) && existsSync(templateActivitiesJsonPath)) {
|
|
341
|
-
copyFileSync(templateActivitiesJsonPath, activitiesJsonPath);
|
|
342
|
-
console.log('π Created activities.json with bundle example activity');
|
|
343
|
-
}
|
|
344
|
-
// Copy example activity from templates
|
|
345
|
-
const activitiesDestDir = join(activitiesDir, 'activities');
|
|
346
|
-
const templateActivitiesDir = join(templateDir, 'activities', 'activities');
|
|
347
|
-
if (!existsSync(activitiesDestDir) && existsSync(templateActivitiesDir)) {
|
|
348
|
-
cpSync(templateActivitiesDir, activitiesDestDir, { recursive: true });
|
|
349
|
-
console.log('π Created example activity with templates');
|
|
350
|
-
}
|
|
351
345
|
return server.initMessages;
|
|
352
346
|
}
|
|
353
347
|
export async function generateBundle(name) {
|
|
@@ -360,17 +354,27 @@ export async function getStatus() {
|
|
|
360
354
|
await server.init({ skipFileWatcher: true });
|
|
361
355
|
const bundles = server.bundleManager.getAllBundleInfo();
|
|
362
356
|
const totalFiles = server.fileSystemManager.getAllFiles().length;
|
|
357
|
+
// Resolve actual file counts from glob patterns (getAllBundleInfo only
|
|
358
|
+
// returns stored counts which are 0 until bundles are generated)
|
|
359
|
+
const bundlesWithCounts = await Promise.all(bundles.map(async (bundle) => {
|
|
360
|
+
if (bundle.fileCount === 0 && bundle.patterns?.length) {
|
|
361
|
+
const files = await server.bundleManager.resolveBundleFiles(bundle.name);
|
|
362
|
+
return { ...bundle, fileCount: files.length };
|
|
363
|
+
}
|
|
364
|
+
return bundle;
|
|
365
|
+
}));
|
|
363
366
|
console.log('π cntx-ui Status');
|
|
364
367
|
console.log('================');
|
|
365
368
|
console.log(`Total files: ${totalFiles}`);
|
|
366
|
-
console.log(`Bundles: ${
|
|
367
|
-
|
|
368
|
-
|
|
369
|
+
console.log(`Bundles: ${bundlesWithCounts.length}`);
|
|
370
|
+
bundlesWithCounts.forEach(bundle => {
|
|
371
|
+
const sizeStr = bundle.size > 0 ? ` (${Math.round(bundle.size / 1024)}KB)` : '';
|
|
372
|
+
console.log(` β’ ${bundle.name}: ${bundle.fileCount} files${sizeStr}`);
|
|
369
373
|
});
|
|
370
374
|
return {
|
|
371
375
|
totalFiles,
|
|
372
|
-
bundles:
|
|
373
|
-
bundleDetails:
|
|
376
|
+
bundles: bundlesWithCounts.length,
|
|
377
|
+
bundleDetails: bundlesWithCounts
|
|
374
378
|
};
|
|
375
379
|
}
|
|
376
380
|
export function setupMCP() {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cntx-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.2",
|
|
5
5
|
"description": "Autonomous Repository Intelligence engine with web UI and MCP server. Unified semantic code understanding, local RAG, and agent working memory.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"repository-intelligence",
|
|
@@ -49,8 +49,15 @@
|
|
|
49
49
|
"better-sqlite3": "^12.2.0",
|
|
50
50
|
"glob": "^9.0.0",
|
|
51
51
|
"tree-sitter": "^0.21.1",
|
|
52
|
+
"tree-sitter-css": "^0.21.1",
|
|
53
|
+
"tree-sitter-html": "^0.20.4",
|
|
52
54
|
"tree-sitter-javascript": "^0.23.1",
|
|
55
|
+
"tree-sitter-json": "^0.21.0",
|
|
56
|
+
"tree-sitter-legacy": "npm:tree-sitter@^0.20.4",
|
|
57
|
+
"tree-sitter-markdown": "^0.7.1",
|
|
53
58
|
"tree-sitter-rust": "^0.21.0",
|
|
59
|
+
"tree-sitter-sql": "^0.1.0",
|
|
60
|
+
"tree-sitter-toml": "^0.5.1",
|
|
54
61
|
"tree-sitter-typescript": "^0.23.2",
|
|
55
62
|
"ws": "^8.13.0"
|
|
56
63
|
},
|
|
@@ -21,20 +21,20 @@ You are an AI agent with access to a specialized "Repository Intelligence" engin
|
|
|
21
21
|
## Available Capabilities
|
|
22
22
|
|
|
23
23
|
### 1. Model Context Protocol (MCP) - PRIMARY
|
|
24
|
-
|
|
24
|
+
Use MCP tools first: `agent/discover`, `agent/query`, `agent/investigate`, `agent/organize`.
|
|
25
25
|
|
|
26
26
|
### 2. HTTP API - FALLBACK
|
|
27
27
|
If MCP is unavailable, use the HTTP endpoints documented in `.cntx/AGENT.md`.
|
|
28
28
|
|
|
29
29
|
## Performance Hierarchy (Use in this order):
|
|
30
30
|
|
|
31
|
-
1. **Semantic Search** (20ms, 90% token savings) - `agent/query` (MCP)
|
|
31
|
+
1. **Semantic Search** (20ms, 90% token savings) - `agent/query` (MCP), fallback: `POST /api/semantic-search`
|
|
32
32
|
- Use for: code discovery, pattern matching, "find functions that..."
|
|
33
33
|
|
|
34
|
-
2. **Bundle System** (50ms) - `list_bundles` (MCP)
|
|
34
|
+
2. **Bundle System** (50ms) - `list_bundles` (MCP), fallback: `GET /api/bundles`
|
|
35
35
|
- Use for: project structure, file organization, high-level overview
|
|
36
36
|
|
|
37
|
-
3. **Discovery Mode** - `agent/discover` (MCP)
|
|
37
|
+
3. **Discovery Mode** - `agent/discover` (MCP), fallback: `GET /api/status`
|
|
38
38
|
- Use for: architectural overview and health check.
|
|
39
39
|
|
|
40
40
|
4. **Traditional Search** (100ms+, high token cost) - `grep/rg/Read`
|
|
@@ -57,7 +57,7 @@ _"Tell me about this codebase"_
|
|
|
57
57
|
|
|
58
58
|
_"Where is the user authentication handled?"_
|
|
59
59
|
|
|
60
|
-
- **ALWAYS use
|
|
60
|
+
- **ALWAYS use MCP `agent/query` first** for semantic discovery (fallback: `POST /api/semantic-search`)
|
|
61
61
|
- Use precise queries like "user authentication login session"
|
|
62
62
|
- Fallback to traditional search only if vector DB fails
|
|
63
63
|
- Always provide specific file paths and line numbers from results
|
|
@@ -67,7 +67,7 @@ _"Where is the user authentication handled?"_
|
|
|
67
67
|
|
|
68
68
|
_"I want to add dark modeβwhat already exists?"_
|
|
69
69
|
|
|
70
|
-
- **Vector search for related patterns** first:
|
|
70
|
+
- **Vector search for related patterns** first: `agent/investigate` (fallback: `POST /api/vector-db/search`)
|
|
71
71
|
- Use the format: β
Existing, β οΈ Partial, β Missing
|
|
72
72
|
- Cross-reference vector results with bundle organization
|
|
73
73
|
- Identify integration points and patterns to follow
|
|
@@ -125,25 +125,11 @@ Would you like me to [specific follow-up options]?
|
|
|
125
125
|
|
|
126
126
|
## Efficiency Principles
|
|
127
127
|
|
|
128
|
-
### Performance Hierarchy (Use in this order):
|
|
129
|
-
|
|
130
|
-
1. **Vector Database** (20ms, 90% token savings) - `POST /api/vector-db/search`
|
|
131
|
-
- Use for: code discovery, pattern matching, "find functions that..."
|
|
132
|
-
- Query format: `{"query": "semantic description", "limit": 5, "minSimilarity": 0.2}`
|
|
133
|
-
|
|
134
|
-
2. **Bundle System** (50ms) - `GET /api/bundles`
|
|
135
|
-
- Use for: project structure, file organization, high-level overview
|
|
136
|
-
|
|
137
|
-
3. **Traditional Search** (100ms+, high token cost) - `grep/rg/Read`
|
|
138
|
-
- Use ONLY when: exact string matching needed, vector search fails
|
|
139
|
-
- Examples: specific error messages, exact function names
|
|
140
|
-
|
|
141
128
|
### Token Optimization:
|
|
142
129
|
- **Vector search**: ~5k tokens per query vs 50k+ for file reading
|
|
143
130
|
- **Real-time updates**: Vector DB stays current with code changes
|
|
144
|
-
- **Comprehensive coverage**: 315+ indexed code chunks across entire codebase
|
|
145
131
|
|
|
146
|
-
## Vector Search Examples
|
|
132
|
+
## Vector Search Examples (HTTP fallback)
|
|
147
133
|
|
|
148
134
|
### Good Query Patterns:
|
|
149
135
|
```bash
|