Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions gitnexus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 19 additions & 9 deletions gitnexus/src/cli/ai-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ interface RepoStats {
processes?: number;
}

export interface AIContextOptions {
skipAgentsMd?: boolean;
}

const GITNEXUS_START_MARKER = '<!-- gitnexus:start -->';
const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';

Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions gitnexus/src/cli/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -237,6 +240,7 @@ export const analyzeCommand = async (inputPath?: string, options?: AnalyzeOption
processes: s.processes,
},
skillResult.skills,
{ skipAgentsMd: options?.skipAgentsMd },
);
}
} catch {
Expand Down
1 change: 1 addition & 0 deletions gitnexus/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
25 changes: 17 additions & 8 deletions gitnexus/src/core/run-analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down
28 changes: 28 additions & 0 deletions gitnexus/test/unit/ai-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
3 changes: 2 additions & 1 deletion gitnexus/test/unit/skip-git-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ 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',
timeout: 10000,
});

expect(helpOutput).toContain('--skip-git');
expect(helpOutput).toContain('--skip-agents-md');
expect(helpOutput).not.toContain('--no-git');
});

Expand Down
Loading