cntx-ui 3.0.9 → 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.
- package/dist/bin/cntx-ui.js +21 -2
- package/dist/lib/agent-runtime.js +10 -2
- package/dist/lib/semantic-splitter.js +230 -6
- package/dist/server.js +197 -5
- package/package.json +9 -2
- package/templates/agent-instructions.md +7 -21
- package/web/dist/assets/{index-D2RTcdqV.js → index-8QXMnXVq.js} +3 -3
- package/web/dist/index.html +1 -1
package/dist/bin/cntx-ui.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readFileSync, existsSync } from 'fs';
|
|
3
3
|
import { dirname, join } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
-
import { startServer, initConfig } from '../server.js';
|
|
5
|
+
import { startServer, initConfig, getStatus, setupMCP, generateBundle } from '../server.js';
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
let packagePath = join(__dirname, '..', 'package.json');
|
|
8
8
|
if (!existsSync(packagePath)) {
|
|
@@ -30,11 +30,27 @@ async function main() {
|
|
|
30
30
|
case 'init':
|
|
31
31
|
console.log('🚀 Initializing cntx-ui...');
|
|
32
32
|
await initConfig();
|
|
33
|
-
console.log('🎉 cntx-ui initialized with full scaffolding!');
|
|
34
33
|
break;
|
|
35
34
|
case 'mcp':
|
|
36
35
|
await startServer({ withMcp: true, skipFileWatcher: true, skipBundleGeneration: true });
|
|
37
36
|
break;
|
|
37
|
+
case 'bundle':
|
|
38
|
+
const bundleName = args[1] || 'master';
|
|
39
|
+
try {
|
|
40
|
+
await generateBundle(bundleName);
|
|
41
|
+
console.log(`✅ Bundle '${bundleName}' generated successfully`);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error(`❌ Failed to generate bundle '${bundleName}': ${error.message}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case 'status':
|
|
49
|
+
await getStatus();
|
|
50
|
+
break;
|
|
51
|
+
case 'setup-mcp':
|
|
52
|
+
setupMCP();
|
|
53
|
+
break;
|
|
38
54
|
case 'version':
|
|
39
55
|
case '-v':
|
|
40
56
|
case '--version':
|
|
@@ -49,6 +65,9 @@ Usage:
|
|
|
49
65
|
cntx-ui watch [port] Start the visual dashboard and intelligence engine (default: 3333)
|
|
50
66
|
cntx-ui init Initialize cntx-ui configuration in the current directory
|
|
51
67
|
cntx-ui mcp Start the Model Context Protocol (MCP) server on stdio
|
|
68
|
+
cntx-ui bundle [name] Generate specific bundle (default: master)
|
|
69
|
+
cntx-ui status Show current project status
|
|
70
|
+
cntx-ui setup-mcp Add this project to Claude Desktop MCP config
|
|
52
71
|
cntx-ui version Show current version
|
|
53
72
|
cntx-ui help Show this help information
|
|
54
73
|
|
|
@@ -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,9 +3,10 @@
|
|
|
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
|
-
import { existsSync, mkdirSync, readFileSync } from 'fs';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, cpSync } from 'fs';
|
|
9
|
+
import { homedir } from 'os';
|
|
9
10
|
// Import our modular components
|
|
10
11
|
import ConfigurationManager from './lib/configuration-manager.js';
|
|
11
12
|
import FileSystemManager from './lib/file-system-manager.js';
|
|
@@ -18,6 +19,21 @@ import SimpleVectorStore from './lib/simple-vector-store.js';
|
|
|
18
19
|
import { MCPServer } from './lib/mcp-server.js';
|
|
19
20
|
import AgentRuntime from './lib/agent-runtime.js';
|
|
20
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
|
+
}
|
|
21
37
|
export class CntxServer {
|
|
22
38
|
CWD;
|
|
23
39
|
CNTX_DIR;
|
|
@@ -120,7 +136,7 @@ export class CntxServer {
|
|
|
120
136
|
catch (e) { }
|
|
121
137
|
// Fresh analysis
|
|
122
138
|
const files = this.fileSystemManager.getAllFiles().map(f => this.fileSystemManager.relativePath(f))
|
|
123
|
-
.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()));
|
|
124
140
|
let bundleConfig = null;
|
|
125
141
|
if (existsSync(this.configManager.CONFIG_FILE)) {
|
|
126
142
|
bundleConfig = JSON.parse(readFileSync(this.configManager.CONFIG_FILE, 'utf8'));
|
|
@@ -218,8 +234,184 @@ export async function startServer(options = {}) {
|
|
|
218
234
|
server.startMCPServer();
|
|
219
235
|
return await server.listen(options.port, options.host);
|
|
220
236
|
}
|
|
237
|
+
// Initialize project configuration
|
|
221
238
|
export async function initConfig(cwd = process.cwd()) {
|
|
222
239
|
const server = new CntxServer(cwd);
|
|
223
|
-
//
|
|
224
|
-
|
|
240
|
+
// 1. Initialize directory structure
|
|
241
|
+
if (!existsSync(server.CNTX_DIR)) {
|
|
242
|
+
mkdirSync(server.CNTX_DIR, { recursive: true });
|
|
243
|
+
console.log('📁 Created .cntx directory');
|
|
244
|
+
}
|
|
245
|
+
// 2. Create .mcp.json for Claude Code discovery
|
|
246
|
+
const mcpConfigPath = join(cwd, '.mcp.json');
|
|
247
|
+
const mcpConfig = {
|
|
248
|
+
mcpServers: {
|
|
249
|
+
"cntx-ui": {
|
|
250
|
+
command: "cntx-ui",
|
|
251
|
+
args: ["mcp"],
|
|
252
|
+
cwd: "."
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), 'utf8');
|
|
257
|
+
console.log('📄 Created .mcp.json for agent auto-discovery');
|
|
258
|
+
// 3. Initialize basic configuration with better defaults and auto-suggestions
|
|
259
|
+
server.configManager.loadConfig();
|
|
260
|
+
const suggestedBundles = {
|
|
261
|
+
master: ['**/*']
|
|
262
|
+
};
|
|
263
|
+
// Directory-based auto-suggestions
|
|
264
|
+
const commonDirs = [
|
|
265
|
+
{ dir: 'src/components', name: 'ui-components' },
|
|
266
|
+
{ dir: 'src/services', name: 'services' },
|
|
267
|
+
{ dir: 'src/lib', name: 'libraries' },
|
|
268
|
+
{ dir: 'src/hooks', name: 'react-hooks' },
|
|
269
|
+
{ dir: 'server', name: 'backend-api' },
|
|
270
|
+
{ dir: 'tests', name: 'test-suite' }
|
|
271
|
+
];
|
|
272
|
+
commonDirs.forEach(d => {
|
|
273
|
+
if (existsSync(join(cwd, d.dir))) {
|
|
274
|
+
suggestedBundles[d.name] = [`${d.dir}/**`];
|
|
275
|
+
console.log(`💡 Suggested bundle: ${d.name} (${d.dir}/**)`);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
server.configManager.saveConfig({
|
|
279
|
+
bundles: suggestedBundles
|
|
280
|
+
});
|
|
281
|
+
// 4. Create robust default .cntxignore
|
|
282
|
+
const ignorePath = join(cwd, '.cntxignore');
|
|
283
|
+
if (!existsSync(ignorePath)) {
|
|
284
|
+
const defaultIgnore = `# Binary files
|
|
285
|
+
*.db
|
|
286
|
+
*.db-journal
|
|
287
|
+
*.png
|
|
288
|
+
*.jpg
|
|
289
|
+
*.jpeg
|
|
290
|
+
*.ico
|
|
291
|
+
*.icns
|
|
292
|
+
*.gif
|
|
293
|
+
*.zip
|
|
294
|
+
*.tar.gz
|
|
295
|
+
|
|
296
|
+
# Generated files
|
|
297
|
+
**/gen/**
|
|
298
|
+
**/dist/**
|
|
299
|
+
**/build/**
|
|
300
|
+
**/node_modules/**
|
|
301
|
+
**/.next/**
|
|
302
|
+
**/.cache/**
|
|
303
|
+
|
|
304
|
+
# cntx-ui internals
|
|
305
|
+
.cntx/**
|
|
306
|
+
.mcp.json
|
|
307
|
+
`;
|
|
308
|
+
writeFileSync(ignorePath, defaultIgnore, 'utf8');
|
|
309
|
+
console.log('📄 Created .cntxignore with smart defaults');
|
|
310
|
+
}
|
|
311
|
+
console.log('⚙️ Basic configuration initialized');
|
|
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);
|
|
318
|
+
// Copy agent configuration files
|
|
319
|
+
const agentFiles = [
|
|
320
|
+
'agent-config.yaml',
|
|
321
|
+
'agent-instructions.md'
|
|
322
|
+
];
|
|
323
|
+
for (const file of agentFiles) {
|
|
324
|
+
const sourcePath = join(templateDir, file);
|
|
325
|
+
const destPath = join(server.CNTX_DIR, file);
|
|
326
|
+
if (existsSync(sourcePath) && !existsSync(destPath)) {
|
|
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
|
+
}
|
|
335
|
+
console.log(`📄 Created ${file}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Copy agent-rules directory structure
|
|
339
|
+
const agentRulesSource = join(templateDir, 'agent-rules');
|
|
340
|
+
const agentRulesDest = join(server.CNTX_DIR, 'agent-rules');
|
|
341
|
+
if (existsSync(agentRulesSource) && !existsSync(agentRulesDest)) {
|
|
342
|
+
cpSync(agentRulesSource, agentRulesDest, { recursive: true });
|
|
343
|
+
console.log('📁 Created agent-rules directory with templates');
|
|
344
|
+
}
|
|
345
|
+
return server.initMessages;
|
|
346
|
+
}
|
|
347
|
+
export async function generateBundle(name) {
|
|
348
|
+
const server = new CntxServer(process.cwd());
|
|
349
|
+
await server.init({ skipFileWatcher: true });
|
|
350
|
+
return await server.bundleManager.regenerateBundle(name);
|
|
351
|
+
}
|
|
352
|
+
export async function getStatus() {
|
|
353
|
+
const server = new CntxServer(process.cwd());
|
|
354
|
+
await server.init({ skipFileWatcher: true });
|
|
355
|
+
const bundles = server.bundleManager.getAllBundleInfo();
|
|
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
|
+
}));
|
|
366
|
+
console.log('📊 cntx-ui Status');
|
|
367
|
+
console.log('================');
|
|
368
|
+
console.log(`Total files: ${totalFiles}`);
|
|
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}`);
|
|
373
|
+
});
|
|
374
|
+
return {
|
|
375
|
+
totalFiles,
|
|
376
|
+
bundles: bundlesWithCounts.length,
|
|
377
|
+
bundleDetails: bundlesWithCounts
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
export function setupMCP() {
|
|
381
|
+
const configPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
382
|
+
const projectPath = process.cwd();
|
|
383
|
+
console.log('🔧 Setting up MCP integration...');
|
|
384
|
+
console.log(`Project: ${projectPath}`);
|
|
385
|
+
console.log(`Claude config: ${configPath}`);
|
|
386
|
+
try {
|
|
387
|
+
let config = {};
|
|
388
|
+
if (existsSync(configPath)) {
|
|
389
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
390
|
+
}
|
|
391
|
+
if (!config.mcpServers) {
|
|
392
|
+
config.mcpServers = {};
|
|
393
|
+
}
|
|
394
|
+
config.mcpServers['cntx-ui'] = {
|
|
395
|
+
command: 'npx',
|
|
396
|
+
args: ['cntx-ui', 'mcp'],
|
|
397
|
+
cwd: projectPath
|
|
398
|
+
};
|
|
399
|
+
// Ensure directory exists
|
|
400
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
401
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
402
|
+
console.log('✅ MCP integration configured');
|
|
403
|
+
console.log('💡 Restart Claude Desktop to apply changes');
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
console.error('❌ Failed to setup MCP:', error.message);
|
|
407
|
+
console.log('💡 You may need to manually add the configuration to Claude Desktop');
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Auto-start server when run directly
|
|
411
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
412
|
+
if (isMainModule) {
|
|
413
|
+
console.log('🚀 Starting cntx-ui server...');
|
|
414
|
+
const server = new CntxServer();
|
|
415
|
+
server.init();
|
|
416
|
+
server.listen(3333, 'localhost');
|
|
225
417
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cntx-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
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",
|
|
@@ -42,15 +42,22 @@
|
|
|
42
42
|
"check:version-sync": "node scripts/check-version-sync.mjs",
|
|
43
43
|
"release:ci": "node scripts/release-from-package.mjs",
|
|
44
44
|
"prepublishOnly": "npm run build",
|
|
45
|
-
"test:local": "npm pack && npm install -g ./cntx-ui-3.0.
|
|
45
|
+
"test:local": "npm pack && npm install -g ./cntx-ui-3.1.0.tgz"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@xenova/transformers": "^2.17.2",
|
|
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
|
},
|