From 6876e4c6c810eae59f5d529fdcb47a9ad2669141 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 19:44:38 +0400 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20change=20dsh-openspec,=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=BD=20=D0=B8=20=D0=B2=D0=B8=D0=B4=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D1=81=D1=82=D1=8C=20=D1=81=D0=BA=D0=B8=D0=BB=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B2=20=D0=BF=D1=80=D0=B8=D1=91=D0=BC=D0=BA?= =?UTF-8?q?=D0=B5=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: DeepSeek Harness --- .../plans/2026-10-24-dsh-openspec.md | 398 ++++++++++++++++++ openspec/changes/dsh-openspec/design.md | 5 +- openspec/changes/dsh-openspec/proposal.md | 64 +++ .../dsh-openspec/specs/dsh-openspec/spec.md | 110 +++++ 4 files changed, 575 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/plans/2026-10-24-dsh-openspec.md create mode 100644 openspec/changes/dsh-openspec/proposal.md create mode 100644 openspec/changes/dsh-openspec/specs/dsh-openspec/spec.md diff --git a/docs/superpowers/plans/2026-10-24-dsh-openspec.md b/docs/superpowers/plans/2026-10-24-dsh-openspec.md new file mode 100644 index 0000000..b755362 --- /dev/null +++ b/docs/superpowers/plans/2026-10-24-dsh-openspec.md @@ -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 `; force-push запрещён. +- Маркировка: находки ревью `T.` / `C-n` / `I-n` / `M-n`, рулинги `R-n (design #n)`, наблюдения `O-n`, замечания человека `H-n`; у каждой статус `fixed ` / `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 ` / `WARN: openspec not found; …` и `ok: openspec agents skills ()` / `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 `; без `openspec/` — `FAIL: 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 не участвует. diff --git a/openspec/changes/dsh-openspec/design.md b/openspec/changes/dsh-openspec/design.md index 1a11d1c..d88f353 100644 --- a/openspec/changes/dsh-openspec/design.md +++ b/openspec/changes/dsh-openspec/design.md @@ -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/` этого проекта. diff --git a/openspec/changes/dsh-openspec/proposal.md b/openspec/changes/dsh-openspec/proposal.md new file mode 100644 index 0000000..1f3aaca --- /dev/null +++ b/openspec/changes/dsh-openspec/proposal.md @@ -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//` +— proposal, дельта спеки, design, tasks; `openspec validate` и `openspec archive` +держат спеки в правде. В DSH этот слой сейчас отсутствует: CLI работает, но +скиллов нет, и агент либо правит спеку руками, либо не заводит change вовсе. +`dsh/PORT.md` при этом обещает «CLI + skills 1:1» — обещание не выполнено. + +Роль dev уже требует спеки в `openspec/changes//` для архитектурных задач. +Пока скиллов нет, это требование в 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//` уже есть; в DSH она + начинает работать, потому что скиллы появились в проекте. diff --git a/openspec/changes/dsh-openspec/specs/dsh-openspec/spec.md b/openspec/changes/dsh-openspec/specs/dsh-openspec/spec.md new file mode 100644 index 0000000..f45137f --- /dev/null +++ b/openspec/changes/dsh-openspec/specs/dsh-openspec/spec.md @@ -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 --change <имя> --json`; агент пишет + `proposal.md`, `specs//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