50c 4.1.0 → 4.1.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.
@@ -1,301 +0,0 @@
1
- /**
2
- * FILE MEMORY - Local file indexing for LLM assistance
3
- * No dependencies, pure Node.js
4
- *
5
- * Helps LLM not "phone it in" on 4000-8000 line files by providing:
6
- * - Function/class locations
7
- * - Line range retrieval
8
- * - Keyword search
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const os = require('os');
14
-
15
- const INDEX_DIR = path.join(os.homedir(), '.50c', 'file_index');
16
-
17
- // Ensure index directory exists
18
- if (!fs.existsSync(INDEX_DIR)) {
19
- fs.mkdirSync(INDEX_DIR, { recursive: true });
20
- }
21
-
22
- function getIndexPath(filepath) {
23
- const hash = require('crypto').createHash('md5')
24
- .update(filepath).digest('hex').slice(0, 12);
25
- return path.join(INDEX_DIR, `${hash}.json`);
26
- }
27
-
28
- function extractSymbols(lines) {
29
- const symbols = [];
30
- lines.forEach((line, i) => {
31
- const trimmed = line.trim();
32
- const lineNum = i + 1;
33
-
34
- // Python
35
- if (trimmed.startsWith('def ')) {
36
- const name = trimmed.slice(4).split('(')[0].trim();
37
- symbols.push({ name, type: 'function', line: lineNum, sig: trimmed.split(':')[0] });
38
- }
39
- else if (trimmed.startsWith('async def ')) {
40
- const name = trimmed.slice(10).split('(')[0].trim();
41
- symbols.push({ name, type: 'async_function', line: lineNum, sig: trimmed.split(':')[0] });
42
- }
43
- else if (trimmed.startsWith('class ')) {
44
- const name = trimmed.slice(6).split('(')[0].split(':')[0].trim();
45
- symbols.push({ name, type: 'class', line: lineNum, sig: trimmed.split(':')[0] });
46
- }
47
- // JavaScript/TypeScript
48
- else if (trimmed.match(/^(export\s+)?(async\s+)?function\s+\w+/)) {
49
- const match = trimmed.match(/function\s+(\w+)/);
50
- if (match) symbols.push({ name: match[1], type: 'function', line: lineNum, sig: trimmed.split('{')[0].trim() });
51
- }
52
- else if (trimmed.match(/^(export\s+)?class\s+\w+/)) {
53
- const match = trimmed.match(/class\s+(\w+)/);
54
- if (match) symbols.push({ name: match[1], type: 'class', line: lineNum, sig: trimmed.split('{')[0].trim() });
55
- }
56
- else if (trimmed.match(/^(const|let|var)\s+\w+\s*=\s*(async\s+)?\(/)) {
57
- const match = trimmed.match(/^(const|let|var)\s+(\w+)/);
58
- if (match) symbols.push({ name: match[2], type: 'arrow_function', line: lineNum, sig: trimmed.slice(0, 60) });
59
- }
60
- });
61
- return symbols;
62
- }
63
-
64
- function indexFile(filepath) {
65
- filepath = path.resolve(filepath);
66
-
67
- if (!fs.existsSync(filepath)) {
68
- return { error: `File not found: ${filepath}` };
69
- }
70
-
71
- const content = fs.readFileSync(filepath, 'utf8');
72
- const lines = content.split('\n');
73
- const symbols = extractSymbols(lines);
74
-
75
- // Create chunks (100 lines each)
76
- const chunkSize = 100;
77
- const chunks = [];
78
- for (let i = 0; i < lines.length; i += chunkSize) {
79
- const chunkLines = lines.slice(i, i + chunkSize);
80
- const chunkSymbols = symbols.filter(s => s.line > i && s.line <= i + chunkSize);
81
- chunks.push({
82
- start: i + 1,
83
- end: Math.min(i + chunkSize, lines.length),
84
- preview: chunkLines.slice(0, 3).join(' ').slice(0, 100),
85
- functions: chunkSymbols.filter(s => s.type !== 'class').map(s => s.name),
86
- classes: chunkSymbols.filter(s => s.type === 'class').map(s => s.name)
87
- });
88
- }
89
-
90
- const index = {
91
- filepath,
92
- totalLines: lines.length,
93
- indexed: new Date().toISOString(),
94
- symbols,
95
- chunks
96
- };
97
-
98
- fs.writeFileSync(getIndexPath(filepath), JSON.stringify(index, null, 2));
99
-
100
- return {
101
- status: 'indexed',
102
- filepath,
103
- lines: lines.length,
104
- functions: symbols.filter(s => s.type !== 'class').length,
105
- classes: symbols.filter(s => s.type === 'class').length
106
- };
107
- }
108
-
109
- function findSymbol(name, filepath = null) {
110
- const files = filepath ? [getIndexPath(path.resolve(filepath))] :
111
- fs.readdirSync(INDEX_DIR).map(f => path.join(INDEX_DIR, f));
112
-
113
- const results = [];
114
- for (const indexFile of files) {
115
- if (!fs.existsSync(indexFile)) continue;
116
- try {
117
- const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
118
- const matches = index.symbols.filter(s =>
119
- s.name.toLowerCase().includes(name.toLowerCase())
120
- );
121
- matches.forEach(m => results.push({ ...m, filepath: index.filepath }));
122
- } catch (e) {}
123
- }
124
- return results;
125
- }
126
-
127
- function getLines(filepath, start, end) {
128
- filepath = path.resolve(filepath);
129
- if (!fs.existsSync(filepath)) {
130
- return { error: `File not found: ${filepath}` };
131
- }
132
-
133
- const lines = fs.readFileSync(filepath, 'utf8').split('\n');
134
- const result = [];
135
- for (let i = start - 1; i < Math.min(end, lines.length); i++) {
136
- result.push(`${(i + 1).toString().padStart(5)}| ${lines[i]}`);
137
- }
138
- return result.join('\n');
139
- }
140
-
141
- function searchFile(filepath, query) {
142
- filepath = path.resolve(filepath);
143
- const indexPath = getIndexPath(filepath);
144
-
145
- if (!fs.existsSync(indexPath)) {
146
- return { error: `File not indexed. Run: file_index("${filepath}")` };
147
- }
148
-
149
- const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
150
- const queryLower = query.toLowerCase();
151
-
152
- // Search symbols first
153
- const symbolMatches = index.symbols.filter(s =>
154
- s.name.toLowerCase().includes(queryLower) ||
155
- s.sig.toLowerCase().includes(queryLower)
156
- ).map(s => ({
157
- type: 'symbol',
158
- name: s.name,
159
- line: s.line,
160
- sig: s.sig
161
- }));
162
-
163
- // Search chunks
164
- const content = fs.readFileSync(filepath, 'utf8');
165
- const lines = content.split('\n');
166
- const lineMatches = [];
167
-
168
- lines.forEach((line, i) => {
169
- if (line.toLowerCase().includes(queryLower)) {
170
- lineMatches.push({
171
- type: 'line',
172
- line: i + 1,
173
- content: line.trim().slice(0, 100)
174
- });
175
- }
176
- });
177
-
178
- return {
179
- symbols: symbolMatches.slice(0, 10),
180
- lines: lineMatches.slice(0, 20)
181
- };
182
- }
183
-
184
- function fileSummary(filepath) {
185
- filepath = path.resolve(filepath);
186
- const indexPath = getIndexPath(filepath);
187
-
188
- if (!fs.existsSync(indexPath)) {
189
- return { error: `File not indexed. Run: file_index("${filepath}")` };
190
- }
191
-
192
- const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
193
-
194
- return {
195
- filepath: index.filepath,
196
- totalLines: index.totalLines,
197
- indexed: index.indexed,
198
- functions: index.symbols.filter(s => s.type !== 'class').length,
199
- classes: index.symbols.filter(s => s.type === 'class').length,
200
- topSymbols: index.symbols.slice(0, 30).map(s => ({
201
- name: s.name,
202
- type: s.type,
203
- line: s.line
204
- }))
205
- };
206
- }
207
-
208
- // MCP Tool definitions for local file operations
209
- const FILE_TOOLS = {
210
- file_index: {
211
- description: "Index a large local file for fast symbol/line lookup. FREE.",
212
- schema: {
213
- type: "object",
214
- properties: { filepath: { type: "string", description: "Path to file" } },
215
- required: ["filepath"]
216
- },
217
- handler: (args) => indexFile(args.filepath)
218
- },
219
- file_find: {
220
- description: "Find where a function/class is defined in indexed files. FREE.",
221
- schema: {
222
- type: "object",
223
- properties: {
224
- name: { type: "string", description: "Symbol name to find" },
225
- filepath: { type: "string", description: "Optional: limit to one file" }
226
- },
227
- required: ["name"]
228
- },
229
- handler: (args) => findSymbol(args.name, args.filepath)
230
- },
231
- file_lines: {
232
- description: "Get specific line range from a file with line numbers. FREE.",
233
- schema: {
234
- type: "object",
235
- properties: {
236
- filepath: { type: "string" },
237
- start: { type: "number", description: "Start line" },
238
- end: { type: "number", description: "End line" }
239
- },
240
- required: ["filepath", "start", "end"]
241
- },
242
- handler: (args) => getLines(args.filepath, args.start, args.end)
243
- },
244
- file_search: {
245
- description: "Search for content in an indexed file. FREE.",
246
- schema: {
247
- type: "object",
248
- properties: {
249
- filepath: { type: "string" },
250
- query: { type: "string", description: "Search query" }
251
- },
252
- required: ["filepath", "query"]
253
- },
254
- handler: (args) => searchFile(args.filepath, args.query)
255
- },
256
- file_summary: {
257
- description: "Get summary of indexed file (functions, classes, line count). FREE.",
258
- schema: {
259
- type: "object",
260
- properties: { filepath: { type: "string" } },
261
- required: ["filepath"]
262
- },
263
- handler: (args) => fileSummary(args.filepath)
264
- }
265
- };
266
-
267
- module.exports = {
268
- FILE_TOOLS,
269
- indexFile,
270
- findSymbol,
271
- getLines,
272
- searchFile,
273
- fileSummary
274
- };
275
-
276
- // CLI test
277
- if (require.main === module) {
278
- const testFile = process.argv[2] || 'C:\\Users\\Administrator\\Desktop\\50c\\nj-deploy\\api-unified-v8-FINAL-ALL.py';
279
-
280
- console.log('='.repeat(60));
281
- console.log('FILE MEMORY - Node.js Test');
282
- console.log('='.repeat(60));
283
-
284
- console.log('\n[1] Indexing...');
285
- console.log(indexFile(testFile));
286
-
287
- console.log('\n[2] Finding fog_check...');
288
- console.log(findSymbol('fog_check'));
289
-
290
- console.log('\n[3] Lines 2385-2395...');
291
- console.log(getLines(testFile, 2385, 2395));
292
-
293
- console.log('\n[4] Search "beacon"...');
294
- const search = searchFile(testFile, 'beacon');
295
- console.log('Symbols:', search.symbols?.slice(0, 5));
296
- console.log('Lines:', search.lines?.slice(0, 5));
297
-
298
- console.log('\n[5] Summary...');
299
- const sum = fileSummary(testFile);
300
- console.log(`${sum.totalLines} lines, ${sum.functions} functions, ${sum.classes} classes`);
301
- }