# 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 ``, 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/.md`, falls back to `~/.claude/roles/.md`, then to the repository's `roles/`, and reads the project `.claude/roles/.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 -> /.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 openspec init --tools agents # или: bash /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 openspec status --change --json openspec instructions proposal --change --json openspec validate --strict openspec archive -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 ``; after compaction a new marker is appended. Verified live on 2026-10-24 in the desktop profile: - the bootstrap marker `` 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 `` 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.