Where each model surface lives, keyed by kind — repo-relative and COMPLETE
(.agents/skills, never skills under a root the reader has to remember).
A kind with no entry is a surface this harness does not have, and that is the
ONLY spelling of "none": there is no "".
🔴 A RECORD, NOT A LIST, AND NOT A SECOND FIELD BESIDE A LIST. What this
replaced was surfaceDirs: readonly string[] standing beside skillDir,
agentDir and commandDir — four places naming the same three directories,
with nothing relating them. opencodeLayout disagreed with itself in exactly
the way that invites: skillDir: ".opencode/skill" while surfaceDirs held
only the agent and command dirs, so OpenCode's skills were named by the port
and never read by anything. With one record there is no second place to
disagree with, surfaceDirs() is derived, and a duplicate kind is a
duplicate object key (TS1117) rather than a conformance finding.
Where each model surface lives, keyed by kind — repo-relative and COMPLETE (
.agents/skills, neverskillsunder a root the reader has to remember).A kind with no entry is a surface this harness does not have, and that is the ONLY spelling of "none": there is no
"".🔴 A RECORD, NOT A LIST, AND NOT A SECOND FIELD BESIDE A LIST. What this replaced was
surfaceDirs: readonly string[]standing besideskillDir,agentDirandcommandDir— four places naming the same three directories, with nothing relating them.opencodeLayoutdisagreed with itself in exactly the way that invites:skillDir: ".opencode/skill"whilesurfaceDirsheld only the agent and command dirs, so OpenCode's skills were named by the port and never read by anything. With one record there is no second place to disagree with,surfaceDirs()is derived, and a duplicate kind is a duplicate object key (TS1117) rather than a conformance finding.