Sdlc/Structural/ReachThroughChain: do not report child sends with arguments or stream and RuboCop AST chains; record parameter roots in held_parameter #1378

Open
opened 2026-10-25 21:23:30 +00:00 by jared · 1 comment
Owner

Problem

Sdlc/Structural/ReachThroughChain is a sweep-only recording cop. It records each chain of two or more named sends from an ivar or lvar root, and it reports the chain when the root is held and the chain is not noise. Its message tells the reader to "hand the child in, or let the parent answer". Three shapes get that message although no held child exists or the child is a value, and one shape records a false fact:

  1. A parameter root records held_ivar: "<parameter>" (site.rb:30). No ivar exists. Detector::ReachThrough::Found#held reads that field to keep the row.
  2. A chain whose child send takes arguments (@client.ask(text, [q]).fetch(:source).choice) is reported as "reaches through @client to its ask". The child is an answer that the caller's own arguments decide, not a part the root holds. The lesson detector/reach-through already says: "The child is a value or needs caller-only arguments: keep."
  3. A stream read or an IO write through a child that is not named stdout or stderr (invocation.stdout.string.strip, @env.out.puts(x)) is not noise. Noise covers only stdout/stderr followed by puts/print/write (noise.rb:27-29). The cop lesson calls a stream chain "not a smell".
  4. In a file that names RuboCop, a chain of RuboCop AST node messages (node.first_argument.value, write.source_range.end_pos) is reported. The cop already treats the same shape as noise for Prism nodes (prism_message?).

Found by the #1325 zero-false-positive audit (docs/plans/class-length-family/sdlc-cop-audit.md). Shapes measured on main 4a0fc41d, 2026-10-26, over every plugins/**/*.rb file (204 offenses) and over the #1317 sweep of os-backlog, os, os-adr, os-aidd-lint, and os-me (31 offenses):

shape all plugins sweep verdict example
parameter root (record field) 72 22 wrong record: held_ivar names a parameter; the offense itself stays (the lesson lists "a method parameter" as held) plugins/os-adr/lib/adr/record/challenge.rb:31 entry.record.id
child send takes arguments 45 8 false positive: no held child plugins/os-me/lib/os_me/answer_source.rb:20 @client.ask(row.text, [question]).fetch(:source).choice; plugins/os-aidd-lint/tests/support/markdown_cop_test_helper.rb:6 cop_class.new(document, config).call
stream read or IO write 15 2 false positive: stream value plugins/os-adr/tests/support/command_invocations.rb:38 invocation.stdout.string.strip; plugins/os-sdlc/eval/ticket-takedown/lib/takedown/controller/run_cli.rb:39 @env.out.puts(report.summary)
RuboCop AST messages in a file that names RuboCop 30 0 false positive: library tree, same as the Prism rule plugins/os-sdlc/lib/os_sdlc/cops/require_inside_code/call.rb:44 node.first_argument.value
held child without arguments, not noise 116 21 kept: the record is true (see Out of scope for the type-dependent part) plugins/os-aidd-lint/lib/aidd_lint/config.rb:64 document.frontmatter.metadata_value('lint-style')

Two sites match two rules, so the removed shapes sum to 90 and the drop is 88.

Approach

Fix by shape in the cop matcher. Never use a config Exclude list or a test-path exclusion.

Rules:

  • A parameter root records held_parameter: <name> and held_ivar: nil. The cop still reports it: smell? reads held_ivar || held_parameter. This keeps ruling R2 (docs/crc/os-sdlc/collaborator-rulings.tsv:83) for every held root.
  • Chain answers if its child send (the first named send after the root) takes arguments. The cop records this as a new fact child_arguments and does not report such a chain. Noise does not change for this rule, so the detector keeps its rows.
  • Noise::CORE_OBJECTS gains StringIO. A chain whose sends after the child are all StringIO or core messages is noise. This covers stream reads and IO writes under any child name.
  • Noise gains a RuboCop rule parallel to prism_message?: in a file whose source names RuboCop, a child that is a public instance method of a RuboCop::AST::Node class (minus Object's methods) is noise.
  • Detector::ReachThrough::Found#held builds its Held from held_parameter when the root is local, so the detector output for parameter roots does not change.

Measured effect (scratch simulation on main 4a0fc41d, the cop patched in process and in the rubocop subprocess): offenses over plugins/**/*.rb go from 204 to 116; the 88 removed offenses are all in the three false-positive shapes above. bin/lint-sweep over the five sweep plugins goes from 31 to 21 ReachThroughChain offenses, and the other sweep cops keep their 246 offenses. plugins/os-sdlc/bin/detector reach-through --target <t> prints the same bytes before and after for all eight targets in .sdlc/project.yaml (os-sdlc has 57 lines; the others print only the header). Without the Found#held change it loses 13 parameter-root rows. With the argument rule in Noise instead of a separate fact it loses 16 rows.

Required work:

  1. In plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/site.rb, record a method-parameter root as Held.new(nil, root, nil). In plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain.rb, make smell? true for held_ivar or held_parameter when the chain is not noise. Update test_a_method_parameter_root_is_held_by_the_parameter_alone (plugins/os-sdlc/tests/cops/reach_through_chain_test.rb:94-98) to expect held_ivar: nil, held_parameter: "bound", and add an assertion that the chain is one offense.
    check: absent held_ivar: "bound" in plugins/os-sdlc/tests/cops/reach_through_chain_test.rb
  2. In plugins/os-sdlc/lib/os_sdlc/detector/reach_through/found.rb, build the detector Held as Held.new(held_parameter, nil, nil) when the fact local is true (the parameter goes in the ivar slot, as today; putting it in the parameter slot makes Parents#through_parameter resolve types and changes the parent column), so that test_a_method_parameter_root_prints_a_row_with_the_parameter_as_parent (plugins/os-sdlc/tests/detector/reach_through_test.rb:86-93) stays green without edits.
    check: present :local in plugins/os-sdlc/lib/os_sdlc/detector/reach_through/found.rb
  3. In plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/chain.rb, answer if the child send takes arguments. The child send is the call that child names (the first of named_calls, so a bare reader root is skipped). A block pass (&:name) is an argument; a literal block alone (@target.items { 1 }.first) is not; &. is the same as .. Record it as the fact child_arguments and do not report a chain where it is true. Update the exact facts hash in test_a_held_ivar_chain_records_its_chain_facts (reach_through_chain_test.rb:27-33) with child_arguments: false. Add must-pass fixtures (@client.ask(text).fetch(:source).choice, cop_class.new(document).call with cop_class a parameter) and keep the must-fire fixture @target.test.command.
    check: present child_arguments in plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain.rb
  4. In plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/noise.rb, add StringIO to CORE_OBJECTS. Add must-pass fixtures invocation.stdout.string.strip (parameter root) and @env.out.puts(1) (held ivar) to reach_through_chain_test.rb.
    check: present StringIO in plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/noise.rb
  5. In noise.rb, add the RuboCop AST message rule parallel to prism_message?, gated on a file whose source names RuboCop. Add a test parallel to test_prism_node_messages_are_noise_only_in_a_file_that_names_prism with node.first_argument.value.
    check: present RuboCop::AST in plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/noise.rb
  6. In the lesson plugins/os-sdlc/lint-lessons/sdlc-structural-reach-through-chain.yml, state in signal that a chain whose child takes arguments is not an offense, and that noise includes a StringIO read or IO write and RuboCop AST messages.
    check: present takes arguments in plugins/os-sdlc/lint-lessons/sdlc-structural-reach-through-chain.yml
  7. Add one History row for this change to docs/implementation-status/os-sdlc.md.
    check: present #1378 in docs/implementation-status/os-sdlc.md

Acceptance criteria

  1. Given def run(bound, name: nil) = bound.test.command, when the cop runs, then it records held_ivar: nil and held_parameter: "bound" and reports one offense.
  2. Given a held @client with @client.ask(text).fetch(:source).choice, or a parameter cop_class with cop_class.new(document).call, when the cop runs, then it records the chain with child_arguments: true and reports no offense.
  3. Given a parameter invocation with invocation.stdout.string.strip, or a held @env with @env.out.puts(1), when the cop runs, then it records noise: true and reports no offense.
  4. Given def run(node) = node.first_argument.value in a file that names RuboCop, when the cop runs, then it records noise: true; in a file that does not name RuboCop, it still reports the chain.
  5. Given the held ivar chain @target.test.command, when the cop runs, then it reports one offense, as today.
  6. Given plugins/os-sdlc/bin/detector reach-through --target <t> for every target in .sdlc/project.yaml, when it runs after the change, then its stdout is byte-identical to its stdout on the base commit.
  7. Given plugins/os-sdlc/bin/lint-sweep plugins/<p> for os-backlog, os, os-adr, os-aidd-lint, and os-me, when it runs, then no Sdlc/Structural/ReachThroughChain offense has a child send with arguments or a stream child (31 offenses on 4a0fc41d become 21).

Owner sketch

  • RuboCop::Cop::Sdlc::Structural::ReachThroughChain (existing) owns the offense decision in smell?. ReachThroughChain::Site (existing) owns what holds the root. ReachThroughChain::Chain (existing) owns the send list and answers child_arguments?. ReachThroughChain::Noise (existing) owns the noise rules. Detector::ReachThrough::Found (existing) maps the facts to the detector's Held. No new object.

In scope / out of scope

  • In scope: the four rules above, their tests, the two pinning tests named in required work 1 and 3, the Found#held mapping, and the lesson signal.
  • Out of scope (cut on 2026-10-26, no site in plugins/): a constructor child without arguments (klass.new.call); 0 offenses remain with .new after the argument rule.
  • Out of scope (type-dependent, decision memo on this ticket): 18 of the 21 remaining sweep offenses read through a Struct or Data value (section.heading.text, entry.record.id, labels.source.source) or a gem client (repo.labels.list). The cop has no type for the root, so no syntax rule separates them from a collaborator with behavior. The 3 others (document.frontmatter.*) are true positives. Sequel calls such as @dispatch.refresh.step_results are in this group.
  • Out of scope: removing the now-redundant stream_write? (StringIO covers it); the other detector code.

Blocking edges

None. One implement run. Target: os-sdlc (/os-sdlc:implement 1378 --target os-sdlc).

Origin

  • Trigger: #1325 zero-false-positive audit of the Sdlc cops
  • Improvised this session: none
  • Chain: false positive ← Noise covers only stream writes; site field mislabels parameters ← DESIGN (plugins/os-sdlc/lib/os_sdlc/cops/reach_through_chain.rb)
  • Root candidate: none found; searched ReachThroughChain
  • Where: plugins/os-sdlc/lib/os_sdlc/cops/reach_through_chain.rb
  • Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl

VERDICT: CREATE

## Problem `Sdlc/Structural/ReachThroughChain` is a sweep-only recording cop. It records each chain of two or more named sends from an ivar or lvar root, and it reports the chain when the root is held and the chain is not noise. Its message tells the reader to "hand the child in, or let the parent answer". Three shapes get that message although no held child exists or the child is a value, and one shape records a false fact: 1. A parameter root records `held_ivar: "<parameter>"` (`site.rb:30`). No ivar exists. `Detector::ReachThrough::Found#held` reads that field to keep the row. 2. A chain whose child send takes arguments (`@client.ask(text, [q]).fetch(:source).choice`) is reported as "reaches through `@client` to its `ask`". The child is an answer that the caller's own arguments decide, not a part the root holds. The lesson `detector/reach-through` already says: "The child is a value or needs caller-only arguments: keep." 3. A stream read or an IO write through a child that is not named `stdout` or `stderr` (`invocation.stdout.string.strip`, `@env.out.puts(x)`) is not noise. `Noise` covers only `stdout`/`stderr` followed by `puts`/`print`/`write` (`noise.rb:27-29`). The cop lesson calls a stream chain "not a smell". 4. In a file that names `RuboCop`, a chain of RuboCop AST node messages (`node.first_argument.value`, `write.source_range.end_pos`) is reported. The cop already treats the same shape as noise for Prism nodes (`prism_message?`). Found by the #1325 zero-false-positive audit (docs/plans/class-length-family/sdlc-cop-audit.md). Shapes measured on main 4a0fc41d, 2026-10-26, over every `plugins/**/*.rb` file (204 offenses) and over the #1317 sweep of os-backlog, os, os-adr, os-aidd-lint, and os-me (31 offenses): | shape | all plugins | sweep | verdict | example | |---|---|---|---|---| | parameter root (record field) | 72 | 22 | wrong record: `held_ivar` names a parameter; the offense itself stays (the lesson lists "a method parameter" as held) | `plugins/os-adr/lib/adr/record/challenge.rb:31` `entry.record.id` | | child send takes arguments | 45 | 8 | false positive: no held child | `plugins/os-me/lib/os_me/answer_source.rb:20` `@client.ask(row.text, [question]).fetch(:source).choice`; `plugins/os-aidd-lint/tests/support/markdown_cop_test_helper.rb:6` `cop_class.new(document, config).call` | | stream read or IO write | 15 | 2 | false positive: stream value | `plugins/os-adr/tests/support/command_invocations.rb:38` `invocation.stdout.string.strip`; `plugins/os-sdlc/eval/ticket-takedown/lib/takedown/controller/run_cli.rb:39` `@env.out.puts(report.summary)` | | RuboCop AST messages in a file that names RuboCop | 30 | 0 | false positive: library tree, same as the Prism rule | `plugins/os-sdlc/lib/os_sdlc/cops/require_inside_code/call.rb:44` `node.first_argument.value` | | held child without arguments, not noise | 116 | 21 | kept: the record is true (see Out of scope for the type-dependent part) | `plugins/os-aidd-lint/lib/aidd_lint/config.rb:64` `document.frontmatter.metadata_value('lint-style')` | Two sites match two rules, so the removed shapes sum to 90 and the drop is 88. ## Approach Fix by shape in the cop matcher. Never use a config Exclude list or a test-path exclusion. Rules: - A parameter root records `held_parameter: <name>` and `held_ivar: nil`. The cop still reports it: `smell?` reads `held_ivar || held_parameter`. This keeps ruling R2 (docs/crc/os-sdlc/collaborator-rulings.tsv:83) for every held root. - `Chain` answers if its child send (the first named send after the root) takes arguments. The cop records this as a new fact `child_arguments` and does not report such a chain. `Noise` does not change for this rule, so the detector keeps its rows. - `Noise::CORE_OBJECTS` gains `StringIO`. A chain whose sends after the child are all `StringIO` or core messages is noise. This covers stream reads and IO writes under any child name. - `Noise` gains a RuboCop rule parallel to `prism_message?`: in a file whose source names `RuboCop`, a child that is a public instance method of a `RuboCop::AST::Node` class (minus `Object`'s methods) is noise. - `Detector::ReachThrough::Found#held` builds its `Held` from `held_parameter` when the root is local, so the detector output for parameter roots does not change. Measured effect (scratch simulation on main 4a0fc41d, the cop patched in process and in the rubocop subprocess): offenses over `plugins/**/*.rb` go from 204 to 116; the 88 removed offenses are all in the three false-positive shapes above. `bin/lint-sweep` over the five sweep plugins goes from 31 to 21 ReachThroughChain offenses, and the other sweep cops keep their 246 offenses. `plugins/os-sdlc/bin/detector reach-through --target <t>` prints the same bytes before and after for all eight targets in `.sdlc/project.yaml` (os-sdlc has 57 lines; the others print only the header). Without the `Found#held` change it loses 13 parameter-root rows. With the argument rule in `Noise` instead of a separate fact it loses 16 rows. Required work: 1. In `plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/site.rb`, record a method-parameter root as `Held.new(nil, root, nil)`. In `plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain.rb`, make `smell?` true for `held_ivar` or `held_parameter` when the chain is not noise. Update `test_a_method_parameter_root_is_held_by_the_parameter_alone` (`plugins/os-sdlc/tests/cops/reach_through_chain_test.rb:94-98`) to expect `held_ivar: nil, held_parameter: "bound"`, and add an assertion that the chain is one offense. check: absent `held_ivar: "bound"` in `plugins/os-sdlc/tests/cops/reach_through_chain_test.rb` 2. In `plugins/os-sdlc/lib/os_sdlc/detector/reach_through/found.rb`, build the detector `Held` as `Held.new(held_parameter, nil, nil)` when the fact `local` is true (the parameter goes in the `ivar` slot, as today; putting it in the `parameter` slot makes `Parents#through_parameter` resolve types and changes the parent column), so that `test_a_method_parameter_root_prints_a_row_with_the_parameter_as_parent` (`plugins/os-sdlc/tests/detector/reach_through_test.rb:86-93`) stays green without edits. check: present `:local` in `plugins/os-sdlc/lib/os_sdlc/detector/reach_through/found.rb` 3. In `plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/chain.rb`, answer if the child send takes arguments. The child send is the call that `child` names (the first of `named_calls`, so a bare reader root is skipped). A block pass (`&:name`) is an argument; a literal block alone (`@target.items { 1 }.first`) is not; `&.` is the same as `.`. Record it as the fact `child_arguments` and do not report a chain where it is true. Update the exact facts hash in `test_a_held_ivar_chain_records_its_chain_facts` (`reach_through_chain_test.rb:27-33`) with `child_arguments: false`. Add must-pass fixtures (`@client.ask(text).fetch(:source).choice`, `cop_class.new(document).call` with `cop_class` a parameter) and keep the must-fire fixture `@target.test.command`. check: present `child_arguments` in `plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain.rb` 4. In `plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/noise.rb`, add `StringIO` to `CORE_OBJECTS`. Add must-pass fixtures `invocation.stdout.string.strip` (parameter root) and `@env.out.puts(1)` (held ivar) to `reach_through_chain_test.rb`. check: present `StringIO` in `plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/noise.rb` 5. In `noise.rb`, add the RuboCop AST message rule parallel to `prism_message?`, gated on a file whose source names `RuboCop`. Add a test parallel to `test_prism_node_messages_are_noise_only_in_a_file_that_names_prism` with `node.first_argument.value`. check: present `RuboCop::AST` in `plugins/os-sdlc/lib/os_sdlc/cops/structural/reach_through_chain/noise.rb` 6. In the lesson `plugins/os-sdlc/lint-lessons/sdlc-structural-reach-through-chain.yml`, state in `signal` that a chain whose child takes arguments is not an offense, and that noise includes a StringIO read or IO write and RuboCop AST messages. check: present `takes arguments` in `plugins/os-sdlc/lint-lessons/sdlc-structural-reach-through-chain.yml` 7. Add one History row for this change to `docs/implementation-status/os-sdlc.md`. check: present `#1378` in `docs/implementation-status/os-sdlc.md` ## Acceptance criteria 1. Given `def run(bound, name: nil) = bound.test.command`, when the cop runs, then it records `held_ivar: nil` and `held_parameter: "bound"` and reports one offense. 2. Given a held `@client` with `@client.ask(text).fetch(:source).choice`, or a parameter `cop_class` with `cop_class.new(document).call`, when the cop runs, then it records the chain with `child_arguments: true` and reports no offense. 3. Given a parameter `invocation` with `invocation.stdout.string.strip`, or a held `@env` with `@env.out.puts(1)`, when the cop runs, then it records `noise: true` and reports no offense. 4. Given `def run(node) = node.first_argument.value` in a file that names `RuboCop`, when the cop runs, then it records `noise: true`; in a file that does not name `RuboCop`, it still reports the chain. 5. Given the held ivar chain `@target.test.command`, when the cop runs, then it reports one offense, as today. 6. Given `plugins/os-sdlc/bin/detector reach-through --target <t>` for every target in `.sdlc/project.yaml`, when it runs after the change, then its stdout is byte-identical to its stdout on the base commit. 7. Given `plugins/os-sdlc/bin/lint-sweep plugins/<p>` for os-backlog, os, os-adr, os-aidd-lint, and os-me, when it runs, then no Sdlc/Structural/ReachThroughChain offense has a child send with arguments or a stream child (31 offenses on 4a0fc41d become 21). ## Owner sketch - `RuboCop::Cop::Sdlc::Structural::ReachThroughChain` (existing) owns the offense decision in `smell?`. `ReachThroughChain::Site` (existing) owns what holds the root. `ReachThroughChain::Chain` (existing) owns the send list and answers `child_arguments?`. `ReachThroughChain::Noise` (existing) owns the noise rules. `Detector::ReachThrough::Found` (existing) maps the facts to the detector's `Held`. No new object. ## In scope / out of scope - In scope: the four rules above, their tests, the two pinning tests named in required work 1 and 3, the `Found#held` mapping, and the lesson signal. - Out of scope (cut on 2026-10-26, no site in `plugins/`): a constructor child without arguments (`klass.new.call`); 0 offenses remain with `.new` after the argument rule. - Out of scope (type-dependent, decision memo on this ticket): 18 of the 21 remaining sweep offenses read through a Struct or Data value (`section.heading.text`, `entry.record.id`, `labels.source.source`) or a gem client (`repo.labels.list`). The cop has no type for the root, so no syntax rule separates them from a collaborator with behavior. The 3 others (`document.frontmatter.*`) are true positives. Sequel calls such as `@dispatch.refresh.step_results` are in this group. - Out of scope: removing the now-redundant `stream_write?` (`StringIO` covers it); the other detector code. ## Blocking edges None. One implement run. Target: `os-sdlc` (`/os-sdlc:implement 1378 --target os-sdlc`). ## Origin - Trigger: #1325 zero-false-positive audit of the Sdlc cops - Improvised this session: none - Chain: false positive ← Noise covers only stream writes; site field mislabels parameters ← DESIGN (plugins/os-sdlc/lib/os_sdlc/cops/reach_through_chain.rb) - Root candidate: none found; searched ReachThroughChain - Where: plugins/os-sdlc/lib/os_sdlc/cops/reach_through_chain.rb - Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl VERDICT: CREATE
Author
Owner

Decision memo (AFK, 2026-10-26): what to do with the type-dependent ReachThroughChain offenses that remain after this ticket.

After #1378, 21 sweep offenses remain. 3 are true positives (document.frontmatter.metadata_value(...), Document is a class with behavior). 18 read through a Struct or Data value or a gem client, which the lessons call "not a smell":

A (default): keep them as offenses. The cop has no type for the root; its message already says "when the root is a collaborator with behavior", and the sweep is a judgment-tier pass.
section.heading.text # Section = Struct.new(:heading, ...) -> offense stays, reader decides
B: report only roots whose type the cop can see as a class with behavior (for example @x = Foo.new with Foo a class, not Struct/Data). Parameter and reader roots never report.
def run(section) = section.heading.text # no offense
@target = Project::Target.new(x); @target.test.command # offense

Recommendation: A for this ticket, confidence 0.6. Deciding reason: no syntax rule separates a Struct parameter from a collaborator parameter, and B drops the 3 true positives too (all are parameter roots). A follow-up can move the type judgment to bin/detector reach-through, which already resolves parent types. Ticket is written on default A.

Decision memo (AFK, 2026-10-26): what to do with the type-dependent ReachThroughChain offenses that remain after this ticket. After #1378, 21 sweep offenses remain. 3 are true positives (`document.frontmatter.metadata_value(...)`, Document is a class with behavior). 18 read through a Struct or Data value or a gem client, which the lessons call "not a smell": A (default): keep them as offenses. The cop has no type for the root; its message already says "when the root is a collaborator with behavior", and the sweep is a judgment-tier pass. section.heading.text # Section = Struct.new(:heading, ...) -> offense stays, reader decides B: report only roots whose type the cop can see as a class with behavior (for example `@x = Foo.new` with Foo a class, not Struct/Data). Parameter and reader roots never report. def run(section) = section.heading.text # no offense @target = Project::Target.new(x); @target.test.command # offense Recommendation: A for this ticket, confidence 0.6. Deciding reason: no syntax rule separates a Struct parameter from a collaborator parameter, and B drops the 3 true positives too (all are parameter roots). A follow-up can move the type judgment to `bin/detector reach-through`, which already resolves parent types. Ticket is written on default A.
jared changed title from Sdlc/Structural/ReachThroughChain: treat stream reads as noise and record parameter roots in the right field to Sdlc/Structural/ReachThroughChain: do not report child sends with arguments or stream and RuboCop AST chains; record parameter roots in held_parameter 2026-10-26 15:26:42 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1378
No description provided.