Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c5d11d1
docs(propose): absence-diagnosis design spec
HumanBean17 Jul 7, 2026
ac54926
docs(plan): absence-diagnosis implementation plan
HumanBean17 Jul 7, 2026
add3542
feat(absence): shared diagnosis types, absence field, config knobs
HumanBean17 Jul 7, 2026
28a4d81
fix(absence): correct DescribeOutput test field + pyproject module order
HumanBean17 Jul 7, 2026
f4fb253
feat(absence): precomputed vocabulary index asset + build hook
HumanBean17 Jul 7, 2026
22ad723
fix(absence): honor configured absence_ngram_q in the build subprocess
HumanBean17 Jul 7, 2026
6e8b192
feat(absence): diagnosis module — classifier + per-cause help
HumanBean17 Jul 7, 2026
ca1d6df
fix(absence): route correct_empty gates on http_endpoint; drop dead i…
HumanBean17 Jul 7, 2026
f142ef2
feat(absence): wire diagnosis into the 5 MCP empty paths
HumanBean17 Jul 7, 2026
41974e8
fix(absence): drop redundant graph-None guard on resolve empty path
HumanBean17 Jul 7, 2026
43e5bef
feat(absence): CLI envelope + renderer alignment
HumanBean17 Jul 7, 2026
3447d35
docs(absence): verdict vocabulary + config knobs
HumanBean17 Jul 7, 2026
0041a2d
docs(absence): correct threshold descriptions (similarity, not distan…
HumanBean17 Jul 7, 2026
edc0a32
fix(absence): restore _init_hash_tracker to write_ladybug; route bloc…
HumanBean17 Jul 7, 2026
31dc1eb
fix(absence): dedent route-block return False (kafka routes no longer…
HumanBean17 Jul 7, 2026
1ecfd2f
fix(absence): reset vocab cache per-test (test isolation for module-l…
HumanBean17 Jul 7, 2026
df3c592
fix(absence): empty-vocab guard (never not_in_project on empty index)…
HumanBean17 Jul 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
696 changes: 696 additions & 0 deletions absence_diagnosis.py

Large diffs are not rendered by default.

124 changes: 124 additions & 0 deletions absence_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""Absence diagnosis data transfer objects.

These types define the contract for explaining empty MCP tool results.
Later PRs (ABS-2, ABS-3) populate these fields; ABS-0 only declares them.
"""

from typing import Literal

from pydantic import BaseModel, Field

from graph_types import NodeRef

__all__ = [
"AbsenceVerdict",
"AbsenceCause",
"ExternalReason",
"AbsenceProof",
"ExternalIdentity",
"VocabularyContext",
"FilterRelaxationDim",
"FilterRelaxation",
"AbsenceDiagnosis",
]

# Literal types for verdicts and causes
AbsenceVerdict = Literal["refine_query", "not_in_project", "external_dependency", "correct_empty"]
AbsenceCause = Literal["identifier_miss", "nl_miss", "filter_miss", "external", "meaningful_empty"]
ExternalReason = Literal["prefix", "phantom", "unresolved-call"]


class AbsenceProof(BaseModel):
"""Evidence backing a hard 'not_in_project' verdict.

Attributes:
nearest_distance: Distance to the closest symbol found (0-1)
symbol_count_scanned: Total symbols examined during search
thresholds_applied: The similarity thresholds used in the decision
query_shape: Shape of the original query (currently only "identifier")
"""
nearest_distance: float
symbol_count_scanned: int
thresholds_applied: dict[str, float]
query_shape: Literal["identifier"]


class ExternalIdentity(BaseModel):
"""Identifies an external dependency that caused the empty result.

Attributes:
fqn: Fully qualified name of the external symbol
reason: Why we believe this is external (prefix, phantom, unresolved call)
source: Optional source name (e.g., "maven", "gradle")
"""
fqn: str
reason: ExternalReason
source: str | None = None


class VocabularyContext(BaseModel):
"""Project vocabulary statistics to inform query refinement.

Attributes:
top_modules: Most frequent modules with counts
top_microservices: Most frequent microservices with counts
roles_present: Symbol roles present with counts
frequent_name_tokens: Common tokens in symbol names
"""
top_modules: list[tuple[str, int]]
top_microservices: list[tuple[str, int]]
roles_present: list[tuple[str, int]]
frequent_name_tokens: list[str]


class FilterRelaxationDim(BaseModel):
"""Relaxation analysis for a single filter dimension.

Attributes:
dimension: The filter dimension (e.g., "microservice", "role")
constrained_value: The value that constrained results
matches_under_relaxation: Results if this dimension were relaxed
suggested_value: Optional alternative value to try
"""
dimension: str
constrained_value: str | None
matches_under_relaxation: int
suggested_value: str | None


class FilterRelaxation(BaseModel):
"""Analysis of how relaxing filters would affect results.

Attributes:
per_dimension: List of relaxation options per dimension
"""
per_dimension: list[FilterRelaxationDim]


class AbsenceDiagnosis(BaseModel):
"""Explains why an MCP tool returned no results.

This is the main DTO that the 5 MCP output models optionally carry.
In PR-ABS-0, the `absence` field stays None everywhere — later PRs
populate it with diagnosis logic.

Attributes:
verdict: High-level judgment on the empty result
cause: Specific cause that led to this verdict
message: Human-readable explanation
closest_symbols: Symbols closest to the query (if any)
distances: Corresponding distance values
proof: Evidence for not_in_project verdict
external_identity: External dependency info
vocabulary_context: Project vocabulary for refinement
filter_relaxation: Filter relaxation suggestions
"""
verdict: AbsenceVerdict
cause: AbsenceCause
message: str
closest_symbols: list[NodeRef] = Field(default_factory=list)
distances: list[float] = Field(default_factory=list)
proof: AbsenceProof | None = None
external_identity: ExternalIdentity | None = None
vocabulary_context: VocabularyContext | None = None
filter_relaxation: FilterRelaxation | None = None
Loading
Loading