5ac0618a33
The 7 code_path_audit*.py files (2604 lines total) are pure static analysis tools. They do AST traversal of src/, no intrusive profiling, no runtime markers. They were inlaid with src/ but only import: - src.result_types (the Result[T] convention type) - each other (the 6 siblings) After the move: - src/ is now pure application code; line-count audit metrics are clean - scripts/code_path_audit/ is a new namespace-isolated subdir per AGENTS.md 'scripts are namespace-isolated by directory' rule TIER-3 READ AGENTS.md + conductor/workflow.md + conductor/edit_workflow.md + conductor/code_styleguides/code_path_audit.md + the 7 files before this commit. Changes: - 7 files moved: src/code_path_audit*.py -> scripts/code_path_audit/ - 7 files updated: internal imports rom src.code_path_audit_X -> rom code_path_audit_X (siblings in same subdir) - 7 files updated: add sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'src')) to find src.result_types when run standalone - 5 test files updated: rom src.code_path_audit -> rom code_path_audit + sys.path setup to find the new subdir - 6 throwaway scripts in scripts/tier2/artifacts/ updated: import path + sys.path setup (parents[3] / 'src' + parents[3] / 'scripts' / 'code_path_audit') - 2 styleguide/spec references updated: conductor/code_styleguides/code_path_audit.md + conductor/tracks/code_path_audit_20260607/spec_v2.md - 1 meta-audit docstring updated: scripts/audit_code_path_audit_coverage.py - 1 type registry entry deleted: docs/type_registry/src_code_path_audit.md (the type is no longer in src/) - 1 type registry index updated: docs/type_registry/index.md (22 files, was 23) Verification: - 7/7 audit gates pass --strict (weak_types 102<=112, type_registry 22 files, main_thread_imports OK, no_models_config_io OK, code_path_audit_coverage 0 violations, exception_handling 0 violations, optional_in_3_files 0 violations) - 6/6 test files pass: test_code_path_audit, test_code_path_audit_integration, test_code_path_audit_phase78, test_code_path_audit_phase89, test_code_path_audit_ssdl_behavioral, test_metadata_nil_sentinel - src/ line count: 29997 lines (down from 32621 = -2624 lines) - scripts/code_path_audit/ line count: 2620 lines
154 lines
5.3 KiB
Python
154 lines
5.3 KiB
Python
"""Tests for src.code_path_audit v2 - DSL renderers + run_audit + CLI + MCP."""
|
|
from __future__ import annotations
|
|
import sys
|
|
from pathlib import Path
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts" / "code_path_audit"))
|
|
import ast
|
|
import tempfile
|
|
import subprocess
|
|
from datetime import date
|
|
from code_path_audit import (
|
|
AggregateKind,
|
|
MemoryDim,
|
|
AccessPattern,
|
|
Frequency,
|
|
RecommendedDirection,
|
|
FunctionRef,
|
|
AccessPatternEvidence,
|
|
FrequencyEvidence,
|
|
ResultCoverage,
|
|
TypeAliasCoverage,
|
|
CrossAuditFindings,
|
|
DecompositionCost,
|
|
OptimizationCandidate,
|
|
AggregateProfile,
|
|
to_markdown,
|
|
to_tree,
|
|
AGGREGATES_IN_SCOPE,
|
|
CANDIDATE_AGGREGATES,
|
|
synthesize_aggregate_profile,
|
|
run_audit,
|
|
render_rollups,
|
|
AuditSummary,
|
|
code_path_audit_v2,
|
|
)
|
|
from src.result_types import Result
|
|
|
|
def _make_profile(name: str = "Metadata", kind: str = "typealias") -> AggregateProfile:
|
|
f = FunctionRef(fqname="src.x.y", file="src/x.py", line=1, role="producer")
|
|
return AggregateProfile(
|
|
name=name,
|
|
aggregate_kind=kind,
|
|
memory_dim="discussion",
|
|
producers=(f,),
|
|
consumers=(),
|
|
access_pattern="whole_struct",
|
|
access_pattern_evidence=(),
|
|
frequency="per_turn",
|
|
frequency_evidence=(),
|
|
result_coverage=ResultCoverage(0, 0, 0, 0, ""),
|
|
type_alias_coverage=TypeAliasCoverage(0, 0, 0, ""),
|
|
cross_audit_findings=CrossAuditFindings((), (), (), (), ()),
|
|
decomposition_cost=DecompositionCost(0, 0, 0, "hold", "no data", None, 0, False),
|
|
optimization_candidates=(),
|
|
is_candidate=False,
|
|
)
|
|
|
|
# Phase 8 Tasks 8.2-8.5 tests
|
|
|
|
def test_to_markdown_10_sections() -> None:
|
|
"""to_markdown emits the 10 sections per spec section 8.1."""
|
|
profile = _make_profile()
|
|
md = to_markdown(profile)
|
|
assert "# Aggregate Profile: Metadata" in md
|
|
assert "## Pipeline summary" in md
|
|
assert "## Access pattern" in md
|
|
assert "## Frequency" in md
|
|
assert "## Result coverage" in md
|
|
assert "## Type alias coverage" in md
|
|
assert "## Cross-audit findings" in md
|
|
assert "## Decomposition cost" in md
|
|
assert "## Optimization candidates" in md
|
|
assert "## Verdict" in md
|
|
|
|
def test_to_tree_box_drawing() -> None:
|
|
"""to_tree uses box-drawing characters."""
|
|
profile = _make_profile()
|
|
tree = to_tree(profile)
|
|
assert "Metadata" in tree
|
|
assert "kind: typealias" in tree
|
|
|
|
# Phase 9 Tasks 9.1-9.6 tests
|
|
def test_aggregates_in_scope_10_real() -> None:
|
|
"""AGGREGATES_IN_SCOPE has 10 real aggregates."""
|
|
expected = {"Metadata", "FileItem", "FileItems", "CommsLogEntry", "CommsLog", "HistoryMessage", "History", "ToolDefinition", "ToolCall", "Result"}
|
|
assert set(AGGREGATES_IN_SCOPE) == expected
|
|
|
|
def test_candidate_aggregates_3_placeholders() -> None:
|
|
"""CANDIDATE_AGGREGATES has the 3 candidate aggregates."""
|
|
expected = {"ToolSpec", "ChatMessage", "ProviderHistory"}
|
|
assert set(CANDIDATE_AGGREGATES) == expected
|
|
|
|
def test_synthesize_real_aggregate() -> None:
|
|
"""synthesize_aggregate_profile returns a real AggregateProfile for a known aggregate."""
|
|
f = FunctionRef(fqname="src.x.y", file="src/x.py", line=1, role="producer")
|
|
profile = synthesize_aggregate_profile(
|
|
aggregate="Metadata",
|
|
pcg_producers={"Metadata": [f]},
|
|
pcg_consumers={"Metadata": [f]},
|
|
audit_inputs={},
|
|
overrides={},
|
|
is_candidate=False,
|
|
)
|
|
assert profile.name == "Metadata"
|
|
assert profile.aggregate_kind == "typealias"
|
|
assert profile.memory_dim == "discussion"
|
|
assert profile.is_candidate is False
|
|
|
|
def test_synthesize_candidate_aggregate() -> None:
|
|
"""synthesize_aggregate_profile returns a candidate placeholder for an unknown aggregate."""
|
|
profile = synthesize_aggregate_profile(
|
|
aggregate="ChatMessage",
|
|
pcg_producers={"ChatMessage": []},
|
|
pcg_consumers={"ChatMessage": []},
|
|
audit_inputs={},
|
|
overrides={},
|
|
is_candidate=True,
|
|
)
|
|
assert profile.name == "ChatMessage"
|
|
assert profile.aggregate_kind == "candidate_dataclass"
|
|
assert profile.is_candidate is True
|
|
assert profile.producers == ()
|
|
assert profile.consumers == ()
|
|
|
|
def test_run_audit_returns_result() -> None:
|
|
"""run_audit returns Result[AuditSummary] per error_handling.md."""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = run_audit(src_dir=tmp, audit_inputs_dir=tmp, output_dir=tmp, date="2026-06-22")
|
|
assert isinstance(result, Result)
|
|
assert result.ok
|
|
|
|
def test_run_audit_produces_13_aggregates() -> None:
|
|
"""run_audit produces 13 AggregateProfiles (10 in-scope + 3 candidate)."""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = run_audit(src_dir=tmp, audit_inputs_dir=tmp, output_dir=tmp, date="2026-06-22")
|
|
assert result.ok
|
|
assert len(result.data.aggregate_profiles) == 13
|
|
|
|
def test_render_rollups_produces_2_files() -> None:
|
|
"""render_rollups produces summary.md + AUDIT_REPORT.md (MVP: single comprehensive document)."""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
audit_result = run_audit(src_dir=tmp, audit_inputs_dir=tmp, output_dir=tmp, date="2026-06-22")
|
|
assert audit_result.ok
|
|
rollup_paths = render_rollups(audit_result.data, Path(tmp) / "2026-06-22")
|
|
assert "summary.md" in rollup_paths
|
|
assert "AUDIT_REPORT.md" in rollup_paths
|
|
|
|
def test_code_path_audit_v2_returns_dict() -> None:
|
|
"""code_path_audit_v2 returns a dict with 'profiles' + 'errors' keys (MCP tool contract)."""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = code_path_audit_v2(src_dir=tmp, audit_inputs_dir=tmp, output_dir=tmp, date="2026-06-22")
|
|
assert isinstance(result, dict)
|
|
assert "profiles" in result
|
|
assert "errors" in result
|
|
assert len(result["profiles"]) == 13 |