The OpenSpec plugin has four layers that coexist by design. They serve different audiences and return different data shapes. Understanding the boundaries prevents "fixing" the duplication by rewriting one layer to use another.
┌─────────────────────────────────────────────────────────────┐
│ Skills (11 upstream workflow prompts) │
│ openspec-propose, openspec-apply-change, etc. │
│ Agent auto-loads when user intent matches description. │
│ Skills instruct agent to run openspec CLI commands. │
└─────────────┬───────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ CLI interface (two paths, same binary) │
│ │
│ openspec_cli tool terminal tool │
│ ├─ Gated by check_fn ├─ Always available │
│ ├─ JSON output parsed ├─ Raw stdout │
│ └─ Discoverable in tool └─ Skills use this by default │
│ list │
└─────────────┬───────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ openspec CLI binary │
│ (installed separately via npm, not part of plugin) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Plugin tools (19 existing + openspec_cli = 20) │
│ openspec_context, openspec_show, openspec_spec_diff, etc. │
│ Different JSON shapes than CLI. │
│ Used for: ad-hoc agent queries, dashboard backend. │
│ NOT used by: upstream skills (they call CLI directly). │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Dashboard │
│ Board, spec browser, change detail │
│ Reads from filesystem + git, not from CLI or plugin tools. │
└─────────────────────────────────────────────────────────────┘
The upstream OpenSpec skills are structured prompts that instruct the agent
to run openspec CLI commands directly:
openspec new change "<name>"
openspec status --change "<name>" --json
openspec instructions apply --change "<name>" --jsonThe CLI returns JSON with fields like applyRequires, artifacts,
contextFiles, actionContext, and planningHome. The skill prompts
parse these fields to drive the workflow.
Our plugin tools wrap a subset of the CLI but return different JSON shapes. For example:
| CLI output | Plugin tool output |
|---|---|
openspec status --json → {changeName, schemaName, artifacts, applyRequires, planningHome, actionContext} |
openspec_status → {ok, change, status, counts} |
openspec instructions apply --json → {contextFiles, state, progress, tasks, instruction} |
openspec_instructions → {ok, artifact, instructions} |
Rewriting skills to use plugin tools would require modifying every prompt to match our shapes — a permanent sync burden with no value gained. The upstream prompts are the product.
| You want to… | Use |
|---|---|
| Follow a workflow (propose, apply, verify, archive) | Skills — the agent loads them automatically when intent matches |
| Run a CLI command with raw JSON output | openspec_cli tool — gated, discoverable, parses JSON |
| Run a CLI command via shell pipeline | terminal tool — always available, raw stdout |
| Query a change/spec without the CLI | Plugin tools (openspec_context, openspec_show, openspec_spec_diff, etc.) — filesystem-backed, work without the CLI binary |
| Browse changes and specs visually | Dashboard at /openspec |
11 skills bundled from upstream OpenSpec CLI v1.4.1:
| Skill | Workflow | Profile |
|---|---|---|
openspec-propose |
propose | core |
openspec-explore |
explore | core |
openspec-apply-change |
apply | core |
openspec-archive-change |
archive | core |
openspec-new-change |
new | expanded |
openspec-continue-change |
continue | expanded |
openspec-ff-change |
ff | expanded |
openspec-verify-change |
verify | expanded |
openspec-sync-specs |
sync | expanded |
openspec-bulk-archive-change |
bulk-archive | expanded |
openspec-onboard |
onboard | expanded |
Skills are generated by openspec init --tools claude and bundled
unmodified. The generatedBy version in each skill's frontmatter shows
which OpenSpec version produced the content.
When upstream OpenSpec updates the workflow prompts:
# 1. Set all workflows in openspec config
python3 -c "
import json
c = json.load(open('~/.config/openspec/config.json'.replace('~', __import__('os').path.expanduser('~'))))
c['profile'] = 'custom'
c['workflows'] = ['propose','explore','new','continue','apply','ff','sync','archive','bulk-archive','verify','onboard']
json.dump(c, open(c_path := __import__('os').path.expanduser('~/.config/openspec/config.json'), 'w'), indent=2)
"
# 2. Generate into a temp dir
mkdir -p /tmp/opsx-regen && cd /tmp/opsx-regen
openspec init . --tools claude
# 3. Copy into the plugin
cp -r .claude/skills/openspec-* /path/to/hermes-openspec/skills/
# 4. Restore core profile
openspec config profile coreThe ideal solution is contributing a Hermes adapter to the OpenSpec CLI's
command-generation system (in dist/core/command-generation/adapters/).
This would let openspec init --tools hermes generate skills directly into
the plugin structure. Until then, the manual regeneration above is
mechanical and infrequent.