Skip to content

fix(opencode): generate slash-command stubs for each skill#776

Open
lucabattistini wants to merge 7 commits intoEveryInc:mainfrom
lucabattistini:fix/opencode-skills-missing-commands
Open

fix(opencode): generate slash-command stubs for each skill#776
lucabattistini wants to merge 7 commits intoEveryInc:mainfrom
lucabattistini:fix/opencode-skills-missing-commands

Conversation

@lucabattistini
Copy link
Copy Markdown

Problem

When installing the compound-engineering plugin to OpenCode, zero slash commands were written to ~/.config/opencode/commands/. The convertClaudeToOpenCode function only consumed plugin.commands (a commands/ directory), but the CE plugin ships only skills — so users had no /ce-work, /ce-plan, etc. available in OpenCode, even though all 54 skills were correctly installed.

Solution

Added convertSkillsToCommands(skills: ClaudeSkill[]): OpenCodeCommandFile[] that generates one slash-command stub per skill. Each stub:

  • Carries the skill's description as frontmatter
  • Forwards argument-hint when present
  • Has a body that tells the model to load and execute the named skill, with $ARGUMENTS passed through

Skills with disable-model-invocation: true are excluded. Platform-filtered skills (e.g. ce_platforms: [claude]) never reach the function.

The generated stubs are merged with any convertCommands() output in convertClaudeToOpenCode, so plugins that have both a commands/ directory and skills get both.

Tests

  • Updated the existing "current compound-engineering output" test to assert commandFiles.length > 0 and ≤ skillDirs.length
  • Added a new test asserting skill-one generates a command with correct frontmatter, and that disabled-skill / claude-only-skill are absent from commandFiles
  • All 1338 tests pass

Each skill installed to OpenCode now also writes a corresponding
slash-command file under `~/.config/opencode/commands/<name>.md`.
This lets users invoke /ce-work, /ce-plan, etc. directly in OpenCode,
mirroring the experience Claude Code users have via native skill loading.

Skills with `disable-model-invocation: true` are excluded, and
platform-filtered skills (e.g. claude-only) never reach the converter.
The `argument-hint` frontmatter field is forwarded from the skill when present.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74a613f92a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/converters/claude-to-opencode.ts Outdated
… skill stubs

If a plugin ships both a commands/ entry and a same-named skill, the
explicit command body previously could be silently overwritten by the
generic skill stub. Now the skill stub is filtered out whenever an
explicit command with the same name already exists.

Addresses review feedback from PR EveryInc#776.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 785ffb210b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/converters/claude-to-opencode.ts Outdated
…form collisions

Deduplication was comparing raw name strings, so an explicit command
'foo:bar' would not block a skill stub named 'foo/bar' — both resolve
to commands/foo/bar.md on disk, causing the stub to silently overwrite
the explicit command.

Fix: extract commandNameToRelativePath() into src/utils/files.ts and
use it in both the converter's dedup set and the writer's path
resolution, so the same normalized key is used everywhere.

Addresses second review comment on PR EveryInc#776.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6bba41d382

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/converters/claude-to-opencode.ts Outdated
…pending

Two skills whose names normalize to the same command path (e.g. 'foo:bar'
and 'foo/bar') would both write to commands/foo/bar.md, silently overwriting
the first. Deduplicate convertSkillsToCommands output by commandNameToRelativePath
before appending, so only the first occurrence is kept.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8bb5330972

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/converters/claude-to-opencode.ts
…mmand stubs

Skills marked user-invocable: false (e.g. ce-session-inventory, ce-session-extract)
are agent-only primitives and must not be exposed as user-facing slash commands.
Parse the user-invocable frontmatter field into ClaudeSkill and filter these skills
out in convertSkillsToCommands alongside the existing disable-model-invocation check.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65e623ec27

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/converters/claude-to-opencode.ts
…der from-commands

applyPermissions only scanned plugin.commands for allowedTools, so a
skill-only plugin (or any plugin whose commands don't list 'skill') would
produce permission.skill = "deny", immediately breaking every generated
stub. When hasSkillStubs is true, unconditionally add 'skill' to the
enabled set in from-commands mode.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 031b2ad735

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/converters/claude-to-opencode.ts
…r from-commands

When a command uses skill(foo-*), patterns["skill"] is populated and the
permission build emits { "*": "deny", "foo-*": "allow" }, blocking all
stubs for skills outside the pattern even though enabled.add("skill") was
set. Fix: delete patterns["skill"] when hasSkillStubs is true so the build
emits a flat "allow" for the skill tool, allowing all generated stubs to
execute. Also fix stray leading space on ClaudeSkill type declaration.
@tmchow
Copy link
Copy Markdown
Collaborator

tmchow commented May 5, 2026

Does this handle the cleanup properly when skills change (renamed or deleted)?

@lucabattistini
Copy link
Copy Markdown
Author

lucabattistini commented May 5, 2026

@tmchow Yes — cleanup rides the existing managed-install-manifest plumbing.

convertSkillsToCommands produces OpenCodeCommandFile[] that get appended to bundle.commandFiles (claude-to-opencode.ts:104-110). The writer then:

  1. Builds currentCommands from bundle.commandFiles (opencode.ts:73)
  2. Calls cleanupRemovedManagedFiles(commandDir, manifest, "commands", currentCommands) before writing (opencode.ts:79) — anything in the prior manifest's commands group that's not in the new currentCommands gets fs.rm-ed
  3. Rewrites the manifest with the current set (opencode.ts:135-144)

So:

Scenario Behavior
Skill renamed Old stub path drops out of currentCommands → removed; new path written and tracked
Skill deleted Old stub path drops out → removed
Skill gains disable-model-invocation / user-invocable: false Filtered out of stubs (claude-to-opencode.ts:184-185) → removed on next install
First upgrade from a pre-776 install Prior manifest had commands: [], so cleanup is a no-op; the 54 stubs become tracked from this install onward

Test coverage for this contract already exists at tests/opencode-writer.test.ts:359 ("removes previously managed OpenCode artifacts that disappear on reinstall") — it writes old:cmd, re-installs with new:cmd only, and asserts commands/old/cmd.md is gone. Stubs ride the same path; nothing in this PR changes the writer/manifest contract, only what feeds into it.

One small caveat (pre-existing, not introduced here): cleanupRemovedManagedFiles removes files but doesn't prune empty parent dirs, so a colon-namespaced rename (foo:barfoo:baz) could leave an empty commands/foo/. Happy to fix that in a follow-up if it's worth it, but it applies to any nested command path, not just skill stubs.

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.

2 participants