Three command helpers bypass the thin-entry-hook convention (underscore lib run as a script; executable logic embedded in command prose)
Severity: low | Confidence: 0.8 | Effort: M
Where:
plugins/ca/hooks/_babysitlib.py:53-75
plugins/ca/commands/metrics.md:24-37
plugins/ca/commands/preview.md:22-39
Evidence: coding-standards.md ("Python (hooks)") mandates a thin non-underscore entry hook wrapping a lib. Three command helpers deviate: (1) _babysitlib.py is an underscore library that also runs as a script — it has def main()/argparse and if __name__ == '__main__' (lines 53-75) and watch.md:36 / pr.md:34 invoke python3 "hooks/_babysitlib.py" --root ...; there is no babysit.py entry hook. (2) _metricslib.compute() and (3) _previewlib.collect_diff()/scan_secrets() are driven by multi-statement inline python -c "..." blocks embedded in metrics.md / preview.md, reaching into the libs' namedtuple internals (.kinds, ._asdict()). No metrics.py/preview.py entry hook exists.
Impact: Convention drift: two invocation styles (underscore-lib-as-script, inline -c) coexist with the documented thin-entry-hook pattern. Untestable coupling: the executable logic embedded in command markdown is coupled to each lib's internal namedtuple API yet lives in prose that py_compile and the unit suites never cover — a rename in _previewlib's namedtuple silently breaks preview.md with nothing in CI to catch it.
Recommendation: Add thin metrics.py / preview.py / babysit.py entry hooks that wrap _metricslib.compute, _previewlib.collect_diff/scan_secrets, and _babysitlib.main and emit the JSON, mirroring taskwrite.py/doctor.py; have the commands invoke python3 "hooks/<name>.py" with the fallback, and drop the main block from _babysitlib so the underscore module is import-only.
Acceptance criteria:
- command prose invokes a thin non-underscore entry hook, not an inline multi-statement python -c body or an underscore lib run as a script
- _babysitlib.py has no main/argparse entry point (import-only, like the other _*lib modules)
- the field-access/serialization logic formerly in prose is covered by a unit test
Three command helpers bypass the thin-entry-hook convention (underscore lib run as a script; executable logic embedded in command prose)
Severity: low | Confidence: 0.8 | Effort: M
Where:
plugins/ca/hooks/_babysitlib.py:53-75
plugins/ca/commands/metrics.md:24-37
plugins/ca/commands/preview.md:22-39
Evidence: coding-standards.md ("Python (hooks)") mandates a thin non-underscore entry hook wrapping a lib. Three command helpers deviate: (1) _babysitlib.py is an underscore library that also runs as a script — it has
def main()/argparse andif __name__ == '__main__'(lines 53-75) and watch.md:36 / pr.md:34 invokepython3 "hooks/_babysitlib.py" --root ...; there is no babysit.py entry hook. (2) _metricslib.compute() and (3) _previewlib.collect_diff()/scan_secrets() are driven by multi-statement inlinepython -c "..."blocks embedded in metrics.md / preview.md, reaching into the libs' namedtuple internals (.kinds,._asdict()). No metrics.py/preview.py entry hook exists.Impact: Convention drift: two invocation styles (underscore-lib-as-script, inline -c) coexist with the documented thin-entry-hook pattern. Untestable coupling: the executable logic embedded in command markdown is coupled to each lib's internal namedtuple API yet lives in prose that py_compile and the unit suites never cover — a rename in _previewlib's namedtuple silently breaks preview.md with nothing in CI to catch it.
Recommendation: Add thin metrics.py / preview.py / babysit.py entry hooks that wrap _metricslib.compute, _previewlib.collect_diff/scan_secrets, and _babysitlib.main and emit the JSON, mirroring taskwrite.py/doctor.py; have the commands invoke
python3 "hooks/<name>.py"with the fallback, and drop the main block from _babysitlib so the underscore module is import-only.Acceptance criteria: