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.
Files changed (32) hide show
  1. hippocampus_layer-1.0.0/.gitignore +18 -0
  2. hippocampus_layer-1.0.0/LICENSE +21 -0
  3. hippocampus_layer-1.0.0/PKG-INFO +140 -0
  4. hippocampus_layer-1.0.0/README.md +117 -0
  5. hippocampus_layer-1.0.0/hippocampus/__init__.py +7 -0
  6. hippocampus_layer-1.0.0/hippocampus/_core/.claude/agents/auditor.md +53 -0
  7. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/capture_discovered_work.sh +4 -0
  8. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/guard_auditor_override.sh +15 -0
  9. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/guard_ci_static.sh +22 -0
  10. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/guard_landing.sh +20 -0
  11. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/guard_push_secret.sh +38 -0
  12. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/lib/configlib.sh +39 -0
  13. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/lib/hooklib.sh +36 -0
  14. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/nudge_declarative.sh +48 -0
  15. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/session_start_handoff.sh +26 -0
  16. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/warn_config_edit.sh +13 -0
  17. hippocampus_layer-1.0.0/hippocampus/_core/.claude/hooks/warn_silent_failure.sh +6 -0
  18. hippocampus_layer-1.0.0/hippocampus/_core/.claude/skills/landing-the-plane/SKILL.md +42 -0
  19. hippocampus_layer-1.0.0/hippocampus/_core/.claude/skills/memory-consolidation/SKILL.md +67 -0
  20. hippocampus_layer-1.0.0/hippocampus/_core/.github/workflows/secret-history-sweep.yml +15 -0
  21. hippocampus_layer-1.0.0/hippocampus/_core/THREAT_MODEL.md +43 -0
  22. hippocampus_layer-1.0.0/hippocampus/_core/ci/gitlab-secret-history-sweep.yml +16 -0
  23. hippocampus_layer-1.0.0/hippocampus/_core/install.sh +533 -0
  24. hippocampus_layer-1.0.0/hippocampus/_core/scripts/check_no_pii.py +307 -0
  25. hippocampus_layer-1.0.0/hippocampus/_core/scripts/consolidate_ops.py +120 -0
  26. hippocampus_layer-1.0.0/hippocampus/_core/scripts/hippocampus_config.py +282 -0
  27. hippocampus_layer-1.0.0/hippocampus/_core/scripts/hippocampus_smoke.sh +155 -0
  28. hippocampus_layer-1.0.0/hippocampus/_core/scripts/hippocampus_sync.py +152 -0
  29. hippocampus_layer-1.0.0/hippocampus/_core/scripts/migrate_memory.py +44 -0
  30. hippocampus_layer-1.0.0/hippocampus/_core/scripts/scan_secrets_range.py +117 -0
  31. hippocampus_layer-1.0.0/hippocampus/cli.py +228 -0
  32. 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,7 @@
1
+ """Hippocampus — persistent memory + enforcement layer for agent-driven git repos.
2
+
3
+ Distribution name on PyPI: ``hippocampus-layer`` (``hippocampus`` is taken);
4
+ import name and console command are both ``hippocampus``.
5
+ """
6
+
7
+ __version__ = "1.0.0"
@@ -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,4 @@
1
+ #!/usr/bin/env bash
2
+ source "$(dirname "$0")/lib/hooklib.sh"
3
+ once_per_session capture || exit 0
4
+ emit_nudge "capture: jot any discovered follow-ups in .workstate/open-work.md as '… (from #X)', and keep .workstate/HANDOFF.md fresh."
@@ -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"