diff --git a/dsh/PORT.md b/dsh/PORT.md index e89c821..3222d30 100644 --- a/dsh/PORT.md +++ b/dsh/PORT.md @@ -2,7 +2,9 @@ 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 +protocol are verified live (three-task trial run); the pre-step +`harness-check`/`role-hint` injections are implemented and tested but not yet +verified live — they need the reinstalled bundle**. This directory is the DeepSeek Harness adapter for `claude-harness`; the Claude Code installation and the repository's source of truth are unchanged. @@ -16,6 +18,7 @@ This is a port, not a copy. Three layers map differently: | Skills | `~/.claude/skills`, `.claude/skills` | `~/.dsh/skills`, `.agents/skills`, `customSkillDirs` | 1:1 format | | Roles | `roles/`, `/role` skill | same files + `/role` command + model-invocable `role` skill | close | | Hard rules | `PreToolUse` shell hooks | `ctx.tools.guard` Cordis guard + approval gates A/B/C | logic reused, wrapper new | +| Session-start checks | `harness-check.sh`, `role-hint.sh` | `agent/pre-step` injections `harness-check`, `role-hint` | checks ported, texts new | | Superpowers | plugin + `SessionStart` hook | pinned sync of tag `v7.0.1` (`DSH_SUPERPOWERS_REF`) into `~/.dsh/skills` + `agent/pre-step` bootstrap | content adapted by sync, bootstrap new | | SDD | `Task` (synchronous) | `subagent` (asynchronous) / `workflow` | semantics differ | | Gitea | CLI + skills | `bash` + skills | 1:1 | @@ -61,11 +64,22 @@ file has to be installed as `$DSH_HOME/AGENTS.md` because DSH does not read проекта, а копия старела бы молча. Слэш-команд нет — только скиллы. Установка в проект — `bash dsh/install.sh --openspec`; проверка — `--check`. +6. **`harness-check` and `role-hint`** through `agent/pre-step` (change + `dsh-prestep`, capability `session-prestep`). `harness-check` walks up to the + nearest `.git` and looks for `openspec/`, `.codegraph/`, and the + `## OpenSpec и superpowers` section in `CLAUDE.md`; a missing signal produces + one non-blocking `[dsh-harness]` message with the setup steps. `role-hint` + reads the first non-harness user message of the step and injects either + `Роль сессии: ` or one question with the four variants. Both are text-only + (`lib/prestep.js` uses fs reads, no spawn), skipped for subagents, gated by + `harnessCheck` / `roleHint` and by the same marker/visibility machinery as the + bootstrap. The Claude Code `SessionStart` scripts are not run under DSH. + ## 3. What is deliberately deferred -- **`harness-check` and `role-hint`**: non-blocking SessionStart hooks. They can - become `agent/pre-step` injections later; the first message role rule already - covers most of `role-hint`. +- **CC `SessionStart` scripts**: `hooks/harness-check.sh` and + `hooks/role-hint.sh` stay for Claude Code sessions; DSH runs the ported checks + itself (§2 item 6), so the scripts and `settings.hooks.json` are not reused. - **`briefing`**: needs an agent preset with read-only tools and a model route; not part of the guard/bootstrap spike. - **SDD parity**: the Claude `Task` is synchronous, while DSH `subagent` returns @@ -107,6 +121,7 @@ dsh/ command-role.js # /role command glue (DSH imports live only here) lib/guards.js # Claude-hook JSON adapter, fail-open lib/bootstrap.js # skill resolution, marker, dedupe, compaction + lib/prestep.js # harness-check/role-hint detection and texts (pure) lib/role.js # role parsing and text building (pure) dsh-tools.md # superpowers tool mapping hooks -> ../../hooks # local-install symlink to the tested bash/Python hooks @@ -153,7 +168,7 @@ agent/pre-step -> next() (other injections happen first) -> skip if reject/aborted, or if subagent and bootstrapSubagents=false -> prepareBootstrap(): resolve skill, read mapping, hash content, build text - -> isBootstrapVisible(agent, decision.messages, marker)? + -> isInjectionVisible(agent, decision.messages, marker)? yes -> no-op no -> append createUserMessage({ source: {kind:"user"} }) ``` @@ -162,6 +177,28 @@ The marker is an HTML comment at the top of the message. The visible-surface scan uses `agent.session.surface.nodes` + `eventAt(seq)`, the same mechanism the skill catalog uses, so a compacted message is re-injected on the next step. +### Pre-step injection flow (harness-check, role-hint) + +``` +agent/pre-step (after next(), same listener as the bootstrap) + -> harness-check: sessionCwd -> detectAdoption() + no .git up to the fs root -> silent + all signals present -> silent + otherwise -> [dsh-harness] Проект … не подключён … + -> role-hint: first user message of this step that is not a harness injection + no such message -> silent (autonomous round) + keyword match -> [dsh-harness] Роль сессии: … + no match -> the four-variant question + -> skip both for subagents (origin=subagent, delegationDepth>0, subagentDepth>0) + -> dedupe by the PRESTEP marker, which is the hash of the injection text +``` + +The checks are gated by `typeof ctx.on === 'function'` rather than by the +`bootstrap` self-check (D-5), so `bootstrap: false` keeps them alive. The +`harness-check` texts contain no Claude Code paths or adopt script (D-9); the +pre-step prefix is `[dsh-harness]`, and the message filter also accepts the +legacy `[claude-harness]` (D-10). + ## 5. Configuration The bundle row can be configured from the profile patch. Defaults are suitable @@ -178,6 +215,8 @@ for the repository layout: selfCheck: true # activation check of the DSH contract; not-ok is logged checkCommand: true # register the dsh-harness-check command bootstrapSubagents: false + harnessCheck: true # pre-step: unadopted project -> setup steps (non-blocking) + roleHint: true # pre-step: first user message -> role hint or question hooksDir: '' # empty -> bundle/hooks -> ../../hooks rolesDir: '' # empty -> ~/.dsh/roles, legacy ~/.claude/roles, repo roles/ projectRolesDir: '' # empty -> /.claude/roles @@ -401,7 +440,17 @@ profile was removed; the real `desktop` profile was not touched. is still pending. - **Subagent bootstrap.** `isSubagent` probes common fields; if none are present on this version, the bootstrap reaches children too. The skill's - `` makes that harmless but token-expensive. + `` makes that harmless but token-expensive. The pre-step + injections use the same predicate and are skipped for subagents; a fork + (`parentSession` without `origin`) is not a subagent and keeps them (T2.3). +- **Pre-step injection limits.** `harness-check` and `role-hint` are text + injections, not enforcement: nothing stops a model from ignoring them, and they + only exist in a session with the reinstalled bundle (D-13). `role-hint` matches + keywords as substrings, so a different stem («миграцию», «спеку») does not + match. Dedupe is per content (marker = hash of the text), not "exactly once per + session": a later step whose first user message has no keyword adds the + four-variant question even after an earlier role hint (D-11). `harness-check` + never adopts a project — there is no DSH port of `adopt-project.sh` (D-9). - **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. @@ -427,8 +476,10 @@ profile was removed; the real `desktop` profile was not touched. 1. Finish the live smoke test: the fresh-session `role` skill acceptance, plus the bootstrap and `/compact` checks already verified. -2. Add `harness-check` and optional `role-hint` as non-blocking pre-step - injections. +2. `harness-check` and optional `role-hint` as non-blocking pre-step injections — + **done** (change `dsh-prestep`, capability `session-prestep`): implemented and + tested in `dsh/bundle/lib/prestep.js` + `index.js`; live verification waits for + the bundle reinstall. 3. Build `briefing` as an agent preset (read-only tools + model route). 4. SDD — **done** (change `dsh-sdd`): the `sdd` skill (async controller, reports, review, fix-loop, ledger) plus the overlay in §5; proven by a diff --git a/dsh/README.md b/dsh/README.md index 84ea039..0a9aaca 100644 --- a/dsh/README.md +++ b/dsh/README.md @@ -15,6 +15,11 @@ separate Cordis bundle plus a thin install helper. `/approve design|plan`; - superpowers bootstrap — `using-superpowers` is injected once per session from `agent/pre-step` and re-injected after compaction; +- pre-step injections — `harness-check` (a project that is not wired into the + harness is offered the setup steps: `openspec/`, `.codegraph/`, the + `## OpenSpec и superpowers` section in `CLAUDE.md`) and `role-hint` (the first + user message of a step names a role or asks one question with the four + variants); both are non-blocking, text-only, and skipped for subagents; - 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 @@ -31,9 +36,11 @@ separate Cordis bundle plus a thin install helper. and logs every missing service; the `dsh-harness-check` command re-runs the same check on demand. -**Not yet:** `briefing`, the `harness-check` SessionStart hook, `role-hint`, -`harness-doctor`, auto-memory, caveman. See [PORT.md](PORT.md) for the design, -mapping table, and risks. +**Not yet:** `briefing`, `harness-doctor`, auto-memory, caveman. The Claude Code +`SessionStart` hooks themselves stay in `hooks/` for Claude Code sessions; the +DSH port runs the same two checks as pre-step injections (see above) and does +not run the CC scripts. See [PORT.md](PORT.md) for the design, mapping table, +and risks. ## Quick start @@ -119,6 +126,9 @@ step, never a background pull (design D-2, cadence D-7): any code. 6. In a project with `.agents/skills`, ask `what skills do you have?` — the six `openspec-*` skills must be listed. +7. In a repository with `.git` but no `openspec/`, expect one + `[dsh-harness] Проект … не подключён к обвязке` message; a first message like + `Напиши тест для модуля` must be followed by `[dsh-harness] Роль сессии: dev`. The injected bootstrap starts with a marker: @@ -129,6 +139,13 @@ The injected bootstrap starts with a marker: After compaction the marker disappears from `session.surface.nodes` and the next step injects a fresh copy. +The two pre-step injections use the same shape; the hash is of the injection +text, so a different hint is a different marker: + +```text + +``` + ## Tests ```bash @@ -153,6 +170,7 @@ dsh/ lib/guards.js DSH tool call -> claude-harness hook JSON lib/gates.js approval gates A/B/C and approval.md/plan io (pure) lib/bootstrap.js superpowers skill resolution, marker, dedupe + lib/prestep.js harness-check/role-hint detection and texts (pure) lib/role.js role layers and text (pure) lib/selfcheck.js bundle contract self-check (pure) dsh-tools.md superpowers tool mapping @@ -190,6 +208,8 @@ repository layout: selfCheck: true # activation check of the DSH contract; not-ok is logged checkCommand: true # register the dsh-harness-check command bootstrapSubagents: false + harnessCheck: true # pre-step: unadopted project -> setup steps (non-blocking) + roleHint: true # pre-step: first user message -> role hint or question hooksDir: '' # empty -> bundle/hooks -> ../../hooks rolesDir: '' # empty -> ~/.dsh/roles, legacy ~/.claude/roles, repo roles/ projectRolesDir: '' # empty -> /.claude/roles @@ -240,6 +260,22 @@ repository layout: skill and `openspec/changes/dsh-sdd/`. - OpenSpec-скиллы проект получает сам (`openspec init --tools agents` или `--openspec`); обвязка ставит их только в проект, не в `~/.dsh`. +- Pre-step injections (`harness-check`, `role-hint`) are active only in a session + with the reinstalled bundle (D-13): an old install has neither the texts nor the + wiring, and a session in an unadopted project stays silent. +- `role-hint` matches keywords as substrings, so a word form that changes the stem + does not match: «миграцию»/«миграции» do not hit `миграция`, «спеку» does not + hit `спека`. Forms that contain the keyword whole («теста», «issue-трекер») do + match; `pr` matches only as a whole word, so `preview` is not dev. +- Injection dedupe is per content (marker = hash of the text), not "exactly once + per session": a later step whose first user message carries no keyword adds the + four-variant question even after an earlier role hint. Both texts stay in the + session and the model follows the one that fits; this is the declared contract + (D-11), not a defect. +- `harness-check` is a hint, never adoption: it writes nothing, and there is no + DSH port of `adopt-project.sh`; the owner runs `openspec init --tools agents`, + `codegraph init`, and adds the `## OpenSpec и superpowers` section to + `CLAUDE.md`. - The bundle self-checks the DSH contract it depends on at activation: `ctx.tools.guard`, `ctx.on` for `agent/pre-step`, `ctx.inject` for commands, `workingDirectory.get`. Each missing service is logged as diff --git a/dsh/bundle/dsh-tools.md b/dsh/bundle/dsh-tools.md index e786051..c4f011a 100644 --- a/dsh/bundle/dsh-tools.md +++ b/dsh/bundle/dsh-tools.md @@ -21,6 +21,7 @@ On DeepSeek Harness these actions resolve to the tools below. | Load a skill | `skill` | | Present a deliverable | `present` | | Approve a plan | `exit_plan_mode` | +| Request an approval | `request_approval {change_id, kind}` — the model initiates it; the owner clicks Approve/Cancel | | Change directory | `working_directory` | | Create a worktree | `create_worktree` when the bundle is enabled, otherwise `bash` with `git worktree` | @@ -42,5 +43,10 @@ On DeepSeek Harness these actions resolve to the tools below. working directory. The `CLAUDE.md` files of this harness need no rewrite. - **`todo_write` is the task-tracking action.** Older `TodoWrite` references mean this tool. +- **An approval is the owner's click, not the model's claim.** + `request_approval({change_id, kind})` starts the flow: the harness writes the + approval marker only after the owner answers Approve; Cancel writes nothing. + Never treat "I asked" or "the tool was called" as approval. `/approve` in the + Claude Code text is the same idea through a slash command. - **Plan mode is a real mode.** Use `exit_plan_mode` to submit a plan; prose agreement does not leave plan mode. diff --git a/openspec/changes/dsh-prestep/design.md b/openspec/changes/dsh-prestep/design.md index d7c69c8..16ac30e 100644 --- a/openspec/changes/dsh-prestep/design.md +++ b/openspec/changes/dsh-prestep/design.md @@ -1,7 +1,9 @@ # Design: dsh-prestep — harness-check и role-hint через agent/pre-step -Issue: #43 (+#62). Статус: черновик до builder-check; утверждение — -`/approve dsh-prestep design`. +Issue: #43 (+#62). Статус: утверждён владельцем 25.10.2026 +(`/approve dsh-prestep design`); реализация — ветка `feat/43-dsh-prestep`, +статусы решений — в конце файла. Спека-дельта — +`openspec/changes/dsh-prestep/specs/session-prestep/spec.md`. ## Контекст @@ -71,6 +73,7 @@ bootstrap). | 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]`); фильтр собственных сообщений — по нему и по `` + общий `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`. diff --git a/openspec/changes/dsh-prestep/specs/session-prestep/spec.md b/openspec/changes/dsh-prestep/specs/session-prestep/spec.md new file mode 100644 index 0000000..27c88e1 --- /dev/null +++ b/openspec/changes/dsh-prestep/specs/session-prestep/spec.md @@ -0,0 +1,272 @@ +# Spec Delta: session-prestep + +## 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`. + +## ADDED 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] Проект не подключён к обвязке: +нет: <список отсутствующего через `; `>. Предложи владельцу настройку: +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] Проект не + подключён к обвязке: нет: 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** сессия стартует в `/dsh/bundle`, а сигналы лежат в `` +- **THEN** корнем считается ``, и текст инъекции называет `` + +#### 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 считаться текст, начинающийся с +``, который строится из хэша текста +инъекции и стоит первой строкой тела; одна и та же видимая инъекция 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** в нём есть требование обязательного падающего теста до кода, и это + требование закреплено тестом diff --git a/openspec/specs/approval-gates/spec.md b/openspec/specs/approval-gates/spec.md index 9bca38d..84fb5b4 100644 --- a/openspec/specs/approval-gates/spec.md +++ b/openspec/specs/approval-gates/spec.md @@ -61,8 +61,13 @@ MUST отказать с перечислением отсутствующих Подтверждение владельца MUST идти через `ctx.userQuestions.ask` (D-12): вопрос с вариантами Approve и Cancel; без сервиса команда MUST отказать, а не писать. При подтверждении пишутся `by: owner` и `at` в ISO 8601 UTC; при отмене или -неподтверждении — отказ `not confirmed`, файл не меняется. Команда MUST NOT -создавать план и не трогать `proposal.md`, `design.md`, `builder-check.md`. +неподтверждении — отказ `not confirmed`, файл не меняется. Сбой самого диалога +(исключение из `ctx.userQuestions.ask`) MUST давать отказ с текстом ошибки — +`userQuestions failed: `, — а не литерал `not confirmed` (T1.5 #77): +владелец должен видеть причину, по которой вопрос не дошёл, а отмена и сбой +различаются в логе. `approval.md` не меняется ни в одном из этих случаев. Команда +MUST NOT создавать план и не трогать `proposal.md`, `design.md`, +`builder-check.md`. #### Scenario: /approve design без builder-check @@ -93,6 +98,17 @@ MUST отказать с перечислением отсутствующих - **THEN** команда отказывает с `userQuestions service unavailable; /approve requires it` и `approval.md` не пишется +#### Scenario: Сбой диалога вопросов + +- **WHEN** `ctx.userQuestions.ask` завершается исключением +- **THEN** команда отказывает с `userQuestions failed: `, а не с + литералом `not confirmed`, и `approval.md` не меняется + +#### Scenario: Отмена в диалоге + +- **WHEN** владелец ответил Cancel (или вопрос остался без ответа) +- **THEN** команда отказывает с `not confirmed` и `approval.md` не меняется + ### Requirement: Гейт A — план до утверждения дизайна Для write/edit/multi_edit/`str_replace_editor` с целевым путём под