Spaces:
Running on Zero
Running on Zero
| """Static code forensics β pure text analysis, no model, no dependencies. | |
| Imports nothing outside the standard library, so it runs and is testable | |
| without torch. | |
| Answers a different question than the stylometry model: not "does this read as | |
| AI-written" but "does this text bear the physical traces of having been pasted | |
| in rather than typed here". | |
| """ | |
| import re | |
| import statistics | |
| COMMENT_PREFIXES = ("#", "//", "/*", "*", "--", "<!--") | |
| IDENT_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*") | |
| CAMEL_RE = re.compile(r"^[a-z]+[A-Z]") | |
| # Curly quotes, en/em dashes, non-breaking space, ellipsis, zero-width space. | |
| # These cannot be typed into a code editor by accident β they arrive by paste | |
| # from a rendered web page, PDF, or chat UI. | |
| SMART_PUNCT_RE = re.compile("[ββββββΒ β¦β]") | |
| # Tutorial-style narration: one comment per step, restating what the next line | |
| # does. Characteristic of AI output and of copied walkthrough articles. | |
| AI_COMMENT_RE = re.compile( | |
| r"^\W*(step\s*\d|read\s+(the\s+)?input|initiali[sz]e|iterate|loop\s+through" | |
| r"|print\s+the|check\s+if|return\s+the|calculate\s+the|handle\s+(the\s+)?edge" | |
| r"|create\s+a|define\s+(a|the)|add\s+the|sort\s+the|first,|finally,)", | |
| re.I, | |
| ) | |
| KEYWORDS = frozenset( | |
| """if elif else for while do return def class import from as in is not and or none | |
| true false null nil public private protected static void int long float double char | |
| bool boolean string str var let const function new this self try except catch finally | |
| raise throw with lambda yield break continue pass switch case default struct enum | |
| typedef sizeof namespace using include main print println printf scanf cout cin endl | |
| std len range list dict set tuple map filter input output append push""".split() | |
| ) | |
| def static_features(code): | |
| """Language-agnostic forensic features. Pure text analysis, no model.""" | |
| lines = code.splitlines() | |
| stripped = [ln.strip() for ln in lines] | |
| body = [ln for ln in lines if ln.strip()] | |
| n_body = len(body) or 1 | |
| comments = [s for s in stripped if s.startswith(COMMENT_PREFIXES)] | |
| lens = [len(ln) for ln in body] or [0] | |
| indented = [ln for ln in body if ln[:1] in (" ", "\t")] | |
| idents = [w for w in IDENT_RE.findall(code) if w.lower() not in KEYWORDS] | |
| uniq = set(idents) | |
| n_uniq = len(uniq) or 1 | |
| return { | |
| "chars": len(code), | |
| "lines": len(lines), | |
| "blank_ratio": round(sum(1 for s in stripped if not s) / (len(lines) or 1), 3), | |
| "comment_ratio": round(len(comments) / n_body, 3), | |
| "ai_style_comments": sum(1 for c in comments if AI_COMMENT_RE.match(c)), | |
| "avg_line_len": round(statistics.fmean(lens), 1), | |
| "stdev_line_len": round(statistics.pstdev(lens), 1) if len(lens) > 1 else 0.0, | |
| "max_line_len": max(lens), | |
| "mixed_indent": any(ln.startswith("\t") for ln in indented) | |
| and any(ln.startswith(" ") for ln in indented), | |
| "trailing_ws_lines": sum(1 for ln in lines if ln != ln.rstrip()), | |
| "crlf": "\r\n" in code, | |
| "smart_punct": len(SMART_PUNCT_RE.findall(code)), | |
| "non_ascii": sum(1 for ch in code if ord(ch) > 127), | |
| "unique_identifiers": len(uniq), | |
| "avg_identifier_len": round( | |
| statistics.fmean([len(w) for w in uniq]) if uniq else 0.0, 2 | |
| ), | |
| "single_char_ratio": round(sum(1 for w in uniq if len(w) == 1) / n_uniq, 3), | |
| "snake_ratio": round( | |
| sum(1 for w in uniq if "_" in w and w.islower()) / n_uniq, 3 | |
| ), | |
| "camel_ratio": round(sum(1 for w in uniq if CAMEL_RE.match(w)) / n_uniq, 3), | |
| "has_docstring": '"""' in code or "'''" in code, | |
| "has_type_hints": bool( | |
| re.search(r"->\s*[A-Za-z\[]|:\s*(int|str|float|bool|List|Dict)\b", code) | |
| ), | |
| "has_main_guard": "__name__" in code and "__main__" in code, | |
| } | |
| # Signal -> (weight, explanation). Weights are the calibration knob: they encode | |
| # how strongly each trace implies "not typed here by this person". | |
| SIGNAL_WEIGHTS = { | |
| "unicode_punctuation": (35, "Curly quotes/dashes β pasted from a rendered page or chat UI, not typed"), | |
| "ai_narration_comments": (25, "Step-by-step narrating comments in AI/tutorial style"), | |
| "mixed_indentation": (20, "Tabs and spaces mixed β assembled from more than one source"), | |
| "heavy_commenting": (15, "Comment density unusually high for a timed contest answer"), | |
| "production_style": (15, "Docstrings + type hints β production habits, rare under time pressure"), | |
| "inconsistent_naming": (15, "snake_case and camelCase both used heavily β merged sources"), | |
| "uniform_line_lengths": (10, "Machine-uniform line lengths"), | |
| "crlf_endings": (10, "CRLF line endings β came from another environment"), | |
| } | |
| def forensic_signals(f): | |
| """Fire named signals from a feature dict. Returns (score 0-100, signals).""" | |
| fired = [] | |
| if f["smart_punct"]: | |
| fired.append("unicode_punctuation") | |
| if f["ai_style_comments"] >= 3: | |
| fired.append("ai_narration_comments") | |
| if f["mixed_indent"]: | |
| fired.append("mixed_indentation") | |
| if f["comment_ratio"] > 0.30: | |
| fired.append("heavy_commenting") | |
| if f["has_docstring"] and f["has_type_hints"]: | |
| fired.append("production_style") | |
| if f["snake_ratio"] > 0.25 and f["camel_ratio"] > 0.25: | |
| fired.append("inconsistent_naming") | |
| if f["lines"] > 15 and f["stdev_line_len"] < 6: | |
| fired.append("uniform_line_lengths") | |
| if f["crlf"]: | |
| fired.append("crlf_endings") | |
| score = min(100, sum(SIGNAL_WEIGHTS[s][0] for s in fired)) | |
| return score, [ | |
| {"code": s, "weight": SIGNAL_WEIGHTS[s][0], "detail": SIGNAL_WEIGHTS[s][1]} | |
| for s in fired | |
| ] | |