..

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 <change-id> 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 for the design, mapping table, and risks.

Quick start

# 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 <harness>/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 <checkout> --out <dir> --ref <tag>, then node dsh/sync/verify-skills.mjs <dir>. 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:

<!-- DSH-HARNESS-BOOTSTRAP:<16-hex-hash> -->

After compaction the marker disappears from session.surface.nodes and the next step injects a fresh copy.

Tests

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

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
В корне проекта (не в dsh/):
  .agents/skills/openspec-*  скиллы OpenSpec, генерирует CLI

Configuration

The row is configurable from the profile patch; defaults target this repository layout:

- 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 -> <cwd>/.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: <feature> 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.