gitnexus 1.3.6 → 1.3.8

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 (47) hide show
  1. package/dist/cli/ai-context.js +77 -23
  2. package/dist/cli/analyze.js +4 -11
  3. package/dist/cli/eval-server.d.ts +7 -0
  4. package/dist/cli/eval-server.js +16 -7
  5. package/dist/cli/index.js +2 -20
  6. package/dist/cli/mcp.js +2 -0
  7. package/dist/cli/setup.js +6 -1
  8. package/dist/config/supported-languages.d.ts +1 -0
  9. package/dist/config/supported-languages.js +1 -0
  10. package/dist/core/ingestion/call-processor.d.ts +5 -1
  11. package/dist/core/ingestion/call-processor.js +78 -0
  12. package/dist/core/ingestion/framework-detection.d.ts +1 -0
  13. package/dist/core/ingestion/framework-detection.js +49 -2
  14. package/dist/core/ingestion/import-processor.js +90 -39
  15. package/dist/core/ingestion/parsing-processor.d.ts +12 -1
  16. package/dist/core/ingestion/parsing-processor.js +92 -51
  17. package/dist/core/ingestion/pipeline.js +21 -2
  18. package/dist/core/ingestion/process-processor.js +0 -1
  19. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
  20. package/dist/core/ingestion/tree-sitter-queries.js +80 -0
  21. package/dist/core/ingestion/utils.d.ts +5 -0
  22. package/dist/core/ingestion/utils.js +20 -0
  23. package/dist/core/ingestion/workers/parse-worker.d.ts +11 -0
  24. package/dist/core/ingestion/workers/parse-worker.js +473 -51
  25. package/dist/core/kuzu/csv-generator.d.ts +4 -0
  26. package/dist/core/kuzu/csv-generator.js +23 -9
  27. package/dist/core/kuzu/kuzu-adapter.js +9 -3
  28. package/dist/core/tree-sitter/parser-loader.d.ts +1 -0
  29. package/dist/core/tree-sitter/parser-loader.js +3 -0
  30. package/dist/mcp/core/kuzu-adapter.d.ts +4 -3
  31. package/dist/mcp/core/kuzu-adapter.js +79 -16
  32. package/dist/mcp/local/local-backend.d.ts +13 -0
  33. package/dist/mcp/local/local-backend.js +148 -105
  34. package/dist/mcp/server.js +26 -11
  35. package/dist/storage/git.js +4 -1
  36. package/dist/storage/repo-manager.js +16 -2
  37. package/hooks/claude/gitnexus-hook.cjs +28 -8
  38. package/hooks/claude/pre-tool-use.sh +2 -1
  39. package/package.json +11 -3
  40. package/dist/cli/claude-hooks.d.ts +0 -22
  41. package/dist/cli/claude-hooks.js +0 -97
  42. package/dist/cli/view.d.ts +0 -13
  43. package/dist/cli/view.js +0 -59
  44. package/dist/core/graph/html-graph-viewer.d.ts +0 -15
  45. package/dist/core/graph/html-graph-viewer.js +0 -542
  46. package/dist/core/graph/html-graph-viewer.test.d.ts +0 -1
  47. package/dist/core/graph/html-graph-viewer.test.js +0 -67
@@ -151,7 +151,13 @@ export const registerRepo = async (repoPath, meta) => {
151
151
  const name = path.basename(resolved);
152
152
  const { storagePath } = getStoragePaths(resolved);
153
153
  const entries = await readRegistry();
154
- const existing = entries.findIndex((e) => path.resolve(e.path) === resolved);
154
+ const existing = entries.findIndex((e) => {
155
+ const a = path.resolve(e.path);
156
+ const b = resolved;
157
+ return process.platform === 'win32'
158
+ ? a.toLowerCase() === b.toLowerCase()
159
+ : a === b;
160
+ });
155
161
  const entry = {
156
162
  name,
157
163
  path: resolved,
@@ -227,5 +233,13 @@ export const loadCLIConfig = async () => {
227
233
  export const saveCLIConfig = async (config) => {
228
234
  const dir = getGlobalDir();
229
235
  await fs.mkdir(dir, { recursive: true });
230
- await fs.writeFile(getGlobalConfigPath(), JSON.stringify(config, null, 2), 'utf-8');
236
+ const configPath = getGlobalConfigPath();
237
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
238
+ // Restrict file permissions on Unix (config may contain API keys)
239
+ if (process.platform !== 'win32') {
240
+ try {
241
+ await fs.chmod(configPath, 0o600);
242
+ }
243
+ catch { /* best-effort */ }
244
+ }
231
245
  };
@@ -101,20 +101,40 @@ function main() {
101
101
  const pattern = extractPattern(toolName, toolInput);
102
102
  if (!pattern || pattern.length < 3) return;
103
103
 
104
- // Resolve CLI path relative to this hook script (same package)
105
- // hooks/claude/gitnexus-hook.cjs dist/cli/index.js
106
- const cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');
104
+ // Resolve CLI path try multiple strategies:
105
+ // 1. Relative path (works when script is inside npm package)
106
+ // 2. require.resolve (works when gitnexus is globally installed)
107
+ // 3. Fall back to npx (works when neither is available)
108
+ let cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');
109
+ if (!fs.existsSync(cliPath)) {
110
+ try {
111
+ cliPath = require.resolve('gitnexus/dist/cli/index.js');
112
+ } catch {
113
+ cliPath = ''; // will use npx fallback
114
+ }
115
+ }
107
116
 
108
117
  // augment CLI writes result to stderr (KuzuDB's native module captures
109
118
  // stdout fd at OS level, making it unusable in subprocess contexts).
110
119
  const { spawnSync } = require('child_process');
111
120
  let result = '';
112
121
  try {
113
- const child = spawnSync(
114
- process.execPath,
115
- [cliPath, 'augment', pattern],
116
- { encoding: 'utf-8', timeout: 8000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
117
- );
122
+ let child;
123
+ if (cliPath) {
124
+ child = spawnSync(
125
+ process.execPath,
126
+ [cliPath, 'augment', pattern],
127
+ { encoding: 'utf-8', timeout: 8000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
128
+ );
129
+ } else {
130
+ // npx fallback
131
+ const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
132
+ child = spawnSync(
133
+ cmd,
134
+ ['-y', 'gitnexus', 'augment', pattern],
135
+ { encoding: 'utf-8', timeout: 15000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
136
+ );
137
+ }
118
138
  result = child.stderr || '';
119
139
  } catch { /* graceful failure */ }
120
140
 
@@ -63,7 +63,8 @@ if [ "$found" = false ]; then
63
63
  fi
64
64
 
65
65
  # Run gitnexus augment — must be fast (<500ms target)
66
- RESULT=$(cd "$CWD" && npx -y gitnexus augment "$PATTERN" 2>/dev/null)
66
+ # augment writes to stderr (KuzuDB captures stdout at OS level), so capture stderr and discard stdout
67
+ RESULT=$(cd "$CWD" && npx -y gitnexus augment "$PATTERN" 2>&1 1>/dev/null)
67
68
 
68
69
  if [ -n "$RESULT" ]; then
69
70
  ESCAPED=$(echo "$RESULT" | jq -Rs .)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.3.6",
3
+ "version": "1.3.8",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -39,6 +39,11 @@
39
39
  "scripts": {
40
40
  "build": "tsc",
41
41
  "dev": "tsx watch src/cli/index.ts",
42
+ "test": "vitest run test/unit",
43
+ "test:integration": "vitest run test/integration",
44
+ "test:all": "vitest run",
45
+ "test:watch": "vitest",
46
+ "test:coverage": "vitest run --coverage",
42
47
  "prepare": "npm run build",
43
48
  "postinstall": "node scripts/patch-tree-sitter-swift.cjs"
44
49
  },
@@ -64,11 +69,11 @@
64
69
  "tree-sitter-go": "^0.21.0",
65
70
  "tree-sitter-java": "^0.21.0",
66
71
  "tree-sitter-javascript": "^0.21.0",
72
+ "tree-sitter-kotlin": "^0.3.8",
67
73
  "tree-sitter-php": "^0.23.12",
68
74
  "tree-sitter-python": "^0.21.0",
69
75
  "tree-sitter-rust": "^0.21.0",
70
76
  "tree-sitter-typescript": "^0.21.0",
71
- "typescript": "^5.4.5",
72
77
  "uuid": "^13.0.0"
73
78
  },
74
79
  "optionalDependencies": {
@@ -80,7 +85,10 @@
80
85
  "@types/express": "^4.17.21",
81
86
  "@types/node": "^20.0.0",
82
87
  "@types/uuid": "^10.0.0",
83
- "tsx": "^4.0.0"
88
+ "@vitest/coverage-v8": "^4.0.18",
89
+ "tsx": "^4.0.0",
90
+ "typescript": "^5.4.5",
91
+ "vitest": "^4.0.18"
84
92
  },
85
93
  "engines": {
86
94
  "node": ">=18.0.0"
@@ -1,22 +0,0 @@
1
- /**
2
- * Claude Code Hook Registration
3
- *
4
- * Registers the GitNexus PreToolUse hook in ~/.claude/hooks.json
5
- * so that grep/glob/bash calls are automatically augmented with
6
- * knowledge graph context.
7
- *
8
- * Idempotent — safe to call multiple times.
9
- */
10
- /**
11
- * Register (or verify) the GitNexus hook in Claude Code's global hooks.json.
12
- *
13
- * - Creates ~/.claude/ and hooks.json if they don't exist
14
- * - Preserves existing hooks from other tools
15
- * - Skips if GitNexus hook is already registered
16
- *
17
- * Returns a status message for the CLI output.
18
- */
19
- export declare function registerClaudeHook(): Promise<{
20
- registered: boolean;
21
- message: string;
22
- }>;
@@ -1,97 +0,0 @@
1
- /**
2
- * Claude Code Hook Registration
3
- *
4
- * Registers the GitNexus PreToolUse hook in ~/.claude/hooks.json
5
- * so that grep/glob/bash calls are automatically augmented with
6
- * knowledge graph context.
7
- *
8
- * Idempotent — safe to call multiple times.
9
- */
10
- import fs from 'fs/promises';
11
- import path from 'path';
12
- import os from 'os';
13
- import { fileURLToPath } from 'url';
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = path.dirname(__filename);
16
- /**
17
- * Get the absolute path to the gitnexus-hook.js file.
18
- * Works for both local dev and npm-installed packages.
19
- */
20
- function getHookScriptPath() {
21
- // From dist/cli/claude-hooks.js → hooks/claude/gitnexus-hook.js
22
- const packageRoot = path.resolve(__dirname, '..', '..');
23
- return path.join(packageRoot, 'hooks', 'claude', 'gitnexus-hook.cjs');
24
- }
25
- /**
26
- * Register (or verify) the GitNexus hook in Claude Code's global hooks.json.
27
- *
28
- * - Creates ~/.claude/ and hooks.json if they don't exist
29
- * - Preserves existing hooks from other tools
30
- * - Skips if GitNexus hook is already registered
31
- *
32
- * Returns a status message for the CLI output.
33
- */
34
- export async function registerClaudeHook() {
35
- const claudeDir = path.join(os.homedir(), '.claude');
36
- const hooksFile = path.join(claudeDir, 'hooks.json');
37
- const hookScript = getHookScriptPath();
38
- // Check if the hook script exists
39
- try {
40
- await fs.access(hookScript);
41
- }
42
- catch {
43
- return { registered: false, message: 'Hook script not found (package may be incomplete)' };
44
- }
45
- // Build the hook command — use node + absolute path for reliability
46
- const hookCommand = `node "${hookScript}"`;
47
- // Check if ~/.claude/ exists (user has Claude Code installed)
48
- try {
49
- await fs.access(claudeDir);
50
- }
51
- catch {
52
- // No Claude Code installation — skip silently
53
- return { registered: false, message: 'Claude Code not detected (~/.claude/ not found)' };
54
- }
55
- // Read existing hooks.json or start fresh
56
- let hooksConfig = {};
57
- try {
58
- const existing = await fs.readFile(hooksFile, 'utf-8');
59
- hooksConfig = JSON.parse(existing);
60
- }
61
- catch {
62
- // File doesn't exist or is invalid — we'll create it
63
- }
64
- // Ensure the hooks structure exists
65
- if (!hooksConfig.hooks) {
66
- hooksConfig.hooks = {};
67
- }
68
- if (!Array.isArray(hooksConfig.hooks.PreToolUse)) {
69
- hooksConfig.hooks.PreToolUse = [];
70
- }
71
- // Check if GitNexus hook is already registered
72
- const existingEntry = hooksConfig.hooks.PreToolUse.find((entry) => {
73
- if (!entry.hooks || !Array.isArray(entry.hooks))
74
- return false;
75
- return entry.hooks.some((h) => h.command && (h.command.includes('gitnexus-hook') ||
76
- h.command.includes('gitnexus augment')));
77
- });
78
- if (existingEntry) {
79
- return { registered: true, message: 'Claude Code hook already registered' };
80
- }
81
- // Add the GitNexus hook entry
82
- hooksConfig.hooks.PreToolUse.push({
83
- matcher: {
84
- tool_name: "Grep|Glob|Bash"
85
- },
86
- hooks: [
87
- {
88
- type: "command",
89
- command: hookCommand,
90
- timeout: 8000
91
- }
92
- ]
93
- });
94
- // Write back
95
- await fs.writeFile(hooksFile, JSON.stringify(hooksConfig, null, 2) + '\n', 'utf-8');
96
- return { registered: true, message: 'Claude Code hook registered' };
97
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * View Command
3
- *
4
- * Generates a self-contained graph.html from the KuzuDB index and
5
- * opens it in the default browser.
6
- *
7
- * Usage: gitnexus view [path] [--no-open]
8
- */
9
- export interface ViewCommandOptions {
10
- noOpen?: boolean;
11
- output?: string;
12
- }
13
- export declare const viewCommand: (inputPath?: string, options?: ViewCommandOptions) => Promise<void>;
package/dist/cli/view.js DELETED
@@ -1,59 +0,0 @@
1
- /**
2
- * View Command
3
- *
4
- * Generates a self-contained graph.html from the KuzuDB index and
5
- * opens it in the default browser.
6
- *
7
- * Usage: gitnexus view [path] [--no-open]
8
- */
9
- import path from 'path';
10
- import fs from 'fs/promises';
11
- import { exec } from 'child_process';
12
- import { findRepo } from '../storage/repo-manager.js';
13
- import { initKuzu } from '../core/kuzu/kuzu-adapter.js';
14
- import { buildGraph } from '../server/api.js';
15
- import { generateHTMLGraphViewer } from '../core/graph/html-graph-viewer.js';
16
- import { getCurrentCommit } from '../storage/git.js';
17
- function openInBrowser(filePath) {
18
- const url = `file://${filePath}`;
19
- let cmd;
20
- if (process.platform === 'darwin') {
21
- cmd = `open "${url}"`;
22
- }
23
- else if (process.platform === 'win32') {
24
- cmd = `start "" "${url}"`;
25
- }
26
- else {
27
- cmd = `xdg-open "${url}"`;
28
- }
29
- exec(cmd, (err) => {
30
- if (err)
31
- console.error('Failed to open browser:', err.message);
32
- });
33
- }
34
- export const viewCommand = async (inputPath, options) => {
35
- console.log('⚠ Experimental: gitnexus view is under active development.\n');
36
- const repoPath = inputPath ? path.resolve(inputPath) : process.cwd();
37
- const repo = await findRepo(repoPath);
38
- if (!repo) {
39
- console.error('No index found. Run: gitnexus analyze');
40
- process.exit(1);
41
- }
42
- const currentCommit = getCurrentCommit(repo.repoPath);
43
- if (currentCommit !== repo.meta.lastCommit) {
44
- console.warn('Index is stale — showing last indexed state. Run: gitnexus analyze\n');
45
- }
46
- await initKuzu(repo.kuzuPath);
47
- const { nodes, relationships } = await buildGraph();
48
- const projectName = path.basename(repo.repoPath);
49
- const outputPath = options?.output
50
- ? path.resolve(options.output)
51
- : path.join(repo.storagePath, 'graph.html');
52
- const html = generateHTMLGraphViewer(nodes, relationships, projectName);
53
- await fs.writeFile(outputPath, html, 'utf-8');
54
- console.log(`Graph written to: ${outputPath}`);
55
- console.log(`Nodes: ${nodes.length} Edges: ${relationships.length}`);
56
- if (!options?.noOpen) {
57
- openInBrowser(outputPath);
58
- }
59
- };
@@ -1,15 +0,0 @@
1
- /**
2
- * HTML Graph Viewer Generator
3
- *
4
- * Produces a self-contained graph.html that renders the knowledge graph
5
- * using Sigma.js v2 + graphology (both from CDN).
6
- *
7
- * Critical: node `content` fields are stripped before embedding to prevent
8
- * </script> injection from source code breaking the HTML parser.
9
- */
10
- import { GraphNode, GraphRelationship } from './types.js';
11
- /**
12
- * Generate a self-contained HTML file that renders the knowledge graph.
13
- * Strips large/unsafe fields from nodes before embedding.
14
- */
15
- export declare function generateHTMLGraphViewer(nodes: GraphNode[], relationships: GraphRelationship[], projectName: string): string;