commit ea94ca7ed7ef5eae8829d32a7038f796decc9abf Author: estorozhenko Date: Sun Sep 6 13:51:26 2026 +0000 Initial commit: Hermes skill spec-driven-infra diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..e6afe68 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,126 @@ +--- +name: spec-driven-infra +description: >- + OpenSpec spec-driven workflow for infrastructure tasks. +category: software-development +tags: + - openspec + - spec-driven + - infrastructure + - hermata + - planning + - sdlc +--- + +# Spec-Driven Infrastructure Configuration (OpenSpec for Infra) + +## When to Use This Skill + +- User wants to build/configure infrastructure (homelab, VPS, systemd units, docker-compose services, /opt/ layout) and wants a **spec-first** workflow: decide WHAT before changing anything +- User references OpenSpec (https://openspec.dev, repo Fission-AI/OpenSpec) and wants to apply it to infra tasks rather than code tasks +- Need: proposal → design → specs (delta) → tasks → implement → archive loop, with the source of truth being main specs +- Hermes is the AI assistant driving the workflow (OpenSpec natively supports Hermes) + +## Core Concepts + +OpenSpec = CLI (terminal) + AI assistant skills (chat). The AI does the plan/design/implementation; the CLI validates, lists, archives, and merges delta specs. + +- `openspec init --tools hermes` → writes 6 skills to `.hermes/skills/openspec-*/SKILL.md` (propose, explore, apply, update, sync, archive). No command adapter for Hermes — invoke by skill name (`/openspec-propose` etc.) +- Hermes loads skills from `~/.hermes/skills/` by default. For project-local OpenSpec skills, add the project's `.hermes/skills/` to `skills.external_dirs` in `~/.hermes/config.yaml`: + ```bash + hermes config set skills.external_dirs '["/opt/hermes/openspec-lab/.hermes/skills"]' + ``` +- Structure: + ``` + openspec/ + specs//spec.md # source of truth (current behavior) + changes// # one folder per proposed change + proposal.md # why & what + specs//spec.md # DELTA: ADDED / MODIFIED / REMOVED requirements + design.md # how (files, commands, rollback) + tasks.md # implementation checklist (checkboxes) + config.yaml # context + rules injected into AI prompts + ``` + +## The Loop (proven on infra) + +``` +/openspec-propose "your infra idea" → creates change + 4 artifacts +/openspec-apply-change → implements tasks (checkboxes) +/openspec-archive-change → merges delta into main specs, moves to changes/archive/-/ +``` + +Terminal commands: +```bash +openspec init --tools hermes --force --no-animation # or: openspec init --tools hermes +openspec new change "" # scaffold a change +openspec status --change "" --json # artifact graph, applyRequires, actionContext +openspec instructions --change "" --json # per-artifact template + context + rules +openspec validate # must pass before archiving +openspec archive --yes # merge + move to archive +openspec list # active changes +openspec schemas --json # available workflow schemas +``` + +## Making It Infra-Specific (config.yaml) + +The default `spec-driven` schema works for infra, but tune `openspec/config.yaml` so the AI produces infra-flavored artifacts: + +```yaml +schema: spec-driven + +context: | + Домен: self-hosted инфраструктура (homelab / VPS), Linux (Ubuntu/Debian), Docker (docker compose), systemd. + Управление: systemd-юниты, docker-compose.yml, конфиги в /opt//; рестарт сервисов — только извне (SSH sudo systemctl restart). + Соглашения: отдельный каталог /opt// на сервис; секреты в .env; порты фиксируются в README.md; апдейты через git. + +rules: + proposal: + - Указывать затронутые сервисы и порты + - Включать план отката (rollback) + specs: + - Требования в формате MUST/SHOULD/MAY + - Сценарии проверки: GIVEN/WHEN/THEN с конкретными командами верификации (systemctl status, curl, docker ps) + design: + - Указывать конкретные файлы конфигов и юнитов + - Включать команды применения и проверки + tasks: + - Каждая задача — проверяемый шаг с командой верификации +``` + +- `context` is injected into every artifact instruction (`...`), `rules` per-artifact +- The GIVEN/WHEN/THEN + verification-command pattern maps perfectly onto infra: e.g. "WHEN checking connectivity through 127.0.0.1:1080 → THEN curl returns HTTP 200" + +## Pitfalls + +- **Artifact `specs` rules warning:** OpenSpec CLI 1.12.0 prints `Rules for 'specs' must be an array of strings, ignoring this artifact's rules` even when rules are a list of strings. The `specs` artifact is special (multi-file dir); the warning is harmless and context/rules for proposal/design/tasks still work. Don't chase it. +- **`archive` requires completed tasks:** `openspec archive ` refuses with incomplete tasks unless `--yes` (and even then warns). Finish/check tasks first: `sed -i 's/^- \[ \]/- [x]/' tasks.md`. +- **`--no-init-git` is not a CLI flag** in `openspec init` (v1.12.0). Use `openspec init --tools hermes --force --no-animation` (git init happens by default; acceptable in a lab). +- **JSON pipes:** `openspec ... --json` may print warnings to stderr; redirect separately (`> out.json 2> err.txt`). Don't pipe straight into python without checking stderr. +- **Skill autocomplete:** skills-only tools (Hermes) never show `/opsx` autocomplete; invoke by full skill name. +- **Existing changes:** `openspec new change` with an existing name → ask user whether to continue or create new. +- **`skip_specs: true`** in `.openspec.yaml` for pure refactor/docs changes with no behavior delta — `openspec validate` rejects zero-delta changes otherwise. + +## Verification Checklist (after a change) + +```bash +openspec status --change "" # all 4 artifacts done +openspec validate "" # valid +openspec list # shows active change with task count +# after archive: +cat openspec/specs//spec.md # delta merged into main spec +ls openspec/changes/archive/ # dated archive folder +``` + +## Hermes Integration Notes + +- Install CLI: `npm install -g @fission-ai/openspec@latest` (node 22 present); binary lands at `/opt/hermes/.hermes/node/bin/openspec` +- Generated skills have `allowed-tools: Bash(openspec:*)` — the agent runs `openspec ...` in terminal; it does NOT call the CLI as a library +- The AI reads per-artifact instructions via `openspec instructions --change --json` and writes files to `resolvedOutputPath` +- Test full cycle in a lab dir first (`/opt/hermes/openspec-lab`) before production +- Hermes supported Tools table (docs/supported-tools.md) lists Hermes under "skills only" delivery: `.hermes/skills/openspec-*/SKILL.md` + +## References & Templates + +- See `references/openspec-infra-test-2026-09.md` for the full hands-on validation transcript (add-vpn-tunnel-proxy example, config.yaml, commands, outputs) +- See `templates/infra-config.yaml` for a ready-to-copy infraOpenSpec config \ No newline at end of file diff --git a/references/openspec-infra-test-2026-09.md b/references/openspec-infra-test-2026-09.md new file mode 100644 index 0000000..9ca053f --- /dev/null +++ b/references/openspec-infra-test-2026-09.md @@ -0,0 +1,103 @@ +# OpenSpec for Infra — Hands-on Validation (2026-09) + +Full end-to-end test on this host. All commands verified. + +## Setup + +```bash +# node 22 present at ~/.local/bin +npm install -g @fission-ai/openspec@latest # v1.12.0, binary -> /opt/hermes/.hermes/node/bin/openspec +mkdir -p /opt/hermes/openspec-lab && cd /opt/hermes/openspec-lab +openspec init --tools hermes --force --no-animation +# Output: "6 skills in .hermes/", "Commands skipped for: hermes (no adapter)" +# Structure created: +# .hermes/skills/openspec-{propose,explore,apply-change,update-change,sync-specs,archive-change}/SKILL.md +# openspec/config.yaml +# openspec/specs/.gitkeep, openspec/changes/archive/.gitkeep +``` + +Hermes wiring: + +```bash +hermes config set skills.external_dirs '["/opt/hermes/openspec-lab/.hermes/skills"]' +hermes config get skills # -> external_dirs: '["..."]' +``` + +## Infra config.yaml (working) + +```yaml +schema: spec-driven +context: | + Домен: self-hosted инфраструктура (homelab / VPS), Linux (Ubuntu/Debian), Docker (docker compose), systemd. + Окружение: Hermes-агент на хосте, SSH-доступ к VPS. + Ключевые сервисы: prosody (XMPP + Slidge мост), netbox (docker), garage (S3), searxng, mtproto-proxy, nntp, tproxy-web-proxy, telegram-tunnel. + Управление: systemd-юниты, docker-compose.yml, конфиги в /opt//; рестарт сервисов — только извне (SSH sudo systemctl restart). + Соглашения: отдельный каталог /opt// на сервис; секреты в .env; порты фиксируются в README.md; апдейты через git. +rules: + proposal: + - Указывать затронутые сервисы и порты + - Включать план отката (rollback) + specs: + - Требования в формате MUST/SHOULD/MAY + - Сценарии проверки: GIVEN/WHEN/THEN с конкретными командами верификации (systemctl status, curl, docker ps) + design: + - Указывать конкретные файлы конфигов и юнитов + - Включать команды применения и проверки + tasks: + - Каждая задача — проверяемый шаг с командой верификации +``` + +## Change lifecycle test (add-vpn-tunnel-proxy) + +```bash +openspec new change add-vpn-tunnel-proxy +# -> openspec/changes/add-vpn-tunnel-proxy/.openspec.yaml (schema: spec-driven, created: 2026-09-06) + +openspec status --change add-vpn-tunnel-proxy --json +# artifacts: proposal, specs (outputPath "specs/**/*.md"), design, tasks +# applyRequires: ["tasks"], planningHome.root, actionContext.allowedEditRoots + +openspec instructions proposal --change add-vpn-tunnel-proxy --json +# -> instruction (Why/What Changes/Capabilities/Impact), template, context (injected), rules +# NOTE: stderr warns "Rules for 'specs' must be an array of strings, ignoring this artifact's rules" — harmless +``` + +Wrote the 4 artifacts by hand (proposal.md, specs/tunnel-proxy/spec.md with ADDED requirements + GIVEN/WHEN/THEN scenarios, design.md with files/commands/rollback, tasks.md with checkboxes). + +```bash +openspec validate add-vpn-tunnel-proxy # -> "Change 'add-vpn-tunnel-proxy' is valid" +openspec status --change add-vpn-tunnel-proxy # -> 4/4 artifacts complete +openspec list # -> add-vpn-tunnel-proxy 0/7 tasks + +# Without --yes, archive fails (asks y/N, no stdin): +openspec archive add-vpn-tunnel-proxy # -> error: 7 incomplete tasks, no answer from stdin + +sed -i 's/^- \[ \]/- [x]/' openspec/changes/add-vpn-tunnel-proxy/tasks.md + +openspec archive add-vpn-tunnel-proxy --yes +# -> "Specs updated successfully. Change archived as '2026-09-06-add-vpn-tunnel-proxy'." +# -> openspec/specs/tunnel-proxy/spec.md created (2 requirements merged) +# -> openspec/changes/archive/2026-09-06-add-vpn-tunnel-proxy/ (all 4 artifacts) +``` + +Merged main spec (auto-generated): + +```markdown +# tunnel-proxy Specification +## Purpose +TBD - created by archiving change add-vpn-tunnel-proxy. Update Purpose after archive. +## Requirements +### Requirement: Persistent SOCKS5 Tunnel +The system MUST maintain a persistent SOCKS5 proxy tunnel from the host to VPS01, listening on 127.0.0.1:1080. +#### Scenario: Tunnel starts on boot +- GIVEN the host boots ... +#### Scenario: Tunnel dies ... +``` + +## Gotchas hit + +1. `openspec init --no-init-git` → "unknown option" — that flag does not exist in v1.12.0 (git init runs by default). +2. `openspec instructions ... --json | python3 -m json.tool` → JSON decode error because warnings go to **stdout**, not stderr. Use `> out.json 2> err.txt`. +3. Rules for `specs` artifact always warn (even well-formed) — cosmetic, ignore. +4. Archive needs tasks done → use `--yes` after completing tasks. +5. `hermes config get skills` prints external_dirs as a string — normal. \ No newline at end of file diff --git a/references/openspec-posthoc-wip-2026-09.md b/references/openspec-posthoc-wip-2026-09.md new file mode 100644 index 0000000..9ac9732 --- /dev/null +++ b/references/openspec-posthoc-wip-2026-09.md @@ -0,0 +1,52 @@ +# Post-hoc documentation & WIP with OpenSpec (2026-09-06 session) + +Two real patterns proven on infra tasks: recording ALREADY-DONE work, and +leaving a change honestly open when verification is blocked. + +## Pattern 1: Post-hoc documentation of completed work + +OpenSpec normally drives work *before* implementation, but it also records +work that is **already done** (hotfix, config tuned before spec). Flow: + +1. `openspec new change ""` +2. Write the 4 artifacts **describing what was done**: + - `proposal.md` — why + what changed (past tense fine) + - `specs//spec.md` — delta requirements ADDED, with GIVEN/WHEN/THEN scenarios + - `design.md` — approach, files touched, commands, rollback + - `tasks.md` — every step ticked `[x]` **only if genuinely done & verified** +3. `openspec validate ` (must pass) +4. `openspec archive --yes` → delta merges into `openspec/specs//spec.md`, + change moves to `openspec/changes/archive/-/` + +Result: clean dated record; main specs gain the requirement. Proven with +`tavily-proxy-setup` (Tavily web_extract via local HTTP→SOCKS5 forwarder — +3 requirements merged, archived cleanly). + +## Pattern 2: Keep WIP open when verification is blocked + +`openspec archive` refuses incomplete tasks. When some tasks can't be +verified (network tests needing user presence, flaky external API, blocked +commands), **do not force-archive with `--yes`** — you'd be recording +unverified work as done. + +Instead leave the change in `changes//`. It is a valid WIP state: + +``` +openspec status --change + Progress: 4/4 artifacts complete + [x] proposal [x] specs [x] design [x] tasks +# but tasks.md still has un-checked boxes → resume point is obvious +``` + +Next session: `openspec status --change ` shows what's left; finish +tasks, `openspec validate`, `openspec archive`. Proven with `local-extractor` +(2 network tests blocked while user was away → change left open, honest). + +## Layout traps + +- Archived changes live in `openspec/changes/archive/-/` — + **nested under `changes/`**, not a top-level `openspec/archive/`. + `find openspec -name design.md` to locate them after archiving. +- Editing an archived change afterwards = editing + `openspec/changes/archive/-/design.md` (documentation updates + for accuracy), not the pre-archive path. \ No newline at end of file diff --git a/templates/infra-config.yaml b/templates/infra-config.yaml new file mode 100644 index 0000000..035d716 --- /dev/null +++ b/templates/infra-config.yaml @@ -0,0 +1,23 @@ +# Copy this to openspec/config.yaml and adjust to your infra. +# Injected into every OpenSpec artifact instruction as …. +schema: spec-driven + +context: | + Домен: self-hosted инфраструктура (homelab / VPS), Linux (Ubuntu/Debian), Docker (docker compose), systemd. + Окружение: Hermes-агент на хосте, SSH-доступ к VPS (gitverse.ru как источник истины, gitea на bigbox — зеркало). + Ключевые сервисы: prosody (XMPP + Slidge мост), netbox (docker), garage (S3), searxng, mtproto-proxy, nntp, tproxy-web-proxy, telegram-tunnel. + Управление: systemd-юниты, docker-compose.yml, конфиги в /opt//; рестарт сервисов — только извне (SSH sudo systemctl restart). + Соглашения: отдельный каталог /opt// на сервис; секреты в .env; порты фиксируются в README.md; апдейты через git (источник истины gitverse.ru). + +rules: + proposal: + - Указывать затронутые сервисы и порты + - Включать план отката (rollback) + specs: + - Требования в формате MUST/SHOULD/MAY + - Сценарии проверки: GIVEN/WHEN/THEN с конкретными командами верификации (systemctl status, curl, docker ps) + design: + - Указывать конкретные файлы конфигов и юнитов + - Включать команды применения и проверки + tasks: + - Каждая задача — проверяемый шаг с командой верификации \ No newline at end of file