chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:20:01 +08:00
commit e65605f012
669 changed files with 128771 additions and 0 deletions
@@ -0,0 +1,121 @@
---
title: "Colon-namespaced skill names break filesystem paths on Windows"
date: 2026-03-26
last_refreshed: 2026-06-20
category: integration-issues
module: cli-converter
problem_type: integration_issue
component: tooling
symptoms:
- "ENOTDIR error when running bun convert on Windows"
- "mkdir fails with '.config\\opencode\\skills\\ce:brainstorm'"
- "All target writers (opencode, codex, copilot, etc.) produce colon paths"
root_cause: config_error
resolution_type: code_fix
severity: high
related_issues:
- "https://github.com/EveryInc/compound-engineering-plugin/issues/366"
related_components:
- targets
- sync
- converters
tags:
- windows
- cross-platform
- path-sanitization
- skill-names
- colons
---
# Colon-namespaced skill names break filesystem paths on Windows
## Problem
Earlier plugin versions allowed skill names containing colons (e.g., `ce:brainstorm`, `ce:plan`) to flow directly into target writer paths. Colons are illegal in Windows filenames, causing `ENOTDIR` errors during `bun convert` or `bun install`.
## Symptoms
```
{ [Error: ENOTDIR: not a directory, mkdir '.config\opencode\skills\ce:brainstorm']
code: 'ENOTDIR',
path: '.config\\opencode\\skills\\ce:brainstorm',
syscall: 'mkdir',
errno: -20 }
```
This affected every target present at the time because all used `skill.name` directly in `path.join()` calls. Current CE source skills are hyphenated (`ce-brainstorm`, `ce-plan`), but the sanitizer still matters for compatibility fixtures, imported third-party plugins, and legacy artifact cleanup.
## What Didn't Work
Using `/` (forward slash) as the replacement character was initially considered — turning `ce:brainstorm` into nested directories `ce/brainstorm/`. This was rejected because:
1. It introduces unnecessary directory nesting for what's fundamentally a character-replacement problem
2. The `isValidSkillName` and `validatePathSafe` functions reject `/` and `\`, so sanitized names would fail existing validation
3. The source directories already use hyphens (`skills/ce-brainstorm/`), so the output should match
## Solution
Added `sanitizePathName()` in `src/utils/files.ts` that replaces colons with hyphens:
```typescript
export function sanitizePathName(name: string): string {
return name.replace(/:/g, "-")
}
```
Applied across two layers:
### Layer 1: Target writers
Every target writer wraps skill/agent names with `sanitizePathName()` when constructing output paths:
```typescript
// Before
await copyDir(skill.sourceDir, path.join(skillsRoot, skill.name))
// After
await copyDir(skill.sourceDir, path.join(skillsRoot, sanitizePathName(skill.name)))
```
Currently applied in the maintained target writers and managed-artifact cleanup path. When this fix was first written, a separate `src/sync/` directory also held path-construction logic that needed the same treatment; that layer has since been consolidated into target writers.
### Layer 2: Converter dedupe sets and manifests
Sanitizing paths in writers created a secondary bug: converter dedupe logic used unsanitized names, so a pass-through skill `ce:plan` and a generated skill normalizing to `ce-plan` wouldn't detect the collision — both would write to `skills/ce-plan/` on disk.
Fixed in converters that maintain dedupe sets — currently `src/converters/claude-to-copilot.ts`:
- `usedSkillNames.add(sanitizePathName(skill.name))` instead of raw `skill.name`
Any future converter that maintains a name-collision set or emits a manifest must apply the same sanitization so the in-memory set matches the on-disk paths.
## Why This Works
The core issue was a mismatch between the logical name domain (where older plugin data used colons as namespace separators) and the filesystem domain (where colons are illegal on Windows). The fix sanitizes at the boundary: legacy/imported names can keep colons in data structures, but paths use hyphens. Current CE source directories and frontmatter use hyphenated names directly (`skills/ce-brainstorm/`, `name: ce-brainstorm`), so the sanitizer is now primarily a compatibility guard.
## Prevention
### 1. Collision detection test
A test in `tests/path-sanitization.test.ts` loads the real compound-engineering plugin and verifies no two skill or agent names collide after sanitization:
```typescript
test("no two skill names collide after sanitization", async () => {
const plugin = await loadClaudePlugin(pluginRoot)
const sanitized = plugin.skills.map((skill) => sanitizePathName(skill.name))
const unique = new Set(sanitized)
expect(unique.size).toBe(sanitized.length)
})
```
### 2. When adding names to filesystem paths
Always use `sanitizePathName()` when constructing output paths from skill, agent, or component names. Never pass `skill.name` or `agent.name` directly to `path.join()` in target writers or managed artifact paths.
### 3. When building dedupe sets in converters
If a converter reserves names for collision detection, the reserved names must be sanitized to match what the writer will produce on disk. Raw names in the set + normalized names from generators = missed collisions.
### 4. Inconsistency with `resolveCommandPath`
Note that `resolveCommandPath` (used for commands) converts colons to nested directories (`ce:plan` -> `ce/plan.md`), while `sanitizePathName` (used for skills/agents and compatibility artifact paths) converts to hyphens (`ce:plan` -> `ce-plan`). This is intentional — commands and skills are different surfaces with different resolution patterns. If a new component type is added, decide which pattern fits and document the choice.
@@ -0,0 +1,159 @@
---
title: "Cross-platform model field normalization for target converters"
date: 2026-03-29
category: integration-issues
module: src/converters
problem_type: integration_issue
component: tooling
symptoms:
- "Target platforms received raw Claude model aliases (e.g., 'sonnet') they could not resolve"
- "Qwen converter mapped model aliases to wrong canonical names (claude-sonnet instead of claude-sonnet-4-6)"
- "OpenClaw and Copilot passed through unnormalized model values in formats the target could not use"
- "Duplicated CLAUDE_FAMILY_ALIASES and normalizeModel logic across converters with divergent alias values"
root_cause: config_error
resolution_type: code_fix
severity: medium
tags:
- model-normalization
- converters
- cross-platform
- opencode
- qwen
- droid
- copilot
- openclaw
- codex
---
# Cross-platform model field normalization for target converters
## Problem
Claude Code uses bare model aliases (`model: sonnet`, `model: haiku`, `model: opus`) in agent and command frontmatter. Each target platform expects a different format for the model field, but the converters handled this inconsistently — some passed through raw values, others had duplicated normalization logic with wrong alias mappings.
## Symptoms
- OpenClaw passed `model: sonnet` through raw — invalid on a platform expecting `anthropic/claude-sonnet-4-6`
- Qwen mapped `sonnet` to `anthropic/claude-sonnet` instead of `anthropic/claude-sonnet-4-6` (wrong alias in its local copy of `CLAUDE_FAMILY_ALIASES`)
- Copilot passed through raw Claude model IDs like `claude-sonnet-4-20250514` — Copilot uses display-name format ("Claude Opus 4.5"), not model IDs
- Codex emitted no model field — correct behavior, but accidental (no deliberate handling)
- Droid passed through as-is — correct behavior, but undocumented as intentional
- Two copies of `CLAUDE_FAMILY_ALIASES` existed in OpenCode and Qwen converters with divergent values
## What Didn't Work
- **Passing model through as-is**: works for Droid (Factory natively resolves bare aliases), breaks OpenClaw/Qwen/OpenCode
- **Mapping bare aliases to incomplete model names**: Qwen's `sonnet` -> `claude-sonnet` was wrong; correct is `claude-sonnet-4-6`
- **Assuming all targets want the same model format**: each platform has fundamentally different expectations
- **Assuming Codex skills support model overrides in frontmatter**: they don't — confirmed by the Rust source `SkillFrontmatter` struct which only has `name` and `description`
- **Initial assumption that Qwen should drop model entirely**: wrong — Qwen is multi-provider and supports Anthropic models via `settings.json` with `anthropic` provider config
- **Initial assumption that Copilot doesn't support models**: wrong — Copilot supports multi-model including Claude, but the exact format is uncertain (display names vs model IDs)
## Solution
Created `src/utils/model.ts` with shared normalization utilities:
```typescript
// Single source of truth for bare Claude family aliases
export const CLAUDE_FAMILY_ALIASES: Record<string, string> = {
haiku: "claude-haiku-4-5",
sonnet: "claude-sonnet-4-6",
opus: "claude-opus-4-6",
}
// Resolve bare alias without provider prefix (used by Droid)
export function resolveClaudeFamilyAlias(model: string): string
// Add provider prefix based on naming conventions
export function addProviderPrefix(model: string): string
// Combined: resolve + prefix (used by OpenCode, Qwen, OpenClaw)
export function normalizeModelWithProvider(model: string): string
```
Each converter uses the appropriate shared utility:
| Target | Behavior | Output for `model: sonnet` |
|--------|----------|----------------------------|
| OpenCode | Resolve alias + add provider prefix | `anthropic/claude-sonnet-4-6` |
| Droid | Pass through as-is | `sonnet` |
| Copilot | Drop entirely | (omitted) |
| Codex | Drop entirely | (omitted) |
> **Note:** This doc was written when the converter set also included Qwen and OpenClaw, both of which used the "Resolve alias + add provider prefix" behavior. Both have since been removed in favor of native plugin install — see `docs/solutions/integrations/native-plugin-install-strategy.md`. The pattern still applies to any future multi-provider target with the `provider/model-id` format.
---
## Why This Works
Each platform has fundamentally different model handling requirements:
**Platforms that normalize (OpenCode, Qwen, OpenClaw):** These are multi-provider platforms that support Anthropic, OpenAI, Google, and other model providers. They need provider-prefixed IDs like `anthropic/claude-sonnet-4-6` to route requests to the correct backend. The `normalizeModelWithProvider` function resolves bare aliases and adds the appropriate prefix.
**Droid (Factory) — pass-through:** Factory is multi-provider but natively resolves Claude's bare aliases (`sonnet`, `opus`, `haiku`) internally. Pass-through is correct and simpler than normalizing to a format Factory would also accept but doesn't require. Factory also accepts full dated model IDs like `claude-sonnet-4-5-20250929` and non-Anthropic models prefixed with `custom:`.
**Copilot — drop:** Copilot supports a `model` field in `.agent.md` frontmatter (documented in `docs/specs/copilot.md`), but the expected values are Copilot-specific display names like "Claude Opus 4.5" — not Claude model IDs like `claude-sonnet-4-20250514` or bare aliases like `sonnet`. Passing through Claude-specific values would emit a field Copilot can't use. Unlike Droid (which natively resolves `sonnet`), Copilot has no documented resolution for Claude model IDs. Dropping is safer: the spec says "If unset, inherits the default model."
**Codex — drop:** Codex skill frontmatter (`SKILL.md`) only supports `name` and `description` fields. This was confirmed by examining the Rust source code (`SkillFrontmatter` struct in `codex-rs/core-skills/src/loader.rs`). Model selection in Codex is global via `config.toml` or runtime `/model` command, not per-skill.
---
## Target platform model field reference
This reference captures research findings as of 2026-03-29. Targets marked **(removed)** below no longer have custom Bun converters — they rely on native plugin install. The research is preserved as a future reference if those targets re-enter the converter set.
### OpenCode
- **Model format:** `provider/model-id` (e.g., `anthropic/claude-sonnet-4-6`)
- **Provider prefixes:** `anthropic/`, `openai/`, `google/`
- **Docs:** Agents defined in `.opencode/agents/*.md`
### Qwen (removed)
- **Model format:** `provider/model-id` (e.g., `anthropic/claude-sonnet-4-6`)
- **Multi-provider:** Yes — supports Anthropic, OpenAI, Google GenAI via `settings.json`
- **Configuration example:** `"anthropic": [{"id": "claude-sonnet-4-20250514", "name": "Claude Sonnet 4", "envKey": "ANTHROPIC_API_KEY"}]`
- **Common misconception:** Qwen is NOT limited to its own foundation model
### Droid (Factory)
- **Model format:** Bare names (`sonnet`, `claude-sonnet-4-5-20250929`) or `custom:<model>` for BYOK
- **Native alias resolution:** Factory resolves `sonnet`, `opus`, `haiku` internally
- **Multi-provider:** Yes — supports Anthropic, OpenAI, Google, and Factory's own `droid-core`
- **Docs:** Custom droids defined in `.factory/droids/*.md`
### Copilot
- **Model format:** Display names (e.g., "Claude Opus 4.5", "GPT-5.2"), possibly array syntax `model: ['Claude Opus 4.5', 'GPT-5.2']`
- **Multi-provider:** Yes — supports Claude and GPT models
- **Current converter behavior:** Drop (Claude model IDs don't map to Copilot's expected format)
- **Note:** Spec says "may be ignored on github.com" — model selection works in IDE but may not apply on the GitHub web platform
- **Docs:** Agents defined in `.github/agents/*.agent.md`
### OpenClaw (removed)
- **Model format:** `provider/model-id` (same as OpenCode)
- **Docs:** Skills defined in `skills/*/SKILL.md`
### Codex
- **Model field in skill frontmatter:** NOT SUPPORTED
- **Supported frontmatter fields:** `name`, `description` only
- **Model configuration:** Global `config.toml` (`model = "gpt-5.4"`) or runtime `/model` command
- **Valid model IDs (as of 2026-03):** `gpt-5.4` (flagship), `gpt-5.4-mini` (fast), `gpt-5.3-codex` (coding-specialized)
- **Deprecated:** `codex-mini-latest` (removed Feb 2026)
- **Docs:** Skills defined in `.codex/skills/*/SKILL.md` or `.agents/skills/*/SKILL.md`
---
## Prevention
1. **Research before implementing:** When adding a new converter target, research its model field format with external documentation before assuming pass-through or copying from another converter. The format varies significantly between platforms.
2. **Single source of truth:** The `CLAUDE_FAMILY_ALIASES` map in `src/utils/model.ts` is the canonical alias map. Update it there — not in individual converters — when new Claude model generations are released.
3. **Test coverage:** Run `bun test` after model-related changes. The test suite covers model handling across all converters (`tests/model-utils.test.ts` plus each converter's test file).
4. **Don't assume format from the field name:** A `model` field in frontmatter doesn't mean the format is the same across platforms. OpenCode wants `anthropic/claude-sonnet-4-6`, Factory wants `sonnet`, Copilot wants "Claude Sonnet 4", and Codex doesn't support the field at all.
5. **When in doubt, drop:** If you can't confidently produce the target's expected format, omit the field rather than emitting a potentially invalid value. Most platforms fall back to a sensible default when model is unset.
## Related Issues
- `docs/solutions/adding-converter-target-providers.md` — Converter architecture doc; should be updated to reference model normalization as part of the conversion pattern
- `docs/solutions/integrations/colon-namespaced-names-break-windows-paths.md` — Structural analog: same pattern of per-target boundary normalization
- `docs/specs/codex.md` — Platform spec (last verified 2026-01-21); confirms skill frontmatter limitations
@@ -0,0 +1,92 @@
---
title: "Use native Kimi plugin manifests instead of a converter target"
date: 2026-06-24
category: integrations
module: installer
problem_type: tooling_decision
component: tooling
severity: medium
applies_when:
- "Adding support for a coding-agent platform that can install plugin manifests directly"
- "A proposed converter target duplicates a platform-native plugin or marketplace flow"
- "Release automation must keep platform manifests in parity with canonical plugin metadata"
related_components:
- release-metadata
- release-please
- native-plugin-install
tags:
- kimi
- native-plugins
- marketplace
- release-validation
- converter-targets
---
# Use native Kimi plugin manifests instead of a converter target
## Problem
Kimi Code support can look like a normal new target provider at first: add `--to kimi`, write a converter, and emit a Kimi-specific output tree. That is the wrong first move when the platform already has a native plugin manifest and custom marketplace contract.
This came up when [PR #997](https://github.com/EveryInc/compound-engineering-plugin/pull/997) by [@mastepanoski](https://github.com/mastepanoski) proposed Kimi support as a converter target. The useful signal was the demand for Kimi support; the implementation shape duplicated a native install surface.
## Decision
Prefer Kimi's native plugin metadata over a converter target:
- Commit `.kimi-plugin/plugin.json` with the Kimi manifest fields that describe this plugin, including `interface`, `skills`, `sessionStart.skill`, and `skillInstructions`.
- Commit `.kimi-plugin/marketplace.json` using Kimi marketplace schema version `2`.
- Keep `.kimi-plugin/plugin.json` in the root release component so release automation bumps it with the canonical plugin version.
- Treat `.kimi-plugin/marketplace.json` as static catalog metadata, and validate it instead of version-bumping it.
- Do not add `src/converters/claude-to-kimi.ts`, `src/targets/kimi.ts`, or a `--to kimi` CLI target unless Kimi later documents a separate generated output format that cannot be represented by the native manifest.
## Why This Matters
Native plugin support is a distribution contract, not a format-conversion problem. A converter target would create another generated install path to document, test, version, and clean up, while Kimi users would still need the manifest and marketplace metadata for the normal install flow.
The correct support surface is therefore:
- platform metadata: `.kimi-plugin/plugin.json` and `.kimi-plugin/marketplace.json`
- release metadata: version parity, required fields, skill path checks, and marketplace schema validation
- user docs: install and local-development instructions for Kimi's native plugin flow
- target spec docs: a short spec explaining which Kimi fields are used and which unsupported runtime fields are intentionally absent
## Implementation Pattern
When adding a platform with a native plugin surface, wire it like a first-class release surface:
1. Add the native manifest and marketplace/catalog files expected by the platform.
2. Put the release-owned manifest file in `.github/release-please-config.json` as an extra file for the canonical component.
3. Exclude static marketplace files from release component ownership when they do not carry a version.
4. Add release validation that rejects missing manifests, version drift, missing declared asset paths, marketplace schema drift, and marketplace plugin-list drift.
5. Update README install docs and `docs/specs/` so future contributors know this is native metadata, not a converter target.
For Kimi specifically, validate at least:
- `.kimi-plugin/plugin.json` exists
- `name`, `version`, `description`, and `skills` are non-empty
- `skills` points at an existing directory in the repo
- the Kimi manifest version equals the root plugin/package version
- `.kimi-plugin/marketplace.json` has schema `version: "2"`
- marketplace plugin IDs match the Claude marketplace plugin IDs
- each marketplace entry has a non-empty `source`
- root-local marketplace sources such as `"."` or `"./"` are rejected because they are only local-development placeholders
## Warning Signs
Reconsider a proposed new target provider when:
- the platform docs describe a `plugin.json`-style manifest in the source repo
- the platform supports a custom marketplace or catalog pointing at repository/plugin sources
- the target would mostly copy existing skills and docs without meaningful tool, permission, hook, or model conversion
- install docs would need to tell users to run this repo's converter instead of the platform's documented plugin install path
Those are signs the platform support belongs in native metadata and release validation.
## Related
- [Native plugin install strategy](./native-plugin-install-strategy.md)
- [Plugin versioning requirements](../plugin-versioning-requirements.md)
- [Adding converter target providers](../adding-converter-target-providers.md)
- [PR #997: original Kimi support proposal](https://github.com/EveryInc/compound-engineering-plugin/pull/997)
- [PR #998: native Kimi plugin support](https://github.com/EveryInc/compound-engineering-plugin/pull/998)
@@ -0,0 +1,180 @@
---
title: "Native plugin install strategy for supported harnesses"
date: 2026-06-19
last_updated: 2026-06-30
category: integrations
module: installer
problem_type: integration_decision
component: installer
symptoms:
- "Formal standalone agent definitions are unevenly supported across coding-agent harnesses"
- "Custom Bun installs create extra update and cleanup behavior for users"
- "OpenCode and Pi can load skills directly from a git-backed package/plugin shape"
root_cause: evolving_platform_install_surfaces
resolution_type: install_strategy
severity: medium
tags:
- install-strategy
- native-plugins
- cursor
- codex
- copilot
- droid
- qwen
- kimi
- antigravity
- opencode
- pi
- cline
---
# Native Plugin Install Strategy
Last verified: 2026-06-20
Compound Engineering now treats the plugin as a self-contained skills package. Specialist reviewer and researcher behavior lives in skill-local prompt assets under `references/agents/` or `references/personas/`, and skills seed generic subagents with those files when the current harness exposes a subagent primitive. There are no formal standalone CE agents in the plugin surface.
The install strategy follows from that: prefer each harness's native plugin/package mechanism, avoid generated agent installs, and keep the Bun converter as repo tooling rather than the user-facing installer.
## Summary
| Harness | Current install path | Bun CLI needed? | Notes |
| --- | --- | --- | --- |
| Claude Code | Native plugin marketplace using `.claude-plugin/marketplace.json` and `.claude-plugin/plugin.json` | No | Claude remains the source plugin format. |
| Codex | Native Codex plugin install from a custom marketplace pointing at this repository root | No | Codex App users add the marketplace manually with no sparse path; Codex CLI users register the repo and install through `/plugins`. Skill-local personas avoid the old custom-agent copy step. |
| Cursor | Native Cursor Plugin Marketplace using `.cursor-plugin/marketplace.json` and `.cursor-plugin/plugin.json` | No | Users install from Cursor Agent chat with `/add-plugin compound-engineering` or marketplace search. |
| GitHub Copilot CLI | Native plugin marketplace using the existing Claude plugin metadata | No | Copilot translates the Claude plugin metadata itself. |
| Factory Droid | Native plugin marketplace pointed at the CE GitHub repository | No | Droid translates Claude Code plugins automatically. |
| Qwen Code | Native extension install from the CE GitHub repository and existing Claude plugin metadata | No | Qwen translates Claude Code extensions automatically. |
| Kimi Code CLI | Native plugin install from this repository using `.kimi-plugin/plugin.json` | No | Kimi can install directly from the GitHub repo and can browse the committed `.kimi-plugin/marketplace.json` custom catalog. |
| OpenCode | Git-backed OpenCode plugin entry in `opencode.json` | No | `.opencode/plugins/compound-engineering.js` registers the CE skills directory directly. |
| Pi | Git-backed Pi package install from this repository | No | Root `package.json` exposes `.pi/extensions/compound-engineering.ts` and the CE skills directory. `pi-ask-user` is a recommended companion for richer prompts. |
| Antigravity CLI | Native plugin install from root `plugin.json` + `skills/`, or bundled `.agy/` entry point | No | `agy plugin install https://github.com/EveryInc/compound-engineering-plugin` for one-command remote install. `.agy/plugin.json` symlinks to the root manifest; `.agy/skills` symlinks to `skills/`. |
| Cline | Native skills install via `.cline/scripts/install-skills.sh` | No | Symlinks invocable CE skills into `~/.cline/skills/` or `.cline/skills/`, skipping manual-only skills (`disable-model-invocation: true`). Enable Skills in the Cline extension settings. |
Kiro is no longer a documented CE install target. Historical converter and cleanup code may remain for regression coverage or old artifact handling, but user-facing install docs should not advertise Kiro.
## OpenCode
OpenCode can load plugins from git package entries in `opencode.json`. CE ships `.opencode/plugins/compound-engineering.js`, which resolves the repository's `skills` directory and appends it to OpenCode's skill paths.
Recommended config:
```json
{
"plugin": ["compound-engineering@git+https://github.com/EveryInc/compound-engineering-plugin.git"]
}
```
For local development, point OpenCode at this checkout:
```json
{
"plugin": ["/path/to/compound-engineering-plugin/.opencode/plugins/compound-engineering.js"]
}
```
This replaces the old custom OpenCode Bun install path for normal CE users. The converter can still exist as development or compatibility tooling, but it is not the primary install story.
## Pi
Pi can install packages from git repositories. CE exposes a Pi package through root `package.json`:
```json
{
"pi": {
"extensions": ["./.pi/extensions/compound-engineering.ts"],
"skills": ["./skills"]
}
}
```
Install:
```bash
pi install git:github.com/EveryInc/compound-engineering-plugin
```
Recommended companion:
```bash
pi install npm:pi-subagents
pi install npm:pi-ask-user
```
`pi-subagents` is required for CE workflows that dispatch reviewer, research, or implementation subagents. `pi-ask-user` is only for richer blocking question UX.
For local development:
```bash
pi -e /path/to/compound-engineering-plugin
```
## Antigravity CLI
Antigravity installs plugins from a local directory or a remote Git URL when the source root contains `plugin.json` and `skills/`. CE publishes both at the repository root.
Recommended one-command install:
```bash
agy plugin install https://github.com/EveryInc/compound-engineering-plugin
```
Local checkout:
```bash
git clone https://github.com/EveryInc/compound-engineering-plugin
agy plugin install ./compound-engineering-plugin
```
The committed `.agy/` bundle remains for explicit local installs (`agy plugin install ./compound-engineering-plugin/.agy`). Its `plugin.json` symlinks to the root manifest and `skills` symlinks to `../skills`.
`agy` still reads `GEMINI.md` as workspace context. See `.agy/INSTALL.md` for pinning, validation, and uninstall.
## Cline
Cline discovers skills from `~/.cline/skills/` (global) or `.cline/skills/` (project). CE ships `.cline/scripts/install-skills.sh`, which symlinks each directory under this repository's `skills/` into the chosen destination.
Recommended global install:
```bash
git clone https://github.com/EveryInc/compound-engineering-plugin
./compound-engineering-plugin/.cline/scripts/install-skills.sh --global
```
For local development from a checkout:
```bash
/path/to/compound-engineering-plugin/.cline/scripts/install-skills.sh --global
```
Enable **Settings -> Features -> Enable Skills** in the Cline extension, then start a new task. The install script skips manual-only skills marked `disable-model-invocation: true` in frontmatter (for example `lfg`, `ce-dogfood`, `ce-polish`) because Cline auto-activates skills from description matching alone. Pass `--include-manual` to link those skills for slash-command use, accepting that Cline may still auto-activate them. CE does not ship a separate Cline CLI `AgentPlugin` entry point; skills are the install surface.
## Kimi Code CLI
Kimi Code CLI has a native plugin surface, so CE should not maintain a Kimi converter target for normal installation. The root `.kimi-plugin/plugin.json` declares the CE skills directory with `skills: "./skills/"` and carries display metadata through Kimi's `interface` object.
Direct install:
```text
/plugins install https://github.com/EveryInc/compound-engineering-plugin
```
Marketplace install:
```text
/plugins marketplace https://raw.githubusercontent.com/EveryInc/compound-engineering-plugin/main/.kimi-plugin/marketplace.json
```
The Kimi marketplace catalog uses schema version `"2"` and entries with `id` plus `source`. It has no release-owned marketplace version, so release automation bumps only `.kimi-plugin/plugin.json` through the root `compound-engineering` component. `bun run release:validate` enforces Kimi manifest and marketplace parity with the Claude source manifest/catalog.
## Bun Package Posture
The root package remains useful for:
- Repo development scripts and tests.
- OpenCode package metadata (`main`).
- Pi package metadata (`pi` field).
- Shared converter code and regression tests for historical or fixture targets.
It is not a public npm installer. Release automation should not publish `@every-env/compound-plugin`, and README install instructions should not rely on `bunx`.
@@ -0,0 +1,120 @@
---
title: OpenCode converter emits a temperature that Sonnet 5 / Opus 4.8 reject
module: src/converters/claude-to-opencode.ts
date: 2026-07-08
problem_type: integration_issue
component: tooling
severity: high
symptoms:
- Converted OpenCode primary agent fails at runtime with HTTP 400 from Anthropic
- Breakage appears only after bumping the Claude family alias map to claude-sonnet-5 / claude-opus-4-8
- inferTemperature (CLI default) emits a temperature (0.1-0.6) on every converted agent
- Primary agents pinned to claude-sonnet-5 or claude-opus-4-8 reject non-default temperature/top_p/top_k
- The same conversion worked previously under claude-sonnet-4-6, which accepts temperature
root_cause: config_error
resolution_type: code_fix
related_components:
- src/utils/model.ts
- tests/model-utils.test.ts
- tests/converter.test.ts
tags:
- opencode
- converter
- temperature
- sampling-params
- claude-sonnet-5
- claude-opus-4-8
- model-alias
---
# OpenCode converter emits a temperature that Sonnet 5 / Opus 4.8 reject
## Problem
Bumping the `sonnet`/`opus` aliases to a newer Claude generation (Sonnet 5, Opus 4.8) made the OpenCode converter emit an inferred `temperature` into primary-agent configs for models that reject non-default sampling params, producing configs the target runtime rejects with HTTP 400.
## Symptoms
- Converted OpenCode primary-agent configs carried an explicit `temperature` for agents pinned to Sonnet 5 (and Opus 4.7/4.8).
- The generated config triggers an HTTP 400 from Anthropic at runtime because Sonnet 5 / Opus 4.7+ reject non-default `temperature`/`top_p`/`top_k`.
- No test failed — no existing test exercised a primary OpenCode agent on a new-generation model, so the breakage was invisible in the suite.
## What Didn't Work
The regression is easy to miss because cause and effect live in different files with no obvious link:
- The triggering change — bumping the `sonnet` alias to Sonnet 5 — looks purely mechanical: a single string swap in an alias map (`CLAUDE_FAMILY_ALIASES` in `src/utils/model.ts`). Nothing at the edit site hints that temperature emission is affected.
- The failing behavior lives elsewhere: the converter's `inferTemperature` emission is in `src/converters/claude-to-opencode.ts`. Reviewing the alias bump in isolation gives no signal.
- No existing test exercised a primary OpenCode agent on a new-generation model, so the suite stayed green.
A naive fix would over- or under-reach:
- Dropping `temperature` for **all** agents over-reaches — models that still accept sampling params (Sonnet 4, Haiku) would lose a valid inferred temperature.
- String-matching `"sonnet"` under-reaches and misclassifies — it would wrongly suppress temperature for older accepting Sonnets (`claude-sonnet-4-20250514`) while missing rejecting Opus generations (`claude-opus-4-7`, `claude-opus-4-8`) entirely.
## Solution
Introduce a precise, canonical-ID-based predicate for "this model rejects sampling params," and gate the converter's temperature emission on it.
Added `rejectsSamplingParams` in `src/utils/model.ts`, backed by a set of canonical IDs. It resolves bare aliases via `resolveClaudeFamilyAlias` and strips the `anthropic/` provider prefix, so every spelling of the same model matches:
```ts
const SAMPLING_PARAM_REJECTING_MODELS: ReadonlySet<string> = new Set([
"claude-sonnet-5",
"claude-opus-4-7",
"claude-opus-4-8",
])
export function rejectsSamplingParams(model: string): boolean {
const canonical = resolveClaudeFamilyAlias(model).replace(/^anthropic\//, "")
return SAMPLING_PARAM_REJECTING_MODELS.has(canonical)
}
```
This matches `sonnet`, `claude-sonnet-5`, and `anthropic/claude-sonnet-5`; it does **not** match `claude-sonnet-4-20250514` or `haiku`.
In `convertAgent` (`src/converters/claude-to-opencode.ts`), the temperature emission is gated so it is skipped only when a rejecting model was actually written to the config. The converter writes `model` only for primary agents, so the `frontmatter.model !== undefined` guard scopes this to primary agents; subagents (no model written — they inherit the parent session's model) keep existing behavior, out of scope because the runtime model is unknown at convert time.
Before:
```ts
if (options.inferTemperature) {
const temperature = inferTemperature(agent)
if (temperature !== undefined) {
frontmatter.temperature = temperature
}
}
```
After:
```ts
if (options.inferTemperature) {
const temperature = inferTemperature(agent)
const modelRejectsTemperature =
frontmatter.model !== undefined &&
typeof agent.model === "string" &&
rejectsSamplingParams(agent.model)
if (temperature !== undefined && !modelRejectsTemperature) {
frontmatter.temperature = temperature
}
}
```
## Why This Works
Sonnet 5 and Opus 4.7/4.8 return HTTP 400 for any non-default `temperature`/`top_p`/`top_k` (per Anthropic's Sonnet 5 and Opus 4.8 migration notes). The converter only writes `model` into the config for primary agents, so tying suppression to "we wrote a rejecting model" (`frontmatter.model !== undefined && rejectsSamplingParams(agent.model)`) targets exactly the case that would fail at runtime — a primary agent pinned to a rejecting model — without touching subagents or any model that still accepts sampling params.
## Prevention
The compounding lesson: **a model alias bump is never purely mechanical.** When you point an alias at a newer Claude generation, the model ID string is the smallest part of the change — audit every downstream emitter for API constraints that shifted with the generation. Newer generations (Sonnet 5+, Opus 4.7+) reject non-default sampling params, so any code path that emits `temperature`/`top_p`/`top_k` for a resolved Claude model must gate on model compatibility.
Concrete guardrails:
- Keep `SAMPLING_PARAM_REJECTING_MODELS` in sync with `CLAUDE_FAMILY_ALIASES` whenever a new generation is added — both live in `src/utils/model.ts` and are co-located intentionally so the two are edited together.
- Test both directions so neither over- nor under-reach regresses: a rejecting model (temperature suppressed) and an accepting model (temperature still inferred). The tests added are `rejectsSamplingParams` unit tests in `tests/model-utils.test.ts` (alias resolution, the `anthropic/` prefix, and the accepting cases `claude-sonnet-4-20250514` / `haiku`), plus a converter test in `tests/converter.test.ts` asserting temperature is suppressed for a Sonnet 5 primary agent but still inferred (0.1) for a Haiku agent.
- Verify bot-sourced API claims against the source of truth. An automated cross-model code-review bot (Codex) caught this before merge, but its factual claim about the HTTP 400 was confirmed against authoritative Anthropic migration docs before building the fix — the claim was true, but bot claims about API behavior must always be verified first.
## Related
- [`cross-platform-model-field-normalization.md`](cross-platform-model-field-normalization.md) — the sibling doc covering how bare Claude aliases resolve to provider-prefixed canonical IDs across target platforms. That doc owns the alias-to-provider normalization *mechanism* (`CLAUDE_FAMILY_ALIASES`, `normalizeModelWithProvider`); this doc covers a distinct downstream constraint (sampling-param API limits of newer generations). Both live in `src/utils/model.ts`.