14 KiB
DSH port — design and spike notes
Status: spike 0.1.0, live guard/require-plan/bootstrap verified; /role and
fresh-session acceptance pending. 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 + a real /role command |
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, OpenSpec | CLI + skills | bash + skills |
1:1 |
| 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
guard-mainandrequire-planthroughctx.tools.guard. The adapter synthesizes the Claude hook payload ({cwd, tool_name, tool_input}), runs the existinghooks/guard-main.sh/hooks/require-plan.sh, turnsexit 2into 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 aftertools/pre-execute, and no later listener can turn the denial back into permission.- Superpowers bootstrap through
agent/pre-step. It reads the installedusing-superpowers/SKILL.md, strips frontmatter, appendsbundle/dsh-tools.md, wraps the result in<EXTREMELY_IMPORTANT>, and injects it once per session. A content-hash marker handles dedupe; scanningsession.surface.nodesre-injects after compaction. /roleas a real command (ctx.commands.register). It reads the global~/.claude/roles/<role>.mdand the project.claude/roles/<role>.md, injects the role text as a user message, and starts a turn for the five-line summary. Auto-memory is explicitly dropped; the role's "Читать на старте" section is left to the model.- Display metadata and an optional CodeGraph MCP overlay so the bundle can be installed through the Plugins page and codegraph stays available.
3. What is deliberately deferred
harness-checkandrole-hint: non-blocking SessionStart hooks. They can becomeagent/pre-stepinjections later; the first message role rule already covers most ofrole-hint.briefing: needs an agent preset with read-only tools and a model route; not part of the guard/bootstrap spike.- SDD: the Claude
Taskis synchronous, while DSHsubagentreturns a child id and reports later. This needs a semantic rewrite (async controller withsend_message+maxDepth: 2, or aworkflowscript). Do not claim SDD parity from this spike. - Codegraph by default:
dsh/mcp.codegraph.ymlis 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.shprepares global instructions and skills; the bundle itself is installed byplugin_managerbecause 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
install.sh # --check / --global / --skills / --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:
- id: dsh-harness
config:
enabled: true
guards: true
bootstrap: true
roleCommand: true
bootstrapSubagents: false
hooksDir: '' # empty -> bundle/hooks -> ../../hooks
rolesDir: '' # empty -> installed ~/.claude/roles, else repo roles/
projectRolesDir: '' # empty -> <cwd>/.claude/roles
roleNames: [dev, admin, analyst, qa]
bootstrapSkillPath: '' # empty -> newest superpowers plugin cache
toolMappingPath: '' # empty -> bundle/dsh-tools.md
bootstrapMaxBytes: 65536
bootstrapSkillPath resolution order: explicit path, DSH_SUPERPOWERS_SKILL,
the newest ~/.claude/plugins/cache/claude-plugins-official/superpowers/*/,
then ~/.claude/skills, ~/.dsh/skills, and ~/.agents/skills.
6. Install and verify
Prerequisites: claude-harness installed in ~/.claude (hooks, roles,
skills), superpowers 6.4.x, DSH 0.2.1-rc.3, python3, git, Node.
# 1. Static checks
bash dsh/install.sh --check
# 2. Global instructions and skills (run from a terminal; writes outside the
# session workspace)
bash dsh/install.sh --global
bash dsh/install.sh --skills
# 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.
Live checks:
- New session: ask "what skills do you have?" — superpowers skills and the DSH catalog should be visible.
- On
main, ask the agent to create a file — expectBLOCKED [guard-main]. - In a worktree on a feature branch, edit
src/...without a plan — expectBLOCKED [require-plan]; add.boundedand retry. /role dev— expect the five-line summary and the injected role rules.- The acceptance prompt "Let's make a react todo list" must trigger
brainstormingbefore code. If it does not, the bootstrap is not visible; check the plugin row and the skill path. - 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 fullusing-superpowersbody arrived in a fresh session, and the skill catalog showed the superpowers skills; writeto a file onmainreturnedBLOCKED [guard-main];- a Bash redirect write on
mainreturnedBLOCKED [guard-main]; - a
writeon a feature branch was allowed; writetosrc/probe.tson the branch returnedBLOCKED [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 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.jsreplacescreateUserMessagefrom@deepseek-ai/dsh-llm, andConfigwas dropped in favor of defaults normalized inapply(). Any future DSH service must be reached throughctx(injected service), never through an@deepseek-ai/*import; a package test enforces this. ctx.tools.guardis synchronous. The guard resolves cwd throughworkingDirectory.get(session)(sync) and runs the hooks withexecFileSync. If a future DSH version changes the guard contract or makes cwd resolution async, the adapter needs atools/pre-executefallback. The guard is global and monotonic; it runs aftertools/pre-execute, so an earlier user approval cannot turn aguard-maindenial back into permission.agent/pre-stepinjection. Verified live: the bootstrap marker and body arrived in a fresh session, and the skill catalog showed the superpowers skills. The localcreateUserMessagecopy works withsource.kind: "user", which avoids an unknown session source variant. Re-injection after compaction is still pending.- Subagent bootstrap.
isSubagentprobes 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/opusare not DSH models. Map them to live provider/model routes before porting the SDD text. - Superpowers porting rules. The upstream guide requires a session-start
bootstrap with no per-session opt-in;
agent/pre-stepsatisfies 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 -> ../../hooksworks. A packed/published bundle must include a real copy ofhooks/; thefilesfield already lists it.
8. Next phases
- Finish the live smoke test:
/role dev, the fresh-session superpowers acceptance, and bootstrap re-injection after/compact. - Add
harness-checkand optionalrole-hintas non-blocking pre-step injections. - Build
briefingas an agent preset (read-only tools + model route). - SDD spike: async controller (
subagent+send_message,maxDepth: 2) for one plan, or aworkflow-based executor; measure which fits the harness. - Enable CodeGraph MCP by default when the binary is present; update role and
project instructions to
mcp__codegraph__codegraph_explore. - Port
harness-doctorchecks to DSH paths. - Decide the long-term home: keep
dsh/in this repository while it shares hooks/roles/skills, then extract adsh-harnessrepository when the bundle gets its own release cadence.