Skip to content

Commit 3114324

Browse files
authored
Merge pull request #1181 from Kiln-AI/scosman/better_checks_v3
Rewrite checks.sh for parallel execution with spinner and --agent-mode
2 parents a6dc6ba + c6fd59e commit 3114324

File tree

4 files changed

+150
-46
lines changed

4 files changed

+150
-46
lines changed

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ This repo is a monorepo containing all of the source code, in the following stru
2727

2828
Agents have access to a range of tools for running tests, linting, formatting and typechecking. Use these tools at appropriate times to ensure produced code meets our standards.
2929

30+
To run all checks in a CLI, run `uv run ./checks.sh --agent-mode` (agent mode will reduce tokens unless there is an error).
31+
3032
### Agent Prompts
3133

3234
Agents have access to a number of helpful prompts, which will give you additional context for how you should write code and docs for this repo. Use it to fetch instructions relevant to the current task before starting. For example, read `python_test_guide.md` before writing tests and `frontend_design_guide.md` before writing front end code.

app/web_ui/eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default [
2020
"**/pnpm-lock.yaml",
2121
"**/package-lock.json",
2222
"**/yarn.lock",
23+
"**/vite.config.ts.timestamp*",
2324
],
2425
},
2526
js.configs.recommended,

checks.sh

Lines changed: 146 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,181 @@
1-
#!/bin/sh
1+
#!/bin/bash
22

33
# Check our project: formatting, linting, testing, building, etc.
44
# Good to call this from .git/hooks/pre-commit
5+
# Runs checks in parallel for speed.
56

67
# Important: run with `uv run` to setup the environment
78

8-
set -e
9+
set -euo pipefail
910

1011
# Parse command line arguments
11-
# --staged-only is useful to only run checks on the types of files that are staged for commit, speeding up pre-commit hooks
12+
# --staged-only: only run checks on the types of files that are staged for commit
13+
# --agent-mode: only print output for failed checks (token-friendly for AI agents)
1214
staged_only=false
13-
for arg in "$@"; do
14-
case $arg in
15+
agent_mode=false
16+
while [[ $# -gt 0 ]]; do
17+
case $1 in
1518
--staged-only)
1619
staged_only=true
17-
shift
20+
;;
21+
--agent-mode)
22+
agent_mode=true
1823
;;
1924
*)
20-
echo "Unknown option: $arg"
21-
echo "Usage: $0 [--staged-only]"
25+
echo "Unknown option: $1"
26+
echo "Usage: $0 [--staged-only] [--agent-mode]"
2227
exit 1
2328
;;
2429
esac
30+
shift
2531
done
2632

2733
# work from the root of the repo
2834
cd "$(dirname "$0")"
29-
echo $PWD
3035

31-
headerStart="\n\033[4;34m=== "
32-
headerEnd=" ===\033[0m\n"
36+
# Create temp dir for check outputs, clean up on exit
37+
tmp_dir=$(mktemp -d)
38+
trap 'rm -rf "$tmp_dir"' EXIT
39+
40+
# ── Parallel check runner ────────────────────────────────────────────
41+
42+
declare -a check_names=()
43+
declare -a check_pids=()
44+
declare -a failed_names=()
45+
46+
# Start a named check running in the background.
47+
# Usage: start_check "name" command arg1 arg2 ...
48+
start_check() {
49+
local name="$1"; shift
50+
check_names+=("$name")
51+
"$@" > "$tmp_dir/$name.out" 2>&1 &
52+
check_pids+=($!)
53+
}
54+
55+
# Wait for all checks and report results.
56+
# Shows a spinner with live status while checks are running (skipped in agent mode).
57+
# Returns 0 if all passed, 1 if any failed.
58+
wait_for_checks() {
59+
local any_failed=0
60+
local green="\033[32m" red="\033[31m" dim="\033[2m" reset="\033[0m"
61+
local total=${#check_pids[@]}
62+
local spin_chars='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
63+
local interactive=false
64+
if [ "$agent_mode" = false ] && [ -t 1 ]; then
65+
interactive=true
66+
fi
67+
68+
# Track per-check status: empty=running, 0=pass, non-zero=fail
69+
declare -a check_status=()
70+
for i in "${!check_pids[@]}"; do
71+
check_status[$i]=""
72+
done
73+
74+
# Print a result line for a completed check. In interactive mode, clears
75+
# the spinner line first so the result is on its own permanent line,
76+
# then redraws the spinner below it.
77+
print_result() {
78+
local name="$1" status="$2"
79+
if [ "$status" -ne 0 ]; then
80+
any_failed=1
81+
failed_names+=("$name")
82+
if [ "$interactive" = true ]; then
83+
printf "\r\033[K"
84+
fi
85+
echo -e "${red}✗ FAIL: ${name}${reset}"
86+
cat "$tmp_dir/$name.out"
87+
echo ""
88+
else
89+
if [ "$interactive" = true ]; then
90+
printf "\r\033[K"
91+
echo -e "${green}✓ PASS: ${name}${reset}"
92+
fi
93+
fi
94+
}
95+
96+
if [ "$agent_mode" = false ] && [ -t 1 ]; then
97+
# Poll loop: print results as they arrive, spinner on last line
98+
local spin_idx=0
99+
local done_count=0
100+
while true; do
101+
for i in "${!check_pids[@]}"; do
102+
if [ -z "${check_status[$i]}" ]; then
103+
if ! kill -0 "${check_pids[$i]}" 2>/dev/null; then
104+
wait "${check_pids[$i]}" 2>/dev/null && check_status[$i]=0 || check_status[$i]=$?
105+
done_count=$((done_count + 1))
106+
print_result "${check_names[$i]}" "${check_status[$i]}"
107+
fi
108+
fi
109+
done
110+
111+
[ "$done_count" -eq "$total" ] && break
112+
113+
local spinner="${spin_chars:$spin_idx:1}"
114+
spin_idx=$(( (spin_idx + 1) % ${#spin_chars} ))
115+
local remaining=$((total - done_count))
116+
printf "\r\033[K%s ${dim}running — %d remaining...${reset}" \
117+
"$spinner" "$remaining"
118+
sleep 0.1
119+
done
120+
else
121+
# Agent mode: wait quietly, print only failures
122+
for i in "${!check_pids[@]}"; do
123+
wait "${check_pids[$i]}" 2>/dev/null && check_status[$i]=0 || check_status[$i]=$?
124+
print_result "${check_names[$i]}" "${check_status[$i]}"
125+
done
126+
fi
33127

34-
echo "${headerStart}Checking Python: uv run ruff check ${headerEnd}"
35-
uv run ruff check
128+
# Reset for next batch
129+
check_names=()
130+
check_pids=()
36131

37-
echo "${headerStart}Checking Python: uv run ruff format --check ${headerEnd}"
38-
uv run ruff format --check .
132+
return $any_failed
133+
}
39134

40-
echo "${headerStart}Checking Python Types: uv run ty check${headerEnd}"
41-
uv run ty check
135+
# ── Kick off all checks ──────────────────────────────────────────────
42136

43-
echo "${headerStart}Checking for Misspellings${headerEnd}"
137+
changed_files=""
138+
if [ "$staged_only" = true ]; then
139+
changed_files=$(git diff --name-only --staged)
140+
fi
141+
142+
# Python checks (always run)
143+
start_check "ruff check" uv run ruff check
144+
start_check "ruff format" uv run ruff format --check .
145+
start_check "ty check" uv run ty check
146+
147+
# Misspelling check
44148
if command -v misspell >/dev/null 2>&1; then
45-
find . -type f | grep -v "/node_modules/" | grep -v "/\." | grep -v "/dist/" | grep -v "/desktop/build/" | grep -v "/app/web_ui/build/" | xargs misspell -error
46-
echo "No misspellings found"
149+
start_check "misspell" bash -c 'find . -type f -not -path "*/node_modules/*" -not -path "*/\.*" -not -path "*/dist/*" -not -path "*/desktop/build/*" -not -path "*/app/web_ui/build/*" -print0 | xargs -0 misspell -error'
47150
else
48-
echo "\033[31mWarning: misspell command not found. Skipping misspelling check.\033[0m"
49-
echo "\033[31mTo install follow the instructions at https://github.com/golangci/misspell \033[0m"
151+
echo -e "\033[33mWarning: misspell not found, skipping. Install: https://github.com/golangci/misspell\033[0m"
50152
fi
51153

52-
echo "${headerStart}OpenAPI Schema Check${headerEnd}"
53-
cd app/web_ui/src/lib/
54-
./check_schema.sh --allow-skip
55-
cd ../../../..
154+
# OpenAPI schema check
155+
start_check "openapi schema" bash -c 'cd app/web_ui/src/lib/ && ./check_schema.sh --allow-skip'
56156

57-
echo "${headerStart}Web UI: format, lint, check${headerEnd}"
58-
changed_files=$(git diff --name-only --staged)
157+
# Web UI checks
59158
if [ "$staged_only" = false ] || [[ "$changed_files" == *"app/web_ui/"* ]]; then
60-
echo "${headerStart}Checking Web UI: format, lint, check${headerEnd}"
61-
cd app/web_ui
62-
npm run format_check
63-
npm run lint
64-
npm run check
65-
npm run test_run
66-
echo "Running vite build"
67-
npm run build > /dev/null
68-
cd ../..
69-
else
70-
echo "Skipping Web UI: no files changed"
159+
start_check "web format" bash -c 'cd app/web_ui && npm run format_check'
160+
start_check "web lint" bash -c 'cd app/web_ui && npm run lint'
161+
start_check "web check" bash -c 'cd app/web_ui && npm run check'
162+
start_check "web test" bash -c 'cd app/web_ui && npm run test_run'
163+
start_check "web build" bash -c 'cd app/web_ui && npm run build'
71164
fi
72165

73-
# Check if python files were changed, and run tests if so
166+
# Python tests
74167
if [ "$staged_only" = false ] || echo "$changed_files" | grep -q "\.py$"; then
75-
echo "${headerStart}Running Python Tests${headerEnd}"
76-
python3 -m pytest --benchmark-quiet -q -n auto .
77-
else
78-
echo "${headerStart}Python Checks${headerEnd}"
79-
echo "Skipping Python tests/typecheck: no .py files changed"
168+
start_check "python tests" python3 -m pytest --benchmark-quiet -q -n auto .
169+
fi
170+
171+
# ── Wait and summarize ────────────────────────────────────────────────
172+
173+
if ! wait_for_checks; then
174+
failed_list=$(IFS=','; echo "${failed_names[*]}" | sed 's/,/, /g')
175+
echo -e "\n\033[31mSome checks failed: ${failed_list}\033[0m"
176+
exit 1
177+
fi
178+
179+
if [ "$agent_mode" = false ] && [ -t 1 ]; then
180+
echo -e "\n\033[32mAll checks passed.\033[0m"
80181
fi

hooks_mcp.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
actions:
55
- name: "check_all"
66
description: "Run all checks for the project (web, python, formatting, etc). Very slow. Use for final checks before committing but use more lightweight checks for faster feedback."
7-
command: "uv run ./checks.sh"
7+
command: "uv run ./checks.sh --agent-mode"
88

99
- name: "all_python_tests"
1010
description: "Run all python tests in the project"

0 commit comments

Comments
 (0)