Skip to content

Commit 7a7b578

Browse files
committed
merge: remove remaining conflict markers; pre-commit unified
2 parents 225778a + b1a9383 commit 7a7b578

File tree

162 files changed

+9087
-667
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

162 files changed

+9087
-667
lines changed

.devcontainer/devcontainer.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "echo-dev",
3+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
4+
"features": {
5+
"ghcr.io/devcontainers/features/common-utils:2": {
6+
"username": "vscode",
7+
"installZsh": false
8+
},
9+
"ghcr.io/devcontainers/features/rust:1": {
10+
"profile": "minimal",
11+
"components": ["rustfmt", "clippy"]
12+
},
13+
"ghcr.io/devcontainers/features/node:1": {
14+
"version": "20"
15+
},
16+
"ghcr.io/devcontainers/features/github-cli:1": {}
17+
},
18+
"customizations": {
19+
"vscode": {
20+
"extensions": [
21+
"rust-lang.rust-analyzer",
22+
"serayuzgur.crates",
23+
"tamasfe.even-better-toml",
24+
"vadimcn.vscode-lldb"
25+
]
26+
}
27+
},
28+
"mounts": [
29+
"source=devcontainer-cargo-cache,target=/usr/local/cargo,type=volume",
30+
"source=devcontainer-rustup-cache,target=/usr/local/rustup,type=volume"
31+
],
32+
"containerEnv": {
33+
"CARGO_TERM_COLOR": "always"
34+
},
35+
"overrideCommand": false,
36+
"postCreateCommand": "/bin/bash .devcontainer/post-create.sh"
37+
}

.devcontainer/post-create.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
echo "[devcontainer] Installing default toolchain (1.71.1 via rust-toolchain.toml)..."
5+
if ! command -v rustup >/dev/null 2>&1; then
6+
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail https://sh.rustup.rs | sh -s -- --default-toolchain none -y
7+
export PATH="$HOME/.cargo/bin:$PATH"
8+
fi
9+
10+
rustup toolchain install 1.71.1 --profile minimal
11+
# Do not override default; let rust-toolchain.toml control selection for this repo.
12+
# Ensure components/targets are available for the default toolchain (1.71.1).
13+
rustup component add --toolchain 1.71.1 rustfmt clippy || true
14+
rustup target add --toolchain 1.71.1 wasm32-unknown-unknown || true
15+
16+
echo "[devcontainer] Priming cargo registry cache (optional)..."
17+
cargo fetch || true
18+
19+
echo "[devcontainer] Done. Run 'cargo test -p rmg-core' or 'make ci-local' to validate."

.githooks/commit-msg

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
FILE="${1:-}"
4+
if [[ -z "$FILE" || ! -f "$FILE" ]]; then
5+
echo "[commit-msg] Missing commit message file. Usage: commit-msg <path>" >&2
6+
exit 1
7+
fi
8+
9+
SUBJECT=$(head -n1 "$FILE")
10+
if [[ -z "${SUBJECT// /}" ]]; then
11+
echo "[commit-msg] Empty subject line is not allowed." >&2
12+
exit 1
13+
fi
14+
15+
# Discourage WIP commits (built-in regex; avoid spawning grep)
16+
if [[ "$SUBJECT" =~ ^[[:space:]]*[Ww][Ii][Pp]\b ]]; then
17+
echo "[commit-msg] Please avoid 'WIP' in commit subjects. Tell a short story instead." >&2
18+
exit 1
19+
fi
20+
21+
# Soft length guidance (warn only)
22+
LEN=${#SUBJECT}
23+
if (( LEN > 72 )); then
24+
echo "[commit-msg] Note: subject exceeds 72 chars ($LEN). Consider wrapping for readability." >&2
25+
fi
26+
27+
exit 0

.githooks/pre-commit

100644100755
Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,107 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
# Enforce coupling between PRNG algorithm/version and golden regression vector.
4+
5+
if [[ "${SKIP_HOOKS:-}" == 1 ]]; then
6+
exit 0
7+
fi
8+
9+
# 1) PRNG coupling guard (existing logic)
510
PRNG_FILE="crates/rmg-core/src/math/prng.rs"
611
if git diff --cached --name-only | grep -qx "$PRNG_FILE"; then
712
DIFF=$(git diff --cached -- "$PRNG_FILE" || true)
8-
# Heuristics to detect algorithm changes: edits to these functions imply behavior change
913
if echo "$DIFF" | grep -E '^(\+|-)\s*(fn\s+next_u64|fn\s+from_seed_u64|fn\s+from_seed\(|fn\s+next_int\()' >/dev/null; then
1014
ALGO_CHANGED=1
1115
else
1216
ALGO_CHANGED=0
1317
fi
14-
15-
# Version bump present?
1618
if echo "$DIFF" | grep -E 'PRNG_ALGO_VERSION' >/dev/null; then
1719
VERSION_CHANGED=1
1820
else
1921
VERSION_CHANGED=0
2022
fi
21-
22-
# Golden regression vector updated?
2323
if echo "$DIFF" | grep -E 'next_int_golden_regression|assert_eq!\(values,\s*vec!\[' >/dev/null; then
2424
GOLDEN_CHANGED=1
2525
else
2626
GOLDEN_CHANGED=0
2727
fi
28-
2928
FAIL=0
3029
if [[ "$ALGO_CHANGED" -eq 1 && "$VERSION_CHANGED" -eq 0 ]]; then
3130
echo "pre-commit: PRNG algorithm changed but PRNG_ALGO_VERSION was not bumped." >&2
3231
FAIL=1
3332
fi
34-
3533
if [[ "$VERSION_CHANGED" -eq 1 && "$GOLDEN_CHANGED" -eq 0 ]]; then
3634
echo "pre-commit: PRNG_ALGO_VERSION bumped but golden regression vector was not updated." >&2
3735
FAIL=1
3836
fi
39-
4037
if [[ "$FAIL" -eq 1 ]]; then
4138
echo "pre-commit: Refusing commit. Update algorithm version and golden regression together." >&2
4239
exit 1
4340
fi
4441
fi
4542

46-
# Format gate: run rustfmt check if Rust files are staged
47-
if git diff --cached --name-only | grep -E '\.rs$' >/dev/null; then
48-
echo "[pre-commit] Running cargo fmt --all -- --check ..."
49-
if ! cargo fmt --all -- --check; then
50-
cat <<'EOM' >&2
51-
[pre-commit] Formatting check failed.
43+
# 2) Enforce toolchain pin (matches rust-toolchain.toml)
44+
if command -v rustup >/dev/null 2>&1; then
45+
PINNED=$(awk -F '"' '/^channel/ {print $2}' rust-toolchain.toml 2>/dev/null || echo "")
46+
ACTIVE=$(rustup show active-toolchain 2>/dev/null | awk '{print $1}')
47+
if [[ -n "$PINNED" && "$ACTIVE" != "$PINNED"* ]]; then
48+
echo "pre-commit: Active toolchain '$ACTIVE' != pinned '$PINNED'. Run: rustup override set $PINNED" >&2
49+
exit 1
50+
fi
51+
fi
5252

53-
Run: cargo fmt --all
53+
# 3) Format (auto-fix if opted in)
54+
_auto_fmt="${ECHO_AUTO_FMT:-1}"
55+
case "${_auto_fmt}" in
56+
1|true|TRUE|yes|YES|on|ON)
57+
echo "pre-commit: ECHO_AUTO_FMT=${_auto_fmt} → running cargo fmt (auto-fix)"
58+
cargo fmt --all || { echo "pre-commit: cargo fmt failed" >&2; exit 1; }
59+
# Re-stage only staged files that were reformatted
60+
if STAGED=$(git diff --cached --name-only); then
61+
if [[ -n "$STAGED" ]]; then
62+
echo "$STAGED" | xargs -r git add --
63+
fi
64+
fi
65+
;;
66+
0|false|FALSE|no|NO|off|OFF)
67+
cargo fmt --all -- --check || { echo "pre-commit: cargo fmt check failed" >&2; exit 1; }
68+
;;
69+
*)
70+
# Unknown value → safest is check-only
71+
cargo fmt --all -- --check || { echo "pre-commit: cargo fmt check failed" >&2; exit 1; }
72+
;;
73+
esac
5474

55-
Our policy is to keep the tree rustfmt-clean to reduce review noise and
56-
merge conflicts. The CI also enforces this gate.
57-
EOM
75+
# 4) Docs guard (scaled): only require docs when core public API changed
76+
STAGED=$(git diff --cached --name-only)
77+
CORE_API_CHANGED=$(echo "$STAGED" | grep -E '^crates/rmg-core/src/.*\.rs$' | grep -v '/tests/' || true)
78+
if [[ -n "$CORE_API_CHANGED" ]]; then
79+
echo "$STAGED" | grep -Fx 'docs/execution-plan.md' >/dev/null || { echo 'pre-commit: docs/execution-plan.md must be updated when core API changes.' >&2; exit 1; }
80+
echo "$STAGED" | grep -Fx 'docs/decision-log.md' >/dev/null || { echo 'pre-commit: docs/decision-log.md must be updated when core API changes.' >&2; exit 1; }
81+
fi
82+
83+
# 5) Lockfile guard: ensure lockfile version is v3 (current cargo format)
84+
if [[ -f Cargo.lock ]]; then
85+
# Normalize detected lockfile version (strip quotes/CR/whitespace)
86+
VER_LINE=$(grep -n '^version = ' Cargo.lock | head -n1 | awk -F'= ' '{print $2}' | tr -d '\r' | tr -d '"' | xargs)
87+
if [[ "$VER_LINE" != "3" ]]; then
88+
# Determine pinned toolchain (normalize), fallback to rust-toolchain.toml if unset
89+
_PINNED_RAW="${PINNED:-}"
90+
if [[ -z "$_PINNED_RAW" ]]; then
91+
_PINNED_RAW=$(awk -F '"' '/^channel/ {print $2}' rust-toolchain.toml 2>/dev/null || echo "")
92+
fi
93+
PINNED_NORM=$(printf "%s" "$_PINNED_RAW" | tr -d '\r' | xargs)
94+
echo "pre-commit: Cargo.lock must be lockfile format v3 (found '$VER_LINE')." >&2
95+
echo "Run: cargo +${PINNED_NORM} generate-lockfile" >&2
5896
exit 1
5997
fi
6098
fi
99+
100+
# 6) Targeted clippy + check for changed crates (fast-ish)
101+
CRATES=$(echo "$STAGED" | sed -n 's#^crates/\([^/]*\)/.*#\1#p' | sort -u)
102+
for c in $CRATES; do
103+
cargo clippy -p "$c" --all-targets -- -D warnings -D missing_docs
104+
cargo check -p "$c" --quiet
105+
done
106+
107+
exit 0

.githooks/pre-push

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
PINNED="${PINNED:-1.71.1}"
4+
# MSRV floor for library checks (override with MSRV env)
5+
MSRV="${MSRV:-1.71.1}"
6+
7+
for cmd in cargo rustup rg; do
8+
if ! command -v "$cmd" >/dev/null 2>&1; then
9+
echo "[pre-push] missing dependency: $cmd. Install it and retry." >&2
10+
exit 1
11+
fi
12+
done
13+
14+
echo "🐰 BunBun 🐇"
15+
16+
if [[ "${SKIP_HOOKS:-}" == 1 ]]; then
17+
exit 0
18+
fi
19+
20+
echo "[pre-push] fmt (default toolchain)"
21+
cargo fmt --all -- --check
22+
23+
echo "[pre-push] clippy (workspace, default toolchain)"
24+
cargo clippy --all-targets -- -D warnings -D missing_docs
25+
26+
echo "[pre-push] tests (workspace, default toolchain)"
27+
cargo test --workspace
28+
29+
echo "[pre-push] Testing against MSRV ${MSRV} (core libraries)"
30+
# If any participating crate declares a rust-version greater than MSRV, skip MSRV checks entirely.
31+
CORE_RV=$(awk -F '"' '/^rust-version/ {print $2}' crates/rmg-core/Cargo.toml 2>/dev/null || echo "")
32+
GEOM_RV=$(awk -F '"' '/^rust-version/ {print $2}' crates/rmg-geom/Cargo.toml 2>/dev/null || echo "")
33+
if { [[ -n "$CORE_RV" ]] && printf '%s\n%s\n' "$MSRV" "$CORE_RV" | sort -V | tail -n1 | grep -qx "$CORE_RV" && [[ "$CORE_RV" != "$MSRV" ]]; } \
34+
|| { [[ -n "$GEOM_RV" ]] && printf '%s\n%s\n' "$MSRV" "$GEOM_RV" | sort -V | tail -n1 | grep -qx "$GEOM_RV" && [[ "$GEOM_RV" != "$MSRV" ]]; }; then
35+
echo "[pre-push] Skipping MSRV block: one or more crates declare rust-version > ${MSRV} (core=${CORE_RV:-unset}, geom=${GEOM_RV:-unset})"
36+
else
37+
if ! rustup run "$MSRV" cargo -V >/dev/null 2>&1; then
38+
echo "[pre-push] MSRV toolchain ${MSRV} not installed. Install via: rustup toolchain install ${MSRV}" >&2
39+
exit 1
40+
fi
41+
# Only run MSRV tests for crates that declare rust-version <= MSRV; skip otherwise.
42+
msrv_ok() {
43+
local crate="$1"
44+
local rv
45+
rv=$(awk -F '"' '/^rust-version/ {print $2}' "crates/${crate}/Cargo.toml" 2>/dev/null || echo "")
46+
if [[ -z "$rv" ]]; then
47+
return 0
48+
fi
49+
# If declared rust-version is greater than MSRV, skip.
50+
if printf '%s\n%s\n' "$MSRV" "$rv" | sort -V | tail -n1 | grep -qx "$rv" && [[ "$rv" != "$MSRV" ]]; then
51+
echo "[pre-push] Skipping MSRV test for ${crate} (rust-version ${rv} > MSRV ${MSRV})"
52+
return 1
53+
fi
54+
# If crate depends on workspace rmg-core whose rust-version exceeds MSRV, skip as well
55+
if grep -qE '^rmg-core\s*=\s*\{[^}]*path\s*=\s*"\.\./rmg-core"' "crates/${crate}/Cargo.toml" 2>/dev/null; then
56+
local core_rv
57+
core_rv=$(awk -F '"' '/^rust-version/ {print $2}' "crates/rmg-core/Cargo.toml" 2>/dev/null || echo "")
58+
if [[ -n "$core_rv" ]] && printf '%s\n%s\n' "$MSRV" "$core_rv" | sort -V | tail -n1 | grep -qx "$core_rv" && [[ "$core_rv" != "$MSRV" ]]; then
59+
echo "[pre-push] Skipping MSRV test for ${crate} (depends on rmg-core ${core_rv} > MSRV ${MSRV})"
60+
return 1
61+
fi
62+
fi
63+
return 0
64+
}
65+
if msrv_ok rmg-core; then cargo +"$MSRV" test -p rmg-core --all-targets; fi
66+
if msrv_ok rmg-geom; then cargo +"$MSRV" test -p rmg-geom --all-targets; fi
67+
fi
68+
69+
# Rustdoc warnings guard (public crates)
70+
echo "[pre-push] rustdoc warnings gate (rmg-core @ $PINNED)"
71+
RUSTDOCFLAGS="-D warnings" cargo +"$PINNED" doc -p rmg-core --no-deps
72+
echo "[pre-push] rustdoc warnings gate (rmg-geom @ $PINNED)"
73+
RUSTDOCFLAGS="-D warnings" cargo +"$PINNED" doc -p rmg-geom --no-deps
74+
75+
# Banned patterns
76+
echo "[pre-push] scanning banned patterns"
77+
# Forbid crate-level allow(missing_docs) in library source files, but allow in tests and build scripts
78+
if rg -n '#!\[allow\([^]]*missing_docs[^]]*\)\]' \
79+
crates \
80+
--glob 'crates/**/src/**/*.rs' \
81+
--glob '!**/telemetry.rs' \
82+
--glob '!**/tests/**' \
83+
--glob '!**/build.rs' >/dev/null; then
84+
echo "pre-push: crate-level allow(missing_docs) is forbidden (except telemetry.rs)." >&2
85+
rg -n '#!\[allow\([^]]*missing_docs[^]]*\)\]' crates \
86+
--glob 'crates/**/src/**/*.rs' \
87+
--glob '!**/telemetry.rs' \
88+
--glob '!**/tests/**' \
89+
--glob '!**/build.rs' | cat >&2 || true
90+
exit 1
91+
fi
92+
if rg -n "\#\[unsafe\(no_mangle\)\]" crates >/dev/null; then
93+
echo "pre-push: #[unsafe(no_mangle)] is invalid; use #[no_mangle]." >&2
94+
rg -n "\#\[unsafe\(no_mangle\)\]" crates | cat >&2 || true
95+
exit 1
96+
fi
97+
98+
exit 0

.githooks/pre-rebase

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env bash
2+
echo "[pre-rebase] Rebase is disallowed for this repository. Use merge instead." >&2
3+
exit 1
4+

.github/workflows/ci.yml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- uses: actions/checkout@v4
1616
with:
1717
submodules: false
18-
- uses: dtolnay/rust-toolchain@stable
18+
- uses: dtolnay/rust-toolchain@1.71.1
1919
- uses: Swatinem/rust-cache@v2
2020
with:
2121
workspaces: |
@@ -30,7 +30,9 @@ jobs:
3030
- uses: actions/checkout@v4
3131
with:
3232
submodules: false
33-
- uses: dtolnay/rust-toolchain@stable
33+
- uses: dtolnay/rust-toolchain@1.71.1
34+
with:
35+
components: clippy
3436
- uses: Swatinem/rust-cache@v2
3537
with:
3638
workspaces: |
@@ -45,13 +47,13 @@ jobs:
4547
- uses: actions/checkout@v4
4648
with:
4749
submodules: false
48-
- uses: dtolnay/rust-toolchain@stable
50+
- uses: dtolnay/rust-toolchain@1.71.1
4951
- uses: Swatinem/rust-cache@v2
5052
with:
5153
workspaces: |
5254
.
53-
- name: cargo test
54-
run: cargo test
55+
- name: cargo test (workspace)
56+
run: cargo test --workspace
5557
- name: PRNG golden regression (rmg-core)
5658
run: cargo test -p rmg-core --features golden_prng -- tests::next_int_golden_regression
5759

@@ -86,3 +88,16 @@ jobs:
8688
echo 'docs/decision-log.md must be updated when non-doc files change.';
8789
exit 1;
8890
}
91+
92+
93+
rustdoc:
94+
name: Rustdoc (rmg-core warnings gate)
95+
runs-on: ubuntu-latest
96+
steps:
97+
- uses: actions/checkout@v4
98+
with:
99+
submodules: false
100+
- uses: dtolnay/rust-toolchain@1.71.1
101+
- uses: Swatinem/rust-cache@v2
102+
- name: rustdoc warnings gate
103+
run: RUSTDOCFLAGS="-D warnings" cargo doc -p rmg-core --no-deps

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ Welcome to the **Echo** project. This file captures expectations for any LLM age
2626
- Respect determinism: preferably no random seeds without going through the Echo PRNG.
2727
- Run `cargo clippy --all-targets -- -D missing_docs` and `cargo test` before every PR; CI will expect a zero-warning, fully documented surface.
2828

29+
### Git Hooks & Local CI
30+
- Install repo hooks once with `make hooks` (configures `core.hooksPath`).
31+
- Formatting: pre-commit auto-fixes with `cargo fmt` by default. Set `ECHO_AUTO_FMT=0` to run check-only instead.
32+
- Toolchain: pre-commit verifies your active toolchain matches `rust-toolchain.toml`.
33+
- Docs Guard: when core API files change, the hook requires updating `docs/execution-plan.md` and `docs/decision-log.md` (mirrors the CI check).
34+
2935
## Git Real
3036
1. **NEVER** use `--force` with any git command. If you think you need it, stop and ask the human for help.
3137
2. **NEVER** use rebase. Embrace messy distributed history; plain merges capture the truth, rebases rewrite it.

0 commit comments

Comments
 (0)