diff --git a/gitnexus/src/cli/ai-context.ts b/gitnexus/src/cli/ai-context.ts index 89bf912f0..32295769e 100644 --- a/gitnexus/src/cli/ai-context.ts +++ b/gitnexus/src/cli/ai-context.ts @@ -381,9 +381,6 @@ async function upsertGitNexusSection( // drop the parenthetical but still refresh the project name so renames // propagate. const newStatsInner = `${stats.nodes || 0} symbols, ${stats.edges || 0} relationships, ${stats.processes || 0} execution flows`; - const statsLine = noStats - ? `Indexed as **${projectName}**` - : `Indexed as **${projectName}** (${newStatsInner})`; // Match either canonical phrasing at line start (`^` with `m` flag) so we // cannot replace prose embedded mid-paragraph. Deliberately no `$`: text @@ -391,10 +388,15 @@ async function upsertGitNexusSection( // The parenthetical is optional so a count-free line left by a prior // --no-stats run still matches — letting the name refresh, and letting // counts return if --no-stats is later dropped. - const statsPattern = /^(?:Indexed as|indexed by GitNexus as) \*\*[^*]+\*\*(?: \([^)]+\))?/m; + const statsPattern = /^(Indexed as|indexed by GitNexus as) \*\*[^*]+\*\*(?: \([^)]+\))?/m; + const statsMatch = existingSection.match(statsPattern); - if (statsPattern.test(existingSection)) { - const updatedSection = existingSection.replace(statsPattern, statsLine); + if (statsMatch) { + const statsPrefix = statsMatch[1]; + const statsLine = noStats + ? `${statsPrefix} **${projectName}**` + : `${statsPrefix} **${projectName}** (${newStatsInner})`; + const updatedSection = existingSection.replace(statsPattern, () => statsLine); // Count-only delta — leave the committed lean block alone (#2907). A // project rename, or --no-stats dropping the parenthetical, still writes. if (stripVolatileCounts(updatedSection) === stripVolatileCounts(existingSection)) { diff --git a/gitnexus/test/unit/ai-context.test.ts b/gitnexus/test/unit/ai-context.test.ts index a7174e674..d35236974 100644 --- a/gitnexus/test/unit/ai-context.test.ts +++ b/gitnexus/test/unit/ai-context.test.ts @@ -1722,4 +1722,33 @@ Indexed as **${name}**${counts}. Lean block. await fs.rm(dir, { recursive: true, force: true }); } }); + + it('preserves the existing stats-line prefix when updating a keep marker', async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-keep-prefix-')); + const storage = path.join(dir, '.gitnexus'); + await fs.mkdir(storage, { recursive: true }); + try { + const original = `# Guide + + + +indexed by GitNexus as **OldName** (10 symbols, 20 relationships, 3 execution flows). Custom. + +`; + await fs.writeFile(path.join(dir, 'CLAUDE.md'), original, 'utf-8'); + + const result = await generateAIContextFiles(dir, storage, 'Repo$1Name', { + nodes: 55, + edges: 66, + processes: 7, + }); + + expect(result.files).toContain('CLAUDE.md (updated)'); + expect(await fs.readFile(path.join(dir, 'CLAUDE.md'), 'utf-8')).toContain( + 'indexed by GitNexus as **Repo$1Name** (55 symbols, 66 relationships, 7 execution flows). Custom.', + ); + } finally { + await fs.rm(dir, { recursive: true, force: true }); + } + }); });