Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ jobs:

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview

- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov

- name: Install cargo-audit
uses: taiki-e/install-action@cargo-audit

- name: Cache cargo
uses: Swatinem/rust-cache@v2


- name: Run unit tests
run: cargo test
- name: CI gate
run: ./harness ci
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
*.zip
node_modules/
/packages/*/vendor/

.claude/
CLAUDE.md
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# AGENTS.md

This subproject is the Corgea developer CLI (Rust → npm + pip via maturin).

## Commands

- After edits: `./harness check` — clippy fix, format, tests, suppression report
- Pre-commit: `./harness pre-commit` — staged Rust files only (auto via git hook)
- CI: `./harness ci` — strict clippy (`-D warnings`), format check, dep audit, tests + coverage gate (min 13%)
- Audit: `./harness audit` — `cargo audit` for known dep vulnerabilities
- Coverage: `./harness coverage [--min=N]` — cargo-llvm-cov; HTML report under `target/llvm-cov/`; fails if line coverage < N (default 13)
- Lint: `./harness lint` — clippy + format check, no fixes
- Test: `./harness test` — `cargo test`
- Fix: `./harness fix` — clippy fix + format
- Setup: `./harness setup-hooks` — install `.git/hooks/pre-commit`
- Auto-format: `./harness post-edit` — runs `cargo fmt` on changed Rust files (wire into your editor/agent's post-edit hook)

Add `--verbose` to stream raw command output instead of the quiet summary.
293 changes: 293 additions & 0 deletions harness
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
#!/usr/bin/env bash
# Project development tasks. Bash + cargo + git only.
# Usage: ./harness <command> [--verbose] [--min=N]
#
# Commands: check, fix, lint, test, audit, coverage, pre-commit, ci,
# post-edit, setup-hooks, suppressions

set -u

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT"

VERBOSE=0
COVERAGE_MIN=13
for arg in "$@"; do
case "$arg" in
--verbose) VERBOSE=1 ;;
--min=*) COVERAGE_MIN="${arg#--min=}" ;;
esac
done

if [ -t 1 ]; then
GREEN=$'\033[32m'; RED=$'\033[31m'; BLUE=$'\033[34m'; DIM=$'\033[2m'; RESET=$'\033[0m'
else
GREEN=""; RED=""; BLUE=""; DIM=""; RESET=""
fi

# ── Runner ──────────────────────────────────────────────────────────
# run <desc> <no_exit:0|1> -- <cmd...>
# Quiet by default: captures stdout+stderr, prints only on failure.
# --verbose streams raw output.
# no_exit=1 lets the caller aggregate failures (e.g. cmd_check).

LAST_RC=0
LAST_OUTPUT=""

run() {
local desc="$1"; shift
local no_exit="$1"; shift
[ "$1" = "--" ] && shift

if [ "$VERBOSE" -eq 1 ]; then
printf " %s→ %s%s\n" "$DIM" "$*" "$RESET"
"$@"
LAST_RC=$?
LAST_OUTPUT=""
if [ "$LAST_RC" -eq 0 ]; then
printf " %s✓%s %s\n" "$GREEN" "$RESET" "$desc"
return 0
fi
printf " %s✗%s %s\n" "$RED" "$RESET" "$desc"
[ "$no_exit" = "0" ] && exit "$LAST_RC"
return "$LAST_RC"
fi

local tmp; tmp="$(mktemp)"
"$@" >"$tmp" 2>&1
LAST_RC=$?
LAST_OUTPUT="$(cat "$tmp")"
rm -f "$tmp"
if [ "$LAST_RC" -eq 0 ]; then
printf " %s✓%s %s\n" "$GREEN" "$RESET" "$desc"
return 0
fi
printf " %s✗%s %s\n" "$RED" "$RESET" "$desc"
[ -n "$LAST_OUTPUT" ] && printf "%s\n" "$LAST_OUTPUT"
[ "$no_exit" = "0" ] && exit "$LAST_RC"
return "$LAST_RC"
}

run_with_summary() {
local desc="$1"; shift
local no_exit="$1"; shift
[ "$1" = "--" ] && shift

run "$desc" "$no_exit" -- "$@"
local rc=$?
[ $rc -ne 0 ] && return $rc

# Reprint last line with test summary suffix (cargo test).
local passed total_passed=0 duration=0
while IFS= read -r line; do
passed="$(printf "%s" "$line" | sed -nE 's/.*ok\. ([0-9]+) passed.*/\1/p')"
[ -n "$passed" ] && total_passed=$(( total_passed + passed ))
local d
d="$(printf "%s" "$line" | sed -nE 's/.*finished in ([0-9.]+)s.*/\1/p')"
if [ -n "$d" ]; then
awk_cmp=$(awk -v a="$d" -v b="$duration" 'BEGIN{print (a>b)?1:0}')
[ "$awk_cmp" = "1" ] && duration="$d"
fi
done <<<"$LAST_OUTPUT"
if [ "$total_passed" -gt 0 ]; then
# Overwrite previous OK line with summary detail.
printf "\033[1A\033[2K %s✓%s %s %s(%s passed, %ss)%s\n" \
"$GREEN" "$RESET" "$desc" "$DIM" "$total_passed" "$duration" "$RESET"
fi
return 0
}

# ── Git helpers ─────────────────────────────────────────────────────

staged_rs_files() {
git diff --cached --name-only --diff-filter=d --relative 2>/dev/null \
| grep -E '\.rs$' || true
}

changed_rs_files() {
git status --porcelain 2>/dev/null \
| sed -E 's/^...//' \
| grep -E '\.rs$' || true
}

# ── Suppressions (report-only) ──────────────────────────────────────

cmd_suppressions() {
printf "\n=== Suppressions ===\n\n"
local total=0 line_total=0 crate_total=0
local file
local tmp; tmp="$(mktemp)"
while IFS= read -r -d '' file; do
grep -oE '#!?\[allow\([^)]*\)\]' "$file" 2>/dev/null >>"$tmp" || true
done < <(find src -type f -name '*.rs' -print0 2>/dev/null)
[ -d tests ] && while IFS= read -r -d '' file; do
grep -oE '#!?\[allow\([^)]*\)\]' "$file" 2>/dev/null >>"$tmp" || true
done < <(find tests -type f -name '*.rs' -print0 2>/dev/null)

line_total=$(awk '/^#\[allow/ {n++} END{print n+0}' "$tmp")
crate_total=$(awk '/^#!\[allow/ {n++} END{print n+0}' "$tmp")
total=$(( line_total + crate_total ))

printf "Suppressions: %d total\n" "$total"
[ "$total" -eq 0 ] && { rm -f "$tmp"; return 0; }
[ "$line_total" -gt 0 ] && printf " allow: %d\n" "$line_total"
[ "$crate_total" -gt 0 ] && printf " allow_crate: %d\n" "$crate_total"

# Top 10 rules across both kinds.
sed -E 's/#!?\[allow\(([^)]*)\)\]/\1/' "$tmp" \
| tr ',' '\n' \
| sed -E 's/^[[:space:]]+|[[:space:]]+$//g' \
| grep -v '^$' \
| sort | uniq -c | sort -rn | head -10 \
| awk '{ rule=$2; for (i=3;i<=NF;i++) rule=rule" "$i; printf " %s: %d\n", rule, $1 }'
rm -f "$tmp"
return 0
}

# ── Commands ────────────────────────────────────────────────────────

cmd_fix() {
run "Clippy fix" 0 -- cargo clippy --fix --allow-dirty --allow-staged
run "Format" 0 -- cargo fmt
}

cmd_lint() {
run "Clippy" 0 -- cargo clippy
run "Format check" 0 -- cargo fmt --check
}

cmd_test() {
run_with_summary "Tests" 0 -- cargo test
}

cmd_audit() {
_cmd_audit_inner 0
}

_cmd_audit_inner() {
local strict="$1"
if cargo audit --version >/dev/null 2>&1; then
run "Dep audit" 0 -- cargo audit
return
fi
if [ "$strict" = "1" ]; then
printf " %s✗%s Dep audit (cargo-audit not installed)\n" "$RED" "$RESET"
exit 1
fi
printf " %s⊘ Dep audit skipped (install: cargo install cargo-audit)%s\n" "$DIM" "$RESET"
}

cmd_coverage() {
printf "\n%s[coverage]%s min=%s%%\n\n" "$BLUE" "$RESET" "$COVERAGE_MIN"
if ! cargo llvm-cov --version >/dev/null 2>&1; then
printf " %s✗%s Coverage (cargo-llvm-cov not installed)\n" "$RED" "$RESET"
printf " %sInstall:%s cargo install cargo-llvm-cov\n" "$DIM" "$RESET"
exit 1
fi
run "Coverage (min ${COVERAGE_MIN}%)" 0 -- \
cargo llvm-cov --summary-only --fail-under-lines "$COVERAGE_MIN"
run "HTML report" 0 -- cargo llvm-cov report --html
printf " %sHTML:%s %s/target/llvm-cov/html/index.html\n" \
"$DIM" "$RESET" "$ROOT"
}

cmd_post_edit() {
local changed; changed="$(changed_rs_files)"
[ -z "$changed" ] && return 0
# Never fail the Stop hook.
run "Format" 1 -- cargo fmt || true
return 0
}

cmd_pre_commit() {
local staged; staged="$(staged_rs_files)"
if [ -z "$staged" ]; then
printf "No staged Rust files — skipping checks\n"
return 0
fi
printf "\n%s[pre-commit]%s\n\n" "$BLUE" "$RESET"
# Check-only: never rewrite the working tree behind the commit.
# Mirrors the CI gate so anything that passes here passes there.
run "Clippy (strict)" 0 -- cargo clippy -- -D warnings
run "Format check" 0 -- cargo fmt --check
cmd_test
Comment thread
juangaitanv marked this conversation as resolved.
Comment thread
juangaitanv marked this conversation as resolved.
}

cmd_check() {
local start; start=$(date +%s)
printf "\n%s[check]%s Running pre-flight checks...\n\n" "$BLUE" "$RESET"

local passed=0 failed=0
run "Clippy fix" 1 -- cargo clippy --fix --allow-dirty --allow-staged
[ $? -eq 0 ] && passed=$(( passed + 1 )) || failed=$(( failed + 1 ))
run "Format" 1 -- cargo fmt
[ $? -eq 0 ] && passed=$(( passed + 1 )) || failed=$(( failed + 1 ))
run "Clippy (strict)" 1 -- cargo clippy -- -D warnings
[ $? -eq 0 ] && passed=$(( passed + 1 )) || failed=$(( failed + 1 ))
run_with_summary "Tests" 1 -- cargo test
[ $? -eq 0 ] && passed=$(( passed + 1 )) || failed=$(( failed + 1 ))

cmd_suppressions

local elapsed=$(( $(date +%s) - start ))
printf "\n"
if [ "$failed" -gt 0 ]; then
printf "%sFAIL%s %d passed, %d failed %s(%ds)%s\n" \
"$RED" "$RESET" "$passed" "$failed" "$DIM" "$elapsed" "$RESET"
exit 1
fi
printf "%sOK%s %d passed %s(%ds)%s\n" \
"$GREEN" "$RESET" "$passed" "$DIM" "$elapsed" "$RESET"
}

cmd_ci() {
printf "\n%s[ci]%s\n\n" "$BLUE" "$RESET"
run "Clippy (strict)" 0 -- cargo clippy -- -D warnings
run "Format check" 0 -- cargo fmt --check
_cmd_audit_inner 1
if ! cargo llvm-cov --version >/dev/null 2>&1; then
printf " %s✗%s Coverage (cargo-llvm-cov not installed)\n" "$RED" "$RESET"
printf " %sInstall:%s cargo install cargo-llvm-cov\n" "$DIM" "$RESET"
exit 1
fi
run_with_summary "Tests + coverage (min ${COVERAGE_MIN}%)" 0 -- \
cargo llvm-cov --summary-only --fail-under-lines "$COVERAGE_MIN"
}

cmd_setup_hooks() {
local hook_dir="$ROOT/.git/hooks"
local hook="$hook_dir/pre-commit"
mkdir -p "$hook_dir"
cat >"$hook" <<'EOF'
#!/bin/sh
exec "$(git rev-parse --show-toplevel)/harness" pre-commit
EOF
chmod +x "$hook"
printf "Installed pre-commit hook at %s\n" "$hook"
}

# ── Dispatch ────────────────────────────────────────────────────────

cmd="${1:-check}"
case "$cmd" in
check) cmd_check ;;
fix) cmd_fix ;;
lint) cmd_lint ;;
test) cmd_test ;;
audit) cmd_audit ;;
coverage) cmd_coverage ;;
pre-commit) cmd_pre_commit ;;
ci) cmd_ci ;;
post-edit) cmd_post_edit ;;
setup-hooks) cmd_setup_hooks ;;
suppressions) cmd_suppressions ;;
-h|--help|help)
printf "Usage: ./harness <command> [--verbose] [--min=N]\n\n"
printf "Commands: check, fix, lint, test, audit, coverage, pre-commit,\n"
printf " ci, post-edit, setup-hooks, suppressions\n"
;;
*)
printf "Unknown command: %s\n" "$cmd" >&2
exit 1
;;
esac
Loading
Loading