|
1 | 1 | #!/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. |
5 | 5 | 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