Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ When encountering file references (e.g., @references/workflow.md), use Read tool

<product_overview>
<entry_point>index.ts</entry_point>
<auto_init>agent registration from bundled assets on startup</auto_init>
<auto_init>agent, skill, and command registration from bundled assets on startup</auto_init>
<build_output>ESM + type declarations</build_output>
</product_overview>

Expand All @@ -49,6 +49,10 @@ When encountering file references (e.g., @references/workflow.md), use Read tool
- Delegate to `opencode-publisher`, fed by the packager's output
- Transforms to npm-ready structure, adds CLI entry point, extracts installer module
</stage>
<stage name="Upgrade" trigger="existing consumer project still on opencode v1">
- Delegate from `architect` to the bundled `opencode-v2-upgrade` skill
- Inventories extensions, ports v1 plugin code and v1 tool files, rewrites configs to v2-native keys, and reports v2 recommendations; no-ops on already-v2 projects, skips consumer-modified files, and never touches the OpenCode app installation
</stage>
</happy_path>

<agent_categories>
Expand Down
11 changes: 11 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ publishes it.
The reference routing examples (request → analysis → agent selection →
execution order) the architect reads before delegating.

**Upgrade skill**:
The bundled `opencode-v2-upgrade` SKILL.md the plugin registers at load. It
defines the one-shot consumer v1→v2 upgrade: inventory, plugin and tool port,
config rewrite, and a recommendations report.
_Avoid_: migration guide (it is a procedure, not prose documentation)

**Upgrade command**:
The bundled `upgrade-opencode-v2` slash command the plugin registers at load.
It submits the upgrade prompt and attaches the upgrade skill.
_Avoid_: upgrade tool

### Distribution

**Consumer**:
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ Also bundled and installed with the agents:

- **References** — self-contained docs covering stable OpenCode fundamentals: agents, commands, config, MCP servers, plugins, prompt engineering, skills, tools, plus worked one-shot examples
- **Templates** — starter files for new skills, plugins, package manifests, and TypeScript configs
- **Upgrade skill and command** — the `opencode-v2-upgrade` skill plus the `/upgrade-opencode-v2` slash command, registered at load

## One-shot upgrade from OpenCode v1 to v2

Say "upgrade my plugin/extensions package to opencode v2" (or run `/upgrade-opencode-v2`) and the suite upgrades one project in a single pass: it inventories your extensions, ports v1 plugin files to the Effect-first v2 plugin API, ports v1 file-based tool files to plugin-registered tools, rewrites your configs to v2-native keys, and finishes with a report recommending v2 capabilities to adopt — richer session hooks, plugin RPC, TUI plugins, MCP Code Mode, and saved approvals.

The upgrade is safe by construction: an already-v2 project is a clean no-op, files you modified after install are skipped with a warning rather than clobbered, and the OpenCode application installation is never touched. The bundled `opencode-v2-upgrade` skill defines the full procedure and every phase's completion criteria.

## When to use these OpenCode agents

Expand Down
2 changes: 2 additions & 0 deletions agents/opencode-architect.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Before any routing decision, read `../references/opencode-architect-oneshots.md`
Route by first match in priority order, delegating through the task tool:

1. Explicit request for an agent: obey the user's choice.
1b. Upgrade an existing project or package from OpenCode v1 to v2 ("upgrade my plugin/extensions package to opencode v2", or the `/upgrade-opencode-v2` command): load the bundled upgrade skill `../skills/opencode-v2-upgrade/SKILL.md` and follow it end to end, delegating each phase to the named specialist (auditor for the inventory, plugin-engineer for the plugin port, tool-builder for the v1 tool-file port); the config rewrite and the recommendations report are the skill's own steps.
2. Create or refine agent definitions and prompts: opencode-agent-designer.
3. Analyze `.opencode/` contents or packaging readiness: opencode-extension-auditor.
3b. Assess an existing built package for conformance to this suite's design ("is this aligned with opencode-architect guidance?", "assess conformance to best practice", "does it account for the manifest implementation?"): opencode-extension-auditor, prompted for a conformance review of the named package path against `../references/conformance-checklist.md`, reporting item verdicts with file:line evidence. Preflight: the prompt requires the auditor to report the absolute path + version of the criteria copy it resolved, and to refuse a Conformant verdict when that copy is stale relative to this suite's repo.
Expand Down Expand Up @@ -93,6 +94,7 @@ Bundled reference files are addressed relative to this agent file's own director
- Use `../references/mcp-servers.md` for MCP configuration and scoping.
- Use `../references/config.md` for config precedence and schema options.
- Use `../references/prompt-engineering.md` for prompt engineering and skill-authoring techniques.
- Use `../skills/opencode-v2-upgrade/SKILL.md` for the one-shot v1→v2 consumer upgrade; follow it end to end when the request is an upgrade.

## Reference resolution

Expand Down
16 changes: 16 additions & 0 deletions commands/upgrade-opencode-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
description: "Upgrade this project's OpenCode extensions to v2 — inventory, plugin and tool port, config rewrite, and a v2 recommendations report"
---

Act as `opencode-architect` and run the suite's one-shot OpenCode v1 → v2
upgrade for this project. Load and follow the `opencode-v2-upgrade` skill end
to end: inventory the extensions, port every v1 plugin file to the
Effect-first v2 plugin API, port v1 file-based tool files to plugin-registered
tools, rewrite the configs to v2-native keys per the verified mapping, and
finish with the v2 capability recommendations report.

Honor the skill's safety contract: no-op on an already-v2 project, skip
consumer-modified files with a warning, and never touch the OpenCode
application installation.

$ARGUMENTS
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
"src",
"agents",
"references",
"templates"
"templates",
"skills",
"commands"
],
"dependencies": {
"@opencode/plugin": "2.0.23",
Expand Down
36 changes: 36 additions & 0 deletions references/opencode-architect-oneshots.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,39 @@ export default Plugin.define({
3. Sequential: `opencode-packager` (package for local sharing first)
4. Ask: "Package ready. Publish to npm?"
5. If yes, Sequential: `opencode-publisher` (transform and publish)

---

## Example 10: One-Shot v1 → v2 Consumer Upgrade

**User Request:**
> Upgrade my plugin/extensions package to opencode v2.

**Analysis:**
- Whole-project v1→v2 upgrade → the bundled `opencode-v2-upgrade` skill
(registered by this package at load), executed by the architect and its
specialists
- Inventory → `opencode-extension-auditor`
- Plugin port → `opencode-plugin-engineer`
- V1 tool-file port → `opencode-tool-builder`
- Config rewrite and recommendations report → the skill's own steps

**Execution:**
1. Load `opencode-v2-upgrade` (SKILL.md) and run it end to end.
2. Sequential: `opencode-extension-auditor` — inventory skills, commands,
agents, plugins, v1 file-based tool files, and configs (facts §12).
3. If the inventory finds no v1 remnant: report the clean no-op and stop.
4. Sequential: `opencode-plugin-engineer` — port v1 plugin files to the
Effect-first v2 plugin API (facts §1, §2, §9).
5. Sequential: `opencode-tool-builder` — port v1 file-based tool files to
plugin-registered tools (facts §8).
6. Rewrite each config to v2-native keys per the verified mapping
(facts §5, §6).
7. Report: consumer-modified files skipped by manifest hash, then the v2
recommendations — session hooks, plugin RPC, TUI plugins, MCP Code Mode,
saved approvals (facts §4, §9, §10, §11).

Honor the safety contract throughout: a clean no-op on an already-v2
project, modified files skipped with a warning rather than clobbered, and the
OpenCode application installation never touched (facts §7).

174 changes: 174 additions & 0 deletions skills/opencode-v2-upgrade/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
name: "opencode-v2-upgrade"
description: "Upgrades a consumer's OpenCode v1 extension package or .opencode/ tree to OpenCode v2 in one pass: extension inventory, plugin code port to the Effect-first plugin API, v1 tool-file port to plugin-registered tools, config rewrite to v2-native keys, and a v2 capability recommendations report. Use when the user asks to upgrade, migrate, or modernize a plugin, custom tools, or an extensions package from OpenCode v1 to v2."
---

# OpenCode v2 one-shot consumer upgrade

Run a v1 → v2 upgrade for one project in a single pass. Every opencode API
claim below traces to the suite's verified-facts record (cited as "facts §n");
the mapping and rules in this skill are self-contained, so cite the fact
number rather than restating fragile API details. The facts record itself
lives in the suite repository as `opencode-v2-facts.md` — a maintainer source
of truth, not a consumer runtime dependency.

## Done when

- An inventory of the project's extensions exists, each classified v1 or v2.
- Every v1 plugin file runs under the v2 Effect-first plugin API.
- Every v1 file-based tool file is a plugin-registered tool.
- Every consumer config is rewritten to v2-native keys per the mapping below.
- A recommendations report names the v2 capabilities worth adopting.
- Already-v2 projects are left byte-for-byte untouched (a clean no-op).

## Safety contract (non-negotiable)

1. **Never touch the OpenCode application installation.** Do not install,
upgrade, pin, or remove the `opencode` binary, its global cache, or its
npm package. The upgrade changes the consumer's project only (facts §7).
2. **No-op on already-v2 projects.** If the inventory finds no v1 remnant,
stop and report zero changes. Re-running the upgrade is always safe
(facts §5, §6, §9).
3. **Never clobber consumer-modified files.** Before rewriting any file the
suite's manifest tracks (`<scope base>/<package>.manifest.json`), compare
its current sha256 against the recorded hash. On mismatch, skip the file
with a warning and list it in the report; taking ownership is CLI-only
behind an explicit `--force` (facts §5, §13 row 10). Manifest rules are
the source of truth: version match alone is not enough.
4. **Preserve what you cannot parse.** A config file that fails to parse is
reported and left byte-for-byte; never rewrite it from an empty object
(facts §5, §13 row 3).
5. **Surgical config writes.** Splice v2 keys into place; never
parse-and-reserialize a file with comments or custom formatting.
6. **Legacy entries are read-only.** A singular v1 `plugin` entry — and the
inert legacy `config.json` candidate — are reported with an upgrade
advisory, never silently rewritten (facts §13 row 3).

## Phase 1 — Inventory (extension auditor)

Delegate to `opencode-extension-auditor` in inventory mode. It scans the
discovered config roots for both directory spellings (facts §12):

- skills: `skill/` + `skills/` (a `SKILL.md` per directory)
- commands: `command/` + `commands/`
- agents: `agent/` + `agents/`
- plugins: `plugin/` + `plugins/` (TypeScript or JavaScript)
- configs: `opencode.json` / `opencode.jsonc` at the global and project roots
- v1 file-based tool files: v1 `.opencode/tools/*.ts` (no v2 equivalent)

Record per extension: type, name, path, v1 or v2, and the specific v1
remnants found. The auditor's report is the input to every later phase.

## Phase 2 — Classify the project

Stop here when the auditor reports no v1 remnant: report the clean no-op and
list what made the project v2-native. Classify every remaining finding by the
mapping tables below, then run phases 3–5.

## Phase 3 — Config rewrite (verified mapping)

Rewrite each config to v2-native keys. All facts §5 unless noted.

| v1 key | v2 destination |
| --- | --- |
| `plugin` (singular) | `plugins` (array: `name@latest` strings or `{ package, options }`) |
| `permission` (keyed record) | `permissions` (ordered `{ action, resource, effect }` ruleset array, facts §4) |
| `tools` (config map) | `permissions` rules (allow/deny per tool) |
| `agent` | `agents` |
| `mode` (config block) | merged into `agents` |
| `command` | `commands` |
| `mcp` command string | `mcp.<name>.command` array; oauth to snake_case; timeout split (facts §10) |
| `skills` (`{ paths, urls }`) | `skills` (string array) |
| `autoupdate` | `update` (`disable`/`notify`/`auto`) |
| `autoshare` | `share` (`manual`/`auto`/`disabled`) |
| `snapshot` | `snapshots` |
| `attachment` | `media` |
| `reference` | `references` |
| `small_model` | `agents.title.model` |
| `enabled_providers` / `disabled_providers` | `experimental` permission rules on the `provider.use` action |
| a `provider/model` model string | `{ "providerID": "...", "model": "...", "variant": "..." }` |

Dropped v1 keys (`logLevel`, `server`, `layout`) are removed as unsupported
top-level keys. Legacy `config.json` is never read in v2: report an entry
there as inert and point the consumer at `opencode.json(c)`.

Agent frontmatter is migrated, not dropped (facts §6):

| v1 frontmatter | v2 destination |
| --- | --- |
| `prompt` | `system` (the markdown body) |
| `model` + `variant` | a `{ providerID, model, variant? }` selection object |
| `temperature`, `top_p`, other provider keys | `request` (passthrough into the request body) |
| `tools` boolean map | `permissions` rules (`edit` gates write/edit/patch) |
| `permission` keyed record | `permissions` ruleset array |
| `maxSteps` | `steps` |
| `disable` | `disabled` |
| theme-name `color` | hex only |

## Phase 4 — Port plugin code (plugin engineer)

Delegate to `opencode-plugin-engineer` for every v1 plugin file. The port
target is the Effect-first v2 plugin API (facts §1, §2, §9):

- Author against `@opencode/plugin`; import `Plugin` from
`@opencode/plugin/effect`. Default-export
`Plugin.define({ id, effect(ctx) {...} })`; the Promise root is the
documented fallback for trivial plugins. The `id` is load-bearing.
- Replace the returned hooks object with per-domain `transform(...)` and
`hook(name, callback)` registrations, and the v1 `event` catch-all with
`ctx.event.subscribe()` (facts §9). Cite the hook-family map instead of
restating each signature.
- Replace the v1 custom-tool helper with `ctx.tool.transform` +
`editor.add(...)` (facts §8).
- Declare `exports["./server"]` in any distributed package (facts §7, §13
row 9). Consumer-facing versions resolve `@latest`, never pinned
(facts §15).
- Preserve the startup non-interference invariant: hooks never throw
(facts §2, §7).

## Phase 5 — Port v1 tool files (tool builder)

Delegate to `opencode-tool-builder` for every v1 file-based tool file (v1
`.opencode/tools/*.ts`). v1 file-based tool definitions have no v2
equivalent: map each to a plugin-registered tool via `ctx.tool.transform`,
with `input` as raw JSON Schema, an Effect codec, or a Standard Schema
validator (facts §8). The v1 `tool.schema` helper style is gone. Tools live
in the plugin package, not as standalone files.

## Phase 6 — Recommendations report

Finish with a report that tells the consumer what v2 buys them beyond
parity. Enumerate at least these, each with a one-line rationale and a
pointer to the fact:

- **Richer session hooks** — per-request-kind hooks for context, compaction,
generate, and title, plus retry and native HTTP/WebSocket hooks (facts §9).
- **Plugin RPC** — `ctx.rpc.register` contracts with an optional `./rpc`
export for typed client calls (facts §11).
- **TUI plugins** — a `./tui` export with Solid helpers for terminal UI
extensions (facts §11).
- **MCP Code Mode** — `codemode` on local and remote MCP server config, and
per-tool Code Mode exposure (facts §8, §10).
- **Saved approvals** — persisted permission approvals with
`PermissionSaved.Info` and `Request.save` (facts §4).

## Report format

Write one report with these sections:

1. **Inventory** — every extension found, type, path, v1 or v2.
2. **No-op or changes** — "clean no-op" and why, or the list of rewrites.
3. **Skipped** — consumer-modified files skipped by hash mismatch, with the
warning text (empty when none).
4. **Recommendations** — the v2 capabilities worth adopting.
5. **App installation** — state explicitly that the OpenCode application
installation was not touched.

## References

- The suite's verified-facts record (`opencode-v2-facts.md`) — the source of
truth behind every "facts §n" citation above (maintainer copy).
- The suite's bundled v2 fundamentals: `references/config.md`,
`references/plugins.md`, `references/tools.md`, `references/agents.md`.
- `references/conformance-checklist.md` — the v2 conformance rubric to
check the upgraded package against.
20 changes: 5 additions & 15 deletions src/agent-loader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import { parse as parseYaml } from "yaml";
import type { Agent } from "@opencode/plugin/effect";
import { FrontmatterParser } from "./frontmatter";

export const AGENT_FILENAMES: readonly string[] = [
"opencode-agent-designer.md",
Expand All @@ -16,7 +16,6 @@ export const AGENT_FILENAMES: readonly string[] = [
"opencode-tool-builder.md",
];

const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
export const RELATIVE_REFERENCE_REGEX = /`((?:\.{1,2})(?:[\\/][^`\\/]+)+)`/g;

export interface LoadedAgent {
Expand All @@ -38,6 +37,7 @@ type PermissionEffect = "allow" | "ask" | "deny";

export class AgentLoader {
private readonly agentsDir: string;
private readonly frontmatter = new FrontmatterParser();

public constructor(agentsDir: string) {
this.agentsDir = agentsDir;
Expand All @@ -61,19 +61,9 @@ export class AgentLoader {
content: string,
agentName: string,
): Promise<LoadedAgent> {
const match = content.match(FRONTMATTER_REGEX);

if (!match || match.length < 3) {
throw new Error(`Agent ${agentName} must have YAML frontmatter`);
}

const frontmatterYaml = match[1] as string;
const rawPrompt = match[2] as string;
const frontmatter = parseYaml(frontmatterYaml) as AgentFrontmatter;
const system = this.resolveReferencePaths(
rawPrompt.replace(/^\r?\n/, ""),
path.dirname(agentPath),
);
const document = this.frontmatter.parse(content, `Agent ${agentName}`);
const frontmatter = document.attributes as unknown as AgentFrontmatter;
const system = this.resolveReferencePaths(document.body, path.dirname(agentPath));

return {
name: agentName,
Expand Down
Loading
Loading