(plugin: Awaited<ReturnType<typeof loadClaudePlugin>>, codexRoot: string)
| 214 | } |
| 215 | |
| 216 | async function cleanupCodex(plugin: Awaited<ReturnType<typeof loadClaudePlugin>>, codexRoot: string): Promise<CleanupResult> { |
| 217 | const bundle = convertClaudeToCodex(plugin, { |
| 218 | agentMode: "subagent", |
| 219 | inferTemperature: true, |
| 220 | permissions: "none", |
| 221 | // Cleanup needs the FULL bundle (skills, command-skills, agents) to know |
| 222 | // what's "current" vs "legacy." The agents-only default of `--to codex` |
| 223 | // is wrong here; it would make cleanup think every existing skill is |
| 224 | // legacy and remove them. |
| 225 | codexIncludeSkills: true, |
| 226 | }) |
| 227 | const artifacts = getLegacyCodexArtifacts(bundle) |
| 228 | const currentNamespacedSkills = new Set([ |
| 229 | ...bundle.skillDirs.map((skill) => sanitizePathName(skill.name)), |
| 230 | ...bundle.generatedSkills.map((skill) => sanitizePathName(skill.name)), |
| 231 | ]) |
| 232 | const currentPrompts = new Set(bundle.prompts.map((prompt) => `${sanitizePathName(prompt.name)}.md`)) |
| 233 | const currentAgents = new Set((bundle.agents ?? []).map((agent) => `${sanitizePathName(agent.name)}.toml`)) |
| 234 | const managedDir = path.join(codexRoot, plugin.manifest.name) |
| 235 | let moved = 0 |
| 236 | for (const skillName of artifacts.skills) { |
| 237 | moved += await moveLegacySkillIfOwned(managedDir, "skills", path.join(codexRoot, "skills"), skillName, "Codex") |
| 238 | if (!currentNamespacedSkills.has(skillName)) { |
| 239 | moved += await moveIfExists( |
| 240 | managedDir, |
| 241 | "skills", |
| 242 | path.join(codexRoot, "skills", plugin.manifest.name), |
| 243 | skillName, |
| 244 | "Codex", |
| 245 | ) |
| 246 | } |
| 247 | } |
| 248 | for (const promptFile of artifacts.prompts) { |
| 249 | // Ownership gate: `~/.codex/prompts/` is a shared directory across plugins |
| 250 | // and user-authored prompts. A filename match against the historical CE |
| 251 | // allow-list is not a strong enough signal — a user who creates |
| 252 | // `~/.codex/prompts/ce-plan.md` for their own workflow would otherwise see |
| 253 | // it swept into `compound-engineering/legacy-backup/` on every cleanup run. |
| 254 | // Mirror the body + frontmatter check used by `cleanupStalePrompts` so |
| 255 | // install-time and standalone cleanup paths treat ownership identically. |
| 256 | // "unknown" (no fingerprint on record) falls through so fully-retired |
| 257 | // historical wrappers still get cleaned up. Manifest-driven migration |
| 258 | // below is already safe because it only touches files CE recorded writing. |
| 259 | const promptPath = path.join(codexRoot, "prompts", promptFile) |
| 260 | const ownership = await classifyCodexLegacyPromptOwnership(promptPath) |
| 261 | if (ownership === "foreign") continue |
| 262 | moved += await moveIfExists(managedDir, "prompts", path.join(codexRoot, "prompts"), promptFile, "Codex") |
| 263 | } |
| 264 | for (const agentFile of artifacts.agents ?? []) { |
| 265 | moved += await moveIfExists( |
| 266 | managedDir, |
| 267 | "agents", |
| 268 | path.join(codexRoot, "agents", plugin.manifest.name), |
| 269 | agentFile, |
| 270 | "Codex", |
| 271 | ) |
| 272 | moved += await moveLegacyAgentIfOwned(managedDir, "agents", path.join(codexRoot, "agents"), agentFile, "Codex", ".toml") |
| 273 | } |
no test coverage detected