grammar-inference-engine/experiments/RESULTS.md
tobjend 36f9e9173d
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
docs: update experiment log, results, and handover for Round 19-20
2026-07-13 01:18:32 +02:00

9.8 KiB
Raw Blame History

Experiment Results — Context Strategies + Reduce

Date: 2026-07-12

Codebases

Codebase Files Methods Language
RAGSAK 462 1594 Kotlin
Flask 24 1391 Python

RAGSAK Results

Strategy Contexts SOREs Coverage Notes
Baseline (package) 116 2 0.6% Structural — useless
File path k=1 43 2 1.1% Structural — no better
File path k=2 54 2 1.1% Same
File path k=3 54 2 1.1% Same
First 1 symbol 134 20 4.6% Behavioral — 10× better
First 2 symbols 141 39 9.8% Behavioral — 20× better
First 3 symbols 117 47 12.0% Winner
Two-dim (p1,s1) 151 24 6.3% Hybrid — worse
Two-dim (p1,s2) 119 33 8.7% Hybrid — worse
Two-dim (p2,s1) 146 24 6.3% Hybrid — worse
Two-dim (p2,s2) 117 33 8.7% Hybrid — worse
Return type heuristic 5 0 0.0% Useless

Reduce Results (RAGSAK)

Base Threshold Merges Coverage Notes
k=1 0.05-0.4 0 4.6% No merges — contexts too distinct
k=2 0.05-0.2 0 9.8% No merges
k=2 0.3 1 9.6% Merged (JobStatus, every) with (JobStatus, now)
k=2 0.4 2 9.5% Merged 2 pairs
k=3 0.05-0.2 0 12.0% No merges
k=3 0.3 1 11.7% Merged (JobStatus, every, getJobStatus) with (JobStatus, now, minusMinutes)
k=3 0.4 1 11.7% Same merge

Flask Results

Strategy Contexts SOREs Coverage Notes
Baseline (package) 9 0 0.0% Structural — useless
File path k=1 7 0 0.0% Same
File path k=2 9 0 0.0% Same
File path k=3 9 0 0.0% Same
First 1 symbol 53 2 1.4% Behavioral — only 2 SOREs
First 2 symbols 96 18 7.3% Behavioral — 15× better
First 3 symbols 85 21 10.7% Winner
Two-dim (p1,s1) 68 5 2.2% Hybrid — worse
Two-dim (p1,s2) 96 22 10.1% Close to behavioral
Two-dim (p2,s1) 69 5 2.2% Worse
Two-dim (p2,s2) 93 19 9.5% Close to behavioral
Return type heuristic 3 0 0.0% Useless

Reduce Results (Flask)

Base Threshold Merges Coverage Notes
k=1 0.05-0.4 0 1.4% No merges
k=2 0.05-0.2 0 7.3% No merges
k=2 0.3-0.4 1 7.3% Merged (def, boolean) with (def, is_boolean)
k=3 0.05-0.2 0 10.7% No merges
k=3 0.3-0.4 1 10.7% Merged (def, boolean, return) with (def, is_boolean, return)

Cross-Codebase Comparison

Metric RAGSAK Flask
Best strategy First 3 symbols First 3 symbols
Best coverage 12.0% 10.7%
SOREs (best) 47 21
Reduce merges (ε=0.3) 1 1
Reduce impact on coverage -0.3% 0%

Key Findings

1. Behavioral grouping wins consistently

  • Both codebases: First-k-symbols beats all other strategies
  • Structural (file path) is useless — doesn't predict behavioral similarity
  • Hybrid (2D) is worse than pure behavioral

2. Coverage ceiling is ~10-12%

  • RAGSAK: 12.0% with 47 SOREs
  • Flask: 10.7% with 21 SOREs
  • Most methods have unique call patterns — they don't share prefixes

3. Reduce has minimal impact

  • Very few merges at any threshold (0-2 per codebase)
  • Support-weighted distance is conservative — requires very similar SOAs
  • Merges that do happen don't improve coverage
  • Reason: contexts created by first-k-symbols are already quite distinct

4. The few Reduce merges are meaningful

  • RAGSAK: (JobStatus, every, getJobStatus)(JobStatus, now, minusMinutes) — same polling pattern, different initial call
  • Flask: (def, boolean)(def, is_boolean) — same type-checking pattern, different method name

5. Flask is harder than RAGSAK

  • Flask has fewer files (24 vs 462) but similar methods (1391 vs 1594)
  • Flask has more unique methods per package — less repetition
  • Flask SOREs are shorter/simpler — less compressible patterns

Round 5: Cross-Package Exact Matches

Hypothesis: Some call sequences appear verbatim in multiple packages. These are the real cross-package conventions.

Method: Group all sequences by exact tuple match, count packages per sequence.

Result: 38 exact cross-package sequences in RAGSAK. Most trivial:

  • ('clearAllMocks',) — 4 packages (test teardown)
  • ('Builder',) — 4 packages (builder pattern)
  • ('Any',) — 4 packages (Kotlin type)

Interesting ones:

  • ('assumeTrue', 'isDockerAvailable', 'start', 'pullAndWarmup') — 4 pkgs (Docker setup)
  • ('isNullOrBlank', 'error', 'error') — 3 pkgs (null check → error)
  • ('sortedBy', 'map', 'toDescriptor') — 3 pkgs (data pipeline)
  • ('ObjectMapper', 'findAndRegisterModules') — 2 pkgs (Jackson config)

Verdict: Exact matches too rare and mostly trivial. The real cross-package patterns are structural, not textual — "null check → error" appears with different method names in different packages.

Summary of Failed/Dismissed Approaches

Approach Why it failed
Per-package inference Too sparse (1-3 seqs/package)
Reduce algorithm Wrong abstraction level — merges states within one automaton, not across packages
Cross-package grouping by first symbol 4.6% / 1.4% coverage — most groups skipped
Exact cross-package matches 38 sequences, mostly trivial single-call
MDL vs Language Size scoring Scoring isn't the bottleneck — pattern extraction is

What Actually Works

  • Behavioral grouping (first 3 symbols) — 12% / 10.7% coverage, consistent across codebases
  • Calling context as prefix — the right signal, but package-specific
  • GBNF conversion — correct implementation, but input patterns too specific

Next: Structural Coarsening + Cross-Package Detection

See EXPERIMENT_LOG.md for full reasoning and experiment design.

Core idea: collapse method names → categories using tree-sitter capture names. Converts textual sequences into structural shapes that repeat across packages.

('isNullOrBlank', 'error', 'error')     →  (CALL, ERROR, ERROR)
('raise', 'ValueError', 'ValueError')   →  (CALL, ERROR, ERROR)

Files Generated

  • experiments/results/ragsak_summary.json — RAGSAK metrics
  • experiments/results/flask_summary.json — Flask metrics
  • experiments/context_eval.py — experiment runner (supports multiple codebases)
  • experiments/EXPERIMENT_LOG.md — full experiment history and next steps
  • bex/reduce.py — Algorithm 4 (TODS 2010) implementation
  • bex/gbnf.py — SORE → GBNF converter
  • tests/test_reduce.py — 24 tests for Reduce
  • tests/test_gbnf.py — 15 tests for GBNF converter

Round 20: AST Pipeline + Scoring Fixes (2026-07-13)

Commit range: ea6cac58b3a454

Codebases

Codebase Files Methods Language
RAGSAK 462 1609 Kotlin
FastAPI Python
Zod TypeScript

Scoring: Language Size over MDL (ADR-13)

Abandoned MDL scoring — it rewards short expressions, so generic info+ beat specific a.b.c.d.e+ (21% vs 98% success in Bex paper). Language Size (lang_size_score) chosen.

Metric Bag grammar Structured grammar
lang_size_score 9975 20
mdl_score 10^12 (clamped) 10^12 (clamped)

Final Defaults

Parameter Before After
decompose False True
max_seq_length 5 4
idregex_refine False True
_COUNT_CAP 10^12 10^30

Results

Codebase Grammars Pure Bags Structured Bag %
RAGSAK (v4) 126 6 120 4.8%
FastAPI (v3) 143 26 117 18.2%
Zod (v3) 23 5 18 21.7%

Quality breakdown: ~85% of grammars across codebases remain orderless bags (A|B|C)+. The ~15% that are structured represent real sequential flows:

  • Web controller tests: post→jsonPath→isEqualTo→exchange→expectStatus
  • API client patterns: request→header→send→statusCode→jsonPath
  • Builder chains: builder→field→value→build→validate

iDRegEx Findings

iDRegEx does NOT help at small scale. On 3-method groups, iDRegEx achieves only 3.8× tighter (below the 10× gate threshold). The gate correctly rejects it.

Group size CRX lang_size iDRegEx lang_size Ratio
3 methods 15 4 3.8×
4 methods (storage) 91× (outlier)

Bags survive because:

  1. CRX emits one grammar deterministically (no alternative to compare)
  2. lang_size_score only ranks between algorithms, not within CRX's own output
  3. iDRegEx is too slow for large groups (200s+ timeout on 2036m FastAPI tests)

Key Insight

The grammar inference pipeline is fundamentally limited by the input: if methods in a package don't share a sequential calling pattern, no algorithm can find one. The ~15% structured grammars represent genuinely reusable patterns; the ~85% bags represent packages with diverse, unrelated methods grouped only by directory proximity.

Files

  • bex/grammar.py: AST nodes, _count_concat memoization, _COUNT_CAP = 10^30
  • bex/crx.py: CRX algorithm (AST-based)
  • bex/mdl.py: lang_size_score, model_cost, data_cost
  • bex/idregex.py: iDRegEx algorithm
  • bex/decompose.py: sequence decomposition
  • bex/tag_preprocessor/analyze.py: pipeline orchestration, all defaults
  • experiments/results/round20_ast_verify/: full experiment data (v2/v3/v4)