From db7da0e9c6e87f051be600b4cbb69c20c99c579c Mon Sep 17 00:00:00 2001 From: alexion Date: Mon, 7 Sep 2026 22:46:14 -0400 Subject: [PATCH] fix(pi): discover ancestor project resources --- .../pi => .pi}/extensions/compact-status.ts | 0 .../extensions/herdr-agent-state.ts | 0 configs/nixos/AGENTS.md | 10 +- configs/nixos/modules/shell/pi/default.nix | 7 +- .../pi-project-resource-ancestors.patch | 140 ++++++++++++++++++ 5 files changed, 146 insertions(+), 11 deletions(-) rename {configs/nixos/modules/shell/pi => .pi}/extensions/compact-status.ts (100%) rename {configs/nixos/modules/shell/pi => .pi}/extensions/herdr-agent-state.ts (100%) create mode 100644 configs/nixos/modules/shell/pi/patches/pi-project-resource-ancestors.patch diff --git a/configs/nixos/modules/shell/pi/extensions/compact-status.ts b/.pi/extensions/compact-status.ts similarity index 100% rename from configs/nixos/modules/shell/pi/extensions/compact-status.ts rename to .pi/extensions/compact-status.ts diff --git a/configs/nixos/modules/shell/pi/extensions/herdr-agent-state.ts b/.pi/extensions/herdr-agent-state.ts similarity index 100% rename from configs/nixos/modules/shell/pi/extensions/herdr-agent-state.ts rename to .pi/extensions/herdr-agent-state.ts diff --git a/configs/nixos/AGENTS.md b/configs/nixos/AGENTS.md index e2b862f..eaffab5 100644 --- a/configs/nixos/AGENTS.md +++ b/configs/nixos/AGENTS.md @@ -32,11 +32,11 @@ - Use produced literals for Herdr key names that include shifted punctuation. For example, use `prefix+<` instead of `prefix+shift+comma`. -- Put global Pi extension sources in `configs/nixos/modules/shell/pi/extensions/`. - Home Manager deploys them to `~/.pi/agent/extensions/`. -- Treat Home Manager-managed Pi extension, prompt, and skill directories as generated output. - Do not edit those directories except for throwaway development or local experiments. - Promote finished work into the NixOS configuration before treating it as deployed. +- Put monorepo Pi extension sources in `.pi/extensions/`. + Pi discovers them from subdirectories through the local ancestor-resource patch. +- Treat Home Manager-managed Pi settings as generated output. + Do not edit them except for throwaway development or local experiments. + Promote finished settings into the NixOS configuration before treating them as deployed. ## Gotchas diff --git a/configs/nixos/modules/shell/pi/default.nix b/configs/nixos/modules/shell/pi/default.nix index fcc514d..39889eb 100644 --- a/configs/nixos/modules/shell/pi/default.nix +++ b/configs/nixos/modules/shell/pi/default.nix @@ -13,6 +13,7 @@ let piDir = "${config.users.users.${user}.home}/.pi/agent"; patchedPi = pkgs.pi-coding-agent.overrideAttrs (old: { patches = (old.patches or [ ]) ++ [ + ./patches/pi-project-resource-ancestors.patch ./patches/pi-tool-lookup-validation.patch ]; }); @@ -44,12 +45,6 @@ in # The first declarative rollout replaces the interactive settings file. # Login state stays in auth.json, which this module does not manage. "${piDir}/settings.json".force = true; - - "${piDir}/extensions" = { - source = ./extensions; - recursive = true; - force = true; - }; }; }; }; diff --git a/configs/nixos/modules/shell/pi/patches/pi-project-resource-ancestors.patch b/configs/nixos/modules/shell/pi/patches/pi-project-resource-ancestors.patch new file mode 100644 index 0000000..fc8ff64 --- /dev/null +++ b/configs/nixos/modules/shell/pi/patches/pi-project-resource-ancestors.patch @@ -0,0 +1,140 @@ +diff --git a/packages/coding-agent/src/core/package-manager.ts b/packages/coding-agent/src/core/package-manager.ts +--- a/packages/coding-agent/src/core/package-manager.ts ++++ b/packages/coding-agent/src/core/package-manager.ts +@@ -459,14 +459,14 @@ function findGitRepoRoot(startDir: string): string | null { + } + } + +-function collectAncestorAgentsSkillDirs(startDir: string): string[] { +- const skillDirs: string[] = []; ++function collectAncestorConfigDirs(startDir: string, configDirName: string): string[] { ++ const configDirs: string[] = []; + const resolvedStartDir = resolve(startDir); + const gitRepoRoot = findGitRepoRoot(resolvedStartDir); + + let dir = resolvedStartDir; + while (true) { +- skillDirs.push(join(dir, ".agents", "skills")); ++ configDirs.push(join(dir, configDirName)); + if (gitRepoRoot && dir === gitRepoRoot) { + break; + } +@@ -477,7 +477,7 @@ function collectAncestorAgentsSkillDirs(startDir: string): string[] { + dir = parent; + } + +- return skillDirs; ++ return configDirs; + } + + function collectAutoPromptEntries(dir: string): string[] { +@@ -2388,16 +2388,13 @@ export class DefaultPackageManager implements PackageManager { + prompts: join(globalBaseDir, "prompts"), + themes: join(globalBaseDir, "themes"), + }; +- const projectDirs = { +- extensions: join(projectBaseDir, "extensions"), +- skills: join(projectBaseDir, "skills"), +- prompts: join(projectBaseDir, "prompts"), +- themes: join(projectBaseDir, "themes"), +- }; + const userAgentsSkillsDir = join(getHomeDir(), ".agents", "skills"); + const projectTrusted = this.settingsManager.isProjectTrusted(); +- const projectAgentsSkillDirs = projectTrusted +- ? collectAncestorAgentsSkillDirs(this.cwd).filter((dir) => resolve(dir) !== resolve(userAgentsSkillsDir)) ++ const projectConfigDirs = projectTrusted ++ ? collectAncestorConfigDirs(this.cwd, CONFIG_DIR_NAME).filter((dir) => resolve(dir) !== resolve(globalBaseDir)) ++ : []; ++ const projectAgentsDirs = projectTrusted ++ ? collectAncestorConfigDirs(this.cwd, ".agents").filter((dir) => resolve(dir) !== resolve(dirname(userAgentsSkillsDir))) + : []; + + const addResources = ( +@@ -2414,56 +2411,54 @@ export class DefaultPackageManager implements PackageManager { + } + }; + +- if (projectTrusted) { +- // Project extensions from .pi/ ++ // Project resources from .pi/ in cwd and ancestors. ++ for (const configDir of projectConfigDirs) { ++ const configMetadata: PathMetadata = { ++ ...projectMetadata, ++ baseDir: configDir, ++ }; + addResources( + "extensions", +- collectAutoExtensionEntries(projectDirs.extensions), +- projectMetadata, ++ collectAutoExtensionEntries(join(configDir, "extensions")), ++ configMetadata, + projectOverrides.extensions, +- projectBaseDir, ++ configDir, + ); +- +- // Project skills from .pi/ + addResources( + "skills", +- collectAutoSkillEntries(projectDirs.skills, "pi"), +- projectMetadata, ++ collectAutoSkillEntries(join(configDir, "skills"), "pi"), ++ configMetadata, + projectOverrides.skills, +- projectBaseDir, ++ configDir, ++ ); ++ addResources( ++ "prompts", ++ collectAutoPromptEntries(join(configDir, "prompts")), ++ configMetadata, ++ projectOverrides.prompts, ++ configDir, ++ ); ++ addResources( ++ "themes", ++ collectAutoThemeEntries(join(configDir, "themes")), ++ configMetadata, ++ projectOverrides.themes, ++ configDir, + ); + } + +- // Project skills from .agents/ (each with its own baseDir) +- for (const agentsSkillsDir of projectAgentsSkillDirs) { +- const agentsBaseDir = dirname(agentsSkillsDir); // the .agents directory ++ // Project skills from .agents/ in cwd and ancestors. ++ for (const agentsDir of projectAgentsDirs) { + const agentsMetadata: PathMetadata = { + ...projectMetadata, +- baseDir: agentsBaseDir, ++ baseDir: agentsDir, + }; + addResources( + "skills", +- collectAutoSkillEntries(agentsSkillsDir, "agents"), ++ collectAutoSkillEntries(join(agentsDir, "skills"), "agents"), + agentsMetadata, + projectOverrides.skills, +- agentsBaseDir, +- ); +- } +- +- if (projectTrusted) { +- addResources( +- "prompts", +- collectAutoPromptEntries(projectDirs.prompts), +- projectMetadata, +- projectOverrides.prompts, +- projectBaseDir, +- ); +- addResources( +- "themes", +- collectAutoThemeEntries(projectDirs.themes), +- projectMetadata, +- projectOverrides.themes, +- projectBaseDir, ++ agentsDir, + ); + } +