Skip to content

enforce strategy-classification invariant between brownfield pipeline and mcp_hints.py #147

@HumanBean17

Description

@HumanBean17

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:

  1. In FUZZY_STRATEGY_SET (fuzzy — hint fires), or
  2. 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

  • A test or check that fails when a new resolution_strategy= or strategy= literal appears in the brownfield pipeline source and is not classified in mcp_hints.py.
  • A documented list of currently-known primary strategies (could be PRIMARY_STRATEGY_SET, could be a docstring, depending on implementation choice).
  • CI runs the check on every PR that touches build_ast_graph.py or graph_enrich.py or mcp_hints.py.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions