Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/supported-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ You can enable expanded workflows (`new`, `continue`, `ff`, `verify`, `bulk-arch
| Factory Droid (`factory`) | `.factory/skills/openspec-*/SKILL.md` | `.factory/commands/opsx-<id>.md` |
| Gemini CLI (`gemini`) | `.gemini/skills/openspec-*/SKILL.md` | `.gemini/commands/opsx/<id>.toml` |
| GitHub Copilot (`github-copilot`) | `.github/skills/openspec-*/SKILL.md` | `.github/prompts/opsx-<id>.prompt.md`\*\* |
| Hermes Agent (`hermes`) | `~/.hermes/skills/openspec-*/SKILL.md`\*\*\* | Not generated (no command adapter; use skill-based `/skill:openspec-*` invocations) |
| iFlow (`iflow`) | `.iflow/skills/openspec-*/SKILL.md` | `.iflow/commands/opsx-<id>.md` |
| Junie (`junie`) | `.junie/skills/openspec-*/SKILL.md` | `.junie/commands/opsx-<id>.md` |
| Kilo Code (`kilocode`) | `.kilocode/skills/openspec-*/SKILL.md` | `.kilocode/workflows/opsx-<id>.md` |
Expand All @@ -58,6 +59,8 @@ You can enable expanded workflows (`new`, `continue`, `ff`, `verify`, `bulk-arch

\*\* GitHub Copilot prompt files are recognized as custom slash commands in IDE extensions (VS Code, JetBrains, Visual Studio). Copilot CLI does not currently consume `.github/prompts/*.prompt.md` directly.

\*\*\* Hermes Agent installs skills to the user-global `~/.hermes/skills/` directory (not project-local) because Hermes discovers skills globally. A project-local marker directory (`.hermes/skills/`) is created during initialization for auto-detection and update-bookkeeping; the `.hermes` directory at the project root triggers tool detection, while `.hermes/skills/` inside it marks the project as OpenSpec-configured. Use `/skill:openspec-*` to invoke in-session.

## Non-Interactive Setup

For CI/CD or scripted setup, use `--tools` (and optionally `--profile`):
Expand All @@ -76,7 +79,7 @@ openspec init --tools none
openspec init --profile core
```

**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `windsurf`
**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `hermes`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `windsurf`

## Workflow-Dependent Installation

Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/add-hermes-agent-support/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-11
3 changes: 3 additions & 0 deletions openspec/changes/add-hermes-agent-support/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# add-hermes-agent-support

Add Hermes Agent as a supported tool with global skill installation
89 changes: 89 additions & 0 deletions openspec/changes/add-hermes-agent-support/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
## Context

OpenSpec currently supports 31 AI coding agents via the `AI_TOOLS` array in `config.ts`. Each tool entry has a `skillsDir` field (e.g., `.claude`) that serves two purposes simultaneously:

1. **Detection**: `getAvailableTools()` checks if `<projectRoot>/<skillsDir>` exists to detect the tool.
2. **Installation**: `init` and `update` write skills to `<projectRoot>/<skillsDir>/skills/`.

This dual-purpose design works for all current tools because they all discover skills from a project-local directory. Hermes Agent is different — it discovers skills from a user-global directory (`~/.hermes/skills/`), not from the project tree.

SpecKit (GitHub Spec Kit) already solved this exact problem with a `detect_dir` vs `dir` separation in its integration model. This change ports that pattern to OpenSpec via a generic `installDir` field.

## Goals / Non-Goals

**Goals:**

- Enable `openspec init --tools hermes` to install OpenSpec skills where Hermes discovers them (`~/.hermes/skills/openspec-*/SKILL.md`).
- Maintain correct project-level detection ("is Hermes configured for this project?") without being polluted by global skills that may exist from other projects.
- Introduce a generic mechanism (`installDir`) that any future global-scope agent can use, not a Hermes-specific hack.
- Centralize path resolution in shared helpers to prevent drift across `init`, `update`, `profile-sync-drift`, and `migration`.

**Non-Goals:**

- Adding a Hermes command adapter — Hermes invokes skills via `/skill:openspec-*`, matching the Kimi/ForgeCode/Vibe pattern.
- Modifying Hermes itself to support project-level skill discovery.
- Reworking `delivery=commands` behavior for global-install tools.

## Decisions

### 1. `installDir` field on `AIToolOption`

Add `installDir?: string` to the `AIToolOption` interface. When set, skills are installed to the resolved path (with `~` expansion) instead of `<projectRoot>/<skillsDir>/skills`. When unset, behavior is unchanged.

**Why a new field instead of overloading `skillsDir`:** `skillsDir` is already used for detection by `getAvailableTools()` — pointing it at `~/.hermes/skills` would cause false detection in every project on the machine. Keeping `skillsDir` as the project-local detection marker and adding `installDir` as the optional installation override cleanly separates the two concerns.

**Alternative considered:** SpecKit's `detect_dir` + `dir` pair. This would require two new fields and a larger refactor of every consumer that reads `skillsDir`. A single optional `installDir` override is simpler and sufficient — `skillsDir` already works as the detection marker.

### 2. Project-local marker directory for global-install tools

When `installDir` is set, `init` and `update` create an empty project-local directory at `<projectRoot>/<skillsDir>/skills/` (the marker directory). This serves:

- **Detection**: `getAvailableTools()` finds `.hermes/` and reports Hermes as available.
- **Configuration tracking**: `getToolSkillStatus()` checks the marker directory to determine if Hermes is "configured" for this project, rather than checking the global path (which may contain skills from other projects).
- **Update bookkeeping**: `profile-sync-drift` and `migration` use the marker to know which tools are active.

Hermes itself ignores this directory — skills live in `~/.hermes/skills/`. The marker is purely an OpenSpec bookkeeping artifact.

### 3. `resolveSkillsDir()` and `resolveMarkerDir()` shared helpers

Centralize path resolution in `tool-detection.ts`:

- `resolveSkillsDir(tool, projectRoot)`: returns `installDir` (expanded) if set, else `<projectRoot>/<skillsDir>/skills`.
- `resolveMarkerDir(tool, projectRoot)`: always returns `<projectRoot>/<skillsDir>/skills` (the project-local path, regardless of `installDir`).

All consumers (`init`, `update`, `profile-sync-drift`, `migration`, `tool-detection`) call these helpers instead of inlining `path.join(projectPath, tool.skillsDir, 'skills')`. This prevents drift when a future tool adds `installDir`.

### 4. `getToolSkillStatus` marker-based configured check for global-install tools

For tools with `installDir`, `configured` is determined by marker directory existence, not by checking the global install path. This prevents a machine-wide `~/.hermes/skills/openspec-*` from causing every project to report Hermes as "configured". `skillCount` and `fullyConfigured` still check the global path (when configured) to report accurate skill counts.

### 5. No command adapter

- **No command adapter** — Hermes exposes skills dynamically as `/skill:<name>` in-session. No file-based command directory exists. This matches the Kimi CLI, ForgeCode, and Mistral Vibe pattern — adapterless tools that are valid for skill generation but skip command-file generation.

### 6. Additive/overwrite-only update for global-install tools

When `installDir` is set, `update` and `init` SHALL NOT remove skill directories from the global path. The `removeUnselectedSkillDirs` and `removeSkillDirs` cleanup functions are skipped for global-install tools. Rationale:

- Global skills are shared across all projects on the machine. A per-project profile or delivery change must not delete skills that other projects depend on.
- Hermes has no command adapter — `delivery=commands` cannot substitute skills with commands. Deleting skills leaves the user with nothing.
- Residual skill directories (not in the current profile) are harmless — Hermes displays them as available, and their content remains valid.

**What still happens during update for global-install tools:**
- Overwrite existing skill files with new version content ✓
- Create newly-selected skill directories ✓
- Create/maintain the project-local marker directory ✓

**What is skipped:**
- `removeUnselectedSkillDirs` — no deletion of non-profile skill dirs
- `removeSkillDirs` — no deletion on delivery=commands switch

For project-local tools (no `installDir`), deletion behavior is unchanged.

## Risks / Trade-offs

- **Global skills are shared across projects**: Running `openspec init --tools hermes` in project A installs skills globally; project B on the same machine will see those skills too. This is inherent to Hermes's global discovery model and matches how Hermes users already manage skills. The marker directory correctly scopes "configured for this project" detection.

- **`openspec update` overwrites global skills**: If two projects use different OpenSpec versions, updating one overwrites the global skill files. This is acceptable because OpenSpec skills are version-pinned — all projects should use the same OpenSpec version. Skill directories not in the current profile are left in place (additive-only); they remain valid but may appear as "extra" workflows to Hermes.

- **`getToolSkillStatus` reads from global path for `skillCount`**: This means `skillCount` may report non-zero even in a fresh project if global skills exist. The `configured` flag correctly uses the marker, so this only affects display, not detection logic.
47 changes: 47 additions & 0 deletions openspec/changes/add-hermes-agent-support/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## Why

Hermes Agent is an open-source AI agent framework by Nous Research that runs in the terminal, messaging platforms, and IDEs. It is in the same category as Claude Code, Codex, and Gemini CLI — all already supported by OpenSpec. Hermes users cannot use `openspec init --tools hermes` because Hermes is absent from the supported tool list.

Unlike existing supported tools, Hermes discovers skills from a user-global directory (`~/.hermes/skills/`) rather than a project-local path. OpenSpec's current architecture assumes `skillsDir` serves double duty — both detection marker and installation target — which breaks for global-scope tools. This change introduces a generic `installDir` field to separate installation path from detection path, enabling Hermes support and any future global-scope agent.

## What Changes

- Add `installDir` optional field to `AIToolOption` interface — when set, skills are installed to the specified (global) path instead of `<projectRoot>/<skillsDir>/skills`. The `~` prefix is expanded to the user's home directory.
- Add Hermes Agent to `AI_TOOLS` with `skillsDir: '.hermes'` (project-local marker for detection) and `installDir: '~/.hermes/skills'` (global installation target). No command adapter — Hermes invokes skills via `/skill:openspec-*` in-session, matching the Kimi/ForgeCode/Vibe pattern.
- When `installDir` is set, `init` and `update` create a project-local marker directory (`.hermes/skills/`) so detection and update-bookkeeping know the tool is configured for this project, while skills are written to the global directory where Hermes discovers them.
- `getToolSkillStatus` uses the marker directory (not the global install path) to determine "configured" for global-install tools, preventing global skills from polluting project-level tool detection.
- Add `resolveSkillsDir()` and `resolveMarkerDir()` helper functions in `tool-detection.ts` as the single source of truth for path resolution, replacing inline `path.join(projectPath, tool.skillsDir, 'skills')` calls across `init.ts`, `update.ts`, `profile-sync-drift.ts`, and `migration.ts`.
- Update `docs/supported-tools.md` with Hermes Agent row, tool ID, and footnotes.
- No command adapter is added — Hermes exposes skills dynamically as `/skill:<name>`.
- When `installDir` is set, `update` and `init` SHALL NOT delete skill directories from the global path — update is additive/overwrite-only for global-install tools. This prevents a per-project profile or delivery change from destroying skills that other projects on the same machine depend on.

## Capabilities

### New Capabilities

_None._

### Modified Capabilities

- `ai-tool-paths`: define the `.hermes` skills root and `~/.hermes/skills` global install path for Hermes Agent.
- `cli-init`: clarify that tools with `installDir` install skills to a global path while creating a project-local marker directory for detection.
- `cli-update`: skill regeneration for `installDir` tools writes to the global path and maintains the project-local marker.

## Impact

- `src/core/config.ts` — add `installDir` field to `AIToolOption`; add Hermes Agent entry to `AI_TOOLS`.
- `src/core/shared/tool-detection.ts` — add `resolveSkillsDir()` and `resolveMarkerDir()` helpers; update `getToolSkillStatus` to use marker directory for global-install tools; update `getToolVersionStatus` to read version from the resolved (global) path.
- `src/core/shared/index.ts` — export new helpers.
- `src/core/init.ts` — use `resolveSkillsDir`/`resolveMarkerDir`; create marker directory for `installDir` tools.
- `src/core/update.ts` — use `resolveSkillsDir`/`resolveMarkerDir`; create marker directory for `installDir` tools (two call sites).
- `src/core/profile-sync-drift.ts` — use `resolveSkillsDir` (two call sites).
- `src/core/migration.ts` — use `resolveSkillsDir`.
- `docs/supported-tools.md` — add Hermes Agent row, tool ID, footnotes.
- `test/core/available-tools.test.ts` — add Hermes detection test.
- `test/core/shared/tool-detection.test.ts` — add `resolveSkillsDir`, `resolveMarkerDir`, Hermes `getToolSkillStatus`/`getToolVersionStatus` tests.

## Non-Goals

- Adding a Hermes command adapter (`src/core/command-generation/adapters/hermes.ts`).
- Changing Hermes itself to support project-level skill discovery — that is a Hermes feature, not an OpenSpec concern.
- Reworking `delivery=commands` behavior for global-install tools beyond ensuring skills are not deleted (no command adapter substitution).
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# ai-tool-paths Delta Specification

## MODIFIED Requirements

### Requirement: AIToolOption skillsDir field

The `AIToolOption` interface SHALL include an optional `skillsDir` field for skill generation path configuration, and an optional `installDir` field for overriding the skill installation directory.

#### Scenario: Interface includes skillsDir field

- **WHEN** a tool entry is defined in `AI_TOOLS` that supports skill generation
- **THEN** it SHALL include a `skillsDir` field specifying the project-local base directory (e.g., `.claude`)

#### Scenario: Skills path follows Agent Skills spec

- **WHEN** generating skills for a tool with `skillsDir: '.claude'` and no `installDir`
- **THEN** skills SHALL be written to `<projectRoot>/<skillsDir>/skills/`
- **AND** the `/skills` suffix is appended per Agent Skills specification

#### Scenario: installDir overrides installation path

- **WHEN** a tool has `installDir` set (e.g., `'~/.hermes/skills'`)
- **THEN** skills SHALL be written to the expanded `installDir` path (with `~` expanded to the user home directory)
- **AND** the `skillsDir` field SHALL still be used as the project-local detection marker

#### Scenario: installDir supports tilde expansion

- **WHEN** `installDir` starts with `~`
- **THEN** the `~` SHALL be expanded to the operating system home directory
- **AND** the resulting path SHALL be absolute

### Requirement: Path configuration for supported tools

The `AI_TOOLS` array SHALL include `skillsDir` for tools that support the Agent Skills specification.

#### Scenario: Claude Code paths defined

- **WHEN** looking up the `claude` tool
- **THEN** `skillsDir` SHALL be `.claude`

#### Scenario: Cursor paths defined

- **WHEN** looking up the `cursor` tool
- **THEN** `skillsDir` SHALL be `.cursor`

#### Scenario: Windsurf paths defined

- **WHEN** looking up the `windsurf` tool
- **THEN** `skillsDir` SHALL be `.windsurf`

#### Scenario: Kimi CLI paths defined

- **WHEN** looking up the `kimi` tool
- **THEN** `skillsDir` SHALL be `.kimi`

#### Scenario: Hermes Agent paths defined

- **WHEN** looking up the `hermes` tool
- **THEN** `skillsDir` SHALL be `.hermes`
- **AND** `installDir` SHALL be `'~/.hermes/skills'`

#### Scenario: Tools without skillsDir

- **WHEN** a tool has no `skillsDir` defined
- **THEN** skill generation SHALL error with message indicating the tool is not supported

### Requirement: Cross-platform path handling

The system SHALL handle paths correctly across operating systems.

#### Scenario: Path construction on Windows

- **WHEN** constructing skill paths on Windows
- **THEN** the system SHALL use `path.join()` for all path construction
- **AND** SHALL NOT hardcode forward slashes

#### Scenario: Path construction on Unix

- **WHEN** constructing skill paths on macOS or Linux
- **THEN** the system SHALL use `path.join()` for consistency

### Requirement: Skills directory resolution helpers

The system SHALL provide shared helper functions to resolve skill installation and marker directories consistently across all consumers.

#### Scenario: Resolving skills directory for project-local tools

- **WHEN** a tool has no `installDir` set
- **THEN** `resolveSkillsDir` SHALL return `<projectRoot>/<skillsDir>/skills`

#### Scenario: Resolving skills directory for global-install tools

- **WHEN** a tool has `installDir` set (e.g., `'~/.hermes/skills'`)
- **THEN** `resolveSkillsDir` SHALL return the expanded absolute path
- **AND** SHALL ignore `projectRoot`

#### Scenario: Resolving marker directory

- **WHEN** resolving the project-local marker directory for any tool
- **THEN** `resolveMarkerDir` SHALL always return `<projectRoot>/<skillsDir>/skills`
- **AND** SHALL ignore `installDir`
Loading