From f714426b11612e0df59011d62f3a2e3e591694d2 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 17:40:24 +0400 Subject: [PATCH] =?UTF-8?q?feat(dsh):=20skill=20sdd=20=E2=80=94=20=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD=D0=B0=20(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dsh/skills/sdd/SKILL.md: два режима контроллера (главная сессия / фоновый субагент), проверка глубины и маршрутов моделей, диспатч и отчёт файлами, контракт DONE/DONE_WITH_CONCERNS/NEEDS_CONTEXT/BLOCKED, ожидание через завершение хода и уведомление, ревью задачи и fix-loop 5 раундов, ledger с ## Findings, таблица отличий от Claude-SDD. Тесты: frontmatter, режимы, инструменты, маршруты, ledger, отсутствие job_output как ожидания; install.test.mjs ждёт два локальных skill. Co-Authored-By: DeepSeek Harness --- dsh/README.md | 22 ++++-- dsh/skills/sdd/SKILL.md | 142 +++++++++++++++++++++++++++++++++++ dsh/tests/install.test.mjs | 6 +- dsh/tests/sdd-skill.test.mjs | 64 ++++++++++++++++ 4 files changed, 226 insertions(+), 8 deletions(-) create mode 100644 dsh/skills/sdd/SKILL.md create mode 100644 dsh/tests/sdd-skill.test.mjs diff --git a/dsh/README.md b/dsh/README.md index 6373bff..a24d3dc 100644 --- a/dsh/README.md +++ b/dsh/README.md @@ -14,11 +14,15 @@ separate Cordis bundle plus a thin install helper. - roles — the model loads them with the `role` skill (`~/.dsh/roles`, then `~/.claude/roles`, then repo `roles/`); `/role dev|admin|analyst|qa` remains the user-facing command and injects the same text, without Claude's - auto-memory step. + auto-memory step; +- SDD — the `sdd` skill executes a plan through asynchronous `subagent` + dispatches (report files, task review, fix loop, ledger), with controller + modes for one plan and for several parallel plans; the optional overlay + enables model routes and depth 2. -**Not yet:** SDD (DSH subagents are asynchronous), `briefing`, -`harness-check`, `role-hint`, `harness-doctor`, auto-memory, caveman. See -[PORT.md](PORT.md) for the design, mapping table, and risks. +**Not yet:** `briefing`, `harness-check`, `role-hint`, `harness-doctor`, +auto-memory, caveman. See [PORT.md](PORT.md) for the design, mapping table, and +risks. ## Quick start @@ -91,6 +95,8 @@ dsh/ icon.svg tests/ install.sh + skills/{role,sdd}/SKILL.md model-invocable skills installed into ~/.dsh + subagent-models.yml opt-in overlay: model routes + maxDepth 2 mcp.codegraph.yml optional CodeGraph MCP overlay PORT.md design, mapping, risks, next phases ``` @@ -131,6 +137,8 @@ repository layout: `agent/pre-step` injection. If the model skips the skill, use the `/role` command or ask it to load the role explicitly. - Model names in roles and superpowers (`sonnet`, `haiku`, `opus`) are not DSH - models. Map them to live provider/model routes before porting SDD. -- SDD is intentionally out of scope for this spike; see - [PORT.md](PORT.md#3-what-is-deliberately-deferred). + models. The `sdd` skill uses two live routes from the opt-in + `dsh/subagent-models.yml` overlay; without it every dispatch runs on the + session model. +- SDD on DSH is asynchronous, not a copy of Claude's `Task`. See the `sdd` + skill and `openspec/changes/dsh-sdd/`. diff --git a/dsh/skills/sdd/SKILL.md b/dsh/skills/sdd/SKILL.md new file mode 100644 index 0000000..fc9a99f --- /dev/null +++ b/dsh/skills/sdd/SKILL.md @@ -0,0 +1,142 @@ +--- +name: sdd +description: Use when executing an implementation plan in DeepSeek Harness (DSH) with subagents — async dispatch, controller modes, ledger, task review, fix loop, final branch review. Not for Claude Code, where subagent-driven-development applies. +--- + +# SDD в DeepSeek Harness + +Исполнение плана субагентами в DSH. Заменяет `subagent-driven-development` +только в DSH-сессиях: у Claude Code синхронный `Task`, у DSH — асинхронный +`subagent`. Это **не паритет** с Claude-SDD и не имитация: протокол построен +на том, что DSH умеет (уведомление о завершении, `send_message`, ledger), а не +на том, чего у него нет (возврат результата из вызова инструмента). + +## Когда применять + +- Есть план в `docs/superpowers/plans/`, задачи независимы, сессия — DSH. +- Не применять: Claude Code (там исходный `subagent-driven-development`); + плана нет (сначала `brainstorming` → `writing-plans`); правка одна + (дешевле `executing-plans`). + +## Режимы контроллера + +| Условие | Контроллер | Глубина | Когда | +|---|---|---|---| +| Один план в сессии | главная сессия | 1 | по умолчанию; рулинги по конфликтам плана делает тот, у кого спека | +| Несколько задач, каждая по SDD, параллельно | фоновый субагент на план | 2 | когда главная сессия нужна другой задаче | + +**Правило выбора:** режим зависит от числа параллельных планов, а не от +привычки. Один план — Режим 1; несколько — Режим 2 на каждый план. + +**Проверка до первого диспатча.** В Режиме 2 поднята ли глубина: +`bash dsh/install.sh --check` должен сказать `ok: subagent maxDepth=2 (...)`, +или первый диспатч контроллера падает с `exceeds maxDepth`. Если глубины нет — +не диспатчить контроллера, зафиксировать рулинг `R-n` об **откате** в Режим 1 и +продолжить в нём. Проверка выбора модели: в схеме `subagent` есть +`provider`/`model` (overlay `dsh/subagent-models.yml`); нет — все диспатчи идут +на модели сессии, и это наблюдение `O-n`, а не выполненное требование. Без +overlay ребёнок наследует модель сессии. + +## Протокол диспатча и отчёта + +1. Первым делом — ledger (см. ниже) и скан плана на конфликты; рулинги до + Task 1. +2. Бриф implementer'а — **файлом** `/briefs/task-.md`: контекст, + требования задачи, точные имена и сигнатуры, тест, команда проверки, + формат коммита. Промпт короткий: «прочитай бриф, выполни, напиши отчёт, + первая строка отчёта — статус». +3. Отчёт — **файлом** `reports/task-.md`. Контракт первой строки: + `DONE`, `DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, `BLOCKED`. Дальше: коммиты, + одна строка сводки тестов, concerns, что осталось. +4. В контекст контроллера возвращается только: статус, коммиты, одна строка + тестов и concerns. Полный отчёт читается с диска при ревью, а не тащится + в контекст. +5. `NEEDS_CONTEXT` — ответить в тот же `send_message`; `BLOCKED` — рулинг + контроллера, а не остановка сессии. + +## Ожидание без блокировки + +`subagent` возвращает id и сразу завершается; результата в значении +инструмента нет. Поэтому: + +- вызвал `subagent` — **закончи ход**. Следующий ход начнётся с уведомления о + завершении ребёнка; +- пока ребёнок работает и есть локальная работа (ревью-пакет, ledger, + следующий бриф) — делай её, а не жди; +- перед каждым следующим диспатчем — `list_agents`: неактивный ребёнок без + отчёта означает, что уведомление потерялось; истина — файл отчёта и git, а + не перезапуск задачи; +- ребёнок висит дольше разумного — `interrupt_agent`, затем решение по + состоянию git; +- `job_output` — не ожидание сабагента, а стрим фонового job'а: здесь + неприменим. + +## Ревью задачи и fix-loop + +Каждая задача проходит ревью **свежим** субагентом: соответствие спеке и +качество кода. Self-review implementer'а ревью не заменяет. + +1. Пакет ревью — файлом: дифф задачи, отчёт implementer'а, выдержка из спеки. +2. Ревьюер возвращает вердикт и находки `T.` со статусом. +3. Fix-loop — не более 5 раундов: раунды 1–3 — `send_message` тому же + implementer'у, раунды 4–5 — свежий implementer на более дорогом маршруте. +4. Ре-ревью — только исправление (скоуп), не вся задача. +5. После 5-го раунда — адъюдикация каждой открытой находки: рулинг + `R-n (design #n)` и статус `fixed`/`deferred`/`rejected <причина>`. + +Находка без статуса в отчёте контроллера — ошибка контроллера. + +## Финальное ревью ветки + +После всех задач — один ревьюер на весь дифф `main...HEAD`: находки `C-n` +(critical), `I-n` (important), `M-n` (minor). При находках — **один** +fix-диспатч и одно скоуп-ревью; остатки адъюдицируются и уходят в +`deferred`/follow-up. + +## Ledger + +`.superpowers/sdd//progress.md` в рабочем каталоге плана; первая строка — +путь плана. Обязательные секции: + +- таблица сканирования плана: конфликт, решение, рулинг `R-n (design #n)`; +- `Task : pending | complete` с коммитом; +- `## Findings` — каждая находка: маркер, одна строка, статус + `fixed `, `deferred` или `rejected <причина>`; +- `## Rulings` — рулинги с ценой ошибки. + +Ledger — единственная память между ходами: чат и контекст теряются, а +ledger лежит в git-ignored scratch (после `git clean -fdx` восстанавливается +по `git log`). Поэтому записи не сжимать. + +## Маршруты моделей + +| Роль в SDD | Маршрут | Почему | +|---|---|---| +| implementer по готовому коду из плана | `deepseek-official/deepseek-flash` | транскрипция и тесты | +| implementer по прозе, интеграция | `deepseek-official/deepseek-v4-pro` | решение в нескольких файлах | +| ревью задачи, ре-ревью | `deepseek-official/deepseek-v4-pro` | вердикт важнее цены | +| финальное ревью ветки | `deepseek-official/deepseek-v4-pro` | самый широкий дифф | + +Маршрут указывается в каждом диспатче: `provider`, `model`, +`reasoning_effort`. Параметры действуют только при включённом overlay +(`dsh/subagent-models.yml`); без него всё идёт на модели сессии, и отчёт +фиксирует это наблюдением `O-n`. + +## Отличие от Claude-SDD + +| Свойство | Claude Code `subagent-driven-development` | DSH `sdd` | +|---|---|---| +| Вызов исполнителя | синхронный `Task`, результат в том же ходе | асинхронный `subagent`, id + уведомление позже | +| Ожидание | возврат значения инструмента | закончи ход, продолжи по уведомлению | +| Продолжение ребёнка | новый `Task` | `send_message` в того же ребёнка | +| Глубина по умолчанию | не ограничена | `maxDepth: 2` только с overlay | +| Выбор модели | тиры sonnet/haiku/opus | два маршрута, только с overlay | +| Изоляция контекста | наследование не по умолчанию | `subagent_fork` наследует ходы — для implementer'а не годится | + +## Запреты + +- Не ждать сабагента через `job_output`, поллинг или повторные `list_agents`. +- Не делать merge и force-push; финиш — `finishing-a-development-branch` и PR. +- Не выдавать прогон без overlay за работу на разных моделях. +- `workflow` — расширение для веера независимых задач, в этой версии не + используется: решения по конфликтам и адъюдикация требуют суждения в моменте. diff --git a/dsh/tests/install.test.mjs b/dsh/tests/install.test.mjs index 1a1e819..3cb08d9 100644 --- a/dsh/tests/install.test.mjs +++ b/dsh/tests/install.test.mjs @@ -32,7 +32,7 @@ test('install.sh copies skills and roles instead of linking into ~/.claude', () const first = run() assert.equal(first.status, 0, first.stderr) assert.match(first.stdout, /copied 2 superpowers skills/) - assert.match(first.stdout, /copied 1 local skills/) + assert.match(first.stdout, /copied 2 local skills/) assert.match(first.stdout, /installed 4 roles/) const skill = join(dshHome, 'skills', 'using-superpowers') @@ -44,6 +44,10 @@ test('install.sh copies skills and roles instead of linking into ~/.claude', () assert.ok(existsSync(roleSkill), 'role skill was not installed') assert.match(readFileSync(roleSkill, 'utf8'), /name: role/) + const sddSkill = join(dshHome, 'skills', 'sdd', 'SKILL.md') + assert.ok(existsSync(sddSkill), 'sdd skill was not installed') + assert.match(readFileSync(sddSkill, 'utf8'), /name: sdd/) + for (const role of ['dev', 'admin', 'analyst', 'qa']) { assert.ok(existsSync(join(dshHome, 'roles', `${role}.md`)), `missing ${role}.md`) } diff --git a/dsh/tests/sdd-skill.test.mjs b/dsh/tests/sdd-skill.test.mjs new file mode 100644 index 0000000..3a5c318 --- /dev/null +++ b/dsh/tests/sdd-skill.test.mjs @@ -0,0 +1,64 @@ +import assert from 'node:assert/strict' +import { 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 skillPath = join(here, '..', 'skills', 'sdd', 'SKILL.md') +const text = () => readFileSync(skillPath, 'utf8') + +test('sdd skill is model-invocable', () => { + const body = text() + assert.match(body, /^---\nname: sdd\n/) + assert.match(body, /\ndescription: .+/) + assert.ok(Buffer.byteLength(body) < 12288, 'skill is over the 12 KiB budget') +}) + +test('sdd skill describes both controller modes and the depth check', () => { + const body = text() + assert.match(body, /Режим 1/) + assert.match(body, /Режим 2/) + assert.match(body, /maxDepth: 2/) + assert.match(body, /глубин/) + assert.match(body, /откат/i) +}) + +test('sdd skill names the asynchronous tools and never job_output', () => { + const body = text() + for (const tool of ['subagent', 'send_message', 'list_agents', 'interrupt_agent']) { + assert.ok(body.includes(tool), `missing ${tool}`) + } + for (const line of body.split('\n').filter((l) => l.includes('job_output'))) { + assert.match(line, /не /i, `job_output proposed as a wait mechanism: ${line}`) + } + assert.match(body, /закончи ход/) + assert.match(body, /уведомлен/) +}) + +test('sdd skill fixes the report contract and the model routes', () => { + const body = text() + for (const status of ['DONE_WITH_CONCERNS', 'NEEDS_CONTEXT', 'BLOCKED']) { + assert.ok(body.includes(status), `missing status ${status}`) + } + assert.match(body, /deepseek-official\/deepseek-flash/) + assert.match(body, /deepseek-official\/deepseek-v4-pro/) + assert.match(body, /модель сессии/) +}) + +test('sdd skill requires the ledger with findings and statuses', () => { + const body = text() + assert.match(body, /## Findings/) + assert.match(body, /fixed /) + assert.match(body, /T\./) + assert.match(body, /deferred/) + assert.match(body, /rejected <причина>/) +}) + +test('sdd skill states the difference from Claude SDD without claiming parity', () => { + const body = text() + assert.match(body, /синхронн/i) + assert.match(body, /асинхронн/i) + assert.match(body, /subagent-driven-development/) + assert.match(body, /не паритет/i) +})