@boyingliu01/xp-gate 0.20.1 → 0.21.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/adapters/java.sh +373 -27
- package/adapters/python.sh +73 -6
- package/gate-10.sh +107 -64
- package/gate-3.sh +51 -3
- package/gate-4.sh +32 -1
- package/hooks/gate-10.sh +107 -64
- package/hooks/gate-3.sh +51 -3
- package/hooks/gate-4.sh +32 -1
- package/hooks/pre-commit +319 -57
- package/lib/__tests__/doctor-spawn-capability.test.js +116 -0
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +49 -10
- package/lib/doctor-tui.js +21 -3
- package/lib/doctor.js +111 -8
- package/lib/gate-audit.ts +3 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/mock-policy/AGENTS.md +4 -4
- package/mutation/AGENTS.md +4 -4
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/admin-template-guidelines/SKILL.md +1 -0
- package/plugins/claude-code/skills/batch-grill-me/SKILL.md +1 -0
- package/plugins/claude-code/skills/clipboard-vision/SKILL.md +1 -0
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/claude-code/skills/domain-modeling/SKILL.md +1 -0
- package/plugins/claude-code/skills/grill-with-docs/SKILL.md +1 -0
- package/plugins/claude-code/skills/grilling/SKILL.md +1 -0
- package/plugins/claude-code/skills/improve-codebase-architecture/SKILL.md +1 -0
- package/plugins/claude-code/skills/ralph-loop/SKILL.md +1 -0
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/test-driven-development/SKILL.md +1 -0
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/claude-code/skills/test-specification-alignment/SKILL.md +1 -0
- package/plugins/claude-code/skills/to-issues/SKILL.md +1 -0
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/admin-template-guidelines/SKILL.md +1 -0
- package/plugins/dsh/skills/batch-grill-me/SKILL.md +1 -0
- package/plugins/dsh/skills/clipboard-vision/SKILL.md +1 -0
- package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/dsh/skills/domain-modeling/SKILL.md +1 -0
- package/plugins/dsh/skills/grill-with-docs/SKILL.md +1 -0
- package/plugins/dsh/skills/grilling/SKILL.md +1 -0
- package/plugins/dsh/skills/improve-codebase-architecture/SKILL.md +1 -0
- package/plugins/dsh/skills/ralph-loop/SKILL.md +1 -0
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/dsh/skills/test-driven-development/SKILL.md +1 -0
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/skills/test-specification-alignment/SKILL.md +1 -0
- package/plugins/dsh/skills/to-issues/SKILL.md +1 -0
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/admin-template-guidelines/SKILL.md +1 -0
- package/plugins/opencode/skills/batch-grill-me/SKILL.md +1 -0
- package/plugins/opencode/skills/clipboard-vision/SKILL.md +1 -0
- package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/opencode/skills/domain-modeling/SKILL.md +1 -0
- package/plugins/opencode/skills/grill-with-docs/SKILL.md +1 -0
- package/plugins/opencode/skills/grilling/SKILL.md +1 -0
- package/plugins/opencode/skills/improve-codebase-architecture/SKILL.md +1 -0
- package/plugins/opencode/skills/ralph-loop/SKILL.md +1 -0
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/test-driven-development/SKILL.md +1 -0
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/opencode/skills/test-specification-alignment/SKILL.md +1 -0
- package/plugins/opencode/skills/to-issues/SKILL.md +1 -0
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
- package/principles/AGENTS.md +4 -4
- package/principles/adapters/__tests__/java.test.ts +317 -0
- package/principles/adapters/java.ts +325 -13
- package/skills/admin-template-guidelines/SKILL.md +1 -0
- package/skills/batch-grill-me/SKILL.md +1 -0
- package/skills/clipboard-vision/SKILL.md +1 -0
- package/skills/delphi-review/AGENTS.md +4 -4
- package/skills/domain-modeling/SKILL.md +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -0
- package/skills/grilling/SKILL.md +1 -0
- package/skills/improve-codebase-architecture/SKILL.md +1 -0
- package/skills/ralph-loop/SKILL.md +1 -0
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/test-driven-development/SKILL.md +1 -0
- package/skills/test-specification-alignment/AGENTS.md +4 -4
- package/skills/test-specification-alignment/SKILL.md +1 -0
- package/skills/to-issues/SKILL.md +1 -0
package/hooks/gate-4.sh
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
1
2
|
# ============================================================================
|
|
2
3
|
# GATE 4: Principles Checker (Clean Code + SOLID)
|
|
3
4
|
# Reuses existing principles checker logic
|
|
@@ -23,6 +24,31 @@ if ! declare -F run_tsx >/dev/null 2>&1; then
|
|
|
23
24
|
}
|
|
24
25
|
fi
|
|
25
26
|
|
|
27
|
+
# Java rule coverage annotation (#507 S4, AC-507-04-02). The normative matrix
|
|
28
|
+
# lives in docs/java-principles-coverage.md; these constants mirror it. Before
|
|
29
|
+
# #507 the Java adapter fed rules only {name,type,line,code}, so the 12 rules
|
|
30
|
+
# reading structured metrics silently saw `undefined` and a Java commit got a
|
|
31
|
+
# green "checked" with zero effective coverage. 12/15 now fire; the three
|
|
32
|
+
# inert ones are rule-side gaps documented in the coverage doc -- they must
|
|
33
|
+
# NOT be announced as covered.
|
|
34
|
+
JAVA_COVERAGE_EFFECTIVE=12
|
|
35
|
+
JAVA_COVERAGE_TOTAL=15
|
|
36
|
+
JAVA_COVERAGE_INERT="lsp, many-exports, code-duplication"
|
|
37
|
+
|
|
38
|
+
# Print the coverage annotation when (and only when) the staged file list
|
|
39
|
+
# contains Java sources. Non-Java commits keep the original output byte for
|
|
40
|
+
# byte. Pure shell builtins: no subprocess per file.
|
|
41
|
+
java_coverage_note() {
|
|
42
|
+
local _f _has_java=""
|
|
43
|
+
for _f in "$@"; do
|
|
44
|
+
case "$_f" in
|
|
45
|
+
*.java|*.Java|*.JAVA) _has_java=1; break ;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
[ -n "$_has_java" ] || return 0
|
|
49
|
+
echo "ℹ️ Java rule coverage: $JAVA_COVERAGE_EFFECTIVE/$JAVA_COVERAGE_TOTAL rules effective (inert on Java: $JAVA_COVERAGE_INERT -- see docs/java-principles-coverage.md)"
|
|
50
|
+
}
|
|
51
|
+
|
|
26
52
|
2>&1 echo ""
|
|
27
53
|
2>&1 echo "→ Gate 4: Principles checker (Clean Code + SOLID)..."
|
|
28
54
|
GATE_4_START=$(gate_start_ms)
|
|
@@ -72,7 +98,12 @@ else
|
|
|
72
98
|
|
|
73
99
|
if [ -n "$PRINCIPLES_DIR" ]; then
|
|
74
100
|
echo "Checking Clean Code + SOLID principles..."
|
|
75
|
-
|
|
101
|
+
|
|
102
|
+
# AC-507-04-02: say what a Java commit is actually covered for, instead
|
|
103
|
+
# of implying a full 15-rule check (#507). Printed before the run so it
|
|
104
|
+
# appears on both the PASS and the FAIL path.
|
|
105
|
+
java_coverage_note ${PRINCIPLES_ARGS[@]+"${PRINCIPLES_ARGS[@]}"}
|
|
106
|
+
|
|
76
107
|
if command -v npx > /dev/null 2>&1 || [ -f "$PRINCIPLES_BASE/node_modules/tsx/dist/cli.mjs" ]; then
|
|
77
108
|
# Run principles checker and store results.
|
|
78
109
|
#
|
package/hooks/pre-commit
CHANGED
|
@@ -242,6 +242,9 @@ record_gate_audit() {
|
|
|
242
242
|
local passed="$3"
|
|
243
243
|
local issues_found="$4"
|
|
244
244
|
local start_ms="$5"
|
|
245
|
+
# Optional free-form detail (e.g. gate-3's effective CCN threshold, #507).
|
|
246
|
+
# Additive and omitted when absent, so existing callers stay unchanged.
|
|
247
|
+
local detail="${6:-}"
|
|
245
248
|
local end_ms
|
|
246
249
|
end_ms=$(now_ms)
|
|
247
250
|
local duration_ms=$((end_ms - start_ms))
|
|
@@ -271,6 +274,7 @@ record_gate_audit() {
|
|
|
271
274
|
--duration-ms "$duration_ms" \
|
|
272
275
|
$duration_anomaly_flag \
|
|
273
276
|
--trigger commit \
|
|
277
|
+
${detail:+--detail "$detail"} \
|
|
274
278
|
2>/dev/null
|
|
275
279
|
} &
|
|
276
280
|
disown 2>/dev/null || true
|
|
@@ -474,8 +478,19 @@ echo ""
|
|
|
474
478
|
# unconditional and fail-closed (#499), and gating it behind a path-filtered
|
|
475
479
|
# list would let a mirror-only commit land with zero secret scanning and no
|
|
476
480
|
# CI backstop.
|
|
481
|
+
# Single source of truth for the mirror-path filter (#510): every consumer
|
|
482
|
+
# that must ignore the byte-identical npm-package mirrors (lint corpus here,
|
|
483
|
+
# Boy Scout's new/modified split further down) reads this one regex, so the
|
|
484
|
+
# two sites cannot drift apart.
|
|
485
|
+
# dashboard/ is a standalone browser-only visualization page (document/Chart.js
|
|
486
|
+
# globals, static HTML output). It is not part of the gate engine, never runs
|
|
487
|
+
# in CI or hooks, and its chart-config object literals trip structural rules
|
|
488
|
+
# (deep-nesting/long-function) that carry no signal for this codebase. Same
|
|
489
|
+
# scope rationale as the mirror exclusion (#507 Delphi round-2 follow-up).
|
|
490
|
+
MIRROR_PATH_FILTER='^src/npm-package/'
|
|
491
|
+
DASHBOARD_PATH_FILTER='^dashboard/'
|
|
477
492
|
RAW_CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
|
|
478
|
-
CHANGED_FILES=$(printf '%s\n' "$RAW_CHANGED_FILES" | grep -v
|
|
493
|
+
CHANGED_FILES=$(printf '%s\n' "$RAW_CHANGED_FILES" | grep -v "$MIRROR_PATH_FILTER" | grep -v "$DASHBOARD_PATH_FILTER" || true)
|
|
479
494
|
|
|
480
495
|
if [ -z "$RAW_CHANGED_FILES" ]; then
|
|
481
496
|
echo "No files changed. Skipping gates."
|
|
@@ -644,21 +659,21 @@ if [ -z "$PROJECT_LANGS" ] && [ -z "$_CONFIG_LANGS" ]; then
|
|
|
644
659
|
PROJECT_LANGS="dart"
|
|
645
660
|
fi
|
|
646
661
|
elif [ -f "pom.xml" ] || [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
|
|
647
|
-
KOTLIN_FILES=$(find . -name "*.kt" -not -path "./.git/*" 2>/dev/null | wc -l)
|
|
662
|
+
KOTLIN_FILES=$(find . -name "*.kt" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | wc -l)
|
|
648
663
|
if [ "$KOTLIN_FILES" -gt 0 ]; then
|
|
649
664
|
PROJECT_LANGS="kotlin"
|
|
650
665
|
else
|
|
651
666
|
PROJECT_LANGS="java"
|
|
652
667
|
fi
|
|
653
668
|
elif [ -f "CMakeLists.txt" ] || [ -f "Makefile" ]; then
|
|
654
|
-
CPP_FILES=$(find . -maxdepth 2 \( -name "*.cpp" -o -name "*.cxx" -o -name "*.cc" \) -not -path "./.git/*" 2>/dev/null | head -1)
|
|
655
|
-
OBJC_FILES=$(find . -maxdepth 2 \( -name "*.m" -o -name "*.mm" \) -not -path "./.git/*" 2>/dev/null | head -1)
|
|
669
|
+
CPP_FILES=$(find . -maxdepth 2 \( -name "*.cpp" -o -name "*.cxx" -o -name "*.cc" \) -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
670
|
+
OBJC_FILES=$(find . -maxdepth 2 \( -name "*.m" -o -name "*.mm" \) -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
656
671
|
if [ -n "$CPP_FILES" ]; then
|
|
657
672
|
PROJECT_LANGS="cpp"
|
|
658
673
|
elif [ -n "$OBJC_FILES" ]; then
|
|
659
674
|
PROJECT_LANGS="objectivec"
|
|
660
675
|
fi
|
|
661
|
-
elif [ -n "$(find . -maxdepth 2 -name "*.ps1" -not -path "./.git/*" 2>/dev/null | head -1)" ]; then
|
|
676
|
+
elif [ -n "$(find . -maxdepth 2 -name "*.ps1" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)" ]; then
|
|
662
677
|
PROJECT_LANGS="powershell"
|
|
663
678
|
fi
|
|
664
679
|
fi
|
|
@@ -695,30 +710,53 @@ if [ -z "$PROJECT_LANGS" ] && [ -z "$_CONFIG_LANGS" ]; then
|
|
|
695
710
|
done
|
|
696
711
|
return 1
|
|
697
712
|
}
|
|
713
|
+
# #515: when the repo ROOT itself declares the project, a nested marker that
|
|
714
|
+
# is merely a module file (skill_cert/cli/setup.py incident) must not hijack
|
|
715
|
+
# PROJECT_SUBDIR — Gates 1-9 would run from the wrong cwd and false-BLOCK on
|
|
716
|
+
# root-relative paths. The root-marker set below MUST stay aligned with
|
|
717
|
+
# _detect_subdir's marker set above (10 markers): every ecosystem recognized
|
|
718
|
+
# for subproject derivation is also protected at the root. The JS-monorepo
|
|
719
|
+
# check runs FIRST (#515 R2 delphi walkthrough): a root package.json with a
|
|
720
|
+
# "workspaces" key declares a JS monorepo (often together with a shared
|
|
721
|
+
# tsconfig.json), not a single project, so derivation keeps running there.
|
|
722
|
+
# Known limitation (#527): Gradle multi-module, CMake subdirectories and
|
|
723
|
+
# pnpm/Lerna workspaces are not disambiguated from single projects yet —
|
|
724
|
+
# see the pinned BATS case.
|
|
725
|
+
_skip_subdir_derive=0
|
|
726
|
+
if [ -f package.json ] && grep -q '"workspaces":' package.json 2>/dev/null; then
|
|
727
|
+
_skip_subdir_derive=0 # JS monorepo root: subproject derivation stays on
|
|
728
|
+
elif [ -f package.json ] || [ -f tsconfig.json ] || [ -f pyproject.toml ] || \
|
|
729
|
+
[ -f setup.py ] || [ -f go.mod ] || [ -f pubspec.yaml ] || \
|
|
730
|
+
[ -f pom.xml ] || [ -f build.gradle ] || \
|
|
731
|
+
[ -f build.gradle.kts ] || [ -f CMakeLists.txt ]; then
|
|
732
|
+
_skip_subdir_derive=1
|
|
733
|
+
fi
|
|
698
734
|
_subdir_set=""
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
735
|
+
if [ "$_skip_subdir_derive" -eq 0 ]; then
|
|
736
|
+
while IFS= read -r _changed; do
|
|
737
|
+
[ -z "$_changed" ] && continue
|
|
738
|
+
_d=$(_detect_subdir "$_changed" 2>/dev/null) || true
|
|
739
|
+
if [ -n "$_d" ] && [ "$_d" != "." ]; then
|
|
740
|
+
case " $_subdir_set " in
|
|
741
|
+
*" $_d "*) ;; # already seen
|
|
742
|
+
*) _subdir_set="${_subdir_set} $_d" ;;
|
|
743
|
+
esac
|
|
744
|
+
fi
|
|
745
|
+
done <<CHANGEDFILELIST
|
|
709
746
|
$CHANGED_FILES
|
|
710
747
|
CHANGEDFILELIST
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
748
|
+
_subdir_count=$(echo "$_subdir_set" | awk '{ n=0; for(i=1;i<=NF;i++) n++; print n }')
|
|
749
|
+
if [ "$_subdir_count" = "1" ]; then
|
|
750
|
+
PROJECT_SUBDIR=$(echo "$_subdir_set" | awk '{print $1}')
|
|
751
|
+
echo ""
|
|
752
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
753
|
+
echo " 📁 MULTILANGUAGE SUBDIR DETECTED FROM CHANGED FILES"
|
|
754
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
755
|
+
echo ""
|
|
756
|
+
echo "Language: $PROJECT_LANGS"
|
|
757
|
+
echo "Location: $PROJECT_SUBDIR/"
|
|
758
|
+
echo ""
|
|
759
|
+
fi
|
|
722
760
|
fi
|
|
723
761
|
fi
|
|
724
762
|
|
|
@@ -726,7 +764,7 @@ CHANGEDFILELIST
|
|
|
726
764
|
# Only used when extension-based detection found nothing
|
|
727
765
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
728
766
|
# Search for TypeScript/JavaScript projects
|
|
729
|
-
TSCONFIG_SUB=$(find . -maxdepth 2 -name "tsconfig.json" -not -path "./.git/*" -not -path "./node_modules/*" 2>/dev/null | head -1)
|
|
767
|
+
TSCONFIG_SUB=$(find . -maxdepth 2 -name "tsconfig.json" -not -path "./.git/*" -not -path "*/node_modules/*" -not -path "./node_modules/*" 2>/dev/null | head -1)
|
|
730
768
|
if [ -n "$TSCONFIG_SUB" ]; then
|
|
731
769
|
PROJECT_SUBDIR=$(dirname "$TSCONFIG_SUB")
|
|
732
770
|
if [ -f "$PROJECT_SUBDIR/package.json" ] && grep -q "react-native" "$PROJECT_SUBDIR/package.json" 2>/dev/null; then
|
|
@@ -738,7 +776,7 @@ CHANGEDFILELIST
|
|
|
738
776
|
|
|
739
777
|
# Search for Python projects
|
|
740
778
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
741
|
-
PYPROJECT_SUB=$(find . -maxdepth 2 -name "pyproject.toml" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
779
|
+
PYPROJECT_SUB=$(find . -maxdepth 2 -name "pyproject.toml" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
742
780
|
if [ -n "$PYPROJECT_SUB" ]; then
|
|
743
781
|
PROJECT_LANGS="python"
|
|
744
782
|
PROJECT_SUBDIR=$(dirname "$PYPROJECT_SUB")
|
|
@@ -747,7 +785,7 @@ CHANGEDFILELIST
|
|
|
747
785
|
|
|
748
786
|
# Search for Go projects
|
|
749
787
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
750
|
-
GOMOD_SUB=$(find . -maxdepth 2 -name "go.mod" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
788
|
+
GOMOD_SUB=$(find . -maxdepth 2 -name "go.mod" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
751
789
|
if [ -n "$GOMOD_SUB" ]; then
|
|
752
790
|
PROJECT_LANGS="go"
|
|
753
791
|
PROJECT_SUBDIR=$(dirname "$GOMOD_SUB")
|
|
@@ -756,7 +794,7 @@ CHANGEDFILELIST
|
|
|
756
794
|
|
|
757
795
|
# Search for Flutter/Dart projects
|
|
758
796
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
759
|
-
PUBSPEC_SUB=$(find . -maxdepth 2 -name "pubspec.yaml" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
797
|
+
PUBSPEC_SUB=$(find . -maxdepth 2 -name "pubspec.yaml" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
760
798
|
if [ -n "$PUBSPEC_SUB" ]; then
|
|
761
799
|
if grep -q "flutter:" "$PUBSPEC_SUB" 2>/dev/null || [ -f "$(dirname "$PUBSPEC_SUB")/.metadata" ]; then
|
|
762
800
|
PROJECT_LANGS="flutter"
|
|
@@ -769,8 +807,8 @@ CHANGEDFILELIST
|
|
|
769
807
|
|
|
770
808
|
# Search for Java/Kotlin projects
|
|
771
809
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
772
|
-
POM_SUB=$(find . -maxdepth 2 -name "pom.xml" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
773
|
-
GRADLE_SUB=$(find . -maxdepth 2 -name "build.gradle" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
810
|
+
POM_SUB=$(find . -maxdepth 2 -name "pom.xml" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
811
|
+
GRADLE_SUB=$(find . -maxdepth 2 -name "build.gradle" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
774
812
|
if [ -n "$POM_SUB" ] || [ -n "$GRADLE_SUB" ]; then
|
|
775
813
|
MANIFEST_FILE="${POM_SUB:-$GRADLE_SUB}"
|
|
776
814
|
PROJECT_SUBDIR=$(dirname "$MANIFEST_FILE")
|
|
@@ -785,7 +823,7 @@ CHANGEDFILELIST
|
|
|
785
823
|
|
|
786
824
|
# Search for C++ projects
|
|
787
825
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
788
|
-
CMAKE_SUB=$(find . -maxdepth 2 -name "CMakeLists.txt" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
826
|
+
CMAKE_SUB=$(find . -maxdepth 2 -name "CMakeLists.txt" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
789
827
|
if [ -n "$CMAKE_SUB" ]; then
|
|
790
828
|
PROJECT_LANGS="cpp"
|
|
791
829
|
PROJECT_SUBDIR=$(dirname "$CMAKE_SUB")
|
|
@@ -794,7 +832,7 @@ CHANGEDFILELIST
|
|
|
794
832
|
|
|
795
833
|
# Search for React Native projects (ios/ + android/ directories)
|
|
796
834
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
797
|
-
RN_SUB=$(find . -maxdepth 2 -type d -name "ios" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
835
|
+
RN_SUB=$(find . -maxdepth 2 -type d -name "ios" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
798
836
|
if [ -n "$RN_SUB" ]; then
|
|
799
837
|
PROJECT_SUBDIR=$(dirname "$RN_SUB")
|
|
800
838
|
if [ -d "$PROJECT_SUBDIR/android" ] || [ -f "$PROJECT_SUBDIR/package.json" ]; then
|
|
@@ -805,7 +843,7 @@ CHANGEDFILELIST
|
|
|
805
843
|
|
|
806
844
|
# Search for PowerShell projects
|
|
807
845
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
808
|
-
PS_SUB=$(find . -maxdepth 2 -name "*.ps1" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
846
|
+
PS_SUB=$(find . -maxdepth 2 -name "*.ps1" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
809
847
|
if [ -n "$PS_SUB" ]; then
|
|
810
848
|
PROJECT_LANGS="powershell"
|
|
811
849
|
PROJECT_SUBDIR=$(dirname "$PS_SUB")
|
|
@@ -830,7 +868,7 @@ CHANGEDFILELIST
|
|
|
830
868
|
|
|
831
869
|
# ---- Documentation-only fallback ----
|
|
832
870
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
833
|
-
CODE_FILES=$(find . -type f \( -name "*.py" -o -name "*.js" -o -name "*.ts" -o -name "*.tsx" -o -name "*.java" -o -name "*.go" -o -name "*.rs" -o -name "*.cpp" -o -name "*.c" -o -name "*.swift" -o -name "*.kt" -o -name "*.sh" -o -name "*.dart" -o -name "*.ps1" \) -not -path "./.git/*" 2>/dev/null | wc -l)
|
|
871
|
+
CODE_FILES=$(find . -type f \( -name "*.py" -o -name "*.js" -o -name "*.ts" -o -name "*.tsx" -o -name "*.java" -o -name "*.go" -o -name "*.rs" -o -name "*.cpp" -o -name "*.c" -o -name "*.swift" -o -name "*.kt" -o -name "*.sh" -o -name "*.dart" -o -name "*.ps1" \) -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | wc -l)
|
|
834
872
|
|
|
835
873
|
if [ "$CODE_FILES" -eq 0 ]; then
|
|
836
874
|
PROJECT_LANGS="documentation-only"
|
|
@@ -1332,7 +1370,66 @@ else
|
|
|
1332
1370
|
echo "✅ PASSED - Basic shell script syntax check."
|
|
1333
1371
|
fi
|
|
1334
1372
|
;;
|
|
1335
|
-
|
|
1373
|
+
|
|
1374
|
+
java)
|
|
1375
|
+
# GATE 1 Java: wc-java-lint with fail-closed verdicts (#507 S1, DR-001).
|
|
1376
|
+
# java.sh run_static_analysis verdict codes:
|
|
1377
|
+
# 0=PASS 1=FAIL(violations) 2=BLOCK(execution error) 3=WARN(non-blocking).
|
|
1378
|
+
# Every non-0/1/2 outcome is scored as WARN -- IN the denominator, so it
|
|
1379
|
+
# forbids an overall PASS. Delphi round-2 A-MAJOR-1: these paths used to
|
|
1380
|
+
# map to SKIP, which sits OUT of the denominator, letting a repo with no
|
|
1381
|
+
# Java engine (or findings suppressed in report mode) report PASS/10.0
|
|
1382
|
+
# while its Java quality was never verified.
|
|
1383
|
+
# The adapter applies the XP_GATE_WC_JAVA_LINT=soft escape valve itself, so
|
|
1384
|
+
# a 2 reaching this branch is hard: BLOCK the commit. Captured to a var
|
|
1385
|
+
# (not piped) so the verdict code survives regardless of output length --
|
|
1386
|
+
# `cmd | head` makes a long report die with SIGPIPE, which would turn a
|
|
1387
|
+
# real verdict into an accident of line count.
|
|
1388
|
+
ADAPTER_FILE=$(resolve_adapter_path "$CURRENT_LANG")
|
|
1389
|
+
if [ -z "$ADAPTER_FILE" ] || ! source "$ADAPTER_FILE" 2>/dev/null; then
|
|
1390
|
+
echo "⚠️ WARN - Java adapter not found (code quality NOT verified)"
|
|
1391
|
+
GATE_1_STATUS="WARN"
|
|
1392
|
+
GATE_1_DETAIL="java: adapter missing"
|
|
1393
|
+
else
|
|
1394
|
+
JAVA_G1_EXIT=0
|
|
1395
|
+
# `|| JAVA_G1_EXIT=$?` (not a bare assignment): under a set -e consumer a
|
|
1396
|
+
# non-zero verdict would abort the host hook before the mapping below.
|
|
1397
|
+
JAVA_G1_OUTPUT=$(run_static_analysis 2>&1) || JAVA_G1_EXIT=$?
|
|
1398
|
+
# SKIP/WARN reasons cross the command-substitution subshell as an
|
|
1399
|
+
# `XP_G1_REASON: ...` stdout line -- an `export` inside the adapter can
|
|
1400
|
+
# NEVER reach this process (#507 Delphi round-1 B-MAJOR-1: verified the
|
|
1401
|
+
# subshell discards the child env). Parse, then strip from display.
|
|
1402
|
+
JAVA_G1_REASON=$(printf '%s\n' "$JAVA_G1_OUTPUT" | sed -n 's/^XP_G1_REASON: //p' | head -1)
|
|
1403
|
+
if [ -n "$JAVA_G1_REASON" ]; then
|
|
1404
|
+
JAVA_G1_OUTPUT=$(printf '%s\n' "$JAVA_G1_OUTPUT" | grep -v '^XP_G1_REASON: ')
|
|
1405
|
+
fi
|
|
1406
|
+
[ -n "$JAVA_G1_OUTPUT" ] && echo "$JAVA_G1_OUTPUT" | head -40
|
|
1407
|
+
case "$JAVA_G1_EXIT" in
|
|
1408
|
+
0)
|
|
1409
|
+
echo "✅ PASSED - Java code quality (wc-java-lint)."
|
|
1410
|
+
;;
|
|
1411
|
+
1)
|
|
1412
|
+
echo "❌ BLOCKED - Java static analysis violations (fix or add suppressions)"
|
|
1413
|
+
exit 1
|
|
1414
|
+
;;
|
|
1415
|
+
2)
|
|
1416
|
+
echo "❌ BLOCKED - wc-java-lint unusable (fail-closed; unset XP_GATE_WC_JAVA_LINT=soft only if you accept the risk)"
|
|
1417
|
+
exit 1
|
|
1418
|
+
;;
|
|
1419
|
+
3)
|
|
1420
|
+
echo "⚠️ WARN - Java code quality NOT verified (engine unavailable or downgraded)"
|
|
1421
|
+
GATE_1_STATUS="WARN"
|
|
1422
|
+
GATE_1_DETAIL="java: ${JAVA_G1_REASON:-unavailable (warn)}"
|
|
1423
|
+
;;
|
|
1424
|
+
*)
|
|
1425
|
+
echo "⚠️ WARN - Java code quality NOT verified (unexpected verdict $JAVA_G1_EXIT)"
|
|
1426
|
+
GATE_1_STATUS="WARN"
|
|
1427
|
+
GATE_1_DETAIL="java: unexpected verdict $JAVA_G1_EXIT"
|
|
1428
|
+
;;
|
|
1429
|
+
esac
|
|
1430
|
+
fi
|
|
1431
|
+
;;
|
|
1432
|
+
|
|
1336
1433
|
*)
|
|
1337
1434
|
# Route to appropriate adapter for other languages
|
|
1338
1435
|
ADAPTER_FILE=$(resolve_adapter_path "$CURRENT_LANG")
|
|
@@ -1367,9 +1464,13 @@ else
|
|
|
1367
1464
|
done
|
|
1368
1465
|
fi
|
|
1369
1466
|
fi # End: if ! any_changed_files_match (Issue #312 skip)
|
|
1370
|
-
GATE_1_STATUS="PASS"
|
|
1467
|
+
GATE_1_STATUS="${GATE_1_STATUS:-PASS}"
|
|
1371
1468
|
GATE_1_TOOL="${PROJECT_LANGS}"
|
|
1372
|
-
|
|
1469
|
+
# The java branch may set GATE_1_STATUS=WARN + a GATE_1_DETAIL warn reason
|
|
1470
|
+
# (engine unavailable / soft or report downgrade); route it into the audit
|
|
1471
|
+
# trail via the S2 detail plumbing so a WARN verdict is diagnosable, not
|
|
1472
|
+
# silent. WARN stays IN the scoring denominator (Delphi round-2 A-MAJOR-1).
|
|
1473
|
+
record_gate_audit "gate-1" "code-quality" "$GATE_1_STATUS" "0" "$GATE_1_START" "${GATE_1_DETAIL:-}"
|
|
1373
1474
|
|
|
1374
1475
|
# ============================================================================
|
|
1375
1476
|
# GATE 2: Duplicate Code Detection (NEW gate)
|
|
@@ -2701,9 +2802,9 @@ else
|
|
|
2701
2802
|
# real source path stays unbudgeted. Exclude it and let the mirroring be
|
|
2702
2803
|
# governed by Mirror Parity in CI, which compares content rather than
|
|
2703
2804
|
# re-linting it. (See the same exclusion in adapters/powershell.sh.)
|
|
2704
|
-
MIRROR_EXCLUDE=
|
|
2705
|
-
NEW_FILES=$(git diff --cached --name-only --diff-filter=A | grep -v "$MIRROR_EXCLUDE" | tr '\n' ' ')
|
|
2706
|
-
MODIFIED_FILES=$(git diff --cached --name-only --diff-filter=M | grep -v "$MIRROR_EXCLUDE" | tr '\n' ' ')
|
|
2805
|
+
MIRROR_EXCLUDE="$MIRROR_PATH_FILTER"
|
|
2806
|
+
NEW_FILES=$(git diff --cached --name-only --diff-filter=A | grep -v "$MIRROR_EXCLUDE" | grep -v "$DASHBOARD_PATH_FILTER" | tr '\n' ' ')
|
|
2807
|
+
MODIFIED_FILES=$(git diff --cached --name-only --diff-filter=M | grep -v "$MIRROR_EXCLUDE" | grep -v "$DASHBOARD_PATH_FILTER" | tr '\n' ' ')
|
|
2707
2808
|
|
|
2708
2809
|
if [ -z "$NEW_FILES" ] && [ -z "$MODIFIED_FILES" ]; then
|
|
2709
2810
|
echo " ✅ No new or modified files to check."
|
|
@@ -2794,9 +2895,73 @@ fi
|
|
|
2794
2895
|
# ============================================================================
|
|
2795
2896
|
# GATE 9: Build Integrity Check (TypeScript compilation + package + imports)
|
|
2796
2897
|
# ============================================================================
|
|
2898
|
+
|
|
2899
|
+
# Java build-integrity dispatch (#507 S5, DR-003). The old gate only knew
|
|
2900
|
+
# TypeScript: a Maven project got a permanent SKIP -- "not a TypeScript
|
|
2901
|
+
# project" -- which is exactly the silent-no-coverage path this sprint
|
|
2902
|
+
# removes. Verdict codes (mirror of the java.sh G1 contract):
|
|
2903
|
+
# 0=PASS 2=BLOCK(compile failed) 3=SKIP+WARN(reason printed)
|
|
2904
|
+
resolve_build_integrity_timeout() {
|
|
2905
|
+
local file=".xp-gate/build-integrity-timeout" val=""
|
|
2906
|
+
if [ -f "$file" ]; then
|
|
2907
|
+
val=$(tr -d '[:space:]' < "$file")
|
|
2908
|
+
fi
|
|
2909
|
+
if [ -z "$val" ]; then
|
|
2910
|
+
echo "180"
|
|
2911
|
+
return 0
|
|
2912
|
+
fi
|
|
2913
|
+
case "$val" in
|
|
2914
|
+
''|*[!0-9]*|0)
|
|
2915
|
+
echo "WARN - invalid $file value '${val}' (expected a positive integer), falling back to 180s" >&2
|
|
2916
|
+
echo "180"
|
|
2917
|
+
;;
|
|
2918
|
+
*) echo "$val" ;;
|
|
2919
|
+
esac
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
run_java_build_integrity() {
|
|
2923
|
+
local timeout_s build_bin
|
|
2924
|
+
# Wrapper first: mvnw pins the project's own Maven version.
|
|
2925
|
+
if [ -x "./mvnw" ]; then
|
|
2926
|
+
build_bin="./mvnw"
|
|
2927
|
+
elif command -v mvn >/dev/null 2>&1; then
|
|
2928
|
+
build_bin="mvn"
|
|
2929
|
+
else
|
|
2930
|
+
echo " ⚠️ Java changes staged but no Maven build tool found (mvnw/mvn)"
|
|
2931
|
+
echo " ⏭️ SKIPPED - Build integrity Java (no build tool)"
|
|
2932
|
+
return 3
|
|
2933
|
+
fi
|
|
2934
|
+
|
|
2935
|
+
timeout_s=$(resolve_build_integrity_timeout)
|
|
2936
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
2937
|
+
timeout "$timeout_s" "$build_bin" -q -DskipTests compile 2>&1 | tail -20
|
|
2938
|
+
else
|
|
2939
|
+
"$build_bin" -q -DskipTests compile 2>&1 | tail -20
|
|
2940
|
+
fi
|
|
2941
|
+
local rc="${PIPESTATUS[0]}"
|
|
2942
|
+
if [ "$rc" -eq 0 ]; then
|
|
2943
|
+
echo " ✅ Java build integrity verified (mvn -q -DskipTests compile)."
|
|
2944
|
+
return 0
|
|
2945
|
+
fi
|
|
2946
|
+
if [ "$rc" -eq 124 ]; then
|
|
2947
|
+
echo " ⏱️ Java build timed out after ${timeout_s}s."
|
|
2948
|
+
echo " ⏭️ SKIPPED - Build integrity Java (timeout)"
|
|
2949
|
+
return 3
|
|
2950
|
+
fi
|
|
2951
|
+
echo " ❌ Java build failed (exit $rc)"
|
|
2952
|
+
return 2
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2797
2955
|
2>&1 echo ""
|
|
2798
2956
|
2>&1 echo "→ Gate 9: Build integrity... "
|
|
2799
2957
|
GATE_9_START=$(gate_start_ms)
|
|
2958
|
+
# Sub-status aggregation (#507 Delphi round-1 B-MAJOR-4): each half records
|
|
2959
|
+
# its own outcome; GATE_9_STATUS is aggregated after both. The previous
|
|
2960
|
+
# single-variable design made a Maven-only repo (TS half never runs, Java
|
|
2961
|
+
# half passes) record an ETERNAL SKIP -- a real compile verification that
|
|
2962
|
+
# never appeared as PASS, contradicting S5's own motivation.
|
|
2963
|
+
GATE_9_TS_STATUS="SKIP"
|
|
2964
|
+
GATE_9_JAVA_STATUS="SKIP"
|
|
2800
2965
|
GATE_9_STATUS="SKIP"
|
|
2801
2966
|
|
|
2802
2967
|
if [ -f "package.json" ] && [ -f "tsconfig.json" ]; then
|
|
@@ -2813,15 +2978,15 @@ if [ -f "package.json" ] && [ -f "tsconfig.json" ]; then
|
|
|
2813
2978
|
CHANGED_FILES_CSV=$(echo "$CHANGED_FILES" | tr ' ' ',')
|
|
2814
2979
|
GATE_9_OUTPUT=$(mktemp)
|
|
2815
2980
|
if RUN_TSX_TIMEOUT=120s run_tsx "$GATE_9_SCRIPT" --changed-files "$CHANGED_FILES_CSV" --project-root "$PROJECT_ROOT" --timeout 115000 > "$GATE_9_OUTPUT" 2>&1; then
|
|
2816
|
-
|
|
2981
|
+
GATE_9_TS_STATUS="PASS"
|
|
2817
2982
|
echo " ✅ Build integrity check passed."
|
|
2818
2983
|
else
|
|
2819
2984
|
GATE_9_EXIT=$?
|
|
2820
2985
|
if [ $GATE_9_EXIT -eq 124 ]; then
|
|
2821
|
-
|
|
2986
|
+
GATE_9_TS_STATUS="SKIP"
|
|
2822
2987
|
echo " ⏱️ Build integrity check timed out (120s). SKIPPED."
|
|
2823
2988
|
else
|
|
2824
|
-
|
|
2989
|
+
GATE_9_TS_STATUS="BLOCK"
|
|
2825
2990
|
cat "$GATE_9_OUTPUT"
|
|
2826
2991
|
echo ""
|
|
2827
2992
|
echo "❌ BLOCKED - Gate 9: Build integrity check failed"
|
|
@@ -2840,6 +3005,52 @@ else
|
|
|
2840
3005
|
echo " ⏭️ SKIPPED - Build integrity (not a TypeScript project)"
|
|
2841
3006
|
fi
|
|
2842
3007
|
|
|
3008
|
+
# Java dispatch (#507 S5): runs when .java files are staged, regardless of
|
|
3009
|
+
# whether the TypeScript half ran. Gradle-only repos SKIP+WARN (gradle
|
|
3010
|
+
# support is backlog; DR-003).
|
|
3011
|
+
if echo "$CHANGED_FILES" | grep -q '\.java$'; then
|
|
3012
|
+
if [ -f "pom.xml" ]; then
|
|
3013
|
+
JAVA_BI_EXIT=0
|
|
3014
|
+
run_java_build_integrity || JAVA_BI_EXIT=$?
|
|
3015
|
+
case "$JAVA_BI_EXIT" in
|
|
3016
|
+
0)
|
|
3017
|
+
GATE_9_JAVA_STATUS="PASS"
|
|
3018
|
+
;;
|
|
3019
|
+
2)
|
|
3020
|
+
GATE_9_JAVA_STATUS="BLOCK"
|
|
3021
|
+
echo ""
|
|
3022
|
+
echo "❌ BLOCKED - Gate 9: Java build integrity check failed"
|
|
3023
|
+
echo "Fix the compile errors before committing."
|
|
3024
|
+
exit 1
|
|
3025
|
+
;;
|
|
3026
|
+
*)
|
|
3027
|
+
GATE_9_JAVA_STATUS="SKIP"
|
|
3028
|
+
echo " ⏭️ SKIPPED - Build integrity Java (see reason above)"
|
|
3029
|
+
;;
|
|
3030
|
+
esac
|
|
3031
|
+
elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
|
|
3032
|
+
echo " ⚠️ Java changes staged but this is a Gradle project - build integrity for Gradle is not implemented yet"
|
|
3033
|
+
echo " ⏭️ SKIPPED - Build integrity Java (Gradle backlog, #507 DR-003)"
|
|
3034
|
+
else
|
|
3035
|
+
echo " ℹ️ Java changes staged but no Maven/Gradle build file - skipping Java build integrity"
|
|
3036
|
+
fi
|
|
3037
|
+
fi
|
|
3038
|
+
|
|
3039
|
+
# Aggregate the two halves (#507 Delphi round-1 B-MAJOR-4): PASS when every
|
|
3040
|
+
# half that RAN passed (TS PASS + Java SKIP, or vice versa, both count),
|
|
3041
|
+
# SKIP only when NOTHING ran, BLOCK on any failure (normally unreachable --
|
|
3042
|
+
# failing halves exit 1 inline). This replaces the blind-review MINOR-1
|
|
3043
|
+
# single-variable guard: with explicit sub-statuses a Java PASS no longer
|
|
3044
|
+
# "resurrects" a TS SKIP -- each half keeps its own record and the gate
|
|
3045
|
+
# reflects the union of what was actually verified.
|
|
3046
|
+
GATE_9_STATUS="SKIP"
|
|
3047
|
+
if [ "$GATE_9_TS_STATUS" = "PASS" ] || [ "$GATE_9_JAVA_STATUS" = "PASS" ]; then
|
|
3048
|
+
GATE_9_STATUS="PASS"
|
|
3049
|
+
fi
|
|
3050
|
+
if [ "$GATE_9_TS_STATUS" = "BLOCK" ] || [ "$GATE_9_JAVA_STATUS" = "BLOCK" ]; then
|
|
3051
|
+
GATE_9_STATUS="BLOCK"
|
|
3052
|
+
fi
|
|
3053
|
+
|
|
2843
3054
|
GATE_9_END=$(gate_start_ms)
|
|
2844
3055
|
GATE_9_DURATION_MS=$((GATE_9_END - GATE_9_START))
|
|
2845
3056
|
GATE_9_DURATION=$((GATE_9_DURATION_MS / 1000))
|
|
@@ -2872,7 +3083,7 @@ fi
|
|
|
2872
3083
|
# Validates sprint state consistency (delphi-review APPROVED before BUILD)
|
|
2873
3084
|
# Uses sprint-gate.sh for standalone validation logic
|
|
2874
3085
|
# ============================================================================
|
|
2875
|
-
GATE_11_START=$(
|
|
3086
|
+
GATE_11_START=$(gate_start_ms)
|
|
2876
3087
|
GATE_11_STATUS="PASS"
|
|
2877
3088
|
|
|
2878
3089
|
# BEGIN SPRINT_GATE_SCRIPT RESOLUTION
|
|
@@ -2961,24 +3172,72 @@ generate_quality_report() {
|
|
|
2961
3172
|
local BRANCH
|
|
2962
3173
|
local PASSED_COUNT=0
|
|
2963
3174
|
local TOTAL_GATES=12
|
|
3175
|
+
local SKIP_COUNT=0
|
|
3176
|
+
local WARN_COUNT=0
|
|
3177
|
+
local NONPASS_COUNT=0
|
|
2964
3178
|
|
|
2965
3179
|
COMMIT_HASH=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
2966
3180
|
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
2967
3181
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2968
3182
|
|
|
3183
|
+
# Scoring per #507 S3: SKIP removes a gate from the denominator (a missing
|
|
3184
|
+
# tool must not drag the score down -- that was the "honest but punishing"
|
|
3185
|
+
# old behavior), WARN stays in the denominator but never counts as passed,
|
|
3186
|
+
# and FAIL/BLOCK count as non-passing items like before. NONPASS tracks
|
|
3187
|
+
# non-passing gates INSIDE the effective denominator only -- a skipped gate
|
|
3188
|
+
# is out of scope, not a failure.
|
|
3189
|
+
local gate_status gate_default
|
|
2969
3190
|
for gate in 1 2 3 4 5 6 7 8 9 10 11 12; do
|
|
2970
3191
|
local status_var="GATE_${gate}_STATUS"
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3192
|
+
# Default mirrors the JSON report rendering below EXACTLY (gates 1-11
|
|
3193
|
+
# PASS, gate 12 WARN): a gate that never assigned a status is "not
|
|
3194
|
+
# applicable" and must render and score identically. A previous scoring
|
|
3195
|
+
# default of FAIL produced self-contradictory reports -- the gate showed
|
|
3196
|
+
# PASS in JSON but scored as FAIL (#507 Delphi round-1 A-MAJOR-2).
|
|
3197
|
+
if [ "$gate" -eq 12 ]; then gate_default="WARN"; else gate_default="PASS"; fi
|
|
3198
|
+
gate_status="${!status_var:-$gate_default}"
|
|
3199
|
+
case "$gate_status" in
|
|
3200
|
+
PASS) PASSED_COUNT=$((PASSED_COUNT + 1)) ;;
|
|
3201
|
+
SKIP) SKIP_COUNT=$((SKIP_COUNT + 1)) ;;
|
|
3202
|
+
WARN) WARN_COUNT=$((WARN_COUNT + 1)); NONPASS_COUNT=$((NONPASS_COUNT + 1)) ;;
|
|
3203
|
+
*) NONPASS_COUNT=$((NONPASS_COUNT + 1)) ;;
|
|
3204
|
+
esac
|
|
2974
3205
|
done
|
|
2975
3206
|
|
|
3207
|
+
local EFFECTIVE_TOTAL=$((TOTAL_GATES - SKIP_COUNT))
|
|
3208
|
+
local VERDICT="PASS"
|
|
3209
|
+
if [ "$EFFECTIVE_TOTAL" -eq 0 ]; then
|
|
3210
|
+
VERDICT="SKIP-ALL" # every gate skipped: never report a fake 10/10
|
|
3211
|
+
elif [ "$NONPASS_COUNT" -gt 0 ]; then
|
|
3212
|
+
if [ "$((NONPASS_COUNT - WARN_COUNT))" -gt 0 ]; then
|
|
3213
|
+
VERDICT="FAIL" # at least one FAIL/BLOCK among the effective gates
|
|
3214
|
+
else
|
|
3215
|
+
VERDICT="WARN" # all effective gates passed or warned; warns forbid PASS
|
|
3216
|
+
fi
|
|
3217
|
+
fi
|
|
3218
|
+
|
|
2976
3219
|
local SCORE=0
|
|
2977
|
-
if
|
|
2978
|
-
SCORE
|
|
3220
|
+
if [ "$EFFECTIVE_TOTAL" -eq 0 ]; then
|
|
3221
|
+
SCORE="N/A"
|
|
2979
3222
|
else
|
|
2980
|
-
|
|
2981
|
-
|
|
3223
|
+
# Single awk path (#507 Delphi round-1 B-MAJOR-2): bc's scale=1 truncates
|
|
3224
|
+
# the DIVISION before multiplying (8/9 -> 0.8, *10 -> 8.0) while awk
|
|
3225
|
+
# rounds the final result (8.9) -- the same commit scored differently on
|
|
3226
|
+
# machines with and without bc. awk(1) is POSIX-guaranteed: one code path,
|
|
3227
|
+
# one score.
|
|
3228
|
+
SCORE=$(awk "BEGIN {printf \"%.1f\", ($PASSED_COUNT / $EFFECTIVE_TOTAL) * 10}")
|
|
3229
|
+
fi
|
|
3230
|
+
local EFFECTIVE_PASS_RATE="N/A"
|
|
3231
|
+
if [ "$EFFECTIVE_TOTAL" -gt 0 ]; then
|
|
3232
|
+
EFFECTIVE_PASS_RATE=$(awk "BEGIN {printf \"%.1f\", ($PASSED_COUNT / $EFFECTIVE_TOTAL) * 100}")
|
|
3233
|
+
fi
|
|
3234
|
+
# JSON bodies need a number or null -- a literal N/A breaks every consumer.
|
|
3235
|
+
local SCORE_JSON="$SCORE"
|
|
3236
|
+
[ "$SCORE_JSON" = "N/A" ] && SCORE_JSON="null"
|
|
3237
|
+
# Same for the pass rate: numeric like `score`, not a quoted string --
|
|
3238
|
+
# mixed types in one object broke consumers (#507 Delphi round-1 B-minor-6).
|
|
3239
|
+
local EFFECTIVE_PASS_RATE_JSON="$EFFECTIVE_PASS_RATE"
|
|
3240
|
+
[ "$EFFECTIVE_PASS_RATE_JSON" = "N/A" ] && EFFECTIVE_PASS_RATE_JSON="null"
|
|
2982
3241
|
|
|
2983
3242
|
local BS_PASSED=0
|
|
2984
3243
|
local BS_BLOCKED=0
|
|
@@ -3018,8 +3277,11 @@ generate_quality_report() {
|
|
|
3018
3277
|
"overall": {
|
|
3019
3278
|
"gatesPassed": $PASSED_COUNT,
|
|
3020
3279
|
"gatesTotal": $TOTAL_GATES,
|
|
3021
|
-
"
|
|
3022
|
-
"
|
|
3280
|
+
"gatesSkipped": $SKIP_COUNT,
|
|
3281
|
+
"gatesEffective": $EFFECTIVE_TOTAL,
|
|
3282
|
+
"effectivePassRate": $EFFECTIVE_PASS_RATE_JSON,
|
|
3283
|
+
"score": $SCORE_JSON,
|
|
3284
|
+
"verdict": "$VERDICT"
|
|
3023
3285
|
},
|
|
3024
3286
|
"gates": {
|
|
3025
3287
|
"gate1_static_analysis": {
|
|
@@ -3114,7 +3376,7 @@ ENDJSON
|
|
|
3114
3376
|
GATES_JSON="${GATES_JSON}\"gate11\":{\"status\":\"${GATE_11_STATUS:-PASS}\",\"name\":\"Sprint Flow\"},"
|
|
3115
3377
|
GATES_JSON="${GATES_JSON}\"gate12\":{\"status\":\"${GATE_12_STATUS:-WARN}\",\"name\":\"File Hygiene\"}}"
|
|
3116
3378
|
|
|
3117
|
-
echo "{\"timestamp\":\"$TIMESTAMP\",\"commit\":\"$COMMIT_HASH\",\"branch\":\"$BRANCH\",\"score\":$
|
|
3379
|
+
echo "{\"timestamp\":\"$TIMESTAMP\",\"commit\":\"$COMMIT_HASH\",\"branch\":\"$BRANCH\",\"score\":$SCORE_JSON,\"passed\":$PASSED_COUNT,\"total\":$TOTAL_GATES,\"effectiveTotal\":$EFFECTIVE_TOTAL,\"effectivePassRate\":$EFFECTIVE_PASS_RATE_JSON,\"skipCount\":$SKIP_COUNT,\"verdict\":\"$VERDICT\",\"gates\":$GATES_JSON,\"coverage\":\"$COV_PCT\",\"complexityWarnings\":${CC_WARNINGS:-0},\"principleWarnings\":${WARNING_COUNT:-0},\"boyScoutBlocked\":${BS_BLOCKED}}" >> "$HISTORY_FILE"
|
|
3118
3380
|
|
|
3119
3381
|
# ── Console output ────────────────────────────────────────────────────────
|
|
3120
3382
|
echo ""
|
|
@@ -3135,7 +3397,7 @@ ENDJSON
|
|
|
3135
3397
|
printf " %-45s %s\n" "Gate 11: Sprint Flow" "${GATE_11_STATUS:-PASS}"
|
|
3136
3398
|
printf " %-45s %s\n" "Gate 12: File Hygiene" "${GATE_12_STATUS:-WARN}"
|
|
3137
3399
|
echo ""
|
|
3138
|
-
echo " Overall Score: $SCORE/10 | $PASSED_COUNT/$TOTAL_GATES gates passed"
|
|
3400
|
+
echo " Overall Score: $SCORE/10 | $PASSED_COUNT/$TOTAL_GATES gates passed (effective: $PASSED_COUNT/$EFFECTIVE_TOTAL, skipped: $SKIP_COUNT, verdict: $VERDICT)"
|
|
3139
3401
|
echo " Branch status: $REPORT_FILE"
|
|
3140
3402
|
echo " Run report: $PERSISTED_REPORT_FILE"
|
|
3141
3403
|
echo " History saved: $HISTORY_FILE"
|