Skip to content

fix(nav): plain Go method names resolve everywhere; session_start per-agent LSP state and detached HEAD - #559

Merged
atlas-from-plumb merged 14 commits into
mainfrom
atlas/fix-546-nav-orientation
Oct 1, 2026
Merged

atlas-from-plumb merged 14 commits into
mainfrom
atlas/fix-546-nav-orientation

Conversation

@atlas-from-plumb

@atlas-from-plumb atlas-from-plumb commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #546

What changed

  1. Plain Go method names resolve everywhere. gopls names methods (*Recv).Method, so symbol_name: "WroteMtime" failed in find_references and get_definition while read_symbol found it via its tree-sitter fallback. resolveSymbolsByName (shared by every name-taking tool) now matches a plain name against a flat Go method's own name, as it already did for nested methods. A symbol literally named Run beats a method that matches only once its receiver is stripped, so Run stays the function beside (*S).Run (review B1: move_symbol/rename_symbol keep working). Equally good matches ((*A).Run, (*B).Run) are never resolved silently: the read-only tools and hierarchies list each; rename_symbol refuses with proven Recv.Method candidates (line/character fallback where no name proves out); move_symbol refuses with the A/Run, B/Run name_paths, which the topology index resolves (tested). topology_impact picks the centre among same-named symbols by its line span (review N1).
  2. Detached HEAD keeps the git policy. gitHeadLabel returns the branch, or detached at <short sha>. The Branch line and the git-policy section in both the brief and full packets use it.
  3. Per-agent LSP-state lines. The Go LSP: … GOWORK=off, warm-up and diagnostics-mode accessors now take the workspace session_start resolved for the caller. In internal/cli, a workspace other than the connection's root is answered by the server detection assigns it (routingProxy.workspaceTarget: the same key route() gives that workspace's files). The connection's own root keeps the primary path, so single-agent output is unchanged. A per-agent re-pin starts no server, so when the caller's server has not started the packet says so (not "LSP is ready") and names the go.work it will start with GOWORK=off against, decided from disk with the pool's own config/env (review B2/N3: readiness and diagnostics-mode lines are per agent too). daemon_info still reports the connection's server.
  4. Docs. The Go-server GOWORK paragraph moves to its own [lsp.<language>] subsection ("The Go language server and GOWORK"). It now says the decision is made once per server start (kept through a crash restart or a hibernation wake): plumb restart always applies a go.work edit, and a primary server's idle teardown also does; an on-demand server for another root lives until the daemon stops. The [git] env text now runs straight into the project/global composition paragraph. docs/tools.md notes the plain-name and ambiguity rules.

Verification

  • Review round (B1, B2, N1, N2, N3, docs nuance): new tests red first; the per-agent cli test no longer pre-seeds the worktree's server (a seeded variant covers the running case). 12 overlay mutants and 2 more through plumb's mutation_test were all killed.

  • Red first: every new test failed before its fix. The plain-method tests returned "No symbol named"; the detached-HEAD test had no Branch/Git lines; the per-agent cli test failed both ways against connection-level stubs.

  • TestSessionStart_LSPStateIsPerAgent checks both directions. A subagent in a worktree on a main-checkout connection sees GOWORK=off and warming, and does not see the main server's diagnostics: pull. A subagent on the main checkout on a worktree connection sees neither line. The coordinator gets the reverse in each case. TestSessionStartLSPStateWiredPerAgent checks that registerAllTools wires the per-workspace accessors.

  • Mutation: 8 hand mutants via go test -overlay, all killed by real test failures. These were the resolver branch, the cross-file != 1 guard, the detached label, the identity and warming ws arguments, servesConnectionRoot, workspaceTarget and the diag-mode accessor. 2 of them were re-run through plumb's mutation_test on ./internal/cli/: 2 killed, 0 survived.

  • go test ./... (GOWORK=off, GOTMPDIR=.testcache, isolated XDG_DATA_HOME) passed before and after merging origin/main. golangci-lint run ./... reported 0 issues, and make check-size check-brief check-changelog check-changelog-placement passed. No tool description changed (tools/list budget untouched).

🤖 Generated with Claude Code

atlas-from-plumb and others added 9 commits October 1, 2026 11:21
… HEAD and server

find_references and get_definition answered "No symbol named" for a plain
Go method name, because gopls names methods "(*Recv).Method" and the shared
resolver only matched that shape through a dotted query. read_symbol only
found it through its tree-sitter fallback. The resolver now matches a plain
name against a flat Go method's own name, so every tool that takes a symbol
name agrees. A shared name still matches every symbol: the read-only tools
list each, rename_symbol and move_symbol refuse, and topology_impact's
cross-file callers report nothing rather than another receiver's callers.

session_start keyed its Branch line and git-policy section on a branch name,
so a detached HEAD, the usual review-worktree setup, lost both. They now
show "detached at <short sha>" and the policy.

The GOWORK=off, warm-up and diagnostics-mode lines described the
connection's primary language server, so a subagent pinned to a worktree
was not told its server runs with GOWORK=off and one on the main checkout
was told it did. They now ask about the workspace session_start resolved
for the caller.

docs/configuration.md says a go.work edit reaches gopls only after plumb
restart, and the paragraph moves to its own [lsp.<language>] subsection so
it no longer reads as covering the [git] env composition rules.

Fixes #546

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plumb-Session: still-spruce
…nt before its server starts

Review of #559 found two regressions in the first cut.

Matching a plain name against gopls's flat "(*S).Run" made a function
literally named Run ambiguous with that method, so move_symbol (and
rename_symbol) refused a move main performs, and suggested a name_path
form the refusal never spelled out. The resolver now prefers a symbol
carrying the name itself and falls back to receiver-stripped methods only
when none does, so only equally good matches stay ambiguous. move_symbol's
refusal now lists the Receiver/Method name_path for each match, which the
topology index resolves. topology_impact picks the method its node names
by line instead of giving up on a shared name such as Close.

The per-agent session_start lines only worked once the worktree's server
was running, but a per-agent re-pin starts none: a subagent's first
session_start got no GOWORK line and "LSP is ready" for the connection's
server. The packet now asks whether the caller's own server has started,
says when it has not, and names the go.work it will start with GOWORK=off
against, decided from disk the way the pool decides it. The test no longer
pre-seeds the worktree's server; a seeded variant covers the running case.

The docs now say an idle teardown also re-decides GOWORK, and describe
rename_symbol's line/character fallback and move_symbol's name_paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plumb-Session: bright-horse
…neric receivers

For `func (s *S[T]) Run`, move_symbol's refusal offered the name_path
`S[T]/Run`, and passing it moved the wrong method. The Go topology extractor
had no case for an instantiated generic type, so the receiver rendered as `_`
and the node's Qualified came out as `(*_).Run`. topologyNodeByPath then found
no parent matching `S[T]` and silently fell back to the first node named
`Run`, which was another type's method.

Three fixes, root cause first:

- topologyNodeByPath no longer falls back. A Parent/Name path whose parent is
  not evidenced matches nothing; a plain name is unchanged. A parent is
  evidenced by the node's Qualified (the Go extractor) or, for the extractors
  that record a member under its bare name (Python, Java, Rust, Kotlin), by a
  node of that name whose span encloses it, so the fallback still resolves
  Class/method there, and "Other/run" no longer answers with Greeter's run. An
  empty segment matches nothing. resolveSymbolOrFallback says the fallback
  found nothing either when the language server failed to answer, since that
  server's own error ("retry shortly") says nothing about the path.
- typeStr renders IndexExpr and IndexListExpr as the base type, so the method
  is `(*S).Run`, and the hint offers `S/Run` with the type parameters stripped.
- The offered name_paths were honest only through the topology index: gopls
  never nests a method, so findSymbolByPath("A/Run") missed and a server with
  no index wired answered "not found". findSymbolByPath now resolves
  Recv/Method against gopls's flat `(*Recv).Method` symbols, and moveNamePaths
  offers a path only once findSymbolByPath is seen to return exactly that
  match, so a symbol nested two deep gets the generic hint, not a path that
  errors.

The CHANGELOG and docs/tools.md claimed equally good matches are never
resolved silently. The exact tier hides `(*T).Run` behind any symbol literally
named Run (a struct field, an interface method), so both now say so.

Also pins the GOWORK prediction session_start makes for a server that has not
started to what the pool then spawns, across every outcome of the decision.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…actor

The hand-built nodes proved the rule; the Python extractor proves it holds for a
language whose nodes record a member under its bare name. Without containment
the no-fallback change would refuse every Class/method path such a language
resolves today, so the test guards the half of the change that could regress an
unrelated language.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
atlas-from-plumb and others added 3 commits October 1, 2026 19:23
…-resolving it for the doc comment

The round-2 parent rule accepted any enclosing node called by the last parent
segment and checked no other segment. Outer/run answered with the run of a
class nested in Outer, Wrong/Inner/run resolved although there is no Wrong, and
a TypeScript B/Foo/run answered with A's. With a healthy language server it did
real damage: replace_symbol_body with include_doc_comment resolved Outer/run
through the server, then resolved the same path again through the tree-sitter
index to find the doc comment. The two disagreed, the edit began at Inner's doc
comment, and Inner's run was deleted. The rule also refused every Rust
Type/method path, since an impl block is no node and the method's qualified
name is bare.

- topologyNodesByPath (was topologyNodeByPath) returns every match. P1/.../Pn/Name
  now needs Name's direct parent to be Pn, that node's direct parent Pn-1, and so
  on, as findSymbolRecursive does. A direct parent is the innermost enclosing
  named node or the node a containment edge ties the member to; a qualified name
  counts only when it spells out the whole chain. No match, or more than one,
  is refused, and the ambiguity refusal says which lines matched.
- Rust resolves through the extractor's impl-to-type link, for inherent and trait
  impls, when the file declares the type. That needed the edges, so the store gains
  ExtractFileGraph; ExtractFile is unchanged. A package node's edge is no parent
  (Go's package, a file-scoped C# namespace), so p/Run stays refused.
- docCommentStartPreferTopology and topologyDocCommentStart take the symbol the
  tool already resolved and find the node of that name whose span starts on its
  line (column, then kind, tell apart several on one line), else the line-scan
  runs. They never resolve the name_path again.
- The Go receiver forms (*S, (*S), S[T]) are normalised in the topology tier as
  findFlatGoMethod does in the language-server tier, and stripTypeParams no longer
  cuts at a bracket that is never closed.
- CHANGELOG and docs/tools.md stop claiming Rust was covered by containment and
  state exactly what is refused.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Plumb-Session: giant-bison
golimpio
golimpio previously approved these changes Oct 1, 2026

@golimpio golimpio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three review rounds. The topology-fallback blockers that kept recurring were resolved by cutting to strict semantics. A name_path must match its full direct-parent chain, any ambiguity is refused with the candidate lines, and the doc-comment path verifies the already-resolved symbol instead of re-resolving it. Rust resolves via impl edges. Every round-3 repro now refuses or resolves correctly, including the end-to-end data-loss case. Mutants are killed, and a 29-extractor survey resolves every member except three deliberately refused ambiguous pairs.

@golimpio golimpio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after a clean merge of main (#552, #547): the CHANGELOG adds lines with no deletions, the build passes, and the targeted symbol, topology, move and path tests pass locally.

golimpio
golimpio previously approved these changes Oct 1, 2026

@golimpio golimpio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after update-branch.

@golimpio golimpio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after merging main (#548): CHANGELOG additions only; build and targeted session_start/stamp/symbol tests pass locally.

@atlas-from-plumb
atlas-from-plumb merged commit 33640b6 into main Oct 1, 2026
9 checks passed
@golimpio
golimpio deleted the atlas/fix-546-nav-orientation branch October 2, 2026 04:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

find_references plain method names; brief session_start on detached HEAD; per-agent Go LSP line; GOWORK docs

2 participants