Skip to content

feat(opencode): sync skills/rules/subagents/MCP into OpenCode config (both scopes) - #306

Merged
jeff-r2026 merged 2 commits into
Tencent:mainfrom
daoiqi:worktree-opencode-agent
Aug 24, 2026
Merged

feat(opencode): sync skills/rules/subagents/MCP into OpenCode config (both scopes)#306
jeff-r2026 merged 2 commits into
Tencent:mainfrom
daoiqi:worktree-opencode-agent

Conversation

@daoiqi

@daoiqi daoiqi commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds OpenCode as a supported AI coding tool. teamai pull now syncs all four resource types into OpenCode's native config, in both user and project scopes (part of #303).

Resource User scope Project scope
Skills ~/.config/opencode/skills/ <repo>/.opencode/skills/
Rules copied + instructions: ["rules/*.md"] copied + instructions: [".opencode/rules/*.md"]
Subagents ~/.config/opencode/agents/*.md <repo>/.opencode/agents/*.md
MCP ~/.config/opencode/opencode.json (mcp key) <repo>/opencode.json (mcp key)

Details

  • Subagents render OpenCode frontmatter (description + mode: subagent + optional model/tool_extras); name comes from the filename, tools omitted.
  • Rules are activated via the opencode.json instructions glob using key-level surgery; the glob is deactivated when the team's last rule is removed upstream, while personal rule files on disk are preserved via the existing tombstone mechanism.
  • MCP is written under the mcp top-level key (not mcpServers) with OpenCode's local/remote shapes; stdio + http transports supported, sse skipped. ${VAR} secrets resolved to plaintext.
  • Scope divergence (~/.config/opencode/... vs <repo>/.opencode/...) handled by a new userScope field on ToolPathsSchema and a scopedToolPaths() choke-point. The install-probe now uses path.dirname so a multi-segment path like .config/opencode/skills resolves to the tool root rather than the near-universal .config.
  • Fixes a pull orchestration bug: pullAllRules is now always called (even with an empty rule set) so the instructions glob is deactivated on the team's last rule removal.

Hooks (JS/TS plugin) are intentionally deferred to a follow-up PR.

Test Plan

All executed against a real CLI build (dist/index.js), not just unit tests:

  • npm run build, npx tsc --noEmit, npx vitest run → 2091 tests pass (157 files)
  • User scope: skills, rules, subagent, and MCP all land under ~/.config/opencode/...; instructions: ["rules/*.md"]
  • Project scope: all four land under <repo>/.opencode/...; MCP in <repo>/opencode.json; instructions: [".opencode/rules/*.md"]
  • Subagent frontmatter renders description + mode: subagent + model + temperature, no name/tools
  • MCP local (stdio → command array + environment) and remote (http → url + headers) shapes; sse skipped; ${VAR} resolved to plaintext
  • Upstream removes last rule → instructions key dropped (glob deactivated), personal rule file preserved on disk
  • User-owned instructions/mcp entries and unrelated top-level keys (e.g. theme) preserved across re-pull (key-level surgery)

🤖 Generated with Claude Code

@jeff-r2026
jeff-r2026 self-requested a review August 21, 2026 02:33

@m0Nst3r873 m0Nst3r873 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the OpenCode support! A few functional notes below — the first one (empty vs. filtered-empty rule set) is the one worth a closer look. Format/commit-convention items are out of scope for this review.

Comment thread src/pull.ts
// stale local rule files and deactivates the OpenCode instructions glob
// when the team's last rule is removed. Guarding on items.length > 0
// would leak those artifacts on the machine after upstream deletion.
await rulesHandler.pullAllRules(freshConfig, localConfig, items);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pullAllRules is now always called, even with an empty set. But an empty items can mean two different things: (a) the team genuinely has no rules, or (b) the team has rules that were all excluded by tag filtering. Both currently trigger deactivation — clearing the Hermes SOUL managed block (upsertSoulRules('')) and removing the OpenCode glob. Case (b) shouldn't deactivate. Consider distinguishing "no team rules" from "filtered to empty" and only cleaning up in the former.

Comment thread src/resources/mcp-format.ts Outdated
if (def.transport === 'stdio') {
e.type = 'local';
// OpenCode folds the executable and its args into one `command` array.
e.command = [def.command ?? '', ...(def.args ?? [])];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.command = [def.command ?? '', ...] — when def.command is missing, this silently emits an empty-string command, producing an invalid OpenCode config with no signal. Consider skipping the server (with a recorded change reason) when command is absent for a stdio transport.

Comment thread src/resources/opencode-config.ts Outdated
const nonStrings = Array.isArray(data.instructions)
? (data.instructions as unknown[]).filter((v) => typeof v !== 'string')
: [];
data.instructions = [...next, ...nonStrings];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preserved non-string instructions entries are re-appended after the string entries ([...next, ...nonStrings]), which reorders the user's original array. Minor, but preserving in-place order would be less surprising for hand-edited configs.

Comment thread src/resources/rules.ts
localConfig: LocalConfig,
present: boolean,
): Promise<void> {
if (isAgentDisabled(localConfig, 'opencode')) return;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When opencode is disabled, activateOpencodeInstructions returns early before removing any glob. A user who previously synced and then disabled opencode will keep a stale glob in their config. Acceptable, but worth a comment noting the residual is intentional.

daoiqi and others added 2 commits August 24, 2026 16:02
…(both scopes)

Add OpenCode as a supported AI coding tool. teamai now syncs all four
resource types into OpenCode's native config in both user and project
scopes:

- Skills: copied to .opencode/skills (project) or ~/.config/opencode/skills
  (user); OpenCode reads these natively.
- Subagents: rendered to .opencode/agents/*.md with OpenCode frontmatter
  (description + mode:subagent + optional model/tool_extras); name comes
  from the filename, tools are omitted.
- Rules: copied to the rules dir and activated via the opencode.json
  `instructions` glob (reconciled with key-level surgery); the glob is
  deactivated when the team's last rule is removed upstream.
- MCP: written under the `mcp` top-level key (not mcpServers) with
  OpenCode's local/remote shapes; stdio + http transports, sse skipped.

User scope diverges from project scope (~/.config/opencode/... vs
<repo>/.opencode/...), handled by a new `userScope` field on
ToolPathsSchema and a scopedToolPaths() choke-point. The install-probe
now uses path.dirname so a multi-segment path like .config/opencode/skills
resolves to the tool root rather than the near-universal .config.

Also fixes a pull orchestration bug: pullAllRules is now always called
(even with an empty rule set) so the OpenCode instructions glob is
deactivated when the team removes its last rule. Personal rule files are
still preserved via the existing tombstone mechanism.

Docs (README + usage-guide, both languages) updated with OpenCode.
…for user scope

The PR added scopedToolPaths() but only wired it into the resource handlers.
Every orchestration/cleanup/deploy site still iterated the raw toolPaths, so
OpenCode's user scope (~/.config/opencode/...) was written to but never cleaned
up or fully deployed. Route the remaining sites through scopedToolPaths so the
correct per-scope path is always used:

- pull.ts: tombstone cleanup, excluded-skill cleanup, inactive-namespace
  cleanup, existing-name detection, culture/claudemd/recall injection
- source.ts: source-skill deploy and removal
- builtin-{skills,agents,rules}.ts: built-in resource deployment
- recall-toggle.ts: recall artifact deploy/removal
- uninstall.ts: per-tool resource discovery
- pre-push-sync.ts: rules/skills sync-to-local (localConfig was unused)
- known-agents.ts: getEffectiveAgents + detectInstalledAgents now scope-aware

Also fix a regression and two edge cases:

- base.ts isToolInstalled: the path.dirname change reported .openclaw/workspace
  as the install root for openclaw's 3-segment claudemd path, wrongly treating
  a .openclaw-without-workspace user as not-installed. Introduce toolInstallRoot()
  which takes the first path segment (restoring the old openclaw behavior) except
  for .config/<tool>/... where it takes the first two (the OpenCode fix). Shared
  with known-agents detection.
- opencode-config.ts reconcileOpencodeInstructions: no longer relocates non-string
  instructions entries to the end of the array on every pull, which could silently
  change instruction precedence. Operates on the array in place.
- mcp-format.ts renderOpencode: guard against a missing stdio command so a
  malformed def never writes an empty-string executable into opencode.json.

Verified end-to-end with a real user-scope pull against a fresh OpenCode-only
HOME: skills deploy to ~/.config/opencode/skills, and a tombstoned skill is now
cleaned up there (baseline build leaves it behind, fixed build removes it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jeff-r2026
jeff-r2026 force-pushed the worktree-opencode-agent branch from 808c719 to 33eb08b Compare August 24, 2026 08:05
@jeff-r2026
jeff-r2026 merged commit 637578f into Tencent:main Aug 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants