Kaynağa Gözat

docs(architecture): import canonical-refactor spec set and maintenance audits

darrenhinde 2 hafta önce
ebeveyn
işleme
f5c93ccbc8

+ 419 - 0
docs/architecture/canonical-refactor/00-INDEX.md

@@ -0,0 +1,419 @@
+# OAC Canonical Refactor — Master Spec Index
+
+> **Status:** Specification — v2 (post spec-pass reconciliation). No implementation yet.
+> **Goal:** Re-architect OAC from an OpenCode-specific project into a tool-agnostic,
+> npm-distributed system with a single neutral source of truth that generates
+> per-tool outputs (OpenCode, Claude Code, Cursor, Windsurf).
+
+This index is the **authoritative shared context** for all spec workstreams. Read it first.
+Where a spec document conflicts with this index, **this index wins**.
+
+---
+
+## Locked Decisions (do not relitigate)
+
+1. **Single source of truth = a new tool-neutral `/content/` directory.**
+   OpenCode, Claude Code, Cursor, Windsurf are all **generated build targets** via
+   `oac build --target <tool>`. `.opencode/` stops being source and becomes build
+   output. **No tool is privileged** — OpenCode is just another adapter.
+
+2. **No hardcoded models.** Canonical `model` defaults to `null` = "use the tool's
+   default model." Content never bakes in a model. Supersedes PRs **#311** and **#324**
+   (both rejected). *Open:* whether `model` should be authorable in `/content/` at all,
+   or exist only in the IR as always-null (see 02 Open Questions).
+
+   ⚠️ **v2.2 — GAP: `model: null` destroys deliberate cost tiering.** The shipped CC agents
+   are **5 `sonnet` + 2 `haiku`** — and the two haiku agents are `context-scout` and
+   `external-scout`. That is not an arbitrary model choice; it is an intentional **cost tier**
+   (cheap/fast models for retrieval-shaped work). Flattening both to `null` silently discards
+   that intent with **no replacement**, and would push the scouts onto an expensive default.
+   **Proposed resolution:** add a neutral **semantic tier** — `inference.tier: fast | balanced | deep`
+   (default `balanced`) — expressing *intent* rather than a model name. Adapters map tier →
+   whatever that tool's fast/deep model is; tools without tiers drop it with a warning. This
+   preserves the optimization while keeping content model-agnostic, satisfying decision #2 in
+   spirit rather than merely in letter. **Needs ratification** — it adds a field to the IR.
+
+3. **Minimal-tests-first.** Ship the smallest test proving the pipeline (golden snapshot
+   + parse/manifest check on ONE worked agent). Write the full layered test spec now;
+   implement later layers at the stages defined in `05`. Priority is core functionality
+   working end to end.
+
+4. **Incremental, each-stage-shippable.** Avoid another stalled mega-PR (cf. #298,
+   +19k lines, now conflicting). Every stage delivers standalone value.
+
+5. **Permissions = ordered rule list + scalar sugar (Option A).** ← *decided v2*
+   The neutral `capabilities` model is an **ordered list** of `{scope, decision}` rules
+   per capability, with scalar sugar for the simple case. Rationale: the previously
+   specified flat model (`edit: deny`) is **provably lossy** — verified against real
+   agents, it cannot express `coder-agent`'s deny-all-then-allowlist (which would break
+   its ability to run its own `router.sh`), loses per-agent security globs
+   (`**/*.env*`, `**/*.key`), and erases the deliberate distinction between
+   `coder-agent` (deny-by-default) and `openagent` (ask-by-default).
+   **Consequence:** safety globs live naturally in content — no separate `guards` hint
+   and no adapter-injected policy needed.
+
+6. **Context metadata stays MVI HTML-comment on disk.** ← *decided v2*
+   296 context `.md` files use a single compact line:
+   `<!-- Context: standards/code | Priority: critical | Version: 2.0 | Updated: … -->`
+   (verified: **286** HTML-comment, 3 YAML `---`, 7 neither). This is **deliberate** —
+   it is far more token-efficient than multi-line YAML and is read by the model on every
+   load. **Do NOT migrate to YAML frontmatter.** The IR parser normalizes this format
+   in-memory. **The authored on-disk format and the IR shape do not have to match.**
+
+   **Two parser traps — both verified. This is the highest-risk parser requirement in the project:**
+   - ⚠️ **Trap 1 (silent data loss):** a generic parser (`gray-matter`) finds **no frontmatter**
+     here and silently drops **all** priority data — and priority drives context ordering.
+   - ⚠️ **Trap 2 (false positives):** "grep the first marker anywhere" is *also* wrong.
+     `openagents-repo/core-concepts/agents.md` has a marker at **line 232** and
+     `categories.md` at **line 301** — both are *prose about the format*. A naive parser
+     assigns `standards/code | critical` as those files' real metadata.
+     **The parser MUST only honor a leading window** (line 1, or the first line after a
+     closing YAML `---`) and treat every later marker as body text.
+   - The 3 "YAML" files also carry an MVI marker at line 11 — they are **dual-format**, not YAML-only.
+
+   **Enum must not be strict:** `Priority: reference` exists in real content
+   (`core/workflows/lightweight-context-handoff-example.md`) outside the documented
+   `critical|high|medium|low` set. A strict `z.enum` rejects a real file. Also, 4 markers
+   omit `Version`/`Updated` — so only `Context` and `Priority` may be required.
+
+   **Verified distribution (v2.2 correction):** high **112**, critical **111**, low **34**,
+   medium **29**, reference **1** — counted over the *leading window only*.
+   ⚠️ *An earlier count of 116/113/34/31/1 was wrong: it grepped for the marker **anywhere**
+   in the file and swept in the line-232/301 prose-about-the-format markers — i.e. it
+   committed Trap 2 while attempting to verify Trap 2.* **Any script counting or parsing
+   these markers must apply the leading-window rule, including the "first line after a
+   closing YAML `---`" case (3 dual-format files carry their marker at line 11).**
+
+## Corrected Census (verified — earlier numbers were wrong)
+
+| Item | Correct | Previously claimed |
+|---|---|---|
+| Agents | **34** `.md` | 39 (= 34 + 5 `0-category.json`); one spec said 160 — false |
+| Commands | **20** `.md` | 29 (= 20 + 9 `.yaml` eval templates) |
+| Context | **296** `.md` (286 HTML-comment / 3 YAML / 7 neither) | 297 |
+| Skills (OpenCode) | **6** across **two** dirs (v2.2 correction) | 2 — wrong |
+| Skills (Claude Code plugin) | **12** (v2.2 correction) | ~11 — wrong |
+
+⚠️ **v2.2 — OpenCode skills live in TWO differently-named directories:**
+`.opencode/skill/` (2: `project-orchestration`, `task-management`) **and** `.opencode/skills/`
+(4: `context-manager`, `context7`, `smart-router-skill`, `task-management`) — with
+**`task-management` duplicated across both**. The earlier "2" was wrong and propagated into
+`02` and `03`. Which of the two `task-management` copies is authoritative is **unresolved**.
+
+⚠️ **v2.2 — 3 SYMLINKS in the context tree, previously unmentioned by every doc:**
+`core/standards/code.md → code-quality.md`, `docs.md → documentation.md`,
+`tests.md → test-coverage.md`. They (a) **break Windows checkouts** — git materializes them
+as text files containing the target path unless `core.symlinks=true`, which `04`'s Windows
+section does not cover; (b) break build determinism; and (c) are **the same three files** as
+`01`'s duplicate-id finding — so the proposed "collapse to `aliases[]`" would delete them and
+break the files referencing the alias paths directly. Needs an explicit decision.
+
+**Non-finding (corrected):** an earlier claim of *"committed `.env` files needing
+rotation"* is **false**. Verified: **0 tracked `.env` files**; `.env` is gitignored
+(`.gitignore:9-13`). No security incident. The only real point is build hygiene — a
+filesystem-globbing build must not sweep local `.env` files into `/content/`.
+
+## Verified Critical Findings
+
+1. **Bun defeats the npm goal (Stage 0 blocker).** `bin/oac.js` does
+   `execFileSync('bun', …)` → *"Bun is required."* **15 files** use Bun-only APIs
+   (`Bun.file`/`Bun.write` across `installer.ts`, `sha256.ts`, `ide-detect.ts`;
+   `import.meta.dir` in `bundled.ts`). `npm i -g` without Bun **fails outright** —
+   defeating the entire reason for npm distribution and likely explaining Windows
+   install bugs (#304, #312). Fix is mechanical: `node:fs/promises`, `node:crypto`,
+   `fileURLToPath(import.meta.url)`.
+
+2. **Dependency resolution is broken — 4 converging signals.** `add.ts` never walks
+   `dependencies[]`; `install.sh`'s recursive resolver swallows `jq` errors via
+   `|| echo ""` (unknown refs → **zero deps, no error**); issue **#310**'s reporter
+   independently diagnosed *"broken dependency chains from incomplete installation."*
+   Likely consequence: `/add-context` installs **none** of the three standards it tells
+   agents to follow. Fixing this will surface currently-hidden breakage.
+
+3. **Seeding `/content/` is a MERGE, not a copy.** Drift is **bidirectional** — Claude
+   Code is *ahead* in places: ~11 skills vs OpenCode's 2 (**disjoint sets**);
+   hand-authored `<example>` blocks existing nowhere in `.opencode/`; and
+   **`session-start.sh`** (skill catalogue, first-run onboarding, context-discovery
+   bootstrap, injection defense) has **no OpenCode equivalent** — arguably the most
+   important runtime feature, living in a file currently slated for deletion.
+   **Seeding from `.opencode/` alone destroys all of it.**
+
+4. **`ClaudeAdapter.ts` targets the wrong layout.** It writes the *project* layout
+   (`.claude/agents/*.md`, `.claude/config.json`); the authoritative format is the
+   *plugin* layout (`.claude-plugin/plugin.json`, flat `agents/`, `hooks/hooks.json`,
+   bundled `context/`). As written it produces something CC cannot load as a plugin.
+
+5. **`CapabilityMatrix.ts` lies in two rows.** `agentModes: claude = full` → should be
+   **partial** (plugin format has no per-agent `mode:`; every `agents/` file is a
+   subagent, so `role: primary` flattens). `agentCategories: claude` → likely **none**
+   (survives only as `plugin.json` keywords).
+
+6. **Version mismatch: 5 numbers across 7 files** (verified): `VERSION`/root `package.json`
+   = 0.7.1, `packages/cli` = 1.0.0, `compatibility-layer` = 0.1.0, `evals/framework` = 0.1.1,
+   marketplace = 1.0.0, **CC plugin cache = 1.0.2**. The cache is **ahead of the
+   marketplace entry it's built from**, which would *suppress* the `/plugin update` that
+   delivers any fix. Must be reconciled before any CC release.
+
+7. **`bundled.ts` breaks post-refactor.** It identifies the package root by the *absence*
+   of `registry.json` — but `registry.json` will ship *inside* the package.
+
+8. **`oac apply` is already ~80% of `oac build`** (`loadAgents → adapter.fromOAC → write`).
+   `build` is that pipeline promoted, re-rooted at `/content`, with OpenCode as a peer target.
+
+9. **The CLI is already safer than the bash scripts** — sha256 manifest gating means user
+   edits survive updates, whereas `update.sh` blind-curl-overwrites everything. Migration
+   selling point, not just parity.
+
+10. 🔴 **LIVE SECURITY GAP — `coder-agent` on Claude Code (exists in production today).**
+    `coder-agent.edit` lists only denies with no `*` rule ⇒ implicit default **allow** ⇒ CC
+    grants `Edit` and **all five security globs vanish** (`**/*.env*`, `**/*.key`,
+    `**/*.secret`, `node_modules/**`, `.git/**`). Verified: the shipped plugin declares
+    `tools: Read, Write, Edit, Glob, Grep`. **The live CC `coder-agent` can edit `.env`/`.key`/
+    `.secret` files that OpenCode's `coder-agent` is explicitly denied.** This is a defect in
+    the current product, not a refactor risk. No adapter-side fix exists (CC cannot express
+    path-scoped denies per-agent). **Open (03 Q3): should this BLOCK the build rather than warn?**
+
+11. **`coder-agent` bash → OMIT `Bash`, fail-closed.** Including it grants unrestricted shell
+    to an agent authored as "no shell except two exact commands" — a silent escalation from a
+    build step nobody inspects. Omitting it is a *loud, visible, recoverable* functional break.
+    The asymmetry decides: a wrong deny yields a broken agent; a wrong allow yields `rm -rf` in
+    a repo the owner believed sandboxed. Corroborated — the hand-authored plugin already ships
+    exactly this (no `Bash`).
+
+12. **`WindsurfAdapter.ts:511-516` is broken, but fails CLOSED** *(corrected — an earlier claim
+    that it escalates to unrestricted shell was wrong)*. It does
+    `const hasAllow = permObj.allow !== undefined` — but OpenCode permissions are **glob-keyed**
+    (`{"*": "deny", "cmd*": "allow"}`), so no key named `allow` ever exists and `hasAllow` is
+    *always* false. Every granular permission collapses to deny: over-restrictive, **not** a
+    privilege escalation. Real bug; needs replacing, not re-pointing.
+
+13. **The shipped CC agents hardcode `model: sonnet`** — violates locked decision #2. Must
+    become null/omitted when `/content/` is seeded from the CC side (see finding #3: seeding
+    is a merge, so CC-side content carries this in).
+    *Unverified:* `settings.json {"model": "opusplan"}` (#264, `40dd267`) is plausibly a
+    **no-op** — `model` may not be a supported plugin-settings key. Worth an issue; flagged,
+    not asserted.
+
+## Target Repo Shape
+
+```
+/content              ← THE source of truth (neutral; context keeps MVI HTML-comment metadata)
+    /agents /skills /commands /context
+    registry.json     ← component catalog
+/packages
+    /core             ← Zod IR schema + parser + registry loader   (from compatibility-layer/{types,core})
+    /adapters         ← opencode | claude | cursor | windsurf       (from compatibility-layer/adapters)
+    /cli              ← oac init|add|update|build|doctor            (from packages/cli, absorbs install.sh)
+/evals                ← behavioral tests (unchanged; a free end-to-end gate — see 05)
+```
+
+**To be deleted (only after the minimal golden test is green):** `install.sh`, `update.sh`,
+`scripts/bridge/sync-to-claude.sh`, `plugins/claude-code/` (generated), `.opencode/` as *source*.
+⚠️ `session-start.sh` must be **preserved into `/content/`** before `plugins/claude-code/` is deleted.
+
+## Existing Assets to Build On (extend, do NOT reinvent)
+
+- `packages/compatibility-layer/src/types.ts` — Zod `OpenAgentSchema` (the IR).
+  ⚠️ Known defects: `ModelIdentifierSchema = z.union([z.string(), z.string()])` is a
+  **no-op union** (line 116) with no null default — cannot enforce decision #2; three
+  overlapping identity carriers (frontmatter + metadata + `OpenAgentSchema.metadata` +
+  sidecar) → collapse to one flat schema; `category` is **conflated** — *domain*
+  (`core`/`development`) in agent metadata vs *distribution tier* (`essential`/`standard`)
+  in `registry.json` → split into `category` + `profiles`.
+- `packages/compatibility-layer/src/core/` — `TranslationEngine`, `AdapterRegistry`,
+  `AgentLoader`, `CapabilityMatrix`
+- `packages/compatibility-layer/src/adapters/` — `Base`, `Claude`, `Cursor`, `Windsurf`.
+  Warning templates already exist at `BaseAdapter.ts:187-208` — **reuse their exact output**.
+- `packages/compatibility-layer/src/mappers/` — `Context`, `Model`, `Permission`, `Tool`.
+  ⚠️ `ContextMapper` anchors `.opencode/context` as privileged base → must become `/content/`.
+- `packages/cli/src/**` — `init/add/update/doctor/status/list/apply` + `ide-detect` + `registry` + `installer`
+- `registry.json` — 107KB component catalog
+
+---
+
+## Worked Example (the alignment reference)
+
+Everyone must align to this. **Revised in v2 for the Option A capabilities model.**
+
+### Capabilities model
+
+```
+capability: <scalar>                    # sugar → [{ scope: "*", decision: <scalar> }]
+capability: [ { scope, decision }, … ]  # full ordered form
+delegate:   { name: decision, … }       # map sugar → [{ scope: name, decision }, … ]
+```
+`decision ∈ allow | deny | ask`. Simple agents use sugar and stay simple; the nested form
+appears **only** when scoping actually matters. Map sugar is accepted for **any** capability
+— which is OpenCode's exact on-disk shape, making the OpenCode importer a lossless desugar.
+`delegate` is not a special case: it is simply the capability whose scopes are agent ids
+rather than globs.
+
+#### Precedence — **last-match-wins** ⚠️ *STRONGLY INDICATED (v2.2) — NOT independently confirmed*
+
+⚠️ **Epistemic correction (v2.2).** v2.1 called this "CONFIRMED via three independent
+sources." That was an **overclaim** — all three are documents *this project wrote about
+itself*, not primary evidence of OpenCode's resolver. Worse, the primary citation
+(`12-MASTER-SYNTHESIS.md:432`) describes a **JSON array** permission format
+(`[{ "deny": "bash(**)" }, …]`) that **does not exist** in any agent — they all use YAML
+maps (`bash: { "*": "deny" }`). It is aspirational planning prose, not a description of
+reality. The conclusion is still probably right (first-match-wins is *provably* wrong
+against the corpus, and the authored order only makes sense under last-match), but
+**primary verification against OpenCode's actual resolver is still REQUIRED before Stage 1.**
+
+**In-repo sources (self-authored — corroborating, not probative):**
+- `docs/planning/12-MASTER-SYNTHESIS.md:432` — *"The `permission:` field uses **last-match-wins**
+  evaluation (same as OpenCode's native system)"*
+- `.opencode/context/openagents-repo/standards/permission-patterns.md:11` — *"OpenCode v1.1.1+
+  uses `permission:` … Rules follow **last-matching-wins** evaluation order."*
+- `.opencode/context/openagents-repo/standards/agent-frontmatter.md:45` — `"*": "ask"  # Catch-all (last-match-wins)`
+
+No further verification against OpenCode's resolver is required. Two agents reached this
+conclusion independently before the documentation was found.
+
+⚠️ **v2 correction — the earlier "first-match-wins" was wrong and security-regressing.**
+Real agents author **broad-first, specific-after**:
+```yaml
+coder-agent  bash: { "*": deny,  "…router.sh complete*": allow, … }
+openagent    bash: { "*": ask,   "rm -rf /*": deny, "sudo *": deny, … }
+```
+Under first-match-wins, `"*"` matches everything first and every later rule is unreachable:
+coder-agent's `router.sh` would be **denied** (breaking the exact behavior decision #5 exists
+to protect) and openagent's `sudo *` would degrade **deny → ask** (a security regression).
+**Last-match-wins** makes the authored content correct as written and maps 1:1 to OpenCode
+key order. *Most-specific-wins* also produces correct results and agrees with last-match-wins
+on all 34 agents, but requires defining a glob-specificity ranking.
+
+Last-match-wins needs **zero reordering transform** and requires no migration of the 34
+agents. First-match-wins would force order-reversal on both serialize *and* parse, plus an
+order-reversing migration of every agent file.
+
+⚠️ **Residual risk — order-as-semantics is an implicit contract.** YAML mappings are
+*unordered per spec*, so OpenCode's key order is a convention its parser honors, not a
+guarantee. Two concrete failure modes the IR MUST validate against:
+- **integer-like scopes** (e.g. `"8080"`) silently jump to the front under ECMAScript
+  integer-key ordering;
+- **duplicate scopes** silently collapse.
+
+Both are cheap to validate at parse time. See `02` Q2.
+
+#### Default semantics — **restriction list, not allowlist**
+
+⚠️ **v2 correction.** An absent capability does **not** mean `deny`. OpenCode's `permission`
+block is a *restriction list*: `openagent` declares **no `write:` key at all** yet relies on
+write being allowed (verified). Therefore:
+- absent capability → `[]` → **tool default**
+- no rule matched → **allow**
+
+Defaulting `write`/`edit`/`bash` to `deny` (as v1 did) would silently break real agents.
+
+### Neutral source — `/content/agents/code-reviewer.md`
+
+```yaml
+---
+id: code-reviewer
+name: Code Reviewer            # adapters slugify per tool
+role: subagent                 # primary | subagent
+category: development          # domain (distribution tier lives in registry `profiles`)
+description: Review code for security, correctness, and quality before commit.
+tags: [review, security, quality]
+capabilities:                  # INTENT, not any tool's syntax
+  read: allow                  # sugar
+  grep: allow
+  glob: allow
+  edit: deny
+  write: deny
+  bash: deny
+  delegate: { contextscout: allow }
+inference:
+  temperature: 0.1
+  model: null                  # null ⇒ tool default (no hardcoded models)
+context:
+  - { path: core/standards/security.md, priority: high }
+dependencies: [subagent:contextscout]
+examples:
+  - { context: "coder finished an auth service", user: "check it?", assistant: "Running code-review before commit." }
+---
+<system prompt / instructions — authored once>
+```
+
+### A scoping-heavy agent — `/content/agents/coder-agent.md` (why Option A exists)
+
+```yaml
+capabilities:
+  bash:                                          # deny-all-then-allowlist: flat CANNOT express this
+    - { scope: "*",                          decision: deny }
+    - { scope: "bash …/router.sh complete*", decision: allow }
+    - { scope: "bash …/router.sh status*",   decision: allow }
+  edit:
+    - { scope: "**/*.env*",     decision: deny }   # real security controls
+    - { scope: "**/*.key",      decision: deny }
+    - { scope: "**/*.secret",   decision: deny }
+    - { scope: "node_modules/**", decision: deny }
+    - { scope: ".git/**",       decision: deny }
+  delegate: { contextscout: allow, externalscout: allow, TestEngineer: allow }
+```
+
+### `oac build --target opencode` → `.opencode/agent/subagents/code/reviewer.md`
+
+```yaml
+---
+name: CodeReviewer                          # id → PascalCase
+description: Review code for security, correctness, and quality before commit.
+mode: subagent                              # role → mode
+temperature: 0.1                            # supported → preserved
+permission:                                 # capabilities → OpenCode granular block
+  bash:  { "*":     "deny" }
+  edit:  { "**/*":  "deny" }
+  write: { "**/*":  "deny" }
+  task:  { contextscout: "allow" }
+---
+```
+Metadata → `agent-metadata.json` sidecar; no `model:` line (null → OpenCode default).
+**OpenCode round-trips Option A exactly** — same expressive power, reserialized.
+
+### `oac build --target claude` → `agents/code-reviewer.md`
+
+```yaml
+---
+name: code-reviewer                         # id → kebab-case
+description: |
+  Review code for security, correctness, and quality before commit.
+  Examples:
+  <example>Context: coder finished an auth service. user: "check it?" …</example>
+tools: Read, Glob, Grep                     # capabilities allow-set → tools allowlist
+---
+```
+`context[]` → bundled into plugin `context/`, injected via SessionStart hook; contributes
+to `.claude-plugin/plugin.json`.
+⚠️ **2 warnings** *(corrected in v2.1 — was "1")*: (a) `temperature` dropped — CC has no
+per-agent temperature; (b) `delegate: {contextscout: allow}` is map sugar for a **scoped**
+rule, so dropping `Task` is a real loss and "never silent" demands a warning. The emitted
+file is identical either way; only the count changes. This is an expected consequence of
+Option A — under the old flat model, `delegate` carried no scope. **`coder-agent` = 4 warnings.**
+
+✅ **CC `settings.json` — question CLOSED, negative (v2.1).** Plugins **cannot ship
+permissions at all**: plugin `settings.json` supports only the `agent` and
+`subagentStatusLine` keys, and our CC target *is* a plugin — anything the adapter wrote
+would be ignored. Even via user settings it fails: rules are project-scoped (restoring
+coder-agent's intent needs `deny: ["Bash(*)"]` project-wide, breaking every other agent),
+and the agent's `tools:` allowlist gates the tool regardless.
+**Resolution: emit documentation, not configuration** — a `RECOMMENDED-PERMISSIONS.md` with
+an opt-in snippet, **deny rules only** (they over-apply in the *safe* direction; hoisting an
+`allow` would re-create the escalation one layer up).
+
+---
+
+## Spec Documents
+
+| File | Scope | Status |
+|------|-------|--------|
+| `01-feature-inventory.md` | Feature inventory; universal vs OpenCode-specific; ~150-item preservation checklist | ✅ v1 |
+| `02-canonical-schema.md` | Neutral Zod IR for all 7 content types | ✅ v2 (Option A + MVI parser) |
+| `03-adapter-specs.md` | Per-tool transforms, layouts, capability matrix, warnings | ✅ v2 (Option A) |
+| `04-cli-build-distribution.md` | CLI commands, build pipeline, npm distribution, install.sh parity | ✅ v1 |
+| `05-impact-migration-tests.md` | Impact, staged migration, minimal + deferred test spec | ✅ v1 |
+
+### Conventions
+- Ground every claim in real files (cite paths). Verify counts before asserting them.
+- End each doc with `## Open Questions` for anything needing a human decision.
+- Spec only — **no implementation code**.

+ 1503 - 0
docs/architecture/canonical-refactor/01-feature-inventory.md

@@ -0,0 +1,1503 @@
+# 01 — Feature & Content Inventory
+
+> **Owner:** Agent A
+> **Status:** Spec only — no implementation.
+> **Read first:** [`00-INDEX.md`](./00-INDEX.md) for locked decisions.
+>
+> **Purpose:** Exhaustive inventory of every feature and capability that exists today in
+> `.opencode/` (source), `registry.json`, and `plugins/claude-code/`. Each item is classified
+> **UNIVERSAL** (belongs in the neutral `/content/` schema) vs **OPENCODE-SPECIFIC**
+> (needs adapter handling) vs **AT RISK** (no obvious home — will be silently lost unless
+> a decision is made).
+>
+> The user's #1 requirement: **nothing gets lost.** Every row below must land somewhere.
+
+---
+
+## Legend
+
+| Tag | Meaning |
+|-----|---------|
+| 🟢 **UNIVERSAL** | Belongs in the neutral IR. All/most target tools express this concept. |
+| 🟡 **OPENCODE-SPECIFIC** | Real capability, but expressed in OpenCode syntax. Adapter must render it; other adapters may degrade it (with a warning). |
+| 🔵 **REGISTRY/BUILD** | Not agent-facing content — belongs to the catalog or build pipeline. |
+| 🔴 **AT RISK** | Currently has no home in the target architecture. **Needs a human decision** (see Open Questions). |
+
+---
+
+## 0. Census — what is actually on disk
+
+Counts verified by enumeration, not by trusting the docs.
+
+| Area | Path | Count | Notes |
+|------|------|-------|-------|
+| Agents (markdown) | `.opencode/agent/**/*.md` | **34** | Task brief said 39; 39 = 34 `.md` + 5 `0-category.json`. |
+| Category descriptors | `.opencode/agent/**/0-category.json` | **5** | `content`, `core`, `data`, `meta`, `subagents/development`. |
+| Agent metadata entries | `.opencode/config/agent-metadata.json` | **28** | 8 `type: agent`, 20 `type: subagent`. **6 disk agents have no entry.** |
+| Commands | `.opencode/command/**/*.md` | **20** | Task brief said 29; 29 = 20 `.md` + 9 `.yaml` eval templates. |
+| Context files | `.opencode/context/**/*` | **300** | 297 `.md` + `paths.json` + 2 JSON schemas. 79 are `navigation.md`. |
+| Skills (plural dir) | `.opencode/skills/*/SKILL.md` | **4** | `task-management`, `context-manager`, `context7`, `smart-router-skill`. |
+| Skills (singular dir) | `.opencode/skill/*/` | **2** | `project-orchestration`, `task-management` — **not in registry** (see §4.3). |
+| Tools | `.opencode/tool/*/index.ts` | **3** | `env`, `gemini`, `template`. |
+| Plugins | `.opencode/plugin/*.ts` | **2** | `notify.ts`, `agent-validator.ts` (+ `.disabled` twin). |
+| Plugins (plural dir) | `.opencode/plugins/*/` | **1** | `coder-verification`. |
+| Profiles | `.opencode/profiles/*/profile.json` | **5** | **Stale duplicates** of `registry.json#profiles` (see §8.2). |
+| Prompts | `.opencode/prompts/**` | **6 agents × N models** | Per-model prompt variants + eval results. |
+| Registry components | `registry.json#components` | **8 types / 245 entries** | agents 8, subagents 19, commands 17, tools 2, plugins 1, skills 4, contexts 191, config 3. |
+| CC plugin agents | `plugins/claude-code/agents/` | **7** | vs 34 in `.opencode/` — **the drift**. |
+| CC plugin skills | `plugins/claude-code/skills/` | **12** | vs 4 in `.opencode/skills/` — **CC is ahead here**. |
+
+> **Drift is bidirectional.** The common framing is "CC has 7 agents vs OpenCode's 34, CC is
+> behind." That is only half true: **CC has 12 skills vs OpenCode's 4**, and CC's session-start
+> context-injection has no OpenCode equivalent at all. The migration must merge *both*
+> directions, not just replay `.opencode/` into CC.
+
+---
+
+## 1. Agents & Subagents
+
+### 1.1 Frontmatter fields actually in use
+
+Measured across all 34 agent `.md` files (`awk` over the YAML block):
+
+| Field | Occurrences | What it does | Class | Post-migration home |
+|-------|-------------|--------------|-------|---------------------|
+| `name` | 34 | Display/invocation name. PascalCase in OpenCode (`CodeReviewer`). | 🟢 UNIVERSAL | `id` (canonical, kebab) + `name` (human). Adapters slugify: OpenCode→PascalCase, Claude→kebab. |
+| `description` | 34 | One-line purpose; drives delegation routing. | 🟢 UNIVERSAL | `description`. Claude adapter **appends `examples[]`** into this field (see §1.5). |
+| `mode` | 34 | `primary` \| `subagent`. | 🟢 UNIVERSAL | `role: primary \| subagent`. OpenCode adapter renames `role`→`mode`. |
+| `temperature` | 33 | Sampling temperature (`0`–`0.2` in practice). | 🟡 OPENCODE-SPECIFIC | `inference.temperature`. **Claude Code has no per-agent temperature → degradation warning** (already predicted in `00-INDEX.md`). |
+| `permission` | 24 | Granular per-tool, per-glob allow/ask/deny. | 🟡 OPENCODE-SPECIFIC | `capabilities` (intent). This is the **hardest transform** — see §1.3. |
+| `model` | **0** | — | 🟢 UNIVERSAL | `inference.model: null`. **Confirms Locked Decision #2:** no agent hardcodes a model today. PRs #311/#324 would have *introduced* the problem, not preserved it. |
+| `tools` | **0** | — | 🟢 UNIVERSAL | OpenCode expresses tool access via `permission`, not `tools`. Claude adapter derives `tools:` allowlist from `capabilities`. |
+| `hooks` | **0** | — | n/a | No agent declares hooks in frontmatter. Hooks exist only as *plugins* (§6) and CC `hooks.json` (§9.2). |
+| `id`,`category`,`type`,`version`,`author` | **1** | Only `eval-runner.md` inlines these. | 🔵 REGISTRY | Everywhere else these live in the `agent-metadata.json` sidecar. `eval-runner` is the **inconsistent outlier**. |
+
+**Representative — `.opencode/agent/subagents/code/reviewer.md`** (the `00-INDEX.md` worked example, verified verbatim):
+
+```yaml
+---
+name: CodeReviewer
+description: Code review, security, and quality assurance agent
+mode: subagent
+temperature: 0.1
+permission:
+  bash:  { "*": "deny" }
+  edit:  { "**/*": "deny" }
+  write: { "**/*": "deny" }
+  task:  { contextscout: "allow" }
+---
+```
+
+**Representative — `.opencode/agent/core/openagent.md`** (primary agent, security-critical denies):
+
+```yaml
+---
+name: OpenAgent
+description: "Universal agent for answering queries, executing tasks, and coordinating workflows across any domain"
+mode: primary
+temperature: 0.2
+permission:
+  bash:
+    "*": "ask"
+    "rm -rf *": "ask"
+    "rm -rf /*": "deny"
+    "sudo *": "deny"
+    "> /dev/*": "deny"
+  edit:
+    "**/*.env*": "deny"
+    "**/*.key": "deny"
+    "**/*.secret": "deny"
+    "node_modules/**": "deny"
+    ".git/**": "deny"
+---
+```
+
+**Representative — `.opencode/agent/subagents/code/coder-agent.md`** (the `allow`-listed-command pattern):
+
+```yaml
+permission:
+  bash:
+    "*": "deny"
+    "bash .opencode/skills/task-management/router.sh complete*": "allow"
+    "bash .opencode/skills/task-management/router.sh status*": "allow"
+  task:
+    contextscout: "allow"
+    externalscout: "allow"
+    TestEngineer: "allow"
+```
+
+> ⚠️ Two failure modes visible in this one block:
+> 1. The allowed bash commands **hardcode `.opencode/skills/...`** — a build-output path baked into
+>    neutral content. See §3.4.
+> 2. `task:` mixes **id-style** (`contextscout`) and **name-style** (`TestEngineer`) references in the
+>    same map. The neutral `delegate:` map must pick one (canonical `id`) and the OpenCode adapter
+>    must re-render to whatever OpenCode matches on. See §1.4.
+
+### 1.2 The three-way naming problem (highest-risk aliasing)
+
+For a single agent there are up to **four** distinct identifiers:
+
+| Surface | Value | Source |
+|---------|-------|--------|
+| Registry / dependency id | `tester` | `registry.json#components.subagents[].id` |
+| Filename | `test-engineer.md` | `.opencode/agent/subagents/code/test-engineer.md` |
+| Frontmatter `name` | `TestEngineer` | the agent file |
+| CC plugin name | `test-engineer` | `plugins/claude-code/agents/test-engineer.md` |
+
+`agent-metadata.json` confirms the split:
+
+```json
+"tester": {
+  "id": "tester", "name": "TestEngineer",
+  "category": "subagents/code", "type": "subagent",
+  "dependencies": ["context:standards-tests"]
+}
+```
+
+…while `registry.json` maps that same id to a *differently named file*:
+
+```json
+{ "id": "tester", "name": "TestEngineer",
+  "path": ".opencode/agent/subagents/code/test-engineer.md" }
+```
+
+**Class: 🔴 AT RISK.** A naive "filename = id" migration silently breaks every
+`subagent:tester` dependency edge and every `task: { TestEngineer: allow }` permission entry.
+The neutral schema needs an explicit `id` **plus** an `aliases[]` field, and the build must
+emit an id→name→path map per target. See Open Question **Q1**.
+
+### 1.3 `permission` → `capabilities` transform (the crown-jewel transform for agents)
+
+OpenCode's `permission` is a **three-valued, glob-scoped, per-tool matrix**:
+
+```
+permission.<tool>.<glob-pattern> = "allow" | "ask" | "deny"
+```
+
+The neutral `capabilities` shape in `00-INDEX.md` is **flatter**:
+
+```yaml
+capabilities:
+  read: allow
+  edit: deny
+  delegate: { contextscout: allow }
+```
+
+Concrete losses if the flat shape is adopted as-is:
+
+| OpenCode construct | Example | Survives flat `capabilities`? |
+|---|---|---|
+| Tri-state `ask` | `bash: { "*": "ask" }` | ⚠️ Only if the enum is `allow\|ask\|deny`, **not** `allow\|deny`. Claude has no `ask` → maps to prompt-on-use or `deny`. |
+| Glob scoping | `edit: { "**/*.env*": "deny" }` | ❌ **Lost.** Flat `edit: deny` cannot express "deny only secrets, allow the rest". |
+| Deny-by-default + narrow allow | `bash: {"*":"deny", "bash .../router.sh complete*":"allow"}` | ❌ **Lost.** This is a *security control* on `coder-agent`. |
+| Ordered precedence | `"*": "ask"` then `"rm -rf /*": "deny"` | ❌ **Lost.** Specificity/ordering semantics disappear. |
+
+**Class: 🟡 OPENCODE-SPECIFIC, but 🔴 AT RISK of silent loss.**
+
+**Recommendation to Agent B:** `capabilities.<tool>` must accept **either** a scalar
+(`allow|ask|deny`) **or** an ordered **rule list**:
+
+```yaml
+capabilities:
+  bash:
+    - { pattern: "*",          effect: ask }
+    - { pattern: "sudo *",     effect: deny }
+  edit:
+    - { pattern: "**/*.env*",  effect: deny }
+    - { pattern: "**/*",       effect: allow }
+```
+
+The scalar form is sugar for `[{pattern: "**/*", effect: <v>}]`. The OpenCode adapter renders
+this back losslessly; the Claude adapter collapses to a `tools:`/`disallowedTools:` allowlist
+**and emits a warning naming each dropped glob rule**. This makes the loss *known and reported*,
+per the `00-INDEX.md` principle. See Open Question **Q2**.
+
+### 1.4 `permission.task` → `delegate`
+
+`permission.task` is the **delegation graph** — which subagents an agent may call.
+
+| Aspect | Detail | Class | Home |
+|---|---|---|---|
+| Delegation allowlist | `task: { contextscout: "allow" }` | 🟢 UNIVERSAL | `capabilities.delegate` |
+| Delegation denylist | `task: { "*": "deny" }` (contextscout) | 🟢 UNIVERSAL | `capabilities.delegate` with `"*"` rule |
+| Mixed id/name refs | `contextscout` vs `TestEngineer` | 🔴 AT RISK | Canonicalize to `id`; adapter re-renders. |
+| Claude Code equivalent | CC has no per-agent delegation allowlist; it has `disallowedTools: Task` (all-or-nothing) | 🟡 | Claude adapter: any `delegate` entry ⇒ `Task` in `tools`; a `delegate` denylist ⇒ `Task` in `disallowedTools` **+ warning** that per-target granularity was dropped. |
+
+### 1.5 Claude Code agent frontmatter (what the neutral schema must *also* feed)
+
+`plugins/claude-code/agents/code-reviewer.md` uses a **different field set** that the neutral
+schema must be able to produce:
+
+```yaml
+---
+name: code-reviewer
+description: |
+  Review code for security vulnerabilities, correctness, and quality. Use after implementation is complete and before committing.
+  Examples:
+  <example>
+  Context: coder-agent has finished implementing a new auth service.
+  user: "The auth service is done, can you check it?"
+  assistant: "I'll run the code-review skill to have code-reviewer validate it before we commit."
+  <commentary>Implementation is complete — code-reviewer validates before commit.</commentary>
+  </example>
+tools: Read, Glob, Grep
+disallowedTools: Write, Edit, Bash, Task
+model: sonnet
+---
+```
+
+| CC field | Neutral source | Class | Notes |
+|---|---|---|---|
+| `name` (kebab) | `id` | 🟢 | Adapter slugifies. |
+| `description` (multiline + `<example>` blocks) | `description` + `examples[]` | 🟢 | **`examples[]` is a first-class neutral field** — CC folds it into `description`; OpenCode drops it. Today these examples exist **only** in the CC files and would be lost if `.opencode/` is treated as the sole migration source. |
+| `tools` (allowlist) | `capabilities` allow-set | 🟢 | Derived. |
+| `disallowedTools` | `capabilities` deny-set | 🟢 | Derived. |
+| `model: sonnet` | `inference.model` | 🔴 **VIOLATES Locked Decision #2** | CC agents **hardcode `model: sonnet`**. Under `model: null` the build must emit **no** `model:` line. This is a deliberate behavior change — confirm with Open Question **Q3**. |
+
+> 🔴 **Preservation alert:** the `<example>` blocks in all 7 CC agents are hand-authored content
+> that exists **nowhere in `.opencode/`**. If `/content/` is seeded only from `.opencode/`,
+> **these are lost.** They must be harvested from `plugins/claude-code/agents/` into `examples[]`.
+
+### 1.6 `0-category.json` — category descriptors
+
+5 files, e.g. `.opencode/agent/core/0-category.json`:
+
+```json
+{
+  "name": "Core Agents",
+  "description": "System-level agents with Open branding - the Operating System",
+  "icon": "⚙️",
+  "agents": {
+    "openagent": {
+      "description": "Universal task coordinator",
+      "commonSubagents": ["subagents/core/task-manager", "subagents/code/*"],
+      "commonTools": ["gemini", "env"],
+      "commonContext": ["core/essential-patterns", "core/workflows/*", "core/standards/*"]
+    }
+  }
+}
+```
+
+| Field | What it does | Class | Home |
+|---|---|---|---|
+| `name`, `description`, `icon` | Category display metadata for install UX | 🔵 REGISTRY | `registry.json#categories[]` — **must gain `icon` + `order` + `status`**, which it lacks today. |
+| `order` | Display sort (only in `subagents/development`) | 🔵 REGISTRY | `registry.json#categories[].order` |
+| `status` | `active` (only in `subagents/development`) | 🔵 REGISTRY | `registry.json#categories[].status` |
+| `agents.<id>.description` | Per-agent blurb — **duplicates** the agent's own `description` | 🔴 DUPLICATE | Delete; single-source from the agent file. |
+| `agents.<id>.commonSubagents` | Suggested delegation targets, **supports `*` globs** | 🔴 AT RISK | Overlaps `dependencies` + `capabilities.delegate` but is **advisory, not enforced**. No consumer found. See **Q4**. |
+| `agents.<id>.commonTools` | Suggested tools | 🔴 AT RISK | Same — no consumer found. |
+| `agents.<id>.commonContext` | Suggested context, **supports `*` globs** | 🔴 AT RISK | Overlaps `context[]`. No consumer found. |
+
+> **Finding:** `0-category.json` is **read by nothing** — no reference in `install.sh`,
+> `registry.json`, or `scripts/`. It is *latent documentation*. Its category metadata
+> (`icon`/`order`/`status`) is genuinely useful and **richer than `registry.json#categories`**
+> (which is a flat `id: "description string"` map). Decision needed: promote or delete. See **Q4**.
+
+### 1.7 `agent-metadata.json` sidecar
+
+`.opencode/config/agent-metadata.json` — 28 entries + a `defaults` block.
+
+```json
+{
+  "$schema": "https://opencode.ai/schemas/agent-metadata.json",
+  "schema_version": "1.0.0",
+  "agents": { "openagent": { "id": "openagent", "name": "OpenAgent", "category": "core",
+    "type": "agent", "version": "1.0.0", "author": "opencode",
+    "tags": ["universal", "coordination", "primary"],
+    "dependencies": ["subagent:task-manager", "context:standards-code", ...] } },
+  "defaults": {
+    "agent":    { "version": "1.0.0", "author": "opencode", "type": "agent",    "tags": [] },
+    "subagent": { "version": "1.0.0", "author": "opencode", "type": "subagent", "tags": [] }
+  }
+}
+```
+
+| Field | What it does | Class | Home |
+|---|---|---|---|
+| `id` | Canonical id (dependency-graph key) | 🟢 UNIVERSAL | Neutral `id` — **promote into the agent's own frontmatter**. |
+| `name` | Display name | 🟢 UNIVERSAL | Neutral `name`. |
+| `category` | Grouping. **Two incompatible formats:** `"core"` (flat) vs `"subagents/core"` (path) | 🔴 AT RISK | Neutral `category`. **Must normalize** — see **Q5**. |
+| `type` | `agent` \| `subagent` | 🟢 UNIVERSAL | Redundant with `role`/`mode`. Collapse into `role`. |
+| `version` | SemVer per agent (`1.0.0`, `task-manager` is `2.0.0`) | 🔵 REGISTRY | Neutral `version`; flows to registry. |
+| `author` | Attribution (`opencode` for all 28) | 🔵 REGISTRY | Neutral `author`. Note: value `"opencode"` is a **tool name used as an author name** — rename to `oac`? **Q6**. |
+| `tags` | Search/filter | 🟢 UNIVERSAL | Neutral `tags[]`. |
+| `dependencies` | `subagent:x` / `context:y` edges | 🟢 UNIVERSAL | Neutral `dependencies[]`. **The crown jewel — see §7.** |
+| `defaults.*` | Fallbacks when an entry omits fields | 🔵 REGISTRY | Build-time defaults in the Zod schema (`.default()`). |
+
+> **The sidecar exists only because OpenCode's agent schema rejects unknown frontmatter keys.**
+> That is an *OpenCode adapter constraint*, not a content constraint. In `/content/`, all of this
+> belongs **in the agent's own frontmatter** (single file, single source). The OpenCode adapter
+> then **splits** it back out into `agent-metadata.json` at build time — exactly as
+> `00-INDEX.md` states ("Plus metadata split into `agent-metadata.json` sidecar").
+>
+> **This inverts today's authoring model** and is the single biggest ergonomic win of the refactor.
+
+### 1.8 🔴 Agents with NO metadata and NO registry entry
+
+Verified by set-difference between disk, `agent-metadata.json`, and `registry.json`:
+
+| Agent | File | In metadata? | In registry? |
+|---|---|---|---|
+| `adr-manager` | `.opencode/agent/subagents/planning/adr-manager.md` | ❌ | ❌ |
+| `architecture-analyzer` | `.opencode/agent/subagents/planning/architecture-analyzer.md` | ❌ | ❌ |
+| `contract-manager` | `.opencode/agent/subagents/planning/contract-manager.md` | ❌ | ❌ |
+| `prioritization-engine` | `.opencode/agent/subagents/planning/prioritization-engine.md` | ❌ | ❌ |
+| `story-mapper` | `.opencode/agent/subagents/planning/story-mapper.md` | ❌ | ❌ |
+| `stage-orchestrator` | `.opencode/agent/subagents/core/stage-orchestrator.md` | ❌ | ❌ |
+| `test-engineer` | `.opencode/agent/subagents/code/test-engineer.md` | ⚠️ as `tester` | ⚠️ as `tester` |
+| `batch-executor` | `.opencode/agent/subagents/core/batch-executor.md` | ✅ | ❌ **registry only** |
+
+**6 agents are entirely invisible to the installer** — they ship in the repo but no profile can
+install them. The whole `subagents/planning/` category (5 agents) plus `stage-orchestrator` are
+orphans, despite `.opencode/docs/agents/planning-agents-guide.md` and
+`.opencode/skill/project-orchestration/workflows/planning-agents.md` documenting them as a
+feature.
+
+**Migration decision required:** are these (a) real features to be registered, or (b) dead code
+to be deleted? Silently carrying them into `/content/` re-creates the mess. See **Q7**.
+
+---
+
+## 2. Commands
+
+20 markdown commands under `.opencode/command/` (+9 `.yaml` eval templates counted in the "29").
+
+### 2.1 Frontmatter fields in use
+
+| Field | Occurrences | What it does | Class | Home |
+|---|---|---|---|---|
+| `description` | 18 | Command purpose; shown in the picker | 🟢 UNIVERSAL | `description` |
+| `tags` | 3 | Search/filter | 🟢 UNIVERSAL | `tags[]` |
+| `dependencies` | 3 | `subagent:` / `context:` edges | 🟢 UNIVERSAL | `dependencies[]` |
+| `id`,`name`,`type`,`category`,`version` | 1 | Only `analyze-patterns.md` inlines these | 🔵 REGISTRY | Neutral frontmatter (same inversion as agents). |
+| `mode`,`temperature`,`tools`,`permissions` | 2 | **Only in `new-agents/README.md` and `templates/agent-template.md`** | 🔵 NOT COMMANDS | These are *agent templates* misfiled under `command/`. See §2.3. |
+
+**Two commands carry no frontmatter at all** beyond `description` — the minimum viable command
+is literally `description:` + a markdown body.
+
+**Representative — `.opencode/command/context.md`** (block-style deps):
+
+```yaml
+---
+description: Context system manager - harvest summaries, extract knowledge, organize context
+tags:
+  - context
+  - knowledge-management
+  - harvest
+dependencies:
+  - subagent:context-organizer
+  - subagent:contextscout
+---
+```
+
+**Representative — `.opencode/command/add-context.md`** (flow-style tags + **path-style deps**):
+
+```yaml
+---
+description: Interactive wizard to add project patterns using Project Intelligence standard
+tags: [context, onboarding, project-intelligence, wizard]
+dependencies:
+  - subagent:context-organizer
+  - context:core/context-system/standards/mvi.md
+  - context:core/context-system/standards/frontmatter.md
+  - context:core/standards/project-intelligence.md
+---
+```
+
+> 🔴 **Critical inconsistency.** `add-context.md` uses **full-path** context refs
+> (`context:core/context-system/standards/mvi.md`, with `.md`), while `agent-metadata.json`
+> uses **short-id** refs (`context:standards-code`). Both are fed to the same
+> `resolve_dependencies()`. See §7.3 — this is a **live bug**, not just a style nit.
+
+### 2.2 Command body structure
+
+Bodies are **prompt templates** using a house XML-ish DSL. From `.opencode/command/context.md`:
+
+```markdown
+<critical_rules priority="absolute" enforcement="strict">
+  <rule id="mvi_strict">
+    Files MUST be <200 lines. Extract core concepts only ...
+  </rule>
+  <rule id="lazy_load">
+    ALWAYS read required context files from .opencode/context/core/context-system/ BEFORE executing operations.
+  </rule>
+</critical_rules>
+
+<execution_priority>
+  <tier level="1" desc="Safety & MVI"> ... </tier>
+  <tier level="2" desc="Core Operations"> ... </tier>
+</execution_priority>
+```
+
+| Feature | Class | Home |
+|---|---|---|
+| Markdown body as prompt | 🟢 UNIVERSAL | Neutral body (verbatim). |
+| `<critical_rules>` / `<rule id=… enforcement=…>` DSL | 🟢 UNIVERSAL | Body prose — **passes through untouched**. Not schema. |
+| `<execution_priority>` / `<tier>` DSL | 🟢 UNIVERSAL | Body prose — passes through. |
+| `@rule.id` cross-references (e.g. `@critical_rules.mvi_strict`) | 🟢 UNIVERSAL | Body prose. |
+| Hardcoded `.opencode/context/...` inside bodies | 🔴 **AT RISK** | **11 command files** embed `.opencode/` paths in prose. See §3.4. |
+
+> The body DSL is **tool-agnostic prose** — it works because LLMs read it, not because
+> OpenCode parses it. It needs **zero** adapter work. Good news: the highest-value content
+> (the actual prompt engineering) is already portable.
+
+### 2.3 🔴 Misfiled agent templates
+
+`.opencode/command/openagents/new-agents/` is **not commands**. It contains:
+
+- `templates/agent-template.md` — an *agent* scaffold with `mode`/`temperature`/`tools`/`permissions`
+- `templates/context-template.md` — a *context* scaffold
+- `templates/test-{1..8}-*.yaml` + `test-config-template.yaml` — **9 eval scenario templates**
+  (planning-approval, context-loading, incremental, tool-usage, error-handling,
+  extended-thinking, compaction, completion)
+- `create-agent.md`, `create-tests.md`, `README.md` — the actual commands
+
+The 8 numbered YAML tests encode **OAC's agent quality bar** (every new agent should pass these
+8 scenarios). This is real IP filed in the wrong place and **not in the registry**.
+
+| Item | Class | Home |
+|---|---|---|
+| `create-agent.md`, `create-tests.md` | 🟢 UNIVERSAL | `/content/commands/` |
+| `agent-template.md`, `context-template.md` | 🔵 BUILD | Scaffolding templates → `/packages/cli` templates (used by `oac add`). |
+| `test-{1..8}-*.yaml` | 🔴 AT RISK | Belongs with `/evals`. Not registered, not run by CI as far as can be seen. See **Q8**. |
+
+---
+
+## 3. Context System — **DEEP DIVE** 🏆
+
+> This is OAC's crown jewel and the most at-risk asset: **300 files, 79 navigation hubs,
+> 191 registry entries, and a metadata convention that is not YAML frontmatter.**
+
+### 3.1 Context metadata is an HTML comment, not frontmatter
+
+Standard defined in `.opencode/context/core/context-system/standards/frontmatter.md`:
+
+```markdown
+<rule id="frontmatter_required" enforcement="strict">
+  ALL context files MUST start with:
+
+  <!-- Context: {category}/{function} | Priority: {level} | Version: X.Y | Updated: YYYY-MM-DD -->
+</rule>
+```
+
+Live example — `.opencode/context/core/standards/code-quality.md` line 1:
+
+```markdown
+<!-- Context: standards/code | Priority: critical | Version: 2.0 | Updated: 2025-01-21 -->
+```
+
+| Component | Values | What it does | Class | Home |
+|---|---|---|---|---|
+| `Context: {category}/{function}` | `standards/code`, `core/mvi`, … | Dual taxonomy: **category** = domain, **function** = file type (`concepts`/`examples`/`guides`/`lookup`/`errors`) | 🟢 UNIVERSAL | Neutral `category` + `function`. |
+| `Priority` | `critical` \| `high` \| `medium` \| `low` | **Load-ordering + budget signal.** Documented distribution: critical=80% of use cases, high=15%, medium=4%, low=1%. | 🟢 UNIVERSAL | Neutral `priority` — **already in the `00-INDEX.md` worked example** (`context: [{path, priority}]`). |
+| `Version` | `X.Y` | Change tracking | 🟢 UNIVERSAL | Neutral `version`. ⚠️ **`X.Y`, not SemVer `X.Y.Z`** — conflicts with agent `version: 1.0.0`. See **Q9**. |
+| `Updated` | `YYYY-MM-DD` | Staleness signal; "must match metadata section" | 🟢 UNIVERSAL | Neutral `updated`. |
+
+> **Why an HTML comment and not YAML?** Because context files are **read as prose by agents**.
+> A `<!-- -->` header renders invisibly in every markdown viewer while staying machine-parseable.
+> YAML frontmatter would display as a table or raw text in some renderers.
+>
+> **Migration risk 🔴:** a generic markdown/frontmatter parser (e.g. `gray-matter`) will find
+> **no frontmatter** on ~297 context files and treat `Priority`/`Version`/`Updated` as body text.
+> **All priority information silently vanishes**, and priority is what drives context ordering.
+> The neutral parser **must** implement a dedicated HTML-comment reader. This is the single
+> most likely way the refactor silently destroys the crown jewel. See **Q10**.
+
+**Compliance is not universal.** The frontmatter-key census over non-navigation context files
+shows only ~16 files with a `description:` key and a long tail of *false positives* — lines like
+`Agent:`, `User:`, `Voice:`, `--text-muted:`, `curl:` matched as "frontmatter keys" because they
+are **body content** (dialogue transcripts, CSS variables, code samples). Real finding:
+**most context files carry only the HTML-comment header**, and a handful (`.opencode/context/tasks/`,
+some `openagents-repo/` files) additionally use YAML frontmatter with `id`/`type`/`category`/`model`/`tools`.
+**Two metadata conventions coexist in one tree.** See **Q10**.
+
+### 3.2 `navigation.md` — the discovery backbone
+
+**79 `navigation.md` files** — one per directory, at every level. This is a **hand-maintained
+routing tree**, not an index generated from the filesystem.
+
+Root — `.opencode/context/navigation.md`:
+
+```markdown
+<!-- Context: core/navigation | Priority: critical | Version: 1.0 | Updated: 2026-02-15 -->
+# Context Navigation
+
+**New here?** → `openagents-repo/quick-start.md`
+
+## Structure
+.opencode/context/
+├── core/                   # Universal standards & workflows
+├── openagents-repo/        # OpenAgents Control repository work
+...
+
+## Quick Routes
+| Task | Path |
+|------|------|
+| **Write code** | `core/standards/code-quality.md` |
+| **Review code** | `core/workflows/code-review.md` |
+| **Delegate task** | `core/workflows/task-delegation-basics.md` |
+```
+
+| Feature | What it does | Class | Home |
+|---|---|---|---|
+| Per-directory `navigation.md` | Human+agent routing hub | 🟢 UNIVERSAL | Neutral content, verbatim. |
+| **Task→path "Quick Routes" table** | **Intent-based routing** — the actual discovery mechanism | 🟢 UNIVERSAL | Verbatim. This is *the* feature. |
+| Registry id `root-navigation` | Makes the root hub installable | 🔵 REGISTRY | Registry entry. |
+| Directory-tree ASCII diagram | Orientation | 🔴 AT RISK | **Hardcodes `.opencode/context/`** — wrong after any rename. |
+| Relative path links (`core/navigation.md`) | Chain traversal | 🟢 UNIVERSAL | Portable **because relative**. |
+
+> **Design insight worth preserving explicitly:** navigation uses **relative** links between
+> hubs (`core/navigation.md`) but the *root* hub hardcodes the absolute tree
+> (`.opencode/context/`). So the **chain is portable; only the entry point is not.** The adapter
+> only needs to rewrite the root, not all 79. This is a much smaller job than it first appears —
+> and worth stating so nobody "fixes" all 79 files.
+
+### 3.3 Context discovery / loading — the runtime chain
+
+Two *different* discovery systems exist today.
+
+**(a) OpenCode: `paths.json` + `@`-reference.** `.opencode/context/core/config/paths.json`:
+
+```json
+{
+  "description": "Additional context file paths - agents load this via @ reference for dynamic pathing",
+  "paths": { "local": ".opencode/context", "global": "~/.config/opencode/context" }
+}
+```
+
+| Feature | Class | Home |
+|---|---|---|
+| local + global context roots | 🟢 UNIVERSAL | Neutral config; adapter rewrites values per tool. |
+| Registry id `context-paths-config` | 🔵 REGISTRY | Registry entry — **the only non-`.md` context component** (fixed by PR #252 per git log; the `expand_context_wildcard` `sub("\\.md$";"")` strips `.md`, so a `.json` context is a special case). |
+| `@`-reference loading | 🟡 OPENCODE-SPECIFIC | OpenCode's `@file` syntax. Claude uses the session-start hook instead (§9.2). |
+
+**(b) Claude Code: `.oac.json` + a documented protocol.**
+`plugins/claude-code/skills/context-discovery/context-discovery-protocol.md`:
+
+```
+.oac.json exists?  YES → read context.root → done (fast path)
+                   NO  → run discovery chain
+                          Found?  YES → signal main agent to write .oac.json (if project-local)
+                                  NO  → return setup tips
+```
+
+```json
+{ "version": "1", "context": { "root": ".claude/context" } }
+```
+
+| Feature | What it does | Class | Home |
+|---|---|---|---|
+| `.oac.json` fast path | Cache the resolved context root | 🟢 UNIVERSAL | **Should be the neutral, cross-tool mechanism.** |
+| Discovery chain fallback | Find context root heuristically | 🟢 UNIVERSAL | Neutral. |
+| `navigation.md` presence = validity check | "if `context.root` has no `navigation.md`, warn and fall through" | 🟢 UNIVERSAL | Neutral. Ties (a) and (b) together. |
+| Self-healing (write `.oac.json` on discovery) | Fast path next session | 🟢 UNIVERSAL | Neutral. |
+| "Single source of truth for all context root discovery" | Referenced by `context-scout` agent, `context-discovery` skill | 🟢 UNIVERSAL | Neutral doc. |
+
+> 🔴 **These two systems are unaware of each other.** `paths.json` says the root is
+> `.opencode/context`; `.oac.json` says `.claude/context`. There is no shared abstraction.
+> The refactor should **unify on the `.oac.json` protocol** (it is strictly better: fast path,
+> fallback chain, validity check, self-healing) and have `paths.json` become an
+> OpenCode-adapter-generated artifact. **The CC protocol is the more mature design and must not
+> be discarded just because it lives in the "drifted" plugin.** See **Q11**.
+
+### 3.4 🔴 Hardcoded `.opencode/` paths in content bodies — the migration's biggest mechanical risk
+
+Counted by `grep -rl '\.opencode/'`:
+
+| Area | Files containing `.opencode/` in body | Worst offenders |
+|---|---|---|
+| Agents | **31 / 34** | `openagent.md` (31 refs), `task-manager.md` (23), `context-retriever.md` (23), `repo-manager.md` (19) |
+| Context docs | **73** | across the tree |
+| Skills | **14** | routers + SKILL.md |
+| Commands | **11** | `context.md` et al. |
+| **Total** | **~129 files** | |
+
+Example — `.opencode/agent/core/openagent.md`:
+
+```
+- Code tasks → .opencode/context/core/standards/code-quality.md
+- Docs tasks → .opencode/context/core/standards/documentation.md
+...
+- code (write/edit code) → Read .opencode/context/core/standards/code-quality.md NOW
+```
+
+**Why this matters:** `00-INDEX.md` says bodies are "authored once" in `/content/`. But a body
+containing `.opencode/context/...` is **not neutral** — it instructs the agent to read a path
+that only exists in the OpenCode build output. On the Claude target, context lives at
+`.claude/context/`, so **every one of those instructions points at a non-existent file.**
+
+This is almost certainly *already broken* in `plugins/claude-code/` today and is a strong
+candidate for why the CC plugin was hand-rewritten rather than generated.
+
+| Option | Approach | Trade-off |
+|---|---|---|
+| **A. Token substitution** | Author `{{CONTEXT_ROOT}}/core/standards/code-quality.md`; adapters substitute. | Explicit, greppable, verifiable. Requires touching ~129 files once. |
+| **B. Adapter rewrite** | Adapters regex-replace `.opencode/context/` → target root. | Zero content churn; brittle, silent when it misses a variant. |
+| **C. Runtime indirection** | Bodies reference *ids* (`context:standards-code`); agent resolves via the discovery protocol. | Cleanest, most portable; largest rewrite; adds runtime lookup. |
+
+**Recommendation:** **A** for paths + **C** long-term for dependencies. A is mechanical,
+one-time, and testable with a lint rule (`no raw .opencode/ in /content/`). See **Q12**.
+
+### 3.5 🔴 Context registry coverage gaps
+
+Computed by set-difference between disk and `registry.json#components.contexts`:
+
+| Metric | Count |
+|---|---|
+| Context files on disk | **300** |
+| Registry context entries | **191** (188 unique paths) |
+| On disk but **NOT** in registry | **112** |
+| In registry but **NOT** on disk (broken) | **0** ✅ |
+| `navigation.md` on disk | **79** |
+| `navigation.md` in registry | **36** |
+
+**43 of 79 navigation hubs are unregistered** ⇒ installing a context subtree can produce a
+directory whose `navigation.md` was never installed ⇒ **the discovery chain dead-ends**, and the
+`.oac.json` protocol's "no `navigation.md` ⇒ invalid root" check then rejects a root that *is*
+correct. This is a **silent, in-the-field breakage of the crown jewel**.
+
+Unregistered examples: `.opencode/context/CODEBASE_STANDARDS.md`,
+`.opencode/context/content-creation/**/navigation.md` (all 5),
+`.opencode/context/core/context-system/{examples,guides,operations,standards}/navigation.md`,
+`.opencode/context/core/context-system/standards/typescript-coding.md`.
+
+**Migration requirement:** the build must **generate** registry context entries from disk, not
+hand-maintain them. A `navigation.md` must be **auto-included** whenever any file in its
+directory is installed (a structural dependency, not a declared one). See **Q13**.
+
+### 3.6 🔴 Registry context duplicates (live bugs)
+
+**Three files have two ids each** (alias-by-duplication instead of the `aliases[]` field):
+
+| Path | ids |
+|---|---|
+| `core/standards/code-quality.md` | `standards-code` **and** `code-quality` |
+| `core/standards/test-coverage.md` | `standards-tests` **and** `test-coverage` |
+| `core/standards/documentation.md` | `standards-docs` **and** `documentation` |
+
+⇒ `resolve_dependencies` can add **both** ids for the same file, and `install.sh` will copy it
+twice. Meanwhile a proper `aliases[]` field **does exist** and is used by exactly 3 entries
+(`feature-breakdown → workflows-task-breakdown`, `session-management → workflows-sessions`,
+`component-planning → workflows-component-planning`). **Two mechanisms for one concept.**
+
+**Three duplicate ids** also exist within `components.contexts`: **`agents`**, **`ui-navigation`**,
+**`context-bundle-template`**. Since `resolve_dependencies` matches with
+`select(.id == "…")` and jq returns **all** matches, a dependency on any of these emits
+**multiple paths** — nondeterministic install behavior.
+
+**Requirement:** neutral schema enforces **unique `id`**, aliases go in `aliases[]` only, and the
+build **fails** on duplicates. This is a test the minimal-test-first slice should include.
+
+### 3.7 Context sub-features worth naming explicitly
+
+| Feature | Where | Class | Home |
+|---|---|---|---|
+| **MVI principle** | `core/context-system/standards/mvi.md` | 🟢 UNIVERSAL | Neutral content. Formula: Core Concept (1-3 sentences) → Key Points (3-5 bullets) → Quick Example (5-10 lines) → Reference Link → Related Files. "Scannable in <30 seconds." |
+| **<200-line rule** | enforced in `command/context.md` `<rule id="mvi_strict">` | 🟢 UNIVERSAL | Neutral + **lintable**. Candidate `oac doctor` check. |
+| **Function-based structure** | `concepts/ examples/ guides/ lookup/ errors/` | 🟢 UNIVERSAL | Neutral convention; visible across `mastra-ai/`, `openagents-repo/`, `ui/web/design/`. |
+| Structure/templates/codebase-reference standards | `core/context-system/standards/{structure,templates,codebase-references}.md` | 🟢 UNIVERSAL | Neutral. |
+| Context **operations** | `core/context-system/operations/{harvest,extract,organize,update,migrate,error}.md` | 🟢 UNIVERSAL | Neutral. The verbs behind `/context`. |
+| Context **guides** | `.../guides/{compact,creation,organizing-context,workflows,navigation-design-basics,navigation-templates}.md` | 🟢 UNIVERSAL | Neutral. |
+| Context-system **CHANGELOG** | `core/context-system/CHANGELOG.md` | 🟢 UNIVERSAL | Neutral. |
+| Task JSON schemas | `context/tasks/schemas/{task,subtask}-schema.json` | 🟢 UNIVERSAL | Neutral — **non-`.md` context**, same special case as `paths.json`. |
+| `index.md` (root) | `.opencode/context/index.md` | 🟢 UNIVERSAL | Neutral. Note: **`index.md` AND `navigation.md` both at root** — overlapping entry points. **Q14**. |
+| Project Intelligence standard | `core/standards/project-intelligence.md`, `context/project-intelligence/` | 🟢 UNIVERSAL | Neutral. Driven by `/add-context`. |
+| `CONTEXT_SYSTEM_GUIDE.md` (16KB, repo root) | root | 🟢 UNIVERSAL | **Outside `.opencode/`** — easily missed by a `.opencode/`-only migration. |
+
+---
+
+## 4. Skills
+
+### 4.1 Format
+
+A skill = `SKILL.md` (frontmatter + body) + optional `router.sh` + `scripts/` + `workflows/` + `tests/`.
+
+```yaml
+---
+name: task-management
+description: Task management CLI for tracking and managing feature subtasks with status, dependencies, and validation
+version: 1.0.0
+author: opencode
+type: skill
+category: development
+tags: [tasks, management, tracking, dependencies, cli]
+---
+```
+
+| Field | Class | Home |
+|---|---|---|
+| `name` | 🟢 UNIVERSAL | `id`/`name`. **Already kebab-case in both OpenCode and CC** — skills need no slugify transform. |
+| `description` | 🟢 UNIVERSAL | `description`. **Load-bearing at runtime:** CC's `session-start.sh` greps `^description:` to build the skill catalogue. |
+| `version`, `author`, `type`, `category`, `tags` | 🔵 REGISTRY | Neutral frontmatter → registry. **Skills already inline their metadata** — no sidecar. **This is the model agents should follow** (§1.7). |
+| `router.sh` | 🟡 | Bash entrypoint; adapter must preserve the executable bit + `files[]`. |
+| `scripts/*.ts` | 🟡 | Bun/TS runtime. See §4.4. |
+| `workflows/*.md` | 🟢 UNIVERSAL | Prose. |
+| `tests/*.test.ts` | 🔵 BUILD | Should move to `/evals` or a package test dir. |
+
+> **Skills are the most portable content type in the repo**: kebab ids, self-contained metadata,
+> `SKILL.md` convention identical across OpenCode and Claude Code. The skill adapter is mostly
+> `cp` + `files[]` bookkeeping.
+
+### 4.2 Multi-file skills need `files[]`
+
+Skills are the **only** component type using the registry `files[]` field:
+
+```json
+{ "id": "task-management", "path": ".opencode/skills/task-management/SKILL.md",
+  "files": [".opencode/skills/task-management/SKILL.md",
+            ".opencode/skills/task-management/router.sh",
+            ".opencode/skills/task-management/scripts/task-cli.ts"] }
+```
+
+| Registry `files[]` count | Actual disk files | |
+|---|---|---|
+| `task-management` | 3 | 3 ✅ |
+| `context-manager` | 2 | 2 ✅ |
+| `context7` | 4 | 4 ✅ |
+| `smart-router-skill` | 6 | 6 ✅ |
+
+Hand-maintained but currently **accurate**. Should be **generated** (glob the skill dir) so it
+cannot drift. 🔵 BUILD.
+
+### 4.3 🔴 `skill/` vs `skills/` — an orphaned directory
+
+**Two sibling directories exist.** The registry only knows `.opencode/skills/` (plural).
+
+| Path | In registry? | Contents |
+|---|---|---|
+| `.opencode/skills/` (plural) | ✅ 4 skills | task-management, context-manager, context7, smart-router-skill |
+| `.opencode/skill/project-orchestration/` | ❌ **orphan** | `SKILL.md`, `router.sh`, 3 scripts (`context-index.ts`, `session-context-manager.ts`, `stage-cli.ts`), 3 workflows (`8-stage-delivery.md`, `context-handoff.md`, `planning-agents.md`) |
+| `.opencode/skill/task-management/tests/` | ❌ **orphan** | `enhanced-schema.test.ts`, `line-number-validation.test.ts` — tests **split from** the skill in `skills/` |
+
+`project-orchestration` is a **fully-built, uninstallable skill** — with an 8-stage delivery
+workflow and a stage CLI. It pairs with the 6 unregistered planning agents (§1.8): the
+`planning-agents.md` workflow references exactly the `subagents/planning/` agents that have no
+registry entry. **An entire orchestration feature is dark.**
+
+Likewise `.opencode/skill/task-management/tests/` are the tests for
+`.opencode/skills/task-management/` — the same skill, split across two directory names.
+
+**Decision required:** ship `project-orchestration` + planning agents as a feature, or delete
+both. They are coupled — decide together. See **Q7**.
+
+### 4.4 🔴 Runtime dependency: Bun/TypeScript
+
+`.opencode/skills/task-management/scripts/task-cli.ts`, `.opencode/scripts/task-cli.ts`,
+`.opencode/skill/project-orchestration/scripts/*.ts`, and every `router.sh` shell out to a
+TypeScript runtime. `.opencode/{package.json,bun.lock,node_modules}` and
+`.opencode/tool/{package.json,tsconfig.json,bun.lock}` confirm **Bun**.
+
+For npm distribution (`04-cli-build-distribution.md`), this is a real constraint: an installed
+OAC skill requires Bun on the user's machine. Cross-platform (Windows) `router.sh` is a further
+problem. Flagging for Agent D. See **Q15**.
+
+### 4.5 🔴 CC has 12 skills; OpenCode has 4
+
+`plugins/claude-code/skills/`: `code-execution`, `code-review`, `context-discovery`,
+`context-setup`, `debugger`, `external-research`, `oac-approach`, `parallel-execution`,
+`task-breakdown`, `test-generation`, `using-oac`, `verification-before-completion`.
+
+**Only `context-manager`/`context7`/`task-management`/`smart-router-skill` exist on the OpenCode
+side, and none of those 4 names appear in CC.** The two skill sets are **disjoint**.
+
+These 12 CC skills are the **primary UX of the shipped plugin** (they are what the
+`session-start.sh` catalogue advertises, and what this very repo's tooling exposes as
+`oac:code-review`, `oac:task-breakdown`, …). They are **not derivable from `.opencode/`**.
+
+🔴 **If `/content/` is seeded only from `.opencode/`, all 12 are lost.** They must be harvested
+from `plugins/claude-code/skills/` — and arguably they, not the OpenCode 4, are the better
+starting point. See **Q16**.
+
+---
+
+## 5. Tools
+
+`.opencode/tool/` — TypeScript modules.
+
+| Item | Path | Registry | Class | Home |
+|---|---|---|---|---|
+| `env` | `.opencode/tool/env/index.ts` | ✅ `tool:env` | 🟡 | Loads `.env` from a search-path list (`./.env`, `../.env`, `../../.env`, `../plugin/.env`, …). Exports `loadEnvVariables(config)`. |
+| `gemini` | `.opencode/tool/gemini/index.ts` | ✅ `tool:gemini`, `dependencies: ["tool:env"]` | 🟡 | Image gen/edit/analysis. **The only tool→tool dependency in the registry.** |
+| `template` | `.opencode/tool/template/index.ts` + `README.md` | ❌ | 🔵 BUILD | Scaffold for new tools → CLI template. |
+| `index.ts` | `.opencode/tool/index.ts` | ❌ | 🟡 | Barrel export. |
+| `.opencode/tool/.env` | — | 🔴 **SECURITY** | A committed `.env` inside the tool dir. **Must not be copied into `/content/`.** See **Q17**. |
+| `package.json`/`tsconfig.json`/`bun.lock` | — | 🔵 BUILD | Tool workspace config. |
+
+**Class overall: 🟡 OPENCODE-SPECIFIC.** Tools implement the OpenCode plugin/tool API. Claude
+Code has no equivalent user-defined "tool" primitive (its analogue is an MCP server). The tool
+adapter is either (a) OpenCode-only with a "not supported" warning for other targets, or
+(b) tools get re-expressed as MCP servers. Big scope call — see **Q18**.
+
+---
+
+## 6. Plugins & Hooks
+
+Two directories again (`plugin/` singular, `plugins/` plural).
+
+| Item | Path | Registry | Class | Notes |
+|---|---|---|---|---|
+| `notify` | `.opencode/plugin/notify.ts` | ✅ `plugin:notify` | 🟡 | Hooks OpenCode's `event` with `session.idle` → `say "Your code is done!"`. **Ships disabled** (`const ENABLED = false`). |
+| `agent-validator` | `.opencode/plugin/agent-validator.ts` | ❌ | 🔴 | **Also present as `agent-validator.ts.disabled`** — two copies, neither registered. Has `docs/VALIDATOR_GUIDE.md` + `tests/validator/{test-validation.sh,test-validator.sh,interactive.md}`. |
+| `coder-verification` | `.opencode/plugins/coder-verification/` | ❌ | 🔴 | Own `plugin.json` (name/description/version/author), `index.ts`, `README.md`, `IMPLEMENTATION_SUMMARY.md`. "Reminds about checks and auto-completes tasks" for CoderAgent. **Unregistered.** |
+| `.opencode/plugin/.env` | — | 🔴 **SECURITY** | Second committed `.env`. See **Q17**. |
+
+**Findings:**
+- **Hooks exist only as plugins in OpenCode** — there is no `hooks:` frontmatter field anywhere
+  (§1.1 confirmed 0 occurrences). OpenCode hooks = a plugin exporting an `event` handler.
+  Claude Code hooks = declarative `hooks.json` (§9.2). **These models are fundamentally different**
+  (imperative TS callback vs declarative JSON→shell command).
+- `plugin.json` (in `plugins/coder-verification/`) has **the same shape** as
+  `plugins/claude-code/.claude-plugin/plugin.json` — name/description/version/author. A neutral
+  plugin-manifest schema could cover both.
+- The registry knows about **1 of 3** plugins.
+
+**Class: 🟡 OPENCODE-SPECIFIC**, and the **hook model is 🔴 AT RISK** — there is no neutral
+abstraction that covers both an OpenCode TS `event` callback and a CC `SessionStart` shell hook.
+Agent C must decide whether `hooks` is a neutral content type at all, or purely per-adapter.
+See **Q19**.
+
+---
+
+## 7. Dependency Resolution — **DEEP DIVE** 🏆
+
+> The second crown jewel. This is what makes `oac add code-reviewer` pull in the right context
+> and subagents. It drives installation.
+
+### 7.1 The reference grammar
+
+Dependencies are strings of the form `<type>:<id>`:
+
+```
+subagent:contextscout
+context:standards-code
+tool:env
+context:core/*                                        ← wildcard
+context:core/context-system/standards/mvi.md          ← full path with extension
+```
+
+Declared in **three** places:
+1. `registry.json#components.<type>[].dependencies[]` — **what `install.sh` actually reads**
+2. `.opencode/config/agent-metadata.json#agents.<id>.dependencies[]` — parallel copy
+3. Command frontmatter `dependencies:` — e.g. `add-context.md`, `context.md`
+
+### 7.2 The algorithm (`install.sh:420-481`)
+
+```bash
+resolve_dependencies() {
+    local component=$1
+    local type="${component%%:*}"        # left of first ':'
+    local id="${component##*:}"          # right of last ':'
+    registry_key=$(get_registry_key "$type")     # singular → plural
+
+    deps=$(jq_exec ".components.${registry_key}[] \
+      | select(.id == \"${id}\" or (.aliases // [] | index(\"${id}\"))) \
+      | .dependencies[]?" "$TEMP_DIR/registry.json")
+
+    for dep in $deps; do
+        if [[ "$dep" == *"*"* ]] && [ "$dep_type" = "context" ]; then
+            matched=$(expand_context_wildcard "$dep_id")
+            # add each match if not already selected, then recurse
+        fi
+        # add dep if not already in SELECTED_COMPONENTS, then recurse
+    done
+}
+```
+
+| Property | Behavior | Class |
+|---|---|---|
+| **Recursive transitive closure** | Yes — recurses into each newly added dep | 🟢 UNIVERSAL |
+| **Cycle safety** | **Implicit** — recursion only fires when the dep was *not* already in `SELECTED_COMPONENTS`, so a cycle terminates. Never *detected* or reported. | 🟡 Fragile |
+| **Dedupe** | Linear scan of `SELECTED_COMPONENTS` before add (O(n²), fine at this scale) | 🔵 BUILD |
+| **Alias resolution** | `.id == id or (.aliases // []) | index(id)` | 🟢 UNIVERSAL |
+| **Wildcard expansion** | **`context:` only**; `expand_selected_components` prints `"Wildcard only supported for context components"` otherwise | 🟢 UNIVERSAL |
+| **Missing dep handling** | `jq … || echo ""` ⇒ an **unknown id resolves to zero deps, silently**. No "unknown component" error. | 🔴 **AT RISK** |
+| **Type↔registry-key mapping** | `get_registry_key()`: `agent→agents`, `context→contexts`, `skill→skills`, `config→config` (special: stays singular), `*s→as-is`, default `+s` | 🟡 |
+| **Install path mapping** | `get_install_path()`: strips `.opencode/` prefix, prepends `$INSTALL_DIR` | 🟡 → adapter |
+
+**Wildcard expansion (`install.sh:361-371`):**
+
+```bash
+expand_context_wildcard() {
+    prefix="${pattern%%\**}"; prefix="${prefix%/}"; [ -n "$prefix" ] && prefix="${prefix}/"
+    jq_exec ".components.contexts[]? | select(.path | startswith(\".opencode/context/${prefix}\"))
+             | .path | sub(\"^\\\\.opencode/context/\"; \"\") | sub(\"\\\\.md$\"; \"\")"
+}
+```
+
+| Detail | Consequence |
+|---|---|
+| `*` is a **prefix match**, not a glob | `core/*` matches `core/**` recursively too. `ui/web/a*` would match anything starting `ui/web/a`. **Not real glob semantics.** |
+| `sub("\\.md$"; "")` strips the extension | A **non-`.md` context** (`paths.json`, `tasks/schemas/*.json`) survives wildcard expansion **with its extension intact**, producing id `core/config/paths.json` — which then must match a registry `id`. This is exactly the class of bug PR #252 ("resolve non-.md context paths like paths.json") fixed. 🔴 |
+| Matches on **`path`**, emits a **path-derived id** | So wildcards produce **path-style** ids while hand-written deps use **short-style** ids. Two id namespaces in one list. 🔴 |
+
+### 7.3 🔴 The dual-namespace bug
+
+`resolve_dependencies` matches **`.id`**, but wildcards and some hand-written deps supply
+**paths**:
+
+| Reference | Form | Resolves? |
+|---|---|---|
+| `context:standards-code` | short id | ✅ matches `.id == "standards-code"` |
+| `context:core/*` | wildcard → `core/standards/code-quality` … | ⚠️ produces **path-style** strings; the *next* recursion looks for `.id == "core/standards/code-quality"` — **no such id** ⇒ **its dependencies are silently skipped** |
+| `context:core/context-system/standards/mvi.md` (`add-context.md`) | full path **with `.md`** | ❌ **no registry entry has that as `.id`** ⇒ **silently resolves to nothing** |
+
+So `/add-context`'s three declared context dependencies — the MVI standard, the frontmatter
+standard, and the project-intelligence standard, i.e. **exactly the docs the command tells the
+agent to follow** — **almost certainly do not install today.** Because `jq` failure is swallowed
+by `|| echo ""`, there is no error.
+
+**Requirement for the neutral schema:** ONE reference form. Recommend canonical **`<type>:<id>`
+with unique ids**, plus `aliases[]`, plus a build-time validation pass that **fails** on an
+unresolvable reference. The "silently resolves to nothing" behavior is the single most dangerous
+property of the current system and the easiest thing to lose *and not notice* during migration.
+See **Q20**.
+
+### 7.4 Structural (undeclared) dependencies
+
+Not expressible today; must be **inferred** by the build:
+
+| Implicit dependency | Why | Today |
+|---|---|---|
+| context file → its dir's `navigation.md` | Discovery chain dead-ends without it | ❌ **not declared** — 43 hubs unregistered (§3.5) |
+| context file → parent `navigation.md` … → root `navigation.md` | Chain traversal from the root | ❌ not declared |
+| agent → `agent-metadata.json` | The sidecar carries its metadata | ⚠️ `config:agent-metadata` in some profiles |
+| skill → `files[]` | Multi-file skills | ✅ via `files[]` |
+| agent → `context-paths-config` | `@`-reference loading | ⚠️ only in `.opencode/profiles/*/profile.json`, **not** in `registry.json#profiles` (§8.2) |
+| tool `gemini` → `tool:env` | Reads API key | ✅ declared |
+| CC agent → its plugin skills | `code-reviewer` says "I'll run the code-review skill" | ❌ not declared |
+
+**Requirement:** the build **auto-includes the navigation chain** for every installed context
+file. This is a structural rule, not a declared edge.
+
+---
+
+## 8. Registry (`registry.json`, 107KB) — **the installation driver**
+
+### 8.1 Schema
+
+```json
+{
+  "version": "2.0.0",
+  "schema_version": "2.0.0",
+  "repository": "https://github.com/darrenhinde/OpenAgentsControl",
+  "categories": { "essential": "…", "standard": "…", "extended": "…",
+                  "specialized": "…", "meta": "…" },
+  "components": { "agents": [], "subagents": [], "commands": [], "tools": [],
+                  "plugins": [], "skills": [], "contexts": [], "config": [] },
+  "profiles":  { "essential": {}, "developer": {}, "business": {}, "full": {}, "advanced": {} },
+  "metadata":  { "lastUpdated": "2026-03-21", "schemaVersion": "1.0.0" },
+  "subagents": { "test": { "simple-responder": { … } } }
+}
+```
+
+**Component entry — union of all keys observed across all 8 types:**
+`id`, `name`, `type`, `path`, `description`, `tags[]`, `dependencies[]`, `category`,
+`aliases[]` (3 contexts), `files[]` (4 skills), `version` (rare).
+
+| Field | What it does | Class | Home |
+|---|---|---|---|
+| `id` | Dependency-graph key | 🟢 UNIVERSAL | Neutral `id`. **Must be unique — 3 dupes today (§3.6).** |
+| `name` | Display name | 🟢 UNIVERSAL | Neutral `name`. |
+| `type` | Component type | 🔵 REGISTRY | Derived from `/content/` subdir. |
+| `path` | **Source** path (`.opencode/agent/...`) | 🔵 REGISTRY | **Becomes `/content/...`; per-target output path is adapter-computed.** This field is the load-bearing OpenCode assumption in the registry. |
+| `description` | Install-UI text | 🟢 UNIVERSAL | Neutral `description`. |
+| `tags[]` | Filter/search | 🟢 UNIVERSAL | Neutral `tags[]`. |
+| `dependencies[]` | Resolution graph | 🟢 UNIVERSAL | Neutral `dependencies[]`. |
+| `category` | `essential`\|`standard`\|`extended`\|`specialized`\|`meta` | 🔵 REGISTRY | ⚠️ **Collides with the agent's *domain* category** (`core`, `meta`, `content`). Registry `category` = **install tier**; metadata `category` = **domain**. **Same word, two meanings.** See **Q5**. |
+| `aliases[]` | Alt ids | 🟢 UNIVERSAL | Neutral `aliases[]`. |
+| `files[]` | Multi-file components | 🔵 REGISTRY | **Generate** by globbing. |
+| `version` | SemVer | 🔵 REGISTRY | Neutral `version`. |
+| `categories` (top) | id → description **string** | 🔵 REGISTRY | Should become an object (`{description, icon, order, status}`) to absorb `0-category.json` (§1.6). |
+| `repository` | Source repo for fetches | 🔵 REGISTRY | Registry. |
+| `version` vs `schema_version` vs `metadata.schemaVersion` | **Three** version fields; `metadata.schemaVersion` (`1.0.0`) **contradicts** top-level `schema_version` (`2.0.0`) | 🔴 | Collapse to one. **Q21**. |
+| `subagents` (top-level) | 🔴 **Orphan key** — `{test: {simple-responder: {…}}}`, a *second* place subagents are described, with fields (`mode`, `status`) the components entries lack. `simple-responder` is **also** in `components.subagents`. | 🔴 | **Delete.** Dead schema wart. |
+
+### 8.2 🔴 Profiles are duplicated AND drifted
+
+Profiles exist in **two** places:
+- `registry.json#profiles` — **what `install.sh` reads** (`jq '.profiles.developer.components[]'`, lines 294/636-688/1222)
+- `.opencode/profiles/<name>/profile.json` — read **only** by `scripts/registry/check-dependencies.ts:159`
+
+**All 5 profiles differ:**
+
+| Profile | registry components | file components | Identical? |
+|---|---|---|---|
+| `essential` | 25 | 27 | ❌ |
+| `developer` | 41 | 39 | ❌ |
+| `business` | 25 | 26 | ❌ |
+| `full` | 50 | 42 | ❌ |
+| `advanced` | 68 | 51 | ❌ |
+
+For `developer` alone:
+- **registry-only (15):** `command:add-context`, `config:agent-metadata`, `context:quick-start`,
+  `context:clean-code`, `context:api-design`, `context:development/*`, `context:openagents-repo/*`,
+  `context:ui/*`, `context:react-patterns`, `context:design-systems`, `context:animation-{basics,components,advanced}`,
+  `context:ui-styling-standards`, `context:adding-skill-basics`
+- **file-only (13):** `subagent:image-specialist`, `tool:gemini`, `skill:context-manager`,
+  `context:root-navigation`, `context:context-paths-config`, `context:context-system`,
+  `context:workflows-external-context-{management,integration}`, `context:ui/web/*` (5 path-style variants)
+
+Note the **id-vs-path drift** inside this diff: registry says `context:ui-styling-standards`
+(short id), the file says `context:ui/web/ui-styling-standards` (path) — **the §7.3 dual-namespace
+bug, frozen into the profiles.**
+
+Also note `context:root-navigation` and `context:context-paths-config` are **file-only** ⇒ the
+**authoritative** registry `developer` profile installs **neither the root navigation hub nor the
+context paths config** — i.e. the context system's entry point and its path config are **missing
+from the recommended profile**. 🔴
+
+**Requirement:** ONE profile source. `registry.json#profiles` wins (it is what runs).
+`.opencode/profiles/` must be **deleted** (and `check-dependencies.ts` repointed), or generated.
+But **first reconcile the diffs** — the file versions contain fixes the registry lacks and vice
+versa. See **Q22**.
+
+**Profile fields:** `name`, `description`, `components[]`, `badge` (developer: `"RECOMMENDED"`),
+`additionalPaths[]` (**advanced only**: `[".Building/", ".github/workflows/"]` — raw directory
+copies outside the component model, read at `install.sh:1222`). 🔴 `additionalPaths` is an
+escape hatch that bypasses the component/dependency system entirely.
+
+---
+
+## 9. `plugins/claude-code/` — the drifted, hand-maintained CC plugin
+
+Per `00-INDEX.md` this becomes **generated output** and the directory is deleted. Everything of
+value must therefore be captured **now**.
+
+### 9.1 Inventory
+
+| Item | Count | Class | Notes |
+|---|---|---|---|
+| `agents/` | 7 | 🟢 → regenerate | `code-reviewer`, `coder-agent`, `context-manager`, `context-scout`, `external-scout`, `task-manager`, `test-engineer` |
+| `commands/` | 6 | 🔴 **CC-only** | `brainstorm`, `debug`, `install-context`, `oac-cleanup`, `oac-help`, `oac-status` — **none exist in `.opencode/command/`** |
+| `skills/` | 12 | 🔴 **CC-only** | §4.5 — disjoint from OpenCode's 4 |
+| `hooks/hooks.json` + `hooks/session-start.sh` | 2 | 🔴 **CC-only, critical** | §9.2 |
+| `.claude-plugin/plugin.json` | 1 | 🔵 | name `oac`, version `1.0.2`, author, license, repo, homepage, keywords[] |
+| `.context-manifest.json` | 1 (×2 copies) | 🔴 **CC-only, critical** | §9.3 — also duplicated at `scripts/.context-manifest.json` |
+| `.oac.json.example` + `.oac.example` | 2 | 🔴 | **Two example files**, one presumably stale |
+| `scripts/` | 12 files | 🔴 | `install-context.{js,sh,ts}` — **three implementations of the same thing** — plus `test-install.ts`, `cleanup-tmp.sh`, `types/{manifest,registry}.ts`, `utils/{git-sparse,registry-fetcher}.ts`, `tsconfig.json` |
+| `settings.json` / `settings.local.json` | 2 | 🟡 | Per git log #264: "add settings.json with opusplan model" |
+| `README.md` | 1 | 🔵 | |
+
+### 9.2 🏆 `session-start.sh` — the highest-value CC-only feature
+
+`plugins/claude-code/hooks/hooks.json`:
+
+```json
+{ "hooks": { "SessionStart": [ { "hooks": [
+  { "type": "command",
+    "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh\"",
+    "timeout": 30 } ] } ] } }
+```
+
+`session-start.sh` does **six** distinct things — every one must survive:
+
+| # | Capability | Detail | Class | Home |
+|---|---|---|---|---|
+| 1 | **Inline `using-oac` skill** | Reads `skills/using-oac/SKILL.md` **in full** into session context | 🟢 UNIVERSAL intent | Neutral: "bootstrap doc". Adapter renders per tool. |
+| 2 | **Skill catalogue generation** | Loops `skills/*/SKILL.md`, greps `^description:`, emits `- oac:<name> — <description>` | 🔵 BUILD/adapter | **Could be build-time (static) instead of runtime (shell).** |
+| 3 | **First-run context warning** | If neither `$(pwd)/.claude/.context-manifest.json` nor `~/.claude/.context-manifest.json` exists ⇒ inject an `<important-reminder>` telling the agent to run `context-setup` | 🟢 UNIVERSAL intent | Neutral onboarding rule. |
+| 4 | **OAC system paths block** | Emits `Plugin Root:` + `Context Discovery Protocol:` absolute paths | 🟢 UNIVERSAL intent | Neutral; adapter computes paths. |
+| 5 | **Context-discovery instruction** | "Before responding to any coding request this session, use `oac:context-discovery` … runs once per session" | 🟢 UNIVERSAL | **This is the runtime entry point to the whole context system.** |
+| 6 | **Dual-format JSON output** | Emits **both** `additional_context` (Cursor/OpenCode/other) **and** `hookSpecificOutput.additionalContext` (Claude Code) | 🟡 | **Already a hand-rolled adapter!** Proof the cross-tool need is real. |
+
+Plus a **security control** worth naming: `escape_for_json()` with the comment
+*"SECURITY: Prevents command injection attacks from malicious SKILL.md files"*, escaping
+backslash-first then quotes/newlines/CR/tabs. **If the build generates this hook, the generator
+must preserve the escaping** — a naive template would reintroduce the injection vuln.
+
+> 🔴 **Nothing in `.opencode/` does any of this.** OpenCode has no session-start context
+> injection at all. This is the CC plugin being **ahead**, not behind. It is the mechanism by
+> which OAC introduces itself, advertises its skills, and bootstraps context discovery — arguably
+> **the most important runtime feature in the entire product**, and it exists in exactly one
+> file that the refactor plans to delete.
+
+### 9.3 `.context-manifest.json` — installed-state tracking
+
+```json
+{
+  "version": "1.0.0",
+  "profile": "standard",
+  "source": { "repository": "darrenhinde/OpenAgentsControl", "branch": "main",
+              "commit": "1442740b4a776a790754c9dbde64ab7f1aa9d4e8",
+              "downloaded_at": "2026-02-23T11:37:10Z" },
+  "categories": ["core", "openagents-repo"],
+  "files": { "core": 86, "openagents-repo": 107 }
+}
+```
+
+| Feature | What it does | Class | Home |
+|---|---|---|---|
+| Provenance (`repository`/`branch`/**`commit`**) | Pins the exact source commit | 🟢 UNIVERSAL | **`oac update`/`oac doctor` need this.** Agent D. |
+| `downloaded_at` | Staleness | 🟢 UNIVERSAL | Same. |
+| `profile` | Which profile was installed | 🟢 UNIVERSAL | Same. ⚠️ Value is **`"standard"` — not one of the 5 profile names** (essential/developer/business/full/advanced). `standard` is a *registry **category***, not a profile. Another id-namespace collision. 🔴 |
+| `categories[]` + `files{}` | What/how much was installed | 🟢 UNIVERSAL | Same. |
+| Dual location (project `.claude/` or global `~/.claude/`) | Local vs global install | 🟢 UNIVERSAL | Mirrors `paths.json`'s `local`/`global`. |
+| Used as the first-run sentinel | `session-start.sh` §9.2 #3 | 🟢 UNIVERSAL | |
+
+> `install.sh` (the OpenCode path) writes **no equivalent manifest**. So **OpenCode installs are
+> not updatable or auditable** — you cannot tell what version is installed. The CC plugin solved
+> a problem the "source of truth" side never did. **`oac update` depends on this existing.**
+
+---
+
+## 10. Other assets (easily missed)
+
+| Asset | Path | Class | Notes |
+|---|---|---|---|
+| Per-model prompt variants | `.opencode/prompts/core/openagent/{gemini,gpt,grok,llama,minimax,openrouter}.md` | 🔴 AT RISK | **6 model-specific rewrites of the OpenAgent prompt.** Directly tensions Locked Decision #2 (`model: null`). Also `prompts/core/opencoder/{gemini,gpt,grok,llama}.md`. |
+| Prompt templates | `.opencode/prompts/*/TEMPLATE.md`, `README.md` | 🔵 | |
+| Eval results | `.opencode/prompts/core/openagent/results/*.json`, `default-output.log` | 🔵 | Build artifacts committed to git. |
+| `.opencode/docs/` | `agents/planning-agents-guide.md`, `guides/task-schema-migration.md`, `workflows/full-project-workflow.md` | 🟢 | 3 docs. `planning-agents-guide.md` documents the **unregistered** planning agents (§1.8). |
+| `.opencode/scripts/task-cli.ts` | | 🔴 | **Duplicate** of `.opencode/skills/task-management/scripts/task-cli.ts`? Unregistered. |
+| `.opencode/opencode.json` | `{"$schema": "https://opencode.ai/config.json"}` | 🟡 | Empty but for the schema ref. |
+| `.opencode/config.json` | `{"agent": "eval-runner"}` | 🟡 | Sets the default agent — **to the eval harness**, which is marked `DO NOT USE DIRECTLY`. Likely a committed local override. 🔴 |
+| `CONTEXT_SYSTEM_GUIDE.md` | repo root, 16KB | 🟢 | Outside `.opencode/` — the main context-system doc. |
+| `COMPATIBILITY.md` | repo root | 🔵 | |
+| `registry:config` entries | `env.example`, `README.md`, `.opencode/config/agent-metadata.json` | 🔵 | **`config:readme` installs the repo README into the user's project.** Intentional? 🔴 |
+
+---
+
+## 11. FEATURE PRESERVATION CHECKLIST
+
+> Flat list. Every capability that **must survive**. Each maps to its new home.
+> `[ ]` = must be verified in the migrated system before `.opencode/` is demoted.
+
+### Agents
+- [ ] **34 agent bodies** preserved verbatim → `/content/agents/**/*.md` body
+- [ ] `name` → neutral `name` + `id`; adapters slugify (OpenCode PascalCase, Claude kebab)
+- [ ] `description` → neutral `description`
+- [ ] `mode: primary|subagent` → neutral `role`
+- [ ] `temperature` (33 agents) → `inference.temperature`; **Claude drops it → warning**
+- [ ] `model` stays absent → `inference.model: null` (Locked Decision #2)
+- [ ] `permission.<tool>.<glob>` **tri-state** (`allow`/`ask`/`deny`) → `capabilities` **rule list** (§1.3)
+- [ ] **Glob-scoped denies preserved** (`**/*.env*`, `**/*.key`, `**/*.secret`, `node_modules/**`, `.git/**`) — *security control*
+- [ ] **Deny-by-default + narrow allow preserved** (`coder-agent` bash allowlist) — *security control*
+- [ ] `permission.bash` destructive-command denies preserved (`sudo *`, `rm -rf /*`, `> /dev/*`) — *security control*
+- [ ] `permission.task` → `capabilities.delegate`; **id vs name refs canonicalized**
+- [ ] **`tester`/`test-engineer`/`TestEngineer` aliasing** resolved without breaking dep edges (§1.2)
+- [ ] `eval-runner.md`'s inline `id`/`category`/`type`/`version`/`author` normalized
+- [ ] **CC `<example>` blocks harvested** from `plugins/claude-code/agents/` → neutral `examples[]` (§1.5)
+- [ ] CC `tools:` / `disallowedTools:` derivable from `capabilities`
+- [ ] 6 unregistered planning/orchestration agents: **registered or deleted** (§1.8, Q7)
+- [ ] `batch-executor` (in metadata, not registry) resolved
+
+### Agent metadata
+- [ ] `id`, `name`, `category`, `type`, `version`, `author`, `tags[]`, `dependencies[]` → **neutral frontmatter**
+- [ ] `defaults.{agent,subagent}` → Zod `.default()`
+- [ ] OpenCode adapter **re-splits** metadata into `agent-metadata.json` (OpenCode rejects unknown keys)
+- [ ] `agent-metadata.json` `$schema` + `schema_version` emitted by the adapter
+- [ ] `category` dual format (`core` vs `subagents/core`) normalized (Q5)
+
+### Categories
+- [ ] `0-category.json` `name`/`description`/`icon`/`order`/`status` → `registry.json#categories[]` (upgraded to objects)
+- [ ] `commonSubagents`/`commonTools`/`commonContext` — **promoted or deleted** (Q4)
+
+### Commands
+- [ ] **20 command bodies** verbatim → `/content/commands/`
+- [ ] `description`, `tags[]`, `dependencies[]` preserved
+- [ ] `<critical_rules>`/`<rule id= enforcement=>`/`<execution_priority>`/`<tier>` DSL passes through untouched
+- [ ] `@rule.id` cross-refs intact
+- [ ] `analyze-patterns.md` inline metadata normalized
+- [ ] Agent/context templates relocated out of `command/` → CLI scaffolds (§2.3)
+- [ ] **8 numbered eval templates** (`test-1-planning-approval` … `test-8-completion`) relocated → `/evals` (Q8)
+- [ ] **6 CC-only commands** (`brainstorm`, `debug`, `install-context`, `oac-cleanup`, `oac-help`, `oac-status`) harvested → `/content/commands/`
+
+### Context system 🏆
+- [ ] **All 300 context files** migrated (297 `.md` + `paths.json` + 2 task schemas)
+- [ ] **HTML-comment header parsed, not ignored**: `<!-- Context: {cat}/{fn} | Priority: … | Version: X.Y | Updated: … -->` (§3.1) — **the #1 silent-loss risk**
+- [ ] `Priority` (`critical`/`high`/`medium`/`low`) → neutral `priority` — **drives load order**
+- [ ] `Category`/`Function` dual taxonomy preserved
+- [ ] `Version` `X.Y` form reconciled with SemVer (Q9)
+- [ ] `Updated` date preserved
+- [ ] **All 79 `navigation.md` hubs** migrated
+- [ ] **Task→path "Quick Routes" tables** preserved verbatim — *the* discovery mechanism
+- [ ] **Relative inter-hub links** stay relative (portable); only the **root** tree diagram is rewritten (§3.2)
+- [ ] **43 unregistered `navigation.md`** registered / auto-included (§3.5)
+- [ ] **112 unregistered context files** registered or consciously dropped (§3.5)
+- [ ] **Navigation chain auto-included** as a structural dependency (§7.4)
+- [ ] **3 duplicate-path context ids** collapsed to `aliases[]` (§3.6)
+- [ ] **3 duplicate context ids** (`agents`, `ui-navigation`, `context-bundle-template`) made unique (§3.6)
+- [ ] `paths.json` local/global roots → neutral config; adapter rewrites
+- [ ] **`.oac.json` discovery protocol preserved** (fast path → chain → validity check → self-heal) (§3.3)
+- [ ] `navigation.md`-presence validity check preserved
+- [ ] Non-`.md` contexts (`paths.json`, task schemas) survive wildcard expansion (§7.2, PR #252)
+- [ ] **MVI standard** preserved (formula, what-to-extract/skip, <30s scannable)
+- [ ] **<200-line rule** preserved (candidate `oac doctor` lint)
+- [ ] **Function-based structure** (`concepts/ examples/ guides/ lookup/ errors/`) preserved
+- [ ] Context **operations** (harvest/extract/organize/update/migrate/error) preserved
+- [ ] Context **guides** (compact/creation/organizing/workflows/navigation-design) preserved
+- [ ] `structure.md`/`templates.md`/`codebase-references.md`/`typescript-coding.md` preserved
+- [ ] `core/context-system/CHANGELOG.md` preserved
+- [ ] Task JSON schemas (`tasks/schemas/{task,subtask}-schema.json`) preserved
+- [ ] Root `index.md` **and** `navigation.md` reconciled (Q14)
+- [ ] `CODEBASE_STANDARDS.md` (unregistered) preserved
+- [ ] **`CONTEXT_SYSTEM_GUIDE.md`** (repo root, outside `.opencode/`) preserved
+- [ ] Project Intelligence standard + `context/project-intelligence/` preserved
+- [ ] **~129 files with hardcoded `.opencode/` paths** de-hardcoded (§3.4, Q12)
+
+### Dependency resolution 🏆
+- [ ] `<type>:<id>` grammar preserved
+- [ ] **Recursive transitive closure** preserved
+- [ ] **Dedupe** preserved
+- [ ] **Cycle termination** preserved — and now **detected + reported**
+- [ ] `aliases[]` resolution preserved
+- [ ] **`context:` wildcard expansion** preserved (note: **prefix match**, not true glob)
+- [ ] Wildcard **restricted to `context:`** (or consciously widened)
+- [ ] `get_registry_key` singular↔plural mapping preserved (incl. `config` staying singular)
+- [ ] `get_install_path` (`.opencode/` → `$INSTALL_DIR`) → **adapter output-path logic**
+- [ ] 🔴 **Unresolvable refs now FAIL** instead of silently resolving to nothing (§7.3)
+- [ ] 🔴 **Dual-namespace (id vs path) refs unified** — fixes `/add-context`'s 3 dead deps
+- [ ] Wildcard-produced path-ids resolve their own transitive deps
+
+### Registry
+- [ ] All **245 component entries** across 8 types preserved
+- [ ] `id`/`name`/`type`/`path`/`description`/`tags[]`/`dependencies[]`/`category` preserved
+- [ ] `aliases[]` (3) + `files[]` (4 skills) preserved
+- [ ] `path` re-pointed `.opencode/…` → `/content/…`; **output paths adapter-computed**
+- [ ] `categories` install tiers (essential/standard/extended/specialized/meta) preserved
+- [ ] 🔴 **`category` name collision** (install tier vs domain) resolved (Q5)
+- [ ] `repository` preserved
+- [ ] 3 conflicting version fields collapsed (Q21)
+- [ ] Orphan top-level `subagents.test.simple-responder` key deleted
+- [ ] **`files[]` generated**, not hand-maintained
+
+### Profiles
+- [ ] All **5 profiles** preserved (essential/developer/business/full/advanced)
+- [ ] `name`/`description`/`components[]` preserved
+- [ ] `badge` (`RECOMMENDED`) preserved
+- [ ] `additionalPaths[]` (advanced: `.Building/`, `.github/workflows/`) preserved or replaced (§8.2)
+- [ ] 🔴 **registry-vs-file profile drift reconciled** before deleting `.opencode/profiles/` (§8.2, Q22)
+- [ ] 🔴 `developer` profile regains `context:root-navigation` + `context:context-paths-config`
+- [ ] `scripts/registry/check-dependencies.ts` repointed off `.opencode/profiles/`
+
+### Skills
+- [ ] **4 OpenCode skills** preserved (task-management, context-manager, context7, smart-router-skill)
+- [ ] 🔴 **12 CC skills harvested** (§4.5, Q16) — `code-execution`, `code-review`, `context-discovery`, `context-setup`, `debugger`, `external-research`, `oac-approach`, `parallel-execution`, `task-breakdown`, `test-generation`, `using-oac`, `verification-before-completion`
+- [ ] `SKILL.md` frontmatter (`name`/`description`/`version`/`author`/`type`/`category`/`tags[]`)
+- [ ] `description` remains greppable by `^description:` (session-start catalogue depends on it)
+- [ ] `router.sh` preserved **with the executable bit**
+- [ ] `scripts/*.ts` preserved
+- [ ] `workflows/*.md` preserved (incl. `8-stage-delivery.md`, `context-handoff.md`, `planning-agents.md`)
+- [ ] `tests/*.test.ts` relocated
+- [ ] `smart-router-skill` `config/personality-config.json` + `scripts/{sherlock,stark,yoda}-workflow.sh` preserved
+- [ ] `context7` `library-registry.md` + `navigation.md` preserved
+- [ ] 🔴 `.opencode/skill/project-orchestration` (orphan) registered or deleted (§4.3, Q7)
+- [ ] 🔴 `.opencode/skill/task-management/tests/` reunited with its skill
+- [ ] Bun/TS runtime dependency addressed for npm distribution (§4.4, Q15)
+
+### Tools
+- [ ] `tool:env` (`loadEnvVariables`, multi-path `.env` search) preserved
+- [ ] `tool:gemini` (image gen/edit/analyze) preserved
+- [ ] `tool:gemini → tool:env` dependency preserved
+- [ ] `tool/template/` → CLI scaffold
+- [ ] Non-OpenCode targets: tools unsupported → **explicit warning**, or MCP path (Q18)
+- [ ] 🔴 `.opencode/tool/.env` **NOT** carried into `/content/` (Q17)
+
+### Plugins & hooks
+- [ ] `plugin:notify` preserved (incl. `ENABLED = false` default)
+- [ ] 🔴 `agent-validator.ts` + `.disabled` twin resolved; `VALIDATOR_GUIDE.md` + tests preserved
+- [ ] 🔴 `plugins/coder-verification/` (index.ts, plugin.json, README, IMPLEMENTATION_SUMMARY) registered or deleted
+- [ ] `plugin.json` manifest shape unified with `.claude-plugin/plugin.json`
+- [ ] 🔴 OpenCode `event` callback vs CC `hooks.json` — hook model decided (§6, Q19)
+- [ ] 🔴 `.opencode/plugin/.env` **NOT** carried into `/content/` (Q17)
+
+### Claude Code plugin 🏆
+- [ ] `.claude-plugin/plugin.json` generated (name/description/version/author/license/repo/homepage/keywords)
+- [ ] `hooks/hooks.json` `SessionStart` (+ `timeout: 30`, `${CLAUDE_PLUGIN_ROOT}`) generated
+- [ ] **`session-start.sh` #1** inline `using-oac` full text
+- [ ] **#2** skill catalogue (`- oac:<name> — <description>`)
+- [ ] **#3** first-run warning when no `.context-manifest.json` (project **or** global)
+- [ ] **#4** OAC system paths block (plugin root + protocol path)
+- [ ] **#5** context-discovery instruction ("once per session")
+- [ ] **#6** dual-format JSON (`additional_context` **and** `hookSpecificOutput.additionalContext`)
+- [ ] 🔴 **`escape_for_json()` injection defense preserved** (backslash-first ordering) — *security control*
+- [ ] `<EXTREMELY_IMPORTANT>` / `<important-reminder>` wrappers preserved
+- [ ] **`.context-manifest.json`** written on install: `version`/`profile`/`source.{repository,branch,commit}`/`downloaded_at`/`categories[]`/`files{}`
+- [ ] 🔴 **OpenCode installs gain a manifest too** (today they have none ⇒ not updatable)
+- [ ] `.oac.json` (`version`, `context.root`) supported; **`.oac.example` vs `.oac.json.example` deduped**
+- [ ] Project-local **and** global manifest locations supported
+- [ ] `scripts/install-context.{js,sh,ts}` → **one** implementation in `/packages/cli`
+- [ ] `scripts/utils/{git-sparse,registry-fetcher}.ts` (sparse-checkout fetch) preserved → CLI
+- [ ] `scripts/types/{manifest,registry}.ts` → `/packages/core` types
+- [ ] `scripts/cleanup-tmp.sh` → CLI (`oac clean`)
+- [ ] `settings.json` (opusplan model, PR #264) + `settings.local.json` decided (Q3)
+- [ ] 🔴 `.context-manifest.json` `profile: "standard"` (not a real profile name) fixed
+
+### Prompts / misc
+- [ ] 🔴 **10 per-model prompt variants** (`openagent/{gemini,gpt,grok,llama,minimax,openrouter}.md`, `opencoder/{gemini,gpt,grok,llama}.md`) — tension with `model: null` (Q23)
+- [ ] `prompts/*/TEMPLATE.md` + READMEs preserved
+- [ ] Eval `results/*.json` — keep or gitignore
+- [ ] `.opencode/docs/` (3 docs) relocated
+- [ ] 🔴 `.opencode/scripts/task-cli.ts` duplicate resolved
+- [ ] `.opencode/config.json` (`{"agent": "eval-runner"}`) — **almost certainly a bad committed default**
+- [ ] `config:env-example`, `config:readme`, `config:agent-metadata` install-components decided (Q24)
+
+---
+
+## Open Questions
+
+Ordered by blocking-ness. **Q1, Q2, Q10, Q12, Q16, Q20 block the schema (Agent B) and should be
+answered first.**
+
+**Q1 — Canonical id vs filename vs display name.** `tester` (id) / `test-engineer.md` (file) /
+`TestEngineer` (frontmatter `name`) / `test-engineer` (CC) all denote one agent. Is `id`
+independent of filename (needs `aliases[]` + an id→path map), or do we **rename files to match
+ids** as a one-time migration and make `filename == id` an invariant? The latter is much simpler
+but breaks any external reference to the old paths. **Recommendation: filename == id, with
+`aliases[]` retained for dependency back-compat.**
+
+**Q2 — `capabilities`: flat scalars or ordered rule lists?** The `00-INDEX.md` worked example
+shows `read: allow` / `edit: deny`. That shape **cannot** express OpenCode's glob-scoped,
+ordered, tri-state permissions — and those encode real security controls (`**/*.env*: deny`,
+`coder-agent`'s bash allowlist, `rm -rf /*: deny`). Adopt the rule-list form (§1.3) with scalar
+sugar? **This changes the worked example in `00-INDEX.md`** and needs sign-off.
+
+**Q3 — Model policy vs shipped reality.** Locked Decision #2 says `model: null`. But
+`plugins/claude-code/agents/*.md` hardcode `model: sonnet`, `settings.json` sets `opusplan`
+(PR #264, merged), and `.opencode/prompts/` holds 10 per-model prompt variants. Does `model: null`
+mean (a) **never** emit `model:` for any target, or (b) allow a **per-adapter default** in adapter
+config (not content)? (b) preserves shipped behavior; (a) is a deliberate behavior change.
+
+**Q4 — `0-category.json`: promote or delete?** It's read by nothing. Its `icon`/`order`/`status`
+are richer than `registry.json#categories` (flat strings) and worth promoting. But
+`commonSubagents`/`commonTools`/`commonContext` (with globs) overlap `dependencies` +
+`capabilities.delegate` + `context[]` without being enforced. Promote the category display
+metadata and **delete** the `common*` advisory fields?
+
+**Q5 — `category` means two things.** Registry `category` = **install tier**
+(`essential`/`standard`/`extended`/`specialized`/`meta`). Metadata `category` = **domain**
+(`core`/`meta`/`content`/`subagents/core`). Same key, different value spaces, both on the same
+component. Rename to `tier` + `category`? And normalize the flat-vs-path domain form
+(`core` vs `subagents/core`)?
+
+**Q6 — `author: "opencode"`.** All 28 metadata entries claim author `opencode` — a *tool name*
+used as an author. Now that OpenCode is "just another adapter", should this become `oac`? Affects
+every generated `agent-metadata.json`.
+
+**Q7 — The dark orchestration feature.** 6 unregistered agents (`subagents/planning/*` +
+`stage-orchestrator`) + the unregistered `.opencode/skill/project-orchestration` skill
+(8-stage delivery workflow, stage CLI, context handoff) + `.opencode/docs/agents/planning-agents-guide.md`
+form **one coherent, fully-built, uninstallable feature**. Ship it (register + profile it) or
+delete it? Carrying it into `/content/` as-is just relocates the ambiguity.
+
+**Q8 — The 8 eval templates.** `command/openagents/new-agents/templates/test-{1..8}-*.yaml`
+encode OAC's agent quality bar (planning-approval, context-loading, incremental, tool-usage,
+error-handling, extended-thinking, compaction, completion). Move to `/evals`? Wire into CI as the
+acceptance gate for new agents? Coordinate with Agent E.
+
+**Q9 — Version format.** Context files use `Version: X.Y`; agents/skills use SemVer `X.Y.Z`;
+registry has `version` **and** `schema_version` **and** `metadata.schemaVersion`. One format
+(SemVer) with a migration of ~300 context headers, or keep `X.Y` for context as a distinct field?
+
+**Q10 — Context metadata: HTML comment or YAML frontmatter?** ~297 files use
+`<!-- Context: … | Priority: … -->`; a handful additionally use YAML. **Two conventions in one
+tree.** Options: (a) neutral parser reads the HTML comment (zero content churn, must be
+hand-written — `gray-matter` will silently return nothing); (b) migrate all ~297 to YAML
+(one-time churn, standard tooling, but YAML renders visibly in markdown viewers — which is *why*
+the HTML comment was chosen). **This is the highest-risk decision in the doc: pick (a) or (b),
+but never let a generic parser near these files.**
+
+**Q11 — Unify context discovery on `.oac.json`?** OpenCode has `paths.json` (static local/global);
+CC has the `.oac.json` protocol (fast path → discovery chain → `navigation.md` validity check →
+self-healing write). The CC design is strictly better. Make `.oac.json` the neutral mechanism and
+demote `paths.json` to OpenCode-adapter output?
+
+**Q12 — De-hardcoding `.opencode/` from ~129 content bodies.** Token substitution
+(`{{CONTEXT_ROOT}}`, explicit + lintable, touches 129 files), adapter regex rewrite (zero churn,
+brittle), or id-based indirection (cleanest, biggest rewrite)? **Recommendation: tokens now +
+a `no raw .opencode/ in /content/` lint; ids later.** Without this, generated CC agents point at
+paths that don't exist — likely why the CC plugin was hand-written in the first place.
+
+**Q13 — Auto-generate registry context entries?** 112 of 300 context files are unregistered
+(incl. 43 of 79 `navigation.md`). Hand-maintenance has demonstrably failed. Generate all context
+entries from disk at build time, with `navigation.md` auto-included whenever any sibling is
+installed? What then determines a context's `category`/tier — the HTML `Priority`? Its directory?
+
+**Q14 — Root `index.md` vs root `navigation.md`.** Both exist at `.opencode/context/`. Which is
+the canonical entry point? (`navigation.md` is what `context-discovery-protocol.md` checks for.)
+Merge or define the split.
+
+**Q15 — Bun/TypeScript runtime for skills.** Every `router.sh` shells out to `.ts` under Bun.
+For npm distribution, does OAC (a) require Bun, (b) precompile skills to JS, or (c) rewrite
+routers in Node? Also: `router.sh` is bash — **what is the Windows story?** Blocks Agent D.
+
+**Q16 — Which skill set seeds `/content/skills/`?** OpenCode has 4 skills; CC has 12; the sets
+are **disjoint**. The CC 12 are the product's actual shipped UX (`oac:code-review`,
+`oac:task-breakdown`, …). Seed from CC, from OpenCode, or the union of 16? **If `/content/` is
+seeded only from `.opencode/`, the 12 CC skills are silently destroyed.**
+
+**Q17 — Committed `.env` files.** `.opencode/tool/.env` and `.opencode/plugin/.env` are in the
+repo. Are they real secrets (⇒ rotate + purge history) or empty templates? Either way they must
+**not** be copied into `/content/` or any build output. Needs a human to look.
+
+**Q18 — Tools on non-OpenCode targets.** `.opencode/tool/*` implements the OpenCode tool API.
+Claude Code has no equivalent primitive (nearest: MCP servers). Are tools (a) OpenCode-only with
+an explicit "unsupported on target X" warning, or (b) re-expressed as MCP servers for CC? (b) is
+a large scope increase. Agent C.
+
+**Q19 — Is `hooks` a neutral content type?** OpenCode hooks = a TS plugin exporting an `event`
+callback. CC hooks = declarative `hooks.json` → shell command. These are not the same shape and
+don't obviously unify. Options: (a) `hooks` is per-adapter, not content; (b) a neutral
+event→action schema that both render (constrains OpenCode plugins to declarative actions);
+(c) declare only `SessionStart` neutrally (the one hook that matters) and leave the rest
+per-adapter. **Recommendation: (c).**
+
+**Q20 — Fail-fast on unresolvable dependencies?** Today `jq … || echo ""` means an unknown
+component resolves to **zero dependencies, silently** — which is why `/add-context`'s three
+path-style context deps (mvi, frontmatter, project-intelligence) almost certainly install
+nothing today. Confirm the new resolver **errors** on unresolvable refs. This is a **behavior
+change that will surface currently-hidden breakage** (probably a lot of it) — the migration
+should expect a burst of newly-visible failures and budget for it. Coordinate with Agent E.
+
+**Q21 — Registry version fields.** Top-level `version: "2.0.0"`, top-level
+`schema_version: "2.0.0"`, and `metadata.schemaVersion: "1.0.0"` — the last **contradicts** the
+second. Which is authoritative? Collapse to one `schemaVersion` + one `contentVersion`?
+
+**Q22 — Reconciling profile drift before deletion.** All 5 profiles differ between
+`registry.json#profiles` (authoritative — `install.sh` reads it) and `.opencode/profiles/*/profile.json`
+(read only by `scripts/registry/check-dependencies.ts`). Each side has things the other lacks —
+the file side has `root-navigation` + `context-paths-config` (which the recommended `developer`
+profile is **missing**), the registry side has broader wildcards. This needs a **human to
+reconcile 5 diffs**; it cannot be auto-merged. Blocks deleting `.opencode/profiles/`.
+
+**Q23 — Per-model prompt variants.** `.opencode/prompts/core/{openagent,opencoder}/{gemini,gpt,grok,llama,minimax,openrouter}.md`
+are 10 model-specific rewrites of two agents' system prompts, with committed eval results. Under
+"no hardcoded models", do these (a) get deleted, (b) become an opt-in `prompts/` overlay keyed by
+model family, or (c) move to `/evals` as prompt-engineering artifacts? Are they even current?
+
+**Q24 — `config:` components.** `config:readme` installs **this repo's `README.md`** into the
+user's project; `config:env-example` installs `env.example`; `config:agent-metadata` installs the
+sidecar. `readme` looks like a bug (why would a user want OAC's README in their project?).
+Keep, drop, or replace with a generated project README?
+
+---
+
+## Appendix — Sources
+
+Every claim above is grounded in these files (all paths repo-relative):
+
+- `.opencode/agent/**/*.md` (34), `.opencode/agent/**/0-category.json` (5)
+- `.opencode/config/agent-metadata.json`
+- `.opencode/command/**/*.md` (20), `.opencode/command/openagents/new-agents/templates/*.yaml` (9)
+- `.opencode/context/**` (300), esp.
+  `context/navigation.md`, `context/index.md`, `context/CODEBASE_STANDARDS.md`,
+  `context/core/config/paths.json`,
+  `context/core/context-system/standards/{frontmatter,mvi,structure,templates,codebase-references}.md`,
+  `context/core/context-system/operations/*.md`, `context/core/standards/code-quality.md`,
+  `context/tasks/schemas/{task,subtask}-schema.json`
+- `.opencode/skills/*/SKILL.md` (4), `.opencode/skill/{project-orchestration,task-management}/**`
+- `.opencode/tool/{env,gemini,template}/index.ts`, `.opencode/tool/.env`
+- `.opencode/plugin/{notify.ts,agent-validator.ts,agent-validator.ts.disabled,.env}`,
+  `.opencode/plugins/coder-verification/{index.ts,plugin.json}`
+- `.opencode/profiles/*/profile.json` (5), `.opencode/prompts/**`, `.opencode/docs/**`,
+  `.opencode/scripts/task-cli.ts`, `.opencode/{opencode.json,config.json}`
+- `registry.json` (107KB) — `components` (8 types / 245 entries), `profiles` (5), `categories` (5)
+- `install.sh` — `get_registry_key():331`, `get_install_path():353`,
+  `expand_context_wildcard():361`, `expand_selected_components():373`,
+  `resolve_dependencies():420-481`, profile reads `:294,:636-688`, `additionalPaths` `:1222`
+- `scripts/registry/check-dependencies.ts:159`
+- `plugins/claude-code/**` — `hooks/{hooks.json,session-start.sh}`,
+  `.claude-plugin/plugin.json`, `.context-manifest.json`, `.oac.json.example`, `.oac.example`,
+  `agents/*.md` (7), `commands/*.md` (6), `skills/*/SKILL.md` (12),
+  `skills/context-discovery/context-discovery-protocol.md`, `scripts/**`, `settings.json`
+- `CONTEXT_SYSTEM_GUIDE.md`, `COMPATIBILITY.md` (repo root)

+ 927 - 0
docs/architecture/canonical-refactor/02-canonical-schema.md

@@ -0,0 +1,927 @@
+# 02 — Canonical IR Schema — **v2**
+
+> **Status:** Specification only — v2 (realigned to `00-INDEX.md` v2). No implementation.
+> Zod blocks are *illustrative*: they define the target shape of `packages/core/src/schema/*.ts`,
+> not code to ship as-is.
+> **Read `00-INDEX.md` (v2) first.** Where this doc conflicts with the index, **the index wins**.
+>
+> **Changes from v1:**
+> - §1.2 **Capabilities rewritten to Option A** (ordered rule list + scalar sugar) per locked
+>   decision #5. The v1 flat model is withdrawn — verified provably lossy (§1.2.1).
+> - **`guards` hint deleted.** Option A puts safety globs in content natively; the v1 open
+>   question about globs is resolved and removed.
+> - §4 **Context rewritten** around the MVI HTML-comment format per locked decision #6.
+>   No YAML migration. Adds a precise parser spec — **the highest-risk parser requirement in
+>   the project**.
+> - Census corrected (§0.1); `CapabilityMatrix` row corrections folded into §8.
+
+## 0. What this document is
+
+The canonical **Intermediate Representation (IR)** is the tool-neutral in-memory shape that
+`oac build --target <tool>` consumes. Authors write neutral content in `/content/`; the parser
+produces IR objects validated by these schemas; adapters (Agent C) transform IR → per-tool
+output. This doc defines the IR for **all seven content types** — Agent, Skill, Command, Context,
+Tool, Hook, Registry entry.
+
+For every field: name, type, required/optional, default, and whether it is a **neutral invariant**
+(all adapters honor it) or a **hint** (adapters may drop it, but must report the drop via
+`CapabilityMatrix` — never silently, per the index's "known and reported" rule).
+
+`packages/compatibility-layer/src/types.ts` is the starting point but is still OpenCode-shaped in
+several places. Each section ends with a **"Too OpenCode-shaped — must change"** callout citing
+concrete lines.
+
+### 0.1 Corrected census (re-verified independently for this doc)
+
+| Item | Count | Method |
+|---|---|---|
+| Agents | **34** `.md` | `find .opencode/agent -name '*.md'` |
+| Commands | **20** `.md` | `find .opencode/command -name '*.md'` |
+| Context | **297** `.md` | `find .opencode/context -name '*.md'` |
+
+⚠️ **Context count delta vs index.** The index states **296** (286 HTML-comment / 3 YAML /
+7 neither). My reproducible count over `.opencode/context/**/*.md` gives **297**, and the
+format breakdown is materially different from a naive reading — see §4.1, which supersedes the
+"286/3/7" summary with a verified line-position analysis. The 1-file delta does not change any
+design decision (the parser must handle every bucket regardless), but the *bucket semantics* do
+change the parser spec. Flagged for reconciliation in Open Questions.
+
+### 0.2 Design rules for neutrality
+
+1. **Intent over syntax.** The IR encodes what the author wants, never a tool's serialization.
+2. **Closed vocabularies for invariants, open strings for hints.** Anything an adapter must reason
+   about is a Zod `enum`; free-form author intent stays `string`.
+3. **`null` means "tool default"; absent means "unspecified".** Per locked decision #2.
+4. **Metadata is folded into frontmatter.** No sidecar in `/content/`. The OpenCode *adapter* may
+   re-emit `agent-metadata.json`; the IR has no sidecar concept.
+5. **The authored on-disk format and the IR shape do not have to match** (locked decision #6).
+   This is load-bearing for Context (§4): MVI HTML-comment on disk → structured fields in memory.
+
+### 0.3 Shared primitives
+
+```ts
+import { z } from "zod";
+
+/** Stable machine identity. Slug form; adapters re-case per tool (kebab for Claude,
+ *  PascalCase for OpenCode). NEUTRAL INVARIANT — the join key across registry + deps. */
+export const IdSchema = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/,
+  "id must be kebab-case: lowercase alphanumeric words joined by single hyphens");
+
+/** Human display name. HINT for casing — adapters slugify/re-case freely. */
+export const NameSchema = z.string().min(1);
+
+/** One-line purpose. NEUTRAL INVARIANT — every tool surfaces a description. */
+export const DescriptionSchema = z.string().min(1);
+
+/** Free-form discovery labels. NEUTRAL but non-semantic — adapters may ignore. */
+export const TagsSchema = z.array(z.string()).default([]);
+
+/** Semver of the authored component. NEUTRAL for the registry/update engine; most tools
+ *  drop it (HINT at the tool layer). NOTE: context files use X.Y on disk → normalized (§4). */
+export const VersionSchema = z.string().regex(/^\d+\.\d+\.\d+$/).default("1.0.0");
+```
+
+---
+
+## 1. Agent
+
+Anchor of the refactor; must serialize to the three worked-example outputs in `00-INDEX.md` v2.
+Real sources modelled against: `.opencode/agent/subagents/code/reviewer.md`,
+`.opencode/agent/subagents/code/coder-agent.md`, `.opencode/agent/core/openagent.md`.
+
+### 1.1 Role — `mode` → `role`
+
+```ts
+/** Whether the agent is user-facing or only reachable via delegation.
+ *  NEUTRAL INVARIANT. Rationale: "who can invoke this" is universal (Claude: agent vs
+ *  sub-agent; OpenCode: mode primary|subagent). We drop OpenCode's third value "all" —
+ *  a tool quirk, not an authoring intent; no other tool models it. */
+export const RoleSchema = z.enum(["primary", "subagent"]);
+```
+
+`types.ts:88` `AgentModeSchema` leaks `"all"`. On import, legacy `all` normalizes to `primary`
+(widest reach) with a migration note; the IR never stores it.
+
+⚠️ Per index v2 finding #5, `agentModes: claude` is **partial**, not full: the CC *plugin* layout
+has no per-agent `mode:` — every file under `agents/` is a subagent, so `role: primary` flattens
+and **must warn**. Reflected in §8.
+
+### 1.2 Capabilities — **Option A: ordered rule list + scalar sugar** (locked)
+
+#### 1.2.1 Why the v1 flat model is withdrawn (verified)
+
+I re-verified the two agents the index cites. Both refute a flat scalar model:
+
+```yaml
+# .opencode/agent/subagents/code/coder-agent.md — deny-all-then-allowlist
+permission:
+  bash:
+    "*": "deny"
+    "bash .opencode/skills/task-management/router.sh complete*": "allow"
+    "bash .opencode/skills/task-management/router.sh status*": "allow"
+  edit:
+    "**/*.env*": "deny"      # real per-agent security controls
+    "**/*.key": "deny"
+    "**/*.secret": "deny"
+    "node_modules/**": "deny"
+    ".git/**": "deny"
+```
+
+```yaml
+# .opencode/agent/core/openagent.md — ask-by-default with specific denies
+permission:
+  bash:
+    "*": "ask"
+    "rm -rf *": "ask"
+    "rm -rf /*": "deny"
+    "sudo *": "deny"
+    "> /dev/*": "deny"
+```
+
+No flat scalar expresses either: `bash: deny` breaks coder-agent's own `router.sh`; `bash: allow`
+defeats the deny-all; and `coder-agent` (deny-by-default) vs `openagent` (ask-by-default) is a
+deliberate distinction a flat model erases. **Option A is required.**
+
+#### 1.2.2 The rule model
+
+```ts
+/** A single permission decision. NEUTRAL across allow/deny. "ask" is neutral in intent but
+ *  UNSUPPORTED by claude/cursor/windsurf (CapabilityMatrix askPermissions = none) — a HINT
+ *  there: adapters degrade per a documented strategy and MUST warn. */
+export const DecisionSchema = z.enum(["allow", "deny", "ask"]);
+
+/** One scoped rule. `scope` is a glob whose namespace depends on the capability (§1.2.3).
+ *  NEUTRAL INVARIANT — {scope, decision} is the irreducible unit of authored intent. */
+export const RuleSchema = z.object({
+  scope: z.string().min(1),
+  decision: DecisionSchema,
+});
+
+/** The full ordered form. Order is SEMANTIC and must be preserved end-to-end (§1.2.4). */
+export const RuleListSchema = z.array(RuleSchema);
+```
+
+#### 1.2.3 Scope namespaces (per capability)
+
+The closed capability vocabulary is unchanged from v1; what changes is that each maps to a
+`RuleList` rather than a scalar. **`delegate` is not a special case** — it is simply the capability
+whose scope namespace is *agent ids* instead of path/command globs. That unification is the main
+elegance of Option A:
+
+| Capability | Scope namespace | Example scope |
+|---|---|---|
+| `read` `write` `edit` `glob` `grep` | path glob | `**/*.env*`, `node_modules/**` |
+| `bash` | command glob | `sudo *`, `bash …/router.sh status*` |
+| `web` | URL/domain glob | `https://internal.*` |
+| `delegate` | agent id (kebab) or `*` | `contextscout` |
+
+#### 1.2.4 Precedence — ⚠️ index prose and index example conflict; needs ratification
+
+The index v2 says *"first-match-wins ordering preserved"*, but its own worked example — and
+**100% of the real scoped agents** — author **broad-first, specific-after**:
+
+- coder-agent: `"*": deny` is **first**, allowlist entries follow.
+- openagent: `"*": ask` is **first**, specific denies follow.
+
+Under **naive first-match-wins in authored order**, `"*"` matches everything and every later rule
+is unreachable: coder-agent's `router.sh` would be **denied** (breaking the exact behavior locked
+decision #5 exists to protect), and openagent's `sudo *` would resolve to **ask**, not **deny** —
+a real security regression. So first-match-wins over the authored order is provably wrong against
+the current corpus.
+
+Two semantics produce correct results on all real data:
+
+| Semantics | Definition | Verdict on real data |
+|---|---|---|
+| **Last-match-wins** *(recommended)* | Evaluate in authored order; the **last** matching rule wins. | ✅ correct for both agents |
+| Most-specific-wins | Order-independent; the most specific matching pattern wins. | ✅ correct for both agents |
+| Naive first-match-wins | First matching rule wins. | ❌ breaks both |
+
+**Recommendation: last-match-wins.** It (a) makes the index's own worked example correct *as
+written*, with no re-authoring of 34 agents; (b) reads the way humans write policy — broad
+default, then exceptions; (c) maps 1:1 to OpenCode's YAML mapping key order (JS string keys
+preserve insertion order), so IR→OpenCode→IR round-trips order-preserving and lossless.
+Most-specific-wins would need a specificity metric (pattern length? segment count?) that is
+ambiguous for equal-specificity overlaps.
+
+**Both candidates agree on every one of the 34 current agents**, so this choice is about
+future-proofing, not migration risk. Critically, **the schema shape is identical either way** —
+only the resolver's evaluation differs. So the shape below can be locked now and the semantics
+ratified independently without blocking any workstream. Raised in Open Questions.
+
+```ts
+/** Resolve a request against a rule list. Illustrative signature only.
+ *  Terminal fallback when NO rule matches = "allow" (§1.2.5). */
+declare function resolve(rules: Rule[], request: string): "allow" | "deny" | "ask";
+```
+
+#### 1.2.5 Defaults — absent capability vs. no-rule-matched
+
+Two distinct "nothing said" cases, and getting them wrong breaks every existing agent:
+
+- **Capability absent** from `capabilities` → empty list `[]` → *no author constraint* → the
+  tool's own default applies. Rationale: `openagent` declares no `write` key and OpenCode
+  therefore **allows** write. If the IR defaulted `write: deny` (as v1 did), the build would
+  silently strip a capability the agent relies on.
+- **Rules present, none match** → terminal fallback **`allow`**.
+
+Rationale for both: OpenCode's `permission` block is a **restriction list** over an
+otherwise-permitted tool, and all 34 agents were authored against that semantics. Reinterpreting
+them as deny-by-default would break the corpus. Authors wanting deny-by-default write it
+explicitly — exactly as `coder-agent` does with a leading `{scope: "*", decision: "deny"}`.
+
+> **v1 correction:** the v1 `CapabilitiesSchema` defaulted `write/edit/bash/web` to `deny`. That
+> was wrong on this evidence and is withdrawn along with the flat model.
+
+#### 1.2.6 Sugar ↔ full desugaring
+
+Authors use sugar for the simple case; the parser desugars to the canonical list form. **The IR
+stores only the desugared full form** — one shape for adapters to consume.
+
+```ts
+/** Scalar sugar:  read: allow            → [{ scope: "*", decision: "allow" }] */
+const ScalarSugar = DecisionSchema;
+
+/** Map sugar (delegate): { contextscout: allow, externalscout: allow }
+ *                        → [{ scope: "contextscout",  decision: "allow" },
+ *                           { scope: "externalscout", decision: "allow" }]
+ *  Map iteration order = authored key order (preserved by YAML→JS object key order).
+ *  Map sugar is accepted for ANY capability, not just delegate — it is just a rule list
+ *  whose keys are scopes. This is precisely OpenCode's on-disk shape, which makes the
+ *  OpenCode importer a pure desugar with zero information loss. */
+const MapSugar = z.record(z.string(), DecisionSchema);
+
+/** What an author may write for one capability. */
+export const CapabilityInputSchema = z.union([ScalarSugar, MapSugar, RuleListSchema]);
+
+/** Desugaring (illustrative):
+ *    "deny"                          → [{ scope: "*", decision: "deny" }]
+ *    { "a": "allow", "b": "deny" }   → [{ scope: "a", decision: "allow" },
+ *                                       { scope: "b", decision: "deny" }]
+ *    [ {scope,decision}, … ]         → as authored (identity)
+ *  Desugaring is total and order-preserving. Re-sugaring on output is an adapter
+ *  concern: a single {scope:"*"} rule MAY re-emit as a scalar. */
+declare function desugar(input: z.infer<typeof CapabilityInputSchema>): Rule[];
+```
+
+#### 1.2.7 The capability set
+
+```ts
+/** Authored form — sugar allowed per capability. */
+export const CapabilitiesInputSchema = z.object({
+  read:     CapabilityInputSchema.optional(),
+  write:    CapabilityInputSchema.optional(),
+  edit:     CapabilityInputSchema.optional(),
+  bash:     CapabilityInputSchema.optional(),
+  grep:     CapabilityInputSchema.optional(),
+  glob:     CapabilityInputSchema.optional(),
+  web:      CapabilityInputSchema.optional(),
+  delegate: CapabilityInputSchema.optional(),
+}).default({});
+
+/** Canonical IR form — always desugared rule lists. Absent capability ⇒ [] ⇒ tool default.
+ *  Verb rationale (unchanged from v1): each is an intent verb every target can express or
+ *  be told to refuse. `delegate` replaces OpenCode's `task`; `patch` (an OpenCode alias of
+ *  edit) and `question` are dropped as tool quirks. */
+export const CapabilitiesSchema = z.object({
+  read:     RuleListSchema.default([]),
+  write:    RuleListSchema.default([]),
+  edit:     RuleListSchema.default([]),
+  bash:     RuleListSchema.default([]),
+  grep:     RuleListSchema.default([]),
+  glob:     RuleListSchema.default([]),
+  web:      RuleListSchema.default([]),
+  delegate: RuleListSchema.default([]),
+}).default({});
+```
+
+Round-trip of the index's worked examples: `code-reviewer`'s all-sugar block desugars to eight
+single-rule lists and re-emits as OpenCode's `{ "*": "deny" }` form; `coder-agent`'s bash list
+survives intact in both directions. **OpenCode round-trips Option A exactly** (index v2).
+
+#### 1.2.8 Collapsing for lossy targets
+
+Claude/Cursor/Windsurf have no scoped permissions (`granularPermissions` + `pathPatterns` =
+`none` everywhere but OAC). They need a **collapse** from `RuleList` → a single grant:
+
+```ts
+/** Collapse a rule list to a coarse grant for targets without scoping.
+ *  Reuses the existing permissive strategy in PermissionMapper.resolvePermissionRule's
+ *  record branch (hasAllow || !hasDeny) so behavior stays continuous with today's adapters.
+ *  MUST warn whenever rules.length > 1 or any rule.scope !== "*" — per index v2:
+ *  "Scoped rules degrade to a coarse allowlist and MUST warn."
+ *  Reuse the existing warning templates at BaseAdapter.ts:187-208 verbatim. */
+declare function collapse(
+  rules: Rule[],
+  strategy: "permissive" | "restrictive" | "ask-as-deny",
+): { grant: "allow" | "deny" | "ask"; warnings: string[] };
+```
+
+Worked: `coder-agent.bash` collapses to `allow` under `permissive` (it has allows) — meaning
+Claude gets `Bash` with **none of the deny-all scoping**. That is a genuine, material capability
+loss and exactly why it must warn loudly rather than degrade quietly.
+
+### 1.3 Inference block (temperature, maxSteps, model=null)
+
+```ts
+/** Model tuning knobs, grouped so adapters process them as a unit and the degradation
+ *  report is legible ("inference.temperature dropped; inference.model → tool default"). */
+export const InferenceSchema = z.object({
+  /** null ⇒ tool default (locked decision #2 — NO hardcoded models). When set, a neutral
+   *  family id (e.g. "claude-sonnet-4"); ModelMapper resolves to the tool's dated id. */
+  model: z.string().nullable().default(null),
+  /** 0.0–2.0. NEUTRAL intent; HINT at Claude (temperatureControl = none → dropped + warned),
+   *  partial at cursor/windsurf. */
+  temperature: z.number().min(0).max(2).optional(),
+  /** HINT — only OAC/OpenCode honor it (maxSteps: claude/cursor/windsurf = none). */
+  maxSteps: z.number().int().positive().optional(),
+}).default({ model: null });
+```
+
+`types.ts` puts these as flat siblings (181–184) and — **confirmed defect, index v2** —
+`ModelIdentifierSchema = z.union([z.string(), z.string()])` (line 116) is a **no-op union** with
+no null default, so it cannot enforce decision #2.
+
+### 1.4 Context references
+
+```ts
+/** Priority vocabulary. See §4.3 — real data contains a value outside this enum. */
+export const PrioritySchema = z.enum(["critical", "high", "medium", "low"]);
+
+/** Pointer to a Context document. NEUTRAL: the reference survives wherever externalContext
+ *  is supported (Claude bundles + injects via SessionStart hook; OpenCode/Windsurf copy;
+ *  Cursor inlines). priority/description are HINTS — dropped by claude/cursor/windsurf
+ *  (contextPriority = none). */
+export const ContextRefSchema = z.object({
+  /** Registry id of a context OR a repo-relative path under /content/context. */
+  path: z.string().min(1),
+  priority: PrioritySchema.optional(),
+  description: z.string().optional(),
+});
+```
+
+Matches `types.ts:63-67` (already neutral). Only change: prefer `id` references over raw
+`.opencode/...` paths, which bake in the source tool.
+
+### 1.5 Dependencies (typed refs)
+
+```ts
+export const DependencyKindSchema = z.enum([
+  "subagent", "context", "command", "skill", "tool",
+]);
+
+export const DependencyRefSchema = z.object({
+  kind: DependencyKindSchema,
+  id: IdSchema,
+});
+
+/** Also accepts the compact "kind:id" string — the REAL on-disk format in both
+ *  .opencode/config/agent-metadata.json and registry.json ("subagent:contextscout"). */
+export const DependencyInputSchema = z.union([
+  DependencyRefSchema,
+  z.string().regex(/^(subagent|context|command|skill|tool):[a-z0-9-]+$/),
+]);
+```
+
+Rename `type` → `kind` (avoids collision with the codebase's several other `type` fields).
+Neutral because tools consume the resolved closure, not the notation.
+
+### 1.6 Examples (few-shot)
+
+```ts
+/** NEUTRAL as authored data; HINT at the tool layer. Claude BAKES these into the agent
+ *  description as <example> blocks (index worked example → claude target); OpenCode has no
+ *  field for them and drops them (an enhancement, not a capability loss → no warning).
+ *  Index v2 finding #3: hand-authored <example> blocks exist in the CC plugin and NOWHERE
+ *  in .opencode/ — so seeding /content/ must MERGE, or this data is destroyed. */
+export const AgentExampleSchema = z.object({
+  context: z.string(),
+  user: z.string(),
+  assistant: z.string(),
+});
+```
+
+### 1.7 Category vs. profile (confirmed conflation)
+
+Confirmed by index v2: `types.ts` `AgentCategorySchema` (line 93) means *domain*
+(`core`/`development`) while `registry.json`'s `category` means *distribution tier*
+(`essential`/`standard`). Two axes sharing one name → split:
+
+```ts
+/** Domain grouping. NEUTRAL but low-stakes (agentCategories: claude ≈ none per index v2
+ *  finding #5 — survives only as plugin.json keywords). Open string, not enum: the
+ *  types.ts list is arbitrary and real data already exceeds it ("subagents/core",
+ *  "testing", "data"…). */
+export const CategorySchema = z.string().optional();
+
+/** Distribution profile — which install tier ships this. Owned by the registry entry (§7). */
+export const ProfileSchema = z.enum([
+  "essential", "standard", "extended", "specialized", "meta",
+]);
+```
+
+### 1.8 Full Agent IR
+
+```ts
+export const AgentSchema = z.object({
+  // ---- identity (folded-in metadata; NO sidecar) ----
+  id: IdSchema,
+  name: NameSchema,
+  description: DescriptionSchema,
+  role: RoleSchema,                          // was `mode`
+  category: CategorySchema,                  // domain; tier lives in registry.profiles
+  tags: TagsSchema,
+  version: VersionSchema,
+  author: z.string().default("oac"),
+  dependencies: z.array(DependencyInputSchema).default([]),
+
+  // ---- behavior ----
+  capabilities: CapabilitiesSchema,          // Option A; replaces tools + permission
+  inference: InferenceSchema,                // model = null default
+  context: z.array(ContextRefSchema).default([]),
+  examples: z.array(AgentExampleSchema).default([]),
+
+  // ---- lifecycle flags (HINTS) ----
+  disable: z.boolean().default(false),
+  hidden:  z.boolean().default(false),
+
+  // ---- authored prose ----
+  /** System prompt / instructions. NEUTRAL INVARIANT. From the Markdown body,
+   *  NOT frontmatter. */
+  systemPrompt: z.string().min(1),
+});
+```
+
+> **Too OpenCode-shaped — must change (Agent).**
+> - **Three overlapping identity carriers** (index v2, confirmed): `AgentFrontmatterSchema`
+>   (177–191) + `AgentMetadataSchema` (201–210) + `OpenAgentSchema.metadata` (225–234), plus a
+>   sidecar → collapse to one flat `AgentSchema`.
+> - `ToolAccessSchema` (11–21): booleans keyed by OpenCode tool names (`task`, `patch`,
+>   `question`) → replaced by the Option A capability set (`delegate`, `web`).
+> - `GranularPermissionSchema`/`PermissionRuleSchema` (33–49): the glob-record model is
+>   OpenCode's *serialization*, but it is **not** discarded — under Option A it is exactly the
+>   `MapSugar` input form (§1.2.6), making the OpenCode importer a lossless desugar. What must
+>   change is that the IR stores the **ordered list**, not the record, because record key order
+>   is a fragile carrier of semantic ordering.
+> - `AgentModeSchema` "all" (88) → drop. `AgentTypeSchema` (107) duplicates `role` → delete.
+> - `ModelIdentifierSchema` (116) no-op union, no null default → replaced by `InferenceSchema`.
+> - `sections` grab-bag (237–242) → superseded by typed `dependencies` + structured `examples`.
+
+---
+
+## 2. Skill
+
+Real source: `.opencode/skill/project-orchestration/SKILL.md` + `scripts/`/`workflows/` siblings;
+registry `skills` entries carry a `files: [...]` array. A Skill is a **directory** rooted at
+`SKILL.md` (lean YAML frontmatter: `name`, `description`, `version`, `type`, `category`, `tags`);
+body = instructions; bundled files travel with it.
+
+⚠️ Index v2 finding #3: OpenCode has **2** skill dirs, the CC plugin has **~11**, and the sets are
+**disjoint**. Seeding `/content/` from `.opencode/` alone destroys the CC set — a merge concern
+for Agent E, but it means `SkillSchema` must model both origins.
+
+```ts
+/** A file shipping alongside SKILL.md. NEUTRAL as a manifest entry; whether a target can
+ *  EXECUTE it is a HINT (skillsSystem: cursor = none, windsurf = partial). */
+export const SkillFileSchema = z.object({
+  /** Path relative to the skill directory root, e.g. "scripts/stage-cli.ts". */
+  path: z.string().min(1),
+  role: z.enum(["script", "workflow", "reference", "test", "asset", "entry"]).default("reference"),
+  description: z.string().optional(),
+});
+
+export const SkillSchema = z.object({
+  id: IdSchema,
+  name: NameSchema,
+  description: DescriptionSchema,
+  category: CategorySchema,
+  tags: TagsSchema,
+  version: VersionSchema,
+  author: z.string().default("oac"),
+  dependencies: z.array(DependencyInputSchema).default([]),
+
+  /** Directory basename rooting the skill (contains SKILL.md). NEUTRAL — every tool with
+   *  skills is directory-based (.claude/skills/<dir>/SKILL.md, .opencode/skill/<dir>/SKILL.md). */
+  directory: z.string().min(1),
+  /** The OTHER bundled files (SKILL.md implicit). Mirrors the registry `files` array. */
+  files: z.array(SkillFileSchema).default([]),
+
+  /** SKILL.md body. NEUTRAL INVARIANT. */
+  instructions: z.string().min(1),
+});
+```
+
+> **Too OpenCode-shaped — must change (Skill).** `types.ts` has no first-class Skill — only
+> `SkillReferenceSchema` (132–138), a *reference* permitting inline `config` that no target
+> honors (`ContextMapper.mapSkillsToClaudeFormat` warns and drops it). Add `SkillSchema`;
+> collapse `SkillReferenceSchema` into `DependencyRef{ kind:"skill" }`; drop inline config.
+
+---
+
+## 3. Command
+
+Real source: `.opencode/command/add-context.md`, `context.md`. Verified frontmatter across the
+**20** commands: `description` (18×), `tags` (3×), `dependencies` (3×), plus OpenCode extras
+(`tools`, `temperature`, `mode`, `permissions` — ≤2× each). Body = prompt/template.
+
+```ts
+export const CommandSchema = z.object({
+  id: IdSchema,
+  name: NameSchema.optional(),           // often absent; derive from id
+  description: DescriptionSchema,        // NEUTRAL INVARIANT — the only universal field
+  tags: TagsSchema,
+  dependencies: z.array(DependencyInputSchema).default([]),
+
+  /** OPTIONAL, HINT. OpenCode lets a slash-command pin an agent/inference/capabilities;
+   *  Claude/Cursor slash-commands are plain prompt templates. Reuses Agent primitives
+   *  (incl. Option A) rather than inventing parallel shapes. */
+  agent: IdSchema.optional(),
+  inference: InferenceSchema.partial().optional(),
+  capabilities: CapabilitiesInputSchema.optional(),
+
+  version: VersionSchema,
+
+  /** Prompt/template body (may contain $ARGUMENTS-style placeholders). NEUTRAL. */
+  body: z.string().min(1),
+});
+```
+
+Commands are the most portable type — neutral core is `{ id, description, tags, dependencies,
+body }`. `types.ts` has no Command schema; this is net-new from already-neutral primitives.
+
+---
+
+## 4. Context document — **MVI HTML-comment (locked decision #6)**
+
+Locked: context metadata **stays** the compact MVI HTML-comment line on disk. **No YAML
+migration.** It is deliberate token-efficiency — the model reads it on every context load — and
+multi-line YAML would cost tokens on all ~297 files. The IR normalizes it in memory; **on-disk
+format ≠ IR shape**.
+
+### 4.1 ⚠️ Verified format census — the highest-risk parser requirement
+
+I re-derived the breakdown by **marker line position**, which the "286/3/7" summary obscures:
+
+| Bucket | Count | Detail |
+|---|---|---|
+| Marker on **line 1** (happy path) | **287** | `<!-- Context: … -->` first line |
+| Marker present but **NOT line 1** | **7** | see below — two very different kinds |
+| **No marker at all** | **3** | `index.md`, `core/workflows/task-delegation.md`, `core/context-system/standards/typescript-coding.md` |
+| **Total** | **297** | |
+
+The 7 "marker not on line 1" files split into two kinds that must be handled **oppositely**:
+
+1. **Dual-format (3)** — YAML frontmatter on line 1 *and* an MVI marker at line 11:
+   `core/standards/csharp.md`, `core/standards/csharp-project-structure.md`,
+   `openagents-repo/quality/registry-dependencies.md`. These are the index's "3 YAML" files —
+   but note they carry **both**, so a YAML-only parser still loses nothing here while an
+   MVI-only parser would need to look past the YAML block.
+2. **Marker-as-prose (4)** — the marker appears *deep in the body as documentation*, not as
+   metadata: `openagents-repo/core-concepts/agents.md` (**line 232**),
+   `openagents-repo/core-concepts/categories.md` (**line 301**),
+   `core/context-system/standards/templates.md` (line 25, placeholder literals),
+   `core/context-system/standards/frontmatter.md` (line 43, the standard doc showing examples).
+
+> ### 🚨 Two parser traps, both verified
+>
+> **Trap 1 — `gray-matter` silently drops everything.** A generic frontmatter parser finds **no
+> frontmatter** in 287/297 files, yielding `{}` metadata with **no error**. Priority drives
+> context ordering, so this silently degrades every agent's context loading. This is the
+> single highest-risk parser requirement in the project.
+>
+> **Trap 2 — "grep the first marker anywhere" is also wrong.** It would assign
+> `standards/code | critical` to `openagents-repo/core-concepts/agents.md` from a marker at
+> **line 232** that is prose *about* the format. The parser MUST only honor a marker in a
+> **leading window** (line 1, or the first line after a closing YAML `---`), and treat every
+> later occurrence as body text. `templates.md` additionally contains **placeholder literals**
+> (`{category}/concepts`, `Priority: {critical|high|medium|low}`, `Updated: YYYY-MM-DD`) that
+> would fail validation — template/standard files must be excluded from metadata parsing.
+
+### 4.2 Parser specification
+
+```
+parseContext(file):
+  1. If line 1 starts with "---" → parse the YAML block; remember where it closes.
+     Set cursor = first non-blank line after the closing "---".  [3 files]
+     Else cursor = line 1.                                        [294 files]
+  2. If the line at `cursor` matches /^<!--\s*Context:\s*(.*?)\s*-->$/ →
+     parse it as MVI metadata (§4.3). Consume it; it is NOT body.  [287 + 3 files]
+     Else → NO metadata in the leading window.                     [7 files]
+  3. NEVER scan beyond the leading window for a marker. Later markers are body. [Trap 2]
+  4. Merge precedence when both YAML and MVI are present: MVI wins for the four MVI
+     fields (it is the maintained convention); YAML supplies any extra keys.
+  5. No metadata found → derive: id/name from path, priority = "medium" (schema default),
+     and record a `doctor` finding. NEVER silently succeed with {} metadata.  [Trap 1]
+  6. Exclude from metadata parsing (template/standard exemplars, by path allowlist):
+     core/context-system/standards/templates.md, .../frontmatter.md.
+```
+
+The MVI line grammar is pipe-delimited `Key: Value`, tolerant of missing trailing fields:
+
+```
+<!-- Context: {category}/{function} | Priority: {level} | Version: X.Y | Updated: YYYY-MM-DD -->
+```
+
+**Verified tolerance requirements** (real deviations, all 4 found):
+
+| File | Marker | Missing |
+|---|---|---|
+| `core/workflows/component-planning.md` | `Context: … \| Priority: high \| Version: 1.0` | `Updated` |
+| `openagents-repo/core-concepts/categories.md` | `Context: … \| Priority: high` | `Version`, `Updated` |
+| `openagents-repo/core-concepts/agents.md` | `Context: … \| Priority: critical` | `Version`, `Updated` |
+| `core/context-system/standards/templates.md` | placeholder literals | — (exclude) |
+
+So: **`Context` and `Priority` are required; `Version` and `Updated` are optional.** A grammar
+demanding all four fails on real content.
+
+### 4.3 ⚠️ Priority vocabulary — real data escapes the documented enum
+
+`.opencode/context/core/context-system/standards/frontmatter.md` documents
+`critical|high|medium|low`. Verified actual distribution across the parseable markers:
+
+| Value | Count |
+|---|---|
+| `high` | 113 |
+| `critical` | 111 |
+| `low` | 34 |
+| `medium` | 29 |
+| **`reference`** | **1** ← outside the enum |
+
+The outlier is `core/workflows/lightweight-context-handoff-example.md`
+(`Priority: reference`). A strict `z.enum` **rejects a real file**. Options: (a) coerce
+`reference` → `low` on parse with a `doctor` warning; (b) fix the one file; (c) widen the enum.
+Recommend (b) + keep the strict enum — one-file fix, keeps the vocabulary closed. Raised in Open
+Questions.
+
+`Version` on disk is **X.Y**, not semver (verified: `1.0` ×271, `2.0` ×10, `1.1` ×4, plus `3.1`,
+`2.1`, `1.3`) → normalize `X.Y` → `X.Y.0` for `VersionSchema`.
+
+### 4.4 The Context IR
+
+```ts
+export const ContextSchema = z.object({
+  id: IdSchema,
+  name: NameSchema,
+  description: DescriptionSchema,
+  /** "{category}/{function}" from the MVI marker, e.g. "core/standards". Open string. */
+  category: CategorySchema,
+  tags: TagsSchema,
+  /** From the MVI marker. NEUTRAL intent; HINT at every non-OAC target
+   *  (contextPriority = none for claude/cursor/windsurf). Drives context ORDERING —
+   *  losing it is the Trap 1 failure mode. */
+  priority: PrioritySchema.default("medium"),
+  /** X.Y on disk → normalized to semver in memory (§4.3). */
+  version: VersionSchema,
+  /** ISO date from "Updated:". Optional — real files omit it. HINT at tool layer. */
+  updated: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
+  dependencies: z.array(DependencyInputSchema).default([]),
+
+  /** Markdown body, marker consumed. NEUTRAL INVARIANT. */
+  body: z.string().min(1),
+});
+```
+
+**Serialization back to disk** is the inverse: the OpenCode/Claude adapters re-emit the MVI
+one-liner (not YAML), preserving the token-efficiency the format exists for.
+
+**MVI lint rules** (validated by `doctor`, not by Zod — Agent E's test spec): ≤200 lines, marker
+in the leading window, priority in-enum, a "📂 Codebase References" section, `updated` matching
+any in-body date stamp.
+
+> **Too OpenCode-shaped — must change (Context).** `types.ts` has only
+> `ContextReferenceSchema` (a pointer) — **no Context *document* schema**. And
+> `ContextMapper.ts` anchors `.opencode/context` as the privileged **source** base
+> (`CONTEXT_CONFIGS.oac`, 55–61); post-refactor the source is `/content/context` and OpenCode is
+> just a target, so the `oac` key must be renamed (`content`/`ir`) and re-rooted (index v2).
+
+---
+
+## 5. Tool
+
+Real source: `.opencode/tool/gemini/index.ts` + `README.md`; registry `tools` entries carry
+`tool:env`-style deps. A Tool is an **executable module**; the IR describes and locates it, it
+does not model the runtime.
+
+```ts
+export const ToolParamSchema = z.object({
+  name: z.string(),
+  type: z.enum(["string", "number", "boolean", "object", "array"]),
+  description: z.string().optional(),
+  required: z.boolean().default(false),
+});
+
+export const ToolSchema = z.object({
+  id: IdSchema,
+  name: NameSchema,
+  description: DescriptionSchema,        // NEUTRAL — the model needs to know what it does
+  tags: TagsSchema,
+  version: VersionSchema,
+  dependencies: z.array(DependencyInputSchema).default([]),  // e.g. tool:env
+
+  /** Entry module relative to the tool dir, e.g. "index.ts". NEUTRAL as a locator; whether a
+   *  target can LOAD a custom tool is a HINT (OpenCode plugins vs Claude MCP). */
+  entry: z.string().min(1),
+  runtime: z.enum(["node", "bun", "deno", "shell", "mcp"]).default("node"),
+
+  /** HINT — only targets with typed tool schemas use these. */
+  parameters: z.array(ToolParamSchema).default([]),
+
+  /** Env var NAMES only, never values. NEUTRAL doc + doctor check.
+   *  Build hygiene (index v2): a filesystem-globbing build must not sweep local .env files
+   *  into /content/. (There is NO committed-.env security issue — 0 tracked, gitignored.) */
+  env: z.array(z.string()).default([]),
+});
+```
+
+> **Too OpenCode-shaped — must change (Tool).** `types.ts` `ToolConfigSchema` (253–257) is not a
+> tool description — it is the adapter *output envelope*. Rename to `EmittedFileSchema` and move
+> to the adapter contract (Agent C). `ToolSchema` is net-new. Note the naming trap: capability
+> verbs (§1.2) are unrelated to first-class custom `Tool`s despite both being called "tools".
+
+---
+
+## 6. Hook
+
+No per-agent hooks are authored in the corpus today, but `types.ts` has `HookDefinitionSchema`
+(158–167) and `CapabilityMatrix` tracks `hooks` (full on oac/claude; none on cursor/windsurf).
+
+⚠️ Index v2 finding #3: **`session-start.sh`** (skill catalogue, first-run onboarding,
+context-discovery bootstrap, injection defense) has **no OpenCode equivalent** and lives in a
+directory slated for deletion. It is arguably the most important runtime feature and **must be
+preserved into `/content/`**. It is a `session-start` hook in this model — which makes the Hook
+schema load-bearing, not theoretical.
+
+```ts
+/** Neutral lifecycle events — named by the MOMENT, not a tool's callback id. The current
+ *  enum (types.ts:147) is Claude-shaped. Adapters map to tool names (Claude:
+ *  PreToolUse/PostToolUse/SessionStart; OpenCode plugin events). NEUTRAL vocabulary; the
+ *  whole feature is a HINT for cursor/windsurf (hooks = none → drop + blocking warning). */
+export const HookEventSchema = z.enum([
+  "before-tool",     // was PreToolUse
+  "after-tool",      // was PostToolUse
+  "permission-ask",  // was PermissionRequest
+  "session-start",   // was AgentStart  ← session-start.sh lands here
+  "session-end",     // was AgentEnd
+]);
+
+/** A shell action. NEUTRAL — a command string is universal. */
+export const HookActionSchema = z.object({
+  type: z.literal("command"),
+  command: z.string().min(1),
+});
+
+export const HookSchema = z.object({
+  event: HookEventSchema,
+  /** Optional filters (tool names/globs). HINT — matcher semantics vary; empty = always. */
+  matchers: z.array(z.string()).default([]),
+  actions: z.array(HookActionSchema).min(1),   // was `commands`
+});
+```
+
+> **Too OpenCode-shaped — must change (Hook).** Rename events off Claude's
+> `PreToolUse`/`AgentStart` vocabulary; rename `commands` → `actions` (it holds actions, and
+> "commands" collides with the Command content type). Otherwise structurally adoptable.
+
+---
+
+## 7. Registry entry
+
+Real source: `registry.json` v2.0.0 — `components: { agents, subagents, commands, tools, plugins,
+skills, contexts, config }`, each an array of `{ id, name, type, path, description, tags,
+dependencies, category, files? }`. This is the neutral catalog the CLI and build closure walk.
+
+Adds what the current registry lacks: **profiles** and **checksums** — the latter directly
+addresses index v2 finding #2 (dependency resolution is broken; `install.sh`'s resolver swallows
+`jq` errors via `|| echo ""`, so unknown refs yield **zero deps, no error**) and the
+bidirectional drift in finding #3.
+
+```ts
+export const ComponentTypeSchema = z.enum([
+  "agent", "subagent", "command", "skill", "context", "tool", "hook", "plugin", "config",
+]);
+
+export const RegistryEntrySchema = z.object({
+  id: IdSchema,
+  name: NameSchema,
+  type: ComponentTypeSchema,
+  /** Source path under /content/ (NOT .opencode/). NEUTRAL INVARIANT — catalog→source join. */
+  path: z.string().min(1),
+  description: DescriptionSchema,
+  tags: TagsSchema,
+  category: CategorySchema,                                  // domain
+  /** Compact "kind:id" refs. Drives the build closure. An unresolvable ref MUST be a hard
+   *  error — never `|| echo ""` (index v2 finding #2). */
+  dependencies: z.array(DependencyInputSchema).default([]),
+  files: z.array(z.string()).default([]),                    // multi-file components
+
+  // ---- new in the refactor ----
+  /** Install tiers including this component. Replaces the overloaded registry `category`
+   *  tier with an explicit, multi-valued set. */
+  profiles: z.array(ProfileSchema).default(["standard"]),
+  /** Content hash at publish time — drift/update detection. */
+  checksum: z.string().optional(),
+  version: VersionSchema,
+});
+
+export const RegistrySchema = z.object({
+  version: VersionSchema,
+  schema_version: VersionSchema,
+  repository: z.string().url().optional(),
+  targets: z.array(z.enum(["opencode", "claude", "cursor", "windsurf"])).default([
+    "opencode", "claude", "cursor", "windsurf",
+  ]),
+  components: z.record(z.string(), z.array(RegistryEntrySchema)),
+});
+```
+
+> **Too OpenCode-shaped — must change (Registry).**
+> - Every `path` starts with `.opencode/...` → rewrite to `/content/...`. `.opencode/` is a build
+>   target; it cannot also be the catalog's source of truth.
+> - `category` overloaded as *tier* (`essential|standard|…`) vs agent metadata's *domain*
+>   (`core|development|…`) → split into `category` + `profiles` (index v2, confirmed).
+> - Add `checksum` — without it `oac update` cannot detect the drift the index calls out as the
+>   core failure of the `cp`-based bridge.
+
+---
+
+## 8. Neutral-vs-hint summary (adapter contract)
+
+Every droppable field must be reported via `CapabilityMatrix` — never silently.
+
+| IR field | Status | Degrades on | Matrix feature |
+|---|---|---|---|
+| `role` | invariant | **claude = partial** ⚠️ (plugin layout has no per-agent mode; `primary` flattens → warn) | `agentModes` |
+| `capabilities.<verb>` allow/deny | invariant | — | `fileOperations`, `bashExecution`, `searchOperations` |
+| **rule `scope`** (any non-`*`) | **hint** | claude/cursor/windsurf — collapse to coarse grant, **MUST warn** | `granularPermissions`, `pathPatterns` |
+| **rule ordering** | **hint** | all but OpenCode (collapse discards order) | `granularPermissions` |
+| `decision: ask` | hint | claude/cursor/windsurf | `askPermissions` = none |
+| `capabilities.delegate` | invariant\* | cursor (none) | `taskDelegation` |
+| `inference.model` (null) | invariant | — (null ⇒ default) | `modelSelection` |
+| `inference.temperature` | hint | claude (none); cursor/windsurf (partial) | `temperatureControl` |
+| `inference.maxSteps` | hint | claude/cursor/windsurf | `maxSteps` |
+| `context[].path` | invariant | cursor (inline-only) | `externalContext` |
+| `context[].priority` | hint | claude/cursor/windsurf | `contextPriority` |
+| `category` | hint | **claude ≈ none** ⚠️ (plugin.json keywords only) | `agentCategories` |
+| `examples` | hint | OpenCode (no field; enhancement → no warning) | — |
+| `dependencies` | invariant | cursor (none), windsurf (partial) | `dependencies` |
+| `skill.files` executability | hint | cursor (none), windsurf (partial) | `skillsSystem` |
+| `hooks` | hint (**blocking**) | cursor/windsurf (none) | `hooks` |
+
+\* `delegate` is invariant wherever delegation exists; on Cursor it is a hard drop → **blocker**,
+not a soft warning.
+
+⚠️ Two rows marked above reflect index v2 finding #5: `CapabilityMatrix.ts` currently **lies** —
+`agentModes: claude = full` should be **partial**, and `agentCategories: claude` should likely be
+**none**. Both must be corrected before the matrix can be trusted as the degradation oracle.
+
+---
+
+## Open Questions
+
+1. **Precedence semantics — index prose vs. index example (§1.2.4). ⚠️ Needs ratification.**
+   The index says *"first-match-wins"*, but its own worked example and **all 34 real agents**
+   author broad-first/specific-after, under which naive first-match-wins denies coder-agent's
+   `router.sh` and downgrades openagent's `sudo *` deny to `ask`. **Recommend last-match-wins**
+   (makes the index example correct as written; maps 1:1 to OpenCode key order). Most-specific-wins
+   also works and agrees on 100% of current data. **The schema shape is identical under all three**
+   — only the resolver differs — so this can be ratified without blocking any workstream.
+
+2. **`Priority: reference` escapes the documented enum (§4.3).** One real file
+   (`core/workflows/lightweight-context-handoff-example.md`) uses a value the standard doesn't
+   define, so a strict `z.enum` rejects real content. Coerce→`low` with a warning, fix the one
+   file, or widen the enum? *Recommend fixing the file and keeping the enum closed.*
+
+3. **Should `model` be authorable in `/content/` at all?** (Carried from v1; still open per index
+   decision #2.) Since content must never hardcode a model, `inference.model` could be dropped
+   from the *authored* schema entirely and exist only as an always-null IR field plus a build-time
+   user/project override layer. Keeping it authorable invites the exact violation #2 forbids.
+
+4. **Context census delta: 296 (index) vs 297 (my count) (§0.1).** My reproducible count over
+   `.opencode/context/**/*.md` gives 297 (287 marker-on-line-1 / 7 marker-elsewhere / 3 no-marker).
+   Likely a scope difference in what was enumerated. No design impact — but the numbers should be
+   reconciled before they're quoted in shipping docs.
+
+5. **Template/standard exemplar exclusion (§4.2 step 6).** `templates.md` and `frontmatter.md`
+   contain placeholder/example markers that must not be parsed as metadata. Is a hardcoded path
+   allowlist acceptable, or should such files carry an explicit opt-out marker (e.g.
+   `<!-- oac:no-parse -->`)? A path allowlist is brittle as content grows.
+
+6. **`web` as a first-class capability?** No current agent uses a `web` permission; included
+   because Claude has WebFetch/WebSearch and it is a real intent. Confirm it belongs in the closed
+   capability set, or defer until a target needs it.
+
+7. **`id` vs `path` in `ContextRefSchema` and dependency joins.** Require registry `id`s (fully
+   neutral, but forces every one of ~297 contexts into the registry) or keep allowing relative
+   paths (flexible, weaker neutral anchor)? Affects how strict the build closure can be — and
+   interacts with index v2 finding #2 (unresolvable refs must hard-error).
+
+8. **Where does the emitted-file envelope live?** `types.ts` `ToolConfigSchema`/`ConversionResult`
+   (253–316) are adapter outputs, not IR. Confirm they move wholesale into Agent C's adapter
+   contract and out of `packages/core`.
+
+9. **Profiles source of truth.** Proposed on the registry entry (§7). Should agents/skills also
+   self-declare a profile in frontmatter (author-controlled but duplicated), or is the registry
+   sole owner (single source, but authors can't express intent locally)?
+
+10. **`primary|subagent` — are two roles enough?** Locking to two matches Claude + the worked
+    example. Flag before the enum freezes if any target needs a third (e.g. background/always-on).

+ 895 - 0
docs/architecture/canonical-refactor/03-adapter-specs.md

@@ -0,0 +1,895 @@
+# 03 — Adapter & Transform Specs (Agent C) — **v2**
+
+> **Status:** Specification only — no implementation code. **v2** (realigned to Option A
+> permissions + MVI context metadata, per `00-INDEX.md` v2).
+> **Scope:** For each of the 4 build targets (OpenCode, Claude Code, Cursor, Windsurf), the
+> complete transform from the neutral IR to the tool's on-disk format, plus a consolidated
+> capability matrix and the exact warning text emitted on any lossy transform.
+>
+> **Read first:** `00-INDEX.md` v2 (locked decisions). Where this doc conflicts with the
+> index, **the index wins** — except where this doc raises a *verified contradiction*, which
+> is escalated in §0.5 and Open Questions rather than silently resolved.
+
+**v2 changes:** Option A (ordered rule list) replaces the flat `capabilities` model in every
+mapping table · new §0.4 (capabilities model) and §0.5 (**ordering-direction contradiction —
+blocking**) · OpenCode serialization + ordering analysis (§1.3) · CC collapse rule, the
+`coder-agent` bash case, and a **verified negative** on the `settings.json` escape hatch
+(§2.6) · new matrix rows for scoped permissions, ask tri-state, rule ordering · census
+corrected (34 agents / 20 commands / 296 context) · context bundling corrected to MVI
+HTML-comment · worked example re-verified (§2.8).
+
+---
+
+## 0. Shared Conventions (apply to all adapters)
+
+### 0.1 Neutral IR fields (the input every adapter consumes)
+
+Per `00-INDEX.md` v2 worked example and `packages/compatibility-layer/src/types.ts`:
+
+| Neutral field | Type | Meaning |
+|---|---|---|
+| `id` | string (kebab) | Stable identity; adapters slugify per tool. |
+| `name` | string | Human display name. |
+| `role` | `primary` \| `subagent` | Operational mode. |
+| `category` | enum (`core`,`development`,…) | **Domain** only; distribution tier lives in registry `profiles`. |
+| `description` | string | One-line summary. |
+| `tags[]` | string[] | Free-form labels. |
+| `capabilities` | see §0.4 | Ordered rule lists + scalar sugar. **INTENT, not any tool's syntax.** |
+| `inference.temperature` | number \| absent | Sampling temperature. |
+| `inference.model` | string \| **null** | `null` ⇒ tool default. **Never hardcode** (locked #2). |
+| `inference.maxSteps` | number \| absent | Step cap. |
+| `context[]` | `{ path, priority?, description? }` | External context refs. See §0.6. |
+| `dependencies[]` | `subagent:` \| `context:` \| `skill:` \| `command:` \| `tool:` | Declared deps. |
+| `examples[]` | `{ context, user, assistant }` | Few-shot delegation examples. |
+| `hooks[]` | `{ event, command, … }` | Lifecycle hooks. |
+| body | markdown | System prompt authored once. |
+
+Verified census (index v2): **34** agents, **20** commands, **296** context `.md`
+(286 HTML-comment / 3 YAML / 7 neither), 2 OpenCode skills, ~11 CC plugin skills.
+
+### 0.2 Warning contract
+
+`BaseAdapter.ts:187-208` already defines both templates. **Reuse their exact output** — every
+lossy transform below cites the literal string:
+
+```
+unsupportedFeatureWarning(feature, value?):
+  ⚠️  Feature '<feature>'( (<value>))? is not supported by <displayName>
+
+degradedFeatureWarning(feature, from, to):
+  ⚠️  Feature '<feature>' will be degraded: <from> → <to>
+```
+
+`<displayName>` ∈ `OpenCode` / `Claude Code` / `Cursor IDE` / `Windsurf`.
+
+### 0.3 Warnings are never silent
+
+A transform that drops or degrades any field MUST emit a warning AND `analyzeCompatibility()`
+MUST predict it. Builds fail only on `blockers`; warnings are reported and counted.
+**Security-relevant losses (§2.3) are warnings today — see Open Questions #Q3 for whether they
+should block.**
+
+### 0.4 The capabilities model (Option A — locked decision #5)
+
+```
+capability: <scalar>                    # sugar → [{ scope: "*", decision: <scalar> }]
+capability: [ { scope, decision }, … ]  # full ordered form
+delegate:   { name: decision, … }       # map sugar → [{ scope: name, decision }, … ]
+```
+`decision ∈ allow | deny | ask`. Every adapter normalizes sugar → ordered list *first*, then
+transforms. Adapters never see the sugar forms.
+
+**Derived concepts every adapter needs:**
+
+- **Default-scope decision** — the decision of the rule whose scope is `*`, if present. This is
+  what coarse targets (CC/Cursor/Windsurf) collapse to.
+- **Exceptions** — every rule whose scope ≠ `*`. These are what coarse targets lose.
+- **Implicit default** — when *no* `*` rule exists. ⚠️ **The IR does not currently define this**,
+  and the two real agents disagree on what it should be: `coder-agent.edit` lists only *denies*
+  (`**/*.env*`, `**/*.key`, …) and clearly means "edit anything except these" ⇒ default
+  **allow**; `code-reviewer.delegate` lists only *allows* (`contextscout`) and clearly means
+  "only this one" ⇒ default **deny**. A workable inference rule is *"implicit default = the
+  opposite of the decisions present; mixed-without-`*` is an error"* — but this is a **schema
+  decision for `02`, not an adapter decision**. Escalated as Open Question #Q2. Every collapse
+  rule below depends on it.
+
+### 0.5 ⚠️ BLOCKING CONTRADICTION — first-match-wins vs last-match-wins
+
+**The locked shape says "first-match-wins". Every real agent is authored last-match-wins, and
+first-match-wins would break exactly the agents Option A was chosen to protect.**
+
+Verified evidence — `.opencode/agent/subagents/code/coder-agent.md` as authored on disk:
+
+```yaml
+permission:
+  bash:
+    "*": "deny"                                                        # ← broad rule FIRST
+    "bash .opencode/skills/task-management/router.sh complete*": "allow"
+    "bash .opencode/skills/task-management/router.sh status*": "allow"
+```
+
+and `.opencode/agent/core/openagent.md`:
+
+```yaml
+  bash:
+    "*": "ask"          # ← broad rule FIRST
+    "rm -rf /*": "deny"
+    "sudo *": "deny"
+```
+
+Under **first-match-wins**, `"*": deny` matches every command and the two `router.sh`
+exceptions **never fire** → `coder-agent` cannot run `router.sh` → it cannot complete or
+report task status. That is precisely the breakage the index cites as Option A's rationale
+(*"which would break its ability to run its own router.sh"*). Likewise `openagent`'s
+`sudo *: deny` would never fire — `sudo` would resolve to `ask`, a **security regression**.
+
+Under **last-match-wins**, both files behave exactly as authored and intended.
+
+The repo's own planning doc agrees — `docs/planning/12-MASTER-SYNTHESIS.md:442`:
+
+> "The `permission:` field uses **last-match-wins** evaluation (same as OpenCode's native
+> system) … Rules are evaluated in order; the LAST matching rule wins. This matches OpenCode's
+> permission semantics exactly, ensuring IDE-native compatibility."
+
+**The ordered-list decision is right; the stated match direction is wrong.** Two resolutions:
+
+| | Resolution | Consequence |
+|---|---|---|
+| **A (recommended)** | IR adopts **last-match-wins** | Matches OpenCode natively, matches all 34 authored agents **as written**, zero reordering transform, and the index's own `coder-agent` example becomes correct as printed. |
+| B | IR keeps **first-match-wins** | The index's `coder-agent` example must be **reordered specific-first** (it is currently broken as printed); the OpenCode adapter must **reverse rule order** on both serialize and parse; and all 34 agents must be order-reversed at migration. Equivalent expressive power, strictly more machinery and one more place to get it wrong. |
+
+**This doc specifies Resolution A (last-match-wins) throughout.** If the coordinator confirms
+B, §1.3 gains a reversal step and §0.4's "default-scope decision" becomes "the *last* `*` rule".
+Escalated as **Open Question #Q1 — blocking**; it changes generated output for every target.
+
+### 0.6 Context on disk (MVI HTML-comment — locked decision #6)
+
+Context `.md` files carry a single compact metadata line, **not** YAML frontmatter:
+
+```
+<!-- Context: standards/code | Priority: critical | Version: 2.0 | Updated: … -->
+```
+
+286 of 296 files use this. It is **deliberate** (token efficiency; the model reads it on every
+load) and **must not be migrated to YAML**. Consequences for adapters:
+
+- Adapters **copy context files byte-for-byte** — the MVI comment travels with the file and is
+  never rewritten into YAML on any target.
+- `context[].priority` in the IR is **parsed from this comment** in-memory by the core parser,
+  not from frontmatter. Adapters consume the normalized IR value and never re-parse.
+- ⚠️ A generic parser (`gray-matter`) finds **no frontmatter** here and silently yields
+  `priority: undefined` — which would degrade every priority-ordering claim in this doc to a
+  no-op. This is the core parser's contract (`02`), but adapters depending on `priority`
+  (OpenCode sidecar, Windsurf `contexts[].priority`) inherit the risk.
+
+---
+
+## 1. OpenCode Adapter
+
+`.opencode/` is build **output**, not source (locked #1). Real formats inspected:
+`.opencode/agent/**`, `.opencode/config/agent-metadata.json`, `.opencode/agent/**/0-category.json`,
+`.opencode/skill/<name>/SKILL.md`, `.opencode/command/*.md`, `.opencode/opencode.json`,
+`.opencode/config.json`.
+
+**Highest-fidelity target** — the only one with per-scope allow/deny/ask, temperature,
+delegation, external context refs, and skills.
+
+### 1.1 File / directory layout
+
+```
+.opencode/
+  agent/
+    <category>/<id>.md                    # role: primary      e.g. core/opencoder.md
+    subagents/<category>/<id>.md          # role: subagent     e.g. subagents/code/reviewer.md
+    <category>/0-category.json            # category descriptor
+  config/agent-metadata.json              # sidecar: metadata outside the OpenCode schema
+  skill/<skill-id>/SKILL.md
+  command/<command-id>.md
+  context/<category>/<...>.md             # copied verbatim (MVI comment intact) — referenced, not inlined
+  opencode.json                           # { "$schema": "https://opencode.ai/config.json" }
+  config.json                             # { "agent": "<default primary id>" }
+```
+
+File name = `id` (kebab); `name:` frontmatter = PascalCase. Verified against
+`agent/core/opencoder.md` (`name: OpenCoder`) and `agent/subagents/code/reviewer.md`
+(`name: CodeReviewer`).
+
+### 1.2 Field mapping table
+
+| Neutral field | OpenCode target | Transform rule |
+|---|---|---|
+| `id` | file path stem + `agent-metadata.json` key | kebab, unchanged; drives location |
+| `name` | `name:` | **id → PascalCase** (`code-reviewer` → `CodeReviewer`) |
+| `role` | `mode:` | 1:1 (`primary`/`subagent`) |
+| `category` | dir segment + sidecar + `0-category.json` | 1:1 (domain only) |
+| `description` | `description:` | verbatim |
+| `tags[]` | sidecar `tags` | not in OpenCode agent schema |
+| `capabilities` | `permission:` map | **ordered list → map, order preserved** (§1.3) |
+| `capabilities.delegate` | `permission.task` | `[{scope:X,decision:D}] → task: { X: "D" }` |
+| `inference.temperature` | `temperature:` | verbatim |
+| `inference.model` = null | *omit `model:`* | null ⇒ OpenCode default |
+| `inference.model` set | `model:` | verbatim |
+| `inference.maxSteps` | sidecar (advisory) | no native field |
+| `context[]` | `@`-ref / `paths.json` + sidecar `context:<id>` dep | referenced, not inlined |
+| `dependencies[]` | sidecar `dependencies[]` | verbatim |
+| `examples[]` | body prose / sidecar | no native `<example>` convention |
+| `hooks[]` | `.opencode/plugin/` registration | full support |
+| body | markdown | verbatim |
+
+### 1.3 Capabilities → `permission:` — exact serialization & ordering
+
+**Answer to the coordinator's question: OpenCode round-trips Option A exactly *in expressive
+power*, but its map form preserves ordering only *by convention, not by specification*.**
+
+Serialization is a direct 1:1 emission — for each capability, emit each rule in IR list order
+as a `"<scope>": "<decision>"` entry:
+
+```
+IR                                                   →  OpenCode
+bash: [ {scope:"*",decision:deny},                      permission:
+        {scope:"…/router.sh complete*",decision:allow},   bash:
+        {scope:"…/router.sh status*",decision:allow} ]       "*": "deny"
+                                                             "bash …/router.sh complete*": "allow"
+                                                             "bash …/router.sh status*": "allow"
+```
+
+Capability→key map: `bash→bash`, `edit→edit`, `write→write`, `delegate→task`. `read`/`grep`/
+`glob` are granted by default in OpenCode; emit a key only when a rule is not a plain
+`*: allow`. Scalar sugar round-trips as a single `"*"` entry. `ask` is preserved natively —
+**OpenCode is the only target that can express it.**
+
+**Ordering preservation — three caveats, in decreasing severity:**
+
+1. **YAML mappings are unordered *by spec*.** The YAML spec defines a mapping as an unordered
+   set of key/value pairs. Emitting in list order preserves order *textually*, and the common
+   parsers (`js-yaml`, `yaml`) preserve document order into JS object insertion order — so it
+   works in practice, and OpenCode's own files rely on it (`"*": deny` first). But
+   **order-as-semantics in a map is an implicit contract with OpenCode's parser**, not a
+   guarantee. If OpenCode ever normalizes, sorts, or round-trips permission keys through an
+   unordered dict, every deny-all-then-allowlist agent silently inverts. This is a real
+   fragility in the *target format*, inherited by us — worth stating plainly rather than
+   assuming.
+2. **Integer-like keys silently jump position.** Per ECMAScript, integer-index-like string
+   keys (`"2"`, `"8080"`) are ordered numerically *ahead of* all other string keys regardless
+   of insertion order. Real scopes are globs (`*`, `**/*.env*`, `sudo *`) and are never
+   integer-like, so this is currently theoretical — but it is a silent, order-inverting failure
+   if a scope like `"8080"` ever appears. **Mitigation:** the IR should reject integer-like
+   scopes at parse time (cheap validation).
+3. **Duplicate scopes collapse.** The IR list can hold the same scope twice with different
+   decisions; a map cannot (the later key silently overwrites). **Mitigation:** enforce
+   scope-uniqueness-per-capability in the IR schema. Duplicate scopes are meaningless under
+   either match direction anyway.
+
+**Conclusion:** with those two cheap IR validations (no integer-like scopes; unique scopes per
+capability), OpenCode **round-trips Option A exactly and deterministically**. Without them,
+ordering is preserved in practice but is not deterministic-by-spec. Both validations belong in
+`02` — Open Question #Q2.
+
+### 1.4 Content-type mapping
+
+**Agents.** Frontmatter per the index worked example (`name`/`description`/`mode`/`temperature`/
+`permission`). Because Option A carries scoping in content, **no adapter-injected safety
+preset is needed** (locked #5: "safety globs live naturally in content"). This removes the v1
+"default safety pattern set" proposal — v1's Open Question on that is **closed**.
+
+**Skills.** → `.opencode/skill/<id>/SKILL.md`, frontmatter `name/description/version/author/
+type: skill/category/tags` (verified: `skill/project-orchestration/SKILL.md`).
+
+**Commands.** → `.opencode/command/<id>.md`, frontmatter `description:` only (verified across
+the 20 real command files).
+
+**Context.** Referenced, not copied-and-rewritten: files land at `.opencode/context/<path>`
+**byte-for-byte (MVI comment intact — §0.6)**; loading is via `@`-import + `paths.json`
+(verified in `opencoder.md`: *"paths.json is loaded via @ reference in frontmatter"*);
+`context:<id>` recorded in sidecar `dependencies`.
+
+**Hooks.** → `.opencode/plugin/` registration. Full support.
+
+### 1.5 Manifest generation
+
+- `.opencode/opencode.json` — static `{ "$schema": "https://opencode.ai/config.json" }`.
+- `.opencode/config.json` — `{ "agent": "<default primary id>" }` (observed: `eval-runner`).
+- `.opencode/config/agent-metadata.json` — sidecar: `id/name/category/type/version/author/
+  tags/dependencies[]` + `maxSteps` + `context[].priority` + folded `examples`.
+- `.opencode/agent/<category>/0-category.json` — `name/description/icon/agents{}` with
+  `commonSubagents/commonTools/commonContext`.
+
+### 1.6 Lossy transforms (OpenCode)
+
+| Field | Disposition | Warning |
+|---|---|---|
+| `inference.maxSteps` | sidecar (advisory; unenforced) | `⚠️  Feature 'maxSteps' (<n>) is not supported by OpenCode` |
+| `context[].priority` | sidecar; not enforced at load | `⚠️  Feature 'context priority' will be degraded: ordered load → sidecar metadata only` |
+| `examples[]` | folded into body/sidecar | *(none — content preserved)* |
+
+**Capabilities: zero loss** — scopes, ordering, and `ask` all survive (subject to §1.3).
+Temperature, delegation, skills, hooks, external context: **full fidelity**.
+
+---
+
+## 2. Claude Code Adapter
+
+**Authoritative target = the plugin layout**, verified at
+`~/.claude/plugins/cache/oac-marketplace/oac/1.0.2/`.
+
+> ### ⚠️ Locked finding (index v2 #4): `ClaudeAdapter.ts` targets the wrong layout
+> It writes the **project** layout (`.claude/agents/*.md`, `.claude/config.json`,
+> `.claude/skills/<name>/SKILL.md` — see `ClaudeAdapter.ts:95-124`). The authoritative format
+> is the **plugin** layout (`.claude-plugin/plugin.json`, flat `agents/`, `hooks/hooks.json`,
+> bundled `context/`). **As written it produces something CC cannot load as a plugin.** The
+> adapter must be retargeted to §2.1 before any CC output is trustworthy.
+
+### 2.1 File / directory layout
+
+```
+<plugin-root>/                       # e.g. dist/claude/ → plugins/cache/oac/<ver>/
+  .claude-plugin/plugin.json         # plugin manifest
+  agents/<id>.md                     # FLAT dir, kebab id — no category nesting
+  skills/<skill-id>/SKILL.md         # directory-per-skill
+  skills/<skill-id>/<supporting>.md  # e.g. context-discovery-protocol.md
+  commands/<command-id>.md
+  hooks/hooks.json                   # event → command wiring
+  hooks/session-start.sh             # ⚠️ preserve — no OpenCode equivalent (index v2 #3)
+  context/<category>/<...>.md        # BUNDLED, copied byte-for-byte (MVI comment intact)
+  .context-manifest.json
+  settings.json                      # ⚠️ see §2.5 — `model` key may be a no-op
+  README.md
+```
+
+### 2.2 Field mapping table
+
+| Neutral field | CC target | Transform rule |
+|---|---|---|
+| `id` | `name:` + `agents/<id>.md` | **id → kebab-case** |
+| `name` | H1 in body | PascalCase, prose only |
+| `role` | *(none)* | every `agents/` file is a subagent; `primary` flattens → warn |
+| `category` | `plugin.json` keywords | no per-agent field → warn |
+| `description` | `description:` `\|` block | folded with `examples[]` as `<example>` blocks |
+| `tags[]` | `plugin.json` keywords | merged at manifest level |
+| `capabilities` | `tools:` / `disallowedTools:` | **ordered list → coarse allowlist** (§2.3) — lossy |
+| `capabilities.delegate` | `Task` in `tools:` | collapse per §2.3 |
+| `inference.temperature` | **dropped** | warn |
+| `inference.model` = null | omit | harness default |
+| `inference.model` set | `model:` | CC alias (`sonnet`/`opus`/`haiku`) |
+| `inference.maxSteps` | dropped | warn |
+| `context[]` | copied → `context/` + SessionStart hook | **bundled**, not referenced (§2.4) |
+| `dependencies[]` | resolved & bundled | `subagent:` deps force-emit that agent file |
+| `examples[]` | `<example>` blocks in `description` | verified format (§2.3) |
+| `hooks[]` (agent-level) | **dropped** | ⚠️ plugin subagents ignore `hooks` frontmatter (§2.5) |
+| `hooks[]` (plugin-level) | `hooks/hooks.json` | full support |
+| body | markdown | verbatim |
+
+Capability→tool name (`ToolMapper.ts` `claude.fromOAC`, PascalCased): `read→Read, write→Write,
+edit→Edit, bash→Bash, glob→Glob, grep→Grep, delegate→Task`.
+
+### 2.3 The collapse rule — ordered rule list → coarse allowlist
+
+**Verified constraint (CC docs, `sub-agents.md`):** `tools:` and `disallowedTools:` are
+allowlists/denylists of **tool NAMES only**. A pattern like `Bash(foo:*)` is **not valid** in
+agent frontmatter — argument scoping exists *only* in `settings.json`. So **every scope ≠ `*`
+is unrepresentable per-agent in CC.** The only question is which way each capability rounds.
+
+**Rule — round to the default-scope decision (fail-closed on ambiguity):**
+
+1. Normalize sugar → ordered list. Determine the **default-scope decision** (the `*` rule; if
+   absent, the implicit default per §0.4 — Open Question #Q2).
+2. `default = allow` → tool goes in `tools:`. Any `deny` exceptions are **dropped** → warn.
+3. `default = deny` → tool goes in `disallowedTools:`, **omitted** from `tools:`. Any `allow`
+   exceptions are **dropped** → warn.
+4. `default = ask` → **round to deny** (CC has no per-agent interactive gate) → warn.
+5. Exceptions are *never* used to flip the default.
+
+**The `coder-agent` bash case** — `"*": deny` + two `router.sh` allows. The coordinator is
+right that neither answer is correct; here is the choice and the justification:
+
+> **Omit `Bash` (fail-closed).** Including `Bash` would grant **unrestricted shell** to an
+> agent whose authored intent is "no shell except two exact `router.sh` invocations" — a
+> silent privilege **escalation** of the widest possible blast radius, produced by a build
+> step the user never inspects. Omitting `Bash` is a **functional regression** (`coder-agent`
+> cannot update task status) that is **loud, visible at first use, and recoverable**.
+> Asymmetry decides it: a wrong `deny` yields a broken agent; a wrong `allow` yields an agent
+> that can `rm -rf` in a repo whose owner believed it was sandboxed. Escalation-by-default is
+> never an acceptable build artifact. This also matches what the real plugin already ships —
+> `agents/coder-agent.md` has `tools: Read, Write, Edit, Glob, Grep` (no `Bash`, no `Task`).
+
+**Warnings** (both fire for this case):
+
+```
+⚠️  Feature 'scoped bash permissions' will be degraded: 1 default rule + 2 scoped exceptions → coarse tool allowlist
+⚠️  Feature 'scoped bash allow-exceptions' (2 rules) is not supported by Claude Code
+```
+
+The second warning MUST enumerate the dropped scopes verbatim (`bash …/router.sh complete*`,
+`bash …/router.sh status*`) so the loss is auditable, and point at the manual remedy in §2.6.
+
+**The `edit` case is the more dangerous one and is easy to miss.** `coder-agent.edit` lists
+*only* denies (`**/*.env*`, `**/*.key`, `**/*.secret`, `node_modules/**`, `.git/**`) with no
+`*` rule ⇒ implicit default **allow** ⇒ `Edit` lands in `tools:` and **all five security globs
+are dropped**. The CC agent can then edit `.env` and `.key` files that the authored agent was
+explicitly forbidden from touching. This is a **silent security downgrade** produced by a
+faithful application of the rule, so it must warn loudest:
+
+```
+⚠️  Feature 'scoped edit permissions' will be degraded: 5 deny-globs → unrestricted Edit
+⚠️  Feature 'edit deny-globs' (**/*.env*, **/*.key, **/*.secret, node_modules/**, .git/**) is not supported by Claude Code
+```
+
+See Open Question #Q3: security-glob loss is arguably a **blocker**, not a warning.
+
+**Agents.** Verified frontmatter (`agents/code-reviewer.md`):
+
+```yaml
+---
+name: code-reviewer
+description: |
+  Review code for security vulnerabilities, correctness, and quality. Use after implementation is complete and before committing.
+  Examples:
+  <example>
+  Context: coder-agent has finished implementing a new auth service.
+  user: "The auth service is done, can you check it?"
+  assistant: "I'll run the code-review skill to have code-reviewer validate it before we commit."
+  <commentary>Implementation is complete — code-reviewer validates before commit.</commentary>
+  </example>
+tools: Read, Glob, Grep
+disallowedTools: Write, Edit, Bash, Task
+model: sonnet
+---
+```
+
+### 2.4 Content-type mapping
+
+**Skills.** → `skills/<id>/SKILL.md`. Verified frontmatter (`skills/code-review/SKILL.md`):
+`name / description / context: fork / agent: code-reviewer`. The `context: fork` + `agent:`
+keys (isolated forked execution) are CC-specific; the neutral skill IR needs optional
+`execution: { isolate?, agent? }` to round-trip them (Open Question #Q5). Supporting `.md`
+files copied alongside.
+
+**Commands.** → `commands/<id>.md`, frontmatter `name / description`, body verbatim
+(`commands/oac-status.md`). `${CLAUDE_PLUGIN_ROOT}` bash blocks preserved verbatim.
+
+**Context — bundled, not referenced.** Unlike OpenCode:
+1. every `context[]` entry and every transitive `context:` dependency is **copied byte-for-byte**
+   into `context/<category>/…` — **the MVI HTML-comment line travels with the file and is never
+   rewritten to YAML** (§0.6);
+2. `.context-manifest.json` is generated: `version / profile / source{repository,branch,commit,
+   downloaded_at} / categories[] / files{<cat>: <count>}` (verified against the shipped manifest);
+3. at runtime `hooks/session-start.sh` (SessionStart) injects the skill catalogue + `using-oac`
+   via dual-format JSON — `hookSpecificOutput.additionalContext` for CC, `additional_context`
+   for other tools. `context[].priority` is **not** representable in that payload → dropped.
+
+⚠️ `session-start.sh` has **no OpenCode equivalent** and lives in a directory slated for
+deletion (index v2 #3) — it must be preserved into `/content/` first.
+
+**Hooks.** Plugin-level `hooks/hooks.json` → full support; baseline always emits
+`SessionStart → bash "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"` (timeout 30).
+**Agent-level `hooks[]` are dropped** — see §2.5.
+
+### 2.5 Lossy transforms (Claude Code)
+
+| Field / feature | Disposition | Exact warning |
+|---|---|---|
+| `inference.temperature` | **dropped** (no per-agent temperature) | `⚠️  Feature 'temperature' (<value>) is not supported by Claude Code` |
+| scoped rules (scope ≠ `*`) | dropped; default-scope decision wins | `⚠️  Feature 'scoped <cap> permissions' will be degraded: <n> rules → coarse tool allowlist` + an enumerating `is not supported` line |
+| `decision: ask` | rounded to deny | `⚠️  Feature 'ask permission' for '<cap>' will be degraded: ask → deny (Claude Code has no per-agent interactive ask)` |
+| rule **ordering** | meaningless after collapse | `⚠️  Feature 'permission rule ordering' is not supported by Claude Code` (emit once per agent with any multi-rule capability) |
+| `context[].priority` | dropped (hook payload unordered) | `⚠️  Feature 'context priority' (<level>) is not supported by Claude Code` |
+| `inference.maxSteps` | dropped | `⚠️  Feature 'maxSteps' (<n>) is not supported by Claude Code` |
+| `category` | → keywords/prose | `⚠️  Feature 'agent category' will be degraded: typed category → plugin keywords` |
+| `role: primary` | flattened | `⚠️  Feature 'agent mode' will be degraded: primary/subagent → subagent (Claude Code agents/ are all subagents)` |
+| **agent-level `hooks[]`** | **dropped — verified** | `⚠️  Feature 'agent hooks' (<n> hooks) is not supported by Claude Code` |
+
+**Verified (CC docs, `sub-agents.md`):** *"For security reasons, plugin subagents don't support
+the `hooks`, `mcpServers`, or `permissionMode` frontmatter fields. These fields are ignored when
+loading agents from a plugin."* So agent-level hooks are **silently ignored by CC** — the
+adapter must warn rather than emit them. `permissionMode` being unavailable also closes off a
+possible per-agent scoping route.
+
+**⚠️ Related finding — `settings.json: {"model": "opusplan"}` is probably a no-op.** CC docs
+(`plugins.md`, "Ship default settings with your plugin") state that in a plugin-shipped
+`settings.json` *"only the `agent` and `subagentStatusLine` keys are supported."* `model` is
+not in that list, so the value shipped by the plugin (added in **#264**, commit `40dd267`) is
+likely **ignored by CC**. Confidence: **high** on the doc statement, **medium** on it applying
+to this exact file (not runtime-verified). If true this is both (a) a live bug worth an issue,
+and (b) the answer to v1's Q4 — the question is moot because the key has no effect. Flagged as
+Open Question #Q6.
+
+### 2.6 `settings.json` scoped permissions — investigated; **recommend AGAINST**
+
+The coordinator asked whether the adapter can preserve scoping via CC `settings.json`. I
+investigated, and the answer is a **verified negative** — it is not available to us at all.
+
+**What's real and confirmed.** `settings.json` genuinely supports scoped rules with
+`allow`/`deny`/`ask` arrays. Real examples from this very repo (`.claude/settings.local.json`)
+include `Bash(git log:*)`, `Bash(npm test:*)`, and — strikingly — `Bash(bash
+.opencode/skills/worktree/router.sh create fix/…)`, i.e. **exactly the `router.sh` scoping
+shape `coder-agent` needs**. Syntax: `Tool(pattern)`, gitignore-style globs for path tools,
+`:*` a trailing-wildcard form equivalent to ` *`. Precedence is **deny → ask → allow,
+first match in that order; specificity does not reorder** — so a broad deny beats a narrow
+allow. (Source: `code.claude.com/docs/en/permissions.md`.)
+
+So the shape fits. **Three independent blockers kill it anyway:**
+
+1. **Plugins cannot ship permissions — decisive.** Plugin-shipped `settings.json` supports
+   *"only the `agent` and `subagentStatusLine` keys"* (`plugins.md`). OAC's CC target **is a
+   plugin**. The adapter therefore **cannot emit permission rules at all** — anything it wrote
+   would be ignored. This alone ends the option.
+2. **Project scope ≠ agent scope.** Even via the user's own settings, rules are
+   managed/local/project/user-scoped — never per-subagent. Restoring `coder-agent`'s intent
+   ("deny all bash except two `router.sh` commands") would require `deny: ["Bash(*)"]`
+   **project-wide**, breaking every other agent and the user's own shell. The one construct
+   that would make it safe — per-agent scoping — is exactly what CC lacks.
+3. **The agent allowlist wins anyway.** If `Bash` is omitted from an agent's `tools:`, a
+   `settings.json` `Bash(...)` allow **cannot re-enable it** — the frontmatter allowlist is
+   what gates the tool. So the escape hatch cannot rescue a fail-closed agent without *also*
+   hand-editing its `tools:`. (Confidence: **medium** — CC docs describe subagents as having
+   "independent permissions" but do not spell out the interaction; flagged in #Q4.)
+
+**Recommendation — emit documentation, not configuration.** The adapter should generate a
+**`RECOMMENDED-PERMISSIONS.md`** (or a README section) containing a copy-pasteable
+`settings.json` snippet for the rules that were dropped, and reference it from the warnings.
+Constrain it to the **safe direction only**:
+
+- **Hoist `deny` rules that are universal across all agents.** The security globs are already
+  near-universal in content (`**/*.env*`, `**/*.key`, `**/*.secret`, `node_modules/**`,
+  `.git/**` appear on `coder-agent` *and* `openagent`; `sudo *`, `rm -rf /*` recur too). As
+  project-level denies — `Edit(**/*.env*)`, `Bash(sudo *)` — they over-apply in the **safe**
+  direction, and CC's deny-first precedence makes them robust. This genuinely recovers the
+  §2.3 `edit` security downgrade, *if the user opts in*.
+- **Never suggest hoisting `allow` rules.** A project-level `Bash(bash …/router.sh complete:*)`
+  would grant that command to **every** agent, including ones authored with no shell at all —
+  re-introducing the escalation §2.3 exists to prevent, just one layer up.
+
+Net: **scoping cannot be preserved mechanically for CC.** Fail-closed per-agent (§2.3) plus an
+opt-in, deny-only documented snippet is the honest ceiling. Recording this as a closed question
+rather than an open one is itself a deliverable — the index currently lists it as open.
+
+### 2.7 Manifest generation
+
+- `.claude-plugin/plugin.json` — `name / description / version / author{name,url} / license /
+  repository / homepage / keywords[]` (keywords aggregated from agent `tags` + `category`).
+  Verified against the shipped 1.0.2 manifest. ⚠️ Version must be reconciled first — the cache
+  is at **1.0.2**, *ahead* of the marketplace entry it builds from (index v2 #6), which would
+  suppress the `/plugin update` delivering any fix.
+- `hooks/hooks.json` — event→command map; always includes the SessionStart baseline.
+- `.context-manifest.json` — see §2.4.
+- `settings.json` — see the §2.5 no-op finding.
+
+### 2.8 Worked-example re-verification under Option A
+
+**`code-reviewer` → the index predicts exactly 1 warning (temperature). Under Option A as
+specified here it produces 2, and the extra one is load-bearing.**
+
+Walking it: `read/grep/glob: allow` and `edit/write/bash: deny` are all **scalar sugar** ⇒ each
+is a single `*` rule ⇒ **no scoping, no ordering, no `ask`** ⇒ they collapse cleanly to
+`tools: Read, Glob, Grep` + `disallowedTools: Write, Edit, Bash` with **zero warnings**. That
+part confirms the index exactly, and confirms Option A costs nothing for simple agents —
+sugar-only agents are warning-free apart from unsupported scalars. `temperature: 0.1` →
+**warning 1**.
+
+But `delegate: { contextscout: allow }` is **map sugar for a scoped rule** —
+`[{scope: "contextscout", decision: allow}]`, scope ≠ `*`. Its intent is "may delegate to
+contextscout **and nothing else**". CC's `Task` is coarse: granting it permits delegation to
+**any** agent. So per §2.3 (implicit default = deny, since only allows are listed) `Task` is
+**omitted** — which is exactly what the index's `tools: Read, Glob, Grep` shows, and exactly
+what the shipped `agents/code-reviewer.md` does (`disallowedTools: … Task`). The output is
+right; but dropping a delegation the agent was granted **is** a scoped-rule degradation, and
+§0.3 forbids silent loss → **warning 2**:
+
+```
+⚠️  Feature 'scoped delegate permissions' (contextscout) is not supported by Claude Code
+```
+
+Reconciliation options: **(a) accept 2 warnings** and correct the index — recommended, because
+the alternative is either a silent capability drop or (worse) granting unrestricted `Task`; or
+**(b)** define delegate→Task collapse as an expected non-warning narrowing — which contradicts
+"warnings are never silent" and hides a real loss. **The output artifact is identical either
+way; only the count changes.** Open Question #Q7. Note the count is 1 *only* under the v1 flat
+model, where `delegate` carried no scope — so this is a genuine, expected consequence of
+Option A, not a regression.
+
+**`coder-agent` → 4 warnings**, all from §2.3/§2.5:
+
+| # | Cause | Warning |
+|---|---|---|
+| 1 | `temperature: 0` | `⚠️  Feature 'temperature' (0) is not supported by Claude Code` |
+| 2 | `bash` `*: deny` + 2 `router.sh` allows → `Bash` omitted (fail-closed) | `⚠️  Feature 'scoped bash permissions' will be degraded: 1 default rule + 2 scoped exceptions → coarse tool allowlist` |
+| 3 | `edit` 5 security deny-globs → unrestricted `Edit` | `⚠️  Feature 'scoped edit permissions' will be degraded: 5 deny-globs → unrestricted Edit` |
+| 4 | `delegate` allowlist (contextscout, externalscout, TestEngineer) → `Task` omitted | `⚠️  Feature 'scoped delegate permissions' (3 rules) is not supported by Claude Code` |
+
+Resulting file matches the shipped `agents/coder-agent.md` (`tools: Read, Write, Edit, Glob,
+Grep`) — i.e. **the hand-authored CC plugin already made every one of these choices**, which is
+independent corroboration that fail-closed is the right rule. **Warning 3 is the one to act on:**
+`coder-agent` on CC is strictly less safe than on OpenCode, and no adapter-side fix exists
+(§2.6).
+
+---
+
+## 3. Cursor Adapter
+
+Described from `adapters/CursorAdapter.ts`, `fixtures/sample-cursorrules`, and `ToolMapper.ts`
+`cursor`. **Format carries real uncertainty — see Open Questions.**
+
+Lowest-fidelity target: effectively single-agent, no skills, no hooks, no delegation, no
+external context refs, no permission model to speak of.
+
+### 3.1 Layout
+
+- **Legacy (stub's output):** single `.cursorrules` at project root.
+- **Modern (likely correct):** `.cursor/rules/*.mdc` — MDC files with `description` / `globs` /
+  `alwaysApply` frontmatter.
+
+Multiple neutral agents are **merged into one** (`CursorAdapter.mergeAgents()`): prompts joined
+under `# Agent N: <name>`, union of tools, **max** temperature, first non-null model.
+
+### 3.2 Field mapping table
+
+| Neutral field | Cursor target | Transform rule |
+|---|---|---|
+| `id` / `name` | `name:` | verbatim (merged agent) |
+| `role` | *(none)* | dropped |
+| `category` | *(none)* | dropped |
+| `description` | `description:` | verbatim |
+| `capabilities` | prose "# Tool Access" list | **default-scope decision only**; all scoping dropped (§3.3) |
+| `capabilities.delegate` | dropped | `task` unsupported (`ToolMapper` marks it so) |
+| `inference.temperature` | `temperature:` | verbatim; limited range |
+| `inference.model` = null | omit | Cursor default |
+| `inference.model` set | `model:` | `mapOACModelToCursor` (falls back to `gpt-4`) |
+| `context[]` | **inlined** into rules body | copied as prose; MVI comment travels inline verbatim |
+| `dependencies[]` | dropped | no dependency system |
+| `examples[]` | prose | no native support |
+| `hooks[]` | dropped | no hooks |
+| body | rules body | verbatim |
+
+Tool names (`ToolMapper.ts` `cursor.fromOAC`): `bash→terminal, read→file_read, write→file_write,
+edit→file_edit, glob→file_search, grep→content_search, task→(unsupported)`.
+
+### 3.3 Capabilities under Option A
+
+Cursor has **no enforced permission model** in `.cursorrules` — tools are listed as *prose*, so
+even the default-scope decision is advisory. Collapse rule: same as CC §2.3 (fail-closed;
+default-scope decision wins; `ask` → deny), then render the resulting allow-set as a prose
+list. Every scope ≠ `*` is dropped, and ordering is meaningless. **Cursor is the weakest target
+for security intent** — `coder-agent`'s deny-globs become, at best, a suggestion in a rules
+file. Open Question #Q8 asks whether `.mdc` `globs:` can carry any of this (it scopes *rule
+activation by file*, which is not the same as *permission*, but is the nearest construct).
+
+### 3.4 Content types
+
+- **Agents:** merged; frontmatter `name / description / model? / temperature?` (per fixture).
+- **Skills:** none → inlined (warn).
+- **Commands:** no native system (Open Question #Q8).
+- **Context:** **inlined** under `# Context Files` with `## <path>`, optional `*description*`,
+  `**Priority**`, and a "load this file" note. Priority is written as text, never enforced.
+- **Hooks / dependencies:** dropped.
+
+### 3.5 Manifest
+
+**None.** `getConfigPath()` → `.cursorrules` (legacy) or `.cursor/`. The rules file *is* the
+configuration.
+
+### 3.6 Lossy transforms (Cursor)
+
+| Field / feature | Disposition | Exact warning |
+|---|---|---|
+| multiple agents | merged | `⚠️  Cursor IDE does not distinguish between primary and subagent modes` (per subagent) |
+| scoped rules | dropped | `⚠️  Feature 'scoped <cap> permissions' (<n> rules) is not supported by Cursor IDE` |
+| `decision: ask` | → deny | `⚠️  Feature 'ask permission' for '<cap>' will be degraded: ask → deny (Cursor IDE has no interactive ask)` |
+| rule ordering | meaningless | `⚠️  Feature 'permission rule ordering' is not supported by Cursor IDE` |
+| permissions generally | advisory prose only | `⚠️  Feature 'permissions' will be degraded: enforced allow/deny/ask → advisory prose in rules file` |
+| `skills[]` | inlined | `⚠️  Feature 'skills' (<n> skills) is not supported by Cursor IDE` + `💡 Consider inlining skill content into the main prompt for Cursor` |
+| `hooks[]` | dropped | `⚠️  Feature 'hooks' (<n> hooks) is not supported by Cursor IDE` |
+| `inference.maxSteps` | dropped | `⚠️  Feature 'maxSteps' (<n>) is not supported by Cursor IDE` |
+| `capabilities.delegate` | dropped | `Tool 'task' is not supported by cursor` (`ToolMapper`) |
+| `context[]` | inlined | `💡 <n> context file(s) referenced - consider loading them manually in Cursor` |
+
+---
+
+## 4. Windsurf Adapter
+
+From `adapters/WindsurfAdapter.ts` + `ToolMapper.ts` `windsurf`. **Inferred, not verified
+against a live install — see Open Questions.**
+
+Mid-fidelity: multiple agents ✅, external context refs ✅; binary permissions,
+temperature→creativity, partial skills, no hooks.
+
+### 4.1 Layout
+
+```
+.windsurf/
+  config.json                    # role: primary
+  agents/<id>.json               # role: subagent
+  context/<...>.md               # copied byte-for-byte (MVI comment intact)
+```
+The ecosystem also uses root `.windsurfrules` and `.windsurf/rules/*.md`; the stub commits to
+JSON (Open Question #Q9).
+
+### 4.2 Field mapping table
+
+| Neutral field | Windsurf target | Transform rule |
+|---|---|---|
+| `id` / `name` | `name` | verbatim |
+| `role` | `type` | `primary`/`subagent` (partial) |
+| `category` | `category` | verbatim (enum-validated) |
+| `description` | `description` | verbatim |
+| `capabilities` | `tools` + `permissions` (binary) | **default-scope decision → boolean**; scoping dropped (§4.3) |
+| `capabilities.delegate` | `delegate` tool | partial (`task→delegate`) |
+| `inference.temperature` | `creativity` | `≤0.4→low`, `≤0.8→medium`, `>0.8→high` |
+| `inference.model` = null | omit | Windsurf default |
+| `inference.model` set | `model` | `mapOACModelToWindsurf` (fallback `claude-4-sonnet`) |
+| `context[]` | `contexts[]` path refs | referenced under `.windsurf/context/` |
+| `context[].priority` | `priority` | `critical/high→high`, `medium/low→low` (4→2) |
+| `dependencies[]` | partial | subagent deps → separate JSONs; others dropped |
+| `examples[]` | dropped | no support |
+| `hooks[]` | dropped | no hooks |
+| `skills[]` | `contexts[]` refs | → `.windsurf/context/<skill>.md` |
+| body | `systemPrompt` | verbatim |
+
+Tool names (`ToolMapper.ts` `windsurf.fromOAC`): `bash→shell, read→read_file, write→write_file,
+edit→edit_file, glob→find_files, grep→search_content, task→delegate`.
+
+### 4.3 Capabilities under Option A
+
+`mapOACPermissionsToWindsurf` produces `Record<string, boolean>` — **binary, unscoped,
+unordered**. Collapse: default-scope decision → `allow→true`, `deny→false`, `ask→false`
+(bespoke warning below, matching the stub's existing literal). All scope ≠ `*` rules dropped;
+ordering meaningless. Same fail-closed asymmetry as CC §2.3: `coder-agent.bash` (`*: deny`)
+→ `shell: false`; its `edit` deny-globs (implicit default allow) → `edit_file: true` with the
+globs lost.
+
+⚠️ The stub's current granular branch does the **opposite** of fail-closed —
+`WindsurfAdapter.ts:511-516` sets `windsurfPerms[tool] = hasAllow` for object-valued
+permissions, i.e. **any** `allow` key flips the tool **on**. Under Option A that turns
+`coder-agent`'s deny-all-plus-two-exceptions into **unrestricted shell** — the exact
+escalation §2.3 rejects. **This branch must be replaced by the default-scope rule**, not
+merely re-pointed at the new shape.
+
+### 4.4 Content types
+
+- **Agents:** JSON (`name/description/type/systemPrompt/model?/tools/creativity?/category/
+  contexts[]/permissions`).
+- **Skills:** degraded → `contexts[]` refs at `.windsurf/context/<skill>.md`.
+- **Commands:** no clear equivalent (Open Question #Q9).
+- **Context:** referenced, copied byte-for-byte; priority collapsed 4→2.
+- **Hooks:** dropped.
+
+### 4.5 Manifest
+
+`.windsurf/config.json` doubles as primary-agent file and de-facto manifest. No dedicated
+plugin manifest (Open Question #Q9).
+
+### 4.6 Lossy transforms (Windsurf)
+
+| Field / feature | Disposition | Exact warning |
+|---|---|---|
+| `hooks[]` | dropped | `⚠️  Feature 'hooks' (<n> hooks) is not supported by Windsurf` + `❌ Windsurf does not support hooks - behavioral rules will be lost` |
+| scoped rules | dropped | `⚠️  Feature 'scoped <cap> permissions' (<n> rules) is not supported by Windsurf` |
+| `decision: ask` | → false | `⚠️  Permission "ask" for <tool> degraded to false (deny). Windsurf only supports binary on/off.` *(stub literal — preserve verbatim)* |
+| rule ordering | meaningless | `⚠️  Feature 'permission rule ordering' is not supported by Windsurf` |
+| granular permissions | binary | `⚠️  Feature 'granular permissions' will be degraded: ordered scope rules → binary on/off per tool` |
+| `skills[]` | context refs | `⚠️  Feature 'skills' will be degraded: full Skills system → basic context references` |
+| `inference.temperature` | → creativity | `⚠️  Feature 'temperature' will be degraded: numeric temperature → creativity (low/medium/high)` |
+| `context[].priority` | 4→2 | `⚠️  Feature 'context priority' will be degraded: critical/high/medium/low → high/low` |
+| `inference.maxSteps` | dropped | `⚠️  Feature 'maxSteps' (<n>) is not supported by Windsurf` |
+| `context[]` | reminder | `💡 <n> context file(s) referenced - ensure they exist in .windsurf/context/` |
+
+---
+
+## 5. Consolidated Capability Matrix
+
+Extends `CAPABILITY_MATRIX` in `core/CapabilityMatrix.ts`. Cells: **full** / **partial** /
+**none**. `oac` = the OpenCode adapter target.
+
+| Feature (category) | oac | claude | cursor | windsurf | Notes |
+|---|:--:|:--:|:--:|:--:|---|
+| multipleAgents (agents) | full | full | none | full | cursor: single/merged rules file |
+| agentModes (agents) | full | **partial** ✏️ | none | partial | ✏️ **corrected** (index v2 #5): plugin has no per-agent `mode:`; every `agents/` file is a subagent → `primary` flattens |
+| agentCategories (agents) | full | **none** ✏️ | none | partial | ✏️ **corrected** (index v2 #5): survives only as `plugin.json` keywords |
+| granularPermissions (permissions) | full | none | none | none | only oac has per-scope allow/deny/ask |
+| **scopedPermissions** (permissions) † | full | none | none | none | **NEW** — glob/argument scoping *within* a capability (`**/*.env*`, `router.sh …*`). CC `tools:` is tool-NAME-only (verified, `sub-agents.md`); `Bash(x:*)` is settings.json-only and **plugins cannot ship permissions** (§2.6) |
+| **askTriState** (permissions) † | full | none | none | none | **NEW** — CC *platform* has a settings.json `ask` array, but it is **unreachable from a plugin** (§2.6) ⇒ adapter-reachable support is none; rounds to deny |
+| **permissionRuleOrdering** (permissions) † | full | none | none | none | **NEW** — ordered match-wins lists. oac preserves by convention, not by YAML spec (§1.3). ⚠️ direction unresolved (§0.5) |
+| pathPatterns (permissions) | full | none | none | **none** ✏️ | ✏️ windsurf downgraded partial→none: `Record<string,boolean>` cannot hold a path pattern |
+| taskDelegation (tools) | full | **partial** ✏️ | none | partial | ✏️ claude downgraded full→partial: `Task` is coarse — a scoped delegate allowlist cannot be expressed (§2.8) |
+| bashExecution (tools) | full | full | full | full | name remap only |
+| fileOperations (tools) | full | full | full | full | name remap only |
+| searchOperations (tools) | full | full | full | full | name remap only |
+| externalContext (context) | full | full | none | full | cursor: refs unsupported, content inlined (see contextBundling) |
+| contextPriority (context) | full | none | none | partial | windsurf 4→2; claude drops; oac sidecar-only. ⚠️ all depend on the MVI parser (§0.6) |
+| contextSubdirs (context) | full | full | none | full | cursor single-file |
+| skillsSystem (context) | full | full | none | partial | claude dir-per-skill; windsurf → context refs; cursor inline |
+| modelSelection (model) | full | full | full | full | **null ⇒ tool default** (locked #2) |
+| temperatureControl (model) | full | none | partial | partial | claude drops; cursor limited; windsurf → creativity |
+| maxSteps (model) | full | none | none | none | oac sidecar advisory |
+| hooks — plugin/project-level (advanced) | full | full | none | none | claude: `hooks.json` + `session-start.sh` |
+| **hooks — agent-level (advanced)** † | full | none | none | none | **NEW** — verified: plugin subagents **ignore** `hooks`/`mcpServers`/`permissionMode` frontmatter (`sub-agents.md`) |
+| dependencies (advanced) | full | full | none | partial | cursor none; windsurf subagent deps only |
+| priorityLevels (advanced) | full | partial | none | partial | oac 4 levels; claude/windsurf 2 |
+| **commands** (advanced) † | full | full | none | none | **NEW** — claude `commands/*.md`; cursor/windsurf have no command system |
+| **manifest/packaging** (advanced) † | full | full | none | partial | **NEW** — claude `.claude-plugin/plugin.json`; oac `opencode.json` + sidecar; windsurf `config.json` only |
+| **contextBundling** (context) † | *reference* | *bundle+hook* | *inline* | *reference* | **NEW** — descriptive, not a support level: *how* externalContext is realized |
+
+† = new row (beyond current `CapabilityMatrix.ts`). ✏️ = corrects a value that is **wrong in
+the current file**.
+
+**Corrections to land in `CapabilityMatrix.ts`** (4 value fixes, all verified):
+`agentModes: claude` full→partial · `agentCategories: claude` partial→none ·
+`pathPatterns: windsurf` partial→none · `taskDelegation: claude` full→partial.
+`externalContext: cursor` stays **none** (referencing is unsupported; inlining is captured by
+`contextBundling`, not by upgrading this cell — Open Question #Q10).
+
+---
+
+## 6. Adapter selection & pipeline notes
+
+- `oac build --target <tool>` selects via `AdapterRegistry`; each adapter implements
+  `fromOAC(agent) → ConversionResult` (`{ files: ToolConfig[], warnings: string[] }`).
+- `analyzeCompatibility(agent, target)` runs first: `blockers` fail the build; `warnings` are
+  printed and counted. It must be extended to predict the new rows above — today it has no
+  concept of scoped rules and would under-report every Option A agent.
+- Whole-repo builds run manifest generation once after per-agent conversion.
+- `oac apply` is already ~80% of this pipeline (index v2 #8).
+
+---
+
+## Open Questions
+
+1. **Q1 — first-match-wins vs last-match-wins (BLOCKING; §0.5).** The locked shape says
+   first-match-wins; all 34 authored agents and `docs/planning/12-MASTER-SYNTHESIS.md:442`
+   say last-match-wins, and first-match-wins would silently break `coder-agent`'s `router.sh`
+   allowlist and neuter `openagent`'s `sudo *: deny`. The index's own `coder-agent` example is
+   broken as printed under first-match-wins. **Recommend adopting last-match-wins.** This
+   changes generated output for every target and must be resolved before `02` or `03` freeze.
+
+2. **Q2 — implicit default + IR validations (§0.4, §1.3).** For `02`: (a) when a capability has
+   no `*` rule, is the implicit default allow or deny? `coder-agent.edit` (denies only) implies
+   allow; `code-reviewer.delegate` (allows only) implies deny — proposed rule: *"opposite of
+   the decisions present; mixed-without-`*` is an error."* (b) Should the IR reject
+   integer-like scopes and enforce scope-uniqueness per capability? Both are required for
+   OpenCode's map form to round-trip deterministically.
+
+3. **Q3 — are dropped security globs a warning or a blocker? (§2.3)** `coder-agent` on CC gets
+   unrestricted `Edit` with `**/*.env*` / `**/*.key` protections silently gone, and no
+   adapter-side remedy exists (§2.6). Warning (current spec) or hard blocker requiring explicit
+   opt-in?
+
+4. **Q4 — CC agent `tools:` vs `settings.json` interaction (confidence: medium).** If `Bash` is
+   omitted from an agent's `tools:`, can a project `settings.json` `Bash(...)` allow re-enable
+   it for that agent? Docs say subagents have "independent permissions" but don't specify the
+   interaction. §2.6's conclusion holds either way (plugins can't ship permissions), but this
+   determines whether the documented manual remedy needs a `tools:` edit too.
+
+5. **Q5 — skill execution metadata.** CC skills carry `context: fork` + `agent: <id>`. Does the
+   neutral skill IR gain `execution: { isolate?, agent? }`? Where does it degrade on
+   OpenCode/Cursor/Windsurf?
+
+6. **Q6 — plugin `settings.json: {"model": "opusplan"}` is likely a no-op (§2.5).** Docs say
+   plugin settings.json supports only `agent` and `subagentStatusLine`. If confirmed, #264 /
+   commit `40dd267` shipped a setting CC ignores — worth an issue, and it moots v1's Q4.
+   Needs runtime verification.
+
+7. **Q7 — `code-reviewer` warning count: 1 or 2? (§2.8).** Under Option A, `delegate:
+   {contextscout: allow}` is a scoped rule; collapsing it to "no `Task`" is a real loss that
+   §0.3 says must warn → 2 warnings, not the index's 1. The emitted file is identical either
+   way. **Recommend accepting 2 and correcting the index**; the alternative is a silent drop.
+
+8. **Q8 — Cursor: legacy `.cursorrules` vs `.cursor/rules/*.mdc`.** The stub emits legacy
+   single-file. Modern `.mdc` supports multiple rule files (relaxing the merge, changing
+   `multipleAgents: cursor` from `none`) and a `globs:` key — can `globs:` carry *any* of the
+   scoped-permission intent, or does it only scope rule *activation*? Also: do neutral commands
+   become inlined docs or get skipped?
+
+9. **Q9 — Windsurf real format (unverified).** The `.windsurf/config.json` +
+   `.windsurf/agents/*.json` layout is inferred from the stub, never checked against a live
+   install. Confirm: (a) canonical layout vs `.windsurfrules` / `.windsurf/rules/*.md`;
+   (b) any manifest listing all agents; (c) any home for commands; (d) exact `creativity`
+   vocabulary (`low/medium/high` vs numeric); (e) whether *any* scoped permission construct
+   exists.
+
+10. **Q10 — `externalContext: cursor` cell semantics.** Keep `none` (referencing unsupported;
+    inlining captured by the new `contextBundling` row), or reclassify? Needs a decision so
+    `CapabilityMatrix.ts` and this doc agree.
+
+**Closed in v2:** v1's Q1 (ClaudeAdapter wrong layout — **confirmed**, now index v2 #4 and
+§2 header) · v1's Q9 (OpenCode safety-pattern preset — **moot**: Option A puts safety globs in
+content, per locked #5) · the index's open CC-`settings.json`-scoping question (**closed
+negative**, §2.6: plugins cannot ship permissions).

+ 513 - 0
docs/architecture/canonical-refactor/04-cli-build-distribution.md

@@ -0,0 +1,513 @@
+# 04 — CLI, Build Pipeline & Distribution
+
+> **Owner:** Agent D
+> **Status:** Specification (no implementation)
+> **Scope:** The `oac` npm CLI, the neutral-source → per-tool **build pipeline**, full
+> `install.sh`/`update.sh` **parity**, and cross-platform npm **distribution**.
+> **Reads-from:** `01-feature-inventory.md` (must-survive list), `02-canonical-schema.md`
+> (the IR this CLI parses), `03-adapter-specs.md` (the transforms `oac build` invokes).
+
+This document specifies the CLI that **replaces** `install.sh` (1510 lines, 35 functions)
+and `update.sh` (344 lines). Both bash scripts are **deleted** per the locked decisions in
+`00-INDEX.md`. Nothing they do may be lost — §3 is the line-by-line parity contract.
+
+---
+
+## 0. Context: what exists today vs. what must change
+
+### 0.1 What is already built (extend, do not reinvent)
+
+| Asset | Location | Reuse |
+|-------|----------|-------|
+| CLI entry | `packages/cli/src/index.ts` + `bin/oac.js` wrapper | Keep; add `build`, `remove` already exists |
+| Commands | `commands/{init,add,update,apply,doctor,list,status}.ts` | Keep all; `apply` becomes an alias/subset of `build` |
+| Install engine | `lib/installer.ts` (`installFiles`, `updateFiles`, sha256 decide-logic) | Keep — this is the collision/update strategy engine |
+| Manifest | `lib/manifest.ts` → `.oac/manifest.json` (per-file sha256) | Keep — replaces install.sh collision detection |
+| Config | `lib/config.ts` → `.oac/config.json` (yoloMode, autoBackup) | Keep |
+| Bundled files | `lib/bundled.ts` (`getPackageRoot`, `listBundledFiles`) | **Rework** to bundle `/content` not `.opencode/` |
+| Registry | `lib/registry.ts` (Zod-validated `registry.json`) | Keep; extend for profiles + dependency resolution |
+| IDE detect | `lib/ide-detect.ts` | Keep; feeds `--target` auto-detection |
+| Adapters | `packages/compatibility-layer/{adapters,core,mappers}` | The engine `oac build` drives |
+
+### 0.2 The three structural changes this refactor forces on the CLI
+
+1. **Source flips.** Today `installFiles`/`bundled.ts` copy `.opencode/**` verbatim into the
+   user's project. After the refactor, the bundle is **`/content`** (tool-neutral), and
+   `.opencode/` is a **generated build target** — so a plain copy is no longer enough for
+   OpenCode users. `oac init` for a tool now means **install `/content` + build the target**.
+2. **`apply` generalises into `build`.** `commands/apply.ts` already does
+   `loadAgents → adapter.fromOAC → write` for Cursor/Claude/Windsurf. `oac build` is that
+   pipeline, promoted to a first-class command, reading `/content` (not `.opencode/agent/`)
+   and adding OpenCode as just another target (§1, §2).
+3. **Runtime must be Node-portable.** `bin/oac.js` currently `execFileSync('bun', …)` and the
+   whole CLI uses Bun-only APIs (`Bun.file`, `Bun.write`, `import.meta.dir`). A user running
+   `npm i -g @nextsystems/oac` will **not** have Bun. This is the single biggest cross-platform
+   install blocker and is resolved in §4.1.
+
+---
+
+## 1. Command spec
+
+Global conventions (all commands):
+
+- **Flags:** `--dry-run` (plan only, no writes), `--verbose`, `--yolo` (backup + overwrite
+  user-modified files), `--json` (machine output where meaningful), `-v/--version`, `-h/--help`.
+- **Project root** = `process.cwd()`, validated by `isProjectRoot()` (has `package.json` or `.git`).
+- **State files:** `.oac/manifest.json` (sha256 ledger), `.oac/config.json` (prefs),
+  `.oac/backups/{ISO-timestamp}/…` (yolo/backup copies).
+- **Exit codes:** `0` success (warnings allowed), `1` hard error. `--dry-run` never writes.
+
+### 1.1 `oac init` — set up a project (EXTENDED)
+
+Set up OAC in the current project: install neutral `/content` and generate the chosen target(s).
+
+| Aspect | Spec |
+|--------|------|
+| **Args** | none |
+| **Flags** | `--target <tool...>` (opencode\|claude\|cursor\|windsurf; repeatable; default = auto-detect via `ide-detect`, fallback `opencode`), `--profile <name>` (essential\|developer\|business\|full\|advanced; default `essential`), `--global` (install to user-global location, §4.2), `--dir <path>` (explicit install dir; replaces `install.sh --install-dir`), `--no-build` (install `/content` only, skip target generation), `--yolo`, `--dry-run`, `--verbose` |
+| **Reads** | bundled `/content/**` + `registry.json` from package root; existing `.oac/manifest.json` (if re-init) |
+| **Writes** | `content/**` (neutral source, tracked in manifest) + target output (e.g. `.opencode/**`, `CLAUDE.md`, `.cursorrules`) + `.oac/manifest.json` + `.oac/config.json` (only if absent) |
+| **Behavior** | 1) assert project root; 2) resolve profile → component set + **dependency closure** (§2.3); 3) resolve install location (§4.2); 4) print plan (files, targets, location) + collision preview; 5) `installFiles(profileFiles)` into `content/`; 6) unless `--no-build`, run **build pipeline** (§2) for each `--target`; 7) write manifest + config; 8) summary. `CI=true` implies `--yolo` (already implemented). |
+
+Replaces: `install.sh` interactive location menu + main menu + profile menu + preview + install.
+
+### 1.2 `oac add <ref>` — add one component (EXTENDED with deps)
+
+| Aspect | Spec |
+|--------|------|
+| **Args** | `ref` = `<type>:<id>` (e.g. `agent:code-reviewer`, `context:security`, `skill:task-management`). No arg → list available components grouped by type (current behavior). |
+| **Flags** | `--target <tool...>` (rebuild these targets after add; default = targets already present per manifest), `--force` (reinstall if present), `--no-deps` (skip dependency resolution — **new**), `--dry-run`, `--yolo`, `--verbose` |
+| **Reads** | `registry.json`, bundled `/content`, manifest |
+| **Writes** | component file(s) into `content/**` (skills copy their `files[]` array — already supported by registry `files`), manifest entry per file, then re-build affected targets |
+| **Behavior** | resolve ref → component; **resolve dependencies recursively** (§2.3) unless `--no-deps` (this is a parity gap — `install.sh resolve_dependencies` exists, current `add.ts` does not resolve); wildcard context refs (`context:core/*`) expand via registry (§2.3); copy files; update manifest; rebuild targets so the new component appears in generated output. |
+
+Replaces: `install.sh` custom component selection + `resolve_dependencies` + `expand_context_wildcard`.
+
+### 1.3 `oac remove <ref>` — remove a component (EXISTS)
+
+| Aspect | Spec |
+|--------|------|
+| **Args** | `ref` = `<type>:<id>` (required) |
+| **Flags** | `--target <tool...>` (rebuild after removal), `--dry-run`, `--verbose` |
+| **Reads** | `registry.json`, manifest |
+| **Writes** | deletes `content/**` file(s), removes manifest entry, rebuilds targets |
+| **Behavior** | current impl already deletes file + updates manifest. **Add:** rebuild targets so generated output no longer references the removed component; warn if other installed components depend on it (reverse-dependency check via registry). |
+
+### 1.4 `oac update` — update installed files (EXISTS, keep)
+
+| Aspect | Spec |
+|--------|------|
+| **Args** | none |
+| **Flags** | `--dry-run` / `--check` (alias), `--yolo`, `--verbose`, `--target <tool...>` (rebuild after content update) |
+| **Reads** | manifest, bundled `/content`, config (picks up persisted `yoloMode`) |
+| **Writes** | updated `content/**`, manifest, rebuilt targets |
+| **Behavior** | `updateFiles()` algorithm (already built): per bundled file — in-manifest+hash-matches → safe update; in-manifest+hash-differs → skip (or `--yolo`: backup+overwrite); not-in-manifest → install. Files in manifest but no longer in bundle → drop from manifest, leave user copy, warn. **Add:** after content update, rebuild every target recorded in the manifest so generated output tracks the new source. |
+
+Replaces: **all of `update.sh`** — and does it far better (sha256 protection of user edits vs. update.sh's blind curl-overwrite-with-backup).
+
+### 1.5 `oac build` — generate tool output from neutral source (NEW)
+
+The centerpiece of the refactor. Loads `/content`, parses to IR, runs the adapter, writes the
+tool's file layout, validates the result.
+
+| Aspect | Spec |
+|--------|------|
+| **Args** | `[target]` optional positional (e.g. `oac build opencode`) |
+| **Flags** | `--target <tool...>` (repeatable; alternative to positional), `--all` (build every detected/known target), `--out <dir>` (override output root; default per-target convention), `--dry-run` (preview + diff, write nothing), `--verbose` (per-transform + degradation warnings), `--strict` (treat adapter warnings as errors → exit 1), `--check` (build to temp, diff against on-disk output, exit 1 if drift — for CI), `--json` |
+| **Reads** | `content/**` (agents, skills, commands, context) + `registry.json` (dependency graph, ordering) + `.oac/config.json` |
+| **Writes** | target-specific layout (from `03-adapter-specs.md`): opencode → `.opencode/agent/**` + `agent-metadata.json` sidecars + `.opencode/skills/**` + `.opencode/command/**` + `opencode.json`; claude → `agents/*.md` + `.claude-plugin/plugin.json` + wired `context/`; cursor → `.cursorrules`; windsurf → `.windsurfrules`. Each written file is added to the manifest with `source: "generated"` and its sha256. |
+| **Behavior** | full pipeline in §2. Target selection: explicit `--target`/positional > `--all` > auto-detect via `ide-detect` > default `opencode`. Emits **degradation warnings** predicted by `CapabilityMatrix` (e.g. "temperature dropped for Claude") — never silent loss. |
+
+`oac apply` (current) is retained as a **thin deprecated alias** → `oac build --target <ide>` for
+Cursor/Claude/Windsurf, so existing muscle memory and docs keep working during migration
+(migration detail owned by Agent E).
+
+### 1.6 `oac doctor` — health check (EXISTS, extend)
+
+Runs the 7 checks already implemented (Bun/Node runtime, config, manifest, files-on-disk,
+sha256 modified-file detection, IDE detection, npm version vs latest). Extend with:
+- **`/content` parse check** — every `content/**` agent/skill validates against the Zod IR (§2.1).
+- **Build-drift check** — for each target in the manifest, is on-disk output in sync with a fresh
+  build of current `/content`? (reuses `oac build --check`). Reports "run `oac build`".
+- **Dependency check** — every installed component's `dependencies[]` are also present.
+- Keep `--json` (CI) and exit `1` on errors.
+
+Replaces: `install.sh check_dependencies` / `check_bash_version` (now a runtime check).
+
+### 1.7 `oac status` — quick project summary (EXISTS)
+
+Reads manifest + config + `ide-detect`. Prints component counts by type, modified-file count
+(sha256 diff), detected IDEs, and installed targets. **Add:** show which targets are built and
+whether they are stale relative to `/content`. Read-only, exit `0`.
+
+### 1.8 `oac list` — list components (EXISTS, two modes)
+
+- `oac list` (no manifest context) / `oac add` (no ref) → list **available** registry components
+  grouped by type with `oac add <type>:<id>` hints.
+- `oac list --installed` → list components tracked in the manifest (current `list.ts` behavior),
+  filterable by `--type/--agents/--context/--skills`, showing install date + user-modified flag.
+
+Replaces: `install.sh list_components`.
+
+### 1.9 Command → target-selection summary
+
+| Command | Touches `/content` | Runs build pipeline | Target selection |
+|---------|:---:|:---:|------|
+| `init` | install | yes (unless `--no-build`) | `--target` > auto-detect > `opencode` |
+| `add` | install file | yes (affected targets) | manifest targets or `--target` |
+| `remove` | delete file | yes (affected targets) | manifest targets or `--target` |
+| `update` | update files | yes (all manifest targets) | manifest targets or `--target` |
+| `build` | read only | **yes** | `--target`/positional > `--all` > auto-detect > `opencode` |
+| `doctor`/`status`/`list` | read only | no (`--check` only) | — |
+
+---
+
+## 2. Build pipeline
+
+`oac build` is a pure, deterministic function of `/content` + `registry.json` + target adapter.
+Same inputs ⇒ byte-identical output (required for golden-snapshot tests and `--check` drift
+detection). Stages:
+
+```
+/content/**                                                    packages/compatibility-layer
+   │                                                                    │
+   ▼                                                                    ▼
+[1] LOAD ──► [2] PARSE→IR ──► [3] RESOLVE deps/order ──► [4] ADAPT ──► [5] BUNDLE context
+                                                                          │
+                                            [7] MANIFEST ◄── [6] WRITE ◄──┤
+                                                    │                     │
+                                                    └──► [8] VALIDATE ────┘
+```
+
+### Stage 1 — Load
+Enumerate `content/{agents,skills,commands,context}/**`. Reuse the enumeration pattern from
+`bundled.ts` (`collectFiles`), but rooted at `content/` and **skipping `node_modules/`** (issue
+#308 — the current `listBundledFiles` already excludes it structurally; `oac build` must too, and
+`update.sh`'s `-not -path "*/node_modules/*"` guard must survive here). Split frontmatter + body.
+
+### Stage 2 — Parse to IR
+Validate each file's frontmatter against the canonical Zod schema (`OpenAgentSchema` /
+`02-canonical-schema.md`) via `compatibility-layer`'s `AgentLoader.loadAgents`. On invalid
+frontmatter: collect a structured error with file path + Zod issue list, and **fail the file, not
+the build** (partial builds are useful — mirrors `installer.ts` per-file error handling). `--strict`
+promotes any parse warning to a build failure.
+
+### Stage 3 — Resolve dependencies & order
+Use `registry.json` `dependencies[]` to compute the transitive closure of what a target needs, and
+a **stable topological order** so output is deterministic. This is where the neutral graph lives —
+`resolve_dependencies` + `expand_context_wildcard` from `install.sh` move here as typed functions
+(see §2.3). Cycles → reported error, not infinite recursion (the bash version could self-reference;
+the TS version must guard with a visited-set).
+
+### Stage 4 — Adapt (IR → tool)
+Select the adapter from `AdapterRegistry` for the target and call `adapter.fromOAC(agent)` for each
+agent (and the analogous skill/command transforms per `03-adapter-specs.md`). The adapter returns a
+`ConversionResult` with `configs[]` (path + content) and `warnings[]`. `CapabilityMatrix` predicts
+lossy transforms (temperature dropped for Claude, granular permissions flattened to a tool allowlist,
+`model: null` → omit line so the tool default applies — **no hardcoded models**, locked decision #2).
+All warnings surface to the user; `--verbose` lists each, otherwise a count.
+
+### Stage 5 — Bundle context
+Context files referenced by an agent's `context[]` are copied/wired per target:
+- **opencode** — context files copied under `.opencode/context/`, and path references rewritten.
+  This is the home of `install.sh`'s global-path rewrite (`@.opencode/context/` →
+  `@<install-dir>/context/`) — see §2.4. Metadata split into `agent-metadata.json` sidecars.
+- **claude** — context copied into the plugin `context/`, wired via a session-start hook; contributes
+  entries to `.claude-plugin/plugin.json`.
+- **cursor/windsurf** — context inlined into the single rules file (size-limited; `apply.ts` already
+  warns at 80KB / errors at 100KB for Cursor — that guard carries into `build`).
+
+### Stage 6 — Write
+Write each `config.path` under the target's output root (or `--out`). Writes go through the existing
+**collision/update engine** (`installer.ts` decide-logic): a generated file whose on-disk sha256
+still matches the manifest is safely overwritten; a user-modified generated file is skipped unless
+`--yolo` (then backed up to `.oac/backups/…`). This is how "no accidental overwrite" (#321, #326)
+is enforced for generated output, not just copied source. `--dry-run` prints a unified diff instead.
+
+### Stage 7 — Manifest generation
+Every written file gets a manifest entry: `{ sha256, type, source: "generated", target, installedAt }`.
+The manifest becomes the single source of truth for "what OAC produced" — replacing `install.sh`'s
+ad-hoc collision scan and `update.sh`'s find-everything approach. `source` distinguishes
+`bundled` (neutral `/content` copied), `registry` (added component), `generated` (build output),
+`custom` (user file — never touched).
+
+### Stage 8 — Validate
+After writing, re-load the generated output and assert it is well-formed for the target (e.g. parse
+the generated OpenCode frontmatter; validate `plugin.json` against Claude's plugin schema; assert
+Cursor file ≤ size limit). Validation failures in `--strict`/`--check` → exit 1. This is the
+"golden snapshot + parse/manifest check on ONE worked agent" that the locked minimal-test decision
+(#3) requires, run live.
+
+### 2.1 Determinism requirements
+- Stable sort of inputs (by id) before writing.
+- No timestamps **inside** generated content (timestamps live only in the manifest).
+- Frontmatter key order fixed by the adapter, not by object-iteration order.
+- `oac build && oac build` is a no-op (second run: all sha256 match → 0 changes).
+
+### 2.2 Profiles
+Profiles (`essential`, `developer`, `business`, `full`, `advanced`) live in `registry.json`
+`profiles.*.components[]` (25/41/25/50/68 components; `advanced` adds `additionalPaths`
+`.Building/`, `.github/workflows/`). `oac init --profile` selects the component set; the CLI
+resolves it exactly as `install.sh get_profile_components` did, then runs dependency closure.
+`additionalPaths` (advanced) — `install.sh` only *printed* these as "manual download required";
+the CLI must actually copy them from the bundle (parity improvement, not just parity).
+
+### 2.3 Dependency & wildcard resolution (moves from bash to `packages/core`)
+`install.sh` did this in `resolve_dependencies` (recursive) + `expand_context_wildcard`
+(`context:core/*` → all matching context ids) + `expand_selected_components` (dedupe). Reimplement
+as typed, cycle-safe functions in `packages/core` (or `lib/registry.ts`), consumed by `init`, `add`,
+and Stage 3. Must handle: singular/plural type keys (`get_registry_key`), aliases
+(`.aliases[]` in registry), and non-`.md` context paths like `paths.json` (issue #251/#252 —
+`resolve_component_path` already special-cases this; preserve it).
+
+### 2.4 Path rewriting for non-local installs
+`install.sh perform_installation` rewrote `@.opencode/context/` and `.opencode/context` references
+to the absolute install dir whenever installing anywhere other than a local `.opencode/`. This must
+survive as a **Stage-5 concern**: when the OpenCode target root is not the default project-local
+`.opencode/` (i.e. `--global` or `--dir`), the adapter/bundler rewrites context path references to
+the resolved absolute location. This is central to issues #321/#326 (global vs project-local paths).
+
+---
+
+## 3. `install.sh` → CLI parity checklist (all 35 functions)
+
+Every function in `install.sh` maps to a CLI equivalent or is justified obsolete. Nothing lost.
+
+| # | `install.sh` function | CLI equivalent | Notes |
+|--:|------------------------|----------------|-------|
+| 1 | `jq_exec` | `lib/registry.ts` (`readRegistry`, Zod) | Native JSON parse; no `jq` dependency. Also strips `\r` — Node parse is CRLF-safe. |
+| 2 | `print_header` | `ui/logger.ts` + `ui/spinner.ts` | Banner via logger. |
+| 3 | `print_success` | `ui/logger.ts` `success()` | |
+| 4 | `print_error` | `ui/logger.ts` `error()` | |
+| 5 | `print_info` | `ui/logger.ts` `info()` | |
+| 6 | `print_warning` | `ui/logger.ts` `warn()` | |
+| 7 | `print_step` | `ui/logger.ts` `bold()`/`info()` + spinner | |
+| 8 | `normalize_and_validate_path` | `lib/paths.ts` `normalizeInstallPath()` **(new)** | Tilde expansion, `\`→`/`, trailing-slash strip, relative→absolute. Node `path`+`os.homedir()`. Cross-platform (#304/#312). |
+| 9 | `validate_install_path` | `lib/paths.ts` `validateInstallPath()` **(new)** | Parent-exists + writable checks via `fs.access`. |
+| 10 | `get_global_install_path` | `lib/paths.ts` `getGlobalInstallDir()` **(new)** | `~/.config/opencode` (all platforms today); §4.2 revisits Windows `%APPDATA%`. |
+| 11 | `check_bash_version` | **obsolete** | No bash. Replaced by Node/Bun runtime check in `doctor` (`checkBunVersion` → generalized). |
+| 12 | `check_dependencies` (curl, jq) | **obsolete** | No curl/jq — `fetch` + native JSON built into runtime. `doctor` checks the runtime instead. |
+| 13 | `fetch_registry` (file:// or curl) | `lib/registry.ts` `readRegistry()` + §4.5 remote/cache | Registry is **bundled** in the package (offline by default); optional remote fetch + cache in §4.5. |
+| 14 | `get_profile_components` | `lib/registry.ts` `getProfileComponents()` **(new)** | Reads `profiles.*.components[]`. §2.2. |
+| 15 | `get_component_info` | `lib/registry.ts` `resolveComponent()` | Already exists; extend for context-path special case. |
+| 16 | `resolve_component_path` | `lib/registry.ts` `getDestRelativePath()` / `getBundledSourcePath()` | Exists; preserve `.md`-then-raw fallback for `paths.json` (#251). |
+| 17 | `get_registry_key` (singular/plural) | `lib/registry.ts` type normalization | `ComponentTypeSchema` + plural section keys; fold aliases in. |
+| 18 | `get_install_path` (strip `.opencode/`) | `lib/registry.ts` `INSTALL_DIRS` + install-dir join | Post-refactor: source lands in `content/`, targets under their own roots. |
+| 19 | `expand_context_wildcard` | `packages/core` `expandWildcard()` **(new)** | §2.3. Typed, cycle-safe. |
+| 20 | `expand_selected_components` (dedupe) | `packages/core` `dedupeComponents()` **(new)** | Set-based dedupe. |
+| 21 | `resolve_dependencies` (recursive) | `packages/core` `resolveDependencyClosure()` **(new)** | §2.3. **Current parity gap** — `add.ts` does not yet resolve deps; must add. |
+| 22 | `check_interactive_mode` (TTY guard) | `commander` + `process.stdin.isTTY` | curl\|bash pipe → non-interactive; profile/flags drive it. `CI=true`→`--yolo`. |
+| 23 | `show_install_location_menu` | `oac init` flags `--global`/`--dir` (+ optional prompt) | Non-interactive first; interactive prompt optional (Open Q). |
+| 24 | `show_main_menu` | `oac init` / `oac add` command routing | Commander subcommands replace the menu. |
+| 25 | `show_profile_menu` | `oac init --profile <name>` | Flag replaces menu; `oac list --profiles` to browse (Open Q). |
+| 26 | `show_custom_menu` | `oac add <ref>` (repeatable) | Per-component add replaces category menu. |
+| 27 | `show_component_selection` | `oac add` (no ref) lists; user picks refs | `printAvailableComponents()` exists. |
+| 28 | `show_installation_preview` | `oac init/add --dry-run` + pre-write plan | `printPlan()` exists in `init.ts`; extend with collision preview. |
+| 29 | `show_collision_report` | `lib/installer.ts` decide-logic + summary printers | sha256 manifest diff replaces filename-only collision scan; grouped report in summary. |
+| 30 | `get_install_strategy` (skip/overwrite/backup/cancel) | `--yolo` flag + `config.autoBackup` + skip-default | Default = skip user-modified (safe); `--yolo` = backup+overwrite; cancel = don't run. Backups → `.oac/backups/{ts}/`. |
+| 31 | `perform_installation` | `lib/installer.ts` `installFiles`/`updateFiles` + `oac build` | The core engine, already built + Stage 6 write. |
+| 32 | `show_post_install` | `init.ts` `printSummary()` | Exists; add "run `oac build`/next steps". |
+| 33 | `list_components` | `oac list` / `oac add` (no ref) | Exists (§1.8). |
+| 34 | `cleanup_and_exit` | Node process exit + `try/finally` temp cleanup | No global temp dir needed (no downloads by default); §4.5 cache cleanup if remote used. |
+| 35 | `main` (arg parse, profile shortcuts, env vars) | `index.ts` + `commander` | `OPENCODE_INSTALL_DIR`→`--dir`, `OPENCODE_BRANCH`→(obsolete; bundled), profile posit:onals→`--profile`. |
+
+**Obsolete-and-why summary:** `check_bash_version`, `check_dependencies` (no bash/curl/jq —
+Node runtime + `fetch` + native JSON), the interactive `show_*_menu` chain (Commander flags +
+non-interactive-first design; optional prompts are a nice-to-have, see Open Questions). Everything
+else has a living equivalent.
+
+### 3.1 `update.sh` parity
+
+| `update.sh` capability | CLI equivalent |
+|------------------------|----------------|
+| `get_global_install_path` / `normalize_path` / `resolve_install_dir` (CLI→env→local→global auto-detect) | `oac update` resolves target from manifest location; `--dir`/`--global`; `lib/paths.ts` |
+| `update_component` (backup, curl, restore-on-fail) | `installer.ts` `updateFiles` — sha256-gated, atomic per file, `.oac/backups/` |
+| `update_all_components` (find `*.md`/`*.ts`/`*.sh`, **skip node_modules**) | `listBundledFiles` (already excludes node_modules, #308) + manifest-driven set |
+| Blind overwrite-with-backup | **Superseded** by user-edit protection: user-modified files skipped unless `--yolo` |
+| `cleanup_backups` (trap) | `.oac/backups/{ts}/` retained (not auto-deleted) — user-recoverable; `oac` cleanup skill can prune |
+
+`oac update` is strictly safer than `update.sh`: it never clobbers a user's local edits by default,
+whereas `update.sh` overwrote every file and relied on a transient `.backup`.
+
+---
+
+## 4. Cross-platform distribution
+
+Core goal (from the task): **better cross-platform install via npm**, one system exporting to
+different AI tools.
+
+### 4.1 Runtime portability (THE blocker) — Node-first, Bun-optional
+
+**Problem:** `bin/oac.js` does `execFileSync('bun', …)` and the CLI uses `Bun.file`, `Bun.write`,
+`Bun.version`, `import.meta.dir`. A `npm i -g` user on Windows/macOS/Linux without Bun gets
+"Bun is required". That defeats npm distribution.
+
+**Decision:** ship a **Node-compatible build**. Two viable paths (pick in Open Questions):
+- **(A) Portability shim** — replace Bun APIs with a tiny `lib/fs.ts` abstraction over
+  `node:fs/promises` (`readFile`/`writeFile`/`mkdir -p`), `node:crypto` (already used in
+  `sha256.ts`), and `import.meta.url`→`fileURLToPath` for `__dirname`. Build with `tsc`/`esbuild`
+  targeting Node ≥18. `bin/oac.js` runs `node dist/index.js` directly — no Bun. **Recommended:**
+  smallest dependency surface, works everywhere npm works.
+- **(B) Bundle a runtime** — compile a standalone binary per platform (Bun `--compile`) and publish
+  via optional-deps or `postinstall`. Heavier, platform matrix to maintain, npm-global unfriendly.
+
+Either way: keep Bun as the **dev/test** runtime (`bun test` is fast), but the **published artifact
+must run on stock Node**. Root `package.json` already declares `engines.node >=18`.
+
+### 4.2 Install locations — local vs global (issues #321, #326)
+
+| Mode | Neutral source (`content/`) | Target output | State (`.oac/`) |
+|------|------------------------------|---------------|-----------------|
+| **Project-local** (default) | `<project>/content/` | `<project>/.opencode/`, `<project>/CLAUDE.md`, … | `<project>/.oac/` |
+| **Global** (`--global`) | `~/.config/oac/content/` | `~/.config/opencode/` (OpenCode global), tool-global dirs | `~/.config/oac/` |
+| **Custom** (`--dir <path>`) | `<path>/content/` | `<path>/…` | `<path>/.oac/` |
+
+- Default is **project-local** — the safe, no-surprise default. `--global`/`--dir` are explicit
+  opt-ins (mirrors `install.sh`'s location menu but flag-driven).
+- **No accidental overwrite (#321/#326):** every write is gated by the sha256 manifest — a file
+  the user edited is never silently replaced. Path normalization (`lib/paths.ts`) rejects paths that
+  escape the install root (`update.sh` already guarded `../` — preserve that: reject `..`/absolute
+  escapes before any write).
+- Global path today is `~/.config/opencode` on all platforms (from `get_global_install_path`).
+  Windows should additionally honor `%APPDATA%`/`%LOCALAPPDATA%` — flagged in Open Questions.
+
+### 4.3 Windows support (issues #304, #312)
+
+- **No shell scripts:** deleting `install.sh`/`update.sh` removes the bash-3.2/Git-Bash dependency
+  entirely — the #1 Windows friction. Pure Node CLI runs in PowerShell/CMD natively.
+- **Path handling:** use `node:path` throughout (never string-concat `/`); `normalizeInstallPath`
+  converts `\`→`/` internally and resolves drive-letter paths (`C:\…`). The bash version already
+  handled `^[A-Za-z]:` — the TS port must match.
+- **No color/emoji assumptions:** `ui/logger.ts` should degrade on terminals without color
+  (install.sh gated on `WT_SESSION`/`ConEmuPID`; `chalk` auto-detects TTY/`NO_COLOR`).
+- **Line endings:** JSON parsing is CRLF-safe natively (removes the `tr -d '\r'` hack in `jq_exec`).
+- **Case-insensitive FS:** manifest keys are stored as authored (POSIX-style relative paths); do not
+  assume case-sensitivity when checking existence.
+
+### 4.4 npx vs global install
+
+| Method | Command | Use case | Notes |
+|--------|---------|----------|-------|
+| **npx (no install)** | `npx @nextsystems/oac init` | one-off / trial / CI | Downloads on demand; needs Node ≥18; the modern replacement for `curl \| bash`. |
+| **Global** | `npm i -g @nextsystems/oac && oac init` | frequent users | `oac` on PATH; `oac doctor` checks for updates vs npm registry (already implemented). |
+| **Dev/local** | `bun run src/index.ts` or `npm link` | contributors | `OAC_PACKAGE_ROOT` env override lets the CLI find bundled `/content` in the monorepo. |
+
+Registry is **bundled**, so `npx` and global both work **offline after download** — no network at
+run time (unlike `install.sh`, which curled every file from `raw.githubusercontent.com`).
+
+### 4.5 Registry fetching, caching, offline
+
+- **Default: bundled + offline.** `registry.json` and `/content` ship inside the package
+  (`readRegistry` reads from `getPackageRoot()`). No network needed for `init/add/build/update`.
+- **Optional remote registry** (parity with `install.sh` `REGISTRY_URL`/`OPENCODE_BRANCH`): a
+  `--registry <url>` flag or `OAC_REGISTRY_URL` env can fetch a newer catalog via `fetch`. Cache it
+  under `~/.cache/oac/registry-<hash>.json` (or `%LOCALAPPDATA%` on Windows) with a TTL; fall back to
+  the bundled copy when offline. This subsumes `fetch_registry`'s `file://`/remote branching.
+- **`doctor`** already does a non-blocking npm-latest check with a 5s timeout and offline fallback —
+  the model for all network calls: **never block on the network**.
+
+### 4.6 Checksum / collision / update strategy (from install.sh, upgraded)
+
+| Concern | install.sh | CLI |
+|---------|-----------|-----|
+| Collision detect | filename existence scan (`perform_installation`) | **sha256 manifest** — knows if the existing file is OAC's unchanged copy or a user edit |
+| Strategy choice | interactive menu (skip/overwrite/backup/cancel) | default **skip user-modified**; `--yolo`=backup+overwrite; `config.autoBackup` |
+| Backup | `.opencode.backup.<timestamp>/` mirror | `.oac/backups/<ISO-timestamp>/<relpath>` |
+| Update safety | blind curl overwrite + `.backup`/restore | user edits never lost without `--yolo` |
+| Integrity | none | every tracked file has a sha256; `doctor` detects drift/corruption |
+
+### 4.7 Migration for existing `curl | bash` users
+
+Existing users have a project-local `.opencode/` installed by `install.sh` but **no `.oac/`
+manifest**. Migration path (details owned by Agent E; CLI responsibilities here):
+1. `oac init` (or a dedicated `oac migrate`) detects an OAC-shaped `.opencode/` without a manifest.
+2. It **adopts** the existing files: sha256-hashes each known OAC file and writes a manifest marking
+   files that match a bundled/known hash as `source: generated/bundled` and unknown ones as
+   `custom` (never touched). No overwrite without `--yolo`.
+3. Because the pre-refactor repo had `.opencode/` as *source*, migration also seeds `content/` from
+   the bundled neutral source so future `oac build` works — offering a diff if the user hand-edited
+   `.opencode/` agents.
+4. README swaps `curl … | bash` for `npx @nextsystems/oac init`; the old one-liner can print a
+   deprecation notice pointing at npm (kept for one release).
+
+---
+
+## 5. npm packaging
+
+### 5.1 What ships in the package
+
+Post-refactor `files` (root `package.json`) replaces the `.opencode/**` enumeration with:
+
+```jsonc
+"files": [
+  "content/",            // ← THE bundled neutral source of truth (agents, skills, commands, context)
+  "registry.json",       // component + profile + dependency catalog
+  "packages/cli/dist/",  // Node-runnable compiled CLI
+  "packages/core/dist/", // IR schema + parser + registry loader
+  "packages/adapters/dist/", // per-tool adapters used by `oac build`
+  "bin/",                // oac entrypoint (node, not bun)
+  "VERSION", "LICENSE", "README.md", "CHANGELOG.md"
+]
+// Explicitly NOT shipped: install.sh, update.sh, scripts/bridge/*, .opencode/ (now generated),
+// **/node_modules/  (#308)
+```
+
+- The current `files` list bundles `.opencode/**` and `install.sh` — both **removed**. `/content`
+  becomes the shipped asset.
+- `bundled.ts` `getPackageRoot()`/`listBundledFiles()` **retarget** from `.opencode/{agent,context,
+  skills}` to `content/{agents,skills,commands,context}`. The `registry.json`-absence heuristic used
+  to distinguish CLI-package-root from monorepo-root needs revisiting since `registry.json` now
+  ships **with** the package — replace the heuristic with an explicit `package.json` `name` check or
+  an `OAC_PACKAGE_ROOT`-style anchor file.
+- `node_modules` exclusion (#308) stays enforced both in `files` globs and in the runtime file
+  walker.
+
+### 5.2 Bundling pattern (`bundled.ts`)
+
+Keep the proven pattern: **assets travel inside the published tarball**, resolved at runtime by
+walking up from the CLI's own location to the package root, with `OAC_PACKAGE_ROOT` as the
+dev/monorepo override. This is why the CLI works offline. The only change is the **root directory**
+(`content/` not `.opencode/`) and the **root-detection anchor** (§5.1).
+
+### 5.3 Versioning
+
+- **Single version** for the whole product = root `package.json` `version` (currently `0.7.1`),
+  surfaced by `readCliVersion()` and written into `.oac/manifest.json` `oacVersion` on init/update.
+  `doctor` compares it to the npm-latest.
+- The `@nextsystems/oac-cli` sub-package version should track the root (or be dropped in favor of a
+  single published package — Open Question). `bump-version.sh` + `npm version` scripts already exist
+  and write `VERSION`; keep them, but ensure `/content`, adapters, and CLI ship as one coherent
+  version so a manifest's `oacVersion` unambiguously identifies the source that produced the output.
+- **Manifest schema version** (`manifest.version: "1"`) and **registry schema version**
+  (`registry.schema_version`) are independent of the product version and gate migrations.
+
+---
+
+## Open Questions
+
+1. **Runtime choice (§4.1):** ship a Node-portable build (option A, recommended) or per-platform
+   compiled binaries (option B)? This determines the whole packaging story and must be decided first.
+2. **Single vs. multi package:** publish one `@nextsystems/oac` (CLI + core + adapters + content) or
+   keep `@nextsystems/oac-cli` + `@openagents-control/compatibility-layer` separate? Affects `bin`,
+   `files`, and version coupling (§5.3).
+3. **Interactive prompts:** `install.sh` had rich menus. Do we ship any interactivity (e.g.
+   `@clack/prompts` for `oac init` when TTY), or stay strictly flag-driven + non-interactive? The
+   parity table treats menus as obsolete, but some users liked the guided flow.
+4. **Default target for `oac init`:** when no IDE is detected and no `--target` given, default to
+   `opencode` (assumed here) or prompt/refuse? Ties to Q3.
+5. **Global path on Windows (§4.2):** keep `~/.config/opencode` everywhere (current bash behavior),
+   or use `%APPDATA%`/`%LOCALAPPDATA%` on Windows? The latter is more native but diverges from
+   existing installs.
+6. **Remote registry (§4.5):** is a fetchable/newer-than-bundled registry actually needed, given
+   `/content` is bundled and versioned? If components can't be installed without their bundled
+   source, a remote registry pointing at unbundled components is meaningless — likely drop it and
+   keep registry fully bundled.
+7. **`apply` deprecation window:** how long is `oac apply` kept as an alias for
+   `oac build --target`? (Coordinate with Agent E's migration timeline.)
+8. **Backup retention:** `.oac/backups/` grows unbounded (never auto-pruned, unlike `update.sh`'s
+   trap cleanup). Add a retention policy / `oac clean` command, or leave to the existing cleanup skill?
+9. **`content/` on disk vs. build-only:** does the user keep an editable `content/` in their project
+   (source they can customize, then `oac build`), or is `/content` purely internal to the package and
+   only targets land in the project? This is the biggest UX fork and affects `init`, `add`, `update`,
+   and migration (§4.7). Needs alignment with Agent A/B.
+```

+ 393 - 0
docs/architecture/canonical-refactor/05-impact-migration-tests.md

@@ -0,0 +1,393 @@
+# 05 — Impact Analysis, Migration Staging & Test Spec
+
+> **Owner:** Agent E
+> **Status:** Spec only — no implementation code.
+> **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/` (**160** agent `.md` files under `agent/`) | `find .opencode -path '*agent*' -name '*.md'` |
+| Claude Code output today | `plugins/claude-code/` (**7** agents) — badly drifted | `find plugins/claude-code -path '*agents*' -name '*.md'` |
+| 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 (or diff-explained) to hand-maintained; **evals smoke-test
+  still passes** against generated output.
+- **Release:** minor bump; `oac build` is a real new capability.
+
+### Stage 3 — Migrate all content into `/content`; retire the bridge *(the big sweep, staged)*
+- **Goal:** move all 160 agents + skills/commands/context into `/content`; `oac build`
+  reproduces the full `.opencode/` + full CC plugin.
+- **Deliverable:** a one-shot migration script (`content/*` from current `.opencode/*`);
+  delete `scripts/bridge/sync-to-claude.sh`; `plugins/claude-code/` now 100% generated (fixes
+  the 7-vs-160 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** via existing marketplace update.
+- **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).
+- **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 the **known `temperature`-dropped warning**
+   is emitted (from `CapabilityMatrix`), not silent.
+
+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 + one expected
+warning string. 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).
+  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-160
+     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.)

+ 1001 - 0
docs/architecture/canonical-refactor/06-REVIEW.md

@@ -0,0 +1,1001 @@
+# 06 — Adversarial Cold Review
+
+> **Reviewer:** independent; no involvement in the conversation that produced `00`–`05`.
+> **Method:** every load-bearing claim re-derived from the working tree. Where I could not
+> verify, I say "unverifiable" rather than guess.
+> **Scope:** `00-INDEX.md` … `05-impact-migration-tests.md`, all read in full.
+
+---
+
+## Verdict
+
+**Not implementable as written.** The architecture is sound and the analysis is unusually good
+— `01` in particular is the most carefully verified document I have reviewed in this repo, and
+Option A is the right call for the right reasons. But the spec set cannot be handed to an
+implementer today, for three reasons that are structural rather than cosmetic. **First, the
+centerpiece schema rejects the corpus it was written for:** `02`'s `DependencyKindSchema` omits
+the `agent`, `plugin`, and `config` kinds that `registry.json` actually uses, and its
+`DependencyInputSchema` regex rejects **19 real dependency refs** — including every wildcard
+(`context:core/*`) that `01` calls a crown-jewel feature. `05`'s Layer-1 test ("every
+`/content/*` file parses") would fail on day one. **Second, `01` and `05` were never revised
+after the v2 decisions landed, and the index's table marks them "✅ v1" as though that were a
+status rather than a defect.** `05` still says the repo has **160 agents** — four times, in its
+ground-truth table, in its Stage 3 deliverable, and twice in its test spec — a number the index
+itself explicitly flags as false. `01` still carries the committed-`.env` security alarm the
+index retracted as a non-finding. **Third, the single hardest task in the migration — merging
+`.opencode/` and `plugins/claude-code/` into `/content/` — is owned by nobody.** The index
+declares it a merge; `05` Stage 3 specifies a "one-shot migration script (`content/*` from
+current `.opencode/*`)", which is exactly the copy the index forbids. For `code-reviewer` the
+two source bodies are **108 vs 269 lines with entirely different descriptions**, and no document
+states which wins. Separately, the precedence decision the index calls "CONFIRMED — question
+closed" rests on three in-repo documents this project wrote about itself, none of which is
+OpenCode's resolver; that is an overclaim on the most load-bearing semantic in the design. Fix
+the schema, revise `01`/`05`, and assign the merge an owner, and this becomes buildable — the
+bones are good.
+
+---
+
+## Falsified or Unverifiable Claims
+
+Ranked by severity. Every command below is reproducible from the repo root.
+
+### F1 🔴 `05`'s "160 agents" — false, uncorrected, and load-bearing on a deliverable
+
+`05:22` states the source of truth is `.opencode/` with **160** agent `.md` files, citing
+`find .opencode -path '*agent*' -name '*.md'`.
+
+```
+$ find .opencode -path '*agent*' -name '*.md' | wc -l
+160
+$ find .opencode/agent -name '*.md' | wc -l
+34
+```
+
+The command reproduces, but the **interpretation is wrong**: `-path '*agent*'` matches
+`.opencode/context/open**agent**s-repo/**` (23 files in `guides/` alone), and
+`.opencode/prompts/core/open**agent**/` (8 files). The real count is **34**, exactly as
+`00-INDEX.md:73` says — and the index explicitly calls out *"one spec said 160 — false."*
+
+This is not a stray typo. `160` propagates into work:
+- `05:22` ground-truth table
+- `05:220` Stage 3 goal — *"move all **160** agents + skills/commands/context into `/content`"*
+- `05:222` — *"fixes the 7-vs-**160** drift"*
+- `05:343` Layer 4 test case 1 — *"guards the 7-vs-**160** drift regression directly"*
+
+A Stage-3 deliverable and a test assertion are both scoped to a number that is **4.7× reality**.
+`05` is marked "✅ v1" in the index's spec table (`00:372`) and was never revised.
+
+### F2 🔴 `02`'s dependency schema rejects 19 real refs — the schema does not fit the corpus
+
+`02:369-383` defines:
+
+```ts
+export const DependencyKindSchema = z.enum(["subagent","context","command","skill","tool"]);
+export const DependencyInputSchema = z.union([
+  DependencyRefSchema,
+  z.string().regex(/^(subagent|context|command|skill|tool):[a-z0-9-]+$/),
+]);
+```
+
+and comments that this is *"the **REAL** on-disk format in both `.opencode/config/agent-metadata.json`
+and `registry.json`."* It is not. Measured against `registry.json`:
+
+```
+refs REJECTED by 02 DependencyInputSchema regex: 19
+context:core/context-system/*     context:core/*              context:ui/*
+context:development/*             context:openagents-repo/*   context:project-intelligence/*
+agent:openagent                   agent:opencoder             agent:system-builder
+agent:copywriter                  agent:technical-writer      agent:data-analyst
+agent:eval-runner                 agent:repo-manager          plugin:notify
+config:env-example                config:agent-metadata       config:readme
+```
+
+Three independent defects:
+1. **`agent:` is missing from the kind enum entirely.** The IR cannot express a dependency on a
+   primary agent, though `registry.json` and all five profiles use it.
+2. **`plugin:` and `config:` are missing**, though `plugin:notify`, `config:agent-metadata`, and
+   `config:readme` are live registry refs (`01` §10 discusses `config:readme` at length).
+3. **Wildcards are rejected by the regex.** `context:core/*` is the mechanism `01` §7.2 documents
+   as `expand_context_wildcard` and the preservation checklist requires (`01:1244`: *"`context:`
+   wildcard expansion preserved"*). `02`'s schema makes it unrepresentable.
+
+`02` contradicts **itself** here: `02:799-801` `ComponentTypeSchema` has **nine** types
+(`agent, subagent, command, skill, context, tool, hook, plugin, config`) while
+`DependencyKindSchema` has **five**. The same document cannot agree on the type vocabulary.
+
+Consequence: `05`'s Layer 1 ("Every `/content/*` file parses against the Zod IR", `05:317`) fails
+at Stage 1 on real content.
+
+### F3 🔴 The precedence decision is "confirmed" against sources that cannot confirm it
+
+`00:219-229` declares last-match-wins **"✅ CONFIRMED (v2.1) — question closed"**, *"Verified
+against three independent in-repo sources"*, concluding: *"**No further verification against
+OpenCode's resolver is required.**"*
+
+I verified all three citations. Two are accurate as quotes; one has a wrong line number; **and
+the claim of independence does not hold.**
+
+| Cited source | Verified? | What it actually is |
+|---|---|---|
+| `docs/planning/12-MASTER-SYNTHESIS.md:432` | ✅ text at :432 | **This project's own planning doc**, describing a *different, rejected* format |
+| `.opencode/context/openagents-repo/standards/permission-patterns.md:11` | ✅ verbatim | **This project's own context file** |
+| `.opencode/context/openagents-repo/standards/agent-frontmatter.md:45` | ✅ verbatim | **This project's own context file** |
+
+All three are documents **this repository wrote about itself**. None is OpenCode's resolver,
+OpenCode's upstream documentation, or an executed test. Three in-repo files agreeing is one
+belief written down three times — not three independent verifications.
+
+Worse, the `12-MASTER-SYNTHESIS.md:432` source describes a **format that does not exist**:
+
+```json
+"permission": [
+  { "deny": "bash(**)" },
+  { "allow": "bash(git status)" }
+]
+```
+
+That is a JSON array of `{decision: pattern}` objects — a *proposal* from a planning doc, not
+OpenCode's real map form (`{"*": "deny", "git status*": "allow"}`). A doc asserting last-match-wins
+about its own hypothetical schema is not evidence about OpenCode's behavior.
+
+**I am not claiming last-match-wins is wrong.** The reasoning in `02` §1.2.4 and `03` §0.5 —
+that first-match-wins provably breaks `coder-agent` and `openagent` as authored — is sound and
+I verified both agent files reproduce exactly as quoted. Last-match-wins is almost certainly
+right. **The defect is the epistemics:** the single most load-bearing semantic in the design is
+marked "closed, no further verification required" on the strength of self-citation. The cost of
+being wrong is every generated permission block on every target inverting. A ten-minute
+experiment against a real OpenCode install would settle it. Do that before freezing.
+
+Also: `03:129` cites this quote at **`:442`**; the index cites **`:432`**. `:432` is correct.
+
+### F4 🟠 The index's priority distribution is wrong — and so is `02`'s
+
+`00:66-67` asserts: *"Verified distribution: high 116, critical 113, low 34, medium 31,
+reference 1."* Those sum to **295**, which matches no count in any document.
+
+`02:640-648` asserts: high 113, critical 111, low 34, medium 29, reference 1 (**= 288**).
+
+Measured over the leading-window markers:
+
+```
+$ for f in $(find .opencode/context -name '*.md'); do head -1 "$f" | grep -o 'Priority: *[a-z]*'; done \
+    | sed 's/Priority: *//' | sort | uniq -c | sort -rn
+ 112 high
+ 111 critical
+  34 low
+  29 medium
+   1 reference
+```
+
+**112 / 111 / 34 / 29 / 1 = 287** — which exactly matches the 287 line-1 markers `02` §4.1
+independently derived. `02` is right on four of five values and wrong on `high`; the index is
+wrong on three of five and its total is internally inconsistent. Since the index "wins on
+conflict" (`00:9`), an implementer following the rules adopts the **more wrong** numbers.
+
+The `Priority: reference` outlier is **real and verified** — that finding stands.
+
+### F5 🟠 `01`'s navigation counts are inflated by a suffix-match bug
+
+`01:518` — *"**79 `navigation.md` files** — one per directory, at every level"*, and `01:651-652`
+tabulates 79 on disk / 36 in registry.
+
+```
+$ find .opencode/context -name 'navigation.md' | wc -l
+76
+$ find .opencode/context -name '*navigation.md' | wc -l
+79
+$ find .opencode/context -name '*navigation.md' ! -name 'navigation.md'
+.opencode/context/development/fullstack-navigation.md
+.opencode/context/development/ui-navigation.md
+.opencode/context/development/backend-navigation.md
+```
+
+The methodology matched `*navigation.md` and swept in three files that are **not** navigation
+hubs. Registry side is identically inflated: **33** exact-basename entries, 36 by suffix.
+
+The derived claim *"43 of 79 navigation hubs are unregistered"* (`01:655`) survives only because
+the errors cancel (76−33 = 79−36 = 43). The **conclusion is right; the evidence is wrong** — and
+the "one per directory, at every level" characterization is built on the bad number. Note the
+interaction: `ui-navigation` is *also* one of the three duplicate registry ids `01` §3.6 flags,
+so the bug and the finding overlap.
+
+### F6 🟠 `01` still asserts the committed-`.env` security alarm the index retracted
+
+`00:80-82` — *"**Non-finding (corrected):** an earlier claim of 'committed `.env` files needing
+rotation' is **false**. Verified: **0 tracked `.env` files**."* I confirm:
+
+```
+$ git ls-files | grep -E '\.env$|\.env\.'
+(no output)
+$ sed -n '9,13p' .gitignore
+.env
+.env.local
+...
+```
+
+But `01` was never updated and still says:
+- `01:823` — `` `.opencode/tool/.env` | 🔴 **SECURITY** | A committed `.env` inside the tool dir ``
+- `01:842` — `` `.opencode/plugin/.env` | 🔴 **SECURITY** | Second committed `.env` ``
+- `01:1429-1431` **Q17** — *"`.opencode/tool/.env` and `.opencode/plugin/.env` are **in the repo**. Are they real secrets (⇒ rotate + purge history)…? Needs a human to look."*
+
+Both files exist **on disk** but are untracked and gitignored. Q17 asks a human to consider
+history purging for a non-incident. The index wins, so **Q17 is dead** — but nothing in the doc
+set says so, and a reader of `01` alone would open a security workstream.
+
+### F7 🟡 Index skill counts are wrong, and the error propagated into `02` and `03`
+
+`00:76-77` — *"Skills (OpenCode) | **2** dirs"* and *"Skills (Claude Code plugin) | **~11**"*.
+
+```
+$ ls -d .opencode/skills/*/     → 4  (context-manager, context7, smart-router-skill, task-management)
+$ ls -d .opencode/skill/*/      → 2  (project-orchestration, task-management)
+$ ls -d plugins/claude-code/skills/*/ | wc -l → 12
+```
+
+OpenCode has **4 + 2 = 6** skill directories across two sibling trees; CC has **12**, not ~11.
+`01` §0 gets this exactly right. The index — which overrides `01` on conflict — gets it wrong,
+and both downstream docs inherited it: `02:479` (*"OpenCode has **2** skill dirs, the CC plugin
+has **~11**"*) and `03:48` (*"2 OpenCode skills, ~11 CC plugin skills"*). Three of five documents
+now carry the wrong number because the authoritative one did.
+
+### F8 🟡 "The shipped CC agents hardcode `model: sonnet`" — 2 of 7 are `haiku`
+
+`00:158` and `01:255` both state the CC agents hardcode `model: sonnet`.
+
+```
+$ grep -H '^model:' plugins/claude-code/agents/*.md
+code-reviewer.md:model: sonnet      coder-agent.md:model: sonnet
+context-scout.md:model: haiku       external-scout.md:model: haiku
+task-manager.md:model: sonnet       context-manager.md:model: sonnet
+test-engineer.md:model: sonnet
+```
+
+Five `sonnet`, **two `haiku`**. This is not pedantry: the two `haiku` agents are the *scouts* —
+cheap, high-frequency, read-only discovery agents. That is a deliberate **cost/latency tier**,
+not an accidental hardcode. Locked decision #2 (`model: null`) deletes it and **no document
+proposes a replacement**. `01`'s Q3 asks about "a per-adapter default", which cannot express
+"this agent is cheap and that one isn't". Both docs characterize the finding as a uniform,
+trivially-droppable constant, which understates what is being thrown away. See **L4**.
+
+### F9 🟡 Context census: all three numbers are wrong about composition
+
+- Index (`00:75`) + `03:47`: **296** `.md` (286 HTML-comment / 3 YAML / 7 neither)
+- `02:40` + §4.1: **297** `.md` (287 line-1 / 7 elsewhere / 3 no-marker)
+- `01:38`: **300** files (297 `.md` + `paths.json` + 2 JSON schemas)
+
+Measured:
+
+```
+$ find .opencode/context -name '*.md' | wc -l           → 297
+$ find .opencode/context -type f -name '*.md' | wc -l   → 294   ← note the delta
+$ find .opencode/context -type f ! -name '*.md' | wc -l → 3
+```
+
+**297 `.md` path entries, but only 294 regular files.** The other three are **symlinks** (see
+**U1**). `02`'s 297 and `01`'s 300 are right as path counts; the index's 296 is wrong and its
+`286/3/7` bucket split is superseded by `02`'s verified `287/7/3`, which I reproduce exactly.
+`02` §4.1's parser analysis — including the line-232 and line-301 prose-marker traps and the
+dual-format files — is **correct and is the best analysis in the doc set**. `02`'s own Q4 flags
+the delta as "no design impact"; that is right for the delta, wrong for the composition.
+
+Also `01:1206`: *"All 300 context files migrated (297 `.md` + `paths.json` + 2 task schemas)"* —
+arithmetically fine, but it means **294 real files + 3 symlinks + 3 JSON**.
+
+### F10 🟡 `01`'s unregistered-context count is off by two
+
+`01:649` claims **112** on-disk-but-unregistered. Measured: **110** (`.md` on disk, not matching
+any registry `path`). `01`'s companion claims verify exactly: registry has **191** entries /
+**188** unique paths ✅, and **0** registry entries point at a missing `.md` ✅ (the single
+"missing" path is `paths.json`, which is the non-`.md` special case `01` correctly documents).
+
+### Claims I verified as CORRECT (so they are not relitigated)
+
+Both index-level and doc-level. This list matters as much as the falsifications:
+
+| Claim | Status |
+|---|---|
+| 34 agents, 20 commands | ✅ exact |
+| `coder-agent.md` / `reviewer.md` / `openagent.md` permission blocks quoted verbatim | ✅ byte-exact |
+| `types.ts:116` `z.union([z.string(), z.string()])` no-op union | ✅ exact line |
+| `BaseAdapter.ts:187-208` warning templates | ✅ exact strings |
+| `WindsurfAdapter.ts:511-516` `hasAllow = permObj.allow !== undefined`, **fails closed** | ✅ — the v2 correction is right |
+| `ClaudeAdapter.ts:95-124` writes `.claude/agents/*.md` + `.claude/config.json` (project, not plugin layout) | ✅ |
+| `CapabilityMatrix.ts`: `agentModes: claude=full`, `agentCategories: claude=partial`, `pathPatterns: windsurf=partial`, `taskDelegation: claude=full` | ✅ all four; `03` §5's corrections are justified |
+| `bin/oac.js` `execFileSync('bun', …)` → *"Bun is required"* | ✅ verbatim |
+| 15 files using Bun-only APIs | ✅ exactly 15 |
+| Version drift: 0.7.1 / 0.7.1 / 1.0.0 / 0.1.0 / 0.1.1 / 1.0.0 / **1.0.2** | ✅ all seven |
+| `install.sh` 1510 lines, 35 functions; `get_registry_key():331`, `get_install_path():353`, `expand_context_wildcard():361`, `expand_selected_components():373`, `resolve_dependencies():420` | ✅ every line number |
+| `add.ts` never resolves dependencies (zero references) | ✅ |
+| `/add-context`'s 3 path-style context deps all resolve to **nothing** | ✅ verified dead |
+| Registry: 245 entries (8/19/17/2/1/4/191/3); dupe ids `agents`, `ui-navigation`, `context-bundle-template`; orphan top-level `subagents` key; `version` 2.0.0 vs `metadata.schemaVersion` 1.0.0 | ✅ all |
+| Profiles 25/41/25/50/68; `developer` badge `RECOMMENDED`; `advanced.additionalPaths = [".Building/", ".github/workflows/"]` | ✅ all |
+| `agent-metadata.json`: 28 entries, 8 agent / 20 subagent | ✅ |
+| CC `coder-agent` ships `tools: Read, Write, Edit, Glob, Grep` — **no `disallowedTools`** ⇒ live Edit grant, security globs gone | ✅ **the LIVE SECURITY GAP (index #10) is real** |
+| `bundled.ts` uses `registry.json`-absence as the package-root heuristic | ✅ (and see **G4**) |
+| `sync-to-claude.sh` = 39 lines | ✅ |
+| `apply.ts` cursor guard `warn: 80*1024, limit: 100*1024` | ✅ |
+| `session-start.sh` + `escape_for_json()` + *"SECURITY: Prevents command injection"* | ✅ exists, `hooks/` only |
+| `.opencode/config.json` = `{"agent": "eval-runner"}` | ✅ |
+| All `router.sh` are `0755` | ✅ |
+
+### Unverifiable
+
+- **`03` §2.3/§2.5/§2.6 CC-documentation quotes** (*"plugin subagents don't support `hooks`…"*,
+  *"only the `agent` and `subagentStatusLine` keys are supported"*, `tools:` is names-only). These
+  cite `sub-agents.md` / `plugins.md` / `permissions.md` — external docs not in this repo. I cannot
+  verify them here. `03` correctly self-labels confidence (high on the doc statement, medium on
+  applicability) — **that is the right way to write an unverified claim** and the rest of the set
+  should copy it. But note the **entire CC adapter design rests on them**, and `00:352-360`
+  promotes them to a locked "✅ question CLOSED, negative" without preserving that hedge.
+- **`03` §4 Windsurf format** — explicitly self-labelled *"Inferred, not verified against a live
+  install."* Honest, and correspondingly Q9 is a genuine blocker for that target.
+- **`00:161-163`** — `settings.json {"model": "opusplan"}` no-op. Correctly flagged *"Unverified …
+  flagged, not asserted."*
+
+---
+
+## Cross-Document Contradictions
+
+### C1 🔴 Three documents argue a question the index has already closed
+
+`00:219` marks precedence **"✅ CONFIRMED (v2.1) — question closed."** Yet:
+
+- `02:881` Open Question 1 — *"**⚠️ Needs ratification.** The index says 'first-match-wins'…"*
+- `03:96` §0.5 — *"**⚠️ BLOCKING CONTRADICTION** — first-match-wins vs last-match-wins"*
+- `03:836` Q1 — *"**BLOCKING** … must be resolved before `02` or `03` freeze"*
+
+`02` and `03` are arguing against an **index version that no longer exists**. They quote the
+index as saying "first-match-wins"; the current index says the opposite, at length, and credits
+*"Two agents reached this conclusion independently"* — i.e. it absorbed `02`'s and `03`'s
+arguments and then never told them. An implementer reading `03` sees a **blocking** contradiction
+that is resolved two files away. **Same pattern, same cause** for `03` Q7 (*"warning count: 1 or 2?
+Recommend accepting 2 and correcting the index"*) — `00:346-350` already says 2. And `00:352`
+already closes the `settings.json` question `03` §2.6 re-derives.
+
+Three of the ~60 open questions are **not open**. They are stale citations. Not harmless: `03`
+Q1 is labelled BLOCKING and would stop an implementer cold.
+
+### C2 🔴 `05` Stage 3 specifies exactly the copy the index forbids
+
+`00:102-107` finding #3 — *"**Seeding `/content/` is a MERGE, not a copy.** Drift is bidirectional
+… **Seeding from `.opencode/` alone destroys all of it.**"*
+
+`05:221` Stage 3 deliverable — *"a one-shot migration script (`content/*` from current
+`.opencode/*`)"*.
+
+That is the copy. `05` names no merge, no CC-side harvest, no conflict rule — and its Stage 3
+goal (`05:220`) is *"move all 160 agents"* (F1), i.e. it is not even scoped to the right tree.
+Under `05` as written, the 12 CC skills, the 6 CC-only commands, every `<example>` block, and
+`session-start.sh` are **destroyed at Stage 3** — precisely the outcome the index's #1 finding
+exists to prevent. See **L1**.
+
+### C3 🔴 `05`'s minimal test contradicts `02`'s schema and `03`'s transform
+
+`05:328` Layer 1 case 2: *"`model:` set to a string (not `null`) → **rejected** (enforces locked
+decision #2)."*
+
+But `02:330-332`: `model: z.string().nullable().default(null)` — **accepts strings**, and the
+comment says *"When set, a neutral family id (e.g. `"claude-sonnet-4"`); ModelMapper resolves to
+the tool's dated id."* And `03:210-211` / `03:352-353` both specify `inference.model` set →
+emit `model:`.
+
+So `05` tests for a rejection that `02` does not implement and `03` explicitly relies on. This
+is `02`'s Q3 (*"Should `model` be authorable at all?"*) — **unresolved**, and `05` has already
+assumed one answer while `02`/`03` assume the other. `00:22-23` admits the question is open.
+Three documents, three positions, one field.
+
+### C4 🟠 `05`'s golden test asserts the wrong warning count
+
+`05:300` — *"the **known `temperature`-dropped warning** is emitted"* (singular).
+
+`03` §2.8 and `00:346` both say `code-reviewer` on CC emits **2** warnings (temperature +
+scoped-delegate). If the minimal test — *"the only test that must exist before any deletion"*
+(`05:287`) — asserts one warning, it either fails on correct output or is written loosely
+enough to prove nothing.
+
+### C5 🟠 `04` conflates two different `/content` directories, and admits it in Q9
+
+`00:168` puts `/content` at the **OAC repo root** — the source the maintainers author, which
+ships inside the npm package.
+
+`04:361-363` puts `content/` in the **user's project** (`<project>/content/`, `~/.config/oac/content/`),
+tracked in the user's manifest, updated by `oac update`.
+
+These are different artifacts with different lifecycles, and `04` uses one name for both. `04`'s
+own Q9 (`04:509`) says the quiet part out loud: *"does the user keep an editable `content/` in
+their project … or is `/content` purely internal to the package? **This is the biggest UX fork**
+and affects `init`, `add`, `update`, and migration."* It is not a UX fork — it is a
+**prerequisite**. It decides whether `oac build` runs on the user's machine at all, and therefore
+whether the CLI ships a parser, the adapters, and the whole IR, or just pre-built outputs. §1.1,
+§1.2, §1.4, §2, §4.2 and §5.1 are all written as though it were settled in opposite directions.
+
+### C6 🟠 `01` §1.3 recommends a schema `02` did not adopt — with different field names
+
+`01:200-208` recommends the rule list as `{ pattern, effect }`:
+
+```yaml
+capabilities:
+  bash:
+    - { pattern: "*", effect: ask }
+```
+
+`00:209-211` and `02:155-158` adopt `{ scope, decision }`. Same concept, different keys. `01` is
+the preservation checklist an implementer will grep against; it is now describing a schema that
+does not exist. `01:1172` (`permission.<tool>.<glob>` tri-state → `capabilities` rule list) is
+still right; §1.3's worked YAML is not.
+
+### C7 🟡 The index and `01` disagree on skill counts; `02`/`03` follow the index
+
+Covered in **F7**. `01` is right (4+2 / 12); the authoritative index is wrong (2 / ~11); `02:479`
+and `03:48` inherited the index's error. Since the index wins on conflict, the rule as written
+propagates the falsehood.
+
+### C8 🟡 `03` §0.4 escalates to `02` a question `02` never answers
+
+`03:88-94` — *"**Implicit default** — when *no* `*` rule exists. ⚠️ **The IR does not currently
+define this** … Escalated as Open Question #Q2. **Every collapse rule below depends on it.**"*
+
+`02` does not define it. `02` §1.2.5 defines only two cases — *capability absent* → `[]` → tool
+default; *rules present, none match* → `allow`. Neither covers "rules present, all match a subset,
+no `*` rule" — which is `coder-agent.edit` (five denies, no `*`), the exact case that produces the
+**live security gap** (index #10). `03` proposes the inference rule (*"opposite of the decisions
+present; mixed-without-`*` is an error"*); `02` never adopts it; the index never rules. So the
+highest-severity finding in the whole set (`00:135-142`) depends on a rule **no document owns**.
+
+### C9 🟡 `02` §8 marks `hooks` a "blocking" hint; `03` treats it as a warning
+
+`02:868` — `hooks | hint (**blocking**) | cursor/windsurf (none)`, and `02:870` says `delegate`
+on Cursor is *"a hard drop → **blocker**, not a soft warning."*
+`03:680` and `03:764` emit both as ordinary `⚠️` warnings; `03:68` says *"Builds fail only on
+`blockers`."* So `oac build --target cursor` either fails on every delegating agent (`02`) or
+warns and proceeds (`03`). Nobody reconciled it, and it changes whether Cursor is a supported
+target at all.
+
+### C10 🟡 `03`'s citation drift
+
+`03:129` cites `docs/planning/12-MASTER-SYNTHESIS.md:442`; the index cites `:432`; `:432` is
+correct (verified). Minor on its own, but it is the same quote used to close the most important
+question in the set (F3), and two of the docs citing it disagree on where it is.
+
+---
+
+## Feature Loss Risks
+
+Testing `01` §11's checklist against `02`'s schema and `03`'s transforms. Items with **no home**:
+
+### L1 🔴 The merge itself has no owner, no rule, and no spec
+
+`01`'s checklist demands harvesting from the CC side:
+- `01:1179` — *"**CC `<example>` blocks harvested** from `plugins/claude-code/agents/`"*
+- `01:1275` — *"🔴 **12 CC skills harvested**"*
+- `01:1203` — *"**6 CC-only commands** … harvested"*
+- `01:1307-1313` — all six `session-start.sh` capabilities
+
+`02` models the *result* (`examples[]`, `SkillSchema`) but explicitly disclaims the *process*
+(`02:396` — *"a merge concern for **Agent E**"*). `03` says `session-start.sh` *"must be preserved
+into `/content/`"* (`03:460`) and moves on. **Agent E's document (`05`) specifies a copy from
+`.opencode/`** (C2). The merge is passed to Agent E, and Agent E did not receive it.
+
+This is not a paperwork gap. The concrete unanswered question:
+
+```
+$ wc -l .opencode/agent/subagents/code/reviewer.md plugins/claude-code/agents/code-reviewer.md
+     108 .opencode/agent/subagents/code/reviewer.md
+     269 plugins/claude-code/agents/code-reviewer.md
+
+OpenCode description: "Code review, security, and quality assurance agent"
+CC description:       "Review code for security vulnerabilities, correctness, and quality.
+                       Use after implementation is complete and before committing."
+```
+
+**Two source files, 108 vs 269 lines, entirely different descriptions, both claiming to be
+`code-reviewer`.** Which body becomes `/content/agents/code-reviewer.md`? No document says.
+Seven agents are in this position.
+
+Tellingly, `00`'s worked example (`00:275`) uses the **CC description** — while `01:80` presents
+the **OpenCode description** as *"verified verbatim"* for the same agent. The index has silently
+already made a merge decision for one field of one agent, and did not record it as a decision.
+
+### L2 🔴 There is no way to say "this component does not apply to this target"
+
+No schema in `02` — Agent, Skill, Command, Context, Tool, Hook, Registry entry — has a `targets:`
+or applicability field. Everything is implicitly universal. But `01` documents whole classes that
+are structurally single-target:
+
+- **Tools** (`01` §5) implement the OpenCode tool API; CC has no equivalent primitive (`01` Q18).
+- **The 12 CC skills** carry CC-only frontmatter (`context: fork`, `agent:` — `03:441-443`) and
+  advertise as `oac:` commands. On an OpenCode build they are inert.
+- **The 4+2 OpenCode skills** shell out to Bun `.ts` via `router.sh` (`01` §4.4). On a CC build
+  they need a Bun runtime the CC user never installed.
+- **`session-start.sh`** is a CC hook with no OpenCode equivalent (`00:104`).
+
+`01`'s Q16 asks *which set seeds `/content/skills/`* and offers "the union of 16". If the answer
+is the union, then `oac build --target opencode` emits 12 skills OpenCode cannot use and
+`--target claude` emits 6 that CC cannot run — and `03`'s only lever is a per-agent, per-target
+warning, which turns "never silent" into unreadable noise. The union is the right answer **and it
+requires a schema field that does not exist.**
+
+### L3 🔴 Path-bearing permission scopes are neither tokenized nor rewritten
+
+`coder-agent`'s bash allowlist (verified verbatim):
+
+```yaml
+"bash .opencode/skills/task-management/router.sh complete*": "allow"
+```
+
+The scope string **hardcodes a build-output path into neutral content**. `01` §3.4 catches this
+class of problem (~129 files) and Q12 recommends `{{CONTEXT_ROOT}}`-style tokens + a
+`no raw .opencode/ in /content/` lint. But:
+
+- `02`'s `RuleSchema.scope = z.string().min(1)` — no token concept, no validation.
+- `03` §1.3 serializes scopes **verbatim** (`"<scope>": "<decision>"`); `03` §2.3 drops them.
+- `04` §2.4 specifies path rewriting for `@.opencode/context/` refs on `--global`/`--dir`
+  installs — **and only for context refs.**
+
+So on a global install, `coder-agent`'s allowlist still points at `.opencode/skills/...`, which is
+not where the skill landed. The two `router.sh` commands are denied. The agent cannot complete or
+report task status — **the exact breakage Option A exists to prevent** (`00:38-39`), reintroduced
+by the install-location feature. `01` catches the path problem in *bodies*; nobody catches it in
+*scopes*.
+
+Compounding: the CC skill set is **disjoint** — there is no `task-management` skill in
+`plugins/claude-code/skills/`. So on the CC target the scope references a skill that does not
+exist in the output at all. It is invisible today only because `03` fail-closes and drops `Bash`.
+
+### L4 🟠 Per-agent model *tiering* is destroyed with no replacement
+
+Per F8: `context-scout` and `external-scout` ship `model: haiku`; the other five ship `sonnet`.
+That encodes "scouts are cheap and fast; coders are capable." Under `model: null` (`00:20-23`),
+**every agent gets the same default** and the tiering is gone. The user pays sonnet rates for
+every discovery call.
+
+`01`'s Q3 offers (a) never emit `model:` or (b) *"a **per-adapter** default in adapter config"*.
+Neither expresses per-*agent* intent. `02`'s Q3 pushes the other way — *drop `model` from the
+authored schema entirely*. Nobody has noticed that the thing being deleted is a **two-tier cost
+policy**, not a hardcoded constant.
+
+A `tier: fast | balanced | capable` field would preserve the intent without naming a model — and
+would honor decision #2's actual spirit (no *hardcoded* models) rather than its letter. Not
+proposed anywhere.
+
+### L5 🟠 `oac build` cannot express `install.sh`'s `additionalPaths`
+
+`registry.json#profiles.advanced.additionalPaths = [".Building/", ".github/workflows/"]`
+(verified). `01:1268` requires it *"preserved or replaced"*; `04` §2.2 goes further and promises
+a **parity improvement** (*"`install.sh` only printed these … the CLI must actually copy them"*).
+
+But `02`'s `RegistrySchema` (`02:826-834`) has **no `profiles` object at all** — only
+`components`. Profiles exist in `02` solely as a `ProfileSchema` enum *tag on each entry*
+(`02:820`). Under that model there is nowhere to put `additionalPaths`, nowhere for a profile
+`name`/`description`, and nowhere for `badge: "RECOMMENDED"` (`01:1267`). Three checklist items
+have no home, and `04` §2.2 reads `profiles.*.components[]` — a shape `02` deleted.
+
+### L6 🟠 `0-category.json` `icon`/`order`/`status` have no schema
+
+`01` §1.6 recommends promoting these into `registry.json#categories[]` *"upgraded to objects"*,
+and `01:1192` puts it on the checklist. `03` §1.5 keeps *emitting* `0-category.json` on the
+OpenCode target. But `02`'s `RegistrySchema` has **no `categories` key at all** — the field is
+simply absent. So the OpenCode adapter is specified to emit a file whose source data the IR
+cannot hold.
+
+### L7 🟡 `aliases[]` is required by `01`, absent from `02`
+
+`01:167` — *"The neutral schema needs an explicit `id` **plus** an `aliases[]` field"*;
+`01:1218` and `01:1255` both require it; `01` §7.2 documents `resolve_dependencies` matching
+`.id == id or (.aliases // []) | index(id)`; three registry entries use it today.
+
+`02`'s `RegistryEntrySchema` (`02:803-824`) has **no `aliases`**. `02` §1.8 `AgentSchema` has no
+`aliases`. The `tester`/`test-engineer`/`TestEngineer` problem `01` §1.2 calls *"highest-risk
+aliasing"* therefore has no representation, and `01`'s Q1 recommendation ("filename == id, with
+`aliases[]` retained for dependency back-compat") is unimplementable against `02`.
+
+### L8 🟡 Mixed-case delegate scopes round-trip broken
+
+`coder-agent` declares `task: { TestEngineer: "allow" }` (verified). `02` §1.2.3 says the delegate
+scope namespace is *"agent id (**kebab**)"* — but `RuleSchema.scope` is `z.string().min(1)`, which
+happily accepts `TestEngineer`, and `IdSchema`'s kebab regex is never applied to scopes. `03:208`
+maps `[{scope:X, decision:D}] → task: { X: "D" }` verbatim.
+
+So the pipeline **preserves the bug**: `TestEngineer` goes in, `TestEngineer` comes out, and the
+registry id is `tester`. `01` §1.4 flags it (*"The neutral `delegate:` map must pick one
+(canonical `id`)"*); no document specifies the canonicalization step or where it runs.
+
+### L9 🟡 `Version: X.Y` normalization is lossy on round-trip
+
+`02` §4.3 normalizes context `X.Y` → `X.Y.0` for `VersionSchema` (verified real distribution:
+`1.0`×271, `2.0`×10, `1.1`×4, plus `3.1`, `2.1`, `1.3`). `02:684` says adapters re-emit the MVI
+one-liner. But nothing specifies re-emitting `2.0.0` **back to `2.0`**. A build that round-trips
+context files rewrites 297 markers from `Version: 2.0` to `Version: 2.0.0` — churning every
+context file on first build and breaking `01`'s *"byte-for-byte"* contract (`03:281`) and `04`'s
+determinism requirement (`04:245`, *"`oac build && oac build` is a no-op"*).
+
+### L10 🟡 The `.oac.json` discovery protocol is on the checklist and in no schema
+
+`01:1221` — *"**`.oac.json` discovery protocol preserved** (fast path → chain → validity check →
+self-heal)"*, which `01` §3.3 argues is *"strictly better"* than `paths.json` and should become
+**the neutral, cross-tool mechanism** (Q11). `02` has no schema for it. `03` mentions
+`.context-manifest.json` (§2.4) but never `.oac.json`. `04` never mentions it. The
+`navigation.md`-presence validity check (`01:1222`) likewise has no home.
+
+---
+
+## Implementability Gaps
+
+Where an implementer hits a wall. Walked end-to-end as instructed.
+
+### G1 🔴 Walkthrough: `coder-agent.md` → `/content` → `oac build`. It stops at step 2.
+
+**Step 1 — Author `/content/agents/coder-agent.md`.** Fine; `00:299-312` shows the target.
+
+**Step 2 — Parse. FAIL.** The agent's real metadata (`agent-metadata.json`) carries
+`dependencies: ["subagent:task-manager", "context:standards-code", …]`. Its registry entry and
+the profiles carry `agent:openagent`, `config:agent-metadata`, `context:core/*`. Per **F2**,
+`02`'s `DependencyInputSchema` rejects the `agent:`, `config:`, `plugin:` kinds and every
+wildcard. Nineteen refs. The implementer must invent the vocabulary; `02` §7 gives nine types and
+§1.5 gives five, and they disagree.
+
+**Step 3 — Desugar capabilities.** `edit` has five denies and **no `*` rule**. `02` §1.2.5 covers
+"absent" and "no rule matched", not this. `03` §0.4 says the IR must define it and escalates to
+`02`; `02` doesn't. The implementer must guess — and guessing wrong here **is** the live security
+gap (`00:135`). **Wall.** (See C8.)
+
+**Step 4 — Resolve `delegate: {TestEngineer: allow}`.** Canonicalize to `tester`? To
+`test-engineer`? Leave it? `01` says canonicalize to `id`; `02`/`03` pass it through. **Wall.** (L8.)
+
+**Step 5 — Build `--target opencode`.** Emit `permission.bash` with the `router.sh` scopes. Are
+they rewritten for `--global`? `04` §2.4 says only context refs. **Wall.** (L3.)
+
+**Step 6 — Build `--target claude`.** Fail-closed on `Bash` — well specified and correct. But
+`03` §2.8 predicts **4 warnings** for this agent, one of which is *"the one to act on"* because
+it is a silent security downgrade with no remedy (`03:601`). Whether that blocks the build is
+`03`'s Q3, unanswered. **Wall.**
+
+**Six steps, four walls, on the agent the index nominates as the hardest case.** That is the
+correct agent to have chosen — but the choosing was not followed by resolving.
+
+### G2 🔴 Walkthrough: a real context file. The parser spec is good; the writer spec is missing.
+
+Take `.opencode/context/core/standards/code-quality.md`:
+
+```
+<!-- Context: standards/code | Priority: critical | Version: 2.0 | Updated: 2025-01-21 -->
+```
+
+**Parse:** `02` §4.2's spec handles this correctly, including the leading-window rule that avoids
+the line-232/line-301 prose traps. **This is the best-specified part of the whole set.** Two gaps:
+
+1. **`description` is required and unobtainable.** `02:665` — `description: DescriptionSchema`
+   (`z.string().min(1)`, **not** optional). The MVI marker has **no description field** — it is
+   `Context | Priority | Version | Updated`. `02` §4.2 step 5 derives *"id/name from path,
+   priority = medium"* on failure, but never says where `description` comes from. **Every one of
+   the 294 context files fails `ContextSchema` validation.** Same for `name`.
+2. **`category` from the marker is `standards/code`** — a `{category}/{function}` compound.
+   `02:666` stores it whole in `CategorySchema` (open string). But `01:491` requires the dual
+   taxonomy be **split** into *"Neutral `category` + `function`"*, and `01:1209` puts it on the
+   checklist. `02` has no `function` field. The `concepts/examples/guides/lookup/errors` taxonomy
+   (`01:697`, on the checklist at `01:1226`) is silently flattened into a string.
+
+**Serialize:** per L9, `Version: 2.0` comes back as `2.0.0`. The file churns.
+
+**Exclusion list:** `02` §4.2 step 6 hardcodes a two-path allowlist and `02`'s own Q5 admits
+*"A path allowlist is brittle as content grows."* It is also already incomplete — `02` §4.1
+identifies **four** marker-as-prose files (`agents.md:232`, `categories.md:301`, `templates.md:25`,
+`frontmatter.md:43`) and excludes **two**. The other two (`agents.md`, `categories.md`) are handled
+by the leading-window rule, which is correct — but then the allowlist is doing something different
+from what its own §4.1 analysis implies, and nothing says which mechanism owns which file.
+
+### G3 🟠 `02`'s registry schema cannot drive `04`'s pipeline
+
+`04` §2.2 reads `profiles.*.components[]` and `additionalPaths`. `02` §7 has no `profiles` object
+(L5), no `categories` (L6), no `aliases` (L7). `04` §3 row 14 maps `get_profile_components` →
+`lib/registry.ts getProfileComponents()` *"Reads `profiles.*.components[]`"* — against a schema
+that deleted the key.
+
+Meanwhile `02:833` — `components: z.record(z.string(), z.array(RegistryEntrySchema))` — is a
+**bare record**, so the singular/plural key mapping (`get_registry_key`, `01:914`, checklist at
+`01:1246`) is unconstrained. `04` §3 row 17 says *"`ComponentTypeSchema` + plural section keys;
+fold aliases in"* — but `ComponentTypeSchema` is **singular** (`"agent"`) and the registry keys are
+**plural** (`"agents"`), with `config` staying singular as a documented special case. Nobody
+specifies the mapping; three docs assume someone else did.
+
+### G4 🟠 `bundled.ts` breaks on two anchors, not one
+
+`00:126` finding #7 — *"It identifies the package root by the *absence* of `registry.json` — but
+`registry.json` will ship *inside* the package."* Verified. But the function has **three**
+conditions:
+
+```ts
+const hasOpencode   = existsSync(join(current, ".opencode"));
+const hasPackageJson = existsSync(join(current, "package.json"));
+// + NOT registry.json
+```
+
+and `BUNDLED_SUBDIRS = [".opencode/agent", ".opencode/context", ".opencode/skills"]`.
+
+Post-refactor, `04` §5.1 explicitly **stops shipping `.opencode/`**. So `hasOpencode` is false at
+every level and the walk throws before the `registry.json` heuristic is ever consulted. The index
+names one bug; there are two, and the `.opencode/` anchor fails **first**. `04` §5.1 does say to
+*"replace the heuristic with an explicit `package.json` `name` check"*, which would incidentally
+fix both — but only if the implementer knows to look. Note also `BUNDLED_SUBDIRS` omits
+`.opencode/command` today, so commands are **not currently bundled at all** — an existing bug
+neither `01` nor `04` records, and `04`'s retarget list (`04:458`) would silently inherit it.
+
+### G5 🟠 `03` §1.3's "emit a key only when a rule is not a plain `*: allow`" is unspecified for the round-trip
+
+`03:236-239` — *"`read`/`grep`/`glob` are granted by default in OpenCode; emit a key only when a
+rule is not a plain `*: allow`."*
+
+So `read: allow` → **no `read:` key emitted**. But `02` §1.2.5 says an **absent** capability
+parses back to `[]` → *"tool default"*, not to `[{scope:"*", decision:"allow"}]`. Therefore
+`IR → OpenCode → IR` maps `read: [{*, allow}]` to `read: []`. **Not idempotent.** This directly
+violates `04:245` (*"`oac build && oac build` is a no-op"*), `05` Layer 2 case 2 (*".opencode
+output → re-parse → re-build → equals first build"*), and `00:330` (*"**OpenCode round-trips
+Option A exactly**"*). The claim of exact round-trip is false for exactly the three capabilities
+`03` optimizes.
+
+### G6 🟡 Determinism vs. the sha256 write gate
+
+`04` §2.1 requires byte-identical output; `04` Stage 6 routes writes through `installer.ts`, which
+**skips user-modified files** unless `--yolo`. So `oac build` on a repo with one hand-edited
+generated file produces output that is *not* a function of `/content` — and `--check`
+(`04:116`, *"exit 1 if drift"*) will report drift that `build` then refuses to fix. Neither `04`
+nor `05` addresses the interaction, and `05`'s Stage 5 exit criterion (*"a fresh clone → `oac
+build` → clean `git diff`"*) only holds on a fresh clone — i.e. it tests the one case where the
+conflict cannot arise.
+
+### G7 🟡 `05`'s Stage 2 gate has an escape hatch that voids it
+
+`05:215` — *"generated `.opencode/` `code-reviewer` is byte-identical **(or diff-explained)** to
+hand-maintained."*
+
+"Or diff-explained" makes the gate unfalsifiable. `05:91` calls this *"the single most important
+gate — it is the 'left' of both delete arrows."* A gate that passes on a prose explanation is not
+a gate. Given that the two `code-reviewer` sources differ by 161 lines (L1), the diff will be
+enormous and "explaining" it will be the entire merge decision, made under deadline pressure at
+the moment the deletion is blocked on it.
+
+---
+
+## Open Question Triage
+
+**Count check:** the brief says ~25+. Actual: **60** (01: 24, 02: 10, 03: 10, 04: 9, 05: 7). The
+volume is itself a finding — 60 open questions is not a spec, it is a research note. Below I
+triage all that matter; unlisted ones are defaults.
+
+| Question | Verdict | Recommendation |
+|---|---|---|
+| **03 Q1** — first/last-match-wins (self-labelled BLOCKING) | **already-answered** | `00:219` closed it (last-match-wins). Delete from `03`. **But** F3: re-verify against a real OpenCode install before freeze — the "confirmed" rests on self-citation. |
+| **02 Q1** — same, "needs ratification" | **already-answered** | Same. Delete. |
+| **03 Q7** — code-reviewer 1 or 2 warnings | **already-answered** | `00:346` says 2. Delete; fix `05:300` (C4). |
+| **01 Q17** — committed `.env`, rotate history? | **already-answered** | `00:80` retracted it as false (F6). **Close now.** Retain only the build-hygiene rule: never glob `.env` into `/content`. |
+| **01 Q2** — flat vs rule-list capabilities | **already-answered** | Locked decision #5. Delete. |
+| **03 Q2 / 02 (missing)** — implicit default with no `*` rule | 🔴 **BLOCKER** | Nobody owns it (C8) and the live security gap depends on it. Adopt `03`'s rule: *opposite of decisions present; mixed-without-`*` is an error*. **Put it in `02` §1.2.5.** Nothing builds until this exists. |
+| **02 Q3 / 01 Q3 / 05 L1-case-2** — is `model` authorable? | 🔴 **BLOCKER** | Three docs, three answers (C3). Decide: `model` **not authorable**; add `tier: fast\|balanced\|capable` to preserve the haiku/sonnet intent (L4). Then `05`'s test is right and `03` drops its `model set →` rows. |
+| **04 Q9** — is `content/` in the user's project? | 🔴 **BLOCKER** | Mislabelled "UX fork" (C5). It decides whether the CLI ships the IR + adapters at all. **Recommend: yes, editable `content/` in-project** — otherwise `oac build` has no input and the whole "author once, build many" premise dies at the user boundary. |
+| **01 Q16** — which skill set seeds `/content/skills/` | 🔴 **BLOCKER** | Answer is the **union of 16** — and that answer requires a `targets: []` schema field that does not exist (L2). Add the field, then the question is trivial. |
+| **01 Q12** — de-hardcoding `.opencode/` from ~129 bodies | 🔴 **BLOCKER** | Take `01`'s recommendation (tokens + lint), **and extend it to permission scopes** (L3), which `01` did not consider. Without it, generated CC agents point at nonexistent paths — `01` correctly suspects this is why the CC plugin was hand-written. |
+| **01 Q10** — HTML comment or YAML | **already-answered** | Locked decision #6. Close. `02` §4 is the implementation and is good. |
+| **01 Q1** — canonical id vs filename | 🔴 **BLOCKER** | `02` has no `aliases[]` (L7), so `01`'s own recommendation is unimplementable. Adopt *filename == id* + add `aliases[]` to `02` §1.8 and §7. Blocks every dependency edge. |
+| **01 Q20** — fail-fast on unresolvable deps | **default** | Yes, hard error. `02:812` already says so. `01` correctly warns this *"will surface currently-hidden breakage"* — budget for it (the three `/add-context` deps are verified dead **today**). |
+| **02 Q2** — `Priority: reference` | **default** | Fix the one file → `low`. Keep the enum closed. Do it now; it is a one-line commit. |
+| **03 Q3** — dropped security globs: warn or block? | 🟠 **blocker-ish** | This is the live gap (index #10) shipping in production. Recommend: **blocker with explicit `--allow-unsafe-degradation` opt-in.** A warning in a build nobody reads is how the gap got there. |
+| **03 Q9** — Windsurf real format | 🟠 **blocker for that target only** | The whole adapter is *"inferred, never checked against a live install."* Either verify or **cut Windsurf from v1**. Do not ship an adapter written from a stub. |
+| **04 Q1** — Node-portable vs compiled binaries | **default** | Option A (Node shim). `04` already recommends it; `00:88` calls it *"mechanical"*. Close it. |
+| **05 Q1** — test runner | **default** | vitest for new packages. Close. |
+| **05 Q3** — commit generated output? | **default** | Commit through Stage 4, flip at Stage 5, exactly as `05` recommends. Close. |
+| **01 Q15** — Bun runtime for skills | 🟠 **blocker for skills** | `router.sh` is bash + Bun `.ts`. `04` §4.3 sells *"No shell scripts"* as the Windows fix while every skill ships one (all four verified `0755`). The contradiction is unaddressed. |
+| **01 Q22** — profile drift, 5 diffs | **blocker, human-only** | Correctly identified as un-automatable. Assign it. Note `02` deleted the profiles object it needs (L5). |
+| **01 Q7** — the dark orchestration feature | **default** | Delete or register; do not carry ambiguity into `/content`. Recommend **delete** (unregistered, uninstallable, undocumented outside itself) — reversible via git. |
+| **01 Q4** (0-category), **Q6** (author), **Q9** (version format), **Q14** (index vs navigation), **Q21** (registry versions), **Q24** (config: components) | **bikeshedding** | Close all six with defaults: promote `icon/order/status` + delete `common*`; `author: oac`; SemVer everywhere with `X.Y`→`X.Y.0` **and a re-emit rule** (L9); `navigation.md` canonical; one `schemaVersion` + one `contentVersion`; drop `config:readme`. None blocks code. |
+| **02 Q6** (`web` capability), **Q10** (`primary\|subagent` enough), **03 Q10** (cursor cell semantics), **04 Q5** (Windows global path), **04 Q8** (backup retention) | **bikeshedding** | Defaults: keep `web`; two roles; `none`; `%APPDATA%`; `oac clean`. |
+| **02 Q4** — 296 vs 297 | **already-answered by measurement** | Neither. **297 path entries = 294 files + 3 symlinks** (F9, U1). Fix all four docs. |
+| **02 Q5** — exclusion allowlist brittleness | **default** | Use `<!-- oac:no-parse -->`. The allowlist is already incomplete (G2). |
+
+---
+
+## Unasked Questions
+
+The dangerous gaps — risks **no document mentions at all**.
+
+### U1 🔴 The context tree contains symlinks. No document knows.
+
+```
+$ find .opencode/context -name '*.md' ! -type f -exec ls -la {} \;
+core/standards/tests.md -> test-coverage.md
+core/standards/docs.md  -> documentation.md
+core/standards/code.md  -> code-quality.md
+```
+
+Zero mentions of symlinks across all six documents. Consequences, none considered:
+
+1. **Windows.** Git on Windows without Developer Mode or `core.symlinks=true` checks out a symlink
+   as a **plain text file containing the target path**. `code.md` becomes a ~16-byte file whose
+   entire content is the string `code-quality.md`. Any agent instructed to read
+   `.opencode/context/core/standards/code.md` gets garbage. `04` §4.3 is a dedicated Windows
+   section (issues #304/#312) covering path separators, line endings, and case sensitivity — and
+   misses the one thing that silently corrupts content.
+2. **npm pack/publish.** Symlink handling in tarballs is inconsistent across npm versions and
+   extraction paths. `04` §5 specifies `files: ["content/", …]` with no symlink policy.
+3. **Build determinism.** A copying build dereferences them → three duplicate files. `04` §2.1
+   demands byte-identical output and `05` Stage 5 demands `git diff` clean after build. A
+   dereferenced symlink is a **new file** in the diff, every time.
+4. **They are the same three files as the alias bug.** `01` §3.6 flags `code-quality.md`,
+   `test-coverage.md`, `documentation.md` as having *"two registry ids each"* and requires
+   collapsing to `aliases[]` (`01:1218`). It concludes *"**Two mechanisms for one concept**"*.
+   There are **three** — and the third is the filesystem. Collapsing to `aliases[]` deletes the
+   symlinks, and **three files reference the alias paths directly**:
+   `.opencode/agent/subagents/core/context-retriever.md`,
+   `.opencode/context/openagents-repo/templates/context-bundle-template.md`, and
+   `plugins/claude-code/skills/test-generation/SKILL.md`. Deleting the symlinks breaks all three.
+
+### U2 🔴 There is no rollback story, and the delivery channel is auto-updating
+
+`05` §1.3 is proud that *"Users migrate by **doing nothing**; the payload behind the same entry
+point improves"* (`05:124`). That is also the risk, and it is never named. CC users on
+`/plugin install oac` receive whatever `oac build --target claude` commits — automatically. If a
+build regression ships (say, `03` §2.3's fail-closed rule firing on an agent that needed `Bash`),
+every CC user's plugin breaks **with no action on their part and no way to pin the old version**.
+
+No document specifies: a rollback procedure, a canary/staged rollout, a `--pin` for the
+marketplace, or an abort criterion. `05` §2 defines exit criteria for entering each stage and
+none for **reversing** one. Stage 3 deletes `sync-to-claude.sh` — the only thing producing CC
+output today — behind a gate whose escape hatch is "or diff-explained" (G7).
+
+### U3 🟠 Nobody asked whether `oac build --target opencode` can reproduce today's `.opencode/`
+
+The entire plan assumes it can. But `.opencode/` currently contains, verified: `prompts/` (10
+per-model variants + committed eval results), `node_modules/`, `bun.lock`, `.env` ×2 (untracked),
+`tool/` TypeScript workspaces with their own `package.json`/`tsconfig.json`, `plugin/*.ts` +
+`.ts.disabled` twin, `docs/`, `scripts/task-cli.ts`, `profiles/`, `opencode.json`, `config.json`.
+
+`03` §1.1's OpenCode layout emits **seven** things: `agent/`, `config/agent-metadata.json`,
+`skill/`, `command/`, `context/`, `opencode.json`, `config.json`. Everything else in the real
+`.opencode/` is **not generated by anything**. So after Stage 5's *"stop committing generated
+trees"*, either those files vanish or `.opencode/` is a hybrid of generated and hand-maintained
+content — which is the exact dual-source mess (`05:253`) Stage 5 exists to end. `01` inventories
+these assets; nobody checks them against `03`'s output surface.
+
+### U4 🟠 Content is a supply chain, and `session-start.sh` proves the threat is understood
+
+`session-start.sh` carries `escape_for_json()` with the comment *"SECURITY: Prevents command
+injection attacks from malicious SKILL.md files"* (verified) — i.e. **the project already knows
+that content can be hostile**, and `01:1313` correctly requires the defense be preserved.
+
+But the refactor **inverts the trust model** and nobody notices. Today `install.sh` copies inert
+files. After the refactor, `oac build` **parses untrusted content and generates executable
+artifacts** — permission blocks, `hooks.json` shell commands (`03:464`), `router.sh` invocations
+baked into allowlists. A malicious or compromised `/content/agents/*.md` can inject an
+`{scope: "*", decision: "allow"}` bash rule, or a `HookActionSchema.command` (`02:768` —
+`command: z.string().min(1)`, **no validation whatsoever**) that runs at every SessionStart.
+
+There is no content signing, no `checksum` verification path (`02:822` *adds* a `checksum` field
+but nothing verifies it), no provenance check, and no threat model. `01` §9.2 preserves one
+escaping function; nobody asks what the build itself is now trusted to do.
+
+### U5 🟡 No performance or scale budget
+
+`oac build --target opencode` must parse 294 context files + 34 agents + 20 commands + 6 skill
+trees, resolve a 245-node dependency graph, and write hundreds of files — on every `add`,
+`remove`, and `update` (`04` §1.9 marks all three "runs build pipeline: yes"). No document states
+a budget, and `04` §2 specifies no incremental build or caching. If `oac add context:foo` takes
+20 seconds, the UX regresses hard against `install.sh`. Unmeasurable today (nothing is built), but
+unbudgeted is how it becomes unfixable.
+
+### U6 🟡 The 3 dual-format context files have a merge rule nobody validated
+
+`02` §4.2 step 4 — *"Merge precedence when both YAML and MVI are present: **MVI wins** for the
+four MVI fields; YAML supplies any extra keys."* Sensible. But nobody checked whether the three
+files (`csharp.md`, `csharp-project-structure.md`, `registry-dependencies.md` — all verified to
+start with `---`) actually **agree** across the two blocks. If a file's YAML says
+`priority: high` and its MVI marker says `Priority: critical`, MVI-wins silently discards an
+authored value. Three files, five minutes to check, never checked.
+
+### U7 🟡 `--strict` is specified into a corner
+
+`04:116` — `--strict` *"treat adapter warnings as errors → exit 1"*. But `03` §2.8 predicts **2
+warnings for the simplest agent** and **4 for `coder-agent`**, and 33 of 34 agents carry
+`temperature` (verified) which always warns on the CC target. So `--strict` **can never pass** on
+the CC target for any real agent. `05` §3.3 wires golden tests into CI without specifying whether
+they run strict. A flag that cannot be satisfied is a flag nobody will use — and it is the only
+mechanism `04` offers for "never ship a degradation."
+
+---
+
+## Top 5 Things To Fix Before Writing Code
+
+Ranked by what unblocks the most work and what costs most if skipped.
+
+### 1. 🔴 Fix `02`'s schema so it accepts the corpus it describes. *(F2, C8, L5, L6, L7, G2, G3)*
+
+`02` is the centerpiece and it does not fit the data. Minimum:
+- **`DependencyKindSchema`**: add `agent`, `plugin`, `config`; reconcile with `ComponentTypeSchema`'s
+  nine types (one vocabulary, one place). Allow **wildcards** and **path-style refs** in
+  `DependencyInputSchema`, or `01`'s crown-jewel wildcard expansion is unrepresentable.
+- **Define the implicit default** (no `*` rule) in §1.2.5. Adopt `03` §0.4's rule. **The live
+  security gap depends on this and nobody owns it today.**
+- **`ContextSchema.description`/`name`** → optional or derived. As written, **all 294 context
+  files fail validation** — the MVI marker has no description field.
+- **Add `function`** to split the `{category}/{function}` taxonomy `01` requires.
+- **Restore `profiles` (object, with `additionalPaths`/`badge`/`description`), `categories`, and
+  `aliases[]`** to the registry schema — `01` requires all three, `04` reads two of them, `02`
+  deleted them.
+- **Add `targets: []`** to every content schema (L2). Without it there is no union skill set, no
+  home for tools, and no way to say "OpenCode-only."
+
+Nothing downstream is real until this is done. `05`'s Layer-1 test is the forcing function: write
+it first and let it fail.
+
+### 2. 🔴 Revise `01` and `05` to v2, or stop citing them as authoritative. *(F1, F6, C2, C3, C4, C6)*
+
+The index marks both "✅ v1" as a status. They are **stale**, and they are the two documents an
+implementer touches most — `01` is the preservation checklist, `05` is the build order.
+- `05`: purge **160** (four sites); rewrite Stage 3's "copy from `.opencode/`" into the merge the
+  index mandates; fix the Layer-1 `model` test and the Layer-0 warning count; **delete "or
+  diff-explained"** from the Stage-2 gate.
+- `01`: retract the `.env` security alarm and **close Q17**; fix `79`→`76` / `36`→`33` navigation
+  counts; align §1.3's `{pattern, effect}` to `{scope, decision}`; fix `112`→`110`.
+- Index: fix the priority distribution (112/111/34/29/1), the skill counts (4+2 / 12), the context
+  census (297 entries = 294 files + 3 symlinks), and "model: sonnet" → "5 sonnet, 2 haiku."
+- **Add a revision-status column that distinguishes "reviewed against v2" from "never revisited."**
+  "✅ v1" hid two stale documents in plain sight and is the root cause of half this section.
+
+### 3. 🔴 Assign the merge an owner and write the conflict rules. *(L1, C2, G7, U3)*
+
+The index's finding #3 is correct and orphaned. Someone must decide, per content type and per
+conflict:
+- For the **7 agents in both trees**: which body wins? `code-reviewer` is **108 vs 269 lines with
+  different descriptions**. The index's own worked example already silently picked the CC
+  description while `01` presents the OpenCode one as verbatim — **an undocumented decision
+  already made**. Make it explicit or it will be made again, differently, under deadline.
+- **Skills**: union of 16, requiring `targets: []` (fix #1).
+- **`session-start.sh`**: which schema holds it? `02` §6's `HookSchema` is *"load-bearing, not
+  theoretical"* by its own admission but has never been tested against the real file's six
+  capabilities.
+- **The six `<example>` blocks / 6 CC-only commands**: mechanical harvest, but nobody scheduled it.
+
+This is the highest-risk task in the project and currently the least specified. It is also the
+gate on deleting `sync-to-claude.sh`, so it will be done in a hurry unless it is done first.
+
+### 4. 🔴 Answer the three real blockers; close the other 55 with defaults. *(triage table)*
+
+Sixty open questions is not a spec. Exactly three block code:
+1. **Is `content/` in the user's project?** (`04` Q9 — decides whether the CLI ships the IR at all)
+2. **Is `model` authorable?** (`02` Q3 / `01` Q3 / `05` — three docs, three answers; and see the
+   `tier:` proposal in L4, which nobody raised)
+3. **Implicit default with no `*` rule** (`03` Q2 → `02` — folded into fix #1)
+
+Four more are already answered and just not reconciled (`03` Q1, `03` Q7, `02` Q1, `01` Q17, `01`
+Q2, `01` Q10) — **delete them**; `03` Q1 is labelled BLOCKING and will stop an implementer for
+nothing. The rest get defaults today. Then **re-verify last-match-wins against a real OpenCode
+install** (F3) — ten minutes to retire the largest piece of unearned confidence in the set.
+
+### 5. 🟠 Confront the three things nobody asked. *(U1, U2, U4)*
+
+- **Symlinks** (U1). Three of them, invisible to all six documents, and they break Windows
+  checkouts, build determinism, and the `aliases[]` plan simultaneously — while being *the same
+  three files* as `01` §3.6's duplicate-id bug. Decide now: keep as symlinks (and specify Windows
+  + pack + build behavior), or convert to `aliases[]` (and fix the **three files** that reference
+  the alias paths).
+- **Rollback** (U2). The marketplace auto-updates. `05` sells that as the migration's elegance and
+  never asks what happens when a bad build ships to every CC user at once. Define the abort
+  criterion and the pin/rollback path **before** Stage 3 deletes the only working CC producer.
+- **Build-time trust** (U4). `oac build` turns content into executable artifacts —
+  `HookActionSchema.command` is an unvalidated `z.string()` that runs at every SessionStart. The
+  project already ships `escape_for_json()` because it knows SKILL.md can be hostile. The refactor
+  widens that surface enormously and has no threat model.
+
+---
+
+### Closing note
+
+The parts of this spec set that are good are **very** good: `02` §4's MVI parser spec (the
+line-232/line-301 trap analysis is exactly right and I reproduced its bucket counts exactly),
+`03` §2.3's fail-closed asymmetry argument, `03` §2.6's verified-negative on `settings.json`, and
+`01`'s registry/dependency forensics — where I checked ~20 claims and found two counting errors
+and one stale retraction. The live security gap (index #10) is real, correctly diagnosed, and
+shipping in production today; that finding alone justifies the exercise.
+
+The failure mode here is not sloppiness. It is that **five authors revised at different times
+against a moving index, and the index recorded its conclusions without telling the documents that
+supplied them.** Fixes #1–#4 are all versions of the same repair: make the documents agree with
+each other and with the disk. Do that and this is buildable.

+ 299 - 0
docs/architecture/canonical-refactor/07-EXECUTION-PLAN.md

@@ -0,0 +1,299 @@
+# 07 — Execution Plan (Staged, Test-Gated)
+
+> **Status:** Approved direction — supersedes `05`'s staging where they conflict.
+> **Date:** 2026-07-15 · **Branch:** `refactor/canonical`
+> Incorporates the `06-REVIEW` verdict: architecture sound, spec not buildable as written.
+> Every stage below states its **Why**, the **core features** it must achieve, and the
+> **tests that gate it**. A stage is done when its tests pass in CI — not before, not "mostly."
+
+---
+
+## North Star
+
+**OAC is the build system for AI agent configuration.**
+Author agents, permissions, and team context **once** in a neutral TypeScript-parsed format
+(`/content/`); compile to every tool a team uses (`oac build --target <tool>`); install and
+update on user machines safely (`oac init|add|update`); and **verify** the result actually
+loads in each real tool. Never degrade silently.
+
+### Locked platform decisions (this plan)
+
+| Decision | Choice | Why |
+|---|---|---|
+| Language | **TypeScript everywhere** — kill the 52KB `install.sh`/`update.sh` and the Bun-only APIs | One codebase, testable, cross-platform; bash installers are the root of the Windows bug class (#304, #312) |
+| Runtime | **Node ≥ 20** (no Bun requirement) | `npm i -g` must work on a clean machine; Bun coupling defeats npm distribution (15 files to convert, mechanical) |
+| Workspace manager | **pnpm** (workspaces + lockfile) | Security: strict non-flat `node_modules` (no phantom deps), `ignore-scripts` by default, single content-addressed store, better lockfile integrity. Replaces the current bun.lock + package-lock.json split |
+| Distribution | **npm registry** (`npm i -g oac` / `npx oac`) | Users install with the tool they already have; pnpm is our dev tool, not a user requirement |
+| First-class targets v1 | **OpenCode + Claude Code** (+ cheap `agents-md` target) | The two with real users and verified formats. Windsurf CUT from v1 (format never verified against a live install); Cursor = experimental |
+| Core product | **Context management** — team standards versioned, installed, updated safely | This is the wedge: "bring your standards, we deploy them to every tool." The generic context library becomes optional starter profiles |
+| Verification | **Install-verification matrix** as a product feature (`oac doctor --verify`) and a CI gate | "It installed" ≠ "the tool loads it." Headless real-tool load checks across OS × tool × profile |
+
+---
+
+## Stage Overview
+
+| # | Stage | Ships | Gate (summary) |
+|---|---|---|---|
+| 0 | Quick wins — stop the bleeding | CI gates, security hotfix, version sync, junk sweep, pnpm workspace | packages-only PR triggers full CI; hotfix live |
+| 1 | Spec repair & decisions | Fixed `02` schema, revised `01`/`05`, merge conflict rules, 3 blockers answered | Zero BLOCKING open questions; precedence experiment run |
+| 2 | `packages/core` — IR schema + parsers | Zod IR, MVI parser, agent/skill/command/registry loaders | **Corpus test:** every real content file parses |
+| 3 | Build pipeline + golden tests | `oac build --target opencode\|claude\|agents-md` for worked agents | Golden snapshots byte-stable; build idempotent; warning counts exact |
+| 4 | Content merge & migration | `/content/` seeded from BOTH trees; bridge retired | Generated trees load in real tools; evals pass on generated output |
+| 5 | CLI, installer parity & verification matrix | Node-portable `oac init/add/update/doctor`; dependency resolution fixed; 3-OS install e2e | `npm i -g` works on clean Windows/macOS/Linux, no Bun; verify matrix green |
+| 6 | Flip source of truth & release | Generated trees uncommitted; bash installers deleted; rollback/pinning story; README rewrite | Fresh clone → `oac build` → clean `git diff`; one version number |
+
+Dependency order is strict 0→1→2→3→4→5→6, but Stage 0 items are independently shippable
+today and Stage 1 is documentation/decision work that can overlap Stage 0.
+
+---
+
+## Stage 0 — Quick wins: stop the bleeding
+
+**Why:** The refactor will be built on the same ungated pipeline that produced the current
+drift unless CI is fixed first. A live security gap is shipping to Claude Code users today.
+Version drift would suppress the `/plugin update` that delivers any fix. None of this needs
+the refactor — it needs a week.
+
+**Core features:**
+1. **CI gates for the core packages.** New workflow runs `packages/cli`,
+   `packages/compatibility-layer`, `packages/plugin-abilities` tests + lint + typecheck on
+   every PR. Add a `packages/` branch to `scripts/validation/detect-pr-changes.ts` (today a
+   packages-only PR triggers **zero** checks).
+2. **Security hotfix for the live CC `coder-agent` gap** (index finding #10): tighten the
+   shipped plugin agent's `tools:`, ship `RECOMMENDED-PERMISSIONS.md` (deny-only snippets),
+   changelog advisory. Independent of the refactor.
+3. **Version reconciliation** — one version across `VERSION`, root + package manifests,
+   marketplace, plugin (currently 5 numbers across 7 files; the plugin cache is *ahead* of
+   the marketplace).
+4. **pnpm workspace bootstrap.** `pnpm-workspace.yaml` covering `packages/*` +
+   `evals/framework`; one `pnpm-lock.yaml`; remove root `package-lock.json`/`bun.lock`
+   split. Dev scripts run via pnpm; nothing user-facing changes yet.
+5. **Junk sweep:** delete/gitignore `dev/`, `context-findings/`, empty `claude-plugin/`
+   stub; archive `docs/planning/` (16 stale synthesis docs) under `docs/archive/`.
+
+**Tests / gate:**
+- A PR touching only `packages/**` runs build + tests + lint + typecheck in CI (prove with a
+  no-op PR).
+- `pnpm install && pnpm -r test` green from a fresh clone.
+- One grep proves one version string repo-wide.
+- Hotfix verified: shipped CC agent no longer grants unscoped `Edit`, or the documented
+  mitigation is published and linked from the README.
+
+---
+
+## Stage 1 — Spec repair & decisions (docs only, no code)
+
+**Why:** `06-REVIEW`'s verdict is verified: the centerpiece schema rejects the corpus it
+describes (19 real dependency refs, all 294 context files), two docs were never revised
+after v2 decisions, and the hardest migration task (the merge) has no owner. Writing code
+against a self-contradicting spec reproduces the drift we're curing.
+
+**Core features:**
+1. **Fix `02`'s schema on paper:** dependency kinds `agent|plugin|config` + wildcard refs;
+   `ContextSchema.description`/`name` optional/derived; `targets: []` applicability field on
+   every content type; restore `profiles` (with `additionalPaths`/`badge`), `categories`,
+   `aliases[]`; define the **implicit-default rule** (no `*` rule present → opposite of the
+   decisions present; mixed-without-`*` = parse error).
+2. **Revise `01` and `05` to v2:** purge the false "160 agents" (4 sites); rewrite `05`
+   Stage 3 as a **merge**, not a copy; delete the "or diff-explained" escape hatch from the
+   Stage-2 gate; retract `01`'s `.env` alarm (Q17 closed); align `{pattern,effect}` →
+   `{scope,decision}`.
+3. **Merge ownership + conflict rules** (per content type, written down): for the 7
+   dual-home agents, **CC body wins** (newer, richer, has `<example>` blocks) with
+   OpenCode-only fields grafted; skills = union of 16 via `targets:[]`;
+   `session-start.sh` preserved into `/content/` hooks.
+4. **Answer the 3 blockers:** user projects hold an **editable `content/`** (yes — else
+   "author once" dies at the user boundary); `model` **not authorable** — add
+   `inference.tier: fast|balanced|deep` preserving the haiku-scouts cost tiering; implicit
+   default per item 1.
+5. **Run the last-match-wins experiment** against a real OpenCode install (10 minutes;
+   currently "confirmed" only by self-citation).
+6. **Decide symlinks:** convert the 3 context symlinks to `aliases[]` and fix the 3 files
+   referencing alias paths (symlinks silently corrupt Windows checkouts).
+7. **Write the rollback story** (marketplace pinning, abort criterion) before anything is
+   deleted.
+
+**Tests / gate:** no code tests — the gate is editorial and explicit:
+- Zero open questions labelled BLOCKING across `01`–`05` (each of `06`'s F/C/L/G findings
+  marked *fixed* or *accepted* in a disposition table appended to `06`).
+- The precedence experiment's transcript committed to this directory.
+- Merge conflict rules signed off (a table in `08-MERGE-RULES.md`: content type → source of
+  truth → conflict resolution).
+
+---
+
+## Stage 2 — `packages/core`: IR schema + parsers (test-first)
+
+**Why:** Everything downstream consumes the IR. `06` proved the previous schema draft
+rejected the real corpus — so the corpus test is written **first** and drives the
+implementation. This is the forcing function that proves Stage 1 actually worked.
+
+**Core features:**
+1. New `packages/core` (pure TS, zero Bun, zero I/O beyond `node:fs`): Zod IR schemas for
+   all content types (agent, skill, command, context, tool, hook, registry, profiles).
+2. **MVI context parser** with the leading-window rule (line 1, or first line after a
+   closing YAML `---`), dual-format merge (MVI wins for its 4 fields), non-strict priority
+   enum, `X.Y` version normalization **with a re-emit rule** (`2.0.0` → `2.0` on serialize,
+   no churn).
+3. Agent/skill/command frontmatter loaders; registry + profiles loader with alias and
+   wildcard dependency resolution (`context:core/*`).
+4. Capabilities model: Option A ordered rules + scalar/map sugar desugaring; last-match-wins
+   resolution; parse-time validation for integer-like scopes and duplicate scopes.
+
+**Tests / gate (all in CI from this stage on):**
+- **Corpus test (Layer 1):** every real file parses — 34 agents, 294 context files, 20
+  commands, 16 skills, `registry.json` including all 19 previously-rejected dependency refs
+  and all 5 profiles. Zero failures, zero skips.
+- **Trap tests:** the line-232/line-301 prose-marker files parse with correct (path-derived)
+  metadata, NOT the in-body marker; the 3 dual-format files resolve MVI-wins; the
+  `Priority: reference` outlier is accepted.
+- **Round-trip unit tests:** parse→serialize is byte-identical for a sample of each content
+  type (catches the version-churn and `read: allow` omission classes).
+- **Capability tests:** `coder-agent`'s deny-all-then-allowlist and `openagent`'s
+  ask-with-denies resolve correctly under last-match-wins; implicit-default rule covered by
+  explicit cases including the five-denies-no-`*` `edit` block.
+
+---
+
+## Stage 3 — Build pipeline + golden tests (the pipeline proof)
+
+**Why:** `oac apply` is already ~80% of `oac build`. Proving the pipeline on the easiest
+and hardest real agents — before migrating 900 files — is the cheapest possible point of
+failure. If adapters can't express the real agents acceptably, we find out here for the
+cost of a week (see kill criteria).
+
+**Core features:**
+1. `oac build --target opencode|claude` re-rooted at `/content/`, built on the existing
+   adapters — with `ClaudeAdapter` rewritten to the **plugin layout**
+   (`.claude-plugin/plugin.json`, flat `agents/`, `hooks/hooks.json`, bundled `context/`).
+2. **`agents-md` target:** emit a well-formed `AGENTS.md` from the same IR — near-free
+   adapter that makes OAC useful to every tool that reads the emerging standard.
+3. Warning system: every capability/field drop reported, never silent; `--strict` semantics
+   redefined so it is satisfiable (known-expected warnings baselined per target).
+4. Fail-closed rules from `03`: omit `Bash` on scoped-bash agents for CC; security-glob
+   drops are **build blockers** with explicit `--allow-unsafe-degradation` opt-in.
+
+**Tests / gate:**
+- **Golden snapshots (Layer 0):** `code-reviewer` (easy) and `coder-agent` (hard) build
+  byte-stable for both targets; snapshots reviewed against the hand-maintained plugin files
+  and every diff recorded as an explicit merge decision (no "diff-explained" hand-waving).
+- **Warning-count exactness:** `code-reviewer` on CC = 2 warnings; `coder-agent` = 4 — the
+  counts from `03`, asserted, not approximated.
+- **Idempotence:** `oac build && oac build` = no-op; OpenCode output re-parsed → re-built =
+  identical (Layer 2).
+- **Security gate test:** building CC `coder-agent` without the opt-in flag **fails** on the
+  dropped security globs.
+
+---
+
+## Stage 4 — Content merge & migration (the big sweep)
+
+**Why:** The single highest-risk task (`06` L1/C2): `.opencode/` and `plugins/claude-code/`
+have drifted bidirectionally (108 vs 269-line versions of the same agent; disjoint skill
+sets; `session-start.sh` exists only on the CC side). It is executed here — against the
+written Stage-1 rules — not improvised under deadline.
+
+**Core features:**
+1. Seed `/content/` from **both** trees per `08-MERGE-RULES.md`; harvest CC `<example>`
+   blocks, the 12 CC skills, 6 CC-only commands, and `session-start.sh`'s six capabilities.
+2. **Path tokenization** in bodies *and permission scopes* (`{{SKILL_ROOT}}` etc.) + a lint
+   forbidding raw `.opencode/` paths in `/content/` (~129 files affected).
+3. Symlinks → `aliases[]`; fix the 3 referencing files.
+4. Retire `scripts/bridge/sync-to-claude.sh` — only after golden parity holds.
+5. Registry regenerated from `/content/` (closing the 110 unregistered-file gap becomes
+   mechanical).
+
+**Tests / gate:**
+- Corpus test still green over the merged `/content/` (now the only source).
+- **Full-tree structural test (Layer 4):** generated `.opencode/` and plugin trees contain
+  every expected file; manifests valid; tokenized paths resolve in both install layouts.
+- **Real-tool load (Layer 5, first pass):** OpenCode headless (`@opencode-ai/sdk`) and
+  Claude Code load the **generated** trees without error.
+- **Evals as the behavior gate:** the existing eval suites run against generated (not
+  hand-written) agents; pass rate ≥ current baseline on main.
+
+---
+
+## Stage 5 — CLI, installer parity & the install-verification matrix
+
+**Why:** This is where the user-facing promise lands: `npm i -g oac` on a clean machine —
+including Windows — with no Bun, no bash, safe updates, and working dependency resolution
+(today `add.ts` installs a component and **none** of its dependencies; issue #310 is a user
+discovering this). And "it installed" must be checkable per tool: your explicit requirement
+for a system that tests installation across AI tools lives here as a feature, not a script.
+
+**Core features:**
+1. **Node-portability:** convert the 15 Bun-API files (`node:fs/promises`, `node:crypto`,
+   `fileURLToPath`); `bin/oac.js` runs on plain Node; fix `bundled.ts`'s package-root
+   heuristic (explicit `package.json` name check).
+2. **Dependency resolution done right:** transitive walk with cycle detection, wildcard
+   expansion, alias resolution; unresolvable ref = hard error (this will surface hidden
+   breakage — budgeted).
+3. `oac init|add|update|remove|status|doctor` at full `install.sh` parity — absorbing the
+   requirements encoded in open PRs #326/#328/#309/#312, then closing them; sha256 manifest
+   gating so user edits survive updates.
+4. **Install-verification matrix (`oac doctor --verify`):** per detected tool, verify the
+   installed output is *loadable* — OpenCode headless session; CC plugin manifest + agent
+   frontmatter validation; agents-md well-formedness. Same checks exposed as a CI matrix.
+5. Profile-aware context install (`oac init --profile standard`) — the "install the right
+   context" UX, with `additionalPaths` actually copied (a parity *improvement* over
+   `install.sh`, which only printed them).
+
+**Tests / gate:**
+- **3-OS e2e in CI:** `npm i -g` (packed tarball) → `oac init` → `oac doctor --verify` on
+  ubuntu, macOS, **windows-latest** — no Bun present. This workflow replaces
+  `installer-checks.yml`'s bash jobs.
+- Dependency tests: known graph fixtures incl. wildcards, aliases, cycles, unknown refs
+  (hard error), and the 3 previously-dead `/add-context` deps now resolving.
+- Update-safety test: hand-edit an installed file → `oac update` preserves it; `--yolo`
+  overwrites; `--check` reports drift honestly (the determinism/sha256 interaction from
+  `06` G6, specified and tested).
+- Uninstall test: `oac remove` leaves no orphans (closes the "clean uninstall" ask, #266).
+
+---
+
+## Stage 6 — Flip source of truth, release & rollback
+
+**Why:** Only after everything upstream is green does deleting the old world become safe.
+This stage makes the repo *become its own pitch* — and protects the 4.5k-star install base
+from a bad auto-updating release.
+
+**Core features:**
+1. Stop committing generated trees; delete `install.sh`, `update.sh`, the bridge script;
+   `.opencode/` in-repo becomes build output for dogfooding (and the OAC repo itself runs on
+   OAC-installed context — the dogfooding gap closes here).
+2. **Release topology:** one version; changesets-style release flow via pnpm; marketplace
+   entry regenerated from build; **pinning + rollback documented and tested** before the
+   first auto-updating release goes out.
+3. README rewrite to the positioning: build-system framing, honest comparison, 5-minute
+   quickstart per OS, eval badge.
+4. PR/issue triage sweep: close the obsoleted installer PRs with pointers; convert the
+   spec's QUICK-WINS list.
+
+**Tests / gate:**
+- Fresh clone → `pnpm install && pnpm build && oac build` → **clean `git diff`**.
+- Release dry-run: pack → install from tarball → verify matrix green on 3 OSes.
+- Rollback drill: publish `x.y.z-rc`, then verify a documented pin/rollback path actually
+  restores `x.y.(z-1)` for a CC plugin user and an npm user.
+
+---
+
+## Kill / stop criteria (agreed up front)
+
+- **Stage 3 kill:** if golden tests prove the adapters cannot express the real agents
+  without unacceptable loss (security semantics or delegation gone on a first-class target),
+  STOP — fall back to OpenCode-as-source + docs-generation. Sunk cost ≈ 1–2 weeks, not 2 months.
+- **Stage 4 kill:** if the merge reveals the two agent sets are different *products* rather
+  than drifted copies, STOP and pick one tool to be great at.
+- **New adapters:** only on demonstrated user demand (N real requests), never because a tool
+  exists. Windsurf stays cut until its format is verified against a live install.
+- **Done line:** IR frozen at v1, two first-class targets + agents-md, one version number,
+  boring releases. After that: content, DX, and users — not more infrastructure.
+
+## Working agreements
+
+- Each stage lands as a stack of small PRs into `refactor/canonical`; the branch merges to
+  `main` per stage (each stage is shippable), not as a mega-PR (cf. #298, +19k lines, stalled).
+- Tests are written before or with the feature they gate — never after the stage "works."
+- Any claim about counts/behavior gets verified against disk before it enters a doc
+  (the `06` discipline is now house style).

+ 114 - 0
docs/maintenance/PR_AUDIT_2026-07.md

@@ -0,0 +1,114 @@
+# Open Pull Request Audit — July 2026
+
+## Purpose
+
+This audit reviews all 18 pull requests open on 2026-07-14. It explains what each PR contributes, whether it fits OpenAgents Control, and what should happen next. No PR was merged, closed, approved, or modified during the audit.
+
+## Decision Summary
+
+| PR | Purpose | Recommendation | Main reason |
+|---|---|---|---|
+| #195 | “Auto-migration to Cloud” bundle | **Close** | Unrelated Django, CI, eval, and package changes; failed checks; no coherent OAC feature |
+| #295 | Worktree management skill | **Needs redesign** | Forced worktree deletion, Docker volume deletion, hidden package-script execution, unstable ports |
+| #296 | Claude plugin registration and ability executor | **Needs redesign** | Invalid manifest shape, shell injection risk, permission bypasses, fake approvals, incomplete execution |
+| #297 | Installer and registry corrections | **Needs redesign** | Root-level files can escape the selected install directory; conflicts; metadata fix already merged elsewhere |
+| #298 | npm/Bun OAC package manager | **Needs redesign** | Valuable direction, but duplicates merged work and can overwrite or recursively delete user-owned files |
+| #300 | TDD enforcement and model router | **Needs redesign** | Unsafe registry-driven writes, impossible permission flow, language-specific policy presented as universal |
+| #301 | Task router worktree detection | **Merge after minor revisions** | Correct `.git` file support, but fallback wrongly accepts arbitrary directories as project roots |
+| #302 | Allow OpenCode `question` tool | **Merge** | Valid two-line permission change that improves clarification without granting execution authority |
+| #305 | Task CLI Node/ESM imports | **Merge after minor revisions** | Correct narrow fix; runtime and CI typecheck/smoke-test requirements are still undeclared |
+| #309 | Skip `node_modules` in updater | **Merge after minor revisions** | Focused valid fix; add a durable updater regression test |
+| #311 | Eval fallback model change | **Needs redesign** | Replacement model is stale and the underlying unavailable-model false-success behavior remains |
+| #312 | Auto-install Windows dependencies | **Needs redesign** | Read-only commands may silently install host software without approval |
+| #314 | Nix flake and Home Manager support | **Needs redesign** | Supply-chain and approval problems; duplicates installer/resolver logic; high maintenance burden |
+| #316 | OpenCode tool-barrel workaround | **Needs redesign** | Useful one-file fix bundled with unsafe approval-evaluator and unrelated behavior changes |
+| #324 | MiniMax M3 support | **Merge after minor revisions** | Strong product fit; vendor capability claims and behavior values need correction and real test evidence |
+| #325 | Register missing TypeScript/C# contexts | **Merge after minor revisions** | Correct registry repair; changed Bash validator needs focused tests |
+| #326 | Preserve local context paths | **Needs redesign** | Fixes one path case by broadly breaking globally installed context dependencies |
+| #328 | Legacy updater rewrite | **Needs redesign** | Treats network failures as local-only files and removes safe recovery from failed overwrites |
+
+## Immediate Repository Blockers
+
+### 1. Privileged PR workflow
+
+`.github/workflows/validate-registry.yml` uses `pull_request_target`, grants repository write permissions, checks out contributor-controlled code, installs its dependencies, and executes its scripts. A malicious fork could execute code in a privileged workflow context.
+
+Required direction:
+
+- Run contributor code under `pull_request` with read-only permissions and no secrets.
+- Keep any `pull_request_target` job metadata-only; never check out or execute the contributor head there.
+- Move registry updates to a separate maintainer-approved workflow.
+- Pin third-party actions to reviewed commit SHAs where practical.
+
+### 2. PR checks do not trigger correctly
+
+`.github/workflows/pr-checks.yml` declares outputs from `steps.filter.outputs.evals`, but the script writes `has-evals`. As a result, relevant TypeScript build validation can be skipped. The workflow also does not run Vitest.
+
+Required direction:
+
+- Make output names consistent.
+- Run compilation, unit tests, and targeted validation based on changed areas.
+- Ensure summary jobs fail when any required matrix job fails.
+
+### 3. Green CI is not sufficient evidence
+
+Most open PRs only passed registry validation. Shell installers, updater behavior, worktree cleanup, plugin loading, task CLI execution, eval behavior, Nix modules, and model tests were usually not exercised.
+
+## Safe Processing Order
+
+1. Fix PR workflow security and CI change detection.
+2. Merge #302 after a final diff check.
+3. Revise, validate, and process #309 and #325.
+4. Revise and validate #305, then #301.
+5. Correct model facts and run real tests for #324.
+6. Close #195 after preserving any useful isolated ideas as issues.
+7. Ask authors of redesign PRs to split or replace them with focused PRs based on current `main`.
+8. Do not merge #295, #296, #297, #298, #300, #311, #312, #314, #316, #326, or #328 as submitted.
+
+## Required Splits and Replacements
+
+### #316
+
+Create a clean PR containing only the `.opencode/tool/index.ts` workaround. Review approval-evaluator changes separately.
+
+### #297
+
+Recreate useful custom-selection fixes on current `main`. Exclude the unsafe root destination behavior and agent metadata already handled by merged #280.
+
+### #296
+
+Split into:
+
+1. A narrow Claude Code plugin discovery/manifest correction.
+2. A later permission-aware ability executor with structured arguments, real approval responses, worktree containment, and cancellation.
+
+### #300
+
+Split into:
+
+1. Language-neutral TDD guidance.
+2. Read-before-write agent policy.
+3. Model-routing architecture proposal.
+4. Optional model-specific defaults.
+
+### #298
+
+Treat as a v1 architecture program rather than a mergeable feature branch. Rebase against the CLI work already merged through #259 and deliver file ownership, collision safety, rollback, and migration semantics first.
+
+## Merge Candidate Validation
+
+| PR | Required evidence before merge |
+|---|---|
+| #302 | Agent configuration validation and a small runtime/eval check for question availability |
+| #309 | Updater fixture covering Markdown, TypeScript, shell files, and `node_modules` exclusion |
+| #325 | Bash validator fixtures for valid/missing skills and non-verbose success; profile install verification |
+| #305 | Pinned runtime decision plus task CLI typecheck and smoke test |
+| #301 | Real linked-worktree test; fail clearly outside a repository |
+| #324 | Correct vendor capability data; TypeScript compile; Vitest; real MiniMax behavior verification |
+
+## Audit Limitations
+
+- Tests were intentionally not run during the read-only review phase.
+- Existing checks were treated as evidence, not proof.
+- Repository documentation contains contradictory standards, especially `permission` versus `permissions` and registry metadata placement.
+- Package-refactor documents are proposals, not adopted architecture decisions.

+ 233 - 0
docs/maintenance/REPOSITORY_MANAGEMENT_PLAN.md

@@ -0,0 +1,233 @@
+# OpenAgents Control Repository Management Plan
+
+## Product Definition
+
+OpenAgents Control should be maintained as a **human-controlled orchestration and context layer for AI-assisted work**, initially focused on software development.
+
+Its current product promise is:
+
+1. **Project-aware output** — agents load version-controlled project and team patterns before acting.
+2. **Human control** — agents propose plans and request approval before execution or destructive operations.
+3. **Editable behavior** — agents and context remain inspectable Markdown/configuration rather than hidden proprietary behavior.
+4. **Repeatability** — teams can share context, agents, and workflows through source control.
+5. **Behavioral validation** — the eval framework checks approval gates, context loading, tool use, and failure handling.
+6. **Distribution** — a registry, profiles, installer, and package tooling distribute supported components.
+7. **Model flexibility** — users can choose providers and models without redesigning the workflow.
+
+OpenCode is the primary runtime today. Claude Code support is beta. Cursor, Windsurf, a larger package manager, marketplace, lockfile, and broad multi-IDE management are strategic directions, not fully adopted current commitments.
+
+## Primary Users
+
+### Current priority
+
+1. **Solo developers** who want safe setup, predictable behavior, and quick recovery.
+2. **Team leads** who need shared standards, reproducible configuration, and controlled changes.
+
+### Later users
+
+- Open-source maintainers who publish and review community components.
+- Content creators who would require simpler or graphical interfaces.
+- Enterprise administrators, which should remain post-v1 rather than driving current complexity.
+
+## Product Principles
+
+Every feature should pass these tests:
+
+- **Safe by default:** no silent package installs, destructive cleanup, overwrites, or remote execution.
+- **Previewable:** risky operations show planned effects before execution.
+- **Recoverable:** backups, atomic writes, rollback, and clear failure states.
+- **Local-first:** project configuration wins when working in a repository.
+- **User-owned customization:** updates must not destroy unmanaged or modified files.
+- **One source of truth:** avoid independent installer, resolver, model, and metadata implementations.
+- **Evidence-driven:** tests must exercise the behavior actually changed.
+- **Focused changes:** large architectural ideas are split into reviewable vertical slices.
+
+## Current Architecture
+
+The implemented repository has four central systems:
+
+1. **Agents and subagents** in `.opencode/agent/` define orchestrators and delegated specialists.
+2. **Context** in `.opencode/context/` records standards, workflows, and project knowledge discovered lazily by ContextScout.
+3. **Registry and profiles** in `registry.json` and `.opencode/profiles/` describe distributable components and dependencies.
+4. **Evaluation and validation** in `evals/` and `scripts/` test agent behavior and repository structure.
+
+Additional systems include the shell installer/updater, Claude Code plugin, compatibility package, task management, skills, and experimental ability enforcement.
+
+## Strategic Decisions Required
+
+Record these as ADRs before restarting the large v1 refactor:
+
+1. **Installer authority:** Bash, Node/Bun CLI, or one shared resolver consumed by all frontends.
+2. **File ownership:** define generated, OAC-owned, user-edited, and unmanaged files.
+3. **Runtime:** supported Node versions and whether Bun is required, optional, or build-only.
+4. **Registry versioning:** separate package version, registry data version, and schema version.
+5. **Agent metadata:** decide between strict OpenCode frontmatter and centralized metadata.
+6. **Permission syntax:** adopt singular `permission` and retire contradictory examples.
+7. **Model configuration:** inherited runtime defaults, per-agent values, or a central optional router.
+8. **Multi-IDE scope:** define what is native, converted, or intentionally unsupported.
+9. **Editable versus declarative installs:** define how Nix/immutable modes coexist with editable agents.
+10. **Release model:** version bump, npm publication, GitHub release, rollback, and support policy.
+
+## Roadmap
+
+### Phase 0 — Security and governance
+
+- Fix privileged PR workflow execution.
+- Fix broken PR change detection and add meaningful test jobs.
+- Add `SECURITY.md` and enable private vulnerability reporting.
+- Define required checks and branch protection.
+- Establish the source-of-truth hierarchy for code, registry, docs, context, and planning proposals.
+
+### Phase 1 — Stabilize 0.7.x
+
+- Process the six focused merge candidates from the July PR audit.
+- Fix installer, context-path, updater, and task-management regressions through focused replacements.
+- Correct stale README, roadmap, changelog, version, and context documentation.
+- Establish Linux, macOS, Windows/Git Bash, and worktree validation where relevant.
+- Publish one stabilization release after installation and update smoke tests pass.
+
+### Phase 2 — Decide the v1 architecture
+
+- Write and approve the ten ADRs above.
+- Define a shared file-ownership and installation contract.
+- Reconcile the shell installer with the existing CLI work from #259.
+- Define migration from current installations.
+- Decide which package-refactor features are truly v1: onboarding, discovery, lockfile, rollback, security, and multi-IDE support.
+
+### Phase 3 — Deliver v1 incrementally
+
+- Build vertical slices, not one large branch.
+- Start with safe initialization and ownership tracking.
+- Add doctor/status and rollback before broad update automation.
+- Add reproducible lockfiles before community distribution.
+- Add marketplace/community features only after signing, verification, and contribution governance exist.
+
+## PR Management Policy
+
+### Intake
+
+Every PR must have:
+
+- A linked issue or a clear reason why one is unnecessary.
+- One problem and one coherent outcome.
+- Scope, risk, user impact, and test plan.
+- Conventional title.
+- Documentation and migration notes when behavior changes.
+
+### Required review states
+
+- **Needs triage** — metadata and ownership incomplete.
+- **Ready for review** — checks pass and acceptance criteria are clear.
+- **Changes requested** — actionable blockers recorded.
+- **Approved** — no unresolved critical findings.
+- **Deferred** — valid but not aligned with the current roadmap.
+- **Superseded/close** — stale, duplicated, or replaced.
+
+### Size policy
+
+- Small fix: ideally fewer than 200 changed lines.
+- Feature: one independently testable vertical slice.
+- Architecture program: tracking issue plus multiple PRs; never a single 10k–20k-line merge.
+
+### Review SLA
+
+- Security: same day.
+- User-blocking bug: 2 business days.
+- Small maintenance PR: 5 business days.
+- Feature or architecture PR: triage within 7 days; schedule separately.
+
+## Issue Management Policy
+
+- Triage new issues weekly.
+- Label by type, area, priority, and status.
+- Close answered questions after confirmation or a reasonable inactivity period.
+- Merge duplicates into one canonical issue.
+- Require reproduction details for bugs before implementation unless impact is urgent.
+- Convert large features into an epic with explicit decisions and vertical slices.
+- Add progress comments at least every two weeks for active work.
+
+## Worktree and Branch Policy
+
+- Use one worktree per active PR or focused issue.
+- Keep worktrees outside the main checkout in a single managed parent directory.
+- Never force-remove a dirty worktree by default.
+- Never delete Docker volumes, environment files, or branches as an implicit cleanup step.
+- Never run dependency lifecycle scripts automatically when checking out an untrusted PR.
+- Remove worktrees only after verifying clean state and merged/abandoned status.
+- Delete remote branches after merge unless retained for an active release or long-running program.
+- Review stale branches monthly.
+
+## Validation Matrix
+
+| Changed area | Minimum validation |
+|---|---|
+| Agent/context | Frontmatter validation, registry validation, context links, focused eval |
+| Registry/profile | TypeScript and Bash validator tests, dependency resolution, profile install smoke test |
+| Installer/updater | Shell syntax, unit fixtures, clean install, upgrade, collision, rollback, platform matrix |
+| Eval framework | TypeScript build, Vitest, suite validation, focused SDK test |
+| Plugin | Manifest validation, installed-plugin discovery, command/skill smoke test |
+| Task/worktree tooling | Shell tests, normal checkout, linked worktree, dirty-state and containment tests |
+| Package/CLI | Typecheck, unit tests, `npm pack`, clean global/local install, migration test |
+| Workflow | Least-privilege review, fork simulation, required-check behavior |
+| Documentation | Link validation, version/current-command review |
+
+## Release Management
+
+Before release:
+
+1. Confirm approved scope and semantic version.
+2. Run the complete validation matrix for affected systems.
+3. Reconcile `VERSION`, package versions, registry/schema versions, and lockfiles.
+4. Update a chronological, user-focused changelog.
+5. Verify package contents with `npm pack`.
+6. Test fresh install and update from the previous supported version.
+7. Publish npm artifacts and GitHub release through a documented, auditable runbook.
+8. Verify installation from the published artifacts.
+9. Record rollback instructions and known issues.
+
+## Maintenance Cadence
+
+### Weekly
+
+- Triage new issues and PRs.
+- Review security and user-blocking reports first.
+- Keep no more than three implementation items actively in progress.
+- Update owners, status, and blockers.
+
+### Monthly
+
+- Review stale PRs, issues, branches, and worktrees.
+- Audit dependency and action updates.
+- Check README, roadmap, changelog, and context drift.
+- Review release readiness and health metrics.
+
+### Quarterly
+
+- Reconfirm product priorities and supported platforms.
+- Review accepted ADRs and deprecations.
+- Audit installer/resolver duplication and maintenance cost.
+- Review contributor experience and security posture.
+
+## Health Metrics
+
+Track:
+
+- Median time to first PR review.
+- Open security and user-blocking issues.
+- PRs older than 30 days without a decision.
+- Percentage of PRs with meaningful behavioral tests.
+- Install/update success by supported platform.
+- Documentation pages known to be stale.
+- Number of active versus stale branches and worktrees.
+- Release frequency and rollback incidents.
+
+## Source-of-Truth Order
+
+Until formal ADRs are adopted, use:
+
+1. Executable code, package manifests, registry, and active workflows.
+2. Root README for current public positioning.
+3. Package-level READMEs for implemented package behavior.
+4. Current repository standards and context.
+5. Contributor guides and historical audits.
+6. `docs/planning/` as proposals only.