|
| 1 | +--- |
| 2 | +date: 2026-02-09 |
| 3 | +topic: frontmatter-conversion-and-upstream-tracking |
| 4 | +--- |
| 5 | + |
| 6 | +# Frontmatter Conversion Improvements & Upstream Tracking |
| 7 | + |
| 8 | +## What We're Building |
| 9 | + |
| 10 | +Two related capabilities for the Systematic plugin, with a project skill as the end goal: |
| 11 | + |
| 12 | +1. **Improved frontmatter conversion** — Stop stripping Claude Code (CC) frontmatter fields during CEP-to-OpenCode conversion. Instead, map fields where OC equivalents exist and preserve the rest as pass-through. This lets CEP `.md` definitions be dropped in with minimal manual editing. |
| 13 | + |
| 14 | +2. **Upstream provenance manifest** — A separate manifest file (`sync-manifest.json`) that records which upstream source repo/commit each bundled definition originated from. Supports multiple upstream sources (CEP, `anthropics/skills`, etc.). This is provenance metadata for AI agents — not sync state, since all imported definitions will diverge after manual editing. |
| 15 | + |
| 16 | +3. **`sync-definitions` project skill** (end goal) — A project skill in `.opencode/skills/sync-definitions/` that an AI agent invokes in a session to: fetch upstream content, run conversion, intelligently rewrite definitions for OC compatibility and Systematic's style preferences, and update the manifest. The skill includes instructions and reference for git operations, file copying, and the existing conversion workflow. |
| 17 | + |
| 18 | +## Why This Approach |
| 19 | + |
| 20 | +**Converter-first (Approach A)** was chosen over manifest-first or big-bang because: |
| 21 | +- The converter is the immediate bottleneck — CEP definitions can't be imported cleanly without fixing field handling |
| 22 | +- The manifest schema is cheap to define alongside converter work |
| 23 | +- The sync skill is deferred to a follow-up, informed by real usage of manual imports |
| 24 | +- Small, testable changes with clear verification |
| 25 | + |
| 26 | +**Map-and-preserve** field strategy was chosen over strip-all or pass-through-all because: |
| 27 | +- Preserves information from upstream (no data loss) |
| 28 | +- Actively maps fields where OC equivalents exist (not just ignoring them) |
| 29 | +- Unmapped CC fields are harmlessly ignored by OC (AgentConfig has `[key: string]: unknown`) |
| 30 | +- Definitions remain portable — could round-trip back to CC if needed |
| 31 | + |
| 32 | +**Separate manifest** was chosen over frontmatter-embedded tracking because: |
| 33 | +- Clean separation of concerns — definitions stay portable, tracking is machine metadata |
| 34 | +- The manifest is for AI agents, not humans — it provides context for the `sync-definitions` skill |
| 35 | +- No frontmatter pollution or stripping needed during conversion |
| 36 | +- Manifest is provenance (where did this come from?), not sync state (everything will be modified) |
| 37 | + |
| 38 | +**User overrides via existing opencode.json** — No new override mechanism needed. OpenCode's existing config precedence already handles this: |
| 39 | +- Systematic spreads bundled content first, then existing user config on top (`{ ...bundled, ...existing }`) |
| 40 | +- User-defined agents/commands in `opencode.json` fully replace bundled ones with the same name |
| 41 | +- This is full-object replacement (not field-level merge) — intentional and documented by OpenCode |
| 42 | +- Verified: [opencode.ai/docs/agents](https://opencode.ai/docs/agents) confirms agents are complete definitions, not partial patches |
| 43 | + |
| 44 | +## Key Decisions |
| 45 | + |
| 46 | +- **Field handling strategy**: Map CC fields to OC equivalents where possible, preserve unmapped fields in output frontmatter (don't strip) |
| 47 | +- **Tracking location**: Separate `sync-manifest.json` file, not embedded in definition frontmatter |
| 48 | +- **Manifest purpose**: Provenance for AI agents — attribute upstream source and locate content for diffing, not sync state tracking |
| 49 | +- **Manifest scope**: Multi-source — supports arbitrary git repo upstreams, not just CEP |
| 50 | +- **Execution order**: Converter changes first, manifest schema alongside, sync skill later |
| 51 | +- **Content divergence expected**: Every imported definition will be manually modified — the `modified` field is dropped in favor of `notes` describing what changed |
| 52 | +- **User overrides**: No new mechanism — existing `opencode.json` agent/command config takes precedence over bundled content (verified against OC docs) |
| 53 | +- **CLI convert command**: Can be moved to scripts since it's a project-specific task, not user-facing |
| 54 | +- **`maxSteps` → `steps`**: OpenCode deprecated `maxSteps` in favor of `steps` — Systematic should migrate |
| 55 | + |
| 56 | +## Verified Field Reference |
| 57 | + |
| 58 | +Sources: [opencode.ai/docs/agents](https://opencode.ai/docs/agents), [opencode.ai/docs/commands](https://opencode.ai/docs/commands), [opencode.ai/docs/skills](https://opencode.ai/docs/skills), [docs.anthropic.com/en/docs/claude-code/skills](https://docs.anthropic.com/en/docs/claude-code/skills), [docs.anthropic.com/en/docs/claude-code/sub-agents](https://docs.anthropic.com/en/docs/claude-code/sub-agents) |
| 59 | + |
| 60 | +### Skills: CC Fields to OC Behavior |
| 61 | + |
| 62 | +| CC Field | Type | Current Behavior | New Behavior | |
| 63 | +|----------|------|-----------------|--------------| |
| 64 | +| `name` | string | Kept | Kept (no change) | |
| 65 | +| `description` | string | Kept | Kept (no change) | |
| 66 | +| `license` | string | Kept | Kept (no change) | |
| 67 | +| `compatibility` | string | Kept | Kept (no change) | |
| 68 | +| `metadata` | map | Kept | Kept (no change) | |
| 69 | +| `model` | string | **Stripped** | Normalize (add provider prefix), preserve in frontmatter | |
| 70 | +| `allowed-tools` | string | **Stripped** | Preserve as-is (no OC equivalent for skills-as-commands) | |
| 71 | +| `disable-model-invocation` | boolean | **Stripped** (but read into `SkillInfo`) | Preserve; continue using to drive `userInvocable`/visibility behavior | |
| 72 | +| `user-invocable` | boolean | **Stripped** (but read into `SkillInfo`) | Preserve; continue driving visibility filtering | |
| 73 | +| `context` | string | **Stripped** (mapped to `subtask`) | Preserve; continue mapping `fork` to `subtask: true` | |
| 74 | +| `agent` | string | **Stripped** | Preserve; pass through to command config `agent` field | |
| 75 | +| `argument-hint` | string | **Stripped** from commands | Preserve in frontmatter (OC ignores it, useful for docs/tooling) | |
| 76 | +| `hooks` | object | **Not handled** | Preserve as-is (no OC skill equivalent, but don't lose data) | |
| 77 | + |
| 78 | +### Agents: CC Fields to OC Mapping |
| 79 | + |
| 80 | +| CC Field | OC Equivalent | Mapping | Status | |
| 81 | +|----------|--------------|---------|--------| |
| 82 | +| `name` | `name` | Direct | Done | |
| 83 | +| `description` | `description` | Direct (with Systematic suffix) | Done | |
| 84 | +| `model` | `model` | Normalize provider prefix; CC uses `sonnet`/`opus`/`haiku`/`inherit`, OC uses `provider/model-id` | Done | |
| 85 | +| `temperature` | `temperature` | Direct (infer if missing) | Done | |
| 86 | +| `maxTurns` | `steps` | Rename (**`maxSteps` deprecated by OC, use `steps`**) | **Needs update** | |
| 87 | +| `tools` (array) | `tools` (map) | Convert CC array `["Read", "Grep"]` to OC map `{ read: true, grep: true }` | **New** | |
| 88 | +| `disallowedTools` (array) | `tools` (map) | Convert to `{ tool: false }` map | **New** | |
| 89 | +| `permissionMode` | `permission` | Map CC modes to OC permission config where possible | **New** | |
| 90 | +| `skills` (array) | — | Preserve (no OC equivalent) | **New** | |
| 91 | +| `memory` (string) | — | Preserve (no OC equivalent) | **New** | |
| 92 | +| `hooks` (object) | — | Preserve (no OC equivalent) | **New** | |
| 93 | +| `mcpServers` (object) | — | Preserve (no OC equivalent) | **New** | |
| 94 | + |
| 95 | +### OC-Only Fields (additive, not from CC) |
| 96 | + |
| 97 | +These can be added to definitions IN ADDITION to CC fields: |
| 98 | + |
| 99 | +| OC Field | Purpose | Status | |
| 100 | +|----------|---------|--------| |
| 101 | +| `mode` | `subagent` / `primary` / `all` (defaults to `all` if omitted per OC docs) | Done | |
| 102 | +| `top_p` | Top-p sampling | Done | |
| 103 | +| `color` | Hex color or theme color for UI | Done | |
| 104 | +| `steps` | Max agentic iterations (**replaces deprecated `maxSteps`**) | **Needs update** | |
| 105 | +| `hidden` | Hide subagent from `@` autocomplete (subagent-only) | **New — maps to `disable-model-invocation` concept** | |
| 106 | +| `permission` | Granular permission ruleset (edit, bash, webfetch, doom_loop, external_directory, task, skill) | Done (partial — `task` and `skill` permissions are new) | |
| 107 | +| `tools` (map) | Tool whitelist/blacklist with wildcard support | Done | |
| 108 | +| `disable` | Disable agent | Done | |
| 109 | +| `subtask` | Command runs as subtask | Done | |
| 110 | +| `agent` | Command delegates to agent | Done | |
| 111 | + |
| 112 | +### OC Command Fields (verified from [opencode.ai/docs/commands](https://opencode.ai/docs/commands)) |
| 113 | + |
| 114 | +| Field | Required | Description | |
| 115 | +|-------|----------|-------------| |
| 116 | +| `template` | Yes | Prompt content (body in markdown files) | |
| 117 | +| `description` | No | Description shown in TUI | |
| 118 | +| `agent` | No | Agent to execute with | |
| 119 | +| `model` | No | Model override | |
| 120 | +| `subtask` | No | Force subagent invocation | |
| 121 | + |
| 122 | +### OC Skill Fields (verified from [opencode.ai/docs/skills](https://opencode.ai/docs/skills)) |
| 123 | + |
| 124 | +Only these fields are recognized by OpenCode's native skill system: |
| 125 | +- `name` (required) |
| 126 | +- `description` (required) |
| 127 | +- `license` (optional) |
| 128 | +- `compatibility` (optional) |
| 129 | +- `metadata` (optional, string-to-string map) |
| 130 | + |
| 131 | +**Unknown frontmatter fields are ignored** — this confirms our pass-through strategy is safe. |
| 132 | + |
| 133 | +## Manifest Schema |
| 134 | + |
| 135 | +```jsonc |
| 136 | +// sync-manifest.json — provenance metadata for AI agents |
| 137 | +{ |
| 138 | + "$schema": "./sync-manifest.schema.json", |
| 139 | + "sources": { |
| 140 | + "cep": { |
| 141 | + "repo": "EveryInc/compound-engineering-plugin", |
| 142 | + "branch": "main", |
| 143 | + "url": "https://github.com/EveryInc/compound-engineering-plugin" |
| 144 | + }, |
| 145 | + "anthropic-skills": { |
| 146 | + "repo": "anthropics/skills", |
| 147 | + "branch": "main", |
| 148 | + "url": "https://github.com/anthropics/skills" |
| 149 | + } |
| 150 | + }, |
| 151 | + "definitions": { |
| 152 | + "agents/review/security-sentinel": { |
| 153 | + "source": "cep", |
| 154 | + "upstream_path": "agents/security-sentinel.md", |
| 155 | + "upstream_commit": "abc1234", |
| 156 | + "synced_at": "2026-02-09T00:00:00Z", |
| 157 | + "notes": "Adapted prompt for OC tool names, added permission config, rewrote for Systematic style" |
| 158 | + }, |
| 159 | + "skills/brainstorming": { |
| 160 | + "source": "cep", |
| 161 | + "upstream_path": "skills/brainstorming/SKILL.md", |
| 162 | + "upstream_commit": "def5678", |
| 163 | + "synced_at": "2026-02-09T00:00:00Z", |
| 164 | + "notes": "Restructured phases, added YAGNI section, tuned question flow" |
| 165 | + } |
| 166 | + } |
| 167 | +} |
| 168 | +``` |
| 169 | + |
| 170 | +### Manifest Fields |
| 171 | + |
| 172 | +**Source entry:** |
| 173 | +- `repo` — GitHub `owner/repo` identifier |
| 174 | +- `branch` — Default branch to track |
| 175 | +- `url` — Full repo URL |
| 176 | + |
| 177 | +**Definition entry:** |
| 178 | +- `source` — Key into `sources` map |
| 179 | +- `upstream_path` — File path within the source repo |
| 180 | +- `upstream_commit` — Commit SHA when content was last pulled |
| 181 | +- `synced_at` — ISO 8601 timestamp of last pull |
| 182 | +- `notes` — What was changed/adapted (for AI agent context) |
| 183 | + |
| 184 | +## Open Questions |
| 185 | + |
| 186 | +- Should `sync-manifest.json` live at repo root or in a dedicated directory? |
| 187 | +- For the `sync-definitions` skill: should it use `gh` CLI, raw git, or GitHub API for fetching upstream? |
| 188 | +- Should we validate the manifest schema at build time or just when the skill runs? |
| 189 | +- Should the `convert` CLI command be moved to a script immediately, or left in place until the skill replaces it? |
| 190 | +- What style preferences should the `sync-definitions` skill encode for rewriting? (e.g., tool name casing, prompt structure, description format) |
| 191 | + |
| 192 | +## Next Steps |
| 193 | + |
| 194 | +-> `/workflows:plan` for implementation details when ready. |
| 195 | + |
| 196 | +### Phase 1: Converter Changes |
| 197 | +- Refactor `CC_ONLY_SKILL_FIELDS` and `CC_ONLY_COMMAND_FIELDS` — stop bulk-stripping, replace with targeted mapping |
| 198 | +- Add CC-to-OC field mappers: `tools` array→map, `disallowedTools`→map, `maxTurns`→`steps`, `permissionMode`→`permission` |
| 199 | +- Preserve unmapped CC fields (`hooks`, `skills`, `memory`, `mcpServers`) in output frontmatter |
| 200 | +- Add `hidden` as a recognized OC agent field |
| 201 | +- Migrate `maxSteps` to `steps` throughout codebase |
| 202 | +- Update tests to verify new field handling |
| 203 | + |
| 204 | +### Phase 2: Manifest System |
| 205 | +- Define `SyncManifest` TypeScript types |
| 206 | +- Create `src/lib/manifest.ts` — read/write/validate manifest (or put in scripts if project-only) |
| 207 | +- Define JSON schema for `sync-manifest.json` |
| 208 | +- Populate initial manifest entries for existing bundled definitions |
| 209 | +- Optional: add manifest read to CLI for inspection |
| 210 | + |
| 211 | +### Phase 3: `sync-definitions` Project Skill |
| 212 | +- Create `.opencode/skills/sync-definitions/SKILL.md` |
| 213 | +- Skill instructions cover: git operations for fetching upstream, file targeting/copying, conversion workflow, intelligent rewriting for OC compatibility and Systematic style |
| 214 | +- Skill reads manifest for provenance context |
| 215 | +- Skill updates manifest after successful import |
| 216 | +- Skill is invoked in development sessions (like this one) |
0 commit comments