feat(kernel): add foundational family registry schema (#840)
This commit is contained in:
parent
5cd377b1b8
commit
168c9f26ac
3 changed files with 549 additions and 0 deletions
|
|
@ -279,3 +279,99 @@ The substantive result of this session is not just “0005 runnable.” It is th
|
|||
|
||||
- **Versor condition**: No geometric algebra files were touched; versor invariants remain fully satisfied.
|
||||
- **Docs-Only Scope**: Verified that all changes are purely documentation additions or comments, with no runtime code or test suite modifications.
|
||||
|
||||
---
|
||||
|
||||
## PR #840 Addendum — feat(kernel): add foundational family registry schema
|
||||
|
||||
### Agent and Session
|
||||
|
||||
- **Agent:** gpt55
|
||||
- **Date:** 2026-06-20
|
||||
- **Reasoning effort used:** medium
|
||||
- **Grok Build mode used:** Headless
|
||||
- **Session entry point:** Implement the typed, read-only foundational-family registry schema for the two #839 specifications without adding recognition, assessment, routing, solving, or serving behavior.
|
||||
- **Branch:** `feat/kernel-foundational-family-registry-schema`
|
||||
- **Base:** `origin/main` at `5cd377b1b8169826ea2406a93136b7615c616286`
|
||||
|
||||
### Smoke Suite + Bootstrap Status
|
||||
|
||||
```text
|
||||
uv run python -m core.cli test --suite smoke -q
|
||||
108 passed in 123.94s (baseline)
|
||||
108 passed in 131.98s (post-edit)
|
||||
```
|
||||
|
||||
`core-bootstrap` was not available as a skill; the GPT55.md checklist was completed manually. The acknowledged dirty/diverged `/Users/kaizenpro/Projects/core` main worktree was not modified, stashed, reset, cleaned, or reconciled.
|
||||
|
||||
### Modules Touched
|
||||
|
||||
| File | Change type | Summary |
|
||||
|---|---|---|
|
||||
| `generate/foundational_families.py` | new | Frozen specification/evidence records, two manually mirrored unauthorized family specs, immutable indices, and public accessors. |
|
||||
| `tests/test_foundational_families.py` | new | Public-API invariant tests for exact registry membership/order, uniqueness, immutability, required fields, cross-domain evidence, authorization gates, and absence of file/Markdown loading. |
|
||||
| `HANDOFF-gpt55-2026-06-20.md` | modified | Added this PR #840 continuity record. |
|
||||
|
||||
### Invariants Verified
|
||||
|
||||
- **Field closure:** Preserved by construction. No algebra, field, propagation, vault, normalization, or runtime field-state path was imported or modified.
|
||||
- **Exact recall:** Preserved. No CGA lookup, cosine, ANN, HNSW, or memory path was added.
|
||||
- **Serving and implementation gates:** Both families explicitly carry `serving_allowed=False` and `implementation_authorized=False`; tests enforce both gates.
|
||||
- **No behavior slice:** The module is unreferenced by runtime code and provides descriptive immutable metadata only. No parser, recognizer, adapter, `ContractAssessment`, routing, derivation, eval, report, or serving behavior was added.
|
||||
- **No live-substrate claim:** Module and current-state text explicitly state that general substrate/CGA retrieval is not live for these families.
|
||||
- **No universal IR:** The schema mirrors only ADR-0224/#839 specification fields and contains only the two authorized registry entries.
|
||||
- **Determinism:** Registry iteration is an explicit immutable tuple; family IDs and primary relation types are unique and tested.
|
||||
|
||||
### Subagent / Arena Reconciliation
|
||||
|
||||
- Number of subagents spawned: 0
|
||||
- Each subagent independently verified versor closure: N/A
|
||||
- Reconciliation: N/A
|
||||
|
||||
### Tests Run
|
||||
|
||||
```bash
|
||||
# exit 0 — 13 passed
|
||||
uv run python -m pytest -q \
|
||||
tests/test_foundational_families.py \
|
||||
tests/test_construction_affordances.py
|
||||
|
||||
# exit 0 — 108 passed
|
||||
uv run python -m core.cli test --suite smoke -q
|
||||
|
||||
# exit 0
|
||||
python3 -m compileall -q \
|
||||
generate/foundational_families.py \
|
||||
tests/test_foundational_families.py
|
||||
|
||||
# exit 0
|
||||
git diff --check
|
||||
```
|
||||
|
||||
### Open Tasks / Next Session Entry Point
|
||||
|
||||
1. Review and publish PR #840 with the required body sections from the implementation brief.
|
||||
2. Do not connect this registry to `ProblemFrame`, `ContractAssessment`, proposal routing, recognition, adapters, or serving without a separately authorized behavior PR.
|
||||
|
||||
### Known Hazards / Do Not Touch
|
||||
|
||||
- The two #839 specifications are gating artifacts, not implementation authorization.
|
||||
- Do not parse `docs/specs/foundational-families/*.md` at runtime.
|
||||
- Do not add acquisition, transaction, another family, or a universal family IR in this slice.
|
||||
- The acknowledged local-only state under `/Users/kaizenpro/Projects/core` remains outside this PR and must not be altered.
|
||||
|
||||
### Architectural Decision
|
||||
|
||||
The foundational registry remains separate from `ConstructionFamily`. The latter describes live diagnostic construction proposals; this schema mirrors constitutional family gates. Combining them would falsely couple specification completeness to executable assessment machinery and blur current versus target state.
|
||||
|
||||
### What Must Not Be Forgotten
|
||||
|
||||
PR #840 makes the family-spec gate structurally inspectable; it authorizes nothing. Both entries remain non-serving and non-implementable until a separate evidence-backed PR is approved.
|
||||
|
||||
### Skills Used
|
||||
|
||||
- core-bootstrap: unavailable; checklist completed manually
|
||||
- versor-coherence-guardian: unavailable; closure preserved by scope and smoke validation
|
||||
- pre-edit-sweep: completed manually across imports/call sites in `generate/`, `tests/`, `evals/`, and `calibration/`
|
||||
- claim-proposal-guardian: not needed; no claim or proposal mutation path touched
|
||||
- Other: none
|
||||
|
|
|
|||
294
generate/foundational_families.py
Normal file
294
generate/foundational_families.py
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
"""Read-only registry of approved foundational-family specifications.
|
||||
|
||||
This module manually mirrors the constitutional family specifications approved
|
||||
under ADR-0224. It is descriptive metadata only: it does not read specification
|
||||
documents, recognize constructions, assess ``ProblemFrame`` contracts, route
|
||||
proposals, or authorize implementation or serving.
|
||||
|
||||
The current registry deliberately contains only the two specifications approved
|
||||
by the initial family-spec gate. General substrate/CGA retrieval for these
|
||||
families is not live.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from types import MappingProxyType
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class FoundationalDomainEvidence:
|
||||
"""One cross-domain example declared by a foundational-family spec."""
|
||||
|
||||
domain: str
|
||||
example: str
|
||||
expected_roles: tuple[str, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class FoundationalFamilySpec:
|
||||
"""Immutable descriptive mirror of an approved family specification.
|
||||
|
||||
The fields state the future contract shape and its constitutional gates.
|
||||
They do not implement recognition, binding, assessment, verification, or
|
||||
serving behavior.
|
||||
"""
|
||||
|
||||
family_id: str
|
||||
display_name: str
|
||||
status: str
|
||||
related_adrs: tuple[str, ...]
|
||||
domains: tuple[str, ...]
|
||||
summary: str
|
||||
surface_chunk_patterns: tuple[str, ...]
|
||||
semantic_neighborhood: tuple[str, ...]
|
||||
construction_signatures: tuple[str, ...]
|
||||
required_roles: tuple[str, ...]
|
||||
optional_roles: tuple[str, ...]
|
||||
hazards_confusers: tuple[str, ...]
|
||||
frame_representation: tuple[str, ...]
|
||||
contract_readiness_criteria: tuple[str, ...]
|
||||
verification_style: tuple[str, ...]
|
||||
refusal_conditions: tuple[str, ...]
|
||||
cross_domain_evidence: tuple[FoundationalDomainEvidence, ...]
|
||||
current_state: str
|
||||
target_state: str
|
||||
serving_status: str
|
||||
serving_allowed: bool
|
||||
implementation_authorized: bool
|
||||
primary_relation_type: str
|
||||
future_adapter: str | None = None
|
||||
|
||||
|
||||
_QUANTITY_ENTITY_BINDING = FoundationalFamilySpec(
|
||||
family_id="binding.quantity_entity",
|
||||
display_name="Quantity-Entity Binding",
|
||||
status="Proposed (gating specification)",
|
||||
related_adrs=("ADR-0223", "ADR-0224"),
|
||||
domains=(
|
||||
"arithmetic_quantitative",
|
||||
"physical_science",
|
||||
"charts_tables_data",
|
||||
"social_studies",
|
||||
),
|
||||
summary=(
|
||||
"Binds a grounded scalar quantity to the entity, object, category, or "
|
||||
"measured property that it quantifies."
|
||||
),
|
||||
surface_chunk_patterns=(
|
||||
"<number> <noun>",
|
||||
"<number> of the <noun>",
|
||||
"<noun>: <number>",
|
||||
"<number> <unit> of <material>",
|
||||
),
|
||||
semantic_neighborhood=(
|
||||
"quantified_group",
|
||||
"measured_property",
|
||||
"category_cardinality",
|
||||
"resource_population",
|
||||
),
|
||||
construction_signatures=("quantity_entity_binding",),
|
||||
required_roles=("quantity", "entity"),
|
||||
optional_roles=("unit",),
|
||||
hazards_confusers=(
|
||||
"PF-EN-002 quantity_entity_unbound",
|
||||
"PF-LX-004 span_collision",
|
||||
"PF-EN-005 role_alias_collision",
|
||||
"PF-HZ-005 conflict_auto_resolved",
|
||||
),
|
||||
frame_representation=(
|
||||
"GroundedMention for the quantity and entity spans",
|
||||
"MentionBinding(binding_type='quantity_entity') from quantity to entity",
|
||||
"Optional MentionBinding(binding_type='quantity_unit') from quantity to unit",
|
||||
),
|
||||
contract_readiness_criteria=(
|
||||
"Quantity is grounded to an exact parsed scalar.",
|
||||
"Entity is grounded to a valid noun phrase or category span.",
|
||||
"No active overlap or scalar span collision remains.",
|
||||
"No unresolved quantity-entity hazard remains.",
|
||||
"Independent verification and wrong-zero preservation are required before serving.",
|
||||
),
|
||||
verification_style=(
|
||||
"Lexical-substitution invariance must preserve binding topology modulo spans.",
|
||||
"Comparative confusers must not be accepted as generic quantity-entity bindings.",
|
||||
),
|
||||
refusal_conditions=(
|
||||
"Ambiguous referents remain unresolved while multiple entity candidates are active.",
|
||||
"Overlapping scalar spans cannot be deterministically ordered.",
|
||||
),
|
||||
cross_domain_evidence=(
|
||||
FoundationalDomainEvidence(
|
||||
domain="physical_science",
|
||||
example="A block of iron has a mass of 12 grams.",
|
||||
expected_roles=("quantity", "entity", "unit"),
|
||||
),
|
||||
FoundationalDomainEvidence(
|
||||
domain="charts_tables_data",
|
||||
example="The bar graph shows: Apples: 15, Bananas: 8.",
|
||||
expected_roles=("quantity", "entity"),
|
||||
),
|
||||
FoundationalDomainEvidence(
|
||||
domain="social_studies",
|
||||
example="The town of Shelbyville has a population of 50,000 residents.",
|
||||
expected_roles=("quantity", "entity"),
|
||||
),
|
||||
),
|
||||
current_state=(
|
||||
"Not implemented as a general family. Local parser heuristics and selected "
|
||||
"math diagnostic proposal traces exist; general substrate/CGA retrieval does not."
|
||||
),
|
||||
target_state=(
|
||||
"Proposal-first quantity-entity binding with span grounding, contract assessment, "
|
||||
"independent verification, and cross-domain evidence."
|
||||
),
|
||||
serving_status="Not implemented / not serving.",
|
||||
serving_allowed=False,
|
||||
implementation_authorized=False,
|
||||
primary_relation_type="quantity_entity",
|
||||
future_adapter="quantity_entity_adapter",
|
||||
)
|
||||
|
||||
|
||||
_STATE_CHANGE = FoundationalFamilySpec(
|
||||
family_id="state_change.transition",
|
||||
display_name="State Change",
|
||||
status="Proposed (gating specification)",
|
||||
related_adrs=("ADR-0223", "ADR-0224"),
|
||||
domains=(
|
||||
"arithmetic_proportional",
|
||||
"physical_science",
|
||||
"life_science",
|
||||
"reading_comprehension",
|
||||
"procedural_reasoning",
|
||||
),
|
||||
summary=(
|
||||
"Represents an entity or system transitioning from an initial state to a "
|
||||
"final state through an event, action, or process."
|
||||
),
|
||||
surface_chunk_patterns=(
|
||||
"<verb-change> to <value>",
|
||||
"<verb-change> by <value>",
|
||||
"originally <value>, now <value>",
|
||||
"after <event>, <actor> has <value>",
|
||||
),
|
||||
semantic_neighborhood=(
|
||||
"possession_change",
|
||||
"proportional_scaling",
|
||||
"temperature_transition",
|
||||
"growth_stage",
|
||||
"procedural_step",
|
||||
),
|
||||
construction_signatures=("state_change",),
|
||||
required_roles=("entity", "initial_state", "transition_event", "final_state"),
|
||||
optional_roles=("scale", "delta"),
|
||||
hazards_confusers=(
|
||||
"PF-BD-004 positional_binding",
|
||||
"PF-TG-004 target_direction_unknown",
|
||||
"PF-TP-006 state_transition_open",
|
||||
"PF-HZ-003 hazard_ignored_by_contract",
|
||||
),
|
||||
frame_representation=(
|
||||
"BoundRelation(relation_type='state_change')",
|
||||
"RelationRole bindings for entity, initial_state, transition, and final_state",
|
||||
"Optional RelationRole bindings for scale or delta",
|
||||
"Exact SourceSpan evidence for the proposed transition",
|
||||
),
|
||||
contract_readiness_criteria=(
|
||||
"The changing entity is bound with continuity across the transition span.",
|
||||
"Transition direction is explicitly resolved from grounded evidence.",
|
||||
"The question target names a mathematically closed variable.",
|
||||
"No unresolved 'by' versus 'to' directional hazard remains.",
|
||||
"Independent verification and wrong-zero preservation are required before serving.",
|
||||
),
|
||||
verification_style=(
|
||||
"Validate the applicable state equation over grounded roles.",
|
||||
"Changing 'decreased to' to 'decreased by' must require rebinding and reverification.",
|
||||
),
|
||||
refusal_conditions=(
|
||||
"Event order is missing or temporally ambiguous.",
|
||||
"The transition is open because initial, delta, and final values cannot close a target.",
|
||||
),
|
||||
cross_domain_evidence=(
|
||||
FoundationalDomainEvidence(
|
||||
domain="physical_science",
|
||||
example="Water originally at 20 degrees Celsius is heated to 80 degrees Celsius.",
|
||||
expected_roles=("entity", "initial_state", "transition_event", "final_state"),
|
||||
),
|
||||
FoundationalDomainEvidence(
|
||||
domain="life_science",
|
||||
example="The plant grew from 5 inches tall to 9 inches tall over the summer.",
|
||||
expected_roles=("entity", "initial_state", "transition_event", "final_state"),
|
||||
),
|
||||
FoundationalDomainEvidence(
|
||||
domain="reading_comprehension",
|
||||
example=(
|
||||
"Before the storm, the streets were dry. After the storm, the streets "
|
||||
"were flooded."
|
||||
),
|
||||
expected_roles=("entity", "initial_state", "transition_event", "final_state"),
|
||||
),
|
||||
FoundationalDomainEvidence(
|
||||
domain="procedural_reasoning",
|
||||
example="Step 1: Set X to 10. Step 2: Decrement X by 2.",
|
||||
expected_roles=("entity", "initial_state", "transition_event", "delta"),
|
||||
),
|
||||
),
|
||||
current_state=(
|
||||
"Selected math constructions have assessment-backed diagnostic proposal traces. "
|
||||
"A general state-change adapter and non-math serving frames do not exist."
|
||||
),
|
||||
target_state=(
|
||||
"Proposal-first state-change binding with role-complete contract assessment, "
|
||||
"independent verification, typed refusal, and cross-domain evidence."
|
||||
),
|
||||
serving_status="Not implemented / not serving.",
|
||||
serving_allowed=False,
|
||||
implementation_authorized=False,
|
||||
primary_relation_type="state_change",
|
||||
future_adapter="state_change_adapter",
|
||||
)
|
||||
|
||||
|
||||
_FAMILIES = (_QUANTITY_ENTITY_BINDING, _STATE_CHANGE)
|
||||
_BY_ID = MappingProxyType({family.family_id: family for family in _FAMILIES})
|
||||
_BY_RELATION_TYPE = MappingProxyType(
|
||||
{family.primary_relation_type: family for family in _FAMILIES}
|
||||
)
|
||||
|
||||
|
||||
def iter_foundational_families() -> tuple[FoundationalFamilySpec, ...]:
|
||||
"""Return all approved foundational-family specs in deterministic order."""
|
||||
|
||||
return _FAMILIES
|
||||
|
||||
|
||||
def get_foundational_family(family_id: str) -> FoundationalFamilySpec | None:
|
||||
"""Return the registered spec for ``family_id``, if present."""
|
||||
|
||||
return _BY_ID.get(family_id)
|
||||
|
||||
|
||||
def require_foundational_family(family_id: str) -> FoundationalFamilySpec:
|
||||
"""Return the registered spec or raise ``KeyError`` with a clear identifier."""
|
||||
|
||||
family = get_foundational_family(family_id)
|
||||
if family is None:
|
||||
raise KeyError(f"unknown foundational family: {family_id!r}")
|
||||
return family
|
||||
|
||||
|
||||
def get_foundational_family_by_relation_type(
|
||||
relation_type: str,
|
||||
) -> FoundationalFamilySpec | None:
|
||||
"""Return the spec whose primary relation type matches ``relation_type``."""
|
||||
|
||||
return _BY_RELATION_TYPE.get(relation_type)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"FoundationalDomainEvidence",
|
||||
"FoundationalFamilySpec",
|
||||
"get_foundational_family",
|
||||
"get_foundational_family_by_relation_type",
|
||||
"iter_foundational_families",
|
||||
"require_foundational_family",
|
||||
)
|
||||
159
tests/test_foundational_families.py
Normal file
159
tests/test_foundational_families.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from dataclasses import FrozenInstanceError
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import generate.foundational_families as foundational_families
|
||||
from generate.foundational_families import (
|
||||
FoundationalFamilySpec,
|
||||
get_foundational_family,
|
||||
get_foundational_family_by_relation_type,
|
||||
iter_foundational_families,
|
||||
require_foundational_family,
|
||||
)
|
||||
|
||||
|
||||
EXPECTED_FAMILY_IDS = (
|
||||
"binding.quantity_entity",
|
||||
"state_change.transition",
|
||||
)
|
||||
|
||||
|
||||
def test_registry_contains_only_the_two_approved_specs_in_deterministic_order() -> None:
|
||||
families = iter_foundational_families()
|
||||
|
||||
assert isinstance(families, tuple)
|
||||
assert tuple(family.family_id for family in families) == EXPECTED_FAMILY_IDS
|
||||
assert tuple(family.family_id for family in iter_foundational_families()) == (
|
||||
EXPECTED_FAMILY_IDS
|
||||
)
|
||||
|
||||
|
||||
def test_public_family_accessors() -> None:
|
||||
quantity = get_foundational_family("binding.quantity_entity")
|
||||
state_change = require_foundational_family("state_change.transition")
|
||||
|
||||
assert quantity is not None
|
||||
assert quantity.family_id == "binding.quantity_entity"
|
||||
assert state_change.family_id == "state_change.transition"
|
||||
assert get_foundational_family("missing.family") is None
|
||||
|
||||
with pytest.raises(KeyError, match="missing.family"):
|
||||
require_foundational_family("missing.family")
|
||||
|
||||
|
||||
def test_lookup_by_primary_relation_type() -> None:
|
||||
quantity = get_foundational_family_by_relation_type("quantity_entity")
|
||||
state_change = get_foundational_family_by_relation_type("state_change")
|
||||
|
||||
assert quantity is get_foundational_family("binding.quantity_entity")
|
||||
assert state_change is get_foundational_family("state_change.transition")
|
||||
assert get_foundational_family_by_relation_type("missing_relation") is None
|
||||
|
||||
|
||||
def test_registry_keys_are_unique() -> None:
|
||||
families = iter_foundational_families()
|
||||
family_ids = tuple(family.family_id for family in families)
|
||||
relation_types = tuple(family.primary_relation_type for family in families)
|
||||
|
||||
assert len(family_ids) == len(set(family_ids))
|
||||
assert len(relation_types) == len(set(relation_types))
|
||||
|
||||
|
||||
def test_specs_are_frozen_and_explicitly_not_authorized() -> None:
|
||||
for family in iter_foundational_families():
|
||||
assert isinstance(family, FoundationalFamilySpec)
|
||||
assert family.serving_allowed is False
|
||||
assert family.implementation_authorized is False
|
||||
assert "not serving" in family.serving_status.lower()
|
||||
|
||||
with pytest.raises(FrozenInstanceError):
|
||||
family.display_name = "mutated" # type: ignore[misc]
|
||||
|
||||
|
||||
def test_required_adr_0224_fields_are_populated() -> None:
|
||||
required_fields = (
|
||||
"family_id",
|
||||
"display_name",
|
||||
"status",
|
||||
"related_adrs",
|
||||
"domains",
|
||||
"summary",
|
||||
"surface_chunk_patterns",
|
||||
"semantic_neighborhood",
|
||||
"construction_signatures",
|
||||
"required_roles",
|
||||
"optional_roles",
|
||||
"hazards_confusers",
|
||||
"frame_representation",
|
||||
"contract_readiness_criteria",
|
||||
"verification_style",
|
||||
"refusal_conditions",
|
||||
"cross_domain_evidence",
|
||||
"current_state",
|
||||
"target_state",
|
||||
"serving_status",
|
||||
"primary_relation_type",
|
||||
"future_adapter",
|
||||
)
|
||||
|
||||
for family in iter_foundational_families():
|
||||
assert all(getattr(family, field) for field in required_fields)
|
||||
assert "ADR-0224" in family.related_adrs
|
||||
assert family.hazards_confusers
|
||||
assert family.refusal_conditions
|
||||
assert family.current_state != family.target_state
|
||||
|
||||
|
||||
def test_each_family_has_at_least_two_non_math_evidence_examples() -> None:
|
||||
math_domains = {
|
||||
"arithmetic",
|
||||
"arithmetic_quantitative",
|
||||
"arithmetic_proportional",
|
||||
"mathematics",
|
||||
}
|
||||
|
||||
for family in iter_foundational_families():
|
||||
non_math_evidence = tuple(
|
||||
evidence
|
||||
for evidence in family.cross_domain_evidence
|
||||
if evidence.domain not in math_domains
|
||||
)
|
||||
assert len(non_math_evidence) >= 2
|
||||
assert all(evidence.example for evidence in non_math_evidence)
|
||||
assert all(evidence.expected_roles for evidence in non_math_evidence)
|
||||
|
||||
|
||||
def test_registry_module_has_no_markdown_or_filesystem_loading_surface() -> None:
|
||||
source_path = Path(foundational_families.__file__)
|
||||
tree = ast.parse(source_path.read_text(encoding="utf-8"))
|
||||
|
||||
imported_modules = {
|
||||
alias.name
|
||||
for node in ast.walk(tree)
|
||||
if isinstance(node, ast.Import)
|
||||
for alias in node.names
|
||||
}
|
||||
imported_from_modules = {
|
||||
node.module
|
||||
for node in ast.walk(tree)
|
||||
if isinstance(node, ast.ImportFrom) and node.module is not None
|
||||
}
|
||||
called_names = {
|
||||
node.func.id
|
||||
for node in ast.walk(tree)
|
||||
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
|
||||
}
|
||||
called_attributes = {
|
||||
node.func.attr
|
||||
for node in ast.walk(tree)
|
||||
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)
|
||||
}
|
||||
|
||||
assert "pathlib" not in imported_modules | imported_from_modules
|
||||
assert "markdown" not in imported_modules | imported_from_modules
|
||||
assert "open" not in called_names
|
||||
assert not ({"read_text", "read_bytes"} & called_attributes)
|
||||
Loading…
Reference in a new issue