Skip to content

Commit b6a5fd1

Browse files
committed
feat(hooks): add enchanter-hooks plugin — 11 advisory fail-open Claude Code hooks
A new vis package (enchanter-hooks) shipping 11 deterministic, fail-open, quiet hooks that enforce the conduct substrate at lifecycle events in-context conduct can't self-time. Distributed via the vis marketplace (/plugin install enchanter-hooks@vis) — hooks activate natively, no settings.json editing. Hooks (event -> F-code): compact-checkpoint SessionStart(compact) F03 secret-scan PreToolUse(Write|Edit) secret-exfil config-self-edit-guard PreToolUse(Write|Edit) self-modification substrate-engine-write-guard PreToolUse(Write|Edit) F24 artifact-authorship-guard PreToolUse(Write|Edit) authorship append-only-log-edit-guard PreToolUse(Edit) F14 reversibility-guard PreToolUse(Bash) F26 debug-hygiene PostToolUse(Write|Edit) code-hygiene post-write-validate PostToolUse(Write|Edit) F02/F14 stale-pathref-guard PostToolUse(Write|Edit) F02/F27 machine-path-leak-guard PostToolUse(Write|Edit) F02 Each clears a 5-part smart-test (deterministic, does-what-conduct-can't, advisory/fail-open, non-redundant, quiet) and is verified incl. false-positive + fail-open audits. Built from an open-web hook survey + a per-package mining sweep of the vis conduct substrate. Also: register in marketplace.json (8 plugins), list in README, and add .gitattributes forcing LF on *.sh so marketplace-cloned hooks don't break on Windows CRLF checkout.
1 parent 19c00b5 commit b6a5fd1

17 files changed

Lines changed: 479 additions & 8 deletions

.claude-plugin/marketplace.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vis",
33
"owner": { "name": "enchanter-ai" },
44
"metadata": {
5-
"description": "Behavioral substrate for the @enchanter-ai ecosystem — conduct-as-code, failure taxonomy, engines, recipes. Split into 7 focused plugins.",
5+
"description": "Behavioral substrate for the @enchanter-ai ecosystem — conduct-as-code, failure taxonomy, engines, recipes. Split into 8 focused plugins.",
66
"version": "0.6.0",
77
"pluginRoot": "./packages"
88
},
@@ -13,6 +13,7 @@
1313
{ "name": "enchanter-web", "source": "web", "version": "0.6.0", "description": "Live web access conduct: cache, dedup, budget, cite hygiene." },
1414
{ "name": "enchanter-memory", "source": "memory", "version": "0.6.0", "description": "Cross-session memory hygiene: working memory, decay, recall verification." },
1515
{ "name": "enchanter-cost", "source": "cost", "version": "0.6.0", "description": "Session economics: cost-accounting, latency-budgeting, eval-harnesses recipe." },
16-
{ "name": "enchanter-safety", "source": "safety", "version": "0.6.0", "description": "Safety + compliance + security + operator-wiring: refusal-and-recovery, F15-F21 taxonomy, FedRAMP/ISO/SOC2/NIST evidence, pentest, synthetic-fire, OTLP/PagerDuty/Sentry wiring." }
16+
{ "name": "enchanter-safety", "source": "safety", "version": "0.6.0", "description": "Safety + compliance + security + operator-wiring: refusal-and-recovery, F15-F21 taxonomy, FedRAMP/ISO/SOC2/NIST evidence, pentest, synthetic-fire, OTLP/PagerDuty/Sentry wiring." },
17+
{ "name": "enchanter-hooks", "source": "hooks", "version": "0.6.0", "description": "Advisory Claude Code hooks (fail-open, deterministic, quiet) that enforce the conduct substrate at lifecycle events: post-compaction checkpoint (F03), pre-write secret scan, post-edit debug-artifact hygiene. Install activates hooks natively — no settings.json editing." }
1718
]
1819
}

.gitattributes

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Shell scripts must stay LF on every platform. They run via bash — including the
2+
# enchanter-hooks plugin scripts, which are distributed through the vis marketplace and
3+
# cloned into users' plugin caches (~/.claude/plugins/cache) on Windows too. A CRLF
4+
# checkout makes bash fail on the trailing \r, so force LF regardless of core.autocrlf.
5+
*.sh text eol=lf
6+
7+
# Keep these LF as well for cross-platform consistency of the distributed package.
8+
*.json text eol=lf
9+
*.md text eol=lf

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ The behavioral substrate for building durable AI agents — conduct, engines, ta
2929

3030
**In plain English:** Most agent stacks ship with prompts, tools, and hopes. The thing that actually keeps an agent from refactoring code you didn't ask it to touch, or pushing to main after you said not to, isn't another tool — it's a behavior rule that survives the long context. vis is the dependency-free pile of those rules, plus the math, taxonomy, and host recipes around them.
3131

32-
**Technically:** 37 conduct modules across 7 packages (`core` / `skills` / `orchestration` / `safety` / `web` / `memory` / `cost`). 12 algorithmic engines with paper-backed derivations (Aho-Corasick pattern detection, Shannon entropy, Beta-Bernoulli trust scoring, Markov drift, Hunt-Szymanski LCS, Zhang-Shasha tree-edit, Tarjan SCC, Wald SPRT, Jaccard-cosine boundary segmentation, contextual LLM bandit, agentproof DFA, sycophancy calibration). 21 named failure codes (F01–F21) with testable counters, mapped to a 5-axis hybrid taxonomy (memory / reflection / planning / action / system) and 21 incident-response runbooks. 9 adoption recipes (Claude Code, OpenAI Agents SDK, Cursor, LangChain, Pydantic-AI, BAML, raw system-prompt, eval-harnesses, stupid-agent-review). Zero runtime dependencies — pure prose + math, loadable into any system that accepts text instructions.
32+
**Technically:** 37 conduct modules across 7 conduct packages (`core` / `skills` / `orchestration` / `safety` / `web` / `memory` / `cost`), plus a `hooks` package shipping 6 runtime advisory hooks (the **enchanter-hooks** plugin, installable via the vis marketplace). 12 algorithmic engines with paper-backed derivations (Aho-Corasick pattern detection, Shannon entropy, Beta-Bernoulli trust scoring, Markov drift, Hunt-Szymanski LCS, Zhang-Shasha tree-edit, Tarjan SCC, Wald SPRT, Jaccard-cosine boundary segmentation, contextual LLM bandit, agentproof DFA, sycophancy calibration). 21 named failure codes (F01–F21) with testable counters, mapped to a 5-axis hybrid taxonomy (memory / reflection / planning / action / system) and 21 incident-response runbooks. 9 adoption recipes (Claude Code, OpenAI Agents SDK, Cursor, LangChain, Pydantic-AI, BAML, raw system-prompt, eval-harnesses, stupid-agent-review). Zero runtime dependencies — pure prose + math, loadable into any system that accepts text instructions.
3333

3434
## Origin
3535

@@ -132,9 +132,14 @@ vis/
132132
│ │ adaptive-adversary-baseline, provider-resilience
133133
│ ├── memory/ ← memory hygiene
134134
│ │ └── conduct/ ← memory-hygiene.md
135-
│ └── cost/ ← cost + latency + eval harnesses
136-
│ ├── conduct/ ← cost-accounting.md, latency-budgeting.md
137-
│ └── recipes/ ← eval-harnesses.md
135+
│ ├── cost/ ← cost + latency + eval harnesses
136+
│ │ ├── conduct/ ← cost-accounting.md, latency-budgeting.md
137+
│ │ └── recipes/ ← eval-harnesses.md
138+
│ └── hooks/ ← runtime advisory hooks (the enchanter-hooks plugin)
139+
│ ├── hooks/hooks.json ← 6 hooks: SessionStart(compact) / PreToolUse / PostToolUse
140+
│ ├── scripts/ ← compact-checkpoint, secret-scan, config-self-edit-guard,
141+
│ │ reversibility-guard, debug-hygiene, post-write-validate
142+
│ └── .claude-plugin/ ← plugin.json (installable via the vis marketplace)
138143
├── docs/ ← cross-cutting docs: architecture overview, ADRs
139144
│ (0001 four-layers, 0002 taxonomy expansion),
140145
│ CROSS_REPO_VERSIONING.md
@@ -143,7 +148,7 @@ vis/
143148
└── package.json ← changesets meta-package for cross-repo versioning
144149
```
145150

146-
Counts as of the latest tag: **37 conduct modules** across core / skills / orchestration / safety / web / memory / cost · **12 engines** in `orchestration/engines/` · **21 failure codes** split F01–F14 (core) and F15–F21 (safety) · **21 runbooks** mirroring the F-codes · **9 recipes** (8 in `skills/recipes/` + `cost/recipes/eval-harnesses.md`).
151+
Counts as of the latest tag: **37 conduct modules** across core / skills / orchestration / safety / web / memory / cost · **12 engines** in `orchestration/engines/` · **21 failure codes** split F01–F14 (core) and F15–F21 (safety) · **21 runbooks** mirroring the F-codes · **9 recipes** (8 in `skills/recipes/` + `cost/recipes/eval-harnesses.md`) · **6 runtime advisory hooks** in `hooks/` (the **enchanter-hooks** plugin).
147152

148153
---
149154

@@ -213,7 +218,7 @@ In your project's `CLAUDE.md`:
213218
- @shared/vis/packages/core/conduct/failure-modes.md
214219
```
215220

216-
For runtime enforcement (not just description), wire hooks per [`packages/skills/recipes/claude-code.md`](packages/skills/recipes/claude-code.md) § Enforcement wiring. The framework now includes copy-paste shell skeletons in [`packages/core/conduct/hooks.md`](packages/core/conduct/hooks.md) § Starter patterns — PreToolUse deny, PostToolUse inject, Stop notify.
221+
For runtime enforcement (not just description), wire hooks per [`packages/skills/recipes/claude-code.md`](packages/skills/recipes/claude-code.md) § Enforcement wiring. The framework now includes copy-paste shell skeletons in [`packages/core/conduct/hooks.md`](packages/core/conduct/hooks.md) § Starter patterns — PreToolUse deny, PostToolUse inject, Stop notify. Or install them ready-made — `/plugin marketplace add enchanter-ai/vis` then `/plugin install enchanter-hooks@vis` — the **enchanter-hooks** plugin ships 6 advisory, fail-open hooks (post-compaction checkpoint, secret scan, config self-edit guard, reversibility guard, debug-hygiene, syntax validation) that activate without editing `settings.json`.
217222

218223
### OpenAI Agents SDK
219224

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "enchanter-hooks",
3+
"version": "0.6.0",
4+
"description": "vis-governed advisory Claude Code hooks: deterministic, fail-open enforcement of the conduct substrate at lifecycle events in-context conduct cannot self-time. v0.6 ships eleven quiet, conditional hooks (speak only when they have something to say) across SessionStart / PreToolUse / PostToolUse: post-compaction checkpoint (F03), secret scan, config self-edit guard, substrate-engine-write guard (F24), append-only-ledger guard (F14), artifact-authorship guard, bash reversibility guard (F26), debug-artifact hygiene, syntax validation (F02/F14), stale-pathref guard (F02/F27), and machine-path-leak guard. Every hook exits 0 and never blocks.",
5+
"dependencies": ["enchanter-core"]
6+
}

packages/hooks/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# enchanter-hooks
2+
3+
vis-governed **advisory** Claude Code hooks — deterministic, fail-open enforcement of the
4+
conduct substrate at lifecycle events that in-context conduct **cannot self-time**.
5+
6+
Distributed the native way: `enchanter-hooks` is a Claude Code plugin in the `vis` marketplace.
7+
Installing it activates the hooks automatically — no `settings.json` editing.
8+
9+
```
10+
/plugin marketplace add enchanter-ai/vis
11+
/plugin install enchanter-hooks@vis
12+
```
13+
14+
## Design contract
15+
16+
Every hook here obeys five rules (the "smart-test" — a hook that fails any is rejected, not shipped):
17+
18+
1. **Deterministic trigger** — fires on an unambiguous event (tool call / lifecycle), never fuzzy intent.
19+
2. **Does what conduct can't** — earns its place by acting where in-context conduct structurally can't, or reliably misses.
20+
3. **Advisory / fail-open** — injects or warns, **never blocks**; every script exits 0 (and on a malformed payload, does nothing).
21+
4. **Not redundant** — doesn't re-implement Claude Code's built-in permission gating (or, where it complements it, adds a stated delta).
22+
5. **Quiet** — emits *nothing* unless it has something to say, so it never becomes ignored wallpaper.
23+
24+
## Hooks (v0.6 — 11)
25+
26+
| Hook | Event | F-code | What it does |
27+
|---|---|---|---|
28+
| `compact-checkpoint` | `SessionStart(compact)` | F03 | Re-injects a goal/state/invariants checkpoint right after a compaction (the one moment the model can't self-time). |
29+
| `secret-scan` | `PreToolUse(Write\|Edit)` | secret-exfil | Scans content about to be written for high-signal secret patterns; warns. |
30+
| `config-self-edit-guard` | `PreToolUse(Write\|Edit)` | self-modification | Warns on edits to the agent's own startup/hook/plugin config (`.claude/settings.json`, `hooks.json`, `.claude-plugin/`, `.mcp.json`). |
31+
| `substrate-engine-write-guard` | `PreToolUse(Write\|Edit)` | F24 | Warns on hand-writes to inference-engine state (`catalog.json`/`artifacts.jsonl`/briefings/`.lock`) — must go through the engine. |
32+
| `artifact-authorship-guard` | `PreToolUse(Write\|Edit)` | authorship | Warns when a public artifact (LICENSE/`.cff`/README/`.bib`) has author lines that don't credit "Enchanter Labs". Generic — no hardcoded names. |
33+
| `append-only-log-edit-guard` | `PreToolUse(Edit\|MultiEdit)` | F14 | Warns on an in-place Edit of an append-only ledger (`.jsonl`/`.ndjson`) — append a row instead. |
34+
| `reversibility-guard` | `PreToolUse(Bash)` | F26 | Advisory caution on hard-to-reverse commands (`rm -rf`, force-push, `--hard`, `mkfs`, `curl\|sh`, `DROP TABLE`). |
35+
| `debug-hygiene` | `PostToolUse(Write\|Edit)` | code-hygiene | Flags leftover debug artifacts (`console.log`/`debug`, `debugger`, `breakpoint()`, `pdb`/`pry`). |
36+
| `post-write-validate` | `PostToolUse(Write\|Edit)` | F02/F14 | Fast-parses the written file (`.py`/`.json`/`.sh`/`.toml`) and warns on a syntax error, same turn. |
37+
| `stale-pathref-guard` | `PostToolUse(Write\|Edit)` | F02/F27 | After writing a `.md`, checks its `@-imports` / local links actually resolve — the silent CLAUDE.md breakage. |
38+
| `machine-path-leak-guard` | `PostToolUse(Write\|Edit)` | F02 | Warns when a git-tracked file gains machine-absolute paths (`C:\git`, `/Users/<n>/`) — use repo-relative form. |
39+
40+
Built in two passes: the first 6 from an open-web survey of agent-hook patterns; the last 5 from a
41+
per-package mining sweep of the vis conduct substrate (each candidate gate-tested, deduped, and
42+
verified against live repo evidence — e.g. `stale-pathref` against real `@-imports`, `authorship`
43+
against a live `CITATION.cff` slip).
44+
45+
## Deferred (considered, did not clear the bar — or need a convention first)
46+
47+
- `claude-format-to-nonclaude-target`, `scratch-in-prompts-folder-guard` — wixie-`prompts/`-specific, not general vis; belong in a wixie-local hook set.
48+
- `mcp-manifest-gate` — would warn on *every* MCP call until an `mcp-manifests/*.fingerprint.json` convention exists → wallpaper today. Revisit when that convention lands.
49+
- `finish-checklist` (Stop), `substrate-injection` / `build-premise` (UserPromptSubmit) — fire every turn or rely on fuzzy intent. `build-premise` is superseded by the F29 *conduct* gate in `doubt-engine.md`.
50+
- `backup-before-compact` — low value (transcripts are usually recoverable) + project-side file writes.
51+
- egress-fence, write-scope-audit, subagent-spawn/budget counters, `learnings.md`/`SKILL.md` schema lints — failed "quiet" in practice, or cited contract files that don't exist in this codebase.
52+
53+
## Notes
54+
55+
- Scripts are **self-contained** (no references outside the plugin root) so they survive the
56+
plugin cache (`~/.claude/plugins/cache/`). Require `jq` + `bash`; `python3`/`python` for the
57+
syntax validators (fail-open if absent).
58+
- `config-self-edit-guard`, `reversibility-guard`, and `substrate-engine-write-guard` are advisory
59+
**complements** to permission gating, not replacements — the delta is the self-config/substrate
60+
category and the reversibility framing.
61+
- The `vis-drift` SessionStart hook is **not** shipped here — it is project-local (reads the
62+
consuming repo's `.vis-lock` against a sibling `../vis` checkout), which is not plugin-portable.

packages/hooks/hooks/hooks.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"description": "enchanter-hooks — vis advisory Claude Code hooks. Deterministic triggers, fail-open (every hook exits 0, never blocks), and quiet (each emits nothing unless it has something to say). Each maps to a vis F-code. Self-contained scripts (plugin-cache-safe) under scripts/, referenced via ${CLAUDE_PLUGIN_ROOT}.",
3+
"hooks": {
4+
"SessionStart": [
5+
{
6+
"matcher": "compact",
7+
"hooks": [
8+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/compact-checkpoint.sh\"", "timeout": 5 }
9+
]
10+
}
11+
],
12+
"PreToolUse": [
13+
{
14+
"matcher": "Write|Edit",
15+
"hooks": [
16+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/secret-scan.sh\"", "timeout": 5 },
17+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/config-self-edit-guard.sh\"", "timeout": 5 },
18+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/substrate-engine-write-guard.sh\"", "timeout": 5 },
19+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/artifact-authorship-guard.sh\"", "timeout": 5 }
20+
]
21+
},
22+
{
23+
"matcher": "Edit|MultiEdit",
24+
"hooks": [
25+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/append-only-log-edit-guard.sh\"", "timeout": 5 }
26+
]
27+
},
28+
{
29+
"matcher": "Bash",
30+
"hooks": [
31+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/reversibility-guard.sh\"", "timeout": 5 }
32+
]
33+
}
34+
],
35+
"PostToolUse": [
36+
{
37+
"matcher": "Write|Edit",
38+
"hooks": [
39+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/debug-hygiene.sh\"", "timeout": 5 },
40+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/post-write-validate.sh\"", "timeout": 5 },
41+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/stale-pathref-guard.sh\"", "timeout": 5 },
42+
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/machine-path-leak-guard.sh\"", "timeout": 5 }
43+
]
44+
}
45+
]
46+
}
47+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env bash
2+
# append-only-log-edit-guard.sh — PreToolUse(Edit|MultiEdit) advisory. Counter to F14 (append-only ledger).
3+
#
4+
# Warns when an in-place Edit targets an append-only ledger (*.jsonl / *.ndjson, or a named
5+
# artifacts/metrics/ledger/audit log). Such files take a NEW appended row — rewriting a prior row in
6+
# place corrupts their history. Advisory: stderr + exit 0. Quiet unless the path matches. Self-contained.
7+
set -uo pipefail
8+
9+
[[ -n "${CLAUDE_SUBAGENT:-}" ]] && exit 0
10+
11+
payload="$(cat 2>/dev/null || true)"
12+
tool="$(printf '%s' "$payload" | jq -r '.tool_name // empty' 2>/dev/null || true)"
13+
file="$(printf '%s' "$payload" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true)"
14+
[[ -z "$file" ]] && exit 0
15+
case "$tool" in Edit|MultiEdit) ;; *) exit 0 ;; esac
16+
17+
if printf '%s' "$file" | grep -Eq '\.(jsonl|ndjson)$|(artifacts|metrics|ledger|audit)\.jsonl$'; then
18+
{
19+
echo "[vis F14 · append-only] $(basename "$file") looks like an append-only ledger."
20+
echo "An in-place Edit rewrites a prior row — append a NEW row instead, or you corrupt the ledger's history."
21+
} >&2
22+
fi
23+
24+
exit 0
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
# artifact-authorship-guard.sh — PreToolUse(Write|Edit) advisory. Authorship contract.
3+
#
4+
# Warns when a PUBLIC artifact (LICENSE, *.cff / CITATION, README, *.bib) is written with an
5+
# author / copyright / alias line that does NOT credit "Enchanter Labs". GENERIC — it does not
6+
# hardcode any individual's name or email; it simply flags authorship lines missing the org credit.
7+
# Advisory: stderr + exit 0. Quiet unless an author line lacks the credit. Self-contained.
8+
set -uo pipefail
9+
10+
[[ -n "${CLAUDE_SUBAGENT:-}" ]] && exit 0
11+
12+
payload="$(cat 2>/dev/null || true)"
13+
file="$(printf '%s' "$payload" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true)"
14+
content="$(printf '%s' "$payload" | jq -r '.tool_input.content // .tool_input.new_string // empty' 2>/dev/null || true)"
15+
[[ -z "$file" || -z "$content" ]] && exit 0
16+
17+
case "$(basename "$file")" in
18+
LICENSE|LICENSE.*|*.cff|CITATION.*|README.md|*.bib) ;;
19+
*) exit 0 ;;
20+
esac
21+
22+
authlines="$(printf '%s' "$content" | grep -niE 'author|copyright|alias|\(c\)' | head -10 || true)"
23+
[[ -z "$authlines" ]] && exit 0
24+
25+
if ! printf '%s' "$content" | grep -qi 'Enchanter Labs'; then
26+
{
27+
echo "[vis · authorship] $(basename "$file") has author/copyright lines that don't credit 'Enchanter Labs':"
28+
printf '%s\n' "$authlines" | head -4
29+
echo "Public-artifact authorship must read 'Enchanter Labs', not a personal handle/email."
30+
} >&2
31+
fi
32+
33+
exit 0

0 commit comments

Comments
 (0)