Reviewed GitHub issue and related code.
UnderstandImplemented fix for extension discovery.
BuildRan tests to confirm fix.
VerifyReleased new version and closed issue.
CleanupAnalyze GitHub issue(s): https://github.com/badlogic/pi-mono/issues/1102
For each issue:
Read the issue in full, including all comments and linked issues/PRs.
For bugs:
For feature requests:
Do NOT implement unless explicitly asked. Analyze and propose only.
Let me find the relevant extension loading code:
Let me examine the specific functions that handle extension collection:
Now I understand the root cause. Let me verify by looking at the specific sections:
Now let me check the collectFilesFromPaths function that's also relevant:
Type: Bug
Labels: bug, should add pkg:coding-agent
The bug is in packages/coding-agent/src/core/package-manager.ts. There are two different code paths for discovering extension files:
Auto-discovery (local directories) uses collectAutoExtensionEntries() which properly handles multi-file extensions:
.ts/.js files → loaded directlyresolveExtensionEntries() which finds only index.ts/index.js or explicit manifest entriesPackage resolution uses collectFiles() which recursively collects ALL .ts/.js files, including helper modules:
// Line ~1216 in collectPackageResourcesconst files = resourceType === "skills" ? collectSkillEntries(dir) : collectFiles(dir, FILE_PATTERNS[resourceType]);When collectFiles is used for extensions, every .ts file in the package's extensions/ directory gets loaded as a standalone extension, including helper modules like agents.ts that don't export a factory function.
| Method | Line | Current | Should Use |
|---|---|---|---|
collectPackageResources | ~1216 | collectFiles() | collectAutoExtensionEntries() |
collectDefaultResources | ~1242 | collectFiles() | collectAutoExtensionEntries() |
collectManifestFiles | ~1301 | collectFiles() | collectAutoExtensionEntries() |
collectFilesFromPaths | ~1501 | collectFiles() | collectAutoExtensionEntries() |
Create a helper function and use it consistently:
function collectResourceFiles(dir: string, resourceType: ResourceType): string[] { if (resourceType === "skills") { return collectSkillEntries(dir); } if (resourceType === "extensions") { return collectAutoExtensionEntries(dir); } return collectFiles(dir, FILE_PATTERNS[resourceType]);}Then replace all four occurrences of the ternary with collectResourceFiles(dir, resourceType).
packages/coding-agent/src/core/package-manager.ts (4 edits)packages/coding-agent/CHANGELOG.md (add entry under [Unreleased] → Fixed)shouldn't auto collection be the same for packages and local paths in ~/.pi/agent and .pi/?
Yes, exactly. That's precisely what the fix does.
The bug exists because there are two different functions being used:
~/.pi/agent/extensions/, .pi/extensions/): uses collectAutoExtensionEntries()collectFiles() (recursive, grabs everything)The fix unifies them so packages also use collectAutoExtensionEntries(), making the behavior identical:
| Location | Entry Type | Behavior |
|---|---|---|
foo.ts | File | Load it |
bar/index.ts | Subdirectory with index | Load only index.ts |
bar/helper.ts | Helper module | Ignored (imported by index.ts) |
baz/package.json with pi.extensions | Subdirectory with manifest | Load declared entries |
This should work the same whether the extension is in:
~/.pi/agent/extensions/.pi/extensions/extensions/ directoryok, but we only use that if no manifest is given, correct?
Correct. Looking at the code flow:
With manifest (package.json has pi.extensions): Uses the explicit paths declared in the manifest - collectAutoExtensionEntries is not involved
Without manifest: Falls back to convention-based discovery (looks for extensions/ directory) - this is where the bug occurs and where collectAutoExtensionEntries should be used
The workaround in the issue confirms this:
{ "pi": { "extensions": [ "./extensions/foo.ts", "./extensions/bar/index.ts" ] }}This works because the manifest path is used directly, bypassing the buggy collectFiles() fallback.
The fix only affects packages that rely on convention-based discovery (no manifest, or manifest without explicit extensions field).
ok, apply the fix, also add a regression test so this shit never happens again
Now I'll apply the fix and add the regression test:
Now let me add the regression test:
Now let's run the tests:
All tests pass. Let me run the full check:
Now let me update the changelog:
Let me verify the fix by checking the updated code:
Done. Here's a summary of the changes:
Files modified:
packages/coding-agent/src/core/package-manager.ts - Fixed extension discoverypackages/coding-agent/test/package-manager.test.ts - Added regression testspackages/coding-agent/CHANGELOG.md - Added changelog entryThe fix:
Added a collectResourceFiles() helper function that routes to the correct collection function based on resource type:
function collectResourceFiles(dir: string, resourceType: ResourceType): string[] { if (resourceType === "skills") { return collectSkillEntries(dir); } if (resourceType === "extensions") { return collectAutoExtensionEntries(dir); // Uses smart discovery } return collectFiles(dir, FILE_PATTERNS[resourceType]);}Replaced 4 occurrences where collectFiles() was incorrectly used for extensions.
Regression tests added (4 tests):
index.ts from subdirectories, not helper modulespackage.json pi.extensions manifest in subdirectoriesindex.ts or manifestrun the test, if it passes:
commit & push comment on issue "Thanks for reporting, fixed on main" patch release
Tests pass. Committing and pushing:
Done. Released v0.50.7 with the fix:
ed80ab21 - fix(coding-agent): multi-file extensions in packages now discovered correctlyfixes #1102)@mariozechner/pi-coding-agent@0.50.7 and all other packages// Line ~1216 in collectPackageResourcesconst files = resourceType === "skills" ? collectSkillEntries(dir) : collectFiles(dir, FILE_PATTERNS[resourceType]);function collectResourceFiles(dir: string, resourceType: ResourceType): string[] { if (resourceType === "skills") { return collectSkillEntries(dir); } if (resourceType === "extensions") { return collectAutoExtensionEntries(dir); } return collectFiles(dir, FILE_PATTERNS[resourceType]);}{ "pi": { "extensions": [ "./extensions/foo.ts", "./extensions/bar/index.ts" ] }}function collectResourceFiles(dir: string, resourceType: ResourceType): string[] { if (resourceType === "skills") { return collectSkillEntries(dir); } if (resourceType === "extensions") { return collectAutoExtensionEntries(dir); // Uses smart discovery } return collectFiles(dir, FILE_PATTERNS[resourceType]);}