From 37e2b6f0529ca8eb36c5e314cf0a03eac20451ff Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 3 Apr 2026 11:56:43 -0400 Subject: [PATCH] fix: resolve agents path correctly in gsd-new-workspace worktree context (#1512) (#1596) Copilot installs agent files as gsd-*.agent.md (not gsd-*.md), so checkAgentsInstalled() always returned agents_installed=false for Copilot. - checkAgentsInstalled() now recognises both .md and .agent.md formats - getAgentsDir() respects GSD_AGENTS_DIR env override for testability Co-authored-by: Claude Sonnet 4.6 --- get-shit-done/bin/lib/core.cjs | 13 +++- tests/agent-install-validation.test.cjs | 99 +++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 041d6557a..13422bc8a 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -1135,9 +1135,15 @@ function getRoadmapPhaseInternal(cwd, phaseNum) { * gsd-tools.cjs lives at /get-shit-done/bin/gsd-tools.cjs, * so agents/ is at /agents/. * + * GSD_AGENTS_DIR env var overrides the default path. Used in tests and for + * installs where the agents directory is not co-located with gsd-tools.cjs. + * * @returns {string} Absolute path to the agents directory */ function getAgentsDir() { + if (process.env.GSD_AGENTS_DIR) { + return process.env.GSD_AGENTS_DIR; + } // __dirname is get-shit-done/bin/lib/ → go up 3 levels to configDir return path.join(__dirname, '..', '..', '..', 'agents'); } @@ -1146,6 +1152,9 @@ function getAgentsDir() { * Check which GSD agents are installed on disk. * Returns an object with installation status and details. * + * Recognises both standard format (gsd-planner.md) and Copilot format + * (gsd-planner.agent.md). Copilot renames agent files during install (#1512). + * * @returns {{ agents_installed: boolean, missing_agents: string[], installed_agents: string[], agents_dir: string }} */ function checkAgentsInstalled() { @@ -1164,8 +1173,10 @@ function checkAgentsInstalled() { } for (const agent of expectedAgents) { + // Check both .md (standard) and .agent.md (Copilot) file formats. const agentFile = path.join(agentsDir, `${agent}.md`); - if (fs.existsSync(agentFile)) { + const agentFileCopilot = path.join(agentsDir, `${agent}.agent.md`); + if (fs.existsSync(agentFile) || fs.existsSync(agentFileCopilot)) { installed.push(agent); } else { missing.push(agent); diff --git a/tests/agent-install-validation.test.cjs b/tests/agent-install-validation.test.cjs index c64c7f701..071d31b59 100644 --- a/tests/agent-install-validation.test.cjs +++ b/tests/agent-install-validation.test.cjs @@ -156,6 +156,105 @@ describe('validate health: agent installation check W010 (#1371)', () => { }); }); +// ─── Copilot .agent.md detection (#1512) ──────────────────────────────────── + +describe('checkAgentsInstalled: Copilot .agent.md format (#1512)', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempProject(); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('agents_installed=true when agents exist as .agent.md (Copilot format)', () => { + // Simulate a Copilot install: agents are named gsd-*.agent.md, not gsd-*.md + // Use GSD_AGENTS_DIR to point at an isolated dir with ONLY .agent.md files, + // so the test does not accidentally pass via the repo's own agents/ dir. + const agentsDir = path.join(tmpDir, 'copilot-agents'); + fs.mkdirSync(agentsDir, { recursive: true }); + for (const name of EXPECTED_AGENTS) { + fs.writeFileSync( + path.join(agentsDir, `${name}.agent.md`), + `---\nname: ${name}\ndescription: Test agent\n---\nAgent content.\n` + ); + } + + const result = runGsdTools('validate agents --raw', tmpDir, { GSD_AGENTS_DIR: agentsDir }); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + // Must report the custom dir, not the default repo agents dir + assert.strictEqual(output.agents_dir, agentsDir, + 'agents_dir must be the GSD_AGENTS_DIR override, not the repo default'); + assert.strictEqual(output.agents_found, true, + 'agents_found must be true when agents exist as .agent.md (Copilot format)'); + assert.deepStrictEqual(output.missing, [], + 'missing must be empty when all agents exist as .agent.md'); + }); + + test('agents_installed=false when .agent.md files exist for only some agents', () => { + const agentsDir = path.join(tmpDir, 'agents'); + fs.mkdirSync(agentsDir, { recursive: true }); + // Only install the first agent + const firstAgent = EXPECTED_AGENTS[0]; + fs.writeFileSync( + path.join(agentsDir, `${firstAgent}.agent.md`), + `---\nname: ${firstAgent}\ndescription: Test agent\n---\nAgent content.\n` + ); + + const result = runGsdTools('validate agents --raw', tmpDir, { GSD_AGENTS_DIR: agentsDir }); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.agents_found, false, + 'agents_found must be false when only some agents exist'); + assert.ok(output.missing.length > 0, 'missing must be non-empty when some agents are absent'); + }); + + test('init new-workspace includes agents_installed=true with Copilot .agent.md files', () => { + // Use an isolated dir with ONLY .agent.md files (no .md fallback) + const agentsDir = path.join(tmpDir, 'copilot-agents'); + fs.mkdirSync(agentsDir, { recursive: true }); + for (const name of EXPECTED_AGENTS) { + fs.writeFileSync( + path.join(agentsDir, `${name}.agent.md`), + `---\nname: ${name}\ndescription: Test agent\n---\nAgent content.\n` + ); + } + + const result = runGsdTools('init new-workspace --raw', tmpDir, { GSD_AGENTS_DIR: agentsDir }); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.agents_installed, true, + 'agents_installed must be true when Copilot .agent.md files are present'); + assert.deepStrictEqual(output.missing_agents, [], + 'missing_agents must be empty when all .agent.md files are present'); + }); + + test('GSD_AGENTS_DIR env var overrides default agents directory', () => { + // Create a custom agents dir in a subdirectory + const customAgentsDir = path.join(tmpDir, 'custom-agents'); + fs.mkdirSync(customAgentsDir, { recursive: true }); + // Put one agent there as .md (standard format) + fs.writeFileSync( + path.join(customAgentsDir, `${EXPECTED_AGENTS[0]}.md`), + `---\nname: ${EXPECTED_AGENTS[0]}\ndescription: Test agent\n---\nAgent content.\n` + ); + + const result = runGsdTools('validate agents --raw', tmpDir, { GSD_AGENTS_DIR: customAgentsDir }); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + // The custom dir path should be reported + assert.strictEqual(output.agents_dir, customAgentsDir, + 'agents_dir must reflect GSD_AGENTS_DIR override'); + }); +}); + // ─── validate agents subcommand ───────────────────────────────────────────── describe('validate agents subcommand (#1371)', () => {