diff --git a/gitnexus/src/cli/analyze.ts b/gitnexus/src/cli/analyze.ts index e7deb0add..da67eda69 100644 --- a/gitnexus/src/cli/analyze.ts +++ b/gitnexus/src/cli/analyze.ts @@ -154,6 +154,16 @@ export interface AnalyzeOptions { embeddingDevice?: string; } +/** + * Whether community skill files (`--skills`) should run after indexing. + * Kept as a pure helper so the `--index-only --skills` contract is unit-tested + * without booting the full analyze pipeline (#742 review). + */ +export const shouldGenerateCommunitySkillFiles = ( + options: Pick | undefined, + pipelineResult: unknown, +): boolean => Boolean(options?.skills && pipelineResult && !options?.indexOnly); + export const analyzeCommand = async (inputPath?: string, options?: AnalyzeOptions) => { if (ensureHeap()) return; @@ -465,10 +475,9 @@ export const analyzeCommand = async (inputPath?: string, options?: AnalyzeOption await assertAnalysisFinalized(repoPath); // Skill generation (CLI-only, uses pipeline result from analysis). - // Gated by !skipAll so `--index-only --skills` truly skips ALL file - // injection — otherwise `generateSkillFiles()` would still write - // community-derived skill files to .claude/skills/generated/. - if (options?.skills && result.pipelineResult && !skipAll) { + // Gated so `--index-only --skills` skips community skill writes too + // (`shouldGenerateCommunitySkillFiles` — see unit test). + if (shouldGenerateCommunitySkillFiles(options, result.pipelineResult)) { updateBar(99, 'Generating skill files...'); try { const { generateSkillFiles } = await import('./skill-gen.js'); diff --git a/gitnexus/src/cli/index.ts b/gitnexus/src/cli/index.ts index 47d933c21..c8f3e9464 100644 --- a/gitnexus/src/cli/index.ts +++ b/gitnexus/src/cli/index.ts @@ -38,7 +38,9 @@ program .option('--no-stats', 'Omit volatile file/symbol counts from AGENTS.md and CLAUDE.md') .option( '--skip-skills', - 'Skip installing standard GitNexus skill files to .claude/skills/gitnexus/', + 'Skip installing standard GitNexus skill files under .claude/skills/gitnexus/. ' + + 'Does not suppress community skills from --skills (those use .claude/skills/generated/). ' + + 'Use --index-only to skip all AI-context file injection.', ) .option('--index-only', 'Pure index mode: skip all file injection (AGENTS.md, CLAUDE.md, skills)') .option( diff --git a/gitnexus/test/unit/analyze-community-skills-gate.test.ts b/gitnexus/test/unit/analyze-community-skills-gate.test.ts new file mode 100644 index 000000000..35eaa5c77 --- /dev/null +++ b/gitnexus/test/unit/analyze-community-skills-gate.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest'; +import { shouldGenerateCommunitySkillFiles } from '../../src/cli/analyze.js'; + +describe('shouldGenerateCommunitySkillFiles (#742 / PR 1485)', () => { + it('is false when --index-only is set even if --skills and pipelineResult are present', () => { + expect(shouldGenerateCommunitySkillFiles({ skills: true, indexOnly: true }, { ok: true })).toBe( + false, + ); + }); + + it('is false when pipelineResult is missing', () => { + expect(shouldGenerateCommunitySkillFiles({ skills: true, indexOnly: false }, null)).toBe(false); + expect(shouldGenerateCommunitySkillFiles({ skills: true }, undefined)).toBe(false); + }); + + it('is true when --skills is set, pipeline exists, and not index-only', () => { + expect( + shouldGenerateCommunitySkillFiles({ skills: true, indexOnly: false }, { communities: [] }), + ).toBe(true); + expect(shouldGenerateCommunitySkillFiles({ skills: true }, { x: 1 })).toBe(true); + }); + + it('is false when --skills is omitted', () => { + expect(shouldGenerateCommunitySkillFiles({ indexOnly: false }, { x: 1 })).toBe(false); + expect(shouldGenerateCommunitySkillFiles(undefined, { x: 1 })).toBe(false); + }); +}); diff --git a/gitnexus/test/unit/skip-git-cli.test.ts b/gitnexus/test/unit/skip-git-cli.test.ts index 069a9686f..6bb9e28cc 100644 --- a/gitnexus/test/unit/skip-git-cli.test.ts +++ b/gitnexus/test/unit/skip-git-cli.test.ts @@ -17,6 +17,8 @@ describe('--skip-git CLI flag', () => { expect(helpOutput).toContain('--skip-git'); expect(helpOutput).toContain('--skip-agents-md'); + expect(helpOutput).toContain('--skip-skills'); + expect(helpOutput).toContain('--index-only'); expect(helpOutput).not.toContain('--no-git'); });