feat: add Hermes Agent integration (with review fixes)#2651
Open
majordave wants to merge 8 commits into
Open
Conversation
- Full SkillsIntegration subclass with dual install strategy (project-local .hermes/skills/ + global ~/.hermes/skills/) - CLI fix: integration_uninstall now calls integration.teardown() instead of manifest.uninstall() directly, allowing custom cleanup - Fix Copilot review issues: - Docstring now reflects both -Q (quiet) and -q (query) flags - Empty command guard prevents passing empty skill names - Add catalog entry for hermes in integrations/catalog.json Co-authored-by: Zhaoxiaoguang001 <3357983213@qq.com>
Hermes loads skills from the global ~/.hermes/skills/ directory, not from project-local paths. The old dual-install strategy copied SKILL.md files to both locations — project-local (for manifest tracking) and global (for Hermes discovery). This change removes the project-local copies entirely: - setup() writes directly to ~/.hermes/skills/speckit-*/SKILL.md - An empty .hermes/skills/ marker directory is created in the project so extension commands (e.g. git) can detect Hermes as an active integration via register_commands_for_all_agents() - teardown() cleans both the global speckit-* dirs and the local marker - import yaml moved to local import inside setup() Tests updated: Hermes-specific tests now assert global skill location, and shared SkillsIntegrationTests that assumed project-local files are overridden with Hermes-appropriate assertions. Co-authored-by: Zhaoxiaoguang001 <3357983213@qq.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a new built-in Hermes Agent integration to Spec Kit’s integration system, enabling specify init --integration hermes / --ai hermes to install Spec Kit command skills under Hermes’ expected skills directory and wire up project context via AGENTS.md.
Changes:
- Introduces
HermesIntegration(aSkillsIntegrationsubclass) and registers it as a built-in integration. - Updates
specify integration uninstallto callintegration.teardown()(enabling integrations to perform custom uninstall logic). - Adds Hermes to the integration catalog and adds a Hermes integration test suite based on shared
SkillsIntegrationTests.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/integrations/hermes/__init__.py |
New Hermes integration implementation (global skills install + custom teardown + CLI dispatch args). |
src/specify_cli/integrations/__init__.py |
Registers Hermes as a built-in integration. |
src/specify_cli/__init__.py |
Switches uninstall path to use integration.teardown() and adds an error when integration is missing from registry. |
integrations/catalog.json |
Adds hermes entry to the integration catalog. |
tests/integrations/test_integration_hermes.py |
Adds Hermes-specific integration tests, overriding shared assumptions about project-local skills. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 5/5 changed files
- Comments generated: 6
Addresses all 6 review comments from copilot-pull-request-reviewer: 1. Hard-fail on missing integration key → fall back to manifest.uninstall() with a warning instead of raising an error. Allows users to always remove stale integration files even when the integration class is missing from the registry. 2. HOME isolation in tests → every test that calls setup() or CliRunner now monkeypatches Path.home() to a temp directory, keeping the test suite hermetic and non-destructive. 3. HermesIntegration.teardown() now delegates to manifest.uninstall() for project-local tracked files (scripts, manifest), merging results with global cleanup. 4. Global skills cleanup gated behind force=True to avoid destroying speckit-* skills shared across multiple Spec Kit projects when running 'specify integration uninstall hermes' without --force. 5. Line 160 isolation (CLI test test_complete_file_inventory_sh). 6. Line 258 isolation (Path.home assertion in test_ai_hermes_without_ai_skills_auto_promotes).
2c4604e to
70f5732
Compare
| Hermes discovers them globally. A project-local marker directory | ||
| (``.hermes/skills/`` empty) is created so extension commands (e.g. | ||
| git) can detect Hermes as an active integration. Uninstall removes | ||
| both the marker and global skills. |
Comment on lines
+90
to
+103
| templates = self.list_command_templates() | ||
| if not templates: | ||
| return [] | ||
|
|
||
| script_type = opts.get("script_type", "sh") | ||
| arg_placeholder = ( | ||
| self.registrar_config.get("args", "$ARGUMENTS") | ||
| if self.registrar_config | ||
| else "$ARGUMENTS" | ||
| ) | ||
|
|
||
| global_skills_dir = self._hermes_home_skills_dir() | ||
| global_skills_dir.mkdir(parents=True, exist_ok=True) | ||
|
|
Comment on lines
+113
to
+125
| # Parse frontmatter for description | ||
| frontmatter: dict[str, Any] = {} | ||
| if raw.startswith("---"): | ||
| parts = raw.split("---", 2) | ||
| if len(parts) >= 3: | ||
| import yaml | ||
|
|
||
| try: | ||
| fm = yaml.safe_load(parts[1]) | ||
| if isinstance(fm, dict): | ||
| frontmatter = fm | ||
| except yaml.YAMLError: | ||
| pass |
Comment on lines
+225
to
+227
| if skill_file.exists(): | ||
| removed.append(skill_file) | ||
| rmtree(skill_dir, ignore_errors=True) |
Comment on lines
+134
to
+153
| def test_modified_file_survives_uninstall(self, tmp_path, monkeypatch): | ||
| """Override: Hermes global skills are removed on uninstall only | ||
| when force=True (no hash-based preservation since they're not in manifest).""" | ||
| home = _fake_home(tmp_path) | ||
| monkeypatch.setattr(Path, "home", lambda: home) | ||
|
|
||
| i = get_integration(self.KEY) | ||
| m = IntegrationManifest(self.KEY, tmp_path) | ||
| created = i.install(tmp_path, m) | ||
| m.save() | ||
| # Pick a global skill file | ||
| skill_files = [f for f in created if "SKILL.md" in str(f)] | ||
| assert len(skill_files) > 0 | ||
| modified_file = skill_files[0] | ||
| modified_file.write_text("user modified this", encoding="utf-8") | ||
| # Global skills are only removed with force=True | ||
| removed, skipped = i.teardown(tmp_path, m, force=True) | ||
| assert not modified_file.exists(), ( | ||
| "Modified global skill should be removed on teardown with force=True" | ||
| ) |
Comment on lines
+80
to
+88
| """Install command templates as global Hermes skills. | ||
|
|
||
| Writes each skill directly to | ||
| ``~/.hermes/skills/speckit-<name>/SKILL.md`` where Hermes | ||
| discovers them at runtime. No project-local SKILL.md copies are | ||
| created — the global directory is the single source of truth. | ||
| A project-local marker (``.hermes/skills/`` empty) is created | ||
| so extension commands (e.g. git) can detect Hermes as an active | ||
| integration. |
- Move to module scope (was inside per-template loop) - Add safety checks in setup() matching standard - Fix docstrings: global skills always removed on uninstall (standard) - Fix removal tracking: only report after successful rmtree - Override shared test_modified_file_survives_uninstall with Hermes-appropriate behaviour (global skills always removed, no hash tracking) - Update PR description to match implementation (global-only skills + marker)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a new built-in integration for Hermes Agent (by Nous Research) to Spec Kit's integration system, enabling
specify init --integration hermes/--ai hermesto scaffold Spec Kit commands as Hermes skills under~/.hermes/skills/with anAGENTS.mdcontext file.Based on the original work by @Zhaoxiaoguang001 in PR #2547, with the following fixes and improvements:
Changes from original PR #2547
src/specify_cli/integrations/hermes/__init__.py— Full Hermes integration:SkillsIntegration(skills-based, same as Claude/Codex)~/.hermes/skills/speckit-<name>/SKILL.md— no project-local copies (Hermes discovers skills globally). An empty.hermes/skills/marker is created in the project so extension commands (e.g. git) can detect Hermes as active.setup()now includesmanifest.project_rootsafety checks matching the standard patternteardown()always removes globalspeckit-*skills (matching standard integration behaviour where all created files are cleaned up), plus the project-local marker and manifestimport yamlmoved to module scope (was inside the per-template loop)src/specify_cli/__init__.py— CLIintegration_uninstallnow callsintegration.teardown()instead of callingmanifest.uninstall()directly, allowing custom teardown logic in integrations to run properly. Falls back tomanifest.uninstall()when the integration class is missing from the registry.integrations/catalog.json— Addedhermescatalog entrytests/integrations/test_integration_hermes.py— Uses sharedSkillsIntegrationTests(32 tests, all passing). Every test that touches~/.hermes/monkeypatchesPath.home()to a temp directory for hermetic, non-destructive execution.Why global skills only?
Unlike Claude Code (
.claude/skills/) or Codex (.agents/skills/) which load skills from a project-local directory, Hermes loads skills exclusively from~/.hermes/skills/(user home directory). Skills are written directly to the global directory — a project-local.hermes/skills/directory is created empty as a detection marker for Spec Kit extension commands.All global
speckit-*skills are cleaned up onspecify integration uninstall(standard behaviour, matching every other built-in integration).Co-authored-by: Zhaoxiaoguang001 3357983213@qq.com