fix(#2602): discover project-local Codex agents (#2623)

* 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:
Daniel Einspanjer
2026-07-31 21:20:46 -04:00
committed by GitHub
parent 7b204ad2ac
commit f0ff23635e
10 changed files with 495 additions and 12 deletions

View File

@@ -14,6 +14,22 @@ const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const MODEL_PROFILES = require('../gsd-core/bin/lib/model-profiles.cjs').MODEL_PROFILES;
const EXPECTED_AGENTS = Object.keys(MODEL_PROFILES);
function createCompleteCodexAgents(projectRoot) {
const agentsDir = path.join(projectRoot, '.codex', 'agents');
const files = {};
fs.mkdirSync(agentsDir, { recursive: true });
for (const name of EXPECTED_AGENTS) {
fs.writeFileSync(path.join(agentsDir, `${name}.md`), `# ${name}\n`);
fs.writeFileSync(path.join(agentsDir, `${name}.toml`), `name = "${name}"\n`);
files[`agents/${name}.md`] = {};
files[`agents/${name}.toml`] = {};
}
fs.writeFileSync(path.join(projectRoot, '.codex', 'gsd-file-manifest.json'), JSON.stringify({ files }));
}
// ─── JSON output shape ────────────────────────────────────────────────────────
describe('docs-init command', () => {
@@ -91,6 +107,49 @@ describe('docs-init command', () => {
});
});
describe('docs-init Codex project-local agent status', () => {
let tmpDir;
let globalHome;
beforeEach(() => {
tmpDir = createTempProject();
globalHome = path.join(tmpDir, 'global-codex');
fs.writeFileSync(path.join(tmpDir, '.planning', 'config.json'), JSON.stringify({ runtime: 'codex' }));
fs.mkdirSync(path.join(globalHome, 'agents'), { recursive: true });
for (const name of EXPECTED_AGENTS) {
fs.writeFileSync(path.join(globalHome, 'agents', `${name}.md`), `# global-${name}\n`);
}
});
afterEach(() => {
cleanup(tmpDir);
});
test('reports a complete local Codex installation instead of a global fixture', () => {
createCompleteCodexAgents(tmpDir);
const result = runGsdTools('docs-init --raw', tmpDir, { CODEX_HOME: globalHome });
assert.ok(result.success, `docs-init failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.project_root, fs.realpathSync(tmpDir));
assert.strictEqual(output.agents_installed, true);
assert.deepStrictEqual(output.missing_agents, []);
});
test('reports an empty local Codex directory as unhealthy even with global agents', () => {
fs.mkdirSync(path.join(tmpDir, '.codex', 'agents'), { recursive: true });
fs.writeFileSync(path.join(tmpDir, '.codex', 'gsd-file-manifest.json'), JSON.stringify({ files: {} }));
const result = runGsdTools('docs-init --raw', tmpDir, { CODEX_HOME: globalHome });
assert.ok(result.success, `docs-init failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.agents_installed, false);
assert.deepStrictEqual(output.missing_agents, EXPECTED_AGENTS);
});
});
// ─── response_language wiring (#2402) ─────────────────────────────────────────
//
// cmdDocsInit predates withProjectRoot (init.cts) and never picked up