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