Skip to content

Commit 9e64f2b

Browse files
author
RichardHightower
committed
feat: PostToolUse hook is fail-closed validate, not curate (0.7.2) #55
hooks.json runs okf-hook-validate.sh. This pack validates. It does not curate. okf-curate.sh stays as an exec shim.
1 parent 5887cc3 commit 9e64f2b

13 files changed

Lines changed: 214 additions & 121 deletions

.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "okf-graph-eng",
3-
"version": "0.7.1",
3+
"version": "0.7.2",
44
"description": "Graph engineering for OKF repositories — impact analysis, progressive disclosure, typed edges, and fail-closed post-edit validate. Codex-compatible packaging for the same skills and hooks used by Claude Code and Grok Build.",
55
"author": {
66
"name": "Rick Hightower",

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
Notable changes to **okf-graph-eng**. Newest first. Released sections are
44
frozen — corrections go in the next release's notes.
55

6+
## 0.7.2 — 2026-08-17
7+
8+
### Changed
9+
10+
- PostToolUse hook is **fail-closed validate**, named that way.
11+
`hooks/hooks.json` now runs `scripts/okf-hook-validate.sh`.
12+
- This pack **validates**. It does not curate (no catalog rewrite after Write).
13+
- `scripts/okf-curate.sh` remains a one-line exec shim for old skill text.
14+
- Codex `.codex-plugin/plugin.json` still points at `hooks/hooks.json`; the
15+
command behind that file is now the validate script.
16+
- Implements the hook close-bar on [okf-plugin#55](https://github.com/SpillwaveSolutions/okf-plugin/issues/55).
17+
618
## 0.7.1 — 2026-08-16
719

820
### Changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Works in **Claude Code** and **Grok Build** (zero-config: Grok Build reads Claud
88
|---|---|
99
| **Plugin name** | `okf-graph-eng` |
1010
| **Repo** | [SpillwaveSolutions/okf-plugin](https://github.com/SpillwaveSolutions/okf-plugin) |
11-
| **Version** | 0.5.0 |
11+
| **Version** | 0.7.2 |
1212
| **License** | MIT |
1313

1414
## Why this plugin

docs/.index/publish-manifest.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -852,9 +852,9 @@
852852
"frozen": false,
853853
"page_name": "CLI-Reference",
854854
"render": "doc+banner",
855-
"render_hash": "3ba9e7ebe0ed",
855+
"render_hash": "16b061c0a5a3",
856856
"source": "docs/user_guide/cli-reference.md",
857-
"source_hash": "67635d2a7565",
857+
"source_hash": "b15752cebe01",
858858
"title": "CLI Reference",
859859
"truth_state": "current",
860860
"wiki_key": "guide/cli-reference"
@@ -864,9 +864,9 @@
864864
"frozen": false,
865865
"page_name": "Plugin-Guide",
866866
"render": "doc+banner",
867-
"render_hash": "460b1f65b656",
867+
"render_hash": "c64c3cd67135",
868868
"source": "docs/user_guide/plugin-guide.md",
869-
"source_hash": "9b9acb68ce72",
869+
"source_hash": "351f14bd5dec",
870870
"title": "Plugin Guide",
871871
"truth_state": "current",
872872
"wiki_key": "guide/plugin-guide"
@@ -876,9 +876,9 @@
876876
"frozen": false,
877877
"page_name": "User-Guide",
878878
"render": "doc+banner",
879-
"render_hash": "ecd7dbdca3e4",
879+
"render_hash": "1d6bc732fdf8",
880880
"source": "docs/user_guide/user-guide.md",
881-
"source_hash": "24db8f5f5da9",
881+
"source_hash": "638e6d93d25a",
882882
"title": "User Guide",
883883
"truth_state": "current",
884884
"wiki_key": "guide/user-guide"

docs/user_guide/cli-reference.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,28 +78,30 @@ python3 scripts/okf-ticket-link.py emit --bundle <bundle> --dry-run # preview
7878

7979
Default GitHub project: `SpillwaveSolutions/okf-plugin`.
8080

81-
## `scripts/okf-curate.sh`
81+
## `scripts/okf-hook-validate.sh`
8282

83-
Post-edit hook helper, wired to `Write|Edit|MultiEdit` in `hooks/hooks.json`.
83+
Post-edit hook helper, wired to `apply_patch|Write|Edit|MultiEdit` in
84+
`hooks/hooks.json`. This pack **validates**. It does not curate.
8485
Takes a file path as `$1`, or reads the `PostToolUse` payload as JSON on stdin
85-
(`.tool_input.file_path`) — which is how Claude Code actually delivers it.
86+
(`.tool_input.file_path` or a Codex `apply_patch` patch).
8687

8788
A Markdown extension (`.md`/`.markdown`) is the only pre-check; **bundle
8889
membership decides the rest**. It walks up from the edited file for the nearest
8990
ancestor holding an `index.md` containing `okf_version`, or a `.okf/` directory
90-
with an `index.md`. That root is what gets curated, so a bundle rooted anywhere
91-
— not just `.okf/`, `knowledge/` or `sample-okf/` — is covered.
91+
with an `index.md`. That root is what gets validated, so a bundle rooted
92+
anywhere — not just `.okf/`, `knowledge/` or `sample-okf/` — is covered.
9293

9394
Finding no bundle root is a **silent** no-op: there is no fallback to the repo's
9495
own `.okf/` or `sample-okf/`, so editing an unrelated Markdown file neither
95-
curates the wrong bundle nor prints anything.
96+
validates the wrong bundle nor prints anything.
9697

97-
Inside a bundle it runs `okf validate` (plus `okf lint` if available), `okfcli
98-
validate`, or — with no external CLI — `okf-graph.py validate` from this repo.
99-
Never fails the edit: every branch exits `0`.
98+
Inside a bundle it runs `okf validate`, `okfcli validate`, or — with no
99+
external CLI — `okf-graph.py validate` from this repo. **Fail-closed:** a
100+
broken bundle exits non-zero. `scripts/okf-curate.sh` is a compatibility shim
101+
that execs this script.
100102

101103
```bash
102-
scripts/okf-curate.sh sample-okf/knowledge/tool-okf-graph-py.md
104+
scripts/okf-hook-validate.sh sample-okf/knowledge/tool-okf-graph-py.md
103105
```
104106

105107
## `scripts/substack_okf.py`

docs/user_guide/plugin-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Metadata: `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`.
4040
- **Skills**`skills/*/SKILL.md`
4141
- **Commands**`commands/*.md` (`/okf-init`, `/okf-impact`, …)
4242
- **Agent**`agents/graph-engineer.md`
43-
- **Hooks**`hooks/hooks.json`post-edit curate
43+
- **Hooks**`hooks/hooks.json`fail-closed `okf-hook-validate.sh`
4444

4545
Use `${CLAUDE_PLUGIN_ROOT}` for script paths inside skills/hooks.
4646

docs/user_guide/user-guide.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,18 @@ python3 scripts/okf-graph.py edges <bundle> --rel routes_to
128128
# or /okf-maintain
129129
```
130130

131-
### Curation on save
131+
### Validate on save
132132

133-
The post-edit hook (`Write|Edit|MultiEdit`) runs `scripts/okf-curate.sh` on
134-
every Markdown edit. The script walks up from the edited file looking for a
135-
bundle root — the nearest ancestor with an `index.md` containing `okf_version`,
136-
or a `.okf/` directory — and validates that bundle. A bundle rooted anywhere
137-
qualifies, not just `.okf/`, `knowledge/` or `sample-okf/`.
133+
The post-edit hook (`apply_patch|Write|Edit|MultiEdit`) runs
134+
`scripts/okf-hook-validate.sh` on every Markdown edit. The script walks up from
135+
the edited file looking for a bundle root — the nearest ancestor with an
136+
`index.md` containing `okf_version`, or a `.okf/` directory — and **validates**
137+
that bundle. This pack does not curate. A bundle rooted anywhere qualifies,
138+
not just `.okf/`, `knowledge/` or `sample-okf/`.
138139

139140
Edit a Markdown file that is in no bundle and the hook does nothing and says
140-
nothing: there is no fallback to some other bundle in the repo. It reports; it
141-
never blocks the edit.
141+
nothing: there is no fallback to some other bundle in the repo. Inside a
142+
bundle a failed validate is fail-closed (non-zero).
142143

143144
### Tickets (WikiTicket / worklog)
144145

hooks/hooks.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
{
2+
"description": "Fail-closed validate after knowledge writes. This pack does not curate. No SessionStart reminders.",
23
"hooks": {
34
"PostToolUse": [
45
{
56
"matcher": "apply_patch|Write|Edit|MultiEdit",
67
"hooks": [
78
{
89
"type": "command",
9-
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/okf-curate.sh\"",
10+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/okf-hook-validate.sh\"",
1011
"timeout": 45
1112
}
1213
]

plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
33
"name": "okf-graph-eng",
4-
"version": "0.7.1",
4+
"version": "0.7.2",
55
"description": "Graph engineering for OKF repositories — impact analysis, agent/harness graphs, progressive disclosure, typed edges, TicketLink/worklog bridges, and write isolation bindings for multi-host agents.",
66
"author": {
77
"name": "Rick Hightower",

scripts/okf-curate.sh

Lines changed: 4 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,6 @@
11
#!/usr/bin/env bash
2-
# PostToolUse hook: fail-closed validate of the touched OKF bundle.
3-
# Takes a file path as $1, or reads a Claude / Codex PostToolUse payload
4-
# from stdin (Write/Edit file_path, or apply_patch patch text).
2+
# Compatibility shim. The PostToolUse hook is fail-closed validate.
3+
# Prefer scripts/okf-hook-validate.sh. This name stays so old skill text
4+
# and operator muscle memory still work.
55
set -euo pipefail
6-
7-
FILE="${1:-}"
8-
if [[ -z "$FILE" ]]; then
9-
FILE="$(python3 -c '
10-
import json, re, sys
11-
def extract(data):
12-
if not isinstance(data, dict):
13-
return ""
14-
nests = [data.get("tool_input"), data.get("arguments"), data]
15-
for nest in nests:
16-
if not isinstance(nest, dict):
17-
continue
18-
for key in ("file_path", "path", "file"):
19-
v = nest.get(key)
20-
if isinstance(v, str) and v.strip():
21-
return v.strip()
22-
for key in ("input", "patch"):
23-
v = nest.get(key)
24-
if not isinstance(v, str):
25-
continue
26-
m = re.search(r"\*\*\* (?:Add|Update|Delete) File: (.+)", v)
27-
if m:
28-
return m.group(1).strip()
29-
return ""
30-
try:
31-
print(extract(json.load(sys.stdin)))
32-
except Exception:
33-
pass
34-
' 2>/dev/null || true)"
35-
fi
36-
37-
# Cheap pre-check only: OKF bundles are Markdown, so anything else can never
38-
# need validation and is not worth a filesystem walk. Bundle membership itself
39-
# is decided by find_bundle_root below — a hard-coded list of path fragments
40-
# ("knowledge/", "sample-okf/") is not a bundle test.
41-
if [[ -z "$FILE" ]]; then
42-
exit 0
43-
fi
44-
case "$FILE" in
45-
*.md|*.markdown) ;;
46-
*) exit 0 ;;
47-
esac
48-
49-
if [[ "$FILE" != /* ]]; then
50-
FILE="$(pwd)/$FILE"
51-
fi
52-
53-
# Resolve bundle root: nearest ancestor containing index.md with okf_version,
54-
# or a .okf/ bundle directory. No fallback to a repo's .okf/ or sample-okf/:
55-
# a file that is not inside a bundle must not be validated against an
56-
# unrelated one just because the repo happens to ship a bundle somewhere.
57-
find_bundle_root() {
58-
local dir
59-
dir="$(cd "$(dirname "$FILE")" 2>/dev/null && pwd)" || return 1
60-
while [[ "$dir" != "/" ]]; do
61-
if [[ -f "$dir/index.md" ]] && grep -q 'okf_version' "$dir/index.md" 2>/dev/null; then
62-
echo "$dir"
63-
return 0
64-
fi
65-
if [[ -d "$dir/.okf" && -f "$dir/.okf/index.md" ]]; then
66-
echo "$dir/.okf"
67-
return 0
68-
fi
69-
dir="$(dirname "$dir")"
70-
done
71-
return 1
72-
}
73-
74-
# Silent when the file is not in a bundle: every Markdown edit in every repo
75-
# reaches this point, and the hook must not narrate non-events.
76-
BUNDLE_ROOT="$(find_bundle_root || true)"
77-
if [[ -z "${BUNDLE_ROOT:-}" ]]; then
78-
exit 0
79-
fi
80-
81-
echo "okf-validate: validating bundle at $BUNDLE_ROOT (touched: $FILE)"
82-
83-
if command -v okf >/dev/null 2>&1; then
84-
okf validate "$BUNDLE_ROOT"
85-
elif command -v okfcli >/dev/null 2>&1; then
86-
okfcli validate "$BUNDLE_ROOT"
87-
else
88-
# No external CLI: use this repo's own validator, which sits next to us and
89-
# understands typed edges. Fail-closed: propagate the validator exit code.
90-
python3 "$(dirname "$0")/okf-graph.py" validate "$BUNDLE_ROOT"
91-
fi
6+
exec "$(cd "$(dirname "$0")" && pwd)/okf-hook-validate.sh" "$@"

0 commit comments

Comments
 (0)