Repository navigation
Expand file tree
/
Copy pathBUILD.bazel
More file actions
466 lines (451 loc) · 28.7 KB
/
Copy pathBUILD.bazel
File metadata and controls
466 lines (451 loc) · 28.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2026 Mike Mol
# ⚑ THE LOCK IS EXPORTED, NOT A TARGET. `pip.parse` in MODULE.bazel reads both locks by LABEL at
# module-extension evaluation time, which is before any target in this package is built — so what
# is needed is visibility of the files, not a rule producing them.
load("@hooks_dev//:requirements.bzl", dev_all_requirements = "all_requirements", dev_requirement = "requirement")
load("@mikemol_check_mutants//:defs.bzl", "mutants_sharded")
load("@mikemol_rules_py//:dist.bzl", "dist_checks")
load("@mikemol_rules_py//:venv.bzl", "venv_from_hub")
load("@hooks_deps//:requirements.bzl", "requirement")
load("@rules_python//python:defs.bzl", "py_library", "py_test")
exports_files([
"requirements.txt",
"requirements-dev.txt",
# ⚑ THE DEV HUB IS FED FROM uv.lock (W10), so the host venv and the bazel venv resolve one
# pyproject to one closure; `requirements-dev.txt` stays exported for the reader that
# compares them and for the shipping-hub split MODULE.bazel argues.
"uv.lock",
])
# ⚑⚑ `imports = ["src"]` IS THE PEP 420 LAYOUT MADE EXPLICIT TO BAZEL. The runfiles tree resolves
# `mikemol.hooks` by LAYOUT rather than by an installed `.pth`, which is the mechanism that
# retires the `mypy_path`/`explicit_package_bases` pair (⟡mtools-drop-mypypath): that pair exists
# because mypy reads the filesystem and an editable install's import hook is invisible to it.
py_library(
name = "hooks",
srcs = glob(["src/mikemol/hooks/**/*.py"]),
data = glob(["src/mikemol/hooks/py.typed"], allow_empty = True),
imports = ["src"],
deps = [
requirement("ruff"),
requirement("mypy"),
],
visibility = ["//visibility:public"],
)
# ⚑ THE TEST MODULES THAT SPAWN NESTED PYTEST PROCESSES, sized `medium` below (W329). Each is
# listed with its measurement: test_grade, 21 cases in 5.3 s alone, and a 60 s TIMEOUT after 7
# cases under a full //... run, twice on 2026-10-01.
_SPAWNS_NESTED_PYTEST = ["tests/test_grade.py"]
# ⚑⚑⚑ ONE TARGET PER TEST MODULE, AND EACH IS A WITNESS. The thesis in MODULE.bazel is that a
# pytest case and a paperkit witness are the same object; this is where that stops being prose. A
# failing module names itself rather than collapsing into one red suite target.
[
py_test(
name = src.removeprefix("tests/").removesuffix(".py"),
srcs = [src, "@mikemol_rules_py//:pytest_main.py"] + glob(["tests/conftest.py"], allow_empty = True),
main = "@mikemol_rules_py//:pytest_main.py",
args = [
"$(location " + src + ")",
"-q",
"--strict-markers",
"--strict-config",
"-c",
"$(location pyproject.toml)",
],
# ⚑⚑ ruff IS STAGED AND NAMED, because `test_bar_fires` runs the checker to prove each
# rule fires. Reading `.venv/bin/ruff` made those arms pass only where a developer venv
# existed — measured from a fresh clone, that target was the one failure in 25.
data = [
"pyproject.toml",
"requirements.txt",
# ⚑⚑⚑ AND THE RATCHET BASELINE, WHICH IS THE SAME LESSON A THIRD TIME IN THIS LIST.
# An arm asserting that every baseline key names a rule the checker still recognises
# read `ratchet-preview.txt` and found nothing in the sandbox: it is a data dependency
# of the `ratchet` target, not of this one. Its positive control refused to pass
# vacuously — a missing baseline reported as "no keys" would have read as "no bad
# keys" — so the arm failed honestly rather than going quiet.
# ⚑⚑ THE COMMENT BELOW ALREADY SAYS A RECORDED LESSON IS NOT AN APPLIED ONE, about
# `_GATE`, in this same data list. This is the next arm reading a path nothing staged.
"ratchet-preview.txt",
# ⚑⚑⚑ THE RATCHET CLI, AND THIS IS THE FIRST ENTRY IN THIS LIST ADDED *BEFORE* THE
# HERMETIC RUN CAUGHT IT RATHER THAN AFTER. `test_a_key_absent_from_the_baseline_is_
# refused_when_its_finding_returns` stopped describing the refusal and started RUNNING
# it — planting a finding in a copy of this distribution and requiring the ratchet to
# exit nonzero and name it — which makes the ratchet a real input to this target.
# ⚑⚑ THE FIRST DRAFT OF THAT ARM REACHED FOR `ratchet/.venv/bin/mikemol-ratchet`, a
# HOST path present on one workstation and in no sandbox — ⟐UNDECLARED-HOST-INPUTS
# written fresh into a new arm while its symbol sits in the poll. Caught by reading
# this list's own four warnings, which is the first time they were consulted while
# writing rather than quoted afterwards.
# ⚑ `//ratchet:ratchet_cli` WAS ALREADY IN THE GRAPH for `//hooks:ratchet` below; it
# needed naming here, not building. An instrument staged for a sibling target is not
# an input to this one.
"//ratchet:ratchet_cli",
# ⚑⚑⚑ THE LAUNCHERS, AND THIS IS THE SAME LESSON A FOURTH TIME IN THIS LIST. An arm
# asserting that `hooks/bin/mikemol-hook-*` carries both a refusal AND a bootstrap
# exemption passed locally and failed hermetically with `FileNotFoundError` — the
# sandbox stages only declared inputs, and a launcher tracked in the repository is
# still not an input to this target until it is named here.
# ⚑⚑ THE LIST ITSELF SAYS "A RECORDED LESSON IS NOT AN APPLIED ONE" TWICE ALREADY,
# about `ratchet-preview.txt` and about `_GATE`. Writing a new arm that reads a new
# path did not prompt me to consult either. The instrument that caught it was the
# hermetic run, not the reasoning sitting in the file being edited.
"bin/mikemol-hook-structural-query",
"bin/mikemol-hook-no-chaining",
"bin/mikemol-hook-no-verify",
# ⚑ THE FIFTH TIME, AND THIS ONE PASSED PLAIN PYTEST: `test_bin_launchers_argv` (W635)
# EXECUTES every launcher that forwards its arguments, and the four not named above
# failed hermetically with `FileNotFoundError` (12 of its 21 cases) while the same file
# passed from the working tree. The instrument that caught it was the commit gate.
"bin/mikemol-hook-inbound-asks",
"bin/mikemol-hook-nemik-check",
"bin/mikemol-hook-pycheck",
"bin/mikemol-hook-shellcheck",
# W824: the compound kata, READ and EXECUTED by test_hooks_preflight.
"bin/mikemol-hooks-preflight",
# W796: the stable launcher for `mikemol-commit`, EXECUTED by test_commit_launcher.
"bin/mikemol-commit",
# ⚑ W238: the standing launcher and its policy, EXECUTED by test_standing_facts with
# the pinned opa (OPA_BIN below), so a gathered fact is proven to reach the policy.
"bin/mikemol-hook-standing",
"policy/standing.rego",
# W513: the rule-8 Stop launcher, EXECUTED by test_standing_stop_launcher, and the
# transcript reader it runs (as a subprocess on PYTHONPATH, never imported here).
"bin/mikemol-hook-standing-stop",
"//transcriptstruct",
"@opa//file",
# ⚑ THE CONSUMER LAUNCHER, EXECUTED rather than read by `test_adopt_launcher.py` —
# the same lesson as the three above: tracked is not staged until named here.
"adopt/tools-hook",
# ⚑⚑⚑ THE GATE ITSELF IS DATA, AND ITS ABSENCE MADE 56 ARMS FAIL HERMETICALLY WHILE
# THE PREFLIGHT READ THEM ALL GREEN. `_GATE` is read by 22 call sites here; in the
# sandbox the path did not exist, so every one raised FileNotFoundError. The preflight
# runs from the working tree where `.githooks/pre-commit` is simply there.
# ⚑⚑ THE LESSON WAS ALREADY IN THIS FILE. The comment below records that a
# `.githooks/` read "taught this here two ticks ago, where a test passed locally and
# failed hermetically" — and the fix that lesson describes was never applied to this
# target. A RECORDED LESSON IS NOT AN APPLIED ONE: the reasoning existed, in the same
# data list, and nothing consulted it when the next arm reading that path was written.
# ⚑ NAMED BY LABEL, not by glob. The root package exports it (`//:` at BUILD.bazel:62)
# so the dependency is declared where a reader looking for it will find it, and a glob
# would stage whatever else lands in `.githooks/` without anyone deciding to.
"//:.githooks/pre-commit",
# ⚑⚑ AND THE OTHER EIGHT, DERIVED FROM THE FAILURES RATHER THAN GUESSED AT. Staging
# the gate took 56 hermetic failures to 39; the remainder named nine distinct paths,
# and this list is that enumeration. A hand-written guess at "what a test module
# probably reads" is the reified population this repository has measured nine times.
# ⚑ EVERY ONE IS A SUBJECT THIS SUITE ASSERTS ABOUT — the poll, the preflight that
# predicts the gate, the refusal recorder, both commit hooks, the shell gate, the
# warrant ledger, the census brief, and the settings that arm the hooks. They are
# data because the arms read them, not because they happen to be nearby.
# ⚑⚑⚑ THE ROOT SCRIPTS, GLOBBED FROM THE PACKAGE THAT OWNS THEM. This was NINE
# transcribed labels and the tree holds sixteen scripts, so seven were unreachable.
# The arm `test_no_gate_asserts_a_figure_it_cannot_reach` globs `*.sh` and counted
# eleven (nine plus the two hooks), which is how the gap was measured, not noticed.
# ⚑ THE FIGURE IN AN EARLIER DRAFT OF THIS COMMENT WAS TWELVE, taken from `grep -o`,
# which counts MATCHES — three more labels exist in this file as `srcs` of separate
# targets and are correctly individual. Reading the lines gives nine. A count
# characterised from an instrument's output rather than its subject, written into the
# comment repairing exactly that defect.
# ⚑⚑ AN ARM WHOSE POPULATION IS A GLOB, FED BY A DATA LIST THAT IS A TRANSCRIPTION,
# IS GREEN OVER WHATEVER THE LIST HAPPENED TO NAME. The arm was right and its
# environment was short — the same shape as every mis-named population this suite
# exists to refuse, with the truncation living in the build file rather than the test.
# ⚑ `//:all_root_scripts` is a filegroup so the population is declared ONCE, in the
# package that owns those files, and every consumer inherits additions for free.
"//:all_root_scripts",
# ⚑ THE PAIRING CHECK IS STAGED BECAUSE A WITNESS RUNS IT — the gate's own call, over
# this distribution's staged ledger and suite, with one orphan planted in a copy.
"//:count_test_functions.py",
# ⚑⚑ THE LIVE ROUTING TABLE, THE SAME LESSON A FIFTH TIME IN THIS LIST. An arm
# asserting the parts of this repo's own claim set read `{}` hermetically: the table
# was not an input, and its non-empty control refused to pass vacuously (2026-09-22).
"//:.claude/skills/struct-tools/SKILL.md",
"//:.githooks/commit-msg",
# W661, W662: `.githooks/post-commit` EXECUTED by test_post_commit_hook in a decoy repo.
"//:.githooks/post-commit",
# W588: `.githooks/pre-push` EXECUTED by test_githook_pre_push against a stand-in launcher.
"//:.githooks/pre-push",
"warrants.bib",
"//:.claude/settings.json",
"//:findings/CENSUS-BRIEF.md",
# ⚑ THE BUILD FILES ARE DATA BECAUSE TWO WITNESSES READ THEM — the one asserting
# every py_test names the pytest entry point, and the one asserting the gate's own
# shell is checked. They passed before only by resolving OUT of the sandbox into the
# live tree; with that escape closed they must be staged, which is the honest form.
# ⚑ `.bazelrc` IS DATA because a witness asserts the hermetic-sandbox flags are
# unconditional. It reads the file rather than trusting the flags to be in effect,
# since they are NOT in the action key — an action cannot observe its own sandbox
# regime, so the config text is the only evidence available to it.
# ⚑⚑ THE CITATION GATE IS STAGED BECAUSE A WITNESS RUNS IT. It is a repo-root script
# and the sandbox holds only declared inputs — the same lesson a `.githooks/` read
# taught here two ticks ago, where a test passed locally and failed hermetically. Its
# CORPUS IS NOT STAGED, DELIBERATELY: the gate takes it as an argument, so the
# witness supplies a fixture it controls. Staging the real rules document would make
# every arm depend on a file that changes every tick — an arm asserting "Rule 99 is
# absent" would break the day someone writes Rule 99.
# ⚑ THE ORPHAN GATE IS STAGED AND ITS ARMS ARE HERMETIC, unlike the citation gate's.
# It is pure shell over a filesystem the witness builds, with no reader to route
# through — so there is no venv to reach for and no reason to skip.
# ⚑ THE FRESHNESS GATE IS STAGED AS A SOURCE THE WITNESS REWRITES, not run in place:
# its arms substitute a fixture corpus for the real one, so the sandbox never needs
# the 1,500-line rules document. `kubectl` and `curl` are absent there, which is
# exactly the UNVERIFIABLE path the gate declares — the arms exercise the corpus
# branch, which is the half that does not need a cluster.
# ⚑ THE WITNESS IS STAGED FOR ITS PRE-BAZEL ARMS ONLY. Its later arms invoke bazel and
# cannot run here; what the sandbox exercises is argument validation and the probe-kind
# guard, both of which refuse before the first side effect and need only the script.
"//:.bazelrc",
# ⚑⚑⚑ AND THIS LIST WENT STALE EXACTLY AS THE COMMENT BELOW PREDICTS OF LISTS.
# `fence` landed at cc3d301 and was named here nowhere, so the arm asserting every
# BUILD file wires `pytest_main.py` iterated a population the SANDBOX had truncated
# to three — passing over a set that omitted the newest distribution, which is the
# `confidently over a population its ENVIRONMENT truncated` defect recorded below,
# reproduced in the data list that records it.
# ⚑⚑ THE `pyproject.toml` FILES ARE STAGED BECAUSE THE POPULATION IS NOW DERIVED FROM
# THEM. `_distributions()` globs `*/pyproject.toml`, matching `blockers.sh`'s
# structural criterion — so in the sandbox that glob sees only what is declared here,
# and an undeclared distribution is invisible rather than merely unchecked.
# ⚑ MEASURED: with only hooks' own pyproject staged, the derivation returned
# `['hooks']` and the arm's POSITIVE CONTROL refused. Had that control been `>= 1`,
# or absent, the arm would have passed green over a one-distribution view of a
# four-distribution repository.
# ⚑ STILL A LIST, NOT A GLOB, because bazel labels cannot glob across packages — the
# residue is stated rather than hidden, and the arm's control is what catches it.
"//:BUILD.bazel",
"//fence:BUILD.bazel",
"//fence:pyproject.toml",
"//hooks:BUILD.bazel",
"//mdstruct:BUILD.bazel",
"//mdstruct:pyproject.toml",
"//ratchet:BUILD.bazel",
"//ratchet:pyproject.toml",
"@mikemol_check_ruff//:bin",
# ⚑⚑⚑ THE SIBLING TEST MODULES, because three arms sweep the whole SUITE rather
# than this file: the warrant ledger's 1:1 against every test NAME, the
# module-consumer check, and the harness-wide figure arm. In the sandbox they saw
# only this module and reported 130 warrant selectors as naming no test.
# ⚑⚑ THE ARMS WERE RIGHT AND THE POPULATION WAS SHORT — a checker reporting
# confidently over a population its ENVIRONMENT truncated. The preflight could not
# see it because the working tree holds every file; only the sandbox is short.
# ⚑ GLOBBED, because the population is *every test module in this distribution*
# and a list would go stale the next time one is added — the reified population
# this suite exists to refuse.
] + glob(["tests/test_*.py"]),
env = {
"RUFF_BIN": "$(location @mikemol_check_ruff//:bin)",
"OPA_BIN": "$(location @opa//file)",
},
# ⚑ SMALL IS A DECLARATION, NOT A GUESS: bazel's default of `medium` reserves a timeout
# budget nothing uses, and an honest size is what lets a genuinely slow witness stand
# out instead of blending in.
# ⚑⚑ AND IT HAD STOPPED BEING TRUE FOR ONE MODULE (W329). The note here said "every module
# runs in under two seconds". test_grade runs 21 cases in 5.3 s ALONE, because each
# sandbox case grades arms by spawning nested pytest processes. In a full `//...` run
# (W326's gate, 2026-10-01) the mutation grids saturate all 24 cores, and it timed out at
# small's 60 s after 7 cases, twice. Not a hang: slower startup for every nested process.
# A module that spawns nested pytest gets `medium` (300 s). The operator's rule: no
# wall-clock-sensitive tests.
size = "medium" if src in _SPAWNS_NESTED_PYTEST else "small",
deps = [
":hooks",
dev_requirement("pytest"),
# ⚑ ruff IS A DEP SO `test_bar_fires` CAN RUN THE CHECKER IT ASSERTS ABOUT. It ships
# as a Python package with a bundled binary, so declaring it here puts that binary in
# the runfiles tree; the test resolves it rather than reading `.venv/bin/ruff`, which
# existed only on a developer machine.
],
imports = ["src"],
)
for src in glob(["tests/test_*.py"])
]
# W238: the standing policy's own witnesses and controls (policy/standing_test.rego), run by the
# pinned opa. Before this target they ran only when someone typed `opa test` by hand.
sh_test(
name = "policy_test",
srcs = ["policy_test.sh"],
args = [
"$(location @opa//file)",
"$(location policy/standing.rego)",
],
data = ["@opa//file"] + glob(["policy/*.rego"]),
size = "small",
)
# ⚑⚑ ruff IN THE GRAPH. Outside it, ruff had no key at all — full re-execution every commit,
# nothing reused. Its domain is the sources, the config and the binary, all declared below, and
# all three are hand-writable because ruff does not follow imports (measured: breaking a type in
# an imported module changes mypy's verdict on the importer and leaves ruff's unchanged).
sh_test(
name = "ruff",
srcs = ["@mikemol_check_ruff//:ruff_check.sh"],
args = [
"$(location @mikemol_check_ruff//:bin)",
"$(location :pyproject.toml)",
] + ["$(location " + f + ")" for f in glob(["src/**/*.py", "tests/**/*.py"])],
data = [
"pyproject.toml",
"@mikemol_check_ruff//:bin",
] + glob(["src/**/*.py", "tests/**/*.py"]),
size = "small",
)
# ⚑⚑ THE RATCHET IN THE GRAPH, WITH ITS BASELINE DECLARED. Outside it, the ratchet had no key.
# Its domain is this distribution's sources and config, the ruff binary, AND `ratchet-preview.txt`
# — the last being the one a careless declaration drops. A census keyed without the thing it is
# compared against could be lowered with no gate noticing, which is the laundering `--init-absent`
# refuses on a second run.
sh_test(
name = "ratchet",
srcs = ["@mikemol_check_ratchet//:ratchet_check.sh"],
args = [
"$(location //ratchet:ratchet_cli)",
"$(location @mikemol_check_ruff//:bin)",
"$(location :pyproject.toml)",
],
data = [
"pyproject.toml",
"ratchet-preview.txt",
"//ratchet:ratchet_cli",
"@mikemol_check_ruff//:bin",
] + glob(["src/**/*.py", "tests/**/*.py"]),
size = "small",
)
# ⚑⚑ mypy IN THE GRAPH, ONE ACTION PER DISTRIBUTION. It was the last check running outside — no
# key, no invalidation, full re-execution every commit. The grain was measured rather than
# inherited: whole-distribution mypy costs 0.12s against ~3.0s for 27 per-file runs, because
# mypy's startup dominates at this scale and each per-file run re-analyses the same closure.
py_binary(
name = "mypy_runner",
srcs = ["@mikemol_check_mypy//:mypy_runner.py"],
main = "@mikemol_check_mypy//:mypy_runner.py",
# ⚑⚑ VISIBLE TO THE ROOT, WHICH BORROWS THIS RUNNER RATHER THAN DECLARING ITS OWN. A py_binary
# in the root package writes its generated `_<name>_stage2_bootstrap.py` into `_main/` — the
# directory `//:mypy` checks with `files = ["*.py"]`. MEASURED: a root-owned runner made
# `//:mypy` report 154 errors, every one in that bootstrap. A runner in this package leaves
# its bootstrap in `_main/hooks/`, outside the root's glob. Same pins (`@hooks_dev`).
visibility = ["//:__pkg__"],
# ⚑ pytest IS A DEP OF THE CHECKER, NOT JUST OF THE TESTS. mypy checks `files = ["src",
# "tests"]`, and a test module imports pytest — so without it staged, mypy reports
# `Cannot find implementation or library stub for module named "pytest"` and every fixture
# degrades to Any. The checker's domain includes what the checked code imports.
deps = [
dev_requirement("mypy"),
dev_requirement("pytest"),
],
)
sh_test(
name = "mypy",
srcs = ["@mikemol_check_mypy//:mypy_check.sh"],
args = [
"$(location :mypy_runner)",
"$(location :pyproject.toml)",
],
data = [
"pyproject.toml",
":mypy_runner",
] + glob(["src/**/*.py", "tests/**/*.py", "stubs/**"], allow_empty = True),
size = "small",
# ⚑ A GENEROUS TIMEOUT, NOT A TIGHT ONE (operator, 2026-09-24: "I hate wallclock-sensitive
# things"). MEASURED under host contention: a cold mypy took wall=295s against user=2.9s, so
# the 60s default of `small` timed out on waiting, not on work. `size` stays small (it is a
# resource claim); only the wall-clock allowance grows.
timeout = "long",
)
# ⚑⚑ THE MUTATION GRID AS A GATE TARGET — same shape as //ratchet:mutants, which carries the
# rationale; //mdstruct:mutants is the F-arm (real deps).
mutants_sharded(
name = "mutants",
shards = 3,
cpus = 8,
srcs = ["@mikemol_check_mutants//:mutate_check.sh"],
args = [
"$(location :pyproject.toml)",
"$(location @mikemol_check_mutants//:mutate_runner.py)",
],
data = [
"pyproject.toml",
# W629: the defect classes this distribution declares, planted one at a time by the runner.
"mutants.regex",
":.venv",
"//fence:fence",
"//mutation:mutation",
"@mikemol_check_mutants//:mutate_runner.py",
] + glob(["src/**/*.py", "tests/**/*.py"]),
size = "medium",
# ⚑⚑ THIS TARGET WAS THE LOAD, AND EVERY OTHER TARGET'S TIMEOUT WAS ITS VICTIM (W942). It plants
# 390 defects and re-runs a 1,100-test suite for each. It was tagged `exclusive` to run it alone;
# the operator ruled (2026-10-10) that a target that is too big under load is SPLIT, not
# serialized (W969). Three shards of about 136 sites are three short targets bazel schedules
# beside the rest, and `:mutants` still names all of them.
)
# ⚑⚑⚑ THE DISTRIBUTION'S `.venv`, AS A BUILD ARTIFACT. One macro call; the same four lines appear
# in every distribution, which is the "trivially-templatizable" property the direction asked for.
# ⚑⚑ THE POPULATION IS `all_requirements` — THE HUB'S OWN DERIVED CLOSURE, NOT A LIST KEPT HERE.
# `pip.parse` generates it in `requirements.bzl` from the lock; it is 12 packages for hooks_dev
# today and becomes 13 the day a wheel is added, with no edit to this file. Naming packages here
# would be the hand-written-population defect that ⟐POLL-RUF201-POPULATION was, one layer down.
# ⚑ AND `requirement()` RESOLVES TO `:pkg`, WHICH IS NOT WHAT THIS RULE WANTS. Measured by reading
# the generated `requirements.bzl`: the accessors are `requirement`/`whl_requirement`/
# `data_requirement`/`dist_info_requirement` and there is NO accessor for `extracted_whl_files`.
# A first draft here wrote `dev_requirement("pytest") + "_extracted"`, which would have named a
# label that does not exist — a guessed suffix, caught by reading the source rather than by the
# error it would have produced. The macro rewrites `:pkg` to `:extracted_whl_files` itself.
# ⚑⚑⚑ THE CONSOLE SCRIPTS MIRROR `[project.scripts]` IN pyproject.toml, AND THE DUPLICATION IS
# GATED RATHER THAN TRUSTED. Starlark cannot read TOML at analysis time — `rules_python`'s
# `read_pyproject` takes a `module_ctx`, which exists only in a module extension, not in a rule —
# so the mapping is restated here. ⚑ A restated population is the defect this repository measures
# most, so `//hooks:test_bar_fires` asserts the two agree: a script added to pyproject and not
# here (or vice versa) fails rather than silently shipping a venv missing its entry point.
# ⚑⚑ AND THESE ARE LOAD-BEARING FOR THE HARNESS, not a convenience: `.claude/settings.json`
# invokes all three as PreToolUse hooks. A missing one FAILS OPEN — measured: rc=0, empty stdout,
# no decision, which the harness reads as *allow*. `hooks/bin/` holds tracked launchers that
# refuse loudly instead.
venv_from_hub(
name = ".venv",
console_scripts = {
# W526: every hook enters through `entry`, which refuses an argument before stdin.
"mikemol-hook-structural-query": "mikemol.hooks.entry:structural_query_main",
"mikemol-hook-no-chaining": "mikemol.hooks.entry:no_chaining_main",
"mikemol-hook-no-verify": "mikemol.hooks.entry:no_verify_main",
"mikemol-hook-shellcheck": "mikemol.hooks.entry:shellcheck_main",
"mikemol-hook-pycheck": "mikemol.hooks.entry:pycheck_main",
"mikemol-hook-inbound-asks": "mikemol.hooks.entry:inbound_asks_main",
"mikemol-hook-nemik-check": "mikemol.hooks.entry:nemik_check_main",
# W811: the Stop guard for the host tick lock.
"mikemol-hook-tick-stop": "mikemol.hooks.entry:tick_stop_main",
# W812: the UserPromptSubmit tick gate.
"mikemol-hook-tick-gate": "mikemol.hooks.entry:tick_gate_main",
# W813: the SessionStart(compact) after-compaction context.
"mikemol-hook-after-compaction": "mikemol.hooks.entry:after_compaction_main",
# W815: the SessionEnd tick-lock release.
"mikemol-hook-tick-release": "mikemol.hooks.entry:tick_release_main",
"mikemol-hook-work-claims": "mikemol.hooks.entry:work_claims_main",
# W814: the PreToolUse(Read) oversized-read guard.
"mikemol-hook-read-guard": "mikemol.hooks.entry:read_guard_main",
"mikemol-shellcheck": "mikemol.hooks.shellcheck_cli:main",
# W829: the PostToolUseFailure build-failure report.
"mikemol-hook-build-failure": "mikemol.hooks.entry:build_failure_main",
"mikemol-build-failure": "mikemol.hooks.build_failure:report_main",
"mikemol-commit": "mikemol.hooks.commit_kata:main",
"mikemol-hooks-probe": "mikemol.hooks.probe:main",
"mikemol-snapshot": "mikemol.hooks.snapshot:main",
"mikemol-bazel-admit": "mikemol.hooks.bazel_admit:main",
# W818: the PostToolUse closure advisory.
"mikemol-hook-pycheck-advise": "mikemol.hooks.entry:pycheck_advise_main",
# W808: the edit gate's verdict on one file by hand.
"mikemol-pycheck": "mikemol.hooks.pycheck_cli:main",
"mikemol-githook-pre-push": "mikemol.hooks.githook_pre_push:main",
"mikemol-githook-post-commit": "mikemol.hooks.githook_post_commit:main",
"mikemol-githook-prepare-commit-msg": "mikemol.hooks.githook_prepare_commit_msg:main",
"mikemol-gate-ledger": "mikemol.hooks.gate_ledger:main",
"mikemol-gen-warrants": "mikemol.hooks.gen_warrants:main",
"mikemol-new-dist": "mikemol.hooks.new_dist_cli:main",
"mikemol-repin": "mikemol.hooks.repin_cli:main",
"mikemol-wheel": "mikemol.hooks.wheel:main",
},
hub_requirements = dev_all_requirements,
python_version = "3.13.13",
python_version_short = "3.13",
srcs = glob(["src/mikemol/hooks/**"]),
)
# W376: this distribution's :venv (W342) and :suite (W362), declared together.
dist_checks()