cntx-ui 2.0.15 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -344
- package/bin/cntx-ui-mcp.sh +3 -0
- package/bin/cntx-ui.js +2 -1
- package/lib/agent-runtime.js +161 -1340
- package/lib/agent-tools.js +9 -7
- package/lib/api-router.js +262 -79
- package/lib/bundle-manager.js +172 -407
- package/lib/configuration-manager.js +94 -59
- package/lib/database-manager.js +397 -0
- package/lib/file-system-manager.js +17 -0
- package/lib/heuristics-manager.js +119 -17
- package/lib/mcp-server.js +125 -55
- package/lib/semantic-splitter.js +222 -481
- package/lib/simple-vector-store.js +69 -300
- package/package.json +18 -31
- package/server.js +151 -73
- package/templates/TOOLS.md +41 -0
- package/templates/activities/activities/create-project-bundles/README.md +4 -3
- package/templates/activities/activities/create-project-bundles/notes.md +15 -19
- package/templates/activities/activities/create-project-bundles/tasks.md +4 -4
- package/templates/activities/activities.json +1 -1
- package/templates/agent-config.yaml +0 -13
- package/templates/agent-instructions.md +22 -6
- package/templates/agent-rules/capabilities/bundle-system.md +1 -1
- package/templates/agent-rules/project-specific/architecture.md +1 -1
- package/web/dist/assets/index-B2OdTzzI.css +1 -0
- package/web/dist/assets/index-D0tBsKiR.js +2016 -0
- package/web/dist/index.html +2 -2
- package/mcp-config-example.json +0 -9
- package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +0 -1
- package/web/dist/assets/index-dF3qg-y_.js +0 -2486
- package/web/dist/assets/index-h5FGSg_P.css +0 -1
|
@@ -102,18 +102,66 @@ export default class HeuristicsManager {
|
|
|
102
102
|
* Determine function purpose using configured heuristics
|
|
103
103
|
*/
|
|
104
104
|
determinePurpose(func) {
|
|
105
|
-
const config = this.getConfig()
|
|
106
|
-
const name = func.name.toLowerCase()
|
|
105
|
+
const config = this.getConfig();
|
|
106
|
+
const name = func.name.toLowerCase();
|
|
107
|
+
const pathParts = func.pathParts || [];
|
|
107
108
|
|
|
109
|
+
// console.log(`🔍 Determining purpose for: ${func.name} in ${pathParts.join('/')}`);
|
|
110
|
+
|
|
108
111
|
// Check each purpose pattern
|
|
109
112
|
for (const [patternName, pattern] of Object.entries(config.purposeHeuristics.patterns)) {
|
|
110
|
-
if (this.evaluateConditions(pattern.conditions, { func, name })) {
|
|
111
|
-
|
|
113
|
+
if (this.evaluateConditions(pattern.conditions, { func, name, pathParts })) {
|
|
114
|
+
// console.log(` ✅ Matched pattern: ${patternName} -> ${pattern.purpose}`);
|
|
115
|
+
return pattern.purpose;
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
// Return fallback
|
|
116
|
-
return config.purposeHeuristics.fallback.purpose
|
|
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);
|
|
117
165
|
}
|
|
118
166
|
|
|
119
167
|
/**
|
|
@@ -179,9 +227,9 @@ export default class HeuristicsManager {
|
|
|
179
227
|
conditions = [conditions]
|
|
180
228
|
}
|
|
181
229
|
|
|
182
|
-
// For
|
|
183
|
-
//
|
|
184
|
-
const needsAndLogic = this.requiresAndLogic(conditions)
|
|
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)
|
|
185
233
|
|
|
186
234
|
if (needsAndLogic) {
|
|
187
235
|
return conditions.every(condition => {
|
|
@@ -277,6 +325,22 @@ export default class HeuristicsManager {
|
|
|
277
325
|
return pathParts.includes(includesMatch[1])
|
|
278
326
|
}
|
|
279
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
|
+
}
|
|
280
344
|
|
|
281
345
|
return false
|
|
282
346
|
}
|
|
@@ -299,6 +363,36 @@ export default class HeuristicsManager {
|
|
|
299
363
|
purpose: "React hook",
|
|
300
364
|
confidence: 0.9
|
|
301
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
|
+
},
|
|
302
396
|
apiHandler: {
|
|
303
397
|
conditions: ["name.includes('api')", "name.includes('endpoint')"],
|
|
304
398
|
purpose: "API handler",
|
|
@@ -398,17 +492,25 @@ export default class HeuristicsManager {
|
|
|
398
492
|
}
|
|
399
493
|
|
|
400
494
|
/**
|
|
401
|
-
* Update configuration (for API endpoints)
|
|
495
|
+
* Update configuration (for API endpoints) and persist to disk
|
|
402
496
|
*/
|
|
403
497
|
async updateConfig(newConfig) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
+
}
|
|
412
514
|
}
|
|
413
515
|
|
|
414
516
|
/**
|
package/lib/mcp-server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
2
|
-
import { join, relative } from 'path';
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync, copyFileSync } from 'fs';
|
|
2
|
+
import { join, relative, dirname } from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
3
4
|
import AgentRuntime from './agent-runtime.js';
|
|
4
5
|
|
|
5
6
|
export class MCPServer {
|
|
@@ -54,6 +55,12 @@ export class MCPServer {
|
|
|
54
55
|
case 'tools/call':
|
|
55
56
|
return this.handleCallTool(params, id);
|
|
56
57
|
|
|
58
|
+
case 'prompts/list':
|
|
59
|
+
return this.handleListPrompts(id);
|
|
60
|
+
|
|
61
|
+
case 'prompts/get':
|
|
62
|
+
return this.handleGetPrompt(params, id);
|
|
63
|
+
|
|
57
64
|
case 'prompts/list':
|
|
58
65
|
return this.createErrorResponse(id, -32601, 'Method not found');
|
|
59
66
|
|
|
@@ -159,6 +166,62 @@ export class MCPServer {
|
|
|
159
166
|
}
|
|
160
167
|
}
|
|
161
168
|
|
|
169
|
+
// List available prompts
|
|
170
|
+
handleListPrompts(id) {
|
|
171
|
+
const prompts = [
|
|
172
|
+
{
|
|
173
|
+
name: 'onboard-me',
|
|
174
|
+
description: 'Guided walkthrough of the current codebase architecture.',
|
|
175
|
+
arguments: []
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'refactor-planner',
|
|
179
|
+
description: 'Plan a refactor by analyzing semantic dependencies.',
|
|
180
|
+
arguments: [
|
|
181
|
+
{ name: 'target', description: 'The function or file to refactor', required: true }
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
];
|
|
185
|
+
return this.createSuccessResponse(id, { prompts });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Get a specific prompt
|
|
189
|
+
handleGetPrompt(params, id) {
|
|
190
|
+
const { name, arguments: args } = params;
|
|
191
|
+
|
|
192
|
+
if (name === 'onboard-me') {
|
|
193
|
+
return this.createSuccessResponse(id, {
|
|
194
|
+
description: 'Codebase Onboarding',
|
|
195
|
+
messages: [
|
|
196
|
+
{
|
|
197
|
+
role: 'user',
|
|
198
|
+
content: {
|
|
199
|
+
type: 'text',
|
|
200
|
+
text: 'Please perform a comprehensive discovery of this codebase using the `agent/discover` tool. Focus on identifying the primary entry points and the core architectural patterns. Use the results to explain how the project is organized.'
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (name === 'refactor-planner') {
|
|
208
|
+
return this.createSuccessResponse(id, {
|
|
209
|
+
description: 'Refactor Planning',
|
|
210
|
+
messages: [
|
|
211
|
+
{
|
|
212
|
+
role: 'user',
|
|
213
|
+
content: {
|
|
214
|
+
type: 'text',
|
|
215
|
+
text: `I want to refactor ${args.target}. Use the \`agent/query\` tool to find all semantic chunks related to this target and the \`agent/investigate\` tool to identify potential impact on other modules. Present a step-by-step refactoring plan.`
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return this.createErrorResponse(id, -32602, 'Prompt not found');
|
|
223
|
+
}
|
|
224
|
+
|
|
162
225
|
// List available tools
|
|
163
226
|
handleListTools(id) {
|
|
164
227
|
const tools = [
|
|
@@ -912,22 +975,20 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
|
|
|
912
975
|
return this.createErrorResponse(id, -32602, `Bundle '${name}' already exists`);
|
|
913
976
|
}
|
|
914
977
|
|
|
915
|
-
//
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
config.bundles[name] = patterns;
|
|
978
|
+
// Create bundle in bundle-states.json (single source of truth)
|
|
979
|
+
this.cntxServer.configManager.bundleStates.set(name, {
|
|
980
|
+
patterns: patterns,
|
|
981
|
+
files: [],
|
|
982
|
+
content: '',
|
|
983
|
+
changed: false,
|
|
984
|
+
size: 0,
|
|
985
|
+
generated: null
|
|
986
|
+
});
|
|
925
987
|
|
|
926
|
-
// Save
|
|
927
|
-
|
|
988
|
+
// Save bundle states
|
|
989
|
+
this.cntxServer.configManager.saveBundleStates();
|
|
928
990
|
|
|
929
|
-
//
|
|
930
|
-
this.cntxServer.loadConfig();
|
|
991
|
+
// Regenerate bundles
|
|
931
992
|
this.cntxServer.generateAllBundles();
|
|
932
993
|
|
|
933
994
|
const result = {
|
|
@@ -969,18 +1030,15 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
|
|
|
969
1030
|
return this.createErrorResponse(id, -32602, 'Cannot update master bundle');
|
|
970
1031
|
}
|
|
971
1032
|
|
|
972
|
-
//
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
// Update bundle patterns
|
|
977
|
-
config.bundles[name] = patterns;
|
|
1033
|
+
// Update bundle in bundle-states.json (single source of truth)
|
|
1034
|
+
const bundle = this.cntxServer.configManager.bundleStates.get(name);
|
|
1035
|
+
bundle.patterns = patterns;
|
|
1036
|
+
bundle.changed = true;
|
|
978
1037
|
|
|
979
|
-
// Save
|
|
980
|
-
|
|
1038
|
+
// Save bundle states
|
|
1039
|
+
this.cntxServer.configManager.saveBundleStates();
|
|
981
1040
|
|
|
982
|
-
//
|
|
983
|
-
this.cntxServer.loadConfig();
|
|
1041
|
+
// Regenerate bundles
|
|
984
1042
|
this.cntxServer.generateAllBundles();
|
|
985
1043
|
|
|
986
1044
|
const result = {
|
|
@@ -1022,18 +1080,13 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
|
|
|
1022
1080
|
return this.createErrorResponse(id, -32602, 'Cannot delete master bundle');
|
|
1023
1081
|
}
|
|
1024
1082
|
|
|
1025
|
-
//
|
|
1026
|
-
|
|
1027
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1028
|
-
|
|
1029
|
-
// Remove bundle
|
|
1030
|
-
delete config.bundles[name];
|
|
1083
|
+
// Remove bundle from bundle-states.json (single source of truth)
|
|
1084
|
+
this.cntxServer.configManager.bundleStates.delete(name);
|
|
1031
1085
|
|
|
1032
|
-
// Save
|
|
1033
|
-
|
|
1086
|
+
// Save bundle states
|
|
1087
|
+
this.cntxServer.configManager.saveBundleStates();
|
|
1034
1088
|
|
|
1035
|
-
//
|
|
1036
|
-
this.cntxServer.loadConfig();
|
|
1089
|
+
// Regenerate bundles
|
|
1037
1090
|
this.cntxServer.generateAllBundles();
|
|
1038
1091
|
|
|
1039
1092
|
const result = {
|
|
@@ -1188,36 +1241,53 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
|
|
|
1188
1241
|
|
|
1189
1242
|
// New tool implementations
|
|
1190
1243
|
async toolReadFile(args, id) {
|
|
1191
|
-
const { path, includeMetadata = true } = args;
|
|
1244
|
+
const { path: filePath, includeMetadata = true } = args;
|
|
1192
1245
|
|
|
1193
|
-
if (!
|
|
1246
|
+
if (!filePath) {
|
|
1194
1247
|
return this.createErrorResponse(id, -32602, 'Path is required');
|
|
1195
1248
|
}
|
|
1196
1249
|
|
|
1197
1250
|
try {
|
|
1198
|
-
const fullPath = join(this.cntxServer.CWD,
|
|
1251
|
+
const fullPath = join(this.cntxServer.CWD, filePath);
|
|
1199
1252
|
|
|
1200
1253
|
if (!existsSync(fullPath)) {
|
|
1201
|
-
return this.createErrorResponse(id, -32602, `File not found: ${
|
|
1254
|
+
return this.createErrorResponse(id, -32602, `File not found: ${filePath}`);
|
|
1202
1255
|
}
|
|
1203
1256
|
|
|
1204
1257
|
const content = readFileSync(fullPath, 'utf8');
|
|
1205
|
-
|
|
1258
|
+
|
|
1259
|
+
// Fetch semantic context for this file
|
|
1260
|
+
const chunks = this.cntxServer.databaseManager.getChunksByFile(filePath);
|
|
1261
|
+
const totalComplexity = chunks.reduce((sum, c) => sum + (c.complexity?.score || 0), 0);
|
|
1262
|
+
const avgComplexity = chunks.length > 0 ? Math.round(totalComplexity / chunks.length) : 0;
|
|
1263
|
+
|
|
1264
|
+
let semanticHeader = `--- SEMANTIC CONTEXT ---\n`;
|
|
1265
|
+
semanticHeader += `File importance: ${chunks.length} semantic chunks found.\n`;
|
|
1266
|
+
semanticHeader += `Aggregate Complexity: ${totalComplexity} (Avg: ${avgComplexity})\n`;
|
|
1267
|
+
if (chunks.length > 0) {
|
|
1268
|
+
semanticHeader += `Primary purposes: ${[...new Set(chunks.map(c => c.purpose))].join(', ')}\n`;
|
|
1269
|
+
}
|
|
1270
|
+
semanticHeader += `------------------------\n\n`;
|
|
1271
|
+
|
|
1272
|
+
const result = {
|
|
1273
|
+
path: filePath,
|
|
1274
|
+
content: semanticHeader + content
|
|
1275
|
+
};
|
|
1206
1276
|
|
|
1207
1277
|
if (includeMetadata) {
|
|
1208
|
-
const stats =
|
|
1278
|
+
const stats = fs.statSync(fullPath);
|
|
1209
1279
|
const bundles = [];
|
|
1210
1280
|
|
|
1211
1281
|
// Find which bundles include this file
|
|
1212
1282
|
this.cntxServer.bundles.forEach((bundle, name) => {
|
|
1213
|
-
if (bundle.files && bundle.files.includes(
|
|
1283
|
+
if (bundle.files && bundle.files.includes(filePath)) {
|
|
1214
1284
|
bundles.push(name);
|
|
1215
1285
|
}
|
|
1216
1286
|
});
|
|
1217
1287
|
|
|
1218
1288
|
result.metadata = {
|
|
1219
1289
|
size: stats.size,
|
|
1220
|
-
mimeType: this.getMimeType(
|
|
1290
|
+
mimeType: this.getMimeType(filePath),
|
|
1221
1291
|
modified: stats.mtime.toISOString(),
|
|
1222
1292
|
lines: content.split('\n').length,
|
|
1223
1293
|
bundles: bundles
|
|
@@ -1225,7 +1295,7 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
|
|
|
1225
1295
|
}
|
|
1226
1296
|
|
|
1227
1297
|
return this.createSuccessResponse(id, {
|
|
1228
|
-
|
|
1298
|
+
content: [{
|
|
1229
1299
|
type: 'text',
|
|
1230
1300
|
text: JSON.stringify(result, null, 2)
|
|
1231
1301
|
}]
|
|
@@ -1236,25 +1306,25 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
|
|
|
1236
1306
|
}
|
|
1237
1307
|
|
|
1238
1308
|
async toolWriteFile(args, id) {
|
|
1239
|
-
const { path, content, backup = true, createDirs = true } = args;
|
|
1309
|
+
const { path: filePath, content, backup = true, createDirs = true } = args;
|
|
1240
1310
|
|
|
1241
|
-
if (!
|
|
1311
|
+
if (!filePath || content === undefined) {
|
|
1242
1312
|
return this.createErrorResponse(id, -32602, 'Path and content are required');
|
|
1243
1313
|
}
|
|
1244
1314
|
|
|
1245
1315
|
try {
|
|
1246
|
-
const fullPath = join(this.cntxServer.CWD,
|
|
1247
|
-
const parentDir =
|
|
1316
|
+
const fullPath = join(this.cntxServer.CWD, filePath);
|
|
1317
|
+
const parentDir = dirname(fullPath);
|
|
1248
1318
|
|
|
1249
1319
|
// Create parent directories if needed
|
|
1250
1320
|
if (createDirs && !existsSync(parentDir)) {
|
|
1251
|
-
|
|
1321
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
1252
1322
|
}
|
|
1253
1323
|
|
|
1254
1324
|
// Create backup if file exists
|
|
1255
1325
|
if (backup && existsSync(fullPath)) {
|
|
1256
1326
|
const backupPath = `${fullPath}.backup.${Date.now()}`;
|
|
1257
|
-
|
|
1327
|
+
fs.copyFileSync(fullPath, backupPath);
|
|
1258
1328
|
}
|
|
1259
1329
|
|
|
1260
1330
|
// Write the file
|
|
@@ -1269,13 +1339,13 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
|
|
|
1269
1339
|
}
|
|
1270
1340
|
});
|
|
1271
1341
|
|
|
1272
|
-
const stats =
|
|
1342
|
+
const stats = fs.statSync(fullPath);
|
|
1273
1343
|
|
|
1274
1344
|
return this.createSuccessResponse(id, {
|
|
1275
|
-
|
|
1345
|
+
content: [{
|
|
1276
1346
|
type: 'text',
|
|
1277
1347
|
text: JSON.stringify({
|
|
1278
|
-
path,
|
|
1348
|
+
path: filePath,
|
|
1279
1349
|
written: true,
|
|
1280
1350
|
size: stats.size,
|
|
1281
1351
|
modified: stats.mtime.toISOString()
|
|
@@ -1390,7 +1460,7 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
|
|
|
1390
1460
|
}
|
|
1391
1461
|
|
|
1392
1462
|
return this.createSuccessResponse(id, {
|
|
1393
|
-
|
|
1463
|
+
content: [{
|
|
1394
1464
|
type: 'text',
|
|
1395
1465
|
text: JSON.stringify(result, null, 2)
|
|
1396
1466
|
}]
|