Problem
mcp_hints.py (added in #144, extended in #146 hints-v2) classifies edge resolution strategies as fuzzy (emit a road-sign hint to the agent) or primary (silent). The current design uses an explicit FUZZY_STRATEGY_SET:
FUZZY_STRATEGY_SET = frozenset({
"layer_c_source",
"layer_b_fqn",
"phantom",
"chained_receiver",
"overload_ambiguous",
"implicit_super",
})
Anything not in this set is silently treated as primary. The brownfield pipeline (build_ast_graph.py, graph_enrich.py) emits strategy strings as plain literals. Nothing today prevents drift between the pipeline and the hint catalog.
Concrete drift scenario
Suppose someone adds a new fallback resolution heuristic to graph_enrich.py:
brownfield_calls.append(replace(c, resolution_strategy="bean_method_inference"))
They forget to update mcp_hints.py. Result:
neighbors(...) returns edges with strategy: "bean_method_inference".
- The new strategy is not in
FUZZY_STRATEGY_SET.
- The fuzzy-strategy hint does not fire.
- The agent treats these edges as fully resolved.
This is a false negative — a fuzzy edge silently slips through as primary. No test catches it. No build fails.
Proposed solution: a strategy-classification invariant
Add a test (or pre-commit lint) that scans the source tree for every string literal assigned as a resolution_strategy or strategy= value in build_ast_graph.py / graph_enrich.py, and asserts each discovered strategy is classified in mcp_hints.py as either:
- In
FUZZY_STRATEGY_SET (fuzzy — hint fires), or
- In a new
PRIMARY_STRATEGY_SET allowlist (primary — silent).
If a strategy is discovered that's in neither set, the test fails with a message like:
Unclassified resolution strategy: 'bean_method_inference'
Found at: graph_enrich.py:1352
Classify it in mcp_hints.py as either FUZZY_STRATEGY_SET or PRIMARY_STRATEGY_SET.
This converts a silent drift into a build-time failure. The author of the new strategy has to make a deliberate classification decision in the same PR that introduces it.
Why this is its own issue, not part of #146
The hints-v2 propose decides the policy (categorical strategy classification, not threshold). It does not commit to a specific enforcement mechanism — that's plan-level and could be:
- A pytest test that greps source files.
- A pre-commit hook.
- A property of the
ResolutionStrategy type if we introduce a Literal enum (a larger refactor).
Each has tradeoffs. The propose punts on this with Risk §8: "Adding a new strategy is a one-line PR. The propose decision is the policy, not the exact membership." This issue is the concrete follow-up to make that enforceable.
Related design question (linked, not blocking)
There's a separate question of which set should be the closed list — fuzzy or primary. The hints-v2 propose chose explicit-fuzzy (current code). The opposite design (explicit-primary, implicit-fuzzy) has a different failure mode: new primary strategies silently fire a hint, training the agent to ignore them.
The classification invariant above sidesteps that question by requiring both sets to be explicit. That's likely the right answer, but it's also outside the scope of hints-v2 and worth a separate review.
Acceptance criteria
Not in scope
- Changing the hint emission policy itself (that's hints-v2 / a future hints-v3).
- Introducing a
ResolutionStrategy Literal type across the codebase (separate refactor).
- Surfacing strategy classification in tool docs or
describe output.
Problem
mcp_hints.py(added in #144, extended in #146 hints-v2) classifies edge resolution strategies as fuzzy (emit a road-sign hint to the agent) or primary (silent). The current design uses an explicitFUZZY_STRATEGY_SET:Anything not in this set is silently treated as primary. The brownfield pipeline (
build_ast_graph.py,graph_enrich.py) emits strategy strings as plain literals. Nothing today prevents drift between the pipeline and the hint catalog.Concrete drift scenario
Suppose someone adds a new fallback resolution heuristic to
graph_enrich.py:They forget to update
mcp_hints.py. Result:neighbors(...)returns edges withstrategy: "bean_method_inference".FUZZY_STRATEGY_SET.This is a false negative — a fuzzy edge silently slips through as primary. No test catches it. No build fails.
Proposed solution: a strategy-classification invariant
Add a test (or pre-commit lint) that scans the source tree for every string literal assigned as a
resolution_strategyorstrategy=value inbuild_ast_graph.py/graph_enrich.py, and asserts each discovered strategy is classified inmcp_hints.pyas either:FUZZY_STRATEGY_SET(fuzzy — hint fires), orPRIMARY_STRATEGY_SETallowlist (primary — silent).If a strategy is discovered that's in neither set, the test fails with a message like:
This converts a silent drift into a build-time failure. The author of the new strategy has to make a deliberate classification decision in the same PR that introduces it.
Why this is its own issue, not part of #146
The hints-v2 propose decides the policy (categorical strategy classification, not threshold). It does not commit to a specific enforcement mechanism — that's plan-level and could be:
ResolutionStrategytype if we introduce a Literal enum (a larger refactor).Each has tradeoffs. The propose punts on this with Risk §8: "Adding a new strategy is a one-line PR. The propose decision is the policy, not the exact membership." This issue is the concrete follow-up to make that enforceable.
Related design question (linked, not blocking)
There's a separate question of which set should be the closed list — fuzzy or primary. The hints-v2 propose chose explicit-fuzzy (current code). The opposite design (explicit-primary, implicit-fuzzy) has a different failure mode: new primary strategies silently fire a hint, training the agent to ignore them.
The classification invariant above sidesteps that question by requiring both sets to be explicit. That's likely the right answer, but it's also outside the scope of hints-v2 and worth a separate review.
Acceptance criteria
resolution_strategy=orstrategy=literal appears in the brownfield pipeline source and is not classified inmcp_hints.py.PRIMARY_STRATEGY_SET, could be a docstring, depending on implementation choice).build_ast_graph.pyorgraph_enrich.pyormcp_hints.py.Not in scope
ResolutionStrategyLiteral type across the codebase (separate refactor).describeoutput.