mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
fix(sdk): point checkAgentsInstalled at ~/.claude/agents (#2401)
This commit is contained in:
@@ -10,6 +10,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- **`/gsd-ingest-docs` command** — Scan a repo containing mixed ADRs, PRDs, SPECs, and DOCs and bootstrap or merge the full `.planning/` setup from them in a single pass. Parallel classification (`gsd-doc-classifier`), synthesis with precedence rules and cycle detection (`gsd-doc-synthesizer`), three-bucket conflicts report (`INGEST-CONFLICTS.md`: auto-resolved, competing-variants, unresolved-blockers), and hard-block on LOCKED-vs-LOCKED ADR contradictions in both new and merge modes. Supports directory-convention discovery and `--manifest <file>` YAML override with per-doc precedence. v1 caps at 50 docs per invocation; `--resolve interactive` is reserved. Extracts shared conflict-detection contract into `references/doc-conflict-engine.md` which `/gsd-import` now also consumes (#2387)
|
||||
|
||||
### Fixed
|
||||
- **`init` query agents-installed check looks at the correct directory** — `checkAgentsInstalled` in `sdk/src/query/init.ts` defaulted to `~/.claude/get-shit-done/agents/`, but the installer writes GSD agents to `~/.claude/agents/`. Every init query therefore reported `agents_installed: false` on clean installs, which made workflows refuse to spawn `gsd-executor` and other parallel subagents. The default now matches `sdk/src/init-runner.ts` and the installer (#2400)
|
||||
- **Installer now installs `@gsd-build/sdk` automatically** so `gsd-sdk` lands on PATH. Resolves `command not found: gsd-sdk` errors that affected every `/gsd-*` command after a fresh install or `/gsd-update` to 1.36+. Adds `--no-sdk` to opt out and `--sdk` to force reinstall. Implements the `--sdk` flag that was previously documented in README but never wired up (#2385)
|
||||
|
||||
## [1.37.1] - 2026-04-17
|
||||
|
||||
@@ -9,7 +9,7 @@ Read all files referenced by the invoking prompt's execution_context before star
|
||||
|
||||
Key references:
|
||||
- @$HOME/.claude/get-shit-done/references/ui-brand.md (display patterns)
|
||||
- @$HOME/.claude/get-shit-done/agents/gsd-user-profiler.md (profiler agent definition)
|
||||
- @$HOME/.claude/agents/gsd-user-profiler.md (profiler agent definition)
|
||||
- @$HOME/.claude/get-shit-done/references/user-profiling.md (profiling reference doc)
|
||||
</required_reading>
|
||||
|
||||
|
||||
@@ -33,11 +33,12 @@ import type { GSDEventStream } from './event-stream.js';
|
||||
import { loadConfig } from './config.js';
|
||||
import { runPhaseStepSession } from './session-runner.js';
|
||||
import { sanitizePrompt } from './prompt-sanitizer.js';
|
||||
import { resolveAgentsDir } from './query/helpers.js';
|
||||
|
||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
const GSD_TEMPLATES_DIR = join(homedir(), '.claude', 'get-shit-done', 'templates');
|
||||
const GSD_AGENTS_DIR = join(homedir(), '.claude', 'agents');
|
||||
const GSD_AGENTS_DIR = resolveAgentsDir();
|
||||
|
||||
const RESEARCH_TYPES = ['STACK', 'FEATURES', 'ARCHITECTURE', 'PITFALLS'] as const;
|
||||
type ResearchType = (typeof RESEARCH_TYPES)[number];
|
||||
|
||||
@@ -19,8 +19,27 @@
|
||||
|
||||
import { join, relative, resolve, isAbsolute, normalize } from 'node:path';
|
||||
import { realpath } from 'node:fs/promises';
|
||||
import { homedir } from 'node:os';
|
||||
import { GSDError, ErrorClassification } from '../errors.js';
|
||||
|
||||
// ─── resolveAgentsDir ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Resolve the Claude Code agents directory using the same precedence as the
|
||||
* installer (`bin/install.js` getGlobalDir for Claude):
|
||||
* 1. GSD_AGENTS_DIR — explicit SDK override
|
||||
* 2. CLAUDE_CONFIG_DIR/agents — matches installer's --config-dir / env
|
||||
* 3. ~/.claude/agents — default
|
||||
*
|
||||
* Must mirror `bin/install.js:389-396` for Claude. Used by both
|
||||
* `query/init.ts:checkAgentsInstalled` and `init-runner.ts`.
|
||||
*/
|
||||
export function resolveAgentsDir(): string {
|
||||
if (process.env.GSD_AGENTS_DIR) return process.env.GSD_AGENTS_DIR;
|
||||
if (process.env.CLAUDE_CONFIG_DIR) return join(process.env.CLAUDE_CONFIG_DIR, 'agents');
|
||||
return join(homedir(), '.claude', 'agents');
|
||||
}
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Paths to common .planning files. */
|
||||
|
||||
@@ -116,6 +116,95 @@ describe('withProjectRoot', () => {
|
||||
const enriched = withProjectRoot(tmpDir, result, {});
|
||||
expect(enriched.response_language).toBeUndefined();
|
||||
});
|
||||
|
||||
// Regression: #2400 — checkAgentsInstalled was looking at the wrong default
|
||||
// directory (~/.claude/get-shit-done/agents) while the installer writes to
|
||||
// ~/.claude/agents, causing agents_installed: false even on clean installs.
|
||||
it('reports agents_installed: true when all expected agents exist in GSD_AGENTS_DIR', async () => {
|
||||
const { MODEL_PROFILES } = await import('./config-query.js');
|
||||
const agentsDir = join(tmpDir, 'fake-agents');
|
||||
await mkdir(agentsDir, { recursive: true });
|
||||
for (const name of Object.keys(MODEL_PROFILES)) {
|
||||
await writeFile(join(agentsDir, `${name}.md`), '# stub');
|
||||
}
|
||||
const prev = process.env.GSD_AGENTS_DIR;
|
||||
process.env.GSD_AGENTS_DIR = agentsDir;
|
||||
try {
|
||||
const enriched = withProjectRoot(tmpDir, {});
|
||||
expect(enriched.agents_installed).toBe(true);
|
||||
expect(enriched.missing_agents).toEqual([]);
|
||||
} finally {
|
||||
if (prev === undefined) delete process.env.GSD_AGENTS_DIR;
|
||||
else process.env.GSD_AGENTS_DIR = prev;
|
||||
}
|
||||
});
|
||||
|
||||
it('reports missing agents when GSD_AGENTS_DIR is empty', async () => {
|
||||
const agentsDir = join(tmpDir, 'empty-agents');
|
||||
await mkdir(agentsDir, { recursive: true });
|
||||
const prev = process.env.GSD_AGENTS_DIR;
|
||||
process.env.GSD_AGENTS_DIR = agentsDir;
|
||||
try {
|
||||
const enriched = withProjectRoot(tmpDir, {}) as Record<string, unknown>;
|
||||
expect(enriched.agents_installed).toBe(false);
|
||||
expect((enriched.missing_agents as string[]).length).toBeGreaterThan(0);
|
||||
} finally {
|
||||
if (prev === undefined) delete process.env.GSD_AGENTS_DIR;
|
||||
else process.env.GSD_AGENTS_DIR = prev;
|
||||
}
|
||||
});
|
||||
|
||||
// Regression: #2400 follow-up — installer honors CLAUDE_CONFIG_DIR for custom
|
||||
// Claude install roots. The SDK check must follow the same precedence or it
|
||||
// false-negatives agent presence on non-default installs.
|
||||
it('honors CLAUDE_CONFIG_DIR when GSD_AGENTS_DIR is unset', async () => {
|
||||
const { MODEL_PROFILES } = await import('./config-query.js');
|
||||
const configDir = join(tmpDir, 'custom-claude');
|
||||
const agentsDir = join(configDir, 'agents');
|
||||
await mkdir(agentsDir, { recursive: true });
|
||||
for (const name of Object.keys(MODEL_PROFILES)) {
|
||||
await writeFile(join(agentsDir, `${name}.md`), '# stub');
|
||||
}
|
||||
const prevAgents = process.env.GSD_AGENTS_DIR;
|
||||
const prevClaude = process.env.CLAUDE_CONFIG_DIR;
|
||||
delete process.env.GSD_AGENTS_DIR;
|
||||
process.env.CLAUDE_CONFIG_DIR = configDir;
|
||||
try {
|
||||
const enriched = withProjectRoot(tmpDir, {}) as Record<string, unknown>;
|
||||
expect(enriched.agents_installed).toBe(true);
|
||||
expect(enriched.missing_agents).toEqual([]);
|
||||
} finally {
|
||||
if (prevAgents === undefined) delete process.env.GSD_AGENTS_DIR;
|
||||
else process.env.GSD_AGENTS_DIR = prevAgents;
|
||||
if (prevClaude === undefined) delete process.env.CLAUDE_CONFIG_DIR;
|
||||
else process.env.CLAUDE_CONFIG_DIR = prevClaude;
|
||||
}
|
||||
});
|
||||
|
||||
it('GSD_AGENTS_DIR takes precedence over CLAUDE_CONFIG_DIR', async () => {
|
||||
const { MODEL_PROFILES } = await import('./config-query.js');
|
||||
const winningDir = join(tmpDir, 'winning-agents');
|
||||
const losingDir = join(tmpDir, 'losing-config', 'agents');
|
||||
await mkdir(winningDir, { recursive: true });
|
||||
await mkdir(losingDir, { recursive: true });
|
||||
// Only populate the winning dir.
|
||||
for (const name of Object.keys(MODEL_PROFILES)) {
|
||||
await writeFile(join(winningDir, `${name}.md`), '# stub');
|
||||
}
|
||||
const prevAgents = process.env.GSD_AGENTS_DIR;
|
||||
const prevClaude = process.env.CLAUDE_CONFIG_DIR;
|
||||
process.env.GSD_AGENTS_DIR = winningDir;
|
||||
process.env.CLAUDE_CONFIG_DIR = join(tmpDir, 'losing-config');
|
||||
try {
|
||||
const enriched = withProjectRoot(tmpDir, {}) as Record<string, unknown>;
|
||||
expect(enriched.agents_installed).toBe(true);
|
||||
} finally {
|
||||
if (prevAgents === undefined) delete process.env.GSD_AGENTS_DIR;
|
||||
else process.env.GSD_AGENTS_DIR = prevAgents;
|
||||
if (prevClaude === undefined) delete process.env.CLAUDE_CONFIG_DIR;
|
||||
else process.env.CLAUDE_CONFIG_DIR = prevClaude;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('initExecutePhase', () => {
|
||||
|
||||
@@ -27,7 +27,7 @@ import { loadConfig } from '../config.js';
|
||||
import { resolveModel, MODEL_PROFILES } from './config-query.js';
|
||||
import { findPhase } from './phase.js';
|
||||
import { roadmapGetPhase, getMilestoneInfo } from './roadmap.js';
|
||||
import { planningPaths, normalizePhaseName, toPosixPath } from './helpers.js';
|
||||
import { planningPaths, normalizePhaseName, toPosixPath, resolveAgentsDir } from './helpers.js';
|
||||
import type { QueryHandler } from './utils.js';
|
||||
|
||||
// ─── Internal helpers ──────────────────────────────────────────────────────
|
||||
@@ -82,8 +82,7 @@ function getLatestCompletedMilestone(projectDir: string): { version: string; nam
|
||||
* Port of checkAgentsInstalled from core.cjs lines 1274-1306.
|
||||
*/
|
||||
function checkAgentsInstalled(): { agents_installed: boolean; missing_agents: string[] } {
|
||||
const agentsDir = process.env.GSD_AGENTS_DIR
|
||||
|| join(homedir(), '.claude', 'get-shit-done', 'agents');
|
||||
const agentsDir = resolveAgentsDir();
|
||||
const expectedAgents = Object.keys(MODEL_PROFILES);
|
||||
|
||||
if (!existsSync(agentsDir)) {
|
||||
|
||||
Reference in New Issue
Block a user