Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions skills/ce-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Also remediate these project issues when the report names them:
- `.compound-engineering/config.example.yaml` is missing or outdated
- the health report marks the `ce-work` skill implementation engine unavailable or invalid, detects retired scalar routing keys, or reports malformed dormant `work_engine_preferences`
- the health report marks `docs_root` invalid (`Invalid docs_root ...`) — CE artifacts will not be written until it is fixed
- the health report names a **legacy Compound Codex tool map** in `${CODEX_HOME:-$HOME/.codex}/AGENTS.md` (or a profile copy) — session-level; follow `references/legacy-codex-tool-map.md` in this skill. Do not rewrite `ce-work` skip phrases in this skill.

If optional tools are missing, do not offer a bulk install. The diagnostic already printed the relevant install command or project URL. Say: "Install optional tools only for the workflows you use."

Expand Down
34 changes: 34 additions & 0 deletions skills/ce-setup/references/legacy-codex-tool-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Remove the retired Compound Codex tool map

The Bun-era convert/install path could insert a managed block into
global Codex instructions:

`<!-- BEGIN COMPOUND CODEX TOOL MAP -->` … `<!-- END COMPOUND CODEX TOOL MAP -->`

in `${CODEX_HOME:-$HOME/.codex}/AGENTS.md` and in named
profile copies under `~/.codex/profiles/*/AGENTS.md`.

That Claude-compat map is obsolete — CE skills name Codex tools inline —
and one line incorrectly told Codex to collapse subagent dispatch onto
the main thread. Native plugin install does **not** add this block.

## Safe removal

1. Check `${CODEX_HOME:-$HOME/.codex}/AGENTS.md`. Also check
`~/.codex/profiles/*/AGENTS.md`.
2. Look for the exact sentinels `<!-- BEGIN COMPOUND CODEX TOOL MAP -->`
and `<!-- END COMPOUND CODEX TOOL MAP -->`.
3. Only if a BEGIN line is followed later by an END line (each sentinel
occupying its own line), delete the span from that first BEGIN through
that later END (inclusive). A stray END before the first BEGIN does
not cancel a later ordered pair. Inline mentions of the marker strings
are not a block. If no standalone BEGIN has a standalone END after it,
Comment thread
saurabhkagent-lab marked this conversation as resolved.
leave the file alone. Leave any other user content untouched. Do not
edit project/repo `AGENTS.md` unless those exact sentinels form an
ordered pair of lines there.
4. If the file is empty after the removal, delete the file.
5. Show a short before/after of what changed (or say the block was
already absent). Do not add a replacement tool map.

This file ships with the `ce-setup` skill so marketplace/converted
installs still have the procedure without the repo `docs/` tree.
58 changes: 58 additions & 0 deletions skills/ce-setup/scripts/check-health
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,54 @@ read_work_engine_preferences() {
has_brew=$(command -v brew >/dev/null 2>&1 && echo "yes" || echo "no")
in_repo=$(git rev-parse --is-inside-work-tree >/dev/null 2>&1 && echo "yes" || echo "no")

# Retired Bun-era Codex tool map (#1099 / #1559). Exact sentinels from docs/install/upgrading.md.
CODEX_TOOL_MAP_START="<!-- BEGIN COMPOUND CODEX TOOL MAP -->"
CODEX_TOOL_MAP_END="<!-- END COMPOUND CODEX TOOL MAP -->"
codex_home="${CODEX_HOME:-$HOME/.codex}"
default_codex_home="$HOME/.codex"
legacy_codex_map_files=()
# Ordered pair of standalone sentinel lines (same contract as
# removeCodexAgentsToolMapBlock). A stray earlier END must not hide a later
# removable block. Inline mentions of both marker strings are not a block.
has_ordered_codex_tool_map() {
awk -v s="$CODEX_TOOL_MAP_START" -v e="$CODEX_TOOL_MAP_END" '
{
line = $0
sub(/\r$/, "", line)
if (line == s) seen = 1
if (seen && line == e) found = 1
}
END { exit found ? 0 : 1 }
' "$1"
}
scan_codex_agents_file() {
local f="$1"
local existing
[ -f "$f" ] || return 0
for existing in "${legacy_codex_map_files[@]}"; do
[ "$existing" = "$f" ] && return 0
done
if has_ordered_codex_tool_map "$f"; then
legacy_codex_map_files+=("$f")
fi
}
scan_codex_profile_tree() {
local root="$1"
local f
[ -d "$root/profiles" ] || return 0
for f in "$root/profiles"/*/AGENTS.md; do
[ -f "$f" ] || continue
scan_codex_agents_file "$f"
done
}
scan_codex_agents_file "$codex_home/AGENTS.md"
scan_codex_profile_tree "$codex_home"
# Named profiles live under ~/.codex even when CODEX_HOME points at an active profile.
# Do not scan inactive ~/.codex/AGENTS.md — that file is not the current session.
if [ "$codex_home" != "$default_codex_home" ]; then
scan_codex_profile_tree "$default_codex_home"
fi

# =====================================================
# Check optional capabilities
# =====================================================
Expand Down Expand Up @@ -627,4 +675,14 @@ if [ "$capability_missing" -gt 0 ]; then
echo " Missing optional tools do not block setup; install them only for the workflows you use."
fi

if [ "${#legacy_codex_map_files[@]}" -gt 0 ]; then
echo ""
section "Codex instructions"
for f in "${legacy_codex_map_files[@]}"; do
warn "Legacy Compound Codex tool map still present in $f"
done
detail "This retired block can make Codex skip ce-code-review as if no runner exists."
detail "Remove only the BEGIN/END COMPOUND CODEX TOOL MAP span — see this skill's references/legacy-codex-tool-map.md"
fi

echo ""
33 changes: 29 additions & 4 deletions src/utils/codex-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,36 @@ export async function stripCodexAgentsToolMap(codexHome: string): Promise<void>
}

/** Pure strip helper — exported for tests. */
export function removeCodexAgentsToolMapBlock(existing: string): string {
const startIndex = existing.indexOf(CODEX_AGENTS_BLOCK_START)
const endIndex = existing.indexOf(CODEX_AGENTS_BLOCK_END)
function indexOfStandaloneLine(haystack: string, needle: string, fromIndex = 0): number {
let pos = fromIndex
while (pos <= haystack.length) {
const i = haystack.indexOf(needle, pos)
if (i === -1) {
return -1
}
const beforeOk = i === 0 || haystack[i - 1] === "\n"
const after = i + needle.length
const afterOk =
after === haystack.length || haystack[after] === "\n" || haystack.startsWith("\r\n", after)
if (beforeOk && afterOk) {
return i
}
pos = i + 1
}
return -1
}

if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
export function removeCodexAgentsToolMapBlock(existing: string): string {
const startIndex = indexOfStandaloneLine(existing, CODEX_AGENTS_BLOCK_START)
if (startIndex === -1) {
return existing
}
const endIndex = indexOfStandaloneLine(
existing,
CODEX_AGENTS_BLOCK_END,
startIndex + CODEX_AGENTS_BLOCK_START.length,
)
if (endIndex === -1) {
return existing
}

Expand Down
42 changes: 42 additions & 0 deletions tests/codex-agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,48 @@ describe("removeCodexAgentsToolMapBlock", () => {
expect(result).not.toContain("old managed content")
})

test("strips a later ordered pair after a stray earlier END", () => {
const input = [
"keep this",
CODEX_AGENTS_BLOCK_END,
"user-owned notes",
CODEX_AGENTS_BLOCK_START,
"legacy map",
CODEX_AGENTS_BLOCK_END,
"",
].join("\n")

const result = removeCodexAgentsToolMapBlock(input)
expect(result).toContain("keep this")
expect(result).toContain("user-owned notes")
expect(result).toContain(CODEX_AGENTS_BLOCK_END)
expect(result).not.toContain(CODEX_AGENTS_BLOCK_START)
expect(result).not.toContain("legacy map")
expect(result.indexOf(CODEX_AGENTS_BLOCK_END)).toBe(
input.indexOf(CODEX_AGENTS_BLOCK_END),
)
})

test("leaves inline documentation of both sentinels unchanged", () => {
const input = [
"keep this",
`The retired map used \`${CODEX_AGENTS_BLOCK_START}\` … \`${CODEX_AGENTS_BLOCK_END}\` inline.`,
"",
].join("\n")
expect(removeCodexAgentsToolMapBlock(input)).toBe(input)
})

test("leaves END-then-BEGIN without a later END unchanged", () => {
const input = [
"keep this",
CODEX_AGENTS_BLOCK_END,
"user-owned notes",
CODEX_AGENTS_BLOCK_START,
"",
].join("\n")
expect(removeCodexAgentsToolMapBlock(input)).toBe(input)
})

test("returns empty string when the file is only the managed block", () => {
const input = [CODEX_AGENTS_BLOCK_START, "only this", CODEX_AGENTS_BLOCK_END].join("\n")
expect(removeCodexAgentsToolMapBlock(input)).toBe("")
Expand Down
167 changes: 166 additions & 1 deletion tests/skills/ce-setup-check-health.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,20 @@ type RunResult = {
stderr: string
}

async function runCheckHealth(cwd: string, pathValue: string): Promise<RunResult> {
async function runCheckHealth(
cwd: string,
pathValue: string,
extraEnv: Record<string, string> = {},
): Promise<RunResult> {
const proc = Bun.spawn(["bash", checkHealthScript], {
cwd,
env: {
...process.env,
HOME: cwd,
PATH: pathValue,
// Isolate from the host Codex install (CI/dev machines often set CODEX_HOME).
CODEX_HOME: path.join(cwd, ".codex"),
...extraEnv,
},
stderr: "pipe",
stdout: "pipe",
Expand Down Expand Up @@ -57,6 +64,164 @@ describe("ce-setup check-health", () => {
expect(script).not.toMatch(/<<<\s/)
})

test("reports the legacy Codex tool map when both sentinels are present", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "ce-setup-health-"))
try {
await initGitRepo(root)
await mkdir(path.join(root, ".codex"), { recursive: true })
await writeFile(
path.join(root, ".codex", "AGENTS.md"),
[
"keep this",
"<!-- BEGIN COMPOUND CODEX TOOL MAP -->",
"Task (subagent dispatch) / Subagent / Parallel: run sequentially in main thread",
"<!-- END COMPOUND CODEX TOOL MAP -->",
"",
].join("\n"),
)
const result = await runCheckHealth(root, "/usr/bin:/bin")
Comment thread
saurabhkagent-lab marked this conversation as resolved.
expect(result.exitCode).toBe(0)
expect(result.stdout).toContain("Legacy Compound Codex tool map still present")
expect(result.stdout).toContain("references/legacy-codex-tool-map.md")
} finally {
await rm(root, { recursive: true, force: true })
}
})

test("does not warn when Codex AGENTS.md has no tool-map sentinels", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "ce-setup-health-"))
try {
await initGitRepo(root)
await mkdir(path.join(root, ".codex"), { recursive: true })
await writeFile(path.join(root, ".codex", "AGENTS.md"), "# user instructions\n")
const result = await runCheckHealth(root, "/usr/bin:/bin")
expect(result.exitCode).toBe(0)
expect(result.stdout).not.toContain("Legacy Compound Codex tool map still present")
} finally {
await rm(root, { recursive: true, force: true })
}
})

test("warns when a stray earlier END precedes a later ordered BEGIN/END pair", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "ce-setup-health-"))
try {
await initGitRepo(root)
await mkdir(path.join(root, ".codex"), { recursive: true })
await writeFile(
path.join(root, ".codex", "AGENTS.md"),
[
"keep this",
"<!-- END COMPOUND CODEX TOOL MAP -->",
"user-owned notes",
"<!-- BEGIN COMPOUND CODEX TOOL MAP -->",
"Task (subagent dispatch) / Subagent / Parallel: run sequentially in main thread",
"<!-- END COMPOUND CODEX TOOL MAP -->",
"",
].join("\n"),
)
const result = await runCheckHealth(root, "/usr/bin:/bin")
expect(result.exitCode).toBe(0)
expect(result.stdout).toContain("Legacy Compound Codex tool map still present")
} finally {
await rm(root, { recursive: true, force: true })
}
})

test("does not warn when both sentinels appear inline in prose", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "ce-setup-health-"))
try {
await initGitRepo(root)
await mkdir(path.join(root, ".codex"), { recursive: true })
await writeFile(
path.join(root, ".codex", "AGENTS.md"),
[
"keep this",
"The retired map used `<!-- BEGIN COMPOUND CODEX TOOL MAP -->` … `<!-- END COMPOUND CODEX TOOL MAP -->` inline.",
"",
].join("\n"),
)
const result = await runCheckHealth(root, "/usr/bin:/bin")
expect(result.exitCode).toBe(0)
expect(result.stdout).not.toContain("Legacy Compound Codex tool map still present")
} finally {
await rm(root, { recursive: true, force: true })
}
})

test("does not warn when END sentinel precedes BEGIN (no ordered span)", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "ce-setup-health-"))
try {
await initGitRepo(root)
await mkdir(path.join(root, ".codex"), { recursive: true })
await writeFile(
path.join(root, ".codex", "AGENTS.md"),
[
"keep this",
"<!-- END COMPOUND CODEX TOOL MAP -->",
"user-owned notes",
"<!-- BEGIN COMPOUND CODEX TOOL MAP -->",
"",
].join("\n"),
)
const result = await runCheckHealth(root, "/usr/bin:/bin")
expect(result.exitCode).toBe(0)
expect(result.stdout).not.toContain("Legacy Compound Codex tool map still present")
} finally {
await rm(root, { recursive: true, force: true })
}
})

test("still finds named-profile copies under ~/.codex when CODEX_HOME is elsewhere", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "ce-setup-health-"))
try {
await initGitRepo(root)
const customHome = path.join(root, "custom-codex")
await mkdir(customHome, { recursive: true })
await writeFile(path.join(customHome, "AGENTS.md"), "# active profile, no map\n")
await mkdir(path.join(root, ".codex", "profiles", "work"), { recursive: true })
await writeFile(
path.join(root, ".codex", "profiles", "work", "AGENTS.md"),
[
"<!-- BEGIN COMPOUND CODEX TOOL MAP -->",
"Task (subagent dispatch) / Subagent / Parallel: run sequentially in main thread",
"<!-- END COMPOUND CODEX TOOL MAP -->",
"",
].join("\n"),
)
const result = await runCheckHealth(root, "/usr/bin:/bin", { CODEX_HOME: customHome })
expect(result.exitCode).toBe(0)
expect(result.stdout).toContain("Legacy Compound Codex tool map still present")
expect(result.stdout).toContain("references/legacy-codex-tool-map.md")
} finally {
await rm(root, { recursive: true, force: true })
}
})

test("does not warn on inactive ~/.codex/AGENTS.md when CODEX_HOME is a custom home", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "ce-setup-health-"))
try {
await initGitRepo(root)
const customHome = path.join(root, "custom-codex")
await mkdir(customHome, { recursive: true })
await writeFile(path.join(customHome, "AGENTS.md"), "# active profile, no map\n")
await mkdir(path.join(root, ".codex"), { recursive: true })
await writeFile(
path.join(root, ".codex", "AGENTS.md"),
[
"<!-- BEGIN COMPOUND CODEX TOOL MAP -->",
"Task (subagent dispatch) / Subagent / Parallel: run sequentially in main thread",
"<!-- END COMPOUND CODEX TOOL MAP -->",
"",
].join("\n"),
)
const result = await runCheckHealth(root, "/usr/bin:/bin", { CODEX_HOME: customHome })
expect(result.exitCode).toBe(0)
expect(result.stdout).not.toContain("Legacy Compound Codex tool map still present")
} finally {
await rm(root, { recursive: true, force: true })
}
})

test("advertises agent-browser only for its current consumers", async () => {
const [script, setupDocs, polishSkill, polishRun, polishDocs] = await Promise.all([
readFile(checkHealthScript, "utf8"),
Expand Down