diff --git a/README.md b/README.md index 5616b6a818..56b4d1b3a6 100644 --- a/README.md +++ b/README.md @@ -165,13 +165,14 @@ args = ["-y", "gitnexus@latest", "mcp"] ### CLI Commands ```bash -gitnexus setup # Configure MCP for your editors (one-time) -gitnexus analyze [path] # Index a repository (or update stale index) -gitnexus analyze --force # Force full re-index -gitnexus analyze --skills # Generate repo-specific skill files from detected communities +gitnexus setup # Configure MCP for your editors (one-time) +gitnexus analyze [path] # Index a repository (or update stale index) +gitnexus analyze --force # Force full re-index +gitnexus analyze --skills # Generate repo-specific skill files from detected communities gitnexus analyze --skip-embeddings # Skip embedding generation (faster) -gitnexus analyze --embeddings # Enable embedding generation (slower, better search) -gitnexus analyze --verbose # Log skipped files when parsers are unavailable +gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits +gitnexus analyze --embeddings # Enable embedding generation (slower, better search) +gitnexus analyze --verbose # Log skipped files when parsers are unavailable gitnexus mcp # Start MCP server (stdio) — serves all indexed repos gitnexus serve # Start local HTTP server (multi-repo) for web UI connection gitnexus list # List all indexed repositories diff --git a/gitnexus/README.md b/gitnexus/README.md index 312a465391..b56e6c2192 100644 --- a/gitnexus/README.md +++ b/gitnexus/README.md @@ -149,11 +149,12 @@ Your AI agent gets these tools automatically: ## CLI Commands ```bash -gitnexus setup # Configure MCP for your editors (one-time) -gitnexus analyze [path] # Index a repository (or update stale index) -gitnexus analyze --force # Force full re-index -gitnexus analyze --embeddings # Enable embedding generation (slower, better search) -gitnexus analyze --verbose # Log skipped files when parsers are unavailable +gitnexus setup # Configure MCP for your editors (one-time) +gitnexus analyze [path] # Index a repository (or update stale index) +gitnexus analyze --force # Force full re-index +gitnexus analyze --embeddings # Enable embedding generation (slower, better search) +gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits +gitnexus analyze --verbose # Log skipped files when parsers are unavailable gitnexus mcp # Start MCP server (stdio) — serves all indexed repos gitnexus serve # Start local HTTP server (multi-repo) for web UI gitnexus index # Register an existing .gitnexus/ folder into the global registry diff --git a/gitnexus/src/cli/ai-context.ts b/gitnexus/src/cli/ai-context.ts index d1e6b7ba84..81aaa496d8 100644 --- a/gitnexus/src/cli/ai-context.ts +++ b/gitnexus/src/cli/ai-context.ts @@ -24,6 +24,10 @@ interface RepoStats { processes?: number; } +export interface AIContextOptions { + skipAgentsMd?: boolean; +} + const GITNEXUS_START_MARKER = ''; const GITNEXUS_END_MARKER = ''; @@ -299,19 +303,25 @@ export async function generateAIContextFiles( projectName: string, stats: RepoStats, generatedSkills?: GeneratedSkillInfo[], + options?: AIContextOptions, ): Promise<{ files: string[] }> { const content = generateGitNexusContent(projectName, stats, generatedSkills); const createdFiles: string[] = []; - // Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Cline, etc.) - const agentsPath = path.join(repoPath, 'AGENTS.md'); - const agentsResult = await upsertGitNexusSection(agentsPath, content); - createdFiles.push(`AGENTS.md (${agentsResult})`); - - // Create CLAUDE.md (for Claude Code) - const claudePath = path.join(repoPath, 'CLAUDE.md'); - const claudeResult = await upsertGitNexusSection(claudePath, content); - createdFiles.push(`CLAUDE.md (${claudeResult})`); + if (!options?.skipAgentsMd) { + // Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Cline, etc.) + const agentsPath = path.join(repoPath, 'AGENTS.md'); + const agentsResult = await upsertGitNexusSection(agentsPath, content); + createdFiles.push(`AGENTS.md (${agentsResult})`); + + // Create CLAUDE.md (for Claude Code) + const claudePath = path.join(repoPath, 'CLAUDE.md'); + const claudeResult = await upsertGitNexusSection(claudePath, content); + createdFiles.push(`CLAUDE.md (${claudeResult})`); + } else { + createdFiles.push('AGENTS.md (skipped via --skip-agents-md)'); + createdFiles.push('CLAUDE.md (skipped via --skip-agents-md)'); + } // Install skills to .claude/skills/gitnexus/ const installedSkills = await installSkills(repoPath); diff --git a/gitnexus/src/cli/analyze.ts b/gitnexus/src/cli/analyze.ts index 8dcb19e469..c77903de0b 100644 --- a/gitnexus/src/cli/analyze.ts +++ b/gitnexus/src/cli/analyze.ts @@ -45,6 +45,8 @@ export interface AnalyzeOptions { embeddings?: boolean; skills?: boolean; verbose?: boolean; + /** Skip AGENTS.md and CLAUDE.md gitnexus block updates. */ + skipAgentsMd?: boolean; /** Index the folder even when no .git directory is present. */ skipGit?: boolean; } @@ -174,6 +176,7 @@ export const analyzeCommand = async (inputPath?: string, options?: AnalyzeOption force: options?.force || options?.skills, embeddings: options?.embeddings, skipGit: options?.skipGit, + skipAgentsMd: options?.skipAgentsMd, }, { onProgress: (_phase, percent, message) => { @@ -237,6 +240,7 @@ export const analyzeCommand = async (inputPath?: string, options?: AnalyzeOption processes: s.processes, }, skillResult.skills, + { skipAgentsMd: options?.skipAgentsMd }, ); } } catch { diff --git a/gitnexus/src/cli/index.ts b/gitnexus/src/cli/index.ts index 7ccdf16de1..0cb338ad11 100644 --- a/gitnexus/src/cli/index.ts +++ b/gitnexus/src/cli/index.ts @@ -24,6 +24,7 @@ program .option('-f, --force', 'Force full re-index even if up to date') .option('--embeddings', 'Enable embedding generation for semantic search (off by default)') .option('--skills', 'Generate repo-specific skill files from detected communities') + .option('--skip-agents-md', 'Skip updating the gitnexus section in AGENTS.md and CLAUDE.md') .option('--skip-git', 'Index a folder without requiring a .git directory') .option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)') .addHelpText( diff --git a/gitnexus/src/core/run-analyze.ts b/gitnexus/src/core/run-analyze.ts index 3a6425ba79..e8a108c716 100644 --- a/gitnexus/src/core/run-analyze.ts +++ b/gitnexus/src/core/run-analyze.ts @@ -46,6 +46,8 @@ export interface AnalyzeOptions { force?: boolean; embeddings?: boolean; skipGit?: boolean; + /** Skip AGENTS.md and CLAUDE.md gitnexus block updates. */ + skipAgentsMd?: boolean; } export interface AnalyzeResult { @@ -312,14 +314,21 @@ export async function runFullAnalysis( } try { - await generateAIContextFiles(repoPath, storagePath, projectName, { - files: pipelineResult.totalFileCount, - nodes: stats.nodes, - edges: stats.edges, - communities: pipelineResult.communityResult?.stats.totalCommunities, - clusters: aggregatedClusterCount, - processes: pipelineResult.processResult?.stats.totalProcesses, - }); + await generateAIContextFiles( + repoPath, + storagePath, + projectName, + { + files: pipelineResult.totalFileCount, + nodes: stats.nodes, + edges: stats.edges, + communities: pipelineResult.communityResult?.stats.totalCommunities, + clusters: aggregatedClusterCount, + processes: pipelineResult.processResult?.stats.totalProcesses, + }, + undefined, + { skipAgentsMd: options.skipAgentsMd }, + ); } catch { // Best-effort — don't fail the entire analysis for context file issues } diff --git a/gitnexus/test/unit/ai-context.test.ts b/gitnexus/test/unit/ai-context.test.ts index 489eac9a1e..0a9f52a687 100644 --- a/gitnexus/test/unit/ai-context.test.ts +++ b/gitnexus/test/unit/ai-context.test.ts @@ -79,4 +79,32 @@ describe('generateAIContextFiles', () => { // Skills dir may not be created if skills source doesn't exist in test context } }); + + it('preserves manual AGENTS.md and CLAUDE.md edits when skipAgentsMd is enabled', async () => { + const stats = { nodes: 42, edges: 84, processes: 3 }; + const agentsPath = path.join(tmpDir, 'AGENTS.md'); + const claudePath = path.join(tmpDir, 'CLAUDE.md'); + const agentsContent = '# AGENTS\n\nCustom manual instructions only\n'; + const claudeContent = '# CLAUDE\n\nCustom manual instructions only\n'; + + await fs.writeFile(agentsPath, agentsContent, 'utf-8'); + await fs.writeFile(claudePath, claudeContent, 'utf-8'); + + const result = await generateAIContextFiles( + tmpDir, + storagePath, + 'TestProject', + stats, + undefined, + { skipAgentsMd: true }, + ); + + expect(result.files).toContain('AGENTS.md (skipped via --skip-agents-md)'); + expect(result.files).toContain('CLAUDE.md (skipped via --skip-agents-md)'); + + const agentsAfter = await fs.readFile(agentsPath, 'utf-8'); + const claudeAfter = await fs.readFile(claudePath, 'utf-8'); + expect(agentsAfter).toBe(agentsContent); + expect(claudeAfter).toBe(claudeContent); + }); }); diff --git a/gitnexus/test/unit/skip-git-cli.test.ts b/gitnexus/test/unit/skip-git-cli.test.ts index 73707fe61b..740c8265a7 100644 --- a/gitnexus/test/unit/skip-git-cli.test.ts +++ b/gitnexus/test/unit/skip-git-cli.test.ts @@ -6,7 +6,7 @@ import fs from 'fs'; describe('--skip-git CLI flag', () => { it('Commander maps --skip-git to options.skipGit (not --no-git inversion)', () => { - // Verify the CLI defines --skip-git, not --no-git + // Verify the CLI defines --skip-git and --skip-agents-md in analyze help. const helpOutput = execSync('node dist/cli/index.js analyze --help', { cwd: path.resolve(__dirname, '../..'), encoding: 'utf8', @@ -14,6 +14,7 @@ describe('--skip-git CLI flag', () => { }); expect(helpOutput).toContain('--skip-git'); + expect(helpOutput).toContain('--skip-agents-md'); expect(helpOutput).not.toContain('--no-git'); });