diff --git a/README.md b/README.md
index f0f1d7571..65ac7d332 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ https://github.com/user-attachments/assets/172685ba-8e54-4ea7-9ad1-e31a3398da72
| **What** | Index repos locally, connect AI agents via MCP | Visual graph explorer + AI chat in browser |
| **For** | Daily development with Cursor, Claude Code, Codex, Windsurf, OpenCode | Quick exploration, demos, one-off analysis |
| **Scale** | Full repos, any size | Limited by browser memory (~5k files), or unlimited via backend mode |
-| **Install** | `npm install -g gitnexus` | No install —[gitnexus.vercel.app](https://gitnexus.vercel.app) |
+| **Install** | `npm install -g gitnexus` | No install — [gitnexus.vercel.app](https://gitnexus.vercel.app) |
| **Storage** | LadybugDB native (fast, persistent) | LadybugDB WASM (in-memory, per session) |
| **Parsing** | Tree-sitter native bindings | Tree-sitter WASM |
| **Privacy** | Everything local, no network | Everything in-browser, no server |
diff --git a/gitnexus-web/src/components/OnboardingGuide.tsx b/gitnexus-web/src/components/OnboardingGuide.tsx
index cbfd16236..796e0f148 100644
--- a/gitnexus-web/src/components/OnboardingGuide.tsx
+++ b/gitnexus-web/src/components/OnboardingGuide.tsx
@@ -201,7 +201,7 @@ interface OnboardingGuideProps {
}
export const OnboardingGuide = ({ isPolling }: OnboardingGuideProps) => {
- const primary = isDev ? 'cd gitnexus && npm run serve' : 'npx gitnexus@latest serve';
+ const primary = isDev ? 'npm run --prefix gitnexus serve' : 'npx gitnexus@latest serve';
const termLabel = isDev ? 'Start backend' : 'Terminal';
// Step states: step 1 = copy command, step 2 = run/wait, step 3 = auto-connect
@@ -277,7 +277,9 @@ export const OnboardingGuide = ({ isPolling }: OnboardingGuideProps) => {
state={step2State}
number={2}
title={isPolling ? 'Waiting for server to start' : 'Paste and run in your terminal'}
- description={isPolling ? undefined : 'Open a new terminal window, paste, and hit Enter.'}
+ description={
+ isPolling ? undefined : 'Open a terminal at the project root, paste, and hit Enter.'
+ }
>
{isPolling && }
diff --git a/gitnexus/package.json b/gitnexus/package.json
index 7699208e2..07723f3e3 100644
--- a/gitnexus/package.json
+++ b/gitnexus/package.json
@@ -104,7 +104,8 @@
"overrides": {
"@huggingface/transformers": {
"onnxruntime-node": "$onnxruntime-node"
- }
+ },
+ "tree-sitter-c": "0.23.2"
},
"engines": {
"node": ">=20.0.0"
diff --git a/gitnexus/src/core/ingestion/filesystem-walker.ts b/gitnexus/src/core/ingestion/filesystem-walker.ts
index 11721d6a7..575efc0f3 100644
--- a/gitnexus/src/core/ingestion/filesystem-walker.ts
+++ b/gitnexus/src/core/ingestion/filesystem-walker.ts
@@ -1,3 +1,4 @@
+import { isVerboseIngestionEnabled } from './utils/verbose.js';
import fs from 'fs/promises';
import path from 'path';
import { glob } from 'glob';
@@ -43,6 +44,7 @@ export const walkRepositoryPaths = async (
const entries: ScannedFile[] = [];
let processed = 0;
let skippedLarge = 0;
+ const skippedLargePaths: string[] = [];
for (let start = 0; start < filtered.length; start += READ_CONCURRENCY) {
const batch = filtered.slice(start, start + READ_CONCURRENCY);
@@ -52,6 +54,7 @@ export const walkRepositoryPaths = async (
const stat = await fs.stat(fullPath);
if (stat.size > MAX_FILE_SIZE) {
skippedLarge++;
+ skippedLargePaths.push(relativePath.replace(/\\/g, '/'));
return null;
}
return { path: relativePath.replace(/\\/g, '/'), size: stat.size };
@@ -73,6 +76,11 @@ export const walkRepositoryPaths = async (
console.warn(
` Skipped ${skippedLarge} large files (>${MAX_FILE_SIZE / 1024}KB, likely generated/vendored)`,
);
+ if (isVerboseIngestionEnabled()) {
+ for (const p of skippedLargePaths) {
+ console.warn(` - ${p}`);
+ }
+ }
}
return entries;
diff --git a/gitnexus/src/storage/repo-manager.ts b/gitnexus/src/storage/repo-manager.ts
index cc447aa4c..b233d44fb 100644
--- a/gitnexus/src/storage/repo-manager.ts
+++ b/gitnexus/src/storage/repo-manager.ts
@@ -212,7 +212,7 @@ export const addToGitignore = async (repoPath: string): Promise => {
* Get the path to the global GitNexus directory
*/
export const getGlobalDir = (): string => {
- return path.join(os.homedir(), '.gitnexus');
+ return process.env.GITNEXUS_HOME || path.join(os.homedir(), '.gitnexus');
};
/**
diff --git a/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/app.ts b/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/app.ts
index 33433b777..14add7d54 100644
--- a/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/app.ts
+++ b/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/app.ts
@@ -2,8 +2,8 @@ import { User } from './user';
import { Repo } from './repo';
export function processEntities(): void {
- const user = new User();
- const repo = new Repo();
+ const user = new User('alice');
+ const repo = new Repo('/tmp/repo');
user.save();
repo.save();
}
diff --git a/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/repo.ts b/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/repo.ts
index 19631246b..671de6458 100644
--- a/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/repo.ts
+++ b/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/repo.ts
@@ -1,5 +1,7 @@
export class Repo {
+ constructor(private readonly path: string) {}
+
save(): boolean {
- return false;
+ return this.path.length > 0;
}
}
diff --git a/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/user.ts b/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/user.ts
index e2af97ca7..f20c2200f 100644
--- a/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/user.ts
+++ b/gitnexus/test/fixtures/lang-resolution/typescript-constructor-type-inference/src/user.ts
@@ -1,5 +1,7 @@
export class User {
+ constructor(private readonly name: string) {}
+
save(): boolean {
- return true;
+ return this.name.length > 0;
}
}
diff --git a/gitnexus/test/integration/resolvers/typescript.test.ts b/gitnexus/test/integration/resolvers/typescript.test.ts
index 53c2287c5..c883678ac 100644
--- a/gitnexus/test/integration/resolvers/typescript.test.ts
+++ b/gitnexus/test/integration/resolvers/typescript.test.ts
@@ -519,6 +519,16 @@ describe('TypeScript constructor-inferred type resolution', () => {
expect(saveMethods.length).toBe(2);
});
+ it('resolves explicit constructor calls for User and Repo', () => {
+ const calls = getRelationships(result, 'CALLS');
+ const userCtor = calls.find((c) => c.target === 'User' && c.targetFilePath === 'src/user.ts');
+ const repoCtor = calls.find((c) => c.target === 'Repo' && c.targetFilePath === 'src/repo.ts');
+ expect(userCtor).toBeDefined();
+ expect(repoCtor).toBeDefined();
+ expect(userCtor!.targetLabel).toBe('Class');
+ expect(repoCtor!.targetLabel).toBe('Class');
+ });
+
it('resolves user.save() to src/user.ts via constructor-inferred type', () => {
const calls = getRelationships(result, 'CALLS');
const userSave = calls.find((c) => c.target === 'save' && c.targetFilePath === 'src/user.ts');
@@ -538,6 +548,14 @@ describe('TypeScript constructor-inferred type resolution', () => {
const saveCalls = calls.filter((c) => c.target === 'save');
expect(saveCalls.length).toBe(2);
});
+
+ it('resolves constructor calls for both User and Repo', () => {
+ const calls = getRelationships(result, 'CALLS');
+ const userCtor = calls.find((c) => c.target === 'User');
+ const repoCtor = calls.find((c) => c.target === 'Repo');
+ expect(userCtor).toBeDefined();
+ expect(repoCtor).toBeDefined();
+ });
});
// ---------------------------------------------------------------------------