mirror of
https://github.com/actualbudget/actual.git
synced 2026-07-11 11:59:55 -05:00
* [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 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
101 lines
3.3 KiB
Bash
Executable File
101 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Agent hook (Stop), shared across Claude and Codex.
|
|
#
|
|
# When the agent finishes its turn, run typecheck + test for each workspace this
|
|
# branch has touched (scoped — far cheaper than the whole-monorepo check).
|
|
# Failures are surfaced back to the agent via exit code 2 so it can fix them.
|
|
# Adapted from a suggestion by @StephenBrown2 on PR #8089.
|
|
set -uo pipefail
|
|
|
|
# Read the hook payload (Claude provides stop_hook_active to prevent loops).
|
|
payload=$(cat 2>/dev/null || true)
|
|
if printf '%s' "$payload" | jq -e '.stop_hook_active == true' >/dev/null 2>&1; then
|
|
exit 0
|
|
fi
|
|
|
|
# Resolve the repo root regardless of which agent invoked us.
|
|
. "$(dirname "$0")/common.sh"
|
|
ROOT=$(resolve_repo_root)
|
|
cd "$ROOT" 2>/dev/null || exit 0
|
|
|
|
# Skip if dependencies aren't installed.
|
|
[ -f "$ROOT/node_modules/.yarn-state.yml" ] || exit 0
|
|
|
|
# Collect changed files, NUL-delimited so paths with spaces survive: this
|
|
# branch's commits vs master, plus tracked + untracked working-tree changes.
|
|
base=$(git merge-base HEAD origin/master 2>/dev/null || true)
|
|
files=()
|
|
while IFS= read -r -d '' f; do
|
|
[ -n "$f" ] && files+=("$f")
|
|
done < <(
|
|
{
|
|
[ -n "$base" ] && git diff -z --name-only "$base"...HEAD
|
|
git diff -z --name-only HEAD
|
|
git ls-files -z --others --exclude-standard
|
|
} 2>/dev/null
|
|
)
|
|
[ "${#files[@]}" -gt 0 ] || exit 0
|
|
|
|
# Map changed source files to the workspace dir that owns them (deduped).
|
|
# Stock macOS ships bash 3.2, which has no associative arrays (`declare -A`) and
|
|
# evaluates `arr[$d]` subscripts as arithmetic (slashes/hyphens in a path then
|
|
# blow up under `set -u`). So dedupe via a space-padded string match instead —
|
|
# `ws_dirs` stays a plain indexed array, which bash 3.2 handles fine.
|
|
seen=" "
|
|
ws_dirs=()
|
|
for f in "${files[@]}"; do
|
|
case "$f" in
|
|
*.js | *.mjs | *.jsx | *.ts | *.tsx) ;;
|
|
*) continue ;;
|
|
esac
|
|
d=$(dirname "$f")
|
|
while [ "$d" != "." ] && [ "$d" != "/" ]; do
|
|
if [ -f "$ROOT/$d/package.json" ]; then
|
|
case "$seen" in
|
|
*" $d "*) ;;
|
|
*)
|
|
seen="$seen$d "
|
|
ws_dirs+=("$d")
|
|
;;
|
|
esac
|
|
break
|
|
fi
|
|
d=$(dirname "$d")
|
|
done
|
|
done
|
|
[ "${#ws_dirs[@]}" -gt 0 ] || exit 0
|
|
|
|
fail=0
|
|
report=""
|
|
for d in "${ws_dirs[@]}"; do
|
|
# One jq read per package.json: name + which scripts exist. Every field is a
|
|
# non-empty token (the name falls back to "-", the flags to yes/no) so that
|
|
# `read` can't collapse an empty middle field — tab is IFS-whitespace, so a
|
|
# package with test-but-no-typecheck would otherwise shift the columns.
|
|
IFS=$'\t' read -r name tc te < <(
|
|
jq -r '[.name // "-",
|
|
(if .scripts.typecheck then "yes" else "no" end),
|
|
(if .scripts.test then "yes" else "no" end)] | @tsv' "$ROOT/$d/package.json"
|
|
)
|
|
[ "$name" != "-" ] || continue
|
|
|
|
if [ "$tc" = "yes" ]; then
|
|
if ! out=$(yarn workspace "$name" run typecheck 2>&1); then
|
|
report="$report"$'\n'"[typecheck failed: $name]"$'\n'"$(printf '%s' "$out" | tail -25)"
|
|
fail=1
|
|
fi
|
|
fi
|
|
if [ "$te" = "yes" ]; then
|
|
if ! out=$(yarn workspace "$name" run test 2>&1); then
|
|
report="$report"$'\n'"[tests failed: $name]"$'\n'"$(printf '%s' "$out" | tail -25)"
|
|
fail=1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [ "$fail" -eq 1 ]; then
|
|
printf 'Stop check found failures in touched workspaces:%s\n' "$report" >&2
|
|
exit 2
|
|
fi
|
|
exit 0
|