mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-17 13:45:34 +02:00
feat(dsh): integrate version-scoped hook setup and distribution wiring
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"source": "./",
|
||||
"description": "Harness-native ECC operator layer - 71 agents, 303 skills, 97 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
|
||||
"description": "Harness-native ECC operator layer - 71 agents, 304 skills, 97 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
|
||||
"version": "2.2.3",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"version": "2.2.3",
|
||||
"description": "Harness-native ECC plugin for engineering teams - 71 agents, 303 skills, 97 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
||||
"description": "Harness-native ECC plugin for engineering teams - 71 agents, 304 skills, 97 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
"url": "https://x.com/affaanmustafa"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ This file provides Gemini CLI with the baseline ECC workflow, review standards,
|
||||
|
||||
## Overview
|
||||
|
||||
ECC is a cross-harness coding system with 71 specialized agents, 303 skills, and 97 commands.
|
||||
ECC is a cross-harness coding system with 71 specialized agents, 304 skills, and 97 commands.
|
||||
|
||||
Gemini support is currently focused on a strong project-local instruction layer via `.gemini/GEMINI.md`, plus the shared MCP catalog and package-manager setup assets shipped by the installer.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "ecc",
|
||||
"displayName": "ECC",
|
||||
"version": "2.2.3",
|
||||
"description": "Harness-native engineering workflows for Qoder: 302 skills, 71 agents, legacy command shims, a reviewed SessionStart hook, and optional MCP tooling.",
|
||||
"description": "Harness-native engineering workflows for Qoder: canonical skills and agents, legacy command shims, a reviewed SessionStart hook, and optional MCP tooling.",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
"url": "https://x.com/affaanmustafa"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ECC — Agent Instructions
|
||||
|
||||
This is a **production-ready AI coding plugin** providing 71 specialized agents, 303 skills, 97 commands, and automated hook workflows for software development.
|
||||
This is a **production-ready AI coding plugin** providing 71 specialized agents, 304 skills, 97 commands, and automated hook workflows for software development.
|
||||
|
||||
**Version:** 2.2.3
|
||||
|
||||
@@ -202,7 +202,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
|
||||
|
||||
```
|
||||
agents/ — 71 specialized subagents
|
||||
skills/ — 303 workflow skills and domain knowledge
|
||||
skills/ — 304 workflow skills and domain knowledge
|
||||
commands/ — 97 slash commands
|
||||
hooks/ — Trigger-based automations
|
||||
rules/ — Always-follow guidelines (common + per-language)
|
||||
|
||||
@@ -140,12 +140,12 @@ Instead of rebuilding that process in every prompt, you install it once and make
|
||||
|
||||
ECC is MIT-licensed open source. It works best with Claude Code today, has native Codex and Qoder plugin paths, and provides capability-limited adapters for Cursor, OpenCode, Gemini, Zed, GitHub Copilot, Antigravity, Qwen, and other harnesses. See the [support status matrix](#platform-support) before assuming feature parity.
|
||||
|
||||
Access to 71 agents, 303 skills, and 97 legacy command shims, plus hooks, rules, memory, continuous learning, and AgentShield security scanning. The agents are specialized for planning, review, build repair, security, architecture, and domain work.
|
||||
Access to 71 agents, 304 skills, and 97 legacy command shims, plus hooks, rules, memory, continuous learning, and AgentShield security scanning. The agents are specialized for planning, review, build repair, security, architecture, and domain work.
|
||||
|
||||
| Included | Count | What it gives you |
|
||||
| ---------------- | ----------: | ------------------------------------------------------------------------------------ |
|
||||
| Agents | 71 agents | Planning, review, build repair, security, architecture, and domain work |
|
||||
| Skills | 303 skills | TDD, research, security, docs, frontend, data, ML, operations, and more |
|
||||
| Skills | 304 skills | TDD, research, security, docs, frontend, data, ML, operations, and more |
|
||||
| Commands | 97 commands | Convenient entry points while ECC moves to a skills-first surface |
|
||||
| Hooks and memory | Runtime | Enforcement, session summaries, continuous learning, instincts, and context controls |
|
||||
| Rules | Selective | Always-loaded standards you choose by language or project |
|
||||
@@ -385,7 +385,7 @@ For repo navigation, surface ownership, and PR diff packet guidance, read the [C
|
||||
|
||||
### Qoder CLI and IDE
|
||||
|
||||
ECC ships a native Qoder plugin that reuses the canonical 302 skills, 71 agents,
|
||||
ECC ships a native Qoder plugin that reuses the canonical skills, agents,
|
||||
95 legacy command shims, and the pinned `chrome-devtools` MCP definition. From a
|
||||
reviewed checkout, validate and install it with Qoder CLI:
|
||||
|
||||
|
||||
+1
-1
@@ -200,7 +200,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**完成!** 你现在可以使用 71 个代理、303 个技能和 97 个命令。
|
||||
**完成!** 你现在可以使用 71 个代理、304 个技能和 97 个命令。
|
||||
|
||||
### multi-* 命令需要额外配置
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Soul
|
||||
|
||||
## Core Identity
|
||||
ECC is a production-ready AI coding plugin with 71 specialized agents, 303 skills, 97 commands, and automated hook workflows for software development.
|
||||
ECC is a production-ready AI coding plugin with 71 specialized agents, 304 skills, 97 commands, and automated hook workflows for software development.
|
||||
|
||||
## Core Principles
|
||||
1. **Agent-First** — route work to the right specialist as early as possible.
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
# DeepSeek Harness Guide
|
||||
|
||||
ECC runs on the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (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 (the verified version; reverify newer releases) (`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
|
||||
<project>/.dsh/skills
|
||||
<project>/.agents/skills
|
||||
custom dirs (~/.dsh/profiles/<profile>/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/<skill-name> ~/.dsh/skills/<skill-name>
|
||||
```
|
||||
|
||||
Run this per skill with real names substituted for both placeholders (for
|
||||
example `ln -s ~/ECC/skills/tdd-workflow ~/.dsh/skills/tdd-workflow`); the
|
||||
angle brackets are placeholders, not shell syntax.
|
||||
|
||||
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-root> ECC_PLUGIN_ROOT=<ecc-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/<profile>/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 <profile> --dump-config`
|
||||
(compose errors and dropped entries are reported on stderr).
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
# Compose check (no session boot). Keep stderr visible: silently dropped or
|
||||
# rejected patch entries are reported there while the exit status stays 0.
|
||||
cd ~/.dsh && dsh --profile web --dump-config > /tmp/dump.yml
|
||||
|
||||
grep ecc-hooks /tmp/dump.yml
|
||||
|
||||
# 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'
|
||||
```
|
||||
|
||||
A successful compose does not prove the entry mounted: dropped entries and
|
||||
"is not a group" rejections only appear on stderr, never in the exit status.
|
||||
Confirm the entry id exists in the dump and that stderr was empty.
|
||||
|
||||
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 | Native discovery; individual workflows retain their harness prerequisites |
|
||||
| Hooks (23 command hooks, 5 of 7 events) | Works via the bridge; 3 events unsupported |
|
||||
| Commands (slash-command surface) | Not available (Claude Code mechanism); workflows are covered by skills |
|
||||
| Agents | Native dispatch unverified; use canonical prompt guidance only after review |
|
||||
|
||||
The validated setup here targets the CLI/web profile. Desktop profiles have a separate
|
||||
installation-owned runtime and skill discovery must be verified there independently.
|
||||
Generate hooks only from a reviewed ECC checkout; the linked checkout supplies their runtime.
|
||||
@@ -44,6 +44,7 @@ The matrix below is rendered from
|
||||
| Gemini | Instruction-backed | Gemini project-local instructions; shared skills; rules; compatibility docs | No full ECC hook parity; ecosystem ports must document drift from upstream ECC | `./install.sh --profile minimal --target gemini` | `node tests/lib/install-targets.test.js` | Treat Gemini ports as ecosystem adapters until validated end to end inside Gemini CLI. |
|
||||
| Zed | Adapter-backed | Zed project settings; flattened project rules; shared skills; commands; agents | Zed external agents and native Agent Panel permissions are not Claude hooks | `./install.sh --profile minimal --target zed` | `node tests/lib/install-targets.test.js`; `npm run harness:audit -- --format json` | Keep project settings conservative and do not copy BYOK/OpenRouter secrets into `.zed/`. |
|
||||
| dmux | Adapter-backed | session snapshots; tmux/worktree orchestration status; handoff exports | dmux is an orchestration runtime, not an install target for skills/rules | `node scripts/session-inspect.js --list-adapters`; dmux session target inspection | `node tests/lib/session-adapters.test.js` | Treat dmux events as session/runtime signals, not as a replacement for repo validation. |
|
||||
| DeepSeek Harness | Adapter-backed | canonical ECC skills (skills/) via native DSH skill roots; command hooks through the first-party @deepseek-ai/dsh-hooks-claude-code bridge (23 commands on 5 of 7 bridged events); hooks config generator (scripts/dsh/generate-hooks-config.js); profile patch mounting with compose-time validation | Claude slash commands and ECC subagent surfaces have no DSH equivalent; workflows are covered by skills; PreCompact, PostToolUseFailure, and SessionEnd hook events have no bridge counterpart; no dedicated installer target; setup is documented manual configuration | link the curated catalog: ln -s /path/to/ECC/skills/<skill> ~/.dsh/skills/<skill>; generate the hooks config: node scripts/dsh/generate-hooks-config.js --ecc-root /path/to/ECC --out ~/.dsh/ecc/hooks-dsh.json; mount a bare - insert: entry for @deepseek-ai/dsh-hooks-claude-code in ~/.dsh/profiles/<profile>/cordis.patch.yml | `node tests/scripts/dsh-generate-hooks-config.test.js`; `node scripts/ci/validate-skills.js`; `dsh --profile <profile> --dump-config` (compose-time validation) | Hook commands embed the ECC checkout path; regenerate the hooks config after moving the repo; Keep the tool-name matcher mapping (Bash to bash, Edit/MultiEdit to edit) in sync with DSH tool names; A plain top-level patch entry is silently dropped; additions require a bare insert entry |
|
||||
| Orca | Reference-only | worktree lifecycle; review state; notification; provider-identity design pressure | No ECC installer or direct adapter today | Use as a comparison target for worktree/session state requirements | `npm run observability:ready` | Do not import product-specific assumptions; convert lessons into ECC event fields. |
|
||||
| Superset | Reference-only | workspace presets; parallel-agent review loops; worktree isolation design pressure | No ECC installer or direct adapter today | Use as a comparison target for workspace preset taxonomy | `npm run observability:ready` | Keep ECC portable; do not require a desktop workspace to get basic value. |
|
||||
| Ghast | Reference-only | terminal-native pane grouping; cwd grouping; search; notifications | No ECC installer or direct adapter today | Use as a comparison target for terminal-first session grouping | `node scripts/session-inspect.js --list-adapters` | Preserve terminal ergonomics before adding visual UI assumptions. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ECC — 智能体指令
|
||||
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 71 个专业代理、303 项技能、97 条命令以及自动化钩子工作流,用于软件开发。
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 71 个专业代理、304 项技能、97 条命令以及自动化钩子工作流,用于软件开发。
|
||||
|
||||
**版本:** 2.2.3
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
```
|
||||
agents/ — 71 个专业子代理
|
||||
skills/ — 303 个工作流技能和领域知识
|
||||
skills/ — 304 个工作流技能和领域知识
|
||||
commands/ — 97 个斜杠命令
|
||||
hooks/ — 基于触发的自动化
|
||||
rules/ — 始终遵循的指导方针(通用 + 每种语言)
|
||||
|
||||
@@ -264,7 +264,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**搞定!** 你现在可以使用 71 个智能体、303 项技能和 97 个命令了。
|
||||
**搞定!** 你现在可以使用 71 个智能体、304 项技能和 97 个命令了。
|
||||
|
||||
***
|
||||
|
||||
@@ -1178,7 +1178,7 @@ opencode
|
||||
|---------|---------------|----------|--------|
|
||||
| 智能体 | PASS: 71 个 | PASS: 12 个 | **Claude Code 领先** |
|
||||
| 命令 | PASS: 97 个 | PASS: 35 个 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 303 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 304 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
|
||||
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
|
||||
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
|
||||
@@ -1286,7 +1286,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
||||
|---------|-----------------------|------------|-----------|----------|
|
||||
| **智能体** | 71 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
||||
| **命令** | 97 | 共享 | 基于指令 | 35 |
|
||||
| **技能** | 303 | 共享 | 10 (原生格式) | 37 |
|
||||
| **技能** | 304 | 共享 | 10 (原生格式) | 37 |
|
||||
| **钩子事件** | 8 种类型 | 15 种类型 | SessionStart(1 种类型) | 11 种类型 |
|
||||
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | 1 个 SessionStart 引导脚本 | 插件钩子 |
|
||||
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"id": "hand-seeded",
|
||||
"source": "manual-curation-pending-regeneration"
|
||||
},
|
||||
"registryDigest": "cc5b64b326164acd4241f1ccd74dd0cd75a92550afcc76561e02a3e4eb2eb240",
|
||||
"registryDigest": "280c821f48ecc538706e75d13175ec7707ade0d307b74f3c865d29edc6f39167",
|
||||
"schemaVersion": 1,
|
||||
"triggers": {
|
||||
"skill:api-connector-builder": [
|
||||
|
||||
@@ -358,6 +358,7 @@
|
||||
"skills/agent-introspection-debugging",
|
||||
"skills/ai-regression-testing",
|
||||
"skills/configure-ecc",
|
||||
"skills/deepseek-harness-setup",
|
||||
"skills/code-tour",
|
||||
"skills/continuous-learning",
|
||||
"skills/continuous-learning-v2",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"scripts/consult.js",
|
||||
"scripts/profile.js",
|
||||
"scripts/astra-review.js",
|
||||
"scripts/dsh/generate-hooks-config.js",
|
||||
"scripts/auto-update.js",
|
||||
"scripts/claw.js",
|
||||
"scripts/control-pane.js",
|
||||
@@ -226,6 +227,7 @@
|
||||
"skills/data-scraper-agent/",
|
||||
"skills/database-migrations/",
|
||||
"skills/deep-research/",
|
||||
"skills/deepseek-harness-setup/",
|
||||
"skills/defi-amm-security/",
|
||||
"skills/deployment-patterns/",
|
||||
"skills/dev-team/",
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/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 <dir>] [--out <file>]
|
||||
*
|
||||
* Defaults:
|
||||
* --ecc-root directory of this checkout (repo root)
|
||||
* --out <ecc-root>/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' || a === '--out') {
|
||||
const value = argv[++i];
|
||||
if (value === undefined || value.startsWith('--')) {
|
||||
process.stderr.write('Missing value for ' + a + '\n');
|
||||
process.exit(2);
|
||||
}
|
||||
if (a === '--ecc-root') opts.eccRoot = value;
|
||||
else opts.out = value;
|
||||
}
|
||||
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 === '' || /^\^?mcp__/.test(v) || /[\\^$.*+?()[\]{}]/.test(v)) 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 "..." <hook-id> <script> [args]`)
|
||||
* that resolves scripts/hooks/plugin-hook-bootstrap.js from the
|
||||
* CLAUDE_PLUGIN_ROOT environment variable, so prefixing the variables
|
||||
* inline (env-prefix form) is the only required change.
|
||||
*/
|
||||
function shellQuote(value) {
|
||||
return "'" + String(value).replace(/'/g, "'\\''") + "'";
|
||||
}
|
||||
|
||||
function mapCommand(command, eccRoot) {
|
||||
const prefix = 'CLAUDE_PLUGIN_ROOT=' + shellQuote(eccRoot) +
|
||||
' ECC_PLUGIN_ROOT=' + shellQuote(eccRoot) + ' ';
|
||||
return prefix + command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the ECC hooks document into the DSH bridge format.
|
||||
* Returns { config, report } where report lists skipped events/entries.
|
||||
*/
|
||||
function generateConfig(eccRoot, sourceDoc) {
|
||||
const out = { hooks: {} };
|
||||
const report = [];
|
||||
for (const entry of Object.entries(sourceDoc.hooks || {})) {
|
||||
const event = entry[0];
|
||||
const groups = entry[1];
|
||||
if (!SUPPORTED_EVENTS.has(event)) {
|
||||
report.push('skip event ' + event + ' (not bridged by DSH)');
|
||||
continue;
|
||||
}
|
||||
const outGroups = [];
|
||||
for (const group of groups) {
|
||||
const hooks = [];
|
||||
for (const h of group.hooks || []) {
|
||||
if (h.type !== 'command' || typeof h.command !== 'string') {
|
||||
report.push('skip non-command hook on ' + event);
|
||||
continue;
|
||||
}
|
||||
const mapped = { type: 'command', command: mapCommand(h.command, eccRoot) };
|
||||
if (typeof h.timeout === 'number') mapped.timeout = h.timeout;
|
||||
hooks.push(mapped);
|
||||
report.push('ok ' + event);
|
||||
}
|
||||
if (hooks.length) {
|
||||
outGroups.push({ matcher: mapMatcher(group.matcher), hooks: hooks });
|
||||
}
|
||||
}
|
||||
if (outGroups.length) out.hooks[event] = outGroups;
|
||||
}
|
||||
return { config: out, report: report };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const opts = parseArgs(process.argv);
|
||||
const eccRoot = path.resolve(opts.eccRoot || path.join(__dirname, '..', '..'));
|
||||
const sourcePath = path.join(eccRoot, 'hooks', 'hooks.json');
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
process.stderr.write('ECC hooks.json not found at ' + sourcePath + '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
let doc;
|
||||
try {
|
||||
doc = JSON.parse(fs.readFileSync(sourcePath, 'utf8'));
|
||||
} catch (err) {
|
||||
process.stderr.write('Failed to parse ' + sourcePath + ': ' + err.message + '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
const result = generateConfig(eccRoot, doc);
|
||||
const outPath = opts.out || path.join(eccRoot, 'hooks', 'hooks.dsh.json');
|
||||
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
||||
fs.writeFileSync(outPath, JSON.stringify(result.config, null, 2) + '\n');
|
||||
const okCount = result.report.filter(function (r) { return r.indexOf('ok ') === 0; }).length;
|
||||
const notes = result.report.filter(function (r) { return r.indexOf('ok ') !== 0; });
|
||||
process.stderr.write('wrote ' + outPath + '\n');
|
||||
process.stderr.write('hook commands: ' + okCount + '\n');
|
||||
if (notes.length) process.stderr.write(notes.join('; ') + '\n');
|
||||
}
|
||||
|
||||
module.exports = { generateConfig, mapMatcher, mapCommand, shellQuote, SUPPORTED_EVENTS, TOOL_NAME_MAP };
|
||||
|
||||
if (require.main === module) main();
|
||||
@@ -258,6 +258,45 @@ const ADAPTER_RECORDS = Object.freeze([
|
||||
'tests/lib/session-adapters.test.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'deepseek-harness',
|
||||
harness: 'DeepSeek Harness',
|
||||
state: 'Adapter-backed',
|
||||
supported_assets: [
|
||||
'canonical ECC skills (skills/) via native DSH skill roots',
|
||||
'command hooks through the first-party @deepseek-ai/dsh-hooks-claude-code bridge (23 commands on 5 of 7 bridged events)',
|
||||
'hooks config generator (scripts/dsh/generate-hooks-config.js)',
|
||||
'profile patch mounting with compose-time validation',
|
||||
],
|
||||
unsupported_surfaces: [
|
||||
'Claude slash commands and ECC subagent surfaces have no DSH equivalent; workflows are covered by skills',
|
||||
'PreCompact, PostToolUseFailure, and SessionEnd hook events have no bridge counterpart',
|
||||
'no dedicated installer target; setup is documented manual configuration',
|
||||
],
|
||||
install_or_onramp: [
|
||||
'link the curated catalog: ln -s /path/to/ECC/skills/<skill> ~/.dsh/skills/<skill>',
|
||||
'generate the hooks config: node scripts/dsh/generate-hooks-config.js --ecc-root /path/to/ECC --out ~/.dsh/ecc/hooks-dsh.json',
|
||||
'mount a bare - insert: entry for @deepseek-ai/dsh-hooks-claude-code in ~/.dsh/profiles/<profile>/cordis.patch.yml',
|
||||
],
|
||||
verification_commands: [
|
||||
'`node tests/scripts/dsh-generate-hooks-config.test.js`',
|
||||
'`node scripts/ci/validate-skills.js`',
|
||||
'`dsh --profile <profile> --dump-config` (compose-time validation)',
|
||||
],
|
||||
risk_notes: [
|
||||
'Hook commands embed the ECC checkout path; regenerate the hooks config after moving the repo',
|
||||
'Keep the tool-name matcher mapping (Bash to bash, Edit/MultiEdit to edit) in sync with DSH tool names',
|
||||
'A plain top-level patch entry is silently dropped; additions require a bare insert entry',
|
||||
],
|
||||
last_verified_at: '2026-10-01',
|
||||
owner: 'ECC maintainers',
|
||||
source_docs: [
|
||||
'docs/DEEPSEEK-HARNESS-GUIDE.md',
|
||||
'skills/deepseek-harness-setup/SKILL.md',
|
||||
'scripts/dsh/generate-hooks-config.js',
|
||||
'tests/scripts/dsh-generate-hooks-config.test.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'orca',
|
||||
harness: 'Orca',
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
name: deepseek-harness-setup
|
||||
description: "Install and operate Everything Claude Code (ECC) on the DeepSeek Harness (DSH): native skill roots (~/.dsh/skills, .agents/skills), the @deepseek-ai/dsh-hooks-claude-code bridge for command hooks, bare-insert patch mounting, generator usage, event-support limits, and update workflow. Use when setting up ECC on DeepSeek Harness, wiring ECC hooks into a DSH profile, or debugging why ECC skills or hooks do not appear in a DSH session."
|
||||
metadata:
|
||||
origin: community
|
||||
---
|
||||
|
||||
# DeepSeek Harness Setup
|
||||
|
||||
Run ECC on the DeepSeek Harness. Full guide: `docs/DEEPSEEK-HARNESS-GUIDE.md`.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Installing ECC skills or hooks on a DeepSeek Harness install.
|
||||
- Deciding which ECC surfaces work on DSH and which do not.
|
||||
- Debugging a DSH session that does not show ECC skills or does not fire ECC hooks.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Skills need no conversion.** DSH reads `SKILL.md` frontmatter
|
||||
(name/description) from native skill roots. Link the ECC catalog once:
|
||||
|
||||
```bash
|
||||
ln -s /path/to/ECC/skills/<skill-name> ~/.dsh/skills/<skill-name>
|
||||
```
|
||||
|
||||
Root priority (project `.dsh` and `.agents` roots beat `~/.dsh`): skip
|
||||
linking skills whose names already exist in a higher-priority root.
|
||||
Substitute real skill names for the placeholders; the angle brackets are
|
||||
not shell syntax.
|
||||
|
||||
2. **Hooks go through the first-party bridge.** Generate a DSH-format config
|
||||
from the canonical ECC hooks file:
|
||||
|
||||
```bash
|
||||
node scripts/dsh/generate-hooks-config.js --ecc-root /path/to/ECC --out ~/.dsh/ecc/hooks-dsh.json
|
||||
```
|
||||
|
||||
The generator lower-cases tool-name matchers, prefixes
|
||||
`CLAUDE_PLUGIN_ROOT`/`ECC_PLUGIN_ROOT` inline (the bridge does not export
|
||||
the plugin root), preserves timeouts, and skips events DSH cannot bridge:
|
||||
`PreCompact`, `PostToolUseFailure`, `SessionEnd`.
|
||||
|
||||
3. **Mount with a bare insert** in `~/.dsh/profiles/<profile>/cordis.patch.yml`:
|
||||
|
||||
```yaml
|
||||
- insert:
|
||||
- id: ecc-hooks-claude-code
|
||||
name: "@deepseek-ai/dsh-hooks-claude-code"
|
||||
config:
|
||||
configPath: /absolute/path/to/hooks-dsh.json
|
||||
```
|
||||
|
||||
Loader rules that cost debugging time when learned the hard way:
|
||||
a plain `- id:` top-level entry is silently dropped; `insert` under an
|
||||
existing non-group id is rejected as "not a group"; first-party plugin
|
||||
packages need no profile `package.json` entry.
|
||||
|
||||
4. **Validate by composing, then verify live.**
|
||||
|
||||
```bash
|
||||
cd ~/.dsh && dsh --profile web --dump-config > /tmp/dump.yml
|
||||
grep ecc-hooks /tmp/dump.yml
|
||||
```
|
||||
|
||||
Compose exit status stays 0 even when the loader drops or rejects an
|
||||
entry — those problems are only reported on stderr. Confirm the entry id
|
||||
appears in the dump and stderr is empty. A live GateGuard denial on the
|
||||
session's first edit/write/bash is the visible signal the bridge is
|
||||
active.
|
||||
|
||||
## Updating
|
||||
|
||||
`git pull` reflects into linked skills immediately; regenerate the hooks
|
||||
config with the same generator command. If the ECC checkout moves, regenerate
|
||||
(the path is embedded in every hook command) and relink skills.
|
||||
|
||||
## Limits
|
||||
|
||||
| Surface | Status on DSH |
|
||||
|---------|---------------|
|
||||
| Skills | Native discovery; workflows may require host adaptation |
|
||||
| Hooks | 23 command hooks on 5 of 7 bridge events |
|
||||
| Slash commands | Not available (Claude Code mechanism); workflows covered by skills |
|
||||
| Agents | Native dispatch unverified; use canonical prompt guidance only after review |
|
||||
|
||||
The validated setup here targets the CLI/web profile. Desktop profiles have a separate
|
||||
installation-owned runtime and skill discovery must be verified there independently.
|
||||
Generate hooks only from a reviewed ECC checkout; the linked checkout supplies their runtime.
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Tests for scripts/dsh/generate-hooks-config.js
|
||||
*
|
||||
* Run with: node tests/scripts/dsh-generate-hooks-config.test.js
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const gen = require('../../scripts/dsh/generate-hooks-config');
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(' \u2713 ' + name);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(' \u2717 ' + name);
|
||||
console.log(' Error: ' + err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
if (test('mapMatcher maps Claude tool names to DSH lower-case names', () => {
|
||||
assert.strictEqual(gen.mapMatcher('Bash'), 'bash');
|
||||
assert.strictEqual(gen.mapMatcher('Edit|MultiEdit'), 'edit');
|
||||
assert.strictEqual(gen.mapMatcher('Write|Edit|MultiEdit'), 'write|edit');
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('mapMatcher keeps regex passthrough tokens untouched', () => {
|
||||
assert.strictEqual(gen.mapMatcher('.*'), '.*');
|
||||
assert.strictEqual(gen.mapMatcher('^mcp__'), '^mcp__');
|
||||
assert.strictEqual(gen.mapMatcher('.*|Task'), '.*|task');
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('mapCommand prefixes plugin-root env vars and keeps the resolver intact', () => {
|
||||
const cmd = 'node -e "resolver" pretooluse:bash:check-console-log scripts/hooks/check-console-log.js standard';
|
||||
const out = gen.mapCommand(cmd, '/ecc');
|
||||
assert.strictEqual(out, 'CLAUDE_PLUGIN_ROOT=\'/ecc\' ECC_PLUGIN_ROOT=\'/ecc\' ' + cmd);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('mapCommand passes through non-resolver commands with prefix only', () => {
|
||||
const out = gen.mapCommand('echo hi', '/ecc');
|
||||
assert.strictEqual(out, 'CLAUDE_PLUGIN_ROOT=\'/ecc\' ECC_PLUGIN_ROOT=\'/ecc\' echo hi');
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('generateConfig skips unsupported events and reports them', () => {
|
||||
const doc = { hooks: {
|
||||
PreToolUse: [{ matcher: 'Bash', hooks: [{ type: 'command', command: 'node -e "x" node scripts/hooks/a.js' }] }],
|
||||
PreCompact: [{ matcher: '.*', hooks: [{ type: 'command', command: 'echo hi' }] }],
|
||||
SessionEnd: [{ matcher: '.*', hooks: [{ type: 'command', command: 'echo hi' }] }],
|
||||
} };
|
||||
const { config, report } = gen.generateConfig('/ecc', doc);
|
||||
assert.deepStrictEqual(Object.keys(config.hooks), ['PreToolUse']);
|
||||
assert.ok(report.some(r => r.includes('PreCompact')));
|
||||
assert.ok(report.some(r => r.includes('SessionEnd')));
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('generateConfig preserves timeout and maps matcher groups', () => {
|
||||
const doc = { hooks: {
|
||||
PostToolUse: [{ matcher: 'Edit|MultiEdit|Write', hooks: [
|
||||
{ type: 'command', command: 'node -e "x" node scripts/hooks/b.js', timeout: 30 },
|
||||
] }],
|
||||
} };
|
||||
const { config } = gen.generateConfig('/ecc', doc);
|
||||
const g = config.hooks.PostToolUse[0];
|
||||
assert.strictEqual(g.matcher, 'edit|write');
|
||||
assert.strictEqual(g.hooks[0].timeout, 30);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('generateConfig supports the bridge-only Subagent events', () => {
|
||||
assert.ok(gen.SUPPORTED_EVENTS.has('SubagentStart'));
|
||||
assert.ok(gen.SUPPORTED_EVENTS.has('SubagentStop'));
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('mapMatcher preserves case-sensitive regex tokens with metacharacters', () => {
|
||||
assert.strictEqual(gen.mapMatcher('^mcp__GitHub__.*'), '^mcp__GitHub__.*');
|
||||
assert.strictEqual(gen.mapMatcher('\\S'), '\\S');
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('mapCommand shell-quotes dangerous path characters', () => {
|
||||
const dollar = gen.mapCommand('echo hi', '/home/u/$proj');
|
||||
assert.ok(dollar.startsWith("CLAUDE_PLUGIN_ROOT='/home/u/$proj' "), 'literal inside single quotes: ' + dollar);
|
||||
const quoted = gen.mapCommand('echo hi', "/home/u'z");
|
||||
assert.ok(quoted.startsWith("CLAUDE_PLUGIN_ROOT='/home/u'\\''z' "), 'embedded quote escaped: ' + quoted);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('CLI rejects missing flag values with exit code 2', () => {
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
const res = spawnSync('node', [
|
||||
path.join(repoRoot, 'scripts', 'dsh', 'generate-hooks-config.js'),
|
||||
'--ecc-root',
|
||||
], { encoding: 'utf8' });
|
||||
assert.strictEqual(res.status, 2, 'exit 2 on missing value, stderr: ' + res.stderr);
|
||||
assert.ok(res.stderr.includes('Missing value for --ecc-root'));
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('CLI end-to-end writes valid JSON to --out', () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-dsh-'));
|
||||
const outPath = path.join(tmp, 'hooks.dsh.json');
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
const res = spawnSync('node', [
|
||||
path.join(repoRoot, 'scripts', 'dsh', 'generate-hooks-config.js'),
|
||||
'--ecc-root', repoRoot, '--out', outPath,
|
||||
], { encoding: 'utf8' });
|
||||
assert.strictEqual(res.status, 0, 'CLI exit 0, stderr: ' + res.stderr);
|
||||
const doc = JSON.parse(fs.readFileSync(outPath, 'utf8'));
|
||||
assert.ok(doc.hooks.PreToolUse.length > 0, 'PreToolUse groups present');
|
||||
assert.ok(Object.keys(doc.hooks).includes('SessionStart'));
|
||||
assert.ok(!Object.keys(doc.hooks).includes('PreCompact'), 'unsupported event excluded');
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('');
|
||||
console.log('Passed: ' + passed + ', Failed: ' + failed);
|
||||
return failed === 0;
|
||||
}
|
||||
|
||||
process.exit(runTests() ? 0 : 1);
|
||||
@@ -75,6 +75,7 @@ function buildExpectedPublishPaths(repoRoot) {
|
||||
"scripts/ci/validate-skills.js",
|
||||
"scripts/consult.js",
|
||||
"scripts/astra-review.js",
|
||||
"scripts/dsh/generate-hooks-config.js",
|
||||
"scripts/profile.js",
|
||||
"scripts/control-pane.js",
|
||||
"scripts/dashboard-web.js",
|
||||
@@ -235,6 +236,7 @@ function main() {
|
||||
"scripts/ci/validate-skills.js",
|
||||
"scripts/consult.js",
|
||||
"scripts/astra-review.js",
|
||||
"scripts/dsh/generate-hooks-config.js",
|
||||
"scripts/profile.js",
|
||||
"scripts/lib/context-profiles.js",
|
||||
"scripts/lib/context-pack-registry.js",
|
||||
|
||||
Reference in New Issue
Block a user