Merge pull request 'DSH: SDD — skill, overlay, пробный прогон (#41)' (#50) from feat/41-dsh-sdd-spike into main

This commit is contained in:
aep 2026-10-24 16:55:41 +03:00
commit 303b7f08aa
32 changed files with 1643 additions and 21 deletions

View File

@ -0,0 +1,241 @@
# DSH-native SDD — 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-сессии инструкцию исполнения плана субагентами (асинхронный `subagent`, ledger, ревью, fix-loop) и opt-in overlay профиля с глубиной 2 и двумя маршрутами моделей; доказать семантику пробным прогоном из 3 задач.
**Architecture:** Три слоя. (1) `dsh/subagent-models.yml` — overlay, который владелец копирует в `cordis.patch.yml` профиля: включает `subagent-model-selection-settings` (маршруты `deepseek-flash` / `deepseek-v4-pro`) и `subagent.maxDepth: 2`; в бандл не входит, иначе падает headless. (2) `dsh/skills/sdd/SKILL.md` — DSH-семантика исполнения плана: два режима контроллера, протокол ожидания через завершение хода и уведомление, ревью задачи, fix-loop, ledger `## Findings`. (3) Пробный прогон в temp-репозитории: контроллер — главная сессия, implementer (flash) → reviewer (v4-pro) → fix через `send_message` → финальное ревью (v4-pro); отдельной пробой проверяется режим 2 (фоновый контроллер, глубина 2).
**Tech Stack:** bash, python3 stdlib, Node test runner (`node --test`), markdown, DSH 0.2.1-rc.3 (Cordis bundle, `subagent`, `send_message`, `list_agents`), Gitea CLI `tea`.
**Spec:** `openspec/changes/dsh-sdd/` — `proposal.md`, `design.md` (источник решений), `specs/dsh-sdd/spec.md` (требования и сценарии).
**Ветка:** `feat/41-dsh-sdd-spike`, issue #41, worktree `.claude/worktrees/sdd-spike`.
## Global Constraints
- DSH 0.2.1-rc.3; версия плагинного API не заморожена, после апгрейда тесты прогоняются заново.
- Бандл импортирует только Node builtins и локальные файлы (`dsh/tests/package.test.mjs` это проверяет): никаких `@deepseek-ai/*` импортов.
- `subagent-model-selection-settings` нельзя ставить в `dsh/bundle/cordis.patch.yml`: строки нет в headless-профиле, включённый флаг роняет загрузку. Только overlay профиля.
- Skill `sdd` ставится только в `~/.dsh/skills` через `bash dsh/install.sh --skills`. Claude Code его не получает: `roles/`, `hooks/`, `global/`, `install.sh` (корневой), `agents/` не меняются.
- Тесты идут без DSH runtime: `bash dsh/tests/run.sh` и `bash tests/run.sh` (630/630 на момент дизайна) — зелёными должны быть оба набора.
- Документация и тексты — по-русски, формат планов/ledger — как в этом репозитории.
- Коммиты — с 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
- Overlay копируют в headless-профиль → загрузка падает на «requires … in the Host scope»; overlay и PORT.md обязаны это сказать явно, а не только в design.md.
- Skill прочитан в сессии без model selection → диспатч с `provider`/`model` молча игнорируется или падает; skill обязан деградировать на модель сессии и записать это наблюдением, а не выдавать за выполненное требование.
- Ожидание описано как `job_output`/поллинг или как возврат значения `subagent` → инструкция врёт о платформе; ожидание — только конец хода + уведомление + `list_agents`.
- Фоновый контроллер при `maxDepth: 1` → `subagent depth 2 exceeds maxDepth 1`; skill обязан проверить глубину до диспатча и откатиться в режим 1 с рулингом.
- Ledger без `## Findings`/статусов или находка без маркера → отчёт контроллера считается ошибочным; тест skill обязан требовать разделы, а пробный прогон — показать заполненный ledger.
- `install.sh --skills` с новым skill ломает счётчик «copied 1 local skills» в `dsh/tests/install.test.mjs` → счётчик и проверка наличия `sdd` обновляются в той же задаче.
---
### Task 1: Overlay профиля и статическая проверка глубины
**Files:**
- Create: `dsh/subagent-models.yml`
- Create: `dsh/tests/subagent-models.test.mjs`
- Modify: `dsh/install.sh` (`check()`, после проверки hook bridge)
- Modify: `dsh/PORT.md` (§5 Configuration — подраздел про overlay и глубину; §7 — риск «Model tiers»; §8 — п. 4)
**Interfaces:**
- Produces: файл `dsh/subagent-models.yml` — YAML-массив из двух записей: `- id: subagent-model-selection-settings` с `config.enabled: true` и `config.allowedModels` из двух пар (`deepseek-official/deepseek-flash`, `deepseek-official/deepseek-v4-pro`), затем `- id: subagent` с `config.maxDepth: 2`.
- Produces: строки вывода `check()`: `ok: subagent overlay (<путь>)` или `WARN: subagent overlay missing …`; `ok: subagent maxDepth=2 (<файл>)` или `WARN: subagent maxDepth is not 2 in any profile patch; SDD mode 2 is unavailable`.
- Consumes: ничего из других задач.
- [x] **Step 1: Написать падающий тест** `dsh/tests/subagent-models.test.mjs`
```js
import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
import { cleanup, tempDir, write } from './helpers.mjs'
const here = dirname(fileURLToPath(import.meta.url))
const overlay = join(here, '..', 'subagent-models.yml')
const script = join(here, '..', 'install.sh')
test('overlay enables model selection for both routes and depth 2', () => {
assert.ok(existsSync(overlay), 'dsh/subagent-models.yml is missing')
const text = readFileSync(overlay, 'utf8')
assert.match(text, /id: subagent-model-selection-settings/)
assert.match(text, /model: deepseek-flash/)
assert.match(text, /model: deepseek-v4-pro/)
assert.match(text, /id: subagent\n\s+config:\n\s+maxDepth: 2/)
assert.match(text, /headless/)
})
test('install.sh --check reports the overlay and the profile depth', () => {
const home = tempDir()
try {
const dshHome = join(home, '.dsh')
const env = { ...process.env, HOME: home, DSH_HOME: dshHome }
const missing = spawnSync('bash', [script, '--check'], { env, encoding: 'utf8' })
assert.match(missing.stderr, /subagent maxDepth is not 2/)
write(join(dshHome, 'profiles', 'desktop', 'cordis.patch.yml'), '- id: subagent\n config:\n maxDepth: 2\n')
const present = spawnSync('bash', [script, '--check'], { env, encoding: 'utf8' })
assert.match(present.stdout, /ok: subagent overlay/)
assert.match(present.stdout, /ok: subagent maxDepth=2/)
} finally {
cleanup(home)
}
})
```
- [x] **Step 2: Прогнать тест и убедиться, что он падает**
Run: `node --test dsh/tests/subagent-models.test.mjs`
Expected: FAIL — `dsh/subagent-models.yml is missing`.
- [x] **Step 3: Создать `dsh/subagent-models.yml`**
YAML-массив с комментарием-шапкой: overlay для DSH-native SDD (#41); копировать в `$DSH_HOME/profiles/desktop/cordis.patch.yml`, не в бандл, потому что строки `subagent-model-selection-settings` нет в headless; после копирования — рестарт DSH; проверка — `bash dsh/install.sh --check`. Две записи из блока Interfaces, значения — дословно.
- [x] **Step 4: Добавить проверки в `dsh/install.sh`**
В `check()` после блока hook bridge: проверить наличие `$ROOT/dsh/subagent-models.yml`; отдельно — `grep -qs 'maxDepth: 2' "$DSH_HOME"/profiles/*/cordis.patch.yml` (glob не раскрылся — предупреждение, не ошибка; `failed` не поднимать: overlay опционален).
- [x] **Step 5: Обновить `dsh/PORT.md`**
§5: подраздел «Model routes and delegation depth» — как включить (скопировать `dsh/subagent-models.yml` в профиль, рестарт), что даёт (provider/model у `subagent` и `subagent_fork`, глубина 2), чего не делает (не в бандле; без overlay всё идёт на модели сессии).
§7: в пункте «Model tiers» — два маршрута и ссылка на overlay.
§8: п. 4 (SDD spike) получает статус «в работе, change `dsh-sdd`».
- [x] **Step 6: Прогнать тесты**
Run: `node --test dsh/tests/subagent-models.test.mjs`
Expected: PASS (2 tests).
- [x] **Step 7: Прогнать оба набора**
Run: `bash dsh/tests/run.sh && bash tests/run.sh`
Expected: все зелёные (если в worktree падает guard на 5 тестах — проверить `ls -ld dsh/bundle/hooks`, должен быть симлинк `../../hooks`, O-1).
- [x] **Step 8: Коммит**
```bash
git add dsh/subagent-models.yml dsh/tests/subagent-models.test.mjs dsh/install.sh dsh/PORT.md
git commit -m "feat(dsh): overlay профиля — model selection и maxDepth 2 (#41)"
```
---
### Task 2: Skill `sdd` — DSH-семантика исполнения плана
**Files:**
- Create: `dsh/skills/sdd/SKILL.md`
- Create: `dsh/tests/sdd-skill.test.mjs`
- Modify: `dsh/tests/install.test.mjs` (ожидание «copied 2 local skills», проверка `skills/sdd/SKILL.md`)
- Modify: `dsh/README.md` (What works / Not yet / Layout / Known limits)
**Interfaces:**
- Produces: skill с frontmatter `name: sdd` и `description: Use when executing an implementation plan in DeepSeek Harness …`; текст содержит: оба режима, `maxDepth`, инструменты `subagent`/`send_message`/`list_agents`/`interrupt_agent`, статусы `DONE`/`DONE_WITH_CONCERNS`/`NEEDS_CONTEXT`/`BLOCKED`, маршруты `deepseek-official/deepseek-flash` и `deepseek-official/deepseek-v4-pro`, ledger с `## Findings`, таблицу отличий от Claude-SDD.
- Consumes: `dsh/subagent-models.yml` из Task 1 (ссылка в разделе про глубину и модели).
- [x] **Step 1: Написать падающий тест** `dsh/tests/sdd-skill.test.mjs`
Проверки (каждая — отдельный `test`):
1. frontmatter: `/^---\nname: sdd\n/`, `description: .+`;
2. режимы: `/Режим 1/`, `/Режим 2/`, `/maxDepth: 2/` и `/глубин/`;
3. инструменты: `subagent`, `send_message`, `list_agents`, `interrupt_agent`;
4. статусы: `DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, `BLOCKED`;
5. маршруты: `deepseek-official/deepseek-flash`, `deepseek-official/deepseek-v4-pro`;
6. ledger: `## Findings`, `fixed <commit>`, `T<n>.<k>`;
7. ожидание: `/закончи ход/` и `/уведомлен/`; НЕ содержит `job_output`;
8. отличие: `синхронный` и `асинхронный`, `subagent-driven-development`;
9. деградация: `/модель сессии/`, `/не паритет/` — заголовок таблицы отличий с запретом выдавать за паритет;
10. размер: `Buffer.byteLength(text) < 12288`.
Run: `node --test dsh/tests/sdd-skill.test.mjs` → FAIL (файла нет).
- [x] **Step 2: Написать `dsh/skills/sdd/SKILL.md`**
Разделы: When to use / when not (DSH-сессии; в Claude Code — исходный `subagent-driven-development`); два режима и правило выбора (один план — главная сессия, глубина 1; несколько параллельных планов — фоновый контроллер на план, глубина 2) + проверка глубины до первого диспатча и рулинг об откате; протокол диспатча (бриф файлом, отчёт файлом, контракт статусов, в контекст — только статус, коммиты, одна строка тестов); ожидание (конец хода, уведомление, `list_agents` перед каждым диспатчем, `interrupt_agent` для зависших, запрет `job_output`); ревью задачи (спека + качество) и fix-loop 5 раундов (1–3 — тот же implementer через `send_message`, 4–5 — свежий на тир выше, потом адъюдикация); финальное ревью ветки `C-n`/`I-n`/`M-n`; ledger (первая строка — путь плана, таблица конфликтов, рулинги `R-n (design #n)`, `## Findings` со статусами); маршруты моделей и деградация; таблица отличий от `subagent-driven-development` (синхронный `Task` vs асинхронный `subagent`, `send_message`, глубина по умолчанию, выбор модели, ожидание); запреты (не заявлять паритет, `workflow` — расширение, в первой версии не включать).
- [x] **Step 3: Прогнать тест**
Run: `node --test dsh/tests/sdd-skill.test.mjs` → PASS.
- [x] **Step 4: Обновить `dsh/tests/install.test.mjs`**
`copied 1 local skills` → `copied 2 local skills`; после проверки `role` проверить `join(dshHome, 'skills', 'sdd', 'SKILL.md')` — существует и содержит `name: sdd`.
- [x] **Step 5: Обновить `dsh/README.md`**
What works: SDD-инструкция и overlay. Not yet: убрать SDD, оставить `briefing`, `harness-check`, `role-hint`, `harness-doctor`, auto-memory, caveman. Layout: `skills/sdd/SKILL.md`, `subagent-models.yml`. Known limits: заменить строку про `sonnet`/`haiku`/`opus` ссылкой на overlay.
- [x] **Step 6: Прогнать всё**
Run: `bash dsh/tests/run.sh && bash tests/run.sh` → зелёные.
- [x] **Step 7: Коммит**
```bash
git add dsh/skills/sdd/SKILL.md dsh/tests/sdd-skill.test.mjs dsh/tests/install.test.mjs dsh/README.md
git commit -m "feat(dsh): skill sdd — асинхронный протокол исполнения плана (#41)"
```
---
### Task 3: Пробный прогон 3 задач и запись результатов
**Files:**
- Create: `/tmp/dsh-sdd-trial*` — temp-репозиторий (не входит в PR)
- Modify: `openspec/changes/dsh-sdd/design.md` — раздел «Результаты пробного прогона»
- Modify: `dsh/PORT.md` §8 — п. 4 закрыт ссылкой на прогон
- Modify: этот план — отметить шаги
- Comment: issue #41, затем PR (`tea pulls create`)
**Interfaces:**
- Consumes: skill `sdd` из Task 2, overlay из Task 1 (в профиле уже включён владельцем).
- Produces: транскрипт прогона (диспатчи, статусы, отчёты, ledger) в `design.md`; вывод о режимах 1 и 2.
- [x] **Step 1: Создать temp-репозиторий и план**
`TRIAL="$(mktemp -d)/trial"`; `git init -b main`, коммит `README.md`; `git checkout -b feat/trial`; файл `docs/superpowers/plans/2026-10-24-trial.md` — один план (require-plan разрешает правки), проект: Python-модуль `src/stats.py`. Три задачи: (1) `mean(values)` — float, `ValueError` на пустом списке, готовый код и тест в плане; (2) `median(values)` — по требованиям для чётной длины среднее двух средних; **в плане намеренно оставлен дефект**: `return xs[len(xs) // 2]`, а тест в плане покрывает только нечётную длину (дефект должен найти ревьюер, не тесты); (3) `describe(values)` — dict `{count, mean, median}` через две функции. Тесты — `python3 -m unittest discover -s tests`. Спека прогона — `SPEC.md` в temp-репо: требования к `mean`/`median`/`describe`, включая «чётная длина — среднее двух средних».
- [x] **Step 2: Ledger прогона**
`.superpowers/sdd/trial-plan/progress.md`: первая строка — путь плана; таблица сканирования плана (конфликт: план Task 2 противоречит `SPEC.md` — рулинг `R-1`: следовать `SPEC.md`, дефект фиксировать находкой `T2.1` при ревью, а не править план до диспатча); пустой `## Findings`; три строки `Task N: pending`.
- [x] **Step 3: Диспатч implementer'а Task 1 (режим 1)**
`subagent` c `description` «trial task 1», `cwd` = temp-репо, `provider: deepseek-official`, `model: deepseek-flash`; промпт — brief файлом `briefs/task-1.md` (контекст, требования, точный код, тест, коммит), отчёт — `reports/task-1.md` с первой строкой-статусом; требование: `python3 -m unittest discover -s tests` зелёный, коммит. После вызова — закончить ход; продолжить по уведомлению.
- [x] **Step 4: Собрать отчёт Task 1 и отправить на ревью**
Прочитать `reports/task-1.md`, сверить коммит (`git -C "$TRIAL" log --oneline`) и прогон тестов; `list_agents` — сверка состояния; в ledger `Task 1: complete`. Диффпакет — `review/pkg-task-1.txt` (`git diff main...HEAD`); `subagent` с `model: deepseek-v4-pro`: ревью по `SPEC.md` и по качеству, находки `T1.<k>`, вердикт в `reports/review-1.md`.
- [x] **Step 5: Task 2 — диспатч, ревью, fix-loop**
Так же диспатчить Task 2 (flash, brief с кодом из плана и дефектом). Ревью (v4-pro) должно вернуть `T2.1` — чётная длина медианы. Fix-раунд 1: `send_message` тому же implementer'у с находкой; по уведомлению — тест на чётную длину, фикс, коммит; ре-ревью скопом — новый `subagent` (v4-pro) с пакетом `review/pkg-fix-1.txt`; в ledger `T2.1: fixed <commit>`. Если ревьюер дефект не нашёл — зафиксировать `O-1` (протокол пройден, но приёмка #41 «ревьюер находит дефект» не доказана) и повторить ревью с явным указанием проверять требование «чётная длина».
- [x] **Step 6: Task 3 и финальное ревью ветки**
Task 3 — flash, по плану; ревью — v4-pro; финальное ревью всей ветки — `subagent` v4-pro по полному диффу `main...HEAD`, находки `C-n`/`I-n`/`M-n`; один fix-диспатч при находках, одно скоуп-ревью.
- [x] **Step 7: Проба режима 2 (глубина 2)**
Отдельный temp-репозиторий или новая ветка в том же: `subagent` (flash) с промптом «ты контроллер, задиспатчь implementer'а на задачу X, дождись уведомления, верни отчёт»; контроллер обязан сам вызвать `subagent` и завершиться. Записать: работает / падает `exceeds maxDepth` / уведомление не пришло. При падении — находка `O-n` и вывод, что режим 2 требует поднятой глубины (overlay уже в профиле).
- [x] **Step 8: Записать результаты**
`openspec/changes/dsh-sdd/design.md` — раздел «Результаты пробного прогона 24.10.2026»: таблица приёмки #41 (пункт → чем доказан → статус), реальные диспатчи с моделями, найденный дефект `T2.1` с коммитом фикса, ledger дословно, вывод по режимам, наблюдения `O-n` со статусами. `dsh/PORT.md` §8 п. 4 — закрыт.
- [x] **Step 9: Проверки, коммит, PR**
Run: `bash dsh/tests/run.sh && bash tests/run.sh`; `openspec validate dsh-sdd --strict` (если поддерживается).
Коммит `docs(dsh): пробный прогон SDD — режимы, дефект и fix-loop (#41)`, push, `tea pulls create --base main --head feat/41-dsh-sdd-spike --title "DSH: SDD — skill, overlay, пробный прогон (#41)"`. Комментарий в #41 — состояние фазы и ссылка на PR.
## Acceptance
Из #41: три задачи не теряются (ledger `Task 1..3: complete` + три коммита в temp-репо), отчёты собираются (файлы отчётов + строки ledger), ревьюер находит и проверяет хотя бы один дефект (`T2.1` `fixed <commit>`), fix-loop завершается (ре-ревью чистое), ledger содержит `## Findings` со статусами, прогон тестов зелёный, отличие от Claude-SDD указано таблицей в skill.

View File

@ -1,10 +1,10 @@
# DSH port — design and spike notes
Status: **spike 0.2.0, live guard/require-plan/bootstrap verified; `/role`
command works, the model-invocable `role` skill is added but its live
acceptance is pending**. This directory is the DeepSeek Harness adapter for
`claude-harness`; the Claude Code installation and the repository's source of
truth are unchanged.
Status: **spike 0.2.1, live guard/require-plan/bootstrap verified; `/role`
command works, the model-invocable `role` skill and the asynchronous SDD
protocol are verified live (three-task trial run)**. This directory is the
DeepSeek Harness adapter for `claude-harness`; the Claude Code installation and
the repository's source of truth are unchanged.
## 1. Verdict
@ -60,10 +60,10 @@ file has to be installed as `$DSH_HOME/AGENTS.md` because DSH does not read
covers most of `role-hint`.
- **`briefing`**: needs an agent preset with read-only tools and a model route;
not part of the guard/bootstrap spike.
- **SDD**: the Claude `Task` is synchronous, while DSH `subagent` returns a
child id and reports later. This needs a semantic rewrite (async controller
with `send_message` + `maxDepth: 2`, or a `workflow` script). Do not claim SDD
parity from this spike.
- **SDD parity**: the Claude `Task` is synchronous, while DSH `subagent` returns
a child id and reports later. The `sdd` skill implements the asynchronous
analogue (report files, notification as the wait, `send_message` for fix
rounds, ledger as memory); there is no parity and the skill says so.
- **Codegraph by default**: `dsh/mcp.codegraph.yml` is an opt-in overlay; the
bundle does not assume the binary is installed.
- **`harness-doctor`**: its scripts inspect `~/.claude`; a DSH variant is a
@ -162,6 +162,32 @@ for the repository layout:
bootstrapMaxBytes: 65536
```
### Model routes and delegation depth
SDD needs two things the default profile does not give: a model choice for
`subagent` and a second level of delegation for a background controller.
`dsh/subagent-models.yml` is the opt-in overlay for both:
```bash
# 1. copy the two entries into the profile patch (desktop is Electron-managed)
open -e ~/.dsh/profiles/desktop/cordis.patch.yml # append dsh/subagent-models.yml
# 2. restart DSH, then check
bash dsh/install.sh --check # "ok: subagent maxDepth=2 (...)"
```
After the restart the `subagent` and `subagent_fork` schemas accept
`provider`, `model`, `reasoning_effort`, and a background controller subagent
can dispatch an implementer. Routes measured in 0.2.1-rc.3:
`deepseek-official/deepseek-flash` (transcription and mechanics) and
`deepseek-official/deepseek-v4-pro` (integration and every review). Without the
overlay the agent runs everything on the session model and at depth 1; the
`sdd` skill must say so in its report instead of pretending the routes exist.
The overlay stays out of `dsh/bundle/cordis.patch.yml` on purpose: the
`subagent-model-selection-settings` row exists in the web/desktop profile and is
absent in headless, and enabling it from the bundle would break headless boot
with "requires ... in the Host scope".
`bootstrapSkillPath` resolution order: explicit path, `DSH_SUPERPOWERS_SKILL`,
`~/.dsh/skills`, `~/.agents/skills`, the newest
`~/.claude/plugins/cache/claude-plugins-official/superpowers/*/`, then
@ -271,8 +297,9 @@ profile was removed; the real `desktop` profile was not touched.
- **Subagent bootstrap.** `isSubagent` probes common fields; if none are present
on this version, the bootstrap reaches children too. The skill's
`<SUBAGENT-STOP>` makes that harmless but token-expensive.
- **Model tiers.** `sonnet`/`haiku`/`opus` are not DSH models. Map them to live
provider/model routes before porting the SDD text.
- **Model tiers.** `sonnet`/`haiku`/`opus` are not DSH models. The port uses two
live routes (`deepseek-flash`, `deepseek-v4-pro`) from the opt-in overlay in
`dsh/subagent-models.yml`; without it every dispatch runs on the session model.
- **`~/.claude` dependence.** `dsh/install.sh --skills --roles` copies skills
and roles into `~/.dsh`; the bootstrap and `/role` prefer those copies, and
the Claude cache/roles are fallbacks. An existing installation must rerun
@ -291,8 +318,10 @@ profile was removed; the real `desktop` profile was not touched.
2. Add `harness-check` and optional `role-hint` as non-blocking pre-step
injections.
3. Build `briefing` as an agent preset (read-only tools + model route).
4. SDD spike: async controller (`subagent` + `send_message`, `maxDepth: 2`) for
one plan, or a `workflow`-based executor; measure which fits the harness.
4. SDD — **done** (change `dsh-sdd`): the `sdd` skill (async controller,
reports, review, fix-loop, ledger) plus the overlay in §5; proven by a
three-task trial run (`openspec/changes/dsh-sdd/trial/`, results in
`design.md`).
5. Enable CodeGraph MCP by default when the binary is present; update role and
project instructions to `mcp__codegraph__codegraph_explore`.
6. Port `harness-doctor` checks to DSH paths.

View File

@ -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/`.

View File

@ -102,6 +102,24 @@ check() {
echo "FAIL: hook bridge is missing (is dsh/bundle/hooks a symlink to ../../hooks?)" >&2
failed=1
fi
if [ -f "$ROOT/dsh/subagent-models.yml" ]; then
echo "ok: subagent overlay ($ROOT/dsh/subagent-models.yml)"
else
echo "WARN: subagent overlay missing; SDD runs on the session model at depth 1" >&2
fi
local patch found=0
for patch in "$DSH_HOME"/profiles/*/cordis.patch.yml; do
[ -f "$patch" ] || continue
if grep -qs 'maxDepth: 2' "$patch"; then
found=1
break
fi
done
if [ "$found" = 1 ]; then
echo "ok: subagent maxDepth=2 ($patch)"
else
echo "WARN: subagent maxDepth is not 2 in any profile patch; SDD mode 2 is unavailable" >&2
fi
if [ -f "$DSH_HOME/skills/using-superpowers/SKILL.md" ]; then
echo "ok: dsh skills ($DSH_HOME/skills)"
elif superpowers_skills >/dev/null 2>&1; then

150
dsh/skills/sdd/SKILL.md Normal file
View File

@ -0,0 +1,150 @@
---
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` — рулинг
контроллера, а не остановка сессии.
6. Замеченное расхождение с брифом или спекой — это `DONE_WITH_CONCERNS`, даже
если задача формально выполнена: ревью его проверит, контроллер — решит.
## Ожидание без блокировки
`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 <причина>`.
Находка без статуса в отчёте контроллера — ошибка контроллера.
Ожидаемое число тестов в плане — ориентир, а не контракт: fix-раунд добавляет
тесты, поэтому в отчёте фиксируется фактическое число, а расхождение вверх
объясняется, а не считается дефектом.
## Финальное ревью ветки
После всех задач — один ревьюер на весь дифф `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`. Если `subagent` вернул «child LLM provider
and model must be supplied together», хотя оба параметра переданы, — повтори
вызов один раз: валидация в 0.2.1-rc.3 нестабильна.
## Отличие от 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
dsh/subagent-models.yml Normal file
View File

@ -0,0 +1,32 @@
# Opt-in overlay for DSH-native SDD (issue #41).
#
# Copy these two entries into $DSH_HOME/profiles/desktop/cordis.patch.yml, then
# restart DSH (the plugin page must reload the profile). Verify with:
# bash dsh/install.sh --check
# # in a new session the `subagent` tool schema shows provider/model/reasoning_effort
#
# Do NOT add them to dsh/bundle/cordis.patch.yml: the
# `subagent-model-selection-settings` row exists in the web/desktop profile and
# is absent in headless, so enabling it in the bundle breaks headless boot
# ("requires ... in the Host scope").
#
# What it unlocks:
# - `subagent` and `subagent_fork` accept provider/model/reasoning_effort,
# so an implementer can run on a cheaper route than the reviewer;
# - `subagent.maxDepth: 2`, so a background controller subagent (depth 1)
# can dispatch implementers (depth 2).
#
# Routes measured in DSH 0.2.1-rc.3:
# deepseek-official/deepseek-flash - transcription and mechanics
# deepseek-official/deepseek-v4-pro - integration and every review
- id: subagent-model-selection-settings
config:
enabled: true
allowedModels:
- provider: deepseek-official
model: deepseek-flash
- provider: deepseek-official
model: deepseek-v4-pro
- id: subagent
config:
maxDepth: 2

View File

@ -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`)
}

View 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)
})

View File

@ -0,0 +1,39 @@
import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
import { cleanup, tempDir, write } from './helpers.mjs'
const here = dirname(fileURLToPath(import.meta.url))
const overlay = join(here, '..', 'subagent-models.yml')
const script = join(here, '..', 'install.sh')
test('overlay enables model selection for both routes and depth 2', () => {
assert.ok(existsSync(overlay), 'dsh/subagent-models.yml is missing')
const text = readFileSync(overlay, 'utf8')
assert.match(text, /id: subagent-model-selection-settings/)
assert.match(text, /model: deepseek-flash/)
assert.match(text, /model: deepseek-v4-pro/)
assert.match(text, /id: subagent\n\s+config:\n\s+maxDepth: 2/)
assert.match(text, /headless/)
})
test('install.sh --check reports the overlay and the profile depth', () => {
const home = tempDir()
try {
const dshHome = join(home, '.dsh')
const env = { ...process.env, HOME: home, DSH_HOME: dshHome }
const missing = spawnSync('bash', [script, '--check'], { env, encoding: 'utf8' })
assert.match(missing.stderr, /subagent maxDepth is not 2/)
write(join(dshHome, 'profiles', 'desktop', 'cordis.patch.yml'), '- id: subagent\n config:\n maxDepth: 2\n')
const present = spawnSync('bash', [script, '--check'], { env, encoding: 'utf8' })
assert.match(present.stdout, /ok: subagent overlay/)
assert.match(present.stdout, /ok: subagent maxDepth=2/)
} finally {
cleanup(home)
}
})

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-24

View File

@ -0,0 +1,226 @@
# Design: DSH-native SDD
## Context
Claude-SDD (`~/.dsh/skills/subagent-driven-development/SKILL.md`) опирается на
синхронный `Task`: контроллер вызвал implementer'а и в том же ходе получил
отчёт. DSH так не умеет — и это не деталь, а другая форма исполнения.
Что измерено в DSH 0.2.1-rc.3 (`@deepseek-ai/dsh-tool-subagent`,
`dsh-subagent`, `dsh-tool-workflow`, `dsh-workflow-ptc`):
| Свойство | Значение в DSH | Следствие для SDD |
|---|---|---|
| `subagent` возвращает | `started subagent <id>` сразу, результат — уведомлением позже | нет синхронного ожидания; ход контроллера заканчивается |
| Продолжение ребёнка | `send_message` (провайдер `spawn` continuable) | раунды фикса идут в того же implementer'а |
| `subagent_fork` | наследует завершённые ходы родителя | для implementer'а не годится: нужен свежий контекст |
| `subagent.maxDepth` | по умолчанию **1** (настройка сервиса, volatile) | контроллер-сабагент не позовёт implementer'а, пока не поднято до 2 |
| Выбор модели у `subagent` | только при `modelSelectionSettings: true` на строке `tool-subagent`; иначе ребёнок наследует модель сессии | «укажи модель в dispatch» невыполнимо без правки профиля |
| `workflow` | синхронный, один вызов инструмента; `agent(prompt, {schema, provider, model, label, phase})` | годится для детерминированного fan-out с выбором модели без настроек |
| Кэпы `workflow` | `maxTotalAgents`, `maxConcurrentAgents`, `maxItemsPerCall` | backstop для веера; для 3 задач не упираемся |
| Наблюдение за детьми | `list_agents`, `interrupt_agent`, уведомления | «дождаться» = закончить ход и проснуться от уведомления |
| Модели в окружении | `deepseek-official/deepseek-flash`, `deepseek-official/deepseek-v4-pro` | три тира роли схлопываются в два |
Строка `subagent-model-selection-settings` есть в web/desktop-профиле и
отсутствует в headless. Поэтому флаг `modelSelectionSettings` нельзя ставить в
бандл: headless упадёт на загрузке с «requires … in the Host scope». Он живёт
в opt-in overlay профиля.
## Goals / Non-Goals
**Goals**
- Инструкция, по которой DSH-сессия исполняет план без вранья о возможностях
платформы: два режима контроллера, протокол отчётов, ревью задачи, fix-loop,
ledger с `## Findings`, финальное ревью.
- Проверяемый прогон: план из 3 задач в temp-репозитории, дефект найден
ревьюером и закрыт в fix-loop.
- Тесты, идущие без DSH runtime.
- Явная таблица отличий от Claude-SDD.
**Non-Goals**
- Паритет с `Task`: его не будет, и это фиксируется, а не имитируется.
- Переписывание `~/.dsh/skills/subagent-driven-development/` — skill
superpowers остаётся как есть; DSH-семантика живёт в отдельном skill.
- Правка `roles/dev.md` в общем слое: роль для DSH — слой `~/.dsh/roles`.
- Автоматическое включение выбора модели и глубины: это решение владельца.
## Решения владельца (brainstorming 24.10.2026)
- **Режим контроллера зависит от контекста, а не один на всех.**
Одна сложная задача — контроллер главная сессия. Несколько задач параллельно,
каждая по SDD, — контроллер фоновый сабагент на задачу.
## Design
### Режим 1 — главная сессия контроллер
Главная сессия читает план, ведёт ledger, диспатчит `subagent` (depth 1,
укладывается в дефолтный `maxDepth: 1`), получает уведомление, диспатчит
ревьюера, при находках — `send_message` implementer'у. Плюс: рулинги по
конфликтам плана принимает тот, у кого есть спека и контекст. Минус: контекст
главной сессии растёт, поэтому отчёты — файлами, а в контекст возвращается
только статус, коммиты и одна строка тестов.
### Режим 2 — фоновый контроллер
Один `subagent` на план, `maxDepth: 2`. Контроллер не блокируется: диспатч
implementer'а возвращает id, ход заканчивается, уведомление о завершении
начинает следующий ход. Пропущенное уведомление ловится `list_agents` перед
следующим диспатчем. Главная сессия в это время свободна для другой задачи.
### Протокол (общий для обоих режимов)
1. Setup: ledger `.superpowers/sdd/<plan>/progress.md`, первая строка —
путь плана; скан плана на конфликты (таблица в ledger, рулинги до Task 1).
2. Dispatch implementer'а: brief файлом, отчёт файлом, контракт — `DONE`,
`DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, `BLOCKED`.
3. Ревью задачи: пакет диффа файлом, вердикт по спеке и по качеству, находки
`T<n>.<k>`.
4. Fix-loop до 5 раундов: раунды 1–3 — тот же implementer через
`send_message`, 4–5 — свежий и на тир выше; затем адъюдикация.
5. Финальное ревью ветки: `C-n`/`I-n`/`M-n`, один fix-диспатч, одно
скоуп-ревью.
6. Ledger: `## Findings` с маркером и статусом; рулинги `R-n (design #n)`.
### Модели
Два маршрута вместо трёх тиров:
| Роль в SDD | Маршрут | Почему |
|---|---|---|
| implementer по готовому коду из плана | `deepseek-official/deepseek-flash` | транскрипция + тесты |
| implementer по прозе, интеграция | `deepseek-official/deepseek-v4-pro` | решение в нескольких файлах |
| ревью задачи, ре-ревью | `deepseek-official/deepseek-v4-pro` | вердикт важнее цены |
| финальное ревью ветки | `deepseek-official/deepseek-v4-pro` | самый широкий дифф |
Без overlay всё идёт на модели сессии (`deepseek-flash`); тиры становятся
недоступны, и инструкция это явно говорит, а не молча игнорирует.
### Где живёт
`dsh/skills/sdd/SKILL.md` — в репозитории обвязки, ставится
`dsh/install.sh --skills` в `~/.dsh/skills`. Claude Code его не получает:
DoD порта требует, чтобы CC-харнес не менялся.
## Альтернативы
- **Только workflow.** Детерминированный fan-out силён для независимых задач,
но решения по конфликтам плана, адъюдикация находок и «стоп, план врёт»
требуют суждения в моменте. Скрипт может ветвиться по структурному ответу
ревьюера, но не может решить, что находка неверна.
- **Только фоновый контроллер.** Дороже на одну задачу и требует поднятой
глубины; для одиночной задачи лишний уровень.
- **Гибрид.** `workflow` — для веера независимых задач внутри плана,
контроллер — для последовательной петли. Держим как расширение, в первой
версии не включаем: веер ещё не встречался в планах этого репозитория.
## Риски
- Уведомление о завершении ребёнка не пришло (обрыв, компакция) — контроллер
обязан сверяться с `list_agents` и git, а не с памятью.
- `maxDepth: 2` не поднят, а режим 2 выбран — диспатч упадёт
`subagent depth 2 exceeds maxDepth 1`; инструкция требует проверить до
старта и откатиться в режим 1 с записью рулинга.
- Ledger лежит в git-ignored scratch: `git clean -fdx` его уничтожает;
восстановление — по `git log`.
- Модели две: «haiku» из Claude-текста не существует, перенос формулировок
тиров без таблицы превратится в фикцию.
## Что должен доказать пробный прогон
| Пункт приёмки #41 | Чем доказывается |
|---|---|
| 3 задачи не теряются | ledger: три `Task N: complete`, три коммита |
| отчёты собираются | файлы отчётов implementer'ов + строки в ledger |
| ревьюер находит дефект | `T1.1` с `fixed <commit>` |
| fix-loop завершается | раунд в ledger, ре-ревью чистое |
| ledger содержит `## Findings` со статусами | сам файл |
| отличие от Claude-SDD указано | таблица выше + раздел в skill |
## Результаты пробного прогона 24.10.2026
Площадка — `/tmp/dsh-sdd-trial`: отдельный git-репозиторий, ветка `feat/trial`,
база `main`; модуль `src/stats.py` из трёх задач. Контроллер — главная сессия
(Режим 1), overlay профиля включён (`model selection` + `maxDepth: 2`),
`deepseek-flash` — implementer'ы, `deepseek-v4-pro` — все ревью. Артефакты
скопированы в `trial/`: `SPEC.md`, `plan.md`, брифы, отчёты implementer'ов и
ревьюеров, `ledger.md` (первая строка — путь плана, `## Findings`, `## Rulings`).
### Диспатчи
| Шаг | Агент | Маршрут | Результат |
|---|---|---|---|
| Task 1 implementer | `1c906f6f` | flash | `DONE`, `dc1670f`, 2 теста OK |
| Task 1 review | `68a1aeb4` | v4-pro | `APPROVED`, находок нет |
| Task 2 implementer | `1ec8f7fc` | flash | `DONE`, `836a730`, 4 теста OK, concern: план противоречит спеке на чётной длине |
| Task 2 review | `e50c74d2` | v4-pro | `CHANGES_REQUESTED`: `T2.1`, `T2.2` (проверено независимо от concerns) |
| Task 2 fix, раунд 1 | `1ec8f7fc` (send_message) | flash | `186575c`, 6 тестов OK |
| Task 2 re-review (скоуп) | `7912c16b` | v4-pro | `APPROVED`: `T2.1 fixed 186575c`, `T2.2 fixed 186575c` |
| Task 3 implementer | `6ca07edc` | flash | `DONE`, `7cb0383`, 7 тестов OK |
| Task 3 review | `f10e3165` | v4-pro | `APPROVED`, находок нет |
| Final branch review | `0fec8102` | v4-pro | `APPROVED`, находок нет |
### Приёмка #41
| Пункт приёмки | Чем доказан | Статус |
|---|---|---|
| 3 задачи не теряются | ledger `Task 1..3: complete`, 4 коммита `dc1670f`/`836a730`/`186575c`/`7cb0383` | выполнен |
| отчёты собираются | 9 файлов в `trial/reports/`, статус первой строкой у каждого | выполнен |
| ревьюер находит и проверяет дефект | `T2.1` найден независимо (`median([1,2]) == 2.0` вместо `1.5`), `T2.2` — отсутствие теста; оба `fixed 186575c`, ре-ревью APPROVED | выполнен |
| fix-loop завершается | раунд 1 из 5, ре-ревью чистое | выполнен |
| ledger содержит `## Findings` со статусами | `trial/ledger.md` | выполнен |
| итоговый прогон зелёный | `python3 -m unittest discover -s tests` → 7 тестов OK; `bash dsh/tests/run.sh` и `bash tests/run.sh` зелёные | выполнен |
| отличие от Claude-SDD указано | таблица в `dsh/skills/sdd/SKILL.md` | выполнен |
### Находки и наблюдения прогона
| Маркер | Что | Статус |
|---|---|---|
| `T2.1` | `median` на чётной длине возвращал верхний средний элемент вместо среднего двух | `fixed 186575c` |
| `T2.2` | не было теста на чётную длину | `fixed 186575c` |
| `O-1` | «Interfaces» плана обещают аннотации типов, «Exact code» их не содержит; SPEC аннотаций не требует | `rejected` (правка плана в прогоне не даёт результата) |
| `O-2`, `O-3` | первый вызов `subagent` с переданными `provider`/`model` вернул «child LLM provider and model must be supplied together»; немедленный повтор тем же вызовом проходит (2 раза за прогон) | `deferred` (наблюдение о стабильности валидации DSH; повтор в skill) |
| `O-4` | ожидаемое число тестов в плане (`5`) устарело после fix-раунда Task 2 (фактически 7) | `fixed` — в skill добавлено правило «число тестов в плане — ориентир» |
| `O-5` | тест `describe([1,2,3])` на симметричном входе не отличил бы перестановку `mean`/`median` | `deferred` (реализация верна, слабость плана) |
Отдельно: implementer Task 2 заметил расхождение плана со спекой, но поставил
`DONE`, а не `DONE_WITH_CONCERNS`; ревью дефект всё равно поймало, потому что
ревьюер проверял по `SPEC.md`, а не по отчёту. Правило уточнено в skill:
замеченное расхождение с брифом — это `DONE_WITH_CONCERNS`.
### Режим 2 (фоновый контроллер)
Отдельная площадка `/tmp/dsh-sdd-mode2`. Контроллер `afe886e5` (flash) вызвал
`subagent` (flash) и получил id; ход закончился, уведомление о завершении пришло
отдельным сообщением, после чего контроллер проверил отчёт, коммит `0601d6e` и
тесты независимо. Глубина 2 работает с overlay; синхронного ожидания нет.
### Отличия от дизайна, обнаруженные прогоном
- Число тестов в плане — ориентир: fix-раунд добавляет тесты, и расхождение
вверх не дефект (O-4).
- Ошибка валидации `provider`/`model` нестабильна; лечится повтором, а не
пересмотром маршрутов (O-2/O-3).
- Ревьюер, проверяющий по спеке, а не по отчёту implementer'а, ловит дефекты,
даже если implementer промолчал бы.
## Decisions made without discussion
- **24.10.2026 — overlay вместо флага в бандле.** Обоснование: headless-профиль
не содержит строку `subagent-model-selection-settings`, включённый флаг роняет
загрузку. Цена ошибки: владельцу нужно скопировать три записи в профиль
руками; забыл — работает на модели сессии.
- **24.10.2026 — карта тиров.** `flash` — транскрипция и механика, `v4-pro` —
интеграция и все ревью. Цена ошибки: дороже прогон или слабее ревью; правится
одной таблицей в skill.
- **24.10.2026 — skill ставится только в `~/.dsh`.** Обоснование: DoD порта
запрещает менять CC-харнес. Цена ошибки: в Claude-сессиях инструкции про
DSH-режимы нет — там работает исходный `subagent-driven-development`.
- **24.10.2026 — гибрид отложен.** `workflow` описан как расширение, не
реализуется в этом change. Цена ошибки: веер задач придётся ввести позже
отдельной правкой skill.
## Открытые вопросы
- Нужно ли после приёмки добавить в `~/.dsh/roles/dev.md` строку «в DSH
исполнение плана — skill `sdd`» (сейчас роль ссылается на superpowers-SDD).

View File

@ -0,0 +1,71 @@
# Proposal: DSH-native SDD
Issue: #41 (роадмап порта — #48). Источники:
- `dsh/PORT.md`, разделы 3 (deferred: SDD), 7 (риски), 8 (next phases, п. 4);
- brainstorming 24.10.2026 (ответы владельца о режимах контроллера);
- разбор исходников DSH 0.2.1-rc.3: `dsh-tool-subagent`, `dsh-tool-workflow`,
`dsh-subagent`, `dsh-workflow-ptc`.
## Why
SDD — единственное место, где DSH расходится с Claude Code не текстом, а
семантикой. В Claude Code контроллер исполняет план, вызывая `Task` и получая
результат в том же ходе; в DSH `subagent` возвращает id и завершается
уведомлением позже, а `workflow` исполняет детерминированный скрипт и ждёт
всех детей. Пока эта разница не переписана, `subagent-driven-development`
в DSH-сессии либо врёт о возможностях платформы, либо ломается на первом
ожидании.
Дополнительно разбор исходников показал две вещи, которые меняют план:
1. Глубина делегирования по умолчанию — 1. Фоновый контроллер-сабагент не
может позвать implementer'а, пока `subagent.maxDepth` не поднят до 2.
2. У инструмента `subagent` нет `provider`/`model` в схеме, пока строке
`tool-subagent` не поставлен `modelSelectionSettings: true`; ребёнок
наследует модель сессии. Выбор модели работает сразу только внутри
`workflow.agent()`. Маршрутов в этом окружении два:
`deepseek-official/deepseek-flash` и `deepseek-official/deepseek-v4-pro`.
Без этих двух фактов любая инструкция SDD «укажи модель в dispatch» невыполнима
и превращается в фикцию.
## What Changes
- **Новый DSH-skill `dsh/skills/sdd/SKILL.md`** — инструкция исполнения плана
в DSH: два режима контроллера (главная сессия и фоновый сабагент), протокол
dispatch/wait/report, ревью задачи, fix-loop, ledger `## Findings`, рулинги,
финальное ревью ветки, отличие от Claude-SDD одной таблицей.
Ставится `dsh/install.sh --skills` в `~/.dsh/skills`; Claude Code его не
получает — харнес Claude не меняется.
- **`dsh/subagent-models.yml`** — opt-in overlay профиля: `modelSelectionSettings`
для `tool-subagent` и `tool-subagent-fork` плюс `subagent.maxDepth: 2`.
В бандл не входит: строка `subagent-model-selection-settings` есть в
web/desktop-профиле и отсутствует в headless, и включённый флаг уронил бы
headless на загрузке.
- **`dsh/PORT.md`** — раздел о включении выбора модели и глубины, ссылка на
overlay, снятие пункта 4 из «Next phases» после приёмки.
- **`dsh/tests/sdd-skill.test.mjs`** — проверки, которые идут без DSH runtime:
frontmatter skill, оба режима, имена инструментов, требование маршрута
модели, отсутствие ссылок на несуществующие средства (`job_output` как
ожидание сабагента, `maxDepth` без оговорки).
- **Пробный прогон** в temp-репозитории: план из 3 задач, implementer →
reviewer → fix → финальное ревью; транскрипт и ledger — в отчёте issue.
Прогон доказывает семантику, а не заменяет тесты: без DSH runtime его не
повторить, поэтому он делается один раз и результаты фиксируются в
`design.md` и в комментарии к #41.
## Impact
- Claude-harness не меняется: `roles/`, `hooks/`, `global/`, `install.sh`,
`agents/` остаются как есть.
- Бандл не меняет состав строк профиля; новое поведение — только по явному
решению владельца через overlay.
- Роль dev после приёмки должна ссылаться на новый skill для DSH-сессий
(одна строка; правится в `~/.dsh/roles`, не в общем `roles/dev.md`).
## Acceptance
Из #41, без изменений: три задачи не теряются, отчёты собираются, ревьюер
находит и проверяет хотя бы один дефект, fix-loop завершается, ledger содержит
`## Findings` со статусами, прогон тестов зелёный, отличие от Claude-SDD
указано явно.

View File

@ -0,0 +1,105 @@
# Spec Delta: dsh-sdd
## Purpose
Исполнение плана субагентами в DeepSeek Harness (SDD). Контроллер диспатчит
implementer'а на задачу плана, получает отчёт, отправляет задачу на ревью
и ведёт fix-loop до чистоты, затем заказывает финальное ревью ветки. DSH не
имеет синхронного `Task`, поэтому протокол строится на асинхронном `subagent`
(идентификатор, уведомление, `send_message`) и на ledger как единственной
памяти между ходами.
## ADDED Requirements
### Requirement: Режим контроллера выбирается по контексту
Skill SHALL предлагать два режима и MUST выбирать между ними по числу
параллельных планов, а не по умолчанию.
- Один план в сессии — контроллер главная сессия; implementer'ы и ревьюеры
диспатчатся на глубину 1, что укладывается в `subagent.maxDepth` по
умолчанию.
- Несколько задач, каждая по SDD, — контроллер фоновый сабагент на план,
глубина 2.
Skill MUST проверить доступную глубину до первого диспатча и MUST записать
рулинг с откатом в режим главной сессии, если глубина меньше нужной.
#### Scenario: Одна сложная задача
- **WHEN** в сессии один план и он архитектурный
- **THEN** контроллером остаётся главная сессия, а субагенты вызываются с
глубины 1
#### Scenario: Глубина не поднята
- **WHEN** выбрано несколько параллельных планов, а `subagent.maxDepth` равен 1
- **THEN** skill не диспатчит контроллера, фиксирует рулинг об откате в режим
главной сессии и продолжает исполнение в нём
### Requirement: Асинхронный протокол диспатча и отчёта
Диспатч implementer'а SHALL передавать требования файлом брифа, а результат
MUST возвращаться файлом отчёта; в контекст контроллера возвращается только
статус, коммиты, одна строка сводки тестов и concerns. Контракт статусов:
`DONE`, `DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, `BLOCKED`.
#### Scenario: Отчёт файлом
- **WHEN** implementer завершил задачу
- **THEN** полный отчёт лежит в файле отчёта, а в ledger и контекст попали
только статус, коммиты и строка тестов
### Requirement: Ожидание без блокировки
Skill MUST NOT ждать ребёнка синхронно: в DSH такого средства нет. Пока
у контроллера есть локальная работа, он MUST продолжать её; когда работы нет,
он MUST закончить ход и продолжить по уведомлению о завершении. Перед каждым
следующим диспатчем контроллер MUST сверить живых детей через `list_agents`,
чтобы поймать завершившегося без отчёта.
#### Scenario: Ребёнок завершился без отчёта
- **WHEN** уведомление о завершении не пришло, а `list_agents` показывает
неактивного ребёнка
- **THEN** контроллер считает задачу завершённой по git-состоянию и ledger,
а не перезапускает её
### Requirement: Ревью задачи и fix-loop
Каждая задача MUST проходить ревью по спеке и по качеству; implementer self-review
его не заменяет. Fix-loop MUST длиться не более 5 раундов: раунды 1–3 — тот же
implementer через `send_message`, раунды 4–5 — свежий implementer на тир выше.
После пятого раунда контроллер MUST адъюдицировать каждую открытую находку.
#### Scenario: Дефект найден и закрыт
- **WHEN** ревьюер задачи вернул находку по существу
- **THEN** раунд фикса уходит тому же implementer'у, ре-ревью ограничено
исправлением, находка получает статус `fixed <commit>`
### Requirement: Ledger и маркировка находок
Своя рабочая директория плана SHALL содержать `progress.md` с первой строкой —
путём плана. Ledger MUST содержать таблицу сканирования плана, рулинги
`R-n (design #n)` и раздел `## Findings`, где у каждой находки есть маркер
источника (`T<n>.<k>`, `C-n`/`I-n`/`M-n`) и статус `fixed <commit>`,
`deferred` или `rejected <причина>`.
#### Scenario: Находка без статуса
- **WHEN** финальное ревью ветки вернуло находку, а исполнение остановлено
- **THEN** отчёт контроллера обязан показать её со статусом; находка без
статуса считается ошибкой контроллера
### Requirement: Тира моделей и деградация
Skill MUST называть маршруты моделей явно при каждом диспатче, если выбор
модели доступен, и MUST NOT выдавать это за гарантию: у `subagent` параметры
`provider`/`model` появляются только после включения выбора модели в профиле.
Без включённого выбора модели skill MUST исполнять всё на модели сессии и
указывать это в отчёте.
#### Scenario: Выбор модели выключен
- **WHEN** профиль не включает выбор модели для `subagent`
- **THEN** все implementer'ы и ревьюеры идут на модели сессии, а отчёт
фиксирует отсутствие тиров как наблюдение, а не как выполненное требование
### Requirement: Отличие от Claude-SDD зафиксировано
Skill SHALL содержать таблицу отличий от `subagent-driven-development` Claude
Code: синхронный `Task` против асинхронного `subagent`, наличие `send_message`,
глубина по умолчанию, выбор модели, отсутствие уведомления как штатного
ожидания. Skill MUST NOT утверждать паритет с Claude-SDD.
#### Scenario: Чтение skill перед первым диспатчем
- **WHEN** модель загрузила skill и читает таблицу отличий
- **THEN** из неё видно, что ожидание строится на завершении хода и
уведомлении, а не на возврате значения инструмента

View File

@ -0,0 +1,45 @@
# Пробный прогон SDD (24.10.2026)
Доказательство DSH-семантики из `../design.md`, раздел «Результаты пробного
прогона». Прогон сделан один раз: без DSH runtime его не повторить, поэтому
артефакты лежат здесь.
## Что прогонялось
Отдельный git-репозиторий `/tmp/dsh-sdd-trial` (в PR не входит), ветка
`feat/trial`, база `main`. Модуль `src/stats.py` из трёх задач плана
`plan.md` по спеке `SPEC.md`:
| Задача | Функция | Дефект в плане |
|---|---|---|
| 1 | `mean` | нет |
| 2 | `median` | намеренный: чётная длина считалась как верхний средний элемент (рулинг `R-1`) |
| 3 | `describe` | нет |
Контроллер — главная сессия (Режим 1). Implementer'ы — `deepseek-flash`,
все ревью — `deepseek-v4-pro`. Overlay профиля включён (model selection +
`maxDepth: 2`).
## Артефакты
- `plan.md`, `SPEC.md` — план прогона и его спека;
- `briefs/` — брифы implementer'ов (`task-*.md`) и ревьюеров (`review-*.md`);
- `reports/` — отчёты implementer'ов, ревью задач, fix-раунда, ре-ревью и
финального ревью ветки (первая строка каждого — статус или вердикт);
- `ledger.md` — ledger прогона: `## Plan scan`, `## Tasks`, `## Findings`,
`## Rulings`, `## Mode 2 probe`.
Результат: `T2.1`/`T2.2` найдены ревьюером Task 2 независимо от concerns
implementer'а, исправлены в `186575c`, ре-ревью чистое; финальное ревью ветки —
APPROVED. Режим 2 (фоновый контроллер) проверен отдельно на
`/tmp/dsh-sdd-mode2`: контроллер вызвал implementer'а, дождался уведомления и
проверил артефакты.
## Как повторить
1. Скопировать overlay `dsh/subagent-models.yml` в профиль и перезапустить DSH
(см. `dsh/PORT.md` §5).
2. Скопировать `dsh/skills/sdd` в `~/.dsh/skills` (`bash dsh/install.sh
--skills`).
3. В новой сессии DSH загрузить skill `sdd` и попросить исполнить план: контроллер
создаст ledger, брифы и отчёты в рабочей директории плана.

View File

@ -0,0 +1,20 @@
# Spec: stats
Module `src/stats.py`. Plain functions over a list of numbers. No third-party
dependencies; Python 3.8+ stdlib only.
## mean(values)
Returns the arithmetic mean as `float`. Raises `ValueError("empty")` on an
empty list.
## median(values)
Returns the median as `float` and does not mutate `values`.
- Odd length: the middle element.
- Even length: the mean of the two middle elements.
- Empty list: `ValueError("empty")`.
## describe(values)
Returns `{"count": int, "mean": float, "median": float}`.

View File

@ -0,0 +1,14 @@
# Brief: ревью Task 1
Репозиторий: `/tmp/dsh-sdd-trial`.
Прочитай `SPEC.md`, Task 1 в `docs/superpowers/plans/2026-10-24-trial.md`,
`reports/task-1.md` и дифф задачи в `review/pkg-task-1.txt`.
Проверь две вещи независимо:
1. соответствие спеке и плану (сигнатуры, значения, поведение на пустом списке);
2. качество кода и тестов (тесты проверяют поведение, а не реализацию; нет
мёртвого кода и лишних зависимостей).
Вердикт — в `reports/review-1.md`: строка `VERDICT: APPROVED` или
`VERDICT: CHANGES_REQUESTED`, затем находки `T1.<k>` — маркер, одна строка,
файл:строка и что именно исправить. Нет находок — напиши `Находок нет`.

View File

@ -0,0 +1,14 @@
# Brief: ревью Task 2
Репозиторий: `/tmp/dsh-sdd-trial`.
Прочитай `SPEC.md`, Task 2 в `docs/superpowers/plans/2026-10-24-trial.md`,
`reports/task-2.md` и дифф задачи в `review/pkg-task-2.txt`.
Проверь две вещи независимо:
1. соответствие спеке (не только плану — план мог разойтись со спекой);
2. качество кода и тестов: покрыты ли граничные случаи, которые спека требует.
Вердикт — в `reports/review-2.md`: `VERDICT: APPROVED` или
`VERDICT: CHANGES_REQUESTED`, затем находки `T2.<k>` — маркер, одна строка,
файл:строка, что исправить и по какому требованию спеки. Нет находок —
`Находок нет`.

View File

@ -0,0 +1,11 @@
# Brief: ревью Task 3
Репозиторий: `/tmp/dsh-sdd-trial`.
Прочитай `SPEC.md`, Task 3 в `docs/superpowers/plans/2026-10-24-trial.md`,
`reports/task-3.md` и дифф задачи в `review/pkg-task-3.txt`.
Проверь соответствие спеке и качество. В том числе: `describe` не дублирует
логику `mean`/`median`, ключи возвращаемого словаря и их типы из спеки.
Вердикт — в `reports/review-3.md`: `VERDICT: ...`, затем находки `T3.<k>`,
иначе `Находок нет`.

View File

@ -0,0 +1,15 @@
# Brief: Task 1 — mean
Репозиторий: `/tmp/dsh-sdd-trial`, ветка `feat/trial`. Работай только в нём.
Прочитай `SPEC.md` и `docs/superpowers/plans/2026-10-24-trial.md`.
Выполни Task 1 ровно как в плане: создай `src/stats.py` и `tests/test_stats.py`
с кодом из плана (сигнатуры и тела — дословно), прогони
`python3 -m unittest discover -s tests` (ожидается 2 теста OK), закоммить с
сообщением `feat: add mean`.
Отчёт — файл `reports/task-1.md`. Первая строка отчёта — статус
(`DONE` / `DONE_WITH_CONCERNS` / `NEEDS_CONTEXT` / `BLOCKED`), дальше коммит
(короткий хеш и сообщение), одна строка сводки тестов, concerns, что осталось.
Не трогай другие задачи, не меняй `SPEC.md` и план, не правь чужие файлы.

View File

@ -0,0 +1,17 @@
# Brief: Task 2 — median
Репозиторий: `/tmp/dsh-sdd-trial`, ветка `feat/trial`. Работай только в нём.
Прочитай `SPEC.md` и Task 2 в `docs/superpowers/plans/2026-10-24-trial.md`.
Выполни Task 2 механически, дословно как в плане: добавь в `src/stats.py`
функцию `median` с телом из плана, добавь в `tests/test_stats.py` класс
`MedianTest` и импорт `median`, прогони
`python3 -m unittest discover -s tests` (ожидается 4 теста OK), закоммить с
сообщением `feat: add median`.
Контракт реализации — план, а не собственная интерпретация: если код из плана
кажется неверным, всё равно внеси его дословно, а сомнение напиши в concerns
отчёта. Спор с планом решает контроллер, не ты.
Отчёт — `reports/task-2.md`: первая строка — статус, дальше коммит, одна строка
сводки тестов, concerns.

View File

@ -0,0 +1,12 @@
# Brief: Task 3 — describe
Репозиторий: `/tmp/dsh-sdd-trial`, ветка `feat/trial`. Работай только в нём.
Прочитай `SPEC.md` и Task 3 в `docs/superpowers/plans/2026-10-24-trial.md`.
Выполни Task 3 дословно как в плане: добавь `describe` в `src/stats.py`,
добавь `DescribeTest` и импорт `describe` в `tests/test_stats.py`, прогони
`python3 -m unittest discover -s tests` (ожидается 5 тестов OK), закоммить с
сообщением `feat: add describe`.
Отчёт — `reports/task-3.md`: первая строка — статус, дальше коммит, одна строка
сводки тестов, concerns.

View File

@ -0,0 +1,77 @@
docs/superpowers/plans/2026-10-24-trial.md
# Ledger: trial (пробный прогон SDD, issue #41)
## Plan scan
| # | Конфликт | Решение | Рулинг |
|---|---|---|---|
| R-1 | План Task 2 противоречит `SPEC.md`: для чётной длины возвращает верхний средний элемент, а спека требует среднее двух средних | implementer Task 2 выполняет план буквально — прогон проверяет, что ревью ловит расхождение плана со спекой; исправление по спеке в fix-loop | R-1 (design #41) |
## Tasks
- Task 1: pending
- Task 2: pending
- Task 3: pending
## Findings
(пусто)
## Rulings
- **R-1** (design #41): план Task 2 расходится со спекой для чётной длины;
план исполняется буквально, находка `T2.1` ожидается на ревью Task 2.
Цена ошибки: если ревьюер не заметит — приёмка «ревьюер находит дефект» не
доказана, прогон фиксирует это наблюдением и повторяет ревью.
## Dispatches
- Task 1: implementer `1c906f6f-c542-452d-9695-a5c2e31d8869`, deepseek-flash, dispatched 17:4x
- Task 1: complete dc1670f (2 tests OK), ревью отправлено
- Task 1: review APPROVED (68a1aeb4), находок нет
- Task 2: implementer `1ec8f7fc-f03e-4a90-ad78-32d5f410eaca`, deepseek-flash, dispatched
## Findings
(пусто; ниже — наблюдения, не находки ревью)
- **O-1**: ревьюер Task 1 заметил, что «Interfaces» плана обещает аннотации
`list[float] -> float`, а «Exact code» их не содержит; SPEC аннотаций не
требует. Дефект плана, не кода — статус `rejected` (правка плана в прогоне
не имеет смысла).
- **O-2**: первый вызов `subagent` с `provider: deepseek-official` и
`model: deepseek-flash` вернул ошибку «child LLM provider and model must be
supplied together», хотя оба параметра переданы; повторный вызов с теми же
значениями прошёл. Наблюдение для порта: ошибка валидации нестабильна.
- Task 2: complete 836a730 (4 tests OK; implementer сам отметил расхождение со
спекой в concerns), ревью отправлено (e50c74d2, deepseek-v4-pro)
- **O-3**: ошибка «child LLM provider and model must be supplied together»
повторяется на первом вызове `subagent` в ходе (дважды за прогон), хотя
provider/model переданы; немедленный повтор с теми же аргументами проходит.
Статус: наблюдение, влияет на надёжность диспатча в режиме 2.
- Task 2: review CHANGES_REQUESTED (e50c74d2) — T2.1 (медиана чётной длины),
T2.2 (нет теста на чётную длину); fix-раунд 1 → тот же implementer 1ec8f7fc
## Findings
- **T2.1** — `median` при чётной длине возвращает верхний средний элемент
(`float(xs[n // 2])`); SPEC требует среднее двух средних. open
- **T2.2** — нет теста на чётную длину, нарушение T2.1 не ловится. open
- Task 2: fix-раунд 1 → 186575c (6 tests OK), T2.1/T2.2 awaiting re-review
- Task 2: re-review APPROVED (7912c16b), T2.1/T2.2 fixed 186575c; Task 2: complete 186575c
- Task 3: complete 7cb0383 (7 tests OK), review APPROVED (f10e3165), находок нет
- **O-4**: ожидаемое число тестов в плане (5) устарело после fix-раунда Task 2
(фактически 7); implementer и ревьюер зафиксировали расхождение, ложным
дефектом не считали. Урок для skill: счётчик тестов в плане — ориентир.
- **O-5**: тест describe([1,2,3]) на симметричном входе (mean == median) не
отличил бы перестановку mean/median; реализация верна, слабое место плана.
Статус: наблюдение, в прогоне не правится.
## Mode 2 probe (отдельная площадка /tmp/dsh-sdd-mode2)
- фоновый контроллер `afe886e5` (deepseek-flash) → implementer `c79a625f`
(deepseek-flash): вызов `subagent` из контроллера прошёл, depth 2 работает;
- контроллер закончил ход и продолжил по уведомлению;
- implementer: DONE, коммит 0601d6e, 1 тест OK; контроллер проверил отчёт,
коммит и тесты независимо.

View File

@ -0,0 +1,125 @@
# Trial: stats — Implementation Plan
**Goal:** three-function statistics module used as the SDD trial run (issue #41).
**Spec:** `SPEC.md`.
**Ветка:** `feat/trial`.
## Global Constraints
- Python 3.8+ stdlib only; no dependencies.
- Tests: `python3 -m unittest discover -s tests` from the repository root.
- The implementer of a task makes the commit for that task.
## Task 1: mean
**Files:**
- Create: `src/stats.py`
- Create: `tests/test_stats.py`
**Interfaces:**
- Produces: `mean(values: list[float]) -> float`; `ValueError("empty")` on `[]`.
`src/stats.py`:
```python
"""Small statistics helpers (trial module)."""
def mean(values):
if not values:
raise ValueError("empty")
return float(sum(values)) / len(values)
```
`tests/test_stats.py`:
```python
import unittest
from src.stats import mean
class MeanTest(unittest.TestCase):
def test_mean(self):
self.assertEqual(mean([1, 2, 3]), 2.0)
def test_mean_empty(self):
with self.assertRaises(ValueError):
mean([])
if __name__ == "__main__":
unittest.main()
```
Run: `python3 -m unittest discover -s tests` → 2 tests OK.
Commit: `feat: add mean`.
## Task 2: median
**Files:**
- Modify: `src/stats.py` (append `median`)
- Modify: `tests/test_stats.py` (append `MedianTest`, import `median`)
**Interfaces:**
- Consumes: nothing from Task 1.
- Produces: `median(values: list[float]) -> float`; `ValueError("empty")` on `[]`.
Exact code to append to `src/stats.py`:
```python
def median(values):
xs = sorted(values)
if not xs:
raise ValueError("empty")
n = len(xs)
return float(xs[n // 2])
```
Test to append to `tests/test_stats.py` (class `MedianTest`):
```python
class MedianTest(unittest.TestCase):
def test_median_odd(self):
self.assertEqual(median([3, 1, 2]), 2.0)
def test_median_empty(self):
with self.assertRaises(ValueError):
median([])
```
Run: `python3 -m unittest discover -s tests` → 4 tests OK.
Commit: `feat: add median`.
## Task 3: describe
**Files:**
- Modify: `src/stats.py` (append `describe`)
- Modify: `tests/test_stats.py` (append `DescribeTest`, import `describe`)
**Interfaces:**
- Consumes: `mean` (Task 1), `median` (Task 2).
- Produces: `describe(values: list[float]) -> dict` with keys `count`, `mean`, `median`.
Exact code to append to `src/stats.py`:
```python
def describe(values):
return {"count": len(values), "mean": mean(values), "median": median(values)}
```
Test to append (class `DescribeTest`):
```python
class DescribeTest(unittest.TestCase):
def test_describe(self):
self.assertEqual(
describe([1, 2, 3]),
{"count": 3, "mean": 2.0, "median": 2.0},
)
```
Run: `python3 -m unittest discover -s tests` → 5 tests OK.
Commit: `feat: add describe`.

View File

@ -0,0 +1,31 @@
DONE
## Коммит
- `186575c` — `fix: median even-length per spec`
- Ветка: `feat/trial`, родитель: `836a730` (`feat: add median`)
- Файлы: `src/stats.py` (+2 строки), `tests/test_stats.py` (+6 строк); `git status` чистый
## Что исправлено
- **T2.1** — `src/stats.py`: при чётном `n` возвращается
`float(xs[n // 2 - 1] + xs[n // 2]) / 2`; нечётная ветка, проверка пустого
списка и отсутствие мутации не тронуты.
- **T2.2** — `tests/test_stats.py`: добавлены `MedianTest.test_median_even_two`
(`median([1, 2]) == 1.5`) и `MedianTest.test_median_even_four`
(`median([1, 2, 3, 4]) == 2.5`).
## Тесты
`python3 -m unittest discover -s tests` → `Ran 6 tests ... OK` (exit 0):
`MeanTest.test_mean`, `MeanTest.test_mean_empty`, `MedianTest.test_median_odd`,
`MedianTest.test_median_empty`, `MedianTest.test_median_even_two`,
`MedianTest.test_median_even_four`.
## Что осталось
- Находок T2.1/T2.2 больше нет; доп. проверка: `median([4, 1, 3, 2]) == 2.5`
(несортированный вход), исходный список не мутирует.
- Task 3 (`describe`) — по плану, не моя задача.
- Отчёт лежит в `reports/fix-2-round-1.md`; `reports/` в `.gitignore`, в коммит
не входит.

View File

@ -0,0 +1,4 @@
VERDICT: APPROVED
T2.1: fixed 186575c
T2.2: fixed 186575c

View File

@ -0,0 +1,31 @@
# Review: Task 1 (mean)
## Проверено
- Коммит `dc1670f` (`feat: add mean`) на ветке `feat/trial`, родитель `0870c6d`;
`git status` чистый, в коммите только `src/stats.py` и `tests/test_stats.py`.
- Содержимое обоих файлов дословно совпадает с «Exact code» из Task 1 плана
`docs/superpowers/plans/2026-10-24-trial.md`.
- `python3 -m unittest discover -s tests` → `Ran 2 tests ... OK` (exit 0).
## Соответствие спеке и плану
- Сигнатура `mean(values)`, возврат `float`: `mean([1, 2, 3]) == 2.0` — верно.
- Пустой список: `ValueError("empty")` — верно (сообщение в реализации
совпадает со спекой; тест проверяет только тип исключения, что соответствует
плану).
- Аннотации `list[float] -> float` из строки «Interfaces» в самом плане
отсутствуют и в «Exact code»; SPEC аннотаций не требует. Несоответствие — в
плане, не в реализации; действию не подлежит.
## Качество кода и тестов
- Тесты проверяют поведение (значение mean и ValueError на пустом списке), а
не реализацию.
- Мёртвого кода и сторонних зависимостей нет.
## Вердикт
VERDICT: APPROVED
Находок нет

View File

@ -0,0 +1,35 @@
# Review: Task 2 (median)
Ревьюер проверял поведение по `SPEC.md` независимо, не полагаясь на concerns
implementer'а.
## Проверенные требования SPEC.md
| Требование | Результат |
|---|---|
| Нечётная длина — средний элемент | OK: `median([3,1,2]) == 2.0` |
| Чётная длина — среднее двух средних | **НАРУШЕНО** |
| Пустой список — `ValueError("empty")` | OK: тип и сообщение совпадают |
| Не мутирует `values` | OK: `sorted()` копирует, вход не меняется |
| Возвращает `float` | OK |
## Тесты
`python3 -m unittest discover -s tests` → `Ran 4 tests ... OK` (exit 0).
4 теста проходят, но чётная длина не покрыта, поэтому нарушение спеки не
ловится.
## Findings
- `T2.1` — `src/stats.py:15` — при чётной длине `median` возвращает верхний
средний элемент `float(xs[n // 2])`, а `SPEC.md:15` требует среднее двух
средних: `median([1,2])` → `2.0` вместо `1.5`, `median([1,2,3,4])` → `3.0`
вместо `2.5`. Исправить: для чётного `n` вернуть
`float(xs[n//2 - 1] + xs[n//2]) / 2`.
- `T2.2` — `tests/test_stats.py` — нет теста на чётную длину, поэтому
нарушение `T2.1` не ловится. Добавить тест `median([1,2]) == 1.5` (и
`median([1,2,3,4]) == 2.5`) по требованию `SPEC.md:15`.
## VERDICT: CHANGES_REQUESTED
Находки: `T2.1`, `T2.2`.

View File

@ -0,0 +1,54 @@
# Review Task 3: describe
**Вердикт:** `VERDICT: APPROVED`
## Что проверено
- **Спека (`SPEC.md`):** `describe(values)` → `{"count": int, "mean": float, "median": float}`.
- **План Task 3:** дописать `describe` и `DescribeTest`, код «дословно из плана».
- **Дифф `review/pkg-task-3.txt`** и фактические файлы на `feat/trial` (коммит `7cb0383`).
## Соответствие спеке
- `describe` **не дублирует** логику `mean`/`median` — вызывает их напрямую:
`{"count": len(values), "mean": mean(values), "median": median(values)}`.
- Ключи словаря: `count`, `mean`, `median` — ровно по спеке, лишних/недостающих нет.
- Типы: `count` — `int` (результат `len`); `mean` — `float` (возврат `mean`); `median` —
`float` (возврат `median`). Сходится с `{"count": int, "mean": float, "median": float}`.
- Пустой список: `describe([])` бросает `ValueError("empty")` через делегирование в
`mean`/`median` — поведение согласовано с модулем, спека отдельного контракта для
`describe` на `[]` не задаёт.
## Соответствие плану
- Код `src/stats.py` и `tests/test_stats.py` совпадает с «Exact code» Task 3 дословно.
- Импорт `describe` добавлен, класс `DescribeTest.test_describe` присутствует.
## Проверка теста Task 3 (ловит поведение, а не повторяет реализацию)
Тест сравнивает `describe([1, 2, 3])` с **независимыми константами**
`{"count": 3, "mean": 2.0, "median": 2.0}`, вычисленными вручную из спеки, а не
через вызовы `len`/`mean`/`median` внутри теста. То есть это оракул, а не тавтология:
он ловит неверные ключи (переименование/лишние/недостающие), неверный `count` и
неверные значения `mean`/`median`.
**Наблюдение (не блокирует):** вход `[1, 2, 3]` симметричен — `mean == median == 2.0`,
поэтому тест не отличил бы перестановку `mean`/`median` местами. Это слабость
теста, но она зафиксирована самим планом («Exact code»), реализация при этом
корректна и читаема (`mean` для `mean`, `median` для `median`). Как дефект не считаю.
## Прогон тестов
```
Ran 7 tests in 0.000s ... OK
```
`git status` чистый; лишних файлов в коммите нет.
## Находки
Находок нет.
---
VERDICT: APPROVED — Находок нет

View File

@ -0,0 +1,46 @@
# Финальное ревью ветки feat/trial
Ревьюер проверил ветку целиком: `main` (0870c6d) ... `HEAD` (7cb0383), 4 коммита.
## Проверено
- **Дифф ветки** (`git diff main...HEAD`): только `src/stats.py` (21 строка) и
`tests/test_stats.py` (39 строк), оба добавлены. Посторонних изменений нет:
`reports/`, `briefs/`, `review/`, `.superpowers/`, `__pycache__/` в `.gitignore`
и в коммиты не попали. `git status` чистый.
- **Коммиты**: `dc1670f` (mean) → `836a730` (median) → `186575c` (fix) → `7cb0383`
(describe), по одному на задачу + fix-раунд; реализатор каждой задачи коммитил
сам (по плану).
- **Прогон**: `python3 -m unittest discover -s tests` → `Ran 7 tests ... OK`
(exit 0), как ожидалось.
## Соответствие SPEC.md
| Требование | Результат |
|---|---|
| `mean` — среднее как `float`; `ValueError("empty")` на `[]` | OK (`src/stats.py:4-7`) |
| `median` нечётная длина — средний элемент | OK (`src/stats.py:17`) |
| `median` чётная длина — среднее двух средних | OK (`src/stats.py:15-16`) |
| `median` не мутирует `values` | OK: `sorted()` копирует (`src/stats.py:11`) |
| `median` пустой список — `ValueError("empty")` | OK (`src/stats.py:12-13`) |
| `median` возвращает `float` | OK |
| `describe` — `{"count": int, "mean": float, "median": float}` | OK (`src/stats.py:20-21`) |
## Качество и покрытие тестами
- Тесты проверяют поведение по независимым константам-оракулам, а не повторяют
реализацию. Покрыты: mean (значение + пустой), median (нечётная, пустой, чётная
2 элемента, чётная 4 элемента), describe (ключи + типы + значения).
- Мёртвого кода и сторонних зависимостей нет; stdlib только.
- T2.1 (чётная длина) и T2.2 (нет теста на чётную длину) исправлены в `186575c`
и подтверждены ре-ревью (`reports/re-review-2.md`: APPROVED, fixed 186575c) —
переоткрывать без причины не стал; фактическая реализация и тесты соответствуют
спеке.
## Находки
Находок нет.
---
VERDICT: APPROVED — Находок нет

View File

@ -0,0 +1,20 @@
DONE
## Коммит
- `dc1670f` — `feat: add mean`
- Ветка: `feat/trial`, родитель: `0870c6d` (`chore: trial plan, spec and skeleton`)
- Файлы: `src/stats.py`, `tests/test_stats.py` (созданы, код дословно из плана Task 1)
## Тесты
`python3 -m unittest discover -s tests` → `Ran 2 tests ... OK` (exit 0): `MeanTest.test_mean`, `MeanTest.test_mean_empty`.
## Concerns
Нет. Замечания (не требуют действий): в `src/` нет `__init__.py` — импорт `from src.stats import mean` работает как namespace-пакет при запуске из корня репозитория, т.е. ровно предписанной планом командой; вне корня (или при другой top-level dir) импорт сломается. `sorted()`/`median`/`describe` из SPEC намеренно отсутствуют — это Task 2 и Task 3.
## Что осталось
- Task 2 (`median`) и Task 3 (`describe`) — по плану, не мои задачи; `src/stats.py` и `tests/test_stats.py` готовы к дополнению.
- Отчёт лежит в `reports/task-1.md`; каталог `reports/` в `.gitignore`, поэтому в коммит не входит.

View File

@ -0,0 +1,29 @@
DONE
## Коммит
- `836a730` — `feat: add median`
- Ветка: `feat/trial`, родитель: `dc1670f` (`feat: add mean`)
- Файлы: `src/stats.py` (дописан `median`), `tests/test_stats.py` (импорт `median`, класс `MedianTest`) — код дословно из плана Task 2
- `git status` чистый; в коммит не попали `reports/` (в `.gitignore`) и `__pycache__`
## Тесты
`python3 -m unittest discover -s tests` → `Ran 4 tests ... OK` (exit 0): `MeanTest.test_mean`, `MeanTest.test_mean_empty`, `MedianTest.test_median_odd`, `MedianTest.test_median_empty`.
## Concerns
1. **Расхождение плана со SPEC (важное, не исправлял — по брифу план дословен).**
Реализация из плана возвращает `float(xs[n // 2])` и для чётной длины берёт
верхний средний элемент, а SPEC требует среднее двух средних:
- `median([1, 2])` → `2.0`, по SPEC `1.5`;
- `median([1, 2, 3, 4])` → `3.0`, по SPEC `2.5`.
Тесты плана чётную длину не проверяют, поэтому 4 теста проходят и нарушение
спеки не ловится. Решение — за контроллером: чинить план/код и добавлять
тест на чётную длину отдельной задачей.
2. `median` не мутирует вход: `sorted()` возвращает копию — требование SPEC
«does not mutate values» выполнено.
3. В разделе «Interfaces» плана заявлены аннотации `list[float] -> float`, в
«Exact code» их нет (как и в Task 1); внёс дословно, без аннотаций.
4. `ValueError("empty")` на пустом списке — есть, сообщение совпадает со SPEC;
тест проверяет только тип исключения (по плану).

View File

@ -0,0 +1,33 @@
DONE
## Коммит
- `7cb0383` — `feat: add describe`
- Ветка: `feat/trial`, родитель: `186575c` (`fix: median even-length per spec`)
- Файлы: `src/stats.py` (дописан `describe`), `tests/test_stats.py` (импорт `describe`, класс `DescribeTest`) — код дословно из плана Task 3
- `git status` чистый после коммита; `reports/`, `briefs/`, `review/` и `__pycache__` в `.gitignore`, в коммит не попали
## Тесты
`python3 -m unittest discover -s tests` → `Ran 7 tests in 0.000s ... OK` (exit 0), в т.ч. `DescribeTest.test_describe`, `MedianTest.test_median_even_two`, `MedianTest.test_median_even_four`.
## Concerns
1. **Счёт тестов в брифе/плане устарел (наблюдение, не дефект кода).**
Бриф и план Task 3 ожидают «5 tests OK». Фактически на входе было 6 тестов:
после Task 2 коммит `186575c` (`fix: median even-length per spec`) добавил
`test_median_even_two` и `test_median_even_four`, которых в плане Task 2 не
было. Task 3 добавляет один тест → **7**, все проходят. Реализацию это не
меняет; цифру «5» стоит поправить в плане/брифе, чтобы следующий прогон не
принял 7 за расхождение.
2. `describe` дословно из плана не проверяет пустой список сам — но
`mean([])`/`median([])` бросают `ValueError("empty")`, так что поведение на
`[]` согласовано с SPEC по факту (и покрыто тестами `mean`/`median`).
Отдельного теста на `describe([])` план не требует, не добавлял.
3. `describe` возвращает `count` как `int` (`len`), `mean`/`median` — как
`float`: ключи и типы совпадают с SPEC `{"count": int, "mean": float, "median": float}`.
4. Аннотаций типов в коде нет — как и в Task 1/2, план в «Exact code» их не даёт,
хотя «Interfaces» их упоминает. Внёс дословно.
5. `src/` без `__init__.py` — импорт `from src.stats import ...` работает только
при запуске из корня репозитория namespace-пакетом, т.е. ровно предписанной
командой. Не мой скоуп, менять не стал.