docs(openspec): change dsh-openspec, план и видимость скиллов в приёмке (#42)

Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
This commit is contained in:
Andrey Epifancev 2026-10-24 19:44:38 +04:00
parent ca3814f62a
commit 6876e4c6c8
4 changed files with 575 additions and 2 deletions

View File

@ -0,0 +1,398 @@
# DSH OpenSpec Integration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Дать DSH-проекту воркфлоу OpenSpec без Claude Code: шесть скиллов в `.agents/skills`, проверка и установка адаптера в `dsh/install.sh`, документация в PORT/README и живая приёмка полного цикла `new change → validate --strict → archive -y`.
**Architecture:** Генератор `openspec init --tools agents` кладёт скиллы в проектный корень `.agents/skills`, который DSH читает нативно (проверено сабагентом 24.10.2026). Обвязка не переписывает воркфлоу и не копирует его глобально: она коммитит скиллы в свой репозиторий (dogfooding), проверяет CLI и каталог в `--check` и добавляет agents-адаптер в чужой проект флагом `--openspec`. Слэш-команд нет — вход через model-invocable скиллы.
**Tech Stack:** bash (`set -euo pipefail`), Node test runner (`node --test`), markdown, openspec CLI 1.13.1.
**Spec:** `openspec/changes/dsh-openspec/` — `proposal.md`, `design.md` (источник решений), `specs/dsh-openspec/spec.md` (требования и сценарии).
**Ветка:** `feat/42-dsh-openspec`, issue #42, worktree `.claude/worktrees/openspec`.
## Global Constraints
- `dsh/install.sh` остаётся идемпотентным; новые шаги не трогают `~/.claude`, `~/.dsh/roles`, бандл и профиль.
- Никаких копий `openspec-*` в `~/.dsh/skills`: `--skills` копирует только `dsh/skills/*` (сейчас `role`, `sdd`).
- Тесты идут без DSH runtime и без установленного openspec: внешний CLI подменяется скриптом в `PATH`.
- Оба набора зелёные: `bash dsh/tests/run.sh` и `bash tests/run.sh`. Новый файл `dsh/tests/openspec.test.mjs` подхватывается `dsh/tests/run.sh` по маске.
- Документация и тексты — по-русски; структура PORT.md (нумерованные разделы) и README сохраняется.
- Коммиты — с trailer `Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>`; force-push запрещён.
- Маркировка: находки ревью `T<n>.<k>` / `C-n` / `I-n` / `M-n`, рулинги `R-n (design #n)`, наблюдения `O-n`, замечания человека `H-n`; у каждой статус `fixed <commit>` / `deferred` / `rejected <причина>`.
## Review Focus
- `--openspec` в каталоге без `openspec/` → скрипт обязан отказать и не создать корень; иначе флаг в чужом проекте молча заводит `openspec/`.
- `--check` без `openspec` в `PATH` → `WARN`, код возврата 0; остальные `ok:`-строки на месте, `python3/git/node` не задеты.
- `bash dsh/install.sh --skills` → в `~/.dsh/skills` нет каталогов `openspec-*`; появление там копии — регресс R-1.
- Повторный `--openspec` в том же проекте → второй прогон не ломает скиллы и не плодит файлы.
- Frontmatter каждого скилла: `name` совпадает с именем каталога, `description` содержит «openspec».
---
### Task 1: Скиллы `.agents/skills` в репозитории
**Files:**
- Create: `.agents/skills/openspec-{propose,apply-change,archive-change,explore,sync-specs,update-change}/SKILL.md`
- Create: `.agents/skills/.openspec-target`
- Create: `dsh/tests/openspec.test.mjs` (первый тест)
**Interfaces:**
- Produces: каталоги `.agents/skills/openspec-*/SKILL.md` с frontmatter `name`, `description`, `metadata.generatedBy`; файл `.openspec-target` со строкой `agents`.
- Consumes: ничего.
- [ ] **Step 1: Сгенерировать скиллы генератором, а не руками**
Run: `openspec init --tools agents --no-animation` из корня worktree (он же корень репозитория обвязки); повторить после Task 5, если версия CLI изменится.
Expected: `6 skills in .agents/`, `Config: openspec/config.yaml (exists)`; `git status --short` показывает только `?? .agents/`.
- [ ] **Step 2: Написать падающий тест** `dsh/tests/openspec.test.mjs`
```js
import assert from 'node:assert/strict'
import { existsSync, readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
const here = dirname(fileURLToPath(import.meta.url))
const root = join(here, '..', '..')
const agentsSkills = join(root, '.agents', 'skills')
const NAMES = [
'openspec-apply-change',
'openspec-archive-change',
'openspec-explore',
'openspec-propose',
'openspec-sync-specs',
'openspec-update-change',
]
test('repository ships six agents skills for DSH', () => {
assert.equal(readFileSync(join(agentsSkills, '.openspec-target'), 'utf8').trim(), 'agents')
for (const name of NAMES) {
const file = join(agentsSkills, name, 'SKILL.md')
assert.ok(existsSync(file), `${name}/SKILL.md is missing`)
const text = readFileSync(file, 'utf8')
assert.match(text, new RegExp(`^name: ${name}$`, 'm'), `${name}: frontmatter name must match the dir`)
assert.match(text, /description: .*openspec/i, `${name}: description must mention openspec`)
}
})
```
- [ ] **Step 3: Запустить тест и убедиться, что он падает**
Run: `node --test dsh/tests/openspec.test.mjs`
Expected: FAIL на первом же assert, если каталог не создан; после Step 1 — PASS. Если PASS сразу после Step 1, закоммитить тест вместе со скиллами.
- [ ] **Step 4: Проверить, что `--skills` не копирует `.agents`**
Run: `HOME=$(mktemp -d) bash dsh/install.sh --skills` (HOME во временном каталоге; `--skills` берёт `$ROOT/dsh/skills`)
Expected: `copied 2 local skills into …`; `ls $HOME/.dsh/skills` не содержит `openspec-*`.
- [ ] **Step 5: Коммит**
```bash
git add .agents dsh/tests/openspec.test.mjs
git commit -m "feat(dsh): скиллы OpenSpec в .agents/skills проекта (#42)"
```
---
### Task 2: `--check` сообщает про OpenSpec
**Files:**
- Modify: `dsh/install.sh` (`check()`, после проверки subagent overlay)
- Modify: `dsh/tests/openspec.test.mjs`
**Interfaces:**
- Produces: строки вывода `check()` — `ok: openspec <version>` / `WARN: openspec not found; …` и `ok: openspec agents skills (<path>)` / `WARN: .agents/skills missing; run --openspec in the project`.
- Consumes: `.agents/skills/openspec-propose/SKILL.md` из Task 1; `$ROOT`.
- [ ] **Step 1: Добавить падающие тесты**
```js
test('install.sh --check reports the openspec CLI and the agents skills', () => {
const home = tempDir()
try {
const bin = join(home, 'bin')
write(join(bin, 'openspec'), '#!/bin/sh\n[ "$1" = "--version" ] && echo 1.13.1\nexit 0\n')
// python3/git/node нужны, чтобы остальные пункты --check не падали
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: `${bin}:${process.env.PATH}` }
const run = spawnSync('bash', [script, '--check'], { env, encoding: 'utf8' })
assert.equal(run.status, 0, run.stderr)
assert.match(run.stdout, /ok: openspec 1\.13\.1/)
assert.match(run.stdout, /ok: openspec agents skills \(/)
} finally {
cleanup(home)
}
})
test('install.sh --check warns but does not fail without the openspec CLI', () => {
const home = tempDir()
try {
const bin = join(home, 'bin')
write(join(bin, 'node'), '#!/bin/sh\nexit 0\n')
// PATH только из временного каталога: openspec заведомо нет
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: bin }
const run = spawnSync('bash', [script, '--check'], { env, encoding: 'utf8' })
assert.match(run.stdout + run.stderr, /WARN: openspec not found/)
} finally {
cleanup(home)
}
})
```
(импорт `spawnSync` добавляется в шапку файла; `write` создаёт файл `0755` — см. `helpers.mjs`; если `write` не выставляет права, добавить `chmodSync(join(bin, 'openspec'), 0o755)`.)
- [ ] **Step 2: Запустить и убедиться, что падает**
Run: `node --test dsh/tests/openspec.test.mjs`
Expected: FAIL — в выводе `--check` нет строк `openspec`.
- [ ] **Step 3: Реализовать проверки в `check()`**
```bash
if command -v openspec >/dev/null 2>&1; then
echo "ok: openspec $(openspec --version 2>/dev/null | head -1)"
else
echo "WARN: openspec not found; OpenSpec workflows are unavailable" >&2
fi
if [ -f "$ROOT/.agents/skills/openspec-propose/SKILL.md" ]; then
echo "ok: openspec agents skills ($ROOT/.agents/skills)"
else
echo "WARN: .agents/skills missing; run --openspec in the project" >&2
fi
```
- [ ] **Step 4: Прогнать тест и весь набор**
Run: `node --test dsh/tests/openspec.test.mjs && bash dsh/tests/run.sh`
Expected: PASS, набор зелёный.
- [ ] **Step 5: Коммит**
```bash
git add dsh/install.sh dsh/tests/openspec.test.mjs
git commit -m "feat(dsh): --check сообщает OpenSpec CLI и agents-скиллы (#42)"
```
---
### Task 3: `--openspec` ставит адаптер в проект
**Files:**
- Modify: `dsh/install.sh` (usage, разбор аргументов, новая функция `install_openspec()`)
- Modify: `dsh/tests/openspec.test.mjs`
**Interfaces:**
- Produces: `bash dsh/install.sh --openspec` — работает в `$PWD`; при `openspec/` в `$PWD` вызывает `openspec init --tools agents`, печатает `ok: openspec agents adapter in <dir>`; без `openspec/` — `FAIL: <dir> has no openspec/ root; run: openspec init --tools agents`, код 1, ничего не пишет; без CLI — `FAIL: openspec CLI not found`, код 1.
- Consumes: test-хелперы `tempDir`, `write`, `cleanup` из `dsh/tests/helpers.mjs`.
- [ ] **Step 1: Добавить падающие тесты**
```js
function fakeOpenspec(dir, log) {
const bin = join(dir, 'bin')
write(join(bin, 'openspec'), `#!/bin/sh\necho "$PWD $@" >> ${JSON.stringify(log)}\nexit 0\n`)
chmodSync(join(bin, 'openspec'), 0o755)
return bin
}
test('--openspec adds the agents adapter to an existing OpenSpec project', () => {
const home = tempDir()
try {
const project = join(home, 'project')
mkdirSync(join(project, 'openspec'), { recursive: true })
const log = join(home, 'calls.log')
const bin = fakeOpenspec(home, log)
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: `${bin}:${process.env.PATH}` }
const run = spawnSync('bash', [script, '--openspec'], { cwd: project, env, encoding: 'utf8' })
assert.equal(run.status, 0, run.stderr)
assert.match(run.stdout, /ok: openspec agents adapter in /)
assert.match(readFileSync(log, 'utf8'), new RegExp(`^${project} init --tools agents$`, 'm'))
} finally {
cleanup(home)
}
})
test('--openspec refuses a project without an openspec root', () => {
const home = tempDir()
try {
const project = join(home, 'plain')
mkdirSync(project, { recursive: true })
const log = join(home, 'calls.log')
const bin = fakeOpenspec(home, log)
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: `${bin}:${process.env.PATH}` }
const run = spawnSync('bash', [script, '--openspec'], { cwd: project, env, encoding: 'utf8' })
assert.notEqual(run.status, 0)
assert.match(run.stdout + run.stderr, /has no openspec\/ root/)
assert.equal(existsSync(log), false, 'openspec must not run in a non-OpenSpec project')
assert.equal(existsSync(join(project, 'openspec')), false)
} finally {
cleanup(home)
}
})
```
- [ ] **Step 2: Запустить и убедиться, что падает**
Run: `node --test dsh/tests/openspec.test.mjs`
Expected: FAIL — `unknown option: --openspec`, код 2.
- [ ] **Step 3: Реализовать флаг**
```bash
install_openspec() {
local dir="${1:-$PWD}"
if ! command -v openspec >/dev/null 2>&1; then
echo "FAIL: openspec CLI not found; install it first" >&2
return 1
fi
if [ ! -d "$dir/openspec" ]; then
echo "FAIL: $dir has no openspec/ root; run: openspec init --tools agents" >&2
return 1
fi
( cd "$dir" && openspec init --tools agents )
echo "ok: openspec agents adapter in $dir"
}
```
В разборе аргументов: `--openspec) DO_OPENSPEC=1 ;;`, в `usage()` строка
` --openspec add the agents skill adapter to the current OpenSpec project`,
в конце — `if [ "$DO_OPENSPEC" = 1 ]; then install_openspec "$PWD"; fi`.
- [ ] **Step 4: Прогнать тест и набор**
Run: `node --test dsh/tests/openspec.test.mjs && bash dsh/tests/run.sh`
Expected: PASS; повторный `--openspec` в том же каталоге даёт тот же `ok:`, фейковый лог получает вторую строку (идемпотентность проверяется живой приёмкой, не юнитом).
- [ ] **Step 5: Коммит**
```bash
git add dsh/install.sh dsh/tests/openspec.test.mjs
git commit -m "feat(dsh): --openspec добавляет agents-адаптер в проект (#42)"
```
---
### Task 4: Документация и проверка полноты
**Files:**
- Modify: `dsh/PORT.md` (§1 таблица — OpenSpec; §2 — новый пункт; §5 — подраздел «OpenSpec skills»; §6 — шаг установки; §8 — состояния фаз)
- Modify: `dsh/README.md` (What works, Quick start, Verify, Layout, Known limits)
**Interfaces:**
- Consumes: строки `--check`/`--openspec` из Task 2–3; `openspec/changes/dsh-openspec/design.md` (решения R-1…R-5).
- Produces: тексты, на которые ссылается PR и issue #42.
- [ ] **Step 1: PORT.md — факты и отказ от старых обещаний**
В §1 в строке `Gitea, OpenSpec` дописать статус: CLI + проектные скиллы `.agents/skills`.
В §2 добавить пункт 5:
```markdown
5. **OpenSpec** — воркфлоу идут проектными скиллами `.agents/skills/openspec-*`,
которые пишет `openspec init --tools agents`. DSH читает этот каталог нативно;
проверено сабагентом с cwd в проекте (все шесть имён в каталоге скиллов).
Глобальной копии в `~/.dsh/skills` нет: генератор обновляется вместе с CLI
проекта. Слэш-команд нет — только скиллы. Установка в проект:
`bash dsh/install.sh --openspec`; проверка: `--check`.
```
В §5 добавить `### OpenSpec skills` с командами `openspec new change`,
`status/instructions`, `validate --strict`, `archive -y` и оговоркой, что
gemerated-скиллы руками не правятся (`openspec update`).
В §6 добавить шаг «в проекте: `bash dsh/install.sh --openspec`; в сессии
спросить "openspec propose"».
В §8 снять OpenSpec из «next phases», если он там упомянут, и обновить статус
спайка.
- [ ] **Step 2: README.md**
- «What works» — пункт про OpenSpec (2 строки + ссылка на PORT §2).
- Quick start — `bash dsh/install.sh --openspec` с пометкой «из корня проекта
с `openspec/`».
- Verify — пункт: в проекте с `.agents/skills` спросить «what skills do you
have?» → должны быть `openspec-*`.
- Layout — строка `.agents/skills/openspec-* проектные скиллы (генерирует openspec CLI)`.
- Known limits — одна строка: OpenSpec-скиллы не ставятся глобально; в новом
проекте их нужно добавить `--openspec`.
- [ ] **Step 3: Проверить, что документация не врёт**
Run: `grep -n "openspec" dsh/README.md dsh/PORT.md | head -30` и пройти по каждой строке глазами: команда, флаг и путь существуют в `dsh/install.sh` этой ветки.
Expected: нет упоминаний `/opsx`, `~/.dsh/skills/openspec`, `openspec change new` и `openspec archive` без `-y`.
- [ ] **Step 4: Полный прогон**
Run: `bash dsh/tests/run.sh && bash tests/run.sh`
Expected: оба набора зелёные.
- [ ] **Step 5: Коммит**
```bash
git add dsh/PORT.md dsh/README.md
git commit -m "docs(dsh): OpenSpec — проектные скиллы, --openspec, проверка (#42)"
```
---
### Task 5: Живая приёмка DSH-цикла
**Files:**
- Modify: `openspec/changes/dsh-openspec/design.md` (§6 — результаты приёмки)
- Не коммитить: temp-проект живёт в `/tmp`.
**Interfaces:**
- Consumes: скиллы из Task 1, `--openspec` из Task 3, CLI openspec 1.13.1.
- Produces: раздел «Приёмка: прогон» в design.md и комментарий в issue #42.
- [ ] **Step 1: Собрать temp-проект с веткой, чтобы guard-main не блокировал**
Run:
```bash
TD=$(mktemp -d /tmp/openspec-accept-XXXX) && cd "$TD" && git init -q . \
&& git commit -q --allow-empty -m init && git checkout -q -b feat/probe \
&& openspec init --tools agents --no-animation | tail -3
```
Expected: `6 skills in .agents/`; `git status --short` — только `.agents/`, `openspec/`.
- [ ] **Step 2: Спросить DSH-сабагента, видит ли он скиллы**
Dispatch: `subagent` с `cwd` = temp-проект, промпт: «перечисли имена скиллов своего каталога; подтверди, есть ли `openspec-propose`». Ожидание: все шесть `openspec-*` в ответе.
- [ ] **Step 3: Прогнать полный цикл через ровно те команды, которые предписывает скилл**
Run (в temp-проекте):
```bash
openspec new change probe
openspec status --change probe --json >/dev/null
openspec instructions proposal --change probe --json >/dev/null
# написать proposal.md + specs/probe/spec.md с одной дельтой
openspec validate probe --strict
openspec archive probe -y
test -f openspec/specs/probe/spec.md && echo "spec archived"
```
Expected: `Change 'probe' is valid`, `archived as '2026-10-24-probe'`, `spec archived`.
- [ ] **Step 4: Записать результаты в design.md §6 и в issue**
Добавить в `design.md` §6 подраздел «Приёмка: прогон 24.10.2026» — дата, версия CLI, что увидел сабагент, вывод validate/archive, путь temp-проекта. Затем `tea comments 42 <файл с текстом>` (тело — из файла, а не аргументом).
- [ ] **Step 5: Коммит design.md**
```bash
git add openspec/changes/dsh-openspec/design.md
git commit -m "docs(openspec): результаты приёмки DSH-цикла (#42)"
```
---
## Self-review заметки
- Спека `dsh-openspec`: requirement «Скиллы живут в проекте» → Task 1; «Цикл без Claude Code» → Task 5; «Проверка установки» → Task 2; «Установка в проект» → Task 3; «Вход — скиллы» → Task 4 (тексты) + отсутствие изменения бандла в задачах 1–3.
- `--check` в Task 2 использует `$ROOT/.agents/skills`, а не `$PWD`: это решение R-1/design §4, тест это фиксирует.
- Идемпотентность `--openspec` юнитом не проверяется (фейковый openspec всегда возвращает 0) — проверяется живой приёмкой Task 5, Step 3 (повторный `openspec init` без ошибок), где Fake не участвует.

View File

@ -133,5 +133,6 @@ node, хуки, бандл).
- `openspec archive probe -y` → `Specs updated successfully.` / `Change 'probe'
archived as '2026-10-24-probe'.`, основная спека
`openspec/specs/probe/spec.md` создана.
- Видимость скиллов в DSH-сессии проверяет контроллер отдельным сабагентом;
результат дописывается сюда после прогона.
- Видимость скиллов в DSH: сабагент с cwd `/tmp/dsh-openspec-accept` увидел в
каталоге скиллов все шесть `openspec-*`; источник — проектный
`.agents/skills/` этого проекта.

View File

@ -0,0 +1,64 @@
# Proposal: OpenSpec в DSH
Issue: #42 (роадмап порта — #48). Источники:
- `dsh/PORT.md`, разделы 1 (таблица слоёв: «Gitea, OpenSpec — CLI + skills, 1:1»),
3 (deferred), 8 (next phases);
- спайк 24.10.2026 в temp-проекте: `openspec init --tools agents` + полный цикл
CLI без Claude Code;
- ответы владельца 24.10.2026: скиллы — в проект, слэш-команда не нужна.
## Why
В Claude Code архитектурная задача идёт через OpenSpec: `openspec/changes/<id>/`
— proposal, дельта спеки, design, tasks; `openspec validate` и `openspec archive`
держат спеки в правде. В DSH этот слой сейчас отсутствует: CLI работает, но
скиллов нет, и агент либо правит спеку руками, либо не заводит change вовсе.
`dsh/PORT.md` при этом обещает «CLI + skills 1:1» — обещание не выполнено.
Роль dev уже требует спеки в `openspec/changes/<id>/` для архитектурных задач.
Пока скиллов нет, это требование в DSH-сессии выполняется только «на память
модели»: формат proposal/дельты и порядок команд ничем не закреплены.
## What Changes
- **Скиллы OpenSpec в проекте.** `openspec init --tools agents` создаёт
`.agents/skills/openspec-{propose,apply-change,archive-change,explore,sync-specs,update-change}/SKILL.md`
— ровно тот формат, который DSH читает как проектные скиллы. В репозиторий
обвязки эти шесть скиллов добавляются (dogfooding: обвязка сама ведёт спеки
через OpenSpec). Скиллы остаются генератором openspec: `openspec update`
обновляет их под версию CLI проекта.
- **Глобальной копии нет.** В `~/.dsh/skills` скиллы OpenSpec не ставятся: копия
стареет относительно CLI проекта, а в проектах с собственным `.agents/skills`
появляются два скилла с одним именем.
- **`dsh/install.sh`:** `--check` сообщает версию CLI `openspec` и наличие
agents-скиллов в репозитории обвязки; новая опция `--openspec` добавляет
agents-адаптер в текущий проект (`openspec init --tools agents`), только если
`openspec/` уже существует, — идемпотентно, `.claude/` не трогает.
- **`dsh/PORT.md` и `dsh/README.md`:** раздел про OpenSpec, путь `.agents/skills`,
цикл проверки, снятие пункта из «Next phases».
- **`dsh/tests/openspec.test.mjs`:** проверки без DSH runtime — наличие и
frontmatter скиллов в репозитории, поведение `--check` и `--openspec` с
подставным бинарём `openspec` в `PATH`.
- **Слэш-команд нет.** В Claude Code есть `/opsx:*`; в DSH вход — скиллы,
которые модель вызывает по фразе «openspec propose» / «openspec archive».
`/role` и bootstrap не меняются.
## Capabilities
### New Capabilities
- `dsh-openspec`: как DSH-адаптер обвязки ставит и проверяет скиллы OpenSpec,
чем отличается от Claude Code и что обязано работать без Claude Code.
### Modified Capabilities
## Impact
- Claude Code не меняется: `.claude/skills/openspec-*` и `.claude/commands/opsx/*`
остаются как есть; `roles/`, `hooks/`, `global/`, корневой `install.sh` не трогаются.
- В репозитории появляется `.agents/skills/` (6 каталогов + `.openspec-target`).
Дублирование с `.claude/skills/` — цена поддержки двух клиентов одним
генератором; расхождение версий ловится `openspec update`.
- `dsh/install.sh --openspec` получает право вызывать внешний CLI; в тестах он
подменяется, в живом прогоне требуется установленный `openspec` ≥ 1.13.
- Роль dev не меняется: строка про `openspec/changes/<id>/` уже есть; в DSH она
начинает работать, потому что скиллы появились в проекте.

View File

@ -0,0 +1,110 @@
# Spec Delta: dsh-openspec
## Purpose
Слой OpenSpec в DeepSeek Harness: скиллы воркфлоу (propose → apply → archive →
sync) и их установка в проект. В Claude Code эти скиллы генерирует
`openspec init --tools claude` в `.claude/skills`; в DSH тот же генератор с
адаптером `agents` кладёт их в `.agents/skills`, который DSH читает как
проектный корень скиллов. Обвязка не переписывает воркфлоу: она ставит
адаптер, проверяет его и описывает цикл.
## ADDED Requirements
### Requirement: Скиллы OpenSpec живут в проекте
Воркфлоу OpenSpec SHALL поставляться проектными скиллами в `.agents/skills`,
созданными генератором `openspec init --tools agents`, и MUST NOT копироваться
в глобальный `~/.dsh/skills`.
- Набор: `openspec-propose`, `openspec-apply-change`, `openspec-archive-change`,
`openspec-explore`, `openspec-sync-specs`, `openspec-update-change`.
- Каждый `SKILL.md` MUST иметь frontmatter с `name`, совпадающим с именем
каталога, и `description`, по которому модель отличает propose от archive.
- Источник истины — CLI `openspec` проекта: `openspec update` обновляет скиллы,
ручные правки в них не переживают обновление.
#### Scenario: Проект с agents-адаптером
- **WHEN** в проекте выполнено `openspec init --tools agents`
- **THEN** в `.agents/skills/` лежат шесть каталогов `openspec-*` с `SKILL.md`,
а `openspec list --json` возвращает ненулевой `root`
#### Scenario: DSH видит проектные скиллы
- **WHEN** DSH-сессия стартует с cwd в проекте, где есть `.agents/skills/openspec-*`
- **THEN** каталог скиллов сессии содержит эти шесть имён, и их можно вызвать
инструментом `skill`
#### Scenario: Глобальная копия не создаётся
- **WHEN** выполнено `bash dsh/install.sh --skills`
- **THEN** в `~/.dsh/skills` нет каталогов `openspec-*`: они остаются проектными
#### Scenario: Два адаптера в одном проекте
- **WHEN** проект инициализирован с `--tools claude`, затем с `--tools agents`
- **THEN** `.claude/skills` и `.claude/commands` остаются без изменений, а
`.agents/skills` добавляется рядом
### Requirement: Цикл change без Claude Code
В DSH-сессии MUST проходиться полный цикл OpenSpec без Claude Code: создание
change, артефакты по инструкциям CLI, строгая валидация и архив.
- Создание: `openspec new change <имя>`.
- Артефакты: `openspec status --change <имя> --json` и
`openspec instructions <artifact> --change <имя> --json`; агент пишет
`proposal.md`, `specs/<capability>/spec.md`, `design.md`, `tasks.md`.
- Проверка: `openspec validate <имя> --strict`.
- Архив: `openspec archive <имя> -y` (без интерактивных подтверждений), спеки
обновляются в `openspec/specs/`.
#### Scenario: Архив неинтерактивен
- **WHEN** change валиден и все задачи `tasks.md` отмечены выполненными
- **THEN** `openspec archive <имя> -y` завершается кодом 0, создаёт
`openspec/changes/archive/<дата>-<имя>/` и обновляет основную спеку
#### Scenario: Невалидный change останавливает архив
- **WHEN** в change нет ни одной дельты и в `.openspec.yaml` не стоит
`skip_specs: true`
- **THEN** `openspec validate --strict` завершается ненулевым кодом, и архив не
выполняется
### Requirement: Проверка установки
`dsh/install.sh --check` SHALL сообщать состояние слоя OpenSpec: наличие CLI,
версию и наличие agents-скиллов в репозитории обвязки; отсутствие CLI MUST быть
предупреждением, а не отказом, потому что остальная обвязка от OpenSpec не
зависит.
#### Scenario: CLI установлен, скиллы на месте
- **WHEN** `openspec` есть в `PATH` и `.agents/skills/openspec-propose/SKILL.md`
существует
- **THEN** `--check` печатает `ok: openspec <версия>` и `ok: openspec agents skills`
#### Scenario: CLI отсутствует
- **WHEN** `openspec` нет в `PATH`
- **THEN** `--check` печатает `WARN` с подсказкой установить CLI и продолжает
проверку остальных пунктов; код возврата не меняется из-за одного OpenSpec
### Requirement: Установка в проект
`bash dsh/install.sh --openspec` SHALL добавлять agents-адаптер в текущий проект
вызовом `openspec init --tools agents`, MUST быть идемпотентной и MUST NOT
создавать OpenSpec-корень там, где его нет.
#### Scenario: Проект уже использует OpenSpec
- **WHEN** cwd — проект с каталогом `openspec/`, вызван `--openspec`
- **THEN** скиллы появляются в `.agents/skills`, повторный вызов не дублирует и
не ломает существующие файлы
#### Scenario: Проект без OpenSpec
- **WHEN** cwd — проект без `openspec/`, вызван `--openspec`
- **THEN** скрипт отказывается с сообщением `openspec init --tools agents`,
ничего не создаёт и завершается ненулевым кодом
### Requirement: Вход в DSH — скиллы, не команды
Слой OpenSpec MUST NOT добавлять слэш-команды в бандл: единственная команда
обвязки остаётся `/role`. Модель вызывает воркфлоу скиллом по `description`
(«openspec propose», «openspec archive»).
#### Scenario: Команды бандла не изменились
- **WHEN** установлен бандл этой версии
- **THEN** список команд содержит `/role` и не содержит `/openspec` и `/opsx`
#### Scenario: Роль и bootstrap работают как раньше
- **WHEN** в проекте с `.agents/skills` загружается роль `dev`
- **THEN** `skill role` и bootstrap-маркер ведут себя так же, как без OpenSpec