Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/scripts/pr-scope-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ SOFT_FLAGS=()
# Known top-level allow-list
# ---------------------------------------------------------------------------

ALLOWED_TOP_DIRS=(faderpunk libfp configurator gen-bindings docs .github)
ALLOWED_TOP_DIRS=(faderpunk libfp fpapp fpapp-sdk configurator gen-bindings docs .github)
ALLOWED_ROOT_FILES=(
README.md CONTRIBUTING.md AGENTS.md CLAUDE.md CODE_OF_CONDUCT.md LICENSE
Cargo.toml Cargo.lock knope.toml devenv.nix devenv.yaml devenv.lock
Expand Down Expand Up @@ -154,8 +154,10 @@ for i in "${!FILENAMES[@]}"; do
touches_manual=true
elif [[ "$f" == faderpunk/src/tasks/midi.rs || "$f" == faderpunk/src/storage.rs ]]; then
: # legitimate common companion touches for app PRs, not counted against any category
elif [[ "$f" == libfp/src/* || "$f" == "libfp/Cargo.toml" ]]; then
elif [[ "$f" == libfp/src/* || "$f" == "libfp/Cargo.toml" || "$f" == fpapp-sdk/* ]]; then
touches_libfp=true
elif [[ "$f" == fpapp/* ]]; then
touches_ci_tooling=true
elif [[ "$f" == gen-bindings/* ]]; then
touches_gen_bindings=true
elif [[ "$f" == configurator/* ]]; then
Expand Down Expand Up @@ -320,6 +322,8 @@ fi

if [[ "$is_app_category" == true && "$touches_libfp" == true ]]; then
SOFT_FLAGS+=("App PR also touches \`libfp/src/**\` — legitimate before (e.g. GenSeq's shared slide utility), but worth a look.")
elif [[ "$touches_libfp" == true ]]; then
SOFT_FLAGS+=("Touches \`libfp/src/**\`/\`fpapp-sdk/**\` — shared protocol/ABI surface that ripples into both firmware and configurator, always worth a manual look (see CONTRIBUTING.md's Protocol / libfp row).")
fi

if [[ ${#other_apps_touched[@]} -gt 0 ]]; then
Expand Down
48 changes: 29 additions & 19 deletions .github/scripts/pr-scope-check.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,37 @@ trap 'rm -rf "$TMP"' EXIT
pass=0
fail=0

# name : expect_exit : expect_hard_fail_count (or "any">=1 style via -ge marker) : note
# Format per line: "<fixture> <expected_exit> <expected_hardfail_op><n>"
# name : expect_exit : expect_hard_fail_count (or "any">=1 style via -ge marker) : required substrings
# Format per line: "<fixture> <expected_exit> <expected_hardfail_op><n> <substring_spec>"
# expected_hardfail_op is "=" or ">="
# substring_spec is "-" (no content check) or a literal substring (may
# contain spaces — it's the rest of the line) that must appear somewhere in
# the summary markdown (covers both the "**Category**: ..." line and
# SOFT_FLAGS bullets) — catches a regression that keeps the hard-fail count
# right but silently drops or changes the category/soft-flag content the
# fixture exists to protect.
CASES=(
"573 0 =0"
"602 1 >=1"
"612 1 >=1"
"529 0 =0"
"629 1 =2"
"521 1 =1"
"607 1 =2"
"603 1 =1"
"474 1 =1"
"637 1 =1"
"645 0 =0"
"601 0 =0"
"614 0 =0"
"synthetic-mixed-commits 0 =0"
"synthetic-nested-use-bypass 1 =1"
"synthetic-rename 0 =0"
"573 0 =0 -"
"602 1 >=1 -"
"612 1 >=1 -"
"529 0 =0 -"
"629 1 =2 -"
"521 1 =1 -"
"607 1 =2 -"
"603 1 =1 -"
"474 1 =1 -"
"637 1 =1 -"
"645 0 =0 -"
"601 0 =0 -"
"614 0 =0 -"
"synthetic-mixed-commits 0 =0 -"
"synthetic-nested-use-bypass 1 =1 -"
"synthetic-rename 0 =0 -"
"synthetic-fpapp-directories 0 =0 Touches \`libfp/src/**\`/\`fpapp-sdk/**\`"
)

for case_line in "${CASES[@]}"; do
read -r name expect_exit hf_spec <<<"$case_line"
read -r name expect_exit hf_spec expect_substr <<<"$case_line"
files="$DATA/$name-files.json"
commits="$DATA/$name-commits.json"
if [[ ! -f "$files" || ! -f "$commits" ]]; then
Expand Down Expand Up @@ -77,6 +84,9 @@ for case_line in "${CASES[@]}"; do
status="ok"
[[ "$actual_exit" -ne "$expect_exit" ]] && status="FAIL(exit: got $actual_exit want $expect_exit)"
[[ "$hf_ok" == "no" ]] && status="$status FAIL(hardfails: got $hf_count want $hf_desc)"
if [[ "$expect_substr" != "-" && "$expect_substr" != "" ]]; then
grep -qF -- "$expect_substr" "$summary" || status="$status FAIL(missing content: \"$expect_substr\")"
fi

if [[ "$status" == "ok" ]]; then
echo "PASS $name (exit=$actual_exit, hard-fails=$hf_count)"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"commit": {
"message": "feat(fpapp): add native package tooling"
}
}
]
16 changes: 16 additions & 0 deletions .github/scripts/testdata/synthetic-fpapp-directories-files.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"filename": "fpapp/src/main.rs",
"status": "added",
"additions": 20,
"deletions": 0,
"patch": "@@ -0,0 +1,1 @@\n+fn main() {}"
},
{
"filename": "fpapp-sdk/src/lib.rs",
"status": "added",
"additions": 20,
"deletions": 0,
"patch": "@@ -0,0 +1,1 @@\n+pub fn host_abi() {}"
}
]
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ Every PR is automatically classified by what it touches, not by what you say it
| **App fix** | Only the existing app's own file(s) under `faderpunk/src/apps/` |
| **Firmware core** | Core firmware files outside `apps/` (e.g. `app.rs`, `layout.rs`, `tasks/*`, `memory.x`, `.cargo/config.toml`) |
| **Configurator** | Files under `configurator/` |
| **Protocol / libfp** | Shared `libfp/` types and generated bindings — always flagged for a manual look, since these ripple into both firmware and configurator |
| **Protocol / libfp** | Shared `libfp/` types, the `fpapp-sdk/` ABI facade, and generated bindings — always flagged for a manual look, since these ripple into both firmware and configurator |
| **Docs** | README, docs folder, etc. |
| **CI / tooling** | Workflow and build-tooling files — legitimate on their own, but not bundled with feature work |
| **CI / tooling** | Workflow and build-tooling files, including the `fpapp/` package builder — legitimate on their own, but not bundled with feature work |

**Automatically rejected** (once enforcement is on — see "Rollout status" below):
- Touching a file outside all the categories above — this usually means an unrelated standalone project got bundled into the PR.
Expand Down
Loading