Skip to content

fix: support SKILL.md rendering for the generic integration - #4562

Open
chelsealong wants to merge 2 commits into
github:mainfrom
chelsealong:fix/4561-generic-skills-rendering
Open

fix: support SKILL.md rendering for the generic integration#4562
chelsealong wants to merge 2 commits into
github:mainfrom
chelsealong:fix/4561-generic-skills-rendering

Conversation

@chelsealong

Copy link
Copy Markdown
Contributor

Fixes #4561

Problem

--integration generic (the "bring your own agent" escape hatch) could only
ever emit flat speckit.<name>.md command files. There was no way to get the
speckit-<name>/SKILL.md layout that every skills-format agent (Claude,
Codex, Kimi, Bob, Copilot in skills mode, …) uses, even though GenericIntegration
already has all the machinery (process_template, write_file_and_record,
manifest tracking) needed to produce it.

Fix

Added a --skills flag to GenericIntegration.options() (parsed the same way
every other integration's --integration-options flags are — no special
casing needed). When set, setup() renders each command template as
<commands-dir>/speckit-<name>/SKILL.md with the same frontmatter shape
(name, description, compatibility, metadata) that
SkillsIntegration.setup() produces for other agents, instead of the flat
speckit.<name>.md file. Default behavior (no --skills) is unchanged
byte-for-byte.

This is scoped to GenericIntegration's own setup() only — it does not
touch CommandRegistrar.AGENT_CONFIGS, presets, or extensions, which already
exclude generic entirely (its output directory is a runtime CLI option, not
a static per-agent convention) regardless of layout. So skills-mode generic
gets the exact same (lack of) preset/extension registration support that
flat-mode generic already has today — no new inconsistency introduced.

Also, _build_skill_content() now calls post_process_skill_content() (via a
small internal _GenericSkillsHelper(SkillsIntegration), the same delegation
pattern CopilotIntegration uses for its own skills mode) so generated
SKILL.md bodies get the shared dot-to-hyphen hook-invocation note
(_HOOK_COMMAND_NOTE in base.py) before every "For each executable hook,
output the following" instruction — matching what every other
SkillsIntegration subclass (Claude, Codex, Kimi, Bob, …) already emits.
Without it, a hook configured in .specify/extensions.yml (e.g.
speckit.git.commit) would have been invoked verbatim as /speckit.git.commit,
which doesn't exist under the speckit-<name>/SKILL.md layout this PR
introduces — the real skill lives at speckit-git-commit/SKILL.md, invoked
as /speckit-git-commit.

Testing

Added tests to tests/integrations/test_integration_generic.py:

  • --skills is a declared, non-required boolean flag defaulting to False
  • setup() with --skills writes speckit-<name>/SKILL.md files under
    --commands-dir
  • the generated SKILL.md has the expected frontmatter and fully-processed
    body (no leftover {SCRIPT} / __AGENT__ / __SPECKIT_COMMAND_*__ tokens)
  • the generated SKILL.md body includes the dot-to-hyphen hook-invocation
    note with the /-prefixed (not $- or /skill:-prefixed) example, since
    generic isn't in DOLLAR_SKILLS_AGENTS/SKILL_COLON_AGENTS
  • without --skills, output is still flat speckit.<name>.md (regression
    guard)
  • skill files are tracked in the manifest and survive an install/uninstall
    round trip

Verified the new tests fail without the fix and pass with it:

$ git checkout HEAD~1 -- src/specify_cli/integrations/generic/__init__.py
$ .venv/bin/python -m pytest tests/integrations/test_integration_generic.py -q
...
FAILED ...::test_options_include_commands_dir - AssertionError: assert 1 == 2
FAILED ...::test_options_include_skills_flag - StopIteration
FAILED ...::test_setup_writes_skill_md_when_skills_flag_set - AssertionError: assert 'speckit.analyze.md' == 'SKILL.md'
FAILED ...::test_skill_content_has_expected_frontmatter - AssertionError: assert False
4 failed, 44 passed in 1.17s

$ git checkout HEAD -- src/specify_cli/integrations/generic/__init__.py
$ .venv/bin/python -m pytest tests/integrations/test_integration_generic.py tests/test_agent_config_consistency.py -q
77 passed in 1.27s

Separately, reverting only the post_process_skill_content() call (keeping
everything else) reproduces the missing-hook-note failure in isolation:
test_skill_content_has_hook_command_note fails with the note absent, and
passes once the call is restored. Also ran the full tests/integrations/
suite: 2911 passed, 5 skipped — no regressions.

Also ran the full suite (.venv/bin/python -m pytest tests -q): 8049 passed,
12 skipped, 10 failed. The 10 failures are pre-existing *_python_parity
tests (test_check_prerequisites_python_parity.py,
test_create_new_feature_python_parity.py,
test_resolve_template_python_parity.py,
test_setup_plan_python_parity.py, test_setup_tasks_python_parity.py) that
fail identically on unmodified main in this sandbox (verified via
git stash), unrelated to this change.

Manually exercised the CLI end-to-end:

$ specify init --here --integration generic \
    --integration-options="--commands-dir .myagent/skills --skills" --script sh
$ find .myagent/skills -type f
.myagent/skills/speckit-analyze/SKILL.md
.myagent/skills/speckit-checklist/SKILL.md
.myagent/skills/speckit-clarify/SKILL.md
.myagent/skills/speckit-constitution/SKILL.md
.myagent/skills/speckit-converge/SKILL.md
.myagent/skills/speckit-implement/SKILL.md
.myagent/skills/speckit-plan/SKILL.md
.myagent/skills/speckit-specify/SKILL.md
.myagent/skills/speckit-tasks/SKILL.md
.myagent/skills/speckit-taskstoissues/SKILL.md

and confirmed omitting --skills still produces the original flat
.myagent/commands/speckit.<name>.md layout.

AI disclosure

This PR was written by an autonomous Claude Code agent (Claude Sonnet 5),
including the code change, tests, and this description. I (the human
submitter) reviewed the diff and the test run output above before opening it.

🤖 Generated with Claude Code

The generic (bring-your-own-agent) escape hatch could only ever emit
flat speckit.<name>.md command files, with no way to opt into the
speckit-<name>/SKILL.md layout every skills-format agent (Claude,
Codex, etc.) uses. Add a --skills flag to --integration-options that
renders the same command templates as SKILL.md directories under
--commands-dir instead, matching the agentskills.io layout used
elsewhere. Default behavior (flat .md files) is unchanged.

Fixes github#4561
_build_skill_content() duplicated SkillsIntegration.setup()'s per-file
body but dropped the call to post_process_skill_content(), which
injects the dot-to-hyphen hook-invocation note before every "For each
executable hook" instruction. Without it, a configured extension hook
(e.g. speckit.git.commit) would be invoked verbatim as
/speckit.git.commit, which doesn't exist under the
speckit-<name>/SKILL.md layout this feature introduces.

Add a small _GenericSkillsHelper(SkillsIntegration) — the same
delegation pattern CopilotIntegration uses for its own skills mode —
and call its post_process_skill_content() after building the SKILL.md
body. Add a regression test asserting the note appears.
@chelsealong
chelsealong requested a review from mnriem as a code owner September 12, 2026 14:39
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.

[Bug]: Generic escape hatch does not support SKILL.md rendering

1 participant