fix(coding-agent): strip trailing index.js|ts from extension labels in startup banner (#3596)

Strip `index.ts` and `index.js` from extension display paths in both
compact and expanded views of the startup banner. This makes the list more
readable by removing redundant filename noise.

Changes:
- Add `formatExtensionDisplayPath()` helper to centralize path formatting
- Update `getCompactExtensionLabels()` to strip index files from segments
- Update expanded view formatting to use the new helper
- Add comprehensive tests for both views

Fixes #3549
Fixes #3559
This commit is contained in:
Aliou Diallo
2026-04-23 21:03:26 +02:00
committed by GitHub
parent ff220fa41d
commit 05e2e9d170
2 changed files with 253 additions and 15 deletions

View File

@@ -910,6 +910,12 @@ export class InteractiveMode {
return result;
}
private formatExtensionDisplayPath(path: string): string {
let result = this.formatDisplayPath(path);
result = result.replace(/\/index\.ts$/, "").replace(/\/index\.js$/, "");
return result;
}
private formatContextPath(p: string): string {
const cwd = path.resolve(this.sessionManager.getCwd());
const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);
@@ -1044,11 +1050,18 @@ export class InteractiveMode {
private getCompactExtensionLabels(extensions: Array<{ path: string; sourceInfo?: SourceInfo }>): string[] {
const nonPackageExtensions = extensions
.map((extension) => ({
path: extension.path,
sourceInfo: extension.sourceInfo,
segments: this.getCompactDisplayPathSegments(extension.path),
}))
.map((extension) => {
const segments = this.getCompactDisplayPathSegments(extension.path);
const lastSegment = segments[segments.length - 1];
if (segments.length > 1 && (lastSegment === "index.ts" || lastSegment === "index.js")) {
segments.pop();
}
return {
path: extension.path,
sourceInfo: extension.sourceInfo,
segments,
};
})
.filter((extension) => !this.isPackageSource(extension.sourceInfo));
return extensions.map((extension) => {
@@ -1373,8 +1386,9 @@ export class InteractiveMode {
if (extensions.length > 0) {
const groups = this.buildScopeGroups(extensions);
const extList = this.formatScopeGroups(groups, {
formatPath: (item) => this.formatDisplayPath(item.path),
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
formatPath: (item) => this.formatExtensionDisplayPath(item.path),
formatPackagePath: (item) =>
this.formatExtensionDisplayPath(this.getShortPath(item.path, item.sourceInfo)),
});
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));
addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading");

View File

@@ -262,6 +262,8 @@ describe("InteractiveMode.showLoadedResources", () => {
},
},
formatDisplayPath: (p: string) => (InteractiveMode as any).prototype.formatDisplayPath.call(fakeThis, p),
formatExtensionDisplayPath: (p: string) =>
(InteractiveMode as any).prototype.formatExtensionDisplayPath.call(fakeThis, p),
formatContextPath: (p: string) => (InteractiveMode as any).prototype.formatContextPath.call(fakeThis, p),
getStartupExpansionState: () => (InteractiveMode as any).prototype.getStartupExpansionState.call(fakeThis),
buildScopeGroups: () => [],
@@ -482,7 +484,7 @@ describe("InteractiveMode.showLoadedResources", () => {
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
"[Extensions]
@scope/pi-scoped, answer.ts, cli-extension.ts, HazAT/pi-interactive-subagents, HazAT/pi-interactive-subagents:subagents, local-index/index.ts, pi-markdown-preview, user-index/index.ts"`);
@scope/pi-scoped, answer.ts, cli-extension.ts, HazAT/pi-interactive-subagents, HazAT/pi-interactive-subagents:subagents, local-index, pi-markdown-preview, user-index"`);
});
test("adds more parent folders until local extension labels are unique", () => {
@@ -528,9 +530,231 @@ describe("InteractiveMode.showLoadedResources", () => {
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
"[Extensions]
alpha/one/index.ts, beta/one/index.ts, gamma/one/index.ts"`);
alpha/one, beta/one, gamma/one"`);
});
test("strips index.ts from local extension label, showing parent dir", () => {
const extensions: ExtensionFixture[] = [
{
path: "/tmp/extensions/plan-mode/index.ts",
sourceInfo: createSourceInfo("/tmp/extensions/plan-mode/index.ts", {
source: "local",
scope: "project",
origin: "top-level",
baseDir: "/tmp/extensions",
}),
},
];
const fakeThis = createShowLoadedResourcesThis({
quietStartup: false,
extensions,
useRealScopeGroups: true,
});
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
force: false,
});
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
"[Extensions]
plan-mode"`);
});
test("strips index.js from local extension label, showing parent dir", () => {
const extensions: ExtensionFixture[] = [
{
path: "/tmp/extensions/plan-mode/index.js",
sourceInfo: createSourceInfo("/tmp/extensions/plan-mode/index.js", {
source: "local",
scope: "project",
origin: "top-level",
baseDir: "/tmp/extensions",
}),
},
];
const fakeThis = createShowLoadedResourcesThis({
quietStartup: false,
extensions,
useRealScopeGroups: true,
});
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
force: false,
});
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
"[Extensions]
plan-mode"`);
});
test("mixed single-file and subdirectory index.ts extensions strip index.ts", () => {
const extensions: ExtensionFixture[] = [
{
path: "/tmp/extensions/webfetch.ts",
sourceInfo: createSourceInfo("/tmp/extensions/webfetch.ts", {
source: "local",
scope: "project",
origin: "top-level",
baseDir: "/tmp/extensions",
}),
},
{
path: "/tmp/extensions/plan-mode/index.ts",
sourceInfo: createSourceInfo("/tmp/extensions/plan-mode/index.ts", {
source: "local",
scope: "project",
origin: "top-level",
baseDir: "/tmp/extensions",
}),
},
];
const fakeThis = createShowLoadedResourcesThis({
quietStartup: false,
extensions,
useRealScopeGroups: true,
});
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
force: false,
});
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
"[Extensions]
plan-mode, webfetch.ts"`);
});
test("multiple index.ts with unique parent dirs need no disambiguation", () => {
const extensions: ExtensionFixture[] = [
{
path: "/tmp/extensions/foo/index.ts",
sourceInfo: createSourceInfo("/tmp/extensions/foo/index.ts", {
source: "local",
scope: "project",
origin: "top-level",
baseDir: "/tmp/extensions",
}),
},
{
path: "/tmp/extensions/bar/index.ts",
sourceInfo: createSourceInfo("/tmp/extensions/bar/index.ts", {
source: "local",
scope: "project",
origin: "top-level",
baseDir: "/tmp/extensions",
}),
},
];
const fakeThis = createShowLoadedResourcesThis({
quietStartup: false,
extensions,
useRealScopeGroups: true,
});
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
force: false,
});
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
"[Extensions]
bar, foo"`);
});
test("multiple index.ts with same parent dir name disambiguated with grandparent", () => {
const extensions: ExtensionFixture[] = [
{
path: "/tmp/alpha/tools/index.ts",
sourceInfo: createSourceInfo("/tmp/alpha/tools/index.ts", {
source: "cli",
scope: "temporary",
origin: "top-level",
baseDir: "/tmp/alpha",
}),
},
{
path: "/tmp/beta/tools/index.ts",
sourceInfo: createSourceInfo("/tmp/beta/tools/index.ts", {
source: "cli",
scope: "temporary",
origin: "top-level",
baseDir: "/tmp/beta",
}),
},
];
const fakeThis = createShowLoadedResourcesThis({
quietStartup: false,
extensions,
useRealScopeGroups: true,
});
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
force: false,
});
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
"[Extensions]
alpha/tools, beta/tools"`);
});
test("non-index file in subdirectory stays as filename", () => {
const extensions: ExtensionFixture[] = [
{
path: "/tmp/extensions/my-ext/main.ts",
sourceInfo: createSourceInfo("/tmp/extensions/my-ext/main.ts", {
source: "local",
scope: "project",
origin: "top-level",
baseDir: "/tmp/extensions",
}),
},
];
const fakeThis = createShowLoadedResourcesThis({
quietStartup: false,
extensions,
useRealScopeGroups: true,
});
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
force: false,
});
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
"[Extensions]
main.ts"`);
});
test("package extensions still strip index.ts correctly (regression guard)", () => {
const extensions: ExtensionFixture[] = [
{
path: "/tmp/project/.pi/npm/node_modules/pi-markdown-preview/extensions/index.ts",
sourceInfo: createSourceInfo("/tmp/project/.pi/npm/node_modules/pi-markdown-preview/extensions/index.ts", {
source: "npm:pi-markdown-preview",
scope: "project",
origin: "package",
baseDir: "/tmp/project/.pi/npm/node_modules/pi-markdown-preview",
}),
},
];
const fakeThis = createShowLoadedResourcesThis({
quietStartup: false,
extensions,
useRealScopeGroups: true,
});
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
force: false,
});
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
"[Extensions]
pi-markdown-preview"`);
});
test("captures mixed extension layouts in expanded output", () => {
const fakeThis = createShowLoadedResourcesThis({
quietStartup: false,
@@ -547,16 +771,16 @@ describe("InteractiveMode.showLoadedResources", () => {
"[Extensions]
project
/tmp/project/.pi/extensions/answer.ts
/tmp/project/.pi/extensions/local-index/index.ts
/tmp/project/.pi/extensions/local-index
git:github.com/HazAT/pi-interactive-subagents
extensions/index.ts
extensions/subagents/index.ts
extensions
extensions/subagents
npm:@scope/pi-scoped
extensions/index.ts
extensions
npm:pi-markdown-preview
extensions/index.ts
extensions
user
/tmp/agent/extensions/user-index/index.ts
/tmp/agent/extensions/user-index
path
/tmp/temp/cli-extension.ts"`);
});