@biffo/cli 0.172.1 → 0.172.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Does each repo's local gate actually cover that repo's CI?
|
|
4
|
+
#
|
|
5
|
+
# ## Why this exists
|
|
6
|
+
#
|
|
7
|
+
# `hook-audit.sh` answers "will a hook execute here". That question hit 100%
|
|
8
|
+
# across the estate on 2026-07-29 while eight repos had a gate that checked
|
|
9
|
+
# **nothing they were written in** — `tabsii-crm` ran one check, terraform-fmt,
|
|
10
|
+
# on a 700-line TypeScript-and-Python change, and printed `verify passed`.
|
|
11
|
+
#
|
|
12
|
+
# Arming was a proxy and it was reported as the outcome. This is the metric that
|
|
13
|
+
# can actually fail: for every repo, the set of check *kinds* its CI runs, minus
|
|
14
|
+
# the ones deliberately excluded, must be covered by the set its gate runs.
|
|
15
|
+
#
|
|
16
|
+
# The parity test in `cli/src/lib/verify-parity.test.ts` compares the gate to CI
|
|
17
|
+
# too — but it runs only in `biffo-template`, the single repo with both a root
|
|
18
|
+
# `package.json` and a root `pyproject.toml`, i.e. the one place the root-only
|
|
19
|
+
# assumption held. It validated the gate where the gate was already correct.
|
|
20
|
+
# This runs everywhere, which is the whole point.
|
|
21
|
+
#
|
|
22
|
+
# Usage:
|
|
23
|
+
# sh scripts/gate-coverage.sh # this repo
|
|
24
|
+
# sh scripts/gate-coverage.sh --estate ~/code # every repo under a directory
|
|
25
|
+
#
|
|
26
|
+
# Exits non-zero if any repo's gate misses a check kind its CI runs.
|
|
27
|
+
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
|
|
30
|
+
ESTATE=""
|
|
31
|
+
[ "${1:-}" = "--estate" ] && ESTATE="${2:-}"
|
|
32
|
+
|
|
33
|
+
# Deliberately excluded from local gates, with the reason. Kept in step with
|
|
34
|
+
# verify-parity.test.ts. An exclusion here must describe what the CI step
|
|
35
|
+
# actually DOES, not what it was assumed to do — the bandit exclusion claimed
|
|
36
|
+
# "the finding gate is the upload step" when `bandit -ll` exits non-zero and it
|
|
37
|
+
# is the run step that fails (#855).
|
|
38
|
+
EXCLUDED_KINDS="pytest build audit gitleaks codeql e2e playwright release-subject ownership"
|
|
39
|
+
|
|
40
|
+
# Reduce a command to the KIND of check it is. Comparing raw command strings is
|
|
41
|
+
# hopeless across repos — CI uses `working-directory:` where the gate uses
|
|
42
|
+
# `--dir` — and what matters is "does a lint run locally where CI runs a lint".
|
|
43
|
+
kind_of() {
|
|
44
|
+
case "$1" in
|
|
45
|
+
*"run lint"*) echo lint ;;
|
|
46
|
+
*"run typecheck"*) echo typecheck ;;
|
|
47
|
+
*"run format:check"*) echo format ;;
|
|
48
|
+
*"run e2e"*) echo e2e ;;
|
|
49
|
+
*"run build"*|*"portal build"*) echo build ;;
|
|
50
|
+
*"run test"*) echo test ;;
|
|
51
|
+
*"ruff check"*) echo ruff-check ;;
|
|
52
|
+
*"ruff format"*) echo ruff-format ;;
|
|
53
|
+
*pyright*) echo pyright ;;
|
|
54
|
+
*pytest*) echo pytest ;;
|
|
55
|
+
*bandit*) echo bandit ;;
|
|
56
|
+
*"terraform fmt"*) echo terraform-fmt ;;
|
|
57
|
+
*playwright*) echo playwright ;;
|
|
58
|
+
*"dependency-audit"*|*"pnpm audit"*|*"pip-audit"*) echo audit ;;
|
|
59
|
+
*gitleaks*) echo gitleaks ;;
|
|
60
|
+
*"check release-subject"*) echo release-subject ;;
|
|
61
|
+
*"check ownership"*) echo ownership ;;
|
|
62
|
+
*"check plugin-terraform"*) echo plugin-terraform ;;
|
|
63
|
+
*"check plugin-collisions"*) echo plugin-collisions ;;
|
|
64
|
+
*install*|*"uv sync"*) echo "" ;;
|
|
65
|
+
*) echo "" ;;
|
|
66
|
+
esac
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
kinds_from_ci() {
|
|
70
|
+
d="$1"
|
|
71
|
+
f="$d/.github/workflows/ci.yml"
|
|
72
|
+
[ -f "$f" ] || return 0
|
|
73
|
+
grep -hoE "run: (pnpm|uv|terraform|sh scripts/)[^\"']*" "$f" 2>/dev/null |
|
|
74
|
+
sed 's/^run: //' | while read -r cmd; do kind_of "$cmd"; done | grep -v '^$' | sort -u
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
kinds_from_gate() {
|
|
78
|
+
d="$1"
|
|
79
|
+
[ -f "$d/scripts/verify.sh" ] || return 0
|
|
80
|
+
(cd "$d" && sh scripts/verify.sh --list 2>/dev/null) |
|
|
81
|
+
while read -r cmd; do kind_of "$cmd"; done | grep -v '^$' | sort -u
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
failed=0
|
|
85
|
+
report() {
|
|
86
|
+
d="$1"
|
|
87
|
+
label="$2"
|
|
88
|
+
if [ ! -f "$d/scripts/verify.sh" ]; then
|
|
89
|
+
printf '%-26s \033[33mNO GATE\033[0m\n' "$label"
|
|
90
|
+
return
|
|
91
|
+
fi
|
|
92
|
+
ci=$(kinds_from_ci "$d")
|
|
93
|
+
gate=$(kinds_from_gate "$d")
|
|
94
|
+
if [ -z "$ci" ]; then
|
|
95
|
+
printf '%-26s \033[90mno CI to mirror\033[0m (gate runs %s)\n' "$label" "$(echo "$gate" | grep -cv '^$')"
|
|
96
|
+
return
|
|
97
|
+
fi
|
|
98
|
+
missing=""
|
|
99
|
+
total=0
|
|
100
|
+
covered=0
|
|
101
|
+
for k in $ci; do
|
|
102
|
+
case " $EXCLUDED_KINDS " in *" $k "*) continue ;; esac
|
|
103
|
+
total=$((total + 1))
|
|
104
|
+
if echo "$gate" | grep -qx "$k"; then covered=$((covered + 1)); else missing="$missing $k"; fi
|
|
105
|
+
done
|
|
106
|
+
if [ -n "$missing" ]; then
|
|
107
|
+
failed=1
|
|
108
|
+
printf '%-26s \033[31m%s/%s\033[0m missing:%s\n' "$label" "$covered" "$total" "$missing"
|
|
109
|
+
else
|
|
110
|
+
printf '%-26s \033[32m%s/%s\033[0m\n' "$label" "$covered" "$total"
|
|
111
|
+
fi
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
printf '\ngate coverage - CI check kinds mirrored by the local gate\n\n'
|
|
115
|
+
if [ -n "$ESTATE" ]; then
|
|
116
|
+
for d in "$ESTATE"/*/; do
|
|
117
|
+
[ -e "$d/.git" ] || continue
|
|
118
|
+
report "${d%/}" "$(basename "${d%/}")"
|
|
119
|
+
done
|
|
120
|
+
else
|
|
121
|
+
root=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "not a git repo" >&2; exit 2; }
|
|
122
|
+
report "$root" "$(basename "$root")"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
printf '\n'
|
|
126
|
+
if [ "$failed" -ne 0 ]; then
|
|
127
|
+
printf '\033[31mSome gates do not cover their own CI.\033[0m A gate that misses the language a\n'
|
|
128
|
+
printf 'change is written in reports `verify passed` on work it never checked.\n\n'
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
printf '\033[32mEvery gate covers its own CI.\033[0m\n\n'
|
|
@@ -108,15 +108,45 @@ have_script() {
|
|
|
108
108
|
# it was written to enforce says exactly that inapplicable and absent must not
|
|
109
109
|
# look the same -- while reporting "not applicable" for the language the change
|
|
110
110
|
# was written in.
|
|
111
|
+
# Every directory holding a Python project this repo owns.
|
|
112
|
+
#
|
|
113
|
+
# ## Why this exists (#855)
|
|
114
|
+
#
|
|
115
|
+
# #853 fixed this for JavaScript and left Python with the identical bug. The
|
|
116
|
+
# check was `[ -f pyproject.toml ]` — root only. Every sibling keeps its API at
|
|
117
|
+
# `services/api/pyproject.toml`, so ruff, ruff-format and pyright were skipped
|
|
118
|
+
# entirely, and #853's own rationale applies verbatim: a change pushed green
|
|
119
|
+
# with zero verification of the language it was written in.
|
|
120
|
+
#
|
|
121
|
+
# Found by an agent whose 700-line TypeScript-and-Python change to tabsii-crm
|
|
122
|
+
# ran exactly one check — terraform-fmt — and printed `verify passed`.
|
|
123
|
+
py_dirs() {
|
|
124
|
+
if [ -f pyproject.toml ]; then
|
|
125
|
+
echo "."
|
|
126
|
+
return
|
|
127
|
+
fi
|
|
128
|
+
find . -name pyproject.toml \
|
|
129
|
+
-not -path "*/node_modules/*" -not -path "*/.venv/*" -not -path "*/dist/*" \
|
|
130
|
+
-not -path "*/.worktrees/*" -not -path "*/.terraform/*" -not -path "*/vendor/*" \
|
|
131
|
+
-not -path "*/site-packages/*" 2>/dev/null |
|
|
132
|
+
sed 's|/pyproject.toml$||' | sort
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
js_dirs() {
|
|
112
136
|
if [ -f package.json ]; then
|
|
113
137
|
echo "."
|
|
114
138
|
return
|
|
115
139
|
fi
|
|
140
|
+
# `.terraform/` is a DOWNLOAD CACHE of third-party modules, and the two runner
|
|
141
|
+
# repos carry eight vendored lambda packages in it -- each declaring lint and
|
|
142
|
+
# test scripts. Linting someone else's vendored code is slow, always red, and
|
|
143
|
+
# not this repo's business. It is gitignored, so a fresh worktree never has
|
|
144
|
+
# it and the omission was invisible until a primary checkout was audited.
|
|
116
145
|
find . -name package.json \
|
|
117
146
|
-not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/.next/*" \
|
|
118
147
|
-not -path "*/.turbo/*" -not -path "*/.worktrees/*" -not -path "*/out/*" \
|
|
119
|
-
-not -path "*/coverage/*" -not -path "*/.venv/*"
|
|
148
|
+
-not -path "*/coverage/*" -not -path "*/.venv/*" \
|
|
149
|
+
-not -path "*/.terraform/*" -not -path "*/vendor/*" 2>/dev/null |
|
|
120
150
|
sed 's|/package.json$||' | sort
|
|
121
151
|
}
|
|
122
152
|
|
|
@@ -149,21 +179,42 @@ skip() {
|
|
|
149
179
|
|
|
150
180
|
# Python first: ruff is near-instant, so the cheapest feedback on the largest
|
|
151
181
|
# single class of failure comes back immediately.
|
|
152
|
-
|
|
182
|
+
PY_DIRS=$(py_dirs)
|
|
183
|
+
if [ -n "$PY_DIRS" ]; then
|
|
153
184
|
if [ -n "$LIST" ] || command -v uv >/dev/null 2>&1; then
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
185
|
+
for d in $PY_DIRS; do
|
|
186
|
+
suffix=""
|
|
187
|
+
[ "$d" != "." ] && suffix="(${d#./})"
|
|
188
|
+
if [ "$d" = "." ]; then
|
|
189
|
+
run_check "ruff-check$suffix" uv run ruff check .
|
|
190
|
+
run_check "ruff-format$suffix" uv run ruff format --check .
|
|
191
|
+
run_check "pyright$suffix" uv run pyright
|
|
192
|
+
# bandit is NOT excluded: it exits non-zero on findings and it is the
|
|
193
|
+
# RUN step that fails in CI, not the artefact upload. See the exclusion
|
|
194
|
+
# audit in verify-parity.test.ts (#855).
|
|
195
|
+
[ -d services ] && run_check "bandit$suffix" uv run bandit -r services -ll -q
|
|
196
|
+
if [ -n "$PYTEST" ]; then
|
|
197
|
+
run_check "pytest$suffix" uv run pytest -q
|
|
198
|
+
else
|
|
199
|
+
skip "pytest$suffix" "excluded - set BIFFO_VERIFY_PYTEST=1 where the suite is fast"
|
|
200
|
+
fi
|
|
201
|
+
else
|
|
202
|
+
run_check "ruff-check$suffix" uv run --directory "$d" ruff check .
|
|
203
|
+
run_check "ruff-format$suffix" uv run --directory "$d" ruff format --check .
|
|
204
|
+
run_check "pyright$suffix" uv run --directory "$d" pyright
|
|
205
|
+
run_check "bandit$suffix" uv run --directory "$d" bandit -r src -ll -q
|
|
206
|
+
if [ -n "$PYTEST" ]; then
|
|
207
|
+
run_check "pytest$suffix" uv run --directory "$d" pytest -q
|
|
208
|
+
else
|
|
209
|
+
skip "pytest$suffix" "excluded - set BIFFO_VERIFY_PYTEST=1 where the suite is fast"
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
done
|
|
162
213
|
else
|
|
163
214
|
skip python "uv not installed"
|
|
164
215
|
fi
|
|
165
216
|
else
|
|
166
|
-
skip python "no pyproject.toml in this repo"
|
|
217
|
+
skip python "no pyproject.toml anywhere in this repo"
|
|
167
218
|
fi
|
|
168
219
|
|
|
169
220
|
# Terraform, wherever this repo keeps it: modules/ in the template and
|
|
@@ -234,6 +285,17 @@ if [ -n "$FAILED" ]; then
|
|
|
234
285
|
printf 'Most format failures are one command: pnpm run format\n\n'
|
|
235
286
|
exit 1
|
|
236
287
|
fi
|
|
288
|
+
if [ -z "$PASSED" ]; then
|
|
289
|
+
# "Nothing applicable ran" is a different outcome from "checks passed", and
|
|
290
|
+
# conflating them is the exact failure this gate exists to remove -- the
|
|
291
|
+
# standard's own principle, applied to the gate itself. tabsii-crm ran ONE
|
|
292
|
+
# check on a 700-line change and printed a pass (#855).
|
|
293
|
+
printf '\033[31mverify ran NOTHING - this is not a pass\033[0m\n'
|
|
294
|
+
printf 'No check in this repo was applicable. Either its toolchain is somewhere\n'
|
|
295
|
+
printf 'verify.sh does not look, or this repo should not carry a gate at all.\n'
|
|
296
|
+
printf 'See docs/practices/standards/local-gates.md\n\n'
|
|
297
|
+
exit 1
|
|
298
|
+
fi
|
|
237
299
|
printf '\033[32mverify passed\033[0m -%s\n' "$PASSED"
|
|
238
300
|
[ -n "$SKIPPED" ] && printf '\033[90mnot applicable here:%s\033[0m\n' "$SKIPPED"
|
|
239
301
|
printf '\n'
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Does each repo's local gate actually cover that repo's CI?
|
|
4
|
+
#
|
|
5
|
+
# ## Why this exists
|
|
6
|
+
#
|
|
7
|
+
# `hook-audit.sh` answers "will a hook execute here". That question hit 100%
|
|
8
|
+
# across the estate on 2026-07-29 while eight repos had a gate that checked
|
|
9
|
+
# **nothing they were written in** — `tabsii-crm` ran one check, terraform-fmt,
|
|
10
|
+
# on a 700-line TypeScript-and-Python change, and printed `verify passed`.
|
|
11
|
+
#
|
|
12
|
+
# Arming was a proxy and it was reported as the outcome. This is the metric that
|
|
13
|
+
# can actually fail: for every repo, the set of check *kinds* its CI runs, minus
|
|
14
|
+
# the ones deliberately excluded, must be covered by the set its gate runs.
|
|
15
|
+
#
|
|
16
|
+
# The parity test in `cli/src/lib/verify-parity.test.ts` compares the gate to CI
|
|
17
|
+
# too — but it runs only in `biffo-template`, the single repo with both a root
|
|
18
|
+
# `package.json` and a root `pyproject.toml`, i.e. the one place the root-only
|
|
19
|
+
# assumption held. It validated the gate where the gate was already correct.
|
|
20
|
+
# This runs everywhere, which is the whole point.
|
|
21
|
+
#
|
|
22
|
+
# Usage:
|
|
23
|
+
# sh scripts/gate-coverage.sh # this repo
|
|
24
|
+
# sh scripts/gate-coverage.sh --estate ~/code # every repo under a directory
|
|
25
|
+
#
|
|
26
|
+
# Exits non-zero if any repo's gate misses a check kind its CI runs.
|
|
27
|
+
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
|
|
30
|
+
ESTATE=""
|
|
31
|
+
[ "${1:-}" = "--estate" ] && ESTATE="${2:-}"
|
|
32
|
+
|
|
33
|
+
# Deliberately excluded from local gates, with the reason. Kept in step with
|
|
34
|
+
# verify-parity.test.ts. An exclusion here must describe what the CI step
|
|
35
|
+
# actually DOES, not what it was assumed to do — the bandit exclusion claimed
|
|
36
|
+
# "the finding gate is the upload step" when `bandit -ll` exits non-zero and it
|
|
37
|
+
# is the run step that fails (#855).
|
|
38
|
+
EXCLUDED_KINDS="pytest build audit gitleaks codeql e2e playwright release-subject ownership"
|
|
39
|
+
|
|
40
|
+
# Reduce a command to the KIND of check it is. Comparing raw command strings is
|
|
41
|
+
# hopeless across repos — CI uses `working-directory:` where the gate uses
|
|
42
|
+
# `--dir` — and what matters is "does a lint run locally where CI runs a lint".
|
|
43
|
+
kind_of() {
|
|
44
|
+
case "$1" in
|
|
45
|
+
*"run lint"*) echo lint ;;
|
|
46
|
+
*"run typecheck"*) echo typecheck ;;
|
|
47
|
+
*"run format:check"*) echo format ;;
|
|
48
|
+
*"run e2e"*) echo e2e ;;
|
|
49
|
+
*"run build"*|*"portal build"*) echo build ;;
|
|
50
|
+
*"run test"*) echo test ;;
|
|
51
|
+
*"ruff check"*) echo ruff-check ;;
|
|
52
|
+
*"ruff format"*) echo ruff-format ;;
|
|
53
|
+
*pyright*) echo pyright ;;
|
|
54
|
+
*pytest*) echo pytest ;;
|
|
55
|
+
*bandit*) echo bandit ;;
|
|
56
|
+
*"terraform fmt"*) echo terraform-fmt ;;
|
|
57
|
+
*playwright*) echo playwright ;;
|
|
58
|
+
*"dependency-audit"*|*"pnpm audit"*|*"pip-audit"*) echo audit ;;
|
|
59
|
+
*gitleaks*) echo gitleaks ;;
|
|
60
|
+
*"check release-subject"*) echo release-subject ;;
|
|
61
|
+
*"check ownership"*) echo ownership ;;
|
|
62
|
+
*"check plugin-terraform"*) echo plugin-terraform ;;
|
|
63
|
+
*"check plugin-collisions"*) echo plugin-collisions ;;
|
|
64
|
+
*install*|*"uv sync"*) echo "" ;;
|
|
65
|
+
*) echo "" ;;
|
|
66
|
+
esac
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
kinds_from_ci() {
|
|
70
|
+
d="$1"
|
|
71
|
+
f="$d/.github/workflows/ci.yml"
|
|
72
|
+
[ -f "$f" ] || return 0
|
|
73
|
+
grep -hoE "run: (pnpm|uv|terraform|sh scripts/)[^\"']*" "$f" 2>/dev/null |
|
|
74
|
+
sed 's/^run: //' | while read -r cmd; do kind_of "$cmd"; done | grep -v '^$' | sort -u
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
kinds_from_gate() {
|
|
78
|
+
d="$1"
|
|
79
|
+
[ -f "$d/scripts/verify.sh" ] || return 0
|
|
80
|
+
(cd "$d" && sh scripts/verify.sh --list 2>/dev/null) |
|
|
81
|
+
while read -r cmd; do kind_of "$cmd"; done | grep -v '^$' | sort -u
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
failed=0
|
|
85
|
+
report() {
|
|
86
|
+
d="$1"
|
|
87
|
+
label="$2"
|
|
88
|
+
if [ ! -f "$d/scripts/verify.sh" ]; then
|
|
89
|
+
printf '%-26s \033[33mNO GATE\033[0m\n' "$label"
|
|
90
|
+
return
|
|
91
|
+
fi
|
|
92
|
+
ci=$(kinds_from_ci "$d")
|
|
93
|
+
gate=$(kinds_from_gate "$d")
|
|
94
|
+
if [ -z "$ci" ]; then
|
|
95
|
+
printf '%-26s \033[90mno CI to mirror\033[0m (gate runs %s)\n' "$label" "$(echo "$gate" | grep -cv '^$')"
|
|
96
|
+
return
|
|
97
|
+
fi
|
|
98
|
+
missing=""
|
|
99
|
+
total=0
|
|
100
|
+
covered=0
|
|
101
|
+
for k in $ci; do
|
|
102
|
+
case " $EXCLUDED_KINDS " in *" $k "*) continue ;; esac
|
|
103
|
+
total=$((total + 1))
|
|
104
|
+
if echo "$gate" | grep -qx "$k"; then covered=$((covered + 1)); else missing="$missing $k"; fi
|
|
105
|
+
done
|
|
106
|
+
if [ -n "$missing" ]; then
|
|
107
|
+
failed=1
|
|
108
|
+
printf '%-26s \033[31m%s/%s\033[0m missing:%s\n' "$label" "$covered" "$total" "$missing"
|
|
109
|
+
else
|
|
110
|
+
printf '%-26s \033[32m%s/%s\033[0m\n' "$label" "$covered" "$total"
|
|
111
|
+
fi
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
printf '\ngate coverage - CI check kinds mirrored by the local gate\n\n'
|
|
115
|
+
if [ -n "$ESTATE" ]; then
|
|
116
|
+
for d in "$ESTATE"/*/; do
|
|
117
|
+
[ -e "$d/.git" ] || continue
|
|
118
|
+
report "${d%/}" "$(basename "${d%/}")"
|
|
119
|
+
done
|
|
120
|
+
else
|
|
121
|
+
root=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "not a git repo" >&2; exit 2; }
|
|
122
|
+
report "$root" "$(basename "$root")"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
printf '\n'
|
|
126
|
+
if [ "$failed" -ne 0 ]; then
|
|
127
|
+
printf '\033[31mSome gates do not cover their own CI.\033[0m A gate that misses the language a\n'
|
|
128
|
+
printf 'change is written in reports `verify passed` on work it never checked.\n\n'
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
printf '\033[32mEvery gate covers its own CI.\033[0m\n\n'
|
|
@@ -108,15 +108,45 @@ have_script() {
|
|
|
108
108
|
# it was written to enforce says exactly that inapplicable and absent must not
|
|
109
109
|
# look the same -- while reporting "not applicable" for the language the change
|
|
110
110
|
# was written in.
|
|
111
|
+
# Every directory holding a Python project this repo owns.
|
|
112
|
+
#
|
|
113
|
+
# ## Why this exists (#855)
|
|
114
|
+
#
|
|
115
|
+
# #853 fixed this for JavaScript and left Python with the identical bug. The
|
|
116
|
+
# check was `[ -f pyproject.toml ]` — root only. Every sibling keeps its API at
|
|
117
|
+
# `services/api/pyproject.toml`, so ruff, ruff-format and pyright were skipped
|
|
118
|
+
# entirely, and #853's own rationale applies verbatim: a change pushed green
|
|
119
|
+
# with zero verification of the language it was written in.
|
|
120
|
+
#
|
|
121
|
+
# Found by an agent whose 700-line TypeScript-and-Python change to tabsii-crm
|
|
122
|
+
# ran exactly one check — terraform-fmt — and printed `verify passed`.
|
|
123
|
+
py_dirs() {
|
|
124
|
+
if [ -f pyproject.toml ]; then
|
|
125
|
+
echo "."
|
|
126
|
+
return
|
|
127
|
+
fi
|
|
128
|
+
find . -name pyproject.toml \
|
|
129
|
+
-not -path "*/node_modules/*" -not -path "*/.venv/*" -not -path "*/dist/*" \
|
|
130
|
+
-not -path "*/.worktrees/*" -not -path "*/.terraform/*" -not -path "*/vendor/*" \
|
|
131
|
+
-not -path "*/site-packages/*" 2>/dev/null |
|
|
132
|
+
sed 's|/pyproject.toml$||' | sort
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
js_dirs() {
|
|
112
136
|
if [ -f package.json ]; then
|
|
113
137
|
echo "."
|
|
114
138
|
return
|
|
115
139
|
fi
|
|
140
|
+
# `.terraform/` is a DOWNLOAD CACHE of third-party modules, and the two runner
|
|
141
|
+
# repos carry eight vendored lambda packages in it -- each declaring lint and
|
|
142
|
+
# test scripts. Linting someone else's vendored code is slow, always red, and
|
|
143
|
+
# not this repo's business. It is gitignored, so a fresh worktree never has
|
|
144
|
+
# it and the omission was invisible until a primary checkout was audited.
|
|
116
145
|
find . -name package.json \
|
|
117
146
|
-not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/.next/*" \
|
|
118
147
|
-not -path "*/.turbo/*" -not -path "*/.worktrees/*" -not -path "*/out/*" \
|
|
119
|
-
-not -path "*/coverage/*" -not -path "*/.venv/*"
|
|
148
|
+
-not -path "*/coverage/*" -not -path "*/.venv/*" \
|
|
149
|
+
-not -path "*/.terraform/*" -not -path "*/vendor/*" 2>/dev/null |
|
|
120
150
|
sed 's|/package.json$||' | sort
|
|
121
151
|
}
|
|
122
152
|
|
|
@@ -149,21 +179,42 @@ skip() {
|
|
|
149
179
|
|
|
150
180
|
# Python first: ruff is near-instant, so the cheapest feedback on the largest
|
|
151
181
|
# single class of failure comes back immediately.
|
|
152
|
-
|
|
182
|
+
PY_DIRS=$(py_dirs)
|
|
183
|
+
if [ -n "$PY_DIRS" ]; then
|
|
153
184
|
if [ -n "$LIST" ] || command -v uv >/dev/null 2>&1; then
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
185
|
+
for d in $PY_DIRS; do
|
|
186
|
+
suffix=""
|
|
187
|
+
[ "$d" != "." ] && suffix="(${d#./})"
|
|
188
|
+
if [ "$d" = "." ]; then
|
|
189
|
+
run_check "ruff-check$suffix" uv run ruff check .
|
|
190
|
+
run_check "ruff-format$suffix" uv run ruff format --check .
|
|
191
|
+
run_check "pyright$suffix" uv run pyright
|
|
192
|
+
# bandit is NOT excluded: it exits non-zero on findings and it is the
|
|
193
|
+
# RUN step that fails in CI, not the artefact upload. See the exclusion
|
|
194
|
+
# audit in verify-parity.test.ts (#855).
|
|
195
|
+
[ -d services ] && run_check "bandit$suffix" uv run bandit -r services -ll -q
|
|
196
|
+
if [ -n "$PYTEST" ]; then
|
|
197
|
+
run_check "pytest$suffix" uv run pytest -q
|
|
198
|
+
else
|
|
199
|
+
skip "pytest$suffix" "excluded - set BIFFO_VERIFY_PYTEST=1 where the suite is fast"
|
|
200
|
+
fi
|
|
201
|
+
else
|
|
202
|
+
run_check "ruff-check$suffix" uv run --directory "$d" ruff check .
|
|
203
|
+
run_check "ruff-format$suffix" uv run --directory "$d" ruff format --check .
|
|
204
|
+
run_check "pyright$suffix" uv run --directory "$d" pyright
|
|
205
|
+
run_check "bandit$suffix" uv run --directory "$d" bandit -r src -ll -q
|
|
206
|
+
if [ -n "$PYTEST" ]; then
|
|
207
|
+
run_check "pytest$suffix" uv run --directory "$d" pytest -q
|
|
208
|
+
else
|
|
209
|
+
skip "pytest$suffix" "excluded - set BIFFO_VERIFY_PYTEST=1 where the suite is fast"
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
done
|
|
162
213
|
else
|
|
163
214
|
skip python "uv not installed"
|
|
164
215
|
fi
|
|
165
216
|
else
|
|
166
|
-
skip python "no pyproject.toml in this repo"
|
|
217
|
+
skip python "no pyproject.toml anywhere in this repo"
|
|
167
218
|
fi
|
|
168
219
|
|
|
169
220
|
# Terraform, wherever this repo keeps it: modules/ in the template and
|
|
@@ -234,6 +285,17 @@ if [ -n "$FAILED" ]; then
|
|
|
234
285
|
printf 'Most format failures are one command: pnpm run format\n\n'
|
|
235
286
|
exit 1
|
|
236
287
|
fi
|
|
288
|
+
if [ -z "$PASSED" ]; then
|
|
289
|
+
# "Nothing applicable ran" is a different outcome from "checks passed", and
|
|
290
|
+
# conflating them is the exact failure this gate exists to remove -- the
|
|
291
|
+
# standard's own principle, applied to the gate itself. tabsii-crm ran ONE
|
|
292
|
+
# check on a 700-line change and printed a pass (#855).
|
|
293
|
+
printf '\033[31mverify ran NOTHING - this is not a pass\033[0m\n'
|
|
294
|
+
printf 'No check in this repo was applicable. Either its toolchain is somewhere\n'
|
|
295
|
+
printf 'verify.sh does not look, or this repo should not carry a gate at all.\n'
|
|
296
|
+
printf 'See docs/practices/standards/local-gates.md\n\n'
|
|
297
|
+
exit 1
|
|
298
|
+
fi
|
|
237
299
|
printf '\033[32mverify passed\033[0m -%s\n' "$PASSED"
|
|
238
300
|
[ -n "$SKIPPED" ] && printf '\033[90mnot applicable here:%s\033[0m\n' "$SKIPPED"
|
|
239
301
|
printf '\n'
|