Merge pull request 'chore(openspec): архив change dsh-prestep (#43)' (#80) from chore/archive-dsh-prestep into main
This commit is contained in:
commit
9074eff33e
271
openspec/specs/session-prestep/spec.md
Normal file
271
openspec/specs/session-prestep/spec.md
Normal file
@ -0,0 +1,271 @@
|
||||
# session-prestep Specification
|
||||
|
||||
## Purpose
|
||||
Две неблокирующие инъекции шага `agent/pre-step` в DSH-бандле: `harness-check`
|
||||
(проект не подключён к обвязке → список отсутствующего и шаги настройки) и
|
||||
`role-hint` (первое пользовательское сообщение шага подсказывает роль или задаёт
|
||||
один вопрос с четырьмя вариантами). В Claude Code это SessionStart-хуки
|
||||
`harness-check.sh` и `role-hint.sh`; в DSH слоя shell-хуков нет, поэтому та же
|
||||
логика живёт в бандле и использует общую с bootstrap машинерию маркера и
|
||||
видимости. Обе инъекции — текстовые, неблокирующие и отключаются конфигом.
|
||||
Здесь же едут follow-ups #62 того же цикла: изоляция PATH в негативном тесте,
|
||||
WARN смешанного HOME/DSH_HOME и обязательный TDD-порядок в `sdd`-скилле.
|
||||
Тексты собираются модулем `dsh/bundle/lib/prestep.js` (чистые функции: только
|
||||
fs-чтения, без `ctx`-сервисов и без `spawn`), а решает об инъекции
|
||||
`dsh/bundle/index.js`.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Инъекция harness-check
|
||||
|
||||
Инъекция SHALL проверять подключение проекта к обвязке и MUST оставаться
|
||||
неблокирующей: при неподключённом проекте шаг продолжается, к сообщениям шага
|
||||
добавляется одно пользовательское сообщение с текстом подсказки.
|
||||
|
||||
Корень проекта MUST определяться upward-walk от рабочей директории сессии до
|
||||
ближайшего каталога с `.git` (каталог или файл — worktree-чекаут тоже корень);
|
||||
при отсутствии `.git` до корня ФС проект считается вне git и инъекции нет.
|
||||
Сигналы подключения MUST быть ровно три и проверяться в этом порядке:
|
||||
`openspec/` (каталог), `.codegraph/` (каталог), секция `## OpenSpec и
|
||||
superpowers` в `CLAUDE.md` (строка заголовка второго уровня). Все сигналы на
|
||||
месте — инъекция MUST NOT появляться; часть отсутствует — инъекция SHALL
|
||||
перечислять отсутствующие в этом порядке.
|
||||
|
||||
Текст инъекции MUST быть: `[dsh-harness] Проект <root> не подключён к обвязке:
|
||||
нет: <список отсутствующего через '; '>. Предложи владельцу настройку:
|
||||
openspec init --tools agents, codegraph init и секцию «OpenSpec и superpowers» в
|
||||
CLAUDE.md.` В тексте MUST NOT быть Claude Code-артефактов: ни путей `~/.claude`,
|
||||
ни `adopt-project.sh`, ни `.claude/code-paths` (D-9).
|
||||
|
||||
Инъекция SHALL управляться конфигом `harnessCheck` (по умолчанию `true`) вместе
|
||||
с общим `enabled` бандла и MUST требовать только наличия `ctx.on`
|
||||
(`typeof ctx.on === 'function'`, D-5), а не успеха self-check `bootstrap`: при
|
||||
`bootstrap: false` harness-check обязан работать. Реализация MUST использовать
|
||||
fs-чтения (`stat`/`readFile`) и MUST NOT запускать `git`.
|
||||
|
||||
#### Scenario: Проект не подключён
|
||||
|
||||
- **WHEN** сессия стартует в git-репозитории, где нет `openspec/` и секции в
|
||||
`CLAUDE.md`, а `.codegraph/` есть
|
||||
- **THEN** в сообщения шага добавляется `[dsh-harness] Проект <root> не
|
||||
подключён к обвязке: нет: openspec/; секция «OpenSpec и superpowers» в
|
||||
CLAUDE.md.` и готовые команды настройки
|
||||
|
||||
#### Scenario: Проект подключён
|
||||
|
||||
- **WHEN** в корне проекта есть `openspec/`, `.codegraph/` и секция `## OpenSpec
|
||||
и superpowers` в `CLAUDE.md`
|
||||
- **THEN** harness-check молчит, сообщений не добавляется
|
||||
|
||||
#### Scenario: Вне git-репозитория
|
||||
|
||||
- **WHEN** рабочая директория сессии не имеет `.git` ни в одном родительском
|
||||
каталоге до корня ФС
|
||||
- **THEN** инъекции нет: harness-check не подсказывает настройку вне проекта
|
||||
|
||||
#### Scenario: Рабочая директория в подкаталоге проекта
|
||||
|
||||
- **WHEN** сессия стартует в `<repo>/dsh/bundle`, а сигналы лежат в `<repo>`
|
||||
- **THEN** корнем считается `<repo>`, и текст инъекции называет `<repo>`
|
||||
|
||||
#### Scenario: Отключённый bootstrap не выключает harness-check
|
||||
|
||||
- **WHEN** профиль задаёт `bootstrap: false` и `harnessCheck: true`
|
||||
- **THEN** harness-check по-прежнему инъецирует подсказку: гейт — `ctx.on`, а не
|
||||
`bootstrapOk`
|
||||
|
||||
#### Scenario: Конфиг выключает инъекцию
|
||||
|
||||
- **WHEN** профиль задаёт `harnessCheck: false`
|
||||
- **THEN** harness-check не инъецируется, остальные pre-step инъекции не
|
||||
затрагиваются
|
||||
|
||||
### Requirement: Инъекция role-hint
|
||||
|
||||
Инъекция SHALL выбирать роль по первому пользовательскому сообщению шага; она
|
||||
MUST NOT блокировать шаг и MUST NOT утверждать роль как факт.
|
||||
|
||||
Триггер MUST быть таким: в сообщениях текущего шага есть сообщение с
|
||||
`source.kind === 'user'`, чей текст непуст и не является собственной инъекцией
|
||||
обвязки. Собственной инъекцией MUST считаться текст, начинающийся с
|
||||
`<!-- DSH-HARNESS-`, либо содержащий `[dsh-harness]`, либо содержащий
|
||||
`[claude-harness]` (legacy-префикс до обновления install.sh, D-10). Фильтр MUST
|
||||
применяться до выбора роли: иначе текст вопроса сам содержит все ключевики.
|
||||
Сообщений без нового пользовательского текста (в т.ч. автономный раунд) —
|
||||
инъекции нет.
|
||||
|
||||
Ключевики SHALL проверяться по нижнему регистру, в порядке приоритета
|
||||
admin → qa → analyst → dev; побеждает первый совпавший класс:
|
||||
|
||||
- admin: `jira`, `гант`, `спринт`;
|
||||
- qa: `стенд`, `прогон`, `скриншот`, `макет`;
|
||||
- analyst: `тз`, `oq`, `спека`, `методика`;
|
||||
- dev: `срез`, `issue`, `pr`, `тест`, `миграция`.
|
||||
|
||||
Совпадение MUST быть подстрокой вхождения целиком, кроме `pr`, который MUST
|
||||
матчиться только отдельным словом (`\bpr\b`): `PR`, `pr.` — совпадение,
|
||||
`preview`, `пр` — нет. Семантика подстрок заявлена явно (T1.2): словоформы,
|
||||
меняющие основу, ключевик не содержат и MUST NOT матчиться — «миграцию»,
|
||||
«миграции», «спеку» остаются без совпадения, тогда как формы, содержащие
|
||||
ключевик целиком («теста», «issue-трекер», «ТЗ-документ»), матчатся.
|
||||
|
||||
При совпадении инъекция MUST быть: `[dsh-harness] Роль сессии: <r>. Вызови
|
||||
skill role. Если не так — скажи.` Без совпадения инъекция MUST быть вопросом:
|
||||
`[dsh-harness] Роль сессии неясна. Вызови skill role: dev — код, issue, PR,
|
||||
тест, миграция; admin — Jira, Гант, спринт; analyst — ТЗ, OQ, спека, методика;
|
||||
qa — стенд, прогон, скриншот, макет. Задай владельцу один вопрос с этими
|
||||
четырьмя вариантами.`
|
||||
|
||||
Инъекция SHALL управляться конфигом `roleHint` (по умолчанию `true`) и, как
|
||||
harness-check, MUST требовать только `ctx.on` (D-5), независимо от `bootstrap`.
|
||||
|
||||
#### Scenario: Первое сообщение с ключевиком
|
||||
|
||||
- **WHEN** первое пользовательское сообщение шага — «Напиши тест для модуля»
|
||||
- **THEN** добавляется `[dsh-harness] Роль сессии: dev. Вызови skill role. Если
|
||||
не так — скажи.`
|
||||
|
||||
#### Scenario: Приоритет классов ролей
|
||||
|
||||
- **WHEN** сообщение содержит и `jira`, и `спринт`, и `тест`
|
||||
- **THEN** подсказка называет admin: admin выигрывает у qa, analyst и dev
|
||||
|
||||
#### Scenario: Бесключевое сообщение
|
||||
|
||||
- **WHEN** первое пользовательское сообщение шага не содержит ни одного
|
||||
ключевика
|
||||
- **THEN** добавляется вопрос с четырьмя вариантами ролей
|
||||
|
||||
#### Scenario: Своя инъекция не считается сообщением
|
||||
|
||||
- **WHEN** единственное пользовательское сообщение шага дословно равно вопросу
|
||||
role-hint
|
||||
- **THEN** инъекции нет: фильтр собственных сообщений срабатывает до выбора роли
|
||||
|
||||
#### Scenario: Legacy-префикс Claude Code
|
||||
|
||||
- **WHEN** единственное пользовательское сообщение начинается с
|
||||
`[claude-harness]`
|
||||
- **THEN** роль по нему не выбирается и инъекции нет
|
||||
|
||||
#### Scenario: Шаг без нового пользовательского сообщения
|
||||
|
||||
- **WHEN** в сообщениях шага нет пользовательского сообщения с непустым текстом
|
||||
- **THEN** role-hint молчит
|
||||
|
||||
#### Scenario: Флексия ключевика не матчится (T1.2)
|
||||
|
||||
- **WHEN** первое сообщение шага — «Сделай миграцию и обнови спеку»
|
||||
- **THEN** ни `миграция`, ни `спека` не совпадают, подсказки роли нет — выдаётся
|
||||
вопрос с четырьмя вариантами; это заявленный контракт подстрок, а не дефект
|
||||
|
||||
#### Scenario: `pr` только отдельным словом
|
||||
|
||||
- **WHEN** первое сообщение содержит `preview`, но не содержит слова `pr`
|
||||
- **THEN** dev не выбирается; `PR` или `pr.` в сообщении дают dev
|
||||
|
||||
### Requirement: Инъекции пропускаются в сабагентах
|
||||
|
||||
Обе инъекции MUST пропускаться на делегированном шаге: шаг сабагента не должен
|
||||
получать ни harness-check, ни role-hint. Делегирование SHALL определяться по
|
||||
реальным сигналам рантайма: `agent.session.header.origin === 'subagent'`, либо
|
||||
`agent.session.header.delegationDepth > 0`, либо
|
||||
`agent.options.subagentDepth > 0` (T2.1). Форк сессии MUST NOT считаться
|
||||
сабагентом: форк (в т.ч. `parentSession` без `origin`) получает все инъекции
|
||||
(T2.3).
|
||||
|
||||
#### Scenario: Делегированный шаг
|
||||
|
||||
- **WHEN** pre-step выполняется для агента с `origin: 'subagent'` (или с
|
||||
положительной глубиной делегирования)
|
||||
- **THEN** ни harness-check, ни role-hint не инъецируются, даже если проект не
|
||||
подключён, а сообщение содержит ключевик
|
||||
|
||||
#### Scenario: Форк сессии получает инъекции
|
||||
|
||||
- **WHEN** pre-step выполняется для форка (`parentSession` без `origin`)
|
||||
- **THEN** пропуска нет: инъекции работают как в главной сессии
|
||||
|
||||
### Requirement: Повторная инъекция и видимость
|
||||
|
||||
Инъекции SHALL дедуплицироваться по маркеру
|
||||
`<!-- DSH-HARNESS-PRESTEP:<16-hex> -->`, который строится из хэша текста
|
||||
инъекции и стоит первой строкой тела; одна и та же видимая инъекция MUST NOT
|
||||
добавляться дважды. Видимость SHALL проверяться общим хелпером
|
||||
`isInjectionVisible(agent, messages, marker)`: сначала сообщения текущего шага,
|
||||
затем durable-поверхность `session.surface.nodes` + `eventAt(seq)`; после
|
||||
компакции маркер исчезает с поверхности и инъекция повторяется (D-6).
|
||||
|
||||
Дедупликация MUST быть по содержимому (маркеру), а не «ровно один раз за
|
||||
сессию» (T2.2). Фактическое поведение, которое и заявляется контрактом:
|
||||
|
||||
- роль, определённая на другом шаге иначе, даёт другой текст и другой маркер —
|
||||
повторная подсказка роли возможна;
|
||||
- если на более позднем шаге первый пользовательский текст бесключевой,
|
||||
role-hint может выдать вопрос с четырьмя вариантами после уже выданной
|
||||
подсказки роли: это следствие per-text дедупа, а не дефект (D-11);
|
||||
- harness-check при неизменном состоянии проекта даёт тот же текст и тот же
|
||||
маркер — повтор возможен только после компакции или изменения набора
|
||||
отсутствующих сигналов.
|
||||
|
||||
#### Scenario: Та же инъекция не дублируется
|
||||
|
||||
- **WHEN** тот же текст инъекции уже виден в сообщениях шага или на поверхности
|
||||
сессии
|
||||
- **THEN** вторая копия не добавляется
|
||||
|
||||
#### Scenario: Другой текст даёт вторую инъекцию (T2.2)
|
||||
|
||||
- **WHEN** подсказка роли уже выдана на первом шаге, а на следующем шаге первое
|
||||
пользовательское сообщение бесключевое
|
||||
- **THEN** добавляется вопрос с четырьмя вариантами: маркер другого текста не
|
||||
виден, и дедуп по содержимому его не подавляет
|
||||
|
||||
#### Scenario: Повтор после компакции
|
||||
|
||||
- **WHEN** маркер инъекции пропал с поверхности сессии после `/compact`
|
||||
- **THEN** следующий шаг добавляет инъекцию заново
|
||||
|
||||
#### Scenario: Смена роли на новом шаге
|
||||
|
||||
- **WHEN** на первом шаге роль была dev, а на следующем сообщение содержит
|
||||
`jira`
|
||||
- **THEN** добавляется подсказка admin: текст и маркер другие
|
||||
|
||||
### Requirement: Follow-ups #62 в этом же change
|
||||
|
||||
Пункты #62 SHALL ехать задачами этого change, а не отдельными фичами (D-4), и
|
||||
каждый MUST быть закрыт тестом или пином текста:
|
||||
|
||||
- T2.1/M-2: негативный тест `install.sh --check` без `openspec` MUST изолировать
|
||||
PATH (каталог-заглушка как весь PATH), а не наследовать host PATH с
|
||||
фиксированным системным префиксом: глобальный npm-префикс на Linux кладёт
|
||||
`openspec` в `/usr/bin`, и тест «бинарника нет» иначе проверяет не то.
|
||||
- O-2: `install.sh` MUST предупреждать (WARN, прогон продолжается) о смешанном
|
||||
состоянии, когда `DSH_HOME` пришёл из окружения, указывает на существующий
|
||||
каталог и не равен `$HOME/.dsh`, при нестандартном HOME: изоляция только HOME
|
||||
пишет в реальный `DSH_HOME`. WARN MUST быть один на прогон и MUST
|
||||
срабатывать и в `--global` (`install_global`), а не только в `--skills`.
|
||||
- T3.4: `dsh/skills/sdd/SKILL.md` MUST содержать порядок TDD: шаг «напиши тест и
|
||||
убедись, что он падает (RED)» обязателен до кода; текст добавляется с нуля и
|
||||
пинится тестом (D-8).
|
||||
|
||||
#### Scenario: Негативный тест `--check` без openspec
|
||||
|
||||
- **WHEN** тест запускает `install.sh --check` с PATH, состоящим только из
|
||||
каталога-заглушки без `openspec`
|
||||
- **THEN** проверка сообщает об отсутствии `openspec`, а host-бинарник не влияет
|
||||
на результат
|
||||
|
||||
#### Scenario: Смешанный HOME и DSH_HOME
|
||||
|
||||
- **WHEN** `--global` запускается с нестандартным HOME и `DSH_HOME`, указывающим
|
||||
на существующий каталог
|
||||
- **THEN** печатается `WARN: DSH_HOME=… both HOME and DSH_HOME must be isolated
|
||||
together`, и установка продолжается
|
||||
|
||||
#### Scenario: TDD-порядок в sdd-скилле
|
||||
|
||||
- **WHEN** модель читает `sdd`-скилл перед первым диспатчем
|
||||
- **THEN** в нём есть требование обязательного падающего теста до кода, и это
|
||||
требование закреплено тестом
|
||||
Loading…
x
Reference in New Issue
Block a user