diff --git a/gitnexus/src/cli/analyze.ts b/gitnexus/src/cli/analyze.ts index da67eda69..d5a7638f9 100644 --- a/gitnexus/src/cli/analyze.ts +++ b/gitnexus/src/cli/analyze.ts @@ -155,7 +155,15 @@ export interface AnalyzeOptions { } /** - * Whether community skill files (`--skills`) should run after indexing. + * Whether the post-index skill step should run. + * + * The gated block does two things in sequence: (1) generates the community + * skill files from `--skills`, and (2) re-runs `generateAIContextFiles` so + * AGENTS.md/CLAUDE.md can reference the freshly written skills. Both are + * suppressed together — `--index-only` drops the entire step, not just the + * community-skill write. Name retained for the test contract; see call site + * in `analyzeCommand` for the AGENTS.md/CLAUDE.md re-generation it also gates. + * * Kept as a pure helper so the `--index-only --skills` contract is unit-tested * without booting the full analyze pipeline (#742 review). */ @@ -259,6 +267,18 @@ export const analyzeCommand = async (inputPath?: string, options?: AnalyzeOption console.log('\n GitNexus Analyzer\n'); + // `--index-only` is the stronger contract — it suppresses every form of file + // injection, including community skill writes that `--skills` would normally + // produce. Surface the override explicitly so users don't wonder why a + // pipeline re-index ran but no skill files appeared. The pipeline still + // re-runs (see `force: options?.force || options?.skills` below); the warning + // is purely about the dropped post-index write step. + if (options?.indexOnly && options?.skills) { + console.log( + ' Note: --index-only overrides --skills; community skill files will not be written.\n', + ); + } + let repoPath: string; if (inputPath) { repoPath = path.resolve(inputPath); diff --git a/gitnexus/src/cli/index.ts b/gitnexus/src/cli/index.ts index c8f3e9464..d38675f03 100644 --- a/gitnexus/src/cli/index.ts +++ b/gitnexus/src/cli/index.ts @@ -33,7 +33,11 @@ program 'Drop existing embeddings on rebuild. By default, an `analyze` without `--embeddings` ' + 'preserves any embeddings already present in the index.', ) - .option('--skills', 'Generate repo-specific skill files from detected communities') + .option( + '--skills', + 'Generate repo-specific skill files from detected communities ' + + '(no-op when --index-only is also set).', + ) .option('--skip-agents-md', 'Skip updating the gitnexus section in AGENTS.md and CLAUDE.md') .option('--no-stats', 'Omit volatile file/symbol counts from AGENTS.md and CLAUDE.md') .option( diff --git a/gitnexus/test/unit/skip-git-cli.test.ts b/gitnexus/test/unit/skip-git-cli.test.ts index 6bb9e28cc..80c07ed17 100644 --- a/gitnexus/test/unit/skip-git-cli.test.ts +++ b/gitnexus/test/unit/skip-git-cli.test.ts @@ -22,6 +22,36 @@ describe('--skip-git CLI flag', () => { expect(helpOutput).not.toContain('--no-git'); }); + it('warns when --index-only overrides --skills (PR 1485)', () => { + // `--index-only` suppresses the post-index skill step that `--skills` + // would otherwise trigger. Without an explicit warning, the user sees a + // pipeline re-index complete and silently no skill files written — the + // silent-contradiction case flagged in PR 1485 review. + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gn-index-only-skills-')); + const gitnexusHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gn-index-only-skills-home-')); + // Make tmpDir a git repo so analyze accepts it without --skip-git. + execSync('git init', { cwd: tmpDir, stdio: 'ignore' }); + fs.writeFileSync(path.join(tmpDir, 'a.ts'), 'export const a = 1;\n'); + + const env = { + ...process.env, + HOME: gitnexusHome, + GITNEXUS_HOME: gitnexusHome, + GITNEXUS_LBUG_EXTENSION_INSTALL: 'never', + }; + + try { + const output = execSync( + `node "${cliPath}" analyze "${tmpDir}" --index-only --skills --skip-agents-md`, + { encoding: 'utf8', timeout: 60000, env }, + ); + expect(output).toContain('--index-only overrides --skills'); + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(gitnexusHome, { recursive: true, force: true }); + } + }); + it('rejects non-git folder without --skip-git', () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gn-no-git-')); fs.writeFileSync(path.join(tmpDir, 'test.ts'), 'export const x = 1;');