#!/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' || 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 "..."