KeiSeiKit-1.0/hooks/auto-dev-guard.sh
Parfii-bot f3f5f79760 feat(frontend-loop): kei-db-contract primitive + frontend-validator agent + auto-dev-guard hook
Frontend continuous-quality loop landed. Three composable cubes:

Wave 1 — kei-db-contract primitive (~870 LOC, 7 cubes per Constructor Pattern):
- Diffs SQL CREATE TABLE migrations against TypeScript type/interface declarations
- 4 drift modes: ORPHAN-SQL, ORPHAN-TS, TYPE-MISMATCH, NULL-MISMATCH
- Reuses sqlparser-rs (Apache 2.0) + regex + walkdir + serde_json + clap
- CLI: kei-db-contract <project-root> [--output json|text] [--strict]
- 5/5 integration tests pass (cargo check + cargo test green)
- Smoke-tested on keisei-marketplace: drift_count=266 across 30 tables
  (expected — marketplace uses raw better-sqlite3 without explicit row types)

Wave 2 — frontend-validator agent + dev-guard skill extension:
- New _manifests/frontend-validator.toml (substrate_role: edit-local, tools: Bash+Read+Glob+Grep)
- Agent runs: stack detect → tsc --noEmit → eslint → kei-db-contract → playwright (optional)
- Severity rules: TYPE_CHECK FAIL = block, DB_CONTRACT drift > 0 = block, lint = advisory
- skills/dev-guard/SKILL.md extended: 4th agent triggered on .tsx/.ts/.dart edits or DB-layer touches
- adaptive-depth table extended with frontend + DB-layer rows

Wave 3 — auto-dev-guard.sh hook (PostToolUse:Edit|Write):
- Trivial-edit gate: skip if delta < 30 LOC (avoid spawn fatigue)
- File-pattern match: *.tsx|*.ts|*.svelte|*.vue|*.dart OR migrations/*.sql OR src/db/** OR src/types/** OR prisma/schema.prisma OR drizzle.config.*
- Auto-runs kei-db-contract for DB-layer edits if binary on PATH
- Stderr advisory only (exit 0 always — never blocks)
- Bypass: KEI_DISABLED_HOOKS or KEI_HOOK_PROFILE in {advisory-off, minimal, off}
- Smoke-tested with synthetic Edit input (39 LOC delta on .tsx → emits advisory)
- Registered in hooks/hooks.json under PostToolUse:Write|Edit chain

Reusability map (Constructor Pattern compose):
  shared cubes: detect-stack, tsc, eslint, kei-db-contract, kei-visual-snapshot (deferred)
  orchestrators: /dev-start (pre), /dev-guard (during, NOW with frontend-validator),
                 /dev-ship (final), /site-create (init)

Verify-before-commit (RULE 0.13):
- cargo check -p kei-db-contract: PASS
- cargo test -p kei-db-contract: 5 passed
- jq . hooks/hooks.json: valid
- bash hooks/auto-dev-guard.sh < synthetic-input: works (frontend-relevant edit detected, exit 0)

=== STATUS-TRUTH MARKER ===
shipped: functional
stubs: 0
cargo-check: PASS
cargo-test: PASS (5 tests, 0 failures)
behaviour-verified: yes
follow-up-required:
  - kei-visual-snapshot primitive (Playwright wrap) — Wave 4, deferred
  - /dev-start frontend-contract-designer agent + /dev-ship frontend-final-gate — Wave 5, after Wave 1-3 obkatka
  - install.sh wiring for kei-db-contract binary
  - hermes-style emit-on-drift advisory mode

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:39 +08:00

100 lines
3.7 KiB
Bash
Executable file

#!/bin/sh
# auto-dev-guard.sh — PostToolUse:Edit|Write advisory hook.
#
# Triggers a frontend-validator pass after meaningful Edit/Write on
# frontend files (.tsx, .ts, .svelte, .vue, .dart) OR DB-layer files
# (migrations/*.sql, src/db/**, src/types/**, prisma/schema.prisma,
# drizzle.config.*).
#
# DOES NOT block. Emits stderr advisory pointing the user at /dev-guard
# OR auto-spawns a single-shot validator pass (advisory mode) if
# `kei-db-contract` binary is on PATH.
#
# Skip-on-trivial: if Edit's diff is < 30 LOC, skip. Avoid spawn-fatigue.
#
# Bypass: KEI_DISABLED_HOOKS contains "auto-dev-guard" OR
# KEI_HOOK_PROFILE in {advisory-off, minimal, off}.
command -v jq >/dev/null 2>&1 || exit 0
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"
if [ -r "$_KEI_LIB" ]; then
. "$_KEI_LIB"
kei_hook_gate "auto-dev-guard" || exit 0
fi
set -eu
input="$(cat)"
# --- Detect tool + file_path ---
tool=$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null || true)
file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true)
[ -n "$file" ] || exit 0
# --- Match frontend / DB-layer patterns ---
match=0
case "$file" in
*.tsx|*.ts|*.svelte|*.vue|*.dart) match=1 ;;
*/migrations/*.sql) match=1 ;;
*/src/db/*|*/src/types/*) match=1 ;;
*/prisma/schema.prisma|*/drizzle.config.*) match=1 ;;
esac
[ "$match" = "1" ] || exit 0
# --- Trivial-edit gate: skip if change is small ---
# For Edit tool, count line delta. For Write, count total lines.
delta=0
if [ "$tool" = "Edit" ]; then
old_str=$(printf '%s' "$input" | jq -r '.tool_input.old_string // empty' 2>/dev/null || true)
new_str=$(printf '%s' "$input" | jq -r '.tool_input.new_string // empty' 2>/dev/null || true)
old_lines=$(printf '%s' "$old_str" | wc -l 2>/dev/null | tr -d ' ')
new_lines=$(printf '%s' "$new_str" | wc -l 2>/dev/null | tr -d ' ')
delta=$((new_lines > old_lines ? new_lines - old_lines : old_lines - new_lines))
elif [ "$tool" = "Write" ]; then
content=$(printf '%s' "$input" | jq -r '.tool_input.content // empty' 2>/dev/null || true)
delta=$(printf '%s' "$content" | wc -l 2>/dev/null | tr -d ' ')
fi
[ "$delta" -ge 30 ] || exit 0
# --- Resolve project root from file path ---
# Walk up from $file until we find package.json / pubspec.yaml / Cargo.toml.
project_root=""
dir=$(dirname "$file")
for _ in 1 2 3 4 5 6 7 8; do
if [ -f "$dir/package.json" ] || [ -f "$dir/pubspec.yaml" ]; then
project_root="$dir"
break
fi
parent=$(dirname "$dir")
[ "$parent" = "$dir" ] && break
dir="$parent"
done
# --- Emit advisory + (optionally) run kei-db-contract for DB drift ---
echo "[auto-dev-guard] Frontend-relevant edit: $(basename "$file") ($delta LOC delta)" >&2
if [ -n "$project_root" ] && command -v kei-db-contract >/dev/null 2>&1; then
# Only run DB contract check when DB-layer files were edited
case "$file" in
*/migrations/*.sql|*/src/db/*|*/src/types/*|*/prisma/schema.prisma|*/drizzle.config.*)
drift_json=$(kei-db-contract "$project_root" --output json 2>/dev/null || true)
if [ -n "$drift_json" ]; then
drift_count=$(printf '%s' "$drift_json" | jq -r '.drift_count // 0' 2>/dev/null || echo 0)
if [ "$drift_count" -gt 0 ]; then
echo "[auto-dev-guard] DB-contract drift: $drift_count table(s)." >&2
echo "[auto-dev-guard] Run /dev-guard or 'kei-db-contract $project_root' for details." >&2
fi
fi
;;
esac
fi
# Suggest /dev-guard for manual full pass
case "$file" in
*.tsx|*.ts|*.svelte|*.vue|*.dart)
echo "[auto-dev-guard] Tip: /dev-guard for full TS / lint / DB / visual pass." >&2
;;
esac
exit 0