DSH: SDD — skill, overlay, пробный прогон (#41) #50

Merged
aep merged 4 commits from feat/41-dsh-sdd-spike into main 2026-10-24 16:55:43 +03:00
23 changed files with 746 additions and 38 deletions
Showing only changes of commit c8b92fae01 - Show all commits

View File

@ -47,7 +47,7 @@
- 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: ничего из других задач.
- [ ] **Step 1: Написать падающий тест** `dsh/tests/subagent-models.test.mjs`
- [x] **Step 1: Написать падающий тест** `dsh/tests/subagent-models.test.mjs`
```js
import assert from 'node:assert/strict'
@ -91,36 +91,36 @@ test('install.sh --check reports the overlay and the profile depth', () => {
})
```
- [ ] **Step 2: Прогнать тест и убедиться, что он падает**
- [x] **Step 2: Прогнать тест и убедиться, что он падает**
Run: `node --test dsh/tests/subagent-models.test.mjs`
Expected: FAIL — `dsh/subagent-models.yml is missing`.
- [ ] **Step 3: Создать `dsh/subagent-models.yml`**
- [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, значения — дословно.
- [ ] **Step 4: Добавить проверки в `dsh/install.sh`**
- [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 опционален).
- [ ] **Step 5: Обновить `dsh/PORT.md`**
- [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`».
- [ ] **Step 6: Прогнать тесты**
- [x] **Step 6: Прогнать тесты**
Run: `node --test dsh/tests/subagent-models.test.mjs`
Expected: PASS (2 tests).
- [ ] **Step 7: Прогнать оба набора**
- [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).
- [ ] **Step 8: Коммит**
- [x] **Step 8: Коммит**
```bash
git add dsh/subagent-models.yml dsh/tests/subagent-models.test.mjs dsh/install.sh dsh/PORT.md
@ -141,7 +141,7 @@ git commit -m "feat(dsh): overlay профиля — model selection и maxDepth
- 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 (ссылка в разделе про глубину и модели).
- [ ] **Step 1: Написать падающий тест** `dsh/tests/sdd-skill.test.mjs`
- [x] **Step 1: Написать падающий тест** `dsh/tests/sdd-skill.test.mjs`
Проверки (каждая — отдельный `test`):
1. frontmatter: `/^---\nname: sdd\n/`, `description: .+`;
@ -157,27 +157,27 @@ git commit -m "feat(dsh): overlay профиля — model selection и maxDepth
Run: `node --test dsh/tests/sdd-skill.test.mjs` → FAIL (файла нет).
- [ ] **Step 2: Написать `dsh/skills/sdd/SKILL.md`**
- [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` — расширение, в первой версии не включать).
- [ ] **Step 3: Прогнать тест**
- [x] **Step 3: Прогнать тест**
Run: `node --test dsh/tests/sdd-skill.test.mjs` → PASS.
- [ ] **Step 4: Обновить `dsh/tests/install.test.mjs`**
- [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`.
- [ ] **Step 5: Обновить `dsh/README.md`**
- [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.
- [ ] **Step 6: Прогнать всё**
- [x] **Step 6: Прогнать всё**
Run: `bash dsh/tests/run.sh && bash tests/run.sh` → зелёные.
- [ ] **Step 7: Коммит**
- [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
@ -199,39 +199,39 @@ git commit -m "feat(dsh): skill sdd — асинхронный протокол
- Consumes: skill `sdd` из Task 2, overlay из Task 1 (в профиле уже включён владельцем).
- Produces: транскрипт прогона (диспатчи, статусы, отчёты, ledger) в `design.md`; вывод о режимах 1 и 2.
- [ ] **Step 1: Создать temp-репозиторий и план**
- [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`, включая «чётная длина — среднее двух средних».
- [ ] **Step 2: Ledger прогона**
- [x] **Step 2: Ledger прогона**
`.superpowers/sdd/trial-plan/progress.md`: первая строка — путь плана; таблица сканирования плана (конфликт: план Task 2 противоречит `SPEC.md` — рулинг `R-1`: следовать `SPEC.md`, дефект фиксировать находкой `T2.1` при ревью, а не править план до диспатча); пустой `## Findings`; три строки `Task N: pending`.
- [ ] **Step 3: Диспатч implementer'а Task 1 (режим 1)**
- [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` зелёный, коммит. После вызова — закончить ход; продолжить по уведомлению.
- [ ] **Step 4: Собрать отчёт Task 1 и отправить на ревью**
- [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`.
- [ ] **Step 5: Task 2 — диспатч, ревью, fix-loop**
- [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 «ревьюер находит дефект» не доказана) и повторить ревью с явным указанием проверять требование «чётная длина».
- [ ] **Step 6: Task 3 и финальное ревью ветки**
- [x] **Step 6: Task 3 и финальное ревью ветки**
Task 3 — flash, по плану; ревью — v4-pro; финальное ревью всей ветки — `subagent` v4-pro по полному диффу `main...HEAD`, находки `C-n`/`I-n`/`M-n`; один fix-диспатч при находках, одно скоуп-ревью.
- [ ] **Step 7: Проба режима 2 (глубина 2)**
- [x] **Step 7: Проба режима 2 (глубина 2)**
Отдельный temp-репозиторий или новая ветка в том же: `subagent` (flash) с промптом «ты контроллер, задиспатчь implementer'а на задачу X, дождись уведомления, верни отчёт»; контроллер обязан сам вызвать `subagent` и завершиться. Записать: работает / падает `exceeds maxDepth` / уведомление не пришло. При падении — находка `O-n` и вывод, что режим 2 требует поднятой глубины (overlay уже в профиле).
- [ ] **Step 8: Записать результаты**
- [x] **Step 8: Записать результаты**
`openspec/changes/dsh-sdd/design.md` — раздел «Результаты пробного прогона 24.10.2026»: таблица приёмки #41 (пункт → чем доказан → статус), реальные диспатчи с моделями, найденный дефект `T2.1` с коммитом фикса, ledger дословно, вывод по режимам, наблюдения `O-n` со статусами. `dsh/PORT.md` §8 п. 4 — закрыт.
- [ ] **Step 9: Проверки, коммит, PR**
- [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.

View File

@ -1,10 +1,10 @@
# DSH port — design and spike notes
Status: **spike 0.2.0, live guard/require-plan/bootstrap verified; `/role`
command works, the model-invocable `role` skill is added but its live
acceptance is pending**. This directory is the DeepSeek Harness adapter for
`claude-harness`; the Claude Code installation and the repository's source of
truth are unchanged.
Status: **spike 0.2.1, live guard/require-plan/bootstrap verified; `/role`
command works, the model-invocable `role` skill and the asynchronous SDD
protocol are verified live (three-task trial run)**. This directory is the
DeepSeek Harness adapter for `claude-harness`; the Claude Code installation and
the repository's source of truth are unchanged.
## 1. Verdict
@ -60,10 +60,10 @@ file has to be installed as `$DSH_HOME/AGENTS.md` because DSH does not read
covers most of `role-hint`.
- **`briefing`**: needs an agent preset with read-only tools and a model route;
not part of the guard/bootstrap spike.
- **SDD**: the Claude `Task` is synchronous, while DSH `subagent` returns a
child id and reports later. This needs a semantic rewrite (async controller
with `send_message` + `maxDepth: 2`, or a `workflow` script). Do not claim SDD
parity from this spike.
- **SDD parity**: the Claude `Task` is synchronous, while DSH `subagent` returns
a child id and reports later. The `sdd` skill implements the asynchronous
analogue (report files, notification as the wait, `send_message` for fix
rounds, ledger as memory); there is no parity and the skill says so.
- **Codegraph by default**: `dsh/mcp.codegraph.yml` is an opt-in overlay; the
bundle does not assume the binary is installed.
- **`harness-doctor`**: its scripts inspect `~/.claude`; a DSH variant is a
@ -318,10 +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: 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 in
a temporary repository. See
`openspec/changes/dsh-sdd/design.md`.
4. SDD — **done** (change `dsh-sdd`): the `sdd` skill (async controller,
reports, review, fix-loop, ledger) plus the overlay in §5; proven by a
three-task trial run (`openspec/changes/dsh-sdd/trial/`, results in
`design.md`).
5. Enable CodeGraph MCP by default when the binary is present; update role and
project instructions to `mcp__codegraph__codegraph_explore`.
6. Port `harness-doctor` checks to DSH paths.

View File

@ -53,6 +53,8 @@ overlay ребёнок наследует модель сессии.
в контекст.
5. `NEEDS_CONTEXT` — ответить в тот же `send_message`; `BLOCKED` — рулинг
контроллера, а не остановка сессии.
6. Замеченное расхождение с брифом или спекой — это `DONE_WITH_CONCERNS`, даже
если задача формально выполнена: ревью его проверит, контроллер — решит.
## Ожидание без блокировки
@ -86,6 +88,10 @@ overlay ребёнок наследует модель сессии.
Находка без статуса в отчёте контроллера — ошибка контроллера.
Ожидаемое число тестов в плане — ориентир, а не контракт: fix-раунд добавляет
тесты, поэтому в отчёте фиксируется фактическое число, а расхождение вверх
объясняется, а не считается дефектом.
## Финальное ревью ветки
После всех задач — один ревьюер на весь дифф `main...HEAD`: находки `C-n`
@ -120,7 +126,9 @@ ledger лежит в git-ignored scratch (после `git clean -fdx` восст
Маршрут указывается в каждом диспатче: `provider`, `model`,
`reasoning_effort`. Параметры действуют только при включённом overlay
(`dsh/subagent-models.yml`); без него всё идёт на модели сессии, и отчёт
фиксирует это наблюдением `O-n`.
фиксирует это наблюдением `O-n`. Если `subagent` вернул «child LLM provider
and model must be supplied together», хотя оба параметра переданы, — повтори
вызов один раз: валидация в 0.2.1-rc.3 нестабильна.
## Отличие от Claude-SDD

View File

@ -137,6 +137,73 @@ DoD порта требует, чтобы CC-харнес не менялся.
| 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-профиль

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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