From 1d30806f3e8721412098dee729df8335cf23054d Mon Sep 17 00:00:00 2001 From: JirA44 Date: Thu, 28 May 2026 11:21:45 +0200 Subject: [PATCH] fix(hermes): detect sibling list-item indent when creating new cli block When platform_toolsets already exists but has no cli key, the previous code unconditionally used 4-space item indent, producing mixed indentation if sibling sections (discord, telegram, etc.) used the 2-space PyYAML-default style. Add detectItemIndent() to scan existing list items in the parent range and return their actual leading whitespace, then use that in the !cli fallback so the new block matches whatever style is already present. Fixes #507 --- __tests__/installer-targets.test.ts | 32 +++++++++++++++++++++++++++++ src/installer/targets/hermes.ts | 11 +++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/__tests__/installer-targets.test.ts b/__tests__/installer-targets.test.ts index 697f8e976..b08f0857b 100644 --- a/__tests__/installer-targets.test.ts +++ b/__tests__/installer-targets.test.ts @@ -793,6 +793,38 @@ describe('Installer targets — partial-state idempotency', () => { expect(body).toContain(' telegram:\n - hermes-telegram'); }); + it('hermes: install matches existing sibling list-item indent when creating new cli block (issue #507)', () => { + const hermes = getTarget('hermes')!; + const config = path.join(tmpHome, '.hermes', 'config.yaml'); + fs.mkdirSync(path.dirname(config), { recursive: true }); + // platform_toolsets exists but has no cli key; siblings use 2-space (PyYAML) item style + const original = [ + 'platform_toolsets:', + ' discord:', + ' - hermes-discord', + ' telegram:', + ' - hermes-telegram', + '', + ].join('\n'); + fs.writeFileSync(config, original); + + hermes.install('global', { autoAllow: true }); + const body = fs.readFileSync(config, 'utf-8'); + + // New cli block must use the same 2-space item indent as discord/telegram + expect(body).toContain(' cli:\n - hermes-cli\n - mcp-codegraph'); + // Existing sections untouched + expect(body).toContain(' discord:\n - hermes-discord'); + expect(body).toContain(' telegram:\n - hermes-telegram'); + // No 4-space item indent leaked in + expect(body).not.toMatch(/^ {4}- hermes-cli/m); + expect(body).not.toMatch(/^ {4}- mcp-codegraph/m); + + // Idempotent + const second = hermes.install('global', { autoAllow: true }); + expect(second.files[0]?.action).toBe('unchanged'); + }); + it('opencode: uninstall removes only mcp.codegraph, preserves comments and siblings', () => { const opencode = getTarget('opencode')!; const dir = path.join(tmpHome, '.config', 'opencode'); diff --git a/src/installer/targets/hermes.ts b/src/installer/targets/hermes.ts index eacab6d3d..b55c7d7d8 100644 --- a/src/installer/targets/hermes.ts +++ b/src/installer/targets/hermes.ts @@ -305,6 +305,14 @@ function removeCodeGraphMcpServer(content: string): string { return joinLines(lines); } +function detectItemIndent(lines: string[], range: LineRange): string { + for (let i = range.start + 1; i < range.end; i++) { + const m = (lines[i] ?? '').match(/^( +)- /); + if (m && m[1]) return m[1]; + } + return ' '; +} + function upsertCodeGraphToolset(content: string): string { const lines = splitLines(content); const parent = topLevelRange(lines, 'platform_toolsets'); @@ -318,7 +326,8 @@ function upsertCodeGraphToolset(content: string): string { } if (!cli) { - lines.splice(parent.end, 0, ' cli:', ' - hermes-cli', ' - mcp-codegraph'); + const itemIndent = detectItemIndent(lines, parent); + lines.splice(parent.end, 0, ' cli:', `${itemIndent}- hermes-cli`, `${itemIndent}- mcp-codegraph`); return joinLines(lines); }