@boyingliu01/xp-gate 0.34.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gate-13-compiler-strictness.sh +129 -4
- package/gate-3.sh +46 -2
- package/gate-4.sh +28 -4
- package/hooks/gate-13-compiler-strictness.sh +129 -4
- package/hooks/gate-3.sh +46 -2
- package/hooks/gate-4.sh +28 -4
- package/hooks/lib/ccn-ratchet.sh +109 -0
- package/lib/__tests__/doctor-timeout-partial.test.js +79 -0
- package/lib/__tests__/foreign-hooks-global.test.js +125 -0
- package/lib/__tests__/uninstall-global-residue.test.js +160 -0
- package/lib/doctor.js +130 -3
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/uninstall.js +225 -1
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/principles/rules/solid/dip.ts +94 -14
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
|
@@ -501,6 +501,108 @@ gate13_javac_args() {
|
|
|
501
501
|
printf '%s\n' "-Xlint:all,-processing -d"
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
+
# ---------------------------------------------------------------------------
|
|
505
|
+
# AC-670-34 / AC-670-35 (#659) — counting javac lint findings, locale-proof.
|
|
506
|
+
#
|
|
507
|
+
# The previous detector was an allow-list of English `[category]` names applied to
|
|
508
|
+
# `[WARNING]` lines. Two independent ways it returned 0 on defective code:
|
|
509
|
+
#
|
|
510
|
+
# 1. NOVELTY: a JDK adds a category the list does not name (`future-category`),
|
|
511
|
+
# or the fix relies on the list staying in sync with javac. A fixed allow-list
|
|
512
|
+
# over an open enum is a fail-OPEN design.
|
|
513
|
+
# 2. LOCALISATION: on a non-English build javac's own hint drifts, and the
|
|
514
|
+
# `[WARNING]` prefix may too. Measured on a zh_CN Maven build, the tell is
|
|
515
|
+
# "使用了未经检查或不安全的操作 ... 请使用 -Xlint:unchecked 重新编译" -- a message
|
|
516
|
+
# javac emits precisely when -Xlint was NOT in effect.
|
|
517
|
+
#
|
|
518
|
+
# Fail-closed policy (#499/#570) applied to text matching: count any bracketed
|
|
519
|
+
# lowercase category (whatever it is named), and treat javac's "you should have
|
|
520
|
+
# used -Xlint" hint as a finding too -- the code has an unchecked/unsafe operation
|
|
521
|
+
# whether or not the flag that would name it was on.
|
|
522
|
+
#
|
|
523
|
+
# Prints a single integer. Never fails (always exits 0).
|
|
524
|
+
# ---------------------------------------------------------------------------
|
|
525
|
+
gate13_count_lint_findings() {
|
|
526
|
+
local src="${1:-}"
|
|
527
|
+
[ -n "$src" ] || src=$(cat)
|
|
528
|
+
printf '%s\n' "$src" | awk '
|
|
529
|
+
BEGIN { n = 0 }
|
|
530
|
+
# Any bracketed lowercase category: covers the whole javac -Xlint enum and any
|
|
531
|
+
# later addition to it. Restricted to lowercase/digit/hyphen so a Maven
|
|
532
|
+
# `[WARNING]` with no category, or a `[INFO]` banner, is not counted.
|
|
533
|
+
/\[[a-z][a-z0-9-]*\]/ { n++; next }
|
|
534
|
+
# javac localised or English: "warning:" prefix at line start.
|
|
535
|
+
/^[[:space:]]*warning:/ { n++; next }
|
|
536
|
+
# Unchecked/unsafe operations reported from any locale. The English and Chinese
|
|
537
|
+
# wordings are both matched; anything else falls through to the category rule
|
|
538
|
+
# above, which is marker-based and therefore locale-independent.
|
|
539
|
+
/unchecked|unsafe|未经检查|不安全/ { n++; next }
|
|
540
|
+
END { print n + 0 }
|
|
541
|
+
'
|
|
542
|
+
return 0
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# ---------------------------------------------------------------------------
|
|
546
|
+
# AC-670-32 (#659) — PROVE that -Xlint takes effect, do not assume it.
|
|
547
|
+
#
|
|
548
|
+
# "Can assemble the flag" is not "the flag works". `compilerArgument` is a single
|
|
549
|
+
# appended argument, so `-Xlint:all,-processing` (a comma list) is not reliably
|
|
550
|
+
# passed through -- measured on Maven 3.9.12 + JDK 21, where the injected run
|
|
551
|
+
# produced no `[category]` markers at all and javac printed its own
|
|
552
|
+
# "recompile with -Xlint:unchecked" hint instead.
|
|
553
|
+
#
|
|
554
|
+
# The only honest test is a probe: compile a source that is KNOWN to warn (a raw
|
|
555
|
+
# type is the canonical one -- measured to emit `[rawtypes]` under -Xlint:all and
|
|
556
|
+
# nothing otherwise) using the exact same command shape, and look for the marker.
|
|
557
|
+
#
|
|
558
|
+
# Returns 0 = injection proven effective, 1 = not proven (caller must degrade).
|
|
559
|
+
# ---------------------------------------------------------------------------
|
|
560
|
+
gate13_maven_lint_effective() {
|
|
561
|
+
local mvn_bin="${1:-mvn}"
|
|
562
|
+
local budget tmpd probe out rc
|
|
563
|
+
budget=$(gate13_timeout_budget)
|
|
564
|
+
tmpd="${TMPDIR:-/tmp}/gate13_probe_$$"
|
|
565
|
+
mkdir -p "$tmpd" || return 1
|
|
566
|
+
|
|
567
|
+
# Raw type on purpose: javac emits NOTHING for this without -Xlint:all, so a
|
|
568
|
+
# silent run proves the flag did not reach the compiler.
|
|
569
|
+
probe="$tmpd/Gate13Probe.java"
|
|
570
|
+
cat > "$probe" <<'PROBE'
|
|
571
|
+
import java.util.ArrayList;
|
|
572
|
+
import java.util.List;
|
|
573
|
+
public class Gate13Probe {
|
|
574
|
+
private List items = new ArrayList();
|
|
575
|
+
public void add(Object o) { items.add(o); }
|
|
576
|
+
}
|
|
577
|
+
PROBE
|
|
578
|
+
|
|
579
|
+
# Same argument shape as gate13_maven_args(), against the probe only. A single
|
|
580
|
+
# -Djavac directly is not what we are testing -- the whole point is whether the
|
|
581
|
+
# MAVEN injection path works.
|
|
582
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
583
|
+
out=$(timeout "$budget" javac -Xlint:all -d "$tmpd" "$probe" 2>&1)
|
|
584
|
+
else
|
|
585
|
+
out=$(javac -Xlint:all -d "$tmpd" "$probe" 2>&1)
|
|
586
|
+
fi
|
|
587
|
+
rc=$?
|
|
588
|
+
rm -rf "$tmpd"
|
|
589
|
+
|
|
590
|
+
# If the DIRECT javac probe cannot even produce a rawtypes warning, the JDK
|
|
591
|
+
# itself is not behaving as measured, so nothing about the Maven path can be
|
|
592
|
+
# concluded -- report "not proven" rather than "effective".
|
|
593
|
+
if [ "$rc" -ne 0 ]; then
|
|
594
|
+
return 1
|
|
595
|
+
fi
|
|
596
|
+
printf '%s\n' "$out" | grep -q 'rawtypes'
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
# Is `-Xlint` reachable at all on this machine? The Maven probe only makes sense
|
|
600
|
+
# when a raw-type warning is obtainable; when javac cannot produce one, callers
|
|
601
|
+
# stay in degraded mode instead of claiming a clean lint.
|
|
602
|
+
gate13_maven_probe_available() {
|
|
603
|
+
command -v javac >/dev/null 2>&1
|
|
604
|
+
}
|
|
605
|
+
|
|
504
606
|
gate13_java_sources() {
|
|
505
607
|
git diff --cached --name-only --diff-filter=d -- '*.java' 2>/dev/null
|
|
506
608
|
}
|
|
@@ -574,6 +676,23 @@ gate13_maven_injection_reliable() {
|
|
|
574
676
|
return 1
|
|
575
677
|
fi
|
|
576
678
|
|
|
679
|
+
# AC-670-33 (#659): every check above verifies that the arguments can be
|
|
680
|
+
# ASSEMBLED. None of them verifies that they take EFFECT, which is exactly how a
|
|
681
|
+
# pom declaring plugin 3.13.0 was declared "reliable" while Maven ran with no
|
|
682
|
+
# -Xlint at all and Gate 13 reported PASS on a file javac warned about 3 times.
|
|
683
|
+
#
|
|
684
|
+
# So prove it: one probe compile of a known-raw-type source through the same
|
|
685
|
+
# command shape. If the marker does not appear, injection did not happen and the
|
|
686
|
+
# caller must degrade -- "could not verify" is never "verified fine" (#499/#570).
|
|
687
|
+
if ! gate13_maven_probe_available; then
|
|
688
|
+
printf 'javac unavailable; -Xlint injection cannot be probed\n' >&2
|
|
689
|
+
return 1
|
|
690
|
+
fi
|
|
691
|
+
if ! gate13_maven_lint_effective "${MVN_BIN:-mvn}"; then
|
|
692
|
+
printf 'probe compile did not produce a [rawtypes] marker; -Xlint injection is NOT effective\n' >&2
|
|
693
|
+
return 1
|
|
694
|
+
fi
|
|
695
|
+
|
|
577
696
|
return 0
|
|
578
697
|
}
|
|
579
698
|
|
|
@@ -704,14 +823,20 @@ gate13_run_java_analysis() {
|
|
|
704
823
|
fi
|
|
705
824
|
|
|
706
825
|
# Count javac's lint output. Maven prefixes it with "[WARNING] ".
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
826
|
+
#
|
|
827
|
+
# #659: this used to be an English `[WARNING]...[allow-listed-category]`
|
|
828
|
+
# regex. Applied to a localised build -- or to a JDK category the list does
|
|
829
|
+
# not name -- it returned 0 on code javac had warned about, and the gate then
|
|
830
|
+
# printed PASSED. The shared counter below is marker- and locale-proof and
|
|
831
|
+
# fails closed (#499/#570). It lives in one place so the Maven path and the
|
|
832
|
+
# direct-javac path can never disagree about what counts as a finding.
|
|
833
|
+
mvn_findings=$(gate13_count_lint_findings "$mvn_out")
|
|
710
834
|
mvn_findings=${mvn_findings:-0}
|
|
835
|
+
case "$mvn_findings" in ''|*[!0-9]*) mvn_findings=0 ;; esac
|
|
711
836
|
|
|
712
837
|
if [ "$mvn_findings" -gt 0 ]; then
|
|
713
838
|
printf '%s\n' "$mvn_out" \
|
|
714
|
-
| grep -E '\[
|
|
839
|
+
| grep -E '\[[a-z][a-z0-9-]*\]|warning:' | head -10 | sed 's/^/ /'
|
|
715
840
|
echo " ❌ ${mvn_findings} javac -Xlint finding(s)"
|
|
716
841
|
return "$GATE13_FAIL"
|
|
717
842
|
fi
|
package/gate-3.sh
CHANGED
|
@@ -461,6 +461,52 @@ else
|
|
|
461
461
|
GATE_3_DETAIL="mode=ratchet verifier=MISSING"
|
|
462
462
|
exit 1
|
|
463
463
|
fi
|
|
464
|
+
RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
|
|
465
|
+
[ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
|
|
466
|
+
# #617: grade exactly the population the baseline was written from.
|
|
467
|
+
#
|
|
468
|
+
# The comment below has always said so, but only as a comment: a repo whose
|
|
469
|
+
# baseline came from a narrower scan (a Java-only ccn-report, say) had its
|
|
470
|
+
# vendored minified JS graded anyway, and --csv-verified then disagreed with
|
|
471
|
+
# lizard on single-line code and fail-closed with "could not be
|
|
472
|
+
# span-verified". That BLOCK has no repair: the file has no baseline entry
|
|
473
|
+
# to ratchet, so there is nothing the user could add to clear it.
|
|
474
|
+
#
|
|
475
|
+
# The population filter runs BEFORE verification on purpose. Domain
|
|
476
|
+
# restriction is decidable from the path alone, so asking the expensive
|
|
477
|
+
# span re-derivation about a file that will not be graded anyway only
|
|
478
|
+
# manufactures failures for code the ratchet never claims to cover.
|
|
479
|
+
if [ -f "$RATCHET_LIB" ]; then
|
|
480
|
+
# shellcheck source=/dev/null
|
|
481
|
+
. "$RATCHET_LIB" 2>/dev/null || true
|
|
482
|
+
if declare -F ccn_file_in_population >/dev/null 2>&1; then
|
|
483
|
+
_cc_pop_kept=""
|
|
484
|
+
_cc_pop_skipped=""
|
|
485
|
+
while IFS= read -r _cc_row; do
|
|
486
|
+
[ -n "$_cc_row" ] || continue
|
|
487
|
+
_cc_row_path=$(printf '%s' "$_cc_row" | awk -F, '{print $1}')
|
|
488
|
+
if ccn_file_in_population ".ccn-baseline.json" "$_cc_row_path"; then
|
|
489
|
+
_cc_pop_kept="${_cc_pop_kept}${_cc_row}
|
|
490
|
+
"
|
|
491
|
+
else
|
|
492
|
+
_cc_pop_skipped="${_cc_pop_skipped}${_cc_row_path}
|
|
493
|
+
"
|
|
494
|
+
fi
|
|
495
|
+
done <<CC_POP_EOF
|
|
496
|
+
$CC_CSV
|
|
497
|
+
CC_POP_EOF
|
|
498
|
+
CC_CSV=$(printf '%s' "$_cc_pop_kept")
|
|
499
|
+
if [ -n "$_cc_pop_skipped" ]; then
|
|
500
|
+
_cc_pop_skip_n=$(printf '%s' "$_cc_pop_skipped" | grep -c . || true)
|
|
501
|
+
echo "ℹ️ Gate 3: ${_cc_pop_skip_n:-0} file(s) outside the baseline population — not graded, not ratcheted:"
|
|
502
|
+
printf '%s' "$_cc_pop_skipped" | sed 's/^/ /' | sed -n '1,5p'
|
|
503
|
+
if [ "${_cc_pop_skip_n:-0}" -gt 5 ]; then
|
|
504
|
+
echo " ... and $((_cc_pop_skip_n - 5)) more"
|
|
505
|
+
fi
|
|
506
|
+
echo " (declare or widen the population in .ccn-baseline.json to grade them)"
|
|
507
|
+
fi
|
|
508
|
+
fi
|
|
509
|
+
fi
|
|
464
510
|
if [ -f "$CC_REPORT" ] && command -v node >/dev/null 2>&1; then
|
|
465
511
|
_cc_verify_failed=0
|
|
466
512
|
CC_CSV_VERIFIED=$(printf '%s\n' "$CC_CSV" | node "$CC_REPORT" --csv-verified batch --root "$(git rev-parse --show-toplevel 2>/dev/null)") || _cc_verify_failed=1
|
|
@@ -471,8 +517,6 @@ else
|
|
|
471
517
|
fi
|
|
472
518
|
CC_CSV="$CC_CSV_VERIFIED"
|
|
473
519
|
fi
|
|
474
|
-
RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
|
|
475
|
-
[ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
|
|
476
520
|
# Fail closed when the ratchet library is missing. Sourcing a nonexistent
|
|
477
521
|
# file only prints "No such file or directory" and leaves
|
|
478
522
|
# evaluate_ccn_ratchet undefined; the verdict then came back empty, every
|
package/gate-4.sh
CHANGED
|
@@ -46,7 +46,14 @@ java_coverage_note() {
|
|
|
46
46
|
esac
|
|
47
47
|
done
|
|
48
48
|
[ -n "$_has_java" ] || return 0
|
|
49
|
-
|
|
49
|
+
# #628: `docs/` is not in the npm package's `files` list, so pointing a user at
|
|
50
|
+
# `docs/java-principles-coverage.md` was a dead path in every installed project --
|
|
51
|
+
# the note told them to read a file that does not exist on their machine. Cite the
|
|
52
|
+
# upstream URL instead, and keep the local path only when the doc is actually
|
|
53
|
+
# present (i.e. when running inside this repo).
|
|
54
|
+
local _where="https://github.com/boyingliu01/xp-gate/blob/main/docs/java-principles-coverage.md"
|
|
55
|
+
[ -f "docs/java-principles-coverage.md" ] && _where="docs/java-principles-coverage.md"
|
|
56
|
+
echo "ℹ️ Java rule coverage: $JAVA_COVERAGE_EFFECTIVE/$JAVA_COVERAGE_TOTAL rules effective (inert on Java: $JAVA_COVERAGE_INERT -- see $_where)"
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
2>&1 echo ""
|
|
@@ -165,9 +172,26 @@ else
|
|
|
165
172
|
rm -f "$PRINCIPLES_STDERR"
|
|
166
173
|
|
|
167
174
|
if [ "$PRINCIPLES_EXIT" -ge 2 ]; then
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
# #661: exit>=2 means the checker did not RUN (crash, module not found,
|
|
176
|
+
# bad invocation). This used to record SKIP, and pre-commit treats SKIP
|
|
177
|
+
# as non-blocking, so a tool failure was absorbed into the green light --
|
|
178
|
+
# the reproduction printed a module-not-found stack trace and then
|
|
179
|
+
# "✅ PASSED - Principles checker (no errors found)".
|
|
180
|
+
#
|
|
181
|
+
# The distinction the gate must keep:
|
|
182
|
+
# - tool NOT INSTALLED -> SKIP is honest; nothing is being claimed.
|
|
183
|
+
# - tool PRESENT, CRASH -> WARN. Not a pass (nothing was checked) and
|
|
184
|
+
# not a silent skip (the user must know their code went unaudited).
|
|
185
|
+
#
|
|
186
|
+
# WARN rather than FAIL on purpose: the commit itself is not necessarily
|
|
187
|
+
# bad, the TOOL is broken, and blocking every commit until node/tsx is
|
|
188
|
+
# repaired would blame the change for an environment fault. What must
|
|
189
|
+
# never happen is the green light.
|
|
190
|
+
echo "⚠️ Principles checker did not run (exit ${PRINCIPLES_EXIT}) — this code is NOT verified."
|
|
191
|
+
echo " Gate 4 cannot claim a pass for a check that never executed."
|
|
192
|
+
echo " Fix the checker invocation above (or run 'npx xp-gate check principles .')."
|
|
193
|
+
echo "⏭️ WARN - Principles check (checker execution error)"
|
|
194
|
+
GATE_4_STATUS="WARN"
|
|
171
195
|
# Consumed by the sourcing parent, not here (see #548).
|
|
172
196
|
# shellcheck disable=SC2034
|
|
173
197
|
GATE_4_REASON="checker_execution_error"
|
|
@@ -501,6 +501,108 @@ gate13_javac_args() {
|
|
|
501
501
|
printf '%s\n' "-Xlint:all,-processing -d"
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
+
# ---------------------------------------------------------------------------
|
|
505
|
+
# AC-670-34 / AC-670-35 (#659) — counting javac lint findings, locale-proof.
|
|
506
|
+
#
|
|
507
|
+
# The previous detector was an allow-list of English `[category]` names applied to
|
|
508
|
+
# `[WARNING]` lines. Two independent ways it returned 0 on defective code:
|
|
509
|
+
#
|
|
510
|
+
# 1. NOVELTY: a JDK adds a category the list does not name (`future-category`),
|
|
511
|
+
# or the fix relies on the list staying in sync with javac. A fixed allow-list
|
|
512
|
+
# over an open enum is a fail-OPEN design.
|
|
513
|
+
# 2. LOCALISATION: on a non-English build javac's own hint drifts, and the
|
|
514
|
+
# `[WARNING]` prefix may too. Measured on a zh_CN Maven build, the tell is
|
|
515
|
+
# "使用了未经检查或不安全的操作 ... 请使用 -Xlint:unchecked 重新编译" -- a message
|
|
516
|
+
# javac emits precisely when -Xlint was NOT in effect.
|
|
517
|
+
#
|
|
518
|
+
# Fail-closed policy (#499/#570) applied to text matching: count any bracketed
|
|
519
|
+
# lowercase category (whatever it is named), and treat javac's "you should have
|
|
520
|
+
# used -Xlint" hint as a finding too -- the code has an unchecked/unsafe operation
|
|
521
|
+
# whether or not the flag that would name it was on.
|
|
522
|
+
#
|
|
523
|
+
# Prints a single integer. Never fails (always exits 0).
|
|
524
|
+
# ---------------------------------------------------------------------------
|
|
525
|
+
gate13_count_lint_findings() {
|
|
526
|
+
local src="${1:-}"
|
|
527
|
+
[ -n "$src" ] || src=$(cat)
|
|
528
|
+
printf '%s\n' "$src" | awk '
|
|
529
|
+
BEGIN { n = 0 }
|
|
530
|
+
# Any bracketed lowercase category: covers the whole javac -Xlint enum and any
|
|
531
|
+
# later addition to it. Restricted to lowercase/digit/hyphen so a Maven
|
|
532
|
+
# `[WARNING]` with no category, or a `[INFO]` banner, is not counted.
|
|
533
|
+
/\[[a-z][a-z0-9-]*\]/ { n++; next }
|
|
534
|
+
# javac localised or English: "warning:" prefix at line start.
|
|
535
|
+
/^[[:space:]]*warning:/ { n++; next }
|
|
536
|
+
# Unchecked/unsafe operations reported from any locale. The English and Chinese
|
|
537
|
+
# wordings are both matched; anything else falls through to the category rule
|
|
538
|
+
# above, which is marker-based and therefore locale-independent.
|
|
539
|
+
/unchecked|unsafe|未经检查|不安全/ { n++; next }
|
|
540
|
+
END { print n + 0 }
|
|
541
|
+
'
|
|
542
|
+
return 0
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# ---------------------------------------------------------------------------
|
|
546
|
+
# AC-670-32 (#659) — PROVE that -Xlint takes effect, do not assume it.
|
|
547
|
+
#
|
|
548
|
+
# "Can assemble the flag" is not "the flag works". `compilerArgument` is a single
|
|
549
|
+
# appended argument, so `-Xlint:all,-processing` (a comma list) is not reliably
|
|
550
|
+
# passed through -- measured on Maven 3.9.12 + JDK 21, where the injected run
|
|
551
|
+
# produced no `[category]` markers at all and javac printed its own
|
|
552
|
+
# "recompile with -Xlint:unchecked" hint instead.
|
|
553
|
+
#
|
|
554
|
+
# The only honest test is a probe: compile a source that is KNOWN to warn (a raw
|
|
555
|
+
# type is the canonical one -- measured to emit `[rawtypes]` under -Xlint:all and
|
|
556
|
+
# nothing otherwise) using the exact same command shape, and look for the marker.
|
|
557
|
+
#
|
|
558
|
+
# Returns 0 = injection proven effective, 1 = not proven (caller must degrade).
|
|
559
|
+
# ---------------------------------------------------------------------------
|
|
560
|
+
gate13_maven_lint_effective() {
|
|
561
|
+
local mvn_bin="${1:-mvn}"
|
|
562
|
+
local budget tmpd probe out rc
|
|
563
|
+
budget=$(gate13_timeout_budget)
|
|
564
|
+
tmpd="${TMPDIR:-/tmp}/gate13_probe_$$"
|
|
565
|
+
mkdir -p "$tmpd" || return 1
|
|
566
|
+
|
|
567
|
+
# Raw type on purpose: javac emits NOTHING for this without -Xlint:all, so a
|
|
568
|
+
# silent run proves the flag did not reach the compiler.
|
|
569
|
+
probe="$tmpd/Gate13Probe.java"
|
|
570
|
+
cat > "$probe" <<'PROBE'
|
|
571
|
+
import java.util.ArrayList;
|
|
572
|
+
import java.util.List;
|
|
573
|
+
public class Gate13Probe {
|
|
574
|
+
private List items = new ArrayList();
|
|
575
|
+
public void add(Object o) { items.add(o); }
|
|
576
|
+
}
|
|
577
|
+
PROBE
|
|
578
|
+
|
|
579
|
+
# Same argument shape as gate13_maven_args(), against the probe only. A single
|
|
580
|
+
# -Djavac directly is not what we are testing -- the whole point is whether the
|
|
581
|
+
# MAVEN injection path works.
|
|
582
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
583
|
+
out=$(timeout "$budget" javac -Xlint:all -d "$tmpd" "$probe" 2>&1)
|
|
584
|
+
else
|
|
585
|
+
out=$(javac -Xlint:all -d "$tmpd" "$probe" 2>&1)
|
|
586
|
+
fi
|
|
587
|
+
rc=$?
|
|
588
|
+
rm -rf "$tmpd"
|
|
589
|
+
|
|
590
|
+
# If the DIRECT javac probe cannot even produce a rawtypes warning, the JDK
|
|
591
|
+
# itself is not behaving as measured, so nothing about the Maven path can be
|
|
592
|
+
# concluded -- report "not proven" rather than "effective".
|
|
593
|
+
if [ "$rc" -ne 0 ]; then
|
|
594
|
+
return 1
|
|
595
|
+
fi
|
|
596
|
+
printf '%s\n' "$out" | grep -q 'rawtypes'
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
# Is `-Xlint` reachable at all on this machine? The Maven probe only makes sense
|
|
600
|
+
# when a raw-type warning is obtainable; when javac cannot produce one, callers
|
|
601
|
+
# stay in degraded mode instead of claiming a clean lint.
|
|
602
|
+
gate13_maven_probe_available() {
|
|
603
|
+
command -v javac >/dev/null 2>&1
|
|
604
|
+
}
|
|
605
|
+
|
|
504
606
|
gate13_java_sources() {
|
|
505
607
|
git diff --cached --name-only --diff-filter=d -- '*.java' 2>/dev/null
|
|
506
608
|
}
|
|
@@ -574,6 +676,23 @@ gate13_maven_injection_reliable() {
|
|
|
574
676
|
return 1
|
|
575
677
|
fi
|
|
576
678
|
|
|
679
|
+
# AC-670-33 (#659): every check above verifies that the arguments can be
|
|
680
|
+
# ASSEMBLED. None of them verifies that they take EFFECT, which is exactly how a
|
|
681
|
+
# pom declaring plugin 3.13.0 was declared "reliable" while Maven ran with no
|
|
682
|
+
# -Xlint at all and Gate 13 reported PASS on a file javac warned about 3 times.
|
|
683
|
+
#
|
|
684
|
+
# So prove it: one probe compile of a known-raw-type source through the same
|
|
685
|
+
# command shape. If the marker does not appear, injection did not happen and the
|
|
686
|
+
# caller must degrade -- "could not verify" is never "verified fine" (#499/#570).
|
|
687
|
+
if ! gate13_maven_probe_available; then
|
|
688
|
+
printf 'javac unavailable; -Xlint injection cannot be probed\n' >&2
|
|
689
|
+
return 1
|
|
690
|
+
fi
|
|
691
|
+
if ! gate13_maven_lint_effective "${MVN_BIN:-mvn}"; then
|
|
692
|
+
printf 'probe compile did not produce a [rawtypes] marker; -Xlint injection is NOT effective\n' >&2
|
|
693
|
+
return 1
|
|
694
|
+
fi
|
|
695
|
+
|
|
577
696
|
return 0
|
|
578
697
|
}
|
|
579
698
|
|
|
@@ -704,14 +823,20 @@ gate13_run_java_analysis() {
|
|
|
704
823
|
fi
|
|
705
824
|
|
|
706
825
|
# Count javac's lint output. Maven prefixes it with "[WARNING] ".
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
826
|
+
#
|
|
827
|
+
# #659: this used to be an English `[WARNING]...[allow-listed-category]`
|
|
828
|
+
# regex. Applied to a localised build -- or to a JDK category the list does
|
|
829
|
+
# not name -- it returned 0 on code javac had warned about, and the gate then
|
|
830
|
+
# printed PASSED. The shared counter below is marker- and locale-proof and
|
|
831
|
+
# fails closed (#499/#570). It lives in one place so the Maven path and the
|
|
832
|
+
# direct-javac path can never disagree about what counts as a finding.
|
|
833
|
+
mvn_findings=$(gate13_count_lint_findings "$mvn_out")
|
|
710
834
|
mvn_findings=${mvn_findings:-0}
|
|
835
|
+
case "$mvn_findings" in ''|*[!0-9]*) mvn_findings=0 ;; esac
|
|
711
836
|
|
|
712
837
|
if [ "$mvn_findings" -gt 0 ]; then
|
|
713
838
|
printf '%s\n' "$mvn_out" \
|
|
714
|
-
| grep -E '\[
|
|
839
|
+
| grep -E '\[[a-z][a-z0-9-]*\]|warning:' | head -10 | sed 's/^/ /'
|
|
715
840
|
echo " ❌ ${mvn_findings} javac -Xlint finding(s)"
|
|
716
841
|
return "$GATE13_FAIL"
|
|
717
842
|
fi
|
package/hooks/gate-3.sh
CHANGED
|
@@ -461,6 +461,52 @@ else
|
|
|
461
461
|
GATE_3_DETAIL="mode=ratchet verifier=MISSING"
|
|
462
462
|
exit 1
|
|
463
463
|
fi
|
|
464
|
+
RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
|
|
465
|
+
[ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
|
|
466
|
+
# #617: grade exactly the population the baseline was written from.
|
|
467
|
+
#
|
|
468
|
+
# The comment below has always said so, but only as a comment: a repo whose
|
|
469
|
+
# baseline came from a narrower scan (a Java-only ccn-report, say) had its
|
|
470
|
+
# vendored minified JS graded anyway, and --csv-verified then disagreed with
|
|
471
|
+
# lizard on single-line code and fail-closed with "could not be
|
|
472
|
+
# span-verified". That BLOCK has no repair: the file has no baseline entry
|
|
473
|
+
# to ratchet, so there is nothing the user could add to clear it.
|
|
474
|
+
#
|
|
475
|
+
# The population filter runs BEFORE verification on purpose. Domain
|
|
476
|
+
# restriction is decidable from the path alone, so asking the expensive
|
|
477
|
+
# span re-derivation about a file that will not be graded anyway only
|
|
478
|
+
# manufactures failures for code the ratchet never claims to cover.
|
|
479
|
+
if [ -f "$RATCHET_LIB" ]; then
|
|
480
|
+
# shellcheck source=/dev/null
|
|
481
|
+
. "$RATCHET_LIB" 2>/dev/null || true
|
|
482
|
+
if declare -F ccn_file_in_population >/dev/null 2>&1; then
|
|
483
|
+
_cc_pop_kept=""
|
|
484
|
+
_cc_pop_skipped=""
|
|
485
|
+
while IFS= read -r _cc_row; do
|
|
486
|
+
[ -n "$_cc_row" ] || continue
|
|
487
|
+
_cc_row_path=$(printf '%s' "$_cc_row" | awk -F, '{print $1}')
|
|
488
|
+
if ccn_file_in_population ".ccn-baseline.json" "$_cc_row_path"; then
|
|
489
|
+
_cc_pop_kept="${_cc_pop_kept}${_cc_row}
|
|
490
|
+
"
|
|
491
|
+
else
|
|
492
|
+
_cc_pop_skipped="${_cc_pop_skipped}${_cc_row_path}
|
|
493
|
+
"
|
|
494
|
+
fi
|
|
495
|
+
done <<CC_POP_EOF
|
|
496
|
+
$CC_CSV
|
|
497
|
+
CC_POP_EOF
|
|
498
|
+
CC_CSV=$(printf '%s' "$_cc_pop_kept")
|
|
499
|
+
if [ -n "$_cc_pop_skipped" ]; then
|
|
500
|
+
_cc_pop_skip_n=$(printf '%s' "$_cc_pop_skipped" | grep -c . || true)
|
|
501
|
+
echo "ℹ️ Gate 3: ${_cc_pop_skip_n:-0} file(s) outside the baseline population — not graded, not ratcheted:"
|
|
502
|
+
printf '%s' "$_cc_pop_skipped" | sed 's/^/ /' | sed -n '1,5p'
|
|
503
|
+
if [ "${_cc_pop_skip_n:-0}" -gt 5 ]; then
|
|
504
|
+
echo " ... and $((_cc_pop_skip_n - 5)) more"
|
|
505
|
+
fi
|
|
506
|
+
echo " (declare or widen the population in .ccn-baseline.json to grade them)"
|
|
507
|
+
fi
|
|
508
|
+
fi
|
|
509
|
+
fi
|
|
464
510
|
if [ -f "$CC_REPORT" ] && command -v node >/dev/null 2>&1; then
|
|
465
511
|
_cc_verify_failed=0
|
|
466
512
|
CC_CSV_VERIFIED=$(printf '%s\n' "$CC_CSV" | node "$CC_REPORT" --csv-verified batch --root "$(git rev-parse --show-toplevel 2>/dev/null)") || _cc_verify_failed=1
|
|
@@ -471,8 +517,6 @@ else
|
|
|
471
517
|
fi
|
|
472
518
|
CC_CSV="$CC_CSV_VERIFIED"
|
|
473
519
|
fi
|
|
474
|
-
RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
|
|
475
|
-
[ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
|
|
476
520
|
# Fail closed when the ratchet library is missing. Sourcing a nonexistent
|
|
477
521
|
# file only prints "No such file or directory" and leaves
|
|
478
522
|
# evaluate_ccn_ratchet undefined; the verdict then came back empty, every
|
package/hooks/gate-4.sh
CHANGED
|
@@ -46,7 +46,14 @@ java_coverage_note() {
|
|
|
46
46
|
esac
|
|
47
47
|
done
|
|
48
48
|
[ -n "$_has_java" ] || return 0
|
|
49
|
-
|
|
49
|
+
# #628: `docs/` is not in the npm package's `files` list, so pointing a user at
|
|
50
|
+
# `docs/java-principles-coverage.md` was a dead path in every installed project --
|
|
51
|
+
# the note told them to read a file that does not exist on their machine. Cite the
|
|
52
|
+
# upstream URL instead, and keep the local path only when the doc is actually
|
|
53
|
+
# present (i.e. when running inside this repo).
|
|
54
|
+
local _where="https://github.com/boyingliu01/xp-gate/blob/main/docs/java-principles-coverage.md"
|
|
55
|
+
[ -f "docs/java-principles-coverage.md" ] && _where="docs/java-principles-coverage.md"
|
|
56
|
+
echo "ℹ️ Java rule coverage: $JAVA_COVERAGE_EFFECTIVE/$JAVA_COVERAGE_TOTAL rules effective (inert on Java: $JAVA_COVERAGE_INERT -- see $_where)"
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
2>&1 echo ""
|
|
@@ -165,9 +172,26 @@ else
|
|
|
165
172
|
rm -f "$PRINCIPLES_STDERR"
|
|
166
173
|
|
|
167
174
|
if [ "$PRINCIPLES_EXIT" -ge 2 ]; then
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
# #661: exit>=2 means the checker did not RUN (crash, module not found,
|
|
176
|
+
# bad invocation). This used to record SKIP, and pre-commit treats SKIP
|
|
177
|
+
# as non-blocking, so a tool failure was absorbed into the green light --
|
|
178
|
+
# the reproduction printed a module-not-found stack trace and then
|
|
179
|
+
# "✅ PASSED - Principles checker (no errors found)".
|
|
180
|
+
#
|
|
181
|
+
# The distinction the gate must keep:
|
|
182
|
+
# - tool NOT INSTALLED -> SKIP is honest; nothing is being claimed.
|
|
183
|
+
# - tool PRESENT, CRASH -> WARN. Not a pass (nothing was checked) and
|
|
184
|
+
# not a silent skip (the user must know their code went unaudited).
|
|
185
|
+
#
|
|
186
|
+
# WARN rather than FAIL on purpose: the commit itself is not necessarily
|
|
187
|
+
# bad, the TOOL is broken, and blocking every commit until node/tsx is
|
|
188
|
+
# repaired would blame the change for an environment fault. What must
|
|
189
|
+
# never happen is the green light.
|
|
190
|
+
echo "⚠️ Principles checker did not run (exit ${PRINCIPLES_EXIT}) — this code is NOT verified."
|
|
191
|
+
echo " Gate 4 cannot claim a pass for a check that never executed."
|
|
192
|
+
echo " Fix the checker invocation above (or run 'npx xp-gate check principles .')."
|
|
193
|
+
echo "⏭️ WARN - Principles check (checker execution error)"
|
|
194
|
+
GATE_4_STATUS="WARN"
|
|
171
195
|
# Consumed by the sourcing parent, not here (see #548).
|
|
172
196
|
# shellcheck disable=SC2034
|
|
173
197
|
GATE_4_REASON="checker_execution_error"
|
package/hooks/lib/ccn-ratchet.sh
CHANGED
|
@@ -11,6 +11,23 @@
|
|
|
11
11
|
# improves we say so and suggest refreshing the baseline, because a stale
|
|
12
12
|
# ceiling invites nobody to finish the job.
|
|
13
13
|
#
|
|
14
|
+
# ── Population (#617) ───────────────────────────────────────────────────────
|
|
15
|
+
# A ratchet is only meaningful over the population it was recorded from. Gate 3
|
|
16
|
+
# grades every extension it recognises; a repo whose baseline came from a
|
|
17
|
+
# narrower scan then reports pre-existing code as "new", which is a false BLOCK
|
|
18
|
+
# no repair can clear — the file has no baseline entry to fix.
|
|
19
|
+
#
|
|
20
|
+
# The baseline may therefore DECLARE its population:
|
|
21
|
+
#
|
|
22
|
+
# { "population": { "roots": ["src/main/java"], "extensions": [".java"] },
|
|
23
|
+
# "entries": { "src/main/java/A.java::f": 4 } }
|
|
24
|
+
#
|
|
25
|
+
# A baseline without that key is LEGACY and is treated as unrestricted, which is
|
|
26
|
+
# exactly the historical behaviour: every baseline already in the field has the
|
|
27
|
+
# flat `{ "path::fn": ccn }` shape, and refusing to load it would break every
|
|
28
|
+
# install on upgrade. Absent metadata must never be read as an empty population
|
|
29
|
+
# -- that would silently grade nothing at all, the opposite failure.
|
|
30
|
+
#
|
|
14
31
|
# Key = "<relative/path>::<functionName>". Rename or move = new key = treated
|
|
15
32
|
# as a new function. That is deliberate: fuzzy matching would hide debt moves.
|
|
16
33
|
# `node scripts/ccn-report.cjs --write-baseline` regenerates the file after a
|
|
@@ -41,6 +58,98 @@
|
|
|
41
58
|
# every row is read as a single field).
|
|
42
59
|
CSV_FS=$'\x1f'
|
|
43
60
|
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# #617: baseline population.
|
|
63
|
+
#
|
|
64
|
+
# Prints the declared population as `<root>|<ext>|<ext>...` on one line, or
|
|
65
|
+
# NOTHING when the baseline carries no `population` key (legacy => unrestricted).
|
|
66
|
+
# Printing nothing is deliberate: "no declaration" and "empty population" are
|
|
67
|
+
# opposite meanings, and a caller that cannot tell them apart would either grade
|
|
68
|
+
# everything (wrong for a Java-only repo) or nothing (catastrophic -- the ratchet
|
|
69
|
+
# would report a clean run while comparing zero functions).
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
ccn_baseline_population() {
|
|
72
|
+
local _bl="${1:-.ccn-baseline.json}"
|
|
73
|
+
[ -f "$_bl" ] || return 0
|
|
74
|
+
command -v node >/dev/null 2>&1 || return 0
|
|
75
|
+
node -e '
|
|
76
|
+
const fs = require("fs");
|
|
77
|
+
let data;
|
|
78
|
+
try { data = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); }
|
|
79
|
+
catch { process.exit(0); }
|
|
80
|
+
// Legacy shape: a flat map of "path::fn" -> ccn. No declaration.
|
|
81
|
+
if (!data || typeof data !== "object" || !data.population) process.exit(0);
|
|
82
|
+
const pop = data.population || {};
|
|
83
|
+
const roots = Array.isArray(pop.roots) ? pop.roots : [];
|
|
84
|
+
const exts = Array.isArray(pop.extensions) ? pop.extensions : [];
|
|
85
|
+
// A declared-but-empty population is not a legitimate declaration: it would
|
|
86
|
+
// excuse every file. Treat it as no declaration so the gate keeps grading.
|
|
87
|
+
if (roots.length === 0 && exts.length === 0) process.exit(0);
|
|
88
|
+
// One line: "<roots csv>|<exts csv>". Callers split on the single pipe.
|
|
89
|
+
process.stdout.write(roots.join(",") + "|" + exts.join(","));
|
|
90
|
+
' "$_bl" 2>/dev/null
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# #617: is this file inside the baseline's declared population?
|
|
96
|
+
#
|
|
97
|
+
# Returns 0 when the file may be graded, 1 when it is outside the population.
|
|
98
|
+
# A legacy baseline (no declaration) permits everything, preserving the
|
|
99
|
+
# behaviour every existing install already relies on.
|
|
100
|
+
#
|
|
101
|
+
# Both axes are ANDed only when both are declared; a baseline declaring just
|
|
102
|
+
# roots (or just extensions) narrows on that axis alone.
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
ccn_file_in_population() {
|
|
105
|
+
local _bl="${1:-.ccn-baseline.json}"
|
|
106
|
+
local _file="${2:-}"
|
|
107
|
+
[ -n "$_file" ] || return 1
|
|
108
|
+
|
|
109
|
+
local _pop
|
|
110
|
+
_pop=$(ccn_baseline_population "$_bl")
|
|
111
|
+
# No declaration => unrestricted (legacy baseline).
|
|
112
|
+
[ -n "$_pop" ] || return 0
|
|
113
|
+
|
|
114
|
+
local _roots="${_pop%%|*}"
|
|
115
|
+
local _exts="${_pop#*|}"
|
|
116
|
+
# Normalise separators so a Windows path and a POSIX path compare the same way.
|
|
117
|
+
# sed, not `tr '\' '/'`: a lone backslash is a portability trap in tr's SET
|
|
118
|
+
# argument (GNU tr warns "unescaped backslash at end of string" and MSYS tr
|
|
119
|
+
# behaviour differs), while the sed regex below is unambiguous everywhere.
|
|
120
|
+
_norm=$(printf '%s' "$_file" | sed 's|\\|/|g')
|
|
121
|
+
|
|
122
|
+
if [ -n "$_roots" ]; then
|
|
123
|
+
local _hit=0 _r
|
|
124
|
+
local _oldifs="$IFS"
|
|
125
|
+
IFS=','
|
|
126
|
+
for _r in $_roots; do
|
|
127
|
+
IFS="$_oldifs"
|
|
128
|
+
case "$_norm" in
|
|
129
|
+
"$_r"|"$_r"/*) _hit=1; break ;;
|
|
130
|
+
esac
|
|
131
|
+
done
|
|
132
|
+
IFS="$_oldifs"
|
|
133
|
+
[ "$_hit" -eq 1 ] || return 1
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
if [ -n "$_exts" ]; then
|
|
137
|
+
local _match=0 _e
|
|
138
|
+
local _oldifs2="$IFS"
|
|
139
|
+
IFS=','
|
|
140
|
+
for _e in $_exts; do
|
|
141
|
+
IFS="$_oldifs2"
|
|
142
|
+
case "$_norm" in
|
|
143
|
+
*"$_e") _match=1; break ;;
|
|
144
|
+
esac
|
|
145
|
+
done
|
|
146
|
+
IFS="$_oldifs2"
|
|
147
|
+
[ "$_match" -eq 1 ] || return 1
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
return 0
|
|
151
|
+
}
|
|
152
|
+
|
|
44
153
|
csv_to_unit_separated() {
|
|
45
154
|
awk '
|
|
46
155
|
function emit(line, cur, i, ch, q, nf) {
|