From df38e5b48e1924426e91081068ca7b7f0f87b6f7 Mon Sep 17 00:00:00 2001 From: Sunil Srivatsa Date: Sat, 1 Aug 2026 13:24:11 -0400 Subject: [PATCH] fix(coding-agent): rebind relative extension roots --- .../src/discovery/omp-extension-roots.ts | 15 +++++++++++---- .../test/discovery/omp-plugins.test.ts | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/coding-agent/src/discovery/omp-extension-roots.ts b/packages/coding-agent/src/discovery/omp-extension-roots.ts index c9345cb31..f2c32eabb 100644 --- a/packages/coding-agent/src/discovery/omp-extension-roots.ts +++ b/packages/coding-agent/src/discovery/omp-extension-roots.ts @@ -36,6 +36,8 @@ export interface OmpExtensionRoot { interface InjectedRoot { path: string; + /** Relative CLI spelling, rebound against the active project on discovery. */ + relativePath?: string; level: "user" | "project"; } @@ -73,13 +75,16 @@ export function injectOmpExtensionCliRoots( if (paths.length === 0) return; const expanded = paths.map(raw => { const tilde = expandTilde(raw, home); - return path.isAbsolute(tilde) ? tilde : path.resolve(cwd, tilde); + return { + path: path.isAbsolute(tilde) ? tilde : path.resolve(cwd, tilde), + relativePath: path.isAbsolute(tilde) ? undefined : tilde, + }; }); const merged = new Map(); for (const root of injectedCliRoots) merged.set(root.path, root); - for (const resolved of expanded) { + for (const { path: resolved, relativePath } of expanded) { // CLI scope mirrors how `--extension` is treated elsewhere — user-level overrides win. - if (!merged.has(resolved)) merged.set(resolved, { path: resolved, level: "user" }); + if (!merged.has(resolved)) merged.set(resolved, { path: resolved, relativePath, level: "user" }); } injectedCliRoots = [...merged.values()]; } @@ -154,7 +159,9 @@ async function isDirectory(p: string): Promise { * other sources still surface. */ export async function listOmpExtensionRoots(ctx: LoadContext): Promise { - let candidates: InjectedRoot[] = [...injectedCliRoots]; + let candidates: InjectedRoot[] = injectedCliRoots.map(root => + root.relativePath ? { ...root, path: path.resolve(ctx.cwd, root.relativePath) } : root, + ); if (injectedCliRootMode === "merge") { const { project, user } = scopeDirs(ctx); const [projectExtensions, userExtensions, installedPlugins] = await Promise.all([ diff --git a/packages/coding-agent/test/discovery/omp-plugins.test.ts b/packages/coding-agent/test/discovery/omp-plugins.test.ts index dc85124f6..f4320d546 100644 --- a/packages/coding-agent/test/discovery/omp-plugins.test.ts +++ b/packages/coding-agent/test/discovery/omp-plugins.test.ts @@ -158,6 +158,24 @@ test("`--extension` CLI injection is wired through the same provider", async () expect(tools.map(t => t.name)).toEqual(expect.arrayContaining(["wcount", "deep-tool"])); }); +test("relative CLI roots rebind when resume switches projects", async () => { + const relativeRoot = "relative-extension"; + const launchRoot = path.join(project, relativeRoot); + const destination = path.join(tempDir, "destination"); + const destinationRoot = path.join(destination, relativeRoot); + buildExtensionPackage(launchRoot, "launch-skill"); + buildExtensionPackage(destinationRoot, "destination-skill"); + + injectOmpExtensionCliRoots([`./${relativeRoot}`], home, project); + + const destinationContext = { cwd: destination, home, repoRoot: destination }; + const roots = await listOmpExtensionRoots(destinationContext); + const skills = await loadFromPlugin<{ name: string }>(skillCapability.id, destinationContext); + expect(roots.map(root => root.path)).toEqual([destinationRoot]); + expect(skills.map(skill => skill.name)).toContain("destination-skill"); + expect(skills.map(skill => skill.name)).not.toContain("launch-skill"); +}); + test("explicit-only CLI roots replace stale state and exclude every ambient package source", async () => { const stale = path.join(tempDir, "stale-extension"); const projectExt = path.join(tempDir, "project-extension");