fix: stabilize agent file change previews

This commit is contained in:
Hamza Ayed
2026-10-07 21:10:36 +03:00
parent fc36f54f15
commit 95c9bc231b
9 changed files with 823 additions and 224 deletions
@@ -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()),
@@ -75,11 +75,22 @@ try {
-ContentType 'application/json' `
-Body $previewRequest `
-TimeoutSec 240
$previewDiff = [string]$previewResult.proposal.diff
$unchanged = [IO.File]::ReadAllText($readmePath).Contains('The greet function returns')
if ($previewResult.tool -ne 'propose_file_change' -or
$previewResult.proposal.path -ne 'README.md' -or
-not $unchanged) {
throw 'The live agent did not return a README.md diff preview while preserving the file.'
if ($previewResult.tool -ne 'propose_file_change') {
throw "The live model selected '$($previewResult.tool)' instead of propose_file_change."
}
if ($previewResult.proposal.path -ne 'README.md') {
throw 'The live proposal targeted a different file than README.md.'
}
if ([string]::IsNullOrWhiteSpace($previewDiff)) {
throw 'The live proposal did not include diff text.'
}
if ($previewDiff -notmatch '(?m)^\+[^+]') {
throw 'The live proposal diff did not contain an added line.'
}
if (-not $unchanged) {
throw 'The smoke test changed README.md before explicit approval.'
}
[pscustomobject]@{
@@ -91,6 +102,7 @@ try {
preview_tool = $previewResult.tool
preview_path = $previewResult.proposal.path
preview_operation = $previewResult.proposal.operation
preview_diff_lines = @($previewDiff -split "`n").Count
file_unchanged_without_user_approval = $unchanged
} | ConvertTo-Json -Depth 5
}