Repository navigation
tcl: Mode B slices the parameter list as the function body — every tcl function in every scan records loc 1, complexity 0 #2763
Description
Found by a pass over the red cells in the keyword-rosetta bias variance chart (2026-09-05, engine 3b3568e0, corpus main 6183c4b). tcl is the only language in the corpus whose every function records loc = 1, complexity = 0 and struct_branch = 0. It is not morphology — the slicer is measuring the parameter list and calling it the body.
Mechanism
tcl has no ScopeParsingRegistry entry and its lexical_family is line_exclusive, so _function_slice routes it to Mode B (_slice_by_braces, detector.py:3457). Mode B finds the body by taking the first { at or after the match start:
brace_idx = safe_code.find(opener, start_idx, search_limit) # detector.py:4592A tcl procedure has two brace groups, not one:
proc probe_globals {env_kit} {
global region
::env
}tcl.py's func_start already consumes the parameter group — its optional (?:[ \t\n]+\{...\})? arm — so the match ends after {env_kit}, but the brace search restarts at start_idx. The first { from there is the parameter list, and _find_balanced_end closes on } one character later.
Measured on keyword-rosetta/data/tcl/a.tcl (LANGUAGE_DEFINITIONS['tcl']['rules']['func_start'].finditer):
| match | start_idx |
match.end() |
first { from start_idx |
first { from match.end() |
|---|---|---|---|---|
proc probe_globals {env_kit} |
208 | 236 | 227 (the parameter list) | 237 (the body) |
proc probe_test {kit} |
270 | 291 | 286 | 292 |
proc probe_safety {value} |
330 | 355 | 348 | 356 |
Incidence: 100%, and it is not corpus-specific
galaxyscope --db-only over language-crucible/data/tcl (MacPorts port API + registry, SQLite test suite):
functions: 352
loc <= 1: 352 (100%)
mean loc: 1.00 max loc: 1
complexity > 0: 1
Every real tcl function in the crucible records a one-line body. Control languages on the same scan pass, same commit: python mean 4.54, lua 4.92, perl 5.54.
So for tcl, engine-wide: avg_func_loc, avg_func_complexity, max_func_complexity, func_complexity_gini, func_internal_density and every per-function signal vector are computed over a signature line. function_data.token_mass reads 6–8 for a body that is 20–40 tokens. The file-level rule counts are unaffected (coding_analysis runs over the whole stream), which is why verify_language.py tcl passes 76 assertions while the shape descriptors sit at −100%.
Candidate shape
Anchor the body search after the signature the regex already consumed, as the c/cpp args_sig_end bound below it already does for the argument slice:
body_search_start = match.end() if lang_id == "tcl" else start_idx
brace_idx = safe_code.find(opener, body_search_start, search_limit)match.end() is safe for tcl specifically because func_start's trailing lookahead (?=[ \t\n]*\{|[ \t\n]|$) guarantees the next non-space character is either the body brace or nothing. A proc whose parameter list is written on its own line is covered too — the pattern's [ \t\n] classes already span newlines (the "vertical proc shield" its own comment describes).
The generic form of the same fix — "start the body search at match.end() whenever func_start consumed the parameter list" — is worth considering, but every other Mode B language in the corpus uses (...) for parameters and {...} for the body, so nothing else in the 46 is affected and a tcl-scoped gate needs no re-audit. Strict tests belong in tests/extraction/languages/test_tcl_strict.py: single-line proc, vertical proc, nested proc inside a namespace eval, and a proc whose parameter list carries a braced default value (proc f {{a 1}} {...} — the depth-aware case _args_tcl_pattern_list_groups already exists for).
Blast radius
Golden-master movement is confined to directory groups containing .tcl/.itcl/.tm/.tbc files, and to per-function columns only — loc, complexity, token_mass, the function-scoped signal vector, and the equations a function-scoped dampener changes. Scope the diff check the way #2634 did.
Corpus pairing
keyword-rosetta/data/tcl needs no authoring change — the shells already have three-line bodies. The re-bless moves tcl's manifest per-function values and retires tcl from the function-body-length-morphology ledger entry, which currently reads "tcl 1.0 (−80%, a proc body the slicer captures as a single line)" and files it under intended morphology. It is not morphology; that line is this bug, described accurately and classified wrongly.
Activity