diff --git a/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts b/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts new file mode 100644 index 000000000..14cf587e5 --- /dev/null +++ b/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; + +import { discoverAgents } from "../../subagent/agents.ts"; + +function makeProjectRoot(t: test.TestContext): string { + const root = mkdtempSync(join(tmpdir(), "gsd-subagent-agents-")); + t.after(() => rmSync(root, { recursive: true, force: true })); + return root; +} + +function writeAgent(root: string, configDirName: ".gsd" | ".pi", name = "ping"): string { + const agentsDir = join(root, configDirName, "agents"); + mkdirSync(agentsDir, { recursive: true }); + writeFileSync( + join(agentsDir, `${name}.md`), + `---\nname: ${name}\ndescription: ${name} agent\n---\nSay hello\n`, + ); + return agentsDir; +} + +test("discoverAgents finds project agents in .gsd/agents", (t) => { + const root = makeProjectRoot(t); + const agentsDir = writeAgent(root, ".gsd"); + + const discovery = discoverAgents(root, "project"); + + assert.equal(discovery.projectAgentsDir, agentsDir); + assert.deepEqual(discovery.agents.map((agent) => agent.name), ["ping"]); + assert.equal(discovery.agents[0]?.source, "project"); +}); + +test("discoverAgents falls back to legacy .pi/agents when needed", (t) => { + const root = makeProjectRoot(t); + const agentsDir = writeAgent(root, ".pi"); + + const discovery = discoverAgents(root, "project"); + + assert.equal(discovery.projectAgentsDir, agentsDir); + assert.deepEqual(discovery.agents.map((agent) => agent.name), ["ping"]); +}); diff --git a/src/resources/extensions/subagent/agents.ts b/src/resources/extensions/subagent/agents.ts index 498ec31cc..43e124fa3 100644 --- a/src/resources/extensions/subagent/agents.ts +++ b/src/resources/extensions/subagent/agents.ts @@ -6,6 +6,8 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { getAgentDir, parseFrontmatter } from "@gsd/pi-coding-agent"; +const PROJECT_AGENT_DIR_CANDIDATES = [".gsd", ".pi"] as const; + export type AgentScope = "user" | "project" | "both"; export interface AgentConfig { @@ -85,8 +87,12 @@ function isDirectory(p: string): boolean { function findNearestProjectAgentsDir(cwd: string): string | null { let currentDir = cwd; while (true) { - const candidate = path.join(currentDir, ".pi", "agents"); - if (isDirectory(candidate)) return candidate; + // Prefer the documented project-local location while preserving support + // for older workarounds that placed agents under .pi/agents. + for (const configDir of PROJECT_AGENT_DIR_CANDIDATES) { + const candidate = path.join(currentDir, configDir, "agents"); + if (isDirectory(candidate)) return candidate; + } const parentDir = path.dirname(currentDir); if (parentDir === currentDir) return null;