mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-26 18:35:05 +00:00
feat(install): hermes-hooks install module — installer-native delivery
Greptile's review asked for committed coverage and installer parity; this makes the hooks installable through ECC's own machinery instead of a copy-paste doc: - manifests/install-modules.json: new module hermes-hooks (kind hooks, targets hermes + openclaw, paths hooks/hermes + docs/HERMES-HOOKS.md, light/stable, defaultInstall). Disjoint from hooks-runtime, which stays Claude/Cursor/OpenCode/CodeBuddy — the Hermes target gets hooks support without inheriting the Claude-format hook graph. - manifests/install-profiles.json: full profile gains hermes-hooks (validator requires full to include every module). - docs/HERMES-HOOKS.md: install section now leads with ./install.sh --target hermes --modules hermes-hooks --enable-hooks; the manual block registers from the landed ~/.hermes/hooks/hermes/ paths. The installer never edits config.yaml — registration stays operator-owned. - tests/hermes-hooks.test.js: installer-integration case (14 total) drives the real installer into a throwaway HOME and proves the LANDED guard blocks an eslint.config edit. Validated: install-manifests (37 modules), no-personal-paths, markdownlint, 14/14 tests. E2E on a throwaway HOME: module lands both scripts + doc and the landed guard emits a block decision.
This commit is contained in:
1 parent
b30a8c782a
commit
df77893e0a
4 files changed
+56
-10
No files matched your search
+14
-9
@@ -30,18 +30,23 @@ These two are harness-independent policy:
|
||||
|
||||
## Install
|
||||
|
||||
Preferred — through the ECC installer (module `hermes-hooks`):
|
||||
|
||||
```bash
|
||||
# 1. Put the scripts anywhere stable (they are referenced by absolute path):
|
||||
mkdir -p ~/.hermes/hooks
|
||||
cp hooks/hermes/config-protection.py ~/.hermes/hooks/ecc-config-protection.py
|
||||
cp hooks/hermes/check-console-log.py ~/.hermes/hooks/ecc-check-console-log.py
|
||||
chmod +x ~/.hermes/hooks/ecc-*.py
|
||||
./install.sh --target hermes --modules hermes-hooks --enable-hooks
|
||||
# or: node scripts/install-apply.js --target hermes --modules hermes-hooks --enable-hooks
|
||||
```
|
||||
|
||||
# 2. Point the commands at the copies you just made. Substitute YOUR absolute
|
||||
# home path — Hermes resolves hook commands verbatim, without ~ expansion.
|
||||
HOOK_DIR="$HOME/.hermes/hooks"
|
||||
This lands `hooks/hermes/*.py` under `~/.hermes/hooks/hermes/` and this guide
|
||||
under `~/.hermes/docs/`. Then register the hooks (the installer deliberately
|
||||
never edits your `config.yaml`):
|
||||
|
||||
# 3. Append to ~/.hermes/config.yaml (Hermes wants the mapping keyed by event):
|
||||
```bash
|
||||
# Scripts are already at ~/.hermes/hooks/hermes/. Point the commands at them —
|
||||
# Hermes resolves hook commands verbatim, without ~ expansion.
|
||||
HOOK_DIR="$HOME/.hermes/hooks/hermes"
|
||||
|
||||
# Append to ~/.hermes/config.yaml (Hermes wants the mapping keyed by event):
|
||||
cat >> ~/.hermes/config.yaml <<EOF
|
||||
hooks:
|
||||
pre_tool_call:
|
||||
|
||||
@@ -106,6 +106,23 @@
|
||||
"cost": "medium",
|
||||
"stability": "stable"
|
||||
},
|
||||
{
|
||||
"id": "hermes-hooks",
|
||||
"kind": "hooks",
|
||||
"description": "Hermes-native guard hooks (config protection, console.log warning) plus install guide.",
|
||||
"paths": [
|
||||
"hooks/hermes",
|
||||
"docs/HERMES-HOOKS.md"
|
||||
],
|
||||
"targets": [
|
||||
"hermes",
|
||||
"openclaw"
|
||||
],
|
||||
"dependencies": [],
|
||||
"defaultInstall": true,
|
||||
"cost": "light",
|
||||
"stability": "stable"
|
||||
},
|
||||
{
|
||||
"id": "platform-configs",
|
||||
"kind": "platform",
|
||||
|
||||
@@ -98,7 +98,8 @@
|
||||
"devops-infra",
|
||||
"machine-learning",
|
||||
"supply-chain-domain",
|
||||
"document-processing"
|
||||
"document-processing",
|
||||
"hermes-hooks"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,5 +237,28 @@ run('unreadable file: skips AND reports on stderr', () => {
|
||||
assert.ok(/not a regular file|skipping/.test(stderr), 'diagnostic required');
|
||||
});
|
||||
|
||||
|
||||
// --- installer integration: the hermes-hooks module installs and the landed copy runs ---
|
||||
run('hermes-hooks module installs via the real installer and the landed guard blocks', () => {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-mod-'));
|
||||
const inst = spawnSync('node', ['scripts/install-apply.js', '--target', 'hermes', '--modules', 'hermes-hooks', '--enable-hooks'], {
|
||||
cwd: repoRoot, encoding: 'utf8', env: { ...process.env, HOME: home }, timeout: 60000,
|
||||
});
|
||||
assert.strictEqual(inst.status, 0, 'installer must exit 0');
|
||||
const landed = path.join(home, '.hermes', 'hooks', 'hermes', 'config-protection.py');
|
||||
assert.ok(fs.existsSync(landed), 'guard must land under ~/.hermes/hooks/hermes/');
|
||||
assert.ok(fs.existsSync(path.join(home, '.hermes', 'docs', 'HERMES-HOOKS.md')), 'doc must land');
|
||||
const t = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-guard-'));
|
||||
const target = path.join(t, 'eslint.config.mjs');
|
||||
fs.writeFileSync(target, 'export default {}\n');
|
||||
const g = spawnSync('python3', [landed], {
|
||||
input: JSON.stringify({ hook_event_name: 'pre_tool_call', tool_name: 'patch', tool_input: { path: target, old_string: 'a', new_string: 'b' } }),
|
||||
encoding: 'utf8', timeout: 15000,
|
||||
});
|
||||
assert.ok((g.stdout || '').includes('"decision": "block"'), 'landed guard must block');
|
||||
fs.rmSync(home, { recursive: true, force: true });
|
||||
fs.rmSync(t, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
if (failed > 0) process.exit(1);
|
||||
Reference in new issue
Block a user