15 KiB
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 throughctx.tools.guard; the agent getsBLOCKEDwith 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), andapproval.mdis written only by/approve <change-id> design|plan; -
superpowers bootstrap —
using-superpowersis injected once per session fromagent/pre-stepand 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 и superpowerssection inCLAUDE.md) androle-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
roleskill (~/.dsh/roles, then~/.claude/roles, then reporoles/);/role dev|admin|analyst|qaremains the user-facing command and injects the same text, without Claude's auto-memory step; -
SDD — the
sddskill executes a plan through asynchronoussubagentdispatches (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-checkcommand re-runs the same check on demand.
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 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):
- One issue per
obra/superpowersrelease (sync → tests → PR). Compare the release tags with the pin indsh/install.sh. - Bump
DSH_SUPERPOWERS_REFindsh/install.shand the pin quoted in this file and inPORT.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>, thennode dsh/sync/verify-skills.mjs <dir>. A changed transform anchor or a changed skill set is a hardFAIL: adapt the transform or the expected set, do not bypass it. - Reinstall and check:
bash dsh/install.sh --skills, thenbash 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));--checkprints 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. bash dsh/tests/run.shmust stay green. The fixtures are a reduced upstream tree, so a transform that stopped matching fails there first.
Verify
- In a new session, ask
what skills do you have?— the superpowers catalog should be visible. - In a repository on
main, ask the agent to create a file — expectBLOCKED [guard-main]. - Create a worktree on a feature branch, ask for an edit under
src/— expectBLOCKED [require-plan]until.boundedor a plan for the branch exists. - The model should load a role with
skill({name:"role"});/role devis the user-facing command with the same file. Expect the five-line role summary. - Prompt
Let's make a react todo list—brainstormingmust trigger before any code. - In a project with
.agents/skills, askwhat skills do you have?— the sixopenspec-*skills must be listed. - In a repository with
.gitbut noopenspec/, expect one[dsh-harness] Проект … не подключён к обвязкеmessage; a first message likeНапиши тест для модуляmust be followed by[dsh-harness] Роль сессии: dev.
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.
The two pre-step injections use the same shape; the hash is of the injection text, so a different hint is a different marker:
<!-- DSH-HARNESS-PRESTEP:<16-hex-hash> -->
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/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
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
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 -> <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
python3is missing, the hooks fail open by design. - Approval gates (change
enforce-upstream-loop) live inside the guard chain: they needenabled: true,guards: true,approvalGates: trueand a passingguardsself-check (guardsOk);approvalGates: truealone 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 thesddskill checks the plan approval by hand. - Gate A is content-based: it reads
**Spec:**fromcontent/new_string. Aneditfragment 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 andstr_replace_editorcreates are denied because the gate cannot see their content; the deny text then names the missing**Spec:**, not the real reason. terminal_open/terminal_sendwrite files outside both the gates and the bash hooks — a residual risk not covered by the port.bundle/hooksis a symlink for local-directory installation. A published bundle needs a real copy ofhooks/(already listed inpackage.jsonfiles).using-superpowersis read from~/.dsh/skillsafter--skills. The install-time source is--superpowers-from,DSH_SUPERPOWERS_SOURCE, or the pinned clone~/.dsh/superpowers(DSH_SUPERPOWERS_REF, defaultv7.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. SetbootstrapSkillPathorDSH_SUPERPOWERS_SKILLfor another layout.- Roles resolve
~/.dsh/rolesfirst;~/.claude/rolesand the repositoryroles/are fallbacks. After--rolesthe runtime never needs~/.claude. - Role loading is model-driven through the
roleskill; there is no automaticagent/pre-stepinjection. If the model skips the skill, use the/rolecommand or ask it to load the role explicitly. - Model names in roles and superpowers (
sonnet,haiku,opus) are not DSH models. Thesddskill uses two live routes from the opt-indsh/subagent-models.ymloverlay; without it every dispatch runs on the session model. - SDD on DSH is asynchronous, not a copy of Claude's
Task. See thesddskill andopenspec/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-hintmatches 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;prmatches only as a whole word, sopreviewis 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-checkis a hint, never adoption: it writes nothing, and there is no DSH port ofadopt-project.sh; the owner runsopenspec init --tools agents,codegraph init, and adds the## OpenSpec и superpowerssection toCLAUDE.md.- The bundle self-checks the DSH contract it depends on at activation:
ctx.tools.guard,ctx.onforagent/pre-step,ctx.injectfor 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-checkcommand re-runs the same check on demand (oneok/not-okline per feature). Disable withselfCheck: false/checkCommand: false.