"""SecureCodeEnv - Consistency Grader v4 — clamped scores""" from codegraph.graph import CodeGraph from codegraph.extractor import extract_metadata from graders.clamp import clamp GOOD_PRACTICES = { "uses_type_hints": 0.15, "uses_docstrings": 0.15, "uses_try_catch": 0.10, "no_print_stmts": 0.10, "no_hardcoded_secrets": 0.10, } def grade_consistency(code: str, filename: str, graph: CodeGraph, step: int) -> dict: new_meta = extract_metadata(code, filename, step) conv = new_meta.conventions if not graph.components: checks = {k: 1.0 if conv.get(k, False) else 0.0 for k in GOOD_PRACTICES} raw = sum(checks.values()) / max(len(checks), 1) raw = max(0.45, raw) # floor so first step never crushes reward return {"score": clamp(raw), "checks": checks, "feedback": _first_feedback(raw, checks)} established = graph.conventions checks = {} naming = established.get("naming") if naming and naming != "mixed" and new_meta.functions: fns = new_meta.functions if naming == "snake_case": correct = sum(1 for f in fns if "_" in f["name"] or f["name"].islower()) else: correct = sum(1 for f in fns if f["name"] and f["name"][0].islower() and any(c.isupper() for c in f["name"])) checks["naming_convention"] = correct / len(fns) if established.get("error_handling") == "try_catch": checks["error_handling"] = 1.0 if conv.get("uses_try_catch") else 0.3 if established.get("uses_type_hints"): checks["type_hints"] = 1.0 if conv.get("uses_type_hints") else 0.4 if established.get("uses_docstrings"): checks["docstrings"] = 1.0 if conv.get("uses_docstrings") else 0.5 existing_no_print = all(c.conventions.get("no_print_stmts", True) for c in graph.components.values()) if existing_no_print: checks["no_print_drift"] = 1.0 if conv.get("no_print_stmts", True) else 0.3 reuse_opp = reuse_taken = 0 for name in graph.components: if name.lower() in code.lower(): reuse_opp += 1 if name in code: reuse_taken += 1 if reuse_opp > 0: checks["component_reuse"] = reuse_taken / reuse_opp raw = sum(checks.values()) / max(len(checks), 1) if checks else 0.7 return {"score": clamp(raw), "checks": checks, "feedback": _feedback(raw, checks)} def _first_feedback(score, checks): missing = [k for k, v in checks.items() if v == 0.0] if not missing: return f"Good conventions established ({score:.2f})" return f"Missing practices: {', '.join(missing)}" def _feedback(score, checks): if score >= 0.85: return "Excellent consistency with codebase" failing = [k for k, v in checks.items() if isinstance(v, float) and v < 0.5] return f"Convention drift in: {', '.join(failing)}" if failing else f"Minor drift ({score:.2f})"