Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "okf-graph-eng",
"version": "0.7.1",
"version": "0.7.2",
"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.",
"author": {
"name": "Rick Hightower",
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
Notable changes to **okf-graph-eng**. Newest first. Released sections are
frozen — corrections go in the next release's notes.

## 0.7.2 — 2026-08-17

### Changed

- PostToolUse hook is **fail-closed validate**, named that way.
`hooks/hooks.json` now runs `scripts/okf-hook-validate.sh`.
- This pack **validates**. It does not curate (no catalog rewrite after Write).
- `scripts/okf-curate.sh` remains a one-line exec shim for old skill text.
- Codex `.codex-plugin/plugin.json` still points at `hooks/hooks.json`; the
command behind that file is now the validate script.
- Implements the hook close-bar on [okf-plugin#55](https://github.com/SpillwaveSolutions/okf-plugin/issues/55).

## 0.7.1 — 2026-08-16

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Works in **Claude Code** and **Grok Build** (zero-config: Grok Build reads Claud
|---|---|
| **Plugin name** | `okf-graph-eng` |
| **Repo** | [SpillwaveSolutions/okf-plugin](https://github.com/SpillwaveSolutions/okf-plugin) |
| **Version** | 0.5.0 |
| **Version** | 0.7.2 |
| **License** | MIT |

## Why this plugin
Expand Down
12 changes: 6 additions & 6 deletions docs/.index/publish-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -852,9 +852,9 @@
"frozen": false,
"page_name": "CLI-Reference",
"render": "doc+banner",
"render_hash": "3ba9e7ebe0ed",
"render_hash": "16b061c0a5a3",
"source": "docs/user_guide/cli-reference.md",
"source_hash": "67635d2a7565",
"source_hash": "b15752cebe01",
"title": "CLI Reference",
"truth_state": "current",
"wiki_key": "guide/cli-reference"
Expand All @@ -864,9 +864,9 @@
"frozen": false,
"page_name": "Plugin-Guide",
"render": "doc+banner",
"render_hash": "460b1f65b656",
"render_hash": "c64c3cd67135",
"source": "docs/user_guide/plugin-guide.md",
"source_hash": "9b9acb68ce72",
"source_hash": "351f14bd5dec",
"title": "Plugin Guide",
"truth_state": "current",
"wiki_key": "guide/plugin-guide"
Expand All @@ -876,9 +876,9 @@
"frozen": false,
"page_name": "User-Guide",
"render": "doc+banner",
"render_hash": "ecd7dbdca3e4",
"render_hash": "1d6bc732fdf8",
"source": "docs/user_guide/user-guide.md",
"source_hash": "24db8f5f5da9",
"source_hash": "638e6d93d25a",
"title": "User Guide",
"truth_state": "current",
"wiki_key": "guide/user-guide"
Expand Down
22 changes: 12 additions & 10 deletions docs/user_guide/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,28 +78,30 @@ python3 scripts/okf-ticket-link.py emit --bundle <bundle> --dry-run # preview

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

## `scripts/okf-curate.sh`
## `scripts/okf-hook-validate.sh`

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

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

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

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

```bash
scripts/okf-curate.sh sample-okf/knowledge/tool-okf-graph-py.md
scripts/okf-hook-validate.sh sample-okf/knowledge/tool-okf-graph-py.md
```

## `scripts/substack_okf.py`
Expand Down
2 changes: 1 addition & 1 deletion docs/user_guide/plugin-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Metadata: `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`.
- **Skills** — `skills/*/SKILL.md`
- **Commands** — `commands/*.md` (`/okf-init`, `/okf-impact`, …)
- **Agent** — `agents/graph-engineer.md`
- **Hooks** — `hooks/hooks.json` → post-edit curate
- **Hooks** — `hooks/hooks.json` → fail-closed `okf-hook-validate.sh`

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

Expand Down
17 changes: 9 additions & 8 deletions docs/user_guide/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,18 @@ python3 scripts/okf-graph.py edges <bundle> --rel routes_to
# or /okf-maintain
```

### Curation on save
### Validate on save

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

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

### Tickets (WikiTicket / worklog)

Expand Down
3 changes: 2 additions & 1 deletion hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"description": "Fail-closed validate after knowledge writes. This pack does not curate. No SessionStart reminders.",
"hooks": {
"PostToolUse": [
{
"matcher": "apply_patch|Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/okf-curate.sh\"",
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/okf-hook-validate.sh\"",
"timeout": 45
}
]
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "okf-graph-eng",
"version": "0.7.1",
"version": "0.7.2",
"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.",
"author": {
"name": "Rick Hightower",
Expand Down
93 changes: 4 additions & 89 deletions scripts/okf-curate.sh
Original file line number Diff line number Diff line change
@@ -1,91 +1,6 @@
#!/usr/bin/env bash
# PostToolUse hook: fail-closed validate of the touched OKF bundle.
# Takes a file path as $1, or reads a Claude / Codex PostToolUse payload
# from stdin (Write/Edit file_path, or apply_patch patch text).
# Compatibility shim. The PostToolUse hook is fail-closed validate.
# Prefer scripts/okf-hook-validate.sh. This name stays so old skill text
# and operator muscle memory still work.
set -euo pipefail

FILE="${1:-}"
if [[ -z "$FILE" ]]; then
FILE="$(python3 -c '
import json, re, sys
def extract(data):
if not isinstance(data, dict):
return ""
nests = [data.get("tool_input"), data.get("arguments"), data]
for nest in nests:
if not isinstance(nest, dict):
continue
for key in ("file_path", "path", "file"):
v = nest.get(key)
if isinstance(v, str) and v.strip():
return v.strip()
for key in ("input", "patch"):
v = nest.get(key)
if not isinstance(v, str):
continue
m = re.search(r"\*\*\* (?:Add|Update|Delete) File: (.+)", v)
if m:
return m.group(1).strip()
return ""
try:
print(extract(json.load(sys.stdin)))
except Exception:
pass
' 2>/dev/null || true)"
fi

# Cheap pre-check only: OKF bundles are Markdown, so anything else can never
# need validation and is not worth a filesystem walk. Bundle membership itself
# is decided by find_bundle_root below — a hard-coded list of path fragments
# ("knowledge/", "sample-okf/") is not a bundle test.
if [[ -z "$FILE" ]]; then
exit 0
fi
case "$FILE" in
*.md|*.markdown) ;;
*) exit 0 ;;
esac

if [[ "$FILE" != /* ]]; then
FILE="$(pwd)/$FILE"
fi

# Resolve bundle root: nearest ancestor containing index.md with okf_version,
# or a .okf/ bundle directory. No fallback to a repo's .okf/ or sample-okf/:
# a file that is not inside a bundle must not be validated against an
# unrelated one just because the repo happens to ship a bundle somewhere.
find_bundle_root() {
local dir
dir="$(cd "$(dirname "$FILE")" 2>/dev/null && pwd)" || return 1
while [[ "$dir" != "/" ]]; do
if [[ -f "$dir/index.md" ]] && grep -q 'okf_version' "$dir/index.md" 2>/dev/null; then
echo "$dir"
return 0
fi
if [[ -d "$dir/.okf" && -f "$dir/.okf/index.md" ]]; then
echo "$dir/.okf"
return 0
fi
dir="$(dirname "$dir")"
done
return 1
}

# Silent when the file is not in a bundle: every Markdown edit in every repo
# reaches this point, and the hook must not narrate non-events.
BUNDLE_ROOT="$(find_bundle_root || true)"
if [[ -z "${BUNDLE_ROOT:-}" ]]; then
exit 0
fi

echo "okf-validate: validating bundle at $BUNDLE_ROOT (touched: $FILE)"

if command -v okf >/dev/null 2>&1; then
okf validate "$BUNDLE_ROOT"
elif command -v okfcli >/dev/null 2>&1; then
okfcli validate "$BUNDLE_ROOT"
else
# No external CLI: use this repo's own validator, which sits next to us and
# understands typed edges. Fail-closed: propagate the validator exit code.
python3 "$(dirname "$0")/okf-graph.py" validate "$BUNDLE_ROOT"
fi
exec "$(cd "$(dirname "$0")" && pwd)/okf-hook-validate.sh" "$@"
92 changes: 92 additions & 0 deletions scripts/okf-hook-validate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# PostToolUse hook: fail-closed validate of the touched OKF bundle.
# This pack validates. It does not curate (no catalog rewrite, no mutation).
# Takes a file path as $1, or reads a Claude / Codex PostToolUse payload
# from stdin (Write/Edit file_path, or apply_patch patch text).
set -euo pipefail

FILE="${1:-}"
if [[ -z "$FILE" ]]; then
FILE="$(python3 -c '
import json, re, sys
def extract(data):
if not isinstance(data, dict):
return ""
nests = [data.get("tool_input"), data.get("arguments"), data]
for nest in nests:
if not isinstance(nest, dict):
continue
for key in ("file_path", "path", "file"):
v = nest.get(key)
if isinstance(v, str) and v.strip():
return v.strip()
for key in ("input", "patch"):
v = nest.get(key)
if not isinstance(v, str):
continue
m = re.search(r"\*\*\* (?:Add|Update|Delete) File: (.+)", v)
if m:
return m.group(1).strip()
return ""
try:
print(extract(json.load(sys.stdin)))
except Exception:
pass
' 2>/dev/null || true)"
fi

# Cheap pre-check only: OKF bundles are Markdown, so anything else can never
# need validation and is not worth a filesystem walk. Bundle membership itself
# is decided by find_bundle_root below — a hard-coded list of path fragments
# ("knowledge/", "sample-okf/") is not a bundle test.
if [[ -z "$FILE" ]]; then
exit 0
fi
case "$FILE" in
*.md|*.markdown) ;;
*) exit 0 ;;
esac

if [[ "$FILE" != /* ]]; then
FILE="$(pwd)/$FILE"
fi

# Resolve bundle root: nearest ancestor containing index.md with okf_version,
# or a .okf/ bundle directory. No fallback to a repo's .okf/ or sample-okf/:
# a file that is not inside a bundle must not be validated against an
# unrelated one just because the repo happens to ship a bundle somewhere.
find_bundle_root() {
local dir
dir="$(cd "$(dirname "$FILE")" 2>/dev/null && pwd)" || return 1
while [[ "$dir" != "/" ]]; do
if [[ -f "$dir/index.md" ]] && grep -q 'okf_version' "$dir/index.md" 2>/dev/null; then
echo "$dir"
return 0
fi
if [[ -d "$dir/.okf" && -f "$dir/.okf/index.md" ]]; then
echo "$dir/.okf"
return 0
fi
dir="$(dirname "$dir")"
done
return 1
}

# Silent when the file is not in a bundle: every Markdown edit in every repo
# reaches this point, and the hook must not narrate non-events.
BUNDLE_ROOT="$(find_bundle_root || true)"
if [[ -z "${BUNDLE_ROOT:-}" ]]; then
exit 0
fi

echo "okf-validate: validating bundle at $BUNDLE_ROOT (touched: $FILE)"

if command -v okf >/dev/null 2>&1; then
okf validate "$BUNDLE_ROOT"
elif command -v okfcli >/dev/null 2>&1; then
okfcli validate "$BUNDLE_ROOT"
else
# No external CLI: use this repo's own validator, which sits next to us and
# understands typed edges. Fail-closed: propagate the validator exit code.
python3 "$(dirname "$0")/okf-graph.py" validate "$BUNDLE_ROOT"
fi
Loading
Loading