plugins/foundry/skills/migrate-dual-harness/SKILL.md
Migrate a single-harness repo to the dual-harness layout so both Claude Code and Codex load the same instructions and skills — AGENTS.md as the portable canonical doc, a thin CLAUDE.md `@AGENTS.md` bridge, and a `.agents/skills` dir with a `.claude/skills` symlink onto it. Use when asked to migrate to dual-harness, make this repo work in both Claude and Codex, or for agent metadata cleanup.
npx skillsauth add coalesce-labs/catalyst migrate-dual-harnessInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Converge a single-harness repo (Claude-only monolithic CLAUDE.md, or Codex-only AGENTS.md
with no bridge) onto the target dual-harness layout, applying every mechanical fix automatically
and only asking the model to do the one thing a script can't: split a monolithic CLAUDE.md
into its portable core and its Claude-specific remainder.
User-invoked only. This skill sets disable-model-invocation: true, so nothing can
auto-trigger it as a handoff — not catalyst-foundry:setup-catalyst (which hits this exact
case at its rc 11 row) and not check-project-setup.sh's §10 checkup warning. Both merely
reference /catalyst-foundry:migrate-dual-harness and tell the user to run it themselves;
invoke this skill directly (/catalyst-foundry:migrate-dual-harness) to perform the split.
AGENTS.md — portable, tool-agnostic instructions (canonical).CLAUDE.md — thin bridge: literal line 1 is @AGENTS.md, then only Claude-specific notes..agents/skills/ — canonical skills dir..claude/skills — committed relative symlink → ../.agents/skills.AGENTS.md carries a ## Skills pointer section when the repo has skills..codex/ dir needed — Codex reads AGENTS.md and .agents/skills/ natively.Locate the backing script (lives in catalyst-dev, the shared framework core):
source "${CLAUDE_PLUGIN_ROOT:-plugins/foundry}/scripts/require-catalyst-dev.sh" \
"${CLAUDE_PLUGIN_ROOT:-plugins/foundry}" || exit 1
SCRIPT="${CATALYST_DEV_SCRIPTS}/migrate-dual-harness.sh"
Dry-run the classifier — it writes nothing:
bash "$SCRIPT" --repo . 2>&1
Branch on the exit code:
| rc | Meaning | Next step |
|----|---------|-----------|
| 0 | already dual-ok, or no-harness (out of scope for this skill) | Phase 5 (house rules) — for no-harness, re-run the mechanical fix per Phase 5's note — then Phase 6 |
| 10 | mechanical changes needed (bridge / skills wiring / pointer) — no monolithic problem | Phase 3 |
| 11 | CLAUDE.md is monolithic — needs the intelligent split | Phase 3 (mechanical parts only), then Phase 4 |
| 2 | bad usage (--repo not a dir, unknown flag) | fix the invocation and re-run |
| 4 | ambiguous skills state (message says exactly what conflicts) | STOP — surface the script's stderr diagnostic verbatim; never modify either skills tree yourself; present the conflict and ask the human operator to resolve it, then re-run the classifier (Phase 2) to verify |
| 5 | I/O error | inspect the printed message |
Applies to rc 10 and rc 11 alike (rc 11 still gets the skills/pointer wiring — only the
CLAUDE.md split itself is deferred to Phase 4):
bash "$SCRIPT" --repo . --fix 2>&1
10, re-run the Phase 2 dry-run and require rc 0.11, re-run the Phase 2 dry-run and expect rc 11 again (the monolithic
split is still outstanding) — proceed to Phase 4.4 (ambiguous skills state) is a STOP, not an auto-fix and not a retry loop: surface the
script's stderr diagnostic verbatim to the user and never modify either skills tree yourself —
resolving the conflict is a decision for the human operator. Present the conflict and ask; once
they've resolved it, re-run the classifier (Phase 2) to verify.This is the one step a script cannot do — reconciling prose requires model judgment.
Read CLAUDE.md IN FULL. Not a partial read — every line needs a classification
decision below.
Partition every line/section into:
.claude/ paths, Claude Code plugin/marketplace/session mechanics,
claude CLI invocations, MCP registration commands that are Claude-scoped.@-import lines are classified by their TARGET, not by their syntax (the @ mechanism
is Claude-only, but what it imports usually isn't): read the imported file; if its content
is portable guidance (e.g. @docs/development.md), reference it from AGENTS.md as a
plain read-this path (like the Reference Docs pattern) so Codex sees it too, and the
@-import may stay in the bridge for Claude's auto-load. Only an import whose target is
genuinely Claude-specific stays bridge-only. Never let portable guidance survive solely
behind an @-import Codex cannot interpret.Merge the portable content into AGENTS.md:
AGENTS.md doesn't exist, create it from the portable content.AGENTS.md already exists, merge without duplicating — existing AGENTS.md sections
stay authoritative on conflict; note any discrepancy to the user rather than silently
overwriting it.Rewrite CLAUDE.md as: line 1 @AGENTS.md, a blank line, then only the Claude-specific
remainder under clear headings.
Conservation check: every H2/H3 heading and every non-blank line of the original
CLAUDE.md must appear — verbatim or explicitly merged — in exactly one of the two files.
List anything dropped and why (this should be nothing; if something doesn't fit either
bucket cleanly, default to portable rather than dropping it).
Re-run the mechanical fix and require rc 0:
bash "$SCRIPT" --repo . --fix 2>&1
Run the seeder so the repo also carries the "Working the Loop" reflexes (independent of the dual-harness layout, but always worth syncing while touching the agent docs):
bash "${CATALYST_DEV_SCRIPTS}/ensure-agent-house-rules.sh" --repo . --fix 2>&1
no-harness repos: the seeder creates the AGENTS.md/CLAUDE.md pair from scratch (the
doc-pair itself is out of scope for migrate-dual-harness.sh — see Phase 2's no-harness note),
even when Phase 3 already wired the skills tree. The freshly created AGENTS.md can't yet carry
the ## Skills pointer, so re-run the mechanical fix to pick it up (and the @AGENTS.md bridge,
if still missing) before the final verification:
bash "$SCRIPT" --repo . --fix 2>&1
Re-run the classifier and confirm rc 0:
bash "$SCRIPT" --repo . 2>&1
Print a summary table rendered from the OBSERVED state, never from this template's example
values — check each artifact and report what actually exists (a repo with no skills tree
legitimately reaches rc 0 with no .agents/skills, no symlink, and no pointer; those rows must
say none, not claim artifacts that don't exist):
── Dual-Harness Migration ──────────────────────
Docs: <e.g. AGENTS.md canonical, CLAUDE.md → @AGENTS.md bridge>
Skills: <.agents/skills/ (.claude/skills → symlink) | none>
Pointer: <## Skills section present in AGENTS.md | n/a (no skills)>
House rules: <Working the Loop block current | absent>
Status: <rc 0 classification>
chore(meta): migrate <repo> to dual-harness layout) — the user commits..claude/skills and .agents/skills both existing as real, byte-identical directories — the
script collapses that to a symlink, guarded by diff -r.4 is a stop sign, not a retry loop. Ambiguous skills state means two real trees with
different content, or a symlink pointing somewhere unexpected. The agent must never modify
either skills tree itself — surface the script's stderr diagnostic verbatim and present the
conflict to the human operator; resolving it is their decision, not the agent's. Re-run the
classifier (Phase 2) to verify only after they've resolved it.tools
Goal-driven senior-engineer pipeline-unstick sweep (CTL-1176 rung 3). Given the stuck/failed/needs-human set (or ONE ticket handed by the recovery router), its GOAL is to get the pipeline MOVING again — not to fix one ticket's review findings (that is phase-remediate). It runs AFTER the eyes (diagnostician evidence) and the hands (deterministic unstuck-sweep seams) have already tried, and it CONSUMES their output from a recovery-pass.json brief rather than re-diagnosing or redoing their narrow work. It acts like a senior engineer with full tool access — it resolves merge conflicts, rebases, force-pushes, merges green PRs, and re-dispatches stalled phases AUTONOMOUSLY — and escalates to the operator ONLY for a genuine value judgment / something that degrades other functionality / a real cost-benefit trade-off / a serious architecture change / an ADR conflict. On escalation it AUTHORS the operator inbox row + the push notification (executive-voiced). Dispatched as a `claude --bg` job by phase-agent-dispatch via slash command, AND invocable bare by the operator as a sweep — hence `user-invocable: true`. Ships behind CATALYST_RECOVERY_PASS (off by default — no live behavior change until shadow/enforce).
tools
Diagnose and fix Catalyst setup issues. Validates tools, database, config, OTel, direnv, and thoughts. Automatically fixes what it can — creates directories, initializes the database, sets WAL mode, runs migrations. Use for new installs, upgrades, or when something isn't working.
tools
--- name: phase-triage description: Phase agent that triages a Linear ticket — expands acronyms, classifies (feature/bug/docs/refactor/chore), identifies genuine blockers (a semantic second-pass over the backlog — NOT a prose scrape; CTL-838), estimates scope, writes triage.json, and posts a triage analysis comment to Linear. Triage completion is signaled by that comment plus the local triage.json — there is no `triaged` label. Emits phase.triage.complete.<TICKET> on success and phase.triage.fai
development
Reference template every phase-agent skill copies (CTL-448). The leading underscore on the directory name prevents the skill loader from picking this up — it is NOT a runnable skill, only a structural template the nine real phase agents (phase-triage, phase-research, phase-plan, phase-implement, phase-verify, phase-review, phase-pr, phase-monitor-merge, phase-monitor-deploy) clone and specialize. The real phase skills MUST set `user-invocable: true` so `phase-agent-dispatch`'s `claude --bg "/catalyst-dev:phase-X ..."` slash command resolves (CTL-490).