From 4c54a2e4cdefa41b50b818507663f3a63c878d7c Mon Sep 17 00:00:00 2001 From: luyua9 Date: Thu, 13 Aug 2026 07:03:31 +0800 Subject: [PATCH 1/2] fix(cli): preserve keep-marker stats prefix --- gitnexus/src/cli/ai-context.ts | 12 ++++++----- gitnexus/test/unit/ai-context.test.ts | 29 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/gitnexus/src/cli/ai-context.ts b/gitnexus/src/cli/ai-context.ts index 258aeb46c..c6b098807 100644 --- a/gitnexus/src/cli/ai-context.ts +++ b/gitnexus/src/cli/ai-context.ts @@ -369,9 +369,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 @@ -379,9 +376,14 @@ 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)) { + 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. diff --git a/gitnexus/test/unit/ai-context.test.ts b/gitnexus/test/unit/ai-context.test.ts index d8cf4b208..418274073 100644 --- a/gitnexus/test/unit/ai-context.test.ts +++ b/gitnexus/test/unit/ai-context.test.ts @@ -1496,4 +1496,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, 'NewName', { + 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 **NewName** (55 symbols, 66 relationships, 7 execution flows). Custom.', + ); + } finally { + await fs.rm(dir, { recursive: true, force: true }); + } + }); }); From 7b91fe3247705867b04e693eb7e6dfb11bcf9f04 Mon Sep 17 00:00:00 2001 From: zuyua9 <948423378@qq.com> Date: Thu, 13 Aug 2026 11:58:33 +0800 Subject: [PATCH 2/2] fix: preserve literal dollar signs in stats names --- gitnexus/src/cli/ai-context.ts | 2 +- gitnexus/test/unit/ai-context.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gitnexus/src/cli/ai-context.ts b/gitnexus/src/cli/ai-context.ts index c6b098807..dec8f420d 100644 --- a/gitnexus/src/cli/ai-context.ts +++ b/gitnexus/src/cli/ai-context.ts @@ -384,7 +384,7 @@ async function upsertGitNexusSection( const statsLine = noStats ? `${statsPrefix} **${projectName}**` : `${statsPrefix} **${projectName}** (${newStatsInner})`; - const updatedSection = existingSection.replace(statsPattern, statsLine); + 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 418274073..75056a836 100644 --- a/gitnexus/test/unit/ai-context.test.ts +++ b/gitnexus/test/unit/ai-context.test.ts @@ -1511,7 +1511,7 @@ indexed by GitNexus as **OldName** (10 symbols, 20 relationships, 3 execution fl `; await fs.writeFile(path.join(dir, 'CLAUDE.md'), original, 'utf-8'); - const result = await generateAIContextFiles(dir, storage, 'NewName', { + const result = await generateAIContextFiles(dir, storage, 'Repo$1Name', { nodes: 55, edges: 66, processes: 7, @@ -1519,7 +1519,7 @@ indexed by GitNexus as **OldName** (10 symbols, 20 relationships, 3 execution fl expect(result.files).toContain('CLAUDE.md (updated)'); expect(await fs.readFile(path.join(dir, 'CLAUDE.md'), 'utf-8')).toContain( - 'indexed by GitNexus as **NewName** (55 symbols, 66 relationships, 7 execution flows). Custom.', + 'indexed by GitNexus as **Repo$1Name** (55 symbols, 66 relationships, 7 execution flows). Custom.', ); } finally { await fs.rm(dir, { recursive: true, force: true });