Detect a repo's coding harness(es) — OpenCode v1/v2, pi, Claude Code, Codex — and provision parity project-level setup: shared .agents/skills dir, canonical AGENTS.md with per-harness shims, harness config files, MCP per supported harness. Triggers: set up harness for this repo, harness parity, project harness setup, setup pi/opencode/claude/codex.
Resources
2Install
npx skillscat add darellchua2/civiltekk-skills/civiltekk-coding-harness-setup-skill Install via the SkillsCat registry.
What I do
I make one repo work identically across the team's coding-agent harnesses. I detect which harness(es) a repo already carries, then provision the parity surface: skills in the neutral .agents/skills/ dir, one canonical AGENTS.md, per-harness config deltas, and MCP config only where the harness supports it. I run in the primary session of the TARGET repo, never the configurator checkout.
Parity is reported, not assumed: the final matrix shows provisioned / shimmed / unsupported per harness and layer — and pi's "no MCP" row is always stated, never silently blank.
When to use me
- "set up harness for this repo", "harness parity", "make this repo work with pi and opencode"
- First session in a repo with a foreign harness layout (
.pi/,.codex/,.claude/present, nothing provisioned for the team's harness) - A teammate joins with a different harness and the repo must gain their dirs
Layer rules (the parity contract)
- Skills — neutral-first: one master copy in
.agents/skills/<name>/(read natively at project level by OpenCode, pi, Codex, Kilo, Kimi — seedocs/harness-landscape-2026-09.md§Standards layer). Claude Code is the only shim: refreshed copies into.claude/skills/on every run. Never edit two masters. - Instructions — one source: canonical root
AGENTS.md(read natively by all five profiles). Claude Code'sCLAUDE.mdbecomes a one-line@AGENTS.mdimport. An existing content-bearingCLAUDE.mdis a conflict — surface it, ask, never auto-merge. - Config — delta-only: per-harness writes are backup-then-merge, never clobber (procedure below). pi needs zero config for skills (reads
.agents/skills/natively). - MCP — honest asymmetry: one user-stated server list translated per harness that supports MCP (OpenCode
opencode.json, Claude Code.mcp.json, Codexconfig.toml). pi has no MCP by design — its matrix row always reads "unsupported — extensions path", and the user decides whether to note that in AGENTS.md. - Freshness — verify or disclose: each profile side file lists official doc URLs. Unless the user pinned a version, fetch them before writing config. Fetch fails → use this skill's embedded baseline and disclose that in the report.
Step 1 — Detect
Read-only scan. Filenames match case-insensitively. A harness-shaped signal not listed below → ask, never guess.
| Signal | Detects |
|---|---|
opencode.json or .opencode/ |
OpenCode — version via opencode --version; binary absent → config-shape fallback (markers in the profile side files) |
.pi/ |
pi |
.claude/ or CLAUDE.md |
Claude Code |
.codex/ |
Codex CLI |
AGENTS.md (any case) |
instructions layer present (harness-agnostic) |
.agents/skills/ |
neutral skills layer present |
Report findings as one table, then branch:
- 0 harnesses → ask which to provision
- 1 harness → provision it; offer the team-standard set ("which other harnesses should also work here?")
- ≥2 → parity mode: provision missing dirs per harness, neutral-first
Step 2 — Ask
Harness binding (§Portability contract): OpenCode —
questiontool. Claude Code —AskUserQuestion. Other/none — print the menu in a plain reply and wait; non-interactive → apply defaults and disclose.
One multi-select: which harness profiles to provision (detected ones pre-checked), plus which MCP servers the repo needs, if any.
Step 3 — Load the profile
Side-file load table — read ONLY the file for the current profile; never preload:
| Read | When | Use |
|---|---|---|
references/harnesses/opencode-v2.md |
detection resolved OpenCode v2 | dirs, config keys, version markers, verify commands, doc URLs |
references/harnesses/opencode-v1.md |
detection resolved OpenCode v1 | same skeleton for the v1 shape |
references/harnesses/pi.md |
detection resolved pi | dirs, settings override, extensions-not-MCP asymmetry |
references/harnesses/claude-code.md |
detection resolved Claude Code | dirs, CLAUDE.md shim, .mcp.json |
references/harnesses/codex.md |
detection resolved Codex | dirs, AGENTS.md layering, config.toml MCP |
A harness with no side file: fetch its official skills/setup docs (Step 4 rules), scaffold from what they say, and offer to contribute references/harnesses/<name>.md by copying an existing skeleton.
Step 4 — Freshness gate
Unless the user pinned a version: fetch the profile's doc URLs and reconcile conflicts — the live doc wins over this skill; fix the side file's stale value in the same run. Fetch fails or offline → proceed on the embedded baseline and disclose that in the report. Version pinned → fetch that version's docs, else baseline with disclosure.
Step 5 — Write
- Create-if-absent:
.agents/skills/,.claude/skills/(Claude shim), per-harness dirs from the profile - Refresh copies:
.agents/skills/→.claude/skills/(Claude Code only, every run) - Instructions: create
AGENTS.mdif absent; else marker-append under## Harness Setup(marker<!-- harness-setup -->);CLAUDE.mdshim per profile — content-bearing existing file → surface, ask - Config deltas — backup-then-merge, delta-only. MCP server maps take FULL entries only (a bare
{"disabled":false}stub can erase a global transport in OpenCode — profile file carries the detail):
Backup first: cp <config> <config>.bak-$(date +%Y%m%d_%H%M%S)
Merge delta into existing config (existing base, delta wins). jq:
# Requires bash (git-bash/WSL on Windows)
jq -s '.[0] * .[1]' <config> <delta.json> > <config>.new && mv <config>.new <config>No jq — Node (guaranteed wherever the installer ran):
node -e "
const fs=require('fs');
const isObj=(v)=>v&&typeof v==='object'&&!Array.isArray(v);
const merge=(b,d)=>{for(const k of Object.keys(d)){b[k]=(isObj(b[k])&&isObj(d[k]))?merge(b[k],d[k]):d[k];}return b;};
const base=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));
const delta=JSON.parse(fs.readFileSync(process.argv[2],'utf8'));
fs.writeFileSync(process.argv[1],JSON.stringify(merge(base,delta),null,2)+'\n');
" <config> <delta.json>Diff-check after every merge: only the intended keys added (git diff <config> or diff vs the .bak). Invalid JSON in an existing config → STOP, show the user, never guess.
Step 6 — Verify + report
Run each provisioned profile's verify command (from its side file). Report exactly:
- Parity matrix: harness × layer (skills / instructions / config / MCP) → provisioned | shimmed | unsupported (why)
- Files written + backup paths (
.bak-<timestamp>) - Doc freshness: which URLs verified live, which fell back to baseline
- Revert: delete created dirs/files, remove appended AGENTS.md blocks, restore
.bakfiles
Gates (hard rules)
- No preload — never "read all references first"; load only the current profile's side file
- Never clobber — merge, diff-check, report backups; project layer is opt-in only
- pi's MCP row never blank — "unsupported — extensions path", every run
- Offline → disclose; version pin → honor
- Every side-file value carries a citation or a verify-locally note; drift found at runtime → fix the side file in the same run
Governance
| Aspect | Source of truth |
|---|---|
| Harness dirs + config syntax | profile side files' cited official docs — live-fetched at the freshness gate |
| Neutral-dir readers, convergence claims | docs/harness-landscape-2026-09.md (in-repo research baseline, verified 2026-09-26) |
| OpenCode MCP merge semantics; deep MCP opt-ins | opencode v2 docs; for server catalogs and opt-in flows prefer opencode-repo-setup-skill when installed |
| v1→v2 conversion | opencode-v2-migration-skill when installed; otherwise profile version markers + pinned docs |
This skill ships no MCP server inventory, no model IDs, and no harness version numbers beyond detection markers — refresh from the cited docs when they drift.