From 6f2b67570608e7f7f054b2d9b35f8dab3b781eed Mon Sep 17 00:00:00 2001 From: ThinkAI Readiness Date: Tue, 26 May 2026 17:30:33 +0000 Subject: [PATCH] chore(readiness): improve AI agent readiness --- .cursor/hooks.json | 17 ++++++++ .cursor/hooks/README.md | 66 ++++++++++++++++++++++++++++++++ .cursor/hooks/after-file-edit.sh | 38 ++++++++++++++++++ .cursor/hooks/post-shell-hint.sh | 45 ++++++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 .cursor/hooks.json create mode 100644 .cursor/hooks/README.md create mode 100755 .cursor/hooks/after-file-edit.sh create mode 100755 .cursor/hooks/post-shell-hint.sh diff --git a/.cursor/hooks.json b/.cursor/hooks.json new file mode 100644 index 0000000000..e56a808c37 --- /dev/null +++ b/.cursor/hooks.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "hooks": { + "afterFileEdit": [ + { + "command": ".cursor/hooks/after-file-edit.sh", + "matcher": "Write|StrReplace" + } + ], + "postToolUse": [ + { + "command": ".cursor/hooks/post-shell-hint.sh", + "matcher": "Shell" + } + ] + } +} diff --git a/.cursor/hooks/README.md b/.cursor/hooks/README.md new file mode 100644 index 0000000000..fadd225b15 --- /dev/null +++ b/.cursor/hooks/README.md @@ -0,0 +1,66 @@ +# Cursor agent hooks + +Project-level [Cursor hooks](https://cursor.com/docs/agent/hooks) automate checks around agent file edits and shell commands. Configuration lives in [`.cursor/hooks.json`](../hooks.json); scripts run from the repository root. + +## Registered hooks + +| Event | Script | Matcher | Purpose | +|-------|--------|---------|---------| +| `afterFileEdit` | `after-file-edit.sh` | `Write`, `StrReplace` | Optionally run [prek](https://prek.j178.dev/) on the edited file (lint/format aligned with `.pre-commit-config.yaml`). | +| `postToolUse` | `post-shell-hint.sh` | `Shell` | After shell commands, suggest targeted backend tests when a matching test file exists. | + +Both hooks **fail open** (errors do not block the agent) unless you change the scripts or set `failClosed: true` in `hooks.json`. + +## Opt-in: prek after agent edits + +By default, `after-file-edit.sh` is a no-op so agent sessions stay fast. To run prek on each agent-edited file under `backend/` or `frontend/`: + +```bash +export CURSOR_RUN_PREK_ON_EDIT=1 +``` + +Requires `uv` on `PATH` (same as [development.md](../../development.md#pre-commits-and-code-linting)). Manual equivalent: + +```bash +uv run prek run --files path/to/changed/file +``` + +## Post-shell test hints + +After a `Shell` tool call that touches `backend/app/**/*.py`, `post-shell-hint.sh` looks for a corresponding `backend/tests/**/test_*.py` and returns `additional_context` suggesting: + +```bash +cd backend && uv run pytest path/to/test_file.py +``` + +This does not run pytest automatically (tests may need Docker/DB). Run tests when you are ready. + +## Adding or changing hooks + +1. Edit `.cursor/hooks.json` (schema `version: 1`). +2. Add scripts under `.cursor/hooks/` with a shebang; make them executable (`chmod +x`). +3. Use the narrowest [hook event](https://cursor.com/docs/agent/hooks) and a `matcher` when possible. +4. Reload: save `hooks.json` or restart Cursor; verify in **Settings → Hooks** or the **Hooks** output channel. + +Example — gate destructive git commands (`failClosed: true`): + +```json +{ + "version": 1, + "hooks": { + "beforeShellExecution": [ + { + "command": ".cursor/hooks/block-force-push.sh", + "matcher": "git push.*--force", + "failClosed": true + } + ] + } +} +``` + +## Safety conventions + +- Prefer **audit or suggest** over auto-running heavy commands (full `prek --all-files`, full pytest suite) during agent loops. +- Do not commit secrets; hooks read stdin JSON only — no credential files. +- Match [CONTRIBUTING.md](../../CONTRIBUTING.md) and pre-commit rules; hooks complement, not replace, `uv run prek install` before `git commit`. diff --git a/.cursor/hooks/after-file-edit.sh b/.cursor/hooks/after-file-edit.sh new file mode 100755 index 0000000000..1ad4e8b2f1 --- /dev/null +++ b/.cursor/hooks/after-file-edit.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Optional prek on agent-edited files. See .cursor/hooks/README.md +set -euo pipefail + +if [[ "${CURSOR_RUN_PREK_ON_EDIT:-}" != "1" ]]; then + exit 0 +fi + +input=$(cat) +file_path=$(printf '%s' "$input" | python3 -c ' +import json, sys +try: + data = json.load(sys.stdin) +except json.JSONDecodeError: + sys.exit(0) +path = data.get("file_path") or data.get("path") or "" +if path: + print(path) +' 2>/dev/null || true) + +[[ -n "${file_path:-}" ]] || exit 0 + +repo_root="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$repo_root" + +case "$file_path" in + backend/*|frontend/*) + ;; + *) + exit 0 + ;; +esac + +if command -v uv >/dev/null 2>&1; then + uv run prek run --files "$file_path" || true +fi + +exit 0 diff --git a/.cursor/hooks/post-shell-hint.sh b/.cursor/hooks/post-shell-hint.sh new file mode 100755 index 0000000000..48a4d26db3 --- /dev/null +++ b/.cursor/hooks/post-shell-hint.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Suggest targeted pytest after backend shell edits. See .cursor/hooks/README.md +set -euo pipefail + +repo_root="$(cd "$(dirname "$0")/../.." && pwd)" +input=$(cat) +hint=$(printf '%s' "$input" | REPO_ROOT="$repo_root" python3 -c ' +import json, os, re, sys +from pathlib import Path + +try: + data = json.load(sys.stdin) +except json.JSONDecodeError: + sys.exit(0) + +command = data.get("command") or data.get("tool_input", {}).get("command") or "" +if not command: + sys.exit(0) + +repo = Path(os.environ["REPO_ROOT"]) +matches = re.findall(r"backend/app/[\w./-]+\.py", command) +if not matches: + sys.exit(0) + +module_path = matches[-1] +stem = Path(module_path).stem +tests_dir = repo / "backend" / "tests" +if not tests_dir.is_dir(): + sys.exit(0) + +candidates = sorted(tests_dir.rglob(f"test_{stem}.py")) +if not candidates: + sys.exit(0) + +rel = candidates[0].relative_to(repo / "backend") +print(f"If you changed {module_path}, consider: cd backend && uv run pytest {rel}") +' 2>/dev/null || true) + +if [[ -n "${hint:-}" ]]; then + python3 -c 'import json,sys; print(json.dumps({"additional_context": sys.argv[1]}))' "$hint" +else + echo '{}' +fi + +exit 0