Repository navigation
fix(skills): match plugin workspace folders across Windows path spellings - #1883
Conversation
…ings isPluginWorkspace built its containment prefix with a literal "/" and compared spellings verbatim, but the two inputs arrive from different worlds: the plugin folder comes from a native dialog (backslashes on Windows) while the workspace comes from the host, which can persist a forward-slash or //?/X:/… spelling. On Windows no plugin path ever matched, so the bundled plugin-development skill was never offered for a workspace a plugin was already loaded from — the manifest.json probe only rescued the single-plugin-at-root layout. Containment now folds both sides (separators unified, Windows verbatim prefix stripped, case folded everywhere but Linux, like the worktree ref folding) in a dependency-free module so its boundary behavior — sibling prefixes like repo-x must not count — is tested directly.
|
The Windows separator defect is real, and I tested the new helper directly. It introduces a regression for a valid POSIX filesystem-root workspace: |
Folding a bare root by trimming its trailing separators turned "/" into the empty string, so pluginWorkspaceContains("/", "/tmp/plugins/example") answered false where the prefix check it replaced answered true. Preserve the POSIX root through the fold, compare every absolute path against it directly, and cover the root cases (/ and C:/) alongside the sibling boundary.
|
Good catch — fixed in The regression: The fix: a path made only of separators is now recognized as the filesystem root and preserved through the fold ( Coverage added (kept the sibling boundary test untouched):
|
|
The revised root normalization addresses the regression from the previous review. Independently ran all 18 plugin-workspace/plugin-skill tests and Desktop typecheck, and bundled/imported the production |
What
isPluginWorkspacenever matched on Windows, so the bundledpi-desktop/plugin-developmentskill was never offered for a workspace a plugin was already loaded from.Root cause
apps/desktop/electron/main/builtin-skills.tsbuilt its containment prefix with a literal/and compared spellings verbatim:The two inputs arrive from different worlds:
pluginPaths—plugins.listLoaded().map((loaded) => loaded.path), stored verbatim from the folder the user picked (dialog.showOpenDialog→ native backslashes on Windows).workspacePath— the host'ssession.get/workspace.getanswer, which can persist a forward-slash spelling or the//?/X:/…long-path variant (electron/main/path-utils.tsexists precisely because both spellings occur).Even with both sides natively backslashed, the
/-suffixed prefix (C:\dev\repo/) never matchedC:\dev\repo\plugins\my-plugin. On macOS/Linux both sides are forward-slashed, so only Windows broke. Themanifest.json-at-root probe still rescued the single-plugin-at-root layout; a monorepo with plugins in a subdirectory got nothing. Both callers are affected: session launch (runtime/session-launch.ts:546) and the composer command build (ipc/composer-ipc.ts:63).Fix
Containment now folds both sides before comparing — separators unified to
/, the Windows verbatim prefix stripped in either spelling, trailing separators dropped, and case folded everywhere but Linux (the same platform split the worktree ref folding uses). The sibling-prefix boundary is preserved:repo-xnever counts as insiderepo. The comparison lives in a new dependency-free module (plugin-workspace-contains.ts) so its behavior is tested directly, following theplugin-session-push-gate.tsprecedent.Tests
apps/desktop/test/plugin-workspace-contains.test.mjs— separator/long-prefix/case unification, the workspace-root case, and the sibling-prefix boundary on both drive and POSIX shapes. 2/2 pass locally (Windows box, which is the broken platform);plugin-skills.test.mjsstays green.