SPEC v0.1 and project rules for Kvitt

Write SPEC.md v0.1 from the owner interview (requirements, data model,
stack, API outline, security, business rules, deployment, milestones,
UX sprint brief, contracts). Fill the CLAUDE.md blanks, move the Gradle
pin files into android/, point the tools at android/ and server/, and
record the git bootstrap exception in the handoff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 20:39:02 +02:00
parent 8bef22afb2
commit cab3e935d6
12 changed files with 2412 additions and 48 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Version drift report for gradle/libs.versions.toml (Plan 09, D-056).
"""Version drift report for android/gradle/libs.versions.toml (Plan 09, D-056).
Reads every [versions] entry, finds an artifact that uses it in [libraries] or [plugins], and asks Google Maven and
Maven Central for that artifact's maven-metadata.xml. Prints one table: current, latest stable, newest (only where a
@@ -19,7 +19,7 @@ import urllib.request
import xml.etree.ElementTree as ET
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
ROOT = Path(__file__).resolve().parents[2] / "android" # the Gradle project lives in android/
REPOS = ["https://dl.google.com/android/maven2", "https://repo1.maven.org/maven2"]
# Catalog entries that are SDK levels, not artifacts.
NOT_ARTIFACTS = {"minSdk", "compileSdk", "targetSdk"}
+23 -16
View File
@@ -1,10 +1,12 @@
#!/bin/sh
# Kvitt pre-commit hook: the fast subset of `./gradlew check`. Installed by tools/install-git-hooks.sh.
# Kvitt pre-commit hook: the fast subset of both gates. Installed by tools/install-git-hooks.sh.
# Each check runs only when its project exists, so the hook works on branches before android/ or server/ exist.
set -e
top=$(git rev-parse --show-toplevel)
# Secrets and personal data never reach git. Adjust the list to this project (see CLAUDE.md › Never commit).
bad=$(git -C "$top" ls-files --cached -- local.properties '*.jks' '*.keystore' '*.p12' '*.aar' 'models/' 'export/' | head -5)
# Secrets and personal data never reach git (CLAUDE.md › Never commit).
bad=$(git -C "$top" ls-files --cached -- '*local.properties' '*.jks' '*.keystore' '*.p12' '*.aar' 'models/' 'export/' \
'*service-account*.json' '*google-services.json' '*.pem' '*.sqlite' '*.db' 'infra/secrets/' | head -5)
if [ -n "$bad" ]; then
echo "pre-commit: a secret, keystore, vendor binary or personal-data file is staged or tracked:" >&2
echo "$bad" >&2
@@ -12,17 +14,22 @@ if [ -n "$bad" ]; then
exit 1
fi
[ -x "$top/gradlew" ] || exit 0
# Android: the pure model tests, on the pinned JDK (android/gradle.properties); the shell's java may be a stub.
if [ -x "$top/android/gradlew" ] && [ -d "$top/android/core/model" ]; then
case "$("${JAVA_HOME:+$JAVA_HOME/bin/}java" -version 2>&1 | head -1)" in
*\"25*) ;;
*) [ -d /opt/homebrew/opt/openjdk@25 ] && export JAVA_HOME=/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home ;;
esac
(cd "$top/android" && ./gradlew --quiet :core:model:test) || {
echo "pre-commit: Android fast tests failed (see above). Run tools/gradle-serial.sh check for the full picture." >&2
exit 1
}
fi
# The build runs on the pinned JDK (gradle.properties); the shell's default java may be newer or a macOS stub.
case "$("${JAVA_HOME:+$JAVA_HOME/bin/}java" -version 2>&1 | head -1)" in
*\"25*) ;;
*) [ -d /opt/homebrew/opt/openjdk@25 ] && export JAVA_HOME=/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home ;;
esac
cd "$top"
# Replace with this project's fast checks (architecture rules, the pure-module tests). Keep it under a minute.
./gradlew --quiet :core:model:test || {
echo "pre-commit: fast tests failed (see above). Run ./gradlew check for the full picture." >&2
exit 1
}
# Server: the pure domain crate tests.
if [ -f "$top/server/Cargo.toml" ] && [ -d "$top/server/crates/kvitt-domain" ] && command -v cargo >/dev/null 2>&1; then
(cd "$top/server" && cargo test --quiet -p kvitt-domain) || {
echo "pre-commit: kvitt-domain tests failed (see above). Run the server gate for the full picture." >&2
exit 1
}
fi
+4 -2
View File
@@ -1,9 +1,11 @@
#!/bin/sh
# Runs ./gradlew with the given arguments, one Gradle build at a time across every worktree and agent on this machine
# (CLAUDE.md › Build and verify: parallel builds exhaust memory). Waits for a running build to finish, then runs.
# Usage from any worktree root: tools/gradle-serial.sh check
# The Gradle project lives in android/; the wrapper there is used whatever directory this is started from.
# Usage from any directory of a checkout or worktree: tools/gradle-serial.sh check (or ../tools/gradle-serial.sh from android/)
lock=/tmp/kvitt-gradle.lock
top=$(git rev-parse --show-toplevel) || exit 1
[ -x "$top/android/gradlew" ] || { echo "gradle-serial: no android/gradlew in $top yet" >&2; exit 1; }
while ! mkdir "$lock" 2>/dev/null; do
pid=$(cat "$lock/pid" 2>/dev/null)
if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then rm -rf "$lock"; continue; fi
@@ -11,4 +13,4 @@ while ! mkdir "$lock" 2>/dev/null; do
done
echo $$ > "$lock/pid"
trap 'rm -rf "$lock"' EXIT INT TERM HUP
cd "$top" && ./gradlew "$@"
cd "$top/android" && ./gradlew "$@"
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Installs the Kvitt pre-commit hook into this repository's hooks folder (shared by all worktrees).
# The hook guards itself: on branches without the app build it only runs the export/ check.
# The hook guards itself: on branches without android/ or server/ it only runs the secrets check.
set -euo pipefail
here="$(cd "$(dirname "$0")" && pwd)"
hooks="$(git -C "$here" rev-parse --path-format=absolute --git-common-dir)/hooks"