22 KiB
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
-
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. -
Roles —
/roleis a real command (ctx.commands.register); it reads~/.dsh/roles/<role>.md, falls back to~/.claude/roles/<role>.md, then to the repository'sroles/, and reads the project.claude/roles/<role>.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.mdis 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. -
Display metadata and an optional CodeGraph MCP overlay so the bundle can be installed through the Plugins page and codegraph stays available.
-
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-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 parity: the Claude
Taskis synchronous, while DSHsubagentreturns a child id and reports later. Thesddskill implements the asynchronous analogue (report files, notification as the wait,send_messagefor fix rounds, ledger as memory); there is no parity and the skill says so. - 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. - Upstream pin (change
dsh-port-v2, D-2): the default install source is the clone at~/.dsh/superpowerspinned to$DSH_SUPERPOWERS_REF(defaultv7.0.1).install.sh --skillsclones or updates it, runs the vendored sync + verify, and copies the adapted skills; a broken or empty synced tree is never installed. Explicit sources (--superpowers-from,$DSH_SUPERPOWERS_SOURCE) are synced directly with no network. Upstream moves independently, so each release is a deliberate pin bump (an issue per release, D-7). Removing the legacy~/.claudefallbacks is deferred until every machine has rerun--skills --roles; until then both runtime and installer warn when they use one.
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:
- id: dsh-harness
config:
enabled: true
guards: true
bootstrap: true
roleCommand: true
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
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:
# 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 Code locations are a legacy fallback and the
bundle logs one warning per process when it resolves one of them.
Install-time source resolution is separate and stricter (change
dsh-install-source, pin in change dsh-port-v2): --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) → the legacy cache → ~/.claude/skills. Explicit
sources are synced in place with no network and must be a checkout root or
its skills/ directory — a bare skills directory outside that layout is
invalid, and --check and --skills fail the same way (narrower than change
dsh-install-source #49; the spec delta records the MODIFIED). Explicit
syncs stage into ~/.dsh/.skills.synced beside the clone dir, which stays
reserved for the pin; an existing clone is updated by fetching
$DSH_SUPERPOWERS_REPO (not its origin) and reused offline only when already
on the ref. An invalid explicit source is a hard FAIL; a machine offline
without a clone is a FAIL with the clone hint, falling back to legacy only
when a legacy source exists.
OpenSpec skills
Воркфлоу OpenSpec ставит генератор CLI, а не обвязка:
cd <project>
openspec init --tools agents # или: bash <harness>/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:
openspec new change <id>
openspec status --change <id> --json
openspec instructions proposal --change <id> --json
openspec validate <id> --strict
openspec archive <id> -y
6. Install and verify
Prerequisites: the claude-harness checkout, DSH 0.2.1-rc.3, python3,
git, Node. Claude Code is not required at all: --skills clones the pinned
superpowers checkout itself (DSH_SUPERPOWERS_REF, default v7.0.1) and runs
the DSH sync + verify before installing, so a manual clone is no longer the
first step; --roles copies the role files; the hooks come from this
checkout.
# 1. Static checks (offline; reports the configured pin and the source ref)
bash dsh/install.sh --check
# 2. Global instructions, skills, and roles (run from a terminal; writes
# outside the session workspace). --skills clones the pinned superpowers
# checkout (DSH_SUPERPOWERS_REF, default v7.0.1) and syncs it; or pass
# --superpowers-from / DSH_SUPERPOWERS_SOURCE to sync a checkout directly.
bash dsh/install.sh --global
bash dsh/install.sh --skills # or: --skills --superpowers-from DIR
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:
- 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. The port uses two live routes (deepseek-flash,deepseek-v4-pro) from the opt-in overlay indsh/subagent-models.yml; without it every dispatch runs on the session model. - One-sided model route. The trial's «child LLM provider and model must be
supplied together» was not a race: the logged tool call carried only
model(issue #52). DSH rejects a one-sided route correctly, so no upstream report is needed; thesddskill now says to retry with both fields. ~/.claudedependence. No longer needed for install, bootstrap, or roles: the install-time source is explicit, and--skills --rolescopy real files into~/.dsh. The Claude Code locations remain a legacy migration fallback; the installer,--check, and the runtime warn when they are used (runtime — once per process). An existing installation must rerun--skills --rolesonce to stop relying on them; removal of the fallbacks is a separate task.- 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: the fresh-session
roleskill acceptance, plus the bootstrap and/compactchecks already verified. - Add
harness-checkand optionalrole-hintas non-blocking pre-step injections. - Build
briefingas an agent preset (read-only tools + model route). - SDD — done (change
dsh-sdd): thesddskill (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 indesign.md). - 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. - OpenSpec — done (change
dsh-openspec): проектные скиллы,--openspec,--check; приёмка — в дизайне change. - Install without
~/.claude— done (changedsh-install-source):--superpowers-from/DSH_SUPERPOWERS_SOURCE/~/.dsh/superpowers, hardFAILinstead of a silent empty bootstrap, DSH-owned role paths in the installedAGENTS.md, legacy warnings at install and runtime.