Repository navigation
fix(halstead): vocabulary and decision batch 2026-10-10 - #1605
Merged
Merged
Conversation
TypeScript billed no class-member modifier but `declare`, while C# and
Kotlin bill theirs. Each modifier keyword leaf is now an operator in
the TS and TSX branch: `public`, `private`, `protected`, `override`,
`readonly`, `abstract` and `accessor`. The `accessibility_modifier` /
`override_modifier` wrappers stay unlisted, so each bills once. Every
word has one kind, so no alias is missed. The leaves bill outside a
class as well: parameter properties, `readonly T[]`, a mapped type's
`-readonly`, `abstract new () => T`.
`static` joins the shared JS-family arm, so JavaScript and MozJS bill
it too, in fields, methods and `static {}` blocks. Their one-token
`static get` (getter name on the next line) bills as one operator.
Used as names (`let readonly = 1`, `obj.static`, `{ public: 1 }`, a
field named `static`) the words parse as identifiers and stay
operands. The pinned TS grammar misreads a few valid name spellings as
the keyword (`abstract();` as a statement, a field `accessor = 1`, an
object-type member `abstract: T`); those now bill the operator where
they billed nothing, recorded in the getter comment. None occurs in
the corpora.
Snapshots: 60 pdf.js files move in halstead.* and mi.* only, operand
counts unchanged. Submodule 0708c3d9.
Fixes #1563
Kotlin billed none of `typealias`, `init`, `where`, `by`, `get` or `set`, while it bills its other declaration keywords and every modifier. Each is now an operator, as C# bills `get` / `set` / `init` / `where` and TypeScript `type`. Each is the keyword leaf; the `type_alias`, `anonymous_initializer`, `type_constraints`, `property_delegate` / `explicit_delegation`, `getter` and `setter` wrappers stay unlisted, so each bills once. Every word has one kind. `get` and `set` bill only directly under `getter` / `setter`: the same leaves are the `@get:` / `@set:` use-site targets, which bill nothing like `@field:`, and kotlin-ng strands a `get(1)` call after a local `val f = 6` in an ERROR. `by`, `where` and `init` used as names parse as identifiers, and `typealias` is a hard keyword, so those four need no guard. The accessor body's `field` is an identifier and stays an operand. No corpus snapshot moves: the one Kotlin corpus file uses none of the words. Fixes #1564
Java and Groovy billed `abstract`, `final`, `transient`, `synchronized` and `default` but no other modifier, while C# and Kotlin bill all of theirs. Each modifier keyword leaf is now an operator in both: `public`, `private`, `protected`, `static`, `native`, `strictfp`, `volatile`, `sealed` and `non-sealed`, plus the `permits` clause keyword beside the already-billed `extends` and `implements`. Java's `modifiers` wrapper stays unlisted and Groovy has none, so each modifier bills once; an annotation inside `modifiers` is unchanged. No word has a numeric-suffix alias. Java's `default` is one kind for the switch label and the interface-method modifier and already billed. The `static` of `import static` and Java's `requires static` bills nothing, as `import` and `requires` bill nothing. Java parses `sealed` and `permits` used as names as identifiers, so they stay operands. Groovy fails a statement that opens with one (`sealed = 1`) into an ERROR holding the keyword, so its `sealed` and `permits` bill only under their declaration; the gate joins the existing `trait` / `record` one in a single (keyword, owner) table. No corpus snapshot moves: the DeepSpeech corpus globs only C++, and no Java or Groovy corpus is snapshotted. Fixes #1565
A Java class literal billed its whole text as one operand while the `.`, `[]` and primitive inside it also billed as operators, and the `class` tail and type names were suppressed: `int.class` paid `int` and `.` on top of the `int.class` operand that already spelled them. JLS 15.8.2 defines the class literal as a primary expression, not a literal (3.10 lists none), so its tokens are its vocabulary. The `class_literal` kind leaves the operand arm, the #1560 type-name guard is deleted so the type bills as everywhere else, and the `class` tail becomes an operand, as Groovy's `Foo.class` and Kotlin's and PHP's `Foo::class` bill theirs. The declaration keyword stays an operator. `java.util.List.class` now bills three `.` and operands `java`, `util`, `List`, `class`; `Foo[].class` bills `[]`, `.`, `Foo`, `class`; `int.class` / `void.class` bill `int` / `void`, `.` and `class`. The shared shapes are asserted against Groovy from one table. No Java corpus file is snapshotted, so no integration snapshot moves. Fixes #1566
#1559 billed a generic list's angle brackets as one `<>` pair. Three bracket pairs that enclose types but are not list nodes still billed the comparison `<` and `>`: a Rust qualified path's bracketed_type (`<T as Tr>::f`, `<T>::f`), a C# function_pointer_type (`delegate*<int, void>`) and a Kotlin super_expression (`super<A>`). Each parent holds `<` / `>` only as its own brackets, so adding it to the grammar's GENERIC_ANGLES.lists cannot capture a comparison. The rule is now "angle brackets that enclose types are one bracket pair". Kotlin's row leaves the ABC/Halstead bracket parity test, which excludes generic brackets by design; ABC's half stays pinned by kotlin_super_type_argument_is_not_a_condition. Nine serde corpus snapshots move in halstead.* and mi.* only. Fixes #1567
Groovy billed `int`, `boolean`, `void` and the other primitive type names as operands while Java bills them as operators, so identical declarations scored differently by language. The dekobon grammar has no primitive node: a declared type is a `type_identifier`, and a class literal's receiver (`int.class`) or an uninitialised local (`int t`, parsed as two expression statements) is a plain `identifier`. `get_op_type_with_code` now bills either kind as an operator when its text is one of Java's eight primitive keywords or `void`, and `is_primitive` keys it by that text. Groovy reserves these words, so a name can spell one only as a member selector after a `.`-family operator or as a map or named-argument label; those stay operands. Java's `boolean` is now listed as `boolean` rather than its node kind `boolean_type`, so `bca ops` reports both languages alike. The shared Java/Groovy class-literal table takes the `int.class`, `int[].class` and `void.class` rows. No corpus snapshot moves: no Java or Groovy corpus is snapshotted. Fixes #1568
An interpolated string's alignment `,` and format `:` (`$"{x,8:F2}"`)
were billed as Halstead operators. Both delimit the field like the
`{` / `}` braces #1314 left unbilled, and Python's format-spec `:`
went the same way in #1486. Parent-guard COMMA on
interpolation_alignment_clause and COLON on
interpolation_format_clause; the same tokens elsewhere, including a
ternary, named argument or argument list inside the hole, keep
billing.
The alignment value still walks as an ordinary expression: `8` is an
operand and `{x,-w}` bills `-` and `w`, since the spec makes it a
constant_expression that may name a const.
The csharp/strings.cs corpus snapshot moves by its two `:F2` colons
(Halstead and MI only).
Fixes #1573
Re-derive the halstead.effort default after the Halstead vocabulary changes (#1486-#1573) on a rebuilt 41-repository, 20-language corpus, measured with this branch's release bca and, for comparison, v2.3.0. At 50,000 the median language flags 2.20% of its functions (v2.3.0: 2.23%), inside the 1-3% sizing band, so the default stays. Update its rationale (per-language p90 now 3k Ruby to 60k C), regenerate the book's per-language halstead.effort column, correct the two book paragraphs the new figures contradict, and record the corpus with commit SHAs in docs/development/threshold_corpus.md so the next re-derivation can reproduce it. Fixes #1569
Correct the CHANGELOG's #1567 n1 claim and name Mozjs and its one-token `static get` under #1563. In the book, add `default` to the modifiers Java and Groovy billed before #1565, document the `static get` newline spelling, and scope the procedural-language threshold advice to cognitive/cyclomatic, since Tcl, Bash and Perl effort sits near the default (AGENTS.md likewise). State `Checker::is_primitive`'s precondition: it is sound only for a node the getter already billed as an operator, which Groovy's kind-only impl relies on. Note that invalid C# interpolations (`$"{x,}"`, `$"{:F2}"`) bill their delimiter, untested by design. Fix the Groovy #1565 test doc that still called `void` an operand, add a Java-to-Groovy twin oracle for #1565, and compare the #1573 test's vocabulary as returned instead of re-sorting it. Re-wrap ragged comments.
C, C++ and Mozcpp billed no storage class, function specifier or type qualifier, while every other class language bills its modifiers. Each keyword leaf is now an operator: static, extern, register, inline, thread_local, const, volatile, restrict, _Atomic, constexpr, alignas, _Noreturn and their vendor spellings (__inline, __restrict__, __thread, ...), C's storage class auto, and C++'s virtual, mutable, constinit, consteval, explicit, friend, the access labels, override, final and the noexcept of an exception specification. The storage_class_specifier, type_qualifier, virtual_specifier, access_specifier, explicit_function_specifier and noexcept wrappers stay unlisted, so each modifier bills once; a member-function trailer const and a pointer's const each bill where they appear. None of the words has a numeric-suffix alias. override and final used as names parse as identifiers and stay operands. C++'s auto is a placeholder type and stays unbilled; attributes, MS calling conventions and the Gecko MOZ_* macros are unchanged. The DeepSpeech corpus moves in 884 snapshot files, Halstead operator counts and MI only: operands and structure are unchanged. Fixes #1578
Objective-C billed none of C's storage classes, function specifiers or type qualifiers, so a .m scored a C declaration below a .c or .mm. The ObjC getter now bills C's list exactly as c.rs does (#1578), plus ObjC's own modifiers as keyword leaves: the @public / @Private / @Protected / @Package ivar labels, @optional / @required, the ARC ownership qualifiers and __bridge casts, the nullability qualifiers, __kindof, __covariant / __contravariant, _Complex, and the in / out / inout / bycopy / byref / oneway method-type qualifiers. A property attribute has no keyword kind: it is the first identifier of a property_attribute, which now bills as an operator keyed by its text; a getter= / setter= value stays an operand. The nullable qualifier is text-keyed too, so it and the nullable attribute share one n1 entry. Apple SDK macros (NS_INLINE, FOUNDATION_EXPORT, IBOutlet, __unused, ...) stay unbilled, as Mozcpp's MOZ_* do. No corpus file is Objective-C, so no snapshot moves. Fixes #1579
tree-sitter-perl 1.1.2 parses every auto-quoted bareword key as a
call: `(name => 1)` and `new(name => $x)` scored an ABC branch per key,
and `(-name => 1)` / `$h{-name}` also billed a Halstead `-` operator
and the unsigned operand `name`. Perl quotes a word before `=>`, and
alone in a hash subscript, so each holds a string.
lang_helpers::perl::perl_bareword_is_key now decides every bareword
key shape through one key-position test, which #1545's file-test keys
share. `=>` quotes the word right before it whatever expression it
closes, so the test climbs while the key is its parent's last child:
`(1 - name => 2)` quotes `name` too. ABC's bareword-call arm and the
Halstead getter consult it; a signed key's `-` bills nothing and the
key is spelled `-name`. Package-qualified words, words with arguments
and bareword values keep their call. Cyclomatic and cognitive were
never affected.
Adds the abc/perl-bareword-keys width probe to the scaling gate.
Fixes #1572
Two spellings of one Elixir program scored differently in five places. A bracketed keyword list in last position (`if(c, [do: 1, else: 2])`) is the same argument as the bare `do:` form, but every section reader only recognised a bare `keywords` node. One lookup now finds the trailing keyword list in either spelling, and quoted keys (`"do":`), for `if` / `unless` else, `with`'s else tier, `cond`'s free `true ->` and the `for ... reduce:` head skip. `Kernel.||(a, b)` and `Kernel.&&(a, b)` now score ABC conditions and a cognitive boolean sequence through the predicate cyclomatic already used, and a nested call no longer pays a slot as an operand. A user module's `Foo.||` still scores nothing. Cognitive scored the keyword forms' `else:` / `rescue:` / `catch:` nothing; they now score as their blocks do, for `if`, `unless`, `try` and the `def` family. A `with`'s `else` adds nothing in either form: its clauses are the arms of a `case`, already paid by the container. Fixes #1574
Seven probes for the walks #1273, #1536 and #1540 made quadratic, which the complexity-class gate could not see: a Perl `||` chain and a chain a misparsed comparison absorbed (the spine climb), a block of `&&` statements (the stop parent's operator lookup), a file whose tail recovers into a flat ERROR root (perl_binding_rewrites), a Bash `[ … -o … ]` run (cognitive), a valid `(( a = … = 0 ))` and a `[[ … = … && … ]]` run (ABC). Each fails at 1.91-2.06 with its pre-fix walk restored and holds at 0.98-1.19 clean. The ERROR-tail probe is the first whose shape must not parse; RECOVERY_PROBES exempts it from the parse check and pins its ERROR root. Its body carries only `=~`: a bareword or chain under a flat ERROR is still quadratic today (#1584). Fixes #1575
A seek by byte reaches a child in O(log n) only where the grammar
stores the list as a repeat; an ERROR that recovery built by skipping
tokens is one flat array, and no tree-sitter call reaches a child of
one in better than its index. Perl ABC and Halstead ask for a sibling
in such an ERROR once per child (the bareword and dash key tests, the
misparse operator lookup, the key-sign test), so a file whose tail
fails to parse was quadratic in the tail: 2.15 s at 16 000 copies of
`{ last,`. Kotlin's `Ancestors::previous_sibling` scanned a string's
children from its start, making `"$a $a ..."` quadratic on valid input
(11.5 s at 8 000 names).
The metric and ops walks now record the children of every node wider
than 64 as they push them, in a SiblingCache they lend through
Ancestors. Ancestors::siblings_after / siblings_before and
previous_sibling binary-search that array; a narrow or unrecorded
parent falls back to the seek, so every answer is unchanged. A debug
assertion fails any walk that lends a cache without recording a wide
node. Ancestors moves to node/ancestors.rs, keeping node.rs under its
ploc limit.
New probes halstead/perl-error-tail-keys and
halstead/kotlin-short-interpolations, and abc/perl-error-tail widened
to carry a bareword and a chain, all trip bench-scaling with the cache
ignored (2.10, 1.74, 2.02) and hold at ~1.0 with it.
Fixes #1584
tree-sitter-perl parses a call's name as a bareword call nested in
the call, and an auto-quoted hash key as a bareword call too, so
`bca find --type call` reported `foo(1)` twice and every `name =>`
and `$h{-name}` key once. ABC already skipped both through a gate
that needs the ancestor chain, which the byte-less `is_call` lacks.
The gate moves to lang_helpers::perl::perl_bareword_is_call, which
ABC and a new Checker::is_call_with_code share; the `call` filter
now calls the chain-carrying spelling, whose default forwards to
`is_call`. The bareword is kept, gated, rather than dropped: it is
the only node for `foo;`, `&foo;` and the wrapper-less `foo(1` of
error recovery. Perl's `call` set now equals its ABC branches.
The key test reads the token after each bareword, a seek that is
quadratic under a flat ERROR (#1584), so act_on_node now records
and lends a SiblingCache as the metric and ops walks do. The new
count/perl-error-tail-calls probe fits 0.88 with it and 1.89 with
the cache left unlent.
Fixes #1580
A `receive` is a `case` over the mailbox, but modified cyclomatic and cognitive did not know the construct: a `receive` with any number of clauses sat at the base of both, and nothing inside it was nested. It now pays the container decision and +nesting as `case` does, in the block, keyword and bracketed spellings. Its `after` clause is the timeout, the path taken when no message clause matched in time. The compiler accepts exactly one, its head is a timeout rather than a pattern, and `try`'s `after` takes no clauses, so it is `receive`'s default arm and is free in every metric, as a `case`'s `_ ->` is. Standard cyclomatic and ABC counted it before. A `receive` holding only an `after` is a timed wait and scores nothing. Fixes #1581
`Kernel.and(a, b)` and `Kernel.or(a, b)` expand the same short-circuit macros as `a and b` / `a or b`, but scored no decision in any metric. #1574 routed `Kernel.&&` / `Kernel.||` through the remote-call predicate; the word forms never reached it, because the grammar names them with a plain `identifier` rather than an operator token. The `Kernel` lookup now returns that identifier when its bytes spell `and` or `or`, and one helper keys every spelling of a short-circuit operation to its symbol, so cognitive treats `a or Kernel.||(b, c)` as one sequence. Cyclomatic, ABC and cognitive each read the identifier beside the token. `Kernel.not` and `Kernel.!` stay non-decisions, and a user module's `Foo.and` stays an ordinary call. Fixes #1582
`f(x, rescue: (e -> e))` counts its clause as a decision because the section gate does not ask which call owns the pair. The compiler rejects that argument for any function, so only a user macro can take it, and its clauses mean whatever the macro makes of them. Gating on the owner would move no valid program's score in a defensible direction, so the gap is documented rather than fixed and is not pinned by a test, per grammar-dispatch section 6. Also repoint the paragraph's stale `elixir_is_section_pair` link. Refs #1583
The `Kernel` operator lookup repeated the `and` / `or` byte list that `elixir_short_circuit_name` owns; it now asks that helper, so the two cannot drift. The book still listed `after:` among the keys whose clauses score, which #1581 removed.
tree-sitter-perl 1.1.2 parses an auto-quoted hash key as a bareword call (upstream #1571), so `(exit => 1, die => 2)` scored two exits its quoted twin does not. ABC and `find --type call` already skip such a key through perl_bareword_is_key, which reads the token after the word. Exit::compute now takes the walk's Ancestors chain, as every other metric's compute does, and the Perl impl asks the key test after its text test matches. The chain carries the walk's SiblingCache, so a flat ERROR tail of keys stays linear. Exit is pub(crate), so the widened signature changes no public API. Fixes #1585
B1: a `receive` now gets exactly one free arm: its bare `_ ->` if the
do-section has one, otherwise its `after` timeout. Beside a catch-all
the timeout is a third path, so `receive do {:a, y} -> y; _ -> 0
after 10 -> :t end` scores what the three-clause `case` does in
standard cyclomatic and ABC; modified and cognitive pay the container
only and are unchanged. New parity rows in all three spellings.
T2: `WIDE_PARENT` is `pub` + `#[doc(hidden)]` and the three wide-parent
fixtures size themselves from it instead of a copied 64. T3: note that
`act_on_node_records_a_wide_seeded_ancestor` discriminates only in a
debug build (lesson #80).
T5: `nexits/perl-error-tail-keys` scaling probe (1.11 clean, 1.87 with
`Ancestors::lookup` ignoring the cache), listed in benchmarking.md.
T4/D2: C++ and C both parse `int restrict = 0;` / `int noreturn = 1;`
as a qualifier before a zero-width name; documented as grammar-decided
in `getter/c.rs`. D1, D3, N1: doc, changelog and comment fixes.
Add lessons 96 (a tree-sitter child seek is O(log n) only through a grammar repetition, #1584) and 97 (a before/after on a rebuilt corpus measures the corpus, #1569), and extend 53 (fixed-depth ancestor climb, #1574), 57 (reserved word folded into the name kind, #1568), 64 (two candidate default arms, #1581), 82 (reuse the caller's chain, #1580) and 95 (a real token wrapped in an extra ERROR, #1572).
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1605 +/- ##
==========================================
+ Coverage 98.18% 98.20% +0.01%
==========================================
Files 364 365 +1
Lines 99890 101068 +1178
Branches 99459 100637 +1178
==========================================
+ Hits 98077 99250 +1173
+ Misses 1182 1173 -9
- Partials 631 645 +14
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Bill the vendor spelling `__volatile__` (Volatile2) in the C, C++, Mozcpp and ObjC modifier arms, so `__asm__ __volatile__` scores like `asm volatile`. Read an Elixir keyword pair's section off the ancestor chain instead of rescanning `arguments` per pair or clause, which was O(P*K) on wide calls (5.4 s -> 0.06 s at 16k arguments), bound the bracketing-list climb to one step, and share one section-holder helper across cyclomatic, ABC and cognitive. Pin that Perl's is_call_with_code disagrees with the byte-less is_call on a key or wrapper callee, pin the `(-foo, => 1)` and comment-in-slice shapes, and record the ast-crate API changes and the cognitive scope of #1574 in the changelog. Add the cognitive/elixir-mixed-arguments probe.
`--compare` diffed per-test membership from each item's own cfg and its enclosing items', but never the cfg on the `mod` line that loads the file. A narrowed root gate in a `tests/*/main.rs` therefore dropped a whole suite from a single-language leg with nothing reporting it: #1567's deliberate drop of `kotlin` from `abc_halstead_bracket_parity` read as OK against main. Each file now inherits the conjoined cfg of the `mod` declarations that reach it, and `--compare` intersects test membership with it. The non-compare gate is unchanged.
Re-measure the C, C++ and Objective-C halstead.effort rows at d361bc3: #1578, #1579 and `__volatile__` landed after the 18c28ca measurement and raise C-family effort. The median-language rate stays 2.20% (2.78% / 1.76% at 40k / 60k), so the default stands. Move the `(-foo, => 1)` row out of the "keeps its reading" test into a pin of the grammar misparse carrying FIXME(#1571), restructure the book's Halstead bullets so the declaration-modifier rule is stated once, and correct lesson 53, the #1574 and #1578 changelog entries, the vendor-spelling list and the probe table order.
dekobon
force-pushed
the
fix/batch-2026-10-10
branch
from
October 15, 2026 21:47
39acb28 to
fe76014
Compare
Upgrade rustls to 0.23.46 and replace serde_yaml with serde-saphyr to remove unsafe-libyaml. Preserve YAML data and round-trip behavior with updated tests and explicit string/float coverage.
Remove obsolete timeout-clause lookup and carry the recognized argument list into ABC. Add boundary tests for short source buffers, root nodes, empty arguments and standalone keyword lists, preserving metric values while reducing uncovered production paths.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Halstead vocabulary and decision-counting batch: 28 signed commits, 16 issues fixed, one closed as won't-fix (#1583). Every fix went through investigate → simplify-rust → rust-optimize → review → audit-tests, then the branch had four cross-fix review passes (two whole-branch
reviewruns, a max/code-review --fix, and a Fable-model gap review), each of which found something the earlier ones had not.Halstead vocabulary
staticand the one-tokenstatic get(fix(getter/ts): TypeScript bills no member modifier #1563)typealias,init,where,by,get,set(fix(getter/kotlin): typealias, init, by, where unbilled #1564)permits;import staticstays unbilled (fix(getter/java): Java and Groovy bill half their modifiers #1565)classtail is an operand (fix(getter/java): class literal interior double-bills #1566)<T as Tr>, C#delegate*<>, Kotlinsuper<A>bill as one<>pair (design(halstead): bill <T as Tr>, delegate*<> and super<A> as <>? #1567),and format:unbilled (fix(getter/csharp): bill no interpolation format clause #1573)__volatile__(fix(getter/cpp): C, C++ and Mozcpp bill no modifier #1578); Objective-C's C modifiers and its own (fix(getter/objc): Objective-C bills no C modifier #1579)Decisions and walks
=>keys are string operands, not calls (fix(perl): a bareword key before => scores an ABC branch #1572);find/count --type callreport each call once and skip keys via a newChecker::is_call_with_code(fix(checker/perl): is_call matches bareword keys and double-reports calls #1580);nexitsskips key-positiondie/exit(fix(nexits/perl): an auto-quoted die/exit key counts as an exit #1585)Kernel.&&/||andKernel.and/orare decisions;receivescores likecasewith exactly one free arm (fix(elixir): keyword-list and Kernel.|| spellings diverge #1574, fix(elixir): receive pays no modified or cognitive cost #1581, fix(elixir): Kernel.and / Kernel.or score no decision #1582)ERRORwere O(index): the walks now record wide nodes into aSiblingCachelent throughAncestors(perf(abc/perl): sibling lookups are quadratic under a flat ERROR #1584); seven new Perl/Bash scaling probes plus six more from the fixes, every one watched tripping (test(bench): add Perl and Bash scaling probes #1575)halstead.effortdefault re-measured on a rebuilt, now recorded, 20-language corpus: stays at 50,000 (2.2% of functions in the median language) (chore(thresholds): re-derive halstead.effort defaults after the vocabulary changes #1569)Gates
make pre-commitpasses; 6,001 tests; 49/49bench-scalingprobes;chain-auditandfuzz-checkgreen. Patch coverage 98.3% against a project figure of 96.76%, CI method on a clean target. Snapshot submodule at3c754c5a(pdf.js, serde, one C# file, 884 DeepSpeech files; every moved value ishalstead.*ormi.*, checked field by field).Label
gate-narrowing-intended:tests/parity/main.rsdeliberately dropsfeature = "kotlin"from theabc_halstead_bracket_paritymodgate, because the Kotlinsuper<A>row moved to the<>family (#1567). The compare gate did not previously see rootmodgates at all;ec2feb72fixes that, so it now reports this narrowing, correctly.Follow-ups filed from the reviews
#1598 C-family kind table · #1599
is_primitivehook · #1600import staticvs C#'susing· #1601 non-optionalSiblingCachelending · #1602 cognitive owner-list residual · #1603 fabricated chain inabc/perl.rs· #1604-barewordoutside key position.Closes #1563, closes #1564, closes #1565, closes #1566, closes #1567, closes #1568, closes #1569, closes #1572, closes #1573, closes #1574, closes #1575, closes #1578, closes #1579, closes #1580, closes #1581, closes #1582, closes #1584, closes #1585.