code-graph-llm 1.1.0 → 1.2.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.
package/index.js CHANGED
@@ -35,7 +35,7 @@ const SYMBOL_REGEXES = [
35
35
  // Dart: class Name, void name, var name (void/var covered by C-style pattern)
36
36
  ];
37
37
 
38
- const SUPPORTED_EXTENSIONS = [
38
+ export const SUPPORTED_EXTENSIONS = [
39
39
  '.js', '.ts', '.jsx', '.tsx',
40
40
  '.py', '.go', '.rs', '.java',
41
41
  '.cpp', '.c', '.h', '.hpp', '.cc',
@@ -43,8 +43,8 @@ const SUPPORTED_EXTENSIONS = [
43
43
  '.cs', '.dart', '.scala', '.m', '.mm'
44
44
  ];
45
45
 
46
- function getIgnores(cwd) {
47
- const ig = ignore().add(['.git', 'node_modules', DEFAULT_MAP_FILE, 'package-lock.json']);
46
+ export function getIgnores(cwd) {
47
+ const ig = ignore().add(['.git/', 'node_modules/', DEFAULT_MAP_FILE, 'package-lock.json', '.idea/', 'build/', 'dist/', 'bin/', 'obj/']);
48
48
  const ignorePath = path.join(cwd, IGNORE_FILE);
49
49
  if (fs.existsSync(ignorePath)) {
50
50
  ig.add(fs.readFileSync(ignorePath, 'utf8'));
@@ -52,7 +52,7 @@ function getIgnores(cwd) {
52
52
  return ig;
53
53
  }
54
54
 
55
- function extractSymbols(content) {
55
+ export function extractSymbols(content) {
56
56
  const symbols = [];
57
57
  for (const regex of SYMBOL_REGEXES) {
58
58
  let match;
@@ -81,10 +81,11 @@ function extractSymbols(content) {
81
81
  // 2. Backup: Extract Signature (Parameters/Type) if no comment
82
82
  let context = comment;
83
83
  if (!context) {
84
- const remainingLine = content.substring(match.index + match[0].length).split('\n')[0];
85
- const sigMatch = remainingLine.match(/^[^:{;]*/);
86
- if (sigMatch && sigMatch[0].trim()) {
87
- context = sigMatch[0].trim();
84
+ const remainingLine = content.substring(match.index + match[0].length);
85
+ // Match until the first opening brace or colon or end of line, but include balanced parentheses
86
+ const sigMatch = remainingLine.match(/^\s*(\([^)]*\)|[^\n{;]*)/);
87
+ if (sigMatch && sigMatch[1].trim()) {
88
+ context = sigMatch[1].trim();
88
89
  }
89
90
  }
90
91
 
@@ -95,7 +96,7 @@ function extractSymbols(content) {
95
96
  return Array.from(new Set(symbols)).sort();
96
97
  }
97
98
 
98
- async function generate(cwd = process.cwd()) {
99
+ export async function generate(cwd = process.cwd()) {
99
100
  const ig = getIgnores(cwd);
100
101
  const files = [];
101
102
 
@@ -105,11 +106,12 @@ async function generate(cwd = process.cwd()) {
105
106
  const fullPath = path.join(dir, entry.name);
106
107
  let relativePath = path.relative(cwd, fullPath);
107
108
  const normalizedPath = relativePath.replace(/\\/g, '/');
108
- const checkPath = entry.isDirectory() ? `${normalizedPath}/` : normalizedPath;
109
+ const isDirectory = entry.isDirectory();
110
+ const checkPath = isDirectory ? `${normalizedPath}/` : normalizedPath;
109
111
 
110
112
  if (ig.ignores(checkPath)) continue;
111
113
 
112
- if (entry.isDirectory()) {
114
+ if (isDirectory) {
113
115
  walk(fullPath);
114
116
  } else if (entry.isFile()) {
115
117
  const ext = path.extname(entry.name);
@@ -154,14 +156,23 @@ async function generate(cwd = process.cwd()) {
154
156
  console.log(`[Code-Graph] Updated ${DEFAULT_MAP_FILE}`);
155
157
  }
156
158
 
157
- function watch(cwd = process.cwd()) {
159
+ export function watch(cwd = process.cwd()) {
158
160
  console.log(`[Code-Graph] Watching for changes in ${cwd}...`);
159
161
  const ig = getIgnores(cwd);
160
162
 
161
163
  const watcher = chokidar.watch(cwd, {
162
164
  ignored: (p) => {
163
- const rel = path.relative(cwd, p);
164
- return rel && ig.ignores(rel);
165
+ if (p === cwd) return false;
166
+ const rel = path.relative(cwd, p).replace(/\\/g, '/');
167
+ // We must check if p is a directory to append the trailing slash
168
+ // Since chokidar's ignore function is synchronous, we use fs.statSync
169
+ try {
170
+ const stats = fs.statSync(p);
171
+ const checkPath = stats.isDirectory() ? `${rel}/` : rel;
172
+ return ig.ignores(checkPath);
173
+ } catch (e) {
174
+ return false;
175
+ }
165
176
  },
166
177
  persistent: true,
167
178
  ignoreInitial: true
@@ -179,7 +190,7 @@ function watch(cwd = process.cwd()) {
179
190
  });
180
191
  }
181
192
 
182
- function installHook(cwd = process.cwd()) {
193
+ export function installHook(cwd = process.cwd()) {
183
194
  const hooksDir = path.join(cwd, '.git', 'hooks');
184
195
  if (!fs.existsSync(hooksDir)) {
185
196
  console.error('[Code-Graph] No .git directory found. Cannot install hook.');
@@ -193,15 +204,17 @@ function installHook(cwd = process.cwd()) {
193
204
  console.log('[Code-Graph] Installed pre-commit hook.');
194
205
  }
195
206
 
196
- const args = process.argv.slice(2);
197
- const command = args[0] || 'generate';
198
-
199
- if (command === 'generate') {
200
- generate();
201
- } else if (command === 'watch') {
202
- watch();
203
- } else if (command === 'install-hook') {
204
- installHook();
205
- } else {
206
- console.log('Usage: code-graph [generate|watch|install-hook]');
207
+ if (process.argv[1] && (process.argv[1] === fileURLToPath(import.meta.url) || process.argv[1].endsWith('index.js'))) {
208
+ const args = process.argv.slice(2);
209
+ const command = args[0] || 'generate';
210
+
211
+ if (command === 'generate') {
212
+ generate();
213
+ } else if (command === 'watch') {
214
+ watch();
215
+ } else if (command === 'install-hook') {
216
+ installHook();
217
+ } else {
218
+ console.log('Usage: code-graph [generate|watch|install-hook]');
219
+ }
207
220
  }
package/llm-code-graph.md CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  - .gitignore
5
5
  - index.js | desc: !usrbinenv node
6
- - syms: [Name [Dart:], Name [PHP: class Name, interface Name, trait Name,], Name [PHP: class Name, interface Name,], Name [PHP: class Name,], Name [PHP:], Name [Ruby: def name, class Name,], Name [Ruby: def name,], Name [Swift: func name, class Name, struct Name, protocol Name,], Name [Swift: func name, class Name, struct Name,], Name [Swift: func name, class Name,], Name [Swift: func name,], extractSymbols [(content)], generate [(cwd = process.cwd())], getIgnores [(cwd)], installHook [(cwd = process.cwd())], name [Dart: class Name, void name,], name [Ruby:], name [Swift:], walk [(dir)], watch [(cwd = process.cwd())]]
6
+ - syms: [Name [Dart:], Name [PHP: class Name, interface Name, trait Name,], Name [PHP: class Name, interface Name,], Name [PHP: class Name,], Name [PHP:], Name [Ruby: def name, class Name,], Name [Ruby: def name,], Name [Swift: func name, class Name, struct Name, protocol Name,], Name [Swift: func name, class Name, struct Name,], Name [Swift: func name, class Name,], Name [Swift: func name,], SUPPORTED_EXTENSIONS [= [], extractSymbols [(content)], function [generate(cwd = process.cwd())], generate [(cwd = process.cwd()], getIgnores [(cwd)], installHook [(cwd = process.cwd()], is [We must check if p is a directory to append the trailing slash Since chokidar's ignore], name [Dart: class Name, void name,], name [Ruby:], name [Swift:], walk [(dir)], watch [(cwd = process.cwd()]]
7
7
  - package.json
8
- - README.md
8
+ - README.md
9
+ - test/index.test.js | desc: Contains 5 symbols.
10
+ - syms: [noDocFunc [(arg1: string, arg2: number)], py_func [(x)], py_func [Note: Current regex captures '], py_func_2 [This is a python comment], testFunc [This is a test function]]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-graph-llm",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Compact, language-agnostic codebase mapper for LLM token efficiency.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -24,5 +24,8 @@
24
24
  "chokidar": "^3.6.0",
25
25
  "ignore": "^5.3.1"
26
26
  },
27
- "type": "module"
27
+ "type": "module",
28
+ "scripts": {
29
+ "test": "node --test test/*.test.js"
30
+ }
28
31
  }
@@ -0,0 +1,72 @@
1
+ import assert from 'node:assert';
2
+ import test from 'node:test';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ // Import the core functions by reading the file and eval-ing or refactoring.
8
+ // For simplicity in this environment, I will redefine the core logic in the test
9
+ // or point to the index.js if it was exported.
10
+ // Since index.js is a CLI, I'll extract the logic into a testable state.
11
+
12
+ import {
13
+ extractSymbols,
14
+ getIgnores,
15
+ SUPPORTED_EXTENSIONS
16
+ } from '../index.js';
17
+
18
+ test('extractSymbols - JS/TS Docstrings', () => {
19
+ const code = `
20
+ /**
21
+ * This is a test function
22
+ */
23
+ function testFunc(a, b) {}
24
+ `;
25
+ const symbols = extractSymbols(code);
26
+ assert.ok(symbols.some(s => s.includes('testFunc') && s.includes('This is a test function')));
27
+ });
28
+
29
+ test('extractSymbols - Signature Fallback', () => {
30
+ const code = `
31
+ function noDocFunc(arg1: string, arg2: number) {
32
+ return true;
33
+ }
34
+ `;
35
+ const symbols = extractSymbols(code);
36
+ assert.ok(symbols.some(s => s.includes('noDocFunc') && s.includes('arg1: string, arg2: number')));
37
+ });
38
+
39
+ test('extractSymbols - Python Docstrings', () => {
40
+ const code = `
41
+ def py_func(x):
42
+ """
43
+ Python docstring test
44
+ """
45
+ pass
46
+ `;
47
+ const symbols = extractSymbols(code);
48
+ // Note: Current regex captures 'def py_func'. Docstring is captured if it's ABOVE the def.
49
+ // Let's test the comment above pattern which is common for our current extractor.
50
+ const codeWithComment = `
51
+ # This is a python comment
52
+ def py_func_2(x):
53
+ pass
54
+ `;
55
+ const symbols2 = extractSymbols(codeWithComment);
56
+ assert.ok(symbols2.some(s => s.includes('py_func_2') && s.includes('This is a python comment')));
57
+ });
58
+
59
+ test('getIgnores - Default Patterns', () => {
60
+ const ig = getIgnores(process.cwd());
61
+ assert.strictEqual(ig.ignores('.git/'), true);
62
+ assert.strictEqual(ig.ignores('node_modules/'), true);
63
+ assert.strictEqual(ig.ignores('.idea/'), true);
64
+ assert.strictEqual(ig.ignores('src/main.js'), false);
65
+ });
66
+
67
+ test('Supported Extensions', () => {
68
+ assert.ok(SUPPORTED_EXTENSIONS.includes('.js'));
69
+ assert.ok(SUPPORTED_EXTENSIONS.includes('.py'));
70
+ assert.ok(SUPPORTED_EXTENSIONS.includes('.go'));
71
+ assert.ok(SUPPORTED_EXTENSIONS.includes('.rs'));
72
+ });