Merge feat/phase-4-hook-wiring — PreToolUse glue for kei-capability

This commit is contained in:
Parfii-bot 2026-04-23 02:51:11 +08:00
commit bd32b48a54
6 changed files with 146 additions and 4 deletions

View file

@ -92,9 +92,9 @@ _primitives/_rust/kei-capability/ — BINARY (phase 3)
kei-capability check <name> (stdin JSON, exit 0|2)
kei-capability verify <name> (env-driven, exit 0 or fail)
hooks/ — 3-line shell glue (phase 4)
├── agent-capability-check.sh — `exec kei-capability check "$CAP_NAME" "$@"`
└── agent-capability-verify.sh — called by orchestrator post-agent
hooks/ — 3-line shell glue (phase 4 ✓ shipped)
├── agent-capability-check.sh — `exec kei-capability check "$KEI_CAPABILITY_NAME"` — PreToolUse:Bash|Edit|Write, no-op when env unset, fail-open on missing binary
└── agent-capability-verify.sh — orchestrator-driven post-agent: `exec kei-capability verify "$KEI_CAPABILITY_NAME"` with AGENT_ID/TASK_TOML/WORKTREE_PATH/MAIN_REPO/RUN_MODE env
tasks/ — ephemeral, gitignored
└── <agent-id>/{task.toml, prompt.md}
@ -495,7 +495,7 @@ Execution flow:
| 1 | Capability library — 10 × (`capability.toml` + `text.md`) = **20 declarative files** | phase 0 | 1 code-implementer | 1-2 days |
| 2 | Role matrix — 5 `_roles/*.toml` + auto-gen `docs/AGENT-ROLES.md` | phase 0 | 1 code-implementer | 0.5 day |
| 3 | `kei-agent-runtime` + `kei-capability` binaries — compose/spawn/verify CLI + 6 gate modules + 8 verify modules + registry + simulated-merge executor | phase 0 | 1 code-implementer | 5-6 days |
| 4 | Hook wiring — `agent-capability-check.sh` + `agent-capability-verify.sh` 3-line glue + settings.json registration | phases 1+3 | 1 code-implementer | 0.5 day |
| 4 | Hook wiring — `agent-capability-check.sh` + `agent-capability-verify.sh` 3-line glue + settings.json registration | phases 1+3 | 1 code-implementer | 0.5 day (shipped) |
| 5 | Migration — 5 custom agents (code-implementer / critic / architect / security-auditor / validator) adopt role+task-spec invocation | phases 1+2+3+4 | 1 code-implementer | 1 day |
**Phases 1, 2, 3 start in parallel immediately after lock** (different dirs, zero file overlap).

27
hooks/agent-capability-check.sh Executable file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
# agent-capability-check.sh — 3-line hook glue (Agent Substrate v1, phase 4).
#
# Claude-Code hook adapter that routes a PreToolUse event (Bash|Edit|Write)
# to `kei-capability check <capability-name>`. The capability name is set
# per-agent by the orchestrator via env $KEI_CAPABILITY_NAME at Agent spawn
# time; Claude Code's hook protocol has no per-spawn scoping, so this script
# NO-OPs (exit 0, pass-through) when the env var is unset.
#
# Fail-open convention (RULE 0.13, kit-wide): a missing kei-capability
# binary MUST NOT block all tool use — it exits 0 with a stderr note.
# Block semantics come from the gate logic itself (exit 2 on Deny), never
# from adapter absence.
#
# See docs/AGENT-SUBSTRATE-SCHEMA.md §File layout / §Verify execution.
set -eu
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"
if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "agent-capability-check" || exit 0; fi
CAP="${KEI_CAPABILITY_NAME:-}"
[ -z "$CAP" ] && exit 0
command -v kei-capability >/dev/null 2>&1 || {
echo "[agent-capability-check] kei-capability binary not in PATH — fail-open pass-through" >&2
exit 0
}
exec kei-capability check "$CAP"

View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
# agent-capability-verify.sh — orchestrator-driven verify glue (phase 4).
#
# Called by the orchestrator after agent return (NOT by Claude Code's
# hook protocol directly). The orchestrator sets the full context in env:
# KEI_CAPABILITY_NAME — e.g. "quality::cargo-check-green"
# AGENT_ID — ledger agent id
# TASK_TOML — path to task.toml (parameterizes scope/output caps)
# WORKTREE_PATH — agent's worktree
# MAIN_REPO — orchestrator's main repo root
# RUN_MODE — worktree | simulated-merge
#
# Passes through stdin, stdout, exit code from kei-capability verify.
# Fail-open on missing binary (exit 0 + stderr note) — same convention as
# the check side; absence of the adapter must not crash the merge ceremony.
#
# See docs/AGENT-SUBSTRATE-SCHEMA.md §Verify execution.
set -eu
CAP="${KEI_CAPABILITY_NAME:-}"
if [ -z "$CAP" ]; then
echo "[agent-capability-verify] KEI_CAPABILITY_NAME unset — nothing to verify" >&2
exit 0
fi
command -v kei-capability >/dev/null 2>&1 || {
echo "[agent-capability-verify] kei-capability binary not in PATH — fail-open pass-through" >&2
exit 0
}
exec kei-capability verify "$CAP"

View file

@ -43,6 +43,11 @@
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/assemble-validate.sh"
},
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/agent-capability-check.sh",
"statusMessage": "agent-capability-check (Agent Substrate v1, phase 4)..."
}
]
},
@ -52,6 +57,11 @@
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/no-hand-edit-agents.sh"
},
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/agent-capability-check.sh",
"statusMessage": "agent-capability-check (Agent Substrate v1, phase 4)..."
}
]
},

View file

@ -44,6 +44,11 @@
{
"type": "command",
"command": "~/.claude/hooks/assemble-validate.sh"
},
{
"type": "command",
"command": "~/.claude/hooks/agent-capability-check.sh",
"statusMessage": "agent-capability-check (Agent Substrate v1, phase 4)..."
}
]
},
@ -53,6 +58,11 @@
{
"type": "command",
"command": "~/.claude/hooks/no-hand-edit-agents.sh"
},
{
"type": "command",
"command": "~/.claude/hooks/agent-capability-check.sh",
"statusMessage": "agent-capability-check (Agent Substrate v1, phase 4)..."
}
]
},

View file

@ -0,0 +1,66 @@
#!/usr/bin/env bash
# hook_wiring_integration.sh — phase-4 smoke test for Agent Substrate v1.
#
# Asserts the three contract behaviours of hooks/agent-capability-check.sh:
# 1. KEI_CAPABILITY_NAME unset → exit 0 (pass-through)
# 2. Bash "git push" + policy::no-git-ops → exit 2 (deny)
# 3. Bash "cargo check" + policy::no-git-ops → exit 0 (allow)
#
# Build step: `cargo build --release -p kei-capability` from _primitives/_rust.
# PATH is shimmed to include the freshly-built binary; no sudo, no install.
#
# Exit 0 = all 3 assertions pass
# Exit 1 = any assertion failed — stderr names the offending case
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
HOOK="$ROOT/hooks/agent-capability-check.sh"
fail() { echo "HOOK-WIRING FAIL: $*" >&2; exit 1; }
[ -x "$HOOK" ] || chmod +x "$HOOK" 2>/dev/null || fail "hook script not executable: $HOOK"
echo "==> Building kei-capability release binary…"
cd "$ROOT/_primitives/_rust"
cargo build --release -p kei-capability >/dev/null 2>&1 \
|| fail "cargo build -p kei-capability failed"
BIN_DIR="$(pwd)/target/release"
cd "$ROOT"
[ -x "$BIN_DIR/kei-capability" ] || fail "kei-capability binary missing at $BIN_DIR"
export PATH="$BIN_DIR:$PATH"
# ---- Assertion 1: pass-through when KEI_CAPABILITY_NAME unset -----------
echo "==> Assertion 1: env unset → pass-through (exit 0)…"
set +e
( unset KEI_CAPABILITY_NAME
echo '{"tool_name":"Bash","tool_input":{"command":"git push"}}' | "$HOOK" >/dev/null 2>&1
) ; RC=$?
set -e
[ "$RC" -eq 0 ] || fail "unset env must pass-through, got exit $RC"
# ---- Assertion 2: deny git push under policy::no-git-ops ----------------
echo "==> Assertion 2: Bash 'git push' under policy::no-git-ops → deny (exit 2)…"
set +e
OUT=$(KEI_CAPABILITY_NAME=policy::no-git-ops \
echo '{"tool_name":"Bash","tool_input":{"command":"git push"}}' \
| KEI_CAPABILITY_NAME=policy::no-git-ops "$HOOK" 2>&1)
RC=$?
set -e
[ "$RC" -eq 2 ] || fail "expected exit 2 on git-op deny, got $RC (output: $OUT)"
echo "$OUT" | grep -q "policy::no-git-ops\|RULE 0.13\|git operation blocked" \
|| fail "deny output missing expected marker (output: $OUT)"
# ---- Assertion 3: allow cargo check under policy::no-git-ops -----------
echo "==> Assertion 3: Bash 'cargo check' under policy::no-git-ops → allow (exit 0)…"
set +e
OUT=$(echo '{"tool_name":"Bash","tool_input":{"command":"cargo check"}}' \
| KEI_CAPABILITY_NAME=policy::no-git-ops "$HOOK" 2>&1)
RC=$?
set -e
[ "$RC" -eq 0 ] || fail "cargo check must be allowed by policy::no-git-ops, got exit $RC (output: $OUT)"
echo ""
echo "✓ HOOK-WIRING PASS — 3/3 assertions (pass-through / deny / allow)"