/** * @file Tests for codex-discovery.js: read-only discovery of Codex config * surfaces (skills, agents, rules, memory, config) across the user (CODEX_HOME) * and project (/.codex) scopes. * @author Nguyễn Ngọc Trí Vĩ */ const fs = require("node:fs"); const path = require("node:path"); const os = require("node:os"); const { describe, it, before, after } = require("node:test"); const assert = require("node:assert/strict"); function write(file, content) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, content); } describe("codex-discovery", () => { let TMP; let REPO; let prevCodexHome; let cx; before(() => { prevCodexHome = process.env.CODEX_HOME; TMP = fs.mkdtempSync(path.join(os.tmpdir(), "codex-disc-")); REPO = fs.mkdtempSync(path.join(os.tmpdir(), "codex-disc-repo-")); process.env.CODEX_HOME = TMP; delete require.cache[require.resolve("../lib/codex-home")]; delete require.cache[require.resolve("../lib/codex-discovery")]; cx = require("../lib/codex-discovery"); }); after(() => { if (prevCodexHome === undefined) delete process.env.CODEX_HOME; else process.env.CODEX_HOME = prevCodexHome; fs.rmSync(TMP, { recursive: true, force: true }); fs.rmSync(REPO, { recursive: true, force: true }); }); it("reads a user skill and a project skill into one scoped list", () => { write( path.join(TMP, "skills", "release-guard", "SKILL.md"), "---\nname: release-guard\n---\nbody" ); write( path.join(REPO, ".codex", "skills", "onboarding", "SKILL.md"), "---\nname: onboarding\n---\nb" ); const rows = cx.readSkills({ cwd: REPO }); assert.deepEqual(rows.map((r) => [r.scope, r.name]).sort(), [ ["project", "onboarding"], ["user", "release-guard"], ]); assert.equal(rows.find((r) => r.name === "release-guard").frontmatter.name, "release-guard"); }); it("pulls an agent's identity out of its TOML without a TOML parser", () => { write( path.join(REPO, ".codex", "agents", "reviewer.toml"), 'name = "reviewer"\ndescription = "Read-only review agent."\nmodel = "gpt-5.4"\n' + 'developer_instructions = """\nmulti\nline\n"""\n' ); const [agent] = cx.readAgents({ cwd: REPO, scope: "project" }); assert.equal(agent.meta.name, "reviewer"); assert.equal(agent.meta.description, "Read-only review agent."); assert.equal(agent.meta.model, "gpt-5.4"); }); it("does not mistake a key inside a triple-quoted block for a top-level field", () => { const meta = cx.readTomlMeta( 'name = "implementer"\n' + 'sandbox_mode = "workspace-write"\n' + 'developer_instructions = """\n' + 'model = "not-a-real-field"\n' + '"""\n' + 'model = "gpt-5.3-codex-spark"\n' ); assert.equal(meta.name, "implementer"); assert.equal(meta.sandbox_mode, "workspace-write"); assert.equal(meta.model, "gpt-5.3-codex-spark"); }); it("keeps a field with a trailing inline comment, and keeps a '#' that is inside the string", () => { const meta = cx.readTomlMeta( 'model = "gpt-5.4" # default\n' + 'description = "uses # for headings"\n' ); assert.equal(meta.model, "gpt-5.4"); assert.equal(meta.description, "uses # for headings"); }); it("does not let an escaped quote before a real trailing comment truncate the value", () => { const meta = cx.readTomlMeta('description = "say \\"hi\\"" # not a comment\n'); assert.equal(meta.description, 'say \\"hi\\"'); }); it("recognises a quote preceded by an escaped backslash as a real terminator", () => { const meta = cx.readTomlMeta('path = "path\\\\" # c\n'); assert.equal(meta.path, "path\\"); }); it("keeps counting backslash pairs correctly with four of them before the terminator", () => { const meta = cx.readTomlMeta('key = "a\\\\\\\\" # c\n'); assert.equal(meta.key, "a\\\\"); }); it("reads a rule file's preview", () => { write( path.join(TMP, "rules", "no-force-push.rules"), "never force-push main\nask before deleting" ); const [rule] = cx.readRules({ cwd: REPO, scope: "user" }); assert.equal(rule.name, "no-force-push"); assert.match(rule.preview, /never force-push main/); }); it("reports a missing AGENTS.md as a row that does not exist, not a gap", () => { const rows = cx.readMemory({ cwd: REPO }); const user = rows.find((r) => r.scope === "user"); assert.equal(user.exists, false, "the UI offers to create it, so the row must be present"); }); it("reads project AGENTS.md from the repo root, not under .codex", () => { write(path.join(REPO, "AGENTS.md"), "# Project instructions"); const rows = cx.readMemory({ cwd: REPO }); const project = rows.find((r) => r.scope === "project"); assert.equal(project.exists, true); assert.equal(project.file, path.join(REPO, "AGENTS.md")); }); it("reads config.toml content for both scopes", () => { write(path.join(TMP, "config.toml"), 'model = "gpt-5.3-codex-spark"\n'); const rows = cx.readConfig({ cwd: REPO }); const user = rows.find((r) => r.scope === "user"); assert.equal(user.exists, true); assert.match(user.content, /gpt-5\.3-codex-spark/); const project = rows.find((r) => r.scope === "project"); assert.equal(project.exists, false); }); it("follows a skill directory symlinked in from outside CODEX_HOME — a supported install pattern", () => { const outside = fs.mkdtempSync(path.join(os.tmpdir(), "codex-disc-outside-")); write(path.join(outside, "SKILL.md"), "---\nname: linked\n---\nbody"); const linkPath = path.join(TMP, "skills", "linked"); fs.symlinkSync(outside, linkPath, "dir"); const [linked] = cx.readSkills({ cwd: REPO, scope: "user" }); assert.equal(linked.scope, "user"); assert.equal(linked.name, "linked"); assert.equal(linked.frontmatter.name, "linked", "frontmatter must be read through the link"); assert.equal(linked.file, path.join(linkPath, "SKILL.md")); fs.rmSync(outside, { recursive: true, force: true }); }); it("follows an agent .toml symlinked in from outside CODEX_HOME", () => { const outside = fs.mkdtempSync(path.join(os.tmpdir(), "codex-disc-outside-")); const realFile = path.join(outside, "reviewer.toml"); write(realFile, 'name = "reviewer"\n'); const linkPath = path.join(TMP, "agents", "reviewer.toml"); fs.mkdirSync(path.dirname(linkPath), { recursive: true }); fs.symlinkSync(realFile, linkPath, "file"); const [agent] = cx.readAgents({ cwd: REPO, scope: "user" }); assert.equal(agent.scope, "user"); assert.equal(agent.name, "reviewer"); assert.equal(agent.meta.name, "reviewer", "meta must be read through the link"); assert.equal(agent.file, linkPath); fs.rmSync(outside, { recursive: true, force: true }); }); it("summarizes counts and roots in the overview", () => { const overview = cx.readOverview({ cwd: REPO }); assert.equal(overview.roots.projectRoot, REPO); assert.ok(overview.counts.skills.user >= 1); assert.ok(overview.counts.agents.project >= 1); }); });