cntx-ui 2.0.13 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +51 -339
  2. package/VISION.md +110 -0
  3. package/bin/cntx-ui-mcp.sh +3 -0
  4. package/bin/cntx-ui.js +138 -55
  5. package/lib/agent-runtime.js +301 -0
  6. package/lib/agent-tools.js +370 -0
  7. package/lib/api-router.js +1161 -0
  8. package/lib/bundle-manager.js +236 -0
  9. package/lib/configuration-manager.js +760 -0
  10. package/lib/database-manager.js +397 -0
  11. package/lib/file-system-manager.js +489 -0
  12. package/lib/heuristics-manager.js +527 -0
  13. package/lib/mcp-server.js +1125 -2
  14. package/lib/semantic-splitter.js +225 -491
  15. package/lib/simple-vector-store.js +98 -0
  16. package/lib/websocket-manager.js +470 -0
  17. package/package.json +19 -25
  18. package/server.js +742 -1935
  19. package/templates/TOOLS.md +41 -0
  20. package/templates/activities/README.md +67 -0
  21. package/templates/activities/activities/create-project-bundles/README.md +84 -0
  22. package/templates/activities/activities/create-project-bundles/notes.md +98 -0
  23. package/templates/activities/activities/create-project-bundles/progress.md +63 -0
  24. package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
  25. package/templates/activities/activities.json +219 -0
  26. package/templates/activities/lib/.markdownlint.jsonc +18 -0
  27. package/templates/activities/lib/create-activity.mdc +63 -0
  28. package/templates/activities/lib/generate-tasks.mdc +64 -0
  29. package/templates/activities/lib/process-task-list.mdc +52 -0
  30. package/templates/agent-config.yaml +65 -0
  31. package/templates/agent-instructions.md +234 -0
  32. package/templates/agent-rules/capabilities/activities-system.md +147 -0
  33. package/templates/agent-rules/capabilities/bundle-system.md +131 -0
  34. package/templates/agent-rules/capabilities/vector-search.md +135 -0
  35. package/templates/agent-rules/core/codebase-navigation.md +91 -0
  36. package/templates/agent-rules/core/performance-hierarchy.md +48 -0
  37. package/templates/agent-rules/core/response-formatting.md +120 -0
  38. package/templates/agent-rules/project-specific/architecture.md +145 -0
  39. package/templates/config.json +76 -0
  40. package/templates/hidden-files.json +14 -0
  41. package/web/dist/assets/index-B2OdTzzI.css +1 -0
  42. package/web/dist/assets/index-D0tBsKiR.js +2016 -0
  43. package/web/dist/cntx-ui.svg +18 -0
  44. package/web/dist/index.html +25 -8
  45. package/lib/semantic-integration.js +0 -441
  46. package/mcp-config-example.json +0 -9
  47. package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
  48. package/web/dist/assets/index-IUp4q_fr.css +0 -1
  49. package/web/dist/vite.svg +0 -21
@@ -0,0 +1,527 @@
1
+ /**
2
+ * Heuristics Manager - Centralized service for loading and applying heuristics
3
+ * Manages configuration-based code categorization logic
4
+ */
5
+
6
+ import { readFileSync, existsSync, watchFile } from 'fs'
7
+ import { join } from 'path'
8
+
9
+ export default class HeuristicsManager {
10
+ constructor(configPath = './heuristics-config.json') {
11
+ this.configPath = configPath
12
+ this.config = null
13
+ this.cache = new Map()
14
+ this.isWatching = false
15
+ this.lastLoaded = null
16
+ }
17
+
18
+ /**
19
+ * Load heuristics configuration from file
20
+ */
21
+ loadConfig() {
22
+ try {
23
+ if (!existsSync(this.configPath)) {
24
+ console.warn(`Heuristics config not found at ${this.configPath}, using fallback`)
25
+ this.config = this.getFallbackConfig()
26
+ return
27
+ }
28
+
29
+ const configContent = readFileSync(this.configPath, 'utf8')
30
+ const newConfig = JSON.parse(configContent)
31
+
32
+ // Validate config structure
33
+ this.validateConfig(newConfig)
34
+
35
+ this.config = newConfig
36
+ this.lastLoaded = Date.now()
37
+ this.cache.clear() // Clear cache when config changes
38
+
39
+ // Set up file watching if not already watching
40
+ if (!this.isWatching) {
41
+ this.setupFileWatcher()
42
+ }
43
+
44
+ console.log('✅ Heuristics configuration loaded successfully')
45
+ } catch (error) {
46
+ console.error('❌ Failed to load heuristics config:', error.message)
47
+ console.log('📦 Falling back to hardcoded heuristics')
48
+ this.config = this.getFallbackConfig()
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Validate heuristics configuration structure
54
+ */
55
+ validateConfig(config) {
56
+ const required = ['purposeHeuristics', 'bundleHeuristics', 'semanticTypeMapping']
57
+
58
+ for (const field of required) {
59
+ if (!config[field]) {
60
+ throw new Error(`Missing required field: ${field}`)
61
+ }
62
+ }
63
+
64
+ // Validate purpose heuristics structure
65
+ if (!config.purposeHeuristics.patterns || !config.purposeHeuristics.fallback) {
66
+ throw new Error('Invalid purposeHeuristics structure')
67
+ }
68
+
69
+ // Validate bundle heuristics structure
70
+ if (!config.bundleHeuristics.patterns || !config.bundleHeuristics.fallback) {
71
+ throw new Error('Invalid bundleHeuristics structure')
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Set up file watcher for config changes
77
+ */
78
+ setupFileWatcher() {
79
+ if (!existsSync(this.configPath)) return
80
+
81
+ watchFile(this.configPath, (curr, prev) => {
82
+ if (curr.mtime !== prev.mtime) {
83
+ console.log('📝 Heuristics config file changed, reloading...')
84
+ this.loadConfig()
85
+ }
86
+ })
87
+
88
+ this.isWatching = true
89
+ }
90
+
91
+ /**
92
+ * Get configuration, loading if necessary
93
+ */
94
+ getConfig() {
95
+ if (!this.config) {
96
+ this.loadConfig()
97
+ }
98
+ return this.config
99
+ }
100
+
101
+ /**
102
+ * Determine function purpose using configured heuristics
103
+ */
104
+ determinePurpose(func) {
105
+ const config = this.getConfig();
106
+ const name = func.name.toLowerCase();
107
+ const pathParts = func.pathParts || [];
108
+
109
+ // console.log(`🔍 Determining purpose for: ${func.name} in ${pathParts.join('/')}`);
110
+
111
+ // Check each purpose pattern
112
+ for (const [patternName, pattern] of Object.entries(config.purposeHeuristics.patterns)) {
113
+ if (this.evaluateConditions(pattern.conditions, { func, name, pathParts })) {
114
+ // console.log(` ✅ Matched pattern: ${patternName} -> ${pattern.purpose}`);
115
+ return pattern.purpose;
116
+ }
117
+ }
118
+
119
+ // Return fallback
120
+ return config.purposeHeuristics.fallback.purpose;
121
+ }
122
+
123
+ /**
124
+ * Infer business domains (e.g. auth, editing, file-mgmt)
125
+ */
126
+ inferBusinessDomains(func) {
127
+ const domains = new Set();
128
+ const name = func.name.toLowerCase();
129
+ const path = (func.pathParts || []).join('/').toLowerCase();
130
+ const imports = func.includes?.imports || [];
131
+
132
+ // Path-based domains
133
+ if (path.includes('auth')) domains.add('authentication');
134
+ if (path.includes('component') || path.includes('ui')) domains.add('ui-layer');
135
+ if (path.includes('service') || path.includes('api')) domains.add('api-integration');
136
+ if (path.includes('test') || path.includes('spec')) domains.add('testing');
137
+
138
+ // Import-based domains
139
+ if (imports.some(i => i.includes('tauri'))) domains.add('desktop-runtime');
140
+ if (imports.some(i => i.includes('tiptap') || i.includes('prosemirror'))) domains.add('text-editing');
141
+ if (imports.some(i => i.includes('react'))) domains.add('frontend-ui');
142
+
143
+ // Name-based domains
144
+ if (/file|save|export|read|write/i.test(name)) domains.add('file-management');
145
+ if (/login|user|session/i.test(name)) domains.add('authentication');
146
+
147
+ return Array.from(domains);
148
+ }
149
+
150
+ /**
151
+ * Infer technical patterns (e.g. hooks, async-io, event-handlers)
152
+ */
153
+ inferTechnicalPatterns(func) {
154
+ const patterns = new Set();
155
+ const name = func.name.toLowerCase();
156
+ const code = func.code || '';
157
+
158
+ if (name.startsWith('use')) patterns.add('react-hooks');
159
+ if (code.includes('async') || code.includes('await')) patterns.add('async-io');
160
+ if (code.includes('on(') || code.includes('addListener') || name.startsWith('handle')) patterns.add('event-driven');
161
+ if (code.includes('new ') || code.includes('class ')) patterns.add('object-oriented');
162
+ if (func.isExported) patterns.add('public-api');
163
+
164
+ return Array.from(patterns);
165
+ }
166
+
167
+ /**
168
+ * Suggest bundles for file using configured heuristics
169
+ */
170
+ suggestBundlesForFile(filePath) {
171
+ const config = this.getConfig()
172
+ const fileName = filePath.toLowerCase()
173
+ const pathParts = fileName.split('/')
174
+ const suggestions = []
175
+
176
+ // Check each bundle pattern
177
+ for (const [patternName, pattern] of Object.entries(config.bundleHeuristics.patterns)) {
178
+ if (this.evaluateConditions(pattern.conditions, { fileName, filePath, pathParts })) {
179
+ suggestions.push(pattern.bundle)
180
+
181
+ // Check sub-patterns
182
+ if (pattern.subPatterns) {
183
+ for (const [subName, subPattern] of Object.entries(pattern.subPatterns)) {
184
+ if (this.evaluateConditions(subPattern.conditions, { fileName, filePath, pathParts })) {
185
+ suggestions.push(subPattern.bundle)
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ // Apply fallback logic if no suggestions
193
+ if (suggestions.length === 0) {
194
+ const fallback = config.bundleHeuristics.fallback
195
+
196
+ if (fallback.webFallback && this.evaluateConditions(fallback.webFallback.conditions, { fileName, filePath, pathParts })) {
197
+ suggestions.push(fallback.webFallback.bundle)
198
+ } else if (fallback.defaultFallback) {
199
+ suggestions.push(...fallback.defaultFallback.bundles)
200
+ }
201
+ }
202
+
203
+ return [...new Set(suggestions)] // Remove duplicates
204
+ }
205
+
206
+ /**
207
+ * Get semantic type cluster mapping
208
+ */
209
+ getSemanticTypeMapping() {
210
+ const config = this.getConfig()
211
+ const mapping = {}
212
+
213
+ for (const [clusterName, cluster] of Object.entries(config.semanticTypeMapping.clusters)) {
214
+ for (const type of cluster.types) {
215
+ mapping[type] = cluster.clusterId
216
+ }
217
+ }
218
+
219
+ return mapping
220
+ }
221
+
222
+ /**
223
+ * Evaluate condition strings against context
224
+ */
225
+ evaluateConditions(conditions, context) {
226
+ if (!Array.isArray(conditions)) {
227
+ conditions = [conditions]
228
+ }
229
+
230
+ // For purpose heuristics, we generally want high precision, so use AND logic
231
+ // if there are multiple conditions
232
+ const needsAndLogic = conditions.length > 1 || this.requiresAndLogic(conditions)
233
+
234
+ if (needsAndLogic) {
235
+ return conditions.every(condition => {
236
+ try {
237
+ return this.evaluateCondition(condition, context)
238
+ } catch (error) {
239
+ console.warn(`Failed to evaluate condition: ${condition}`, error)
240
+ return false
241
+ }
242
+ })
243
+ } else {
244
+ return conditions.some(condition => {
245
+ try {
246
+ return this.evaluateCondition(condition, context)
247
+ } catch (error) {
248
+ console.warn(`Failed to evaluate condition: ${condition}`, error)
249
+ return false
250
+ }
251
+ })
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Determine if conditions require AND logic vs OR logic
257
+ */
258
+ requiresAndLogic(conditions) {
259
+ // React hook pattern specifically needs AND logic
260
+ if (conditions.length === 2 &&
261
+ conditions.some(c => c.includes('name.startsWith')) &&
262
+ conditions.some(c => c.includes('func.type'))) {
263
+ return true
264
+ }
265
+
266
+ // Frontend pattern needs AND logic for web + src
267
+ if (conditions.length === 2 &&
268
+ conditions.some(c => c.includes("pathParts.includes('web')")) &&
269
+ conditions.some(c => c.includes("pathParts.includes('src')"))) {
270
+ return true
271
+ }
272
+
273
+ // Default to OR logic for other patterns
274
+ return false
275
+ }
276
+
277
+ /**
278
+ * Evaluate a single condition
279
+ */
280
+ evaluateCondition(condition, context) {
281
+ const { func, name, fileName, filePath, pathParts } = context
282
+
283
+ // Handle function type conditions
284
+ if (condition.includes('func.type ===')) {
285
+ const typeMatch = condition.match(/func\.type === ['"]([^'"]+)['"]/)
286
+ if (typeMatch && func) {
287
+ return func.type === typeMatch[1]
288
+ }
289
+ }
290
+
291
+ // Handle name-based conditions
292
+ if (condition.includes('name.startsWith(')) {
293
+ const prefixMatch = condition.match(/name\.startsWith\(['"]([^'"]+)['"]\)/)
294
+ if (prefixMatch && name) {
295
+ return name.startsWith(prefixMatch[1])
296
+ }
297
+ }
298
+
299
+ if (condition.includes('name.includes(')) {
300
+ const includesMatch = condition.match(/name\.includes\(['"]([^'"]+)['"]\)/)
301
+ if (includesMatch && name) {
302
+ return name.includes(includesMatch[1])
303
+ }
304
+ }
305
+
306
+ // Handle fileName conditions
307
+ if (condition.includes('fileName.includes(')) {
308
+ const includesMatch = condition.match(/fileName\.includes\(['"]([^'"]+)['"]\)/)
309
+ if (includesMatch && fileName) {
310
+ return fileName.includes(includesMatch[1])
311
+ }
312
+ }
313
+
314
+ if (condition.includes('fileName.endsWith(')) {
315
+ const endsWithMatch = condition.match(/fileName\.endsWith\(['"]([^'"]+)['"]\)/)
316
+ if (endsWithMatch && fileName) {
317
+ return fileName.endsWith(endsWithMatch[1])
318
+ }
319
+ }
320
+
321
+ // Handle pathParts conditions
322
+ if (condition.includes('pathParts.includes(')) {
323
+ const includesMatch = condition.match(/pathParts\.includes\(['"]([^'"]+)['"]\)/)
324
+ if (includesMatch && pathParts) {
325
+ return pathParts.includes(includesMatch[1])
326
+ }
327
+ }
328
+
329
+ // New: Check imports in the chunk context
330
+ if (condition.includes('chunk.imports.includes(')) {
331
+ const importMatch = condition.match(/chunk\.imports\.includes\(['"]([^'"]+)['"]\)/)
332
+ if (importMatch && func.includes?.imports) {
333
+ return func.includes.imports.some(imp => imp.includes(importMatch[1]))
334
+ }
335
+ }
336
+
337
+ // New: Check for specific naming patterns (case-insensitive)
338
+ if (condition.includes('name.matches(')) {
339
+ const regexMatch = condition.match(/name\.matches\(['"]([^'"]+)['"]\)/)
340
+ if (regexMatch && name) {
341
+ return new RegExp(regexMatch[1], 'i').test(name)
342
+ }
343
+ }
344
+
345
+ return false
346
+ }
347
+
348
+ /**
349
+ * Fallback configuration for when config file is unavailable
350
+ */
351
+ getFallbackConfig() {
352
+ return {
353
+ version: "1.0.0",
354
+ purposeHeuristics: {
355
+ patterns: {
356
+ reactComponent: {
357
+ conditions: ["func.type === 'react_component'"],
358
+ purpose: "React component",
359
+ confidence: 0.95
360
+ },
361
+ reactHook: {
362
+ conditions: ["name.startsWith('use')", "func.type === 'function'"],
363
+ purpose: "React hook",
364
+ confidence: 0.9
365
+ },
366
+ serviceLayer: {
367
+ conditions: ["pathParts.includes('services')"],
368
+ purpose: "Service layer logic",
369
+ confidence: 0.7
370
+ },
371
+ componentLayer: {
372
+ conditions: ["pathParts.includes('components')"],
373
+ purpose: "UI component logic",
374
+ confidence: 0.7
375
+ },
376
+ tauriCommand: {
377
+ conditions: ["name.matches('command')", "chunk.imports.includes('tauri')"],
378
+ purpose: "Tauri backend command",
379
+ confidence: 0.9
380
+ },
381
+ textEditing: {
382
+ conditions: ["chunk.imports.includes('tiptap')", "chunk.imports.includes('prosemirror')"],
383
+ purpose: "Rich text editing logic",
384
+ confidence: 0.95
385
+ },
386
+ fileManagement: {
387
+ conditions: ["pathParts.includes('services')", "name.matches('file|export|save')"],
388
+ purpose: "File system service",
389
+ confidence: 0.85
390
+ },
391
+ uiComponent: {
392
+ conditions: ["pathParts.includes('components')", "name.matches('modal|button|menu|bar')"],
393
+ purpose: "UI component logic",
394
+ confidence: 0.85
395
+ },
396
+ apiHandler: {
397
+ conditions: ["name.includes('api')", "name.includes('endpoint')"],
398
+ purpose: "API handler",
399
+ confidence: 0.85
400
+ },
401
+ dataRetrieval: {
402
+ conditions: ["name.includes('get')", "name.includes('fetch')"],
403
+ purpose: "Data retrieval",
404
+ confidence: 0.8
405
+ },
406
+ dataCreation: {
407
+ conditions: ["name.includes('create')", "name.includes('add')"],
408
+ purpose: "Data creation",
409
+ confidence: 0.8
410
+ },
411
+ dataModification: {
412
+ conditions: ["name.includes('update')", "name.includes('edit')"],
413
+ purpose: "Data modification",
414
+ confidence: 0.8
415
+ },
416
+ dataDeletion: {
417
+ conditions: ["name.includes('delete')", "name.includes('remove')"],
418
+ purpose: "Data deletion",
419
+ confidence: 0.8
420
+ },
421
+ validation: {
422
+ conditions: ["name.includes('validate')", "name.includes('check')"],
423
+ purpose: "Validation",
424
+ confidence: 0.75
425
+ },
426
+ dataProcessing: {
427
+ conditions: ["name.includes('parse')", "name.includes('format')"],
428
+ purpose: "Data processing",
429
+ confidence: 0.75
430
+ }
431
+ },
432
+ fallback: {
433
+ purpose: "Utility function",
434
+ confidence: 0.5
435
+ }
436
+ },
437
+ bundleHeuristics: {
438
+ patterns: {
439
+ frontend: {
440
+ conditions: ["pathParts.includes('web')", "pathParts.includes('src')"],
441
+ bundle: "frontend",
442
+ confidence: 0.8,
443
+ subPatterns: {
444
+ uiComponents: {
445
+ conditions: ["pathParts.includes('components')"],
446
+ bundle: "ui-components",
447
+ confidence: 0.9
448
+ }
449
+ }
450
+ },
451
+ server: {
452
+ conditions: ["fileName.includes('server')", "fileName.includes('api')", "pathParts.includes('bin')"],
453
+ bundle: "server",
454
+ confidence: 0.85
455
+ },
456
+ configuration: {
457
+ conditions: ["fileName.includes('config')", "fileName.includes('setup')", "fileName.endsWith('.json')", "fileName.endsWith('.sh')", "fileName.includes('package')"],
458
+ bundle: "config",
459
+ confidence: 0.9
460
+ },
461
+ documentation: {
462
+ conditions: ["fileName.endsWith('.md')", "fileName.includes('doc')", "fileName.includes('readme')"],
463
+ bundle: "docs",
464
+ confidence: 0.95
465
+ }
466
+ },
467
+ fallback: {
468
+ webFallback: {
469
+ conditions: ["pathParts.includes('web')"],
470
+ bundle: "frontend",
471
+ confidence: 0.6
472
+ },
473
+ defaultFallback: {
474
+ bundles: ["server", "config"],
475
+ confidence: 0.4
476
+ }
477
+ }
478
+ },
479
+ semanticTypeMapping: {
480
+ clusters: {
481
+ businessLogic: { types: ["business_logic", "algorithm"], clusterId: 0 },
482
+ dataLayer: { types: ["data_processing", "database"], clusterId: 1 },
483
+ apiLayer: { types: ["api_integration", "middleware", "routing"], clusterId: 2 },
484
+ uiLayer: { types: ["ui_component", "page_component", "layout_component", "hook"], clusterId: 3 },
485
+ utilities: { types: ["utility", "configuration", "function", "type_definition"], clusterId: 4 },
486
+ testing: { types: ["testing", "documentation", "monitoring"], clusterId: 5 },
487
+ infrastructure: { types: ["error_handling", "performance", "security"], clusterId: 6 },
488
+ unknown: { types: ["unknown"], clusterId: 7 }
489
+ }
490
+ }
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Update configuration (for API endpoints) and persist to disk
496
+ */
497
+ async updateConfig(newConfig) {
498
+ try {
499
+ this.validateConfig(newConfig)
500
+
501
+ // Write to disk
502
+ writeFileSync(this.configPath, JSON.stringify(newConfig, null, 2), 'utf8')
503
+
504
+ this.config = newConfig
505
+ this.cache.clear()
506
+ this.lastLoaded = Date.now()
507
+
508
+ console.log('📝 Heuristics configuration updated and saved to disk')
509
+ return true
510
+ } catch (error) {
511
+ console.error('❌ Failed to update heuristics config:', error.message)
512
+ throw error
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Get performance metrics
518
+ */
519
+ getPerformanceMetrics() {
520
+ // TODO: Implement performance tracking
521
+ return {
522
+ totalEvaluations: 0,
523
+ accuracyScore: 0.0,
524
+ lastUpdated: this.lastLoaded
525
+ }
526
+ }
527
+ }