grammar-inference-engine/docs/adr/0006-argument-pattern-extraction.md
tobjend ca7ccb36ff
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
docs: add 8 architecture decision records
ADR 0001: nvim-treesitter highlights.scm as capture source
ADR 0002: language-agnostic method extraction via child_by_field_name
ADR 0003: method-level n-gram clustering before inference
ADR 0004: frequency filter with min_coverage threshold
ADR 0005: import extraction per cluster
ADR 0006: argument pattern extraction via AST node classification
ADR 0007: JSON output for LLM prompt injection
ADR 0008: BEX ensemble for grammar inference
2026-07-03 22:01:35 +02:00

53 lines
2.8 KiB
Markdown

# 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`, `call_suffix` (Kotlin), `template_string` (JS tagged templates).
3. Kotlin special case: `call_suffix` may contain a direct `lambda_expression` child (for `every { ... }` syntax) or a `value_arguments → value_argument` chain (for `func(a, b)` syntax).
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.
- No per-language branches — the tiered arglist detection 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.