@@ -5,6 +5,9 @@ set -euo pipefail
55SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) "
66source " $SCRIPT_DIR /lib/json-emit.sh"
77source " $SCRIPT_DIR /lib/predicates.sh"
8+ source " $SCRIPT_DIR /lib/hunk-filter.sh"
9+ source " $SCRIPT_DIR /lib/baseline.sh"
10+ source " $SCRIPT_DIR /lib/peer-consistency.sh"
811
912LANGUAGE=" ${LANGUAGE:- python} "
1013REPO_ROOT=" ${REPO_ROOT:- .} "
@@ -33,20 +36,31 @@ while IFS= read -r mod; do
3336
3437 [ -d " $mod_dir " ] || continue
3538
36- # PY-PT-01 / JV-PT-01: factory exists — only fire on client modules
39+ # PY-PT-01 / JV-PT-01: peer-consistency check (FP-G-01).
40+ # Previously: BLOCK if client module missing create_client()/ClientFactory.
41+ # Empirically wrong — only `destination` follows that shape. Now: FLAG only
42+ # when the module diverges from an element that ≥80% of peers adopt.
43+ # Tier: FLAG (never BLOCK).
3744 shape=$( module_shape " $mod_dir " )
3845 if [ " $shape " = " client" ]; then
39- if [ " $LANGUAGE " = " python" ]; then
40- if ! grep -rqE ' ^def create_[a-z_]*client\(' " $mod_dir " 2> /dev/null; then
41- emit_finding " PY-PT-01" " BLOCK" " src/sap_cloud_sdk/$mod /" 1 \
42- " Client module '$mod ' missing create_client() factory function" " " >> " $findings "
46+ for element in factory client config user-guide.md exceptions py.typed; do
47+ # py.typed only applies to Python
48+ if [ " $element " = " py.typed" ] && [ " $LANGUAGE " != " python" ]; then continue ; fi
49+ # exceptions is checked separately below with AST for accuracy
50+ if [ " $element " = " exceptions" ]; then continue ; fi
51+ if should_flag_peer_divergence " $mod_dir " " $element " " $LANGUAGE " " 0.80" ; then
52+ if [ " $LANGUAGE " = " python" ]; then
53+ rule_id=" PY-PT-01"
54+ rel=" src/sap_cloud_sdk/$mod /"
55+ else
56+ rule_id=" JV-PT-01"
57+ rel=" src/main/java/com/sap/cloud/sdk/$mod /"
58+ fi
59+ emit_finding " $rule_id " " FLAG" " $rel " 1 \
60+ " Module '$mod ' diverges from peer convention: missing '$element ' (≥80% of peer modules have it)" \
61+ " Consider adding $element for consistency with sibling modules" >> " $findings "
4362 fi
44- else
45- if ! grep -rqE ' class [A-Z][A-Za-z]*ClientFactory' " $mod_dir " 2> /dev/null; then
46- emit_finding " JV-PT-01" " BLOCK" " src/main/java/com/sap/cloud/sdk/$mod /" 1 \
47- " Client module '$mod ' missing ClientFactory class" " " >> " $findings "
48- fi
49- fi
63+ done
5064 fi
5165
5266 # PY-PT-03: py.typed marker
@@ -58,10 +72,12 @@ while IFS= read -r mod; do
5872 fi
5973
6074 # PY-PT-04 / JV-PT-05: module-specific exceptions
75+ # FP-C-02: pass if module has ANY class subclassing Exception (or *Error/*Exception),
76+ # regardless of whether it lives in exceptions.py or __init__.py or elsewhere.
6177 if [ " $LANGUAGE " = " python" ]; then
62- if [ ! -f " $mod_dir /exceptions .py" ] ; then
78+ if ! python3 " $SCRIPT_DIR /lib/ast_python_checks .py" pt-04 " $mod_dir " 2> /dev/null ; then
6379 emit_finding " PY-PT-04" " FLAG" " src/sap_cloud_sdk/$mod /exceptions.py" 1 \
64- " Module lacks exceptions.py — module-specific exception hierarchy recommended " " " >> " $findings "
80+ " Module lacks Exception subclasses — define a module-specific exception hierarchy (in exceptions.py or __init__.py) " " " >> " $findings "
6581 fi
6682 else
6783 if [ ! -d " $mod_dir /exceptions" ]; then
@@ -73,11 +89,28 @@ while IFS= read -r mod; do
7389done <<< " $modules"
7490
7591# PY-PT-08 via AST on changed Python files
92+ # FP-A-01: filter by ADDED_LINES_FILE (only fire on functions declared on lines the PR touched)
93+ # FP-E-01: consult line-level baseline for pre-existing PT-08 debt
7694if [ " $LANGUAGE " = " python" ]; then
7795 changed_py=$( echo " $diff_content " | grep -oE ' ^\+\+\+ b/src/sap_cloud_sdk/.*\.py' | sed ' s|^+++ b/||' | grep -v ' __pycache__' | sort -u)
7896 if [ -n " $changed_py " ]; then
97+ raw_pt08=$( mktemp) ; trap ' rm -f "$raw_pt08"' EXIT
7998 # shellcheck disable=SC2086
80- python3 " $SCRIPT_DIR /lib/ast_python_checks.py" pt-08 $changed_py 2> /dev/null >> " $findings " || true
99+ python3 " $SCRIPT_DIR /lib/ast_python_checks.py" pt-08 $changed_py 2> /dev/null > " $raw_pt08 " || true
100+ # Filter: keep only findings on lines touched by this PR AND not in baseline
101+ while IFS= read -r finding; do
102+ [ -z " $finding " ] && continue
103+ f=$( echo " $finding " | python3 -c " import json,sys;o=json.loads(sys.stdin.read());print(o.get('file',''))" )
104+ ln=$( echo " $finding " | python3 -c " import json,sys;o=json.loads(sys.stdin.read());print(o.get('line',0))" )
105+ [ -z " $f " ] && continue
106+ # Baseline check first (bypasses hunk filter — always suppressed)
107+ if is_in_line_baseline " PY-PT-08" " $f " " $ln " ; then continue ; fi
108+ # Hunk filter
109+ if is_line_touched " $f " " $ln " ; then
110+ echo " $finding " >> " $findings "
111+ fi
112+ done < " $raw_pt08 "
113+ rm -f " $raw_pt08 "
81114 fi
82115fi
83116
0 commit comments