Skip to content

fix(core-engine): the function population is real functions only (#2691, #2692) - #2693

Merged
squid-protocol merged 3 commits into
mainfrom
fix/2669-slicer-function-population
Sep 3, 2026
Merged

fix(core-engine): the function population is real functions only (#2691, #2692)#2693
squid-protocol merged 3 commits into
mainfrom
fix/2669-slicer-function-population

Conversation

@squid-protocol

Copy link
Copy Markdown
Owner

Closes #2691 and #2692. Both were found while working #2669 Batch D, and both are extraction-layer
defects answering the same question — what counts as a function, and what counts as one of its
arguments
— so they batch under the one-layer-per-PR rule.

#2691 — synthetic slices were counted as functions

The slicer synthesizes a __global_context__ bucket to hold a file's top-level statements, and it
was counted in the function population. Every per-function average was therefore taken over entries
that are not functions: on the keyword-rosetta control corpus, livecode/lua/matlab/ruby/shell
reported 16 functions against 13 planted
, diluting each descriptor by ~3/16 and carrying into
cog_raw and structural_mass downstream.

The verdict is stamped once, in detector.py's orphan/duplicate loop — the single place every
function already passes through _is_synthetic_satellite_name (#2547's helper) — and read by the
three consumers that compute population statistics:

consumer what it computes
signal_processor max_func_comp, avg_func_args, func_count, func_complexity_gini
record_keeper function_count — what the corpus reads as functions_found
security_auditor avg_func_args

That beats re-deriving the rule in three files, and FunctionNode declares the new key rather
than growing an undeclared one (mypy caught the first attempt; the TypedDict was the right fix, not
a suppression).

The buckets keep existing and keep their signals at file level. Only their membership in "how
many functions does this file have, and what is the average one like" was wrong — file_mass still
sums every slice's impact and the rows stay in function_data. That distinction is what makes the
change safe, and it has its own test.

#2692 — a typed parameter counted as three arguments

The comma-free fallback whitespace-split its capture. That is correct for the languages it was
written for — Scheme's (define (f arg1 arg2), shell positionals — but it is reached by any
comma-free capture, and a single typed parameter is exactly that shape. ada's Env : Integer
scored 3; X : Integer; Y : Integer scored 6, because Ada separates parameters with semicolons.

_count_space_separated_args applies two ordered discriminators:

  1. Split on ; — a parameter separator in the Ada/Pascal family that never appears in a
    Lisp/shell parameter list (commas never reach this branch; the caller handles them).
  2. A colon adjacent to whitespace marks a type annotation, so the segment declares one parameter.

The whitespace requirement is the load-bearing part: it keeps Lisp identifiers containing a bare
colon (foo:bar, a legal Scheme symbol) counting as the separate arguments they are. Tested
explicitly, alongside x: int, x: number and const X: Integer.

Verification

  • End-to-end on the corpus: functions_found 16 → 13 for all five languages, and ada's average
    args now identical to python's.
  • All 46 corpus gates still PASS — the manifests assert signal counts, which neither fix
    touches, so there is no corpus rebless in this PR.
  • Full suite 7479 passed; audit_check all clear.
  • New tests fail on origin/main.

Golden master — 147 zero-dep differences, needs a bless

Every one is a cognitive-load column — Cognitive Load Exposure (72), Raw Cognitive Density (58),
cognitive_load (15) and two aggregates — in exactly the five affected languages
(livecode, lua, matlab, ruby, shell). No structural signal count moves at all, which is the
expected shape: the fix changes a denominator, not what is extracted.

#2692 contributes nothing to this diff: ada is not in language-crucible, so its fix is
corpus-visible only. Worth stating plainly rather than leaving a reader to assume the ada change was
exercised here.

Awaiting a bless go-ahead.

Baselines

ruff regenerated — the tool confirmed all 70 findings were pure line-shifts from the comment
inserts. mypy unchanged. One earlier attempt stripped 17 unrelated trailing-whitespace lines in
record_keeper.py; reverted, since fixing baselined backlog is a separate, deliberate PR.

🤖 Generated with Claude Code

…, #2692)

Two extraction-layer defects found while working #2669 Batch D, batched
because they are the same layer and the same question: what counts as a
function, and what counts as one of its arguments.

#2691 -- synthetic slices counted as functions
  The slicer synthesizes a `__global_context__` bucket to hold a file's
  top-level statements, and it was counted in the function population, so
  every per-function average was taken over entries that are not functions.
  On the keyword-rosetta corpus, livecode/lua/matlab/ruby/shell reported 16
  functions against 13 planted -- a ~3/16 dilution of every descriptor, and
  of cog_raw and structural_mass downstream.

  The verdict is stamped once, in detector.py's orphan/duplicate loop -- the
  single place every function already passes through the synthetic-name test
  (#2547's `_is_synthetic_satellite_name`) -- and read by the three
  consumers that compute population statistics: signal_processor's
  per-function aggregates, record_keeper's `function_count` (what the corpus
  reads as `functions_found`), and security_auditor's `avg_func_args`. This
  beats re-deriving the rule in three files, and `FunctionNode` declares the
  key rather than growing an undeclared one.

  The buckets keep existing and keep their signals at file level. Only their
  membership in "how many functions does this file have, and what is the
  average one like" was ever wrong -- `file_mass` still sums every slice's
  impact, and the rows stay in `function_data`.

#2692 -- a typed parameter counted as three arguments
  The comma-free fallback whitespace-split its capture, which is correct for
  the languages it was written for (Scheme's `(define (f arg1 arg2)`, shell
  positionals) but is reached by ANY comma-free capture -- and a single typed
  parameter is exactly that shape. ada's `Env : Integer` scored 3, and
  `X : Integer; Y : Integer` scored 6, since Ada separates parameters with
  semicolons.

  `_count_space_separated_args` applies two ordered discriminators: split on
  `;` (a parameter separator in the Ada/Pascal family that never appears in a
  Lisp/shell list, and commas never reach this branch), then treat a colon
  ADJACENT TO WHITESPACE as a type annotation marking one parameter. The
  whitespace requirement is what keeps Lisp identifiers with a bare colon
  (`foo:bar`, a legal Scheme symbol) counting as the separate arguments they
  are -- that case is tested.

Verified end-to-end on the corpus: functions_found 16 -> 13 for all five
languages, ada's avg args now identical to python's. All 46 corpus gates
still PASS -- the manifests assert signal counts, which neither fix touches.

Golden master: 147 zero-dep differences, every one a cognitive-load column
(Cognitive Load Exposure, Raw Cognitive Density, and the two aggregates) in
exactly the five affected languages. No structural signal count moves at all.
#2692 contributes nothing here: ada is not in language-crucible, so its fix
is corpus-visible only.

Baselines: ruff regenerated, all pure line-shifts from the comment inserts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

squid-protocol and others added 2 commits September 3, 2026 09:05
…l function (#2691)

CI caught this on the first push: tree-sitter accuracy dropped go's
found_functions 897 -> 896 and args_exact_match 822 -> 821. The population
filter reused `_is_synthetic_satellite_name`, which is correct for the
orphan/duplicate checks it was written for (#2547) but also covers "Main" --
so `func main()` stopped being counted as a function.

This repo had already reasoned the distinction out once, in
tests/tools/tree_sitter_accuracy_audit.py's `_SYNTHETIC_GG_FUNC_NAMES`, whose
comment states exactly why "Main" is deliberately NOT excluded (it collides
with an extremely common real function name) and why a `_[Truncated]` suffix
stays countable (it marks a real block that hit EOF unclosed -- a diagnostic
about real code, not a placeholder). `_UNCOUNTABLE_SLICE_NAMES` is that list,
in the engine, with the reasoning restated where the filter lives.

A test now guards the distinction directly, including an assertion that the
broad helper still treats "Main" as synthetic -- so if the two lists ever
converge the test says so instead of silently protecting nothing.

Golden master re-measured after narrowing: 147 -> 133 zero-dep differences,
still every one a cognitive-load column in exactly the five affected
languages, with no go movement. tree-sitter 30/30 OK, tri-comparison 3/3 OK,
suite 7479 passed, corpus fix intact (functions_found 16 -> 13).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#2692)

133 differences in each fixture, regenerated with `crucible_check.py
--update --yes` (user-authorised). Byte-identical to the diff measured and
attributed before the bless.

Every difference is a cognitive-load column -- `Cognitive Load Exposure`
(65), `Raw Cognitive Density` (54), `cognitive_load` (12) and two aggregates
-- confined to the five languages whose files carry top-level code:
livecode, lua, matlab, ruby, shell. No structural signal count moves, and no
per-function descriptor moves outside those five.

The direction is the correction: cognitive load DROPS on these files
(lua/pandoc 33.4 -> 29.21, ruby/rails 30.85 -> 26.68, matlab/eeglab
65.29 -> 62.99) because it is no longer averaged over the slicer's synthetic
`__global_context__` buckets, which were never functions.

#2692 contributes nothing to this diff: ada is not in language-crucible, so
its fix is corpus-visible only (keyword-rosetta's ada avg_func_args 3.0 ->
1.0, matching the planted single parameter).

The earlier over-broad version of this fix moved go as well, by excluding
`func main()` from the function population; tree-sitter accuracy caught it
(897 -> 896) and `_UNCOUNTABLE_SLICE_NAMES` narrowed it. go is absent from
this diff, which is the confirmation that landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

functions_found counts the slicer's synthetic __global_context__ buckets, inflating every per-function descriptor for 5 languages

1 participant