From f59be2882b9fc33c36174d1b69f8cb63f7834127 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Sun, 1 Nov 2026 14:31:12 -0800 Subject: [PATCH] fix(ci): check web projects and Rust inputs --- .githooks/pre-commit | 24 +++--- .github/workflows/homie-core-lint.yml | 19 ++++- docs/ci-checks.md | 33 ++++++++ scripts/test-git-checks.py | 107 ++++++++++++++++++++++++++ src/web/package.json | 2 +- 5 files changed, 172 insertions(+), 13 deletions(-) create mode 100644 docs/ci-checks.md create mode 100644 scripts/test-git-checks.py diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 5479c24..8c5c332 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -4,16 +4,18 @@ set -euo pipefail ROOT="$(git rev-parse --show-toplevel)" cd "$ROOT" -STAGED="$(git diff --cached --name-only --diff-filter=ACMR)" +PATHS="$(mktemp)" +trap 'rm -f "$PATHS"' EXIT +git diff --cached --name-only --no-renames -z > "$PATHS" -if [[ -z "$STAGED" ]]; then - exit 0 -fi - -if ! printf '%s\n' "$STAGED" | rg -q '^(Cargo\.toml|Cargo\.lock|src/core/|src/infra/homie-protocol/|src/infra/roci/)'; then - exit 0 -fi - -echo "pre-commit: running homie-core clippy (staged Rust/core changes detected)" -cargo clippy -p homie-core --all-targets --all-features -- -D warnings +while IFS= read -r -d '' path; do + case "$path" in + Cargo.toml|Cargo.lock|.gitmodules|.cargo/*|rust-toolchain*|rustfmt.toml|.rustfmt.toml|*.rs|*/Cargo.toml|src/core/*|src/infra/homie-protocol/*|src/infra/roci|src/infra/roci/*|.githooks/*) + echo "pre-commit: checking Rust workspace formatting and homie-core clippy" + cargo fmt --all -- --check + cargo clippy --locked -p homie-core --all-targets --all-features -- -D warnings + exit 0 + ;; + esac +done < "$PATHS" diff --git a/.github/workflows/homie-core-lint.yml b/.github/workflows/homie-core-lint.yml index 28d686e..c9c2e17 100644 --- a/.github/workflows/homie-core-lint.yml +++ b/.github/workflows/homie-core-lint.yml @@ -9,26 +9,43 @@ on: - Cargo.toml - Cargo.lock - src/core/** + - src/gateway/** - src/infra/homie-protocol/** + - src/infra/roci - src/infra/roci/** + - .gitmodules + - .cargo/** + - rust-toolchain* + - rustfmt.toml + - .rustfmt.toml - .github/workflows/homie-core-lint.yml pull_request: paths: - Cargo.toml - Cargo.lock - src/core/** + - src/gateway/** - src/infra/homie-protocol/** + - src/infra/roci - src/infra/roci/** + - .gitmodules + - .cargo/** + - rust-toolchain* + - rustfmt.toml + - .rustfmt.toml - .github/workflows/homie-core-lint.yml workflow_dispatch: jobs: homie-core-lint: - runs-on: ubuntu-latest + runs-on: boat timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v4 + with: + submodules: recursive + persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable diff --git a/docs/ci-checks.md b/docs/ci-checks.md new file mode 100644 index 0000000..fbec084 --- /dev/null +++ b/docs/ci-checks.md @@ -0,0 +1,33 @@ +# Local CI checks + +Install existing pnpm workspace dependencies with `pnpm install --frozen-lockfile`. +Build shared declarations with `pnpm shared:build` before checking consumers. +`pnpm web:typecheck` now explicitly checks both the application and Vite/Node +projects. The root solution config has no files; plain `tsc --noEmit` against it +was not a web source check. Explicit project checks retain `noEmit` and do not +write build-info files. + +The existing Rust lint workflow checks out the recorded roci submodule recursively, +without persisting checkout credentials. It retains the pinned gitlink, rather +than fetching a moving upstream branch. Gateway, submodule identity and shared +Rust configuration now trigger the existing workflow. Its Linux job uses Boat; +owner-only public execution is configured separately, and no workflow was dispatched. + +`python3 scripts/test-git-checks.py` runs offline disposable Git fixtures against +the executable pre-commit hook and existing CI path filters. The hook handles +staged deletions and renames, spaces and gitlinks, runs nonmutating workspace +format verification and preserves homie-core Clippy failure codes. No hook was +installed as part of this change. + +Qualification limits from secret-free Linux checks follow. + +- Web app and Node/Vite typechecks pass. Deliberate errors in each fail. +- Shared typecheck passes. Mobile typecheck still fails on missing Vitest/Node + test-tool declarations in its existing package contract. +- Pinned roci retrieval succeeds and resolves the missing-path dependency failure. +- Workspace formatting passes. The sandbox Clippy baseline reports existing + `collapsible_match` and `question_mark` diagnostics in homie-core. + +A new mandatory full-graph pre-push gate remains deferred until those existing +baseline failures are resolved. No new schedule, required-check policy, native +acceptance or credentialed integration tests were enabled. diff --git a/scripts/test-git-checks.py b/scripts/test-git-checks.py new file mode 100644 index 0000000..3a3d29d --- /dev/null +++ b/scripts/test-git-checks.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Offline fixtures for the executable pre-commit and existing CI path scopes.""" +import fnmatch +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] + + +class HookTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory(prefix="homie hook ") + self.addCleanup(self.temp.cleanup) + self.repo = Path(self.temp.name) + self.git("init", "-q") + self.git("config", "user.email", "fixture@example.invalid") + self.git("config", "user.name", "Fixture") + (self.repo / "README.md").write_text("fixture\n") + self.git("add", "README.md") + self.git("commit", "-qm", "fixture") + self.bin = self.repo / "bin" + self.bin.mkdir() + cargo = self.bin / "cargo" + cargo.write_text('#!/bin/sh\nprintf "%s\\n" "$*" >> "$CALLS"\n' + 'if [ "$1" = "$FAIL_COMMAND" ]; then exit 23; fi\n') + cargo.chmod(0o755) + self.calls = self.repo / "calls" + + def git(self, *args): + return subprocess.check_output(["git", *args], cwd=self.repo, text=True).strip() + + def stage(self, path): + file = self.repo / path + file.parent.mkdir(parents=True, exist_ok=True) + file.write_text("fixture\n") + self.git("add", "--", path) + + def hook(self, fail=""): + self.calls.write_text("") + result = subprocess.run([str(ROOT / ".githooks/pre-commit")], cwd=self.repo, + env={**os.environ, "PATH": f"{self.bin}:{os.environ['PATH']}", + "CALLS": str(self.calls), "FAIL_COMMAND": fail}, + capture_output=True, text=True) + return result, self.calls.read_text().splitlines() + + def test_workspace_shared_and_gitlink_inputs(self): + for path in ["src/gateway/src/path with spaces.rs", "Cargo.toml", "Cargo.lock", + ".gitmodules", ".cargo/config.toml", "rust-toolchain.toml"]: + with self.subTest(path=path): + self.git("reset", "-q") + self.stage(path) + result, calls = self.hook() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(calls, ["fmt --all -- --check", "clippy --locked -p homie-core --all-targets --all-features -- -D warnings"]) + self.git("reset", "-q") + self.git("update-index", "--add", "--cacheinfo", f"160000,{self.git('rev-parse', 'HEAD')},src/infra/roci") + self.assertEqual(len(self.hook()[1]), 2) + + def test_deleted_and_renamed_rust_inputs(self): + self.stage("src/gateway/deleted.rs") + self.git("commit", "-qm", "source") + self.git("rm", "src/gateway/deleted.rs") + self.assertEqual(len(self.hook()[1]), 2) + self.git("reset", "--hard", "-q") + self.git("mv", "src/gateway/deleted.rs", "renamed.md") + self.assertEqual(len(self.hook()[1]), 2) + + def test_embedded_markdown_modification_and_deletion_check_core(self): + self.stage('src/core/system_prompt.md') + self.git('commit', '-qm', 'embedded input') + self.stage('src/core/system_prompt.md') + (self.repo / 'src/core/system_prompt.md').write_text('changed input\n') + self.git('add', 'src/core/system_prompt.md') + self.assertEqual(len(self.hook()[1]), 2) + self.git('reset', '--hard', '-q') + self.git('rm', 'src/core/system_prompt.md') + self.assertEqual(len(self.hook()[1]), 2) + + def test_nonzero_commands_stop_gate(self): + self.stage("Cargo.lock") + for command in ["fmt", "clippy"]: + with self.subTest(command=command): + self.assertEqual(self.hook(command)[0].returncode, 23) + + def test_empty_and_docs_skip(self): + self.assertEqual(self.hook()[1], []) + self.stage("README.md") + self.assertEqual(self.hook()[1], []) + + def test_existing_ci_triggers_cover_full_workspace(self): + text = (ROOT / ".github/workflows/homie-core-lint.yml").read_text() + for event in ["push:", "pull_request:"]: + block = text.split(" " + event, 1)[1].split(" workflow_dispatch:", 1)[0] + if event == "push:": + block = block.split(" pull_request:", 1)[0] + patterns = [line.strip()[2:] for line in block.splitlines() if line.strip().startswith("- ")] + for path in ["src/gateway/src/lib.rs", "src/core/src/lib.rs", "Cargo.toml", "Cargo.lock", + ".gitmodules", "src/infra/roci", "src/infra/homie-protocol/Cargo.toml", + ".cargo/config.toml", "rust-toolchain.toml"]: + self.assertTrue(any(fnmatch.fnmatchcase(path, pattern) for pattern in patterns), path) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/web/package.json b/src/web/package.json index ec9bda3..d0a6b16 100644 --- a/src/web/package.json +++ b/src/web/package.json @@ -8,7 +8,7 @@ "dev": "vite --host", "prebuild": "pnpm --dir ../apps/shared build", "build": "tsc -b && vite build", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --noEmit -p tsconfig.app.json && tsc --noEmit -p tsconfig.node.json", "lint": "eslint .", "preview": "vite preview" },