# 05 — Impact Analysis, Migration Staging & Test Spec

> **Owner:** Agent E
> **Status:** Spec only — no implementation code. **v2** (2026-07-15): the false "160 agents"
> figure purged (real count: **34**, disk-verified — see `06-REVIEW` F1); Stage 3 rewritten as
> a **merge from both trees** (`.opencode/` AND `plugins/claude-code/`), not a copy from
> `.opencode/` (`06-REVIEW` C2); the "or diff-explained" escape hatch deleted from the Stage-2
> gate (`06-REVIEW` G7); golden-test warning count corrected to 2 (`06-REVIEW` C4).
> **Read first:** [`00-INDEX.md`](./00-INDEX.md) (locked decisions). This doc depends on the
> canonical schema (02), adapter specs (03), and CLI/build/distribution (04).

**Prime directive for this workstream (from the user):** *understand how the change
affects everything before moving anything*, then *ship the smallest test that proves the
pipeline* and *write the full test spec now but defer most of it*. This document is the
"blast-radius map + safe route + proof harness." It does not move code; it tells the other
workstreams what will break and in what order to change it.

---

## 0. Ground Truth (measured, not assumed)

Facts below are pulled from the current tree and anchor every claim in this doc.

| Fact | Value | Source |
|------|-------|--------|
| Source of truth today (OpenCode side) | `.opencode/` (**34** agent `.md` files under `agent/`) | `find .opencode/agent -name '*.md' \| wc -l` → 34. *(v1 claimed 160 via `-path '*agent*'`, which also matched `context/openagents-repo/**` and `prompts/core/openagent/**` — falsified in `06-REVIEW` F1.)* |
| Claude Code side today | `plugins/claude-code/` — **7** agents, **12** skills, **6** CC-only commands, `hooks/session-start.sh` | drift is **bidirectional**: behind on agents, *ahead* on skills/commands/hooks (`00-INDEX` finding #3) |
| What syncs them | `scripts/bridge/sync-to-claude.sh` — a **39-line naive `cp`** | file `wc -l` |
| OpenCode install path | `curl … install.sh \| bash -s <profile>` (52 KB bash) | `README.md:125` |
| OpenCode update path | `curl … update.sh \| bash` (10 KB bash) | `README.md:139` |
| Claude Code install path | `/plugin marketplace add darrenhinde/OpenAgentsControl` → `/plugin install oac` | `README.md:170-177` |
| Marketplace plugin source | `./plugins/claude-code` | `.claude-plugin/marketplace.json` |
| npm publish payload | `files[]` ships `.opencode/**`, `scripts/`, `bin/`, `registry.json`, `install.sh`, `VERSION` | root `package.json` |
| npm workspaces | `evals/framework`, `packages/cli`, `packages/compatibility-layer` | root `package.json` |
| Adapters already built | `Base`, `Claude`, `Cursor`, `Windsurf` | `packages/compatibility-layer/src/adapters/` |
| Core already built | `AdapterRegistry`, `AgentLoader`, `CapabilityMatrix`, `TranslationEngine` | `packages/compatibility-layer/src/core/` |
| Test runners in use | **vitest** (compat-layer, evals) **and** `bun:test` (cli) — split | test files below |
| Golden/snapshot tests | **none exist** in first-party code today | grep `toMatchSnapshot` → only `node_modules` |

### 0.1 The version mismatch (must be reconciled — see §2.7)

| Artifact | Declared version | File |
|----------|------------------|------|
| `VERSION` | **0.7.1** | `/VERSION` |
| root `package.json` | **0.7.1** | `/package.json` |
| `packages/cli` | **1.0.0** | `packages/cli/package.json` |
| `packages/compatibility-layer` | **0.1.0** | `packages/compatibility-layer/package.json` |
| `evals/framework` | **0.1.1** | `evals/framework/package.json` |
| marketplace entry `oac` | **1.0.0** | `.claude-plugin/marketplace.json` |
| CC plugin manifest | **1.0.2** | `plugins/claude-code/.claude-plugin/plugin.json` |

Five different version numbers for one product across seven files. The CC plugin cache
(1.0.2) is ahead of the marketplace declaration (1.0.0) it's supposedly built from, and both
are ahead of the "real" `VERSION` (0.7.1) that the installer advertises. **No single source
of truth for the version exists today** — this is itself a symptom the refactor must fix.

---

## 1. Impact Analysis

### 1.1 Blast radius by area

Ripple = what silently breaks if the change lands without care. Mitigation = the guardrail
this refactor must put in place first.

| Area | Change | Ripple effect | Mitigation |
|------|--------|---------------|------------|
| `.opencode/` (source→output) | Demoted from source of truth to a **generated** build target | Every doc/link/PR that edits `.opencode/*` directly is now editing generated output; contributor muscle-memory breaks; git churn if generated files are committed | Keep `.opencode/` generated **and committed** during transition (dual-write) so nothing 404s; add a `// GENERATED — edit /content` banner; CODEOWNERS/CI warns on hand-edits to `.opencode/**` |
| `install.sh` (52 KB) | Deleted; absorbed into `oac` CLI | `curl\|bash` URL in README + every blog/gist/star breaks; `installer-checks.yml` (shellcheck/syntax/e2e) references it; `package.json files[]` ships it; issues #308/#304/#237/#277/#321/#310 are all against it | **Do not delete until §2 Stage where CLI reaches parity.** Replace `install.sh` with a thin shim that `exec`s `npx oac init` (keeps the URL alive); retire `installer-checks.yml` jobs in the same PR that deletes the script |
| `update.sh` (10 KB) | Deleted; absorbed into `oac update` | README update URL breaks; `installer-checks.yml` syntax-checks it | Same shim strategy → `npx oac update`; already have `packages/cli/src/lib/installer-update.ts` + tests to build on |
| `scripts/bridge/sync-to-claude.sh` | Deleted | The **only thing that actually produces CC output today**; `plugins/claude-code/` stops updating | Delete **only after** `oac build --target claude` provably reproduces (and improves on) its output — golden test in §3.1 is the gate |
| `plugins/claude-code/` | Deleted; becomes `oac build --target claude` output | Marketplace `source: ./plugins/claude-code` points at a path that will no longer be hand-maintained; installed CC users pull from here | Keep the path populated by the **build** (committed output) so the marketplace keeps resolving; see §1.3 for the CC-user migration |
| `.claude-plugin/marketplace.json` | `source` still `./plugins/claude-code`, but content now generated; `version` (1.0.0) must track real version | Version drift worsens if marketplace and `plugin.json` are bumped by different hands | Generate `plugin.json` **and** sync `marketplace.json.version` from the single `VERSION` during `oac build --target claude`; CI asserts they match |
| `registry.json` (107 KB, root) | Moves to `/content/registry.json`; becomes generated from `/content/*` frontmatter | `update-registry.yml`, `validate-registry.yml`, `sync-docs.yml`, `post-merge-pr.yml` all reference root `registry.json`; the CLI installer reads it | Introduce generated registry at new path with a **root symlink/copy** for one release; migrate the 4 workflows in lockstep; `oac build` emits registry, `validate-registry` validates the emitted one |
| `.github/workflows/installer-checks.yml` | Retire (bash installer gone) | Loses shellcheck/e2e coverage of the install path | Replace with a **CLI e2e job**: `npx oac init` into a temp dir on macOS+Linux+Windows runners; reuse the intent of `scripts/tests/test-non-interactive.sh` |
| `.github/workflows/update-registry.yml` + `validate-registry.yml` | Repoint to `/content/registry.json`; validation becomes "registry == build output" | Stale registry / drift re-introduced if generation and validation disagree | Single generator; `validate` runs the generator and `git diff --exit-code` |
| `.github/workflows/pr-checks.yml` + `post-merge-pr.yml` | Version-bump semantics must key off the **new single `VERSION`** | Conventional-commit → semver bump logic currently bumps root `package.json`/`VERSION`; with monorepo publish it must bump the published packages too | Define release topology in §2.7; keep conventional-commit title check (it's good) |
| npm `package.json files[]` | Rewritten: ship `dist/` of `packages/*` + `/content` bundle, **not** `.opencode/**` + `install.sh` | Anyone doing `npm i -g` today gets `.opencode/`; changing payload changes what `oac` finds at runtime | Version the payload change as a **major** bump of the CLI; `oac doctor` detects legacy layout |
| `evals/` (448 files) + `evals/framework` (workspace) | Should stay behavioral tests, but it loads agents from `.opencode/` via `@opencode-ai/sdk` | If `.opencode/` layout/paths shift, `test:ci` smoke test and all `eval:sdk` runs break; root `npm test` = `cd evals/framework && npm run eval:sdk` | Evals keep pointing at **generated** `.opencode/` output (the build target), so they validate the pipeline end-to-end for free; pin the smoke-test agent path; see §1.4 |
| Two test runners (`vitest` + `bun:test`) | New `/packages/core` + `/packages/adapters` need a runner | Fragmentation: contributors unsure which to use; CI must run both | **Decision needed** (Open Q1). Recommend standardizing new packages on **vitest** (already used by compat-layer + evals; richer snapshot API for §3.1 golden tests) |
| Root docs (`README.md`, `CONTEXT_SYSTEM_GUIDE.md`, `COMPATIBILITY.md`, `ROADMAP.md`, `plugins/claude-code/*.md`) | Install/edit instructions change | Users follow stale curl/`/plugin` docs; contributors edit wrong tree | Docs updated **in the same stage** that changes the user-facing command; `sync-docs.yml` already exists to propagate |

### 1.2 Dependency ordering (what must change before what)

```
/content authored ──► packages/core (schema+loader) ──► packages/adapters ──► oac build
                                                                                  │
              golden test (§3.1) gates ─────────────────────────────────────────┤
                                                                                  ▼
   sync-to-claude.sh delete ◄── plugins/claude-code generated ◄── build --target claude
   install.sh → shim ◄────────── oac init parity ◄────────────── build --target opencode
   registry workflows repoint ◄─ /content/registry.json generated
   installer-checks retire ◄──── CLI e2e job green
```
Nothing on the right may happen before the thing on its left is proven. The golden test in
§3.1 is the single most important gate — it is the "left" of both delete arrows.

### 1.3 Migrating already-installed users without breakage

Two populations, two mechanisms, both must keep working through the whole migration.

**A. `curl | bash install.sh` users (OpenCode)**
- They have a `.opencode/` tree written by the bash installer and (maybe) a global `oac` bin.
- **Break risk:** deleting `install.sh` 404s the curl URL; changing the npm `files[]` payload
  changes what a reinstall produces.
- **Migration path:**
  1. Keep the `install.sh` URL live as a **shim** that `exec`s `npx oac init` (or prints a
     one-line upgrade notice). The bookmark/README curl command never breaks.
  2. `oac doctor` (exists: `packages/cli/src/**`) detects a legacy hand-installed `.opencode/`
     and offers `oac migrate` to reconcile it with the manifest-tracked layout.
  3. Reuse the existing **manifest** (`packages/cli/src/lib/manifest.ts` + `manifest.test.ts`)
     so updates are diff-based, not clobbering — this already exists and must be preserved.

**B. `/plugin install oac` users (Claude Code)**
- They installed from marketplace `source: ./plugins/claude-code` at some cached version
  (observed 1.0.2). CC re-resolves the marketplace ref on update.
- **Break risk:** if `plugins/claude-code/` stops being maintained (or its layout changes) but
  the marketplace still points there, CC users get a stale or malformed plugin.
- **Migration path:**
  1. `plugins/claude-code/` stays at the **same path**, now populated by `oac build --target
     claude` and committed — so `/plugin update` keeps resolving with no user action.
  2. The generated `plugin.json` fixes the drift (7 → full agent set) — this is a **visible
     upgrade** for CC users, delivered transparently through the existing marketplace channel.
  3. `marketplace.json.version` is stamped from the single `VERSION` so the update actually
     registers as a new version (today's 1.0.0-vs-1.0.2 drift would suppress the update).

> **Non-negotiable:** neither install URL nor the marketplace ref changes identity during the
> refactor. Users migrate by *doing nothing*; the payload behind the same entry point improves.

### 1.4 Evals impact (the free end-to-end check)

`evals/framework` (`@opencode-agents/eval-framework`, v0.1.1) is an npm workspace that loads
agents through `@opencode-ai/sdk` and runs behavioral suites; root `npm test` delegates to it
(`test:all` → `cd evals/framework && npm run eval:sdk`). Because it reads the **`.opencode/`
tree**, and `.opencode/` becomes **build output**, the evals automatically become an
integration test of the build pipeline: if `oac build --target opencode` produces a broken
agent, `test:ci` (the `smoke-test.yaml`, `--no-evaluators`) fails. **Do not rewire evals to
read `/content` directly** — keeping them on the generated output is what makes them prove the
build. Only pin: the smoke-test agent id and its path so a layout change is a loud failure.

### 1.5 PR & issue triage (18 open PRs, 37 open issues)

Classified into four buckets. **Land quick-wins first** (before touching architecture) to
shrink the conflict surface; **port** the good ideas into the new design; **close obsolete**
ones with a pointer to #206; **reject** what the locked decisions forbid.

#### OBSOLETE — superseded by the refactor (close with pointer to #206)
| Item | Why obsolete |
|------|-------------|
| **#298** (stalled CLI mega-PR, +19k, conflicting) | This refactor *is* the replacement, delivered incrementally. Salvage ideas, don't merge the blob — its size is the anti-pattern the locked decisions call out. |
| **#316** (opencode compat) | Compat is subsumed by the adapter architecture (`/packages/adapters`); re-express any specific fix as an adapter rule. |
| **#237 / #277 / #321** (installer bugs, if they're `install.sh` internals) | The bash installer is being deleted; fixing its internals is throwaway work. Verify each isn't a UX requirement that must be **ported** to `oac init` (see below). |

#### REJECTED — violate locked decision #2 (no hardcoded models)
| Item | Reason |
|------|--------|
| **#311**, **#324** | Hardcode model defaults. Canonical `model` field is `null` ⇒ tool default. Close with the locked-decision link. |

#### PORT — good change that must be re-expressed in the new design
| Item | Port target |
|------|-------------|
| **#326 / #328 / #309 / #312** (install.sh patches) | Each patch encodes a real install requirement (flags, platform handling, non-interactive mode). Extract the *requirement* and re-implement in `oac init`/`oac update`. Treat these PRs as the **spec for installer parity** (§2 Stage 4), then close. |
| **#325** (typescript.md, née #322) | Content change → lands in `/content/context/` once that dir exists; trivially re-homed. Can also land **now** in `.opencode/` and be swept into `/content` by the migration script (quick-win-friendly). |
| **#308 / #304 / #310** (installer bugs, if UX/behavior) | Any that describe *what the installer should do* (not *how the bash does it*) become acceptance criteria / test cases for the CLI installer and its e2e job. |

#### QUICK-WINS — independent, land before the refactor to de-risk
| Item | Note |
|------|------|
| **#325** (typescript.md) | Pure content; no architectural coupling. Merge now. |
| Any doc-only / registry-data PRs among the 18 | `validate-registry.yml` already gates them; low conflict risk. |
| **The version reconciliation itself** | Not a PR yet — do it as the very first chore (§2 Stage 0). |

> **Rule of thumb applied:** an item is *obsolete* if it edits a file slated for deletion in a
> way the new design already covers; *port* if it edits such a file but encodes a real
> requirement; *quick-win* if it touches neither the deleted files nor the schema.

---

## 2. Migration Staging Plan

Design constraint (locked decision #4): **every stage ships and is independently valuable.**
Explicitly *not* the #298 pattern (one +19k PR). Each stage is a handful of PRs and a release.

### Stage 0 — Reconcile versions & freeze the map *(chore; no behavior change)*
- **Goal:** one version number; agree the release topology; land pure quick-wins.
- **Deliverable:** single `VERSION` (or root `package.json.version`) becomes the source; a
  short `VERSIONING.md` (extends existing `.github/workflows/VERSION_BUMP_GUIDE.md`); merge
  #325 and any doc-only PRs; close #311/#324 (rejected) with rationale.
- **Ships to users:** nothing user-visible except the typescript.md content; a clean version.
- **Independently valuable:** stops the drift bleeding; unblocks honest release notes.
- **Exit criteria:** all seven version sites resolve from one number in CI; `git grep` for
  hardcoded versions is clean; rejected PRs closed.
- **Release:** patch bump (e.g. → 0.7.2) purely to prove the reconciled pipeline.

### Stage 1 — Stand up `/content` + `packages/core` (schema & loader) *(additive)*
- **Goal:** neutral source of truth exists and parses; **one** agent authored canonically.
- **Deliverable:** `/content/agents/code-reviewer.md` (the worked example from `00-INDEX.md`);
  `packages/core` = Zod `OpenAgentSchema` (moved/extended from
  `packages/compatibility-layer/src/types.ts`) + registry loader (from `core/AgentLoader.ts`).
  Nothing deleted; `.opencode/` still authoritative for everything else.
- **Ships to users:** nothing (internal). Optionally publish `@oac/core` as a library.
- **Independently valuable:** the schema + loader are usable/testable in isolation; contributors
  can start authoring in `/content` without any build wired.
- **Exit criteria:** `packages/core` builds; **schema-validation test** (§3, Layer 1) green for
  the one agent; CI runs the new package's tests.

### Stage 2 — Build pipeline + the minimal golden test *(the pipeline proof)*
- **Goal:** `oac build --target <opencode|claude>` produces output for the one agent, proven
  byte-stable by a golden snapshot.
- **Deliverable:** `packages/adapters` (adopt existing `Base/Claude/Cursor/Windsurf`); `oac
  build` command; **the minimal test in §3.1**: golden snapshot of `code-reviewer` → opencode
  + claude, plus a parse/manifest check that the CC output loads. `.opencode/` and
  `plugins/claude-code/` outputs are **generated and committed** but the legacy
  `sync-to-claude.sh` still runs in parallel (dual-write, not yet deleted).
- **Ships to users:** nothing visible yet; internally the CC plugin can already be regenerated.
- **Independently valuable:** this is the "does the whole idea work?" proof; unblocks every
  later delete.
- **Exit criteria:** §3.1 golden + manifest tests green in CI; generated `.opencode/`
  `code-reviewer` is **byte-identical** to the committed golden — **no escape hatch**. (The
  v1 wording "byte-identical *or diff-explained*" made the gate unfalsifiable — `06-REVIEW`
  G7 — and was deleted. Any difference between generated output and the hand-maintained file
  is resolved as an explicit, written merge decision in `09-MERGE-RULES.md` *before* the
  golden is committed; the gate itself only accepts byte-identity.) **Evals smoke-test still
  passes** against generated output.
- **Release:** minor bump; `oac build` is a real new capability.

### Stage 3 — MERGE both trees into `/content`; retire the bridge *(the big sweep, staged)*

> ⚠️ **This is a MERGE, not a copy** (`00-INDEX` finding #3; `06-REVIEW` C2/L1). Drift is
> **bidirectional**. `.opencode/` has 34 agents to CC's 7 — but `plugins/claude-code/` is
> *ahead* everywhere else: **12 skills** vs OpenCode's 4+2 (disjoint sets), hand-authored
> `<example>` blocks that exist nowhere in `.opencode/`, **6 CC-only commands**, and
> `hooks/session-start.sh` — the context-injection hook with no OpenCode equivalent.
> Seeding `/content/` from `.opencode/` alone destroys all of it.

- **Goal:** merge **both** source trees — `.opencode/` (34 agents, 20 commands, 4+2 skills,
  297 context entries) **and** `plugins/claude-code/` (7 agents, 12 skills, 6 commands,
  hooks) — into `/content` per the written conflict rules in `09-MERGE-RULES.md`; `oac build`
  then reproduces the full `.opencode/` **and** the full CC plugin from one source.
- **Deliverable:** a migration script driven by `09-MERGE-RULES.md` — **not** a blind copy of
  `.opencode/*`. Per the Stage-1 conflict rules: for the **7 dual-home agents**, the CC body
  wins (newer, richer, carries the `<example>` blocks) with OpenCode-only fields grafted;
  skills = **union of 16**, gated by `targets: []`; harvest the CC `<example>` blocks into
  `examples[]`, the 6 CC-only commands into `/content/commands/`, and `session-start.sh`'s
  six capabilities into `/content/hooks/`; every conflict resolved by a written rule and
  recorded in the PR. Then: delete `scripts/bridge/sync-to-claude.sh`;
  `plugins/claude-code/` now 100% generated (fixes the 7-vs-34 agent drift — a visible
  CC-user upgrade); `registry.json` generated to `/content/registry.json` with a root copy
  for compat; repoint `update-registry.yml` / `validate-registry.yml` / `sync-docs.yml` /
  `post-merge-pr.yml`.
- **Ships to users:** **CC users get the full agent set**; OpenCode users gain the merged
  CC-side content where applicable — via the existing channels.
- **Independently valuable:** kills the single worst bug in the repo (the naive-`cp` drift).
- **Exit criteria:** structural/manifest tests (§3, Layer 4) green for the whole set; evals full
  suite unaffected; `validate-registry` = "registry matches build output"; no hand-edits
  remain in `.opencode/` (CI guard); **no CC-only asset lost** — the 12 skills, 6 commands,
  all `<example>` blocks, and `session-start.sh`'s six capabilities are present in `/content`
  (checked against `01` §11's preservation checklist).
- **Release:** minor/major; announce CC plugin parity.

### Stage 4 — Absorb `install.sh` / `update.sh` into the CLI *(installer parity)*
- **Goal:** `oac init` / `oac update` reach parity with the bash installers; the ported
  requirements from #326/#328/#309/#312 and issues #308/#304/#310 are satisfied.
- **Deliverable:** parity feature-matrix (derived from those PRs/issues) implemented in
  `packages/cli` (build on existing `installer.ts` + `installer-update.ts` + `manifest.ts` +
  tests); replace `install.sh`/`update.sh` with thin shims that `exec npx oac`; **new CLI e2e
  workflow** (macOS+Linux+Windows `oac init` into temp dir) replacing `installer-checks.yml`.
- **Ships to users:** the curl URL now bootstraps the CLI; identical entry point, better guts.
- **Independently valuable:** closes the long tail of installer bug issues in one supported path.
- **Exit criteria:** CLI e2e job green on 3 OSes; real-tool headless-load test (§3, Layer 5)
  green; the ported PRs closed with "delivered by `oac init`."
- **Release:** **major** bump of the CLI/product (npm `files[]` payload changes — breaking).

### Stage 5 — Flip source-of-truth signals; finalize *(cleanup)*
- **Goal:** `/content` is unambiguously the source; `.opencode/` is unambiguously output.
- **Deliverable:** `// GENERATED` banners on all `.opencode/**` + `plugins/claude-code/**`;
  CODEOWNERS/CI hard-fail on hand-edits; README/docs rewritten to author-in-`/content`; retire
  `installer-checks.yml`; optional: stop committing generated trees and build on publish.
- **Ships to users:** documentation clarity; no functional change.
- **Independently valuable:** prevents regression to the old dual-source mess.
- **Exit criteria:** round-trip/idempotence + capability-matrix conformance tests (§3, Layers
  2–3) green; docs link-checked; a fresh clone → `oac build` → clean `git diff`.

### 2.7 Version bumps & release topology (resolving §0.1)

- **Single version source:** root `VERSION` (kept in sync with root `package.json.version` by
  the existing `post-merge-pr.yml` conventional-commit bump). Everything else *derives* from it.
- **Derived at build time:** `oac build --target claude` stamps
  `plugins/claude-code/.claude-plugin/plugin.json.version` **and**
  `.claude-plugin/marketplace.json` plugin `version` from `VERSION`. CI asserts equality
  (kills the 1.0.0-vs-1.0.2 drift permanently).
- **Package versions:** `packages/*` may retain independent semver *iff* published separately;
  simplest is **lockstep** (all publish at product `VERSION`). Decide in Open Q2.
- **Where bumps happen:** Stage 0 patch (reconcile); Stage 2 minor (`oac build`); Stage 4
  **major** (payload/CLI breaking change). `pr-checks.yml`'s conventional-commit title gate
  already maps type→bump — reuse it; no new mechanism.

---

## 3. Test Spec

Reuse the two patterns already in the tree:
- **vitest + `__tests__/` + `fixtures/`** (from `packages/compatibility-layer/src/**/__tests__/`;
  `convert.test.ts` already does temp-dir + roundtrip integration and is the template).
- **`bun:test` + `src/lib/*.test.ts`** (from `packages/cli`; `manifest.test.ts` is the template
  for manifest/parse checks).

Recommendation (Open Q1): author the **new** `core`/`adapters` tests in **vitest** — it already
backs compat-layer and evals and has first-class `toMatchSnapshot`/`toMatchFileSnapshot`, which
the golden layer needs.

### 3.1 MINIMAL FIRST — the pipeline-proof test (build at Stage 2, blocks everything)

The smallest test that proves the whole idea. **This is the only test that must exist before
any deletion.** Two assertions on **one** agent (`code-reviewer`):

1. **Golden snapshot — opencode target**
   `oac build --target opencode` on `/content/agents/code-reviewer.md` ⇒ compare emitted
   `.opencode/agent/subagents/code/reviewer.md` (+ `agent-metadata.json` sidecar) against a
   committed golden file. Asserts: `name` PascalCased, `mode: subagent`, `temperature: 0.1`
   preserved, `permission` granular block correct, **no `model:` line** (null → default).

2. **Golden snapshot — claude target**
   `oac build --target claude` ⇒ compare emitted `agents/code-reviewer.md` + generated
   `plugin.json` fragment against golden. Asserts: `name` kebab-cased, `tools: Read, Glob, Grep`
   allowlist, examples folded into `description`, and **exactly 2 warnings** emitted (from
   `CapabilityMatrix`), never silent: (a) `temperature` dropped — CC has no per-agent
   temperature; (b) the scoped `delegate` rule dropped with `Task`. *(v1 asserted one warning
   — corrected per `00-INDEX` worked example and `03` §2.8; `06-REVIEW` C4. `coder-agent` =
   4 warnings.)*

3. **Parse/manifest load check (CC output actually loads)**
   Parse the generated CC `plugin.json` + agent front-matter (reuse the `manifest.ts` reader
   pattern from `packages/cli/src/lib/manifest.test.ts`): valid JSON, required keys present,
   `version` matches `VERSION`, every referenced agent file exists on disk. Proves the output
   isn't just byte-stable but *loadable*.

Fixtures: one input (`/content/agents/code-reviewer.md`) + two golden outputs + two expected
warning strings. Golden files are regenerated with an `--update` flag (vitest snapshot update),
reviewed in PR — the human-diff of a golden change is a feature, not a chore.

### 3.2 DEFERRED — the layered spec (write now, build at the marked stage)

| # | Layer | Proves | Build at | Template to reuse |
|---|-------|--------|----------|-------------------|
| 0 | **Golden snapshot** (the §3.1 minimal) | Build output is byte-stable & loadable | **Stage 2** | new vitest `toMatchFileSnapshot` |
| 1 | **Schema validation** | Every `/content/*` file parses against the Zod IR | **Stage 1** | `compatibility-layer` `validate.test.ts` |
| 2 | **Round-trip / idempotence** | `build` is deterministic; re-import→re-build is stable | **Stage 5** | `convert.test.ts` roundtrip block |
| 3 | **Capability-matrix conformance** | Every unsupported-capability drop is *reported*, never silent | **Stage 2→3** | `core/CapabilityMatrix.ts` + a matrix test |
| 4 | **Structural / manifest** | Full generated tree has all files, valid manifest, versions synced | **Stage 3** | `cli` `manifest.test.ts` |
| 5 | **Real-tool headless load** | OpenCode/CC actually load the built output without error | **Stage 4** | evals `@opencode-ai/sdk` smoke pattern |
| 6 | **Behavioral evals** | Built agents still *behave* correctly | **already exists** | `evals/framework` `eval:sdk` suites |

Initial concrete cases per layer (2–3 each), to be authored when its stage arrives:

- **Layer 1 — Schema validation**
  1. `code-reviewer.md` validates; all required IR fields present.
  2. `model:` set to a string (not `null`) → **rejected** (enforces locked decision #2;
     ratified in `07-EXECUTION-PLAN` Stage 1: `model` is **not authorable** — cost intent
     moves to `inference.tier`, resolving `06-REVIEW` C3).
  3. Unknown capability key → validation error with the offending path.

- **Layer 2 — Round-trip / idempotence**
  1. `build → build` yields identical bytes (no timestamp/order nondeterminism).
  2. `.opencode` output → re-parse to IR → re-build → equals first build (semantic round-trip).
  3. Reordering `capabilities:` keys in source does not change output (canonical ordering).

- **Layer 3 — Capability-matrix conformance**
  1. `temperature` on claude target → exactly one warning, emitted and collected.
  2. `delegate:` on a target without task-delegation → warned, not dropped silently.
  3. A fully-supported agent → **zero** warnings (no false positives).

- **Layer 4 — Structural / manifest**
  1. Full build emits the same **count** of agents as `/content/agents/*` (guards the 7-vs-34
     drift regression directly).
  2. Manifest `sha256` per file matches on-disk content (reuse `sha256.test.ts`).
  3. `plugin.json.version` == `marketplace.json` version == `VERSION`.

- **Layer 5 — Real-tool headless load**
  1. `@opencode-ai/sdk` loads the built `code-reviewer` without error.
  2. CC plugin manifest passes whatever `/plugin` validation is scriptable (or a JSON-schema
     stand-in) in a temp `oac init`'d dir.
  3. `oac doctor` on a fresh `oac init` reports zero problems (3-OS e2e job).

- **Layer 6 — Behavioral evals** (unchanged)
  1. Existing `smoke-test.yaml` (`test:ci`) passes against **generated** `.opencode/`.
  2. `code-reviewer` denies write/edit/bash at runtime (matches its `capabilities`).
  3. Delegation to `contextscout` still fires.

### 3.3 CI wiring for tests

- New `packages/core` + `packages/adapters` join the root `test` script (add to the vitest
  projects); they run on `pr-checks.yml`.
- Golden layer runs on every PR; a golden diff requires an explicit `--update` commit (visible).
- `validate-registry.yml` becomes "run generator, `git diff --exit-code`" (registry == output).
- Replace `installer-checks.yml` bash jobs with the 3-OS `oac init` e2e job at Stage 4.
- Evals `test:ci` smoke test stays as the always-on end-to-end guard against the built output.

---

## Open Questions

1. **Test runner unification.** New `core`/`adapters` on **vitest** (matches compat-layer +
   evals, better snapshot API) while `packages/cli` stays on `bun:test`? Or migrate cli to
   vitest for one runner? Split runners mean two CI invocations and contributor confusion.
2. **Package version topology.** Lockstep-publish all `packages/*` at the single product
   `VERSION`, or keep independent semver per package (requires per-package release plumbing)?
   §2.7 assumes lockstep unless decided otherwise.
3. **Commit generated output, or build-on-publish?** Committing `.opencode/` +
   `plugins/claude-code/` keeps the curl/marketplace paths trivially resolvable during
   migration but adds git churn and invites hand-edits. Building only on `npm publish` is
   cleaner long-term but needs the marketplace to resolve a built artifact. Recommend commit
   through Stage 4, flip in Stage 5 — confirm.
4. **`install.sh` shim vs. hard cutover.** Keep the curl URL alive as a shim that `exec`s
   `npx oac init` indefinitely (max compatibility), or announce a deprecation window then 404
   it? Affects how long we maintain the shim.
5. **Registry location transition.** Root `registry.json` → `/content/registry.json`: symlink,
   copy, or hard move with a redirect? Four workflows + the CLI installer read the root path;
   pick the least-surprising transition.
6. **Which installer-bug issues are UX vs. bash-internal?** #308/#304/#237/#277/#321/#310 must
   each be read to decide *port* (becomes a CLI acceptance test) vs. *obsolete* (bash-internal,
   discarded). This triage needs a human pass before Stage 4.
7. **Evals coupling to `@opencode-ai/sdk`.** Evals validating the built OpenCode output is a
   feature, but it hard-couples the test suite to one tool's SDK. Acceptable, or do we want a
   tool-neutral behavioral harness eventually? (Out of scope for this refactor; flag it.)
