* fix(#2602): discover project-local Codex agents - Select an existing local Codex agents directory before global fallback - Prove init reports the canonical local installation through compiled CJS * test(#2602): lock Codex agent precedence - Cover override, local authority, global fallback, and runtime compatibility - Exercise installed state through the compiled resolver * fix(#2602): resolve local Codex agent skills - Pass the canonical project root to the non-Claude persona fallback - Cover nested-Codex fallback and Claude compatibility through the CLI * test(#2602): cover local Codex validation status - Assert emitted validate and health commands use the project-local install - Preserve empty local-directory authority beside complete global agents * fix(#2602): align validation with local Codex discovery - Pass the resolved runtime and project root to health W010 - Resolve the validate-agents runtime before checking installation status * test(#2602): cover local Codex docs status - Assert docs-init reports an authoritative empty local install as unhealthy * fix(#2602): align docs with local Codex discovery - Pass the resolved runtime and canonical project root to the shared agent checker * fix(#2602): honor agent-skills runtime override - Resolve agent-skills fallback runtime through the canonical project resolver - Cover conflicting config and GSD_RUNTIME values through the emitted CLI * fix(#2602): ignore non-directory local agents paths - Treat only a local Codex agents directory as authoritative - Cover regular-file fallback through the emitted install checker * chore(#2602): add changelog fragment - record the user-visible local Codex agent discovery fix for PR #2623 * fix(#2602): align local agent discovery with runtime policy - Resolve Codex's local config directory through the canonical runtime policy - Use test-managed cleanup for local-agent discovery coverage * fix(#2602): discover local agents across runtimes - Prefer manifest-backed project-local installs for non-Claude runtimes - Respect runtime-specific local install roots and preserve global fallback behavior - Cover native, partial, cross-runtime, and project-root local discovery * fix(#2602): preserve agent discovery fallback - Fall back globally when local-install probes fail - Document and test symlink rejection - Align the changeset with repository format * fix(#2602): reuse local directory policy - Resolve runtimes without local config through the canonical sentinel - Document the manifest gate and refresh the context index --------- Co-authored-by: Daniel E. <daniel.e@teachingstrategies.com> Co-authored-by: Rezolv <dave@sienkowski.com>
This commit is contained in:
committed by
GitHub
parent
7b204ad2ac
commit
f0ff23635e
@@ -2,8 +2,8 @@
|
||||
* Agent Install Check — moved from core.cts (ADR-857 T0 #1268 phase rehome-core-squatters).
|
||||
*
|
||||
* Owns:
|
||||
* - getAgentsDir(runtime?): string
|
||||
* - checkAgentsInstalled(runtime?): AgentsInstalledResult
|
||||
* - getAgentsDir(runtime?, projectRoot?): string
|
||||
* - checkAgentsInstalled(runtime?, projectRoot?): AgentsInstalledResult
|
||||
*
|
||||
* The core.cjs re-export spine was retired in epic #1267; callers import
|
||||
* these symbols from agent-install-check.cjs directly.
|
||||
@@ -15,6 +15,7 @@ import path from 'node:path';
|
||||
import modelProfiles = require('./model-profiles.cjs');
|
||||
const { MODEL_PROFILES } = modelProfiles;
|
||||
import { getGlobalConfigDir } from './runtime-homes.cjs';
|
||||
import { getDirName, NO_LOCAL_CONFIG_DIR_SENTINEL } from './runtime-name-policy.cjs';
|
||||
|
||||
interface AgentsInstalledResult {
|
||||
agents_installed: boolean;
|
||||
@@ -33,11 +34,17 @@ interface AgentsInstalledResult {
|
||||
* 2. For claude runtime: __dirname-relative path (agents/ sibling of gsd-core/)
|
||||
* This is correct for both repo runs and real installs (the runtime config dir's
|
||||
* agents/ folder) because gsd-tools.cjs lives inside gsd-core/bin/ in both cases.
|
||||
* 3. For non-claude runtimes: getGlobalConfigDir(runtime)/agents
|
||||
* 3. For non-claude runtimes with a manifest-backed project-local install:
|
||||
* <projectRoot>/<localConfigDir>/agents (or <projectRoot>/agents when
|
||||
* the runtime's local install targets the project root). Requiring the
|
||||
* GSD manifest prevents runtime-native project agents from shadowing a
|
||||
* working global GSD install. Symlinked local agent directories are ignored.
|
||||
* 4. For non-claude runtimes: getGlobalConfigDir(runtime)/agents
|
||||
*
|
||||
* @param runtime - the active runtime name; defaults to GSD_RUNTIME env, then 'claude'
|
||||
* @param projectRoot - canonical project root for local-install discovery
|
||||
*/
|
||||
function getAgentsDir(runtime?: string): string {
|
||||
function getAgentsDir(runtime?: string, projectRoot?: string): string {
|
||||
if (process.env['GSD_AGENTS_DIR']) {
|
||||
return process.env['GSD_AGENTS_DIR'];
|
||||
}
|
||||
@@ -45,6 +52,31 @@ function getAgentsDir(runtime?: string): string {
|
||||
if (resolved === 'claude') {
|
||||
return path.join(__dirname, '..', '..', '..', 'agents');
|
||||
}
|
||||
if (projectRoot) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { runtimes } = require('./capability-registry.cjs') as {
|
||||
runtimes: Record<string, { runtime?: { hostBehaviors?: { localTargetIsProjectRoot?: boolean } } }>;
|
||||
};
|
||||
const runtimeConfig = runtimes[resolved]?.runtime;
|
||||
const localConfigDirName = getDirName(resolved);
|
||||
const localConfigDir = localConfigDirName === NO_LOCAL_CONFIG_DIR_SENTINEL
|
||||
? undefined
|
||||
: runtimeConfig?.hostBehaviors?.localTargetIsProjectRoot
|
||||
? projectRoot
|
||||
: path.join(projectRoot, localConfigDirName);
|
||||
if (!localConfigDir) {
|
||||
return path.join(getGlobalConfigDir(resolved), 'agents');
|
||||
}
|
||||
const localAgentsDir = path.join(localConfigDir, 'agents');
|
||||
const manifestPath = path.join(localConfigDir, 'gsd-file-manifest.json');
|
||||
try {
|
||||
if (fs.lstatSync(localAgentsDir).isDirectory() && fs.lstatSync(manifestPath).isFile()) {
|
||||
return localAgentsDir;
|
||||
}
|
||||
} catch {
|
||||
// Local discovery is best-effort; any probe failure preserves global fallback.
|
||||
}
|
||||
}
|
||||
return path.join(getGlobalConfigDir(resolved), 'agents');
|
||||
}
|
||||
|
||||
@@ -52,10 +84,11 @@ function getAgentsDir(runtime?: string): string {
|
||||
* Check which GSD agents are installed on disk.
|
||||
*
|
||||
* @param runtime - the active runtime name; defaults to GSD_RUNTIME env, then 'claude'
|
||||
* @param projectRoot - canonical project root for local-install discovery
|
||||
*/
|
||||
function checkAgentsInstalled(runtime?: string): AgentsInstalledResult {
|
||||
function checkAgentsInstalled(runtime?: string, projectRoot?: string): AgentsInstalledResult {
|
||||
const resolvedRuntime = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
|
||||
const agentsDir = getAgentsDir(resolvedRuntime);
|
||||
const agentsDir = getAgentsDir(resolvedRuntime, projectRoot);
|
||||
const expectedAgents = Object.keys(MODEL_PROFILES);
|
||||
const installed: string[] = [];
|
||||
const missing: string[] = [];
|
||||
|
||||
@@ -27,6 +27,7 @@ const { pathExistsInternal, toPosixPath } = coreUtils;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import agentInstallCheck = require('./agent-install-check.cjs');
|
||||
const { checkAgentsInstalled } = agentInstallCheck;
|
||||
import { resolveRuntime } from './runtime-slash.cjs';
|
||||
import { platformReadSync } from './shell-command-projection.cjs';
|
||||
|
||||
// ─── Constants ────────────────────────────────────────────────────────────────
|
||||
@@ -282,7 +283,7 @@ function cmdDocsInit(cwd: string, raw: boolean): void {
|
||||
};
|
||||
// Inject project_root and agent installation status (mirrors withProjectRoot in init.cjs)
|
||||
result['project_root'] = cwd;
|
||||
const agentStatus = checkAgentsInstalled();
|
||||
const agentStatus = checkAgentsInstalled(resolveRuntime(cwd), cwd);
|
||||
result['agents_installed'] = agentStatus.agents_installed;
|
||||
result['missing_agents'] = agentStatus.missing_agents;
|
||||
// #2402: withProjectRoot injects response_language when set; cmdDocsInit predates
|
||||
|
||||
@@ -226,7 +226,7 @@ function getLatestCompletedMilestone(cwd: string): { version: string; name: stri
|
||||
function withProjectRoot(cwd: string, result: Record<string, unknown>): Record<string, unknown> {
|
||||
result['project_root'] = cwd;
|
||||
const activeRuntime = resolveRuntime(cwd);
|
||||
const agentStatus = checkAgentsInstalled(activeRuntime);
|
||||
const agentStatus = checkAgentsInstalled(activeRuntime, cwd);
|
||||
result['agents_installed'] = agentStatus.agents_installed;
|
||||
result['missing_agents'] = agentStatus.missing_agents;
|
||||
result['agents_dir'] = agentStatus.agents_dir;
|
||||
@@ -2268,7 +2268,7 @@ function cmdAgentSkills(
|
||||
if (!block) {
|
||||
const runtime = (config && (config['runtime'] as string)) || process.env['GSD_RUNTIME'] || 'claude';
|
||||
if (runtime !== 'claude') {
|
||||
const agentCheck = checkAgentsInstalled(runtime) as unknown as { agents_dir?: string } | null;
|
||||
const agentCheck = checkAgentsInstalled(runtime, projectRoot) as unknown as { agents_dir?: string } | null;
|
||||
const agentsDir = agentCheck?.agents_dir;
|
||||
if (typeof agentsDir === 'string' && agentsDir.length > 0) {
|
||||
const agentFile = path.join(agentsDir, `${agentType}.md`);
|
||||
|
||||
@@ -1855,7 +1855,7 @@ function cmdValidateHealth(
|
||||
}
|
||||
|
||||
try {
|
||||
const agentStatus = checkAgentsInstalled();
|
||||
const agentStatus = checkAgentsInstalled(_slashRuntime, cwd);
|
||||
if (!agentStatus.agents_installed) {
|
||||
if ((agentStatus.installed_agents).length === 0) {
|
||||
addIssue(
|
||||
@@ -2396,7 +2396,7 @@ function cmdValidateHealth(
|
||||
}
|
||||
|
||||
function cmdValidateAgents(cwd: string, raw: boolean): void {
|
||||
const agentStatus = checkAgentsInstalled();
|
||||
const agentStatus = checkAgentsInstalled(resolveRuntime(cwd), cwd);
|
||||
const expected = Object.keys(MODEL_PROFILES);
|
||||
|
||||
output(
|
||||
|
||||
Reference in New Issue
Block a user