From 85dd488892c20b9e3bc6bcf0886cf2be5bf71007 Mon Sep 17 00:00:00 2001 From: phungthien269 Date: Fri, 2 Oct 2026 00:17:22 +0700 Subject: [PATCH 1/3] feat: add DeepSeek Harness guide, hooks generator, and setup skill - docs/DEEPSEEK-HARNESS-GUIDE.md: skills roots, hooks bridge mounting, loader rules learned from practice, scope table | - scripts/dsh/generate-hooks-config.js: convert hooks/hooks.json to the dsh-hooks-claude-code bridge format (lower-case tool matchers, inline plugin-root env prefix, timeout preserved, unsupported events skipped) | - skills/deepseek-harness-setup: skills-first entry point per repo policy | - tests/scripts/dsh-generate-hooks-config.test.js: 8 unit + CLI tests --- docs/DEEPSEEK-HARNESS-GUIDE.md | 128 +++++++++++++++ scripts/dsh/generate-hooks-config.js | 154 ++++++++++++++++++ skills/deepseek-harness-setup/SKILL.md | 79 +++++++++ .../scripts/dsh-generate-hooks-config.test.js | 104 ++++++++++++ 4 files changed, 465 insertions(+) create mode 100644 docs/DEEPSEEK-HARNESS-GUIDE.md create mode 100644 scripts/dsh/generate-hooks-config.js create mode 100644 skills/deepseek-harness-setup/SKILL.md create mode 100644 tests/scripts/dsh-generate-hooks-config.test.js diff --git a/docs/DEEPSEEK-HARNESS-GUIDE.md b/docs/DEEPSEEK-HARNESS-GUIDE.md new file mode 100644 index 000000000..8d62c15fb --- /dev/null +++ b/docs/DEEPSEEK-HARNESS-GUIDE.md @@ -0,0 +1,128 @@ +# DeepSeek Harness Guide + +ECC runs on the [DeepSeek Harness](https://github.com/deepseek-ai/dsh) (DSH) via its +native skill roots and the first-party Claude Code hooks bridge. This guide covers +what works out of the box, how to mount the ECC hooks, and the current limits. + +Unlike the Qwen or Codex adapters, there is no dedicated installer target yet; the +setup is a small amount of manual configuration described below. + +## Prerequisites + +- DeepSeek Harness 0.2.0-rc.1 or newer (`dsh --version`). +- ECC repository cloned locally, for example `~/ECC` (the hooks bridge commands + reference this path, so keep the checkout in place). +- Skills need no extra tooling; hooks use the `@deepseek-ai/dsh-hooks-claude-code` + plugin that ships inside the DSH runtime itself. + +## Skills (automatic) + +DSH loads skills from these roots (earlier roots win on name conflicts): + +```text +/.dsh/skills +/.agents/skills +custom dirs (~/.dsh/profiles//cordis.patch.yml -> skill-filesystem) +~/.dsh/skills +~/.agents/skills +``` + +Because ECC already ships `.agents/skills/` for Codex, opening a session with the +ECC checkout as the working directory exposes a subset of ECC skills immediately. +To expose the full curated set in every project, link the catalog once: + +```bash +ln -s /path/to/ECC/skills/ ~/.dsh/skills/ +``` + +DSH reads `SKILL.md` frontmatter (name/description), which matches the ECC skill +format, so no conversion is needed. Name conflicts with pre-existing user skills +are resolved by root priority; skip those skills when linking. + +## Hooks + +### How the bridge works + +DSH bridges ECC command hooks through `@deepseek-ai/dsh-hooks-claude-code` +(first-party, bundled with the runtime). Constraints that shape the conversion: + +- Supported events: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, + `PostToolUse`, `Stop`, `SubagentStart`, `SubagentStop`. ECC events without a + bridge counterpart (`PreCompact`, `PostToolUseFailure`, `SessionEnd`) are + skipped by the generator. +- DSH tool names are lower-case (`bash`, `edit`, `write`, `skill`, `task`), + while ECC matchers use Claude Code names (`Bash`, `Edit|MultiEdit`, ...). The + generator maps known tool names and keeps regex passthrough tokens. +- The bridge runs commands with `bash -c` and does not export the plugin root as + an environment variable for hook processes. The generator prefixes each command + with `CLAUDE_PLUGIN_ROOT= ECC_PLUGIN_ROOT=` (env-prefix + form), which the ECC hook bootstrap reads at startup. +- `timeout` values are seconds in both formats and are preserved. + +### Generate the config + +```bash +node scripts/dsh/generate-hooks-config.js --ecc-root /path/to/ECC --out ~/.dsh/ecc/hooks-dsh.json +``` + +The script validates nothing about your DSH install; it only converts +`hooks/hooks.json` into the bridge format and prints what it skipped. + +### Mount into a profile + +Add a bare insert entry to the profile patch layer +(`~/.dsh/profiles//cordis.patch.yml`). This mirrors how the bundled web +app preset mounts its plugins: + +```yaml +- insert: + - id: ecc-hooks-claude-code + name: "@deepseek-ai/dsh-hooks-claude-code" + config: + configPath: /absolute/path/to/hooks-dsh.json +``` + +Configuration notes from practice: + +- A plain top-level entry (`- id: ...`) is silently dropped; the loader only + treats `insert` entries as additions. +- `insert` under an existing non-group id is rejected ("not a group"); use a bare + insert without a target id. +- The plugin package does not need to be added to the profile `package.json`; + first-party plugins resolve from the DSH runtime install. +- Validate without booting: `cd ~/.dsh && dsh --profile --dump-config` + (compose errors and dropped entries are reported on stderr). + +### Verify + +```bash +# Compose check (no session boot) +cd ~/.dsh && dsh --profile web --dump-config > /dev/null && echo OK + +# End-to-end: fire a SessionStart hook in a one-shot headless session +# (uses a test config with a single marker-writing hook) +dsh --profile headless --patch /tmp/ecc-patch-test.yml 'Reply with exactly: BOOT-OK' +``` + +In a live web session, the GateGuard fact-forcing hook denies the first +edit/write/bash attempt until facts are presented — a visible signal that the +bridge is active. + +## Updating + +```bash +cd /path/to/ECC && git pull +node scripts/dsh/generate-hooks-config.js --ecc-root /path/to/ECC --out ~/.dsh/ecc/hooks-dsh.json +``` + +Linked skills reflect the pull immediately (symlinks), only the hooks config +needs regeneration. + +## Scope + +| Surface | Status on DSH | +|---------|---------------| +| Skills (293) | Works; link or rely on `.agents/skills` | +| Hooks (20 command hooks, 4 of 7 events) | Works via the bridge; 3 events unsupported | +| Commands (94 slash commands) | Not available (Claude Code mechanism); workflows are covered by skills | +| Agents (68) | Not available as subagents; equivalent personas exist as skills | diff --git a/scripts/dsh/generate-hooks-config.js b/scripts/dsh/generate-hooks-config.js new file mode 100644 index 000000000..f66b82570 --- /dev/null +++ b/scripts/dsh/generate-hooks-config.js @@ -0,0 +1,154 @@ +#!/usr/bin/env node +/** + * Generate a DeepSeek Harness (DSH) hooks config from hooks/hooks.json. + * + * DSH bridges ECC command hooks through the first-party plugin + * @deepseek-ai/dsh-hooks-claude-code. The bridge supports a subset of + * Claude Code hook events and lower-cases tool names in matchers, so this + * script converts the canonical ECC hooks.json into a DSH-compatible file. + * + * Usage: + * node scripts/dsh/generate-hooks-config.js [--ecc-root ] [--out ] + * + * Defaults: + * --ecc-root directory of this checkout (repo root) + * --out /hooks/hooks.dsh.json + * + * The output is a Claude Code hooks-format file consumed by the bridge: + * { "hooks": { "PreToolUse": [ { "matcher": "...", "hooks": [...] } ] } } + * + * Unsupported ECC events (no DSH bridge counterpart) are skipped and + * reported on stderr: PreCompact, PostToolUseFailure, SessionEnd. + */ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const SUPPORTED_EVENTS = new Set([ + 'SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'Stop', + 'SubagentStart', 'SubagentStop', +]); + +// Claude Code tool names -> DSH lower-case tool names used by the bridge. +const TOOL_NAME_MAP = { + Bash: 'bash', + PowerShell: 'powershell', + Write: 'write', + Edit: 'edit', + MultiEdit: 'edit', + Skill: 'skill', + Task: 'task', +}; + +function parseArgs(argv) { + const opts = { eccRoot: null, out: null }; + for (let i = 2; i < argv.length; i++) { + const a = argv[i]; + if (a === '--ecc-root') opts.eccRoot = argv[++i]; + else if (a === '--out') opts.out = argv[++i]; + else { + process.stderr.write('Unknown argument: ' + a + '\n'); + process.exit(2); + } + } + return opts; +} + +/** + * Map a Claude Code matcher expression to DSH tool-name tokens. + * Split on |, map known tool names to lower-case DSH names, keep regex + * passthrough tokens (like .* or ^mcp__) untouched, de-duplicate. + */ +function mapMatcher(matcher) { + if (typeof matcher !== 'string' || matcher.length === 0) return matcher; + const parts = matcher.split('|').map(function (t) { + const v = t.trim(); + if (v === '' || v === '.*' || v === '^mcp__') return v; + const mapped = TOOL_NAME_MAP[v]; + return mapped !== undefined ? mapped : v.toLowerCase(); + }); + return Array.from(new Set(parts)).join('|'); +} + +/** + * Rewrite an ECC command into a DSH bridge command. + * + * The bridge runs commands with bash -c but does not export the plugin + * root as an environment variable for every hook process. Each ECC hook + * command embeds a resolver (`node -e "..."