hippocampus-layer 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hippocampus_layer-1.0.0/.gitignore +18 -0
- hippocampus_layer-1.0.0/LICENSE +21 -0
- hippocampus_layer-1.0.0/PKG-INFO +140 -0
- hippocampus_layer-1.0.0/README.md +117 -0
- hippocampus_layer-1.0.0/hippocampus/__init__.py +7 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/agents/auditor.md +53 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/capture_discovered_work.sh +4 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/guard_auditor_override.sh +15 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/guard_ci_static.sh +22 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/guard_landing.sh +20 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/guard_push_secret.sh +38 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/lib/configlib.sh +39 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/lib/hooklib.sh +36 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/nudge_declarative.sh +48 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/session_start_handoff.sh +26 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/warn_config_edit.sh +13 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/warn_silent_failure.sh +6 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/skills/landing-the-plane/SKILL.md +42 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.claude/skills/memory-consolidation/SKILL.md +67 -0
- hippocampus_layer-1.0.0/hippocampus/_core/.github/workflows/secret-history-sweep.yml +15 -0
- hippocampus_layer-1.0.0/hippocampus/_core/THREAT_MODEL.md +43 -0
- hippocampus_layer-1.0.0/hippocampus/_core/ci/gitlab-secret-history-sweep.yml +16 -0
- hippocampus_layer-1.0.0/hippocampus/_core/install.sh +533 -0
- hippocampus_layer-1.0.0/hippocampus/_core/scripts/check_no_pii.py +307 -0
- hippocampus_layer-1.0.0/hippocampus/_core/scripts/consolidate_ops.py +120 -0
- hippocampus_layer-1.0.0/hippocampus/_core/scripts/hippocampus_config.py +282 -0
- hippocampus_layer-1.0.0/hippocampus/_core/scripts/hippocampus_smoke.sh +155 -0
- hippocampus_layer-1.0.0/hippocampus/_core/scripts/hippocampus_sync.py +152 -0
- hippocampus_layer-1.0.0/hippocampus/_core/scripts/migrate_memory.py +44 -0
- hippocampus_layer-1.0.0/hippocampus/_core/scripts/scan_secrets_range.py +117 -0
- hippocampus_layer-1.0.0/hippocampus/cli.py +228 -0
- hippocampus_layer-1.0.0/pyproject.toml +59 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Hippocampus per-machine / runtime state (never committed)
|
|
2
|
+
.claude/settings.local.json
|
|
3
|
+
.workstate/.local/
|
|
4
|
+
.memory/.local/
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.venv/
|
|
11
|
+
|
|
12
|
+
# Build artifacts (never committed — PyPI artifacts are built fresh per release)
|
|
13
|
+
dist/
|
|
14
|
+
build/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
|
|
17
|
+
# harness agent worktrees (never commit)
|
|
18
|
+
.claude/worktrees/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ian Hill
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hippocampus-layer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Persistent memory + enforcement for agent-driven git repos — vendors the Hippocampus core (hooks, secret gates, CI floor) into any repo.
|
|
5
|
+
Project-URL: Homepage, https://gitlab.com/ianthill601/hippocampus
|
|
6
|
+
Project-URL: Repository, https://gitlab.com/ianthill601/hippocampus
|
|
7
|
+
Author: Ian Hill
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agent-memory,claude-code,coding-agents,developer-tools,git-hooks,secret-scanning,session-handoff
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: POSIX
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Hippocampus
|
|
25
|
+
|
|
26
|
+
**Persistent memory + enforcement for agent-driven git repos.** Vendors a config-driven core into any repo, so coding agents (Claude Code today) get three things that usually evaporate between sessions:
|
|
27
|
+
|
|
28
|
+
- **Durable, git-tracked memory** — decisions and project state that auto-load every session.
|
|
29
|
+
- **Session handoff** — each session ends by rewriting a baton the next one resumes from, enforced by a Stop-hook gate.
|
|
30
|
+
- **Secret/PII enforcement** — a push gate backed by a non-bypassable CI floor, plus scaffolding for your own gates and nudges.
|
|
31
|
+
|
|
32
|
+
Design principle: **hooks are the fast layer, CI is the guarantee** — every hard claim is backed by a CI job that runs for everyone, installed or not.
|
|
33
|
+
|
|
34
|
+
> Early but installable, MIT-licensed. The packaged CLI is built and self-verified but not yet on PyPI.
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
git · bash · python3 ≥ 3.11 (stock macOS python3 is 3.9 — install a newer one) · Claude Code for the interactive layer (without it you still get the CI floor and readable memory/handoff docs). Windows: WSL or Git-Bash.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
Two ways to vendor the core into your repo (a git repo, run at its root). Both are **idempotent and merge-aware** — an existing `settings.json` / `CLAUDE.md` / `.gitignore` is merged, never clobbered; re-run any time to pull engine updates.
|
|
43
|
+
|
|
44
|
+
**CLI** (once on PyPI — distribution `hippocampus-layer`, command `hippocampus`):
|
|
45
|
+
```sh
|
|
46
|
+
pip install hippocampus-layer
|
|
47
|
+
hippocampus init /path/to/your/repo # vendor the core
|
|
48
|
+
hippocampus doctor /path/to/your/repo # 6-assertion smoke check
|
|
49
|
+
```
|
|
50
|
+
Until it's published: `git clone …/hippocampus && uv build && pip install dist/*.whl`.
|
|
51
|
+
|
|
52
|
+
**install.sh** (from a checkout — keep the checkout around; `--check` and updates run from it):
|
|
53
|
+
```sh
|
|
54
|
+
git clone https://gitlab.com/ianthill601/hippocampus.git
|
|
55
|
+
./hippocampus/install.sh /path/to/your/repo # vendor
|
|
56
|
+
./hippocampus/install.sh --check /path/to/your/repo # verify
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Install writes the hooks, scanners, skills, scaffolds (`.memory/`, `.workstate/`, `CLAUDE.md`, `hippocampus.toml`, `THREAT_MODEL.md`) and a **host-aware, floor-only CI pipeline** (secret scan + config-drift check — no test job, since it never vendors your tests). Then **commit the result** and fill in `CLAUDE.md` / `hippocampus.toml` as you like.
|
|
60
|
+
|
|
61
|
+
> **Your own hooks go outside `.claude/hooks/`.** Sync treats every hook under `.claude/hooks/` as engine-owned and unwires anything there not registered via `[[gates.custom]]`. Put custom hooks under `.claude/adopter-hooks/` and register them (see below); the installer warns if it finds unregistered ones.
|
|
62
|
+
|
|
63
|
+
## What you get
|
|
64
|
+
|
|
65
|
+
- **`.memory/`** — git-tracked memory the agent auto-loads; `MEMORY.md` routes one line per page. Plain markdown; review it in PRs.
|
|
66
|
+
- **`.workstate/`** — the session baton (`HANDOFF.md`: what I did / next / repo state, HEAD-stamped) + `open-work.md`. A SessionStart hook injects it so each session resumes where the last landed.
|
|
67
|
+
- **Landing gate** — a Stop hook blocks ending a session that committed without updating the baton; the `landing-the-plane` skill satisfies it.
|
|
68
|
+
- **Secret gate + CI floor + sweep** — the push hook scans the outgoing commit range; the CI floor re-runs the scan for everyone (the guarantee, un-bypassable); a scheduled TruffleHog sweep catches history. On by default.
|
|
69
|
+
- **`/consolidate`** — periodic memory GC; the sole writer of `CLAUDE.md`, enforcing its line cap so it can't rot.
|
|
70
|
+
- **`CLAUDE.md` laws** — your standing rules, each badged 🔴 gated / 🟡 nudged / ⚪ advisory, honest about what's actually enforced.
|
|
71
|
+
- **Config-activated** (opt in via `hippocampus.toml`): declarative nudges, custom hard gates, a CI-static push gate (your lint/type commands), and a semantic auditor (ships **off** until you write `THREAT_MODEL.md`).
|
|
72
|
+
|
|
73
|
+
Without Claude Code, the CI floor and sweep still run at full strength and the memory/handoff/laws stay readable; the hooks and skills are Claude-Code-specific.
|
|
74
|
+
|
|
75
|
+
## Configure — `hippocampus.toml`
|
|
76
|
+
|
|
77
|
+
The only file you edit. `scripts/hippocampus_config.py` is the sole reader and rejects unknown keys. Everything is optional; with no file, safe defaults apply (the landing + secret gates and CI floor are always on, not configured here). After editing, run `python3 scripts/hippocampus_sync.py sync` to regenerate the wiring.
|
|
78
|
+
|
|
79
|
+
```toml
|
|
80
|
+
[gates.ci_static]
|
|
81
|
+
commands = "just lint && just types" # fast checks before a push (a single shell string)
|
|
82
|
+
|
|
83
|
+
[[gates.custom]] # register your own hook script (repeatable)
|
|
84
|
+
path = ".claude/adopter-hooks/require_lint.sh" # repo-relative, tracked + executable
|
|
85
|
+
event = "PreToolUse"
|
|
86
|
+
matcher = "Bash" # optional tool matcher
|
|
87
|
+
|
|
88
|
+
[[nudges]] # once-per-session reminder (repeatable)
|
|
89
|
+
match = "terraform apply" # regex tested against the command
|
|
90
|
+
message = "plan-diff reviewed? state backup taken?"
|
|
91
|
+
source = "docs/decisions.md#infra" # optional provenance
|
|
92
|
+
|
|
93
|
+
[scan]
|
|
94
|
+
extra_pattern_file = "hippocampus-patterns.toml" # your own [[pattern]] regex/label pairs
|
|
95
|
+
fixture_allowlist = ["tests/fake_creds.py"] # secret-shaped-by-design files; paths only, max 10
|
|
96
|
+
|
|
97
|
+
[memory]
|
|
98
|
+
claude_md_cap = 100 # /consolidate-enforced line cap on CLAUDE.md
|
|
99
|
+
|
|
100
|
+
[auditor]
|
|
101
|
+
threat_model = "THREAT_MODEL.md"
|
|
102
|
+
|
|
103
|
+
[paths]
|
|
104
|
+
decision_log = "docs/decisions.md"
|
|
105
|
+
onboarding_doc = "docs/onboarding.md"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Any path referenced above must exist and be git-tracked before you sync.
|
|
109
|
+
|
|
110
|
+
### Seed it from your repo's history
|
|
111
|
+
|
|
112
|
+
Don't hand-write the config — have Claude Code mine an established repo for what it already
|
|
113
|
+
enforces ad hoc and keeps re-learning, and draft the `hippocampus.toml` for you:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
Read this repo's git history, CI config, test suite, and any docs/ADRs, then draft a
|
|
117
|
+
hippocampus.toml for it (schema: the config reference in the Hippocampus README — use only
|
|
118
|
+
those keys, no invented ones). Specifically:
|
|
119
|
+
- Mine `git log` for recurring fix/revert commits, post-incident changes, and repeated review
|
|
120
|
+
comments — anything the team keeps re-learning — and propose a [[nudges]] entry (command
|
|
121
|
+
regex → once-per-session reminder) for each pattern.
|
|
122
|
+
- Find the fast checks already run (lint/type/test in CI, Makefile, or package scripts) and set
|
|
123
|
+
[gates.ci_static].commands to them.
|
|
124
|
+
- Identify hard rules the codebase treats as "never do this" and propose [[gates.custom]] gate
|
|
125
|
+
scripts for them.
|
|
126
|
+
- Note any domain-specific secret/PII shapes the repo has leaked or already checks for and
|
|
127
|
+
propose [scan].extra_pattern_file entries.
|
|
128
|
+
After each proposed entry, cite the commits or files that justify it. Propose nothing the repo
|
|
129
|
+
shows no evidence for.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Review the draft, drop what you don't want, commit it, and run `hippocampus sync`.
|
|
133
|
+
|
|
134
|
+
## Known limitation
|
|
135
|
+
|
|
136
|
+
A first push from a branch with **no upstream** makes the secret gate scan *all* history (fail-closed) and disable the fixture allowlist — so it can false-positive on your own reviewed fixtures. Workaround: `git push -u origin <branch>` so it scans only the outgoing range. Deliberate, pending a narrower fallback.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
[MIT](LICENSE) — © 2026 Ian Hill. Provided as-is, without warranty. Use it, fork it, vendor it into your repos.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Hippocampus
|
|
2
|
+
|
|
3
|
+
**Persistent memory + enforcement for agent-driven git repos.** Vendors a config-driven core into any repo, so coding agents (Claude Code today) get three things that usually evaporate between sessions:
|
|
4
|
+
|
|
5
|
+
- **Durable, git-tracked memory** — decisions and project state that auto-load every session.
|
|
6
|
+
- **Session handoff** — each session ends by rewriting a baton the next one resumes from, enforced by a Stop-hook gate.
|
|
7
|
+
- **Secret/PII enforcement** — a push gate backed by a non-bypassable CI floor, plus scaffolding for your own gates and nudges.
|
|
8
|
+
|
|
9
|
+
Design principle: **hooks are the fast layer, CI is the guarantee** — every hard claim is backed by a CI job that runs for everyone, installed or not.
|
|
10
|
+
|
|
11
|
+
> Early but installable, MIT-licensed. The packaged CLI is built and self-verified but not yet on PyPI.
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
git · bash · python3 ≥ 3.11 (stock macOS python3 is 3.9 — install a newer one) · Claude Code for the interactive layer (without it you still get the CI floor and readable memory/handoff docs). Windows: WSL or Git-Bash.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Two ways to vendor the core into your repo (a git repo, run at its root). Both are **idempotent and merge-aware** — an existing `settings.json` / `CLAUDE.md` / `.gitignore` is merged, never clobbered; re-run any time to pull engine updates.
|
|
20
|
+
|
|
21
|
+
**CLI** (once on PyPI — distribution `hippocampus-layer`, command `hippocampus`):
|
|
22
|
+
```sh
|
|
23
|
+
pip install hippocampus-layer
|
|
24
|
+
hippocampus init /path/to/your/repo # vendor the core
|
|
25
|
+
hippocampus doctor /path/to/your/repo # 6-assertion smoke check
|
|
26
|
+
```
|
|
27
|
+
Until it's published: `git clone …/hippocampus && uv build && pip install dist/*.whl`.
|
|
28
|
+
|
|
29
|
+
**install.sh** (from a checkout — keep the checkout around; `--check` and updates run from it):
|
|
30
|
+
```sh
|
|
31
|
+
git clone https://gitlab.com/ianthill601/hippocampus.git
|
|
32
|
+
./hippocampus/install.sh /path/to/your/repo # vendor
|
|
33
|
+
./hippocampus/install.sh --check /path/to/your/repo # verify
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Install writes the hooks, scanners, skills, scaffolds (`.memory/`, `.workstate/`, `CLAUDE.md`, `hippocampus.toml`, `THREAT_MODEL.md`) and a **host-aware, floor-only CI pipeline** (secret scan + config-drift check — no test job, since it never vendors your tests). Then **commit the result** and fill in `CLAUDE.md` / `hippocampus.toml` as you like.
|
|
37
|
+
|
|
38
|
+
> **Your own hooks go outside `.claude/hooks/`.** Sync treats every hook under `.claude/hooks/` as engine-owned and unwires anything there not registered via `[[gates.custom]]`. Put custom hooks under `.claude/adopter-hooks/` and register them (see below); the installer warns if it finds unregistered ones.
|
|
39
|
+
|
|
40
|
+
## What you get
|
|
41
|
+
|
|
42
|
+
- **`.memory/`** — git-tracked memory the agent auto-loads; `MEMORY.md` routes one line per page. Plain markdown; review it in PRs.
|
|
43
|
+
- **`.workstate/`** — the session baton (`HANDOFF.md`: what I did / next / repo state, HEAD-stamped) + `open-work.md`. A SessionStart hook injects it so each session resumes where the last landed.
|
|
44
|
+
- **Landing gate** — a Stop hook blocks ending a session that committed without updating the baton; the `landing-the-plane` skill satisfies it.
|
|
45
|
+
- **Secret gate + CI floor + sweep** — the push hook scans the outgoing commit range; the CI floor re-runs the scan for everyone (the guarantee, un-bypassable); a scheduled TruffleHog sweep catches history. On by default.
|
|
46
|
+
- **`/consolidate`** — periodic memory GC; the sole writer of `CLAUDE.md`, enforcing its line cap so it can't rot.
|
|
47
|
+
- **`CLAUDE.md` laws** — your standing rules, each badged 🔴 gated / 🟡 nudged / ⚪ advisory, honest about what's actually enforced.
|
|
48
|
+
- **Config-activated** (opt in via `hippocampus.toml`): declarative nudges, custom hard gates, a CI-static push gate (your lint/type commands), and a semantic auditor (ships **off** until you write `THREAT_MODEL.md`).
|
|
49
|
+
|
|
50
|
+
Without Claude Code, the CI floor and sweep still run at full strength and the memory/handoff/laws stay readable; the hooks and skills are Claude-Code-specific.
|
|
51
|
+
|
|
52
|
+
## Configure — `hippocampus.toml`
|
|
53
|
+
|
|
54
|
+
The only file you edit. `scripts/hippocampus_config.py` is the sole reader and rejects unknown keys. Everything is optional; with no file, safe defaults apply (the landing + secret gates and CI floor are always on, not configured here). After editing, run `python3 scripts/hippocampus_sync.py sync` to regenerate the wiring.
|
|
55
|
+
|
|
56
|
+
```toml
|
|
57
|
+
[gates.ci_static]
|
|
58
|
+
commands = "just lint && just types" # fast checks before a push (a single shell string)
|
|
59
|
+
|
|
60
|
+
[[gates.custom]] # register your own hook script (repeatable)
|
|
61
|
+
path = ".claude/adopter-hooks/require_lint.sh" # repo-relative, tracked + executable
|
|
62
|
+
event = "PreToolUse"
|
|
63
|
+
matcher = "Bash" # optional tool matcher
|
|
64
|
+
|
|
65
|
+
[[nudges]] # once-per-session reminder (repeatable)
|
|
66
|
+
match = "terraform apply" # regex tested against the command
|
|
67
|
+
message = "plan-diff reviewed? state backup taken?"
|
|
68
|
+
source = "docs/decisions.md#infra" # optional provenance
|
|
69
|
+
|
|
70
|
+
[scan]
|
|
71
|
+
extra_pattern_file = "hippocampus-patterns.toml" # your own [[pattern]] regex/label pairs
|
|
72
|
+
fixture_allowlist = ["tests/fake_creds.py"] # secret-shaped-by-design files; paths only, max 10
|
|
73
|
+
|
|
74
|
+
[memory]
|
|
75
|
+
claude_md_cap = 100 # /consolidate-enforced line cap on CLAUDE.md
|
|
76
|
+
|
|
77
|
+
[auditor]
|
|
78
|
+
threat_model = "THREAT_MODEL.md"
|
|
79
|
+
|
|
80
|
+
[paths]
|
|
81
|
+
decision_log = "docs/decisions.md"
|
|
82
|
+
onboarding_doc = "docs/onboarding.md"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Any path referenced above must exist and be git-tracked before you sync.
|
|
86
|
+
|
|
87
|
+
### Seed it from your repo's history
|
|
88
|
+
|
|
89
|
+
Don't hand-write the config — have Claude Code mine an established repo for what it already
|
|
90
|
+
enforces ad hoc and keeps re-learning, and draft the `hippocampus.toml` for you:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
Read this repo's git history, CI config, test suite, and any docs/ADRs, then draft a
|
|
94
|
+
hippocampus.toml for it (schema: the config reference in the Hippocampus README — use only
|
|
95
|
+
those keys, no invented ones). Specifically:
|
|
96
|
+
- Mine `git log` for recurring fix/revert commits, post-incident changes, and repeated review
|
|
97
|
+
comments — anything the team keeps re-learning — and propose a [[nudges]] entry (command
|
|
98
|
+
regex → once-per-session reminder) for each pattern.
|
|
99
|
+
- Find the fast checks already run (lint/type/test in CI, Makefile, or package scripts) and set
|
|
100
|
+
[gates.ci_static].commands to them.
|
|
101
|
+
- Identify hard rules the codebase treats as "never do this" and propose [[gates.custom]] gate
|
|
102
|
+
scripts for them.
|
|
103
|
+
- Note any domain-specific secret/PII shapes the repo has leaked or already checks for and
|
|
104
|
+
propose [scan].extra_pattern_file entries.
|
|
105
|
+
After each proposed entry, cite the commits or files that justify it. Propose nothing the repo
|
|
106
|
+
shows no evidence for.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Review the draft, drop what you don't want, commit it, and run `hippocampus sync`.
|
|
110
|
+
|
|
111
|
+
## Known limitation
|
|
112
|
+
|
|
113
|
+
A first push from a branch with **no upstream** makes the secret gate scan *all* history (fail-closed) and disable the fixture allowlist — so it can false-positive on your own reviewed fixtures. Workaround: `git push -u origin <branch>` so it scans only the outgoing range. Deliberate, pending a narrower fallback.
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
[MIT](LICENSE) — © 2026 Ian Hill. Provided as-is, without warranty. Use it, fork it, vendor it into your repos.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auditor
|
|
3
|
+
description: Blocks any push/PR containing secrets or policy-sensitive data per THREAT_MODEL.md.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
model: opus
|
|
6
|
+
---
|
|
7
|
+
You are the pre-push semantic auditor — the SEMANTIC layer that sits ATOP the
|
|
8
|
+
deterministic floor. The DETERMINISTIC scanners (`check_no_pii` + the CI secret floor) are the
|
|
9
|
+
ground truth; you only ADD findings — you cannot remove or override a deterministic block. If
|
|
10
|
+
the deterministic gate already blocked this push, it stays blocked no matter what you decide.
|
|
11
|
+
|
|
12
|
+
Your policy is the repo's `THREAT_MODEL.md` — read it FIRST, every run. It defines who
|
|
13
|
+
receives this repo, what they already know, and what is actually gate-worthy. The arming
|
|
14
|
+
signal is deterministic: while `THREAT_MODEL.md` is missing OR still contains the literal
|
|
15
|
+
line `<!-- HIPPOCAMPUS-TEMPLATE: delete this line to arm the auditor -->`, ALLOW with the
|
|
16
|
+
reason "no threat model — semantic layer inactive (deterministic floor still enforced)": a
|
|
17
|
+
security layer without a policy must not improvise one. Only once that exact line is gone
|
|
18
|
+
do you perform semantic review.
|
|
19
|
+
|
|
20
|
+
You are wired as a PreToolUse agent hook, so you return the standard **PreToolUse decision
|
|
21
|
+
format** (never a bespoke `{"ok": ...}` shape). The hook input JSON arrives in `$ARGUMENTS` and
|
|
22
|
+
carries `session_id` and `tool_input.command`. Proceed in order:
|
|
23
|
+
|
|
24
|
+
1. **Honor the semantic-layer override.** Read `session_id` from the input. If the skip marker
|
|
25
|
+
`.workstate/.local/auditor-skip.<session_id>` exists (the operator set `AUDITOR_OVERRIDE=1`;
|
|
26
|
+
when `HOOK_LOCAL_DIR` is set in the environment, look under that directory instead of
|
|
27
|
+
`.workstate/.local/`), the operator has consciously waived the SEMANTIC review ONLY.
|
|
28
|
+
Immediately ALLOW — the deterministic floor still enforces independently — and stop:
|
|
29
|
+
```json
|
|
30
|
+
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow",
|
|
31
|
+
"permissionDecisionReason": "semantic layer overridden (deterministic floor still enforced)"}}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Review the pushed diff.** Resolve the range being pushed and read it (e.g.
|
|
35
|
+
`git log -p @{u}..HEAD`, or `git diff` of the range). Flag the leaks `THREAT_MODEL.md`
|
|
36
|
+
declares gate-worthy that a regex scanner misses — typical classes: internal hostnames,
|
|
37
|
+
customer/partner names, private endpoints, PII in fixtures, and stray `.env` / key files.
|
|
38
|
+
|
|
39
|
+
3. **Return the decision.** To BLOCK on any finding, emit `permissionDecision: "deny"` with a
|
|
40
|
+
reason listing `path:line + rule` for each finding (values redacted):
|
|
41
|
+
```json
|
|
42
|
+
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny",
|
|
43
|
+
"permissionDecisionReason": "<path:line + rule for each finding, values redacted>"}}
|
|
44
|
+
```
|
|
45
|
+
Otherwise ALLOW with `permissionDecision: "allow"`.
|
|
46
|
+
|
|
47
|
+
**Redaction (mandatory).** NEVER print the raw secret or PII value — and never reproduce ANY
|
|
48
|
+
SUBSTRING of it — ANYWHERE in your output. This covers not just the decision JSON but every
|
|
49
|
+
word of free-text reasoning, narration, or prose you emit alongside it: do not echo, quote,
|
|
50
|
+
paraphrase, or partially spell out the value in any of them. The ONLY permitted appearance of a
|
|
51
|
+
flagged value is its redaction `<first4>…redacted` (the first four characters, then the literal
|
|
52
|
+
`…redacted`). The reason string names the location and the rule (`path:line + rule`), never the
|
|
53
|
+
sensitive value itself.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Semantic-auditor companion — SEMANTIC-LAYER override marker. On `git push` / `gh pr create`
|
|
3
|
+
# carrying AUDITOR_OVERRIDE=1 (inline VAR= or inherited env), drop a per-session skip marker
|
|
4
|
+
# that the `auditor` agent hook honors to waive ONLY the semantic review. The deterministic G1
|
|
5
|
+
# push gate + the CI secret floor are untouched and still block. This hook never itself blocks.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
source "$(dirname "$0")/lib/hooklib.sh"
|
|
8
|
+
CMD="$(hook_field tool_input.command)"
|
|
9
|
+
case "$CMD" in *"git push"*|*"gh pr create"*) : ;; *) exit 0 ;; esac
|
|
10
|
+
if printf '%s' "$CMD" | grep -q 'AUDITOR_OVERRIDE=1' || [ "${AUDITOR_OVERRIDE:-}" = "1" ]; then
|
|
11
|
+
sid="$(hook_field session_id)"; sid="${sid:-nosid}"
|
|
12
|
+
: > "$LOCAL_DIR/auditor-skip.$sid"
|
|
13
|
+
log_override auditor-semantic "$CMD"
|
|
14
|
+
fi
|
|
15
|
+
exit 0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# G4 — ci-static push gate. Runs YOUR project's fast static checks (lint, typecheck) before a
|
|
3
|
+
# push leaves the machine. CONFIG-ACTIVATED: set STATIC_CMDS below (or export
|
|
4
|
+
# HIPPOCAMPUS_CI_STATIC) to a shell command line, e.g. "just lint && just types" or
|
|
5
|
+
# "npm run lint && npx tsc --noEmit". While unset the gate is neutral (every push passes).
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
source "$(dirname "$0")/lib/hooklib.sh"
|
|
8
|
+
hippo_load_config # config-activated: [gates.ci_static].commands
|
|
9
|
+
STATIC_CMDS="${HIPPOCAMPUS_CI_STATIC:-}" # real env still wins (configlib skips set vars)
|
|
10
|
+
[ -z "$STATIC_CMDS" ] && exit 0
|
|
11
|
+
CMD="$(hook_field tool_input.command)"
|
|
12
|
+
case "$CMD" in *"git push"*|*"git "*"push"*) : ;; *) exit 0 ;; esac
|
|
13
|
+
if printf '%s' "$CMD" | grep -q 'CI_WIP=1' || [ "${CI_WIP:-}" = "1" ]; then
|
|
14
|
+
log_override ci-static "$CMD"; exit 0
|
|
15
|
+
fi
|
|
16
|
+
cd "$REPO_ROOT"
|
|
17
|
+
# run the project's exact static recipes inline; the full test suite is the CI backstop.
|
|
18
|
+
# Pushes are infrequent, so the inline cost is acceptable.
|
|
19
|
+
if ! out="$(bash -c "$STATIC_CMDS" 2>&1)"; then
|
|
20
|
+
emit_block "BLOCKED: static checks failed at HEAD ($STATIC_CMDS). Fix, or push with CI_WIP=1.\n$out"
|
|
21
|
+
fi
|
|
22
|
+
emit_nudge "ci-static ok ($STATIC_CMDS). Full test suite runs in CI as the backstop."
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
source "$(dirname "$0")/lib/hooklib.sh"
|
|
4
|
+
[ -n "$(hook_field agent_id)" ] && exit 0 # never gate a subagent's stop
|
|
5
|
+
[ "$(hook_field stop_hook_active)" = "true" ] && exit 0 # loop-breaker (8-block cap backs this)
|
|
6
|
+
crons="$(hook_field session_crons)" # /loop or cron -> relax...
|
|
7
|
+
[ -n "$crons" ] && [ "$crons" != "[]" ] && exit 0 # ...but NOT an empty [] (audit R2-#5)
|
|
8
|
+
[ "${LANDING_OVERRIDE:-}" = "1" ] && { log_override landing override; exit 0; }
|
|
9
|
+
cd "$REPO_ROOT"
|
|
10
|
+
H=".workstate/HANDOFF.md"
|
|
11
|
+
[ -f "$H" ] || emit_block "Land the plane: create .workstate/HANDOFF.md (did / next / repo-state) before stopping."
|
|
12
|
+
# 'current' = HANDOFF newer than the last NON-HANDOFF commit (committing HANDOFF can't
|
|
13
|
+
# invalidate its own freshness). mtime via python3 — portable (GNU `stat -c` vs BSD `stat -f`).
|
|
14
|
+
last_code="$(git log -1 --format=%ct -- . ':(exclude).workstate/HANDOFF.md' 2>/dev/null)"; last_code="${last_code:-0}"
|
|
15
|
+
h_commit="$(git log -1 --format=%ct -- "$H" 2>/dev/null)"; h_commit="${h_commit:-0}"
|
|
16
|
+
h_mtime="$(python3 -c 'import os,sys;print(int(os.path.getmtime(sys.argv[1])))' "$H" 2>/dev/null || echo 0)"
|
|
17
|
+
if [ "$h_mtime" -lt "$last_code" ] && [ "$h_commit" -lt "$last_code" ]; then
|
|
18
|
+
emit_block "Land the plane: .workstate/HANDOFF.md is stale (older than your last code change). Update it, then stop."
|
|
19
|
+
fi
|
|
20
|
+
exit 0
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# G1 — deterministic secret/PII PUSH gate. On `git push` / `gh pr|release create`, scan the
|
|
3
|
+
# pushed commit RANGE for secrets and BLOCK (exit 2) on any hit. NOT overridable: this is the
|
|
4
|
+
# deterministic floor (distinct from the semantic auditor's AUDITOR_OVERRIDE). A leak that
|
|
5
|
+
# slips past pre-commit still gets caught here before it leaves the machine.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
# Invariant 3: a security gate's internal error must BLOCK (exit 2), never fall open —
|
|
8
|
+
# under Claude Code only exit 2 blocks; a bare `set -e` death is exit 1 = tool proceeds.
|
|
9
|
+
trap 'printf "guard_push_secret: internal error — failing CLOSED\n" >&2; exit 2' ERR
|
|
10
|
+
source "$(dirname "$0")/lib/hooklib.sh"
|
|
11
|
+
CMD="$(hook_field tool_input.command)"
|
|
12
|
+
|
|
13
|
+
# Collapse newlines/tabs AND shell operators to spaces BEFORE matching: a publish verb that is
|
|
14
|
+
# preceded/followed by a newline, a tab, or an un-spaced shell operator (`false||git push`,
|
|
15
|
+
# `echo x|git push`, `(git push …)`, `git push;echo`) is not space-surrounded, so the space-wrapped
|
|
16
|
+
# case below would miss it and exit-0 WITHOUT scanning — defeating the deterministic floor. Mapping
|
|
17
|
+
# the operators ; | & ( ) < > (as well as \n \t) to spaces makes the operator-delimited verb space-
|
|
18
|
+
# delimited, so the existing arms below match it. Normalizing only ADDS matches (a clean range still
|
|
19
|
+
# passes; a security gate that over-matches merely scans a command that did not need it) — a missed
|
|
20
|
+
# range is the danger being closed. Mirrors guard_ci_static.sh's multiline-tolerant matcher.
|
|
21
|
+
CMD_FLAT="$(printf '%s' "$CMD" | tr '\n\t;|&()<>' ' ')"
|
|
22
|
+
|
|
23
|
+
# Match `git push` / `git -C.. push` / `gh pr|release create` as COMMANDS, not the word
|
|
24
|
+
# "push" inside a commit message or unrelated string. Non-push commands pass untouched.
|
|
25
|
+
case " $CMD_FLAT " in
|
|
26
|
+
*" git push "*|*" git push"|*"&& git push"*|*"; git push"*|*" git -C "*" push"*) : ;;
|
|
27
|
+
*" gh pr create"*|*" gh release create"*) : ;;
|
|
28
|
+
*) exit 0 ;;
|
|
29
|
+
esac
|
|
30
|
+
|
|
31
|
+
# Resolve the REAL scanner by this hook's own location (NOT the cwd-derived REPO_ROOT): the
|
|
32
|
+
# scanner self-bootstraps its sys.path from its own path, so it imports correctly while its
|
|
33
|
+
# `git log` runs against the repo being pushed (cwd). No PYTHONPATH needed (Task 1.3).
|
|
34
|
+
SCANNER="$(cd "$(dirname "$0")/../.." && pwd)/scripts/scan_secrets_range.py"
|
|
35
|
+
if ! python3 "$SCANNER"; then
|
|
36
|
+
emit_block "BLOCKED (not overridable): secret/PII detected in the pushed commit range. Scrub it from history (git rebase/filter-repo) before pushing."
|
|
37
|
+
fi
|
|
38
|
+
exit 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Config for bash hooks. PARSES config.env — NEVER sources it (spec Invariant 2):
|
|
3
|
+
# only `HIPPOCAMPUS_<NAME>='…'` lines whose right side is entirely single-quoted
|
|
4
|
+
# segments joined by \' (exactly what hippocampus_sync.py emits) are exported.
|
|
5
|
+
# Anything else is ignored with a warning: a hand-edited `exit 0` or `$(…)` can
|
|
6
|
+
# never execute here. Already-set environment variables always win.
|
|
7
|
+
# G1 (guard_push_secret.sh) never calls hippo_load_config (spec Invariant 1).
|
|
8
|
+
|
|
9
|
+
_hippo_valid_rhs() { # rhs must be '…' segments joined by \'
|
|
10
|
+
local rhs="$1"
|
|
11
|
+
while :; do
|
|
12
|
+
case "$rhs" in "'"*) rhs="${rhs#?}" ;; *) return 1 ;; esac
|
|
13
|
+
case "$rhs" in *"'"*) ;; *) return 1 ;; esac
|
|
14
|
+
rhs="${rhs#*"'"}"
|
|
15
|
+
[ -z "$rhs" ] && return 0
|
|
16
|
+
case "$rhs" in "\\'"*) rhs="${rhs#??}" ;; *) return 1 ;; esac
|
|
17
|
+
done
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
hippo_load_config() { # opt-in per hook; silent no-op when config.env is absent
|
|
21
|
+
local root f line name lineno
|
|
22
|
+
local name_re='^HIPPOCAMPUS_[A-Z0-9_]+$'
|
|
23
|
+
root="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
24
|
+
f="${HIPPOCAMPUS_CONFIG_ENV:-$root/.claude/hooks/lib/config.env}"
|
|
25
|
+
[ -f "$f" ] || return 0
|
|
26
|
+
lineno=0
|
|
27
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
28
|
+
lineno=$((lineno + 1))
|
|
29
|
+
case "$line" in ''|'#'*) continue ;; esac
|
|
30
|
+
name="${line%%=*}"
|
|
31
|
+
if ! [[ $name =~ $name_re ]] || ! _hippo_valid_rhs "${line#*=}"; then
|
|
32
|
+
printf 'configlib: ignoring malformed config.env line %d (not sync output)\n' "$lineno" >&2
|
|
33
|
+
continue
|
|
34
|
+
fi
|
|
35
|
+
if eval "[ -n \"\${${name}+x}\" ]"; then continue; fi # real env wins
|
|
36
|
+
eval "export $line" # safe: grammar-validated above — value is inert quoted text
|
|
37
|
+
done < "$f"
|
|
38
|
+
return 0
|
|
39
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared hook helpers. Sourced by every hook. Reads the JSON payload from stdin once.
|
|
3
|
+
HOOK_INPUT="${HOOK_INPUT:-$(cat)}"
|
|
4
|
+
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
5
|
+
LOCAL_DIR="${HOOK_LOCAL_DIR:-$REPO_ROOT/.workstate/.local}" # tests set HOOK_LOCAL_DIR to a tmp dir
|
|
6
|
+
mkdir -p "$LOCAL_DIR" 2>/dev/null || true
|
|
7
|
+
source "$(dirname "${BASH_SOURCE[0]}")/configlib.sh" # defines hippo_load_config; NEVER calls it
|
|
8
|
+
|
|
9
|
+
hook_field() { # hook_field <dotted.key> -> prints string value ('' if absent)
|
|
10
|
+
printf '%s' "$HOOK_INPUT" | python3 -c '
|
|
11
|
+
import sys, json
|
|
12
|
+
try: d = json.load(sys.stdin)
|
|
13
|
+
except Exception: sys.exit(0)
|
|
14
|
+
cur = d
|
|
15
|
+
for part in sys.argv[1].split("."):
|
|
16
|
+
cur = cur.get(part) if isinstance(cur, dict) else None
|
|
17
|
+
if cur is None: break
|
|
18
|
+
print(cur if isinstance(cur, str) else ("" if cur is None else json.dumps(cur)))
|
|
19
|
+
' "$1"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
once_per_session() { # exit 0 the FIRST time this (session,key) is seen
|
|
23
|
+
local sid marker
|
|
24
|
+
sid="$(hook_field session_id)"; sid="${sid:-nosid}"
|
|
25
|
+
marker="$LOCAL_DIR/nudge.$sid.$1"
|
|
26
|
+
[ -e "$marker" ] && return 1
|
|
27
|
+
: > "$marker"; return 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
log_override() { # log_override <gate> <reason>
|
|
31
|
+
printf '%s\t%s\t%s\n' "$(git rev-parse --short HEAD 2>/dev/null || echo -)" "$1" "$2" \
|
|
32
|
+
>> "$LOCAL_DIR/overrides.log"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
emit_block() { printf '%b\n' "$1" >&2; exit 2; } # block tool/stop (%b renders \n)
|
|
36
|
+
emit_nudge() { printf '%b\n' "$1" >&2; exit 0; } # advisory, non-blocking
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Declarative [[nudges]] from hippocampus.toml (compiled into config.env by sync).
|
|
3
|
+
# Python `re` match against the Bash command — 4KB truncation, forked child killed
|
|
4
|
+
# after 2s (spec: a pathological regex fails to "no nudge", never hangs, NEVER blocks).
|
|
5
|
+
source "$(dirname "$0")/lib/hooklib.sh"
|
|
6
|
+
hippo_load_config
|
|
7
|
+
count="${HIPPOCAMPUS_NUDGE_COUNT:-0}"
|
|
8
|
+
case "$count" in ''|*[!0-9]*) exit 0 ;; esac
|
|
9
|
+
[ "$count" -eq 0 ] && exit 0
|
|
10
|
+
CMD="$(hook_field tool_input.command)"
|
|
11
|
+
[ -z "$CMD" ] && exit 0
|
|
12
|
+
msg="$(printf '%s' "$CMD" | python3 -c '
|
|
13
|
+
import os, re, signal, sys
|
|
14
|
+
count = int(sys.argv[1])
|
|
15
|
+
cmd = sys.stdin.read()[:4096]
|
|
16
|
+
r, w = os.pipe()
|
|
17
|
+
pid = os.fork()
|
|
18
|
+
if pid == 0: # child: match and flush incrementally
|
|
19
|
+
os.close(r) # so a later rule timeout cannot erase
|
|
20
|
+
for i in range(1, count + 1): # earlier rules already-found messages
|
|
21
|
+
pat = os.environ.get(f"HIPPOCAMPUS_NUDGE_{i}_MATCH", "")
|
|
22
|
+
m = os.environ.get(f"HIPPOCAMPUS_NUDGE_{i}_MSG", "")
|
|
23
|
+
src = os.environ.get(f"HIPPOCAMPUS_NUDGE_{i}_SRC", "")
|
|
24
|
+
if not (pat and m):
|
|
25
|
+
continue
|
|
26
|
+
try:
|
|
27
|
+
hit = re.search(pat, cmd)
|
|
28
|
+
except re.error:
|
|
29
|
+
hit = None
|
|
30
|
+
if hit:
|
|
31
|
+
line = m + (f" [{src}]" if src else "")
|
|
32
|
+
os.write(w, (line + "\n").encode())
|
|
33
|
+
os._exit(0)
|
|
34
|
+
os.close(w) # parent: 2s or the child dies
|
|
35
|
+
signal.signal(signal.SIGALRM, lambda *_: os.kill(pid, signal.SIGKILL))
|
|
36
|
+
signal.alarm(2)
|
|
37
|
+
data = b""
|
|
38
|
+
while True:
|
|
39
|
+
chunk = os.read(r, 65536)
|
|
40
|
+
if not chunk:
|
|
41
|
+
break
|
|
42
|
+
data += chunk
|
|
43
|
+
os.waitpid(pid, 0)
|
|
44
|
+
signal.alarm(0)
|
|
45
|
+
sys.stdout.write(data.decode(errors="replace"))
|
|
46
|
+
' "$count" 2>/dev/null)" || exit 0
|
|
47
|
+
[ -n "$msg" ] && emit_nudge "$msg"
|
|
48
|
+
exit 0
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
4
|
+
CONFIGLIB="$REPO_ROOT/.claude/hooks/lib/configlib.sh"
|
|
5
|
+
[ -f "$CONFIGLIB" ] && source "$CONFIGLIB" 2>/dev/null || true
|
|
6
|
+
type hippo_load_config >/dev/null 2>&1 && { hippo_load_config || true; }
|
|
7
|
+
ONBOARDING="${HIPPOCAMPUS_ONBOARDING_DOC:-docs/onboarding.md}"
|
|
8
|
+
# startup check: warn if auto-memory isn't pointed in-repo, else writes go OFF-repo silently
|
|
9
|
+
CFG="$REPO_ROOT/.claude/settings.local.json"; want="$REPO_ROOT/.memory"
|
|
10
|
+
got="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("autoMemoryDirectory",""))' "$CFG" 2>/dev/null || echo "")"
|
|
11
|
+
[ "$got" = "$want" ] || echo "⚠️ autoMemoryDirectory='$got' (expected '$want') — memory may be writing OFF-repo. See $ONBOARDING"
|
|
12
|
+
# the CI secret floor ships for GitHub + GitLab only; other hosts get hooks-only — say so
|
|
13
|
+
remotes="$(git -C "$REPO_ROOT" remote -v 2>/dev/null || true)"
|
|
14
|
+
if [ -n "$remotes" ]; then
|
|
15
|
+
case "$remotes" in
|
|
16
|
+
*github.com*|*gitlab*) : ;;
|
|
17
|
+
*) echo "⚠️ no known CI floor host in git remotes — the non-bypassable CI secret floor ships for GitHub/GitLab; on this host only the local hooks protect you. See $ONBOARDING" ;;
|
|
18
|
+
esac
|
|
19
|
+
fi
|
|
20
|
+
[ -n "${HIPPOCAMPUS_DECISION_LOG:-}" ] && echo "Durable record: ${HIPPOCAMPUS_DECISION_LOG} (write history there, not in the baton)"
|
|
21
|
+
H="$REPO_ROOT/.workstate/HANDOFF.md"
|
|
22
|
+
if [ -f "$H" ]; then
|
|
23
|
+
echo "=== .workstate/HANDOFF.md (session baton) ==="
|
|
24
|
+
cat "$H"
|
|
25
|
+
fi
|
|
26
|
+
exit 0
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Invariant 6: the enforcement layer's control files are never edited silently.
|
|
3
|
+
# 🟡 nudge on Write/Edit touching hippocampus.toml, config.env, or settings.json.
|
|
4
|
+
source "$(dirname "$0")/lib/hooklib.sh"
|
|
5
|
+
FP="$(hook_field tool_input.file_path)"
|
|
6
|
+
[ -z "$FP" ] && exit 0
|
|
7
|
+
rel="${FP#"$REPO_ROOT"/}"
|
|
8
|
+
case "$rel" in
|
|
9
|
+
hippocampus.toml|.claude/hooks/lib/config.env|.claude/settings.json) : ;;
|
|
10
|
+
*) exit 0 ;;
|
|
11
|
+
esac
|
|
12
|
+
changed="$(cd "$REPO_ROOT" && git diff -U0 -- "$rel" 2>&1 | grep '^[+-][^+-]' | head -12 || true)"
|
|
13
|
+
emit_nudge "config-edit: $rel changed — this file configures the enforcement layer. If you edited hippocampus.toml, run: python3 scripts/hippocampus_sync.py sync. If you edited a GENERATED file (config.env, settings.json) by hand, sync will overwrite it and CI's drift check will fail.\nChanged lines:\n$changed"
|