feat(dsh): skill sdd — асинхронный протокол исполнения плана (#41)
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 <noreply@deepseek.com>
This commit is contained in:
parent
2bee63d305
commit
f714426b11
@ -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/`.
|
||||
|
||||
142
dsh/skills/sdd/SKILL.md
Normal file
142
dsh/skills/sdd/SKILL.md
Normal file
@ -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'а — **файлом** `<workspace>/briefs/task-<n>.md`: контекст,
|
||||
требования задачи, точные имена и сигнатуры, тест, команда проверки,
|
||||
формат коммита. Промпт короткий: «прочитай бриф, выполни, напиши отчёт,
|
||||
первая строка отчёта — статус».
|
||||
3. Отчёт — **файлом** `reports/task-<n>.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<n>.<k>` со статусом.
|
||||
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/<plan>/progress.md` в рабочем каталоге плана; первая строка —
|
||||
путь плана. Обязательные секции:
|
||||
|
||||
- таблица сканирования плана: конфликт, решение, рулинг `R-n (design #n)`;
|
||||
- `Task <n>: pending | complete` с коммитом;
|
||||
- `## Findings` — каждая находка: маркер, одна строка, статус
|
||||
`fixed <commit>`, `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` — расширение для веера независимых задач, в этой версии не
|
||||
используется: решения по конфликтам и адъюдикация требуют суждения в моменте.
|
||||
@ -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`)
|
||||
}
|
||||
|
||||
64
dsh/tests/sdd-skill.test.mjs
Normal file
64
dsh/tests/sdd-skill.test.mjs
Normal file
@ -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 <commit>/)
|
||||
assert.match(body, /T<n>\.<k>/)
|
||||
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)
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user