fix(core-engine): args counts declared parameters, not call sites (objective-c, typescript) - #2786
Merged
Merged
Conversation
squid-protocol
force-pushed
the
fix/2773-args-counts-call-sites
branch
from
September 6, 2026 01:16
7c7d02f to
5b618f3
Compare
…jective-c, typescript) Closes #2773. `args` sat at 73% consistency across the control corpus. The documented low tail is the no-parameter-surface family; the HIGH tail had never been examined, and two languages turned out to be counting the arguments at *call* sites alongside the parameters at declarations -- so their `args` total scaled with how many calls a file makes. Stated the contract (`docs/args_rule_contract.md`, the api_rule_contract.md shape): args matches the parameters a callable declares. Every figure below comes from classifying each regex match against a tree-sitter parse of the SAME code stream the engine scans (prism blanks comments in place, so byte offsets stay aligned): `formal_parameters`/`parameter_list`/`method_definition` = a declaration, `arguments`/`argument_list`/`message_expression` = a call. 101 of 378 hits were declarations (26.7% precision). * The colon-selector arm matched every Objective-C MESSAGE SEND -- a send is lexically identical to the untyped `label:name` parameter shape #1335 added. 120 false hits, plus 3 ternaries and both `@interface Anchor:Object` superclass colons. Now requires a `-`/`+` method-declaration lead, which also closes the string-literal hole the issue asked for a negative test on (prism strips comments, not strings, so `@"status: ok"` really was an args hit). * The plain C arm accepted any `name(...)` followed by `{`/`;`, so `free(conn);`, `abort();` and `printf(...)` all scored an argument. 146 false hits. Now demands the same structural proof c/cpp demand -- the list must open with a type token -- plus a parameter NAME after it, without which `StrAllocCopy(Address, tag);` still reads as typed via the PascalCase typedef fallback all three rules share. After: 100 of 101 hits are declarations (the 101st is a method definition tree-sitter itself mis-parses). The only real declaration dropped is `page_width()` -- an empty parameter list, which c/cpp already treat the same way. * The class-member arm was `^[ \t]*IDENT(...)` with no declaration anchor -- equally the shape of a bare call statement. 6133 call sites against 3026 declarations. `javascript`, the same rule one file over, already anchors this with a `(?=[ \t\n]*\{)` body lookahead; typescript needs `{` or a `:` return type, which keeps 95.8% of declarations and drops 98.5% of calls. A bodyless CONSTRUCTOR overload has neither by grammar, so it is named explicitly rather than by readmitting a bare `;` (which would bring back 4906 calls). * The arrow arm's gap between `)` and `=>` was `[^=;{]*` -- unbounded and newline-crossing, so any parenthesised expression matched if a `=>` turned up later (384 casts, `if` conditions and grouped operands). Bounded to whitespace or a `:`-led return type, which may itself carry one level of balanced parens: a curried arrow's return type IS a function type (`): ((...a: A) => Either<E, B>) =>`), and excluding `(` outright made the arm match the annotation's inner list instead of the real one -- 6 fp-ts functions, caught only by tree_sitter_accuracy_audit (args_exact_match 2881 -> 2875), not by the file-level count. Pinned by its own regression test. Precision 61.3% -> 99.0%, and recall RISES (10326 -> 10910 real declarations): the over-reaching arrow arm used to swallow real parameter lists inside an over-long match. * `docs/args_rule_contract.md`: the contract, three corollaries, the fallback-family table, and the 46-language audit. Four violations found and filed -- #2782 (groovy +46%), #2783 (apex +38%), #2784 (m4 counts `$1` references), #2785 (haskell counts an arrow-less `::` signature). `css` is documented as the one knowingly-approximate fallback. * `how_to_add_a_language.md`: the one-line contract next to `args` in the output schema. * `docs/why_gitgalaxy_beats_ast_here.md` Claim 15: tree-sitter-objc emits an ERROR node at `- keyDown:(NXEvent*)theEvent` followed by `#ifdef TRY1` (worldwideweb/HyperText.m:1426) and no method node covers that line; GitGalaxy reads it correctly. * Golden masters blessed: 25 differences, EVERY one of them `Function Parameters`, on typescript and objective-c files only. Zero off-target languages, zero topological drift. * `rosetta_audit.py`: 46 languages, 2 moved -- objective-c and typescript, both onto the planted 13 exactly. The other 44 are byte-identical. * `tree_sitter_accuracy_audit.py --ci --all`: 30 languages, all OK. typescript's args_exact_match holds at 2881/2915, objective-c's at 153/153. * `tri_comparison_chart.py --all --ci`: 3 languages, all OK. * Scaling sweep over both rules: linear at n=2000..16000 on every adversarial shape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
squid-protocol
force-pushed
the
fix/2773-args-counts-call-sites
branch
from
September 6, 2026 01:23
5b618f3 to
8c7e016
Compare
Contributor
squid-protocol
added a commit
that referenced
this pull request
Sep 6, 2026
…had already settled (#2788) Follow-up to #2773 / #2786. The 46-language audit in `docs/args_rule_contract.md` reported four contract violations. Two of them were already triaged in keyword-rosetta's `deviation_ledger.json`, and checking it first would have avoided re-litigating them. * `haskell` is INSIDE the contract, not narrowly outside it. `haskell-caf-bindings-count-as-functions` (engine-semantic, validated 2026-09-04) reads an arrow-less `::` signature as a CAF, and a Haskell top-level value genuinely is a nullary function. The engine is consistent: `func_start` counts it too and `_args_arrow_count_groups` derives arity 0. The narrowing the audit proposed would have made `args` and `func_start` disagree on the same binding. #2785 closed as not-planned. * `m4`'s FILE-LEVEL count is intended morphology (`m4-parameters-are-use-sites`): a macro names no parameters, so `$1` in the body IS the parameter, and equalising it would mean writing macros that never reference their own argument. What survives is narrower and untouched by that verdict -- `avg_func_args` has no `_args_findall_max_groups`, so `AT_SETUP($1) AT_CHECK($1)` reads arity 2 for a one-parameter macro. #2784 rescoped to that. `groovy` (#2782) and `apex` (#2783) stand unchanged; keyword-rosetta now carries them as `args-call-site-counting-apex-groovy`, split out of `ts-callparen-args` when this fix retired that entry's typescript and objective-c arms. Corollary 3 is reworded to say what the ledger established: at file level a positional reference count is the language's morphology, and it is `avg_func_args` -- an arity -- where counting references instead of distinct positions is wrong. Adds "check the ledger first" to the notes for the next rule, since that is the reusable lesson here. Co-authored-by: Joe Esquibel <squid-protocol@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
squid-protocol
added a commit
that referenced
this pull request
Sep 6, 2026
…ated audit, rule-contract-audit skill (#2811) Phase 1 of docs/contract_roadmap.md (approved 2026-09-06). No engine behaviour change: nothing at scan time imports the new module. - docs/contract_roadmap.md: the assessment (267 gated red cells by cause: 102 echo, ~45 inherency, ~50 extraction, 38 correlation, ~32 scoring), the three-layer diagnosis (stream / count / score), decisions D1-D4, phases 0-5, issue disposition, and the corrections to the first assessment (#2535 closed as not-a-bug: no string shielding exists; the x3 flux lives in spatial_correlation.py, pinned by #2631). - gitgalaxy/standards/signal_contracts.py: one SignalContract per registry rule key (68 entries over 80 keys; 12 helper keys described separately) with kind, unit, contract sentence, status, doc, issue, planted. The stream contract and the count contract as module constants. api and args are `stated` (#2730/#2743, #2773/#2786); the other 66 are `draft` -- the schema comment transcribed verbatim. - tests/signal_contract_audit.py (+ baseline, + workflow beside dead-key-audit): missing-contract / orphan-contract / schema-drift / missing-schema / draft findings, baseline-gated like dead_key_audit.py. --render writes docs/signal_contracts.md. - how_to_add_a_language.md: CRITICAL ENGINE RULE 18 (the stream contract) and a pointer above the OUTPUT SCHEMA making the comment lines the prompt form of the sheet. - .claude/skills/rule-contract-audit: the #2730/#2743 method as a repeatable workflow (read the ledger first; sentence + corollaries; 46-language audit via screen_plant.py + crucible incidence on the Prism code stream; engine PR one layer; corpus plant PR; sheet row -> stated). - docs/ecosystem.md: skills table and the rosetta workflow row (cause family, not language). Co-authored-by: Joe Esquibel <squid-protocol@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2773.
argssat at 73% consistency across the control corpus. The documented low tail is theno-parameter-surface family; the high tail had never been examined, and two languages turned
out to be counting the arguments at call sites alongside the parameters at declarations — so
their
argstotal scaled with how many calls a file happens to make.The contract is stated in the new
docs/args_rule_contract.md, in the shape #2730 established forapi:How the numbers were measured
Every regex match is classified against a tree-sitter parse of the same code stream the engine
scans (
prism.split_streams(...)["code_stream"]— prism blanks comments in place, so byteoffsets stay aligned). A match whose parenthesised span resolves to
formal_parameters/parameter_list/method_definitionis a declaration; one that resolves toarguments/argument_list/message_expressionis a call. That is the rule-17 hand-checkable oracle, andunlike #2753's PyYAML oracle or #2752's CSS walker it generalises to any language in
tree_sitter_language_pack.objective-c — 378 → 101 on the crucible, 21 → 13 on the control shells
Only 101 of 378 hits were declarations (26.7% precision).
[store setVersion:V],[list objectAt:i]— a send is lexically identical to the untypedlabel:nameparameter shapeobjective-c args extraction: untyped keyword-message params undercount to 0, and body content can leak into the count #1335 added. 120 of the 277 false hits, plus 3 ternaries (
cond ? MIN_HEIGHT : maxY) andboth
@interface Anchor:Objectsuperclass colons.Now requires a real
-/+method-declaration head (withfunc_start's own optional macro /__attribute__prefix and an optional return type) — the one context a keyword-messagesignature can appear in and a send never can, since a send always leads with
[.This also closes the string-literal hole the issue asked for a negative test on: prism strips
comments but not strings (prism/detector: string-literal shielding is selective by signal family (if/try/open count inside a literal, eval does not) #2535), so
@"status: ok"really was anargshit.name(...)followed by{/;.free(conn);,abort();,printf("...", x);each scored an argument — 146 of the false hits. Now demands the samestructural proof c/cpp demand (the list must open with a type token) plus a parameter name
after that token, without which
StrAllocCopy(Address, tag);still reads as typed via thePascalCase typedef fallback all three rules share.
After: 100 of 101 hits are declarations — the 101st is a method definition tree-sitter itself
mis-parses (see Claim 15 below). The only real declaration dropped is
page_width(), an emptyparameter list, which c/cpp already treat the same way.
typescript — 16844 → 11025 on the crucible, 15 → 13 on the control shells
^[ \t]*IDENT(...)is equally theshape of a bare call statement: 6133 call sites against 3026 declarations, and
describe(kit);/expect(kit);in the control shell were two of them.javascript— the samerule one file over — already anchors this with a
(?=[ \t\n]*\{)body lookahead; typescript,which also has return-type annotations, needs
{or a:return type. Measured: keeps 95.8%of declarations, drops 98.5% of calls.
A bodyless constructor overload (
constructor(runner: () => void, timeout: number);) hasneither by grammar, and all 125 declarations the anchor would otherwise lose are constructors —
so
constructoris named explicitly rather than readmitting a bare;, which would bring back4906 call statements.
)and=>was[^=;{]*— unbounded and newline-crossing, soany parenthesised expression matched if a
=>turned up later (384 casts,ifconditions andgrouped operands). Only a return-type annotation may legally sit there, so the gap is now
whitespace or a bounded
:-led annotation carrying one level of balanced parens (a curriedarrow's return type is itself a function type —
): ((...a: A) => Either<E, B>) =>).Precision 61.3% → 99.0%, and recall rises (10326 → 10910 real declarations): the
over-reaching arrow arm used to swallow real parameter lists inside an over-long match.
The audit — all 46 languages
docs/args_rule_contract.mdcarries the contract, three corollaries, the fallback-family table forlanguages with no formal parameter list, and a per-language table with both corpora. Four
violations found beyond the two fixed here, each filed with its own evidence:
groovy+46%{0,3}, so the method arm degenerates to^IDENT(...)apex+38%m4+38%$1references;shellcounts the highest position (_args_findall_max_groups)haskell::signature (region :: IORef Int) is a value, not a callablecsscountingcalc()/var()/url()arguments is a call site too, but it is settled and ledgered(
args-no-parameter-surface-morphology) — documented as the one knowingly-approximate fallback,the same way
matlabis indocs/api_rule_contract.md, not re-litigated here.The compliant rules cluster into four anchors, and the doc names them so the next rule does not
have to rediscover them: a declaration keyword (19 languages), a mandatory return type
(java/csharp), a terminator lookahead (javascript/dart, now typescript), or a typed parameter list
(c/cpp, now objc's C arm).
Also in this PR
gitgalaxy/standards/how_to_add_a_language.md— the one-line contract next toargsin theoutput schema, matching what The api rule has no stated contract: 7 languages' own visibility idiom is invisible to it, and 5 others count any
publictoken #2730 did forapi.docs/why_gitgalaxy_beats_ast_here.mdClaim 15 —tree-sitter-objccannot parse- keyDown:(NXEvent*)theEventfollowed by#ifdef TRY1(
language-crucible/data/objective-c/worldwideweb/HyperText.m:1426) and loses the region toerror recovery -- no method node covers that line at all. GitGalaxy
reads it correctly. Logged per
CLAUDE.md's standing instruction, with its own "where this doesNOT apply" section — the same parse was the oracle that found GitGalaxy's own 277 false
positives.
Verification
Function Parameters, ontypescript and objective-c files only (plus
javascript/react/BabelPlugin.ts, a.tsfile).Zero off-target languages, zero topological drift.
rosetta_audit.py— 46 languages, 2 moved:objective-candtypescript, both onto theplanted 13 exactly. The other 44 are byte-identical. That is the mechanical check that the
anchors are right and not merely narrower.
tree_sitter_accuracy_audit.py --ci --all— 30 languages, no regressions. typescript'sargs_exact_matchholds at 2881/2915 and objective-c's at 153/153. An earlier spelling of thearrow-arm gap that excluded
(outright cost 6 curried fp-ts functions here; that is what thebalanced-paren allowance and its regression test exist for.
tri_comparison_chart.py --all --ci— 3 languages, all OK.runs, selector-shaped text with no lead, unterminated parameter lists, return-type gaps that
never reach an arrow).
Corpus pairing (keyword-rosetta, after this merges)
No authoring change is needed — the fix lands both shells on 13 exactly. What is owed is the
re-bless of
data/objective-canddata/typescriptexpected_signals.json, and re-scopingdeviation_ledger.json'sts-callparen-args: retire its objective-c and typescript arms, keepapex and groovy and re-point them at #2783 / #2782.
🤖 Generated with Claude Code