Skip to content

fix(core-engine): args counts declared parameters, not call sites (objective-c, typescript) - #2786

Merged
squid-protocol merged 1 commit into
mainfrom
fix/2773-args-counts-call-sites
Sep 6, 2026
Merged

fix(core-engine): args counts declared parameters, not call sites (objective-c, typescript)#2786
squid-protocol merged 1 commit into
mainfrom
fix/2773-args-counts-call-sites

Conversation

@squid-protocol

@squid-protocol squid-protocol commented Sep 6, 2026

Copy link
Copy Markdown
Owner

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 happens to make.

The contract is stated in the new docs/args_rule_contract.md, in the shape #2730 established for
api:

args matches the parameters a callable declares.

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 byte
offsets stay aligned). A match whose parenthesised span resolves to formal_parameters /
parameter_list / method_definition is a declaration; one that resolves to arguments /
argument_list / message_expression is a call. That is the rule-17 hand-checkable oracle, and
unlike #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).

  • The colon-selector arm matched every Objective-C message send. [store setVersion:V],
    [list objectAt:i] — a send is lexically identical to the untyped label:name parameter shape
    objective-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) and
    both @interface Anchor:Object superclass colons.
    Now requires a real -/+ method-declaration head (with func_start's own optional macro /
    __attribute__ prefix and an optional return type) — the one context a keyword-message
    signature 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 an args hit.
  • The plain C arm accepted any name(...) followed by {/;. free(conn);, abort();,
    printf("...", x); each scored an argument — 146 of the false hits. Now demands the same
    structural 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 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 (see Claim 15 below). The only real declaration dropped is page_width(), an empty
parameter list, which c/cpp already treat the same way.

typescript — 16844 → 11025 on the crucible, 15 → 13 on the control shells

  • The class-member arm had no declaration anchor at all. ^[ \t]*IDENT(...) is equally the
    shape 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 same
    rule 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);) has
    neither by grammar, and all 125 declarations the anchor would otherwise lose are constructors —
    so constructor is named explicitly rather than readmitting a bare ;, which would bring back
    4906 call statements.
  • 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). 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 curried
    arrow'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.md carries the contract, three corollaries, the fallback-family table for
languages 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:

#2782 groovy +46% the return-type run is {0,3}, so the method arm degenerates to ^IDENT(...)
#2783 apex +38% the return type is optional, same degeneration
#2784 m4 +38% counts $1 references; shell counts the highest position (_args_findall_max_groups)
#2785 haskell an arrow-less :: signature (region :: IORef Int) is a value, not a callable

css counting calc()/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 matlab is in docs/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 to args in the
    output schema, matching what The api rule has no stated contract: 7 languages' own visibility idiom is invisible to it, and 5 others count any public token #2730 did for api.
  • docs/why_gitgalaxy_beats_ast_here.md Claim 15tree-sitter-objc cannot parse
    - keyDown:(NXEvent*)theEvent followed by #ifdef TRY1
    (language-crucible/data/objective-c/worldwideweb/HyperText.m:1426) and loses the region to
    error 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 does
    NOT apply" section — the same parse was the oracle that found GitGalaxy's own 277 false
    positives.

Verification

  • Golden masters blessed: 25 differences, every one of them Function Parameters, on
    typescript and objective-c files only (plus javascript/react/BabelPlugin.ts, a .ts file).
    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. 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's
    args_exact_match holds at 2881/2915 and objective-c's at 153/153. An earlier spelling of the
    arrow-arm gap that excluded ( outright cost 6 curried fp-ts functions here; that is what the
    balanced-paren allowance and its regression test exist for.
  • tri_comparison_chart.py --all --ci — 3 languages, all OK.
  • Scaling sweep over both rules: linear at n=2000…16000 on every adversarial shape (macro-prefix
    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-c and data/typescript expected_signals.json, and re-scoping
deviation_ledger.json's ts-callparen-args: retire its objective-c and typescript arms, keep
apex and groovy and re-point them at #2783 / #2782.

🤖 Generated with Claude Code

@squid-protocol
squid-protocol force-pushed the fix/2773-args-counts-call-sites branch from 7c7d02f to 5b618f3 Compare September 6, 2026 01:16
@squid-protocol squid-protocol changed the title fix(core-engine): args counts declared parameters, not call sites (ob… fix(core-engine): args counts declared parameters, not call sites (objective-c, typescript) Sep 6, 2026
…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
squid-protocol force-pushed the fix/2773-args-counts-call-sites branch from 5b618f3 to 8c7e016 Compare September 6, 2026 01:23
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

@squid-protocol squid-protocol added the rosetta:rebless-owed Intentionally moves keyword-rosetta counts; audit warns, corpus re-blesses after merge label Sep 6, 2026
@squid-protocol
squid-protocol merged commit 0b2e9b2 into main Sep 6, 2026
54 of 56 checks passed
@squid-protocol
squid-protocol deleted the fix/2773-args-counts-call-sites branch September 6, 2026 01:37
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rosetta:rebless-owed Intentionally moves keyword-rosetta counts; audit warns, corpus re-blesses after merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

args counts call-site arguments in objective-c (+62%) and typescript: a coupling signal that scales with how many calls a file makes

1 participant