Andrey Epifancev cd5ef4f765 docs(dsh): OpenSpec — проектные скиллы, --openspec, проверка (#42)
Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
2026-10-24 19:35:27 +04:00

377 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DSH port — design and spike notes
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
This is a port, not a copy. Three layers map differently:
| Layer | Claude Code | DeepSeek Harness | Status |
|---|---|---|---|
| Instructions | `~/.claude/CLAUDE.md`, project `CLAUDE.md` | `$DSH_HOME/AGENTS.md`, project `CLAUDE.md`/`AGENTS.md`, nested | almost 1:1 |
| 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 | logic reused, wrapper new |
| Superpowers | plugin + `SessionStart` hook | skills + `agent/pre-step` bootstrap | content 1:1, bootstrap new |
| SDD | `Task` (synchronous) | `subagent` (asynchronous) / `workflow` | semantics differ |
| Gitea | CLI + skills | `bash` + skills | 1:1 |
| OpenSpec | CLI + `.claude/skills` + `/opsx:*` | CLI + проектные `.agents/skills` | close: нет слэш-команд |
| codegraph | MCP | MCP (`mcp__codegraph__*`) | 1:1 with names |
| auto-memory | Claude Code feature | none | dropped/replaced with files |
| install | `install.sh` + `settings.json` | bundle + profile manager | new |
The important discovery: **DSH loads `CLAUDE.md` as a project instruction file**,
so the project templates and nested instructions need no rewrite. The global
file has to be installed as `$DSH_HOME/AGENTS.md` because DSH does not read
`~/.claude/CLAUDE.md`.
## 2. What this spike implements
1. **`guard-main` and `require-plan`** through `ctx.tools.guard`. The adapter
synthesizes the Claude hook payload (`{cwd, tool_name, tool_input}`), runs
the existing `hooks/guard-main.sh` / `hooks/require-plan.sh`, turns `exit 2`
into a denial reason, and fails open on every other error. The Python core
(`hooks/harness_target.py`) and its tests remain the source of truth. A guard
is synchronous and monotonic: it runs after `tools/pre-execute`, and no later
listener can turn the denial back into permission.
2. **Superpowers bootstrap** through `agent/pre-step`. It reads the installed
`using-superpowers/SKILL.md`, strips frontmatter, appends
`bundle/dsh-tools.md`, wraps the result in `<EXTREMELY_IMPORTANT>`, and
injects it once per session. A content-hash marker handles dedupe; scanning
`session.surface.nodes` re-injects after compaction.
3. **Roles** — `/role` is a real command (`ctx.commands.register`); it reads
`~/.dsh/roles/<role>.md`, falls back to `~/.claude/roles/<role>.md`, then to
the repository's `roles/`, and reads the project `.claude/roles/<role>.md`.
It injects the role text as a user message and starts a turn for the
five-line summary. Because a model cannot call a slash command,
`skills/role/SKILL.md` is the model-invocable loader: it infers the role,
reads the file, and tells the model to follow it. Auto-memory is explicitly
dropped; the role's "Читать на старте" section is left to the model.
4. **Display metadata and an optional CodeGraph MCP overlay** so the bundle can
be installed through the Plugins page and codegraph stays available.
5. **OpenSpec** — воркфлоу идут проектными скиллами `.agents/skills/openspec-*`,
которые пишет `openspec init --tools agents`. DSH читает этот каталог нативно;
проверено сабагентом с cwd в проекте — все шесть имён в каталоге скиллов.
Глобальной копии в `~/.dsh/skills` нет: генератор обновляется вместе с CLI
проекта, а копия старела бы молча. Слэш-команд нет — только скиллы.
Установка в проект — `bash dsh/install.sh --openspec`; проверка — `--check`.
## 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`.
- **`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
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
separate task.
- **Auto-memory, caveman**: no direct DSH equivalents; caveman is off by default
anyway.
- **Full installer**: `install.sh` prepares global instructions and skills; the
bundle itself is installed by `plugin_manager` because the Desktop profile is
Electron-managed.
## 4. Architecture
```
dsh/
bundle/
package.json # dsh.bundle.patch, exports, display resources
cordis.patch.yml # inserts the dsh-harness row
index.js # ctx.tools.guard + agent/pre-step + /role wiring
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/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
locale/{en,ru}.json
icon.svg
tests/ # Node tests, no DSH runtime needed
skills/role/SKILL.md # model-invocable role loader (installed to ~/.dsh)
install.sh # --check / --global / --skills / --roles / --all
mcp.codegraph.yml # optional CodeGraph MCP row
```
The bundle imports only Node builtins and files inside this package. It
deliberately does not import `@deepseek-ai/dsh-llm` or
`@deepseek-ai/schemastery`: a local-directory profile install resolves imports
from the profile, not from the DSH installation, so a DSH-internal import fails
at activation with `ERR_MODULE_NOT_FOUND`. `lib/message.js` is a local copy of
the tiny `createUserMessage` constructor, and `Config` was replaced by defaults
normalized in `apply()`. The pure modules are testable with plain Node; this is
why the port's 26 tests run without a DSH process.
### Guard flow
```
model calls write/edit/bash
-> tools/pre-execute (approval, extensible policy)
-> ctx.tools.guard (dsh-harness, synchronous, monotonic)
-> lib/guards.js maps tool + args
-> bash hooks/guard-main.sh (cwd, tool_name, tool_input)
-> python3 hooks/harness_target.py guard-main
-> exit 2 => deny reason (final)
-> exit 0 or any failure => allow
-> require-plan.sh for write/edit
-> execution
```
The guard returns a denial; the tool pipeline materializes it as the model-facing
error. Other listeners cannot turn it back into permission. A script failure,
missing Python, or malformed JSON fails open, matching the harness invariant.
### Bootstrap flow
```
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)?
yes -> no-op
no -> append createUserMessage({ source: {kind:"user"} })
```
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.
## 5. Configuration
The bundle row can be configured from the profile patch. Defaults are suitable
for the repository layout:
```yaml
- id: dsh-harness
config:
enabled: true
guards: true
bootstrap: true
roleCommand: true
bootstrapSubagents: false
hooksDir: '' # empty -> bundle/hooks -> ../../hooks
rolesDir: '' # empty -> ~/.dsh/roles, ~/.claude/roles, repo roles/
projectRolesDir: '' # empty -> <cwd>/.claude/roles
roleNames: [dev, admin, analyst, qa]
bootstrapSkillPath: '' # empty -> ~/.dsh/skills, then Claude cache
toolMappingPath: '' # empty -> bundle/dsh-tools.md
bootstrapMaxBytes: 65536
```
### Model routes and delegation depth
SDD needs two things the default profile does not give: a model choice for
`subagent` and a second level of delegation for a background controller.
`dsh/subagent-models.yml` is the opt-in overlay for both:
```bash
# 1. copy the two entries into the profile patch (desktop is Electron-managed)
open -e ~/.dsh/profiles/desktop/cordis.patch.yml # append dsh/subagent-models.yml
# 2. restart DSH, then check
bash dsh/install.sh --check # "ok: subagent maxDepth=2 (...)"
```
After the restart the `subagent` and `subagent_fork` schemas accept
`provider`, `model`, `reasoning_effort`, and a background controller subagent
can dispatch an implementer. Routes measured in 0.2.1-rc.3:
`deepseek-official/deepseek-flash` (transcription and mechanics) and
`deepseek-official/deepseek-v4-pro` (integration and every review). Without the
overlay the agent runs everything on the session model and at depth 1; the
`sdd` skill must say so in its report instead of pretending the routes exist.
The overlay stays out of `dsh/bundle/cordis.patch.yml` on purpose: the
`subagent-model-selection-settings` row exists in the web/desktop profile and is
absent in headless, and enabling it from the bundle would break headless boot
with "requires ... in the Host scope".
`bootstrapSkillPath` resolution order: explicit path, `DSH_SUPERPOWERS_SKILL`,
`~/.dsh/skills`, `~/.agents/skills`, the newest
`~/.claude/plugins/cache/claude-plugins-official/superpowers/*/`, then
`~/.claude/skills`. The DSH copy wins because `dsh/install.sh --skills` copies
real files there; the Claude cache is only a fallback.
### OpenSpec skills
Воркфлоу OpenSpec ставит генератор CLI, а не обвязка:
```bash
cd <project>
openspec init --tools agents # или: bash <harness>/dsh/install.sh --openspec
openspec update # обновить скиллы после апгрейда CLI
```
Скиллы лежат в `.agents/skills/openspec-{propose,apply-change,archive-change,explore,sync-specs,update-change}/SKILL.md`
и перезаписываются генератором: править их руками нельзя. Команды адаптер
`agents` не ставит, поэтому `/openspec` в DSH нет — модель вызывает скилл по
фразе «openspec propose» / «openspec archive».
Цикл без Claude Code:
```bash
openspec new change <id>
openspec status --change <id> --json
openspec instructions proposal --change <id> --json
openspec validate <id> --strict
openspec archive <id> -y
```
## 6. Install and verify
Prerequisites: the `claude-harness` checkout, a superpowers source, DSH
0.2.1-rc.3, `python3`, `git`, Node. Claude Code itself is optional after
`dsh/install.sh --skills --roles` has copied the skills and roles into
`~/.dsh`; the hooks come from this checkout.
```bash
# 1. Static checks
bash dsh/install.sh --check
# 2. Global instructions, skills, and roles (run from a terminal; writes
# outside the session workspace)
bash dsh/install.sh --global
bash dsh/install.sh --skills
bash dsh/install.sh --roles
# or all three at once:
bash dsh/install.sh --global --skills --roles
# 3. Install the bundle in DSH:
# Plugins -> install bundle from dsh/bundle
# or ask the agent:
# plugin_manager install_bundle target=/absolute/path/to/claude-harness/dsh/bundle
# 4. Optional: CodeGraph MCP
# apply dsh/mcp.codegraph.yml as a --patch overlay; the tool is
# mcp__codegraph__codegraph_explore.
```
`--openspec` добавляет agents-адаптер в текущий проект (`$PWD`) и требует
существующий `openspec/`: без него скрипт отказывается и печатает готовую
команду. В живой сессии проверка такая: в проекте с `.agents/skills`
спросить «what skills do you have?» — должны быть шесть `openspec-*`.
`--roles` also appends the DSH SDD note to `~/.dsh/roles/dev.md` (the bullet that
points `dev` at the `sdd` skill). The note lives only in the installed copy: the
shared `roles/dev.md` stays Claude Code-compatible, so a Claude session never
sees DSH-only instructions. The insertion is idempotent — every `--roles` run
recopies the role from the repository and re-inserts exactly one note.
Live checks:
1. New session: ask "what skills do you have?" — superpowers skills and the
DSH catalog should be visible.
2. On `main`, ask the agent to create a file — expect
`BLOCKED [guard-main]`.
3. In a worktree on a feature branch, edit `src/...` without a plan — expect
`BLOCKED [require-plan]`; add `.bounded` and retry.
4. `/role dev` — expect the five-line summary and the injected role rules.
5. The acceptance prompt "Let's make a react todo list" must trigger
`brainstorming` before code. If it does not, the bootstrap is not visible;
check the plugin row and the skill path.
6. Guard marker: the injected message starts with
`<!-- DSH-HARNESS-BOOTSTRAP:<hash> -->`; after compaction a new marker is
appended.
Verified live on 2026-10-24 in the desktop profile:
- the bootstrap marker `<!-- DSH-HARNESS-BOOTSTRAP:6b676dd9bb8a0234 -->` and the
full `using-superpowers` body arrived in a fresh session, and the skill
catalog showed the superpowers skills;
- `write` to a file on `main` returned `BLOCKED [guard-main]`;
- a Bash redirect write on `main` returned `BLOCKED [guard-main]`;
- a `write` on a feature branch was allowed;
- `write` to `src/probe.ts` on the branch returned `BLOCKED [require-plan]`,
and the same write succeeded after creating `.bounded`.
Still pending: `/role dev`, the fresh-session "react todo list" acceptance, and
re-injection after `/compact`.
Automated tests:
```bash
bash dsh/tests/run.sh
```
They cover main/detached guards, branch allowance, Bash writes, `require-plan`
with `.bounded` and a branch plan, bootstrap frontmatter/marker/dedupe,
role-layer composition, and package metadata.
Composition smoke test (no model call): the bundle was installed with
`dsh plugin --profile test add dsh/bundle` into a temporary `DSH_HOME` based on
the shipped `headless` profile. `--dump-config` shows the `dsh-harness` row, the
profile lists `@local/dsh-harness` in `dsh.profile.bundles`, and the package's
internal `hooks` symlink resolves to the repository `hooks/`. The temporary
profile was removed; the real `desktop` profile was not touched.
## 7. Risks and open questions
- **DSH is 0.2.1-rc.3.** The plugin/event API is not frozen; pin the version and
re-run the tests after upgrades.
- **Local profile imports are not resolved from the DSH installation.** The
first install attempt failed at activation with
`ERR_MODULE_NOT_FOUND: Cannot find package '@deepseek-ai/schemastery'`. The
bundle now imports only Node builtins and local files: `lib/message.js`
replaces `createUserMessage` from `@deepseek-ai/dsh-llm`, and `Config` was
dropped in favor of defaults normalized in `apply()`. Any future DSH service
must be reached through `ctx` (injected service), never through an
`@deepseek-ai/*` import; a package test enforces this.
- **`ctx.tools.guard` is synchronous.** The guard resolves cwd through
`workingDirectory.get(session)` (sync) and runs the hooks with
`execFileSync`. If a future DSH version changes the guard contract or makes
cwd resolution async, the adapter needs a `tools/pre-execute` fallback. The
guard is global and monotonic; it runs after `tools/pre-execute`, so an earlier
user approval cannot turn a `guard-main` denial back into permission.
- **`agent/pre-step` injection.** Verified live: the bootstrap marker and body
arrived in a fresh session, and the skill catalog showed the superpowers
skills. The local `createUserMessage` copy works with `source.kind: "user"`,
which avoids an unknown session source variant. Re-injection after compaction
is still pending.
- **Subagent bootstrap.** `isSubagent` probes common fields; if none are present
on this version, the bootstrap reaches children too. The skill's
`<SUBAGENT-STOP>` makes that harmless but token-expensive.
- **Model tiers.** `sonnet`/`haiku`/`opus` are not DSH models. The port uses two
live routes (`deepseek-flash`, `deepseek-v4-pro`) from the opt-in overlay in
`dsh/subagent-models.yml`; without it every dispatch runs on the session model.
- **`~/.claude` dependence.** `dsh/install.sh --skills --roles` copies skills
and roles into `~/.dsh`; the bootstrap and `/role` prefer those copies, and
the Claude cache/roles are fallbacks. An existing installation must rerun
`--skills --roles` once to become independent of a Claude Code rollback.
- **Superpowers porting rules.** The upstream guide requires a session-start
bootstrap with no per-session opt-in; `agent/pre-step` satisfies that shape,
but a real acceptance transcript is still required before claiming a port.
- **Hooks symlink.** Local-directory installation links the checkout, so
`bundle/hooks -> ../../hooks` works. A packed/published bundle must include a
real copy of `hooks/`; the `files` field already lists it.
## 8. Next phases
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.
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
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.
7. Decide the long-term home: keep `dsh/` in this repository while it shares
hooks/roles/skills, then extract a `dsh-harness` repository when the bundle
gets its own release cadence.
8. OpenSpec — **done** (change `dsh-openspec`): проектные скиллы, `--openspec`,
`--check`; приёмка — в дизайне change.