From c55ae2faa5d850e0e4650bd573f7f241b10e2e0b Mon Sep 17 00:00:00 2001 From: David Brailovsky Date: Wed, 22 Jul 2026 16:08:21 +0200 Subject: [PATCH] fix(coding-agent): display path of sibling dependent extensions (#6964) fixes: #6619 --- .../src/modes/interactive/interactive-mode.ts | 12 ++- .../test/interactive-mode-status.test.ts | 78 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 09bfd86d..294d60b9 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -1077,8 +1077,16 @@ export class InteractiveMode { * Get a short path relative to the package root for display. */ private getShortPath(fullPath: string, sourceInfo?: SourceInfo): string { + const normalizedFullPath = fullPath.replace(/\\/g, "/"); const baseDir = sourceInfo?.baseDir; if (baseDir && this.isPackageSource(sourceInfo)) { + const normalizedBaseDir = baseDir.replace(/\\/g, "/"); + const npmRootMatch = normalizedBaseDir.match(/^(.*\/node_modules)\/(@?[^/]+(?:\/[^/]+)?)$/); + // If fullPath is under the same node_modules root as baseDir, preserve that relative topology. + if (npmRootMatch?.[1] && normalizedFullPath.startsWith(`${npmRootMatch[1]}/`)) { + return path.posix.relative(normalizedBaseDir, normalizedFullPath); + } + const relativePath = path.relative(path.resolve(baseDir), path.resolve(fullPath)); if ( relativePath && @@ -1092,12 +1100,12 @@ export class InteractiveMode { } const source = sourceInfo?.source ?? ""; - const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/); + const npmMatch = normalizedFullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/); if (npmMatch && source.startsWith("npm:")) { return npmMatch[2]; } - const gitMatch = fullPath.match(/git\/[^/]+\/[^/]+\/(.*)/); + const gitMatch = normalizedFullPath.match(/git\/[^/]+\/[^/]+\/(.*)/); if (gitMatch && source.startsWith("git:")) { return gitMatch[1]; } diff --git a/packages/coding-agent/test/interactive-mode-status.test.ts b/packages/coding-agent/test/interactive-mode-status.test.ts index 54c615bc..19a7f9e9 100644 --- a/packages/coding-agent/test/interactive-mode-status.test.ts +++ b/packages/coding-agent/test/interactive-mode-status.test.ts @@ -1019,6 +1019,84 @@ describe("InteractiveMode.showLoadedResources", () => { "[Extensions] pi-markdown-preview"`); }); + + test("labels npm sibling extensions relative to the declaring package", () => { + const extensions: ExtensionFixture[] = [ + { + path: "/tmp/project/.pi/npm/node_modules/primary-package/index.ts", + sourceInfo: createSourceInfo("/tmp/project/.pi/npm/node_modules/primary-package/index.ts", { + source: "npm:primary-package", + scope: "project", + origin: "package", + baseDir: "/tmp/project/.pi/npm/node_modules/primary-package", + }), + }, + { + path: "/tmp/project/.pi/npm/node_modules/sibling-package/index.ts", + sourceInfo: createSourceInfo("/tmp/project/.pi/npm/node_modules/sibling-package/index.ts", { + source: "npm:primary-package", + scope: "project", + origin: "package", + baseDir: "/tmp/project/.pi/npm/node_modules/primary-package", + }), + }, + ]; + + const fakeThis = createShowLoadedResourcesThis({ + quietStartup: false, + extensions, + useRealScopeGroups: true, + }); + + (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { + force: false, + }); + + expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(` +"[Extensions] + primary-package, primary-package:../sibling-package"`); + }); + + test("labels Windows npm sibling extensions relative to the declaring package", () => { + const primaryPath = "C:\\Users\\me\\.pi\\agent\\npm\\node_modules\\primary-package\\index.ts"; + const siblingPath = "C:\\Users\\me\\.pi\\agent\\npm\\node_modules\\sibling-package\\index.ts"; + const baseDir = "C:\\Users\\me\\.pi\\agent\\npm\\node_modules\\primary-package"; + const extensions: ExtensionFixture[] = [ + { + path: primaryPath, + sourceInfo: createSourceInfo(primaryPath, { + source: "npm:primary-package", + scope: "user", + origin: "package", + baseDir, + }), + }, + { + path: siblingPath, + sourceInfo: createSourceInfo(siblingPath, { + source: "npm:primary-package", + scope: "user", + origin: "package", + baseDir, + }), + }, + ]; + + const fakeThis = createShowLoadedResourcesThis({ + quietStartup: false, + extensions, + useRealScopeGroups: true, + }); + + (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { + force: false, + }); + + expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(` +"[Extensions] + primary-package, primary-package:../sibling-package"`); + }); + test("captures mixed extension layouts in expanded output", () => { const fakeThis = createShowLoadedResourcesThis({ quietStartup: false,