Files
stack/src/sem/nodes.py
kert 96b8ccf6df
Some checks failed
CI / skinny-install (aco) (push) Successful in 53s
CI / lint-test (push) Successful in 1m15s
CI / skinny-install (api) (push) Successful in 26s
CI / skinny-install (bcda) (push) Successful in 29s
CI / skinny-install (bib) (push) Successful in 31s
CI / skinny-install (bls) (push) Successful in 27s
CI / skinny-install (ccw) (push) Successful in 30s
CI / skinny-install (cli) (push) Successful in 30s
CI / skinny-install (cms) (push) Successful in 26s
CI / skinny-install (conf) (push) Successful in 26s
CI / skinny-install (opps) (push) Successful in 28s
CI / skinny-install (perf) (push) Successful in 29s
CI / skinny-install (pfs) (push) Successful in 27s
Infra CI / notebooks (push) Successful in 7s
Infra CI / zotero (push) Successful in 7s
CI / skinny-install (rex) (push) Successful in 27s
Infra CI / docs (push) Failing after 9s
Infra CI / api (push) Successful in 8s
Infra CI / mc (push) Successful in 6s
Package Supply Chain / pkg-supply-chain (push) Successful in 52s
CI / lint-test (pull_request) Successful in 1m18s
CI / skinny-install (aco) (pull_request) Successful in 44s
CI / skinny-install (api) (pull_request) Successful in 24s
CI / skinny-install (bcda) (pull_request) Successful in 32s
CI / skinny-install (bib) (pull_request) Successful in 30s
CI / skinny-install (bls) (pull_request) Successful in 25s
CI / skinny-install (ccw) (pull_request) Successful in 23s
CI / skinny-install (cli) (pull_request) Successful in 30s
CI / skinny-install (cms) (pull_request) Successful in 27s
CI / skinny-install (conf) (pull_request) Successful in 30s
CI / skinny-install (opps) (pull_request) Successful in 29s
CI / skinny-install (perf) (pull_request) Successful in 32s
CI / skinny-install (pfs) (pull_request) Successful in 33s
Infra CI / notebooks (pull_request) Successful in 7s
Infra CI / zotero (pull_request) Successful in 6s
Infra CI / docs (pull_request) Failing after 6s
CI / skinny-install (rex) (pull_request) Successful in 27s
Infra CI / api (pull_request) Successful in 6s
Infra CI / mc (pull_request) Successful in 6s
feat: sem — semantic coverage orchestration (AST + Ruff + ty + coverage)
4-layer architecture for tracking coverage as semantic AST nodes rather
than raw line numbers: parse → enrich → runtime → plan.  Stable node
identities, Ruff/ty diagnostic attachment, coverage mapping, priority
scoring heuristic, and drift-resistant rolling state persistence.

39 tests, ruff clean.
2026-03-26 14:37:54 -04:00

120 lines
2.9 KiB
Python

"""Semantic node model.
Each meaningful AST construct becomes a ``SemanticNode`` with a stable
identity derived from its module path, qualified symbol name, node kind,
and ordinal position — never from line numbers.
"""
from __future__ import annotations
import enum
import hashlib
from dataclasses import dataclass, field
class NodeKind(enum.Enum):
"""Taxonomy of tracked AST constructs."""
FUNCTION = "function"
METHOD = "method"
CLASS = "class"
BRANCH_IF = "branch_if"
BRANCH_ELSE = "branch_else"
BRANCH_ELIF = "branch_elif"
MATCH_CASE = "match_case"
EXCEPT_HANDLER = "except_handler"
FOR_LOOP = "for_loop"
WHILE_LOOP = "while_loop"
WITH_BLOCK = "with_block"
RETURN = "return"
RAISE = "raise"
ASSERT = "assert"
def _make_node_id(
module: str,
symbol_context: list[str],
kind: NodeKind,
ordinal: int,
) -> str:
"""Build a stable, human-readable node identity string.
Example: ``app.config::load_settings::branch_if[1]``
"""
parts = [module]
if symbol_context:
parts.append("::".join(symbol_context))
parts.append(f"{kind.value}[{ordinal}]")
return "::".join(parts)
@dataclass
class Span:
"""Byte-offset span within a source file (line numbers as metadata only)."""
start_line: int
end_line: int
start_col: int = 0
end_col: int = 0
@dataclass
class RuntimeInfo:
"""Runtime coverage data attached after test execution."""
hit: bool = False
hit_count: int = 0
tests_seen: list[str] = field(default_factory=list)
@dataclass
class SemanticNode:
"""A single tracked semantic unit in the codebase."""
node_id: str
kind: NodeKind
span: Span
module: str
symbol_context: list[str] = field(default_factory=list)
source_hash: str = ""
# Enrichment: Ruff diagnostics
ruff_codes: list[str] = field(default_factory=list)
# Enrichment: ty diagnostics
ty_codes: list[str] = field(default_factory=list)
# Runtime coverage
runtime: RuntimeInfo = field(default_factory=RuntimeInfo)
# Planner state
status: str = "unexplored"
priority: float = 0.0
attempts: int = 0
intent_tags: list[str] = field(default_factory=list)
@staticmethod
def build(
module: str,
symbol_context: list[str],
kind: NodeKind,
ordinal: int,
span: Span,
source_fragment: str = "",
) -> SemanticNode:
"""Construct a node with a stable ID and optional source hash."""
node_id = _make_node_id(module, symbol_context, kind, ordinal)
src_hash = ""
if source_fragment:
src_hash = hashlib.sha1(
source_fragment.encode(), usedforsecurity=False
).hexdigest()[:12]
return SemanticNode(
node_id=node_id,
kind=kind,
span=span,
module=module,
symbol_context=list(symbol_context),
source_hash=src_hash,
)