# dsh — DeepSeek Harness adapter Spike of the `claude-harness` port for DeepSeek Harness (DSH 0.2.1-rc.3). The Claude Code installation in `~/.claude` stays untouched; this directory is a separate Cordis bundle plus a thin install helper. **What works in the spike:** - `guard-main` / `require-plan` — the tested bash/Python hooks run through `ctx.tools.guard`; the agent gets `BLOCKED` with the same messages as in Claude Code; - approval gates A/B/C — `lib/gates.js` + `command-approve.js`: a plan cannot be written before its change design is approved, sdd cannot start before the plan is approved (sha256-pinned), and `approval.md` is written only by `/approve design|plan`; - superpowers bootstrap — `using-superpowers` is injected once per session from `agent/pre-step` and re-injected after compaction; - 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 auto-memory step; - SDD — the `sdd` skill executes a plan through asynchronous `subagent` dispatches (report files, task review, fix loop, ledger), with controller modes for one plan and for several parallel plans; the optional overlay enables model routes and depth 2. - OpenSpec — воркфлоу идут проектными скиллами `.agents/skills/openspec-*` (ставит `openspec init --tools agents`); глобальной копии нет, слэш-команд нет. Установка в проект: `bash dsh/install.sh --openspec`. - self-check — at activation the bundle verifies the DSH contract it depends on 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. ## Quick start ```bash # Static check (offline; reports the configured pin and the current source) bash dsh/install.sh --check # Superpowers source. The default is a pinned clone: --skills clones or # updates ~/.dsh/superpowers at $DSH_SUPERPOWERS_REF (default v7.0.1, fetched # from $DSH_SUPERPOWERS_REPO), then runs sync + verify and copies the adapted # skills. Offline it reuses the clone only when it is already on the ref. bash dsh/install.sh --skills # ...or sync a checkout directly (no network, no pin enforcement): bash dsh/install.sh --skills --superpowers-from /path/to/superpowers # ...or export DSH_SUPERPOWERS_SOURCE=/path/to/superpowers # Global instructions, local role skill, and roles # (run from a terminal; these write outside the session workspace) bash dsh/install.sh --global bash dsh/install.sh --roles # В корне проекта, где уже есть openspec/: добавить agents-скиллы bash /dsh/install.sh --openspec # Install the bundle in DSH: # Plugins -> install a bundle from dsh/bundle # or ask the agent to call plugin_manager install_bundle with that target. ``` `--skills` resolves superpowers in this order: `--superpowers-from` → `$DSH_SUPERPOWERS_SOURCE` → the pinned clone `~/.dsh/superpowers` at `$DSH_SUPERPOWERS_REF` (default `v7.0.1`, cloned or updated from `$DSH_SUPERPOWERS_REPO`; offline it is reused only when already on the ref) → the Claude Code plugin cache (legacy, warns). Explicit sources are synced in place with no network; they must be a checkout root or its `skills/` directory — a bare skills directory outside that layout is a hard `FAIL` in both `--skills` and `--check`. Explicit syncs stage into `~/.dsh/.skills.synced`, never into `~/.dsh/superpowers` (reserved for the pinned clone). Every sync runs `dsh/sync/sync-from-upstream.mjs` (DSH adaptations) and `dsh/sync/verify-skills.mjs`; a tree that fails verification or holds zero skills is never installed. Legacy sources are copied raw. A clean HOME with no source and no clone fails with the clone command instead of pretending success; the local `role` and `sdd` skills are copied either way. Do not hand-edit `$DSH_HOME/profiles/desktop/package.json` or `cordis.patch.yml`: the Desktop profile is Electron-managed and the plugin manager owns those writes. ## Update Upstream superpowers moves on its own schedule, so a pin bump is a deliberate step, never a background pull (design D-2, cadence D-7): 1. One issue per `obra/superpowers` release (sync → tests → PR). Compare the release tags with the pin in `dsh/install.sh`. 2. Bump `DSH_SUPERPOWERS_REF` in `dsh/install.sh` and the pin quoted in this file and in `PORT.md`; then run the sync and the verifier by hand against a checkout of the new tag: `node dsh/sync/sync-from-upstream.mjs --out --ref `, then `node dsh/sync/verify-skills.mjs `. A changed transform anchor or a changed skill set is a hard `FAIL`: adapt the transform or the expected set, do not bypass it. 3. Reinstall and check: `bash dsh/install.sh --skills`, then `bash dsh/install.sh --check`. The source line must name the new ref and the synced version (`ref v7.0.2 (synced, version 7.0.2)`); `--check` prints the configured pin next to the ref recorded by the last sync, so a pin that was bumped without resyncing (or a sync that never ran) is visible instead of silently old text. 4. `bash dsh/tests/run.sh` must stay green. The fixtures are a reduced upstream tree, so a transform that stopped matching fails there first. ## Verify 1. In a new session, ask `what skills do you have?` — the superpowers catalog should be visible. 2. In a repository on `main`, ask the agent to create a file — expect `BLOCKED [guard-main]`. 3. Create a worktree on a feature branch, ask for an edit under `src/` — expect `BLOCKED [require-plan]` until `.bounded` or a plan for the branch exists. 4. The model should load a role with `skill({name:"role"})`; `/role dev` is the user-facing command with the same file. Expect the five-line role summary. 5. Prompt `Let's make a react todo list` — `brainstorming` must trigger before any code. 6. In a project with `.agents/skills`, ask `what skills do you have?` — the six `openspec-*` skills must be listed. The injected bootstrap starts with a marker: ```text ``` After compaction the marker disappears from `session.surface.nodes` and the next step injects a fresh copy. ## Tests ```bash bash dsh/tests/run.sh ``` Pure Node; no DSH process is required. The guard tests create a throwaway git repository and exercise the real `hooks/guard-main.sh` and `hooks/require-plan.sh` through the adapter. ## Layout ```text dsh/ bundle/ package.json DSH bundle manifest (dsh.bundle.patch) cordis.patch.yml inserts the dsh-harness row index.js ctx.tools.guard + agent/pre-step + /role command-role.js /role command glue command-check.js dsh-harness-check command glue command-approve.js /approve command: the only writer of approval.md 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/role.js role layers and text (pure) lib/selfcheck.js bundle contract self-check (pure) dsh-tools.md superpowers tool mapping hooks -> ../../hooks local install: tested hooks are reused, not copied locale/{en,ru}.json icon.svg sync/ vendored sync machinery (Node builtins only) sync-from-upstream.mjs pinned upstream -> adapted skills (asserted transforms) verify-skills.mjs synced-tree validation (frontmatter, prefixes, links) tests/ install.sh skills/{role,sdd}/SKILL.md model-invocable skills installed into ~/.dsh subagent-models.yml opt-in overlay: model routes + maxDepth 2 mcp.codegraph.yml optional CodeGraph MCP overlay PORT.md design, mapping, risks, next phases ``` ```text В корне проекта (не в dsh/): .agents/skills/openspec-* скиллы OpenSpec, генерирует CLI ``` ## Configuration The row is configurable from the profile patch; defaults target this repository layout: ```yaml - id: dsh-harness config: guards: true approvalGates: true # gates A/B/C; effective only inside the guard chain bootstrap: true roleCommand: true selfCheck: true # activation check of the DSH contract; not-ok is logged checkCommand: true # register the dsh-harness-check command bootstrapSubagents: false hooksDir: '' # empty -> bundle/hooks -> ../../hooks rolesDir: '' # empty -> ~/.dsh/roles, legacy ~/.claude/roles, repo roles/ projectRolesDir: '' # empty -> /.claude/roles roleNames: [dev, admin, analyst, qa] bootstrapSkillPath: '' # empty -> ~/.dsh/skills, then legacy Claude Code paths toolMappingPath: '' # empty -> bundle/dsh-tools.md bootstrapMaxBytes: 65536 ``` ## Known limits - The guard adapter uses the repository's bash/Python hooks; if `python3` is missing, the hooks fail open by design. - Approval gates (change `enforce-upstream-loop`) live inside the guard chain: they need `enabled: true`, `guards: true`, `approvalGates: true` **and** a passing `guards` self-check (`guardsOk`); `approvalGates: true` alone does not enable them. They are active only in a session with the reinstalled bundle (D-13): an old install keeps the text-only loop, and the `sdd` skill checks the plan approval by hand. - Gate A is content-based: it reads `**Spec:**` from `content` / `new_string`. An `edit` fragment is checked against its own text, so a fragment carrying a `**Spec:**` line for an approved change passes, while the usual Spec-less fragment is denied (rewrite the plan file in full). Bash plan targets and `str_replace_editor` creates are denied because the gate cannot see their content; the deny text then names the missing `**Spec:**`, not the real reason. - `terminal_open` / `terminal_send` write files outside both the gates and the bash hooks — a residual risk not covered by the port. - `bundle/hooks` is a symlink for local-directory installation. A published bundle needs a real copy of `hooks/` (already listed in `package.json` `files`). - `using-superpowers` is read from `~/.dsh/skills` after `--skills`. The install-time source is `--superpowers-from`, `DSH_SUPERPOWERS_SOURCE`, or the pinned clone `~/.dsh/superpowers` (`DSH_SUPERPOWERS_REF`, default `v7.0.1`); Claude Code locations are a **legacy migration fallback** and log one warning per process when used, so a partial Claude Code rollback does not break the bootstrap. Set `bootstrapSkillPath` or `DSH_SUPERPOWERS_SKILL` for another layout. - Roles resolve `~/.dsh/roles` first; `~/.claude/roles` and the repository `roles/` are fallbacks. After `--roles` the runtime never needs `~/.claude`. - Role loading is model-driven through the `role` skill; there is no automatic `agent/pre-step` injection. If the model skips the skill, use the `/role` command or ask it to load the role explicitly. - Model names in roles and superpowers (`sonnet`, `haiku`, `opus`) are not DSH models. The `sdd` skill uses two live routes from the opt-in `dsh/subagent-models.yml` overlay; without it every dispatch runs on the session model. - SDD on DSH is asynchronous, not a copy of Claude's `Task`. See the `sdd` skill and `openspec/changes/dsh-sdd/`. - OpenSpec-скиллы проект получает сам (`openspec init --tools agents` или `--openspec`); обвязка ставит их только в проект, не в `~/.dsh`. - 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 `[dsh-harness] self-check: not-ok: …`, and the feature whose service is missing is skipped (guards, bootstrap, or commands — each gated by its own check), so a DSH version without one of the services does not crash the bundle; the remaining features keep working. The `/dsh-harness-check` command re-runs the same check on demand (one `ok` / `not-ok` line per feature). Disable with `selfCheck: false` / `checkCommand: false`.