06-REVIEW.md 68 KB

06 — Adversarial Cold Review

Reviewer: independent; no involvement in the conversation that produced 0005. 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.md05-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:

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/archive/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:

"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, Grepno 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-163settings.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 }:

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 matchallow. 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:868hooks | 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/archive/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):

"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.YX.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/contentoac 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:665description: 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_componentslib/registry.ts getProfileComponents() "Reads profiles.*.components[]" — against a schema that deleted the key.

Meanwhile 02:833components: 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:

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: allowno 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 Q2Priority: 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.YX.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:768command: 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 7976 / 3633 navigation counts; align §1.3's {pattern, effect} to {scope, decision}; fix 112110.
  • 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&#39;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.


Stage 1 disposition table

Gate closure: 2026-07-15, canonical-refactor subtask 10. Rule: fixed means the governing specification now contains the required decision or correction. accepted means the limitation is intentionally outside the first-class v1 scope and has an explicit guardrail. These dispositions override the original review text where they conflict, per 12-DISPATCH.md.

Finding Disposition Resolution evidence
F1 fixed 05-impact-migration-tests.md v2 replaces the false 160-agent scope with the disk-verified inventory and merge stages.
F2 fixed 02-canonical-schema.md v3 §0.2 unifies all dependency kinds and accepts path/wildcard refs; Stage 2's corpus gate covers the 19 former rejects.
F3 fixed 10-PRECEDENCE-EXPERIMENT.md confirms last-match-wins through live OpenCode 1.17.20 probes and resolver .findLast() evidence.
F4 fixed 00-INDEX.md v2.2 and 02-canonical-schema.md §4 use the corrected leading-window priority distribution.
F5 fixed 01-feature-inventory.md v2 uses exact-basename navigation counts and preserves the separate suffix-named files.
F6 fixed 01-feature-inventory.md v2 retracts the committed-secret claim; .env remains an untracked build-hygiene exclusion only.
F7 fixed 00-INDEX.md v2.2, 01 §4.5, 02 §2, 03 §0.1, and 09 §4 align on 12 CC skills plus 4 canonical OpenCode skills.
F8 fixed 00-INDEX.md v2.2 and 02's inference.tier preserve the five-sonnet/two-haiku semantic cost tiers without authoring model ids.
F9 fixed 01-feature-inventory.md v2 and 09-MERGE-RULES.md §§7,9 distinguish regular files, symlinks, and JSON; later work must recount after tree changes.
F10 fixed 01-feature-inventory.md v2 corrects the unregistered-context result and separates path aliases from canonical files.
C1 fixed 03-adapter-specs.md §0.5 and its question dispositions now cite 10-PRECEDENCE-EXPERIMENT.md; stale precedence blockers are removed.
C2 fixed 05-impact-migration-tests.md v2 defines a two-tree merge; 09-MERGE-RULES.md owns the per-content-type decisions.
C3 fixed 02-canonical-schema.md v3 makes raw model ids non-authorable and uses inference.tier; 05 tests the same contract.
C4 fixed 05-impact-migration-tests.md and 07-EXECUTION-PLAN.md assert exact CC warning counts: two for code-reviewer, four for coder-agent.
C5 fixed 08-STRUCTURE-AND-PACKAGING.md §§2.1–2.2 distinguishes bundled canonical content from each user's editable project content/.
C6 fixed 01-feature-inventory.md v2 aligns capability examples to {scope, decision}, matching 02.
C7 fixed The corrected skill census is aligned across 00, 01, 02, 03 §0.1, and 09 §4.
C8 fixed 02-canonical-schema.md §1.2.5 owns the implicit-default rule; 03 §0.4 consumes it without escalation.
C9 accepted 07-EXECUTION-PLAN.md limits first-class v1 targets to OpenCode and Claude; Cursor is experimental and Windsurf is cut, so unsupported hooks/delegation remain explicit blockers outside the v1 promise.
C10 fixed 03-adapter-specs.md corrects the archived citation to line 432; live behavior is now sourced from 10, not that citation.
L1 fixed 09-MERGE-RULES.md is the signed-off owner and conflict contract for every content type.
L2 fixed 02-canonical-schema.md v3 adds targets:[]; 09 uses it for the 16-skill union and target-specific hooks.
L3 fixed 09-MERGE-RULES.md §§3,6 tokenizes path-bearing bodies and permission scopes; 07 Stage 4 adds a no-raw-.opencode/ lint.
L4 fixed 02-canonical-schema.md v3 adds `inference.tier: fast
L5 fixed 02-canonical-schema.md v3 restores profile objects including additionalPaths, badges, descriptions, and components.
L6 fixed 02-canonical-schema.md v3 models registry categories and the promoted icon/order/status metadata.
L7 fixed 02-canonical-schema.md v3 models aliases; 09 §7 defines symlink and duplicate-id collapse into aliases[].
L8 fixed 09-MERGE-RULES.md §§3,7 resolves delegate names through canonical ids or aliases, preserving mixed-case legacy scopes such as TestEngineer.
L9 fixed 02-canonical-schema.md's MVI serializer specifies SemVer normalization with compact X.Y re-emission; 05 requires byte-stable round trips.
L10 fixed 08-STRUCTURE-AND-PACKAGING.md §2 defines .oac.json as project discovery for editable content/; 04 owns CLI discovery/self-healing behavior.
G1 fixed 02 resolves dependency vocabulary/defaults, 09 resolves aliases/path scopes, and 07 Stage 3 makes security loss a blocker with explicit opt-in.
G2 fixed 02-canonical-schema.md v3 defines path-derived context identity, optional metadata, split taxonomy, leading-window MVI parsing, and stable re-emission.
G3 fixed 02 restores profiles/categories/aliases and plural registry sections; 04 §2.3 defines dependency and section-key normalization.
G4 fixed 08-STRUCTURE-AND-PACKAGING.md §4 replaces both brittle anchors with an explicit package-name check plus OAC_PACKAGE_ROOT.
G5 fixed 02 §1.2.5 distinguishes absent/default capabilities from explicit *: allow; 05 requires OpenCode parse→build idempotence.
G6 fixed 04-cli-build-distribution.md separates deterministic build/check output from update-time sha256 user-edit protection; 07 Stage 5 tests preservation and --yolo.
G7 fixed 05-impact-migration-tests.md v2 removes the diff-explained escape hatch; 07 Stage 3 requires reviewed, byte-stable goldens and explicit merge decisions.

All 37 F/C/L/G findings are dispositioned above: 36 fixed, 1 accepted. U2 is closed by 11-ROLLBACK.md; U1 and U3 are closed by 09-MERGE-RULES.md; remaining U-items are carried as explicit implementation/test constraints in 02, 05, and 07 rather than open Stage 1 gates.