Skip to content

feat: add --skills support to opencode integration#2494

Open
tinesoft wants to merge 1 commit into
github:mainfrom
tinesoft:feat/opencode-skills
Open

feat: add --skills support to opencode integration#2494
tinesoft wants to merge 1 commit into
github:mainfrom
tinesoft:feat/opencode-skills

Conversation

@tinesoft
Copy link
Copy Markdown
Contributor

@tinesoft tinesoft commented May 8, 2026

Description

Adds opt-in --skills support to OpencodeIntegration, mirroring the pattern used by ClaudeIntegration. When the flag
is passed, commands are scaffolded as speckit-<name>/SKILL.md files under .opencode/skills/ instead of flat .md
files under .opencode/command/.

Opencode natively supports the agentskills.io SKILL.md format at .opencode/skills/<name>/SKILL.md (see
https://opencode.ai/docs/skills/), with the same frontmatter (name, description, compatibility, metadata) that
CommandRegistrar.build_skill_frontmatter() already produces — so no custom post-processing was needed.

Activate via:

specify init --integration opencode --integration-options="--skills"

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync --extra test && uv run pytest
  • Tested with a sample project (if applicable)

All 28 opencode integration tests pass, including 5 new tests in TestOpencodeSkillsMode covering:

  • --skills option declaration (is_flag=True, default=False)
  • SKILL.md file creation under .opencode/skills/
  • No .md command files created in skills mode
  • Correct SKILL.md frontmatter (name, description, compatibility, metadata.author)
  • Regression guard confirming default markdown mode is unchanged

Agent config consistency check (tests/test_agent_config_consistency.py) also passes (26/26).

Manual smoke test:

specify init /tmp/speckit-test --integration opencode --integration-options="--skills"
# ✅ .opencode/skills/speckit-specify/SKILL.md created
specify init /tmp/speckit-test2 --integration opencode                                                                   
# ✅ .opencode/command/speckit.specify.md created (default mode unchanged)

AI Disclosure

  • I did use AI assistance (describe below)

This PR was written with Claude Code (claude-sonnet-4-6). The implementation approach, code, and tests were generated
with AI assistance and reviewed and validated by the author.

Copilot AI review requested due to automatic review settings May 8, 2026 05:19
@tinesoft tinesoft requested a review from mnriem as a code owner May 8, 2026 05:19
@tinesoft tinesoft changed the title feat: add --skills flag to scaffold commands as SKILL.md files feat: add --skills support to opencode integration May 8, 2026
@tinesoft tinesoft force-pushed the feat/opencode-skills branch from 5b3b00c to ac6514e Compare May 8, 2026 05:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-in --skills mode to the opencode integration so scaffolding can produce agentskills.io-style SKILL.md files under .opencode/skills/<skill>/SKILL.md, with accompanying tests.

Changes:

  • Added a --skills integration option to OpencodeIntegration and a setup() branch to scaffold skills-format outputs.
  • Added Opencode skills-mode tests validating SKILL.md creation, frontmatter shape, and that default markdown mode remains unchanged.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/specify_cli/integrations/opencode/__init__.py Adds --skills option and delegates setup to SkillsIntegration when enabled.
tests/integrations/test_integration_opencode.py Adds a new test class covering skills-mode scaffolding behavior and SKILL.md frontmatter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/specify_cli/integrations/opencode/__init__.py
Comment thread tests/integrations/test_integration_opencode.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment on lines +42 to +50
def effective_invoke_separator(
self, parsed_options: dict[str, Any] | None = None
) -> str:
if parsed_options and parsed_options.get("skills"):
return "-"
if self._skills_mode:
return "-"
return self.invoke_separator # default: "."

assert skills_dir.is_dir(), "Skills directory was not created"
plan_skill = skills_dir / "speckit-plan" / "SKILL.md"
assert plan_skill.exists(), "speckit-plan/SKILL.md not found"
assert not list((project / ".opencode" / "command").glob("*.md")) if (project / ".opencode" / "command").exists() else True
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

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

Please address Copilot feedback

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 1

Comment on lines +72 to +83
if not self._skills_mode and project_root:
skills_dir = project_root / ".opencode" / "skills"
if skills_dir.is_dir():
self._skills_mode = any(
d.is_dir() and (d / "SKILL.md").is_file()
for d in skills_dir.glob("speckit-*")
)
return super().dispatch_command(
command_name, args,
project_root=project_root, model=model, timeout=timeout, stream=stream,
)

@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 12, 2026

Please address Copilot feedback

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 4

Comment on lines +92 to +93
self._skills_mode = bool(parsed_options.get("skills"))
if self._skills_mode:
Comment on lines +91 to +93
parsed_options = parsed_options or {}
self._skills_mode = bool(parsed_options.get("skills"))
if self._skills_mode:
Comment on lines +91 to +93
parsed_options = parsed_options or {}
self._skills_mode = bool(parsed_options.get("skills"))
if self._skills_mode:
) -> list[Path]:
parsed_options = parsed_options or {}
self._skills_mode = bool(parsed_options.get("skills"))
if self._skills_mode:
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 14, 2026

Please address Copilot feedback. If not applicable, please explain why

@tinesoft tinesoft force-pushed the feat/opencode-skills branch from 0d9fd06 to 1e37a4c Compare May 15, 2026 11:44
@tinesoft
Copy link
Copy Markdown
Contributor Author

I adressed the changes and aligned the skill integration with how it was done for Copilot

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 5

Comment on lines +826 to +829
# Persist the CLI options so later operations (e.g. extension install, preset add)
# can adapt their behaviour without re-scanning the filesystem.
# Must be saved BEFORE extension and preset install so _get_skills_dir() works.
init_opts = {
Comment on lines +302 to +306
result = CliRunner().invoke(app, [
"init", "--here", "--integration", "opencode",
"--integration-options", "--skills",
"--script", "sh", "--no-git", "--ignore-agent-tools",
], catch_exceptions=False)
Comment on lines +94 to +99
# Detect skills mode from project layout when not already set via setup().
# Kept local so the singleton's _skills_mode is never mutated here.
skills_mode = self._skills_mode
if not skills_mode and project_root:
skills_dir = project_root / ".opencode" / "skills"
if skills_dir.is_dir():
Comment on lines +53 to +61
@classmethod
def options(cls) -> list[IntegrationOption]:
return [
IntegrationOption(
"--skills",
is_flag=True,
default=False,
help="Scaffold commands as agent skills (speckit-<name>/SKILL.md) instead of .md files",
),
Comment on lines +838 to +844
# Ensure ai_skills is set for SkillsIntegration so downstream
# tools (extensions, presets) emit SKILL.md overrides correctly.
# Also set for integrations running in skills mode (e.g. Copilot
# with --skills or Opencode with --skills).
from .integrations.base import SkillsIntegration as _SkillsPersist
if isinstance(resolved_integration, _SkillsPersist) or getattr(resolved_integration, "_skills_mode", False):
init_opts["ai_skills"] = True
@tinesoft tinesoft force-pushed the feat/opencode-skills branch from 1e37a4c to 9a0b836 Compare May 18, 2026 01:48
Adds opt-in `--skills` support to OpencodeIntegration, producing `speckit-<name>/SKILL.md` files under `.opencode/skills/` instead of flat `.md` files.
Opencode natively supports this format (https://opencode.ai/docs/skills/).

Activate via: `specify init --integration opencode --integration-options="--skills"`
@tinesoft tinesoft force-pushed the feat/opencode-skills branch from 9a0b836 to 1fecdea Compare May 18, 2026 01:50
@mnriem mnriem requested a review from Copilot May 18, 2026 14:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 3

Comment on lines +841 to +847
# Also set for integrations running in skills mode (e.g. Copilot
# with --skills or Opencode with --skills).
# Use parsed_options as the source of truth for skills mode, not
# _skills_mode (a mutable runtime flag), so the setting persists
# correctly even if the integration is restored without setup().
from .integrations.base import SkillsIntegration as _SkillsPersist
if isinstance(resolved_integration, _SkillsPersist) or integration_parsed_options.get("skills"):
Comment on lines +420 to +438
integration._skills_mode = True # stale flag from prior skills setup

project = tmp_path / "regular-project"
project.mkdir()
(project / ".opencode").mkdir()
# No .opencode/skills/ — this is a non-skills project

with mock.patch("subprocess.run") as mock_run:
mock_run.return_value = mock.MagicMock(returncode=0)
integration.dispatch_command(
"speckit.plan", "test args", project_root=project, stream=False,
)

called_args = mock_run.call_args[0][0]
assert "--command" in called_args
# Should use dotted invocation despite stale _skills_mode=True
assert called_args[called_args.index("--command") + 1] == "speckit.plan"
# singleton _skills_mode remains unchanged
assert integration._skills_mode is True
Comment on lines +930 to +931
from .integrations.base import IntegrationBase as _IntegrationBase
body = _IntegrationBase.resolve_command_refs(body, "-")
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 20, 2026

Please address Copilot feedback. Note if opencode is only a skills based integration you do not have to make it accept --integration-options="--skills". Some of the other coding agents initially had commands and added skills support later so they needed a way to express that they want skills vs commands.

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