Repository navigation
[TIER 1] Class detection is 0% across apex/csharp/fortran/solidity despite near-perfect function detection #1264
Description
Summary
Four languages (apex, csharp, fortran, solidity) show 0% class detection (found_classes=0) in their respective language-crucible corpora, yet each has excellent function detection (89-100% recall). This pattern suggests a shared root cause rather than four independent bugs -- file as ONE issue to investigate the common cause first.
Related epic: #1261
Per-language measurements
| Language | Files | Real Classes | Found Classes | Recall | Real Functions | Found Functions | Recall |
|---|---|---|---|---|---|---|---|
| apex | 4 | 4 | 0 | 0% | 38 | 38 | 100% |
| csharp | 6 | 19 | 0 | 0% | 558 | 553 | 99% |
| fortran | 6 | 11 | 0 | 0% | 121 | 119 | 98% |
| solidity | 7 | 7 | 0 | 0% | 95 | 85 | 89% |
Data from tests/tree_sitter_accuracy_baseline_<lang>.json files.
Why this pattern is suspicious
- Function detection works on the same files: If the corpus were somehow unsuitable or the language unrepresentable, both would fail. They don't.
- These are recent additions: fortran/apex/solidity were rolled out in PRs feat(ci): roll out C-Family cluster for tree-sitter accuracy audits #1240/feat(ci): roll out Web and Config language cluster for tree-sitter accuracy audits #1242/feat(ci): roll out apex/fortran/makefile/matlab/tcl tree-sitter accuracy tracking #1253 (within the last few days). csharp was earlier but may share the same review/validation pattern.
- Hypothesis: These languages'
class_startregexes may not have been validated against real corpus samples the wayfunc_startregexes were, i.e. this is a process gap (untestedclass_startpatterns across a recent rollout batch) rather than four unrelated bugs.
Action (investigate the common cause first)
-
Before modifying any of the four
class_startregexes independently, determine:- Were the
class_startpatterns validated against real corpus samples when these languages were added? (Check the PRs feat(ci): roll out C-Family cluster for tree-sitter accuracy audits #1240, feat(ci): roll out Web and Config language cluster for tree-sitter accuracy audits #1242, feat(ci): roll out apex/fortran/makefile/matlab/tcl tree-sitter accuracy tracking #1253, and earlier csharp commits) - Do apex/fortran/solidity have a different class-naming convention than JavaScript/Python/Java that would explain why
func_startworks butclass_startdoesn't? - Are the
class_startregexes actually present and enabled for all four, or are they commented-out/missing?
- Were the
-
If it's a process gap: Document what validation was/wasn't done, add it to the
harden-language-extractionskill's checklist, and ensure all future language additions includeclass_startcorpus validation. -
If the regexes are genuinely broken: Each language's
class_startmay need separate investigation, but file SEPARATE sub-issues rather than expanding this one. Do NOT treat this as four independent PRs -- keep them coordinated to avoid the same mistake repeating.
Notes
- Function detection working proves the corpus and measurement tool are sound.
- This pattern (100% function recall, 0% class recall on identical files) is rare enough to suggest something systematic about the
class_startregexes specifically, not the measurement method or the languages.
Activity
squid-protocol commentedon Aug 12, 2026
Root cause found: it's not the four
class_startregexes -- they're fineVerified each of the four languages'
class_startregex inlanguage_standards.pydirectly against sample text: all match correctly in isolation (tested apex/csharp/fortran/solidity patterns standalone with realistic input). So hypothesis #3 in the issue body ("regexes are genuinely broken") is ruled out, and hypothesis #1's specific guess (unvalidatedclass_startpatterns at rollout) is also not quite it -- the patterns themselves are sound.The actual bug:
detector.py's named-entity class extractor never usesclass_startat all.function_data/class_data(the tables the tree-sitter accuracy harness diffs against) are populated from two structurally different code paths:Functions:
detector.pypulls matches fromrules.get("func_start")-- the real per-language regex fromlanguage_standards.py-- see e.g. detector.py:1503-1571.Classes:
detector.pyhas a second, entirely separate "FAST CLASS EXTRACTOR" (detector.py:554-560) that never touchesrules["class_start"]. It's one hardcoded, language-agnostic regex:This is what actually builds the
classeslist that becomesclass_datarows,class_count, and the method→class linkage.class_start's own matches only ever feed the numeric signal count inequations/counts(risk-scoringclass_startfrequency) -- they never reach the named-entity table the accuracy harness reads.So the harness's
found_classesmetric was never testing the four languages' hand-tunedclass_startregexes to begin with -- it's been testing this one hardcoded fallback for every language, the whole time.Confirmed against the real corpus (not just synthetic samples)
Ran
class_patterndirectly againstlanguage-crucible's actual apex/csharp/fortran/solidity files:class--public with sharing class Foo,global class Foo. The pattern's modifier group is a single non-repeating optional slot ((?:export|public|abstract)?), andglobal/with sharingaren't in it at all. 0/0 matches on the corpus.internal sealed class,internal sealed partial class,private sealed class--internal/sealed/partial/privatearen't in the 3-word alternation, and even legal combos likepublic abstract classfail because the slot only fires once. 0/0 matches.MODULE/TYPE ::/SUBMODULE-- none of those keywords exist in the pattern's keyword set (class|struct|interface|trait|enum) at all. Structurally unreachable, not a modifier issue. 0/0 matches.contract(andlibrary) -- neither keyword is in the set either (onlyinterfacehappens to overlap). The pattern did fire 10 times on the corpus, but only by accidentally matching nestedstructdefinitions (ProposalCore,AddressSlot, etc.) -- zero overlap with the 7 real contract/interface/library names ground truth counts, sofound_classesstayed 0 and those 10 became noise inextra_classesinstead.This is bigger than 4 languages
Surveyed all
class_startregexes inlanguage_standards.pyagainst the universal pattern's(?:export|public|abstract)?(?:class|struct|interface|trait|enum)shape. Languages whose real class-equivalent keyword or modifier grammar falls outside that shape -- and would fail the same way if/when they get a tree-sitter baseline -- include at least: go (type X struct), rust (pub struct--pubisn't in the modifier set), ruby (module), perl (package/role), cobol (PROGRAM-ID/CLASS-ID), haskell (data/newtype/type), zig (const X = struct), matlab (classdef), scala/kotlin/swift/dart/groovy (compound modifier stacks like C#/apex), abap, tcl, scheme, ada, livecode. These four just happen to be the ones currently measured (they have tree-sitter baselines); the same silent failure is very likely already present, unmeasured, everywhere else that isn'tpython/javascript-shaped.Recommendation
Don't touch the four
class_startregexes -- they're not the bug. The fix belongs indetector.py's class extractor: replace (or fall back from) the hardcoded universalclass_patternwith the per-languagerules.get("class_start"), mirroring exactly howfunc_startis already consulted at detector.py:1503. That's a single shared code path serving two different purposes today (numeric signal count vs. named-entity extraction) that need to be reconciled -- worth scoping as its own issue given the blast radius above, separate from any per-language follow-ups.Given the size of this (core extraction pipeline, ~20+ potentially-affected languages, both the numeric-signal and named-entity paths need reconciling), I'd suggest this becomes the primary tracked issue for epic #1261's class-detection thread rather than filing four separate per-language regex fixes as originally proposed in this issue's Action item #3.
fix(core): class extractor never consulted per-language class_start, …
chore(tooling): scaffold epic #1295 -- diff script, checklist doc, sk…
chore(tooling): scaffold epic #1295 -- diff script, checklist doc, sk…
fix(core): extend class_start named extraction to go and objective-c (#…
fix(swift): wire swift into class_start named-entity extraction (#1361)
fix(zig): wire zig into class_start named-entity extraction (#1367)
fix(powershell): wire up class_start named extraction, clarify css/ht…
fix(scala): recognize private/protected modifiers on class_start
Fix/powershell css class recall graph (#1401)
docs(readme): tighten AST-vs-signature thesis, add measured tree-sitt…
docs(language-status): add apex.md (#1966)