117 lines
13 KiB
Markdown
117 lines
13 KiB
Markdown
# Design: dsh-prestep — harness-check и role-hint через agent/pre-step
|
||
|
||
Issue: #43 (+#62). Статус: утверждён владельцем 25.10.2026
|
||
(`/approve dsh-prestep design`); реализация — ветка `feat/43-dsh-prestep`,
|
||
статусы решений — в конце файла. Спека-дельта —
|
||
`openspec/changes/dsh-prestep/specs/session-prestep/spec.md`.
|
||
|
||
## Контекст
|
||
|
||
CC-хуки harness-check/role-hint — SessionStart; в DSH аналог — инъекции
|
||
`agent/pre-step` (уже используем для superpowers-bootstrap). Гейты #59
|
||
живые: этот change идёт первым по гейтованному циклу (acceptance #59).
|
||
|
||
## Механика
|
||
|
||
Каждая инъекция — по содержимому и маркеру (per-text dedupe); повторная
|
||
инъекция с другим текстом возможна — см. D-11. Маркер
|
||
`<!-- DSH-HARNESS-PRESTEP:<hash> -->` и тот же механизм видимости, что у
|
||
bootstrap (`isBootstrapVisible` переименован в общий
|
||
`isInjectionVisible(agent, messages, marker)`).
|
||
Порядок в pre-step: другие инъекции → harness-check → role-hint (обе
|
||
неблокирующие; reject/abort — пропуск; сабагенты — пропуск, как
|
||
bootstrap).
|
||
|
||
### harness-check
|
||
|
||
- Сигнал подключения (проектные, без CC-артефактов): upward-walk от
|
||
sessionCwd до корня ФС: ближайший каталог с `.git` (или без него до
|
||
упора) — «корень проекта»; сигналы: `openspec/`, `.codegraph/`,
|
||
секция `## OpenSpec и superpowers` в `CLAUDE.md`. Всё на месте —
|
||
молчим. Вне git-репозитория (`.git` не найден до корня) — пропуск.
|
||
Чего-то нет — неблокирующая инъекция:
|
||
«[dsh-harness] Проект <root> не подключён к обвязке: нет: … .
|
||
Предложи владельцу настройку: `openspec init --tools agents`,
|
||
`codegraph init` и секцию «OpenSpec и superpowers» в CLAUDE.md.»
|
||
(DSH-нативный текст: без `~/.claude`-путей и adopt-скрипта CC — их
|
||
порт под DSH — отдельная фича, D-9). HARNESS_AUTO_ADOPT — вне скоупа
|
||
(D-2).
|
||
- Реализация — fs-чтения (stat/readFile) по путям, без spawn git.
|
||
|
||
### role-hint
|
||
|
||
- Триггер: **новое** пользовательское сообщение текущего pre-step
|
||
(`decision.messages`), source.kind === 'user', И текст не начинается с
|
||
`<!-- DSH-HARNESS-` и не содержит `[dsh-harness]` (фильтр собственных
|
||
инъекций — bootstrap/harness-check не считаются «первым сообщением»).
|
||
Нет такого — инъекции нет (в т.ч. автономные раунды без нового
|
||
сообщения).
|
||
- Ключевые слова (точно как в AGENTS.md; builder-check нашёл опечатку —
|
||
«грант» убран):
|
||
- admin: `jira`, `гант`, `спринт`;
|
||
- qa: `стенд`, `прогон`, `скриншот`, `макет`;
|
||
- analyst: `тз`, `oq`, `спека`, `методика`;
|
||
- dev: `срез`, `issue`, `pr`, `тест`, `миграция`.
|
||
Совпадение — инъекция `[dsh-harness] Роль сессии: <r>. Вызови
|
||
skill role. Если не так — скажи.` Нет — инъекция вопроса (текст сочинён
|
||
здесь, в репо его не было): «Роль сессии неясна. Вызови skill role:
|
||
dev — код, issue, PR, тест, миграция; admin — Jira, Гант, спринт;
|
||
analyst — ТЗ, OQ, спека, методика; qa — стенд, прогон, скриншот, макет.
|
||
Задай владельцу один вопрос с этими четырьмя вариантами.»
|
||
- Матчинг: нижний регистр; `pr` — только отдельным словом; остальные —
|
||
подстроки.
|
||
|
||
## Decisions made without discussion
|
||
|
||
| # | Решение | Обоснование | Цена ошибки |
|
||
|---|---|---|---|
|
||
| D-1 | Роль определяется по первому сообщению (не по заголовку — его в DSH нет) | Ровно то правило AGENTS.md, которое модель сейчас исполняет «по памяти»; переносим в технику | Ложная подсказка роли при неоднозначном сообщении — поправима (`/role`), не блокирует |
|
||
| D-2 | harness-check — только подсказка, без HARNESS_AUTO_ADOPT | YAGNI; авто-подключение — отдельная фича при запросе | Сессия в неподключённом проекте продолжает работу — как и сейчас |
|
||
| D-3 | Общий хелпер инъекций (рефакторинг bootstrap) без изменения поведения bootstrap | Два новых pre-step не должны дублировать маркер/дедуп | Регрессия bootstrap — страхуется существующими тестами bootstrap |
|
||
| D-4 | #62-пункты едут задачами этого change (не отдельными фичами) | Мелкие, один PR-цикл дешевле трёх | Смешивание тем в одном PR — компенсируется отдельными задачами плана |
|
||
| D-5 | Конфиг-ключи harnessCheck/roleHint — по умолчанию true; гейт — прямое `typeof ctx.on === 'function'` (не bootstrapOk: selfcheck пропускает запись bootstrap при config.bootstrap=false, а инъекциям нужен ctx.on независимо от bootstrap) | Симметрия с bootstrap; корректность при выключенном bootstrap | Нет |
|
||
| D-6 | Маркер инъекций — `<!-- DSH-HARNESS-PRESTEP:<hash> -->` (формат bootstrap, не `<--`); `isBootstrapVisible` → общий `isInjectionVisible` (переименование трогает imports bootstrap.test.mjs — учтено в плане) | Одна машинерия видимости на все инъекции | Регрессия bootstrap — страхуется его тестами |
|
||
| D-7 | Текст вопроса с четырьмя вариантами сочинён в этом дизайне (в репо его не было) | Builder-check подтвердил отсутствие | Нет |
|
||
| D-8 | T3.4: в sdd-скилле предложения про TDD-порядок нет — текст добавляется с нуля (пин в тесте), а не «усиливается» | Факт builder-check | Нет |
|
||
| D-9 | DSH-автономность (замечание владельца 25.10): в текстах и сигналах дизайна нет CC-артефактов — ни `~/.claude`-путей, ни `adopt-project.sh`, ни `.claude/code-paths`; CC-хуки harness-check.sh/role-hint.sh живут только для CC-сессий; DSH-порт adopt-скрипта — отдельная фича (non-goal этого change) | Порт должен жить самостоятельно; CC-сторона — параллельный мир | Подключение проекта в DSH остаётся ручным до порта adopt-скрипта — инъекция даёт точный список шагов |
|
||
| D-10 | Префикс DSH-инъекций — `[dsh-harness]` (не `[claude-harness]`); фильтр собственных сообщений — по нему и по `<!-- DSH-HARNESS-` | Имя бандла — dsh-harness; старый префикс — след CC-происхождения | Установленный AGENTS.md пока содержит `[claude-harness]` — фильтр учитывает оба префикса до обновления install.sh |
|
||
| D-11 | Дедуп инъекций — по содержимому (маркер = хэш текста), а не «ровно раз за сессию»: role-hint на более позднем шаге с бесключевым первым сообщением может добавить вопрос с четырьмя вариантами после уже выданной подсказки роли | Это фактическое поведение общей машинерии bootstrap (маркер + `isInjectionVisible`), переиспользованной без изменения поведения (D-3); «раз за сессию» в разделе «Механика» было неточной формулировкой (T2.2) | Лишний вопрос-подсказка в сессии — не блокирует и правится моделью; глобальный session-флаг потребовал бы нового состояния и не переживал бы компакцию |
|
||
|
||
## Тесты (идеи для плана)
|
||
|
||
- pure: detectAdoption(files), detectRole(firstUserText) → role|null,
|
||
buildRoleHintText(role), buildHarnessCheckText(), маркер-дедуп;
|
||
- wiring: apply с fake ctx — инъекции появляются при наличии
|
||
пользовательского сообщения, один раз, пропускаются для сабагентов;
|
||
- T2.1: негативный тест openspec — PATH-изоляция (временный каталог без
|
||
openspec-бинарника);
|
||
- O-2: install.sh WARN при DSH_HOME→существующий каталог + нестандартный
|
||
HOME (тест в install.test.mjs);
|
||
- T3.4: пин текста в sdd-скилле («шаг „тест падает“ обязателен»).
|
||
|
||
## На потом (в план)
|
||
|
||
- Разбивка: Task 1 prestep.js (harness-check+role-hint pure) → Task 2
|
||
wiring+bootstrap-рефакторинг → Task 3 #62 (T2.1/O-2/T3.4) → Task 4
|
||
спека-дельта `session-prestep` + доки.
|
||
|
||
## Статус реализации (25.10.2026, ветка `feat/43-dsh-prestep`)
|
||
|
||
| # | Решение | Статус | Где |
|
||
|---|---|---|---|
|
||
| D-1 | Роль по первому сообщению | реализовано | `lib/prestep.js` `detectRole`; пин в `dsh/tests/prestep.test.mjs` |
|
||
| D-2 | harness-check без HARNESS_AUTO_ADOPT | реализовано | `lib/prestep.js` — только текст, без записи |
|
||
| D-3 | Общий хелпер инъекций, поведение bootstrap не меняется | реализовано (6e0828e, a2453e3) | `lib/bootstrap.js` `isInjectionVisible` (+ алиас `isBootstrapVisible`); тесты bootstrap зелёные |
|
||
| D-4 | #62-пункты — задачи этого change | реализовано (183f8d8, 6800550) | `dsh/tests/openspec.test.mjs`, `dsh/install.sh`, `dsh/skills/sdd/SKILL.md` |
|
||
| D-5 | `harnessCheck`/`roleHint` по умолчанию `true`; гейт — `typeof ctx.on === 'function'` | реализовано (a2453e3) | `index.js` wiring; тест «bootstrap:false оставляет harness-check» |
|
||
| D-6 | Маркер `<!-- DSH-HARNESS-PRESTEP:<hash> -->` + общий `isInjectionVisible` | реализовано (a2453e3) | `lib/prestep.js` `makePrestepMarker`, `lib/bootstrap.js` |
|
||
| D-7 | Текст вопроса с четырьмя вариантами | реализовано | `buildRoleHintText(null)`; пины в тестах |
|
||
| D-8 | TDD-текст в `sdd`-скилле добавлен с нуля | реализовано (183f8d8) | `dsh/skills/sdd/SKILL.md`, «Порядок TDD» |
|
||
| D-9 | DSH-автономность: без `~/.claude`-путей и adopt-скрипта | реализовано | тексты `lib/prestep.js` |
|
||
| D-10 | Префикс `[dsh-harness]`, фильтр обоих префиксов | реализовано | `isHarnessMessage` |
|
||
| D-11 | Дедуп по содержимому, не «ровно раз за сессию» | реализовано как факт (a2453e3) | заявлено в спеке `session-prestep`, требование «Повторная инъекция и видимость» |
|
||
|
||
Статусы `T1.2`, `T2.2`, `T1.3` и `T1.5` (оба из #77) — в спеке-дельте
|
||
`session-prestep` и `openspec/specs/approval-gates/spec.md`; ledger —
|
||
`.superpowers/sdd/2026-10-25-dsh-prestep/progress.md`.
|