Merge pull request 'DSH: SDD — skill, overlay, пробный прогон (#41)' (#50) from feat/41-dsh-sdd-spike into main
This commit is contained in:
commit
303b7f08aa
241
docs/superpowers/plans/2026-10-24-dsh-sdd.md
Normal file
241
docs/superpowers/plans/2026-10-24-dsh-sdd.md
Normal 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.
|
||||
55
dsh/PORT.md
55
dsh/PORT.md
@ -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.
|
||||
|
||||
@ -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/`.
|
||||
|
||||
@ -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
150
dsh/skills/sdd/SKILL.md
Normal 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
32
dsh/subagent-models.yml
Normal 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
|
||||
@ -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)
|
||||
})
|
||||
39
dsh/tests/subagent-models.test.mjs
Normal file
39
dsh/tests/subagent-models.test.mjs
Normal 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)
|
||||
}
|
||||
})
|
||||
2
openspec/changes/dsh-sdd/.openspec.yaml
Normal file
2
openspec/changes/dsh-sdd/.openspec.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-24
|
||||
226
openspec/changes/dsh-sdd/design.md
Normal file
226
openspec/changes/dsh-sdd/design.md
Normal 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).
|
||||
71
openspec/changes/dsh-sdd/proposal.md
Normal file
71
openspec/changes/dsh-sdd/proposal.md
Normal 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
|
||||
указано явно.
|
||||
105
openspec/changes/dsh-sdd/specs/dsh-sdd/spec.md
Normal file
105
openspec/changes/dsh-sdd/specs/dsh-sdd/spec.md
Normal 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** из неё видно, что ожидание строится на завершении хода и
|
||||
уведомлении, а не на возврате значения инструмента
|
||||
45
openspec/changes/dsh-sdd/trial/README.md
Normal file
45
openspec/changes/dsh-sdd/trial/README.md
Normal 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, брифы и отчёты в рабочей директории плана.
|
||||
20
openspec/changes/dsh-sdd/trial/SPEC.md
Normal file
20
openspec/changes/dsh-sdd/trial/SPEC.md
Normal 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}`.
|
||||
14
openspec/changes/dsh-sdd/trial/briefs/review-1.md
Normal file
14
openspec/changes/dsh-sdd/trial/briefs/review-1.md
Normal 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>` — маркер, одна строка,
|
||||
файл:строка и что именно исправить. Нет находок — напиши `Находок нет`.
|
||||
14
openspec/changes/dsh-sdd/trial/briefs/review-2.md
Normal file
14
openspec/changes/dsh-sdd/trial/briefs/review-2.md
Normal 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>` — маркер, одна строка,
|
||||
файл:строка, что исправить и по какому требованию спеки. Нет находок —
|
||||
`Находок нет`.
|
||||
11
openspec/changes/dsh-sdd/trial/briefs/review-3.md
Normal file
11
openspec/changes/dsh-sdd/trial/briefs/review-3.md
Normal 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>`,
|
||||
иначе `Находок нет`.
|
||||
15
openspec/changes/dsh-sdd/trial/briefs/task-1.md
Normal file
15
openspec/changes/dsh-sdd/trial/briefs/task-1.md
Normal 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` и план, не правь чужие файлы.
|
||||
17
openspec/changes/dsh-sdd/trial/briefs/task-2.md
Normal file
17
openspec/changes/dsh-sdd/trial/briefs/task-2.md
Normal 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.
|
||||
12
openspec/changes/dsh-sdd/trial/briefs/task-3.md
Normal file
12
openspec/changes/dsh-sdd/trial/briefs/task-3.md
Normal 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.
|
||||
77
openspec/changes/dsh-sdd/trial/ledger.md
Normal file
77
openspec/changes/dsh-sdd/trial/ledger.md
Normal 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; контроллер проверил отчёт,
|
||||
коммит и тесты независимо.
|
||||
125
openspec/changes/dsh-sdd/trial/plan.md
Normal file
125
openspec/changes/dsh-sdd/trial/plan.md
Normal 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`.
|
||||
31
openspec/changes/dsh-sdd/trial/reports/fix-2-round-1.md
Normal file
31
openspec/changes/dsh-sdd/trial/reports/fix-2-round-1.md
Normal 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`, в коммит
|
||||
не входит.
|
||||
4
openspec/changes/dsh-sdd/trial/reports/re-review-2.md
Normal file
4
openspec/changes/dsh-sdd/trial/reports/re-review-2.md
Normal file
@ -0,0 +1,4 @@
|
||||
VERDICT: APPROVED
|
||||
|
||||
T2.1: fixed 186575c
|
||||
T2.2: fixed 186575c
|
||||
31
openspec/changes/dsh-sdd/trial/reports/review-1.md
Normal file
31
openspec/changes/dsh-sdd/trial/reports/review-1.md
Normal 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
|
||||
|
||||
Находок нет
|
||||
35
openspec/changes/dsh-sdd/trial/reports/review-2.md
Normal file
35
openspec/changes/dsh-sdd/trial/reports/review-2.md
Normal 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`.
|
||||
54
openspec/changes/dsh-sdd/trial/reports/review-3.md
Normal file
54
openspec/changes/dsh-sdd/trial/reports/review-3.md
Normal 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 — Находок нет
|
||||
46
openspec/changes/dsh-sdd/trial/reports/review-final.md
Normal file
46
openspec/changes/dsh-sdd/trial/reports/review-final.md
Normal 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 — Находок нет
|
||||
20
openspec/changes/dsh-sdd/trial/reports/task-1.md
Normal file
20
openspec/changes/dsh-sdd/trial/reports/task-1.md
Normal 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`, поэтому в коммит не входит.
|
||||
29
openspec/changes/dsh-sdd/trial/reports/task-2.md
Normal file
29
openspec/changes/dsh-sdd/trial/reports/task-2.md
Normal 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;
|
||||
тест проверяет только тип исключения (по плану).
|
||||
33
openspec/changes/dsh-sdd/trial/reports/task-3.md
Normal file
33
openspec/changes/dsh-sdd/trial/reports/task-3.md
Normal 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-пакетом, т.е. ровно предписанной
|
||||
командой. Не мой скоуп, менять не стал.
|
||||
Loading…
x
Reference in New Issue
Block a user