mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-10 18:07:05 +01:00
repo contract: CLAUDE.md commands, test_changed, CI gate, AGENTS.md update
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a6d5688eb7
commit
8e254dbef6
25
.github/workflows/repo-contract.yml
vendored
Normal file
25
.github/workflows/repo-contract.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: repo-contract
|
||||
|
||||
# The local pre-commit hook can be skipped (a fresh clone has no hooks), so the
|
||||
# contract is enforced on push too. Mirrors md-naming.yml.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
repo-contract:
|
||||
name: repo contract
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check out the shared gate
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: kuhyx/utils
|
||||
path: .utils
|
||||
|
||||
- name: Enforce the repo contract
|
||||
run: bash .utils/scripts/check_repo_contract.sh
|
||||
@ -32,6 +32,12 @@ repos:
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: repo-contract
|
||||
name: repo contract (CLAUDE.md Commands + test_changed)
|
||||
entry: scripts/check_repo_contract.sh
|
||||
language: system
|
||||
pass_filenames: false
|
||||
files: ^(CLAUDE\.md|AGENTS\.md|scripts/test_changed\.sh)$
|
||||
- id: no-noqa
|
||||
name: Block noqa comments
|
||||
entry: '(?i)#\s*(noqa|type:\s*ignore)'
|
||||
|
||||
27
AGENTS.md
27
AGENTS.md
@ -212,21 +212,20 @@ and the root ring unit's `systemctl start|stop` lines live there.
|
||||
|
||||
## Commands
|
||||
|
||||
- Run tests: `python -m pytest wake_alarm/tests/ --cov=wake_alarm --cov-branch --cov-fail-under=100`
|
||||
- Phone tests: `cd phone_app && flutter test && flutter analyze`
|
||||
- Walk the morning on the emulator: `phone_app/scripts/emu_morning.py` (`DOCS-morning-session.md`)
|
||||
- Lint: `pre-commit run --all-files`
|
||||
- Show the resolved alarm / the morning: `python -m wake_alarm._sync --show` (`--no-cache` live), `python -m wake_alarm._session --show`
|
||||
- Show what the next tick would do: `python -m wake_alarm._trigger --dry-run`
|
||||
- Watch it live: `journalctl --user -fu wake-alarm-trigger.service`
|
||||
- Install: `bash install.sh` — Uninstall: `bash uninstall.sh`
|
||||
- Gate the shell/system scripts: `bash scripts/vmbox/harness.sh`
|
||||
(disposable Arch VM; see **`DOCS-vmbox.md`**)
|
||||
- Ring the phone for a manual test: `lib/main_ring_probe.dart` (see
|
||||
`DOCS-phone-alarm.md` — it starts a **real** alarm, and the new sequence is
|
||||
what stops it)
|
||||
- run: n/a: no single entry point; systemd timers (`install.sh`) drive it, inspect with `python -m wake_alarm._sync --show`, `python -m wake_alarm._session --show`, `python -m wake_alarm._trigger --dry-run`
|
||||
- test: `python -m pytest -q wake_alarm/tests/ && cd phone_app && flutter test --reporter=compact`
|
||||
- test-changed: `scripts/test_changed.sh`
|
||||
- lint: `pre-commit run ruff --all-files && cd phone_app && flutter analyze --fatal-infos --fatal-warnings`
|
||||
- coverage: `python -m pytest -q wake_alarm/tests/ --cov=wake_alarm --cov-branch --cov-report=xml --cov-fail-under=100`
|
||||
- coverage-gaps: `coverage-gaps coverage.xml`
|
||||
|
||||
The dev venv lacks `gatelock`, so anything importing `_state` (including
|
||||
Other: full lint gate `pre-commit run --all-files`; watch live
|
||||
`journalctl --user -fu wake-alarm-trigger.service`; `bash install.sh` /
|
||||
`bash uninstall.sh`; shell/system scripts gate `bash scripts/vmbox/harness.sh`
|
||||
(disposable Arch VM, **`DOCS-vmbox.md`**); emulator morning
|
||||
`phone_app/scripts/emu_morning.py` (`DOCS-morning-session.md`); ring the phone
|
||||
via `lib/main_ring_probe.dart` (`DOCS-phone-alarm.md` — starts a **real**
|
||||
alarm). The dev venv lacks `gatelock`: anything importing `_state` (incl.
|
||||
`_trigger`) needs `/usr/bin/python3`, not `.venv/bin/python`.
|
||||
|
||||
## Do NOT
|
||||
|
||||
26
scripts/check_repo_contract.sh
Executable file
26
scripts/check_repo_contract.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Fail if this repo breaks the shared repo contract.
|
||||
#
|
||||
# Thin delegate to the shared gate in ~/src/utils, which owns the contract
|
||||
# text. Copy this file to scripts/check_repo_contract.sh in a repo; never copy
|
||||
# the shared gate itself, or the shim would exec itself forever.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
readonly SHARED_GATE="${UTILS_ROOT:-$HOME/src/utils}/scripts/check_repo_contract.sh"
|
||||
|
||||
main() {
|
||||
if [[ ! -x "$SHARED_GATE" ]]; then
|
||||
echo "Error: shared repo-contract gate not found at $SHARED_GATE" >&2
|
||||
echo " Clone github.com/kuhyx/utils to ~/src/utils, or set" >&2
|
||||
echo " UTILS_ROOT to where it lives." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec bash "$SHARED_GATE" "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
70
scripts/test_changed.sh
Executable file
70
scripts/test_changed.sh
Executable file
@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Run only the tests related to files changed vs HEAD (staged, unstaged and
|
||||
# untracked). Quiet: failures plus a one-line summary. Falls back to the full
|
||||
# suite when a change cannot be mapped to tests. Generated by repo_contract.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
CHANGED=()
|
||||
while IFS= read -r f; do
|
||||
[[ -n "$f" && -e "$f" ]] && CHANGED+=("$f")
|
||||
done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u)
|
||||
|
||||
pytest_run() {
|
||||
if [[ -f uv.lock ]]; then uv run pytest "$@"
|
||||
elif [[ -f poetry.lock ]]; then poetry run pytest "$@"
|
||||
else python3 -m pytest "$@"; fi
|
||||
}
|
||||
|
||||
if grep -qs testmon pyproject.toml requirements*.txt; then
|
||||
pytest_run --testmon -q --tb=short; exit $?
|
||||
fi
|
||||
|
||||
full=0
|
||||
dart=0
|
||||
dfull=0
|
||||
tests=()
|
||||
dtests=()
|
||||
for f in "${CHANGED[@]}"; do
|
||||
case "$f" in
|
||||
*/conftest.py | conftest.py | pyproject.toml | requirements*.txt | pytest.ini | setup.cfg) full=1 ;;
|
||||
phone_app/lib/*.dart)
|
||||
rel="${f#phone_app/lib/}"
|
||||
[[ -f "phone_app/test/${rel%.dart}_test.dart" ]] && dtests+=("test/${rel%.dart}_test.dart")
|
||||
dart=1
|
||||
;;
|
||||
phone_app/test/*_test.dart) dtests+=("${f#phone_app/}"); dart=1 ;;
|
||||
phone_app/pubspec.yaml | phone_app/pubspec.lock) dart=1; dfull=1 ;;
|
||||
*.py)
|
||||
base="$(basename "$f" .py)"
|
||||
if [[ "$base" == test_* || "$base" == *_test ]]; then
|
||||
tests+=("$f")
|
||||
else
|
||||
while IFS= read -r t; do tests+=("$t"); done \
|
||||
< <(git ls-files "*test_${base}.py" "*${base}_test.py")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
dart_run() {
|
||||
if [[ $dfull -eq 1 || ${#dtests[@]} -eq 0 ]]; then (cd phone_app && flutter test --reporter=compact)
|
||||
else (cd phone_app && flutter test --reporter=compact "${dtests[@]}"); fi
|
||||
}
|
||||
|
||||
if [[ $full -eq 0 && ${#CHANGED[@]} -eq 0 ]]; then echo "no changes vs HEAD: nothing to test"; exit 0; fi
|
||||
if [[ $full -eq 0 && ( ${#tests[@]} -gt 0 || $dart -eq 1 ) ]]; then
|
||||
rc=0
|
||||
[[ ${#tests[@]} -gt 0 ]] && { pytest_run -q --tb=short --no-cov "${tests[@]}" || rc=$?; }
|
||||
[[ $dart -eq 1 ]] && { dart_run || rc=$?; }
|
||||
exit $rc
|
||||
fi
|
||||
if [[ $full -eq 0 ]] && ! printf '%s\n' "${CHANGED[@]}" | grep -q '\.py$'; then
|
||||
echo "no python changes: nothing to test"; exit 0
|
||||
fi
|
||||
echo "no mapped tests: running full suite"
|
||||
pytest_run -q --tb=short --no-cov
|
||||
Loading…
Reference in New Issue
Block a user