feat(coding-agent): add packages array with filtering support

- Add PackageSource type for npm/git sources with optional filtering
- Migrate npm:/git: sources from extensions to packages array
- Add getPackages(), setPackages(), setProjectPackages() methods
- Update package-manager to resolve from packages array
- Support selective loading: extensions, skills, prompts, themes per package
- Update pi list to show packages
- Add migration tests for settings

closes #645
This commit is contained in:
Mario Zechner 2026-01-23 19:51:23 +01:00
parent dd838d0fe0
commit ef1fc3103e
8 changed files with 434 additions and 63 deletions

View file

@ -106,6 +106,112 @@ describe("SettingsManager", () => {
});
});
describe("packages migration", () => {
it("should migrate npm: sources from extensions to packages", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(
settingsPath,
JSON.stringify({
extensions: ["npm:pi-doom", "/local/ext.ts", "npm:shitty-extensions"],
}),
);
const manager = SettingsManager.create(projectDir, agentDir);
expect(manager.getPackages()).toEqual(["npm:pi-doom", "npm:shitty-extensions"]);
expect(manager.getExtensionPaths()).toEqual(["/local/ext.ts"]);
});
it("should migrate git: sources from extensions to packages", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(
settingsPath,
JSON.stringify({
extensions: ["git:github.com/user/repo", "/local/ext.ts"],
}),
);
const manager = SettingsManager.create(projectDir, agentDir);
expect(manager.getPackages()).toEqual(["git:github.com/user/repo"]);
expect(manager.getExtensionPaths()).toEqual(["/local/ext.ts"]);
});
it("should migrate raw github URLs from extensions to packages", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(
settingsPath,
JSON.stringify({
extensions: ["https://github.com/user/repo", "/local/ext.ts"],
}),
);
const manager = SettingsManager.create(projectDir, agentDir);
expect(manager.getPackages()).toEqual(["https://github.com/user/repo"]);
expect(manager.getExtensionPaths()).toEqual(["/local/ext.ts"]);
});
it("should keep local-only extensions in extensions array", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(
settingsPath,
JSON.stringify({
extensions: ["/local/ext.ts", "./relative/ext.ts"],
}),
);
const manager = SettingsManager.create(projectDir, agentDir);
expect(manager.getPackages()).toEqual([]);
expect(manager.getExtensionPaths()).toEqual(["/local/ext.ts", "./relative/ext.ts"]);
});
it("should preserve existing packages when migrating", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(
settingsPath,
JSON.stringify({
packages: ["npm:existing-pkg"],
extensions: ["npm:new-pkg", "/local/ext.ts"],
}),
);
const manager = SettingsManager.create(projectDir, agentDir);
expect(manager.getPackages()).toEqual(["npm:existing-pkg", "npm:new-pkg"]);
expect(manager.getExtensionPaths()).toEqual(["/local/ext.ts"]);
});
it("should handle packages with filtering objects", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(
settingsPath,
JSON.stringify({
packages: [
"npm:simple-pkg",
{
source: "npm:shitty-extensions",
extensions: ["extensions/oracle.ts"],
skills: [],
},
],
}),
);
const manager = SettingsManager.create(projectDir, agentDir);
const packages = manager.getPackages();
expect(packages).toHaveLength(2);
expect(packages[0]).toBe("npm:simple-pkg");
expect(packages[1]).toEqual({
source: "npm:shitty-extensions",
extensions: ["extensions/oracle.ts"],
skills: [],
});
});
});
describe("shellCommandPrefix", () => {
it("should load shellCommandPrefix from settings", () => {
const settingsPath = join(agentDir, "settings.json");