Add heuristic resolver, Chain, and case-insensitive matching - #1
Merged
Conversation
heuristic.Resolver derives conventional source names from a package's PURL type and name for npm, pypi, golang, gem, cargo, composer, hex, and maven, tagged with EvidenceHeuristic. It reads no files and makes no network requests. Chain runs several SurfaceResolvers over the same package and merges their results, so a curated catalog or artifact resolver can be tried first and the naming convention fills whatever it does not cover. ProvidedName.CaseInsensitive folds ASCII case during matching for languages whose module or namespace lookup is itself case-insensitive. The composer heuristic sets it since PHP resolves 'use GuzzleHttp\X' and 'use guzzlehttp\x' identically and no naming rule can recover the canonical spelling from the vendor name alone. SurfaceResolverFunc adapts a plain function to a SurfaceResolver.
There was a problem hiding this comment.
Pull request overview
This PR expands surface-resolution capabilities by adding a heuristic resolver (PURL → conventional source names), a composable Chain resolver to merge multiple resolution strategies, and a ProvidedName.CaseInsensitive flag to support languages with case-insensitive name lookup (notably PHP namespaces).
Changes:
- Add
ProvidedName.CaseInsensitiveand apply it in name matching plus merge/dedup keys. - Introduce
SurfaceResolverFuncandChain(resolvers...)to compose multipleSurfaceResolvers and merge their results. - Add
heuristic.Resolver()with per-ecosystem naming conventions and accompanying tests/docs.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| types.go | Adds ProvidedName.CaseInsensitive to support case-folded matching semantics. |
| match.go | Implements case-insensitive matching behavior in ProvidedName.Matches. |
| import.go | Includes CaseInsensitive in import match deduplication keys. |
| merge.go | Includes CaseInsensitive in provided-name merge keys (but see determinism note in review comments). |
| resolver.go | Adds SurfaceResolverFunc and Chain to combine multiple surface resolvers. |
| resolver_test.go | Adds tests validating chaining behavior (merge, dedupe, error handling, nil/empty). |
| heuristic/heuristic.go | Adds heuristic resolver mapping PURL types to conventional provided names. |
| heuristic/heuristic_test.go | Adds tests for heuristic conventions and evidence/diagnostic behavior. |
| match_test.go | Adds tests for CaseInsensitive matching. |
| README.md | Documents case-insensitive matching, heuristic surfaces, and chaining. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…eterminism The PyPI heuristic comment referenced PEP 503 equivalence of -/_/., but the code only maps hyphens to underscores. That is intentional: dotted distribution names conventionally install as namespace packages with the dot preserved (zope.interface, ruamel.yaml). The comment now states what the code does and a test covers the dotted case. Chain collected each resolver's diagnostics and also passed the full SurfaceResult (including diagnostics) to MergeSurfaceResults, which merged them again. mergeDiagnostics deduplicates so the output was correct, but the work was redundant. Chain now passes only the Surface to the merge and adds diagnostics once. MergeSurfaceResults' sort comparator did not include CaseInsensitive, so two entries differing only in that field could order nondeterministically via map iteration. The comparator now falls through to CaseInsensitive after Separator, sorting the case-sensitive entry first.
andrew
added a commit
to alpha-omega-security/hyrum
that referenced
this pull request
Aug 11, 2026
One tree-sitter-backed indexer replaces the per-ecosystem regex and
text scanners in js.go, python.go, and generic.go. For each source
file it calls outline.Imports to extract import statements, filters
them via provides.ProvidedName.Matches (prefix + separator per
ecosystem, case-folded for PHP), collects the local identifiers each
matching import binds, then calls outline.Refs to trace direct member
accesses on those identifiers. Member accesses now record under the
export or module name rather than whatever local alias the target
chose, so 'import {X as Y}; Y.foo' surfaces as X.foo.
Seed receivers cover ecosystems where the dependency's canonical name
is referenced without an import line: Ruby's Bundler-autoloaded
constant, Rust and Elixir's module identifier, PHP's PSR-4 root, and
Go's implicit package name for an unaliased import.
outline is pinned to a475607 (git-pkgs/outline#30, merged) for Go
qualified_type refs and the Ruby call-with-block method-field fix;
provides to 29fb3d0 (git-pkgs/provides#1) for CaseInsensitive matching.
The strict linter set (gocognit, goconst, gocyclo, maintidx, dupl,
mnd, unparam, ireturn) is now enabled in .golangci.yml so CI enforces
what the previous commit cleaned up; the deleted files were the last
offenders.
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.
heuristic.Resolver()derives conventional source names from a package's PURL type and name alone: npmws→ws(+ anyws/...subpath), PyPIEngine-IO-Parser→engine_io_parser, gemactive_support→ featureactive_supportand constantActiveSupport, cargotokio-util→tokio_util, composer vendor → PSR-4 root, hex → camelised module, maven → group-id package prefix. Every returned name carriesEvidenceHeuristic. Ecosystems without a convention resolve to an empty surface.Chain(resolvers...)runs severalSurfaceResolvers over the same package and merges results viaMergeSurfaceResults, so a curated catalog or artifact resolver can be tried first and the naming convention fills what it does not cover. A resolver error becomes a diagnostic and the chain continues.ProvidedName.CaseInsensitivefolds ASCII case during matching. PHP namespace resolution is case-insensitive at the language level (use GuzzleHttp\Clientanduse guzzlehttp\clientresolve identically), so a heuristic composer→PSR-4 mapping cannot produce one canonical spelling; the composer convention sets this flag.Namestill retains its canonical spelling.SurfaceResolverFuncadapts a plain function to the interface;Chainandheuristic.Resolverreturn the concrete type.The field addition is backward-compatible; existing struct literals and merge/dedup keys carry the new field through.