Files
actual/.cursor/hooks/guard-shell.sh
T
Matiss Janis AboltinsandClaude Opus 4.8 328417f8de [AI] Consolidate agent tooling and simplify developer guidance (#8089)
* [AI] Replace mechanical agent rules with shared agent hooks + nano-staged

Move the mechanical "remember to run X" rules out of AGENTS.md and the
agent guidance docs into deterministic, cross-agent hooks, and migrate
the pre-commit runner from lint-staged to nano-staged.

- Add shared hook scripts in scripts/agent-hooks/ (git-guard,
  format-edited-file, no-strict-ignore-new-file, prefer-one-component,
  check-on-stop, common helpers) wired for Claude (.claude/settings.json),
  Codex (.codex/config.toml), and Cursor (.cursor/hooks.json + adapters).
- Enforce "avoid enum" via a new actual/no-enum lint rule, grandfathering
  the two existing declarations in .oxlintrc.json overrides.
- Migrate lint-staged -> nano-staged (.nano-staged.json, .husky/pre-commit,
  package.json).
- Trim AGENTS.md, .github/agents/pr-and-commit-rules.md, the Cursor rule,
  and the committing skill down to the rules that aren't auto-enforced.
- Add release note 8089.md.

* [AI] Align PR-title wording in AGENTS.md with canonical rules

* [AI] Fix check-on-stop hook on bash 3.2 and TSV column collapse

The Stop hook silently no-op'd in two ways:

- `declare -A` isn't supported on stock macOS bash 3.2, and the
  slash/hyphen path subscript aborted under `set -u`. Dedupe via a
  space-padded string match instead.
- jq emitted an empty middle TSV field for a package with test but no
  typecheck; tab is IFS-whitespace so `read` collapsed it and shifted the
  columns (eslint-plugin-actual then ran neither). Emit non-empty
  sentinels (-, yes/no) so no field can collapse.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

---------

Co-authored-by: Claude <[email protected]>
2026-06-08 20:54:54 +00:00

47 lines
1.5 KiB
Bash
Executable File

#!/bin/sh
# Cursor `beforeShellExecution` adapter.
#
# Translates Cursor's hook I/O to the shared guard in
# scripts/agent-hooks/git-guard.sh. Cursor input on stdin: { command, cwd, ... }.
# Output on stdout: { permission: "allow" | "deny", userMessage, agentMessage }.
input=$(cat)
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
deny() {
jq -n --arg m "$1" \
'{permission:"deny", userMessage:"Blocked by repo policy", agentMessage:$m}'
exit 0
}
allow() {
jq -n '{permission:"allow"}'
exit 0
}
# Fail closed: a malformed payload (jq parse failure / missing command) must
# deny, not silently allow.
cmd=$(printf '%s' "$input" | jq -re '.command // empty') ||
deny "Invalid Cursor hook payload: could not read .command."
cwd=$(printf '%s' "$input" | jq -r '.cwd // ""')
# Cursor passes cwd rather than a literal `cd packages/...`, so catch
# yarn-run-from-a-workspace here before delegating.
case "$cwd" in
"$ROOT"/packages | "$ROOT"/packages/*)
case "$cmd" in
yarn* | *" yarn "*)
deny "Run yarn from the repo root, not a child workspace (AGENTS.md). Use 'yarn workspace <name> <cmd>' from $ROOT." ;;
esac ;;
esac
# Delegate the git-safety checks to the shared guard (reads .tool_input.command).
# Fail closed: deny on a block (exit 2) AND on any unexpected guard failure.
status=0
err=$(printf '{"tool_input":{"command":%s}}' "$(jq -n --arg c "$cmd" '$c')" |
"$ROOT/scripts/agent-hooks/git-guard.sh" 2>&1) || status=$?
case "$status" in
0) allow ;;
2) deny "$err" ;;
*) deny "git-guard failed (exit $status): $err" ;;
esac