fix: stabilize agent file change previews
This commit is contained in:
@@ -24,8 +24,8 @@ except ModuleNotFoundError: # direct execution as `python scripts/...py`
|
||||
)
|
||||
|
||||
|
||||
def candidate_license(text: str) -> tuple[str | None, str]:
|
||||
"""Suggest an SPDX identifier only when the license's identifying text is clear.
|
||||
def license_signatures(text: str) -> dict[str, str]:
|
||||
"""Return conservative SPDX-like signatures found anywhere in the file.
|
||||
|
||||
Suggestions are for human triage. They do not establish provenance, package
|
||||
applicability, exceptions, or permission to redistribute.
|
||||
@@ -33,32 +33,39 @@ def candidate_license(text: str) -> tuple[str | None, str]:
|
||||
normalized = re.sub(r"(?m)^\s*(?://|#|\*)\s?", "", text).lower()
|
||||
normalized = re.sub(r"\s+", " ", normalized)
|
||||
|
||||
matches: list[tuple[str, str]] = []
|
||||
matches: dict[str, str] = {}
|
||||
if "mozilla public license version 2.0" in normalized:
|
||||
matches.append(("MPL-2.0", "license text contains the Mozilla Public License 2.0 title"))
|
||||
matches["MPL-2.0"] = "Mozilla Public License 2.0 title"
|
||||
if "apache license" in normalized and "version 2.0" in normalized:
|
||||
matches.append(("Apache-2.0", "license text contains the Apache License 2.0 title"))
|
||||
matches["Apache-2.0"] = "Apache License 2.0 title"
|
||||
if (
|
||||
"permission is hereby granted, free of charge" in normalized
|
||||
and "the software is provided" in normalized
|
||||
and "in no event shall" in normalized
|
||||
):
|
||||
matches.append(("MIT", "license text contains the standard MIT grant and warranty disclaimer"))
|
||||
matches["MIT"] = "standard MIT grant and warranty disclaimer"
|
||||
if (
|
||||
"redistribution and use in source and binary forms" in normalized
|
||||
and "neither the name" in normalized
|
||||
and "disclaimer" in normalized
|
||||
):
|
||||
matches.append(("BSD-3-Clause", "license text contains the three-clause BSD endorsement restriction"))
|
||||
matches["BSD-3-Clause"] = "BSD three-clause endorsement restriction"
|
||||
if (
|
||||
"redistribution and use in source and binary forms" in normalized
|
||||
and "neither the name" not in normalized
|
||||
and "disclaimer" in normalized
|
||||
and "provided that the following conditions are met" in normalized
|
||||
):
|
||||
matches.append(("BSD-2-Clause", "license text contains a two-clause BSD-style grant and disclaimer"))
|
||||
matches["BSD-2-Clause"] = "BSD two-clause grant and disclaimer"
|
||||
return matches
|
||||
|
||||
|
||||
def candidate_license(text: str) -> tuple[str | None, str]:
|
||||
"""Suggest one SPDX identifier only when exactly one signature is present."""
|
||||
matches = license_signatures(text)
|
||||
if len(matches) == 1:
|
||||
return matches[0]
|
||||
license_id, basis = next(iter(matches.items()))
|
||||
return license_id, basis
|
||||
if len(matches) > 1:
|
||||
return None, "multiple license signatures occur in this file; manual review required"
|
||||
return None, "no conservative license-text signature matched"
|
||||
@@ -81,6 +88,7 @@ def build_report(root: Path) -> dict[str, object]:
|
||||
|
||||
text = license_file.read_text(encoding="utf-8", errors="replace")
|
||||
candidate, basis = candidate_license(text)
|
||||
signatures = license_signatures(text)
|
||||
entries.append(
|
||||
{
|
||||
"name": name,
|
||||
@@ -89,18 +97,30 @@ def build_report(root: Path) -> dict[str, object]:
|
||||
"license_file": license_file.name,
|
||||
"license_file_sha256": digest(license_file),
|
||||
"candidate_spdx": candidate,
|
||||
"detected_spdx_candidates": sorted(signatures),
|
||||
"candidate_basis": basis,
|
||||
"status": "candidate only; human review required",
|
||||
"status": (
|
||||
"single signature candidate; human review required"
|
||||
if candidate
|
||||
else "composite or unclassified notice; manual review required"
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
counts: dict[str, int] = {}
|
||||
signature_counts: dict[str, int] = {}
|
||||
multi_signature_files = 0
|
||||
for entry in entries:
|
||||
candidate = entry["candidate_spdx"] or "unclassified"
|
||||
counts[str(candidate)] = counts.get(str(candidate), 0) + 1
|
||||
detected = entry["detected_spdx_candidates"]
|
||||
if len(detected) > 1:
|
||||
multi_signature_files += 1
|
||||
for license_id in detected:
|
||||
signature_counts[license_id] = signature_counts.get(license_id, 0) + 1
|
||||
|
||||
return {
|
||||
"format": "flutter-license-triage-v1",
|
||||
"format": "flutter-license-triage-v2",
|
||||
"generated_at": datetime.now(UTC).isoformat(),
|
||||
"source": "flutter_app/pubspec.lock and resolved package LICENSE files",
|
||||
"notice": (
|
||||
@@ -112,6 +132,8 @@ def build_report(root: Path) -> dict[str, object]:
|
||||
"packages_with_license_file": len(entries),
|
||||
"packages_without_license_file": len(missing),
|
||||
"candidate_counts": dict(sorted(counts.items())),
|
||||
"multi_signature_files": multi_signature_files,
|
||||
"detected_signature_counts": dict(sorted(signature_counts.items())),
|
||||
},
|
||||
"packages": sorted(entries, key=lambda item: str(item["name"]).lower()),
|
||||
"missing_license_file": sorted(missing, key=lambda item: item["name"].lower()),
|
||||
|
||||
Reference in New Issue
Block a user