[Documentation] Use when generating, smart-merging or refactoring the root AI context (CLAUDE.md, AGENTS.md) and its Claude/Codex projections. Config file only: project-config.
日本語の概要は準備中です。原文の説明を表示しています。
[opencode] Use when running the opencode sync and verify pipeline: opencode.json reconcile, skill policy, hidden-skill commands, hooks bridge, sub-agent mirror, drift check.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Goal: Reconcile the project-root opencode.json with the framework's recommended opencode defaults, compile .claude/settings.json into a portable opencode plugin (.opencode/plugins/easy-claude-hooks.js), and verify both. This runner is the single and only entrypoint for the opencode surface pipeline.
PORTABILITY CONTRACT —
.claude/and.opencode/are portable, self-running and self-testing. Copy them into ANY repository — a Python repo, a .NET repo, a repo with nopackage.jsonat all — and the sync / verify / test entrypoint still works, because it is a path INSIDE the bundle invoked with plainnode. Never drive this framework through a hostpackage.jsonscript, and never document one:npm run …names a command that does not exist in most projects the bundle is copied into. The only external requirement isnode>= 18 — nonode_modules, no npm, no lockfile. The generated plugin imports onlynode:built-ins and spawns the canonical.claude/hooks/*.cjsscripts.Discover the roster instead of memorizing it:
node .claude/skills/sync-opencode/scripts/run-opencode-sync.mjs --list-stages
Summary:
.claude/settings.json into a bridge plugin whose runtime drives the original Claude hooks from opencode's plugin events..opencode/opencode.recommended.json is the single source of truth for the framework's opencode defaults; the config stage deep-merges it into the project-root opencode.json (recommended keys win, project-only keys survive)..claude/skills and .agents/skills, so there is no skill mirroring here (unlike /sync-codex). opencode ignores disable-model-invocation, so the skills stage enforces the selection policy through permission.skill in the project-root opencode.json instead, and writes a .opencode/commands/<name>.md per hidden skill so its explicit /name keeps working — see Skill permissions. Sub-agents are NOT auto-discovered, so .claude/agents/*.md IS mirrored into .opencode/agent/*.md by the agents stage — that is what lets the workflow protocols dispatch the same specialists (architect, code-reviewer, security-auditor, …) on opencode..claude canonical: edit .claude/settings.json / .claude/hooks/** and re-run this pipeline; never hand-edit the generated .opencode/plugins/easy-claude-hooks.js..opencode/opencode.recommended.json, then re-run this pipeline to propagate it into the root config of every project the .opencode/ folder is copied into..opencode/plugins/notification.js is superseded by the generated bridge; the runner backs it up under tmp/opencode-legacy/ and removes it so notifications are not sent twice.Workflow:
node .claude/scripts/opencode/sync-config.mjs deep-merges .opencode/opencode.recommended.json into the project-root opencode.json.node .claude/skills/sync-opencode/scripts/run-opencode-sync.mjs rewrites permission.skill in the project-root opencode.json together with its ownership ledger .opencode/skill-permissions.generated.json, writes a marked .opencode/commands/<name>.md per hidden skill, and regenerates the bridge plugin, the .opencode/agent/*.md mirror, and the sync report.--only=<stage> --verbose.Key Rules:
.opencode/plugins/easy-claude-hooks.js; regenerate it from .claude/settings.jsonopencode.json as the way to change framework defaults — edit .opencode/opencode.recommended.json and re-run the pipeline.opencode/opencode.recommended.json MUST NOT be named .opencode/opencode.json; opencode auto-loads that path as project config.opencode/plugins/** are the only opencode hook surface; no skill mirror is produced (skills are auto-discovered, sub-agents are mirrored into .opencode/agent/**)node "$CLAUDE_PROJECT_DIR"/... hook commands are compiled; other command shapes are reported as unsupported-command-shapeskipped-events in the sync report — never silently droppedskills stage changes only permission.skill keys recorded in .opencode/skill-permissions.generated.json; a user-set key is never adopted, overwritten or loosened, and a called skill is never hidden; it rewrites or deletes only .opencode/commands/*.md files that carry its generated markerThe
configstage above reconciles framework-recommended opencode defaults; it is not a mirror of Claude settings. This section explains why the project's hook surface is generated as a plugin.
Codex supports lifecycle hooks and can be driven by .codex/hooks.json. opencode cannot: its only
extension point for lifecycle behavior is a JS/TS plugin under .opencode/plugins/ (or an npm plugin).
So instead of transcribing commands into a JSON file, this pipeline emits a self-contained plugin that
spawns the canonical Claude hook scripts and translates both directions:
| Direction | Translation |
|---|---|
| opencode → Claude hook | event name, tool id → Claude matcher name, opencode args → tool_input field names |
| Claude hook → opencode | exit 0 = allow · exit 2 = block (throw) · stdout hookSpecificOutput.updatedInput = argument rewrite · stdout hookSpecificOutput.additionalContext = injected context · stdout hookSpecificOutput.permissionDecision: "deny" = block |
The framework ships recommended opencode defaults as a portable template and
reconciles them into whatever project the .opencode/ folder is copied into.
| Item | Path |
|---|---|
| Source of truth (edit this) | .opencode/opencode.recommended.json |
| Generated target (never hand-edit for defaults) | <project-root>/opencode.json |
| Writer / verifier | .claude/scripts/opencode/sync-config.mjs (--check for verify) |
To change a default recommended opencode setting in the future, edit
.opencode/opencode.recommended.jsonand re-run/sync-opencode. Every project that receives the.opencode/folder then gets the updated default the next time the pipeline runs. The recommended file is deliberately NOT named.opencode/opencode.jsonbecause opencode auto-loads that path as project config — keeping the.recommended.jsonname makes it a template, not an active config.
Merge semantics: the writer deep-merges the recommended defaults into the existing root opencode.json. Recommended keys win at every leaf; object keys that exist only in the project survive untouched; arrays in the recommended file replace the project's array. A project with no root config receives the recommended defaults verbatim. A malformed existing root config is reported, never clobbered.
The framework pins no auto-compaction budget on any of its three surfaces (Claude Code,
Codex, opencode), so each host applies its own default and each user sets their own.
The recommended file therefore carries no model limit: opencode takes the window from
the model's registry entry (models.dev). For the pinned model that is context: 1000000,
output: 384000 (verified with opencode models opencode-go --verbose, v1.18.31).
opencode has no absolute compaction threshold — it compacts relative to the model's
declared window, so the window IS the knob. From session/overflow.ts (v1.18.31):
usable = limit.input ? max(0, limit.input - (compaction.reserved ?? min(20_000, maxOutput)))
: max(0, limit.context - maxOutput)
maxOutput = min(limit.output, 32_000) // OUTPUT_TOKEN_MAX
compaction happens once total tokens >= usable
So the registry window compacts at 968,000 tokens (1,000,000 − 32,000). A user who
wants an earlier point sets their own limit on the model — in the project-root
opencode.json or their global opencode config. For example, limit.context: 500000 +
limit.output: 384000 compacts at 468,000.
Retiring the old pin: the config stage removes the model's limit from the root
opencode.json only when it is exactly the formerly bundled
{ "context": 500000, "output": 384000 } (the deep merge alone never deletes a key). Any
other limit is the user's: it is kept and reported with one kept user-set … line. Keep
a personal 500K budget in the global opencode config if the root file must stay untouched.
Two traps for anyone setting their own limit, both verified against the released source:
compaction.reserved is inert here. It is only read on the limit.input branch,
and models.dev declares no input for this model. Raising it does nothing.limit.input. It is undocumented, and limit.context
is what the rest of opencode reads as the window — the TUI context percentage, ACP usage
reporting, and the limits handed to the AI SDK. Capping input while leaving context
at 1M shows ~48% in the TUI at the moment it compacts.Copying the framework into a new project: copy .claude/ and .opencode/ (including .opencode/opencode.recommended.json), then run /sync-opencode — it generates/updates that project's root opencode.json, bridge plugin, and reports. Do NOT copy .opencode/skill-permissions.generated.json or .opencode/commands/: both are generated per project by the skills stage, and a copied ledger is ignored anyway because it names the other project — why: a ledger from another project would otherwise claim the adopter's own permission.skill entries.
opencode lists every discovered skill to the model and ignores disable-model-invocation, so the skills stage (.claude/scripts/opencode/sync-skills.mjs) writes the framework's selection policy into permission.skill of the project-root opencode.json. A deny entry hides the skill from the model and rejects loading it through the skill tool.
| Skill | Effective tier or mark | permission.skill entry |
|---|---|---|
Command-only skill (disable-model-invocation: true) | — | deny |
Workflow wrapper (skill name = a .claude/workflows.json id) | manual | deny |
| Workflow wrapper | confirm | ask |
| Workflow wrapper | auto | none |
Skill in skillProfile.nameOnly | — | no entry from the profile; a stricter entry from a row above stays |
Skill in skillProfile.commandOnly or skillProfile.off | — | deny plus a generated command, so /name still runs it |
| Any other skill | — | none |
portability.workflowActivation in the project config, default docs/project-config.json) — never the raw workflows.json value. The tier wins over the wrapper's own disable-model-invocation mark, so an override that loosens a manual workflow to auto removes its entry. opencode.json is a project file, so a developer's .claude/.ck.local.json never lands in it.sequence or any variants.*.sequence), in an agent's skills: frontmatter list, or in the curated calledByOthers or entrySkills lists of .claude/config/skill-profiles.json gets no policy entry, and the run prints one line per skill: skipped <name>: called by <callers>. The called set has one owner, resolveProfile().called in .claude/scripts/sync-skill-profile.cjs. A .claude/workflows.json without a workflows map stops the stage instead of counting as empty.skillProfile rows come from the same resolveProfile() as the Claude sync (read .claude/config/README.md → Skill profile when you need the presets and lists). nameOnly writes nothing, because an ask would stop every workflow step that loads the skill; it never loosens an entry the policy already writes. A called skill in commandOnly or off is refused unless skillProfile.allowHidingCalledSkills is true: the stage prints the resolver's message and skill-profile: nothing was written, exits non-zero, and --check fails the same way. With the opt-in the deny is written and a warning line names the skill. Resolver warnings print as warning: skill profile: ... only when skillProfile is declared. Profile entries go through the same ownership ledger, so a user key is never adopted, overwritten or loosened..opencode/skill-permissions.generated.json lists each key the generator owns, with the value it wrote. Only listed keys are updated or removed.project = project.name from the project config (default docs/project-config.json; .claude/.ck.json portability.projectConfigPath relocates it), or null when there is none. A ledger whose project is missing or differs — copied from another project, or left over from a rename — is ignored with one line, warning: ignored .opencode/skill-permissions.generated.json: it belongs to another project (...), owns nothing, and is rewritten for this project, so every entry already in opencode.json stays the user's. A project config that exists but is not valid JSON stops the stage before anything is written.| Situation | Outcome |
|---|---|
| Key existed before the generator first wrote it | Kept and never recorded as owned; conflict: permission.skill.<name> is <user value>, generator wants <value>; kept the user value when it differs |
| Key listed only in a ledger from another project | Treated as the previous row: never owned |
| Owned key the user changed | Kept; the same conflict line |
| Owned key the user deleted | Restored to the policy value with no conflict line — set an explicit value such as allow to keep a skill loadable |
permission or permission.skill is a single value, not a map | Unchanged; one conflict line; no skill entries written |
Wildcard key such as internal-* | Always the user's; never rewritten |
read, edit or other permission, so reading .claude/skills/<name>/SKILL.md by path keeps working for workflows that load a skill file directly.^[a-z0-9][a-z0-9-]*$) is skipped with a warning.--check fails when permission.skill, the ledger or a generated command differs from a fresh sync. A kept user value or user command is not drift.A hidden skill keeps its explicit /name: the same stage writes .opencode/commands/<name>.md for every skill whose final exact permission.skill entry is deny — the policy's own entries and a user deny alike (wildcard keys are not evaluated). A skill the policy denies but whose kept user value is not deny, or whose entry could not be written because permission / permission.skill is not a map, gets no command.
---
description: "<the skill description, whitespace collapsed, YAML-escaped>"
---
<!-- GENERATED OPENCODE COMMAND (sync-skills.mjs) for .claude/skills/<name>/SKILL.md — do not hand-edit; re-run:
node .claude/skills/sync-opencode/scripts/run-opencode-sync.mjs -->
@.claude/skills/<name>/SKILL.md
Arguments: $ARGUMENTS
name: inside the skill; the write path must resolve inside .opencode/commands/.GENERATED OPENCODE COMMAND marker are rewritten or deleted. A marked command whose skill is no longer hidden is removed..opencode/commands/ or opencode's singular .opencode/command/ folder — is kept unchanged, no generated command replaces it, and the run prints conflict: <path> is a user command without the generated marker; kept it, no command generated for skill <name>.!`…`. Shell note: opencode substitutes $ARGUMENTS before it runs !`cmd` injections, so typed arguments that themselves contain !`…` run as shell on that host. Do not paste untrusted text as command arguments.| Claude hook event | opencode plugin surface | Notes |
|---|---|---|
PreToolUse | tool.execute.before | throw blocks the tool; updatedInput rewrites output.args |
PostToolUse | tool.execute.after | additionalContext is appended to the tool output |
UserPromptSubmit | chat.message | additionalContext is injected as a synthetic text part |
SessionStart | event:session.created / event:session.compacted + experimental.chat.system.transform | additionalContext is injected into the system prompt |
SessionEnd | event:session.deleted | |
Stop | event:session.idle | |
Notification | event:question.asked / event:permission.asked | matcher vocabulary AskUserPrompt / permission_prompt |
PermissionRequest | permission.ask | blocked → status: "deny" |
Tool id aliases (opencode → Claude matcher names the hooks are written against):
| opencode tool | Claude matcher names |
|---|---|
bash | Bash |
edit | Edit, MultiEdit |
write | Write |
read | Read |
grep | Grep |
glob | Glob |
apply_patch | Edit, Write, MultiEdit, NotebookEdit |
todowrite | TodoWrite, TaskCreate, TaskUpdate, update_plan |
webfetch / websearch | WebFetch / WebSearch |
question | ask user question tool |
skill | Skill |
<server>_<tool> (MCP) | mcp__<server>__* |
<server>_<tool>, and Claude matchers like mcp__github__* are matched against that convention. However, opencode does not expose MCP tool arguments to tool.execute.before, so an MCP PreToolUse hook that inspects tool_input cannot see them on this host.apply_patch has no file_path. The bridge reports it as Edit/Write/MultiEdit and passes patchText through as patch; hooks that require file_path (doc-sync-gate) allow/ignore it exactly as they do on other hosts.SessionStart runs on session.created and session.compacted; when opencode resumes a session without either event, the bridge runs source: "startup" once on the first chat.message.updatedInput, additionalContext, and permissionDecision are ignored by the bridge.# Discover the stage roster:
node .claude/skills/sync-opencode/scripts/run-opencode-sync.mjs --list-stages
# Full sync + test + verify:
node .claude/skills/sync-opencode/scripts/run-opencode-sync.mjs
# Stream live child output:
node .claude/skills/sync-opencode/scripts/run-opencode-sync.mjs --verbose
# Every read-only gate (no mutation):
node .claude/skills/sync-opencode/scripts/run-opencode-sync.mjs --verify-only
# Just regenerate the plugin:
node .claude/scripts/opencode/sync-hooks.mjs
# Just verify the tracked plugin is current:
node .claude/scripts/opencode/sync-hooks.mjs --check
# Just reconcile the recommended root opencode.json:
node .claude/scripts/opencode/sync-config.mjs
# Just verify the root opencode.json is current:
node .claude/scripts/opencode/sync-config.mjs --check
# Just write the skill permissions:
node .claude/scripts/opencode/sync-skills.mjs
# Just verify the skill permissions are current:
node .claude/scripts/opencode/sync-skills.mjs --check
# Skip a stage while debugging:
node .claude/skills/sync-opencode/scripts/run-opencode-sync.mjs --skip=hooks
Exit codes: 0 all pass · 1 orchestrator failure · non-zero propagates from failing stage.
9 stages, sequential — the complete opencode surface pipeline, owned entirely by this runner:
| # | Stage | Script | Effect |
|---|---|---|---|
| 1 | config | .claude/scripts/opencode/sync-config.mjs | Deep-merge .opencode/opencode.recommended.json into the project-root opencode.json |
| 2 | skills | .claude/scripts/opencode/sync-skills.mjs | Write permission.skill in the project-root opencode.json, the ownership ledger .opencode/skill-permissions.generated.json, and a marked .opencode/commands/<name>.md per hidden skill (see Skill permissions) |
| 3 | hooks | .claude/scripts/opencode/sync-hooks.mjs | Generate .opencode/plugins/easy-claude-hooks.js + tmp/opencode-hooks.sync.report.json; back up/remove legacy notification.js |
| 4 | agents | .claude/scripts/opencode/sync-agents.mjs | Mirror .claude/agents/*.md into .opencode/agent/*.md (mode: subagent + the canonical body verbatim) |
| 5 | tests | Runner discovers .claude/scripts/opencode/tests/*.test.{mjs,cjs} | Run opencode tooling tests; missing or empty discovery fails |
| 6 | verify-config | .claude/scripts/opencode/sync-config.mjs --check | Re-merge with the REAL writer and byte-compare with the project-root opencode.json |
| 7 | verify-skills | .claude/scripts/opencode/sync-skills.mjs --check | Re-plan with the REAL writer; fail when permission.skill, the ledger, or a generated command is missing, changed or stale |
| 8 | verify-hooks | .claude/scripts/opencode/sync-hooks.mjs --check | Re-render with the REAL writer and byte-compare with the tracked plugin |
| 9 | verify-agents | .claude/scripts/opencode/sync-agents.mjs --check | Re-render every agent with the REAL writer and byte-compare with the tracked .opencode/agent/*.md |
MUST ATTENTION keep the /sync-opencode skill user-invoked-only; no unrelated skill, agent, or workflow may auto-run the mutating pipeline.
MUST ATTENTION edit .claude/settings.json and .claude/hooks/** as the source, then regenerate; NEVER hand-edit .opencode/plugins/easy-claude-hooks.js
MUST ATTENTION the framework's default opencode settings live in .opencode/opencode.recommended.json — edit THAT file to change defaults, then re-run this pipeline; never treat a project-root opencode.json as the source
MUST ATTENTION the generated plugin must import only node: built-ins so .claude/.opencode stay portable into any project
MUST ATTENTION the config stage deep-merges (recommended wins, project-only keys survive) and never clobbers a malformed root config — it reports instead
MUST ATTENTION the runner auto-resolves the repo root from its own path — do not pass a cwd flag
MUST ATTENTION the legacy notification.js is backed up, never destroyed, before removal
Anti-Rationalization:
| Evasion | Rebuttal |
|---|---|
| "Just edit the .opencode plugin directly" | Next sync overwrites it. Edit .claude/settings.json and regenerate. |
| "Just edit the project-root opencode.json to change the defaults" | The next sync re-merges the recommended file and your default is lost, and no other project gets it. Edit .opencode/opencode.recommended.json and re-run. |
"Make the recommended file .opencode/opencode.json" | opencode auto-loads that exact path as project config, so it would stop being a template. Keep the .recommended.json name. |
| "Skip the tests stage" | The tests exercise the generated plugin against real hook subprocesses; skipping ships an untested bridge. |
| "Mirror skills too for symmetry with sync-codex" | opencode already discovers .claude/skills; a mirror would duplicate and drift. Out of scope by design. |
[FAILS FAST] First non-zero stage exit aborts the chain. Re-run the failing stage with
--only=<id> --verbose. [REPO ROOT] The orchestrator auto-resolves the repo root from its own path.
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
[Documentation] Use when generating, smart-merging or refactoring the root AI context (CLAUDE.md, AGENTS.md) and its Claude/Codex projections. Config file only: project-config.
日本語の概要は準備中です。原文の説明を表示しています。
[Code Quality] Use when a workflow step or the user asks for AI code or plan review: LLM calls, prompts, agents, RAG, evals and guardrails. --mode={code|plan}; --report-only is read-only.
日本語の概要は準備中です。原文の説明を表示しています。
[Architecture] Use when a workflow step or the user asks for architecture design, compliance review, scalability grading or whole-project audit via --mode={design|review|scalability|full}.
日本語の概要は準備中です。原文の説明を表示しています。
[Content] Use when a workflow step or the user asks for product/feature ideation with ranked opportunities and validation tests. --mode={roadmap|scope}.
日本語の概要は準備中です。原文の説明を表示しています。
[Content] Use when a workflow step or the user asks for business viability: Business Model Canvas, financials, risks and go-to-market recommendation.
日本語の概要は準備中です。原文の説明を表示しています。
[Code Quality] Use when a workflow step or the user asks for a focused code-change review: staged, unstaged, branch diff or simple pull request. --fix-loop fixes and re-reviews. Complex pull request: workflow-review-changes; rationale: why-review.
日本語の概要は準備中です。原文の説明を表示しています。