Merge branch 'main' into fix/telemetry-opt-out

This commit is contained in:
Colby McHenry
2026-04-01 14:12:28 -05:00
3 changed files with 61 additions and 24 deletions
+8 -6
View File
@@ -162,6 +162,7 @@ npx @colbymchenry/codegraph
``` ```
The interactive installer will: The interactive installer will:
- Prompt to install `codegraph` globally (needed for hooks & MCP server to work)
- Configure the MCP server in `~/.claude.json` - Configure the MCP server in `~/.claude.json`
- Set up auto-allow permissions for CodeGraph tools - Set up auto-allow permissions for CodeGraph tools
- Add global instructions to `~/.claude/CLAUDE.md` (teaches Claude how to use CodeGraph) - Add global instructions to `~/.claude/CLAUDE.md` (teaches Claude how to use CodeGraph)
@@ -306,12 +307,13 @@ npx @colbymchenry/codegraph # Run via npx (no global install needed)
``` ```
The installer will: The installer will:
1. Ask for installation location (global `~/.claude` or local `./.claude`) 1. Prompt to install `codegraph` globally (needed for hooks & MCP server)
2. Configure the MCP server in `claude.json` 2. Ask for installation location (global `~/.claude` or local `./.claude`)
3. Optionally set up auto-allow permissions 3. Configure the MCP server in `claude.json`
4. Add global instructions to `~/.claude/CLAUDE.md` (teaches Claude how to use CodeGraph) 4. Optionally set up auto-allow permissions
5. Install Claude Code hooks for automatic index syncing 5. Add global instructions to `~/.claude/CLAUDE.md` (teaches Claude how to use CodeGraph)
6. For local installs: initialize and index the current project 6. Install Claude Code hooks for automatic index syncing
7. For local installs: initialize and index the current project
### `codegraph init [path]` ### `codegraph init [path]`
+20 -11
View File
@@ -7,7 +7,7 @@
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { showBanner, showNextSteps, success, error, info, chalk } from './banner'; import { showBanner, showNextSteps, success, error, info, chalk } from './banner';
import { promptInstallLocation, promptAutoAllow, InstallLocation } from './prompts'; import { promptInstallLocation, promptAutoAllow, promptConfirm, InstallLocation } from './prompts';
import { writeMcpConfig, writePermissions, writeClaudeMd, writeHooks, hasMcpConfig, hasPermissions, hasHooks } from './config-writer'; import { writeMcpConfig, writePermissions, writeClaudeMd, writeHooks, hasMcpConfig, hasPermissions, hasHooks } from './config-writer';
/** /**
@@ -25,16 +25,25 @@ export async function runInstaller(): Promise<void> {
showBanner(); showBanner();
try { try {
// Step 1: Install codegraph globally. // Step 1: Install codegraph globally (with user consent).
// Always run npm install -g — we can't use `command -v codegraph` to check // The global install is needed because Claude Code hooks and the MCP server
// because npx puts a temporary binary in PATH that vanishes when npx exits. // invoke `codegraph` by name — the temporary npx binary vanishes when npx exits.
console.log(chalk.dim(' Installing codegraph globally...')); console.log(chalk.bold(' Install codegraph globally?') + chalk.dim(' (Required for hooks & MCP server)'));
try { console.log();
execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe' }); const shouldInstallGlobally = await promptConfirm('Install globally via npm', true);
success('Installed codegraph command globally');
} catch { if (shouldInstallGlobally) {
info('Could not install globally (permission denied)'); console.log(chalk.dim(' Installing codegraph globally...'));
info('Try: sudo npm install -g @colbymchenry/codegraph'); try {
execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe' });
success('Installed codegraph command globally');
} catch {
info('Could not install globally (permission denied)');
info('Try: sudo npm install -g @colbymchenry/codegraph');
}
} else {
info('Skipped global install — hooks and MCP server may not work without it');
info('You can install later: npm install -g @colbymchenry/codegraph');
} }
console.log(); console.log();
+33 -7
View File
@@ -8,9 +8,11 @@ import * as path from 'path';
import { Node } from '../types'; import { Node } from '../types';
/** /**
* Common stop words to filter from search queries * Common stop words to filter from search queries.
* Includes generic English + code-specific noise words.
*/ */
export const STOP_WORDS = new Set([ export const STOP_WORDS = new Set([
// English
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
'of', 'with', 'by', 'from', 'is', 'it', 'that', 'this', 'are', 'was', 'of', 'with', 'by', 'from', 'is', 'it', 'that', 'this', 'are', 'was',
'be', 'has', 'had', 'have', 'do', 'does', 'did', 'will', 'would', 'could', 'be', 'has', 'had', 'have', 'do', 'does', 'did', 'will', 'would', 'could',
@@ -18,17 +20,41 @@ export const STOP_WORDS = new Set([
'every', 'how', 'what', 'where', 'when', 'who', 'which', 'why', 'every', 'how', 'what', 'where', 'when', 'who', 'which', 'why',
'i', 'me', 'my', 'we', 'our', 'you', 'your', 'he', 'she', 'they', 'i', 'me', 'my', 'we', 'our', 'you', 'your', 'he', 'she', 'they',
'find', 'show', 'get', 'list', 'give', 'tell', 'find', 'show', 'get', 'list', 'give', 'tell',
'been', 'done', 'made', 'used', 'using', 'work', 'works', 'found',
'also', 'into', 'then', 'than', 'just', 'more', 'some', 'such',
'over', 'only', 'new', 'out', 'its', 'so', 'up', 'as', 'if',
// Code-specific noise
'code', 'file', 'files', 'function', 'method', 'class', 'type',
'build', 'run', 'test', 'fix', 'bug', 'call', 'called', 'set', 'add',
]); ]);
/** /**
* Extract meaningful search terms from a natural language query * Extract meaningful search terms from a natural language query.
* Splits camelCase, PascalCase, snake_case, SCREAMING_SNAKE, and dot.notation
* into individual tokens before filtering.
*/ */
export function extractSearchTerms(query: string): string[] { export function extractSearchTerms(query: string): string[] {
return query const tokens = new Set<string>();
.toLowerCase()
.replace(/[^\w\s-]/g, ' ') // Split camelCase / PascalCase: "getUserName" → "get User Name"
.split(/\s+/) const camelSplit = query
.filter(term => term.length > 1 && !STOP_WORDS.has(term)); .replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
// Replace underscores and dots with spaces (snake_case, dot.notation)
const normalised = camelSplit.replace(/[_.]+/g, ' ');
// Split on any non-alphanumeric character
const words = normalised.split(/[^a-zA-Z0-9]+/).filter(Boolean);
for (const word of words) {
const lower = word.toLowerCase();
if (lower.length < 3) continue;
if (STOP_WORDS.has(lower)) continue;
tokens.add(lower);
}
return [...tokens];
} }
/** /**