@duytransipher/gitnexus 1.0.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/LICENSE +73 -0
- package/README.md +261 -0
- package/dist/cli/ai-context.d.ts +23 -0
- package/dist/cli/ai-context.js +265 -0
- package/dist/cli/analyze.d.ts +12 -0
- package/dist/cli/analyze.js +345 -0
- package/dist/cli/augment.d.ts +13 -0
- package/dist/cli/augment.js +33 -0
- package/dist/cli/clean.d.ts +10 -0
- package/dist/cli/clean.js +60 -0
- package/dist/cli/eval-server.d.ts +37 -0
- package/dist/cli/eval-server.js +389 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +137 -0
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +30 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +36 -0
- package/dist/cli/serve.d.ts +4 -0
- package/dist/cli/serve.js +6 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +367 -0
- package/dist/cli/sipher-patched.d.ts +2 -0
- package/dist/cli/sipher-patched.js +77 -0
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +36 -0
- package/dist/cli/tool.d.ts +60 -0
- package/dist/cli/tool.js +180 -0
- package/dist/cli/wiki.d.ts +15 -0
- package/dist/cli/wiki.js +365 -0
- package/dist/config/ignore-service.d.ts +26 -0
- package/dist/config/ignore-service.js +284 -0
- package/dist/config/supported-languages.d.ts +15 -0
- package/dist/config/supported-languages.js +16 -0
- package/dist/core/augmentation/engine.d.ts +26 -0
- package/dist/core/augmentation/engine.js +240 -0
- package/dist/core/embeddings/embedder.d.ts +60 -0
- package/dist/core/embeddings/embedder.js +251 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
- package/dist/core/embeddings/embedding-pipeline.js +356 -0
- package/dist/core/embeddings/index.d.ts +9 -0
- package/dist/core/embeddings/index.js +9 -0
- package/dist/core/embeddings/text-generator.d.ts +24 -0
- package/dist/core/embeddings/text-generator.js +182 -0
- package/dist/core/embeddings/types.d.ts +87 -0
- package/dist/core/embeddings/types.js +32 -0
- package/dist/core/graph/graph.d.ts +2 -0
- package/dist/core/graph/graph.js +66 -0
- package/dist/core/graph/types.d.ts +66 -0
- package/dist/core/graph/types.js +1 -0
- package/dist/core/ingestion/ast-cache.d.ts +11 -0
- package/dist/core/ingestion/ast-cache.js +35 -0
- package/dist/core/ingestion/call-processor.d.ts +23 -0
- package/dist/core/ingestion/call-processor.js +793 -0
- package/dist/core/ingestion/call-routing.d.ts +68 -0
- package/dist/core/ingestion/call-routing.js +129 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
- package/dist/core/ingestion/cluster-enricher.js +170 -0
- package/dist/core/ingestion/community-processor.d.ts +39 -0
- package/dist/core/ingestion/community-processor.js +312 -0
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +40 -0
- package/dist/core/ingestion/entry-point-scoring.js +353 -0
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +231 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
- package/dist/core/ingestion/filesystem-walker.js +81 -0
- package/dist/core/ingestion/framework-detection.d.ts +54 -0
- package/dist/core/ingestion/framework-detection.js +411 -0
- package/dist/core/ingestion/heritage-processor.d.ts +28 -0
- package/dist/core/ingestion/heritage-processor.js +251 -0
- package/dist/core/ingestion/import-processor.d.ts +34 -0
- package/dist/core/ingestion/import-processor.js +398 -0
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +19 -0
- package/dist/core/ingestion/parsing-processor.js +315 -0
- package/dist/core/ingestion/pipeline.d.ts +6 -0
- package/dist/core/ingestion/pipeline.js +401 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +315 -0
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +18 -0
- package/dist/core/ingestion/resolvers/index.js +13 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -0
- package/dist/core/ingestion/resolvers/python.d.ts +19 -0
- package/dist/core/ingestion/resolvers/python.js +52 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
- package/dist/core/ingestion/resolvers/ruby.js +15 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +123 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +122 -0
- package/dist/core/ingestion/structure-processor.d.ts +2 -0
- package/dist/core/ingestion/structure-processor.js +36 -0
- package/dist/core/ingestion/symbol-table.d.ts +63 -0
- package/dist/core/ingestion/symbol-table.js +85 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
- package/dist/core/ingestion/tree-sitter-queries.js +888 -0
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +613 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +383 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +467 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
- package/dist/core/ingestion/type-extractors/index.js +31 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +681 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +549 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +455 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/ruby.js +389 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +456 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
- package/dist/core/ingestion/type-extractors/shared.js +810 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +137 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
- package/dist/core/ingestion/type-extractors/types.js +1 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +494 -0
- package/dist/core/ingestion/utils.d.ts +138 -0
- package/dist/core/ingestion/utils.js +1290 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
- package/dist/core/ingestion/workers/parse-worker.js +1126 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
- package/dist/core/ingestion/workers/worker-pool.js +128 -0
- package/dist/core/lbug/csv-generator.d.ts +33 -0
- package/dist/core/lbug/csv-generator.js +366 -0
- package/dist/core/lbug/lbug-adapter.d.ts +103 -0
- package/dist/core/lbug/lbug-adapter.js +769 -0
- package/dist/core/lbug/schema.d.ts +53 -0
- package/dist/core/lbug/schema.js +430 -0
- package/dist/core/search/bm25-index.d.ts +23 -0
- package/dist/core/search/bm25-index.js +96 -0
- package/dist/core/search/hybrid-search.d.ts +49 -0
- package/dist/core/search/hybrid-search.js +118 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
- package/dist/core/tree-sitter/parser-loader.js +63 -0
- package/dist/core/wiki/generator.d.ts +120 -0
- package/dist/core/wiki/generator.js +939 -0
- package/dist/core/wiki/graph-queries.d.ts +80 -0
- package/dist/core/wiki/graph-queries.js +238 -0
- package/dist/core/wiki/html-viewer.d.ts +10 -0
- package/dist/core/wiki/html-viewer.js +297 -0
- package/dist/core/wiki/llm-client.d.ts +43 -0
- package/dist/core/wiki/llm-client.js +186 -0
- package/dist/core/wiki/prompts.d.ts +53 -0
- package/dist/core/wiki/prompts.js +174 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +3 -0
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +108 -0
- package/dist/mcp/core/lbug-adapter.d.ts +57 -0
- package/dist/mcp/core/lbug-adapter.js +455 -0
- package/dist/mcp/local/local-backend.d.ts +181 -0
- package/dist/mcp/local/local-backend.js +1722 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +411 -0
- package/dist/mcp/server.d.ts +23 -0
- package/dist/mcp/server.js +296 -0
- package/dist/mcp/staleness.d.ts +15 -0
- package/dist/mcp/staleness.js +29 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +292 -0
- package/dist/server/api.d.ts +10 -0
- package/dist/server/api.js +344 -0
- package/dist/server/mcp-http.d.ts +13 -0
- package/dist/server/mcp-http.js +100 -0
- package/dist/storage/git.d.ts +6 -0
- package/dist/storage/git.js +35 -0
- package/dist/storage/repo-manager.d.ts +138 -0
- package/dist/storage/repo-manager.js +299 -0
- package/dist/types/pipeline.d.ts +32 -0
- package/dist/types/pipeline.js +18 -0
- package/dist/unreal/bridge.d.ts +4 -0
- package/dist/unreal/bridge.js +113 -0
- package/dist/unreal/config.d.ts +6 -0
- package/dist/unreal/config.js +55 -0
- package/dist/unreal/types.d.ts +105 -0
- package/dist/unreal/types.js +1 -0
- package/hooks/claude/gitnexus-hook.cjs +238 -0
- package/hooks/claude/pre-tool-use.sh +79 -0
- package/hooks/claude/session-start.sh +42 -0
- package/package.json +100 -0
- package/scripts/ensure-cli-executable.cjs +21 -0
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/scripts/setup-unreal-gitnexus.ps1 +191 -0
- package/skills/gitnexus-cli.md +82 -0
- package/skills/gitnexus-debugging.md +89 -0
- package/skills/gitnexus-exploring.md +78 -0
- package/skills/gitnexus-guide.md +64 -0
- package/skills/gitnexus-impact-analysis.md +97 -0
- package/skills/gitnexus-pr-review.md +163 -0
- package/skills/gitnexus-refactoring.md +121 -0
- package/vendor/GitNexusUnreal/GitNexusUnreal.uplugin +18 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/GitNexusUnreal.Build.cs +29 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Private/GitNexusBlueprintAnalyzerCommandlet.cpp +465 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Private/GitNexusUnrealModule.cpp +15 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Public/GitNexusBlueprintAnalyzerCommandlet.h +46 -0
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Public/GitNexusUnrealModule.h +10 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GitNexus Claude Code Hook
|
|
4
|
+
*
|
|
5
|
+
* PreToolUse — intercepts Grep/Glob/Bash searches and augments
|
|
6
|
+
* with graph context from the GitNexus index.
|
|
7
|
+
* PostToolUse — detects stale index after git mutations and notifies
|
|
8
|
+
* the agent to reindex.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: SessionStart hooks are broken on Windows (Claude Code bug).
|
|
11
|
+
* Session context is injected via CLAUDE.md / skills instead.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Read JSON input from stdin synchronously.
|
|
20
|
+
*/
|
|
21
|
+
function readInput() {
|
|
22
|
+
try {
|
|
23
|
+
const data = fs.readFileSync(0, 'utf-8');
|
|
24
|
+
return JSON.parse(data);
|
|
25
|
+
} catch {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Find the .gitnexus directory by walking up from startDir.
|
|
32
|
+
* Returns the path to .gitnexus/ or null if not found.
|
|
33
|
+
*/
|
|
34
|
+
function findGitNexusDir(startDir) {
|
|
35
|
+
let dir = startDir || process.cwd();
|
|
36
|
+
for (let i = 0; i < 5; i++) {
|
|
37
|
+
const candidate = path.join(dir, '.gitnexus');
|
|
38
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
39
|
+
const parent = path.dirname(dir);
|
|
40
|
+
if (parent === dir) break;
|
|
41
|
+
dir = parent;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract search pattern from tool input.
|
|
48
|
+
*/
|
|
49
|
+
function extractPattern(toolName, toolInput) {
|
|
50
|
+
if (toolName === 'Grep') {
|
|
51
|
+
return toolInput.pattern || null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (toolName === 'Glob') {
|
|
55
|
+
const raw = toolInput.pattern || '';
|
|
56
|
+
const match = raw.match(/[*\/]([a-zA-Z][a-zA-Z0-9_-]{2,})/);
|
|
57
|
+
return match ? match[1] : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (toolName === 'Bash') {
|
|
61
|
+
const cmd = toolInput.command || '';
|
|
62
|
+
if (!/\brg\b|\bgrep\b/.test(cmd)) return null;
|
|
63
|
+
|
|
64
|
+
const tokens = cmd.split(/\s+/);
|
|
65
|
+
let foundCmd = false;
|
|
66
|
+
let skipNext = false;
|
|
67
|
+
const flagsWithValues = new Set(['-e', '-f', '-m', '-A', '-B', '-C', '-g', '--glob', '-t', '--type', '--include', '--exclude']);
|
|
68
|
+
|
|
69
|
+
for (const token of tokens) {
|
|
70
|
+
if (skipNext) { skipNext = false; continue; }
|
|
71
|
+
if (!foundCmd) {
|
|
72
|
+
if (/\brg$|\bgrep$/.test(token)) foundCmd = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (token.startsWith('-')) {
|
|
76
|
+
if (flagsWithValues.has(token)) skipNext = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const cleaned = token.replace(/['"]/g, '');
|
|
80
|
+
return cleaned.length >= 3 ? cleaned : null;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the gitnexus CLI path.
|
|
90
|
+
* 1. Relative path (works when script is inside npm package)
|
|
91
|
+
* 2. require.resolve (works when gitnexus is globally installed)
|
|
92
|
+
* 3. Fall back to npx (returns empty string)
|
|
93
|
+
*/
|
|
94
|
+
function resolveCliPath() {
|
|
95
|
+
let cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');
|
|
96
|
+
if (!fs.existsSync(cliPath)) {
|
|
97
|
+
try {
|
|
98
|
+
cliPath = require.resolve('gitnexus/dist/cli/index.js');
|
|
99
|
+
} catch {
|
|
100
|
+
cliPath = '';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return cliPath;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Spawn a gitnexus CLI command synchronously.
|
|
108
|
+
* Returns the stderr output (KuzuDB captures stdout at OS level).
|
|
109
|
+
*/
|
|
110
|
+
function runGitNexusCli(cliPath, args, cwd, timeout) {
|
|
111
|
+
const isWin = process.platform === 'win32';
|
|
112
|
+
if (cliPath) {
|
|
113
|
+
return spawnSync(
|
|
114
|
+
process.execPath,
|
|
115
|
+
[cliPath, ...args],
|
|
116
|
+
{ encoding: 'utf-8', timeout, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
// On Windows, invoke npx.cmd directly (no shell needed)
|
|
120
|
+
return spawnSync(
|
|
121
|
+
isWin ? 'npx.cmd' : 'npx',
|
|
122
|
+
['-y', 'gitnexus', ...args],
|
|
123
|
+
{ encoding: 'utf-8', timeout: timeout + 5000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* PreToolUse handler — augment searches with graph context.
|
|
129
|
+
*/
|
|
130
|
+
function handlePreToolUse(input) {
|
|
131
|
+
const cwd = input.cwd || process.cwd();
|
|
132
|
+
if (!path.isAbsolute(cwd)) return;
|
|
133
|
+
if (!findGitNexusDir(cwd)) return;
|
|
134
|
+
|
|
135
|
+
const toolName = input.tool_name || '';
|
|
136
|
+
const toolInput = input.tool_input || {};
|
|
137
|
+
|
|
138
|
+
if (toolName !== 'Grep' && toolName !== 'Glob' && toolName !== 'Bash') return;
|
|
139
|
+
|
|
140
|
+
const pattern = extractPattern(toolName, toolInput);
|
|
141
|
+
if (!pattern || pattern.length < 3) return;
|
|
142
|
+
|
|
143
|
+
const cliPath = resolveCliPath();
|
|
144
|
+
let result = '';
|
|
145
|
+
try {
|
|
146
|
+
const child = runGitNexusCli(cliPath, ['augment', '--', pattern], cwd, 7000);
|
|
147
|
+
if (!child.error && child.status === 0) {
|
|
148
|
+
result = child.stderr || '';
|
|
149
|
+
}
|
|
150
|
+
} catch { /* graceful failure */ }
|
|
151
|
+
|
|
152
|
+
if (result && result.trim()) {
|
|
153
|
+
sendHookResponse('PreToolUse', result.trim());
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Emit a PostToolUse hook response with additional context for the agent.
|
|
159
|
+
*/
|
|
160
|
+
function sendHookResponse(hookEventName, message) {
|
|
161
|
+
console.log(JSON.stringify({
|
|
162
|
+
hookSpecificOutput: { hookEventName, additionalContext: message }
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* PostToolUse handler — detect index staleness after git mutations.
|
|
168
|
+
*
|
|
169
|
+
* Instead of spawning a full `gitnexus analyze` synchronously (which blocks
|
|
170
|
+
* the agent for up to 120s and risks KuzuDB corruption on timeout), we do a
|
|
171
|
+
* lightweight staleness check: compare `git rev-parse HEAD` against the
|
|
172
|
+
* lastCommit stored in `.gitnexus/meta.json`. If they differ, notify the
|
|
173
|
+
* agent so it can decide when to reindex.
|
|
174
|
+
*/
|
|
175
|
+
function handlePostToolUse(input) {
|
|
176
|
+
const toolName = input.tool_name || '';
|
|
177
|
+
if (toolName !== 'Bash') return;
|
|
178
|
+
|
|
179
|
+
const command = (input.tool_input || {}).command || '';
|
|
180
|
+
if (!/\bgit\s+(commit|merge|rebase|cherry-pick|pull)(\s|$)/.test(command)) return;
|
|
181
|
+
|
|
182
|
+
// Only proceed if the command succeeded
|
|
183
|
+
const toolOutput = input.tool_output || {};
|
|
184
|
+
if (toolOutput.exit_code !== undefined && toolOutput.exit_code !== 0) return;
|
|
185
|
+
|
|
186
|
+
const cwd = input.cwd || process.cwd();
|
|
187
|
+
if (!path.isAbsolute(cwd)) return;
|
|
188
|
+
const gitNexusDir = findGitNexusDir(cwd);
|
|
189
|
+
if (!gitNexusDir) return;
|
|
190
|
+
|
|
191
|
+
// Compare HEAD against last indexed commit — skip if unchanged
|
|
192
|
+
let currentHead = '';
|
|
193
|
+
try {
|
|
194
|
+
const headResult = spawnSync('git', ['rev-parse', 'HEAD'], {
|
|
195
|
+
encoding: 'utf-8', timeout: 3000, cwd, stdio: ['pipe', 'pipe', 'pipe'],
|
|
196
|
+
});
|
|
197
|
+
currentHead = (headResult.stdout || '').trim();
|
|
198
|
+
} catch { return; }
|
|
199
|
+
|
|
200
|
+
if (!currentHead) return;
|
|
201
|
+
|
|
202
|
+
let lastCommit = '';
|
|
203
|
+
let hadEmbeddings = false;
|
|
204
|
+
try {
|
|
205
|
+
const meta = JSON.parse(fs.readFileSync(path.join(gitNexusDir, 'meta.json'), 'utf-8'));
|
|
206
|
+
lastCommit = meta.lastCommit || '';
|
|
207
|
+
hadEmbeddings = (meta.stats && meta.stats.embeddings > 0);
|
|
208
|
+
} catch { /* no meta — treat as stale */ }
|
|
209
|
+
|
|
210
|
+
// If HEAD matches last indexed commit, no reindex needed
|
|
211
|
+
if (currentHead && currentHead === lastCommit) return;
|
|
212
|
+
|
|
213
|
+
const analyzeCmd = `npx gitnexus analyze${hadEmbeddings ? ' --embeddings' : ''}`;
|
|
214
|
+
sendHookResponse('PostToolUse',
|
|
215
|
+
`GitNexus index is stale (last indexed: ${lastCommit ? lastCommit.slice(0, 7) : 'never'}). ` +
|
|
216
|
+
`Run \`${analyzeCmd}\` to update the knowledge graph.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Dispatch map for hook events
|
|
221
|
+
const handlers = {
|
|
222
|
+
PreToolUse: handlePreToolUse,
|
|
223
|
+
PostToolUse: handlePostToolUse,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
function main() {
|
|
227
|
+
try {
|
|
228
|
+
const input = readInput();
|
|
229
|
+
const handler = handlers[input.hook_event_name || ''];
|
|
230
|
+
if (handler) handler(input);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
if (process.env.GITNEXUS_DEBUG) {
|
|
233
|
+
console.error('GitNexus hook error:', (err.message || '').slice(0, 200));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
main();
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GitNexus PreToolUse hook for Claude Code
|
|
3
|
+
# Intercepts Grep/Glob/Bash searches and augments with graph context.
|
|
4
|
+
# Receives JSON on stdin with { tool_name, tool_input, cwd, ... }
|
|
5
|
+
# Returns JSON with additionalContext for graph-enriched results.
|
|
6
|
+
|
|
7
|
+
INPUT=$(cat)
|
|
8
|
+
|
|
9
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
10
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
|
|
11
|
+
|
|
12
|
+
# Extract search pattern based on tool type
|
|
13
|
+
PATTERN=""
|
|
14
|
+
|
|
15
|
+
case "$TOOL_NAME" in
|
|
16
|
+
Grep)
|
|
17
|
+
PATTERN=$(echo "$INPUT" | jq -r '.tool_input.pattern // empty' 2>/dev/null)
|
|
18
|
+
;;
|
|
19
|
+
Glob)
|
|
20
|
+
# Glob patterns are file paths, not search terms — extract meaningful part
|
|
21
|
+
RAW=$(echo "$INPUT" | jq -r '.tool_input.pattern // empty' 2>/dev/null)
|
|
22
|
+
# Strip glob syntax to get the meaningful name (e.g., "**/*.ts" → skip, "auth*.ts" → "auth")
|
|
23
|
+
PATTERN=$(echo "$RAW" | sed -n 's/.*[*\/]\([a-zA-Z][a-zA-Z0-9_-]*\).*/\1/p')
|
|
24
|
+
;;
|
|
25
|
+
Bash)
|
|
26
|
+
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
27
|
+
# Only augment grep/rg commands
|
|
28
|
+
if echo "$CMD" | grep -qE '\brg\b|\bgrep\b'; then
|
|
29
|
+
# Extract pattern from rg/grep
|
|
30
|
+
if echo "$CMD" | grep -qE '\brg\b'; then
|
|
31
|
+
PATTERN=$(echo "$CMD" | sed -n "s/.*\brg\s\+\(--[^ ]*\s\+\)*['\"]\\?\([^'\";\| >]*\\).*/\2/p")
|
|
32
|
+
elif echo "$CMD" | grep -qE '\bgrep\b'; then
|
|
33
|
+
PATTERN=$(echo "$CMD" | sed -n "s/.*\bgrep\s\+\(-[^ ]*\s\+\)*['\"]\\?\([^'\";\| >]*\\).*/\2/p")
|
|
34
|
+
fi
|
|
35
|
+
fi
|
|
36
|
+
;;
|
|
37
|
+
*)
|
|
38
|
+
# Not a search tool — skip
|
|
39
|
+
exit 0
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
42
|
+
|
|
43
|
+
# Skip if pattern too short or empty
|
|
44
|
+
if [ -z "$PATTERN" ] || [ ${#PATTERN} -lt 3 ]; then
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Check if we're in a GitNexus-indexed repo
|
|
49
|
+
dir="${CWD:-$PWD}"
|
|
50
|
+
found=false
|
|
51
|
+
for i in 1 2 3 4 5; do
|
|
52
|
+
if [ -d "$dir/.gitnexus" ]; then
|
|
53
|
+
found=true
|
|
54
|
+
break
|
|
55
|
+
fi
|
|
56
|
+
parent="$(dirname "$dir")"
|
|
57
|
+
[ "$parent" = "$dir" ] && break
|
|
58
|
+
dir="$parent"
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
if [ "$found" = false ]; then
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Run gitnexus augment — must be fast (<500ms target)
|
|
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)
|
|
68
|
+
|
|
69
|
+
if [ -n "$RESULT" ]; then
|
|
70
|
+
ESCAPED=$(echo "$RESULT" | jq -Rs .)
|
|
71
|
+
jq -n --argjson ctx "$ESCAPED" '{
|
|
72
|
+
hookSpecificOutput: {
|
|
73
|
+
hookEventName: "PreToolUse",
|
|
74
|
+
additionalContext: $ctx
|
|
75
|
+
}
|
|
76
|
+
}'
|
|
77
|
+
else
|
|
78
|
+
exit 0
|
|
79
|
+
fi
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GitNexus SessionStart hook for Claude Code
|
|
3
|
+
# Fires on session startup. Stdout is injected into Claude's context.
|
|
4
|
+
# Checks if the current directory has a GitNexus index.
|
|
5
|
+
|
|
6
|
+
dir="$PWD"
|
|
7
|
+
found=false
|
|
8
|
+
for i in 1 2 3 4 5; do
|
|
9
|
+
if [ -d "$dir/.gitnexus" ]; then
|
|
10
|
+
found=true
|
|
11
|
+
break
|
|
12
|
+
fi
|
|
13
|
+
parent="$(dirname "$dir")"
|
|
14
|
+
[ "$parent" = "$dir" ] && break
|
|
15
|
+
dir="$parent"
|
|
16
|
+
done
|
|
17
|
+
|
|
18
|
+
if [ "$found" = false ]; then
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Inject GitNexus context — this stdout goes directly into Claude's context
|
|
23
|
+
cat << 'EOF'
|
|
24
|
+
## GitNexus Code Intelligence
|
|
25
|
+
|
|
26
|
+
This codebase is indexed by GitNexus, providing a knowledge graph with execution flows, relationships, and semantic search.
|
|
27
|
+
|
|
28
|
+
**Available MCP Tools:**
|
|
29
|
+
- `query` — Process-grouped code intelligence (execution flows related to a concept)
|
|
30
|
+
- `context` — 360-degree symbol view (categorized refs, process participation)
|
|
31
|
+
- `impact` — Blast radius analysis (what breaks if you change a symbol)
|
|
32
|
+
- `detect_changes` — Git-diff impact analysis (what do your changes affect)
|
|
33
|
+
- `rename` — Multi-file coordinated rename with confidence tags
|
|
34
|
+
- `cypher` — Raw graph queries
|
|
35
|
+
- `list_repos` — Discover indexed repos
|
|
36
|
+
|
|
37
|
+
**Quick Start:** READ `gitnexus://repo/{name}/context` for codebase overview, then use `query` to find execution flows.
|
|
38
|
+
|
|
39
|
+
**Resources:** `gitnexus://repo/{name}/context` (overview), `/processes` (execution flows), `/schema` (for Cypher)
|
|
40
|
+
EOF
|
|
41
|
+
|
|
42
|
+
exit 0
|
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@duytransipher/gitnexus",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Sipher-maintained fork of GitNexus for graph-powered code intelligence via MCP and CLI.",
|
|
5
|
+
"author": "DuyTranSipher",
|
|
6
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
7
|
+
"homepage": "https://github.com/DuyTranSipher/sipher-git-nexus#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/DuyTranSipher/sipher-git-nexus.git",
|
|
11
|
+
"directory": "gitnexus"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/DuyTranSipher/sipher-git-nexus/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"code-intelligence",
|
|
20
|
+
"knowledge-graph",
|
|
21
|
+
"cursor",
|
|
22
|
+
"claude",
|
|
23
|
+
"ai-agent",
|
|
24
|
+
"gitnexus",
|
|
25
|
+
"static-analysis",
|
|
26
|
+
"codebase-indexing"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"bin": {
|
|
30
|
+
"gitnexus": "dist/cli/index.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"hooks",
|
|
35
|
+
"scripts",
|
|
36
|
+
"skills",
|
|
37
|
+
"vendor"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"dev": "tsx watch src/cli/index.ts",
|
|
42
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:unit": "vitest run test/unit",
|
|
45
|
+
"test:integration": "vitest run test/integration",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"test:coverage": "vitest run --coverage",
|
|
48
|
+
"prepare": "npm run build",
|
|
49
|
+
"postinstall": "node scripts/patch-tree-sitter-swift.cjs",
|
|
50
|
+
"prepack": "npm run build && node scripts/ensure-cli-executable.cjs"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@huggingface/transformers": "^3.0.0",
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
55
|
+
"cli-progress": "^3.12.0",
|
|
56
|
+
"commander": "^12.0.0",
|
|
57
|
+
"cors": "^2.8.5",
|
|
58
|
+
"express": "^4.19.2",
|
|
59
|
+
"glob": "^11.0.0",
|
|
60
|
+
"graphology": "^0.25.4",
|
|
61
|
+
"graphology-indices": "^0.17.0",
|
|
62
|
+
"graphology-utils": "^2.3.0",
|
|
63
|
+
"@ladybugdb/core": "^0.15.2",
|
|
64
|
+
"ignore": "^7.0.5",
|
|
65
|
+
"lru-cache": "^11.0.0",
|
|
66
|
+
"mnemonist": "^0.39.0",
|
|
67
|
+
"pandemonium": "^2.4.0",
|
|
68
|
+
"tree-sitter": "^0.21.0",
|
|
69
|
+
"tree-sitter-c": "^0.21.0",
|
|
70
|
+
"tree-sitter-c-sharp": "^0.21.0",
|
|
71
|
+
"tree-sitter-cpp": "^0.22.0",
|
|
72
|
+
"tree-sitter-go": "^0.21.0",
|
|
73
|
+
"tree-sitter-java": "^0.21.0",
|
|
74
|
+
"tree-sitter-javascript": "^0.21.0",
|
|
75
|
+
"tree-sitter-php": "^0.23.12",
|
|
76
|
+
"tree-sitter-python": "^0.21.0",
|
|
77
|
+
"tree-sitter-ruby": "^0.23.1",
|
|
78
|
+
"tree-sitter-rust": "^0.21.0",
|
|
79
|
+
"tree-sitter-typescript": "^0.21.0",
|
|
80
|
+
"uuid": "^13.0.0"
|
|
81
|
+
},
|
|
82
|
+
"optionalDependencies": {
|
|
83
|
+
"tree-sitter-kotlin": "^0.3.8",
|
|
84
|
+
"tree-sitter-swift": "^0.6.0"
|
|
85
|
+
},
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"@types/cli-progress": "^3.11.6",
|
|
88
|
+
"@types/cors": "^2.8.17",
|
|
89
|
+
"@types/express": "^4.17.21",
|
|
90
|
+
"@types/node": "^20.0.0",
|
|
91
|
+
"@types/uuid": "^10.0.0",
|
|
92
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
93
|
+
"tsx": "^4.0.0",
|
|
94
|
+
"typescript": "^5.4.5",
|
|
95
|
+
"vitest": "^4.0.18"
|
|
96
|
+
},
|
|
97
|
+
"engines": {
|
|
98
|
+
"node": ">=18.0.0"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const cliPath = path.join(__dirname, '..', 'dist', 'cli', 'index.js');
|
|
5
|
+
|
|
6
|
+
if (!fs.existsSync(cliPath)) {
|
|
7
|
+
console.warn(`[prepack] Skipping executable bit because ${cliPath} does not exist.`);
|
|
8
|
+
process.exit(0);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
fs.chmodSync(cliPath, 0o755);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (process.platform === 'win32') {
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
console.warn(`[prepack] Continuing without chmod on Windows: ${message}`);
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* WORKAROUND: tree-sitter-swift@0.6.0 binding.gyp build failure
|
|
4
|
+
*
|
|
5
|
+
* Background:
|
|
6
|
+
* tree-sitter-swift@0.6.0's binding.gyp contains an "actions" array that
|
|
7
|
+
* invokes `tree-sitter generate` to regenerate parser.c from grammar.js.
|
|
8
|
+
* This is intended for grammar developers, but the published npm package
|
|
9
|
+
* already ships pre-generated parser files (parser.c, scanner.c), so the
|
|
10
|
+
* actions are unnecessary for consumers. Since consumers don't have
|
|
11
|
+
* tree-sitter-cli installed, the actions always fail during `npm install`.
|
|
12
|
+
*
|
|
13
|
+
* Why we can't just upgrade:
|
|
14
|
+
* tree-sitter-swift@0.7.1 fixes this (removes postinstall, ships prebuilds),
|
|
15
|
+
* but it requires tree-sitter@^0.22.1. The upstream project pins tree-sitter
|
|
16
|
+
* to ^0.21.0 and all other grammar packages depend on that version.
|
|
17
|
+
* Upgrading tree-sitter would be a separate breaking change.
|
|
18
|
+
*
|
|
19
|
+
* How this workaround works:
|
|
20
|
+
* 1. tree-sitter-swift's own postinstall fails (npm warns but continues)
|
|
21
|
+
* 2. This script runs as gitnexus's postinstall
|
|
22
|
+
* 3. It removes the "actions" array from binding.gyp
|
|
23
|
+
* 4. It rebuilds the native binding with the cleaned binding.gyp
|
|
24
|
+
*
|
|
25
|
+
* TODO: Remove this script when tree-sitter is upgraded to ^0.22.x,
|
|
26
|
+
* which allows using tree-sitter-swift@0.7.1+ directly.
|
|
27
|
+
*/
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
const { execSync } = require('child_process');
|
|
31
|
+
|
|
32
|
+
const swiftDir = path.join(__dirname, '..', 'node_modules', 'tree-sitter-swift');
|
|
33
|
+
const bindingPath = path.join(swiftDir, 'binding.gyp');
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(bindingPath)) {
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const content = fs.readFileSync(bindingPath, 'utf8');
|
|
41
|
+
let needsRebuild = false;
|
|
42
|
+
|
|
43
|
+
if (content.includes('"actions"')) {
|
|
44
|
+
// Strip Python-style comments (#) before JSON parsing
|
|
45
|
+
const cleaned = content.replace(/#[^\n]*/g, '');
|
|
46
|
+
const gyp = JSON.parse(cleaned);
|
|
47
|
+
|
|
48
|
+
if (gyp.targets && gyp.targets[0] && gyp.targets[0].actions) {
|
|
49
|
+
delete gyp.targets[0].actions;
|
|
50
|
+
fs.writeFileSync(bindingPath, JSON.stringify(gyp, null, 2) + '\n');
|
|
51
|
+
console.log('[tree-sitter-swift] Patched binding.gyp (removed actions array)');
|
|
52
|
+
needsRebuild = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if native binding exists
|
|
57
|
+
const bindingNode = path.join(swiftDir, 'build', 'Release', 'tree_sitter_swift_binding.node');
|
|
58
|
+
if (!fs.existsSync(bindingNode)) {
|
|
59
|
+
needsRebuild = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (needsRebuild) {
|
|
63
|
+
console.log('[tree-sitter-swift] Rebuilding native binding...');
|
|
64
|
+
execSync('npx node-gyp rebuild', {
|
|
65
|
+
cwd: swiftDir,
|
|
66
|
+
stdio: 'pipe',
|
|
67
|
+
timeout: 120000,
|
|
68
|
+
});
|
|
69
|
+
console.log('[tree-sitter-swift] Native binding built successfully');
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.warn('[tree-sitter-swift] Could not build native binding:', err.message);
|
|
73
|
+
console.warn('[tree-sitter-swift] You may need to manually run: cd node_modules/tree-sitter-swift && npx node-gyp rebuild');
|
|
74
|
+
}
|