Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "technical-debt-engine-runtime"
version = "1.1.0"
version = "1.1.1"
description = "Technical Debt Engine runtime foundation API"
requires-python = ">=3.11"
dependencies = ["lizard==1.23.0", "radon==6.0.1"]
Expand Down
2 changes: 1 addition & 1 deletion src/tde_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from tde_runtime.differential import AssessmentBaselineRegistry, DifferentialEngine, DifferentialError


CLI_VERSION = "1.1.0"
CLI_VERSION = "1.1.1"
GENERATION = "1"


Expand Down
27 changes: 21 additions & 6 deletions src/tde_runtime/complexity.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
from .source_classification import classification, language_for, primary_languages

CAPABILITY_ID = "complexity"
CAPABILITY_VERSION = "1.1.0"
CAPABILITY_VERSION = "1.1.1"
RADON_ADAPTER_ID = "complexity.radon"
LIZARD_ADAPTER_ID = "complexity.lizard"
ADAPTER_ID = RADON_ADAPTER_ID # Compatibility import for existing Python consumers.
ADAPTER_VERSION = "1.1.0"
ADAPTER_VERSION = "1.1.1"
MINIMUM_RADON_VERSION = (6, 0)
MINIMUM_LIZARD_VERSION = (1, 23)
LIZARD_LANGUAGES = {"JavaScript": "javascript", "TypeScript": "typescript", "Swift": "swift", "C": "cpp", "C++": "cpp", "C#": "csharp"}
Expand Down Expand Up @@ -154,6 +154,12 @@ def _lizard(root: Path, paths: list[Path], languages: tuple[str, ...], timeout:
except (ValueError, IndexError):
return {"status": "INVALID_EVIDENCE", "limitations": [{"id": "complexity.lizard.malformed_output", "description": "Lizard CSV contained invalid symbol data.", "cause": "invalid analyzer evidence"}]}
language = language_for(path)
# Lizard emits a file-level ``*global*`` row at source line zero for
# some C# files. It is not a function or method measurement and has
# no canonical symbol location, so it must not participate in the
# symbol contract.
if name == "*global*" and line == 0:
continue
if not name or not path or language not in languages or line < 1:
return {"status": "INVALID_EVIDENCE", "limitations": [{"id": "complexity.lizard.location_missing", "description": "Lizard omitted a required source location or language.", "cause": "invalid analyzer evidence"}]}
symbols.append({"path": path, "classification": classification(path), "language": language, "name": name,
Expand Down Expand Up @@ -196,10 +202,19 @@ def analyze(root: Path, timeout: int = 60, configuration: Mapping[str, Any] | No
adapters.append(result["adapter"])
ignored_symbols = set(_items(configuration.get("ignoredSymbols")))
symbols = [symbol for symbol in symbols if symbol["name"] not in ignored_symbols]
identities = [(symbol["path"], symbol["name"], symbol["line"], symbol["adapterId"]) for symbol in symbols]
if len(identities) != len(set(identities)):
return {"status": "INVALID_EVIDENCE", "symbols": symbols, "adapters": adapters, "thresholds": thresholds,
"primaryLanguages": list(primary), "limitations": [{"id": "complexity.symbol.duplicate", "description": "Analyzer results contain duplicate symbol identities.", "cause": "conflicting analyzer evidence"}]}
canonical_symbols: dict[tuple[str, str, int, str], dict[str, Any]] = {}
for symbol in symbols:
identity = (symbol["path"], symbol["name"], symbol["line"], symbol["adapterId"])
existing = canonical_symbols.get(identity)
if existing is None:
canonical_symbols[identity] = symbol
elif existing != symbol:
return {"status": "INVALID_EVIDENCE", "symbols": symbols, "adapters": adapters, "thresholds": thresholds,
"primaryLanguages": list(primary), "limitations": [{"id": "complexity.symbol.duplicate", "description": "Analyzer results contain conflicting symbol identities.", "cause": "conflicting analyzer evidence"}]}
# Some Lizard language readers emit an identical row twice. Preserve the
# raw analyzer output in provenance, while normalising the canonical
# symbol set to one measurement per identity.
symbols = list(canonical_symbols.values())
symbols.sort(key=lambda item: (item["language"], item["path"], item["line"], item["name"]))
return {"status": "VALID", "symbols": symbols, "adapters": adapters, "thresholds": thresholds,
"primaryLanguages": list(primary), "limitations": limitations}
2 changes: 1 addition & 1 deletion src/tde_runtime/policies/generation-1.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"description": "Default, language-independent Generation 1 qualification policy.",
"supportedCapabilities": ["code_size", "complexity", "coverage", "dependency_health"],
"supportedSchemas": ["1.0.0"],
"supportedRuntimeVersions": ["0.2.0", "1.0.0rc1", "1.0.0rc2", "1.0.0rc3", "1.0.0", "1.0.1", "1.0.2", "1.0.3", "1.0.4", "1.0.5", "1.1.0"],
"supportedRuntimeVersions": ["0.2.0", "1.0.0rc1", "1.0.0rc2", "1.0.0rc3", "1.0.0", "1.0.1", "1.0.2", "1.0.3", "1.0.4", "1.0.5", "1.1.0", "1.1.1"],
"rules": [
{"id": "code_size.repository_lines", "type": "threshold", "capability": "code_size", "metric": "code_size.source_lines", "operator": "greater_than", "threshold": {"warning": 50000, "blocking": 75000}, "severity": {"warning": "WARNING", "blocking": "BLOCKING"}, "enabled": true, "rationale": "Repository-size decisions measure product source only. Tests, documentation and configuration remain visible as evidence but do not inflate the source-code threshold."},
{"id": "complexity.product.maximum", "type": "threshold", "capability": "complexity", "metric": "complexity.cyclomatic.product.maximum", "operator": "greater_than", "threshold": {"warning": 15, "blocking": 30}, "severity": {"warning": "WARNING", "blocking": "BLOCKING"}, "enabled": true, "rationale": "The blocking complexity gate applies only to production source. Test, fixture and verification complexity remains canonical evidence but does not redefine the production-source outcome."},
Expand Down
6 changes: 3 additions & 3 deletions src/tde_runtime/registries.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class CapabilityRegistry:
def discover(self) -> tuple[object, ...]:
return (
{"id": "code_size", "version": "0.1.0", "description": "Canonical physical code-size metrics", "outputContract": "tde.code_size.v1", "analyzerSelection": "highest_priority", "qualificationRules": "complete_adapter_evidence", "supportedAnalyzers": ("code_size.cloc",)},
{"id": "complexity", "version": "1.1.0", "description": "Canonical cross-language cyclomatic-complexity metrics", "outputContract": "tde.complexity.v1", "analyzerSelection": "repository_primary_language", "qualificationRules": "complete_adapter_evidence", "supportedAnalyzers": ("complexity.radon", "complexity.lizard")},
{"id": "complexity", "version": "1.1.1", "description": "Canonical cross-language cyclomatic-complexity metrics", "outputContract": "tde.complexity.v1", "analyzerSelection": "repository_primary_language", "qualificationRules": "complete_adapter_evidence", "supportedAnalyzers": ("complexity.radon", "complexity.lizard")},
{"id": "coverage", "version": "0.1.0", "description": "Canonical test coverage metrics from existing artifacts", "outputContract": "tde.coverage.v1", "analyzerSelection": "highest_priority", "qualificationRules": "complete_adapter_evidence", "supportedAnalyzers": ("coverage.artifact",)},
{"id":"maintainability","version":"0.1.0","status":"VALIDATED"},
{"id":"dependency_health","version":"1.0.0","description":"Canonical DJConnect dependency-health evidence", "outputContract":"tde.dependency_health.v1", "analyzerSelection":"highest_priority", "qualificationRules":"complete_adapter_evidence", "supportedAnalyzers":("dependency_health.platform",)},
Expand All @@ -23,8 +23,8 @@ class AdapterRegistry:
def discover(self) -> tuple[object, ...]:
return (
{"id": "code_size.cloc", "version": "0.1.0", "analyzer": "cloc", "capabilities": ("code_size",), "minimumVersion": "2.10", "platforms": ("any",), "priority": 100},
{"id":"complexity.radon","version":"1.1.0","analyzer":"radon", "capabilities": ("complexity",), "minimumVersion": "6.0", "platforms": ("any",), "languages": ("Python",), "priority": 100},
{"id":"complexity.lizard","version":"1.1.0","analyzer":"lizard", "capabilities": ("complexity",), "minimumVersion": "1.23", "platforms": ("any",), "languages": ("JavaScript", "TypeScript", "Swift", "C", "C++", "C#"), "priority": 90},
{"id":"complexity.radon","version":"1.1.1","analyzer":"radon", "capabilities": ("complexity",), "minimumVersion": "6.0", "platforms": ("any",), "languages": ("Python",), "priority": 100},
{"id":"complexity.lizard","version":"1.1.1","analyzer":"lizard", "capabilities": ("complexity",), "minimumVersion": "1.23", "platforms": ("any",), "languages": ("JavaScript", "TypeScript", "Swift", "C", "C++", "C#"), "priority": 90},
{"id":"coverage.artifact","version":"0.1.0","analyzer":"coverage-artifact", "capabilities": ("coverage",), "minimumVersion": "1.0", "platforms": ("any",), "priority": 100},
{"id":"dependency_health.platform","version":"1.0.0","analyzer":"consumer-native", "capabilities": ("dependency_health",), "minimumVersion": "1.0", "platforms": ("any",), "priority": 100},
)
Expand Down
2 changes: 1 addition & 1 deletion src/tde_runtime/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from .runtime_qualification import RuntimeQualificationEngine
from .schemas import SchemaRegistry

RUNTIME_VERSION = "1.1.0"
RUNTIME_VERSION = "1.1.1"
EVIDENCE_SCHEMA_VERSION = "1.0.0"


Expand Down
4 changes: 2 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def test_version_includes_cli_runtime_schema_and_generation(self) -> None:
code, output = self.invoke("--format", "json", "--version")
self.assertEqual(ExitCode.SUCCESS, code)
version = json.loads(output)
self.assertEqual("1.1.0", version["cliVersion"])
self.assertEqual("1.1.0", version["runtimeVersion"])
self.assertEqual("1.1.1", version["cliVersion"])
self.assertEqual("1.1.1", version["runtimeVersion"])
self.assertEqual("1.0.0", version["schemaVersion"])
self.assertEqual("1", version["generation"])

Expand Down
32 changes: 32 additions & 0 deletions tests/test_complexity.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,38 @@ def test_lizard_normalizes_typescript_symbols_with_provenance(self):
self.assertEqual("complexity.lizard", result["symbols"][0]["adapterId"])
self.assertEqual("lizard==1.23.0", result["adapter"]["analyzer"]["package"])

def test_lizard_ignores_synthetic_csharp_file_statistics(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
target = root / "Program.cs"
target.write_text("class Program {}\n", encoding="utf-8")
native = (
f'116,1,117,0,1372,"*global*@0-1371@{target}","{target}",*global*,*global*,0,1371\n'
f'4,2,20,0,5,"Branch@2-6@{target}","{target}",Branch,Branch(),2,6\n'
)
with patch("tde_runtime.complexity.discover", return_value={"status": "VALID", "executable": "lizard", "version": "1.23.0"}), \
patch("tde_runtime.complexity.subprocess.run") as run:
run.return_value.stdout = native
result = _lizard(root, [target], ("C#",), 10)
self.assertEqual("VALID", result["status"])
self.assertEqual(["Branch"], [symbol["name"] for symbol in result["symbols"]])

def test_identical_analyzer_symbols_are_normalized_but_conflicts_block(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / "Program.cs").write_text("class Program {}\n", encoding="utf-8")
base = {"path": "Program.cs", "classification": "PRODUCT_SOURCE", "language": "C#", "name": "Branch", "type": "function", "line": 2, "endLine": 6, "complexity": 2, "adapterId": "complexity.lizard", "toolId": "lizard"}
adapter = {"id": "complexity.lizard", "version": "1.1.1", "analyzer": {"id": "lizard", "version": "1.23.0"}, "rawOutput": "", "rawOutputHash": "sha256:test"}
with patch("tde_runtime.complexity._lizard", return_value={"status": "VALID", "symbols": [base, dict(base)], "adapter": adapter}):
valid = analyze(root)
self.assertEqual("VALID", valid["status"])
self.assertEqual(1, len(valid["symbols"]))
conflicting = dict(base, complexity=3)
with patch("tde_runtime.complexity._lizard", return_value={"status": "VALID", "symbols": [base, conflicting], "adapter": adapter}):
invalid = analyze(root)
self.assertEqual("INVALID_EVIDENCE", invalid["status"])
self.assertEqual("complexity.symbol.duplicate", invalid["limitations"][0]["id"])

def test_primary_language_prevents_auxiliary_python_from_qualifying_csharp(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def test_corrupt_coverage_fails_closed(self) -> None:

def test_policy_threshold_uses_coverage_without_runtime_specific_policy_logic(self) -> None:
(self.root / "coverage.xml").write_text(COBERTURA, encoding="utf-8")
policy = {"identifier": "coverage-policy", "version": "1.0.0", "scope": "repository", "owner": "tests", "description": "coverage threshold", "supportedCapabilities": ["coverage"], "supportedSchemas": ["1.0.0"], "supportedRuntimeVersions": ["1.1.0"], "rules": [{"id": "minimum-line-coverage", "type": "threshold", "capability": "coverage", "metric": "coverage.line_coverage", "operator": "less_than", "threshold": {"warning": 80, "blocking": 60}, "severity": {"warning": "WARNING", "blocking": "BLOCKING"}, "enabled": True, "rationale": "coverage floor"}]}
policy = {"identifier": "coverage-policy", "version": "1.0.0", "scope": "repository", "owner": "tests", "description": "coverage threshold", "supportedCapabilities": ["coverage"], "supportedSchemas": ["1.0.0"], "supportedRuntimeVersions": ["1.1.1"], "rules": [{"id": "minimum-line-coverage", "type": "threshold", "capability": "coverage", "metric": "coverage.line_coverage", "operator": "less_than", "threshold": {"warning": 80, "blocking": 60}, "severity": {"warning": "WARNING", "blocking": "BLOCKING"}, "enabled": True, "rationale": "coverage floor"}]}
path = self.root / "policy.json"; path.write_text(json.dumps(policy), encoding="utf-8")
code, result = self.invoke("--policy", str(path), "assess", "--capability", "coverage", str(self.root))
self.assertEqual(ExitCode.FAILED, code)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_dependency_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def test_unsupported_repository_remains_valid_with_unavailable_evidence(self) ->

def test_unknown_dependency_and_policy_are_evaluated(self) -> None:
self.project(missing=True); self.npm({})
policy = {"identifier": "dependency-policy", "version": "1.0.0", "scope": "repository", "owner": "tests", "description": "dependency threshold", "supportedCapabilities": ["dependency_health"], "supportedSchemas": ["1.0.0"], "supportedRuntimeVersions": ["1.1.0"], "rules": [{"id": "unknown", "type": "threshold", "capability": "dependency_health", "metric": "dependency_health.unknown_dependencies", "operator": "greater_than", "threshold": {"warning": 1, "blocking": 1}, "severity": {"warning": "WARNING", "blocking": "BLOCKING"}, "enabled": True, "rationale": "unknown dependencies"}]}
policy = {"identifier": "dependency-policy", "version": "1.0.0", "scope": "repository", "owner": "tests", "description": "dependency threshold", "supportedCapabilities": ["dependency_health"], "supportedSchemas": ["1.0.0"], "supportedRuntimeVersions": ["1.1.1"], "rules": [{"id": "unknown", "type": "threshold", "capability": "dependency_health", "metric": "dependency_health.unknown_dependencies", "operator": "greater_than", "threshold": {"warning": 1, "blocking": 1}, "severity": {"warning": "WARNING", "blocking": "BLOCKING"}, "enabled": True, "rationale": "unknown dependencies"}]}
path = self.root / "policy.json"; path.write_text(json.dumps(policy), encoding="utf-8")
code, result = self.invoke("--policy", str(path), "assess", "--capability", "dependency_health", str(self.root))
self.assertEqual(ExitCode.FAILED, code)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_public_policy_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def configuration(threshold: int) -> dict[str, object]:
"identifier": "example.code-size", "version": "2026.1", "scope": "repository",
"owner": "example", "description": "Example organization policy.",
"supportedCapabilities": ["code_size"], "supportedSchemas": ["1.0.0"],
"supportedRuntimeVersions": ["1.1.0"],
"supportedRuntimeVersions": ["1.1.1"],
"rules": [{"id": "example.code-size.lines", "type": "threshold", "capability": "code_size",
"metric": "code_size.code_lines", "operator": "greater_than",
"threshold": {"warning": threshold, "blocking": threshold + 1000},
Expand Down Expand Up @@ -93,7 +93,7 @@ def test_installed_wheel_publishes_and_enforces_the_schema_contract(self) -> Non
*evidence["assessment"]["capabilityExecutions"]]:
self.assertEqual("1.0.0", item["schema"]["version"])
self.assertEqual("1", item["schema"]["compatibilityVersion"])
self.assertEqual("1.1.0", item["schema"]["runtimeVersion"])
self.assertEqual("1.1.1", item["schema"]["runtimeVersion"])
record = next((location / "evidence").glob("*.json"))
persisted = json.loads(record.read_text(encoding="utf-8"))
persisted["evidence"]["policyEvidence"]["schema"]["version"] = "999.0.0"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def test_policy_override_can_block_a_measurement(self) -> None:

def test_context_contains_canonical_runtime_values(self) -> None:
result = Runtime().execute(self.root)
self.assertEqual("1.1.0", result.context.runtime_version)
self.assertEqual("1.1.1", result.context.runtime_version)
self.assertEqual("1.0.0", result.context.schema_version)
self.assertTrue(result.context.execution_id.startswith("execution."))
self.assertEqual("content_digest", result.context.candidate["identityType"])
Expand Down