perf: Index workspace nodes by name in project_relationships (#13647)
### Description
Projecting the workspace dependency graph looked up every edge's target
node by constructing a `PackageName` (heap `String`) wrapped in a
`PackageNode` just to probe `node_lookup` — **one allocation per
dependency edge** — plus a per-group `name` clone for the source node
and a fresh `seen`/`internal` set per group. dhat flagged
`project_relationships` as a top-10 allocation site.
This builds one `&str -> NodeIndex` index from `node_lookup` up front
and probes it by borrowed identity, and reuses the `seen`/`internal`
buffers across groups (`clear()` instead of realloc). The graph fields
are borrowed disjointly — the index reads `node_lookup` while `add_edge`
mutates `workspace_graph` — so nothing is cloned. Edges and error
semantics (`MissingDescriptor` / `UnknownRelationshipTarget`) are
unchanged.
**Verified with dhat** on a ~1,200-workspace pnpm monorepo
(`dhat::HeapStats` around `PackageGraphBuilder::build`): package graph
construction drops from ~2,128,000 to ~2,100,000 allocations — **~28,300
fewer (~3 MB)**.
### Testing Instructions
- `cargo test -p turborepo-repository` — all 419 tests pass, including
graph-construction tests (the resulting workspace graph is identical).
- `cargo clippy -p turborepo-repository --all-targets` is clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01VGZtfhEfgWhrAFMTMhwTfU
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VGZtfhEfgWhrAFMTMhwTfU)_
Co-authored-by: Claude <noreply@anthropic.com>