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:
@@ -910,6 +910,12 @@ export class InteractiveMode {
|
|||||||
return result;
|
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 {
|
private formatContextPath(p: string): string {
|
||||||
const cwd = path.resolve(this.sessionManager.getCwd());
|
const cwd = path.resolve(this.sessionManager.getCwd());
|
||||||
const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);
|
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[] {
|
private getCompactExtensionLabels(extensions: Array<{ path: string; sourceInfo?: SourceInfo }>): string[] {
|
||||||
const nonPackageExtensions = extensions
|
const nonPackageExtensions = extensions
|
||||||
.map((extension) => ({
|
.map((extension) => {
|
||||||
path: extension.path,
|
const segments = this.getCompactDisplayPathSegments(extension.path);
|
||||||
sourceInfo: extension.sourceInfo,
|
const lastSegment = segments[segments.length - 1];
|
||||||
segments: this.getCompactDisplayPathSegments(extension.path),
|
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));
|
.filter((extension) => !this.isPackageSource(extension.sourceInfo));
|
||||||
|
|
||||||
return extensions.map((extension) => {
|
return extensions.map((extension) => {
|
||||||
@@ -1373,8 +1386,9 @@ export class InteractiveMode {
|
|||||||
if (extensions.length > 0) {
|
if (extensions.length > 0) {
|
||||||
const groups = this.buildScopeGroups(extensions);
|
const groups = this.buildScopeGroups(extensions);
|
||||||
const extList = this.formatScopeGroups(groups, {
|
const extList = this.formatScopeGroups(groups, {
|
||||||
formatPath: (item) => this.formatDisplayPath(item.path),
|
formatPath: (item) => this.formatExtensionDisplayPath(item.path),
|
||||||
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
formatPackagePath: (item) =>
|
||||||
|
this.formatExtensionDisplayPath(this.getShortPath(item.path, item.sourceInfo)),
|
||||||
});
|
});
|
||||||
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));
|
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));
|
||||||
addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading");
|
addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading");
|
||||||
|
|||||||
@@ -262,6 +262,8 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
formatDisplayPath: (p: string) => (InteractiveMode as any).prototype.formatDisplayPath.call(fakeThis, p),
|
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),
|
formatContextPath: (p: string) => (InteractiveMode as any).prototype.formatContextPath.call(fakeThis, p),
|
||||||
getStartupExpansionState: () => (InteractiveMode as any).prototype.getStartupExpansionState.call(fakeThis),
|
getStartupExpansionState: () => (InteractiveMode as any).prototype.getStartupExpansionState.call(fakeThis),
|
||||||
buildScopeGroups: () => [],
|
buildScopeGroups: () => [],
|
||||||
@@ -482,7 +484,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[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", () => {
|
test("adds more parent folders until local extension labels are unique", () => {
|
||||||
@@ -528,9 +530,231 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[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", () => {
|
test("captures mixed extension layouts in expanded output", () => {
|
||||||
const fakeThis = createShowLoadedResourcesThis({
|
const fakeThis = createShowLoadedResourcesThis({
|
||||||
quietStartup: false,
|
quietStartup: false,
|
||||||
@@ -547,16 +771,16 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
"[Extensions]
|
"[Extensions]
|
||||||
project
|
project
|
||||||
/tmp/project/.pi/extensions/answer.ts
|
/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
|
git:github.com/HazAT/pi-interactive-subagents
|
||||||
extensions/index.ts
|
extensions
|
||||||
extensions/subagents/index.ts
|
extensions/subagents
|
||||||
npm:@scope/pi-scoped
|
npm:@scope/pi-scoped
|
||||||
extensions/index.ts
|
extensions
|
||||||
npm:pi-markdown-preview
|
npm:pi-markdown-preview
|
||||||
extensions/index.ts
|
extensions
|
||||||
user
|
user
|
||||||
/tmp/agent/extensions/user-index/index.ts
|
/tmp/agent/extensions/user-index
|
||||||
path
|
path
|
||||||
/tmp/temp/cli-extension.ts"`);
|
/tmp/temp/cli-extension.ts"`);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user