Skip to content

Commit f574af2

Browse files
feat(scripts): wire recipe-fix-pmpl-drift from hypatia#423 (#256)
Closes the hypatia → gitbot-fleet loop for PMPL drift detection per the 2026-06-02 estate license policy. After this lands, the gitbot-fleet dispatcher can route hypatia findings of pattern `PA-license-pmpl_drift` to `fix-pmpl-drift.sh` and auto-file PRs for any new drift detected estate-wide. References: standards#308 (umbrella campaign, ~14k files swept), standards#349 (docs/LICENSE-POLICY.md canonical reference), hypatia#423 (source recipe + script). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e312f46 commit f574af2

2 files changed

Lines changed: 127 additions & 2 deletions

File tree

scripts/fix-pmpl-drift.sh

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# fix-pmpl-drift.sh — Flip legacy PMPL-1.0-or-later SPDX stamps to repo-category target
4+
# Recipe: recipe-fix-pmpl-drift (confidence: 0.95, auto_fixable: true)
5+
#
6+
# Per 2026-06-02 owner directive:
7+
# sole-owner default → MPL-2.0
8+
# son-shared → AGPL-3.0-or-later (confirmed: idaptik, burble, standards, rattlescript, vcl-ut)
9+
# palimpsest carve-out → KEEP PMPL (palimpsest-license, palimpsest-plasma; consent-aware-http special)
10+
# 007 → DO NOT TOUCH (ARR)
11+
# third-party/forks → DO NOT TOUCH
12+
#
13+
# Usage: fix-pmpl-drift.sh <repo-path> [--dry-run] [--file <specific-file>]
14+
15+
set -euo pipefail
16+
17+
REPO_PATH="${1:?Usage: fix-pmpl-drift.sh <repo-path> [--dry-run]}"
18+
DRY_RUN=""
19+
SPECIFIC_FILE=""
20+
21+
shift
22+
while [ $# -gt 0 ]; do
23+
case "$1" in
24+
--dry-run) DRY_RUN=1 ;;
25+
--file) SPECIFIC_FILE="$2"; shift ;;
26+
*) echo "Unknown arg: $1" >&2; exit 1 ;;
27+
esac
28+
shift
29+
done
30+
31+
REPO_NAME=$(basename "$REPO_PATH")
32+
33+
# ─── Carve-out check ───
34+
SKIP_REPOS=(
35+
"007"
36+
"palimpsest-license"
37+
"palimpsest-plasma"
38+
"game-server-admin"
39+
"airborne-submarine-squadron"
40+
"paint-type"
41+
)
42+
for skip in "${SKIP_REPOS[@]}"; do
43+
if [ "$REPO_NAME" = "$skip" ]; then
44+
echo "SKIP: $REPO_NAME is on the exclude_repos list (carve-out or third-party)"
45+
exit 0
46+
fi
47+
done
48+
49+
# ─── Category classification via LICENSE file ───
50+
LICENSE_PATH="$REPO_PATH/LICENSE"
51+
if [ ! -f "$LICENSE_PATH" ]; then
52+
echo "WARN: no LICENSE file at $LICENSE_PATH — skipping (cannot classify)"
53+
exit 0
54+
fi
55+
56+
LIC_HEAD=$(head -1 "$LICENSE_PATH" | tr -d '\n')
57+
58+
if echo "$LIC_HEAD" | grep -q "MPL-2.0"; then
59+
TARGET="MPL-2.0"
60+
CATEGORY="sole-owner"
61+
elif echo "$LIC_HEAD" | grep -q "AGPL-3.0"; then
62+
TARGET="AGPL-3.0-or-later"
63+
CATEGORY="son-shared"
64+
elif echo "$LIC_HEAD" | grep -q "PMPL-1.0-or-later"; then
65+
echo "INFO: $REPO_NAME LICENSE is PMPL-1.0-or-later (carve-out or staging) — skipping"
66+
exit 0
67+
else
68+
echo "WARN: $REPO_NAME LICENSE is unfamiliar (\"$LIC_HEAD\") — skipping (manual review)"
69+
exit 0
70+
fi
71+
72+
# ─── Sub-path exclusions ───
73+
EXCLUDE_PATTERNS=(
74+
"/.git/"
75+
"/LICENSES/"
76+
"/rescript-tea/"
77+
"/rescript-vite/"
78+
"/affinescript-vite/"
79+
"/idaptik-rescript13-staging/"
80+
"/consent-aware-http/"
81+
)
82+
GREP_EXCLUDE=""
83+
for p in "${EXCLUDE_PATTERNS[@]}"; do
84+
GREP_EXCLUDE+="${GREP_EXCLUDE:+|}$p"
85+
done
86+
87+
# ─── Submodule exclusion (skip submodule paths) ───
88+
SUB_PATHS=""
89+
if [ -f "$REPO_PATH/.gitmodules" ]; then
90+
while IFS= read -r submodule_path; do
91+
SUB_PATHS+="${SUB_PATHS:+|}${submodule_path}/"
92+
done < <(grep "path =" "$REPO_PATH/.gitmodules" | awk '{print $3}')
93+
fi
94+
[ -n "$SUB_PATHS" ] && GREP_EXCLUDE="$GREP_EXCLUDE|$SUB_PATHS"
95+
96+
# ─── Find drift files ───
97+
cd "$REPO_PATH"
98+
if [ -n "$SPECIFIC_FILE" ]; then
99+
FILES="$SPECIFIC_FILE"
100+
else
101+
FILES=$(grep -rl "SPDX-License-Identifier: PMPL-1.0-or-later" . 2>/dev/null | grep -vE "$GREP_EXCLUDE" || true)
102+
fi
103+
104+
COUNT=$(echo "$FILES" | grep -c . 2>/dev/null || echo 0)
105+
106+
if [ "$COUNT" -eq 0 ]; then
107+
echo "OK: $REPO_NAME has no PMPL drift in safe scope"
108+
exit 0
109+
fi
110+
111+
echo "$REPO_NAME ($CATEGORY$TARGET): $COUNT files with PMPL drift"
112+
113+
if [ -n "$DRY_RUN" ]; then
114+
echo "$FILES" | head -20
115+
[ "$COUNT" -gt 20 ] && echo "... and $((COUNT - 20)) more"
116+
exit 0
117+
fi
118+
119+
# ─── Flip ───
120+
echo "$FILES" | xargs sed -i "s|SPDX-License-Identifier: PMPL-1.0-or-later|SPDX-License-Identifier: $TARGET|g"
121+
122+
REMAINING=$(grep -rl "SPDX-License-Identifier: PMPL-1.0-or-later" . 2>/dev/null | grep -vE "$GREP_EXCLUDE" | wc -l)
123+
echo "Flipped $COUNT files. Remaining PMPL in scope: $REMAINING."

scripts/fix-script-registry.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
"MissingVerisimdbFeed": "fix-missing-verisimdb-feed.sh",
4646
"MissingFeedbackIntegration": "fix-missing-feedback-integration.sh",
4747
"MissingVexometerHooks": "fix-missing-vexometer-hooks.sh",
48-
"TrackedNpmLockfile": "fix-tracked-package-lock.sh"
48+
"TrackedNpmLockfile": "fix-tracked-package-lock.sh",
49+
"LicensePMPLDrift": "fix-pmpl-drift.sh"
4950
},
5051
"by_recipe": {
5152
"recipe-heredoc-to-install": "fix-heredoc-install.sh",
@@ -103,7 +104,8 @@
103104
"chapel-manpath-guard": "fix-chapel-manpath-guard.sh",
104105
"chapel-chpl-llvm-export": "fix-chapel-chpl-llvm-export.sh",
105106
"chapel-replace-chpl-about-with-version": "fix-chapel-replace-chpl-about.sh",
106-
"reusable_workflow_sha_bump_needs_propagation": "propagate-sha-bump.sh"
107+
"reusable_workflow_sha_bump_needs_propagation": "propagate-sha-bump.sh",
108+
"recipe-fix-pmpl-drift": "fix-pmpl-drift.sh"
107109
}
108110
}
109111
}

0 commit comments

Comments
 (0)