fix(cli): warn when --index-only silently overrides --skills

Address review findings on PR 1485 follow-ups:
- analyze.ts emits a one-line note when both --index-only and --skills
  are set, so users see why a pipeline re-index ran with no skill files
  written.
- index.ts --skills help text now flags the --index-only override.
- shouldGenerateCommunitySkillFiles JSDoc documents the dual role of
  the gate (community skills + AGENTS.md/CLAUDE.md re-generation).
- skip-git-cli.test.ts pins the override-warning surface end-to-end.
This commit is contained in:
Gergo Magyar 2026-05-11 13:30:58 +01:00
parent cb4170d85e
commit 2140f6465a
3 changed files with 56 additions and 2 deletions

View file

@ -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);

View file

@ -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(

View file

@ -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;');