cntx-ui 3.1.0 → 3.1.3

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/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';
@@ -13,12 +13,28 @@ import FileSystemManager from './lib/file-system-manager.js';
13
13
  import BundleManager from './lib/bundle-manager.js';
14
14
  import APIRouter from './lib/api-router.js';
15
15
  import WebSocketManager from './lib/websocket-manager.js';
16
+ import ArtifactManager from './lib/artifact-manager.js';
16
17
  // Import existing lib modules
17
18
  import SemanticSplitter from './lib/semantic-splitter.js';
18
19
  import SimpleVectorStore from './lib/simple-vector-store.js';
19
20
  import { MCPServer } from './lib/mcp-server.js';
20
21
  import AgentRuntime from './lib/agent-runtime.js';
21
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ function getProjectName(cwd) {
24
+ const packageJsonPath = join(cwd, 'package.json');
25
+ if (existsSync(packageJsonPath)) {
26
+ try {
27
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
28
+ if (typeof packageJson?.name === 'string' && packageJson.name.trim()) {
29
+ return packageJson.name.trim();
30
+ }
31
+ }
32
+ catch {
33
+ // Fall through to directory name
34
+ }
35
+ }
36
+ return basename(cwd);
37
+ }
22
38
  export class CntxServer {
23
39
  CWD;
24
40
  CNTX_DIR;
@@ -32,6 +48,7 @@ export class CntxServer {
32
48
  bundleManager;
33
49
  webSocketManager;
34
50
  apiRouter;
51
+ artifactManager;
35
52
  semanticSplitter;
36
53
  vectorStore;
37
54
  agentRuntime;
@@ -56,6 +73,7 @@ export class CntxServer {
56
73
  this.fileSystemManager = new FileSystemManager(cwd, { verbose: this.verbose });
57
74
  this.bundleManager = new BundleManager(this.configManager, this.fileSystemManager, this.verbose);
58
75
  this.webSocketManager = new WebSocketManager(this.bundleManager, this.configManager, { verbose: this.verbose });
76
+ this.artifactManager = new ArtifactManager(cwd);
59
77
  // AI Components
60
78
  this.semanticSplitter = new SemanticSplitter({
61
79
  maxChunkSize: 2000,
@@ -97,6 +115,7 @@ export class CntxServer {
97
115
  }
98
116
  // Load reasoning/manifest
99
117
  await this.agentRuntime.generateAgentManifest();
118
+ this.artifactManager.refresh();
100
119
  }
101
120
  startWatching() {
102
121
  this.fileSystemManager.startWatching((eventType, filename) => {
@@ -121,7 +140,7 @@ export class CntxServer {
121
140
  catch (e) { }
122
141
  // Fresh analysis
123
142
  const files = this.fileSystemManager.getAllFiles().map(f => this.fileSystemManager.relativePath(f))
124
- .filter(f => ['.js', '.jsx', '.ts', '.tsx', '.rs'].includes(extname(f).toLowerCase()));
143
+ .filter(f => ['.js', '.jsx', '.ts', '.tsx', '.rs', '.json', '.css', '.scss', '.html', '.sql', '.md', '.toml'].includes(extname(f).toLowerCase()));
125
144
  let bundleConfig = null;
126
145
  if (existsSync(this.configManager.CONFIG_FILE)) {
127
146
  bundleConfig = JSON.parse(readFileSync(this.configManager.CONFIG_FILE, 'utf8'));
@@ -137,15 +156,31 @@ export class CntxServer {
137
156
  async enhanceSemanticChunksIfNeeded(analysis) {
138
157
  if (!analysis || !analysis.chunks)
139
158
  return;
140
- if (!this.vectorStoreInitialized) {
141
- await this.vectorStore.init();
142
- this.vectorStoreInitialized = true;
143
- }
144
- for (const chunk of analysis.chunks) {
145
- if (!this.databaseManager.getEmbedding(chunk.id)) {
146
- await this.vectorStore.upsertChunk(chunk);
159
+ try {
160
+ if (!this.vectorStoreInitialized) {
161
+ await this.vectorStore.init();
162
+ this.vectorStoreInitialized = true;
163
+ }
164
+ let embedded = 0;
165
+ let skipped = 0;
166
+ for (const chunk of analysis.chunks) {
167
+ if (!this.databaseManager.getEmbedding(chunk.id)) {
168
+ try {
169
+ await this.vectorStore.upsertChunk(chunk);
170
+ embedded++;
171
+ }
172
+ catch (e) {
173
+ skipped++;
174
+ }
175
+ }
176
+ }
177
+ if (skipped > 0) {
178
+ console.warn(`⚠️ Embedded ${embedded} chunks, skipped ${skipped} (too large or invalid)`);
147
179
  }
148
180
  }
181
+ catch (e) {
182
+ console.error(`⚠️ Vector embedding failed: ${e.message}. Semantic search will be unavailable.`);
183
+ }
149
184
  }
150
185
  async refreshSemanticAnalysis() {
151
186
  this.databaseManager.db.prepare('DELETE FROM semantic_chunks').run();
@@ -178,7 +213,11 @@ export class CntxServer {
178
213
  });
179
214
  }
180
215
  handleStaticFile(req, res, url) {
181
- const webDir = join(__dirname, 'web/dist');
216
+ let webDir = join(__dirname, 'web', 'dist');
217
+ if (!existsSync(webDir)) {
218
+ // Fallback for dist/ context — web/dist is at package root
219
+ webDir = join(__dirname, '..', 'web', 'dist');
220
+ }
182
221
  let filePath = join(webDir, url.pathname === '/' ? 'index.html' : url.pathname);
183
222
  if (!existsSync(filePath)) {
184
223
  filePath = join(webDir, 'index.html');
@@ -294,7 +333,12 @@ export async function initConfig(cwd = process.cwd()) {
294
333
  console.log('📄 Created .cntxignore with smart defaults');
295
334
  }
296
335
  console.log('⚙️ Basic configuration initialized');
297
- const templateDir = join(__dirname, 'templates');
336
+ let templateDir = join(__dirname, 'templates');
337
+ if (!existsSync(templateDir)) {
338
+ // Fallback for dist/ context
339
+ templateDir = join(__dirname, '..', 'templates');
340
+ }
341
+ const projectName = getProjectName(cwd);
298
342
  // Copy agent configuration files
299
343
  const agentFiles = [
300
344
  'agent-config.yaml',
@@ -304,7 +348,14 @@ export async function initConfig(cwd = process.cwd()) {
304
348
  const sourcePath = join(templateDir, file);
305
349
  const destPath = join(server.CNTX_DIR, file);
306
350
  if (existsSync(sourcePath) && !existsSync(destPath)) {
307
- copyFileSync(sourcePath, destPath);
351
+ if (file === 'agent-config.yaml') {
352
+ const template = readFileSync(sourcePath, 'utf8');
353
+ const updated = template.replace(/^project:\s*["'].*?["']\s*$/m, `project: "${projectName}"`);
354
+ writeFileSync(destPath, updated, 'utf8');
355
+ }
356
+ else {
357
+ copyFileSync(sourcePath, destPath);
358
+ }
308
359
  console.log(`📄 Created ${file}`);
309
360
  }
310
361
  }
@@ -315,39 +366,6 @@ export async function initConfig(cwd = process.cwd()) {
315
366
  cpSync(agentRulesSource, agentRulesDest, { recursive: true });
316
367
  console.log('📁 Created agent-rules directory with templates');
317
368
  }
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
369
  return server.initMessages;
352
370
  }
353
371
  export async function generateBundle(name) {
@@ -360,17 +378,27 @@ export async function getStatus() {
360
378
  await server.init({ skipFileWatcher: true });
361
379
  const bundles = server.bundleManager.getAllBundleInfo();
362
380
  const totalFiles = server.fileSystemManager.getAllFiles().length;
381
+ // Resolve actual file counts from glob patterns (getAllBundleInfo only
382
+ // returns stored counts which are 0 until bundles are generated)
383
+ const bundlesWithCounts = await Promise.all(bundles.map(async (bundle) => {
384
+ if (bundle.fileCount === 0 && bundle.patterns?.length) {
385
+ const files = await server.bundleManager.resolveBundleFiles(bundle.name);
386
+ return { ...bundle, fileCount: files.length };
387
+ }
388
+ return bundle;
389
+ }));
363
390
  console.log('📊 cntx-ui Status');
364
391
  console.log('================');
365
392
  console.log(`Total files: ${totalFiles}`);
366
- console.log(`Bundles: ${bundles.length}`);
367
- bundles.forEach(bundle => {
368
- console.log(` • ${bundle.name}: ${bundle.fileCount} files (${Math.round(bundle.size / 1024)}KB)`);
393
+ console.log(`Bundles: ${bundlesWithCounts.length}`);
394
+ bundlesWithCounts.forEach(bundle => {
395
+ const sizeStr = bundle.size > 0 ? ` (${Math.round(bundle.size / 1024)}KB)` : '';
396
+ console.log(` • ${bundle.name}: ${bundle.fileCount} files${sizeStr}`);
369
397
  });
370
398
  return {
371
399
  totalFiles,
372
- bundles: bundles.length,
373
- bundleDetails: bundles
400
+ bundles: bundlesWithCounts.length,
401
+ bundleDetails: bundlesWithCounts
374
402
  };
375
403
  }
376
404
  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.0",
4
+ "version": "3.1.3",
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
- You have direct access to surgical intelligence tools. Refer to the **Intelligence Interface** section in `.cntx/AGENT.md` for full parameter schemas.
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) or `POST /api/semantic-search` (HTTP)
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) or `GET /api/bundles` (HTTP)
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) or `GET /api/status` (HTTP)
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 vector database first** for semantic discovery (`POST /api/vector-db/search`)
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: "theme dark mode styling colors"
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