thorns 5.1.9

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/lib.js ADDED
@@ -0,0 +1,537 @@
1
+ import Parser from 'tree-sitter';
2
+ import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
3
+ import { join, extname, relative } from 'path';
4
+ import JavaScript from 'tree-sitter-javascript';
5
+ import TypeScript from 'tree-sitter-typescript';
6
+ import Python from 'tree-sitter-python';
7
+ import Rust from 'tree-sitter-rust';
8
+ import Go from 'tree-sitter-go';
9
+ import C from 'tree-sitter-c';
10
+ import Cpp from 'tree-sitter-cpp';
11
+ import Java from 'tree-sitter-java';
12
+ import CSharp from 'tree-sitter-c-sharp';
13
+ import Ruby from 'tree-sitter-ruby';
14
+ import PHP from 'tree-sitter-php';
15
+ import JSONParser from 'tree-sitter-json';
16
+ import { extractEntities, calculateMetrics } from './analyzer.js';
17
+ import { formatUltraCompact } from './compact-formatter.js';
18
+ import { extractDependencies, buildDependencyGraph, analyzeModules } from './dependency-analyzer.js';
19
+ import { extractAdvancedMetrics, detectDuplication, hashFunction, detectCircularDeps, analyzeFileSizes } from './advanced-metrics.js';
20
+ import { buildIgnoreSet, shouldIgnore } from './ignore-parser.js';
21
+
22
+ const LANGUAGES = {
23
+ '.js': { parser: JavaScript, name: 'JavaScript' },
24
+ '.mjs': { parser: JavaScript, name: 'JavaScript' },
25
+ '.cjs': { parser: JavaScript, name: 'JavaScript' },
26
+ '.jsx': { parser: JavaScript, name: 'JSX' },
27
+ '.ts': { parser: TypeScript.typescript, name: 'TypeScript' },
28
+ '.tsx': { parser: TypeScript.tsx, name: 'TSX' },
29
+ '.py': { parser: Python, name: 'Python' },
30
+ '.rs': { parser: Rust, name: 'Rust' },
31
+ '.go': { parser: Go, name: 'Go' },
32
+ '.c': { parser: C, name: 'C' },
33
+ '.h': { parser: C, name: 'C' },
34
+ '.cpp': { parser: Cpp, name: 'C++' },
35
+ '.cc': { parser: Cpp, name: 'C++' },
36
+ '.cxx': { parser: Cpp, name: 'C++' },
37
+ '.hpp': { parser: Cpp, name: 'C++' },
38
+ '.java': { parser: Java, name: 'Java' },
39
+ '.cs': { parser: CSharp, name: 'C#' },
40
+ '.rb': { parser: Ruby, name: 'Ruby' },
41
+ '.php': { parser: PHP, name: 'PHP' },
42
+ '.json': { parser: JSONParser, name: 'JSON' }
43
+ };
44
+
45
+ const MAX_FILE_SIZE = 200 * 1024; // 200KB - anything larger is build/generated code
46
+
47
+ function getLanguage(filepath) {
48
+ const ext = extname(filepath);
49
+ return LANGUAGES[ext];
50
+ }
51
+
52
+ function* walkDir(dir, baseDir = dir, ignorePatterns = new Set()) {
53
+ const entries = readdirSync(dir, { withFileTypes: true });
54
+ for (const entry of entries) {
55
+ const fullPath = join(dir, entry.name);
56
+ const relativePath = relative(baseDir, fullPath);
57
+
58
+ // Check if path should be ignored
59
+ if (shouldIgnore(relativePath, ignorePatterns) || shouldIgnore(entry.name, ignorePatterns)) {
60
+ continue;
61
+ }
62
+
63
+ if (entry.isDirectory()) {
64
+ yield* walkDir(fullPath, baseDir, ignorePatterns);
65
+ } else if (entry.isFile()) {
66
+ const lang = getLanguage(entry.name);
67
+ if (lang) {
68
+ try {
69
+ const stat = statSync(fullPath);
70
+ if (stat.size <= MAX_FILE_SIZE) {
71
+ yield { path: fullPath, relativePath, lang };
72
+ }
73
+ } catch (e) {}
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ function extractFunctionName(node) {
80
+ for (const child of node.children) {
81
+ if (child.type === 'identifier' || child.type === 'property_identifier') {
82
+ return child.text;
83
+ }
84
+ }
85
+ return 'anonymous';
86
+ }
87
+
88
+ function extractClassName(node) {
89
+ for (const child of node.children) {
90
+ if (child.type === 'identifier' || child.type === 'type_identifier') {
91
+ return child.text;
92
+ }
93
+ }
94
+ return 'Anonymous';
95
+ }
96
+
97
+ function countNodeParams(node) {
98
+ let count = 0;
99
+ function traverse(n) {
100
+ if (n.type === 'parameter' || n.type === 'formal_parameter' || n.type.includes('param')) {
101
+ count++;
102
+ }
103
+ for (const child of n.children) traverse(child);
104
+ }
105
+ traverse(node);
106
+ return count;
107
+ }
108
+
109
+ function analyzeTree(tree, sourceCode) {
110
+ const stats = {
111
+ functions: 0,
112
+ classes: 0,
113
+ imports: 0,
114
+ exports: 0,
115
+ complexity: 0,
116
+ lines: sourceCode.split('\n').length
117
+ };
118
+
119
+ function traverse(node) {
120
+ const type = node.type;
121
+
122
+ if (type.includes('function') && type.includes('declaration')) stats.functions++;
123
+ if (type.includes('class') && type.includes('declaration')) stats.classes++;
124
+ if (type.includes('import')) stats.imports++;
125
+ if (type.includes('export')) stats.exports++;
126
+ if (['if_statement', 'while_statement', 'for_statement', 'case_statement', 'catch_clause'].includes(type)) {
127
+ stats.complexity++;
128
+ }
129
+
130
+ for (let child of node.children) {
131
+ traverse(child);
132
+ }
133
+ }
134
+
135
+ traverse(tree.rootNode);
136
+ return stats;
137
+ }
138
+
139
+ function detectDeadCode(depGraph, fileMetrics, projectContext) {
140
+ const deadCode = {
141
+ unusedExports: [],
142
+ testFiles: [],
143
+ orphanedFiles: [],
144
+ possiblyDead: []
145
+ };
146
+
147
+ if (!depGraph?.nodes) return deadCode;
148
+
149
+ const reExporters = new Set();
150
+ for (const [file, node] of depGraph.nodes) {
151
+ if (node.importsFrom.size > 0 && node.exportedNames.size > 0) {
152
+ const fileName = file.split('/').pop();
153
+ if (fileName.includes('index.') || fileName.includes('lib.') || fileName.includes('main.')) {
154
+ reExporters.add(file);
155
+ for (const imported of node.importsFrom) {
156
+ const targetNode = depGraph.nodes.get(imported);
157
+ if (targetNode) {
158
+ targetNode.importedBy.add(file + ':reexport');
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ for (const [file, node] of depGraph.nodes) {
166
+ const fileName = file.split('/').pop();
167
+ const isTest = fileName.includes('.test.') || fileName.includes('.spec.') ||
168
+ file.includes('/test/') || file.includes('/__tests__/');
169
+
170
+ if (isTest) {
171
+ deadCode.testFiles.push(file);
172
+ continue;
173
+ }
174
+
175
+ const realImporters = Array.from(node.importedBy).filter(i => !i.includes(':reexport'));
176
+ const hasReExporter = Array.from(node.importedBy).some(i => i.includes(':reexport'));
177
+
178
+ if (realImporters.length === 0 && !hasReExporter && node.exportedNames.size > 0) {
179
+ const isEntry = fileName.includes('index.') || fileName.includes('main.') ||
180
+ fileName.includes('app.') || fileName.includes('server.') ||
181
+ fileName.includes('lib.') || fileName.includes('cli.');
182
+ const isConfig = fileName.includes('config') || fileName.includes('.config.');
183
+
184
+ if (!isEntry && !isConfig) {
185
+ deadCode.unusedExports.push({
186
+ file,
187
+ exports: Array.from(node.exportedNames).slice(0, 3)
188
+ });
189
+ }
190
+ }
191
+
192
+ if (node.importedBy.size === 0 && node.importsFrom.size === 0) {
193
+ const isEntry = fileName.includes('index.') || fileName.includes('main.') ||
194
+ fileName.includes('app.') || fileName.includes('server.') ||
195
+ fileName.includes('lib.') || fileName.includes('cli.');
196
+ if (!isEntry) {
197
+ deadCode.orphanedFiles.push(file);
198
+ }
199
+ }
200
+
201
+ if (realImporters.length === 1 && node.importsFrom.size === 0 && !hasReExporter) {
202
+ deadCode.possiblyDead.push({
203
+ file,
204
+ usedBy: realImporters[0]
205
+ });
206
+ }
207
+ }
208
+
209
+ return deadCode;
210
+ }
211
+
212
+ function analyzeProjectContext(rootPath) {
213
+ const context = {
214
+ type: 'unknown',
215
+ framework: null,
216
+ runtime: null,
217
+ packageManager: null,
218
+ scripts: {},
219
+ dependencies: {},
220
+ devDependencies: {},
221
+ entry: null,
222
+ build: null,
223
+ test: null,
224
+ name: null,
225
+ description: null,
226
+ version: null,
227
+ readme: null
228
+ };
229
+
230
+ try {
231
+ const packagePath = join(rootPath, 'package.json');
232
+ if (existsSync(packagePath)) {
233
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
234
+ context.name = pkg.name;
235
+ context.description = pkg.description;
236
+ context.version = pkg.version;
237
+ context.scripts = pkg.scripts || {};
238
+ context.dependencies = pkg.dependencies || {};
239
+ context.devDependencies = pkg.devDependencies || {};
240
+
241
+ if (pkg.dependencies?.next || pkg.devDependencies?.next) {
242
+ context.framework = 'Next.js';
243
+ context.type = 'web-app';
244
+ } else if (pkg.dependencies?.react || pkg.devDependencies?.react) {
245
+ context.framework = 'React';
246
+ context.type = 'web-app';
247
+ } else if (pkg.dependencies?.vite || pkg.devDependencies?.vite) {
248
+ context.framework = 'Vite';
249
+ context.type = 'web-app';
250
+ } else if (pkg.dependencies?.express || pkg.devDependencies?.express) {
251
+ context.framework = 'Express';
252
+ context.type = 'server';
253
+ } else if (pkg.bin) {
254
+ context.type = 'cli';
255
+ } else if (pkg.main || pkg.exports) {
256
+ context.type = 'library';
257
+ }
258
+
259
+ if (context.scripts.start) context.entry = context.scripts.start;
260
+ if (context.scripts.build) context.build = context.scripts.build;
261
+ if (context.scripts.test) context.test = context.scripts.test;
262
+ }
263
+
264
+ const readmeFiles = ['README.md', 'readme.md', 'README.txt', 'README'];
265
+ for (const file of readmeFiles) {
266
+ const readmePath = join(rootPath, file);
267
+ if (existsSync(readmePath)) {
268
+ const content = readFileSync(readmePath, 'utf8');
269
+ const firstPara = content.split('\n\n').slice(0, 2).join(' ').replace(/^#+ /, '').replace(/\n/g, ' ').slice(0, 300);
270
+ context.readme = firstPara.trim();
271
+ break;
272
+ }
273
+ }
274
+
275
+ const denoPath = join(rootPath, 'deno.json');
276
+ if (existsSync(denoPath)) {
277
+ context.runtime = 'Deno';
278
+ const deno = JSON.parse(readFileSync(denoPath, 'utf8'));
279
+ if (deno.tasks) context.scripts = deno.tasks;
280
+ }
281
+
282
+ if (existsSync(join(rootPath, 'yarn.lock'))) context.packageManager = 'yarn';
283
+ else if (existsSync(join(rootPath, 'pnpm-lock.yaml'))) context.packageManager = 'pnpm';
284
+ else if (existsSync(join(rootPath, 'package-lock.json'))) context.packageManager = 'npm';
285
+ } catch (e) {}
286
+
287
+ return context;
288
+ }
289
+
290
+ function analyzeCodebase(rootPath = '.') {
291
+ const parser = new Parser();
292
+ const stats = { files: 0, totalLines: 0, byLanguage: {}, errors: [] };
293
+ const entities = {};
294
+ const metrics = { depths: [], hotspots: [] };
295
+ const fileMetrics = {};
296
+ const fileAnalysis = {};
297
+ const projectContext = analyzeProjectContext(rootPath);
298
+
299
+ // Build comprehensive ignore set - always exclude build artifacts
300
+ const ignorePatterns = buildIgnoreSet(rootPath);
301
+
302
+ for (const { path, relativePath, lang } of walkDir(rootPath, rootPath, ignorePatterns)) {
303
+ try {
304
+ parser.setLanguage(lang.parser);
305
+ const source = readFileSync(path, 'utf8');
306
+ const tree = parser.parse(source);
307
+
308
+ const basicStats = analyzeTree(tree, source);
309
+ const ents = extractEntities(tree, source, lang.name);
310
+ const mets = calculateMetrics(tree, source);
311
+ const deps = extractDependencies(tree, source, relativePath, lang.name);
312
+ const advanced = extractAdvancedMetrics(tree, source);
313
+
314
+ stats.files++;
315
+ stats.totalLines += basicStats.lines;
316
+
317
+ if (!stats.byLanguage[lang.name]) {
318
+ stats.byLanguage[lang.name] = { files: 0, lines: 0, functions: 0, classes: 0, imports: 0, exports: 0, complexity: 0 };
319
+ }
320
+
321
+ const langStats = stats.byLanguage[lang.name];
322
+ langStats.files++;
323
+ langStats.lines += basicStats.lines;
324
+ langStats.functions += basicStats.functions;
325
+ langStats.classes += basicStats.classes;
326
+ langStats.imports += basicStats.imports;
327
+ langStats.exports += basicStats.exports;
328
+ langStats.complexity += basicStats.complexity;
329
+
330
+ if (!entities[lang.name]) {
331
+ entities[lang.name] = {
332
+ functions: new Map(),
333
+ classes: new Map(),
334
+ imports: new Set(),
335
+ exports: new Set(),
336
+ patterns: new Map(),
337
+ asyncPatterns: { async: 0, await: 0, promise: 0, callback: 0, thenCatch: 0 },
338
+ errorPatterns: { tryCatch: 0, throw: 0, errorTypes: new Set() },
339
+ internalCalls: new Map(),
340
+ constants: [],
341
+ globalState: [],
342
+ envVars: new Set(),
343
+ urls: new Set(),
344
+ filePaths: new Set(),
345
+ eventPatterns: { emitters: 0, listeners: 0 },
346
+ httpPatterns: { routes: [], fetches: 0, axios: 0 },
347
+ storagePatterns: { sql: 0, fileOps: 0, json: 0 }
348
+ };
349
+ }
350
+
351
+ for (const [sig, data] of ents.functions) {
352
+ const existing = entities[lang.name].functions.get(sig) || { count: 0, ...data };
353
+ existing.count += data.count;
354
+ entities[lang.name].functions.set(sig, existing);
355
+ }
356
+
357
+ for (const [name, data] of ents.classes) {
358
+ const existing = entities[lang.name].classes.get(name) || { count: 0, ...data };
359
+ existing.count += data.count;
360
+ entities[lang.name].classes.set(name, existing);
361
+ }
362
+
363
+ for (const imp of ents.imports) entities[lang.name].imports.add(imp);
364
+ for (const exp of ents.exports) entities[lang.name].exports.add(exp);
365
+
366
+ for (const [pattern, count] of ents.patterns) {
367
+ entities[lang.name].patterns.set(pattern, (entities[lang.name].patterns.get(pattern) || 0) + count);
368
+ }
369
+
370
+ if (ents.asyncPatterns) {
371
+ entities[lang.name].asyncPatterns.async += ents.asyncPatterns.async;
372
+ entities[lang.name].asyncPatterns.await += ents.asyncPatterns.await;
373
+ entities[lang.name].asyncPatterns.promise += ents.asyncPatterns.promise;
374
+ entities[lang.name].asyncPatterns.callback += ents.asyncPatterns.callback;
375
+ entities[lang.name].asyncPatterns.thenCatch += ents.asyncPatterns.thenCatch;
376
+ }
377
+
378
+ if (ents.errorPatterns) {
379
+ entities[lang.name].errorPatterns.tryCatch += ents.errorPatterns.tryCatch;
380
+ entities[lang.name].errorPatterns.throw += ents.errorPatterns.throw;
381
+ for (const errType of ents.errorPatterns.errorTypes) {
382
+ entities[lang.name].errorPatterns.errorTypes.add(errType);
383
+ }
384
+ }
385
+
386
+ if (ents.internalCalls) {
387
+ for (const [name, count] of ents.internalCalls) {
388
+ entities[lang.name].internalCalls.set(name, (entities[lang.name].internalCalls.get(name) || 0) + count);
389
+ }
390
+ }
391
+
392
+ if (ents.constants) {
393
+ entities[lang.name].constants.push(...ents.constants);
394
+ }
395
+
396
+ if (ents.globalState) {
397
+ entities[lang.name].globalState.push(...ents.globalState);
398
+ }
399
+
400
+ if (ents.envVars) {
401
+ for (const v of ents.envVars) entities[lang.name].envVars.add(v);
402
+ }
403
+ if (ents.urls) {
404
+ for (const u of ents.urls) entities[lang.name].urls.add(u);
405
+ }
406
+ if (ents.filePaths) {
407
+ for (const p of ents.filePaths) entities[lang.name].filePaths.add(p);
408
+ }
409
+ if (ents.eventPatterns) {
410
+ entities[lang.name].eventPatterns.emitters += ents.eventPatterns.emitters;
411
+ entities[lang.name].eventPatterns.listeners += ents.eventPatterns.listeners;
412
+ }
413
+ if (ents.httpPatterns) {
414
+ entities[lang.name].httpPatterns.fetches += ents.httpPatterns.fetches;
415
+ entities[lang.name].httpPatterns.axios += ents.httpPatterns.axios;
416
+ entities[lang.name].httpPatterns.routes.push(...ents.httpPatterns.routes);
417
+ }
418
+ if (ents.storagePatterns) {
419
+ entities[lang.name].storagePatterns.sql += ents.storagePatterns.sql;
420
+ entities[lang.name].storagePatterns.fileOps += ents.storagePatterns.fileOps;
421
+ entities[lang.name].storagePatterns.json += ents.storagePatterns.json;
422
+ }
423
+
424
+ metrics.depths.push(mets.maxDepth);
425
+
426
+ if (mets.branches > 10 || mets.maxDepth > 8) {
427
+ metrics.hotspots.push({
428
+ file: relativePath,
429
+ cx: mets.branches,
430
+ depth: mets.maxDepth,
431
+ loc: mets.loc
432
+ });
433
+ }
434
+
435
+ // Store for dependency/duplication analysis
436
+ fileAnalysis[relativePath] = {
437
+ imports: deps.imports,
438
+ exports: deps.exports,
439
+ importPaths: deps.importPaths,
440
+ exportedNames: deps.exportedNames
441
+ };
442
+
443
+ fileMetrics[relativePath] = {
444
+ loc: mets.loc,
445
+ advanced,
446
+ functionHashes: {},
447
+ functions: [],
448
+ classes: []
449
+ };
450
+
451
+ function collectFunctionHashes(node, depth = 0) {
452
+ if (node.type.includes('function') && node.type.includes('declaration') ||
453
+ node.type === 'method_definition' || node.type === 'function_item') {
454
+ const hash = hashFunction(node);
455
+ const sig = node.text.slice(0, 50);
456
+ fileMetrics[relativePath].functionHashes[sig] = hash;
457
+
458
+ const name = extractFunctionName(node);
459
+ const lines = node.text.split('\n').length;
460
+ const startLine = node.startPosition.row + 1;
461
+ fileMetrics[relativePath].functions.push({
462
+ name,
463
+ lines,
464
+ startLine,
465
+ params: countNodeParams(node)
466
+ });
467
+ }
468
+
469
+ if (node.type.includes('class') && node.type.includes('declaration') ||
470
+ node.type === 'struct_item' || node.type === 'enum_item' || node.type === 'interface_declaration') {
471
+ const name = extractClassName(node);
472
+ const startLine = node.startPosition.row + 1;
473
+ fileMetrics[relativePath].classes.push({
474
+ name,
475
+ startLine
476
+ });
477
+ }
478
+
479
+ for (const child of node.children) collectFunctionHashes(child, depth + 1);
480
+ }
481
+ collectFunctionHashes(tree.rootNode);
482
+
483
+ } catch (e) {
484
+ stats.errors.push({ file: relativePath, error: e.message });
485
+ }
486
+ }
487
+
488
+ metrics.hotspots.sort((a, b) => b.cx + b.depth - (a.cx + a.depth));
489
+
490
+ // Advanced analysis
491
+ const depGraph = buildDependencyGraph(fileAnalysis);
492
+ const duplicates = detectDuplication(fileMetrics);
493
+ const circular = detectCircularDeps(depGraph);
494
+ const fileSizes = analyzeFileSizes(fileMetrics);
495
+ const modules = analyzeModules(fileAnalysis, rootPath);
496
+
497
+ // Aggregate advanced metrics
498
+ const allIdentifiers = new Map();
499
+ const allFuncLengths = [];
500
+ const allFuncParams = [];
501
+
502
+ for (const [file, data] of Object.entries(fileMetrics)) {
503
+ if (data.advanced) {
504
+ for (const [id, count] of data.advanced.identifiers) {
505
+ allIdentifiers.set(id, (allIdentifiers.get(id) || 0) + count);
506
+ }
507
+ allFuncLengths.push(...data.advanced.functionLengths);
508
+ allFuncParams.push(...data.advanced.functionParams);
509
+ }
510
+ }
511
+
512
+ const deadCode = detectDeadCode(depGraph, fileMetrics, projectContext);
513
+
514
+ return {
515
+ stats,
516
+ entities,
517
+ metrics,
518
+ depGraph,
519
+ duplicates,
520
+ circular,
521
+ fileSizes,
522
+ modules,
523
+ identifiers: allIdentifiers,
524
+ funcLengths: allFuncLengths,
525
+ funcParams: allFuncParams,
526
+ fileMetrics,
527
+ projectContext,
528
+ deadCode
529
+ };
530
+ }
531
+
532
+ export function analyze(rootPath = '.') {
533
+ const aggregated = analyzeCodebase(rootPath);
534
+ return formatUltraCompact(aggregated);
535
+ }
536
+
537
+ export { analyzeCodebase, formatUltraCompact };
package/one-liner.sh ADDED
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+ # Minimal one-liner for running Thorns directly from GitHub
3
+ # Requires: bun (or node as fallback)
4
+ # Usage: bash <(curl -fsSL https://raw.githubusercontent.com/AnEntrypoint/mcp-thorns/main/one-liner.sh) [path]
5
+
6
+ set -e
7
+ T=$(mktemp -d)
8
+ cd "$T"
9
+ trap "rm -rf '$T'" EXIT
10
+
11
+ # Download minimal set of files
12
+ for f in index.js lib.js analyzer.js compact-formatter.js dependency-analyzer.js advanced-metrics.js ignore-parser.js queries.js .thornsignore; do
13
+ curl -fsSL -o "$f" "https://raw.githubusercontent.com/AnEntrypoint/mcp-thorns/main/$f"
14
+ done
15
+
16
+ # Fetch package.json for dependencies info (not executed, just for reference)
17
+ curl -fsSL -o package.json "https://raw.githubusercontent.com/AnEntrypoint/mcp-thorns/main/package.json"
18
+
19
+ # Execute with bun if available, fallback to node
20
+ if command -v bun &> /dev/null; then
21
+ bun index.js "${1:-.}"
22
+ else
23
+ node index.js "${1:-.}"
24
+ fi
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "thorns",
3
+ "version": "5.1.9",
4
+ "description": "Perfect one-shot codebase overview: project context, architecture flow, async/error patterns, dead code, internal call graph",
5
+ "type": "module",
6
+ "main": "./lib.js",
7
+ "exports": {
8
+ ".": "./lib.js"
9
+ },
10
+ "bin": {
11
+ "mcp-thorns": "./index.js",
12
+ "thorns": "./index.js"
13
+ },
14
+ "files": [
15
+ "index.js",
16
+ "lib.js",
17
+ "analyzer.js",
18
+ "compact-formatter.js",
19
+ "dependency-analyzer.js",
20
+ "advanced-metrics.js",
21
+ "ignore-parser.js",
22
+ ".thornsignore",
23
+ "queries.js",
24
+ "README.md",
25
+ "run.sh",
26
+ "one-liner.sh"
27
+ ],
28
+ "scripts": {
29
+ "analyze": "node index.js"
30
+ },
31
+ "bun": {
32
+ "bin": {
33
+ "mcp-thorns": "./index.js",
34
+ "thorns": "./index.js"
35
+ }
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/AnEntrypoint/mcp-thorns.git"
40
+ },
41
+ "author": "AnEntrypoint",
42
+ "license": "MIT",
43
+ "homepage": "https://github.com/AnEntrypoint/mcp-thorns#readme",
44
+ "bugs": {
45
+ "url": "https://github.com/AnEntrypoint/mcp-thorns/issues"
46
+ },
47
+ "dependencies": {
48
+ "tree-sitter": "^0.21.1",
49
+ "tree-sitter-javascript": "^0.21.0",
50
+ "tree-sitter-typescript": "^0.21.0",
51
+ "tree-sitter-python": "^0.21.0",
52
+ "tree-sitter-rust": "^0.21.0",
53
+ "tree-sitter-go": "^0.21.0",
54
+ "tree-sitter-c": "^0.21.0",
55
+ "tree-sitter-cpp": "^0.22.0",
56
+ "tree-sitter-java": "^0.21.0",
57
+ "tree-sitter-c-sharp": "^0.21.0",
58
+ "tree-sitter-ruby": "^0.21.0",
59
+ "tree-sitter-php": "^0.22.0",
60
+ "tree-sitter-json": "^0.21.0"
61
+ },
62
+ "engines": {
63
+ "node": ">=18.0.0"
64
+ },
65
+ "keywords": [
66
+ "tree-sitter",
67
+ "analysis",
68
+ "ast",
69
+ "codebase"
70
+ ]
71
+ }