Skip to content

Commit 520fc6d

Browse files
authored
feat: map-and-preserve strategy for frontmatter conversion (#60)
* feat(converter): map-and-preserve strategy for frontmatter conversion Replace destructive field stripping with non-destructive map-and-preserve: - Agent transform copies all fields, then maps known CC→OC equivalents - CC tools arrays → OC tools maps with canonical name mapping - CC permissionMode → OC permission via explicit mapping table - CC maxTurns/maxSteps → OC steps (min strategy, positive integer validation) - CC disable-model-invocation → OC hidden - Skills/commands preserve all fields (CC_ONLY constants removed) - Model normalization applied to skills (not just agents/commands) - context: fork → subtask: true mapping for skills - Parse errors now still transform body content - Converter cache versioned for invalidation on logic changes - PermissionConfig extended with task/skill keys - maxSteps renamed to steps across AgentFrontmatter and config-handler * feat(manifest): add sync-manifest provenance tracking system Add provenance manifest for tracking upstream definition sources: - SyncManifest types with ManifestSource and ManifestDefinition interfaces - Read/write/validate functions with type guard validation - findStaleEntries for detecting orphaned manifest entries - JSON Schema for editor autocomplete and CI validation - Empty manifest at repo root with schema reference - 16 unit tests covering read, write, validate, stale detection * docs: mark completed acceptance criteria in plan * fix: correct TodoWrite mapping in bootstrap and clarify TOOL_NAME_MAP sync - bootstrap.ts: TodoWrite → todowrite (was incorrectly update_plan) - converter.ts: clarify TOOL_NAME_MAP must stay in sync with TOOL_MAPPINGS * docs: add brainstorm and compound solution documentation - Add brainstorm doc capturing the design exploration for map-and-preserve strategy - Add compound solution doc for destructive-to-nondestructive converter transformation - Create docs/solutions/best-practices/ directory for compound-docs workflow * docs: update conversion guide for map-and-preserve strategy Revise CEP-to-OpenCode conversion guide to document the new non-destructive frontmatter handling. CC fields are now mapped to OC equivalents or passed through rather than stripped. - Add permission mode and tools array mapping tables - Document maxSteps→steps, disable-model-invocation→hidden mappings - Update field action language (Map/Pass through vs REMOVE) - Clarify unknown fields pass through untouched
1 parent e18bc1f commit 520fc6d

18 files changed

Lines changed: 2652 additions & 326 deletions

docs/CONVERSION-GUIDE.md

Lines changed: 166 additions & 88 deletions
Large diffs are not rendered by default.
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
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

Comments
 (0)