Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixes

- Indexing a project that contains only config-style files (YAML, Twig, or `.properties`) no longer misleadingly reports "No files found to index" — these files are tracked at the file level and are now counted as indexed. Thanks @luojiyin1987 (#357).
- Codex installs and uninstalls now preserve TOML array-of-table sections that appear after the CodeGraph MCP entry, so unrelated user configuration is no longer removed.

## [0.9.7] - 2026-05-28

Expand Down
38 changes: 38 additions & 0 deletions __tests__/installer-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,44 @@ describe('Installer targets — TOML serializer (Codex backbone)', () => {
expect(content.match(/\[\[foo\]\]/g)?.length).toBe(2);
expect(content).toContain('[mcp_servers.codegraph]');
});

it('upsert preserves a following array-of-tables sibling [[foo]]', () => {
const existing = [
'[mcp_servers.codegraph]',
'command = "old-codegraph"',
'args = ["old"]',
'',
'[[foo]]',
'name = "a"',
'',
'[[foo]]',
'name = "b"',
'',
].join('\n');
const block = buildTomlTable('mcp_servers.codegraph', { command: 'codegraph', args: ['serve'] });
const { content, action } = upsertTomlTable(existing, 'mcp_servers.codegraph', block);
expect(action).toBe('replaced');
expect(content.match(/\[\[foo\]\]/g)?.length).toBe(2);
expect(content).toContain('name = "a"');
expect(content).toContain('name = "b"');
});

it('removeTomlTable preserves a following array-of-tables sibling [[foo]]', () => {
const existing = [
'[mcp_servers.codegraph]',
'command = "codegraph"',
'args = ["serve"]',
'',
'[[foo]]',
'name = "keep-me"',
'',
].join('\n');
const { content, action } = removeTomlTable(existing, 'mcp_servers.codegraph');
expect(action).toBe('removed');
expect(content).toContain('[[foo]]');
expect(content).toContain('name = "keep-me"');
expect(content).not.toContain('mcp_servers.codegraph');
});
});

describe('Installer — uninstallTargets sweep (codegraph uninstall)', () => {
Expand Down
31 changes: 10 additions & 21 deletions src/installer/targets/toml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
*
* Strategy: treat the file as text. Find the `[mcp_servers.codegraph]`
* header line, splice it (and the lines that follow it until the next
* `[...]` header or EOF) in or out. Everything outside that block is
* preserved verbatim, byte-for-byte.
* `[...]` or `[[...]]` header or EOF) in or out. Everything outside
* that block is preserved verbatim, byte-for-byte.
*
* Limitations (acceptable for our narrow use):
* - Only handles top-level table headers; not array-of-tables or
* subtables nested inside `[mcp_servers]` itself (we always write
* the full dotted key `[mcp_servers.codegraph]`).
* - Only edits top-level table headers. Array-of-tables and subtables
* nested inside `[mcp_servers]` are preserved as opaque siblings
* (we always write the full dotted key `[mcp_servers.codegraph]`).
* - Doesn't validate sibling TOML — if the file is malformed
* elsewhere, our injection won't fix it but won't make it worse.
* - Quotes string values with double quotes; escapes `\` and `"`.
Expand Down Expand Up @@ -133,22 +133,11 @@ function findHeaderIndex(content: string, headerLine: string): number {
}

/**
* Find the byte index of the next top-level `[...]` table header
* (excluding array-of-tables `[[...]]`) starting from `from`, or
* return content length when none.
* Find the byte index of the next top-level `[...]` or `[[...]]`
* table header starting from `from`, or return content length when
* none.
*/
function findNextTableHeader(content: string, from: number): number {
// Look for "\n[" but skip "\n[[" (array of tables).
let i = from;
while (i < content.length) {
const nlIdx = content.indexOf('\n[', i);
if (nlIdx === -1) return content.length;
if (content[nlIdx + 2] === '[') {
// [[...]] — keep searching past it.
i = nlIdx + 2;
continue;
}
return nlIdx + 1;
}
return content.length;
const nlIdx = content.indexOf('\n[', from);
return nlIdx === -1 ? content.length : nlIdx + 1;
}