gitnexus 1.4.0 → 1.4.1
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 +194 -214
- package/dist/cli/ai-context.d.ts +1 -2
- package/dist/cli/ai-context.js +90 -117
- package/dist/cli/analyze.d.ts +0 -2
- package/dist/cli/analyze.js +2 -20
- package/dist/cli/index.js +25 -17
- package/dist/cli/setup.js +19 -17
- package/dist/core/augmentation/engine.js +20 -20
- package/dist/core/embeddings/embedding-pipeline.js +26 -26
- package/dist/core/graph/types.d.ts +2 -5
- package/dist/core/ingestion/ast-cache.js +2 -3
- package/dist/core/ingestion/call-processor.d.ts +5 -5
- package/dist/core/ingestion/call-processor.js +258 -173
- package/dist/core/ingestion/cluster-enricher.js +16 -16
- package/dist/core/ingestion/entry-point-scoring.d.ts +1 -2
- package/dist/core/ingestion/entry-point-scoring.js +22 -81
- package/dist/core/ingestion/framework-detection.d.ts +1 -5
- package/dist/core/ingestion/framework-detection.js +8 -39
- package/dist/core/ingestion/heritage-processor.d.ts +4 -13
- package/dist/core/ingestion/heritage-processor.js +28 -92
- package/dist/core/ingestion/import-processor.d.ts +19 -17
- package/dist/core/ingestion/import-processor.js +695 -170
- package/dist/core/ingestion/parsing-processor.d.ts +10 -1
- package/dist/core/ingestion/parsing-processor.js +177 -41
- package/dist/core/ingestion/pipeline.js +26 -49
- package/dist/core/ingestion/process-processor.js +1 -2
- package/dist/core/ingestion/symbol-table.d.ts +1 -12
- package/dist/core/ingestion/symbol-table.js +12 -19
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
- package/dist/core/ingestion/tree-sitter-queries.js +485 -590
- package/dist/core/ingestion/utils.d.ts +0 -67
- package/dist/core/ingestion/utils.js +9 -692
- package/dist/core/ingestion/workers/parse-worker.d.ts +3 -20
- package/dist/core/ingestion/workers/parse-worker.js +345 -84
- package/dist/core/ingestion/workers/worker-pool.js +0 -8
- package/dist/core/kuzu/csv-generator.js +3 -19
- package/dist/core/kuzu/kuzu-adapter.js +19 -14
- package/dist/core/kuzu/schema.d.ts +3 -3
- package/dist/core/kuzu/schema.js +288 -303
- package/dist/core/search/bm25-index.js +6 -7
- package/dist/core/search/hybrid-search.js +3 -3
- package/dist/core/wiki/diagrams.d.ts +27 -0
- package/dist/core/wiki/diagrams.js +163 -0
- package/dist/core/wiki/generator.d.ts +50 -2
- package/dist/core/wiki/generator.js +548 -49
- package/dist/core/wiki/graph-queries.d.ts +42 -0
- package/dist/core/wiki/graph-queries.js +276 -97
- package/dist/core/wiki/html-viewer.js +192 -192
- package/dist/core/wiki/llm-client.js +73 -11
- package/dist/core/wiki/prompts.d.ts +52 -8
- package/dist/core/wiki/prompts.js +200 -86
- package/dist/mcp/core/kuzu-adapter.d.ts +3 -1
- package/dist/mcp/core/kuzu-adapter.js +44 -13
- package/dist/mcp/local/local-backend.js +128 -128
- package/dist/mcp/resources.js +42 -42
- package/dist/mcp/server.js +19 -18
- package/dist/mcp/tools.js +104 -103
- package/hooks/claude/gitnexus-hook.cjs +155 -238
- package/hooks/claude/pre-tool-use.sh +79 -79
- package/hooks/claude/session-start.sh +42 -42
- package/package.json +96 -96
- package/scripts/patch-tree-sitter-swift.cjs +74 -74
- package/skills/gitnexus-cli.md +82 -82
- package/skills/gitnexus-debugging.md +89 -89
- package/skills/gitnexus-exploring.md +78 -78
- package/skills/gitnexus-guide.md +64 -64
- package/skills/gitnexus-impact-analysis.md +97 -97
- package/skills/gitnexus-pr-review.md +163 -163
- package/skills/gitnexus-refactoring.md +121 -121
- package/vendor/leiden/index.cjs +355 -355
- package/vendor/leiden/utils.cjs +392 -392
- package/dist/cli/lazy-action.d.ts +0 -6
- package/dist/cli/lazy-action.js +0 -18
- package/dist/cli/skill-gen.d.ts +0 -26
- package/dist/cli/skill-gen.js +0 -549
- package/dist/core/ingestion/constants.d.ts +0 -16
- package/dist/core/ingestion/constants.js +0 -16
- package/dist/core/ingestion/export-detection.d.ts +0 -18
- package/dist/core/ingestion/export-detection.js +0 -230
- package/dist/core/ingestion/language-config.d.ts +0 -46
- package/dist/core/ingestion/language-config.js +0 -167
- package/dist/core/ingestion/mro-processor.d.ts +0 -45
- package/dist/core/ingestion/mro-processor.js +0 -369
- package/dist/core/ingestion/named-binding-extraction.d.ts +0 -61
- package/dist/core/ingestion/named-binding-extraction.js +0 -363
- package/dist/core/ingestion/resolvers/csharp.d.ts +0 -22
- package/dist/core/ingestion/resolvers/csharp.js +0 -109
- package/dist/core/ingestion/resolvers/go.d.ts +0 -19
- package/dist/core/ingestion/resolvers/go.js +0 -42
- package/dist/core/ingestion/resolvers/index.d.ts +0 -16
- package/dist/core/ingestion/resolvers/index.js +0 -11
- package/dist/core/ingestion/resolvers/jvm.d.ts +0 -23
- package/dist/core/ingestion/resolvers/jvm.js +0 -87
- package/dist/core/ingestion/resolvers/php.d.ts +0 -15
- package/dist/core/ingestion/resolvers/php.js +0 -35
- package/dist/core/ingestion/resolvers/rust.d.ts +0 -15
- package/dist/core/ingestion/resolvers/rust.js +0 -73
- package/dist/core/ingestion/resolvers/standard.d.ts +0 -28
- package/dist/core/ingestion/resolvers/standard.js +0 -145
- package/dist/core/ingestion/resolvers/utils.d.ts +0 -33
- package/dist/core/ingestion/resolvers/utils.js +0 -120
- package/dist/core/ingestion/symbol-resolver.d.ts +0 -32
- package/dist/core/ingestion/symbol-resolver.js +0 -83
- package/dist/core/ingestion/type-env.d.ts +0 -27
- package/dist/core/ingestion/type-env.js +0 -86
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/c-cpp.js +0 -60
- package/dist/core/ingestion/type-extractors/csharp.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/csharp.js +0 -89
- package/dist/core/ingestion/type-extractors/go.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/go.js +0 -105
- package/dist/core/ingestion/type-extractors/index.d.ts +0 -21
- package/dist/core/ingestion/type-extractors/index.js +0 -29
- package/dist/core/ingestion/type-extractors/jvm.d.ts +0 -3
- package/dist/core/ingestion/type-extractors/jvm.js +0 -121
- package/dist/core/ingestion/type-extractors/php.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/php.js +0 -31
- package/dist/core/ingestion/type-extractors/python.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/python.js +0 -41
- package/dist/core/ingestion/type-extractors/rust.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/rust.js +0 -39
- package/dist/core/ingestion/type-extractors/shared.d.ts +0 -17
- package/dist/core/ingestion/type-extractors/shared.js +0 -97
- package/dist/core/ingestion/type-extractors/swift.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/swift.js +0 -43
- package/dist/core/ingestion/type-extractors/types.d.ts +0 -14
- package/dist/core/ingestion/type-extractors/types.js +0 -1
- package/dist/core/ingestion/type-extractors/typescript.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/typescript.js +0 -46
- package/dist/mcp/compatible-stdio-transport.d.ts +0 -25
- package/dist/mcp/compatible-stdio-transport.js +0 -200
package/dist/cli/ai-context.js
CHANGED
|
@@ -24,111 +24,84 @@ const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';
|
|
|
24
24
|
* - Exact tool commands with parameters — vague directives get ignored
|
|
25
25
|
* - Self-review checklist — forces model to verify its own work
|
|
26
26
|
*/
|
|
27
|
-
function generateGitNexusContent(projectName, stats
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
##
|
|
75
|
-
|
|
76
|
-
|
|
|
77
|
-
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
|
88
|
-
|
|
89
|
-
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
1. \`gitnexus_impact\` was run for all modified symbols
|
|
106
|
-
2. No HIGH/CRITICAL risk warnings were ignored
|
|
107
|
-
3. \`gitnexus_detect_changes()\` confirms changes match expected scope
|
|
108
|
-
4. All d=1 (WILL BREAK) dependents were updated
|
|
109
|
-
|
|
110
|
-
## Keeping the Index Fresh
|
|
111
|
-
|
|
112
|
-
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
|
|
113
|
-
|
|
114
|
-
\`\`\`bash
|
|
115
|
-
npx gitnexus analyze
|
|
116
|
-
\`\`\`
|
|
117
|
-
|
|
118
|
-
If the index previously included embeddings, preserve them by adding \`--embeddings\`:
|
|
119
|
-
|
|
120
|
-
\`\`\`bash
|
|
121
|
-
npx gitnexus analyze --embeddings
|
|
122
|
-
\`\`\`
|
|
123
|
-
|
|
124
|
-
To check whether embeddings exist, inspect \`.gitnexus/meta.json\` — the \`stats.embeddings\` field shows the count (0 means no embeddings). **Running analyze without \`--embeddings\` will delete any previously generated embeddings.**
|
|
125
|
-
|
|
126
|
-
> Claude Code users: A PostToolUse hook handles this automatically after \`git commit\` and \`git merge\`.
|
|
127
|
-
|
|
128
|
-
## CLI
|
|
129
|
-
|
|
130
|
-
${skillsTable}
|
|
131
|
-
|
|
27
|
+
function generateGitNexusContent(projectName, stats) {
|
|
28
|
+
return `${GITNEXUS_START_MARKER}
|
|
29
|
+
# GitNexus — Code Intelligence
|
|
30
|
+
|
|
31
|
+
This project is indexed by GitNexus as **${projectName}** (${stats.nodes || 0} symbols, ${stats.edges || 0} relationships, ${stats.processes || 0} execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
|
32
|
+
|
|
33
|
+
> If any GitNexus tool warns the index is stale, run \`npx gitnexus analyze\` in terminal first.
|
|
34
|
+
|
|
35
|
+
## Always Do
|
|
36
|
+
|
|
37
|
+
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run \`gitnexus_impact({target: "symbolName", direction: "upstream"})\` and report the blast radius (direct callers, affected processes, risk level) to the user.
|
|
38
|
+
- **MUST run \`gitnexus_detect_changes()\` before committing** to verify your changes only affect expected symbols and execution flows.
|
|
39
|
+
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
|
|
40
|
+
- When exploring unfamiliar code, use \`gitnexus_query({query: "concept"})\` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
|
|
41
|
+
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use \`gitnexus_context({name: "symbolName"})\`.
|
|
42
|
+
|
|
43
|
+
## When Debugging
|
|
44
|
+
|
|
45
|
+
1. \`gitnexus_query({query: "<error or symptom>"})\` — find execution flows related to the issue
|
|
46
|
+
2. \`gitnexus_context({name: "<suspect function>"})\` — see all callers, callees, and process participation
|
|
47
|
+
3. \`READ gitnexus://repo/${projectName}/process/{processName}\` — trace the full execution flow step by step
|
|
48
|
+
4. For regressions: \`gitnexus_detect_changes({scope: "compare", base_ref: "main"})\` — see what your branch changed
|
|
49
|
+
|
|
50
|
+
## When Refactoring
|
|
51
|
+
|
|
52
|
+
- **Renaming**: MUST use \`gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})\` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with \`dry_run: false\`.
|
|
53
|
+
- **Extracting/Splitting**: MUST run \`gitnexus_context({name: "target"})\` to see all incoming/outgoing refs, then \`gitnexus_impact({target: "target", direction: "upstream"})\` to find all external callers before moving code.
|
|
54
|
+
- After any refactor: run \`gitnexus_detect_changes({scope: "all"})\` to verify only expected files changed.
|
|
55
|
+
|
|
56
|
+
## Never Do
|
|
57
|
+
|
|
58
|
+
- NEVER edit a function, class, or method without first running \`gitnexus_impact\` on it.
|
|
59
|
+
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
|
|
60
|
+
- NEVER rename symbols with find-and-replace — use \`gitnexus_rename\` which understands the call graph.
|
|
61
|
+
- NEVER commit changes without running \`gitnexus_detect_changes()\` to check affected scope.
|
|
62
|
+
|
|
63
|
+
## Tools Quick Reference
|
|
64
|
+
|
|
65
|
+
| Tool | When to use | Command |
|
|
66
|
+
|------|-------------|---------|
|
|
67
|
+
| \`query\` | Find code by concept | \`gitnexus_query({query: "auth validation"})\` |
|
|
68
|
+
| \`context\` | 360-degree view of one symbol | \`gitnexus_context({name: "validateUser"})\` |
|
|
69
|
+
| \`impact\` | Blast radius before editing | \`gitnexus_impact({target: "X", direction: "upstream"})\` |
|
|
70
|
+
| \`detect_changes\` | Pre-commit scope check | \`gitnexus_detect_changes({scope: "staged"})\` |
|
|
71
|
+
| \`rename\` | Safe multi-file rename | \`gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})\` |
|
|
72
|
+
| \`cypher\` | Custom graph queries | \`gitnexus_cypher({query: "MATCH ..."})\` |
|
|
73
|
+
|
|
74
|
+
## Impact Risk Levels
|
|
75
|
+
|
|
76
|
+
| Depth | Meaning | Action |
|
|
77
|
+
|-------|---------|--------|
|
|
78
|
+
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
|
|
79
|
+
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
|
|
80
|
+
| d=3 | MAY NEED TESTING — transitive | Test if critical path |
|
|
81
|
+
|
|
82
|
+
## Resources
|
|
83
|
+
|
|
84
|
+
| Resource | Use for |
|
|
85
|
+
|----------|---------|
|
|
86
|
+
| \`gitnexus://repo/${projectName}/context\` | Codebase overview, check index freshness |
|
|
87
|
+
| \`gitnexus://repo/${projectName}/clusters\` | All functional areas |
|
|
88
|
+
| \`gitnexus://repo/${projectName}/processes\` | All execution flows |
|
|
89
|
+
| \`gitnexus://repo/${projectName}/process/{name}\` | Step-by-step execution trace |
|
|
90
|
+
|
|
91
|
+
## Self-Check Before Finishing
|
|
92
|
+
|
|
93
|
+
Before completing any code modification task, verify:
|
|
94
|
+
1. \`gitnexus_impact\` was run for all modified symbols
|
|
95
|
+
2. No HIGH/CRITICAL risk warnings were ignored
|
|
96
|
+
3. \`gitnexus_detect_changes()\` confirms changes match expected scope
|
|
97
|
+
4. All d=1 (WILL BREAK) dependents were updated
|
|
98
|
+
|
|
99
|
+
## CLI
|
|
100
|
+
|
|
101
|
+
- Re-index: \`npx gitnexus analyze\`
|
|
102
|
+
- Check freshness: \`npx gitnexus status\`
|
|
103
|
+
- Generate docs: \`npx gitnexus wiki\`
|
|
104
|
+
|
|
132
105
|
${GITNEXUS_END_MARKER}`;
|
|
133
106
|
}
|
|
134
107
|
/**
|
|
@@ -220,16 +193,16 @@ async function installSkills(repoPath) {
|
|
|
220
193
|
}
|
|
221
194
|
catch {
|
|
222
195
|
// Fallback: generate minimal skill content
|
|
223
|
-
skillContent = `---
|
|
224
|
-
name: ${skill.name}
|
|
225
|
-
description: ${skill.description}
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
# ${skill.name.charAt(0).toUpperCase() + skill.name.slice(1)}
|
|
229
|
-
|
|
230
|
-
${skill.description}
|
|
231
|
-
|
|
232
|
-
Use GitNexus tools to accomplish this task.
|
|
196
|
+
skillContent = `---
|
|
197
|
+
name: ${skill.name}
|
|
198
|
+
description: ${skill.description}
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
# ${skill.name.charAt(0).toUpperCase() + skill.name.slice(1)}
|
|
202
|
+
|
|
203
|
+
${skill.description}
|
|
204
|
+
|
|
205
|
+
Use GitNexus tools to accomplish this task.
|
|
233
206
|
`;
|
|
234
207
|
}
|
|
235
208
|
await fs.writeFile(skillPath, skillContent, 'utf-8');
|
|
@@ -245,8 +218,8 @@ Use GitNexus tools to accomplish this task.
|
|
|
245
218
|
/**
|
|
246
219
|
* Generate AI context files after indexing
|
|
247
220
|
*/
|
|
248
|
-
export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats
|
|
249
|
-
const content = generateGitNexusContent(projectName, stats
|
|
221
|
+
export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats) {
|
|
222
|
+
const content = generateGitNexusContent(projectName, stats);
|
|
250
223
|
const createdFiles = [];
|
|
251
224
|
// Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Cline, etc.)
|
|
252
225
|
const agentsPath = path.join(repoPath, 'AGENTS.md');
|
package/dist/cli/analyze.d.ts
CHANGED
package/dist/cli/analyze.js
CHANGED
|
@@ -16,7 +16,6 @@ import { initKuzu, loadGraphToKuzu, getKuzuStats, executeQuery, executeWithReuse
|
|
|
16
16
|
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath } from '../storage/repo-manager.js';
|
|
17
17
|
import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
|
|
18
18
|
import { generateAIContextFiles } from './ai-context.js';
|
|
19
|
-
import { generateSkillFiles } from './skill-gen.js';
|
|
20
19
|
import fs from 'fs/promises';
|
|
21
20
|
const HEAP_MB = 8192;
|
|
22
21
|
const HEAP_FLAG = `--max-old-space-size=${HEAP_MB}`;
|
|
@@ -59,9 +58,6 @@ const PHASE_LABELS = {
|
|
|
59
58
|
export const analyzeCommand = async (inputPath, options) => {
|
|
60
59
|
if (ensureHeap())
|
|
61
60
|
return;
|
|
62
|
-
if (options?.verbose) {
|
|
63
|
-
process.env.GITNEXUS_VERBOSE = '1';
|
|
64
|
-
}
|
|
65
61
|
console.log('\n GitNexus Analyzer\n');
|
|
66
62
|
let repoPath;
|
|
67
63
|
if (inputPath) {
|
|
@@ -84,7 +80,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
84
80
|
const { storagePath, kuzuPath } = getStoragePaths(repoPath);
|
|
85
81
|
const currentCommit = getCurrentCommit(repoPath);
|
|
86
82
|
const existingMeta = await loadMeta(storagePath);
|
|
87
|
-
if (existingMeta && !options?.force &&
|
|
83
|
+
if (existingMeta && !options?.force && existingMeta.lastCommit === currentCommit) {
|
|
88
84
|
console.log(' Already up to date\n');
|
|
89
85
|
return;
|
|
90
86
|
}
|
|
@@ -246,13 +242,6 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
246
242
|
}
|
|
247
243
|
// ── Phase 5: Finalize (98–100%) ───────────────────────────────────
|
|
248
244
|
updateBar(98, 'Saving metadata...');
|
|
249
|
-
// Count embeddings in the index (cached + newly generated)
|
|
250
|
-
let embeddingCount = 0;
|
|
251
|
-
try {
|
|
252
|
-
const embResult = await executeQuery(`MATCH (e:CodeEmbedding) RETURN count(e) AS cnt`);
|
|
253
|
-
embeddingCount = embResult?.[0]?.cnt ?? 0;
|
|
254
|
-
}
|
|
255
|
-
catch { /* table may not exist if embeddings never ran */ }
|
|
256
245
|
const meta = {
|
|
257
246
|
repoPath,
|
|
258
247
|
lastCommit: currentCommit,
|
|
@@ -263,7 +252,6 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
263
252
|
edges: stats.edges,
|
|
264
253
|
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
265
254
|
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
266
|
-
embeddings: embeddingCount,
|
|
267
255
|
},
|
|
268
256
|
};
|
|
269
257
|
await saveMeta(storagePath, meta);
|
|
@@ -279,12 +267,6 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
279
267
|
}
|
|
280
268
|
aggregatedClusterCount = Array.from(groups.values()).filter(count => count >= 5).length;
|
|
281
269
|
}
|
|
282
|
-
let generatedSkills = [];
|
|
283
|
-
if (options?.skills && pipelineResult.communityResult) {
|
|
284
|
-
updateBar(99, 'Generating skill files...');
|
|
285
|
-
const skillResult = await generateSkillFiles(repoPath, projectName, pipelineResult);
|
|
286
|
-
generatedSkills = skillResult.skills;
|
|
287
|
-
}
|
|
288
270
|
const aiContext = await generateAIContextFiles(repoPath, storagePath, projectName, {
|
|
289
271
|
files: pipelineResult.totalFileCount,
|
|
290
272
|
nodes: stats.nodes,
|
|
@@ -292,7 +274,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
292
274
|
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
293
275
|
clusters: aggregatedClusterCount,
|
|
294
276
|
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
295
|
-
}
|
|
277
|
+
});
|
|
296
278
|
await closeKuzu();
|
|
297
279
|
// Note: we intentionally do NOT call disposeEmbedder() here.
|
|
298
280
|
// ONNX Runtime's native cleanup segfaults on macOS and some Linux configs.
|
package/dist/cli/index.js
CHANGED
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
// Heap re-spawn removed — only analyze.ts needs the 8GB heap (via its own ensureHeap()).
|
|
3
3
|
// Removing it from here improves MCP server startup time significantly.
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
+
import { analyzeCommand } from './analyze.js';
|
|
6
|
+
import { serveCommand } from './serve.js';
|
|
7
|
+
import { listCommand } from './list.js';
|
|
8
|
+
import { statusCommand } from './status.js';
|
|
9
|
+
import { mcpCommand } from './mcp.js';
|
|
10
|
+
import { cleanCommand } from './clean.js';
|
|
11
|
+
import { setupCommand } from './setup.js';
|
|
12
|
+
import { augmentCommand } from './augment.js';
|
|
13
|
+
import { wikiCommand } from './wiki.js';
|
|
14
|
+
import { queryCommand, contextCommand, impactCommand, cypherCommand } from './tool.js';
|
|
15
|
+
import { evalServerCommand } from './eval-server.js';
|
|
5
16
|
import { createRequire } from 'node:module';
|
|
6
|
-
import { createLazyAction } from './lazy-action.js';
|
|
7
17
|
const _require = createRequire(import.meta.url);
|
|
8
18
|
const pkg = _require('../../package.json');
|
|
9
19
|
const program = new Command();
|
|
@@ -14,39 +24,37 @@ program
|
|
|
14
24
|
program
|
|
15
25
|
.command('setup')
|
|
16
26
|
.description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode')
|
|
17
|
-
.action(
|
|
27
|
+
.action(setupCommand);
|
|
18
28
|
program
|
|
19
29
|
.command('analyze [path]')
|
|
20
30
|
.description('Index a repository (full analysis)')
|
|
21
31
|
.option('-f, --force', 'Force full re-index even if up to date')
|
|
22
32
|
.option('--embeddings', 'Enable embedding generation for semantic search (off by default)')
|
|
23
|
-
.
|
|
24
|
-
.option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)')
|
|
25
|
-
.action(createLazyAction(() => import('./analyze.js'), 'analyzeCommand'));
|
|
33
|
+
.action(analyzeCommand);
|
|
26
34
|
program
|
|
27
35
|
.command('serve')
|
|
28
36
|
.description('Start local HTTP server for web UI connection')
|
|
29
37
|
.option('-p, --port <port>', 'Port number', '4747')
|
|
30
38
|
.option('--host <host>', 'Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)')
|
|
31
|
-
.action(
|
|
39
|
+
.action(serveCommand);
|
|
32
40
|
program
|
|
33
41
|
.command('mcp')
|
|
34
42
|
.description('Start MCP server (stdio) — serves all indexed repos')
|
|
35
|
-
.action(
|
|
43
|
+
.action(mcpCommand);
|
|
36
44
|
program
|
|
37
45
|
.command('list')
|
|
38
46
|
.description('List all indexed repositories')
|
|
39
|
-
.action(
|
|
47
|
+
.action(listCommand);
|
|
40
48
|
program
|
|
41
49
|
.command('status')
|
|
42
50
|
.description('Show index status for current repo')
|
|
43
|
-
.action(
|
|
51
|
+
.action(statusCommand);
|
|
44
52
|
program
|
|
45
53
|
.command('clean')
|
|
46
54
|
.description('Delete GitNexus index for current repo')
|
|
47
55
|
.option('-f, --force', 'Skip confirmation prompt')
|
|
48
56
|
.option('--all', 'Clean all indexed repos')
|
|
49
|
-
.action(
|
|
57
|
+
.action(cleanCommand);
|
|
50
58
|
program
|
|
51
59
|
.command('wiki [path]')
|
|
52
60
|
.description('Generate repository wiki from knowledge graph')
|
|
@@ -56,11 +64,11 @@ program
|
|
|
56
64
|
.option('--api-key <key>', 'LLM API key (saved to ~/.gitnexus/config.json)')
|
|
57
65
|
.option('--concurrency <n>', 'Parallel LLM calls (default: 3)', '3')
|
|
58
66
|
.option('--gist', 'Publish wiki as a public GitHub Gist after generation')
|
|
59
|
-
.action(
|
|
67
|
+
.action(wikiCommand);
|
|
60
68
|
program
|
|
61
69
|
.command('augment <pattern>')
|
|
62
70
|
.description('Augment a search pattern with knowledge graph context (used by hooks)')
|
|
63
|
-
.action(
|
|
71
|
+
.action(augmentCommand);
|
|
64
72
|
// ─── Direct Tool Commands (no MCP overhead) ────────────────────────
|
|
65
73
|
// These invoke LocalBackend directly for use in eval, scripts, and CI.
|
|
66
74
|
program
|
|
@@ -71,7 +79,7 @@ program
|
|
|
71
79
|
.option('-g, --goal <text>', 'What you want to find')
|
|
72
80
|
.option('-l, --limit <n>', 'Max processes to return (default: 5)')
|
|
73
81
|
.option('--content', 'Include full symbol source code')
|
|
74
|
-
.action(
|
|
82
|
+
.action(queryCommand);
|
|
75
83
|
program
|
|
76
84
|
.command('context [name]')
|
|
77
85
|
.description('360-degree view of a code symbol: callers, callees, processes')
|
|
@@ -79,7 +87,7 @@ program
|
|
|
79
87
|
.option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
|
|
80
88
|
.option('-f, --file <path>', 'File path to disambiguate common names')
|
|
81
89
|
.option('--content', 'Include full symbol source code')
|
|
82
|
-
.action(
|
|
90
|
+
.action(contextCommand);
|
|
83
91
|
program
|
|
84
92
|
.command('impact <target>')
|
|
85
93
|
.description('Blast radius analysis: what breaks if you change a symbol')
|
|
@@ -87,17 +95,17 @@ program
|
|
|
87
95
|
.option('-r, --repo <name>', 'Target repository')
|
|
88
96
|
.option('--depth <n>', 'Max relationship depth (default: 3)')
|
|
89
97
|
.option('--include-tests', 'Include test files in results')
|
|
90
|
-
.action(
|
|
98
|
+
.action(impactCommand);
|
|
91
99
|
program
|
|
92
100
|
.command('cypher <query>')
|
|
93
101
|
.description('Execute raw Cypher query against the knowledge graph')
|
|
94
102
|
.option('-r, --repo <name>', 'Target repository')
|
|
95
|
-
.action(
|
|
103
|
+
.action(cypherCommand);
|
|
96
104
|
// ─── Eval Server (persistent daemon for SWE-bench) ─────────────────
|
|
97
105
|
program
|
|
98
106
|
.command('eval-server')
|
|
99
107
|
.description('Start lightweight HTTP server for fast tool calls during evaluation')
|
|
100
108
|
.option('-p, --port <port>', 'Port number', '4848')
|
|
101
109
|
.option('--idle-timeout <seconds>', 'Auto-shutdown after N seconds idle (0 = disabled)', '0')
|
|
102
|
-
.action(
|
|
110
|
+
.action(evalServerCommand);
|
|
103
111
|
program.parse(process.argv);
|
package/dist/cli/setup.js
CHANGED
|
@@ -147,34 +147,36 @@ async function installClaudeCodeHooks(result) {
|
|
|
147
147
|
// even when it's no longer inside the npm package tree
|
|
148
148
|
const resolvedCli = path.join(__dirname, '..', 'cli', 'index.js');
|
|
149
149
|
const normalizedCli = path.resolve(resolvedCli).replace(/\\/g, '/');
|
|
150
|
-
|
|
151
|
-
content = content.replace("let cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');", `let cliPath = ${jsonCli};`);
|
|
150
|
+
content = content.replace("let cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');", `let cliPath = '${normalizedCli}';`);
|
|
152
151
|
await fs.writeFile(dest, content, 'utf-8');
|
|
153
152
|
}
|
|
154
153
|
catch {
|
|
155
154
|
// Script not found in source — skip
|
|
156
155
|
}
|
|
157
|
-
const
|
|
158
|
-
const hookCmd = `node "${hookPath.replace(/"/g, '\\"')}"`;
|
|
156
|
+
const hookCmd = `node "${path.join(destHooksDir, 'gitnexus-hook.cjs').replace(/\\/g, '/')}"`;
|
|
159
157
|
// Merge hook config into ~/.claude/settings.json
|
|
160
158
|
const existing = await readJsonFile(settingsPath) || {};
|
|
161
159
|
if (!existing.hooks)
|
|
162
160
|
existing.hooks = {};
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
161
|
+
// NOTE: SessionStart hooks are broken on Windows (Claude Code bug #23576).
|
|
162
|
+
// Session context is delivered via CLAUDE.md / skills instead.
|
|
163
|
+
// Add PreToolUse hook if not already present
|
|
164
|
+
if (!existing.hooks.PreToolUse)
|
|
165
|
+
existing.hooks.PreToolUse = [];
|
|
166
|
+
const hasPreToolHook = existing.hooks.PreToolUse.some((h) => h.hooks?.some((hh) => hh.command?.includes('gitnexus')));
|
|
167
|
+
if (!hasPreToolHook) {
|
|
168
|
+
existing.hooks.PreToolUse.push({
|
|
169
|
+
matcher: 'Grep|Glob|Bash',
|
|
170
|
+
hooks: [{
|
|
171
|
+
type: 'command',
|
|
172
|
+
command: hookCmd,
|
|
173
|
+
timeout: 8000,
|
|
174
|
+
statusMessage: 'Enriching with GitNexus graph context...',
|
|
175
|
+
}],
|
|
176
|
+
});
|
|
173
177
|
}
|
|
174
|
-
ensureHookEntry('PreToolUse', 'Grep|Glob|Bash', 10, 'Enriching with GitNexus graph context...');
|
|
175
|
-
ensureHookEntry('PostToolUse', 'Bash', 10, 'Checking GitNexus index freshness...');
|
|
176
178
|
await writeJsonFile(settingsPath, existing);
|
|
177
|
-
result.configured.push('Claude Code hooks (PreToolUse
|
|
179
|
+
result.configured.push('Claude Code hooks (PreToolUse)');
|
|
178
180
|
}
|
|
179
181
|
catch (err) {
|
|
180
182
|
result.errors.push(`Claude Code hooks: ${err.message}`);
|
|
@@ -98,11 +98,11 @@ export async function augment(pattern, cwd) {
|
|
|
98
98
|
for (const result of bm25Results.slice(0, 5)) {
|
|
99
99
|
const escaped = result.filePath.replace(/'/g, "''");
|
|
100
100
|
try {
|
|
101
|
-
const symbols = await executeQuery(repoId, `
|
|
102
|
-
MATCH (n) WHERE n.filePath = '${escaped}'
|
|
103
|
-
AND n.name CONTAINS '${pattern.replace(/'/g, "''").split(/\s+/)[0]}'
|
|
104
|
-
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
105
|
-
LIMIT 3
|
|
101
|
+
const symbols = await executeQuery(repoId, `
|
|
102
|
+
MATCH (n) WHERE n.filePath = '${escaped}'
|
|
103
|
+
AND n.name CONTAINS '${pattern.replace(/'/g, "''").split(/\s+/)[0]}'
|
|
104
|
+
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
105
|
+
LIMIT 3
|
|
106
106
|
`);
|
|
107
107
|
for (const sym of symbols) {
|
|
108
108
|
symbolMatches.push({
|
|
@@ -130,10 +130,10 @@ export async function augment(pattern, cwd) {
|
|
|
130
130
|
// Callers
|
|
131
131
|
let callers = [];
|
|
132
132
|
try {
|
|
133
|
-
const rows = await executeQuery(repoId, `
|
|
134
|
-
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(n {id: '${escaped}'})
|
|
135
|
-
RETURN caller.name AS name
|
|
136
|
-
LIMIT 3
|
|
133
|
+
const rows = await executeQuery(repoId, `
|
|
134
|
+
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(n {id: '${escaped}'})
|
|
135
|
+
RETURN caller.name AS name
|
|
136
|
+
LIMIT 3
|
|
137
137
|
`);
|
|
138
138
|
callers = rows.map((r) => r.name || r[0]).filter(Boolean);
|
|
139
139
|
}
|
|
@@ -141,10 +141,10 @@ export async function augment(pattern, cwd) {
|
|
|
141
141
|
// Callees
|
|
142
142
|
let callees = [];
|
|
143
143
|
try {
|
|
144
|
-
const rows = await executeQuery(repoId, `
|
|
145
|
-
MATCH (n {id: '${escaped}'})-[:CodeRelation {type: 'CALLS'}]->(callee)
|
|
146
|
-
RETURN callee.name AS name
|
|
147
|
-
LIMIT 3
|
|
144
|
+
const rows = await executeQuery(repoId, `
|
|
145
|
+
MATCH (n {id: '${escaped}'})-[:CodeRelation {type: 'CALLS'}]->(callee)
|
|
146
|
+
RETURN callee.name AS name
|
|
147
|
+
LIMIT 3
|
|
148
148
|
`);
|
|
149
149
|
callees = rows.map((r) => r.name || r[0]).filter(Boolean);
|
|
150
150
|
}
|
|
@@ -152,9 +152,9 @@ export async function augment(pattern, cwd) {
|
|
|
152
152
|
// Processes
|
|
153
153
|
let processes = [];
|
|
154
154
|
try {
|
|
155
|
-
const rows = await executeQuery(repoId, `
|
|
156
|
-
MATCH (n {id: '${escaped}'})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
157
|
-
RETURN p.heuristicLabel AS label, r.step AS step, p.stepCount AS stepCount
|
|
155
|
+
const rows = await executeQuery(repoId, `
|
|
156
|
+
MATCH (n {id: '${escaped}'})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
157
|
+
RETURN p.heuristicLabel AS label, r.step AS step, p.stepCount AS stepCount
|
|
158
158
|
`);
|
|
159
159
|
processes = rows.map((r) => {
|
|
160
160
|
const label = r.label || r[0];
|
|
@@ -167,10 +167,10 @@ export async function augment(pattern, cwd) {
|
|
|
167
167
|
// Cluster cohesion (internal ranking signal)
|
|
168
168
|
let cohesion = 0;
|
|
169
169
|
try {
|
|
170
|
-
const rows = await executeQuery(repoId, `
|
|
171
|
-
MATCH (n {id: '${escaped}'})-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
172
|
-
RETURN c.cohesion AS cohesion
|
|
173
|
-
LIMIT 1
|
|
170
|
+
const rows = await executeQuery(repoId, `
|
|
171
|
+
MATCH (n {id: '${escaped}'})-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
172
|
+
RETURN c.cohesion AS cohesion
|
|
173
|
+
LIMIT 1
|
|
174
174
|
`);
|
|
175
175
|
if (rows.length > 0) {
|
|
176
176
|
cohesion = (rows[0].cohesion ?? rows[0][0]) || 0;
|