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(); + }); }); // ---------------------------------------------------------------------------