@veewo/gitnexus 1.3.4
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/README.md +234 -0
- package/dist/benchmark/agent-context/evaluators.d.ts +9 -0
- package/dist/benchmark/agent-context/evaluators.js +196 -0
- package/dist/benchmark/agent-context/evaluators.test.d.ts +1 -0
- package/dist/benchmark/agent-context/evaluators.test.js +39 -0
- package/dist/benchmark/agent-context/io.d.ts +2 -0
- package/dist/benchmark/agent-context/io.js +23 -0
- package/dist/benchmark/agent-context/io.test.d.ts +1 -0
- package/dist/benchmark/agent-context/io.test.js +19 -0
- package/dist/benchmark/agent-context/report.d.ts +2 -0
- package/dist/benchmark/agent-context/report.js +59 -0
- package/dist/benchmark/agent-context/report.test.d.ts +1 -0
- package/dist/benchmark/agent-context/report.test.js +85 -0
- package/dist/benchmark/agent-context/runner.d.ts +46 -0
- package/dist/benchmark/agent-context/runner.js +111 -0
- package/dist/benchmark/agent-context/runner.test.d.ts +1 -0
- package/dist/benchmark/agent-context/runner.test.js +79 -0
- package/dist/benchmark/agent-context/tool-runner.d.ts +7 -0
- package/dist/benchmark/agent-context/tool-runner.js +18 -0
- package/dist/benchmark/agent-context/tool-runner.test.d.ts +1 -0
- package/dist/benchmark/agent-context/tool-runner.test.js +11 -0
- package/dist/benchmark/agent-context/types.d.ts +40 -0
- package/dist/benchmark/agent-context/types.js +1 -0
- package/dist/benchmark/analyze-runner.d.ts +16 -0
- package/dist/benchmark/analyze-runner.js +51 -0
- package/dist/benchmark/analyze-runner.test.d.ts +1 -0
- package/dist/benchmark/analyze-runner.test.js +37 -0
- package/dist/benchmark/evaluators.d.ts +6 -0
- package/dist/benchmark/evaluators.js +10 -0
- package/dist/benchmark/evaluators.test.d.ts +1 -0
- package/dist/benchmark/evaluators.test.js +12 -0
- package/dist/benchmark/io.d.ts +7 -0
- package/dist/benchmark/io.js +25 -0
- package/dist/benchmark/io.test.d.ts +1 -0
- package/dist/benchmark/io.test.js +35 -0
- package/dist/benchmark/neonspark-candidates.d.ts +19 -0
- package/dist/benchmark/neonspark-candidates.js +94 -0
- package/dist/benchmark/neonspark-candidates.test.d.ts +1 -0
- package/dist/benchmark/neonspark-candidates.test.js +43 -0
- package/dist/benchmark/neonspark-materialize.d.ts +19 -0
- package/dist/benchmark/neonspark-materialize.js +111 -0
- package/dist/benchmark/neonspark-materialize.test.d.ts +1 -0
- package/dist/benchmark/neonspark-materialize.test.js +124 -0
- package/dist/benchmark/neonspark-sync.d.ts +3 -0
- package/dist/benchmark/neonspark-sync.js +53 -0
- package/dist/benchmark/neonspark-sync.test.d.ts +1 -0
- package/dist/benchmark/neonspark-sync.test.js +20 -0
- package/dist/benchmark/report.d.ts +1 -0
- package/dist/benchmark/report.js +7 -0
- package/dist/benchmark/runner.d.ts +48 -0
- package/dist/benchmark/runner.js +302 -0
- package/dist/benchmark/runner.test.d.ts +1 -0
- package/dist/benchmark/runner.test.js +50 -0
- package/dist/benchmark/scoring.d.ts +16 -0
- package/dist/benchmark/scoring.js +27 -0
- package/dist/benchmark/scoring.test.d.ts +1 -0
- package/dist/benchmark/scoring.test.js +24 -0
- package/dist/benchmark/tool-runner.d.ts +6 -0
- package/dist/benchmark/tool-runner.js +17 -0
- package/dist/benchmark/types.d.ts +36 -0
- package/dist/benchmark/types.js +1 -0
- package/dist/cli/ai-context.d.ts +22 -0
- package/dist/cli/ai-context.js +184 -0
- package/dist/cli/ai-context.test.d.ts +1 -0
- package/dist/cli/ai-context.test.js +30 -0
- package/dist/cli/analyze-multi-scope-regression.test.d.ts +1 -0
- package/dist/cli/analyze-multi-scope-regression.test.js +22 -0
- package/dist/cli/analyze-options.d.ts +7 -0
- package/dist/cli/analyze-options.js +56 -0
- package/dist/cli/analyze-options.test.d.ts +1 -0
- package/dist/cli/analyze-options.test.js +36 -0
- package/dist/cli/analyze.d.ts +14 -0
- package/dist/cli/analyze.js +384 -0
- package/dist/cli/augment.d.ts +13 -0
- package/dist/cli/augment.js +33 -0
- package/dist/cli/benchmark-agent-context.d.ts +29 -0
- package/dist/cli/benchmark-agent-context.js +61 -0
- package/dist/cli/benchmark-agent-context.test.d.ts +1 -0
- package/dist/cli/benchmark-agent-context.test.js +80 -0
- package/dist/cli/benchmark-unity.d.ts +15 -0
- package/dist/cli/benchmark-unity.js +31 -0
- package/dist/cli/benchmark-unity.test.d.ts +1 -0
- package/dist/cli/benchmark-unity.test.js +18 -0
- package/dist/cli/claude-hooks.d.ts +22 -0
- package/dist/cli/claude-hooks.js +97 -0
- package/dist/cli/clean.d.ts +10 -0
- package/dist/cli/clean.js +60 -0
- package/dist/cli/eval-server.d.ts +30 -0
- package/dist/cli/eval-server.js +372 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +182 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +33 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +34 -0
- package/dist/cli/repo-manager-alias.test.d.ts +1 -0
- package/dist/cli/repo-manager-alias.test.js +40 -0
- package/dist/cli/scope-filter.test.d.ts +1 -0
- package/dist/cli/scope-filter.test.js +49 -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 +311 -0
- package/dist/cli/setup.test.d.ts +1 -0
- package/dist/cli/setup.test.js +31 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +27 -0
- package/dist/cli/tool.d.ts +40 -0
- package/dist/cli/tool.js +94 -0
- package/dist/cli/version.test.d.ts +1 -0
- package/dist/cli/version.test.js +19 -0
- package/dist/cli/wiki.d.ts +15 -0
- package/dist/cli/wiki.js +361 -0
- package/dist/config/ignore-service.d.ts +1 -0
- package/dist/config/ignore-service.js +210 -0
- package/dist/config/supported-languages.d.ts +12 -0
- package/dist/config/supported-languages.js +15 -0
- package/dist/core/augmentation/engine.d.ts +26 -0
- package/dist/core/augmentation/engine.js +213 -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 +329 -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 +61 -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 +34 -0
- package/dist/core/ingestion/call-processor.d.ts +15 -0
- package/dist/core/ingestion/call-processor.js +327 -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/entry-point-scoring.d.ts +39 -0
- package/dist/core/ingestion/entry-point-scoring.js +260 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
- package/dist/core/ingestion/filesystem-walker.js +80 -0
- package/dist/core/ingestion/framework-detection.d.ts +39 -0
- package/dist/core/ingestion/framework-detection.js +235 -0
- package/dist/core/ingestion/heritage-processor.d.ts +20 -0
- package/dist/core/ingestion/heritage-processor.js +197 -0
- package/dist/core/ingestion/import-processor.d.ts +38 -0
- package/dist/core/ingestion/import-processor.js +778 -0
- package/dist/core/ingestion/parsing-processor.d.ts +15 -0
- package/dist/core/ingestion/parsing-processor.js +291 -0
- package/dist/core/ingestion/pipeline.d.ts +5 -0
- package/dist/core/ingestion/pipeline.js +323 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +309 -0
- package/dist/core/ingestion/scope-filter.d.ts +25 -0
- package/dist/core/ingestion/scope-filter.js +100 -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 +33 -0
- package/dist/core/ingestion/symbol-table.js +38 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -0
- package/dist/core/ingestion/tree-sitter-queries.js +398 -0
- package/dist/core/ingestion/utils.d.ts +10 -0
- package/dist/core/ingestion/utils.js +50 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +59 -0
- package/dist/core/ingestion/workers/parse-worker.js +672 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
- package/dist/core/ingestion/workers/worker-pool.js +120 -0
- package/dist/core/kuzu/csv-generator.d.ts +29 -0
- package/dist/core/kuzu/csv-generator.js +336 -0
- package/dist/core/kuzu/kuzu-adapter.d.ts +101 -0
- package/dist/core/kuzu/kuzu-adapter.js +753 -0
- package/dist/core/kuzu/schema.d.ts +53 -0
- package/dist/core/kuzu/schema.js +407 -0
- package/dist/core/search/bm25-index.d.ts +23 -0
- package/dist/core/search/bm25-index.js +95 -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 +4 -0
- package/dist/core/tree-sitter/parser-loader.js +44 -0
- package/dist/core/wiki/generator.d.ts +110 -0
- package/dist/core/wiki/generator.js +786 -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 +40 -0
- package/dist/core/wiki/llm-client.js +162 -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/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +108 -0
- package/dist/mcp/core/kuzu-adapter.d.ts +34 -0
- package/dist/mcp/core/kuzu-adapter.js +231 -0
- package/dist/mcp/local/local-backend.d.ts +160 -0
- package/dist/mcp/local/local-backend.js +1646 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +407 -0
- package/dist/mcp/server.d.ts +23 -0
- package/dist/mcp/server.js +251 -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 +195 -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 +32 -0
- package/dist/storage/repo-manager.d.ts +125 -0
- package/dist/storage/repo-manager.js +257 -0
- package/dist/types/pipeline.d.ts +34 -0
- package/dist/types/pipeline.js +18 -0
- package/hooks/claude/gitnexus-hook.cjs +135 -0
- package/hooks/claude/pre-tool-use.sh +78 -0
- package/hooks/claude/session-start.sh +42 -0
- package/package.json +92 -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-refactoring.md +121 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
}>;
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean Command
|
|
3
|
+
*
|
|
4
|
+
* Removes the .gitnexus index from the current repository.
|
|
5
|
+
* Also unregisters it from the global registry.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import { findRepo, unregisterRepo, listRegisteredRepos } from '../storage/repo-manager.js';
|
|
9
|
+
export const cleanCommand = async (options) => {
|
|
10
|
+
// --all flag: clean all indexed repos
|
|
11
|
+
if (options?.all) {
|
|
12
|
+
if (!options?.force) {
|
|
13
|
+
const entries = await listRegisteredRepos();
|
|
14
|
+
if (entries.length === 0) {
|
|
15
|
+
console.log('No indexed repositories found.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.log(`This will delete GitNexus indexes for ${entries.length} repo(s):`);
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
console.log(` - ${entry.name} (${entry.path})`);
|
|
21
|
+
}
|
|
22
|
+
console.log('\nRun with --force to confirm deletion.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const entries = await listRegisteredRepos();
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
try {
|
|
28
|
+
await fs.rm(entry.storagePath, { recursive: true, force: true });
|
|
29
|
+
await unregisterRepo(entry.path);
|
|
30
|
+
console.log(`Deleted: ${entry.name} (${entry.storagePath})`);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
console.error(`Failed to delete ${entry.name}:`, err);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Default: clean current repo
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
const repo = await findRepo(cwd);
|
|
41
|
+
if (!repo) {
|
|
42
|
+
console.log('No indexed repository found in this directory.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const repoName = repo.repoPath.split(/[/\\]/).pop() || repo.repoPath;
|
|
46
|
+
if (!options?.force) {
|
|
47
|
+
console.log(`This will delete the GitNexus index for: ${repoName}`);
|
|
48
|
+
console.log(` Path: ${repo.storagePath}`);
|
|
49
|
+
console.log('\nRun with --force to confirm deletion.');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await fs.rm(repo.storagePath, { recursive: true, force: true });
|
|
54
|
+
await unregisterRepo(repo.repoPath);
|
|
55
|
+
console.log(`Deleted: ${repo.storagePath}`);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error('Failed to delete:', err);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eval Server — Lightweight HTTP server for SWE-bench evaluation
|
|
3
|
+
*
|
|
4
|
+
* Keeps KuzuDB warm in memory so tool calls from the agent are near-instant.
|
|
5
|
+
* Designed to run inside Docker containers during SWE-bench evaluation.
|
|
6
|
+
*
|
|
7
|
+
* KEY DESIGN: Returns LLM-friendly text, not raw JSON.
|
|
8
|
+
* Raw JSON wastes tokens and is hard for models to parse. The text formatter
|
|
9
|
+
* converts structured results into compact, readable output that models
|
|
10
|
+
* can immediately act on. Next-step hints guide the agent through a
|
|
11
|
+
* productive tool-chaining workflow (query → context → impact → fix).
|
|
12
|
+
*
|
|
13
|
+
* Architecture:
|
|
14
|
+
* Agent bash cmd → curl localhost:PORT/tool/query → eval-server → LocalBackend → format → text
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* gitnexus eval-server # default port 4848
|
|
18
|
+
* gitnexus eval-server --port 4848 # explicit port
|
|
19
|
+
* gitnexus eval-server --idle-timeout 300 # auto-shutdown after 300s idle
|
|
20
|
+
*
|
|
21
|
+
* API:
|
|
22
|
+
* POST /tool/:name — Call a tool. Body is JSON arguments. Returns formatted text.
|
|
23
|
+
* GET /health — Health check. Returns {"status":"ok","repos":[...]}
|
|
24
|
+
* POST /shutdown — Graceful shutdown.
|
|
25
|
+
*/
|
|
26
|
+
export interface EvalServerOptions {
|
|
27
|
+
port?: string;
|
|
28
|
+
idleTimeout?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function evalServerCommand(options?: EvalServerOptions): Promise<void>;
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eval Server — Lightweight HTTP server for SWE-bench evaluation
|
|
3
|
+
*
|
|
4
|
+
* Keeps KuzuDB warm in memory so tool calls from the agent are near-instant.
|
|
5
|
+
* Designed to run inside Docker containers during SWE-bench evaluation.
|
|
6
|
+
*
|
|
7
|
+
* KEY DESIGN: Returns LLM-friendly text, not raw JSON.
|
|
8
|
+
* Raw JSON wastes tokens and is hard for models to parse. The text formatter
|
|
9
|
+
* converts structured results into compact, readable output that models
|
|
10
|
+
* can immediately act on. Next-step hints guide the agent through a
|
|
11
|
+
* productive tool-chaining workflow (query → context → impact → fix).
|
|
12
|
+
*
|
|
13
|
+
* Architecture:
|
|
14
|
+
* Agent bash cmd → curl localhost:PORT/tool/query → eval-server → LocalBackend → format → text
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* gitnexus eval-server # default port 4848
|
|
18
|
+
* gitnexus eval-server --port 4848 # explicit port
|
|
19
|
+
* gitnexus eval-server --idle-timeout 300 # auto-shutdown after 300s idle
|
|
20
|
+
*
|
|
21
|
+
* API:
|
|
22
|
+
* POST /tool/:name — Call a tool. Body is JSON arguments. Returns formatted text.
|
|
23
|
+
* GET /health — Health check. Returns {"status":"ok","repos":[...]}
|
|
24
|
+
* POST /shutdown — Graceful shutdown.
|
|
25
|
+
*/
|
|
26
|
+
import http from 'http';
|
|
27
|
+
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
28
|
+
// ─── Text Formatters ──────────────────────────────────────────────────
|
|
29
|
+
// Convert structured JSON results into compact, LLM-friendly text.
|
|
30
|
+
// Design: minimize tokens, maximize actionability.
|
|
31
|
+
function formatQueryResult(result) {
|
|
32
|
+
if (result.error)
|
|
33
|
+
return `Error: ${result.error}`;
|
|
34
|
+
const lines = [];
|
|
35
|
+
const processes = result.processes || [];
|
|
36
|
+
const symbols = result.process_symbols || [];
|
|
37
|
+
const defs = result.definitions || [];
|
|
38
|
+
if (processes.length === 0 && defs.length === 0) {
|
|
39
|
+
return 'No matching execution flows found. Try a different search term or use grep.';
|
|
40
|
+
}
|
|
41
|
+
lines.push(`Found ${processes.length} execution flow(s):\n`);
|
|
42
|
+
for (let i = 0; i < processes.length; i++) {
|
|
43
|
+
const p = processes[i];
|
|
44
|
+
lines.push(`${i + 1}. ${p.summary} (${p.step_count} steps, ${p.symbol_count} symbols)`);
|
|
45
|
+
// Show symbols belonging to this process
|
|
46
|
+
const procSymbols = symbols.filter((s) => s.process_id === p.id);
|
|
47
|
+
for (const s of procSymbols.slice(0, 6)) {
|
|
48
|
+
const loc = s.startLine ? `:${s.startLine}` : '';
|
|
49
|
+
lines.push(` ${s.type} ${s.name} → ${s.filePath}${loc}`);
|
|
50
|
+
}
|
|
51
|
+
if (procSymbols.length > 6) {
|
|
52
|
+
lines.push(` ... and ${procSymbols.length - 6} more`);
|
|
53
|
+
}
|
|
54
|
+
lines.push('');
|
|
55
|
+
}
|
|
56
|
+
if (defs.length > 0) {
|
|
57
|
+
lines.push(`Standalone definitions:`);
|
|
58
|
+
for (const d of defs.slice(0, 8)) {
|
|
59
|
+
lines.push(` ${d.type || 'Symbol'} ${d.name} → ${d.filePath || '?'}`);
|
|
60
|
+
}
|
|
61
|
+
if (defs.length > 8)
|
|
62
|
+
lines.push(` ... and ${defs.length - 8} more`);
|
|
63
|
+
}
|
|
64
|
+
return lines.join('\n').trim();
|
|
65
|
+
}
|
|
66
|
+
function formatContextResult(result) {
|
|
67
|
+
if (result.error)
|
|
68
|
+
return `Error: ${result.error}`;
|
|
69
|
+
if (result.status === 'ambiguous') {
|
|
70
|
+
const lines = [`Multiple symbols named '${result.candidates?.[0]?.name || '?'}'. Disambiguate with file path:\n`];
|
|
71
|
+
for (const c of result.candidates || []) {
|
|
72
|
+
lines.push(` ${c.kind} ${c.name} → ${c.filePath}:${c.line || '?'} (uid: ${c.uid})`);
|
|
73
|
+
}
|
|
74
|
+
lines.push(`\nRe-run: gitnexus-context "${result.candidates?.[0]?.name}" "<file_path>"`);
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
77
|
+
const sym = result.symbol;
|
|
78
|
+
if (!sym)
|
|
79
|
+
return 'Symbol not found.';
|
|
80
|
+
const lines = [];
|
|
81
|
+
const loc = sym.startLine ? `:${sym.startLine}-${sym.endLine}` : '';
|
|
82
|
+
lines.push(`${sym.kind} ${sym.name} → ${sym.filePath}${loc}`);
|
|
83
|
+
lines.push('');
|
|
84
|
+
// Incoming refs (who calls/imports/extends this)
|
|
85
|
+
const incoming = result.incoming || {};
|
|
86
|
+
const incomingCount = Object.values(incoming).reduce((sum, arr) => sum + arr.length, 0);
|
|
87
|
+
if (incomingCount > 0) {
|
|
88
|
+
lines.push(`Called/imported by (${incomingCount}):`);
|
|
89
|
+
for (const [relType, refs] of Object.entries(incoming)) {
|
|
90
|
+
for (const ref of refs.slice(0, 10)) {
|
|
91
|
+
lines.push(` ← [${relType}] ${ref.kind} ${ref.name} → ${ref.filePath}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
lines.push('');
|
|
95
|
+
}
|
|
96
|
+
// Outgoing refs (what this calls/imports)
|
|
97
|
+
const outgoing = result.outgoing || {};
|
|
98
|
+
const outgoingCount = Object.values(outgoing).reduce((sum, arr) => sum + arr.length, 0);
|
|
99
|
+
if (outgoingCount > 0) {
|
|
100
|
+
lines.push(`Calls/imports (${outgoingCount}):`);
|
|
101
|
+
for (const [relType, refs] of Object.entries(outgoing)) {
|
|
102
|
+
for (const ref of refs.slice(0, 10)) {
|
|
103
|
+
lines.push(` → [${relType}] ${ref.kind} ${ref.name} → ${ref.filePath}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
lines.push('');
|
|
107
|
+
}
|
|
108
|
+
// Processes
|
|
109
|
+
const procs = result.processes || [];
|
|
110
|
+
if (procs.length > 0) {
|
|
111
|
+
lines.push(`Participates in ${procs.length} execution flow(s):`);
|
|
112
|
+
for (const p of procs) {
|
|
113
|
+
lines.push(` • ${p.name} (step ${p.step_index}/${p.step_count})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (sym.content) {
|
|
117
|
+
lines.push('');
|
|
118
|
+
lines.push(`Source:`);
|
|
119
|
+
lines.push(sym.content);
|
|
120
|
+
}
|
|
121
|
+
return lines.join('\n').trim();
|
|
122
|
+
}
|
|
123
|
+
function formatImpactResult(result) {
|
|
124
|
+
if (result.error)
|
|
125
|
+
return `Error: ${result.error}`;
|
|
126
|
+
const target = result.target;
|
|
127
|
+
const direction = result.direction;
|
|
128
|
+
const byDepth = result.byDepth || {};
|
|
129
|
+
const total = result.impactedCount || 0;
|
|
130
|
+
if (total === 0) {
|
|
131
|
+
return `${target?.name || '?'}: No ${direction} dependencies found. This symbol appears isolated.`;
|
|
132
|
+
}
|
|
133
|
+
const lines = [];
|
|
134
|
+
const dirLabel = direction === 'upstream' ? 'depends on this (will break if changed)' : 'this depends on';
|
|
135
|
+
lines.push(`Blast radius for ${target?.kind || ''} ${target?.name} (${direction}): ${total} symbol(s) ${dirLabel}\n`);
|
|
136
|
+
const depthLabels = {
|
|
137
|
+
1: 'WILL BREAK (direct)',
|
|
138
|
+
2: 'LIKELY AFFECTED (indirect)',
|
|
139
|
+
3: 'MAY NEED TESTING (transitive)',
|
|
140
|
+
};
|
|
141
|
+
for (const depth of [1, 2, 3]) {
|
|
142
|
+
const items = byDepth[depth];
|
|
143
|
+
if (!items || items.length === 0)
|
|
144
|
+
continue;
|
|
145
|
+
lines.push(`d=${depth}: ${depthLabels[depth] || ''} (${items.length})`);
|
|
146
|
+
for (const item of items.slice(0, 12)) {
|
|
147
|
+
const conf = item.confidence < 1 ? ` (conf: ${item.confidence})` : '';
|
|
148
|
+
lines.push(` ${item.type} ${item.name} → ${item.filePath} [${item.relationType}]${conf}`);
|
|
149
|
+
}
|
|
150
|
+
if (items.length > 12) {
|
|
151
|
+
lines.push(` ... and ${items.length - 12} more`);
|
|
152
|
+
}
|
|
153
|
+
lines.push('');
|
|
154
|
+
}
|
|
155
|
+
return lines.join('\n').trim();
|
|
156
|
+
}
|
|
157
|
+
function formatCypherResult(result) {
|
|
158
|
+
if (result.error)
|
|
159
|
+
return `Error: ${result.error}`;
|
|
160
|
+
if (Array.isArray(result)) {
|
|
161
|
+
if (result.length === 0)
|
|
162
|
+
return 'Query returned 0 rows.';
|
|
163
|
+
// Format as simple table
|
|
164
|
+
const keys = Object.keys(result[0]);
|
|
165
|
+
const lines = [`${result.length} row(s):\n`];
|
|
166
|
+
for (const row of result.slice(0, 30)) {
|
|
167
|
+
const parts = keys.map(k => `${k}: ${row[k]}`);
|
|
168
|
+
lines.push(` ${parts.join(' | ')}`);
|
|
169
|
+
}
|
|
170
|
+
if (result.length > 30) {
|
|
171
|
+
lines.push(` ... ${result.length - 30} more rows`);
|
|
172
|
+
}
|
|
173
|
+
return lines.join('\n');
|
|
174
|
+
}
|
|
175
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
176
|
+
}
|
|
177
|
+
function formatDetectChangesResult(result) {
|
|
178
|
+
if (result.error)
|
|
179
|
+
return `Error: ${result.error}`;
|
|
180
|
+
const summary = result.summary || {};
|
|
181
|
+
const lines = [];
|
|
182
|
+
if (summary.changed_count === 0) {
|
|
183
|
+
return 'No changes detected.';
|
|
184
|
+
}
|
|
185
|
+
lines.push(`Changes: ${summary.changed_files || 0} files, ${summary.changed_count || 0} symbols`);
|
|
186
|
+
lines.push(`Affected processes: ${summary.affected_count || 0}`);
|
|
187
|
+
lines.push(`Risk level: ${summary.risk_level || 'unknown'}\n`);
|
|
188
|
+
const changed = result.changed_symbols || [];
|
|
189
|
+
if (changed.length > 0) {
|
|
190
|
+
lines.push(`Changed symbols:`);
|
|
191
|
+
for (const s of changed.slice(0, 15)) {
|
|
192
|
+
lines.push(` ${s.type} ${s.name} → ${s.filePath}`);
|
|
193
|
+
}
|
|
194
|
+
if (changed.length > 15)
|
|
195
|
+
lines.push(` ... and ${changed.length - 15} more`);
|
|
196
|
+
lines.push('');
|
|
197
|
+
}
|
|
198
|
+
const affected = result.affected_processes || [];
|
|
199
|
+
if (affected.length > 0) {
|
|
200
|
+
lines.push(`Affected execution flows:`);
|
|
201
|
+
for (const p of affected.slice(0, 10)) {
|
|
202
|
+
const steps = (p.changed_steps || []).map((s) => s.symbol).join(', ');
|
|
203
|
+
lines.push(` • ${p.name} (${p.step_count} steps) — changed: ${steps}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return lines.join('\n').trim();
|
|
207
|
+
}
|
|
208
|
+
function formatListReposResult(result) {
|
|
209
|
+
if (!Array.isArray(result) || result.length === 0) {
|
|
210
|
+
return 'No indexed repositories.';
|
|
211
|
+
}
|
|
212
|
+
const lines = ['Indexed repositories:\n'];
|
|
213
|
+
for (const r of result) {
|
|
214
|
+
const stats = r.stats || {};
|
|
215
|
+
lines.push(` ${r.name} — ${stats.nodes || '?'} symbols, ${stats.edges || '?'} relationships, ${stats.processes || '?'} flows`);
|
|
216
|
+
lines.push(` Path: ${r.path}`);
|
|
217
|
+
lines.push(` Indexed: ${r.indexedAt}`);
|
|
218
|
+
}
|
|
219
|
+
return lines.join('\n');
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Format a tool result as compact, LLM-friendly text.
|
|
223
|
+
*/
|
|
224
|
+
function formatToolResult(toolName, result) {
|
|
225
|
+
switch (toolName) {
|
|
226
|
+
case 'query': return formatQueryResult(result);
|
|
227
|
+
case 'context': return formatContextResult(result);
|
|
228
|
+
case 'impact': return formatImpactResult(result);
|
|
229
|
+
case 'cypher': return formatCypherResult(result);
|
|
230
|
+
case 'detect_changes': return formatDetectChangesResult(result);
|
|
231
|
+
case 'list_repos': return formatListReposResult(result);
|
|
232
|
+
default: return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// ─── Next-Step Hints ──────────────────────────────────────────────────
|
|
236
|
+
// Guide the agent to the logical next tool call.
|
|
237
|
+
// Critical for tool chaining: query → context → impact → fix.
|
|
238
|
+
function getNextStepHint(toolName) {
|
|
239
|
+
switch (toolName) {
|
|
240
|
+
case 'query':
|
|
241
|
+
return '\n---\nNext: Pick a symbol above and run gitnexus-context "<name>" to see all its callers, callees, and execution flows.';
|
|
242
|
+
case 'context':
|
|
243
|
+
return '\n---\nNext: To check what breaks if you change this, run gitnexus-impact "<name>" upstream';
|
|
244
|
+
case 'impact':
|
|
245
|
+
return '\n---\nNext: Review d=1 items first (WILL BREAK). Read the source with cat to understand the code, then make your fix.';
|
|
246
|
+
case 'cypher':
|
|
247
|
+
return '\n---\nNext: To explore a result symbol in depth, run gitnexus-context "<name>"';
|
|
248
|
+
case 'detect_changes':
|
|
249
|
+
return '\n---\nNext: Run gitnexus-context "<symbol>" on high-risk changed symbols to check their callers.';
|
|
250
|
+
default:
|
|
251
|
+
return '';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// ─── Server ───────────────────────────────────────────────────────────
|
|
255
|
+
export async function evalServerCommand(options) {
|
|
256
|
+
const port = parseInt(options?.port || '4848');
|
|
257
|
+
const idleTimeoutSec = parseInt(options?.idleTimeout || '0');
|
|
258
|
+
const backend = new LocalBackend();
|
|
259
|
+
const ok = await backend.init();
|
|
260
|
+
if (!ok) {
|
|
261
|
+
console.error('GitNexus eval-server: No indexed repositories found. Run: gitnexus analyze');
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
const repos = await backend.listRepos();
|
|
265
|
+
console.error(`GitNexus eval-server: ${repos.length} repo(s) loaded: ${repos.map(r => r.name).join(', ')}`);
|
|
266
|
+
let idleTimer = null;
|
|
267
|
+
function resetIdleTimer() {
|
|
268
|
+
if (idleTimeoutSec <= 0)
|
|
269
|
+
return;
|
|
270
|
+
if (idleTimer)
|
|
271
|
+
clearTimeout(idleTimer);
|
|
272
|
+
idleTimer = setTimeout(async () => {
|
|
273
|
+
console.error('GitNexus eval-server: Idle timeout reached, shutting down');
|
|
274
|
+
await backend.disconnect();
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}, idleTimeoutSec * 1000);
|
|
277
|
+
}
|
|
278
|
+
const server = http.createServer(async (req, res) => {
|
|
279
|
+
resetIdleTimer();
|
|
280
|
+
try {
|
|
281
|
+
// Health check
|
|
282
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
283
|
+
res.setHeader('Content-Type', 'application/json');
|
|
284
|
+
res.writeHead(200);
|
|
285
|
+
res.end(JSON.stringify({ status: 'ok', repos: repos.map(r => r.name) }));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
// Shutdown
|
|
289
|
+
if (req.method === 'POST' && req.url === '/shutdown') {
|
|
290
|
+
res.setHeader('Content-Type', 'application/json');
|
|
291
|
+
res.writeHead(200);
|
|
292
|
+
res.end(JSON.stringify({ status: 'shutting_down' }));
|
|
293
|
+
setTimeout(async () => {
|
|
294
|
+
await backend.disconnect();
|
|
295
|
+
server.close();
|
|
296
|
+
process.exit(0);
|
|
297
|
+
}, 100);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
// Tool calls: POST /tool/:name
|
|
301
|
+
const toolMatch = req.url?.match(/^\/tool\/(\w+)$/);
|
|
302
|
+
if (req.method === 'POST' && toolMatch) {
|
|
303
|
+
const toolName = toolMatch[1];
|
|
304
|
+
const body = await readBody(req);
|
|
305
|
+
let args = {};
|
|
306
|
+
if (body.trim()) {
|
|
307
|
+
try {
|
|
308
|
+
args = JSON.parse(body);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
312
|
+
res.writeHead(400);
|
|
313
|
+
res.end('Error: Invalid JSON body');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Call tool, format result as text, append next-step hint
|
|
318
|
+
const result = await backend.callTool(toolName, args);
|
|
319
|
+
const formatted = formatToolResult(toolName, result);
|
|
320
|
+
const hint = getNextStepHint(toolName);
|
|
321
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
322
|
+
res.writeHead(200);
|
|
323
|
+
res.end(formatted + hint);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// 404
|
|
327
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
328
|
+
res.writeHead(404);
|
|
329
|
+
res.end('Not found. Use POST /tool/:name or GET /health');
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
333
|
+
res.writeHead(500);
|
|
334
|
+
res.end(`Error: ${err.message || 'Internal error'}`);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
server.listen(port, '127.0.0.1', () => {
|
|
338
|
+
console.error(`GitNexus eval-server: listening on http://127.0.0.1:${port}`);
|
|
339
|
+
console.error(` POST /tool/query — search execution flows`);
|
|
340
|
+
console.error(` POST /tool/context — 360-degree symbol view`);
|
|
341
|
+
console.error(` POST /tool/impact — blast radius analysis`);
|
|
342
|
+
console.error(` POST /tool/cypher — raw Cypher query`);
|
|
343
|
+
console.error(` GET /health — health check`);
|
|
344
|
+
console.error(` POST /shutdown — graceful shutdown`);
|
|
345
|
+
if (idleTimeoutSec > 0) {
|
|
346
|
+
console.error(` Auto-shutdown after ${idleTimeoutSec}s idle`);
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
process.stdout.write(`GITNEXUS_EVAL_SERVER_READY:${port}\n`);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// stdout may not be available
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
resetIdleTimer();
|
|
356
|
+
const shutdown = async () => {
|
|
357
|
+
console.error('GitNexus eval-server: shutting down...');
|
|
358
|
+
await backend.disconnect();
|
|
359
|
+
server.close();
|
|
360
|
+
process.exit(0);
|
|
361
|
+
};
|
|
362
|
+
process.on('SIGINT', shutdown);
|
|
363
|
+
process.on('SIGTERM', shutdown);
|
|
364
|
+
}
|
|
365
|
+
function readBody(req) {
|
|
366
|
+
return new Promise((resolve, reject) => {
|
|
367
|
+
const chunks = [];
|
|
368
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
369
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
370
|
+
req.on('error', reject);
|
|
371
|
+
});
|
|
372
|
+
}
|