diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ab704d..3176285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixes +- On Windows, a console window no longer briefly flashes when CodeGraph runs as a background MCP server. When the npm launcher started the bundled runtime — which happens every time an editor starts the server or reconnects after the daemon idles out — and during its self-heal step that extracts a missing platform bundle, Windows would pop up a black console (conhost) window for a moment. Both now launch hidden, matching how the daemon already behaved; the browser-open step of `codegraph login` was hardened the same way. Thanks @luoyerr for the report and root-cause analysis. (#1092) - C++ forward declarations no longer crowd out the real class definition. A `class Foo;` forward declaration — common in large C++ and Unreal Engine codebases, where a heavily used class is forward-declared across dozens of headers — was indexed as its own class node every time it appeared. So exploring that class returned mostly forward-declaration sites, and could even pick one of them as the representative for blast-radius, burying the actual definition and its members and callers. Bodiless forward declarations are now skipped for C and C++, exactly as forward-declared structs and enums already were, so only the real definition is indexed. Languages where a class with no body is a complete definition — such as Kotlin's `class Empty` and Scala — are unaffected. Thanks @luoyxy for the report and root-cause analysis. (#1093) - C++ methods that return a reference, and user-defined conversion operators, are now indexed under their correct names. An inline getter like `const FGameplayTagContainer& GetActiveTags() const` — everywhere in Unreal Engine headers — was indexed as `& GetActiveTags() const` instead of `GetActiveTags`, and a conversion operator like `operator EALSMovementState() const` kept its trailing `() const` instead of reading `operator EALSMovementState`. In both cases the garbled name meant you couldn't find the symbol by name and its callers weren't linked. Both now read cleanly, matching how pointer-returning and value-returning methods already worked. (#1096) - C++ functions written with an inline-specifier macro before the return type are now indexed correctly. In Unreal Engine, inline helpers are commonly written `FORCEINLINE FString GetEnumerationToString(...)`; the `FORCEINLINE` macro made the parser read the return type as part of the function's name (`FString GetEnumerationToString` instead of `GetEnumerationToString`) and lose the real return type, so the function couldn't be found by name and its callers weren't linked. CodeGraph now recognizes the standard Unreal inline macros (`FORCEINLINE`, `FORCENOINLINE`, `FORCEINLINE_DEBUGGABLE`), so both the name and the return type are captured. (#1100) diff --git a/__tests__/npm-shim.test.ts b/__tests__/npm-shim.test.ts index bcb109f..39e7f91 100644 --- a/__tests__/npm-shim.test.ts +++ b/__tests__/npm-shim.test.ts @@ -71,6 +71,23 @@ function runShim(pkgDir: string, args: string[], env: Record) { }); } +// Static source guard (all platforms): every child spawn in the shim must set +// windowsHide, or a console (conhost) window flashes when the shim runs as a +// background MCP server on Windows (issue #1092). windowsHide is a Windows-only +// spawn behavior that can't be observed from these POSIX-only subprocess tests, +// so we assert it at the source level instead — and this also catches any new +// spawn site added to the shim later. +describe('npm-shim windowsHide (#1092)', () => { + it('sets windowsHide: true on every spawn in the shim', () => { + const src = fs.readFileSync(SHIM_SRC, 'utf8'); + const spawnLines = src.split('\n').filter((l) => /\.spawn(Sync)?\(/.test(l)); + expect(spawnLines.length).toBeGreaterThan(0); // guard against a false pass if the calls move + for (const line of spawnLines) { + expect(line, `spawn without windowsHide: ${line.trim()}`).toContain('windowsHide: true'); + } + }); +}); + describe.skipIf(isWindows)('npm-shim launcher', () => { it('runs the installed optional-dependency bundle without any download', async () => { const pkg = makePkg(); diff --git a/scripts/npm-shim.js b/scripts/npm-shim.js index 8a756f5..3f2c2f3 100755 --- a/scripts/npm-shim.js +++ b/scripts/npm-shim.js @@ -45,7 +45,7 @@ async function main() { // Happy path: the npm-installed optional dependency. Fall back to a download // when the registry didn't deliver it. var resolved = resolveInstalledBundle() || (await selfHealBundle()); - var res = childProcess.spawnSync(resolved.command, resolved.args, { stdio: 'inherit' }); + var res = childProcess.spawnSync(resolved.command, resolved.args, { stdio: 'inherit', windowsHide: true }); if (res.error) { process.stderr.write('codegraph: ' + res.error.message + '\n'); process.exit(1); @@ -222,7 +222,7 @@ function extract(archive, destDir) { var args = isWindows ? ['-xf', archive, '-C', destDir, '--strip-components=1'] : ['-xzf', archive, '-C', destDir, '--strip-components=1']; - var res = childProcess.spawnSync('tar', args, { stdio: 'ignore' }); + var res = childProcess.spawnSync('tar', args, { stdio: 'ignore', windowsHide: true }); if (res.error) throw new Error('tar unavailable: ' + res.error.message); if (res.status !== 0) throw new Error('tar exited ' + res.status); } diff --git a/src/reasoning/login.ts b/src/reasoning/login.ts index 15061fa..70faf18 100644 --- a/src/reasoning/login.ts +++ b/src/reasoning/login.ts @@ -80,7 +80,7 @@ export async function openBrowser(url: string): Promise { : process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]] : ['xdg-open', [url]]; try { - const child = spawn(cmd as string, args as string[], { stdio: 'ignore', detached: true }); + const child = spawn(cmd as string, args as string[], { stdio: 'ignore', detached: true, windowsHide: true }); child.on('error', () => {}); child.unref(); } catch { diff --git a/src/upgrade/index.ts b/src/upgrade/index.ts index a949440..e98f5ff 100644 --- a/src/upgrade/index.ts +++ b/src/upgrade/index.ts @@ -546,7 +546,7 @@ export function hasCommand(cmd: string): boolean { } export function defaultRun(cmd: string, args: string[], env?: NodeJS.ProcessEnv): number { - const r = spawnSync(cmd, args, { stdio: 'inherit', env: env ?? process.env }); + const r = spawnSync(cmd, args, { stdio: 'inherit', env: env ?? process.env, windowsHide: true }); if (r.error) return -1; return r.status ?? -1; }