* ci: add informational code-erosion (slop metrics) workflow
Report SlopCodeBench verbosity/erosion metrics on every PR using the
official scb-check tool, pinned to 0.2.0 (the first release with
TypeScript support; 0.1.3 is Python-only).
The workflow is informational and never blocks a merge: scb-check's exit
code is swallowed, and the numbers are posted as a deduplicated PR comment
with the run's job summary as a fallback (so fork PRs, whose token is
read-only, still surface the report). Tests are excluded via scb-check.toml
so metrics reflect the product surface.
On TypeScript the ast-grep verbosity rule component is Python-only and
contributes 0, so verbosity reflects clone + wrapper detection only;
erosion is fully faithful. This caveat is documented in the bilingual
docs/ci-code-erosion.{md,zh-CN.md}.
* ci(code-erosion): add independent TS verbosity rule layer
scb-check only runs its ast-grep rules on Python files, so on this
TypeScript repo its verbosity rule component is always 0. This adds a
standalone ast-grep pass with a small, hand-ported rule set to fill that
gap, reported as a separate "Rule hits (TS verbosity layer)" section in
the same non-blocking PR comment.
Only purely structural rules are ported. Rules that hinge on truthiness or
type semantics (len==0, ==True, redundant template strings) were tried and
deliberately dropped: they are false positives in TypeScript, where
arr.length>0 is idiomatic and x!==true is not equivalent to x===false
(TS has undefined). Ported rules verified against src/ for false positives:
unnecessary-else-after-return, empty-catch-block, redundant-ternary-same,
if-return-boolean-literal, return-ternary-boolean-literal,
duplicated-if-condition, self-assignment.
Rules use severity: hint and the scan step has `|| true`, so the layer
never blocks CI. Bilingual docs updated with the honest scope: this is an
extra signal, not a reproduction of the paper's verbosity number.
* ci(code-erosion): slim down the PR comment, defer detail to docs
The comment carried long inline explanations (verbosity footnote, rule-layer
paragraph). Move the prose to docs/ci-code-erosion.md and keep the comment to
numbers plus a one-line pointer. Also replace the ambiguous "(informational)"
tag with plain "never blocks the merge".
* ci(code-erosion): drop the two repeated doc links in the comment
The top line already points to docs/ci-code-erosion.md; the verbosity
footnote and rule-layer note repeated the same link. Keep one pointer.
5.5 KiB
CI Code Erosion (informational)
The Code Erosion workflow (.github/workflows/code-erosion.yml) reports two
"code sloppiness" metrics on every pull request, using the official
scb-check tool (the SlopCodeBench
reference implementation, from Measuring the sloppiness of
code).
It is informational only — it never blocks a merge. The numbers are posted as a PR comment (and mirrored to the run's job summary) for reviewers to eyeball; they do not gate anything.
What it measures
| Metric | Meaning |
|---|---|
| Verbosity | Fraction of source lines that are redundant: |clone lines ∪ wrapper lines ∪ ast-grep rule hits| / SLOC. |
| Erosion | Concentration of complexity in already-complex functions: mass(f) = CC(f) × √SLOC(f); the share of total mass held by functions with cyclomatic complexity > 10. |
| Cognitive erosion | Same shape as erosion, but weighted by cognitive complexity instead of cyclomatic. An extra signal scb-check provides. |
Reference bands from the source post (calibrated on Python repos):
| Metric | Human repos | Agent-generated |
|---|---|---|
| Verbosity | 0.15 ± 0.06 | 0.33 ± 0.10 |
| Erosion | 0.31 ± 0.17 | 0.68 ± 0.20 |
Important caveat for this TypeScript repo
scb-check's verbosity signal has three components: clone detection, trivial
wrappers, and 197 hand-authored ast-grep rules. Those 197 rules are
Python-only — they encode Python-specific wasteful patterns (dict idioms,
comprehensions, for i in range(len(...)), …) that have no TypeScript
equivalent. On this repo they contribute 0.
So read the numbers this way:
erosion/cognitive erosionare faithful. Cyclomatic and cognitive complexity are language-agnostic; the TypeScript implementation uses the same algorithm as Python.verbosityis partial. It reflects clone + wrapper detection only. The source post notes clones drive ~66% of agent slop growth and the ast-grep rules only ~15.6%, so the number still captures the larger part — but it is not directly comparable to the paper's verbosity figures or the Python-calibrated bands. Treat the bands as direction, not verdict.
Current baseline
Scanned src/ (tests excluded, see scb-check.toml), scb-check==0.2.0:
| Metric | Value | Reading |
|---|---|---|
| Verbosity | ~0.092 | below the human band |
| Erosion | ~0.65 | inside the agent band |
| Cognitive erosion | ~0.86 | — |
Erosion sits in the agent band mainly because of a handful of very large
functions (e.g. pullForScope, init, pushCore). That is the actionable
part of this report if the team ever wants to bring the number down.
Details
- Trigger: any PR targeting
master/main. - Tool version: pinned to
scb-check==0.2.0— the first release with TypeScript support. The version the paper pins,0.1.3, is Python-only. Pinning also keeps the numbers comparable across runs (the rule set changes between releases). - Scope:
src/, excluding**/__tests__/**and*.test.ts/*.spec.ts(configured inscb-check.toml), so metrics reflect the product surface, not the far larger test suite. - Never blocks:
scb-checkexits non-zero when it finds any slop (the normal case). The workflow deliberately swallows that exit code; the job is always green. - Fork PRs: the PR-comment step needs a write token, which fork PRs don't get. It degrades gracefully to the job summary — nothing fails.
Run it locally
uvx --from 'scb-check==0.2.0' scb-check check src \
--report --include-all --config scb-check.toml
Add --output-format human for a readable console table, or drop --report
for the default human output.
TS verbosity rule layer
Because scb-check's ast-grep rules never run on TypeScript (see the caveat
above), a small independent layer runs a standalone ast-grep with a
hand-ported rule set at .github/ast-grep-rules/ts-verbosity.yml, and the
report gains a Rule hits (TS verbosity layer) table.
Honest scope: this is not a reproduction of the paper's verbosity number.
It is an extra, separate signal. Of SlopCodeBench's 197 Python rules, roughly
half are Python-syntax-specific (dict idioms, comprehensions, typing) and
cannot exist in TypeScript. Of the rest, only purely structural rules were
ported — rules that hinge on truthiness or type semantics were tried and
deliberately dropped because they are false positives in TypeScript:
len(x) == 0→arr.length > 0is idiomatic TS, not slop.x == True→x !== truehandlesboolean | undefinedand is not equivalent tox === false(TS hasundefined; Python does not).- template-string checks mostly flag multi-line string concatenation.
What is ported (all structural, verified against src/ for false positives):
unnecessary-else-after-return, empty-catch-block, redundant-ternary-same,
if-return-boolean-literal, return-ternary-boolean-literal,
duplicated-if-condition, self-assignment. Rules use severity: hint so the
scan never blocks CI. Run it locally with:
npx -p @ast-grep/cli@0.45.3 ast-grep scan \
-r .github/ast-grep-rules/ts-verbosity.yml --json=stream src
Where to read results
- PR comment — one comment per PR, updated in place on each push.
- Job summary — the same table on the workflow run's summary page (the only surface on fork PRs).