250 lines
11 KiB
Markdown

# DSH port — design and spike notes
Status: **spike 0.1.0, live verification 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
1. **`guard-main` and `require-plan`** through `ctx.tools.guard`. The adapter
synthesizes the Claude hook payload (`{cwd, tool_name, tool_input}`), runs
the existing `hooks/guard-main.sh` / `hooks/require-plan.sh`, turns `exit 2`
into 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 after `tools/pre-execute`, and no later
listener can turn the denial back into permission.
2. **Superpowers bootstrap** through `agent/pre-step`. It reads the installed
`using-superpowers/SKILL.md`, strips frontmatter, appends
`bundle/dsh-tools.md`, wraps the result in `<EXTREMELY_IMPORTANT>`, and
injects it once per session. A content-hash marker handles dedupe; scanning
`session.surface.nodes` re-injects after compaction.
3. **`/role`** as a real command (`ctx.commands.register`). It reads the global
`~/.claude/roles/<role>.md` and 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.
4. **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-check` and `role-hint`**: non-blocking SessionStart hooks. They can
become `agent/pre-step` injections later; the first message role rule already
covers most of `role-hint`.
- **`briefing`**: needs an agent preset with read-only tools and a model route;
not part of the guard/bootstrap spike.
- **SDD**: the Claude `Task` is synchronous, while DSH `subagent` returns a
child id and reports later. This needs a semantic rewrite (async controller
with `send_message` + `maxDepth: 2`, or a `workflow` script). Do not claim SDD
parity from this spike.
- **Codegraph by default**: `dsh/mcp.codegraph.yml` is 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.sh` prepares global instructions and skills; the
bundle itself is installed by `plugin_manager` because 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
```
`index.js` is the only file importing DSH packages
(`@deepseek-ai/dsh-llm`, `@deepseek-ai/schemastery`). The pure modules are
testable with plain Node; this is why the port's 22 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:
```yaml
- 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.
```bash
# 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:
1. New session: ask "what skills do you have?" — superpowers skills and the
DSH catalog should be visible.
2. On `main`, ask the agent to create a file — expect
`BLOCKED [guard-main]`.
3. In a worktree on a feature branch, edit `src/...` without a plan — expect
`BLOCKED [require-plan]`; add `.bounded` and retry.
4. `/role dev` — expect the five-line summary and the injected role rules.
5. The acceptance prompt "Let's make a react todo list" must trigger
`brainstorming` before code. If it does not, the bootstrap is not visible;
check the plugin row and the skill path.
6. Guard marker: the injected message starts with
`<!-- DSH-HARNESS-BOOTSTRAP:<hash> -->`; after compaction a new marker is
appended.
Automated tests:
```bash
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.
## 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.
- **`ctx.tools.guard` is synchronous.** The guard resolves cwd through
`workingDirectory.get(session)` (sync) and runs the hooks with
`execFileSync`. If a future DSH version changes the guard contract or makes
cwd resolution async, the adapter needs a `tools/pre-execute` fallback. The
guard is global and monotonic; it runs after `tools/pre-execute`, so an earlier
user approval cannot turn a `guard-main` denial back into permission.
- **`agent/pre-step` injection.** The code follows the shipped skill-catalog
pattern (`next()` then append `messages`), but the live behavior of
`createUserMessage({source:{kind:"user"}})` in a host bundle still needs a
smoke test. The source kind is intentionally `user` to avoid an unknown
session source variant.
- **Subagent bootstrap.** `isSubagent` probes 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`/`opus` are 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-step` satisfies 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 -> ../../hooks` works. A packed/published bundle must include a
real copy of `hooks/`; the `files` field already lists it.
## 8. Next phases
1. Live smoke test in DSH: guard, bootstrap marker, `/role`.
2. Add `harness-check` and optional `role-hint` as non-blocking pre-step
injections.
3. Build `briefing` as an agent preset (read-only tools + model route).
4. SDD spike: async controller (`subagent` + `send_message`, `maxDepth: 2`) for
one plan, or a `workflow`-based executor; measure which fits the harness.
5. Enable CodeGraph MCP by default when the binary is present; update role and
project instructions to `mcp__codegraph__codegraph_explore`.
6. Port `harness-doctor` checks to DSH paths.
7. Decide the long-term home: keep `dsh/` in this repository while it shares
hooks/roles/skills, then extract a `dsh-harness` repository when the bundle
gets its own release cadence.