grammar-inference-engine/docs/adr/0006-argument-pattern-extraction.md
tobjend e23922a1b7
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat: adaptive multi-assignment clustering; add ADRs 1-10
- Multi-assignment clustering (no greedy 'used' set)
- Adaptive ngram fallback (shrink when (other) > 60%)
- Add docs/adr/ with 10 architecture decision records
- Fix ADR 1 (query modification description)
- Fix ADR 3 (multi-assignment + adaptive shrink)
- Fix ADR 5 (import sort order clarification)
- Fix ADR 6 (remove Kotlin call_suffix references)
- New ADR 9 (adaptive clustering rationale)
- New ADR 10 (universal package mapping via relpath)
2026-07-03 22:58:09 +02:00

2.8 KiB

6. Argument pattern extraction via AST node classification

Date: 2026-07-03

Status: Accepted

Context

A behavioral token like assertEquals tells the LLM that the function is called, but not how. Two codebases both use assertEquals — one writes assertEquals(expected, actual) and the other writes assertEquals(actual, expected) with swapped argument order. An LLM guessing the wrong order writes broken tests.

The highlights.scm captures tell us that a function is called. We need the argument structure — number of arguments, their types, and the common patterns.

Decision

For each behavioral capture node, walk up to its parent call_expression (or equivalent), find the argument list node, and classify each argument by structural role.

Argument classification is language-agnostic:

Classification Matches
lit string, number, boolean, null
var identifiers, names
call nested call expressions, method invocations
lambda lambda expressions, blocks, do-blocks
kwarg keyword/named arguments
expr binary/unary/ternary/operator expressions
template string interpolation, template literals
other anything else (fallback)

Argument list node detection uses a tiered approach:

  1. child_by_field_name("arguments") — works for Python, JS, TS, Java, Go, Ruby, Rust.
  2. Fallback: scan children for argument_list, arguments, template_string (JS tagged templates).

The argument iterator is a simple generic traversal: yield all named children of the arglist node. No per-language special cases. This works for positional args, keyword args, lambdas inside argument lists, and template expressions.

Results are aggregated per cluster into a summary showing min/max/common arg counts and the top argument-type patterns.

Consequences

Positive:

  • Reveals argument ordering conventions: assertEquals: n=2 [lit,var] means expected-first.
  • Reveals calling convention variance: verify: n=0 [] | n=1 [lambda] | n=1 [var] means three styles coexist.
  • Zero per-language branches — generic tiered detection and iteration handles all 10 grammars.

Negative:

  • kwarg detection only covers named arguments, not default values or spread operators.
  • Nested destructuring patterns fall into other bucket — no granularity for complex argument shapes.
  • other is a catch-all that can hide meaningful distinctions we haven't classified yet.

Alternatives Considered

  • Extract raw argument text: Language-agnostic but fragile — variable names change per test, producing high variance and low signal.
  • No argument extraction: The LLM sees assertEquals but doesn't know argument order. Leads to wrong code.
  • Per-language argument extractors: Would be more precise but violate the zero-adapters constraint.