gitnexus 1.4.9 → 1.5.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/README.md +6 -5
- package/dist/cli/ai-context.d.ts +4 -1
- package/dist/cli/ai-context.js +19 -11
- package/dist/cli/analyze.d.ts +6 -0
- package/dist/cli/analyze.js +105 -251
- package/dist/cli/eval-server.js +20 -11
- package/dist/cli/index-repo.js +20 -22
- package/dist/cli/index.js +8 -7
- package/dist/cli/mcp.js +1 -1
- package/dist/cli/serve.js +29 -1
- package/dist/cli/setup.js +9 -9
- package/dist/cli/skill-gen.js +15 -9
- package/dist/cli/wiki.d.ts +2 -0
- package/dist/cli/wiki.js +141 -26
- package/dist/config/ignore-service.js +102 -22
- package/dist/config/supported-languages.d.ts +8 -42
- package/dist/config/supported-languages.js +8 -43
- package/dist/core/augmentation/engine.js +19 -7
- package/dist/core/embeddings/embedder.js +19 -15
- package/dist/core/embeddings/embedding-pipeline.js +6 -6
- package/dist/core/embeddings/http-client.js +3 -3
- package/dist/core/embeddings/text-generator.js +9 -24
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/embeddings/types.js +1 -7
- package/dist/core/graph/graph.js +6 -2
- package/dist/core/graph/types.d.ts +9 -59
- package/dist/core/ingestion/ast-cache.js +3 -3
- package/dist/core/ingestion/call-processor.d.ts +20 -2
- package/dist/core/ingestion/call-processor.js +347 -144
- package/dist/core/ingestion/call-routing.js +10 -4
- package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +10 -0
- package/dist/core/ingestion/call-sites/extract-language-call-site.js +22 -0
- package/dist/core/ingestion/call-sites/java.d.ts +9 -0
- package/dist/core/ingestion/call-sites/java.js +30 -0
- package/dist/core/ingestion/cluster-enricher.js +6 -8
- package/dist/core/ingestion/cobol/cobol-copy-expander.js +10 -3
- package/dist/core/ingestion/cobol/cobol-preprocessor.js +287 -81
- package/dist/core/ingestion/cobol/jcl-parser.js +1 -1
- package/dist/core/ingestion/cobol/jcl-processor.js +1 -1
- package/dist/core/ingestion/cobol-processor.js +102 -56
- package/dist/core/ingestion/community-processor.js +21 -15
- package/dist/core/ingestion/entry-point-scoring.d.ts +1 -1
- package/dist/core/ingestion/entry-point-scoring.js +5 -6
- package/dist/core/ingestion/export-detection.js +32 -9
- package/dist/core/ingestion/field-extractor.d.ts +1 -1
- package/dist/core/ingestion/field-extractors/configs/c-cpp.js +8 -12
- package/dist/core/ingestion/field-extractors/configs/csharp.js +45 -2
- package/dist/core/ingestion/field-extractors/configs/dart.js +5 -3
- package/dist/core/ingestion/field-extractors/configs/go.js +3 -7
- package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -0
- package/dist/core/ingestion/field-extractors/configs/helpers.js +14 -0
- package/dist/core/ingestion/field-extractors/configs/jvm.js +7 -7
- package/dist/core/ingestion/field-extractors/configs/php.js +9 -11
- package/dist/core/ingestion/field-extractors/configs/python.js +1 -1
- package/dist/core/ingestion/field-extractors/configs/ruby.js +4 -3
- package/dist/core/ingestion/field-extractors/configs/rust.js +2 -5
- package/dist/core/ingestion/field-extractors/configs/swift.js +9 -7
- package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +2 -6
- package/dist/core/ingestion/field-extractors/generic.d.ts +5 -2
- package/dist/core/ingestion/field-extractors/generic.js +6 -0
- package/dist/core/ingestion/field-extractors/typescript.d.ts +1 -1
- package/dist/core/ingestion/field-extractors/typescript.js +1 -1
- package/dist/core/ingestion/field-types.d.ts +4 -2
- package/dist/core/ingestion/filesystem-walker.js +3 -3
- package/dist/core/ingestion/framework-detection.d.ts +1 -1
- package/dist/core/ingestion/framework-detection.js +355 -85
- package/dist/core/ingestion/heritage-processor.d.ts +24 -0
- package/dist/core/ingestion/heritage-processor.js +99 -8
- package/dist/core/ingestion/import-processor.js +44 -15
- package/dist/core/ingestion/import-resolvers/csharp.js +7 -3
- package/dist/core/ingestion/import-resolvers/dart.js +1 -1
- package/dist/core/ingestion/import-resolvers/go.js +4 -2
- package/dist/core/ingestion/import-resolvers/jvm.js +4 -4
- package/dist/core/ingestion/import-resolvers/php.js +4 -4
- package/dist/core/ingestion/import-resolvers/python.js +1 -1
- package/dist/core/ingestion/import-resolvers/rust.js +9 -3
- package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
- package/dist/core/ingestion/import-resolvers/standard.js +6 -5
- package/dist/core/ingestion/import-resolvers/swift.js +2 -1
- package/dist/core/ingestion/import-resolvers/utils.js +26 -7
- package/dist/core/ingestion/language-config.js +5 -4
- package/dist/core/ingestion/language-provider.d.ts +7 -2
- package/dist/core/ingestion/languages/c-cpp.js +106 -21
- package/dist/core/ingestion/languages/cobol.js +1 -1
- package/dist/core/ingestion/languages/csharp.js +96 -19
- package/dist/core/ingestion/languages/dart.js +23 -7
- package/dist/core/ingestion/languages/go.js +1 -1
- package/dist/core/ingestion/languages/index.d.ts +1 -1
- package/dist/core/ingestion/languages/index.js +2 -3
- package/dist/core/ingestion/languages/java.js +4 -1
- package/dist/core/ingestion/languages/kotlin.js +60 -13
- package/dist/core/ingestion/languages/php.js +102 -25
- package/dist/core/ingestion/languages/python.js +28 -5
- package/dist/core/ingestion/languages/ruby.js +56 -14
- package/dist/core/ingestion/languages/rust.js +55 -11
- package/dist/core/ingestion/languages/swift.js +112 -27
- package/dist/core/ingestion/languages/typescript.js +95 -19
- package/dist/core/ingestion/markdown-processor.js +5 -5
- package/dist/core/ingestion/method-extractors/configs/csharp.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/csharp.js +283 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.d.ts +3 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.js +326 -0
- package/dist/core/ingestion/method-extractors/generic.d.ts +5 -0
- package/dist/core/ingestion/method-extractors/generic.js +137 -0
- package/dist/core/ingestion/method-types.d.ts +61 -0
- package/dist/core/ingestion/method-types.js +2 -0
- package/dist/core/ingestion/mro-processor.d.ts +1 -1
- package/dist/core/ingestion/mro-processor.js +12 -8
- package/dist/core/ingestion/named-binding-processor.js +2 -2
- package/dist/core/ingestion/named-bindings/rust.js +3 -1
- package/dist/core/ingestion/parsing-processor.js +74 -24
- package/dist/core/ingestion/pipeline.d.ts +2 -1
- package/dist/core/ingestion/pipeline.js +208 -102
- package/dist/core/ingestion/process-processor.js +12 -10
- package/dist/core/ingestion/resolution-context.js +3 -3
- package/dist/core/ingestion/route-extractors/middleware.js +31 -7
- package/dist/core/ingestion/route-extractors/php.js +2 -1
- package/dist/core/ingestion/route-extractors/response-shapes.js +8 -4
- package/dist/core/ingestion/structure-processor.d.ts +1 -1
- package/dist/core/ingestion/structure-processor.js +4 -4
- package/dist/core/ingestion/symbol-table.d.ts +1 -1
- package/dist/core/ingestion/symbol-table.js +22 -6
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +1 -1
- package/dist/core/ingestion/type-env.d.ts +2 -2
- package/dist/core/ingestion/type-env.js +75 -50
- package/dist/core/ingestion/type-extractors/c-cpp.js +33 -30
- package/dist/core/ingestion/type-extractors/csharp.js +24 -14
- package/dist/core/ingestion/type-extractors/dart.js +6 -8
- package/dist/core/ingestion/type-extractors/go.js +7 -6
- package/dist/core/ingestion/type-extractors/jvm.js +10 -21
- package/dist/core/ingestion/type-extractors/php.js +26 -13
- package/dist/core/ingestion/type-extractors/python.js +11 -15
- package/dist/core/ingestion/type-extractors/ruby.js +8 -3
- package/dist/core/ingestion/type-extractors/rust.js +6 -8
- package/dist/core/ingestion/type-extractors/shared.js +134 -50
- package/dist/core/ingestion/type-extractors/swift.js +16 -13
- package/dist/core/ingestion/type-extractors/typescript.js +23 -15
- package/dist/core/ingestion/utils/ast-helpers.d.ts +8 -8
- package/dist/core/ingestion/utils/ast-helpers.js +72 -35
- package/dist/core/ingestion/utils/call-analysis.d.ts +2 -0
- package/dist/core/ingestion/utils/call-analysis.js +96 -49
- package/dist/core/ingestion/utils/event-loop.js +1 -1
- package/dist/core/ingestion/workers/parse-worker.d.ts +7 -2
- package/dist/core/ingestion/workers/parse-worker.js +364 -84
- package/dist/core/ingestion/workers/worker-pool.js +5 -10
- package/dist/core/lbug/csv-generator.js +54 -15
- package/dist/core/lbug/lbug-adapter.d.ts +5 -0
- package/dist/core/lbug/lbug-adapter.js +86 -23
- package/dist/core/lbug/schema.d.ts +3 -6
- package/dist/core/lbug/schema.js +6 -30
- package/dist/core/run-analyze.d.ts +49 -0
- package/dist/core/run-analyze.js +257 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +1 -1
- package/dist/core/tree-sitter/parser-loader.js +1 -1
- package/dist/core/wiki/cursor-client.js +2 -7
- package/dist/core/wiki/generator.js +38 -23
- package/dist/core/wiki/graph-queries.js +10 -10
- package/dist/core/wiki/html-viewer.js +7 -3
- package/dist/core/wiki/llm-client.d.ts +23 -2
- package/dist/core/wiki/llm-client.js +96 -26
- package/dist/core/wiki/prompts.js +7 -6
- package/dist/mcp/core/embedder.js +1 -1
- package/dist/mcp/core/lbug-adapter.d.ts +4 -1
- package/dist/mcp/core/lbug-adapter.js +17 -7
- package/dist/mcp/local/local-backend.js +247 -95
- package/dist/mcp/resources.js +14 -6
- package/dist/mcp/server.js +13 -5
- package/dist/mcp/staleness.js +5 -1
- package/dist/mcp/tools.js +100 -23
- package/dist/server/analyze-job.d.ts +53 -0
- package/dist/server/analyze-job.js +146 -0
- package/dist/server/analyze-worker.d.ts +13 -0
- package/dist/server/analyze-worker.js +59 -0
- package/dist/server/api.js +795 -44
- package/dist/server/git-clone.d.ts +25 -0
- package/dist/server/git-clone.js +91 -0
- package/dist/storage/git.js +1 -3
- package/dist/storage/repo-manager.d.ts +5 -2
- package/dist/storage/repo-manager.js +4 -4
- package/dist/types/pipeline.d.ts +1 -21
- package/dist/types/pipeline.js +1 -18
- package/hooks/claude/gitnexus-hook.cjs +52 -22
- package/package.json +13 -13
- package/dist/core/ingestion/utils/language-detection.d.ts +0 -9
- package/dist/core/ingestion/utils/language-detection.js +0 -70
package/README.md
CHANGED
|
@@ -149,11 +149,12 @@ Your AI agent gets these tools automatically:
|
|
|
149
149
|
## CLI Commands
|
|
150
150
|
|
|
151
151
|
```bash
|
|
152
|
-
gitnexus setup
|
|
153
|
-
gitnexus analyze [path]
|
|
154
|
-
gitnexus analyze --force
|
|
155
|
-
gitnexus analyze --embeddings
|
|
156
|
-
gitnexus analyze --
|
|
152
|
+
gitnexus setup # Configure MCP for your editors (one-time)
|
|
153
|
+
gitnexus analyze [path] # Index a repository (or update stale index)
|
|
154
|
+
gitnexus analyze --force # Force full re-index
|
|
155
|
+
gitnexus analyze --embeddings # Enable embedding generation (slower, better search)
|
|
156
|
+
gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits
|
|
157
|
+
gitnexus analyze --verbose # Log skipped files when parsers are unavailable
|
|
157
158
|
gitnexus mcp # Start MCP server (stdio) — serves all indexed repos
|
|
158
159
|
gitnexus serve # Start local HTTP server (multi-repo) for web UI
|
|
159
160
|
gitnexus index # Register an existing .gitnexus/ folder into the global registry
|
package/dist/cli/ai-context.d.ts
CHANGED
|
@@ -14,10 +14,13 @@ interface RepoStats {
|
|
|
14
14
|
clusters?: number;
|
|
15
15
|
processes?: number;
|
|
16
16
|
}
|
|
17
|
+
export interface AIContextOptions {
|
|
18
|
+
skipAgentsMd?: boolean;
|
|
19
|
+
}
|
|
17
20
|
/**
|
|
18
21
|
* Generate AI context files after indexing
|
|
19
22
|
*/
|
|
20
|
-
export declare function generateAIContextFiles(repoPath: string, _storagePath: string, projectName: string, stats: RepoStats, generatedSkills?: GeneratedSkillInfo[]): Promise<{
|
|
23
|
+
export declare function generateAIContextFiles(repoPath: string, _storagePath: string, projectName: string, stats: RepoStats, generatedSkills?: GeneratedSkillInfo[], options?: AIContextOptions): Promise<{
|
|
21
24
|
files: string[];
|
|
22
25
|
}>;
|
|
23
26
|
export {};
|
package/dist/cli/ai-context.js
CHANGED
|
@@ -25,8 +25,10 @@ const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';
|
|
|
25
25
|
* - Self-review checklist — forces model to verify its own work
|
|
26
26
|
*/
|
|
27
27
|
function generateGitNexusContent(projectName, stats, generatedSkills) {
|
|
28
|
-
const generatedRows =
|
|
29
|
-
? generatedSkills
|
|
28
|
+
const generatedRows = generatedSkills && generatedSkills.length > 0
|
|
29
|
+
? generatedSkills
|
|
30
|
+
.map((s) => `| Work in the ${s.label} area (${s.symbolCount} symbols) | \`.claude/skills/generated/${s.name}/SKILL.md\` |`)
|
|
31
|
+
.join('\n')
|
|
30
32
|
: '';
|
|
31
33
|
const skillsTable = `| Task | Read this skill file |
|
|
32
34
|
|------|---------------------|
|
|
@@ -245,17 +247,23 @@ Use GitNexus tools to accomplish this task.
|
|
|
245
247
|
/**
|
|
246
248
|
* Generate AI context files after indexing
|
|
247
249
|
*/
|
|
248
|
-
export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats, generatedSkills) {
|
|
250
|
+
export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats, generatedSkills, options) {
|
|
249
251
|
const content = generateGitNexusContent(projectName, stats, generatedSkills);
|
|
250
252
|
const createdFiles = [];
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
if (!options?.skipAgentsMd) {
|
|
254
|
+
// Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Cline, etc.)
|
|
255
|
+
const agentsPath = path.join(repoPath, 'AGENTS.md');
|
|
256
|
+
const agentsResult = await upsertGitNexusSection(agentsPath, content);
|
|
257
|
+
createdFiles.push(`AGENTS.md (${agentsResult})`);
|
|
258
|
+
// Create CLAUDE.md (for Claude Code)
|
|
259
|
+
const claudePath = path.join(repoPath, 'CLAUDE.md');
|
|
260
|
+
const claudeResult = await upsertGitNexusSection(claudePath, content);
|
|
261
|
+
createdFiles.push(`CLAUDE.md (${claudeResult})`);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
createdFiles.push('AGENTS.md (skipped via --skip-agents-md)');
|
|
265
|
+
createdFiles.push('CLAUDE.md (skipped via --skip-agents-md)');
|
|
266
|
+
}
|
|
259
267
|
// Install skills to .claude/skills/gitnexus/
|
|
260
268
|
const installedSkills = await installSkills(repoPath);
|
|
261
269
|
if (installedSkills.length > 0) {
|
package/dist/cli/analyze.d.ts
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
* Analyze Command
|
|
3
3
|
*
|
|
4
4
|
* Indexes a repository and stores the knowledge graph in .gitnexus/
|
|
5
|
+
*
|
|
6
|
+
* Delegates core analysis to the shared runFullAnalysis orchestrator.
|
|
7
|
+
* This CLI wrapper handles: heap management, progress bar, SIGINT,
|
|
8
|
+
* skill generation (--skills), summary output, and process.exit().
|
|
5
9
|
*/
|
|
6
10
|
export interface AnalyzeOptions {
|
|
7
11
|
force?: boolean;
|
|
8
12
|
embeddings?: boolean;
|
|
9
13
|
skills?: boolean;
|
|
10
14
|
verbose?: boolean;
|
|
15
|
+
/** Skip AGENTS.md and CLAUDE.md gitnexus block updates. */
|
|
16
|
+
skipAgentsMd?: boolean;
|
|
11
17
|
/** Index the folder even when no .git directory is present. */
|
|
12
18
|
skipGit?: boolean;
|
|
13
19
|
}
|
package/dist/cli/analyze.js
CHANGED
|
@@ -2,21 +2,19 @@
|
|
|
2
2
|
* Analyze Command
|
|
3
3
|
*
|
|
4
4
|
* Indexes a repository and stores the knowledge graph in .gitnexus/
|
|
5
|
+
*
|
|
6
|
+
* Delegates core analysis to the shared runFullAnalysis orchestrator.
|
|
7
|
+
* This CLI wrapper handles: heap management, progress bar, SIGINT,
|
|
8
|
+
* skill generation (--skills), summary output, and process.exit().
|
|
5
9
|
*/
|
|
6
10
|
import path from 'path';
|
|
7
11
|
import { execFileSync } from 'child_process';
|
|
8
12
|
import v8 from 'v8';
|
|
9
13
|
import cliProgress from 'cli-progress';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// versions whose ABI is not yet supported by the native binary (#89).
|
|
15
|
-
// disposeEmbedder intentionally not called — ONNX Runtime segfaults on cleanup (see #38)
|
|
16
|
-
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath, cleanupOldKuzuFiles } from '../storage/repo-manager.js';
|
|
17
|
-
import { getCurrentCommit, getGitRoot, hasGitDir } from '../storage/git.js';
|
|
18
|
-
import { generateAIContextFiles } from './ai-context.js';
|
|
19
|
-
import { generateSkillFiles } from './skill-gen.js';
|
|
14
|
+
import { closeLbug } from '../core/lbug/lbug-adapter.js';
|
|
15
|
+
import { getStoragePaths, getGlobalRegistryPath } from '../storage/repo-manager.js';
|
|
16
|
+
import { getGitRoot, hasGitDir } from '../storage/git.js';
|
|
17
|
+
import { runFullAnalysis } from '../core/run-analyze.js';
|
|
20
18
|
import fs from 'fs/promises';
|
|
21
19
|
const HEAP_MB = 8192;
|
|
22
20
|
const HEAP_FLAG = `--max-old-space-size=${HEAP_MB}`;
|
|
@@ -39,23 +37,6 @@ function ensureHeap() {
|
|
|
39
37
|
}
|
|
40
38
|
return true;
|
|
41
39
|
}
|
|
42
|
-
/** Threshold: auto-skip embeddings for repos with more nodes than this */
|
|
43
|
-
const EMBEDDING_NODE_LIMIT = 50_000;
|
|
44
|
-
const PHASE_LABELS = {
|
|
45
|
-
extracting: 'Scanning files',
|
|
46
|
-
structure: 'Building structure',
|
|
47
|
-
parsing: 'Parsing code',
|
|
48
|
-
imports: 'Resolving imports',
|
|
49
|
-
calls: 'Tracing calls',
|
|
50
|
-
heritage: 'Extracting inheritance',
|
|
51
|
-
communities: 'Detecting communities',
|
|
52
|
-
processes: 'Detecting processes',
|
|
53
|
-
complete: 'Pipeline complete',
|
|
54
|
-
lbug: 'Loading into LadybugDB',
|
|
55
|
-
fts: 'Creating search indexes',
|
|
56
|
-
embeddings: 'Generating embeddings',
|
|
57
|
-
done: 'Done',
|
|
58
|
-
};
|
|
59
40
|
export const analyzeCommand = async (inputPath, options) => {
|
|
60
41
|
if (ensureHeap())
|
|
61
42
|
return;
|
|
@@ -91,26 +72,12 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
91
72
|
if (!repoHasGit) {
|
|
92
73
|
console.log(' Warning: no .git directory found \u2014 commit-tracking and incremental updates disabled.\n');
|
|
93
74
|
}
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
// If kuzu existed but lbug doesn't, we're doing a migration re-index — say so.
|
|
97
|
-
const kuzuResult = await cleanupOldKuzuFiles(storagePath);
|
|
98
|
-
if (kuzuResult.found && kuzuResult.needsReindex) {
|
|
99
|
-
console.log(' Migrating from KuzuDB to LadybugDB — rebuilding index...\n');
|
|
100
|
-
}
|
|
101
|
-
const currentCommit = repoHasGit ? getCurrentCommit(repoPath) : '';
|
|
102
|
-
const existingMeta = await loadMeta(storagePath);
|
|
103
|
-
if (existingMeta && !options?.force && !options?.skills && existingMeta.lastCommit === currentCommit) {
|
|
104
|
-
// Non-git folders have currentCommit = '' — always rebuild since we can't detect changes
|
|
105
|
-
if (currentCommit !== '') {
|
|
106
|
-
console.log(' Already up to date\n');
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
75
|
+
// KuzuDB migration cleanup is handled by runFullAnalysis internally.
|
|
76
|
+
// Note: --skills is handled after runFullAnalysis using the returned pipelineResult.
|
|
110
77
|
if (process.env.GITNEXUS_NO_GITIGNORE) {
|
|
111
78
|
console.log(' GITNEXUS_NO_GITIGNORE is set — skipping .gitignore (still reading .gitnexusignore)\n');
|
|
112
79
|
}
|
|
113
|
-
//
|
|
80
|
+
// ── CLI progress bar setup ─────────────────────────────────────────
|
|
114
81
|
const bar = new cliProgress.SingleBar({
|
|
115
82
|
format: ' {bar} {percentage}% | {phase}',
|
|
116
83
|
barCompleteChar: '\u2588',
|
|
@@ -122,35 +89,33 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
122
89
|
stopOnComplete: false,
|
|
123
90
|
}, cliProgress.Presets.shades_grey);
|
|
124
91
|
bar.start(100, 0, { phase: 'Initializing...' });
|
|
125
|
-
// Graceful SIGINT handling
|
|
92
|
+
// Graceful SIGINT handling
|
|
126
93
|
let aborted = false;
|
|
127
94
|
const sigintHandler = () => {
|
|
128
95
|
if (aborted)
|
|
129
|
-
process.exit(1);
|
|
96
|
+
process.exit(1);
|
|
130
97
|
aborted = true;
|
|
131
98
|
bar.stop();
|
|
132
99
|
console.log('\n Interrupted — cleaning up...');
|
|
133
|
-
closeLbug()
|
|
100
|
+
closeLbug()
|
|
101
|
+
.catch(() => { })
|
|
102
|
+
.finally(() => process.exit(130));
|
|
134
103
|
};
|
|
135
104
|
process.on('SIGINT', sigintHandler);
|
|
136
|
-
// Route
|
|
137
|
-
// multiple times when other code writes to stdout/stderr mid-render.
|
|
105
|
+
// Route console output through bar.log() to prevent progress bar corruption
|
|
138
106
|
const origLog = console.log.bind(console);
|
|
139
107
|
const origWarn = console.warn.bind(console);
|
|
140
108
|
const origError = console.error.bind(console);
|
|
141
109
|
const barLog = (...args) => {
|
|
142
|
-
// Clear the bar line, print the message, then let the next bar.update redraw
|
|
143
110
|
process.stdout.write('\x1b[2K\r');
|
|
144
|
-
origLog(args.map(a => (typeof a === 'string' ? a : String(a))).join(' '));
|
|
111
|
+
origLog(args.map((a) => (typeof a === 'string' ? a : String(a))).join(' '));
|
|
145
112
|
};
|
|
146
113
|
console.log = barLog;
|
|
147
114
|
console.warn = barLog;
|
|
148
115
|
console.error = barLog;
|
|
149
|
-
// Track elapsed time per phase
|
|
150
|
-
// same format so they don't flicker against each other.
|
|
116
|
+
// Track elapsed time per phase
|
|
151
117
|
let lastPhaseLabel = 'Initializing...';
|
|
152
118
|
let phaseStart = Date.now();
|
|
153
|
-
/** Update bar with phase label + elapsed seconds (shown after 3s). */
|
|
154
119
|
const updateBar = (value, phaseLabel) => {
|
|
155
120
|
if (phaseLabel !== lastPhaseLabel) {
|
|
156
121
|
lastPhaseLabel = phaseLabel;
|
|
@@ -160,217 +125,106 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
160
125
|
const display = elapsed >= 3 ? `${phaseLabel} (${elapsed}s)` : phaseLabel;
|
|
161
126
|
bar.update(value, { phase: display });
|
|
162
127
|
};
|
|
163
|
-
// Tick elapsed seconds for phases with infrequent progress callbacks
|
|
164
|
-
// (e.g. CSV streaming, FTS indexing). Uses the same display format as
|
|
165
|
-
// updateBar so there's no flickering.
|
|
166
128
|
const elapsedTimer = setInterval(() => {
|
|
167
129
|
const elapsed = Math.round((Date.now() - phaseStart) / 1000);
|
|
168
130
|
if (elapsed >= 3) {
|
|
169
131
|
bar.update({ phase: `${lastPhaseLabel} (${elapsed}s)` });
|
|
170
132
|
}
|
|
171
133
|
}, 1000);
|
|
172
|
-
const
|
|
173
|
-
// ──
|
|
174
|
-
let cachedEmbeddingNodeIds = new Set();
|
|
175
|
-
let cachedEmbeddings = [];
|
|
176
|
-
if (options?.embeddings && existingMeta && !options?.force) {
|
|
177
|
-
try {
|
|
178
|
-
updateBar(0, 'Caching embeddings...');
|
|
179
|
-
await initLbug(lbugPath);
|
|
180
|
-
const cached = await loadCachedEmbeddings();
|
|
181
|
-
cachedEmbeddingNodeIds = cached.embeddingNodeIds;
|
|
182
|
-
cachedEmbeddings = cached.embeddings;
|
|
183
|
-
await closeLbug();
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
try {
|
|
187
|
-
await closeLbug();
|
|
188
|
-
}
|
|
189
|
-
catch { }
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
// ── Phase 1: Full Pipeline (0–60%) ─────────────────────────────────
|
|
193
|
-
const pipelineResult = await runPipelineFromRepo(repoPath, (progress) => {
|
|
194
|
-
const phaseLabel = PHASE_LABELS[progress.phase] || progress.phase;
|
|
195
|
-
const scaled = Math.round(progress.percent * 0.6);
|
|
196
|
-
updateBar(scaled, phaseLabel);
|
|
197
|
-
});
|
|
198
|
-
// ── Phase 2: LadybugDB (60–85%) ──────────────────────────────────────
|
|
199
|
-
updateBar(60, 'Loading into LadybugDB...');
|
|
200
|
-
await closeLbug();
|
|
201
|
-
const lbugFiles = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.lock`];
|
|
202
|
-
for (const f of lbugFiles) {
|
|
203
|
-
try {
|
|
204
|
-
await fs.rm(f, { recursive: true, force: true });
|
|
205
|
-
}
|
|
206
|
-
catch { }
|
|
207
|
-
}
|
|
208
|
-
const t0Lbug = Date.now();
|
|
209
|
-
await initLbug(lbugPath);
|
|
210
|
-
let lbugMsgCount = 0;
|
|
211
|
-
const lbugResult = await loadGraphToLbug(pipelineResult.graph, pipelineResult.repoPath, storagePath, (msg) => {
|
|
212
|
-
lbugMsgCount++;
|
|
213
|
-
const progress = Math.min(84, 60 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 24));
|
|
214
|
-
updateBar(progress, msg);
|
|
215
|
-
});
|
|
216
|
-
const lbugTime = ((Date.now() - t0Lbug) / 1000).toFixed(1);
|
|
217
|
-
const lbugWarnings = lbugResult.warnings;
|
|
218
|
-
// ── Phase 3: FTS (85–90%) ─────────────────────────────────────────
|
|
219
|
-
updateBar(85, 'Creating search indexes...');
|
|
220
|
-
const t0Fts = Date.now();
|
|
134
|
+
const t0 = Date.now();
|
|
135
|
+
// ── Run shared analysis orchestrator ───────────────────────────────
|
|
221
136
|
try {
|
|
222
|
-
await
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
137
|
+
const result = await runFullAnalysis(repoPath, {
|
|
138
|
+
force: options?.force || options?.skills,
|
|
139
|
+
embeddings: options?.embeddings,
|
|
140
|
+
skipGit: options?.skipGit,
|
|
141
|
+
skipAgentsMd: options?.skipAgentsMd,
|
|
142
|
+
}, {
|
|
143
|
+
onProgress: (_phase, percent, message) => {
|
|
144
|
+
updateBar(percent, message);
|
|
145
|
+
},
|
|
146
|
+
onLog: barLog,
|
|
147
|
+
});
|
|
148
|
+
if (result.alreadyUpToDate) {
|
|
149
|
+
clearInterval(elapsedTimer);
|
|
150
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
151
|
+
console.log = origLog;
|
|
152
|
+
console.warn = origWarn;
|
|
153
|
+
console.error = origError;
|
|
154
|
+
bar.stop();
|
|
155
|
+
console.log(' Already up to date\n');
|
|
156
|
+
// Safe to return without process.exit(0) — the early-return path in
|
|
157
|
+
// runFullAnalysis never opens LadybugDB, so no native handles prevent exit.
|
|
158
|
+
return;
|
|
242
159
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
160
|
+
// Skill generation (CLI-only, uses pipeline result from analysis)
|
|
161
|
+
if (options?.skills && result.pipelineResult) {
|
|
162
|
+
updateBar(99, 'Generating skill files...');
|
|
163
|
+
try {
|
|
164
|
+
const { generateSkillFiles } = await import('./skill-gen.js');
|
|
165
|
+
const { generateAIContextFiles } = await import('./ai-context.js');
|
|
166
|
+
const skillResult = await generateSkillFiles(repoPath, result.repoName, result.pipelineResult);
|
|
167
|
+
if (skillResult.skills.length > 0) {
|
|
168
|
+
barLog(` Generated ${skillResult.skills.length} skill files`);
|
|
169
|
+
// Re-generate AI context files now that we have skill info
|
|
170
|
+
const s = result.stats;
|
|
171
|
+
const communityResult = result.pipelineResult?.communityResult;
|
|
172
|
+
let aggregatedClusterCount = 0;
|
|
173
|
+
if (communityResult?.communities) {
|
|
174
|
+
const groups = new Map();
|
|
175
|
+
for (const c of communityResult.communities) {
|
|
176
|
+
const label = c.heuristicLabel || c.label || 'Unknown';
|
|
177
|
+
groups.set(label, (groups.get(label) || 0) + c.symbolCount);
|
|
178
|
+
}
|
|
179
|
+
aggregatedClusterCount = Array.from(groups.values()).filter((count) => count >= 5).length;
|
|
180
|
+
}
|
|
181
|
+
const { storagePath: sp } = getStoragePaths(repoPath);
|
|
182
|
+
await generateAIContextFiles(repoPath, sp, result.repoName, {
|
|
183
|
+
files: s.files ?? 0,
|
|
184
|
+
nodes: s.nodes ?? 0,
|
|
185
|
+
edges: s.edges ?? 0,
|
|
186
|
+
communities: s.communities,
|
|
187
|
+
clusters: aggregatedClusterCount,
|
|
188
|
+
processes: s.processes,
|
|
189
|
+
}, skillResult.skills, { skipAgentsMd: options?.skipAgentsMd });
|
|
251
190
|
}
|
|
252
|
-
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
/* best-effort */
|
|
253
194
|
}
|
|
254
195
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
196
|
+
const totalTime = ((Date.now() - t0) / 1000).toFixed(1);
|
|
197
|
+
clearInterval(elapsedTimer);
|
|
198
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
199
|
+
console.log = origLog;
|
|
200
|
+
console.warn = origWarn;
|
|
201
|
+
console.error = origError;
|
|
202
|
+
bar.update(100, { phase: 'Done' });
|
|
203
|
+
bar.stop();
|
|
204
|
+
// ── Summary ────────────────────────────────────────────────────
|
|
205
|
+
const s = result.stats;
|
|
206
|
+
console.log(`\n Repository indexed successfully (${totalTime}s)\n`);
|
|
207
|
+
console.log(` ${(s.nodes ?? 0).toLocaleString()} nodes | ${(s.edges ?? 0).toLocaleString()} edges | ${s.communities ?? 0} clusters | ${s.processes ?? 0} flows`);
|
|
208
|
+
console.log(` ${repoPath}`);
|
|
209
|
+
try {
|
|
210
|
+
await fs.access(getGlobalRegistryPath());
|
|
267
211
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const { isHttpMode } = await import('../core/embeddings/http-client.js');
|
|
271
|
-
const httpMode = isHttpMode();
|
|
272
|
-
updateBar(90, httpMode ? 'Connecting to embedding endpoint...' : 'Loading embedding model...');
|
|
273
|
-
const t0Emb = Date.now();
|
|
274
|
-
const { runEmbeddingPipeline } = await import('../core/embeddings/embedding-pipeline.js');
|
|
275
|
-
await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (progress) => {
|
|
276
|
-
const scaled = 90 + Math.round((progress.percent / 100) * 8);
|
|
277
|
-
const label = progress.phase === 'loading-model'
|
|
278
|
-
? (httpMode ? 'Connecting to embedding endpoint...' : 'Loading embedding model...')
|
|
279
|
-
: `Embedding ${progress.nodesProcessed || 0}/${progress.totalNodes || '?'}`;
|
|
280
|
-
updateBar(scaled, label);
|
|
281
|
-
}, {}, cachedEmbeddingNodeIds.size > 0 ? cachedEmbeddingNodeIds : undefined);
|
|
282
|
-
embeddingTime = ((Date.now() - t0Emb) / 1000).toFixed(1);
|
|
283
|
-
}
|
|
284
|
-
// ── Phase 5: Finalize (98–100%) ───────────────────────────────────
|
|
285
|
-
updateBar(98, 'Saving metadata...');
|
|
286
|
-
// Count embeddings in the index (cached + newly generated)
|
|
287
|
-
let embeddingCount = 0;
|
|
288
|
-
try {
|
|
289
|
-
const embResult = await executeQuery(`MATCH (e:CodeEmbedding) RETURN count(e) AS cnt`);
|
|
290
|
-
embeddingCount = embResult?.[0]?.cnt ?? 0;
|
|
291
|
-
}
|
|
292
|
-
catch { /* table may not exist if embeddings never ran */ }
|
|
293
|
-
const meta = {
|
|
294
|
-
repoPath,
|
|
295
|
-
lastCommit: currentCommit,
|
|
296
|
-
indexedAt: new Date().toISOString(),
|
|
297
|
-
stats: {
|
|
298
|
-
files: pipelineResult.totalFileCount,
|
|
299
|
-
nodes: stats.nodes,
|
|
300
|
-
edges: stats.edges,
|
|
301
|
-
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
302
|
-
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
303
|
-
embeddings: embeddingCount,
|
|
304
|
-
},
|
|
305
|
-
};
|
|
306
|
-
await saveMeta(storagePath, meta);
|
|
307
|
-
await registerRepo(repoPath, meta);
|
|
308
|
-
// Only attempt to update .gitignore when a .git directory is present.
|
|
309
|
-
// Use hasGitDir (filesystem check) rather than git CLI subprocess
|
|
310
|
-
// so we skip correctly for --skip-git folders even if git CLI is available.
|
|
311
|
-
if (hasGitDir(repoPath)) {
|
|
312
|
-
await addToGitignore(repoPath);
|
|
313
|
-
}
|
|
314
|
-
const projectName = path.basename(repoPath);
|
|
315
|
-
let aggregatedClusterCount = 0;
|
|
316
|
-
if (pipelineResult.communityResult?.communities) {
|
|
317
|
-
const groups = new Map();
|
|
318
|
-
for (const c of pipelineResult.communityResult.communities) {
|
|
319
|
-
const label = c.heuristicLabel || c.label || 'Unknown';
|
|
320
|
-
groups.set(label, (groups.get(label) || 0) + c.symbolCount);
|
|
212
|
+
catch {
|
|
213
|
+
console.log('\n Tip: Run `gitnexus setup` to configure MCP for your editor.');
|
|
321
214
|
}
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
335
|
-
clusters: aggregatedClusterCount,
|
|
336
|
-
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
337
|
-
}, generatedSkills);
|
|
338
|
-
await closeLbug();
|
|
339
|
-
// Note: we intentionally do NOT call disposeEmbedder() here.
|
|
340
|
-
// ONNX Runtime's native cleanup segfaults on macOS and some Linux configs.
|
|
341
|
-
// Since the process exits immediately after, Node.js reclaims everything.
|
|
342
|
-
const totalTime = ((Date.now() - t0Global) / 1000).toFixed(1);
|
|
343
|
-
clearInterval(elapsedTimer);
|
|
344
|
-
process.removeListener('SIGINT', sigintHandler);
|
|
345
|
-
console.log = origLog;
|
|
346
|
-
console.warn = origWarn;
|
|
347
|
-
console.error = origError;
|
|
348
|
-
bar.update(100, { phase: 'Done' });
|
|
349
|
-
bar.stop();
|
|
350
|
-
// ── Summary ───────────────────────────────────────────────────────
|
|
351
|
-
const embeddingsCached = cachedEmbeddings.length > 0;
|
|
352
|
-
console.log(`\n Repository indexed successfully (${totalTime}s)${embeddingsCached ? ` [${cachedEmbeddings.length} embeddings cached]` : ''}\n`);
|
|
353
|
-
console.log(` ${stats.nodes.toLocaleString()} nodes | ${stats.edges.toLocaleString()} edges | ${pipelineResult.communityResult?.stats.totalCommunities || 0} clusters | ${pipelineResult.processResult?.stats.totalProcesses || 0} flows`);
|
|
354
|
-
console.log(` LadybugDB ${lbugTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
|
|
355
|
-
console.log(` ${repoPath}`);
|
|
356
|
-
if (aiContext.files.length > 0) {
|
|
357
|
-
console.log(` Context: ${aiContext.files.join(', ')}`);
|
|
358
|
-
}
|
|
359
|
-
// Show a quiet summary if some edge types needed fallback insertion
|
|
360
|
-
if (lbugWarnings.length > 0) {
|
|
361
|
-
const totalFallback = lbugWarnings.reduce((sum, w) => {
|
|
362
|
-
const m = w.match(/\((\d+) edges\)/);
|
|
363
|
-
return sum + (m ? parseInt(m[1]) : 0);
|
|
364
|
-
}, 0);
|
|
365
|
-
console.log(` Note: ${totalFallback} edges across ${lbugWarnings.length} types inserted via fallback (schema will be updated in next release)`);
|
|
366
|
-
}
|
|
367
|
-
try {
|
|
368
|
-
await fs.access(getGlobalRegistryPath());
|
|
369
|
-
}
|
|
370
|
-
catch {
|
|
371
|
-
console.log('\n Tip: Run `gitnexus setup` to configure MCP for your editor.');
|
|
215
|
+
console.log('');
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
clearInterval(elapsedTimer);
|
|
219
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
220
|
+
console.log = origLog;
|
|
221
|
+
console.warn = origWarn;
|
|
222
|
+
console.error = origError;
|
|
223
|
+
bar.stop();
|
|
224
|
+
console.error(`\n Analysis failed: ${err.message}\n`);
|
|
225
|
+
process.exitCode = 1;
|
|
226
|
+
return;
|
|
372
227
|
}
|
|
373
|
-
console.log('');
|
|
374
228
|
// LadybugDB's native module holds open handles that prevent Node from exiting.
|
|
375
229
|
// ONNX Runtime also registers native atexit hooks that segfault on some
|
|
376
230
|
// platforms (#38, #40). Force-exit to ensure clean termination.
|
package/dist/cli/eval-server.js
CHANGED
|
@@ -68,7 +68,9 @@ export function formatContextResult(result) {
|
|
|
68
68
|
if (result.error)
|
|
69
69
|
return `Error: ${result.error}`;
|
|
70
70
|
if (result.status === 'ambiguous') {
|
|
71
|
-
const lines = [
|
|
71
|
+
const lines = [
|
|
72
|
+
`Multiple symbols named '${result.candidates?.[0]?.name || '?'}'. Disambiguate with file path:\n`,
|
|
73
|
+
];
|
|
72
74
|
for (const c of result.candidates || []) {
|
|
73
75
|
lines.push(` ${c.kind} ${c.name} → ${c.filePath}:${c.line || '?'} (uid: ${c.uid})`);
|
|
74
76
|
}
|
|
@@ -171,7 +173,7 @@ export function formatCypherResult(result) {
|
|
|
171
173
|
const keys = Object.keys(result[0]);
|
|
172
174
|
const lines = [`${result.length} row(s):\n`];
|
|
173
175
|
for (const row of result.slice(0, 30)) {
|
|
174
|
-
const parts = keys.map(k => `${k}: ${row[k]}`);
|
|
176
|
+
const parts = keys.map((k) => `${k}: ${row[k]}`);
|
|
175
177
|
lines.push(` ${parts.join(' | ')}`);
|
|
176
178
|
}
|
|
177
179
|
if (result.length > 30) {
|
|
@@ -230,13 +232,20 @@ export function formatListReposResult(result) {
|
|
|
230
232
|
*/
|
|
231
233
|
function formatToolResult(toolName, result) {
|
|
232
234
|
switch (toolName) {
|
|
233
|
-
case 'query':
|
|
234
|
-
|
|
235
|
-
case '
|
|
236
|
-
|
|
237
|
-
case '
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
case 'query':
|
|
236
|
+
return formatQueryResult(result);
|
|
237
|
+
case 'context':
|
|
238
|
+
return formatContextResult(result);
|
|
239
|
+
case 'impact':
|
|
240
|
+
return formatImpactResult(result);
|
|
241
|
+
case 'cypher':
|
|
242
|
+
return formatCypherResult(result);
|
|
243
|
+
case 'detect_changes':
|
|
244
|
+
return formatDetectChangesResult(result);
|
|
245
|
+
case 'list_repos':
|
|
246
|
+
return formatListReposResult(result);
|
|
247
|
+
default:
|
|
248
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
240
249
|
}
|
|
241
250
|
}
|
|
242
251
|
// ─── Next-Step Hints ──────────────────────────────────────────────────
|
|
@@ -269,7 +278,7 @@ export async function evalServerCommand(options) {
|
|
|
269
278
|
process.exit(1);
|
|
270
279
|
}
|
|
271
280
|
const repos = await backend.listRepos();
|
|
272
|
-
console.error(`GitNexus eval-server: ${repos.length} repo(s) loaded: ${repos.map(r => r.name).join(', ')}`);
|
|
281
|
+
console.error(`GitNexus eval-server: ${repos.length} repo(s) loaded: ${repos.map((r) => r.name).join(', ')}`);
|
|
273
282
|
let idleTimer = null;
|
|
274
283
|
function resetIdleTimer() {
|
|
275
284
|
if (idleTimeoutSec <= 0)
|
|
@@ -289,7 +298,7 @@ export async function evalServerCommand(options) {
|
|
|
289
298
|
if (req.method === 'GET' && req.url === '/health') {
|
|
290
299
|
res.setHeader('Content-Type', 'application/json');
|
|
291
300
|
res.writeHead(200);
|
|
292
|
-
res.end(JSON.stringify({ status: 'ok', repos: repos.map(r => r.name) }));
|
|
301
|
+
res.end(JSON.stringify({ status: 'ok', repos: repos.map((r) => r.name) }));
|
|
293
302
|
return;
|
|
294
303
|
}
|
|
295
304
|
// Shutdown
|